/* * 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; 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); }