Removed layers, moved z to element metadata, made pools resize on demand instead of static size

main
noah metz 2026-07-27 11:21:18 -06:00
parent 7981274c42
commit 9ecce26d18
13 changed files with 1246 additions and 656 deletions

@ -23,13 +23,24 @@
#define DRAWABLE_TYPE_GLYPH 1
#define DRAWABLE_TYPE_IMAGE 2
#define DRAWABLE_TYPE_RECT_HSV 3
// Tombstone: excluded from the index list; the vertex shader also emits a
// degenerate quad for it since glyph scratch slots are tombstoned GPU-side
#define DRAWABLE_TYPE_NONE 0xFFFFFFFF
// GPUString.scratch value for strings with no glyph slot reservation
// (dead slots and max_length == 0); string.comp skips them
#define STRING_SCRATCH_NONE 0xFFFFFFFF
#define UI_EVENT_CURSOR (1 << 0)
#define UI_EVENT_SCROLL (1 << 1)
#define UI_EVENT_BUTTON (1 << 2)
#define CONTAINER_MIN_DRAWABLES 16
#define CONTAINER_MIN_STRINGS 4
#define CONTAINER_MIN_CODES 64
typedef struct UIPushConstantStruct {
VkDeviceAddress layer;
VkDeviceAddress container;
float time;
uint32_t pad;
} UIPushConstant;
@ -72,6 +83,8 @@ typedef struct TextureStruct {
char* path;
} Texture;
// Layout mirrors std430 in ui_common.glsl; cglm vec4 is 16-byte aligned so
// C offsets match. z and scratch fill what used to be tail padding.
typedef struct GPUStringStruct {
vec2 pos;
vec4 color;
@ -79,6 +92,8 @@ typedef struct GPUStringStruct {
uint32_t length;
uint32_t font;
uint32_t max_length;
float z;
uint32_t scratch;
VkDeviceAddress codes;
} GPUString;
@ -89,79 +104,79 @@ typedef struct GPUDrawableStruct {
uint32_t type;
uint32_t var;
uint32_t events;
float z;
} GPUDrawable;
typedef struct GPULayerStruct {
typedef struct GPUContainerStruct {
DrawCommand draw;
DispatchCommand dispatch_strings;
uint32_t anchor;
uint32_t max_drawables;
uint32_t max_strings;
uint32_t num_drawables;
vec2 offset;
vec2 size;
VkDeviceAddress strings;
VkDeviceAddress drawables;
VkDeviceAddress container;
} GPULayer;
VkDeviceAddress indices;
VkDeviceAddress context;
} GPUContainer;
typedef struct StringResourcesStruct {
VkBuffer codes[MAX_FRAMES_IN_FLIGHT];
VmaAllocation codes_memory[MAX_FRAMES_IN_FLIGHT];
uint32_t* codes_buffer;
uint32_t max_codes;
} StringResources;
typedef struct LayerStruct {
typedef struct UIContextStruct UIContext;
typedef struct ContainerStruct Container;
struct ContainerStruct {
VkBuffer container[MAX_FRAMES_IN_FLIGHT];
VkBuffer strings[MAX_FRAMES_IN_FLIGHT];
VkBuffer drawables[MAX_FRAMES_IN_FLIGHT];
VkBuffer layer[MAX_FRAMES_IN_FLIGHT];
VkBuffer indices[MAX_FRAMES_IN_FLIGHT];
VmaAllocation container_memory[MAX_FRAMES_IN_FLIGHT];
VmaAllocation strings_memory[MAX_FRAMES_IN_FLIGHT];
VmaAllocation drawables_memory[MAX_FRAMES_IN_FLIGHT];
VmaAllocation layer_memory[MAX_FRAMES_IN_FLIGHT];
VmaAllocation indices_memory[MAX_FRAMES_IN_FLIGHT];
VkDeviceAddress address[MAX_FRAMES_IN_FLIGHT];
GPUContainer data;
// CPU mirrors; drawable pool slots [cap_drawables, cap_drawables+cap_codes)
// are glyph scratch written by string.comp and have no CPU mirror entries
GPUDrawable* drawables_buffer;
GPUString* strings_buffer;
StringResources* string_resources;
uint32_t* indices_buffer;
GPULayer data;
} Layer;
uint32_t cap_drawables;
uint32_t cap_codes;
uint32_t cap_strings;
typedef struct LayerInputStruct {
uint32_t num_strings;
uint32_t num_drawables;
uint32_t num_strings;
uint32_t scratch_used;
uint32_t max_strings;
uint32_t max_drawables;
GPUString* strings;
GPUDrawable* drawables;
} LayerInput;
typedef struct GPUContainerStruct {
vec2 offset;
vec2 size;
uint32_t anchor;
VkDeviceAddress context;
} GPUContainer;
typedef struct UIContextStruct UIContext;
typedef struct ContainerStruct Container;
struct ContainerStruct {
VkBuffer container[MAX_FRAMES_IN_FLIGHT];
uint8_t* drawable_live;
uint8_t* string_live;
VmaAllocation container_memory[MAX_FRAMES_IN_FLIGHT];
// Generations invalidate stale Lua handles after slot reuse; refs keep each
// slot's handle userdata alive so event dispatch can pass the same object
uint32_t* drawable_generation;
uint32_t* string_generation;
int* drawable_ref;
int* string_ref;
VkDeviceAddress address[MAX_FRAMES_IN_FLIGHT];
GPUContainer data;
uint32_t* free_drawables;
uint32_t free_drawable_count;
uint32_t* free_strings;
uint32_t free_string_count;
uint32_t id;
uint32_t layer_count;
Layer* layers;
int script_env; // Lua registry ref for this container's environment table, 0 = no script
char* script_path;
@ -173,10 +188,7 @@ typedef struct ContainerInputStruct {
vec2 offset;
vec2 size;
uint32_t layer_count;
LayerInput* layers;
const char* script_path; // path to .lua file, NULL for containers with no script
const char* script_path; // path to the .lua file declaring the container's elements
} ContainerInput;
typedef struct GPUUIContextStruct {
@ -217,6 +229,11 @@ struct UIContextStruct {
uint32_t max_containers;
Container* containers;
// Container slot indices in draw order (back to front). Loading appends,
// so new containers spawn on top.
uint32_t* container_order;
uint32_t container_order_count;
GPUUIContext data;
FT_Library freetype;
@ -224,7 +241,6 @@ struct UIContextStruct {
lua_State* lua;
Container* active_container;
uint32_t active_layer;
uint32_t active_element;
double cursor[2];
@ -261,11 +277,43 @@ VkResult unload_container(
RenderContext* gpu,
UIContext* context);
VkResult create_layer(
uint32_t index,
LayerInput* input,
void ui_container_to_front(uint32_t id, UIContext* ui);
// Runtime element management. Slots are stable for the element's lifetime;
// destroyed slots are recycled by later creates.
VkResult ui_create_drawable(
Container* container,
const GPUDrawable* drawable,
RenderContext* gpu,
Container* container);
uint32_t* slot);
VkResult ui_destroy_drawable(
Container* container,
uint32_t slot,
RenderContext* gpu);
VkResult ui_create_string(
Container* container,
const GPUString* string,
RenderContext* gpu,
uint32_t* slot);
VkResult ui_destroy_string(
Container* container,
uint32_t slot,
RenderContext* gpu);
VkResult ui_set_drawable_z(
Container* container,
uint32_t slot,
float z,
RenderContext* gpu);
VkResult ui_set_string_z(
Container* container,
uint32_t slot,
float z,
RenderContext* gpu);
VkResult map_string(
const char* text,
@ -280,7 +328,6 @@ VkResult update_ui_context_resolution(
VkResult update_ui_string(
const char* string,
Container* container,
uint32_t layer_index,
uint32_t string_index,
UIContext* ui,
RenderContext* gpu);
@ -293,11 +340,10 @@ bool ui_intersect(
UIContext* ui,
uint32_t events,
uint32_t* container,
uint32_t* layer,
uint32_t* element,
vec2 position);
void set_active_element(uint32_t container, uint32_t layer, uint32_t element, UIContext* ui);
void set_active_element(uint32_t container, uint32_t element, UIContext* ui);
void clear_active_element(UIContext* ui, RenderContext* gpu);

@ -14,13 +14,20 @@ VkResult ui_lua_load_container(
RenderContext* gpu,
const char* path);
// Calls a global function in the container's script with one string argument
VkResult ui_lua_call(
UIContext* ui,
RenderContext* gpu,
Container* c,
const char* function,
const char* argument);
// Called by the engine input path. Each returns true if the container's script
// defined the handler and it returned a truthy value (event consumed).
bool ui_lua_dispatch_button(
UIContext* ui,
RenderContext* gpu,
Container* c,
uint32_t layer,
uint32_t element,
float x,
float y,
@ -32,7 +39,6 @@ bool ui_lua_dispatch_cursor(
UIContext* ui,
RenderContext* gpu,
Container* c,
uint32_t layer,
uint32_t element,
float x,
float y);
@ -41,7 +47,6 @@ bool ui_lua_dispatch_scroll(
UIContext* ui,
RenderContext* gpu,
Container* c,
uint32_t layer,
uint32_t element,
double x,
double y);
@ -50,7 +55,6 @@ bool ui_lua_dispatch_key(
UIContext* ui,
RenderContext* gpu,
Container* c,
uint32_t layer,
uint32_t element,
int key,
int action,
@ -60,7 +64,6 @@ bool ui_lua_dispatch_text(
UIContext* ui,
RenderContext* gpu,
Container* c,
uint32_t layer,
uint32_t element,
unsigned int codepoint);
@ -68,7 +71,6 @@ bool ui_lua_dispatch_deselect(
UIContext* ui,
RenderContext* gpu,
Container* c,
uint32_t layer,
uint32_t element);
#endif

@ -1,14 +1,83 @@
-- Color picker logic, ported from the C callbacks that used to live in editor.c
--
-- Layer 0 elements:
-- 0 background
-- 1 sv square (HSV rect, hue in color channel 0)
-- 2 hue bar (HSV rect)
-- 3 sv select outline
-- 4 sv select (HSV rect)
-- 5 hex string input area
-- 6 hue select bar
-- 7-18 saved color slots
-- Color picker: element declarations and interaction logic.
-- z controls stacking within the container; equal z means don't overlap.
local bg = ui.rect{
pos = {0, 0},
size = {190, 150},
color = {0.4, 0.4, 0.4, 0.8},
z = 0,
}
local sv_square = ui.rect{
type = RECT_HSV,
pos = {2, 2},
size = {130, 130},
colors = {{0, 0, 1, 1}, {0, 1, 1, 1}, {0, 0, 0, 1}, {0, 1, 0, 1}},
events = EVENT_BUTTON,
z = 1,
}
local hue_bar = ui.rect{
type = RECT_HSV,
pos = {134, 2},
size = {10, 130},
colors = {{0, 1, 1, 1}, {0, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}},
events = EVENT_BUTTON + EVENT_SCROLL,
z = 1,
}
local sv_outline = ui.rect{
pos = {130-4, 130-4},
size = {7, 7},
color = {0, 0, 0, 1},
z = 2,
}
local sv_select = ui.rect{
type = RECT_HSV,
pos = {130-3, 130-3},
size = {5, 5},
color = {1, 0, 0, 1},
z = 3,
}
local hex_area = ui.rect{
pos = {20, 134},
size = {95, 15},
events = EVENT_BUTTON + EVENT_CURSOR,
z = 1,
}
local hue_select = ui.rect{
pos = {134, 2},
size = {10, 1},
color = {0, 0, 0, 1},
z = 2,
}
local hex_text = ui.text{
pos = {2, 150},
size = 16,
color = {1, 1, 1, 1},
font = 0,
max_length = 16,
z = 2,
}
-- 12 saved color slots in a 2-wide grid
local slots = {}
local slot_index = {}
for i = 1, 12 do
local slot = ui.rect{
pos = {146 + ((i-1) % 2)*22, 2 + math.floor((i-1)/2)*22},
size = {20, 20},
color = {0, 0, 0, 1},
events = EVENT_BUTTON,
z = 1,
}
slots[i] = slot
slot_index[slot] = i
end
local state = {
hsv = {0, 0, 0},
@ -26,7 +95,7 @@ local function update_hex_string()
math.floor(state.rgb[2]*255 + 0.5),
math.floor(state.rgb[3]*255 + 0.5),
math.floor(state.rgb[4]*255 + 0.5))
ui.set_string(0, 0, state.hex_string)
hex_text:set_text(state.hex_string)
end
local function sync_rgb_from_hsv()
@ -41,11 +110,11 @@ local function sv_pick(s, v)
state.hsv[2] = s
state.hsv[3] = v
ui.set_pos(0, 4, s*130 - 2, 130 - v*130 - 2)
sv_select:set_pos(s*130 - 2, 130 - v*130 - 2)
for corner = 0, 3 do
ui.set_corner_color(0, 4, corner, state.hsv[1], s, v, 1)
sv_select:set_corner_color(corner, state.hsv[1], s, v, 1)
end
ui.set_pos(0, 3, s*130 - 3, 130 - v*130 - 3)
sv_outline:set_pos(s*130 - 3, 130 - v*130 - 3)
sync_rgb_from_hsv()
end
@ -54,30 +123,30 @@ local function hue_set(h)
h = clamp01(h)
state.hsv[1] = h
for _, drawable in ipairs({1, 4}) do
for _, element in ipairs({sv_square, sv_select}) do
for corner = 0, 3 do
local _, s, v, a = ui.get_corner_color(0, drawable, corner)
ui.set_corner_color(0, drawable, corner, h, s, v, a)
local _, s, v, a = element:corner_color(corner)
element:set_corner_color(corner, h, s, v, a)
end
end
ui.set_pos(0, 6, 134, 2 + h*130)
hue_select:set_pos(134, 2 + h*130)
sync_rgb_from_hsv()
end
local function hex_string_highlight(value)
for corner = 0, 3 do
local r, g, _, _ = ui.get_corner_color(0, 5, corner)
ui.set_corner_color(0, 5, corner, r, g, value, value)
local r, g, _, _ = hex_area:corner_color(corner)
hex_area:set_corner_color(corner, r, g, value, value)
end
end
local function set_saved(index, color)
state.saved[index] = {color[1], color[2], color[3], color[4]}
local element = 6 + index
local slot = slots[index]
for corner = 0, 3 do
local _, _, _, a = ui.get_corner_color(0, element, corner)
ui.set_corner_color(0, element, corner, color[1], color[2], color[3], a)
local _, _, _, a = slot:corner_color(corner)
slot:set_corner_color(corner, color[1], color[2], color[3], a)
end
end
@ -90,29 +159,29 @@ local function apply_state_rgb()
end
local drag = {
[1] = function(x, y) sv_pick(x, 1 - y) end,
[2] = function(x, y) hue_set(y) end,
[sv_square] = function(x, y) sv_pick(x, 1 - y) end,
[hue_bar] = function(x, y) hue_set(y) end,
}
function on_button(layer, element, x, y, button, action, mods)
function on_button(element, x, y, button, action, mods)
if drag[element] then
if action == PRESS and button == MOUSE_LEFT then
ui.focus(layer, element)
element:focus()
drag[element](x, y)
elseif action == RELEASE and button == MOUSE_LEFT then
ui.blur()
end
return true
elseif element == 5 then -- hex string input
elseif element == hex_area then
if action == PRESS and button == MOUSE_LEFT then
ui.focus(layer, element)
element:focus()
hex_string_highlight(1)
end
return true
elseif element >= 7 and element <= 18 then -- saved color slots
local index = element - 6
elseif slot_index[element] then
local index = slot_index[element]
if action == PRESS then
if button == MOUSE_LEFT then
local saved = state.saved[index]
@ -130,9 +199,9 @@ function on_button(layer, element, x, y, button, action, mods)
return false
end
function on_cursor(layer, element, x, y)
function on_cursor(element, x, y)
for el, fn in pairs(drag) do
if ui.is_focused(0, el) then
if el:is_focused() then
fn(x, y)
return true
end
@ -140,16 +209,16 @@ function on_cursor(layer, element, x, y)
return false
end
function on_scroll(layer, element, x, y)
if element == 2 then -- hue bar
function on_scroll(element, x, y)
if element == hue_bar then
hue_set(state.hsv[1] + y*0.01)
return true
end
return false
end
function on_key(layer, element, key, action, mods)
if element ~= 5 then return false end
function on_key(element, key, action, mods)
if element ~= hex_area then return false end
if action == PRESS then
if key == KEY_ESCAPE then
@ -165,7 +234,7 @@ function on_key(layer, element, key, action, mods)
elseif key == KEY_BACKSPACE then
if #state.hex_string > 1 then
state.hex_string = state.hex_string:sub(1, -2)
ui.set_string(0, 0, state.hex_string)
hex_text:set_text(state.hex_string)
end
end
end
@ -173,24 +242,24 @@ function on_key(layer, element, key, action, mods)
return true
end
function on_text(layer, element, codepoint)
if element ~= 5 then return false end
function on_text(element, codepoint)
if element ~= hex_area then return false end
if codepoint < 128 then
local ch = string.char(codepoint):upper()
if ch:match("[0-9A-F]") and #state.hex_string < 9 then
state.hex_string = state.hex_string .. ch
ui.set_string(0, 0, state.hex_string)
hex_text:set_text(state.hex_string)
end
end
return true
end
function on_deselect(layer, element)
if element == 5 then
function on_deselect(element)
if element == hex_area then
hex_string_highlight(0)
end
end
-- runs once at container load
ui.set_string(0, 0, state.hex_string)
hex_text:set_text(state.hex_string)

@ -0,0 +1,16 @@
-- Mode indicator label; the editor calls set_mode() on mode changes
local label = ui.text{
pos = {0, 32},
size = 32,
color = {1, 1, 1, 1},
font = 0,
max_length = 8,
z = 0,
}
function set_mode(name)
label:set_text(name)
end
set_mode("None")

@ -7,23 +7,34 @@ layout(local_size_x = 1) in;
void main() {
uint gID = gl_GlobalInvocationID.x;
String string = pc.layer.strings.s[gID];
Font font = pc.layer.container.context.fonts.f[string.font];
String string = pc.container.strings.s[gID];
uint buffer_pos = atomicAdd(pc.layer.draw.instance_count, string.len);
// Dead slot or no glyph reservation
if(string.scratch == 0xFFFFFFFF) {
return;
}
Font font = pc.container.context.fonts.f[string.font];
vec2 pen = vec2(0.0, 0.0);
for(uint i = 0; i < string.len; i++) {
uint code = (string.codes.c[i/4] >> (i % 4)*8) & 0xFF;
Symbol symbol = font.symbols.s[code];
pc.layer.drawables.d[buffer_pos + i].pos = string.pos + pen;
uint slot = string.scratch + i;
pc.container.drawables.d[slot].pos = string.pos + pen;
pen += string.size*symbol.advance;
pc.layer.drawables.d[buffer_pos + i].size = vec2(string.size, string.size);
pc.layer.drawables.d[buffer_pos + i].color[0] = string.color;
pc.layer.drawables.d[buffer_pos + i].color[1] = string.color;
pc.layer.drawables.d[buffer_pos + i].color[2] = string.color;
pc.layer.drawables.d[buffer_pos + i].color[3] = string.color;
pc.layer.drawables.d[buffer_pos + i].var = code + (string.font << 16);
pc.layer.drawables.d[buffer_pos + i].type = 1;
pc.container.drawables.d[slot].size = vec2(string.size, string.size);
pc.container.drawables.d[slot].color[0] = string.color;
pc.container.drawables.d[slot].color[1] = string.color;
pc.container.drawables.d[slot].color[2] = string.color;
pc.container.drawables.d[slot].color[3] = string.color;
pc.container.drawables.d[slot].var = code + (string.font << 16);
pc.container.drawables.d[slot].type = 1;
}
// Reserved-but-unused slots stay in the index list; tombstone them so
// shrinking text doesn't leave stale quads
for(uint i = string.len; i < string.max_len; i++) {
pc.container.drawables.d[string.scratch + i].type = 0xFFFFFFFF;
}
}

@ -43,9 +43,9 @@ vec4 hsv_to_rgb(vec4 hsv) {
}
void main() {
vec2 pos = (gl_FragCoord.xy - vec2(0.5, 0.5))/pc.layer.container.context.scale;
vec2 pos = (gl_FragCoord.xy - vec2(0.5, 0.5))/pc.container.context.scale;
vec2 min = container_offset;
vec2 max = min + pc.layer.container.size;
vec2 max = min + pc.container.size;
if(pos.x < min.x || pos.y < min.y
|| pos.x > max.x || pos.y > max.y) {
discard;

@ -24,33 +24,41 @@ const vec2 square[6] = {
};
void main() {
Drawable drawable = pc.layer.drawables.d[gl_InstanceIndex];
uint slot = pc.container.indices.i[gl_InstanceIndex];
Drawable drawable = pc.container.drawables.d[slot];
// Tombstoned glyph scratch slot: emit a degenerate quad
if(drawable.type == 0xFFFFFFFF) {
gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
return;
}
vec2 pos = square[gl_VertexIndex];
// Top Left
if(pc.layer.container.anchor == 0) {
container_offset = pc.layer.container.offset;
if(pc.container.anchor == 0) {
container_offset = pc.container.offset;
}
// Top Right
if(pc.layer.container.anchor == 1) {
container_offset = pc.layer.container.offset + vec2(pc.layer.container.context.extent.x - pc.layer.container.size.x, 0);
if(pc.container.anchor == 1) {
container_offset = pc.container.offset + vec2(pc.container.context.extent.x - pc.container.size.x, 0);
}
// Bottom Left
if(pc.layer.container.anchor == 2) {
container_offset = pc.layer.container.offset + vec2(0, pc.layer.container.context.extent.y - pc.layer.container.size.y);
if(pc.container.anchor == 2) {
container_offset = pc.container.offset + vec2(0, pc.container.context.extent.y - pc.container.size.y);
}
// Bottom Right
if(pc.layer.container.anchor == 3) {
container_offset = pc.layer.container.offset + vec2(pc.layer.container.context.extent.x - pc.layer.container.size.x, pc.layer.container.context.extent.y - pc.layer.container.size.y);
if(pc.container.anchor == 3) {
container_offset = pc.container.offset + vec2(pc.container.context.extent.x - pc.container.size.x, pc.container.context.extent.y - pc.container.size.y);
}
if(drawable.type == 1) {
index = (drawable.var >> 16) & 0xFFFF;
glyph = drawable.var & 0xFFFF;
Font font = pc.layer.container.context.fonts.f[index];
Font font = pc.container.context.fonts.f[index];
Symbol symbol = font.symbols.s[glyph];
fragUV = pos * vec2(symbol.width, symbol.height);
@ -61,9 +69,8 @@ void main() {
}
gl_Position = vec4((pos * drawable.size + drawable.pos + container_offset) * pc.layer.container.context.screen * 2, 0.0, 1.0) - vec4(1.0, 1.0, 0.0, 0.0);
gl_Position = vec4((pos * drawable.size + drawable.pos + container_offset) * pc.container.context.screen * 2, 0.0, 1.0) - vec4(1.0, 1.0, 0.0, 0.0);
fragColor = drawable.color[color_index[gl_VertexIndex]];
type = drawable.type;
}

@ -49,6 +49,8 @@ struct String {
uint len;
uint font;
uint max_len;
float z;
uint scratch;
CodeList codes;
};
@ -63,12 +65,17 @@ struct Drawable {
uint type;
uint var;
uint events;
float z;
};
layout(std430, buffer_reference) readonly buffer DrawableList {
layout(std430, buffer_reference) buffer DrawableList {
Drawable d[];
};
layout(std430, buffer_reference) readonly buffer IndexList {
uint i[];
};
layout(std430, buffer_reference) buffer Context {
FontList fonts;
vec2 screen;
@ -77,28 +84,21 @@ layout(std430, buffer_reference) buffer Context {
};
layout(std430, buffer_reference) readonly buffer Container {
vec2 offset;
vec2 size;
uint anchor;
Context context;
};
layout(std430, buffer_reference) readonly buffer Layer {
DrawCommand draw;
DispatchCommand dispatch;
uint anchor;
uint max_drawables;
uint max_strings;
uint num_drawables;
vec2 offset;
vec2 size;
StringList strings;
DrawableList drawables;
Container container;
IndexList indices;
Context context;
};
layout(std430, push_constant) uniform PushConstant {
Layer layer;
Container container;
float time;
uint frame;
} pc;

@ -12,12 +12,11 @@ void record_ui_draw(VkCommandBuffer command_buffer, UIContext* ui_context, doubl
vkCmdBindDescriptorSets(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, ui_context->pipeline.layout, 1, 1, &ui_context->font_textures, 0, NULL);
vkCmdBindDescriptorSets(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, ui_context->pipeline.layout, 2, 1, &ui_context->samplers, 0, NULL);
vkCmdBindDescriptorSets(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, ui_context->pipeline.layout, 3, 1, &ui_context->textures, 0, NULL);
for(uint32_t i = 0; i < ui_context->max_containers; i++) {
for(uint32_t j = 0; j < ui_context->containers[i].layer_count; j++) {
push.layer = ui_context->containers[i].layers[j].address[frame];
for(uint32_t o = 0; o < ui_context->container_order_count; o++) {
Container* c = &ui_context->containers[ui_context->container_order[o]];
push.container = c->address[frame];
vkCmdPushConstants(command_buffer, ui_context->pipeline.layout, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, 16, &push);
vkCmdDrawIndirect(command_buffer, ui_context->containers[i].layers[j].layer[frame], offsetof(GPULayer, draw), 1, 0);
}
vkCmdDrawIndirect(command_buffer, c->container[frame], offsetof(GPUContainer, draw), 1, 0);
}
}
@ -45,29 +44,17 @@ void record_hex_draw(VkCommandBuffer command_buffer, HexContext* hex, double tim
void record_ui_compute(VkCommandBuffer command_buffer, UIContext* ui, uint32_t frame) {
UIPushConstant push = {
.time = 0.0,
.layer = 0,
.container = 0,
.pad = frame,
};
vkCmdBindPipeline(command_buffer, VK_PIPELINE_BIND_POINT_COMPUTE, ui->string_pipeline.pipeline);
for(uint32_t i = 0; i < ui->max_containers; i++) {
if(ui->containers[i].id != 0x00000000) {
for(uint32_t j = 0; j < ui->containers[i].layer_count; j++) {
command_copy_buffer(command_buffer, ui->containers[i].layers[j].layer[frame], ui->containers[i].layers[j].layer[frame], offsetof(GPULayer, num_drawables), offsetof(GPULayer, draw) + offsetof(DrawCommand, instance_count), sizeof(uint32_t));
}
}
}
for(uint32_t i = 0; i < ui->max_containers; i++) {
if(ui->containers[i].id != 0x00000000) {
for(uint32_t j = 0; j < ui->containers[i].layer_count; j++) {
push.layer = ui->containers[i].layers[j].address[frame];
for(uint32_t o = 0; o < ui->container_order_count; o++) {
Container* c = &ui->containers[ui->container_order[o]];
push.container = c->address[frame];
vkCmdPushConstants(command_buffer, ui->string_pipeline.layout, VK_SHADER_STAGE_COMPUTE_BIT, 0, 16, &push);
vkCmdDispatchIndirect(command_buffer, ui->containers[i].layers[j].layer[frame], offsetof(GPULayer, dispatch_strings));
}
vkCmdDispatchIndirect(command_buffer, c->container[frame], offsetof(GPUContainer, dispatch_strings));
}
}
}
VkResult draw_frame(

@ -1,6 +1,7 @@
#include <stdio.h>
#include "hex.h"
#include "engine.h"
#include "ui_lua.h"
#include "vulkan/vulkan_core.h"
#include <math.h>
@ -120,147 +121,12 @@ uint32_t add_hex_region(ClientContext* context) {
return i;
}
// TODO: Make load from current state instead of having a default state
VkResult color_ui(ClientContext* context) {
if(context_container(COLOR_PICK_CONTAINER_ID, &context->ui) != NULL) {
return VK_SUCCESS;
}
GPUString strings[] = {
{
.pos = {2, 150},
.color = {1, 1, 1, 1},
.size = 16,
.font = 0,
.max_length = 16,
},
};
GPUDrawable drawables[] = {
{
.pos = {0, 0},
.size = {190, 150},
.color = {{0.4, 0.4, 0.4, 0.8}, {0.4, 0.4, 0.4, 0.8}, {0.4, 0.4, 0.4, 0.8}, {0.4, 0.4, 0.4, 0.8}},
},
{
.type = DRAWABLE_TYPE_RECT_HSV,
.pos = {2, 2},
.size = {130, 130},
.color = {{0, 0, 1, 1}, {0, 1, 1, 1}, {0, 0, 0, 1}, {0, 1, 0, 1}},
.events = UI_EVENT_BUTTON,
},
{
.type = DRAWABLE_TYPE_RECT_HSV,
.pos = {134, 2},
.size = {10, 130},
.color = {{0, 1, 1, 1}, {0, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}},
.events = UI_EVENT_BUTTON | UI_EVENT_SCROLL,
},
{
.pos = {130-4, 130-4},
.size = {7, 7},
.color = {{0, 0, 0, 1}, {0, 0, 0, 1}, {0, 0, 0, 1}, {0, 0, 0, 1}},
},
{
.type = DRAWABLE_TYPE_RECT_HSV,
.pos = {130-3, 130-3},
.size = {5, 5},
.color = {{1, 0, 0, 1}, {1, 0, 0, 1}, {1, 0, 0, 1}, {1, 0, 0, 1}},
},
{
.pos = {20, 134},
.size = {95, 15},
.events = UI_EVENT_BUTTON | UI_EVENT_CURSOR,
},
{
.pos = {134, 2},
.size = {10, 1},
.color = {{0, 0, 0, 1}, {0, 0, 0, 1}, {0, 0, 0, 1}, {0, 0, 0, 1}},
},
{
.pos = {146, 2},
.size = {20, 20},
.color = {{0, 0, 0, 1}, {0, 0, 0, 1}, {0, 0, 0, 1}, {0, 0, 0, 1}},
.events = UI_EVENT_BUTTON,
},
{
.pos = {168, 2},
.size = {20, 20},
.color = {{0, 0, 0, 1}, {0, 0, 0, 1}, {0, 0, 0, 1}, {0, 0, 0, 1}},
.events = UI_EVENT_BUTTON,
},
{
.pos = {146, 24},
.size = {20, 20},
.color = {{0, 0, 0, 1}, {0, 0, 0, 1}, {0, 0, 0, 1}, {0, 0, 0, 1}},
.events = UI_EVENT_BUTTON,
},
{
.pos = {168, 24},
.size = {20, 20},
.color = {{0, 0, 0, 1}, {0, 0, 0, 1}, {0, 0, 0, 1}, {0, 0, 0, 1}},
.events = UI_EVENT_BUTTON,
},
{
.pos = {146, 46},
.size = {20, 20},
.color = {{0, 0, 0, 1}, {0, 0, 0, 1}, {0, 0, 0, 1}, {0, 0, 0, 1}},
.events = UI_EVENT_BUTTON,
},
{
.pos = {168, 46},
.size = {20, 20},
.color = {{0, 0, 0, 1}, {0, 0, 0, 1}, {0, 0, 0, 1}, {0, 0, 0, 1}},
.events = UI_EVENT_BUTTON,
},
{
.pos = {146, 68},
.size = {20, 20},
.color = {{0, 0, 0, 1}, {0, 0, 0, 1}, {0, 0, 0, 1}, {0, 0, 0, 1}},
.events = UI_EVENT_BUTTON,
},
{
.pos = {168, 68},
.size = {20, 20},
.color = {{0, 0, 0, 1}, {0, 0, 0, 1}, {0, 0, 0, 1}, {0, 0, 0, 1}},
.events = UI_EVENT_BUTTON,
},
{
.pos = {146, 90},
.size = {20, 20},
.color = {{0, 0, 0, 1}, {0, 0, 0, 1}, {0, 0, 0, 1}, {0, 0, 0, 1}},
.events = UI_EVENT_BUTTON,
},
{
.pos = {168, 90},
.size = {20, 20},
.color = {{0, 0, 0, 1}, {0, 0, 0, 1}, {0, 0, 0, 1}, {0, 0, 0, 1}},
.events = UI_EVENT_BUTTON,
},
{
.pos = {146, 112},
.size = {20, 20},
.color = {{0, 0, 0, 1}, {0, 0, 0, 1}, {0, 0, 0, 1}, {0, 0, 0, 1}},
.events = UI_EVENT_BUTTON,
},
{
.pos = {168, 112},
.size = {20, 20},
.color = {{0, 0, 0, 1}, {0, 0, 0, 1}, {0, 0, 0, 1}, {0, 0, 0, 1}},
.events = UI_EVENT_BUTTON,
},
};
LayerInput layer = {
.strings = strings,
.num_strings = sizeof(strings)/sizeof(GPUString),
.drawables = drawables,
.num_drawables = sizeof(drawables)/sizeof(GPUDrawable),
};
ContainerInput container = {
.layers = &layer,
.layer_count = 1,
.anchor = ANCHOR_BOTTOM_LEFT,
.id = COLOR_PICK_CONTAINER_ID,
.offset = {0, 0},
@ -272,37 +138,19 @@ VkResult color_ui(ClientContext* context) {
}
VkResult mode_string_ui(ClientContext* context) {
VkResult result;
GPUString string = {
.pos = {0, 32},
.font = 0,
.size = 32,
.color = {1, 1, 1, 1},
.max_length = 8,
};
LayerInput layer = {
.num_strings = 1,
.strings = &string,
};
ContainerInput container = {
.id = MODE_STRING_CONTAINER_ID,
.layer_count = 1,
.layers = &layer,
.anchor = ANCHOR_TOP_LEFT,
.size = {160, 40},
.script_path = "script/mode_string.lua",
};
VK_RESULT(load_container(&container, &context->render, &context->ui));
Container* c = context_container(MODE_STRING_CONTAINER_ID, &context->ui);
return update_ui_string(ModeStrings[MODE_NONE], c, 0, 0, &context->ui, &context->render);
return load_container(&container, &context->render, &context->ui);
}
VkResult update_mode_string(ClientContext* context, EditorData* data) {
Container* container = context_container(MODE_STRING_CONTAINER_ID, &context->ui);
return update_ui_string(ModeStrings[data->mode], container, 0, 0, &context->ui, &context->render);
return ui_lua_call(&context->ui, &context->render, container, "set_mode", ModeStrings[data->mode]);
}
void editor_key_callback(ClientContext* context, int key, int action, int mods) {

@ -10,7 +10,6 @@ void text_callback(GLFWwindow* window, unsigned int codepoint) {
&context->ui,
&context->render,
context->ui.active_container,
context->ui.active_layer,
context->ui.active_element,
codepoint)) return;
@ -28,7 +27,6 @@ void key_callback(GLFWwindow* window, int key, int scancode, int action, int mod
&context->ui,
&context->render,
context->ui.active_container,
context->ui.active_layer,
context->ui.active_element,
key, action, mods)) return;
@ -38,7 +36,6 @@ void key_callback(GLFWwindow* window, int key, int scancode, int action, int mod
void button_callback(GLFWwindow* window, int button, int action, int mods) {
double cursor[2];
uint32_t container;
uint32_t layer;
uint32_t element;
vec2 position;
@ -48,7 +45,7 @@ void button_callback(GLFWwindow* window, int button, int action, int mods) {
if(context->ui.active_container != NULL && context->ui.active_container->script_env != 0) {
Container* container_ptr = context->ui.active_container;
GPUDrawable* drawable_ptr = &container_ptr->layers[context->ui.active_layer].drawables_buffer[context->ui.active_element];
GPUDrawable* drawable_ptr = &container_ptr->drawables_buffer[context->ui.active_element];
vec2 element_pos = {
drawable_ptr->pos[0] + container_ptr->data.offset[0],
drawable_ptr->pos[1] + container_ptr->data.offset[1],
@ -69,7 +66,6 @@ void button_callback(GLFWwindow* window, int button, int action, int mods) {
&context->ui,
&context->render,
container_ptr,
context->ui.active_layer,
context->ui.active_element,
point[0],
point[1],
@ -82,13 +78,12 @@ void button_callback(GLFWwindow* window, int button, int action, int mods) {
}
}
if(ui_intersect(cursor, &context->render, &context->ui, UI_EVENT_BUTTON, &container, &layer, &element, position)) {
if(ui_intersect(cursor, &context->render, &context->ui, UI_EVENT_BUTTON, &container, &element, position)) {
Container* container_ptr = context_container(container, &context->ui);
if(ui_lua_dispatch_button(
&context->ui,
&context->render,
container_ptr,
layer,
element,
position[0],
position[1],
@ -103,7 +98,6 @@ void button_callback(GLFWwindow* window, int button, int action, int mods) {
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset) {
(void)xoffset;
uint32_t container;
uint32_t layer;
uint32_t element;
vec2 position;
@ -113,7 +107,6 @@ void scroll_callback(GLFWwindow* window, double xoffset, double yoffset) {
&context->ui,
&context->render,
context->ui.active_container,
context->ui.active_layer,
context->ui.active_element,
xoffset, yoffset)) return;
@ -123,7 +116,6 @@ void scroll_callback(GLFWwindow* window, double xoffset, double yoffset) {
&context->ui,
UI_EVENT_SCROLL,
&container,
&layer,
&element,
position)) {
Container* container_ptr = context_container(container, &context->ui);
@ -131,7 +123,6 @@ void scroll_callback(GLFWwindow* window, double xoffset, double yoffset) {
&context->ui,
&context->render,
container_ptr,
layer,
element,
xoffset,
yoffset);
@ -144,7 +135,6 @@ void cursor_callback(GLFWwindow* window, double xpos, double ypos) {
ClientContext* context = (ClientContext*)glfwGetWindowUserPointer(window);
uint32_t container;
uint32_t element;
uint32_t layer;
vec2 position;
context->ui.cursor[0] = xpos;
@ -152,7 +142,7 @@ void cursor_callback(GLFWwindow* window, double xpos, double ypos) {
if(context->ui.active_container != NULL && context->ui.active_container->script_env != 0) {
Container* container_ptr = context->ui.active_container;
GPUDrawable* drawable_ptr = &container_ptr->layers[context->ui.active_layer].drawables_buffer[context->ui.active_element];
GPUDrawable* drawable_ptr = &container_ptr->drawables_buffer[context->ui.active_element];
vec2 element_pos = {
drawable_ptr->pos[0] + container_ptr->data.offset[0],
drawable_ptr->pos[1] + container_ptr->data.offset[1],
@ -166,19 +156,17 @@ void cursor_callback(GLFWwindow* window, double xpos, double ypos) {
&context->ui,
&context->render,
container_ptr,
context->ui.active_layer,
context->ui.active_element,
(context->ui.cursor[0] - element_pos[0])/element_size[0],
(context->ui.cursor[1] - element_pos[1])/element_size[1])) return;
}
if(ui_intersect(context->ui.cursor, &context->render, &context->ui, UI_EVENT_CURSOR, &container, &layer, &element, position)) {
if(ui_intersect(context->ui.cursor, &context->render, &context->ui, UI_EVENT_CURSOR, &container, &element, position)) {
Container* container_ptr = context_container(container, &context->ui);
ui_lua_dispatch_cursor(
&context->ui,
&context->render,
container_ptr,
layer,
element,
position[0],
position[1]);

@ -234,34 +234,380 @@ VkResult create_ui_pipeline(
return VK_SUCCESS;
}
// Layer structs come from uninitialized malloc, so cleanup is gated on the
// data.* count fields create_layer always sets, not on pointer NULL checks
static void destroy_layer(Layer* layer, RenderContext* gpu) {
// Reassigns glyph scratch slots densely. Scratch contents don't need copying:
// string.comp rewrites every live glyph each dispatch.
static VkResult compact_scratch(Container* c, RenderContext* gpu) {
VkResult result;
uint32_t bump = 0;
for(uint32_t s = 0; s < c->num_strings; s++) {
if(c->string_live[s] == 0 || c->strings_buffer[s].max_length == 0) continue;
uint32_t scratch = c->cap_drawables + bump;
bump += c->strings_buffer[s].max_length;
if(c->strings_buffer[s].scratch != scratch) {
c->strings_buffer[s].scratch = scratch;
VK_RESULT(add_transfers(
&c->strings_buffer[s].scratch,
c->strings,
sizeof(GPUString)*s + offsetof(GPUString, scratch),
sizeof(uint32_t),
gpu));
}
}
c->scratch_used = bump;
return VK_SUCCESS;
}
typedef struct ZEntryStruct {
float z;
uint32_t string;
uint32_t slot;
} ZEntry;
// Plain float compare; equal-z draw order is undefined and may change
// across rebuilds
static int z_entry_compare(const void* a, const void* b) {
float za = ((const ZEntry*)a)->z;
float zb = ((const ZEntry*)b)->z;
return (za > zb) - (za < zb);
}
// Rewrites the z-sorted index list and instance count after any structural
// change (create/destroy/z change). Strings reserve max_length entries at
// their z, pointing into glyph scratch.
static VkResult rebuild_indices(Container* c, RenderContext* gpu) {
VkResult result;
ZEntry* entries = malloc(sizeof(ZEntry)*(c->num_drawables + c->num_strings));
uint32_t entry_count = 0;
for(uint32_t d = 0; d < c->num_drawables; d++) {
if(c->drawable_live[d] == 0) continue;
entries[entry_count].z = c->drawables_buffer[d].z;
entries[entry_count].string = 0;
entries[entry_count].slot = d;
entry_count += 1;
}
for(uint32_t s = 0; s < c->num_strings; s++) {
if(c->string_live[s] == 0 || c->strings_buffer[s].max_length == 0) continue;
entries[entry_count].z = c->strings_buffer[s].z;
entries[entry_count].string = 1;
entries[entry_count].slot = s;
entry_count += 1;
}
qsort(entries, entry_count, sizeof(ZEntry), z_entry_compare);
uint32_t n = 0;
for(uint32_t e = 0; e < entry_count; e++) {
if(entries[e].string == 0) {
c->indices_buffer[n] = entries[e].slot;
n += 1;
} else {
GPUString* string = &c->strings_buffer[entries[e].slot];
for(uint32_t i = 0; i < string->max_length; i++) {
c->indices_buffer[n] = string->scratch + i;
n += 1;
}
}
}
free(entries);
if(n > 0) {
VK_RESULT(add_transfers(c->indices_buffer, c->indices, 0, sizeof(uint32_t)*n, gpu));
}
c->data.draw.instance_count = n;
return add_transfers(
&c->data.draw.instance_count,
c->container,
offsetof(GPUContainer, draw) + offsetof(DrawCommand, instance_count),
sizeof(uint32_t),
gpu);
}
// Recreates the drawable pool (and the index list, whose capacity is tied to
// it) at a new capacity. Old buffers are retired, addresses re-patched, and
// scratch bases recomputed since they sit at cap_drawables.
static VkResult container_realloc_drawable_pool(
Container* c,
uint32_t new_cap_drawables,
uint32_t new_cap_codes,
RenderContext* gpu) {
VkResult result;
uint32_t pool = new_cap_drawables + new_cap_codes;
c->drawables_buffer = realloc(c->drawables_buffer, sizeof(GPUDrawable)*new_cap_drawables);
c->drawable_live = realloc(c->drawable_live, new_cap_drawables);
memset(c->drawable_live + c->cap_drawables, 0, new_cap_drawables - c->cap_drawables);
c->free_drawables = realloc(c->free_drawables, sizeof(uint32_t)*new_cap_drawables);
c->indices_buffer = realloc(c->indices_buffer, sizeof(uint32_t)*pool);
c->drawable_generation = realloc(c->drawable_generation, sizeof(uint32_t)*new_cap_drawables);
memset(c->drawable_generation + c->cap_drawables, 0, sizeof(uint32_t)*(new_cap_drawables - c->cap_drawables));
c->drawable_ref = realloc(c->drawable_ref, sizeof(int)*new_cap_drawables);
for(uint32_t i = c->cap_drawables; i < new_cap_drawables; i++) {
c->drawable_ref[i] = LUA_NOREF;
}
for(uint32_t f = 0; f < MAX_FRAMES_IN_FLIGHT; f++) {
retire_buffer(layer->layer[f], layer->layer_memory[f], gpu);
if(layer->data.max_strings > 0) {
retire_buffer(layer->strings[f], layer->strings_memory[f], gpu);
retire_buffer(c->drawables[f], c->drawables_memory[f], gpu);
retire_buffer(c->indices[f], c->indices_memory[f], gpu);
VK_RESULT(create_storage_buffer(gpu->allocator, 0, sizeof(GPUDrawable)*pool, &c->drawables[f], &c->drawables_memory[f]));
VK_RESULT(create_storage_buffer(gpu->allocator, 0, sizeof(uint32_t)*pool, &c->indices[f], &c->indices_memory[f]));
VkDeviceAddress drawables_address = buffer_address(gpu->device, c->drawables[f]);
VkDeviceAddress indices_address = buffer_address(gpu->device, c->indices[f]);
VK_RESULT(add_transfer(&drawables_address, c->container[f], offsetof(GPUContainer, drawables), sizeof(VkDeviceAddress), f, gpu));
VK_RESULT(add_transfer(&indices_address, c->container[f], offsetof(GPUContainer, indices), sizeof(VkDeviceAddress), f, gpu));
}
c->cap_drawables = new_cap_drawables;
c->cap_codes = new_cap_codes;
if(c->num_drawables > 0) {
VK_RESULT(add_transfers(c->drawables_buffer, c->drawables, 0, sizeof(GPUDrawable)*c->num_drawables, gpu));
}
return compact_scratch(c, gpu);
}
static VkResult container_realloc_string_pool(
Container* c,
uint32_t new_cap,
RenderContext* gpu) {
VkResult result;
c->strings_buffer = realloc(c->strings_buffer, sizeof(GPUString)*new_cap);
c->string_resources = realloc(c->string_resources, sizeof(StringResources)*new_cap);
memset(c->string_resources + c->cap_strings, 0, sizeof(StringResources)*(new_cap - c->cap_strings));
c->string_live = realloc(c->string_live, new_cap);
memset(c->string_live + c->cap_strings, 0, new_cap - c->cap_strings);
c->free_strings = realloc(c->free_strings, sizeof(uint32_t)*new_cap);
c->string_generation = realloc(c->string_generation, sizeof(uint32_t)*new_cap);
memset(c->string_generation + c->cap_strings, 0, sizeof(uint32_t)*(new_cap - c->cap_strings));
c->string_ref = realloc(c->string_ref, sizeof(int)*new_cap);
for(uint32_t i = c->cap_strings; i < new_cap; i++) {
c->string_ref[i] = LUA_NOREF;
}
for(uint32_t f = 0; f < MAX_FRAMES_IN_FLIGHT; f++) {
retire_buffer(c->strings[f], c->strings_memory[f], gpu);
VK_RESULT(create_storage_buffer(gpu->allocator, 0, sizeof(GPUString)*new_cap, &c->strings[f], &c->strings_memory[f]));
VkDeviceAddress address = buffer_address(gpu->device, c->strings[f]);
VK_RESULT(add_transfer(&address, c->container[f], offsetof(GPUContainer, strings), sizeof(VkDeviceAddress), f, gpu));
}
c->cap_strings = new_cap;
if(c->num_strings > 0) {
VK_RESULT(add_transfers(c->strings_buffer, c->strings, 0, sizeof(GPUString)*c->num_strings, gpu));
// The bulk transfer wrote the mirror's zeroed codes fields; restore the
// per-frame codes buffer addresses
for(uint32_t s = 0; s < c->num_strings; s++) {
if(c->string_resources[s].codes_buffer == NULL) continue;
for(uint32_t f = 0; f < MAX_FRAMES_IN_FLIGHT; f++) {
VkDeviceAddress address = buffer_address(gpu->device, c->string_resources[s].codes[f]);
VK_RESULT(add_transfer(&address, c->strings[f], sizeof(GPUString)*s + offsetof(GPUString, codes), sizeof(VkDeviceAddress), f, gpu));
}
}
}
return VK_SUCCESS;
}
static VkResult container_add_drawable(
Container* c,
const GPUDrawable* drawable,
RenderContext* gpu,
uint32_t* slot_out) {
VkResult result;
uint32_t slot;
if(c->free_drawable_count > 0) {
c->free_drawable_count -= 1;
slot = c->free_drawables[c->free_drawable_count];
} else {
if(c->num_drawables == c->cap_drawables) {
VK_RESULT(container_realloc_drawable_pool(c, c->cap_drawables*2, c->cap_codes, gpu));
}
slot = c->num_drawables;
c->num_drawables += 1;
}
if(layer->data.max_drawables > 0) {
retire_buffer(layer->drawables[f], layer->drawables_memory[f], gpu);
c->drawables_buffer[slot] = *drawable;
c->drawable_live[slot] = 1;
VK_RESULT(add_transfers(&c->drawables_buffer[slot], c->drawables, sizeof(GPUDrawable)*slot, sizeof(GPUDrawable), gpu));
*slot_out = slot;
return VK_SUCCESS;
}
static VkResult container_add_string(
Container* c,
const GPUString* string,
RenderContext* gpu,
uint32_t* slot_out) {
VkResult result;
uint32_t slot;
if(c->free_string_count > 0) {
c->free_string_count -= 1;
slot = c->free_strings[c->free_string_count];
} else {
if(c->num_strings == c->cap_strings) {
VK_RESULT(container_realloc_string_pool(c, c->cap_strings*2, gpu));
}
slot = c->num_strings;
c->num_strings += 1;
c->data.dispatch_strings.x = c->num_strings;
VK_RESULT(add_transfers(
&c->data.dispatch_strings.x,
c->container,
offsetof(GPUContainer, dispatch_strings),
sizeof(uint32_t),
gpu));
}
if(layer->data.max_strings > 0) {
for(uint32_t s = 0; s < layer->data.max_strings; s++) {
if(layer->string_resources[s].codes_buffer == NULL) continue;
StringResources* sr = &c->string_resources[slot];
uint32_t max_len = string->max_length;
if(max_len > sr->max_codes) {
// Recycled slot's codes buffers (if any) are too small for this string
if(sr->codes_buffer != NULL) {
for(uint32_t f = 0; f < MAX_FRAMES_IN_FLIGHT; f++) {
retire_buffer(layer->string_resources[s].codes[f], layer->string_resources[s].codes_memory[f], gpu);
retire_buffer(sr->codes[f], sr->codes_memory[f], gpu);
}
free(layer->string_resources[s].codes_buffer);
free(sr->codes_buffer);
}
free(layer->string_resources);
free(layer->strings_buffer);
uint32_t codes_size = ((max_len + 3) / 4) * sizeof(uint32_t);
sr->codes_buffer = malloc(codes_size);
memset(sr->codes_buffer, 0, codes_size);
sr->max_codes = max_len;
for(uint32_t f = 0; f < MAX_FRAMES_IN_FLIGHT; f++) {
VK_RESULT(create_storage_buffer(gpu->allocator, 0, codes_size, &sr->codes[f], &sr->codes_memory[f]));
}
}
c->strings_buffer[slot] = *string;
c->strings_buffer[slot].codes = 0;
if(layer->data.num_drawables > 0) {
free(layer->drawables_buffer);
if(max_len > 0) {
if(c->scratch_used + max_len > c->cap_codes) {
VK_RESULT(compact_scratch(c, gpu));
}
if(c->scratch_used + max_len > c->cap_codes) {
uint32_t new_cap = c->cap_codes*2;
while(c->scratch_used + max_len > new_cap) {
new_cap *= 2;
}
VK_RESULT(container_realloc_drawable_pool(c, c->cap_drawables, new_cap, gpu));
}
c->strings_buffer[slot].scratch = c->cap_drawables + c->scratch_used;
c->scratch_used += max_len;
} else {
c->strings_buffer[slot].scratch = STRING_SCRATCH_NONE;
}
c->string_live[slot] = 1;
VK_RESULT(add_transfers(&c->strings_buffer[slot], c->strings, sizeof(GPUString)*slot, sizeof(GPUString), gpu));
if(max_len > 0) {
for(uint32_t f = 0; f < MAX_FRAMES_IN_FLIGHT; f++) {
VkDeviceAddress address = buffer_address(gpu->device, sr->codes[f]);
VK_RESULT(add_transfer(&address, c->strings[f], sizeof(GPUString)*slot + offsetof(GPUString, codes), sizeof(VkDeviceAddress), f, gpu));
}
}
*slot_out = slot;
return VK_SUCCESS;
}
VkResult ui_create_drawable(
Container* container,
const GPUDrawable* drawable,
RenderContext* gpu,
uint32_t* slot) {
VkResult result;
VK_RESULT(container_add_drawable(container, drawable, gpu, slot));
return rebuild_indices(container, gpu);
}
VkResult ui_destroy_drawable(
Container* container,
uint32_t slot,
RenderContext* gpu) {
if(slot >= container->num_drawables || container->drawable_live[slot] == 0) {
return VK_ERROR_VALIDATION_FAILED_EXT;
}
container->drawable_live[slot] = 0;
container->drawable_generation[slot] += 1;
container->drawables_buffer[slot].type = DRAWABLE_TYPE_NONE;
container->drawables_buffer[slot].events = 0;
container->free_drawables[container->free_drawable_count] = slot;
container->free_drawable_count += 1;
return rebuild_indices(container, gpu);
}
VkResult ui_create_string(
Container* container,
const GPUString* string,
RenderContext* gpu,
uint32_t* slot) {
VkResult result;
VK_RESULT(container_add_string(container, string, gpu, slot));
return rebuild_indices(container, gpu);
}
VkResult ui_destroy_string(
Container* container,
uint32_t slot,
RenderContext* gpu) {
VkResult result;
if(slot >= container->num_strings || container->string_live[slot] == 0) {
return VK_ERROR_VALIDATION_FAILED_EXT;
}
container->string_live[slot] = 0;
container->string_generation[slot] += 1;
container->strings_buffer[slot].length = 0;
container->strings_buffer[slot].scratch = STRING_SCRATCH_NONE;
// Neutralize on the GPU so the compute pass can't write into scratch slots
// that later get reassigned
VK_RESULT(add_transfers(
&container->strings_buffer[slot].scratch,
container->strings,
sizeof(GPUString)*slot + offsetof(GPUString, scratch),
sizeof(uint32_t),
gpu));
container->free_strings[container->free_string_count] = slot;
container->free_string_count += 1;
// codes buffers are kept for slot reuse
return rebuild_indices(container, gpu);
}
VkResult ui_set_drawable_z(
Container* container,
uint32_t slot,
float z,
RenderContext* gpu) {
if(slot >= container->num_drawables || container->drawable_live[slot] == 0) {
return VK_ERROR_VALIDATION_FAILED_EXT;
}
container->drawables_buffer[slot].z = z;
return rebuild_indices(container, gpu);
}
VkResult ui_set_string_z(
Container* container,
uint32_t slot,
float z,
RenderContext* gpu) {
if(slot >= container->num_strings || container->string_live[slot] == 0) {
return VK_ERROR_VALIDATION_FAILED_EXT;
}
container->strings_buffer[slot].z = z;
return rebuild_indices(container, gpu);
}
VkResult unload_container(
@ -273,16 +619,57 @@ VkResult unload_container(
return VK_ERROR_VALIDATION_FAILED_EXT;
}
for(uint32_t l = 0; l < container->layer_count; l++) {
destroy_layer(&container->layers[l], gpu);
for(uint32_t f = 0; f < MAX_FRAMES_IN_FLIGHT; f++) {
retire_buffer(container->container[f], container->container_memory[f], gpu);
retire_buffer(container->strings[f], container->strings_memory[f], gpu);
retire_buffer(container->drawables[f], container->drawables_memory[f], gpu);
retire_buffer(container->indices[f], container->indices_memory[f], gpu);
}
free(container->layers);
container->layers = NULL;
container->layer_count = 0;
for(uint32_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
retire_buffer(container->container[i], container->container_memory[i], gpu);
container->address[i] = 0;
for(uint32_t s = 0; s < container->cap_strings; s++) {
if(container->string_resources[s].codes_buffer == NULL) continue;
for(uint32_t f = 0; f < MAX_FRAMES_IN_FLIGHT; f++) {
retire_buffer(container->string_resources[s].codes[f], container->string_resources[s].codes_memory[f], gpu);
}
free(container->string_resources[s].codes_buffer);
}
// Release the Lua handle refs; the userdata objects themselves die with
// the script environment
for(uint32_t d = 0; d < container->cap_drawables; d++) {
if(container->drawable_ref[d] != LUA_NOREF) {
luaL_unref(context->lua, LUA_REGISTRYINDEX, container->drawable_ref[d]);
}
}
for(uint32_t s = 0; s < container->cap_strings; s++) {
if(container->string_ref[s] != LUA_NOREF) {
luaL_unref(context->lua, LUA_REGISTRYINDEX, container->string_ref[s]);
}
}
free(container->drawables_buffer);
free(container->strings_buffer);
free(container->string_resources);
free(container->indices_buffer);
free(container->drawable_live);
free(container->string_live);
free(container->free_drawables);
free(container->free_strings);
free(container->drawable_generation);
free(container->string_generation);
free(container->drawable_ref);
free(container->string_ref);
uint32_t index = (uint32_t)(container - context->containers);
for(uint32_t i = 0; i < context->container_order_count; i++) {
if(context->container_order[i] == index) {
memmove(
&context->container_order[i],
&context->container_order[i+1],
(context->container_order_count - i - 1)*sizeof(uint32_t));
context->container_order_count -= 1;
break;
}
}
// Drop focus without dispatching on_deselect: the script is being unloaded
@ -293,27 +680,25 @@ VkResult unload_container(
if(container->script_env != 0) {
luaL_unref(context->lua, LUA_REGISTRYINDEX, container->script_env);
container->script_env = 0;
}
if(container->script_path != NULL) {
free(container->script_path);
container->script_path = NULL;
}
container->id = 0;
memset(container, 0, sizeof(Container));
return VK_SUCCESS;
}
VkResult load_container(
ContainerInput* container,
ContainerInput* input,
RenderContext* gpu,
UIContext* context) {
if(container == NULL) return VK_ERROR_VALIDATION_FAILED_EXT;
if(container->id == 0) return VK_ERROR_VALIDATION_FAILED_EXT;
if(input == NULL) return VK_ERROR_VALIDATION_FAILED_EXT;
if(input->id == 0) return VK_ERROR_VALIDATION_FAILED_EXT;
for(uint32_t i = 0; i < context->max_containers; i++) {
if(context->containers[i].id == container->id) return VK_ERROR_VALIDATION_FAILED_EXT;
if(context->containers[i].id == input->id) return VK_ERROR_VALIDATION_FAILED_EXT;
}
uint32_t index = 0xFFFFFFFF;
@ -327,158 +712,89 @@ VkResult load_container(
return VK_ERROR_OUT_OF_HOST_MEMORY;
}
VkResult result;
for(uint32_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
VK_RESULT(create_storage_buffer(gpu->allocator, 0, sizeof(GPUContainer), &context->containers[index].container[i], &context->containers[index].container_memory[i]));
context->containers[index].address[i] = buffer_address(gpu->device, context->containers[index].container[i]);
}
context->containers[index].data.offset[0] = container->offset[0];
context->containers[index].data.offset[1] = container->offset[1];
context->containers[index].data.size[0] = container->size[0];
context->containers[index].data.size[1] = container->size[1];
context->containers[index].data.anchor = container->anchor;
context->containers[index].data.context = context->address;
add_transfers(&context->containers[index].data, context->containers[index].container, 0, sizeof(GPUContainer), gpu);
Container* c = &context->containers[index];
memset(c, 0, sizeof(Container));
context->containers[index].id = container->id;
context->containers[index].layers = malloc(sizeof(Layer)*container->layer_count);
for(uint32_t i = 0; i < container->layer_count; i++) {
VK_RESULT(create_layer(i, &container->layers[i], gpu, &context->containers[index]));
}
c->cap_drawables = CONTAINER_MIN_DRAWABLES;
c->cap_strings = CONTAINER_MIN_STRINGS;
c->cap_codes = CONTAINER_MIN_CODES;
context->containers[index].layer_count = container->layer_count;
uint32_t pool = c->cap_drawables + c->cap_codes;
context->containers[index].script_env = 0;
context->containers[index].script_path = NULL;
if(container->script_path != NULL) {
context->containers[index].script_path = strdup(container->script_path);
VK_RESULT(ui_lua_load_container(
context->lua,
&context->containers[index],
context,
gpu,
container->script_path));
c->drawables_buffer = malloc(sizeof(GPUDrawable)*c->cap_drawables);
c->strings_buffer = malloc(sizeof(GPUString)*c->cap_strings);
c->string_resources = malloc(sizeof(StringResources)*c->cap_strings);
memset(c->string_resources, 0, sizeof(StringResources)*c->cap_strings);
c->indices_buffer = malloc(sizeof(uint32_t)*pool);
c->drawable_live = malloc(c->cap_drawables);
memset(c->drawable_live, 0, c->cap_drawables);
c->string_live = malloc(c->cap_strings);
memset(c->string_live, 0, c->cap_strings);
c->free_drawables = malloc(sizeof(uint32_t)*c->cap_drawables);
c->free_strings = malloc(sizeof(uint32_t)*c->cap_strings);
c->drawable_generation = malloc(sizeof(uint32_t)*c->cap_drawables);
memset(c->drawable_generation, 0, sizeof(uint32_t)*c->cap_drawables);
c->string_generation = malloc(sizeof(uint32_t)*c->cap_strings);
memset(c->string_generation, 0, sizeof(uint32_t)*c->cap_strings);
c->drawable_ref = malloc(sizeof(int)*c->cap_drawables);
for(uint32_t i = 0; i < c->cap_drawables; i++) {
c->drawable_ref[i] = LUA_NOREF;
}
return VK_SUCCESS;
}
VkResult create_layer(
uint32_t index,
LayerInput* input,
RenderContext* gpu,
Container* container) {
VkResult result;
uint32_t max_strings = (input->num_strings > input->max_strings) ? input->num_strings : input->max_strings;
uint32_t max_drawables = (input->num_drawables > input->max_drawables) ? input->num_drawables : input->max_drawables;
uint32_t total_max_codes = 0;
for(uint32_t s = 0; s < input->num_strings; s++) {
total_max_codes += input->strings[s].max_length;
c->string_ref = malloc(sizeof(int)*c->cap_strings);
for(uint32_t i = 0; i < c->cap_strings; i++) {
c->string_ref[i] = LUA_NOREF;
}
if(max_strings > 0) {
container->layers[index].strings_buffer = malloc(sizeof(GPUString)*max_strings);
container->layers[index].string_resources = malloc(sizeof(StringResources)*max_strings);
memset(container->layers[index].string_resources, 0, sizeof(StringResources)*max_strings);
}
if(max_drawables > 0) {
container->layers[index].drawables_buffer = malloc(sizeof(GPUDrawable)*max_drawables);
}
for(uint32_t s = 0; s < input->num_strings; s++) {
uint32_t max_len = input->strings[s].max_length;
if(max_len == 0) continue;
uint32_t codes_size = ((max_len + 3) / 4) * sizeof(uint32_t);
container->layers[index].string_resources[s].codes_buffer = malloc(codes_size);
memset(container->layers[index].string_resources[s].codes_buffer, 0, codes_size);
VkResult result;
for(uint32_t f = 0; f < MAX_FRAMES_IN_FLIGHT; f++) {
VK_RESULT(create_storage_buffer(gpu->allocator, 0, codes_size,
&container->layers[index].string_resources[s].codes[f],
&container->layers[index].string_resources[s].codes_memory[f]));
}
}
for(uint32_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
VK_RESULT(create_storage_buffer(gpu->allocator, VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT, sizeof(GPULayer), &container->layers[index].layer[i], &container->layers[index].layer_memory[i]));
if(max_strings > 0) {
VK_RESULT(create_storage_buffer(gpu->allocator, 0, sizeof(GPUString)*max_strings, &container->layers[index].strings[i], &container->layers[index].strings_memory[i]));
VkDeviceAddress address = buffer_address(gpu->device, container->layers[index].strings[i]);
add_transfer(
&address,
container->layers[index].layer[i],
offsetof(GPULayer, strings),
sizeof(VkDeviceAddress),
i,
gpu);
}
if(total_max_codes + max_drawables > 0) {
VK_RESULT(create_storage_buffer(gpu->allocator, 0, sizeof(GPUDrawable)*(max_drawables + total_max_codes), &container->layers[index].drawables[i], &container->layers[index].drawables_memory[i]));
VkDeviceAddress address = buffer_address(gpu->device, container->layers[index].drawables[i]);
add_transfer(
&address,
container->layers[index].layer[i],
offsetof(GPULayer, drawables),
sizeof(VkDeviceAddress),
i,
gpu);
}
container->layers[index].address[i] = buffer_address(gpu->device, container->layers[index].layer[i]);
}
for(uint32_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
add_transfer(
&container->address[i],
container->layers[index].layer[i],
offsetof(GPULayer, container),
sizeof(VkDeviceAddress),
i,
gpu);
VK_RESULT(create_storage_buffer(gpu->allocator, VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT, sizeof(GPUContainer), &c->container[f], &c->container_memory[f]));
VK_RESULT(create_storage_buffer(gpu->allocator, 0, sizeof(GPUString)*c->cap_strings, &c->strings[f], &c->strings_memory[f]));
VK_RESULT(create_storage_buffer(gpu->allocator, 0, sizeof(GPUDrawable)*pool, &c->drawables[f], &c->drawables_memory[f]));
VK_RESULT(create_storage_buffer(gpu->allocator, 0, sizeof(uint32_t)*pool, &c->indices[f], &c->indices_memory[f]));
c->address[f] = buffer_address(gpu->device, c->container[f]);
}
c->data.draw.vertex_count = 6;
c->data.draw.instance_count = 0;
c->data.draw.first_vertex = 0;
c->data.draw.first_instance = 0;
c->data.dispatch_strings.x = 0;
c->data.dispatch_strings.y = 1;
c->data.dispatch_strings.z = 1;
c->data.anchor = input->anchor;
c->data.offset[0] = input->offset[0];
c->data.offset[1] = input->offset[1];
c->data.size[0] = input->size[0];
c->data.size[1] = input->size[1];
c->data.context = context->address;
// Buffer addresses differ per frame: transfer the shared prefix once, then
// patch each frame's copies individually
VK_RESULT(add_transfers(&c->data, c->container, 0, offsetof(GPUContainer, strings), gpu));
VK_RESULT(add_transfers(&c->data.context, c->container, offsetof(GPUContainer, context), sizeof(VkDeviceAddress), gpu));
for(uint32_t f = 0; f < MAX_FRAMES_IN_FLIGHT; f++) {
VkDeviceAddress strings_address = buffer_address(gpu->device, c->strings[f]);
VkDeviceAddress drawables_address = buffer_address(gpu->device, c->drawables[f]);
VkDeviceAddress indices_address = buffer_address(gpu->device, c->indices[f]);
VK_RESULT(add_transfer(&strings_address, c->container[f], offsetof(GPUContainer, strings), sizeof(VkDeviceAddress), f, gpu));
VK_RESULT(add_transfer(&drawables_address, c->container[f], offsetof(GPUContainer, drawables), sizeof(VkDeviceAddress), f, gpu));
VK_RESULT(add_transfer(&indices_address, c->container[f], offsetof(GPUContainer, indices), sizeof(VkDeviceAddress), f, gpu));
}
container->layers[index].data.draw.first_vertex = 0;
container->layers[index].data.draw.vertex_count = 6;
container->layers[index].data.draw.first_instance = 0;
container->layers[index].data.draw.instance_count = 0;
container->layers[index].data.dispatch_strings.x = max_strings;
container->layers[index].data.dispatch_strings.y = 1;
container->layers[index].data.dispatch_strings.z = 1;
container->layers[index].data.max_drawables = max_drawables + total_max_codes;
container->layers[index].data.max_strings = max_strings;
container->layers[index].data.num_drawables = max_drawables;
add_transfers(&container->layers[index].data, container->layers[index].layer, 0, sizeof(GPULayer)-3*sizeof(VkDeviceAddress), gpu);
if(input->num_strings > 0) {
memcpy(container->layers[index].strings_buffer, input->strings, sizeof(GPUString)*input->num_strings);
for(uint32_t s = 0; s < input->num_strings; s++) {
container->layers[index].strings_buffer[s].codes = 0;
}
add_transfers(container->layers[index].strings_buffer, container->layers[index].strings, 0, sizeof(GPUString)*input->num_strings, gpu);
c->id = input->id;
context->container_order[context->container_order_count] = index;
context->container_order_count += 1;
for(uint32_t s = 0; s < input->num_strings; s++) {
if(input->strings[s].max_length == 0) continue;
for(uint32_t f = 0; f < MAX_FRAMES_IN_FLIGHT; f++) {
VkDeviceAddress addr = buffer_address(gpu->device, container->layers[index].string_resources[s].codes[f]);
add_transfer(
&addr,
container->layers[index].strings[f],
s * sizeof(GPUString) + offsetof(GPUString, codes),
sizeof(VkDeviceAddress),
f,
gpu);
}
}
}
VK_RESULT(rebuild_indices(c, gpu));
if(input->num_drawables > 0) {
memcpy(container->layers[index].drawables_buffer, input->drawables, sizeof(GPUDrawable)*input->num_drawables);
add_transfers(container->layers[index].drawables_buffer, container->layers[index].drawables, 0, sizeof(GPUDrawable)*input->num_drawables, gpu);
if(input->script_path != NULL) {
c->script_path = strdup(input->script_path);
VK_RESULT(ui_lua_load_container(
context->lua,
c,
context,
gpu,
input->script_path));
}
return VK_SUCCESS;
@ -1227,6 +1543,8 @@ VkResult create_ui_context(
memset(context->fonts, 0, max_fonts*sizeof(Font));
context->containers = malloc(max_containers*sizeof(Container));
memset(context->containers, 0, max_containers*sizeof(Container));
context->container_order = malloc(max_containers*sizeof(uint32_t));
context->container_order_count = 0;
context->lua = luaL_newstate();
if(context->lua == NULL) {
@ -1279,7 +1597,6 @@ Container* context_container(uint32_t container_id, UIContext* ui) {
VkResult update_ui_string(
const char* string,
Container* container,
uint32_t layer_index,
uint32_t string_index,
UIContext* ui,
RenderContext* gpu) {
@ -1288,25 +1605,27 @@ VkResult update_ui_string(
if(container == NULL) {
return VK_ERROR_UNKNOWN;
}
if(string_index >= container->num_strings || container->string_live[string_index] == 0) {
return VK_ERROR_VALIDATION_FAILED_EXT;
}
Layer* layer = &container->layers[layer_index];
StringResources* sr = &layer->string_resources[string_index];
StringResources* sr = &container->string_resources[string_index];
uint32_t len = strlen(string);
if(len > layer->strings_buffer[string_index].max_length) {
len = layer->strings_buffer[string_index].max_length;
if(len > container->strings_buffer[string_index].max_length) {
len = container->strings_buffer[string_index].max_length;
}
layer->strings_buffer[string_index].length = len;
container->strings_buffer[string_index].length = len;
if(len > 0) {
VK_RESULT(map_string(string, sr->codes_buffer, layer->strings_buffer[string_index].font, ui));
VK_RESULT(map_string(string, sr->codes_buffer, container->strings_buffer[string_index].font, ui));
VK_RESULT(add_transfers(sr->codes_buffer, sr->codes, 0, ((len + 3) / 4) * sizeof(uint32_t), gpu));
}
VK_RESULT(add_transfers(
&layer->strings_buffer[string_index].length,
layer->strings,
&container->strings_buffer[string_index].length,
container->strings,
sizeof(GPUString)*string_index + offsetof(GPUString, length),
sizeof(uint32_t),
gpu));
@ -1341,54 +1660,74 @@ bool ui_intersect(
UIContext* ui,
uint32_t events,
uint32_t* container,
uint32_t* layer,
uint32_t* element,
vec2 position) {
for(uint32_t c = 0; c < ui->max_containers; c++) {
if(ui->containers[c].id == 0x00000000) {
continue;
}
vec2 container_offset = {ui->containers[c].data.offset[0], ui->containers[c].data.offset[1]};
anchor_offset(gpu, &ui->containers[c], container_offset);
for(uint32_t l = 0; l < ui->containers[c].layer_count; l++) {
for(uint32_t d = 0; d < ui->containers[c].layers[l].data.num_drawables; d++) {
if(ui->containers[c].layers[l].drawables_buffer[d].events & events
// Walk containers and their z-sorted index lists in reverse draw order so
// the top-most drawn element wins the hit
for(uint32_t o = ui->container_order_count; o > 0; o--) {
Container* c = &ui->containers[ui->container_order[o-1]];
vec2 container_offset = {c->data.offset[0], c->data.offset[1]};
anchor_offset(gpu, c, container_offset);
uint32_t count = c->data.draw.instance_count;
for(uint32_t k = count; k > 0; k--) {
uint32_t slot = c->indices_buffer[k-1];
if(slot >= c->cap_drawables) {
continue; // glyph scratch, not hit-testable
}
GPUDrawable* drawable = &c->drawables_buffer[slot];
if(drawable->events & events
&& ui_contains(
cursor,
container_offset,
ui->containers[c].data.size,
&ui->containers[c].layers[l].drawables_buffer[d],
c->data.size,
drawable,
position)) {
*container = ui->containers[c].id;
*layer = l;
*element = d;
*container = c->id;
*element = slot;
return true;
}
}
}
}
return false;
}
void ui_container_to_front(uint32_t id, UIContext* ui) {
Container* container = context_container(id, ui);
if(container == NULL) {
return;
}
uint32_t index = (uint32_t)(container - ui->containers);
for(uint32_t i = 0; i < ui->container_order_count; i++) {
if(ui->container_order[i] == index) {
memmove(
&ui->container_order[i],
&ui->container_order[i+1],
(ui->container_order_count - i - 1)*sizeof(uint32_t));
ui->container_order[ui->container_order_count - 1] = index;
break;
}
}
}
void clear_active_element(UIContext* ui, RenderContext* gpu) {
// Clear the active fields before dispatching so a script calling
// ui.blur() inside on_deselect can't recurse forever
Container* container = ui->active_container;
uint32_t layer = ui->active_layer;
uint32_t element = ui->active_element;
ui->active_container = NULL;
ui->active_element = 0;
if(container != NULL) {
ui_lua_dispatch_deselect(ui, gpu, container, layer, element);
ui_lua_dispatch_deselect(ui, gpu, container, element);
}
}
void set_active_element(uint32_t container, uint32_t layer, uint32_t element, UIContext* ui) {
void set_active_element(uint32_t container, uint32_t element, UIContext* ui) {
Container* container_ptr = context_container(container, ui);
if(container_ptr == NULL) {
fprintf(stderr, "set_active_element passed invalid container id");
@ -1396,7 +1735,6 @@ void set_active_element(uint32_t container, uint32_t layer, uint32_t element, UI
}
ui->active_element = element;
ui->active_layer = layer;
ui->active_container = container_ptr;
}

@ -11,6 +11,18 @@
#define UI_LUA_CONTEXT "ui_context"
#define UI_LUA_GPU "ui_gpu"
#define UI_LUA_ELEMENT_META "ui_element"
// Opaque element handle passed to scripts. container_id + generation let the
// binding detect stale handles (container unloaded / slot recycled) and raise
// a Lua error instead of touching a reused slot.
typedef struct UIElementHandleStruct {
uint32_t slot;
uint32_t generation;
uint32_t is_string;
uint32_t container_id;
} UIElementHandle;
static void* registry_pointer(lua_State* L, const char* key) {
lua_getfield(L, LUA_REGISTRYINDEX, key);
void* ptr = lua_touserdata(L, -1);
@ -27,73 +39,272 @@ static void set_registry_pointers(lua_State* L, Container* c, UIContext* ui, Ren
lua_setfield(L, LUA_REGISTRYINDEX, UI_LUA_GPU);
}
static GPUDrawable* check_drawable(lua_State* L, Container* c, uint32_t layer, uint32_t drawable) {
if(layer >= c->layer_count) {
luaL_error(L, "layer %d out of range", layer);
// ---------- handle plumbing ----------
static UIElementHandle* check_handle(lua_State* L, int idx) {
return luaL_checkudata(L, idx, UI_LUA_ELEMENT_META);
}
static Container* resolve_handle(lua_State* L, UIElementHandle* h) {
UIContext* ui = registry_pointer(L, UI_LUA_CONTEXT);
Container* c = context_container(h->container_id, ui);
if(c == NULL) {
luaL_error(L, "element handle's container %d is unloaded", h->container_id);
}
if(h->is_string) {
if(h->slot >= c->num_strings
|| c->string_live[h->slot] == 0
|| c->string_generation[h->slot] != h->generation) {
luaL_error(L, "stale string handle (slot %d)", h->slot);
}
} else {
if(h->slot >= c->num_drawables
|| c->drawable_live[h->slot] == 0
|| c->drawable_generation[h->slot] != h->generation) {
luaL_error(L, "stale element handle (slot %d)", h->slot);
}
}
if(drawable >= c->layers[layer].data.num_drawables) {
luaL_error(L, "drawable %d out of range in layer %d", drawable, layer);
return c;
}
// Pushes the handle userdata and refs it against the slot so dispatch can
// pass the same object back to the script
static void push_new_handle(lua_State* L, Container* c, uint32_t slot, uint32_t is_string) {
UIElementHandle* h = lua_newuserdata(L, sizeof(UIElementHandle));
h->slot = slot;
h->is_string = is_string;
h->container_id = c->id;
h->generation = is_string ? c->string_generation[slot] : c->drawable_generation[slot];
luaL_setmetatable(L, UI_LUA_ELEMENT_META);
lua_pushvalue(L, -1);
int ref = luaL_ref(L, LUA_REGISTRYINDEX);
if(is_string) {
c->string_ref[slot] = ref;
} else {
c->drawable_ref[slot] = ref;
}
}
static void push_element_handle(lua_State* L, Container* c, uint32_t element) {
int ref = (element < c->cap_drawables) ? c->drawable_ref[element] : LUA_NOREF;
if(ref == LUA_NOREF) {
lua_pushnil(L);
} else {
lua_rawgeti(L, LUA_REGISTRYINDEX, ref);
}
return &c->layers[layer].drawables_buffer[drawable];
}
// ui.set_pos(layer, drawable, x, y)
static int lua_ui_set_pos(lua_State* L) {
// ---------- declaration table parsing ----------
static float opt_number_field(lua_State* L, int table, const char* key, float def) {
lua_getfield(L, table, key);
float v = lua_isnil(L, -1) ? def : (float)luaL_checknumber(L, -1);
lua_pop(L, 1);
return v;
}
static uint32_t opt_integer_field(lua_State* L, int table, const char* key, uint32_t def) {
lua_getfield(L, table, key);
uint32_t v = lua_isnil(L, -1) ? def : (uint32_t)luaL_checkinteger(L, -1);
lua_pop(L, 1);
return v;
}
static void opt_vec2_field(lua_State* L, int table, const char* key, vec2 out) {
out[0] = 0;
out[1] = 0;
lua_getfield(L, table, key);
if(!lua_isnil(L, -1)) {
luaL_checktype(L, -1, LUA_TTABLE);
for(int i = 0; i < 2; i++) {
lua_rawgeti(L, -1, i + 1);
out[i] = (float)luaL_checknumber(L, -1);
lua_pop(L, 1);
}
}
lua_pop(L, 1);
}
// Reads {r, g, b, a} from the table at the top of the stack
static void read_vec4(lua_State* L, vec4 out) {
luaL_checktype(L, -1, LUA_TTABLE);
for(int i = 0; i < 4; i++) {
lua_rawgeti(L, -1, i + 1);
out[i] = (float)luaL_checknumber(L, -1);
lua_pop(L, 1);
}
}
// color={r,g,b,a} fills all corners; colors={{...},{...},{...},{...}} sets
// each corner
static void opt_corner_colors(lua_State* L, int table, vec4 corners[4]) {
memset(corners, 0, sizeof(vec4)*4);
lua_getfield(L, table, "color");
if(!lua_isnil(L, -1)) {
vec4 color;
read_vec4(L, color);
for(int i = 0; i < 4; i++) {
memcpy(corners[i], color, sizeof(vec4));
}
lua_pop(L, 1);
return;
}
lua_pop(L, 1);
lua_getfield(L, table, "colors");
if(!lua_isnil(L, -1)) {
luaL_checktype(L, -1, LUA_TTABLE);
for(int i = 0; i < 4; i++) {
lua_rawgeti(L, -1, i + 1);
read_vec4(L, corners[i]);
lua_pop(L, 1);
}
}
lua_pop(L, 1);
}
// ---------- element constructors ----------
// ui.rect{pos=, size=, z=, type=, var=, events=, color=|colors=} -> handle
static int lua_ui_rect(lua_State* L) {
Container* container = registry_pointer(L, UI_LUA_CONTAINER);
RenderContext* gpu = registry_pointer(L, UI_LUA_GPU);
luaL_checktype(L, 1, LUA_TTABLE);
GPUDrawable drawable;
memset(&drawable, 0, sizeof(GPUDrawable));
opt_vec2_field(L, 1, "pos", drawable.pos);
opt_vec2_field(L, 1, "size", drawable.size);
opt_corner_colors(L, 1, drawable.color);
drawable.type = opt_integer_field(L, 1, "type", DRAWABLE_TYPE_RECT);
drawable.var = opt_integer_field(L, 1, "var", 0);
drawable.events = opt_integer_field(L, 1, "events", 0);
drawable.z = opt_number_field(L, 1, "z", 0);
uint32_t slot;
if(ui_create_drawable(container, &drawable, gpu, &slot) != VK_SUCCESS) {
return luaL_error(L, "ui.rect: failed to create drawable");
}
push_new_handle(L, container, slot, 0);
return 1;
}
// ui.text{pos=, z=, size=, color=, font=, max_length=, text=} -> handle
static int lua_ui_text(lua_State* L) {
Container* container = registry_pointer(L, UI_LUA_CONTAINER);
UIContext* ui = registry_pointer(L, UI_LUA_CONTEXT);
RenderContext* gpu = registry_pointer(L, UI_LUA_GPU);
uint32_t layer = luaL_checkinteger(L, 1);
uint32_t drawable = luaL_checkinteger(L, 2);
GPUDrawable* d = check_drawable(L, container, layer, drawable);
luaL_checktype(L, 1, LUA_TTABLE);
GPUString string;
memset(&string, 0, sizeof(GPUString));
opt_vec2_field(L, 1, "pos", string.pos);
string.size = opt_number_field(L, 1, "size", 16);
string.font = opt_integer_field(L, 1, "font", 0);
string.max_length = opt_integer_field(L, 1, "max_length", 0);
string.z = opt_number_field(L, 1, "z", 0);
string.color[0] = 1;
string.color[1] = 1;
string.color[2] = 1;
string.color[3] = 1;
lua_getfield(L, 1, "color");
if(!lua_isnil(L, -1)) {
read_vec4(L, string.color);
}
lua_pop(L, 1);
uint32_t slot;
if(ui_create_string(container, &string, gpu, &slot) != VK_SUCCESS) {
return luaL_error(L, "ui.text: failed to create string");
}
lua_getfield(L, 1, "text");
if(!lua_isnil(L, -1)) {
update_ui_string(luaL_checkstring(L, -1), container, slot, ui, gpu);
}
lua_pop(L, 1);
push_new_handle(L, container, slot, 1);
return 1;
}
// ---------- handle methods ----------
d->pos[0] = luaL_checknumber(L, 3);
d->pos[1] = luaL_checknumber(L, 4);
// h:set_pos(x, y)
static int lua_element_set_pos(lua_State* L) {
UIElementHandle* h = check_handle(L, 1);
Container* c = resolve_handle(L, h);
RenderContext* gpu = registry_pointer(L, UI_LUA_GPU);
if(h->is_string) {
GPUString* s = &c->strings_buffer[h->slot];
s->pos[0] = luaL_checknumber(L, 2);
s->pos[1] = luaL_checknumber(L, 3);
add_transfers(
&s->pos,
c->strings,
h->slot*sizeof(GPUString) + offsetof(GPUString, pos),
sizeof(vec2),
gpu);
} else {
GPUDrawable* d = &c->drawables_buffer[h->slot];
d->pos[0] = luaL_checknumber(L, 2);
d->pos[1] = luaL_checknumber(L, 3);
add_transfers(
&d->pos,
container->layers[layer].drawables,
drawable*sizeof(GPUDrawable) + offsetof(GPUDrawable, pos),
c->drawables,
h->slot*sizeof(GPUDrawable) + offsetof(GPUDrawable, pos),
sizeof(vec2),
gpu);
}
return 0;
}
// ui.set_corner_color(layer, drawable, corner, r, g, b, a)
static int lua_ui_set_corner_color(lua_State* L) {
Container* container = registry_pointer(L, UI_LUA_CONTAINER);
// h:set_corner_color(corner, r, g, b, a)
static int lua_element_set_corner_color(lua_State* L) {
UIElementHandle* h = check_handle(L, 1);
Container* c = resolve_handle(L, h);
RenderContext* gpu = registry_pointer(L, UI_LUA_GPU);
uint32_t layer = luaL_checkinteger(L, 1);
uint32_t drawable = luaL_checkinteger(L, 2);
uint32_t corner = luaL_checkinteger(L, 3);
if(h->is_string) {
return luaL_error(L, "set_corner_color is not valid on text elements");
}
uint32_t corner = luaL_checkinteger(L, 2);
if(corner > 3) {
return luaL_error(L, "corner %d out of range", corner);
}
GPUDrawable* d = check_drawable(L, container, layer, drawable);
d->color[corner][0] = luaL_checknumber(L, 4);
d->color[corner][1] = luaL_checknumber(L, 5);
d->color[corner][2] = luaL_checknumber(L, 6);
d->color[corner][3] = luaL_checknumber(L, 7);
GPUDrawable* d = &c->drawables_buffer[h->slot];
d->color[corner][0] = luaL_checknumber(L, 3);
d->color[corner][1] = luaL_checknumber(L, 4);
d->color[corner][2] = luaL_checknumber(L, 5);
d->color[corner][3] = luaL_checknumber(L, 6);
add_transfers(
&d->color[corner],
container->layers[layer].drawables,
drawable*sizeof(GPUDrawable) + offsetof(GPUDrawable, color) + corner*sizeof(vec4),
c->drawables,
h->slot*sizeof(GPUDrawable) + offsetof(GPUDrawable, color) + corner*sizeof(vec4),
sizeof(vec4),
gpu);
return 0;
}
// ui.get_corner_color(layer, drawable, corner) -> r, g, b, a
static int lua_ui_get_corner_color(lua_State* L) {
Container* container = registry_pointer(L, UI_LUA_CONTAINER);
uint32_t layer = luaL_checkinteger(L, 1);
uint32_t drawable = luaL_checkinteger(L, 2);
uint32_t corner = luaL_checkinteger(L, 3);
// h:corner_color(corner) -> r, g, b, a
static int lua_element_corner_color(lua_State* L) {
UIElementHandle* h = check_handle(L, 1);
Container* c = resolve_handle(L, h);
if(h->is_string) {
return luaL_error(L, "corner_color is not valid on text elements");
}
uint32_t corner = luaL_checkinteger(L, 2);
if(corner > 3) {
return luaL_error(L, "corner %d out of range", corner);
}
GPUDrawable* d = check_drawable(L, container, layer, drawable);
GPUDrawable* d = &c->drawables_buffer[h->slot];
lua_pushnumber(L, d->color[corner][0]);
lua_pushnumber(L, d->color[corner][1]);
lua_pushnumber(L, d->color[corner][2]);
@ -101,36 +312,81 @@ static int lua_ui_get_corner_color(lua_State* L) {
return 4;
}
// ui.set_string(layer, string_index, text)
static int lua_ui_set_string(lua_State* L) {
Container* container = registry_pointer(L, UI_LUA_CONTAINER);
// h:set_text(text)
static int lua_element_set_text(lua_State* L) {
UIElementHandle* h = check_handle(L, 1);
Container* c = resolve_handle(L, h);
UIContext* ui = registry_pointer(L, UI_LUA_CONTEXT);
RenderContext* gpu = registry_pointer(L, UI_LUA_GPU);
uint32_t layer = luaL_checkinteger(L, 1);
uint32_t string_index = luaL_checkinteger(L, 2);
const char* text = luaL_checkstring(L, 3);
if(layer >= container->layer_count) {
return luaL_error(L, "layer %d out of range", layer);
if(!h->is_string) {
return luaL_error(L, "set_text is not valid on rect elements");
}
update_ui_string(luaL_checkstring(L, 2), c, h->slot, ui, gpu);
return 0;
}
// h:set_z(z)
static int lua_element_set_z(lua_State* L) {
UIElementHandle* h = check_handle(L, 1);
Container* c = resolve_handle(L, h);
RenderContext* gpu = registry_pointer(L, UI_LUA_GPU);
float z = luaL_checknumber(L, 2);
if(h->is_string) {
ui_set_string_z(c, h->slot, z, gpu);
} else {
ui_set_drawable_z(c, h->slot, z, gpu);
}
if(string_index >= container->layers[layer].data.max_strings) {
return luaL_error(L, "string %d out of range in layer %d", string_index, layer);
return 0;
}
// h:focus()
static int lua_element_focus(lua_State* L) {
UIElementHandle* h = check_handle(L, 1);
Container* c = resolve_handle(L, h);
UIContext* ui = registry_pointer(L, UI_LUA_CONTEXT);
if(h->is_string) {
return luaL_error(L, "focus is not valid on text elements");
}
update_ui_string(text, container, layer, string_index, ui, gpu);
set_active_element(c->id, h->slot, ui);
return 0;
}
// ui.focus(layer, element)
static int lua_ui_focus(lua_State* L) {
Container* container = registry_pointer(L, UI_LUA_CONTAINER);
// h:is_focused() -> bool
static int lua_element_is_focused(lua_State* L) {
UIElementHandle* h = check_handle(L, 1);
Container* c = resolve_handle(L, h);
UIContext* ui = registry_pointer(L, UI_LUA_CONTEXT);
uint32_t layer = luaL_checkinteger(L, 1);
uint32_t element = luaL_checkinteger(L, 2);
set_active_element(container->id, layer, element, ui);
lua_pushboolean(L,
h->is_string == 0
&& ui->active_container == c
&& ui->active_element == h->slot);
return 1;
}
// h:remove()
static int lua_element_remove(lua_State* L) {
UIElementHandle* h = check_handle(L, 1);
Container* c = resolve_handle(L, h);
RenderContext* gpu = registry_pointer(L, UI_LUA_GPU);
if(h->is_string) {
luaL_unref(L, LUA_REGISTRYINDEX, c->string_ref[h->slot]);
c->string_ref[h->slot] = LUA_NOREF;
ui_destroy_string(c, h->slot, gpu);
} else {
luaL_unref(L, LUA_REGISTRYINDEX, c->drawable_ref[h->slot]);
c->drawable_ref[h->slot] = LUA_NOREF;
ui_destroy_drawable(c, h->slot, gpu);
}
return 0;
}
// ---------- ui table functions ----------
// ui.blur()
static int lua_ui_blur(lua_State* L) {
UIContext* ui = registry_pointer(L, UI_LUA_CONTEXT);
@ -140,18 +396,13 @@ static int lua_ui_blur(lua_State* L) {
return 0;
}
// ui.is_focused(layer, element) -> bool
static int lua_ui_is_focused(lua_State* L) {
// ui.raise() — bring the calling script's container to the front
static int lua_ui_raise(lua_State* L) {
Container* container = registry_pointer(L, UI_LUA_CONTAINER);
UIContext* ui = registry_pointer(L, UI_LUA_CONTEXT);
uint32_t layer = luaL_checkinteger(L, 1);
uint32_t element = luaL_checkinteger(L, 2);
lua_pushboolean(L,
ui->active_container == container
&& ui->active_layer == layer
&& ui->active_element == element);
return 1;
ui_container_to_front(container->id, ui);
return 0;
}
// ui.hsv_to_rgb(h, s, v) -> r, g, b
@ -186,13 +437,10 @@ static int lua_ui_rgb_to_hsv(lua_State* L) {
void ui_lua_register(lua_State* L) {
static const luaL_Reg ui_funcs[] = {
{"set_pos", lua_ui_set_pos},
{"set_corner_color", lua_ui_set_corner_color},
{"get_corner_color", lua_ui_get_corner_color},
{"set_string", lua_ui_set_string},
{"focus", lua_ui_focus},
{"rect", lua_ui_rect},
{"text", lua_ui_text},
{"blur", lua_ui_blur},
{"is_focused", lua_ui_is_focused},
{"raise", lua_ui_raise},
{"hsv_to_rgb", lua_ui_hsv_to_rgb},
{"rgb_to_hsv", lua_ui_rgb_to_hsv},
{NULL, NULL},
@ -200,6 +448,22 @@ void ui_lua_register(lua_State* L) {
luaL_newlib(L, ui_funcs);
lua_setglobal(L, "ui");
static const luaL_Reg element_methods[] = {
{"set_pos", lua_element_set_pos},
{"set_corner_color", lua_element_set_corner_color},
{"corner_color", lua_element_corner_color},
{"set_text", lua_element_set_text},
{"set_z", lua_element_set_z},
{"focus", lua_element_focus},
{"is_focused", lua_element_is_focused},
{"remove", lua_element_remove},
{NULL, NULL},
};
luaL_newmetatable(L, UI_LUA_ELEMENT_META);
luaL_newlib(L, element_methods);
lua_setfield(L, -2, "__index");
lua_pop(L, 1);
lua_pushinteger(L, GLFW_PRESS); lua_setglobal(L, "PRESS");
lua_pushinteger(L, GLFW_RELEASE); lua_setglobal(L, "RELEASE");
lua_pushinteger(L, GLFW_MOUSE_BUTTON_LEFT); lua_setglobal(L, "MOUSE_LEFT");
@ -208,6 +472,13 @@ void ui_lua_register(lua_State* L) {
lua_pushinteger(L, GLFW_KEY_ESCAPE); lua_setglobal(L, "KEY_ESCAPE");
lua_pushinteger(L, GLFW_KEY_ENTER); lua_setglobal(L, "KEY_ENTER");
lua_pushinteger(L, GLFW_KEY_BACKSPACE); lua_setglobal(L, "KEY_BACKSPACE");
lua_pushinteger(L, DRAWABLE_TYPE_RECT); lua_setglobal(L, "RECT");
lua_pushinteger(L, DRAWABLE_TYPE_RECT_HSV); lua_setglobal(L, "RECT_HSV");
lua_pushinteger(L, DRAWABLE_TYPE_IMAGE); lua_setglobal(L, "IMAGE");
lua_pushinteger(L, UI_EVENT_BUTTON); lua_setglobal(L, "EVENT_BUTTON");
lua_pushinteger(L, UI_EVENT_SCROLL); lua_setglobal(L, "EVENT_SCROLL");
lua_pushinteger(L, UI_EVENT_CURSOR); lua_setglobal(L, "EVENT_CURSOR");
}
VkResult ui_lua_load_container(
@ -237,7 +508,7 @@ VkResult ui_lua_load_container(
// Ref the env first so the chunk (now at the top) can be called
c->script_env = luaL_ref(L, LUA_REGISTRYINDEX);
// Top-level script code may call ui.* during execution
// Top-level script code declares elements via ui.* during execution
set_registry_pointers(L, c, ui, gpu);
if(lua_pcall(L, 0, 0, 0) != LUA_OK) {
@ -285,11 +556,29 @@ static bool ui_lua_finish_dispatch(lua_State* L, const char* handler, int nargs)
return consumed;
}
VkResult ui_lua_call(
UIContext* ui,
RenderContext* gpu,
Container* c,
const char* function,
const char* argument) {
lua_State* L = ui->lua;
if(!ui_lua_begin_dispatch(L, ui, gpu, c, function)) {
return VK_ERROR_UNKNOWN;
}
lua_pushstring(L, argument);
if(lua_pcall(L, 1, 0, 0) != LUA_OK) {
fprintf(stderr, "%s: %s\n", function, lua_tostring(L, -1));
lua_pop(L, 1);
return VK_ERROR_UNKNOWN;
}
return VK_SUCCESS;
}
bool ui_lua_dispatch_button(
UIContext* ui,
RenderContext* gpu,
Container* c,
uint32_t layer,
uint32_t element,
float x,
float y,
@ -298,93 +587,82 @@ bool ui_lua_dispatch_button(
int mods) {
lua_State* L = ui->lua;
if(!ui_lua_begin_dispatch(L, ui, gpu, c, "on_button")) return false;
lua_pushinteger(L, layer);
lua_pushinteger(L, element);
push_element_handle(L, c, element);
lua_pushnumber(L, x);
lua_pushnumber(L, y);
lua_pushinteger(L, button);
lua_pushinteger(L, action);
lua_pushinteger(L, mods);
return ui_lua_finish_dispatch(L, "on_button", 7);
return ui_lua_finish_dispatch(L, "on_button", 6);
}
bool ui_lua_dispatch_cursor(
UIContext* ui,
RenderContext* gpu,
Container* c,
uint32_t layer,
uint32_t element,
float x,
float y) {
lua_State* L = ui->lua;
if(!ui_lua_begin_dispatch(L, ui, gpu, c, "on_cursor")) return false;
lua_pushinteger(L, layer);
lua_pushinteger(L, element);
push_element_handle(L, c, element);
lua_pushnumber(L, x);
lua_pushnumber(L, y);
return ui_lua_finish_dispatch(L, "on_cursor", 4);
return ui_lua_finish_dispatch(L, "on_cursor", 3);
}
bool ui_lua_dispatch_scroll(
UIContext* ui,
RenderContext* gpu,
Container* c,
uint32_t layer,
uint32_t element,
double x,
double y) {
lua_State* L = ui->lua;
if(!ui_lua_begin_dispatch(L, ui, gpu, c, "on_scroll")) return false;
lua_pushinteger(L, layer);
lua_pushinteger(L, element);
push_element_handle(L, c, element);
lua_pushnumber(L, x);
lua_pushnumber(L, y);
return ui_lua_finish_dispatch(L, "on_scroll", 4);
return ui_lua_finish_dispatch(L, "on_scroll", 3);
}
bool ui_lua_dispatch_key(
UIContext* ui,
RenderContext* gpu,
Container* c,
uint32_t layer,
uint32_t element,
int key,
int action,
int mods) {
lua_State* L = ui->lua;
if(!ui_lua_begin_dispatch(L, ui, gpu, c, "on_key")) return false;
lua_pushinteger(L, layer);
lua_pushinteger(L, element);
push_element_handle(L, c, element);
lua_pushinteger(L, key);
lua_pushinteger(L, action);
lua_pushinteger(L, mods);
return ui_lua_finish_dispatch(L, "on_key", 5);
return ui_lua_finish_dispatch(L, "on_key", 4);
}
bool ui_lua_dispatch_text(
UIContext* ui,
RenderContext* gpu,
Container* c,
uint32_t layer,
uint32_t element,
unsigned int codepoint) {
lua_State* L = ui->lua;
if(!ui_lua_begin_dispatch(L, ui, gpu, c, "on_text")) return false;
lua_pushinteger(L, layer);
lua_pushinteger(L, element);
push_element_handle(L, c, element);
lua_pushinteger(L, codepoint);
return ui_lua_finish_dispatch(L, "on_text", 3);
return ui_lua_finish_dispatch(L, "on_text", 2);
}
bool ui_lua_dispatch_deselect(
UIContext* ui,
RenderContext* gpu,
Container* c,
uint32_t layer,
uint32_t element) {
lua_State* L = ui->lua;
if(!ui_lua_begin_dispatch(L, ui, gpu, c, "on_deselect")) return false;
lua_pushinteger(L, layer);
lua_pushinteger(L, element);
return ui_lua_finish_dispatch(L, "on_deselect", 2);
push_element_handle(L, c, element);
return ui_lua_finish_dispatch(L, "on_deselect", 1);
}