summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/gpu.h10
-rw-r--r--shaders/shape.frag.slang65
-rw-r--r--shaders/shape.vert.slang94
-rw-r--r--src/gpu.c214
4 files changed, 382 insertions, 1 deletions
diff --git a/include/gpu.h b/include/gpu.h
index ceba781..5e10d1e 100644
--- a/include/gpu.h
+++ b/include/gpu.h
@@ -20,6 +20,7 @@
#define TA_GPU_H
#include <stdbool.h>
+#include "color.h"
#include "ta_sdl.h"
extern SDL_GPUDevice *ta_gpu_device;
@@ -33,6 +34,13 @@ extern SDL_GPUTextureFormat ta_gpu_swapchain_format;
void ta_gpu_init(void);
void ta_gpu_start_frame(void);
+void ta_gpu_draw_filled_rectangle(ta_color color, int x, int y, int width, int height);
+void ta_gpu_draw_rectangle(ta_color color, int x, int y, int width, int height, int thickness);
+void ta_gpu_draw_line(ta_color color, int x1, int y1, int x2, int y2, int thickness);
+void ta_gpu_draw_circle(ta_color color, int x, int y, int radius, int thickness);
+void ta_gpu_draw_filled_circle(ta_color color, int x, int y, int radius);
+void ta_gpu_draw_triangle(ta_color color, int x1, int y1, int x2, int y2, int x3, int y3, int thickness);
+void ta_gpu_draw_filled_triangle(ta_color color, int x1, int y1, int x2, int y2, int x3, int y3);
void ta_gpu_end_frame(void);
-#endif // TA_RENDERER_H \ No newline at end of file
+#endif // TA_GPU_H \ No newline at end of file
diff --git a/shaders/shape.frag.slang b/shaders/shape.frag.slang
new file mode 100644
index 0000000..b75625b
--- /dev/null
+++ b/shaders/shape.frag.slang
@@ -0,0 +1,65 @@
+/*
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+struct ShapeFragmentUniforms {
+ float4 bounds;
+ float4 points[3];
+ float4 viewport;
+ float4 color;
+ float4 params;
+};
+
+[[vk::binding(0, 3)]]
+ConstantBuffer<ShapeFragmentUniforms> uniforms : register(b0, space3);
+
+struct FSOutput {
+ float4 color : SV_Target0;
+};
+
+FSOutput main(float2 uv : TEXCOORD0) {
+ uint shape_type = (uint)uniforms.params.x;
+ float distance_from_edge = 0.0;
+ float coverage = 1.0;
+
+ if (shape_type == 1) {
+ distance_from_edge = min(
+ min(uv.x * uniforms.bounds.z, (1.0 - uv.x) * uniforms.bounds.z),
+ min(uv.y * uniforms.bounds.w, (1.0 - uv.y) * uniforms.bounds.w)
+ );
+ float edge_width = max(fwidth(distance_from_edge), 0.5);
+ coverage = 1.0 - smoothstep(uniforms.params.y - edge_width, uniforms.params.y + edge_width, distance_from_edge);
+ } else if (shape_type >= 3 && shape_type <= 4) {
+ float2 center_offset = (uv - 0.5) * uniforms.bounds.zw;
+ float radius = min(uniforms.bounds.z, uniforms.bounds.w) * 0.5;
+ float distance_from_center = length(center_offset);
+ float edge_width = max(fwidth(distance_from_center), 0.5);
+ if (shape_type == 3 && distance_from_center > radius) {
+ coverage = 1.0 - smoothstep(radius - edge_width, radius + edge_width, distance_from_center);
+ }
+ if (shape_type == 4) {
+ float outer_coverage = 1.0 - smoothstep(radius - edge_width, radius + edge_width, distance_from_center);
+ float inner_coverage = smoothstep(radius - uniforms.params.y - edge_width, radius - uniforms.params.y + edge_width, distance_from_center);
+ coverage = outer_coverage * inner_coverage;
+ }
+ }
+
+ FSOutput output;
+ output.color = uniforms.color;
+ output.color.a *= coverage;
+ return output;
+}
diff --git a/shaders/shape.vert.slang b/shaders/shape.vert.slang
new file mode 100644
index 0000000..34834a4
--- /dev/null
+++ b/shaders/shape.vert.slang
@@ -0,0 +1,94 @@
+/*
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+struct ShapeVertexUniforms {
+ float4 bounds;
+ float4 points[3];
+ float4 viewport;
+ float4 color;
+ float4 params;
+};
+
+[[vk::binding(0, 1)]]
+ConstantBuffer<ShapeVertexUniforms> uniforms : register(b0, space1);
+
+struct VSOutput {
+ float4 position : SV_Position;
+ float2 uv : TEXCOORD0;
+};
+
+float2 to_clip(float2 point) {
+ return float2(
+ 2.0 * point.x / uniforms.viewport.x - 1.0,
+ 1.0 - 2.0 * point.y / uniforms.viewport.y
+ );
+}
+
+float2 line_vertex(float2 first, float2 second, uint vertexID) {
+ float2 direction = normalize(second - first);
+ float2 normal = float2(-direction.y, direction.x) * uniforms.params.y * 0.5;
+ static const float2 corners[6] = {
+ float2( 1.0, 1.0),
+ float2( 1.0, -1.0),
+ float2(-1.0, 1.0),
+ float2(-1.0, 1.0),
+ float2( 1.0, -1.0),
+ float2(-1.0, -1.0)
+ };
+ float2 corner = corners[vertexID];
+ return lerp(first, second, corner.x * 0.5 + 0.5) + normal * corner.y;
+}
+
+VSOutput main(uint vertexID : SV_VertexID) {
+ static const float2 quad_positions[6] = {
+ float2(0.0, 0.0),
+ float2(1.0, 0.0),
+ float2(0.0, 1.0),
+ float2(0.0, 1.0),
+ float2(1.0, 0.0),
+ float2(1.0, 1.0)
+ };
+
+ uint shape_type = (uint)uniforms.params.x;
+ float2 position;
+ float2 uv = float2(0.0, 0.0);
+
+ if (shape_type <= 1 || shape_type >= 3 && shape_type <= 4) {
+ uv = quad_positions[vertexID];
+ position = uniforms.bounds.xy + uv * uniforms.bounds.zw;
+ } else if (shape_type == 2) {
+ position = line_vertex(uniforms.points[0].xy, uniforms.points[1].xy, vertexID);
+ } else if (shape_type == 5) {
+ position = uniforms.points[vertexID].xy;
+ } else {
+ uint edge = vertexID / 6;
+ uint edge_vertex = vertexID % 6;
+ uint first_index = edge;
+ uint second_index = (edge + 1) % 3;
+ position = line_vertex(
+ uniforms.points[first_index].xy,
+ uniforms.points[second_index].xy,
+ edge_vertex
+ );
+ }
+
+ VSOutput output;
+ output.position = float4(to_clip(position), 0.0, 1.0);
+ output.uv = uv;
+ return output;
+}
diff --git a/src/gpu.c b/src/gpu.c
index 4cba190..8bc5721 100644
--- a/src/gpu.c
+++ b/src/gpu.c
@@ -17,6 +17,8 @@
*/
#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
#include "ta_sdl.h"
#include "window.h"
#include "files.h"
@@ -26,6 +28,32 @@
#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;
@@ -33,6 +61,7 @@
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;
@@ -41,6 +70,9 @@ 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 &&
@@ -140,6 +172,30 @@ void ta_gpu_init(void) {
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,
@@ -152,6 +208,7 @@ void ta_gpu_init(void) {
}
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);
@@ -162,6 +219,142 @@ void ta_gpu_start_frame(void) {
}
}
+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);
@@ -197,6 +390,27 @@ void ta_gpu_end_frame(void) {
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);
}