#include "static-ui.hpp" #include "ecs.hpp" #include "font.hpp" #include "game-settings.hpp" #include "pk.h" #include "player-input.hpp" #include "static-plane.hpp" #include "vendor-glm-include.hpp" #include "window.hpp" #include #include #include #include #include // 2025-09-25 TODO PERF // Consider writing a generic recursive function for the boxes. // It would make some of the work here easier to digest. // Existing recursive functions *might* be too big. // Some things (like checking for removing children) could be split out. // However, this would likely have performance implications (maybe). // I should come up with some concrete UI setups to test against. const char *const ui_ux_mouse_left = "ui_ux_mouse_left"; const char *const ui_ux_mouse_right = "ui_ux_mouse_right"; const char *const ui_ux_mouse_middle = "ui_ux_mouse_middle"; #ifndef PKE_UI_CLR_DISABLED_BORDER #define PKE_UI_CLR_DISABLED_BORDER glm::vec4(0.6,0.6,0.6,1.0) #endif #ifndef PKE_UI_CLR_DISABLED_BACKGROUND #define PKE_UI_CLR_DISABLED_BACKGROUND glm::vec4(0.4,0.4,0.4,1.0) #endif TypeSafeInt_B(PKE_UI_BOX_TYPE); TypeSafeInt_B(PKE_UI_BOX_FLAG); TypeSafeInt_B(PKE_UI_BOX_STATE_FLAG); struct pke_ui_box_instance_buffer_item { glm::mat4 pos_scale; glm::vec4 color_border; glm::vec4 color_background; glm::vec2 px_scale; float depth; float texture_layer; }; typedef pk_bkt_arr_t texture_binding_bkt_arr; struct pke_ui_master { pk_membucket *bkt; pke_ui_box **root_boxes; pke_ui_box_count_T h_root_boxes; pke_ui_box_count_T r_root_boxes; pke_ui_graphics_bindings bindings; texture_binding_bkt_arr bindings_texture{}; bool should_update_buffer = false; glm::vec2 px_scale; pk_arr_t events_this_tick; pke_input_action_set_handle input_action_set_handle = pke_input_action_set_handle_MAX; struct pke_ui_master_state { const pke_input_event *mouse_left = nullptr; const pke_input_event *mouse_right = nullptr; const pke_input_event *mouse_middle = nullptr; pke_ui_box *deepest_pressed = nullptr; } state; pk_arr_t boxes_to_delete{}; } pke_ui_master; void pke_ui_teardown_box_recursive(pke_ui_box *box, uint8_t depth); // typedef void(*pke_box_search)(pke_ui_box &box); typedef std::function pke_box_iterate ; /* 2025-07-28 JCB TODO * Needs PERF? Consider separate searches by need (depth/breadth). * With a depth-first, if the parent kept track of how deep its children are, we could short-cut the search (assuming we're searching for depth-most) */ void pke_ui_internal_box_iterate_top_down_recursive(pke_box_iterate *iters, int n_iters, pke_ui_box **boxes, pke_ui_box_count_T box_count) { pke_ui_box_count_T i; int k; for (i = 0; i < box_count; ++i) { for (k = 0; k < n_iters; ++k) { iters[k](*boxes[i]); } } for (i = 0; i < box_count; ++i) { pke_ui_internal_box_iterate_top_down_recursive(iters, n_iters, boxes[i]->internal.children, boxes[i]->internal.h_children); } } void pke_ui_init() { pke_ui_master.bkt = pk_mem_bucket_create("pke ui", PK_MEM_DEFAULT_BUCKET_SIZE, PK_MEMBUCKET_FLAG_NONE); pke_ui_master.r_root_boxes = 8; pke_ui_master.root_boxes = pk_new_arr(pke_ui_master.r_root_boxes, pke_ui_master.bkt); pke_ui_master.h_root_boxes = 0; pke_ui_master.bindings = {}; pke_ui_master.events_this_tick.bkt = pke_ui_master.bkt; new (&pke_ui_master.bindings_texture) texture_binding_bkt_arr{ pk_bkt_arr_handle_MAX_constexpr, pke_ui_master.bkt, MemBkt_Vulkan }; pke_input_set ui_controls_set{}; ui_controls_set.title = "static ui"; ui_controls_set.actionCount = 3; ui_controls_set.bkt = pke_ui_master.bkt; ui_controls_set.actions = pk_new_arr(ui_controls_set.actionCount, ui_controls_set.bkt); ui_controls_set.flags = PKE_INPUT_ACTION_SET_FLAG_DO_NOT_SERIALIZE; ui_controls_set.actions[0].name = ui_ux_mouse_left; ui_controls_set.actions[0].masks[0] = pke_input_event_mask { .computedHash = PKE_INPUT_HASH_ALL_MOUSE_BUTTON_EVENTS, .button = GLFW_MOUSE_BUTTON_LEFT, }; ui_controls_set.actions[1].name = ui_ux_mouse_right; ui_controls_set.actions[1].masks[0] = pke_input_event_mask { .computedHash = PKE_INPUT_HASH_ALL_MOUSE_BUTTON_EVENTS, .button = GLFW_MOUSE_BUTTON_RIGHT, }; ui_controls_set.actions[2].name = ui_ux_mouse_middle; ui_controls_set.actions[2].masks[0] = pke_input_event_mask { .computedHash = PKE_INPUT_HASH_ALL_MOUSE_BUTTON_EVENTS, .button = GLFW_MOUSE_BUTTON_MIDDLE, }; pke_ui_master.input_action_set_handle = pke_input_register_set(std::move(ui_controls_set)); } void pke_ui_force_recalc() { pke_ui_master.should_update_buffer = true; } void pke_ui_init_bindings() { pkvk_buffer_create_data create_data{}; create_data.buffer_byte_length[0] = sizeof(pkeIntrinsicsPlane.vert[0]) * 4; create_data.buffer_byte_length[1] = sizeof(pkeIntrinsicsPlane.uv[0]) * 4; create_data.buffer_byte_length[2] = sizeof(pkeIntrinsicsPlane.index[0]) * 6; create_data.buffer_byte_length[3] = sizeof(pke_ui_box_instance_buffer_item); create_data.src_data[0] = &pkeIntrinsicsPlane.vert; create_data.src_data[1] = &pkeIntrinsicsPlane.uv; create_data.src_data[2] = &pkeIntrinsicsPlane.index; create_data.src_data[3] = nullptr; create_data.n_buffers = 4; create_data.index_index = 2; create_data.index_instance = 3; pkvk_buffer_create_data_out out{}; pkvk_buffer_create(&create_data, &out); pke_ui_master.bindings.bd_vertex.buffer = out.buffers[0]; pke_ui_master.bindings.bd_vertex.firstBinding = 0; pke_ui_master.bindings.bd_vertex.bindingCount = 1; pke_ui_master.bindings.bd_uv.buffer = out.buffers[1]; pke_ui_master.bindings.bd_uv.firstBinding = 1; pke_ui_master.bindings.bd_uv.bindingCount = 1; pke_ui_master.bindings.bd_index.buffer = out.buffers[2]; pke_ui_master.bindings.bd_index.firstBinding = 0; pke_ui_master.bindings.bd_index.bindingCount = 1; pke_ui_master.bindings.index_count = 6; pke_ui_master.bindings.bd_instance.buffer = out.buffers[3]; pke_ui_master.bindings.bd_instance.firstBinding = 2; pke_ui_master.bindings.bd_instance.bindingCount = 1; pke_ui_master.bindings.deviceMemoryVert = out.device_memory_vertex; pke_ui_master.bindings.deviceMemoryInst = out.device_memory_instance; pke_ui_master.bindings.instance_buffer_max_count = out.memory_requirements_instance.size / sizeof(pke_ui_box_instance_buffer_item); } /* UI layout notes Some restrictions: - children cannot change the size of a parent, parents must be sized manually - e.g.: exact font width - consider writing a method to calculate text - Children can ONLY be horizontal or vertical - this means that tables or similar need to consist of rows of columns */ void pke_ui_update_state(pke_ui_box &box) { double mouse_x, mouse_y; PKE_UI_BOX_STATE_FLAG old_state; PKE_UI_BOX_STATE_FLAG flgs; // update state old_state = box.state_flags; box.state_flags = PKE_UI_BOX_STATE_FLAG_NONE; if (PK_HAS_FLAG(box.flags, PKE_UI_BOX_FLAG_VISIBILITY_INVISIBLE)) { // invisible boxes have no state box.state_flags_mem = PKE_UI_BOX_STATE_FLAG_NONE; return; } if (pke_ui_master.state.mouse_left == nullptr || (pke_ui_master.state.mouse_left->data.mouse_button.isPressed == false && pke_ui_master.state.mouse_left->data.mouse_button.thisTick == false)) { flgs = PKE_UI_BOX_STATE_FLAG_ON_BTN_PRIMARY_RELEASE | PKE_UI_BOX_STATE_FLAG_ON_BTN_PRIMARY_PRESS | PKE_UI_BOX_STATE_FLAG_ON_BTN_PRIMARY_PRESSED; box.state_flags_mem &= ~flgs; } if (pke_ui_master.state.mouse_right == nullptr || (pke_ui_master.state.mouse_right->data.mouse_button.isPressed == false && pke_ui_master.state.mouse_right->data.mouse_button.thisTick == false)) { flgs = PKE_UI_BOX_STATE_FLAG_ON_BTN_SECONDARY_RELEASE | PKE_UI_BOX_STATE_FLAG_ON_BTN_SECONDARY_PRESS | PKE_UI_BOX_STATE_FLAG_ON_BTN_SECONDARY_PRESSED; box.state_flags_mem &= ~flgs; } if (pke_ui_master.state.mouse_middle == nullptr || (pke_ui_master.state.mouse_middle->data.mouse_button.isPressed == false && pke_ui_master.state.mouse_middle->data.mouse_button.thisTick == false)) { flgs = PKE_UI_BOX_STATE_FLAG_ON_BTN_MIDDLE_RELEASE | PKE_UI_BOX_STATE_FLAG_ON_BTN_MIDDLE_PRESS | PKE_UI_BOX_STATE_FLAG_ON_BTN_MIDDLE_PRESSED; box.state_flags_mem &= ~flgs; } pke_input_query_mouse_pos(mouse_x, mouse_y); if (mouse_x >= box.internal.px_corner.x && mouse_y >= box.internal.px_corner.y && mouse_x <= box.internal.px_corner.x + box.internal.px_size.x && mouse_y <= box.internal.px_corner.y + box.internal.px_size.y) { box.state_flags |= PKE_UI_BOX_STATE_FLAG_MOUSE_HOVER; if (!PK_HAS_FLAG(old_state, PKE_UI_BOX_STATE_FLAG_MOUSE_HOVER)) { box.state_flags |= PKE_UI_BOX_STATE_FLAG_MOUSE_ENTERED; } if (pke_ui_master.state.mouse_left != nullptr && pke_ui_master.state.mouse_left->data.mouse_button.thisTick) { if (pke_ui_master.state.mouse_left->data.mouse_button.isPressed) { box.state_flags |= PKE_UI_BOX_STATE_FLAG_ON_BTN_PRIMARY_PRESS; box.state_flags_mem |= PKE_UI_BOX_STATE_FLAG_ON_BTN_PRIMARY_PRESS; } else if (PK_HAS_FLAG(box.state_flags_mem, PKE_UI_BOX_STATE_FLAG_ON_BTN_PRIMARY_PRESS)) { box.state_flags |= PKE_UI_BOX_STATE_FLAG_ON_BTN_PRIMARY_PRESSED; if (box.type == PKE_UI_BOX_TYPE_BUTTON_TEXT || box.type == PKE_UI_BOX_TYPE_BUTTON_IMAGE) { if (pke_ui_master.state.deepest_pressed == nullptr || pke_ui_master.state.deepest_pressed->internal.depth < box.internal.depth) { pke_ui_master.state.deepest_pressed = &box; } } } } if (pke_ui_master.state.mouse_right != nullptr && pke_ui_master.state.mouse_right->data.mouse_button.thisTick) { if (pke_ui_master.state.mouse_right->data.mouse_button.isPressed) { box.state_flags |= PKE_UI_BOX_STATE_FLAG_ON_BTN_SECONDARY_PRESS; box.state_flags_mem |= PKE_UI_BOX_STATE_FLAG_ON_BTN_SECONDARY_PRESS; } else if (PK_HAS_FLAG(box.state_flags_mem, PKE_UI_BOX_STATE_FLAG_ON_BTN_SECONDARY_PRESS)) { box.state_flags |= PKE_UI_BOX_STATE_FLAG_ON_BTN_SECONDARY_PRESSED; } } if (pke_ui_master.state.mouse_middle != nullptr && pke_ui_master.state.mouse_middle->data.mouse_button.thisTick) { if (pke_ui_master.state.mouse_middle->data.mouse_button.isPressed) { box.state_flags |= PKE_UI_BOX_STATE_FLAG_ON_BTN_MIDDLE_PRESS; box.state_flags_mem |= PKE_UI_BOX_STATE_FLAG_ON_BTN_MIDDLE_PRESS; } else if (PK_HAS_FLAG(box.state_flags_mem, PKE_UI_BOX_STATE_FLAG_ON_BTN_MIDDLE_PRESS)) { box.state_flags |= PKE_UI_BOX_STATE_FLAG_ON_BTN_MIDDLE_PRESSED; } } } else if (PK_HAS_FLAG(old_state, PKE_UI_BOX_STATE_FLAG_MOUSE_HOVER)) { box.state_flags |= PKE_UI_BOX_STATE_FLAG_MOUSE_EXITED; } } struct pke_ui_flex_params { float px_per_unit; float unit_total; float used_units; }; bool pke_ui_calc_px(pk_arr_t &buffer, pk_arr_t> &tmp_txtr_buffer, pke_ui_flex_params *flex_params, pke_ui_box *box, uint8_t child_index) { assert(box != nullptr); pke_ui_box_count_T i; float flex_padding, l, t; glm::vec2 px_size; glm::vec2 px_min_size; glm::vec2 px_max_size; glm::vec2 parent_size_padded; glm::vec2 parent_pos_and_offset; if (box->isMarkedForRemoval == true) { // root if (box->internal.parent == nullptr) { // this shouldn't happen return true; } // TODO simplify for (i = 0; i < box->internal.parent->internal.h_children; ++i) { if (box->internal.parent->internal.children[i] == box) { break; } } for (; i < box->internal.parent->internal.h_children; ++i) { if (i+1 >= box->internal.parent->internal.r_children) { box->internal.parent->internal.children[i] = nullptr; break; } box->internal.parent->internal.children[i] = box->internal.parent->internal.children[i+1]; } box->internal.parent->internal.h_children -= 1; pke_ui_teardown_box_recursive(box, 0); pke_ui_master.should_update_buffer = true; return false; } assert(box->pos_top_left.x >= 0.0); assert(box->pos_top_left.y >= 0.0); assert(box->max_size.x >= 0.0); assert(box->max_size.y >= 0.0); assert(box->min_size.x >= 0.0); assert(box->min_size.y >= 0.0); assert(box->min_size.x <= box->max_size.x); assert(box->min_size.y <= box->max_size.y); PKE_UI_BOX_STATE_FLAG prev_state_flags = box->state_flags; PKE_UI_BOX_STATE_FLAG prev_state_flags_mem = box->state_flags_mem; if (box->internal.parent != nullptr) { parent_pos_and_offset.x = box->internal.parent->internal.px_corner.x + box->internal.parent->internal.px_padding_l; parent_pos_and_offset.y = box->internal.parent->internal.px_corner.y + box->internal.parent->internal.px_padding_t; parent_size_padded.x = box->internal.parent->internal.px_size.x; parent_size_padded.y = box->internal.parent->internal.px_size.y; // built-in padding parent_size_padded.x -= box->internal.parent->internal.px_padding_l + box->internal.parent->internal.px_padding_r; parent_size_padded.y -= box->internal.parent->internal.px_padding_t + box->internal.parent->internal.px_padding_b; px_size = parent_size_padded; } else { parent_pos_and_offset = glm::vec2(0); parent_size_padded = glm::vec2(Extent.width, Extent.height); px_size = glm::vec2(Extent.width, Extent.height); } px_min_size.x = box->min_size.x; px_min_size.y = box->min_size.y; px_max_size.x = box->max_size.x; px_max_size.y = box->max_size.y; box->internal.px_corner.x = 0 + parent_pos_and_offset.x + box->pos_top_left.x; box->internal.px_corner.y = 0 + parent_pos_and_offset.y + box->pos_top_left.y; if (PK_HAS_FLAG(box->flags, PKE_UI_BOX_FLAG_POSITION_TYPE_FLEX)) { assert(flex_params != nullptr); assert(box->internal.parent != nullptr); flex_padding = box->internal.parent->flex_padding * float(child_index + 1); if (box->internal.parent->flex_direction == 0) { px_size.x = flex_params->px_per_unit * box->flex_weight; px_size.y = parent_size_padded.y; box->internal.px_corner.x += flex_params->used_units * flex_params->px_per_unit; box->internal.px_corner.x += flex_padding; } else { px_size.x = parent_size_padded.x; px_size.y = flex_params->px_per_unit * box->flex_weight; box->internal.px_corner.y += flex_params->used_units * flex_params->px_per_unit; box->internal.px_corner.y += flex_padding; } flex_params->used_units += box->flex_weight; } else if (PK_HAS_FLAG(box->flags, PKE_UI_BOX_FLAG_POSITION_TYPE_STATIC)) { px_size.x -= box->pos_top_left.x; px_size.y -= box->pos_top_left.y; } else { assert(box->pos_top_left.x < 1.0); assert(box->pos_top_left.y < 1.0); box->internal.px_corner.x = parent_pos_and_offset.x; box->internal.px_corner.y = parent_pos_and_offset.y; float px_left = px_size.x * box->pos_top_left.x; float px_top = px_size.y * box->pos_top_left.y; box->internal.px_corner.x += px_left; box->internal.px_corner.y += px_top; px_size.x -= px_left; px_size.y -= px_top; px_min_size.x = box->min_size.x * parent_size_padded.x; px_min_size.y = box->min_size.y * parent_size_padded.y; px_max_size.x = box->max_size.x * parent_size_padded.x; px_max_size.y = box->max_size.y * parent_size_padded.y; } px_size = glm::clamp(px_size, px_min_size, px_max_size); // built-in padding box->internal.px_padding_l = built_in_offset; box->internal.px_padding_b = built_in_offset; box->internal.px_padding_r = built_in_offset; box->internal.px_padding_t = built_in_offset; if (PK_HAS_FLAG(box->flags, PKE_UI_BOX_FLAG_CENTER_HORIZONTAL)) { if (parent_size_padded.x > px_size.x) { box->internal.px_corner.x += (parent_size_padded.x - px_size.x) / 2.0; } } if (PK_HAS_FLAG(box->flags, PKE_UI_BOX_FLAG_CENTER_VERTICAL)) { if (parent_size_padded.y > px_size.y) { box->internal.px_corner.y += (parent_size_padded.y - px_size.y) / 2.0; } } box->internal.px_size.x = px_size.x; box->internal.px_size.y = px_size.y; if (PK_HAS_FLAG(box->flags, PKE_UI_BOX_FLAG_VISIBILITY_PIXEL_PERFECT)) { l = fmod(box->internal.px_corner.x, 1.0); t = fmod(box->internal.px_corner.y, 1.0); box->internal.px_corner.x -= l; box->internal.px_corner.y -= t; box->internal.px_size.x += l; box->internal.px_size.y += t; box->internal.px_size.x = floor(box->internal.px_size.x); box->internal.px_size.y = floor(box->internal.px_size.y); } // type-specific changes FontRenderSettings frs; switch (box->type) { case PKE_UI_BOX_TYPE_TEXT: if (box->type_data->text.font_type_render.font_render_handle != FontRenderHandle_MAX) { frs = FontType_GetFontRender(box->type_data->text.font_type_render)->settings; frs.surface_area_pos = box->internal.px_corner; frs.surface_area_size = box->internal.px_size; FontType_UpdateStringRender(box->type_data->text.font_type_render, &frs); } break; case PKE_UI_BOX_TYPE_BUTTON_TEXT: if (box->type_data->button_text.font_type_render.font_render_handle != FontRenderHandle_MAX) { frs = FontType_GetFontRender(box->type_data->button_text.font_type_render)->settings; frs.surface_area_pos = box->internal.px_corner; frs.surface_area_size = box->internal.px_size; FontType_UpdateStringRender(box->type_data->button_text.font_type_render, &frs); } break; case PKE_UI_BOX_TYPE_BUTTON_IMAGE: break; case PKE_UI_BOX_TYPE_INPUT_TEXT: // TODO break; case PKE_UI_BOX_TYPE_INPUT_MULTILINE_TEXT: // TODO break; default: break; } pke_ui_update_state(*box); if (prev_state_flags != box->state_flags || prev_state_flags_mem != box->state_flags_mem) { pke_ui_master.should_update_buffer = true; } if (PK_HAS_FLAG(box->flags, PKE_UI_BOX_FLAG_VISIBILITY_INVISIBLE)) { // TODO PERF we need to track state changes // currently this updates the buffer every frame if: // - box type text (always invisible with visible text) pke_ui_master.should_update_buffer = true; return true; } // update buffer glm::vec3 scale = glm::vec3(box->internal.px_size.x / (float)Extent.width, box->internal.px_size.y / (float)Extent.height, 1.0); glm::vec3 translate = glm::vec3(1); // ((val) - (width/2)) / (width/2) // place left line of box at edge of screen translate.x = (box->internal.px_size.x / 2.0); // box position translate.x += box->internal.px_corner.x; translate.x -= ((float)Extent.width / 2.0); translate.x /= ((float)Extent.width / 2.0); // ((val) - (height/2)) / (height/2) // place top line of box at edge of screen translate.y = (box->internal.px_size.y / 2.0); // box position translate.y += box->internal.px_corner.y; translate.y -= ((float)Extent.height / 2.0); translate.y /= ((float)Extent.height / 2.0); translate.z = 0; pke_ui_box_instance_buffer_item tmp{}; tmp.pos_scale = glm::translate(glm::mat4(1), translate); tmp.pos_scale = glm::scale(tmp.pos_scale, scale); tmp.color_border = box->color_border; tmp.color_background = box->color_background; if (PK_HAS_FLAG(box->flags, PKE_UI_BOX_FLAG_VISIBILITY_DISABLED)) { tmp.color_border = PKE_UI_CLR_DISABLED_BORDER; tmp.color_background = PKE_UI_CLR_DISABLED_BACKGROUND; } tmp.px_scale.x = (1.f / box->internal.px_size.x); tmp.px_scale.y = (1.f / box->internal.px_size.y); tmp.depth = (float)box->layer; tmp.texture_layer = -1; if (PK_HAS_FLAG(box->type, PKE_UI_BOX_TYPE_BUTTON_IMAGE)) { if (PK_HAS_FLAG(box->state_flags_mem, PKE_UI_BOX_STATE_FLAG_ON_BTN_PRIMARY_PRESS) && PK_HAS_FLAG(box->state_flags, PKE_UI_BOX_STATE_FLAG_MOUSE_HOVER)) { tmp.texture_layer = 2; } else if (PK_HAS_FLAG(box->state_flags, PKE_UI_BOX_STATE_FLAG_MOUSE_HOVER)) { tmp.texture_layer = 1; } else { tmp.texture_layer = 0; } pk_arr_append_t(&tmp_txtr_buffer, {box, tmp}); } else { pk_arr_append_t(&buffer, tmp); } return true; } void pke_ui_recalc_sizes_recursive(pk_arr_t &arr, pk_arr_t> &tmp_txtr_buffer, pke_ui_box *box) { PKE_UI_BOX_FLAG_T flags_masked; uint8_t flex_count = 0; uint8_t unreal_count = 0; float available_size = 0; pke_ui_flex_params flex_params{}; flex_params.px_per_unit = 0; flex_params.unit_total = 0; flex_params.used_units = 0; if (box->isMarkedForRemoval == true) return; flags_masked = PKE_UI_BOX_FLAG_T(box->flags & PKE_UI_BOX_FLAG_POSITION_TYPE_ALL); if (flags_masked == 0b000 || flags_masked == 0b011 || flags_masked == 0b101 || flags_masked == 0b110 || flags_masked == 0b111) { fprintf(stderr, "[%s] ui box invalid flags: position type\n", __FILE__); return; } for (pke_ui_box_count_T i = 0; i < box->internal.h_children; ++i) { if (box->internal.children[i]->isMarkedForRemoval == true) { unreal_count += 1; continue; } if (PK_HAS_FLAG(box->internal.children[i]->flags, PKE_UI_BOX_FLAG_POSITION_TYPE_FLEX)) { flex_count += 1; if (box->internal.children[i]->flex_weight <= 0 || !(box->internal.children[i]->flex_weight == box->internal.children[i]->flex_weight)) { fprintf(stderr, "[%s] ui box invalid flex_weight: '%f' on child #: %u\n", __FILE__, box->internal.children[i]->flex_weight, i); flex_count = 0; break; } flex_params.unit_total += box->internal.children[i]->flex_weight; } } if (flex_count != 0 && (flex_count + unreal_count) != box->internal.h_children) { fprintf(stderr, "[%s] ui box invalid flags: if one child is type FLEX, then all children must be type FLEX\n", __FILE__); return; } if (flex_count != 0) { assert(!glm::isnan(box->flex_padding)); available_size = box->flex_direction == 0 ? (box->internal.px_size.x - box->internal.px_padding_l - box->internal.px_padding_r) : (box->internal.px_size.y - box->internal.px_padding_t - box->internal.px_padding_b); available_size -= (float)(flex_count+1) * (box->flex_padding); if (PK_HAS_FLAG(box->flags, PKE_UI_BOX_FLAG_VISIBILITY_PIXEL_PERFECT)) { assert(fmod(box->flex_padding, 1.0) == 0.0); available_size = floor(available_size); flex_params.px_per_unit = available_size / flex_params.unit_total; flex_params.px_per_unit = floor(flex_params.px_per_unit); } else { flex_params.px_per_unit = available_size / flex_params.unit_total; } } for (pke_ui_box_count_T i = 0; i < box->internal.h_children; ++i) { if (!pke_ui_calc_px(arr, tmp_txtr_buffer, &flex_params, box->internal.children[i], i)) { i -= 1; continue; } pke_ui_recalc_sizes_recursive(arr, tmp_txtr_buffer, box->internal.children[i]); } } int pke_ui_box_ibi_sort_for_buffer(const void *lhs, const void *rhs) { const pke_ui_box_instance_buffer_item *l = reinterpret_cast(lhs); const pke_ui_box_instance_buffer_item *r = reinterpret_cast(rhs); return l->texture_layer > r->texture_layer; } void pke_ui_update_instance_buffer(pk_arr_t &arr) { VkResult vkResult; VkDeviceSize sz; if (arr.next == 0) { pke_ui_master.bindings.instance_counter = 0; return; } if (arr.next > pke_ui_master.bindings.instance_buffer_max_count) { VkBuffer newBuffer; VkBufferCreateInfo bufferCI; bufferCI.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; bufferCI.pNext = nullptr; bufferCI.flags = {}; bufferCI.sharingMode = VK_SHARING_MODE_EXCLUSIVE; bufferCI.queueFamilyIndexCount = 1; bufferCI.pQueueFamilyIndices = &graphicsFamilyIndex; bufferCI.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; bufferCI.size = sizeof(pke_ui_box_instance_buffer_item) * arr.next; vkResult = vkCreateBuffer(vkDevice, &bufferCI, vkAllocator, &newBuffer); assert(vkResult == VK_SUCCESS); VkMemoryRequirements vkMemReqs; vkGetBufferMemoryRequirements(vkDevice, newBuffer, &vkMemReqs); assert(sizeof(pke_ui_box_instance_buffer_item) % vkMemReqs.alignment == 0); vkDestroyBuffer(vkDevice, newBuffer, vkAllocator); newBuffer = VK_NULL_HANDLE; bufferCI.size = vkMemReqs.size + (vkMemReqs.alignment - (vkMemReqs.size & vkMemReqs.alignment)); vkResult = vkCreateBuffer(vkDevice, &bufferCI, vkAllocator, &newBuffer); assert(vkResult == VK_SUCCESS); VkDeviceMemory new_memory; VkMemoryAllocateInfo vkMemoryAllocateInfo; vkMemoryAllocateInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; vkMemoryAllocateInfo.pNext = nullptr; vkMemoryAllocateInfo.allocationSize = bufferCI.size; vkMemoryAllocateInfo.memoryTypeIndex = FindMemoryTypeIndex(vkMemReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); vkResult = vkAllocateMemory(vkDevice, &vkMemoryAllocateInfo, vkAllocator, &new_memory); assert(vkResult == VK_SUCCESS); vkResult = vkBindBufferMemory(vkDevice, newBuffer, new_memory, 0); assert(vkResult == VK_SUCCESS); if (pke_ui_master.bindings.bd_instance.buffer != VK_NULL_HANDLE) { pkvk_queue_vk_buffer_destroy(pke_ui_master.bindings.bd_instance.buffer); } if (pke_ui_master.bindings.deviceMemoryInst != VK_NULL_HANDLE) { pkvk_queue_vk_memory_free(pke_ui_master.bindings.deviceMemoryInst); } pke_ui_master.bindings.bd_instance.buffer = newBuffer; pke_ui_master.bindings.deviceMemoryInst = new_memory; pke_ui_master.bindings.instance_buffer_max_count = arr.next; } if (arr.next > 0 && arr.data != nullptr ) { PKVK_TmpBufferDetails tmpBufferDetails{}; sz = sizeof(pke_ui_box_instance_buffer_item) * arr.next; PKVK_BeginBuffer(graphicsFamilyIndex, sz, tmpBufferDetails); { VkCommandBufferBeginInfo vkCommandBufferBeginInfo; vkCommandBufferBeginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; vkCommandBufferBeginInfo.pNext = nullptr; // TODO consider single-use? vkCommandBufferBeginInfo.flags = 0; vkCommandBufferBeginInfo.pInheritanceInfo = nullptr; vkResult = vkBeginCommandBuffer(tmpBufferDetails.cmdBuffer, &vkCommandBufferBeginInfo); assert(vkResult == VK_SUCCESS); memcpy(tmpBufferDetails.deviceData, arr.data, sz); VkBufferCopy vk_buffer_copy{}; vk_buffer_copy.srcOffset = 0; vk_buffer_copy.dstOffset = 0; vk_buffer_copy.size = sz; vkCmdCopyBuffer(tmpBufferDetails.cmdBuffer, tmpBufferDetails.buffer, pke_ui_master.bindings.bd_instance.buffer, 1, &vk_buffer_copy); vkResult = vkEndCommandBuffer(tmpBufferDetails.cmdBuffer); assert(vkResult == VK_SUCCESS); VkSubmitInfo submitInfo; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; submitInfo.pNext = nullptr; submitInfo.waitSemaphoreCount = 0; submitInfo.pWaitSemaphores = nullptr; submitInfo.pWaitDstStageMask = nullptr; submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &tmpBufferDetails.cmdBuffer; submitInfo.signalSemaphoreCount = 0; submitInfo.pSignalSemaphores = nullptr; vkResult = vkQueueSubmit(tmpBufferDetails.queue, 1, &submitInfo, nullptr); assert(vkResult == VK_SUCCESS); vkResult = vkQueueWaitIdle(tmpBufferDetails.queue); assert(vkResult == VK_SUCCESS); } PKVK_EndBuffer(tmpBufferDetails); } pke_ui_master.bindings.instance_counter = arr.next; } void pke_ui_tick(double delta) { (void)delta; pke_ui_box_count_T i, ii; uint32_t u; for (u = 0; u < pke_ui_master.boxes_to_delete.next; ++u) { pk_delete(pke_ui_master.boxes_to_delete[u], pke_ui_master.bkt); } pk_arr_clear(&pke_ui_master.boxes_to_delete); if (pke_ui_master.h_root_boxes == 0) return; pke_ui_master.state.mouse_left = pke_input_query_by_action_name(ui_ux_mouse_left); pke_ui_master.state.mouse_right = pke_input_query_by_action_name(ui_ux_mouse_right); pke_ui_master.state.mouse_middle = pke_input_query_by_action_name(ui_ux_mouse_middle); pke_ui_master.state.deepest_pressed = nullptr; pk_arr_t arr{}; arr.bkt = pkeSettings.mem_bkt.game_transient; pk_arr_t> tmp_txtr_arr; tmp_txtr_arr.bkt = pkeSettings.mem_bkt.game_transient; pke_ui_master.px_scale = glm::vec2( 2.0 / (float)Extent.width, 2.0 / (float)Extent.height ); for (i = 0; i < pke_ui_master.h_root_boxes; ++i) { pke_ui_box *box = pke_ui_master.root_boxes[i]; // 2025-08-18 JCB // This currently only calls tear-down for boxes on the root list. // Leaving this as-is for the time being because you can control the // visibility of child boxes with flags and don't need to completely remove them. if (box->isMarkedForRemoval == true) { pke_ui_teardown_box_recursive(box, 0); for (ii = 0; i + ii + 1 < pke_ui_master.r_root_boxes; ++ii) { pke_ui_master.root_boxes[i + ii] = pke_ui_master.root_boxes[i + ii + 1]; } pke_ui_master.h_root_boxes -= 1; i -= 1; pke_ui_master.should_update_buffer = true; if (pke_ui_master.h_root_boxes == 0) { pke_input_deactivate_set(pke_ui_master.input_action_set_handle); break; } continue; } // root boxes marked for removal were already skipped pke_ui_calc_px(arr, tmp_txtr_arr, nullptr, box, 0); pke_ui_recalc_sizes_recursive(arr, tmp_txtr_arr, box); } for (i = 0; i < tmp_txtr_arr.next; ++i) { const std::pair &pair = tmp_txtr_arr[i]; if (pair.first->type_data->button_image.gr_binds_bkt_arr_handle == pk_bkt_arr_handle_MAX) { continue; } pke_ui_graphics_bindings_texture *bindings = &pke_ui_master.bindings_texture[pair.first->type_data->button_image.gr_binds_bkt_arr_handle]; if (bindings == nullptr || bindings->descriptor_sets == nullptr) { continue; } bindings->instance_count = 1; bindings->instance_offset = arr.next; pk_arr_append_t(&arr, pair.second); } if (pke_ui_master.should_update_buffer || pkeSettings.rt.was_framebuffer_resized) { pke_ui_master.should_update_buffer = false; pke_ui_update_instance_buffer(arr); } if (pke_ui_master.state.deepest_pressed != NULL) { // fprintf(stdout, "box: " pk_uuid_printf_format " pressed!\n", pk_uuid_printf_var(pke_ui_master.state.deepest_pressed->uuid)); struct pke_component_event *component_event = nullptr; switch (pke_ui_master.state.deepest_pressed->type) { case PKE_UI_BOX_TYPE_BUTTON_TEXT: component_event = ECS_GetEv(pke_ui_master.state.deepest_pressed->type_data->button_text.pke_event_handle); break; case PKE_UI_BOX_TYPE_BUTTON_IMAGE: component_event = ECS_GetEv(pke_ui_master.state.deepest_pressed->type_data->button_image.pke_event_handle); break; default: fprintf(stderr, "[static-ui] Something was pressed but we didn't know the type and couldn't emit.\n"); } if (component_event != nullptr) { pk_ev_emit(component_event->ev_mgr_id, component_event->ev_id, nullptr); } } } void pke_ui_teardown_box_recursive(pke_ui_box *box, uint8_t depth) { FontRender *fr = nullptr; pke_ui_graphics_bindings_texture *bindings = nullptr; for (pke_ui_box_count_T i = 0; i < box->internal.h_children; ++i) { pke_ui_teardown_box_recursive(box->internal.children[i], depth + 1); } if (box->internal.children != nullptr) { pk_delete_arr(box->internal.children, box->internal.r_children, pke_ui_master.bkt); } if (box->type_data != nullptr) { switch (box->type) { case PKE_UI_BOX_TYPE_TEXT: fr = FontType_GetFontRender(box->type_data->text.font_type_render); break; case PKE_UI_BOX_TYPE_BUTTON_TEXT: fr = FontType_GetFontRender(box->type_data->button_text.font_type_render); break; case PKE_UI_BOX_TYPE_BUTTON_IMAGE: if (box->type_data->button_image.gr_binds_bkt_arr_handle != pk_bkt_arr_handle_MAX) { bindings = &pke_ui_master.bindings_texture[box->type_data->button_image.gr_binds_bkt_arr_handle]; // reminder that we are not passing the flag on the pool to manually manage descriptor sets, so all we need to do is destroy the pool if (bindings->descriptor_pool != VK_NULL_HANDLE) { pkvk_queue_vk_descriptor_pool_destroy(bindings->descriptor_pool); } if (bindings->descriptor_sets != nullptr) { pk_delete_arr(bindings->descriptor_sets, prevSwapchainLength, MemBkt_Vulkan); } if (bindings->image_view!= VK_NULL_HANDLE) { pkvk_queue_vk_image_view_destroy(bindings->image_view); } if (bindings->image!= VK_NULL_HANDLE) { pkvk_queue_vk_image_destroy(bindings->image); } if (bindings->image_memory != VK_NULL_HANDLE) { pkvk_queue_vk_memory_free(bindings->image_memory); } bindings->descriptor_sets = nullptr; bindings->descriptor_pool = VK_NULL_HANDLE; pk_bkt_arr_free_handle(&pke_ui_master.bindings_texture, box->type_data->button_image.gr_binds_bkt_arr_handle); bindings = nullptr; } break; case PKE_UI_BOX_TYPE_INPUT_TEXT: // TODO break; case PKE_UI_BOX_TYPE_INPUT_MULTILINE_TEXT: // TODO break; default: fprintf(stderr, "[%s][pke_ui_teardown_box_recursive] unknown box type: %hhu", __FILE__, static_cast(box->type)); } pk_delete(box->type_data, pke_ui_master.bkt); box->type_data = nullptr; } pk_arr_append_t(&pke_ui_master.boxes_to_delete, box); // 2025-09-16 JCB // If correctly parented, this is unnecessary, but de/serialization does // not construct text boxes in this way. // Handle explicitly to avoid memory leaks. if (fr != nullptr && fr->parentHandle != box->handle) { FontType_RemoveStringRender(FontTypeRender{fr->font_type_handle, fr->font_render_handle}); } /* 2025-09-25 JCB HACK * I'd like this to be more elegant, this seems hacky. * We need to skip the first (the one we marked for removal). * If we are manually managing child boxes (like deleting in the editor), * then this can get called when depth > 0 but the box is already marked. * The box at depth==0 is already marked, so only mark children. */ if (depth != 0 && box->isMarkedForRemoval == false) { ECS_MarkForRemoval(box); } pke_ui_master.should_update_buffer = true; } void pke_ui_teardown() { for (pke_ui_box_count_T i = 0; i < pke_ui_master.h_root_boxes; ++i) { pke_ui_teardown_box_recursive(pke_ui_master.root_boxes[i], 0); } pk_delete_arr(pke_ui_master.root_boxes, pke_ui_master.r_root_boxes, pke_ui_master.bkt); pke_ui_master.bindings_texture.~texture_binding_bkt_arr(); pke_ui_master.root_boxes = nullptr; pke_ui_master.h_root_boxes = 0; pke_ui_master.r_root_boxes = 0; if (pke_ui_master.bindings.bd_vertex.buffer != VK_NULL_HANDLE) vkDestroyBuffer(vkDevice, pke_ui_master.bindings.bd_vertex.buffer, vkAllocator); pke_ui_master.bindings.bd_vertex.buffer = VK_NULL_HANDLE; pke_ui_master.bindings.bd_vertex.firstBinding = 0; pke_ui_master.bindings.bd_vertex.bindingCount = 0; pke_ui_master.bindings.bd_vertex.offsets[0] = 0; if (pke_ui_master.bindings.bd_uv.buffer != VK_NULL_HANDLE) vkDestroyBuffer(vkDevice, pke_ui_master.bindings.bd_uv.buffer, vkAllocator); pke_ui_master.bindings.bd_uv.buffer = VK_NULL_HANDLE; pke_ui_master.bindings.bd_uv.firstBinding = 0; pke_ui_master.bindings.bd_uv.bindingCount = 0; pke_ui_master.bindings.bd_uv.offsets[0] = 0; if (pke_ui_master.bindings.bd_index.buffer != VK_NULL_HANDLE) vkDestroyBuffer(vkDevice, pke_ui_master.bindings.bd_index.buffer, vkAllocator); pke_ui_master.bindings.bd_index.buffer = VK_NULL_HANDLE; pke_ui_master.bindings.bd_index.bindingCount = 0; pke_ui_master.bindings.bd_index.offsets[0] = 0; pke_ui_master.bindings.index_count = 0; if (pke_ui_master.bindings.bd_instance.buffer != VK_NULL_HANDLE) vkDestroyBuffer(vkDevice, pke_ui_master.bindings.bd_instance.buffer, vkAllocator); pke_ui_master.bindings.bd_instance.buffer = VK_NULL_HANDLE; pke_ui_master.bindings.bd_instance.firstBinding = 0; pke_ui_master.bindings.bd_instance.bindingCount = 0; pke_ui_master.bindings.instance_counter = 0; pke_ui_master.bindings.instance_buffer_max_count = 0; pke_ui_master.bindings.bd_instance.offsets[0] = 0; if (pke_ui_master.bindings.deviceMemoryInst != VK_NULL_HANDLE) vkFreeMemory(vkDevice, pke_ui_master.bindings.deviceMemoryInst, vkAllocator); pke_ui_master.bindings.deviceMemoryInst = VK_NULL_HANDLE; if (pke_ui_master.bindings.deviceMemoryVert != VK_NULL_HANDLE) vkFreeMemory(vkDevice, pke_ui_master.bindings.deviceMemoryVert, vkAllocator); pke_ui_master.bindings.deviceMemoryVert = VK_NULL_HANDLE; pk_arr_reset(&pke_ui_master.boxes_to_delete); pke_input_unregister_set(pke_ui_master.input_action_set_handle); pk_mem_bucket_destroy(pke_ui_master.bkt); pke_ui_master.bkt = nullptr; } pke_ui_box **pke_ui_get_root_boxes(pke_ui_box_count_T *count) { assert(count != nullptr); *count = pke_ui_master.h_root_boxes; return pke_ui_master.root_boxes; } void pke_ui_internal_new_typed_box(pke_ui_box *box, const PKE_UI_BOX_TYPE type) { assert(box->type == type); pke_ui_box_type_data *tp_data = nullptr; pke_component_event *pressed_ev = nullptr; switch (type) { case PKE_UI_BOX_TYPE_STANDARD: break; case PKE_UI_BOX_TYPE_TEXT: tp_data = pk_new(pke_ui_master.bkt); tp_data->text.font_type_render = FontTypeRender_MAX; box->flags |= PKE_UI_BOX_FLAG_VISIBILITY_INVISIBLE; break; case PKE_UI_BOX_TYPE_INPUT_TEXT: break; case PKE_UI_BOX_TYPE_BUTTON_TEXT: tp_data = pk_new(pke_ui_master.bkt); tp_data->button_text.font_type_render = FontTypeRender_MAX; pressed_ev = ECS_CreateEv(box, pk_uuid_max); tp_data->button_text.pke_event_handle = pressed_ev->pke_event_handle; tp_data->button_text.ev_id = pressed_ev->ev_id; break; case PKE_UI_BOX_TYPE_BUTTON_IMAGE: tp_data = pk_new(pke_ui_master.bkt); tp_data->button_image.gr_binds_bkt_arr_handle = pk_bkt_arr_handle_MAX; pressed_ev = ECS_CreateEv(box, pk_uuid_max); tp_data->button_image.pke_event_handle = pressed_ev->pke_event_handle; tp_data->button_image.ev_id = pressed_ev->ev_id; break; default: assert(true == false && "unknown pke_ui_box::type"); break; } box->type_data = tp_data; } pke_ui_box *pke_ui_box_new_root(const PKE_UI_BOX_TYPE type, pk_uuid uuid) { if (pke_ui_master.h_root_boxes == 0) { pke_input_activate_set(pke_ui_master.input_action_set_handle); } if (pke_ui_master.h_root_boxes == pke_ui_master.r_root_boxes) { pke_ui_box_count_T prev_r_root_boxes = pke_ui_master.r_root_boxes; pke_ui_master.r_root_boxes *= 1.5; pke_ui_box **boxes = pk_new_arr(pke_ui_master.r_root_boxes, pke_ui_master.bkt); for (pke_ui_box_count_T i = 0; i < pke_ui_master.h_root_boxes; ++i) { boxes[i] = pke_ui_master.root_boxes[i]; } if (pke_ui_master.root_boxes != nullptr) pk_delete_arr(pke_ui_master.root_boxes, prev_r_root_boxes, pke_ui_master.bkt); pke_ui_master.root_boxes = boxes; } assert(pke_ui_master.h_root_boxes < pke_ui_master.r_root_boxes); pke_ui_box *box = pk_new(pke_ui_master.bkt); new (box) pke_ui_box{}; pke_ui_master.root_boxes[pke_ui_master.h_root_boxes] = box; pke_ui_master.h_root_boxes += 1; pke_ui_master.should_update_buffer = true; box->type = type; box->uuid = uuid; box->internal.depth = 0; ECS_CreateEntity(box, nullptr); pke_ui_internal_new_typed_box(box, type); return box; } pke_ui_box *pke_ui_box_new_child(pke_ui_box *parent, const PKE_UI_BOX_TYPE type, pk_uuid uuid) { assert(parent != nullptr); // validation if (parent->type >= PKE_UI_BOX_TYPE_TEXT) { // this might be too broad, alter if needed fprintf(stdout, "[pke_ui_box_new_child] Validation error: PKE_UI_BOX_TYPE_TEXT cannot have children.\n"); return nullptr; } if (parent->internal.h_children == parent->internal.r_children) { pke_ui_box_count_T prev_r_children = parent->internal.r_children; parent->internal.r_children = PK_MAX(parent->internal.r_children * 1.5, 2); pke_ui_box **boxes = pk_new_arr(parent->internal.r_children, pke_ui_master.bkt); for (pke_ui_box_count_T i = 0; i < parent->internal.h_children; ++i) { boxes[i] = parent->internal.children[i]; } if (parent->internal.children != nullptr) { pk_delete_arr(parent->internal.children, prev_r_children, pke_ui_master.bkt); } parent->internal.children = boxes; } pke_ui_box *box = pk_new(pke_ui_master.bkt); *box = {}; parent->internal.children[parent->internal.h_children] = box; parent->internal.h_children += 1; box->type = type; box->internal.parent = parent; box->internal.depth = parent->internal.depth + 1; box->uuid = uuid; ECS_CreateEntity(box, parent); pke_ui_internal_new_typed_box(box, type); pke_ui_master.should_update_buffer = true; return box; } void pke_ui_box_update_textures(pke_ui_box *box) { uint32_t i; pke_ui_graphics_bindings_texture *bindings = nullptr; pkvk_texture_upload_data data{}; pkvk_texture_upload_data_out out{}; AssetHandle handle_default; AssetHandle handle_hovered; AssetHandle handle_pressed; const Asset *asset_default = nullptr; const Asset *asset_hovered = nullptr; const Asset *asset_pressed = nullptr; if (box->type != PKE_UI_BOX_TYPE_BUTTON_IMAGE) { fprintf(stderr, "[pke_ui_box_handle_textures] attempted to process textures on non-texture typed ui box. Got type: %hhu\n", static_cast(box->type)); return; } if (box->type_data->button_image.gr_binds_bkt_arr_handle == pk_bkt_arr_handle_MAX) { box->type_data->button_image.gr_binds_bkt_arr_handle = pk_bkt_arr_new_handle(&pke_ui_master.bindings_texture); bindings = &pke_ui_master.bindings_texture[box->type_data->button_image.gr_binds_bkt_arr_handle]; bindings->descriptor_sets = nullptr; bindings->descriptor_pool = VK_NULL_HANDLE; bindings->instance_count = 0; bindings->instance_offset = 0; } else { bindings = &pke_ui_master.bindings_texture[box->type_data->button_image.gr_binds_bkt_arr_handle]; } assert(bindings != nullptr); handle_default = AM_GetHandle(box->type_data->button_image.img_key_default); handle_hovered = AM_GetHandle(box->type_data->button_image.img_key_hovered); handle_pressed = AM_GetHandle(box->type_data->button_image.img_key_pressed); if (handle_default == AssetHandle_MAX) return; if (handle_hovered == AssetHandle_MAX) return; if (handle_pressed == AssetHandle_MAX) return; asset_default = AM_Get(handle_default); asset_hovered = AM_Get(handle_hovered); asset_pressed = AM_Get(handle_pressed); if (asset_default == nullptr || asset_default->type != PKE_ASSET_TYPE_TEXTURE) { fprintf(stderr, "[pke_ui_box_update_textures] `img_key_default` was not found or was not a texture.\n"); return; } if (asset_hovered == nullptr || asset_hovered->type != PKE_ASSET_TYPE_TEXTURE) { fprintf(stderr, "[pke_ui_box_update_textures] `img_key_hovered` was not found or was not a texture.\n"); } if (asset_pressed == nullptr || asset_pressed->type != PKE_ASSET_TYPE_TEXTURE) { fprintf(stderr, "[pke_ui_box_update_textures] `img_key_pressed` was not found or was not a texture.\n"); } data.n_textures = 3; data.texture_assets[0] = asset_default; data.texture_assets[1] = asset_hovered; data.texture_assets[2] = asset_pressed; pkvk_texture_upload_array(&data, &out); bindings->image = out.images[0]; bindings->image_view = out.image_views[0]; bindings->image_memory = out.device_memory; if (bindings->descriptor_sets != nullptr) { vkDestroyDescriptorPool(vkDevice, bindings->descriptor_pool, vkAllocator); pk_delete_arr(bindings->descriptor_sets, prevSwapchainLength, MemBkt_Vulkan); } VkDescriptorPoolSize pool_size{}; pool_size.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; pool_size.descriptorCount = (uint32_t)swapchainLength; VkDescriptorPoolCreateInfo dpci{}; dpci.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; dpci.pNext = VK_NULL_HANDLE; dpci.flags = 0; dpci.maxSets = swapchainLength; dpci.poolSizeCount = (uint32_t)1; dpci.pPoolSizes = &pool_size; VkDescriptorSetLayout *descriptorSets = pk_new_arr(swapchainLength, pkeSettings.mem_bkt.game_transient); for (i = 0; i < swapchainLength; ++i) { descriptorSets[i] = pkePipelines.descr_layouts.named.txtr; } VkDescriptorSetAllocateInfo vkDescriptorSetAllocateInfo; vkDescriptorSetAllocateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; vkDescriptorSetAllocateInfo.pNext = nullptr; vkDescriptorSetAllocateInfo.descriptorSetCount = swapchainLength; vkDescriptorSetAllocateInfo.pSetLayouts = descriptorSets; VkWriteDescriptorSet *writeDescriptorSets = pk_new_arr(swapchainLength, pkeSettings.mem_bkt.game_transient); for (i = 0; i < swapchainLength; ++i) { writeDescriptorSets[i].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; writeDescriptorSets[i].pNext = VK_NULL_HANDLE; writeDescriptorSets[i].dstSet = VK_NULL_HANDLE; writeDescriptorSets[i].dstBinding = 0; writeDescriptorSets[i].dstArrayElement = 0; writeDescriptorSets[i].descriptorCount = 1; writeDescriptorSets[i].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; writeDescriptorSets[i].pImageInfo = VK_NULL_HANDLE; writeDescriptorSets[i].pBufferInfo = VK_NULL_HANDLE; writeDescriptorSets[i].pTexelBufferView = nullptr; } VkDescriptorImageInfo textureDescriptorInfo; textureDescriptorInfo.sampler = global_sampler; textureDescriptorInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; textureDescriptorInfo.imageView = bindings->image_view; // consider making me a global pool auto vkResult = vkCreateDescriptorPool(vkDevice, &dpci, vkAllocator, &bindings->descriptor_pool); assert(vkResult == VK_SUCCESS); vkDescriptorSetAllocateInfo.descriptorPool = bindings->descriptor_pool; bindings->descriptor_sets = pk_new_arr(swapchainLength, MemBkt_Vulkan); vkResult = vkAllocateDescriptorSets(vkDevice, &vkDescriptorSetAllocateInfo, bindings->descriptor_sets); assert(vkResult == VK_SUCCESS); for (i = 0; i < swapchainLength; ++i) { writeDescriptorSets[i].pImageInfo = &textureDescriptorInfo; writeDescriptorSets[i].dstSet = bindings->descriptor_sets[i]; } vkUpdateDescriptorSets(vkDevice, swapchainLength, writeDescriptorSets, 0, nullptr); AM_Release(handle_pressed); AM_Release(handle_hovered); AM_Release(handle_default); } const pke_ui_graphics_bindings &pke_ui_get_graphics_bindings() { return pke_ui_master.bindings; } void pke_ui_get_graphics_bindings_texture(pk_arr *arr) { bool b; pk_arr_t &arr_t = *static_cast*>(arr); assert(arr_t.stride == sizeof(pke_ui_graphics_bindings_texture)); assert(arr_t.alignment == alignof(pke_ui_graphics_bindings_texture)); pk_iter_t iter_gr{}; b = pk_bkt_arr_iter_begin(&pke_ui_master.bindings_texture, &iter_gr); while (b == true) { pk_arr_append_t(&arr_t, *iter_gr); b = pk_bkt_arr_iter_increment(&pke_ui_master.bindings_texture, &iter_gr); } }