add support for scissoring views, fix text culling issues

This commit is contained in:
Matthew 2025-12-22 15:05:54 +11:00
parent 9950647cf9
commit bf1e8aaac2
8 changed files with 215 additions and 110 deletions

Binary file not shown.

Binary file not shown.

@ -1 +1 @@
Subproject commit b3639d9c884c09fb98b750ba613118d633470312
Subproject commit 11cf96b799c6ccee3c4f569e89c263d11c4d5ff8

View File

@ -192,12 +192,12 @@ Cycle(EditorCtx* ctx, Inputs* inputs)
Push!("bg_col", true)(ui_ctx, white);
Push!("text_col", true)(ui_ctx, black[0]);
Push!("padding", true)(ui_ctx, Vec2(8.0));
UIItem* text2 = MakeItem("A different line of text for testing purposes##text", UIF.DrawBackground|UIF.DrawText|UIF.TextWrap);
UIItem* text2 = MakeItem("A different line of text for testing purposes##text2", UIF.DrawBackground|UIF.DrawText|UIF.TextWrap);
Pop!("parent")(ui_ctx);
Pop!("parent")(ui_ctx);
Push!("size_info", true)(ui_ctx, MakeUISizeX(ST.Pixels, 2.0));
Push!("size_info", true)(ui_ctx, MakeUISizeX(ST.Pixels, 100.0));
Push!("bg_col", true)(ui_ctx, black);
UIItem* sep = MakeItem("###sep", UIF.Draggable|UIF.DrawBackground|UIF.ResizeAdjacent);

View File

