Welcome to collectivesolver - Programming & Software Q&A with code examples. A website with trusted programming answers. All programs are tested and work.

Contact: aviboots(AT)netvision.net.il

Semrush - keyword research tool

Create your online store today with Shopify

Turn ChatGPT, Claude, Gemini, And CoPilot Into Your Personal Assistant, Business Coach, Content Creator, And More

AFFILIATE MARKETING Your all-in-one performance engine Manage affiliates, creators, and customer referrals in one unified platform—turning every partnership into measurable growth

Secure & Reliable Web Hosting, Free Domain, Free SSL, 1-Click WordPress Install, Expert 24/7 Support

Disclosure: My content contains affiliate links.

43,563 questions

56,781 answers

573 users

How to render a triangle using SDL_GPU in C++

1 Answer

0 votes
#define SDL_MAIN_USE_CALLBACKS
#include <SDL3/SDL_main.h>
#include <SDL3/SDL.h>

// Vertex Definition 
// ----------------- 

// A vertex contains a 3D position and an RGBA color.
// This matches the layout expected by the vertex shader.
struct Vertex
{
    float x, y, z;      // Position (vec3)
    float r, g, b, a;   // Color    (vec4)
};

// Three vertices forming a triangle.
// Each vertex has a unique color → fragment shader interpolates colors.
static Vertex vertices[]
{
    { 0.0f,  0.5f, 0.0f, 1, 0, 0, 1 },   // Top (red)
    {-0.5f, -0.5f, 0.0f, 1, 1, 0, 1 },   // Bottom-left (yellow)
    { 0.5f, -0.5f, 0.0f, 0, 0, 1, 1 }    // Bottom-right (blue)
};


SDL_Window* window;
SDL_GPUDevice* device;
SDL_GPUBuffer* vertexBuffer;
SDL_GPUTransferBuffer* transferBuffer;
SDL_GPUGraphicsPipeline* graphicsPipeline;

// SDL_AppInit — Initialization Phase 
// ---------------------------------- 

