/* sdl_gpu_test.c -- -lSDL3 -lm */ #define SDL_MAIN_USE_CALLBACKS 1 #include #include #include #include #include SDL_Window *win; SDL_GPUDevice *gpudev; SDL_GPUGraphicsPipeline *pipeline_fill; SDL_GPUBuffer *buf_vert; SDL_GPUTransferBuffer *buf_vert_xfer; typedef struct { float x, y; float u, v; } Vertex; Vertex vertices[] = { { -1, -1, 0, 0, }, { 0, -1, 1, 0, }, { 0, 0, 1, 1, }, { -1, -1, 0, 0, }, { -1, 0, 0, 1, }, { 0, 0, 1, 1, }, { 0, 0, 0, 0, }, { 1, 0, 1, 0, }, { 1, 1, 1, 1, }, { 0, 0, 0, 0, }, { 0, 1, 0, 1, }, { 1, 1, 1, 1, }, }; typedef struct { SDL_GPUShaderStage stage; Uint32 n_sampler, n_uniformbuf, n_storagebuf, n_storagetex; } ShaderDesc; SDL_GPUShader* load_shader(SDL_GPUDevice* dev, const char *path, ShaderDesc *desc) { size_t code_size; void *code = SDL_LoadFile(path, &code_size); if (!code) { fprintf(stderr, "couldn't read file: %s\n", SDL_GetError()); return NULL; } SDL_GPUShader *sh = SDL_CreateGPUShader(dev, &(SDL_GPUShaderCreateInfo) { .code = code, .code_size = code_size, .entrypoint = "main", .format = SDL_GPU_SHADERFORMAT_SPIRV, .stage = desc->stage, .num_samplers = desc->n_sampler, .num_uniform_buffers = desc->n_uniformbuf, .num_storage_buffers = desc->n_storagebuf, .num_storage_textures = desc->n_storagetex }); if (!sh) return NULL; SDL_free(code); return sh; } #define CHECK(t, msg) do { if (!(t)) { fprintf(stderr, "%s: %s\n", msg, SDL_GetError()); return SDL_APP_FAILURE; } } while(0) SDL_AppResult SDL_AppInit(void **appstate, int argc, char **argv) { (void)argc; (void)argv; (void)appstate; /* TODO: support more than just SPIR-V */ win = SDL_CreateWindow("Hello, world!", 1024, 768, SDL_WINDOW_RESIZABLE | SDL_WINDOW_VULKAN); CHECK(win, "failed to create window"); gpudev = SDL_CreateGPUDevice(SDL_GPU_SHADERFORMAT_SPIRV, 0, NULL); CHECK(gpudev, "failed to create gpu device"); CHECK(SDL_ClaimWindowForGPUDevice(gpudev, win), "failed to claim window"); SDL_GPUShader *shader_vert = load_shader(gpudev, "shader/tri.vert.spv", &(ShaderDesc) { .stage = SDL_GPU_SHADERSTAGE_VERTEX, .n_uniformbuf = 1 }); CHECK(shader_vert, "load vertex shader"); SDL_GPUShader *shader_frag = load_shader(gpudev, "shader/tri.frag.spv", &(ShaderDesc) { SDL_GPU_SHADERSTAGE_FRAGMENT }); CHECK(shader_frag, "load fragment shader"); buf_vert = SDL_CreateGPUBuffer(gpudev, &(SDL_GPUBufferCreateInfo) { .usage = SDL_GPU_BUFFERUSAGE_VERTEX, .size = sizeof(vertices), .props = 0 }); CHECK(buf_vert, "vertex buffer"); buf_vert_xfer = SDL_CreateGPUTransferBuffer( gpudev, &(SDL_GPUTransferBufferCreateInfo) { .usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD, .size = sizeof(vertices), .props = 0 } ); CHECK(buf_vert_xfer, "vertex transfer buffer"); void *map = SDL_MapGPUTransferBuffer(gpudev, buf_vert_xfer, false); SDL_memcpy(map, vertices, sizeof(vertices)); SDL_UnmapGPUTransferBuffer(gpudev, buf_vert_xfer); SDL_GPUCommandBuffer *cmdbuf = SDL_AcquireGPUCommandBuffer(gpudev); CHECK(cmdbuf, "gpu command buffer acquisition"); SDL_GPUCopyPass *copypass = SDL_BeginGPUCopyPass(cmdbuf); CHECK(copypass, "gpu copy pass"); SDL_UploadToGPUBuffer( copypass, &(SDL_GPUTransferBufferLocation) { .transfer_buffer = buf_vert_xfer, .offset = 0 }, &(SDL_GPUBufferRegion) { .buffer = buf_vert, .offset = 0, .size = sizeof(vertices) }, false ); SDL_EndGPUCopyPass(copypass); SDL_SubmitGPUCommandBuffer(cmdbuf); SDL_ReleaseGPUTransferBuffer(gpudev, buf_vert_xfer); pipeline_fill = SDL_CreateGPUGraphicsPipeline(gpudev, &(SDL_GPUGraphicsPipelineCreateInfo) { .target_info = { .num_color_targets = 1, .color_target_descriptions = (SDL_GPUColorTargetDescription[]) {{ .format = SDL_GetGPUSwapchainTextureFormat(gpudev, win), .blend_state = { .enable_blend = true, .color_blend_op = SDL_GPU_BLENDOP_ADD, .alpha_blend_op = SDL_GPU_BLENDOP_ADD, .src_color_blendfactor = SDL_GPU_BLENDFACTOR_SRC_ALPHA, .dst_color_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, .src_alpha_blendfactor = SDL_GPU_BLENDFACTOR_SRC_ALPHA, .dst_alpha_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, } }}, }, .primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST, .vertex_shader = shader_vert, .fragment_shader = shader_frag, .vertex_input_state = { .num_vertex_buffers = 1, .vertex_buffer_descriptions = &(SDL_GPUVertexBufferDescription) { .slot = 0, .pitch = sizeof(Vertex), .input_rate = SDL_GPU_VERTEXINPUTRATE_VERTEX, .instance_step_rate = 0, }, .num_vertex_attributes = 2, .vertex_attributes = (SDL_GPUVertexAttribute[]) { { .location = 0, .buffer_slot = 0, .format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2, .offset = 0 }, { .location = 1, .buffer_slot = 0, .format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2, .offset = sizeof(float) * 2 } } }, .rasterizer_state = { .fill_mode = SDL_GPU_FILLMODE_FILL }, .props = 0 }); CHECK(pipeline_fill, "graphics pipeline"); SDL_ReleaseGPUShader(gpudev, shader_frag); SDL_ReleaseGPUShader(gpudev, shader_vert); return SDL_APP_CONTINUE; } SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) { (void)appstate; switch (event->type) { case SDL_EVENT_QUIT: puts("Quit!"); return SDL_APP_SUCCESS; default: break; } return SDL_APP_CONTINUE; } SDL_AppResult SDL_AppIterate(void *appstate) { (void)appstate; SDL_GPUCommandBuffer *cmdbuf = SDL_AcquireGPUCommandBuffer(gpudev); CHECK(cmdbuf, "gpu cmd buffer acquisition"); SDL_GPUTexture *swapchain; Uint32 swc_width, swc_height; CHECK(SDL_WaitAndAcquireGPUSwapchainTexture(cmdbuf, win, &swapchain, &swc_width, &swc_height), "swapchain texture acquisition"); CHECK(swapchain, "nil swapchain texture"); float how = (float)swc_height / (float)swc_width; float woh = (float)swc_width / (float)swc_height; float scale = (32 * 2) / fminf(swc_width, swc_height); float scale_x = (swc_height < swc_width ? how : 1) * scale; float scale_y = (swc_height < swc_width ? 1 : woh) * scale; float cam_mat[4][4] = { { scale_x, 0, 0, 0, }, { 0, scale_y, 0, 0, }, { 0, 0, 1, 0, }, { 0, 0, 0, 1, }, }; SDL_PushGPUVertexUniformData(cmdbuf, 0, &cam_mat, sizeof(cam_mat)); SDL_GPURenderPass *render_pass = SDL_BeginGPURenderPass( cmdbuf, &(SDL_GPUColorTargetInfo) { .texture = swapchain, .clear_color = { 0, 0, 0, 1 }, .load_op = SDL_GPU_LOADOP_CLEAR, .store_op = SDL_GPU_STOREOP_STORE, }, 1, NULL ); SDL_BindGPUGraphicsPipeline(render_pass, pipeline_fill); SDL_BindGPUVertexBuffers(render_pass, 0, &(SDL_GPUBufferBinding) { .buffer = buf_vert, .offset = 0 }, 1); SDL_DrawGPUPrimitives(render_pass, sizeof(vertices) / sizeof(Vertex), 1, 0, 0); SDL_EndGPURenderPass(render_pass); SDL_SubmitGPUCommandBuffer(cmdbuf); return SDL_APP_CONTINUE; } void SDL_AppQuit(void *appstate, SDL_AppResult result) { (void)appstate; (void)result; SDL_ReleaseGPUGraphicsPipeline(gpudev, pipeline_fill); SDL_ReleaseWindowFromGPUDevice(gpudev, win); SDL_DestroyGPUDevice(gpudev); SDL_DestroyWindow(win); }