renderer now uses a single pipeline for ugui

This commit is contained in:
Alessandro Mauri 2025-09-15 18:53:42 +02:00
parent 622b648d26
commit be1476d107
10 changed files with 370 additions and 312 deletions

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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));
}

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -47,13 +47,8 @@ fn TimeStats Times.get_stats(&times)
} }
const char[*] MSDF_FS_PATH = "resources/shaders/compiled/msdf.frag.spv"; const char[*] VS_PATH = "resources/shaders/compiled/ugui.vert.spv";
const char[*] SPRITE_FS_PATH = "resources/shaders/compiled/sprite.frag.spv"; const char[*] FS_PATH = "resources/shaders/compiled/ugui.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[*] STYLESHEET_PATH = "resources/style.css"; const char[*] STYLESHEET_PATH = "resources/style.css";
@ -79,10 +74,8 @@ fn int main(String[] args)
// import font in the ui context // import font in the ui context
ui.load_font("font1", "resources/hack-nerd.ttf", 16)!!; 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.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 // send the atlas to the gpu
Atlas* font_atlas = ui.get_font_atlas("font1")!!; 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("tux", "resources/tux.qoi")!!;
ui.import_sprite_file_qoi("tick", "resources/tick_sdf.qoi", SpriteType.SPRITE_MSDF)!!; 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"); 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 // upload the atlas to the gpu
Atlas atlas = ui.sprite_atlas.atlas; Atlas atlas = ui.sprite_atlas.atlas;
ren.new_texture("icons", FULL_COLOR, atlas.buffer, atlas.width, atlas.height); 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.load_spirv_shader_from_file("UGUI_PIPELINE", VS_PATH, FS_PATH, 2, 0);
ren.create_pipeline("UGUI_PIPELINE_RECT", RECT); ren.create_pipeline("UGUI_PIPELINE", RECT);
// ========================================================================================== // // ========================================================================================== //
// CSS INPUT // // CSS INPUT //

View File