SDL_AppResult SDL_AppInit(void** appstate, int argc, char** argv)
{
    // create a window
    window = SDL_CreateWindow("SDL_GPU - Create Triangle", 960, 540, SDL_WINDOW_RESIZABLE);

    // Create GPU device using SPIR-V shaders
    device = SDL_CreateGPUDevice(SDL_GPU_SHADERFORMAT_SPIRV, true, NULL); // Vulkan - cross-platform

    // Attach window to GPU device (swapchain creation)
    SDL_ClaimWindowForGPUDevice(device, window);

    // Load & Create Shaders 
    // --------------------- 
    
    // Vertex Shader 
    // -------------
    // 
    // load the vertex shader code
    size_t vertexCodeSize;
    // compile the shader in commant prompt:
    // glslc -fshader-stage=vertex shaders/vertex.glsl -o shaders/vertex.spv
    void* vertexCode = SDL_LoadFile("shaders/vertex.spv", &vertexCodeSize);

    // create the vertex shader
    SDL_GPUShaderCreateInfo vertexInfo{};
    vertexInfo.code = (Uint8*)vertexCode;
    vertexInfo.code_size = vertexCodeSize;
    vertexInfo.entrypoint = "main"; // SPIR-V entry point
    vertexInfo.format = SDL_GPU_SHADERFORMAT_SPIRV;
    vertexInfo.stage = SDL_GPU_SHADERSTAGE_VERTEX;
    vertexInfo.num_samplers = 0;
    vertexInfo.num_storage_buffers = 0;
    vertexInfo.num_storage_textures = 0;
    vertexInfo.num_uniform_buffers = 0;

    SDL_GPUShader* vertexShader = SDL_CreateGPUShader(device, &vertexInfo);

    // free the file
    SDL_free(vertexCode);

    // Fragment Shader
    // ---------------

    // load the fragment shader code
    size_t fragmentCodeSize;
    // compile the shader in commant prompt:
    // glslc -fshader-stage=fragment shaders/fragment.glsl -o shaders/fragment.spv
    void* fragmentCode = SDL_LoadFile("shaders/fragment.spv", &fragmentCodeSize);

    // create the fragment shader
    SDL_GPUShaderCreateInfo fragmentInfo{};
    fragmentInfo.code = (Uint8*)fragmentCode;
    fragmentInfo.code_size = fragmentCodeSize;
    fragmentInfo.entrypoint = "main";
    fragmentInfo.format = SDL_GPU_SHADERFORMAT_SPIRV;
    fragmentInfo.stage = SDL_GPU_SHADERSTAGE_FRAGMENT;
    fragmentInfo.num_samplers = 0;
    fragmentInfo.num_storage_buffers = 0;
    fragmentInfo.num_storage_textures = 0;
    fragmentInfo.num_uniform_buffers = 0;

    SDL_GPUShader* fragmentShader = SDL_CreateGPUShader(device, &fragmentInfo);

    // free the file
    SDL_free(fragmentCode);

    // Create Graphics Pipeline
    // ------------------------
    
    // A pipeline describes:
    //    shaders
    //    vertex layout
    //    blending
    //    primitive type

    SDL_GPUGraphicsPipelineCreateInfo pipelineInfo{};
    pipelineInfo.vertex_shader = vertexShader;
    pipelineInfo.fragment_shader = fragmentShader;
    pipelineInfo.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST;

    // describe the vertex buffers
    SDL_GPUVertexBufferDescription vertexBufferDesctiptions[1];
    vertexBufferDesctiptions[0].slot = 0;
    vertexBufferDesctiptions[0].input_rate = SDL_GPU_VERTEXINPUTRATE_VERTEX;
    vertexBufferDesctiptions[0].instance_step_rate = 0;
    vertexBufferDesctiptions[0].pitch = sizeof(Vertex);

    pipelineInfo.vertex_input_state.num_vertex_buffers = 1;
    pipelineInfo.vertex_input_state.vertex_buffer_descriptions = vertexBufferDesctiptions;

    // describe the vertex attribute
    
    SDL_GPUVertexAttribute vertexAttributes[2];

    // a_position // Position attribute (vec3)
    vertexAttributes[0].buffer_slot = 0;
    vertexAttributes[0].location = 0;
    vertexAttributes[0].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT3;
    vertexAttributes[0].offset = 0;

    // a_color // Color attribute (vec4)
    vertexAttributes[1].buffer_slot = 0;
    vertexAttributes[1].location = 1;
    vertexAttributes[1].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT4;
    vertexAttributes[1].offset = sizeof(float) * 3;

    pipelineInfo.vertex_input_state.num_vertex_attributes = 2;
    pipelineInfo.vertex_input_state.vertex_attributes = vertexAttributes;

    // Color Target (swapchain)
    // ------------------------
    
    // describe the color target
    SDL_GPUColorTargetDescription colorTargetDescriptions[1];
    colorTargetDescriptions[0] = {};
    colorTargetDescriptions[0].blend_state.enable_blend = true;
    colorTargetDescriptions[0].blend_state.color_blend_op = SDL_GPU_BLENDOP_ADD;
    colorTargetDescriptions[0].blend_state.alpha_blend_op = SDL_GPU_BLENDOP_ADD;
    colorTargetDescriptions[0].blend_state.src_color_blendfactor = SDL_GPU_BLENDFACTOR_SRC_ALPHA;
    colorTargetDescriptions[0].blend_state.dst_color_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA;
    colorTargetDescriptions[0].blend_state.src_alpha_blendfactor = SDL_GPU_BLENDFACTOR_SRC_ALPHA;
    colorTargetDescriptions[0].blend_state.dst_alpha_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA;
    colorTargetDescriptions[0].format = SDL_GetGPUSwapchainTextureFormat(device, window);

    pipelineInfo.target_info.num_color_targets = 1;
    pipelineInfo.target_info.color_target_descriptions = colorTargetDescriptions;

    // create the pipeline
    graphicsPipeline = SDL_CreateGPUGraphicsPipeline(device, &pipelineInfo);

    // we don't need to store the shaders after creating the pipeline
    SDL_ReleaseGPUShader(device, vertexShader);
    SDL_ReleaseGPUShader(device, fragmentShader);

    // Create Vertex Buffer & Upload Data
    // ----------------------------------
    
    // Create GPU Buffer
    SDL_GPUBufferCreateInfo bufferInfo{};
    bufferInfo.size = sizeof(vertices);
    bufferInfo.usage = SDL_GPU_BUFFERUSAGE_VERTEX;
    vertexBuffer = SDL_CreateGPUBuffer(device, &bufferInfo);

    // create a transfer buffer (staging buffer) to upload to the vertex buffer
    SDL_GPUTransferBufferCreateInfo transferInfo{};
    transferInfo.size = sizeof(vertices);
    transferInfo.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD;
    transferBuffer = SDL_CreateGPUTransferBuffer(device, &transferInfo);

    // fill the transfer buffer
    Vertex* data = (Vertex*)SDL_MapGPUTransferBuffer(device, transferBuffer, false);

    SDL_memcpy(data, (void*)vertices, sizeof(vertices));

    // data[0] = vertices[0];
    // data[1] = vertices[1];
    // data[2] = vertices[2];

    SDL_UnmapGPUTransferBuffer(device, transferBuffer);

    // start a copy pass // Copy Transfer Buffer → Vertex Buffer
    SDL_GPUCommandBuffer* commandBuffer = SDL_AcquireGPUCommandBuffer(device);
    SDL_GPUCopyPass* copyPass = SDL_BeginGPUCopyPass(commandBuffer);

    // where is the data
    SDL_GPUTransferBufferLocation location{};
    location.transfer_buffer = transferBuffer;
    location.offset = 0;

    // where to upload the data
    SDL_GPUBufferRegion region{};
    region.buffer = vertexBuffer;
    region.size = sizeof(vertices);
    region.offset = 0;

    // upload the data
    SDL_UploadToGPUBuffer(copyPass, &location, &region, true);

    // end the copy pass
    SDL_EndGPUCopyPass(copyPass);
    SDL_SubmitGPUCommandBuffer(commandBuffer);

    return SDL_APP_CONTINUE;
}

