Compare commits

..

No commits in common. "bd8c73ecd5818542f94a587c919c9dfdbb78ce7d" and "24bc2c67bcd4c42e5d9763671c964340ebf86f08" have entirely different histories.

21 changed files with 230 additions and 470 deletions

4
.gitmodules vendored
View File

@ -14,7 +14,3 @@
path = lib/schrift.c3l/thirdparty/libschrift path = lib/schrift.c3l/thirdparty/libschrift
url = https://github.com/tomolt/libschrift url = https://github.com/tomolt/libschrift
ignore = dirty ignore = dirty
[submodule "lib/sdl3.c3l"]
path = lib/sdl3.c3l
url = https://git.alemauri.eu/alema/sdl3.c3l
ignore = dirty

View File

@ -1,3 +1,3 @@
test_renderer: test_renderer.c3 src/renderer.c3 resources/shaders/source/* test_renderer: test_renderer.c3 src/renderer.c3 resources/shaders/source/*
scripts/compile_shaders.sh scripts/compile_shaders.sh
c3c compile -g -O0 test_renderer.c3 src/renderer.c3 --libdir ../sdl3.c3l --lib sdl3 c3c compile-run -g -O0 test_renderer.c3 src/renderer.c3 --libdir ../sdl3.c3l --lib sdl3

8
TODO
View File

@ -107,11 +107,3 @@ to maintain focus until mouse release (fix scroll bars)
queried by the user for later use. This allows for smaller caches and in general queried by the user for later use. This allows for smaller caches and in general
reduces some load, since most of the stuff is recomputed for every frame. reduces some load, since most of the stuff is recomputed for every frame.
## SDL3 Renderer
- smart batching
- maybe use instancing since we are always drawing the same geometry. With instancing every
different quad could have its coulour, border and radius with much better performance than
issuing a draw call for every quad (and uploading it)
https://rastertek.com/dx11win10tut48.html
https://www.braynzarsoft.net/viewtutorial/q16390-33-instancing-with-indexed-primitives

@ -1 +0,0 @@
Subproject commit 076355e2d126e7546e53663b97e8dec22667d34d

View File

@ -1,10 +1,8 @@
module fifo::faults;
faultdef FULL, EMPTY;
module fifo{Type}; module fifo{Type};
import std::core::mem; import std::core::mem;
import std::sort;
faultdef FULL, EMPTY;
// TODO: specify the allocator // TODO: specify the allocator
@ -29,7 +27,7 @@ fn void Fifo.free(&fifo)
fn void? Fifo.enqueue(&fifo, Type *elem) fn void? Fifo.enqueue(&fifo, Type *elem)
{ {
if (fifo.count >= fifo.arr.len) { if (fifo.count >= fifo.arr.len) {
return fifo::faults::FULL?; return FULL?;
} }
usz in = (fifo.out + fifo.count) % fifo.arr.len; usz in = (fifo.out + fifo.count) % fifo.arr.len;
fifo.arr[in] = *elem; fifo.arr[in] = *elem;
@ -39,53 +37,10 @@ fn void? Fifo.enqueue(&fifo, Type *elem)
fn Type*? Fifo.dequeue(&fifo) fn Type*? Fifo.dequeue(&fifo)
{ {
if (fifo.count == 0) { if (fifo.count == 0) {
return fifo::faults::EMPTY?; return EMPTY?;
} }
Type *ret = &fifo.arr[fifo.out]; Type *ret = &fifo.arr[fifo.out];
fifo.count--; fifo.count--;
fifo.out = (fifo.out + 1) % fifo.arr.len; fifo.out = (fifo.out + 1) % fifo.arr.len;
return ret; return ret;
} }
macro Type Fifo.get(&fifo, usz i) @operator([])
{
return fifo.arr[(fifo.out + i) % fifo.arr.len];
}
fn void Fifo.set(&fifo, usz i, Type val) @operator([]=)
{
fifo.arr[(fifo.out + i) % fifo.arr.len] = val;
}
macro Type* Fifo.get_ref(&fifo, usz i) @operator(&[])
{
return &fifo.arr[(fifo.out + i) % fifo.arr.len];
}
macro usz Fifo.len(&fifo) @operator(len)
{
return fifo.count;
}
fn void? Fifo.sort(&fifo)
{
Type[] arr = mem::new_array(Type, fifo.count);
defer mem::free(arr);
foreach(i, c: fifo) {
arr[i] = c;
}
// doesn't keep ordering
//sort::quicksort(arr);
// seems to keep the right order but we will never know...
// also since most things are already ordered the time is closer to O(n) than to O(n^2)
sort::insertionsort(arr);
fifo.count = 0;
fifo.out = 0;
foreach (&c: arr) {
fifo.enqueue(c)!;
}
}

View File

@ -40,7 +40,7 @@ fn ElemEvents? Ctx.button(&ctx, String label, Rect size, bool state = false)
} }
// Draw the button // Draw the button
ctx.push_rect(elem.bounds, col, parent.div.z_index, do_border: true, do_radius: true)!; ctx.push_rect(elem.bounds, col, do_border: true, do_radius: true)!;
return elem.events; return elem.events;
} }
@ -80,8 +80,8 @@ fn ElemEvents? Ctx.button_label(&ctx, String label, Rect size = {0,0,short.max,s
Point off = ctx.center_text(text_size, elem.bounds); Point off = ctx.center_text(text_size, elem.bounds);
text_size.x += off.x; text_size.x += off.x;
text_size.y += off.y; text_size.y += off.y;
ctx.push_rect(elem.bounds, col, parent.div.z_index, do_border: true, do_radius: true)!; ctx.push_rect(elem.bounds, col, do_border: true, do_radius: true)!;
ctx.push_string(text_size, label, parent.div.z_index)!; ctx.push_string(text_size, label)!;
return elem.events; return elem.events;
} }
@ -116,13 +116,13 @@ fn ElemEvents? Ctx.button_icon(&ctx, String label, String icon, String on_icon =
Id tex_id = ctx.sprite_atlas.id; Id tex_id = ctx.sprite_atlas.id;
if (state && on_icon != "") { if (state && on_icon != "") {
ctx.push_sprite(elem.bounds, on_sprite.uv(), tex_id, parent.div.z_index, type: on_sprite.type)!; ctx.push_sprite(elem.bounds, on_sprite.uv(), tex_id, type: on_sprite.type)!;
} else { } else {
ctx.push_sprite(elem.bounds, def_sprite.uv(), tex_id, parent.div.z_index, type: def_sprite.type)!; ctx.push_sprite(elem.bounds, def_sprite.uv(), tex_id, type: def_sprite.type)!;
} }
// Draw the button // Draw the button
ctx.push_rect(elem.bounds, col, parent.div.z_index, do_border: true, do_radius: true)!; ctx.push_rect(elem.bounds, col, do_border: true, do_radius: true)!;
return elem.events; return elem.events;
} }
@ -159,7 +159,7 @@ fn void? Ctx.checkbox(&ctx, String label, String description, Point off, bool* s
Color col; Color col;
if (tick_sprite != {}) { if (tick_sprite != {}) {
col = ctx.style.bgcolor; col = ctx.style.bgcolor;
ctx.push_rect(elem.bounds, col, parent.div.z_index, do_border: true, do_radius: true)!; ctx.push_rect(elem.bounds, col, do_border: true, do_radius: true)!;
if (*state) { if (*state) {
ctx.draw_sprite_raw(tick_sprite, elem.bounds)!; ctx.draw_sprite_raw(tick_sprite, elem.bounds)!;
} }
@ -170,7 +170,7 @@ fn void? Ctx.checkbox(&ctx, String label, String description, Point off, bool* s
col = 0xff00ffffu.to_rgba(); col = 0xff00ffffu.to_rgba();
} }
// Draw the button // Draw the button
ctx.push_rect(elem.bounds, col, parent.div.z_index, do_border: true, do_radius: true)!; ctx.push_rect(elem.bounds, col, do_border: true, do_radius: true)!;
} }
} }
@ -211,7 +211,7 @@ fn void? Ctx.toggle(&ctx, String label, String description, Point off, bool* sta
} }
// Draw the button // Draw the button
// FIXME: THIS IS SHIT // FIXME: THIS IS SHIT
ctx.push_rect(elem.bounds, ctx.style.bgcolor, parent.div.z_index, do_border: true, do_radius: true)!; ctx.push_rect(elem.bounds, ctx.style.bgcolor, do_border: true, do_radius: true)!;
Rect t = elem.bounds.add({*state ? (DEFAULT_SWITCH_SIZE+3) : +3, +3, -DEFAULT_SWITCH_SIZE-6, -6}); Rect t = elem.bounds.add({*state ? (DEFAULT_SWITCH_SIZE+3) : +3, +3, -DEFAULT_SWITCH_SIZE-6, -6});
ctx.push_rect(t, col, parent.div.z_index, do_border: false, do_radius: true)!; ctx.push_rect(t, col, do_border: false, do_radius: true)!;
} }

View File

@ -1,7 +1,6 @@
module ugui; module ugui;
import std::ascii; import std::ascii;
import std::io;
// command type // command type
enum CmdType { enum CmdType {
@ -38,9 +37,8 @@ struct CmdScissor {
} }
// command structure // command structure
struct Cmd (Printable) { struct Cmd {
CmdType type; CmdType type;
int z_index;
union { union {
CmdRect rect; CmdRect rect;
CmdUpdateAtlas update_atlas; CmdUpdateAtlas update_atlas;
@ -49,18 +47,6 @@ struct Cmd (Printable) {
} }
} }
fn int Cmd.compare_to(Cmd a, Cmd b)
{
if (a.z_index == b.z_index) return 0;
return a.z_index > b.z_index ? 1 : -1;
}
// implement the Printable interface
fn usz? Cmd.to_format(Cmd* cmd, Formatter *f) @dynamic
{
return f.printf("Cmd{ type: %s, z_index: %d }", cmd.type, cmd.z_index);
}
macro bool cull_rect(Rect rect, Rect clip = {0,0,short.max,short.max}) macro bool cull_rect(Rect rect, Rect clip = {0,0,short.max,short.max})
{ {
bool no_area = rect.w <= 0 || rect.h <= 0; bool no_area = rect.w <= 0 || rect.h <= 0;
@ -68,9 +54,8 @@ macro bool cull_rect(Rect rect, Rect clip = {0,0,short.max,short.max})
} }
// FIXME: this whole thing could be done at compile time, maybe // FIXME: this whole thing could be done at compile time, maybe
macro Ctx.push_cmd(&ctx, Cmd *cmd, int z_index) macro Ctx.push_cmd(&ctx, Cmd *cmd)
{ {
cmd.z_index = z_index;
Rect rect; Rect rect;
switch (cmd.type) { switch (cmd.type) {
case CMD_RECT: rect = cmd.rect.rect; case CMD_RECT: rect = cmd.rect.rect;
@ -81,18 +66,9 @@ macro Ctx.push_cmd(&ctx, Cmd *cmd, int z_index)
return ctx.cmd_queue.enqueue(cmd); return ctx.cmd_queue.enqueue(cmd);
} }
fn void? Ctx.push_scissor(&ctx, Rect rect, int z_index)
{
Cmd sc = {
.type = CMD_SCISSOR,
.scissor.rect = rect.intersection(ctx.div_scissor),
};
ctx.push_cmd(&sc, z_index)!;
}
// FIXME: is this really the best solution? // FIXME: is this really the best solution?
// "rect" is the bounding box of the element, which includes the border and the padding (so not just the content) // "rect" is the bounding box of the element, which includes the border and the padding (so not just the content)
fn void? Ctx.push_rect(&ctx, Rect rect, Color color, int z_index, bool do_border = false, bool do_padding = false, bool do_radius = false) fn void? Ctx.push_rect(&ctx, Rect rect, Color color, bool do_border = false, bool do_padding = false, bool do_radius = false)
{ {
Rect border = ctx.style.border; Rect border = ctx.style.border;
Rect padding = ctx.style.padding; Rect padding = ctx.style.padding;
@ -106,7 +82,7 @@ fn void? Ctx.push_rect(&ctx, Rect rect, Color color, int z_index, bool do_border
.rect.color = border_color, .rect.color = border_color,
.rect.radius = do_radius ? radius : 0, .rect.radius = do_radius ? radius : 0,
}; };
ctx.push_cmd(&cmd, z_index)!; ctx.push_cmd(&cmd)!;
} }
Cmd cmd = { Cmd cmd = {
@ -121,11 +97,11 @@ fn void? Ctx.push_rect(&ctx, Rect rect, Color color, int z_index, bool do_border
.rect.radius = do_radius ? radius : 0, .rect.radius = do_radius ? radius : 0,
}; };
if (cull_rect(cmd.rect.rect, ctx.div_scissor)) return; if (cull_rect(cmd.rect.rect, ctx.div_scissor)) return;
ctx.push_cmd(&cmd, z_index)!; ctx.push_cmd(&cmd)!;
} }
// TODO: add texture id // TODO: add texture id
fn void? Ctx.push_sprite(&ctx, Rect bounds, Rect texture, Id texture_id, int z_index, Color hue = 0xffffffffu.to_rgba(), SpriteType type = SPRITE_NORMAL) fn void? Ctx.push_sprite(&ctx, Rect bounds, Rect texture, Id texture_id, Color hue = 0xffffffffu.to_rgba(), SpriteType type = SPRITE_NORMAL)
{ {
Cmd cmd = { Cmd cmd = {
.type = CMD_SPRITE, .type = CMD_SPRITE,
@ -135,16 +111,16 @@ fn void? Ctx.push_sprite(&ctx, Rect bounds, Rect texture, Id texture_id, int z_i
.sprite.texture_id = texture_id, .sprite.texture_id = texture_id,
.sprite.hue = hue, .sprite.hue = hue,
}; };
ctx.push_cmd(&cmd, z_index)!; ctx.push_cmd(&cmd)!;
} }
fn void? Ctx.push_string(&ctx, Rect bounds, String text, int z_index, Color hue = 0xffffffffu.to_rgba()) fn void? Ctx.push_string(&ctx, Rect bounds, String text, Color hue = 0xffffffffu.to_rgba())
{ {
if (text.len == 0) { if (text.len == 0) {
return; return;
} }
ctx.push_scissor(bounds, z_index)!; ctx.push_scissor(bounds)!;
short baseline = (short)ctx.font.ascender; short baseline = (short)ctx.font.ascender;
short line_height = (short)ctx.font.ascender - (short)ctx.font.descender; short line_height = (short)ctx.font.ascender - (short)ctx.font.descender;
@ -176,7 +152,7 @@ fn void? Ctx.push_string(&ctx, Rect bounds, String text, int z_index, Color hue
.h = gp.h, .h = gp.h,
}; };
// push the sprite only if it collides with the bounds // push the sprite only if it collides with the bounds
if (!cull_rect(gb, bounds)) ctx.push_sprite(gb, gt, texture_id, z_index, hue)!; if (!cull_rect(gb, bounds)) ctx.push_sprite(gb, gt, texture_id, hue)!;
line_len += gp.adv; line_len += gp.adv;
} else if (cp == '\n'){ } else if (cp == '\n'){
orig.y += line_height + line_gap; orig.y += line_height + line_gap;
@ -187,7 +163,7 @@ fn void? Ctx.push_string(&ctx, Rect bounds, String text, int z_index, Color hue
} }
// FIXME: we never get here if an error was thrown before // FIXME: we never get here if an error was thrown before
ctx.push_scissor({}, z_index)!; ctx.push_scissor({})!;
} }
fn void? Ctx.push_update_atlas(&ctx, Atlas* atlas) fn void? Ctx.push_update_atlas(&ctx, Atlas* atlas)
@ -202,7 +178,14 @@ fn void? Ctx.push_update_atlas(&ctx, Atlas* atlas)
.bpp = (ushort)atlas.type.bpp(), .bpp = (ushort)atlas.type.bpp(),
}, },
}; };
// update the atlases before everything else ctx.push_cmd(&up)!;
ctx.push_cmd(&up, -1)!;
} }
fn void? Ctx.push_scissor(&ctx, Rect rect)
{
Cmd sc = {
.type = CMD_SCISSOR,
.scissor.rect = rect.intersection(ctx.div_scissor),
};
ctx.push_cmd(&sc)!;
}

View File

@ -244,7 +244,6 @@ fn void? Ctx.frame_begin(&ctx)
}, },
.div = { .div = {
.layout = LAYOUT_ROW, .layout = LAYOUT_ROW,
.z_index = 0,
.children_bounds = { .children_bounds = {
.w = ctx.width, .w = ctx.width,
.h = ctx.height, .h = ctx.height,
@ -264,8 +263,6 @@ fn void? Ctx.frame_begin(&ctx)
// TODO: add a background color taken from a theme or config // TODO: add a background color taken from a theme or config
} }
const int DEBUG = 1;
fn void? Ctx.frame_end(&ctx) fn void? Ctx.frame_end(&ctx)
{ {
Elem* root = ctx.get_elem_by_tree_idx(0)!; Elem* root = ctx.get_elem_by_tree_idx(0)!;
@ -288,12 +285,10 @@ fn void? Ctx.frame_end(&ctx)
ctx.sprite_atlas.should_update = false; ctx.sprite_atlas.should_update = false;
} }
// debug $if 1:
$if DEBUG == 1:
// draw mouse position // draw mouse position
Cmd cmd = { Cmd cmd = {
.type = CMD_RECT, .type = CMD_RECT,
.z_index = int.max-1, // hopefully over everything else
.rect.rect = { .rect.rect = {
.x = ctx.input.mouse.pos.x - 2, .x = ctx.input.mouse.pos.x - 2,
.y = ctx.input.mouse.pos.y - 2, .y = ctx.input.mouse.pos.y - 2,
@ -303,17 +298,6 @@ $if DEBUG == 1:
.rect.color = 0xff00ffffu.to_rgba() .rect.color = 0xff00ffffu.to_rgba()
}; };
ctx.cmd_queue.enqueue(&cmd)!; ctx.cmd_queue.enqueue(&cmd)!;
// dump the command buffer
// io::printn("Command Buffer Dump:");
// foreach(idx, c: ctx.cmd_queue) {
// io::printfn("\t [%d] = {%s}", idx, c);
// }
ctx.cmd_queue.sort()!;
// io::printn("Sorted Command Buffer Dump:");
// foreach(idx, c: ctx.cmd_queue) {
// io::printfn("\t [%d] = {%s}", idx, c);
// }
$endif $endif
} }

View File

@ -18,7 +18,6 @@ struct ElemDiv {
bool on; bool on;
float value; float value;
} }
int z_index;
Rect children_bounds; // current frame children bounds Rect children_bounds; // current frame children bounds
Rect pcb; // previous frame children bounds Rect pcb; // previous frame children bounds
Point origin_r, origin_c; Point origin_r, origin_c;
@ -47,7 +46,6 @@ fn void? Ctx.div_begin(&ctx, String label, Rect size, bool scroll_x = false, boo
} }
elem.div.scroll_x.enabled = scroll_x; elem.div.scroll_x.enabled = scroll_x;
elem.div.scroll_y.enabled = scroll_y; elem.div.scroll_y.enabled = scroll_y;
elem.div.z_index = parent.div.z_index + 1;
// 2. layout the element // 2. layout the element
Rect wanted_size = { Rect wanted_size = {
@ -61,7 +59,7 @@ fn void? Ctx.div_begin(&ctx, String label, Rect size, bool scroll_x = false, boo
// update the ctx scissor // update the ctx scissor
ctx.div_scissor = elem.bounds; ctx.div_scissor = elem.bounds;
ctx.push_scissor(elem.bounds, elem.div.z_index)!; ctx.push_scissor(elem.bounds)!;
// 4. Fill the div fields // 4. Fill the div fields
elem.div.origin_c = { elem.div.origin_c = {
@ -73,7 +71,7 @@ fn void? Ctx.div_begin(&ctx, String label, Rect size, bool scroll_x = false, boo
// Add the background to the draw stack // Add the background to the draw stack
bool do_border = parent.div.layout == LAYOUT_FLOATING; bool do_border = parent.div.layout == LAYOUT_FLOATING;
ctx.push_rect(elem.bounds, ctx.style.bgcolor, elem.div.z_index, do_border: do_border)!; ctx.push_rect(elem.bounds, ctx.style.bgcolor, do_border: do_border)!;
elem.events = ctx.get_elem_events(elem); elem.events = ctx.get_elem_events(elem);

View File

@ -112,8 +112,8 @@ fn void Ctx.input_mouse_button(&ctx, MouseButtons buttons)
// Mouse was moved, report absolute position // Mouse was moved, report absolute position
fn void Ctx.input_mouse_abs(&ctx, short x, short y) fn void Ctx.input_mouse_abs(&ctx, short x, short y)
{ {
ctx.input.mouse.pos.x = math::clamp(x, (short)0, ctx.width); ctx.input.mouse.pos.x = math::clamp(x, 0u16, ctx.width);
ctx.input.mouse.pos.y = math::clamp(y, (short)0, ctx.height); ctx.input.mouse.pos.y = math::clamp(y, 0u16, ctx.height);
short dx, dy; short dx, dy;
dx = x - ctx.input.mouse.pos.x; dx = x - ctx.input.mouse.pos.x;
@ -135,8 +135,8 @@ fn void Ctx.input_mouse_delta(&ctx, short dx, short dy)
mx = ctx.input.mouse.pos.x + dx; mx = ctx.input.mouse.pos.x + dx;
my = ctx.input.mouse.pos.y + dy; my = ctx.input.mouse.pos.y + dy;
ctx.input.mouse.pos.x = math::clamp(mx, (short)0, ctx.width); ctx.input.mouse.pos.x = math::clamp(mx, 0u16, ctx.width);
ctx.input.mouse.pos.y = math::clamp(my, (short)0, ctx.height); ctx.input.mouse.pos.y = math::clamp(my, 0u16, ctx.height);
ctx.input.events.mouse_move = dx != 0 || dy != 0; ctx.input.events.mouse_move = dx != 0 || dy != 0;
} }

View File

@ -61,8 +61,8 @@ fn ElemEvents? Ctx.slider_hor(&ctx,
} }
// Draw the slider background and handle // Draw the slider background and handle
ctx.push_rect(elem.bounds, bgcolor, parent.div.z_index)!; ctx.push_rect(elem.bounds, bgcolor)!;
ctx.push_rect(elem.slider.handle, handlecolor, parent.div.z_index)!; ctx.push_rect(elem.slider.handle, handlecolor)!;
return elem.events; return elem.events;
} }
@ -124,8 +124,8 @@ fn ElemEvents? Ctx.slider_ver(&ctx,
} }
// Draw the slider background and handle // Draw the slider background and handle
ctx.push_rect(elem.bounds, bgcolor, parent.div.z_index)!; ctx.push_rect(elem.bounds, bgcolor)!;
ctx.push_rect(elem.slider.handle, handlecolor, parent.div.z_index)!; ctx.push_rect(elem.slider.handle, handlecolor)!;
return elem.events; return elem.events;
} }

View File

@ -164,13 +164,12 @@ fn void? Ctx.draw_sprite(&ctx, String label, String name, Point off = {0,0})
Id tex_id = ctx.sprite_atlas.id; Id tex_id = ctx.sprite_atlas.id;
return ctx.push_sprite(elem.bounds, uv, tex_id, parent.div.z_index)!; return ctx.push_sprite(elem.bounds, uv, tex_id)!;
} }
fn void? Ctx.draw_sprite_raw(&ctx, String name, Rect bounds) fn void? Ctx.draw_sprite_raw(&ctx, String name, Rect bounds)
{ {
Elem *parent = ctx.get_parent()!;
Sprite* sprite = ctx.sprite_atlas.get(name)!; Sprite* sprite = ctx.sprite_atlas.get(name)!;
Id tex_id = ctx.sprite_atlas.id; Id tex_id = ctx.sprite_atlas.id;
return ctx.push_sprite(bounds, sprite.uv(), tex_id, parent.div.z_index, type: sprite.type)!; return ctx.push_sprite(bounds, sprite.uv(), tex_id, type: sprite.type)!;
} }

View File

@ -26,5 +26,5 @@ fn void? Ctx.text_unbounded(&ctx, String label, String text)
elem.bounds = ctx.position_element(parent, text_size, true); elem.bounds = ctx.position_element(parent, text_size, true);
if (elem.bounds.is_null()) { return; } if (elem.bounds.is_null()) { return; }
ctx.push_string(elem.bounds, text, parent.div.z_index)!; ctx.push_string(elem.bounds, text)!;
} }

View File

@ -1,6 +1,3 @@
module vtree::faults;
faultdef CANNOT_SHRINK, INVALID_REFERENCE, TREE_FULL, REFERENCE_NOT_PRESENT, INVALID_ARGUMENT;
module vtree{ElemType}; module vtree{ElemType};
import std::core::mem; import std::core::mem;
@ -12,6 +9,7 @@ struct VTree {
isz[] refs, ordered_refs; isz[] refs, ordered_refs;
} }
faultdef CANNOT_SHRINK, INVALID_REFERENCE, TREE_FULL, REFERENCE_NOT_PRESENT, INVALID_ARGUMENT;
macro VTree.ref_is_valid(&tree, isz ref) { return (ref >= 0 && ref < tree.refs.len); } macro VTree.ref_is_valid(&tree, isz ref) { return (ref >= 0 && ref < tree.refs.len); }
macro VTree.ref_is_present(&tree, isz ref) { return tree.refs[ref] >= 0; } macro VTree.ref_is_present(&tree, isz ref) { return tree.refs[ref] >= 0; }
@ -90,14 +88,14 @@ fn void? VTree.resize(&tree, usz newsize)
{ {
// return error when shrinking with too many elements // return error when shrinking with too many elements
if (newsize < tree.elements) { if (newsize < tree.elements) {
return vtree::faults::CANNOT_SHRINK?; return CANNOT_SHRINK?;
} }
// pack the vector when shrinking to avoid data loss // pack the vector when shrinking to avoid data loss
if ((int)newsize < tree.size()) { if ((int)newsize < tree.size()) {
// FIXME: packing destroys all references to elements of vec // FIXME: packing destroys all references to elements of vec
// so shrinking may cause dangling pointers // so shrinking may cause dangling pointers
return vtree::faults::CANNOT_SHRINK?; return CANNOT_SHRINK?;
} }
usz old_size = tree.size(); usz old_size = tree.size();
@ -122,18 +120,18 @@ fn isz? VTree.add(&tree, ElemType elem, isz parent)
{ {
// invalid parent // invalid parent
if (!tree.ref_is_valid(parent)) { if (!tree.ref_is_valid(parent)) {
return vtree::faults::INVALID_REFERENCE?; return INVALID_REFERENCE?;
} }
// no space left // no space left
if (tree.elements >= tree.size()) { if (tree.elements >= tree.size()) {
return vtree::faults::TREE_FULL?; return TREE_FULL?;
} }
// check if the parent exists // check if the parent exists
// if there are no elements in the tree the first add will set the root // if there are no elements in the tree the first add will set the root
if (!tree.ref_is_present(parent) && tree.elements != 0) { if (!tree.ref_is_present(parent) && tree.elements != 0) {
return vtree::faults::REFERENCE_NOT_PRESENT?; return REFERENCE_NOT_PRESENT?;
} }
// get the first free spot // get the first free spot
@ -145,7 +143,7 @@ fn isz? VTree.add(&tree, ElemType elem, isz parent)
} }
} }
if (free_spot < 0) { if (free_spot < 0) {
return vtree::faults::TREE_FULL?; return TREE_FULL?;
} }
// finally add the element // finally add the element
@ -161,7 +159,7 @@ fn isz? VTree.add(&tree, ElemType elem, isz parent)
fn usz? VTree.prune(&tree, isz ref) fn usz? VTree.prune(&tree, isz ref)
{ {
if (!tree.ref_is_valid(ref)) { if (!tree.ref_is_valid(ref)) {
return vtree::faults::INVALID_REFERENCE?; return INVALID_REFERENCE?;
} }
if (!tree.ref_is_present(ref)) { if (!tree.ref_is_present(ref)) {
@ -195,7 +193,7 @@ fn usz VTree.nuke(&tree)
fn usz? VTree.subtree_size(&tree, isz ref) fn usz? VTree.subtree_size(&tree, isz ref)
{ {
if (!tree.ref_is_valid(ref)) { if (!tree.ref_is_valid(ref)) {
return vtree::faults::INVALID_REFERENCE?; return INVALID_REFERENCE?;
} }
if (!tree.ref_is_present(ref)) { if (!tree.ref_is_present(ref)) {
@ -217,17 +215,17 @@ fn usz? VTree.subtree_size(&tree, isz ref)
fn isz? VTree.children_it(&tree, isz parent, isz *cursor) fn isz? VTree.children_it(&tree, isz parent, isz *cursor)
{ {
if (cursor == null) { if (cursor == null) {
return vtree::faults::INVALID_ARGUMENT?; return INVALID_ARGUMENT?;
} }
// if the cursor is out of bounds then we are done for sure // if the cursor is out of bounds then we are done for sure
if (!tree.ref_is_valid(*cursor)) { if (!tree.ref_is_valid(*cursor)) {
return vtree::faults::INVALID_REFERENCE?; return INVALID_REFERENCE?;
} }
// same for the parent, if it's invalid it can't have children // same for the parent, if it's invalid it can't have children
if (!tree.ref_is_valid(parent) || !tree.ref_is_present(parent)) { if (!tree.ref_is_valid(parent) || !tree.ref_is_present(parent)) {
return vtree::faults::INVALID_REFERENCE?; return INVALID_REFERENCE?;
} }
// find the first child, update the cursor and return the ref // find the first child, update the cursor and return the ref
@ -255,7 +253,7 @@ fn isz? VTree.children_it(&tree, isz parent, isz *cursor)
fn isz? VTree.level_order_it(&tree, isz ref, isz *cursor) fn isz? VTree.level_order_it(&tree, isz ref, isz *cursor)
{ {
if (cursor == null) { if (cursor == null) {
return vtree::faults::INVALID_ARGUMENT?; return INVALID_ARGUMENT?;
} }
isz[] queue = tree.ordered_refs; isz[] queue = tree.ordered_refs;
@ -302,11 +300,11 @@ fn isz? VTree.level_order_it(&tree, isz ref, isz *cursor)
fn isz? VTree.parentof(&tree, isz ref) fn isz? VTree.parentof(&tree, isz ref)
{ {
if (!tree.ref_is_valid(ref)) { if (!tree.ref_is_valid(ref)) {
return vtree::faults::INVALID_REFERENCE?; return INVALID_REFERENCE?;
} }
if (!tree.ref_is_present(ref)) { if (!tree.ref_is_present(ref)) {
return vtree::faults::REFERENCE_NOT_PRESENT?; return REFERENCE_NOT_PRESENT?;
} }
return tree.refs[ref]; return tree.refs[ref];
@ -315,11 +313,11 @@ fn isz? VTree.parentof(&tree, isz ref)
fn ElemType? VTree.get(&tree, isz ref) fn ElemType? VTree.get(&tree, isz ref)
{ {
if (!tree.ref_is_valid(ref)) { if (!tree.ref_is_valid(ref)) {
return vtree::faults::INVALID_REFERENCE?; return INVALID_REFERENCE?;
} }
if (!tree.ref_is_present(ref)) { if (!tree.ref_is_present(ref)) {
return vtree::faults::REFERENCE_NOT_PRESENT?; return REFERENCE_NOT_PRESENT?;
} }
return tree.vector[ref]; return tree.vector[ref];

View File

@ -1,7 +1,7 @@
{ {
"langrev": "1", "langrev": "1",
"warnings": ["no-unused"], "warnings": ["no-unused"],
"dependency-search-paths": ["lib", "../../Programs/Source/c3-vendor/libraries"], "dependency-search-paths": ["lib", "../../Programs/Source/c3-vendor/libraries", "../sdl3.c3l"],
"dependencies": ["raylib55", "sdl3", "ugui"], "dependencies": ["raylib55", "sdl3", "ugui"],
"features": [], "features": [],
"authors": ["Alessandro Mauri <ale@shitposting.expert>"], "authors": ["Alessandro Mauri <ale@shitposting.expert>"],

View File

@ -0,0 +1,18 @@
#version 330 core
// viewsize.x = viewport width in pixels; viewsize.y = viewport height in pixels
uniform ivec2 viewsize;
uniform ivec2 texturesize;
// texture uv coordinate in texture space
in vec2 uv;
uniform sampler2DRect ts;
const vec3 textcolor = vec3(1.0, 1.0, 1.0);
void main()
{
//gl_FragColor = vec4(1.0f,0.0f,0.0f,1.0f);
gl_FragColor = vec4(textcolor, texture(ts, uv));
}

View File

@ -0,0 +1,26 @@
#version 330 core
// viewsize.x = viewport width in pixels; viewsize.y = viewport height in pixels
uniform ivec2 viewsize;
uniform ivec2 texturesize;
// both position and and uv are in pixels, they where converted to floats when
// passed to the shader
layout(location = 0) in vec2 position;
layout(location = 1) in vec2 txcoord;
out vec2 uv;
void main()
{
vec2 v = vec2(float(viewsize.x), float(viewsize.y));
// vec2 p = vec2(position.x*2.0f/v.x - 1.0f, position.y*2.0f/v.y - 1.0f);
vec2 p = vec2(position.x*2.0/v.x - 1.0, 1.0 - position.y*2.0/v.y);
vec4 pos = vec4(p.x, p.y, 0.0f, 1.0f);
gl_Position = pos;
// since the texture is a GL_TEXTURE_RECTANGLE the coordintes do not need to
// be normalized
uv = vec2(txcoord.x, txcoord.y);
}

View File

@ -1,14 +0,0 @@
#version 450
layout(location = 0) in vec2 uv;
layout(location = 0) out vec4 fragColor;
layout(set = 2, binding = 0) uniform sampler2D tx;
void main()
{
ivec2 ts = textureSize(tx, 0);
vec2 fts = vec2(float(ts.x), float(ts.y));
vec2 real_uv = uv / fts;
fragColor = texture(tx, real_uv);
}

View File

@ -1,22 +0,0 @@
#version 450
layout(set = 1, binding = 0) uniform Viewport {
ivec2 view;
ivec2 not_needed;
};
layout(location = 0) in ivec2 position;
layout(location = 1) in ivec2 in_uv;
layout(location = 2) in ivec4 color;
layout(location = 0) out vec2 out_uv;
void main()
{
vec2 pos;
pos.x = float(position.x)*2.0 / view.x - 1.0;
pos.y = -(float(position.y)*2.0 / view.y - 1.0);
gl_Position = vec4(pos, 0.0, 1.0);
out_uv = vec2(float(in_uv.x), float(in_uv.y));
}

View File

@ -28,14 +28,11 @@ struct Texture {
uint id; uint id;
} }
// The GPU buffers that contain quad info, the size is determined by MAX_QUAD_BATCH // gpu buffer that contains a single quad
const int MAX_QUAD_BATCH = 128;
struct QuadBuffer { struct QuadBuffer {
sdl::GPUBuffer* vert_buf; sdl::GPUBuffer* vert_buf;
sdl::GPUBuffer* idx_buf; sdl::GPUBuffer* idx_buf;
bool initialized; bool initialized;
int count;
int off; // the offset to draw from
} }
alias ShaderList = List{Shader}; alias ShaderList = List{Shader};
@ -96,8 +93,6 @@ $if DEBUG == 0:
sdl::set_hint(sdl::HINT_VIDEO_DRIVER, "wayland"); sdl::set_hint(sdl::HINT_VIDEO_DRIVER, "wayland");
} }
$else $else
// in debug mode set the video driver to X11 because renderdoc/
// doesn't support debugging in wayland yet.
sdl::set_hint(sdl::HINT_VIDEO_DRIVER, "x11"); sdl::set_hint(sdl::HINT_VIDEO_DRIVER, "x11");
$endif $endif
@ -106,40 +101,44 @@ $endif
// init subsystems // init subsystems
if (!sdl::init(INIT_VIDEO)) { if (!sdl::init(INIT_VIDEO)) {
unreachable("sdl error: %s", sdl::get_error()); io::eprintfn("sdl error: %s", sdl::get_error());
libc::exit(1);
} }
// create the window // create the window
self.win = sdl::create_window(title, 640, 480, WINDOW_RESIZABLE|WINDOW_VULKAN); self.win = sdl::create_window(title, 640, 480, WINDOW_RESIZABLE|WINDOW_VULKAN);
if (self.win == null) { if (self.win == null) {
unreachable("sdl error: %s", sdl::get_error()); io::eprintfn("sdl error: %s", sdl::get_error());
libc::exit(1);
} }
// get the gpu device handle // get the gpu device handle
self.gpu = sdl::create_gpu_device(GPU_SHADERFORMAT_SPIRV, true, "vulkan"); self.gpu = sdl::create_gpu_device(GPU_SHADERFORMAT_SPIRV, true, "vulkan");
if (self.gpu == null) { if (self.gpu == null) {
unreachable("failed to create gpu device: %s", sdl::get_error()); io::eprintfn("failed to create gpu device: %s", sdl::get_error());
libc::exit(1);
} }
if (!sdl::claim_window_for_gpu_device(self.gpu, self.win)) { if (!sdl::claim_window_for_gpu_device(self.gpu, self.win)) {
unreachable("failed to claim window for use with gpu: %s", sdl::get_error()); io::eprintfn("failed to claim window for use with gpu: %s", sdl::get_error());
libc::exit(1);
} }
//
// initialize the quad buffer // initialize the quad buffer
// ==========================
self.quad_buffer.vert_buf = sdl::create_gpu_buffer(self.gpu, self.quad_buffer.vert_buf = sdl::create_gpu_buffer(self.gpu,
&&(GPUBufferCreateInfo){.usage = GPU_BUFFERUSAGE_VERTEX, .size = Quad.vertices.sizeof * MAX_QUAD_BATCH} &&(GPUBufferCreateInfo){.usage = GPU_BUFFERUSAGE_VERTEX, .size = Quad.vertices.sizeof}
); );
if (self.quad_buffer.vert_buf == null) { if (self.quad_buffer.vert_buf == null) {
unreachable("failed to initialize quad buffer (vertex): %s", sdl::get_error()); io::eprintfn("failed to initialize quad buffer (vertex): %s", sdl::get_error());
libc::exit(1);
} }
self.quad_buffer.idx_buf = sdl::create_gpu_buffer(self.gpu, self.quad_buffer.idx_buf = sdl::create_gpu_buffer(self.gpu,
&&(GPUBufferCreateInfo){.usage = GPU_BUFFERUSAGE_INDEX, .size = Quad.indices.sizeof * MAX_QUAD_BATCH} &&(GPUBufferCreateInfo){.usage = GPU_BUFFERUSAGE_INDEX, .size = Quad.indices.sizeof}
); );
if (self.quad_buffer.idx_buf == null) { if (self.quad_buffer.idx_buf == null) {
unreachable("failed to initialize quad buffer (index): %s", sdl::get_error()); io::eprintfn("failed to initialize quad buffer (index): %s", sdl::get_error());
libc::exit(1);
} }
self.quad_buffer.initialized = true; self.quad_buffer.initialized = true;
@ -190,7 +189,8 @@ fn void Renderer.load_spirv_shader_from_mem(&self, String name, char[] vert_code
s.vert = sdl::create_gpu_shader(self.gpu, &shader_info); s.vert = sdl::create_gpu_shader(self.gpu, &shader_info);
if (s.vert == null) { if (s.vert == null) {
unreachable("failed to create gpu vertex shader: %s", sdl::get_error()); io::eprintfn("failed to create gpu vertex shader: %s", sdl::get_error());
libc::exit(1);
} }
} }
@ -211,7 +211,8 @@ fn void Renderer.load_spirv_shader_from_mem(&self, String name, char[] vert_code
s.frag = sdl::create_gpu_shader(self.gpu, &shader_info); s.frag = sdl::create_gpu_shader(self.gpu, &shader_info);
if (s.frag == null) { if (s.frag == null) {
unreachable("failed to create gpu fragment shader: %s", sdl::get_error()); io::eprintfn("failed to create gpu fragment shader: %s", sdl::get_error());
libc::exit(1);
} }
} }
@ -229,16 +230,21 @@ fn void Renderer.load_spirv_shader_from_file(&self, String name, String vert_pat
char[] frag_code; char[] frag_code;
// create vertex shader // create vertex shader
if (vert_path != "") {
vert_code = mem::new_array(char, file::get_size(vert_path)!!+1); vert_code = mem::new_array(char, file::get_size(vert_path)!!+1);
file::load_buffer(vert_path, vert_code)!!; file::load_buffer(vert_path, vert_code)!!;
defer mem::free(vert_code); }
// create fragment shader // create fragment shader
if (frag_path != "") {
frag_code = mem::new_array(char, file::get_size(frag_path)!!+1); frag_code = mem::new_array(char, file::get_size(frag_path)!!+1);
file::load_buffer(frag_path, frag_code)!!; file::load_buffer(frag_path, frag_code)!!;
defer mem::free(frag_code); }
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);
if (vert_code.ptr) mem::free(vert_code);
if (frag_code.ptr) mem::free(frag_code);
} }
fn Shader* ShaderList.get_from_name(&self, String name) fn Shader* ShaderList.get_from_name(&self, String name)
@ -290,7 +296,8 @@ fn void Renderer.create_pipeline(&self, String shader_name, PipelineType type)
{ {
Shader *s = self.shaders.get_from_name(shader_name); Shader *s = self.shaders.get_from_name(shader_name);
if (s == null) { if (s == null) {
unreachable("error in creating pipeline: no shader named %s", shader_name); io::eprintfn("error in creating pipeline: no shader named %s", shader_name);
libc::exit(1);
} }
GPUGraphicsPipelineCreateInfo ci = { GPUGraphicsPipelineCreateInfo ci = {
@ -368,21 +375,21 @@ fn void Renderer.create_pipeline(&self, String shader_name, PipelineType type)
}; };
if (p.pipeline == null) { if (p.pipeline == null) {
unreachable("failed to create pipeline (shaders: %s, type: %s): %s", shader_name, type.nameof, sdl::get_error()); io::eprintfn("failed to create pipeline (shaders: %s, type: %s): %s", shader_name, type.nameof, sdl::get_error());
libc::exit(1);
} }
self.pipelines.push(p); self.pipelines.push(p);
} }
// NOTE: with TEXTUREUSAGE_SAMPLER the texture format cannot be intger _UINT so it has to be nermalized
enum TextureType : (GPUTextureFormat format) { enum TextureType : (GPUTextureFormat format) {
FULL_COLOR = GPU_TEXTUREFORMAT_R8G8B8A8_UNORM, FULL_COLOR = GPU_TEXTUREFORMAT_R8G8B8A8_UINT,
JUST_ALPHA = GPU_TEXTUREFORMAT_R8_UNORM JUST_ALPHA = GPU_TEXTUREFORMAT_R8_UINT
} }
// 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 s given an id and pushed into a texture list
fn void Renderer.new_texture(&self, String name, TextureType type, char[] pixels, uint width, uint height) fn void Renderer.new_texture(&self, String name, TextureType type, char[] pixels, ushort width, ushort height)
{ {
uint id = name.hash(); uint id = name.hash();
@ -395,13 +402,14 @@ fn void Renderer.new_texture(&self, String name, TextureType type, char[] pixels
.width = width, .width = width,
.height = height, .height = height,
.layer_count_or_depth = 1, .layer_count_or_depth = 1,
.num_levels = 1, // no mip maps so just one level .num_levels = 0, // no mip maps
// .sample_count not used since the texture is not a render target // .sample_count not used since the texture is not a render target
}; };
GPUTexture* texture = sdl::create_gpu_texture(self.gpu, &tci); GPUTexture* texture = sdl::create_gpu_texture(self.gpu, &tci);
if (texture == null) { if (texture == null) {
unreachable("failed to create texture (name: %s, type: %s): %s", name, type.nameof, sdl::get_error()); io::eprintfn("failed to create texture (name: %s, type: %s): %s", name, type.nameof, sdl::get_error());
libc::exit(1);
} }
// the sampler description, how the texture should be sampled // the sampler description, how the texture should be sampled
@ -417,7 +425,8 @@ fn void Renderer.new_texture(&self, String name, TextureType type, char[] pixels
GPUSampler* sampler = sdl::create_gpu_sampler(self.gpu, &sci); GPUSampler* sampler = sdl::create_gpu_sampler(self.gpu, &sci);
if (sampler == null) { if (sampler == null) {
unreachable("failed to create sampler (texture name: %s, type: %s): %s", name, type.nameof, sdl::get_error()); io::eprintfn("failed to create sampler (texture name: %s, type: %s): %s", name, type.nameof, sdl::get_error());
libc::exit(1);
} }
Texture t = { Texture t = {
@ -431,39 +440,45 @@ fn void Renderer.new_texture(&self, String name, TextureType type, char[] pixels
self.update_texture(name, pixels, width, height); self.update_texture(name, pixels, width, height);
} }
fn void Renderer.update_texture(&self, String name, char[] pixels, uint width, uint height, uint x = 0, uint y = 0) fn void Renderer.update_texture(&self, String name, char[] pixels, ushort width, ushort height, ushort x = 0, ushort y = 0)
{ {
Texture* t = self.textures.get_from_name(name); Texture* t = self.textures.get_from_name(name);
if (t == null || t.texture == null) { if (t == null || t.texture == null) {
unreachable("failed updating texture: no texture named %s", name); io::eprintf("failed updating texture: no texture named %s", name);
libc::exit(1);
} }
GPUTexture* texture = t.texture; GPUTexture* texture = t.texture;
// FIXME: do a better job at validating the copy // FIXME: do a better job at validating the copy
if (x > t.width || y > t.height) { if (x > t.width || y > t.height) {
unreachable("failed updating texture: attempting to copy outside of the texture region", name); io::eprintf("failed updating texture: attempting to copy outside of the texture region", name);
libc::exit(1);
} }
// upload image data // upload image data
GPUCommandBuffer* cmdbuf = sdl::acquire_gpu_command_buffer(self.gpu); GPUCommandBuffer* cmdbuf = sdl::acquire_gpu_command_buffer(self.gpu);
if (cmdbuf == null) { if (cmdbuf == null) {
unreachable("failed to upload texture data at acquiring command buffer: %s", sdl::get_error()); io::eprintfn("failed to upload texture data at acquiring command buffer: %s", sdl::get_error());
libc::exit(1);
} }
GPUCopyPass* copypass = sdl::begin_gpu_copy_pass(cmdbuf); GPUCopyPass* copypass = sdl::begin_gpu_copy_pass(cmdbuf);
if (copypass == null) { if (copypass == null) {
unreachable("failed to upload texture data at beginning copy pass: %s", sdl::get_error()); io::eprintfn("failed to upload texture data at beginning copy pass: %s", sdl::get_error());
libc::exit(1);
} }
GPUTransferBuffer* buf = sdl::create_gpu_transfer_buffer(self.gpu, GPUTransferBuffer* buf = sdl::create_gpu_transfer_buffer(self.gpu,
&&(GPUTransferBufferCreateInfo){.usage = GPU_TRANSFERBUFFERUSAGE_UPLOAD, .size = pixels.len} &&(GPUTransferBufferCreateInfo){.usage = GPU_TRANSFERBUFFERUSAGE_UPLOAD, .size = pixels.len}
); );
if (buf == null) { if (buf == null) {
unreachable("failed to upload texture data at creating the transfer buffer: %s", sdl::get_error()); io::eprintfn("failed to upload texture data at creating the transfer buffer: %s", sdl::get_error());
libc::exit(1);
} }
char* gpu_mem = (char*)sdl::map_gpu_transfer_buffer(self.gpu, buf, false); char* gpu_mem = (char*)sdl::map_gpu_transfer_buffer(self.gpu, buf, false);
if (gpu_mem == null) { if (gpu_mem == null) {
unreachable("failed to upload texture data at mapping the transfer buffer: %s", sdl::get_error()); io::eprintfn("failed to upload texture data at mapping the transfer buffer: %s", sdl::get_error());
libc::exit(1);
} }
// copy the data to the driver's memory // copy the data to the driver's memory
gpu_mem[:pixels.len] = pixels[..]; gpu_mem[:pixels.len] = pixels[..];
@ -478,115 +493,34 @@ fn void Renderer.update_texture(&self, String name, char[] pixels, uint width, u
sdl::end_gpu_copy_pass(copypass); sdl::end_gpu_copy_pass(copypass);
if (!sdl::submit_gpu_command_buffer(cmdbuf)) { if (!sdl::submit_gpu_command_buffer(cmdbuf)) {
unreachable("failed to upload texture data at command buffer submission: %s", sdl::get_error()); io::eprintfn("failed to upload texture data at command buffer submission: %s", sdl::get_error());
libc::exit(1);
} }
sdl::release_gpu_transfer_buffer(self.gpu, buf); sdl::release_gpu_transfer_buffer(self.gpu, buf);
} }
fn bool Renderer.push_sprite(&self, short x, short y, short w, short h, short u, short v) { // an highly inefficient way to draw a single quad, no batching, per-quad upload
if (self.quad_buffer.count >= MAX_QUAD_BATCH) { fn void Renderer.draw_rect(&self, short x, short y, short w, short h, uint color, String shader_name)
return false;
}
// upload the quad data to the gpu
if (self.quad_buffer.initialized == false) {
unreachable("quad buffer not initialized");
}
GPUTransferBuffer* buf = sdl::create_gpu_transfer_buffer(self.gpu,
&&(GPUTransferBufferCreateInfo){.usage = GPU_TRANSFERBUFFERUSAGE_UPLOAD, .size = Quad.sizeof}
);
if (buf == null) {
unreachable("failed to create gpu transfer buffer: %s", sdl::get_error());
}
Quad* quad = (Quad*)sdl::map_gpu_transfer_buffer(self.gpu, buf, false);
if (quad == null) {
unreachable("failed to map gpu transfer buffer: %s", sdl::get_error());
}
/* v1 v4
* +-------------+
* | _/|
* | _/ |
* | 1 _/ |
* | _/ |
* | _/ |
* | _/ 2 |
* |/ |
* +-------------+
* v2 v3
*/
quad.vertices.v1 = {.pos = {.x = x, .y = y}, .uv = {.u = u, .v = v}};
quad.vertices.v2 = {.pos = {.x = x, .y = y+h}, .uv = {.u = u, .v = v+h}};
quad.vertices.v3 = {.pos = {.x = x+w, .y = y+h}, .uv = {.u = u+w, .v = v+h}};
quad.vertices.v4 = {.pos = {.x = x+w, .y = y}, .uv = {.u = u+w, .v = v}};
// triangle 1 indices
quad.indices.i1 = 0; // v1
quad.indices.i2 = 1; // v2
quad.indices.i3 = 3; // v4
// triangle 2 indices
quad.indices.i4 = 1; // v2
quad.indices.i5 = 2; // v3
quad.indices.i6 = 3; // v4
sdl::unmap_gpu_transfer_buffer(self.gpu, buf);
GPUCommandBuffer* cmd = sdl::acquire_gpu_command_buffer(self.gpu);
if (cmd == null) {
unreachable("failed to upload quad at acquiring command buffer: %s", sdl::get_error());
}
GPUCopyPass* cpy = sdl::begin_gpu_copy_pass(cmd);
// upload vertices
QuadBuffer* qb = &self.quad_buffer;
sdl::upload_to_gpu_buffer(cpy,
&&(GPUTransferBufferLocation){.transfer_buffer = buf, .offset = Quad.vertices.offsetof},
&&(GPUBufferRegion){.buffer = qb.vert_buf, .offset = qb.count * Quad.vertices.sizeof, .size = Quad.vertices.sizeof},
false
);
// upload indices
sdl::upload_to_gpu_buffer(cpy,
&&(GPUTransferBufferLocation){.transfer_buffer = buf, .offset = Quad.indices.offsetof},
&&(GPUBufferRegion){.buffer = qb.idx_buf, .offset = qb.count * Quad.indices.sizeof, .size = Quad.indices.sizeof},
false
);
sdl::end_gpu_copy_pass(cpy);
if (!sdl::submit_gpu_command_buffer(cmd)) {
unreachable("failed to upload quads at submit command buffer: %s", sdl::get_error());
}
sdl::release_gpu_transfer_buffer(self.gpu, buf);
//sdl::wait_for_gpu_idle(self.gpu);
qb.count++;
return true;
}
// Push a quad into the quad buffer, return true on success and false on failure
fn bool Renderer.push_quad(&self, short x, short y, short w, short h, uint color)
{ {
if (self.quad_buffer.count >= MAX_QUAD_BATCH) {
return false;
}
// upload the quad data to the gpu // upload the quad data to the gpu
if (self.quad_buffer.initialized == false) { if (self.quad_buffer.initialized == false) {
unreachable("quad buffer not initialized"); io::eprintfn("quad buffer not initialized");
libc::exit(1);
} }
GPUTransferBuffer* buf = sdl::create_gpu_transfer_buffer(self.gpu, GPUTransferBuffer* buf = sdl::create_gpu_transfer_buffer(self.gpu,
&&(GPUTransferBufferCreateInfo){.usage = GPU_TRANSFERBUFFERUSAGE_UPLOAD, .size = Quad.sizeof} &&(GPUTransferBufferCreateInfo){.usage = GPU_TRANSFERBUFFERUSAGE_UPLOAD, .size = Quad.sizeof}
); );
if (buf == null) { if (buf == null) {
unreachable("failed to create gpu transfer buffer: %s", sdl::get_error()); io::eprintfn("failed to create gpu transfer buffer: %s", sdl::get_error());
libc::exit(1);
} }
Quad* quad = (Quad*)sdl::map_gpu_transfer_buffer(self.gpu, buf, false); Quad* quad = (Quad*)sdl::map_gpu_transfer_buffer(self.gpu, buf, false);
if (quad == null) { if (quad == null) {
unreachable("failed to map gpu transfer buffer: %s", sdl::get_error()); io::eprintfn("failed to map gpu transfer buffer: %s", sdl::get_error());
libc::exit(1);
} }
/* v1 v4 /* v1 v4
@ -605,11 +539,13 @@ fn bool Renderer.push_quad(&self, short x, short y, short w, short h, uint color
quad.vertices.v2 = {.pos = {.x = x, .y = y+h}, .col.u = color}; quad.vertices.v2 = {.pos = {.x = x, .y = y+h}, .col.u = color};
quad.vertices.v3 = {.pos = {.x = x+w, .y = y+h}, .col.u = color}; quad.vertices.v3 = {.pos = {.x = x+w, .y = y+h}, .col.u = color};
quad.vertices.v4 = {.pos = {.x = x+w, .y = y}, .col.u = color}; quad.vertices.v4 = {.pos = {.x = x+w, .y = y}, .col.u = color};
// triangle 1 indices
// triangle 1
quad.indices.i1 = 0; // v1 quad.indices.i1 = 0; // v1
quad.indices.i2 = 1; // v2 quad.indices.i2 = 1; // v2
quad.indices.i3 = 3; // v4 quad.indices.i3 = 3; // v4
// triangle 2 indices // triangle 2
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
@ -618,21 +554,21 @@ fn bool Renderer.push_quad(&self, short x, short y, short w, short h, uint color
GPUCommandBuffer* cmd = sdl::acquire_gpu_command_buffer(self.gpu); GPUCommandBuffer* cmd = sdl::acquire_gpu_command_buffer(self.gpu);
if (cmd == null) { if (cmd == null) {
unreachable("failed to upload quad at acquiring command buffer: %s", sdl::get_error()); io::eprintfn("failed to upload quad at acquiring command buffer: %s", sdl::get_error());
libc::exit(1);
} }
GPUCopyPass* cpy = sdl::begin_gpu_copy_pass(cmd); GPUCopyPass* cpy = sdl::begin_gpu_copy_pass(cmd);
// upload vertices // upload vertices
QuadBuffer* qb = &self.quad_buffer;
sdl::upload_to_gpu_buffer(cpy, sdl::upload_to_gpu_buffer(cpy,
&&(GPUTransferBufferLocation){.transfer_buffer = buf, .offset = Quad.vertices.offsetof}, &&(GPUTransferBufferLocation){.transfer_buffer = buf, .offset = Quad.vertices.offsetof},
&&(GPUBufferRegion){.buffer = qb.vert_buf, .offset = qb.count * Quad.vertices.sizeof, .size = Quad.vertices.sizeof}, &&(GPUBufferRegion){.buffer = self.quad_buffer.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 = buf, .offset = Quad.indices.offsetof}, &&(GPUTransferBufferLocation){.transfer_buffer = buf, .offset = Quad.indices.offsetof},
&&(GPUBufferRegion){.buffer = qb.idx_buf, .offset = qb.count * Quad.indices.sizeof, .size = Quad.indices.sizeof}, &&(GPUBufferRegion){.buffer = self.quad_buffer.idx_buf, .offset = 0, .size = Quad.indices.sizeof},
false false
); );
@ -641,59 +577,42 @@ fn bool Renderer.push_quad(&self, short x, short y, short w, short h, uint color
unreachable("failed to upload quads at submit command buffer: %s", sdl::get_error()); unreachable("failed to upload quads at submit command buffer: %s", sdl::get_error());
} }
sdl::release_gpu_transfer_buffer(self.gpu, buf); sdl::release_gpu_transfer_buffer(self.gpu, buf);
//sdl::wait_for_gpu_idle(self.gpu); sdl::wait_for_gpu_idle(self.gpu);
qb.count++; /*
// now finally draw the quad
return true; // if we are not in a render pass then we can't render shit
if (self.render_cmd == null) {
unreachable("start rendering first before trying to render a quad");
} }
// draw all quads in the quad buffer, since uniforms are per-drawcall it makes no sense // FIXME: this could be done at the start of rendering
// to draw them one a the time GPUTexture* t;
fn void Renderer.draw_quads(&self, GPURenderPass* pass) if (!sdl::wait_and_acquire_gpu_swapchain_texture(self.render_cmd, self.win, &t, null, null)) {
{ unreachable("failed to acquire swapchain texture: %s", sdl::get_error());
QuadBuffer* qb = &self.quad_buffer;
if (qb.off == qb.count) return;
sdl::bind_gpu_vertex_buffers(pass, 0, (GPUBufferBinding[]){{.buffer = qb.vert_buf, .offset = qb.off*Quad.vertices.sizeof}}, 1);
sdl::bind_gpu_index_buffer(pass, &&(GPUBufferBinding){.buffer = qb.idx_buf, .offset = qb.off*Quad.indices.sizeof}, GPU_INDEXELEMENTSIZE_16BIT);
// we need instancing to not do this
for (int i = 0; i < qb.count - qb.off; i++) {
sdl::draw_gpu_indexed_primitives(pass, 6, 1, i*6, i*4, 0);
} }
qb.off = qb.count; // TODO: begin render pass
Pipeline* p = self.pipelines.get_from_name(shader_name);
if (p == null) {
unreachable("no pipeline named: %s", shader_name);
} }
fn void Renderer.reset_quads(&self) // bind the data
{ sdl::bind_gpu_graphics_pipeline(self.render_pass, pipeline);
self.quad_buffer.count = 0; sdl::bind_gpu_vertex_buffer(self.render_pass, 0,
self.quad_buffer.off = 0; &&(GPUBufferBinding){.buffer = self.quad_buffer.vert_buf, .offset = 0}, 1
);
sdl::bind_gpu_index_buffer(self.render_pass, 0,
&&(GPUBufferBinding){.buffer = self.quad_buffer.idx_buf, .offset = 0}, 1
);
sdl::draw_gpu_indexed_primitives(self.render_pass, 6, 1, 0, 0, 0);
*/
} }
// TODO: fn Renderer.draw_quad, it has to use a vertex buffer and an index buffer
// TODO: fn Renderer.draw_sprite, same as draw_quad but also bind the texture // TODO: fn Renderer.draw_sprite, same as draw_quad but also bind the texture
// TODO: fn Renderer.begin_render // TODO: fn Renderer.begin_render
// TODO: fn Renderer.end_render // TODO: fn Renderer.end_render
/// === NOTES ===
/* 1. The uniform data is per-render pass. So you can do:
* - push uniform
* - draw 1
* - draw 2
* But not:
* - push uniform
* - draw
* - push new uniform
* - draw
* And not even:
* - 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.
*/

