diff --git a/client/include/ui.h b/client/include/ui.h index 4556d58..e05c590 100644 --- a/client/include/ui.h +++ b/client/include/ui.h @@ -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 { - VkBuffer strings[MAX_FRAMES_IN_FLIGHT]; - VkBuffer drawables[MAX_FRAMES_IN_FLIGHT]; - VkBuffer layer[MAX_FRAMES_IN_FLIGHT]; +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 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 { @@ -204,7 +216,7 @@ struct UIContextStruct { VkDescriptorSetLayout textures_layout; VkDescriptorPool fonts_pool; VkDescriptorPool textures_pool; - + GraphicsPipeline pipeline; ComputePipeline string_pipeline; @@ -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); + 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); diff --git a/client/include/ui_lua.h b/client/include/ui_lua.h index b958ad9..2c84430 100644 --- a/client/include/ui_lua.h +++ b/client/include/ui_lua.h @@ -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 diff --git a/client/script/color_picker.lua b/client/script/color_picker.lua index 2e6a286..37183d5 100644 --- a/client/script/color_picker.lua +++ b/client/script/color_picker.lua @@ -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) diff --git a/client/script/mode_string.lua b/client/script/mode_string.lua new file mode 100644 index 0000000..72e337a --- /dev/null +++ b/client/script/mode_string.lua @@ -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") diff --git a/client/shader/string.comp b/client/shader/string.comp index 5c047ec..2aa37ac 100644 --- a/client/shader/string.comp +++ b/client/shader/string.comp @@ -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; } } diff --git a/client/shader/ui.frag b/client/shader/ui.frag index eb7e3f8..38e7027 100644 --- a/client/shader/ui.frag +++ b/client/shader/ui.frag @@ -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; diff --git a/client/shader/ui.vert b/client/shader/ui.vert index f447d2a..a2368d0 100644 --- a/client/shader/ui.vert +++ b/client/shader/ui.vert @@ -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; } - diff --git a/client/shader/ui_common.glsl b/client/shader/ui_common.glsl index b9d24fd..5563909 100644 --- a/client/shader/ui_common.glsl +++ b/client/shader/ui_common.glsl @@ -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; diff --git a/client/src/draw.c b/client/src/draw.c index 834b761..c0fa5b3 100644 --- a/client/src/draw.c +++ b/client/src/draw.c @@ -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]; - 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); - } + 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, 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]; - 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)); - } - } + 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, c->container[frame], offsetof(GPUContainer, dispatch_strings)); } - } VkResult draw_frame( diff --git a/client/src/editor.c b/client/src/editor.c index 3276693..8e8e320 100644 --- a/client/src/editor.c +++ b/client/src/editor.c @@ -1,6 +1,7 @@ #include #include "hex.h" #include "engine.h" +#include "ui_lua.h" #include "vulkan/vulkan_core.h" #include @@ -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) { +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) { diff --git a/client/src/engine.c b/client/src/engine.c index c547b13..9f6df54 100644 --- a/client/src/engine.c +++ b/client/src/engine.c @@ -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]); diff --git a/client/src/ui.c b/client/src/ui.c index 904b028..0ec7108 100644 --- a/client/src/ui.c +++ b/client/src/ui.c @@ -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(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(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->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)); } - if(layer->data.max_drawables > 0) { - retire_buffer(layer->drawables[f], layer->drawables_memory[f], gpu); + slot = c->num_drawables; + c->num_drawables += 1; + } + + 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(sr->codes_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(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; } - free(layer->string_resources[s].codes_buffer); + VK_RESULT(container_realloc_drawable_pool(c, c->cap_drawables, new_cap, gpu)); } - free(layer->string_resources); - free(layer->strings_buffer); + 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; } - if(layer->data.num_drawables > 0) { - free(layer->drawables_buffer); + 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); - - 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])); + Container* c = &context->containers[index]; + memset(c, 0, sizeof(Container)); + + c->cap_drawables = CONTAINER_MIN_DRAWABLES; + c->cap_strings = CONTAINER_MIN_STRINGS; + c->cap_codes = CONTAINER_MIN_CODES; + + uint32_t pool = c->cap_drawables + c->cap_codes; + + 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; } - - context->containers[index].layer_count = container->layer_count; - - 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->string_ref = malloc(sizeof(int)*c->cap_strings); + for(uint32_t i = 0; i < c->cap_strings; i++) { + c->string_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; - } - - 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); - 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 f = 0; f < MAX_FRAMES_IN_FLIGHT; f++) { + 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]); } - 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); + 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; + c->id = input->id; + context->container_order[context->container_order_count] = index; + context->container_order_count += 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); + VK_RESULT(rebuild_indices(c, 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); - - 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); - } - } - } - - 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,31 +1660,33 @@ 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 - && ui_contains( - cursor, - container_offset, - ui->containers[c].data.size, - &ui->containers[c].layers[l].drawables_buffer[d], - position)) { - *container = ui->containers[c].id; - *layer = l; - *element = d; - return true; - } + vec2 position) { + // 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, + c->data.size, + drawable, + position)) { + *container = c->id; + *element = slot; + return true; } } } @@ -1373,22 +1694,40 @@ bool ui_intersect( 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; } diff --git a/client/src/ui_lua.c b/client/src/ui_lua.c index e8139ea..37b2b59 100644 --- a/client/src/ui_lua.c +++ b/client/src/ui_lua.c @@ -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); + } + } + 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; } - if(drawable >= c->layers[layer].data.num_drawables) { - luaL_error(L, "drawable %d out of range in layer %d", drawable, layer); +} + +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); - d->pos[0] = luaL_checknumber(L, 3); - d->pos[1] = luaL_checknumber(L, 4); + uint32_t slot; + if(ui_create_string(container, &string, gpu, &slot) != VK_SUCCESS) { + return luaL_error(L, "ui.text: failed to create string"); + } - add_transfers( - &d->pos, - container->layers[layer].drawables, - drawable*sizeof(GPUDrawable) + offsetof(GPUDrawable, pos), - sizeof(vec2), - gpu); + 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 ---------- + +// 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, + 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"); } - if(string_index >= container->layers[layer].data.max_strings) { - return luaL_error(L, "string %d out of range in layer %d", string_index, layer); + + 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); } + return 0; +} - update_ui_string(text, container, layer, string_index, ui, gpu); +// 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"); + } + + 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,20 +437,33 @@ 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}, - {"blur", lua_ui_blur}, - {"is_focused", lua_ui_is_focused}, - {"hsv_to_rgb", lua_ui_hsv_to_rgb}, - {"rgb_to_hsv", lua_ui_rgb_to_hsv}, + {"rect", lua_ui_rect}, + {"text", lua_ui_text}, + {"blur", lua_ui_blur}, + {"raise", lua_ui_raise}, + {"hsv_to_rgb", lua_ui_hsv_to_rgb}, + {"rgb_to_hsv", lua_ui_rgb_to_hsv}, {NULL, NULL}, }; 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); }