777 lines
32 KiB
C++
777 lines
32 KiB
C++
/**
|
|
******************************************************************************
|
|
* Xenia : Xbox 360 Emulator Research Project *
|
|
******************************************************************************
|
|
* Copyright 2021 Ben Vanik. All rights reserved. *
|
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
|
******************************************************************************
|
|
*/
|
|
|
|
#include "xenia/gpu/vulkan/vulkan_render_target_cache.h"
|
|
|
|
#include <algorithm>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <cstring>
|
|
#include <tuple>
|
|
#include <utility>
|
|
|
|
#include "xenia/base/assert.h"
|
|
#include "xenia/base/logging.h"
|
|
#include "xenia/base/math.h"
|
|
#include "xenia/gpu/registers.h"
|
|
#include "xenia/gpu/vulkan/vulkan_command_processor.h"
|
|
#include "xenia/ui/vulkan/vulkan_util.h"
|
|
|
|
namespace xe {
|
|
namespace gpu {
|
|
namespace vulkan {
|
|
|
|
VulkanRenderTargetCache::VulkanRenderTargetCache(
|
|
VulkanCommandProcessor& command_processor,
|
|
const RegisterFile& register_file)
|
|
: RenderTargetCache(register_file), command_processor_(command_processor) {}
|
|
|
|
VulkanRenderTargetCache::~VulkanRenderTargetCache() { Shutdown(true); }
|
|
|
|
bool VulkanRenderTargetCache::Initialize() {
|
|
InitializeCommon();
|
|
return true;
|
|
}
|
|
|
|
void VulkanRenderTargetCache::Shutdown(bool from_destructor) {
|
|
const ui::vulkan::VulkanProvider& provider =
|
|
command_processor_.GetVulkanContext().GetVulkanProvider();
|
|
const ui::vulkan::VulkanProvider::DeviceFunctions& dfn = provider.dfn();
|
|
VkDevice device = provider.device();
|
|
|
|
last_update_framebuffer_ = VK_NULL_HANDLE;
|
|
for (const auto& framebuffer_pair : framebuffers_) {
|
|
dfn.vkDestroyFramebuffer(device, framebuffer_pair.second.framebuffer,
|
|
nullptr);
|
|
}
|
|
framebuffers_.clear();
|
|
|
|
last_update_render_pass_ = VK_NULL_HANDLE;
|
|
for (const auto& render_pass_pair : render_passes_) {
|
|
dfn.vkDestroyRenderPass(device, render_pass_pair.second, nullptr);
|
|
}
|
|
render_passes_.clear();
|
|
|
|
if (!from_destructor) {
|
|
ShutdownCommon();
|
|
}
|
|
}
|
|
|
|
void VulkanRenderTargetCache::ClearCache() {
|
|
const ui::vulkan::VulkanProvider& provider =
|
|
command_processor_.GetVulkanContext().GetVulkanProvider();
|
|
const ui::vulkan::VulkanProvider::DeviceFunctions& dfn = provider.dfn();
|
|
VkDevice device = provider.device();
|
|
|
|
// Framebuffer objects must be destroyed because they reference views of
|
|
// attachment images, which may be removed by the common ClearCache.
|
|
last_update_framebuffer_ = VK_NULL_HANDLE;
|
|
for (const auto& framebuffer_pair : framebuffers_) {
|
|
dfn.vkDestroyFramebuffer(device, framebuffer_pair.second.framebuffer,
|
|
nullptr);
|
|
}
|
|
framebuffers_.clear();
|
|
|
|
last_update_render_pass_ = VK_NULL_HANDLE;
|
|
for (const auto& render_pass_pair : render_passes_) {
|
|
dfn.vkDestroyRenderPass(device, render_pass_pair.second, nullptr);
|
|
}
|
|
render_passes_.clear();
|
|
|
|
RenderTargetCache::ClearCache();
|
|
}
|
|
|
|
bool VulkanRenderTargetCache::Update(bool is_rasterization_done,
|
|
uint32_t shader_writes_color_targets) {
|
|
if (!RenderTargetCache::Update(is_rasterization_done,
|
|
shader_writes_color_targets)) {
|
|
return false;
|
|
}
|
|
|
|
auto rb_surface_info = register_file().Get<reg::RB_SURFACE_INFO>();
|
|
RenderTarget* const* depth_and_color_render_targets =
|
|
last_update_accumulated_render_targets();
|
|
uint32_t render_targets_are_srgb =
|
|
gamma_render_target_as_srgb_
|
|
? last_update_accumulated_color_targets_are_gamma()
|
|
: 0;
|
|
|
|
RenderPassKey render_pass_key;
|
|
render_pass_key.msaa_samples = rb_surface_info.msaa_samples;
|
|
// TODO(Triang3l): 2x MSAA as 4x.
|
|
if (depth_and_color_render_targets[0]) {
|
|
render_pass_key.depth_and_color_used |= 1 << 0;
|
|
render_pass_key.depth_format =
|
|
depth_and_color_render_targets[0]->key().GetDepthFormat();
|
|
}
|
|
if (depth_and_color_render_targets[1]) {
|
|
render_pass_key.depth_and_color_used |= 1 << 1;
|
|
render_pass_key.color_0_view_format =
|
|
(render_targets_are_srgb & (1 << 0))
|
|
? xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA
|
|
: depth_and_color_render_targets[1]->key().GetColorFormat();
|
|
}
|
|
if (depth_and_color_render_targets[2]) {
|
|
render_pass_key.depth_and_color_used |= 1 << 2;
|
|
render_pass_key.color_1_view_format =
|
|
(render_targets_are_srgb & (1 << 1))
|
|
? xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA
|
|
: depth_and_color_render_targets[2]->key().GetColorFormat();
|
|
}
|
|
if (depth_and_color_render_targets[3]) {
|
|
render_pass_key.depth_and_color_used |= 1 << 3;
|
|
render_pass_key.color_2_view_format =
|
|
(render_targets_are_srgb & (1 << 2))
|
|
? xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA
|
|
: depth_and_color_render_targets[3]->key().GetColorFormat();
|
|
}
|
|
if (depth_and_color_render_targets[4]) {
|
|
render_pass_key.depth_and_color_used |= 1 << 4;
|
|
render_pass_key.color_3_view_format =
|
|
(render_targets_are_srgb & (1 << 3))
|
|
? xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA
|
|
: depth_and_color_render_targets[4]->key().GetColorFormat();
|
|
}
|
|
|
|
const Framebuffer* framebuffer = last_update_framebuffer_;
|
|
VkRenderPass render_pass = last_update_render_pass_key_ == render_pass_key
|
|
? last_update_render_pass_
|
|
: VK_NULL_HANDLE;
|
|
if (render_pass == VK_NULL_HANDLE) {
|
|
render_pass = GetRenderPass(render_pass_key);
|
|
if (render_pass == VK_NULL_HANDLE) {
|
|
return false;
|
|
}
|
|
// Framebuffer for a different render pass needed now.
|
|
framebuffer = nullptr;
|
|
}
|
|
|
|
uint32_t pitch_tiles_at_32bpp =
|
|
((rb_surface_info.surface_pitch
|
|
<< uint32_t(rb_surface_info.msaa_samples >= xenos::MsaaSamples::k4X)) +
|
|
(xenos::kEdramTileWidthSamples - 1)) /
|
|
xenos::kEdramTileWidthSamples;
|
|
if (framebuffer) {
|
|
if (last_update_framebuffer_pitch_tiles_at_32bpp_ != pitch_tiles_at_32bpp ||
|
|
std::memcmp(last_update_framebuffer_attachments_,
|
|
depth_and_color_render_targets,
|
|
sizeof(last_update_framebuffer_attachments_))) {
|
|
framebuffer = nullptr;
|
|
}
|
|
}
|
|
if (!framebuffer) {
|
|
framebuffer = GetFramebuffer(render_pass_key, pitch_tiles_at_32bpp,
|
|
depth_and_color_render_targets);
|
|
if (!framebuffer) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Successful update - write the new configuration.
|
|
last_update_render_pass_key_ = render_pass_key;
|
|
last_update_render_pass_ = render_pass;
|
|
last_update_framebuffer_pitch_tiles_at_32bpp_ = pitch_tiles_at_32bpp;
|
|
std::memcpy(last_update_framebuffer_attachments_,
|
|
depth_and_color_render_targets,
|
|
sizeof(last_update_framebuffer_attachments_));
|
|
last_update_framebuffer_ = framebuffer;
|
|
|
|
// Transition the used render targets.
|
|
VkPipelineStageFlags barrier_src_stage_mask = 0;
|
|
VkPipelineStageFlags barrier_dst_stage_mask = 0;
|
|
VkImageMemoryBarrier barrier_image_memory[1 + xenos::kMaxColorRenderTargets];
|
|
uint32_t barrier_image_memory_count = 0;
|
|
for (uint32_t i = 0; i < 1 + xenos::kMaxColorRenderTargets; ++i) {
|
|
RenderTarget* rt = depth_and_color_render_targets[i];
|
|
if (!rt) {
|
|
continue;
|
|
}
|
|
auto& vulkan_rt = *static_cast<VulkanRenderTarget*>(rt);
|
|
VkPipelineStageFlags rt_src_stage_mask = vulkan_rt.current_stage_mask();
|
|
VkAccessFlags rt_src_access_mask = vulkan_rt.current_access_mask();
|
|
VkImageLayout rt_old_layout = vulkan_rt.current_layout();
|
|
VkPipelineStageFlags rt_dst_stage_mask;
|
|
VkAccessFlags rt_dst_access_mask;
|
|
VkImageLayout rt_new_layout;
|
|
if (i) {
|
|
rt_dst_stage_mask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
|
rt_dst_access_mask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT |
|
|
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
|
rt_new_layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
|
} else {
|
|
rt_dst_stage_mask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
|
|
VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
|
|
rt_dst_access_mask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT |
|
|
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
|
|
rt_new_layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
|
}
|
|
bool rt_image_memory_barrier_needed =
|
|
rt_src_access_mask != rt_dst_access_mask ||
|
|
rt_old_layout != rt_new_layout;
|
|
if (rt_image_memory_barrier_needed ||
|
|
rt_src_stage_mask != rt_dst_stage_mask) {
|
|
barrier_src_stage_mask |= rt_src_stage_mask;
|
|
barrier_dst_stage_mask |= rt_dst_stage_mask;
|
|
if (rt_image_memory_barrier_needed) {
|
|
VkImageMemoryBarrier& rt_image_memory_barrier =
|
|
barrier_image_memory[barrier_image_memory_count++];
|
|
rt_image_memory_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
|
rt_image_memory_barrier.pNext = nullptr;
|
|
rt_image_memory_barrier.srcAccessMask = rt_src_access_mask;
|
|
rt_image_memory_barrier.dstAccessMask = rt_dst_access_mask;
|
|
rt_image_memory_barrier.oldLayout = rt_old_layout;
|
|
rt_image_memory_barrier.newLayout = rt_new_layout;
|
|
rt_image_memory_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
|
rt_image_memory_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
|
rt_image_memory_barrier.image = vulkan_rt.image();
|
|
ui::vulkan::util::InitializeSubresourceRange(
|
|
rt_image_memory_barrier.subresourceRange,
|
|
i ? VK_IMAGE_ASPECT_COLOR_BIT
|
|
: (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT));
|
|
}
|
|
vulkan_rt.SetUsage(rt_dst_stage_mask, rt_dst_access_mask, rt_new_layout);
|
|
}
|
|
}
|
|
if (barrier_src_stage_mask || barrier_dst_stage_mask ||
|
|
barrier_image_memory_count) {
|
|
if (!barrier_src_stage_mask) {
|
|
barrier_src_stage_mask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
|
|
}
|
|
if (!barrier_dst_stage_mask) {
|
|
barrier_dst_stage_mask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
|
|
}
|
|
command_processor_.EndRenderPass();
|
|
command_processor_.deferred_command_buffer().CmdVkPipelineBarrier(
|
|
barrier_src_stage_mask, barrier_dst_stage_mask, 0, 0, nullptr, 0,
|
|
nullptr, barrier_image_memory_count, barrier_image_memory);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
VkRenderPass VulkanRenderTargetCache::GetRenderPass(RenderPassKey key) {
|
|
auto it = render_passes_.find(key.key);
|
|
if (it != render_passes_.end()) {
|
|
return it->second;
|
|
}
|
|
|
|
VkSampleCountFlagBits samples;
|
|
switch (key.msaa_samples) {
|
|
case xenos::MsaaSamples::k1X:
|
|
samples = VK_SAMPLE_COUNT_1_BIT;
|
|
break;
|
|
case xenos::MsaaSamples::k2X:
|
|
// Using unconditionally because if 2x is emulated as 4x, the key will
|
|
// also contain 4x.
|
|
samples = VK_SAMPLE_COUNT_2_BIT;
|
|
break;
|
|
case xenos::MsaaSamples::k4X:
|
|
samples = VK_SAMPLE_COUNT_4_BIT;
|
|
break;
|
|
default:
|
|
return VK_NULL_HANDLE;
|
|
}
|
|
|
|
VkAttachmentDescription attachments[1 + xenos::kMaxColorRenderTargets];
|
|
if (key.depth_and_color_used & 0b1) {
|
|
VkAttachmentDescription& attachment = attachments[0];
|
|
attachment.flags = 0;
|
|
attachment.format = GetDepthVulkanFormat(key.depth_format);
|
|
attachment.samples = samples;
|
|
attachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
|
|
attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
|
attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
|
|
attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_STORE;
|
|
attachment.initialLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
|
attachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
|
}
|
|
VkAttachmentReference color_attachments[xenos::kMaxColorRenderTargets];
|
|
xenos::ColorRenderTargetFormat color_formats[] = {
|
|
key.color_0_view_format,
|
|
key.color_1_view_format,
|
|
key.color_2_view_format,
|
|
key.color_3_view_format,
|
|
};
|
|
for (uint32_t i = 0; i < xenos::kMaxColorRenderTargets; ++i) {
|
|
VkAttachmentReference& color_attachment = color_attachments[i];
|
|
color_attachment.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
|
uint32_t attachment_bit = uint32_t(1) << (1 + i);
|
|
if (!(key.depth_and_color_used & attachment_bit)) {
|
|
color_attachment.attachment = VK_ATTACHMENT_UNUSED;
|
|
continue;
|
|
}
|
|
uint32_t attachment_index =
|
|
xe::bit_count(key.depth_and_color_used & (attachment_bit - 1));
|
|
color_attachment.attachment = attachment_index;
|
|
VkAttachmentDescription& attachment = attachments[attachment_index];
|
|
attachment.flags = 0;
|
|
attachment.format = GetColorVulkanFormat(color_formats[i]);
|
|
attachment.samples = samples;
|
|
attachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
|
|
attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
|
attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
|
attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
|
attachment.initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
|
attachment.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
|
}
|
|
|
|
VkAttachmentReference depth_stencil_attachment;
|
|
depth_stencil_attachment.attachment =
|
|
(key.depth_and_color_used & 0b1) ? 0 : VK_ATTACHMENT_UNUSED;
|
|
depth_stencil_attachment.layout =
|
|
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
|
|
|
VkSubpassDescription subpass;
|
|
subpass.flags = 0;
|
|
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
|
|
subpass.inputAttachmentCount = 0;
|
|
subpass.pInputAttachments = nullptr;
|
|
subpass.colorAttachmentCount =
|
|
32 - xe::lzcnt(uint32_t(key.depth_and_color_used >> 1));
|
|
subpass.pColorAttachments = color_attachments;
|
|
subpass.pResolveAttachments = nullptr;
|
|
subpass.pDepthStencilAttachment =
|
|
(key.depth_and_color_used & 0b1) ? &depth_stencil_attachment : nullptr;
|
|
subpass.preserveAttachmentCount = 0;
|
|
subpass.pPreserveAttachments = nullptr;
|
|
|
|
VkPipelineStageFlags dependency_stage_mask = 0;
|
|
VkAccessFlags dependency_access_mask = 0;
|
|
if (key.depth_and_color_used & 0b1) {
|
|
dependency_stage_mask |= VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
|
|
VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
|
|
dependency_access_mask |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT |
|
|
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
|
|
}
|
|
if (key.depth_and_color_used >> 1) {
|
|
dependency_stage_mask |= VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
|
dependency_access_mask |= VK_ACCESS_COLOR_ATTACHMENT_READ_BIT |
|
|
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
|
}
|
|
VkSubpassDependency subpass_dependencies[2];
|
|
subpass_dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
|
|
subpass_dependencies[0].dstSubpass = 0;
|
|
subpass_dependencies[0].srcStageMask = dependency_stage_mask;
|
|
subpass_dependencies[0].dstStageMask = dependency_stage_mask;
|
|
subpass_dependencies[0].srcAccessMask = dependency_access_mask;
|
|
subpass_dependencies[0].dstAccessMask = dependency_access_mask;
|
|
subpass_dependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
|
|
subpass_dependencies[1].srcSubpass = 0;
|
|
subpass_dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL;
|
|
subpass_dependencies[1].srcStageMask = dependency_stage_mask;
|
|
subpass_dependencies[1].dstStageMask = dependency_stage_mask;
|
|
subpass_dependencies[1].srcAccessMask = dependency_access_mask;
|
|
subpass_dependencies[1].dstAccessMask = dependency_access_mask;
|
|
subpass_dependencies[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
|
|
|
|
VkRenderPassCreateInfo render_pass_create_info;
|
|
render_pass_create_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
|
|
render_pass_create_info.pNext = nullptr;
|
|
render_pass_create_info.flags = 0;
|
|
render_pass_create_info.attachmentCount =
|
|
xe::bit_count(key.depth_and_color_used);
|
|
render_pass_create_info.pAttachments = attachments;
|
|
render_pass_create_info.subpassCount = 1;
|
|
render_pass_create_info.pSubpasses = &subpass;
|
|
render_pass_create_info.dependencyCount =
|
|
key.depth_and_color_used ? uint32_t(xe::countof(subpass_dependencies))
|
|
: 0;
|
|
render_pass_create_info.pDependencies = subpass_dependencies;
|
|
|
|
const ui::vulkan::VulkanProvider& provider =
|
|
command_processor_.GetVulkanContext().GetVulkanProvider();
|
|
const ui::vulkan::VulkanProvider::DeviceFunctions& dfn = provider.dfn();
|
|
VkDevice device = provider.device();
|
|
VkRenderPass render_pass;
|
|
if (dfn.vkCreateRenderPass(device, &render_pass_create_info, nullptr,
|
|
&render_pass) != VK_SUCCESS) {
|
|
XELOGE("Failed to create a Vulkan render pass");
|
|
return VK_NULL_HANDLE;
|
|
}
|
|
render_passes_.emplace(key.key, render_pass);
|
|
return render_pass;
|
|
}
|
|
|
|
VkFormat VulkanRenderTargetCache::GetDepthVulkanFormat(
|
|
xenos::DepthRenderTargetFormat format) const {
|
|
// TODO(Triang3l): Conditional 24-bit depth.
|
|
return VK_FORMAT_D32_SFLOAT_S8_UINT;
|
|
}
|
|
|
|
VkFormat VulkanRenderTargetCache::GetColorVulkanFormat(
|
|
xenos::ColorRenderTargetFormat format) const {
|
|
switch (format) {
|
|
case xenos::ColorRenderTargetFormat::k_8_8_8_8:
|
|
return VK_FORMAT_R8G8B8A8_UNORM;
|
|
case xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA:
|
|
return gamma_render_target_as_srgb_ ? VK_FORMAT_R8G8B8A8_SRGB
|
|
: VK_FORMAT_R8G8B8A8_UNORM;
|
|
case xenos::ColorRenderTargetFormat::k_2_10_10_10:
|
|
case xenos::ColorRenderTargetFormat::k_2_10_10_10_AS_10_10_10_10:
|
|
return VK_FORMAT_A8B8G8R8_UNORM_PACK32;
|
|
case xenos::ColorRenderTargetFormat::k_2_10_10_10_FLOAT:
|
|
case xenos::ColorRenderTargetFormat::k_2_10_10_10_FLOAT_AS_16_16_16_16:
|
|
return VK_FORMAT_R16G16B16A16_SFLOAT;
|
|
case xenos::ColorRenderTargetFormat::k_16_16:
|
|
// TODO(Triang3l): Fallback to float16 (disregarding clearing correctness
|
|
// likely) - possibly on render target gathering, treating them entirely
|
|
// as float16.
|
|
return VK_FORMAT_R16G16_SNORM;
|
|
case xenos::ColorRenderTargetFormat::k_16_16_16_16:
|
|
// TODO(Triang3l): Fallback to float16 (disregarding clearing correctness
|
|
// likely) - possibly on render target gathering, treating them entirely
|
|
// as float16.
|
|
return VK_FORMAT_R16G16B16A16_SNORM;
|
|
case xenos::ColorRenderTargetFormat::k_16_16_FLOAT:
|
|
return VK_FORMAT_R16G16_SFLOAT;
|
|
case xenos::ColorRenderTargetFormat::k_16_16_16_16_FLOAT:
|
|
return VK_FORMAT_R16G16B16A16_SFLOAT;
|
|
case xenos::ColorRenderTargetFormat::k_32_FLOAT:
|
|
return VK_FORMAT_R32_SFLOAT;
|
|
case xenos::ColorRenderTargetFormat::k_32_32_FLOAT:
|
|
return VK_FORMAT_R32G32_SFLOAT;
|
|
default:
|
|
assert_unhandled_case(format);
|
|
return VK_FORMAT_UNDEFINED;
|
|
}
|
|
}
|
|
|
|
VkFormat VulkanRenderTargetCache::GetColorOwnershipTransferVulkanFormat(
|
|
xenos::ColorRenderTargetFormat format, bool* is_integer_out) const {
|
|
if (is_integer_out) {
|
|
*is_integer_out = true;
|
|
}
|
|
// Floating-point numbers have NaNs that need to be propagated without
|
|
// modifications to the bit representation, and SNORM has two representations
|
|
// of -1.
|
|
switch (format) {
|
|
case xenos::ColorRenderTargetFormat::k_16_16:
|
|
case xenos::ColorRenderTargetFormat::k_16_16_FLOAT:
|
|
return VK_FORMAT_R16G16_UINT;
|
|
case xenos::ColorRenderTargetFormat::k_16_16_16_16:
|
|
case xenos::ColorRenderTargetFormat::k_16_16_16_16_FLOAT:
|
|
return VK_FORMAT_R16G16B16A16_UINT;
|
|
case xenos::ColorRenderTargetFormat::k_32_FLOAT:
|
|
return VK_FORMAT_R32_UINT;
|
|
case xenos::ColorRenderTargetFormat::k_32_32_FLOAT:
|
|
return VK_FORMAT_R32G32_UINT;
|
|
default:
|
|
if (is_integer_out) {
|
|
*is_integer_out = false;
|
|
}
|
|
return GetColorVulkanFormat(format);
|
|
}
|
|
}
|
|
|
|
VulkanRenderTargetCache::VulkanRenderTarget::~VulkanRenderTarget() {
|
|
const ui::vulkan::VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
|
|
VkDevice device = provider_.device();
|
|
if (view_color_transfer_separate_ != VK_NULL_HANDLE) {
|
|
dfn.vkDestroyImageView(device, view_color_transfer_separate_, nullptr);
|
|
}
|
|
if (view_srgb_ != VK_NULL_HANDLE) {
|
|
dfn.vkDestroyImageView(device, view_srgb_, nullptr);
|
|
}
|
|
if (view_stencil_ != VK_NULL_HANDLE) {
|
|
dfn.vkDestroyImageView(device, view_stencil_, nullptr);
|
|
}
|
|
if (view_depth_stencil_ != VK_NULL_HANDLE) {
|
|
dfn.vkDestroyImageView(device, view_depth_stencil_, nullptr);
|
|
}
|
|
dfn.vkDestroyImageView(device, view_depth_color_, nullptr);
|
|
dfn.vkDestroyImage(device, image_, nullptr);
|
|
dfn.vkFreeMemory(device, memory_, nullptr);
|
|
}
|
|
|
|
uint32_t VulkanRenderTargetCache::GetMaxRenderTargetWidth() const {
|
|
const ui::vulkan::VulkanProvider& provider =
|
|
command_processor_.GetVulkanContext().GetVulkanProvider();
|
|
return provider.device_properties().limits.maxFramebufferWidth;
|
|
}
|
|
|
|
uint32_t VulkanRenderTargetCache::GetMaxRenderTargetHeight() const {
|
|
const ui::vulkan::VulkanProvider& provider =
|
|
command_processor_.GetVulkanContext().GetVulkanProvider();
|
|
return provider.device_properties().limits.maxFramebufferHeight;
|
|
}
|
|
|
|
RenderTargetCache::RenderTarget* VulkanRenderTargetCache::CreateRenderTarget(
|
|
RenderTargetKey key) {
|
|
const ui::vulkan::VulkanProvider& provider =
|
|
command_processor_.GetVulkanContext().GetVulkanProvider();
|
|
const ui::vulkan::VulkanProvider::DeviceFunctions& dfn = provider.dfn();
|
|
VkDevice device = provider.device();
|
|
|
|
// Create the image.
|
|
|
|
VkImageCreateInfo image_create_info;
|
|
image_create_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
|
|
image_create_info.pNext = nullptr;
|
|
image_create_info.flags = 0;
|
|
image_create_info.imageType = VK_IMAGE_TYPE_2D;
|
|
// TODO(Triang3l): Resolution scaling.
|
|
image_create_info.extent.width = key.GetWidth();
|
|
image_create_info.extent.height =
|
|
GetRenderTargetHeight(key.pitch_tiles_at_32bpp, key.msaa_samples);
|
|
image_create_info.extent.depth = 1;
|
|
image_create_info.mipLevels = 1;
|
|
image_create_info.arrayLayers = 1;
|
|
// TODO(Triang3l): 2x MSAA as 4x.
|
|
image_create_info.samples =
|
|
VkSampleCountFlagBits(uint32_t(1) << uint32_t(key.msaa_samples));
|
|
image_create_info.tiling = VK_IMAGE_TILING_OPTIMAL;
|
|
image_create_info.usage = VK_IMAGE_USAGE_SAMPLED_BIT;
|
|
image_create_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
|
image_create_info.queueFamilyIndexCount = 0;
|
|
image_create_info.pQueueFamilyIndices = nullptr;
|
|
image_create_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
|
VkFormat transfer_format;
|
|
bool is_srgb_view_needed = false;
|
|
if (key.is_depth) {
|
|
image_create_info.format = GetDepthVulkanFormat(key.GetDepthFormat());
|
|
transfer_format = image_create_info.format;
|
|
image_create_info.usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
|
|
} else {
|
|
xenos::ColorRenderTargetFormat color_format = key.GetColorFormat();
|
|
image_create_info.format = GetColorVulkanFormat(color_format);
|
|
transfer_format = GetColorOwnershipTransferVulkanFormat(color_format);
|
|
is_srgb_view_needed =
|
|
gamma_render_target_as_srgb_ &&
|
|
(color_format == xenos::ColorRenderTargetFormat::k_8_8_8_8 ||
|
|
color_format == xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA);
|
|
if (image_create_info.format != transfer_format || is_srgb_view_needed) {
|
|
image_create_info.flags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT;
|
|
}
|
|
image_create_info.usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
|
|
}
|
|
if (image_create_info.format == VK_FORMAT_UNDEFINED) {
|
|
XELOGE("VulkanRenderTargetCache: Unknown {} render target format {}",
|
|
key.is_depth ? "depth" : "color", key.resource_format);
|
|
return nullptr;
|
|
}
|
|
VkImage image;
|
|
if (dfn.vkCreateImage(device, &image_create_info, nullptr, &image) !=
|
|
VK_SUCCESS) {
|
|
// TODO(Triang3l): Error message.
|
|
return nullptr;
|
|
}
|
|
|
|
// Allocate and bind the memory.
|
|
|
|
VkMemoryAllocateInfo memory_allocate_info;
|
|
VkMemoryRequirements memory_requirements;
|
|
dfn.vkGetImageMemoryRequirements(device, image, &memory_requirements);
|
|
if (!xe::bit_scan_forward(memory_requirements.memoryTypeBits &
|
|
provider.memory_types_device_local(),
|
|
&memory_allocate_info.memoryTypeIndex)) {
|
|
dfn.vkDestroyImage(device, image, nullptr);
|
|
return nullptr;
|
|
}
|
|
memory_allocate_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
|
VkMemoryDedicatedAllocateInfoKHR memory_dedicated_allocate_info;
|
|
if (provider.device_extensions().khr_dedicated_allocation) {
|
|
memory_dedicated_allocate_info.sType =
|
|
VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO_KHR;
|
|
memory_dedicated_allocate_info.pNext = nullptr;
|
|
memory_dedicated_allocate_info.image = image;
|
|
memory_dedicated_allocate_info.buffer = VK_NULL_HANDLE;
|
|
memory_allocate_info.pNext = &memory_dedicated_allocate_info;
|
|
} else {
|
|
memory_allocate_info.pNext = nullptr;
|
|
}
|
|
memory_allocate_info.allocationSize = memory_requirements.size;
|
|
VkDeviceMemory memory;
|
|
if (dfn.vkAllocateMemory(device, &memory_allocate_info, nullptr, &memory) !=
|
|
VK_SUCCESS) {
|
|
// TODO(Triang3l): Error message.
|
|
dfn.vkDestroyImage(device, image, nullptr);
|
|
return nullptr;
|
|
}
|
|
if (dfn.vkBindImageMemory(device, image, memory, 0) != VK_SUCCESS) {
|
|
// TODO(Triang3l): Error message.
|
|
dfn.vkDestroyImage(device, image, nullptr);
|
|
dfn.vkFreeMemory(device, memory, nullptr);
|
|
return nullptr;
|
|
}
|
|
|
|
// Create the image views.
|
|
|
|
VkImageViewCreateInfo view_create_info;
|
|
view_create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
|
|
view_create_info.pNext = nullptr;
|
|
view_create_info.flags = 0;
|
|
view_create_info.image = image;
|
|
view_create_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
|
view_create_info.format = image_create_info.format;
|
|
view_create_info.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
|
|
view_create_info.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
|
|
view_create_info.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
|
|
view_create_info.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;
|
|
ui::vulkan::util::InitializeSubresourceRange(
|
|
view_create_info.subresourceRange,
|
|
key.is_depth ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_COLOR_BIT);
|
|
VkImageView view_depth_color;
|
|
if (dfn.vkCreateImageView(device, &view_create_info, nullptr,
|
|
&view_depth_color) != VK_SUCCESS) {
|
|
// TODO(Triang3l): Error message.
|
|
dfn.vkDestroyImage(device, image, nullptr);
|
|
dfn.vkFreeMemory(device, memory, nullptr);
|
|
return nullptr;
|
|
}
|
|
VkImageView view_depth_stencil = VK_NULL_HANDLE;
|
|
VkImageView view_stencil = VK_NULL_HANDLE;
|
|
VkImageView view_srgb = VK_NULL_HANDLE;
|
|
VkImageView view_color_transfer_separate = VK_NULL_HANDLE;
|
|
if (key.is_depth) {
|
|
view_create_info.subresourceRange.aspectMask =
|
|
VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
|
|
if (dfn.vkCreateImageView(device, &view_create_info, nullptr,
|
|
&view_depth_stencil) != VK_SUCCESS) {
|
|
// TODO(Triang3l): Error message.
|
|
dfn.vkDestroyImageView(device, view_depth_color, nullptr);
|
|
dfn.vkDestroyImage(device, image, nullptr);
|
|
dfn.vkFreeMemory(device, memory, nullptr);
|
|
return nullptr;
|
|
}
|
|
view_create_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_STENCIL_BIT;
|
|
if (dfn.vkCreateImageView(device, &view_create_info, nullptr,
|
|
&view_stencil) != VK_SUCCESS) {
|
|
// TODO(Triang3l): Error message.
|
|
dfn.vkDestroyImageView(device, view_depth_stencil, nullptr);
|
|
dfn.vkDestroyImageView(device, view_depth_color, nullptr);
|
|
dfn.vkDestroyImage(device, image, nullptr);
|
|
dfn.vkFreeMemory(device, memory, nullptr);
|
|
return nullptr;
|
|
}
|
|
} else {
|
|
if (is_srgb_view_needed) {
|
|
view_create_info.format = VK_FORMAT_R8G8B8A8_SRGB;
|
|
if (dfn.vkCreateImageView(device, &view_create_info, nullptr,
|
|
&view_srgb) != VK_SUCCESS) {
|
|
// TODO(Triang3l): Error message.
|
|
dfn.vkDestroyImageView(device, view_depth_color, nullptr);
|
|
dfn.vkDestroyImage(device, image, nullptr);
|
|
dfn.vkFreeMemory(device, memory, nullptr);
|
|
return nullptr;
|
|
}
|
|
}
|
|
if (transfer_format != image_create_info.format) {
|
|
view_create_info.format = transfer_format;
|
|
if (dfn.vkCreateImageView(device, &view_create_info, nullptr,
|
|
&view_color_transfer_separate) != VK_SUCCESS) {
|
|
// TODO(Triang3l): Error message.
|
|
if (view_srgb != VK_NULL_HANDLE) {
|
|
dfn.vkDestroyImageView(device, view_srgb, nullptr);
|
|
}
|
|
dfn.vkDestroyImageView(device, view_depth_color, nullptr);
|
|
dfn.vkDestroyImage(device, image, nullptr);
|
|
dfn.vkFreeMemory(device, memory, nullptr);
|
|
return nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
VkImageView view_transfer_separate = VK_NULL_HANDLE;
|
|
|
|
return new VulkanRenderTarget(key, provider, image, memory, view_depth_color,
|
|
view_depth_stencil, view_stencil, view_srgb,
|
|
view_color_transfer_separate);
|
|
}
|
|
|
|
const VulkanRenderTargetCache::Framebuffer*
|
|
VulkanRenderTargetCache::GetFramebuffer(
|
|
RenderPassKey render_pass_key, uint32_t pitch_tiles_at_32bpp,
|
|
const RenderTarget* const* depth_and_color_render_targets) {
|
|
FramebufferKey key;
|
|
key.render_pass_key = render_pass_key;
|
|
key.pitch_tiles_at_32bpp = pitch_tiles_at_32bpp;
|
|
if (render_pass_key.depth_and_color_used & (1 << 0)) {
|
|
key.depth_base_tiles = depth_and_color_render_targets[0]->key().base_tiles;
|
|
}
|
|
if (render_pass_key.depth_and_color_used & (1 << 1)) {
|
|
key.color_0_base_tiles =
|
|
depth_and_color_render_targets[1]->key().base_tiles;
|
|
}
|
|
if (render_pass_key.depth_and_color_used & (1 << 2)) {
|
|
key.color_1_base_tiles =
|
|
depth_and_color_render_targets[2]->key().base_tiles;
|
|
}
|
|
if (render_pass_key.depth_and_color_used & (1 << 3)) {
|
|
key.color_2_base_tiles =
|
|
depth_and_color_render_targets[3]->key().base_tiles;
|
|
}
|
|
if (render_pass_key.depth_and_color_used & (1 << 4)) {
|
|
key.color_3_base_tiles =
|
|
depth_and_color_render_targets[4]->key().base_tiles;
|
|
}
|
|
auto it = framebuffers_.find(key);
|
|
if (it != framebuffers_.end()) {
|
|
return &it->second;
|
|
}
|
|
|
|
const ui::vulkan::VulkanProvider& provider =
|
|
command_processor_.GetVulkanContext().GetVulkanProvider();
|
|
const ui::vulkan::VulkanProvider::DeviceFunctions& dfn = provider.dfn();
|
|
VkDevice device = provider.device();
|
|
|
|
VkRenderPass render_pass = GetRenderPass(render_pass_key);
|
|
if (render_pass == VK_NULL_HANDLE) {
|
|
return nullptr;
|
|
}
|
|
|
|
VkImageView attachments[1 + xenos::kMaxColorRenderTargets];
|
|
uint32_t attachment_count = 0;
|
|
uint32_t depth_and_color_rts_remaining = render_pass_key.depth_and_color_used;
|
|
uint32_t rt_index;
|
|
while (xe::bit_scan_forward(depth_and_color_rts_remaining, &rt_index)) {
|
|
depth_and_color_rts_remaining &= ~(uint32_t(1) << rt_index);
|
|
const auto& vulkan_rt = *static_cast<const VulkanRenderTarget*>(
|
|
depth_and_color_render_targets[rt_index]);
|
|
attachments[attachment_count++] = rt_index ? vulkan_rt.view_depth_color()
|
|
: vulkan_rt.view_depth_stencil();
|
|
}
|
|
|
|
VkFramebufferCreateInfo framebuffer_create_info;
|
|
framebuffer_create_info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
|
|
framebuffer_create_info.pNext = nullptr;
|
|
framebuffer_create_info.flags = 0;
|
|
framebuffer_create_info.renderPass = render_pass;
|
|
framebuffer_create_info.attachmentCount = attachment_count;
|
|
framebuffer_create_info.pAttachments = attachments;
|
|
VkExtent2D host_extent;
|
|
if (pitch_tiles_at_32bpp) {
|
|
host_extent.width = RenderTargetKey::GetWidth(pitch_tiles_at_32bpp,
|
|
render_pass_key.msaa_samples);
|
|
host_extent.height = GetRenderTargetHeight(pitch_tiles_at_32bpp,
|
|
render_pass_key.msaa_samples);
|
|
} else {
|
|
assert_zero(render_pass_key.depth_and_color_used);
|
|
host_extent.width = 0;
|
|
host_extent.height = 0;
|
|
}
|
|
// Vulkan requires width and height greater than 0.
|
|
framebuffer_create_info.width = std::max(host_extent.width, uint32_t(1));
|
|
framebuffer_create_info.height = std::max(host_extent.height, uint32_t(1));
|
|
framebuffer_create_info.layers = 1;
|
|
VkFramebuffer framebuffer;
|
|
if (dfn.vkCreateFramebuffer(device, &framebuffer_create_info, nullptr,
|
|
&framebuffer) != VK_SUCCESS) {
|
|
return nullptr;
|
|
}
|
|
// Creates at a persistent location - safe to use pointers.
|
|
return &framebuffers_
|
|
.emplace(std::piecewise_construct, std::forward_as_tuple(key),
|
|
std::forward_as_tuple(framebuffer, host_extent))
|
|
.first->second;
|
|
}
|
|
|
|
} // namespace vulkan
|
|
} // namespace gpu
|
|
} // namespace xe
|