easy-gl › Compute Shader
Compute Shader
Uses a compute shader to procedurally fill a texture, then renders it. Demonstrates dispatch_compute, image load/store, and memory_barrier.
Prerequisite check
device.require(easygl::Feature::ComputeShader); // throws if GL < 4.3
Compute shader source
const char* compSrc = R"(#version 430 core
layout (local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
layout (rgba8, binding = 0) uniform image2D uOutput;
uniform float uTime;
void main() {
ivec2 coord = ivec2(gl_GlobalInvocationID.xy);
ivec2 size = imageSize(uOutput);
if (coord.x >= size.x || coord.y >= size.y) return;
vec2 uv = (vec2(coord) + 0.5) / vec2(size);
float r = 0.5 + 0.5 * sin(uv.x * 20.0 + uTime);
float g = 0.5 + 0.5 * cos(uv.y * 20.0 + uTime * 0.7);
float b = 0.5 + 0.5 * sin((uv.x + uv.y) * 15.0 - uTime * 1.3);
imageStore(uOutput, coord, vec4(r, g, b, 1.0));
})";
Setup compute program
easygl::Shader compShader(easygl::ShaderType::Compute);
compShader.create();
compShader.compile_from_source(compSrc);
easygl::Program compProg;
compProg.create();
compProg.attach_owned(compShader);
compProg.link();
const int timeLoc = compProg.uniform_location("uTime");
Create the output texture
constexpr int TEX_W = 512, TEX_H = 512;
easygl::Texture outputTex;
outputTex.create();
outputTex.bind(easygl::TextureTarget::Texture2D);
outputTex.set_storage_2d(easygl::TextureTarget::Texture2D, 1,
easygl::InternalFormat::Rgba8, TEX_W, TEX_H);
Render loop — dispatch and display
while (running) {
const float time = static_cast<float>(SDL_GetTicks()) / 1000.0f;
// ---- Compute pass: fill texture ----
compProg.use();
compProg.set_uniform(timeLoc, time);
outputTex.bind_image(
0, // image unit 0
0, // mip level 0
false, // not layered
0, // layer
easygl::ImageAccess::WriteOnly,
easygl::InternalFormat::Rgba8);
// Dispatch: work groups of 8×8, covering 512×512 pixels
device.dispatch_compute(TEX_W / 8, TEX_H / 8, 1);
// Barrier: ensure imageStore writes are visible to texture sampling below
device.memory_barrier(easygl::MemoryBarrierMask::TextureFetch);
// ---- Render pass: display texture on fullscreen quad ----
easygl::Framebuffer::unbind();
device.set_viewport(0, 0, winW, winH);
device.clear(easygl::ClearFlags::Color);
displayProg.use();
outputTex.active_bind(easygl::TextureUnit::Texture0,
easygl::TextureTarget::Texture2D);
displayProg.set_uniform(displayProg.uniform_location("uTex"), 0);
fsVao.bind();
device.draw_arrays(easygl::PrimitiveType::Triangles, 0, 6);
SDL_GL_SwapWindow(window);
}
Key points
- Compute shaders require GL 4.3. Always
require(Feature::ComputeShader)before use. - The work group size (
local_size_x/y) is defined in the shader. Choose multiples of the warp/wavefront size (typically 32 or 64) for best performance. - Number of groups =
ceil(dimension / local_size). The guardif (coord >= size) return;in the shader handles the case where the dimension is not a multiple of the group size. memory_barrier(TextureFetch)must be inserted between the image store in the compute shader and the texture sample in the fragment shader.bind_imageis separate fromactive_bind— image units and texture units are distinct.