text rendering

This commit is contained in:
PS 2022-04-07 14:02:12 +02:00
parent 6e80c811cf
commit d7c708e5d4
13 changed files with 5096 additions and 80 deletions

View File

@ -91,9 +91,44 @@ void make_quad(Platform_Geometry_Buffer* geo, Platform_Shader* shd, Platform_Tex
}
internal void
ed_load_font_cb(Platform_File_Async_Job_Args result)
ed_load_font_cb(Platform_File_Async_Job_Args result, u8* user_data)
{
s32 x = 5;
App_State* state = (App_State*)user_data;
UI* ui = &state->editor->ui;
u8* f = result.data.base;
stbtt_fontinfo font;
if (!stbtt_InitFont(&font, f, stbtt_GetFontOffsetForIndex(f, 0)))
{
invalid_code_path;
}
r32 scale = stbtt_ScaleForPixelHeight(&font, 18.0f);
s32 ascent, descent, line_gap;
stbtt_GetFontVMetrics(&font, &ascent, &descent, &line_gap);
ui->font_ascent = (r32)ascent * scale;
ui->font_descent = (r32)descent * scale;
ui->font_line_gap = (r32)line_gap * scale;
if (ui->font_line_gap == 0) ui->font_line_gap = 5;
String c = lit_str("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()-=_+[]{}\\|;:'\",<.>/?`~");
for (u64 i = 0; i < c.len; i++)
{
s32 w, h, xoff, yoff;
u32 id = (u32)c.str[i];
u8* bp = stbtt_GetCodepointBitmap(&font, 0, scale, (char)c.str[i], &w, &h, &xoff, &yoff);
s32 x0, y0, x1, y1;
stbtt_GetCodepointBitmapBoxSubpixel(&font, (char)c.str[i], scale, scale, 0, 0, &x0, &y0, &x1, &y1);
v2 offset = v2{ 0, (r32)y0 };
texture_atlas_register(&state->editor->ui.atlas, bp, (u32)w, (u32)h, id, offset, TextureAtlasRegistration_PixelFormat_Alpha);
stbtt_FreeBitmap(bp, 0);
}
Texture_Atlas_Sprite m_sprite = texture_atlas_sprite_get(&state->editor->ui.atlas, (u32)'m');
ui->font_space_width = (r32)(m_sprite.max_x - m_sprite.min_x);
platform_texture_update(ui->atlas_texture, ui->atlas.pixels, 1024, 1024, 1024);
}
internal void

View File

