This commit is contained in:
xsl
2025-09-04 10:54:47 +08:00
commit 6bc8f61b18
1808 changed files with 208268 additions and 0 deletions
+190
View File
@@ -0,0 +1,190 @@
/* Copyright (c) 2023-2025, NVIDIA CORPORATION. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 the "License";
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "pipeline_state.h"
#include <vulkan/vulkan.hpp>
namespace vkb
{
namespace core
{
class HPPPipelineLayout;
class HPPRenderPass;
} // namespace core
namespace rendering
{
struct HPPColorBlendAttachmentState
{
vk::Bool32 blend_enable = false;
vk::BlendFactor src_color_blend_factor = vk::BlendFactor::eOne;
vk::BlendFactor dst_color_blend_factor = vk::BlendFactor::eZero;
vk::BlendOp color_blend_op = vk::BlendOp::eAdd;
vk::BlendFactor src_alpha_blend_factor = vk::BlendFactor::eOne;
vk::BlendFactor dst_alpha_blend_factor = vk::BlendFactor::eZero;
vk::BlendOp alpha_blend_op = vk::BlendOp::eAdd;
vk::ColorComponentFlags color_write_mask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA;
};
struct HPPColorBlendState
{
vk::Bool32 logic_op_enable = false;
vk::LogicOp logic_op = vk::LogicOp::eClear;
std::vector<HPPColorBlendAttachmentState> attachments;
};
struct HPPStencilOpState
{
vk::StencilOp fail_op = vk::StencilOp::eReplace;
vk::StencilOp pass_op = vk::StencilOp::eReplace;
vk::StencilOp depth_fail_op = vk::StencilOp::eReplace;
vk::CompareOp compare_op = vk::CompareOp::eNever;
};
struct HPPDepthStencilState
{
vk::Bool32 depth_test_enable = true;
vk::Bool32 depth_write_enable = true;
vk::CompareOp depth_compare_op = vk::CompareOp::eGreater; // Note: Using reversed depth-buffer for increased precision, so Greater depth values are kept
vk::Bool32 depth_bounds_test_enable = false;
vk::Bool32 stencil_test_enable = false;
HPPStencilOpState front;
HPPStencilOpState back;
};
struct HPPInputAssemblyState
{
vk::PrimitiveTopology topology = vk::PrimitiveTopology::eTriangleList;
vk::Bool32 primitive_restart_enable = false;
};
struct HPPMultisampleState
{
vk::SampleCountFlagBits rasterization_samples = vk::SampleCountFlagBits::e1;
vk::Bool32 sample_shading_enable = false;
float min_sample_shading = 0.0f;
vk::SampleMask sample_mask = 0;
vk::Bool32 alpha_to_coverage_enable = false;
vk::Bool32 alpha_to_one_enable = false;
};
struct HPPRasterizationState
{
vk::Bool32 depth_clamp_enable = false;
vk::Bool32 rasterizer_discard_enable = false;
vk::PolygonMode polygon_mode = vk::PolygonMode::eFill;
vk::CullModeFlags cull_mode = vk::CullModeFlagBits::eBack;
vk::FrontFace front_face = vk::FrontFace::eCounterClockwise;
vk::Bool32 depth_bias_enable = false;
};
class HPPSpecializationConstantState : private vkb::SpecializationConstantState
{};
struct HPPVertexInputState
{
std::vector<vk::VertexInputBindingDescription> bindings;
std::vector<vk::VertexInputAttributeDescription> attributes;
};
struct HPPViewportState
{
uint32_t viewport_count = 1;
uint32_t scissor_count = 1;
};
class HPPPipelineState : private vkb::PipelineState
{
public:
using vkb::PipelineState::clear_dirty;
using vkb::PipelineState::get_subpass_index;
using vkb::PipelineState::is_dirty;
using vkb::PipelineState::reset;
using vkb::PipelineState::set_specialization_constant;
using vkb::PipelineState::set_subpass_index;
public:
const vkb::rendering::HPPColorBlendState &get_color_blend_state() const
{
return reinterpret_cast<vkb::rendering::HPPColorBlendState const &>(vkb::PipelineState::get_color_blend_state());
}
const vkb::core::HPPPipelineLayout &get_pipeline_layout() const
{
return reinterpret_cast<vkb::core::HPPPipelineLayout const &>(vkb::PipelineState::get_pipeline_layout());
}
const vkb::core::HPPRenderPass *get_render_pass() const
{
return reinterpret_cast<vkb::core::HPPRenderPass const *>(vkb::PipelineState::get_render_pass());
}
const vkb::rendering::HPPSpecializationConstantState &get_specialization_constant_state() const
{
return reinterpret_cast<vkb::rendering::HPPSpecializationConstantState const &>(vkb::PipelineState::get_specialization_constant_state());
}
void set_color_blend_state(const vkb::rendering::HPPColorBlendState &color_blend_state)
{
vkb::PipelineState::set_color_blend_state(reinterpret_cast<vkb::ColorBlendState const &>(color_blend_state));
}
void set_depth_stencil_state(const vkb::rendering::HPPDepthStencilState &depth_stencil_state)
{
vkb::PipelineState::set_depth_stencil_state(reinterpret_cast<vkb::DepthStencilState const &>(depth_stencil_state));
}
void set_input_assembly_state(const vkb::rendering::HPPInputAssemblyState &input_assembly_state)
{
vkb::PipelineState::set_input_assembly_state(reinterpret_cast<vkb::InputAssemblyState const &>(input_assembly_state));
}
void set_multisample_state(const vkb::rendering::HPPMultisampleState &multisample_state)
{
vkb::PipelineState::set_multisample_state(reinterpret_cast<vkb::MultisampleState const &>(multisample_state));
}
void set_pipeline_layout(vkb::core::HPPPipelineLayout &pipeline_layout)
{
vkb::PipelineState::set_pipeline_layout(reinterpret_cast<vkb::PipelineLayout &>(pipeline_layout));
}
void set_rasterization_state(const vkb::rendering::HPPRasterizationState &rasterization_state)
{
vkb::PipelineState::set_rasterization_state(reinterpret_cast<vkb::RasterizationState const &>(rasterization_state));
}
void set_render_pass(const vkb::core::HPPRenderPass &render_pass)
{
vkb::PipelineState::set_render_pass(reinterpret_cast<vkb::RenderPass const &>(render_pass));
}
void set_vertex_input_state(const vkb::rendering::HPPVertexInputState &vertex_input_state)
{
vkb::PipelineState::set_vertex_input_state(reinterpret_cast<vkb::VertexInputState const &>(vertex_input_state));
}
void set_viewport_state(const vkb::rendering::HPPViewportState &viewport_state)
{
vkb::PipelineState::set_viewport_state(reinterpret_cast<vkb::ViewportState const &>(viewport_state));
}
};
} // namespace rendering
} // namespace vkb
+522
View File
@@ -0,0 +1,522 @@
/* Copyright (c) 2023-2025, NVIDIA CORPORATION. All rights reserved.
* Copyright (c) 2024-2025, Arm Limited and Contributors
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 the "License";
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "rendering/hpp_render_context.h"
#include "buffer_pool.h"
#include "core/command_buffer.h"
#include "core/hpp_physical_device.h"
#include "core/hpp_queue.h"
#include "platform/window.h"
namespace vkb
{
namespace rendering
{
vk::Format HPPRenderContext::DEFAULT_VK_FORMAT = vk::Format::eR8G8B8A8Srgb;
HPPRenderContext::HPPRenderContext(vkb::core::DeviceCpp &device,
vk::SurfaceKHR surface,
const vkb::Window &window,
vk::PresentModeKHR present_mode,
std::vector<vk::PresentModeKHR> const &present_mode_priority_list,
std::vector<vk::SurfaceFormatKHR> const &surface_format_priority_list) :
device{device}, window{window}, queue{device.get_queue_by_flags(vk::QueueFlagBits::eGraphics, 0)}, surface_extent{window.get_extent().width, window.get_extent().height}
{
if (surface)
{
vk::SurfaceCapabilitiesKHR surface_properties = device.get_gpu().get_handle().getSurfaceCapabilitiesKHR(surface);
if (surface_properties.currentExtent.width == 0xFFFFFFFF)
{
swapchain =
std::make_unique<vkb::core::HPPSwapchain>(device, surface, present_mode, present_mode_priority_list, surface_format_priority_list, surface_extent);
}
else
{
swapchain = std::make_unique<vkb::core::HPPSwapchain>(device, surface, present_mode, present_mode_priority_list, surface_format_priority_list);
}
}
}
void HPPRenderContext::prepare(size_t thread_count, vkb::rendering::HPPRenderTarget::CreateFunc create_render_target_func)
{
device.get_handle().waitIdle();
if (swapchain)
{
surface_extent = swapchain->get_extent();
vk::Extent3D extent{surface_extent.width, surface_extent.height, 1};
for (auto &image_handle : swapchain->get_images())
{
auto swapchain_image = core::HPPImage{device, image_handle, extent, swapchain->get_format(), swapchain->get_usage()};
auto render_target = create_render_target_func(std::move(swapchain_image));
frames.emplace_back(std::make_unique<vkb::rendering::RenderFrameCpp>(device, std::move(render_target), thread_count));
}
}
else
{
// Otherwise, create a single RenderFrame
swapchain = nullptr;
auto color_image = vkb::core::HPPImage{device,
vk::Extent3D{surface_extent.width, surface_extent.height, 1},
DEFAULT_VK_FORMAT, // We can use any format here that we like
vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eTransferSrc,
VMA_MEMORY_USAGE_GPU_ONLY};
auto render_target = create_render_target_func(std::move(color_image));
frames.emplace_back(std::make_unique<vkb::rendering::RenderFrameCpp>(device, std::move(render_target), thread_count));
}
this->create_render_target_func = create_render_target_func;
this->thread_count = thread_count;
this->prepared = true;
}
vk::Format HPPRenderContext::get_format() const
{
return swapchain ? swapchain->get_format() : DEFAULT_VK_FORMAT;
}
void HPPRenderContext::update_swapchain(const vk::Extent2D &extent)
{
if (!swapchain)
{
LOGW("Can't update the swapchains extent. No swapchain, offscreen rendering detected, skipping.");
return;
}
device.get_resource_cache().clear_framebuffers();
swapchain = std::make_unique<vkb::core::HPPSwapchain>(*swapchain, extent);
recreate();
}
void HPPRenderContext::update_swapchain(const uint32_t image_count)
{
if (!swapchain)
{
LOGW("Can't update the swapchains image count. No swapchain, offscreen rendering detected, skipping.");
return;
}
device.get_resource_cache().clear_framebuffers();
device.get_handle().waitIdle();
swapchain = std::make_unique<vkb::core::HPPSwapchain>(*swapchain, image_count);
recreate();
}
void HPPRenderContext::update_swapchain(const std::set<vk::ImageUsageFlagBits> &image_usage_flags)
{
if (!swapchain)
{
LOGW("Can't update the swapchains image usage. No swapchain, offscreen rendering detected, skipping.");
return;
}
device.get_resource_cache().clear_framebuffers();
swapchain = std::make_unique<vkb::core::HPPSwapchain>(*swapchain, image_usage_flags);
recreate();
}
void HPPRenderContext::update_swapchain(const vk::Extent2D &extent, const vk::SurfaceTransformFlagBitsKHR transform)
{
if (!swapchain)
{
LOGW("Can't update the swapchains extent and surface transform. No swapchain, offscreen rendering detected, skipping.");
return;
}
device.get_resource_cache().clear_framebuffers();
auto width = extent.width;
auto height = extent.height;
if (transform == vk::SurfaceTransformFlagBitsKHR::eRotate90 || transform == vk::SurfaceTransformFlagBitsKHR::eRotate270)
{
// Pre-rotation: always use native orientation i.e. if rotated, use width and height of identity transform
std::swap(width, height);
}
swapchain = std::make_unique<vkb::core::HPPSwapchain>(*swapchain, vk::Extent2D{width, height}, transform);
// Save the preTransform attribute for future rotations
pre_transform = transform;
recreate();
}
void HPPRenderContext::recreate()
{
LOGI("Recreated swapchain");
vk::Extent2D swapchain_extent = swapchain->get_extent();
vk::Extent3D extent{swapchain_extent.width, swapchain_extent.height, 1};
auto frame_it = frames.begin();
for (auto &image_handle : swapchain->get_images())
{
vkb::core::HPPImage swapchain_image{device, image_handle, extent, swapchain->get_format(), swapchain->get_usage()};
auto render_target = create_render_target_func(std::move(swapchain_image));
if (frame_it != frames.end())
{
(*frame_it)->update_render_target(std::move(render_target));
}
else
{
// Create a new frame if the new swapchain has more images than current frames
frames.emplace_back(std::make_unique<vkb::rendering::RenderFrameCpp>(device, std::move(render_target), thread_count));
}
++frame_it;
}
device.get_resource_cache().clear_framebuffers();
}
bool HPPRenderContext::handle_surface_changes(bool force_update)
{
if (!swapchain)
{
LOGW("Can't handle surface changes. No swapchain, offscreen rendering detected, skipping.");
return false;
}
vk::SurfaceCapabilitiesKHR surface_properties = device.get_gpu().get_handle().getSurfaceCapabilitiesKHR(swapchain->get_surface());
if (surface_properties.currentExtent.width == 0xFFFFFFFF)
{
return false;
}
// Only recreate the swapchain if the dimensions have changed;
// handle_surface_changes() is called on VK_SUBOPTIMAL_KHR,
// which might not be due to a surface resize
if (surface_properties.currentExtent.width != surface_extent.width ||
surface_properties.currentExtent.height != surface_extent.height ||
force_update)
{
// Recreate swapchain
device.get_handle().waitIdle();
update_swapchain(surface_properties.currentExtent, pre_transform);
surface_extent = surface_properties.currentExtent;
return true;
}
return false;
}
std::shared_ptr<vkb::core::CommandBufferCpp> HPPRenderContext::begin(vkb::CommandBufferResetMode reset_mode)
{
assert(prepared && "HPPRenderContext not prepared for rendering, call prepare()");
if (!frame_active)
{
begin_frame();
}
if (!acquired_semaphore)
{
throw std::runtime_error("Couldn't begin frame");
}
const auto &queue = device.get_queue_by_flags(vk::QueueFlagBits::eGraphics, 0);
return get_active_frame().get_command_pool(queue, reset_mode).request_command_buffer();
}
void HPPRenderContext::submit(vkb::core::CommandBufferCpp &command_buffer)
{
submit({&command_buffer});
}
void HPPRenderContext::submit(const std::vector<vkb::core::CommandBufferCpp *> &command_buffers)
{
assert(frame_active && "HPPRenderContext is inactive, cannot submit command buffer. Please call begin()");
vk::Semaphore render_semaphore;
if (swapchain)
{
assert(acquired_semaphore && "We do not have acquired_semaphore, it was probably consumed?\n");
render_semaphore = submit(queue, command_buffers, acquired_semaphore, vk::PipelineStageFlagBits::eColorAttachmentOutput);
}
else
{
submit(queue, command_buffers);
}
end_frame(render_semaphore);
}
void HPPRenderContext::begin_frame()
{
// Only handle surface changes if a swapchain exists
if (swapchain)
{
handle_surface_changes();
}
assert(!frame_active && "Frame is still active, please call end_frame");
auto &prev_frame = *frames[active_frame_index];
// We will use the acquired semaphore in a different frame context,
// so we need to hold ownership.
acquired_semaphore = prev_frame.get_semaphore_pool().request_semaphore_with_ownership();
if (swapchain)
{
vk::Result result;
try
{
std::tie(result, active_frame_index) = swapchain->acquire_next_image(acquired_semaphore);
}
catch (vk::OutOfDateKHRError & /*err*/)
{
result = vk::Result::eErrorOutOfDateKHR;
}
if (result == vk::Result::eSuboptimalKHR || result == vk::Result::eErrorOutOfDateKHR)
{
#if defined(PLATFORM__MACOS)
// On Apple platforms, force swapchain update on both eSuboptimalKHR and eErrorOutOfDateKHR
// eSuboptimalKHR may occur on macOS/iOS following changes to swapchain other than extent/size
bool swapchain_updated = handle_surface_changes(true);
#else
bool swapchain_updated = handle_surface_changes(result == vk::Result::eErrorOutOfDateKHR);
#endif
if (swapchain_updated)
{
// Need to destroy and reallocate acquired_semaphore since it may have already been signaled
device.get_handle().destroySemaphore(acquired_semaphore);
acquired_semaphore = prev_frame.get_semaphore_pool().request_semaphore_with_ownership();
std::tie(result, active_frame_index) = swapchain->acquire_next_image(acquired_semaphore);
}
}
if (result != vk::Result::eSuccess)
{
prev_frame.reset();
return;
}
}
// Now the frame is active again
frame_active = true;
// Wait on all resource to be freed from the previous render to this frame
wait_frame();
}
vk::Semaphore HPPRenderContext::submit(const vkb::core::HPPQueue &queue,
const std::vector<vkb::core::CommandBufferCpp *> &command_buffers,
vk::Semaphore wait_semaphore,
vk::PipelineStageFlags wait_pipeline_stage)
{
std::vector<vk::CommandBuffer> cmd_buf_handles(command_buffers.size(), nullptr);
std::ranges::transform(command_buffers, cmd_buf_handles.begin(), [](const vkb::core::CommandBufferCpp *cmd_buf) { return cmd_buf->get_handle(); });
vkb::rendering::RenderFrameCpp &frame = get_active_frame();
vk::Semaphore signal_semaphore = frame.get_semaphore_pool().request_semaphore();
vk::SubmitInfo submit_info{.commandBufferCount = static_cast<uint32_t>(cmd_buf_handles.size()),
.pCommandBuffers = cmd_buf_handles.data(),
.signalSemaphoreCount = 1,
.pSignalSemaphores = &signal_semaphore};
if (wait_semaphore)
{
submit_info.setWaitSemaphores(wait_semaphore);
submit_info.pWaitDstStageMask = &wait_pipeline_stage;
}
vk::Fence fence = frame.get_fence_pool().request_fence();
queue.get_handle().submit(submit_info, fence);
return signal_semaphore;
}
void HPPRenderContext::submit(const vkb::core::HPPQueue &queue, const std::vector<vkb::core::CommandBufferCpp *> &command_buffers)
{
std::vector<vk::CommandBuffer> cmd_buf_handles(command_buffers.size(), nullptr);
std::ranges::transform(command_buffers, cmd_buf_handles.begin(), [](const vkb::core::CommandBufferCpp *cmd_buf) { return cmd_buf->get_handle(); });
vkb::rendering::RenderFrameCpp &frame = get_active_frame();
vk::SubmitInfo submit_info{.commandBufferCount = static_cast<uint32_t>(cmd_buf_handles.size()), .pCommandBuffers = cmd_buf_handles.data()};
vk::Fence fence = frame.get_fence_pool().request_fence();
queue.get_handle().submit(submit_info, fence);
}
void HPPRenderContext::wait_frame()
{
get_active_frame().reset();
}
void HPPRenderContext::end_frame(vk::Semaphore semaphore)
{
assert(frame_active && "Frame is not active, please call begin_frame");
if (swapchain)
{
vk::SwapchainKHR vk_swapchain = swapchain->get_handle();
vk::PresentInfoKHR present_info{
.waitSemaphoreCount = 1, .pWaitSemaphores = &semaphore, .swapchainCount = 1, .pSwapchains = &vk_swapchain, .pImageIndices = &active_frame_index};
vk::DisplayPresentInfoKHR disp_present_info;
if (device.get_gpu().is_extension_supported(VK_KHR_DISPLAY_SWAPCHAIN_EXTENSION_NAME) &&
window.get_display_present_info(reinterpret_cast<VkDisplayPresentInfoKHR *>(&disp_present_info), surface_extent.width, surface_extent.height))
{
// Add display present info if supported and wanted
present_info.pNext = &disp_present_info;
}
vk::Result result;
try
{
result = queue.present(present_info);
}
catch (vk::OutOfDateKHRError & /*err*/)
{
result = vk::Result::eErrorOutOfDateKHR;
}
if (result == vk::Result::eSuboptimalKHR || result == vk::Result::eErrorOutOfDateKHR)
{
handle_surface_changes();
}
}
// Frame is not active anymore
if (acquired_semaphore)
{
release_owned_semaphore(acquired_semaphore);
acquired_semaphore = nullptr;
}
frame_active = false;
}
vk::Semaphore HPPRenderContext::consume_acquired_semaphore()
{
assert(frame_active && "Frame is not active, please call begin_frame");
return std::exchange(acquired_semaphore, nullptr);
}
vkb::rendering::RenderFrameCpp &HPPRenderContext::get_active_frame()
{
assert(frame_active && "Frame is not active, please call begin_frame");
return *frames[active_frame_index];
}
uint32_t HPPRenderContext::get_active_frame_index()
{
assert(frame_active && "Frame is not active, please call begin_frame");
return active_frame_index;
}
vkb::rendering::RenderFrameCpp &HPPRenderContext::get_last_rendered_frame()
{
assert(!frame_active && "Frame is still active, please call end_frame");
return *frames[active_frame_index];
}
vk::Semaphore HPPRenderContext::request_semaphore()
{
return get_active_frame().get_semaphore_pool().request_semaphore();
}
vk::Semaphore HPPRenderContext::request_semaphore_with_ownership()
{
return get_active_frame().get_semaphore_pool().request_semaphore_with_ownership();
}
void HPPRenderContext::release_owned_semaphore(vk::Semaphore semaphore)
{
get_active_frame().get_semaphore_pool().release_owned_semaphore(semaphore);
}
vkb::core::DeviceCpp &HPPRenderContext::get_device()
{
return device;
}
void HPPRenderContext::recreate_swapchain()
{
device.get_handle().waitIdle();
device.get_resource_cache().clear_framebuffers();
vk::Extent2D swapchain_extent = swapchain->get_extent();
vk::Extent3D extent{swapchain_extent.width, swapchain_extent.height, 1};
auto frame_it = frames.begin();
for (auto &image_handle : swapchain->get_images())
{
vkb::core::HPPImage swapchain_image{device, image_handle, extent, swapchain->get_format(), swapchain->get_usage()};
auto render_target = create_render_target_func(std::move(swapchain_image));
(*frame_it)->update_render_target(std::move(render_target));
++frame_it;
}
}
bool HPPRenderContext::has_swapchain()
{
return swapchain != nullptr;
}
vkb::core::HPPSwapchain const &HPPRenderContext::get_swapchain() const
{
assert(swapchain && "Swapchain is not valid");
return *swapchain;
}
vk::Extent2D const &HPPRenderContext::get_surface_extent() const
{
return surface_extent;
}
uint32_t HPPRenderContext::get_active_frame_index() const
{
return active_frame_index;
}
std::vector<std::unique_ptr<vkb::rendering::RenderFrameCpp>> &HPPRenderContext::get_render_frames()
{
return frames;
}
} // namespace rendering
} // namespace vkb
+254
View File
@@ -0,0 +1,254 @@
/* Copyright (c) 2022-2025, NVIDIA CORPORATION. All rights reserved.
* Copyright (c) 2024-2025, Arm Limited and Contributors
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 the "License";
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "common/vk_common.h"
#include "core/hpp_swapchain.h"
#include "rendering/hpp_render_target.h"
#include "rendering/render_frame.h"
namespace vkb
{
class Window;
namespace core
{
template <vkb::BindingType bindingType>
class CommandBuffer;
using CommandBufferCpp = CommandBuffer<vkb::BindingType::Cpp>;
class HPPQueue;
} // namespace core
namespace rendering
{
/**
* @brief HPPRenderContext is a transcoded version of vkb::RenderContext from vulkan to vulkan-hpp.
*
* See vkb::RenderContext for documentation
*/
class HPPRenderContext
{
public:
// The format to use for the RenderTargets if a swapchain isn't created
static vk::Format DEFAULT_VK_FORMAT;
/**
* @brief Constructor
* @param device A valid device
* @param surface A surface, nullptr if in offscreen mode
* @param window The window where the surface was created
* @param present_mode Requests to set the present mode of the swapchain
* @param present_mode_priority_list The order in which the swapchain prioritizes selecting its present mode
* @param surface_format_priority_list The order in which the swapchain prioritizes selecting its surface format
*/
HPPRenderContext(vkb::core::DeviceCpp &device,
vk::SurfaceKHR surface,
const vkb::Window &window,
vk::PresentModeKHR present_mode = vk::PresentModeKHR::eFifo,
std::vector<vk::PresentModeKHR> const &present_mode_priority_list = {vk::PresentModeKHR::eFifo, vk::PresentModeKHR::eMailbox},
std::vector<vk::SurfaceFormatKHR> const &surface_format_priority_list = {
{vk::Format::eR8G8B8A8Srgb, vk::ColorSpaceKHR::eSrgbNonlinear}, {vk::Format::eB8G8R8A8Srgb, vk::ColorSpaceKHR::eSrgbNonlinear}});
HPPRenderContext(const HPPRenderContext &) = delete;
HPPRenderContext(HPPRenderContext &&) = delete;
virtual ~HPPRenderContext() = default;
HPPRenderContext &operator=(const HPPRenderContext &) = delete;
HPPRenderContext &operator=(HPPRenderContext &&) = delete;
/**
* @brief Prepares the RenderFrames for rendering
* @param thread_count The number of threads in the application, necessary to allocate this many resource pools for each RenderFrame
* @param create_render_target_func A function delegate, used to create a RenderTarget
*/
void prepare(size_t thread_count = 1, HPPRenderTarget::CreateFunc create_render_target_func = HPPRenderTarget::DEFAULT_CREATE_FUNC);
/**
* @brief Updates the swapchains extent, if a swapchain exists
* @param extent The width and height of the new swapchain images
*/
void update_swapchain(const vk::Extent2D &extent);
/**
* @brief Updates the swapchains image count, if a swapchain exists
* @param image_count The amount of images in the new swapchain
*/
void update_swapchain(const uint32_t image_count);
/**
* @brief Updates the swapchains image usage, if a swapchain exists
* @param image_usage_flags The usage flags the new swapchain images will have
*/
void update_swapchain(const std::set<vk::ImageUsageFlagBits> &image_usage_flags);
/**
* @brief Updates the swapchains extent and surface transform, if a swapchain exists
* @param extent The width and height of the new swapchain images
* @param transform The surface transform flags
*/
void update_swapchain(const vk::Extent2D &extent, const vk::SurfaceTransformFlagBitsKHR transform);
/**
* @returns True if a valid swapchain exists in the HPPRenderContext
*/
bool has_swapchain();
/**
* @brief Recreates the RenderFrames, called after every update
*/
void recreate();
/**
* @brief Recreates the swapchain
*/
void recreate_swapchain();
/**
* @brief Prepares the next available frame for rendering
* @param reset_mode How to reset the command buffer
* @returns A valid command buffer to record commands to be submitted
* Also ensures that there is an active frame if there is no existing active frame already
*/
std::shared_ptr<vkb::core::CommandBufferCpp> begin(vkb::CommandBufferResetMode reset_mode = vkb::CommandBufferResetMode::ResetPool);
/**
* @brief Submits the command buffer to the right queue
* @param command_buffer A command buffer containing recorded commands
*/
void submit(vkb::core::CommandBufferCpp &command_buffer);
/**
* @brief Submits multiple command buffers to the right queue
* @param command_buffers Command buffers containing recorded commands
*/
void submit(const std::vector<vkb::core::CommandBufferCpp *> &command_buffers);
/**
* @brief begin_frame
*/
void begin_frame();
vk::Semaphore submit(const vkb::core::HPPQueue &queue,
const std::vector<vkb::core::CommandBufferCpp *> &command_buffers,
vk::Semaphore wait_semaphore,
vk::PipelineStageFlags wait_pipeline_stage);
/**
* @brief Submits a command buffer related to a frame to a queue
*/
void submit(const vkb::core::HPPQueue &queue, const std::vector<vkb::core::CommandBufferCpp *> &command_buffers);
/**
* @brief Waits a frame to finish its rendering
*/
virtual void wait_frame();
void end_frame(vk::Semaphore semaphore);
/**
* @brief An error should be raised if the frame is not active.
* A frame is active after @ref begin_frame has been called.
* @return The current active frame
*/
vkb::rendering::RenderFrameCpp &get_active_frame();
/**
* @brief An error should be raised if the frame is not active.
* A frame is active after @ref begin_frame has been called.
* @return The current active frame index
*/
uint32_t get_active_frame_index();
/**
* @brief An error should be raised if a frame is active.
* A frame is active after @ref begin_frame has been called.
* @return The previous frame
*/
vkb::rendering::RenderFrameCpp &get_last_rendered_frame();
vk::Semaphore request_semaphore();
vk::Semaphore request_semaphore_with_ownership();
void release_owned_semaphore(vk::Semaphore semaphore);
vkb::core::DeviceCpp &get_device();
/**
* @brief Returns the format that the RenderTargets are created with within the HPPRenderContext
*/
vk::Format get_format() const;
vkb::core::HPPSwapchain const &get_swapchain() const;
vk::Extent2D const &get_surface_extent() const;
uint32_t get_active_frame_index() const;
std::vector<std::unique_ptr<vkb::rendering::RenderFrameCpp>> &get_render_frames();
/**
* @brief Handles surface changes, only applicable if the render_context makes use of a swapchain
*/
virtual bool handle_surface_changes(bool force_update = false);
/**
* @brief Returns the WSI acquire semaphore. Only to be used in very special circumstances.
* @return The WSI acquire semaphore.
*/
vk::Semaphore consume_acquired_semaphore();
protected:
vk::Extent2D surface_extent;
private:
vkb::core::DeviceCpp &device;
const vkb::Window &window;
/// If swapchain exists, then this will be a present supported queue, else a graphics queue
const vkb::core::HPPQueue &queue;
std::unique_ptr<vkb::core::HPPSwapchain> swapchain;
vkb::core::HPPSwapchainProperties swapchain_properties;
std::vector<std::unique_ptr<vkb::rendering::RenderFrameCpp>> frames;
vk::Semaphore acquired_semaphore;
bool prepared{false};
/// Current active frame index
uint32_t active_frame_index{0};
/// Whether a frame is active or not
bool frame_active{false};
HPPRenderTarget::CreateFunc create_render_target_func = HPPRenderTarget::DEFAULT_CREATE_FUNC;
vk::SurfaceTransformFlagBitsKHR pre_transform{vk::SurfaceTransformFlagBitsKHR::eIdentity};
size_t thread_count{1};
};
} // namespace rendering
} // namespace vkb
+51
View File
@@ -0,0 +1,51 @@
/* Copyright (c) 2021-2025, NVIDIA CORPORATION. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 the "License";
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <rendering/render_pipeline.h>
#include <rendering/subpasses/hpp_forward_subpass.h>
namespace vkb
{
namespace rendering
{
/**
* @brief facade class around vkb::RenderPipeline, providing a vulkan.hpp-based interface
*
* See vkb::RenderPipeline for documentation
*/
class HPPRenderPipeline : private vkb::RenderPipeline
{
public:
void add_subpass(std::unique_ptr<vkb::rendering::subpasses::HPPForwardSubpass> &&subpass)
{
vkb::RenderPipeline::add_subpass(std::move(subpass));
}
void draw(vkb::core::CommandBufferCpp &command_buffer,
vkb::rendering::HPPRenderTarget &render_target,
vk::SubpassContents contents = vk::SubpassContents::eInline)
{
vkb::RenderPipeline::draw(reinterpret_cast<vkb::core::CommandBufferC &>(command_buffer),
reinterpret_cast<vkb::RenderTarget &>(render_target),
static_cast<VkSubpassContents>(contents));
}
};
} // namespace rendering
} // namespace vkb
+151
View File
@@ -0,0 +1,151 @@
/* Copyright (c) 2022-2025, NVIDIA CORPORATION. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 the "License";
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "rendering/hpp_render_target.h"
#include "common/hpp_vk_common.h"
#include "core/device.h"
#include "core/hpp_image_view.h"
#include "core/hpp_physical_device.h"
namespace vkb
{
namespace rendering
{
const HPPRenderTarget::CreateFunc HPPRenderTarget::DEFAULT_CREATE_FUNC = [](core::HPPImage &&swapchain_image) -> std::unique_ptr<HPPRenderTarget> {
vk::Format depth_format = common::get_suitable_depth_format(swapchain_image.get_device().get_gpu().get_handle());
core::HPPImage depth_image{swapchain_image.get_device(), swapchain_image.get_extent(),
depth_format,
vk::ImageUsageFlagBits::eDepthStencilAttachment | vk::ImageUsageFlagBits::eTransientAttachment,
VMA_MEMORY_USAGE_GPU_ONLY};
std::vector<core::HPPImage> images;
images.push_back(std::move(swapchain_image));
images.push_back(std::move(depth_image));
return std::make_unique<HPPRenderTarget>(std::move(images));
};
HPPRenderTarget::HPPRenderTarget(std::vector<core::HPPImage> &&images_) :
device{images_.back().get_device()},
images{std::move(images_)}
{
assert(!images.empty() && "Should specify at least 1 image");
// check that every image is 2D
auto it = std::ranges::find_if(images, [](core::HPPImage const &image) { return image.get_type() != vk::ImageType::e2D; });
if (it != images.end())
{
throw VulkanException{VK_ERROR_INITIALIZATION_FAILED, "Image type is not 2D"};
}
extent.width = images.front().get_extent().width;
extent.height = images.front().get_extent().height;
// check that every image has the same extent
it = std::find_if(std::next(images.begin()),
images.end(),
[this](core::HPPImage const &image) { return (extent.width != image.get_extent().width) || (extent.height != image.get_extent().height); });
if (it != images.end())
{
throw VulkanException{VK_ERROR_INITIALIZATION_FAILED, "Extent size is not unique"};
}
for (auto &image : images)
{
views.emplace_back(image, vk::ImageViewType::e2D);
attachments.emplace_back(HPPAttachment{image.get_format(), image.get_sample_count(), image.get_usage()});
}
}
HPPRenderTarget::HPPRenderTarget(std::vector<core::HPPImageView> &&image_views) :
device{image_views.back().get_image().get_device()},
views{std::move(image_views)}
{
assert(!views.empty() && "Should specify at least 1 image view");
const uint32_t mip_level = views.front().get_subresource_range().baseMipLevel;
extent.width = views.front().get_image().get_extent().width >> mip_level;
extent.height = views.front().get_image().get_extent().height >> mip_level;
// check that every image view has the same extent
auto it = std::find_if(std::next(views.begin()),
views.end(),
[this](core::HPPImageView const &image_view) {
const uint32_t mip_level = image_view.get_subresource_range().baseMipLevel;
return (extent.width != image_view.get_image().get_extent().width >> mip_level) ||
(extent.height != image_view.get_image().get_extent().height >> mip_level);
});
if (it != views.end())
{
throw VulkanException{VK_ERROR_INITIALIZATION_FAILED, "Extent size is not unique"};
}
for (auto &view : views)
{
const auto &image = view.get_image();
attachments.emplace_back(HPPAttachment{image.get_format(), image.get_sample_count(), image.get_usage()});
}
}
const vk::Extent2D &HPPRenderTarget::get_extent() const
{
return extent;
}
const std::vector<core::HPPImageView> &HPPRenderTarget::get_views() const
{
return views;
}
const std::vector<HPPAttachment> &HPPRenderTarget::get_attachments() const
{
return attachments;
}
void HPPRenderTarget::set_input_attachments(std::vector<uint32_t> &input)
{
input_attachments = input;
}
const std::vector<uint32_t> &HPPRenderTarget::get_input_attachments() const
{
return input_attachments;
}
void HPPRenderTarget::set_output_attachments(std::vector<uint32_t> &output)
{
output_attachments = output;
}
const std::vector<uint32_t> &HPPRenderTarget::get_output_attachments() const
{
return output_attachments;
}
void HPPRenderTarget::set_layout(uint32_t attachment, vk::ImageLayout layout)
{
attachments[attachment].initial_layout = layout;
}
vk::ImageLayout HPPRenderTarget::get_layout(uint32_t attachment) const
{
return attachments[attachment].initial_layout;
}
} // namespace rendering
} // namespace vkb
+110
View File
@@ -0,0 +1,110 @@
/* Copyright (c) 2021-2025, NVIDIA CORPORATION. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 the "License";
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "core/hpp_image.h"
#include "core/hpp_image_view.h"
#include <functional>
#include <memory>
namespace vkb
{
namespace core
{
class HPPDevice;
}
namespace rendering
{
/**
* @brief Description of render pass attachments.
* HPPAttachment descriptions can be used to automatically create render target images.
*/
struct HPPAttachment
{
HPPAttachment() = default;
HPPAttachment(vk::Format format, vk::SampleCountFlagBits samples, vk::ImageUsageFlags usage) :
format{format},
samples{samples},
usage{usage}
{}
vk::Format format = vk::Format::eUndefined;
vk::SampleCountFlagBits samples = vk::SampleCountFlagBits::e1;
vk::ImageUsageFlags usage = vk::ImageUsageFlagBits::eSampled;
vk::ImageLayout initial_layout = vk::ImageLayout::eUndefined;
};
/**
* @brief HPPRenderContext is a transcoded version of vkb::RenderContext from vulkan to vulkan-hpp.
*
* See vkb::RenderContext for documentation
*/
class HPPRenderTarget
{
public:
using CreateFunc = std::function<std::unique_ptr<HPPRenderTarget>(core::HPPImage &&)>;
static const CreateFunc DEFAULT_CREATE_FUNC;
HPPRenderTarget(std::vector<core::HPPImage> &&images);
HPPRenderTarget(std::vector<core::HPPImageView> &&image_views);
HPPRenderTarget(const HPPRenderTarget &) = delete;
HPPRenderTarget(HPPRenderTarget &&) = delete;
HPPRenderTarget &operator=(const HPPRenderTarget &other) noexcept = delete;
HPPRenderTarget &operator=(HPPRenderTarget &&other) noexcept = delete;
const vk::Extent2D &get_extent() const;
const std::vector<core::HPPImageView> &get_views() const;
const std::vector<HPPAttachment> &get_attachments() const;
/**
* @brief Sets the current input attachments overwriting the current ones
* Should be set before beginning the render pass and before starting a new subpass
* @param input Set of attachment reference number to use as input
*/
void set_input_attachments(std::vector<uint32_t> &input);
const std::vector<uint32_t> &get_input_attachments() const;
/**
* @brief Sets the current output attachments overwriting the current ones
* Should be set before beginning the render pass and before starting a new subpass
* @param output Set of attachment reference number to use as output
*/
void set_output_attachments(std::vector<uint32_t> &output);
const std::vector<uint32_t> &get_output_attachments() const;
void set_layout(uint32_t attachment, vk::ImageLayout layout);
vk::ImageLayout get_layout(uint32_t attachment) const;
private:
vkb::core::DeviceCpp const &device;
vk::Extent2D extent;
std::vector<core::HPPImage> images;
std::vector<core::HPPImageView> views;
std::vector<HPPAttachment> attachments;
std::vector<uint32_t> input_attachments = {}; // By default there are no input attachments
std::vector<uint32_t> output_attachments = {0}; // By default the output attachments is attachment 0
};
} // namespace rendering
} // namespace vkb
+351
View File
@@ -0,0 +1,351 @@
/* Copyright (c) 2019-2025, Arm Limited and Contributors
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 the "License";
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "pipeline_state.h"
#include "core/render_pass.h"
bool operator==(const VkVertexInputAttributeDescription &lhs, const VkVertexInputAttributeDescription &rhs)
{
return std::tie(lhs.binding, lhs.format, lhs.location, lhs.offset) == std::tie(rhs.binding, rhs.format, rhs.location, rhs.offset);
}
bool operator==(const VkVertexInputBindingDescription &lhs, const VkVertexInputBindingDescription &rhs)
{
return std::tie(lhs.binding, lhs.inputRate, lhs.stride) == std::tie(rhs.binding, rhs.inputRate, rhs.stride);
}
bool operator==(const vkb::ColorBlendAttachmentState &lhs, const vkb::ColorBlendAttachmentState &rhs)
{
return std::tie(lhs.alpha_blend_op, lhs.blend_enable, lhs.color_blend_op, lhs.color_write_mask, lhs.dst_alpha_blend_factor, lhs.dst_color_blend_factor, lhs.src_alpha_blend_factor, lhs.src_color_blend_factor) ==
std::tie(rhs.alpha_blend_op, rhs.blend_enable, rhs.color_blend_op, rhs.color_write_mask, rhs.dst_alpha_blend_factor, rhs.dst_color_blend_factor, rhs.src_alpha_blend_factor, rhs.src_color_blend_factor);
}
bool operator!=(const vkb::StencilOpState &lhs, const vkb::StencilOpState &rhs)
{
return std::tie(lhs.compare_op, lhs.depth_fail_op, lhs.fail_op, lhs.pass_op) != std::tie(rhs.compare_op, rhs.depth_fail_op, rhs.fail_op, rhs.pass_op);
}
bool operator!=(const vkb::VertexInputState &lhs, const vkb::VertexInputState &rhs)
{
return lhs.attributes != rhs.attributes || lhs.bindings != rhs.bindings;
}
bool operator!=(const vkb::InputAssemblyState &lhs, const vkb::InputAssemblyState &rhs)
{
return std::tie(lhs.primitive_restart_enable, lhs.topology) != std::tie(rhs.primitive_restart_enable, rhs.topology);
}
bool operator!=(const vkb::RasterizationState &lhs, const vkb::RasterizationState &rhs)
{
return std::tie(lhs.cull_mode, lhs.depth_bias_enable, lhs.depth_clamp_enable, lhs.front_face, lhs.front_face, lhs.polygon_mode, lhs.rasterizer_discard_enable) !=
std::tie(rhs.cull_mode, rhs.depth_bias_enable, rhs.depth_clamp_enable, rhs.front_face, rhs.front_face, rhs.polygon_mode, rhs.rasterizer_discard_enable);
}
bool operator!=(const vkb::ViewportState &lhs, const vkb::ViewportState &rhs)
{
return lhs.viewport_count != rhs.viewport_count || lhs.scissor_count != rhs.scissor_count;
}
bool operator!=(const vkb::MultisampleState &lhs, const vkb::MultisampleState &rhs)
{
return std::tie(lhs.alpha_to_coverage_enable, lhs.alpha_to_one_enable, lhs.min_sample_shading, lhs.rasterization_samples, lhs.sample_mask, lhs.sample_shading_enable) !=
std::tie(rhs.alpha_to_coverage_enable, rhs.alpha_to_one_enable, rhs.min_sample_shading, rhs.rasterization_samples, rhs.sample_mask, rhs.sample_shading_enable);
}
bool operator!=(const vkb::DepthStencilState &lhs, const vkb::DepthStencilState &rhs)
{
return std::tie(lhs.depth_bounds_test_enable, lhs.depth_compare_op, lhs.depth_test_enable, lhs.depth_write_enable, lhs.stencil_test_enable) !=
std::tie(rhs.depth_bounds_test_enable, rhs.depth_compare_op, rhs.depth_test_enable, rhs.depth_write_enable, rhs.stencil_test_enable) ||
lhs.back != rhs.back || lhs.front != rhs.front;
}
bool operator!=(const vkb::ColorBlendState &lhs, const vkb::ColorBlendState &rhs)
{
return std::tie(lhs.logic_op, lhs.logic_op_enable) != std::tie(rhs.logic_op, rhs.logic_op_enable) ||
lhs.attachments.size() != rhs.attachments.size() ||
!std::equal(lhs.attachments.begin(), lhs.attachments.end(), rhs.attachments.begin(),
[](const vkb::ColorBlendAttachmentState &lhs, const vkb::ColorBlendAttachmentState &rhs) {
return lhs == rhs;
});
}
namespace vkb
{
void SpecializationConstantState::reset()
{
if (dirty)
{
specialization_constant_state.clear();
}
dirty = false;
}
bool SpecializationConstantState::is_dirty() const
{
return dirty;
}
void SpecializationConstantState::clear_dirty()
{
dirty = false;
}
void SpecializationConstantState::set_constant(uint32_t constant_id, const std::vector<uint8_t> &value)
{
auto data = specialization_constant_state.find(constant_id);
if (data != specialization_constant_state.end() && data->second == value)
{
return;
}
dirty = true;
specialization_constant_state[constant_id] = value;
}
void SpecializationConstantState::set_specialization_constant_state(const std::map<uint32_t, std::vector<uint8_t>> &state)
{
specialization_constant_state = state;
}
const std::map<uint32_t, std::vector<uint8_t>> &SpecializationConstantState::get_specialization_constant_state() const
{
return specialization_constant_state;
}
void PipelineState::reset()
{
clear_dirty();
pipeline_layout = nullptr;
render_pass = nullptr;
specialization_constant_state.reset();
vertex_input_state = {};
input_assembly_state = {};
rasterization_state = {};
multisample_state = {};
depth_stencil_state = {};
color_blend_state = {};
subpass_index = {0U};
}
void PipelineState::set_pipeline_layout(PipelineLayout &new_pipeline_layout)
{
if (pipeline_layout)
{
if (pipeline_layout->get_handle() != new_pipeline_layout.get_handle())
{
pipeline_layout = &new_pipeline_layout;
dirty = true;
}
}
else
{
pipeline_layout = &new_pipeline_layout;
dirty = true;
}
}
void PipelineState::set_render_pass(const RenderPass &new_render_pass)
{
if (render_pass)
{
if (render_pass->get_handle() != new_render_pass.get_handle())
{
render_pass = &new_render_pass;
dirty = true;
}
}
else
{
render_pass = &new_render_pass;
dirty = true;
}
}
void PipelineState::set_specialization_constant(uint32_t constant_id, const std::vector<uint8_t> &data)
{
specialization_constant_state.set_constant(constant_id, data);
if (specialization_constant_state.is_dirty())
{
dirty = true;
}
}
void PipelineState::set_vertex_input_state(const VertexInputState &new_vertex_input_state)
{
if (vertex_input_state != new_vertex_input_state)
{
vertex_input_state = new_vertex_input_state;
dirty = true;
}
}
void PipelineState::set_input_assembly_state(const InputAssemblyState &new_input_assembly_state)
{
if (input_assembly_state != new_input_assembly_state)
{
input_assembly_state = new_input_assembly_state;
dirty = true;
}
}
void PipelineState::set_rasterization_state(const RasterizationState &new_rasterization_state)
{
if (rasterization_state != new_rasterization_state)
{
rasterization_state = new_rasterization_state;
dirty = true;
}
}
void PipelineState::set_viewport_state(const ViewportState &new_viewport_state)
{
if (viewport_state != new_viewport_state)
{
viewport_state = new_viewport_state;
dirty = true;
}
}
void PipelineState::set_multisample_state(const MultisampleState &new_multisample_state)
{
if (multisample_state != new_multisample_state)
{
multisample_state = new_multisample_state;
dirty = true;
}
}
void PipelineState::set_depth_stencil_state(const DepthStencilState &new_depth_stencil_state)
{
if (depth_stencil_state != new_depth_stencil_state)
{
depth_stencil_state = new_depth_stencil_state;
dirty = true;
}
}
void PipelineState::set_color_blend_state(const ColorBlendState &new_color_blend_state)
{
if (color_blend_state != new_color_blend_state)
{
color_blend_state = new_color_blend_state;
dirty = true;
}
}
void PipelineState::set_subpass_index(uint32_t new_subpass_index)
{
if (subpass_index != new_subpass_index)
{
subpass_index = new_subpass_index;
dirty = true;
}
}
const PipelineLayout &PipelineState::get_pipeline_layout() const
{
assert(pipeline_layout && "Graphics state Pipeline layout is not set");
return *pipeline_layout;
}
const RenderPass *PipelineState::get_render_pass() const
{
return render_pass;
}
const SpecializationConstantState &PipelineState::get_specialization_constant_state() const
{
return specialization_constant_state;
}
const VertexInputState &PipelineState::get_vertex_input_state() const
{
return vertex_input_state;
}
const InputAssemblyState &PipelineState::get_input_assembly_state() const
{
return input_assembly_state;
}
const RasterizationState &PipelineState::get_rasterization_state() const
{
return rasterization_state;
}
const ViewportState &PipelineState::get_viewport_state() const
{
return viewport_state;
}
const MultisampleState &PipelineState::get_multisample_state() const
{
return multisample_state;
}
const DepthStencilState &PipelineState::get_depth_stencil_state() const
{
return depth_stencil_state;
}
const ColorBlendState &PipelineState::get_color_blend_state() const
{
return color_blend_state;
}
uint32_t PipelineState::get_subpass_index() const
{
return subpass_index;
}
bool PipelineState::is_dirty() const
{
return dirty || specialization_constant_state.is_dirty();
}
void PipelineState::clear_dirty()
{
dirty = false;
specialization_constant_state.clear_dirty();
}
} // namespace vkb
+252
View File
@@ -0,0 +1,252 @@
/* Copyright (c) 2019-2025, Arm Limited and Contributors
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 the "License";
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <vector>
#include "common/vk_common.h"
#include "core/pipeline_layout.h"
namespace vkb
{
class RenderPass;
struct VertexInputState
{
std::vector<VkVertexInputBindingDescription> bindings;
std::vector<VkVertexInputAttributeDescription> attributes;
};
struct InputAssemblyState
{
VkPrimitiveTopology topology{VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST};
VkBool32 primitive_restart_enable{VK_FALSE};
};
struct RasterizationState
{
VkBool32 depth_clamp_enable{VK_FALSE};
VkBool32 rasterizer_discard_enable{VK_FALSE};
VkPolygonMode polygon_mode{VK_POLYGON_MODE_FILL};
VkCullModeFlags cull_mode{VK_CULL_MODE_BACK_BIT};
VkFrontFace front_face{VK_FRONT_FACE_COUNTER_CLOCKWISE};
VkBool32 depth_bias_enable{VK_FALSE};
};
struct ViewportState
{
uint32_t viewport_count{1};
uint32_t scissor_count{1};
};
struct MultisampleState
{
VkSampleCountFlagBits rasterization_samples{VK_SAMPLE_COUNT_1_BIT};
VkBool32 sample_shading_enable{VK_FALSE};
float min_sample_shading{0.0f};
VkSampleMask sample_mask{0};
VkBool32 alpha_to_coverage_enable{VK_FALSE};
VkBool32 alpha_to_one_enable{VK_FALSE};
};
struct StencilOpState
{
VkStencilOp fail_op{VK_STENCIL_OP_REPLACE};
VkStencilOp pass_op{VK_STENCIL_OP_REPLACE};
VkStencilOp depth_fail_op{VK_STENCIL_OP_REPLACE};
VkCompareOp compare_op{VK_COMPARE_OP_NEVER};
};
struct DepthStencilState
{
VkBool32 depth_test_enable{VK_TRUE};
VkBool32 depth_write_enable{VK_TRUE};
// Note: Using reversed depth-buffer for increased precision, so Greater depth values are kept
VkCompareOp depth_compare_op{VK_COMPARE_OP_GREATER};
VkBool32 depth_bounds_test_enable{VK_FALSE};
VkBool32 stencil_test_enable{VK_FALSE};
StencilOpState front{};
StencilOpState back{};
};
struct ColorBlendAttachmentState
{
VkBool32 blend_enable{VK_FALSE};
VkBlendFactor src_color_blend_factor{VK_BLEND_FACTOR_ONE};
VkBlendFactor dst_color_blend_factor{VK_BLEND_FACTOR_ZERO};
VkBlendOp color_blend_op{VK_BLEND_OP_ADD};
VkBlendFactor src_alpha_blend_factor{VK_BLEND_FACTOR_ONE};
VkBlendFactor dst_alpha_blend_factor{VK_BLEND_FACTOR_ZERO};
VkBlendOp alpha_blend_op{VK_BLEND_OP_ADD};
VkColorComponentFlags color_write_mask{VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT};
};
struct ColorBlendState
{
VkBool32 logic_op_enable{VK_FALSE};
VkLogicOp logic_op{VK_LOGIC_OP_CLEAR};
std::vector<ColorBlendAttachmentState> attachments;
};
/// Helper class to create specialization constants for a Vulkan pipeline. The state tracks a pipeline globally, and not per shader. Two shaders using the same constant_id will have the same data.
class SpecializationConstantState
{
public:
void reset();
bool is_dirty() const;
void clear_dirty();
template <class T>
void set_constant(uint32_t constant_id, const T &data);
void set_constant(uint32_t constant_id, const std::vector<uint8_t> &data);
void set_specialization_constant_state(const std::map<uint32_t, std::vector<uint8_t>> &state);
const std::map<uint32_t, std::vector<uint8_t>> &get_specialization_constant_state() const;
private:
bool dirty{false};
// Map tracking state of the Specialization Constants
std::map<uint32_t, std::vector<uint8_t>> specialization_constant_state;
};
template <class T>
inline void SpecializationConstantState::set_constant(std::uint32_t constant_id, const T &data)
{
set_constant(constant_id, to_bytes(static_cast<std::uint32_t>(data)));
}
template <>
inline void SpecializationConstantState::set_constant<bool>(std::uint32_t constant_id, const bool &data)
{
set_constant(constant_id, to_bytes(static_cast<std::uint32_t>(data)));
}
class PipelineState
{
public:
void reset();
void set_pipeline_layout(PipelineLayout &pipeline_layout);
void set_render_pass(const RenderPass &render_pass);
void set_specialization_constant(uint32_t constant_id, const std::vector<uint8_t> &data);
void set_vertex_input_state(const VertexInputState &vertex_input_state);
void set_input_assembly_state(const InputAssemblyState &input_assembly_state);
void set_rasterization_state(const RasterizationState &rasterization_state);
void set_viewport_state(const ViewportState &viewport_state);
void set_multisample_state(const MultisampleState &multisample_state);
void set_depth_stencil_state(const DepthStencilState &depth_stencil_state);
void set_color_blend_state(const ColorBlendState &color_blend_state);
void set_subpass_index(uint32_t subpass_index);
const PipelineLayout &get_pipeline_layout() const;
const RenderPass *get_render_pass() const;
const SpecializationConstantState &get_specialization_constant_state() const;
const VertexInputState &get_vertex_input_state() const;
const InputAssemblyState &get_input_assembly_state() const;
const RasterizationState &get_rasterization_state() const;
const ViewportState &get_viewport_state() const;
const MultisampleState &get_multisample_state() const;
const DepthStencilState &get_depth_stencil_state() const;
const ColorBlendState &get_color_blend_state() const;
uint32_t get_subpass_index() const;
bool is_dirty() const;
void clear_dirty();
private:
bool dirty{false};
PipelineLayout *pipeline_layout{nullptr};
const RenderPass *render_pass{nullptr};
SpecializationConstantState specialization_constant_state{};
VertexInputState vertex_input_state{};
InputAssemblyState input_assembly_state{};
RasterizationState rasterization_state{};
ViewportState viewport_state{};
MultisampleState multisample_state{};
DepthStencilState depth_stencil_state{};
ColorBlendState color_blend_state{};
uint32_t subpass_index{0U};
};
} // namespace vkb
@@ -0,0 +1,278 @@
/* Copyright (c) 2020-2025, Arm Limited and Contributors
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 the "License";
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "postprocessing_computepass.h"
#include "postprocessing_pipeline.h"
namespace vkb
{
PostProcessingComputePass::PostProcessingComputePass(PostProcessingPipeline *parent, const ShaderSource &cs_source, const ShaderVariant &cs_variant,
std::shared_ptr<core::Sampler> &&default_sampler) :
PostProcessingPass{parent},
cs_source{cs_source},
cs_variant{cs_variant},
default_sampler{std::move(default_sampler)}
{
if (this->default_sampler == nullptr)
{
// Setup a sane default sampler if none was passed
VkSamplerCreateInfo sampler_info{VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO};
sampler_info.minFilter = VK_FILTER_LINEAR;
sampler_info.magFilter = VK_FILTER_LINEAR;
sampler_info.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
sampler_info.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
sampler_info.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
sampler_info.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
sampler_info.mipLodBias = 0.0f;
sampler_info.compareOp = VK_COMPARE_OP_NEVER;
sampler_info.minLod = 0.0f;
sampler_info.maxLod = 0.0f;
sampler_info.anisotropyEnable = VK_FALSE;
sampler_info.maxAnisotropy = 0.0f;
sampler_info.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
this->default_sampler = std::make_shared<vkb::core::Sampler>(get_render_context().get_device(), sampler_info);
// Also create a nearest filtering version as a fallback
sampler_info.minFilter = VK_FILTER_NEAREST;
sampler_info.magFilter = VK_FILTER_NEAREST;
this->default_sampler_nearest = std::make_shared<vkb::core::Sampler>(get_render_context().get_device(), sampler_info);
}
}
void PostProcessingComputePass::prepare(vkb::core::CommandBufferC &command_buffer, RenderTarget &default_render_target)
{
// Build the compute shader upfront
auto &resource_cache = get_render_context().get_device().get_resource_cache();
resource_cache.request_shader_module(VK_SHADER_STAGE_COMPUTE_BIT, cs_source, cs_variant);
}
PostProcessingComputePass &PostProcessingComputePass::bind_sampled_image(const std::string &name, core::SampledImage &&new_image)
{
auto it = sampled_images.find(name);
if (it != sampled_images.end())
{
it->second = new_image;
}
else
{
sampled_images.emplace(name, new_image);
}
return *this;
}
PostProcessingComputePass &PostProcessingComputePass::bind_storage_image(const std::string &name, core::SampledImage &&new_image)
{
auto it = storage_images.find(name);
if (it != storage_images.end())
{
it->second = new_image;
}
else
{
storage_images.emplace(name, new_image);
}
return *this;
}
void PostProcessingComputePass::transition_images(vkb::core::CommandBufferC &command_buffer, RenderTarget &default_render_target)
{
BarrierInfo fallback_barrier_src{};
fallback_barrier_src.pipeline_stage = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
fallback_barrier_src.image_read_access = 0; // For UNDEFINED -> STORAGE in first CP
fallback_barrier_src.image_write_access = 0;
const auto prev_pass_barrier_info = get_predecessor_src_barrier_info(fallback_barrier_src);
// Get compute shader from cache
auto &resource_cache = command_buffer.get_device().get_resource_cache();
auto &shader_module = resource_cache.request_shader_module(VK_SHADER_STAGE_COMPUTE_BIT, cs_source, cs_variant);
auto &pipeline_layout = resource_cache.request_pipeline_layout({&shader_module});
for (const auto &sampled : sampled_images)
{
if (const uint32_t *attachment = sampled.second.get_target_attachment())
{
auto *sampled_rt = sampled.second.get_render_target();
if (sampled_rt == nullptr)
{
sampled_rt = &default_render_target;
}
if (sampled_rt->get_layout(*attachment) == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
{
// No-op
continue;
}
vkb::ImageMemoryBarrier barrier;
barrier.old_layout = sampled_rt->get_layout(*attachment);
barrier.new_layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
barrier.src_access_mask = prev_pass_barrier_info.image_write_access;
barrier.dst_access_mask = VK_ACCESS_SHADER_READ_BIT;
barrier.src_stage_mask = prev_pass_barrier_info.pipeline_stage;
barrier.dst_stage_mask = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
assert(*attachment < sampled_rt->get_views().size());
command_buffer.image_memory_barrier(sampled_rt->get_views()[*attachment], barrier);
sampled_rt->set_layout(*attachment, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
}
}
const auto &bindings = pipeline_layout.get_descriptor_set_layout(0);
for (const auto &storage : storage_images)
{
if (const uint32_t *attachment = storage.second.get_target_attachment())
{
auto *storage_rt = storage.second.get_render_target();
if (storage_rt == nullptr)
{
storage_rt = &default_render_target;
}
// A storage image is either readonly or writeonly;
// use shader reflection to figure out which case, then transition
// NOTE: Could add a <name -> readonly?> cache to make this faster?
auto resource = std::find_if(pipeline_layout.get_resources().begin(), pipeline_layout.get_resources().end(),
[&storage](const auto &res) {
return res.set == 0 && res.name == storage.first;
});
if (resource == pipeline_layout.get_resources().end())
{
// No such storage image to bind
continue;
}
const bool readable = !(resource->qualifiers & ShaderResourceQualifiers::NonReadable);
const bool writable = !(resource->qualifiers & ShaderResourceQualifiers::NonReadable);
vkb::ImageMemoryBarrier barrier;
barrier.old_layout = storage_rt->get_layout(*attachment);
barrier.new_layout = (readable && !writable) ? VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL : VK_IMAGE_LAYOUT_GENERAL;
if (storage_rt->get_layout(*attachment) == barrier.new_layout)
{
// No-op
continue;
}
barrier.src_stage_mask = prev_pass_barrier_info.pipeline_stage;
barrier.dst_stage_mask = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
barrier.src_access_mask = prev_pass_barrier_info.image_write_access;
barrier.dst_access_mask = 0;
if (readable)
{
barrier.dst_access_mask |= VK_ACCESS_SHADER_READ_BIT;
}
if (writable)
{
barrier.dst_access_mask |= VK_ACCESS_SHADER_WRITE_BIT;
}
assert(*attachment < storage_rt->get_views().size());
command_buffer.image_memory_barrier(storage_rt->get_views()[*attachment], barrier);
storage_rt->set_layout(*attachment, barrier.new_layout);
}
}
}
void PostProcessingComputePass::draw(vkb::core::CommandBufferC &command_buffer, RenderTarget &default_render_target)
{
transition_images(command_buffer, default_render_target);
// Get compute shader from cache
auto &resource_cache = command_buffer.get_device().get_resource_cache();
auto &shader_module = resource_cache.request_shader_module(VK_SHADER_STAGE_COMPUTE_BIT, cs_source, cs_variant);
// Create pipeline layout and bind it
auto &pipeline_layout = resource_cache.request_pipeline_layout({&shader_module});
command_buffer.bind_pipeline_layout(pipeline_layout);
const auto &bindings = pipeline_layout.get_descriptor_set_layout(0);
// Bind samplers to set = 0, binding = <according to name>
for (const auto &it : sampled_images)
{
if (auto layout_binding = bindings.get_layout_binding(it.first))
{
const auto &view = it.second.get_image_view(default_render_target);
// Get the properties for the image format. We need to check whether a linear sampler is valid.
const VkFormatProperties fmtProps = get_render_context().get_device().get_gpu().get_format_properties(view.get_format());
bool has_linear_filter = (fmtProps.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT);
const auto &sampler = it.second.get_sampler() ? *it.second.get_sampler() :
(has_linear_filter ? *default_sampler : *default_sampler_nearest);
command_buffer.bind_image(view, sampler, 0, layout_binding->binding, 0);
}
}
// Bind storage images to set = 0, binding = <according to name>
for (const auto &it : storage_images)
{
if (auto layout_binding = bindings.get_layout_binding(it.first))
{
const auto &view = it.second.get_image_view(default_render_target);
command_buffer.bind_image(view, 0, layout_binding->binding, 0);
}
}
if (!uniform_data.empty())
{
auto &render_frame = parent->get_render_context().get_active_frame();
uniform_alloc = std::make_unique<BufferAllocationC>(render_frame.allocate_buffer(VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, uniform_data.size()));
uniform_alloc->update(uniform_data);
// Bind buffer to set = 0, binding = 0
command_buffer.bind_buffer(uniform_alloc->get_buffer(), uniform_alloc->get_offset(), uniform_alloc->get_size(), 0, 0, 0);
}
if (!push_constants_data.empty())
{
command_buffer.push_constants(push_constants_data);
}
// Dispatch compute
command_buffer.dispatch(n_workgroups.x, n_workgroups.y, n_workgroups.z);
}
PostProcessingComputePass::BarrierInfo PostProcessingComputePass::get_src_barrier_info() const
{
BarrierInfo info{};
info.pipeline_stage = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
info.image_read_access = VK_ACCESS_SHADER_READ_BIT;
info.image_write_access = VK_ACCESS_SHADER_WRITE_BIT;
return info;
}
PostProcessingComputePass::BarrierInfo PostProcessingComputePass::get_dst_barrier_info() const
{
BarrierInfo info{};
info.pipeline_stage = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
info.image_read_access = VK_ACCESS_SHADER_READ_BIT;
info.image_write_access = VK_ACCESS_SHADER_WRITE_BIT;
return info;
}
} // namespace vkb
@@ -0,0 +1,172 @@
/* Copyright (c) 2020-2025, Arm Limited and Contributors
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 the "License";
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "common/glm_common.h"
#include "core/sampled_image.h"
#include "core/shader_module.h"
#include "postprocessing_pass.h"
namespace vkb
{
/**
* @brief Maps in-shader binding names to the core::SampledImage to bind.
*/
using SampledImageMap = std::unordered_map<std::string, core::SampledImage>;
/**
* @brief A compute pass in a vkb::PostProcessingPipeline.
*/
class PostProcessingComputePass : public PostProcessingPass<PostProcessingComputePass>
{
public:
PostProcessingComputePass(PostProcessingPipeline *parent, const ShaderSource &cs_source, const ShaderVariant &cs_variant = {},
std::shared_ptr<core::Sampler> &&default_sampler = {});
PostProcessingComputePass(const PostProcessingComputePass &to_copy) = delete;
PostProcessingComputePass &operator=(const PostProcessingComputePass &to_copy) = delete;
PostProcessingComputePass(PostProcessingComputePass &&to_move) = default;
PostProcessingComputePass &operator=(PostProcessingComputePass &&to_move) = default;
void prepare(vkb::core::CommandBufferC &command_buffer, RenderTarget &default_render_target) override;
void draw(vkb::core::CommandBufferC &command_buffer, RenderTarget &default_render_target) override;
/**
* @brief Sets the number of workgroups to be dispatched each draw().
*/
inline PostProcessingComputePass &set_dispatch_size(glm::tvec3<uint32_t> new_size)
{
n_workgroups = new_size;
return *this;
}
/**
* @brief Gets the number of workgroups that will be dispatched each draw().
*/
inline glm::tvec3<uint32_t> get_dispatch_size() const
{
return n_workgroups;
}
/**
* @brief Maps the names of samplers in the shader to vkb::core::SampledImage.
* These are given as samplers to the subpass, at set 0; they are bound automatically according to their name.
* @remarks PostProcessingPipeline::get_sampler() is used as the default sampler if none is specified.
* The RenderTarget for the current PostprocessingStep is used if none is specified for attachment images.
*/
inline const SampledImageMap &get_sampled_images() const
{
return sampled_images;
}
/**
* @brief Maps the names of storage images in the shader to vkb::core::SampledImage.
* These are given as image2D / image2DArray / ... to the subpass, at set 0;
* they are bound automatically according to their name.
*/
inline const SampledImageMap &get_storage_images() const
{
return storage_images;
}
/**
* @brief Changes (or adds) the sampled image at name for this step.
* @remarks If no RenderTarget is specifically set for the core::SampledImage,
* it will default to sample in the RenderTarget currently bound for drawing in the parent PostProcessingRenderpass.
* @remarks Images from RenderTarget attachments are automatically transitioned to SHADER_READ_ONLY_OPTIMAL layout if needed.
*/
PostProcessingComputePass &bind_sampled_image(const std::string &name, core::SampledImage &&new_image);
/**
* @brief Changes (or adds) the storage image at name for this step.
* @remarks Images from RenderTarget attachments are automatically transitioned to GENERAL layout if needed.
*/
PostProcessingComputePass &bind_storage_image(const std::string &name, core::SampledImage &&new_image);
/**
* @brief Set the uniform data to be bound at set 0, binding 0.
*/
template <typename T>
inline PostProcessingComputePass &set_uniform_data(const T &data)
{
uniform_data.reserve(sizeof(data));
auto data_ptr = reinterpret_cast<const uint8_t *>(&data);
uniform_data.assign(data_ptr, data_ptr + sizeof(data));
return *this;
}
/**
* @copydoc set_uniform_data(const T&)
*/
inline PostProcessingComputePass &set_uniform_data(const std::vector<uint8_t> &data)
{
uniform_data = data;
return *this;
}
/**
* @brief Set the constants that are pushed before each draw.
*/
template <typename T>
inline PostProcessingComputePass &set_push_constants(const T &data)
{
push_constants_data.reserve(sizeof(data));
auto data_ptr = reinterpret_cast<const uint8_t *>(&data);
push_constants_data.assign(data_ptr, data_ptr + sizeof(data));
return *this;
}
/**
* @copydoc set_push_constants(const T&)
*/
inline PostProcessingComputePass &set_push_constants(const std::vector<uint8_t> &data)
{
push_constants_data = data;
return *this;
}
private:
ShaderSource cs_source;
ShaderVariant cs_variant;
glm::tvec3<uint32_t> n_workgroups{1, 1, 1};
std::shared_ptr<core::Sampler> default_sampler{};
std::shared_ptr<core::Sampler> default_sampler_nearest{};
SampledImageMap sampled_images{};
SampledImageMap storage_images{};
std::vector<uint8_t> uniform_data{};
std::unique_ptr<BufferAllocationC> uniform_alloc{};
std::vector<uint8_t> push_constants_data{};
/**
* @brief Transitions sampled_images (to SHADER_READ_ONLY_OPTIMAL)
* and storage_images (to GENERAL) as appropriate.
*/
void transition_images(vkb::core::CommandBufferC &command_buffer, RenderTarget &default_render_target);
BarrierInfo get_src_barrier_info() const override;
BarrierInfo get_dst_barrier_info() const override;
};
} // namespace vkb
@@ -0,0 +1,52 @@
/* Copyright (c) 2020, Arm Limited and Contributors
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 the "License";
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "postprocessing_pass.h"
#include "postprocessing_pipeline.h"
namespace vkb
{
PostProcessingPassBase::PostProcessingPassBase(PostProcessingPipeline *parent) :
parent{parent}
{}
RenderContext &PostProcessingPassBase::get_render_context() const
{
return *parent->render_context;
}
ShaderSource &PostProcessingPassBase::get_triangle_vs() const
{
return parent->triangle_vs;
}
PostProcessingPassBase::BarrierInfo PostProcessingPassBase::get_predecessor_src_barrier_info(BarrierInfo fallback) const
{
const size_t cur_pass_i = parent->get_current_pass_index();
if (cur_pass_i > 0)
{
const auto &prev_pass = parent->get_pass<vkb::PostProcessingPassBase>(cur_pass_i - 1);
return prev_pass.get_src_barrier_info();
}
else
{
return fallback;
}
}
} // namespace vkb
+207
View File
@@ -0,0 +1,207 @@
/* Copyright (c) 2020-2025, Arm Limited and Contributors
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 the "License";
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "core/command_buffer.h"
#include "render_context.h"
#include "render_target.h"
#include <functional>
namespace vkb
{
class PostProcessingPipeline;
/**
* @brief The base of all types of passes in a vkb::PostProcessingPipeline.
*/
class PostProcessingPassBase
{
friend class PostProcessingPipeline;
public:
PostProcessingPassBase(PostProcessingPipeline *parent);
PostProcessingPassBase(const PostProcessingPassBase &to_copy) = delete;
PostProcessingPassBase &operator=(const PostProcessingPassBase &to_copy) = delete;
PostProcessingPassBase(PostProcessingPassBase &&to_move) = default;
PostProcessingPassBase &operator=(PostProcessingPassBase &&to_move) = default;
virtual ~PostProcessingPassBase() = default;
protected:
/**
* @brief Prepares this pass, recording commands into the given command buffer.
* @remarks Passes that that do not explicitly have a vkb::RenderTarget set will render
* to default_render_target.
*/
virtual void prepare(vkb::core::CommandBufferC &command_buffer, RenderTarget &default_render_target)
{
prepared = true;
}
/**
* @brief Runs this pass, recording commands into the given command buffer.
* @remarks Passes that that do not explicitly have a vkb::RenderTarget set will render
* to default_render_target.
*/
virtual void draw(vkb::core::CommandBufferC &command_buffer, RenderTarget &default_render_target)
{}
/**
* @brief A functor ran in the context of this renderpass.
* @see set_pre_draw_func(), set_post_draw_func()
*/
using HookFunc = std::function<void()>;
// NOTE: Protected members are exposed via getters and setters in PostProcessingPass<>
PostProcessingPipeline *parent{nullptr};
bool prepared{false};
std::string debug_name{};
RenderTarget *render_target{nullptr};
std::shared_ptr<core::Sampler> default_sampler{};
HookFunc pre_draw{};
HookFunc post_draw{};
/**
* @brief Returns the parent's render context.
*/
RenderContext &get_render_context() const;
/**
* @brief Returns the parent's fullscreen triangle vertex shader source.
*/
ShaderSource &get_triangle_vs() const;
struct BarrierInfo
{
VkPipelineStageFlags pipeline_stage; // Pipeline stage of this pass' inputs/outputs
VkAccessFlags image_read_access; // Access mask for images read from this pass
VkAccessFlags image_write_access; // Access mask for images written to by this pass
};
/**
* @brief Returns information that can be used to setup memory barriers of images
* that are produced (e.g. image stores, color attachment output) by this pass.
*/
virtual BarrierInfo get_src_barrier_info() const = 0;
/**
* @brief Returns information that can be used to setup memory barriers of images
* that are consumed (e.g. image loads, texture sampling) by this pass.
*/
virtual BarrierInfo get_dst_barrier_info() const = 0;
/**
* @brief Convenience function that calls get_src_barrier_info() on the previous pass of the pipeline,
* if any, or returns the specified default if this is the first pass in the pipeline.
*/
BarrierInfo get_predecessor_src_barrier_info(BarrierInfo fallback = {}) const;
};
/**
* @brief CRTP base of all types of passes in a vkb::PostProcessingPipeline.
*/
template <typename Self>
class PostProcessingPass : public PostProcessingPassBase
{
public:
using PostProcessingPassBase::PostProcessingPassBase;
PostProcessingPass(const PostProcessingPass &to_copy) = delete;
PostProcessingPass &operator=(const PostProcessingPass &to_copy) = delete;
PostProcessingPass(PostProcessingPass &&to_move) = default;
PostProcessingPass &operator=(PostProcessingPass &&to_move) = default;
virtual ~PostProcessingPass() = default;
/**
* @brief Sets a functor that, if non-null, will be invoked before draw()ing this pass.
* @remarks The function is invoked after ending the previous RenderPass, and before beginning this one.
*/
inline Self &set_pre_draw_func(HookFunc &&new_func)
{
pre_draw = std::move(new_func);
return static_cast<Self &>(*this);
}
/**
* @brief Sets a functor that, if non-null, will be invoked after draw()ing this pass.
* @remarks The function after drawing the last subpass, and before ending this RenderPass.
*/
inline Self &set_post_draw_func(HookFunc &&new_func)
{
post_draw = std::move(new_func);
return static_cast<Self &>(*this);
}
/**
* @brief Render target to output to.
* If set, this pass will output to the given render target instead of the one passed to draw().
*/
inline RenderTarget *get_render_target() const
{
return render_target;
}
/**
* @brief Render target to output to.
* If set, this pass will output to the given render target instead of the one passed to draw().
* @param new_render_target the new render target to output too
*/
inline Self &set_render_target(RenderTarget *new_render_target)
{
render_target = new_render_target;
return static_cast<Self &>(*this);
}
/**
* @brief Returns the the debug name of this pass.
*/
inline const std::string &get_debug_name() const
{
return debug_name;
}
/**
* @brief Sets the debug name of this pass.
*/
inline Self &set_debug_name(const std::string &new_debug_name)
{
debug_name = new_debug_name;
return static_cast<Self &>(*this);
}
/**
* @brief Returns the vkb::PostProcessingPipeline that is the parent of this pass.
*/
inline PostProcessingPipeline &get_parent() const
{
return *parent;
}
};
} // namespace vkb
@@ -0,0 +1,69 @@
/* Copyright (c) 2020-2025, Arm Limited and Contributors
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 the "License";
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "postprocessing_pipeline.h"
#include "common/utils.h"
namespace vkb
{
PostProcessingPipeline::PostProcessingPipeline(RenderContext &render_context, ShaderSource triangle_vs) :
render_context{&render_context},
triangle_vs{std::move(triangle_vs)}
{}
void PostProcessingPipeline::draw(vkb::core::CommandBufferC &command_buffer, RenderTarget &default_render_target)
{
for (current_pass_index = 0; current_pass_index < passes.size(); current_pass_index++)
{
auto &pass = *passes[current_pass_index];
if (pass.debug_name.empty())
{
pass.debug_name = fmt::format("PPP pass #{}", current_pass_index);
}
ScopedDebugLabel marker{command_buffer, pass.debug_name.c_str()};
if (!pass.prepared)
{
ScopedDebugLabel marker{command_buffer, "Prepare"};
pass.prepare(command_buffer, default_render_target);
pass.prepared = true;
}
if (pass.pre_draw)
{
ScopedDebugLabel marker{command_buffer, "Pre-draw"};
pass.pre_draw();
}
pass.draw(command_buffer, default_render_target);
if (pass.post_draw)
{
ScopedDebugLabel marker{command_buffer, "Post-draw"};
pass.post_draw();
}
}
current_pass_index = 0;
}
} // namespace vkb
@@ -0,0 +1,105 @@
/* Copyright (c) 2020-2025, Arm Limited and Contributors
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 the "License";
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "postprocessing_pass.h"
namespace vkb
{
class PostProcessingRenderPass;
/**
* @brief A rendering pipeline specialized for fullscreen post-processing and compute passes.
*/
class PostProcessingPipeline
{
public:
friend class PostProcessingPassBase;
/**
* @brief Creates a rendering pipeline entirely made of fullscreen post-processing subpasses.
*/
PostProcessingPipeline(RenderContext &render_context, ShaderSource triangle_vs);
PostProcessingPipeline(const PostProcessingPipeline &to_copy) = delete;
PostProcessingPipeline &operator=(const PostProcessingPipeline &to_copy) = delete;
PostProcessingPipeline(PostProcessingPipeline &&to_move) = delete;
PostProcessingPipeline &operator=(PostProcessingPipeline &&to_move) = delete;
virtual ~PostProcessingPipeline() = default;
/**
* @brief Runs all renderpasses in this pipeline, recording commands into the given command buffer.
* @remarks vkb::PostProcessingRenderpass that do not explicitly have a vkb::RenderTarget set will render
* to default_render_target.
*/
void draw(vkb::core::CommandBufferC &command_buffer, RenderTarget &default_render_target);
/**
* @brief Gets all of the passes in the pipeline.
*/
inline std::vector<std::unique_ptr<PostProcessingPassBase>> &get_passes()
{
return passes;
}
/**
* @brief Get the pass at a certain index as a `TPass`.
*/
template <typename TPass = vkb::PostProcessingRenderPass>
inline TPass &get_pass(size_t index)
{
return *dynamic_cast<TPass *>(passes[index].get());
}
/**
* @brief Adds a pass of the given type to the end of the pipeline by constructing it in-place.
*/
template <typename TPass = vkb::PostProcessingRenderPass, typename... ConstructorArgs>
TPass &add_pass(ConstructorArgs &&...args)
{
passes.emplace_back(std::make_unique<TPass>(this, std::forward<ConstructorArgs>(args)...));
auto &added_pass = *dynamic_cast<TPass *>(passes.back().get());
return added_pass;
}
/**
* @brief Returns the current render context.
*/
inline RenderContext &get_render_context() const
{
return *render_context;
}
/**
* @brief Returns the index of the currently-being-drawn pass.
*/
inline size_t get_current_pass_index() const
{
return current_pass_index;
}
private:
RenderContext *render_context{nullptr};
ShaderSource triangle_vs;
std::vector<std::unique_ptr<PostProcessingPassBase>> passes{};
size_t current_pass_index{0};
};
} // namespace vkb
@@ -0,0 +1,544 @@
/* Copyright (c) 2021-2025, Arm Limited and Contributors
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 the "License";
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "postprocessing_renderpass.h"
#include "postprocessing_pipeline.h"
namespace vkb
{
constexpr uint32_t DEPTH_RESOLVE_BITMASK = 0x80000000;
constexpr uint32_t ATTACHMENT_BITMASK = 0x7FFFFFFF;
PostProcessingSubpass::PostProcessingSubpass(PostProcessingRenderPass *parent, RenderContext &render_context, ShaderSource &&triangle_vs,
ShaderSource &&fs, ShaderVariant &&fs_variant) :
Subpass(render_context, std::move(triangle_vs), std::move(fs)),
parent{parent},
fs_variant{std::move(fs_variant)}
{
set_disable_depth_stencil_attachment(true);
std::vector<uint32_t> input_attachments{};
for (const auto &it : this->input_attachments)
{
input_attachments.push_back(it.second);
}
set_input_attachments(input_attachments);
}
PostProcessingSubpass::PostProcessingSubpass(PostProcessingSubpass &&to_move) :
Subpass{std::move(to_move)},
parent{std::move(to_move.parent)},
fs_variant{std::move(to_move.fs_variant)},
input_attachments{std::move(to_move.input_attachments)},
sampled_images{std::move(to_move.sampled_images)}
{}
PostProcessingSubpass &PostProcessingSubpass::bind_input_attachment(const std::string &name, uint32_t new_input_attachment)
{
input_attachments[name] = new_input_attachment;
std::vector<uint32_t> input_attachments{};
for (const auto &it : this->input_attachments)
{
input_attachments.push_back(it.second);
}
set_input_attachments(input_attachments);
parent->load_stores_dirty = true;
return *this;
}
void PostProcessingSubpass::unbind_sampled_image(const std::string &name)
{
sampled_images.erase(name);
}
PostProcessingSubpass &PostProcessingSubpass::bind_sampled_image(const std::string &name, core::SampledImage &&new_image)
{
auto it = sampled_images.find(name);
if (it != sampled_images.end())
{
it->second = std::move(new_image);
}
else
{
sampled_images.emplace(name, std::move(new_image));
}
parent->load_stores_dirty = true;
return *this;
}
PostProcessingSubpass &PostProcessingSubpass::bind_storage_image(const std::string &name, const core::ImageView &new_image)
{
auto it = storage_images.find(name);
if (it != storage_images.end())
{
it->second = &new_image;
}
else
{
storage_images.emplace(name, &new_image);
}
return *this;
}
PostProcessingSubpass &PostProcessingSubpass::set_push_constants(const std::vector<uint8_t> &data)
{
push_constants_data = data;
return *this;
}
PostProcessingSubpass &PostProcessingSubpass::set_draw_func(DrawFunc &&new_func)
{
draw_func = std::move(new_func);
return *this;
}
void PostProcessingSubpass::prepare()
{
// Build all shaders upfront
auto &resource_cache = get_render_context().get_device().get_resource_cache();
resource_cache.request_shader_module(VK_SHADER_STAGE_VERTEX_BIT, get_vertex_shader());
resource_cache.request_shader_module(VK_SHADER_STAGE_FRAGMENT_BIT, get_fragment_shader(), fs_variant);
}
void PostProcessingSubpass::draw(vkb::core::CommandBufferC &command_buffer)
{
// Get shaders from cache
auto &resource_cache = command_buffer.get_device().get_resource_cache();
auto &vert_shader_module = resource_cache.request_shader_module(VK_SHADER_STAGE_VERTEX_BIT, get_vertex_shader());
auto &frag_shader_module = resource_cache.request_shader_module(VK_SHADER_STAGE_FRAGMENT_BIT, get_fragment_shader(), fs_variant);
std::vector<ShaderModule *> shader_modules{&vert_shader_module, &frag_shader_module};
// Create pipeline layout and bind it
auto &pipeline_layout = resource_cache.request_pipeline_layout(shader_modules);
command_buffer.bind_pipeline_layout(pipeline_layout);
// Disable culling
RasterizationState rasterization_state;
rasterization_state.cull_mode = VK_CULL_MODE_NONE;
command_buffer.set_rasterization_state(rasterization_state);
auto &render_target = *parent->draw_render_target;
const auto &target_views = render_target.get_views();
const uint32_t n_input_attachments = static_cast<uint32_t>(get_input_attachments().size());
if (parent->uniform_buffer_alloc != nullptr)
{
// Bind buffer to set = 0, binding = 0
auto &uniform_alloc = *parent->uniform_buffer_alloc;
command_buffer.bind_buffer(uniform_alloc.get_buffer(), uniform_alloc.get_offset(), uniform_alloc.get_size(), 0, 0, 0);
}
const auto &bindings = pipeline_layout.get_descriptor_set_layout(0);
// Bind subpass inputs to set = 0, binding = <according to name>
for (const auto &it : input_attachments)
{
if (auto layout_binding = bindings.get_layout_binding(it.first))
{
assert(it.second < target_views.size());
command_buffer.bind_input(target_views[it.second], 0, layout_binding->binding, 0);
}
}
// Bind samplers to set = 0, binding = <according to name>
for (const auto &it : sampled_images)
{
if (auto layout_binding = bindings.get_layout_binding(it.first))
{
const auto &view = it.second.get_image_view(render_target);
// Get the properties for the image format. We need to check whether a linear sampler is valid.
const VkFormatProperties fmtProps = get_render_context().get_device().get_gpu().get_format_properties(view.get_format());
bool has_linear_filter = (fmtProps.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT);
const auto &sampler = it.second.get_sampler() ? *it.second.get_sampler() :
(has_linear_filter ? *parent->default_sampler : *parent->default_sampler_nearest);
command_buffer.bind_image(view, sampler, 0, layout_binding->binding, 0);
}
}
// Bind storage images to set = 0, binding = <according to name>
for (const auto &it : storage_images)
{
if (auto layout_binding = bindings.get_layout_binding(it.first))
{
command_buffer.bind_image(*it.second, 0, layout_binding->binding, 0);
}
}
// Per-draw push constants
command_buffer.push_constants(push_constants_data);
// draw full screen triangle
draw_func(command_buffer, render_target);
}
void PostProcessingSubpass::default_draw_func(vkb::core::CommandBufferC &command_buffer, vkb::RenderTarget &)
{
command_buffer.draw(3, 1, 0, 0);
}
PostProcessingRenderPass::PostProcessingRenderPass(PostProcessingPipeline *parent, std::unique_ptr<core::Sampler> &&default_sampler) :
PostProcessingPass{parent},
default_sampler{std::move(default_sampler)}
{
if (this->default_sampler == nullptr)
{
// Setup a sane default sampler if none was passed
VkSamplerCreateInfo sampler_info{VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO};
sampler_info.minFilter = VK_FILTER_LINEAR;
sampler_info.magFilter = VK_FILTER_LINEAR;
sampler_info.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
sampler_info.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
sampler_info.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
sampler_info.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
sampler_info.mipLodBias = 0.0f;
sampler_info.compareOp = VK_COMPARE_OP_NEVER;
sampler_info.minLod = 0.0f;
sampler_info.maxLod = 0.0f;
sampler_info.anisotropyEnable = VK_FALSE;
sampler_info.maxAnisotropy = 0.0f;
sampler_info.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
this->default_sampler = std::make_unique<vkb::core::Sampler>(get_render_context().get_device(), sampler_info);
// Also create a nearest filtering version as a fallback
sampler_info.minFilter = VK_FILTER_NEAREST;
sampler_info.magFilter = VK_FILTER_NEAREST;
this->default_sampler_nearest = std::make_unique<vkb::core::Sampler>(get_render_context().get_device(), sampler_info);
}
}
void PostProcessingRenderPass::update_load_stores(
const AttachmentSet &input_attachments,
const SampledAttachmentSet &sampled_attachments,
const AttachmentSet &output_attachments,
const RenderTarget &fallback_render_target)
{
if (!load_stores_dirty)
{
return;
}
const auto &render_target = this->render_target ? *this->render_target : fallback_render_target;
// Update load/stores accordingly
load_stores.clear();
for (uint32_t j = 0; j < static_cast<uint32_t>(render_target.get_attachments().size()); j++)
{
const bool is_input = input_attachments.find(j) != input_attachments.end();
const bool is_sampled = std::ranges::find_if(sampled_attachments,
[&render_target, j](auto &pair) {
// NOTE: if RT not set, default is the currently-active one
auto *sampled_rt = pair.first ? pair.first : &render_target;
// unpack attachment
uint32_t attachment = pair.second & ATTACHMENT_BITMASK;
return attachment == j && sampled_rt == &render_target;
}) != sampled_attachments.end();
const bool is_output = output_attachments.find(j) != output_attachments.end();
VkAttachmentLoadOp load;
if (is_input || is_sampled)
{
load = VK_ATTACHMENT_LOAD_OP_LOAD;
}
else if (is_output)
{
load = VK_ATTACHMENT_LOAD_OP_CLEAR;
}
else
{
load = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
}
VkAttachmentStoreOp store;
if (is_output)
{
store = VK_ATTACHMENT_STORE_OP_STORE;
}
else
{
store = VK_ATTACHMENT_STORE_OP_DONT_CARE;
}
load_stores.push_back({load, store});
}
pipeline.set_load_store(load_stores);
load_stores_dirty = false;
}
PostProcessingRenderPass::BarrierInfo PostProcessingRenderPass::get_src_barrier_info() const
{
BarrierInfo info{};
info.pipeline_stage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
info.image_read_access = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT;
info.image_write_access = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
return info;
}
PostProcessingRenderPass::BarrierInfo PostProcessingRenderPass::get_dst_barrier_info() const
{
BarrierInfo info{};
info.pipeline_stage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
info.image_read_access = VK_ACCESS_SHADER_READ_BIT;
info.image_write_access = VK_ACCESS_SHADER_WRITE_BIT;
return info;
}
// If the passed `src_access` is zero, guess it - and the corresponding source stage - from the src_access_mask
// of the image
static void ensure_src_access(uint32_t &src_access, uint32_t &src_stage, VkImageLayout layout)
{
if (src_access == 0)
{
switch (layout)
{
case VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL:
src_stage = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
src_access = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
src_access |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT;
break;
default:
src_stage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
src_access = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
break;
}
}
}
void PostProcessingRenderPass::transition_attachments(const AttachmentSet &input_attachments,
const SampledAttachmentSet &sampled_attachments,
const AttachmentSet &output_attachments,
vkb::core::CommandBufferC &command_buffer,
RenderTarget &fallback_render_target)
{
auto &render_target = this->render_target ? *this->render_target : fallback_render_target;
const auto &views = render_target.get_views();
BarrierInfo fallback_barrier_src{};
fallback_barrier_src.pipeline_stage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
fallback_barrier_src.image_read_access = 0; // For UNDEFINED -> COLOR_ATTACHMENT_OPTIMAL in first RP
fallback_barrier_src.image_write_access = 0;
auto prev_pass_barrier_info = get_predecessor_src_barrier_info(fallback_barrier_src);
for (uint32_t input : input_attachments)
{
const VkImageLayout prev_layout = render_target.get_layout(input);
if (prev_layout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
{
// No-op
continue;
}
ensure_src_access(prev_pass_barrier_info.image_write_access, prev_pass_barrier_info.pipeline_stage,
prev_layout);
vkb::ImageMemoryBarrier barrier;
barrier.old_layout = render_target.get_layout(input);
barrier.new_layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
barrier.src_access_mask = prev_pass_barrier_info.image_write_access;
barrier.dst_access_mask = VK_ACCESS_INPUT_ATTACHMENT_READ_BIT;
barrier.src_stage_mask = prev_pass_barrier_info.pipeline_stage;
barrier.dst_stage_mask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
assert(input < views.size());
command_buffer.image_memory_barrier(views[input], barrier);
render_target.set_layout(input, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
}
for (const auto &sampled : sampled_attachments)
{
auto *sampled_rt = sampled.first ? sampled.first : &render_target;
// unpack depth resolve flag and attachment
bool is_depth_resolve = sampled.second & DEPTH_RESOLVE_BITMASK;
uint32_t attachment = sampled.second & ATTACHMENT_BITMASK;
const auto prev_layout = sampled_rt->get_layout(attachment);
if (prev_layout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
{
// No-op
continue;
}
if (prev_layout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL)
{
// Synchronize with previous pass writes as barrier below might do image transition
prev_pass_barrier_info.pipeline_stage |= VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
prev_pass_barrier_info.image_read_access |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
// The resolving depth occurs in the COLOR_ATTACHMENT_OUT stage, not in the EARLY\LATE_FRAGMENT_TESTS stage
// and the corresponding access mask is COLOR_ATTACHMENT_WRITE_BIT, not DEPTH_STENCIL_ATTACHMENT_WRITE_BIT.
if (is_depth_resolve)
{
prev_pass_barrier_info.pipeline_stage |= VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
prev_pass_barrier_info.image_read_access |= VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
}
}
else
{
ensure_src_access(prev_pass_barrier_info.image_read_access, prev_pass_barrier_info.pipeline_stage,
prev_layout);
}
vkb::ImageMemoryBarrier barrier;
barrier.old_layout = prev_layout;
barrier.new_layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
barrier.src_access_mask = prev_pass_barrier_info.image_read_access;
barrier.dst_access_mask = VK_ACCESS_SHADER_READ_BIT;
barrier.src_stage_mask = prev_pass_barrier_info.pipeline_stage;
barrier.dst_stage_mask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
assert(attachment < sampled_rt->get_views().size());
command_buffer.image_memory_barrier(sampled_rt->get_views()[attachment], barrier);
sampled_rt->set_layout(attachment, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
}
for (uint32_t output : output_attachments)
{
assert(output < views.size());
const VkFormat attachment_format = views[output].get_format();
const bool is_depth_stencil = vkb::is_depth_format(attachment_format);
const VkImageLayout output_layout = is_depth_stencil ? VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
if (render_target.get_layout(output) == output_layout)
{
// No-op
continue;
}
vkb::ImageMemoryBarrier barrier;
barrier.old_layout = VK_IMAGE_LAYOUT_UNDEFINED; // = don't care about previous contents
barrier.new_layout = output_layout;
barrier.src_access_mask = 0;
if (is_depth_stencil)
{
barrier.dst_access_mask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
barrier.src_stage_mask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
barrier.dst_stage_mask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
}
else
{
barrier.dst_access_mask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
barrier.src_stage_mask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
barrier.dst_stage_mask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
}
command_buffer.image_memory_barrier(views[output], barrier);
render_target.set_layout(output, output_layout);
}
// NOTE: Unused attachments might be carried over to other render passes,
// so we don't want to transition them to UNDEFINED layout here
}
void PostProcessingRenderPass::prepare_draw(vkb::core::CommandBufferC &command_buffer, RenderTarget &fallback_render_target)
{
// Collect all input, output, and sampled-from attachments from all subpasses (steps)
AttachmentSet input_attachments, output_attachments;
SampledAttachmentSet sampled_attachments;
for (auto &step_ptr : pipeline.get_subpasses())
{
auto &step = *dynamic_cast<PostProcessingSubpass *>(step_ptr.get());
for (auto &it : step.get_input_attachments())
{
input_attachments.insert(it.second);
}
for (auto &it : step.get_sampled_images())
{
if (const uint32_t *sampled_attachment = it.second.get_target_attachment())
{
auto *image_rt = it.second.get_render_target();
auto packed_sampled_attachment = *sampled_attachment;
// pack sampled attachment
if (it.second.is_depth_resolve())
{
packed_sampled_attachment |= DEPTH_RESOLVE_BITMASK;
}
sampled_attachments.insert({image_rt, packed_sampled_attachment});
}
}
for (uint32_t it : step.get_output_attachments())
{
output_attachments.insert(it);
}
}
transition_attachments(input_attachments, sampled_attachments, output_attachments,
command_buffer, fallback_render_target);
update_load_stores(input_attachments, sampled_attachments, output_attachments,
fallback_render_target);
}
void PostProcessingRenderPass::draw(vkb::core::CommandBufferC &command_buffer, RenderTarget &default_render_target)
{
prepare_draw(command_buffer, default_render_target);
if (!uniform_data.empty())
{
// Allocate a buffer (using the buffer pool from the active frame to store uniform values) and bind it
auto &render_frame = parent->get_render_context().get_active_frame();
uniform_buffer_alloc = std::make_shared<BufferAllocationC>(render_frame.allocate_buffer(VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, uniform_data.size()));
uniform_buffer_alloc->update(uniform_data);
}
// Update render target for this draw
draw_render_target = render_target ? render_target : &default_render_target;
// Set appropriate viewport & scissor for this RT
{
auto &extent = draw_render_target->get_extent();
VkViewport viewport{};
viewport.width = static_cast<float>(extent.width);
viewport.height = static_cast<float>(extent.height);
viewport.minDepth = 0.0f;
viewport.maxDepth = 1.0f;
command_buffer.set_viewport(0, {viewport});
VkRect2D scissor{};
scissor.extent = extent;
command_buffer.set_scissor(0, {scissor});
}
// Finally draw all subpasses
pipeline.draw(command_buffer, *draw_render_target);
if (parent->get_current_pass_index() < (parent->get_passes().size() - 1))
{
// Leave the last renderpass open for user modification (e.g., drawing GUI)
command_buffer.end_render_pass();
}
}
} // namespace vkb
@@ -0,0 +1,326 @@
/* Copyright (c) 2020-2025, Arm Limited and Contributors
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 the "License";
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "core/sampled_image.h"
#include "postprocessing_pass.h"
#include "render_pipeline.h"
#include "subpass.h"
namespace vkb
{
/**
* @brief An utility struct for hashing pairs.
*/
struct PairHasher
{
template <typename TPair>
size_t operator()(const TPair &pair) const
{
std::hash<decltype(pair.first)> hash1{};
std::hash<decltype(pair.second)> hash2{};
return hash1(pair.first) * 43 + pair.second;
}
};
/**
* @brief Maps in-shader binding names to indices into a RenderTarget's attachments.
*/
using AttachmentMap = std::unordered_map<std::string, uint32_t>;
/**
* @brief Maps in-shader binding names to the core::SampledImage to bind.
*/
using SampledMap = std::unordered_map<std::string, core::SampledImage>;
/**
* @brief Maps in-shader binding names to the core::ImageView to bind for storage images.
*/
using StorageImageMap = std::unordered_map<std::string, const core::ImageView *>;
/**
* @brief A list of indices into a RenderTarget's attachments.
*/
using AttachmentList = std::vector<uint32_t>;
/**
* @brief A set of indices into a RenderTarget's attachments.
*/
using AttachmentSet = std::unordered_set<uint32_t>;
class PostProcessingRenderPass;
/**
* @brief A single step of a vkb::PostProcessingRenderPass.
*/
class PostProcessingSubpass : public vkb::rendering::SubpassC
{
public:
PostProcessingSubpass(PostProcessingRenderPass *parent, RenderContext &render_context, ShaderSource &&triangle_vs,
ShaderSource &&fs, ShaderVariant &&fs_variant = {});
PostProcessingSubpass(const PostProcessingSubpass &to_copy) = delete;
PostProcessingSubpass &operator=(const PostProcessingSubpass &to_copy) = delete;
PostProcessingSubpass(PostProcessingSubpass &&to_move);
PostProcessingSubpass &operator=(PostProcessingSubpass &&to_move) = delete;
~PostProcessingSubpass() = default;
/**
* @brief Maps the names of input attachments in the shader to indices into the render target's images.
* These are given as `subpassInput`s to the subpass, at set 0; they are bound automatically according to their name.
*/
inline const AttachmentMap &get_input_attachments() const
{
return input_attachments;
}
/**
* @brief Maps the names of samplers in the shader to vkb::core::SampledImage.
* These are given as samplers to the subpass, at set 0; they are bound automatically according to their name.
* @remarks PostProcessingPipeline::get_sampler() is used as the default sampler if none is specified.
* The RenderTarget for the current PostProcessingSubpass is used if none is specified for attachment images.
*/
inline const SampledMap &get_sampled_images() const
{
return sampled_images;
}
/**
* @brief Maps the names of storage images in the shader to vkb::core::ImageView.
* These are given as image2D[Array] to the subpass, at set 0; they are bound automatically according to their name.
*/
inline const StorageImageMap &get_storage_images() const
{
return storage_images;
}
/**
* @brief Returns the shader variant used for this postprocess' fragment shader.
*/
inline ShaderVariant &get_fs_variant()
{
return fs_variant;
}
/**
* @brief Sets the shader variant that will be used for this postprocess' fragment shader.
*/
inline PostProcessingSubpass &set_fs_variant(ShaderVariant &&new_variant)
{
fs_variant = std::move(new_variant);
return *this;
}
/**
* @brief Changes the debug name of this Subpass.
*/
inline PostProcessingSubpass &set_debug_name(const std::string &name)
{
Subpass::set_debug_name(name);
return *this;
}
/**
* @brief Changes (or adds) the input attachment at name for this step.
*/
PostProcessingSubpass &bind_input_attachment(const std::string &name, uint32_t new_input_attachment);
/**
* @brief Changes (or adds) the sampled image at name for this step.
* @remarks If no RenderTarget is specifically set for the core::SampledImage,
* it will default to sample in the RenderTarget currently bound for drawing in the parent PostProcessingRenderpass.
*/
PostProcessingSubpass &bind_sampled_image(const std::string &name, core::SampledImage &&new_image);
/**
* @brief Changes (or adds) the storage image at name for this step.
*/
PostProcessingSubpass &bind_storage_image(const std::string &name, const core::ImageView &new_image);
/**
* @brief Removes the sampled image at name for this step.
*/
void unbind_sampled_image(const std::string &name);
/**
* @brief Set the constants that are pushed before each fullscreen draw.
*/
PostProcessingSubpass &set_push_constants(const std::vector<uint8_t> &data);
/**
* @brief Set the constants that are pushed before each fullscreen draw.
*/
template <typename T>
inline PostProcessingSubpass &set_push_constants(const T &data)
{
push_constants_data.reserve(sizeof(data));
auto data_ptr = reinterpret_cast<const uint8_t *>(&data);
push_constants_data.assign(data_ptr, data_ptr + sizeof(data));
return *this;
}
/**
* @brief A functor used to draw the primitives for a post-processing step.
* @see default_draw_func()
*/
using DrawFunc = std::function<void(vkb::core::CommandBufferC &command_buffer, RenderTarget &render_target)>;
/**
* @brief Sets the function used to draw this postprocessing step.
* @see default_draw_func()
*/
PostProcessingSubpass &set_draw_func(DrawFunc &&new_func);
/**
* @brief The default function used to draw a step; it draws 1 instance with 3 vertices.
*/
static void default_draw_func(vkb::core::CommandBufferC &command_buffer, vkb::RenderTarget &render_target);
private:
PostProcessingRenderPass *parent;
ShaderVariant fs_variant{};
AttachmentMap input_attachments{};
SampledMap sampled_images{};
StorageImageMap storage_images{};
std::vector<uint8_t> push_constants_data{};
DrawFunc draw_func{&PostProcessingSubpass::default_draw_func};
void prepare() override;
void draw(vkb::core::CommandBufferC &command_buffer) override;
};
/**
* @brief A collection of vkb::PostProcessingSubpass that are run as a single renderpass.
*/
class PostProcessingRenderPass : public PostProcessingPass<PostProcessingRenderPass>
{
public:
friend class PostProcessingSubpass;
PostProcessingRenderPass(PostProcessingPipeline *parent, std::unique_ptr<core::Sampler> &&default_sampler = nullptr);
PostProcessingRenderPass(const PostProcessingRenderPass &to_copy) = delete;
PostProcessingRenderPass &operator=(const PostProcessingRenderPass &to_copy) = delete;
PostProcessingRenderPass(PostProcessingRenderPass &&to_move) = default;
PostProcessingRenderPass &operator=(PostProcessingRenderPass &&to_move) = default;
void draw(vkb::core::CommandBufferC &command_buffer, RenderTarget &default_render_target) override;
/**
* @brief Gets the step at the given index.
*/
inline PostProcessingSubpass &get_subpass(size_t index)
{
assert(index < pipeline.get_subpasses().size());
return *dynamic_cast<PostProcessingSubpass *>(pipeline.get_subpasses()[index].get());
}
/**
* @brief Constructs a new PostProcessingSubpass and adds it to the tail of the pipeline.
* @remarks `this`, the render context and the vertex shader source are passed automatically before `args`.
* @returns The inserted step.
*/
template <typename... ConstructorArgs>
PostProcessingSubpass &add_subpass(ConstructorArgs &&...args)
{
ShaderSource vs_copy = get_triangle_vs();
auto new_subpass = std::make_unique<PostProcessingSubpass>(this, get_render_context(), std::move(vs_copy), std::forward<ConstructorArgs>(args)...);
auto &new_subpass_ref = *new_subpass;
pipeline.add_subpass(std::move(new_subpass));
return new_subpass_ref;
}
/**
* @brief Set the uniform data to be bound at set 0, binding 0.
*/
template <typename T>
inline PostProcessingRenderPass &set_uniform_data(const T &data)
{
uniform_data.reserve(sizeof(data));
auto data_ptr = reinterpret_cast<const uint8_t *>(&data);
uniform_data.assign(data_ptr, data_ptr + sizeof(data));
return *this;
}
/**
* @copydoc set_uniform_data(const T&)
*/
inline PostProcessingRenderPass &set_uniform_data(const std::vector<uint8_t> &data)
{
uniform_data = data;
return *this;
}
private:
// An attachment sampled from a rendertarget
using SampledAttachmentSet = std::unordered_set<std::pair<RenderTarget *, uint32_t>, PairHasher>;
/**
* @brief Transition input, sampled and output attachments as appropriate.
* @remarks If a RenderTarget is not explicitly set for this pass, fallback_render_target is used.
*/
void transition_attachments(const AttachmentSet &input_attachments,
const SampledAttachmentSet &sampled_attachments,
const AttachmentSet &output_attachments,
vkb::core::CommandBufferC &command_buffer,
RenderTarget &fallback_render_target);
/**
* @brief Select appropriate load/store operations for each buffer of render_target,
* according to the subpass inputs/sampled inputs/subpass outputs of all steps
* in the pipeline.
* @remarks If a RenderTarget is not explicitly set for this pass, fallback_render_target is used.
*/
void update_load_stores(const AttachmentSet &input_attachments,
const SampledAttachmentSet &sampled_attachments,
const AttachmentSet &output_attachments,
const RenderTarget &fallback_render_target);
/**
* @brief Transition images and prepare load/stores before draw()ing.
*/
void prepare_draw(vkb::core::CommandBufferC &command_buffer, RenderTarget &fallback_render_target);
BarrierInfo get_src_barrier_info() const override;
BarrierInfo get_dst_barrier_info() const override;
RenderPipeline pipeline{};
std::unique_ptr<core::Sampler> default_sampler{};
std::unique_ptr<core::Sampler> default_sampler_nearest{};
RenderTarget *draw_render_target{nullptr};
std::vector<LoadStoreInfo> load_stores{};
bool load_stores_dirty{true};
std::vector<uint8_t> uniform_data{};
std::shared_ptr<BufferAllocationC> uniform_buffer_alloc{};
};
} // namespace vkb
+561
View File
@@ -0,0 +1,561 @@
/* Copyright (c) 2019-2025, Arm Limited and Contributors
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 the "License";
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "render_context.h"
#include "platform/window.h"
namespace vkb
{
VkFormat RenderContext::DEFAULT_VK_FORMAT = VK_FORMAT_R8G8B8A8_SRGB;
RenderContext::RenderContext(vkb::core::DeviceC &device,
VkSurfaceKHR surface,
const Window &window,
VkPresentModeKHR present_mode,
const std::vector<VkPresentModeKHR> &present_mode_priority_list,
const std::vector<VkSurfaceFormatKHR> &surface_format_priority_list) :
device{device}, window{window}, queue{device.get_queue_by_flags(VK_QUEUE_GRAPHICS_BIT, 0)}, surface_extent{window.get_extent().width, window.get_extent().height}
{
if (surface != VK_NULL_HANDLE)
{
VkSurfaceCapabilitiesKHR surface_properties;
VK_CHECK(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device.get_gpu().get_handle(),
surface,
&surface_properties));
if (surface_properties.currentExtent.width == 0xFFFFFFFF)
{
swapchain = std::make_unique<Swapchain>(device, surface, present_mode, present_mode_priority_list, surface_format_priority_list, surface_extent);
}
else
{
swapchain = std::make_unique<Swapchain>(device, surface, present_mode, present_mode_priority_list, surface_format_priority_list);
}
}
}
void RenderContext::prepare(size_t thread_count, RenderTarget::CreateFunc create_render_target_func)
{
device.wait_idle();
if (swapchain)
{
surface_extent = swapchain->get_extent();
VkExtent3D extent{surface_extent.width, surface_extent.height, 1};
for (auto &image_handle : swapchain->get_images())
{
auto swapchain_image = core::Image{
device, image_handle,
extent,
swapchain->get_format(),
swapchain->get_usage()};
auto render_target = create_render_target_func(std::move(swapchain_image));
frames.emplace_back(std::make_unique<vkb::rendering::RenderFrameC>(device, std::move(render_target), thread_count));
}
}
else
{
// Otherwise, create a single RenderFrame
swapchain = nullptr;
auto color_image = core::Image{device,
VkExtent3D{surface_extent.width, surface_extent.height, 1},
DEFAULT_VK_FORMAT, // We can use any format here that we like
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT,
VMA_MEMORY_USAGE_GPU_ONLY};
auto render_target = create_render_target_func(std::move(color_image));
frames.emplace_back(std::make_unique<vkb::rendering::RenderFrameC>(device, std::move(render_target), thread_count));
}
this->create_render_target_func = create_render_target_func;
this->thread_count = thread_count;
this->prepared = true;
}
VkFormat RenderContext::get_format() const
{
VkFormat format = DEFAULT_VK_FORMAT;
if (swapchain)
{
format = swapchain->get_format();
}
return format;
}
void RenderContext::update_swapchain(const VkExtent2D &extent)
{
if (!swapchain)
{
LOGW("Can't update the swapchains extent. No swapchain, offscreen rendering detected, skipping.");
return;
}
device.get_resource_cache().clear_framebuffers();
swapchain = std::make_unique<Swapchain>(*swapchain, extent);
recreate();
}
void RenderContext::update_swapchain(const uint32_t image_count)
{
if (!swapchain)
{
LOGW("Can't update the swapchains image count. No swapchain, offscreen rendering detected, skipping.");
return;
}
device.get_resource_cache().clear_framebuffers();
device.wait_idle();
swapchain = std::make_unique<Swapchain>(*swapchain, image_count);
recreate();
}
void RenderContext::update_swapchain(const std::set<VkImageUsageFlagBits> &image_usage_flags)
{
if (!swapchain)
{
LOGW("Can't update the swapchains image usage. No swapchain, offscreen rendering detected, skipping.");
return;
}
device.get_resource_cache().clear_framebuffers();
swapchain = std::make_unique<Swapchain>(*swapchain, image_usage_flags);
recreate();
}
void RenderContext::update_swapchain(const VkExtent2D &extent, const VkSurfaceTransformFlagBitsKHR transform)
{
if (!swapchain)
{
LOGW("Can't update the swapchains extent and surface transform. No swapchain, offscreen rendering detected, skipping.");
return;
}
device.get_resource_cache().clear_framebuffers();
auto width = extent.width;
auto height = extent.height;
if (transform == VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR || transform == VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR)
{
// Pre-rotation: always use native orientation i.e. if rotated, use width and height of identity transform
std::swap(width, height);
}
swapchain = std::make_unique<Swapchain>(*swapchain, VkExtent2D{width, height}, transform);
// Save the preTransform attribute for future rotations
pre_transform = transform;
recreate();
}
void RenderContext::update_swapchain(const VkImageCompressionFlagsEXT compression, const VkImageCompressionFixedRateFlagsEXT compression_fixed_rate)
{
if (!swapchain)
{
LOGW("Can't update the swapchains compression. No swapchain, offscreen rendering detected, skipping.");
return;
}
device.get_resource_cache().clear_framebuffers();
swapchain = std::make_unique<Swapchain>(*swapchain, compression, compression_fixed_rate);
recreate();
}
void RenderContext::recreate()
{
LOGI("Recreated swapchain");
VkExtent2D swapchain_extent = swapchain->get_extent();
VkExtent3D extent{swapchain_extent.width, swapchain_extent.height, 1};
auto frame_it = frames.begin();
for (auto &image_handle : swapchain->get_images())
{
core::Image swapchain_image{device, image_handle,
extent,
swapchain->get_format(),
swapchain->get_usage()};
auto render_target = create_render_target_func(std::move(swapchain_image));
if (frame_it != frames.end())
{
(*frame_it)->update_render_target(std::move(render_target));
}
else
{
// Create a new frame if the new swapchain has more images than current frames
frames.emplace_back(std::make_unique<vkb::rendering::RenderFrameC>(device, std::move(render_target), thread_count));
}
++frame_it;
}
device.get_resource_cache().clear_framebuffers();
}
bool RenderContext::handle_surface_changes(bool force_update)
{
if (!swapchain)
{
LOGW("Can't handle surface changes. No swapchain, offscreen rendering detected, skipping.");
return false;
}
VkSurfaceCapabilitiesKHR surface_properties;
VK_CHECK(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device.get_gpu().get_handle(),
swapchain->get_surface(),
&surface_properties));
if (surface_properties.currentExtent.width == 0xFFFFFFFF)
{
return false;
}
// Only recreate the swapchain if the dimensions have changed;
// handle_surface_changes() is called on VK_SUBOPTIMAL_KHR,
// which might not be due to a surface resize
if (surface_properties.currentExtent.width != surface_extent.width ||
surface_properties.currentExtent.height != surface_extent.height ||
force_update)
{
// Recreate swapchain
device.wait_idle();
update_swapchain(surface_properties.currentExtent, pre_transform);
surface_extent = surface_properties.currentExtent;
return true;
}
return false;
}
std::shared_ptr<vkb::core::CommandBufferC> RenderContext::begin(vkb::CommandBufferResetMode reset_mode)
{
assert(prepared && "RenderContext not prepared for rendering, call prepare()");
if (!frame_active)
{
begin_frame();
}
if (acquired_semaphore == VK_NULL_HANDLE)
{
throw std::runtime_error("Couldn't begin frame");
}
const auto &queue = device.get_queue_by_flags(VK_QUEUE_GRAPHICS_BIT, 0);
return get_active_frame().get_command_pool(queue, reset_mode).request_command_buffer();
}
void RenderContext::submit(std::shared_ptr<vkb::core::CommandBufferC> command_buffer)
{
std::vector<std::shared_ptr<vkb::core::CommandBufferC>> command_buffers(1, command_buffer);
submit(command_buffers);
}
void RenderContext::submit(const std::vector<std::shared_ptr<vkb::core::CommandBufferC>> &command_buffers)
{
assert(frame_active && "RenderContext is inactive, cannot submit command buffer. Please call begin()");
VkSemaphore render_semaphore = VK_NULL_HANDLE;
if (swapchain)
{
assert(acquired_semaphore && "We do not have acquired_semaphore, it was probably consumed?\n");
render_semaphore = submit(queue, command_buffers, acquired_semaphore, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT);
}
else
{
submit(queue, command_buffers);
}
end_frame(render_semaphore);
}
void RenderContext::begin_frame()
{
// Only handle surface changes if a swapchain exists
if (swapchain)
{
handle_surface_changes();
}
assert(!frame_active && "Frame is still active, please call end_frame");
assert(active_frame_index < frames.size());
auto &prev_frame = *frames[active_frame_index];
// We will use the acquired semaphore in a different frame context,
// so we need to hold ownership.
acquired_semaphore = prev_frame.get_semaphore_pool().request_semaphore_with_ownership();
if (swapchain)
{
auto result = swapchain->acquire_next_image(active_frame_index, acquired_semaphore, VK_NULL_HANDLE);
if (result == VK_SUBOPTIMAL_KHR || result == VK_ERROR_OUT_OF_DATE_KHR)
{
#if defined(PLATFORM__MACOS)
// On Apple platforms, force swapchain update on both VK_SUBOPTIMAL_KHR and VK_ERROR_OUT_OF_DATE_KHR
// VK_SUBOPTIMAL_KHR may occur on macOS/iOS following changes to swapchain other than extent/size
bool swapchain_updated = handle_surface_changes(true);
#else
bool swapchain_updated = handle_surface_changes(result == VK_ERROR_OUT_OF_DATE_KHR);
#endif
if (swapchain_updated)
{
// Need to destroy and reallocate acquired_semaphore since it may have already been signaled
vkDestroySemaphore(device.get_handle(), acquired_semaphore, nullptr);
acquired_semaphore = prev_frame.get_semaphore_pool().request_semaphore_with_ownership();
result = swapchain->acquire_next_image(active_frame_index, acquired_semaphore, VK_NULL_HANDLE);
}
}
if (result != VK_SUCCESS)
{
prev_frame.reset();
return;
}
}
// Now the frame is active again
frame_active = true;
// Wait on all resource to be freed from the previous render to this frame
wait_frame();
}
VkSemaphore RenderContext::submit(const Queue &queue,
const std::vector<std::shared_ptr<vkb::core::CommandBufferC>> &command_buffers,
VkSemaphore wait_semaphore,
VkPipelineStageFlags wait_pipeline_stage)
{
std::vector<VkCommandBuffer> cmd_buf_handles(command_buffers.size(), VK_NULL_HANDLE);
std::ranges::transform(command_buffers, cmd_buf_handles.begin(), [](auto const &cmd_buf) { return cmd_buf->get_handle(); });
vkb::rendering::RenderFrameC &frame = get_active_frame();
VkSemaphore signal_semaphore = frame.get_semaphore_pool().request_semaphore();
VkSubmitInfo submit_info{VK_STRUCTURE_TYPE_SUBMIT_INFO};
submit_info.commandBufferCount = to_u32(cmd_buf_handles.size());
submit_info.pCommandBuffers = cmd_buf_handles.data();
if (wait_semaphore != VK_NULL_HANDLE)
{
submit_info.waitSemaphoreCount = 1;
submit_info.pWaitSemaphores = &wait_semaphore;
submit_info.pWaitDstStageMask = &wait_pipeline_stage;
}
submit_info.signalSemaphoreCount = 1;
submit_info.pSignalSemaphores = &signal_semaphore;
VkFence fence = frame.get_fence_pool().request_fence();
VK_CHECK(queue.submit({submit_info}, fence));
return signal_semaphore;
}
void RenderContext::submit(const Queue &queue, const std::vector<std::shared_ptr<vkb::core::CommandBufferC>> &command_buffers)
{
std::vector<VkCommandBuffer> cmd_buf_handles(command_buffers.size(), VK_NULL_HANDLE);
std::ranges::transform(command_buffers, cmd_buf_handles.begin(), [](auto const &cmd_buf) { return cmd_buf->get_handle(); });
vkb::rendering::RenderFrameC &frame = get_active_frame();
VkSubmitInfo submit_info{VK_STRUCTURE_TYPE_SUBMIT_INFO};
submit_info.commandBufferCount = to_u32(cmd_buf_handles.size());
submit_info.pCommandBuffers = cmd_buf_handles.data();
VkFence fence = frame.get_fence_pool().request_fence();
VK_CHECK(queue.submit({submit_info}, fence));
}
void RenderContext::wait_frame()
{
vkb::rendering::RenderFrameC &frame = get_active_frame();
frame.reset();
}
void RenderContext::end_frame(VkSemaphore semaphore)
{
assert(frame_active && "Frame is not active, please call begin_frame");
if (swapchain)
{
VkSwapchainKHR vk_swapchain = swapchain->get_handle();
VkPresentInfoKHR present_info{VK_STRUCTURE_TYPE_PRESENT_INFO_KHR};
present_info.waitSemaphoreCount = 1;
present_info.pWaitSemaphores = &semaphore;
present_info.swapchainCount = 1;
present_info.pSwapchains = &vk_swapchain;
present_info.pImageIndices = &active_frame_index;
VkDisplayPresentInfoKHR disp_present_info{};
if (device.get_gpu().is_extension_supported(VK_KHR_DISPLAY_SWAPCHAIN_EXTENSION_NAME) &&
window.get_display_present_info(&disp_present_info, surface_extent.width, surface_extent.height))
{
// Add display present info if supported and wanted
present_info.pNext = &disp_present_info;
}
VkResult result = queue.present(present_info);
if (result == VK_SUBOPTIMAL_KHR || result == VK_ERROR_OUT_OF_DATE_KHR)
{
handle_surface_changes();
}
}
// Frame is not active anymore
if (acquired_semaphore)
{
release_owned_semaphore(acquired_semaphore);
acquired_semaphore = VK_NULL_HANDLE;
}
frame_active = false;
}
VkSemaphore RenderContext::consume_acquired_semaphore()
{
assert(frame_active && "Frame is not active, please call begin_frame");
auto sem = acquired_semaphore;
acquired_semaphore = VK_NULL_HANDLE;
return sem;
}
vkb::rendering::RenderFrameC &RenderContext::get_active_frame()
{
assert(frame_active && "Frame is not active, please call begin_frame");
assert(active_frame_index < frames.size());
return *frames[active_frame_index];
}
uint32_t RenderContext::get_active_frame_index()
{
assert(frame_active && "Frame is not active, please call begin_frame");
return active_frame_index;
}
vkb::rendering::RenderFrameC &RenderContext::get_last_rendered_frame()
{
assert(!frame_active && "Frame is still active, please call end_frame");
assert(active_frame_index < frames.size());
return *frames[active_frame_index];
}
VkSemaphore RenderContext::request_semaphore()
{
vkb::rendering::RenderFrameC &frame = get_active_frame();
return frame.get_semaphore_pool().request_semaphore();
}
VkSemaphore RenderContext::request_semaphore_with_ownership()
{
vkb::rendering::RenderFrameC &frame = get_active_frame();
return frame.get_semaphore_pool().request_semaphore_with_ownership();
}
void RenderContext::release_owned_semaphore(VkSemaphore semaphore)
{
vkb::rendering::RenderFrameC &frame = get_active_frame();
frame.get_semaphore_pool().release_owned_semaphore(semaphore);
}
vkb::core::DeviceC &RenderContext::get_device()
{
return device;
}
void RenderContext::recreate_swapchain()
{
device.wait_idle();
device.get_resource_cache().clear_framebuffers();
VkExtent2D swapchain_extent = swapchain->get_extent();
VkExtent3D extent{swapchain_extent.width, swapchain_extent.height, 1};
auto frame_it = frames.begin();
for (auto &image_handle : swapchain->get_images())
{
core::Image swapchain_image{device, image_handle,
extent,
swapchain->get_format(),
swapchain->get_usage()};
auto render_target = create_render_target_func(std::move(swapchain_image));
(*frame_it)->update_render_target(std::move(render_target));
++frame_it;
}
}
bool RenderContext::has_swapchain()
{
return swapchain != nullptr;
}
Swapchain const &RenderContext::get_swapchain() const
{
assert(swapchain && "Swapchain is not valid");
return *swapchain;
}
VkExtent2D const &RenderContext::get_surface_extent() const
{
return surface_extent;
}
uint32_t RenderContext::get_active_frame_index() const
{
return active_frame_index;
}
std::vector<std::unique_ptr<vkb::rendering::RenderFrameC>> &RenderContext::get_render_frames()
{
return frames;
}
} // namespace vkb
+272
View File
@@ -0,0 +1,272 @@
/* Copyright (c) 2019-2025, Arm Limited and Contributors
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 the "License";
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "common/helpers.h"
#include "common/vk_common.h"
#include "core/command_buffer.h"
#include "core/command_pool.h"
#include "core/descriptor_set.h"
#include "core/descriptor_set_layout.h"
#include "core/framebuffer.h"
#include "core/pipeline.h"
#include "core/pipeline_layout.h"
#include "core/queue.h"
#include "core/render_pass.h"
#include "core/shader_module.h"
#include "core/swapchain.h"
#include "rendering/pipeline_state.h"
#include "rendering/render_frame.h"
#include "rendering/render_target.h"
#include "resource_cache.h"
namespace vkb
{
class Window;
/**
* @brief RenderContext acts as a frame manager for the sample, with a lifetime that is the
* same as that of the Application itself. It acts as a container for RenderFrame objects,
* swapping between them (begin_frame, end_frame) and forwarding requests for Vulkan resources
* to the active frame. Note that it's guaranteed that there is always an active frame.
* More than one frame can be in-flight in the GPU, thus the need for per-frame resources.
*
* It requires a Device to be valid on creation, and will take control of a given Swapchain.
*
* For normal rendering (using a swapchain), the RenderContext can be created by passing in a
* swapchain. A RenderFrame will then be created for each Swapchain image.
*
* For offscreen rendering (no swapchain), the RenderContext can be given a valid Device, and
* a width and height. A single RenderFrame will then be created.
*/
class RenderContext
{
public:
// The format to use for the RenderTargets if a swapchain isn't created
static VkFormat DEFAULT_VK_FORMAT;
/**
* @brief Constructor
* @param device A valid device
* @param surface A surface, VK_NULL_HANDLE if in offscreen mode
* @param window The window where the surface was created
* @param present_mode Requests to set the present mode of the swapchain
* @param present_mode_priority_list The order in which the swapchain prioritizes selecting its present mode
* @param surface_format_priority_list The order in which the swapchain prioritizes selecting its surface format
*/
RenderContext(vkb::core::DeviceC &device,
VkSurfaceKHR surface,
const Window &window,
VkPresentModeKHR present_mode = VK_PRESENT_MODE_FIFO_KHR,
const std::vector<VkPresentModeKHR> &present_mode_priority_list = {VK_PRESENT_MODE_FIFO_KHR, VK_PRESENT_MODE_MAILBOX_KHR},
const std::vector<VkSurfaceFormatKHR> &surface_format_priority_list = {
{VK_FORMAT_R8G8B8A8_SRGB, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR},
{VK_FORMAT_B8G8R8A8_SRGB, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR}});
RenderContext(const RenderContext &) = delete;
RenderContext(RenderContext &&) = delete;
virtual ~RenderContext() = default;
RenderContext &operator=(const RenderContext &) = delete;
RenderContext &operator=(RenderContext &&) = delete;
/**
* @brief Prepares the RenderFrames for rendering
* @param thread_count The number of threads in the application, necessary to allocate this many resource pools for each RenderFrame
* @param create_render_target_func A function delegate, used to create a RenderTarget
*/
void prepare(size_t thread_count = 1, RenderTarget::CreateFunc create_render_target_func = RenderTarget::DEFAULT_CREATE_FUNC);
/**
* @brief Updates the swapchains extent, if a swapchain exists
* @param extent The width and height of the new swapchain images
*/
void update_swapchain(const VkExtent2D &extent);
/**
* @brief Updates the swapchains image count, if a swapchain exists
* @param image_count The amount of images in the new swapchain
*/
void update_swapchain(const uint32_t image_count);
/**
* @brief Updates the swapchains image usage, if a swapchain exists
* @param image_usage_flags The usage flags the new swapchain images will have
*/
void update_swapchain(const std::set<VkImageUsageFlagBits> &image_usage_flags);
/**
* @brief Updates the swapchains extent and surface transform, if a swapchain exists
* @param extent The width and height of the new swapchain images
* @param transform The surface transform flags
*/
void update_swapchain(const VkExtent2D &extent, const VkSurfaceTransformFlagBitsKHR transform);
/**
* @brief Updates the swapchain's compression settings, if a swapchain exists
* @param compression The compression to use for swapchain images (default, fixed-rate, none)
* @param compression_fixed_rate The rate to use, if compression is fixed-rate
*/
void update_swapchain(const VkImageCompressionFlagsEXT compression, const VkImageCompressionFixedRateFlagsEXT compression_fixed_rate);
/**
* @returns True if a valid swapchain exists in the RenderContext
*/
bool has_swapchain();
/**
* @brief Recreates the RenderFrames, called after every update
*/
void recreate();
/**
* @brief Recreates the swapchain
*/
void recreate_swapchain();
/**
* @brief Prepares the next available frame for rendering
* @param reset_mode How to reset the command buffer
* @returns A valid command buffer to record commands to be submitted
* Also ensures that there is an active frame if there is no existing active frame already
*/
std::shared_ptr<vkb::core::CommandBufferC> begin(vkb::CommandBufferResetMode reset_mode = vkb::CommandBufferResetMode::ResetPool);
/**
* @brief Submits the command buffer to the right queue
* @param command_buffer A command buffer containing recorded commands
*/
void submit(std::shared_ptr<vkb::core::CommandBufferC> command_buffer);
/**
* @brief Submits multiple command buffers to the right queue
* @param command_buffers Command buffers containing recorded commands
*/
void submit(const std::vector<std::shared_ptr<vkb::core::CommandBufferC>> &command_buffers);
/**
* @brief begin_frame
*/
void begin_frame();
VkSemaphore submit(const Queue &queue,
const std::vector<std::shared_ptr<vkb::core::CommandBufferC>> &command_buffers,
VkSemaphore wait_semaphore,
VkPipelineStageFlags wait_pipeline_stage);
/**
* @brief Submits a command buffer related to a frame to a queue
*/
void submit(const Queue &queue, const std::vector<std::shared_ptr<vkb::core::CommandBufferC>> &command_buffers);
/**
* @brief Waits a frame to finish its rendering
*/
virtual void wait_frame();
void end_frame(VkSemaphore semaphore);
/**
* @brief An error should be raised if the frame is not active.
* A frame is active after @ref begin_frame has been called.
* @return The current active frame
*/
vkb::rendering::RenderFrameC &get_active_frame();
/**
* @brief An error should be raised if the frame is not active.
* A frame is active after @ref begin_frame has been called.
* @return The current active frame index
*/
uint32_t get_active_frame_index();
/**
* @brief An error should be raised if a frame is active.
* A frame is active after @ref begin_frame has been called.
* @return The previous frame
*/
vkb::rendering::RenderFrameC &get_last_rendered_frame();
VkSemaphore request_semaphore();
VkSemaphore request_semaphore_with_ownership();
void release_owned_semaphore(VkSemaphore semaphore);
vkb::core::DeviceC &get_device();
/**
* @brief Returns the format that the RenderTargets are created with within the RenderContext
*/
VkFormat get_format() const;
Swapchain const &get_swapchain() const;
VkExtent2D const &get_surface_extent() const;
uint32_t get_active_frame_index() const;
std::vector<std::unique_ptr<vkb::rendering::RenderFrameC>> &get_render_frames();
/**
* @brief Handles surface changes, only applicable if the render_context makes use of a swapchain
*/
virtual bool handle_surface_changes(bool force_update = false);
/**
* @brief Returns the WSI acquire semaphore. Only to be used in very special circumstances.
* @return The WSI acquire semaphore.
*/
VkSemaphore consume_acquired_semaphore();
protected:
VkExtent2D surface_extent;
private:
vkb::core::DeviceC &device;
const Window &window;
/// If swapchain exists, then this will be a present supported queue, else a graphics queue
const Queue &queue;
std::unique_ptr<Swapchain> swapchain;
SwapchainProperties swapchain_properties;
std::vector<std::unique_ptr<vkb::rendering::RenderFrameC>> frames;
VkSemaphore acquired_semaphore;
bool prepared{false};
/// Current active frame index
uint32_t active_frame_index{0};
/// Whether a frame is active or not
bool frame_active{false};
RenderTarget::CreateFunc create_render_target_func = RenderTarget::DEFAULT_CREATE_FUNC;
VkSurfaceTransformFlagBitsKHR pre_transform{VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR};
size_t thread_count{1};
};
} // namespace vkb
+563
View File
@@ -0,0 +1,563 @@
/* Copyright (c) 2019-2025, Arm Limited and Contributors
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 the "License";
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "buffer_pool.h"
#include "common/hpp_resource_caching.h"
#include "core/command_pool.h"
#include "core/hpp_queue.h"
#include "core/queue.h"
#include "hpp_semaphore_pool.h"
namespace vkb
{
namespace rendering
{
enum BufferAllocationStrategy
{
OneAllocationPerBuffer,
MultipleAllocationsPerBuffer
};
enum DescriptorManagementStrategy
{
StoreInCache,
CreateDirectly
};
/**
* @brief RenderFrame is a container for per-frame data, including BufferPool objects,
* synchronization primitives (semaphores, fences) and the swapchain RenderTarget.
*
* When creating a RenderTarget, we need to provide images that will be used as attachments
* within a RenderPass. The RenderFrame is responsible for creating a RenderTarget using
* RenderTarget::CreateFunc. A custom RenderTarget::CreateFunc can be provided if a different
* render target is required.
*
* A RenderFrame cannot be destroyed individually since frames are managed by the RenderContext,
* the whole context must be destroyed. This is because each RenderFrame holds Vulkan objects
* such as the swapchain image.
*/
template <vkb::BindingType bindingType>
class RenderFrame
{
public:
using BufferUsageFlagsType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vk::BufferUsageFlags, VkBufferUsageFlags>::type;
using CommandBufferLevelType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vk::CommandBufferLevel, VkCommandBufferLevel>::type;
using DescriptorBufferInfoType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vk::DescriptorBufferInfo, VkDescriptorBufferInfo>::type;
using DescriptorImageInfoType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vk::DescriptorImageInfo, VkDescriptorImageInfo>::type;
using DescriptorSetType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vk::DescriptorSet, VkDescriptorSet>::type;
using DeviceSizeType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vk::DeviceSize, VkDeviceSize>::type;
using FenceType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vk::Fence, VkFence>::type;
using SemaphoreType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vk::Semaphore, VkSemaphore>::type;
using DescriptorSetLayoutType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vkb::core::HPPDescriptorSetLayout, vkb::DescriptorSetLayout>::type;
using FencePoolType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vkb::HPPFencePool, vkb::FencePool>::type;
using QueueType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vkb::core::HPPQueue, vkb::Queue>::type;
using RenderTargetType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vkb::rendering::HPPRenderTarget, vkb::RenderTarget>::type;
using SemaphorePoolType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vkb::HPPSemaphorePool, vkb::SemaphorePool>::type;
public:
RenderFrame(vkb::core::Device<bindingType> &device, std::unique_ptr<RenderTargetType> &&render_target, size_t thread_count = 1);
RenderFrame(RenderFrame<bindingType> const &) = delete;
RenderFrame(RenderFrame<bindingType> &&) = default;
RenderFrame &operator=(RenderFrame<bindingType> const &) = delete;
RenderFrame &operator=(RenderFrame<bindingType> &&) = default;
/**
* @param usage Usage of the buffer
* @param size Amount of memory required
* @param thread_index Index of the buffer pool to be used by the current thread
* @return The requested allocation, it may be empty
*/
vkb::BufferAllocation<bindingType> allocate_buffer(BufferUsageFlagsType usage, DeviceSizeType size, size_t thread_index = 0);
void clear_descriptors();
/**
* @brief Get the command pool of the active frame
* A frame should be active at the moment of requesting it
* @param queue The queue command buffers will be submitted on
* @param reset_mode Indicate how the command buffer will be used, may trigger a pool re-creation to set necessary flags
* @param thread_index Selects the thread's command pool used to manage the buffer
* @return The command pool related to the current active frame
*/
vkb::core::CommandPool<bindingType> &get_command_pool(
QueueType const &queue, vkb::CommandBufferResetMode reset_mode = vkb::CommandBufferResetMode::ResetPool, size_t thread_index = 0);
vkb::core::Device<bindingType> &get_device();
FencePoolType &get_fence_pool();
FencePoolType const &get_fence_pool() const;
RenderTargetType &get_render_target();
RenderTargetType const &get_render_target() const;
SemaphorePoolType &get_semaphore_pool();
SemaphorePoolType const &get_semaphore_pool() const;
DescriptorSetType request_descriptor_set(DescriptorSetLayoutType const &descriptor_set_layout,
BindingMap<DescriptorBufferInfoType> const &buffer_infos,
BindingMap<DescriptorImageInfoType> const &image_infos,
bool update_after_bind,
size_t thread_index = 0);
void reset();
/**
* @brief Sets a new buffer allocation strategy
* @param new_strategy The new buffer allocation strategy
*/
void set_buffer_allocation_strategy(BufferAllocationStrategy new_strategy);
/**
* @brief Sets a new descriptor set management strategy
* @param new_strategy The new descriptor set management strategy
*/
void set_descriptor_management_strategy(DescriptorManagementStrategy new_strategy);
/**
* @brief Updates all the descriptor sets in the current frame at a specific thread index
*/
void update_descriptor_sets(size_t thread_index = 0);
/**
* @brief Called when the swapchain changes
* @param render_target A new render target with updated images
*/
void update_render_target(std::unique_ptr<RenderTargetType> &&render_target);
private:
vkb::BufferAllocationCpp allocate_buffer_impl(vk::BufferUsageFlags usage, vk::DeviceSize size, size_t thread_index);
vkb::core::CommandPoolCpp &get_command_pool_impl(vkb::core::HPPQueue const &queue, vkb::CommandBufferResetMode reset_mode, size_t thread_index);
/**
* @brief Retrieve the frame's command pool(s)
* @param queue The queue command buffers will be submitted on
* @param reset_mode Indicate how the command buffers will be reset after execution,
* may trigger a pool re-creation to set necessary flags
* @return The frame's command pool(s)
*/
std::vector<vkb::core::CommandPoolCpp> &get_command_pools(const vkb::core::HPPQueue &queue, vkb::CommandBufferResetMode reset_mode);
vk::DescriptorSet request_descriptor_set_impl(vkb::core::HPPDescriptorSetLayout const &descriptor_set_layout,
BindingMap<vk::DescriptorBufferInfo> const &buffer_infos,
BindingMap<vk::DescriptorImageInfo> const &image_infos,
bool update_after_bind,
size_t thread_index = 0);
private:
vkb::core::DeviceCpp &device;
std::map<vk::BufferUsageFlags, std::vector<std::pair<vkb::BufferPoolCpp, vkb::BufferBlockCpp *>>> buffer_pools;
std::map<uint32_t, std::vector<vkb::core::CommandPoolCpp>> command_pools; // Commands pools per queue family index
std::vector<std::unordered_map<std::size_t, vkb::core::HPPDescriptorPool>> descriptor_pools; // Descriptor pools per thread
std::vector<std::unordered_map<std::size_t, vkb::core::HPPDescriptorSet>> descriptor_sets; // Descriptor sets per thread
vkb::HPPFencePool fence_pool;
vkb::HPPSemaphorePool semaphore_pool;
std::unique_ptr<vkb::rendering::HPPRenderTarget> swapchain_render_target;
size_t thread_count;
BufferAllocationStrategy buffer_allocation_strategy = BufferAllocationStrategy::MultipleAllocationsPerBuffer;
DescriptorManagementStrategy descriptor_management_strategy = DescriptorManagementStrategy::StoreInCache;
};
using RenderFrameC = RenderFrame<vkb::BindingType::C>;
using RenderFrameCpp = RenderFrame<vkb::BindingType::Cpp>;
template <vkb::BindingType bindingType>
inline RenderFrame<bindingType>::RenderFrame(vkb::core::Device<bindingType> &device_,
std::unique_ptr<RenderTargetType> &&render_target,
size_t thread_count) :
device(reinterpret_cast<vkb::core::DeviceCpp &>(device_)), fence_pool{device}, semaphore_pool{device}, thread_count{thread_count}, descriptor_pools(thread_count), descriptor_sets(thread_count)
{
static constexpr uint32_t BUFFER_POOL_BLOCK_SIZE = 256; // Block size of a buffer pool in kilobytes
// A map of the supported usages to a multiplier for the BUFFER_POOL_BLOCK_SIZE
static const std::unordered_map<vk::BufferUsageFlags, uint32_t> supported_usage_map = {
{vk::BufferUsageFlagBits::eUniformBuffer, 1},
{vk::BufferUsageFlagBits::eStorageBuffer, 2}, // x2 the size of BUFFER_POOL_BLOCK_SIZE since SSBOs are normally much larger than other types of buffers
{vk::BufferUsageFlagBits::eVertexBuffer, 1},
{vk::BufferUsageFlagBits::eIndexBuffer, 1}};
update_render_target(std::move(render_target));
for (auto &usage_it : supported_usage_map)
{
auto [buffer_pools_it, inserted] = buffer_pools.emplace(usage_it.first, std::vector<std::pair<vkb::BufferPoolCpp, vkb::BufferBlockCpp *>>{});
if (!inserted)
{
throw std::runtime_error("Failed to insert buffer pool");
}
for (size_t i = 0; i < thread_count; ++i)
{
buffer_pools_it->second.push_back(
std::make_pair(vkb::BufferPoolCpp{device, BUFFER_POOL_BLOCK_SIZE * 1024 * usage_it.second, usage_it.first}, nullptr));
}
}
}
template <vkb::BindingType bindingType>
inline BufferAllocation<bindingType> RenderFrame<bindingType>::allocate_buffer(BufferUsageFlagsType usage, DeviceSizeType size, size_t thread_index)
{
assert(thread_index < thread_count && "Thread index is out of bounds");
if constexpr (bindingType == vkb::BindingType::Cpp)
{
return allocate_buffer_impl(usage, size, thread_index);
}
else
{
vkb::BufferAllocationCpp buffer_allocation =
allocate_buffer_impl(static_cast<vk::BufferUsageFlags>(usage), static_cast<vk::DeviceSize>(size), thread_index);
return *reinterpret_cast<vkb::BufferAllocationC *>(&buffer_allocation);
}
}
template <vkb::BindingType bindingType>
inline vkb::BufferAllocationCpp RenderFrame<bindingType>::allocate_buffer_impl(vk::BufferUsageFlags usage, vk::DeviceSize size, size_t thread_index)
{
// Find a pool for this usage
auto buffer_pool_it = buffer_pools.find(usage);
if (buffer_pool_it == buffer_pools.end())
{
LOGE("No buffer pool for buffer usage {} ", vk::to_string(usage));
return vkb::BufferAllocationCpp{};
}
assert(thread_index < buffer_pool_it->second.size());
auto &buffer_pool = buffer_pool_it->second[thread_index].first;
auto &buffer_block = buffer_pool_it->second[thread_index].second;
bool want_minimal_block = (buffer_allocation_strategy == BufferAllocationStrategy::OneAllocationPerBuffer);
if (want_minimal_block || !buffer_block || !buffer_block->can_allocate(size))
{
// If we are creating a buffer for each allocation of there is no block associated with the pool or the current block is too small
// for this allocation, request a new buffer block
buffer_block = &buffer_pool.request_buffer_block(size, want_minimal_block);
}
return buffer_block->allocate(to_u32(size));
}
template <vkb::BindingType bindingType>
inline void RenderFrame<bindingType>::clear_descriptors()
{
for (auto &desc_sets_per_thread : descriptor_sets)
{
desc_sets_per_thread.clear();
}
for (auto &desc_pools_per_thread : descriptor_pools)
{
for (auto &desc_pool : desc_pools_per_thread)
{
desc_pool.second.reset();
}
}
}
template <vkb::BindingType bindingType>
inline std::vector<vkb::core::CommandPoolCpp> &RenderFrame<bindingType>::get_command_pools(const vkb::core::HPPQueue &queue,
vkb::CommandBufferResetMode reset_mode)
{
auto command_pool_it = command_pools.find(queue.get_family_index());
if (command_pool_it != command_pools.end())
{
assert(!command_pool_it->second.empty());
if (command_pool_it->second[0].get_reset_mode() != reset_mode)
{
device.get_handle().waitIdle();
// Delete pools
command_pools.erase(command_pool_it);
}
else
{
return command_pool_it->second;
}
}
bool inserted = false;
std::tie(command_pool_it, inserted) = command_pools.emplace(queue.get_family_index(), std::vector<vkb::core::CommandPoolCpp>{});
if (!inserted)
{
throw std::runtime_error("Failed to insert command pool");
}
for (size_t i = 0; i < thread_count; i++)
{
command_pool_it->second.emplace_back(device, queue.get_family_index(), reinterpret_cast<vkb::rendering::RenderFrameCpp *>(this), i, reset_mode);
}
return command_pool_it->second;
}
template <vkb::BindingType bindingType>
inline vkb::core::CommandPool<bindingType> &
RenderFrame<bindingType>::get_command_pool(QueueType const &queue, vkb::CommandBufferResetMode reset_mode, size_t thread_index)
{
if constexpr (bindingType == vkb::BindingType::Cpp)
{
return get_command_pool_impl(queue, reset_mode, thread_index);
}
else
{
return reinterpret_cast<vkb::core::CommandPoolC &>(
get_command_pool_impl(reinterpret_cast<vkb::core::HPPQueue const &>(queue), reset_mode, thread_index));
}
}
template <vkb::BindingType bindingType>
inline vkb::core::CommandPoolCpp &
RenderFrame<bindingType>::get_command_pool_impl(vkb::core::HPPQueue const &queue, vkb::CommandBufferResetMode reset_mode, size_t thread_index)
{
assert(thread_index < thread_count && "Thread index is out of bounds");
auto &command_pools = get_command_pools(queue, reset_mode);
auto command_pool_it = std::ranges::find_if(
command_pools, [&thread_index](vkb::core::CommandPoolCpp &cmd_pool) { return cmd_pool.get_thread_index() == thread_index; });
assert(command_pool_it != command_pools.end());
return *command_pool_it;
}
template <vkb::BindingType bindingType>
inline typename vkb::core::Device<bindingType> &RenderFrame<bindingType>::get_device()
{
if constexpr (bindingType == vkb::BindingType::Cpp)
{
return device;
}
else
{
return reinterpret_cast<vkb::core::DeviceC &>(device);
}
}
template <vkb::BindingType bindingType>
inline typename RenderFrame<bindingType>::FencePoolType &RenderFrame<bindingType>::get_fence_pool()
{
if constexpr (bindingType == vkb::BindingType::Cpp)
{
return fence_pool;
}
else
{
return reinterpret_cast<vkb::FencePool &>(fence_pool);
}
}
template <vkb::BindingType bindingType>
inline typename RenderFrame<bindingType>::FencePoolType const &RenderFrame<bindingType>::get_fence_pool() const
{
if constexpr (bindingType == vkb::BindingType::Cpp)
{
return fence_pool;
}
else
{
return reinterpret_cast<vkb::FencePool const &>(fence_pool);
}
}
template <vkb::BindingType bindingType>
inline typename RenderFrame<bindingType>::RenderTargetType &RenderFrame<bindingType>::get_render_target()
{
if constexpr (bindingType == vkb::BindingType::Cpp)
{
return *swapchain_render_target;
}
else
{
return reinterpret_cast<vkb::RenderTarget &>(*swapchain_render_target);
}
}
template <vkb::BindingType bindingType>
inline typename RenderFrame<bindingType>::RenderTargetType const &RenderFrame<bindingType>::get_render_target() const
{
if constexpr (bindingType == vkb::BindingType::Cpp)
{
return *swapchain_render_target;
}
else
{
return reinterpret_cast<vkb::RenderTarget const &>(*swapchain_render_target);
}
}
template <vkb::BindingType bindingType>
inline typename RenderFrame<bindingType>::SemaphorePoolType &RenderFrame<bindingType>::get_semaphore_pool()
{
if constexpr (bindingType == vkb::BindingType::Cpp)
{
return semaphore_pool;
}
else
{
return reinterpret_cast<vkb::SemaphorePool &>(semaphore_pool);
}
}
template <vkb::BindingType bindingType>
inline typename RenderFrame<bindingType>::SemaphorePoolType const &RenderFrame<bindingType>::get_semaphore_pool() const
{
if constexpr (bindingType == vkb::BindingType::Cpp)
{
return semaphore_pool;
}
else
{
return reinterpret_cast<vkb::SemaphorePool const &>(semaphore_pool);
}
}
template <vkb::BindingType bindingType>
inline typename RenderFrame<bindingType>::DescriptorSetType RenderFrame<bindingType>::request_descriptor_set(DescriptorSetLayoutType const &descriptor_set_layout,
BindingMap<DescriptorBufferInfoType> const &buffer_infos,
BindingMap<DescriptorImageInfoType> const &image_infos,
bool update_after_bind,
size_t thread_index)
{
assert(thread_index < thread_count && "Thread index is out of bounds");
assert(thread_index < descriptor_pools.size());
if constexpr (bindingType == vkb::BindingType::Cpp)
{
return request_descriptor_set_impl(descriptor_set_layout, buffer_infos, image_infos, update_after_bind, thread_index);
}
else
{
return static_cast<VkDescriptorSet>(request_descriptor_set_impl(reinterpret_cast<vkb::core::HPPDescriptorSetLayout const &>(descriptor_set_layout),
reinterpret_cast<BindingMap<vk::DescriptorBufferInfo> const &>(buffer_infos),
reinterpret_cast<BindingMap<vk::DescriptorImageInfo> const &>(image_infos),
update_after_bind,
thread_index));
}
}
template <vkb::BindingType bindingType>
inline vk::DescriptorSet RenderFrame<bindingType>::request_descriptor_set_impl(vkb::core::HPPDescriptorSetLayout const &descriptor_set_layout,
BindingMap<vk::DescriptorBufferInfo> const &buffer_infos,
BindingMap<vk::DescriptorImageInfo> const &image_infos,
bool update_after_bind,
size_t thread_index)
{
auto &descriptor_pool = vkb::common::request_resource(device, nullptr, descriptor_pools[thread_index], descriptor_set_layout);
if (descriptor_management_strategy == DescriptorManagementStrategy::StoreInCache)
{
// The bindings we want to update before binding, if empty we update all bindings
std::set<uint32_t> bindings_to_update;
// If update after bind is enabled, we store the binding index of each binding that need to be updated before being bound
if (update_after_bind)
{
auto aggregate_binding_to_update = [&bindings_to_update, &descriptor_set_layout](const auto &infos_map) {
for (const auto &[binding_index, ignored] : infos_map)
{
if (!(descriptor_set_layout.get_layout_binding_flag(binding_index) & vk::DescriptorBindingFlagBits::eUpdateAfterBind))
{
bindings_to_update.insert(binding_index);
}
}
};
aggregate_binding_to_update(buffer_infos);
aggregate_binding_to_update(image_infos);
}
// Request a descriptor set from the render frame, and write the buffer infos and image infos of all the specified bindings
assert(thread_index < descriptor_sets.size());
auto &descriptor_set =
vkb::common::request_resource(device, nullptr, descriptor_sets[thread_index], descriptor_set_layout, descriptor_pool, buffer_infos, image_infos);
descriptor_set.update({bindings_to_update.begin(), bindings_to_update.end()});
return descriptor_set.get_handle();
}
else
{
// Request a descriptor pool, allocate a descriptor set, write buffer and image data to it
vkb::core::HPPDescriptorSet descriptor_set{device, descriptor_set_layout, descriptor_pool, buffer_infos, image_infos};
descriptor_set.apply_writes();
return descriptor_set.get_handle();
}
}
template <vkb::BindingType bindingType>
inline void RenderFrame<bindingType>::reset()
{
VK_CHECK(fence_pool.wait());
fence_pool.reset();
for (auto &command_pools_per_queue : command_pools)
{
for (auto &command_pool : command_pools_per_queue.second)
{
command_pool.reset_pool();
}
}
for (auto &buffer_pools_per_usage : buffer_pools)
{
for (auto &buffer_pool : buffer_pools_per_usage.second)
{
buffer_pool.first.reset();
buffer_pool.second = nullptr;
}
}
semaphore_pool.reset();
if (descriptor_management_strategy == DescriptorManagementStrategy::CreateDirectly)
{
clear_descriptors();
}
}
template <vkb::BindingType bindingType>
inline void RenderFrame<bindingType>::set_buffer_allocation_strategy(BufferAllocationStrategy new_strategy)
{
buffer_allocation_strategy = new_strategy;
}
template <vkb::BindingType bindingType>
inline void RenderFrame<bindingType>::set_descriptor_management_strategy(DescriptorManagementStrategy new_strategy)
{
descriptor_management_strategy = new_strategy;
}
template <vkb::BindingType bindingType>
inline void RenderFrame<bindingType>::update_descriptor_sets(size_t thread_index)
{
assert(thread_index < descriptor_sets.size());
auto &thread_descriptor_sets = descriptor_sets[thread_index];
for (auto &descriptor_set_it : thread_descriptor_sets)
{
descriptor_set_it.second.update();
}
}
template <vkb::BindingType bindingType>
inline void RenderFrame<bindingType>::update_render_target(std::unique_ptr<RenderTargetType> &&render_target)
{
if constexpr (bindingType == vkb::BindingType::Cpp)
{
swapchain_render_target = std::move(render_target);
}
else
{
swapchain_render_target.reset(reinterpret_cast<vkb::rendering::HPPRenderTarget *>(render_target.release()));
}
}
} // namespace rendering
} // namespace vkb
+132
View File
@@ -0,0 +1,132 @@
/* Copyright (c) 2019-2025, Arm Limited and Contributors
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 the "License";
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "render_pipeline.h"
#include "scene_graph/components/camera.h"
#include "scene_graph/components/image.h"
#include "scene_graph/components/material.h"
#include "scene_graph/components/mesh.h"
#include "scene_graph/components/pbr_material.h"
#include "scene_graph/components/sampler.h"
#include "scene_graph/components/sub_mesh.h"
#include "scene_graph/components/texture.h"
#include "scene_graph/node.h"
namespace vkb
{
RenderPipeline::RenderPipeline(std::vector<std::unique_ptr<vkb::rendering::SubpassC>> &&subpasses_) :
subpasses{std::move(subpasses_)}
{
prepare();
// Default load/store for swapchain
load_store[0].load_op = VK_ATTACHMENT_LOAD_OP_CLEAR;
load_store[0].store_op = VK_ATTACHMENT_STORE_OP_STORE;
// Default load/store for depth attachment
load_store[1].load_op = VK_ATTACHMENT_LOAD_OP_CLEAR;
load_store[1].store_op = VK_ATTACHMENT_STORE_OP_DONT_CARE;
// Default clear value
clear_value[0].color = {0.0f, 0.0f, 0.0f, 1.0f};
clear_value[1].depthStencil = {0.0f, ~0U};
}
void RenderPipeline::prepare()
{
for (auto &subpass : subpasses)
{
subpass->prepare();
}
}
void RenderPipeline::add_subpass(std::unique_ptr<vkb::rendering::SubpassC> &&subpass)
{
subpass->prepare();
subpasses.emplace_back(std::move(subpass));
}
std::vector<std::unique_ptr<vkb::rendering::SubpassC>> &RenderPipeline::get_subpasses()
{
return subpasses;
}
const std::vector<LoadStoreInfo> &RenderPipeline::get_load_store() const
{
return load_store;
}
void RenderPipeline::set_load_store(const std::vector<LoadStoreInfo> &ls)
{
load_store = ls;
}
const std::vector<VkClearValue> &RenderPipeline::get_clear_value() const
{
return clear_value;
}
void RenderPipeline::set_clear_value(const std::vector<VkClearValue> &cv)
{
clear_value = cv;
}
void RenderPipeline::draw(vkb::core::CommandBufferC &command_buffer, RenderTarget &render_target, VkSubpassContents contents)
{
assert(!subpasses.empty() && "Render pipeline should contain at least one sub-pass");
// Pad clear values if they're less than render target attachments
while (clear_value.size() < render_target.get_attachments().size())
{
clear_value.push_back({0.0f, 0.0f, 0.0f, 1.0f});
}
for (size_t i = 0; i < subpasses.size(); ++i)
{
active_subpass_index = i;
auto &subpass = subpasses[i];
subpass->update_render_target_attachments(render_target);
if (i == 0)
{
command_buffer.begin_render_pass(render_target, load_store, clear_value, subpasses, contents);
}
else
{
command_buffer.next_subpass();
}
if (subpass->get_debug_name().empty())
{
subpass->set_debug_name(fmt::format("RP subpass #{}", i));
}
ScopedDebugLabel subpass_debug_label{command_buffer, subpass->get_debug_name().c_str()};
subpass->draw(command_buffer);
}
active_subpass_index = 0;
}
std::unique_ptr<vkb::rendering::SubpassC> &RenderPipeline::get_active_subpass()
{
return subpasses[active_subpass_index];
}
} // namespace vkb
+109
View File
@@ -0,0 +1,109 @@
/* Copyright (c) 2019-2025, Arm Limited and Contributors
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 the "License";
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "common/helpers.h"
#include "common/utils.h"
#include "core/buffer.h"
#include "rendering/render_frame.h"
#include "rendering/subpass.h"
namespace vkb
{
/**
* @brief A RenderPipeline is a sequence of Subpass objects.
* Subpass holds shaders and can draw the core::sg::Scene.
* More subpasses can be added to the sequence if required.
* For example, postprocessing can be implemented with two pipelines which
* share render targets.
*
* GeometrySubpass -> Processes Scene for Shaders, use by itself if shader requires no lighting
* ForwardSubpass -> Binds lights at the beginning of a GeometrySubpass to create Forward Rendering, should be used with most default shaders
* LightingSubpass -> Holds a Global Light uniform, Can be combined with GeometrySubpass to create Deferred Rendering
*/
class RenderPipeline
{
public:
RenderPipeline(std::vector<std::unique_ptr<vkb::rendering::SubpassC>> &&subpasses = {});
RenderPipeline(const RenderPipeline &) = delete;
RenderPipeline(RenderPipeline &&) = default;
virtual ~RenderPipeline() = default;
RenderPipeline &operator=(const RenderPipeline &) = delete;
RenderPipeline &operator=(RenderPipeline &&) = default;
/**
* @brief Prepares the subpasses
*/
void prepare();
/**
* @return Load store info
*/
const std::vector<LoadStoreInfo> &get_load_store() const;
/**
* @param load_store Load store info to set
*/
void set_load_store(const std::vector<LoadStoreInfo> &load_store);
/**
* @return Clear values
*/
const std::vector<VkClearValue> &get_clear_value() const;
/**
* @param clear_values Clear values to set
*/
void set_clear_value(const std::vector<VkClearValue> &clear_values);
/**
* @brief Appends a subpass to the pipeline
* @param subpass Subpass to append
*/
void add_subpass(std::unique_ptr<vkb::rendering::SubpassC> &&subpass);
std::vector<std::unique_ptr<vkb::rendering::SubpassC>> &get_subpasses();
/**
* @brief Record draw commands for each Subpass
*/
void draw(vkb::core::CommandBufferC &command_buffer, RenderTarget &render_target, VkSubpassContents contents = VK_SUBPASS_CONTENTS_INLINE);
/**
* @return Subpass currently being recorded, or the first one
* if drawing has not started
*/
std::unique_ptr<vkb::rendering::SubpassC> &get_active_subpass();
private:
std::vector<std::unique_ptr<vkb::rendering::SubpassC>> subpasses;
/// Default to two load store
std::vector<LoadStoreInfo> load_store = std::vector<LoadStoreInfo>(2);
/// Default to two clear values
std::vector<VkClearValue> clear_value = std::vector<VkClearValue>(2);
size_t active_subpass_index{0};
};
} // namespace vkb
+168
View File
@@ -0,0 +1,168 @@
/* Copyright (c) 2019-2025, Arm Limited and Contributors
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 the "License";
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "rendering/render_target.h"
#include "core/device.h"
#include "core/image.h"
namespace vkb
{
namespace
{
struct CompareExtent2D
{
bool operator()(const VkExtent2D &lhs, const VkExtent2D &rhs) const
{
return !(lhs.width == rhs.width && lhs.height == rhs.height) && (lhs.width < rhs.width && lhs.height < rhs.height);
}
};
} // namespace
Attachment::Attachment(VkFormat format, VkSampleCountFlagBits samples, VkImageUsageFlags usage) :
format{format},
samples{samples},
usage{usage}
{
}
const RenderTarget::CreateFunc RenderTarget::DEFAULT_CREATE_FUNC = [](core::Image &&swapchain_image) -> std::unique_ptr<RenderTarget> {
VkFormat depth_format = get_suitable_depth_format(swapchain_image.get_device().get_gpu().get_handle());
core::Image depth_image{swapchain_image.get_device(), swapchain_image.get_extent(),
depth_format,
VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT,
VMA_MEMORY_USAGE_GPU_ONLY};
std::vector<core::Image> images;
images.push_back(std::move(swapchain_image));
images.push_back(std::move(depth_image));
return std::make_unique<RenderTarget>(std::move(images));
};
vkb::RenderTarget::RenderTarget(std::vector<core::Image> &&images) :
device{images.back().get_device()},
images{std::move(images)}
{
assert(!this->images.empty() && "Should specify at least 1 image");
std::set<VkExtent2D, CompareExtent2D> unique_extent;
// Returns the image extent as a VkExtent2D structure from a VkExtent3D
auto get_image_extent = [](const core::Image &image) { return VkExtent2D{image.get_extent().width, image.get_extent().height}; };
// Constructs a set of unique image extents given a vector of images
std::transform(this->images.begin(), this->images.end(), std::inserter(unique_extent, unique_extent.end()), get_image_extent);
// Allow only one extent size for a render target
if (unique_extent.size() != 1)
{
throw VulkanException{VK_ERROR_INITIALIZATION_FAILED, "Extent size is not unique"};
}
extent = *unique_extent.begin();
for (auto &image : this->images)
{
if (image.get_type() != VK_IMAGE_TYPE_2D)
{
throw VulkanException{VK_ERROR_INITIALIZATION_FAILED, "Image type is not 2D"};
}
views.emplace_back(image, VK_IMAGE_VIEW_TYPE_2D);
attachments.emplace_back(Attachment{image.get_format(), image.get_sample_count(), image.get_usage()});
}
}
vkb::RenderTarget::RenderTarget(std::vector<core::ImageView> &&image_views) :
device{const_cast<core::Image &>(image_views.back().get_image()).get_device()},
images{},
views{std::move(image_views)}
{
assert(!views.empty() && "Should specify at least 1 image view");
std::set<VkExtent2D, CompareExtent2D> unique_extent;
// Returns the extent of the base mip level pointed at by a view
auto get_view_extent = [](const core::ImageView &view) {
const VkExtent3D mip0_extent = view.get_image().get_extent();
const uint32_t mip_level = view.get_subresource_range().baseMipLevel;
return VkExtent2D{mip0_extent.width >> mip_level, mip0_extent.height >> mip_level};
};
// Constructs a set of unique image extents given a vector of image views;
// allow only one extent size for a render target
std::transform(views.begin(), views.end(), std::inserter(unique_extent, unique_extent.end()), get_view_extent);
if (unique_extent.size() != 1)
{
throw VulkanException{VK_ERROR_INITIALIZATION_FAILED, "Extent size is not unique"};
}
extent = *unique_extent.begin();
for (auto &view : views)
{
const auto &image = view.get_image();
attachments.emplace_back(Attachment{image.get_format(), image.get_sample_count(), image.get_usage()});
}
}
const VkExtent2D &RenderTarget::get_extent() const
{
return extent;
}
const std::vector<core::ImageView> &RenderTarget::get_views() const
{
return views;
}
const std::vector<Attachment> &RenderTarget::get_attachments() const
{
return attachments;
}
void RenderTarget::set_input_attachments(std::vector<uint32_t> &input)
{
input_attachments = input;
}
const std::vector<uint32_t> &RenderTarget::get_input_attachments() const
{
return input_attachments;
}
void RenderTarget::set_output_attachments(std::vector<uint32_t> &output)
{
output_attachments = output;
}
const std::vector<uint32_t> &RenderTarget::get_output_attachments() const
{
return output_attachments;
}
void RenderTarget::set_layout(uint32_t attachment, VkImageLayout layout)
{
attachments[attachment].initial_layout = layout;
}
VkImageLayout RenderTarget::get_layout(uint32_t attachment) const
{
return attachments[attachment].initial_layout;
}
} // namespace vkb
+125
View File
@@ -0,0 +1,125 @@
/* Copyright (c) 2019-2025, Arm Limited and Contributors
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 the "License";
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "common/helpers.h"
#include "common/vk_common.h"
#include "core/image_view.h"
namespace vkb
{
/**
* @brief Description of render pass attachments.
* Attachment descriptions can be used to automatically create render target images.
*/
struct Attachment
{
VkFormat format{VK_FORMAT_UNDEFINED};
VkSampleCountFlagBits samples{VK_SAMPLE_COUNT_1_BIT};
VkImageUsageFlags usage{VK_IMAGE_USAGE_SAMPLED_BIT};
VkImageLayout initial_layout{VK_IMAGE_LAYOUT_UNDEFINED};
Attachment() = default;
Attachment(VkFormat format, VkSampleCountFlagBits samples, VkImageUsageFlags usage);
};
namespace core
{
template <vkb::BindingType bindingType>
class Device;
using DeviceC = Device<vkb::BindingType::C>;
} // namespace core
/**
* @brief RenderTarget contains three vectors for: core::Image, core::ImageView and Attachment.
* The first two are Vulkan images and corresponding image views respectively.
* Attachment (s) contain a description of the images, which has two main purposes:
* - RenderPass creation only needs a list of Attachment (s), not the actual images, so we keep
* the minimum amount of information necessary
* - Creation of a RenderTarget becomes simpler, because the caller can just ask for some
* Attachment (s) without having to create the images
*/
class RenderTarget
{
public:
using CreateFunc = std::function<std::unique_ptr<RenderTarget>(core::Image &&)>;
static const CreateFunc DEFAULT_CREATE_FUNC;
RenderTarget(std::vector<core::Image> &&images);
RenderTarget(std::vector<core::ImageView> &&image_views);
RenderTarget(const RenderTarget &) = delete;
RenderTarget(RenderTarget &&) = delete;
RenderTarget &operator=(const RenderTarget &other) noexcept = delete;
RenderTarget &operator=(RenderTarget &&other) noexcept = delete;
const VkExtent2D &get_extent() const;
const std::vector<core::ImageView> &get_views() const;
const std::vector<Attachment> &get_attachments() const;
/**
* @brief Sets the current input attachments overwriting the current ones
* Should be set before beginning the render pass and before starting a new subpass
* @param input Set of attachment reference number to use as input
*/
void set_input_attachments(std::vector<uint32_t> &input);
const std::vector<uint32_t> &get_input_attachments() const;
/**
* @brief Sets the current output attachments overwriting the current ones
* Should be set before beginning the render pass and before starting a new subpass
* @param output Set of attachment reference number to use as output
*/
void set_output_attachments(std::vector<uint32_t> &output);
const std::vector<uint32_t> &get_output_attachments() const;
void set_layout(uint32_t attachment, VkImageLayout layout);
VkImageLayout get_layout(uint32_t attachment) const;
private:
vkb::core::DeviceC const &device;
VkExtent2D extent{};
std::vector<core::Image> images;
std::vector<core::ImageView> views;
std::vector<Attachment> attachments;
/// By default there are no input attachments
std::vector<uint32_t> input_attachments = {};
/// By default the output attachments is attachment 0
std::vector<uint32_t> output_attachments = {0};
};
} // namespace vkb
+488
View File
@@ -0,0 +1,488 @@
/* Copyright (c) 2019-2025, Arm Limited and Contributors
* Copyright (c) 2024-2025, NVIDIA CORPORATION. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 the "License";
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "buffer_pool.h"
#include "rendering/hpp_pipeline_state.h"
#include "rendering/hpp_render_target.h"
#include "rendering/pipeline_state.h"
#include "rendering/render_frame.h"
#include "scene_graph/components/light.h"
#include "scene_graph/node.h"
namespace vkb
{
class RenderContext;
class RenderTarget;
class ShaderSource;
namespace core
{
template <vkb::BindingType bindingType>
class CommandBuffer;
}
namespace rendering
{
class HPPRenderContext;
struct alignas(16) Light
{
glm::vec4 position; // position.w represents type of light
glm::vec4 color; // color.w represents light intensity
glm::vec4 direction; // direction.w represents range
glm::vec2 info; // (only used for spot lights) info.x represents light inner cone angle, info.y represents light outer cone angle
};
template <vkb::BindingType bindingType>
struct LightingState
{
std::vector<Light> directional_lights;
std::vector<Light> point_lights;
std::vector<Light> spot_lights;
BufferAllocation<bindingType> light_buffer;
};
using LightingStateC = LightingState<vkb::BindingType::C>;
using LightingStateCpp = LightingState<vkb::BindingType::Cpp>;
/**
* @brief Calculates the vulkan style projection matrix
* @param proj The projection matrix
* @return The vulkan style projection matrix
*/
glm::mat4 vulkan_style_projection(const glm::mat4 &proj);
inline const std::vector<std::string> light_type_definitions = {
"DIRECTIONAL_LIGHT " + std::to_string(static_cast<float>(sg::LightType::Directional)),
"POINT_LIGHT " + std::to_string(static_cast<float>(sg::LightType::Point)),
"SPOT_LIGHT " + std::to_string(static_cast<float>(sg::LightType::Spot))};
/**
* @brief This class defines an interface for subpasses
* where they need to implement the draw function.
* It is used to construct a RenderPipeline
*/
template <vkb::BindingType bindingType>
class Subpass
{
using ResolveModeFlagBitsType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vk::ResolveModeFlagBits, VkResolveModeFlagBits>::type;
using SampleCountflagBitsType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vk::SampleCountFlagBits, VkSampleCountFlagBits>::type;
using DepthStencilStateType =
typename std::conditional<bindingType == vkb::BindingType::Cpp, vkb::rendering::HPPDepthStencilState, vkb::DepthStencilState>::type;
using RenderContextType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vkb::rendering::HPPRenderContext, vkb::RenderContext>::type;
using RenderTargetType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vkb::rendering::HPPRenderTarget, vkb::RenderTarget>::type;
public:
Subpass(RenderContextType &render_context, ShaderSource &&vertex_shader, ShaderSource &&fragment_shader);
Subpass(const Subpass &) = delete;
Subpass(Subpass &&) = default;
virtual ~Subpass() = default;
Subpass &operator=(const Subpass &) = delete;
Subpass &operator=(Subpass &&) = delete;
/**
* @brief Draw virtual function
* @param command_buffer Command buffer to use to record draw commands
*/
virtual void draw(vkb::core::CommandBuffer<bindingType> &command_buffer) = 0;
/**
* @brief Prepares the shaders and shader variants for a subpass
*/
virtual void prepare() = 0;
/**
* @brief Prepares the lighting state to have its lights
*
* @tparam A light structure that has 'directional_lights', 'point_lights' and 'spot_light' array fields defined.
* @param scene_lights All of the light components from the scene graph
* @param max_lights_per_type The maximum amount of lights allowed for any given type of light.
*/
template <typename T>
void allocate_lights(const std::vector<sg::Light *> &scene_lights,
size_t max_lights_per_type);
const std::vector<uint32_t> &get_color_resolve_attachments() const;
const std::string &get_debug_name() const;
const uint32_t &get_depth_stencil_resolve_attachment() const;
ResolveModeFlagBitsType get_depth_stencil_resolve_mode() const;
DepthStencilStateType &get_depth_stencil_state();
const bool &get_disable_depth_stencil_attachment() const;
const ShaderSource &get_fragment_shader() const;
const std::vector<uint32_t> &get_input_attachments() const;
LightingState<bindingType> &get_lighting_state();
const std::vector<uint32_t> &get_output_attachments() const;
RenderContextType &get_render_context();
std::unordered_map<std::string, ShaderResourceMode> const &get_resource_mode_map() const;
SampleCountflagBitsType get_sample_count() const;
const ShaderSource &get_vertex_shader() const;
void set_color_resolve_attachments(std::vector<uint32_t> const &color_resolve);
void set_debug_name(const std::string &name);
void set_disable_depth_stencil_attachment(bool disable_depth_stencil);
void set_depth_stencil_resolve_attachment(uint32_t depth_stencil_resolve);
void set_depth_stencil_resolve_mode(ResolveModeFlagBitsType mode);
void set_input_attachments(std::vector<uint32_t> const &input);
void set_output_attachments(std::vector<uint32_t> const &output);
void set_sample_count(SampleCountflagBitsType sample_count);
/**
* @brief Updates the render target attachments with the ones stored in this subpass
* This function is called by the RenderPipeline before beginning the render
* pass and before proceeding with a new subpass.
*/
void update_render_target_attachments(RenderTargetType &render_target);
private:
/// Default to no color resolve attachments
std::vector<uint32_t> color_resolve_attachments = {};
std::string debug_name{};
/**
* @brief When creating the renderpass, if not None, the resolve
* of the multisampled depth attachment will be enabled,
* with this mode, to depth_stencil_resolve_attachment
*/
vk::ResolveModeFlagBits depth_stencil_resolve_mode{vk::ResolveModeFlagBits::eNone};
vkb::rendering::HPPDepthStencilState depth_stencil_state{};
/**
* @brief When creating the renderpass, pDepthStencilAttachment will
* be set to nullptr, which disables depth testing
*/
bool disable_depth_stencil_attachment{false};
/// Default to no depth stencil resolve attachment
uint32_t depth_stencil_resolve_attachment{VK_ATTACHMENT_UNUSED};
/// The structure containing all the requested render-ready lights for the scene
LightingStateCpp lighting_state{};
ShaderSource fragment_shader;
/// Default to no input attachments
std::vector<uint32_t> input_attachments = {};
/// Default to swapchain output attachment
std::vector<uint32_t> output_attachments = {0};
vkb::rendering::HPPRenderContext &render_context;
// A map of shader resource names and the mode of constant data
std::unordered_map<std::string, ShaderResourceMode> resource_mode_map;
vk::SampleCountFlagBits sample_count{vk::SampleCountFlagBits::e1};
ShaderSource vertex_shader;
};
using SubpassC = Subpass<vkb::BindingType::C>;
using SubpassCpp = Subpass<vkb::BindingType::Cpp>;
} // namespace rendering
} // namespace vkb
#include "rendering/hpp_render_context.h"
namespace vkb
{
namespace rendering
{
inline glm::mat4 vulkan_style_projection(const glm::mat4 &proj)
{
// Flip Y in clipspace. X = -1, Y = -1 is topLeft in Vulkan.
glm::mat4 mat = proj;
mat[1][1] *= -1;
return mat;
}
template <vkb::BindingType bindingType>
inline Subpass<bindingType>::Subpass(RenderContextType &render_context, ShaderSource &&vertex_source, ShaderSource &&fragment_source) :
render_context{reinterpret_cast<vkb::rendering::HPPRenderContext &>(render_context)},
vertex_shader{std::move(vertex_source)},
fragment_shader{std::move(fragment_source)}
{
}
template <vkb::BindingType bindingType>
inline const std::vector<uint32_t> &Subpass<bindingType>::get_input_attachments() const
{
return input_attachments;
}
template <vkb::BindingType bindingType>
inline LightingState<bindingType> &Subpass<bindingType>::get_lighting_state()
{
if constexpr (bindingType == vkb::BindingType::Cpp)
{
return lighting_state;
}
else
{
return reinterpret_cast<LightingState<bindingType> &>(lighting_state);
}
}
template <vkb::BindingType bindingType>
inline const std::vector<uint32_t> &Subpass<bindingType>::get_output_attachments() const
{
return output_attachments;
}
template <vkb::BindingType bindingType>
inline typename vkb::rendering::Subpass<bindingType>::RenderContextType &Subpass<bindingType>::get_render_context()
{
if constexpr (bindingType == vkb::BindingType::Cpp)
{
return render_context;
}
else
{
return reinterpret_cast<vkb::RenderContext &>(render_context);
}
}
template <vkb::BindingType bindingType>
std::unordered_map<std::string, ShaderResourceMode> const &Subpass<bindingType>::get_resource_mode_map() const
{
return resource_mode_map;
}
template <vkb::BindingType bindingType>
inline typename Subpass<bindingType>::SampleCountflagBitsType Subpass<bindingType>::get_sample_count() const
{
if constexpr (bindingType == vkb::BindingType::Cpp)
{
return sample_count;
}
else
{
return static_cast<VkSampleCountFlagBits>(sample_count);
}
}
template <vkb::BindingType bindingType>
inline const ShaderSource &Subpass<bindingType>::get_vertex_shader() const
{
return vertex_shader;
}
template <vkb::BindingType bindingType>
template <typename T>
void Subpass<bindingType>::allocate_lights(const std::vector<sg::Light *> &scene_lights,
size_t max_lights_per_type)
{
lighting_state.directional_lights.clear();
lighting_state.point_lights.clear();
lighting_state.spot_lights.clear();
for (auto &scene_light : scene_lights)
{
const auto &properties = scene_light->get_properties();
auto &transform = scene_light->get_node()->get_transform();
Light light{{transform.get_translation(), static_cast<float>(scene_light->get_light_type())},
{properties.color, properties.intensity},
{transform.get_rotation() * properties.direction, properties.range},
{properties.inner_cone_angle, properties.outer_cone_angle}};
switch (scene_light->get_light_type())
{
case sg::LightType::Directional:
{
if (lighting_state.directional_lights.size() < max_lights_per_type)
{
lighting_state.directional_lights.push_back(light);
}
else
{
LOGE("Subpass::allocate_lights: exceeding max_lights_per_type of {} for directional lights", max_lights_per_type);
}
break;
}
case sg::LightType::Point:
{
if (lighting_state.point_lights.size() < max_lights_per_type)
{
lighting_state.point_lights.push_back(light);
}
else
{
LOGE("Subpass::allocate_lights: exceeding max_lights_per_type of {} for point lights", max_lights_per_type);
}
break;
}
case sg::LightType::Spot:
{
if (lighting_state.spot_lights.size() < max_lights_per_type)
{
lighting_state.spot_lights.push_back(light);
}
else
{
LOGE("Subpass::allocate_lights: exceeding max_lights_per_type of {} for spot lights", max_lights_per_type);
}
break;
}
default:
LOGE("Subpass::allocate_lights: encountered unknown light type {}", to_string(scene_light->get_light_type()));
break;
}
}
T light_info;
std::copy(lighting_state.directional_lights.begin(), lighting_state.directional_lights.end(), light_info.directional_lights);
std::copy(lighting_state.point_lights.begin(), lighting_state.point_lights.end(), light_info.point_lights);
std::copy(lighting_state.spot_lights.begin(), lighting_state.spot_lights.end(), light_info.spot_lights);
auto &render_frame = render_context.get_active_frame();
lighting_state.light_buffer = render_frame.allocate_buffer(vk::BufferUsageFlagBits::eUniformBuffer, sizeof(T));
lighting_state.light_buffer.update(light_info);
}
template <vkb::BindingType bindingType>
inline const std::vector<uint32_t> &Subpass<bindingType>::get_color_resolve_attachments() const
{
return color_resolve_attachments;
}
template <vkb::BindingType bindingType>
inline const std::string &Subpass<bindingType>::get_debug_name() const
{
return debug_name;
}
template <vkb::BindingType bindingType>
inline const uint32_t &Subpass<bindingType>::get_depth_stencil_resolve_attachment() const
{
return depth_stencil_resolve_attachment;
}
template <vkb::BindingType bindingType>
inline typename Subpass<bindingType>::ResolveModeFlagBitsType Subpass<bindingType>::get_depth_stencil_resolve_mode() const
{
if constexpr (bindingType == vkb::BindingType::Cpp)
{
return depth_stencil_resolve_mode;
}
else
{
return static_cast<VkResolveModeFlagBits>(depth_stencil_resolve_mode);
}
}
template <vkb::BindingType bindingType>
inline typename Subpass<bindingType>::DepthStencilStateType &Subpass<bindingType>::get_depth_stencil_state()
{
if constexpr (bindingType == vkb::BindingType::Cpp)
{
return depth_stencil_state;
}
else
{
return reinterpret_cast<vkb::DepthStencilState &>(depth_stencil_state);
}
}
template <vkb::BindingType bindingType>
inline const bool &Subpass<bindingType>::get_disable_depth_stencil_attachment() const
{
return disable_depth_stencil_attachment;
}
template <vkb::BindingType bindingType>
inline const ShaderSource &Subpass<bindingType>::get_fragment_shader() const
{
return fragment_shader;
}
template <vkb::BindingType bindingType>
inline void Subpass<bindingType>::set_color_resolve_attachments(std::vector<uint32_t> const &color_resolve)
{
color_resolve_attachments = color_resolve;
}
template <vkb::BindingType bindingType>
inline void Subpass<bindingType>::set_depth_stencil_resolve_attachment(uint32_t depth_stencil_resolve)
{
depth_stencil_resolve_attachment = depth_stencil_resolve;
}
template <vkb::BindingType bindingType>
inline void Subpass<bindingType>::set_debug_name(const std::string &name)
{
debug_name = name;
}
template <vkb::BindingType bindingType>
inline void Subpass<bindingType>::set_disable_depth_stencil_attachment(bool disable_depth_stencil)
{
disable_depth_stencil_attachment = disable_depth_stencil;
}
template <vkb::BindingType bindingType>
inline void Subpass<bindingType>::set_depth_stencil_resolve_mode(ResolveModeFlagBitsType mode)
{
if constexpr (bindingType == vkb::BindingType::Cpp)
{
depth_stencil_resolve_mode = mode;
}
else
{
depth_stencil_resolve_mode = static_cast<vk::ResolveModeFlagBits>(mode);
}
}
template <vkb::BindingType bindingType>
inline void Subpass<bindingType>::set_input_attachments(std::vector<uint32_t> const &input)
{
input_attachments = input;
}
template <vkb::BindingType bindingType>
inline void Subpass<bindingType>::set_output_attachments(std::vector<uint32_t> const &output)
{
output_attachments = output;
}
template <vkb::BindingType bindingType>
inline void Subpass<bindingType>::set_sample_count(SampleCountflagBitsType sample_count_)
{
if constexpr (bindingType == vkb::BindingType::Cpp)
{
sample_count = sample_count_;
}
else
{
sample_count = static_cast<vk::SampleCountFlagBits>(sample_count_);
}
}
template <vkb::BindingType bindingType>
inline void Subpass<bindingType>::update_render_target_attachments(RenderTargetType &render_target)
{
render_target.set_input_attachments(input_attachments);
render_target.set_output_attachments(output_attachments);
}
} // namespace rendering
} // namespace vkb
@@ -0,0 +1,61 @@
/* Copyright (c) 2018-2025, Arm Limited and Contributors
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 the "License";
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "rendering/subpasses/forward_subpass.h"
#include "common/utils.h"
#include "common/vk_common.h"
#include "rendering/render_context.h"
#include "scene_graph/components/camera.h"
#include "scene_graph/components/image.h"
#include "scene_graph/components/material.h"
#include "scene_graph/components/mesh.h"
#include "scene_graph/components/pbr_material.h"
#include "scene_graph/components/sub_mesh.h"
#include "scene_graph/components/texture.h"
#include "scene_graph/node.h"
#include "scene_graph/scene.h"
namespace vkb
{
ForwardSubpass::ForwardSubpass(RenderContext &render_context, ShaderSource &&vertex_source, ShaderSource &&fragment_source, sg::Scene &scene_, sg::Camera &camera) :
GeometrySubpass{render_context, std::move(vertex_source), std::move(fragment_source), scene_, camera}
{
}
void ForwardSubpass::prepare()
{
auto &device = get_render_context().get_device();
for (auto &mesh : meshes)
{
for (auto &sub_mesh : mesh->get_submeshes())
{
auto &variant = sub_mesh->get_mut_shader_variant();
auto &vert_module = device.get_resource_cache().request_shader_module(VK_SHADER_STAGE_VERTEX_BIT, get_vertex_shader(), variant);
auto &frag_module = device.get_resource_cache().request_shader_module(VK_SHADER_STAGE_FRAGMENT_BIT, get_fragment_shader(), variant);
}
}
}
void ForwardSubpass::draw(vkb::core::CommandBufferC &command_buffer)
{
allocate_lights<ForwardLights>(scene.get_components<sg::Light>(), MAX_FORWARD_LIGHT_COUNT);
command_buffer.bind_lighting(get_lighting_state(), 0, 4);
GeometrySubpass::draw(command_buffer);
}
} // namespace vkb
@@ -0,0 +1,70 @@
/* Copyright (c) 2018-2025, Arm Limited and Contributors
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 the "License";
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "buffer_pool.h"
#include "rendering/subpasses/geometry_subpass.h"
// This value is per type of light that we feed into the shader
#define MAX_FORWARD_LIGHT_COUNT 8
namespace vkb
{
namespace sg
{
class Scene;
class Node;
class Mesh;
class SubMesh;
class Camera;
} // namespace sg
struct alignas(16) ForwardLights
{
vkb::rendering::Light directional_lights[MAX_FORWARD_LIGHT_COUNT];
vkb::rendering::Light point_lights[MAX_FORWARD_LIGHT_COUNT];
vkb::rendering::Light spot_lights[MAX_FORWARD_LIGHT_COUNT];
};
/**
* @brief This subpass is responsible for rendering a Scene
*/
class ForwardSubpass : public GeometrySubpass
{
public:
/**
* @brief Constructs a subpass designed for forward rendering
* @param render_context Render context
* @param vertex_shader Vertex shader source
* @param fragment_shader Fragment shader source
* @param scene Scene to render on this subpass
* @param camera Camera used to look at the scene
*/
ForwardSubpass(RenderContext &render_context, ShaderSource &&vertex_shader, ShaderSource &&fragment_shader, sg::Scene &scene, sg::Camera &camera);
virtual ~ForwardSubpass() = default;
virtual void prepare() override;
/**
* @brief Record draw commands
*/
virtual void draw(vkb::core::CommandBufferC &command_buffer) override;
};
} // namespace vkb
@@ -0,0 +1,322 @@
/* Copyright (c) 2019-2025, Arm Limited and Contributors
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 the "License";
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "rendering/subpasses/geometry_subpass.h"
#include "common/utils.h"
#include "common/vk_common.h"
#include "rendering/render_context.h"
#include "scene_graph/components/camera.h"
#include "scene_graph/components/image.h"
#include "scene_graph/components/material.h"
#include "scene_graph/components/mesh.h"
#include "scene_graph/components/pbr_material.h"
#include "scene_graph/components/texture.h"
#include "scene_graph/node.h"
#include "scene_graph/scene.h"
namespace vkb
{
GeometrySubpass::GeometrySubpass(RenderContext &render_context, ShaderSource &&vertex_source, ShaderSource &&fragment_source, sg::Scene &scene_, sg::Camera &camera) :
Subpass{render_context, std::move(vertex_source), std::move(fragment_source)},
meshes{scene_.get_components<sg::Mesh>()},
camera{camera},
scene{scene_}
{
}
void GeometrySubpass::prepare()
{
// Build all shader variance upfront
auto &device = get_render_context().get_device();
for (auto &mesh : meshes)
{
for (auto &sub_mesh : mesh->get_submeshes())
{
auto &variant = sub_mesh->get_shader_variant();
auto &vert_module = device.get_resource_cache().request_shader_module(VK_SHADER_STAGE_VERTEX_BIT, get_vertex_shader(), variant);
auto &frag_module = device.get_resource_cache().request_shader_module(VK_SHADER_STAGE_FRAGMENT_BIT, get_fragment_shader(), variant);
}
}
}
void GeometrySubpass::get_sorted_nodes(std::multimap<float, std::pair<sg::Node *, sg::SubMesh *>> &opaque_nodes, std::multimap<float, std::pair<sg::Node *, sg::SubMesh *>> &transparent_nodes)
{
auto camera_transform = camera.get_node()->get_transform().get_world_matrix();
for (auto &mesh : meshes)
{
for (auto &node : mesh->get_nodes())
{
auto node_transform = node->get_transform().get_world_matrix();
const sg::AABB &mesh_bounds = mesh->get_bounds();
sg::AABB world_bounds{mesh_bounds.get_min(), mesh_bounds.get_max()};
world_bounds.transform(node_transform);
float distance = glm::length(glm::vec3(camera_transform[3]) - world_bounds.get_center());
for (auto &sub_mesh : mesh->get_submeshes())
{
if (sub_mesh->get_material()->alpha_mode == sg::AlphaMode::Blend)
{
transparent_nodes.emplace(distance, std::make_pair(node, sub_mesh));
}
else
{
opaque_nodes.emplace(distance, std::make_pair(node, sub_mesh));
}
}
}
}
}
void GeometrySubpass::draw(vkb::core::CommandBufferC &command_buffer)
{
std::multimap<float, std::pair<sg::Node *, sg::SubMesh *>> opaque_nodes;
std::multimap<float, std::pair<sg::Node *, sg::SubMesh *>> transparent_nodes;
get_sorted_nodes(opaque_nodes, transparent_nodes);
// Draw opaque objects in front-to-back order
{
ScopedDebugLabel opaque_debug_label{command_buffer, "Opaque objects"};
for (auto node_it = opaque_nodes.begin(); node_it != opaque_nodes.end(); node_it++)
{
update_uniform(command_buffer, *node_it->second.first, thread_index);
// Invert the front face if the mesh was flipped
const auto &scale = node_it->second.first->get_transform().get_scale();
bool flipped = scale.x * scale.y * scale.z < 0;
VkFrontFace front_face = flipped ? VK_FRONT_FACE_CLOCKWISE : VK_FRONT_FACE_COUNTER_CLOCKWISE;
draw_submesh(command_buffer, *node_it->second.second, front_face);
}
}
// Enable alpha blending
ColorBlendAttachmentState color_blend_attachment{};
color_blend_attachment.blend_enable = VK_TRUE;
color_blend_attachment.src_color_blend_factor = VK_BLEND_FACTOR_SRC_ALPHA;
color_blend_attachment.dst_color_blend_factor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
color_blend_attachment.src_alpha_blend_factor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
ColorBlendState color_blend_state{};
color_blend_state.attachments.resize(get_output_attachments().size());
for (auto &it : color_blend_state.attachments)
{
it = color_blend_attachment;
}
command_buffer.set_color_blend_state(color_blend_state);
command_buffer.set_depth_stencil_state(get_depth_stencil_state());
// Draw transparent objects in back-to-front order
{
ScopedDebugLabel transparent_debug_label{command_buffer, "Transparent objects"};
for (auto node_it = transparent_nodes.rbegin(); node_it != transparent_nodes.rend(); node_it++)
{
update_uniform(command_buffer, *node_it->second.first, thread_index);
draw_submesh(command_buffer, *node_it->second.second);
}
}
}
void GeometrySubpass::update_uniform(vkb::core::CommandBufferC &command_buffer, sg::Node &node, size_t thread_index)
{
GlobalUniform global_uniform;
global_uniform.camera_view_proj = camera.get_pre_rotation() * vkb::rendering::vulkan_style_projection(camera.get_projection()) * camera.get_view();
auto &render_frame = get_render_context().get_active_frame();
auto &transform = node.get_transform();
auto allocation = render_frame.allocate_buffer(VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, sizeof(GlobalUniform), thread_index);
global_uniform.model = transform.get_world_matrix();
global_uniform.camera_position = glm::vec3(glm::inverse(camera.get_view())[3]);
allocation.update(global_uniform);
command_buffer.bind_buffer(allocation.get_buffer(), allocation.get_offset(), allocation.get_size(), 0, 1, 0);
}
void GeometrySubpass::draw_submesh(vkb::core::CommandBufferC &command_buffer, sg::SubMesh &sub_mesh, VkFrontFace front_face)
{
auto &device = command_buffer.get_device();
ScopedDebugLabel submesh_debug_label{command_buffer, sub_mesh.get_name().c_str()};
prepare_pipeline_state(command_buffer, front_face, sub_mesh.get_material()->double_sided);
MultisampleState multisample_state{};
multisample_state.rasterization_samples = get_sample_count();
command_buffer.set_multisample_state(multisample_state);
auto &vert_shader_module = device.get_resource_cache().request_shader_module(VK_SHADER_STAGE_VERTEX_BIT, get_vertex_shader(), sub_mesh.get_shader_variant());
auto &frag_shader_module = device.get_resource_cache().request_shader_module(VK_SHADER_STAGE_FRAGMENT_BIT, get_fragment_shader(), sub_mesh.get_shader_variant());
std::vector<ShaderModule *> shader_modules{&vert_shader_module, &frag_shader_module};
auto &pipeline_layout = prepare_pipeline_layout(command_buffer, shader_modules);
command_buffer.bind_pipeline_layout(pipeline_layout);
if (pipeline_layout.get_push_constant_range_stage(sizeof(PBRMaterialUniform)) != 0)
{
prepare_push_constants(command_buffer, sub_mesh);
}
DescriptorSetLayout &descriptor_set_layout = pipeline_layout.get_descriptor_set_layout(0);
for (auto &texture : sub_mesh.get_material()->textures)
{
if (auto layout_binding = descriptor_set_layout.get_layout_binding(texture.first))
{
command_buffer.bind_image(texture.second->get_image()->get_vk_image_view(),
texture.second->get_sampler()->vk_sampler,
0, layout_binding->binding, 0);
}
}
auto vertex_input_resources = pipeline_layout.get_resources(ShaderResourceType::Input, VK_SHADER_STAGE_VERTEX_BIT);
VertexInputState vertex_input_state;
for (auto &input_resource : vertex_input_resources)
{
sg::VertexAttribute attribute;
if (!sub_mesh.get_attribute(input_resource.name, attribute))
{
continue;
}
VkVertexInputAttributeDescription vertex_attribute{};
vertex_attribute.binding = input_resource.location;
vertex_attribute.format = attribute.format;
vertex_attribute.location = input_resource.location;
vertex_attribute.offset = attribute.offset;
vertex_input_state.attributes.push_back(vertex_attribute);
VkVertexInputBindingDescription vertex_binding{};
vertex_binding.binding = input_resource.location;
vertex_binding.stride = attribute.stride;
vertex_input_state.bindings.push_back(vertex_binding);
}
command_buffer.set_vertex_input_state(vertex_input_state);
// Find submesh vertex buffers matching the shader input attribute names
for (auto &input_resource : vertex_input_resources)
{
const auto &buffer_iter = sub_mesh.vertex_buffers.find(input_resource.name);
if (buffer_iter != sub_mesh.vertex_buffers.end())
{
std::vector<std::reference_wrapper<const vkb::core::BufferC>> buffers;
buffers.emplace_back(std::ref(buffer_iter->second));
// Bind vertex buffers only for the attribute locations defined
command_buffer.bind_vertex_buffers(input_resource.location, std::move(buffers), {0});
}
}
draw_submesh_command(command_buffer, sub_mesh);
}
void GeometrySubpass::prepare_pipeline_state(vkb::core::CommandBufferC &command_buffer,
VkFrontFace front_face,
bool double_sided_material)
{
RasterizationState rasterization_state = base_rasterization_state;
rasterization_state.front_face = front_face;
if (double_sided_material)
{
rasterization_state.cull_mode = VK_CULL_MODE_NONE;
}
command_buffer.set_rasterization_state(rasterization_state);
MultisampleState multisample_state{};
multisample_state.rasterization_samples = get_sample_count();
command_buffer.set_multisample_state(multisample_state);
}
PipelineLayout &GeometrySubpass::prepare_pipeline_layout(vkb::core::CommandBufferC &command_buffer,
const std::vector<ShaderModule *> &shader_modules)
{
// Sets any specified resource modes
for (auto &shader_module : shader_modules)
{
for (auto &resource_mode : get_resource_mode_map())
{
shader_module->set_resource_mode(resource_mode.first, resource_mode.second);
}
}
return command_buffer.get_device().get_resource_cache().request_pipeline_layout(shader_modules);
}
void GeometrySubpass::prepare_push_constants(vkb::core::CommandBufferC &command_buffer, sg::SubMesh &sub_mesh)
{
auto pbr_material = dynamic_cast<const sg::PBRMaterial *>(sub_mesh.get_material());
PBRMaterialUniform pbr_material_uniform{};
pbr_material_uniform.base_color_factor = pbr_material->base_color_factor;
pbr_material_uniform.metallic_factor = pbr_material->metallic_factor;
pbr_material_uniform.roughness_factor = pbr_material->roughness_factor;
auto data = to_bytes(pbr_material_uniform);
if (!data.empty())
{
command_buffer.push_constants(data);
}
}
void GeometrySubpass::draw_submesh_command(vkb::core::CommandBufferC &command_buffer, sg::SubMesh &sub_mesh)
{
// Draw submesh indexed if indices exists
if (sub_mesh.vertex_indices != 0)
{
// Bind index buffer of submesh
command_buffer.bind_index_buffer(*sub_mesh.index_buffer, sub_mesh.index_offset, sub_mesh.index_type);
// Draw submesh using indexed data
command_buffer.draw_indexed(sub_mesh.vertex_indices, 1, 0, 0, 0);
}
else
{
// Draw submesh using vertices only
command_buffer.draw(sub_mesh.vertices_count, 1, 0, 0);
}
}
void GeometrySubpass::set_thread_index(uint32_t index)
{
thread_index = index;
}
} // namespace vkb
@@ -0,0 +1,130 @@
/* Copyright (c) 2019-2025, Arm Limited and Contributors
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 the "License";
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "common/error.h"
#include "common/glm_common.h"
#include "rendering/subpass.h"
namespace vkb
{
namespace core
{
template <vkb::BindingType bindingType>
class CommandBuffer;
using CommandBufferC = CommandBuffer<vkb::BindingType::C>;
} // namespace core
namespace sg
{
class Scene;
class Node;
class Mesh;
class SubMesh;
class Camera;
} // namespace sg
/**
* @brief Global uniform structure for base shader
*/
struct alignas(16) GlobalUniform
{
glm::mat4 model;
glm::mat4 camera_view_proj;
glm::vec3 camera_position;
};
/**
* @brief PBR material uniform for base shader
*/
struct PBRMaterialUniform
{
glm::vec4 base_color_factor;
float metallic_factor;
float roughness_factor;
};
/**
* @brief This subpass is responsible for rendering a Scene
*/
class GeometrySubpass : public vkb::rendering::SubpassC
{
public:
/**
* @brief Constructs a subpass for the geometry pass of Deferred rendering
* @param render_context Render context
* @param vertex_shader Vertex shader source
* @param fragment_shader Fragment shader source
* @param scene Scene to render on this subpass
* @param camera Camera used to look at the scene
*/
GeometrySubpass(RenderContext &render_context, ShaderSource &&vertex_shader, ShaderSource &&fragment_shader, sg::Scene &scene, sg::Camera &camera);
virtual ~GeometrySubpass() = default;
virtual void prepare() override;
/**
* @brief Record draw commands
*/
virtual void draw(vkb::core::CommandBufferC &command_buffer) override;
/**
* @brief Thread index to use for allocating resources
*/
void set_thread_index(uint32_t index);
protected:
virtual void update_uniform(vkb::core::CommandBufferC &command_buffer, sg::Node &node, size_t thread_index);
void draw_submesh(vkb::core::CommandBufferC &command_buffer, sg::SubMesh &sub_mesh, VkFrontFace front_face = VK_FRONT_FACE_COUNTER_CLOCKWISE);
virtual void prepare_pipeline_state(vkb::core::CommandBufferC &command_buffer, VkFrontFace front_face, bool double_sided_material);
virtual PipelineLayout &prepare_pipeline_layout(vkb::core::CommandBufferC &command_buffer,
const std::vector<ShaderModule *> &shader_modules);
virtual void prepare_push_constants(vkb::core::CommandBufferC &command_buffer, sg::SubMesh &sub_mesh);
virtual void draw_submesh_command(vkb::core::CommandBufferC &command_buffer, sg::SubMesh &sub_mesh);
/**
* @brief Sorts objects based on distance from camera and classifies them
* into opaque and transparent in the arrays provided
*/
void get_sorted_nodes(std::multimap<float, std::pair<sg::Node *, sg::SubMesh *>> &opaque_nodes,
std::multimap<float, std::pair<sg::Node *, sg::SubMesh *>> &transparent_nodes);
sg::Camera &camera;
std::vector<sg::Mesh *> meshes;
sg::Scene &scene;
uint32_t thread_index{0};
vkb::RasterizationState base_rasterization_state{};
};
} // namespace vkb
@@ -0,0 +1,53 @@
/* Copyright (c) 2022-2024, NVIDIA CORPORATION. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 the "License";
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "rendering/subpasses/forward_subpass.h"
#include <rendering/hpp_render_context.h>
namespace vkb
{
namespace rendering
{
namespace subpasses
{
/**
* @brief facade class around vkb::ForwardSubpass, providing a vulkan.hpp-based interface
*
* See vkb::ForwardSubpass for documentation
*/
class HPPForwardSubpass : public vkb::ForwardSubpass
{
public:
HPPForwardSubpass(vkb::rendering::HPPRenderContext &render_context,
vkb::ShaderSource &&vertex_shader,
vkb::ShaderSource &&fragment_shader,
vkb::scene_graph::HPPScene &scene,
vkb::sg::Camera &camera) :
vkb::ForwardSubpass(reinterpret_cast<vkb::RenderContext &>(render_context),
std::forward<ShaderSource>(vertex_shader),
std::forward<ShaderSource>(fragment_shader),
reinterpret_cast<vkb::sg::Scene &>(scene),
camera)
{}
};
} // namespace subpasses
} // namespace rendering
} // namespace vkb
@@ -0,0 +1,101 @@
/* Copyright (c) 2019-2025, Arm Limited and Contributors
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 the "License";
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "lighting_subpass.h"
#include "buffer_pool.h"
#include "rendering/render_context.h"
#include "scene_graph/components/camera.h"
#include "scene_graph/scene.h"
namespace vkb
{
LightingSubpass::LightingSubpass(RenderContext &render_context, ShaderSource &&vertex_shader, ShaderSource &&fragment_shader, sg::Camera &cam, sg::Scene &scene_) :
Subpass{render_context, std::move(vertex_shader), std::move(fragment_shader)},
camera{cam},
scene{scene_}
{
}
void LightingSubpass::prepare()
{
// Build all shaders upfront
auto &resource_cache = get_render_context().get_device().get_resource_cache();
resource_cache.request_shader_module(VK_SHADER_STAGE_VERTEX_BIT, get_vertex_shader(), lighting_variant);
resource_cache.request_shader_module(VK_SHADER_STAGE_FRAGMENT_BIT, get_fragment_shader(), lighting_variant);
}
void LightingSubpass::draw(vkb::core::CommandBufferC &command_buffer)
{
allocate_lights<DeferredLights>(scene.get_components<sg::Light>(), MAX_DEFERRED_LIGHT_COUNT);
command_buffer.bind_lighting(get_lighting_state(), 0, 4);
// Get shaders from cache
auto &resource_cache = command_buffer.get_device().get_resource_cache();
auto &vert_shader_module = resource_cache.request_shader_module(VK_SHADER_STAGE_VERTEX_BIT, get_vertex_shader(), lighting_variant);
auto &frag_shader_module = resource_cache.request_shader_module(VK_SHADER_STAGE_FRAGMENT_BIT, get_fragment_shader(), lighting_variant);
std::vector<ShaderModule *> shader_modules{&vert_shader_module, &frag_shader_module};
// Create pipeline layout and bind it
auto &pipeline_layout = resource_cache.request_pipeline_layout(shader_modules);
command_buffer.bind_pipeline_layout(pipeline_layout);
// we know, that the lighting subpass does not have any vertex stage input -> reset the vertex input state
assert(pipeline_layout.get_resources(ShaderResourceType::Input, VK_SHADER_STAGE_VERTEX_BIT).empty());
command_buffer.set_vertex_input_state({});
// Get image views of the attachments
auto &render_target = get_render_context().get_active_frame().get_render_target();
auto &target_views = render_target.get_views();
assert(3 < target_views.size());
// Bind depth, albedo, and normal as input attachments
auto &depth_view = target_views[1];
command_buffer.bind_input(depth_view, 0, 0, 0);
auto &albedo_view = target_views[2];
command_buffer.bind_input(albedo_view, 0, 1, 0);
auto &normal_view = target_views[3];
command_buffer.bind_input(normal_view, 0, 2, 0);
// Set cull mode to front as full screen triangle is clock-wise
RasterizationState rasterization_state;
rasterization_state.cull_mode = VK_CULL_MODE_FRONT_BIT;
command_buffer.set_rasterization_state(rasterization_state);
// Populate uniform values
LightUniform light_uniform;
// Inverse resolution
light_uniform.inv_resolution.x = 1.0f / render_target.get_extent().width;
light_uniform.inv_resolution.y = 1.0f / render_target.get_extent().height;
// Inverse view projection
light_uniform.inv_view_proj = glm::inverse(vkb::rendering::vulkan_style_projection(camera.get_projection()) * camera.get_view());
// Allocate a buffer using the buffer pool from the active frame to store uniform values and bind it
auto &render_frame = get_render_context().get_active_frame();
auto allocation = render_frame.allocate_buffer(VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, sizeof(LightUniform));
allocation.update(light_uniform);
command_buffer.bind_buffer(allocation.get_buffer(), allocation.get_offset(), allocation.get_size(), 0, 3, 0);
// Draw full screen triangle triangle
command_buffer.draw(3, 1, 0, 0);
}
} // namespace vkb
@@ -0,0 +1,82 @@
/* Copyright (c) 2019-2025, Arm Limited and Contributors
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 the "License";
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "buffer_pool.h"
#include "rendering/subpass.h"
#include "common/glm_common.h"
// This value is per type of light that we feed into the shader
#define MAX_DEFERRED_LIGHT_COUNT 48
namespace vkb
{
namespace core
{
template <vkb::BindingType bindingType>
class CommandBuffer;
using CommandBufferC = CommandBuffer<vkb::BindingType::C>;
} // namespace core
namespace sg
{
class Camera;
class Light;
class Scene;
} // namespace sg
/**
* @brief Light uniform structure for lighting shader
* Inverse view projection matrix and inverse resolution vector are used
* in lighting pass to reconstruct position from depth and frag coord
*/
struct alignas(16) LightUniform
{
glm::mat4 inv_view_proj;
glm::vec2 inv_resolution;
};
struct alignas(16) DeferredLights
{
vkb::rendering::Light directional_lights[MAX_DEFERRED_LIGHT_COUNT];
vkb::rendering::Light point_lights[MAX_DEFERRED_LIGHT_COUNT];
vkb::rendering::Light spot_lights[MAX_DEFERRED_LIGHT_COUNT];
};
/**
* @brief Lighting pass of Deferred Rendering
*/
class LightingSubpass : public vkb::rendering::SubpassC
{
public:
LightingSubpass(RenderContext &render_context, ShaderSource &&vertex_shader, ShaderSource &&fragment_shader, sg::Camera &camera, sg::Scene &scene);
virtual void prepare() override;
void draw(vkb::core::CommandBufferC &command_buffer) override;
private:
sg::Camera &camera;
sg::Scene &scene;
ShaderVariant lighting_variant;
};
} // namespace vkb