Cemu/src/Cafe/HW/Latte/Renderer/Vulkan/TextureReadbackVk.cpp
Exzap 0f0ce67aa5 Latte: Add support for VK_EXT_attachment_feedback_loop
Requires both VK_EXT_attachment_feedback_loop and VK_EXT_attachment_feedback_loop_dynamic_state to be provided by the driver. Otherwise it will fall back to the old code. Main benefit is for improving performance as it makes barriers cheaper and doesn't force us to unnecessarily interrupt renderpasses
2026-06-27 07:15:50 +02:00

156 lines
5.6 KiB
C++

#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h"
#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanTextureReadback.h"
#include "Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.h"
LatteTextureReadbackInfoVk::LatteTextureReadbackInfoVk(VkDevice device, LatteTextureView* textureView)
: LatteTextureReadbackInfo(textureView), m_device(device)
{
m_image_size = GetImageSize(textureView);
}
LatteTextureReadbackInfoVk::~LatteTextureReadbackInfoVk()
{
}
uint32 LatteTextureReadbackInfoVk::GetImageSize(LatteTextureView* textureView)
{
const auto* baseTexture = (LatteTextureVk*)textureView->baseTexture;
// handle format
const auto textureFormat = baseTexture->GetFormat();
if (textureView->format == Latte::E_GX2SURFFMT::R8_G8_B8_A8_UNORM)
{
cemu_assert(textureFormat == VK_FORMAT_R8G8B8A8_UNORM);
return baseTexture->width * baseTexture->height * 4;
}
else if (textureView->format == Latte::E_GX2SURFFMT::R8_UNORM )
{
cemu_assert(textureFormat == VK_FORMAT_R8_UNORM);
return baseTexture->width * baseTexture->height * 1;
}
else if (textureView->format == Latte::E_GX2SURFFMT::R8_G8_B8_A8_SRGB)
{
cemu_assert(textureFormat == VK_FORMAT_R8G8B8A8_SRGB);
return baseTexture->width * baseTexture->height * 4;
}
else if (textureView->format == Latte::E_GX2SURFFMT::R32_G32_B32_A32_FLOAT)
{
cemu_assert(textureFormat == VK_FORMAT_R32G32B32A32_SFLOAT);
return baseTexture->width * baseTexture->height * 16;
}
else if (textureView->format == Latte::E_GX2SURFFMT::R32_FLOAT)
{
cemu_assert(textureFormat == VK_FORMAT_R32_SFLOAT || textureFormat == VK_FORMAT_D32_SFLOAT);
if (baseTexture->isDepth)
return baseTexture->width * baseTexture->height * 4;
else
return baseTexture->width * baseTexture->height * 4;
}
else if (textureView->format == Latte::E_GX2SURFFMT::R16_UNORM)
{
cemu_assert(textureFormat == VK_FORMAT_R16_UNORM);
if (baseTexture->isDepth)
{
cemu_assert_debug(false);
return baseTexture->width * baseTexture->height * 2;
}
else
{
return baseTexture->width * baseTexture->height * 2;
}
}
else if (textureView->format == Latte::E_GX2SURFFMT::R16_G16_B16_A16_FLOAT)
{
cemu_assert(textureFormat == VK_FORMAT_R16G16B16A16_SFLOAT);
return baseTexture->width * baseTexture->height * 8;
}
else if (textureView->format == Latte::E_GX2SURFFMT::R8_G8_UNORM)
{
cemu_assert(textureFormat == VK_FORMAT_R8G8_UNORM);
return baseTexture->width * baseTexture->height * 2;
}
else if (textureView->format == Latte::E_GX2SURFFMT::R16_G16_B16_A16_UNORM)
{
cemu_assert(textureFormat == VK_FORMAT_R16G16B16A16_UNORM);
return baseTexture->width * baseTexture->height * 8;
}
else if (textureView->format == Latte::E_GX2SURFFMT::D24_S8_UNORM)
{
cemu_assert(textureFormat == VK_FORMAT_D24_UNORM_S8_UINT);
// todo - if driver does not support VK_FORMAT_D24_UNORM_S8_UINT this is represented as VK_FORMAT_D32_SFLOAT_S8_UINT which is 8 bytes
return baseTexture->width * baseTexture->height * 4;
}
else if (textureView->format == Latte::E_GX2SURFFMT::R5_G6_B5_UNORM )
{
if(textureFormat == VK_FORMAT_R5G6B5_UNORM_PACK16){
return baseTexture->width * baseTexture->height * 2;
}
return 0;
}
else
{
cemuLog_log(LogType::Force, "Unsupported texture readback format {:04x}", (uint32)textureView->format);
cemu_assert_debug(false);
return 0;
}
}
void LatteTextureReadbackInfoVk::StartTransfer()
{
cemu_assert(m_textureView);
auto* baseTexture = (LatteTextureVk*)m_textureView->baseTexture;
baseTexture->GetImageObj()->flagForCurrentCommandBuffer();
cemu_assert_debug(m_textureView->firstSlice == 0);
cemu_assert_debug(m_textureView->firstMip == 0);
cemu_assert_debug(m_textureView->baseTexture->dim != Latte::E_DIM::DIM_3D);
VkBufferImageCopy region{};
region.bufferOffset = m_buffer_offset;
region.bufferRowLength = baseTexture->width;
region.bufferImageHeight = baseTexture->height;
region.imageSubresource.aspectMask = baseTexture->GetImageAspect();
region.imageSubresource.baseArrayLayer = 0;
region.imageSubresource.layerCount = 1;
region.imageSubresource.mipLevel = 0;
region.imageOffset = {0,0,0};
region.imageExtent = {(uint32)baseTexture->width,(uint32)baseTexture->height,1};
const auto renderer = VulkanRenderer::GetInstance();
renderer->draw_endRenderPass();
renderer->barrier_image<VulkanRenderer::ANY_TRANSFER | VulkanRenderer::IMAGE_WRITE, VulkanRenderer::TRANSFER_READ>(baseTexture, region.imageSubresource, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
renderer->barrier_sequentializeTransfer();
vkCmdCopyImageToBuffer(renderer->getCurrentCommandBuffer(), baseTexture->GetImageObj()->m_image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, m_buffer, 1, &region);
renderer->barrier_sequentializeTransfer();
renderer->barrier_image<VulkanRenderer::TRANSFER_READ, VulkanRenderer::ANY_TRANSFER | VulkanRenderer::IMAGE_WRITE>(baseTexture, region.imageSubresource, baseTexture->GetDefaultLayout()); // make sure transfer is finished before image is modified
renderer->barrier_bufferRange<VulkanRenderer::TRANSFER_WRITE, VulkanRenderer::HOST_READ>(m_buffer, m_buffer_offset, m_image_size); // make sure transfer is finished before result is read
m_associatedCommandBufferId = renderer->GetCurrentCommandBufferId();
m_textureView = nullptr;
// to decrease latency of readbacks make sure that the current command buffer is submitted soon
renderer->RequestSubmitSoon();
renderer->RequestSubmitOnIdle();
}
bool LatteTextureReadbackInfoVk::IsFinished()
{
const auto renderer = VulkanRenderer::GetInstance();
return renderer->HasCommandBufferFinished(m_associatedCommandBufferId);
}
void LatteTextureReadbackInfoVk::ForceFinish()
{
const auto renderer = VulkanRenderer::GetInstance();
renderer->WaitCommandBufferFinished(m_associatedCommandBufferId);
}