started work on an atlas interface, DOES NOT WORK

font_atlas
Alessandro Mauri 2 weeks ago
parent 5a89e9ec7d
commit fa4d3ed0ec
  1. 15
      TODO
  2. 183
      src/ugui_atlas.c3
  3. 3
      src/ugui_data.c3
  4. 90
      src/ugui_font.c3
  5. 2
      src/ugui_text.c3

15
TODO

@ -1,4 +1,6 @@
# TODOs, semi-random sorting # TODOs, semi-random sorting
[ ] Check every instance of foreach to see if I am using by-copy or by-reference correctly
[x] Implement glyph draw command [x] Implement glyph draw command
[x] Implement div.view and scrollbars [x] Implement div.view and scrollbars
[ ] Port font system from C to C3 (rewrite1) [ ] Port font system from C to C3 (rewrite1)
@ -7,26 +9,33 @@
[ ] Use an arena allocator for cache [ ] Use an arena allocator for cache
[ ] Do not redraw if there was no update (no layout and no draw) [ ] Do not redraw if there was no update (no layout and no draw)
[ ] Better handling of the active and focused widgets, try [ ] Better handling of the active and focused widgets, try
to maintain focus until mouse release (fix scroll bars) to maintain focus until mouse release (fix scroll bars)
[ ] Write a description for each file and the structs, interfaces provided
## Commands ## Commands
[x] rect commads should have: [x] rect commads should have:
* border width _ border width
* border radius _ border radius
[x] add a command to update an atlas [x] add a command to update an atlas
## Atlases ## Atlases
[ ] Add an interface to create, destroy, update and get atlases based on their ids [ ] Add an interface to create, destroy, update and get atlases based on their ids
[ ] Implement multiple font atlases [ ] Implement multiple font atlases
[ ] Create and use ShortIds for atlases
## Fonts ## Fonts
[ ] Fix the missing alpha channel [ ] Fix the missing alpha channel
[x] Fix the alignment [x] Fix the alignment
## Raylib ## Raylib
[ ] Implement type (Rect, Color, Point) conversion functions between rl:: and ugui:: [ ] Implement type (Rect, Color, Point) conversion functions between rl:: and ugui::
[x] Implement pixel radius rounding for border radius [x] Implement pixel radius rounding for border radius
## Widgets ## Widgets
[ ] Dynamic text box to implement an fps counter [ ] Dynamic text box to implement an fps counter
[ ] Button with label [ ] Button with label

