feat: Implement render graph support in Vulkan backend

- Added RecordRenderGraph method to IRenderCommandService and its implementation in RenderCommandService.
- Updated VulkanGraphicsBackend to handle render graph definitions and record commands accordingly.
- Introduced GetDepthFormat method in ISwapchainService to retrieve depth buffer format.
- Enhanced VulkanGraphicsBackend with methods to set render graph definitions and manage render graph resources.
- Added RenderGraphImage structure to manage render targets and depth targets within the render graph.
- Updated interfaces and services to accommodate new render graph functionality, including descriptor set layout retrieval in IPipelineService.
This commit is contained in:
2026-01-06 02:02:52 +00:00
parent b11a2f9e30
commit fb71265a5b
15 changed files with 1349 additions and 9 deletions

View File

@@ -237,6 +237,125 @@ local function resolve_color3(value, fallback)
return {fallback[1], fallback[2], fallback[3]}
end
local function resolve_number_optional(value)
if type(value) == "number" then
return value
end
return nil
end
local function resolve_color3_optional(value)
if type(value) == "table" then
local r = tonumber(value[1])
local g = tonumber(value[2])
local b = tonumber(value[3])
if r and g and b then
return {r, g, b}
end
end
return nil
end
local function format_optional_number(value)
if type(value) == "number" then
return string_format("%.3f", value)
end
return "default"
end
local function format_optional_color(value)
if type(value) == "table"
and type(value[1]) == "number"
and type(value[2]) == "number"
and type(value[3]) == "number" then
return string_format("{%.2f, %.2f, %.2f}", value[1], value[2], value[3])
end
return "default"
end
local function build_shader_parameter_overrides()
if type(config) ~= "table" or type(config.atmospherics) ~= "table" then
return {}
end
local atmospherics = config.atmospherics
local ambient_strength = resolve_number_optional(atmospherics.ambient_strength)
local light_intensity = resolve_number_optional(atmospherics.light_intensity)
local key_intensity = resolve_number_optional(atmospherics.key_light_intensity)
local fill_intensity = resolve_number_optional(atmospherics.fill_light_intensity)
local light_color = resolve_color3_optional(atmospherics.light_color)
local pbr_roughness = resolve_number_optional(atmospherics.pbr_roughness)
local pbr_metallic = resolve_number_optional(atmospherics.pbr_metallic)
local parameters = {}
local function apply_common(key)
if ambient_strength == nil and light_intensity == nil and light_color == nil then
return
end
local entry = {}
if ambient_strength ~= nil then
entry.ambient_strength = ambient_strength
end
if light_intensity ~= nil then
entry.light_intensity = light_intensity
end
if light_color ~= nil then
entry.light_color = light_color
end
parameters[key] = entry
end
apply_common("solid")
apply_common("floor")
apply_common("wall")
apply_common("ceiling")
if ambient_strength ~= nil or key_intensity ~= nil or fill_intensity ~= nil or light_intensity ~= nil then
local entry = {}
if ambient_strength ~= nil then
entry.ambient_strength = ambient_strength
end
if key_intensity ~= nil then
entry.key_intensity = key_intensity
elseif light_intensity ~= nil then
entry.key_intensity = light_intensity
end
if fill_intensity ~= nil then
entry.fill_intensity = fill_intensity
elseif light_intensity ~= nil then
entry.fill_intensity = light_intensity * 0.45
end
parameters.default = entry
end
if light_intensity ~= nil or light_color ~= nil or pbr_roughness ~= nil or pbr_metallic ~= nil then
local entry = {}
if light_intensity ~= nil then
entry.light_intensity = light_intensity
end
if light_color ~= nil then
entry.light_color = light_color
end
if pbr_roughness ~= nil then
entry.material_roughness = pbr_roughness
end
if pbr_metallic ~= nil then
entry.material_metallic = pbr_metallic
end
parameters.pbr = entry
end
if next(parameters) ~= nil then
log_debug("Shader lighting overrides: ambient=%s light_intensity=%s light_color=%s",
format_optional_number(ambient_strength),
format_optional_number(light_intensity),
format_optional_color(light_color))
end
return parameters
end
local function apply_skybox_color_from_config()
if type(config) ~= "table" then
return
@@ -335,6 +454,7 @@ end
local function build_shader_variants()
apply_skybox_color_from_config()
local shader_parameters = build_shader_parameter_overrides()
local ok, toolkit = pcall(require, "shader_toolkit")
if not ok then
@@ -345,7 +465,7 @@ local function build_shader_variants()
local output_mode = "source"
local compile = false
local ok_generate, generated = pcall(toolkit.generate_cube_demo_variants,
{compile = compile, output_mode = output_mode})
{compile = compile, output_mode = output_mode, parameters = shader_parameters})
if not ok_generate then
log_debug("Shader generation failed: %s", tostring(generated))
return build_static_shader_variants()

