From e174c500a063bb89058adafcde84bcf1f7b255ac Mon Sep 17 00:00:00 2001 From: hachem Date: Sat, 22 Aug 2026 00:46:02 +0200 Subject: [feat]: add dynamic UBO and scene manager support for vulkan --- src/core/application.cpp | 42 +++++++++++++++++++- src/platform/vulkan/vulkan_renderer.cpp | 70 +++++++++++++++++++++++---------- src/platform/vulkan/vulkan_renderer.h | 14 +++++++ 3 files changed, 104 insertions(+), 22 deletions(-) (limited to 'src') diff --git a/src/core/application.cpp b/src/core/application.cpp index 82b1be2..f9e52a4 100644 --- a/src/core/application.cpp +++ b/src/core/application.cpp @@ -97,8 +97,46 @@ namespace Donut if (scene) { - ImGui::TextDisabled("Lit sphere + HDRI skybox on the reference grid."); - ImGui::TextDisabled("Drag to orbit, scroll to zoom."); + ImGui::TextDisabled("Drag to orbit, scroll to zoom. HDRI skybox behind."); + ImGui::Separator(); + + auto& objs = m_vulkan_renderer->scene_objects(); + static int sel = 0; + if (sel >= (int)objs.size()) sel = (int)objs.size() - 1; + + ImGui::Text("Spheres (%d)", (int)objs.size()); + if (ImGui::Button("Add") && objs.size() < 64) + { + SceneObject o; + o.position = glm::vec3(((int)objs.size() % 5) * 5.0f - 10.0f, 2.0f, ((int)objs.size() / 5) * 5.0f); + o.radius = 1.5f; + o.color = glm::vec3(0.35f + 0.12f * (objs.size() % 5), 0.55f, 0.9f - 0.12f * (objs.size() % 4)); + objs.push_back(o); + sel = (int)objs.size() - 1; + } + ImGui::SameLine(); + if (ImGui::Button("Delete") && !objs.empty()) + { + objs.erase(objs.begin() + sel); + if (sel >= (int)objs.size()) sel = (int)objs.size() - 1; + } + + ImGui::BeginChild("obj_list", ImVec2(0, 90), true); + for (int i = 0; i < (int)objs.size(); ++i) + { + std::string label = "Sphere " + std::to_string(i); + if (ImGui::Selectable(label.c_str(), sel == i)) sel = i; + } + ImGui::EndChild(); + + m_vulkan_renderer->set_selected_object(sel); + if (sel >= 0 && sel < (int)objs.size()) + { + SceneObject& o = objs[sel]; + ImGui::DragFloat3("Position", &o.position.x, 0.1f); + ImGui::DragFloat("Radius", &o.radius, 0.05f, 0.1f, 20.0f); + ImGui::ColorEdit3("Color", &o.color.x); + } } else { diff --git a/src/platform/vulkan/vulkan_renderer.cpp b/src/platform/vulkan/vulkan_renderer.cpp index 80f0976..f2037dc 100644 --- a/src/platform/vulkan/vulkan_renderer.cpp +++ b/src/platform/vulkan/vulkan_renderer.cpp @@ -147,6 +147,10 @@ namespace Donut VkBuffer sphere_vb = VK_NULL_HANDLE; VkDeviceMemory sphere_vb_mem = VK_NULL_HANDLE; VkBuffer sphere_ib = VK_NULL_HANDLE; VkDeviceMemory sphere_ib_mem = VK_NULL_HANDLE; uint32_t sphere_index_count = 0; + static constexpr uint32_t MAX_SCENE_OBJECTS = 64; + std::vector scene_objects{ SceneObject{} }; // one default sphere + int selected_object = 0; + VkDeviceSize sphere_ubo_stride = 0; // aligned per-object UBO slot (dynamic offset) VkBuffer sphere_ubo = VK_NULL_HANDLE; VkDeviceMemory sphere_ubo_mem = VK_NULL_HANDLE; void* sphere_ubo_mapped = nullptr; VkDescriptorSetLayout sphere_set_layout = VK_NULL_HANDLE; @@ -1179,23 +1183,27 @@ namespace Donut vkMapMemory(device, sphere_vb_mem, 0, svsz, 0, &sp); memcpy(sp, sv.data(), svsz); vkUnmapMemory(device, sphere_vb_mem); vkMapMemory(device, sphere_ib_mem, 0, sisz, 0, &sp); memcpy(sp, si.data(), sisz); vkUnmapMemory(device, sphere_ib_mem); - if (!create_buffer(208, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, host_vis, sphere_ubo, sphere_ubo_mem)) return false; - vkMapMemory(device, sphere_ubo_mem, 0, 208, 0, &sphere_ubo_mapped); + VkPhysicalDeviceProperties pdp{}; vkGetPhysicalDeviceProperties(physical, &pdp); + VkDeviceSize min_align = pdp.limits.minUniformBufferOffsetAlignment; + sphere_ubo_stride = ((208 + min_align - 1) / min_align) * min_align; // per-object slot + VkDeviceSize sphere_ubo_size = sphere_ubo_stride * MAX_SCENE_OBJECTS; + if (!create_buffer(sphere_ubo_size, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, host_vis, sphere_ubo, sphere_ubo_mem)) return false; + vkMapMemory(device, sphere_ubo_mem, 0, sphere_ubo_size, 0, &sphere_ubo_mapped); VkDescriptorSetLayoutBinding sb[2]{}; - sb[0].binding = 0; sb[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; sb[0].descriptorCount = 1; sb[0].stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT; + sb[0].binding = 0; sb[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC; sb[0].descriptorCount = 1; sb[0].stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT; sb[1].binding = 1; sb[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; sb[1].descriptorCount = 1; sb[1].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; VkDescriptorSetLayoutCreateInfo sdslci{ VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO }; sdslci.bindingCount = 2; sdslci.pBindings = sb; VK_CHECK(vkCreateDescriptorSetLayout(device, &sdslci, nullptr, &sphere_set_layout)); - VkDescriptorPoolSize sps[2] = { { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1 }, { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1 } }; + VkDescriptorPoolSize sps[2] = { { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1 }, { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1 } }; VkDescriptorPoolCreateInfo sdpci{ VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO }; sdpci.maxSets = 1; sdpci.poolSizeCount = 2; sdpci.pPoolSizes = sps; VK_CHECK(vkCreateDescriptorPool(device, &sdpci, nullptr, &sphere_pool)); VkDescriptorSetAllocateInfo sdsai{ VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO }; sdsai.descriptorPool = sphere_pool; sdsai.descriptorSetCount = 1; sdsai.pSetLayouts = &sphere_set_layout; VK_CHECK(vkAllocateDescriptorSets(device, &sdsai, &sphere_set)); - VkDescriptorBufferInfo sbi{ sphere_ubo, 0, VK_WHOLE_SIZE }; + VkDescriptorBufferInfo sbi{ sphere_ubo, 0, 208 }; // per-draw size; the dynamic offset selects the object slot VkDescriptorImageInfo sii{ cube_sampler, cube_view, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL }; VkWriteDescriptorSet sw[2]{}; - sw[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; sw[0].dstSet = sphere_set; sw[0].dstBinding = 0; sw[0].descriptorCount = 1; sw[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; sw[0].pBufferInfo = &sbi; + sw[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; sw[0].dstSet = sphere_set; sw[0].dstBinding = 0; sw[0].descriptorCount = 1; sw[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC; sw[0].pBufferInfo = &sbi; sw[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; sw[1].dstSet = sphere_set; sw[1].dstBinding = 1; sw[1].descriptorCount = 1; sw[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; sw[1].pImageInfo = &sii; vkUpdateDescriptorSets(device, 2, sw, 0, nullptr); @@ -1342,19 +1350,26 @@ namespace Donut glm::vec3 light_pos; float p1; // 160, 172 glm::vec3 camera_pos; int is_selected; // 176, 188 glm::vec3 outline_color; float outline_width; // 192, 204 - } s{}; + }; static_assert(sizeof(SphereUBO) == 208, "SphereUBO std140 layout mismatch"); - s.view_projection = vp; // same no-transpose rule as the grid - s.transform = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 2.0f, 0.0f)) * glm::scale(glm::mat4(1.0f), glm::vec3(2.0f)); - s.color = glm::vec3(0.85f, 0.35f, 0.2f); - s.specular = 0.6f; - s.emission = 0.0f; - s.light_pos = glm::vec3(10.0f, 20.0f, 10.0f); - s.camera_pos = scene_camera.get_orbital_position(); - s.is_selected = 0; - s.outline_color = glm::vec3(1.0f, 1.0f, 0.0f); - s.outline_width = 0.1f; - if (sphere_ubo_mapped) memcpy(sphere_ubo_mapped, &s, sizeof(s)); + glm::vec3 cam_pos = scene_camera.get_orbital_position(); + uint32_t obj_count = (uint32_t)std::min(scene_objects.size(), (size_t)MAX_SCENE_OBJECTS); + for (uint32_t i = 0; i < obj_count; ++i) + { + const SceneObject& o = scene_objects[i]; + SphereUBO s{}; + s.view_projection = vp; // same no-transpose rule as the grid + s.transform = glm::translate(glm::mat4(1.0f), o.position) * glm::scale(glm::mat4(1.0f), glm::vec3(o.radius)); + s.color = o.color; + s.specular = 0.6f; + s.emission = 0.0f; + s.light_pos = glm::vec3(10.0f, 20.0f, 10.0f); + s.camera_pos = cam_pos; + s.is_selected = ((int)i == selected_object) ? 1 : 0; + s.outline_color = glm::vec3(1.0f, 1.0f, 0.0f); + s.outline_width = 0.15f; + if (sphere_ubo_mapped) memcpy((char*)sphere_ubo_mapped + (VkDeviceSize)i * sphere_ubo_stride, &s, sizeof(s)); + } struct SkyboxUBO { glm::mat4 projection; glm::mat4 view; } sky{}; static_assert(sizeof(SkyboxUBO) == 128, "SkyboxUBO std140 layout mismatch"); @@ -1552,10 +1567,15 @@ namespace Donut vkCmdDraw(cmd, 36, 1, 0, 0); vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, sphere_pipeline); - vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, sphere_pipeline_layout, 0, 1, &sphere_set, 0, nullptr); VkDeviceSize soff = 0; vkCmdBindVertexBuffers(cmd, 0, 1, &sphere_vb, &soff); vkCmdBindIndexBuffer(cmd, sphere_ib, 0, VK_INDEX_TYPE_UINT32); - vkCmdDrawIndexed(cmd, sphere_index_count, 1, 0, 0, 0); + uint32_t obj_count = (uint32_t)std::min(scene_objects.size(), (size_t)MAX_SCENE_OBJECTS); + for (uint32_t i = 0; i < obj_count; ++i) + { + uint32_t dyn = (uint32_t)((VkDeviceSize)i * sphere_ubo_stride); // select this object's UBO slot + vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, sphere_pipeline_layout, 0, 1, &sphere_set, 1, &dyn); + vkCmdDrawIndexed(cmd, sphere_index_count, 1, 0, 0, 0); + } vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, grid_pipeline); vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, grid_pipeline_layout, 0, 1, &grid_set, 0, nullptr); @@ -1775,6 +1795,16 @@ namespace Donut return m_impl && m_impl->scene_mode; } + auto VulkanRenderer::scene_objects() -> std::vector& + { + return m_impl->scene_objects; + } + + auto VulkanRenderer::set_selected_object(int index) -> void + { + if (m_impl) m_impl->selected_object = index; + } + auto VulkanRenderer::draw_frame(const glm::vec4& clear_color, const std::function& build_ui) -> void { Impl& v = *m_impl; diff --git a/src/platform/vulkan/vulkan_renderer.h b/src/platform/vulkan/vulkan_renderer.h index e6920c5..a760cb4 100644 --- a/src/platform/vulkan/vulkan_renderer.h +++ b/src/platform/vulkan/vulkan_renderer.h @@ -3,6 +3,7 @@ #include #include #include +#include // Live-window Vulkan backend: owns the instance, surface, device, swapchain, // render pass, framebuffers and per-frame synchronization, and drives the @@ -15,6 +16,14 @@ namespace Donut // macOS/Homebrew) and configures the MoltenVK ICD / layer paths. auto vulkan_prepare_glfw() -> void; + // A placeable/editable sphere in the world-builder scene view. + struct SceneObject + { + glm::vec3 position = { 0.0f, 2.0f, 0.0f }; + float radius = 2.0f; + glm::vec3 color = { 0.85f, 0.35f, 0.2f }; + }; + class VulkanRenderer { public: @@ -52,6 +61,11 @@ namespace Donut auto set_scene_mode(bool enabled) -> void; auto is_scene_mode() const -> bool; + // Runtime scene objects (spheres) drawn in scene view; the UI mutates this + // list directly. set_selected_object marks one for a rim highlight (-1 = none). + auto scene_objects() -> std::vector&; + auto set_selected_object(int index) -> void; + private: struct Impl; Impl* m_impl = nullptr; -- cgit v1.3