@ -23,7 +23,6 @@ static String ui_shader_frag_win32 = lit_str(
"uniform sampler2D texture;\n"
"void main(void) {\n"
" FragColor = texture(texture, uv) * color;\n"
" if (FragColor.w <= 0.01f) discard;\n"
"}"
);
@ -73,8 +72,13 @@ ui_create(u32 widget_pool_cap, u32 verts_cap, Input_State* input, Allocator* a)
result.atlas = texture_atlas_create(1024, 1024, 512, permanent);
result.atlas_texture = platform_texture_create(result.atlas.pixels, 1024, 1024, 1024);
u32 white_sprite[] = { 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF };
ui_sprite_register(&result, (u8*)white_sprite, 2, 2, WHITE_SPRITE_ID);
u32 white_sprite[] = {
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
};
ui_sprite_register(&result, (u8*)white_sprite, 4, 4, WHITE_SPRITE_ID);
return result;
}
@ -119,14 +123,17 @@ ui_quad_push(UI* ui, v3 pmin, v3 pmax, v2 tmin, v2 tmax, v4 c)
internal void
ui_sprite_register(UI* ui, u8* pixels, u32 w, u32 h, u32 id)
{
texture_atlas_register(&ui->atlas, pixels, w, h, id);
texture_atlas_register(&ui->atlas, pixels, w, h, id, v2{0,0}, TextureAtlasRegistration_PixelFormat_RGBA);
platform_texture_update(ui->atlas_texture, ui->atlas.pixels, ui->atlas.width, ui->atlas.height, ui->atlas.width);
}
internal void
ui_sprite_push(UI* ui, v3 pmin, v3 pmax, u32 id, v4 color)
{
v4 uv = texture_atlas_sprite_get_uvs(&ui->atlas, id);
Texture_Atlas_Sprite sprite = texture_atlas_sprite_get(&ui->atlas, id);
v4 uv = texture_atlas_sprite_get_uvs(&ui->atlas, sprite);
pmin.XY += sprite.draw_offset;
pmax.XY += sprite.draw_offset;
ui_quad_push(ui, pmin, pmax, uv.xy, uv.zw, color);
}
@ -136,6 +143,37 @@ ui_sprite_push(UI* ui, v3 pmin, v3 pmax, u32 id)
ui_sprite_push(ui, pmin, pmax, id, v4{1,1,1,1});
}
struct UI_Char_Draw_Cmd
{
v4 uv;
v3 pmin;
v3 pmax;
v3 baseline_after;
};
internal UI_Char_Draw_Cmd
ui_sprite_char_get_draw_cmd(UI* ui, v3 at, u32 codepoint)
{
UI_Char_Draw_Cmd result = {};
Texture_Atlas_Sprite sprite = texture_atlas_sprite_get(&ui->atlas, codepoint);
result.uv = texture_atlas_sprite_get_uvs(&ui->atlas, sprite);
v3 dim = v3{
(r32)(sprite.max_x - sprite.min_x),
(r32)(sprite.max_y - sprite.min_y),
0,
};
result.pmin = at;
result.pmin.XY += sprite.draw_offset;
result.pmin = v3_floor(result.pmin);
result.pmax = result.pmin + dim;
result.baseline_after = v3{ result.pmax.x, at.y, at.z };
return result;
}
internal void
ui_frame_prepare(UI* ui, v2 window_dim)
{
@ -184,12 +222,12 @@ ui_draw(UI* ui)
UI_Widget_Result r0 = ui_widget_push(ui, d0);
UI_Widget_Desc d1 = d0;
d1.style.flags |= UIWidgetStyle_Outline | UIWidgetStyle_MouseClick;
d1.style.color_bg = PINK_V4;
d1.style.flags |= UIWidgetStyle_Outline | UIWidgetStyle_MouseClick | UIWidgetStyle_Text;
d1.style.color_bg = PINK_V4;//{ 0.1f, 0.1f, 0.1f, 1.0f }; //
d1.style.color_fg = GREEN_V4;
d1.p_min = v2{ 512, 32 };
d1.p_max = v2{ 640, 128 };
d1.string = lit_str("Hello");
d1.string = lit_str("Hello there my friend, what's going on?");
UI_Widget_Result r1 = ui_widget_push(ui, d1);
bool clicked_r1 = has_flag(r1.flags, UIWidgetResult_MouseLeft_WentUp);
if (clicked_r1) show = !show;
@ -214,7 +252,7 @@ ui_draw(UI* ui)
u32 widget_count = ui->widgets.free_len;
r32 range_min = -10;
r32 range_max = -1;
r32 range_step = (range_max - range_min) / (r32)widget_count;
r32 range_step = (range_max - range_min) / (r32)(widget_count * 4);
ui_widgets_to_geometry_recursive(ui, ui->widgets.root, -10, range_step);
platform_geometry_buffer_update(
@ -402,14 +440,48 @@ ui_widgets_to_geometry_recursive(UI* ui, UI_Widget* widget, r32 z_start, r32 z_s
if (has_flag(child->desc.style.flags, UIWidgetStyle_Bg))
{
bg_min.z += z_step;
bg_max.z += z_step;
z_at += z_step;
bg_min.z = z_at;
bg_max.z = z_at;
ui_sprite_push(ui, bg_min, bg_max, desc.style.sprite, color_bg);
}
if (has_flag(child->desc.style.flags, UIWidgetStyle_Text))
{
// TODO(PS):
z_at += z_step + z_step;
r32 space_width = ui->font_space_width;
r32 to_baseline = ui->font_line_gap + ui->font_ascent;
v3 line_offset = { 5, 3 + to_baseline, 0 };
r32 baseline_x_start = desc.p_min.x + line_offset.x;
r32 baseline_y_start = desc.p_min.y + line_offset.y;
v3 baseline = { baseline_x_start, baseline_y_start, z_at };
for (u64 i = 0; i < child->desc.string.len; i++)
{
u8 at = child->desc.string.str[i];
UI_Char_Draw_Cmd cmd = {};
if (!char_is_space(at))
{
cmd = ui_sprite_char_get_draw_cmd(ui, baseline, (u32)at);
}
else
{
cmd.baseline_after = baseline;
cmd.baseline_after.x += space_width;
}
if (cmd.baseline_after.x >= desc.p_max.x - 5)
{
baseline.x = baseline_x_start;
baseline.y += ui->font_ascent + ui->font_descent + ui->font_line_gap;
cmd = ui_sprite_char_get_draw_cmd(ui, baseline, (u32)at);
}
if (!char_is_space(at))
{
ui_quad_push(ui, cmd.pmin, cmd.pmax, cmd.uv.xy, cmd.uv.zw, color_fg);
}
baseline = cmd.baseline_after;
}
}
if (child->child_first)

View File

@ -130,6 +130,7 @@ struct UI
u32 indices_cap;
Texture_Atlas atlas;
r32 font_ascent, font_descent, font_line_gap, font_space_width;
UI_Widget_Pool widgets;
UI_Style_Sheet* style_sheet;
@ -156,6 +157,7 @@ internal void ui_quad_push(UI* ui, v3 pmin, v3 pmax, v2 tmin, v2 tmax, v4 c);
internal void ui_sprite_register(UI* ui, u8* pixels, u32 w, u32 h, u32 id);
internal void ui_sprite_push(UI* ui, v3 pmin, v3 pmax, u32 id, v4 color);
internal void ui_sprite_push(UI* ui, v3 pmin, v3 pmax, u32 id);
internal v3 ui_sprite_char_push(UI* ui, v2 at, u32 codepoint, v4 color);
internal void ui_draw(UI* ui);
// Widgets

4853
src_v2/libs/stb_truetype.h Normal file

File diff suppressed because it is too large Load Diff

View File

@ -44,7 +44,7 @@ lumenarium_frame_prepare(App_State* state)
}
incenter_frame_prepare(state);
platform_file_async_jobs_do_work(4);
platform_file_async_jobs_do_work(4, (u8*)state);
}
internal void

View File

@ -9,17 +9,19 @@ struct Texture_Atlas_Sprite
u16 min_y;
u16 max_x;
u16 max_y;
v2 draw_offset;
};
struct Texture_Atlas
{
u8* pixels;
u16 width;
u16 height;
u32 width;
u32 height;
u16 next_x;
u16 next_y;
u16 y_used;
u32 next_x;
u32 next_y;
u32 y_used;
u32* ids;
Texture_Atlas_Sprite* sprites;
@ -36,7 +38,7 @@ texture_atlas_create(u32 width, u32 height, u32 cap, Allocator* allocator)
result.height = (u16)height;
for (u32 i = 0; i < width * height; i++) {
u8* base = result.pixels + (i * 4);
*(u32*)base = 0xFFFFFFFF;
*(u32*)base = 0x00FFFFFF;
}
result.ids = allocator_alloc_array(allocator, u32, cap);
@ -46,52 +48,109 @@ texture_atlas_create(u32 width, u32 height, u32 cap, Allocator* allocator)
return result;
}
internal void
texture_atlas_register(Texture_Atlas* ta, u8* pixels, u32 width, u32 height, u32 id)
enum Texture_Atlas_Registration_Flags
{
u16 min_x = ta->next_x;
u16 min_y = ta->next_y;
u16 max_x = min_x + (u16)width;
u16 max_y = min_y + (u16)height;
TextureAtlasRegistration_None = 0,
TextureAtlasRegistration_PixelFormat_RGBA = 1,
TextureAtlasRegistration_PixelFormat_Alpha = 2,
};
// TODO(PS): if the sprite won't fit in this row, then we need to shift it to
// the next one
internal void
texture_atlas_register(Texture_Atlas* ta, u8* pixels, u32 width, u32 height, u32 id, v2 draw_offset, u32 flags)
{
if (ta->next_x > ta->width || (ta->next_x + width + 2) > ta->width)
{
ta->next_x = 0;
ta->next_y = ta->y_used;
}
u32 min_x = ta->next_x + 1;
u32 min_y = ta->next_y + 1;
u32 max_x = min_x + width;
u32 max_y = min_y + height;
// copy the data
for (u16 y = 0; y < height; y++)
if (has_flag(flags, TextureAtlasRegistration_PixelFormat_RGBA))
{
u16 src_row = (y * (u16)width) * 4;
u16 dst_row = (((y + min_y) * ta->width) + min_x) * 4;
for (u16 x = 0; x < width; x++)
for (u32 y = 0; y < height; y++)
{
ta->pixels[dst_row++] = pixels[src_row++];
ta->pixels[dst_row++] = pixels[src_row++];
ta->pixels[dst_row++] = pixels[src_row++];
ta->pixels[dst_row++] = pixels[src_row++];
u32 src_row = (y * width) * 4;
u32 dst_row = (((y + min_y) * ta->width) + min_x) * 4;
for (u32 x = 0; x < width; x++)
{
ta->pixels[dst_row++] = pixels[src_row++];
ta->pixels[dst_row++] = pixels[src_row++];
ta->pixels[dst_row++] = pixels[src_row++];
ta->pixels[dst_row++] = pixels[src_row++];
}
}
}
else if (has_flag(flags, TextureAtlasRegistration_PixelFormat_Alpha))
{
for (u32 y = 0; y < height; y++)
{
u32 src_row = y * width;
u32 dst_row = (((y + min_y) * ta->width) + min_x) * 4;
for (u32 x = 0; x < width; x++)
{
ta->pixels[dst_row++] = 0xFF;
ta->pixels[dst_row++] = 0xFF;
ta->pixels[dst_row++] = 0xFF;
ta->pixels[dst_row++] = pixels[src_row++];
}
}
}
// copy nearest pixels to the border
u32 pi_width = ta->width;
u32 pi_stride = 4;
#define PIXEL_INDEX(x,y) ((((y) * pi_width) + (x)) * pi_stride)
#define COPY_PIXEL(db,sb) \
ta->pixels[(db) + 0] = ta->pixels[(sb) + 0]; \
ta->pixels[(db) + 1] = ta->pixels[(sb) + 1]; \
ta->pixels[(db) + 2] = ta->pixels[(sb) + 2]; \
ta->pixels[(db) + 3] = ta->pixels[(sb) + 3];
for (u32 x = 0; x < width; x++)
{
u32 top = PIXEL_INDEX(min_x + x, min_y - 1);
u32 top_near = PIXEL_INDEX(min_x + x, min_y);
u32 bot = PIXEL_INDEX(min_x + x, max_y);
u32 bot_near = PIXEL_INDEX(min_x + x, max_y - 1);
COPY_PIXEL(top, top_near);
COPY_PIXEL(bot, bot_near);
}
for (u32 y = 0; y < height + 2; y++)
{
u32 left = PIXEL_INDEX(min_x - 1, min_y + y - 1);
u32 left_near = PIXEL_INDEX(min_x, min_y + y - 1);
u32 right = PIXEL_INDEX(max_x, min_y + y - 1);
u32 right_near = PIXEL_INDEX(max_x - 1, min_y + y - 1);
COPY_PIXEL(left, left_near);
COPY_PIXEL(right, right_near);
}
#undef PIXEL_INDEX
#undef COPY_PIXEL
// register a new slot
u32 index = hash_table_register(ta->ids, ta->cap, id);
Texture_Atlas_Sprite* sprite = ta->sprites + index;
sprite->min_x = min_x;
sprite->min_y = min_y;
sprite->max_x = max_x;
sprite->max_y = max_y;
sprite->min_x = (u16)min_x;
sprite->min_y = (u16)min_y;
sprite->max_x = (u16)max_x;
sprite->max_y = (u16)max_y;
sprite->draw_offset = draw_offset;
// Prepare for next registration
if (max_y > ta->y_used)
{
ta->y_used = max_y;
ta->y_used = max_y + 2;
}
ta->next_x = max_x + 1;
if (ta->next_x > ta->width)
{
ta->next_x = 0;
ta->next_y = ta->y_used;
}
ta->next_x = max_x + 2;
}
internal Texture_Atlas_Sprite
@ -105,9 +164,8 @@ texture_atlas_sprite_get(Texture_Atlas* ta, u32 id)
}
internal v4
texture_atlas_sprite_get_uvs(Texture_Atlas* ta, u32 id)
texture_atlas_sprite_get_uvs(Texture_Atlas* ta, Texture_Atlas_Sprite sprite)
{
Texture_Atlas_Sprite sprite = texture_atlas_sprite_get(ta, id);
v4 result = {};
// uv min
@ -118,11 +176,13 @@ texture_atlas_sprite_get_uvs(Texture_Atlas* ta, u32 id)
result.z = (r32)sprite.max_x / (r32)ta->width;
result.w = (r32)sprite.max_y / (r32)ta->height;
// inset
v2 half_texel = v2{1.0f / ta->width, 1.0f / ta->height};
result.xy += half_texel;
result.zw -= half_texel;
return result;
}
internal v4
texture_atlas_sprite_get_uvs(Texture_Atlas* ta, u32 id)
{
Texture_Atlas_Sprite sprite = texture_atlas_sprite_get(ta, id);
return texture_atlas_sprite_get_uvs(ta, sprite);
}
#endif //LUMENARIUM_TEXTURE_ATLAS_CPP

