first working prototype of sprite drawing

This commit is contained in:
Alessandro Mauri 2025-01-29 01:10:18 +01:00
parent 9aa0d58d68
commit b317951c32
13 changed files with 386 additions and 36 deletions

View file

@ -81,6 +81,8 @@ fn int main(String[] args)
ugui::Ctx ui;
ui.init()!!;
ui.load_font("font1", "resources/hack-nerd.ttf", 16)!!;
ui.sprite_atlas_create("icons", AtlasType.ATLAS_RGBA32, 512, 512)!!;
ui.import_sprite_file_qoi("tux", "resources/tux.qoi")!!;
short width = 800;
short height = 450;
@ -99,8 +101,11 @@ fn int main(String[] args)
// font stuff
rl::Shader font_shader = rl::load_shader_from_memory(null, FONT_FS);
rl::Image font_atlas;
rl::Image sprite_atlas;
rl::Texture2D font_texture;
rl::Texture2D sprite_texture;
ugui::Id font_id = ui.get_font_id("font1");
ugui::Id sprite_id = ui.get_sprite_atlas_id("icons");
// Main loop
while (!rl::window_should_close()) {
@ -164,7 +169,7 @@ fn int main(String[] args)
if (ui.button("button2", ugui::Rect{0,0,30,30})!!.mouse_release) {
io::printn("release button2");
}
ui.layout_set_row()!!;
ui.layout_next_row()!!;
static float rf, gf, bf, af;
@ -179,6 +184,7 @@ fn int main(String[] args)
ui.layout_next_column()!!;
ui.button_label("Continua!")!!;
|};
ui.draw_sprite("tux")!!;
ui.div_end()!!;
ui.div_begin("second", ugui::DIV_FILL, scroll_x: true, scroll_y: true)!!;
@ -205,7 +211,7 @@ fn int main(String[] args)
ui.slider_hor("hs2", ugui::Rect{0,0,100,30}, &f2)!!;
|};
ui.div_end()!!;
// Timings counter
TimeStats dts = draw_times.get_stats();
TimeStats uts = ui_times.get_stats();
@ -237,27 +243,48 @@ fn int main(String[] args)
float roundness = r.w > r.h ? (2.1f*rad)/(float)r.h : (2.1f*rad)/(float)r.w;
rl::draw_rectangle_rounded(cmd.rect.rect.conv(), roundness, 0, cmd.rect.color.conv());
case ugui::CmdType.CMD_UPDATE_ATLAS:
if (cmd.update_atlas.id != font_id) { break; }
//rl::unload_image(font_atlas);
font_atlas.data = cmd.update_atlas.raw_buffer;
font_atlas.width = cmd.update_atlas.width;
font_atlas.height = cmd.update_atlas.height;
font_atlas.mipmaps = 1;
//font_atlas.format = rl::PixelFormat.PIXELFORMAT_UNCOMPRESSED_GRAYSCALE;
font_atlas.format = 1;
if (rl::is_texture_ready(font_texture)) {
rl::unload_texture(font_texture);
if (cmd.update_atlas.id == font_id) {
//rl::unload_image(font_atlas);
font_atlas.data = cmd.update_atlas.raw_buffer;
font_atlas.width = cmd.update_atlas.width;
font_atlas.height = cmd.update_atlas.height;
font_atlas.mipmaps = 1;
//font_atlas.format = rl::PixelFormat.PIXELFORMAT_UNCOMPRESSED_GRAYSCALE;
font_atlas.format = 1;
if (rl::is_texture_ready(font_texture)) {
rl::unload_texture(font_texture);
}
font_texture = rl::load_texture_from_image(font_atlas);
} else if (cmd.update_atlas.id == sprite_id) {
sprite_atlas.data = cmd.update_atlas.raw_buffer;
sprite_atlas.width = cmd.update_atlas.width;
sprite_atlas.height = cmd.update_atlas.height;
sprite_atlas.mipmaps = 1;
//sprite_atlas.format = rl::PixelFormat.PIXELFORMAT_UNCOMPRESSED_R8G8B8A8;
sprite_atlas.format = 7;
if (rl::is_texture_ready(sprite_texture)) {
rl::unload_texture(sprite_texture);
}
sprite_texture = rl::load_texture_from_image(sprite_atlas);
}
font_texture = rl::load_texture_from_image(font_atlas);
case ugui::CmdType.CMD_SPRITE:
if (cmd.sprite.texture_id != font_id) { break; }
rl::Vector2 position = {
.x = cmd.sprite.rect.x,
.y = cmd.sprite.rect.y,
};
rl::begin_shader_mode(font_shader);
rl::draw_texture_rec(font_texture, cmd.sprite.texture_rect.conv(), position, cmd.sprite.hue.conv());
rl::end_shader_mode();
if (cmd.sprite.texture_id == font_id) {
rl::Vector2 position = {
.x = cmd.sprite.rect.x,
.y = cmd.sprite.rect.y,
};
rl::begin_shader_mode(font_shader);
rl::draw_texture_rec(font_texture, cmd.sprite.texture_rect.conv(), position, cmd.sprite.hue.conv());
rl::end_shader_mode();
} else if (cmd.sprite.texture_id == sprite_id) {
rl::Vector2 position = {
.x = cmd.sprite.rect.x,
.y = cmd.sprite.rect.y,
};
rl::draw_texture_rec(sprite_texture, cmd.sprite.texture_rect.conv(), position, cmd.sprite.hue.conv());
} else {
io::printfn("unknown texture id: %d", cmd.sprite.texture_id);
}
case ugui::CmdType.CMD_SCISSOR:
if (cmd.scissor.rect.w == 0 && cmd.scissor.rect.h == 0) {
rl::end_scissor_mode();
@ -272,7 +299,6 @@ fn int main(String[] args)
//draw_times.print_stats();
rl::end_drawing();
/* End Drawing */
}
rl::close_window();

View file

@ -9,6 +9,7 @@ fault UgAtlasError {
enum AtlasType {
ATLAS_GRAYSCALE,
ATLAS_RGBA32,
}
// black and white atlas
@ -28,6 +29,7 @@ macro usz AtlasType.bpp(type)
{
switch (type) {
case ATLAS_GRAYSCALE: return 1;
case ATLAS_RGBA32: return 4;
}
}
@ -46,10 +48,25 @@ fn void Atlas.free(&atlas)
free(atlas.buffer);
}
/*
* pixels -> +--------------+-----+
* | | | h
* | | | e
* | | | i
* | | | g
* | | | h
* | | | t
* +--------------+-----+
* |<--- width -->|
* |<----- stride ----->|
* bytes per pixels are inferred and have to be the same
* as the atlas type
*/
// place a rect inside the atlas
// uses a row first algorithm
// TODO: use a skyline algorithm https://jvernay.fr/en/blog/skyline-2d-packer/implementation/
fn Point! Atlas.place(&atlas, char[] pixels, ushort w, ushort h)
fn Point! Atlas.place(&atlas, char[] pixels, ushort w, ushort h, ushort stride)
{
Point p;
@ -66,11 +83,11 @@ fn Point! Atlas.place(&atlas, char[] pixels, ushort w, ushort h)
}
}
usz bpp = atlas.type.bpp();
for (usz y = 0; y < h; y++) {
for (usz x = 0; x < w; x++) {
char[] buf = atlas.buffer[(usz)(p.y+y)*atlas.width + (p.x+x) ..];
char[] pix = pixels[y*w + x ..];
usz bpp = atlas.type.bpp();
char[] buf = atlas.buffer[(usz)(p.y+y)*atlas.width*bpp + (p.x+x)*bpp ..];
char[] pix = pixels[(usz)y*stride*bpp + x*bpp ..];
buf[0..bpp-1] = pix[0..bpp-1];
}

View file

@ -95,6 +95,7 @@ struct Ctx {
ushort width, height;
Style style;
Font font;
SpriteAtlas sprite_atlas;
bool has_focus;
struct input {
@ -220,6 +221,7 @@ fn void Ctx.free(&ctx)
(void)ctx.cache.free();
(void)ctx.cmd_queue.free();
(void)ctx.font.free();
(void)ctx.sprite_atlas.free();
}
fn void! Ctx.frame_begin(&ctx)
@ -281,6 +283,10 @@ fn void! Ctx.frame_end(&ctx)
ctx.push_update_atlas(&ctx.font.atlas)!;
ctx.font.should_update = false;
}
if (ctx.sprite_atlas.should_update) {
ctx.push_update_atlas(&ctx.sprite_atlas.atlas)!;
ctx.sprite_atlas.should_update = false;
}
$if 1:
// draw mouse position

View file

@ -144,7 +144,7 @@ fn Glyph*! Font.get_glyph(&font, Codepoint code)
//io::printfn("code=%c, w=%d, h=%d, ox=%d, oy=%d, adv=%d",
// glyph.code, glyph.w, glyph.h, glyph.ox, glyph.oy, glyph.adv);
Point uv = font.atlas.place(pixels, glyph.w, glyph.h)!;
Point uv = font.atlas.place(pixels, glyph.w, glyph.h, (ushort)img.width)!;
glyph.u = uv.x;
glyph.v = uv.y;
@ -159,6 +159,7 @@ fn Glyph*! Font.get_glyph(&font, Codepoint code)
fn void Font.free(&font)
{
font.atlas.free();
font.table.free();
schrift::freefont(font.sft.font);
}

130
src/ugui_sprite.c3 Normal file
View file

@ -0,0 +1,130 @@
module ugui;
import std::collections::map;
import std::io;
import mqoi;
const usz SRITES_PER_ATLAS = 64;
struct Sprite {
Id id;
ushort u, v;
ushort w, h;
}
def SpriteMap = map::HashMap(<Id, Sprite>);
struct SpriteAtlas {
Id id;
Atlas atlas;
SpriteMap sprites;
bool should_update;
}
// name: some examples are "icons" or "images"
fn void! SpriteAtlas.init(&this, String name, AtlasType type, ushort width, ushort height)
{
// FIXME: for now only RGBA32 format is supported
if (type != ATLAS_RGBA32) {
return UgAtlasError.INVALID_TYPE?;
}
this.id = name.hash();
this.atlas.new(this.id, AtlasType.ATLAS_RGBA32, width, height)!;
this.sprites.new_init(capacity: SRITES_PER_ATLAS);
this.should_update = false;
}
fn void! SpriteAtlas.free(&this)
{
this.atlas.free();
this.sprites.free();
}
// FIXME: this should throw an error when a different pixel format than the atlas' is used
fn Sprite*! SpriteAtlas.insert(&this, String name, char[] pixels, ushort w, ushort h, ushort stride)
{
Sprite s;
s.id = name.hash();
Point uv = this.atlas.place(pixels, w, h, stride)!;
s.w = w;
s.h = h;
s.u = uv.x;
s.v = uv.y;
this.sprites.set(s.id, s);
this.should_update = true;
return this.sprites.get_ref(s.id);
}
fn Sprite*! SpriteAtlas.get(&this, String name)
{
Id id = name.hash();
return this.sprites.get_ref(id);
}
fn Sprite*! SpriteAtlas.get_by_id(&this, Id id)
{
return this.sprites.get_ref(id);
}
fn void! Ctx.sprite_atlas_create(&ctx, String name, AtlasType type, ushort w, ushort h)
{
ctx.sprite_atlas.init(name, type, w, h)!;
}
fn Id Ctx.get_sprite_atlas_id(&ctx, String name)
{
return name.hash();
}
fn void! Ctx.import_sprite_memory(&ctx, String name, char[] pixels, ushort w, ushort h, ushort stride)
{
ctx.sprite_atlas.insert(name, pixels, w, h, stride)!;
}
fn void! Ctx.import_sprite_file_qoi(&ctx, String name, String path)
{
mqoi::Desc image_desc;
uint w, h;
File file = file::open(path, "rb")!;
defer (void) file.close();
while (!mqoi::desc_done(&image_desc)) {
mqoi::desc_push(&image_desc, file.read_byte()!);
}
if (mqoi::desc_verify(&image_desc, &w, &h) != 0) {
return IoError.FILE_NOT_VALID?;
}
mqoi::Dec dec;
mqoi::Rgba* px;
usz idx;
char[] pixels = mem::new_array(char, (usz)w*h*4);
defer mem::free(pixels);
mqoi::dec_init(&dec, w*h);
while (!mqoi::dec_done(&dec)) {
mqoi::dec_push(&dec, file.read_byte()!);
while ((px = mqoi::dec_pop(&dec)) != null) {
pixels[idx..idx+3] = px.value;
idx += 4;
}
}
ctx.sprite_atlas.insert(name, pixels, (ushort)w, (ushort)h, (ushort)w)!;
}
// FIXME: test function, very different from every other function here
fn void! Ctx.draw_sprite(&ctx, String name)
{
Sprite* sprite = ctx.sprite_atlas.get(name)!;
Rect bounds = { 100, 100, sprite.w, sprite.h };
Rect uv = { sprite.u, sprite.v, sprite.w, sprite.h };
Id tex_id = ctx.sprite_atlas.id;
return ctx.push_sprite(bounds, uv, tex_id)!;
}