ADD: UI/picking manager
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -2,4 +2,7 @@
|
||||
/bin
|
||||
/assets
|
||||
/.idea
|
||||
/cmake-build-debug
|
||||
/cmake-build-debug_win
|
||||
/cmake-build-release_win
|
||||
*.spv
|
||||
@@ -11,6 +11,10 @@ add_executable (vulkan_engine
|
||||
core/engine.h
|
||||
core/engine.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.cpp
|
||||
# core/device
|
||||
|
||||
@@ -27,6 +27,26 @@ void SwapchainManager::init_swapchain()
|
||||
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()
|
||||
{
|
||||
_deletionQueue.flush();
|
||||
|
||||
@@ -14,6 +14,7 @@ public:
|
||||
|
||||
void cleanup();
|
||||
|
||||
void set_window_extent_from_window(struct SDL_Window *window);
|
||||
void init_swapchain();
|
||||
void create_swapchain(uint32_t width, uint32_t height);
|
||||
void destroy_swapchain() const;
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "engine.h"
|
||||
|
||||
#include "SDL2/SDL.h"
|
||||
#include "SDL2/SDL_vulkan.h"
|
||||
|
||||
#include <core/util/initializers.h>
|
||||
#include <core/types.h>
|
||||
@@ -37,9 +38,8 @@
|
||||
#include "render/primitives.h"
|
||||
|
||||
#include "vk_mem_alloc.h"
|
||||
#include "imgui.h"
|
||||
#include "imgui_impl_sdl2.h"
|
||||
#include "imgui_impl_vulkan.h"
|
||||
#include "core/ui/imgui_system.h"
|
||||
#include "core/picking/picking_system.h"
|
||||
#include "render/passes/geometry.h"
|
||||
#include "render/passes/imgui_pass.h"
|
||||
#include "render/passes/lighting.h"
|
||||
@@ -59,6 +59,8 @@ void vk_engine_draw_debug_ui(VulkanEngine *eng);
|
||||
|
||||
VulkanEngine *loadedEngine = nullptr;
|
||||
|
||||
VulkanEngine::~VulkanEngine() = default;
|
||||
|
||||
static VkExtent2D clamp_nonzero_extent(VkExtent2D extent)
|
||||
{
|
||||
if (extent.width == 0) extent.width = 1;
|
||||
@@ -166,6 +168,13 @@ size_t VulkanEngine::query_texture_budget_bytes() const
|
||||
|
||||
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.
|
||||
SDL_Init(SDL_INIT_VIDEO);
|
||||
|
||||
@@ -175,7 +184,11 @@ void VulkanEngine::init()
|
||||
_logicalRenderExtent = clamp_nonzero_extent(_logicalRenderExtent);
|
||||
_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>();
|
||||
|
||||
@@ -188,6 +201,11 @@ void VulkanEngine::init()
|
||||
window_flags
|
||||
);
|
||||
|
||||
if (_swapchainManager)
|
||||
{
|
||||
_swapchainManager->set_window_extent_from_window(_window);
|
||||
}
|
||||
|
||||
_windowMode = WindowMode::Windowed;
|
||||
_windowDisplayIndex = SDL_GetWindowDisplayIndex(_window);
|
||||
if (_windowDisplayIndex < 0) _windowDisplayIndex = 0;
|
||||
@@ -283,6 +301,9 @@ void VulkanEngine::init()
|
||||
_context->window = _window;
|
||||
_context->stats = &stats;
|
||||
|
||||
_picking = std::make_unique<PickingSystem>();
|
||||
_picking->init(_context.get());
|
||||
|
||||
// Render graph skeleton
|
||||
_renderGraph = std::make_unique<RenderGraph>();
|
||||
_renderGraph->init(_context.get());
|
||||
@@ -341,6 +362,10 @@ void VulkanEngine::init()
|
||||
auto imguiPass = std::make_unique<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);
|
||||
|
||||
_context->enableSSR = true;
|
||||
@@ -456,6 +481,10 @@ void VulkanEngine::set_window_mode(WindowMode mode, int display_index)
|
||||
if (_swapchainManager)
|
||||
{
|
||||
_swapchainManager->resize_swapchain(_window);
|
||||
if (_ui)
|
||||
{
|
||||
_ui->on_swapchain_recreated();
|
||||
}
|
||||
if (_swapchainManager->resize_requested)
|
||||
{
|
||||
resize_requested = true;
|
||||
@@ -804,6 +833,17 @@ void VulkanEngine::cleanup()
|
||||
//make sure the gpu has stopped doing its things
|
||||
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
|
||||
for (int i = 0; i < FRAME_OVERLAP; i++)
|
||||
{
|
||||
@@ -861,13 +901,6 @@ void VulkanEngine::cleanup()
|
||||
print_vma_stats(_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();
|
||||
print_vma_stats(_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 (_sceneManager && _mousePosPixels.x >= 0.0f && _mousePosPixels.y >= 0.0f)
|
||||
if (_picking)
|
||||
{
|
||||
RenderObject hoverObj{};
|
||||
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;
|
||||
}
|
||||
_picking->update_hover();
|
||||
}
|
||||
|
||||
// Compute desired internal render-target extent from logical extent + render scale.
|
||||
@@ -1124,69 +1135,12 @@ void VulkanEngine::draw()
|
||||
RGImageHandle hID = _renderGraph->import_id_buffer();
|
||||
geometry->register_graph(_renderGraph.get(), hGBufferPosition, hGBufferNormal, hGBufferAlbedo, hGBufferExtra, hID, hDepth);
|
||||
|
||||
// If ID-buffer picking is enabled and a pick was requested this frame,
|
||||
// add a small transfer pass to read back 1 pixel from the ID buffer.
|
||||
if (_useIdBufferPicking && _pendingPick.active && hID.valid() && _pickReadbackBuffer.buffer)
|
||||
if (_picking && _swapchainManager)
|
||||
{
|
||||
VkExtent2D swapExt = _swapchainManager->swapchainExtent();
|
||||
VkExtent2D drawExt = _drawExtent;
|
||||
|
||||
glm::vec2 logicalPos{};
|
||||
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;
|
||||
}
|
||||
_picking->register_id_buffer_readback(*_renderGraph,
|
||||
hID,
|
||||
_drawExtent,
|
||||
_swapchainManager->swapchainExtent());
|
||||
}
|
||||
}
|
||||
if (auto *lighting = _renderPassManager->getPass<LightingPass>())
|
||||
@@ -1323,7 +1277,11 @@ void VulkanEngine::run()
|
||||
while (SDL_PollEvent(&e) != 0)
|
||||
{
|
||||
//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)
|
||||
{
|
||||
switch (e.window.event)
|
||||
@@ -1341,119 +1299,40 @@ void VulkanEngine::run()
|
||||
resize_requested = true;
|
||||
_last_resize_event_ms = SDL_GetTicks();
|
||||
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:
|
||||
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),
|
||||
static_cast<float>(e.motion.y)};
|
||||
if (_dragState.buttonDown)
|
||||
_ui->process_event(e);
|
||||
}
|
||||
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;
|
||||
// Consider any motion as dragging for now; can add threshold if desired.
|
||||
_dragState.dragging = true;
|
||||
_sceneManager->getMainCamera().processSDLEvent(e);
|
||||
}
|
||||
}
|
||||
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)
|
||||
@@ -1468,6 +1347,10 @@ void VulkanEngine::run()
|
||||
if (now_ms - _last_resize_event_ms >= RESIZE_DEBOUNCE_MS)
|
||||
{
|
||||
_swapchainManager->resize_swapchain(_window);
|
||||
if (_ui)
|
||||
{
|
||||
_ui->on_swapchain_recreated();
|
||||
}
|
||||
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));
|
||||
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;
|
||||
_picking->begin_frame();
|
||||
}
|
||||
|
||||
get_current_frame()._deletionQueue.flush();
|
||||
@@ -1566,17 +1405,11 @@ void VulkanEngine::run()
|
||||
}
|
||||
get_current_frame()._frameDescriptors.clear_pools(_deviceManager->device());
|
||||
|
||||
|
||||
// imgui new frame
|
||||
ImGui_ImplVulkan_NewFrame();
|
||||
ImGui_ImplSDL2_NewFrame();
|
||||
|
||||
ImGui::NewFrame();
|
||||
|
||||
// Build the engine debug UI (tabs, inspectors, etc.).
|
||||
vk_engine_draw_debug_ui(this);
|
||||
|
||||
ImGui::Render();
|
||||
if (_ui)
|
||||
{
|
||||
_ui->begin_frame();
|
||||
_ui->end_frame();
|
||||
}
|
||||
draw();
|
||||
|
||||
auto end = std::chrono::system_clock::now();
|
||||
@@ -1602,12 +1435,6 @@ void VulkanEngine::init_frame_resources()
|
||||
{
|
||||
_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()
|
||||
|
||||
@@ -37,6 +37,8 @@
|
||||
#include "core/raytracing/raytracing.h"
|
||||
#include "core/assets/texture_cache.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,
|
||||
// semaphores, and transient descriptor pools in FrameResources.
|
||||
@@ -70,17 +72,21 @@ public:
|
||||
FullscreenExclusive = 2 // exclusive fullscreen (may change display mode)
|
||||
};
|
||||
|
||||
~VulkanEngine();
|
||||
|
||||
bool _isInitialized{false};
|
||||
int _frameNumber{0};
|
||||
|
||||
std::shared_ptr<DeviceManager> _deviceManager;
|
||||
std::unique_ptr<SwapchainManager> _swapchainManager;
|
||||
std::shared_ptr<ResourceManager> _resourceManager;
|
||||
std::unique_ptr<RenderPassManager> _renderPassManager;
|
||||
std::unique_ptr<SceneManager> _sceneManager;
|
||||
std::unique_ptr<PipelineManager> _pipelineManager;
|
||||
std::unique_ptr<AssetManager> _assetManager;
|
||||
std::unique_ptr<AsyncAssetLoader> _asyncLoader;
|
||||
std::shared_ptr<DeviceManager> _deviceManager;
|
||||
std::unique_ptr<SwapchainManager> _swapchainManager;
|
||||
std::shared_ptr<ResourceManager> _resourceManager;
|
||||
std::unique_ptr<RenderPassManager> _renderPassManager;
|
||||
std::unique_ptr<ImGuiSystem> _ui;
|
||||
std::unique_ptr<SceneManager> _sceneManager;
|
||||
std::unique_ptr<PickingSystem> _picking;
|
||||
std::unique_ptr<PipelineManager> _pipelineManager;
|
||||
std::unique_ptr<AssetManager> _assetManager;
|
||||
std::unique_ptr<AsyncAssetLoader> _asyncLoader;
|
||||
std::unique_ptr<RenderGraph> _renderGraph;
|
||||
std::unique_ptr<RayTracingManager> _rayManager;
|
||||
std::unique_ptr<TextureCache> _textureCache;
|
||||
@@ -91,6 +97,10 @@ public:
|
||||
WindowMode _windowMode{WindowMode::Windowed};
|
||||
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 &get_current_frame() { return _frames[_frameNumber % FRAME_OVERLAP]; };
|
||||
@@ -154,51 +164,11 @@ public:
|
||||
IBLPaths paths{};
|
||||
} _pendingIBLRequest;
|
||||
|
||||
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;
|
||||
} _lastPick;
|
||||
uint32_t _lastPickObjectID = 0;
|
||||
ImGuiSystem *ui() { return _ui.get(); }
|
||||
const ImGuiSystem *ui() const { return _ui.get(); }
|
||||
|
||||
struct PickRequest
|
||||
{
|
||||
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; }
|
||||
PickingSystem *picking() { return _picking.get(); }
|
||||
const PickingSystem *picking() const { return _picking.get(); }
|
||||
|
||||
// Debug: persistent pass enable overrides (by pass name)
|
||||
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().
|
||||
|
||||
#include "engine.h"
|
||||
#include "core/picking/picking_system.h"
|
||||
|
||||
#include "SDL2/SDL.h"
|
||||
#include "SDL2/SDL_vulkan.h"
|
||||
|
||||
#include "imgui.h"
|
||||
#include "ImGuizmo.h"
|
||||
@@ -110,6 +112,75 @@ namespace
|
||||
pending_display = current_display;
|
||||
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
|
||||
@@ -962,10 +1033,27 @@ namespace
|
||||
const DrawContext &dc = eng->_context->getMainDrawContext();
|
||||
ImGui::Text("Opaque draws: %zu", dc.OpaqueSurfaces.size());
|
||||
ImGui::Text("Transp draws: %zu", dc.TransparentSurfaces.size());
|
||||
ImGui::Checkbox("Use ID-buffer picking", &eng->_useIdBufferPicking);
|
||||
ImGui::Text("Picking mode: %s",
|
||||
eng->_useIdBufferPicking ? "ID buffer (async, 1-frame latency)" : "CPU raycast");
|
||||
ImGui::Checkbox("Debug draw mesh BVH (last pick)", &eng->_debugDrawBVH);
|
||||
PickingSystem *picking = eng->picking();
|
||||
if (picking)
|
||||
{
|
||||
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();
|
||||
|
||||
// Spawn glTF instances (runtime)
|
||||
@@ -1119,8 +1207,13 @@ namespace
|
||||
if (ImGui::Button("Delete selected"))
|
||||
{
|
||||
deleteStatus.clear();
|
||||
const auto *pick = eng->_lastPick.valid ? &eng->_lastPick
|
||||
: (eng->_hoverPick.valid ? &eng->_hoverPick : nullptr);
|
||||
const PickingSystem::PickInfo *pick = 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())
|
||||
{
|
||||
deleteStatus = "No selection to delete.";
|
||||
@@ -1128,6 +1221,10 @@ namespace
|
||||
else if (pick->ownerType == RenderObject::OwnerType::MeshInstance)
|
||||
{
|
||||
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
|
||||
: "Mesh instance not found: " + pick->ownerName;
|
||||
}
|
||||
@@ -1137,35 +1234,9 @@ namespace
|
||||
if (ok)
|
||||
{
|
||||
deleteStatus = "Removed glTF instance: " + pick->ownerName;
|
||||
|
||||
// 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)
|
||||
if (picking)
|
||||
{
|
||||
fmt::println("[Debug] Clearing _lastPick for deleted GLTF instance '{}'", 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;
|
||||
picking->clear_owner_picks(RenderObject::OwnerType::GLTFInstance, pick->ownerName);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -1184,36 +1255,37 @@ namespace
|
||||
}
|
||||
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>";
|
||||
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 source: %s",
|
||||
eng->_useIdBufferPicking ? "ID buffer" : "CPU raycast");
|
||||
ImGui::Text("Last pick object ID: %u", eng->_lastPickObjectID);
|
||||
ImGui::Text("Last pick mesh: %s (surface %u)", meshName, eng->_lastPick.surfaceIndex);
|
||||
picking->use_id_buffer_picking() ? "ID buffer" : "CPU raycast");
|
||||
ImGui::Text("Last pick object ID: %u", picking->last_pick_object_id());
|
||||
ImGui::Text("Last pick mesh: %s (surface %u)", meshName, last.surfaceIndex);
|
||||
ImGui::Text("World pos: (%.3f, %.3f, %.3f)",
|
||||
eng->_lastPick.worldPos.x,
|
||||
eng->_lastPick.worldPos.y,
|
||||
eng->_lastPick.worldPos.z);
|
||||
last.worldPos.x,
|
||||
last.worldPos.y,
|
||||
last.worldPos.z);
|
||||
const char *ownerTypeStr = "none";
|
||||
switch (eng->_lastPick.ownerType)
|
||||
switch (last.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();
|
||||
const char *ownerName = last.ownerName.empty() ? "<unnamed>" : last.ownerName.c_str();
|
||||
ImGui::Text("Owner: %s (%s)", ownerName, ownerTypeStr);
|
||||
ImGui::Text("Indices: first=%u count=%u",
|
||||
eng->_lastPick.firstIndex,
|
||||
eng->_lastPick.indexCount);
|
||||
last.firstIndex,
|
||||
last.indexCount);
|
||||
|
||||
if (eng->_sceneManager)
|
||||
{
|
||||
@@ -1235,28 +1307,29 @@ namespace
|
||||
ImGui::TextUnformatted("Last pick: <none>");
|
||||
}
|
||||
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>";
|
||||
ImGui::Text("Hover mesh: %s (surface %u)", meshName, eng->_hoverPick.surfaceIndex);
|
||||
const auto &hover = picking->hover_pick();
|
||||
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";
|
||||
switch (eng->_hoverPick.ownerType)
|
||||
switch (hover.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();
|
||||
const char *ownerName = hover.ownerName.empty() ? "<unnamed>" : hover.ownerName.c_str();
|
||||
ImGui::Text("Hover owner: %s (%s)", ownerName, ownerTypeStr);
|
||||
}
|
||||
else
|
||||
{
|
||||
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();
|
||||
@@ -1271,14 +1344,17 @@ namespace
|
||||
SceneManager *sceneMgr = eng->_sceneManager.get();
|
||||
|
||||
// Choose a pick to edit: prefer last pick, then hover.
|
||||
VulkanEngine::PickInfo *pick = nullptr;
|
||||
if (eng->_lastPick.valid)
|
||||
PickingSystem::PickInfo *pick = nullptr;
|
||||
if (picking)
|
||||
{
|
||||
pick = &eng->_lastPick;
|
||||
}
|
||||
else if (eng->_hoverPick.valid)
|
||||
{
|
||||
pick = &eng->_hoverPick;
|
||||
if (picking->last_pick().valid)
|
||||
{
|
||||
pick = picking->mutable_last_pick();
|
||||
}
|
||||
else if (picking->hover_pick().valid)
|
||||
{
|
||||
pick = picking->mutable_hover_pick();
|
||||
}
|
||||
}
|
||||
|
||||
if (!pick || pick->ownerName.empty())
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "render/passes/tonemap.h"
|
||||
#include "render/passes/fxaa.h"
|
||||
#include "render/renderpass.h"
|
||||
#include "core/picking/picking_system.h"
|
||||
#include "scene/vk_scene.h"
|
||||
#include "scene/camera.h"
|
||||
|
||||
@@ -1160,29 +1161,44 @@ Stats Engine::get_stats() const
|
||||
Engine::PickResult Engine::get_last_pick() const
|
||||
{
|
||||
PickResult r;
|
||||
r.valid = _engine->_lastPick.valid;
|
||||
r.ownerName = _engine->_lastPick.ownerName;
|
||||
r.worldPosition = glm::vec3(_engine->_lastPick.worldPos);
|
||||
const PickingSystem *picking = _engine ? _engine->picking() : nullptr;
|
||||
if (picking)
|
||||
{
|
||||
const auto &pick = picking->last_pick();
|
||||
r.valid = pick.valid;
|
||||
r.ownerName = pick.ownerName;
|
||||
r.worldPosition = glm::vec3(pick.worldPos);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
Engine::PickResultD Engine::get_last_pick_d() const
|
||||
{
|
||||
PickResultD r;
|
||||
r.valid = _engine->_lastPick.valid;
|
||||
r.ownerName = _engine->_lastPick.ownerName;
|
||||
r.worldPosition = _engine->_lastPick.worldPos;
|
||||
const PickingSystem *picking = _engine ? _engine->picking() : nullptr;
|
||||
if (picking)
|
||||
{
|
||||
const auto &pick = picking->last_pick();
|
||||
r.valid = pick.valid;
|
||||
r.ownerName = pick.ownerName;
|
||||
r.worldPosition = pick.worldPos;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
return _engine->_useIdBufferPicking;
|
||||
const PickingSystem *picking = _engine ? _engine->picking() : nullptr;
|
||||
if (!picking) return false;
|
||||
return picking->use_id_buffer_picking();
|
||||
}
|
||||
|
||||
} // 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.h"
|
||||
#include "imgui_impl_sdl2.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 "render/graph/graph.h"
|
||||
|
||||
void ImGuiPass::init(EngineContext *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()
|
||||
{
|
||||
fmt::print("ImGuiPass::cleanup()\n");
|
||||
_deletionQueue.flush();
|
||||
}
|
||||
|
||||
void ImGuiPass::execute(VkCommandBuffer)
|
||||
|
||||
@@ -25,5 +25,4 @@ private:
|
||||
const class RGPassResources &resources,
|
||||
RGImageHandle targetHandle) const;
|
||||
|
||||
DeletionQueue _deletionQueue;
|
||||
};
|
||||
|
||||
@@ -434,7 +434,7 @@ bool SceneManager::pick(const glm::vec2 &mousePosPixels, RenderObject &outObject
|
||||
return false;
|
||||
}
|
||||
|
||||
VkExtent2D dstExtent = swapchain->windowExtent();
|
||||
VkExtent2D dstExtent = swapchain->swapchainExtent();
|
||||
if (dstExtent.width == 0 || dstExtent.height == 0)
|
||||
{
|
||||
return false;
|
||||
@@ -584,7 +584,7 @@ void SceneManager::selectRect(const glm::vec2 &p0, const glm::vec2 &p1, std::vec
|
||||
}
|
||||
|
||||
SwapchainManager *swapchain = _context->getSwapchain();
|
||||
VkExtent2D dstExtent = swapchain->windowExtent();
|
||||
VkExtent2D dstExtent = swapchain->swapchainExtent();
|
||||
if (dstExtent.width == 0 || dstExtent.height == 0)
|
||||
{
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user