feat: add forward+ rendering

This commit is contained in:
2026-10-11 00:12:23 +02:00
parent c96b2282fb
commit 4bf87bd91b
26 changed files with 1346 additions and 191 deletions
+126
View File
@@ -0,0 +1,126 @@
#include <destrum/Components/LightComponent.h>
#include <destrum/Graphics/RenderContext.h>
#include <destrum/ObjectModel/Transform.h>
#include <glm/gtc/constants.hpp>
#include <glm/gtc/quaternion.hpp>
#include <glm/gtx/quaternion.hpp>
#include <algorithm>
#include <array>
#include <imgui.h>
LightComponent::LightComponent(GameObject& parent, LightType type)
: Component(parent, "LightComponent")
, m_Type(type)
{
}
void LightComponent::SetIntensity(float intensity)
{
m_Intensity = std::max(intensity, 0.0f);
}
void LightComponent::SetRange(float range)
{
m_Range = std::max(range, 0.01f);
}
void LightComponent::SetConeAngles(float innerConeAngle, float outerConeAngle)
{
m_InnerConeAngle = std::clamp(innerConeAngle, 0.0f, glm::half_pi<float>());
m_OuterConeAngle = std::clamp(outerConeAngle, m_InnerConeAngle, glm::half_pi<float>());
}
void LightComponent::Render(const RenderContext& ctx)
{
auto& transform = GetTransform();
ctx.renderer.submitLight(Light{
.type = m_Type,
.position = transform.GetWorldPosition(),
.direction = transform.GetWorldRotation() * glm::vec3{0.0f, 0.0f, -1.0f},
.color = m_Color,
.intensity = m_Intensity,
.range = m_Range,
.innerConeAngle = m_InnerConeAngle,
.outerConeAngle = m_OuterConeAngle,
});
}
nlohmann::json LightComponent::Serialize() const {
return {
{"type", static_cast<std::uint32_t>(m_Type)},
{"color", {m_Color.x, m_Color.y, m_Color.z}},
{"intensity", m_Intensity},
{"range", m_Range},
{"innerConeAngle", m_InnerConeAngle},
{"outerConeAngle", m_OuterConeAngle}
};
}
void LightComponent::Deserialize(const nlohmann::json& data) {
if (data.contains("type")) {
const auto type = data.at("type").get<std::uint32_t>();
if (type <= static_cast<std::uint32_t>(LightType::Spot)) {
m_Type = static_cast<LightType>(type);
}
}
if (data.contains("color")) {
const auto& value = data.at("color");
m_Color = {value.at(0).get<float>(), value.at(1).get<float>(), value.at(2).get<float>()};
}
if (data.contains("intensity")) {
SetIntensity(data.at("intensity").get<float>());
}
if (data.contains("range")) {
SetRange(data.at("range").get<float>());
}
float innerConeAngle = m_InnerConeAngle;
float outerConeAngle = m_OuterConeAngle;
if (data.contains("innerConeAngle")) {
innerConeAngle = data.at("innerConeAngle").get<float>();
}
if (data.contains("outerConeAngle")) {
outerConeAngle = data.at("outerConeAngle").get<float>();
}
SetConeAngles(innerConeAngle, outerConeAngle);
}
void LightComponent::ImGuiInspector() {
constexpr std::array typeNames{"Directional", "Point", "Spot"};
int type = static_cast<int>(m_Type);
if (ImGui::Combo("Type", &type, typeNames.data(), static_cast<int>(typeNames.size()))) {
SetType(static_cast<LightType>(type));
}
ImGui::ColorEdit3("Color", &m_Color.x);
float intensity = m_Intensity;
if (ImGui::DragFloat("Intensity", &intensity, 0.05f, 0.0f)) {
SetIntensity(intensity);
}
if (m_Type == LightType::Directional) {
return;
}
float range = m_Range;
if (ImGui::DragFloat("Range", &range, 0.05f, 0.01f)) {
SetRange(range);
}
if (m_Type == LightType::Spot) {
float innerConeAngle = m_InnerConeAngle;
float outerConeAngle = m_OuterConeAngle;
bool changed = ImGui::SliderAngle("Inner Cone", &innerConeAngle, 0.0f, 90.0f);
changed |= ImGui::SliderAngle("Outer Cone", &outerConeAngle, 0.0f, 90.0f);
if (changed) {
SetConeAngles(innerConeAngle, outerConeAngle);
}
}
}
@@ -0,0 +1,271 @@
#include <destrum/Graphics/Pipelines/LightCullingPipeline.h>
#include "destrum/FS/AssetFS.h"
#include "destrum/Graphics/GfxDevice.h"
#include "destrum/Graphics/RenderResources.h"
#include "destrum/Graphics/Util.h"
#include <array>
#include <cassert>
#include <cmath>
#include <cstdint>
#include <stdexcept>
#include "volk.h"
#include "spdlog/spdlog.h"
void LightCullingPipeline::init(GfxDevice& gfxDevice, RenderResources& resources, glm::ivec2 drawImageSize)
{
try
{
const auto& device = gfxDevice.getDevice();
const auto pushConstant = VkPushConstantRange{
.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
.offset = 0,
.size = sizeof(PushConstants),
};
const auto pushConstants = std::array{pushConstant};
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>(pushConstants.size());
pipelineLayoutInfo.pPushConstantRanges = pushConstants.data();
if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &m_pipelineLayout) != VK_SUCCESS)
{
throw std::runtime_error("Could not make light culling pipeline layout");
}
ComputePipelineConfigInfo pipelineConfig{};
pipelineConfig.name = "light culling compute pipeline";
pipelineConfig.pipelineLayout = m_pipelineLayout;
const auto lightCullingShaderPath = AssetFS::GetInstance().GetCookedPathForFile(
"engine://shaders/light_culling.comp");
lightCullingPipeline = std::make_unique<ComputePipeline>(
gfxDevice,
lightCullingShaderPath.generic_string(),
pipelineConfig
);
for (std::size_t i = 0; i < FRAMES_IN_FLIGHT; ++i)
{
auto& lightsBuffer = framesData[i].lightsBuffer;
lightsBuffer.capacity = MAX_LIGHTS;
lightsBuffer.buffer = gfxDevice.createBuffer(
MAX_LIGHTS * sizeof(GPULight),
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT,
VMA_MEMORY_USAGE_CPU_TO_GPU);
vkutil::addDebugLabel(device, lightsBuffer.buffer.buffer, "lights");
}
createTileBuffer(gfxDevice, drawImageSize);
}
catch (...)
{
cleanup(gfxDevice);
throw;
}
}
void LightCullingPipeline::cleanup(GfxDevice& gfxDevice)
{
for (auto& frame : framesData)
{
gfxDevice.destroyBuffer(frame.lightsBuffer.buffer);
frame.lightsBuffer.buffer = {};
frame.lightsBuffer.size = 0;
}
gfxDevice.destroyBuffer(tileLightIndicesBuffer);
tileLightIndicesBuffer = {};
tileCount = glm::uvec2{0u, 0u};
if (m_pipelineLayout != VK_NULL_HANDLE && gfxDevice.getDevice() != VK_NULL_HANDLE)
{
vkDestroyPipelineLayout(gfxDevice.getDevice(), m_pipelineLayout, nullptr);
}
m_pipelineLayout = VK_NULL_HANDLE;
lightCullingPipeline.reset();
}
void LightCullingPipeline::resize(GfxDevice& gfxDevice, glm::ivec2 drawImageSize)
{
gfxDevice.waitIdle();
gfxDevice.destroyBuffer(tileLightIndicesBuffer);
tileLightIndicesBuffer = {};
createTileBuffer(gfxDevice, drawImageSize);
}
void LightCullingPipeline::createTileBuffer(GfxDevice& gfxDevice, glm::ivec2 drawImageSize)
{
assert(drawImageSize.x > 0 && drawImageSize.y > 0);
tileCount = glm::uvec2{
(static_cast<std::uint32_t>(drawImageSize.x) + TILE_SIZE - 1) / TILE_SIZE,
(static_cast<std::uint32_t>(drawImageSize.y) + TILE_SIZE - 1) / TILE_SIZE,
};
const std::size_t numTiles = static_cast<std::size_t>(tileCount.x) * tileCount.y;
tileLightIndicesBuffer = gfxDevice.createBuffer(
numTiles * TILE_STRIDE * sizeof(std::uint32_t),
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT);
vkutil::addDebugLabel(gfxDevice.getDevice(), tileLightIndicesBuffer.buffer, "tile light indices");
spdlog::debug("Forward+ light grid: {}x{} tiles", tileCount.x, tileCount.y);
}
void LightCullingPipeline::beginDrawing(std::size_t frameIndex)
{
currentFrameIndex = frameIndex;
getCurrentFrameData().lightsBuffer.clear();
}
void LightCullingPipeline::appendLight(const Light& light)
{
auto& lightsBuffer = getCurrentFrameData().lightsBuffer;
if (lightsBuffer.size >= lightsBuffer.capacity)
{
if (!warnedAboutLightLimit)
{
spdlog::warn("Light limit of {} reached, extra lights are ignored", MAX_LIGHTS);
warnedAboutLightLimit = true;
}
return;
}
const float directionLength = glm::length(light.direction);
const glm::vec3 direction = directionLength > 1e-6f
? light.direction / directionLength
: glm::vec3{0.0f, -1.0f, 0.0f};
const auto gpuLight = GPULight{
.position = light.position,
.range = light.range,
.color = light.color,
.intensity = light.intensity,
.direction = direction,
.type = static_cast<std::uint32_t>(light.type),
.spotInnerCos = std::cos(light.innerConeAngle),
.spotOuterCos = std::cos(light.outerConeAngle),
.padding = glm::vec2{0.0f},
};
lightsBuffer.append(gpuLight);
}
void LightCullingPipeline::flushCurrentFrame(VkCommandBuffer cmd, GfxDevice& gfxDevice)
{
auto& buffer = getCurrentFrameData().lightsBuffer.buffer;
gfxDevice.getMemoryManager().flushAllocation(buffer, 0, VK_WHOLE_SIZE);
vkutil::bufferHostWriteToShaderReadBarrier(
cmd,
buffer.buffer,
0,
static_cast<VkDeviceSize>(MAX_LIGHTS * sizeof(GPULight)));
}
void LightCullingPipeline::cullLights(
VkCommandBuffer cmd,
const RenderResources& resources,
const GPUBuffer& sceneDataBuffer,
ImageID depthImageId,
VkExtent2D renderExtent)
{
if (!lightCullingPipeline)
{
return;
}
{
// previous frame's mesh pass might still be reading the tile lists
const VkBufferMemoryBarrier2 tileBufferBarrier{
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2,
.srcStageMask = VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT,
.srcAccessMask = VK_ACCESS_2_SHADER_STORAGE_READ_BIT,
.dstStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT,
.dstAccessMask = VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.buffer = tileLightIndicesBuffer.buffer,
.offset = 0,
.size = VK_WHOLE_SIZE,
};
const VkDependencyInfo dependencyInfo{
.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO,
.bufferMemoryBarrierCount = 1,
.pBufferMemoryBarriers = &tileBufferBarrier,
};
vkCmdPipelineBarrier2(cmd, &dependencyInfo);
}
lightCullingPipeline->bind(cmd);
resources.bindBindlessDescSet(cmd, m_pipelineLayout, VK_PIPELINE_BIND_POINT_COMPUTE);
const auto cs = PushConstants{
.sceneDataBuffer = sceneDataBuffer.address,
.screenSize = glm::uvec2{renderExtent.width, renderExtent.height},
.depthTexId = static_cast<std::uint32_t>(depthImageId),
.padding = 0,
};
vkCmdPushConstants(cmd,
m_pipelineLayout,
VK_SHADER_STAGE_COMPUTE_BIT,
0,
sizeof(PushConstants),
&cs);
// one workgroup per tile
vkCmdDispatch(cmd, tileCount.x, tileCount.y, 1);
const VkBufferMemoryBarrier2 tileBufferBarrier{
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2,
.srcStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT,
.srcAccessMask = VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT,
.dstStageMask = VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT,
.dstAccessMask = VK_ACCESS_2_SHADER_STORAGE_READ_BIT,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.buffer = tileLightIndicesBuffer.buffer,
.offset = 0,
.size = VK_WHOLE_SIZE,
};
const VkDependencyInfo dependencyInfo{
.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO,
.bufferMemoryBarrierCount = 1,
.pBufferMemoryBarriers = &tileBufferBarrier,
};
vkCmdPipelineBarrier2(cmd, &dependencyInfo);
}
VkDeviceAddress LightCullingPipeline::getLightsBufferAddress() const
{
return getCurrentFrameData().lightsBuffer.buffer.address;
}
std::uint32_t LightCullingPipeline::getNumLights() const
{
return static_cast<std::uint32_t>(getCurrentFrameData().lightsBuffer.size);
}
LightCullingPipeline::PerFrameData& LightCullingPipeline::getCurrentFrameData()
{
return framesData[currentFrameIndex % FRAMES_IN_FLIGHT];
}
const LightCullingPipeline::PerFrameData& LightCullingPipeline::getCurrentFrameData() const
{
return framesData[currentFrameIndex % FRAMES_IN_FLIGHT];
}
+145 -60
View File
@@ -23,66 +23,122 @@ void MeshPipeline::init(
VkFormat drawImageFormat,
VkFormat depthImageFormat)
{
try {
const auto vertexShader =
AssetFS::GetInstance().GetCookedPathForFile("engine://shaders/mesh.vert");
try
{
const auto vertexShader =
AssetFS::GetInstance().GetCookedPathForFile("engine://shaders/mesh.vert");
const auto fragShader =
AssetFS::GetInstance().GetCookedPathForFile("engine://shaders/mesh.frag");
const auto fragShader =
AssetFS::GetInstance().GetCookedPathForFile("engine://shaders/mesh.frag");
const auto bufferRange = VkPushConstantRange{
.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT,
.offset = 0,
.size = sizeof(PushConstants),
};
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 pushConstantRanges = std::array{bufferRange};
const auto layouts = std::array{
resources.getBindlessDescSetLayout()
};
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();
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(
if (vkCreatePipelineLayout(
gfxDevice.getDevice(),
&pipelineLayoutInfo,
nullptr,
&m_pipelineLayout) != VK_SUCCESS)
{
throw std::runtime_error("Could not create mesh pipeline layout");
{
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 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
);
}
}
PipelineConfigInfo pipelineConfig{};
Pipeline::DefaultPipelineConfigInfo(pipelineConfig);
pipelineConfig.name = "Mesh Pipeline";
pipelineConfig.pipelineLayout = m_pipelineLayout;
pipelineConfig.vertexAttributeDescriptions = {};
pipelineConfig.vertexBindingDescriptions = {};
pipelineConfig.colorAttachments = {drawImageFormat};
pipelineConfig.depthAttachment = depthImageFormat;
m_pipeline = std::make_unique<Pipeline>(
gfxDevice,
vertexShader.string(),
fragShader.string(),
pipelineConfig
);
} catch (...) {
catch (...)
{
cleanup(gfxDevice.getDevice());
throw;
}
}
void MeshPipeline::drawDepthPrepass(
VkCommandBuffer cmd,
VkExtent2D renderExtent,
const RenderResources& resources,
const Camera& camera,
const GPUBuffer& sceneDataBuffer,
const std::vector<MeshDrawCommand>& drawCommands,
const std::vector<std::size_t>& sortedDrawCommands)
{
if (!m_depthPrepassPipeline)
{
return;
}
drawMeshes(
cmd,
*m_depthPrepassPipeline,
renderExtent,
resources,
camera,
sceneDataBuffer,
drawCommands,
sortedDrawCommands);
}
void MeshPipeline::draw(
VkCommandBuffer cmd,
VkExtent2D renderExtent,
@@ -92,13 +148,35 @@ void MeshPipeline::draw(
const std::vector<MeshDrawCommand>& drawCommands,
const std::vector<std::size_t>& sortedDrawCommands)
{
if (!m_pipeline) {
if (!m_pipeline)
{
return;
}
drawMeshes(
cmd,
*m_pipeline,
renderExtent,
resources,
camera,
sceneDataBuffer,
drawCommands,
sortedDrawCommands);
}
void MeshPipeline::drawMeshes(
VkCommandBuffer cmd,
const Pipeline& pipeline,
VkExtent2D renderExtent,
const RenderResources& resources,
const Camera& camera,
const GPUBuffer& sceneDataBuffer,
const std::vector<MeshDrawCommand>& drawCommands,
const std::vector<std::size_t>& sortedDrawCommands)
{
const MeshCache& meshCache = resources.meshes();
m_pipeline->bind(cmd);
pipeline.bind(cmd);
resources.bindBindlessDescSet(cmd, m_pipelineLayout);
@@ -130,8 +208,10 @@ void MeshPipeline::draw(
const auto frustum = edge::createFrustumFromCamera(camera);
const auto drawOne = [&](const MeshDrawCommand& dc) {
if (!edge::isInFrustum(frustum, dc.worldBoundingSphere)) {
const auto drawOne = [&](const MeshDrawCommand& dc)
{
if (!edge::isInFrustum(frustum, dc.worldBoundingSphere))
{
return;
}
@@ -139,7 +219,8 @@ void MeshPipeline::draw(
const auto& mesh = meshCache.getMesh(dc.meshId);
if (dc.meshId != prevMeshId) {
if (dc.meshId != prevMeshId)
{
prevMeshId = dc.meshId;
vkCmdBindIndexBuffer(
@@ -156,9 +237,9 @@ void MeshPipeline::draw(
.transform = dc.transformMatrix,
.sceneDataBuffer = sceneDataBuffer.address,
.vertexBuffer =
dc.skinnedMesh != nullptr
? dc.skinnedMesh->skinnedVertexBuffer.address
: mesh.vertexBuffer.address,
dc.skinnedMesh != nullptr
? dc.skinnedMesh->skinnedVertexBuffer.address
: mesh.vertexBuffer.address,
.materialId = dc.materialId,
.padding = 0,
};
@@ -175,26 +256,30 @@ void MeshPipeline::draw(
vkCmdDrawIndexed(cmd, mesh.numIndices, 1, 0, 0, 0);
};
if (!sortedDrawCommands.empty()) {
for (std::size_t drawIndex : sortedDrawCommands) {
if (!sortedDrawCommands.empty())
{
for (std::size_t drawIndex : sortedDrawCommands)
{
assert(drawIndex < drawCommands.size());
drawOne(drawCommands[drawIndex]);
}
} else {
for (const MeshDrawCommand& dc : drawCommands) {
}
else
{
for (const MeshDrawCommand& dc : drawCommands)
{
drawOne(dc);
}
}
// Optional:
// spdlog::debug("Actual mesh draw calls: {}", actualDrawCalls);
}
void MeshPipeline::cleanup(VkDevice device)
{
m_pipeline.reset();
m_depthPrepassPipeline.reset();
if (m_pipelineLayout != VK_NULL_HANDLE && device != VK_NULL_HANDLE) {
if (m_pipelineLayout != VK_NULL_HANDLE && device != VK_NULL_HANDLE)
{
vkDestroyPipelineLayout(device, m_pipelineLayout, nullptr);
}
m_pipelineLayout = VK_NULL_HANDLE;
@@ -119,14 +119,16 @@ void SkyboxPipeline::draw(
const glm::mat3 r = glm::mat3(skyboxRotation);
// rotation only, translation in the inverse makes the sky jitter far from the origin
const glm::mat4 viewRotation = glm::mat4(glm::mat3(camera.GetViewMatrix()));
const auto pcs = SkyboxPushConstants{
.invViewProj = glm::inverse(camera.GetViewProjectionMatrix()),
.invViewProj = glm::inverse(camera.GetProjectionMatrix() * viewRotation),
.skyboxRot = {
glm::vec4(r[0], 0.f),
glm::vec4(r[1], 0.f),
glm::vec4(r[2], 0.f)
},
.cameraPos = camera.GetPosition(),
.skyboxTextureId = static_cast<std::uint32_t>(skyboxTextureId),
};
+5 -2
View File
@@ -266,10 +266,13 @@ const VkDescriptorSet& RenderResources::getBindlessDescSet() const {
return imageCache->bindlessSetManager.getDescSet();
}
void RenderResources::bindBindlessDescSet(VkCommandBuffer cmd, VkPipelineLayout layout) const {
void RenderResources::bindBindlessDescSet(
VkCommandBuffer cmd,
VkPipelineLayout layout,
VkPipelineBindPoint bindPoint) const {
vkCmdBindDescriptorSets(
cmd,
VK_PIPELINE_BIND_POINT_GRAPHICS,
bindPoint,
layout,
0,
1,
+97 -14
View File
@@ -21,7 +21,8 @@ GameRenderer::GameRenderer()
void GameRenderer::init(GfxDevice& gfxDevice, RenderResources& _resources, glm::ivec2 drawImageSize)
{
try {
try
{
resources = &_resources;
sceneDataBuffer.init(
gfxDevice,
@@ -34,12 +35,15 @@ void GameRenderer::init(GfxDevice& gfxDevice, RenderResources& _resources, glm::
meshPipeline = std::make_unique<MeshPipeline>();
meshPipeline->init(gfxDevice, _resources, drawImageFormat, depthImageFormat);
skyboxPipeline = std::make_unique<SkyboxPipeline>();
skyboxPipeline = std::make_unique<SkyboxPipeline>();
skyboxPipeline->init(gfxDevice, _resources, drawImageFormat, depthImageFormat);
skinningPipeline = std::make_unique<SkinningPipeline>();
skinningPipeline->init(gfxDevice);
lightCullingPipeline = std::make_unique<LightCullingPipeline>();
lightCullingPipeline->init(gfxDevice, _resources, drawImageSize);
lineRenderingPass = std::make_unique<LineRenderingPass>();
lineRenderingPass->init(gfxDevice, drawImageFormat);
@@ -48,7 +52,9 @@ skyboxPipeline = std::make_unique<SkyboxPipeline>();
GameState::GetInstance().SetRenderer(this);
initialized = true;
} catch (...) {
}
catch (...)
{
cleanup(gfxDevice);
throw;
}
@@ -59,6 +65,7 @@ void GameRenderer::beginDrawing(GfxDevice& gfxDevice)
flushMaterialUpdates(gfxDevice);
meshDrawCommands.clear();
skinningPipeline->beginDrawing(gfxDevice.getCurrentFrameIndex());
lightCullingPipeline->beginDrawing(gfxDevice.getCurrentFrameIndex());
QuadRenderingManager::GetInstance().ClearQuads();
}
@@ -70,13 +77,16 @@ void GameRenderer::endDrawing()
std::stable_sort(
sortedMeshDrawCommands.begin(),
sortedMeshDrawCommands.end(),
[this](std::size_t left, std::size_t right) {
[this](std::size_t left, std::size_t right)
{
const auto& lhs = meshDrawCommands[left];
const auto& rhs = meshDrawCommands[right];
if (lhs.meshId != rhs.meshId) {
if (lhs.meshId != rhs.meshId)
{
return lhs.meshId < rhs.meshId;
}
if (lhs.materialId != rhs.materialId) {
if (lhs.materialId != rhs.materialId)
{
return lhs.materialId < rhs.materialId;
}
return (lhs.skinnedMesh != nullptr) < (rhs.skinnedMesh != nullptr);
@@ -106,9 +116,17 @@ void GameRenderer::draw(VkCommandBuffer cmd, GfxDevice& gfxDevice, const Camera&
}
}
{
TracyVkZone(gfxDevice.getTracyVkCtx(), cmd, "Lights Buffer Flush");
lightCullingPipeline->flushCurrentFrame(cmd, gfxDevice);
}
// TODO: there has to be a better way to do this
const auto gpuSceneData = GPUSceneData{
.view = sceneData.camera.GetViewMatrix(),
.proj = sceneData.camera.GetProjectionMatrix(),
.invProj = glm::inverse(sceneData.camera.GetProjectionMatrix()),
.viewProj = sceneData.camera.GetViewProjectionMatrix(),
.cameraPos = glm::vec4{sceneData.camera.m_position, 1.f},
.ambientColor = {sceneData.ambientColor},
@@ -116,6 +134,12 @@ void GameRenderer::draw(VkCommandBuffer cmd, GfxDevice& gfxDevice, const Camera&
.fogColor = {sceneData.fogColor},
.fogDensity = sceneData.fogDensity,
.materialsBuffer = resources->materials().getMaterialDataBufferAddress(),
.lightsBuffer = lightCullingPipeline->getLightsBufferAddress(),
.tileLightIndicesBuffer = lightCullingPipeline->getTileLightIndicesBufferAddress(),
.numLights = lightCullingPipeline->getNumLights(),
.tileCountX = lightCullingPipeline->getTileCount().x,
.debugLightHeatmap = renderLightHeatmap ? 1u : 0u,
.padding = 0,
};
{
@@ -165,12 +189,60 @@ void GameRenderer::draw(VkCommandBuffer cmd, GfxDevice& gfxDevice, const Camera&
depthImage.setLayout(VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL);
}
{
TracyVkZone(gfxDevice.getTracyVkCtx(), cmd, "Depth Prepass");
auto depthPrepassInfo = vkutil::createRenderingInfo({
.renderExtent = drawImage.getExtent2D(),
.depthImageView = depthImage.imageView,
.depthImageClearValue = 1.f,
});
depthPrepassInfo.renderingInfo.pDepthAttachment = &depthPrepassInfo.depthAttachment;
vkCmdBeginRendering(cmd, &depthPrepassInfo.renderingInfo);
meshPipeline->drawDepthPrepass(
cmd,
drawImage.getExtent2D(),
*resources,
camera,
sceneDataBuffer.getBuffer(),
meshDrawCommands,
sortedMeshDrawCommands);
vkCmdEndRendering(cmd);
}
{
TracyVkZone(gfxDevice.getTracyVkCtx(), cmd, "Light Culling");
vkutil::transitionImage(
cmd,
depthImage.image,
depthImage.layout,
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
depthImage.setLayout(VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
lightCullingPipeline->cullLights(
cmd,
*resources,
sceneDataBuffer.getBuffer(),
depthImageId,
drawImage.getExtent2D());
vkutil::transitionImage(
cmd,
depthImage.image,
depthImage.layout,
VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL);
depthImage.setLayout(VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL);
}
auto renderInfo = vkutil::createRenderingInfo({
.renderExtent = drawImage.getExtent2D(),
.colorImageView = drawImage.imageView,
.colorImageClearValue = glm::vec4{0.f, 0.f, 0.f, 1.f},
.depthImageView = depthImage.imageView,
.depthImageClearValue = 1.f,
});
renderInfo.renderingInfo.pColorAttachments = &renderInfo.colorAttachment;
renderInfo.renderingInfo.pDepthAttachment = &renderInfo.depthAttachment;
@@ -226,11 +298,11 @@ void GameRenderer::draw(VkCommandBuffer cmd, GfxDevice& gfxDevice, const Camera&
*resources,
drawImage,
glm::ortho(0.0f,
static_cast<float>(drawImage.extent.width),
static_cast<float>(drawImage.extent.height),
0.0f, -1.0f, 1.0f));
static_cast<float>(drawImage.extent.width),
static_cast<float>(drawImage.extent.height),
0.0f, -1.0f, 1.0f));
}
// vkutil::cmdEndLabel(cmd);
// vkutil::cmdEndLabel(cmd);
}
void GameRenderer::drawQuads(
@@ -246,11 +318,12 @@ void GameRenderer::cleanup(GfxDevice& gfxDevice)
{
VkDevice device = gfxDevice.getDevice();
if (device != VK_NULL_HANDLE) {
if (device != VK_NULL_HANDLE)
{
vkDeviceWaitIdle(device);
}
if (skinningPipeline)
if (skinningPipeline)
skinningPipeline->cleanup(gfxDevice);
if (lineRenderingPass)
@@ -259,6 +332,9 @@ if (skinningPipeline)
if (quadRendererPass)
quadRendererPass->cleanup(gfxDevice);
if (lightCullingPipeline)
lightCullingPipeline->cleanup(gfxDevice);
if (skyboxPipeline)
skyboxPipeline->cleanup(device);
@@ -281,6 +357,7 @@ if (skinningPipeline)
skinningPipeline.reset();
lineRenderingPass.reset();
quadRendererPass.reset();
lightCullingPipeline.reset();
pendingMaterialUploads.clear();
meshDrawCommands.clear();
sortedMeshDrawCommands.clear();
@@ -323,6 +400,11 @@ void GameRenderer::drawSkinnedMesh(MeshID id,
});
}
void GameRenderer::submitLight(const Light& light)
{
lightCullingPipeline->appendLight(light);
}
const GPUImage& GameRenderer::getDrawImage() const
{
assert(resources);
@@ -349,7 +431,8 @@ void GameRenderer::setSkyboxTexture(ImageID skyboxImageId)
void GameRenderer::flushMaterialUpdates(GfxDevice& gfxDevice)
{
if (!pendingMaterialUploads.empty()) {
if (!pendingMaterialUploads.empty())
{
// Material data is currently shared by all frame contexts. Wait before
// mutating it so an older in-flight frame cannot read the overwritten
// range. This can later be replaced with per-frame material buffers.