ADD: object adding system ((RT shadow error)

This commit is contained in:
2025-11-21 15:09:28 +09:00
parent b85bb1b59b
commit 4b05df3c04
6 changed files with 809 additions and 525 deletions

View File

@@ -122,6 +122,19 @@ void TextureCache::watchBinding(TextureHandle handle, VkDescriptorSet set, uint3
// Back-reference for fast per-set markUsed // Back-reference for fast per-set markUsed
_setToHandles[set].push_back(handle); _setToHandles[set].push_back(handle);
// If the texture is already resident, immediately patch the new descriptor
// so re-spawned models using cached textures get the correct bindings.
if (e.state == EntryState::Resident && e.image.imageView != VK_NULL_HANDLE && set != VK_NULL_HANDLE)
{
if (!_context || !_context->getDevice()) return;
DescriptorWriter writer;
writer.write_image(static_cast<int>(binding), e.image.imageView,
p.sampler ? p.sampler : e.sampler,
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER);
writer.update_set(_context->getDevice()->device(), set);
}
} }
void TextureCache::unwatchSet(VkDescriptorSet set) void TextureCache::unwatchSet(VkDescriptorSet set)

View File

@@ -254,13 +254,6 @@ void VulkanEngine::init()
auto imguiPass = std::make_unique<ImGuiPass>(); auto imguiPass = std::make_unique<ImGuiPass>();
_renderPassManager->setImGuiPass(std::move(imguiPass)); _renderPassManager->setImGuiPass(std::move(imguiPass));
const std::string structurePath = _assetManager->modelPath("mirage2000/scene.gltf");
const auto structureFile = _assetManager->loadGLTF(structurePath);
assert(structureFile.has_value());
_sceneManager->loadScene("structure", *structureFile);
_resourceManager->set_deferred_uploads(true); _resourceManager->set_deferred_uploads(true);
//everything went fine //everything went fine
@@ -330,6 +323,8 @@ void VulkanEngine::init_default_data()
BoundsType::Sphere); BoundsType::Sphere);
} }
addGLTFInstance("mirage", "mirage2000/scene.gltf", glm::mat4(1.0f));
_mainDeletionQueue.push_function([&]() { _mainDeletionQueue.push_function([&]() {
_resourceManager->destroy_image(_whiteImage); _resourceManager->destroy_image(_whiteImage);
_resourceManager->destroy_image(_greyImage); _resourceManager->destroy_image(_greyImage);
@@ -340,6 +335,32 @@ void VulkanEngine::init_default_data()
//< default_img //< default_img
} }
bool VulkanEngine::addGLTFInstance(const std::string &instanceName,
const std::string &modelRelativePath,
const glm::mat4 &transform)
{
if (!_assetManager || !_sceneManager)
{
return false;
}
const std::string fullPath = _assetManager->modelPath(modelRelativePath);
auto gltf = _assetManager->loadGLTF(fullPath);
if (!gltf.has_value() || !gltf.value())
{
return false;
}
// Provide a readable debug name for UI/picking when missing.
if ((*gltf)->debugName.empty())
{
(*gltf)->debugName = modelRelativePath;
}
_sceneManager->addGLTFInstance(instanceName, *gltf, transform);
return true;
}
void VulkanEngine::cleanup() void VulkanEngine::cleanup()
{ {
vkDeviceWaitIdle(_deviceManager->device()); vkDeviceWaitIdle(_deviceManager->device());
@@ -449,6 +470,9 @@ void VulkanEngine::draw()
{ {
_hoverPick.mesh = hoverObj.sourceMesh; _hoverPick.mesh = hoverObj.sourceMesh;
_hoverPick.scene = hoverObj.sourceScene; _hoverPick.scene = hoverObj.sourceScene;
_hoverPick.node = hoverObj.sourceNode;
_hoverPick.ownerType = hoverObj.ownerType;
_hoverPick.ownerName = hoverObj.ownerName;
_hoverPick.worldPos = hoverPos; _hoverPick.worldPos = hoverPos;
_hoverPick.worldTransform = hoverObj.transform; _hoverPick.worldTransform = hoverObj.transform;
_hoverPick.firstIndex = hoverObj.firstIndex; _hoverPick.firstIndex = hoverObj.firstIndex;
@@ -459,6 +483,8 @@ void VulkanEngine::draw()
else else
{ {
_hoverPick.valid = false; _hoverPick.valid = false;
_hoverPick.ownerName.clear();
_hoverPick.ownerType = RenderObject::OwnerType::None;
} }
} }
@@ -779,6 +805,9 @@ void VulkanEngine::run()
{ {
_lastPick.mesh = hitObject.sourceMesh; _lastPick.mesh = hitObject.sourceMesh;
_lastPick.scene = hitObject.sourceScene; _lastPick.scene = hitObject.sourceScene;
_lastPick.node = hitObject.sourceNode;
_lastPick.ownerType = hitObject.ownerType;
_lastPick.ownerName = hitObject.ownerName;
_lastPick.worldPos = hitPos; _lastPick.worldPos = hitPos;
_lastPick.worldTransform = hitObject.transform; _lastPick.worldTransform = hitObject.transform;
_lastPick.firstIndex = hitObject.firstIndex; _lastPick.firstIndex = hitObject.firstIndex;
@@ -790,6 +819,8 @@ void VulkanEngine::run()
else else
{ {
_lastPick.valid = false; _lastPick.valid = false;
_lastPick.ownerName.clear();
_lastPick.ownerType = RenderObject::OwnerType::None;
_lastPickObjectID = 0; _lastPickObjectID = 0;
} }
} }
@@ -809,6 +840,9 @@ void VulkanEngine::run()
PickInfo info{}; PickInfo info{};
info.mesh = obj.sourceMesh; info.mesh = obj.sourceMesh;
info.scene = obj.sourceScene; info.scene = obj.sourceScene;
info.node = obj.sourceNode;
info.ownerType = obj.ownerType;
info.ownerName = obj.ownerName;
// Use bounds origin transformed to world as a representative point. // Use bounds origin transformed to world as a representative point.
glm::vec3 centerWorld = glm::vec3(obj.transform * glm::vec4(obj.bounds.origin, 1.0f)); glm::vec3 centerWorld = glm::vec3(obj.transform * glm::vec4(obj.bounds.origin, 1.0f));
info.worldPos = centerWorld; info.worldPos = centerWorld;
@@ -856,6 +890,8 @@ void VulkanEngine::run()
{ {
// No object under cursor in ID buffer: clear last pick. // No object under cursor in ID buffer: clear last pick.
_lastPick.valid = false; _lastPick.valid = false;
_lastPick.ownerName.clear();
_lastPick.ownerType = RenderObject::OwnerType::None;
_lastPickObjectID = 0; _lastPickObjectID = 0;
} }
else else
@@ -868,6 +904,9 @@ void VulkanEngine::run()
glm::vec3 fallbackPos = glm::vec3(picked.transform[3]); glm::vec3 fallbackPos = glm::vec3(picked.transform[3]);
_lastPick.mesh = picked.sourceMesh; _lastPick.mesh = picked.sourceMesh;
_lastPick.scene = picked.sourceScene; _lastPick.scene = picked.sourceScene;
_lastPick.node = picked.sourceNode;
_lastPick.ownerType = picked.ownerType;
_lastPick.ownerName = picked.ownerName;
_lastPick.worldPos = fallbackPos; _lastPick.worldPos = fallbackPos;
_lastPick.worldTransform = picked.transform; _lastPick.worldTransform = picked.transform;
_lastPick.firstIndex = picked.firstIndex; _lastPick.firstIndex = picked.firstIndex;
@@ -878,6 +917,8 @@ void VulkanEngine::run()
else else
{ {
_lastPick.valid = false; _lastPick.valid = false;
_lastPick.ownerName.clear();
_lastPick.ownerType = RenderObject::OwnerType::None;
_lastPickObjectID = 0; _lastPickObjectID = 0;
} }
} }
@@ -968,6 +1009,7 @@ void MeshNode::Draw(const glm::mat4 &topMatrix, DrawContext &ctx)
def.surfaceIndex = i; def.surfaceIndex = i;
def.objectID = ctx.nextID++; def.objectID = ctx.nextID++;
def.sourceScene = scene; def.sourceScene = scene;
def.sourceNode = this;
if (s.material->data.passType == MaterialPass::Transparent) if (s.material->data.passType == MaterialPass::Transparent)
{ {

View File

@@ -120,6 +120,9 @@ public:
{ {
MeshAsset *mesh = nullptr; MeshAsset *mesh = nullptr;
LoadedGLTF *scene = nullptr; LoadedGLTF *scene = nullptr;
Node *node = nullptr;
RenderObject::OwnerType ownerType = RenderObject::OwnerType::None;
std::string ownerName;
glm::vec3 worldPos{0.0f}; glm::vec3 worldPos{0.0f};
glm::mat4 worldTransform{1.0f}; glm::mat4 worldTransform{1.0f};
uint32_t indexCount = 0; uint32_t indexCount = 0;
@@ -174,6 +177,12 @@ public:
// Query a conservative streaming texture budget for the texture cache. // Query a conservative streaming texture budget for the texture cache.
size_t query_texture_budget_bytes() const; size_t query_texture_budget_bytes() const;
// Convenience helper: load a glTF from assets/models and add it as a runtime instance.
// modelRelativePath is relative to the AssetManager model root.
bool addGLTFInstance(const std::string &instanceName,
const std::string &modelRelativePath,
const glm::mat4 &transform = glm::mat4(1.f));
bool resize_requested{false}; bool resize_requested{false};
bool freeze_rendering{false}; bool freeze_rendering{false};

View File

@@ -7,11 +7,13 @@
#include "vk_engine.h" #include "vk_engine.h"
#include "imgui.h" #include "imgui.h"
#include "ImGuizmo.h"
#include "render/primitives.h" #include "render/primitives.h"
#include "vk_mem_alloc.h" #include "vk_mem_alloc.h"
#include "render/vk_renderpass_tonemap.h" #include "render/vk_renderpass_tonemap.h"
#include "render/vk_renderpass_background.h" #include "render/vk_renderpass_background.h"
#include <glm/gtx/euler_angles.hpp>
#include "render/rg_graph.h" #include "render/rg_graph.h"
#include "core/vk_pipeline_manager.h" #include "core/vk_pipeline_manager.h"
#include "core/texture_cache.h" #include "core/texture_cache.h"
@@ -21,11 +23,11 @@
#include "mesh_bvh.h" #include "mesh_bvh.h"
namespace { namespace
// Background / compute playground
static void ui_background(VulkanEngine *eng)
{ {
// Background / compute playground
static void ui_background(VulkanEngine *eng)
{
if (!eng || !eng->_renderPassManager) return; if (!eng || !eng->_renderPassManager) return;
auto *background_pass = eng->_renderPassManager->getPass<BackgroundPass>(); auto *background_pass = eng->_renderPassManager->getPass<BackgroundPass>();
if (!background_pass) if (!background_pass)
@@ -46,11 +48,11 @@ static void ui_background(VulkanEngine *eng)
ImGui::Separator(); ImGui::Separator();
ImGui::SliderFloat("Render Scale", &eng->renderScale, 0.3f, 1.f); ImGui::SliderFloat("Render Scale", &eng->renderScale, 0.3f, 1.f);
} }
// IBL test grid spawner (spheres varying metallic/roughness) // IBL test grid spawner (spheres varying metallic/roughness)
static void spawn_ibl_test(VulkanEngine *eng) static void spawn_ibl_test(VulkanEngine *eng)
{ {
if (!eng || !eng->_assetManager || !eng->_sceneManager) return; if (!eng || !eng->_assetManager || !eng->_sceneManager) return;
using MC = GLTFMetallic_Roughness::MaterialConstants; using MC = GLTFMetallic_Roughness::MaterialConstants;
@@ -92,7 +94,8 @@ static void spawn_ibl_test(VulkanEngine *eng)
MC chrome{}; MC chrome{};
chrome.colorFactors = glm::vec4(0.9f, 0.9f, 0.9f, 1.0f); chrome.colorFactors = glm::vec4(0.9f, 0.9f, 0.9f, 1.0f);
chrome.metal_rough_factors = glm::vec4(1.0f, 0.06f, 0, 0); chrome.metal_rough_factors = glm::vec4(1.0f, 0.06f, 0, 0);
auto mat = eng->_assetManager->createMaterialFromConstants("ibltest.chrome.mat", chrome, MaterialPass::MainColor); auto mat = eng->_assetManager->createMaterialFromConstants("ibltest.chrome.mat", chrome,
MaterialPass::MainColor);
auto mesh = eng->_assetManager->createMesh("ibltest.chrome.mesh", auto mesh = eng->_assetManager->createMesh("ibltest.chrome.mesh",
std::span<Vertex>(verts.data(), verts.size()), std::span<Vertex>(verts.data(), verts.size()),
std::span<uint32_t>(inds.data(), inds.size()), std::span<uint32_t>(inds.data(), inds.size()),
@@ -101,12 +104,12 @@ static void spawn_ibl_test(VulkanEngine *eng)
eng->_sceneManager->addMeshInstance("ibltest.chrome.inst", mesh, M, BoundsType::Sphere); eng->_sceneManager->addMeshInstance("ibltest.chrome.inst", mesh, M, BoundsType::Sphere);
eng->_iblTestNames.insert(eng->_iblTestNames.end(), eng->_iblTestNames.insert(eng->_iblTestNames.end(),
{"ibltest.chrome.inst", "ibltest.chrome.mesh", "ibltest.chrome.mat"}); {"ibltest.chrome.inst", "ibltest.chrome.mesh", "ibltest.chrome.mat"});
} } {
{
MC glass{}; MC glass{};
glass.colorFactors = glm::vec4(0.9f, 0.95f, 1.0f, 0.25f); glass.colorFactors = glm::vec4(0.9f, 0.95f, 1.0f, 0.25f);
glass.metal_rough_factors = glm::vec4(0.0f, 0.02f, 0, 0); glass.metal_rough_factors = glm::vec4(0.0f, 0.02f, 0, 0);
auto mat = eng->_assetManager->createMaterialFromConstants("ibltest.glass.mat", glass, MaterialPass::Transparent); auto mat = eng->_assetManager->createMaterialFromConstants("ibltest.glass.mat", glass,
MaterialPass::Transparent);
auto mesh = eng->_assetManager->createMesh("ibltest.glass.mesh", auto mesh = eng->_assetManager->createMesh("ibltest.glass.mesh",
std::span<Vertex>(verts.data(), verts.size()), std::span<Vertex>(verts.data(), verts.size()),
std::span<uint32_t>(inds.data(), inds.size()), std::span<uint32_t>(inds.data(), inds.size()),
@@ -116,10 +119,10 @@ static void spawn_ibl_test(VulkanEngine *eng)
eng->_iblTestNames.insert(eng->_iblTestNames.end(), eng->_iblTestNames.insert(eng->_iblTestNames.end(),
{"ibltest.glass.inst", "ibltest.glass.mesh", "ibltest.glass.mat"}); {"ibltest.glass.inst", "ibltest.glass.mesh", "ibltest.glass.mat"});
} }
} }
static void clear_ibl_test(VulkanEngine *eng) static void clear_ibl_test(VulkanEngine *eng)
{ {
if (!eng || !eng->_sceneManager || !eng->_assetManager) return; if (!eng || !eng->_sceneManager || !eng->_assetManager) return;
for (size_t i = 0; i < eng->_iblTestNames.size(); ++i) for (size_t i = 0; i < eng->_iblTestNames.size(); ++i)
{ {
@@ -129,21 +132,21 @@ static void clear_ibl_test(VulkanEngine *eng)
else if (n.ends_with(".mesh")) eng->_assetManager->removeMesh(n); else if (n.ends_with(".mesh")) eng->_assetManager->removeMesh(n);
} }
eng->_iblTestNames.clear(); eng->_iblTestNames.clear();
} }
static void ui_ibl(VulkanEngine *eng) static void ui_ibl(VulkanEngine *eng)
{ {
if (!eng) return; if (!eng) return;
if (ImGui::Button("Spawn IBL Test Grid")) { spawn_ibl_test(eng); } if (ImGui::Button("Spawn IBL Test Grid")) { spawn_ibl_test(eng); }
ImGui::SameLine(); ImGui::SameLine();
if (ImGui::Button("Clear IBL Test")) { clear_ibl_test(eng); } if (ImGui::Button("Clear IBL Test")) { clear_ibl_test(eng); }
ImGui::TextUnformatted( ImGui::TextUnformatted(
"5x5 spheres: metallic across columns, roughness across rows.\nExtra: chrome + glass."); "5x5 spheres: metallic across columns, roughness across rows.\nExtra: chrome + glass.");
} }
// Quick stats & targets overview // Quick stats & targets overview
static void ui_overview(VulkanEngine *eng) static void ui_overview(VulkanEngine *eng)
{ {
if (!eng) return; if (!eng) return;
ImGui::Text("frametime %.2f ms", eng->stats.frametime); ImGui::Text("frametime %.2f ms", eng->stats.frametime);
ImGui::Text("draw time %.2f ms", eng->stats.mesh_draw_time); ImGui::Text("draw time %.2f ms", eng->stats.mesh_draw_time);
@@ -157,11 +160,11 @@ static void ui_overview(VulkanEngine *eng)
ImGui::Text("Swapchain: %ux%u", scExt.width, scExt.height); ImGui::Text("Swapchain: %ux%u", scExt.width, scExt.height);
ImGui::Text("Draw fmt: %s", string_VkFormat(eng->_swapchainManager->drawImage().imageFormat)); ImGui::Text("Draw fmt: %s", string_VkFormat(eng->_swapchainManager->drawImage().imageFormat));
ImGui::Text("Swap fmt: %s", string_VkFormat(eng->_swapchainManager->swapchainImageFormat())); ImGui::Text("Swap fmt: %s", string_VkFormat(eng->_swapchainManager->swapchainImageFormat()));
} }
// Texture streaming + budget UI // Texture streaming + budget UI
static const char *stateName(uint8_t s) static const char *stateName(uint8_t s)
{ {
switch (s) switch (s)
{ {
case 0: return "Unloaded"; case 0: return "Unloaded";
@@ -170,10 +173,10 @@ static const char *stateName(uint8_t s)
case 3: return "Evicted"; case 3: return "Evicted";
default: return "?"; default: return "?";
} }
} }
static void ui_textures(VulkanEngine *eng) static void ui_textures(VulkanEngine *eng)
{ {
if (!eng || !eng->_textureCache) if (!eng || !eng->_textureCache)
{ {
ImGui::TextUnformatted("TextureCache not available"); ImGui::TextUnformatted("TextureCache not available");
@@ -266,7 +269,7 @@ static void ui_textures(VulkanEngine *eng)
ImGui::TableSetupColumn("Name"); ImGui::TableSetupColumn("Name");
ImGui::TableHeadersRow(); ImGui::TableHeadersRow();
int count = 0; int count = 0;
for (const auto &r : rows) for (const auto &r: rows)
{ {
if (count++ >= topN) break; if (count++ >= topN) break;
ImGui::TableNextRow(); ImGui::TableNextRow();
@@ -281,11 +284,11 @@ static void ui_textures(VulkanEngine *eng)
} }
ImGui::EndTable(); ImGui::EndTable();
} }
} }
// Shadows / Ray Query controls // Shadows / Ray Query controls
static void ui_shadows(VulkanEngine *eng) static void ui_shadows(VulkanEngine *eng)
{ {
if (!eng) return; if (!eng) return;
const bool rq = eng->_deviceManager->supportsRayQuery(); const bool rq = eng->_deviceManager->supportsRayQuery();
const bool as = eng->_deviceManager->supportsAccelerationStructure(); const bool as = eng->_deviceManager->supportsAccelerationStructure();
@@ -324,11 +327,11 @@ static void ui_shadows(VulkanEngine *eng)
ImGui::Separator(); ImGui::Separator();
ImGui::TextWrapped( ImGui::TextWrapped(
"Clipmap only: raster PCF+RPDB. Clipmap+RT: PCF assisted by ray query at low N·L. RT only: skip shadow maps and use ray tests only."); "Clipmap only: raster PCF+RPDB. Clipmap+RT: PCF assisted by ray query at low N·L. RT only: skip shadow maps and use ray tests only.");
} }
// Render Graph inspection (passes, images, buffers) // Render Graph inspection (passes, images, buffers)
static void ui_render_graph(VulkanEngine *eng) static void ui_render_graph(VulkanEngine *eng)
{ {
if (!eng || !eng->_renderGraph) if (!eng || !eng->_renderGraph)
{ {
ImGui::TextUnformatted("RenderGraph not available"); ImGui::TextUnformatted("RenderGraph not available");
@@ -354,8 +357,7 @@ static void ui_render_graph(VulkanEngine *eng)
ImGui::TableSetupColumn("Attachments", ImGuiTableColumnFlags_WidthFixed, 100); ImGui::TableSetupColumn("Attachments", ImGuiTableColumnFlags_WidthFixed, 100);
ImGui::TableHeadersRow(); ImGui::TableHeadersRow();
auto typeName = [](RGPassType t) auto typeName = [](RGPassType t) {
{
switch (t) switch (t)
{ {
case RGPassType::Graphics: return "Graphics"; case RGPassType::Graphics: return "Graphics";
@@ -411,7 +413,7 @@ static void ui_render_graph(VulkanEngine *eng)
ImGui::TableSetupColumn("Usage", ImGuiTableColumnFlags_WidthFixed, 80); ImGui::TableSetupColumn("Usage", ImGuiTableColumnFlags_WidthFixed, 80);
ImGui::TableSetupColumn("Life", ImGuiTableColumnFlags_WidthFixed, 80); ImGui::TableSetupColumn("Life", ImGuiTableColumnFlags_WidthFixed, 80);
ImGui::TableHeadersRow(); ImGui::TableHeadersRow();
for (const auto &im : imgs) for (const auto &im: imgs)
{ {
ImGui::TableNextRow(); ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0); ImGui::TableSetColumnIndex(0);
@@ -446,7 +448,7 @@ static void ui_render_graph(VulkanEngine *eng)
ImGui::TableSetupColumn("Usage", ImGuiTableColumnFlags_WidthFixed, 100); ImGui::TableSetupColumn("Usage", ImGuiTableColumnFlags_WidthFixed, 100);
ImGui::TableSetupColumn("Life", ImGuiTableColumnFlags_WidthFixed, 80); ImGui::TableSetupColumn("Life", ImGuiTableColumnFlags_WidthFixed, 80);
ImGui::TableHeadersRow(); ImGui::TableHeadersRow();
for (const auto &bf : bufs) for (const auto &bf: bufs)
{ {
ImGui::TableNextRow(); ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0); ImGui::TableSetColumnIndex(0);
@@ -465,11 +467,11 @@ static void ui_render_graph(VulkanEngine *eng)
ImGui::EndTable(); ImGui::EndTable();
} }
} }
} }
// Pipeline manager (graphics) // Pipeline manager (graphics)
static void ui_pipelines(VulkanEngine *eng) static void ui_pipelines(VulkanEngine *eng)
{ {
if (!eng || !eng->_pipelineManager) if (!eng || !eng->_pipelineManager)
{ {
ImGui::TextUnformatted("PipelineManager not available"); ImGui::TextUnformatted("PipelineManager not available");
@@ -487,7 +489,7 @@ static void ui_pipelines(VulkanEngine *eng)
ImGui::TableSetupColumn("FS"); ImGui::TableSetupColumn("FS");
ImGui::TableSetupColumn("Valid", ImGuiTableColumnFlags_WidthFixed, 60); ImGui::TableSetupColumn("Valid", ImGuiTableColumnFlags_WidthFixed, 60);
ImGui::TableHeadersRow(); ImGui::TableHeadersRow();
for (const auto &p : pipes) for (const auto &p: pipes)
{ {
ImGui::TableNextRow(); ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0); ImGui::TableSetColumnIndex(0);
@@ -501,11 +503,11 @@ static void ui_pipelines(VulkanEngine *eng)
} }
ImGui::EndTable(); ImGui::EndTable();
} }
} }
// Post-processing // Post-processing
static void ui_postfx(VulkanEngine *eng) static void ui_postfx(VulkanEngine *eng)
{ {
if (!eng) return; if (!eng) return;
if (auto *tm = eng->_renderPassManager ? eng->_renderPassManager->getPass<TonemapPass>() : nullptr) if (auto *tm = eng->_renderPassManager ? eng->_renderPassManager->getPass<TonemapPass>() : nullptr)
{ {
@@ -530,11 +532,11 @@ static void ui_postfx(VulkanEngine *eng)
{ {
ImGui::TextUnformatted("Tonemap pass not available"); ImGui::TextUnformatted("Tonemap pass not available");
} }
} }
// Scene debug bits // Scene debug bits
static void ui_scene(VulkanEngine *eng) static void ui_scene(VulkanEngine *eng)
{ {
if (!eng) return; if (!eng) return;
const DrawContext &dc = eng->_context->getMainDrawContext(); const DrawContext &dc = eng->_context->getMainDrawContext();
ImGui::Text("Opaque draws: %zu", dc.OpaqueSurfaces.size()); ImGui::Text("Opaque draws: %zu", dc.OpaqueSurfaces.size());
@@ -545,6 +547,93 @@ static void ui_scene(VulkanEngine *eng)
ImGui::Checkbox("Debug draw mesh BVH (last pick)", &eng->_debugDrawBVH); ImGui::Checkbox("Debug draw mesh BVH (last pick)", &eng->_debugDrawBVH);
ImGui::Separator(); ImGui::Separator();
// Spawn glTF instances (runtime)
ImGui::TextUnformatted("Spawn glTF instance");
static char gltfPath[256] = "mirage2000/scene.gltf";
static char gltfName[128] = "gltf_01";
static float gltfPos[3] = {0.0f, 0.0f, 0.0f};
static float gltfRot[3] = {0.0f, 0.0f, 0.0f}; // pitch, yaw, roll (deg)
static float gltfScale[3] = {1.0f, 1.0f, 1.0f};
ImGui::InputText("Model path (assets/models/...)", gltfPath, IM_ARRAYSIZE(gltfPath));
ImGui::InputText("Instance name", gltfName, IM_ARRAYSIZE(gltfName));
ImGui::InputFloat3("Position", gltfPos);
ImGui::InputFloat3("Rotation (deg XYZ)", gltfRot);
ImGui::InputFloat3("Scale", gltfScale);
if (ImGui::Button("Add glTF instance"))
{
glm::mat4 T = glm::translate(glm::mat4(1.0f), glm::vec3(gltfPos[0], gltfPos[1], gltfPos[2]));
glm::mat4 R = glm::eulerAngleXYZ(glm::radians(gltfRot[0]),
glm::radians(gltfRot[1]),
glm::radians(gltfRot[2]));
glm::mat4 S = glm::scale(glm::mat4(1.0f), glm::vec3(gltfScale[0], gltfScale[1], gltfScale[2]));
glm::mat4 M = T * R * S;
eng->addGLTFInstance(gltfName, gltfPath, M);
}
ImGui::Separator();
// Spawn primitive mesh instances (cube/sphere)
ImGui::TextUnformatted("Spawn primitive");
static int primType = 0; // 0 = cube, 1 = sphere
static char primName[128] = "prim_01";
static float primPos[3] = {0.0f, 0.0f, 0.0f};
static float primRot[3] = {0.0f, 0.0f, 0.0f};
static float primScale[3] = {1.0f, 1.0f, 1.0f};
ImGui::RadioButton("Cube", &primType, 0); ImGui::SameLine();
ImGui::RadioButton("Sphere", &primType, 1);
ImGui::InputText("Primitive name", primName, IM_ARRAYSIZE(primName));
ImGui::InputFloat3("Prim Position", primPos);
ImGui::InputFloat3("Prim Rotation (deg XYZ)", primRot);
ImGui::InputFloat3("Prim Scale", primScale);
if (ImGui::Button("Add primitive instance"))
{
std::shared_ptr<MeshAsset> mesh = (primType == 0) ? eng->cubeMesh : eng->sphereMesh;
if (mesh)
{
glm::mat4 T = glm::translate(glm::mat4(1.0f), glm::vec3(primPos[0], primPos[1], primPos[2]));
glm::mat4 R = glm::eulerAngleXYZ(glm::radians(primRot[0]),
glm::radians(primRot[1]),
glm::radians(primRot[2]));
glm::mat4 S = glm::scale(glm::mat4(1.0f), glm::vec3(primScale[0], primScale[1], primScale[2]));
glm::mat4 M = T * R * S;
eng->_sceneManager->addMeshInstance(primName, mesh, M);
}
}
ImGui::Separator();
// Delete selected model/primitive (uses last pick if valid, otherwise hover)
static std::string deleteStatus;
if (ImGui::Button("Delete selected"))
{
deleteStatus.clear();
const auto *pick = eng->_lastPick.valid ? &eng->_lastPick
: (eng->_hoverPick.valid ? &eng->_hoverPick : nullptr);
if (!pick || pick->ownerName.empty())
{
deleteStatus = "No selection to delete.";
}
else if (pick->ownerType == RenderObject::OwnerType::MeshInstance)
{
bool ok = eng->_sceneManager->removeMeshInstance(pick->ownerName);
deleteStatus = ok ? "Removed mesh instance: " + pick->ownerName
: "Mesh instance not found: " + pick->ownerName;
}
else if (pick->ownerType == RenderObject::OwnerType::GLTFInstance)
{
bool ok = eng->_sceneManager->removeGLTFInstance(pick->ownerName);
deleteStatus = ok ? "Removed glTF instance: " + pick->ownerName
: "glTF instance not found: " + pick->ownerName;
}
else
{
deleteStatus = "Cannot delete this object type (static scene).";
}
}
if (!deleteStatus.empty())
{
ImGui::TextUnformatted(deleteStatus.c_str());
}
ImGui::Separator();
if (eng->_lastPick.valid) if (eng->_lastPick.valid)
{ {
const char *meshName = eng->_lastPick.mesh ? eng->_lastPick.mesh->name.c_str() : "<unknown>"; const char *meshName = eng->_lastPick.mesh ? eng->_lastPick.mesh->name.c_str() : "<unknown>";
@@ -562,6 +651,16 @@ static void ui_scene(VulkanEngine *eng)
eng->_lastPick.worldPos.x, eng->_lastPick.worldPos.x,
eng->_lastPick.worldPos.y, eng->_lastPick.worldPos.y,
eng->_lastPick.worldPos.z); eng->_lastPick.worldPos.z);
const char *ownerTypeStr = "none";
switch (eng->_lastPick.ownerType)
{
case RenderObject::OwnerType::MeshInstance: ownerTypeStr = "mesh instance"; break;
case RenderObject::OwnerType::GLTFInstance: ownerTypeStr = "glTF instance"; break;
case RenderObject::OwnerType::StaticGLTF: ownerTypeStr = "glTF scene"; break;
default: break;
}
const char *ownerName = eng->_lastPick.ownerName.empty() ? "<unnamed>" : eng->_lastPick.ownerName.c_str();
ImGui::Text("Owner: %s (%s)", ownerName, ownerTypeStr);
ImGui::Text("Indices: first=%u count=%u", ImGui::Text("Indices: first=%u count=%u",
eng->_lastPick.firstIndex, eng->_lastPick.firstIndex,
eng->_lastPick.indexCount); eng->_lastPick.indexCount);
@@ -590,6 +689,16 @@ static void ui_scene(VulkanEngine *eng)
{ {
const char *meshName = eng->_hoverPick.mesh ? eng->_hoverPick.mesh->name.c_str() : "<unknown>"; const char *meshName = eng->_hoverPick.mesh ? eng->_hoverPick.mesh->name.c_str() : "<unknown>";
ImGui::Text("Hover mesh: %s (surface %u)", meshName, eng->_hoverPick.surfaceIndex); ImGui::Text("Hover mesh: %s (surface %u)", meshName, eng->_hoverPick.surfaceIndex);
const char *ownerTypeStr = "none";
switch (eng->_hoverPick.ownerType)
{
case RenderObject::OwnerType::MeshInstance: ownerTypeStr = "mesh instance"; break;
case RenderObject::OwnerType::GLTFInstance: ownerTypeStr = "glTF instance"; break;
case RenderObject::OwnerType::StaticGLTF: ownerTypeStr = "glTF scene"; break;
default: break;
}
const char *ownerName = eng->_hoverPick.ownerName.empty() ? "<unnamed>" : eng->_hoverPick.ownerName.c_str();
ImGui::Text("Hover owner: %s (%s)", ownerName, ownerTypeStr);
} }
else else
{ {
@@ -599,14 +708,67 @@ static void ui_scene(VulkanEngine *eng)
{ {
ImGui::Text("Drag selection: %zu objects", eng->_dragSelection.size()); ImGui::Text("Drag selection: %zu objects", eng->_dragSelection.size());
} }
}
ImGui::Separator();
ImGui::TextUnformatted("Object Gizmo (ImGuizmo)");
if (!eng->_sceneManager)
{
ImGui::TextUnformatted("SceneManager not available");
return;
}
SceneManager *sceneMgr = eng->_sceneManager.get();
const GPUSceneData &sceneData = sceneMgr->getSceneData();
static ImGuizmo::OPERATION op = ImGuizmo::TRANSLATE;
static ImGuizmo::MODE mode = ImGuizmo::LOCAL;
ImGui::TextUnformatted("Operation");
if (ImGui::RadioButton("Translate", op == ImGuizmo::TRANSLATE)) op = ImGuizmo::TRANSLATE;
ImGui::SameLine();
if (ImGui::RadioButton("Rotate", op == ImGuizmo::ROTATE)) op = ImGuizmo::ROTATE;
ImGui::SameLine();
if (ImGui::RadioButton("Scale", op == ImGuizmo::SCALE)) op = ImGuizmo::SCALE;
ImGui::TextUnformatted("Mode");
if (ImGui::RadioButton("Local", mode == ImGuizmo::LOCAL)) mode = ImGuizmo::LOCAL;
ImGui::SameLine();
if (ImGui::RadioButton("World", mode == ImGuizmo::WORLD)) mode = ImGuizmo::WORLD;
// Resolve a dynamic instance transform for the current pick.
glm::mat4 targetTransform(1.0f);
enum class GizmoTarget
{
None,
MeshInstance,
GLTFInstance,
Node
};
GizmoTarget target = GizmoTarget::None;
ImGuiIO &io = ImGui::GetIO();
ImGuizmo::SetOrthographic(false);
ImGuizmo::SetDrawlist();
ImGuizmo::SetRect(0.0f, 0.0f, io.DisplaySize.x, io.DisplaySize.y);
glm::mat4 view = sceneData.view;
glm::mat4 proj = sceneData.proj;
proj[1][1] *= -1.0f;
ImGuizmo::Manipulate(&view[0][0], &proj[0][0],
op, mode,
&targetTransform[0][0]);
}
} // namespace } // namespace
void vk_engine_draw_debug_ui(VulkanEngine *eng) void vk_engine_draw_debug_ui(VulkanEngine *eng)
{ {
if (!eng) return; if (!eng) return;
ImGuizmo::BeginFrame();
// Consolidated debug window with tabs // Consolidated debug window with tabs
if (ImGui::Begin("Debug")) if (ImGui::Begin("Debug"))
{ {

View File

@@ -34,6 +34,9 @@ void SceneManager::update_scene()
{ {
auto start = std::chrono::system_clock::now(); auto start = std::chrono::system_clock::now();
// Release any GLTF assets that were scheduled for safe destruction after GPU idle.
pendingGLTFRelease.clear();
mainDrawContext.OpaqueSurfaces.clear(); mainDrawContext.OpaqueSurfaces.clear();
mainDrawContext.TransparentSurfaces.clear(); mainDrawContext.TransparentSurfaces.clear();
mainDrawContext.nextID = 1; mainDrawContext.nextID = 1;
@@ -78,12 +81,30 @@ void SceneManager::update_scene()
} }
} }
auto tagOwner = [&](RenderObject::OwnerType type, const std::string &name,
size_t opaqueBegin, size_t transpBegin)
{
for (size_t i = opaqueBegin; i < mainDrawContext.OpaqueSurfaces.size(); ++i)
{
mainDrawContext.OpaqueSurfaces[i].ownerType = type;
mainDrawContext.OpaqueSurfaces[i].ownerName = name;
}
for (size_t i = transpBegin; i < mainDrawContext.TransparentSurfaces.size(); ++i)
{
mainDrawContext.TransparentSurfaces[i].ownerType = type;
mainDrawContext.TransparentSurfaces[i].ownerName = name;
}
};
// Draw all loaded GLTF scenes (static world) // Draw all loaded GLTF scenes (static world)
for (auto &[name, scene] : loadedScenes) for (auto &[name, scene] : loadedScenes)
{ {
if (scene) if (scene)
{ {
const size_t opaqueStart = mainDrawContext.OpaqueSurfaces.size();
const size_t transpStart = mainDrawContext.TransparentSurfaces.size();
scene->Draw(glm::mat4{1.f}, mainDrawContext); scene->Draw(glm::mat4{1.f}, mainDrawContext);
tagOwner(RenderObject::OwnerType::StaticGLTF, name, opaqueStart, transpStart);
} }
} }
@@ -93,7 +114,10 @@ void SceneManager::update_scene()
const GLTFInstance &inst = kv.second; const GLTFInstance &inst = kv.second;
if (inst.scene) if (inst.scene)
{ {
const size_t opaqueStart = mainDrawContext.OpaqueSurfaces.size();
const size_t transpStart = mainDrawContext.TransparentSurfaces.size();
inst.scene->Draw(inst.transform, mainDrawContext); inst.scene->Draw(inst.transform, mainDrawContext);
tagOwner(RenderObject::OwnerType::GLTFInstance, kv.first, opaqueStart, transpStart);
} }
} }
@@ -123,6 +147,8 @@ void SceneManager::update_scene()
obj.sourceMesh = inst.mesh.get(); obj.sourceMesh = inst.mesh.get();
obj.surfaceIndex = surfaceIndex++; obj.surfaceIndex = surfaceIndex++;
obj.objectID = mainDrawContext.nextID++; obj.objectID = mainDrawContext.nextID++;
obj.ownerType = RenderObject::OwnerType::MeshInstance;
obj.ownerName = kv.first;
if (obj.material->passType == MaterialPass::Transparent) if (obj.material->passType == MaterialPass::Transparent)
{ {
mainDrawContext.TransparentSurfaces.push_back(obj); mainDrawContext.TransparentSurfaces.push_back(obj);
@@ -299,7 +325,17 @@ void SceneManager::addGLTFInstance(const std::string &name, std::shared_ptr<Load
bool SceneManager::removeGLTFInstance(const std::string &name) bool SceneManager::removeGLTFInstance(const std::string &name)
{ {
return dynamicGLTFInstances.erase(name) > 0; auto it = dynamicGLTFInstances.find(name);
if (it == dynamicGLTFInstances.end()) return false;
// Defer destruction until after the next frame fence (update_scene).
if (it->second.scene)
{
pendingGLTFRelease.push_back(it->second.scene);
}
dynamicGLTFInstances.erase(it);
return true;
} }
bool SceneManager::setGLTFInstanceTransform(const std::string &name, const glm::mat4 &transform) bool SceneManager::setGLTFInstanceTransform(const std::string &name, const glm::mat4 &transform)
@@ -312,6 +348,13 @@ bool SceneManager::setGLTFInstanceTransform(const std::string &name, const glm::
void SceneManager::clearGLTFInstances() void SceneManager::clearGLTFInstances()
{ {
for (auto &kv : dynamicGLTFInstances)
{
if (kv.second.scene)
{
pendingGLTFRelease.push_back(kv.second.scene);
}
}
dynamicGLTFInstances.clear(); dynamicGLTFInstances.clear();
} }

View File

@@ -5,6 +5,7 @@
#include <memory> #include <memory>
#include <optional> #include <optional>
#include <glm/vec2.hpp> #include <glm/vec2.hpp>
#include <string>
#include "scene/vk_loader.h" #include "scene/vk_loader.h"
class EngineContext; class EngineContext;
@@ -28,8 +29,19 @@ struct RenderObject
uint32_t surfaceIndex = 0; uint32_t surfaceIndex = 0;
// Unique per-draw identifier for ID-buffer picking (0 = none). // Unique per-draw identifier for ID-buffer picking (0 = none).
uint32_t objectID = 0; uint32_t objectID = 0;
// Optional owning glTF scene for this draw (null for procedural/dynamic meshes). // Optional logical owner for editor/picking (instance name etc.).
enum class OwnerType : uint8_t
{
None = 0,
StaticGLTF, // loaded scene
GLTFInstance, // runtime glTF instance with transform
MeshInstance // dynamic primitive/mesh instance
};
OwnerType ownerType = OwnerType::None;
std::string ownerName;
// Optional owning glTF scene and node for this draw (null for procedural/dynamic meshes).
LoadedGLTF *sourceScene = nullptr; LoadedGLTF *sourceScene = nullptr;
Node *sourceNode = nullptr;
}; };
struct DrawContext struct DrawContext
@@ -137,6 +149,9 @@ private:
std::unordered_map<std::string, std::shared_ptr<Node> > loadedNodes; std::unordered_map<std::string, std::shared_ptr<Node> > loadedNodes;
std::unordered_map<std::string, MeshInstance> dynamicMeshInstances; std::unordered_map<std::string, MeshInstance> dynamicMeshInstances;
std::unordered_map<std::string, GLTFInstance> dynamicGLTFInstances; std::unordered_map<std::string, GLTFInstance> dynamicGLTFInstances;
// Keep GLTF assets alive until after the next frame fence to avoid destroying
// GPU resources that might still be in-flight.
std::vector<std::shared_ptr<LoadedGLTF>> pendingGLTFRelease;
PickingDebug pickingDebug{}; PickingDebug pickingDebug{};
}; };