Cemu/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureViewVk.cpp
Exzap bc04662525 Latte+GL+VK: Improve handling of gfx pack texture overwrite format
Graphic packs can overwrite the format of a texture (e.g. for higher bitdepth to lessen banding) but the code for this wasn't correctly working anymore.

- Fixes overwrite format being ignored for texture views on Vulkan backend
- Fixes overwrite format not being used for texture views on OpenGL

Format aliasing is complicated enough as it is, even without overwrites, so this adds a new rule to make behavior more well defined: If two textures share memory but only one uses an overwrite format, then they are no longer synchronized and are considered separate textures.

Bonus fixes for OpenGL:
- Use fbo 0 instead of -1 as the default. This silences some warnings in debug output
- On OpenGL, bind new framebuffers on handle generation so they are considered created
2024-03-13 02:41:42 +01:00

245 lines
7.8 KiB
C++

#include "Cafe/HW/Latte/Renderer/Vulkan/LatteTextureViewVk.h"
#include "Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.h"
#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h"
#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h"
#include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h"
uint32 LatteTextureVk_AdjustTextureCompSel(Latte::E_GX2SURFFMT format, uint32 compSel)
{
switch (format)
{
case Latte::E_GX2SURFFMT::R8_UNORM: // R8 is replicated on all channels (while OpenGL would return 1.0 for BGA instead)
case Latte::E_GX2SURFFMT::R8_SNORM: // probably the same as _UNORM, but needs testing
if (compSel >= 1 && compSel <= 3)
compSel = 0;
break;
case Latte::E_GX2SURFFMT::A1_B5_G5_R5_UNORM: // order of components is reversed (RGBA -> ABGR)
if (compSel >= 0 && compSel <= 3)
compSel = 3 - compSel;
break;
case Latte::E_GX2SURFFMT::BC4_UNORM:
case Latte::E_GX2SURFFMT::BC4_SNORM:
if (compSel >= 1 && compSel <= 3)
compSel = 0;
break;
case Latte::E_GX2SURFFMT::BC5_UNORM:
case Latte::E_GX2SURFFMT::BC5_SNORM:
// RG maps to RG
// B maps to ?
// A maps to G (guessed)
if (compSel == 3)
compSel = 1; // read Alpha as Green
break;
case Latte::E_GX2SURFFMT::A2_B10_G10_R10_UNORM:
// reverse components (Wii U: ABGR, OpenGL: RGBA)
// used in Resident Evil Revelations
if (compSel >= 0 && compSel <= 3)
compSel = 3 - compSel;
break;
case Latte::E_GX2SURFFMT::X24_G8_UINT:
// map everything to alpha?
if (compSel >= 0 && compSel <= 3)
compSel = 3;
break;
case Latte::E_GX2SURFFMT::R4_G4_UNORM:
// red and green swapped
if (compSel == 0)
compSel = 1;
else if (compSel == 1)
compSel = 0;
break;
default:
break;
}
return compSel;
}
LatteTextureViewVk::LatteTextureViewVk(VkDevice device, LatteTextureVk* texture, Latte::E_DIM dim, Latte::E_GX2SURFFMT format, sint32 firstMip, sint32 mipCount, sint32 firstSlice, sint32 sliceCount)
: LatteTextureView(texture, firstMip, mipCount, firstSlice, sliceCount, dim, format), m_device(device)
{
if(texture->overwriteInfo.hasFormatOverwrite)
{
cemu_assert_debug(format == texture->format); // if format overwrite is used, the texture is no longer taking part in aliasing and the format of any view has to match
m_format = texture->GetFormat();
}
else if (dim != texture->dim || format != texture->format)
{
VulkanRenderer::FormatInfoVK texFormatInfo;
VulkanRenderer::GetInstance()->GetTextureFormatInfoVK(format, texture->isDepth, dim, 0, 0, &texFormatInfo);
m_format = texFormatInfo.vkImageFormat;
}
else
m_format = texture->GetFormat();
m_uniqueId = VulkanRenderer::GetInstance()->GenUniqueId();
}
LatteTextureViewVk::~LatteTextureViewVk()
{
while (!list_descriptorSets.empty())
delete list_descriptorSets[0];
if (m_smallCacheView0)
VulkanRenderer::GetInstance()->releaseDestructibleObject(m_smallCacheView0);
if (m_smallCacheView1)
VulkanRenderer::GetInstance()->releaseDestructibleObject(m_smallCacheView1);
if (m_fallbackCache)
{
for (auto& itr : *m_fallbackCache)
VulkanRenderer::GetInstance()->releaseDestructibleObject(itr.second);
delete m_fallbackCache;
m_fallbackCache = nullptr;
}
}
VKRObjectTextureView* LatteTextureViewVk::CreateView(uint32 gpuSamplerSwizzle)
{
uint32 compSelR = (gpuSamplerSwizzle >> 16) & 0x7;
uint32 compSelG = (gpuSamplerSwizzle >> 19) & 0x7;
uint32 compSelB = (gpuSamplerSwizzle >> 22) & 0x7;
uint32 compSelA = (gpuSamplerSwizzle >> 25) & 0x7;
compSelR = LatteTextureVk_AdjustTextureCompSel(format, compSelR);
compSelG = LatteTextureVk_AdjustTextureCompSel(format, compSelG);
compSelB = LatteTextureVk_AdjustTextureCompSel(format, compSelB);
compSelA = LatteTextureVk_AdjustTextureCompSel(format, compSelA);
VkImageViewCreateInfo viewInfo{};
viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
viewInfo.image = GetBaseImage()->GetImageObj()->m_image;
viewInfo.viewType = GetImageViewTypeFromGX2Dim(dim);
viewInfo.format = m_format;
viewInfo.subresourceRange.aspectMask = GetBaseImage()->GetImageAspect();
if (viewInfo.subresourceRange.aspectMask & VK_IMAGE_ASPECT_DEPTH_BIT)
viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; // make sure stencil is never set, we only support sampling depth for now
viewInfo.subresourceRange.baseMipLevel = firstMip;
viewInfo.subresourceRange.levelCount = this->numMip;
if (viewInfo.viewType == VK_IMAGE_VIEW_TYPE_3D && baseTexture->Is3DTexture())
{
cemu_assert_debug(firstMip == 0);
cemu_assert_debug(this->numSlice == baseTexture->depth);
viewInfo.subresourceRange.baseArrayLayer = 0;
viewInfo.subresourceRange.layerCount = 1;
}
else
{
viewInfo.subresourceRange.baseArrayLayer = firstSlice;
viewInfo.subresourceRange.layerCount = this->numSlice;
}
static const VkComponentSwizzle swizzle[] =
{
VK_COMPONENT_SWIZZLE_R,
VK_COMPONENT_SWIZZLE_G,
VK_COMPONENT_SWIZZLE_B,
VK_COMPONENT_SWIZZLE_A,
VK_COMPONENT_SWIZZLE_ZERO,
VK_COMPONENT_SWIZZLE_ONE,
VK_COMPONENT_SWIZZLE_ZERO,
VK_COMPONENT_SWIZZLE_ZERO
};
viewInfo.components.r = swizzle[compSelR];
viewInfo.components.g = swizzle[compSelG];
viewInfo.components.b = swizzle[compSelB];
viewInfo.components.a = swizzle[compSelA];
VkImageView view;
if (vkCreateImageView(m_device, &viewInfo, nullptr, &view) != VK_SUCCESS)
throw std::runtime_error("failed to create texture image view!");
return new VKRObjectTextureView(GetBaseImage()->GetImageObj(), view);
}
VKRObjectTextureView* LatteTextureViewVk::GetViewRGBA()
{
return GetSamplerView(0x06880000); // RGBA swizzle
}
VKRObjectTextureView* LatteTextureViewVk::GetSamplerView(uint32 gpuSamplerSwizzle)
{
gpuSamplerSwizzle &= 0x0FFF0000;
// look up view in cache
// intentionally using unrolled code here instead of a loop for a small performance gain
if (m_smallCacheSwizzle0 == gpuSamplerSwizzle)
return m_smallCacheView0;
if (m_smallCacheSwizzle1 == gpuSamplerSwizzle)
return m_smallCacheView1;
auto fallbackCache = m_fallbackCache;
if (m_fallbackCache)
{
const auto it = fallbackCache->find(gpuSamplerSwizzle);
if (it != fallbackCache->cend())
return it->second;
}
// not cached, create new view and store in cache
auto viewObj = CreateView(gpuSamplerSwizzle);
if (m_smallCacheSwizzle0 == CACHE_EMPTY_ENTRY)
{
m_smallCacheSwizzle0 = gpuSamplerSwizzle;
m_smallCacheView0 = viewObj;
}
else if (m_smallCacheSwizzle1 == CACHE_EMPTY_ENTRY)
{
m_smallCacheSwizzle1 = gpuSamplerSwizzle;
m_smallCacheView1 = viewObj;
}
else
{
if (!m_fallbackCache)
m_fallbackCache = new std::unordered_map<uint32, VKRObjectTextureView*>();
m_fallbackCache->insert_or_assign(gpuSamplerSwizzle, viewObj);
}
return viewObj;
}
VkSampler LatteTextureViewVk::GetDefaultTextureSampler(bool useLinearTexFilter)
{
VkSampler& sampler = GetViewRGBA()->m_textureDefaultSampler[useLinearTexFilter ? 1 : 0];
if (sampler != VK_NULL_HANDLE)
return sampler;
VkSamplerCreateInfo samplerInfo{};
samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
if (useLinearTexFilter)
{
samplerInfo.magFilter = VK_FILTER_LINEAR;
samplerInfo.minFilter = VK_FILTER_LINEAR;
}
else
{
samplerInfo.magFilter = VK_FILTER_NEAREST;
samplerInfo.minFilter = VK_FILTER_NEAREST;
}
if (vkCreateSampler(m_device, &samplerInfo, nullptr, &sampler) != VK_SUCCESS)
{
cemuLog_log(LogType::Force, "Failed to create default sampler");
throw std::runtime_error("failed to create texture sampler!");
}
return sampler;
}
VkImageViewType LatteTextureViewVk::GetImageViewTypeFromGX2Dim(Latte::E_DIM dim)
{
switch (dim)
{
case Latte::E_DIM::DIM_1D:
return VK_IMAGE_VIEW_TYPE_1D;
case Latte::E_DIM::DIM_2D:
case Latte::E_DIM::DIM_2D_MSAA:
return VK_IMAGE_VIEW_TYPE_2D;
case Latte::E_DIM::DIM_2D_ARRAY:
return VK_IMAGE_VIEW_TYPE_2D_ARRAY;
case Latte::E_DIM::DIM_3D:
return VK_IMAGE_VIEW_TYPE_3D;
case Latte::E_DIM::DIM_CUBEMAP:
return VK_IMAGE_VIEW_TYPE_CUBE_ARRAY;
default:
cemu_assert_unimplemented();
}
return VK_IMAGE_VIEW_TYPE_2D;
}