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[*] 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 //

View File

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