EDIT: folder structure refactoring
This commit is contained in:
238
src/render/passes/background.cpp
Normal file
238
src/render/passes/background.cpp
Normal file
@@ -0,0 +1,238 @@
|
||||
#include "background.h"
|
||||
#include <string_view>
|
||||
|
||||
#include "vk_swapchain.h"
|
||||
#include "core/engine_context.h"
|
||||
#include "core/vk_resource.h"
|
||||
#include "core/vk_pipeline_manager.h"
|
||||
#include "core/asset_manager.h"
|
||||
#include "render/graph/graph.h"
|
||||
#include <cstring>
|
||||
|
||||
#include "frame_resources.h"
|
||||
#include "ibl_manager.h"
|
||||
#include "vk_descriptor_manager.h"
|
||||
#include "vk_device.h"
|
||||
#include "vk_sampler_manager.h"
|
||||
|
||||
void BackgroundPass::init(EngineContext *context)
|
||||
{
|
||||
_context = context;
|
||||
init_background_pipelines();
|
||||
}
|
||||
|
||||
void BackgroundPass::init_background_pipelines()
|
||||
{
|
||||
ComputePipelineCreateInfo createInfo{};
|
||||
createInfo.shaderPath = _context->getAssets()->shaderPath("gradient_color.comp.spv");
|
||||
createInfo.descriptorTypes = {VK_DESCRIPTOR_TYPE_STORAGE_IMAGE};
|
||||
createInfo.pushConstantSize = sizeof(ComputePushConstants);
|
||||
_context->pipelines->createComputePipeline("gradient", createInfo);
|
||||
|
||||
createInfo.shaderPath = _context->getAssets()->shaderPath("sky.comp.spv");
|
||||
_context->pipelines->createComputePipeline("sky", createInfo);
|
||||
|
||||
_context->pipelines->createComputeInstance("background.gradient", "gradient");
|
||||
_context->pipelines->createComputeInstance("background.sky", "sky");
|
||||
_context->pipelines->setComputeInstanceStorageImage("background.gradient", 0,
|
||||
_context->getSwapchain()->drawImage().imageView);
|
||||
_context->pipelines->setComputeInstanceStorageImage("background.sky", 0,
|
||||
_context->getSwapchain()->drawImage().imageView);
|
||||
|
||||
ComputeEffect gradient{};
|
||||
gradient.name = "gradient";
|
||||
gradient.data.data1 = glm::vec4(1, 0, 0, 1);
|
||||
gradient.data.data2 = glm::vec4(0, 0, 1, 1);
|
||||
|
||||
ComputeEffect sky{};
|
||||
sky.name = "sky";
|
||||
sky.data.data1 = glm::vec4(0.1, 0.2, 0.4, 0.97);
|
||||
|
||||
_backgroundEffects.push_back(gradient);
|
||||
_backgroundEffects.push_back(sky);
|
||||
// Graphics env (cubemap) background mode
|
||||
ComputeEffect env{}; env.name = "env";
|
||||
_backgroundEffects.push_back(env);
|
||||
|
||||
// Prepare graphics pipeline for environment background (cubemap)
|
||||
// Create an empty descriptor set layout to occupy sets 1 and 2 (shader uses set=0 and set=3)
|
||||
{
|
||||
VkDescriptorSetLayoutCreateInfo info{ VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO };
|
||||
info.bindingCount = 0;
|
||||
info.pBindings = nullptr;
|
||||
vkCreateDescriptorSetLayout(_context->getDevice()->device(), &info, nullptr, &_emptySetLayout);
|
||||
}
|
||||
|
||||
GraphicsPipelineCreateInfo gp{};
|
||||
gp.vertexShaderPath = _context->getAssets()->shaderPath("fullscreen.vert.spv");
|
||||
gp.fragmentShaderPath = _context->getAssets()->shaderPath("background_env.frag.spv");
|
||||
VkDescriptorSetLayout sl0 = _context->getDescriptorLayouts()->gpuSceneDataLayout();
|
||||
VkDescriptorSetLayout sl1 = _emptySetLayout; // placeholder for set=1
|
||||
VkDescriptorSetLayout sl2 = _emptySetLayout; // placeholder for set=2
|
||||
// Ensure IBL layout exists (now owned by IBLManager)
|
||||
VkDescriptorSetLayout sl3 = _emptySetLayout;
|
||||
if (_context->ibl && _context->ibl->ensureLayout())
|
||||
sl3 = _context->ibl->descriptorLayout();
|
||||
gp.setLayouts = { sl0, sl1, sl2, sl3 };
|
||||
gp.configure = [this](PipelineBuilder &b) {
|
||||
b.set_input_topology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST);
|
||||
b.set_polygon_mode(VK_POLYGON_MODE_FILL);
|
||||
b.set_cull_mode(VK_CULL_MODE_NONE, VK_FRONT_FACE_CLOCKWISE);
|
||||
b.set_multisampling_none();
|
||||
b.disable_depthtest();
|
||||
b.disable_blending();
|
||||
b.set_color_attachment_format(_context->getSwapchain()->drawImage().imageFormat);
|
||||
};
|
||||
_context->pipelines->createGraphicsPipeline("background.env", gp);
|
||||
|
||||
// Create fallback 1x1x6 black cube
|
||||
{
|
||||
const uint32_t faceCount = 6;
|
||||
const uint32_t pixel = 0x00000000u; // RGBA8 black
|
||||
std::vector<uint8_t> bytes(faceCount * 4);
|
||||
for (uint32_t f = 0; f < faceCount; ++f) std::memcpy(bytes.data() + f * 4, &pixel, 4);
|
||||
std::vector<VkBufferImageCopy> copies;
|
||||
copies.reserve(faceCount);
|
||||
for (uint32_t f = 0; f < faceCount; ++f) {
|
||||
VkBufferImageCopy r{};
|
||||
r.bufferOffset = f * 4;
|
||||
r.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
r.imageSubresource.mipLevel = 0;
|
||||
r.imageSubresource.baseArrayLayer = f;
|
||||
r.imageSubresource.layerCount = 1;
|
||||
r.imageExtent = {1,1,1};
|
||||
copies.push_back(r);
|
||||
}
|
||||
_fallbackIblCube = _context->getResources()->create_image_compressed_layers(
|
||||
bytes.data(), bytes.size(), VK_FORMAT_R8G8B8A8_UNORM, 1, faceCount, copies,
|
||||
VK_IMAGE_USAGE_SAMPLED_BIT, VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT);
|
||||
}
|
||||
}
|
||||
|
||||
void BackgroundPass::execute(VkCommandBuffer)
|
||||
{
|
||||
// Background is executed via the render graph now.
|
||||
}
|
||||
|
||||
void BackgroundPass::register_graph(RenderGraph *graph, RGImageHandle drawHandle, RGImageHandle depthHandle)
|
||||
{
|
||||
(void) depthHandle; // Reserved for future depth transitions.
|
||||
if (!graph || !drawHandle.valid() || !_context) return;
|
||||
if (_backgroundEffects.empty()) return;
|
||||
|
||||
// Route to compute or graphics depending on selected mode
|
||||
const ComputeEffect &effect = _backgroundEffects[_currentEffect];
|
||||
if (std::string_view(effect.name) == std::string_view("env"))
|
||||
{
|
||||
graph->add_pass(
|
||||
"BackgroundEnv",
|
||||
RGPassType::Graphics,
|
||||
[drawHandle](RGPassBuilder &builder, EngineContext *) {
|
||||
builder.write_color(drawHandle);
|
||||
},
|
||||
[this, drawHandle](VkCommandBuffer cmd, const RGPassResources &res, EngineContext *ctx) {
|
||||
VkImageView drawView = res.image_view(drawHandle);
|
||||
(void) drawView; // handled by RG
|
||||
|
||||
// pipeline + layout
|
||||
if (!ctx->pipelines->getGraphics("background.env", _envPipeline, _envPipelineLayout)) return;
|
||||
|
||||
// Per-frame scene UBO
|
||||
AllocatedBuffer ubo = ctx->getResources()->create_buffer(sizeof(GPUSceneData),
|
||||
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
|
||||
VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
ctx->currentFrame->_deletionQueue.push_function([rm = ctx->getResources(), ubo]() { rm->destroy_buffer(ubo); });
|
||||
VmaAllocationInfo ai{}; vmaGetAllocationInfo(ctx->getDevice()->allocator(), ubo.allocation, &ai);
|
||||
*reinterpret_cast<GPUSceneData*>(ai.pMappedData) = ctx->getSceneData();
|
||||
vmaFlushAllocation(ctx->getDevice()->allocator(), ubo.allocation, 0, sizeof(GPUSceneData));
|
||||
|
||||
VkDescriptorSet global = ctx->currentFrame->_frameDescriptors.allocate(
|
||||
ctx->getDevice()->device(), ctx->getDescriptorLayouts()->gpuSceneDataLayout());
|
||||
DescriptorWriter w0; w0.write_buffer(0, ubo.buffer, sizeof(GPUSceneData), 0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER);
|
||||
w0.update_set(ctx->getDevice()->device(), global);
|
||||
|
||||
// IBL set
|
||||
VkImageView specView = _fallbackIblCube.imageView;
|
||||
if (ctx->ibl && ctx->ibl->specular().imageView) specView = ctx->ibl->specular().imageView;
|
||||
VkDescriptorSetLayout iblLayout = (ctx->ibl ? ctx->ibl->descriptorLayout() : _emptySetLayout);
|
||||
VkDescriptorSet ibl = ctx->currentFrame->_frameDescriptors.allocate(
|
||||
ctx->getDevice()->device(), iblLayout);
|
||||
DescriptorWriter w3;
|
||||
// Bind only specular at binding 0; other bindings are unused in this shader
|
||||
w3.write_image(0, specView, ctx->getSamplers()->defaultLinear(),
|
||||
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER);
|
||||
w3.update_set(ctx->getDevice()->device(), ibl);
|
||||
|
||||
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, _envPipeline);
|
||||
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, _envPipelineLayout, 0, 1, &global, 0, nullptr);
|
||||
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, _envPipelineLayout, 3, 1, &ibl, 0, nullptr);
|
||||
|
||||
VkExtent2D extent = ctx->getDrawExtent();
|
||||
VkViewport vp{0.f, 0.f, float(extent.width), float(extent.height), 0.f, 1.f};
|
||||
VkRect2D sc{{0,0}, extent};
|
||||
vkCmdSetViewport(cmd, 0, 1, &vp);
|
||||
vkCmdSetScissor(cmd, 0, 1, &sc);
|
||||
vkCmdDraw(cmd, 3, 1, 0, 0);
|
||||
}
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
graph->add_pass(
|
||||
"Background",
|
||||
RGPassType::Compute,
|
||||
[drawHandle](RGPassBuilder &builder, EngineContext *) {
|
||||
builder.write(drawHandle, RGImageUsage::ComputeWrite);
|
||||
},
|
||||
[this, drawHandle](VkCommandBuffer cmd, const RGPassResources &res, EngineContext *ctx) {
|
||||
VkImageView drawView = res.image_view(drawHandle);
|
||||
if (drawView != VK_NULL_HANDLE)
|
||||
{
|
||||
_context->pipelines->setComputeInstanceStorageImage("background.gradient", 0, drawView);
|
||||
_context->pipelines->setComputeInstanceStorageImage("background.sky", 0, drawView);
|
||||
}
|
||||
|
||||
ComputeEffect &eff = _backgroundEffects[_currentEffect];
|
||||
|
||||
ComputeDispatchInfo dispatchInfo = ComputeManager::createDispatch2D(
|
||||
ctx->getDrawExtent().width, ctx->getDrawExtent().height);
|
||||
dispatchInfo.pushConstants = &eff.data;
|
||||
dispatchInfo.pushConstantSize = sizeof(ComputePushConstants);
|
||||
|
||||
const char *instanceName = (std::string_view(eff.name) == std::string_view("gradient"))
|
||||
? "background.gradient"
|
||||
: "background.sky";
|
||||
ctx->pipelines->dispatchComputeInstance(cmd, instanceName, dispatchInfo);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void BackgroundPass::cleanup()
|
||||
{
|
||||
if (_context && _context->pipelines)
|
||||
{
|
||||
_context->pipelines->destroyComputeInstance("background.gradient");
|
||||
_context->pipelines->destroyComputeInstance("background.sky");
|
||||
_context->pipelines->destroyComputePipeline("gradient");
|
||||
_context->pipelines->destroyComputePipeline("sky");
|
||||
}
|
||||
if (_envPipeline != VK_NULL_HANDLE || _envPipelineLayout != VK_NULL_HANDLE)
|
||||
{
|
||||
// Pipelines are owned by PipelineManager and destroyed there on cleanup/hot-reload
|
||||
_envPipeline = VK_NULL_HANDLE;
|
||||
_envPipelineLayout = VK_NULL_HANDLE;
|
||||
}
|
||||
if (_emptySetLayout)
|
||||
{
|
||||
vkDestroyDescriptorSetLayout(_context->getDevice()->device(), _emptySetLayout, nullptr);
|
||||
_emptySetLayout = VK_NULL_HANDLE;
|
||||
}
|
||||
if (_fallbackIblCube.image)
|
||||
{
|
||||
_context->getResources()->destroy_image(_fallbackIblCube);
|
||||
_fallbackIblCube = {};
|
||||
}
|
||||
fmt::print("BackgroundPass::cleanup()\n");
|
||||
_backgroundEffects.clear();
|
||||
}
|
||||
36
src/render/passes/background.h
Normal file
36
src/render/passes/background.h
Normal file
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
#include "render/renderpass.h"
|
||||
#include "compute/vk_compute.h"
|
||||
#include "render/graph/types.h"
|
||||
|
||||
class RenderGraph;
|
||||
|
||||
class BackgroundPass : public IRenderPass
|
||||
{
|
||||
public:
|
||||
void init(EngineContext *context) override;
|
||||
void cleanup() override;
|
||||
void execute(VkCommandBuffer cmd) override;
|
||||
const char *getName() const override { return "Background"; }
|
||||
|
||||
void register_graph(RenderGraph *graph, RGImageHandle drawHandle, RGImageHandle depthHandle);
|
||||
|
||||
void setCurrentEffect(int index) { _currentEffect = index; }
|
||||
std::vector<ComputeEffect> &getEffects() { return _backgroundEffects; }
|
||||
|
||||
std::vector<ComputeEffect> _backgroundEffects;
|
||||
int _currentEffect = 2;
|
||||
|
||||
private:
|
||||
EngineContext *_context = nullptr;
|
||||
|
||||
void init_background_pipelines();
|
||||
|
||||
// Graphics env background pipeline
|
||||
VkPipeline _envPipeline = VK_NULL_HANDLE;
|
||||
VkPipelineLayout _envPipelineLayout = VK_NULL_HANDLE;
|
||||
// Empty descriptor layout used as placeholder for sets 1 and 2
|
||||
VkDescriptorSetLayout _emptySetLayout = VK_NULL_HANDLE;
|
||||
// Fallback 1x1x6 black cube if IBL not loaded
|
||||
AllocatedImage _fallbackIblCube{};
|
||||
};
|
||||
321
src/render/passes/geometry.cpp
Normal file
321
src/render/passes/geometry.cpp
Normal file
@@ -0,0 +1,321 @@
|
||||
#include "geometry.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <unordered_set>
|
||||
|
||||
#include "frame_resources.h"
|
||||
#include "texture_cache.h"
|
||||
#include "vk_descriptor_manager.h"
|
||||
#include "vk_device.h"
|
||||
#include "core/engine_context.h"
|
||||
#include "core/vk_initializers.h"
|
||||
#include "core/vk_resource.h"
|
||||
|
||||
#include "vk_mem_alloc.h"
|
||||
#include "vk_scene.h"
|
||||
#include "vk_swapchain.h"
|
||||
#include "render/graph/graph.h"
|
||||
|
||||
// Basic conservative frustum test against RenderObject AABB.
|
||||
// Clip space uses Vulkan Z0 (0..w). Returns true if any part of the box is inside.
|
||||
bool is_visible(const RenderObject &obj, const glm::mat4 &viewproj)
|
||||
{
|
||||
const std::array<glm::vec3, 8> corners{
|
||||
glm::vec3{+1, +1, +1}, glm::vec3{+1, +1, -1}, glm::vec3{+1, -1, +1}, glm::vec3{+1, -1, -1},
|
||||
glm::vec3{-1, +1, +1}, glm::vec3{-1, +1, -1}, glm::vec3{-1, -1, +1}, glm::vec3{-1, -1, -1},
|
||||
};
|
||||
|
||||
const glm::vec3 o = obj.bounds.origin;
|
||||
const glm::vec3 e = obj.bounds.extents;
|
||||
const glm::mat4 m = viewproj * obj.transform; // world -> clip
|
||||
|
||||
glm::vec4 clip[8];
|
||||
for (int i = 0; i < 8; ++i)
|
||||
{
|
||||
const glm::vec3 p = o + corners[i] * e;
|
||||
clip[i] = m * glm::vec4(p, 1.f);
|
||||
}
|
||||
|
||||
auto all_out = [&](auto pred) {
|
||||
for (int i = 0; i < 8; ++i)
|
||||
{
|
||||
if (!pred(clip[i])) return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
// Clip volume in Vulkan (ZO): -w<=x<=w, -w<=y<=w, 0<=z<=w
|
||||
if (all_out([](const glm::vec4 &v) { return v.x < -v.w; })) return false; // left
|
||||
if (all_out([](const glm::vec4 &v) { return v.x > v.w; })) return false; // right
|
||||
if (all_out([](const glm::vec4 &v) { return v.y < -v.w; })) return false; // bottom
|
||||
if (all_out([](const glm::vec4 &v) { return v.y > v.w; })) return false; // top
|
||||
if (all_out([](const glm::vec4 &v) { return v.z < 0.0f; })) return false; // near (ZO)
|
||||
if (all_out([](const glm::vec4 &v) { return v.z > v.w; })) return false; // far
|
||||
|
||||
return true; // intersects or is fully inside
|
||||
}
|
||||
|
||||
void GeometryPass::init(EngineContext *context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
void GeometryPass::execute(VkCommandBuffer)
|
||||
{
|
||||
// Geometry is executed via the render graph now.
|
||||
}
|
||||
|
||||
void GeometryPass::register_graph(RenderGraph *graph,
|
||||
RGImageHandle gbufferPosition,
|
||||
RGImageHandle gbufferNormal,
|
||||
RGImageHandle gbufferAlbedo,
|
||||
RGImageHandle idHandle,
|
||||
RGImageHandle depthHandle)
|
||||
{
|
||||
if (!graph || !gbufferPosition.valid() || !gbufferNormal.valid() || !gbufferAlbedo.valid() ||
|
||||
!idHandle.valid() || !depthHandle.valid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
graph->add_pass(
|
||||
"Geometry",
|
||||
RGPassType::Graphics,
|
||||
[gbufferPosition, gbufferNormal, gbufferAlbedo, idHandle, depthHandle](RGPassBuilder &builder, EngineContext *ctx)
|
||||
{
|
||||
VkClearValue clear{};
|
||||
clear.color = {{0.f, 0.f, 0.f, 0.f}};
|
||||
|
||||
builder.write_color(gbufferPosition, true, clear);
|
||||
builder.write_color(gbufferNormal, true, clear);
|
||||
builder.write_color(gbufferAlbedo, true, clear);
|
||||
VkClearValue clearID{};
|
||||
clearID.color.uint32[0] = 0u;
|
||||
builder.write_color(idHandle, true, clearID);
|
||||
|
||||
// Reverse-Z: clear depth to 0.0
|
||||
VkClearValue depthClear{};
|
||||
depthClear.depthStencil = {0.f, 0};
|
||||
builder.write_depth(depthHandle, true, depthClear);
|
||||
|
||||
// Register read buffers used by all draw calls (index + vertex SSBO)
|
||||
if (ctx)
|
||||
{
|
||||
const DrawContext &dc = ctx->getMainDrawContext();
|
||||
// Collect unique buffers to avoid duplicates
|
||||
std::unordered_set<VkBuffer> indexSet;
|
||||
std::unordered_set<VkBuffer> vertexSet;
|
||||
indexSet.reserve(dc.OpaqueSurfaces.size() + dc.TransparentSurfaces.size());
|
||||
vertexSet.reserve(dc.OpaqueSurfaces.size() + dc.TransparentSurfaces.size());
|
||||
auto collect = [&](const std::vector<RenderObject>& v){
|
||||
for (const auto &r : v)
|
||||
{
|
||||
if (r.indexBuffer) indexSet.insert(r.indexBuffer);
|
||||
if (r.vertexBuffer) vertexSet.insert(r.vertexBuffer);
|
||||
}
|
||||
};
|
||||
collect(dc.OpaqueSurfaces);
|
||||
collect(dc.TransparentSurfaces);
|
||||
|
||||
for (VkBuffer b : indexSet)
|
||||
builder.read_buffer(b, RGBufferUsage::IndexRead, 0, "geom.index");
|
||||
for (VkBuffer b : vertexSet)
|
||||
builder.read_buffer(b, RGBufferUsage::StorageRead, 0, "geom.vertex");
|
||||
}
|
||||
},
|
||||
[this, gbufferPosition, gbufferNormal, gbufferAlbedo, idHandle, depthHandle](VkCommandBuffer cmd,
|
||||
const RGPassResources &res,
|
||||
EngineContext *ctx)
|
||||
{
|
||||
draw_geometry(cmd, ctx, res, gbufferPosition, gbufferNormal, gbufferAlbedo, idHandle, depthHandle);
|
||||
});
|
||||
}
|
||||
|
||||
void GeometryPass::draw_geometry(VkCommandBuffer cmd,
|
||||
EngineContext *context,
|
||||
const RGPassResources &resources,
|
||||
RGImageHandle gbufferPosition,
|
||||
RGImageHandle gbufferNormal,
|
||||
RGImageHandle gbufferAlbedo,
|
||||
RGImageHandle /*idHandle*/,
|
||||
RGImageHandle depthHandle) const
|
||||
{
|
||||
EngineContext *ctxLocal = context ? context : _context;
|
||||
if (!ctxLocal || !ctxLocal->currentFrame) return;
|
||||
|
||||
ResourceManager *resourceManager = ctxLocal->getResources();
|
||||
DeviceManager *deviceManager = ctxLocal->getDevice();
|
||||
DescriptorManager *descriptorLayouts = ctxLocal->getDescriptorLayouts();
|
||||
if (!resourceManager || !deviceManager || !descriptorLayouts) return;
|
||||
|
||||
VkImageView positionView = resources.image_view(gbufferPosition);
|
||||
VkImageView normalView = resources.image_view(gbufferNormal);
|
||||
VkImageView albedoView = resources.image_view(gbufferAlbedo);
|
||||
VkImageView depthView = resources.image_view(depthHandle);
|
||||
|
||||
if (positionView == VK_NULL_HANDLE || normalView == VK_NULL_HANDLE ||
|
||||
albedoView == VK_NULL_HANDLE || depthView == VK_NULL_HANDLE)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& mainDrawContext = ctxLocal->getMainDrawContext();
|
||||
const auto& sceneData = ctxLocal->getSceneData();
|
||||
VkExtent2D drawExtent = ctxLocal->getDrawExtent();
|
||||
|
||||
auto start = std::chrono::system_clock::now();
|
||||
|
||||
std::vector<uint32_t> opaque_draws;
|
||||
opaque_draws.reserve(mainDrawContext.OpaqueSurfaces.size());
|
||||
|
||||
for (int i = 0; i < mainDrawContext.OpaqueSurfaces.size(); i++)
|
||||
{
|
||||
if (is_visible(mainDrawContext.OpaqueSurfaces[i], sceneData.viewproj))
|
||||
{
|
||||
opaque_draws.push_back(i);
|
||||
}
|
||||
}
|
||||
|
||||
// Texture visibility-driven residency
|
||||
if (ctxLocal->textures && !opaque_draws.empty())
|
||||
{
|
||||
std::unordered_set<VkDescriptorSet> seen;
|
||||
seen.reserve(opaque_draws.size());
|
||||
for (uint32_t idx : opaque_draws)
|
||||
{
|
||||
const RenderObject &r = mainDrawContext.OpaqueSurfaces[idx];
|
||||
VkDescriptorSet set = r.material ? r.material->materialSet : VK_NULL_HANDLE;
|
||||
if (set != VK_NULL_HANDLE && seen.insert(set).second)
|
||||
{
|
||||
ctxLocal->textures->markSetUsed(set, ctxLocal->frameIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::sort(opaque_draws.begin(), opaque_draws.end(), [&](const auto &iA, const auto &iB)
|
||||
{
|
||||
const RenderObject &A = mainDrawContext.OpaqueSurfaces[iA];
|
||||
const RenderObject &B = mainDrawContext.OpaqueSurfaces[iB];
|
||||
if (A.material == B.material)
|
||||
{
|
||||
return A.indexBuffer < B.indexBuffer;
|
||||
}
|
||||
return A.material < B.material;
|
||||
});
|
||||
|
||||
// Dynamic rendering is now begun by the RenderGraph using the declared attachments.
|
||||
|
||||
AllocatedBuffer gpuSceneDataBuffer = resourceManager->create_buffer(sizeof(GPUSceneData),
|
||||
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
|
||||
VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
|
||||
ctxLocal->currentFrame->_deletionQueue.push_function([resourceManager, gpuSceneDataBuffer]()
|
||||
{
|
||||
resourceManager->destroy_buffer(gpuSceneDataBuffer);
|
||||
});
|
||||
|
||||
VmaAllocationInfo allocInfo{};
|
||||
vmaGetAllocationInfo(deviceManager->allocator(), gpuSceneDataBuffer.allocation, &allocInfo);
|
||||
auto *sceneUniformData = static_cast<GPUSceneData *>(allocInfo.pMappedData);
|
||||
*sceneUniformData = sceneData;
|
||||
vmaFlushAllocation(deviceManager->allocator(), gpuSceneDataBuffer.allocation, 0, sizeof(GPUSceneData));
|
||||
|
||||
VkDescriptorSet globalDescriptor = ctxLocal->currentFrame->_frameDescriptors.allocate(
|
||||
deviceManager->device(), descriptorLayouts->gpuSceneDataLayout());
|
||||
DescriptorWriter writer;
|
||||
writer.write_buffer(0, gpuSceneDataBuffer.buffer, sizeof(GPUSceneData), 0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER);
|
||||
writer.update_set(deviceManager->device(), globalDescriptor);
|
||||
|
||||
MaterialPipeline *lastPipeline = nullptr;
|
||||
MaterialInstance *lastMaterial = nullptr;
|
||||
VkBuffer lastIndexBuffer = VK_NULL_HANDLE;
|
||||
|
||||
auto draw = [&](const RenderObject &r)
|
||||
{
|
||||
if (r.material != lastMaterial)
|
||||
{
|
||||
lastMaterial = r.material;
|
||||
if (r.material->pipeline != lastPipeline)
|
||||
{
|
||||
lastPipeline = r.material->pipeline;
|
||||
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, r.material->pipeline->pipeline);
|
||||
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, r.material->pipeline->layout, 0, 1,
|
||||
&globalDescriptor, 0, nullptr);
|
||||
|
||||
VkViewport viewport{};
|
||||
viewport.x = 0;
|
||||
viewport.y = 0;
|
||||
viewport.width = static_cast<float>(drawExtent.width);
|
||||
viewport.height = static_cast<float>(drawExtent.height);
|
||||
viewport.minDepth = 0.f;
|
||||
viewport.maxDepth = 1.f;
|
||||
|
||||
vkCmdSetViewport(cmd, 0, 1, &viewport);
|
||||
|
||||
VkRect2D scissor{};
|
||||
scissor.offset.x = 0;
|
||||
scissor.offset.y = 0;
|
||||
scissor.extent.width = drawExtent.width;
|
||||
scissor.extent.height = drawExtent.height;
|
||||
|
||||
vkCmdSetScissor(cmd, 0, 1, &scissor);
|
||||
}
|
||||
|
||||
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, r.material->pipeline->layout, 1, 1,
|
||||
&r.material->materialSet, 0, nullptr);
|
||||
if (ctxLocal->textures)
|
||||
{
|
||||
ctxLocal->textures->markSetUsed(r.material->materialSet, ctxLocal->frameIndex);
|
||||
}
|
||||
}
|
||||
if (r.indexBuffer != lastIndexBuffer)
|
||||
{
|
||||
lastIndexBuffer = r.indexBuffer;
|
||||
vkCmdBindIndexBuffer(cmd, r.indexBuffer, 0, VK_INDEX_TYPE_UINT32);
|
||||
}
|
||||
GPUDrawPushConstants push_constants{};
|
||||
push_constants.worldMatrix = r.transform;
|
||||
push_constants.vertexBuffer = r.vertexBufferAddress;
|
||||
push_constants.objectID = r.objectID;
|
||||
|
||||
vkCmdPushConstants(cmd, r.material->pipeline->layout,
|
||||
VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT,
|
||||
0, sizeof(GPUDrawPushConstants), &push_constants);
|
||||
|
||||
vkCmdDrawIndexed(cmd, r.indexCount, 1, r.firstIndex, 0, 0);
|
||||
|
||||
if (ctxLocal->stats)
|
||||
{
|
||||
ctxLocal->stats->drawcall_count++;
|
||||
ctxLocal->stats->triangle_count += r.indexCount / 3;
|
||||
}
|
||||
};
|
||||
|
||||
if (ctxLocal->stats)
|
||||
{
|
||||
ctxLocal->stats->drawcall_count = 0;
|
||||
ctxLocal->stats->triangle_count = 0;
|
||||
}
|
||||
|
||||
for (auto &r: opaque_draws)
|
||||
{
|
||||
draw(mainDrawContext.OpaqueSurfaces[r]);
|
||||
}
|
||||
|
||||
// Transparent surfaces are rendered in a separate Transparent pass after lighting.
|
||||
|
||||
// RenderGraph will end dynamic rendering for this pass.
|
||||
|
||||
auto end = std::chrono::system_clock::now();
|
||||
|
||||
auto elapsed = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
|
||||
if (ctxLocal->stats)
|
||||
{
|
||||
ctxLocal->stats->mesh_draw_time = elapsed.count() / 1000.f;
|
||||
}
|
||||
}
|
||||
|
||||
void GeometryPass::cleanup()
|
||||
{
|
||||
fmt::print("GeometryPass::cleanup()\n");
|
||||
}
|
||||
35
src/render/passes/geometry.h
Normal file
35
src/render/passes/geometry.h
Normal file
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
#include "render/renderpass.h"
|
||||
#include <render/graph/types.h>
|
||||
|
||||
class SwapchainManager;
|
||||
class RenderGraph;
|
||||
|
||||
class GeometryPass : public IRenderPass
|
||||
{
|
||||
public:
|
||||
void init(EngineContext *context) override;
|
||||
void cleanup() override;
|
||||
void execute(VkCommandBuffer cmd) override;
|
||||
|
||||
const char *getName() const override { return "Geometry"; }
|
||||
|
||||
void register_graph(RenderGraph *graph,
|
||||
RGImageHandle gbufferPosition,
|
||||
RGImageHandle gbufferNormal,
|
||||
RGImageHandle gbufferAlbedo,
|
||||
RGImageHandle idHandle,
|
||||
RGImageHandle depthHandle);
|
||||
|
||||
private:
|
||||
EngineContext *_context = nullptr;
|
||||
|
||||
void draw_geometry(VkCommandBuffer cmd,
|
||||
EngineContext *context,
|
||||
const class RGPassResources &resources,
|
||||
RGImageHandle gbufferPosition,
|
||||
RGImageHandle gbufferNormal,
|
||||
RGImageHandle gbufferAlbedo,
|
||||
RGImageHandle idHandle,
|
||||
RGImageHandle depthHandle) const;
|
||||
};
|
||||
113
src/render/passes/imgui_pass.cpp
Normal file
113
src/render/passes/imgui_pass.cpp
Normal file
@@ -0,0 +1,113 @@
|
||||
#include "imgui_pass.h"
|
||||
|
||||
#include "imgui.h"
|
||||
#include "imgui_impl_sdl2.h"
|
||||
#include "imgui_impl_vulkan.h"
|
||||
#include "vk_device.h"
|
||||
#include "vk_swapchain.h"
|
||||
#include "core/vk_initializers.h"
|
||||
#include "core/engine_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)
|
||||
{
|
||||
// ImGui is executed via the render graph now.
|
||||
}
|
||||
|
||||
void ImGuiPass::register_graph(RenderGraph *graph, RGImageHandle swapchainHandle)
|
||||
{
|
||||
if (!graph || !swapchainHandle.valid()) return;
|
||||
|
||||
graph->add_pass(
|
||||
"ImGui",
|
||||
RGPassType::Graphics,
|
||||
[swapchainHandle](RGPassBuilder &builder, EngineContext *)
|
||||
{
|
||||
builder.write_color(swapchainHandle, false, {});
|
||||
},
|
||||
[this, swapchainHandle](VkCommandBuffer cmd, const RGPassResources &res, EngineContext *ctx)
|
||||
{
|
||||
draw_imgui(cmd, ctx, res, swapchainHandle);
|
||||
});
|
||||
}
|
||||
|
||||
void ImGuiPass::draw_imgui(VkCommandBuffer cmd,
|
||||
EngineContext *context,
|
||||
const RGPassResources &resources,
|
||||
RGImageHandle targetHandle) const
|
||||
{
|
||||
EngineContext *ctxLocal = context ? context : _context;
|
||||
if (!ctxLocal) return;
|
||||
|
||||
VkImageView targetImageView = resources.image_view(targetHandle);
|
||||
if (targetImageView == VK_NULL_HANDLE) return;
|
||||
|
||||
// Dynamic rendering is handled by the RenderGraph; just render draw data.
|
||||
ImGui_ImplVulkan_RenderDrawData(ImGui::GetDrawData(), cmd);
|
||||
}
|
||||
29
src/render/passes/imgui_pass.h
Normal file
29
src/render/passes/imgui_pass.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
#include "render/renderpass.h"
|
||||
#include "core/vk_types.h"
|
||||
#include <render/graph/types.h>
|
||||
|
||||
class ImGuiPass : public IRenderPass
|
||||
{
|
||||
public:
|
||||
void init(EngineContext *context) override;
|
||||
|
||||
void cleanup() override;
|
||||
|
||||
void execute(VkCommandBuffer cmd) override;
|
||||
|
||||
const char *getName() const override { return "ImGui"; }
|
||||
|
||||
void register_graph(class RenderGraph *graph,
|
||||
RGImageHandle swapchainHandle);
|
||||
|
||||
private:
|
||||
EngineContext *_context = nullptr;
|
||||
|
||||
void draw_imgui(VkCommandBuffer cmd,
|
||||
EngineContext *context,
|
||||
const class RGPassResources &resources,
|
||||
RGImageHandle targetHandle) const;
|
||||
|
||||
DeletionQueue _deletionQueue;
|
||||
};
|
||||
329
src/render/passes/lighting.cpp
Normal file
329
src/render/passes/lighting.cpp
Normal file
@@ -0,0 +1,329 @@
|
||||
#include "lighting.h"
|
||||
|
||||
#include "frame_resources.h"
|
||||
#include "vk_descriptor_manager.h"
|
||||
#include "vk_device.h"
|
||||
#include "core/engine_context.h"
|
||||
#include "core/vk_initializers.h"
|
||||
#include "core/vk_resource.h"
|
||||
#include "render/pipelines.h"
|
||||
#include "core/vk_pipeline_manager.h"
|
||||
#include "core/asset_manager.h"
|
||||
#include "core/vk_descriptors.h"
|
||||
#include "core/config.h"
|
||||
|
||||
#include "vk_mem_alloc.h"
|
||||
#include "vk_sampler_manager.h"
|
||||
#include "vk_swapchain.h"
|
||||
#include "render/graph/graph.h"
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
|
||||
#include "ibl_manager.h"
|
||||
#include "vk_raytracing.h"
|
||||
|
||||
void LightingPass::init(EngineContext *context)
|
||||
{
|
||||
_context = context;
|
||||
|
||||
// Placeholder empty set layout to keep array sizes stable if needed
|
||||
{
|
||||
VkDescriptorSetLayoutCreateInfo info{ VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO };
|
||||
info.bindingCount = 0; info.pBindings = nullptr;
|
||||
vkCreateDescriptorSetLayout(_context->getDevice()->device(), &info, nullptr, &_emptySetLayout);
|
||||
}
|
||||
|
||||
// Build descriptor layout for GBuffer inputs
|
||||
{
|
||||
DescriptorLayoutBuilder builder;
|
||||
builder.add_binding(0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER);
|
||||
builder.add_binding(1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER);
|
||||
builder.add_binding(2, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER);
|
||||
_gBufferInputDescriptorLayout = builder.build(
|
||||
_context->getDevice()->device(), VK_SHADER_STAGE_FRAGMENT_BIT,
|
||||
nullptr, VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT);
|
||||
}
|
||||
|
||||
// Allocate and write GBuffer descriptor set
|
||||
_gBufferInputDescriptorSet = _context->getDescriptors()->allocate(
|
||||
_context->getDevice()->device(), _gBufferInputDescriptorLayout);
|
||||
{
|
||||
DescriptorWriter writer;
|
||||
writer.write_image(0, _context->getSwapchain()->gBufferPosition().imageView, _context->getSamplers()->defaultLinear(),
|
||||
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER);
|
||||
writer.write_image(1, _context->getSwapchain()->gBufferNormal().imageView, _context->getSamplers()->defaultLinear(),
|
||||
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER);
|
||||
writer.write_image(2, _context->getSwapchain()->gBufferAlbedo().imageView, _context->getSamplers()->defaultLinear(),
|
||||
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER);
|
||||
writer.update_set(_context->getDevice()->device(), _gBufferInputDescriptorSet);
|
||||
}
|
||||
|
||||
// Shadow map descriptor layout (set = 2, updated per-frame). Use array of cascades
|
||||
{
|
||||
DescriptorLayoutBuilder builder;
|
||||
builder.add_binding(0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, kShadowCascadeCount);
|
||||
_shadowDescriptorLayout = builder.build(
|
||||
_context->getDevice()->device(), VK_SHADER_STAGE_FRAGMENT_BIT,
|
||||
nullptr, VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT);
|
||||
}
|
||||
|
||||
// Build lighting pipelines (RT and non-RT) through PipelineManager
|
||||
// Ensure IBL layout exists (moved to IBLManager)
|
||||
VkDescriptorSetLayout iblLayout = _emptySetLayout;
|
||||
if (_context->ibl && _context->ibl->ensureLayout())
|
||||
iblLayout = _context->ibl->descriptorLayout();
|
||||
|
||||
VkDescriptorSetLayout layouts[] = {
|
||||
_context->getDescriptorLayouts()->gpuSceneDataLayout(), // set=0
|
||||
_gBufferInputDescriptorLayout, // set=1
|
||||
_shadowDescriptorLayout, // set=2
|
||||
iblLayout // set=3
|
||||
};
|
||||
|
||||
GraphicsPipelineCreateInfo baseInfo{};
|
||||
baseInfo.vertexShaderPath = _context->getAssets()->shaderPath("fullscreen.vert.spv");
|
||||
baseInfo.setLayouts.assign(std::begin(layouts), std::end(layouts));
|
||||
baseInfo.configure = [this](PipelineBuilder &b) {
|
||||
b.set_input_topology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST);
|
||||
b.set_polygon_mode(VK_POLYGON_MODE_FILL);
|
||||
b.set_cull_mode(VK_CULL_MODE_NONE, VK_FRONT_FACE_CLOCKWISE);
|
||||
b.set_multisampling_none();
|
||||
b.enable_blending_alphablend();
|
||||
b.disable_depthtest();
|
||||
b.set_color_attachment_format(_context->getSwapchain()->drawImage().imageFormat);
|
||||
};
|
||||
|
||||
// Non-RT variant (no TLAS required)
|
||||
auto infoNoRT = baseInfo;
|
||||
infoNoRT.fragmentShaderPath = _context->getAssets()->shaderPath("deferred_lighting_nort.frag.spv");
|
||||
_context->pipelines->createGraphicsPipeline("deferred_lighting.nort", infoNoRT);
|
||||
|
||||
// RT variant (requires GL_EXT_ray_query and TLAS bound at set=0,binding=1)
|
||||
auto infoRT = baseInfo;
|
||||
infoRT.fragmentShaderPath = _context->getAssets()->shaderPath("deferred_lighting.frag.spv");
|
||||
_context->pipelines->createGraphicsPipeline("deferred_lighting.rt", infoRT);
|
||||
|
||||
_deletionQueue.push_function([&]() {
|
||||
// Pipelines are owned by PipelineManager; only destroy our local descriptor set layout
|
||||
vkDestroyDescriptorSetLayout(_context->getDevice()->device(), _gBufferInputDescriptorLayout, nullptr);
|
||||
vkDestroyDescriptorSetLayout(_context->getDevice()->device(), _shadowDescriptorLayout, nullptr);
|
||||
if (_emptySetLayout) vkDestroyDescriptorSetLayout(_context->getDevice()->device(), _emptySetLayout, nullptr);
|
||||
});
|
||||
|
||||
// Create tiny fallback textures for IBL (grey 2D and RG LUT)
|
||||
// so shaders can safely sample even when IBL isn't loaded.
|
||||
{
|
||||
const uint32_t pixel = 0xFF333333u; // RGBA8 grey
|
||||
_fallbackIbl2D = _context->getResources()->create_image(&pixel, VkExtent3D{1,1,1},
|
||||
VK_FORMAT_R8G8B8A8_UNORM,
|
||||
VK_IMAGE_USAGE_SAMPLED_BIT);
|
||||
}
|
||||
{
|
||||
// 1x1 RG UNORM for BRDF LUT fallback
|
||||
const uint16_t rg = 0x0000u; // R=0,G=0
|
||||
_fallbackBrdfLut2D = _context->getResources()->create_image(
|
||||
&rg, VkExtent3D{1,1,1}, VK_FORMAT_R8G8_UNORM, VK_IMAGE_USAGE_SAMPLED_BIT);
|
||||
}
|
||||
}
|
||||
|
||||
void LightingPass::execute(VkCommandBuffer)
|
||||
{
|
||||
// Lighting is executed via the render graph now.
|
||||
}
|
||||
|
||||
void LightingPass::register_graph(RenderGraph *graph,
|
||||
RGImageHandle drawHandle,
|
||||
RGImageHandle gbufferPosition,
|
||||
RGImageHandle gbufferNormal,
|
||||
RGImageHandle gbufferAlbedo,
|
||||
std::span<RGImageHandle> shadowCascades)
|
||||
{
|
||||
if (!graph || !drawHandle.valid() || !gbufferPosition.valid() || !gbufferNormal.valid() || !gbufferAlbedo.valid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
graph->add_pass(
|
||||
"Lighting",
|
||||
RGPassType::Graphics,
|
||||
[drawHandle, gbufferPosition, gbufferNormal, gbufferAlbedo, shadowCascades](RGPassBuilder &builder, EngineContext *)
|
||||
{
|
||||
builder.read(gbufferPosition, RGImageUsage::SampledFragment);
|
||||
builder.read(gbufferNormal, RGImageUsage::SampledFragment);
|
||||
builder.read(gbufferAlbedo, RGImageUsage::SampledFragment);
|
||||
for (size_t i = 0; i < shadowCascades.size(); ++i)
|
||||
{
|
||||
if (shadowCascades[i].valid()) builder.read(shadowCascades[i], RGImageUsage::SampledFragment);
|
||||
}
|
||||
|
||||
builder.write_color(drawHandle);
|
||||
},
|
||||
[this, drawHandle, shadowCascades](VkCommandBuffer cmd, const RGPassResources &res, EngineContext *ctx)
|
||||
{
|
||||
draw_lighting(cmd, ctx, res, drawHandle, shadowCascades);
|
||||
});
|
||||
}
|
||||
|
||||
void LightingPass::draw_lighting(VkCommandBuffer cmd,
|
||||
EngineContext *context,
|
||||
const RGPassResources &resources,
|
||||
RGImageHandle drawHandle,
|
||||
std::span<RGImageHandle> shadowCascades)
|
||||
{
|
||||
EngineContext *ctxLocal = context ? context : _context;
|
||||
if (!ctxLocal || !ctxLocal->currentFrame) return;
|
||||
|
||||
ResourceManager *resourceManager = ctxLocal->getResources();
|
||||
DeviceManager *deviceManager = ctxLocal->getDevice();
|
||||
DescriptorManager *descriptorLayouts = ctxLocal->getDescriptorLayouts();
|
||||
PipelineManager *pipelineManager = ctxLocal->pipelines;
|
||||
if (!resourceManager || !deviceManager || !descriptorLayouts || !pipelineManager) return;
|
||||
|
||||
VkImageView drawView = resources.image_view(drawHandle);
|
||||
if (drawView == VK_NULL_HANDLE) return;
|
||||
|
||||
// Choose RT only if TLAS is valid; otherwise fall back to non-RT.
|
||||
const bool haveRTFeatures = ctxLocal->getDevice()->supportsAccelerationStructure();
|
||||
const VkAccelerationStructureKHR tlas = (ctxLocal->ray ? ctxLocal->ray->tlas() : VK_NULL_HANDLE);
|
||||
const VkDeviceAddress tlasAddr = (ctxLocal->ray ? ctxLocal->ray->tlasAddress() : 0);
|
||||
const bool useRT = haveRTFeatures && (ctxLocal->shadowSettings.mode != 0u) && (tlas != VK_NULL_HANDLE) && (tlasAddr != 0);
|
||||
|
||||
const char* pipeName = useRT ? "deferred_lighting.rt" : "deferred_lighting.nort";
|
||||
if (!pipelineManager->getGraphics(pipeName, _pipeline, _pipelineLayout))
|
||||
{
|
||||
// Try the other variant as a fallback
|
||||
const char* fallback = useRT ? "deferred_lighting.nort" : "deferred_lighting.rt";
|
||||
if (!pipelineManager->getGraphics(fallback, _pipeline, _pipelineLayout))
|
||||
return; // Neither pipeline is ready
|
||||
}
|
||||
|
||||
// Dynamic rendering is handled by the RenderGraph using the declared draw attachment.
|
||||
|
||||
AllocatedBuffer gpuSceneDataBuffer = resourceManager->create_buffer(
|
||||
sizeof(GPUSceneData), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
|
||||
VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
ctxLocal->currentFrame->_deletionQueue.push_function([resourceManager, gpuSceneDataBuffer]()
|
||||
{
|
||||
resourceManager->destroy_buffer(gpuSceneDataBuffer);
|
||||
});
|
||||
|
||||
VmaAllocationInfo allocInfo{};
|
||||
vmaGetAllocationInfo(deviceManager->allocator(), gpuSceneDataBuffer.allocation, &allocInfo);
|
||||
auto *sceneUniformData = static_cast<GPUSceneData *>(allocInfo.pMappedData);
|
||||
*sceneUniformData = ctxLocal->getSceneData();
|
||||
vmaFlushAllocation(deviceManager->allocator(), gpuSceneDataBuffer.allocation, 0, sizeof(GPUSceneData));
|
||||
|
||||
VkDescriptorSet globalDescriptor = ctxLocal->currentFrame->_frameDescriptors.allocate(
|
||||
deviceManager->device(), descriptorLayouts->gpuSceneDataLayout());
|
||||
DescriptorWriter writer;
|
||||
writer.write_buffer(0, gpuSceneDataBuffer.buffer, sizeof(GPUSceneData), 0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER);
|
||||
// Only write TLAS when using the RT pipeline and we have a valid TLAS
|
||||
if (useRT)
|
||||
{
|
||||
writer.write_acceleration_structure(1, tlas);
|
||||
}
|
||||
writer.update_set(deviceManager->device(), globalDescriptor);
|
||||
|
||||
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, _pipeline);
|
||||
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, _pipelineLayout, 0, 1, &globalDescriptor, 0,
|
||||
nullptr);
|
||||
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, _pipelineLayout, 1, 1,
|
||||
&_gBufferInputDescriptorSet, 0, nullptr);
|
||||
|
||||
// Allocate and write shadow descriptor set for this frame (set = 2).
|
||||
// When RT is enabled, TLAS is bound in the global set at (set=0, binding=1)
|
||||
// via DescriptorManager::gpuSceneDataLayout(). See docs/RayTracing.md.
|
||||
VkDescriptorSet shadowSet = ctxLocal->currentFrame->_frameDescriptors.allocate(
|
||||
deviceManager->device(), _shadowDescriptorLayout);
|
||||
{
|
||||
const uint32_t cascadeCount = std::min<uint32_t>(kShadowCascadeCount, static_cast<uint32_t>(shadowCascades.size()));
|
||||
std::array<VkDescriptorImageInfo, kShadowCascadeCount> infos{};
|
||||
for (uint32_t i = 0; i < cascadeCount; ++i)
|
||||
{
|
||||
infos[i].sampler = ctxLocal->getSamplers()->shadowLinearClamp();
|
||||
infos[i].imageView = resources.image_view(shadowCascades[i]);
|
||||
infos[i].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||
}
|
||||
VkWriteDescriptorSet write{.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET};
|
||||
write.dstSet = shadowSet;
|
||||
write.dstBinding = 0;
|
||||
write.descriptorCount = cascadeCount;
|
||||
write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
||||
write.pImageInfo = infos.data();
|
||||
vkUpdateDescriptorSets(deviceManager->device(), 1, &write, 0, nullptr);
|
||||
}
|
||||
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, _pipelineLayout, 2, 1, &shadowSet, 0, nullptr);
|
||||
|
||||
// IBL descriptor set (set = 3). Use loaded IBL if present, otherwise fall back to black.
|
||||
VkImageView specView = _fallbackIbl2D.imageView;
|
||||
VkImageView brdfView = _fallbackBrdfLut2D.imageView;
|
||||
VkBuffer shBuf = VK_NULL_HANDLE; VkDeviceSize shSize = sizeof(glm::vec4)*9;
|
||||
if (ctxLocal->ibl)
|
||||
{
|
||||
if (ctxLocal->ibl->specular().imageView) specView = ctxLocal->ibl->specular().imageView;
|
||||
if (ctxLocal->ibl->brdf().imageView) brdfView = ctxLocal->ibl->brdf().imageView;
|
||||
if (ctxLocal->ibl->hasSH()) shBuf = ctxLocal->ibl->shBuffer().buffer;
|
||||
}
|
||||
// If SH missing, create a zero buffer for this frame
|
||||
AllocatedBuffer shZero{};
|
||||
if (shBuf == VK_NULL_HANDLE)
|
||||
{
|
||||
shZero = resourceManager->create_buffer(shSize, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
std::memset(shZero.info.pMappedData, 0, shSize);
|
||||
vmaFlushAllocation(deviceManager->allocator(), shZero.allocation, 0, shSize);
|
||||
shBuf = shZero.buffer;
|
||||
ctxLocal->currentFrame->_deletionQueue.push_function([resourceManager, shZero]() { resourceManager->destroy_buffer(shZero); });
|
||||
}
|
||||
// Allocate from IBL layout (must exist because pipeline was created with it)
|
||||
VkDescriptorSetLayout iblSetLayout = (ctxLocal->ibl ? ctxLocal->ibl->descriptorLayout() : _emptySetLayout);
|
||||
VkDescriptorSet iblSet = ctxLocal->currentFrame->_frameDescriptors.allocate(
|
||||
deviceManager->device(), iblSetLayout);
|
||||
{
|
||||
DescriptorWriter w;
|
||||
w.write_image(0, specView, ctxLocal->getSamplers()->defaultLinear(),
|
||||
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER);
|
||||
w.write_image(1, brdfView, ctxLocal->getSamplers()->defaultLinear(),
|
||||
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER);
|
||||
w.write_buffer(2, shBuf, shSize, 0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER);
|
||||
w.update_set(deviceManager->device(), iblSet);
|
||||
}
|
||||
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, _pipelineLayout, 3, 1, &iblSet, 0, nullptr);
|
||||
|
||||
VkViewport viewport{};
|
||||
viewport.x = 0;
|
||||
viewport.y = 0;
|
||||
viewport.width = static_cast<float>(ctxLocal->getDrawExtent().width);
|
||||
viewport.height = static_cast<float>(ctxLocal->getDrawExtent().height);
|
||||
viewport.minDepth = 0.f;
|
||||
viewport.maxDepth = 1.f;
|
||||
vkCmdSetViewport(cmd, 0, 1, &viewport);
|
||||
|
||||
VkRect2D scissor{};
|
||||
scissor.offset = {0, 0};
|
||||
scissor.extent = {ctxLocal->getDrawExtent().width, ctxLocal->getDrawExtent().height};
|
||||
vkCmdSetScissor(cmd, 0, 1, &scissor);
|
||||
|
||||
vkCmdDraw(cmd, 3, 1, 0, 0);
|
||||
|
||||
// RenderGraph ends rendering.
|
||||
}
|
||||
|
||||
void LightingPass::cleanup()
|
||||
{
|
||||
if (_context && _context->getResources())
|
||||
{
|
||||
if (_fallbackIbl2D.image)
|
||||
{
|
||||
_context->getResources()->destroy_image(_fallbackIbl2D);
|
||||
_fallbackIbl2D = {};
|
||||
}
|
||||
if (_fallbackBrdfLut2D.image)
|
||||
{
|
||||
_context->getResources()->destroy_image(_fallbackBrdfLut2D);
|
||||
_fallbackBrdfLut2D = {};
|
||||
}
|
||||
}
|
||||
|
||||
_deletionQueue.flush();
|
||||
fmt::print("LightingPass::cleanup()\n");
|
||||
}
|
||||
45
src/render/passes/lighting.h
Normal file
45
src/render/passes/lighting.h
Normal file
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
#include "render/renderpass.h"
|
||||
#include <render/graph/types.h>
|
||||
#include <span>
|
||||
|
||||
class LightingPass : public IRenderPass
|
||||
{
|
||||
public:
|
||||
void init(EngineContext *context) override;
|
||||
|
||||
void cleanup() override;
|
||||
|
||||
void execute(VkCommandBuffer cmd) override;
|
||||
|
||||
const char *getName() const override { return "Lighting"; }
|
||||
|
||||
// Register lighting; consumes GBuffer + CSM cascades.
|
||||
void register_graph(class RenderGraph *graph,
|
||||
RGImageHandle drawHandle,
|
||||
RGImageHandle gbufferPosition,
|
||||
RGImageHandle gbufferNormal,
|
||||
RGImageHandle gbufferAlbedo, std::span<RGImageHandle> shadowCascades);
|
||||
|
||||
private:
|
||||
EngineContext *_context = nullptr;
|
||||
|
||||
VkDescriptorSetLayout _gBufferInputDescriptorLayout = VK_NULL_HANDLE;
|
||||
VkDescriptorSet _gBufferInputDescriptorSet = VK_NULL_HANDLE;
|
||||
VkDescriptorSetLayout _shadowDescriptorLayout = VK_NULL_HANDLE; // set=2 (array)
|
||||
// Fallbacks if IBL is not loaded
|
||||
AllocatedImage _fallbackIbl2D{}; // 1x1 black
|
||||
AllocatedImage _fallbackBrdfLut2D{}; // 1x1 RG, black
|
||||
|
||||
VkPipelineLayout _pipelineLayout = VK_NULL_HANDLE;
|
||||
VkPipeline _pipeline = VK_NULL_HANDLE;
|
||||
VkDescriptorSetLayout _emptySetLayout = VK_NULL_HANDLE; // placeholder if IBL layout missing
|
||||
|
||||
void draw_lighting(VkCommandBuffer cmd,
|
||||
EngineContext *context,
|
||||
const class RGPassResources &resources,
|
||||
RGImageHandle drawHandle,
|
||||
std::span<RGImageHandle> shadowCascades);
|
||||
|
||||
DeletionQueue _deletionQueue;
|
||||
};
|
||||
205
src/render/passes/shadow.cpp
Normal file
205
src/render/passes/shadow.cpp
Normal file
@@ -0,0 +1,205 @@
|
||||
#include "shadow.h"
|
||||
|
||||
#include <unordered_set>
|
||||
#include <string>
|
||||
|
||||
#include "core/engine_context.h"
|
||||
#include "render/graph/graph.h"
|
||||
#include "render/graph/builder.h"
|
||||
#include "vk_swapchain.h"
|
||||
#include "vk_scene.h"
|
||||
#include "frame_resources.h"
|
||||
#include "vk_descriptor_manager.h"
|
||||
#include "vk_device.h"
|
||||
#include "vk_resource.h"
|
||||
#include "core/vk_initializers.h"
|
||||
#include "core/vk_pipeline_manager.h"
|
||||
#include "core/asset_manager.h"
|
||||
#include "render/pipelines.h"
|
||||
#include "core/vk_types.h"
|
||||
#include "core/config.h"
|
||||
|
||||
void ShadowPass::init(EngineContext *context)
|
||||
{
|
||||
_context = context;
|
||||
|
||||
if (!_context || !_context->pipelines) return;
|
||||
|
||||
// Build a depth-only graphics pipeline for shadow map rendering
|
||||
// Keep push constants matching current shader layout for now
|
||||
VkPushConstantRange pc{};
|
||||
pc.offset = 0;
|
||||
// Push constants layout in shadow.vert is GPUDrawPushConstants + cascade index, rounded to 16 bytes
|
||||
const uint32_t pcRaw = static_cast<uint32_t>(sizeof(GPUDrawPushConstants) + sizeof(uint32_t));
|
||||
const uint32_t pcAligned = (pcRaw + 15u) & ~15u; // 16-byte alignment to match std430 expectations
|
||||
pc.size = pcAligned;
|
||||
pc.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
|
||||
|
||||
GraphicsPipelineCreateInfo info{};
|
||||
info.vertexShaderPath = _context->getAssets()->shaderPath("shadow.vert.spv");
|
||||
info.fragmentShaderPath = _context->getAssets()->shaderPath("shadow.frag.spv");
|
||||
info.setLayouts = { _context->getDescriptorLayouts()->gpuSceneDataLayout() };
|
||||
info.pushConstants = { pc };
|
||||
info.configure = [this](PipelineBuilder &b) {
|
||||
b.set_input_topology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST);
|
||||
b.set_polygon_mode(VK_POLYGON_MODE_FILL);
|
||||
b.set_cull_mode(VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_CLOCKWISE);
|
||||
b.set_multisampling_none();
|
||||
b.disable_blending();
|
||||
|
||||
// Keep reverse-Z convention for shadow maps to match engine depth usage
|
||||
b.enable_depthtest(true, VK_COMPARE_OP_GREATER_OR_EQUAL);
|
||||
b.set_depth_format(VK_FORMAT_D32_SFLOAT);
|
||||
|
||||
// Static depth bias to help with surface acne (tune later)
|
||||
b._rasterizer.depthBiasEnable = VK_TRUE;
|
||||
b._rasterizer.depthBiasConstantFactor = kShadowDepthBiasConstant;
|
||||
b._rasterizer.depthBiasSlopeFactor = kShadowDepthBiasSlope;
|
||||
b._rasterizer.depthBiasClamp = 0.0f;
|
||||
};
|
||||
|
||||
_context->pipelines->createGraphicsPipeline("mesh.shadow", info);
|
||||
}
|
||||
|
||||
void ShadowPass::cleanup()
|
||||
{
|
||||
// Nothing yet; pipelines and descriptors will be added later
|
||||
fmt::print("ShadowPass::cleanup()\n");
|
||||
}
|
||||
|
||||
void ShadowPass::execute(VkCommandBuffer)
|
||||
{
|
||||
// Shadow rendering is done via the RenderGraph registration.
|
||||
}
|
||||
|
||||
void ShadowPass::register_graph(RenderGraph *graph, std::span<RGImageHandle> cascades, VkExtent2D extent)
|
||||
{
|
||||
if (!graph || cascades.empty()) return;
|
||||
|
||||
for (uint32_t i = 0; i < cascades.size(); ++i)
|
||||
{
|
||||
RGImageHandle shadowDepth = cascades[i];
|
||||
if (!shadowDepth.valid()) continue;
|
||||
|
||||
std::string passName = std::string("ShadowMap[") + std::to_string(i) + "]";
|
||||
graph->add_pass(
|
||||
passName.c_str(),
|
||||
RGPassType::Graphics,
|
||||
[shadowDepth](RGPassBuilder &builder, EngineContext *ctx)
|
||||
{
|
||||
VkClearValue clear{}; clear.depthStencil = {0.f, 0};
|
||||
builder.write_depth(shadowDepth, true, clear);
|
||||
|
||||
// Ensure index/vertex buffers are tracked as reads (like Geometry)
|
||||
if (ctx)
|
||||
{
|
||||
const DrawContext &dc = ctx->getMainDrawContext();
|
||||
std::unordered_set<VkBuffer> indexSet;
|
||||
std::unordered_set<VkBuffer> vertexSet;
|
||||
auto collect = [&](const std::vector<RenderObject> &v)
|
||||
{
|
||||
for (const auto &r : v)
|
||||
{
|
||||
if (r.indexBuffer) indexSet.insert(r.indexBuffer);
|
||||
if (r.vertexBuffer) vertexSet.insert(r.vertexBuffer);
|
||||
}
|
||||
};
|
||||
collect(dc.OpaqueSurfaces);
|
||||
// Ignore transparent for shadow map
|
||||
|
||||
for (VkBuffer b : indexSet)
|
||||
builder.read_buffer(b, RGBufferUsage::IndexRead, 0, "shadow.index");
|
||||
for (VkBuffer b : vertexSet)
|
||||
builder.read_buffer(b, RGBufferUsage::StorageRead, 0, "shadow.vertex");
|
||||
}
|
||||
},
|
||||
[this, shadowDepth, extent, i](VkCommandBuffer cmd, const RGPassResources &res, EngineContext *ctx)
|
||||
{
|
||||
draw_shadow(cmd, ctx, res, shadowDepth, extent, i);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void ShadowPass::draw_shadow(VkCommandBuffer cmd,
|
||||
EngineContext *context,
|
||||
const RGPassResources &/*resources*/,
|
||||
RGImageHandle /*shadowDepth*/,
|
||||
VkExtent2D extent,
|
||||
uint32_t cascadeIndex) const
|
||||
{
|
||||
EngineContext *ctxLocal = context ? context : _context;
|
||||
if (!ctxLocal || !ctxLocal->currentFrame) return;
|
||||
|
||||
ResourceManager *resourceManager = ctxLocal->getResources();
|
||||
DeviceManager *deviceManager = ctxLocal->getDevice();
|
||||
DescriptorManager *descriptorLayouts = ctxLocal->getDescriptorLayouts();
|
||||
PipelineManager *pipelineManager = ctxLocal->pipelines;
|
||||
if (!resourceManager || !deviceManager || !descriptorLayouts || !pipelineManager) return;
|
||||
|
||||
VkPipeline pipeline{}; VkPipelineLayout layout{};
|
||||
if (!pipelineManager->getGraphics("mesh.shadow", pipeline, layout)) return;
|
||||
|
||||
// Create and upload per-pass scene UBO
|
||||
AllocatedBuffer gpuSceneDataBuffer = resourceManager->create_buffer(
|
||||
sizeof(GPUSceneData), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
|
||||
VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
ctxLocal->currentFrame->_deletionQueue.push_function([resourceManager, gpuSceneDataBuffer]()
|
||||
{
|
||||
resourceManager->destroy_buffer(gpuSceneDataBuffer);
|
||||
});
|
||||
|
||||
VmaAllocationInfo allocInfo{};
|
||||
vmaGetAllocationInfo(deviceManager->allocator(), gpuSceneDataBuffer.allocation, &allocInfo);
|
||||
auto *sceneUniformData = static_cast<GPUSceneData *>(allocInfo.pMappedData);
|
||||
*sceneUniformData = ctxLocal->getSceneData();
|
||||
vmaFlushAllocation(deviceManager->allocator(), gpuSceneDataBuffer.allocation, 0, sizeof(GPUSceneData));
|
||||
|
||||
VkDescriptorSet globalDescriptor = ctxLocal->currentFrame->_frameDescriptors.allocate(
|
||||
deviceManager->device(), descriptorLayouts->gpuSceneDataLayout());
|
||||
DescriptorWriter writer;
|
||||
writer.write_buffer(0, gpuSceneDataBuffer.buffer, sizeof(GPUSceneData), 0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER);
|
||||
writer.update_set(deviceManager->device(), globalDescriptor);
|
||||
|
||||
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
|
||||
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, layout, 0, 1, &globalDescriptor, 0, nullptr);
|
||||
|
||||
VkViewport viewport{};
|
||||
viewport.x = 0;
|
||||
viewport.y = 0;
|
||||
viewport.width = static_cast<float>(extent.width);
|
||||
viewport.height = static_cast<float>(extent.height);
|
||||
viewport.minDepth = 0.f;
|
||||
viewport.maxDepth = 1.f;
|
||||
vkCmdSetViewport(cmd, 0, 1, &viewport);
|
||||
|
||||
VkRect2D scissor{};
|
||||
scissor.offset = {0, 0};
|
||||
scissor.extent = extent;
|
||||
vkCmdSetScissor(cmd, 0, 1, &scissor);
|
||||
|
||||
const DrawContext &dc = ctxLocal->getMainDrawContext();
|
||||
|
||||
VkBuffer lastIndexBuffer = VK_NULL_HANDLE;
|
||||
|
||||
struct ShadowPC
|
||||
{
|
||||
GPUDrawPushConstants draw;
|
||||
uint32_t cascadeIndex;
|
||||
};
|
||||
for (const auto &r : dc.OpaqueSurfaces)
|
||||
{
|
||||
if (r.indexBuffer != lastIndexBuffer)
|
||||
{
|
||||
lastIndexBuffer = r.indexBuffer;
|
||||
vkCmdBindIndexBuffer(cmd, r.indexBuffer, 0, VK_INDEX_TYPE_UINT32);
|
||||
}
|
||||
|
||||
ShadowPC spc{};
|
||||
spc.draw.worldMatrix = r.transform;
|
||||
spc.draw.vertexBuffer = r.vertexBufferAddress;
|
||||
spc.draw.objectID = r.objectID;
|
||||
spc.cascadeIndex = cascadeIndex;
|
||||
vkCmdPushConstants(cmd, layout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(ShadowPC), &spc);
|
||||
vkCmdDrawIndexed(cmd, r.indexCount, 1, r.firstIndex, 0, 0);
|
||||
}
|
||||
}
|
||||
33
src/render/passes/shadow.h
Normal file
33
src/render/passes/shadow.h
Normal file
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#include "render/renderpass.h"
|
||||
#include <render/graph/types.h>
|
||||
#include <span>
|
||||
|
||||
class RenderGraph;
|
||||
class EngineContext;
|
||||
class RGPassResources;
|
||||
|
||||
// Depth-only directional shadow map pass (CSM-ready API)
|
||||
class ShadowPass : public IRenderPass
|
||||
{
|
||||
public:
|
||||
void init(EngineContext *context) override;
|
||||
void cleanup() override;
|
||||
void execute(VkCommandBuffer cmd) override;
|
||||
|
||||
const char *getName() const override { return "ShadowMap"; }
|
||||
|
||||
// Register N cascades; one graphics pass per cascade.
|
||||
void register_graph(RenderGraph *graph, std::span<RGImageHandle> cascades, VkExtent2D extent);
|
||||
|
||||
private:
|
||||
EngineContext *_context = nullptr;
|
||||
|
||||
void draw_shadow(VkCommandBuffer cmd,
|
||||
EngineContext *context,
|
||||
const RGPassResources &resources,
|
||||
RGImageHandle shadowDepth,
|
||||
VkExtent2D extent,
|
||||
uint32_t cascadeIndex) const;
|
||||
};
|
||||
122
src/render/passes/tonemap.cpp
Normal file
122
src/render/passes/tonemap.cpp
Normal file
@@ -0,0 +1,122 @@
|
||||
#include "tonemap.h"
|
||||
|
||||
#include <core/engine_context.h>
|
||||
#include <core/vk_descriptors.h>
|
||||
#include <core/vk_descriptor_manager.h>
|
||||
#include <core/vk_pipeline_manager.h>
|
||||
#include <core/asset_manager.h>
|
||||
#include <core/vk_device.h>
|
||||
#include <core/vk_resource.h>
|
||||
#include <vk_sampler_manager.h>
|
||||
#include <render/graph/graph.h>
|
||||
#include <render/graph/resources.h>
|
||||
|
||||
#include "frame_resources.h"
|
||||
|
||||
struct TonemapPush
|
||||
{
|
||||
float exposure;
|
||||
int mode;
|
||||
};
|
||||
|
||||
void TonemapPass::init(EngineContext *context)
|
||||
{
|
||||
_context = context;
|
||||
|
||||
_inputSetLayout = _context->getDescriptorLayouts()->singleImageLayout();
|
||||
|
||||
GraphicsPipelineCreateInfo info{};
|
||||
info.vertexShaderPath = _context->getAssets()->shaderPath("fullscreen.vert.spv");
|
||||
info.fragmentShaderPath = _context->getAssets()->shaderPath("tonemap.frag.spv");
|
||||
info.setLayouts = { _inputSetLayout };
|
||||
|
||||
VkPushConstantRange pcr{};
|
||||
pcr.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
|
||||
pcr.offset = 0;
|
||||
pcr.size = sizeof(TonemapPush);
|
||||
info.pushConstants = { pcr };
|
||||
|
||||
info.configure = [this](PipelineBuilder &b) {
|
||||
b.set_input_topology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST);
|
||||
b.set_polygon_mode(VK_POLYGON_MODE_FILL);
|
||||
b.set_cull_mode(VK_CULL_MODE_NONE, VK_FRONT_FACE_CLOCKWISE);
|
||||
b.set_multisampling_none();
|
||||
b.disable_depthtest();
|
||||
b.disable_blending();
|
||||
b.set_color_attachment_format(VK_FORMAT_R8G8B8A8_UNORM);
|
||||
};
|
||||
|
||||
_context->pipelines->createGraphicsPipeline("tonemap", info);
|
||||
|
||||
MaterialPipeline mp{};
|
||||
_context->pipelines->getMaterialPipeline("tonemap", mp);
|
||||
_pipeline = mp.pipeline;
|
||||
_pipelineLayout = mp.layout;
|
||||
}
|
||||
|
||||
void TonemapPass::cleanup()
|
||||
{
|
||||
_deletionQueue.flush();
|
||||
}
|
||||
|
||||
void TonemapPass::execute(VkCommandBuffer)
|
||||
{
|
||||
// Executed via render graph.
|
||||
}
|
||||
|
||||
RGImageHandle TonemapPass::register_graph(RenderGraph *graph, RGImageHandle hdrInput)
|
||||
{
|
||||
if (!graph || !hdrInput.valid()) return {};
|
||||
|
||||
RGImageDesc desc{};
|
||||
desc.name = "ldr.tonemap";
|
||||
desc.format = VK_FORMAT_R8G8B8A8_UNORM;
|
||||
desc.extent = _context->getDrawExtent();
|
||||
desc.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
|
||||
RGImageHandle ldr = graph->create_image(desc);
|
||||
|
||||
graph->add_pass(
|
||||
"Tonemap",
|
||||
RGPassType::Graphics,
|
||||
[hdrInput, ldr](RGPassBuilder &builder, EngineContext *) {
|
||||
builder.read(hdrInput, RGImageUsage::SampledFragment);
|
||||
builder.write_color(ldr, true /*clear*/);
|
||||
},
|
||||
[this, hdrInput](VkCommandBuffer cmd, const RGPassResources &res, EngineContext *ctx) {
|
||||
draw_tonemap(cmd, ctx, res, hdrInput);
|
||||
}
|
||||
);
|
||||
|
||||
return ldr;
|
||||
}
|
||||
|
||||
void TonemapPass::draw_tonemap(VkCommandBuffer cmd, EngineContext *ctx, const RGPassResources &res,
|
||||
RGImageHandle hdrInput)
|
||||
{
|
||||
if (!ctx || !ctx->currentFrame) return;
|
||||
VkDevice device = ctx->getDevice()->device();
|
||||
|
||||
VkImageView hdrView = res.image_view(hdrInput);
|
||||
if (hdrView == VK_NULL_HANDLE) return;
|
||||
|
||||
VkDescriptorSet set = ctx->currentFrame->_frameDescriptors.allocate(device, _inputSetLayout);
|
||||
DescriptorWriter writer;
|
||||
writer.write_image(0, hdrView, ctx->getSamplers()->defaultLinear(),
|
||||
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER);
|
||||
writer.update_set(device, set);
|
||||
|
||||
ctx->pipelines->getGraphics("tonemap", _pipeline, _pipelineLayout);
|
||||
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, _pipeline);
|
||||
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, _pipelineLayout, 0, 1, &set, 0, nullptr);
|
||||
|
||||
TonemapPush push{_exposure, _mode};
|
||||
vkCmdPushConstants(cmd, _pipelineLayout, VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(TonemapPush), &push);
|
||||
|
||||
VkExtent2D extent = ctx->getDrawExtent();
|
||||
VkViewport vp{0.f, 0.f, (float)extent.width, (float)extent.height, 0.f, 1.f};
|
||||
VkRect2D sc{{0,0}, extent};
|
||||
vkCmdSetViewport(cmd, 0, 1, &vp);
|
||||
vkCmdSetScissor(cmd, 0, 1, &sc);
|
||||
vkCmdDraw(cmd, 3, 1, 0, 0);
|
||||
}
|
||||
|
||||
43
src/render/passes/tonemap.h
Normal file
43
src/render/passes/tonemap.h
Normal file
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include <core/vk_types.h>
|
||||
#include <render/renderpass.h>
|
||||
#include <render/graph/types.h>
|
||||
|
||||
class EngineContext;
|
||||
class RenderGraph;
|
||||
class RGPassResources;
|
||||
|
||||
class TonemapPass final : public IRenderPass
|
||||
{
|
||||
public:
|
||||
void init(EngineContext *context) override;
|
||||
void cleanup() override;
|
||||
void execute(VkCommandBuffer) override; // Not used directly; executed via render graph
|
||||
const char *getName() const override { return "Tonemap"; }
|
||||
|
||||
// Register pass in the render graph. Returns the LDR output image handle.
|
||||
RGImageHandle register_graph(RenderGraph *graph, RGImageHandle hdrInput);
|
||||
|
||||
// Runtime parameters
|
||||
void setExposure(float e) { _exposure = e; }
|
||||
float exposure() const { return _exposure; }
|
||||
void setMode(int m) { _mode = m; }
|
||||
int mode() const { return _mode; }
|
||||
|
||||
private:
|
||||
void draw_tonemap(VkCommandBuffer cmd, EngineContext *ctx, const RGPassResources &res,
|
||||
RGImageHandle hdrInput);
|
||||
|
||||
EngineContext *_context = nullptr;
|
||||
|
||||
VkPipeline _pipeline = VK_NULL_HANDLE;
|
||||
VkPipelineLayout _pipelineLayout = VK_NULL_HANDLE;
|
||||
VkDescriptorSetLayout _inputSetLayout = VK_NULL_HANDLE;
|
||||
|
||||
float _exposure = 1.0f;
|
||||
int _mode = 1; // default to ACES
|
||||
|
||||
DeletionQueue _deletionQueue;
|
||||
};
|
||||
|
||||
222
src/render/passes/transparent.cpp
Normal file
222
src/render/passes/transparent.cpp
Normal file
@@ -0,0 +1,222 @@
|
||||
#include "transparent.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <unordered_set>
|
||||
|
||||
#include "ibl_manager.h"
|
||||
#include "texture_cache.h"
|
||||
#include "vk_sampler_manager.h"
|
||||
#include "vk_scene.h"
|
||||
#include "vk_swapchain.h"
|
||||
#include "core/engine_context.h"
|
||||
#include "core/vk_resource.h"
|
||||
#include "core/vk_device.h"
|
||||
#include "core/vk_descriptor_manager.h"
|
||||
#include "core/frame_resources.h"
|
||||
#include "render/graph/graph.h"
|
||||
|
||||
void TransparentPass::init(EngineContext *context)
|
||||
{
|
||||
_context = context;
|
||||
// Create fallback images
|
||||
const uint32_t pixel = 0x00000000u;
|
||||
_fallbackIbl2D = _context->getResources()->create_image(&pixel, VkExtent3D{1,1,1},
|
||||
VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_USAGE_SAMPLED_BIT);
|
||||
_fallbackBrdf2D = _context->getResources()->create_image(&pixel, VkExtent3D{1,1,1},
|
||||
VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_USAGE_SAMPLED_BIT);
|
||||
}
|
||||
|
||||
void TransparentPass::execute(VkCommandBuffer)
|
||||
{
|
||||
// Executed through render graph.
|
||||
}
|
||||
|
||||
void TransparentPass::register_graph(RenderGraph *graph, RGImageHandle drawHandle, RGImageHandle depthHandle)
|
||||
{
|
||||
if (!graph || !drawHandle.valid() || !depthHandle.valid()) return;
|
||||
|
||||
graph->add_pass(
|
||||
"Transparent",
|
||||
RGPassType::Graphics,
|
||||
[drawHandle, depthHandle](RGPassBuilder &builder, EngineContext *ctx) {
|
||||
// Draw transparent to the HDR target with depth testing against the existing depth buffer.
|
||||
builder.write_color(drawHandle);
|
||||
builder.write_depth(depthHandle, false /*load existing depth*/);
|
||||
|
||||
// Register external buffers used by draws
|
||||
if (ctx)
|
||||
{
|
||||
const DrawContext &dc = ctx->getMainDrawContext();
|
||||
std::unordered_set<VkBuffer> indexSet;
|
||||
std::unordered_set<VkBuffer> vertexSet;
|
||||
auto collect = [&](const std::vector<RenderObject> &v) {
|
||||
for (const auto &r: v)
|
||||
{
|
||||
if (r.indexBuffer) indexSet.insert(r.indexBuffer);
|
||||
if (r.vertexBuffer) vertexSet.insert(r.vertexBuffer);
|
||||
}
|
||||
};
|
||||
collect(dc.TransparentSurfaces);
|
||||
for (VkBuffer b: indexSet) builder.read_buffer(b, RGBufferUsage::IndexRead, 0, "trans.index");
|
||||
for (VkBuffer b: vertexSet) builder.read_buffer(b, RGBufferUsage::StorageRead, 0, "trans.vertex");
|
||||
}
|
||||
},
|
||||
[this, drawHandle, depthHandle](VkCommandBuffer cmd, const RGPassResources &res, EngineContext *ctx) {
|
||||
draw_transparent(cmd, ctx, res, drawHandle, depthHandle);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void TransparentPass::draw_transparent(VkCommandBuffer cmd,
|
||||
EngineContext *context,
|
||||
const RGPassResources &resources,
|
||||
RGImageHandle /*drawHandle*/,
|
||||
RGImageHandle /*depthHandle*/) const
|
||||
{
|
||||
EngineContext *ctxLocal = context ? context : _context;
|
||||
if (!ctxLocal || !ctxLocal->currentFrame) return;
|
||||
|
||||
ResourceManager *resourceManager = ctxLocal->getResources();
|
||||
DeviceManager *deviceManager = ctxLocal->getDevice();
|
||||
DescriptorManager *descriptorLayouts = ctxLocal->getDescriptorLayouts();
|
||||
if (!resourceManager || !deviceManager || !descriptorLayouts) return;
|
||||
|
||||
const auto &dc = ctxLocal->getMainDrawContext();
|
||||
const auto &sceneData = ctxLocal->getSceneData();
|
||||
|
||||
// Prepare per-frame scene UBO
|
||||
AllocatedBuffer gpuSceneDataBuffer = resourceManager->create_buffer(
|
||||
sizeof(GPUSceneData), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
ctxLocal->currentFrame->_deletionQueue.push_function([resourceManager, gpuSceneDataBuffer]() {
|
||||
resourceManager->destroy_buffer(gpuSceneDataBuffer);
|
||||
});
|
||||
VmaAllocationInfo allocInfo{};
|
||||
vmaGetAllocationInfo(deviceManager->allocator(), gpuSceneDataBuffer.allocation, &allocInfo);
|
||||
auto *sceneUniformData = static_cast<GPUSceneData *>(allocInfo.pMappedData);
|
||||
*sceneUniformData = sceneData;
|
||||
vmaFlushAllocation(deviceManager->allocator(), gpuSceneDataBuffer.allocation, 0, sizeof(GPUSceneData));
|
||||
|
||||
VkDescriptorSet globalDescriptor = ctxLocal->currentFrame->_frameDescriptors.allocate(
|
||||
deviceManager->device(), descriptorLayouts->gpuSceneDataLayout());
|
||||
DescriptorWriter writer;
|
||||
writer.write_buffer(0, gpuSceneDataBuffer.buffer, sizeof(GPUSceneData), 0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER);
|
||||
writer.update_set(deviceManager->device(), globalDescriptor);
|
||||
|
||||
// Build IBL descriptor set (set=3) once for this pass
|
||||
VkDescriptorSet iblSet = VK_NULL_HANDLE;
|
||||
VkDescriptorSetLayout iblLayout = ctxLocal->ibl ? ctxLocal->ibl->descriptorLayout() : VK_NULL_HANDLE;
|
||||
VkImageView specView = VK_NULL_HANDLE, brdfView = VK_NULL_HANDLE;
|
||||
VkBuffer shBuf = VK_NULL_HANDLE; VkDeviceSize shSize = sizeof(glm::vec4)*9;
|
||||
if (iblLayout)
|
||||
{
|
||||
// Fallbacks: use black if any missing
|
||||
specView = (ctxLocal->ibl && ctxLocal->ibl->specular().imageView) ? ctxLocal->ibl->specular().imageView
|
||||
: _fallbackIbl2D.imageView;
|
||||
brdfView = (ctxLocal->ibl && ctxLocal->ibl->brdf().imageView) ? ctxLocal->ibl->brdf().imageView
|
||||
: _fallbackBrdf2D.imageView;
|
||||
if (ctxLocal->ibl && ctxLocal->ibl->hasSH()) shBuf = ctxLocal->ibl->shBuffer().buffer;
|
||||
|
||||
// If SH missing, allocate zero UBO for this frame
|
||||
AllocatedBuffer shZero{};
|
||||
if (shBuf == VK_NULL_HANDLE)
|
||||
{
|
||||
shZero = resourceManager->create_buffer(shSize, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
std::memset(shZero.info.pMappedData, 0, shSize);
|
||||
vmaFlushAllocation(deviceManager->allocator(), shZero.allocation, 0, shSize);
|
||||
shBuf = shZero.buffer;
|
||||
ctxLocal->currentFrame->_deletionQueue.push_function([resourceManager, shZero]() { resourceManager->destroy_buffer(shZero); });
|
||||
}
|
||||
|
||||
iblSet = ctxLocal->currentFrame->_frameDescriptors.allocate(deviceManager->device(), iblLayout);
|
||||
DescriptorWriter iw;
|
||||
iw.write_image(0, specView, ctxLocal->getSamplers()->defaultLinear(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
|
||||
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER);
|
||||
iw.write_image(1, brdfView, ctxLocal->getSamplers()->defaultLinear(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
|
||||
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER);
|
||||
iw.write_buffer(2, shBuf, shSize, 0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER);
|
||||
iw.update_set(deviceManager->device(), iblSet);
|
||||
}
|
||||
|
||||
// Sort transparent back-to-front using camera-space depth.
|
||||
// We approximate object depth by transforming the mesh bounds origin.
|
||||
// For better results consider using per-object center or per-draw depth range.
|
||||
std::vector<const RenderObject *> draws;
|
||||
draws.reserve(dc.TransparentSurfaces.size());
|
||||
for (const auto &r: dc.TransparentSurfaces) draws.push_back(&r);
|
||||
|
||||
auto view = sceneData.view; // world -> view
|
||||
auto depthOf = [&](const RenderObject *r) {
|
||||
glm::vec4 c = r->transform * glm::vec4(r->bounds.origin, 1.f);
|
||||
float z = (view * c).z;
|
||||
return -z; // positive depth; larger = further
|
||||
};
|
||||
|
||||
std::sort(draws.begin(), draws.end(), [&](const RenderObject *A, const RenderObject *B) {
|
||||
return depthOf(A) > depthOf(B); // far to near
|
||||
});
|
||||
|
||||
VkExtent2D extent = ctxLocal->getDrawExtent();
|
||||
VkViewport viewport{0.f, 0.f, (float) extent.width, (float) extent.height, 0.f, 1.f};
|
||||
vkCmdSetViewport(cmd, 0, 1, &viewport);
|
||||
VkRect2D scissor{{0, 0}, extent};
|
||||
vkCmdSetScissor(cmd, 0, 1, &scissor);
|
||||
|
||||
MaterialPipeline *lastPipeline = nullptr;
|
||||
MaterialInstance *lastMaterial = nullptr;
|
||||
VkBuffer lastIndexBuffer = VK_NULL_HANDLE;
|
||||
|
||||
auto draw = [&](const RenderObject &r) {
|
||||
if (r.material != lastMaterial)
|
||||
{
|
||||
lastMaterial = r.material;
|
||||
if (r.material->pipeline != lastPipeline)
|
||||
{
|
||||
lastPipeline = r.material->pipeline;
|
||||
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, r.material->pipeline->pipeline);
|
||||
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, r.material->pipeline->layout, 0, 1,
|
||||
&globalDescriptor, 0, nullptr);
|
||||
if (iblSet)
|
||||
{
|
||||
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, r.material->pipeline->layout, 3, 1,
|
||||
&iblSet, 0, nullptr);
|
||||
}
|
||||
}
|
||||
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, r.material->pipeline->layout, 1, 1,
|
||||
&r.material->materialSet, 0, nullptr);
|
||||
if (ctxLocal->textures)
|
||||
{
|
||||
ctxLocal->textures->markSetUsed(r.material->materialSet, ctxLocal->frameIndex);
|
||||
}
|
||||
}
|
||||
if (r.indexBuffer != lastIndexBuffer)
|
||||
{
|
||||
lastIndexBuffer = r.indexBuffer;
|
||||
vkCmdBindIndexBuffer(cmd, r.indexBuffer, 0, VK_INDEX_TYPE_UINT32);
|
||||
}
|
||||
GPUDrawPushConstants push{};
|
||||
push.worldMatrix = r.transform;
|
||||
push.vertexBuffer = r.vertexBufferAddress;
|
||||
push.objectID = r.objectID;
|
||||
vkCmdPushConstants(cmd, r.material->pipeline->layout,
|
||||
VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT,
|
||||
0, sizeof(GPUDrawPushConstants), &push);
|
||||
vkCmdDrawIndexed(cmd, r.indexCount, 1, r.firstIndex, 0, 0);
|
||||
if (ctxLocal->stats)
|
||||
{
|
||||
ctxLocal->stats->drawcall_count++;
|
||||
ctxLocal->stats->triangle_count += r.indexCount / 3;
|
||||
}
|
||||
};
|
||||
|
||||
for (auto *pObj: draws) draw(*pObj);
|
||||
}
|
||||
|
||||
void TransparentPass::cleanup()
|
||||
{
|
||||
if (_context && _context->getResources())
|
||||
{
|
||||
if (_fallbackIbl2D.image) _context->getResources()->destroy_image(_fallbackIbl2D);
|
||||
if (_fallbackBrdf2D.image) _context->getResources()->destroy_image(_fallbackBrdf2D);
|
||||
}
|
||||
fmt::print("TransparentPass::cleanup()\n");
|
||||
}
|
||||
30
src/render/passes/transparent.h
Normal file
30
src/render/passes/transparent.h
Normal file
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#include "render/renderpass.h"
|
||||
#include "render/graph/types.h"
|
||||
|
||||
class TransparentPass : public IRenderPass
|
||||
{
|
||||
public:
|
||||
void init(EngineContext *context) override;
|
||||
void execute(VkCommandBuffer cmd) override;
|
||||
void cleanup() override;
|
||||
const char *getName() const override { return "Transparent"; }
|
||||
|
||||
// RenderGraph wiring
|
||||
void register_graph(class RenderGraph *graph,
|
||||
RGImageHandle drawHandle,
|
||||
RGImageHandle depthHandle);
|
||||
|
||||
private:
|
||||
void draw_transparent(VkCommandBuffer cmd,
|
||||
EngineContext *context,
|
||||
const class RGPassResources &resources,
|
||||
RGImageHandle drawHandle,
|
||||
RGImageHandle depthHandle) const;
|
||||
|
||||
EngineContext *_context{};
|
||||
mutable AllocatedImage _fallbackIbl2D{}; // 1x1 black (created in init)
|
||||
mutable AllocatedImage _fallbackBrdf2D{}; // 1x1 black RG
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user