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