@ -1,8 +1,8 @@
module idlist{Type};
// extends the List type to search elements that have a type.id property // 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; import std::collections::list;
alias IdList = List{Type}; alias IdList = List{Type};
@ -22,16 +22,61 @@ macro Type* IdList.get_from_id(&self, id)
return null; return null;
} }
module sdlrenderer::ren;
// 2D renderer for ugui, based on SDL3 using the new GPU API // 2D renderer for ugui, based on SDL3 using the new GPU API
module sdlrenderer::ren;
import std::io; import std::io;
import std::core::mem; import std::core::mem;
import sdl3::sdl; import sdl3::sdl;
import idlist; import idlist;
import ugui; 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 { struct Shader {
sdl::GPUShader* frag; sdl::GPUShader* frag;
sdl::GPUShader* vert; sdl::GPUShader* vert;
@ -51,7 +96,6 @@ struct Texture {
} }
// The GPU buffers that contain quad info, the size is determined by MAX_QUAD_BATCH // The GPU buffers that contain quad info, the size is determined by MAX_QUAD_BATCH
const int MAX_QUAD_BATCH = 2048;
struct QuadBuffer { struct QuadBuffer {
sdl::GPUBuffer* vert_buf; // on-gpu vertex buffer sdl::GPUBuffer* vert_buf; // on-gpu vertex buffer
sdl::GPUBuffer* idx_buf; // on-gpu index buffer sdl::GPUBuffer* idx_buf; // on-gpu index buffer
@ -86,42 +130,20 @@ struct Renderer {
Id sprite_atlas_id; Id sprite_atlas_id;
Id font_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 { // RENDERERER METHODS //
struct pos { // ============================================================================================== //
short x, y, w, h;
}
struct uv {
short u, v;
}
uint color;
}
// 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) fn void Renderer.init(&self, ZString title, uint width, uint height, bool vsync)
{ {
// set wayland hint automagically // set wayland hint automagically
@ -140,7 +162,7 @@ $if DEBUG == 0:
} }
$else $else
// in debug mode set the video driver to X11 because renderdoc // 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_VIDEO_DRIVER, "x11");
sdl::set_hint(sdl::HINT_RENDER_GPU_DEBUG, "1"); sdl::set_hint(sdl::HINT_RENDER_GPU_DEBUG, "1");
$endif $endif
@ -199,7 +221,6 @@ $endif
unreachable("failed to initialize quad buffer (index): %s", sdl::get_error()); unreachable("failed to initialize quad buffer (index): %s", sdl::get_error());
} }
// upload the quad mesh // upload the quad mesh
GPUTransferBuffer *ts = sdl::create_gpu_transfer_buffer(self.gpu, GPUTransferBuffer *ts = sdl::create_gpu_transfer_buffer(self.gpu,
&&(GPUTransferBufferCreateInfo){.usage = GPU_TRANSFERBUFFERUSAGE_UPLOAD, .size = Quad.sizeof} &&(GPUTransferBufferCreateInfo){.usage = GPU_TRANSFERBUFFERUSAGE_UPLOAD, .size = Quad.sizeof}
@ -233,7 +254,7 @@ $endif
quad.indices.i4 = 1; // v2 quad.indices.i4 = 1; // v2
quad.indices.i5 = 2; // v3 quad.indices.i5 = 2; // v3
quad.indices.i6 = 3; // v4 quad.indices.i6 = 3; // v4
sdl::unmap_gpu_transfer_buffer(self.gpu, ts); sdl::unmap_gpu_transfer_buffer(self.gpu, ts);
GPUCommandBuffer* cmd = sdl::acquire_gpu_command_buffer(self.gpu); GPUCommandBuffer* cmd = sdl::acquire_gpu_command_buffer(self.gpu);
@ -243,13 +264,13 @@ $endif
GPUCopyPass* cpy = sdl::begin_gpu_copy_pass(cmd); GPUCopyPass* cpy = sdl::begin_gpu_copy_pass(cmd);
// upload vertices // upload vertices
sdl::upload_to_gpu_buffer(cpy, sdl::upload_to_gpu_buffer(cpy,
&&(GPUTransferBufferLocation){.transfer_buffer = ts, .offset = Quad.vertices.offsetof}, &&(GPUTransferBufferLocation){.transfer_buffer = ts, .offset = Quad.vertices.offsetof},
&&(GPUBufferRegion){.buffer = qb.vert_buf, .offset = 0, .size = Quad.vertices.sizeof}, &&(GPUBufferRegion){.buffer = qb.vert_buf, .offset = 0, .size = Quad.vertices.sizeof},
false false
); );
// upload indices // upload indices
sdl::upload_to_gpu_buffer(cpy, sdl::upload_to_gpu_buffer(cpy,
&&(GPUTransferBufferLocation){.transfer_buffer = ts, .offset = Quad.indices.offsetof}, &&(GPUTransferBufferLocation){.transfer_buffer = ts, .offset = Quad.indices.offsetof},
&&(GPUBufferRegion){.buffer = qb.idx_buf, .offset = 0, .size = Quad.indices.sizeof}, &&(GPUBufferRegion){.buffer = qb.idx_buf, .offset = 0, .size = Quad.indices.sizeof},
false false
@ -278,6 +299,13 @@ $endif
qb.initialized = true; 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) fn void Renderer.free(&self)
{ {
foreach (&s: self.shaders) { foreach (&s: self.shaders) {
@ -310,17 +338,24 @@ fn void Renderer.free(&self)
sdl::quit(); sdl::quit();
} }
fn void Renderer.resize_window(&self, uint width, uint height) fn void Renderer.resize_window(&self, uint width, uint height)
{ {
sdl::set_window_size(self.win, width, height); sdl::set_window_size(self.win, width, height);
} }
fn void Renderer.get_window_size(&self, int* width, int* height) fn void Renderer.get_window_size(&self, int* width, int* height)
{ {
sdl::get_window_size_in_pixels(self.win, width, 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 // 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 // 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) 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); 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 == "") { if (vert_path == "" || frag_path == "") {
unreachable("need both a vertex shader and fragment shader 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); 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 // this describes what we want to draw, since for drawing different things we have to change
// the GPUPrimitiveType and GPURasterizerState for the pipeline. // the GPUPrimitiveType and GPURasterizerState for the pipeline.
enum PipelineType : (GPUPrimitiveType primitive_type, GPURasterizerState raster_state) { 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, .fragment_shader = s.frag,
// This structure specifies how the vertex buffer looks in memory, what it contains // 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, // 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 // 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. // is represented by two floats, the color as 32 bit rgba and the uv also as intgers.
.vertex_input_state = { .vertex_input_state = {
// the description of each vertex buffer, for now I use only one buffer // the description of each vertex buffer, for now I use only one buffer
.vertex_buffer_descriptions = (GPUVertexBufferDescription[]){ .vertex_buffer_descriptions = (GPUVertexBufferDescription[]){
@ -467,9 +509,15 @@ fn void Renderer.create_pipeline(&self, String shader_name, PipelineType type)
.buffer_slot = 1, .buffer_slot = 1,
.format = GPU_VERTEXELEMENTFORMAT_UBYTE4, .format = GPU_VERTEXELEMENTFORMAT_UBYTE4,
.offset = QuadAttributes.color.offsetof, .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 // the pipeline's primitive type and rasterizer state differs based on what needs to
// be drawn // be drawn
@ -513,12 +561,21 @@ fn void Renderer.create_pipeline(&self, String shader_name, PipelineType type)
self.pipelines.push(p); self.pipelines.push(p);
} }
// ============================================================================================== //
// TEXTURE LOADING //
// ============================================================================================== //
// NOTE: with TEXTUREUSAGE_SAMPLER the texture format cannot be intger _UINT so it has to be nermalized // NOTE: with TEXTUREUSAGE_SAMPLER the texture format cannot be intger _UINT so it has to be nermalized
enum TextureType : (GPUTextureFormat format) { enum TextureType : (GPUTextureFormat format) {
FULL_COLOR = GPU_TEXTUREFORMAT_R8G8B8A8_UNORM, FULL_COLOR = GPU_TEXTUREFORMAT_R8G8B8A8_UNORM,
JUST_ALPHA = GPU_TEXTUREFORMAT_R8_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) macro void Renderer.new_texture(&self, name_or_id, TextureType type, char[] pixels, uint width, uint height)
{ {
$switch $typeof(name_or_id): $switch $typeof(name_or_id):
@ -528,6 +585,8 @@ macro void Renderer.new_texture(&self, name_or_id, TextureType type, char[] pixe
$endswitch $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) 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): $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 /* 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 * 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) fn void Renderer.new_texture_by_id(&self, Id id, TextureType type, char[] pixels, uint width, uint height)
{ {
// the texture description // the texture description
GPUTextureCreateInfo tci = { GPUTextureCreateInfo tci = {
.type = GPU_TEXTURETYPE_2D, .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); 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) 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); 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 = { QuadAttributes qa = {
.pos = {.x = x, .y = y, .w = w, .h = h}, .pos = {.x = x, .y = y, .w = w, .h = h},
.uv = {.u = u, .v = v}, .uv = {.u = u, .v = v},
.color = color .color = color,
.type = type,
}; };
return self.map_quad(qa); 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 = { QuadAttributes qa = {
.pos = {.x = x, .y = y, .w = w, .h = h}, .pos = {.x = x, .y = y, .w = w, .h = h},
.uv = {.u = radius, .v = radius}, .uv = {.u = radius, .v = radius},
.color = color .color = color,
.type = type
}; };
return self.map_quad(qa); return self.map_quad(qa);
@ -694,7 +770,7 @@ fn void Renderer.upload_quads(&self)
GPUCopyPass* cpy = sdl::begin_gpu_copy_pass(cmd); GPUCopyPass* cpy = sdl::begin_gpu_copy_pass(cmd);
// upload quad attributes // upload quad attributes
sdl::upload_to_gpu_buffer(cpy, sdl::upload_to_gpu_buffer(cpy,
&&(GPUTransferBufferLocation){.transfer_buffer = qb.attr_ts, .offset = 0}, &&(GPUTransferBufferLocation){.transfer_buffer = qb.attr_ts, .offset = 0},
&&(GPUBufferRegion){.buffer = qb.attr_buf, .offset = 0, .size = QuadAttributes.sizeof * qb.count}, &&(GPUBufferRegion){.buffer = qb.attr_buf, .offset = 0, .size = QuadAttributes.sizeof * qb.count},
false 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); sdl::push_gpu_fragment_uniform_data(self.render_cmdbuf, 0, &v, ViewsizeUniform.sizeof);
if (clear_screen) { if (clear_screen) {
GPURenderPass* pass = sdl::begin_gpu_render_pass(self.render_cmdbuf, GPURenderPass* pass = sdl::begin_gpu_render_pass(self.render_cmdbuf,
&&(GPUColorTargetInfo){ &&(GPUColorTargetInfo){
.texture = self.swapchain_texture, .texture = self.swapchain_texture,
.mip_level = 0, .mip_level = 0,
@ -771,15 +847,17 @@ fn void Renderer.begin_render(&self, bool clear_screen)
} }
} }
fn void Renderer.end_render(&self) fn void Renderer.end_render(&self)
{ {
sdl::submit_gpu_command_buffer(self.render_cmdbuf); sdl::submit_gpu_command_buffer(self.render_cmdbuf);
self.reset_quads(); self.reset_quads();
} }
fn void Renderer.start_render_pass(&self, String pipeline_name) 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){ &&(GPUColorTargetInfo){
.texture = self.swapchain_texture, .texture = self.swapchain_texture,
.mip_level = 0, .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); sdl::bind_gpu_graphics_pipeline(self.render_pass, p);
} }
fn void Renderer.end_render_pass(&self) 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); // TODO: bind in one pass
sdl::bind_gpu_fragment_samplers(self.render_pass, 0, foreach (idx, name: texture_names) {
(GPUTextureSamplerBinding[]){{.texture = tx.texture, .sampler = tx.sampler}}, 1 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) fn void Renderer.set_scissor(&self, int x, int y, int w, int h)
{ {
// in vulkan scissor size must be positive, clamp to zero // in vulkan scissor size must be positive, clamp to zero
@ -852,10 +954,10 @@ fn void Renderer.reset_scissor(&self)
* - draw * - draw
* - push uniform * - push uniform
* - draw * - draw
* *
* 2. The GPU buffers are read per-command-buffer and not per * 2. The GPU buffers are read per-command-buffer and not per
* render pass. So I cannot override an element in the buffer * render pass. So I cannot override an element in the buffer
* before submitting the command buffer. * before submitting the command buffer.
*/ */
/// === END NOTES === /// === END NOTES ===
@ -866,29 +968,31 @@ fn void Renderer.render_ugui(&self, CmdQueue* queue)
foreach (&c : queue) { foreach (&c : queue) {
if (c.type == CMD_RECT) { if (c.type == CMD_RECT) {
CmdRect r = c.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) { } else if (c.type == CMD_SPRITE) {
CmdSprite s = c.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(); self.upload_quads();
Cmd* last_command; self.start_render_pass("UGUI_PIPELINE");
uint off; self.bind_textures_id(self.font_atlas_id, self.sprite_atlas_id);
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;
}
uint calls = 0;
uint off;
for (Cmd* cmd; (cmd = queue.dequeue() ?? null) != null;) {
switch (cmd.type) { switch (cmd.type) {
case CMD_RECT: nextcase;
case CMD_SPRITE:
count++;
case CMD_UPDATE_ATLAS: case CMD_UPDATE_ATLAS:
// TODO: verify the correct type // TODO: verify the correct type
CmdUpdateAtlas u = cmd.update_atlas; CmdUpdateAtlas u = cmd.update_atlas;
@ -903,60 +1007,20 @@ fn void Renderer.render_ugui(&self, CmdQueue* queue)
self.scissor_y = s.y; self.scissor_y = s.y;
self.scissor_w = s.w; self.scissor_w = s.w;
self.scissor_h = s.h; self.scissor_h = s.h;
default: unreachable("unknown command: %s", cmd.type); default:
} self.set_scissor(self.scissor_x, self.scissor_y, self.scissor_w, self.scissor_h);
uint count = 1;
last_command = cmd; while (queue.len() != 0 && (queue.get(0).type == CMD_RECT || queue.get(0).type == CMD_SPRITE)) {
} count++;
self.end_command(last_command, off, count); (void)queue.dequeue();
}
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);
} }
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.draw_quads(off, count);
self.end_render_pass(); off += count;
case CMD_UPDATE_ATLAS: break; calls++;
case CMD_SCISSOR: break; // self.draw_quads(off++, 1);
default: unreachable("unknown command: %s", cmd.type); }
} }
self.end_render_pass();
// ugui::println("calls: ", calls);
} }