View File

@ -244,6 +244,7 @@ hash_table_find(u32* ids, u32 cap, u32 value)
// Vector Extensions
#define v2_to_v3(xy,z) v3{(xy).x, (xy).y, (z)}
#define v3_floor(v) v3{ floorf(v.x), floorf(v.y), floorf(v.z) }
//////////////////////////////////////////////
// Color Constants

View File

@ -26,7 +26,7 @@ void close_err_file() {}
// this assert works by simply trying to write to an invalid address
// (in this case, 0x0), which will crash in most debuggers
# define assert_always (*((volatile s32*)0) = 0xFFFF)
# define assert_always (*((volatile int*)0) = 0xFFFF)
#else
WASM_EXTERN void wasm_assert_always(char* file, unsigned int file_len, unsigned int line);
@ -34,12 +34,13 @@ WASM_EXTERN void wasm_assert_always(char* file, unsigned int file_len, unsigned
#endif // defined(PLATFORM_WASM)
#ifdef USE_ASSERTS
# define assert(c) \
# define assert(c) do { \
if (!(c)) { \
err_write("Assert Hit: %s:%d\n", __FILE__, (u32)__LINE__); \
err_write("Assert Hit: %s:%u\n", __FILE__, (u32)__LINE__); \
close_err_file(); \
assert_always; \
}
} \
} while(false)
// useful for catching cases that you aren't sure you'll hit, but
// want to be alerted when they happen

