ADD: UI/picking manager
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -2,4 +2,7 @@
|
|||||||
/bin
|
/bin
|
||||||
/assets
|
/assets
|
||||||
/.idea
|
/.idea
|
||||||
|
/cmake-build-debug
|
||||||
|
/cmake-build-debug_win
|
||||||
|
/cmake-build-release_win
|
||||||
*.spv
|
*.spv
|
||||||
@@ -11,6 +11,10 @@ add_executable (vulkan_engine
|
|||||||
core/engine.h
|
core/engine.h
|
||||||
core/engine.cpp
|
core/engine.cpp
|
||||||
core/engine_ui.cpp
|
core/engine_ui.cpp
|
||||||
|
core/ui/imgui_system.h
|
||||||
|
core/ui/imgui_system.cpp
|
||||||
|
core/picking/picking_system.h
|
||||||
|
core/picking/picking_system.cpp
|
||||||
core/game_api.h
|
core/game_api.h
|
||||||
core/game_api.cpp
|
core/game_api.cpp
|
||||||
# core/device
|
# core/device
|
||||||
|
|||||||
@@ -27,6 +27,26 @@ void SwapchainManager::init_swapchain()
|
|||||||
resize_render_targets(_renderExtent);
|
resize_render_targets(_renderExtent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SwapchainManager::set_window_extent_from_window(struct SDL_Window *window)
|
||||||
|
{
|
||||||
|
if (!window)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int w = 0, h = 0;
|
||||||
|
SDL_Vulkan_GetDrawableSize(window, &w, &h);
|
||||||
|
if (w <= 0 || h <= 0)
|
||||||
|
{
|
||||||
|
SDL_GetWindowSize(window, &w, &h);
|
||||||
|
}
|
||||||
|
if (w > 0 && h > 0)
|
||||||
|
{
|
||||||
|
_windowExtent.width = static_cast<uint32_t>(w);
|
||||||
|
_windowExtent.height = static_cast<uint32_t>(h);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void SwapchainManager::cleanup()
|
void SwapchainManager::cleanup()
|
||||||
{
|
{
|
||||||
_deletionQueue.flush();
|
_deletionQueue.flush();
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ public:
|
|||||||
|
|
||||||
void cleanup();
|
void cleanup();
|
||||||
|
|
||||||
|
void set_window_extent_from_window(struct SDL_Window *window);
|
||||||
void init_swapchain();
|
void init_swapchain();
|
||||||
void create_swapchain(uint32_t width, uint32_t height);
|
void create_swapchain(uint32_t width, uint32_t height);
|
||||||
void destroy_swapchain() const;
|
void destroy_swapchain() const;
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
#include "engine.h"
|
#include "engine.h"
|
||||||
|
|
||||||
#include "SDL2/SDL.h"
|
#include "SDL2/SDL.h"
|
||||||
|
#include "SDL2/SDL_vulkan.h"
|
||||||
|
|
||||||
#include <core/util/initializers.h>
|
#include <core/util/initializers.h>
|
||||||
#include <core/types.h>
|
#include <core/types.h>
|
||||||
@@ -37,9 +38,8 @@
|
|||||||
#include "render/primitives.h"
|
#include "render/primitives.h"
|
||||||
|
|
||||||
#include "vk_mem_alloc.h"
|
#include "vk_mem_alloc.h"
|
||||||
#include "imgui.h"
|
#include "core/ui/imgui_system.h"
|
||||||
#include "imgui_impl_sdl2.h"
|
#include "core/picking/picking_system.h"
|
||||||
#include "imgui_impl_vulkan.h"
|
|
||||||
#include "render/passes/geometry.h"
|
#include "render/passes/geometry.h"
|
||||||
#include "render/passes/imgui_pass.h"
|
#include "render/passes/imgui_pass.h"
|
||||||
#include "render/passes/lighting.h"
|
#include "render/passes/lighting.h"
|
||||||
@@ -59,6 +59,8 @@ void vk_engine_draw_debug_ui(VulkanEngine *eng);
|
|||||||
|
|
||||||
VulkanEngine *loadedEngine = nullptr;
|
VulkanEngine *loadedEngine = nullptr;
|
||||||
|
|
||||||
|
VulkanEngine::~VulkanEngine() = default;
|
||||||
|
|
||||||
static VkExtent2D clamp_nonzero_extent(VkExtent2D extent)
|
static VkExtent2D clamp_nonzero_extent(VkExtent2D extent)
|
||||||
{
|
{
|
||||||
if (extent.width == 0) extent.width = 1;
|
if (extent.width == 0) extent.width = 1;
|
||||||
@@ -166,6 +168,13 @@ size_t VulkanEngine::query_texture_budget_bytes() const
|
|||||||
|
|
||||||
void VulkanEngine::init()
|
void VulkanEngine::init()
|
||||||
{
|
{
|
||||||
|
// DPI awareness and HiDPI behavior must be configured before initializing the video subsystem.
|
||||||
|
#if defined(_WIN32)
|
||||||
|
#ifdef SDL_HINT_WINDOWS_DPI_AWARENESS
|
||||||
|
SDL_SetHint(SDL_HINT_WINDOWS_DPI_AWARENESS, "permonitorv2");
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
// We initialize SDL and create a window with it.
|
// We initialize SDL and create a window with it.
|
||||||
SDL_Init(SDL_INIT_VIDEO);
|
SDL_Init(SDL_INIT_VIDEO);
|
||||||
|
|
||||||
@@ -175,7 +184,11 @@ void VulkanEngine::init()
|
|||||||
_logicalRenderExtent = clamp_nonzero_extent(_logicalRenderExtent);
|
_logicalRenderExtent = clamp_nonzero_extent(_logicalRenderExtent);
|
||||||
_drawExtent = scaled_extent(_logicalRenderExtent, renderScale);
|
_drawExtent = scaled_extent(_logicalRenderExtent, renderScale);
|
||||||
|
|
||||||
constexpr auto window_flags = static_cast<SDL_WindowFlags>(SDL_WINDOW_VULKAN | SDL_WINDOW_RESIZABLE);
|
SDL_WindowFlags window_flags = static_cast<SDL_WindowFlags>(SDL_WINDOW_VULKAN | SDL_WINDOW_RESIZABLE);
|
||||||
|
if (_hiDpiEnabled)
|
||||||
|
{
|
||||||
|
window_flags = static_cast<SDL_WindowFlags>(window_flags | SDL_WINDOW_ALLOW_HIGHDPI);
|
||||||
|
}
|
||||||
|
|
||||||
_swapchainManager = std::make_unique<SwapchainManager>();
|
_swapchainManager = std::make_unique<SwapchainManager>();
|
||||||
|
|
||||||
@@ -188,6 +201,11 @@ void VulkanEngine::init()
|
|||||||
window_flags
|
window_flags
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (_swapchainManager)
|
||||||
|
{
|
||||||
|
_swapchainManager->set_window_extent_from_window(_window);
|
||||||
|
}
|
||||||
|
|
||||||
_windowMode = WindowMode::Windowed;
|
_windowMode = WindowMode::Windowed;
|
||||||
_windowDisplayIndex = SDL_GetWindowDisplayIndex(_window);
|
_windowDisplayIndex = SDL_GetWindowDisplayIndex(_window);
|
||||||
if (_windowDisplayIndex < 0) _windowDisplayIndex = 0;
|
if (_windowDisplayIndex < 0) _windowDisplayIndex = 0;
|
||||||
@@ -283,6 +301,9 @@ void VulkanEngine::init()
|
|||||||
_context->window = _window;
|
_context->window = _window;
|
||||||
_context->stats = &stats;
|
_context->stats = &stats;
|
||||||
|
|
||||||
|
_picking = std::make_unique<PickingSystem>();
|
||||||
|
_picking->init(_context.get());
|
||||||
|
|
||||||
// Render graph skeleton
|
// Render graph skeleton
|
||||||
_renderGraph = std::make_unique<RenderGraph>();
|
_renderGraph = std::make_unique<RenderGraph>();
|
||||||
_renderGraph->init(_context.get());
|
_renderGraph->init(_context.get());
|
||||||
@@ -341,6 +362,10 @@ 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));
|
||||||
|
|
||||||
|
_ui = std::make_unique<ImGuiSystem>();
|
||||||
|
_ui->init(_context.get());
|
||||||
|
_ui->add_draw_callback([this]() { vk_engine_draw_debug_ui(this); });
|
||||||
|
|
||||||
_resourceManager->set_deferred_uploads(true);
|
_resourceManager->set_deferred_uploads(true);
|
||||||
|
|
||||||
_context->enableSSR = true;
|
_context->enableSSR = true;
|
||||||
@@ -456,6 +481,10 @@ void VulkanEngine::set_window_mode(WindowMode mode, int display_index)
|
|||||||
if (_swapchainManager)
|
if (_swapchainManager)
|
||||||
{
|
{
|
||||||
_swapchainManager->resize_swapchain(_window);
|
_swapchainManager->resize_swapchain(_window);
|
||||||
|
if (_ui)
|
||||||
|
{
|
||||||
|
_ui->on_swapchain_recreated();
|
||||||
|
}
|
||||||
if (_swapchainManager->resize_requested)
|
if (_swapchainManager->resize_requested)
|
||||||
{
|
{
|
||||||
resize_requested = true;
|
resize_requested = true;
|
||||||
@@ -804,6 +833,17 @@ void VulkanEngine::cleanup()
|
|||||||
//make sure the gpu has stopped doing its things
|
//make sure the gpu has stopped doing its things
|
||||||
vkDeviceWaitIdle(_deviceManager->device());
|
vkDeviceWaitIdle(_deviceManager->device());
|
||||||
|
|
||||||
|
if (_ui)
|
||||||
|
{
|
||||||
|
_ui->cleanup();
|
||||||
|
_ui.reset();
|
||||||
|
}
|
||||||
|
if (_picking)
|
||||||
|
{
|
||||||
|
_picking->cleanup();
|
||||||
|
_picking.reset();
|
||||||
|
}
|
||||||
|
|
||||||
// Flush all frame deletion queues first while VMA allocator is still alive
|
// Flush all frame deletion queues first while VMA allocator is still alive
|
||||||
for (int i = 0; i < FRAME_OVERLAP; i++)
|
for (int i = 0; i < FRAME_OVERLAP; i++)
|
||||||
{
|
{
|
||||||
@@ -861,13 +901,6 @@ void VulkanEngine::cleanup()
|
|||||||
print_vma_stats(_deviceManager.get(), "after RTManager");
|
print_vma_stats(_deviceManager.get(), "after RTManager");
|
||||||
dump_vma_json(_deviceManager.get(), "after_RTManager");
|
dump_vma_json(_deviceManager.get(), "after_RTManager");
|
||||||
|
|
||||||
// Destroy pick readback buffer before resource manager cleanup
|
|
||||||
if (_pickReadbackBuffer.buffer)
|
|
||||||
{
|
|
||||||
_resourceManager->destroy_buffer(_pickReadbackBuffer);
|
|
||||||
_pickReadbackBuffer = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
_resourceManager->cleanup();
|
_resourceManager->cleanup();
|
||||||
print_vma_stats(_deviceManager.get(), "after ResourceManager");
|
print_vma_stats(_deviceManager.get(), "after ResourceManager");
|
||||||
dump_vma_json(_deviceManager.get(), "after_ResourceManager");
|
dump_vma_json(_deviceManager.get(), "after_ResourceManager");
|
||||||
@@ -962,31 +995,9 @@ void VulkanEngine::draw()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Per-frame hover raycast based on last mouse position.
|
if (_picking)
|
||||||
if (_sceneManager && _mousePosPixels.x >= 0.0f && _mousePosPixels.y >= 0.0f)
|
|
||||||
{
|
{
|
||||||
RenderObject hoverObj{};
|
_picking->update_hover();
|
||||||
WorldVec3 hoverPos{};
|
|
||||||
if (_sceneManager->pick(_mousePosPixels, hoverObj, hoverPos))
|
|
||||||
{
|
|
||||||
_hoverPick.mesh = hoverObj.sourceMesh;
|
|
||||||
_hoverPick.scene = hoverObj.sourceScene;
|
|
||||||
_hoverPick.node = hoverObj.sourceNode;
|
|
||||||
_hoverPick.ownerType = hoverObj.ownerType;
|
|
||||||
_hoverPick.ownerName = hoverObj.ownerName;
|
|
||||||
_hoverPick.worldPos = hoverPos;
|
|
||||||
_hoverPick.worldTransform = hoverObj.transform;
|
|
||||||
_hoverPick.firstIndex = hoverObj.firstIndex;
|
|
||||||
_hoverPick.indexCount = hoverObj.indexCount;
|
|
||||||
_hoverPick.surfaceIndex = hoverObj.surfaceIndex;
|
|
||||||
_hoverPick.valid = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_hoverPick.valid = false;
|
|
||||||
_hoverPick.ownerName.clear();
|
|
||||||
_hoverPick.ownerType = RenderObject::OwnerType::None;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute desired internal render-target extent from logical extent + render scale.
|
// Compute desired internal render-target extent from logical extent + render scale.
|
||||||
@@ -1124,69 +1135,12 @@ void VulkanEngine::draw()
|
|||||||
RGImageHandle hID = _renderGraph->import_id_buffer();
|
RGImageHandle hID = _renderGraph->import_id_buffer();
|
||||||
geometry->register_graph(_renderGraph.get(), hGBufferPosition, hGBufferNormal, hGBufferAlbedo, hGBufferExtra, hID, hDepth);
|
geometry->register_graph(_renderGraph.get(), hGBufferPosition, hGBufferNormal, hGBufferAlbedo, hGBufferExtra, hID, hDepth);
|
||||||
|
|
||||||
// If ID-buffer picking is enabled and a pick was requested this frame,
|
if (_picking && _swapchainManager)
|
||||||
// add a small transfer pass to read back 1 pixel from the ID buffer.
|
|
||||||
if (_useIdBufferPicking && _pendingPick.active && hID.valid() && _pickReadbackBuffer.buffer)
|
|
||||||
{
|
{
|
||||||
VkExtent2D swapExt = _swapchainManager->swapchainExtent();
|
_picking->register_id_buffer_readback(*_renderGraph,
|
||||||
VkExtent2D drawExt = _drawExtent;
|
hID,
|
||||||
|
_drawExtent,
|
||||||
glm::vec2 logicalPos{};
|
_swapchainManager->swapchainExtent());
|
||||||
if (!vkutil::map_window_to_letterbox_src(_pendingPick.windowPos, drawExt, swapExt, logicalPos))
|
|
||||||
{
|
|
||||||
// Click landed outside the active letterboxed region.
|
|
||||||
_pendingPick.active = false;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
uint32_t idX = uint32_t(glm::clamp(logicalPos.x, 0.0f, float(drawExt.width - 1)));
|
|
||||||
uint32_t idY = uint32_t(glm::clamp(logicalPos.y, 0.0f, float(drawExt.height - 1)));
|
|
||||||
_pendingPick.idCoords = {idX, idY};
|
|
||||||
|
|
||||||
RGImportedBufferDesc bd{};
|
|
||||||
bd.name = "pick.readback";
|
|
||||||
bd.buffer = _pickReadbackBuffer.buffer;
|
|
||||||
bd.size = sizeof(uint32_t);
|
|
||||||
bd.currentStage = VK_PIPELINE_STAGE_2_NONE;
|
|
||||||
bd.currentAccess = 0;
|
|
||||||
RGBufferHandle hPickBuf = _renderGraph->import_buffer(bd);
|
|
||||||
|
|
||||||
_renderGraph->add_pass(
|
|
||||||
"PickReadback",
|
|
||||||
RGPassType::Transfer,
|
|
||||||
[hID, hPickBuf](RGPassBuilder &builder, EngineContext *)
|
|
||||||
{
|
|
||||||
builder.read(hID, RGImageUsage::TransferSrc);
|
|
||||||
builder.write_buffer(hPickBuf, RGBufferUsage::TransferDst);
|
|
||||||
},
|
|
||||||
[this, hID, hPickBuf](VkCommandBuffer cmd, const RGPassResources &res, EngineContext *)
|
|
||||||
{
|
|
||||||
VkImage idImage = res.image(hID);
|
|
||||||
VkBuffer dst = res.buffer(hPickBuf);
|
|
||||||
if (idImage == VK_NULL_HANDLE || dst == VK_NULL_HANDLE) return;
|
|
||||||
|
|
||||||
VkBufferImageCopy region{};
|
|
||||||
region.bufferOffset = 0;
|
|
||||||
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
||||||
region.imageSubresource.mipLevel = 0;
|
|
||||||
region.imageSubresource.baseArrayLayer = 0;
|
|
||||||
region.imageSubresource.layerCount = 1;
|
|
||||||
region.imageOffset = { static_cast<int32_t>(_pendingPick.idCoords.x),
|
|
||||||
static_cast<int32_t>(_pendingPick.idCoords.y),
|
|
||||||
0 };
|
|
||||||
region.imageExtent = {1, 1, 1};
|
|
||||||
|
|
||||||
vkCmdCopyImageToBuffer(cmd,
|
|
||||||
idImage,
|
|
||||||
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
|
||||||
dst,
|
|
||||||
1,
|
|
||||||
®ion);
|
|
||||||
});
|
|
||||||
|
|
||||||
_pickResultPending = true;
|
|
||||||
_pendingPick.active = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (auto *lighting = _renderPassManager->getPass<LightingPass>())
|
if (auto *lighting = _renderPassManager->getPass<LightingPass>())
|
||||||
@@ -1323,7 +1277,11 @@ void VulkanEngine::run()
|
|||||||
while (SDL_PollEvent(&e) != 0)
|
while (SDL_PollEvent(&e) != 0)
|
||||||
{
|
{
|
||||||
//close the window when user alt-f4s or clicks the X button
|
//close the window when user alt-f4s or clicks the X button
|
||||||
if (e.type == SDL_QUIT) bQuit = true;
|
if (e.type == SDL_QUIT)
|
||||||
|
{
|
||||||
|
bQuit = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (e.type == SDL_WINDOWEVENT)
|
if (e.type == SDL_WINDOWEVENT)
|
||||||
{
|
{
|
||||||
switch (e.window.event)
|
switch (e.window.event)
|
||||||
@@ -1341,119 +1299,40 @@ void VulkanEngine::run()
|
|||||||
resize_requested = true;
|
resize_requested = true;
|
||||||
_last_resize_event_ms = SDL_GetTicks();
|
_last_resize_event_ms = SDL_GetTicks();
|
||||||
break;
|
break;
|
||||||
|
case SDL_WINDOWEVENT_MOVED:
|
||||||
|
// Moving between monitors can change DPI scale; ensure swapchain is refreshed.
|
||||||
|
resize_requested = true;
|
||||||
|
_last_resize_event_ms = SDL_GetTicks();
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (e.type == SDL_MOUSEMOTION)
|
|
||||||
|
const bool ui_capture_mouse = _ui && _ui->want_capture_mouse();
|
||||||
|
const bool ui_capture_keyboard = _ui && _ui->want_capture_keyboard();
|
||||||
|
|
||||||
|
if (_ui)
|
||||||
{
|
{
|
||||||
_mousePosPixels = glm::vec2{static_cast<float>(e.motion.x),
|
_ui->process_event(e);
|
||||||
static_cast<float>(e.motion.y)};
|
}
|
||||||
if (_dragState.buttonDown)
|
if (_picking)
|
||||||
|
{
|
||||||
|
_picking->process_event(e, ui_capture_mouse);
|
||||||
|
}
|
||||||
|
if (_sceneManager)
|
||||||
|
{
|
||||||
|
const bool key_event = (e.type == SDL_KEYDOWN) || (e.type == SDL_KEYUP);
|
||||||
|
const bool mouse_event = (e.type == SDL_MOUSEBUTTONDOWN) ||
|
||||||
|
(e.type == SDL_MOUSEBUTTONUP) ||
|
||||||
|
(e.type == SDL_MOUSEMOTION) ||
|
||||||
|
(e.type == SDL_MOUSEWHEEL);
|
||||||
|
|
||||||
|
if (!(ui_capture_keyboard && key_event) && !(ui_capture_mouse && mouse_event))
|
||||||
{
|
{
|
||||||
_dragState.current = _mousePosPixels;
|
_sceneManager->getMainCamera().processSDLEvent(e);
|
||||||
// Consider any motion as dragging for now; can add threshold if desired.
|
|
||||||
_dragState.dragging = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (e.type == SDL_MOUSEBUTTONDOWN && e.button.button == SDL_BUTTON_LEFT)
|
|
||||||
{
|
|
||||||
if (!ImGui::GetIO().WantCaptureMouse)
|
|
||||||
{
|
|
||||||
_dragState.buttonDown = true;
|
|
||||||
_dragState.dragging = false;
|
|
||||||
_dragState.start = glm::vec2{static_cast<float>(e.button.x),
|
|
||||||
static_cast<float>(e.button.y)};
|
|
||||||
_dragState.current = _dragState.start;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (e.type == SDL_MOUSEBUTTONUP && e.button.button == SDL_BUTTON_LEFT)
|
|
||||||
{
|
|
||||||
glm::vec2 releasePos{static_cast<float>(e.button.x),
|
|
||||||
static_cast<float>(e.button.y)};
|
|
||||||
_dragState.buttonDown = false;
|
|
||||||
|
|
||||||
constexpr float clickThreshold = 3.0f;
|
|
||||||
glm::vec2 delta = releasePos - _dragState.start;
|
|
||||||
bool treatAsClick = !_dragState.dragging &&
|
|
||||||
std::abs(delta.x) < clickThreshold &&
|
|
||||||
std::abs(delta.y) < clickThreshold;
|
|
||||||
|
|
||||||
if (treatAsClick)
|
|
||||||
{
|
|
||||||
if (_useIdBufferPicking)
|
|
||||||
{
|
|
||||||
// Asynchronous ID-buffer clicking: queue a pick request for this position.
|
|
||||||
// The result will be resolved at the start of a future frame from the ID buffer.
|
|
||||||
_pendingPick.active = true;
|
|
||||||
_pendingPick.windowPos = releasePos;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Raycast click selection (CPU side) when ID-buffer picking is disabled.
|
|
||||||
if (_sceneManager)
|
|
||||||
{
|
|
||||||
RenderObject hitObject{};
|
|
||||||
WorldVec3 hitPos{};
|
|
||||||
if (_sceneManager->pick(releasePos, hitObject, hitPos))
|
|
||||||
{
|
|
||||||
_lastPick.mesh = hitObject.sourceMesh;
|
|
||||||
_lastPick.scene = hitObject.sourceScene;
|
|
||||||
_lastPick.node = hitObject.sourceNode;
|
|
||||||
_lastPick.ownerType = hitObject.ownerType;
|
|
||||||
_lastPick.ownerName = hitObject.ownerName;
|
|
||||||
_lastPick.worldPos = hitPos;
|
|
||||||
_lastPick.worldTransform = hitObject.transform;
|
|
||||||
_lastPick.firstIndex = hitObject.firstIndex;
|
|
||||||
_lastPick.indexCount = hitObject.indexCount;
|
|
||||||
_lastPick.surfaceIndex = hitObject.surfaceIndex;
|
|
||||||
_lastPick.valid = true;
|
|
||||||
_lastPickObjectID = hitObject.objectID;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_lastPick.valid = false;
|
|
||||||
_lastPick.ownerName.clear();
|
|
||||||
_lastPick.ownerType = RenderObject::OwnerType::None;
|
|
||||||
_lastPickObjectID = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Drag selection completed; compute selection based on screen-space rectangle.
|
|
||||||
_dragSelection.clear();
|
|
||||||
if (_sceneManager)
|
|
||||||
{
|
|
||||||
std::vector<RenderObject> selected;
|
|
||||||
_sceneManager->selectRect(_dragState.start, releasePos, selected);
|
|
||||||
_dragSelection.reserve(selected.size());
|
|
||||||
for (const RenderObject &obj : selected)
|
|
||||||
{
|
|
||||||
PickInfo info{};
|
|
||||||
info.mesh = obj.sourceMesh;
|
|
||||||
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.
|
|
||||||
glm::vec3 centerLocal = glm::vec3(obj.transform * glm::vec4(obj.bounds.origin, 1.0f));
|
|
||||||
info.worldPos = local_to_world(centerLocal, _sceneManager->get_world_origin());
|
|
||||||
info.worldTransform = obj.transform;
|
|
||||||
info.firstIndex = obj.firstIndex;
|
|
||||||
info.indexCount = obj.indexCount;
|
|
||||||
info.surfaceIndex = obj.surfaceIndex;
|
|
||||||
info.valid = true;
|
|
||||||
_dragSelection.push_back(info);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_dragState.dragging = false;
|
|
||||||
}
|
|
||||||
_sceneManager->getMainCamera().processSDLEvent(e);
|
|
||||||
ImGui_ImplSDL2_ProcessEvent(&e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (freeze_rendering)
|
if (freeze_rendering)
|
||||||
@@ -1468,6 +1347,10 @@ void VulkanEngine::run()
|
|||||||
if (now_ms - _last_resize_event_ms >= RESIZE_DEBOUNCE_MS)
|
if (now_ms - _last_resize_event_ms >= RESIZE_DEBOUNCE_MS)
|
||||||
{
|
{
|
||||||
_swapchainManager->resize_swapchain(_window);
|
_swapchainManager->resize_swapchain(_window);
|
||||||
|
if (_ui)
|
||||||
|
{
|
||||||
|
_ui->on_swapchain_recreated();
|
||||||
|
}
|
||||||
resize_requested = false;
|
resize_requested = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1510,53 +1393,9 @@ void VulkanEngine::run()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_pickResultPending && _pickReadbackBuffer.buffer && _sceneManager)
|
if (_picking)
|
||||||
{
|
{
|
||||||
vmaInvalidateAllocation(_deviceManager->allocator(), _pickReadbackBuffer.allocation, 0, sizeof(uint32_t));
|
_picking->begin_frame();
|
||||||
uint32_t pickedID = 0;
|
|
||||||
if (_pickReadbackBuffer.info.pMappedData)
|
|
||||||
{
|
|
||||||
pickedID = *reinterpret_cast<const uint32_t *>(_pickReadbackBuffer.info.pMappedData);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pickedID == 0)
|
|
||||||
{
|
|
||||||
// No object under cursor in ID buffer: clear last pick.
|
|
||||||
_lastPick.valid = false;
|
|
||||||
_lastPick.ownerName.clear();
|
|
||||||
_lastPick.ownerType = RenderObject::OwnerType::None;
|
|
||||||
_lastPickObjectID = 0;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_lastPickObjectID = pickedID;
|
|
||||||
RenderObject picked{};
|
|
||||||
if (_sceneManager->resolveObjectID(pickedID, picked))
|
|
||||||
{
|
|
||||||
// Fallback hit position: object origin in world space (can refine later)
|
|
||||||
glm::vec3 fallbackLocal = glm::vec3(picked.transform[3]);
|
|
||||||
WorldVec3 fallbackPos = local_to_world(fallbackLocal, _sceneManager->get_world_origin());
|
|
||||||
_lastPick.mesh = picked.sourceMesh;
|
|
||||||
_lastPick.scene = picked.sourceScene;
|
|
||||||
_lastPick.node = picked.sourceNode;
|
|
||||||
_lastPick.ownerType = picked.ownerType;
|
|
||||||
_lastPick.ownerName = picked.ownerName;
|
|
||||||
_lastPick.worldPos = fallbackPos;
|
|
||||||
_lastPick.worldTransform = picked.transform;
|
|
||||||
_lastPick.firstIndex = picked.firstIndex;
|
|
||||||
_lastPick.indexCount = picked.indexCount;
|
|
||||||
_lastPick.surfaceIndex = picked.surfaceIndex;
|
|
||||||
_lastPick.valid = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_lastPick.valid = false;
|
|
||||||
_lastPick.ownerName.clear();
|
|
||||||
_lastPick.ownerType = RenderObject::OwnerType::None;
|
|
||||||
_lastPickObjectID = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_pickResultPending = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get_current_frame()._deletionQueue.flush();
|
get_current_frame()._deletionQueue.flush();
|
||||||
@@ -1566,17 +1405,11 @@ void VulkanEngine::run()
|
|||||||
}
|
}
|
||||||
get_current_frame()._frameDescriptors.clear_pools(_deviceManager->device());
|
get_current_frame()._frameDescriptors.clear_pools(_deviceManager->device());
|
||||||
|
|
||||||
|
if (_ui)
|
||||||
// imgui new frame
|
{
|
||||||
ImGui_ImplVulkan_NewFrame();
|
_ui->begin_frame();
|
||||||
ImGui_ImplSDL2_NewFrame();
|
_ui->end_frame();
|
||||||
|
}
|
||||||
ImGui::NewFrame();
|
|
||||||
|
|
||||||
// Build the engine debug UI (tabs, inspectors, etc.).
|
|
||||||
vk_engine_draw_debug_ui(this);
|
|
||||||
|
|
||||||
ImGui::Render();
|
|
||||||
draw();
|
draw();
|
||||||
|
|
||||||
auto end = std::chrono::system_clock::now();
|
auto end = std::chrono::system_clock::now();
|
||||||
@@ -1602,12 +1435,6 @@ void VulkanEngine::init_frame_resources()
|
|||||||
{
|
{
|
||||||
_frames[i].init(_deviceManager.get(), frame_sizes);
|
_frames[i].init(_deviceManager.get(), frame_sizes);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allocate a small readback buffer for ID-buffer picking (single uint32 pixel).
|
|
||||||
_pickReadbackBuffer = _resourceManager->create_buffer(
|
|
||||||
sizeof(uint32_t),
|
|
||||||
VK_BUFFER_USAGE_TRANSFER_DST_BIT,
|
|
||||||
VMA_MEMORY_USAGE_CPU_TO_GPU);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanEngine::init_pipelines()
|
void VulkanEngine::init_pipelines()
|
||||||
|
|||||||
@@ -37,6 +37,8 @@
|
|||||||
#include "core/raytracing/raytracing.h"
|
#include "core/raytracing/raytracing.h"
|
||||||
#include "core/assets/texture_cache.h"
|
#include "core/assets/texture_cache.h"
|
||||||
#include "core/assets/ibl_manager.h"
|
#include "core/assets/ibl_manager.h"
|
||||||
|
#include "core/ui/imgui_system.h"
|
||||||
|
#include "core/picking/picking_system.h"
|
||||||
|
|
||||||
// Number of frames-in-flight. Affects per-frame command buffers, fences,
|
// Number of frames-in-flight. Affects per-frame command buffers, fences,
|
||||||
// semaphores, and transient descriptor pools in FrameResources.
|
// semaphores, and transient descriptor pools in FrameResources.
|
||||||
@@ -70,17 +72,21 @@ public:
|
|||||||
FullscreenExclusive = 2 // exclusive fullscreen (may change display mode)
|
FullscreenExclusive = 2 // exclusive fullscreen (may change display mode)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
~VulkanEngine();
|
||||||
|
|
||||||
bool _isInitialized{false};
|
bool _isInitialized{false};
|
||||||
int _frameNumber{0};
|
int _frameNumber{0};
|
||||||
|
|
||||||
std::shared_ptr<DeviceManager> _deviceManager;
|
std::shared_ptr<DeviceManager> _deviceManager;
|
||||||
std::unique_ptr<SwapchainManager> _swapchainManager;
|
std::unique_ptr<SwapchainManager> _swapchainManager;
|
||||||
std::shared_ptr<ResourceManager> _resourceManager;
|
std::shared_ptr<ResourceManager> _resourceManager;
|
||||||
std::unique_ptr<RenderPassManager> _renderPassManager;
|
std::unique_ptr<RenderPassManager> _renderPassManager;
|
||||||
std::unique_ptr<SceneManager> _sceneManager;
|
std::unique_ptr<ImGuiSystem> _ui;
|
||||||
std::unique_ptr<PipelineManager> _pipelineManager;
|
std::unique_ptr<SceneManager> _sceneManager;
|
||||||
std::unique_ptr<AssetManager> _assetManager;
|
std::unique_ptr<PickingSystem> _picking;
|
||||||
std::unique_ptr<AsyncAssetLoader> _asyncLoader;
|
std::unique_ptr<PipelineManager> _pipelineManager;
|
||||||
|
std::unique_ptr<AssetManager> _assetManager;
|
||||||
|
std::unique_ptr<AsyncAssetLoader> _asyncLoader;
|
||||||
std::unique_ptr<RenderGraph> _renderGraph;
|
std::unique_ptr<RenderGraph> _renderGraph;
|
||||||
std::unique_ptr<RayTracingManager> _rayManager;
|
std::unique_ptr<RayTracingManager> _rayManager;
|
||||||
std::unique_ptr<TextureCache> _textureCache;
|
std::unique_ptr<TextureCache> _textureCache;
|
||||||
@@ -91,6 +97,10 @@ public:
|
|||||||
WindowMode _windowMode{WindowMode::Windowed};
|
WindowMode _windowMode{WindowMode::Windowed};
|
||||||
int _windowDisplayIndex{0};
|
int _windowDisplayIndex{0};
|
||||||
|
|
||||||
|
// HiDPI: allow high-DPI drawables (Retina / Windows scaling).
|
||||||
|
// Window coordinates from SDL events may be in "points" while the drawable/swapchain is in pixels.
|
||||||
|
bool _hiDpiEnabled{true};
|
||||||
|
|
||||||
FrameResources _frames[FRAME_OVERLAP];
|
FrameResources _frames[FRAME_OVERLAP];
|
||||||
|
|
||||||
FrameResources &get_current_frame() { return _frames[_frameNumber % FRAME_OVERLAP]; };
|
FrameResources &get_current_frame() { return _frames[_frameNumber % FRAME_OVERLAP]; };
|
||||||
@@ -154,51 +164,11 @@ public:
|
|||||||
IBLPaths paths{};
|
IBLPaths paths{};
|
||||||
} _pendingIBLRequest;
|
} _pendingIBLRequest;
|
||||||
|
|
||||||
struct PickInfo
|
ImGuiSystem *ui() { return _ui.get(); }
|
||||||
{
|
const ImGuiSystem *ui() const { return _ui.get(); }
|
||||||
MeshAsset *mesh = nullptr;
|
|
||||||
LoadedGLTF *scene = nullptr;
|
|
||||||
Node *node = nullptr;
|
|
||||||
RenderObject::OwnerType ownerType = RenderObject::OwnerType::None;
|
|
||||||
std::string ownerName;
|
|
||||||
WorldVec3 worldPos{0.0, 0.0, 0.0};
|
|
||||||
glm::mat4 worldTransform{1.0f};
|
|
||||||
uint32_t indexCount = 0;
|
|
||||||
uint32_t firstIndex = 0;
|
|
||||||
uint32_t surfaceIndex = 0;
|
|
||||||
bool valid = false;
|
|
||||||
} _lastPick;
|
|
||||||
uint32_t _lastPickObjectID = 0;
|
|
||||||
|
|
||||||
struct PickRequest
|
PickingSystem *picking() { return _picking.get(); }
|
||||||
{
|
const PickingSystem *picking() const { return _picking.get(); }
|
||||||
bool active = false;
|
|
||||||
glm::vec2 windowPos{0.0f};
|
|
||||||
glm::uvec2 idCoords{0, 0};
|
|
||||||
} _pendingPick;
|
|
||||||
bool _pickResultPending = false;
|
|
||||||
AllocatedBuffer _pickReadbackBuffer{};
|
|
||||||
|
|
||||||
// Hover and drag-selection state (raycast-based)
|
|
||||||
PickInfo _hoverPick{};
|
|
||||||
glm::vec2 _mousePosPixels{-1.0f, -1.0f};
|
|
||||||
struct DragState
|
|
||||||
{
|
|
||||||
bool dragging = false;
|
|
||||||
bool buttonDown = false;
|
|
||||||
glm::vec2 start{0.0f};
|
|
||||||
glm::vec2 current{0.0f};
|
|
||||||
} _dragState;
|
|
||||||
// Optional list of last drag-selected objects (for future editing UI)
|
|
||||||
std::vector<PickInfo> _dragSelection;
|
|
||||||
|
|
||||||
// Toggle to enable/disable ID-buffer picking in addition to raycast
|
|
||||||
bool _useIdBufferPicking = false;
|
|
||||||
// Debug: draw mesh BVH boxes for last pick
|
|
||||||
bool _debugDrawBVH = false;
|
|
||||||
|
|
||||||
// Last click selection (CPU ray or ID-buffer). Useful for game/editor code.
|
|
||||||
const PickInfo &get_last_pick() const { return _lastPick; }
|
|
||||||
|
|
||||||
// Debug: persistent pass enable overrides (by pass name)
|
// Debug: persistent pass enable overrides (by pass name)
|
||||||
std::unordered_map<std::string, bool> _rgPassToggles;
|
std::unordered_map<std::string, bool> _rgPassToggles;
|
||||||
|
|||||||
@@ -5,8 +5,10 @@
|
|||||||
// The main frame loop in vk_engine.cpp simply calls vk_engine_draw_debug_ui().
|
// The main frame loop in vk_engine.cpp simply calls vk_engine_draw_debug_ui().
|
||||||
|
|
||||||
#include "engine.h"
|
#include "engine.h"
|
||||||
|
#include "core/picking/picking_system.h"
|
||||||
|
|
||||||
#include "SDL2/SDL.h"
|
#include "SDL2/SDL.h"
|
||||||
|
#include "SDL2/SDL_vulkan.h"
|
||||||
|
|
||||||
#include "imgui.h"
|
#include "imgui.h"
|
||||||
#include "ImGuizmo.h"
|
#include "ImGuizmo.h"
|
||||||
@@ -110,6 +112,75 @@ namespace
|
|||||||
pending_display = current_display;
|
pending_display = current_display;
|
||||||
pending_mode = static_cast<int>(eng->_windowMode);
|
pending_mode = static_cast<int>(eng->_windowMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ImGui::Separator();
|
||||||
|
ImGui::TextUnformatted("HiDPI / Sizes");
|
||||||
|
ImGui::Text("HiDPI enabled: %s", eng->_hiDpiEnabled ? "yes" : "no");
|
||||||
|
|
||||||
|
int winW = 0, winH = 0;
|
||||||
|
SDL_GetWindowSize(eng->_window, &winW, &winH);
|
||||||
|
int drawW = 0, drawH = 0;
|
||||||
|
SDL_Vulkan_GetDrawableSize(eng->_window, &drawW, &drawH);
|
||||||
|
ImGui::Text("Window size: %d x %d", winW, winH);
|
||||||
|
ImGui::Text("Drawable size: %d x %d", drawW, drawH);
|
||||||
|
if (winW > 0 && winH > 0 && drawW > 0 && drawH > 0)
|
||||||
|
{
|
||||||
|
ImGui::Text("Drawable scale: %.3f x %.3f",
|
||||||
|
static_cast<float>(drawW) / static_cast<float>(winW),
|
||||||
|
static_cast<float>(drawH) / static_cast<float>(winH));
|
||||||
|
}
|
||||||
|
if (eng->_swapchainManager)
|
||||||
|
{
|
||||||
|
VkExtent2D sw = eng->_swapchainManager->swapchainExtent();
|
||||||
|
ImGui::Text("Swapchain extent: %u x %u", sw.width, sw.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::Separator();
|
||||||
|
ImGui::TextUnformatted("GPU");
|
||||||
|
if (!eng->_deviceManager || !eng->_deviceManager->physicalDevice())
|
||||||
|
{
|
||||||
|
ImGui::TextUnformatted("No Vulkan device initialized.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
VkPhysicalDevice gpu = eng->_deviceManager->physicalDevice();
|
||||||
|
VkPhysicalDeviceProperties props{};
|
||||||
|
vkGetPhysicalDeviceProperties(gpu, &props);
|
||||||
|
VkPhysicalDeviceMemoryProperties mem{};
|
||||||
|
vkGetPhysicalDeviceMemoryProperties(gpu, &mem);
|
||||||
|
|
||||||
|
auto type_str = [](VkPhysicalDeviceType t) -> const char*
|
||||||
|
{
|
||||||
|
switch (t)
|
||||||
|
{
|
||||||
|
case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU: return "Discrete";
|
||||||
|
case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU: return "Integrated";
|
||||||
|
case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU: return "Virtual";
|
||||||
|
case VK_PHYSICAL_DEVICE_TYPE_CPU: return "CPU";
|
||||||
|
default: return "Other";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
uint64_t device_local_bytes = 0;
|
||||||
|
for (uint32_t i = 0; i < mem.memoryHeapCount; ++i)
|
||||||
|
{
|
||||||
|
if (mem.memoryHeaps[i].flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT)
|
||||||
|
{
|
||||||
|
device_local_bytes += mem.memoryHeaps[i].size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const double vram_gib = static_cast<double>(device_local_bytes) / (1024.0 * 1024.0 * 1024.0);
|
||||||
|
|
||||||
|
const uint32_t api = props.apiVersion;
|
||||||
|
ImGui::Text("Name: %s", props.deviceName);
|
||||||
|
ImGui::Text("Type: %s", type_str(props.deviceType));
|
||||||
|
ImGui::Text("Vendor: 0x%04x Device: 0x%04x", props.vendorID, props.deviceID);
|
||||||
|
ImGui::Text("Vulkan API: %u.%u.%u", VK_VERSION_MAJOR(api), VK_VERSION_MINOR(api), VK_VERSION_PATCH(api));
|
||||||
|
ImGui::Text("Driver: %u (0x%08x)", props.driverVersion, props.driverVersion);
|
||||||
|
ImGui::Text("Device-local memory: %.2f GiB", vram_gib);
|
||||||
|
ImGui::Text("RayQuery: %s AccelStruct: %s",
|
||||||
|
eng->_deviceManager->supportsRayQuery() ? "yes" : "no",
|
||||||
|
eng->_deviceManager->supportsAccelerationStructure() ? "yes" : "no");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Background / compute playground
|
// Background / compute playground
|
||||||
@@ -962,10 +1033,27 @@ namespace
|
|||||||
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());
|
||||||
ImGui::Text("Transp draws: %zu", dc.TransparentSurfaces.size());
|
ImGui::Text("Transp draws: %zu", dc.TransparentSurfaces.size());
|
||||||
ImGui::Checkbox("Use ID-buffer picking", &eng->_useIdBufferPicking);
|
PickingSystem *picking = eng->picking();
|
||||||
ImGui::Text("Picking mode: %s",
|
if (picking)
|
||||||
eng->_useIdBufferPicking ? "ID buffer (async, 1-frame latency)" : "CPU raycast");
|
{
|
||||||
ImGui::Checkbox("Debug draw mesh BVH (last pick)", &eng->_debugDrawBVH);
|
bool use_id = picking->use_id_buffer_picking();
|
||||||
|
if (ImGui::Checkbox("Use ID-buffer picking", &use_id))
|
||||||
|
{
|
||||||
|
picking->set_use_id_buffer_picking(use_id);
|
||||||
|
}
|
||||||
|
ImGui::Text("Picking mode: %s",
|
||||||
|
use_id ? "ID buffer (async, 1-frame latency)" : "CPU raycast");
|
||||||
|
|
||||||
|
bool debug_bvh = picking->debug_draw_bvh();
|
||||||
|
if (ImGui::Checkbox("Debug draw mesh BVH (last pick)", &debug_bvh))
|
||||||
|
{
|
||||||
|
picking->set_debug_draw_bvh(debug_bvh);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ImGui::TextUnformatted("Picking system not available");
|
||||||
|
}
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
|
|
||||||
// Spawn glTF instances (runtime)
|
// Spawn glTF instances (runtime)
|
||||||
@@ -1119,8 +1207,13 @@ namespace
|
|||||||
if (ImGui::Button("Delete selected"))
|
if (ImGui::Button("Delete selected"))
|
||||||
{
|
{
|
||||||
deleteStatus.clear();
|
deleteStatus.clear();
|
||||||
const auto *pick = eng->_lastPick.valid ? &eng->_lastPick
|
const PickingSystem::PickInfo *pick = nullptr;
|
||||||
: (eng->_hoverPick.valid ? &eng->_hoverPick : nullptr);
|
if (picking)
|
||||||
|
{
|
||||||
|
const auto &last = picking->last_pick();
|
||||||
|
const auto &hover = picking->hover_pick();
|
||||||
|
pick = last.valid ? &last : (hover.valid ? &hover : nullptr);
|
||||||
|
}
|
||||||
if (!pick || pick->ownerName.empty())
|
if (!pick || pick->ownerName.empty())
|
||||||
{
|
{
|
||||||
deleteStatus = "No selection to delete.";
|
deleteStatus = "No selection to delete.";
|
||||||
@@ -1128,6 +1221,10 @@ namespace
|
|||||||
else if (pick->ownerType == RenderObject::OwnerType::MeshInstance)
|
else if (pick->ownerType == RenderObject::OwnerType::MeshInstance)
|
||||||
{
|
{
|
||||||
bool ok = eng->_sceneManager->removeMeshInstance(pick->ownerName);
|
bool ok = eng->_sceneManager->removeMeshInstance(pick->ownerName);
|
||||||
|
if (ok && picking)
|
||||||
|
{
|
||||||
|
picking->clear_owner_picks(RenderObject::OwnerType::MeshInstance, pick->ownerName);
|
||||||
|
}
|
||||||
deleteStatus = ok ? "Removed mesh instance: " + pick->ownerName
|
deleteStatus = ok ? "Removed mesh instance: " + pick->ownerName
|
||||||
: "Mesh instance not found: " + pick->ownerName;
|
: "Mesh instance not found: " + pick->ownerName;
|
||||||
}
|
}
|
||||||
@@ -1137,35 +1234,9 @@ namespace
|
|||||||
if (ok)
|
if (ok)
|
||||||
{
|
{
|
||||||
deleteStatus = "Removed glTF instance: " + pick->ownerName;
|
deleteStatus = "Removed glTF instance: " + pick->ownerName;
|
||||||
|
if (picking)
|
||||||
// Debug: log and clear any picks that still reference the deleted instance.
|
|
||||||
fmt::println("[Debug] GLTF delete requested for '{}'; clearing picks if they match.",
|
|
||||||
pick->ownerName);
|
|
||||||
|
|
||||||
if (eng->_lastPick.valid &&
|
|
||||||
eng->_lastPick.ownerType == RenderObject::OwnerType::GLTFInstance &&
|
|
||||||
eng->_lastPick.ownerName == pick->ownerName)
|
|
||||||
{
|
{
|
||||||
fmt::println("[Debug] Clearing _lastPick for deleted GLTF instance '{}'", pick->ownerName);
|
picking->clear_owner_picks(RenderObject::OwnerType::GLTFInstance, pick->ownerName);
|
||||||
eng->_lastPick.valid = false;
|
|
||||||
eng->_lastPick.ownerName.clear();
|
|
||||||
eng->_lastPick.ownerType = RenderObject::OwnerType::None;
|
|
||||||
eng->_lastPick.mesh = nullptr;
|
|
||||||
eng->_lastPick.scene = nullptr;
|
|
||||||
eng->_lastPick.node = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (eng->_hoverPick.valid &&
|
|
||||||
eng->_hoverPick.ownerType == RenderObject::OwnerType::GLTFInstance &&
|
|
||||||
eng->_hoverPick.ownerName == pick->ownerName)
|
|
||||||
{
|
|
||||||
fmt::println("[Debug] Clearing _hoverPick for deleted GLTF instance '{}'", pick->ownerName);
|
|
||||||
eng->_hoverPick.valid = false;
|
|
||||||
eng->_hoverPick.ownerName.clear();
|
|
||||||
eng->_hoverPick.ownerType = RenderObject::OwnerType::None;
|
|
||||||
eng->_hoverPick.mesh = nullptr;
|
|
||||||
eng->_hoverPick.scene = nullptr;
|
|
||||||
eng->_hoverPick.node = nullptr;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -1184,36 +1255,37 @@ namespace
|
|||||||
}
|
}
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
|
|
||||||
if (eng->_lastPick.valid)
|
if (picking && picking->last_pick().valid)
|
||||||
{
|
{
|
||||||
const char *meshName = eng->_lastPick.mesh ? eng->_lastPick.mesh->name.c_str() : "<unknown>";
|
const auto &last = picking->last_pick();
|
||||||
|
const char *meshName = last.mesh ? last.mesh->name.c_str() : "<unknown>";
|
||||||
const char *sceneName = "<none>";
|
const char *sceneName = "<none>";
|
||||||
if (eng->_lastPick.scene && !eng->_lastPick.scene->debugName.empty())
|
if (last.scene && !last.scene->debugName.empty())
|
||||||
{
|
{
|
||||||
sceneName = eng->_lastPick.scene->debugName.c_str();
|
sceneName = last.scene->debugName.c_str();
|
||||||
}
|
}
|
||||||
ImGui::Text("Last pick scene: %s", sceneName);
|
ImGui::Text("Last pick scene: %s", sceneName);
|
||||||
ImGui::Text("Last pick source: %s",
|
ImGui::Text("Last pick source: %s",
|
||||||
eng->_useIdBufferPicking ? "ID buffer" : "CPU raycast");
|
picking->use_id_buffer_picking() ? "ID buffer" : "CPU raycast");
|
||||||
ImGui::Text("Last pick object ID: %u", eng->_lastPickObjectID);
|
ImGui::Text("Last pick object ID: %u", picking->last_pick_object_id());
|
||||||
ImGui::Text("Last pick mesh: %s (surface %u)", meshName, eng->_lastPick.surfaceIndex);
|
ImGui::Text("Last pick mesh: %s (surface %u)", meshName, last.surfaceIndex);
|
||||||
ImGui::Text("World pos: (%.3f, %.3f, %.3f)",
|
ImGui::Text("World pos: (%.3f, %.3f, %.3f)",
|
||||||
eng->_lastPick.worldPos.x,
|
last.worldPos.x,
|
||||||
eng->_lastPick.worldPos.y,
|
last.worldPos.y,
|
||||||
eng->_lastPick.worldPos.z);
|
last.worldPos.z);
|
||||||
const char *ownerTypeStr = "none";
|
const char *ownerTypeStr = "none";
|
||||||
switch (eng->_lastPick.ownerType)
|
switch (last.ownerType)
|
||||||
{
|
{
|
||||||
case RenderObject::OwnerType::MeshInstance: ownerTypeStr = "mesh instance"; break;
|
case RenderObject::OwnerType::MeshInstance: ownerTypeStr = "mesh instance"; break;
|
||||||
case RenderObject::OwnerType::GLTFInstance: ownerTypeStr = "glTF instance"; break;
|
case RenderObject::OwnerType::GLTFInstance: ownerTypeStr = "glTF instance"; break;
|
||||||
case RenderObject::OwnerType::StaticGLTF: ownerTypeStr = "glTF scene"; break;
|
case RenderObject::OwnerType::StaticGLTF: ownerTypeStr = "glTF scene"; break;
|
||||||
default: break;
|
default: break;
|
||||||
}
|
}
|
||||||
const char *ownerName = eng->_lastPick.ownerName.empty() ? "<unnamed>" : eng->_lastPick.ownerName.c_str();
|
const char *ownerName = last.ownerName.empty() ? "<unnamed>" : last.ownerName.c_str();
|
||||||
ImGui::Text("Owner: %s (%s)", ownerName, ownerTypeStr);
|
ImGui::Text("Owner: %s (%s)", ownerName, ownerTypeStr);
|
||||||
ImGui::Text("Indices: first=%u count=%u",
|
ImGui::Text("Indices: first=%u count=%u",
|
||||||
eng->_lastPick.firstIndex,
|
last.firstIndex,
|
||||||
eng->_lastPick.indexCount);
|
last.indexCount);
|
||||||
|
|
||||||
if (eng->_sceneManager)
|
if (eng->_sceneManager)
|
||||||
{
|
{
|
||||||
@@ -1235,28 +1307,29 @@ namespace
|
|||||||
ImGui::TextUnformatted("Last pick: <none>");
|
ImGui::TextUnformatted("Last pick: <none>");
|
||||||
}
|
}
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
if (eng->_hoverPick.valid)
|
if (picking && picking->hover_pick().valid)
|
||||||
{
|
{
|
||||||
const char *meshName = eng->_hoverPick.mesh ? eng->_hoverPick.mesh->name.c_str() : "<unknown>";
|
const auto &hover = picking->hover_pick();
|
||||||
ImGui::Text("Hover mesh: %s (surface %u)", meshName, eng->_hoverPick.surfaceIndex);
|
const char *meshName = hover.mesh ? hover.mesh->name.c_str() : "<unknown>";
|
||||||
|
ImGui::Text("Hover mesh: %s (surface %u)", meshName, hover.surfaceIndex);
|
||||||
const char *ownerTypeStr = "none";
|
const char *ownerTypeStr = "none";
|
||||||
switch (eng->_hoverPick.ownerType)
|
switch (hover.ownerType)
|
||||||
{
|
{
|
||||||
case RenderObject::OwnerType::MeshInstance: ownerTypeStr = "mesh instance"; break;
|
case RenderObject::OwnerType::MeshInstance: ownerTypeStr = "mesh instance"; break;
|
||||||
case RenderObject::OwnerType::GLTFInstance: ownerTypeStr = "glTF instance"; break;
|
case RenderObject::OwnerType::GLTFInstance: ownerTypeStr = "glTF instance"; break;
|
||||||
case RenderObject::OwnerType::StaticGLTF: ownerTypeStr = "glTF scene"; break;
|
case RenderObject::OwnerType::StaticGLTF: ownerTypeStr = "glTF scene"; break;
|
||||||
default: break;
|
default: break;
|
||||||
}
|
}
|
||||||
const char *ownerName = eng->_hoverPick.ownerName.empty() ? "<unnamed>" : eng->_hoverPick.ownerName.c_str();
|
const char *ownerName = hover.ownerName.empty() ? "<unnamed>" : hover.ownerName.c_str();
|
||||||
ImGui::Text("Hover owner: %s (%s)", ownerName, ownerTypeStr);
|
ImGui::Text("Hover owner: %s (%s)", ownerName, ownerTypeStr);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ImGui::TextUnformatted("Hover: <none>");
|
ImGui::TextUnformatted("Hover: <none>");
|
||||||
}
|
}
|
||||||
if (!eng->_dragSelection.empty())
|
if (picking && !picking->drag_selection().empty())
|
||||||
{
|
{
|
||||||
ImGui::Text("Drag selection: %zu objects", eng->_dragSelection.size());
|
ImGui::Text("Drag selection: %zu objects", picking->drag_selection().size());
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
@@ -1271,14 +1344,17 @@ namespace
|
|||||||
SceneManager *sceneMgr = eng->_sceneManager.get();
|
SceneManager *sceneMgr = eng->_sceneManager.get();
|
||||||
|
|
||||||
// Choose a pick to edit: prefer last pick, then hover.
|
// Choose a pick to edit: prefer last pick, then hover.
|
||||||
VulkanEngine::PickInfo *pick = nullptr;
|
PickingSystem::PickInfo *pick = nullptr;
|
||||||
if (eng->_lastPick.valid)
|
if (picking)
|
||||||
{
|
{
|
||||||
pick = &eng->_lastPick;
|
if (picking->last_pick().valid)
|
||||||
}
|
{
|
||||||
else if (eng->_hoverPick.valid)
|
pick = picking->mutable_last_pick();
|
||||||
{
|
}
|
||||||
pick = &eng->_hoverPick;
|
else if (picking->hover_pick().valid)
|
||||||
|
{
|
||||||
|
pick = picking->mutable_hover_pick();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!pick || pick->ownerName.empty())
|
if (!pick || pick->ownerName.empty())
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include "render/passes/tonemap.h"
|
#include "render/passes/tonemap.h"
|
||||||
#include "render/passes/fxaa.h"
|
#include "render/passes/fxaa.h"
|
||||||
#include "render/renderpass.h"
|
#include "render/renderpass.h"
|
||||||
|
#include "core/picking/picking_system.h"
|
||||||
#include "scene/vk_scene.h"
|
#include "scene/vk_scene.h"
|
||||||
#include "scene/camera.h"
|
#include "scene/camera.h"
|
||||||
|
|
||||||
@@ -1160,29 +1161,44 @@ Stats Engine::get_stats() const
|
|||||||
Engine::PickResult Engine::get_last_pick() const
|
Engine::PickResult Engine::get_last_pick() const
|
||||||
{
|
{
|
||||||
PickResult r;
|
PickResult r;
|
||||||
r.valid = _engine->_lastPick.valid;
|
const PickingSystem *picking = _engine ? _engine->picking() : nullptr;
|
||||||
r.ownerName = _engine->_lastPick.ownerName;
|
if (picking)
|
||||||
r.worldPosition = glm::vec3(_engine->_lastPick.worldPos);
|
{
|
||||||
|
const auto &pick = picking->last_pick();
|
||||||
|
r.valid = pick.valid;
|
||||||
|
r.ownerName = pick.ownerName;
|
||||||
|
r.worldPosition = glm::vec3(pick.worldPos);
|
||||||
|
}
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
Engine::PickResultD Engine::get_last_pick_d() const
|
Engine::PickResultD Engine::get_last_pick_d() const
|
||||||
{
|
{
|
||||||
PickResultD r;
|
PickResultD r;
|
||||||
r.valid = _engine->_lastPick.valid;
|
const PickingSystem *picking = _engine ? _engine->picking() : nullptr;
|
||||||
r.ownerName = _engine->_lastPick.ownerName;
|
if (picking)
|
||||||
r.worldPosition = _engine->_lastPick.worldPos;
|
{
|
||||||
|
const auto &pick = picking->last_pick();
|
||||||
|
r.valid = pick.valid;
|
||||||
|
r.ownerName = pick.ownerName;
|
||||||
|
r.worldPosition = pick.worldPos;
|
||||||
|
}
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Engine::set_use_id_buffer_picking(bool use)
|
void Engine::set_use_id_buffer_picking(bool use)
|
||||||
{
|
{
|
||||||
_engine->_useIdBufferPicking = use;
|
if (!_engine) return;
|
||||||
|
PickingSystem *picking = _engine->picking();
|
||||||
|
if (!picking) return;
|
||||||
|
picking->set_use_id_buffer_picking(use);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Engine::get_use_id_buffer_picking() const
|
bool Engine::get_use_id_buffer_picking() const
|
||||||
{
|
{
|
||||||
return _engine->_useIdBufferPicking;
|
const PickingSystem *picking = _engine ? _engine->picking() : nullptr;
|
||||||
|
if (!picking) return false;
|
||||||
|
return picking->use_id_buffer_picking();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace GameAPI
|
} // namespace GameAPI
|
||||||
|
|||||||
405
src/core/picking/picking_system.cpp
Normal file
405
src/core/picking/picking_system.cpp
Normal file
@@ -0,0 +1,405 @@
|
|||||||
|
#include "picking_system.h"
|
||||||
|
|
||||||
|
#include "core/context.h"
|
||||||
|
#include "core/device/device.h"
|
||||||
|
#include "core/device/images.h"
|
||||||
|
#include "core/device/swapchain.h"
|
||||||
|
#include "render/graph/graph.h"
|
||||||
|
|
||||||
|
#include "SDL2/SDL.h"
|
||||||
|
#include "SDL2/SDL_vulkan.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
void PickingSystem::init(EngineContext *context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
_last_pick = {};
|
||||||
|
_hover_pick = {};
|
||||||
|
_drag_selection.clear();
|
||||||
|
_mouse_pos_window = glm::vec2{-1.0f, -1.0f};
|
||||||
|
_drag_state = {};
|
||||||
|
_pending_pick = {};
|
||||||
|
_pick_result_pending = false;
|
||||||
|
_last_pick_object_id = 0;
|
||||||
|
|
||||||
|
_pick_readback_buffer = {};
|
||||||
|
if (_context && _context->getResources())
|
||||||
|
{
|
||||||
|
_pick_readback_buffer = _context->getResources()->create_buffer(
|
||||||
|
sizeof(uint32_t),
|
||||||
|
VK_BUFFER_USAGE_TRANSFER_DST_BIT,
|
||||||
|
VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PickingSystem::cleanup()
|
||||||
|
{
|
||||||
|
if (_pick_readback_buffer.buffer && _context && _context->getResources())
|
||||||
|
{
|
||||||
|
_context->getResources()->destroy_buffer(_pick_readback_buffer);
|
||||||
|
}
|
||||||
|
_pick_readback_buffer = {};
|
||||||
|
|
||||||
|
_context = nullptr;
|
||||||
|
_last_pick = {};
|
||||||
|
_hover_pick = {};
|
||||||
|
_drag_selection.clear();
|
||||||
|
_pending_pick = {};
|
||||||
|
_pick_result_pending = false;
|
||||||
|
_last_pick_object_id = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PickingSystem::process_event(const SDL_Event &event, bool ui_want_capture_mouse)
|
||||||
|
{
|
||||||
|
if (_context == nullptr)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.type == SDL_MOUSEMOTION)
|
||||||
|
{
|
||||||
|
_mouse_pos_window = glm::vec2{static_cast<float>(event.motion.x),
|
||||||
|
static_cast<float>(event.motion.y)};
|
||||||
|
if (_drag_state.button_down)
|
||||||
|
{
|
||||||
|
_drag_state.current = _mouse_pos_window;
|
||||||
|
_drag_state.dragging = true;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.type == SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_LEFT)
|
||||||
|
{
|
||||||
|
if (ui_want_capture_mouse)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_drag_state.button_down = true;
|
||||||
|
_drag_state.dragging = false;
|
||||||
|
_drag_state.start = glm::vec2{static_cast<float>(event.button.x),
|
||||||
|
static_cast<float>(event.button.y)};
|
||||||
|
_drag_state.current = _drag_state.start;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.type == SDL_MOUSEBUTTONUP && event.button.button == SDL_BUTTON_LEFT)
|
||||||
|
{
|
||||||
|
const bool was_down = _drag_state.button_down;
|
||||||
|
_drag_state.button_down = false;
|
||||||
|
if (!was_down)
|
||||||
|
{
|
||||||
|
_drag_state.dragging = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
glm::vec2 release_pos{static_cast<float>(event.button.x),
|
||||||
|
static_cast<float>(event.button.y)};
|
||||||
|
|
||||||
|
constexpr float click_threshold = 3.0f;
|
||||||
|
glm::vec2 delta = release_pos - _drag_state.start;
|
||||||
|
bool treat_as_click = !_drag_state.dragging &&
|
||||||
|
std::abs(delta.x) < click_threshold &&
|
||||||
|
std::abs(delta.y) < click_threshold;
|
||||||
|
|
||||||
|
SceneManager *scene = _context->scene;
|
||||||
|
|
||||||
|
if (treat_as_click)
|
||||||
|
{
|
||||||
|
if (_use_id_buffer_picking)
|
||||||
|
{
|
||||||
|
_pending_pick.active = true;
|
||||||
|
_pending_pick.window_pos_swapchain = window_to_swapchain_pixels(release_pos);
|
||||||
|
}
|
||||||
|
else if (scene)
|
||||||
|
{
|
||||||
|
RenderObject hit_object{};
|
||||||
|
WorldVec3 hit_pos{};
|
||||||
|
if (scene->pick(window_to_swapchain_pixels(release_pos), hit_object, hit_pos))
|
||||||
|
{
|
||||||
|
set_pick_from_hit(hit_object, hit_pos, _last_pick);
|
||||||
|
_last_pick_object_id = hit_object.objectID;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
clear_pick(_last_pick);
|
||||||
|
_last_pick_object_id = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_drag_selection.clear();
|
||||||
|
if (scene)
|
||||||
|
{
|
||||||
|
std::vector<RenderObject> selected;
|
||||||
|
scene->selectRect(window_to_swapchain_pixels(_drag_state.start),
|
||||||
|
window_to_swapchain_pixels(release_pos),
|
||||||
|
selected);
|
||||||
|
_drag_selection.reserve(selected.size());
|
||||||
|
for (const RenderObject &obj : selected)
|
||||||
|
{
|
||||||
|
PickInfo info{};
|
||||||
|
info.mesh = obj.sourceMesh;
|
||||||
|
info.scene = obj.sourceScene;
|
||||||
|
info.node = obj.sourceNode;
|
||||||
|
info.ownerType = obj.ownerType;
|
||||||
|
info.ownerName = obj.ownerName;
|
||||||
|
glm::vec3 center_local = glm::vec3(obj.transform * glm::vec4(obj.bounds.origin, 1.0f));
|
||||||
|
info.worldPos = local_to_world(center_local, scene->get_world_origin());
|
||||||
|
info.worldTransform = obj.transform;
|
||||||
|
info.firstIndex = obj.firstIndex;
|
||||||
|
info.indexCount = obj.indexCount;
|
||||||
|
info.surfaceIndex = obj.surfaceIndex;
|
||||||
|
info.valid = true;
|
||||||
|
_drag_selection.push_back(std::move(info));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_drag_state.dragging = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PickingSystem::update_hover()
|
||||||
|
{
|
||||||
|
if (_context == nullptr || _context->scene == nullptr)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_mouse_pos_window.x < 0.0f || _mouse_pos_window.y < 0.0f)
|
||||||
|
{
|
||||||
|
clear_pick(_hover_pick);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderObject hover_obj{};
|
||||||
|
WorldVec3 hover_pos{};
|
||||||
|
if (_context->scene->pick(window_to_swapchain_pixels(_mouse_pos_window), hover_obj, hover_pos))
|
||||||
|
{
|
||||||
|
set_pick_from_hit(hover_obj, hover_pos, _hover_pick);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
clear_pick(_hover_pick);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PickingSystem::begin_frame()
|
||||||
|
{
|
||||||
|
if (!_pick_result_pending || !_pick_readback_buffer.buffer || _context == nullptr || _context->scene == nullptr)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DeviceManager *dev = _context->getDevice();
|
||||||
|
if (!dev)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
vmaInvalidateAllocation(dev->allocator(), _pick_readback_buffer.allocation, 0, sizeof(uint32_t));
|
||||||
|
|
||||||
|
uint32_t picked_id = 0;
|
||||||
|
if (_pick_readback_buffer.info.pMappedData)
|
||||||
|
{
|
||||||
|
picked_id = *reinterpret_cast<const uint32_t *>(_pick_readback_buffer.info.pMappedData);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (picked_id == 0)
|
||||||
|
{
|
||||||
|
clear_pick(_last_pick);
|
||||||
|
_last_pick_object_id = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_last_pick_object_id = picked_id;
|
||||||
|
RenderObject picked{};
|
||||||
|
if (_context->scene->resolveObjectID(picked_id, picked))
|
||||||
|
{
|
||||||
|
glm::vec3 fallback_local = glm::vec3(picked.transform[3]);
|
||||||
|
WorldVec3 fallback_pos = local_to_world(fallback_local, _context->scene->get_world_origin());
|
||||||
|
set_pick_from_hit(picked, fallback_pos, _last_pick);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
clear_pick(_last_pick);
|
||||||
|
_last_pick_object_id = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_pick_result_pending = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PickingSystem::register_id_buffer_readback(RenderGraph &graph,
|
||||||
|
RGImageHandle id_buffer,
|
||||||
|
VkExtent2D draw_extent,
|
||||||
|
VkExtent2D swapchain_extent)
|
||||||
|
{
|
||||||
|
if (!_use_id_buffer_picking || !_pending_pick.active || !id_buffer.valid() || !_pick_readback_buffer.buffer)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (draw_extent.width == 0 || draw_extent.height == 0 || swapchain_extent.width == 0 || swapchain_extent.height == 0)
|
||||||
|
{
|
||||||
|
_pending_pick.active = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
glm::vec2 logical_pos{};
|
||||||
|
if (!vkutil::map_window_to_letterbox_src(_pending_pick.window_pos_swapchain, draw_extent, swapchain_extent, logical_pos))
|
||||||
|
{
|
||||||
|
_pending_pick.active = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint32_t id_x = static_cast<uint32_t>(std::clamp(logical_pos.x, 0.0f, float(draw_extent.width - 1)));
|
||||||
|
const uint32_t id_y = static_cast<uint32_t>(std::clamp(logical_pos.y, 0.0f, float(draw_extent.height - 1)));
|
||||||
|
_pending_pick.id_coords = {id_x, id_y};
|
||||||
|
|
||||||
|
RGImportedBufferDesc bd{};
|
||||||
|
bd.name = "pick.readback";
|
||||||
|
bd.buffer = _pick_readback_buffer.buffer;
|
||||||
|
bd.size = sizeof(uint32_t);
|
||||||
|
bd.currentStage = VK_PIPELINE_STAGE_2_NONE;
|
||||||
|
bd.currentAccess = 0;
|
||||||
|
RGBufferHandle pick_buf = graph.import_buffer(bd);
|
||||||
|
|
||||||
|
const glm::uvec2 coords = _pending_pick.id_coords;
|
||||||
|
graph.add_pass(
|
||||||
|
"PickReadback",
|
||||||
|
RGPassType::Transfer,
|
||||||
|
[id_buffer, pick_buf](RGPassBuilder &builder, EngineContext *)
|
||||||
|
{
|
||||||
|
builder.read(id_buffer, RGImageUsage::TransferSrc);
|
||||||
|
builder.write_buffer(pick_buf, RGBufferUsage::TransferDst);
|
||||||
|
},
|
||||||
|
[coords, id_buffer, pick_buf](VkCommandBuffer cmd, const RGPassResources &res, EngineContext *)
|
||||||
|
{
|
||||||
|
VkImage id_image = res.image(id_buffer);
|
||||||
|
VkBuffer dst = res.buffer(pick_buf);
|
||||||
|
if (id_image == VK_NULL_HANDLE || dst == VK_NULL_HANDLE) return;
|
||||||
|
|
||||||
|
VkBufferImageCopy region{};
|
||||||
|
region.bufferOffset = 0;
|
||||||
|
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||||
|
region.imageSubresource.mipLevel = 0;
|
||||||
|
region.imageSubresource.baseArrayLayer = 0;
|
||||||
|
region.imageSubresource.layerCount = 1;
|
||||||
|
region.imageOffset = {static_cast<int32_t>(coords.x),
|
||||||
|
static_cast<int32_t>(coords.y),
|
||||||
|
0};
|
||||||
|
region.imageExtent = {1, 1, 1};
|
||||||
|
|
||||||
|
vkCmdCopyImageToBuffer(cmd,
|
||||||
|
id_image,
|
||||||
|
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||||
|
dst,
|
||||||
|
1,
|
||||||
|
®ion);
|
||||||
|
});
|
||||||
|
|
||||||
|
_pick_result_pending = true;
|
||||||
|
_pending_pick.active = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PickingSystem::clear_owner_picks(RenderObject::OwnerType owner_type, const std::string &owner_name)
|
||||||
|
{
|
||||||
|
if (_last_pick.valid && _last_pick.ownerType == owner_type && _last_pick.ownerName == owner_name)
|
||||||
|
{
|
||||||
|
clear_pick(_last_pick);
|
||||||
|
_last_pick_object_id = 0;
|
||||||
|
}
|
||||||
|
if (_hover_pick.valid && _hover_pick.ownerType == owner_type && _hover_pick.ownerName == owner_name)
|
||||||
|
{
|
||||||
|
clear_pick(_hover_pick);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_drag_selection.empty())
|
||||||
|
{
|
||||||
|
_drag_selection.erase(std::remove_if(_drag_selection.begin(),
|
||||||
|
_drag_selection.end(),
|
||||||
|
[&](const PickInfo &p) {
|
||||||
|
return p.valid && p.ownerType == owner_type && p.ownerName == owner_name;
|
||||||
|
}),
|
||||||
|
_drag_selection.end());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
glm::vec2 PickingSystem::window_to_swapchain_pixels(const glm::vec2 &window_pos) const
|
||||||
|
{
|
||||||
|
if (_context == nullptr || _context->window == nullptr || _context->getSwapchain() == nullptr)
|
||||||
|
{
|
||||||
|
return window_pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
int win_w = 0, win_h = 0;
|
||||||
|
SDL_GetWindowSize(_context->window, &win_w, &win_h);
|
||||||
|
|
||||||
|
int draw_w = 0, draw_h = 0;
|
||||||
|
SDL_Vulkan_GetDrawableSize(_context->window, &draw_w, &draw_h);
|
||||||
|
|
||||||
|
glm::vec2 scale{1.0f, 1.0f};
|
||||||
|
if (win_w > 0 && win_h > 0 && draw_w > 0 && draw_h > 0)
|
||||||
|
{
|
||||||
|
scale.x = static_cast<float>(draw_w) / static_cast<float>(win_w);
|
||||||
|
scale.y = static_cast<float>(draw_h) / static_cast<float>(win_h);
|
||||||
|
}
|
||||||
|
|
||||||
|
glm::vec2 drawable_pos{window_pos.x * scale.x, window_pos.y * scale.y};
|
||||||
|
|
||||||
|
VkExtent2D drawable_extent{0, 0};
|
||||||
|
if (draw_w > 0 && draw_h > 0)
|
||||||
|
{
|
||||||
|
drawable_extent.width = static_cast<uint32_t>(draw_w);
|
||||||
|
drawable_extent.height = static_cast<uint32_t>(draw_h);
|
||||||
|
}
|
||||||
|
if ((drawable_extent.width == 0 || drawable_extent.height == 0) && _context->getSwapchain())
|
||||||
|
{
|
||||||
|
drawable_extent = _context->getSwapchain()->windowExtent();
|
||||||
|
}
|
||||||
|
|
||||||
|
VkExtent2D swap = _context->getSwapchain()->swapchainExtent();
|
||||||
|
if (drawable_extent.width == 0 || drawable_extent.height == 0 || swap.width == 0 || swap.height == 0)
|
||||||
|
{
|
||||||
|
return drawable_pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
const float sx = static_cast<float>(swap.width) / static_cast<float>(drawable_extent.width);
|
||||||
|
const float sy = static_cast<float>(swap.height) / static_cast<float>(drawable_extent.height);
|
||||||
|
return glm::vec2{drawable_pos.x * sx, drawable_pos.y * sy};
|
||||||
|
}
|
||||||
|
|
||||||
|
void PickingSystem::set_pick_from_hit(const RenderObject &hit_object, const WorldVec3 &hit_pos, PickInfo &out_pick)
|
||||||
|
{
|
||||||
|
out_pick.mesh = hit_object.sourceMesh;
|
||||||
|
out_pick.scene = hit_object.sourceScene;
|
||||||
|
out_pick.node = hit_object.sourceNode;
|
||||||
|
out_pick.ownerType = hit_object.ownerType;
|
||||||
|
out_pick.ownerName = hit_object.ownerName;
|
||||||
|
out_pick.worldPos = hit_pos;
|
||||||
|
out_pick.worldTransform = hit_object.transform;
|
||||||
|
out_pick.firstIndex = hit_object.firstIndex;
|
||||||
|
out_pick.indexCount = hit_object.indexCount;
|
||||||
|
out_pick.surfaceIndex = hit_object.surfaceIndex;
|
||||||
|
out_pick.valid = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PickingSystem::clear_pick(PickInfo &pick)
|
||||||
|
{
|
||||||
|
pick.mesh = nullptr;
|
||||||
|
pick.scene = nullptr;
|
||||||
|
pick.node = nullptr;
|
||||||
|
pick.ownerType = RenderObject::OwnerType::None;
|
||||||
|
pick.ownerName.clear();
|
||||||
|
pick.worldPos = WorldVec3{0.0, 0.0, 0.0};
|
||||||
|
pick.worldTransform = glm::mat4(1.0f);
|
||||||
|
pick.indexCount = 0;
|
||||||
|
pick.firstIndex = 0;
|
||||||
|
pick.surfaceIndex = 0;
|
||||||
|
pick.valid = false;
|
||||||
|
}
|
||||||
107
src/core/picking/picking_system.h
Normal file
107
src/core/picking/picking_system.h
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <core/types.h>
|
||||||
|
#include <core/world.h>
|
||||||
|
#include <core/device/resource.h>
|
||||||
|
|
||||||
|
#include <scene/vk_scene.h>
|
||||||
|
|
||||||
|
#include <glm/vec2.hpp>
|
||||||
|
#include <glm/mat4x4.hpp>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
union SDL_Event;
|
||||||
|
class EngineContext;
|
||||||
|
class RenderGraph;
|
||||||
|
struct RGImageHandle;
|
||||||
|
|
||||||
|
class PickingSystem
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
struct PickInfo
|
||||||
|
{
|
||||||
|
MeshAsset *mesh = nullptr;
|
||||||
|
LoadedGLTF *scene = nullptr;
|
||||||
|
Node *node = nullptr;
|
||||||
|
RenderObject::OwnerType ownerType = RenderObject::OwnerType::None;
|
||||||
|
std::string ownerName;
|
||||||
|
WorldVec3 worldPos{0.0, 0.0, 0.0};
|
||||||
|
glm::mat4 worldTransform{1.0f};
|
||||||
|
uint32_t indexCount = 0;
|
||||||
|
uint32_t firstIndex = 0;
|
||||||
|
uint32_t surfaceIndex = 0;
|
||||||
|
bool valid = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
void init(EngineContext *context);
|
||||||
|
void cleanup();
|
||||||
|
|
||||||
|
void process_event(const SDL_Event &event, bool ui_want_capture_mouse);
|
||||||
|
void update_hover();
|
||||||
|
|
||||||
|
// Called after the per-frame fence is waited to resolve async ID-buffer picks.
|
||||||
|
void begin_frame();
|
||||||
|
|
||||||
|
// Called during RenderGraph build after the ID buffer is available.
|
||||||
|
void register_id_buffer_readback(RenderGraph &graph,
|
||||||
|
RGImageHandle id_buffer,
|
||||||
|
VkExtent2D draw_extent,
|
||||||
|
VkExtent2D swapchain_extent);
|
||||||
|
|
||||||
|
const PickInfo &last_pick() const { return _last_pick; }
|
||||||
|
const PickInfo &hover_pick() const { return _hover_pick; }
|
||||||
|
const std::vector<PickInfo> &drag_selection() const { return _drag_selection; }
|
||||||
|
|
||||||
|
uint32_t last_pick_object_id() const { return _last_pick_object_id; }
|
||||||
|
|
||||||
|
bool use_id_buffer_picking() const { return _use_id_buffer_picking; }
|
||||||
|
void set_use_id_buffer_picking(bool enabled) { _use_id_buffer_picking = enabled; }
|
||||||
|
|
||||||
|
bool debug_draw_bvh() const { return _debug_draw_bvh; }
|
||||||
|
void set_debug_draw_bvh(bool enabled) { _debug_draw_bvh = enabled; }
|
||||||
|
|
||||||
|
void clear_owner_picks(RenderObject::OwnerType owner_type, const std::string &owner_name);
|
||||||
|
|
||||||
|
PickInfo *mutable_last_pick() { return &_last_pick; }
|
||||||
|
PickInfo *mutable_hover_pick() { return &_hover_pick; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct PickRequest
|
||||||
|
{
|
||||||
|
bool active = false;
|
||||||
|
glm::vec2 window_pos_swapchain{0.0f};
|
||||||
|
glm::uvec2 id_coords{0, 0};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DragState
|
||||||
|
{
|
||||||
|
bool dragging = false;
|
||||||
|
bool button_down = false;
|
||||||
|
glm::vec2 start{0.0f};
|
||||||
|
glm::vec2 current{0.0f};
|
||||||
|
};
|
||||||
|
|
||||||
|
glm::vec2 window_to_swapchain_pixels(const glm::vec2 &window_pos) const;
|
||||||
|
void set_pick_from_hit(const RenderObject &hit_object, const WorldVec3 &hit_pos, PickInfo &out_pick);
|
||||||
|
void clear_pick(PickInfo &pick);
|
||||||
|
|
||||||
|
EngineContext *_context = nullptr;
|
||||||
|
|
||||||
|
PickInfo _last_pick{};
|
||||||
|
PickInfo _hover_pick{};
|
||||||
|
std::vector<PickInfo> _drag_selection{};
|
||||||
|
|
||||||
|
glm::vec2 _mouse_pos_window{-1.0f, -1.0f};
|
||||||
|
DragState _drag_state{};
|
||||||
|
|
||||||
|
bool _use_id_buffer_picking = false;
|
||||||
|
bool _debug_draw_bvh = false;
|
||||||
|
|
||||||
|
uint32_t _last_pick_object_id = 0;
|
||||||
|
PickRequest _pending_pick{};
|
||||||
|
bool _pick_result_pending = false;
|
||||||
|
|
||||||
|
AllocatedBuffer _pick_readback_buffer{};
|
||||||
|
};
|
||||||
309
src/core/ui/imgui_system.cpp
Normal file
309
src/core/ui/imgui_system.cpp
Normal file
@@ -0,0 +1,309 @@
|
|||||||
|
#include "imgui_system.h"
|
||||||
|
|
||||||
|
#include "core/context.h"
|
||||||
|
#include "core/device/swapchain.h"
|
||||||
|
|
||||||
|
#include "SDL2/SDL.h"
|
||||||
|
#include "SDL2/SDL_vulkan.h"
|
||||||
|
|
||||||
|
#include "imgui.h"
|
||||||
|
#include "imgui_impl_sdl2.h"
|
||||||
|
#include "imgui_impl_vulkan.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
#include "device.h"
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
VkDescriptorPool create_imgui_descriptor_pool(VkDevice device)
|
||||||
|
{
|
||||||
|
VkDescriptorPoolSize pool_sizes[] = {
|
||||||
|
{VK_DESCRIPTOR_TYPE_SAMPLER, 1000},
|
||||||
|
{VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1000},
|
||||||
|
{VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 1000},
|
||||||
|
{VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1000},
|
||||||
|
{VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, 1000},
|
||||||
|
{VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, 1000},
|
||||||
|
{VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1000},
|
||||||
|
{VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1000},
|
||||||
|
{VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1000},
|
||||||
|
{VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC, 1000},
|
||||||
|
{VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, 1000}
|
||||||
|
};
|
||||||
|
|
||||||
|
VkDescriptorPoolCreateInfo pool_info{};
|
||||||
|
pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
|
||||||
|
pool_info.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT;
|
||||||
|
pool_info.maxSets = 1000;
|
||||||
|
pool_info.poolSizeCount = static_cast<uint32_t>(std::size(pool_sizes));
|
||||||
|
pool_info.pPoolSizes = pool_sizes;
|
||||||
|
|
||||||
|
VkDescriptorPool pool = VK_NULL_HANDLE;
|
||||||
|
VK_CHECK(vkCreateDescriptorPool(device, &pool_info, nullptr, &pool));
|
||||||
|
return pool;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t clamp_imgui_image_count(uint32_t count)
|
||||||
|
{
|
||||||
|
if (count < 2) return 2;
|
||||||
|
if (count > 8) return 8;
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void ImGuiSystem::init(EngineContext *context)
|
||||||
|
{
|
||||||
|
if (_initialized)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_context = context;
|
||||||
|
if (_context == nullptr || _context->getDevice() == nullptr || _context->getSwapchain() == nullptr || _context->window == nullptr)
|
||||||
|
{
|
||||||
|
fmt::println("[ImGuiSystem] init skipped (missing context/device/swapchain/window)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
VkDevice device = _context->getDevice()->device();
|
||||||
|
|
||||||
|
IMGUI_CHECKVERSION();
|
||||||
|
ImGui::CreateContext();
|
||||||
|
ImGui::StyleColorsDark();
|
||||||
|
|
||||||
|
ImGuiIO &io = ImGui::GetIO();
|
||||||
|
|
||||||
|
_swapchain_format = _context->getSwapchain()->swapchainImageFormat();
|
||||||
|
|
||||||
|
_dpi_scale = std::clamp(compute_dpi_scale(), 0.5f, 4.0f);
|
||||||
|
rebuild_fonts(_dpi_scale);
|
||||||
|
|
||||||
|
_imgui_pool = create_imgui_descriptor_pool(device);
|
||||||
|
|
||||||
|
ImGui_ImplSDL2_InitForVulkan(_context->window);
|
||||||
|
|
||||||
|
ImGui_ImplVulkan_InitInfo init_info{};
|
||||||
|
init_info.Instance = _context->getDevice()->instance();
|
||||||
|
init_info.PhysicalDevice = _context->getDevice()->physicalDevice();
|
||||||
|
init_info.Device = _context->getDevice()->device();
|
||||||
|
init_info.QueueFamily = _context->getDevice()->graphicsQueueFamily();
|
||||||
|
init_info.Queue = _context->getDevice()->graphicsQueue();
|
||||||
|
init_info.DescriptorPool = _imgui_pool;
|
||||||
|
|
||||||
|
const auto &images = _context->getSwapchain()->swapchainImages();
|
||||||
|
uint32_t image_count = images.empty() ? 3u : clamp_imgui_image_count(static_cast<uint32_t>(images.size()));
|
||||||
|
init_info.MinImageCount = image_count;
|
||||||
|
init_info.ImageCount = image_count;
|
||||||
|
|
||||||
|
init_info.UseDynamicRendering = true;
|
||||||
|
init_info.PipelineRenderingCreateInfo = {};
|
||||||
|
init_info.PipelineRenderingCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO;
|
||||||
|
init_info.PipelineRenderingCreateInfo.colorAttachmentCount = 1;
|
||||||
|
init_info.PipelineRenderingCreateInfo.pColorAttachmentFormats = &_swapchain_format;
|
||||||
|
init_info.MSAASamples = VK_SAMPLE_COUNT_1_BIT;
|
||||||
|
|
||||||
|
ImGui_ImplVulkan_Init(&init_info);
|
||||||
|
|
||||||
|
if (!ImGui_ImplVulkan_CreateFontsTexture())
|
||||||
|
{
|
||||||
|
fmt::println("[ImGuiSystem] Warning: ImGui_ImplVulkan_CreateFontsTexture() failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
io.FontGlobalScale = (_dpi_scale > 0.0f) ? (1.0f / _dpi_scale) : 1.0f;
|
||||||
|
|
||||||
|
_initialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImGuiSystem::cleanup()
|
||||||
|
{
|
||||||
|
if (!_initialized)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui_ImplVulkan_Shutdown();
|
||||||
|
ImGui_ImplSDL2_Shutdown();
|
||||||
|
|
||||||
|
if (_context && _context->getDevice() && _imgui_pool != VK_NULL_HANDLE)
|
||||||
|
{
|
||||||
|
vkDestroyDescriptorPool(_context->getDevice()->device(), _imgui_pool, nullptr);
|
||||||
|
}
|
||||||
|
_imgui_pool = VK_NULL_HANDLE;
|
||||||
|
|
||||||
|
ImGui::DestroyContext();
|
||||||
|
|
||||||
|
_draw_callbacks.clear();
|
||||||
|
_context = nullptr;
|
||||||
|
_initialized = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImGuiSystem::process_event(const SDL_Event &event)
|
||||||
|
{
|
||||||
|
if (!_initialized)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ImGui_ImplSDL2_ProcessEvent(const_cast<SDL_Event *>(&event));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImGuiSystem::begin_frame()
|
||||||
|
{
|
||||||
|
if (!_initialized)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui_ImplVulkan_NewFrame();
|
||||||
|
ImGui_ImplSDL2_NewFrame();
|
||||||
|
|
||||||
|
update_framebuffer_scale();
|
||||||
|
|
||||||
|
const float new_scale = std::clamp(compute_dpi_scale(), 0.5f, 4.0f);
|
||||||
|
if (std::isfinite(new_scale) && std::abs(new_scale - _dpi_scale) > 0.05f)
|
||||||
|
{
|
||||||
|
rebuild_fonts(new_scale);
|
||||||
|
_dpi_scale = new_scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::NewFrame();
|
||||||
|
|
||||||
|
for (auto &cb : _draw_callbacks)
|
||||||
|
{
|
||||||
|
if (cb) cb();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImGuiSystem::end_frame()
|
||||||
|
{
|
||||||
|
if (!_initialized)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ImGui::Render();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImGuiSystem::add_draw_callback(DrawCallback callback)
|
||||||
|
{
|
||||||
|
_draw_callbacks.push_back(std::move(callback));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImGuiSystem::clear_draw_callbacks()
|
||||||
|
{
|
||||||
|
_draw_callbacks.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ImGuiSystem::want_capture_mouse() const
|
||||||
|
{
|
||||||
|
if (!_initialized) return false;
|
||||||
|
return ImGui::GetIO().WantCaptureMouse;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ImGuiSystem::want_capture_keyboard() const
|
||||||
|
{
|
||||||
|
if (!_initialized) return false;
|
||||||
|
return ImGui::GetIO().WantCaptureKeyboard;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImGuiSystem::on_swapchain_recreated()
|
||||||
|
{
|
||||||
|
if (!_initialized || _context == nullptr || _context->getSwapchain() == nullptr)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto &images = _context->getSwapchain()->swapchainImages();
|
||||||
|
uint32_t image_count = images.empty() ? 3u : clamp_imgui_image_count(static_cast<uint32_t>(images.size()));
|
||||||
|
ImGui_ImplVulkan_SetMinImageCount(image_count);
|
||||||
|
|
||||||
|
update_framebuffer_scale();
|
||||||
|
}
|
||||||
|
|
||||||
|
float ImGuiSystem::compute_dpi_scale() const
|
||||||
|
{
|
||||||
|
if (_context == nullptr || _context->window == nullptr || _context->getSwapchain() == nullptr)
|
||||||
|
{
|
||||||
|
return 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
int win_w = 0, win_h = 0;
|
||||||
|
SDL_GetWindowSize(_context->window, &win_w, &win_h);
|
||||||
|
if (win_w <= 0 || win_h <= 0)
|
||||||
|
{
|
||||||
|
return _dpi_scale > 0.0f ? _dpi_scale : 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
VkExtent2D swap = _context->getSwapchain()->swapchainExtent();
|
||||||
|
if (swap.width == 0 || swap.height == 0)
|
||||||
|
{
|
||||||
|
return _dpi_scale > 0.0f ? _dpi_scale : 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
float sx = static_cast<float>(swap.width) / static_cast<float>(win_w);
|
||||||
|
float sy = static_cast<float>(swap.height) / static_cast<float>(win_h);
|
||||||
|
|
||||||
|
if (!std::isfinite(sx) || !std::isfinite(sy))
|
||||||
|
{
|
||||||
|
return 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0.5f * (sx + sy);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImGuiSystem::update_framebuffer_scale()
|
||||||
|
{
|
||||||
|
if (_context == nullptr || _context->window == nullptr || _context->getSwapchain() == nullptr)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int win_w = 0, win_h = 0;
|
||||||
|
SDL_GetWindowSize(_context->window, &win_w, &win_h);
|
||||||
|
if (win_w <= 0 || win_h <= 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
VkExtent2D swap = _context->getSwapchain()->swapchainExtent();
|
||||||
|
if (swap.width == 0 || swap.height == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGuiIO &io = ImGui::GetIO();
|
||||||
|
io.DisplayFramebufferScale = ImVec2(static_cast<float>(swap.width) / static_cast<float>(win_w),
|
||||||
|
static_cast<float>(swap.height) / static_cast<float>(win_h));
|
||||||
|
|
||||||
|
const float scale = std::clamp(compute_dpi_scale(), 0.5f, 4.0f);
|
||||||
|
io.FontGlobalScale = (scale > 0.0f) ? (1.0f / scale) : 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImGuiSystem::rebuild_fonts(float dpi_scale)
|
||||||
|
{
|
||||||
|
if (_initialized)
|
||||||
|
{
|
||||||
|
ImGui_ImplVulkan_DestroyFontsTexture();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGuiIO &io = ImGui::GetIO();
|
||||||
|
io.Fonts->Clear();
|
||||||
|
|
||||||
|
ImFontConfig cfg{};
|
||||||
|
cfg.SizePixels = _base_font_size * dpi_scale;
|
||||||
|
cfg.OversampleH = 3;
|
||||||
|
cfg.OversampleV = 2;
|
||||||
|
cfg.PixelSnapH = false;
|
||||||
|
|
||||||
|
io.Fonts->AddFontDefault(&cfg);
|
||||||
|
|
||||||
|
io.FontGlobalScale = (dpi_scale > 0.0f) ? (1.0f / dpi_scale) : 1.0f;
|
||||||
|
|
||||||
|
if (_initialized)
|
||||||
|
{
|
||||||
|
if (!ImGui_ImplVulkan_CreateFontsTexture())
|
||||||
|
{
|
||||||
|
fmt::println("[ImGuiSystem] Warning: ImGui_ImplVulkan_CreateFontsTexture() failed after DPI change");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
46
src/core/ui/imgui_system.h
Normal file
46
src/core/ui/imgui_system.h
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <core/types.h>
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
union SDL_Event;
|
||||||
|
class EngineContext;
|
||||||
|
|
||||||
|
class ImGuiSystem
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using DrawCallback = std::function<void()>;
|
||||||
|
|
||||||
|
void init(EngineContext *context);
|
||||||
|
void cleanup();
|
||||||
|
|
||||||
|
void process_event(const SDL_Event &event);
|
||||||
|
|
||||||
|
void begin_frame();
|
||||||
|
void end_frame();
|
||||||
|
|
||||||
|
void add_draw_callback(DrawCallback callback);
|
||||||
|
void clear_draw_callbacks();
|
||||||
|
|
||||||
|
bool want_capture_mouse() const;
|
||||||
|
bool want_capture_keyboard() const;
|
||||||
|
|
||||||
|
void on_swapchain_recreated();
|
||||||
|
|
||||||
|
private:
|
||||||
|
float compute_dpi_scale() const;
|
||||||
|
void update_framebuffer_scale();
|
||||||
|
void rebuild_fonts(float dpi_scale);
|
||||||
|
|
||||||
|
EngineContext *_context = nullptr;
|
||||||
|
std::vector<DrawCallback> _draw_callbacks;
|
||||||
|
|
||||||
|
VkDescriptorPool _imgui_pool = VK_NULL_HANDLE;
|
||||||
|
VkFormat _swapchain_format = VK_FORMAT_UNDEFINED;
|
||||||
|
|
||||||
|
float _dpi_scale = 1.0f;
|
||||||
|
float _base_font_size = 16.0f;
|
||||||
|
bool _initialized = false;
|
||||||
|
};
|
||||||
@@ -1,78 +1,17 @@
|
|||||||
#include "imgui_pass.h"
|
#include "imgui_pass.h"
|
||||||
|
|
||||||
#include "imgui.h"
|
#include "imgui.h"
|
||||||
#include "imgui_impl_sdl2.h"
|
|
||||||
#include "imgui_impl_vulkan.h"
|
#include "imgui_impl_vulkan.h"
|
||||||
#include "core/device/device.h"
|
|
||||||
#include "core/device/swapchain.h"
|
|
||||||
#include "core/util/initializers.h"
|
|
||||||
#include "core/context.h"
|
#include "core/context.h"
|
||||||
#include "render/graph/graph.h"
|
#include "render/graph/graph.h"
|
||||||
|
|
||||||
void ImGuiPass::init(EngineContext *context)
|
void ImGuiPass::init(EngineContext *context)
|
||||||
{
|
{
|
||||||
_context = context;
|
_context = context;
|
||||||
|
|
||||||
VkDescriptorPoolSize pool_sizes[] = {
|
|
||||||
{VK_DESCRIPTOR_TYPE_SAMPLER, 1000},
|
|
||||||
{VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1000},
|
|
||||||
{VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 1000},
|
|
||||||
{VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1000},
|
|
||||||
{VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, 1000},
|
|
||||||
{VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, 1000},
|
|
||||||
{VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1000},
|
|
||||||
{VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1000},
|
|
||||||
{VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1000},
|
|
||||||
{VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC, 1000},
|
|
||||||
{VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, 1000}
|
|
||||||
};
|
|
||||||
|
|
||||||
VkDescriptorPoolCreateInfo pool_info = {};
|
|
||||||
pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
|
|
||||||
pool_info.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT;
|
|
||||||
pool_info.maxSets = 1000;
|
|
||||||
pool_info.poolSizeCount = (uint32_t) std::size(pool_sizes);
|
|
||||||
pool_info.pPoolSizes = pool_sizes;
|
|
||||||
|
|
||||||
VkDescriptorPool imguiPool;
|
|
||||||
VK_CHECK(vkCreateDescriptorPool(_context->device->device(), &pool_info, nullptr, &imguiPool));
|
|
||||||
|
|
||||||
ImGui::CreateContext();
|
|
||||||
|
|
||||||
ImGui_ImplSDL2_InitForVulkan(_context->window);
|
|
||||||
|
|
||||||
ImGui_ImplVulkan_InitInfo init_info = {};
|
|
||||||
init_info.Instance = _context->getDevice()->instance();
|
|
||||||
init_info.PhysicalDevice = _context->getDevice()->physicalDevice();
|
|
||||||
init_info.Device = _context->getDevice()->device();
|
|
||||||
init_info.Queue = _context->getDevice()->graphicsQueue();
|
|
||||||
init_info.DescriptorPool = imguiPool;
|
|
||||||
init_info.MinImageCount = 3;
|
|
||||||
init_info.ImageCount = 3;
|
|
||||||
init_info.UseDynamicRendering = true;
|
|
||||||
|
|
||||||
init_info.PipelineRenderingCreateInfo = {.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO};
|
|
||||||
init_info.PipelineRenderingCreateInfo.colorAttachmentCount = 1;
|
|
||||||
auto _swapchainImageFormat = _context->getSwapchain()->swapchainImageFormat();
|
|
||||||
init_info.PipelineRenderingCreateInfo.pColorAttachmentFormats = &_swapchainImageFormat;
|
|
||||||
|
|
||||||
init_info.MSAASamples = VK_SAMPLE_COUNT_1_BIT;
|
|
||||||
|
|
||||||
ImGui_ImplVulkan_Init(&init_info);
|
|
||||||
|
|
||||||
ImGui_ImplVulkan_CreateFontsTexture();
|
|
||||||
|
|
||||||
// add the destroy the imgui created structures
|
|
||||||
_deletionQueue.push_function([=]() {
|
|
||||||
ImGui_ImplVulkan_Shutdown();
|
|
||||||
vkDestroyDescriptorPool(_context->getDevice()->device(), imguiPool, nullptr);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ImGuiPass::cleanup()
|
void ImGuiPass::cleanup()
|
||||||
{
|
{
|
||||||
fmt::print("ImGuiPass::cleanup()\n");
|
|
||||||
_deletionQueue.flush();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ImGuiPass::execute(VkCommandBuffer)
|
void ImGuiPass::execute(VkCommandBuffer)
|
||||||
|
|||||||
@@ -25,5 +25,4 @@ private:
|
|||||||
const class RGPassResources &resources,
|
const class RGPassResources &resources,
|
||||||
RGImageHandle targetHandle) const;
|
RGImageHandle targetHandle) const;
|
||||||
|
|
||||||
DeletionQueue _deletionQueue;
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -434,7 +434,7 @@ bool SceneManager::pick(const glm::vec2 &mousePosPixels, RenderObject &outObject
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
VkExtent2D dstExtent = swapchain->windowExtent();
|
VkExtent2D dstExtent = swapchain->swapchainExtent();
|
||||||
if (dstExtent.width == 0 || dstExtent.height == 0)
|
if (dstExtent.width == 0 || dstExtent.height == 0)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@@ -584,7 +584,7 @@ void SceneManager::selectRect(const glm::vec2 &p0, const glm::vec2 &p1, std::vec
|
|||||||
}
|
}
|
||||||
|
|
||||||
SwapchainManager *swapchain = _context->getSwapchain();
|
SwapchainManager *swapchain = _context->getSwapchain();
|
||||||
VkExtent2D dstExtent = swapchain->windowExtent();
|
VkExtent2D dstExtent = swapchain->swapchainExtent();
|
||||||
if (dstExtent.width == 0 || dstExtent.height == 0)
|
if (dstExtent.width == 0 || dstExtent.height == 0)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
|||||||
Reference in New Issue
Block a user