Compare commits
8 Commits
24bc2c67bc
...
bd8c73ecd5
Author | SHA1 | Date | |
---|---|---|---|
bd8c73ecd5 | |||
6e65700f38 | |||
e3d87525d4 | |||
3002123ef7 | |||
ac3fcae649 | |||
c9b74aebc7 | |||
f344c989db | |||
6c5acd6f23 |
4
.gitmodules
vendored
4
.gitmodules
vendored
@ -14,3 +14,7 @@
|
||||
path = lib/schrift.c3l/thirdparty/libschrift
|
||||
url = https://github.com/tomolt/libschrift
|
||||
ignore = dirty
|
||||
[submodule "lib/sdl3.c3l"]
|
||||
path = lib/sdl3.c3l
|
||||
url = https://git.alemauri.eu/alema/sdl3.c3l
|
||||
ignore = dirty
|
||||
|
2
Makefile
2
Makefile
@ -1,3 +1,3 @@
|
||||
test_renderer: test_renderer.c3 src/renderer.c3 resources/shaders/source/*
|
||||
scripts/compile_shaders.sh
|
||||
c3c compile-run -g -O0 test_renderer.c3 src/renderer.c3 --libdir ../sdl3.c3l --lib sdl3
|
||||
c3c compile -g -O0 test_renderer.c3 src/renderer.c3 --libdir ../sdl3.c3l --lib sdl3
|
||||
|
12
TODO
12
TODO
@ -60,8 +60,8 @@ to maintain focus until mouse release (fix scroll bars)
|
||||
## Commands
|
||||
|
||||
[x] rect commads should have:
|
||||
- border width
|
||||
- border radius
|
||||
- border width
|
||||
- border radius
|
||||
[x] add a command to update an atlas
|
||||
[ ] New window command, useful for popups
|
||||
|
||||
@ -107,3 +107,11 @@ to maintain focus until mouse release (fix scroll bars)
|
||||
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.
|
||||
|
||||
## 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
lib/sdl3.c3l
Submodule
1
lib/sdl3.c3l
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 076355e2d126e7546e53663b97e8dec22667d34d
|
@ -1,8 +1,10 @@
|
||||
module fifo::faults;
|
||||
faultdef FULL, EMPTY;
|
||||
|
||||
module fifo{Type};
|
||||
|
||||
import std::core::mem;
|
||||
|
||||
faultdef FULL, EMPTY;
|
||||
import std::sort;
|
||||
|
||||
// TODO: specify the allocator
|
||||
|
||||
@ -27,7 +29,7 @@ fn void Fifo.free(&fifo)
|
||||
fn void? Fifo.enqueue(&fifo, Type *elem)
|
||||
{
|
||||
if (fifo.count >= fifo.arr.len) {
|
||||
return FULL?;
|
||||
return fifo::faults::FULL?;
|
||||
}
|
||||
usz in = (fifo.out + fifo.count) % fifo.arr.len;
|
||||
fifo.arr[in] = *elem;
|
||||
@ -37,10 +39,53 @@ fn void? Fifo.enqueue(&fifo, Type *elem)
|
||||
fn Type*? Fifo.dequeue(&fifo)
|
||||
{
|
||||
if (fifo.count == 0) {
|
||||
return EMPTY?;
|
||||
return fifo::faults::EMPTY?;
|
||||
}
|
||||
Type *ret = &fifo.arr[fifo.out];
|
||||
fifo.count--;
|
||||
fifo.out = (fifo.out + 1) % fifo.arr.len;
|
||||
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)!;
|
||||
}
|
||||
}
|
@ -40,7 +40,7 @@ fn ElemEvents? Ctx.button(&ctx, String label, Rect size, bool state = false)
|
||||
}
|
||||
|
||||
// Draw the button
|
||||
ctx.push_rect(elem.bounds, col, do_border: true, do_radius: true)!;
|
||||
ctx.push_rect(elem.bounds, col, parent.div.z_index, do_border: true, do_radius: true)!;
|
||||
|
||||
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);
|
||||
text_size.x += off.x;
|
||||
text_size.y += off.y;
|
||||
ctx.push_rect(elem.bounds, col, do_border: true, do_radius: true)!;
|
||||
ctx.push_string(text_size, label)!;
|
||||
ctx.push_rect(elem.bounds, col, parent.div.z_index, do_border: true, do_radius: true)!;
|
||||
ctx.push_string(text_size, label, parent.div.z_index)!;
|
||||
|
||||
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;
|
||||
if (state && on_icon != "") {
|
||||
ctx.push_sprite(elem.bounds, on_sprite.uv(), tex_id, type: on_sprite.type)!;
|
||||
ctx.push_sprite(elem.bounds, on_sprite.uv(), tex_id, parent.div.z_index, type: on_sprite.type)!;
|
||||
} else {
|
||||
ctx.push_sprite(elem.bounds, def_sprite.uv(), tex_id, type: def_sprite.type)!;
|
||||
ctx.push_sprite(elem.bounds, def_sprite.uv(), tex_id, parent.div.z_index, type: def_sprite.type)!;
|
||||
}
|
||||
|
||||
// Draw the button
|
||||
ctx.push_rect(elem.bounds, col, do_border: true, do_radius: true)!;
|
||||
ctx.push_rect(elem.bounds, col, parent.div.z_index, do_border: true, do_radius: true)!;
|
||||
|
||||
return elem.events;
|
||||
}
|
||||
@ -159,7 +159,7 @@ fn void? Ctx.checkbox(&ctx, String label, String description, Point off, bool* s
|
||||
Color col;
|
||||
if (tick_sprite != {}) {
|
||||
col = ctx.style.bgcolor;
|
||||
ctx.push_rect(elem.bounds, col, do_border: true, do_radius: true)!;
|
||||
ctx.push_rect(elem.bounds, col, parent.div.z_index, do_border: true, do_radius: true)!;
|
||||
if (*state) {
|
||||
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();
|
||||
}
|
||||
// Draw the button
|
||||
ctx.push_rect(elem.bounds, col, do_border: true, do_radius: true)!;
|
||||
ctx.push_rect(elem.bounds, col, parent.div.z_index, 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
|
||||
// FIXME: THIS IS SHIT
|
||||
ctx.push_rect(elem.bounds, ctx.style.bgcolor, do_border: true, do_radius: true)!;
|
||||
ctx.push_rect(elem.bounds, ctx.style.bgcolor, parent.div.z_index, do_border: true, do_radius: true)!;
|
||||
Rect t = elem.bounds.add({*state ? (DEFAULT_SWITCH_SIZE+3) : +3, +3, -DEFAULT_SWITCH_SIZE-6, -6});
|
||||
ctx.push_rect(t, col, do_border: false, do_radius: true)!;
|
||||
ctx.push_rect(t, col, parent.div.z_index, do_border: false, do_radius: true)!;
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
module ugui;
|
||||
|
||||
import std::ascii;
|
||||
import std::io;
|
||||
|
||||
// command type
|
||||
enum CmdType {
|
||||
@ -37,8 +38,9 @@ struct CmdScissor {
|
||||
}
|
||||
|
||||
// command structure
|
||||
struct Cmd {
|
||||
struct Cmd (Printable) {
|
||||
CmdType type;
|
||||
int z_index;
|
||||
union {
|
||||
CmdRect rect;
|
||||
CmdUpdateAtlas update_atlas;
|
||||
@ -47,6 +49,18 @@ struct Cmd {
|
||||
}
|
||||
}
|
||||
|
||||
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})
|
||||
{
|
||||
bool no_area = rect.w <= 0 || rect.h <= 0;
|
||||
@ -54,8 +68,9 @@ 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
|
||||
macro Ctx.push_cmd(&ctx, Cmd *cmd)
|
||||
macro Ctx.push_cmd(&ctx, Cmd *cmd, int z_index)
|
||||
{
|
||||
cmd.z_index = z_index;
|
||||
Rect rect;
|
||||
switch (cmd.type) {
|
||||
case CMD_RECT: rect = cmd.rect.rect;
|
||||
@ -66,9 +81,18 @@ macro Ctx.push_cmd(&ctx, Cmd *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?
|
||||
// "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, bool do_border = false, bool do_padding = false, bool do_radius = false)
|
||||
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)
|
||||
{
|
||||
Rect border = ctx.style.border;
|
||||
Rect padding = ctx.style.padding;
|
||||
@ -82,7 +106,7 @@ fn void? Ctx.push_rect(&ctx, Rect rect, Color color, bool do_border = false, boo
|
||||
.rect.color = border_color,
|
||||
.rect.radius = do_radius ? radius : 0,
|
||||
};
|
||||
ctx.push_cmd(&cmd)!;
|
||||
ctx.push_cmd(&cmd, z_index)!;
|
||||
}
|
||||
|
||||
Cmd cmd = {
|
||||
@ -97,11 +121,11 @@ fn void? Ctx.push_rect(&ctx, Rect rect, Color color, bool do_border = false, boo
|
||||
.rect.radius = do_radius ? radius : 0,
|
||||
};
|
||||
if (cull_rect(cmd.rect.rect, ctx.div_scissor)) return;
|
||||
ctx.push_cmd(&cmd)!;
|
||||
ctx.push_cmd(&cmd, z_index)!;
|
||||
}
|
||||
|
||||
// TODO: add texture id
|
||||
fn void? Ctx.push_sprite(&ctx, Rect bounds, Rect texture, Id texture_id, Color hue = 0xffffffffu.to_rgba(), SpriteType type = SPRITE_NORMAL)
|
||||
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)
|
||||
{
|
||||
Cmd cmd = {
|
||||
.type = CMD_SPRITE,
|
||||
@ -111,16 +135,16 @@ fn void? Ctx.push_sprite(&ctx, Rect bounds, Rect texture, Id texture_id, Color h
|
||||
.sprite.texture_id = texture_id,
|
||||
.sprite.hue = hue,
|
||||
};
|
||||
ctx.push_cmd(&cmd)!;
|
||||
ctx.push_cmd(&cmd, z_index)!;
|
||||
}
|
||||
|
||||
fn void? Ctx.push_string(&ctx, Rect bounds, String text, Color hue = 0xffffffffu.to_rgba())
|
||||
fn void? Ctx.push_string(&ctx, Rect bounds, String text, int z_index, Color hue = 0xffffffffu.to_rgba())
|
||||
{
|
||||
if (text.len == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.push_scissor(bounds)!;
|
||||
ctx.push_scissor(bounds, z_index)!;
|
||||
|
||||
short baseline = (short)ctx.font.ascender;
|
||||
short line_height = (short)ctx.font.ascender - (short)ctx.font.descender;
|
||||
@ -152,7 +176,7 @@ fn void? Ctx.push_string(&ctx, Rect bounds, String text, Color hue = 0xffffffffu
|
||||
.h = gp.h,
|
||||
};
|
||||
// push the sprite only if it collides with the bounds
|
||||
if (!cull_rect(gb, bounds)) ctx.push_sprite(gb, gt, texture_id, hue)!;
|
||||
if (!cull_rect(gb, bounds)) ctx.push_sprite(gb, gt, texture_id, z_index, hue)!;
|
||||
line_len += gp.adv;
|
||||
} else if (cp == '\n'){
|
||||
orig.y += line_height + line_gap;
|
||||
@ -163,7 +187,7 @@ fn void? Ctx.push_string(&ctx, Rect bounds, String text, Color hue = 0xffffffffu
|
||||
}
|
||||
|
||||
// FIXME: we never get here if an error was thrown before
|
||||
ctx.push_scissor({})!;
|
||||
ctx.push_scissor({}, z_index)!;
|
||||
}
|
||||
|
||||
fn void? Ctx.push_update_atlas(&ctx, Atlas* atlas)
|
||||
@ -178,14 +202,7 @@ fn void? Ctx.push_update_atlas(&ctx, Atlas* atlas)
|
||||
.bpp = (ushort)atlas.type.bpp(),
|
||||
},
|
||||
};
|
||||
ctx.push_cmd(&up)!;
|
||||
// update the atlases before everything else
|
||||
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)!;
|
||||
}
|
||||
|
@ -244,6 +244,7 @@ fn void? Ctx.frame_begin(&ctx)
|
||||
},
|
||||
.div = {
|
||||
.layout = LAYOUT_ROW,
|
||||
.z_index = 0,
|
||||
.children_bounds = {
|
||||
.w = ctx.width,
|
||||
.h = ctx.height,
|
||||
@ -263,6 +264,8 @@ fn void? Ctx.frame_begin(&ctx)
|
||||
// TODO: add a background color taken from a theme or config
|
||||
}
|
||||
|
||||
const int DEBUG = 1;
|
||||
|
||||
fn void? Ctx.frame_end(&ctx)
|
||||
{
|
||||
Elem* root = ctx.get_elem_by_tree_idx(0)!;
|
||||
@ -285,10 +288,12 @@ fn void? Ctx.frame_end(&ctx)
|
||||
ctx.sprite_atlas.should_update = false;
|
||||
}
|
||||
|
||||
$if 1:
|
||||
// debug
|
||||
$if DEBUG == 1:
|
||||
// draw mouse position
|
||||
Cmd cmd = {
|
||||
.type = CMD_RECT,
|
||||
.z_index = int.max-1, // hopefully over everything else
|
||||
.rect.rect = {
|
||||
.x = ctx.input.mouse.pos.x - 2,
|
||||
.y = ctx.input.mouse.pos.y - 2,
|
||||
@ -298,6 +303,17 @@ $if 1:
|
||||
.rect.color = 0xff00ffffu.to_rgba()
|
||||
};
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -18,6 +18,7 @@ struct ElemDiv {
|
||||
bool on;
|
||||
float value;
|
||||
}
|
||||
int z_index;
|
||||
Rect children_bounds; // current frame children bounds
|
||||
Rect pcb; // previous frame children bounds
|
||||
Point origin_r, origin_c;
|
||||
@ -46,6 +47,7 @@ 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_y.enabled = scroll_y;
|
||||
elem.div.z_index = parent.div.z_index + 1;
|
||||
|
||||
// 2. layout the element
|
||||
Rect wanted_size = {
|
||||
@ -59,7 +61,7 @@ fn void? Ctx.div_begin(&ctx, String label, Rect size, bool scroll_x = false, boo
|
||||
|
||||
// update the ctx scissor
|
||||
ctx.div_scissor = elem.bounds;
|
||||
ctx.push_scissor(elem.bounds)!;
|
||||
ctx.push_scissor(elem.bounds, elem.div.z_index)!;
|
||||
|
||||
// 4. Fill the div fields
|
||||
elem.div.origin_c = {
|
||||
@ -71,7 +73,7 @@ fn void? Ctx.div_begin(&ctx, String label, Rect size, bool scroll_x = false, boo
|
||||
|
||||
// Add the background to the draw stack
|
||||
bool do_border = parent.div.layout == LAYOUT_FLOATING;
|
||||
ctx.push_rect(elem.bounds, ctx.style.bgcolor, do_border: do_border)!;
|
||||
ctx.push_rect(elem.bounds, ctx.style.bgcolor, elem.div.z_index, do_border: do_border)!;
|
||||
|
||||
elem.events = ctx.get_elem_events(elem);
|
||||
|
||||
|
@ -112,8 +112,8 @@ fn void Ctx.input_mouse_button(&ctx, MouseButtons buttons)
|
||||
// Mouse was moved, report absolute position
|
||||
fn void Ctx.input_mouse_abs(&ctx, short x, short y)
|
||||
{
|
||||
ctx.input.mouse.pos.x = math::clamp(x, 0u16, ctx.width);
|
||||
ctx.input.mouse.pos.y = math::clamp(y, 0u16, ctx.height);
|
||||
ctx.input.mouse.pos.x = math::clamp(x, (short)0, ctx.width);
|
||||
ctx.input.mouse.pos.y = math::clamp(y, (short)0, ctx.height);
|
||||
|
||||
short dx, dy;
|
||||
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;
|
||||
my = ctx.input.mouse.pos.y + dy;
|
||||
|
||||
ctx.input.mouse.pos.x = math::clamp(mx, 0u16, ctx.width);
|
||||
ctx.input.mouse.pos.y = math::clamp(my, 0u16, ctx.height);
|
||||
ctx.input.mouse.pos.x = math::clamp(mx, (short)0, ctx.width);
|
||||
ctx.input.mouse.pos.y = math::clamp(my, (short)0, ctx.height);
|
||||
|
||||
ctx.input.events.mouse_move = dx != 0 || dy != 0;
|
||||
}
|
||||
|
@ -61,8 +61,8 @@ fn ElemEvents? Ctx.slider_hor(&ctx,
|
||||
}
|
||||
|
||||
// Draw the slider background and handle
|
||||
ctx.push_rect(elem.bounds, bgcolor)!;
|
||||
ctx.push_rect(elem.slider.handle, handlecolor)!;
|
||||
ctx.push_rect(elem.bounds, bgcolor, parent.div.z_index)!;
|
||||
ctx.push_rect(elem.slider.handle, handlecolor, parent.div.z_index)!;
|
||||
|
||||
return elem.events;
|
||||
}
|
||||
@ -124,8 +124,8 @@ fn ElemEvents? Ctx.slider_ver(&ctx,
|
||||
}
|
||||
|
||||
// Draw the slider background and handle
|
||||
ctx.push_rect(elem.bounds, bgcolor)!;
|
||||
ctx.push_rect(elem.slider.handle, handlecolor)!;
|
||||
ctx.push_rect(elem.bounds, bgcolor, parent.div.z_index)!;
|
||||
ctx.push_rect(elem.slider.handle, handlecolor, parent.div.z_index)!;
|
||||
|
||||
return elem.events;
|
||||
}
|
||||
|
@ -164,12 +164,13 @@ fn void? Ctx.draw_sprite(&ctx, String label, String name, Point off = {0,0})
|
||||
|
||||
Id tex_id = ctx.sprite_atlas.id;
|
||||
|
||||
return ctx.push_sprite(elem.bounds, uv, tex_id)!;
|
||||
return ctx.push_sprite(elem.bounds, uv, tex_id, parent.div.z_index)!;
|
||||
}
|
||||
|
||||
fn void? Ctx.draw_sprite_raw(&ctx, String name, Rect bounds)
|
||||
{
|
||||
Elem *parent = ctx.get_parent()!;
|
||||
Sprite* sprite = ctx.sprite_atlas.get(name)!;
|
||||
Id tex_id = ctx.sprite_atlas.id;
|
||||
return ctx.push_sprite(bounds, sprite.uv(), tex_id, type: sprite.type)!;
|
||||
return ctx.push_sprite(bounds, sprite.uv(), tex_id, parent.div.z_index, type: sprite.type)!;
|
||||
}
|
||||
|
@ -26,5 +26,5 @@ fn void? Ctx.text_unbounded(&ctx, String label, String text)
|
||||
elem.bounds = ctx.position_element(parent, text_size, true);
|
||||
if (elem.bounds.is_null()) { return; }
|
||||
|
||||
ctx.push_string(elem.bounds, text)!;
|
||||
ctx.push_string(elem.bounds, text, parent.div.z_index)!;
|
||||
}
|
||||
|
@ -1,3 +1,6 @@
|
||||
module vtree::faults;
|
||||
faultdef CANNOT_SHRINK, INVALID_REFERENCE, TREE_FULL, REFERENCE_NOT_PRESENT, INVALID_ARGUMENT;
|
||||
|
||||
module vtree{ElemType};
|
||||
|
||||
import std::core::mem;
|
||||
@ -9,7 +12,6 @@ struct VTree {
|
||||
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_present(&tree, isz ref) { return tree.refs[ref] >= 0; }
|
||||
@ -88,14 +90,14 @@ fn void? VTree.resize(&tree, usz newsize)
|
||||
{
|
||||
// return error when shrinking with too many elements
|
||||
if (newsize < tree.elements) {
|
||||
return CANNOT_SHRINK?;
|
||||
return vtree::faults::CANNOT_SHRINK?;
|
||||
}
|
||||
|
||||
// pack the vector when shrinking to avoid data loss
|
||||
if ((int)newsize < tree.size()) {
|
||||
// FIXME: packing destroys all references to elements of vec
|
||||
// so shrinking may cause dangling pointers
|
||||
return CANNOT_SHRINK?;
|
||||
return vtree::faults::CANNOT_SHRINK?;
|
||||
}
|
||||
|
||||
usz old_size = tree.size();
|
||||
@ -120,18 +122,18 @@ fn isz? VTree.add(&tree, ElemType elem, isz parent)
|
||||
{
|
||||
// invalid parent
|
||||
if (!tree.ref_is_valid(parent)) {
|
||||
return INVALID_REFERENCE?;
|
||||
return vtree::faults::INVALID_REFERENCE?;
|
||||
}
|
||||
|
||||
// no space left
|
||||
if (tree.elements >= tree.size()) {
|
||||
return TREE_FULL?;
|
||||
return vtree::faults::TREE_FULL?;
|
||||
}
|
||||
|
||||
// check if the parent exists
|
||||
// if there are no elements in the tree the first add will set the root
|
||||
if (!tree.ref_is_present(parent) && tree.elements != 0) {
|
||||
return REFERENCE_NOT_PRESENT?;
|
||||
return vtree::faults::REFERENCE_NOT_PRESENT?;
|
||||
}
|
||||
|
||||
// get the first free spot
|
||||
@ -143,7 +145,7 @@ fn isz? VTree.add(&tree, ElemType elem, isz parent)
|
||||
}
|
||||
}
|
||||
if (free_spot < 0) {
|
||||
return TREE_FULL?;
|
||||
return vtree::faults::TREE_FULL?;
|
||||
}
|
||||
|
||||
// finally add the element
|
||||
@ -159,7 +161,7 @@ fn isz? VTree.add(&tree, ElemType elem, isz parent)
|
||||
fn usz? VTree.prune(&tree, isz ref)
|
||||
{
|
||||
if (!tree.ref_is_valid(ref)) {
|
||||
return INVALID_REFERENCE?;
|
||||
return vtree::faults::INVALID_REFERENCE?;
|
||||
}
|
||||
|
||||
if (!tree.ref_is_present(ref)) {
|
||||
@ -193,7 +195,7 @@ fn usz VTree.nuke(&tree)
|
||||
fn usz? VTree.subtree_size(&tree, isz ref)
|
||||
{
|
||||
if (!tree.ref_is_valid(ref)) {
|
||||
return INVALID_REFERENCE?;
|
||||
return vtree::faults::INVALID_REFERENCE?;
|
||||
}
|
||||
|
||||
if (!tree.ref_is_present(ref)) {
|
||||
@ -215,17 +217,17 @@ fn usz? VTree.subtree_size(&tree, isz ref)
|
||||
fn isz? VTree.children_it(&tree, isz parent, isz *cursor)
|
||||
{
|
||||
if (cursor == null) {
|
||||
return INVALID_ARGUMENT?;
|
||||
return vtree::faults::INVALID_ARGUMENT?;
|
||||
}
|
||||
|
||||
// if the cursor is out of bounds then we are done for sure
|
||||
if (!tree.ref_is_valid(*cursor)) {
|
||||
return INVALID_REFERENCE?;
|
||||
return vtree::faults::INVALID_REFERENCE?;
|
||||
}
|
||||
|
||||
// same for the parent, if it's invalid it can't have children
|
||||
if (!tree.ref_is_valid(parent) || !tree.ref_is_present(parent)) {
|
||||
return INVALID_REFERENCE?;
|
||||
return vtree::faults::INVALID_REFERENCE?;
|
||||
}
|
||||
|
||||
// find the first child, update the cursor and return the ref
|
||||
@ -253,7 +255,7 @@ fn isz? VTree.children_it(&tree, isz parent, isz *cursor)
|
||||
fn isz? VTree.level_order_it(&tree, isz ref, isz *cursor)
|
||||
{
|
||||
if (cursor == null) {
|
||||
return INVALID_ARGUMENT?;
|
||||
return vtree::faults::INVALID_ARGUMENT?;
|
||||
}
|
||||
|
||||
isz[] queue = tree.ordered_refs;
|
||||
@ -300,11 +302,11 @@ fn isz? VTree.level_order_it(&tree, isz ref, isz *cursor)
|
||||
fn isz? VTree.parentof(&tree, isz ref)
|
||||
{
|
||||
if (!tree.ref_is_valid(ref)) {
|
||||
return INVALID_REFERENCE?;
|
||||
return vtree::faults::INVALID_REFERENCE?;
|
||||
}
|
||||
|
||||
if (!tree.ref_is_present(ref)) {
|
||||
return REFERENCE_NOT_PRESENT?;
|
||||
return vtree::faults::REFERENCE_NOT_PRESENT?;
|
||||
}
|
||||
|
||||
return tree.refs[ref];
|
||||
@ -313,11 +315,11 @@ fn isz? VTree.parentof(&tree, isz ref)
|
||||
fn ElemType? VTree.get(&tree, isz ref)
|
||||
{
|
||||
if (!tree.ref_is_valid(ref)) {
|
||||
return INVALID_REFERENCE?;
|
||||
return vtree::faults::INVALID_REFERENCE?;
|
||||
}
|
||||
|
||||
if (!tree.ref_is_present(ref)) {
|
||||
return REFERENCE_NOT_PRESENT?;
|
||||
return vtree::faults::REFERENCE_NOT_PRESENT?;
|
||||
}
|
||||
|
||||
return tree.vector[ref];
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"langrev": "1",
|
||||
"warnings": ["no-unused"],
|
||||
"dependency-search-paths": ["lib", "../../Programs/Source/c3-vendor/libraries", "../sdl3.c3l"],
|
||||
"dependency-search-paths": ["lib", "../../Programs/Source/c3-vendor/libraries"],
|
||||
"dependencies": ["raylib55", "sdl3", "ugui"],
|
||||
"features": [],
|
||||
"authors": ["Alessandro Mauri <ale@shitposting.expert>"],
|
||||
|
@ -1,18 +0,0 @@
|
||||
#version 330 core
|
||||
|
||||
// viewsize.x = viewport width in pixels; viewsize.y = viewport height in pixels
|
||||
uniform ivec2 viewsize;
|
||||
uniform ivec2 texturesize;
|
||||
|
||||
// texture uv coordinate in texture space
|
||||
in vec2 uv;
|
||||
uniform sampler2DRect ts;
|
||||
|
||||
const vec3 textcolor = vec3(1.0, 1.0, 1.0);
|
||||
|
||||
|
||||
void main()
|
||||
{
|
||||
//gl_FragColor = vec4(1.0f,0.0f,0.0f,1.0f);
|
||||
gl_FragColor = vec4(textcolor, texture(ts, uv));
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
#version 330 core
|
||||
|
||||
// viewsize.x = viewport width in pixels; viewsize.y = viewport height in pixels
|
||||
uniform ivec2 viewsize;
|
||||
uniform ivec2 texturesize;
|
||||
|
||||
// both position and and uv are in pixels, they where converted to floats when
|
||||
// passed to the shader
|
||||
layout(location = 0) in vec2 position;
|
||||
layout(location = 1) in vec2 txcoord;
|
||||
|
||||
out vec2 uv;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec2 v = vec2(float(viewsize.x), float(viewsize.y));
|
||||
|
||||
// vec2 p = vec2(position.x*2.0f/v.x - 1.0f, position.y*2.0f/v.y - 1.0f);
|
||||
vec2 p = vec2(position.x*2.0/v.x - 1.0, 1.0 - position.y*2.0/v.y);
|
||||
vec4 pos = vec4(p.x, p.y, 0.0f, 1.0f);
|
||||
|
||||
gl_Position = pos;
|
||||
// since the texture is a GL_TEXTURE_RECTANGLE the coordintes do not need to
|
||||
// be normalized
|
||||
uv = vec2(txcoord.x, txcoord.y);
|
||||
}
|
14
resources/shaders/source/sprite.frag.glsl
Normal file
14
resources/shaders/source/sprite.frag.glsl
Normal file
@ -0,0 +1,14 @@
|
||||
#version 450
|
||||
|
||||
layout(location = 0) in vec2 uv;
|
||||
layout(location = 0) out vec4 fragColor;
|
||||
|
||||
layout(set = 2, binding = 0) uniform sampler2D tx;
|
||||
|
||||
void main()
|
||||
{
|
||||
ivec2 ts = textureSize(tx, 0);
|
||||
vec2 fts = vec2(float(ts.x), float(ts.y));
|
||||
vec2 real_uv = uv / fts;
|
||||
fragColor = texture(tx, real_uv);
|
||||
}
|
22
resources/shaders/source/sprite.vert.glsl
Normal file
22
resources/shaders/source/sprite.vert.glsl
Normal file
@ -0,0 +1,22 @@
|
||||
#version 450
|
||||
|
||||
layout(set = 1, binding = 0) uniform Viewport {
|
||||
ivec2 view;
|
||||
ivec2 not_needed;
|
||||
};
|
||||
|
||||
layout(location = 0) in ivec2 position;
|
||||
layout(location = 1) in ivec2 in_uv;
|
||||
layout(location = 2) in ivec4 color;
|
||||
|
||||
layout(location = 0) out vec2 out_uv;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec2 pos;
|
||||
pos.x = float(position.x)*2.0 / view.x - 1.0;
|
||||
pos.y = -(float(position.y)*2.0 / view.y - 1.0);
|
||||
gl_Position = vec4(pos, 0.0, 1.0);
|
||||
|
||||
out_uv = vec2(float(in_uv.x), float(in_uv.y));
|
||||
}
|
295
src/renderer.c3
295
src/renderer.c3
@ -28,11 +28,14 @@ struct Texture {
|
||||
uint id;
|
||||
}
|
||||
|
||||
// gpu buffer that contains a single quad
|
||||
// The GPU buffers that contain quad info, the size is determined by MAX_QUAD_BATCH
|
||||
const int MAX_QUAD_BATCH = 128;
|
||||
struct QuadBuffer {
|
||||
sdl::GPUBuffer* vert_buf;
|
||||
sdl::GPUBuffer* idx_buf;
|
||||
bool initialized;
|
||||
int count;
|
||||
int off; // the offset to draw from
|
||||
}
|
||||
|
||||
alias ShaderList = List{Shader};
|
||||
@ -93,6 +96,8 @@ $if DEBUG == 0:
|
||||
sdl::set_hint(sdl::HINT_VIDEO_DRIVER, "wayland");
|
||||
}
|
||||
$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");
|
||||
$endif
|
||||
|
||||
@ -101,44 +106,40 @@ $endif
|
||||
|
||||
// init subsystems
|
||||
if (!sdl::init(INIT_VIDEO)) {
|
||||
io::eprintfn("sdl error: %s", sdl::get_error());
|
||||
libc::exit(1);
|
||||
unreachable("sdl error: %s", sdl::get_error());
|
||||
}
|
||||
|
||||
// create the window
|
||||
self.win = sdl::create_window(title, 640, 480, WINDOW_RESIZABLE|WINDOW_VULKAN);
|
||||
if (self.win == null) {
|
||||
io::eprintfn("sdl error: %s", sdl::get_error());
|
||||
libc::exit(1);
|
||||
unreachable("sdl error: %s", sdl::get_error());
|
||||
}
|
||||
|
||||
// get the gpu device handle
|
||||
self.gpu = sdl::create_gpu_device(GPU_SHADERFORMAT_SPIRV, true, "vulkan");
|
||||
if (self.gpu == null) {
|
||||
io::eprintfn("failed to create gpu device: %s", sdl::get_error());
|
||||
libc::exit(1);
|
||||
unreachable("failed to create gpu device: %s", sdl::get_error());
|
||||
}
|
||||
|
||||
if (!sdl::claim_window_for_gpu_device(self.gpu, self.win)) {
|
||||
io::eprintfn("failed to claim window for use with gpu: %s", sdl::get_error());
|
||||
libc::exit(1);
|
||||
unreachable("failed to claim window for use with gpu: %s", sdl::get_error());
|
||||
}
|
||||
|
||||
//
|
||||
// initialize the quad buffer
|
||||
// ==========================
|
||||
self.quad_buffer.vert_buf = sdl::create_gpu_buffer(self.gpu,
|
||||
&&(GPUBufferCreateInfo){.usage = GPU_BUFFERUSAGE_VERTEX, .size = Quad.vertices.sizeof}
|
||||
&&(GPUBufferCreateInfo){.usage = GPU_BUFFERUSAGE_VERTEX, .size = Quad.vertices.sizeof * MAX_QUAD_BATCH}
|
||||
);
|
||||
if (self.quad_buffer.vert_buf == null) {
|
||||
io::eprintfn("failed to initialize quad buffer (vertex): %s", sdl::get_error());
|
||||
libc::exit(1);
|
||||
unreachable("failed to initialize quad buffer (vertex): %s", sdl::get_error());
|
||||
}
|
||||
|
||||
self.quad_buffer.idx_buf = sdl::create_gpu_buffer(self.gpu,
|
||||
&&(GPUBufferCreateInfo){.usage = GPU_BUFFERUSAGE_INDEX, .size = Quad.indices.sizeof}
|
||||
&&(GPUBufferCreateInfo){.usage = GPU_BUFFERUSAGE_INDEX, .size = Quad.indices.sizeof * MAX_QUAD_BATCH}
|
||||
);
|
||||
if (self.quad_buffer.idx_buf == null) {
|
||||
io::eprintfn("failed to initialize quad buffer (index): %s", sdl::get_error());
|
||||
libc::exit(1);
|
||||
unreachable("failed to initialize quad buffer (index): %s", sdl::get_error());
|
||||
}
|
||||
|
||||
self.quad_buffer.initialized = true;
|
||||
@ -189,8 +190,7 @@ fn void Renderer.load_spirv_shader_from_mem(&self, String name, char[] vert_code
|
||||
|
||||
s.vert = sdl::create_gpu_shader(self.gpu, &shader_info);
|
||||
if (s.vert == null) {
|
||||
io::eprintfn("failed to create gpu vertex shader: %s", sdl::get_error());
|
||||
libc::exit(1);
|
||||
unreachable("failed to create gpu vertex shader: %s", sdl::get_error());
|
||||
}
|
||||
}
|
||||
|
||||
@ -211,8 +211,7 @@ fn void Renderer.load_spirv_shader_from_mem(&self, String name, char[] vert_code
|
||||
|
||||
s.frag = sdl::create_gpu_shader(self.gpu, &shader_info);
|
||||
if (s.frag == null) {
|
||||
io::eprintfn("failed to create gpu fragment shader: %s", sdl::get_error());
|
||||
libc::exit(1);
|
||||
unreachable("failed to create gpu fragment shader: %s", sdl::get_error());
|
||||
}
|
||||
}
|
||||
|
||||
@ -230,21 +229,16 @@ fn void Renderer.load_spirv_shader_from_file(&self, String name, String vert_pat
|
||||
char[] frag_code;
|
||||
|
||||
// create vertex shader
|
||||
if (vert_path != "") {
|
||||
vert_code = mem::new_array(char, file::get_size(vert_path)!!+1);
|
||||
file::load_buffer(vert_path, vert_code)!!;
|
||||
}
|
||||
vert_code = mem::new_array(char, file::get_size(vert_path)!!+1);
|
||||
file::load_buffer(vert_path, vert_code)!!;
|
||||
defer mem::free(vert_code);
|
||||
|
||||
// create fragment shader
|
||||
if (frag_path != "") {
|
||||
frag_code = mem::new_array(char, file::get_size(frag_path)!!+1);
|
||||
file::load_buffer(frag_path, frag_code)!!;
|
||||
}
|
||||
frag_code = mem::new_array(char, file::get_size(frag_path)!!+1);
|
||||
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);
|
||||
|
||||
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)
|
||||
@ -296,8 +290,7 @@ fn void Renderer.create_pipeline(&self, String shader_name, PipelineType type)
|
||||
{
|
||||
Shader *s = self.shaders.get_from_name(shader_name);
|
||||
if (s == null) {
|
||||
io::eprintfn("error in creating pipeline: no shader named %s", shader_name);
|
||||
libc::exit(1);
|
||||
unreachable("error in creating pipeline: no shader named %s", shader_name);
|
||||
}
|
||||
|
||||
GPUGraphicsPipelineCreateInfo ci = {
|
||||
@ -375,21 +368,21 @@ fn void Renderer.create_pipeline(&self, String shader_name, PipelineType type)
|
||||
};
|
||||
|
||||
if (p.pipeline == null) {
|
||||
io::eprintfn("failed to create pipeline (shaders: %s, type: %s): %s", shader_name, type.nameof, sdl::get_error());
|
||||
libc::exit(1);
|
||||
unreachable("failed to create pipeline (shaders: %s, type: %s): %s", shader_name, type.nameof, sdl::get_error());
|
||||
}
|
||||
|
||||
self.pipelines.push(p);
|
||||
}
|
||||
|
||||
// NOTE: with TEXTUREUSAGE_SAMPLER the texture format cannot be intger _UINT so it has to be nermalized
|
||||
enum TextureType : (GPUTextureFormat format) {
|
||||
FULL_COLOR = GPU_TEXTUREFORMAT_R8G8B8A8_UINT,
|
||||
JUST_ALPHA = GPU_TEXTUREFORMAT_R8_UINT
|
||||
FULL_COLOR = GPU_TEXTUREFORMAT_R8G8B8A8_UNORM,
|
||||
JUST_ALPHA = GPU_TEXTUREFORMAT_R8_UNORM
|
||||
}
|
||||
|
||||
// create a new gpu texture from a pixel buffer, the format has to be specified
|
||||
// the new texture s given an id and pushed into a texture list
|
||||
fn void Renderer.new_texture(&self, String name, TextureType type, char[] pixels, ushort width, ushort height)
|
||||
fn void Renderer.new_texture(&self, String name, TextureType type, char[] pixels, uint width, uint height)
|
||||
{
|
||||
uint id = name.hash();
|
||||
|
||||
@ -402,14 +395,13 @@ fn void Renderer.new_texture(&self, String name, TextureType type, char[] pixels
|
||||
.width = width,
|
||||
.height = height,
|
||||
.layer_count_or_depth = 1,
|
||||
.num_levels = 0, // no mip maps
|
||||
.num_levels = 1, // no mip maps so just one level
|
||||
// .sample_count not used since the texture is not a render target
|
||||
};
|
||||
|
||||
GPUTexture* texture = sdl::create_gpu_texture(self.gpu, &tci);
|
||||
if (texture == null) {
|
||||
io::eprintfn("failed to create texture (name: %s, type: %s): %s", name, type.nameof, sdl::get_error());
|
||||
libc::exit(1);
|
||||
unreachable("failed to create texture (name: %s, type: %s): %s", name, type.nameof, sdl::get_error());
|
||||
}
|
||||
|
||||
// the sampler description, how the texture should be sampled
|
||||
@ -425,8 +417,7 @@ fn void Renderer.new_texture(&self, String name, TextureType type, char[] pixels
|
||||
|
||||
GPUSampler* sampler = sdl::create_gpu_sampler(self.gpu, &sci);
|
||||
if (sampler == null) {
|
||||
io::eprintfn("failed to create sampler (texture name: %s, type: %s): %s", name, type.nameof, sdl::get_error());
|
||||
libc::exit(1);
|
||||
unreachable("failed to create sampler (texture name: %s, type: %s): %s", name, type.nameof, sdl::get_error());
|
||||
}
|
||||
|
||||
Texture t = {
|
||||
@ -440,45 +431,39 @@ fn void Renderer.new_texture(&self, String name, TextureType type, char[] pixels
|
||||
self.update_texture(name, pixels, width, height);
|
||||
}
|
||||
|
||||
fn void Renderer.update_texture(&self, String name, char[] pixels, ushort width, ushort height, ushort x = 0, ushort y = 0)
|
||||
fn void Renderer.update_texture(&self, String name, char[] pixels, uint width, uint height, uint x = 0, uint y = 0)
|
||||
{
|
||||
Texture* t = self.textures.get_from_name(name);
|
||||
if (t == null || t.texture == null) {
|
||||
io::eprintf("failed updating texture: no texture named %s", name);
|
||||
libc::exit(1);
|
||||
unreachable("failed updating texture: no texture named %s", name);
|
||||
}
|
||||
GPUTexture* texture = t.texture;
|
||||
|
||||
// FIXME: do a better job at validating the copy
|
||||
if (x > t.width || y > t.height) {
|
||||
io::eprintf("failed updating texture: attempting to copy outside of the texture region", name);
|
||||
libc::exit(1);
|
||||
unreachable("failed updating texture: attempting to copy outside of the texture region", name);
|
||||
}
|
||||
|
||||
// upload image data
|
||||
GPUCommandBuffer* cmdbuf = sdl::acquire_gpu_command_buffer(self.gpu);
|
||||
if (cmdbuf == null) {
|
||||
io::eprintfn("failed to upload texture data at acquiring command buffer: %s", sdl::get_error());
|
||||
libc::exit(1);
|
||||
unreachable("failed to upload texture data at acquiring command buffer: %s", sdl::get_error());
|
||||
}
|
||||
GPUCopyPass* copypass = sdl::begin_gpu_copy_pass(cmdbuf);
|
||||
if (copypass == null) {
|
||||
io::eprintfn("failed to upload texture data at beginning copy pass: %s", sdl::get_error());
|
||||
libc::exit(1);
|
||||
unreachable("failed to upload texture data at beginning copy pass: %s", sdl::get_error());
|
||||
}
|
||||
|
||||
GPUTransferBuffer* buf = sdl::create_gpu_transfer_buffer(self.gpu,
|
||||
&&(GPUTransferBufferCreateInfo){.usage = GPU_TRANSFERBUFFERUSAGE_UPLOAD, .size = pixels.len}
|
||||
);
|
||||
if (buf == null) {
|
||||
io::eprintfn("failed to upload texture data at creating the transfer buffer: %s", sdl::get_error());
|
||||
libc::exit(1);
|
||||
unreachable("failed to upload texture data at creating the transfer buffer: %s", sdl::get_error());
|
||||
}
|
||||
|
||||
char* gpu_mem = (char*)sdl::map_gpu_transfer_buffer(self.gpu, buf, false);
|
||||
if (gpu_mem == null) {
|
||||
io::eprintfn("failed to upload texture data at mapping the transfer buffer: %s", sdl::get_error());
|
||||
libc::exit(1);
|
||||
unreachable("failed to upload texture data at mapping the transfer buffer: %s", sdl::get_error());
|
||||
}
|
||||
// copy the data to the driver's memory
|
||||
gpu_mem[:pixels.len] = pixels[..];
|
||||
@ -493,34 +478,115 @@ fn void Renderer.update_texture(&self, String name, char[] pixels, ushort width,
|
||||
|
||||
sdl::end_gpu_copy_pass(copypass);
|
||||
if (!sdl::submit_gpu_command_buffer(cmdbuf)) {
|
||||
io::eprintfn("failed to upload texture data at command buffer submission: %s", sdl::get_error());
|
||||
libc::exit(1);
|
||||
unreachable("failed to upload texture data at command buffer submission: %s", sdl::get_error());
|
||||
}
|
||||
sdl::release_gpu_transfer_buffer(self.gpu, buf);
|
||||
}
|
||||
|
||||
|
||||
// an highly inefficient way to draw a single quad, no batching, per-quad upload
|
||||
fn void Renderer.draw_rect(&self, short x, short y, short w, short h, uint color, String shader_name)
|
||||
{
|
||||
fn bool Renderer.push_sprite(&self, short x, short y, short w, short h, short u, short v) {
|
||||
if (self.quad_buffer.count >= MAX_QUAD_BATCH) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// upload the quad data to the gpu
|
||||
if (self.quad_buffer.initialized == false) {
|
||||
io::eprintfn("quad buffer not initialized");
|
||||
libc::exit(1);
|
||||
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) {
|
||||
io::eprintfn("failed to create gpu transfer buffer: %s", sdl::get_error());
|
||||
libc::exit(1);
|
||||
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) {
|
||||
io::eprintfn("failed to map gpu transfer buffer: %s", sdl::get_error());
|
||||
libc::exit(1);
|
||||
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
|
||||
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
|
||||
@ -539,13 +605,11 @@ fn void Renderer.draw_rect(&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.v3 = {.pos = {.x = x+w, .y = y+h}, .col.u = color};
|
||||
quad.vertices.v4 = {.pos = {.x = x+w, .y = y}, .col.u = color};
|
||||
|
||||
|
||||
// triangle 1
|
||||
// triangle 1 indices
|
||||
quad.indices.i1 = 0; // v1
|
||||
quad.indices.i2 = 1; // v2
|
||||
quad.indices.i3 = 3; // v4
|
||||
// triangle 2
|
||||
// triangle 2 indices
|
||||
quad.indices.i4 = 1; // v2
|
||||
quad.indices.i5 = 2; // v3
|
||||
quad.indices.i6 = 3; // v4
|
||||
@ -554,21 +618,21 @@ fn void Renderer.draw_rect(&self, short x, short y, short w, short h, uint color
|
||||
|
||||
GPUCommandBuffer* cmd = sdl::acquire_gpu_command_buffer(self.gpu);
|
||||
if (cmd == null) {
|
||||
io::eprintfn("failed to upload quad at acquiring command buffer: %s", sdl::get_error());
|
||||
libc::exit(1);
|
||||
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 = self.quad_buffer.vert_buf, .offset = 0, .size = Quad.vertices.sizeof},
|
||||
&&(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 = self.quad_buffer.idx_buf, .offset = 0, .size = Quad.indices.sizeof},
|
||||
&&(GPUBufferRegion){.buffer = qb.idx_buf, .offset = qb.count * Quad.indices.sizeof, .size = Quad.indices.sizeof},
|
||||
false
|
||||
);
|
||||
|
||||
@ -577,42 +641,59 @@ fn void Renderer.draw_rect(&self, short x, short y, short w, short h, uint color
|
||||
unreachable("failed to upload quads at submit command buffer: %s", sdl::get_error());
|
||||
}
|
||||
sdl::release_gpu_transfer_buffer(self.gpu, buf);
|
||||
sdl::wait_for_gpu_idle(self.gpu);
|
||||
//sdl::wait_for_gpu_idle(self.gpu);
|
||||
|
||||
/*
|
||||
// now finally draw the quad
|
||||
// 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");
|
||||
}
|
||||
qb.count++;
|
||||
|
||||
// FIXME: this could be done at the start of rendering
|
||||
GPUTexture* t;
|
||||
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());
|
||||
}
|
||||
|
||||
// TODO: begin render pass
|
||||
|
||||
Pipeline* p = self.pipelines.get_from_name(shader_name);
|
||||
if (p == null) {
|
||||
unreachable("no pipeline named: %s", shader_name);
|
||||
}
|
||||
|
||||
// bind the data
|
||||
sdl::bind_gpu_graphics_pipeline(self.render_pass, pipeline);
|
||||
sdl::bind_gpu_vertex_buffer(self.render_pass, 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);
|
||||
*/
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: fn Renderer.draw_quad, it has to use a vertex buffer and an index buffer
|
||||
// draw all quads in the quad buffer, since uniforms are per-drawcall it makes no sense
|
||||
// to draw them one a the time
|
||||
fn void Renderer.draw_quads(&self, GPURenderPass* pass)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
fn void Renderer.reset_quads(&self)
|
||||
{
|
||||
self.quad_buffer.count = 0;
|
||||
self.quad_buffer.off = 0;
|
||||
}
|
||||
|
||||
|
||||
// TODO: fn Renderer.draw_sprite, same as draw_quad but also bind the texture
|
||||
// TODO: fn Renderer.begin_render
|
||||
// TODO: fn Renderer.end_render
|
||||
// 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.
|
||||
*/
|
@ -2,6 +2,8 @@ import sdlrenderer::ren;
|
||||
import std::io;
|
||||
import std::thread;
|
||||
import sdl3::sdl;
|
||||
import std::compression::qoi;
|
||||
import std::core::mem::allocator;
|
||||
|
||||
struct Viewsize @align(16) {
|
||||
int w, h;
|
||||
@ -13,19 +15,31 @@ fn int main()
|
||||
ren::Renderer ren;
|
||||
ren.init("test window");
|
||||
|
||||
// TODO: these could be the same function
|
||||
ren.load_spirv_shader_from_file("rect shader", "resources/shaders/compiled/rect.vert.spv", "resources/shaders/compiled/rect.frag.spv", 0, 1);
|
||||
ren.create_pipeline("rect shader", RECT);
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
|
||||
ren.draw_rect(100,100,100,100,0xff00ff00,"");
|
||||
// load the tux qoi image
|
||||
QOIDesc img_desc;
|
||||
char[] img_pixels = qoi::read(allocator::temp(), "resources/tux.qoi", &img_desc)!!;
|
||||
// and put it in a texture
|
||||
ren.new_texture("tux", FULL_COLOR, img_pixels, img_desc.width, img_desc.height);
|
||||
// create a new pipeline to use the texture
|
||||
ren.load_spirv_shader_from_file("sprite shader", "resources/shaders/compiled/sprite.vert.spv", "resources/shaders/compiled/sprite.frag.spv", 1, 1);
|
||||
ren.create_pipeline("sprite shader", SPRITE);
|
||||
|
||||
for (int i = 0; i < 20; i++) {
|
||||
GPUCommandBuffer* cmdbuf = sdl::acquire_gpu_command_buffer(ren.gpu);
|
||||
GPUTexture* swapchain_texture;
|
||||
sdl::wait_and_acquire_gpu_swapchain_texture(cmdbuf, ren.win, &swapchain_texture, null, null);
|
||||
|
||||
|
||||
GPURenderPass* pass;
|
||||
GPUGraphicsPipeline* p;
|
||||
Viewsize v = {.w = 640, .h = 480};
|
||||
|
||||
// Colored Rectangles Render Pass
|
||||
// FIXME: if doing damage tracking DO NOT clear the screen
|
||||
GPURenderPass* pass = sdl::begin_gpu_render_pass(cmdbuf,
|
||||
pass = sdl::begin_gpu_render_pass(cmdbuf,
|
||||
&&(GPUColorTargetInfo){
|
||||
.texture = swapchain_texture,
|
||||
.mip_level = 0,
|
||||
@ -47,24 +61,81 @@ fn int main()
|
||||
unreachable("render pass creation went wrong: %s", sdl::get_error());
|
||||
}
|
||||
|
||||
GPUGraphicsPipeline* p = ren.pipelines.get_from_name("rect shader").pipeline;
|
||||
// rect 1
|
||||
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) {
|
||||
unreachable("no pipeline");
|
||||
}
|
||||
|
||||
sdl::bind_gpu_graphics_pipeline(pass, p);
|
||||
sdl::bind_gpu_vertex_buffers(pass, 0, (GPUBufferBinding[]){{.buffer = ren.quad_buffer.vert_buf, .offset = 0}}, 1);
|
||||
sdl::bind_gpu_index_buffer(pass, &&(GPUBufferBinding){.buffer = ren.quad_buffer.idx_buf, .offset = 0}, GPU_INDEXELEMENTSIZE_16BIT);
|
||||
Viewsize v = {.w = 640, .h = 480};
|
||||
v.ox = 50*i;
|
||||
v.oy = 50*i;
|
||||
|
||||
v.ox = 10*i;
|
||||
v.oy = 10*i;
|
||||
sdl::push_gpu_vertex_uniform_data(cmdbuf, 1, &v, Viewsize.sizeof);
|
||||
|
||||
sdl::draw_gpu_indexed_primitives(pass, 6, 1, 0, 0, 0);
|
||||
ren.draw_quads(pass);
|
||||
|
||||
sdl::end_gpu_render_pass(pass);
|
||||
// End Rectangle Render Pass
|
||||
|
||||
|
||||
// Textured Rectangles Render Pass
|
||||
pass = sdl::begin_gpu_render_pass(cmdbuf,
|
||||
&&(GPUColorTargetInfo){
|
||||
.texture = swapchain_texture,
|
||||
.mip_level = 0,
|
||||
.layer_or_depth_plane = 0,
|
||||
.clear_color = {.r = 0.0, .g = 0.0, .b = 0.0, .a = 1.0},
|
||||
.load_op = GPU_LOADOP_DONT_CARE, // clear the screen at the start of the render pass
|
||||
.store_op = GPU_STOREOP_STORE,
|
||||
.resolve_texture = null,
|
||||
.resolve_mip_level = 0,
|
||||
.resolve_layer = 0,
|
||||
.cycle = false,
|
||||
.cycle_resolve_texture = false
|
||||
},
|
||||
1,
|
||||
null // huh
|
||||
);
|
||||
|
||||
if (pass == null) {
|
||||
unreachable("render pass creation went wrong: %s", sdl::get_error());
|
||||
}
|
||||
|
||||
p = ren.pipelines.get_from_name("sprite shader").pipeline;
|
||||
if (p == null) {
|
||||
unreachable("no pipeline");
|
||||
}
|
||||
|
||||
sdl::bind_gpu_graphics_pipeline(pass, p);
|
||||
// in this case it is not an offset but the texture size in pixels
|
||||
v.ox = 54;
|
||||
v.oy = 64;
|
||||
sdl::push_gpu_vertex_uniform_data(cmdbuf, 1, &v, Viewsize.sizeof);
|
||||
|
||||
// bind the pipeline's sampler
|
||||
ren::Texture* tx = ren.textures.get_from_name("tux");
|
||||
sdl::bind_gpu_fragment_samplers(pass, 0,
|
||||
(GPUTextureSamplerBinding[]){{.texture = tx.texture, .sampler = tx.sampler}}, 1
|
||||
);
|
||||
|
||||
// tux
|
||||
ren.push_sprite(300, 0, 54, 64, 0, 0);
|
||||
|
||||
ren.draw_quads(pass);
|
||||
|
||||
sdl::end_gpu_render_pass(pass);
|
||||
// End Textured Rectangle Render Pass
|
||||
|
||||
sdl::submit_gpu_command_buffer(cmdbuf);
|
||||
|
||||
ren.reset_quads();
|
||||
thread::sleep_ms(250);
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user