222 lines
8.2 KiB
C++
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
|