View File

@@ -112,6 +112,9 @@ void GraphicsService::SetRenderGraphDefinition(const RenderGraphDefinition& defi
", passes=" + std::to_string(definition.passes.size()));
renderGraphDefinition_ = definition;
if (backend_) {
backend_->SetRenderGraphDefinition(definition);
}
}
const RenderGraphDefinition& GraphicsService::GetRenderGraphDefinition() const {

View File

@@ -463,6 +463,10 @@ void GxmGraphicsBackend::SetViewProjection(const std::array<float, 16>& viewProj
// Matrix will be set when drawing with specific pipeline
}
void GxmGraphicsBackend::SetRenderGraphDefinition(const RenderGraphDefinition& definition) {
(void)definition;
}
void GxmGraphicsBackend::Draw(GraphicsDeviceHandle device, GraphicsPipelineHandle pipeline,
GraphicsBufferHandle vertexBuffer, GraphicsBufferHandle indexBuffer,
uint32_t indexCount, const std::array<float, 16>& modelMatrix) {

View File

@@ -37,6 +37,7 @@ public:
bool EndFrame(GraphicsDeviceHandle device) override;
void SetViewProjection(const std::array<float, 16>& viewProj) override;
void SetRenderGraphDefinition(const RenderGraphDefinition& definition) override;
void Draw(GraphicsDeviceHandle device, GraphicsPipelineHandle pipeline,
GraphicsBufferHandle vertexBuffer, GraphicsBufferHandle indexBuffer,

View File

@@ -105,6 +105,10 @@ void PipelineService::Cleanup() {
vkDestroyPipelineLayout(device, pipelineLayout_, nullptr);
pipelineLayout_ = VK_NULL_HANDLE;
}
if (descriptorSetLayout_ != VK_NULL_HANDLE) {
vkDestroyDescriptorSetLayout(device, descriptorSetLayout_, nullptr);
descriptorSetLayout_ = VK_NULL_HANDLE;
}
}
void PipelineService::Shutdown() noexcept {
@@ -131,21 +135,42 @@ void PipelineService::CreatePipelineLayout() {
auto device = deviceService_->GetDevice();
if (descriptorSetLayout_ != VK_NULL_HANDLE) {
vkDestroyDescriptorSetLayout(device, descriptorSetLayout_, nullptr);
descriptorSetLayout_ = VK_NULL_HANDLE;
}
if (pipelineLayout_ != VK_NULL_HANDLE) {
vkDestroyPipelineLayout(device, pipelineLayout_, nullptr);
pipelineLayout_ = VK_NULL_HANDLE;
}
VkDescriptorSetLayoutBinding samplerBinding{};
samplerBinding.binding = 0;
samplerBinding.descriptorCount = 1;
samplerBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
samplerBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
VkDescriptorSetLayoutCreateInfo setLayoutInfo{};
setLayoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
setLayoutInfo.bindingCount = 1;
setLayoutInfo.pBindings = &samplerBinding;
if (vkCreateDescriptorSetLayout(device, &setLayoutInfo, nullptr, &descriptorSetLayout_) != VK_SUCCESS) {
throw std::runtime_error("Failed to create descriptor set layout");
}
VkPushConstantRange pushRange{};
pushRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
pushRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
pushRange.offset = 0;
pushRange.size = sizeof(core::PushConstants);
VkPipelineLayoutCreateInfo pipelineLayoutInfo{};
pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
pipelineLayoutInfo.setLayoutCount = 1;
pipelineLayoutInfo.pSetLayouts = &descriptorSetLayout_;
pipelineLayoutInfo.pushConstantRangeCount = 1;
pipelineLayoutInfo.pPushConstantRanges = &pushRange;
if (pipelineLayout_ != VK_NULL_HANDLE) {
vkDestroyPipelineLayout(device, pipelineLayout_, nullptr);
pipelineLayout_ = VK_NULL_HANDLE;
}
if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout_) != VK_SUCCESS) {
throw std::runtime_error("Failed to create pipeline layout");
}

View File

@@ -33,6 +33,10 @@ public:
logger_->Trace("PipelineService", "GetPipelineLayout");
return pipelineLayout_;
}
VkDescriptorSetLayout GetDescriptorSetLayout() const override {
logger_->Trace("PipelineService", "GetDescriptorSetLayout");
return descriptorSetLayout_;
}
bool HasShader(const std::string& key) const override;
size_t GetShaderCount() const override {
logger_->Trace("PipelineService", "GetShaderCount");
@@ -47,6 +51,7 @@ private:
std::shared_ptr<ILogger> logger_;
VkPipelineLayout pipelineLayout_ = VK_NULL_HANDLE;
VkDescriptorSetLayout descriptorSetLayout_ = VK_NULL_HANDLE;
std::unordered_map<std::string, ShaderPaths> shaderPathMap_;
std::unordered_map<std::string, VkPipeline> pipelines_;

File diff suppressed because it is too large Load Diff

View File

@@ -11,6 +11,7 @@
#include "../interfaces/i_logger.hpp"
#include "../../di/lifecycle.hpp"
#include <memory>
#include <unordered_map>
#include <vector>
namespace sdl3cpp::services::impl {
@@ -40,6 +41,10 @@ public:
void RecordCommands(uint32_t imageIndex,
const std::vector<RenderCommand>& commands,
const std::array<float, 16>& viewProj) override;
void RecordRenderGraph(uint32_t imageIndex,
const RenderGraphDefinition& graph,
const std::vector<RenderCommand>& commands,
const std::array<float, 16>& viewProj) override;
bool EndFrame(uint32_t imageIndex) override;
VkCommandBuffer GetCurrentCommandBuffer() const override;
@@ -57,6 +62,16 @@ public:
void Shutdown() noexcept override;
private:
struct RenderGraphImage {
VkImage image = VK_NULL_HANDLE;
VkDeviceMemory memory = VK_NULL_HANDLE;
VkImageView view = VK_NULL_HANDLE;
VkFramebuffer framebuffer = VK_NULL_HANDLE;
VkFormat format = VK_FORMAT_UNDEFINED;
VkExtent2D extent{};
VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED;
};
std::shared_ptr<IVulkanDeviceService> deviceService_;
std::shared_ptr<ISwapchainService> swapchainService_;
std::shared_ptr<IPipelineService> pipelineService_;
@@ -73,6 +88,22 @@ private:
VkSemaphore renderFinishedSemaphore_ = VK_NULL_HANDLE;
VkFence inFlightFence_ = VK_NULL_HANDLE;
VkDescriptorPool descriptorPool_ = VK_NULL_HANDLE;
VkDescriptorSet defaultDescriptorSet_ = VK_NULL_HANDLE;
VkDescriptorSet postProcessDescriptorSet_ = VK_NULL_HANDLE;
VkSampler sampler_ = VK_NULL_HANDLE;
VkImage dummyImage_ = VK_NULL_HANDLE;
VkDeviceMemory dummyImageMemory_ = VK_NULL_HANDLE;
VkImageView dummyImageView_ = VK_NULL_HANDLE;
VkImageLayout dummyImageLayout_ = VK_IMAGE_LAYOUT_UNDEFINED;
VkRenderPass offscreenRenderPass_ = VK_NULL_HANDLE;
std::unordered_map<std::string, RenderGraphImage> renderGraphTargets_;
std::unordered_map<std::string, RenderGraphImage> renderGraphDepth_;
VkExtent2D renderGraphExtent_{};
size_t renderGraphResourceCount_ = 0;
size_t renderGraphPassCount_ = 0;
uint32_t currentFrame_ = 0;
uint32_t maxFramesInFlight_ = 1; // Single frame in flight for simplicity
@@ -82,6 +113,33 @@ private:
void CreateSyncObjects();
void CleanupCommandResources();
void CleanupSyncObjects();
void EnsureDescriptorResources();
void CleanupDescriptorResources();
void CreateDescriptorPool();
void CreateSampler();
void CreateDummyImage();
void AllocateDescriptorSets();
void UpdateDescriptorSet(VkDescriptorSet set, VkImageView view);
void EnsureDummyImageLayout(VkCommandBuffer commandBuffer);
void EnsureRenderGraphResources(const RenderGraphDefinition& graph);
void CleanupRenderGraphResources();
void RegisterRenderGraphShaders(const RenderGraphDefinition& graph);
VkFormat ResolveColorFormat(const std::string& format) const;
VkFormat ResolveDepthFormat(const std::string& format) const;
VkExtent2D ResolveExtent(const RenderGraphResource& resource) const;
RenderGraphImage* FindRenderTarget(const std::string& name);
RenderGraphImage* FindDepthTarget(const std::string& name);
void CreateRenderGraphImage(RenderGraphImage& image, VkFormat format, VkExtent2D extent,
VkImageUsageFlags usage, VkImageAspectFlags aspectMask);
void TransitionImageLayout(VkCommandBuffer commandBuffer, RenderGraphImage& image,
VkImageLayout newLayout, VkImageAspectFlags aspectMask);
bool IsScenePass(const RenderGraphPass& pass) const;
bool IsPassEnabled(const RenderGraphPass& pass) const;
std::string ResolvePassOutput(const RenderGraphPass& pass) const;
std::string ResolvePassInput(const RenderGraphPass& pass) const;
core::PushConstants BuildFullscreenConstants(const RenderGraphPass& pass) const;
};
} // namespace sdl3cpp::services::impl

View File

@@ -54,6 +54,10 @@ public:
logger_->Trace("SwapchainService", "GetSwapchainImageFormat");
return imageFormat_;
}
VkFormat GetDepthFormat() const override {
logger_->Trace("SwapchainService", "GetDepthFormat");
return depthFormat_;
}
VkExtent2D GetSwapchainExtent() const override {
logger_->Trace("SwapchainService", "GetSwapchainExtent");
return extent_;

View File

@@ -220,7 +220,12 @@ bool VulkanGraphicsBackend::EndFrame(GraphicsDeviceHandle device) {
logger_->Trace("VulkanGraphicsBackend", "EndFrame");
// Record all accumulated commands
renderCommandService_->RecordCommands(currentImageIndex_, frameCommands_, currentViewProj_);
if (renderGraphEnabled_) {
renderCommandService_->RecordRenderGraph(currentImageIndex_, renderGraphDefinition_,
frameCommands_, currentViewProj_);
} else {
renderCommandService_->RecordCommands(currentImageIndex_, frameCommands_, currentViewProj_);
}
// End the frame
return renderCommandService_->EndFrame(currentImageIndex_);
@@ -231,6 +236,14 @@ void VulkanGraphicsBackend::SetViewProjection(const std::array<float, 16>& viewP
currentViewProj_ = viewProj;
}
void VulkanGraphicsBackend::SetRenderGraphDefinition(const RenderGraphDefinition& definition) {
logger_->Trace("VulkanGraphicsBackend", "SetRenderGraphDefinition",
"resources=" + std::to_string(definition.resources.size()) +
", passes=" + std::to_string(definition.passes.size()));
renderGraphDefinition_ = definition;
renderGraphEnabled_ = !definition.passes.empty();
}
void VulkanGraphicsBackend::Draw(GraphicsDeviceHandle device, GraphicsPipelineHandle pipeline,
GraphicsBufferHandle vertexBuffer, GraphicsBufferHandle indexBuffer,
uint32_t indexCount, const std::array<float, 16>& modelMatrix) {

View File

@@ -48,6 +48,7 @@ public:
bool EndFrame(GraphicsDeviceHandle device) override;
void SetViewProjection(const std::array<float, 16>& viewProj) override;
void SetRenderGraphDefinition(const RenderGraphDefinition& definition) override;
void Draw(GraphicsDeviceHandle device, GraphicsPipelineHandle pipeline,
GraphicsBufferHandle vertexBuffer, GraphicsBufferHandle indexBuffer,
@@ -72,6 +73,8 @@ private:
uint32_t currentImageIndex_;
std::vector<RenderCommand> frameCommands_;
std::array<float, 16> currentViewProj_;
RenderGraphDefinition renderGraphDefinition_{};
bool renderGraphEnabled_ = false;
std::unordered_map<GraphicsPipelineHandle, std::string> pipelineToShaderKey_;
};

View File

@@ -146,6 +146,13 @@ public:
*/
virtual void SetViewProjection(const std::array<float, 16>& viewProj) = 0;
/**
* @brief Set the render graph definition (optional).
*
* @param definition Render graph definition
*/
virtual void SetRenderGraphDefinition(const RenderGraphDefinition& definition) = 0;
/**
* @brief Draw with a pipeline.
*

View File

@@ -66,6 +66,13 @@ public:
*/
virtual VkPipelineLayout GetPipelineLayout() const = 0;
/**
* @brief Get the descriptor set layout used by pipelines.
*
* @return VkDescriptorSetLayout handle
*/
virtual VkDescriptorSetLayout GetDescriptorSetLayout() const = 0;
/**
* @brief Check if a shader exists.
*

View File

@@ -45,6 +45,19 @@ public:
const std::vector<RenderCommand>& commands,
const std::array<float, 16>& viewProj) = 0;
/**
* @brief Record rendering commands using a render graph.
*
* @param imageIndex Swapchain image index
* @param graph Render graph definition
* @param commands Render commands to execute for scene passes
* @param viewProj View-projection matrix
*/
virtual void RecordRenderGraph(uint32_t imageIndex,
const RenderGraphDefinition& graph,
const std::vector<RenderCommand>& commands,
const std::array<float, 16>& viewProj) = 0;
/**
* @brief End the frame and present.
*

View File

@@ -104,6 +104,13 @@ public:
*/
virtual VkFormat GetSwapchainImageFormat() const = 0;
/**
* @brief Get the depth buffer format.
*
* @return VkFormat of depth images
*/
virtual VkFormat GetDepthFormat() const = 0;
/**
* @brief Get the swapchain extent (size).
*