@ -66,6 +66,7 @@ Vec2 g_padding_default = Vec2(0.0);
f32 g_text_scale_default = 1.0;
Vec2 g_scroll_target_default = Vec2(0.0);
Vec2 g_scroll_clamp_default = Vec2(0.0);
Vec2 g_view_offset_default = Vec2(0.0);
alias g_parent_default = g_UI_NIL;
const UI_COUNT = 5000;
@ -115,9 +116,12 @@ enum UIFlags
RightAlignText = 1<<14,
CenterAlignText = 1<<15, // todo
TextWrap = 1<<16,
PortalView = 1<<17, // Stencil mask this area, no bounds checking children within (for views to drag and view elements)
PortalViewX = 1<<17, // Stencil mask this area, no bounds checking children within (for views to drag and view elements)
PortalViewY = 1<<18,
Clamp = UIFlags.ClampX | UIFlags.ClampY,
Clamp = UIFlags.ClampX | UIFlags.ClampY,
PortalView = UIFlags.PortalViewX | UIFlags.PortalViewY,
Scroll = UIFlags.ScrollX | UIFlags.ScrollY,
}
alias UIF = UIFlags;
@ -242,8 +246,8 @@ struct UICtx
mixin UICtxParameter!(Vec4, "text_hl_col");
mixin UICtxParameter!(Vec2[2], "scroll_clamp");
mixin UICtxParameter!(Vec2, "scroll_target");
mixin UICtxParameter!(Vec2, "fixed_pos");
mixin UICtxParameter!(Vec2, "padding");
mixin UICtxParameter!(Vec2, "view_offset");
mixin UICtxParameter!(UIItem*, "parent");
mixin UICtxParameter!(Axis2D, "layout_axis");
mixin UICtxParameter!(f32, "corner_radius");
@ -316,6 +320,8 @@ struct UIItem
Vec2 scroll_offset;
mixin UIItemParameters!();
bool rendered;
}
enum SizeType
@ -342,6 +348,7 @@ struct UIBuffer
Vertex[] vtx;
u32[] idx;
u32 count;
u32 vtx_offset;
}
struct GlyphBounds
@ -493,21 +500,20 @@ InitUICtx(PlatformWindow* window)
ctx.desc_set = AllocDescSet(&ctx.rd, ctx.desc_set_layout);
ctx.pipeline_layout = CreatePipelineLayout(&ctx.rd, ctx.desc_set_layout, Mat4.sizeof);
Attribute[14] attributes = [
{ binding: 0, location: 0, format: FMT.RGBA_F32, offset: Vertex.cols.offsetof + Vec4.sizeof * 0},
{ binding: 0, location: 1, format: FMT.RGBA_F32, offset: Vertex.cols.offsetof + Vec4.sizeof * 1},
{ binding: 0, location: 2, format: FMT.RGBA_F32, offset: Vertex.cols.offsetof + Vec4.sizeof * 2},
{ binding: 0, location: 3, format: FMT.RGBA_F32, offset: Vertex.cols.offsetof + Vec4.sizeof * 3},
{ binding: 0, location: 4, format: FMT.RG_F32, offset: Vertex.dst_start.offsetof },
{ binding: 0, location: 5, format: FMT.RG_F32, offset: Vertex.dst_end.offsetof },
{ binding: 0, location: 6, format: FMT.RG_F32, offset: Vertex.src_start.offsetof },
{ binding: 0, location: 7, format: FMT.RG_F32, offset: Vertex.src_end.offsetof },
{ binding: 0, location: 8, format: FMT.R_F32, offset: Vertex.border_thickness.offsetof },
{ binding: 0, location: 9, format: FMT.R_F32, offset: Vertex.corner_radius.offsetof },
{ binding: 0, location: 10, format: FMT.R_F32, offset: Vertex.edge_softness.offsetof },
{ binding: 0, location: 11, format: FMT.R_F32, offset: Vertex.raised.offsetof },
{ binding: 0, location: 12, format: FMT.R_F32, offset: Vertex.z_index.offsetof },
{ binding: 0, location: 13, format: FMT.R_U32, offset: Vertex.texture.offsetof },
Attribute[13] attributes = [
{ binding: 0, location: 0, format: FMT.RGBA_F32, offset: Vertex.cols.offsetof + Vec4.sizeof * 0 },
{ binding: 0, location: 1, format: FMT.RGBA_F32, offset: Vertex.cols.offsetof + Vec4.sizeof * 1 },
{ binding: 0, location: 2, format: FMT.RGBA_F32, offset: Vertex.cols.offsetof + Vec4.sizeof * 2 },
{ binding: 0, location: 3, format: FMT.RGBA_F32, offset: Vertex.cols.offsetof + Vec4.sizeof * 3 },
{ binding: 0, location: 4, format: FMT.RG_F32, offset: Vertex.dst_start.offsetof },
{ binding: 0, location: 5, format: FMT.RG_F32, offset: Vertex.dst_end.offsetof },
{ binding: 0, location: 6, format: FMT.RG_F32, offset: Vertex.src_start.offsetof },
{ binding: 0, location: 7, format: FMT.RG_F32, offset: Vertex.src_end.offsetof },
{ binding: 0, location: 8, format: FMT.R_F32, offset: Vertex.border_thickness.offsetof } ,
{ binding: 0, location: 9, format: FMT.R_F32, offset: Vertex.corner_radius.offsetof },
{ binding: 0, location: 10, format: FMT.R_F32, offset: Vertex.edge_softness.offsetof },
{ binding: 0, location: 11, format: FMT.R_F32, offset: Vertex.raised.offsetof },
{ binding: 0, location: 12, format: FMT.R_U32, offset: Vertex.texture.offsetof },
];
GfxPipelineInfo ui_info = {
@ -683,6 +689,7 @@ InitSingleLine:
item.last_frame = ctx.frame;
item.padding += item.border_thickness;
item.rendered = false;
return item;
}
@ -748,6 +755,22 @@ PushUIEvent(UICtx* ctx, UIInput input)
DLLPush(&ctx.events, ev, null);
}
void
DrawUI(UICtx* ctx)
{
version(ENABLE_RENDERER)
{
UIBuffer* b = ctx.buffers.ptr + ctx.f_idx;
if(b.vtx_offset != b.count)
{
u32 count = b.count - b.vtx_offset;
BindBuffers(&ctx.rd, &b.m_idx, &b.m_vtx);
DrawIndexed(&ctx.rd, 6, count, 0, 0, b.vtx_offset);
b.vtx_offset += count;
}
}
}
void
BeginUI(Inputs* inputs)
{
@ -842,7 +865,8 @@ BeginUI(Inputs* inputs)
}
memset(ctx.buffers[ctx.f_idx].vtx.ptr, 0, Vertex.sizeof * ctx.buffers[ctx.f_idx].count);
ctx.buffers[ctx.f_idx].count = 0;
ctx.buffers[ctx.f_idx].count = 0;
ctx.buffers[ctx.f_idx].vtx_offset = 0;
// Root Item
UISize[2] sizes = [UISize(ST.Pixels, ctx.res.x), UISize(ST.Pixels, ctx.res.y)];
@ -989,6 +1013,30 @@ EndUI()
break;
}
}
if(excess > 0.0009)
{
for(UIItem* c = item.last; !Nil(c); c = c.prev)
{
f32 reduced = Min(excess, c.size[axis]);
excess -= reduced;
c.size.v[axis] -= reduced;
if(excess < 0.0009)
{
break;
}
}
assert(excess < 0.0009);
}
}
}
else
{
for(UIItem* c = item.first; !Nil(c); c = c.next)
{
c.size.v[axis] = c.size.v[axis] > size ? size : c.size.v[axis];
}
}
}
@ -1017,6 +1065,28 @@ EndUI()
assert(!isNaN(item.rect.p0.v[axis]));
assert(!isNaN(item.rect.p1.v[axis]));
debug
{
bool portal = cast(bool)(item.parent.flags & (UIF.PortalViewX << axis));
if(!portal && item != ctx.root)
{
UIItem* p = item.parent;
f32 i0 = item.rect.p0.v[axis];
f32 i1 = item.rect.p1.v[axis];
f32 p0 = item.parent.rect.p0.v[axis] + item.parent.border_thickness;
f32 p1 = item.parent.rect.p1.v[axis] - item.parent.border_thickness;
bool in_bounds_start = i0 >= p0;
bool in_bounds_end = i1 <= p1;
if(!in_bounds_start || !in_bounds_end)
{
Errf("[%s] failed bounds check, item bounds: [%s] parent bounds: [%s]", item.key.hash_text, [i0, i1], [p0, p1]);
}
assert(in_bounds_start && in_bounds_end);
}
}
next_pos = item.parent.layout_axis == axis ? end_pos : pos;
if(!Nil(item.first))
@ -1051,13 +1121,12 @@ EndUI()
RenderItems(ctx.root);
RenderItems(ctx.window_root);
version(ENABLE_RENDERER) with(ctx)
version(ENABLE_RENDERER)
{
BindBuffers(&rd, &buffers[f_idx].m_idx, &buffers[f_idx].m_vtx);
DrawIndexed(&rd, 6, buffers[f_idx].count, 0);
DrawUI(ctx);
FinishRendering(&rd);
SubmitAndPresent(&rd);
FinishRendering(&ctx.rd);
SubmitAndPresent(&ctx.rd);
}
if(!Nil(ctx.drag_item))
@ -1073,69 +1142,116 @@ EndUI()
}
}
void
RenderItem(UICtx* ctx, UIItem* item)
{
if(item.rendered) return;
// Doesn't really support nesting scissors, will maybe change this later.
bool scissor_x = cast(bool)(item.flags & UIF.PortalViewX);
bool scissor_y = cast(bool)(item.flags & UIF.PortalViewY);
if(scissor_x || scissor_y)
{
DrawUI(ctx);
u32 x = cast(u32)(scissor_x ? floor(item.rect.p0.x) : ctx.res.x);
u32 y = cast(u32)(scissor_y ? floor(item.rect.p0.y) : ctx.res.y);
u32 w = cast(u32)(scissor_x ? floor(item.rect.p1.x) - x : ctx.res.x);
u32 h = cast(u32)(scissor_y ? floor(item.rect.p1.y) - y : ctx.res.y);
SetScissor(&ctx.rd, x, y, w, h);
}
if(item.flags & UIF.DrawBackground)
{
// DrawRect
Vertex* v = GetVertex(ctx);
v.dst_start = item.rect.p0 + item.border_thickness;
v.dst_end = item.rect.p1 - item.border_thickness;
v.cols = item.bg_col;
v.corner_radius = item.corner_radius;
v.bounds = ItemBounds(item.parent);
AddVertexCount(ctx);
}
if(item.flags & UIF.DrawBorder)
{
Vertex* v = GetVertex(ctx);
v.dst_start = item.rect.p0;
v.dst_end = item.rect.p1;
v.cols = item.border_col;
v.corner_radius = item.corner_radius;
v.border_thickness = item.border_thickness;
v.edge_softness = item.edge_softness;
v.bounds = ItemBounds(item.parent);
AddVertexCount(ctx);
}
if(item.flags & UIF.DrawText || item.display_string)
{
if(item.flags & UIF.CenterAlignText)
{
// TODO
}
else
{
FontAtlas* atl = &ctx.atlas_buf.atlas;
f32 y_pos = item.rect.p0.y + item.padding.y;
foreach(i; 0 .. item.text_lines.length)
{
string str = item.text_lines[i];
f32 x_pos = item.flags & UIF.RightAlignText ? item.rect.p1.x - item.padding.x - CalcTextWidth(str) :
item.rect.p0.x + item.padding.x;
x_pos = clamp(x_pos, item.rect.p0.x, item.rect.p1.x);
foreach(j; 0 .. str.length)
{
u8 ch = str[j];
Glyph* g = ch < atl.glyphs.length ? atl.glyphs.ptr + ch : null;
DrawGlyph(item, g, &x_pos, y_pos);
}
y_pos += TEXT_SIZE;
}
}
}
if(scissor_x || scissor_y)
{
UIItem* first = item.first;
UIItem* last = item.last;
item.first = item.last = g_UI_NIL;
UIItem* next = Recurse!(true)(item, g_UI_NIL);
item.first = first;
item.last = last;
for(UIItem* d = item.first; !Nil(d) && d != next; d = Recurse!(true)(d, g_UI_NIL))
{
RenderItem(ctx, d);
d.rendered = true;
}
DrawUI(ctx);
ResetScissor(&ctx.rd);
}
}
void
RenderItems(UIItem* root)
{
UICtx* ctx = GetCtx();
for(UIItem* item = root; !Nil(item); item = Recurse!(true)(item, g_UI_NIL))
{
if(item.flags & UIF.DrawBackground)
{
// DrawRect
Vertex* v = GetVertex(ctx);
v.dst_start = item.rect.p0 + item.border_thickness;
v.dst_end = item.rect.p1 - item.border_thickness;
v.cols = item.bg_col;
v.corner_radius = item.corner_radius;
v.bounds = ItemBounds(item.parent);
AddVertexCount(ctx);
}
if(item.flags & UIF.DrawBorder)
{
Vertex* v = GetVertex(ctx);
v.dst_start = item.rect.p0;
v.dst_end = item.rect.p1;
v.cols = item.border_col;
v.corner_radius = item.corner_radius;
v.border_thickness = item.border_thickness;
v.edge_softness = item.edge_softness;
v.bounds = ItemBounds(item.parent);
AddVertexCount(ctx);
}
if(item.flags & UIF.DrawText || item.display_string)
{
if(item.flags & UIF.CenterAlignText)
{
// TODO
}
else
{
FontAtlas* atl = &ctx.atlas_buf.atlas;
f32 y_pos = item.rect.p0.y + item.padding.y;
foreach(i; 0 .. item.text_lines.length)
{
string str = item.text_lines[i];
f32 x_pos = item.flags & UIF.RightAlignText ? item.rect.p1.x - item.padding.x - CalcTextWidth(str) :
item.rect.p0.x + item.padding.x;
foreach(j; 0 .. str.length)
{
u8 ch = str[j];
Glyph* g = ch < atl.glyphs.length ? atl.glyphs.ptr + ch : null;
DrawGlyph(item, g, &x_pos, y_pos);
}
y_pos += TEXT_SIZE;
}
}
}
RenderItem(ctx, item);
}
}
@ -1568,17 +1684,10 @@ DrawGlyph(UIItem* item, Glyph* glyph, f32* x_pos, f32 y)
v.src_end = Vec2(gb.atlas_r, gb.atlas_b);
}
/*
f32 end_x = *x_pos + advance;
f32 width = end_x - *x_pos;
f32 bound_x = item.flags & UIF.RightAlignText ? item.rect.p0.x : item.rect.p1.x;
if(item.flags & UIF.RightAlignText && bound_x > *x_pos)
{
f32 cull_pct = (bound_x - *x_pos) / width;
v.dst_start.x += (v.dst_end.x - v.dst_start.x) * cull_pct;
v.src_start.x += (v.src_end.x - v.src_start.x) * cull_pct;
}
else if(!(item.flags & UIF.RightAlignText) && end_x > bound_x)
f32 bound_x = item.rect.p1.x;
if(end_x > bound_x)
{
f32 cull_pct = (end_x - bound_x) / width;
v.dst_end.x -= (v.dst_end.x - v.dst_start.x) * cull_pct;
@ -1601,7 +1710,6 @@ DrawGlyph(UIItem* item, Glyph* glyph, f32* x_pos, f32 y)
v.pos[i].end[axis] = v.pos[i].start.v[axis] > v.pos[i].end.v[axis] ? v.pos[i].start.v[axis] : v.pos[i].end.v[axis];
}
}
*/
AddVertexCount(ctx);

