1
0
Fork 0
tetris-c/vendor/SDL3_ttf-3.2.2/examples/testgputext.c
2026-05-10 00:20:28 +02:00

480 lines
17 KiB
C

#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <SDL3_ttf/SDL_ttf.h>
// 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;
}