// SDL_AppIterate — Per‑Frame Rendering
// ------------------------------------

SDL_AppResult SDL_AppIterate(void* appstate)
{
    // acquire the command buffer
    SDL_GPUCommandBuffer* commandBuffer = SDL_AcquireGPUCommandBuffer(device);

    // get the swapchain texture
    SDL_GPUTexture* swapchainTexture;
    Uint32 width, height;
    SDL_WaitAndAcquireGPUSwapchainTexture(commandBuffer, window, &swapchainTexture, &width, &height);

    // end the frame early if a swapchain texture is not available
    if (swapchainTexture == NULL)
    {
        // you must always submit the command buffer
        SDL_SubmitGPUCommandBuffer(commandBuffer);
        return SDL_APP_CONTINUE;
    }

    // create the color target
    SDL_GPUColorTargetInfo colorTargetInfo{};
    colorTargetInfo.clear_color = { 30 / 255.0f, 200 / 255.0f, 120 / 255.0f, 255 / 255.0f };
    colorTargetInfo.load_op = SDL_GPU_LOADOP_CLEAR;
    colorTargetInfo.store_op = SDL_GPU_STOREOP_STORE;
    colorTargetInfo.texture = swapchainTexture;

    // begin a render pass
    SDL_GPURenderPass* renderPass = SDL_BeginGPURenderPass(commandBuffer, &colorTargetInfo, 1, NULL);

    // draw calls start here // Issue Draw Call
    // -------------------------------------

    // bind the pipeline
    SDL_BindGPUGraphicsPipeline(renderPass, graphicsPipeline);

    // bind the vertex buffer
    SDL_GPUBufferBinding bufferBindings[1];
    bufferBindings[0].buffer = vertexBuffer;
    bufferBindings[0].offset = 0;

    SDL_BindGPUVertexBuffers(renderPass, 0, bufferBindings, 1);

    // issue a draw call // Draw 3 vertices
    SDL_DrawGPUPrimitives(renderPass, 3, 1, 0, 0);

    // ------------------------------------

    // Finish Frame
    // ------------
    
    // end the render pass
    SDL_EndGPURenderPass(renderPass);

    // submit the command buffer
    SDL_SubmitGPUCommandBuffer(commandBuffer);

    return SDL_APP_CONTINUE;
}

SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event)
{
    // close the window on request
    if (event->type == SDL_EVENT_WINDOW_CLOSE_REQUESTED)
    {
        return SDL_APP_SUCCESS;
    }

    return SDL_APP_CONTINUE;
}

// SDL_AppQuit — Cleanup
// ---------------------

void SDL_AppQuit(void* appstate, SDL_AppResult result)
{
    // release buffers
    SDL_ReleaseGPUBuffer(device, vertexBuffer);
    SDL_ReleaseGPUTransferBuffer(device, transferBuffer);

    // release the pipeline
    SDL_ReleaseGPUGraphicsPipeline(device, graphicsPipeline);

    // destroy the GPU device
    SDL_DestroyGPUDevice(device);

    // destroy the window
    SDL_DestroyWindow(window);
}

/*
1. Vertex Data Layout (CPU‑side)
This shows how your Vertex struct is interpreted by the GPU.

Explanation
Each vertex contains position (vec3) and color (vec4).

The GPU reads this memory using the vertex buffer description and vertex attributes you defined.

The offsets match your shader’s layout(location = 0) and layout(location = 1) inputs.

2. GPU Pipeline Structure
This shows the major components of the graphics pipeline SDL_GPU builds.

Key Stages
Vertex Shader
Transforms each vertex and passes color to the fragment shader.

Primitive Assembly
Groups vertices into triangles (you used TRIANGLELIST).

Rasterizer
Converts triangles into fragments (pixels).

Fragment Shader
Computes final pixel color using interpolated vertex colors.

Output Merger
Applies blending and writes to the swapchain texture.

3. Command Buffer & Render Pass Flow
This shows how SDL_GPU organizes rendering commands.

Flow
Acquire command buffer

Acquire swapchain texture

Begin render pass

Bind pipeline

Bind vertex buffer

Issue draw call

End render pass

Submit command buffer

SDL_GPU uses a modern API design similar to Vulkan/Metal/DX12.


4. Final Frame Rendering Path
This shows how the triangle ends up on the screen.

Steps
Vertex buffer → vertex shader

Triangle rasterized

Fragment shader colors pixels

Render pass writes to swapchain texture

Swapchain presents to the window

*/

 

// vertex.glsl
// OpenGL 4.6 - converting to Vulkan SPIRV 

#version 460

layout (location = 0) in vec3 a_position;
layout (location = 1) in vec4 a_color;
layout (location = 0) out vec4 v_color;

void main()
{
    gl_Position = vec4(a_position, 1.0f);
    v_color = a_color;
}

 

// fragment.glsl
// OpenGL 4.6 - converting to Vulkan SPIRV 

#version 460

layout (location = 0) in vec4 v_color;
layout (location = 0) out vec4 FragColor;

void main()
{
    FragColor = v_color;
}

 



answered Jul 23 by avibootz
edited Jul 24 by avibootz
...