rlgl compute
There is no browser build of this example yet.
Is it the same as C?
- Desktop:verified The Zig version renders a final frame that is byte-identical to the C example.
- Browser:not checked yet The browser comparison does not cover this example yet.
//! 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.