Compare commits

...

8 Commits

21 changed files with 472 additions and 232 deletions

4
.gitmodules vendored
View File

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

View File

@ -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
View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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);
if (elem.bounds.is_null()) { return; }
ctx.push_string(elem.bounds, text)!;
ctx.push_string(elem.bounds, text, parent.div.z_index)!;
}

View File

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

View File

@ -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>"],

View File

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

View File

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

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

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

View File

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