feat: add click to select
This commit is contained in:
@@ -8,6 +8,7 @@
|
||||
#include <destrum/FS/AssetFS.h>
|
||||
#include <destrum/Graphics/Resources/FontCache.h>
|
||||
#include <destrum/ObjectModel/GameObject.h>
|
||||
#include <destrum/ObjectModel/Transform.h>
|
||||
#include <destrum/Scene/Scene.h>
|
||||
#include <destrum/Scene/SceneManager.h>
|
||||
#include <destrum/Serialization/SceneSerializer.h>
|
||||
@@ -234,6 +235,7 @@ if (!isRunning) break;
|
||||
{
|
||||
ZoneScopedN("Debug ImGui");
|
||||
drawWindowsMenu();
|
||||
applySelectionPick();
|
||||
drawViewportWindow();
|
||||
|
||||
if (m_DebugWindows.inspector)
|
||||
@@ -380,6 +382,7 @@ if (!isRunning) break;
|
||||
|
||||
{
|
||||
ZoneScopedN("customDraw");
|
||||
renderer.setSelectionPassEnabled(canPickInViewport());
|
||||
customDraw();
|
||||
}
|
||||
|
||||
@@ -549,10 +552,52 @@ void App::drawViewportWindow()
|
||||
|
||||
ImGui::SetCursorScreenPos(origin);
|
||||
drawViewportOverlay();
|
||||
requestSelectionPick();
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void App::requestSelectionPick()
|
||||
{
|
||||
if (!canPickInViewport() || !viewportHovered || viewportMouseClaimed ||
|
||||
!ImGui::IsMouseClicked(ImGuiMouseButton_Left))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const ImVec2 mouse = ImGui::GetMousePos();
|
||||
const glm::vec2 uv = (glm::vec2{mouse.x, mouse.y} - viewportRect.min) / viewportRect.size;
|
||||
if (uv.x < 0.0f || uv.y < 0.0f || uv.x >= 1.0f || uv.y >= 1.0f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const VkExtent3D extent = renderer.getDrawImage().extent;
|
||||
renderer.requestSelectionPick(glm::ivec2{uv * glm::vec2{static_cast<float>(extent.width), static_cast<float>(extent.height)}});
|
||||
pickSelectsParent = ImGui::GetIO().KeyAlt;
|
||||
}
|
||||
|
||||
void App::applySelectionPick()
|
||||
{
|
||||
const auto picked = renderer.takeSelectionPick();
|
||||
auto& sceneManager = SceneManager::GetInstance();
|
||||
if (!picked || sceneManager.GetActiveScenes().empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Scene& scene = sceneManager.GetCurrentScene();
|
||||
GameObject* object = scene.FindGameObject(*picked);
|
||||
if (object != nullptr && pickSelectsParent)
|
||||
{
|
||||
if (const Transform* parent = object->GetTransform().GetParent(); parent != nullptr && parent->GetOwner() != nullptr)
|
||||
{
|
||||
object = parent->GetOwner();
|
||||
}
|
||||
}
|
||||
scene.SetSelectedObject(object != nullptr ? object->GetId() : InvalidObjectId);
|
||||
}
|
||||
|
||||
void App::applyViewportResize()
|
||||
{
|
||||
if (viewportDesiredSize.x <= 0 || viewportDesiredSize.y <= 0)
|
||||
|
||||
@@ -216,9 +216,11 @@ void MeshRendererComponent::Render(const RenderContext& ctx) {
|
||||
GetTransform().GetWorldMatrix(),
|
||||
materialID,
|
||||
m_skinnedMesh.get(),
|
||||
jointMatricesStartIndex
|
||||
jointMatricesStartIndex,
|
||||
static_cast<std::uint32_t>(GetGameObject()->GetId())
|
||||
);
|
||||
} else {
|
||||
ctx.renderer.drawMesh(meshID, GetTransform().GetWorldMatrix(), materialID);
|
||||
ctx.renderer.drawMesh(meshID, GetTransform().GetWorldMatrix(), materialID,
|
||||
static_cast<std::uint32_t>(GetGameObject()->GetId()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ IndirectDrawList::View IndirectDrawList::addView(
|
||||
? dc.skinnedMesh->skinnedVertexBuffer.address
|
||||
: mesh.vertexBuffer.address,
|
||||
.materialId = dc.materialId,
|
||||
.padding = 0,
|
||||
.objectId = dc.objectId,
|
||||
});
|
||||
++commands.back().instanceCount;
|
||||
++view.instanceCount;
|
||||
|
||||
@@ -113,3 +113,15 @@ void MemoryManager::flushAllocation(
|
||||
|
||||
VK_CHECK(vmaFlushAllocation(allocator, buffer.allocation, offset, size));
|
||||
}
|
||||
|
||||
void MemoryManager::invalidateAllocation(
|
||||
const GPUBuffer& buffer,
|
||||
VkDeviceSize offset,
|
||||
VkDeviceSize size) const
|
||||
{
|
||||
if (!buffer.hostVisible || buffer.allocation == VK_NULL_HANDLE) {
|
||||
return;
|
||||
}
|
||||
|
||||
VK_CHECK(vmaInvalidateAllocation(allocator, buffer.allocation, offset, size));
|
||||
}
|
||||
|
||||
@@ -21,7 +21,8 @@ void MeshPipeline::init(
|
||||
GfxDevice& gfxDevice,
|
||||
RenderResources& resources,
|
||||
VkFormat drawImageFormat,
|
||||
VkFormat depthImageFormat)
|
||||
VkFormat depthImageFormat,
|
||||
VkFormat selectionImageFormat)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -31,6 +32,9 @@ void MeshPipeline::init(
|
||||
const auto fragShader =
|
||||
AssetFS::GetInstance().GetCookedPathForFile("engine://shaders/mesh.frag");
|
||||
|
||||
const auto selectionShader =
|
||||
AssetFS::GetInstance().GetCookedPathForFile("engine://shaders/selection.frag");
|
||||
|
||||
const auto bufferRange = VkPushConstantRange{
|
||||
.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT,
|
||||
.offset = 0,
|
||||
@@ -82,6 +86,27 @@ void MeshPipeline::init(
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
PipelineConfigInfo pipelineConfig{};
|
||||
Pipeline::DefaultPipelineConfigInfo(pipelineConfig);
|
||||
|
||||
pipelineConfig.name = "Mesh Selection Prepass Pipeline";
|
||||
pipelineConfig.pipelineLayout = m_pipelineLayout;
|
||||
|
||||
pipelineConfig.vertexAttributeDescriptions = {};
|
||||
pipelineConfig.vertexBindingDescriptions = {};
|
||||
|
||||
pipelineConfig.colorAttachments = {selectionImageFormat};
|
||||
pipelineConfig.depthAttachment = depthImageFormat;
|
||||
|
||||
m_selectionPrepassPipeline = std::make_unique<Pipeline>(
|
||||
gfxDevice,
|
||||
vertexShader.string(),
|
||||
selectionShader.string(),
|
||||
pipelineConfig
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
PipelineConfigInfo pipelineConfig{};
|
||||
Pipeline::DefaultPipelineConfigInfo(pipelineConfig);
|
||||
@@ -120,16 +145,18 @@ void MeshPipeline::drawDepthPrepass(
|
||||
const RenderResources& resources,
|
||||
const GPUBuffer& sceneDataBuffer,
|
||||
const IndirectDrawList& drawList,
|
||||
const IndirectDrawList::View& view)
|
||||
const IndirectDrawList::View& view,
|
||||
bool writeObjectIds)
|
||||
{
|
||||
if (!m_depthPrepassPipeline)
|
||||
const auto& pipeline = writeObjectIds ? m_selectionPrepassPipeline : m_depthPrepassPipeline;
|
||||
if (!pipeline)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
drawMeshes(
|
||||
cmd,
|
||||
*m_depthPrepassPipeline,
|
||||
*pipeline,
|
||||
renderExtent,
|
||||
resources,
|
||||
sceneDataBuffer,
|
||||
@@ -228,6 +255,7 @@ void MeshPipeline::cleanup(VkDevice device)
|
||||
{
|
||||
m_pipeline.reset();
|
||||
m_depthPrepassPipeline.reset();
|
||||
m_selectionPrepassPipeline.reset();
|
||||
|
||||
if (m_pipelineLayout != VK_NULL_HANDLE && device != VK_NULL_HANDLE)
|
||||
{
|
||||
|
||||
@@ -33,7 +33,15 @@ void GameRenderer::init(GfxDevice& gfxDevice, RenderResources& _resources, glm::
|
||||
createDrawImage(gfxDevice, drawImageSize, true);
|
||||
|
||||
meshPipeline = std::make_unique<MeshPipeline>();
|
||||
meshPipeline->init(gfxDevice, _resources, drawImageFormat, depthImageFormat);
|
||||
meshPipeline->init(gfxDevice, _resources, drawImageFormat, depthImageFormat, selectionImageFormat);
|
||||
|
||||
for (GPUBuffer& pickBuffer : pickBuffers)
|
||||
{
|
||||
pickBuffer = gfxDevice.createBuffer(
|
||||
sizeof(std::uint32_t),
|
||||
VK_BUFFER_USAGE_TRANSFER_DST_BIT,
|
||||
VMA_MEMORY_USAGE_GPU_TO_CPU);
|
||||
}
|
||||
|
||||
skyboxPipeline = std::make_unique<SkyboxPipeline>();
|
||||
skyboxPipeline->init(gfxDevice, _resources, drawImageFormat, depthImageFormat);
|
||||
@@ -65,6 +73,15 @@ void GameRenderer::init(GfxDevice& gfxDevice, RenderResources& _resources, glm::
|
||||
|
||||
void GameRenderer::beginDrawing(GfxDevice& gfxDevice)
|
||||
{
|
||||
// This frame's fence was waited on, so a pick copied by its last use has landed
|
||||
const std::uint32_t frameIndex = gfxDevice.getCurrentFrameIndex();
|
||||
if (pickPending[frameIndex])
|
||||
{
|
||||
pickPending[frameIndex] = false;
|
||||
gfxDevice.getMemoryManager().invalidateAllocation(pickBuffers[frameIndex]);
|
||||
pickResult = *static_cast<const std::uint32_t*>(pickBuffers[frameIndex].info.pMappedData);
|
||||
}
|
||||
|
||||
meshDrawCommands.clear();
|
||||
skinningPipeline->beginDrawing(gfxDevice.getCurrentFrameIndex());
|
||||
lightCullingPipeline->beginDrawing(gfxDevice.getCurrentFrameIndex());
|
||||
@@ -207,7 +224,7 @@ void GameRenderer::draw(VkCommandBuffer cmd, GfxDevice& gfxDevice, const Camera&
|
||||
.debugLightHeatmap = renderLightHeatmap ? 1u : 0u,
|
||||
.shadowMapId = static_cast<std::uint32_t>(shadowMapPipeline->getShadowMapId()),
|
||||
.shadowNormalOffset = shadowMapPipeline->getNormalOffset(),
|
||||
.padding = 0,
|
||||
.debugSelectionView = renderSelectionView ? 1u : 0u,
|
||||
.lightViewProj = lightViewProj,
|
||||
.spotShadowAtlasId = static_cast<std::uint32_t>(shadowMapPipeline->getSpotShadowAtlasId()),
|
||||
.spotShadowTilesPerRow = ShadowMapPipeline::SPOT_SHADOW_TILES_PER_ROW,
|
||||
@@ -288,12 +305,29 @@ void GameRenderer::draw(VkCommandBuffer cmd, GfxDevice& gfxDevice, const Camera&
|
||||
TracyVkZone(gfxDevice.getTracyVkCtx(), cmd, "Depth Prepass");
|
||||
vkutil::ScopedCmdLabel label(cmd, "Depth Prepass", vkutil::labelColors::depth);
|
||||
|
||||
const auto& selectionImage = resources->getImage(selectionImageId);
|
||||
if (selectionPassEnabled)
|
||||
{
|
||||
vkutil::transitionImage(
|
||||
cmd,
|
||||
selectionImage.image,
|
||||
selectionImage.layout,
|
||||
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
|
||||
selectionImage.setLayout(VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
|
||||
}
|
||||
|
||||
auto depthPrepassInfo = vkutil::createRenderingInfo({
|
||||
.renderExtent = drawImage.getExtent2D(),
|
||||
.colorImageView = selectionPassEnabled ? selectionImage.imageView : VK_NULL_HANDLE,
|
||||
.colorImageClearValue = glm::vec4{0.f},
|
||||
.depthImageView = depthImage.imageView,
|
||||
.depthImageClearValue = 1.f,
|
||||
});
|
||||
depthPrepassInfo.renderingInfo.pDepthAttachment = &depthPrepassInfo.depthAttachment;
|
||||
if (selectionPassEnabled)
|
||||
{
|
||||
depthPrepassInfo.renderingInfo.pColorAttachments = &depthPrepassInfo.colorAttachment;
|
||||
}
|
||||
|
||||
vkCmdBeginRendering(cmd, &depthPrepassInfo.renderingInfo);
|
||||
|
||||
@@ -303,9 +337,16 @@ void GameRenderer::draw(VkCommandBuffer cmd, GfxDevice& gfxDevice, const Camera&
|
||||
*resources,
|
||||
sceneDataBuffer.getBuffer(),
|
||||
drawList,
|
||||
mainView);
|
||||
mainView,
|
||||
selectionPassEnabled);
|
||||
|
||||
vkCmdEndRendering(cmd);
|
||||
|
||||
if (selectionPassEnabled && requestedPick)
|
||||
{
|
||||
recordSelectionPick(cmd, gfxDevice.getCurrentFrameIndex());
|
||||
}
|
||||
requestedPick.reset();
|
||||
}
|
||||
|
||||
{
|
||||
@@ -404,6 +445,51 @@ void GameRenderer::draw(VkCommandBuffer cmd, GfxDevice& gfxDevice, const Camera&
|
||||
}
|
||||
}
|
||||
|
||||
void GameRenderer::recordSelectionPick(VkCommandBuffer cmd, std::uint32_t frameIndex)
|
||||
{
|
||||
vkutil::ScopedCmdLabel label(cmd, "Selection Pick", vkutil::labelColors::transfer);
|
||||
|
||||
const auto& image = resources->getImage(selectionImageId);
|
||||
const VkExtent2D extent = image.getExtent2D();
|
||||
const glm::ivec2 pixel = glm::clamp(
|
||||
*requestedPick,
|
||||
glm::ivec2{0},
|
||||
glm::ivec2{static_cast<int>(extent.width) - 1, static_cast<int>(extent.height) - 1});
|
||||
|
||||
vkutil::transitionImage(cmd, image.image, image.layout, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
|
||||
image.setLayout(VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
|
||||
|
||||
const VkBufferImageCopy region{
|
||||
.bufferOffset = 0,
|
||||
.imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1},
|
||||
.imageOffset = {pixel.x, pixel.y, 0},
|
||||
.imageExtent = {1, 1, 1},
|
||||
};
|
||||
vkCmdCopyImageToBuffer(
|
||||
cmd,
|
||||
image.image,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
pickBuffers[frameIndex].buffer,
|
||||
1,
|
||||
®ion);
|
||||
|
||||
const VkMemoryBarrier2 toHost{
|
||||
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2,
|
||||
.srcStageMask = VK_PIPELINE_STAGE_2_COPY_BIT,
|
||||
.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT,
|
||||
.dstStageMask = VK_PIPELINE_STAGE_2_HOST_BIT,
|
||||
.dstAccessMask = VK_ACCESS_2_HOST_READ_BIT,
|
||||
};
|
||||
const VkDependencyInfo dependency{
|
||||
.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO,
|
||||
.memoryBarrierCount = 1,
|
||||
.pMemoryBarriers = &toHost,
|
||||
};
|
||||
vkCmdPipelineBarrier2(cmd, &dependency);
|
||||
|
||||
pickPending[frameIndex] = true;
|
||||
}
|
||||
|
||||
void GameRenderer::draw2D(
|
||||
VkCommandBuffer cmd,
|
||||
GfxDevice& gfxDevice,
|
||||
@@ -440,6 +526,13 @@ void GameRenderer::cleanup(GfxDevice& gfxDevice)
|
||||
|
||||
sceneDataBuffer.cleanup(gfxDevice);
|
||||
drawList.cleanup(gfxDevice);
|
||||
for (GPUBuffer& pickBuffer : pickBuffers)
|
||||
{
|
||||
gfxDevice.destroyBuffer(pickBuffer);
|
||||
}
|
||||
pickPending = {};
|
||||
pickResult.reset();
|
||||
requestedPick.reset();
|
||||
|
||||
// if (drawImageId != NULL_IMAGE_ID)
|
||||
// gfxDevice.destroyImage();
|
||||
@@ -449,6 +542,7 @@ void GameRenderer::cleanup(GfxDevice& gfxDevice)
|
||||
|
||||
drawImageId = NULL_IMAGE_ID;
|
||||
depthImageId = NULL_IMAGE_ID;
|
||||
selectionImageId = NULL_IMAGE_ID;
|
||||
resources = nullptr;
|
||||
meshPipeline.reset();
|
||||
skyboxPipeline.reset();
|
||||
@@ -467,7 +561,7 @@ void GameRenderer::cleanup(GfxDevice& gfxDevice)
|
||||
GameState::GetInstance().SetRenderer(nullptr);
|
||||
}
|
||||
|
||||
void GameRenderer::drawMesh(MeshID id, const glm::mat4& transform, MaterialID materialId)
|
||||
void GameRenderer::drawMesh(MeshID id, const glm::mat4& transform, MaterialID materialId, std::uint32_t objectId)
|
||||
{
|
||||
const auto& mesh = resources->meshes().getMesh(id);
|
||||
const auto worldBoundingSphere = edge::calculateBoundingSphereWorld(transform, mesh.boundingSphere, false);
|
||||
@@ -478,6 +572,7 @@ void GameRenderer::drawMesh(MeshID id, const glm::mat4& transform, MaterialID ma
|
||||
.transformMatrix = transform,
|
||||
.worldBoundingSphere = worldBoundingSphere,
|
||||
.materialId = materialId,
|
||||
.objectId = objectId,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -485,7 +580,8 @@ void GameRenderer::drawSkinnedMesh(MeshID id,
|
||||
const glm::mat4& transform,
|
||||
MaterialID materialId,
|
||||
SkinnedMesh* skinnedMesh,
|
||||
std::size_t jointMatricesStartIndex)
|
||||
std::size_t jointMatricesStartIndex,
|
||||
std::uint32_t objectId)
|
||||
{
|
||||
const auto& mesh = resources->meshes().getMesh(id);
|
||||
const auto worldBoundingSphere = edge::calculateBoundingSphereWorld(transform, mesh.boundingSphere, true);
|
||||
@@ -499,6 +595,7 @@ void GameRenderer::drawSkinnedMesh(MeshID id,
|
||||
.materialId = materialId,
|
||||
.skinnedMesh = skinnedMesh,
|
||||
.jointMatricesStartIndex = static_cast<std::uint32_t>(jointMatricesStartIndex),
|
||||
.objectId = objectId,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -650,4 +747,16 @@ void GameRenderer::createDrawImage(GfxDevice& gfxDevice,
|
||||
depthImageId = resources->createImage(gfxDevice, createInfo, "depth image");
|
||||
spdlog::info("Created depth image with id {}", depthImageId);
|
||||
}
|
||||
|
||||
{
|
||||
auto createInfo = vkutil::CreateImageInfo{
|
||||
.format = selectionImageFormat,
|
||||
.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
|
||||
.extent = drawImageExtent,
|
||||
.samples = noMsaa,
|
||||
};
|
||||
|
||||
resources->destroyImage(selectionImageId);
|
||||
selectionImageId = resources->createImage(gfxDevice, createInfo, "selection image");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user