Files
Destrum/destrum/src/Graphics/Pipelines/MeshPipeline.cpp
T
2026-10-23 20:53:56 +02:00

266 lines
7.6 KiB
C++

#include <destrum/Graphics/Pipelines/MeshPipeline.h>
#include <array>
#include <cassert>
#include <stdexcept>
#include <destrum/FS/AssetFS.h>
#include <destrum/Graphics/GfxDevice.h>
#include <destrum/Graphics/RenderResources.h>
#include <destrum/Graphics/Caches/MeshCache.h>
#include <destrum/Graphics/Util.h>
#include "volk.h"
#include "spdlog/spdlog.h"
MeshPipeline::MeshPipeline() = default;
MeshPipeline::~MeshPipeline() = default;
void MeshPipeline::init(
GfxDevice& gfxDevice,
RenderResources& resources,
VkFormat drawImageFormat,
VkFormat depthImageFormat,
VkFormat selectionImageFormat)
{
try
{
const auto vertexShader =
AssetFS::GetInstance().GetCookedPathForFile("engine://shaders/mesh.vert");
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,
.size = sizeof(PushConstants),
};
const auto pushConstantRanges = std::array{bufferRange};
const auto layouts = std::array{
resources.getBindlessDescSetLayout()
};
VkPipelineLayoutCreateInfo pipelineLayoutInfo{};
pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
pipelineLayoutInfo.setLayoutCount = static_cast<std::uint32_t>(layouts.size());
pipelineLayoutInfo.pSetLayouts = layouts.data();
pipelineLayoutInfo.pushConstantRangeCount =
static_cast<std::uint32_t>(pushConstantRanges.size());
pipelineLayoutInfo.pPushConstantRanges = pushConstantRanges.data();
if (vkCreatePipelineLayout(
gfxDevice.getDevice(),
&pipelineLayoutInfo,
nullptr,
&m_pipelineLayout) != VK_SUCCESS)
{
throw std::runtime_error("Could not create mesh pipeline layout");
}
{
PipelineConfigInfo pipelineConfig{};
Pipeline::DefaultPipelineConfigInfo(pipelineConfig);
pipelineConfig.name = "Mesh Depth Prepass Pipeline";
pipelineConfig.pipelineLayout = m_pipelineLayout;
pipelineConfig.vertexAttributeDescriptions = {};
pipelineConfig.vertexBindingDescriptions = {};
pipelineConfig.colorAttachments = {};
pipelineConfig.colorBlendInfo.attachmentCount = 0;
pipelineConfig.depthAttachment = depthImageFormat;
m_depthPrepassPipeline = std::make_unique<Pipeline>(
gfxDevice,
vertexShader.string(),
"",
pipelineConfig
);
}
{
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);
pipelineConfig.name = "Mesh Pipeline";
pipelineConfig.pipelineLayout = m_pipelineLayout;
pipelineConfig.vertexAttributeDescriptions = {};
pipelineConfig.vertexBindingDescriptions = {};
pipelineConfig.colorAttachments = {drawImageFormat};
pipelineConfig.depthAttachment = depthImageFormat;
// depth was already written by the pre-pass, only shade the visible surface
pipelineConfig.depthStencilInfo.depthWriteEnable = VK_FALSE;
pipelineConfig.depthStencilInfo.depthCompareOp = VK_COMPARE_OP_LESS_OR_EQUAL;
m_pipeline = std::make_unique<Pipeline>(
gfxDevice,
vertexShader.string(),
fragShader.string(),
pipelineConfig
);
}
}
catch (...)
{
cleanup(gfxDevice.getDevice());
throw;
}
}
void MeshPipeline::drawDepthPrepass(
VkCommandBuffer cmd,
VkExtent2D renderExtent,
const RenderResources& resources,
const GPUBuffer& sceneDataBuffer,
const IndirectDrawList& drawList,
const IndirectDrawList::View& view,
bool writeObjectIds)
{
const auto& pipeline = writeObjectIds ? m_selectionPrepassPipeline : m_depthPrepassPipeline;
if (!pipeline)
{
return;
}
drawMeshes(
cmd,
*pipeline,
renderExtent,
resources,
sceneDataBuffer,
drawList,
view);
}
void MeshPipeline::draw(
VkCommandBuffer cmd,
VkExtent2D renderExtent,
const RenderResources& resources,
const GPUBuffer& sceneDataBuffer,
const IndirectDrawList& drawList,
const IndirectDrawList::View& view)
{
if (!m_pipeline)
{
return;
}
drawMeshes(
cmd,
*m_pipeline,
renderExtent,
resources,
sceneDataBuffer,
drawList,
view);
}
void MeshPipeline::drawMeshes(
VkCommandBuffer cmd,
const Pipeline& pipeline,
VkExtent2D renderExtent,
const RenderResources& resources,
const GPUBuffer& sceneDataBuffer,
const IndirectDrawList& drawList,
const IndirectDrawList::View& view)
{
pipeline.bind(cmd);
resources.bindBindlessDescSet(cmd, m_pipelineLayout);
const auto viewport = VkViewport{
.x = 0.f,
.y = 0.f,
.width = static_cast<float>(renderExtent.width),
.height = static_cast<float>(renderExtent.height),
.minDepth = 0.f,
.maxDepth = 1.f,
};
vkCmdSetViewport(cmd, 0, 1, &viewport);
const auto scissor = VkRect2D{
.offset = {},
.extent = renderExtent,
};
vkCmdSetScissor(cmd, 0, 1, &scissor);
vkCmdSetPolygonModeEXT(
cmd,
m_renderWireframe ? VK_POLYGON_MODE_LINE : VK_POLYGON_MODE_FILL
);
// The same for every draw, per-draw data (transform, material, vertex buffer)
// lives in the instance buffer and is looked up with gl_InstanceIndex
const auto pushConstants = PushConstants{
.sceneDataBuffer = sceneDataBuffer.address,
.instanceBuffer = drawList.getInstanceBufferAddress(),
};
vkCmdPushConstants(
cmd,
m_pipelineLayout,
VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT,
0,
sizeof(PushConstants),
&pushConstants
);
vkutil::cmdBeginLabelFormatted(
cmd,
vkutil::labelColors::draw,
"%u indirect draws, %u instances",
view.commandCount,
view.instanceCount);
drawList.draw(cmd, resources.meshes().getIndexBuffer(), view);
vkutil::cmdEndLabel(cmd);
}
void MeshPipeline::cleanup(VkDevice device)
{
m_pipeline.reset();
m_depthPrepassPipeline.reset();
m_selectionPrepassPipeline.reset();
if (m_pipelineLayout != VK_NULL_HANDLE && device != VK_NULL_HANDLE)
{
vkDestroyPipelineLayout(device, m_pipelineLayout, nullptr);
}
m_pipelineLayout = VK_NULL_HANDLE;
}