initial
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
file(GLOB CXXFILES src/*.cpp)
|
||||
add_library(loft_render_graph OBJECT ${CXXFILES})
|
||||
add_library(loft::render_graph ALIAS loft_render_graph)
|
||||
|
||||
target_link_libraries(loft_render_graph
|
||||
PUBLIC
|
||||
loft::common
|
||||
loft::base
|
||||
loft_window
|
||||
volk::volk
|
||||
)
|
||||
|
||||
target_include_directories(loft_render_graph
|
||||
PUBLIC
|
||||
include/
|
||||
)
|
||||
|
||||
# add_subdirectory(tests)
|
||||
add_test(NAME RenderGraphBuilderTests COMMAND RenderGraphBuilderTests)
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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) {
|
||||
};
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,270 @@
|
||||
#include "RenderGraph.hpp"
|
||||
#include "AdjacencyMatrix.hpp"
|
||||
#include "Recording.hpp"
|
||||
#include "RenderGraphBuffer.hpp"
|
||||
#include "RenderPass.hpp"
|
||||
#include <algorithm>
|
||||
#include <print>
|
||||
#include <vulkan/vulkan_core.h>
|
||||
|
||||
namespace lft::rg {
|
||||
|
||||
void RenderGraph::create_fences() {
|
||||
VkFenceCreateInfo fence_info = {
|
||||
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
|
||||
.flags = VK_FENCE_CREATE_SIGNALED_BIT
|
||||
};
|
||||
|
||||
m_fences.resize(m_buffers.size());
|
||||
for(uint32_t fence_idx = 0;
|
||||
fence_idx < m_buffers.size();
|
||||
fence_idx++
|
||||
) {
|
||||
if(vkCreateFence(m_gpu->dev(),
|
||||
&fence_info, nullptr, &m_fences[fence_idx])) {
|
||||
throw std::runtime_error("Failed to create fence");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RenderGraph& RenderGraph::invalidate(const std::string& name) {
|
||||
/* for(auto& buffer : m_buffers) {
|
||||
for(uint32_t i = 0; i < buffer.m_command_buffers.size(); i++) {
|
||||
buffer.m_recording_validity[i] = false;
|
||||
}
|
||||
} */
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
RenderGraph::RenderGraph(
|
||||
const Gpu* gpu,
|
||||
const std::string& output_name,
|
||||
const std::vector<RenderGraphBuffer*>& buffers,
|
||||
AdjacencyMatrix* dependencies
|
||||
) :
|
||||
m_gpu(gpu),
|
||||
m_output_name(output_name),
|
||||
m_buffers(std::move(buffers)),
|
||||
m_buffer_idx(0),
|
||||
m_dependency_matrix(dependencies)
|
||||
{
|
||||
if(m_buffers.size() == 0) {
|
||||
throw std::runtime_error("Number of buffers cannot be 0");
|
||||
}
|
||||
|
||||
create_fences();
|
||||
}
|
||||
|
||||
void RenderGraph::wait_for_previous_frame(uint32_t buffer_idx) {
|
||||
vkWaitForFences(m_gpu->dev(),
|
||||
1,
|
||||
&m_fences[buffer_idx],
|
||||
VK_TRUE, UINT64_MAX);
|
||||
vkResetFences(m_gpu->dev(),
|
||||
1,
|
||||
&m_fences[buffer_idx]);
|
||||
}
|
||||
|
||||
void RenderGraph::record_command_buffer(
|
||||
uint32_t buffer_idx,
|
||||
uint32_t batch_idx,
|
||||
uint32_t output_idx
|
||||
) {
|
||||
RenderGraphBuffer* pBuffer = m_buffers[buffer_idx];
|
||||
VkCommandBuffer cmdbuf = pBuffer->batch(batch_idx).output(output_idx).cmdbuf;
|
||||
|
||||
VkCommandBufferBeginInfo cmdbuf_begin_info = {
|
||||
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
|
||||
};
|
||||
|
||||
if(vkBeginCommandBuffer(cmdbuf,
|
||||
&cmdbuf_begin_info)) {
|
||||
throw std::runtime_error("Failed to begin command buffer");
|
||||
}
|
||||
|
||||
|
||||
auto& tasks = pBuffer->batch(batch_idx).tasks;
|
||||
TaskRecordInfo record_info(
|
||||
m_gpu,
|
||||
lft::Recording(cmdbuf),
|
||||
buffer_idx,
|
||||
output_idx,
|
||||
VkViewport {
|
||||
.x = 0,
|
||||
.y = (float)tasks[0].extent.height,
|
||||
.width = (float)tasks[0].extent.width,
|
||||
.height = -(float)tasks[0].extent.height,
|
||||
.minDepth = 0.0f,
|
||||
.maxDepth = 1.0f,
|
||||
});
|
||||
for(auto& task : tasks) {
|
||||
if(task.pDefinition.type() == GRAPHICS_TASK) {
|
||||
std::vector<VkClearValue> clear_values(
|
||||
task.pDefinition.color_outputs().size() +
|
||||
task.pDefinition.depth_output().has_value()
|
||||
);
|
||||
for(uint32_t i = 0; i < task.pDefinition.color_outputs().size(); i++) {
|
||||
clear_values[i] = task.pDefinition.color_outputs()[i].clear_value();
|
||||
}
|
||||
|
||||
if(task.pDefinition.depth_output().has_value()) {
|
||||
clear_values[task.pDefinition.color_outputs().size()] =
|
||||
task.pDefinition.depth_output()->clear_value();
|
||||
}
|
||||
|
||||
VkRenderPassBeginInfo render_pass_begin_info = {
|
||||
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
|
||||
.renderPass = task.render_pass.render_pass,
|
||||
.framebuffer = task.framebuffer[output_idx],
|
||||
.renderArea = {
|
||||
.offset = {0, 0},
|
||||
.extent = task.extent,
|
||||
},
|
||||
.clearValueCount = (uint32_t)clear_values.size(),
|
||||
.pClearValues = clear_values.data(),
|
||||
};
|
||||
|
||||
vkCmdBeginRenderPass(cmdbuf,
|
||||
&render_pass_begin_info, VK_SUBPASS_CONTENTS_INLINE);
|
||||
}
|
||||
|
||||
task.pDefinition.m_record_func(record_info, task.pDefinition.m_pContext);
|
||||
|
||||
if(task.pDefinition.type() == GRAPHICS_TASK) {
|
||||
vkCmdEndRenderPass(cmdbuf);
|
||||
}
|
||||
}
|
||||
|
||||
if(vkEndCommandBuffer(cmdbuf)) {
|
||||
throw std::runtime_error("Failed to end command buffer");
|
||||
}
|
||||
}
|
||||
|
||||
VkSemaphoreSubmitInfoKHR create_simple_semaphore_submit(VkSemaphore signal) {
|
||||
return {
|
||||
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO_KHR,
|
||||
.semaphore = signal,
|
||||
.value = 1,
|
||||
.stageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT_KHR,
|
||||
.deviceIndex = 0
|
||||
};
|
||||
}
|
||||
|
||||
std::vector<VkSemaphoreSubmitInfoKHR> RenderGraph::get_wait_semaphores_for(
|
||||
const RenderGraphBuffer* pBuffer,
|
||||
uint32_t batch_idx
|
||||
) const {
|
||||
std::vector<VkSemaphoreSubmitInfoKHR> semaphores;
|
||||
|
||||
for(auto& task : pBuffer->batch(batch_idx).tasks) {
|
||||
auto dependencies = m_dependency_matrix->get_dependencies(task.pDefinition.name());
|
||||
|
||||
for(int32_t i = batch_idx - 1; i >= 0; i--) {
|
||||
for(auto dependency : dependencies) {
|
||||
if(std::find_if(pBuffer->batch(i).tasks.begin(), pBuffer->batch(i).tasks.end(),
|
||||
[dependency](const Task& other) {
|
||||
return other.pDefinition.name() == dependency;
|
||||
}) != pBuffer->batch(i).tasks.end()) {
|
||||
semaphores.push_back(create_simple_semaphore_submit(pBuffer->batch(i).signal));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return semaphores;
|
||||
}
|
||||
|
||||
void RenderGraph::submit_command_buffer(
|
||||
uint32_t buffer_idx,
|
||||
uint32_t batch_idx,
|
||||
VkSemaphore wait_semaphore,
|
||||
VkFence fence,
|
||||
uint32_t output_idx
|
||||
) {
|
||||
RenderGraphBuffer* pBuffer = m_buffers[buffer_idx];
|
||||
|
||||
VkCommandBufferSubmitInfoKHR cmdbuf = {
|
||||
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO_KHR,
|
||||
.commandBuffer = pBuffer->batch(batch_idx).output(output_idx).cmdbuf,
|
||||
.deviceMask = 0,
|
||||
};
|
||||
|
||||
VkSemaphoreSubmitInfo signal_info = {
|
||||
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO,
|
||||
.semaphore = batch_idx == pBuffer->num_batches() - 1 ?
|
||||
pBuffer->final_signal(output_idx) :
|
||||
pBuffer->batch(batch_idx).signal,
|
||||
.value = 1,
|
||||
.stageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT_KHR,
|
||||
.deviceIndex = 0
|
||||
};
|
||||
|
||||
auto wait_on_semaphores = get_wait_semaphores_for(pBuffer, batch_idx);
|
||||
if(wait_semaphore) {
|
||||
wait_on_semaphores.push_back(create_simple_semaphore_submit(wait_semaphore));
|
||||
}
|
||||
|
||||
VkSubmitInfo2 submit_info = {
|
||||
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2,
|
||||
.waitSemaphoreInfoCount = (uint32_t)wait_on_semaphores.size(),
|
||||
.pWaitSemaphoreInfos = wait_on_semaphores.data(),
|
||||
.commandBufferInfoCount = 1,
|
||||
.pCommandBufferInfos = &cmdbuf,
|
||||
.signalSemaphoreInfoCount = 1,
|
||||
.pSignalSemaphoreInfos = &signal_info,
|
||||
};
|
||||
|
||||
m_gpu->enqueue_graphics(&submit_info, fence);
|
||||
}
|
||||
|
||||
bool RenderGraph::is_recording_invalid(const RenderGraphBuffer& buffer,
|
||||
uint32_t cmdbuf_idx) {
|
||||
return false;
|
||||
// return !buffer.m_recording_validity[cmdbuf_idx];
|
||||
}
|
||||
|
||||
|
||||
bool RenderGraph::is_batch_writing_to_final_image(const Batch& buffer) const {
|
||||
return std::any_of(buffer.tasks.begin(), buffer.tasks.end(),
|
||||
[this](const Task& task) {
|
||||
return task.pDefinition.is_output_to_final() || task.pDefinition.has_output(m_output_name);
|
||||
});
|
||||
}
|
||||
|
||||
void RenderGraph::run(uint32_t chainImageIdx,
|
||||
VkSemaphore semaphore_signal_for_final_image,
|
||||
VkFence fence_signal_for_final_image
|
||||
) {
|
||||
uint32_t buffer_idx = (m_buffer_idx + 1) % m_buffers.size();
|
||||
auto& buffer = m_buffers[buffer_idx];
|
||||
|
||||
// the render graph manages it's resource and must therefore itself wait
|
||||
// for them to be free for write.
|
||||
wait_for_previous_frame(buffer_idx);
|
||||
bool is_fence_reset = false;
|
||||
|
||||
for(uint32_t idx = 0; idx < buffer->num_batches(); idx++) {
|
||||
// if(is_recording_invalid(buffer, idx - 1)) {
|
||||
record_command_buffer(buffer_idx, idx, chainImageIdx);
|
||||
// }
|
||||
|
||||
VkFence fence = idx == buffer->num_batches() - 1 ?
|
||||
m_fences[buffer_idx] : VK_NULL_HANDLE;
|
||||
VkSemaphore wait_semaphore = VK_NULL_HANDLE;
|
||||
if (!is_fence_reset && fence_signal_for_final_image && is_batch_writing_to_final_image(buffer->batch(idx))) {
|
||||
vkWaitForFences(m_gpu->dev(), 1, &fence_signal_for_final_image, VK_TRUE, UINT64_MAX);
|
||||
vkResetFences(m_gpu->dev(), 1, &fence_signal_for_final_image);
|
||||
is_fence_reset = true;
|
||||
}
|
||||
|
||||
if(semaphore_signal_for_final_image && is_batch_writing_to_final_image(buffer->batch(idx))) {
|
||||
wait_semaphore = semaphore_signal_for_final_image;
|
||||
}
|
||||
|
||||
submit_command_buffer(buffer_idx, idx, wait_semaphore, fence, chainImageIdx);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,364 @@
|
||||
|
||||
#include "RenderGraphAllocator.hpp"
|
||||
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
#include <volk.h>
|
||||
|
||||
#include "FramebufferBuilder.hpp"
|
||||
#include "RenderPass.hpp"
|
||||
#include "RenderGraph.hpp"
|
||||
|
||||
namespace lft::rg {
|
||||
|
||||
ImageResource Allocator::allocate_image_resources(
|
||||
const ImageResourceDescription& description,
|
||||
bool is_color
|
||||
) {
|
||||
MemoryAllocationInfo memory_info = {
|
||||
.usage = MEMORY_USAGE_AUTO_PREFER_DEVICE
|
||||
};
|
||||
|
||||
ImageCreateInfo image_info = {
|
||||
.extent = description.extent(),
|
||||
.format = description.format(),
|
||||
.usage = VK_IMAGE_USAGE_SAMPLED_BIT |
|
||||
(VkImageUsageFlags)(is_color ?
|
||||
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT :
|
||||
VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT),
|
||||
.aspectMask = (VkImageAspectFlags)(is_color ?
|
||||
VK_IMAGE_ASPECT_COLOR_BIT :
|
||||
VK_IMAGE_ASPECT_DEPTH_BIT),
|
||||
.arrayLayers = 1,
|
||||
.mipLevels = 1,
|
||||
};
|
||||
|
||||
Image image = {};
|
||||
m_gpu->memory()->create_image(&image_info, &memory_info, &image);
|
||||
|
||||
ImageView view = {};
|
||||
view = image.create_view(m_gpu, description.format(), {
|
||||
.aspectMask = image_info.aspectMask,
|
||||
.baseMipLevel = 0,
|
||||
.levelCount = 1,
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = 1,
|
||||
});
|
||||
|
||||
return ImageResource(image.img, view.view, image_info.extent);
|
||||
}
|
||||
|
||||
BufferResource Allocator::allocate_buffer_resource(const BufferResourceDescription& description) {
|
||||
BufferCreateInfo buffer_info = {
|
||||
.size = description.size(),
|
||||
.usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
|
||||
.isExclusive = true,
|
||||
};
|
||||
|
||||
Buffer buffer = {};
|
||||
m_gpu->memory()->create_buffer(&buffer_info, nullptr, &buffer);
|
||||
|
||||
return BufferResource(buffer.buf, buffer_info.size);
|
||||
}
|
||||
|
||||
VkAttachmentDescription2 Allocator::create_attachment_description(
|
||||
const ImageResourceDescription& definition,
|
||||
bool is_color,
|
||||
std::map<std::string, uint32_t>& resource_count_down,
|
||||
std::set<std::string>& cleared_resources
|
||||
) {
|
||||
VkAttachmentLoadOp load_op = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
||||
VkImageLayout initial_layout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
VkImageLayout final_layout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
|
||||
VkImageLayout middle_layout = is_color ?
|
||||
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL :
|
||||
VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL;
|
||||
|
||||
VkImageLayout last_layout =
|
||||
(definition.name() == m_output_name ? m_output_chain.layout() :
|
||||
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||
|
||||
final_layout = resource_count_down[definition.name()] > 1 ?
|
||||
middle_layout :
|
||||
last_layout;
|
||||
|
||||
if(cleared_resources.find(definition.name()) != cleared_resources.end()/* context.is_clear(definition.name()) */) {
|
||||
load_op = VK_ATTACHMENT_LOAD_OP_LOAD;
|
||||
initial_layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
||||
} else {
|
||||
cleared_resources.insert(definition.name());
|
||||
load_op = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
||||
initial_layout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
}
|
||||
|
||||
// store op is dont care for the last one and store for every other
|
||||
VkAttachmentStoreOp store_op = resource_count_down[definition.name()] == 1 ?
|
||||
VK_ATTACHMENT_STORE_OP_DONT_CARE : VK_ATTACHMENT_STORE_OP_STORE;
|
||||
|
||||
|
||||
// remember to count down the resource
|
||||
resource_count_down[definition.name()]--;
|
||||
|
||||
return {
|
||||
.sType = VK_STRUCTURE_TYPE_ATTACHMENT_DESCRIPTION_2,
|
||||
.format = definition.format(),
|
||||
.samples = VK_SAMPLE_COUNT_1_BIT,
|
||||
.loadOp = load_op,
|
||||
.storeOp = store_op,
|
||||
.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
|
||||
.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
|
||||
.initialLayout = initial_layout,
|
||||
.finalLayout = final_layout
|
||||
};
|
||||
}
|
||||
|
||||
VkRenderPass Allocator::allocate_renderpass(
|
||||
const TaskInfo& task,
|
||||
std::map<std::string, uint32_t>& resource_count_down,
|
||||
std::set<std::string>& cleared_resources
|
||||
) {
|
||||
size_t num_attachments = task.color_outputs().size() + task.depth_output().has_value();
|
||||
std::vector<VkAttachmentDescription2> descriptions(num_attachments);
|
||||
std::vector<VkAttachmentReference2> references(num_attachments);
|
||||
|
||||
uint32_t i = 0;
|
||||
for(auto& output : task.color_outputs()) {
|
||||
descriptions[i] = create_attachment_description(
|
||||
output, true,
|
||||
resource_count_down,
|
||||
cleared_resources);
|
||||
|
||||
// ignore sub passes, so one reference per attachment
|
||||
references[i] = {
|
||||
.sType = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2,
|
||||
.attachment = i,
|
||||
.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
|
||||
};
|
||||
i++;
|
||||
}
|
||||
|
||||
if (task.depth_output().has_value()) {
|
||||
descriptions[i] = create_attachment_description(
|
||||
task.depth_output().value(),
|
||||
false,
|
||||
resource_count_down,
|
||||
cleared_resources);
|
||||
|
||||
references[i] = {
|
||||
.sType = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2,
|
||||
.attachment = i,
|
||||
.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL
|
||||
};
|
||||
}
|
||||
|
||||
VkMemoryBarrier2KHR entryBarrier = {
|
||||
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2_KHR,
|
||||
.pNext = nullptr,
|
||||
.srcStageMask = VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT,
|
||||
.srcAccessMask = 0,
|
||||
.dstStageMask = VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT,
|
||||
.dstAccessMask = 0
|
||||
};
|
||||
|
||||
const VkSubpassDependency2 subpass_dependencies[] = {
|
||||
{
|
||||
.sType = VK_STRUCTURE_TYPE_SUBPASS_DEPENDENCY_2,
|
||||
.pNext = &entryBarrier,
|
||||
.srcSubpass = VK_SUBPASS_EXTERNAL,
|
||||
.dstSubpass = 0,
|
||||
.dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT,
|
||||
}
|
||||
};
|
||||
|
||||
const VkSubpassDescription2 subpass = {
|
||||
.sType = VK_STRUCTURE_TYPE_SUBPASS_DESCRIPTION_2,
|
||||
.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||
.colorAttachmentCount = (uint32_t)references.size() - (task.depth_output().has_value()),
|
||||
.pColorAttachments = references.data(),
|
||||
.pDepthStencilAttachment = task.depth_output().has_value() ? &references[i] : nullptr,
|
||||
};
|
||||
|
||||
VkRenderPassCreateInfo2 renderpass_info = {
|
||||
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO_2,
|
||||
|
||||
.attachmentCount = (uint32_t)descriptions.size(),
|
||||
.pAttachments = descriptions.data(),
|
||||
|
||||
.subpassCount = 1,
|
||||
.pSubpasses = &subpass,
|
||||
|
||||
.dependencyCount = 1,
|
||||
.pDependencies = subpass_dependencies
|
||||
};
|
||||
|
||||
VkRenderPass renderpass;
|
||||
if(vkCreateRenderPass2KHR(m_gpu->dev(), &renderpass_info, nullptr, &renderpass)) {
|
||||
throw std::runtime_error("Failed to create render pass");
|
||||
}
|
||||
|
||||
return renderpass;
|
||||
}
|
||||
|
||||
VkFramebuffer Allocator::create_framebuffer(
|
||||
const TaskInfo& task,
|
||||
VkRenderPass render_pass,
|
||||
uint32_t output_image_idx
|
||||
) {
|
||||
std::vector<ImageView> attachments(task.color_outputs().size() + task.depth_output().has_value());
|
||||
uint32_t i = 0;
|
||||
for(auto& output : task.color_outputs()) {
|
||||
attachments[i++] = get_attachment(output.name(), output_image_idx).view;
|
||||
}
|
||||
|
||||
if (task.depth_output().has_value()) {
|
||||
attachments[i++] = get_attachment(task.depth_output()->name(), output_image_idx).view;
|
||||
}
|
||||
|
||||
if(i != attachments.size()) {
|
||||
throw std::runtime_error("Framebuffer attachments size mismatch");
|
||||
}
|
||||
|
||||
auto fb = FramebufferBuilder(render_pass, task.m_extent, attachments)
|
||||
.build(m_gpu);
|
||||
|
||||
return fb.framebuffer;
|
||||
}
|
||||
|
||||
std::vector<VkCommandBuffer> allocate_command_buffers(const Gpu* gpu, uint32_t count) {
|
||||
VkCommandBufferAllocateInfo cmdbuf_info = {
|
||||
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
|
||||
.commandPool = gpu->graphics_command_pool(),
|
||||
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
|
||||
.commandBufferCount = count,
|
||||
};
|
||||
|
||||
std::vector<VkCommandBuffer> cmd_bufs(count);
|
||||
if(vkAllocateCommandBuffers(gpu->dev(), &cmdbuf_info, cmd_bufs.data())) {
|
||||
throw std::runtime_error("Failed to create command buffer");
|
||||
}
|
||||
|
||||
return cmd_bufs;
|
||||
}
|
||||
|
||||
|
||||
void Allocator::collect_resources(const std::vector<TaskInfo>& tasks) {
|
||||
m_resources.resize(num_buffers());
|
||||
for(auto& task : tasks) {
|
||||
for(auto& output : task.color_outputs()) {
|
||||
if(m_resources[0].find(output.name()) == m_resources[0].end()) {
|
||||
for(uint32_t i = 0; i < num_buffers(); i++) {
|
||||
auto resource = allocate_image_resources(output, true);
|
||||
m_resources[i].insert({output.name(), resource});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(task.depth_output().has_value()) {
|
||||
auto& depth_output = task.depth_output().value();
|
||||
if(m_resources[0].find(depth_output.name()) == m_resources[0].end()) {
|
||||
for(uint32_t i = 0; i < num_buffers(); i++) {
|
||||
auto resource = allocate_image_resources(depth_output, false);
|
||||
m_resources[i].insert({depth_output.name(), resource});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::map<std::string, uint32_t> count_resource_writes(const std::vector<TaskInfo>& tasks) {
|
||||
std::map<std::string, uint32_t> resource_count_down;
|
||||
for(auto& task : tasks) {
|
||||
for(auto& output : task.color_outputs()) {
|
||||
if(resource_count_down.find(output.name()) == resource_count_down.end()) {
|
||||
resource_count_down[output.name()] = 1;
|
||||
} else {
|
||||
resource_count_down[output.name()]++;
|
||||
}
|
||||
}
|
||||
|
||||
if(task.depth_output().has_value()) {
|
||||
auto& depth_output = task.depth_output().value();
|
||||
if(resource_count_down.find(depth_output.name()) == resource_count_down.end()) {
|
||||
resource_count_down[depth_output.name()] = 1;
|
||||
} else {
|
||||
resource_count_down[depth_output.name()]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return resource_count_down;
|
||||
}
|
||||
|
||||
void Allocator::prepare_renderpasses(const std::vector<TaskInfo>& tasks) {
|
||||
std::map<std::string, uint32_t> resource_count_down = count_resource_writes(tasks);
|
||||
std::set<std::string> cleared_resources;
|
||||
|
||||
// prepare render passes
|
||||
for(auto& task : tasks) {
|
||||
auto rp = allocate_renderpass(task, resource_count_down, cleared_resources);
|
||||
m_renderpasses.insert({task.name(), rp});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
RenderGraphBuffer Allocator::allocate_buffer(const GraphAllocationInfo& info, uint32_t buffer_idx) {
|
||||
int cmdbuf_idx = 0;
|
||||
/* std::vector<RenderGraphCommandBuffer> command_buffers;
|
||||
|
||||
for(auto& cmdbuf : info.command_buffers) {
|
||||
std::vector<VkCommandBuffer> cmdbufs(info.output_chain.count());
|
||||
std::vector<Task> tasks;
|
||||
|
||||
for(uint32_t rp_idx = cmdbuf.first_task_idx;
|
||||
rp_idx < cmdbuf.first_task_idx + cmdbuf.num_tasks;
|
||||
rp_idx++
|
||||
) {
|
||||
const TaskInfo* rp = &m_tasks[rp_idx];
|
||||
if(rp->type() == TaskType::GRAPHICS_TASK) {
|
||||
std::vector<VkFramebuffer> framebuffers(info.output_chain.count());
|
||||
|
||||
for(uint32_t image_idx = 0; image_idx < info.output_chain.count(); image_idx++) {
|
||||
auto fb = create_framebuffer(*rp, m_renderpasses[rp->name()], image_idx);
|
||||
|
||||
framebuffers[image_idx] = fb;
|
||||
cmdbufs[image_idx] = allocate_command_buffers(info.gpu, 1)[0];
|
||||
}
|
||||
|
||||
tasks.push_back({
|
||||
.pDefinition = *rp,
|
||||
.render_pass = m_renderpasses[rp->name()],
|
||||
.framebuffer = framebuffers,
|
||||
.extent = info.output_chain.extent()
|
||||
});
|
||||
} else if(rp->type() == TaskType::COMPUTE_TASK) {
|
||||
for(uint32_t image_idx = 0; image_idx < info.output_chain.count(); image_idx++) {
|
||||
cmdbufs[image_idx] = allocate_command_buffers(info.gpu, 1)[0];
|
||||
}
|
||||
|
||||
tasks.push_back({
|
||||
.pDefinition = *rp,
|
||||
.render_pass = VK_NULL_HANDLE,
|
||||
.framebuffer = {},
|
||||
.extent = info.output_chain.extent()
|
||||
});
|
||||
} else {
|
||||
throw std::runtime_error("Unsupported task type");
|
||||
}
|
||||
}
|
||||
|
||||
command_buffers.push_back({
|
||||
.command_buffers = cmdbufs,
|
||||
.render_passes = tasks,
|
||||
.signal = create_semaphore(info.gpu),
|
||||
.dependencies = cmdbuf.wait_signals_idx
|
||||
}); */
|
||||
}
|
||||
|
||||
// RenderGraphBuffer buffer(command_buffers, m_resources[buffer_idx]);
|
||||
|
||||
// return buffer;
|
||||
// }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
#include "RenderGraphBuffer.hpp"
|
||||
|
||||
#include <iterator>
|
||||
#include <unordered_map>
|
||||
#include <iostream>
|
||||
#include <algorithm>
|
||||
|
||||
std::vector<VkCommandBuffer> allocate_cmdbufs(const Gpu* gpu, uint32_t count) {
|
||||
VkCommandBufferAllocateInfo cmdbuf_info = {
|
||||
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
|
||||
.commandPool = gpu->graphics_command_pool(),
|
||||
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
|
||||
.commandBufferCount = count,
|
||||
};
|
||||
|
||||
std::vector<VkCommandBuffer> cmdbufs(cmdbuf_info.commandBufferCount);
|
||||
if(vkAllocateCommandBuffers(gpu->dev(), &cmdbuf_info, cmdbufs.data())) {
|
||||
throw std::runtime_error("Failed to create command buffer");
|
||||
}
|
||||
|
||||
return cmdbufs;
|
||||
}
|
||||
|
||||
|
||||
namespace lft::rg {
|
||||
|
||||
std::vector<BatchOutput> create_batch_outputs(const Gpu* gpu, uint32_t count) {
|
||||
auto cmdbufs = allocate_cmdbufs(gpu, count);
|
||||
std::vector<BatchOutput> outputs;
|
||||
|
||||
std::transform(cmdbufs.begin(), cmdbufs.end(),
|
||||
std::back_inserter(outputs), [](VkCommandBuffer cmdbuf) {
|
||||
return BatchOutput(cmdbuf);
|
||||
});
|
||||
|
||||
return outputs;
|
||||
}
|
||||
|
||||
BatchOutput::BatchOutput(VkCommandBuffer cmdbuf) :
|
||||
cmdbuf(cmdbuf),
|
||||
is_recording_valid(false) {
|
||||
|
||||
}
|
||||
|
||||
Batch::Batch(std::vector<BatchOutput> outputs, VkSemaphore signal) :
|
||||
outputs(outputs),
|
||||
signal(signal) {
|
||||
|
||||
}
|
||||
|
||||
Batch& Batch::invalidate_recordings() {
|
||||
for(auto& output : outputs) {
|
||||
output.is_recording_valid = false;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
Batch& Batch::insert_task(uint32_t idx, Task& task) {
|
||||
tasks.insert(tasks.begin() + idx, task);
|
||||
invalidate_recordings();
|
||||
return *this;
|
||||
}
|
||||
|
||||
Batch& Batch::update_task(uint32_t idx, Task& task) {
|
||||
tasks[idx] = task;
|
||||
invalidate_recordings();
|
||||
return *this;
|
||||
}
|
||||
|
||||
Batch& Batch::remove_task(uint32_t idx) {
|
||||
tasks.erase(tasks.begin() + idx);
|
||||
invalidate_recordings();
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool Batch::equals(const Batch& rhs) const {
|
||||
if(tasks.size() != rhs.tasks.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for(uint32_t i = 0; i < tasks.size(); i++) {
|
||||
if(!tasks[i].equals(rhs.tasks[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if(barriers != rhs.barriers) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(outputs.size() != rhs.outputs.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for(uint32_t i = 0; i < outputs.size(); i++) {
|
||||
if(outputs[i].is_recording_valid != rhs.outputs[i].is_recording_valid) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
RenderGraphBuffer::RenderGraphBuffer(
|
||||
const Gpu* gpu,
|
||||
uint32_t index,
|
||||
uint32_t num_outputs
|
||||
) : m_gpu(gpu), m_index(index), m_final_semaphores(num_outputs) {
|
||||
for(uint32_t i = 0; i < num_outputs; i++) {
|
||||
m_final_semaphores[i] = m_gpu->create_semaphore();
|
||||
}
|
||||
}
|
||||
|
||||
Batch& RenderGraphBuffer::insert_batch(uint32_t idx, uint32_t num_outputs) {
|
||||
m_batches.insert(m_batches.begin() + idx,
|
||||
Batch(create_batch_outputs(m_gpu, num_outputs), m_gpu->create_semaphore()));
|
||||
return m_batches[idx];
|
||||
}
|
||||
|
||||
void RenderGraphBuffer::remove_batch(uint32_t idx) {
|
||||
m_batches.erase(m_batches.begin() + idx);
|
||||
}
|
||||
|
||||
bool is_buffer_resources_equal(
|
||||
std::unordered_map<std::string, BufferResource> lhs,
|
||||
std::unordered_map<std::string, BufferResource> rhs
|
||||
) {
|
||||
for(auto& value : rhs) {
|
||||
if(!lhs.contains(value.first)) {
|
||||
std::cout << "Missing buffer resource: " << value.first << std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool is_image_resources_equal(
|
||||
std::unordered_map<std::string, ImageResource> lhs,
|
||||
std::unordered_map<std::string, ImageResource> rhs
|
||||
) {
|
||||
for(auto& value : rhs) {
|
||||
if(!lhs.contains(value.first)) {
|
||||
std::cout << "Missing image resource: " << value.first << std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RenderGraphBuffer::equals(const RenderGraphBuffer& other) const {
|
||||
if(m_batches.size() != other.m_batches.size()) {
|
||||
std::cout << "Some batches are missing" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
for(uint32_t i = 0; i < m_batches.size(); i++) {
|
||||
if(!m_batches[i].equals(other.m_batches[i])) {
|
||||
std::cout << "Task queues are not the same" << std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// compare resources
|
||||
/* if(!is_buffer_resources_equal(m_buffer_resources, other.m_buffer_resources)) {
|
||||
return false;
|
||||
} */
|
||||
|
||||
if(!is_image_resources_equal(m_image_resources, other.m_image_resources)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,804 @@
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <format>
|
||||
#include <ostream>
|
||||
#include <iostream>
|
||||
#include <queue>
|
||||
#include <stdexcept>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <cstring>
|
||||
|
||||
#include "RenderGraphBuilder.hpp"
|
||||
#include "AdjacencyMatrix.hpp"
|
||||
#include "FramebufferBuilder.hpp"
|
||||
#include "RenderGraph.hpp"
|
||||
#include "RenderGraphBuffer.hpp"
|
||||
#include "RenderPass.hpp"
|
||||
#include "RenderPassLayout.hpp"
|
||||
#include "Resource.hpp"
|
||||
|
||||
namespace lft::rg {
|
||||
|
||||
std::vector<std::string> get_task_names(
|
||||
const std::vector<TaskInfo>& tasks
|
||||
) {
|
||||
std::vector<std::string> names;
|
||||
std::transform(tasks.begin(), tasks.end(),
|
||||
names.begin(),
|
||||
[](const TaskInfo& i) {
|
||||
return i.name();
|
||||
});
|
||||
|
||||
return names;
|
||||
}
|
||||
|
||||
|
||||
std::unordered_map<std::string, uint32_t> count_resource_image_writes(const std::vector<TaskInfo>& tasks) {
|
||||
std::unordered_map<std::string, uint32_t> resource_count_down;
|
||||
for(auto& task : tasks) {
|
||||
for(auto& color_output : task.color_outputs()) {
|
||||
if(resource_count_down.find(color_output.name()) == resource_count_down.end()) {
|
||||
resource_count_down[color_output.name()] = 1;
|
||||
} else {
|
||||
resource_count_down[color_output.name()]++;
|
||||
}
|
||||
}
|
||||
|
||||
if(task.depth_output().has_value()) {
|
||||
auto& depth_output = task.depth_output().value();
|
||||
if(resource_count_down.find(depth_output.name()) == resource_count_down.end()) {
|
||||
resource_count_down[depth_output.name()] = 1;
|
||||
} else {
|
||||
resource_count_down[depth_output.name()]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return resource_count_down;
|
||||
}
|
||||
|
||||
inline bool is_first_resource_write(
|
||||
std::unordered_set<std::string>& cleared_resources,
|
||||
const std::string& resource
|
||||
) {
|
||||
return !cleared_resources.contains(resource);
|
||||
}
|
||||
|
||||
inline bool is_last_resource_write(
|
||||
std::unordered_map<std::string, uint32_t>& resource_count_down,
|
||||
const std::string& resource
|
||||
) {
|
||||
return resource_count_down[resource] == 1;
|
||||
}
|
||||
|
||||
VkAttachmentDescription2 BuilderAllocator::create_attachment_description(
|
||||
const ImageResourceDescription& definition,
|
||||
bool is_first_write,
|
||||
bool is_last_write
|
||||
) {
|
||||
|
||||
VkAttachmentLoadOp load_op = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
||||
VkImageLayout initial_layout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
VkImageLayout final_layout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
|
||||
VkImageLayout middle_layout = definition.is_color() ?
|
||||
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL :
|
||||
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
||||
|
||||
VkImageLayout last_layout =
|
||||
(definition.name() == m_output_name ? m_output_chain.layout() :
|
||||
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||
|
||||
// TODO: If the write is not last yet outputs to the output chain, the layout gets overwriten by middle_layout anyway
|
||||
final_layout = is_last_write ?
|
||||
(definition.name() == m_output_name ? m_output_chain.layout() : last_layout) :
|
||||
middle_layout;
|
||||
|
||||
if(!is_first_write) {
|
||||
load_op = VK_ATTACHMENT_LOAD_OP_LOAD;
|
||||
initial_layout = definition.is_color() ?
|
||||
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL :
|
||||
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
||||
} else {
|
||||
load_op = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
||||
initial_layout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
}
|
||||
|
||||
// store op is dont care for the last one and store for every other
|
||||
VkAttachmentStoreOp store_op = is_last_write && (!m_store_all_images) ?
|
||||
VK_ATTACHMENT_STORE_OP_DONT_CARE : VK_ATTACHMENT_STORE_OP_STORE;
|
||||
|
||||
return {
|
||||
.sType = VK_STRUCTURE_TYPE_ATTACHMENT_DESCRIPTION_2,
|
||||
.format = definition.format(),
|
||||
.samples = VK_SAMPLE_COUNT_1_BIT,
|
||||
.loadOp = load_op,
|
||||
.storeOp = store_op,
|
||||
.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
|
||||
.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
|
||||
.initialLayout = initial_layout,
|
||||
.finalLayout = final_layout
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
TaskRenderPass BuilderAllocator::allocate_renderpass(
|
||||
const TaskInfo& task,
|
||||
std::unordered_map<std::string, uint32_t>& resource_count_down,
|
||||
std::unordered_set<std::string>& cleared_resources
|
||||
) {
|
||||
size_t num_attachments = task.color_outputs().size() + task.depth_output().has_value();
|
||||
std::vector<VkAttachmentDescription2> descriptions(num_attachments);
|
||||
std::vector<VkAttachmentReference2> references(num_attachments);
|
||||
|
||||
TaskRenderPassState state(task.color_outputs().size(), task.depth_output().has_value());
|
||||
|
||||
uint32_t i = 0;
|
||||
for(auto& output : task.color_outputs()) {
|
||||
bool is_first_write = is_first_resource_write(cleared_resources, output.name());
|
||||
bool is_last_write = is_last_resource_write(resource_count_down, output.name());
|
||||
|
||||
if(is_first_write) {
|
||||
state.set_resource_is_first(i);
|
||||
} if(is_last_write) {
|
||||
state.set_resource_is_last(i);
|
||||
}
|
||||
|
||||
descriptions[i] = create_attachment_description(
|
||||
output,
|
||||
is_first_write,
|
||||
is_last_write
|
||||
);
|
||||
|
||||
// ignore sub passes, so one reference per attachment
|
||||
references[i] = {
|
||||
.sType = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2,
|
||||
.attachment = i,
|
||||
.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
|
||||
};
|
||||
i++;
|
||||
}
|
||||
|
||||
if (task.depth_output().has_value()) {
|
||||
bool is_first_write = is_first_resource_write(cleared_resources, task.depth_output()->name());
|
||||
bool is_last_write = is_last_resource_write(resource_count_down, task.depth_output()->name());
|
||||
|
||||
if(is_first_write) {
|
||||
state.set_resource_is_first(i);
|
||||
} if(is_last_write) {
|
||||
state.set_resource_is_last(i);
|
||||
}
|
||||
|
||||
descriptions[i] = create_attachment_description(
|
||||
task.depth_output().value(),
|
||||
is_first_write, is_last_write);
|
||||
|
||||
references[i] = {
|
||||
.sType = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2,
|
||||
.attachment = i,
|
||||
.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL
|
||||
};
|
||||
}
|
||||
|
||||
VkMemoryBarrier2KHR entryBarrier = {
|
||||
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2_KHR,
|
||||
.pNext = nullptr,
|
||||
.srcStageMask = VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT,
|
||||
.srcAccessMask = 0,
|
||||
.dstStageMask = VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT,
|
||||
.dstAccessMask = 0
|
||||
};
|
||||
|
||||
const VkSubpassDependency2 subpass_dependencies[] = {
|
||||
{
|
||||
.sType = VK_STRUCTURE_TYPE_SUBPASS_DEPENDENCY_2,
|
||||
.pNext = &entryBarrier,
|
||||
.srcSubpass = VK_SUBPASS_EXTERNAL,
|
||||
.dstSubpass = 0,
|
||||
.dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT,
|
||||
}
|
||||
};
|
||||
|
||||
const VkSubpassDescription2 subpass = {
|
||||
.sType = VK_STRUCTURE_TYPE_SUBPASS_DESCRIPTION_2,
|
||||
.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||
.colorAttachmentCount = (uint32_t)references.size() - (task.depth_output().has_value()),
|
||||
.pColorAttachments = references.data(),
|
||||
.pDepthStencilAttachment = task.depth_output().has_value() ? &references[i] : nullptr,
|
||||
};
|
||||
|
||||
VkRenderPassCreateInfo2 renderpass_info = {
|
||||
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO_2,
|
||||
|
||||
.attachmentCount = (uint32_t)descriptions.size(),
|
||||
.pAttachments = descriptions.data(),
|
||||
|
||||
.subpassCount = 1,
|
||||
.pSubpasses = &subpass,
|
||||
|
||||
.dependencyCount = 1,
|
||||
.pDependencies = subpass_dependencies
|
||||
};
|
||||
|
||||
VkRenderPass renderpass;
|
||||
if(vkCreateRenderPass2KHR(m_gpu->dev(), &renderpass_info, nullptr, &renderpass)) {
|
||||
throw std::runtime_error("Failed to create render pass");
|
||||
}
|
||||
|
||||
VkDebugUtilsObjectNameInfoEXT render_pass_dbg_info = {
|
||||
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT,
|
||||
.objectType = VK_OBJECT_TYPE_RENDER_PASS,
|
||||
.objectHandle = (uint64_t)renderpass,
|
||||
.pObjectName = strdup(std::format("[RP] {}", task.name()).c_str()),
|
||||
};
|
||||
vkSetDebugUtilsObjectNameEXT(m_gpu->dev(), &render_pass_dbg_info);
|
||||
|
||||
return TaskRenderPass(renderpass, state);
|
||||
}
|
||||
|
||||
|
||||
ImageResource BuilderAllocator::allocate_image_resource(
|
||||
const ImageResourceDescription& desc
|
||||
) const {
|
||||
MemoryAllocationInfo memory_info = {
|
||||
.usage = MEMORY_USAGE_AUTO_PREFER_DEVICE
|
||||
};
|
||||
|
||||
auto extent = get_extent(desc.extent());
|
||||
std::println("Creating resource {} {}", extent.width, extent.height);
|
||||
|
||||
ImageCreateInfo image_info = {
|
||||
.extent = extent,
|
||||
.format = desc.format(),
|
||||
.usage = VK_IMAGE_USAGE_SAMPLED_BIT |
|
||||
(VkImageUsageFlags)(desc.is_color() ?
|
||||
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT :
|
||||
VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT),
|
||||
.aspectMask = (VkImageAspectFlags)(desc.is_color() ?
|
||||
VK_IMAGE_ASPECT_COLOR_BIT :
|
||||
VK_IMAGE_ASPECT_DEPTH_BIT),
|
||||
.arrayLayers = 1,
|
||||
.mipLevels = 1,
|
||||
};
|
||||
|
||||
Image image = {};
|
||||
m_gpu->memory()->create_image(&image_info, &memory_info, &image);
|
||||
|
||||
ImageView view = {};
|
||||
view = image.create_view(m_gpu, desc.format(), {
|
||||
.aspectMask = image_info.aspectMask,
|
||||
.baseMipLevel = 0,
|
||||
.levelCount = 1,
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = 1,
|
||||
});
|
||||
|
||||
return ImageResource(image.img, view.view, image_info.extent);
|
||||
}
|
||||
|
||||
BufferResource BuilderAllocator::allocate_buffer_resource(
|
||||
const BufferResourceDescription& desc
|
||||
) const {
|
||||
|
||||
}
|
||||
|
||||
ImageResourceDescription BuilderAllocator::correct_resource_description(ImageResourceDescription desc) {
|
||||
// correct extent
|
||||
VkExtent2D extent = desc.extent();
|
||||
if(extent.width == 0) {
|
||||
extent.width = m_output_chain.extent().width;
|
||||
}
|
||||
|
||||
if(extent.height == 0) {
|
||||
extent.height = m_output_chain.extent().height;
|
||||
}
|
||||
|
||||
desc.set_extent(extent);
|
||||
return desc;
|
||||
}
|
||||
|
||||
ImageView BuilderAllocator::get_attachment(
|
||||
const ImageResourceDescription& desc,
|
||||
RenderGraphBuffer* pBuffer,
|
||||
uint32_t output_idx
|
||||
) {
|
||||
if(desc.name() == m_output_name) {
|
||||
return m_output_chain.views()[output_idx];
|
||||
}
|
||||
|
||||
auto extent = get_extent(desc.extent());
|
||||
|
||||
auto attachment = pBuffer->get_image_resource(desc.name());
|
||||
if(!attachment.has_value() || attachment.value()->extent.width != extent.width ||
|
||||
attachment.value()->extent.height != extent.height) {
|
||||
auto resource = allocate_image_resource(desc);
|
||||
|
||||
VkDebugUtilsObjectNameInfoEXT img_dbg_info = {
|
||||
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT,
|
||||
.objectType = VK_OBJECT_TYPE_IMAGE,
|
||||
.objectHandle = (uint64_t)resource.image,
|
||||
.pObjectName = strdup(std::format("[IMG:buf({}):out({})]{}", pBuffer->index(), output_idx, desc.name()).c_str()),
|
||||
};
|
||||
vkSetDebugUtilsObjectNameEXT(m_gpu->dev(), &img_dbg_info);
|
||||
|
||||
VkDebugUtilsObjectNameInfoEXT img_view_dbg_info = {
|
||||
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT,
|
||||
.objectType = VK_OBJECT_TYPE_IMAGE_VIEW,
|
||||
.objectHandle = (uint64_t)resource.image_view,
|
||||
.pObjectName = strdup(std::format("[IMG_VIEW:buf({}):out({})]{}", pBuffer->index(), output_idx, desc.name()).c_str()),
|
||||
};
|
||||
vkSetDebugUtilsObjectNameEXT(m_gpu->dev(), &img_view_dbg_info);
|
||||
|
||||
pBuffer->put_image_resource(desc.name(), resource);
|
||||
return resource.image_view;
|
||||
}
|
||||
|
||||
return attachment.value()->image_view;
|
||||
}
|
||||
|
||||
VkFramebuffer BuilderAllocator::create_framebuffer(
|
||||
const TaskInfo& task_info,
|
||||
VkRenderPass renderpass,
|
||||
RenderGraphBuffer *pBuffer,
|
||||
uint32_t output_idx
|
||||
) {
|
||||
uint32_t num_attachments = task_info.color_outputs().size() +
|
||||
task_info.depth_output().has_value();
|
||||
std::vector<ImageView> attachments(num_attachments);
|
||||
uint32_t i = 0;
|
||||
for(auto& output : task_info.color_outputs()) {
|
||||
attachments[i++] = get_attachment(output, pBuffer, output_idx).view;
|
||||
}
|
||||
|
||||
if (task_info.depth_output().has_value()) {
|
||||
attachments[i++] = get_attachment(task_info.depth_output().value(), pBuffer, output_idx);
|
||||
}
|
||||
|
||||
if(i != attachments.size()) {
|
||||
throw std::runtime_error("Framebuffer attachments size mismatch");
|
||||
}
|
||||
|
||||
auto extent = get_extent_for_task(task_info);
|
||||
std::println("Creating framebuffer of size: {} {}", extent.width, extent.height);
|
||||
auto fb = FramebufferBuilder(renderpass, get_extent_for_task(task_info), attachments)
|
||||
.build(m_gpu);
|
||||
VkDebugUtilsObjectNameInfoEXT render_pass_dbg_info = {
|
||||
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT,
|
||||
.objectType = VK_OBJECT_TYPE_FRAMEBUFFER,
|
||||
.objectHandle = (uint64_t)fb.framebuffer,
|
||||
.pObjectName = strdup(std::format("[FB:buf({}):out({})]{}", pBuffer->index(), output_idx, task_info.name()).c_str()),
|
||||
};
|
||||
vkSetDebugUtilsObjectNameEXT(m_gpu->dev(), &render_pass_dbg_info);
|
||||
|
||||
return fb.framebuffer;
|
||||
}
|
||||
|
||||
Task BuilderAllocator::create_graphics_task(
|
||||
const TaskInfo& task_info,
|
||||
RenderGraphBuffer* pBuffer,
|
||||
TaskRenderPass render_pass
|
||||
) {
|
||||
Task task = {.pDefinition = task_info, .render_pass = render_pass};
|
||||
|
||||
// create framebuffer for each image in output chain
|
||||
std::vector<VkFramebuffer> framebuffers(m_output_chain.count());
|
||||
for(
|
||||
uint32_t image_idx = 0;
|
||||
image_idx < m_output_chain.count();
|
||||
image_idx++
|
||||
) {
|
||||
auto fb = create_framebuffer(task_info, task.render_pass.render_pass, pBuffer, image_idx);
|
||||
framebuffers[image_idx] = fb;
|
||||
}
|
||||
|
||||
task.framebuffer = framebuffers;
|
||||
task.extent = get_extent_for_task(task_info);
|
||||
|
||||
return task;
|
||||
}
|
||||
|
||||
Task BuilderAllocator::create_compute_task(
|
||||
const TaskInfo& task_info,
|
||||
RenderGraphBuffer* pBuffer
|
||||
) {
|
||||
Task task = {.pDefinition = task_info};
|
||||
|
||||
return task;
|
||||
}
|
||||
|
||||
Task BuilderAllocator::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
|
||||
) {
|
||||
if(task_info.type() == GRAPHICS_TASK) {
|
||||
auto render_pass = allocate_renderpass(task_info, resource_count_down, cleared_resources);
|
||||
return create_graphics_task(task_info, pBuffer, render_pass);
|
||||
} else if(task_info.type() == COMPUTE_TASK) {
|
||||
return create_compute_task(task_info, pBuffer);
|
||||
} else {
|
||||
throw std::runtime_error(std::format("Unknown task type for {}", task_info.name()));
|
||||
}
|
||||
}
|
||||
|
||||
bool is_render_pass_updated(const Task& old_task,
|
||||
std::unordered_set<std::string> cleared_resources,
|
||||
std::unordered_map<std::string, uint32_t> resource_count_down
|
||||
) {
|
||||
uint32_t i = 0;
|
||||
for(; i < old_task.pDefinition.m_color_outputs.size(); i++) {
|
||||
if(old_task.render_pass.state.is_resource_first(i) != is_first_resource_write(cleared_resources, old_task.pDefinition.m_color_outputs[i].name())) {
|
||||
return true;
|
||||
} else if(old_task.render_pass.state.is_resource_last(i) != is_last_resource_write(resource_count_down, old_task.pDefinition.m_color_outputs[i].name())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if(old_task.pDefinition.depth_output().has_value()) {
|
||||
if(old_task.render_pass.state.is_resource_first(i) != is_first_resource_write(cleared_resources, old_task.pDefinition.depth_output()->name())) {
|
||||
return true;
|
||||
} else if(old_task.render_pass.state.is_resource_last(i) != is_last_resource_write(resource_count_down, old_task.pDefinition.depth_output()->name())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void BuilderAllocator::update_task_buffer(const Task& task, const RenderGraphBuffer* pBuffer) {
|
||||
TaskBuildInfo task_build_info(
|
||||
m_gpu,
|
||||
pBuffer->index(),
|
||||
num_buffers(),
|
||||
get_viewport(),
|
||||
task.render_pass.render_pass,
|
||||
pBuffer->m_image_resources
|
||||
);
|
||||
task.pDefinition.build_func()(task_build_info, task.pDefinition.m_pContext);
|
||||
}
|
||||
|
||||
void BuilderAllocator::update_task_queue(
|
||||
RenderGraphBuffer* pBuffer,
|
||||
const std::vector<TaskInfo>& task_infos
|
||||
) {
|
||||
std::unordered_map<std::string, uint32_t> resource_count_down = count_resource_image_writes(task_infos);
|
||||
std::unordered_set<std::string> cleared_resources;
|
||||
|
||||
uint32_t cmdbuf_idx = 0;
|
||||
|
||||
int32_t remaining_tasks_in_cmdbuf = pBuffer->num_batches() == 0 ? 0 : pBuffer->batch(0).tasks.size();
|
||||
|
||||
// lookahead method
|
||||
uint32_t next_task_idx = 0;
|
||||
uint32_t next_batch_idx = 0;
|
||||
|
||||
uint32_t queue_idx = 0;
|
||||
|
||||
while(next_batch_idx < pBuffer->num_batches()) {
|
||||
Batch* next_batch = &pBuffer->batch(next_batch_idx);
|
||||
Task* next_task = &next_batch->tasks[next_task_idx];
|
||||
|
||||
if(queue_idx >= task_infos.size()) {
|
||||
next_batch->remove_task(next_task_idx);
|
||||
next_task_idx--;
|
||||
|
||||
if(next_batch->tasks.size() == 0) {
|
||||
pBuffer->remove_batch(next_batch_idx);
|
||||
next_task_idx = 0;
|
||||
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
// if next_task and next_queue_item does not match, we found error
|
||||
if(task_infos[queue_idx].name() != next_task->pDefinition.name()) {
|
||||
// if task that should be there is updated, it means it was inserted
|
||||
// if it would be updated, there would not be a name mismatch
|
||||
if(is_task_updated(task_infos[queue_idx].name())) {
|
||||
auto task = create_task(task_infos[queue_idx], pBuffer, cleared_resources, resource_count_down);
|
||||
// insert batch instead of new
|
||||
pBuffer->insert_batch(next_batch_idx, m_output_chain.count())
|
||||
// insert the new task
|
||||
.insert_task(0, task);
|
||||
// notify about update
|
||||
update_task_buffer(task, pBuffer);
|
||||
|
||||
// now the next_task and task_infos[queue_idx] should match, let us move on to the next
|
||||
}
|
||||
// if task that should be there is not updated (it was already in the queue)
|
||||
// but task that is actually there is updated, we should remove the current task,
|
||||
// because the wanted task is next
|
||||
else if(is_task_updated(next_task->pDefinition.name())) {
|
||||
next_batch->remove_task(next_task_idx);
|
||||
if(next_batch->tasks.size() == 0) {
|
||||
pBuffer->remove_batch(next_batch_idx);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
// if tasks match, but task is marked as updated, we should rebuild the task
|
||||
else if(is_task_updated(task_infos[queue_idx].name()) ||
|
||||
is_render_pass_updated(*next_task, cleared_resources, resource_count_down)
|
||||
) {
|
||||
std::println("Updating");
|
||||
auto task = create_task(task_infos[queue_idx], pBuffer, cleared_resources, resource_count_down);
|
||||
next_batch->update_task(next_task_idx, task);
|
||||
}
|
||||
}
|
||||
|
||||
for(auto& output : task_infos[queue_idx].color_outputs()) {
|
||||
resource_count_down[output.name()]--;
|
||||
cleared_resources.insert(output.name());
|
||||
}
|
||||
|
||||
next_batch = &pBuffer->batch(next_batch_idx);
|
||||
|
||||
next_task_idx++;
|
||||
if(next_task_idx == next_batch->tasks.size()) {
|
||||
next_task_idx = 0;
|
||||
next_batch_idx++;
|
||||
}
|
||||
queue_idx++;
|
||||
}
|
||||
|
||||
for(; queue_idx < task_infos.size(); queue_idx++) {
|
||||
auto task = create_task(task_infos[queue_idx], pBuffer, cleared_resources, resource_count_down);
|
||||
pBuffer->insert_batch(pBuffer->num_batches(), m_output_chain.count())
|
||||
.insert_task(0, task);
|
||||
update_task_buffer(task, pBuffer);
|
||||
|
||||
for(auto& output : task_infos[queue_idx].color_outputs()) {
|
||||
resource_count_down[output.name()]--;
|
||||
cleared_resources.insert(output.name());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RenderGraph BuilderAllocator::allocate(
|
||||
std::vector<TaskInfo>& task_infos,
|
||||
AdjacencyMatrix *dependencies
|
||||
) {
|
||||
std::println("Task count: {}", task_infos.size());
|
||||
|
||||
// for(TaskInfo& task_info : task_infos) {
|
||||
// if(task_info.m_extent.width == 0.0f) {
|
||||
// task_info.m_extent.width = m_output_chain.extent().width;
|
||||
// }
|
||||
//
|
||||
// if(task_info.m_extent.height == 0.0f) {
|
||||
// task_info.m_extent.height = m_output_chain.extent().height;
|
||||
// }
|
||||
// }
|
||||
|
||||
std::vector<RenderGraphBuffer*> buffers(num_buffers());
|
||||
for(uint32_t buffer_idx = 0; buffer_idx < num_buffers(); buffer_idx++) {
|
||||
update_task_queue(&m_buffers[buffer_idx], task_infos);
|
||||
buffers[buffer_idx] = &m_buffers[buffer_idx];
|
||||
}
|
||||
|
||||
m_updated_tasks.clear();
|
||||
|
||||
return RenderGraph(m_gpu, m_output_name, buffers, dependencies);
|
||||
}
|
||||
|
||||
bool BuilderAllocator::equals(const BuilderAllocator& other) const {
|
||||
if(m_gpu != other.m_gpu) {
|
||||
std::cout << "GPU mismatch" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
if(m_output_name != other.m_output_name) {
|
||||
std::cout << "Output name mismatch" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
if(m_buffers.size() != other.m_buffers.size()) {
|
||||
std::cout << std::format("Buffer count mismatch: {} != {}", m_buffers.size(), other.m_buffers.size()) << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
for(uint32_t i = 0; i < m_buffers.size(); i++) {
|
||||
if(!m_buffers[i].equals(other.m_buffers[i])) {
|
||||
std::cout << "Buffer mismatch at index " << i << std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if(!std::equal(m_updated_tasks.begin(), m_updated_tasks.end(),
|
||||
other.m_updated_tasks.begin(), other.m_updated_tasks.end())) {
|
||||
std::cout << "Updated tasks mismatch" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
#pragma region TOPOLOGY SORT
|
||||
|
||||
|
||||
bool is_depending_on(const TaskInfo& task, const TaskInfo& depends_on) {
|
||||
return std::find_if(task.dependencies().begin(), task.dependencies().end(),
|
||||
[&](const auto& dependency) {
|
||||
if(dependency == depends_on.m_name) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if(depends_on.m_depth_output.has_value() &&
|
||||
dependency == depends_on.depth_output()->name()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (std::find_if(depends_on.color_outputs().begin(),
|
||||
depends_on.color_outputs().end(),
|
||||
[&](const auto& output) {
|
||||
return output.name() == dependency;
|
||||
})
|
||||
!= depends_on.color_outputs().end()) ||
|
||||
std::find_if(depends_on.buffer_outputs().begin(),
|
||||
depends_on.buffer_outputs().end(),
|
||||
[&](const auto& output) {
|
||||
return output.name() == dependency;
|
||||
}) != depends_on.buffer_outputs().end();
|
||||
}) != task.m_dependencies.end();
|
||||
}
|
||||
|
||||
bool writes_to(const TaskInfo& task, const std::string& name) {
|
||||
if(std::find_if(task.m_color_outputs.begin(), task.m_color_outputs.end(),
|
||||
[name](const ImageResourceDescription& resource) {return resource.name() == name;}) != task.m_color_outputs.end()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if(std::find_if(task.buffer_outputs().begin(), task.buffer_outputs().end(),
|
||||
[name](const BufferResourceDescription& resource) {return resource.name() == name;}) != task.buffer_outputs().end()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return task.m_depth_output.has_value() && task.m_depth_output->name() == name;
|
||||
}
|
||||
|
||||
std::vector<uint32_t> get_adjacent_idxs(
|
||||
std::vector<TaskInfo>* tasks,
|
||||
uint32_t adjacent_of
|
||||
) {
|
||||
auto& task = (*tasks)[adjacent_of];
|
||||
|
||||
std::vector<uint32_t> idxs;
|
||||
uint32_t idx = 0;
|
||||
for(auto dependency : *tasks) {
|
||||
if(dependency.name() == task.name()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if(is_depending_on(dependency, task)) {
|
||||
idxs.push_back(idx);
|
||||
}
|
||||
|
||||
idx++;
|
||||
}
|
||||
|
||||
return idxs;
|
||||
}
|
||||
|
||||
std::vector<std::string> collect_task_names(std::vector<TaskInfo>& tasks) {
|
||||
std::vector<std::string> names(tasks.size());
|
||||
std::transform(tasks.begin(), tasks.end(), names.begin(),
|
||||
[](const TaskInfo& item) { return item.name(); }
|
||||
);
|
||||
|
||||
return names;
|
||||
}
|
||||
|
||||
bool has_common_write(const TaskInfo& task1, const TaskInfo& task2) {
|
||||
for(auto& write : task1.buffer_outputs()) {
|
||||
for(auto& write2 : task2.buffer_outputs()) {
|
||||
if(write2.name() == write.name()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(task1.depth_output().has_value() && task2.depth_output().has_value() &&
|
||||
task1.depth_output()->name() == task2.depth_output()->name()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for(auto& write : task1.color_outputs()) {
|
||||
for(auto& write2 : task2.color_outputs()) {
|
||||
if(write2.name() == write.name()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
AdjacencyMatrix* build_adj_matrix(std::vector<TaskInfo>& tasks, const std::string& output_name) {
|
||||
auto names = collect_task_names(tasks);
|
||||
names.push_back(output_name);
|
||||
AdjacencyMatrix* matrix = new AdjacencyMatrix(names);
|
||||
for(uint32_t y = 0; y < tasks.size(); y++) {
|
||||
for(uint32_t x = 0; x < tasks.size(); x++) {
|
||||
if(x == y) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if(is_depending_on(tasks[y], tasks[x])) {
|
||||
matrix->set(x, y);
|
||||
} else {
|
||||
if(has_common_write(tasks[y], tasks[x]) &&
|
||||
matrix->get(y, x) == false &&
|
||||
matrix->get(x, y) == false) {
|
||||
matrix->set(y, x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(writes_to(tasks[y], output_name)) {
|
||||
matrix->set(y, tasks.size());
|
||||
}
|
||||
}
|
||||
|
||||
matrix->transitive_reduction();
|
||||
|
||||
return matrix;
|
||||
}
|
||||
|
||||
std::vector<TaskInfo> topology_sort(std::vector<TaskInfo>& tasks, const std::string& output_name) {
|
||||
auto matrix = build_adj_matrix(tasks, output_name);
|
||||
|
||||
// get final tasks
|
||||
std::queue<uint32_t> queue;
|
||||
std::vector<bool> done(tasks.size(), false);
|
||||
std::vector<TaskInfo> result;
|
||||
|
||||
auto last = matrix->get_dependencies(tasks.size());
|
||||
for(auto& i : last) {
|
||||
queue.push(i);
|
||||
}
|
||||
|
||||
while(!queue.empty()) {
|
||||
auto item = queue.front();
|
||||
queue.pop();
|
||||
|
||||
if(done[item]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
result.push_back(tasks[item]);
|
||||
|
||||
auto dependencies = matrix->get_dependencies(item);
|
||||
|
||||
while(dependencies.size() == 1) {
|
||||
if(done[item]) break;
|
||||
|
||||
done[item] = true;
|
||||
|
||||
item = dependencies[0];
|
||||
dependencies = matrix->get_dependencies(item);
|
||||
result.push_back(tasks[item]);
|
||||
}
|
||||
|
||||
for(auto& dependency : dependencies) {
|
||||
queue.push(dependency);
|
||||
}
|
||||
}
|
||||
|
||||
std::reverse(result.begin(), result.end());
|
||||
return result;
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
RenderGraph Builder::build() {
|
||||
auto sorted_tasks = topology_sort(m_tasks, m_output_name);
|
||||
|
||||
m_allocator.set_store_all_images(m_store_all_images);
|
||||
|
||||
auto dependencies = build_adj_matrix(m_tasks, m_output_name);
|
||||
return m_allocator.allocate(sorted_tasks, dependencies);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
project(loft_render_graph_tests)
|
||||
|
||||
find_package(Vulkan QUIET)
|
||||
find_package(SDL2 REQUIRED)
|
||||
find_package(Catch2 REQUIRED)
|
||||
|
||||
set(LIBS
|
||||
loft_render_graph
|
||||
loft_base
|
||||
loft_common
|
||||
loft_window
|
||||
${SDL2_LIBRARIES}
|
||||
volk)
|
||||
|
||||
set(FILES
|
||||
# ./DependencyGraphTests.cpp
|
||||
./RenderGraphBuilderTests.cpp
|
||||
./TopologicalSortTests.cpp
|
||||
)
|
||||
|
||||
add_library(render_graph_unit_tests_sources OBJECT Mock.cpp ${FILES})
|
||||
target_link_libraries(render_graph_unit_tests_sources
|
||||
Catch2::Catch2WithMain
|
||||
${LIBS}
|
||||
)
|
||||
|
||||
add_executable(render_graph_unit_tests)
|
||||
|
||||
target_link_libraries(render_graph_unit_tests
|
||||
PRIVATE
|
||||
${LIBS}
|
||||
render_graph_unit_tests_sources
|
||||
Catch2::Catch2WithMain
|
||||
)
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH ${catch2_SOURCE_DIR}/extras)
|
||||
|
||||
include(CTest)
|
||||
include(Catch)
|
||||
|
||||
catch_discover_tests(render_graph_unit_tests)
|
||||
@@ -0,0 +1,40 @@
|
||||
#include "AdjacencyMatrix.hpp"
|
||||
|
||||
#include <set>
|
||||
|
||||
void test_color_dependency() {
|
||||
AdjacencyMatrix adj({"task1", "task2", "final"});
|
||||
adj.set("task1", "task2");
|
||||
adj.set("task2", "final");
|
||||
|
||||
auto deps = adj.get_dependencies("final");
|
||||
ASSERT(deps == std::vector<std::string>({"task2"}));
|
||||
|
||||
deps = adj.get_dependencies("task2");
|
||||
ASSERT(deps == std::vector<std::string>({"task1"}));
|
||||
|
||||
deps = adj.get_dependencies("task1");
|
||||
ASSERT(deps == std::vector<std::string>({}));
|
||||
}
|
||||
|
||||
void test_color_dependency2() {
|
||||
AdjacencyMatrix adj({"task1", "task2", "final"});
|
||||
adj.set("task1", "final");
|
||||
adj.set("task2", "final");
|
||||
|
||||
auto deps = adj.get_dependencies("final");
|
||||
ASSERT(std::set<std::string>(deps.begin(), deps.end()) == std::set<std::string>({"task1", "task2"}));
|
||||
|
||||
deps = adj.get_dependencies("task2");
|
||||
ASSERT(deps == std::vector<std::string>({}));
|
||||
|
||||
deps = adj.get_dependencies("task1");
|
||||
ASSERT(deps == std::vector<std::string>({}));
|
||||
}
|
||||
|
||||
int main() {
|
||||
test_color_dependency();
|
||||
test_color_dependency2();
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
#include "Mock.hpp"
|
||||
|
||||
|
||||
void lft_dbg_callback(lft::dbg::LogMessageSeverity severity,
|
||||
lft::dbg::LogMessageType type,
|
||||
const char *__restrict format,
|
||||
va_list args) {
|
||||
}
|
||||
|
||||
std::unique_ptr<Gpu> create_mock_gpu() {
|
||||
auto instance = std::make_unique<const Instance>(
|
||||
"loft", "loft",
|
||||
std::vector<std::string>({VK_KHR_SURFACE_EXTENSION_NAME}),
|
||||
std::vector<std::string>(),
|
||||
lft_dbg_callback);
|
||||
|
||||
volkLoadInstance(instance->instance());
|
||||
|
||||
return std::make_unique<Gpu>(instance.get(), std::nullopt);
|
||||
}
|
||||
|
||||
|
||||
|
||||
ImageChain create_mock_image_chain(
|
||||
const Gpu* gpu,
|
||||
uint32_t num_images,
|
||||
VkExtent2D extent,
|
||||
VkFormat format
|
||||
) {
|
||||
std::vector<ImageView> images(num_images);
|
||||
for(uint32_t i = 0; i < num_images; i++) {
|
||||
MemoryAllocationInfo memory_info = {
|
||||
.usage = MEMORY_USAGE_AUTO_PREFER_DEVICE
|
||||
};
|
||||
|
||||
ImageCreateInfo image_info = {
|
||||
.extent = extent,
|
||||
.format = format,
|
||||
.usage = VK_IMAGE_USAGE_SAMPLED_BIT |
|
||||
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
|
||||
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
||||
.arrayLayers = 1,
|
||||
.mipLevels = 1,
|
||||
};
|
||||
|
||||
Image image = {};
|
||||
gpu->memory()->create_image(&image_info, &memory_info, &image);
|
||||
|
||||
ImageView view = {};
|
||||
view = image.create_view(gpu, format, {
|
||||
.aspectMask = image_info.aspectMask,
|
||||
.baseMipLevel = 0,
|
||||
.levelCount = 1,
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = 1,
|
||||
});
|
||||
|
||||
images[i] = view;
|
||||
}
|
||||
|
||||
return ImageChain(format, extent, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, images);
|
||||
}
|
||||
|
||||
|
||||
|
||||
lft::rg::RenderTaskBuilder create_empty_task(const std::string& name) {
|
||||
return lft::rg::render_task<EmptyContext>(
|
||||
"task1", new EmptyContext,
|
||||
[](const lft::rg::TaskBuildInfo& info, EmptyContext* ctx) {},
|
||||
[](const lft::rg::TaskRecordInfo& info, EmptyContext* ctx) {}
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef MOCK_DEFINED
|
||||
#define MOCK_DEFINED 1
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "Gpu.hpp"
|
||||
#include "ImageChain.hpp"
|
||||
#include "RenderGraphBuilder.hpp"
|
||||
|
||||
std::unique_ptr<Gpu> create_mock_gpu();
|
||||
|
||||
ImageChain create_mock_image_chain(
|
||||
const Gpu* gpu,
|
||||
uint32_t num_images,
|
||||
VkExtent2D extent,
|
||||
VkFormat format
|
||||
);
|
||||
|
||||
struct EmptyContext { };
|
||||
|
||||
lft::rg::RenderTaskBuilder create_empty_task(const std::string& name);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,613 @@
|
||||
#pragma once
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#define private public
|
||||
#include "Mock.hpp"
|
||||
|
||||
const VkFormat FMT = VK_FORMAT_R8G8B8A8_UNORM;
|
||||
const VkExtent2D EXTENT = {
|
||||
.width = 1024,
|
||||
.height = 1024
|
||||
};
|
||||
|
||||
|
||||
struct Struct {
|
||||
};
|
||||
|
||||
TEST_CASE("BothFinalOutputAndColorOutput", "[rg]") {
|
||||
auto gpu = create_mock_gpu();
|
||||
|
||||
auto image_chain = create_mock_image_chain(gpu.get(), 1, EXTENT, FMT);
|
||||
lft::rg::Builder builder(
|
||||
gpu.get(), image_chain, "output"
|
||||
);
|
||||
|
||||
EmptyContext ctx;
|
||||
|
||||
auto task1 = create_empty_task("task1")
|
||||
.set_output_to_final()
|
||||
.add_color_output("output", FMT)
|
||||
.build();
|
||||
|
||||
builder.add_task(task1);
|
||||
|
||||
auto rg = builder.build();
|
||||
|
||||
REQUIRE(rg.m_buffers[0]->num_batches() == 1);
|
||||
REQUIRE(rg.m_buffers[0]->batch(0).tasks[0].pDefinition.name() == "task1");
|
||||
}
|
||||
|
||||
void test_render_graph_push() {
|
||||
auto gpu = create_mock_gpu();
|
||||
|
||||
VkImageView a;
|
||||
VkFormat fmt = VK_FORMAT_R8G8B8A8_UNORM;
|
||||
ImageChain image_chain = create_mock_image_chain(gpu.get(), 1, EXTENT, FMT);
|
||||
|
||||
lft::rg::Builder builder(gpu.get(), image_chain, "output");
|
||||
|
||||
Struct data = {};
|
||||
auto task1 = lft::rg::render_task<Struct>(
|
||||
"task1", &data,
|
||||
[](const lft::rg::TaskBuildInfo& info, Struct* ctx) {},
|
||||
[](const lft::rg::TaskRecordInfo& info, Struct* ctx) {})
|
||||
.add_color_output("output", FMT, EXTENT, {})
|
||||
.add_color_output("resource1", FMT, EXTENT, {})
|
||||
.set_extent(EXTENT)
|
||||
.build();
|
||||
builder.add_task(task1);
|
||||
|
||||
std::cout << "First build" << std::endl;
|
||||
auto rg = builder.build();
|
||||
|
||||
ASSERT(rg.m_buffers[0]->num_batches() == 1);
|
||||
ASSERT(rg.m_buffers[0]->batch(0).tasks.size() == 1);
|
||||
ASSERT(rg.m_buffers[0]->batch(0).tasks[0].pDefinition.equals(task1));
|
||||
|
||||
auto task2 = create_empty_task("task2")
|
||||
.add_color_output("output", FMT, EXTENT, {})
|
||||
.add_dependency("resource1")
|
||||
.set_extent(EXTENT)
|
||||
.build();
|
||||
builder.add_task(task2);
|
||||
|
||||
std::cout << "Second build" << std::endl;
|
||||
rg = builder.build();
|
||||
|
||||
ASSERT(rg.m_buffers[0]->num_batches() == 2);
|
||||
ASSERT(rg.m_buffers[0]->batch(0).tasks.size() == 1);
|
||||
ASSERT(rg.m_buffers[0]->batch(0).tasks[0].pDefinition.equals(task1));
|
||||
|
||||
ASSERT(rg.m_buffers[0]->batch(1).tasks.size() == 1);
|
||||
ASSERT(rg.m_buffers[0]->batch(1).tasks[0].pDefinition.equals(task2));
|
||||
|
||||
auto deps1 = rg.m_dependency_matrix->get_dependencies(1);
|
||||
ASSERT(deps1.size() == 1);
|
||||
ASSERT(deps1[0] == 0);
|
||||
auto deps2 = rg.m_dependency_matrix->get_dependencies("task2");
|
||||
ASSERT(deps2.size() == 1);
|
||||
ASSERT(deps2[0] == "task1");
|
||||
|
||||
lft::rg::Builder builder2(gpu.get(), image_chain, "output");
|
||||
builder2.add_task(task1);
|
||||
builder2.add_task(task2);
|
||||
std::cout << "Compare build" << std::endl;
|
||||
builder2.build();
|
||||
|
||||
ASSERT(builder.m_allocator.equals(builder2.m_allocator));
|
||||
ASSERT(builder2.m_allocator.equals(builder.m_allocator));
|
||||
}
|
||||
|
||||
void test_render_graph_insert_begin() {
|
||||
VkExtent2D extent = {
|
||||
.width = 1024,
|
||||
.height = 1024
|
||||
};
|
||||
|
||||
auto gpu = create_mock_gpu();
|
||||
|
||||
VkImageView a;
|
||||
VkFormat fmt = VK_FORMAT_R8G8B8A8_UNORM;
|
||||
ImageChain image_chain = create_mock_image_chain(gpu.get(), 1, extent, fmt);
|
||||
|
||||
lft::rg::Builder builder(gpu.get(), image_chain, "output");
|
||||
|
||||
Struct data = {};
|
||||
auto task1 = lft::rg::render_task<Struct>(
|
||||
"task1", &data,
|
||||
[](const lft::rg::TaskBuildInfo& info, Struct* ctx) {},
|
||||
[](const lft::rg::TaskRecordInfo& info, Struct* ctx) {})
|
||||
.add_color_output("resource1", fmt, extent, {});
|
||||
|
||||
auto task2 = lft::rg::render_task<Struct>(
|
||||
"task2", &data,
|
||||
[](const lft::rg::TaskBuildInfo& info, Struct* ctx) {},
|
||||
[](const lft::rg::TaskRecordInfo& info, Struct* ctx) {})
|
||||
.add_color_output("resource2", fmt, extent, {});
|
||||
|
||||
auto task3 = lft::rg::render_task<Struct>(
|
||||
"task3", &data,
|
||||
[](const lft::rg::TaskBuildInfo& info, Struct* ctx) {},
|
||||
[](const lft::rg::TaskRecordInfo& info, Struct* ctx) {})
|
||||
.add_color_output("output", fmt, extent, {})
|
||||
.add_dependency("resource2");
|
||||
|
||||
builder.add_task(task2.build());
|
||||
builder.add_task(task3.build());
|
||||
|
||||
builder.build();
|
||||
|
||||
task2.add_dependency("resource1");
|
||||
builder.add_task(task2.build());
|
||||
builder.add_task(task1.build());
|
||||
|
||||
lft::rg::RenderGraph rg = builder.build();
|
||||
|
||||
ASSERT(rg.buffer(0).num_batches() == 3);
|
||||
ASSERT(rg.buffer(0).batch(0).tasks[0].pDefinition.name() == "task1");
|
||||
ASSERT(rg.buffer(0).batch(1).tasks[0].pDefinition.name() == "task2");
|
||||
ASSERT(rg.buffer(0).batch(2).tasks[0].pDefinition.name() == "task3");
|
||||
}
|
||||
|
||||
|
||||
void test_render_graph_insert_middle() {
|
||||
VkExtent2D extent = {
|
||||
.width = 1024,
|
||||
.height = 1024
|
||||
};
|
||||
|
||||
auto gpu = create_mock_gpu();
|
||||
|
||||
VkImageView a;
|
||||
VkFormat fmt = VK_FORMAT_R8G8B8A8_UNORM;
|
||||
ImageChain image_chain = create_mock_image_chain(gpu.get(), 1, extent, fmt);
|
||||
|
||||
lft::rg::Builder builder(gpu.get(), image_chain, "output");
|
||||
|
||||
Struct data = {};
|
||||
auto task1 = lft::rg::render_task<Struct>(
|
||||
"task1", &data,
|
||||
[](const lft::rg::TaskBuildInfo& info, Struct* ctx) {},
|
||||
[](const lft::rg::TaskRecordInfo& info, Struct* ctx) {})
|
||||
.add_color_output("resource1", fmt, extent, {});
|
||||
|
||||
auto task2 = lft::rg::render_task<Struct>(
|
||||
"task2", &data,
|
||||
[](const lft::rg::TaskBuildInfo& info, Struct* ctx) {},
|
||||
[](const lft::rg::TaskRecordInfo& info, Struct* ctx) {})
|
||||
.add_color_output("resource2", fmt, extent, {})
|
||||
.add_dependency("resource1");
|
||||
|
||||
auto task3 = lft::rg::render_task<Struct>(
|
||||
"task3", &data,
|
||||
[](const lft::rg::TaskBuildInfo& info, Struct* ctx) {},
|
||||
[](const lft::rg::TaskRecordInfo& info, Struct* ctx) {})
|
||||
.add_color_output("output", fmt, extent, {})
|
||||
.add_dependency("resource1");
|
||||
|
||||
builder.add_task(task1.build());
|
||||
builder.add_task(task3.build());
|
||||
|
||||
builder.build();
|
||||
|
||||
task3.add_dependency("resource2");
|
||||
builder.add_task(task3.build());
|
||||
builder.add_task(task2.build());
|
||||
|
||||
lft::rg::RenderGraph rg = builder.build();
|
||||
|
||||
ASSERT(rg.buffer(0).num_batches() == 3);
|
||||
ASSERT(rg.buffer(0).batch(0).tasks[0].pDefinition.name() == "task1");
|
||||
ASSERT(rg.buffer(0).batch(1).tasks[0].pDefinition.name() == "task2");
|
||||
ASSERT(rg.buffer(0).batch(2).tasks[0].pDefinition.name() == "task3");
|
||||
}
|
||||
|
||||
void test_render_graph_extent() {
|
||||
VkExtent2D extent = {
|
||||
.width = 1024,
|
||||
.height = 1024
|
||||
};
|
||||
|
||||
auto gpu = create_mock_gpu();
|
||||
|
||||
VkImageView a;
|
||||
VkFormat fmt = VK_FORMAT_R8G8B8A8_UNORM;
|
||||
ImageChain image_chain = create_mock_image_chain(gpu.get(), 1, extent, fmt);
|
||||
|
||||
lft::rg::Builder builder(gpu.get(), image_chain, "output");
|
||||
|
||||
Struct data = {};
|
||||
auto task1 = lft::rg::render_task<Struct>(
|
||||
"task1", &data,
|
||||
[](const lft::rg::TaskBuildInfo& info, Struct* ctx) {},
|
||||
[](const lft::rg::TaskRecordInfo& info, Struct* ctx) {})
|
||||
.add_color_output("output", fmt, extent, {})
|
||||
.add_color_output("resource1", fmt, extent, {})
|
||||
.build();
|
||||
builder.add_task(task1);
|
||||
|
||||
auto task2 = lft::rg::render_task<Struct>(
|
||||
"task2", &data,
|
||||
[](const lft::rg::TaskBuildInfo& info, Struct* ctx) {},
|
||||
[](const lft::rg::TaskRecordInfo& info, Struct* ctx) {})
|
||||
.add_color_output("output", fmt, extent, {})
|
||||
.add_dependency("resource1")
|
||||
.build();
|
||||
builder.add_task(task2);
|
||||
|
||||
lft::rg::RenderGraph rg = builder.build();
|
||||
|
||||
ASSERT(rg.buffer(0).num_batches() == 2);
|
||||
// equals tests the extent
|
||||
ASSERT(!rg.buffer(0).batch(0).tasks[0].pDefinition.equals(task1));
|
||||
ASSERT(!rg.buffer(0).batch(1).tasks[0].pDefinition.equals(task2));
|
||||
|
||||
ASSERT(rg.buffer(0).batch(0).tasks[0].extent.width == extent.width &&
|
||||
rg.buffer(0).batch(0).tasks[0].extent.height == extent.height);
|
||||
ASSERT(rg.buffer(0).batch(1).tasks[0].extent.width == extent.width &&
|
||||
rg.buffer(0).batch(1).tasks[0].extent.height == extent.height);
|
||||
}
|
||||
|
||||
void test_render_graph_update_renderpass() {
|
||||
std::cout << "Running update test" << std::endl;
|
||||
VkExtent2D extent = {
|
||||
.width = 1024,
|
||||
.height = 1024
|
||||
};
|
||||
|
||||
auto gpu = create_mock_gpu();
|
||||
|
||||
VkImageView a;
|
||||
VkFormat fmt = VK_FORMAT_R8G8B8A8_UNORM;
|
||||
ImageChain image_chain = create_mock_image_chain(gpu.get(), 1, extent, fmt);
|
||||
|
||||
lft::rg::Builder builder(gpu.get(), image_chain, "output");
|
||||
|
||||
Struct data = {};
|
||||
auto task1 = lft::rg::render_task<Struct>(
|
||||
"task1", &data,
|
||||
[](const lft::rg::TaskBuildInfo& info, Struct* ctx) {},
|
||||
[](const lft::rg::TaskRecordInfo& info, Struct* ctx) {})
|
||||
.add_color_output("output", fmt, extent, {})
|
||||
.add_color_output("resource1", fmt, extent, {})
|
||||
.set_extent(extent)
|
||||
.build();
|
||||
builder.add_task(task1);
|
||||
|
||||
auto task2 = lft::rg::render_task<Struct>(
|
||||
"task2", &data,
|
||||
[](const lft::rg::TaskBuildInfo& info, Struct* ctx) {},
|
||||
[](const lft::rg::TaskRecordInfo& info, Struct* ctx) {})
|
||||
.add_color_output("output", fmt, extent, {})
|
||||
.add_dependency("resource1")
|
||||
.set_extent(extent)
|
||||
.build();
|
||||
builder.add_task(task2);
|
||||
lft::rg::RenderGraph rg = builder.build();
|
||||
|
||||
// update
|
||||
task1.add_color_output("resource2", fmt, extent, {});
|
||||
|
||||
builder.add_task(task1);
|
||||
|
||||
VkRenderPass previous_rp = rg.buffer(0).batch(0).tasks[0].render_pass.render_pass;
|
||||
auto previous_fb = rg.buffer(0).batch(0).tasks[0].framebuffer;
|
||||
builder.build();
|
||||
|
||||
ASSERT(rg.buffer(0).num_batches() == 2);
|
||||
auto _task1 = rg.buffer(0).batch(0).tasks[0];
|
||||
auto _task2 = rg.buffer(0).batch(1).tasks[0];
|
||||
|
||||
// equals tests the extent
|
||||
ASSERT(_task1.pDefinition.equals(task1));
|
||||
ASSERT(_task2.pDefinition.equals(task2));
|
||||
|
||||
ASSERT(_task1.render_pass.render_pass != previous_rp);
|
||||
ASSERT(_task1.framebuffer != previous_fb);
|
||||
}
|
||||
|
||||
void test_render_graph_remove() {
|
||||
std::cout << "Running update test" << std::endl;
|
||||
VkExtent2D extent = {
|
||||
.width = 1024,
|
||||
.height = 1024
|
||||
};
|
||||
|
||||
auto gpu = create_mock_gpu();
|
||||
|
||||
VkImageView a;
|
||||
VkFormat fmt = VK_FORMAT_R8G8B8A8_UNORM;
|
||||
ImageChain image_chain = create_mock_image_chain(gpu.get(), 1, extent, fmt);
|
||||
|
||||
lft::rg::Builder builder(gpu.get(), image_chain, "output");
|
||||
|
||||
Struct data = {};
|
||||
auto task1 = lft::rg::render_task<Struct>(
|
||||
"task1", &data,
|
||||
[](const lft::rg::TaskBuildInfo& info, Struct* ctx) {},
|
||||
[](const lft::rg::TaskRecordInfo& info, Struct* ctx) {})
|
||||
.add_color_output("output", fmt, extent, {})
|
||||
.add_color_output("resource1", fmt, extent, {})
|
||||
.add_color_output("resource2", fmt, extent, {})
|
||||
.set_extent(extent)
|
||||
.build();
|
||||
builder.add_task(task1);
|
||||
|
||||
auto task2 = lft::rg::render_task<Struct>(
|
||||
"task2", &data,
|
||||
[](const lft::rg::TaskBuildInfo& info, Struct* ctx) {},
|
||||
[](const lft::rg::TaskRecordInfo& info, Struct* ctx) {})
|
||||
.add_color_output("output", fmt, extent, {})
|
||||
.add_dependency("resource1")
|
||||
.set_extent(extent)
|
||||
.build();
|
||||
|
||||
builder.add_task(task1);
|
||||
builder.add_task(task2);
|
||||
auto rg = builder.build();
|
||||
|
||||
ASSERT(rg.buffer(0).num_batches() == 2);
|
||||
ASSERT(rg.buffer(0).batch(0).tasks.size() == 1);
|
||||
ASSERT(rg.buffer(0).batch(1).tasks.size() == 1);
|
||||
|
||||
builder.remove_task("task2");
|
||||
rg = builder.build();
|
||||
|
||||
ASSERT(rg.buffer(0).num_batches() == 1);
|
||||
ASSERT(rg.buffer(0).batch(0).tasks.size() == 1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Attempts to add task, build the render graph, remove the task again and rebuild repeatedly.
|
||||
*/
|
||||
void test_render_graph_remove_and_add() {
|
||||
std::cout << "Running update test" << std::endl;
|
||||
VkExtent2D extent = {
|
||||
.width = 1024,
|
||||
.height = 1024
|
||||
};
|
||||
|
||||
auto gpu = create_mock_gpu();
|
||||
|
||||
VkImageView a;
|
||||
VkFormat fmt = VK_FORMAT_R8G8B8A8_UNORM;
|
||||
ImageChain image_chain = create_mock_image_chain(gpu.get(), 4, extent, fmt);
|
||||
|
||||
lft::rg::Builder builder(gpu.get(), image_chain, "output");
|
||||
|
||||
Struct data = {};
|
||||
auto task1 = lft::rg::render_task<Struct>(
|
||||
"task1", &data,
|
||||
[](const lft::rg::TaskBuildInfo& info, Struct* ctx) {},
|
||||
[](const lft::rg::TaskRecordInfo& info, Struct* ctx) {})
|
||||
.add_color_output("output", fmt, extent, {})
|
||||
.add_color_output("resource1", fmt, extent, {})
|
||||
.add_color_output("resource2", fmt, extent, {})
|
||||
.set_extent(extent)
|
||||
.build();
|
||||
builder.add_task(task1);
|
||||
|
||||
auto task2 = lft::rg::render_task<Struct>(
|
||||
"task2", &data,
|
||||
[](const lft::rg::TaskBuildInfo& info, Struct* ctx) {},
|
||||
[](const lft::rg::TaskRecordInfo& info, Struct* ctx) {})
|
||||
.add_color_output("output", fmt, extent, {})
|
||||
.add_dependency("resource1")
|
||||
.set_extent(extent)
|
||||
.build();
|
||||
|
||||
builder.add_task(task1);
|
||||
builder.add_task(task2);
|
||||
auto rg = builder.build();
|
||||
|
||||
ASSERT(rg.buffer(0).num_batches() == 2);
|
||||
ASSERT(rg.buffer(0).batch(0).tasks.size() == 1);
|
||||
ASSERT(rg.buffer(0).batch(1).tasks.size() == 1);
|
||||
|
||||
for(uint32_t i = 0; i < 10; i++) {
|
||||
builder.remove_task("task2");
|
||||
rg = builder.build();
|
||||
|
||||
ASSERT(rg.buffer(0).num_batches() == 1);
|
||||
ASSERT(rg.buffer(0).batch(0).tasks.size() == 1);
|
||||
|
||||
builder.add_task(task2);
|
||||
rg = builder.build();
|
||||
|
||||
ASSERT(rg.buffer(0).num_batches() == 2);
|
||||
ASSERT(rg.buffer(0).batch(0).tasks.size() == 1);
|
||||
ASSERT(rg.buffer(0).batch(1).tasks.size() == 1);
|
||||
}
|
||||
}
|
||||
|
||||
void test_buffer_idxs() {
|
||||
VkExtent2D extent = {
|
||||
.width = 1024,
|
||||
.height = 1024
|
||||
};
|
||||
|
||||
auto gpu = create_mock_gpu();
|
||||
|
||||
VkImageView a;
|
||||
VkFormat fmt = VK_FORMAT_R8G8B8A8_UNORM;
|
||||
ImageChain image_chain = create_mock_image_chain(gpu.get(), 4, extent, fmt);
|
||||
|
||||
lft::rg::Builder builder(gpu.get(), image_chain, "output");
|
||||
|
||||
Struct data = {};
|
||||
auto task1 = lft::rg::render_task<Struct>(
|
||||
"task1", &data,
|
||||
[](const lft::rg::TaskBuildInfo& info, Struct* ctx) {
|
||||
ASSERT(info.buffer_idx() == 0);
|
||||
},
|
||||
[](const lft::rg::TaskRecordInfo& info, Struct* ctx) {})
|
||||
.add_color_output("output", fmt, extent, {})
|
||||
.add_color_output("resource1", fmt, extent, {})
|
||||
.add_color_output("resource2", fmt, extent, {})
|
||||
.set_extent(extent)
|
||||
.build();
|
||||
|
||||
auto task2 = lft::rg::render_task<Struct>(
|
||||
"task2", &data,
|
||||
[](const lft::rg::TaskBuildInfo& info, Struct* ctx) {
|
||||
ASSERT(info.buffer_idx() == 0);
|
||||
},
|
||||
[](const lft::rg::TaskRecordInfo& info, Struct* ctx) {})
|
||||
.add_color_output("output", fmt, extent, {})
|
||||
.add_dependency("resource1")
|
||||
.set_extent(extent)
|
||||
.build();
|
||||
|
||||
builder.add_task(task1);
|
||||
builder.add_task(task2);
|
||||
auto rg = builder.build();
|
||||
|
||||
ASSERT(rg.buffer(0).index() == 0);
|
||||
}
|
||||
|
||||
void test_compute() {
|
||||
VkExtent2D extent = {
|
||||
.width = 1024,
|
||||
.height = 1024
|
||||
};
|
||||
|
||||
auto gpu = create_mock_gpu();
|
||||
|
||||
VkImageView a;
|
||||
VkFormat fmt = VK_FORMAT_R8G8B8A8_UNORM;
|
||||
ImageChain image_chain = create_mock_image_chain(gpu.get(), 4, extent, fmt);
|
||||
|
||||
lft::rg::Builder builder(gpu.get(), image_chain, "output");
|
||||
|
||||
Struct data = {};
|
||||
bool is_build_func_called = false;
|
||||
auto task1 = lft::rg::compute_task<Struct>(
|
||||
"task1", &data,
|
||||
[&](const lft::rg::TaskBuildInfo& info, Struct* ctx) {
|
||||
ASSERT(info.buffer_idx() == 0);
|
||||
is_build_func_called = true;
|
||||
},
|
||||
[&](const lft::rg::TaskRecordInfo& info, Struct* ctx) {})
|
||||
.add_buffer_output("resource1", 1000)
|
||||
.build();
|
||||
|
||||
auto task2 = lft::rg::render_task<Struct>(
|
||||
"task2", &data,
|
||||
[](const lft::rg::TaskBuildInfo& info, Struct* ctx) {
|
||||
ASSERT(info.buffer_idx() == 0);
|
||||
},
|
||||
[](const lft::rg::TaskRecordInfo& info, Struct* ctx) {})
|
||||
.add_color_output("output", fmt, extent, {})
|
||||
.add_dependency("resource1")
|
||||
.set_extent(extent)
|
||||
.build();
|
||||
|
||||
builder.add_task(task1);
|
||||
builder.add_task(task2);
|
||||
auto rg = builder.build();
|
||||
|
||||
ASSERT(is_build_func_called);
|
||||
ASSERT(rg.buffer(0).batch(0).tasks[0].pDefinition.name() == "task1");
|
||||
ASSERT(rg.buffer(0).batch(1).tasks[0].pDefinition.name() == "task2");
|
||||
}
|
||||
|
||||
void test_render_graph2() {
|
||||
VkExtent2D extent = {
|
||||
.width = 1024,
|
||||
.height = 1024
|
||||
};
|
||||
|
||||
auto gpu = create_mock_gpu();
|
||||
|
||||
VkImageView a;
|
||||
VkFormat fmt = VK_FORMAT_R8G8B8A8_UNORM;
|
||||
ImageChain image_chain = create_mock_image_chain(gpu.get(), 4, extent, fmt);
|
||||
|
||||
lft::rg::Builder builder(gpu.get(), image_chain, "output");
|
||||
|
||||
Struct data = {};
|
||||
auto task1 = lft::rg::render_task<Struct>(
|
||||
"task1", &data,
|
||||
[&](const lft::rg::TaskBuildInfo& info, Struct* ctx) {},
|
||||
[&](const lft::rg::TaskRecordInfo& info, Struct* ctx) {})
|
||||
.add_color_output("resource1", VK_FORMAT_R8G8B8A8_UNORM, extent, {0.0, 0.0, 0.0, 0.0})
|
||||
.build();
|
||||
|
||||
auto task2 = lft::rg::render_task<Struct>(
|
||||
"task2", &data,
|
||||
[](const lft::rg::TaskBuildInfo& info, Struct* ctx) {},
|
||||
[](const lft::rg::TaskRecordInfo& info, Struct* ctx) {})
|
||||
.add_dependency("resource1")
|
||||
.add_color_output("output", fmt, extent, {})
|
||||
.build();
|
||||
|
||||
auto task3 = lft::rg::compute_task<Struct>(
|
||||
"task3", &data,
|
||||
[](const lft::rg::TaskBuildInfo& info, Struct* ctx) {},
|
||||
[](const lft::rg::TaskRecordInfo& info, Struct* ctx) {})
|
||||
.add_buffer_output("resource2", 1000)
|
||||
.build();
|
||||
|
||||
auto task4 = lft::rg::render_task<Struct>(
|
||||
"task4", &data,
|
||||
[](const lft::rg::TaskBuildInfo& info, Struct* ctx) {},
|
||||
[](const lft::rg::TaskRecordInfo& info, Struct* ctx) {})
|
||||
.add_color_output("output", fmt, extent, {})
|
||||
.add_dependency("resource2")
|
||||
.add_dependency("task2")
|
||||
.build();
|
||||
|
||||
auto task5 = lft::rg::render_task<Struct>(
|
||||
"task5", &data,
|
||||
[](const lft::rg::TaskBuildInfo& info, Struct* ctx) {},
|
||||
[](const lft::rg::TaskRecordInfo& info, Struct* ctx) {})
|
||||
.add_color_output("output", fmt, extent, {})
|
||||
.add_dependency("task2")
|
||||
.build();
|
||||
|
||||
builder.add_task(task1);
|
||||
builder.add_task(task2);
|
||||
builder.add_task(task3);
|
||||
builder.add_task(task4);
|
||||
builder.add_task(task5);
|
||||
auto rg = builder.build();
|
||||
|
||||
auto wait1 = rg.get_wait_semaphores_for(&rg.buffer(0), 0);
|
||||
ASSERT(rg.buffer(0).batch(0).tasks[0].pDefinition.name() == "task3");
|
||||
ASSERT(wait1.empty());
|
||||
|
||||
auto wait2 = rg.get_wait_semaphores_for(&rg.buffer(0), 1);
|
||||
ASSERT(rg.buffer(0).batch(1).tasks[0].pDefinition.name() == "task1");
|
||||
ASSERT(wait2.empty());
|
||||
|
||||
auto wait3 = rg.get_wait_semaphores_for(&rg.buffer(0), 2);
|
||||
ASSERT(rg.buffer(0).batch(2).tasks[0].pDefinition.name() == "task2");
|
||||
ASSERT(wait3.size() == 1);
|
||||
ASSERT(wait3[0].semaphore == rg.buffer(0).batch(1).signal);
|
||||
|
||||
auto wait4 = rg.get_wait_semaphores_for(&rg.buffer(0), 3);
|
||||
ASSERT(rg.buffer(0).batch(3).tasks[0].pDefinition.name() == "task5");
|
||||
ASSERT(wait4.size() == 1);
|
||||
ASSERT(wait4[0].semaphore == rg.buffer(0).batch(2).signal);
|
||||
|
||||
auto wait5 = rg.get_wait_semaphores_for(&rg.buffer(0), 4);
|
||||
ASSERT(rg.buffer(0).batch(4).tasks[0].pDefinition.name() == "task4");
|
||||
ASSERT(wait5.size() == 2);
|
||||
ASSERT(wait5[0].semaphore == rg.buffer(0).batch(2).signal ||
|
||||
wait5[0].semaphore == rg.buffer(0).batch(0).signal);
|
||||
ASSERT(wait5[1].semaphore == rg.buffer(0).batch(0).signal ||
|
||||
wait5[1].semaphore == rg.buffer(0).batch(2).signal);
|
||||
}
|
||||
|
||||
// int main() {
|
||||
// /* test_render_graph_extent();
|
||||
// test_render_graph_push();
|
||||
// test_render_graph_insert_begin();
|
||||
// test_render_graph_insert_middle();
|
||||
// test_render_graph_update_renderpass();
|
||||
// test_render_graph_remove();
|
||||
// test_buffer_idxs();
|
||||
// test_compute(); */
|
||||
// test_render_graph2();
|
||||
// }
|
||||
@@ -0,0 +1,166 @@
|
||||
#pragma once
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#define private public
|
||||
#include "Mock.hpp"
|
||||
|
||||
const VkFormat FMT = VK_FORMAT_R8G8B8A8_UNORM;
|
||||
const VkExtent2D EXTENT = {
|
||||
.width = 1024,
|
||||
.height = 1024
|
||||
};
|
||||
|
||||
/**
|
||||
* Test single task
|
||||
*/
|
||||
TEST_CASE("SingleTask", "[rg]") {
|
||||
auto gpu = create_mock_gpu();
|
||||
|
||||
auto image_chain = create_mock_image_chain(gpu.get(), 1, EXTENT, FMT);
|
||||
lft::rg::Builder builder(
|
||||
gpu.get(), image_chain, "output"
|
||||
);
|
||||
|
||||
EmptyContext ctx;
|
||||
|
||||
auto task1 = create_empty_task("task1")
|
||||
.set_output_to_final()
|
||||
.build();
|
||||
|
||||
builder.add_task(task1);
|
||||
|
||||
auto sorted = topology_sort(builder.m_tasks, "output");
|
||||
|
||||
REQUIRE(sorted.size() == 1);
|
||||
REQUIRE(sorted[0].name() == "task1");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test two tasks where second depends on the first
|
||||
*/
|
||||
void test_topological_sort_01() {
|
||||
auto gpu = create_mock_gpu();
|
||||
|
||||
auto image_chain = create_mock_image_chain(gpu.get(), 1, EXTENT, FMT);
|
||||
lft::rg::Builder builder(
|
||||
gpu.get(), image_chain, "output"
|
||||
);
|
||||
|
||||
EmptyContext ctx;
|
||||
|
||||
auto task1 = lft::rg::render_task<EmptyContext>(
|
||||
"task1", &ctx,
|
||||
[](const lft::rg::TaskBuildInfo& info, EmptyContext* ctx) {},
|
||||
[](const lft::rg::TaskRecordInfo& info, EmptyContext* ctx) {}
|
||||
).add_color_output("resource1", FMT, EXTENT, {})
|
||||
.build();
|
||||
|
||||
auto task2 = lft::rg::render_task<EmptyContext>(
|
||||
"task2", &ctx,
|
||||
[](const lft::rg::TaskBuildInfo& info, EmptyContext* ctx) {},
|
||||
[](const lft::rg::TaskRecordInfo& info, EmptyContext* ctx) {}
|
||||
).add_dependency("resource1")
|
||||
.add_color_output("output", FMT, EXTENT, {})
|
||||
.build();
|
||||
|
||||
builder.add_task(task1);
|
||||
builder.add_task(task2);
|
||||
|
||||
auto sorted = topology_sort(builder.m_tasks, "output");
|
||||
|
||||
ASSERT(sorted.size() == 2);
|
||||
ASSERT(sorted[0].name() == "task1");
|
||||
ASSERT(sorted[1].name() == "task2");
|
||||
}
|
||||
|
||||
void test_topological_sort_02() {
|
||||
auto gpu = create_mock_gpu();
|
||||
|
||||
auto image_chain = create_mock_image_chain(gpu.get(), 1, EXTENT, FMT);
|
||||
lft::rg::Builder builder(
|
||||
gpu.get(), image_chain, "output"
|
||||
);
|
||||
|
||||
EmptyContext ctx;
|
||||
|
||||
auto task1 = lft::rg::render_task<EmptyContext>(
|
||||
"task1", &ctx,
|
||||
[](const lft::rg::TaskBuildInfo& info, EmptyContext* ctx) {},
|
||||
[](const lft::rg::TaskRecordInfo& info, EmptyContext* ctx) {}
|
||||
).add_color_output("resource1", FMT, EXTENT, {})
|
||||
.build();
|
||||
|
||||
auto task2 = lft::rg::render_task<EmptyContext>(
|
||||
"task2", &ctx,
|
||||
[](const lft::rg::TaskBuildInfo& info, EmptyContext* ctx) {},
|
||||
[](const lft::rg::TaskRecordInfo& info, EmptyContext* ctx) {}
|
||||
).add_dependency("resource1")
|
||||
.add_color_output("output", FMT, EXTENT, {})
|
||||
.build();
|
||||
|
||||
// inverted - test if it actually sorts
|
||||
builder.add_task(task2);
|
||||
builder.add_task(task1);
|
||||
|
||||
auto sorted = topology_sort(builder.m_tasks, "output");
|
||||
|
||||
ASSERT(sorted.size() == 2);
|
||||
ASSERT(sorted[0].name() == "task1");
|
||||
ASSERT(sorted[1].name() == "task2");
|
||||
}
|
||||
|
||||
void test_topological_sort_03() {
|
||||
auto gpu = create_mock_gpu();
|
||||
|
||||
auto image_chain = create_mock_image_chain(gpu.get(), 1, EXTENT, FMT);
|
||||
|
||||
EmptyContext ctx;
|
||||
|
||||
auto task1 = lft::rg::render_task<EmptyContext>(
|
||||
"task1", &ctx,
|
||||
[](const lft::rg::TaskBuildInfo& info, EmptyContext* ctx) {},
|
||||
[](const lft::rg::TaskRecordInfo& info, EmptyContext* ctx) {}
|
||||
).add_color_output("resource1", FMT, EXTENT, {})
|
||||
.build();
|
||||
|
||||
auto task2 = lft::rg::render_task<EmptyContext>(
|
||||
"task2", &ctx,
|
||||
[](const lft::rg::TaskBuildInfo& info, EmptyContext* ctx) {},
|
||||
[](const lft::rg::TaskRecordInfo& info, EmptyContext* ctx) {}
|
||||
).add_color_output("resource2", FMT, EXTENT, {})
|
||||
.build();
|
||||
|
||||
auto task3 = lft::rg::render_task<EmptyContext>(
|
||||
"task3", &ctx,
|
||||
[](const lft::rg::TaskBuildInfo& info, EmptyContext* ctx) {},
|
||||
[](const lft::rg::TaskRecordInfo& info, EmptyContext* ctx) {}
|
||||
).add_dependency("resource1")
|
||||
.add_dependency("resource2")
|
||||
.add_color_output("output", FMT, EXTENT, {})
|
||||
.build();
|
||||
|
||||
lft::rg::Builder builder1(
|
||||
gpu.get(), image_chain, "output"
|
||||
);
|
||||
|
||||
builder1.add_task(task1);
|
||||
builder1.add_task(task2);
|
||||
builder1.add_task(task3);
|
||||
|
||||
auto sorted = topology_sort(builder1.m_tasks, "output");
|
||||
|
||||
ASSERT(sorted.size() == 3);
|
||||
ASSERT(sorted[0].name() == "task1" || sorted[0].name() == "task2");
|
||||
ASSERT(sorted[1].name() == "task1" || sorted[1].name() == "task2");
|
||||
ASSERT(sorted[0].name() != sorted[1].name());
|
||||
ASSERT(sorted[2].name() == "task3");
|
||||
}
|
||||
|
||||
// int main() {
|
||||
// test_topological_sort_01();
|
||||
// test_topological_sort_02();
|
||||
// test_topological_sort_03();
|
||||
//
|
||||
// return 0;
|
||||
// }
|
||||
@@ -0,0 +1,5 @@
|
||||
|
||||
|
||||
int main() {
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user