View File

@ -101,7 +101,7 @@ struct Platform_File_Async_Job_Args
u32 error;
};
typedef void Platform_File_Async_Cb(Platform_File_Async_Job_Args args);
typedef void Platform_File_Async_Cb(Platform_File_Async_Job_Args args, u8* user_data);
struct Platform_File_Async_Job
{
@ -187,9 +187,9 @@ platform_file_async_write(String path, Data data, Platform_File_Async_Cb* cb)
}
void
platform_file_async_job_complete(Platform_File_Async_Job* job)
platform_file_async_job_complete(Platform_File_Async_Job* job, u8* user_data)
{
job->cb(job->args);
job->cb(job->args, user_data);
allocator_free(platform_file_jobs_arena, job->job_memory.base, job->job_memory.size);
if (has_flag(job->args.flags, PlatformFileAsyncJob_Write))
{
@ -200,7 +200,7 @@ platform_file_async_job_complete(Platform_File_Async_Job* job)
void platform_file_async_work_on_job(Platform_File_Async_Job* job);
void
platform_file_async_jobs_do_work(u64 max_jobs)
platform_file_async_jobs_do_work(u64 max_jobs, u8* user_data)
{
u64 to_do = max_jobs;
if (max_jobs > platform_file_async_jobs_len) to_do = platform_file_async_jobs_len;
@ -216,7 +216,7 @@ platform_file_async_jobs_do_work(u64 max_jobs)
platform_file_async_work_on_job(job);
if (has_flag(job->args.flags, completed))
{
platform_file_async_job_complete(job);
platform_file_async_job_complete(job, user_data);
platform_file_async_job_rem(i);
}
}

View File

@ -13,6 +13,10 @@
#include "lumenarium_assert.h"
#define STB_TRUETYPE_IMPLEMENTATION
#define STBTT_assert(x) assert(x)
#include "../libs/stb_truetype.h"
// NOTE(PS): only need the opengl extension headers
// when running on a platform that is using opengl 3.3+
#if !defined(PLATFORM_wasm)
@ -21,18 +25,6 @@
# include "wglext.h"
#endif
#if 0
#define HMM_SINF sin
#define HMM_COSF cos
#define HMM_TANF tan
#define HMM_SQRTF sqrt
#define HMM_EXPF exp
#define HMM_LOGF log
#define HMM_ACOSF acos
#define HMM_ATANF atan
#define HMM_ATAN2F atan2
#endif
#define HANDMADE_MATH_IMPLEMENTATION
#define HANDMADE_MATH_CPP_MODE
#define HANDMADE_MATH_STATIC

View File

@ -206,8 +206,8 @@ platform_texture_create(u8* pixels, u32 width, u32 height, u32 stride)
glGenTextures(1, &result.id, sizeof(u32));
glBindTexture(GL_TEXTURE_2D, result.id);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

View File

@ -257,7 +257,7 @@ platform_texture_create(u8* pixels, u32 width, u32 height, u32 stride)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
win32_gl_no_error();