From 322d6a0311624b8304894baa102ab462ea6cf0ea Mon Sep 17 00:00:00 2001 From: ReinUsesLisp Date: Sun, 5 Jan 2020 17:26:04 -0300 Subject: [PATCH 1/2] vk_update_descriptor: Initial implementation The update descriptor is used to store in flat memory a large chunk of staging data used to update descriptor sets through templates. It provides a push interface to easily insert descriptors following the current pipeline. The order used in the descriptor update template has to be implicitly followed. We can catch bugs here using validation layers. --- src/video_core/CMakeLists.txt | 4 +- .../renderer_vulkan/vk_update_descriptor.cpp | 57 ++++++++++++ .../renderer_vulkan/vk_update_descriptor.h | 86 +++++++++++++++++++ 3 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 src/video_core/renderer_vulkan/vk_update_descriptor.cpp create mode 100644 src/video_core/renderer_vulkan/vk_update_descriptor.h diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index abbf218f5..47290cbcb 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -176,7 +176,9 @@ if (ENABLE_VULKAN) renderer_vulkan/vk_stream_buffer.cpp renderer_vulkan/vk_stream_buffer.h renderer_vulkan/vk_swapchain.cpp - renderer_vulkan/vk_swapchain.h) + renderer_vulkan/vk_swapchain.h + renderer_vulkan/vk_update_descriptor.cpp + renderer_vulkan/vk_update_descriptor.h) target_include_directories(video_core PRIVATE sirit ../../externals/Vulkan-Headers/include) target_compile_definitions(video_core PRIVATE HAS_VULKAN) diff --git a/src/video_core/renderer_vulkan/vk_update_descriptor.cpp b/src/video_core/renderer_vulkan/vk_update_descriptor.cpp new file mode 100644 index 000000000..0e577b9ff --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_update_descriptor.cpp @@ -0,0 +1,57 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include + +#include "common/assert.h" +#include "common/logging/log.h" +#include "video_core/renderer_vulkan/declarations.h" +#include "video_core/renderer_vulkan/vk_device.h" +#include "video_core/renderer_vulkan/vk_scheduler.h" +#include "video_core/renderer_vulkan/vk_update_descriptor.h" + +namespace Vulkan { + +VKUpdateDescriptorQueue::VKUpdateDescriptorQueue(const VKDevice& device, VKScheduler& scheduler) + : device{device}, scheduler{scheduler} {} + +VKUpdateDescriptorQueue::~VKUpdateDescriptorQueue() = default; + +void VKUpdateDescriptorQueue::TickFrame() { + payload.clear(); +} + +void VKUpdateDescriptorQueue::Acquire() { + entries.clear(); +} + +void VKUpdateDescriptorQueue::Send(vk::DescriptorUpdateTemplate update_template, + vk::DescriptorSet set) { + if (payload.size() + entries.size() >= payload.max_size()) { + LOG_WARNING(Render_Vulkan, "Payload overflow, waiting for worker thread"); + scheduler.WaitWorker(); + payload.clear(); + } + + const auto payload_start = payload.data() + payload.size(); + for (const auto& entry : entries) { + if (const auto image = std::get_if(&entry)) { + payload.push_back(*image); + } else if (const auto buffer = std::get_if(&entry)) { + payload.emplace_back(*buffer->buffer, buffer->offset, buffer->size); + } else if (const auto texel = std::get_if(&entry)) { + payload.push_back(*texel); + } else { + UNREACHABLE(); + } + } + + scheduler.Record([dev = device.GetLogical(), payload_start, set, + update_template]([[maybe_unused]] auto cmdbuf, auto& dld) { + dev.updateDescriptorSetWithTemplate(set, update_template, payload_start, dld); + }); +} + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_update_descriptor.h b/src/video_core/renderer_vulkan/vk_update_descriptor.h new file mode 100644 index 000000000..8c825aa29 --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_update_descriptor.h @@ -0,0 +1,86 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include + +#include "common/common_types.h" +#include "video_core/renderer_vulkan/declarations.h" + +namespace Vulkan { + +class VKDevice; +class VKScheduler; + +class DescriptorUpdateEntry { +public: + explicit DescriptorUpdateEntry() : image{} {} + + DescriptorUpdateEntry(vk::DescriptorImageInfo image) : image{image} {} + + DescriptorUpdateEntry(vk::Buffer buffer, vk::DeviceSize offset, vk::DeviceSize size) + : buffer{buffer, offset, size} {} + + DescriptorUpdateEntry(vk::BufferView texel_buffer) : texel_buffer{texel_buffer} {} + +private: + union { + vk::DescriptorImageInfo image; + vk::DescriptorBufferInfo buffer; + vk::BufferView texel_buffer; + }; +}; + +class VKUpdateDescriptorQueue final { +public: + explicit VKUpdateDescriptorQueue(const VKDevice& device, VKScheduler& scheduler); + ~VKUpdateDescriptorQueue(); + + void TickFrame(); + + void Acquire(); + + void Send(vk::DescriptorUpdateTemplate update_template, vk::DescriptorSet set); + + void AddSampledImage(vk::Sampler sampler, vk::ImageView image_view) { + entries.emplace_back(vk::DescriptorImageInfo{sampler, image_view, {}}); + } + + void AddImage(vk::ImageView image_view) { + entries.emplace_back(vk::DescriptorImageInfo{{}, image_view, {}}); + } + + void AddBuffer(const vk::Buffer* buffer, u64 offset, std::size_t size) { + entries.push_back(Buffer{buffer, offset, size}); + } + + void AddTexelBuffer(vk::BufferView texel_buffer) { + entries.emplace_back(texel_buffer); + } + + vk::ImageLayout* GetLastImageLayout() { + return &std::get(entries.back()).imageLayout; + } + +private: + struct Buffer { + const vk::Buffer* buffer{}; + u64 offset{}; + std::size_t size{}; + }; + using Variant = std::variant; + // Old gcc versions don't consider this trivially copyable. + // static_assert(std::is_trivially_copyable_v); + + const VKDevice& device; + VKScheduler& scheduler; + + boost::container::static_vector entries; + boost::container::static_vector payload; +}; + +} // namespace Vulkan From 5aeff9aff5c8d9aa21d839560744af883b877b99 Mon Sep 17 00:00:00 2001 From: ReinUsesLisp Date: Sun, 5 Jan 2020 17:32:08 -0300 Subject: [PATCH 2/2] vk_renderpass_cache: Initial implementation The renderpass cache is used to avoid creating renderpasses on each draw. The hashed structure is not currently optimized. --- src/video_core/CMakeLists.txt | 2 + .../renderer_vulkan/vk_renderpass_cache.cpp | 100 ++++++++++++++++++ .../renderer_vulkan/vk_renderpass_cache.h | 97 +++++++++++++++++ 3 files changed, 199 insertions(+) create mode 100644 src/video_core/renderer_vulkan/vk_renderpass_cache.cpp create mode 100644 src/video_core/renderer_vulkan/vk_renderpass_cache.h diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index 47290cbcb..c80171fe6 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -163,6 +163,8 @@ if (ENABLE_VULKAN) renderer_vulkan/vk_image.h renderer_vulkan/vk_memory_manager.cpp renderer_vulkan/vk_memory_manager.h + renderer_vulkan/vk_renderpass_cache.cpp + renderer_vulkan/vk_renderpass_cache.h renderer_vulkan/vk_resource_manager.cpp renderer_vulkan/vk_resource_manager.h renderer_vulkan/vk_sampler_cache.cpp diff --git a/src/video_core/renderer_vulkan/vk_renderpass_cache.cpp b/src/video_core/renderer_vulkan/vk_renderpass_cache.cpp new file mode 100644 index 000000000..93f5d7ba0 --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_renderpass_cache.cpp @@ -0,0 +1,100 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include + +#include "video_core/engines/maxwell_3d.h" +#include "video_core/renderer_vulkan/declarations.h" +#include "video_core/renderer_vulkan/maxwell_to_vk.h" +#include "video_core/renderer_vulkan/vk_device.h" +#include "video_core/renderer_vulkan/vk_renderpass_cache.h" + +namespace Vulkan { + +VKRenderPassCache::VKRenderPassCache(const VKDevice& device) : device{device} {} + +VKRenderPassCache::~VKRenderPassCache() = default; + +vk::RenderPass VKRenderPassCache::GetRenderPass(const RenderPassParams& params) { + const auto [pair, is_cache_miss] = cache.try_emplace(params); + auto& entry = pair->second; + if (is_cache_miss) { + entry = CreateRenderPass(params); + } + return *entry; +} + +UniqueRenderPass VKRenderPassCache::CreateRenderPass(const RenderPassParams& params) const { + std::vector descriptors; + std::vector color_references; + + for (std::size_t rt = 0; rt < params.color_attachments.size(); ++rt) { + const auto attachment = params.color_attachments[rt]; + const auto format = + MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, attachment.pixel_format); + ASSERT_MSG(format.attachable, "Trying to attach a non-attachable format with format={}", + static_cast(attachment.pixel_format)); + + // TODO(Rodrigo): Add eMayAlias when it's needed. + const auto color_layout = attachment.is_texception + ? vk::ImageLayout::eGeneral + : vk::ImageLayout::eColorAttachmentOptimal; + descriptors.emplace_back(vk::AttachmentDescriptionFlagBits::eMayAlias, format.format, + vk::SampleCountFlagBits::e1, vk::AttachmentLoadOp::eLoad, + vk::AttachmentStoreOp::eStore, vk::AttachmentLoadOp::eDontCare, + vk::AttachmentStoreOp::eDontCare, color_layout, color_layout); + color_references.emplace_back(static_cast(rt), color_layout); + } + + vk::AttachmentReference zeta_attachment_ref; + if (params.has_zeta) { + const auto format = + MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, params.zeta_pixel_format); + ASSERT_MSG(format.attachable, "Trying to attach a non-attachable format with format={}", + static_cast(params.zeta_pixel_format)); + + const auto zeta_layout = params.zeta_texception + ? vk::ImageLayout::eGeneral + : vk::ImageLayout::eDepthStencilAttachmentOptimal; + descriptors.emplace_back(vk::AttachmentDescriptionFlags{}, format.format, + vk::SampleCountFlagBits::e1, vk::AttachmentLoadOp::eLoad, + vk::AttachmentStoreOp::eStore, vk::AttachmentLoadOp::eLoad, + vk::AttachmentStoreOp::eStore, zeta_layout, zeta_layout); + zeta_attachment_ref = + vk::AttachmentReference(static_cast(params.color_attachments.size()), zeta_layout); + } + + const vk::SubpassDescription subpass_description( + {}, vk::PipelineBindPoint::eGraphics, 0, nullptr, static_cast(color_references.size()), + color_references.data(), nullptr, params.has_zeta ? &zeta_attachment_ref : nullptr, 0, + nullptr); + + vk::AccessFlags access; + vk::PipelineStageFlags stage; + if (!color_references.empty()) { + access |= + vk::AccessFlagBits::eColorAttachmentRead | vk::AccessFlagBits::eColorAttachmentWrite; + stage |= vk::PipelineStageFlagBits::eColorAttachmentOutput; + } + + if (params.has_zeta) { + access |= vk::AccessFlagBits::eDepthStencilAttachmentRead | + vk::AccessFlagBits::eDepthStencilAttachmentWrite; + stage |= vk::PipelineStageFlagBits::eLateFragmentTests; + } + + const vk::SubpassDependency subpass_dependency(VK_SUBPASS_EXTERNAL, 0, stage, stage, {}, access, + {}); + + const vk::RenderPassCreateInfo create_info({}, static_cast(descriptors.size()), + descriptors.data(), 1, &subpass_description, 1, + &subpass_dependency); + + const auto dev = device.GetLogical(); + const auto& dld = device.GetDispatchLoader(); + return dev.createRenderPassUnique(create_info, nullptr, dld); +} + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_renderpass_cache.h b/src/video_core/renderer_vulkan/vk_renderpass_cache.h new file mode 100644 index 000000000..b49b2db48 --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_renderpass_cache.h @@ -0,0 +1,97 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include + +#include +#include + +#include "video_core/engines/maxwell_3d.h" +#include "video_core/renderer_vulkan/declarations.h" +#include "video_core/surface.h" + +namespace Vulkan { + +class VKDevice; + +// TODO(Rodrigo): Optimize this structure for faster hashing + +struct RenderPassParams { + struct ColorAttachment { + u32 index = 0; + VideoCore::Surface::PixelFormat pixel_format = VideoCore::Surface::PixelFormat::Invalid; + bool is_texception = false; + + std::size_t Hash() const noexcept { + return static_cast(pixel_format) | + static_cast(is_texception) << 6 | + static_cast(index) << 7; + } + + bool operator==(const ColorAttachment& rhs) const noexcept { + return std::tie(index, pixel_format, is_texception) == + std::tie(rhs.index, rhs.pixel_format, rhs.is_texception); + } + }; + + boost::container::static_vector + color_attachments{}; + // TODO(Rodrigo): Unify has_zeta into zeta_pixel_format and zeta_component_type. + VideoCore::Surface::PixelFormat zeta_pixel_format = VideoCore::Surface::PixelFormat::Invalid; + bool has_zeta = false; + bool zeta_texception = false; + + std::size_t Hash() const noexcept { + std::size_t hash = 0; + for (const auto& rt : color_attachments) { + boost::hash_combine(hash, rt.Hash()); + } + boost::hash_combine(hash, zeta_pixel_format); + boost::hash_combine(hash, has_zeta); + boost::hash_combine(hash, zeta_texception); + return hash; + } + + bool operator==(const RenderPassParams& rhs) const { + return std::tie(color_attachments, zeta_pixel_format, has_zeta, zeta_texception) == + std::tie(rhs.color_attachments, rhs.zeta_pixel_format, rhs.has_zeta, + rhs.zeta_texception); + } +}; + +} // namespace Vulkan + +namespace std { + +template <> +struct hash { + std::size_t operator()(const Vulkan::RenderPassParams& k) const noexcept { + return k.Hash(); + } +}; + +} // namespace std + +namespace Vulkan { + +class VKRenderPassCache final { +public: + explicit VKRenderPassCache(const VKDevice& device); + ~VKRenderPassCache(); + + vk::RenderPass GetRenderPass(const RenderPassParams& params); + +private: + UniqueRenderPass CreateRenderPass(const RenderPassParams& params) const; + + const VKDevice& device; + std::unordered_map cache; +}; + +} // namespace Vulkan