// Copyright 2023 Citra Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. #include #include "common/assert.h" #include "video_core/rasterizer_cache/pixel_format.h" #include "video_core/renderer_vulkan/vk_instance.h" #include "video_core/renderer_vulkan/vk_renderpass_cache.h" #include "video_core/renderer_vulkan/vk_scheduler.h" #include "video_core/renderer_vulkan/vk_texture_runtime.h" namespace Vulkan { constexpr u32 MIN_DRAWS_TO_FLUSH = 20; using VideoCore::PixelFormat; using VideoCore::SurfaceType; RenderpassCache::RenderpassCache(const Instance& instance, Scheduler& scheduler) : instance{instance}, scheduler{scheduler} {} RenderpassCache::~RenderpassCache() = default; void RenderpassCache::BeginRendering(const Framebuffer* framebuffer, Common::Rectangle draw_rect) { const vk::Rect2D render_area = { .offset{ .x = static_cast(draw_rect.left), .y = static_cast(draw_rect.bottom), }, .extent{ .width = draw_rect.GetWidth(), .height = draw_rect.GetHeight(), }, }; const RenderPass new_pass = { .framebuffer = framebuffer->Handle(), .render_pass = framebuffer->RenderPass(), .render_area = render_area, .clear = {}, .do_clear = false, }; images = framebuffer->Images(); aspects = framebuffer->Aspects(); BeginRendering(new_pass); } void RenderpassCache::BeginRendering(const RenderPass& new_pass) { if (pass == new_pass) [[likely]] { num_draws++; return; } EndRendering(); scheduler.Record([info = new_pass](vk::CommandBuffer cmdbuf) { const vk::RenderPassBeginInfo renderpass_begin_info = { .renderPass = info.render_pass, .framebuffer = info.framebuffer, .renderArea = info.render_area, .clearValueCount = info.do_clear ? 1u : 0u, .pClearValues = &info.clear, }; cmdbuf.beginRenderPass(renderpass_begin_info, vk::SubpassContents::eInline); }); pass = new_pass; } void RenderpassCache::EndRendering() { if (!pass.render_pass) { return; } pass.render_pass = vk::RenderPass{}; scheduler.Record([images = images, aspects = aspects](vk::CommandBuffer cmdbuf) { u32 num_barriers = 0; vk::PipelineStageFlags pipeline_flags{}; std::array barriers; for (u32 i = 0; i < images.size(); i++) { if (!images[i]) { continue; } const bool is_color = static_cast(aspects[i] & vk::ImageAspectFlagBits::eColor); if (is_color) { pipeline_flags |= vk::PipelineStageFlagBits::eColorAttachmentOutput; } else { pipeline_flags |= vk::PipelineStageFlagBits::eEarlyFragmentTests | vk::PipelineStageFlagBits::eLateFragmentTests; } barriers[num_barriers++] = vk::ImageMemoryBarrier{ .srcAccessMask = is_color ? vk::AccessFlagBits::eColorAttachmentWrite : vk::AccessFlagBits::eDepthStencilAttachmentWrite, .dstAccessMask = vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eTransferRead, .oldLayout = vk::ImageLayout::eGeneral, .newLayout = vk::ImageLayout::eGeneral, .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .image = images[i], .subresourceRange{ .aspectMask = aspects[i], .baseMipLevel = 0, .levelCount = 1, .baseArrayLayer = 0, .layerCount = VK_REMAINING_ARRAY_LAYERS, }, }; } cmdbuf.endRenderPass(); cmdbuf.pipelineBarrier(pipeline_flags, vk::PipelineStageFlagBits::eFragmentShader | vk::PipelineStageFlagBits::eTransfer, vk::DependencyFlagBits::eByRegion, 0, nullptr, 0, nullptr, num_barriers, barriers.data()); }); // The Mali guide recommends flushing at the end of each major renderpass // Testing has shown this has a significant effect on rendering performance if (num_draws > MIN_DRAWS_TO_FLUSH && instance.ShouldFlush()) { scheduler.Flush(); num_draws = 0; } } vk::RenderPass RenderpassCache::GetRenderpass(VideoCore::PixelFormat color, VideoCore::PixelFormat depth, bool is_clear) { std::scoped_lock lock{cache_mutex}; const u32 color_index = color == VideoCore::PixelFormat::Invalid ? MAX_COLOR_FORMATS : static_cast(color); const u32 depth_index = depth == VideoCore::PixelFormat::Invalid ? MAX_DEPTH_FORMATS : (static_cast(depth) - 14); ASSERT_MSG(color_index <= MAX_COLOR_FORMATS && depth_index <= MAX_DEPTH_FORMATS, "Invalid color index {} and/or depth_index {}", color_index, depth_index); vk::UniqueRenderPass& renderpass = cached_renderpasses[color_index][depth_index][is_clear]; if (!renderpass) { const vk::Format color_format = instance.GetTraits(color).native; const vk::Format depth_format = instance.GetTraits(depth).native; const vk::AttachmentLoadOp load_op = is_clear ? vk::AttachmentLoadOp::eClear : vk::AttachmentLoadOp::eLoad; renderpass = CreateRenderPass(color_format, depth_format, load_op); } return *renderpass; } vk::UniqueRenderPass RenderpassCache::CreateRenderPass(vk::Format color, vk::Format depth, vk::AttachmentLoadOp load_op) const { 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 = vk::ImageLayout::eGeneral, .finalLayout = vk::ImageLayout::eGeneral, }; color_attachment_ref = vk::AttachmentReference{ .attachment = attachment_count++, .layout = vk::ImageLayout::eGeneral, }; use_color = true; } if (depth != vk::Format::eUndefined) { attachments[attachment_count] = vk::AttachmentDescription{ .format = depth, .loadOp = load_op, .storeOp = vk::AttachmentStoreOp::eStore, .stencilLoadOp = load_op, .stencilStoreOp = vk::AttachmentStoreOp::eStore, .initialLayout = vk::ImageLayout::eGeneral, .finalLayout = vk::ImageLayout::eGeneral, }; depth_attachment_ref = vk::AttachmentReference{ .attachment = attachment_count++, .layout = vk::ImageLayout::eGeneral, }; use_depth = true; } 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 = 0, .pDependencies = nullptr, }; return instance.GetDevice().createRenderPassUnique(renderpass_info); } } // namespace Vulkan