From 74e8d3f0278a65fdf86a1185fec8a6016e628e88 Mon Sep 17 00:00:00 2001 From: quou Date: Sun, 19 Jan 2025 21:04:51 +1100 Subject: render UI in software --- ui.cpp | 605 ++++++++++++++++++++++++++++------------------------------------- 1 file changed, 261 insertions(+), 344 deletions(-) (limited to 'ui.cpp') diff --git a/ui.cpp b/ui.cpp index 6b282f9..387b5ac 100644 --- a/ui.cpp +++ b/ui.cpp @@ -14,6 +14,8 @@ extern "C" { #define vertex_buffer_count (2048) #define ui_padding 5 +#define DEBUG_CLEARS 0 + static constexpr int font_w = 960; static unsigned font_data[] = { 0x00000000, 0x00003000, 0x00000000, 0x00000000, 0x00000000, @@ -78,58 +80,6 @@ static unsigned font_data[] = { 0x0003c01e, 0x00000000, 0x00000000, 0x00000000, 0x00000000 }; -static Texture_Id create_atlas(Device* d) { - int x, y; - int w = font_w + 10; - int size = 10 * w; - Buffer_Id buf = d->create_buffer( - "font atlas stage", - size, - Buffer_Flags::copy_src | - Buffer_Flags::cpu_readwrite - ); - Texture_Id tex; - uint8_t* pixels = (uint8_t*)d->map_buffer(buf, 0, size); - for (y = 0; y < 10; y++) { - for (x = 0; x < font_w; x++) { - int si = x + y * font_w; - int di = x + y * w; - unsigned bits = font_data[si >> 5]; - int bit = bits & 1 << (si & 0x1f); - pixels[di] = bit? 0xff: 0x00; - } - } - /* for solid colours, one white square at the end */ - for (y = 0; y < 10; y++) { - for (x = font_w; x < w; x++) { - pixels[x + y * w] = 0xff; - } - } - d->unmap_buffer(buf); - tex = d->create_texture( - "font atlas", - texture_format_r8i, - Texture_Flags::sampleable | Texture_Flags::copy_dst, - w, - 10, - 1, - 1, - 1, - buf - ); - d->destroy_bufferi(buf); - return tex; -} - -static Sampler_Id create_clamped_point(Device* dev) { - Sampler_State s{}; - s.min = Filter_Mode::point; - s.mag = Filter_Mode::point; - s.address_u = Address_Mode::clamp; - s.address_v = Address_Mode::clamp; - return dev->create_sampler("clamped point", s); -} - UI::Colour::Colour(unsigned rgb, uint8_t a): r((rgb >> 16) & 0xff), g((rgb >> 8) & 0xff), @@ -157,6 +107,30 @@ void UI::Rect::clip(const Rect& other) { if (h < 0) h = 0; } +void UI::Rect::clip_src(const Rect& other, Rect& src) { + int n; + if ((n = other.x - x) > 0) { + w -= n; + x += n; + src.w -= n; + src.x += n; + } + if ((n = other.y - y) > 0) { + h -= n; + y += n; + src.h -= n; + src.y += n; + } + if ((n = x + w - (other.x + other.w)) > 0) { + w -= n; + src.w -= n; + } + if ((n = y + h - (other.y + other.h)) > 0) { + h -= n; + src.h -= n; + } +} + void UI::Rect::shrink(int a) { x += a; y += a; @@ -170,236 +144,248 @@ bool UI::Rect::contains(int px, int py) { return px >= x && py >= y && px < x + w && py < y + h; } -void UI::Vertex_Buffer::init(Device* dev) { - buf.init( - dev, - "UI mesh", - vertex_buffer_count * sizeof(Vertex) * 4, - Buffer_Flags::vertex_buffer - ); - init_indices(dev); - usage = 0; - start = 0; - next = 0; -} - -void UI::Vertex_Buffer::init_indices(Device* dev) { - auto create_ind = [](uint16_t* target){ - int i, i2, e = vertex_buffer_count * 6; - for (i = 0, i2 = 0; i < e; i += 6, i2 += 4) { - target[i ] = (uint16_t)(i2 ); - target[i + 1] = (uint16_t)(i2 + 1); - target[i + 2] = (uint16_t)(i2 + 2); - target[i + 3] = (uint16_t)(i2 + 1); - target[i + 4] = (uint16_t)(i2 + 3); - target[i + 5] = (uint16_t)(i2 + 2); - } - }; - int s = vertex_buffer_count * 6 * sizeof(uint16_t); - Buffer_Id stage = dev->create_buffer( - "UI mesh indices stage", - s, - Buffer_Flags::copy_src | - Buffer_Flags::cpu_readwrite - ); - void* mem = dev->map_buffer(stage, 0, s); - create_ind((uint16_t*)mem); - indices = dev->create_buffer( - "UI indices", - s, - Buffer_Flags::copy_dst | - Buffer_Flags::index_buffer - ); - Context& ctx = dev->acquire(); - ctx.copy(indices, stage); - dev->submit(ctx); - dev->unmap_buffer(stage); - dev->destroy_bufferi(stage); -} - -void UI::Vertex_Buffer::destroy(UI* ui) { - Device* dev = ui->device; - if (next) { - next->destroy(ui); - heap_free(ui->heap, next); - } - dev->destroy_buffer(indices); - buf.destroy(dev); +static inline UI::Colour blend(UI::Colour dst, UI::Colour src) { + int ia; + ia = 0xff - src.a; + dst.r = (uint8_t)(((src.r * src.a) + (dst.r * ia)) >> 8); + dst.g = (uint8_t)(((src.g * src.a) + (dst.g * ia)) >> 8); + dst.b = (uint8_t)(((src.b * src.a) + (dst.b * ia)) >> 8); + dst.a = (uint8_t)(((src.a * src.a) + (dst.a * ia)) >> 8); + return dst; } -void UI::Vertex_Buffer::add_quad( - UI* ui, + +void UI::Renderer::blit_rect( int x, int y, int w, int h, - float u0, - float v0, - float u1, - float v1, Colour col ) { - Vertex* verts; - int index = usage * 4; - int* scissor = ui->pipeline->scissor; - float r, g, b, a; - if (usage >= vertex_buffer_count) { - if (!next) { - next = (Vertex_Buffer*)heap_alloc(ui->heap, sizeof *next); - next->init(ui->device); + int i, j, ex, ey, s; + Rect r = { x, y, w, h }; + r.clip(clip); + Colour* dst = pixels + r.x + r.y * bound.w; + ex = r.x + r.w; + ey = r.y + r.h; + s = bound.w - r.w; + for (j = r.y; j < ey; j++) { + for (i = r.x; i < ex; i++) { + *dst = blend(*dst, col); + dst++; } - next->add_quad(ui, x, y, w, h, u0, v0, u1, v1, col); - return; + dst += s; } - if ( - clip.x < scissor[0] || - clip.y < scissor[1] || - clip.x + clip.w > scissor[0] + scissor[2] || - clip.y + clip.h > scissor[1] + scissor[3] || - (( - clip.x != scissor[0] || - clip.y != scissor[1] || - clip.w != scissor[2] || - clip.h != scissor[3] - ) && ( - x < clip.x || - y < clip.y || - x + w > clip.x + clip.w || - y + h > clip.y + clip.h - )) - ) { - ui->mesh.draw( - ui, - ui->device->get_ctx(), - *ui->pipeline, - *ui->render_pass - ); - scissor[0] = clip.x; - scissor[1] = clip.y; - scissor[2] = clip.w; - scissor[3] = clip.h; - ui->pipeline->hash(); +} + +void UI::Renderer::blit_char(char ch, int x, int y, Colour col) { + Rect re, su; + int i, j, sx, sy, ex, ey, s; + Colour* dst; + re.x = x; + re.y = y; + re.w = 10; + re.h = 10; + su.x = (ch - ' ') * 10; + su.y = 0; + su.w = 10; + su.h = 10; + re.clip_src(clip, su); + ex = re.x + re.w; + ey = re.y + re.h; + dst = pixels + re.x + re.y * bound.w; + s = bound.w - re.w; + for (j = re.y, sy = su.y; j < ey; j++, sy++) { + for (i = re.x, sx = su.x; i < ex; i++, sx++) { + int si = (sx + sy * font_w); + int bit = (1 << (si & 0x1f)); + bit &= font_data[si >> 5]; + if (bit) + *dst = blend(*dst, col); + dst++; + } + dst += s; } - r = col.r_f(); - g = col.g_f(); - b = col.b_f(); - a = col.a_f(); - verts = &((Vertex*)buf.map(ui->device))[index]; - verts[0] = Vertex { (float)x, (float)y, u0, v0, r, g, b, a }; - verts[1] = Vertex { (float)x + w, (float)y, u1, v0, r, g, b, a }; - verts[2] = Vertex { (float)x, (float)y + h, u0, v1, r, g, b, a }; - verts[3] = Vertex { (float)x + w, (float)y + h, u1, v1, r, g, b, a }; - buf.unmap(ui->device); - usage++; -} - -void UI::Vertex_Buffer::add_rect( - UI* ui, +} + +UI::Renderer::Cmd* UI::Renderer::add_cmd(int size) { + Cmd* r; + size = align_size(size, 8); + assert(cmd_buf_ptr + size <= cmd_buf_size); + r = (Cmd*)&cmd_buf[cmd_buf_ptr]; + r->size = size; + cmd_buf_ptr += size; + return r; +} + +void UI::Renderer::commit_cmd(Cmd* c) { + int x1 = std::min(c->r.x / grid_cell_size[0], grid_size); + int y1 = std::min(c->r.y / grid_cell_size[1], grid_size); + int x2 = std::min((c->r.x + c->r.w + grid_cell_size[0]) / grid_cell_size[0], grid_size); + int y2 = std::min((c->r.y + c->r.h + grid_cell_size[1]) / grid_cell_size[1], grid_size); + int s = grid_size - (x2 - x1); + uint64_t* h = grid + x1 + y1 * grid_size; + int i, j; + for (j = y1; j < y2; j++) { + for (i = x1; i < x2; i++) { + *h = fnv1a64_2(*h, (uint8_t*)c, c->size); + h++; + } + h += s; + } +} + +void UI::Renderer::add_rect( int x, int y, int w, int h, Colour col ) { - constexpr float white_uv_x = (float)font_w / (float)(font_w + 10); - add_quad(ui, x, y, w, h, white_uv_x, 0.0f, 1.0f, 1.0f, col); + Cmd* c = add_cmd(sizeof *c); + c->type = Cmd::RECT; + c->col = col; + c->r.x = x; + c->r.y = y; + c->r.w = w; + c->r.h = h; + commit_cmd(c); } -void UI::Vertex_Buffer::add_char( - UI* ui, +void UI::Renderer::add_text( int x, int y, - char ch, + const char* txt, Colour col ) { - int off = (ch - ' ') * 10; - float w = (float)(font_w + 10); - float u0 = (float)off / w; - float u1 = u0 + (10.0f / w); - add_quad(ui, x, y, 10, 10, u0, 0.0f, u1, 1.0f, col); + int len = string_len(txt); + Cmd* c = add_cmd(sizeof *c + len + 1); + c->type = Cmd::TEXT; + c->col = col; + c->r.x = x; + c->r.y = y; + c->r.w = 10 * len; + c->r.h = 10; + string_copy((char*)(c + 1), txt); + commit_cmd(c); +} + +void UI::Renderer::reset(const Rect& cl) { + int i, e; + uint64_t* dst = grid; + clip = cl; + e = grid_size * grid_size; + for (i = 0; i < e; i++, dst++) + *dst = fnv1a64(0, 0); + grid_cell_size[0] = cl.w / grid_size; + grid_cell_size[1] = cl.h / grid_size; + cmd_buf_ptr = 0; +} + +void UI::Renderer::set_clip(const Rect& r) { + Cmd* c = add_cmd(sizeof *c); + c->type = Cmd::CLIP; + c->col = Colour(0, 0); + c->r = r; + clip = r; } -void UI::Vertex_Buffer::add_text( - UI* ui, - int x, - int y, - const char* txt, - Colour col -) { - for (; *txt; txt++, x += 10) - add_char(ui, x, y, *txt, col); +void UI::Renderer::clear(const Rect& r) { + int x, y; + int ex = r.x + r.w; + int ey = r.y + r.h; + int s = bound.w - r.w; + Colour* dst = pixels + r.x + r.y * bound.w; +#if DEBUG_CLEARS + Colour col(rand(), 0x30); +#else + Colour col(0x000000, 0x00); +#endif + for (y = r.y; y < ey; y++) { + for (x = r.x; x < ex; x++) { + *dst = col; + dst++; + } + dst += s; + } } -void UI::Vertex_Buffer::update_buffer(Context& ctx) { - buf.update(ctx); +const App* g_app; +void UI::Renderer::flush(int x, int y) { + Cmd* cmd = (Cmd*)cmd_buf; + Cmd* last = (Cmd*)(cmd_buf + cmd_buf_ptr); + Rect cell = Rect( + x * grid_cell_size[0], + y * grid_cell_size[1], + grid_cell_size[0], + grid_cell_size[1] + ); + clip = cell; + clear(cell); + while (cmd != last) { + switch (cmd->type) { + case Cmd::RECT: { + Rect& r = cmd->r; + blit_rect(r.x, r.y, r.w, r.h, cmd->col); + } break; + case Cmd::TEXT: { + int x = cmd->r.x; + char* txt = (char*)(cmd + 1); + for (; *txt; txt++, x += 10) + blit_char(*txt, x, cmd->r.y, cmd->col); + } break; + case Cmd::CLIP: { + clip = cmd->r; + clip.clip(cell); + break; + } + } + cmd = (Cmd*)((uint8_t*)cmd + cmd->size); + } } -void UI::Vertex_Buffer::reset(const Rect& c) { - start = 0; - usage = 0; - dirty = true; - clip = c; - if (next) - next->reset(c); +void UI::Renderer::flush() { + uint64_t* next = grid == grid_a? grid_b: grid_a; + uint64_t* h = grid; + uint64_t* p = next; + int i, j; + for (j = 0; j < grid_size; j++) { + for (i = 0; i < grid_size; i++) { + if (*p != *h) { + flush(i, j); + } + h++; + p++; + } + } + grid = next; } -void UI::Vertex_Buffer::set_clip(const Rect& r) { - clip = r; - assert(r.x >= 0 && r.y >= 0); - assert(r.w >= 0 && r.h >= 0); - if (next) - next->set_clip(r); +void UI::Renderer::resize(int w, int h) { + heap_free(arena, pixels); + pixels = (Colour*)heap_alloc(arena, w * h * sizeof *pixels); + assert(pixels != 0); + bound = Rect(0, 0, w, h); } -void UI::Vertex_Buffer::draw( - UI* ui, - Context& ctx, - Pipeline& pip, - Render_Pass& rp -) { - if (usage == start) { - if (next) - next->draw(ui, ctx, pip, rp); - return; - } - if (dirty) { - update_buffer(ctx); - dirty = false; - } - Vertex_Buffer_Binding binding[] = {{ - .id = buf.gpuonly, - .offset = 0, - .target = ui->shader_info.vert_binding - }, {}}; - Index_Buffer_Binding indb = { - .id = indices, - .offset = 0 - }; - Draw d{}; - d.verts = binding; - d.inds = indb; - d.vertex_count = (usage - start) * 6; - d.instance_count = 1; - d.first_vertex = start * 6; - ctx.submit(d, pip, rp); - if (next) - next->draw(ui, ctx, pip, rp); - start = usage; +void UI::Renderer::init(Heap* heap, int w, int h) { + arena = heap; + pixels = (Colour*)heap_alloc(heap, w * h * sizeof *pixels); + assert(pixels != 0); + bound = Rect(0, 0, w, h); + grid = grid_a; } UI* UI::create( - Device* dev, const App* app, - Arena* a, - Shader_Id sh + Arena* a ) { int hs; - Texture_Id atlas = create_atlas(dev); UI* u = (UI*)arena_alloc(a, sizeof *u); Heap* h = (Heap*)arena_alloc(a, sizeof *h); hs = a->size - a->ptr - allocation_default_alignment - 1; init_heap(h, arena_alloc(a, hs), hs); - u->init(dev, app, h, atlas, sh); + u->init(app, h); return u; } @@ -415,44 +401,21 @@ int UI::text_height(const char* t) { } void UI::init( - Device* dev, const App* a, - Heap* h, - Texture_Id at, - Shader_Id sh + Heap* h ) { - Shader* sp; heap = h; - device = dev; app = a; - atlas = at; - shader = sh; hot = 0; hovered = 0; active = 0; - mesh.init(device); - cbuffer.init( - device, - "UI CBuffer", - sizeof(UI_CBuffer), - Buffer_Flags::constant_buffer - ); - sp = &dev->get_shader(sh); - vertex_format = sp->vf; - shader_info.vert_binding = sp->binding_index("verts"); - shader_info.atlas_binding = sp->descriptor_binding("atlas"); - shader_info.config_binding = sp->descriptor_binding("config_buffer"); - sampler = create_clamped_point(device); + ren.init(heap, a->w, a->h); root = create_element(0); ((Container*)root)->padding = 0; } void UI::destroy() { root->destroy(); - device->destroy_texture(atlas); - device->destroy_sampler(sampler); - mesh.destroy(this); - cbuffer.destroy(device); } void UI::layout(int w, int h) { @@ -516,93 +479,60 @@ void UI::update(Arena* s) { layout(area.w, area.h); } -void UI::render(Arena* s, Texture_Id target) { - Context& ctx = device->get_ctx(); - Texture& t = device->get_texture(target); - UI_CBuffer* config = (UI_CBuffer*)cbuffer.map(device); - config->projection = m4f::orth( - 0.0f, (float)t.w, - 0.0f, (float)t.h, - -1.0f, - 1.0f - ); - cbuffer.unmap(device); - cbuffer.update(ctx); - - Pipeline_Builder pb(s, device); - pb.begin_rp(); - pb.rp_target(device->get_backbuffer(), Clear_Mode::restore); - render_pass = &pb.build_rp(); - - pb.begin(); - pb.shader(shader); - pb.vertex_format(vertex_format); - pb.blend( - Blend_Mode::add, - Blend_Factor::src_alpha, - Blend_Factor::inv_src_alpha - ); - pb.cbuffer(shader_info.config_binding, cbuffer.gpuonly); - pb.texture(shader_info.atlas_binding, atlas, sampler); - pipeline = &pb.build(); - +void UI::render(Arena* s) { + if (area.w != ren.bound.w || area.h != ren.bound.h) + ren.resize(area.w, area.h); + ren.reset(ren.bound); root->render(); - mesh.draw(this, ctx, *pipeline, *render_pass); - mesh.reset(Rect(0, 0, t.w, t.h)); + g_app = app; + ren.flush(); } void UI::draw_container(const Rect& bound, Colour c) { /* todo line function lmao */ - mesh.add_rect( - this, + ren.add_rect( bound.x, bound.y, bound.w, bound.h, c ); - mesh.add_rect( - this, + ren.add_rect( bound.x, bound.y, bound.w, 1, 0x000000 ); - mesh.add_rect( - this, + ren.add_rect( bound.x + 1, bound.y + 1, bound.w - 1, 1, 0xa7a7a7 ); - mesh.add_rect( - this, + ren.add_rect( bound.x, bound.y, 1, bound.h, 0x000000 ); - mesh.add_rect( - this, + ren.add_rect( bound.x + 1, bound.y + 1, 1, bound.h - 1, 0xa7a7a7 ); - mesh.add_rect( - this, + ren.add_rect( bound.x + bound.w - 1, bound.y + 1, 1, bound.h - 2, 0x000000 ); - mesh.add_rect( - this, + ren.add_rect( bound.x + 1, bound.y + bound.h - 1, bound.w - 1, @@ -612,56 +542,49 @@ void UI::draw_container(const Rect& bound, Colour c) { } void UI::draw_containeri(const Rect& bound, Colour c) { - mesh.add_rect( - this, + ren.add_rect( bound.x, bound.y, bound.w, bound.h, c ); - mesh.add_rect( - this, + ren.add_rect( bound.x, bound.y, bound.w, 1, 0x000000 ); - mesh.add_rect( - this, + ren.add_rect( bound.x + 1, bound.y + bound.h - 2, bound.w - 2, 1, 0xa7a7a7 ); - mesh.add_rect( - this, + ren.add_rect( bound.x, bound.y, 1, bound.h, 0x000000 ); - mesh.add_rect( - this, + ren.add_rect( bound.x + bound.w - 2, bound.y + 1, 1, bound.h - 2, 0xa7a7a7 ); - mesh.add_rect( - this, + ren.add_rect( bound.x + bound.w - 1, bound.y + 1, 1, bound.h - 2, 0x000000 ); - mesh.add_rect( - this, + ren.add_rect( bound.x + 1, bound.y + bound.h - 1, bound.w - 1, @@ -728,12 +651,12 @@ void UI::Element::on_update() { void UI::Element::render() { Element* child; - Rect old_clip = ui->mesh.clip; + Rect old_clip = ui->ren.clip; on_render(); for (child = children; child; child = child->next) { child->render(); } - ui->mesh.clip = old_clip; + ui->ren.set_clip(old_clip); } void UI::Element::on_message(const Message& msg) { @@ -838,7 +761,7 @@ UI::Rect UI::Container::layout(const Rect& avail) { } void UI::Container::on_render() { - ui->mesh.set_clip(clip); + ui->ren.set_clip(clip); } UI::Table::Table(UI* ui, Element* parent, const int* rs): @@ -921,7 +844,7 @@ UI::Rect UI::Toolbar::layout(const Rect& avail) { } void UI::Toolbar::on_render() { - ui->mesh.set_clip(clip); + ui->ren.set_clip(clip); ui->draw_container( bound, 0xa7a7a7 @@ -958,9 +881,8 @@ void UI::Button::on_render() { ui->draw_containeri(bound, 0xffffff); } else ui->draw_container(bound, 0xffffff); - ui->mesh.set_clip(clip); - ui->mesh.add_text( - ui, + ui->ren.set_clip(clip); + ui->ren.add_text( bound.x + ui_padding + toff[0], bound.y + ui_padding + toff[1], text, @@ -1014,8 +936,7 @@ void UI::Input::on_render() { if ((n = c.x - (clip.x + clip.w)) > 0) tx -= n; c.x = tx + w; - ui->mesh.add_rect( - ui, + ui->ren.add_rect( c.x, c.y, c.w, @@ -1024,9 +945,8 @@ void UI::Input::on_render() { ); } if (buf) { - ui->mesh.set_clip(clip); - ui->mesh.add_text( - ui, + ui->ren.set_clip(clip); + ui->ren.add_text( tx, bound.y + ui_padding, buf, @@ -1155,16 +1075,14 @@ void UI::Slider::on_render() { hc = 0x4f4f4f; Rect sr = track_rect(); Rect ha = handle_rect(); - ui->mesh.add_rect( - ui, + ui->ren.add_rect( sr.x, sr.y, sr.w, sr.h, 0xa7a7a7 ); - ui->mesh.add_rect( - ui, + ui->ren.add_rect( ha.x, ha.y, ha.w, @@ -1271,9 +1189,8 @@ UI::Rect UI::Label::layout(const Rect& avail) { } void UI::Label::on_render() { - ui->mesh.set_clip(clip); - ui->mesh.add_text( - ui, + ui->ren.set_clip(clip); + ui->ren.add_text( bound.x + ui_padding, bound.y + ui_padding, text, @@ -1363,7 +1280,7 @@ UI::Rect UI::Modal::layout(const Rect& avail) { } void UI::Modal::on_render() { - ui->mesh.set_clip(clip); + ui->ren.set_clip(clip); ui->draw_container( contents->bound, Colour(0xffffff, 0x80) -- cgit v1.2.3-54-g00ecf