View File

@ -2,8 +2,6 @@ import sdlrenderer::ren;
import std::io; import std::io;
import std::thread; import std::thread;
import sdl3::sdl; import sdl3::sdl;
import std::compression::qoi;
import std::core::mem::allocator;
struct Viewsize @align(16) { struct Viewsize @align(16) {
int w, h; int w, h;
@ -15,31 +13,19 @@ fn int main()
ren::Renderer ren; ren::Renderer ren;
ren.init("test window"); ren.init("test window");
// TODO: these could be the same function
ren.load_spirv_shader_from_file("rect shader", "resources/shaders/compiled/rect.vert.spv", "resources/shaders/compiled/rect.frag.spv", 0, 1); ren.load_spirv_shader_from_file("rect shader", "resources/shaders/compiled/rect.vert.spv", "resources/shaders/compiled/rect.frag.spv", 0, 1);
ren.create_pipeline("rect shader", RECT); ren.create_pipeline("rect shader", RECT);
// load the tux qoi image for (int i = 0; i < 10; i++) {
QOIDesc img_desc;
char[] img_pixels = qoi::read(allocator::temp(), "resources/tux.qoi", &img_desc)!!; ren.draw_rect(100,100,100,100,0xff00ff00,"");
// and put it in a texture
ren.new_texture("tux", FULL_COLOR, img_pixels, img_desc.width, img_desc.height);
// create a new pipeline to use the texture
ren.load_spirv_shader_from_file("sprite shader", "resources/shaders/compiled/sprite.vert.spv", "resources/shaders/compiled/sprite.frag.spv", 1, 1);
ren.create_pipeline("sprite shader", SPRITE);
for (int i = 0; i < 20; i++) {
GPUCommandBuffer* cmdbuf = sdl::acquire_gpu_command_buffer(ren.gpu); GPUCommandBuffer* cmdbuf = sdl::acquire_gpu_command_buffer(ren.gpu);
GPUTexture* swapchain_texture; GPUTexture* swapchain_texture;
sdl::wait_and_acquire_gpu_swapchain_texture(cmdbuf, ren.win, &swapchain_texture, null, null); sdl::wait_and_acquire_gpu_swapchain_texture(cmdbuf, ren.win, &swapchain_texture, null, null);
GPURenderPass* pass;
GPUGraphicsPipeline* p;
Viewsize v = {.w = 640, .h = 480};
// Colored Rectangles Render Pass
// FIXME: if doing damage tracking DO NOT clear the screen // FIXME: if doing damage tracking DO NOT clear the screen
pass = sdl::begin_gpu_render_pass(cmdbuf, GPURenderPass* pass = sdl::begin_gpu_render_pass(cmdbuf,
&&(GPUColorTargetInfo){ &&(GPUColorTargetInfo){
.texture = swapchain_texture, .texture = swapchain_texture,
.mip_level = 0, .mip_level = 0,
@ -61,81 +47,24 @@ fn int main()
unreachable("render pass creation went wrong: %s", sdl::get_error()); unreachable("render pass creation went wrong: %s", sdl::get_error());
} }
// rect 1 GPUGraphicsPipeline* p = ren.pipelines.get_from_name("rect shader").pipeline;
ren.push_quad(100,100,100,100,0xff00ff00);
// rect 2
ren.push_quad(0,0,20,20,0xff0000ff);
// rect 3
ren.push_quad(200,300,50,50,0xffff0000);
p = ren.pipelines.get_from_name("rect shader").pipeline;
if (p == null) { if (p == null) {
unreachable("no pipeline"); unreachable("no pipeline");
} }
sdl::bind_gpu_graphics_pipeline(pass, p); sdl::bind_gpu_graphics_pipeline(pass, p);
sdl::bind_gpu_vertex_buffers(pass, 0, (GPUBufferBinding[]){{.buffer = ren.quad_buffer.vert_buf, .offset = 0}}, 1);
v.ox = 10*i; sdl::bind_gpu_index_buffer(pass, &&(GPUBufferBinding){.buffer = ren.quad_buffer.idx_buf, .offset = 0}, GPU_INDEXELEMENTSIZE_16BIT);
v.oy = 10*i; Viewsize v = {.w = 640, .h = 480};
v.ox = 50*i;
v.oy = 50*i;
sdl::push_gpu_vertex_uniform_data(cmdbuf, 1, &v, Viewsize.sizeof); sdl::push_gpu_vertex_uniform_data(cmdbuf, 1, &v, Viewsize.sizeof);
ren.draw_quads(pass); sdl::draw_gpu_indexed_primitives(pass, 6, 1, 0, 0, 0);
sdl::end_gpu_render_pass(pass); sdl::end_gpu_render_pass(pass);
// End Rectangle Render Pass
// Textured Rectangles Render Pass
pass = sdl::begin_gpu_render_pass(cmdbuf,
&&(GPUColorTargetInfo){
.texture = swapchain_texture,
.mip_level = 0,
.layer_or_depth_plane = 0,
.clear_color = {.r = 0.0, .g = 0.0, .b = 0.0, .a = 1.0},
.load_op = GPU_LOADOP_DONT_CARE, // clear the screen at the start of the render pass
.store_op = GPU_STOREOP_STORE,
.resolve_texture = null,
.resolve_mip_level = 0,
.resolve_layer = 0,
.cycle = false,
.cycle_resolve_texture = false
},
1,
null // huh
);
if (pass == null) {
unreachable("render pass creation went wrong: %s", sdl::get_error());
}
p = ren.pipelines.get_from_name("sprite shader").pipeline;
if (p == null) {
unreachable("no pipeline");
}
sdl::bind_gpu_graphics_pipeline(pass, p);
// in this case it is not an offset but the texture size in pixels
v.ox = 54;
v.oy = 64;
sdl::push_gpu_vertex_uniform_data(cmdbuf, 1, &v, Viewsize.sizeof);
// bind the pipeline's sampler
ren::Texture* tx = ren.textures.get_from_name("tux");
sdl::bind_gpu_fragment_samplers(pass, 0,
(GPUTextureSamplerBinding[]){{.texture = tx.texture, .sampler = tx.sampler}}, 1
);
// tux
ren.push_sprite(300, 0, 54, 64, 0, 0);
ren.draw_quads(pass);
sdl::end_gpu_render_pass(pass);
// End Textured Rectangle Render Pass
sdl::submit_gpu_command_buffer(cmdbuf); sdl::submit_gpu_command_buffer(cmdbuf);
ren.reset_quads();
thread::sleep_ms(250); thread::sleep_ms(250);
} }