From 6e65700f38cc310f11c2511514559234aa2cf16c Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Sat, 7 Jun 2025 12:42:57 +0200 Subject: [PATCH] scary quads and nice sprites --- resources/shaders/source/font.frag.glsl | 18 ---- resources/shaders/source/font.vert.glsl | 26 ----- resources/shaders/source/sprite.frag.glsl | 14 +++ resources/shaders/source/sprite.vert.glsl | 22 ++++ src/renderer.c3 | 119 ++++++++++++++++++++-- test_renderer.c3 | 83 +++++++++++++-- 6 files changed, 218 insertions(+), 64 deletions(-) delete mode 100644 resources/shaders/source/font.frag.glsl delete mode 100644 resources/shaders/source/font.vert.glsl create mode 100644 resources/shaders/source/sprite.frag.glsl create mode 100644 resources/shaders/source/sprite.vert.glsl diff --git a/resources/shaders/source/font.frag.glsl b/resources/shaders/source/font.frag.glsl deleted file mode 100644 index 1e43b1a..0000000 --- a/resources/shaders/source/font.frag.glsl +++ /dev/null @@ -1,18 +0,0 @@ -#version 330 core - -// viewsize.x = viewport width in pixels; viewsize.y = viewport height in pixels -uniform ivec2 viewsize; -uniform ivec2 texturesize; - -// texture uv coordinate in texture space -in vec2 uv; -uniform sampler2DRect ts; - -const vec3 textcolor = vec3(1.0, 1.0, 1.0); - - -void main() -{ - //gl_FragColor = vec4(1.0f,0.0f,0.0f,1.0f); - gl_FragColor = vec4(textcolor, texture(ts, uv)); -} diff --git a/resources/shaders/source/font.vert.glsl b/resources/shaders/source/font.vert.glsl deleted file mode 100644 index 8d429cb..0000000 --- a/resources/shaders/source/font.vert.glsl +++ /dev/null @@ -1,26 +0,0 @@ -#version 330 core - -// viewsize.x = viewport width in pixels; viewsize.y = viewport height in pixels -uniform ivec2 viewsize; -uniform ivec2 texturesize; - -// both position and and uv are in pixels, they where converted to floats when -// passed to the shader -layout(location = 0) in vec2 position; -layout(location = 1) in vec2 txcoord; - -out vec2 uv; - -void main() -{ - vec2 v = vec2(float(viewsize.x), float(viewsize.y)); - -// vec2 p = vec2(position.x*2.0f/v.x - 1.0f, position.y*2.0f/v.y - 1.0f); - vec2 p = vec2(position.x*2.0/v.x - 1.0, 1.0 - position.y*2.0/v.y); - vec4 pos = vec4(p.x, p.y, 0.0f, 1.0f); - - gl_Position = pos; - // since the texture is a GL_TEXTURE_RECTANGLE the coordintes do not need to - // be normalized - uv = vec2(txcoord.x, txcoord.y); -} diff --git a/resources/shaders/source/sprite.frag.glsl b/resources/shaders/source/sprite.frag.glsl new file mode 100644 index 0000000..142cbf5 --- /dev/null +++ b/resources/shaders/source/sprite.frag.glsl @@ -0,0 +1,14 @@ +#version 450 + +layout(location = 0) in vec2 uv; +layout(location = 0) out vec4 fragColor; + +layout(set = 2, binding = 0) uniform sampler2D tx; + +void main() +{ + ivec2 ts = textureSize(tx, 0); + vec2 fts = vec2(float(ts.x), float(ts.y)); + vec2 real_uv = uv / fts; + fragColor = texture(tx, real_uv); +} \ No newline at end of file diff --git a/resources/shaders/source/sprite.vert.glsl b/resources/shaders/source/sprite.vert.glsl new file mode 100644 index 0000000..4ecba3d --- /dev/null +++ b/resources/shaders/source/sprite.vert.glsl @@ -0,0 +1,22 @@ +#version 450 + +layout(set = 1, binding = 0) uniform Viewport { + ivec2 view; + ivec2 not_needed; +}; + +layout(location = 0) in ivec2 position; +layout(location = 1) in ivec2 in_uv; +layout(location = 2) in ivec4 color; + +layout(location = 0) out vec2 out_uv; + +void main() +{ + vec2 pos; + pos.x = float(position.x)*2.0 / view.x - 1.0; + pos.y = -(float(position.y)*2.0 / view.y - 1.0); + gl_Position = vec4(pos, 0.0, 1.0); + + out_uv = vec2(float(in_uv.x), float(in_uv.y)); +} diff --git a/src/renderer.c3 b/src/renderer.c3 index f4f63fe..ebcfd54 100644 --- a/src/renderer.c3 +++ b/src/renderer.c3 @@ -29,12 +29,13 @@ struct Texture { } // The GPU buffers that contain quad info, the size is determined by MAX_QUAD_BATCH -const int MAX_QUAD_BATCH = 16; +const int MAX_QUAD_BATCH = 128; struct QuadBuffer { sdl::GPUBuffer* vert_buf; sdl::GPUBuffer* idx_buf; bool initialized; int count; + int off; // the offset to draw from } alias ShaderList = List{Shader}; @@ -373,14 +374,15 @@ fn void Renderer.create_pipeline(&self, String shader_name, PipelineType type) self.pipelines.push(p); } +// NOTE: with TEXTUREUSAGE_SAMPLER the texture format cannot be intger _UINT so it has to be nermalized enum TextureType : (GPUTextureFormat format) { - FULL_COLOR = GPU_TEXTUREFORMAT_R8G8B8A8_UINT, - JUST_ALPHA = GPU_TEXTUREFORMAT_R8_UINT + FULL_COLOR = GPU_TEXTUREFORMAT_R8G8B8A8_UNORM, + JUST_ALPHA = GPU_TEXTUREFORMAT_R8_UNORM } // create a new gpu texture from a pixel buffer, the format has to be specified // the new texture s given an id and pushed into a texture list -fn void Renderer.new_texture(&self, String name, TextureType type, char[] pixels, ushort width, ushort height) +fn void Renderer.new_texture(&self, String name, TextureType type, char[] pixels, uint width, uint height) { uint id = name.hash(); @@ -393,7 +395,7 @@ fn void Renderer.new_texture(&self, String name, TextureType type, char[] pixels .width = width, .height = height, .layer_count_or_depth = 1, - .num_levels = 0, // no mip maps + .num_levels = 1, // no mip maps so just one level // .sample_count not used since the texture is not a render target }; @@ -429,7 +431,7 @@ fn void Renderer.new_texture(&self, String name, TextureType type, char[] pixels self.update_texture(name, pixels, width, height); } -fn void Renderer.update_texture(&self, String name, char[] pixels, ushort width, ushort height, ushort x = 0, ushort y = 0) +fn void Renderer.update_texture(&self, String name, char[] pixels, uint width, uint height, uint x = 0, uint y = 0) { Texture* t = self.textures.get_from_name(name); if (t == null || t.texture == null) { @@ -482,6 +484,87 @@ fn void Renderer.update_texture(&self, String name, char[] pixels, ushort width, } +fn bool Renderer.push_sprite(&self, short x, short y, short w, short h, short u, short v) { + if (self.quad_buffer.count >= MAX_QUAD_BATCH) { + return false; + } + + // upload the quad data to the gpu + if (self.quad_buffer.initialized == false) { + unreachable("quad buffer not initialized"); + } + + GPUTransferBuffer* buf = sdl::create_gpu_transfer_buffer(self.gpu, + &&(GPUTransferBufferCreateInfo){.usage = GPU_TRANSFERBUFFERUSAGE_UPLOAD, .size = Quad.sizeof} + ); + if (buf == null) { + unreachable("failed to create gpu transfer buffer: %s", sdl::get_error()); + } + + Quad* quad = (Quad*)sdl::map_gpu_transfer_buffer(self.gpu, buf, false); + if (quad == null) { + unreachable("failed to map gpu transfer buffer: %s", sdl::get_error()); + } + + /* v1 v4 + * +-------------+ + * | _/| + * | _/ | + * | 1 _/ | + * | _/ | + * | _/ | + * | _/ 2 | + * |/ | + * +-------------+ + * v2 v3 + */ + quad.vertices.v1 = {.pos = {.x = x, .y = y}, .uv = {.u = u, .v = v}}; + quad.vertices.v2 = {.pos = {.x = x, .y = y+h}, .uv = {.u = u, .v = v+h}}; + quad.vertices.v3 = {.pos = {.x = x+w, .y = y+h}, .uv = {.u = u+w, .v = v+h}}; + quad.vertices.v4 = {.pos = {.x = x+w, .y = y}, .uv = {.u = u+w, .v = v}}; + // triangle 1 indices + quad.indices.i1 = 0; // v1 + quad.indices.i2 = 1; // v2 + quad.indices.i3 = 3; // v4 + // triangle 2 indices + quad.indices.i4 = 1; // v2 + quad.indices.i5 = 2; // v3 + quad.indices.i6 = 3; // v4 + + sdl::unmap_gpu_transfer_buffer(self.gpu, buf); + + GPUCommandBuffer* cmd = sdl::acquire_gpu_command_buffer(self.gpu); + if (cmd == null) { + unreachable("failed to upload quad at acquiring command buffer: %s", sdl::get_error()); + } + GPUCopyPass* cpy = sdl::begin_gpu_copy_pass(cmd); + + // upload vertices + QuadBuffer* qb = &self.quad_buffer; + sdl::upload_to_gpu_buffer(cpy, + &&(GPUTransferBufferLocation){.transfer_buffer = buf, .offset = Quad.vertices.offsetof}, + &&(GPUBufferRegion){.buffer = qb.vert_buf, .offset = qb.count * Quad.vertices.sizeof, .size = Quad.vertices.sizeof}, + false + ); + // upload indices + sdl::upload_to_gpu_buffer(cpy, + &&(GPUTransferBufferLocation){.transfer_buffer = buf, .offset = Quad.indices.offsetof}, + &&(GPUBufferRegion){.buffer = qb.idx_buf, .offset = qb.count * Quad.indices.sizeof, .size = Quad.indices.sizeof}, + false + ); + + sdl::end_gpu_copy_pass(cpy); + if (!sdl::submit_gpu_command_buffer(cmd)) { + unreachable("failed to upload quads at submit command buffer: %s", sdl::get_error()); + } + sdl::release_gpu_transfer_buffer(self.gpu, buf); + //sdl::wait_for_gpu_idle(self.gpu); + + qb.count++; + + return true; +} + // Push a quad into the quad buffer, return true on success and false on failure fn bool Renderer.push_quad(&self, short x, short y, short w, short h, uint color) { @@ -558,7 +641,7 @@ fn bool Renderer.push_quad(&self, short x, short y, short w, short h, uint color unreachable("failed to upload quads at submit command buffer: %s", sdl::get_error()); } sdl::release_gpu_transfer_buffer(self.gpu, buf); - sdl::wait_for_gpu_idle(self.gpu); + //sdl::wait_for_gpu_idle(self.gpu); qb.count++; @@ -570,17 +653,27 @@ fn bool Renderer.push_quad(&self, short x, short y, short w, short h, uint color fn void Renderer.draw_quads(&self, GPURenderPass* pass) { QuadBuffer* qb = &self.quad_buffer; - sdl::bind_gpu_vertex_buffers(pass, 0, (GPUBufferBinding[]){{.buffer = qb.vert_buf, .offset = 0}}, 1); - sdl::bind_gpu_index_buffer(pass, &&(GPUBufferBinding){.buffer = qb.idx_buf, .offset = 0}, GPU_INDEXELEMENTSIZE_16BIT); + + if (qb.off == qb.count) return; + + sdl::bind_gpu_vertex_buffers(pass, 0, (GPUBufferBinding[]){{.buffer = qb.vert_buf, .offset = qb.off*Quad.vertices.sizeof}}, 1); + sdl::bind_gpu_index_buffer(pass, &&(GPUBufferBinding){.buffer = qb.idx_buf, .offset = qb.off*Quad.indices.sizeof}, GPU_INDEXELEMENTSIZE_16BIT); // we need instancing to not do this - for (int i = 0; i < qb.count; i++) { + for (int i = 0; i < qb.count - qb.off; i++) { sdl::draw_gpu_indexed_primitives(pass, 6, 1, i*6, i*4, 0); } - qb.count = 0; + qb.off = qb.count; } +fn void Renderer.reset_quads(&self) +{ + self.quad_buffer.count = 0; + self.quad_buffer.off = 0; +} + + // TODO: fn Renderer.draw_sprite, same as draw_quad but also bind the texture // TODO: fn Renderer.begin_render // TODO: fn Renderer.end_render @@ -599,4 +692,8 @@ fn void Renderer.draw_quads(&self, GPURenderPass* pass) * - draw * - push uniform * - draw + * + * 2. The GPU buffers are read per-command-buffer and not per + * render pass. So I cannot override an element in the buffer + * before submitting the command buffer. */ \ No newline at end of file diff --git a/test_renderer.c3 b/test_renderer.c3 index 6559bd1..e9c6302 100644 --- a/test_renderer.c3 +++ b/test_renderer.c3 @@ -2,6 +2,8 @@ import sdlrenderer::ren; import std::io; import std::thread; import sdl3::sdl; +import std::compression::qoi; +import std::core::mem::allocator; struct Viewsize @align(16) { int w, h; @@ -13,18 +15,31 @@ fn int main() ren::Renderer ren; ren.init("test window"); + // TODO: these could be the same function ren.load_spirv_shader_from_file("rect shader", "resources/shaders/compiled/rect.vert.spv", "resources/shaders/compiled/rect.frag.spv", 0, 1); ren.create_pipeline("rect shader", RECT); - for (int i = 0; i < 10; i++) { - + // load the tux qoi image + QOIDesc img_desc; + char[] img_pixels = qoi::read(allocator::temp(), "resources/tux.qoi", &img_desc)!!; + // and put it in a texture + ren.new_texture("tux", FULL_COLOR, img_pixels, img_desc.width, img_desc.height); + // create a new pipeline to use the texture + ren.load_spirv_shader_from_file("sprite shader", "resources/shaders/compiled/sprite.vert.spv", "resources/shaders/compiled/sprite.frag.spv", 1, 1); + ren.create_pipeline("sprite shader", SPRITE); + for (int i = 0; i < 20; i++) { GPUCommandBuffer* cmdbuf = sdl::acquire_gpu_command_buffer(ren.gpu); GPUTexture* swapchain_texture; sdl::wait_and_acquire_gpu_swapchain_texture(cmdbuf, ren.win, &swapchain_texture, null, null); - + + GPURenderPass* pass; + GPUGraphicsPipeline* p; + Viewsize v = {.w = 640, .h = 480}; + + // Colored Rectangles Render Pass // FIXME: if doing damage tracking DO NOT clear the screen - GPURenderPass* pass = sdl::begin_gpu_render_pass(cmdbuf, + pass = sdl::begin_gpu_render_pass(cmdbuf, &&(GPUColorTargetInfo){ .texture = swapchain_texture, .mip_level = 0, @@ -53,24 +68,74 @@ fn int main() // rect 3 ren.push_quad(200,300,50,50,0xffff0000); - GPUGraphicsPipeline* p = ren.pipelines.get_from_name("rect shader").pipeline; + p = ren.pipelines.get_from_name("rect shader").pipeline; if (p == null) { unreachable("no pipeline"); } sdl::bind_gpu_graphics_pipeline(pass, p); - Viewsize v = {.w = 640, .h = 480}; - v.ox = 50*i; - v.oy = 50*i; - + v.ox = 10*i; + v.oy = 10*i; sdl::push_gpu_vertex_uniform_data(cmdbuf, 1, &v, Viewsize.sizeof); ren.draw_quads(pass); sdl::end_gpu_render_pass(pass); + // End Rectangle Render Pass + + + // Textured Rectangles Render Pass + pass = sdl::begin_gpu_render_pass(cmdbuf, + &&(GPUColorTargetInfo){ + .texture = swapchain_texture, + .mip_level = 0, + .layer_or_depth_plane = 0, + .clear_color = {.r = 0.0, .g = 0.0, .b = 0.0, .a = 1.0}, + .load_op = GPU_LOADOP_DONT_CARE, // clear the screen at the start of the render pass + .store_op = GPU_STOREOP_STORE, + .resolve_texture = null, + .resolve_mip_level = 0, + .resolve_layer = 0, + .cycle = false, + .cycle_resolve_texture = false + }, + 1, + null // huh + ); + + if (pass == null) { + unreachable("render pass creation went wrong: %s", sdl::get_error()); + } + + p = ren.pipelines.get_from_name("sprite shader").pipeline; + if (p == null) { + unreachable("no pipeline"); + } + + sdl::bind_gpu_graphics_pipeline(pass, p); + // in this case it is not an offset but the texture size in pixels + v.ox = 54; + v.oy = 64; + sdl::push_gpu_vertex_uniform_data(cmdbuf, 1, &v, Viewsize.sizeof); + + // bind the pipeline's sampler + ren::Texture* tx = ren.textures.get_from_name("tux"); + sdl::bind_gpu_fragment_samplers(pass, 0, + (GPUTextureSamplerBinding[]){{.texture = tx.texture, .sampler = tx.sampler}}, 1 + ); + + // tux + ren.push_sprite(300, 0, 54, 64, 0, 0); + + ren.draw_quads(pass); + + sdl::end_gpu_render_pass(pass); + // End Textured Rectangle Render Pass + sdl::submit_gpu_command_buffer(cmdbuf); + ren.reset_quads(); thread::sleep_ms(250); }