@ -0,0 +1,183 @@
module ugui;
import std::core::mem;
import std::math::random;
random::SimpleRandom atlas_seed;
fault UgAtlasError {
CANNOT_PLACE,
INVALID_FORMAT,
MISSING_ATLAS,
}
enum AtlasFormat {
ATLAS_GRAYSCALE,
ATLAS_ALPHA,
}
// black and white atlas
struct Atlas {
AtlasFormat format;
Id id;
ushort width, height;
char[] buffer;
Point row;
ushort row_h;
}
// FIXME: allocate atlases dynamically
fn void! Ctx.init_atlases(&ctx)
{
atlas_seed.set_seed("ciao mamma");
ctx.atlas = mem::new_array(Atlas, MAX_ATLAS);
}
fn void Ctx.free_atlases(&ctx)
{
foreach (a: ctx.atlas) {
a.free();
}
mem::free(ctx.atlas);
}
// FIXME: this could be slow
fn Atlas*! Ctx.get_atlas(&ctx, Id id)
{
foreach (i, a: ctx.atlas) {
if (a.id == id) {
return &ctx.atlas[i];
}
}
return UgAtlasError.MISSING_ATLAS?;
}
// TODO: use the same allocator as all the other parts of the code
fn Atlas*! Ctx.request_new_atlas(&ctx, AtlasFormat format, Id *rid, ushort width, ushort height)
{
usz idx;
bool free = false;
for (; idx < ctx.atlas.len; idx++) {
if (ctx.atlas[idx].id == 0) {
free = true;
break;
}
}
if (free) {
*rid = ctx.unique_atlas_id();
ctx.atlas[idx].new(format, *rid, width, height)!;
return &ctx.atlas[idx];
} else {
// TODO: reallocate the atlas vector
return UgAtlasError.MISSING_ATLAS?;
}
}
// FIXME: WTF
fn Id Ctx.unique_atlas_id(&ctx) {
Id id = atlas_seed.next_long();
bool ok = true;
foreach (a: ctx.atlas) {
if (a.id == id) {
ok = false;
}
}
return ok ? id : ctx.unique_atlas_id();
}
fn void Ctx.discard_atlas(&ctx, Id id)
{
foreach (&a: ctx.atlas) {
if (a.id == id) {
a.free();
*a = Atlas{};
}
}
}
// Try to place a sprite into an atlas of the correct type and return it's id
// new_width and new_height are used in case a new atlas has to be requested, in that
// case they are used as width and height for the new atlas
// FIXME: maybe there has to be a default value
fn Id! Ctx.place_sprite(&ctx, Point* uv, AtlasFormat format, char[] pixels, ushort w, ushort h, ushort new_width, ushort new_height)
{
// try to place the sprite into an existing atlas
foreach (&a: ctx.atlas) {
if (a.id != 0 && a.format == format) {
Point! ouv = a.place(pixels, w, h);
if (catch ouv) {
continue;
}
*uv = ouv;
return a.id;
}
}
// fail... try to request a new atlas and place it there
Id id;
Atlas* atlas = ctx.request_new_atlas(format, &id, new_width, new_height)!;
*uv = atlas.place(pixels, w, h)!;
return id;
}
fn void! Atlas.new(&atlas, AtlasFormat format, Id id, ushort width, ushort height)
{
atlas.format = format;
atlas.id = id;
atlas.width = width;
atlas.height = height;
usz bpp = 1;
switch (format) {
case ATLAS_GRAYSCALE: nextcase;
case ATLAS_ALPHA:
bpp = 1;
default:
return UgAtlasError.INVALID_FORMAT?;
}
usz size = ((usz)atlas.width*atlas.height) * bpp;
atlas.buffer = mem::new_array(char, size);
}
fn void Atlas.free(&atlas)
{
free(atlas.buffer);
}
// 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/
// FIXME: untested with non-1bpp buffer depths
fn Point! Atlas.place(&atlas, char[] pixels, ushort w, ushort h)
{
Point p;
// TODO: simplify this and use rect_collision() and such
if (atlas.row.x + w <= atlas.width && atlas.row.y + h <= atlas.height) {
p = atlas.row;
} else {
atlas.row.x = 0;
atlas.row.y = atlas.row.y + atlas.row_h;
atlas.row_h = 0;
if (atlas.row.x + w <= atlas.width && atlas.row.y + h <= atlas.height) {
p = atlas.row;
} else {
return UgAtlasError.CANNOT_PLACE?;
}
}
for (usz y = 0; y < h; y++) {
for (usz x = 0; x < w; x++) {
atlas.buffer[(usz)(p.y+y)*atlas.width + (p.x+x)] = pixels[y*w + x];
}
}
atlas.row.x += w;
if (h > atlas.row_h) {
atlas.row_h = h;
}
return p;
}

