From 9200731b563632f94181ff84ea16ce95df1107eb Mon Sep 17 00:00:00 2001 From: emufan4568 Date: Wed, 10 Aug 2022 19:47:40 +0300 Subject: [PATCH] video_core: Even more fixes, close to building now --- src/common/bit_field_array.h | 8 - src/common/object_pool.h | 4 + src/video_core/CMakeLists.txt | 2 + src/video_core/common/backend.h | 4 +- src/video_core/common/framebuffer.h | 34 ++- src/video_core/common/pipeline.h | 4 - src/video_core/common/rasterizer.cpp | 14 +- src/video_core/common/rasterizer_cache.cpp | 7 +- src/video_core/common/renderer.cpp | 11 +- src/video_core/renderer_vulkan/vk_backend.cpp | 258 +++++++----------- src/video_core/renderer_vulkan/vk_backend.h | 22 +- .../renderer_vulkan/vk_framebuffer.cpp | 8 +- .../renderer_vulkan/vk_framebuffer.h | 17 +- src/video_core/renderer_vulkan/vk_pipeline.h | 1 + .../renderer_vulkan/vk_renderpass_cache.cpp | 169 ++++++++++++ .../renderer_vulkan/vk_renderpass_cache.h | 39 +++ 16 files changed, 394 insertions(+), 208 deletions(-) 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/common/bit_field_array.h b/src/common/bit_field_array.h index 326f21eb9..5e2482f09 100644 --- a/src/common/bit_field_array.h +++ b/src/common/bit_field_array.h @@ -50,14 +50,6 @@ public: } } - // We explicitly delete the copy assignment operator here, because the - // default copy assignment would copy the full storage value, rather than - // just the bits relevant to this particular bit field. - // Ideally, we would just implement the copy assignment to copy only the - // relevant bits, but we're prevented from doing that because the savestate - // code expects that this class is trivially copyable. - BitFieldArray& operator=(const BitFieldArray&) = delete; - public: constexpr bool IsSigned() const { return std::is_signed(); } constexpr std::size_t StartBit() const { return position; } diff --git a/src/common/object_pool.h b/src/common/object_pool.h index fdec8eaae..5b8dd89dc 100644 --- a/src/common/object_pool.h +++ b/src/common/object_pool.h @@ -62,6 +62,10 @@ struct AlignedAllocation { template class ObjectPool { public: + ObjectPool() { + vacants.reserve(32); + } + template T* Allocate(P&&... p) { #ifndef OBJECT_POOL_DEBUG diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index b25115374..3d60abcc0 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -112,6 +112,8 @@ add_library(video_core STATIC renderer_vulkan/vk_pipeline.cpp renderer_vulkan/vk_pipeline.h renderer_vulkan/vk_platform.h + renderer_vulkan/vk_renderpass_cache.cpp + renderer_vulkan/vk_renderpass_cache.h renderer_vulkan/vk_shader_gen.cpp renderer_vulkan/vk_shader_gen.h renderer_vulkan/vk_shader.cpp diff --git a/src/video_core/common/backend.h b/src/video_core/common/backend.h index d7a93c34f..86b26665f 100644 --- a/src/video_core/common/backend.h +++ b/src/video_core/common/backend.h @@ -59,10 +59,10 @@ public: virtual ShaderHandle CreateShader(ShaderStage stage, std::string_view name, std::string source) = 0; // Binds a vertex buffer at a provided offset - virtual void BindVertexBuffer(BufferHandle buffer, std::span offsets) = 0; + virtual void BindVertexBuffer(BufferHandle buffer, std::span offsets) = 0; // Binds an index buffer at provided offset - virtual void BindIndexBuffer(BufferHandle buffer, AttribType index_type, u32 offset) = 0; + virtual void BindIndexBuffer(BufferHandle buffer, AttribType index_type, u64 offset) = 0; // Start a draw operation virtual void Draw(PipelineHandle pipeline, FramebufferHandle draw_framebuffer, u32 base_vertex, diff --git a/src/video_core/common/framebuffer.h b/src/video_core/common/framebuffer.h index 24eca35ce..5e3b009ff 100644 --- a/src/video_core/common/framebuffer.h +++ b/src/video_core/common/framebuffer.h @@ -16,6 +16,12 @@ enum class MSAASamples : u32 { x8 }; +// States which operation to perform on the framebuffer attachment during rendering +enum class LoadOp : u8 { + Load = 0, + Clear = 1 +}; + /** * Information about a framebuffer */ @@ -31,9 +37,7 @@ struct FramebufferInfo { } }; -/** - * A framebuffer is a collection of render targets and their configuration - */ +// A framebuffer is a collection of render targets and their configuration class FramebufferBase : public IntrusivePtrEnabled { public: FramebufferBase(const FramebufferInfo& info) : info(info) {} @@ -43,8 +47,8 @@ public: FramebufferBase(const FramebufferBase&) = delete; FramebufferBase& operator=(const FramebufferBase&) = delete; - // Clears the attachments bound to the framebuffer - virtual void DoClear(Common::Vec4f color, float depth, u8 stencil) = 0; + // Clears the attachments bound to the framebuffer using the last stored clear value + virtual void DoClear() = 0; // Returns an immutable reference to the color attachment TextureHandle GetColorAttachment() const { @@ -61,8 +65,22 @@ public: draw_rect = rect; } + LoadOp GetLoadOp() const { + return load_op; + } + + void SetLoadOp(LoadOp op) { + load_op = op; + } + + void SetClearValues(Common::Vec4f color, float depth, u8 stencil) { + clear_color_value = color; + clear_depth_value = depth; + clear_stencil_value = stencil; + } + // Returns the area of the framebuffer affected by draw operations - Common::Rectangle GetDrawRect() { + Common::Rectangle GetDrawRect() const { return draw_rect; } @@ -72,6 +90,10 @@ public: } protected: + LoadOp load_op = LoadOp::Load; + Common::Vec4f clear_color_value{}; + float clear_depth_value = 0.f; + u8 clear_stencil_value = 0; Common::Rectangle draw_rect; FramebufferInfo info; }; diff --git a/src/video_core/common/pipeline.h b/src/video_core/common/pipeline.h index 624d25cb2..3c94c3de3 100644 --- a/src/video_core/common/pipeline.h +++ b/src/video_core/common/pipeline.h @@ -39,8 +39,6 @@ enum class BindingType : u32 { using BindingGroup = BitFieldArray<0, 3, MAX_BINDINGS_IN_GROUP, BindingType>; -static_assert(sizeof(BindingGroup)); - /** * Describes all the resources used in the pipeline */ @@ -50,8 +48,6 @@ struct PipelineLayoutInfo { u8 push_constant_block_size = 0; }; -static_assert(sizeof(PipelineLayoutInfo)); - /** * The pipeline state is tightly packed with bitfields to reduce * the overhead of hashing as much as possible diff --git a/src/video_core/common/rasterizer.cpp b/src/video_core/common/rasterizer.cpp index 094478a54..2acbf4929 100644 --- a/src/video_core/common/rasterizer.cpp +++ b/src/video_core/common/rasterizer.cpp @@ -96,7 +96,7 @@ constexpr u32 UTILITY_GROUP = 0; constexpr u32 TEXTURE_GROUP = 1; // Rasterizer pipeline layout -static constexpr PipelineLayoutInfo RASTERIZER_PIPELINE_INFO = { +constexpr PipelineLayoutInfo RASTERIZER_PIPELINE_LAYOUT = { .group_count = 3, .binding_groups = { // Uniform + LUT set @@ -192,6 +192,10 @@ Rasterizer::Rasterizer(Frontend::EmuWindow& emu_window, std::unique_ptr(emu_window, backend); + // Initialize the rasterization pipeline info + raster_info.vertex_layout = HardwareVertex::GetVertexLayout(); + raster_info.layout = RASTERIZER_PIPELINE_LAYOUT; + // Synchronize pica state SyncEntireState(); } @@ -348,7 +352,7 @@ void Rasterizer::SetupVertexArray(u32 vs_input_size, u32 vs_input_index_min, u32 VertexLayout layout{}; std::array enable_attributes{}; - std::array binding_offsets{}; + std::array binding_offsets{}; u32 buffer_offset = 0; for (const auto& loader : vertex_attributes.attribute_loaders) { @@ -448,7 +452,7 @@ void Rasterizer::SetupVertexArray(u32 vs_input_size, u32 vs_input_index_min, u32 vertex_buffer->Commit(vs_input_size); // Bind the vertex buffers with all the bindings - auto offsets = std::span{binding_offsets.data(), layout.binding_count}; + auto offsets = std::span{binding_offsets.data(), layout.binding_count}; backend->BindVertexBuffer(vertex_buffer, offsets); } @@ -587,6 +591,8 @@ bool Rasterizer::Draw(bool accelerate, bool is_indexed) { // Retrieve the framebuffer assigned to the surfaces and update raster_info FramebufferHandle framebuffer = res_cache.GetFramebuffer(color_surface, depth_surface); + framebuffer->SetLoadOp(LoadOp::Load); + raster_info.color_attachment = framebuffer->GetColorAttachment().IsValid() ? framebuffer->GetColorAttachment()->GetFormat() : TextureFormat::Undefined; @@ -815,7 +821,7 @@ bool Rasterizer::Draw(bool accelerate, bool is_indexed) { // Bind the vertex buffer at the current mapped offset. This effectively means // that when base_vertex is zero the GPU will start drawing from the current mapped // offset not the start of the buffer. - const std::array mapped_offset = {vertex_buffer->GetCurrentOffset()}; + const std::array mapped_offset = {vertex_buffer->GetCurrentOffset()}; backend->BindVertexBuffer(vertex_buffer, mapped_offset); const std::size_t max_vertices = VERTEX_BUFFER_INFO.capacity / sizeof(HardwareVertex); diff --git a/src/video_core/common/rasterizer_cache.cpp b/src/video_core/common/rasterizer_cache.cpp index 115715a8d..c553c1a83 100644 --- a/src/video_core/common/rasterizer_cache.cpp +++ b/src/video_core/common/rasterizer_cache.cpp @@ -262,7 +262,7 @@ bool RasterizerCache::FillSurface(const Surface& surface, const u8* fill_data, C tex_info.format = static_cast(surface->pixel_format); const auto color_values = Pica::Texture::LookupTexture(fill_data, 0, 0, tex_info) / 255.f; - framebuffer->DoClear(color_values, 0.0f, 0); + framebuffer->SetClearValues(color_values, 0.0f, 0); } else if (surface->type == SurfaceType::Depth) { u32 depth_32bit = 0; float depth_float; @@ -278,7 +278,7 @@ bool RasterizerCache::FillSurface(const Surface& surface, const u8* fill_data, C UNREACHABLE(); } - framebuffer->DoClear({}, depth_float, 0); + framebuffer->SetClearValues({}, depth_float, 0); } else if (surface->type == SurfaceType::DepthStencil) { u32 value_32bit; std::memcpy(&value_32bit, fill_data, sizeof(u32)); @@ -286,9 +286,10 @@ bool RasterizerCache::FillSurface(const Surface& surface, const u8* fill_data, C float depth_float = (value_32bit & 0xFFFFFF) / 16777215.0f; // 2^24 - 1 u8 stencil_int = (value_32bit >> 24); - framebuffer->DoClear({}, depth_float, stencil_int); + framebuffer->SetClearValues({}, depth_float, stencil_int); } + framebuffer->DoClear(); return true; } diff --git a/src/video_core/common/renderer.cpp b/src/video_core/common/renderer.cpp index 726b4b6f7..1aa271ce5 100644 --- a/src/video_core/common/renderer.cpp +++ b/src/video_core/common/renderer.cpp @@ -134,7 +134,7 @@ constexpr VertexLayout ScreenRectVertex::GetVertexLayout() { } // Renderer pipeline layout -static constexpr PipelineLayoutInfo RENDERER_PIPELINE_INFO = { +static constexpr PipelineLayoutInfo RENDERER_PIPELINE_LAYOUT = { .group_count = 2, .binding_groups = { BindingGroup{ @@ -167,7 +167,7 @@ DisplayRenderer::DisplayRenderer(Frontend::EmuWindow& window) : render_window(wi PipelineInfo present_pipeline_info = { .vertex_layout = ScreenRectVertex::GetVertexLayout(), - .layout = RENDERER_PIPELINE_INFO, + .layout = RENDERER_PIPELINE_LAYOUT, .color_attachment = TextureFormat::PresentColor, .depth_attachment = TextureFormat::Undefined }; @@ -333,9 +333,10 @@ void DisplayRenderer::DrawSingleScreen(u32 screen, bool rotate, float x, float y const ScreenInfo& screen_info = screen_infos[screen]; const auto& texcoords = screen_info.display_texcoords; - // Clear the swapchain framebuffer + // Set the swapchain framebuffer to clear mode FramebufferHandle display = backend->GetWindowFramebuffer(); - display->DoClear(clear_color, 0.f, 0); + display->SetLoadOp(LoadOp::Clear); + display->SetClearValues(clear_color, 0.f, 0); // Update viewport and scissor const auto& color_surface = display->GetColorAttachment(); @@ -360,7 +361,7 @@ void DisplayRenderer::DrawSingleScreen(u32 screen, bool rotate, float x, float y } const u32 size = sizeof(ScreenRectVertex) * vertices.size(); - const u32 mapped_offset = vertex_buffer->GetCurrentOffset(); + const u64 mapped_offset = vertex_buffer->GetCurrentOffset(); auto vertex_data = vertex_buffer->Map(size); // Copy vertex data diff --git a/src/video_core/renderer_vulkan/vk_backend.cpp b/src/video_core/renderer_vulkan/vk_backend.cpp index 9aba97c4b..8ace6b94b 100644 --- a/src/video_core/renderer_vulkan/vk_backend.cpp +++ b/src/video_core/renderer_vulkan/vk_backend.cpp @@ -42,7 +42,7 @@ constexpr vk::IndexType ToVkIndexType(AttribType type) { Backend::Backend(Frontend::EmuWindow& window) : BackendBase(window), instance(window), swapchain(instance, instance.GetSurface()), - scheduler(instance) { + scheduler(instance), renderpass_cache(instance, swapchain) { // TODO: Properly report GPU hardware auto& telemetry_session = Core::System::GetInstance().TelemetrySession(); @@ -51,32 +51,9 @@ Backend::Backend(Frontend::EmuWindow& window) : BackendBase(window), telemetry_session.AddField(user_system, "GPU_Model", "GTX 1650"); telemetry_session.AddField(user_system, "GPU_Vulkan_Version", "Vulkan 1.3"); - // Pre-create all needed renderpasses by the renderer - constexpr std::array color_formats = { - vk::Format::eUndefined, - vk::Format::eR8G8B8A8Unorm, - vk::Format::eR8G8B8Unorm, - vk::Format::eR5G5B5A1UnormPack16, - vk::Format::eR5G6B5UnormPack16, - vk::Format::eR4G4B4A4UnormPack16 - }; - - constexpr std::array depth_stencil_formats = { - vk::Format::eUndefined, - vk::Format::eD16Unorm, - vk::Format::eX8D24UnormPack32, - vk::Format::eD24UnormS8Uint, - }; - - // Create all required renderpasses - for (u32 color = 0; color <= MAX_COLOR_FORMATS; color++) { - for (u32 depth = 0; depth <= MAX_DEPTH_FORMATS; depth++) { - if (color == 0 && depth == 0) continue; - - u32 index = color * MAX_COLOR_FORMATS + depth; - renderpass_cache[index] = CreateRenderPass(color_formats[color], depth_stencil_formats[depth]); - } - } + // Create pipeline cache object + vk::Device device = instance.GetDevice(); + pipeline_cache = device.createPipelineCache({}); constexpr std::array pool_sizes = { vk::DescriptorPoolSize{vk::DescriptorType::eUniformBuffer, 1024}, @@ -93,7 +70,6 @@ Backend::Backend(Frontend::EmuWindow& window) : BackendBase(window), }; // Create descriptor pools - vk::Device device = instance.GetDevice(); for (u32 pool = 0; pool < SCHEDULER_COMMAND_COUNT; pool++) { descriptor_pools[pool] = device.createDescriptorPool(pool_info); } @@ -101,8 +77,10 @@ Backend::Backend(Frontend::EmuWindow& window) : BackendBase(window), Backend::~Backend() { vk::Device device = instance.GetDevice(); - for (auto& renderpass : renderpass_cache) { - device.destroyRenderPass(renderpass); + device.destroyPipelineCache(pipeline_cache); + + for (u32 pool = 0; pool < SCHEDULER_COMMAND_COUNT; pool++) { + device.destroyDescriptorPool(descriptor_pools[pool]); } } @@ -134,9 +112,10 @@ FramebufferHandle Backend::CreateFramebuffer(FramebufferInfo info) { // Get renderpass TextureFormat color = info.color.IsValid() ? info.color->GetFormat() : TextureFormat::Undefined; TextureFormat depth = info.depth_stencil.IsValid() ? info.depth_stencil->GetFormat() : TextureFormat::Undefined; - vk::RenderPass renderpass = GetRenderPass(color, depth); + vk::RenderPass load_renderpass = GetRenderPass(color, depth, false); + vk::RenderPass clear_renderpass = GetRenderPass(color, depth, true); - return FramebufferHandle{framebuffer_pool.Allocate(instance, info, renderpass)}; + return FramebufferHandle{framebuffer_pool.Allocate(instance, info, load_renderpass, clear_renderpass)}; } TextureHandle Backend::CreateTexture(TextureInfo info) { @@ -152,12 +131,14 @@ PipelineHandle Backend::CreatePipeline(PipelineType type, PipelineInfo info) { // Find an owner first if (auto iter = pipeline_owners.find(info.layout); iter != pipeline_owners.end()) { - return PipelineHandle{pipeline_pool.Allocate(instance, iter->second, type, info, renderpass, cache)}; + return PipelineHandle{pipeline_pool.Allocate(instance, iter->second, type, info, + renderpass, pipeline_cache)}; } // Create the layout auto result = pipeline_owners.emplace(info.layout, PipelineOwner{instance, info.layout}); - return PipelineHandle{pipeline_pool.Allocate(instance, result.first->second, type, info, renderpass, cache)}; + return PipelineHandle{pipeline_pool.Allocate(instance, result.first->second, type, info, + renderpass, pipeline_cache)}; } SamplerHandle Backend::CreateSampler(SamplerInfo info) { @@ -165,30 +146,35 @@ SamplerHandle Backend::CreateSampler(SamplerInfo info) { return SamplerHandle{sampler_pool.Allocate(info)}; } +void Backend::BindVertexBuffer(BufferHandle buffer, std::span offsets) { + const Buffer* vertex = static_cast(buffer.Get()); + + std::array buffers; + buffers.fill(vertex->GetHandle()); + + vk::CommandBuffer command_buffer = scheduler.GetRenderCommandBuffer(); + command_buffer.bindVertexBuffers(0, offsets.size(), buffers.data(), offsets.data()); +} + +void Backend::BindIndexBuffer(BufferHandle buffer, AttribType index_type, u64 offset) { + const Buffer* index = static_cast(buffer.Get()); + + vk::CommandBuffer command_buffer = scheduler.GetRenderCommandBuffer(); + command_buffer.bindIndexBuffer(index->GetHandle(), 0, ToVkIndexType(index_type)); +} + void Backend::Draw(PipelineHandle pipeline_handle, FramebufferHandle draw_framebuffer, u32 base_vertex, u32 num_vertices) { + // Bind descriptor sets - vk::CommandBuffer command_buffer = scheduler.GetRenderCommandBuffer(); BindDescriptorSets(pipeline_handle); - // Bind vertex buffer - const Buffer* vertex = static_cast(vertex_buffer.Get()); - command_buffer.bindVertexBuffers(0, vertex->GetHandle(), vertex->GetBindOffset()); - // Begin renderpass - const Framebuffer* framebuffer = static_cast(draw_framebuffer.Get()); - const vk::RenderPassBeginInfo renderpass_begin = { - .renderPass = framebuffer->GetRenderpass(), - .framebuffer = framebuffer->GetHandle(), - .renderArea = ToVkRect2D(framebuffer->GetDrawRectangle()), - .clearValueCount = 0, - .pClearValues = nullptr - }; - - command_buffer.beginRenderPass(renderpass_begin, vk::SubpassContents::eInline); + BeginRenderpass(draw_framebuffer); // Bind pipeline const Pipeline* pipeline = static_cast(pipeline_handle.Get()); + vk::CommandBuffer command_buffer = scheduler.GetRenderCommandBuffer(); command_buffer.bindPipeline(ToVkPipelineBindPoint(pipeline->GetType()), pipeline->GetHandle()); // Submit draw @@ -199,34 +185,16 @@ void Backend::Draw(PipelineHandle pipeline_handle, FramebufferHandle draw_frameb } void Backend::DrawIndexed(PipelineHandle pipeline_handle, FramebufferHandle draw_framebuffer, - BufferHandle vertex_buffer, BufferHandle index_buffer, AttribType index_type, u32 base_index, u32 num_indices, u32 base_vertex) { // Bind descriptor sets - vk::CommandBuffer command_buffer = scheduler.GetRenderCommandBuffer(); BindDescriptorSets(pipeline_handle); - // Bind vertex buffer - const Buffer* vertex = static_cast(vertex_buffer.Get()); - command_buffer.bindVertexBuffers(0, vertex->GetHandle(), vertex->GetBindOffset()); - - // Bind index buffer - const Buffer* index = static_cast(index_buffer.Get()); - command_buffer.bindIndexBuffer(index->GetHandle(), index->GetBindOffset(), ToVkIndexType(index_type)); - // Begin renderpass - const Framebuffer* framebuffer = static_cast(draw_framebuffer.Get()); - const vk::RenderPassBeginInfo renderpass_begin = { - .renderPass = framebuffer->GetRenderpass(), - .framebuffer = framebuffer->GetHandle(), - .renderArea = ToVkRect2D(framebuffer->GetDrawRectangle()), - .clearValueCount = 0, - .pClearValues = nullptr - }; - - command_buffer.beginRenderPass(renderpass_begin, vk::SubpassContents::eInline); + BeginRenderpass(draw_framebuffer); // Bind pipeline const Pipeline* pipeline = static_cast(pipeline_handle.Get()); + vk::CommandBuffer command_buffer = scheduler.GetRenderCommandBuffer(); command_buffer.bindPipeline(ToVkPipelineBindPoint(pipeline->GetType()), pipeline->GetHandle()); // Submit draw @@ -237,106 +205,80 @@ void Backend::DrawIndexed(PipelineHandle pipeline_handle, FramebufferHandle draw } - -vk::RenderPass Backend::CreateRenderPass(vk::Format color, vk::Format depth) const { - // Define attachments - const std::array attachments = { - vk::AttachmentDescription{ - .format = color, - .stencilLoadOp = vk::AttachmentLoadOp::eDontCare, - .stencilStoreOp = vk::AttachmentStoreOp::eDontCare, - .initialLayout = vk::ImageLayout::eShaderReadOnlyOptimal, - .finalLayout = vk::ImageLayout::eColorAttachmentOptimal - }, - vk::AttachmentDescription{ - .format = depth, - .initialLayout = vk::ImageLayout::eShaderReadOnlyOptimal, - .finalLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal - } - }; - - // Our renderpasses only defines one color and depth attachment - const vk::AttachmentReference color_attachment_ref = { - .attachment = 0, - .layout = vk::ImageLayout::eColorAttachmentOptimal - }; - - const vk::AttachmentReference depth_attachment_ref = { - .attachment = 1, - .layout = vk::ImageLayout::eDepthStencilAttachmentOptimal - }; - - const vk::SubpassDependency subpass_dependency = { - .srcSubpass = VK_SUBPASS_EXTERNAL, - .dstSubpass = 0, - .srcStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput | - vk::PipelineStageFlagBits::eEarlyFragmentTests, - .dstStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput | - vk::PipelineStageFlagBits::eEarlyFragmentTests, - .srcAccessMask = vk::AccessFlagBits::eNone, - .dstAccessMask = vk::AccessFlagBits::eColorAttachmentWrite | - vk::AccessFlagBits::eDepthStencilAttachmentWrite, - .dependencyFlags = vk::DependencyFlagBits::eByRegion - }; - - // We also require only one subpass - const vk::SubpassDescription subpass = { - .pipelineBindPoint = vk::PipelineBindPoint::eGraphics, - .inputAttachmentCount = 0, - .pInputAttachments = nullptr, - .colorAttachmentCount = 1, - .pColorAttachments = &color_attachment_ref, - .pResolveAttachments = 0, - .pDepthStencilAttachment = &depth_attachment_ref - }; - - const vk::RenderPassCreateInfo renderpass_info = { - .attachmentCount = 2, - .pAttachments = attachments.data(), - .subpassCount = 1, - .pSubpasses = &subpass, - .dependencyCount = 1, - .pDependencies = &subpass_dependency - }; - - // Create the renderpass - vk::Device device = instance.GetDevice(); - return device.createRenderPass(renderpass_info); -} - -vk::RenderPass Backend::GetRenderPass(TextureFormat color, TextureFormat depth) const { - u32 color_index = color != TextureFormat::Undefined ? static_cast(color) + 1 : 0; - u32 depth_index = depth != TextureFormat::Undefined ? static_cast(depth) - 4 : 0; - return renderpass_cache[color_index * MAX_COLOR_FORMATS + depth_index]; +vk::RenderPass Backend::GetRenderPass(TextureFormat color, TextureFormat depth, bool is_clear) const { + if (color == TextureFormat::PresentColor) { + return renderpass_cache.GetPresentRenderpass(); + } else { + return renderpass_cache.GetRenderpass(color, depth, is_clear); + } } void Backend::BindDescriptorSets(PipelineHandle handle) { Pipeline* pipeline = static_cast(handle.Get()); - PipelineLayout& pipeline_layout = pipeline->GetOwner(); + PipelineOwner& pipeline_owner = pipeline->GetOwner(); - // Allocate required descriptor sets - // TODO: Maybe cache them? - u32 pool_index = scheduler.GetCurrentSlotIndex(); - const vk::DescriptorSetAllocateInfo alloc_info = { - .descriptorPool = descriptor_pools[pool_index], - .descriptorSetCount = pipeline_layout.GetDescriptorSetLayoutCount(), - .pSetLayouts = pipeline_layout.GetDescriptorSetLayouts() - }; + std::array bound_sets; + const u32 set_count = pipeline_owner.GetDescriptorSetLayoutCount(); + for (int i = 0; i < set_count; i++) { + if (!pipeline_owner.descriptor_dirty[i]) { + // Get the ready descriptor if it hasn't been modified + bound_sets[i] = pipeline_owner.descriptor_bank[i]; + } else { + // Otherwise allocate a new set and update it with the needed data + u32 pool_index = scheduler.GetCurrentSlotIndex(); + const vk::DescriptorSetAllocateInfo alloc_info = { + .descriptorPool = descriptor_pools[pool_index], + .descriptorSetCount = 1, + .pSetLayouts = &pipeline_owner.GetDescriptorSetLayouts()[i] + }; - vk::Device device = instance.GetDevice(); - auto descriptor_sets = device.allocateDescriptorSets(alloc_info); + vk::Device device = instance.GetDevice(); + vk::DescriptorSet set = device.allocateDescriptorSets(alloc_info)[0]; + device.updateDescriptorSetWithTemplate(set, pipeline_owner.GetUpdateTemplate(i), + pipeline_owner.GetData(i)); - // Write data to the descriptor sets - for (u32 set = 0; set < descriptor_sets.size(); set++) { - device.updateDescriptorSetWithTemplate(descriptor_sets[set], - pipeline_layout.GetUpdateTemplate(set), - pipeline_layout.GetData(set)); + bound_sets[i] = set; + pipeline_owner.descriptor_bank[i] = set; + pipeline_owner.descriptor_dirty[i] = false; + } } // Bind the descriptor sets vk::CommandBuffer command_buffer = scheduler.GetRenderCommandBuffer(); - command_buffer.bindDescriptorSets(ToVkPipelineBindPoint(handle->GetType()), pipeline_layout.GetLayout(), - 0, descriptor_sets, {}); + command_buffer.bindDescriptorSets(ToVkPipelineBindPoint(handle->GetType()), pipeline_owner.GetLayout(), + 0, set_count, bound_sets.data(), 0, nullptr); +} + +void Backend::BeginRenderpass(FramebufferHandle draw_framebuffer) { + const Framebuffer* framebuffer = static_cast(draw_framebuffer.Get()); + + u32 clear_value_count = 0; + std::array clear_values{}; + + if (framebuffer->GetColorAttachment().IsValid()) { + for (int i = 0; i < 4; i++) { + clear_values[clear_value_count++].color.float32[i] = framebuffer->clear_color_value[i]; + } + } + + if (framebuffer->GetDepthStencilAttachment().IsValid()) { + clear_values[clear_value_count].depthStencil.depth = framebuffer->clear_depth_value; + clear_values[clear_value_count++].depthStencil.stencil = framebuffer->clear_stencil_value; + } + + // Use the clear renderpass if the framebuffer was configured so + const vk::RenderPassBeginInfo renderpass_begin = { + .renderPass = framebuffer->GetLoadOp() == LoadOp::Load ? + framebuffer->GetLoadRenderpass() : + framebuffer->GetClearRenderpass(), + .framebuffer = framebuffer->GetHandle(), + .renderArea = ToVkRect2D(framebuffer->GetDrawRect()), + .clearValueCount = clear_value_count, + .pClearValues = clear_values.data() + }; + + vk::CommandBuffer command_buffer = scheduler.GetRenderCommandBuffer(); + command_buffer.beginRenderPass(renderpass_begin, vk::SubpassContents::eInline); } } // namespace VideoCore::Vulkan diff --git a/src/video_core/renderer_vulkan/vk_backend.h b/src/video_core/renderer_vulkan/vk_backend.h index 6f4a9eb88..02bd48186 100644 --- a/src/video_core/renderer_vulkan/vk_backend.h +++ b/src/video_core/renderer_vulkan/vk_backend.h @@ -10,13 +10,10 @@ #include "video_core/renderer_vulkan/vk_swapchain.h" #include "video_core/renderer_vulkan/vk_instance.h" #include "video_core/renderer_vulkan/vk_pipeline.h" +#include "video_core/renderer_vulkan/vk_renderpass_cache.h" namespace VideoCore::Vulkan { -class Texture; - -constexpr u32 RENDERPASS_COUNT = (MAX_COLOR_FORMATS + 1) * (MAX_DEPTH_FORMATS + 1); - class Backend final : public VideoCore::BackendBase { public: Backend(Frontend::EmuWindow& window); @@ -38,8 +35,8 @@ public: SamplerHandle CreateSampler(SamplerInfo info) override; ShaderHandle CreateShader(ShaderStage stage, std::string_view name, std::string source) override; - void BindVertexBuffer(BufferHandle buffer, std::span offsets) override; - void BindIndexBuffer(BufferHandle buffer, AttribType index_type, u32 offset) override; + void BindVertexBuffer(BufferHandle buffer, std::span offsets) override; + void BindIndexBuffer(BufferHandle buffer, AttribType index_type, u64 offset) override; void Draw(PipelineHandle pipeline, FramebufferHandle draw_framebuffer, u32 base_vertex, u32 num_vertices) override; @@ -61,21 +58,20 @@ public: } private: - vk::RenderPass CreateRenderPass(vk::Format color, vk::Format depth) const; - vk::RenderPass GetRenderPass(TextureFormat color, TextureFormat depth) const; + vk::RenderPass GetRenderPass(TextureFormat color, TextureFormat depth, bool is_clear = false) const; // Allocates and binds descriptor sets for the provided pipeline void BindDescriptorSets(PipelineHandle pipeline); + // Begins the renderpass for the provided framebuffer + void BeginRenderpass(FramebufferHandle framebuffer); + private: Instance instance; Swapchain swapchain; CommandScheduler scheduler; - - // The formats Citra uses are limited so we can pre-create - // all the renderpasses we will need - std::array renderpass_cache; - vk::PipelineCache cache; + RenderpassCache renderpass_cache; + vk::PipelineCache pipeline_cache; // A cache of pipeline owners std::unordered_map pipeline_owners; diff --git a/src/video_core/renderer_vulkan/vk_framebuffer.cpp b/src/video_core/renderer_vulkan/vk_framebuffer.cpp index ec6c97571..4587eeea2 100644 --- a/src/video_core/renderer_vulkan/vk_framebuffer.cpp +++ b/src/video_core/renderer_vulkan/vk_framebuffer.cpp @@ -57,7 +57,7 @@ Framebuffer::~Framebuffer() { device.destroyFramebuffer(framebuffer); } -void Framebuffer::DoClear(Common::Vec4f color, float depth, u8 stencil) { +void Framebuffer::DoClear() { vk::CommandBuffer command_buffer = scheduler.GetRenderCommandBuffer(); u32 clear_value_count = 0; @@ -65,7 +65,7 @@ void Framebuffer::DoClear(Common::Vec4f color, float depth, u8 stencil) { if (info.color.IsValid()) { vk::ClearColorValue clear_color{}; - std::memcpy(clear_color.float32.data(), color.AsArray(), sizeof(float) * 4); + std::memcpy(clear_color.float32.data(), clear_value.color.AsArray(), sizeof(float) * 4); clear_values[clear_value_count++] = vk::ClearValue { .color = clear_color @@ -75,8 +75,8 @@ void Framebuffer::DoClear(Common::Vec4f color, float depth, u8 stencil) { if (info.depth_stencil.IsValid()) { clear_values[clear_value_count++] = vk::ClearValue { .depthStencil = vk::ClearDepthStencilValue { - .depth = depth, - .stencil = stencil + .depth = clear_value.depth, + .stencil = clear_value.stencil } }; } diff --git a/src/video_core/renderer_vulkan/vk_framebuffer.h b/src/video_core/renderer_vulkan/vk_framebuffer.h index c2a6fc1c1..15ba13753 100644 --- a/src/video_core/renderer_vulkan/vk_framebuffer.h +++ b/src/video_core/renderer_vulkan/vk_framebuffer.h @@ -13,12 +13,13 @@ class Instance; class CommandScheduler; class Framebuffer : public VideoCore::FramebufferBase { + friend class Backend; public: Framebuffer(Instance& instance, CommandScheduler& scheduler, const FramebufferInfo& info, vk::RenderPass load_renderpass, vk::RenderPass clear_renderpass); ~Framebuffer() override; - void DoClear(Common::Vec4f color, float depth, u8 stencil) override; + void DoClear() override; vk::Framebuffer GetHandle() const { return framebuffer; @@ -28,6 +29,20 @@ public: return load_renderpass; } + vk::RenderPass GetClearRenderpass() const { + return clear_renderpass; + } + + u32 GetAttachmentCount() const { + if (info.color.IsValid() && info.depth_stencil.IsValid()) { + return 2; + } else if (!info.color.IsValid() && !info.depth_stencil.IsValid()) { + return 0; + } else { + return 1; + } + } + private: Instance& instance; CommandScheduler& scheduler; diff --git a/src/video_core/renderer_vulkan/vk_pipeline.h b/src/video_core/renderer_vulkan/vk_pipeline.h index a8b67b28b..4b26610d1 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline.h +++ b/src/video_core/renderer_vulkan/vk_pipeline.h @@ -25,6 +25,7 @@ union DescriptorData { * Functions as the "parent" to a group of pipelines that share the same layout */ class PipelineOwner { + friend class Backend; public: PipelineOwner(Instance& instance, PipelineLayoutInfo info); ~PipelineOwner(); 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..43062491a --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_renderpass_cache.cpp @@ -0,0 +1,169 @@ +// Copyright 2022 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#define VULKAN_HPP_NO_CONSTRUCTORS +#include "common/assert.h" +#include "video_core/renderer_vulkan/vk_renderpass_cache.h" +#include "video_core/renderer_vulkan/vk_instance.h" +#include "video_core/renderer_vulkan/vk_swapchain.h" + +namespace VideoCore::Vulkan { + +static constexpr std::array color_formats = { + vk::Format::eUndefined, + vk::Format::eR8G8B8A8Unorm, + vk::Format::eR8G8B8Unorm, + vk::Format::eR5G5B5A1UnormPack16, + vk::Format::eR5G6B5UnormPack16, + vk::Format::eR4G4B4A4UnormPack16 +}; + +static constexpr std::array depth_stencil_formats = { + vk::Format::eUndefined, + vk::Format::eD16Unorm, + vk::Format::eX8D24UnormPack32, + vk::Format::eD24UnormS8Uint, +}; + +RenderpassCache::RenderpassCache(Instance& instance, Swapchain& swapchain) : instance(instance) { + // Pre-create all needed renderpasses by the renderer + for (u32 color = 0; color <= MAX_COLOR_FORMATS; color++) { + for (u32 depth = 0; depth <= MAX_DEPTH_FORMATS; depth++) { + if (color == 0 && depth == 0) { + continue; + } + + vk::Format color_format = instance.GetFormatAlternative(color_formats[color]); + vk::Format depth_stencil_format = instance.GetFormatAlternative(depth_stencil_formats[depth]); + + // Construct both load and clear pass + cached_renderpasses[color][depth][0] = CreateRenderPass(color_format, depth_stencil_format, + vk::AttachmentLoadOp::eLoad, + vk::ImageLayout::eShaderReadOnlyOptimal, + vk::ImageLayout::eColorAttachmentOptimal); + cached_renderpasses[color][depth][1] = CreateRenderPass(color_format, depth_stencil_format, + vk::AttachmentLoadOp::eClear, + vk::ImageLayout::eShaderReadOnlyOptimal, + vk::ImageLayout::eColorAttachmentOptimal); + } + } + + // Create the present renderpass + present_renderpass = CreateRenderPass(swapchain.GetSurfaceFormat().format, vk::Format::eUndefined, + vk::AttachmentLoadOp::eClear, + vk::ImageLayout::eUndefined, + vk::ImageLayout::ePresentSrcKHR); +} + +RenderpassCache::~RenderpassCache() { + vk::Device device = instance.GetDevice(); + for (u32 color = 0; color <= MAX_COLOR_FORMATS; color++) { + for (u32 depth = 0; depth <= MAX_DEPTH_FORMATS; depth++) { + if (color == 0 && depth == 0) { + continue; + } + + auto& load_pass = cached_renderpasses[color][depth][0]; + auto& clear_pass = cached_renderpasses[color][depth][1]; + + // Destroy renderpasses + device.destroyRenderPass(load_pass); + device.destroyRenderPass(clear_pass); + } + } + + device.destroyRenderPass(present_renderpass); +} + +vk::RenderPass RenderpassCache::GetRenderpass(TextureFormat color, TextureFormat depth, bool is_clear) const { + const u32 color_index = static_cast(color); + const u32 depth_index = static_cast(depth) - MAX_COLOR_FORMATS; + + ASSERT(color_index < MAX_COLOR_FORMATS && depth_index < MAX_DEPTH_FORMATS); + return cached_renderpasses[color_index][depth_index][is_clear]; +} + +vk::RenderPass RenderpassCache::CreateRenderPass(vk::Format color, vk::Format depth, vk::AttachmentLoadOp load_op, + vk::ImageLayout initial_layout, vk::ImageLayout final_layout) const { + // Define attachments + + u32 attachment_count = 0; + std::array attachments; + + bool use_color = false; + vk::AttachmentReference color_attachment_ref; + bool use_depth = false; + vk::AttachmentReference depth_attachment_ref; + + if (color != vk::Format::eUndefined) { + attachments[attachment_count++] = vk::AttachmentDescription{ + .format = color, + .loadOp = load_op, + .storeOp = vk::AttachmentStoreOp::eStore, + .stencilLoadOp = vk::AttachmentLoadOp::eDontCare, + .stencilStoreOp = vk::AttachmentStoreOp::eDontCare, + .initialLayout = initial_layout, + .finalLayout = final_layout + }; + + color_attachment_ref = vk::AttachmentReference{ + .attachment = 0, + .layout = vk::ImageLayout::eColorAttachmentOptimal + }; + } + + if (depth != vk::Format::eUndefined) { + attachments[attachment_count++] = vk::AttachmentDescription{ + .format = depth, + .loadOp = load_op, + .storeOp = vk::AttachmentStoreOp::eStore, + .initialLayout = vk::ImageLayout::eShaderReadOnlyOptimal, + .finalLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal + }; + + depth_attachment_ref = vk::AttachmentReference{ + .attachment = 1, + .layout = vk::ImageLayout::eDepthStencilAttachmentOptimal + }; + } + + const vk::SubpassDependency subpass_dependency = { + .srcSubpass = VK_SUBPASS_EXTERNAL, + .dstSubpass = 0, + .srcStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput | + vk::PipelineStageFlagBits::eEarlyFragmentTests, + .dstStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput | + vk::PipelineStageFlagBits::eEarlyFragmentTests, + .srcAccessMask = vk::AccessFlagBits::eNone, + .dstAccessMask = vk::AccessFlagBits::eColorAttachmentWrite | + vk::AccessFlagBits::eDepthStencilAttachmentWrite, + .dependencyFlags = vk::DependencyFlagBits::eByRegion + }; + + // We also require only one subpass + const vk::SubpassDescription subpass = { + .pipelineBindPoint = vk::PipelineBindPoint::eGraphics, + .inputAttachmentCount = 0, + .pInputAttachments = nullptr, + .colorAttachmentCount = use_color ? 1u : 0u, + .pColorAttachments = &color_attachment_ref, + .pResolveAttachments = 0, + .pDepthStencilAttachment = use_depth ? &depth_attachment_ref : nullptr + }; + + const vk::RenderPassCreateInfo renderpass_info = { + .attachmentCount = attachment_count, + .pAttachments = attachments.data(), + .subpassCount = 1, + .pSubpasses = &subpass, + .dependencyCount = 1, + .pDependencies = &subpass_dependency + }; + + // Create the renderpass + vk::Device device = instance.GetDevice(); + return device.createRenderPass(renderpass_info); +} + +} // namespace VideoCore::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..31a4620e2 --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_renderpass_cache.h @@ -0,0 +1,39 @@ +// Copyright 2022 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once +#include "video_core/common/texture.h" +#include "video_core/renderer_vulkan/vk_common.h" + +namespace VideoCore::Vulkan { + +class Instance; +class Swapchain; + +class RenderpassCache { +public: + RenderpassCache(Instance& instance, Swapchain& swapchain); + ~RenderpassCache(); + + vk::RenderPass GetRenderpass(TextureFormat color, TextureFormat depth, bool is_clear) const; + + // Returns the special swapchain renderpass + vk::RenderPass GetPresentRenderpass() const { + return present_renderpass; + } + +private: + vk::RenderPass CreateRenderPass(vk::Format color, vk::Format depth, vk::AttachmentLoadOp load_op, + vk::ImageLayout initial_layout, vk::ImageLayout final_layout) const; + +private: + Instance& instance; + + // Special renderpass used for rendering to the swapchain + vk::RenderPass present_renderpass; + // [color_format][depth_format][is_clear_pass] + vk::RenderPass cached_renderpasses[MAX_COLOR_FORMATS][MAX_DEPTH_FORMATS][2]; +}; + +} // namespace VideoCore::Vulkan