← ziglang.inraylib-zigGitHub

rlgl compute

★★★★by Teddy Astie (@tsnake41)

There is no browser build of this example yet.

Is it the same as C?

//! raylib [shaders] example - rlgl compute
//!
//! Zig-API translation of examples/shaders/shaders_rlgl_compute.c (raylib 6.2).
//! Example contributed by Teddy Astie (@tsnake41) and reviewed by Ramon Santamaria (@raysan5)
//! Example licensed under an unmodified zlib/libpng license.
//! Copyright (c) 2021-2025 Teddy Astie (@tsnake41)

const std = @import("std");
const rl = @import("raylib");

// IMPORTANT: This must match gol*.glsl GOL_WIDTH constant
// This must be a multiple of 16 (check golLogic compute dispatch)
const gol_width = 768;

// Maximum amount of queued draw commands (squares draw from mouse down events)
const max_buffered_transferts = 48;

/// Game Of Life Update Command
const GolUpdateCmd = extern struct {
    x: u32, // x coordinate of the gol command
    y: u32, // y coordinate of the gol command
    w: u32, // width of the filled zone
    enabled: u32, // whether to enable or disable zone
};

/// Game Of Life Update Commands SSBO
const GolUpdateSSBO = extern struct {
    count: u32 = 0,
    commands: [max_buffered_transferts]GolUpdateCmd = std.mem.zeroes([max_buffered_transferts]GolUpdateCmd),
};

/// Load a compute shader program from a GLSL file; returns the shader and program ids.
fn loadComputeProgram(file_name: [:0]const u8) !struct { shader: u32, program: u32 } {
    const code = try rl.loadFileText(file_name);
    defer rl.unloadFileText(code);
    const shader = rl.gl.loadShader(code, rl.gl.compute_shader);
    return .{ .shader = shader, .program = rl.gl.loadShaderProgramCompute(shader) };
}

pub fn main() !void {
    // Initialization
    const screen_width = gol_width;
    const screen_height = gol_width;

    rl.initWindow(screen_width, screen_height, "raylib [shaders] example - rlgl compute");
    defer rl.closeWindow(); // Close window and OpenGL context

    const resolution: rl.Vector2 = .{ .x = screen_width, .y = screen_height };
    var brush_size: u32 = 8;

    // Game of Life logic compute shader
    const gol_logic = try loadComputeProgram("resources/shaders/glsl430/gol.glsl");
    defer rl.gl.unloadShaderProgram(gol_logic.program);
    defer rl.gl.unloadShader(gol_logic.shader);

    // Game of Life logic render shader
    const gol_render_shader = try rl.loadShader(null, "resources/shaders/glsl430/gol_render.glsl");
    defer rl.unloadShader(gol_render_shader); // Unload rendering fragment shader
    const res_uniform_loc = rl.getShaderLocation(gol_render_shader, "resolution");

    // Game of Life transfert shader (CPU<->GPU download and upload)
    const gol_transfert = try loadComputeProgram("resources/shaders/glsl430/gol_transfert.glsl");
    defer rl.gl.unloadShaderProgram(gol_transfert.program);
    defer rl.gl.unloadShader(gol_transfert.shader);

    // Load shader storage buffer object (SSBO), id returned
    var ssbo_a = rl.gl.loadShaderBuffer(gol_width * gol_width * @sizeOf(u32), null, rl.gl.dynamic_copy);
    var ssbo_b = rl.gl.loadShaderBuffer(gol_width * gol_width * @sizeOf(u32), null, rl.gl.dynamic_copy);
    const ssbo_transfert = rl.gl.loadShaderBuffer(@sizeOf(GolUpdateSSBO), null, rl.gl.dynamic_copy);
    // Unload shader buffers objects
    defer rl.gl.unloadShaderBuffer(ssbo_transfert);
    defer rl.gl.unloadShaderBuffer(ssbo_b);
    defer rl.gl.unloadShaderBuffer(ssbo_a);

    var transfert_buffer: GolUpdateSSBO = .{};

    // Create a white texture of the size of the window to update
    // each pixel of the window using the fragment shader: golRenderShader
    const white_image = rl.genImageColor(gol_width, gol_width, .white);
    const white_tex = try rl.loadTextureFromImage(white_image);
    defer rl.unloadTexture(white_tex); // Unload white texture
    rl.unloadImage(white_image);

    rl.setTargetFPS(0); // Set our game to run with an uncapped framerate

    // Main game loop
    while (!rl.windowShouldClose()) {
        // Update
        brush_size +%= @bitCast(@as(i32, @intFromFloat(rl.getMouseWheelMove())));
        const half_brush: i32 = @bitCast(brush_size / 2);

        if ((rl.isMouseButtonDown(.left) or rl.isMouseButtonDown(.right)) and
            transfert_buffer.count < max_buffered_transferts)
        {
            // Buffer a new command
            transfert_buffer.commands[transfert_buffer.count] = .{
                .x = @bitCast(rl.getMouseX() -% half_brush),
                .y = @bitCast(rl.getMouseY() -% half_brush),
                .w = brush_size,
                .enabled = @intFromBool(rl.isMouseButtonDown(.left)),
            };
            transfert_buffer.count += 1;
        } else if (transfert_buffer.count > 0) { // Process transfert buffer
            // Send SSBO buffer to GPU
            rl.gl.updateShaderBuffer(ssbo_transfert, std.mem.asBytes(&transfert_buffer), 0);

            // Process SSBO commands on GPU
            rl.gl.enableShader(gol_transfert.program);
            rl.gl.bindShaderBuffer(ssbo_a, 1);
            rl.gl.bindShaderBuffer(ssbo_transfert, 3);
            rl.gl.computeShaderDispatch(transfert_buffer.count, 1, 1); // Each GPU unit will process a command!
            rl.gl.disableShader();

            transfert_buffer.count = 0;
        } else {
            // Process game of life logic
            rl.gl.enableShader(gol_logic.program);
            rl.gl.bindShaderBuffer(ssbo_a, 1);
            rl.gl.bindShaderBuffer(ssbo_b, 2);
            rl.gl.computeShaderDispatch(gol_width / 16, gol_width / 16, 1);
            rl.gl.disableShader();

            // ssboA <-> ssboB
            std.mem.swap(u32, &ssbo_a, &ssbo_b);
        }

        rl.gl.bindShaderBuffer(ssbo_a, 1);
        rl.setShaderValue(gol_render_shader, res_uniform_loc, resolution);

        // Draw
        rl.beginDrawing();
        defer rl.endDrawing();

        rl.clearBackground(.blank);

        {
            rl.beginShaderMode(gol_render_shader);
            defer rl.endShaderMode();

            rl.drawTexture(white_tex, 0, 0, .white);
        }

        const brush: i32 = @bitCast(brush_size);
        rl.drawRectangleLines(rl.getMouseX() -% half_brush, rl.getMouseY() -% half_brush, brush, brush, .red);

        rl.drawText("Use Mouse wheel to increase/decrease brush size", 10, 10, 20, .white);
        rl.drawFPS(rl.getScreenWidth() - 100, 10);
    }
}
/*******************************************************************************************
*
*   raylib [shaders] example - rlgl compute
*
*   WARNING: This example requires raylib compiled with OpenGL 4.3 version for
*         compute shaders support, shaders used in this example are #version 430
*
*   Example complexity rating: [★★★★] 4/4
*
*   Example originally created with raylib 4.0, last time updated with raylib 4.0
*
*   Example contributed by Teddy Astie (@tsnake41) and reviewed by Ramon Santamaria (@raysan5)
*
*   Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
*   BSD-like license that allows static linking with closed source software
*
*   Copyright (c) 2021-2025 Teddy Astie (@tsnake41)
*
********************************************************************************************/