@ -127,6 +127,7 @@ const uint STACK_STEP = 10;
const uint MAX_ELEMS = 128; const uint MAX_ELEMS = 128;
const uint MAX_CMDS = 256; const uint MAX_CMDS = 256;
const uint ROOT_ID = 1; const uint ROOT_ID = 1;
const uint MAX_ATLAS = 2;
// command type // command type
enum CmdType { enum CmdType {
@ -193,6 +194,8 @@ struct Ctx {
// total size in pixels of the context // total size in pixels of the context
ushort width, height; ushort width, height;
Style style; Style style;
Atlas[] atlas;
Font font; Font font;
bool has_focus; bool has_focus;

@ -34,7 +34,7 @@ struct Glyph {
ushort u, v; ushort u, v;
ushort w, h; ushort w, h;
short adv, ox, oy; short adv, ox, oy;
short idx; // atlas index Id atlas_id;
} }
const uint FONT_CACHED = 512; const uint FONT_CACHED = 512;
@ -47,19 +47,6 @@ fault UgFontError {
RENDER_ERROR, RENDER_ERROR,
} }
fault UgAtlasError {
CANNOT_PLACE,
}
// black and white atlas
struct AtlasBW {
ushort width, height;
char[] buffer;
Point row;
ushort row_h;
}
struct Font { struct Font {
schrift::Sft sft; schrift::Sft sft;
String path; String path;
@ -67,57 +54,12 @@ struct Font {
float size; float size;
float ascender, descender, linegap; // Line Metrics float ascender, descender, linegap; // Line Metrics
AtlasBW[] atlas;
} }
fn void! AtlasBW.new(&atlas, ushort width, ushort height)
{
atlas.width = width;
atlas.height = height;
atlas.buffer = mem::new_array(char, (usz)atlas.width*atlas.height);
}
fn void AtlasBW.free(&atlas) fn void! Ctx.font_load(&cxt, String path, uint height, float scale = 1)
{
free(atlas.buffer);
}
// 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! AtlasBW.place(&atlas, char[] pixels, ushort w, ushort h)
{
Point p;
if (atlas.row.x + w <= atlas.width && atlas.row.y + h <= atlas.height) {
p = atlas.row;
} else {
atlas.row.x = 0;
atlas.row.y = atlas.row.y + atlas.row_h;
atlas.row_h = 0;
if (atlas.row.x + w <= atlas.width && atlas.row.y + h <= atlas.height) {
p = atlas.row;
} else {
return UgAtlasError.CANNOT_PLACE?;
}
}
for (usz y = 0; y < h; y++) {
for (usz x = 0; x < w; x++) {
atlas.buffer[(usz)(p.y+y)*atlas.width + (p.x+x)] = pixels[y*w + x];
}
}
atlas.row.x += w;
if (h > atlas.row_h) {
atlas.row_h = h;
}
return p;
}
fn void! Font.load(&font, String path, uint height, float scale = 1)
{ {
Font* font = ctx.font;
font.table.new_init(capacity: FONT_CACHED); font.table.new_init(capacity: FONT_CACHED);
font.size = height*scale; font.size = height*scale;
@ -139,21 +81,11 @@ fn void! Font.load(&font, String path, uint height, float scale = 1)
font.descender = (float)lmetrics.descender; font.descender = (float)lmetrics.descender;
font.linegap = (float)lmetrics.lineGap; font.linegap = (float)lmetrics.lineGap;
//io::printfn("ascender:%d, descender:%d, linegap:%d", font.ascender, font.descender, font.linegap); //io::printfn("ascender:%d, descender:%d, linegap:%d", font.ascender, font.descender, font.linegap);
// TODO: allocate buffer based on FONT_CACHED and the size of a sample letter
// like the letter 'A'
font.atlas = mem::new_array(AtlasBW, 1);
ushort size = (ushort)font.size*256;
font.atlas[0].new(size, size)!;
// preallocate the ASCII range
// for (char c = ' '; c < '~'; c++) {
// font.get_glyph((Codepoint)c)!;
// }
} }
fn Glyph*! Font.get_glyph(&font, Codepoint code, bool* is_new = null) fn Glyph*! Ctx.get_glyph(&ctx, Codepoint code, bool* is_new = null)
{ {
Font* font = &ctx.font;
Glyph*! gp; Glyph*! gp;
gp = font.table.get_ref(code); gp = font.table.get_ref(code);
@ -199,8 +131,12 @@ fn Glyph*! Font.get_glyph(&font, Codepoint code, bool* is_new = null)
//io::printfn("code=%c, w=%d, h=%d, ox=%d, oy=%d, adv=%d", //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); // glyph.code, glyph.w, glyph.h, glyph.ox, glyph.oy, glyph.adv);
Point uv = font.atlas[0].place(pixels, glyph.w, glyph.h)!; // TODO: allocate buffer based on FONT_CACHED and the size of a sample letter
glyph.idx = 0; // like the letter 'A'
ushort size = (ushort)font.size*256;
Point uv;
Id id = ctx.place_sprite(&uv, ATLAS_ALPHA, pixels, glyph.w, glyph.h, size, size)!;
glyph.atlas_id = id;
glyph.u = uv.x; glyph.u = uv.x;
glyph.v = uv.y; glyph.v = uv.y;
@ -212,10 +148,8 @@ fn Glyph*! Font.get_glyph(&font, Codepoint code, bool* is_new = null)
return font.table.get_ref(code); return font.table.get_ref(code);
} }
// FIXME: maybe some atlases should be exclusive to fonts
fn void Font.free(&font) fn void Font.free(&font)
{ {
foreach (atlas: font.atlas) {
atlas.free();
}
schrift::freefont(font.sft.font); schrift::freefont(font.sft.font);
} }

@ -34,7 +34,7 @@ fn Rect! Ctx.get_text_bounds(&ctx, String text, bool* update_atlas)
off += x; off += x;
bool n; bool n;
if (!ascii::is_cntrl((char)cp)) { if (!ascii::is_cntrl((char)cp)) {
gp = ctx.font.get_glyph(cp, &n)!; gp = ctx.get_glyph(cp, &n)!;
line_len += gp.adv; line_len += gp.adv;
if (n) { *update_atlas = true; } if (n) { *update_atlas = true; }
} else if (cp == '\n'){ } else if (cp == '\n'){

Loading…
Cancel
Save