#include #include #include // Shaders #include "testgputext/shaders/shader.vert.spv.h" #include "testgputext/shaders/shader.frag.spv.h" #include "testgputext/shaders/shader-sdf.frag.spv.h" #include "testgputext/shaders/shader.vert.dxil.h" #include "testgputext/shaders/shader.frag.dxil.h" #include "testgputext/shaders/shader-sdf.frag.dxil.h" #include "testgputext/shaders/shader.vert.msl.h" #include "testgputext/shaders/shader.frag.msl.h" #include "testgputext/shaders/shader-sdf.frag.msl.h" #define SDL_MATH_3D_IMPLEMENTATION #include "testgputext/SDL_math3d.h" #define MAX_VERTEX_COUNT 4000 #define MAX_INDEX_COUNT 6000 #define SUPPORTED_SHADER_FORMATS (SDL_GPU_SHADERFORMAT_SPIRV | SDL_GPU_SHADERFORMAT_DXIL | SDL_GPU_SHADERFORMAT_MSL) typedef enum { VertexShader, PixelShader, PixelShader_SDF, } Shader; typedef SDL_FPoint Vec2; typedef struct Vec3 { float x, y, z; } Vec3; typedef struct Vertex { Vec3 pos; SDL_FColor colour; Vec2 uv; } Vertex; typedef struct Context { SDL_GPUDevice *device; SDL_Window *window; SDL_GPUGraphicsPipeline *pipeline; SDL_GPUBuffer *vertex_buffer; SDL_GPUBuffer *index_buffer; SDL_GPUTransferBuffer *transfer_buffer; SDL_GPUSampler *sampler; SDL_GPUCommandBuffer *cmd_buf; } Context; typedef struct GeometryData { Vertex *vertices; int vertex_count; int *indices; int index_count; } GeometryData; void check_error_bool(const bool res) { if (!res) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "%s", SDL_GetError()); } } void *check_error_ptr(void *ptr) { if (!ptr) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "%s", SDL_GetError()); } return ptr; } SDL_GPUShader *load_shader( SDL_GPUDevice *device, Shader shader, Uint32 sampler_count, Uint32 uniform_buffer_count, Uint32 storage_buffer_count, Uint32 storage_texture_count) { SDL_GPUShaderCreateInfo createinfo; createinfo.num_samplers = sampler_count; createinfo.num_storage_buffers = storage_buffer_count; createinfo.num_storage_textures = storage_texture_count; createinfo.num_uniform_buffers = uniform_buffer_count; createinfo.props = 0; SDL_GPUShaderFormat format = SDL_GetGPUShaderFormats(device); if (format & SDL_GPU_SHADERFORMAT_DXIL) { createinfo.format = SDL_GPU_SHADERFORMAT_DXIL; switch (shader) { case VertexShader: createinfo.code = shader_vert_dxil; createinfo.code_size = shader_vert_dxil_len; createinfo.entrypoint = "VSMain"; break; case PixelShader: createinfo.code = shader_frag_dxil; createinfo.code_size = shader_frag_dxil_len; createinfo.entrypoint = "PSMain"; break; case PixelShader_SDF: createinfo.code = shader_sdf_frag_dxil; createinfo.code_size = shader_sdf_frag_dxil_len; createinfo.entrypoint = "PSMain"; break; } } else if (format & SDL_GPU_SHADERFORMAT_MSL) { createinfo.format = SDL_GPU_SHADERFORMAT_MSL; switch (shader) { case VertexShader: createinfo.code = shader_vert_msl; createinfo.code_size = shader_vert_msl_len; createinfo.entrypoint = "main0"; break; case PixelShader: createinfo.code = shader_frag_msl; createinfo.code_size = shader_frag_msl_len; createinfo.entrypoint = "main0"; break; case PixelShader_SDF: createinfo.code = shader_sdf_frag_msl; createinfo.code_size = shader_sdf_frag_msl_len; createinfo.entrypoint = "main0"; break; } } else { createinfo.format = SDL_GPU_SHADERFORMAT_SPIRV; switch (shader) { case VertexShader: createinfo.code = shader_vert_spv; createinfo.code_size = shader_vert_spv_len; createinfo.entrypoint = "main"; break; case PixelShader: createinfo.code = shader_frag_spv; createinfo.code_size = shader_frag_spv_len; createinfo.entrypoint = "main"; break; case PixelShader_SDF: createinfo.code = shader_sdf_frag_spv; createinfo.code_size = shader_sdf_frag_spv_len; createinfo.entrypoint = "main"; break; } } if (shader == VertexShader) { createinfo.stage = SDL_GPU_SHADERSTAGE_VERTEX; } else { createinfo.stage = SDL_GPU_SHADERSTAGE_FRAGMENT; } return SDL_CreateGPUShader(device, &createinfo); } void queue_text_sequence(GeometryData *geometry_data, TTF_GPUAtlasDrawSequence *sequence, SDL_FColor *colour) { for (int i = 0; i < sequence->num_vertices; i++) { Vertex vert; const SDL_FPoint pos = sequence->xy[i]; vert.pos = (Vec3){ pos.x, pos.y, 0.0f }; vert.colour = *colour; vert.uv = sequence->uv[i]; geometry_data->vertices[geometry_data->vertex_count + i] = vert; } SDL_memcpy(geometry_data->indices + geometry_data->index_count, sequence->indices, sequence->num_indices * sizeof(int)); geometry_data->vertex_count += sequence->num_vertices; geometry_data->index_count += sequence->num_indices; } void queue_text(GeometryData *geometry_data, TTF_GPUAtlasDrawSequence *sequence, SDL_FColor *colour) { for ( ; sequence; sequence = sequence->next) { queue_text_sequence(geometry_data, sequence, colour); } } void set_geometry_data(Context *context, GeometryData *geometry_data) { Vertex *transfer_data = SDL_MapGPUTransferBuffer(context->device, context->transfer_buffer, false); SDL_memcpy(transfer_data, geometry_data->vertices, sizeof(Vertex) * geometry_data->vertex_count); SDL_memcpy(transfer_data + MAX_VERTEX_COUNT, geometry_data->indices, sizeof(int) * geometry_data->index_count); SDL_UnmapGPUTransferBuffer(context->device, context->transfer_buffer); } void transfer_data(Context *context, GeometryData *geometry_data) { SDL_GPUCopyPass *copy_pass = check_error_ptr(SDL_BeginGPUCopyPass(context->cmd_buf)); SDL_UploadToGPUBuffer( copy_pass, &(SDL_GPUTransferBufferLocation){ .transfer_buffer = context->transfer_buffer, .offset = 0 }, &(SDL_GPUBufferRegion){ .buffer = context->vertex_buffer, .offset = 0, .size = sizeof(Vertex) * geometry_data->vertex_count }, false); SDL_UploadToGPUBuffer( copy_pass, &(SDL_GPUTransferBufferLocation){ .transfer_buffer = context->transfer_buffer, .offset = sizeof(Vertex) * MAX_VERTEX_COUNT }, &(SDL_GPUBufferRegion){ .buffer = context->index_buffer, .offset = 0, .size = sizeof(int) * geometry_data->index_count }, false); SDL_EndGPUCopyPass(copy_pass); } void draw(Context *context, SDL_Mat4X4 *matrices, int num_matrices, TTF_GPUAtlasDrawSequence *draw_sequence) { SDL_GPUTexture *swapchain_texture; check_error_bool(SDL_WaitAndAcquireGPUSwapchainTexture(context->cmd_buf, context->window, &swapchain_texture, NULL, NULL)); if (swapchain_texture != NULL) { SDL_GPUColorTargetInfo colour_target_info = { 0 }; colour_target_info.texture = swapchain_texture; colour_target_info.clear_color = (SDL_FColor){ 0.3f, 0.4f, 0.5f, 1.0f }; colour_target_info.load_op = SDL_GPU_LOADOP_CLEAR; colour_target_info.store_op = SDL_GPU_STOREOP_STORE; SDL_GPURenderPass *render_pass = SDL_BeginGPURenderPass(context->cmd_buf, &colour_target_info, 1, NULL); SDL_BindGPUGraphicsPipeline(render_pass, context->pipeline); SDL_BindGPUVertexBuffers( render_pass, 0, &(SDL_GPUBufferBinding){ .buffer = context->vertex_buffer, .offset = 0 }, 1); SDL_BindGPUIndexBuffer( render_pass, &(SDL_GPUBufferBinding){ .buffer = context->index_buffer, .offset = 0 }, SDL_GPU_INDEXELEMENTSIZE_32BIT); SDL_PushGPUVertexUniformData(context->cmd_buf, 0, matrices, sizeof(SDL_Mat4X4) * num_matrices); int index_offset = 0, vertex_offset = 0; for (TTF_GPUAtlasDrawSequence *seq = draw_sequence; seq != NULL; seq = seq->next) { SDL_BindGPUFragmentSamplers( render_pass, 0, &(SDL_GPUTextureSamplerBinding){ .texture = seq->atlas_texture, .sampler = context->sampler }, 1); SDL_DrawGPUIndexedPrimitives(render_pass, seq->num_indices, 1, index_offset, vertex_offset, 0); index_offset += seq->num_indices; vertex_offset += seq->num_vertices; } SDL_EndGPURenderPass(render_pass); } } void free_context(Context *context) { SDL_ReleaseGPUTransferBuffer(context->device, context->transfer_buffer); SDL_ReleaseGPUSampler(context->device, context->sampler); SDL_ReleaseGPUBuffer(context->device, context->vertex_buffer); SDL_ReleaseGPUBuffer(context->device, context->index_buffer); SDL_ReleaseGPUGraphicsPipeline(context->device, context->pipeline); SDL_ReleaseWindowFromGPUDevice(context->device, context->window); SDL_DestroyGPUDevice(context->device); SDL_DestroyWindow(context->window); } int main(int argc, char *argv[]) { const char *font_filename = NULL; bool use_SDF = false; (void)argc; for (int i = 1; argv[i]; ++i) { if (SDL_strcasecmp(argv[i], "--sdf") == 0) { use_SDF = true; } else if (*argv[i] == '-') { break; } else { font_filename = argv[i]; break; } } if (!font_filename) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Usage: testgputext [--sdf] FONT_FILENAME"); return 2; } check_error_bool(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS)); bool running = true; Context context = { 0 }; context.window = check_error_ptr(SDL_CreateWindow("GPU text test", 800, 600, 0)); context.device = check_error_ptr(SDL_CreateGPUDevice(SUPPORTED_SHADER_FORMATS, true, NULL)); check_error_bool(SDL_ClaimWindowForGPUDevice(context.device, context.window)); SDL_GPUShader *vertex_shader = check_error_ptr(load_shader(context.device, VertexShader, 0, 1, 0, 0)); SDL_GPUShader *fragment_shader = check_error_ptr(load_shader(context.device, use_SDF ? PixelShader_SDF : PixelShader, 1, 0, 0, 0)); SDL_GPUGraphicsPipelineCreateInfo pipeline_create_info = { .target_info = { .num_color_targets = 1, .color_target_descriptions = (SDL_GPUColorTargetDescription[]){{ .format = SDL_GetGPUSwapchainTextureFormat(context.device, context.window), .blend_state = (SDL_GPUColorTargetBlendState){ .enable_blend = true, .alpha_blend_op = SDL_GPU_BLENDOP_ADD, .color_blend_op = SDL_GPU_BLENDOP_ADD, .color_write_mask = 0xF, .src_alpha_blendfactor = SDL_GPU_BLENDFACTOR_SRC_ALPHA, .dst_alpha_blendfactor = SDL_GPU_BLENDFACTOR_DST_ALPHA, .src_color_blendfactor = SDL_GPU_BLENDFACTOR_SRC_ALPHA, .dst_color_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA } }}, .has_depth_stencil_target = false, .depth_stencil_format = SDL_GPU_TEXTUREFORMAT_INVALID /* Need to set this to avoid missing initializer for field error */ }, .vertex_input_state = (SDL_GPUVertexInputState){ .num_vertex_buffers = 1, .vertex_buffer_descriptions = (SDL_GPUVertexBufferDescription[]){{ .slot = 0, .input_rate = SDL_GPU_VERTEXINPUTRATE_VERTEX, .instance_step_rate = 0, .pitch = sizeof(Vertex) }}, .num_vertex_attributes = 3, .vertex_attributes = (SDL_GPUVertexAttribute[]){{ .buffer_slot = 0, .format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT3, .location = 0, .offset = 0 }, { .buffer_slot = 0, .format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT4, .location = 1, .offset = sizeof(float) * 3 }, { .buffer_slot = 0, .format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2, .location = 2, .offset = sizeof(float) * 7 }} }, .primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST, .vertex_shader = vertex_shader, .fragment_shader = fragment_shader }; context.pipeline = check_error_ptr(SDL_CreateGPUGraphicsPipeline(context.device, &pipeline_create_info)); SDL_ReleaseGPUShader(context.device, vertex_shader); SDL_ReleaseGPUShader(context.device, fragment_shader); SDL_GPUBufferCreateInfo vbf_info = { .usage = SDL_GPU_BUFFERUSAGE_VERTEX, .size = sizeof(Vertex) * MAX_VERTEX_COUNT }; context.vertex_buffer = check_error_ptr(SDL_CreateGPUBuffer(context.device, &vbf_info)); SDL_GPUBufferCreateInfo ibf_info = { .usage = SDL_GPU_BUFFERUSAGE_INDEX, .size = sizeof(int) * MAX_INDEX_COUNT }; context.index_buffer = check_error_ptr(SDL_CreateGPUBuffer(context.device, &ibf_info)); SDL_GPUTransferBufferCreateInfo tbf_info = { .usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD, .size = (sizeof(Vertex) * MAX_VERTEX_COUNT) + (sizeof(int) * MAX_INDEX_COUNT) }; context.transfer_buffer = check_error_ptr(SDL_CreateGPUTransferBuffer(context.device, &tbf_info)); SDL_GPUSamplerCreateInfo sampler_info = { .min_filter = SDL_GPU_FILTER_LINEAR, .mag_filter = SDL_GPU_FILTER_LINEAR, .mipmap_mode = SDL_GPU_SAMPLERMIPMAPMODE_LINEAR, .address_mode_u = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE, .address_mode_v = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE, .address_mode_w = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE }; context.sampler = check_error_ptr(SDL_CreateGPUSampler(context.device, &sampler_info)); GeometryData geometry_data = { 0 }; geometry_data.vertices = SDL_calloc(MAX_VERTEX_COUNT, sizeof(Vertex)); geometry_data.indices = SDL_calloc(MAX_INDEX_COUNT, sizeof(int)); check_error_bool(TTF_Init()); TTF_Font *font = check_error_ptr(TTF_OpenFont(font_filename, 50)); /* Preferably use a Monospaced font */ if (!font) { running = false; } SDL_Log("SDF %s", use_SDF ? "enabled" : "disabled"); TTF_SetFontSDF(font, use_SDF); TTF_SetFontWrapAlignment(font, TTF_HORIZONTAL_ALIGN_CENTER); TTF_TextEngine *engine = check_error_ptr(TTF_CreateGPUTextEngine(context.device)); char str[] = " \nSDL is cool"; TTF_Text *text = check_error_ptr(TTF_CreateText(engine, font, str, 0)); SDL_Mat4X4 *matrices = (SDL_Mat4X4[]){ SDL_MatrixPerspective(SDL_PI_F / 2.0f, 800.0f / 600.0f, 0.1f, 100.0f), SDL_MatrixIdentity() }; float rot_angle = 0; SDL_FColor colour = {1.0f, 1.0f, 0.0f, 1.0f}; while (running) { SDL_Event event; while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_EVENT_KEY_UP: if (event.key.key == SDLK_ESCAPE) { running = false; } break; case SDL_EVENT_QUIT: running = false; break; } } for (int i = 0; i < 5; i++) { str[i] = 65 + SDL_rand(26); } TTF_SetTextString(text, str, 0); int tw, th; check_error_bool(TTF_GetTextSize(text, &tw, &th)); rot_angle = SDL_fmodf(rot_angle + 0.01, 2 * SDL_PI_F); // Create a model matrix to make the text rotate SDL_Mat4X4 model; model = SDL_MatrixIdentity(); model = SDL_MatrixMultiply(model, SDL_MatrixTranslation((SDL_Vec3){ 0.0f, 0.0f, -80.0f })); model = SDL_MatrixMultiply(model, SDL_MatrixScaling((SDL_Vec3){ 0.3f, 0.3f, 0.3f})); model = SDL_MatrixMultiply(model, SDL_MatrixRotationY(rot_angle)); model = SDL_MatrixMultiply(model, SDL_MatrixTranslation((SDL_Vec3){ -tw / 2.0f, th / 2.0f, 0.0f })); matrices[1] = model; // Get the text data and queue the text in a buffer for drawing later TTF_GPUAtlasDrawSequence *sequence = TTF_GetGPUTextDrawData(text); queue_text(&geometry_data, sequence, &colour); set_geometry_data(&context, &geometry_data); context.cmd_buf = check_error_ptr(SDL_AcquireGPUCommandBuffer(context.device)); transfer_data(&context, &geometry_data); draw(&context, matrices, 2, sequence); SDL_SubmitGPUCommandBuffer(context.cmd_buf); geometry_data.vertex_count = 0; geometry_data.index_count = 0; } SDL_free(geometry_data.vertices); SDL_free(geometry_data.indices); TTF_DestroyText(text); TTF_DestroyGPUTextEngine(engine); TTF_CloseFont(font); TTF_Quit(); free_context(&context); SDL_Quit(); return 0; }