#include "raylib.h"

#include "rlgl.h"

#include <stdlib.h>     // Required for: NULL

// IMPORTANT: This must match gol*.glsl GOL_WIDTH constant
// This must be a multiple of 16 (check golLogic compute dispatch)
#define GOL_WIDTH 768

// Maximum amount of queued draw commands (squares draw from mouse down events)
#define MAX_BUFFERED_TRANSFERTS 48

//----------------------------------------------------------------------------------
// Types and Structures Definition
//----------------------------------------------------------------------------------
// Game Of Life Update Command
typedef struct GolUpdateCmd {
    unsigned int x;         // x coordinate of the gol command
    unsigned int y;         // y coordinate of the gol command
    unsigned int w;         // width of the filled zone
    unsigned int enabled;   // whether to enable or disable zone
} GolUpdateCmd;

// Game Of Life Update Commands SSBO
typedef struct GolUpdateSSBO {
    unsigned int count;
    GolUpdateCmd commands[MAX_BUFFERED_TRANSFERTS];
} GolUpdateSSBO;

//------------------------------------------------------------------------------------
// Program main entry point
//------------------------------------------------------------------------------------
int main(void)
{
    // Initialization
    //--------------------------------------------------------------------------------------
    const int screenWidth = GOL_WIDTH;
    const int screenHeight = GOL_WIDTH;

    InitWindow(screenWidth, screenHeight, "raylib [shaders] example - rlgl compute");

    const Vector2 resolution = { (float)screenWidth, (float)screenHeight };
    unsigned int brushSize = 8;

    // Game of Life logic compute shader
    char *golLogicCode = LoadFileText("resources/shaders/glsl430/gol.glsl");
    unsigned int golLogicShader = rlLoadShader(golLogicCode, RL_COMPUTE_SHADER);
    unsigned int golLogicProgram = rlLoadShaderProgramCompute(golLogicShader);
    UnloadFileText(golLogicCode);

    // Game of Life logic render shader
    Shader golRenderShader = LoadShader(NULL, "resources/shaders/glsl430/gol_render.glsl");
    int resUniformLoc = GetShaderLocation(golRenderShader, "resolution");

    // Game of Life transfert shader (CPU<->GPU download and upload)
    char *golTransfertCode = LoadFileText("resources/shaders/glsl430/gol_transfert.glsl");
    unsigned int golTransfertShader = rlLoadShader(golTransfertCode, RL_COMPUTE_SHADER);
    unsigned int golTransfertProgram = rlLoadShaderProgramCompute(golTransfertShader);
    UnloadFileText(golTransfertCode);

    // Load shader storage buffer object (SSBO), id returned
    unsigned int ssboA = rlLoadShaderBuffer(GOL_WIDTH*GOL_WIDTH*sizeof(unsigned int), NULL, RL_DYNAMIC_COPY);
    unsigned int ssboB = rlLoadShaderBuffer(GOL_WIDTH*GOL_WIDTH*sizeof(unsigned int), NULL, RL_DYNAMIC_COPY);
    unsigned int ssboTransfert = rlLoadShaderBuffer(sizeof(GolUpdateSSBO), NULL, RL_DYNAMIC_COPY);

    GolUpdateSSBO transfertBuffer = { 0 };

    // Create a white texture of the size of the window to update
    // each pixel of the window using the fragment shader: golRenderShader
    Image whiteImage = GenImageColor(GOL_WIDTH, GOL_WIDTH, WHITE);
    Texture whiteTex = LoadTextureFromImage(whiteImage);
    UnloadImage(whiteImage);

    SetTargetFPS(0);                    // Set our game to run with an uncapped framerate
    //--------------------------------------------------------------------------------------

    // Main game loop
    while (!WindowShouldClose())
    {
        // Update
        //----------------------------------------------------------------------------------
        brushSize += (int)GetMouseWheelMove();

        if ((IsMouseButtonDown(MOUSE_BUTTON_LEFT) || IsMouseButtonDown(MOUSE_BUTTON_RIGHT))
            && (transfertBuffer.count < MAX_BUFFERED_TRANSFERTS))
        {
            // Buffer a new command
            transfertBuffer.commands[transfertBuffer.count].x = GetMouseX() - brushSize/2;
            transfertBuffer.commands[transfertBuffer.count].y = GetMouseY() - brushSize/2;
            transfertBuffer.commands[transfertBuffer.count].w = brushSize;
            transfertBuffer.commands[transfertBuffer.count].enabled = IsMouseButtonDown(MOUSE_BUTTON_LEFT);
            transfertBuffer.count++;
        }
        else if (transfertBuffer.count > 0)  // Process transfert buffer
        {
            // Send SSBO buffer to GPU
            rlUpdateShaderBuffer(ssboTransfert, &transfertBuffer, sizeof(GolUpdateSSBO), 0);

            // Process SSBO commands on GPU
            rlEnableShader(golTransfertProgram);
            rlBindShaderBuffer(ssboA, 1);
            rlBindShaderBuffer(ssboTransfert, 3);
            rlComputeShaderDispatch(transfertBuffer.count, 1, 1); // Each GPU unit will process a command!
            rlDisableShader();

            transfertBuffer.count = 0;
        }
        else
        {
            // Process game of life logic
            rlEnableShader(golLogicProgram);
            rlBindShaderBuffer(ssboA, 1);
            rlBindShaderBuffer(ssboB, 2);
            rlComputeShaderDispatch(GOL_WIDTH/16, GOL_WIDTH/16, 1);
            rlDisableShader();

            // ssboA <-> ssboB
            int temp = ssboA;
            ssboA = ssboB;
            ssboB = temp;
        }

        rlBindShaderBuffer(ssboA, 1);
        SetShaderValue(golRenderShader, resUniformLoc, &resolution, SHADER_UNIFORM_VEC2);
        //----------------------------------------------------------------------------------

        // Draw
        //----------------------------------------------------------------------------------
        BeginDrawing();

            ClearBackground(BLANK);

            BeginShaderMode(golRenderShader);
                DrawTexture(whiteTex, 0, 0, WHITE);
            EndShaderMode();

            DrawRectangleLines(GetMouseX() - brushSize/2, GetMouseY() - brushSize/2, brushSize, brushSize, RED);

            DrawText("Use Mouse wheel to increase/decrease brush size", 10, 10, 20, WHITE);
            DrawFPS(GetScreenWidth() - 100, 10);

        EndDrawing();
        //----------------------------------------------------------------------------------
    }

    // De-Initialization
    //--------------------------------------------------------------------------------------
    // Unload shader buffers objects
    rlUnloadShaderBuffer(ssboA);
    rlUnloadShaderBuffer(ssboB);
    rlUnloadShaderBuffer(ssboTransfert);

    // Unload compute shader
    rlUnloadShader(golLogicShader);
    rlUnloadShader(golTransfertShader);
    rlUnloadShaderProgram(golTransfertProgram);
    rlUnloadShaderProgram(golLogicProgram);

    UnloadTexture(whiteTex);            // Unload white texture
    UnloadShader(golRenderShader);      // Unload rendering fragment shader

    CloseWindow();                      // Close window and OpenGL context
    //--------------------------------------------------------------------------------------

    return 0;
}

From raylib 6.2. The original on GitHub.