From be1476d107b13b5beaeea0a457e8fb9684c8bff2 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Mon, 15 Sep 2025 18:53:42 +0200 Subject: [PATCH] renderer now uses a single pipeline for ugui --- resources/shaders/source/font.frag.glsl | 21 -- resources/shaders/source/msdf.frag.glsl | 35 --- resources/shaders/source/rect.frag.glsl | 27 -- resources/shaders/source/rect.vert.glsl | 29 -- resources/shaders/source/sprite.frag.glsl | 18 -- resources/shaders/source/sprite.vert.glsl | 28 -- resources/shaders/source/ugui.frag.glsl | 118 ++++++++ resources/shaders/source/ugui.vert.glsl | 47 +++ src/main.c3 | 27 +- src/renderer.c3 | 332 +++++++++++++--------- 10 files changed, 370 insertions(+), 312 deletions(-) delete mode 100644 resources/shaders/source/font.frag.glsl delete mode 100644 resources/shaders/source/msdf.frag.glsl delete mode 100644 resources/shaders/source/rect.frag.glsl delete mode 100644 resources/shaders/source/rect.vert.glsl delete mode 100644 resources/shaders/source/sprite.frag.glsl delete mode 100644 resources/shaders/source/sprite.vert.glsl create mode 100644 resources/shaders/source/ugui.frag.glsl create mode 100644 resources/shaders/source/ugui.vert.glsl diff --git a/resources/shaders/source/font.frag.glsl b/resources/shaders/source/font.frag.glsl deleted file mode 100644 index 56207b4..0000000 --- a/resources/shaders/source/font.frag.glsl +++ /dev/null @@ -1,21 +0,0 @@ -#version 450 - -layout(set = 3, binding = 0) uniform Viewport { - ivec2 view; -}; -layout(set = 2, binding = 0) uniform sampler2D tx; - -layout(location = 0) in vec2 uv; -layout(location = 1) in vec4 color; - -layout(location = 0) out vec4 fragColor; - -void main() -{ - ivec2 ts = textureSize(tx, 0); - vec2 fts = vec2(ts); - vec2 real_uv = uv / fts; - - vec4 opacity = texture(tx, real_uv); - fragColor = vec4(color.rgb, color.a*opacity.r); -} \ No newline at end of file diff --git a/resources/shaders/source/msdf.frag.glsl b/resources/shaders/source/msdf.frag.glsl deleted file mode 100644 index f46be74..0000000 --- a/resources/shaders/source/msdf.frag.glsl +++ /dev/null @@ -1,35 +0,0 @@ -#version 450 - -layout(set = 3, binding = 0) uniform Viewport { - ivec2 view; -}; -layout(set = 2, binding = 0) uniform sampler2D tx; - -const float PX_RANGE = 4.0f; - -layout(location = 0) in vec2 uv; -layout(location = 1) in vec4 color; - -layout(location = 0) out vec4 fragColor; - -float screen_px_range(vec2 uv) { - vec2 unit_range = vec2(PX_RANGE)/vec2(textureSize(tx, 0)); - vec2 texel_size = vec2(1.0)/fwidth(uv); - return max(0.5*dot(unit_range, texel_size), 1.0); -} - -float median(float r, float g, float b) { - return max(min(r, g), min(max(r, g), b)); -} - -void main() { - ivec2 ts = textureSize(tx, 0); - vec2 fts = vec2(ts); - vec2 real_uv = uv / fts; - - vec3 msd = texture(tx, real_uv).rgb; - float sd = median(msd.r, msd.g, msd.b); - float distance = screen_px_range(real_uv)*(sd - 0.5); - float opacity = clamp(distance + 0.5, 0.0, 1.0); - fragColor = color * opacity; -} \ No newline at end of file diff --git a/resources/shaders/source/rect.frag.glsl b/resources/shaders/source/rect.frag.glsl deleted file mode 100644 index 4ae4ea4..0000000 --- a/resources/shaders/source/rect.frag.glsl +++ /dev/null @@ -1,27 +0,0 @@ -#version 450 - -layout(set = 3, binding = 0) uniform Viewport { - ivec2 view; -}; - -layout(location = 0) in vec4 in_color; -layout(location = 1) in vec4 in_quad_size; // x,y, w,h -layout(location = 2) in float in_radius; - -layout(location = 0) out vec4 fragColor; - -// SDF for a rounded rectangle given the centerpoint, half size and radius, all in pixels -float sdf_rr(vec2 p, vec2 half_size, float radius) { - vec2 q = abs(p) - half_size + radius; - return length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - radius; -} - -void main() -{ - vec2 centerpoint = in_quad_size.xy + in_quad_size.zw * 0.5; - vec2 half_size = in_quad_size.zw * 0.5; - float distance = sdf_rr(vec2(gl_FragCoord) - centerpoint, half_size, in_radius); - float alpha = 1.0 - smoothstep(0.0, 1.5, distance); - - fragColor = vec4(in_color.rgb, in_color.a * alpha); -} \ No newline at end of file diff --git a/resources/shaders/source/rect.vert.glsl b/resources/shaders/source/rect.vert.glsl deleted file mode 100644 index 00785db..0000000 --- a/resources/shaders/source/rect.vert.glsl +++ /dev/null @@ -1,29 +0,0 @@ -#version 450 - -layout(set = 1, binding = 0) uniform Viewport { - ivec2 view; -}; - -layout(location = 0) in ivec2 position; -layout(location = 1) in ivec4 attr; // quad x,y,w,h -layout(location = 2) in ivec2 uv; // x,y in the texture -layout(location = 3) in uvec4 color; - -layout(location = 0) out vec4 out_color; -layout(location = 1) out vec4 out_quad_size; -layout(location = 2) out float out_radius; - -void main() -{ - // vertex position - ivec2 px_pos = attr.xy + position.xy * attr.zw; - vec2 clip_pos; - clip_pos.x = float(px_pos.x)*2.0 / view.x - 1.0; - clip_pos.y = -(float(px_pos.y)*2.0 / view.y - 1.0); - - gl_Position = vec4(clip_pos, 0.0, 1.0); - - out_color = vec4(color) / 255.0; - out_quad_size = vec4(attr); - out_radius = float(abs(uv.x)); -} diff --git a/resources/shaders/source/sprite.frag.glsl b/resources/shaders/source/sprite.frag.glsl deleted file mode 100644 index ade47cb..0000000 --- a/resources/shaders/source/sprite.frag.glsl +++ /dev/null @@ -1,18 +0,0 @@ -#version 450 - -layout(set = 3, binding = 0) uniform Viewport { - ivec2 view; -}; - -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(ts); - 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 deleted file mode 100644 index 2dca841..0000000 --- a/resources/shaders/source/sprite.vert.glsl +++ /dev/null @@ -1,28 +0,0 @@ -#version 450 - -layout(set = 1, binding = 0) uniform Viewport { - ivec2 view; -}; - -layout(location = 0) in ivec2 position; -layout(location = 1) in ivec4 attr; // quad x,y,w,h -layout(location = 2) in ivec2 in_uv; -layout(location = 3) in uvec4 color; - -layout(location = 0) out vec2 out_uv; -layout(location = 1) out vec4 out_color; - -void main() -{ - // vertex position - ivec2 px_pos = attr.xy + position.xy * attr.zw; - vec2 clip_pos; - clip_pos.x = float(px_pos.x)*2.0 / view.x - 1.0; - clip_pos.y = -(float(px_pos.y)*2.0 / view.y - 1.0); - - gl_Position = vec4(clip_pos, 0.0, 1.0); - - vec2 px_uv = in_uv.xy + position.xy * attr.zw; - out_uv = vec2(px_uv); - out_color = vec4(color) / 255.0; -} diff --git a/resources/shaders/source/ugui.frag.glsl b/resources/shaders/source/ugui.frag.glsl new file mode 100644 index 0000000..a5b6a50 --- /dev/null +++ b/resources/shaders/source/ugui.frag.glsl @@ -0,0 +1,118 @@ +#version 450 + +/* Combined fragment shader to render UGUI commands */ + +// type values, these are the same as in renderer.c3 +const uint TYPE_RECT = 0; +const uint TYPE_FONT = 1; +const uint TYPE_SPRITE = 2; +const uint TYPE_MSDF = 3; + +// viewport size +layout(set = 3, binding = 0) uniform Viewport { + ivec2 view; +}; + +// textures +layout(set = 2, binding = 0) uniform sampler2D font_atlas; +layout(set = 2, binding = 1) uniform sampler2D sprite_atlas; + +// inputs +layout(location = 0) in vec4 in_color; +layout(location = 1) in vec2 in_uv; +layout(location = 2) in vec4 in_quad_size; +layout(location = 3) in float in_radius; +layout(location = 4) flat in uint in_type; + +// outputs +layout(location = 0) out vec4 fragColor; + + +// SDF for a rounded rectangle given the centerpoint, half size and radius, all in pixels +float sdf_rr(vec2 p, vec2 half_size, float radius) { + vec2 q = abs(p) - half_size + radius; + return length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - radius; +} + + +const float PX_RANGE = 4.0f; +float screen_px_range(vec2 uv, sampler2D tx) { + vec2 unit_range = vec2(PX_RANGE)/vec2(textureSize(tx, 0)); + vec2 texel_size = vec2(1.0)/fwidth(uv); + return max(0.5*dot(unit_range, texel_size), 1.0); +} + + +float median(float r, float g, float b) { + return max(min(r, g), min(max(r, g), b)); +} + + +// main for TYPE_RECT, draw a rouded rectangle with a SDF +void rect_main() +{ + vec2 centerpoint = in_quad_size.xy + in_quad_size.zw * 0.5; + vec2 half_size = in_quad_size.zw * 0.5; + float distance = sdf_rr(vec2(gl_FragCoord) - centerpoint, half_size, in_radius); + float alpha = 1.0 - smoothstep(0.0, 1.5, distance); + fragColor = vec4(in_color.rgb, in_color.a * alpha); +} + + +// main for TYPE_SPRITE, draws a sprite sampled from an atlas +void sprite_main() +{ + ivec2 ts = textureSize(sprite_atlas, 0); + vec2 fts = vec2(ts); + vec2 real_uv = in_uv / fts; + fragColor = texture(sprite_atlas, real_uv); +} + + +// main for TYPE_FONT, draws a character sampled from an atlas that contains only the alpha channel +void font_main() +{ + ivec2 ts = textureSize(font_atlas, 0); + vec2 fts = vec2(ts); + vec2 real_uv = in_uv / fts; + + vec4 opacity = texture(font_atlas, real_uv); + fragColor = vec4(in_color.rgb, in_color.a*opacity.r); +} + + +// main for TYPE_MSDF, draws a sprite that is stored as a multi-channel SDF +void msdf_main() { + ivec2 ts = textureSize(sprite_atlas, 0); + vec2 fts = vec2(ts); + vec2 real_uv = in_uv / fts; + + vec3 msd = texture(sprite_atlas, real_uv).rgb; + float sd = median(msd.r, msd.g, msd.b); + float distance = screen_px_range(real_uv, sprite_atlas)*(sd - 0.5); + float opacity = clamp(distance + 0.5, 0.0, 1.0); + fragColor = in_color * opacity; +} + + +// shader main +void main() +{ + switch (in_type) { + case TYPE_RECT: + rect_main(); + break; + case TYPE_FONT: + font_main(); + break; + case TYPE_SPRITE: + sprite_main(); + break; + case TYPE_MSDF: + msdf_main(); + break; + default: + // ERROR, invalid type, return magenta + fragColor = vec4(1.0, 0.0, 1.0, 1.0); + } +} \ No newline at end of file diff --git a/resources/shaders/source/ugui.vert.glsl b/resources/shaders/source/ugui.vert.glsl new file mode 100644 index 0000000..ae78ec8 --- /dev/null +++ b/resources/shaders/source/ugui.vert.glsl @@ -0,0 +1,47 @@ +#version 450 + +/* Combined vertex shader to render UGUI commands */ + +// Viewport size in pixels +layout(set = 1, binding = 0) uniform Viewport { + ivec2 view; +}; + +// inputs +layout(location = 0) in ivec2 in_position; +layout(location = 1) in ivec4 in_attr; // quad x,y,w,h +layout(location = 2) in ivec2 in_uv; +layout(location = 3) in uvec4 in_color; +layout(location = 4) in uint in_type; + +// outputs +layout(location = 0) out vec4 out_color; +layout(location = 1) out vec2 out_uv; +layout(location = 2) out vec4 out_quad_size; +layout(location = 3) out float out_radius; +layout(location = 4) out uint out_type; + + +void main() +{ + // vertex position + ivec2 px_pos = in_attr.xy + in_position.xy * in_attr.zw; + vec2 clip_pos; + clip_pos.x = float(px_pos.x)*2.0 / view.x - 1.0; + clip_pos.y = -(float(px_pos.y)*2.0 / view.y - 1.0); + gl_Position = vec4(clip_pos, 0.0, 1.0); + + // color output + out_color = vec4(in_color) / 255.0; + + // uv output. only useful if the type is SPRITE + vec2 px_uv = in_uv.xy + in_position.xy * in_attr.zw; + out_uv = vec2(px_uv); + + // quad size and radius output, only useful if type is RECT + out_quad_size = vec4(in_attr); + out_radius = float(abs(in_uv.x)); + + // type output + out_type = in_type; +} \ No newline at end of file diff --git a/src/main.c3 b/src/main.c3 index 6fc447e..27bf793 100644 --- a/src/main.c3 +++ b/src/main.c3 @@ -47,13 +47,8 @@ fn TimeStats Times.get_stats(×) } -const char[*] MSDF_FS_PATH = "resources/shaders/compiled/msdf.frag.spv"; -const char[*] SPRITE_FS_PATH = "resources/shaders/compiled/sprite.frag.spv"; -const char[*] FONT_FS_PATH = "resources/shaders/compiled/font.frag.spv"; -const char[*] RECT_FS_PATH = "resources/shaders/compiled/rect.frag.spv"; - -const char[*] SPRITE_VS_PATH = "resources/shaders/compiled/sprite.vert.spv"; -const char[*] RECT_VS_PATH = "resources/shaders/compiled/rect.vert.spv"; +const char[*] VS_PATH = "resources/shaders/compiled/ugui.vert.spv"; +const char[*] FS_PATH = "resources/shaders/compiled/ugui.frag.spv"; const char[*] STYLESHEET_PATH = "resources/style.css"; @@ -79,10 +74,8 @@ fn int main(String[] args) // import font in the ui context ui.load_font("font1", "resources/hack-nerd.ttf", 16)!!; - // create the rendering pipeline + // set the renderer's font atlas ren.font_atlas_id = ui.get_font_id("font1"); - ren.load_spirv_shader_from_file("UGUI_PIPELINE_FONT", SPRITE_VS_PATH, FONT_FS_PATH, 1, 0); - ren.create_pipeline("UGUI_PIPELINE_FONT", SPRITE); // send the atlas to the gpu Atlas* font_atlas = ui.get_font_atlas("font1")!!; @@ -96,24 +89,18 @@ fn int main(String[] args) ui.import_sprite_file_qoi("tux", "resources/tux.qoi")!!; ui.import_sprite_file_qoi("tick", "resources/tick_sdf.qoi", SpriteType.SPRITE_MSDF)!!; - // create the rendering pipelines + // set the renderer's sprite atlas ren.sprite_atlas_id = ui.get_sprite_atlas_id("icons"); - // normal sprite pipeline - ren.load_spirv_shader_from_file("UGUI_PIPELINE_SPRITE", SPRITE_VS_PATH, SPRITE_FS_PATH, 1, 0); - ren.create_pipeline("UGUI_PIPELINE_SPRITE", SPRITE); - // msdf sprite pipeline - ren.load_spirv_shader_from_file("UGUI_PIPELINE_SPRITE_MSDF", SPRITE_VS_PATH, MSDF_FS_PATH, 1, 0); - ren.create_pipeline("UGUI_PIPELINE_SPRITE_MSDF", SPRITE); // upload the atlas to the gpu Atlas atlas = ui.sprite_atlas.atlas; ren.new_texture("icons", FULL_COLOR, atlas.buffer, atlas.width, atlas.height); // ========================================================================================== // - // RECT PIPELINE // + // PIPELINE SETUP // // ========================================================================================== // - ren.load_spirv_shader_from_file("UGUI_PIPELINE_RECT", RECT_VS_PATH, RECT_FS_PATH, 0, 0); - ren.create_pipeline("UGUI_PIPELINE_RECT", RECT); + ren.load_spirv_shader_from_file("UGUI_PIPELINE", VS_PATH, FS_PATH, 2, 0); + ren.create_pipeline("UGUI_PIPELINE", RECT); // ========================================================================================== // // CSS INPUT // diff --git a/src/renderer.c3 b/src/renderer.c3 index eae3b8b..81be051 100644 --- a/src/renderer.c3 +++ b/src/renderer.c3 @@ -1,8 +1,8 @@ -module idlist{Type}; - // extends the List type to search elements that have a type.id property -// TODO: check that type has an id - +<* + @require $defined((Type){}.id) : `No .id member found in the type` +*> +module idlist{Type}; import std::collections::list; alias IdList = List{Type}; @@ -22,16 +22,61 @@ macro Type* IdList.get_from_id(&self, id) return null; } -module sdlrenderer::ren; // 2D renderer for ugui, based on SDL3 using the new GPU API - +module sdlrenderer::ren; import std::io; import std::core::mem; import sdl3::sdl; import idlist; import ugui; + +// ============================================================================================== // +// CONSTANTS // +// ============================================================================================== // + +const int DEBUG = 1; +const bool CYCLE = true; +const int MAX_QUAD_BATCH = 2048; + + +// ============================================================================================== // +// STRUCTURES // +// ============================================================================================== // + +// How each vertex is represented in the gpu +struct Vertex { + short x, y; +} + +// Attributes of each quad instance +struct QuadAttributes { + struct pos { + short x, y, w, h; + } + struct uv { + short u, v; + } + uint color; + uint type; +} + +// A single quad +struct Quad { + struct vertices { + Vertex v1,v2,v3,v4; + } + struct indices { + short i1,i2,i3,i4,i5,i6; + } +} + +// the viewport size uniform passed to the gpu +struct ViewsizeUniform @align(16) { + int w, h; +} + struct Shader { sdl::GPUShader* frag; sdl::GPUShader* vert; @@ -51,7 +96,6 @@ struct Texture { } // The GPU buffers that contain quad info, the size is determined by MAX_QUAD_BATCH -const int MAX_QUAD_BATCH = 2048; struct QuadBuffer { sdl::GPUBuffer* vert_buf; // on-gpu vertex buffer sdl::GPUBuffer* idx_buf; // on-gpu index buffer @@ -86,42 +130,20 @@ struct Renderer { Id sprite_atlas_id; Id font_atlas_id; - int scissor_x, scissor_y, scissor_w, scissor_h; + int scissor_x, scissor_y, scissor_w, scissor_h; } -// How each vertex is represented in the gpu -struct Vertex { - short x, y; -} -// Attributes of each quad instance -struct QuadAttributes { - struct pos { - short x, y, w, h; - } - struct uv { - short u, v; - } - uint color; -} +// ============================================================================================== // +// RENDERERER METHODS // +// ============================================================================================== // -// A single quad -struct Quad { - struct vertices { - Vertex v1,v2,v3,v4; - } - struct indices { - short i1,i2,i3,i4,i5,i6; - } -} - -struct ViewsizeUniform @align(16) { - int w, h; -} - -const int DEBUG = 1; -const bool CYCLE = true; +/* Initialize the renderer structure, this does a couple of things + * 1. Initializes the SDL video subsystem with the correct hints + * 2. Creates a window and attaches it to the gpu device + * 3. Allocates the quad buffer and uploads the quad mesh to the GPU + */ fn void Renderer.init(&self, ZString title, uint width, uint height, bool vsync) { // set wayland hint automagically @@ -140,7 +162,7 @@ $if DEBUG == 0: } $else // in debug mode set the video driver to X11 because renderdoc - // doesn't support debugging in wayland yet. + // doesn't support debugging in wayland yet. sdl::set_hint(sdl::HINT_VIDEO_DRIVER, "x11"); sdl::set_hint(sdl::HINT_RENDER_GPU_DEBUG, "1"); $endif @@ -199,7 +221,6 @@ $endif unreachable("failed to initialize quad buffer (index): %s", sdl::get_error()); } - // upload the quad mesh GPUTransferBuffer *ts = sdl::create_gpu_transfer_buffer(self.gpu, &&(GPUTransferBufferCreateInfo){.usage = GPU_TRANSFERBUFFERUSAGE_UPLOAD, .size = Quad.sizeof} @@ -233,7 +254,7 @@ $endif quad.indices.i4 = 1; // v2 quad.indices.i5 = 2; // v3 quad.indices.i6 = 3; // v4 - + sdl::unmap_gpu_transfer_buffer(self.gpu, ts); GPUCommandBuffer* cmd = sdl::acquire_gpu_command_buffer(self.gpu); @@ -243,13 +264,13 @@ $endif GPUCopyPass* cpy = sdl::begin_gpu_copy_pass(cmd); // upload vertices - sdl::upload_to_gpu_buffer(cpy, + sdl::upload_to_gpu_buffer(cpy, &&(GPUTransferBufferLocation){.transfer_buffer = ts, .offset = Quad.vertices.offsetof}, &&(GPUBufferRegion){.buffer = qb.vert_buf, .offset = 0, .size = Quad.vertices.sizeof}, false ); // upload indices - sdl::upload_to_gpu_buffer(cpy, + sdl::upload_to_gpu_buffer(cpy, &&(GPUTransferBufferLocation){.transfer_buffer = ts, .offset = Quad.indices.offsetof}, &&(GPUBufferRegion){.buffer = qb.idx_buf, .offset = 0, .size = Quad.indices.sizeof}, false @@ -278,6 +299,13 @@ $endif qb.initialized = true; } + +/* Frees all the renderer structures: + * - Frees all textures and pipelines + * - Releases all GPU transfer buffers + * - Closes the main window + * - Releases the GPU device + */ fn void Renderer.free(&self) { foreach (&s: self.shaders) { @@ -310,17 +338,24 @@ fn void Renderer.free(&self) sdl::quit(); } + fn void Renderer.resize_window(&self, uint width, uint height) { sdl::set_window_size(self.win, width, height); } + fn void Renderer.get_window_size(&self, int* width, int* height) { sdl::get_window_size_in_pixels(self.win, width, height); } +// ============================================================================================== // +// SHADER LOADING // +// ============================================================================================== // + + // Both the vertex shader and fragment shader have an implicit uniform buffer at binding 0 that // contains the viewport size. It is populated automatically at every begin_render() call fn void Renderer.load_spirv_shader_from_mem(&self, String name, char[] vert_code, char[] frag_code, uint textures, uint uniforms) @@ -378,7 +413,8 @@ fn void Renderer.load_spirv_shader_from_mem(&self, String name, char[] vert_code self.shaders.push(s); } -fn void Renderer.load_spirv_shader_from_file(&self, String name, String vert_path, String frag_path, uint textures, uint uniforms) + +fn void Renderer.load_spirv_shader_from_file(&self, String name, String vert_path, String frag_path, uint textures, uint uniforms) { if (vert_path == "" || frag_path == "") { unreachable("need both a vertex shader and fragment shader path"); @@ -402,6 +438,12 @@ fn void Renderer.load_spirv_shader_from_file(&self, String name, String vert_pat self.load_spirv_shader_from_mem(name, vert_code, frag_code, textures, uniforms); } + +// ============================================================================================== // +// PIPELINE CREATION // +// ============================================================================================== // + + // this describes what we want to draw, since for drawing different things we have to change // the GPUPrimitiveType and GPURasterizerState for the pipeline. enum PipelineType : (GPUPrimitiveType primitive_type, GPURasterizerState raster_state) { @@ -425,8 +467,8 @@ fn void Renderer.create_pipeline(&self, String shader_name, PipelineType type) .fragment_shader = s.frag, // This structure specifies how the vertex buffer looks in memory, what it contains // and what is passed where to the gpu. Each vertex has three attributes, position, - // color and uv coordinates. Since this is a 2D pixel-based renderer the position - // is represented by two floats, the color as 32 bit rgba and the uv also as intgers. + // color and uv coordinates. Since this is a 2D pixel-based renderer the position + // is represented by two floats, the color as 32 bit rgba and the uv also as intgers. .vertex_input_state = { // the description of each vertex buffer, for now I use only one buffer .vertex_buffer_descriptions = (GPUVertexBufferDescription[]){ @@ -467,9 +509,15 @@ fn void Renderer.create_pipeline(&self, String shader_name, PipelineType type) .buffer_slot = 1, .format = GPU_VERTEXELEMENTFORMAT_UBYTE4, .offset = QuadAttributes.color.offsetof, + }, + { // at location four there is the quad type + .location = 4, + .buffer_slot = 1, + .format = GPU_VERTEXELEMENTFORMAT_UINT, + .offset = QuadAttributes.type.offsetof, } }, - .num_vertex_attributes = 4, + .num_vertex_attributes = 5, }, // the pipeline's primitive type and rasterizer state differs based on what needs to // be drawn @@ -513,12 +561,21 @@ fn void Renderer.create_pipeline(&self, String shader_name, PipelineType type) self.pipelines.push(p); } + +// ============================================================================================== // +// TEXTURE LOADING // +// ============================================================================================== // + + + // 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_UNORM, JUST_ALPHA = GPU_TEXTUREFORMAT_R8_UNORM -} +} + +// This macro wraps new_texture_by_id() by accepting either a name or an id directly macro void Renderer.new_texture(&self, name_or_id, TextureType type, char[] pixels, uint width, uint height) { $switch $typeof(name_or_id): @@ -528,6 +585,8 @@ macro void Renderer.new_texture(&self, name_or_id, TextureType type, char[] pixe $endswitch } + +// This macro wraps update_texture_by_id() by accepting either a name or an id directly macro void Renderer.update_texture(&self, name_or_id, char[] pixels, uint width, uint height, uint x = 0, uint y = 0) { $switch $typeof(name_or_id): @@ -538,10 +597,11 @@ macro void Renderer.update_texture(&self, name_or_id, char[] pixels, uint width, } -// 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 +/* Create a new gpu texture from a pixel buffer, the format has to be specified. + * The new texture is given an id and pushed into the renderer's texture list. + */ fn void Renderer.new_texture_by_id(&self, Id id, TextureType type, char[] pixels, uint width, uint height) -{ +{ // the texture description GPUTextureCreateInfo tci = { .type = GPU_TEXTURETYPE_2D, @@ -587,6 +647,11 @@ fn void Renderer.new_texture_by_id(&self, Id id, TextureType type, char[] pixels self.update_texture_by_id(id, pixels, width, height, 0, 0); } + +/* Updates a texture on the gpu. + * pixels: the pixel array that contais the texture content + * width, height: the size of the + */ fn void Renderer.update_texture_by_id(&self, Id id, char[] pixels, uint width, uint height, uint x, uint y) { Texture* t = self.textures.get_from_id(id); @@ -640,23 +705,34 @@ fn void Renderer.update_texture_by_id(&self, Id id, char[] pixels, uint width, u } -fn bool Renderer.push_sprite(&self, short x, short y, short w, short h, short u, short v, uint color = 0xffffffff) +// ============================================================================================== // +// RENDER COMMANDS // +// ============================================================================================== // + +const uint TYPE_RECT = 0; +const uint TYPE_FONT = 1; +const uint TYPE_SPRITE = 2; +const uint TYPE_MSDF = 3; + +fn bool Renderer.push_sprite(&self, short x, short y, short w, short h, short u, short v, uint color = 0xffffffff, uint type) { QuadAttributes qa = { .pos = {.x = x, .y = y, .w = w, .h = h}, .uv = {.u = u, .v = v}, - .color = color + .color = color, + .type = type, }; return self.map_quad(qa); } -fn bool Renderer.push_quad(&self, short x, short y, short w, short h, uint color, ushort radius = 0) +fn bool Renderer.push_quad(&self, short x, short y, short w, short h, uint color, ushort radius, uint type) { QuadAttributes qa = { .pos = {.x = x, .y = y, .w = w, .h = h}, .uv = {.u = radius, .v = radius}, - .color = color + .color = color, + .type = type }; return self.map_quad(qa); @@ -694,7 +770,7 @@ fn void Renderer.upload_quads(&self) GPUCopyPass* cpy = sdl::begin_gpu_copy_pass(cmd); // upload quad attributes - sdl::upload_to_gpu_buffer(cpy, + sdl::upload_to_gpu_buffer(cpy, &&(GPUTransferBufferLocation){.transfer_buffer = qb.attr_ts, .offset = 0}, &&(GPUBufferRegion){.buffer = qb.attr_buf, .offset = 0, .size = QuadAttributes.sizeof * qb.count}, false @@ -746,7 +822,7 @@ fn void Renderer.begin_render(&self, bool clear_screen) sdl::push_gpu_fragment_uniform_data(self.render_cmdbuf, 0, &v, ViewsizeUniform.sizeof); if (clear_screen) { - GPURenderPass* pass = sdl::begin_gpu_render_pass(self.render_cmdbuf, + GPURenderPass* pass = sdl::begin_gpu_render_pass(self.render_cmdbuf, &&(GPUColorTargetInfo){ .texture = self.swapchain_texture, .mip_level = 0, @@ -771,15 +847,17 @@ fn void Renderer.begin_render(&self, bool clear_screen) } } + fn void Renderer.end_render(&self) { sdl::submit_gpu_command_buffer(self.render_cmdbuf); self.reset_quads(); } + fn void Renderer.start_render_pass(&self, String pipeline_name) { - self.render_pass = sdl::begin_gpu_render_pass(self.render_cmdbuf, + self.render_pass = sdl::begin_gpu_render_pass(self.render_cmdbuf, &&(GPUColorTargetInfo){ .texture = self.swapchain_texture, .mip_level = 0, @@ -810,19 +888,43 @@ fn void Renderer.start_render_pass(&self, String pipeline_name) sdl::bind_gpu_graphics_pipeline(self.render_pass, p); } + fn void Renderer.end_render_pass(&self) { - sdl::end_gpu_render_pass(self.render_pass); + sdl::end_gpu_render_pass(self.render_pass); } -fn void Renderer.bind_texture(&self, String texture_name) + +fn void Renderer.bind_textures(&self, String... texture_names) { - ren::Texture* tx = self.textures.get_from_name(texture_name); - sdl::bind_gpu_fragment_samplers(self.render_pass, 0, - (GPUTextureSamplerBinding[]){{.texture = tx.texture, .sampler = tx.sampler}}, 1 - ); + // TODO: bind in one pass + foreach (idx, name: texture_names) { + ren::Texture* tx = self.textures.get_from_name(name); + if (tx == null) { + unreachable("texture '%s' was not registered", name); + } + sdl::bind_gpu_fragment_samplers(self.render_pass, (uint)idx, + (GPUTextureSamplerBinding[]){{.texture = tx.texture, .sampler = tx.sampler}}, 1 + ); + } } + +fn void Renderer.bind_textures_id(&self, ugui::Id... texture_ids) +{ + // TODO: bind in one pass + foreach (idx, id: texture_ids) { + ren::Texture* tx = self.textures.get_from_id(id); + if (tx == null) { + unreachable("texture [%d] was not registered", id); + } + sdl::bind_gpu_fragment_samplers(self.render_pass, (uint)idx, + (GPUTextureSamplerBinding[]){{.texture = tx.texture, .sampler = tx.sampler}}, 1 + ); + } +} + + fn void Renderer.set_scissor(&self, int x, int y, int w, int h) { // in vulkan scissor size must be positive, clamp to zero @@ -852,10 +954,10 @@ fn void Renderer.reset_scissor(&self) * - 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. + * before submitting the command buffer. */ /// === END NOTES === @@ -866,29 +968,31 @@ fn void Renderer.render_ugui(&self, CmdQueue* queue) foreach (&c : queue) { if (c.type == CMD_RECT) { CmdRect r = c.rect; - self.push_quad(r.rect.x, r.rect.y, r.rect.w, r.rect.h, r.color.to_uint(), r.radius); + self.push_quad(r.rect.x, r.rect.y, r.rect.w, r.rect.h, r.color.to_uint(), r.radius, TYPE_RECT); } else if (c.type == CMD_SPRITE) { CmdSprite s = c.sprite; - self.push_sprite(s.rect.x, s.rect.y, s.texture_rect.w, s.texture_rect.h, s.texture_rect.x, s.texture_rect.y, s.hue.to_uint()); + uint type; + if (s.texture_id == self.font_atlas_id) { + type = TYPE_FONT; + } else if (s.texture_id == self.sprite_atlas_id && s.type == SPRITE_NORMAL) { + type = TYPE_SPRITE; + } else if (s.texture_id == self.sprite_atlas_id && s.type == SPRITE_MSDF) { + type = TYPE_MSDF; + } else { + unreachable("unrecognized command type"); + } + self.push_sprite(s.rect.x, s.rect.y, s.texture_rect.w, s.texture_rect.h, s.texture_rect.x, s.texture_rect.y, s.hue.to_uint(), type); } } self.upload_quads(); - Cmd* last_command; - uint off; - uint count; - for (Cmd* cmd; (cmd = queue.dequeue() ?? null) != null;) { - if (last_command == null || last_command.type != cmd.type) { - self.end_command(last_command, off, count); - self.begin_command(cmd); - off += count; - count = 0; - } + self.start_render_pass("UGUI_PIPELINE"); + self.bind_textures_id(self.font_atlas_id, self.sprite_atlas_id); + uint calls = 0; + uint off; + for (Cmd* cmd; (cmd = queue.dequeue() ?? null) != null;) { switch (cmd.type) { - case CMD_RECT: nextcase; - case CMD_SPRITE: - count++; case CMD_UPDATE_ATLAS: // TODO: verify the correct type CmdUpdateAtlas u = cmd.update_atlas; @@ -903,60 +1007,20 @@ fn void Renderer.render_ugui(&self, CmdQueue* queue) self.scissor_y = s.y; self.scissor_w = s.w; self.scissor_h = s.h; - default: unreachable("unknown command: %s", cmd.type); - } - - last_command = cmd; - } - self.end_command(last_command, off, count); -} - -fn void Renderer.begin_command(&self, Cmd* cmd) -{ - if (cmd == null) return; - - switch (cmd.type) { - case CMD_RECT: - self.start_render_pass("UGUI_PIPELINE_RECT"); - self.set_scissor(self.scissor_x, self.scissor_y, self.scissor_w, self.scissor_h); - case CMD_SPRITE: - // TODO: support multiple sprite and font atlases - CmdSprite s = cmd.sprite; - String pipeline; - String texture; - if (s.texture_id == self.sprite_atlas_id) { - switch (s.type) { - case SPRITE_NORMAL: pipeline = "UGUI_PIPELINE_SPRITE"; - case SPRITE_SDF: pipeline = "UGUI_PIPELINE_SPRITE_SDF"; - case SPRITE_MSDF: pipeline = "UGUI_PIPELINE_SPRITE_MSDF"; - case SPRITE_ANIMATED: unreachable("animated sprtes are unsupported for now"); - default: unreachable("unknown sprite type %s", s.type); + default: + self.set_scissor(self.scissor_x, self.scissor_y, self.scissor_w, self.scissor_h); + uint count = 1; + while (queue.len() != 0 && (queue.get(0).type == CMD_RECT || queue.get(0).type == CMD_SPRITE)) { + count++; + (void)queue.dequeue(); } - texture = "icons"; - } else if (s.texture_id == self.font_atlas_id) { - pipeline = "UGUI_PIPELINE_FONT"; - texture = "font1"; - } - self.start_render_pass(pipeline); - self.bind_texture(texture); - self.set_scissor(self.scissor_x, self.scissor_y, self.scissor_w, self.scissor_h); - case CMD_UPDATE_ATLAS: break; - case CMD_SCISSOR: break; - default: unreachable("unknown command: %s", cmd.type); - } -} - -fn void Renderer.end_command(&self, Cmd* cmd, uint off, uint count) -{ - if (cmd == null) return; - - switch (cmd.type) { - case CMD_RECT: nextcase; - case CMD_SPRITE: self.draw_quads(off, count); - self.end_render_pass(); - case CMD_UPDATE_ATLAS: break; - case CMD_SCISSOR: break; - default: unreachable("unknown command: %s", cmd.type); + off += count; + calls++; +// self.draw_quads(off++, 1); + } } + self.end_render_pass(); +// ugui::println("calls: ", calls); } +