citra/src/video_core/renderer_vulkan/vk_renderpass_cache.cpp

222 lines
8.2 KiB
C++

// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <limits>
#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<u32> draw_rect) {
const vk::Rect2D render_area = {
.offset{
.x = static_cast<s32>(draw_rect.left),
.y = static_cast<s32>(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<vk::ImageMemoryBarrier, 2> barriers;
for (u32 i = 0; i < images.size(); i++) {
if (!images[i]) {
continue;
}
const bool is_color = static_cast<bool>(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<u32>(color);
const u32 depth_index = depth == VideoCore::PixelFormat::Invalid
? MAX_DEPTH_FORMATS
: (static_cast<u32>(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<vk::AttachmentDescription, 2> 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