/*
* Tuxánci 2 - A first person shooter
* Copyright (C) 2025-2026 Connor Thomson
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include
#include
#include
#include "ta_sdl.h"
#include "window.h"
#include "files.h"
#include "text.h"
#include "logging.h"
#include "screen.h"
#define SPIRV_ENTRY_POINT "main"
typedef enum ta_gpu_shape_type {
TA_GPU_SHAPE_FILLED_RECTANGLE,
TA_GPU_SHAPE_RECTANGLE,
TA_GPU_SHAPE_LINE,
TA_GPU_SHAPE_FILLED_CIRCLE,
TA_GPU_SHAPE_CIRCLE,
TA_GPU_SHAPE_FILLED_TRIANGLE,
TA_GPU_SHAPE_TRIANGLE,
} ta_gpu_shape_type;
typedef struct ta_gpu_shape {
ta_gpu_shape_type type;
float bounds[4];
float points[3][2];
float color[4];
float thickness;
} ta_gpu_shape;
typedef struct ta_gpu_shape_uniforms {
float bounds[4];
float points[3][4];
float viewport[4];
float color[4];
float params[4];
} ta_gpu_shape_uniforms;
// NOTE: Does this formatting look good?
SDL_GPUDevice *ta_gpu_device;
SDL_GPUGraphicsPipeline *ta_gpu_text_pipeline;
SDL_GPUTexture *ta_gpu_text_texture;
SDL_GPUSampler *ta_gpu_text_sampler;
Uint32 ta_gpu_text_texture_width;
Uint32 ta_gpu_text_texture_height;
static SDL_GPUGraphicsPipeline *ta_gpu_shape_pipeline;
static SDL_GPUTexture *ta_gpu_msaa_texture;
static SDL_GPUTexture *ta_gpu_depth_texture;
SDL_GPUSampleCount ta_gpu_sample_count = SDL_GPU_SAMPLECOUNT_8;
SDL_GPUTextureFormat ta_gpu_swapchain_format;
bool ta_gpu_enable_wireframe = false;
static Uint32 ta_gpu_msaa_texture_width;
static Uint32 ta_gpu_msaa_texture_height;
static SDL_GPUCommandBuffer *ta_gpu_command_buffer;
static SDL_GPUTexture *ta_gpu_swapchain_texture;
static ta_gpu_shape *ta_gpu_shapes;
static size_t ta_gpu_shape_count;
static size_t ta_gpu_shape_capacity;
static void ta_gpu_resize_msaa_texture(SDL_GPUTextureFormat format) {
if (ta_gpu_msaa_texture_width == (Uint32)ta_window_width &&
ta_gpu_msaa_texture_height == (Uint32)ta_window_height &&
ta_gpu_msaa_texture) {
return;
}
SDL_ReleaseGPUTexture(ta_gpu_device, ta_gpu_msaa_texture);
SDL_ReleaseGPUTexture(ta_gpu_device, ta_gpu_depth_texture);
ta_gpu_msaa_texture = NULL;
ta_gpu_depth_texture = NULL;
SDL_GPUTextureCreateInfo texture_info = {
.type = SDL_GPU_TEXTURETYPE_2D,
.format = format,
.usage = SDL_GPU_TEXTUREUSAGE_COLOR_TARGET,
.width = (Uint32)ta_window_width,
.height = (Uint32)ta_window_height,
.layer_count_or_depth = 1,
.num_levels = 1,
.sample_count = ta_gpu_sample_count,
};
ta_gpu_msaa_texture = ta_sdl_create_gpu_texture(ta_gpu_device, &texture_info);
texture_info.format = SDL_GPU_TEXTUREFORMAT_D32_FLOAT;
texture_info.usage = SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET;
ta_gpu_depth_texture = ta_sdl_create_gpu_texture(ta_gpu_device, &texture_info);
ta_gpu_msaa_texture_width = (Uint32)ta_window_width;
ta_gpu_msaa_texture_height = (Uint32)ta_window_height;
}
static SDL_GPUShader *ta_gpu_load_shader(SDL_GPUDevice *device, const void *code, size_t code_size, SDL_GPUShaderStage stage, Uint32 num_samplers, Uint32 num_uniform_buffers) {
SDL_GPUShaderCreateInfo info = {
.code = code,
.code_size = code_size,
.entrypoint = SPIRV_ENTRY_POINT,
.format = SDL_GPU_SHADERFORMAT_SPIRV,
.stage = stage,
.num_samplers = num_samplers,
.num_uniform_buffers = num_uniform_buffers,
};
SDL_GPUShader *shader = ta_sdl_create_gpu_shader(device, &info);
return shader;
}
void ta_gpu_init(void) {
ta_gpu_device = ta_sdl_create_gpu_device();
ta_sdl_claim_window_for_gpu_device(ta_gpu_device, ta_window);
ta_gpu_swapchain_format = SDL_GetGPUSwapchainTextureFormat(ta_gpu_device, ta_window);
ta_gpu_resize_msaa_texture(ta_gpu_swapchain_format);
SDL_GPUShader *vertex_shader = ta_gpu_load_shader(
ta_gpu_device,
file_text_vert_slang_spv_start,
file_text_vert_slang_spv_size,
SDL_GPU_SHADERSTAGE_VERTEX,
0,
1
);
SDL_GPUShader *fragment_shader = ta_gpu_load_shader(
ta_gpu_device,
file_text_frag_slang_spv_start,
file_text_frag_slang_spv_size,
SDL_GPU_SHADERSTAGE_FRAGMENT,
1,
0
);
SDL_GPUColorTargetDescription color_target = {
.format = ta_gpu_swapchain_format,
.blend_state = {
.src_color_blendfactor = SDL_GPU_BLENDFACTOR_SRC_ALPHA,
.dst_color_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA,
.color_blend_op = SDL_GPU_BLENDOP_ADD,
.src_alpha_blendfactor = SDL_GPU_BLENDFACTOR_ONE,
.dst_alpha_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA,
.alpha_blend_op = SDL_GPU_BLENDOP_ADD,
.enable_blend = true,
},
};
SDL_GPUGraphicsPipelineCreateInfo pipeline_info = {
.vertex_shader = vertex_shader,
.fragment_shader = fragment_shader,
.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST,
.target_info = {
.num_color_targets = 1,
.color_target_descriptions = &color_target,
},
.multisample_state = {
.sample_count = ta_gpu_sample_count,
},
};
ta_gpu_text_pipeline = ta_sdl_create_gpu_graphics_pipeline(ta_gpu_device, &pipeline_info);
SDL_ReleaseGPUShader(ta_gpu_device, vertex_shader);
SDL_ReleaseGPUShader(ta_gpu_device, fragment_shader);
vertex_shader = ta_gpu_load_shader(
ta_gpu_device,
file_shape_vert_slang_spv_start,
file_shape_vert_slang_spv_size,
SDL_GPU_SHADERSTAGE_VERTEX,
0,
1
);
fragment_shader = ta_gpu_load_shader(
ta_gpu_device,
file_shape_frag_slang_spv_start,
file_shape_frag_slang_spv_size,
SDL_GPU_SHADERSTAGE_FRAGMENT,
0,
1
);
pipeline_info.vertex_shader = vertex_shader;
pipeline_info.fragment_shader = fragment_shader;
pipeline_info.rasterizer_state.cull_mode = SDL_GPU_CULLMODE_NONE;
pipeline_info.depth_stencil_state = (SDL_GPUDepthStencilState){ 0 };
ta_gpu_shape_pipeline = ta_sdl_create_gpu_graphics_pipeline(ta_gpu_device, &pipeline_info);
SDL_ReleaseGPUShader(ta_gpu_device, vertex_shader);
SDL_ReleaseGPUShader(ta_gpu_device, fragment_shader);
SDL_GPUSamplerCreateInfo sampler_info = {
.min_filter = SDL_GPU_FILTER_LINEAR,
.mag_filter = SDL_GPU_FILTER_LINEAR,
.mipmap_mode = SDL_GPU_SAMPLERMIPMAPMODE_NEAREST,
.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,
};
ta_gpu_text_sampler = ta_sdl_create_gpu_sampler(ta_gpu_device, &sampler_info);
}
void ta_gpu_start_frame(void) {
ta_gpu_shape_count = 0;
ta_gpu_command_buffer = ta_sdl_acquire_gpu_command_buffer(ta_gpu_device);
ta_gpu_swapchain_texture = NULL;
ta_gpu_swapchain_format = SDL_GetGPUSwapchainTextureFormat(ta_gpu_device, ta_window);
ta_sdl_wait_and_acquire_gpu_swapchain_texture(ta_gpu_command_buffer, ta_window, &ta_gpu_swapchain_texture);
if (ta_gpu_swapchain_texture) {
ta_gpu_resize_msaa_texture(ta_gpu_swapchain_format);
}
}
static void ta_gpu_queue_shape(const ta_gpu_shape *shape) {
if (ta_gpu_shape_count == ta_gpu_shape_capacity) {
size_t next_capacity = ta_gpu_shape_capacity == 0 ? 64 : ta_gpu_shape_capacity * 2;
ta_gpu_shape *shapes = realloc(ta_gpu_shapes, next_capacity * sizeof(*shapes));
if (!shapes) {
return;
}
ta_gpu_shapes = shapes;
ta_gpu_shape_capacity = next_capacity;
}
ta_gpu_shapes[ta_gpu_shape_count++] = *shape;
}
static void ta_gpu_set_shape_color(float output[4], ta_color color) {
output[0] = (float)color.red / 255.0f;
output[1] = (float)color.green / 255.0f;
output[2] = (float)color.blue / 255.0f;
output[3] = (float)color.alpha / 255.0f;
}
void ta_gpu_draw_filled_rectangle(ta_color color, int x, int y, int width, int height) {
if (width <= 0 || height <= 0) {
return;
}
ta_gpu_shape shape = {
.type = TA_GPU_SHAPE_FILLED_RECTANGLE
};
shape.bounds[0] = (float)x;
shape.bounds[1] = (float)y;
shape.bounds[2] = (float)width;
shape.bounds[3] = (float)height;
ta_gpu_set_shape_color(shape.color, color);
ta_gpu_queue_shape(&shape);
}
void ta_gpu_draw_rectangle(ta_color color, int x, int y, int width, int height, int thickness) {
if (width <= 0 || height <= 0 || thickness <= 0) {
return;
}
ta_gpu_shape shape = {
.type = TA_GPU_SHAPE_RECTANGLE
};
shape.bounds[0] = (float)x;
shape.bounds[1] = (float)y;
shape.bounds[2] = (float)width;
shape.bounds[3] = (float)height;
shape.thickness = (float)thickness;
ta_gpu_set_shape_color(shape.color, color);
ta_gpu_queue_shape(&shape);
}
void ta_gpu_draw_line(ta_color color, int x1, int y1, int x2, int y2, int thickness) {
if (thickness <= 0 || (x1 == x2 && y1 == y2)) {
return;
}
ta_gpu_shape shape = {
.type = TA_GPU_SHAPE_LINE
};
shape.points[0][0] = (float)x1;
shape.points[0][1] = (float)y1;
shape.points[1][0] = (float)x2;
shape.points[1][1] = (float)y2;
shape.thickness = (float)thickness;
ta_gpu_set_shape_color(shape.color, color);
ta_gpu_queue_shape(&shape);
}
void ta_gpu_draw_filled_circle(ta_color color, int x, int y, int radius) {
if (radius <= 0) {
return;
}
ta_gpu_shape shape = {
.type = TA_GPU_SHAPE_FILLED_CIRCLE
};
shape.bounds[0] = (float)(x - radius);
shape.bounds[1] = (float)(y - radius);
shape.bounds[2] = (float)(radius * 2);
shape.bounds[3] = (float)(radius * 2);
ta_gpu_set_shape_color(shape.color, color);
ta_gpu_queue_shape(&shape);
}
void ta_gpu_draw_circle(ta_color color, int x, int y, int radius, int thickness) {
if (radius <= 0 || thickness <= 0) {
return;
}
ta_gpu_shape shape = {
.type = TA_GPU_SHAPE_CIRCLE
};
shape.bounds[0] = (float)(x - radius);
shape.bounds[1] = (float)(y - radius);
shape.bounds[2] = (float)(radius * 2);
shape.bounds[3] = (float)(radius * 2);
shape.thickness = (float)thickness;
ta_gpu_set_shape_color(shape.color, color);
ta_gpu_queue_shape(&shape);
}
static void ta_gpu_set_triangle_points(ta_gpu_shape *shape, int x1, int y1, int x2, int y2, int x3, int y3) {
shape->points[0][0] = (float)x1;
shape->points[0][1] = (float)y1;
shape->points[1][0] = (float)x2;
shape->points[1][1] = (float)y2;
shape->points[2][0] = (float)x3;
shape->points[2][1] = (float)y3;
}
void ta_gpu_draw_filled_triangle(ta_color color, int x1, int y1, int x2, int y2, int x3, int y3) {
ta_gpu_shape shape = {
.type = TA_GPU_SHAPE_FILLED_TRIANGLE
};
ta_gpu_set_triangle_points(&shape, x1, y1, x2, y2, x3, y3);
ta_gpu_set_shape_color(shape.color, color);
ta_gpu_queue_shape(&shape);
}
void ta_gpu_draw_triangle(ta_color color, int x1, int y1, int x2, int y2, int x3, int y3, int thickness) {
if (thickness <= 0) {
return;
}
ta_gpu_shape shape = {
.type = TA_GPU_SHAPE_TRIANGLE
};
ta_gpu_set_triangle_points(&shape, x1, y1, x2, y2, x3, y3);
shape.thickness = (float)thickness;
ta_gpu_set_shape_color(shape.color, color);
ta_gpu_queue_shape(&shape);
}
void ta_gpu_end_frame(void) {
if (ta_gpu_swapchain_texture) {
ta_gpu_upload_text(ta_gpu_command_buffer);
ta_screen_upload(ta_gpu_command_buffer);
SDL_GPUColorTargetInfo color_target = {
.texture = ta_gpu_msaa_texture,
.load_op = SDL_GPU_LOADOP_CLEAR,
.store_op = SDL_GPU_STOREOP_RESOLVE,
.resolve_texture = ta_gpu_swapchain_texture,
.clear_color = {
ta_color_black.red,
ta_color_black.green,
ta_color_black.blue,
ta_color_black.alpha
},
};
SDL_GPUDepthStencilTargetInfo depth_target = {
.texture = ta_gpu_depth_texture,
.clear_depth = 1.0f,
.load_op = SDL_GPU_LOADOP_CLEAR,
.store_op = SDL_GPU_STOREOP_DONT_CARE,
.stencil_load_op = SDL_GPU_LOADOP_DONT_CARE,
.stencil_store_op = SDL_GPU_STOREOP_DONT_CARE,
};
SDL_GPURenderPass *render_pass = ta_sdl_begin_gpu_render_pass(
ta_gpu_command_buffer,
&color_target,
1,
&depth_target
);
ta_text_render(ta_gpu_command_buffer, render_pass);
ta_screen_render(ta_gpu_command_buffer, render_pass);
SDL_BindGPUGraphicsPipeline(render_pass, ta_gpu_shape_pipeline);
for (size_t index = 0; index < ta_gpu_shape_count; index++) {
ta_gpu_shape *shape = &ta_gpu_shapes[index];
ta_gpu_shape_uniforms uniforms = {
0
};
memcpy(uniforms.bounds, shape->bounds, sizeof(uniforms.bounds));
for (int point = 0; point < 3; point++) {
uniforms.points[point][0] = shape->points[point][0];
uniforms.points[point][1] = shape->points[point][1];
}
uniforms.viewport[0] = (float)ta_window_width;
uniforms.viewport[1] = (float)ta_window_height;
memcpy(uniforms.color, shape->color, sizeof(uniforms.color));
uniforms.params[0] = (float)shape->type;
uniforms.params[1] = shape->thickness;
SDL_PushGPUVertexUniformData(ta_gpu_command_buffer, 0, &uniforms, sizeof(uniforms));
SDL_PushGPUFragmentUniformData(ta_gpu_command_buffer, 0, &uniforms, sizeof(uniforms));
Uint32 vertex_count = shape->type == TA_GPU_SHAPE_FILLED_TRIANGLE ? 3 : shape->type == TA_GPU_SHAPE_TRIANGLE ? 18 : 6;
SDL_DrawGPUPrimitives(render_pass, vertex_count, 1, 0, 0);
}
SDL_EndGPURenderPass(render_pass);
}
ta_sdl_submit_gpu_command_buffer(ta_gpu_command_buffer);
}