This commit is contained in:
Martin Slachta
2026-07-18 14:31:15 +02:00
commit a04f0dc262
3343 changed files with 1140208 additions and 0 deletions
@@ -0,0 +1,24 @@
#pragma once
#include "RenderPass.hpp"
namespace lft::rg {
/*
* DependencyGraph is a class that represents a dynamic dependency graph for tasks in a render graph.
*/
class DependencyGraph {
private:
std::vector<TaskInfo> m_queue;
public:
DependencyGraph();
uint32_t add_task(const TaskInfo& task);
void remove_task(const TaskInfo& task);
std::vector<TaskInfo>& build_queue();
};
}
@@ -0,0 +1,74 @@
#pragma once
#include <vector>
#include "props.hpp"
#include "resources/ImageView.hpp"
#include "Swapchain.hpp"
/**
* Chain of image views. Used for swapchain and render graph output.
*/
struct ImageChain {
private:
std::vector<ImageView> m_images;
VkFormat m_format;
VkExtent2D m_extent;
VkImageLayout m_layout;
public:
GET(m_layout, layout);
GET(m_format, format);
GET(m_extent, extent);
[[nodiscard]] inline uint32_t count() const {
return m_images.size();
}
[[nodiscard]] inline const std::vector<ImageView>& views() const {
return m_images;
}
ImageChain(const ImageChain& other) :
m_format(other.m_format),
m_extent(other.m_extent),
m_layout(other.m_layout) {
m_images.clear();
std::copy(other.m_images.begin(), other.m_images.end(),
std::back_inserter(m_images));
}
ImageChain& operator=(const ImageChain& other) {
m_images.clear();
std::copy(other.m_images.begin(), other.m_images.end(),
std::back_inserter(m_images));
m_format = other.m_format;
m_extent = other.m_extent;
m_layout = other.m_layout;
return *this;
}
ImageChain(ImageChain&&) = delete;
ImageChain& operator=(ImageChain&&) = delete;
ImageChain(VkFormat format,
VkExtent2D extent,
VkImageLayout layout,
const std::vector<ImageView>& images) :
m_format(format),
m_extent(extent),
m_layout(layout),
m_images(images) {
}
static ImageChain from_swapchain(const Swapchain& swapchain) {
return ImageChain(swapchain.format().format,
swapchain.extent(),
VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
swapchain.views());
}
};
@@ -0,0 +1,77 @@
#pragma once
#include <cstdint>
#include <map>
#include <memory>
#include "AdjacencyMatrix.hpp"
#include "Gpu.hpp"
#include "RenderGraphBuffer.hpp"
namespace lft::rg {
/**
* Lightweight definition of the render graph to be run.
*/
class RenderGraph {
private:
const Gpu* m_gpu;
std::string m_output_name;
AdjacencyMatrix* m_dependency_matrix;
std::vector<RenderGraphBuffer*> m_buffers;
std::vector<VkFence> m_fences;
uint32_t m_buffer_idx;
void create_fences();
std::vector<VkSemaphoreSubmitInfoKHR> get_wait_semaphores_for(
const RenderGraphBuffer* pBuffer,
uint32_t cmdbuf_idx
) const;
void wait_for_previous_frame(uint32_t buffer_idx);
bool is_recording_invalid(const RenderGraphBuffer& buffer, uint32_t cmdbuf_idx);
void record_command_buffer(
uint32_t buffer_idx,
uint32_t cmdbuf_idx,
uint32_t output_idx
);
void submit_command_buffer(
uint32_t buffer_idx,
uint32_t cmdbuf_idx,
VkSemaphore wait_semaphore,
VkFence fence,
uint32_t output_idx
);
bool is_batch_writing_to_final_image(const Batch& buffer) const;
public:
const RenderGraphBuffer& buffer(uint32_t idx) const {
return *m_buffers[idx];
}
RenderGraph& invalidate(const std::string& name);
RenderGraph(const Gpu* gpu,
const std::string& output_name,
const std::vector<RenderGraphBuffer*>& buffers,
AdjacencyMatrix* dependencies
);
/**
* Runs the render graph. Outputs to final image.
* @param chainImageIdx index of image in the final image chain
* @param final_image_fence fence to wait on for final image. The render graph will attempt to run as much tasks before waiting as possible.
*/
void run(uint32_t chainImageIdx, VkSemaphore semaphore, VkFence final_image_fence);
};
}
@@ -0,0 +1,136 @@
#include <set>
#include <map>
#include <string>
#include <iostream>
#include <volk.h>
#include "ImageChain.hpp"
#include "RenderGraph.hpp"
#include "Resource.hpp"
#include "RenderPass.hpp"
namespace lft::rg {
struct CommandBufferDefinition {
uint32_t first_task_idx;
uint32_t num_tasks;
std::vector<uint32_t> wait_signals_idx;
};
struct GraphAllocationInfo {
const Gpu* gpu;
ImageChain output_chain;
std::string output_name;
std::vector<ImageResourceDescription> resources;
std::vector<TaskInfo> render_passes;
std::vector<CommandBufferDefinition> command_buffers;
};
std::vector<VkCommandBuffer> allocate_command_buffers(const Gpu* gpu, uint32_t count);
class Allocator {
private:
const Gpu* m_gpu;
std::vector<std::map<std::string, ImageResource>> m_resources;
const ImageChain& m_output_chain;
std::string m_output_name;
std::map<std::string, VkRenderPass> m_renderpasses;
std::vector<RenderGraphBuffer> m_buffers;
std::vector<TaskInfo> m_tasks;
ImageResource allocate_image_resources(
const ImageResourceDescription& description,
bool is_color
);
BufferResource allocate_buffer_resource(
const BufferResourceDescription& description
);
void collect_resources(const std::vector<TaskInfo>& tasks);
VkAttachmentDescription2 create_attachment_description(
const ImageResourceDescription& definition,
bool is_color,
std::map<std::string, uint32_t>& resource_count_down,
std::set<std::string>& cleared_resources
);
VkRenderPass allocate_renderpass(
const TaskInfo& task,
std::map<std::string, uint32_t>& resource_count_down,
std::set<std::string>& cleared_resources
);
void prepare_renderpasses(const std::vector<TaskInfo>& tasks);
inline const ImageView get_attachment(
const std::string& name,
uint32_t output_chain_idx
) const {
if(name == m_output_name) {
return m_output_chain.views()[output_chain_idx];
}
if(m_resources[0].find(name) == m_resources[0].end()) {
throw std::runtime_error("Resource " + name + " not found in context");
}
return ImageView(m_resources[0].find(name)->second.image_view);
}
VkFramebuffer create_framebuffer(
const TaskInfo& task,
VkRenderPass render_pass,
uint32_t output_image_idx
);
RenderGraphBuffer allocate_buffer(const GraphAllocationInfo& info, uint32_t buffer_idx);
public:
GET(m_resources.size(), num_buffers);
Allocator(const GraphAllocationInfo& info, uint32_t num_buffers) :
m_gpu(info.gpu),
m_resources(num_buffers),
m_output_chain(info.output_chain),
m_output_name(info.output_name),
m_tasks(info.render_passes)
{
collect_resources(info.render_passes);
prepare_renderpasses(info.render_passes);
for(uint32_t i = 0; i < num_buffers; i++) {
m_buffers.push_back(allocate_buffer(info, i));
}
for(auto& renderpass : info.render_passes) {
/* TaskBuildInfo build_info(info.gpu, num_buffers, {
.x = 0,
.y = 0,
.width = (float)info.output_chain.extent().width,
.height = (float)info.output_chain.extent().height,
.minDepth = 0.0f,
.maxDepth = 1.0f
},
m_renderpasses[renderpass.name()], m_resources);
renderpass.m_build_func(build_info, renderpass.m_pContext); */
}
}
RenderGraph allocate() {
// return RenderGraph(m_gpu, m_buffers);
}
};
}
@@ -0,0 +1,108 @@
#pragma once
#include <unordered_map>
#include <string>
#include "RenderPass.hpp"
#include "Resource.hpp"
#include "Task.hpp"
namespace lft::rg {
struct BatchOutput {
VkCommandBuffer cmdbuf;
bool is_recording_valid;
BatchOutput(VkCommandBuffer cmdbuf);
};
struct Batch {
std::vector<Task> tasks;
std::vector<uint32_t> barriers;
std::vector<BatchOutput> outputs;
VkSemaphore signal;
inline BatchOutput output(uint32_t idx) {
return outputs[idx];
}
Batch(std::vector<BatchOutput> outputs, VkSemaphore signal);
Batch& invalidate_recordings();
Batch& insert_task(uint32_t idx, Task& task);
Batch& update_task(uint32_t idx, Task& task);
Batch& remove_task(uint32_t idx);
bool equals(const Batch& rhs) const;
};
class RenderGraphBuffer {
const Gpu* m_gpu;
uint32_t m_index;
public:
std::unordered_map<std::string, BufferResource> m_buffer_resources;
std::unordered_map<std::string, ImageResource> m_image_resources;
private:
std::vector<Batch> m_batches;
std::vector<VkSemaphore> m_final_semaphores;
public:
GET(m_index, index);
RenderGraphBuffer(
const Gpu* gpu,
uint32_t index,
uint32_t num_outputs);
Batch& batch(uint32_t idx) {
return m_batches[idx];
}
const Batch& batch(uint32_t idx) const {
return m_batches[idx];
}
uint32_t num_batches() const {
return m_batches.size();
}
Batch& insert_batch(uint32_t idx, uint32_t num_outputs);
void remove_batch(uint32_t idx);
VkSemaphore final_signal(uint32_t output_idx) const {
return m_final_semaphores[output_idx];
}
#pragma region IMAGE RESOURCES
bool has_image_resource(const std::string& name) const {
return m_image_resources.find(name) != m_image_resources.end();
}
std::optional<const ImageResource*>
get_image_resource(const std::string& name) const {
if(!has_image_resource(name)) {
return {};
}
return &m_image_resources.find(name)->second;
}
void put_image_resource(
const std::string& name,
const ImageResource& resource
) {
m_image_resources.insert({name, resource});
}
#pragma endregion
bool equals(const RenderGraphBuffer& other) const;
};
}
@@ -0,0 +1,372 @@
#pragma once
#include <string>
#include <unordered_set>
#include <vector>
#include <print>
#include "AdjacencyMatrix.hpp"
#include "RenderGraph.hpp"
#include "ImageChain.hpp"
#include "RenderPass.hpp"
#include "RenderGraphAllocator.hpp"
namespace lft::rg {
class BuilderAllocator {
private:
const Gpu* m_gpu;
ImageChain m_output_chain;
std::string m_output_name;
std::vector<RenderGraphBuffer> m_buffers;
std::unordered_set<std::string> m_updated_tasks;
uint32_t m_num_buffers;
bool is_task_updated(const std::string& name) {
return std::find(m_updated_tasks.begin(),
m_updated_tasks.end(),
name) != m_updated_tasks.end();
}
Task create_graphics_task(
const TaskInfo& task_info,
RenderGraphBuffer* pBuffer,
TaskRenderPass render_pass
);
Task create_compute_task(
const TaskInfo& task_info,
RenderGraphBuffer* pBuffer
);
Task create_task(
const TaskInfo& task_info,
RenderGraphBuffer* pBuffer,
std::unordered_set<std::string>& cleared_resources,
std::unordered_map<std::string, uint32_t>& resource_count_down
);
ImageResource allocate_image_resource(
const ImageResourceDescription& desc
) const;
BufferResource allocate_buffer_resource(const BufferResourceDescription& desc) const;
ImageResourceDescription get_output_image_description() const {
return ImageResourceDescription(m_output_name,
m_output_chain.format(),
m_output_chain.extent(),
(VkClearValue){.color = {0.0f, 0.0f, 0.0f, 0.0f}},
true);
}
ImageResourceDescription correct_resource_description(ImageResourceDescription desc);
/**
* Looks for an image resource in buffer at buffer_idx. Returns if found. Allocates if not found.
* If the wanted image resouce is in the output chain, the output_chain_idx is used.
*/
ImageView get_attachment(
const ImageResourceDescription& desc,
RenderGraphBuffer* pBuffer,
uint32_t output_idx
);
VkSemaphore create_semaphore() {
VkSemaphoreCreateInfo semaphore_info = {
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
};
VkSemaphore semaphore;
if(vkCreateSemaphore(m_gpu->dev(), &semaphore_info, nullptr, &semaphore)) {
throw std::runtime_error("Failed to create semaphore");
}
return semaphore;
}
std::vector<VkCommandBuffer> allocate_command_buffer(uint32_t count) {
VkCommandBufferAllocateInfo cmdbuf_info = {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
.commandPool = m_gpu->graphics_command_pool(),
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
.commandBufferCount = count,
};
std::vector<VkCommandBuffer> cmdbufs(cmdbuf_info.commandBufferCount);
if(vkAllocateCommandBuffers(m_gpu->dev(), &cmdbuf_info, cmdbufs.data())) {
throw std::runtime_error("Failed to create command buffer");
}
return cmdbufs;
}
void update_task_queue(
RenderGraphBuffer* pBuffer,
const std::vector<TaskInfo>& task_infos
);
VkViewport get_viewport() {
return (VkViewport) {
.x = 0, .y = (float)m_output_chain.extent().height,
.width = (float)m_output_chain.extent().width,
.height = -(float)m_output_chain.extent().height,
.minDepth = 0, .maxDepth = 1.0
};
}
void update_task_buffer(const Task& task, const RenderGraphBuffer* pBuffer);
bool m_store_all_images = false;
VkExtent2D get_extent(VkExtent2D extent) const {
if(extent.width == 0) {
extent.width = m_output_chain.extent().width;
} if(extent.height == 0) {
extent.height = m_output_chain.extent().height;
}
return extent;
}
VkExtent2D get_extent_for_task(const TaskInfo& task_info) const {
VkExtent2D extent = task_info.m_extent;
if(extent.width == 0) {
extent.width = m_output_chain.extent().width;
} if(extent.height == 0) {
extent.height = m_output_chain.extent().height;
}
return extent;
}
public:
void remove_task(const std::string& name) {
m_updated_tasks.insert(name);
}
void set_store_all_images(bool value) {
m_store_all_images = value;
}
void set_image_chain(const ImageChain& image_chain) {
m_output_chain = image_chain;
for(int i = 0; i < m_buffers[0].num_batches(); i++) {
for(auto& task : m_buffers[0].batch(i).tasks) {
mark_task_updated(task.pDefinition.name());
}
}
for(auto& view : m_output_chain.views()) {
std::println("Set View: {:#06x}", (unsigned long)view.view);
}
}
GET(m_num_buffers, num_buffers);
REF(m_output_chain, image_chain);
BuilderAllocator(const Gpu* gpu,
ImageChain output_chain,
const std::string& output_name,
uint32_t num_buffers) :
m_gpu(gpu),
m_output_chain(output_chain),
m_output_name(output_name),
m_num_buffers(num_buffers)
{
for(uint32_t i = 0; i < num_buffers; i++) {
m_buffers.emplace_back(m_gpu, i, m_output_chain.count());
}
}
void mark_task_updated(const std::string& name) {
m_updated_tasks.insert(name);
}
void add_buffer_resource(
const std::string& name,
const std::vector<Buffer>& buffers,
size_t size
) {
/* if(buffers.size() <= m_output_chain.count()) {
throw std::runtime_error("Buffer count must be greater than output chain count");
} if(m_output_chain.count() % buffers.size() != 0) {
throw std::runtime_error("Buffer count must be a multiple of output chain count");
} */
for(uint32_t i = 0; i < m_buffers.size(); i++) {
m_buffers[i].m_buffer_resources.insert({name, BufferResource(buffers[i % buffers.size()].buf, size)});
}
}
void add_image_resource(
const std::string& name,
const std::vector<ImageResource> images
) {
if(images.size() <= m_output_chain.count()) {
throw std::runtime_error("Resource count must be greater than output chain count");
} if(m_output_chain.count() % images.size() != 0) {
throw std::runtime_error("Resource count must be a multiple of output chain count");
}
for(uint32_t i = 0; i < m_buffers.size(); i++) {
// m_buffers[i].m_image_resources[name] = images[i % images.size()];
}
}
VkAttachmentDescription2 create_attachment_description(
const ImageResourceDescription& definition,
bool is_first_write,
bool is_last_write
);
TaskRenderPass allocate_renderpass(
const TaskInfo& task,
std::unordered_map<std::string, uint32_t>& resource_count_down,
std::unordered_set<std::string>& cleared_resources
);
VkFramebuffer create_framebuffer(
const TaskInfo& task_info,
VkRenderPass renderpass,
RenderGraphBuffer* pBuffer,
uint32_t output_idx
);
RenderGraph allocate(
std::vector<TaskInfo>& tasks,
AdjacencyMatrix *dependencies
);
bool equals(const BuilderAllocator& other) const;
};
std::vector<TaskInfo> topology_sort(std::vector<TaskInfo>& tasks, const std::string& output_name);
class Builder {
private:
std::string m_output_name;
std::map<std::string, uint32_t> m_name_to_task_idx;
std::vector<TaskInfo> m_tasks;
BuilderAllocator m_allocator;
// counter for how many times a resource is written to
std::unordered_map<std::string, uint32_t> m_resource_write_counts;
const TaskInfo& get_task_by_name(const std::string& name) {
if(m_name_to_task_idx.find(name) == m_name_to_task_idx.end()) {
throw std::runtime_error("Task does not exist");
}
return m_tasks[m_name_to_task_idx[name]];
}
bool m_store_all_images;
public:
void store_all_images() {
m_store_all_images = true;
}
void set_image_chain(const ImageChain& output_chain) {
m_allocator.set_image_chain(output_chain);
}
Builder(const Gpu* gpu,
ImageChain output_chain,
const std::string& output_name
) :
m_output_name(output_name),
m_allocator(gpu, output_chain, output_name, 1)
{
}
/**
* Adds allocated buffer resource
*/
void add_buffer_resource(
const std::string& name,
const std::vector<Buffer>& buffers,
size_t size
) {
m_allocator.add_buffer_resource(name, buffers, size);
}
void add_image_resource(
const std::string& name,
const std::vector<ImageResource> images
) {
m_allocator.add_image_resource(name, images);
}
bool is_task_ok(const TaskInfo& task) {
for(auto& dependency : task.dependencies()) {
for(auto& output : task.color_outputs()) {
if(output.name() == dependency) {
return false;
}
}
if(task.depth_output().has_value() &&
task.depth_output()->name() == dependency) {
return false;
}
for(auto& output : task.buffer_outputs()) {
if(output.name() == dependency) {
return false;
}
}
}
return true;
}
void add_task(TaskInfo task) {
if(!is_task_ok(task)) {
throw std::runtime_error("Task " + task.name() + " output to one of it's dependencies. That is prohibited. To simulate this behaviour, for instance in compute shader, allocate the resource yourself and add it with `add_image_resource` or `add_buffer_resource`.");
}
std::string task_name = task.name();
auto found = std::find_if(m_tasks.begin(), m_tasks.end(),
[&task_name](const TaskInfo& i) {
return i.name() == task_name;
});
if(found != m_tasks.end()) {
m_tasks.erase(found);
}
if(task.is_output_to_final()) {
if(!task.has_output(m_output_name)) {
task.add_color_output(m_output_name,
m_allocator.image_chain().format(),
m_allocator.image_chain().extent(),
{0.0f, 0.0f, 0.0f, 1.0f}
);
}
}
m_tasks.push_back(task);
m_name_to_task_idx[task.m_name] = m_tasks.size() - 1;
m_allocator.mark_task_updated(task.name());
}
void remove_task(const std::string& name) {
m_allocator.remove_task(name);
m_tasks.erase(std::remove_if(m_tasks.begin(), m_tasks.end(),
[name](const TaskInfo& task) {
return task.name() == name;
}), m_tasks.end());
}
RenderGraph build();
};
}
@@ -0,0 +1,478 @@
#pragma once
#include <bitset>
#include <vector>
#include <string>
#include <functional>
#include <optional>
#include <memory>
#include <iostream>
#include "Gpu.hpp"
#include "props.hpp"
#include "Resource.hpp"
#include "Recording.hpp"
#include <volk.h>
#include <map>
namespace lft::rg {
class ImageResourceDescription {
private:
std::string m_name;
VkFormat m_format;
VkExtent2D m_extent;
VkClearValue m_clear_value;
bool m_is_color;
public:
REF(m_name, name);
GET(m_format, format);
GET(m_extent, extent);
GET(m_clear_value, clear_value);
GET(m_is_color, is_color);
inline void set_extent(VkExtent2D extent) {
m_extent = extent;
}
ImageResourceDescription(
const std::string& name,
VkFormat format,
VkExtent2D extent,
VkClearValue clear_value,
bool is_color
) :
m_name(name),
m_format(format),
m_extent(extent),
m_clear_value(clear_value),
m_is_color(is_color) {
}
bool equals(const ImageResourceDescription& other) const {
return m_name == other.m_name &&
m_format == other.m_format &&
m_extent.width == other.m_extent.width &&
m_extent.height == other.m_extent.height &&
m_clear_value.color.uint32[0] == other.m_clear_value.color.uint32[0] &&
m_clear_value.color.uint32[1] == other.m_clear_value.color.uint32[1] &&
m_clear_value.color.uint32[2] == other.m_clear_value.color.uint32[2] &&
m_clear_value.color.uint32[3] == other.m_clear_value.color.uint32[3] &&
m_clear_value.depthStencil.depth == other.m_clear_value.depthStencil.depth;
}
};
struct BufferResourceDescription {
private:
std::string m_name;
VkDeviceSize m_size;
public:
REF(m_name, name);
GET(m_size, size);
BufferResourceDescription(
const std::string& name,
VkDeviceSize size
) :
m_name(name),
m_size(size) {
}
bool equals(const BufferResourceDescription& other) const {
return m_name == other.m_name && m_size == other.m_size;
}
};
class TaskRecordInfo {
const Gpu* m_gpu;
lft::Recording m_recording;
uint32_t m_buffer_idx;
uint32_t m_image_idx;
VkViewport m_viewport;
public:
GET(m_gpu, gpu);
REF(m_recording, recording);
GET(m_image_idx, image_idx);
GET(m_buffer_idx, buffer_idx);
GET(m_viewport, viewport);
TaskRecordInfo(
const Gpu* gpu,
lft::Recording recording,
uint32_t buffer_idx,
uint32_t image_in_flight_idx,
VkViewport viewport) :
m_recording(recording),
m_image_idx(image_in_flight_idx),
m_buffer_idx(buffer_idx),
m_gpu(gpu),
m_viewport(viewport) {
}
};
class TaskBuildInfo {
const Gpu* m_gpu;
uint32_t m_buffer_idx;
uint32_t m_num_buffers;
VkViewport m_viewport;
VkRenderPass m_renderpass;
std::unordered_map<std::string, ImageResource> m_resources;
public:
GET(m_gpu, gpu);
GET(m_num_buffers, num_buffers);
GET(m_buffer_idx, buffer_idx);
GET(m_viewport, viewport);
GET(m_renderpass, renderpass);
inline ImageResource get_resource(
const std::string& name
) const {
return m_resources.find(name)->second;
}
TaskBuildInfo(
const Gpu* gpu,
uint32_t buffer_idx,
uint32_t num_buffers,
VkViewport viewport,
VkRenderPass renderpass,
std::unordered_map<std::string, ImageResource> resources) :
m_gpu(gpu),
m_buffer_idx(buffer_idx),
m_num_buffers(num_buffers),
m_viewport(viewport),
m_renderpass(renderpass),
m_resources(resources) {
}
};
enum TaskType {
GRAPHICS_TASK,
COMPUTE_TASK,
RAY_TRACING_TASK
};
struct TaskInfo {
typedef std::function<void(const TaskBuildInfo&, void*)> TaskBuildFunc;
typedef std::function<void(const TaskRecordInfo&, void*)> TaskRecordFunc;
std::string m_name;
TaskType m_type;
void *m_pContext;
TaskBuildFunc m_build_func;
TaskRecordFunc m_record_func;
std::vector<std::string> m_dependencies;
std::vector<std::string> m_recording_dependencies;
std::vector<BufferResourceDescription> m_buffer_outputs;
std::vector<ImageResourceDescription> m_color_outputs;
std::optional<ImageResourceDescription> m_depth_output;
bool m_is_output_to_final;
VkExtent2D m_extent;
REF(m_name, name);
GET(m_type, type);
REF(m_build_func, build_func);
REF(m_record_func, record_func);
REF(m_dependencies, dependencies);
REF(m_recording_dependencies, recording_dependencies);
REF(m_buffer_outputs, buffer_outputs);
REF(m_color_outputs, color_outputs);
REF(m_depth_output, depth_output);
GET(m_is_output_to_final, is_output_to_final);
TaskInfo() {
}
template<typename T>
TaskInfo(const std::string& name,
TaskType type,
T *pContext,
std::function<void(const TaskBuildInfo&, T*)> build_func,
std::function<void(const TaskRecordInfo&, T*)> record_func
) :
m_name(name),
m_type(type),
m_pContext(pContext),
m_build_func(build_func),
m_record_func(record_func),
m_extent(0, 0)
{
}
bool has_output(const std::string& name) const {
if(m_depth_output.has_value() && m_depth_output->name() == name) {
return true;
}
if(std::any_of(m_buffer_outputs.begin(), m_buffer_outputs.end(),
[name](const BufferResourceDescription& output) {
return output.name() == name;
})) {
return true;
}
if(std::any_of(m_color_outputs.begin(), m_color_outputs.end(),
[name](const ImageResourceDescription& output) {
return output.name() == name;
})) {
return true;
}
return false;
}
TaskInfo& add_color_output(const std::string& name,
VkFormat format,
VkExtent2D extent,
VkClearColorValue clear_value) {
m_color_outputs.emplace_back(name, format, extent,
VkClearValue {
.color = clear_value
}, true);
return *this;
}
TaskInfo& set_depth_output(
const std::string& name,
VkFormat format,
VkExtent2D extent,
VkClearDepthStencilValue clear_value
) {
m_depth_output = ImageResourceDescription(name, format, extent,
VkClearValue {
.depthStencil = clear_value
}, false);
return *this;
}
TaskInfo& add_dependency(const std::string& dependency) {
m_dependencies.emplace_back(dependency);
return *this;
}
TaskInfo& add_recording_dependency(const std::string& dependency) {
m_recording_dependencies.emplace_back(dependency);
return *this;
}
TaskInfo& set_extent(VkExtent2D extent) {
m_extent = extent;
return *this;
}
bool equals(const TaskInfo& other) const {
if(this->name() != other.name()) {
std::cout << "Names are not the same: " << name() << " != " << other.name() << std::endl;
return false;
}
if(this->m_type != other.m_type) {
std::cout << "Task types are not the same: " << m_type << " != " << other.m_type << std::endl;
return false;
}
if(m_dependencies != other.m_dependencies) {
std::cout << "Dependencies are different" << std::endl;
return false;
}
if(m_buffer_outputs.size() != other.m_buffer_outputs.size()) {
return false;
}
for(uint32_t i = 0; i < m_buffer_outputs.size(); i++) {
auto output = other.m_buffer_outputs[i];
auto found = std::find_if(
m_buffer_outputs.begin(),
m_buffer_outputs.end(),
[output](const BufferResourceDescription& desc) {
return output.equals(desc);
});
if(found == other.m_buffer_outputs.end()) {
std::cout << "Missing buffer output: " << output.name() << std::endl;
return false;
}
}
for(uint32_t i = 0; i < m_color_outputs.size(); i++) {
auto output = other.m_color_outputs[i];
auto found = std::find_if(
m_color_outputs.begin(),
m_color_outputs.end(),
[output](const ImageResourceDescription& desc) {
return output.equals(desc);
});
if(found == other.m_color_outputs.end()) {
std::cout << "Missing color output: " << output.name() << std::endl;
return false;
}
}
if(m_depth_output.has_value() != other.m_depth_output.has_value()) {
std::cout << "Depth output differ" << std::endl;
return false;
}
if(m_depth_output.has_value() && !m_depth_output->equals(other.m_depth_output.value())) {
std::cout << "Depth output differ" << std::endl;
return false;
}
if(m_extent.width != other.m_extent.width ||
m_extent.height != other.m_extent.height) {
std::cout << std::format("Extent differ: [{},{}] != [{},{}]", m_extent.width, m_extent.height, other.m_extent.width, other.m_extent.height) << std::endl;
return false;
}
return true;
}
};
class ComputeTaskBuilder {
private:
TaskInfo m_task_info;
public:
ComputeTaskBuilder(const std::string& name,
void *pContext,
std::function<void(const TaskBuildInfo&, void*)> build_func,
std::function<void(const TaskRecordInfo&, void*)> record_func
) :
m_task_info(name, COMPUTE_TASK, pContext, build_func, record_func) {
}
ComputeTaskBuilder& add_buffer_output(const std::string& name,
VkDeviceSize size) {
m_task_info.m_buffer_outputs.emplace_back(name, size);
return *this;
}
ComputeTaskBuilder& add_dependency(const std::string& dependency) {
m_task_info.m_dependencies.emplace_back(dependency);
return *this;
}
ComputeTaskBuilder& add_recording_dependency(const std::string& dependency) {
m_task_info.m_recording_dependencies.emplace_back(dependency);
return *this;
}
TaskInfo build() {
return m_task_info;
}
};
class RenderTaskBuilder {
private:
TaskInfo m_task_info;
public:
RenderTaskBuilder(const std::string& name,
void* pContext,
std::function<void(const TaskBuildInfo&, void*)> build_func,
std::function<void(const TaskRecordInfo&, void*)> record_func
) :
m_task_info(name, GRAPHICS_TASK, pContext, build_func, record_func) {
}
RenderTaskBuilder& add_color_output(const std::string& name,
VkFormat format,
VkExtent2D extent = VkExtent2D(0.0f, 0.0f),
VkClearColorValue clear_value = {0.0f, 0.0f, 0.0f, 0.0f}) {
m_task_info.m_color_outputs.emplace_back(name, format, extent,
VkClearValue {
.color = clear_value
}, true);
return *this;
}
RenderTaskBuilder& set_depth_output(
const std::string& name,
VkFormat format,
VkExtent2D extent = VkExtent2D(0.0f, 0.0f),
VkClearDepthStencilValue clear_value = {1.0f, 0}
) {
m_task_info.m_depth_output = ImageResourceDescription(name, format, extent,
VkClearValue {
.depthStencil = clear_value
}, false);
return *this;
}
RenderTaskBuilder& set_output_to_final() {
m_task_info.m_is_output_to_final = true;
return *this;
}
RenderTaskBuilder& add_dependency(const std::string& dependency) {
m_task_info.m_dependencies.emplace_back(dependency);
return *this;
}
RenderTaskBuilder& add_recording_dependency(const std::string& dependency) {
m_task_info.m_recording_dependencies.emplace_back(dependency);
return *this;
}
RenderTaskBuilder& set_extent(VkExtent2D extent) {
m_task_info.m_extent = extent;
return *this;
}
TaskInfo build() {
return m_task_info;
}
};
template<typename T>
RenderTaskBuilder render_task(const std::string& name,
T* pContext,
std::function<void(const TaskBuildInfo&, T*)> build_func,
std::function<void(const TaskRecordInfo&, T*)> record_func
) {
return RenderTaskBuilder(name, (void*)pContext,
[build_func, pContext](const TaskBuildInfo& info, void* ctx) {
build_func(info, pContext);
},
[record_func, pContext](const TaskRecordInfo& info, void* ctx) {
record_func(info, pContext);
});
}
template<typename T>
ComputeTaskBuilder compute_task(const std::string& name,
T* pContext,
std::function<void(const TaskBuildInfo&, T*)> build_func,
std::function<void(const TaskRecordInfo&, T*)> record_func
) {
return ComputeTaskBuilder(name, (void*)pContext,
[build_func, pContext](const TaskBuildInfo& info, void* ctx) {
build_func(info, pContext);
},
[record_func, pContext](const TaskRecordInfo& info, void* ctx) {
record_func(info, pContext);
});
}
}
+31
View File
@@ -0,0 +1,31 @@
#pragma once
#include <volk.h>
class ImageResource {
public:
VkImage image;
VkImageView image_view;
VkExtent2D extent;
ImageResource(const ImageResource&) = default;
ImageResource(ImageResource&) = default;
ImageResource(ImageResource&&) = default;
ImageResource(VkImage image, VkImageView image_view, VkExtent2D extent) :
image(image),
image_view(image_view),
extent(extent) {
};
};
class BufferResource {
public:
VkBuffer buffer;
VkDeviceSize size;
BufferResource(VkBuffer buffer, VkDeviceSize size) :
buffer(buffer),
size(size) {
};
};
+93
View File
@@ -0,0 +1,93 @@
#pragma once
#include <cassert>
#include <vector>
#include <iostream>
#include <volk.h>
#include "RenderPass.hpp"
namespace lft::rg {
#define MAX_ATTACHMENT_COUNT 9
#define MAX_COLOR_ATTACHMENT_COUNT MAX_ATTACHMENT_COUNT - 1
struct TaskRenderPassState {
uint32_t num_attachments : 4;
uint32_t resource_flags : 18;
TaskRenderPassState() {
}
TaskRenderPassState(
uint32_t num_color_attachments,
bool has_depth_attachment
) {
assert(num_color_attachments <= MAX_COLOR_ATTACHMENT_COUNT);
num_attachments = num_color_attachments + has_depth_attachment;
}
inline void set_resource_is_first(uint32_t resource_idx) {
assert(resource_idx < MAX_ATTACHMENT_COUNT);
resource_flags |= (1 << resource_idx);
}
inline void set_resource_is_last(uint32_t resource_idx) {
assert(resource_idx < MAX_ATTACHMENT_COUNT);
resource_flags |= (1 << (resource_idx + 9));
}
inline bool is_resource_first(uint32_t resource_idx) const {
assert(resource_idx < MAX_ATTACHMENT_COUNT);
return resource_flags & (1 << resource_idx);
}
inline bool is_resource_last(uint32_t resource_idx) const {
assert(resource_idx < MAX_ATTACHMENT_COUNT);
return resource_flags & (1 << (resource_idx + 9));
}
};
struct TaskRenderPass {
VkRenderPass render_pass;
TaskRenderPassState state;
TaskRenderPass() : render_pass(VK_NULL_HANDLE) {
}
TaskRenderPass(const VkRenderPass rp, const TaskRenderPassState state) :
render_pass(rp),
state(state) {
}
};
struct Task {
TaskInfo pDefinition;
TaskRenderPass render_pass;
std::vector<uint32_t> rp_attachment_states;
std::vector<VkFramebuffer> framebuffer;
VkExtent2D extent;
bool equals(const Task& other) const {
if(!pDefinition.equals(other.pDefinition)) {
return false;
}
if(framebuffer.size() != other.framebuffer.size()) {
std::cout << "Number of framebuffers is not equal" << std::endl;
return false;
}
return true;
}
};
}