From 82f028b21e40744b06839ff3844be977c73b776b Mon Sep 17 00:00:00 2001
From: Captain <[EMAIL REDACTED]>
Date: Sat, 7 Dec 2024 21:06:46 +0530
Subject: [PATCH] Added GPUTextEngine (#412)
---
CMakeLists.txt | 6 +-
examples/testgputext.c | 402 +++++
examples/testgputext/SDL_math3d.h | 309 ++++
.../fonts/NotoSansMono-Regular.ttf | Bin 0 -> 405892 bytes
examples/testgputext/fonts/license.txt | 37 +
examples/testgputext/shaders/build-shaders.sh | 106 ++
examples/testgputext/shaders/dxbc50.h | 2 +
examples/testgputext/shaders/dxil60.h | 2 +
examples/testgputext/shaders/metal.h | 2 +
examples/testgputext/shaders/shader.frag | 13 +
.../testgputext/shaders/shader.frag.metal | 23 +
.../testgputext/shaders/shader.frag.metal.h | 40 +
.../shaders/shader.frag.sm50.dxbc.h | 100 ++
.../testgputext/shaders/shader.frag.sm50.hlsl | 32 +
.../shaders/shader.frag.sm60.dxil.h | 467 ++++++
.../testgputext/shaders/shader.frag.sm60.hlsl | 32 +
.../testgputext/shaders/shader.frag.spv.h | 50 +
examples/testgputext/shaders/shader.vert | 19 +
.../testgputext/shaders/shader.vert.metal | 34 +
.../testgputext/shaders/shader.vert.metal.h | 55 +
.../shaders/shader.vert.sm50.dxbc.h | 1320 +++++++++++++++++
.../testgputext/shaders/shader.vert.sm50.hlsl | 47 +
.../shaders/shader.vert.sm60.dxil.h | 723 +++++++++
.../testgputext/shaders/shader.vert.sm60.hlsl | 47 +
.../testgputext/shaders/shader.vert.spv.h | 109 ++
examples/testgputext/shaders/spir-v.h | 2 +
external/SDL | 2 +-
include/SDL3_ttf/SDL_ttf.h | 79 +
src/SDL_gpu_textengine.c | 943 ++++++++++++
src/SDL_ttf.sym | 3 +
30 files changed, 5004 insertions(+), 2 deletions(-)
create mode 100644 examples/testgputext.c
create mode 100644 examples/testgputext/SDL_math3d.h
create mode 100644 examples/testgputext/fonts/NotoSansMono-Regular.ttf
create mode 100644 examples/testgputext/fonts/license.txt
create mode 100755 examples/testgputext/shaders/build-shaders.sh
create mode 100644 examples/testgputext/shaders/dxbc50.h
create mode 100644 examples/testgputext/shaders/dxil60.h
create mode 100644 examples/testgputext/shaders/metal.h
create mode 100644 examples/testgputext/shaders/shader.frag
create mode 100644 examples/testgputext/shaders/shader.frag.metal
create mode 100644 examples/testgputext/shaders/shader.frag.metal.h
create mode 100644 examples/testgputext/shaders/shader.frag.sm50.dxbc.h
create mode 100644 examples/testgputext/shaders/shader.frag.sm50.hlsl
create mode 100644 examples/testgputext/shaders/shader.frag.sm60.dxil.h
create mode 100644 examples/testgputext/shaders/shader.frag.sm60.hlsl
create mode 100644 examples/testgputext/shaders/shader.frag.spv.h
create mode 100644 examples/testgputext/shaders/shader.vert
create mode 100644 examples/testgputext/shaders/shader.vert.metal
create mode 100644 examples/testgputext/shaders/shader.vert.metal.h
create mode 100644 examples/testgputext/shaders/shader.vert.sm50.dxbc.h
create mode 100644 examples/testgputext/shaders/shader.vert.sm50.hlsl
create mode 100644 examples/testgputext/shaders/shader.vert.sm60.dxil.h
create mode 100644 examples/testgputext/shaders/shader.vert.sm60.hlsl
create mode 100644 examples/testgputext/shaders/shader.vert.spv.h
create mode 100644 examples/testgputext/shaders/spir-v.h
create mode 100644 src/SDL_gpu_textengine.c
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 6b622cd8..2f95bc2a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -97,6 +97,7 @@ endif()
add_library(${sdl3_ttf_target_name}
src/SDL_hashtable.c
+ src/SDL_gpu_textengine.c
src/SDL_renderer_textengine.c
src/SDL_surface_textengine.c
src/SDL_ttf.c
@@ -361,6 +362,9 @@ if(SDLTTF_SAMPLES)
add_executable(glfont examples/glfont.c)
add_executable(showfont examples/showfont.c examples/editbox.c)
add_executable(testapp examples/testapp.c)
+ add_executable(testgputext examples/testgputext.c)
+
+ file(COPY examples/testgputext/fonts/NotoSansMono-Regular.ttf DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
set(OpenGL_GL_PREFERENCE GLVND)
find_package(OpenGL)
@@ -372,7 +376,7 @@ if(SDLTTF_SAMPLES)
target_link_libraries(glfont PRIVATE OpenGL::GL)
endif()
- foreach(prog glfont showfont testapp)
+ foreach(prog glfont showfont testapp testgputext)
sdl_add_warning_options(${prog} WARNING_AS_ERROR ${SDLTTF_WERROR})
target_link_libraries(${prog} PRIVATE SDL3_ttf::${sdl3_ttf_target_name})
target_link_libraries(${prog} PRIVATE ${sdl3_target_name})
diff --git a/examples/testgputext.c b/examples/testgputext.c
new file mode 100644
index 00000000..d8f27888
--- /dev/null
+++ b/examples/testgputext.c
@@ -0,0 +1,402 @@
+#include <SDL3/SDL.h>
+#include <SDL3/SDL_main.h>
+#include <SDL3_ttf/SDL_ttf.h>
+
+#include "testgputext/shaders/spir-v.h"
+#include "testgputext/shaders/dxbc50.h"
+#include "testgputext/shaders/dxil60.h"
+#include "testgputext/shaders/metal.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_DXBC | SDL_GPU_SHADERFORMAT_DXIL | SDL_GPU_SHADERFORMAT_METALLIB)
+
+typedef struct Vec2
+{
+ float x, y;
+} 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,
+ bool is_vertex,
+ 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_DXBC) {
+ createinfo.format = SDL_GPU_SHADERFORMAT_DXBC;
+ createinfo.code = (Uint8*)(is_vertex ? shader_vert_sm50_dxbc : shader_frag_sm50_dxbc);
+ createinfo.code_size = is_vertex ? SDL_arraysize(shader_vert_sm50_dxbc) : SDL_arraysize(shader_frag_sm50_dxbc);
+ createinfo.entrypoint = is_vertex ? "VSMain" : "PSMain";
+ } else if (format & SDL_GPU_SHADERFORMAT_DXIL) {
+ createinfo.format = SDL_GPU_SHADERFORMAT_DXIL;
+ createinfo.code = is_vertex ? shader_vert_sm60_dxil : shader_frag_sm60_dxil;
+ createinfo.code_size = is_vertex ? SDL_arraysize(shader_vert_sm60_dxil) : SDL_arraysize(shader_frag_sm60_dxil);
+ createinfo.entrypoint = is_vertex ? "VSMain" : "PSMain";
+ } else if (format & SDL_GPU_SHADERFORMAT_METALLIB) {
+ createinfo.format = SDL_GPU_SHADERFORMAT_METALLIB;
+ createinfo.code = is_vertex ? shader_vert_metal : shader_frag_metal;
+ createinfo.code_size = is_vertex ? shader_vert_metal_len : shader_frag_metal_len;
+ createinfo.entrypoint = is_vertex ? "vs_main" : "fs_main";
+ } else {
+ createinfo.format = SDL_GPU_SHADERFORMAT_SPIRV;
+ createinfo.code = is_vertex ? shader_vert_spv : shader_frag_spv;
+ createinfo.code_size = is_vertex ? shader_vert_spv_len : shader_frag_spv_len;
+ createinfo.entrypoint = "main";
+ }
+
+ createinfo.stage = is_vertex ? SDL_GPU_SHADERSTAGE_VERTEX : 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 float *xy = (float *)((Uint8 *)sequence->xy + i * sequence->xy_stride);
+ vert.pos = (Vec3){ xy[0], xy[1], 0.0f };
+
+ vert.colour = *colour;
+
+ const float *uv = (float *)((Uint8 *)sequence->uv + i * sequence->uv_stride);
+ SDL_memcpy(&vert.uv, uv, 2 * sizeof(float));
+
+ 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)
+{
+ do {
+ queue_text_sequence(geometry_data, sequence, colour);
+ } while ((sequence = sequence->next));
+}
+
+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_AcquireGPUSwapchainTexture(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;
+ 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, 0, 0);
+
+ index_offset += seq->num_indices;
+ }
+ 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[])
+{
+ (void)argc;
+ (void)argv;
+
+ 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, true, 0, 1, 0, 0));
+ SDL_GPUShader *fragment_shader = check_error_ptr(load_shader(context.device, false, 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("NotoSansMono-Regular.ttf", 50));
+ TTF_SetFontWrapAlignment(font, TTF_HORIZONTAL_ALIGN_CENTER);
+ TTF_TextEngine *engine = check_error_ptr(TTF_CreateGPUTextEngine(context.device));
+
+ 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;
+ char str[] = " \nSDL is cool";
+ 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_QUIT:
+ running = false;
+ break;
+ }
+ }
+
+ for (int i = 0; i < 5; i++) {
+ str[i] = 65 + SDL_rand(26);
+ }
+
+ rot_angle = SDL_fmodf(rot_angle + 0.01, 2 * SDL_PI_F);
+
+ int tw, th;
+ TTF_Text *text = check_error_ptr(TTF_CreateText(engine, font, str, 0));
+ check_error_bool(TTF_GetTextSize(text, &tw, &th));
+ TTF_SetTextWrapWidth(text, 0);
+
+ // 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;
+
+ TTF_DestroyText(text);
+ }
+
+ SDL_free(geometry_data.vertices);
+ SDL_free(geometry_data.indices);
+ TTF_DestroyGPUTextEngine(engine);
+ free_context(&context);
+ SDL_Quit();
+
+ return 0;
+}
diff --git a/examples/testgputext/SDL_math3d.h b/examples/testgputext/SDL_math3d.h
new file mode 100644
index 00000000..60116abb
--- /dev/null
+++ b/examples/testgputext/SDL_math3d.h
@@ -0,0 +1,309 @@
+/**
+ * Define SDL_MATH_3D_IMPLEMENTATION before including this header in any one source (.c) files.
+ * Angles are in radians unless specified otherwise.
+**/
+
+#ifndef SDL_MATH_3D_H
+#define SDL_MATH_3D_H
+
+#include <SDL3/SDL_stdinc.h>
+
+// Set up for C function definitions, even when using C++
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct
+{
+ float x;
+ float y;
+} SDL_Vec2;
+
+typedef struct
+{
+ float x;
+ float y;
+ float z;
+} SDL_Vec3;
+
+typedef struct
+{
+ float x;
+ float y;
+ float z;
+ float w;
+} SDL_Vec4;
+
+/**
+ * The matrix is stored in column major format
+ **/
+typedef struct
+{
+ union
+ {
+ struct
+ {
+ float m00, m01, m02, m03;
+ float m10, m11, m12, m13;
+ float m20, m21, m22, m23;
+ float m30, m31, m32, m33;
+ } v;
+ float m[4][4];
+ };
+} SDL_Mat4X4;
+
+
+// SDL Vector functions
+static inline SDL_Vec3 SDL_Vector3(float x, float y, float z);
+static inline float SDL_Vec3Magnitude(SDL_Vec3 vec);
+static inline SDL_Vec3 SDL_Vec3Normalize(SDL_Vec3 vec);
+static inline SDL_Vec3 SDL_Vec3Add(SDL_Vec3 vec1, SDL_Vec3 vec2);
+static inline SDL_Vec3 SDL_Vec3Sub(SDL_Vec3 vec1, SDL_Vec3 vec2);
+static inline SDL_Vec3 SDL_Vec3MultiplyFloat(SDL_Vec3 vec, float val);
+static inline float SDL_Vec3Dot(SDL_Vec3 vec1, SDL_Vec3 vec2);
+static inline SDL_Vec3 SDL_Vec3Cross(SDL_Vec3 vec1, SDL_Vec3 vec2);
+
+// SDL Matrix functions
+static inline SDL_Mat4X4 SDL_Matrix4X4(
+ float m00, float m10, float m20, float m30,
+ float m01, float m11, float m21, float m31,
+ float m02, float m12, float m22, float m32,
+ float m03, float m13, float m23, float m33
+);
+static inline SDL_Mat4X4 SDL_MatrixIdentity(void);
+static inline SDL_Mat4X4 SDL_MatrixTranspose(SDL_Mat4X4 mat);
+static inline SDL_Mat4X4 SDL_MatrixMultiply(SDL_Mat4X4 mat1, SDL_Mat4X4 mat2);
+static inline SDL_Mat4X4 SDL_MatrixScaling(SDL_Vec3 scale);
+static inline SDL_Mat4X4 SDL_MatrixTranslation(SDL_Vec3 offset);
+static inline SDL_Mat4X4 SDL_MatrixRotationX(float angle);
+static inline SDL_Mat4X4 SDL_MatrixRotationY(float angle);
+static inline SDL_Mat4X4 SDL_MatrixRotationZ(float angle);
+
+static inline SDL_Mat4X4 SDL_MatrixOrtho(float left, float right, float bottom, float top, float back, float front);
+static inline SDL_Mat4X4 SDL_MatrixPerspective(float fovy, float aspect_ratio, float near, float far);
+static inline SDL_Mat4X4 SDL_MatrixLookAt(SDL_Vec3 pos, SDL_Vec3 target, SDL_Vec3 up);
+
+
+// Ends C function definitions when using C++
+#ifdef __cplusplus
+}
+#endif
+#endif /* SDL_MATH_3D_H */
+
+
+#ifdef SDL_MATH_3D_IMPLEMENTATION
+
+static inline SDL_Vec3 SDL_Vector3(float x, float y, float z) {
+ return (SDL_Vec3) {.x = x, .y = y, .z = z};
+}
+
+static inline float SDL_Vec3Magnitude(SDL_Vec3 vec)
+{
+ return SDL_sqrtf(vec.x*vec.x + vec.y*vec.y + vec.z*vec.z);
+}
+
+
+static inline SDL_Vec3 SDL_Vec3Normalize(SDL_Vec3 vec)
+{
+ float mag = SDL_Vec3Magnitude(vec);
+
+ if (mag == 0) {
+ return (SDL_Vec3) {0, 0, 0};
+ } else if (mag == 1){
+ return vec;
+ } else {
+ return (SDL_Vec3) {vec.x/mag, vec.y/mag, vec.z/mag};
+ }
+}
+
+static inline SDL_Vec3 SDL_Vec3Add(SDL_Vec3 vec1, SDL_Vec3 vec2)
+{
+ return SDL_Vector3(vec1.x + vec2.x, vec1.y + vec2.y, vec1.z + vec2.z);
+}
+
+static inline SDL_Vec3 SDL_Vec3Sub(SDL_Vec3 vec1, SDL_Vec3 vec2)
+{
+ return SDL_Vector3(vec1.x - vec2.x, vec1.y - vec2.y, vec1.z - vec2.z);
+}
+
+static inline SDL_Vec3 SDL_Vec3MultiplyFloat(SDL_Vec3 vec, float val)
+{
+ return SDL_Vector3(vec.x * val, vec.y * val, vec.z * val);
+}
+
+static inline float SDL_Vec3Dot(SDL_Vec3 vec1, SDL_Vec3 vec2)
+{
+ return (vec1.x * vec2.x + vec1.y * vec2.y + vec1.y * vec2.y);
+}
+
+static inline SDL_Vec3 SDL_Vec3Cross(SDL_Vec3 vec1, SDL_Vec3 vec2) {
+ return SDL_Vector3(
+ vec1.y * vec2.z - vec1.z * vec2.y,
+ vec1.z * vec2.x - vec1.x * vec2.z,
+ vec1.x * vec2.y - vec1.y * vec2.x
+ );
+}
+
+
+static inline SDL_Mat4X4 SDL_Matrix4X4(
+ float m00, float m10, float m20, float m30,
+ float m01, float m11, float m21, float m31,
+ float m02, float m12, float m22, float m32,
+ float m03, float m13, float m23, float m33
+) {
+ return (SDL_Mat4X4) {
+ .m[0][0] = m00, .m[1][0] = m10, .m[2][0] = m20, .m[3][0] = m30,
+ .m[0][1] = m01, .m[1][1] = m11, .m[2][1] = m21, .m[3][1] = m31,
+ .m[0][2] = m02, .m[1][2] = m12, .m[2][2] = m22, .m[3][2] = m32,
+ .m[0][3] = m03, .m[1][3] = m13, .m[2][3] = m23, .m[3][3] = m33
+ };
+}
+
+static inline SDL_Mat4X4 SDL_MatrixIdentity(void)
+{
+ return SDL_Matrix4X4(
+ 1, 0, 0, 0,
+ 0, 1, 0, 0,
+ 0, 0, 1, 0,
+ 0, 0, 0, 1
+ );
+}
+
+static inline SDL_Mat4X4 SDL_MatrixTranspose(SDL_Mat4X4 mat)
+{
+ SDL_Mat4X4 res;
+
+ for (int i = 0; i < 4; i++) {
+ for (int j = 0; j < 4; j++) {
+ res.m[i][j] = mat.m[i][j];
+ }
+ }
+
+ return res;
+}
+
+static inline SDL_Mat4X4 SDL_MatrixMultiply(SDL_Mat4X4 mat1, SDL_Mat4X4 mat2)
+{
+ SDL_Mat4X4 res;
+
+ for (int i = 0; i < 4; i++) {
+ for (int j = 0; j < 4; j++) {
+ float sum = 0;
+ for (int x = 0; x < 4; x++) {
+ sum += mat1.m[x][j] * mat2.m[i][x];
+ }
+ res.m[i][j] = sum;
+ }
+ }
+
+ return res;
+}
+
+static inline SDL_Mat4X4 SDL_MatrixScaling(SDL_Vec3 scale)
+{
+ float x = scale.x, y = scale.y, z = scale.z;
+ return SDL_Matrix4X4(
+ x, 0, 0, 0,
+ 0, y, 0, 0,
+ 0, 0, z, 0,
+ 0, 0, 0, 1
+ );
+}
+
+static inline SDL_Mat4X4 SDL_MatrixTranslation(SDL_Vec3 offset)
+{
+ return SDL_Matrix4X4(
+ 1, 0, 0, offset.x,
+ 0, 1, 0, offset.y,
+ 0, 0, 1, offset.z,
+ 0, 0, 0, 1
+ );
+}
+
+static inline SDL_Mat4X4 SDL_MatrixRotationX(float angle)
+{
+ float cos = SDL_cos(angle);
+ float sin = SDL_sin(angle);
+
+ return SDL_Matrix4X4(
+ 1, 0, 0, 0,
+ 0, cos, -sin, 0,
+ 0, sin, cos, 0,
+ 0, 0, 0, 1
+ );
+}
+
+static inline SDL_Mat4X4 SDL_MatrixRotationY(float angle)
+{
+ float cos = SDL_cos(angle);
+ float sin = SDL_sin(angle);
+
+ return SDL_Matrix4X4(
+ cos, 0, sin, 0,
+ 0, 1, 0, 0,
+ -sin, 0, cos, 0,
+ 0, 0, 0, 1
+ );
+}
+
+static inline SDL_Mat4X4 SDL_MatrixRotationZ(float angle)
+{
+ float cos = SDL_cos(angle);
+ float sin = SDL_sin(angle);
+
+ return SDL_Matrix4X4(
+ cos, -sin, 0, 0,
+ sin, cos, 0, 0,
+ 0, 0, 1, 0,
+ 0, 0, 0, 1
+ );
+}
+
+static inline SDL_Mat4X4 SDL_MatrixOrtho(float left, float right, float bottom, float top, float near, float far)
+{
+ float l = left, r = right, b = bottom, t = top, n = near, f = far;
+
+ float dx = -(r + l) / (r - l);
+ float dy = -(t + b) / (t - b);
+ float dz = -(f + n) / (f - n);
+
+ return SDL_Matrix4X4(
+ 2 / (r - l), 0, 0, dx,
+ 0, 2 / (t - b), 0, dy,
+ 0, 0, 2 / (f - n), dz,
+ 0, 0, 0, 1
+ );
+}
+
+static inline SDL_Mat4X4 SDL_MatrixPerspective(float fovy, float aspect_ratio, float near, float far)
+{
+ float n = near; float f = far;
+ float t = SDL_tanf(fovy/2.0f) * n;
+ float b = -t;
+ float r = t * aspect_ratio;
+ float l = -r;
+
+ return SDL_Matrix4X4(
+ (2 * n) / (r - l), 0, (r + l) / (r - l), 0,
+ 0, (2 * n) / (t - b), (t + b) / (t - b), 0,
+ 0, 0, -(f + n) / (f - n), -(2 * n * f) / (f - n),
+ 0, 0, -1, 1
+ );
+}
+
+static inline SDL_Mat4X4 SDL_MatrixLookAt(SDL_Vec3 pos, SDL_Vec3 target, SDL_Vec3 up)
+{
+ SDL_Vec3 d = SDL_Vec3Normalize(SDL_Vec3Sub(target, pos));
+ SDL_Vec3 u = SDL_Vec3Normalize(up);
+ SDL_Vec3 r = SDL_Vec3Normalize(SDL_Vec3Cross(u, d));
+ u = SDL_Vec3Cross(r, d);
+
+ return SDL_Matrix4X4(
+ r.x, r.y, r.z, -SDL_Vec3Dot(r, pos),
+ u.x, u.y, u.z, -SDL_Vec3Dot(u, pos),
+ -d.x, -d.y, -d.z, SDL_Vec3Dot(d, pos),
+ 0, 0, 0, 1
+ );
+}
+
+#endif /* SDL_MATH_3D_IMPLEMENTATION */
diff --git a/examples/testgputext/fonts/NotoSansMono-Regular.ttf b/examples/testgputext/fonts/NotoSansMono-Regular.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..c2bbb5a39435f859e049d6789e4592b281a1f3d0
GIT binary patch
literal 405892
zcmdSC2bdJa*2jISXG72I&X7U!l5-A{a}I)l0tN)hND@#mW59@lC>aFFpeW{y5tU%X
z07j6Yq9BS{VZYzMcN+F`(d+x(_j{i2ljm2by1K%tb53>l)U4Cy%q%ZSidn~Y?K>>!
zz4)+Ml)|(f9eeiZ{pLee7n$uJZFcS59ebbEX5H>%&CT-cFe`CSkKVQFUSyT;GIO8M
zU|{b)t^546>9b6Z=W*O_@R&j4hpbxkg4rr)xo-HNiQ_5H%lU#xfB5LjhD|-^k?!WD
z_p_9$RYnXQG^FgM>o#+KAm<y7pum4S|9Xy_aa?}Hm`PJo5B>U(S@kJq%~M8?8$77g
z>h<@Vjl7cMc4G!j8J}m3n??N+)Xy3_Xw1;f8(ldwkM?P=j2|~~(u^m5ENE^7r<a^H
ze!|f4Bhsc%qdq!Y`jaku+J=<dIW(7@ZF!KEr>84#oozHGk)=-jm#SKrt;VsBY3d)H
zcXV#Le^MRGQ#UB_B6A)r^kj_-@-+L&B9X7u$){7d?UUTyd`{V;^NwAS?x(!LVVZf#
zKGeI<ky*hYEpOiB;UgwF#+*E4(g>$|dGVU+c`lC)p=Ja;j%FgBC7<HmKt7eDTfKf9
zcg>M*fYo(vD9uQXrlh8LUdk@>vOW7V+c#@0|8(rpqociGmYtn5zmzwethD6w4{3Fp
zw}rm46fu_NZe&!KqJVik<>qAGS&5X<$-HNYlmW?n#9E|eCG#m(J@R8RpK8S-A0_i?
zRyp!;GM{ekB4d+z->OF1CG%10lt|`d*2W9lW>^bvQL;SKnt210`M4|VS|sxcYv}xB
zzNnpMhja3nHOt;k=AC8QE6KcPF?%|hkKmD{ZY~AC+><O%rOw^SeA<cmx^((U+JrIl
z+LUCSDE*92=3|!MlJ;>K^wTF<o@phlQ8FK=PQ_$CVa4b*usL2=+@4|MY=VukK}2b9
z8%l1ZwWVw<F)@^MBI%G*l(a{mNwgevigRt~Ymg1Li|j<LHuO1~o<bRp6&`mF#u;gs
zpg}iu7;Yoz|3v!}Wqmj^63zOO4x!Enazl@|Xw4{-IG#X_K|wCdhFJ}2jpN@%!F(r?
z>&;ost~>d$pwT8_*NN7|YVmI(`cI(O@sv)qnlO@{Yf^Ipy4A8C)&V_pbeV|vI|Thq
zBG;STFj`G!gb8SpMN8EfjU3EzXwxj_nq%b&oYg#3w>Q^|Zpa>tth%{-mJLqyP@QvU
zQ)fb;n`V(^b<j{`<<jh5#|^a^WS63!<}}DIB-PlHIhGz-lxf`7c4m;DgiW&kmlg``
znq$?;j4>Jg#st<H$7q)Y^Q_6d#!)J}Pr@!k(By<q{<ZCJ&Wytc()mQY;T%n(y?iPE
z*0Oe#$ZNwXQw(P@&&x<dkB4JtaqdJ-d15H@A4A{&UNd)}vT;t$f3Me>_JbH%aXOqa
zJ0l0vqgI==JK+V5r&S~!)Mf%PnnjIaq#cprOtXlJZmhKveViP@HLMqX499x%>4bla
zjk>h0O?1@HrA64s39DqC)XRxhP0^<Z|2kUJ<2{7!Lkpa&!@p{DV)kLX6XWWdGamnF
z&E>59EUSyYb@`VQ&5F<yJ@lmJI2*-`2V=o3V)YWFR=L(gmR0_@c$GI);}YWFMBIe&
zGK`jEIX{uek^LqGzN>*QTEj!}N)1NJ(Qh<0v=Xc6?itb3i2h4(l*S#7d!xG*xTm^S
zl=F1K)!13h_?r0sL{_3qqHLmEqC%o#qH>~2qH3aAqI#lbqIIH8qFthMqHCgCVshg0
z#EpsD5;GDr6L%(NCGJYhPRvQnP0UX$NGwV$PTZ4Nl31EpmRO!xkyx2nl~|qF#+^GW
z<=GVO+v#Vfp
(Patch may be truncated, please check the link at the top of this post.)