View File

@ -23,7 +23,7 @@ EditorView(Editor* ed)
UIItem* editor = Get(ed_key);
u64 frame_line_offset = ed.line_offset;
u64 frame_view_offset = editor.scroll_offset%TEXT_SIZE;
f32 frame_view_offset = editor.scroll_offset.y%TEXT_SIZE;
Push!("border_col" )(ctx, Vec4Arr(Vec4(1.0)));
@ -72,8 +72,8 @@ EditorView(Editor* ed)
Push!("parent" )(ctx, line_count);
Push!("text_col" )(ctx, Vec4(1.0));
u64 i = prev_offset;
for(LineBuffer* lb = GetLine(&ed.buf, i); i < prev_offset+view_lines; i += 1)
u64 i = frame_line_offset;
for(LineBuffer* lb = GetLine(&ed.buf, i); i < frame_line_offset+view_lines; i += 1)
{
}

View File

@ -14,8 +14,6 @@ layout (location = 1) in struct FragDataIn {
vec2 dst_center;
vec2 dst_half_size;
vec2 sdf_sample_pos;
vec2 bounds_p0;
vec2 bounds_p1;
float corner_radius;
float softness;
float raised;

View File

@ -8,16 +8,15 @@ layout (location = 0) in vec4 in_col_1;
layout (location = 1) in vec4 in_col_2;
layout (location = 2) in vec4 in_col_3;
layout (location = 3) in vec4 in_col_4;
layout (location = 4) in vec4 in_bounds;
layout (location = 5) in vec2 in_dst_start;
layout (location = 6) in vec2 in_dst_end;
layout (location = 7) in vec2 in_src_start;
layout (location = 8) in vec2 in_src_end;
layout (location = 9) in float border_thickness;
layout (location = 10) in float corner_radius;
layout (location = 11) in float edge_softness;
layout (location = 12) in float raised;
layout (location = 13) in uint in_has_texture;
layout (location = 4) in vec2 in_dst_start;
layout (location = 5) in vec2 in_dst_end;
layout (location = 6) in vec2 in_src_start;
layout (location = 7) in vec2 in_src_end;
layout (location = 8) in float border_thickness;
layout (location = 9) in float corner_radius;
layout (location = 10) in float edge_softness;
layout (location = 11) in float raised;
layout (location = 12) in uint in_has_texture;
layout (location = 0) flat out uint out_has_texture;
@ -91,6 +90,6 @@ void main()
FragData.sdf_sample_pos = (2.0f * dst_verts_pct - 1.0f) * half_size;
out_has_texture = in_has_texture;
vec4 v_pos = PC.projection * vec4(pos.x, pos.y, z_index, 1);
vec4 v_pos = PC.projection * vec4(pos.x, pos.y, 0, 1);
gl_Position = vec4(v_pos.x, v_pos.y, v_pos.z, 1);
}