Last active
January 29, 2026 15:16
-
-
Save API-Beast/5dcaac8271bc51183be2e4ccee00a2f5 to your computer and use it in GitHub Desktop.
SDL GPU with SDL Renderer
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #define SDL_MAIN_USE_CALLBACKS | |
| #include <SDL3/SDL_main.h> | |
| #include <SDL3/SDL.h> | |
| #include "common/viewport.h" | |
| #include <math.h> | |
| #define CONFIG_LIST \ | |
| HEADER("Shader Storage Buffer")\ | |
| FLOAT_SLIDER(hue_shift, 0.0f, -1.0f, 1.0f, 0.01f, 1.0f) | |
| #include "common/config_ui.h" | |
| config_data config; | |
| SDL_Window* window; | |
| SDL_Renderer* renderer; | |
| SDL_GPUDevice* device; | |
| SDL_GPUGraphicsPipeline* pipeline; | |
| const int vertex_buffer_max = 1024; | |
| const int storage_buffer_max = 1024; | |
| SDL_GPUBuffer* vertex_buffer; | |
| SDL_GPUBuffer* storage_buffer; | |
| SDL_GPUTransferBuffer* upload_buffer = NULL; | |
| SDL_Texture* render_target; | |
| SDL_GPUTexture* render_target_gpu_view; | |
| struct Vertex | |
| { | |
| float X, Y; | |
| float U, V; | |
| }; | |
| int window_width = 800; | |
| int window_height = 400; | |
| viewport map_view = (viewport){ | |
| .offset = {-200.0f, -200.0f}, | |
| .scale = 1.0f, | |
| .position = {0.0f, 0.0f}, | |
| .size = {600.0f, 400.0f}, | |
| .started_drag = false, | |
| .x_dir = {1.0f, 0.0f}, | |
| .y_dir = {0.0f, 1.0f} | |
| }; | |
| viewport config_view = (viewport){ | |
| .offset = {0.0f, 0.0f}, | |
| .scale = 1.0f, | |
| .position = {600.0f, 0.0f}, | |
| .size = {200.0f, 400.0f}, | |
| .started_drag = false, | |
| .x_dir = {0.0f, 0.0f}, | |
| .y_dir = {0.0f, 1.0f} | |
| }; | |
| const unsigned char vertex_shader_spv[] = { | |
| #embed "C:/Users/Beast/Desktop/website/build/src/gpurenderer/shaders/vertex.spv" | |
| }; | |
| const unsigned char fragment_shader_spv[] = { | |
| #embed "C:/Users/Beast/Desktop/website/build/src/gpurenderer/shaders/fragment.spv" | |
| }; | |
| void update_gpu_memory() | |
| { | |
| if(!upload_buffer) | |
| upload_buffer = SDL_CreateGPUTransferBuffer(device, &(SDL_GPUTransferBufferCreateInfo){.size = vertex_buffer_max + storage_buffer_max, .usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD}); | |
| void* target = SDL_MapGPUTransferBuffer(device, upload_buffer, true); | |
| vec2f tl = map_view.position + (vec2f){10., 10.}; | |
| vec2f br = map_view.position + map_view.size - (vec2f){10., 10.}; | |
| struct Vertex vertices[] = { | |
| {tl[0], tl[1], 0.0f, 0.0f}, // 0: top-left | |
| {tl[0], br[1], 0.0f, 1.0f}, // 1: bottom-left | |
| {br[0], tl[1], 1.0f, 0.0f}, // 2: top-right | |
| {br[0], br[1], 1.0f, 1.0f} // 3: bottom-right | |
| }; | |
| SDL_memcpy(target, vertices, sizeof(vertices)); | |
| int vertex_buffer_length = sizeof(vertices); | |
| float pixel_to_clip[] = { | |
| 2.0f / window_width, 0.0f, 0.0f, 0.0f, | |
| 0.0f, -2.0f / window_height, 0.0f, 0.0f, | |
| 0.0f, 0.0f, 1.0f, 0.0f, | |
| -1.0f, 1.0f, 0.0f, 1.0f | |
| }; | |
| SDL_memcpy(target+vertex_buffer_max, pixel_to_clip, sizeof(pixel_to_clip)); | |
| SDL_memcpy(target+vertex_buffer_max+sizeof(pixel_to_clip), &config.hue_shift, sizeof(float)); | |
| int storage_buffer_length = sizeof(pixel_to_clip) + sizeof(float); | |
| SDL_UnmapGPUTransferBuffer(device, upload_buffer); | |
| SDL_GPUCommandBuffer* commandBuffer = SDL_AcquireGPUCommandBuffer(device); | |
| SDL_GPUCopyPass* copyPass = SDL_BeginGPUCopyPass(commandBuffer); | |
| SDL_UploadToGPUBuffer(copyPass, | |
| &(SDL_GPUTransferBufferLocation){.transfer_buffer=upload_buffer, .offset = 0}, | |
| &(SDL_GPUBufferRegion){.buffer=vertex_buffer, .offset = 0, .size = vertex_buffer_length}, true); | |
| SDL_UploadToGPUBuffer(copyPass, | |
| &(SDL_GPUTransferBufferLocation){.transfer_buffer=upload_buffer, .offset = vertex_buffer_max}, | |
| &(SDL_GPUBufferRegion){.buffer=storage_buffer, .offset = 0, .size = storage_buffer_length}, true); | |
| SDL_EndGPUCopyPass(copyPass); | |
| SDL_SubmitGPUCommandBuffer(commandBuffer); | |
| } | |
| SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[]) | |
| { | |
| SDL_Init(SDL_INIT_VIDEO); | |
| window = SDL_CreateWindow("Demo", window_width, window_height, 0); | |
| device = SDL_CreateGPUDevice(SDL_GPU_SHADERFORMAT_SPIRV, true, NULL); | |
| // Note, SDL_CreateGPURenderer will claim the window. We aren't allowed to do this ourselves or we will get Vulkan errors. | |
| renderer = SDL_CreateGPURenderer(device, window); | |
| if(device == NULL || renderer == NULL || window == NULL) | |
| { | |
| const char* error = SDL_GetError(); | |
| printf("SDL Initialization failed (%x %x %x)\nReason: ", device, renderer, window); | |
| if(error != NULL){ puts(error); } | |
| return SDL_APP_FAILURE; | |
| } | |
| SDL_GPUGraphicsPipelineCreateInfo pipe_info = { | |
| .primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLESTRIP, | |
| .vertex_input_state = | |
| { | |
| .num_vertex_buffers = 1, | |
| .vertex_buffer_descriptions = (SDL_GPUVertexBufferDescription[]){ | |
| [0] = {.slot = 0, .input_rate = SDL_GPU_VERTEXINPUTRATE_VERTEX, .instance_step_rate = 0, .pitch = sizeof(struct Vertex)} | |
| }, | |
| .num_vertex_attributes = 2, | |
| .vertex_attributes = (SDL_GPUVertexAttribute[]){ | |
| [0] = {.buffer_slot = 0, .location = 0, .format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2, .offset = 0}, | |
| [1] = {.buffer_slot = 0, .location = 1, .format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2, .offset = offsetof(struct Vertex, U)} | |
| } | |
| }, | |
| .target_info = | |
| { | |
| .num_color_targets = 1, | |
| .color_target_descriptions = (SDL_GPUColorTargetDescription[]) | |
| { | |
| [0] = {.format = SDL_GetGPUSwapchainTextureFormat(device, window)} | |
| } | |
| } | |
| }; | |
| SDL_GPUShaderCreateInfo vert_info = { | |
| .entrypoint = "main", .format = SDL_GPU_SHADERFORMAT_SPIRV, .stage = SDL_GPU_SHADERSTAGE_VERTEX, | |
| .code = vertex_shader_spv, .code_size = sizeof(vertex_shader_spv), | |
| .num_storage_buffers = 1 | |
| }; | |
| SDL_GPUShaderCreateInfo frag_info = { | |
| .entrypoint = "main", .format = SDL_GPU_SHADERFORMAT_SPIRV, .stage = SDL_GPU_SHADERSTAGE_FRAGMENT, | |
| .code = fragment_shader_spv, .code_size = sizeof(fragment_shader_spv), | |
| .num_storage_buffers = 1 | |
| }; | |
| pipe_info.vertex_shader = SDL_CreateGPUShader(device, &vert_info); | |
| pipe_info.fragment_shader = SDL_CreateGPUShader(device, &frag_info); | |
| pipeline = SDL_CreateGPUGraphicsPipeline(device, &pipe_info); | |
| SDL_ReleaseGPUShader(device, pipe_info.vertex_shader); | |
| SDL_ReleaseGPUShader(device, pipe_info.fragment_shader); | |
| vertex_buffer = SDL_CreateGPUBuffer(device, &(SDL_GPUBufferCreateInfo){.size = vertex_buffer_max, .usage = SDL_GPU_BUFFERUSAGE_VERTEX}); | |
| storage_buffer = SDL_CreateGPUBuffer(device, &(SDL_GPUBufferCreateInfo){.size = storage_buffer_max, .usage = SDL_GPU_BUFFERUSAGE_GRAPHICS_STORAGE_READ}); | |
| // Note: Specifically Renderer textures. We can't turn GPUTextures into Renderer Textures. | |
| SDL_PixelFormat format = SDL_GetPixelFormatFromGPUTextureFormat(SDL_GetGPUSwapchainTextureFormat(device, window)); | |
| int w, h; | |
| SDL_GetRenderOutputSize(renderer, &w, &h); | |
| render_target = SDL_CreateTexture(renderer, format, SDL_TEXTUREACCESS_TARGET, w, h); | |
| render_target_gpu_view = SDL_GetPointerProperty(SDL_GetTextureProperties(render_target), SDL_PROP_TEXTURE_GPU_TEXTURE_POINTER, NULL); | |
| update_gpu_memory(); | |
| config_init(&config); | |
| return SDL_APP_CONTINUE; | |
| } | |
| SDL_AppResult SDL_AppIterate(void* appstate) { | |
| SDL_FlushRenderer(renderer); | |
| // Do SDL GPU stuff. | |
| SDL_GPUCommandBuffer* commandBuffer = SDL_AcquireGPUCommandBuffer(device); | |
| SDL_GPUTexture* swapchainTexture; | |
| SDL_GPUColorTargetInfo colorTargetInfo = {.load_op = SDL_GPU_LOADOP_CLEAR, .store_op = SDL_GPU_STOREOP_STORE, .texture = render_target_gpu_view}; | |
| SDL_GPURenderPass* renderPass = SDL_BeginGPURenderPass(commandBuffer, &colorTargetInfo, 1, NULL); | |
| SDL_BindGPUGraphicsPipeline(renderPass, pipeline); | |
| SDL_BindGPUVertexStorageBuffers(renderPass, 0, &storage_buffer, 1); | |
| SDL_BindGPUFragmentStorageBuffers(renderPass, 0, &storage_buffer, 1); | |
| SDL_BindGPUVertexBuffers(renderPass, 0, &(SDL_GPUBufferBinding){.buffer = vertex_buffer, .offset = 0}, 1); | |
| SDL_DrawGPUPrimitives(renderPass, 4, 1, 0, 0); | |
| SDL_EndGPURenderPass(renderPass); | |
| SDL_SubmitGPUCommandBuffer(commandBuffer); | |
| // Blit the stuff we did in SDL GPU onto the actual swapchain texture. | |
| SDL_RenderTexture(renderer, render_target, NULL, NULL); | |
| // Now we can let Renderer handle the rest of the drawing. | |
| config_render_ui(renderer, &config, config_view.position[0], config_view.position[1], config_view.size[0], config_view.size[1]); | |
| SDL_RenderPresent(renderer); | |
| if(config.any_changed) | |
| update_gpu_memory(); | |
| config_reset_modified(&config); | |
| return SDL_APP_CONTINUE; | |
| } | |
| SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) | |
| { | |
| if (event->type == SDL_EVENT_QUIT) | |
| { | |
| return SDL_APP_SUCCESS; | |
| } | |
| if(config_handle_input(event, &config, config_view.position[0], config_view.position[1], config_view.size[0], config_view.size[1])) | |
| { | |
| return SDL_APP_CONTINUE; | |
| } | |
| return SDL_APP_CONTINUE; | |
| } | |
| void SDL_AppQuit(void* appstate, SDL_AppResult result) | |
| { | |
| // These are really just because the Vulkan validation layer complains otherwise. | |
| if (storage_buffer) SDL_ReleaseGPUBuffer(device, storage_buffer); | |
| if (vertex_buffer) SDL_ReleaseGPUBuffer(device, vertex_buffer); | |
| if (upload_buffer) SDL_ReleaseGPUTransferBuffer(device, upload_buffer); | |
| if (render_target) SDL_DestroyTexture(render_target); | |
| if (pipeline) SDL_ReleaseGPUGraphicsPipeline(device, pipeline); | |
| // The actual things to deinit. | |
| if (renderer) SDL_DestroyRenderer(renderer); | |
| if (device) SDL_DestroyGPUDevice(device); | |
| if (window) SDL_DestroyWindow(window); | |
| SDL_Quit(); | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #version 460 | |
| layout(set = 2, binding = 0) readonly buffer StorageBuffer { | |
| mat4 view; | |
| float hue_shift; | |
| } storage; | |
| layout(location = 0) in vec2 uv; | |
| layout(location = 0) out vec4 frag_colour; | |
| vec3 hueShift(vec3 color, float hueShift) { | |
| vec3 k = vec3(0.57735, 0.57735, 0.57735); // 1/sqrt(3) | |
| float cosAngle = cos(hueShift * 6.283185); // 2*PI | |
| return color * cosAngle + cross(k, color) * sin(hueShift * 6.283185) + k * dot(k, color) * (1.0 - cosAngle); | |
| } | |
| void main() | |
| { | |
| frag_colour = vec4(hueShift(vec3(uv * 0.5, 1.0), storage.hue_shift), 1.0); | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #version 460 | |
| layout(set = 0, binding = 0) readonly buffer StorageBuffer { | |
| mat4 view; | |
| float hue_shift; | |
| } storage; | |
| layout (location = 0) in vec2 a_position; | |
| layout (location = 1) in vec2 uv; | |
| layout (location = 0) out vec2 uv_out; | |
| void main() | |
| { | |
| gl_Position = storage.view * vec4(a_position, 1.0f, 1.0f); | |
| uv_out = uv; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment