initial commit-moved from vulkan_guide
This commit is contained in:
82
src/render/primitives.h
Normal file
82
src/render/primitives.h
Normal file
@@ -0,0 +1,82 @@
|
||||
#pragma once
|
||||
#include <vector>
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/constants.hpp>
|
||||
#include "core/vk_types.h"
|
||||
|
||||
namespace primitives {
|
||||
|
||||
inline void buildCube(std::vector<Vertex>& vertices, std::vector<uint32_t>& indices) {
|
||||
vertices.clear();
|
||||
indices.clear();
|
||||
|
||||
struct Face {
|
||||
glm::vec3 normal;
|
||||
glm::vec3 v0, v1, v2, v3;
|
||||
} faces[6] = {
|
||||
{ {0,0,1}, { -0.5f,-0.5f, 0.5f}, { 0.5f,-0.5f, 0.5f}, { -0.5f, 0.5f, 0.5f}, { 0.5f, 0.5f, 0.5f} },
|
||||
{ {0,0,-1},{ -0.5f,-0.5f,-0.5f}, { -0.5f, 0.5f,-0.5f}, { 0.5f,-0.5f,-0.5f}, { 0.5f, 0.5f,-0.5f} },
|
||||
{ {0,1,0}, { -0.5f, 0.5f, 0.5f}, { 0.5f, 0.5f, 0.5f}, { -0.5f, 0.5f,-0.5f}, { 0.5f, 0.5f,-0.5f} },
|
||||
{ {0,-1,0},{ -0.5f,-0.5f, 0.5f}, { -0.5f,-0.5f,-0.5f}, { 0.5f,-0.5f, 0.5f}, { 0.5f,-0.5f,-0.5f} },
|
||||
{ {1,0,0}, { 0.5f,-0.5f, 0.5f}, { 0.5f,-0.5f,-0.5f}, { 0.5f, 0.5f, 0.5f}, { 0.5f, 0.5f,-0.5f} },
|
||||
{ {-1,0,0},{ -0.5f,-0.5f, 0.5f}, { -0.5f, 0.5f, 0.5f}, { -0.5f,-0.5f,-0.5f}, { -0.5f, 0.5f,-0.5f} }
|
||||
};
|
||||
|
||||
for (auto& f : faces) {
|
||||
uint32_t start = (uint32_t)vertices.size();
|
||||
Vertex v0{f.v0, 0, f.normal, 0, glm::vec4(1.0f)};
|
||||
Vertex v1{f.v1, 1, f.normal, 0, glm::vec4(1.0f)};
|
||||
Vertex v2{f.v2, 0, f.normal, 1, glm::vec4(1.0f)};
|
||||
Vertex v3{f.v3, 1, f.normal, 1, glm::vec4(1.0f)};
|
||||
vertices.push_back(v0);
|
||||
vertices.push_back(v1);
|
||||
vertices.push_back(v2);
|
||||
vertices.push_back(v3);
|
||||
indices.push_back(start + 0);
|
||||
indices.push_back(start + 1);
|
||||
indices.push_back(start + 2);
|
||||
indices.push_back(start + 2);
|
||||
indices.push_back(start + 1);
|
||||
indices.push_back(start + 3);
|
||||
}
|
||||
}
|
||||
|
||||
inline void buildSphere(std::vector<Vertex>& vertices, std::vector<uint32_t>& indices, int sectors = 16, int stacks = 16) {
|
||||
vertices.clear();
|
||||
indices.clear();
|
||||
float radius = 0.5f;
|
||||
for (int i = 0; i <= stacks; ++i) {
|
||||
float v = (float)i / stacks;
|
||||
const float phi = v * glm::pi<float>();
|
||||
float y = cos(phi);
|
||||
float r = sin(phi);
|
||||
for (int j = 0; j <= sectors; ++j) {
|
||||
float u = (float)j / sectors;
|
||||
float theta = u * glm::two_pi<float>();
|
||||
float x = r * cos(theta);
|
||||
float z = r * sin(theta);
|
||||
Vertex vert;
|
||||
vert.position = glm::vec3(x, y, z) * radius;
|
||||
vert.normal = glm::normalize(glm::vec3(x, y, z));
|
||||
vert.uv_x = u;
|
||||
vert.uv_y = 1.0f - v;
|
||||
vert.color = glm::vec4(1.0f);
|
||||
vertices.push_back(vert);
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < stacks; ++i) {
|
||||
for (int j = 0; j < sectors; ++j) {
|
||||
uint32_t first = i * (sectors + 1) + j;
|
||||
uint32_t second = first + sectors + 1;
|
||||
indices.push_back(first);
|
||||
indices.push_back(second);
|
||||
indices.push_back(first + 1);
|
||||
indices.push_back(first + 1);
|
||||
indices.push_back(second);
|
||||
indices.push_back(second + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace primitives
|
||||
|
||||
98
src/render/rg_builder.cpp
Normal file
98
src/render/rg_builder.cpp
Normal file
@@ -0,0 +1,98 @@
|
||||
#include <render/rg_builder.h>
|
||||
#include <render/rg_resources.h>
|
||||
|
||||
// ---- RGPassResources ----
|
||||
VkImage RGPassResources::image(RGImageHandle h) const
|
||||
{
|
||||
const RGImageRecord *rec = _registry ? _registry->get_image(h) : nullptr;
|
||||
return rec ? rec->image : VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
VkImageView RGPassResources::image_view(RGImageHandle h) const
|
||||
{
|
||||
const RGImageRecord *rec = _registry ? _registry->get_image(h) : nullptr;
|
||||
return rec ? rec->imageView : VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
VkBuffer RGPassResources::buffer(RGBufferHandle h) const
|
||||
{
|
||||
const RGBufferRecord *rec = _registry ? _registry->get_buffer(h) : nullptr;
|
||||
return rec ? rec->buffer : VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
// ---- RGPassBuilder ----
|
||||
void RGPassBuilder::read(RGImageHandle h, RGImageUsage usage)
|
||||
{
|
||||
_imageReads.push_back({h, usage});
|
||||
}
|
||||
|
||||
void RGPassBuilder::write(RGImageHandle h, RGImageUsage usage)
|
||||
{
|
||||
_imageWrites.push_back({h, usage});
|
||||
}
|
||||
|
||||
void RGPassBuilder::read_buffer(RGBufferHandle h, RGBufferUsage usage)
|
||||
{
|
||||
_bufferReads.push_back({h, usage});
|
||||
}
|
||||
|
||||
void RGPassBuilder::write_buffer(RGBufferHandle h, RGBufferUsage usage)
|
||||
{
|
||||
_bufferWrites.push_back({h, usage});
|
||||
}
|
||||
|
||||
void RGPassBuilder::read_buffer(VkBuffer buffer, RGBufferUsage usage, VkDeviceSize size, const char* name)
|
||||
{
|
||||
if (!_registry || buffer == VK_NULL_HANDLE) return;
|
||||
// Dedup/import
|
||||
RGBufferHandle h = _registry->find_buffer(buffer);
|
||||
if (!h.valid())
|
||||
{
|
||||
RGImportedBufferDesc d{};
|
||||
d.name = name ? name : "external.buffer";
|
||||
d.buffer = buffer;
|
||||
d.size = size;
|
||||
d.currentStage = VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT;
|
||||
d.currentAccess = 0;
|
||||
h = _registry->add_imported(d);
|
||||
}
|
||||
read_buffer(h, usage);
|
||||
}
|
||||
|
||||
void RGPassBuilder::write_buffer(VkBuffer buffer, RGBufferUsage usage, VkDeviceSize size, const char* name)
|
||||
{
|
||||
if (!_registry || buffer == VK_NULL_HANDLE) return;
|
||||
RGBufferHandle h = _registry->find_buffer(buffer);
|
||||
if (!h.valid())
|
||||
{
|
||||
RGImportedBufferDesc d{};
|
||||
d.name = name ? name : "external.buffer";
|
||||
d.buffer = buffer;
|
||||
d.size = size;
|
||||
d.currentStage = VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT;
|
||||
d.currentAccess = 0;
|
||||
h = _registry->add_imported(d);
|
||||
}
|
||||
write_buffer(h, usage);
|
||||
}
|
||||
|
||||
void RGPassBuilder::write_color(RGImageHandle h, bool clearOnLoad, VkClearValue clear)
|
||||
{
|
||||
RGAttachmentInfo a{};
|
||||
a.image = h;
|
||||
a.clearOnLoad = clearOnLoad;
|
||||
a.clear = clear;
|
||||
a.store = true;
|
||||
_colors.push_back(a);
|
||||
write(h, RGImageUsage::ColorAttachment);
|
||||
}
|
||||
|
||||
void RGPassBuilder::write_depth(RGImageHandle h, bool clearOnLoad, VkClearValue clear)
|
||||
{
|
||||
if (_depthRef == nullptr) _depthRef = &_depthTemp;
|
||||
_depthRef->image = h;
|
||||
_depthRef->clearOnLoad = clearOnLoad;
|
||||
_depthRef->clear = clear;
|
||||
_depthRef->store = true;
|
||||
write(h, RGImageUsage::DepthAttachment);
|
||||
}
|
||||
90
src/render/rg_builder.h
Normal file
90
src/render/rg_builder.h
Normal file
@@ -0,0 +1,90 @@
|
||||
#pragma once
|
||||
|
||||
#include <render/rg_types.h>
|
||||
#include <vector>
|
||||
|
||||
class RGResourceRegistry;
|
||||
class EngineContext;
|
||||
|
||||
struct RGPassImageAccess
|
||||
{
|
||||
RGImageHandle image;
|
||||
RGImageUsage usage;
|
||||
};
|
||||
|
||||
struct RGPassBufferAccess
|
||||
{
|
||||
RGBufferHandle buffer;
|
||||
RGBufferUsage usage;
|
||||
};
|
||||
|
||||
// Read-only interface for pass record callbacks to fetch resolved resources
|
||||
class RGPassResources
|
||||
{
|
||||
public:
|
||||
RGPassResources(const RGResourceRegistry *registry) : _registry(registry)
|
||||
{
|
||||
}
|
||||
|
||||
VkImage image(RGImageHandle h) const;
|
||||
|
||||
VkImageView image_view(RGImageHandle h) const;
|
||||
|
||||
VkBuffer buffer(RGBufferHandle h) const;
|
||||
|
||||
private:
|
||||
const RGResourceRegistry *_registry;
|
||||
};
|
||||
|
||||
// Builder used inside add_*_pass setup lambda to declare reads/writes/attachments
|
||||
class RGPassBuilder
|
||||
{
|
||||
public:
|
||||
RGPassBuilder(RGResourceRegistry *registry,
|
||||
std::vector<RGPassImageAccess> &reads,
|
||||
std::vector<RGPassImageAccess> &writes,
|
||||
std::vector<RGPassBufferAccess> &bufferReads,
|
||||
std::vector<RGPassBufferAccess> &bufferWrites,
|
||||
std::vector<RGAttachmentInfo> &colorAttachments,
|
||||
RGAttachmentInfo *&depthAttachmentRef)
|
||||
: _registry(registry)
|
||||
, _imageReads(reads)
|
||||
, _imageWrites(writes)
|
||||
, _bufferReads(bufferReads)
|
||||
, _bufferWrites(bufferWrites)
|
||||
, _colors(colorAttachments)
|
||||
, _depthRef(depthAttachmentRef)
|
||||
{
|
||||
}
|
||||
|
||||
// Declare that the pass will sample/read an image
|
||||
void read(RGImageHandle h, RGImageUsage usage);
|
||||
|
||||
// Declare that the pass will write to an image
|
||||
void write(RGImageHandle h, RGImageUsage usage);
|
||||
|
||||
// Declare buffer accesses
|
||||
void read_buffer(RGBufferHandle h, RGBufferUsage usage);
|
||||
|
||||
void write_buffer(RGBufferHandle h, RGBufferUsage usage);
|
||||
|
||||
// Convenience: declare access to external VkBuffer. Will import/dedup and
|
||||
// register the access for this pass.
|
||||
void read_buffer(VkBuffer buffer, RGBufferUsage usage, VkDeviceSize size = 0, const char* name = nullptr);
|
||||
void write_buffer(VkBuffer buffer, RGBufferUsage usage, VkDeviceSize size = 0, const char* name = nullptr);
|
||||
|
||||
// Graphics attachments
|
||||
void write_color(RGImageHandle h, bool clearOnLoad = false, VkClearValue clear = {});
|
||||
|
||||
void write_depth(RGImageHandle h, bool clearOnLoad = false, VkClearValue clear = {});
|
||||
|
||||
private:
|
||||
RGResourceRegistry *_registry;
|
||||
std::vector<RGPassImageAccess> &_imageReads;
|
||||
std::vector<RGPassImageAccess> &_imageWrites;
|
||||
std::vector<RGPassBufferAccess> &_bufferReads;
|
||||
std::vector<RGPassBufferAccess> &_bufferWrites;
|
||||
std::vector<RGAttachmentInfo> &_colors;
|
||||
RGAttachmentInfo *&_depthRef;
|
||||
RGAttachmentInfo _depthTemp{}; // temporary storage used during build
|
||||
};
|
||||
887
src/render/rg_graph.cpp
Normal file
887
src/render/rg_graph.cpp
Normal file
@@ -0,0 +1,887 @@
|
||||
#include <render/rg_graph.h>
|
||||
#include <core/engine_context.h>
|
||||
#include <core/vk_images.h>
|
||||
#include <core/vk_initializers.h>
|
||||
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <queue>
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
|
||||
#include <core/vk_swapchain.h>
|
||||
#include <core/vk_initializers.h>
|
||||
#include <core/vk_debug.h>
|
||||
#include <fmt/core.h>
|
||||
|
||||
#include "vk_device.h"
|
||||
|
||||
void RenderGraph::init(EngineContext *ctx)
|
||||
{
|
||||
_context = ctx;
|
||||
_resources.init(ctx);
|
||||
}
|
||||
|
||||
void RenderGraph::clear()
|
||||
{
|
||||
_passes.clear();
|
||||
_resources.reset();
|
||||
}
|
||||
|
||||
RGImageHandle RenderGraph::import_image(const RGImportedImageDesc &desc)
|
||||
{
|
||||
return _resources.add_imported(desc);
|
||||
}
|
||||
|
||||
RGBufferHandle RenderGraph::import_buffer(const RGImportedBufferDesc &desc)
|
||||
{
|
||||
return _resources.add_imported(desc);
|
||||
}
|
||||
|
||||
RGImageHandle RenderGraph::create_image(const RGImageDesc &desc)
|
||||
{
|
||||
return _resources.add_transient(desc);
|
||||
}
|
||||
|
||||
RGImageHandle RenderGraph::create_depth_image(const char* name, VkExtent2D extent, VkFormat format)
|
||||
{
|
||||
RGImageDesc d{};
|
||||
d.name = name ? name : "depth.transient";
|
||||
d.format = format;
|
||||
d.extent = extent;
|
||||
d.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
|
||||
return create_image(d);
|
||||
}
|
||||
|
||||
RGBufferHandle RenderGraph::create_buffer(const RGBufferDesc &desc)
|
||||
{
|
||||
return _resources.add_transient(desc);
|
||||
}
|
||||
|
||||
void RenderGraph::add_pass(const char *name, RGPassType type, BuildCallback build, RecordCallback record)
|
||||
{
|
||||
Pass p{};
|
||||
p.name = name;
|
||||
p.type = type;
|
||||
p.record = std::move(record);
|
||||
|
||||
// Build declarations via builder
|
||||
RGAttachmentInfo *depthRef = nullptr;
|
||||
RGPassBuilder builder(&_resources,
|
||||
p.imageReads,
|
||||
p.imageWrites,
|
||||
p.bufferReads,
|
||||
p.bufferWrites,
|
||||
p.colorAttachments,
|
||||
depthRef);
|
||||
if (build) build(builder, _context);
|
||||
if (depthRef)
|
||||
{
|
||||
p.hasDepth = true;
|
||||
p.depthAttachment = *depthRef; // copy declared depth attachment
|
||||
}
|
||||
|
||||
_passes.push_back(std::move(p));
|
||||
}
|
||||
|
||||
void RenderGraph::add_pass(const char *name, RGPassType type, RecordCallback record)
|
||||
{
|
||||
// No declarations
|
||||
add_pass(name, type, nullptr, std::move(record));
|
||||
}
|
||||
|
||||
bool RenderGraph::compile()
|
||||
{
|
||||
if (!_context) return false;
|
||||
|
||||
// --- Build dependency graph (topological sort) from declared reads/writes ---
|
||||
const int n = static_cast<int>(_passes.size());
|
||||
if (n <= 1)
|
||||
{
|
||||
// trivial order; still compute barriers below
|
||||
}
|
||||
else
|
||||
{
|
||||
std::vector<std::unordered_set<int> > adjSet(n);
|
||||
std::vector<int> indeg(n, 0);
|
||||
|
||||
auto add_edge = [&](int u, int v) {
|
||||
if (u == v) return;
|
||||
if (u < 0 || v < 0 || u >= n || v >= n) return;
|
||||
if (adjSet[u].insert(v).second) indeg[v]++;
|
||||
};
|
||||
|
||||
std::unordered_map<uint32_t, int> lastWriterImage;
|
||||
std::unordered_map<uint32_t, std::vector<int> > lastReadersImage;
|
||||
std::unordered_map<uint32_t, int> lastWriterBuffer;
|
||||
std::unordered_map<uint32_t, std::vector<int> > lastReadersBuffer;
|
||||
|
||||
for (int i = 0; i < n; ++i)
|
||||
{
|
||||
const auto &p = _passes[i];
|
||||
if (!p.enabled) continue;
|
||||
|
||||
// Image reads
|
||||
for (const auto &r: p.imageReads)
|
||||
{
|
||||
if (!r.image.valid()) continue;
|
||||
auto it = lastWriterImage.find(r.image.id);
|
||||
if (it != lastWriterImage.end()) add_edge(it->second, i);
|
||||
lastReadersImage[r.image.id].push_back(i);
|
||||
}
|
||||
|
||||
// Image writes
|
||||
for (const auto &w: p.imageWrites)
|
||||
{
|
||||
if (!w.image.valid()) continue;
|
||||
auto itW = lastWriterImage.find(w.image.id);
|
||||
if (itW != lastWriterImage.end()) add_edge(itW->second, i); // WAW
|
||||
auto itR = lastReadersImage.find(w.image.id);
|
||||
if (itR != lastReadersImage.end())
|
||||
{
|
||||
for (int rIdx: itR->second) add_edge(rIdx, i); // WAR
|
||||
itR->second.clear();
|
||||
}
|
||||
lastWriterImage[w.image.id] = i;
|
||||
}
|
||||
|
||||
// Buffer reads
|
||||
for (const auto &r: p.bufferReads)
|
||||
{
|
||||
if (!r.buffer.valid()) continue;
|
||||
auto it = lastWriterBuffer.find(r.buffer.id);
|
||||
if (it != lastWriterBuffer.end()) add_edge(it->second, i);
|
||||
lastReadersBuffer[r.buffer.id].push_back(i);
|
||||
}
|
||||
|
||||
// Buffer writes
|
||||
for (const auto &w: p.bufferWrites)
|
||||
{
|
||||
if (!w.buffer.valid()) continue;
|
||||
auto itW = lastWriterBuffer.find(w.buffer.id);
|
||||
if (itW != lastWriterBuffer.end()) add_edge(itW->second, i); // WAW
|
||||
auto itR = lastReadersBuffer.find(w.buffer.id);
|
||||
if (itR != lastReadersBuffer.end())
|
||||
{
|
||||
for (int rIdx: itR->second) add_edge(rIdx, i); // WAR
|
||||
itR->second.clear();
|
||||
}
|
||||
lastWriterBuffer[w.buffer.id] = i;
|
||||
}
|
||||
}
|
||||
|
||||
// Kahn's algorithm
|
||||
std::queue<int> q;
|
||||
for (int i = 0; i < n; ++i) if (indeg[i] == 0) q.push(i);
|
||||
std::vector<int> order;
|
||||
order.reserve(n);
|
||||
while (!q.empty())
|
||||
{
|
||||
int u = q.front();
|
||||
q.pop();
|
||||
order.push_back(u);
|
||||
for (int v: adjSet[u])
|
||||
{
|
||||
if (--indeg[v] == 0) q.push(v);
|
||||
}
|
||||
}
|
||||
|
||||
if (static_cast<int>(order.size()) == n)
|
||||
{
|
||||
// Reorder passes by topological order
|
||||
std::vector<Pass> sorted;
|
||||
sorted.reserve(n);
|
||||
for (int idx: order) sorted.push_back(std::move(_passes[idx]));
|
||||
_passes = std::move(sorted);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Cycle detected; keep insertion order but still compute barriers
|
||||
}
|
||||
}
|
||||
|
||||
struct ImageState
|
||||
{
|
||||
bool initialized = false;
|
||||
VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
VkPipelineStageFlags2 stage = VK_PIPELINE_STAGE_2_NONE;
|
||||
VkAccessFlags2 access = 0;
|
||||
};
|
||||
|
||||
struct BufferState
|
||||
{
|
||||
bool initialized = false;
|
||||
VkPipelineStageFlags2 stage = VK_PIPELINE_STAGE_2_NONE;
|
||||
VkAccessFlags2 access = 0;
|
||||
};
|
||||
|
||||
auto is_depth_format = [](VkFormat format) {
|
||||
switch (format)
|
||||
{
|
||||
case VK_FORMAT_D16_UNORM:
|
||||
case VK_FORMAT_D16_UNORM_S8_UINT:
|
||||
case VK_FORMAT_D24_UNORM_S8_UINT:
|
||||
case VK_FORMAT_D32_SFLOAT:
|
||||
case VK_FORMAT_D32_SFLOAT_S8_UINT:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
auto usage_requires_flag = [](RGImageUsage usage) -> VkImageUsageFlags {
|
||||
switch (usage)
|
||||
{
|
||||
case RGImageUsage::SampledFragment:
|
||||
case RGImageUsage::SampledCompute:
|
||||
return VK_IMAGE_USAGE_SAMPLED_BIT;
|
||||
case RGImageUsage::TransferSrc:
|
||||
return VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
|
||||
case RGImageUsage::TransferDst:
|
||||
return VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
||||
case RGImageUsage::ColorAttachment:
|
||||
return VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
|
||||
case RGImageUsage::DepthAttachment:
|
||||
return VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
|
||||
case RGImageUsage::ComputeWrite:
|
||||
return VK_IMAGE_USAGE_STORAGE_BIT;
|
||||
case RGImageUsage::Present:
|
||||
return 0; // swapchain image
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
struct ImageUsageInfo
|
||||
{
|
||||
VkPipelineStageFlags2 stage;
|
||||
VkAccessFlags2 access;
|
||||
VkImageLayout layout;
|
||||
};
|
||||
|
||||
struct BufferUsageInfo
|
||||
{
|
||||
VkPipelineStageFlags2 stage;
|
||||
VkAccessFlags2 access;
|
||||
};
|
||||
|
||||
auto usage_info_image = [](RGImageUsage usage) {
|
||||
ImageUsageInfo info{};
|
||||
switch (usage)
|
||||
{
|
||||
case RGImageUsage::SampledFragment:
|
||||
info.stage = VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT;
|
||||
info.access = VK_ACCESS_2_SHADER_SAMPLED_READ_BIT;
|
||||
info.layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||
break;
|
||||
case RGImageUsage::SampledCompute:
|
||||
info.stage = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT;
|
||||
info.access = VK_ACCESS_2_SHADER_SAMPLED_READ_BIT;
|
||||
info.layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||
break;
|
||||
case RGImageUsage::TransferSrc:
|
||||
info.stage = VK_PIPELINE_STAGE_2_TRANSFER_BIT;
|
||||
info.access = VK_ACCESS_2_TRANSFER_READ_BIT;
|
||||
info.layout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
|
||||
break;
|
||||
case RGImageUsage::TransferDst:
|
||||
info.stage = VK_PIPELINE_STAGE_2_TRANSFER_BIT;
|
||||
info.access = VK_ACCESS_2_TRANSFER_WRITE_BIT;
|
||||
info.layout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||
break;
|
||||
case RGImageUsage::ColorAttachment:
|
||||
info.stage = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT;
|
||||
info.access = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_2_COLOR_ATTACHMENT_READ_BIT;
|
||||
info.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
||||
break;
|
||||
case RGImageUsage::DepthAttachment:
|
||||
info.stage = VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT;
|
||||
info.access = VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT |
|
||||
VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_READ_BIT;
|
||||
info.layout = VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL;
|
||||
break;
|
||||
case RGImageUsage::ComputeWrite:
|
||||
info.stage = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT;
|
||||
info.access = VK_ACCESS_2_SHADER_STORAGE_READ_BIT | VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT;
|
||||
info.layout = VK_IMAGE_LAYOUT_GENERAL;
|
||||
break;
|
||||
case RGImageUsage::Present:
|
||||
info.stage = VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT;
|
||||
info.access = VK_ACCESS_2_MEMORY_READ_BIT;
|
||||
info.layout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
|
||||
break;
|
||||
default:
|
||||
info.stage = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT;
|
||||
info.access = VK_ACCESS_2_MEMORY_READ_BIT | VK_ACCESS_2_MEMORY_WRITE_BIT;
|
||||
info.layout = VK_IMAGE_LAYOUT_GENERAL;
|
||||
break;
|
||||
}
|
||||
return info;
|
||||
};
|
||||
|
||||
auto usage_info_buffer = [](RGBufferUsage usage) {
|
||||
BufferUsageInfo info{};
|
||||
switch (usage)
|
||||
{
|
||||
case RGBufferUsage::TransferSrc:
|
||||
info.stage = VK_PIPELINE_STAGE_2_TRANSFER_BIT;
|
||||
info.access = VK_ACCESS_2_TRANSFER_READ_BIT;
|
||||
break;
|
||||
case RGBufferUsage::TransferDst:
|
||||
info.stage = VK_PIPELINE_STAGE_2_TRANSFER_BIT;
|
||||
info.access = VK_ACCESS_2_TRANSFER_WRITE_BIT;
|
||||
break;
|
||||
case RGBufferUsage::VertexRead:
|
||||
info.stage = VK_PIPELINE_STAGE_2_VERTEX_INPUT_BIT;
|
||||
info.access = VK_ACCESS_2_VERTEX_ATTRIBUTE_READ_BIT;
|
||||
break;
|
||||
case RGBufferUsage::IndexRead:
|
||||
info.stage = VK_PIPELINE_STAGE_2_INDEX_INPUT_BIT;
|
||||
info.access = VK_ACCESS_2_INDEX_READ_BIT;
|
||||
break;
|
||||
case RGBufferUsage::UniformRead:
|
||||
info.stage = VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT | VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT;
|
||||
info.access = VK_ACCESS_2_UNIFORM_READ_BIT;
|
||||
break;
|
||||
case RGBufferUsage::StorageRead:
|
||||
info.stage = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT | VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT;
|
||||
info.access = VK_ACCESS_2_SHADER_STORAGE_READ_BIT;
|
||||
break;
|
||||
case RGBufferUsage::StorageReadWrite:
|
||||
info.stage = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT | VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT;
|
||||
info.access = VK_ACCESS_2_SHADER_STORAGE_READ_BIT | VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT;
|
||||
break;
|
||||
case RGBufferUsage::IndirectArgs:
|
||||
info.stage = VK_PIPELINE_STAGE_2_DRAW_INDIRECT_BIT;
|
||||
info.access = VK_ACCESS_2_INDIRECT_COMMAND_READ_BIT;
|
||||
break;
|
||||
default:
|
||||
info.stage = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT;
|
||||
info.access = VK_ACCESS_2_MEMORY_READ_BIT | VK_ACCESS_2_MEMORY_WRITE_BIT;
|
||||
break;
|
||||
}
|
||||
return info;
|
||||
};
|
||||
|
||||
auto buffer_usage_requires_flag = [](RGBufferUsage usage) -> VkBufferUsageFlags {
|
||||
switch (usage)
|
||||
{
|
||||
case RGBufferUsage::TransferSrc: return VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
|
||||
case RGBufferUsage::TransferDst: return VK_BUFFER_USAGE_TRANSFER_DST_BIT;
|
||||
case RGBufferUsage::VertexRead: return VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
|
||||
case RGBufferUsage::IndexRead: return VK_BUFFER_USAGE_INDEX_BUFFER_BIT;
|
||||
case RGBufferUsage::UniformRead: return VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT;
|
||||
case RGBufferUsage::StorageRead:
|
||||
case RGBufferUsage::StorageReadWrite: return VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
|
||||
case RGBufferUsage::IndirectArgs: return VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT;
|
||||
default: return 0;
|
||||
}
|
||||
};
|
||||
|
||||
const size_t imageCount = _resources.image_count();
|
||||
const size_t bufferCount = _resources.buffer_count();
|
||||
std::vector<ImageState> imageStates(imageCount);
|
||||
std::vector<BufferState> bufferStates(bufferCount);
|
||||
|
||||
// Track first/last use for lifetime diagnostics and future aliasing
|
||||
std::vector<int> imageFirst(imageCount, -1), imageLast(imageCount, -1);
|
||||
std::vector<int> bufferFirst(bufferCount, -1), bufferLast(bufferCount, -1);
|
||||
|
||||
for (auto &pass: _passes)
|
||||
{
|
||||
pass.preImageBarriers.clear();
|
||||
pass.preBufferBarriers.clear();
|
||||
if (!pass.enabled) { continue; }
|
||||
|
||||
std::unordered_map<uint32_t, RGImageUsage> desiredImageUsages;
|
||||
desiredImageUsages.reserve(pass.imageReads.size() + pass.imageWrites.size());
|
||||
|
||||
for (const auto &access: pass.imageReads)
|
||||
{
|
||||
if (!access.image.valid()) continue;
|
||||
desiredImageUsages.emplace(access.image.id, access.usage);
|
||||
if (access.image.id < imageCount)
|
||||
{
|
||||
if (imageFirst[access.image.id] == -1) imageFirst[access.image.id] = (int)(&pass - _passes.data());
|
||||
imageLast[access.image.id] = (int)(&pass - _passes.data());
|
||||
}
|
||||
}
|
||||
for (const auto &access: pass.imageWrites)
|
||||
{
|
||||
if (!access.image.valid()) continue;
|
||||
desiredImageUsages[access.image.id] = access.usage;
|
||||
if (access.image.id < imageCount)
|
||||
{
|
||||
if (imageFirst[access.image.id] == -1) imageFirst[access.image.id] = (int)(&pass - _passes.data());
|
||||
imageLast[access.image.id] = (int)(&pass - _passes.data());
|
||||
}
|
||||
}
|
||||
|
||||
// Validation: basic layout/format/usage checks for images used by this pass
|
||||
// Also build barriers
|
||||
for (const auto &[id, usage]: desiredImageUsages)
|
||||
{
|
||||
if (id >= imageCount) continue;
|
||||
|
||||
ImageUsageInfo desired = usage_info_image(usage);
|
||||
|
||||
ImageState prev = imageStates[id];
|
||||
VkImageLayout prevLayout = prev.initialized ? prev.layout : _resources.initial_layout(RGImageHandle{id});
|
||||
VkPipelineStageFlags2 srcStage = prev.initialized
|
||||
? prev.stage
|
||||
: (prevLayout == VK_IMAGE_LAYOUT_UNDEFINED
|
||||
? VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT
|
||||
: VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT);
|
||||
VkAccessFlags2 srcAccess = prev.initialized
|
||||
? prev.access
|
||||
: (prevLayout == VK_IMAGE_LAYOUT_UNDEFINED
|
||||
? VkAccessFlags2{0}
|
||||
: (VK_ACCESS_2_MEMORY_READ_BIT | VK_ACCESS_2_MEMORY_WRITE_BIT));
|
||||
|
||||
bool needBarrier = !prev.initialized
|
||||
|| prevLayout != desired.layout
|
||||
|| prev.stage != desired.stage
|
||||
|| prev.access != desired.access;
|
||||
|
||||
if (needBarrier)
|
||||
{
|
||||
VkImageMemoryBarrier2 barrier{.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2};
|
||||
barrier.srcStageMask = srcStage;
|
||||
barrier.srcAccessMask = srcAccess;
|
||||
barrier.dstStageMask = desired.stage;
|
||||
barrier.dstAccessMask = desired.access;
|
||||
barrier.oldLayout = prevLayout;
|
||||
barrier.newLayout = desired.layout;
|
||||
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
|
||||
const RGImageRecord *rec = _resources.get_image(RGImageHandle{id});
|
||||
barrier.image = rec ? rec->image : VK_NULL_HANDLE;
|
||||
|
||||
VkImageAspectFlags aspect = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
if (usage == RGImageUsage::DepthAttachment || (rec && is_depth_format(rec->format)))
|
||||
{
|
||||
aspect = VK_IMAGE_ASPECT_DEPTH_BIT;
|
||||
}
|
||||
barrier.subresourceRange = vkinit::image_subresource_range(aspect);
|
||||
pass.preImageBarriers.push_back(barrier);
|
||||
|
||||
// Validation messages (debug-only style):
|
||||
if (rec)
|
||||
{
|
||||
// Color attachments should not be depth formats and vice versa
|
||||
if (usage == RGImageUsage::ColorAttachment && is_depth_format(rec->format))
|
||||
{
|
||||
fmt::println("[RG][Warn] Pass '{}' binds depth-format image '{}' as color attachment.",
|
||||
pass.name, rec->name);
|
||||
}
|
||||
if (usage == RGImageUsage::DepthAttachment && !is_depth_format(rec->format))
|
||||
{
|
||||
fmt::println("[RG][Warn] Pass '{}' binds non-depth image '{}' as depth attachment.",
|
||||
pass.name, rec->name);
|
||||
}
|
||||
// Usage flag sanity for transients we created
|
||||
if (!rec->imported)
|
||||
{
|
||||
VkImageUsageFlags need = usage_requires_flag(usage);
|
||||
if ((need & rec->creationUsage) != need)
|
||||
{
|
||||
fmt::println("[RG][Warn] Image '{}' used as '{}' but created without needed usage flags (0x{:x}).",
|
||||
rec->name, (int)usage, (unsigned)need);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
imageStates[id].initialized = true;
|
||||
imageStates[id].layout = desired.layout;
|
||||
imageStates[id].stage = desired.stage;
|
||||
imageStates[id].access = desired.access;
|
||||
}
|
||||
|
||||
if (bufferCount == 0) continue;
|
||||
|
||||
std::unordered_map<uint32_t, RGBufferUsage> desiredBufferUsages;
|
||||
desiredBufferUsages.reserve(pass.bufferReads.size() + pass.bufferWrites.size());
|
||||
|
||||
for (const auto &access: pass.bufferReads)
|
||||
{
|
||||
if (!access.buffer.valid()) continue;
|
||||
desiredBufferUsages.emplace(access.buffer.id, access.usage);
|
||||
if (access.buffer.id < bufferCount)
|
||||
{
|
||||
if (bufferFirst[access.buffer.id] == -1) bufferFirst[access.buffer.id] = (int)(&pass - _passes.data());
|
||||
bufferLast[access.buffer.id] = (int)(&pass - _passes.data());
|
||||
}
|
||||
}
|
||||
for (const auto &access: pass.bufferWrites)
|
||||
{
|
||||
if (!access.buffer.valid()) continue;
|
||||
desiredBufferUsages[access.buffer.id] = access.usage;
|
||||
if (access.buffer.id < bufferCount)
|
||||
{
|
||||
if (bufferFirst[access.buffer.id] == -1) bufferFirst[access.buffer.id] = (int)(&pass - _passes.data());
|
||||
bufferLast[access.buffer.id] = (int)(&pass - _passes.data());
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &[id, usage]: desiredBufferUsages)
|
||||
{
|
||||
if (id >= bufferCount) continue;
|
||||
|
||||
BufferUsageInfo desired = usage_info_buffer(usage);
|
||||
|
||||
BufferState prev = bufferStates[id];
|
||||
VkPipelineStageFlags2 srcStage = prev.initialized
|
||||
? prev.stage
|
||||
: _resources.initial_stage(RGBufferHandle{id});
|
||||
if (srcStage == VK_PIPELINE_STAGE_2_NONE)
|
||||
{
|
||||
srcStage = VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT;
|
||||
}
|
||||
VkAccessFlags2 srcAccess = prev.initialized
|
||||
? prev.access
|
||||
: _resources.initial_access(RGBufferHandle{id});
|
||||
|
||||
bool needBarrier = !prev.initialized
|
||||
|| prev.stage != desired.stage
|
||||
|| prev.access != desired.access;
|
||||
|
||||
if (needBarrier)
|
||||
{
|
||||
VkBufferMemoryBarrier2 barrier{.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2};
|
||||
barrier.srcStageMask = srcStage;
|
||||
barrier.srcAccessMask = srcAccess;
|
||||
barrier.dstStageMask = desired.stage;
|
||||
barrier.dstAccessMask = desired.access;
|
||||
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
|
||||
const RGBufferRecord *rec = _resources.get_buffer(RGBufferHandle{id});
|
||||
barrier.buffer = rec ? rec->buffer : VK_NULL_HANDLE;
|
||||
barrier.offset = 0;
|
||||
barrier.size = rec ? rec->size : VK_WHOLE_SIZE;
|
||||
pass.preBufferBarriers.push_back(barrier);
|
||||
|
||||
if (rec && !rec->imported)
|
||||
{
|
||||
VkBufferUsageFlags need = buffer_usage_requires_flag(usage);
|
||||
if ((need & rec->usage) != need)
|
||||
{
|
||||
fmt::println("[RG][Warn] Buffer '{}' used as '{}' but created without needed usage flags (0x{:x}).",
|
||||
rec->name, (int)usage, (unsigned)need);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bufferStates[id].initialized = true;
|
||||
bufferStates[id].stage = desired.stage;
|
||||
bufferStates[id].access = desired.access;
|
||||
}
|
||||
}
|
||||
|
||||
// Store lifetimes into records for diagnostics/aliasing
|
||||
for (size_t i = 0; i < imageCount; ++i)
|
||||
{
|
||||
if (auto *rec = _resources.get_image(RGImageHandle{static_cast<uint32_t>(i)}))
|
||||
{
|
||||
rec->firstUse = imageFirst[i];
|
||||
rec->lastUse = imageLast[i];
|
||||
}
|
||||
}
|
||||
for (size_t i = 0; i < bufferCount; ++i)
|
||||
{
|
||||
if (auto *rec = _resources.get_buffer(RGBufferHandle{static_cast<uint32_t>(i)}))
|
||||
{
|
||||
rec->firstUse = bufferFirst[i];
|
||||
rec->lastUse = bufferLast[i];
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void RenderGraph::execute(VkCommandBuffer cmd)
|
||||
{
|
||||
for (size_t passIndex = 0; passIndex < _passes.size(); ++passIndex)
|
||||
{
|
||||
auto &p = _passes[passIndex];
|
||||
if (!p.enabled) continue;
|
||||
|
||||
// Debug label per pass
|
||||
if (_context && _context->getDevice())
|
||||
{
|
||||
char labelName[128];
|
||||
std::snprintf(labelName, sizeof(labelName), "RG: %s", p.name.c_str());
|
||||
vkdebug::cmd_begin_label(_context->getDevice()->device(), cmd, labelName);
|
||||
}
|
||||
|
||||
if (!p.preImageBarriers.empty() || !p.preBufferBarriers.empty())
|
||||
{
|
||||
VkDependencyInfo dep{.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO};
|
||||
dep.imageMemoryBarrierCount = static_cast<uint32_t>(p.preImageBarriers.size());
|
||||
dep.pImageMemoryBarriers = p.preImageBarriers.empty() ? nullptr : p.preImageBarriers.data();
|
||||
dep.bufferMemoryBarrierCount = static_cast<uint32_t>(p.preBufferBarriers.size());
|
||||
dep.pBufferMemoryBarriers = p.preBufferBarriers.empty() ? nullptr : p.preBufferBarriers.data();
|
||||
vkCmdPipelineBarrier2(cmd, &dep);
|
||||
}
|
||||
|
||||
// Begin dynamic rendering if the pass declared attachments
|
||||
bool doRendering = (!p.colorAttachments.empty() || p.hasDepth);
|
||||
if (doRendering)
|
||||
{
|
||||
std::vector<VkRenderingAttachmentInfo> colorInfos;
|
||||
colorInfos.reserve(p.colorAttachments.size());
|
||||
VkRenderingAttachmentInfo depthInfo{};
|
||||
bool hasDepth = false;
|
||||
|
||||
// Choose renderArea as the min of all attachment extents and the desired draw extent
|
||||
VkExtent2D chosenExtent{_context->getDrawExtent()};
|
||||
auto clamp_min = [](VkExtent2D a, VkExtent2D b) {
|
||||
return VkExtent2D{std::min(a.width, b.width), std::min(a.height, b.height)};
|
||||
};
|
||||
|
||||
// Resolve color attachments
|
||||
VkExtent2D firstColorExtent{0,0};
|
||||
bool warnedExtentMismatch = false;
|
||||
for (const auto &a: p.colorAttachments)
|
||||
{
|
||||
const RGImageRecord *rec = _resources.get_image(a.image);
|
||||
if (!rec || rec->imageView == VK_NULL_HANDLE) continue;
|
||||
VkClearValue *pClear = nullptr;
|
||||
VkClearValue clear = a.clear;
|
||||
if (a.clearOnLoad) pClear = &clear;
|
||||
VkRenderingAttachmentInfo info = vkinit::attachment_info(rec->imageView, pClear,
|
||||
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
|
||||
if (!a.store) info.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||
colorInfos.push_back(info);
|
||||
if (rec->extent.width && rec->extent.height) chosenExtent = clamp_min(chosenExtent, rec->extent);
|
||||
if (firstColorExtent.width == 0 && firstColorExtent.height == 0)
|
||||
{
|
||||
firstColorExtent = rec->extent;
|
||||
}
|
||||
else if (!warnedExtentMismatch && (rec->extent.width != firstColorExtent.width || rec->extent.height != firstColorExtent.height))
|
||||
{
|
||||
fmt::println("[RG][Warn] Pass '{}' has color attachments with mismatched extents ({}x{} vs {}x{}). Using min().",
|
||||
p.name,
|
||||
firstColorExtent.width, firstColorExtent.height,
|
||||
rec->extent.width, rec->extent.height);
|
||||
warnedExtentMismatch = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (p.hasDepth)
|
||||
{
|
||||
const RGImageRecord *rec = _resources.get_image(p.depthAttachment.image);
|
||||
if (rec && rec->imageView != VK_NULL_HANDLE)
|
||||
{
|
||||
depthInfo = vkinit::depth_attachment_info(rec->imageView, VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL);
|
||||
if (p.depthAttachment.clearOnLoad) depthInfo.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
||||
else depthInfo.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
|
||||
if (!p.depthAttachment.store) depthInfo.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||
hasDepth = true;
|
||||
if (rec->extent.width && rec->extent.height) chosenExtent = clamp_min(chosenExtent, rec->extent);
|
||||
}
|
||||
}
|
||||
|
||||
VkRenderingInfo ri{};
|
||||
ri.sType = VK_STRUCTURE_TYPE_RENDERING_INFO;
|
||||
ri.renderArea = VkRect2D{VkOffset2D{0, 0}, chosenExtent};
|
||||
ri.layerCount = 1;
|
||||
ri.colorAttachmentCount = static_cast<uint32_t>(colorInfos.size());
|
||||
ri.pColorAttachments = colorInfos.empty() ? nullptr : colorInfos.data();
|
||||
ri.pDepthAttachment = hasDepth ? &depthInfo : nullptr;
|
||||
ri.pStencilAttachment = nullptr;
|
||||
|
||||
vkCmdBeginRendering(cmd, &ri);
|
||||
}
|
||||
|
||||
if (p.record)
|
||||
{
|
||||
RGPassResources res(&_resources);
|
||||
p.record(cmd, res, _context);
|
||||
}
|
||||
|
||||
if (doRendering)
|
||||
{
|
||||
vkCmdEndRendering(cmd);
|
||||
}
|
||||
|
||||
if (_context && _context->getDevice())
|
||||
{
|
||||
vkdebug::cmd_end_label(_context->getDevice()->device(), cmd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Import helpers ---
|
||||
void RenderGraph::add_present_chain(RGImageHandle sourceDraw,
|
||||
RGImageHandle targetSwapchain,
|
||||
std::function<void(RenderGraph &)> appendExtra)
|
||||
{
|
||||
if (!sourceDraw.valid() || !targetSwapchain.valid()) return;
|
||||
|
||||
add_pass(
|
||||
"CopyToSwapchain",
|
||||
RGPassType::Transfer,
|
||||
[sourceDraw, targetSwapchain](RGPassBuilder &builder, EngineContext *) {
|
||||
builder.read(sourceDraw, RGImageUsage::TransferSrc);
|
||||
builder.write(targetSwapchain, RGImageUsage::TransferDst);
|
||||
},
|
||||
[sourceDraw, targetSwapchain](VkCommandBuffer cmd, const RGPassResources &res, EngineContext *ctx) {
|
||||
VkImage src = res.image(sourceDraw);
|
||||
VkImage dst = res.image(targetSwapchain);
|
||||
if (src == VK_NULL_HANDLE || dst == VK_NULL_HANDLE) return;
|
||||
vkutil::copy_image_to_image(cmd, src, dst, ctx->getDrawExtent(), ctx->getSwapchain()->swapchainExtent());
|
||||
});
|
||||
|
||||
if (appendExtra)
|
||||
{
|
||||
appendExtra(*this);
|
||||
}
|
||||
|
||||
add_pass(
|
||||
"PreparePresent",
|
||||
RGPassType::Transfer,
|
||||
[targetSwapchain](RGPassBuilder &builder, EngineContext *) {
|
||||
builder.write(targetSwapchain, RGImageUsage::Present);
|
||||
},
|
||||
[](VkCommandBuffer, const RGPassResources &, EngineContext *) {
|
||||
});
|
||||
}
|
||||
|
||||
RGImageHandle RenderGraph::import_draw_image()
|
||||
{
|
||||
RGImportedImageDesc d{};
|
||||
d.name = "drawImage";
|
||||
d.image = _context->getSwapchain()->drawImage().image;
|
||||
d.imageView = _context->getSwapchain()->drawImage().imageView;
|
||||
d.format = _context->getSwapchain()->drawImage().imageFormat;
|
||||
d.extent = _context->getDrawExtent();
|
||||
d.currentLayout = VK_IMAGE_LAYOUT_GENERAL;
|
||||
return import_image(d);
|
||||
}
|
||||
|
||||
// --- Debug helpers ---
|
||||
void RenderGraph::debug_get_passes(std::vector<RGDebugPassInfo> &out) const
|
||||
{
|
||||
out.clear();
|
||||
out.reserve(_passes.size());
|
||||
for (const auto &p : _passes)
|
||||
{
|
||||
RGDebugPassInfo info{};
|
||||
info.name = p.name;
|
||||
info.type = p.type;
|
||||
info.enabled = p.enabled;
|
||||
info.imageReads = static_cast<uint32_t>(p.imageReads.size());
|
||||
info.imageWrites = static_cast<uint32_t>(p.imageWrites.size());
|
||||
info.bufferReads = static_cast<uint32_t>(p.bufferReads.size());
|
||||
info.bufferWrites = static_cast<uint32_t>(p.bufferWrites.size());
|
||||
info.colorAttachmentCount = static_cast<uint32_t>(p.colorAttachments.size());
|
||||
info.hasDepth = p.hasDepth;
|
||||
out.push_back(std::move(info));
|
||||
}
|
||||
}
|
||||
|
||||
void RenderGraph::debug_get_images(std::vector<RGDebugImageInfo> &out) const
|
||||
{
|
||||
out.clear();
|
||||
out.reserve(_resources.image_count());
|
||||
for (uint32_t i = 0; i < _resources.image_count(); ++i)
|
||||
{
|
||||
const RGImageRecord *rec = _resources.get_image(RGImageHandle{i});
|
||||
if (!rec) continue;
|
||||
RGDebugImageInfo info{};
|
||||
info.id = i;
|
||||
info.name = rec->name;
|
||||
info.imported = rec->imported;
|
||||
info.format = rec->format;
|
||||
info.extent = rec->extent;
|
||||
info.creationUsage = rec->creationUsage;
|
||||
info.firstUse = rec->firstUse;
|
||||
info.lastUse = rec->lastUse;
|
||||
out.push_back(std::move(info));
|
||||
}
|
||||
}
|
||||
|
||||
void RenderGraph::debug_get_buffers(std::vector<RGDebugBufferInfo> &out) const
|
||||
{
|
||||
out.clear();
|
||||
out.reserve(_resources.buffer_count());
|
||||
for (uint32_t i = 0; i < _resources.buffer_count(); ++i)
|
||||
{
|
||||
const RGBufferRecord *rec = _resources.get_buffer(RGBufferHandle{i});
|
||||
if (!rec) continue;
|
||||
RGDebugBufferInfo info{};
|
||||
info.id = i;
|
||||
info.name = rec->name;
|
||||
info.imported = rec->imported;
|
||||
info.size = rec->size;
|
||||
info.usage = rec->usage;
|
||||
info.firstUse = rec->firstUse;
|
||||
info.lastUse = rec->lastUse;
|
||||
out.push_back(std::move(info));
|
||||
}
|
||||
}
|
||||
|
||||
RGImageHandle RenderGraph::import_depth_image()
|
||||
{
|
||||
RGImportedImageDesc d{};
|
||||
d.name = "depthImage";
|
||||
d.image = _context->getSwapchain()->depthImage().image;
|
||||
d.imageView = _context->getSwapchain()->depthImage().imageView;
|
||||
d.format = _context->getSwapchain()->depthImage().imageFormat;
|
||||
d.extent = _context->getDrawExtent();
|
||||
d.currentLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
return import_image(d);
|
||||
}
|
||||
|
||||
RGImageHandle RenderGraph::import_gbuffer_position()
|
||||
{
|
||||
RGImportedImageDesc d{};
|
||||
d.name = "gBuffer.position";
|
||||
d.image = _context->getSwapchain()->gBufferPosition().image;
|
||||
d.imageView = _context->getSwapchain()->gBufferPosition().imageView;
|
||||
d.format = _context->getSwapchain()->gBufferPosition().imageFormat;
|
||||
d.extent = _context->getDrawExtent();
|
||||
d.currentLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
return import_image(d);
|
||||
}
|
||||
|
||||
RGImageHandle RenderGraph::import_gbuffer_normal()
|
||||
{
|
||||
RGImportedImageDesc d{};
|
||||
d.name = "gBuffer.normal";
|
||||
d.image = _context->getSwapchain()->gBufferNormal().image;
|
||||
d.imageView = _context->getSwapchain()->gBufferNormal().imageView;
|
||||
d.format = _context->getSwapchain()->gBufferNormal().imageFormat;
|
||||
d.extent = _context->getDrawExtent();
|
||||
d.currentLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
return import_image(d);
|
||||
}
|
||||
|
||||
RGImageHandle RenderGraph::import_gbuffer_albedo()
|
||||
{
|
||||
RGImportedImageDesc d{};
|
||||
d.name = "gBuffer.albedo";
|
||||
d.image = _context->getSwapchain()->gBufferAlbedo().image;
|
||||
d.imageView = _context->getSwapchain()->gBufferAlbedo().imageView;
|
||||
d.format = _context->getSwapchain()->gBufferAlbedo().imageFormat;
|
||||
d.extent = _context->getDrawExtent();
|
||||
d.currentLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
return import_image(d);
|
||||
}
|
||||
|
||||
RGImageHandle RenderGraph::import_swapchain_image(uint32_t index)
|
||||
{
|
||||
RGImportedImageDesc d{};
|
||||
d.name = "swapchain.image";
|
||||
const auto &views = _context->getSwapchain()->swapchainImageViews();
|
||||
const auto &imgs = _context->getSwapchain()->swapchainImages();
|
||||
d.image = imgs[index];
|
||||
d.imageView = views[index];
|
||||
d.format = _context->getSwapchain()->swapchainImageFormat();
|
||||
d.extent = _context->getSwapchain()->swapchainExtent();
|
||||
d.currentLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
|
||||
return import_image(d);
|
||||
}
|
||||
142
src/render/rg_graph.h
Normal file
142
src/render/rg_graph.h
Normal file
@@ -0,0 +1,142 @@
|
||||
#pragma once
|
||||
|
||||
#include <core/vk_types.h>
|
||||
#include <render/rg_types.h>
|
||||
#include <render/rg_resources.h>
|
||||
#include <render/rg_builder.h>
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
class EngineContext;
|
||||
|
||||
class RenderGraph
|
||||
{
|
||||
public:
|
||||
void init(EngineContext* ctx);
|
||||
void clear();
|
||||
|
||||
// Import externally owned images (swapchain, drawImage, g-buffers)
|
||||
RGImageHandle import_image(const RGImportedImageDesc& desc);
|
||||
|
||||
// Create transient images (not used in v1 skeleton; stubbed for future)
|
||||
RGImageHandle create_image(const RGImageDesc& desc);
|
||||
|
||||
// Convenience: create a transient depth image suitable for shadow mapping or depth-only passes
|
||||
// Format defaults to D32_SFLOAT; usage is depth attachment + sampled so it can be read later.
|
||||
RGImageHandle create_depth_image(const char* name, VkExtent2D extent, VkFormat format = VK_FORMAT_D32_SFLOAT);
|
||||
|
||||
// Buffer import/create helpers
|
||||
RGBufferHandle import_buffer(const RGImportedBufferDesc& desc);
|
||||
RGBufferHandle create_buffer(const RGBufferDesc& desc);
|
||||
|
||||
// Pass builder API
|
||||
struct Pass; // fwd
|
||||
using RecordCallback = std::function<void(VkCommandBuffer cmd, const class RGPassResources& res, EngineContext* ctx)>;
|
||||
using BuildCallback = std::function<void(class RGPassBuilder& b, EngineContext* ctx)>;
|
||||
|
||||
void add_pass(const char* name, RGPassType type, BuildCallback build, RecordCallback record);
|
||||
// Legacy simple add
|
||||
void add_pass(const char* name, RGPassType type, RecordCallback record);
|
||||
|
||||
// Build internal state for this frame (no-op in v1)
|
||||
bool compile();
|
||||
|
||||
// Execute in insertion order (skips disabled passes)
|
||||
void execute(VkCommandBuffer cmd);
|
||||
|
||||
// Convenience import helpers (read from EngineContext::swapchain)
|
||||
RGImageHandle import_draw_image();
|
||||
RGImageHandle import_depth_image();
|
||||
RGImageHandle import_gbuffer_position();
|
||||
RGImageHandle import_gbuffer_normal();
|
||||
RGImageHandle import_gbuffer_albedo();
|
||||
RGImageHandle import_swapchain_image(uint32_t index);
|
||||
void add_present_chain(RGImageHandle sourceDraw,
|
||||
RGImageHandle targetSwapchain,
|
||||
std::function<void(RenderGraph&)> appendExtra = {});
|
||||
|
||||
// --- Debug helpers ---
|
||||
struct RGDebugPassInfo
|
||||
{
|
||||
std::string name;
|
||||
RGPassType type{};
|
||||
bool enabled = true;
|
||||
uint32_t imageReads = 0;
|
||||
uint32_t imageWrites = 0;
|
||||
uint32_t bufferReads = 0;
|
||||
uint32_t bufferWrites = 0;
|
||||
uint32_t colorAttachmentCount = 0;
|
||||
bool hasDepth = false;
|
||||
};
|
||||
|
||||
struct RGDebugImageInfo
|
||||
{
|
||||
uint32_t id{};
|
||||
std::string name;
|
||||
bool imported = true;
|
||||
VkFormat format = VK_FORMAT_UNDEFINED;
|
||||
VkExtent2D extent{0,0};
|
||||
VkImageUsageFlags creationUsage = 0;
|
||||
int firstUse = -1;
|
||||
int lastUse = -1;
|
||||
};
|
||||
|
||||
struct RGDebugBufferInfo
|
||||
{
|
||||
uint32_t id{};
|
||||
std::string name;
|
||||
bool imported = true;
|
||||
VkDeviceSize size = 0;
|
||||
VkBufferUsageFlags usage = 0;
|
||||
int firstUse = -1;
|
||||
int lastUse = -1;
|
||||
};
|
||||
|
||||
size_t pass_count() const { return _passes.size(); }
|
||||
const char* pass_name(size_t i) const { return i < _passes.size() ? _passes[i].name.c_str() : ""; }
|
||||
bool pass_enabled(size_t i) const { return i < _passes.size() ? _passes[i].enabled : false; }
|
||||
void set_pass_enabled(size_t i, bool e) { if (i < _passes.size()) _passes[i].enabled = e; }
|
||||
|
||||
void debug_get_passes(std::vector<RGDebugPassInfo>& out) const;
|
||||
void debug_get_images(std::vector<RGDebugImageInfo>& out) const;
|
||||
void debug_get_buffers(std::vector<RGDebugBufferInfo>& out) const;
|
||||
|
||||
private:
|
||||
struct ImportedImage
|
||||
{
|
||||
RGImportedImageDesc desc;
|
||||
RGImageHandle handle;
|
||||
};
|
||||
|
||||
struct Pass
|
||||
{
|
||||
std::string name;
|
||||
RGPassType type{};
|
||||
RecordCallback record;
|
||||
|
||||
// Declarations
|
||||
std::vector<RGPassImageAccess> imageReads;
|
||||
std::vector<RGPassImageAccess> imageWrites;
|
||||
std::vector<RGPassBufferAccess> bufferReads;
|
||||
std::vector<RGPassBufferAccess> bufferWrites;
|
||||
std::vector<RGAttachmentInfo> colorAttachments;
|
||||
bool hasDepth = false;
|
||||
RGAttachmentInfo depthAttachment{};
|
||||
|
||||
std::vector<VkImageMemoryBarrier2> preImageBarriers;
|
||||
std::vector<VkBufferMemoryBarrier2> preBufferBarriers;
|
||||
|
||||
// Cached rendering info derived from declared attachments (filled at execute)
|
||||
bool hasRendering = false;
|
||||
VkExtent2D renderExtent{};
|
||||
|
||||
bool enabled = true;
|
||||
};
|
||||
|
||||
EngineContext* _context = nullptr;
|
||||
RGResourceRegistry _resources;
|
||||
std::vector<Pass> _passes;
|
||||
};
|
||||
189
src/render/rg_resources.cpp
Normal file
189
src/render/rg_resources.cpp
Normal file
@@ -0,0 +1,189 @@
|
||||
#include <render/rg_resources.h>
|
||||
#include <core/engine_context.h>
|
||||
#include <core/vk_resource.h>
|
||||
|
||||
#include "frame_resources.h"
|
||||
|
||||
void RGResourceRegistry::reset()
|
||||
{
|
||||
_images.clear();
|
||||
_buffers.clear();
|
||||
_imageLookup.clear();
|
||||
_bufferLookup.clear();
|
||||
}
|
||||
|
||||
RGImageHandle RGResourceRegistry::add_imported(const RGImportedImageDesc& d)
|
||||
{
|
||||
// Deduplicate by VkImage
|
||||
auto it = _imageLookup.find(d.image);
|
||||
if (it != _imageLookup.end())
|
||||
{
|
||||
auto& rec = _images[it->second];
|
||||
rec.name = d.name;
|
||||
rec.image = d.image;
|
||||
rec.imageView = d.imageView;
|
||||
rec.format = d.format;
|
||||
rec.extent = d.extent;
|
||||
rec.initialLayout = d.currentLayout;
|
||||
return RGImageHandle{it->second};
|
||||
}
|
||||
|
||||
RGImageRecord rec{};
|
||||
rec.name = d.name;
|
||||
rec.imported = true;
|
||||
rec.image = d.image;
|
||||
rec.imageView = d.imageView;
|
||||
rec.format = d.format;
|
||||
rec.extent = d.extent;
|
||||
rec.initialLayout = d.currentLayout;
|
||||
_images.push_back(rec);
|
||||
uint32_t id = static_cast<uint32_t>(_images.size() - 1);
|
||||
if (d.image != VK_NULL_HANDLE) _imageLookup[d.image] = id;
|
||||
return RGImageHandle{ id };
|
||||
}
|
||||
|
||||
RGImageHandle RGResourceRegistry::add_transient(const RGImageDesc& d)
|
||||
{
|
||||
RGImageRecord rec{};
|
||||
rec.name = d.name;
|
||||
rec.imported = false;
|
||||
rec.format = d.format;
|
||||
rec.extent = d.extent;
|
||||
rec.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
rec.creationUsage = d.usage;
|
||||
|
||||
VkExtent3D size{ d.extent.width, d.extent.height, 1 };
|
||||
rec.allocation = _ctx->getResources()->create_image(size, d.format, d.usage);
|
||||
rec.image = rec.allocation.image;
|
||||
rec.imageView = rec.allocation.imageView;
|
||||
|
||||
// Cleanup at end of frame
|
||||
if (_ctx && _ctx->currentFrame)
|
||||
{
|
||||
auto img = rec.allocation;
|
||||
_ctx->currentFrame->_deletionQueue.push_function([ctx=_ctx, img]() {
|
||||
ctx->getResources()->destroy_image(img);
|
||||
});
|
||||
}
|
||||
|
||||
_images.push_back(rec);
|
||||
return RGImageHandle{ static_cast<uint32_t>(_images.size() - 1) };
|
||||
}
|
||||
|
||||
RGBufferHandle RGResourceRegistry::add_imported(const RGImportedBufferDesc& d)
|
||||
{
|
||||
// Deduplicate by VkBuffer
|
||||
auto it = _bufferLookup.find(d.buffer);
|
||||
if (it != _bufferLookup.end())
|
||||
{
|
||||
auto& rec = _buffers[it->second];
|
||||
rec.name = d.name;
|
||||
rec.buffer = d.buffer;
|
||||
rec.size = d.size;
|
||||
// Keep the earliest known stage/access if set; otherwise record provided
|
||||
if (rec.initialStage == VK_PIPELINE_STAGE_2_NONE) rec.initialStage = d.currentStage;
|
||||
if (rec.initialAccess == 0) rec.initialAccess = d.currentAccess;
|
||||
return RGBufferHandle{it->second};
|
||||
}
|
||||
|
||||
RGBufferRecord rec{};
|
||||
rec.name = d.name;
|
||||
rec.imported = true;
|
||||
rec.buffer = d.buffer;
|
||||
rec.size = d.size;
|
||||
rec.initialStage = d.currentStage;
|
||||
rec.initialAccess = d.currentAccess;
|
||||
_buffers.push_back(rec);
|
||||
uint32_t id = static_cast<uint32_t>(_buffers.size() - 1);
|
||||
if (d.buffer != VK_NULL_HANDLE) _bufferLookup[d.buffer] = id;
|
||||
return RGBufferHandle{ id };
|
||||
}
|
||||
|
||||
RGBufferHandle RGResourceRegistry::add_transient(const RGBufferDesc& d)
|
||||
{
|
||||
RGBufferRecord rec{};
|
||||
rec.name = d.name;
|
||||
rec.imported = false;
|
||||
rec.size = d.size;
|
||||
rec.usage = d.usage;
|
||||
rec.initialStage = VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT;
|
||||
rec.initialAccess = 0;
|
||||
|
||||
rec.allocation = _ctx->getResources()->create_buffer(d.size, d.usage, d.memoryUsage);
|
||||
rec.buffer = rec.allocation.buffer;
|
||||
|
||||
if (_ctx && _ctx->currentFrame)
|
||||
{
|
||||
auto buf = rec.allocation;
|
||||
_ctx->currentFrame->_deletionQueue.push_function([ctx=_ctx, buf]() {
|
||||
ctx->getResources()->destroy_buffer(buf);
|
||||
});
|
||||
}
|
||||
|
||||
_buffers.push_back(rec);
|
||||
uint32_t id = static_cast<uint32_t>(_buffers.size() - 1);
|
||||
if (rec.buffer != VK_NULL_HANDLE) _bufferLookup[rec.buffer] = id;
|
||||
return RGBufferHandle{ id };
|
||||
}
|
||||
|
||||
const RGImageRecord* RGResourceRegistry::get_image(RGImageHandle h) const
|
||||
{
|
||||
if (!h.valid() || h.id >= _images.size()) return nullptr;
|
||||
return &_images[h.id];
|
||||
}
|
||||
|
||||
RGImageRecord* RGResourceRegistry::get_image(RGImageHandle h)
|
||||
{
|
||||
if (!h.valid() || h.id >= _images.size()) return nullptr;
|
||||
return &_images[h.id];
|
||||
}
|
||||
|
||||
const RGBufferRecord* RGResourceRegistry::get_buffer(RGBufferHandle h) const
|
||||
{
|
||||
if (!h.valid() || h.id >= _buffers.size()) return nullptr;
|
||||
return &_buffers[h.id];
|
||||
}
|
||||
|
||||
RGBufferRecord* RGResourceRegistry::get_buffer(RGBufferHandle h)
|
||||
{
|
||||
if (!h.valid() || h.id >= _buffers.size()) return nullptr;
|
||||
return &_buffers[h.id];
|
||||
}
|
||||
|
||||
VkImageLayout RGResourceRegistry::initial_layout(RGImageHandle h) const
|
||||
{
|
||||
const RGImageRecord* rec = get_image(h);
|
||||
return rec ? rec->initialLayout : VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
}
|
||||
|
||||
VkFormat RGResourceRegistry::image_format(RGImageHandle h) const
|
||||
{
|
||||
const RGImageRecord* rec = get_image(h);
|
||||
return rec ? rec->format : VK_FORMAT_UNDEFINED;
|
||||
}
|
||||
|
||||
VkPipelineStageFlags2 RGResourceRegistry::initial_stage(RGBufferHandle h) const
|
||||
{
|
||||
const RGBufferRecord* rec = get_buffer(h);
|
||||
return rec ? rec->initialStage : VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT;
|
||||
}
|
||||
|
||||
VkAccessFlags2 RGResourceRegistry::initial_access(RGBufferHandle h) const
|
||||
{
|
||||
const RGBufferRecord* rec = get_buffer(h);
|
||||
return rec ? rec->initialAccess : VkAccessFlags2{0};
|
||||
}
|
||||
|
||||
RGBufferHandle RGResourceRegistry::find_buffer(VkBuffer buffer) const
|
||||
{
|
||||
auto it = _bufferLookup.find(buffer);
|
||||
if (it == _bufferLookup.end()) return RGBufferHandle{};
|
||||
return RGBufferHandle{it->second};
|
||||
}
|
||||
|
||||
RGImageHandle RGResourceRegistry::find_image(VkImage image) const
|
||||
{
|
||||
auto it = _imageLookup.find(image);
|
||||
if (it == _imageLookup.end()) return RGImageHandle{};
|
||||
return RGImageHandle{it->second};
|
||||
}
|
||||
90
src/render/rg_resources.h
Normal file
90
src/render/rg_resources.h
Normal file
@@ -0,0 +1,90 @@
|
||||
#pragma once
|
||||
|
||||
#include <core/vk_types.h>
|
||||
#include <render/rg_types.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
|
||||
class EngineContext;
|
||||
|
||||
struct RGImageRecord
|
||||
{
|
||||
std::string name;
|
||||
bool imported = true;
|
||||
|
||||
// Unified view for either imported or transient
|
||||
VkImage image = VK_NULL_HANDLE;
|
||||
VkImageView imageView = VK_NULL_HANDLE;
|
||||
VkFormat format = VK_FORMAT_UNDEFINED;
|
||||
VkExtent2D extent{0, 0};
|
||||
VkImageLayout initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
VkImageUsageFlags creationUsage = 0; // if transient; 0 for imported
|
||||
|
||||
// If transient, keep allocation owner for cleanup
|
||||
AllocatedImage allocation{};
|
||||
|
||||
// Lifetime indices within the compiled pass list (for aliasing/debug)
|
||||
int firstUse = -1;
|
||||
int lastUse = -1;
|
||||
};
|
||||
|
||||
struct RGBufferRecord
|
||||
{
|
||||
std::string name;
|
||||
bool imported = true;
|
||||
|
||||
VkBuffer buffer = VK_NULL_HANDLE;
|
||||
VkDeviceSize size = 0;
|
||||
VkBufferUsageFlags usage = 0;
|
||||
VkPipelineStageFlags2 initialStage = VK_PIPELINE_STAGE_2_NONE;
|
||||
VkAccessFlags2 initialAccess = 0;
|
||||
|
||||
AllocatedBuffer allocation{};
|
||||
|
||||
// Lifetime indices (for aliasing/debug)
|
||||
int firstUse = -1;
|
||||
int lastUse = -1;
|
||||
};
|
||||
|
||||
class RGResourceRegistry
|
||||
{
|
||||
public:
|
||||
void init(EngineContext* ctx) { _ctx = ctx; }
|
||||
|
||||
void reset();
|
||||
|
||||
RGImageHandle add_imported(const RGImportedImageDesc& d);
|
||||
RGImageHandle add_transient(const RGImageDesc& d);
|
||||
|
||||
RGBufferHandle add_imported(const RGImportedBufferDesc& d);
|
||||
RGBufferHandle add_transient(const RGBufferDesc& d);
|
||||
|
||||
// Lookup existing handles by raw Vulkan objects (deduplicates imports)
|
||||
RGBufferHandle find_buffer(VkBuffer buffer) const;
|
||||
RGImageHandle find_image(VkImage image) const;
|
||||
|
||||
const RGImageRecord* get_image(RGImageHandle h) const;
|
||||
RGImageRecord* get_image(RGImageHandle h);
|
||||
|
||||
const RGBufferRecord* get_buffer(RGBufferHandle h) const;
|
||||
RGBufferRecord* get_buffer(RGBufferHandle h);
|
||||
|
||||
size_t image_count() const { return _images.size(); }
|
||||
size_t buffer_count() const { return _buffers.size(); }
|
||||
|
||||
VkImageLayout initial_layout(RGImageHandle h) const;
|
||||
VkFormat image_format(RGImageHandle h) const;
|
||||
|
||||
VkPipelineStageFlags2 initial_stage(RGBufferHandle h) const;
|
||||
VkAccessFlags2 initial_access(RGBufferHandle h) const;
|
||||
|
||||
private:
|
||||
EngineContext* _ctx = nullptr;
|
||||
std::vector<RGImageRecord> _images;
|
||||
std::vector<RGBufferRecord> _buffers;
|
||||
|
||||
// Reverse lookup to avoid duplicate imports of the same VkBuffer/VkImage
|
||||
std::unordered_map<VkImage, uint32_t> _imageLookup;
|
||||
std::unordered_map<VkBuffer, uint32_t> _bufferLookup;
|
||||
};
|
||||
101
src/render/rg_types.h
Normal file
101
src/render/rg_types.h
Normal file
@@ -0,0 +1,101 @@
|
||||
#pragma once
|
||||
|
||||
#include <core/vk_types.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// Lightweight, initial Render Graph types. These will expand as we migrate passes.
|
||||
|
||||
enum class RGPassType
|
||||
{
|
||||
Graphics,
|
||||
Compute,
|
||||
Transfer
|
||||
};
|
||||
|
||||
enum class RGImageUsage
|
||||
{
|
||||
// Read usages
|
||||
SampledFragment,
|
||||
SampledCompute,
|
||||
TransferSrc,
|
||||
|
||||
// Write usages
|
||||
ColorAttachment,
|
||||
DepthAttachment,
|
||||
ComputeWrite,
|
||||
TransferDst,
|
||||
|
||||
// Terminal
|
||||
Present
|
||||
};
|
||||
|
||||
enum class RGBufferUsage
|
||||
{
|
||||
TransferSrc,
|
||||
TransferDst,
|
||||
VertexRead,
|
||||
IndexRead,
|
||||
UniformRead,
|
||||
StorageRead,
|
||||
StorageReadWrite,
|
||||
IndirectArgs
|
||||
};
|
||||
|
||||
struct RGImageHandle
|
||||
{
|
||||
uint32_t id = 0xFFFFFFFFu;
|
||||
bool valid() const { return id != 0xFFFFFFFFu; }
|
||||
explicit operator bool() const { return valid(); }
|
||||
};
|
||||
|
||||
struct RGBufferHandle
|
||||
{
|
||||
uint32_t id = 0xFFFFFFFFu;
|
||||
bool valid() const { return id != 0xFFFFFFFFu; }
|
||||
explicit operator bool() const { return valid(); }
|
||||
};
|
||||
|
||||
struct RGImportedImageDesc
|
||||
{
|
||||
std::string name;
|
||||
VkImage image = VK_NULL_HANDLE;
|
||||
VkImageView imageView = VK_NULL_HANDLE;
|
||||
VkFormat format = VK_FORMAT_UNDEFINED;
|
||||
VkExtent2D extent{0, 0};
|
||||
VkImageLayout currentLayout = VK_IMAGE_LAYOUT_UNDEFINED; // layout at graph begin
|
||||
};
|
||||
|
||||
struct RGImportedBufferDesc
|
||||
{
|
||||
std::string name;
|
||||
VkBuffer buffer = VK_NULL_HANDLE;
|
||||
VkDeviceSize size = 0;
|
||||
VkPipelineStageFlags2 currentStage = VK_PIPELINE_STAGE_2_NONE;
|
||||
VkAccessFlags2 currentAccess = 0;
|
||||
};
|
||||
|
||||
struct RGImageDesc
|
||||
{
|
||||
std::string name;
|
||||
VkFormat format = VK_FORMAT_UNDEFINED;
|
||||
VkExtent2D extent{0, 0};
|
||||
VkImageUsageFlags usage = 0; // creation usage mask; graph sets layouts per-pass
|
||||
};
|
||||
|
||||
struct RGBufferDesc
|
||||
{
|
||||
std::string name;
|
||||
VkDeviceSize size = 0;
|
||||
VkBufferUsageFlags usage = 0;
|
||||
VmaMemoryUsage memoryUsage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
|
||||
};
|
||||
|
||||
// Simple attachment info for dynamic rendering; expanded later for load/store.
|
||||
struct RGAttachmentInfo
|
||||
{
|
||||
RGImageHandle image;
|
||||
VkClearValue clear{}; // default 0
|
||||
bool clearOnLoad = false; // if true, use clear; else load
|
||||
bool store = true; // store results
|
||||
};
|
||||
126
src/render/vk_materials.cpp
Normal file
126
src/render/vk_materials.cpp
Normal file
@@ -0,0 +1,126 @@
|
||||
#include "vk_materials.h"
|
||||
|
||||
#include "core/vk_engine.h"
|
||||
#include "render/vk_pipelines.h"
|
||||
#include "core/vk_initializers.h"
|
||||
#include "core/vk_pipeline_manager.h"
|
||||
#include "core/asset_manager.h"
|
||||
|
||||
namespace vkutil { bool load_shader_module(const char*, VkDevice, VkShaderModule*); }
|
||||
|
||||
void GLTFMetallic_Roughness::build_pipelines(VulkanEngine *engine)
|
||||
{
|
||||
VkPushConstantRange matrixRange{};
|
||||
matrixRange.offset = 0;
|
||||
matrixRange.size = sizeof(GPUDrawPushConstants);
|
||||
matrixRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
|
||||
|
||||
DescriptorLayoutBuilder layoutBuilder;
|
||||
layoutBuilder.add_binding(0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER);
|
||||
layoutBuilder.add_binding(1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER);
|
||||
layoutBuilder.add_binding(2, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER);
|
||||
|
||||
materialLayout = layoutBuilder.build(engine->_deviceManager->device(),
|
||||
VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
|
||||
VkDescriptorSetLayout layouts[] = {
|
||||
engine->_descriptorManager->gpuSceneDataLayout(),
|
||||
materialLayout
|
||||
};
|
||||
|
||||
// Register pipelines with the central PipelineManager
|
||||
GraphicsPipelineCreateInfo opaqueInfo{};
|
||||
opaqueInfo.vertexShaderPath = engine->_context->getAssets()->shaderPath("mesh.vert.spv");
|
||||
opaqueInfo.fragmentShaderPath = engine->_context->getAssets()->shaderPath("mesh.frag.spv");
|
||||
opaqueInfo.setLayouts.assign(std::begin(layouts), std::end(layouts));
|
||||
opaqueInfo.pushConstants = {matrixRange};
|
||||
opaqueInfo.configure = [engine](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_blending();
|
||||
// Reverse-Z depth test configuration
|
||||
b.enable_depthtest(true, VK_COMPARE_OP_GREATER_OR_EQUAL);
|
||||
b.set_color_attachment_format(engine->_swapchainManager->drawImage().imageFormat);
|
||||
b.set_depth_format(engine->_swapchainManager->depthImage().imageFormat);
|
||||
};
|
||||
engine->_pipelineManager->registerGraphics("mesh.opaque", opaqueInfo);
|
||||
|
||||
GraphicsPipelineCreateInfo transparentInfo = opaqueInfo;
|
||||
transparentInfo.configure = [engine](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();
|
||||
// Physically-based transparency uses standard alpha blending
|
||||
b.enable_blending_alphablend();
|
||||
// Transparent pass: keep reverse-Z test (no writes)
|
||||
b.enable_depthtest(false, VK_COMPARE_OP_GREATER_OR_EQUAL);
|
||||
b.set_color_attachment_format(engine->_swapchainManager->drawImage().imageFormat);
|
||||
b.set_depth_format(engine->_swapchainManager->depthImage().imageFormat);
|
||||
};
|
||||
engine->_pipelineManager->registerGraphics("mesh.transparent", transparentInfo);
|
||||
|
||||
GraphicsPipelineCreateInfo gbufferInfo{};
|
||||
gbufferInfo.vertexShaderPath = engine->_context->getAssets()->shaderPath("mesh.vert.spv");
|
||||
gbufferInfo.fragmentShaderPath = engine->_context->getAssets()->shaderPath("gbuffer.frag.spv");
|
||||
gbufferInfo.setLayouts.assign(std::begin(layouts), std::end(layouts));
|
||||
gbufferInfo.pushConstants = {matrixRange};
|
||||
gbufferInfo.configure = [engine](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_blending();
|
||||
// GBuffer uses reverse-Z depth
|
||||
b.enable_depthtest(true, VK_COMPARE_OP_GREATER_OR_EQUAL);
|
||||
VkFormat gFormats[] = {
|
||||
engine->_swapchainManager->gBufferPosition().imageFormat,
|
||||
engine->_swapchainManager->gBufferNormal().imageFormat,
|
||||
engine->_swapchainManager->gBufferAlbedo().imageFormat
|
||||
};
|
||||
b.set_color_attachment_formats(std::span<VkFormat>(gFormats, 3));
|
||||
b.set_depth_format(engine->_swapchainManager->depthImage().imageFormat);
|
||||
};
|
||||
engine->_pipelineManager->registerGraphics("mesh.gbuffer", gbufferInfo);
|
||||
|
||||
engine->_pipelineManager->getMaterialPipeline("mesh.opaque", opaquePipeline);
|
||||
engine->_pipelineManager->getMaterialPipeline("mesh.transparent", transparentPipeline);
|
||||
engine->_pipelineManager->getMaterialPipeline("mesh.gbuffer", gBufferPipeline);
|
||||
}
|
||||
|
||||
void GLTFMetallic_Roughness::clear_resources(VkDevice device) const
|
||||
{
|
||||
vkDestroyDescriptorSetLayout(device, materialLayout, nullptr);
|
||||
}
|
||||
|
||||
MaterialInstance GLTFMetallic_Roughness::write_material(VkDevice device, MaterialPass pass,
|
||||
const MaterialResources &resources,
|
||||
DescriptorAllocatorGrowable &descriptorAllocator)
|
||||
{
|
||||
MaterialInstance matData{};
|
||||
matData.passType = pass;
|
||||
if (pass == MaterialPass::Transparent)
|
||||
{
|
||||
matData.pipeline = &transparentPipeline;
|
||||
}
|
||||
else
|
||||
{
|
||||
matData.pipeline = &gBufferPipeline;
|
||||
}
|
||||
|
||||
matData.materialSet = descriptorAllocator.allocate(device, materialLayout);
|
||||
|
||||
writer.clear();
|
||||
writer.write_buffer(0, resources.dataBuffer, sizeof(MaterialConstants), resources.dataBufferOffset,
|
||||
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER);
|
||||
writer.write_image(1, resources.colorImage.imageView, resources.colorSampler,
|
||||
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER);
|
||||
writer.write_image(2, resources.metalRoughImage.imageView, resources.metalRoughSampler,
|
||||
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER);
|
||||
|
||||
writer.update_set(device, matData.materialSet);
|
||||
|
||||
return matData;
|
||||
}
|
||||
42
src/render/vk_materials.h
Normal file
42
src/render/vk_materials.h
Normal file
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include <core/vk_types.h>
|
||||
#include <core/vk_descriptors.h>
|
||||
|
||||
class VulkanEngine;
|
||||
|
||||
struct GLTFMetallic_Roughness
|
||||
{
|
||||
MaterialPipeline opaquePipeline;
|
||||
MaterialPipeline transparentPipeline;
|
||||
MaterialPipeline gBufferPipeline;
|
||||
|
||||
VkDescriptorSetLayout materialLayout;
|
||||
|
||||
struct MaterialConstants
|
||||
{
|
||||
glm::vec4 colorFactors;
|
||||
glm::vec4 metal_rough_factors;
|
||||
|
||||
glm::vec4 extra[14];
|
||||
};
|
||||
|
||||
struct MaterialResources
|
||||
{
|
||||
AllocatedImage colorImage;
|
||||
VkSampler colorSampler;
|
||||
AllocatedImage metalRoughImage;
|
||||
VkSampler metalRoughSampler;
|
||||
VkBuffer dataBuffer;
|
||||
uint32_t dataBufferOffset;
|
||||
};
|
||||
|
||||
DescriptorWriter writer;
|
||||
|
||||
void build_pipelines(VulkanEngine *engine);
|
||||
|
||||
void clear_resources(VkDevice device) const;
|
||||
|
||||
MaterialInstance write_material(VkDevice device, MaterialPass pass, const MaterialResources &resources,
|
||||
DescriptorAllocatorGrowable &descriptorAllocator);
|
||||
};
|
||||
244
src/render/vk_pipelines.cpp
Normal file
244
src/render/vk_pipelines.cpp
Normal file
@@ -0,0 +1,244 @@
|
||||
#include <render/vk_pipelines.h>
|
||||
#include <fstream>
|
||||
#include <core/vk_initializers.h>
|
||||
|
||||
bool vkutil::load_shader_module(const char *filePath, VkDevice device, VkShaderModule *outShaderModule)
|
||||
{
|
||||
std::ifstream file(filePath, std::ios::ate | std::ios::binary);
|
||||
|
||||
if (!file.is_open())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t fileSize = (size_t) file.tellg();
|
||||
|
||||
std::vector<uint32_t> buffer(fileSize / sizeof(uint32_t));
|
||||
|
||||
file.seekg(0);
|
||||
|
||||
file.read((char *) buffer.data(), fileSize);
|
||||
|
||||
file.close();
|
||||
|
||||
VkShaderModuleCreateInfo createInfo = {};
|
||||
createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
|
||||
createInfo.pNext = nullptr;
|
||||
|
||||
createInfo.codeSize = buffer.size() * sizeof(uint32_t);
|
||||
createInfo.pCode = buffer.data();
|
||||
|
||||
VkShaderModule shaderModule;
|
||||
if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
*outShaderModule = shaderModule;
|
||||
return true;
|
||||
}
|
||||
|
||||
void PipelineBuilder::clear()
|
||||
{
|
||||
_inputAssembly = {.sType=VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO};
|
||||
|
||||
_rasterizer = {.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO};
|
||||
|
||||
_colorBlendAttachment = {};
|
||||
|
||||
_multisampling = {.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO};
|
||||
|
||||
_pipelineLayout = {};
|
||||
|
||||
_depthStencil = {.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO};
|
||||
|
||||
_renderInfo = {.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO};
|
||||
|
||||
_shaderStages.clear();
|
||||
_colorAttachmentFormats.clear();
|
||||
}
|
||||
|
||||
VkPipeline PipelineBuilder::build_pipeline(VkDevice device)
|
||||
{
|
||||
VkPipelineViewportStateCreateInfo viewportState = {};
|
||||
viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
|
||||
viewportState.pNext = nullptr;
|
||||
|
||||
viewportState.viewportCount = 1;
|
||||
viewportState.scissorCount = 1;
|
||||
|
||||
VkPipelineColorBlendStateCreateInfo colorBlending = {};
|
||||
colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
|
||||
colorBlending.pNext = nullptr;
|
||||
|
||||
colorBlending.logicOpEnable = VK_FALSE;
|
||||
colorBlending.logicOp = VK_LOGIC_OP_COPY;
|
||||
// For multiple color attachments (e.g., G-Buffer), we must provide one blend state per attachment.
|
||||
// Depth-only pipelines are allowed (0 color attachments).
|
||||
std::vector<VkPipelineColorBlendAttachmentState> blendAttachments;
|
||||
uint32_t colorAttachmentCount = (uint32_t)_colorAttachmentFormats.size();
|
||||
if (colorAttachmentCount > 0)
|
||||
{
|
||||
blendAttachments.assign(colorAttachmentCount, _colorBlendAttachment);
|
||||
colorBlending.attachmentCount = colorAttachmentCount;
|
||||
colorBlending.pAttachments = blendAttachments.data();
|
||||
}
|
||||
else
|
||||
{
|
||||
colorBlending.attachmentCount = 0;
|
||||
colorBlending.pAttachments = nullptr;
|
||||
}
|
||||
VkPipelineVertexInputStateCreateInfo _vertexInputInfo = {.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO};
|
||||
|
||||
VkGraphicsPipelineCreateInfo pipelineInfo = {.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO};
|
||||
pipelineInfo.pNext = &_renderInfo;
|
||||
|
||||
pipelineInfo.stageCount = (uint32_t) _shaderStages.size();
|
||||
pipelineInfo.pStages = _shaderStages.data();
|
||||
pipelineInfo.pVertexInputState = &_vertexInputInfo;
|
||||
pipelineInfo.pInputAssemblyState = &_inputAssembly;
|
||||
pipelineInfo.pViewportState = &viewportState;
|
||||
pipelineInfo.pRasterizationState = &_rasterizer;
|
||||
pipelineInfo.pMultisampleState = &_multisampling;
|
||||
pipelineInfo.pColorBlendState = &colorBlending;
|
||||
pipelineInfo.pDepthStencilState = &_depthStencil;
|
||||
pipelineInfo.layout = _pipelineLayout;
|
||||
|
||||
VkDynamicState state[] = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
|
||||
VkPipelineDynamicStateCreateInfo dynamicInfo = {.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO};
|
||||
dynamicInfo.pDynamicStates = &state[0];
|
||||
dynamicInfo.dynamicStateCount = 2;
|
||||
|
||||
pipelineInfo.pDynamicState = &dynamicInfo;
|
||||
|
||||
VkPipeline newPipeline;
|
||||
if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo,
|
||||
nullptr, &newPipeline)
|
||||
!= VK_SUCCESS)
|
||||
{
|
||||
fmt::println("failed to create pipeline");
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
else
|
||||
{
|
||||
return newPipeline;
|
||||
}
|
||||
}
|
||||
|
||||
void PipelineBuilder::set_shaders(VkShaderModule vertexShader, VkShaderModule fragmentShader)
|
||||
{
|
||||
_shaderStages.clear();
|
||||
|
||||
_shaderStages.push_back(
|
||||
vkinit::pipeline_shader_stage_create_info(VK_SHADER_STAGE_VERTEX_BIT, vertexShader));
|
||||
|
||||
_shaderStages.push_back(
|
||||
vkinit::pipeline_shader_stage_create_info(VK_SHADER_STAGE_FRAGMENT_BIT, fragmentShader));
|
||||
}
|
||||
|
||||
void PipelineBuilder::set_input_topology(VkPrimitiveTopology topology)
|
||||
{
|
||||
_inputAssembly.topology = topology;
|
||||
_inputAssembly.primitiveRestartEnable = VK_FALSE;
|
||||
}
|
||||
|
||||
void PipelineBuilder::set_polygon_mode(VkPolygonMode mode)
|
||||
{
|
||||
_rasterizer.polygonMode = mode;
|
||||
_rasterizer.lineWidth = 1.f;
|
||||
}
|
||||
|
||||
void PipelineBuilder::set_cull_mode(VkCullModeFlags cullMode, VkFrontFace frontFace)
|
||||
{
|
||||
_rasterizer.cullMode = cullMode;
|
||||
_rasterizer.frontFace = frontFace;
|
||||
}
|
||||
|
||||
void PipelineBuilder::set_multisampling_none()
|
||||
{
|
||||
_multisampling.sampleShadingEnable = VK_FALSE;
|
||||
|
||||
_multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
|
||||
_multisampling.minSampleShading = 1.0f;
|
||||
_multisampling.pSampleMask = nullptr;
|
||||
|
||||
_multisampling.alphaToCoverageEnable = VK_FALSE;
|
||||
_multisampling.alphaToOneEnable = VK_FALSE;
|
||||
}
|
||||
|
||||
void PipelineBuilder::disable_blending()
|
||||
{
|
||||
_colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
|
||||
_colorBlendAttachment.blendEnable = VK_FALSE;
|
||||
}
|
||||
|
||||
void PipelineBuilder::enable_blending_additive()
|
||||
{
|
||||
_colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
|
||||
_colorBlendAttachment.blendEnable = VK_TRUE;
|
||||
_colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
|
||||
_colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE;
|
||||
_colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD;
|
||||
_colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
|
||||
_colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
|
||||
_colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD;
|
||||
}
|
||||
|
||||
void PipelineBuilder::enable_blending_alphablend()
|
||||
{
|
||||
_colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
|
||||
_colorBlendAttachment.blendEnable = VK_TRUE;
|
||||
_colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
|
||||
_colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
|
||||
_colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD;
|
||||
_colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
|
||||
_colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
|
||||
_colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD;
|
||||
}
|
||||
|
||||
void PipelineBuilder::set_color_attachment_format(VkFormat format)
|
||||
{
|
||||
_colorAttachmentFormats.clear();
|
||||
_colorAttachmentFormats.push_back(format);
|
||||
|
||||
_renderInfo.colorAttachmentCount = 1;
|
||||
_renderInfo.pColorAttachmentFormats = _colorAttachmentFormats.data();
|
||||
}
|
||||
|
||||
void PipelineBuilder::set_color_attachment_formats(std::span<VkFormat> formats)
|
||||
{
|
||||
_colorAttachmentFormats.assign(formats.begin(), formats.end());
|
||||
_renderInfo.colorAttachmentCount = (uint32_t)_colorAttachmentFormats.size();
|
||||
_renderInfo.pColorAttachmentFormats = _colorAttachmentFormats.data();
|
||||
}
|
||||
|
||||
void PipelineBuilder::set_depth_format(VkFormat format)
|
||||
{
|
||||
_renderInfo.depthAttachmentFormat = format;
|
||||
}
|
||||
|
||||
void PipelineBuilder::disable_depthtest()
|
||||
{
|
||||
_depthStencil.depthTestEnable = VK_FALSE;
|
||||
_depthStencil.depthWriteEnable = VK_FALSE;
|
||||
_depthStencil.depthCompareOp = VK_COMPARE_OP_NEVER;
|
||||
_depthStencil.depthBoundsTestEnable = VK_FALSE;
|
||||
_depthStencil.stencilTestEnable = VK_FALSE;
|
||||
_depthStencil.front = {};
|
||||
_depthStencil.back = {};
|
||||
_depthStencil.minDepthBounds = 0.f;
|
||||
_depthStencil.maxDepthBounds = 1.f;
|
||||
}
|
||||
|
||||
void PipelineBuilder::enable_depthtest(bool depthWriteEnable, VkCompareOp op)
|
||||
{
|
||||
_depthStencil.depthTestEnable = VK_TRUE;
|
||||
_depthStencil.depthWriteEnable = depthWriteEnable;
|
||||
_depthStencil.depthCompareOp = op;
|
||||
_depthStencil.depthBoundsTestEnable = VK_FALSE;
|
||||
_depthStencil.stencilTestEnable = VK_FALSE;
|
||||
_depthStencil.front = {};
|
||||
_depthStencil.back = {};
|
||||
_depthStencil.minDepthBounds = 0.f;
|
||||
_depthStencil.maxDepthBounds = 1.f;
|
||||
}
|
||||
59
src/render/vk_pipelines.h
Normal file
59
src/render/vk_pipelines.h
Normal file
@@ -0,0 +1,59 @@
|
||||
#pragma once
|
||||
|
||||
#include <core/vk_types.h>
|
||||
#include <fstream>
|
||||
#include <core/vk_initializers.h>
|
||||
|
||||
namespace vkutil
|
||||
{
|
||||
|
||||
bool load_shader_module(const char *filePath, VkDevice device, VkShaderModule *outShaderModule);
|
||||
};
|
||||
|
||||
class PipelineBuilder
|
||||
{
|
||||
public:
|
||||
std::vector<VkPipelineShaderStageCreateInfo> _shaderStages;
|
||||
|
||||
VkPipelineInputAssemblyStateCreateInfo _inputAssembly;
|
||||
VkPipelineRasterizationStateCreateInfo _rasterizer;
|
||||
VkPipelineColorBlendAttachmentState _colorBlendAttachment;
|
||||
VkPipelineMultisampleStateCreateInfo _multisampling;
|
||||
VkPipelineLayout _pipelineLayout;
|
||||
VkPipelineDepthStencilStateCreateInfo _depthStencil;
|
||||
VkPipelineRenderingCreateInfo _renderInfo;
|
||||
VkFormat _colorAttachmentformat;
|
||||
std::vector<VkFormat> _colorAttachmentFormats;
|
||||
|
||||
PipelineBuilder()
|
||||
{ clear(); }
|
||||
|
||||
void clear();
|
||||
|
||||
VkPipeline build_pipeline(VkDevice device);
|
||||
|
||||
void set_shaders(VkShaderModule vertexShader, VkShaderModule fragmentShader);
|
||||
|
||||
void set_input_topology(VkPrimitiveTopology topology);
|
||||
|
||||
void set_polygon_mode(VkPolygonMode mode);
|
||||
|
||||
void set_cull_mode(VkCullModeFlags cullMode, VkFrontFace frontFace);
|
||||
|
||||
void set_multisampling_none();
|
||||
|
||||
void disable_blending();
|
||||
|
||||
void enable_blending_additive();
|
||||
|
||||
void enable_blending_alphablend();
|
||||
|
||||
void set_color_attachment_format(VkFormat format);
|
||||
void set_color_attachment_formats(std::span<VkFormat> formats);
|
||||
|
||||
void set_depth_format(VkFormat format);
|
||||
|
||||
void enable_depthtest(bool depthWriteEnable,VkCompareOp op);
|
||||
|
||||
void disable_depthtest();
|
||||
};
|
||||
74
src/render/vk_renderpass.cpp
Normal file
74
src/render/vk_renderpass.cpp
Normal file
@@ -0,0 +1,74 @@
|
||||
#include "vk_renderpass.h"
|
||||
|
||||
#include "vk_renderpass_background.h"
|
||||
#include "vk_renderpass_geometry.h"
|
||||
#include "vk_renderpass_imgui.h"
|
||||
#include "vk_renderpass_lighting.h"
|
||||
#include "vk_renderpass_transparent.h"
|
||||
#include "vk_renderpass_tonemap.h"
|
||||
#include "vk_renderpass_shadow.h"
|
||||
|
||||
void RenderPassManager::init(EngineContext *context)
|
||||
{
|
||||
_context = context;
|
||||
|
||||
auto backgroundPass = std::make_unique<BackgroundPass>();
|
||||
backgroundPass->init(context);
|
||||
addPass(std::move(backgroundPass));
|
||||
|
||||
// Shadow map pass comes early in the frame
|
||||
auto shadowPass = std::make_unique<ShadowPass>();
|
||||
shadowPass->init(context);
|
||||
addPass(std::move(shadowPass));
|
||||
|
||||
auto geometryPass = std::make_unique<GeometryPass>();
|
||||
geometryPass->init(context);
|
||||
addPass(std::move(geometryPass));
|
||||
|
||||
auto lightingPass = std::make_unique<LightingPass>();
|
||||
lightingPass->init(context);
|
||||
addPass(std::move(lightingPass));
|
||||
|
||||
auto transparentPass = std::make_unique<TransparentPass>();
|
||||
transparentPass->init(context);
|
||||
addPass(std::move(transparentPass));
|
||||
|
||||
auto tonemapPass = std::make_unique<TonemapPass>();
|
||||
tonemapPass->init(context);
|
||||
addPass(std::move(tonemapPass));
|
||||
}
|
||||
|
||||
void RenderPassManager::cleanup()
|
||||
{
|
||||
for (auto &pass: _passes)
|
||||
{
|
||||
pass->cleanup();
|
||||
}
|
||||
if (_imguiPass)
|
||||
{
|
||||
_imguiPass->cleanup();
|
||||
}
|
||||
fmt::print("RenderPassManager::cleanup()\n");
|
||||
_passes.clear();
|
||||
_imguiPass.reset();
|
||||
}
|
||||
|
||||
void RenderPassManager::addPass(std::unique_ptr<IRenderPass> pass)
|
||||
{
|
||||
_passes.push_back(std::move(pass));
|
||||
}
|
||||
|
||||
void RenderPassManager::setImGuiPass(std::unique_ptr<IRenderPass> imguiPass)
|
||||
{
|
||||
_imguiPass = std::move(imguiPass);
|
||||
if (_imguiPass)
|
||||
{
|
||||
_imguiPass->init(_context);
|
||||
}
|
||||
}
|
||||
|
||||
ImGuiPass *RenderPassManager::getImGuiPass()
|
||||
{
|
||||
if (!_imguiPass) return nullptr;
|
||||
return dynamic_cast<ImGuiPass *>(_imguiPass.get());
|
||||
}
|
||||
54
src/render/vk_renderpass.h
Normal file
54
src/render/vk_renderpass.h
Normal file
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
#include <core/vk_types.h>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
|
||||
class EngineContext;
|
||||
class ImGuiPass;
|
||||
|
||||
class IRenderPass
|
||||
{
|
||||
public:
|
||||
virtual ~IRenderPass() = default;
|
||||
|
||||
virtual void init(EngineContext *context) = 0;
|
||||
|
||||
virtual void cleanup() = 0;
|
||||
|
||||
virtual void execute(VkCommandBuffer cmd) = 0;
|
||||
|
||||
virtual const char *getName() const = 0;
|
||||
};
|
||||
|
||||
class RenderPassManager
|
||||
{
|
||||
public:
|
||||
void init(EngineContext *context);
|
||||
|
||||
void cleanup();
|
||||
|
||||
void addPass(std::unique_ptr<IRenderPass> pass);
|
||||
|
||||
void setImGuiPass(std::unique_ptr<IRenderPass> imguiPass);
|
||||
|
||||
ImGuiPass *getImGuiPass();
|
||||
|
||||
template<typename T>
|
||||
T *getPass()
|
||||
{
|
||||
for (auto &pass: _passes)
|
||||
{
|
||||
if (T *typedPass = dynamic_cast<T *>(pass.get()))
|
||||
{
|
||||
return typedPass;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
EngineContext *_context = nullptr;
|
||||
std::vector<std::unique_ptr<IRenderPass> > _passes;
|
||||
std::unique_ptr<IRenderPass> _imguiPass = nullptr;
|
||||
};
|
||||
99
src/render/vk_renderpass_background.cpp
Normal file
99
src/render/vk_renderpass_background.cpp
Normal file
@@ -0,0 +1,99 @@
|
||||
#include "vk_renderpass_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/rg_graph.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);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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 &effect = _backgroundEffects[_currentEffect];
|
||||
|
||||
ComputeDispatchInfo dispatchInfo = ComputeManager::createDispatch2D(
|
||||
ctx->getDrawExtent().width, ctx->getDrawExtent().height);
|
||||
dispatchInfo.pushConstants = &effect.data;
|
||||
dispatchInfo.pushConstantSize = sizeof(ComputePushConstants);
|
||||
|
||||
const char *instanceName = (std::string_view(effect.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");
|
||||
}
|
||||
fmt::print("RenderPassManager::cleanup()\n");
|
||||
_backgroundEffects.clear();
|
||||
}
|
||||
28
src/render/vk_renderpass_background.h
Normal file
28
src/render/vk_renderpass_background.h
Normal file
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
#include "vk_renderpass.h"
|
||||
#include "compute/vk_compute.h"
|
||||
#include "render/rg_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 = 0;
|
||||
|
||||
private:
|
||||
EngineContext *_context = nullptr;
|
||||
|
||||
void init_background_pipelines();
|
||||
};
|
||||
290
src/render/vk_renderpass_geometry.cpp
Normal file
290
src/render/vk_renderpass_geometry.cpp
Normal file
@@ -0,0 +1,290 @@
|
||||
#include "vk_renderpass_geometry.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <unordered_set>
|
||||
|
||||
#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 "vk_mem_alloc.h"
|
||||
#include "vk_scene.h"
|
||||
#include "vk_swapchain.h"
|
||||
#include "render/rg_graph.h"
|
||||
|
||||
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 depthHandle)
|
||||
{
|
||||
if (!graph || !gbufferPosition.valid() || !gbufferNormal.valid() || !gbufferAlbedo.valid() || !depthHandle.valid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
graph->add_pass(
|
||||
"Geometry",
|
||||
RGPassType::Graphics,
|
||||
[gbufferPosition, gbufferNormal, gbufferAlbedo, 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);
|
||||
|
||||
// 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, depthHandle](VkCommandBuffer cmd,
|
||||
const RGPassResources &res,
|
||||
EngineContext *ctx)
|
||||
{
|
||||
draw_geometry(cmd, ctx, res, gbufferPosition, gbufferNormal, gbufferAlbedo, depthHandle);
|
||||
});
|
||||
}
|
||||
|
||||
void GeometryPass::draw_geometry(VkCommandBuffer cmd,
|
||||
EngineContext *context,
|
||||
const RGPassResources &resources,
|
||||
RGImageHandle gbufferPosition,
|
||||
RGImageHandle gbufferNormal,
|
||||
RGImageHandle gbufferAlbedo,
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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 (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;
|
||||
|
||||
vkCmdPushConstants(cmd, r.material->pipeline->layout, VK_SHADER_STAGE_VERTEX_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");
|
||||
}
|
||||
33
src/render/vk_renderpass_geometry.h
Normal file
33
src/render/vk_renderpass_geometry.h
Normal file
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
#include "vk_renderpass.h"
|
||||
#include <render/rg_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 depthHandle);
|
||||
|
||||
private:
|
||||
EngineContext *_context = nullptr;
|
||||
|
||||
void draw_geometry(VkCommandBuffer cmd,
|
||||
EngineContext *context,
|
||||
const class RGPassResources &resources,
|
||||
RGImageHandle gbufferPosition,
|
||||
RGImageHandle gbufferNormal,
|
||||
RGImageHandle gbufferAlbedo,
|
||||
RGImageHandle depthHandle) const;
|
||||
};
|
||||
113
src/render/vk_renderpass_imgui.cpp
Normal file
113
src/render/vk_renderpass_imgui.cpp
Normal file
@@ -0,0 +1,113 @@
|
||||
#include "vk_renderpass_imgui.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/rg_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/vk_renderpass_imgui.h
Normal file
29
src/render/vk_renderpass_imgui.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
#include "vk_renderpass.h"
|
||||
#include "core/vk_types.h"
|
||||
#include <render/rg_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;
|
||||
};
|
||||
208
src/render/vk_renderpass_lighting.cpp
Normal file
208
src/render/vk_renderpass_lighting.cpp
Normal file
@@ -0,0 +1,208 @@
|
||||
#include "vk_renderpass_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/vk_pipelines.h"
|
||||
#include "core/vk_pipeline_manager.h"
|
||||
#include "core/asset_manager.h"
|
||||
#include "core/vk_descriptors.h"
|
||||
|
||||
#include "vk_mem_alloc.h"
|
||||
#include "vk_sampler_manager.h"
|
||||
#include "vk_swapchain.h"
|
||||
#include "render/rg_graph.h"
|
||||
|
||||
void LightingPass::init(EngineContext *context)
|
||||
{
|
||||
_context = context;
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
DescriptorLayoutBuilder builder;
|
||||
builder.add_binding(0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER);
|
||||
_shadowDescriptorLayout = builder.build(_context->getDevice()->device(), VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
}
|
||||
|
||||
// Build lighting pipeline through PipelineManager
|
||||
VkDescriptorSetLayout layouts[] = {
|
||||
_context->getDescriptorLayouts()->gpuSceneDataLayout(),
|
||||
_gBufferInputDescriptorLayout,
|
||||
_shadowDescriptorLayout
|
||||
};
|
||||
|
||||
GraphicsPipelineCreateInfo info{};
|
||||
info.vertexShaderPath = _context->getAssets()->shaderPath("fullscreen.vert.spv");
|
||||
info.fragmentShaderPath = _context->getAssets()->shaderPath("deferred_lighting.frag.spv");
|
||||
info.setLayouts.assign(std::begin(layouts), std::end(layouts));
|
||||
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.enable_blending_alphablend();
|
||||
b.disable_depthtest();
|
||||
b.set_color_attachment_format(_context->getSwapchain()->drawImage().imageFormat);
|
||||
};
|
||||
_context->pipelines->createGraphicsPipeline("deferred_lighting", info);
|
||||
|
||||
// fetch the handles so current frame uses latest versions
|
||||
MaterialPipeline mp{};
|
||||
_context->pipelines->getMaterialPipeline("deferred_lighting", mp);
|
||||
_pipeline = mp.pipeline;
|
||||
_pipelineLayout = mp.layout;
|
||||
|
||||
_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);
|
||||
});
|
||||
}
|
||||
|
||||
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,
|
||||
RGImageHandle shadowDepth)
|
||||
{
|
||||
if (!graph || !drawHandle.valid() || !gbufferPosition.valid() || !gbufferNormal.valid() || !gbufferAlbedo.valid() || !shadowDepth.valid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
graph->add_pass(
|
||||
"Lighting",
|
||||
RGPassType::Graphics,
|
||||
[drawHandle, gbufferPosition, gbufferNormal, gbufferAlbedo, shadowDepth](RGPassBuilder &builder, EngineContext *)
|
||||
{
|
||||
builder.read(gbufferPosition, RGImageUsage::SampledFragment);
|
||||
builder.read(gbufferNormal, RGImageUsage::SampledFragment);
|
||||
builder.read(gbufferAlbedo, RGImageUsage::SampledFragment);
|
||||
builder.read(shadowDepth, RGImageUsage::SampledFragment);
|
||||
|
||||
builder.write_color(drawHandle);
|
||||
},
|
||||
[this, drawHandle, shadowDepth](VkCommandBuffer cmd, const RGPassResources &res, EngineContext *ctx)
|
||||
{
|
||||
draw_lighting(cmd, ctx, res, drawHandle, shadowDepth);
|
||||
});
|
||||
}
|
||||
|
||||
void LightingPass::draw_lighting(VkCommandBuffer cmd,
|
||||
EngineContext *context,
|
||||
const RGPassResources &resources,
|
||||
RGImageHandle drawHandle,
|
||||
RGImageHandle shadowDepth)
|
||||
{
|
||||
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;
|
||||
|
||||
// Re-fetch pipeline in case it was hot-reloaded
|
||||
pipelineManager->getGraphics("deferred_lighting", _pipeline, _pipelineLayout);
|
||||
|
||||
// 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);
|
||||
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)
|
||||
VkDescriptorSet shadowSet = ctxLocal->currentFrame->_frameDescriptors.allocate(
|
||||
deviceManager->device(), _shadowDescriptorLayout);
|
||||
{
|
||||
VkImageView shadowView = resources.image_view(shadowDepth);
|
||||
DescriptorWriter writer2;
|
||||
writer2.write_image(0, shadowView, ctxLocal->getSamplers()->defaultLinear(),
|
||||
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
|
||||
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER);
|
||||
writer2.update_set(deviceManager->device(), shadowSet);
|
||||
}
|
||||
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, _pipelineLayout, 2, 1, &shadowSet, 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()
|
||||
{
|
||||
_deletionQueue.flush();
|
||||
fmt::print("LightingPass::cleanup()\n");
|
||||
}
|
||||
40
src/render/vk_renderpass_lighting.h
Normal file
40
src/render/vk_renderpass_lighting.h
Normal file
@@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
#include "vk_renderpass.h"
|
||||
#include <render/rg_types.h>
|
||||
|
||||
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"; }
|
||||
|
||||
void register_graph(class RenderGraph *graph,
|
||||
RGImageHandle drawHandle,
|
||||
RGImageHandle gbufferPosition,
|
||||
RGImageHandle gbufferNormal,
|
||||
RGImageHandle gbufferAlbedo,
|
||||
RGImageHandle shadowDepth);
|
||||
|
||||
private:
|
||||
EngineContext *_context = nullptr;
|
||||
|
||||
VkDescriptorSetLayout _gBufferInputDescriptorLayout = VK_NULL_HANDLE;
|
||||
VkDescriptorSet _gBufferInputDescriptorSet = VK_NULL_HANDLE;
|
||||
VkDescriptorSetLayout _shadowDescriptorLayout = VK_NULL_HANDLE; // set=2
|
||||
|
||||
VkPipelineLayout _pipelineLayout = VK_NULL_HANDLE;
|
||||
VkPipeline _pipeline = VK_NULL_HANDLE;
|
||||
|
||||
void draw_lighting(VkCommandBuffer cmd,
|
||||
EngineContext *context,
|
||||
const class RGPassResources &resources,
|
||||
RGImageHandle drawHandle,
|
||||
RGImageHandle shadowDepth);
|
||||
|
||||
DeletionQueue _deletionQueue;
|
||||
};
|
||||
184
src/render/vk_renderpass_shadow.cpp
Normal file
184
src/render/vk_renderpass_shadow.cpp
Normal file
@@ -0,0 +1,184 @@
|
||||
#include "vk_renderpass_shadow.h"
|
||||
|
||||
#include <unordered_set>
|
||||
|
||||
#include "core/engine_context.h"
|
||||
#include "render/rg_graph.h"
|
||||
#include "render/rg_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/vk_pipelines.h"
|
||||
#include "core/vk_types.h"
|
||||
|
||||
void ShadowPass::init(EngineContext *context)
|
||||
{
|
||||
_context = context;
|
||||
|
||||
if (!_context || !_context->pipelines) return;
|
||||
|
||||
// Build a depth-only graphics pipeline for shadow map rendering
|
||||
VkPushConstantRange pc{};
|
||||
pc.offset = 0;
|
||||
pc.size = sizeof(GPUDrawPushConstants);
|
||||
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();
|
||||
// Reverse-Z depth test & depth-only pipeline
|
||||
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 (will tune later)
|
||||
b._rasterizer.depthBiasEnable = VK_TRUE;
|
||||
b._rasterizer.depthBiasConstantFactor = 2.0f;
|
||||
b._rasterizer.depthBiasSlopeFactor = 2.0f;
|
||||
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, RGImageHandle shadowDepth, VkExtent2D extent)
|
||||
{
|
||||
if (!graph || !shadowDepth.valid()) return;
|
||||
|
||||
graph->add_pass(
|
||||
"ShadowMap",
|
||||
RGPassType::Graphics,
|
||||
[shadowDepth](RGPassBuilder &builder, EngineContext *ctx)
|
||||
{
|
||||
// Reverse-Z depth clear to 0.0
|
||||
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);
|
||||
// Transparent surfaces are ignored for shadow map in this simple pass
|
||||
|
||||
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](VkCommandBuffer cmd, const RGPassResources &res, EngineContext *ctx)
|
||||
{
|
||||
draw_shadow(cmd, ctx, res, shadowDepth, extent);
|
||||
});
|
||||
}
|
||||
|
||||
void ShadowPass::draw_shadow(VkCommandBuffer cmd,
|
||||
EngineContext *context,
|
||||
const RGPassResources &/*resources*/,
|
||||
RGImageHandle /*shadowDepth*/,
|
||||
VkExtent2D extent) 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;
|
||||
for (const auto &r : dc.OpaqueSurfaces)
|
||||
{
|
||||
if (r.indexBuffer != lastIndexBuffer)
|
||||
{
|
||||
lastIndexBuffer = r.indexBuffer;
|
||||
vkCmdBindIndexBuffer(cmd, r.indexBuffer, 0, VK_INDEX_TYPE_UINT32);
|
||||
}
|
||||
|
||||
GPUDrawPushConstants pc{};
|
||||
pc.worldMatrix = r.transform;
|
||||
pc.vertexBuffer = r.vertexBufferAddress;
|
||||
vkCmdPushConstants(cmd, layout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(GPUDrawPushConstants), &pc);
|
||||
|
||||
vkCmdDrawIndexed(cmd, r.indexCount, 1, r.firstIndex, 0, 0);
|
||||
}
|
||||
}
|
||||
33
src/render/vk_renderpass_shadow.h
Normal file
33
src/render/vk_renderpass_shadow.h
Normal file
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#include "vk_renderpass.h"
|
||||
#include <render/rg_types.h>
|
||||
|
||||
class RenderGraph;
|
||||
class EngineContext;
|
||||
class RGPassResources;
|
||||
|
||||
// Depth-only directional shadow map pass (skeleton)
|
||||
// - Writes a depth image using reversed-Z (clear=0)
|
||||
// - Draw function will be filled in a later step
|
||||
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 the depth-only pass into the render graph
|
||||
void register_graph(RenderGraph *graph, RGImageHandle shadowDepth, VkExtent2D extent);
|
||||
|
||||
private:
|
||||
EngineContext *_context = nullptr;
|
||||
|
||||
void draw_shadow(VkCommandBuffer cmd,
|
||||
EngineContext *context,
|
||||
const RGPassResources &resources,
|
||||
RGImageHandle shadowDepth,
|
||||
VkExtent2D extent) const;
|
||||
};
|
||||
122
src/render/vk_renderpass_tonemap.cpp
Normal file
122
src/render/vk_renderpass_tonemap.cpp
Normal file
@@ -0,0 +1,122 @@
|
||||
#include "vk_renderpass_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/rg_graph.h>
|
||||
#include <render/rg_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/vk_renderpass_tonemap.h
Normal file
43
src/render/vk_renderpass_tonemap.h
Normal file
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include <core/vk_types.h>
|
||||
#include <render/vk_renderpass.h>
|
||||
#include <render/rg_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;
|
||||
};
|
||||
|
||||
159
src/render/vk_renderpass_transparent.cpp
Normal file
159
src/render/vk_renderpass_transparent.cpp
Normal file
@@ -0,0 +1,159 @@
|
||||
#include "vk_renderpass_transparent.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <unordered_set>
|
||||
#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/rg_graph.h"
|
||||
|
||||
void TransparentPass::init(EngineContext *context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// Sort transparent back-to-front using camera-space depth
|
||||
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);
|
||||
}
|
||||
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, r.material->pipeline->layout, 1, 1,
|
||||
&r.material->materialSet, 0, nullptr);
|
||||
}
|
||||
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;
|
||||
vkCmdPushConstants(cmd, r.material->pipeline->layout, VK_SHADER_STAGE_VERTEX_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()
|
||||
{
|
||||
fmt::print("TransparentPass::cleanup()\n");
|
||||
}
|
||||
28
src/render/vk_renderpass_transparent.h
Normal file
28
src/render/vk_renderpass_transparent.h
Normal file
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include "vk_renderpass.h"
|
||||
#include "render/rg_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{};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user