init
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user