Latte: Add non-zero mip support to texture readback + refactor

y
This commit is contained in:
Exzap
2026-09-10 13:49:21 +02:00
parent afb97632f0
commit 9743a32005
18 changed files with 139 additions and 137 deletions

View File

@@ -206,13 +206,6 @@ bool LatteTC_HasTextureChanged(LatteTexture* hostTexture, bool force)
debug_printf("Force invalidate 0x%08x\n", hostTexture->physAddress);
hostTexture->forceInvalidate = false;
}
// if texture is written by GPU operations we switch to a faster hash implementation
// if (hostTexture->isUpdatedOnGPU && hostTexture->useLightHash == false)
// {
// hostTexture->useLightHash = true;
// // update hash
// hostTexture->texDataHash2 = LatteTexture_CalculateTextureDataHash(hostTexture);
// }
// only check each texture for updates once a frame
// todo: Instead of relying on frames, it would be better to recheck only after any GPU wait operation occurred.
if( hostTexture->lastDataUpdateFrameCounter == LatteGPUState.frameCounter && force == false)

View File

@@ -742,7 +742,9 @@ void LatteTextureLoader_writeReadbackTextureToMemory(LatteTextureDefinition* tex
{
optimizedLinearReadbackWriteLoop<uint64>(&textureLoader, linearPixelData, sourceRowPitch);
}
else if (textureData->format == Latte::E_GX2SURFFMT::R32_G32_B32_A32_FLOAT)
else if (textureData->format == Latte::E_GX2SURFFMT::R32_G32_B32_A32_FLOAT ||
textureData->format == Latte::E_GX2SURFFMT::R32_G32_B32_A32_SINT ||
textureData->format == Latte::E_GX2SURFFMT::R32_G32_B32_A32_UINT)
{
for (sint32 y = 0; y < textureLoader.height; y += textureLoader.stepY)
{

View File

@@ -181,3 +181,20 @@ bool LatteTextureReadback_ReadbackToLinearBlocking(LatteTextureView* sourceView,
delete info;
return true;
}
uint32 LatteTextureReadbackInfo::GetReadbackRowPitch(LatteTextureView* textureView, uint32 rowAlignment)
{
uint32 width = textureView->baseTexture->GetMipWidth(textureView->firstMip);
if (Latte::IsCompressedFormat(textureView->format))
width = (width + 3) / 4;
uint32 rowPitch = width * (Latte::GetFormatBits(textureView->format) / 8);
return (rowPitch + rowAlignment - 1) & ~(rowAlignment - 1);
}
uint32 LatteTextureReadbackInfo::GetReadbackImageSize(LatteTextureView* textureView, uint32 rowPitch)
{
uint32 height = textureView->baseTexture->GetMipHeight(textureView->firstMip);
if (Latte::IsCompressedFormat(textureView->format))
height = (height + 3) / 4;
return height * rowPitch;
}

View File

@@ -29,6 +29,9 @@ public:
uint32 m_rowPitch = 0; // bytes between rows in GetData(). Set by the backend
protected:
static uint32 GetReadbackRowPitch(LatteTextureView* textureView, uint32 rowAlignment = 1);
static uint32 GetReadbackImageSize(LatteTextureView* textureView, uint32 rowPitch);
LatteTextureView* m_textureView;
uint32 m_image_size = 0;
};

View File

@@ -75,6 +75,7 @@ LatteTextureMtl::LatteTextureMtl(class MetalRenderer* mtlRenderer, Latte::E_DIM
}
auto pixelFormat = GetMtlPixelFormat(format, isDepth);
m_isAlternateFormat = GetMtlPixelFormatInfo(format, isDepth).isAlternateFormat;
desc->setPixelFormat(pixelFormat);
MTL::TextureUsage usage = MTL::TextureUsageShaderRead | MTL::TextureUsagePixelFormatView;

View File

@@ -19,6 +19,8 @@ public:
void AllocateOnHost() override;
bool m_isAlternateFormat{}; // true if Metal pixel format does not 1:1 match Latte format
protected:
LatteTextureView* CreateView(Latte::E_DIM dim, Latte::E_GX2SURFFMT format, sint32 firstMip, sint32 mipCount, sint32 firstSlice, sint32 sliceCount) override;

View File

@@ -1,7 +1,6 @@
#include "Cafe/HW/Latte/Renderer/Metal/MetalRenderer.h"
#include "Cafe/HW/Latte/Renderer/Metal/LatteTextureReadbackMtl.h"
#include "Cafe/HW/Latte/Renderer/Metal/LatteTextureMtl.h"
#include "Cafe/HW/Latte/Renderer/Metal/LatteToMtl.h"
LatteTextureReadbackInfoMtl::~LatteTextureReadbackInfoMtl()
{
@@ -18,13 +17,12 @@ void LatteTextureReadbackInfoMtl::StartTransfer()
cemu_assert_debug(m_textureView->firstMip == 0);
cemu_assert_debug(m_textureView->baseTexture->dim != Latte::E_DIM::DIM_3D);
size_t bytesPerRow = GetMtlTextureBytesPerRow(baseTexture->format, baseTexture->isDepth, baseTexture->width);
m_rowPitch = static_cast<uint32>(bytesPerRow);
size_t bytesPerImage = GetMtlTextureBytesPerImage(baseTexture->format, baseTexture->isDepth, baseTexture->height, bytesPerRow);
m_rowPitch = GetReadbackRowPitch(m_textureView);
m_image_size = GetReadbackImageSize(m_textureView, m_rowPitch);
auto blitCommandEncoder = m_mtlr->GetBlitCommandEncoder();
blitCommandEncoder->copyFromTexture(baseTexture->GetTexture(), m_firstSlice, 0, MTL::Origin{0, 0, 0}, MTL::Size{(uint32)baseTexture->width, (uint32)baseTexture->height, 1}, m_mtlr->GetTextureReadbackBuffer(), m_bufferOffset, bytesPerRow, bytesPerImage);
blitCommandEncoder->copyFromTexture(baseTexture->GetTexture(), m_firstSlice, 0, MTL::Origin{0, 0, 0}, MTL::Size{(uint32)baseTexture->width, (uint32)baseTexture->height, 1}, m_mtlr->GetTextureReadbackBuffer(), m_bufferOffset, m_rowPitch, m_image_size);
m_commandBuffer = m_mtlr->GetCurrentCommandBuffer()->retain();
// TODO: uncomment?

View File

@@ -6,10 +6,10 @@
std::map<Latte::E_GX2SURFFMT, MetalPixelFormatInfo> MTL_COLOR_FORMAT_TABLE = {
{Latte::E_GX2SURFFMT::INVALID_FORMAT, {MTL::PixelFormatInvalid, MetalDataType::NONE, 0}},
{Latte::E_GX2SURFFMT::R4_G4_UNORM, {MTL::PixelFormatABGR4Unorm, MetalDataType::FLOAT, 2}},
{Latte::E_GX2SURFFMT::R5_G6_B5_UNORM, {MTL::PixelFormatB5G6R5Unorm, MetalDataType::FLOAT, 2}},
{Latte::E_GX2SURFFMT::R5_G5_B5_A1_UNORM, {MTL::PixelFormatBGR5A1Unorm, MetalDataType::FLOAT, 2}},
{Latte::E_GX2SURFFMT::R4_G4_B4_A4_UNORM, {MTL::PixelFormatABGR4Unorm, MetalDataType::FLOAT, 2}},
{Latte::E_GX2SURFFMT::R4_G4_UNORM, {MTL::PixelFormatABGR4Unorm, MetalDataType::FLOAT, 2, {1, 1}, false, nullptr, true}},
{Latte::E_GX2SURFFMT::R5_G6_B5_UNORM, {MTL::PixelFormatB5G6R5Unorm, MetalDataType::FLOAT, 2, {1, 1}, false, nullptr, true}},
{Latte::E_GX2SURFFMT::R5_G5_B5_A1_UNORM, {MTL::PixelFormatBGR5A1Unorm, MetalDataType::FLOAT, 2, {1, 1}, false, nullptr, true}},
{Latte::E_GX2SURFFMT::R4_G4_B4_A4_UNORM, {MTL::PixelFormatABGR4Unorm, MetalDataType::FLOAT, 2, {1, 1}, false, nullptr, true}},
{Latte::E_GX2SURFFMT::A1_B5_G5_R5_UNORM, {MTL::PixelFormatA1BGR5Unorm, MetalDataType::FLOAT, 2}},
{Latte::E_GX2SURFFMT::R8_UNORM, {MTL::PixelFormatR8Unorm, MetalDataType::FLOAT, 1}},
{Latte::E_GX2SURFFMT::R8_SNORM, {MTL::PixelFormatR8Snorm, MetalDataType::FLOAT, 1}},
@@ -25,12 +25,12 @@ std::map<Latte::E_GX2SURFFMT, MetalPixelFormatInfo> MTL_COLOR_FORMAT_TABLE = {
{Latte::E_GX2SURFFMT::R8_G8_B8_A8_SINT, {MTL::PixelFormatRGBA8Sint, MetalDataType::INT, 4}},
{Latte::E_GX2SURFFMT::R8_G8_B8_A8_SRGB, {MTL::PixelFormatRGBA8Unorm_sRGB, MetalDataType::FLOAT, 4}},
{Latte::E_GX2SURFFMT::R10_G10_B10_A2_UNORM, {MTL::PixelFormatRGB10A2Unorm, MetalDataType::FLOAT, 4}},
{Latte::E_GX2SURFFMT::R10_G10_B10_A2_SNORM, {MTL::PixelFormatRGBA16Snorm, MetalDataType::FLOAT, 8}},
{Latte::E_GX2SURFFMT::R10_G10_B10_A2_SNORM, {MTL::PixelFormatRGBA16Snorm, MetalDataType::FLOAT, 8, {1, 1}, false, nullptr, true}},
{Latte::E_GX2SURFFMT::R10_G10_B10_A2_UINT, {MTL::PixelFormatRGB10A2Uint, MetalDataType::UINT, 4}},
{Latte::E_GX2SURFFMT::R10_G10_B10_A2_SINT, {MTL::PixelFormatRGBA16Sint, MetalDataType::INT, 8}},
{Latte::E_GX2SURFFMT::R10_G10_B10_A2_SRGB, {MTL::PixelFormatRGB10A2Unorm, MetalDataType::FLOAT, 4}}, // TODO: sRGB?
{Latte::E_GX2SURFFMT::R10_G10_B10_A2_SINT, {MTL::PixelFormatRGBA16Sint, MetalDataType::INT, 8, {1, 1}, false, nullptr, true}},
{Latte::E_GX2SURFFMT::R10_G10_B10_A2_SRGB, {MTL::PixelFormatRGB10A2Unorm, MetalDataType::FLOAT, 4, {1, 1}, false, nullptr, true}}, // TODO: sRGB?
{Latte::E_GX2SURFFMT::A2_B10_G10_R10_UNORM, {MTL::PixelFormatBGR10A2Unorm, MetalDataType::FLOAT, 4}},
{Latte::E_GX2SURFFMT::A2_B10_G10_R10_UINT, {MTL::PixelFormatRGB10A2Uint, MetalDataType::UINT, 4}},
{Latte::E_GX2SURFFMT::A2_B10_G10_R10_UINT, {MTL::PixelFormatRGB10A2Uint, MetalDataType::UINT, 4, {1, 1}, false, nullptr, true}},
{Latte::E_GX2SURFFMT::R16_UNORM, {MTL::PixelFormatR16Unorm, MetalDataType::FLOAT, 2}},
{Latte::E_GX2SURFFMT::R16_SNORM, {MTL::PixelFormatR16Snorm, MetalDataType::FLOAT, 2}},
{Latte::E_GX2SURFFMT::R16_UINT, {MTL::PixelFormatR16Uint, MetalDataType::UINT, 2}},
@@ -46,11 +46,11 @@ std::map<Latte::E_GX2SURFFMT, MetalPixelFormatInfo> MTL_COLOR_FORMAT_TABLE = {
{Latte::E_GX2SURFFMT::R16_G16_B16_A16_UINT, {MTL::PixelFormatRGBA16Uint, MetalDataType::UINT, 8}},
{Latte::E_GX2SURFFMT::R16_G16_B16_A16_SINT, {MTL::PixelFormatRGBA16Sint, MetalDataType::INT, 8}},
{Latte::E_GX2SURFFMT::R16_G16_B16_A16_FLOAT, {MTL::PixelFormatRGBA16Float, MetalDataType::FLOAT, 8}},
{Latte::E_GX2SURFFMT::R24_X8_UNORM, {MTL::PixelFormatR32Float, MetalDataType::FLOAT, 4}}, // TODO: correct?
{Latte::E_GX2SURFFMT::R24_X8_FLOAT, {MTL::PixelFormatR32Float, MetalDataType::FLOAT, 4}}, // TODO: correct?
{Latte::E_GX2SURFFMT::X24_G8_UINT, {MTL::PixelFormatRGBA8Uint, MetalDataType::UINT, 4}}, // TODO: correct?
{Latte::E_GX2SURFFMT::R32_X8_FLOAT, {MTL::PixelFormatR32Float, MetalDataType::FLOAT, 4}}, // TODO: correct?
{Latte::E_GX2SURFFMT::X32_G8_UINT_X24, {MTL::PixelFormatRGBA16Uint, MetalDataType::UINT, 8}}, // TODO: correct?
{Latte::E_GX2SURFFMT::R24_X8_UNORM, {MTL::PixelFormatR32Float, MetalDataType::FLOAT, 4, {1, 1}, false, nullptr, true}}, // TODO: correct?
{Latte::E_GX2SURFFMT::R24_X8_FLOAT, {MTL::PixelFormatR32Float, MetalDataType::FLOAT, 4, {1, 1}, false, nullptr, true}}, // TODO: correct?
{Latte::E_GX2SURFFMT::X24_G8_UINT, {MTL::PixelFormatRGBA8Uint, MetalDataType::UINT, 4, {1, 1}, false, nullptr, true}}, // TODO: correct?
{Latte::E_GX2SURFFMT::R32_X8_FLOAT, {MTL::PixelFormatR32Float, MetalDataType::FLOAT, 4, {1, 1}, false, nullptr, true}}, // TODO: correct?
{Latte::E_GX2SURFFMT::X32_G8_UINT_X24, {MTL::PixelFormatRGBA16Uint, MetalDataType::UINT, 8, {1, 1}, false, nullptr, true}}, // TODO: correct?
{Latte::E_GX2SURFFMT::R11_G11_B10_FLOAT, {MTL::PixelFormatRG11B10Float, MetalDataType::FLOAT, 4}},
{Latte::E_GX2SURFFMT::R32_UINT, {MTL::PixelFormatR32Uint, MetalDataType::UINT, 4}},
{Latte::E_GX2SURFFMT::R32_SINT, {MTL::PixelFormatR32Sint, MetalDataType::INT, 4}},
@@ -77,8 +77,8 @@ std::map<Latte::E_GX2SURFFMT, MetalPixelFormatInfo> MTL_DEPTH_FORMAT_TABLE = {
{Latte::E_GX2SURFFMT::INVALID_FORMAT, {MTL::PixelFormatInvalid, MetalDataType::NONE, 0}},
{Latte::E_GX2SURFFMT::D24_S8_UNORM, {MTL::PixelFormatDepth24Unorm_Stencil8, MetalDataType::NONE, 4, {1, 1}, true}},
{Latte::E_GX2SURFFMT::D24_S8_FLOAT, {MTL::PixelFormatDepth32Float_Stencil8, MetalDataType::NONE, 4, {1, 1}, true}},
{Latte::E_GX2SURFFMT::D32_S8_FLOAT, {MTL::PixelFormatDepth32Float_Stencil8, MetalDataType::NONE, 5, {1, 1}, true}},
{Latte::E_GX2SURFFMT::D24_S8_FLOAT, {MTL::PixelFormatDepth32Float_Stencil8, MetalDataType::NONE, 4, {1, 1}, true, nullptr, true}},
{Latte::E_GX2SURFFMT::D32_S8_FLOAT, {MTL::PixelFormatDepth32Float_Stencil8, MetalDataType::NONE, 5, {1, 1}, true, nullptr, true}},
{Latte::E_GX2SURFFMT::D16_UNORM, {MTL::PixelFormatDepth16Unorm, MetalDataType::NONE, 2, {1, 1}}},
{Latte::E_GX2SURFFMT::D32_FLOAT, {MTL::PixelFormatDepth32Float, MetalDataType::NONE, 4, {1, 1}}},
};
@@ -142,24 +142,29 @@ void CheckForPixelFormatSupport(const MetalPixelFormatSupport& support)
{
// B5G6R5Unorm
MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::R5_G6_B5_UNORM].pixelFormat = MTL::PixelFormatRGBA8Unorm;
MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::R5_G6_B5_UNORM].isAlternateFormat = true;
MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::R5_G6_B5_UNORM].bytesPerBlock = 4;
MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::R5_G6_B5_UNORM].textureDecoder = TextureDecoder_R5G6B5_UNORM_To_RGBA8::getInstance();
// A1BGR5Unorm
MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::A1_B5_G5_R5_UNORM].pixelFormat = MTL::PixelFormatRGBA8Unorm;
MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::A1_B5_G5_R5_UNORM].isAlternateFormat = true;
MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::A1_B5_G5_R5_UNORM].textureDecoder = TextureDecoder_A1_B5_G5_R5_UNORM_vulkan_To_RGBA8::getInstance();
// ABGR4Unorm
MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::R4_G4_UNORM].pixelFormat = MTL::PixelFormatRG8Unorm;
MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::R4_G4_UNORM].isAlternateFormat = true;
MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::R4_G4_UNORM].bytesPerBlock = 2;
MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::R4_G4_UNORM].textureDecoder = TextureDecoder_R4G4_UNORM_To_RG8::getInstance();
MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::R4_G4_B4_A4_UNORM].pixelFormat = MTL::PixelFormatRGBA8Unorm;
MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::R4_G4_B4_A4_UNORM].isAlternateFormat = true;
MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::R4_G4_B4_A4_UNORM].bytesPerBlock = 4;
MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::R4_G4_B4_A4_UNORM].textureDecoder = TextureDecoder_R4G4B4A4_UNORM_To_RGBA8::getInstance();
// BGR5A1Unorm
MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::R5_G5_B5_A1_UNORM].pixelFormat = MTL::PixelFormatRGBA8Unorm;
MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::R5_G5_B5_A1_UNORM].isAlternateFormat = true;
MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::R5_G5_B5_A1_UNORM].textureDecoder = TextureDecoder_R5_G5_B5_A1_UNORM_swappedRB_To_RGBA8::getInstance();
}
@@ -174,6 +179,7 @@ void CheckForPixelFormatSupport(const MetalPixelFormatSupport& support)
{
// Depth24Unorm_Stencil8
MTL_DEPTH_FORMAT_TABLE[Latte::E_GX2SURFFMT::D24_S8_UNORM].pixelFormat = MTL::PixelFormatDepth32Float_Stencil8;
MTL_DEPTH_FORMAT_TABLE[Latte::E_GX2SURFFMT::D24_S8_UNORM].isAlternateFormat = true;
// TODO: implement the decoder
//MTL_DEPTH_FORMAT_TABLE[Latte::E_GX2SURFFMT::D24_S8_UNORM].textureDecoder = TextureDecoder_D24_S8_To_D32_S8::getInstance();
}
@@ -185,7 +191,7 @@ const MetalPixelFormatInfo GetMtlPixelFormatInfo(Latte::E_GX2SURFFMT format, boo
{
auto it = MTL_DEPTH_FORMAT_TABLE.find(format);
if (it == MTL_DEPTH_FORMAT_TABLE.end())
return {MTL::PixelFormatDepth16Unorm, MetalDataType::NONE, 2}; // Fallback
return {MTL::PixelFormatDepth16Unorm, MetalDataType::NONE, 2, {1, 1}, false, nullptr, true}; // Fallback
else
return it->second;
}
@@ -193,7 +199,7 @@ const MetalPixelFormatInfo GetMtlPixelFormatInfo(Latte::E_GX2SURFFMT format, boo
{
auto it = MTL_COLOR_FORMAT_TABLE.find(format);
if (it == MTL_COLOR_FORMAT_TABLE.end())
return {MTL::PixelFormatR8Unorm, MetalDataType::FLOAT, 1}; // Fallback
return {MTL::PixelFormatR8Unorm, MetalDataType::FLOAT, 1, {1, 1}, false, nullptr, true}; // Fallback
else
return it->second;
}

View File

@@ -29,6 +29,7 @@ struct MetalPixelFormatInfo {
Uvec2 blockTexelSize = {1, 1};
bool hasStencil = false;
TextureDecoder* textureDecoder = nullptr;
bool isAlternateFormat = false; // if true then pixel format does not match Latte 1:1
};
void CheckForPixelFormatSupport(const MetalPixelFormatSupport& support);

View File

@@ -965,7 +965,13 @@ void MetalRenderer::texture_copyImageSubData(LatteTexture* src, sint32 srcMip, s
LatteTextureReadbackInfo* MetalRenderer::texture_createReadback(LatteTextureView* textureView)
{
size_t uploadSize = static_cast<LatteTextureMtl*>(textureView->baseTexture)->GetTexture()->allocatedSize();
auto* baseTexture = static_cast<LatteTextureMtl*>(textureView->baseTexture);
if (baseTexture->m_isAlternateFormat)
{
cemuLog_logDebug(LogType::Force, "Metal does not support readback of texture format 0x{:x}", (uint32)baseTexture->format);
return nullptr;
}
size_t uploadSize = baseTexture->GetTexture()->allocatedSize();
if ((m_readbackBufferWriteOffset + uploadSize) > TEXTURE_READBACK_SIZE)
{

View File

@@ -387,6 +387,14 @@ void LatteTextureGL::GetOpenGLFormatInfo(bool isDepth, Latte::E_GX2SURFFMT forma
glSuppliedFormatType = GL_UNSIGNED_INT;
glIsCompressed = false;
}
else if (format == Latte::E_GX2SURFFMT::R32_G32_B32_A32_SINT)
{
glInternalFormat = GL_RGBA32I;
// supplied format
glSuppliedFormat = GL_RGBA_INTEGER;
glSuppliedFormatType = GL_INT;
glIsCompressed = false;
}
else if (format == Latte::E_GX2SURFFMT::R16_G16_B16_A16_UINT)
{
glInternalFormat = GL_RGBA16UI;

View File

@@ -546,7 +546,9 @@ void OpenGLRenderer::HandleScreenshotRequest(LatteTextureView* texView, bool pad
const sint32 pixelDataSize = screenshotWidth * screenshotHeight * 3;
std::vector<uint8> rgb_data(pixelDataSize);
glPixelStorei(GL_PACK_ALIGNMENT, 1);
glGetTexImage(GL_TEXTURE_2D, 0, GL_RGB, GL_UNSIGNED_BYTE, rgb_data.data());
glPixelStorei(GL_PACK_ALIGNMENT, 4);
texture_bindAndActivate(nullptr, 0);
const bool srcUsesSRGB = HAS_FLAG(texView->format, Latte::E_GX2SURFFMT::FMT_BIT_SRGB);
@@ -983,6 +985,8 @@ TextureDecoder* OpenGLRenderer::texture_chooseDecodedFormat(Latte::E_GX2SURFFMT
texDecoder = TextureDecoder_R11_G11_B10_FLOAT::getInstance();
else if (format == Latte::E_GX2SURFFMT::R32_G32_B32_A32_UINT)
texDecoder = TextureDecoder_R32_G32_B32_A32_UINT::getInstance();
else if (format == Latte::E_GX2SURFFMT::R32_G32_B32_A32_SINT)
texDecoder = TextureDecoder_R32_G32_B32_A32_UINT::getInstance(); // reuse the UINT decoder
else if (format == Latte::E_GX2SURFFMT::R16_G16_B16_A16_UINT)
texDecoder = TextureDecoder_R16_G16_B16_A16_UINT::getInstance();
else if (format == Latte::E_GX2SURFFMT::R8_G8_B8_A8_UINT)

View File

@@ -5,37 +5,41 @@ LatteTextureReadbackInfoGL::LatteTextureReadbackInfoGL(LatteTextureView* texture
: LatteTextureReadbackInfo(textureView, textureView->firstMip)
{
LatteTexture* baseTexture = textureView->baseTexture;
uint32 width = baseTexture->GetMipWidth(m_firstMip);
// handle format
if (textureView->format == Latte::E_GX2SURFFMT::R8_G8_B8_A8_UNORM)
{
m_rowPitch = width * 4;
m_texFormatGL = GL_RGBA;
m_texDataTypeGL = GL_UNSIGNED_BYTE;
}
else if (textureView->format == Latte::E_GX2SURFFMT::R8_G8_B8_A8_SRGB)
{
m_rowPitch = width * 4;
m_texFormatGL = GL_RGBA;
m_texDataTypeGL = GL_UNSIGNED_BYTE;
}
else if (textureView->format == Latte::E_GX2SURFFMT::R32_G32_B32_A32_FLOAT)
{
m_rowPitch = width * 16;
m_texFormatGL = GL_RGBA;
m_texDataTypeGL = GL_FLOAT;
}
else if (textureView->format == Latte::E_GX2SURFFMT::R32_G32_B32_A32_UINT)
{
m_texFormatGL = GL_RGBA_INTEGER;
m_texDataTypeGL = GL_UNSIGNED_INT;
}
else if (textureView->format == Latte::E_GX2SURFFMT::R32_G32_B32_A32_SINT)
{
m_texFormatGL = GL_RGBA_INTEGER;
m_texDataTypeGL = GL_INT;
}
else if (textureView->format == Latte::E_GX2SURFFMT::R32_FLOAT)
{
if (baseTexture->isDepth)
{
m_rowPitch = width * 4;
m_texFormatGL = GL_DEPTH_COMPONENT;
m_texDataTypeGL = GL_FLOAT;
}
else
{
m_rowPitch = width * 4;
m_texFormatGL = GL_RED;
m_texDataTypeGL = GL_FLOAT;
}
@@ -44,33 +48,28 @@ LatteTextureReadbackInfoGL::LatteTextureReadbackInfoGL(LatteTextureView* texture
{
if (baseTexture->isDepth)
{
m_rowPitch = width * 2;
m_texFormatGL = GL_DEPTH_COMPONENT;
m_texDataTypeGL = GL_UNSIGNED_SHORT;
cemu_assert_unimplemented();
}
else
{
m_rowPitch = width * 2;
m_texFormatGL = GL_RED;
m_texDataTypeGL = GL_UNSIGNED_SHORT;
}
}
else if (textureView->format == Latte::E_GX2SURFFMT::R16_G16_B16_A16_FLOAT)
{
m_rowPitch = width * 8;
m_texFormatGL = GL_RGBA;
m_texDataTypeGL = GL_HALF_FLOAT;
}
else if (textureView->format == Latte::E_GX2SURFFMT::R8_G8_UNORM)
{
m_rowPitch = width * 2;
m_texFormatGL = GL_RG;
m_texDataTypeGL = GL_UNSIGNED_BYTE;
}
else if (textureView->format == Latte::E_GX2SURFFMT::R16_G16_B16_A16_UNORM)
{
m_rowPitch = width * 8;
m_texFormatGL = GL_RGBA;
m_texDataTypeGL = GL_UNSIGNED_SHORT;
}
@@ -80,8 +79,8 @@ LatteTextureReadbackInfoGL::LatteTextureReadbackInfoGL(LatteTextureView* texture
return;
}
// OpenGL uses the default 4 byte pack alignment because it gives us slightly better row copy performance
m_rowPitch = (m_rowPitch + 3) & ~3u;
m_image_size = m_rowPitch * baseTexture->GetMipHeight(m_firstMip);
m_rowPitch = GetReadbackRowPitch(textureView, 4);
m_image_size = GetReadbackImageSize(textureView, m_rowPitch);
}
LatteTextureReadbackInfoGL::~LatteTextureReadbackInfoGL()

View File

@@ -49,6 +49,7 @@ LatteTextureVk::LatteTextureVk(class VulkanRenderer* vkRenderer, Latte::E_DIM di
cemu_assert_debug(hasStencil == ((texFormatInfo.vkImageAspect & VK_IMAGE_ASPECT_STENCIL_BIT) != 0));
imageInfo.format = texFormatInfo.vkImageFormat;
vkObjTex->m_imageAspect = texFormatInfo.vkImageAspect;
m_isAlternateFormat = texFormatInfo.isAlternateFormat;
if (isDepth == false && texFormatInfo.isCompressed)
{

View File

@@ -87,8 +87,9 @@ public:
uint32 m_selfDependencyCheckIndex{}; // used to track if texture is being both sampled and output to during drawcall
VkImageAspectFlags m_selfDependencyCheckAspectMask{};
bool m_isAlternateFormat{}; // true if host pixel format does not 1:1 match the emulated guest texture pixel format
private:
class VulkanRenderer* m_vkr;
class VulkanRenderer* m_vkr{};
VKRObjectTexture* vkObjTex{};
VkImageLayout m_defaultLayout{ VK_IMAGE_LAYOUT_GENERAL }; // the targetted long term layout of the texture. Can be either VK_IMAGE_LAYOUT_GENERAL or VK_IMAGE_LAYOUT_ATTACHMENT_FEEDBACK_LOOP_OPTIMAL_EXT for potential rendertargets if supported

View File

@@ -3,10 +3,10 @@
#include "Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.h"
LatteTextureReadbackInfoVk::LatteTextureReadbackInfoVk(VkDevice device, LatteTextureView* textureView)
: LatteTextureReadbackInfo(textureView), m_device(device)
: LatteTextureReadbackInfo(textureView, textureView->firstMip), m_device(device)
{
m_image_size = GetImageSize(textureView);
m_rowPitch = m_image_size / textureView->baseTexture->height;
m_rowPitch = m_image_size / textureView->baseTexture->GetMipHeight(m_firstMip);
}
LatteTextureReadbackInfoVk::~LatteTextureReadbackInfoVk()
@@ -15,84 +15,13 @@ 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)
auto* baseTexture = (LatteTextureVk*)textureView->baseTexture;
if (baseTexture->m_isAlternateFormat || baseTexture->IsCompressedFormat())
{
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);
cemuLog_logDebug(LogType::Force, "Vulkan does not support readback of texture format 0x{:x}", (uint32)baseTexture->format);
return 0;
}
return LatteTextureReadbackInfo::GetReadbackImageSize(textureView, GetReadbackRowPitch(textureView));
}
@@ -103,21 +32,20 @@ void LatteTextureReadbackInfoVk::StartTransfer()
auto* baseTexture = (LatteTextureVk*)m_textureView->baseTexture;
baseTexture->GetImageObj()->flagForCurrentCommandBuffer();
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.bufferRowLength = baseTexture->GetMipWidth(m_firstMip);
region.bufferImageHeight = baseTexture->GetMipHeight(m_firstMip);
region.imageSubresource.aspectMask = baseTexture->GetImageAspect();
region.imageSubresource.baseArrayLayer = m_firstSlice;
region.imageSubresource.layerCount = 1;
region.imageSubresource.mipLevel = 0;
region.imageSubresource.mipLevel = m_firstMip;
region.imageOffset = {0,0,0};
region.imageExtent = {(uint32)baseTexture->width,(uint32)baseTexture->height,1};
region.imageExtent = {region.bufferRowLength, region.bufferImageHeight, 1};
const auto renderer = VulkanRenderer::GetInstance();
renderer->draw_endRenderPass();

View File

@@ -2458,6 +2458,7 @@ void VulkanRenderer::GetTextureFormatInfoVK(Latte::E_GX2SURFFMT format, bool isD
formatInfoOut->texelCountX = width;
formatInfoOut->texelCountY = height;
formatInfoOut->isCompressed = false;
formatInfoOut->isAlternateFormat = false;
if (isDepth)
{
switch (format)
@@ -2468,6 +2469,7 @@ void VulkanRenderer::GetTextureFormatInfoVK(Latte::E_GX2SURFFMT format, bool isD
formatInfoOut->vkImageFormat = VK_FORMAT_D32_SFLOAT_S8_UINT;
formatInfoOut->vkImageAspect = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
formatInfoOut->decoder = TextureDecoder_NullData64::getInstance();
formatInfoOut->isAlternateFormat = true;
}
else
{
@@ -2481,6 +2483,7 @@ void VulkanRenderer::GetTextureFormatInfoVK(Latte::E_GX2SURFFMT format, bool isD
formatInfoOut->vkImageFormat = VK_FORMAT_D32_SFLOAT_S8_UINT;
formatInfoOut->vkImageAspect = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
formatInfoOut->decoder = TextureDecoder_NullData64::getInstance();
formatInfoOut->isAlternateFormat = true;
break;
case Latte::E_GX2SURFFMT::D32_FLOAT:
formatInfoOut->vkImageFormat = VK_FORMAT_D32_SFLOAT;
@@ -2503,6 +2506,7 @@ void VulkanRenderer::GetTextureFormatInfoVK(Latte::E_GX2SURFFMT format, bool isD
formatInfoOut->vkImageFormat = VK_FORMAT_D16_UNORM;
formatInfoOut->vkImageAspect = VK_IMAGE_ASPECT_DEPTH_BIT;
formatInfoOut->decoder = nullptr;
formatInfoOut->isAlternateFormat = true;
break;
}
}
@@ -2586,19 +2590,24 @@ void VulkanRenderer::GetTextureFormatInfoVK(Latte::E_GX2SURFFMT format, bool isD
case Latte::E_GX2SURFFMT::R4_G4_UNORM:
if (m_supportedFormatInfo.fmt_r4g4_unorm_pack == false)
{
if (m_supportedFormatInfo.fmt_r4g4b4a4_unorm_pack == false) {
if (m_supportedFormatInfo.fmt_r4g4b4a4_unorm_pack == false)
{
formatInfoOut->vkImageFormat = VK_FORMAT_R8G8B8A8_UNORM;
formatInfoOut->decoder = TextureDecoder_R4G4_UNORM_To_RGBA8::getInstance();
formatInfoOut->isAlternateFormat = true;
}
else {
else
{
formatInfoOut->vkImageFormat = VK_FORMAT_R4G4B4A4_UNORM_PACK16;
formatInfoOut->decoder = TextureDecoder_R4_G4_UNORM_To_ABGR4::getInstance();
formatInfoOut->isAlternateFormat = true;
}
}
else
{
formatInfoOut->vkImageFormat = VK_FORMAT_R4G4_UNORM_PACK8;
formatInfoOut->decoder = TextureDecoder_R4_G4::getInstance();
formatInfoOut->isAlternateFormat = true; // R and G swapped?
}
break;
// R formats
@@ -2640,37 +2649,49 @@ void VulkanRenderer::GetTextureFormatInfoVK(Latte::E_GX2SURFFMT format, bool isD
break;
// special formats
case Latte::E_GX2SURFFMT::R5_G6_B5_UNORM:
if (m_supportedFormatInfo.fmt_r5g6b5_unorm_pack == false) {
if (m_supportedFormatInfo.fmt_r5g6b5_unorm_pack == false)
{
formatInfoOut->vkImageFormat = VK_FORMAT_R8G8B8A8_UNORM;
formatInfoOut->decoder = TextureDecoder_R5G6B5_UNORM_To_RGBA8::getInstance();
formatInfoOut->isAlternateFormat = true;
}
else {
else
{
// Vulkan has R in MSB, GPU7 has it in LSB
formatInfoOut->vkImageFormat = VK_FORMAT_R5G6B5_UNORM_PACK16;
formatInfoOut->decoder = TextureDecoder_R5_G6_B5_swappedRB::getInstance();
formatInfoOut->isAlternateFormat = true;
}
break;
case Latte::E_GX2SURFFMT::R5_G5_B5_A1_UNORM:
if (m_supportedFormatInfo.fmt_a1r5g5b5_unorm_pack == false) {
if (m_supportedFormatInfo.fmt_a1r5g5b5_unorm_pack == false)
{
formatInfoOut->vkImageFormat = VK_FORMAT_R8G8B8A8_UNORM;
formatInfoOut->decoder = TextureDecoder_R5_G5_B5_A1_UNORM_swappedRB_To_RGBA8::getInstance();
formatInfoOut->isAlternateFormat = true;
}
else {
else
{
// used in Super Mario 3D World for the hidden Luigi sprites
// since order of channels is reversed in Vulkan compared to GX2 the format we need is A1B5G5R5
formatInfoOut->vkImageFormat = VK_FORMAT_A1R5G5B5_UNORM_PACK16;
formatInfoOut->decoder = TextureDecoder_R5_G5_B5_A1_UNORM_swappedRB::getInstance();
formatInfoOut->isAlternateFormat = true;
}
break;
case Latte::E_GX2SURFFMT::A1_B5_G5_R5_UNORM:
if (m_supportedFormatInfo.fmt_a1r5g5b5_unorm_pack == false) {
if (m_supportedFormatInfo.fmt_a1r5g5b5_unorm_pack == false)
{
formatInfoOut->vkImageFormat = VK_FORMAT_R8G8B8A8_UNORM;
formatInfoOut->decoder = TextureDecoder_A1_B5_G5_R5_UNORM_vulkan_To_RGBA8::getInstance();
formatInfoOut->isAlternateFormat = true;
}
else {
else
{
// used by VC64 (e.g. Ocarina of Time)
formatInfoOut->vkImageFormat = VK_FORMAT_A1R5G5B5_UNORM_PACK16; // A 15 R 10..14, G 5..9 B 0..4
formatInfoOut->decoder = TextureDecoder_A1_B5_G5_R5_UNORM_vulkan::getInstance();
formatInfoOut->isAlternateFormat = true;
}
break;
case Latte::E_GX2SURFFMT::R11_G11_B10_FLOAT:
@@ -2678,13 +2699,17 @@ void VulkanRenderer::GetTextureFormatInfoVK(Latte::E_GX2SURFFMT format, bool isD
formatInfoOut->decoder = TextureDecoder_R11_G11_B10_FLOAT::getInstance();
break;
case Latte::E_GX2SURFFMT::R4_G4_B4_A4_UNORM:
if (m_supportedFormatInfo.fmt_r4g4b4a4_unorm_pack == false) {
if (m_supportedFormatInfo.fmt_r4g4b4a4_unorm_pack == false)
{
formatInfoOut->vkImageFormat = VK_FORMAT_R8G8B8A8_UNORM;
formatInfoOut->decoder = TextureDecoder_R4G4B4A4_UNORM_To_RGBA8::getInstance();
formatInfoOut->isAlternateFormat = true;
}
else {
else
{
formatInfoOut->vkImageFormat = VK_FORMAT_R4G4B4A4_UNORM_PACK16;
formatInfoOut->decoder = TextureDecoder_R4_G4_B4_A4_UNORM::getInstance();
formatInfoOut->isAlternateFormat = true; // channel order is different?
}
break;
// special formats - R10G10B10_A2
@@ -2695,6 +2720,7 @@ void VulkanRenderer::GetTextureFormatInfoVK(Latte::E_GX2SURFFMT format, bool isD
case Latte::E_GX2SURFFMT::R10_G10_B10_A2_SNORM:
formatInfoOut->vkImageFormat = VK_FORMAT_R16G16B16A16_SNORM; // Vulkan has VK_FORMAT_A2R10G10B10_SNORM_PACK32 but it doesnt work?
formatInfoOut->decoder = TextureDecoder_R10_G10_B10_A2_SNORM_To_RGBA16::getInstance();
formatInfoOut->isAlternateFormat = true;
break;
case Latte::E_GX2SURFFMT::R10_G10_B10_A2_SRGB:
//formatInfoOut->vkImageFormat = VK_FORMAT_R16G16B16A16_SNORM; // Vulkan has no uncompressed SRGB format with more than 8 bits per channel
@@ -2702,6 +2728,7 @@ void VulkanRenderer::GetTextureFormatInfoVK(Latte::E_GX2SURFFMT format, bool isD
//break;
formatInfoOut->vkImageFormat = VK_FORMAT_A2B10G10R10_UNORM_PACK32; // todo - verify
formatInfoOut->decoder = TextureDecoder_R10_G10_B10_A2_UNORM::getInstance();
formatInfoOut->isAlternateFormat = true;
break;
// compressed formats
case Latte::E_GX2SURFFMT::BC1_SRGB:
@@ -2747,15 +2774,19 @@ void VulkanRenderer::GetTextureFormatInfoVK(Latte::E_GX2SURFFMT format, bool isD
case Latte::E_GX2SURFFMT::R24_X8_UNORM:
formatInfoOut->vkImageFormat = VK_FORMAT_R32_SFLOAT;
formatInfoOut->decoder = TextureDecoder_R24_X8::getInstance();
formatInfoOut->isAlternateFormat = true;
break;
case Latte::E_GX2SURFFMT::X24_G8_UINT:
// used by Color Splash and Resident Evil
formatInfoOut->vkImageFormat = VK_FORMAT_R8G8B8A8_UINT; // todo - should we use ABGR format?
formatInfoOut->decoder = TextureDecoder_X24_G8_UINT::getInstance(); // todo - verify
formatInfoOut->isAlternateFormat = true;
break;
case Latte::E_GX2SURFFMT::R32_X8_FLOAT:
// seen in Disney Infinity 3.0
formatInfoOut->vkImageFormat = VK_FORMAT_R32_SFLOAT;
formatInfoOut->decoder = TextureDecoder_NullData64::getInstance();
formatInfoOut->isAlternateFormat = true;
break;
default:
cemuLog_log(LogType::Force, "Unsupported color texture format {:04x}", (uint32)format);

View File

@@ -165,6 +165,7 @@ public:
VkFormat vkImageFormat;
VkImageAspectFlags vkImageAspect;
bool isCompressed;
bool isAlternateFormat; // true if the host pixel format doesn't 1:1 match the emulated format
// texture decoder info
TextureDecoder* decoder;