Cemu/src/Cafe/HW/Latte/Core/LatteTexture.cpp
SamoZ256 26e40a4bce
Some checks failed
Build check / build (push) Has been cancelled
Generate translation template / generate-pot (push) Has been cancelled
Add Metal backend (#1287)
2025-12-06 17:14:25 +01:00

1378 lines
57 KiB
C++

#include "Cafe/HW/Latte/Core/Latte.h"
#include "Cafe/HW/Latte/Core/LatteShader.h"
#include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h"
#include "Cafe/HW/Latte/Core/LatteTexture.h"
#include "Cafe/HW/Latte/Renderer/Renderer.h"
#include "Cafe/HW/Latte/LatteAddrLib/LatteAddrLib.h"
#include "Cafe/GraphicPack/GraphicPack2.h"
#include <boost/container/small_vector.hpp>
struct TexMemOccupancyEntry
{
uint32 addrStart;
uint32 addrEnd;
LatteTextureSliceMipInfo* sliceMipInfo;
};
#define TEX_OCCUPANCY_BUCKET_COUNT (0x800) // each bucket covers a range of 2MB
#define TEX_OCCUPANCY_BUCKET_SIZE (0x100000000/TEX_OCCUPANCY_BUCKET_COUNT)
#define loopItrMemOccupancyBuckets(__startAddr, __endAddr) for(sint32 startBucketIndex = ((__startAddr)/TEX_OCCUPANCY_BUCKET_SIZE), bucketIndex=startBucketIndex; bucketIndex<=((__endAddr-1)/TEX_OCCUPANCY_BUCKET_SIZE); bucketIndex++)
std::vector<TexMemOccupancyEntry> list_texMemOccupancyBucket[TEX_OCCUPANCY_BUCKET_COUNT];
std::atomic_bool s_refreshTextureQueryList;
std::vector<LatteTextureInformation> s_cacheInfoList;
std::vector<LatteTextureInformation> LatteTexture_QueryCacheInfo()
{
// raise request flag to refresh cache
s_refreshTextureQueryList.store(true);
// wait until cleared or until timeout occurred
auto begin = std::chrono::high_resolution_clock::now();
while (true)
{
if (!s_refreshTextureQueryList)
break;
auto dur = std::chrono::high_resolution_clock::now() - begin;
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(dur).count();
if (ms >= 1000) // dont stall more than one second
return std::vector<LatteTextureInformation>();
}
return s_cacheInfoList;
}
void LatteTexture_RefreshInfoCache()
{
if (!s_refreshTextureQueryList)
return;
std::vector<LatteTextureInformation> infoCache;
std::set<LatteTexture*> visitedTextures;
std::unordered_set<LatteTextureView*> allViews = LatteTextureViewLookupCache::GetAllViews();
for (auto& it : allViews)
{
LatteTexture* baseTexture = it->baseTexture;
if(visitedTextures.find(baseTexture) != visitedTextures.end())
continue;
visitedTextures.emplace(baseTexture);
// add cache info
auto& entry = infoCache.emplace_back();
entry.physAddress = baseTexture->physAddress;
entry.physMipAddress = baseTexture->physMipAddress;
entry.width = baseTexture->width;
entry.height = baseTexture->height;
entry.depth = baseTexture->depth;
entry.pitch = baseTexture->pitch;
entry.mipLevels = baseTexture->mipLevels;
entry.format = baseTexture->format;
entry.isDepth = baseTexture->isDepth;
entry.dim = baseTexture->dim;
entry.tileMode = baseTexture->tileMode;
entry.lastAccessTick = baseTexture->lastAccessTick;
entry.lastAccessFrameCount = baseTexture->lastAccessFrameCount;
entry.isUpdatedOnGPU = baseTexture->isUpdatedOnGPU;
// overwrite info
entry.overwriteInfo.hasResolutionOverwrite = baseTexture->overwriteInfo.hasResolutionOverwrite;
entry.overwriteInfo.width = baseTexture->overwriteInfo.width;
entry.overwriteInfo.height = baseTexture->overwriteInfo.height;
entry.overwriteInfo.depth = baseTexture->overwriteInfo.depth;
// count number of alternative views
entry.alternativeViewCount = 0;
// views
for (auto& viewItr : baseTexture->views)
{
if(viewItr == baseTexture->baseView)
continue;
auto& viewEntry = entry.views.emplace_back();
viewEntry.physAddress = viewItr->baseTexture->physAddress;
viewEntry.physMipAddress = viewItr->baseTexture->physMipAddress;
viewEntry.width = viewItr->baseTexture->width;
viewEntry.height = viewItr->baseTexture->height;
viewEntry.pitch = viewItr->baseTexture->pitch;
viewEntry.firstMip = viewItr->firstMip;
viewEntry.numMip = viewItr->numMip;
viewEntry.firstSlice = viewItr->firstSlice;
viewEntry.numSlice = viewItr->numSlice;
viewEntry.format = viewItr->format;
viewEntry.dim = viewItr->dim;
}
}
std::swap(s_cacheInfoList, infoCache);
s_refreshTextureQueryList.store(false);
}
void LatteTexture_AddTexMemOccupancyInterval(LatteTextureSliceMipInfo* sliceMipInfo)
{
TexMemOccupancyEntry entry;
entry.addrStart = sliceMipInfo->addrStart;
entry.addrEnd = sliceMipInfo->addrEnd;
entry.sliceMipInfo = sliceMipInfo;
loopItrMemOccupancyBuckets(entry.addrStart, entry.addrEnd)
list_texMemOccupancyBucket[bucketIndex].push_back(entry);
}
void LatteTexture_RegisterTextureMemoryOccupancy(LatteTexture* texture)
{
sint32 mipLevels = texture->mipLevels;
sint32 sliceCount = texture->depth;
for (sint32 mipIndex = 0; mipIndex < mipLevels; mipIndex++)
{
sint32 mipSliceCount;
if (texture->Is3DTexture())
mipSliceCount = std::max(1, sliceCount >> mipIndex);
else
mipSliceCount = sliceCount;
for (sint32 sliceIndex = 0; sliceIndex < mipSliceCount; sliceIndex++)
{
LatteTextureSliceMipInfo* sliceMipInfo = texture->sliceMipInfo + texture->GetSliceMipArrayIndex(sliceIndex, mipIndex);
LatteTexture_AddTexMemOccupancyInterval(sliceMipInfo);
}
}
}
void LatteTexture_RemoveTexMemOccupancyInterval(LatteTexture* texture, LatteTextureSliceMipInfo* sliceMipInfo)
{
loopItrMemOccupancyBuckets(sliceMipInfo->addrStart, sliceMipInfo->addrEnd)
{
for (sint32 i = 0; i < list_texMemOccupancyBucket[bucketIndex].size(); i++)
{
if (list_texMemOccupancyBucket[bucketIndex][i].sliceMipInfo->texture == texture)
{
list_texMemOccupancyBucket[bucketIndex].erase(list_texMemOccupancyBucket[bucketIndex].begin() + i);
i--;
continue;
}
}
}
}
void LatteTexture_UnregisterTextureMemoryOccupancy(LatteTexture* texture)
{
sint32 mipLevels = texture->mipLevels;
sint32 sliceCount = texture->depth;
for (sint32 mipIndex = 0; mipIndex < mipLevels; mipIndex++)
{
sint32 mipSliceCount;
if (texture->Is3DTexture())
mipSliceCount = std::max(1, sliceCount >> mipIndex);
else
mipSliceCount = sliceCount;
for (sint32 sliceIndex = 0; sliceIndex < mipSliceCount; sliceIndex++)
{
LatteTextureSliceMipInfo* sliceMipInfo = texture->sliceMipInfo + texture->GetSliceMipArrayIndex(sliceIndex, mipIndex);
LatteTexture_RemoveTexMemOccupancyInterval(texture, sliceMipInfo);
}
}
}
// calculate the actually accessed data range
// the resulting range is an estimate and may be smaller than the actual slice size (but not larger)
void LatteTexture_EstimateMipSliceAccessedDataRange(LatteTexture* texture, sint32 sliceIndex, sint32 mipIndex, LatteTextureSliceMipInfo* sliceMipInfo)
{
uint32 estAddrStart;
uint32 estAddrEnd;
LatteTextureLoader_estimateAccessedDataRange(texture, sliceIndex, mipIndex, estAddrStart, estAddrEnd);
cemu_assert_debug(estAddrStart >= sliceMipInfo->addrStart);
cemu_assert_debug(estAddrEnd <= sliceMipInfo->addrEnd);
cemu_assert_debug(estAddrStart <= estAddrEnd);
sliceMipInfo->estDataAddrStart = estAddrStart;
sliceMipInfo->estDataAddrEnd = estAddrEnd;
}
void LatteTexture_InitSliceAndMipInfo(LatteTexture* texture)
{
cemu_assert_debug(texture->mipLevels > 0);
cemu_assert_debug(texture->depth > 0);
sint32 mipSliceCount = texture->GetSliceMipArraySize();
texture->sliceMipInfo = new LatteTextureSliceMipInfo[mipSliceCount]();
// todo - mipLevels can be greater than maximum possible mip count. How to handle this? Probably should differentiate between mipLevels and effective mip levels
sint32 mipLevels = texture->mipLevels;
sint32 sliceCount = texture->depth;
for (sint32 mipIndex = 0; mipIndex < mipLevels; mipIndex++)
{
sint32 mipSliceCount;
if (texture->Is3DTexture())
{
mipSliceCount = std::max(1, sliceCount >> mipIndex);
}
else
mipSliceCount = sliceCount;
for (sint32 sliceIndex = 0; sliceIndex < mipSliceCount; sliceIndex++)
{
uint32 calcSliceAddr;
uint32 calcSliceSize;
sint32 calcSubSliceIndex;
LatteAddrLib::CalculateMipAndSliceAddr(texture->physAddress, texture->physMipAddress, texture->format, texture->width, texture->height, texture->depth, texture->dim, texture->tileMode, texture->swizzle, 0, mipIndex, sliceIndex, &calcSliceAddr, &calcSliceSize, &calcSubSliceIndex);
LatteTextureSliceMipInfo* sliceMipInfo = texture->sliceMipInfo + texture->GetSliceMipArrayIndex(sliceIndex, mipIndex);
sliceMipInfo->addrStart = calcSliceAddr;
sliceMipInfo->addrEnd = calcSliceAddr + calcSliceSize;
sliceMipInfo->subIndex = calcSubSliceIndex;
sliceMipInfo->dataChecksum = 0;
sliceMipInfo->sliceIndex = sliceIndex;
sliceMipInfo->mipIndex = mipIndex;
sliceMipInfo->texture = texture;
// get additional slice/mip info
LatteAddrLib::AddrSurfaceInfo_OUT surfaceInfo;
LatteAddrLib::GX2CalculateSurfaceInfo(texture->format, texture->width, texture->height, texture->depth, texture->dim, Latte::MakeGX2TileMode(texture->tileMode), 0, mipIndex, &surfaceInfo);
sliceMipInfo->tileMode = surfaceInfo.hwTileMode;
if (mipIndex == 0)
sliceMipInfo->pitch = texture->pitch; // for the base level, use the pitch value configured in hardware
else
sliceMipInfo->pitch = surfaceInfo.pitch;
LatteTexture_EstimateMipSliceAccessedDataRange(texture, sliceIndex, mipIndex, sliceMipInfo);
}
}
}
// if this function returns false, textures will not be synchronized even if their data overlaps
bool LatteTexture_IsFormatViewCompatible(Latte::E_GX2SURFFMT formatA, Latte::E_GX2SURFFMT formatB)
{
if(formatA == formatB)
return true; // if the format is identical then compatibility must be guaranteed (otherwise we can't create the necessary default view of a texture)
// todo - find a better way to handle this
for (sint32 swap = 0; swap < 2; swap++)
{
// other formats
// seems like format 0x19 (RGB10_A2) has issues on OpenGL Intel and AMD when copying texture data
Latte::E_HWSURFFMT hwFormatA = Latte::GetHWFormat(formatA);
Latte::E_HWSURFFMT hwFormatB = Latte::GetHWFormat(formatB);
if (hwFormatA == Latte::E_HWSURFFMT::HWFMT_2_10_10_10 && formatB == Latte::E_GX2SURFFMT::R11_G11_B10_FLOAT)
return false;
if (formatA == Latte::E_GX2SURFFMT::R11_G11_B10_FLOAT && hwFormatB == Latte::E_HWSURFFMT::HWFMT_2_10_10_10)
return false;
if (hwFormatA == Latte::E_HWSURFFMT::HWFMT_2_10_10_10 && formatB == Latte::E_GX2SURFFMT::R8_G8_B8_A8_UNORM)
return false;
if (formatA == Latte::E_GX2SURFFMT::R8_G8_B8_A8_UNORM && hwFormatB == Latte::E_HWSURFFMT::HWFMT_2_10_10_10)
return false;
// format A1B5G5R5 views are not compatible with other 16-bit formats in OpenGL
if (formatA == Latte::E_GX2SURFFMT::A1_B5_G5_R5_UNORM || formatB == Latte::E_GX2SURFFMT::A1_B5_G5_R5_UNORM)
return false;
// used in N64 VC (E.g. Super Mario 64)
// used in Smash
if (formatA == Latte::E_GX2SURFFMT::D24_S8_UNORM && formatB == Latte::E_GX2SURFFMT::R10_G10_B10_A2_SNORM)
return false;
if (formatA == Latte::E_GX2SURFFMT::R32_FLOAT && formatB == Latte::E_GX2SURFFMT::R10_G10_B10_A2_SNORM)
return false;
// loop again with swapped vars
Latte::E_GX2SURFFMT temp = formatA;
formatA = formatB;
formatB = temp;
}
return true;
}
bool LatteTexture_IsTexelSizeCompatibleFormat(Latte::E_GX2SURFFMT formatA, Latte::E_GX2SURFFMT formatB)
{
// handle some special cases where formats are incompatible regardless of equal bpp
if (formatA == Latte::E_GX2SURFFMT::D24_S8_UNORM && formatB == Latte::E_GX2SURFFMT::D32_FLOAT)
return false;
if (Latte::IsCompressedFormat(formatA) && Latte::IsCompressedFormat(formatB))
{
if (Latte::GetHWFormat(formatA) != Latte::GetHWFormat(formatB))
return false; // compressed formats with different encodings are considered incompatible
}
return Latte::GetFormatBits((Latte::E_GX2SURFFMT)formatA) == Latte::GetFormatBits((Latte::E_GX2SURFFMT)formatB);
}
void LatteTexture_copyData(LatteTexture* srcTexture, LatteTexture* dstTexture, sint32 mipCount, sint32 sliceCount)
{
cemu_assert_debug(mipCount != 0);
cemu_assert_debug(sliceCount != 0);
sint32 effectiveCopyWidth = srcTexture->width;
sint32 effectiveCopyHeight = srcTexture->height;
if (LatteTexture_doesEffectiveRescaleRatioMatch(dstTexture, 0, srcTexture, 0))
{
// adjust copy size
LatteTexture_scaleToEffectiveSize(dstTexture, &effectiveCopyWidth, &effectiveCopyHeight, 0);
}
else
{
sint32 effectiveWidth_dst, effectiveHeight_dst;
srcTexture->GetEffectiveSize(effectiveWidth_dst, effectiveHeight_dst, 0);
sint32 effectiveWidth_src, effectiveHeight_src;
dstTexture->GetEffectiveSize(effectiveWidth_src, effectiveHeight_src, 0);
debug_printf("texture_copyData(): Effective size mismatch\n");
cemuLog_logDebug(LogType::Force, "texture_copyData(): Effective size mismatch (due to texture rule)");
cemuLog_logDebug(LogType::Force, "Destination: origResolution {:04}x{:04} effectiveResolution {:04}x{:04} fmt {:04x} mipIndex {}", srcTexture->width, srcTexture->height, effectiveWidth_dst, effectiveHeight_dst, (uint32)dstTexture->format, 0);
cemuLog_logDebug(LogType::Force, "Source: origResolution {:04}x{:04} effectiveResolution {:04}x{:04} fmt {:04x} mipIndex {}", srcTexture->width, srcTexture->height, effectiveWidth_src, effectiveHeight_src, (uint32)srcTexture->format, 0);
return;
}
for (sint32 mipIndex = 0; mipIndex < mipCount; mipIndex++)
{
sint32 sliceCopyWidth = std::max(effectiveCopyWidth >> mipIndex, 1);
sint32 sliceCopyHeight = std::max(effectiveCopyHeight >> mipIndex, 1);
g_renderer->texture_copyImageSubData(srcTexture, mipIndex, 0, 0, 0, dstTexture, mipIndex, 0, 0, 0, sliceCopyWidth, sliceCopyHeight, sliceCount);
sint32 mipSliceCount = sliceCount;
if (dstTexture->Is3DTexture())
mipSliceCount >>= mipIndex;
for (sint32 sliceIndex = 0; sliceIndex < mipSliceCount; sliceIndex++)
{
LatteTextureSliceMipInfo* srcTexSliceInfo = srcTexture->sliceMipInfo + srcTexture->GetSliceMipArrayIndex(sliceIndex, mipIndex);
LatteTextureSliceMipInfo* dstTexSliceInfo = dstTexture->sliceMipInfo + dstTexture->GetSliceMipArrayIndex(sliceIndex, mipIndex);
dstTexSliceInfo->lastDynamicUpdate = srcTexSliceInfo->lastDynamicUpdate;
}
}
}
template<bool bothMustMatch>
bool LatteTexture_DoesWidthHeightMatch(Latte::E_GX2SURFFMT format1, uint32 width1, uint32 height1, Latte::E_GX2SURFFMT format2, uint32 width2, uint32 height2)
{
if (Latte::IsCompressedFormat(format1))
{
width1 <<= 2;
height1 <<= 2;
}
if (Latte::IsCompressedFormat(format2))
{
width2 <<= 2;
height2 <<= 2;
}
if constexpr(bothMustMatch)
return width1 == width2 && height1 == height2;
else
return width1 == width2 || height1 == height2;
}
void LatteTexture_CopySlice(LatteTexture* srcTexture, sint32 srcSlice, sint32 srcMip, LatteTexture* dstTexture, sint32 dstSlice, sint32 dstMip, sint32 srcX, sint32 srcY, sint32 dstX, sint32 dstY, sint32 width, sint32 height)
{
if (srcTexture->isDepth != dstTexture->isDepth)
{
g_renderer->surfaceCopy_copySurfaceWithFormatConversion(srcTexture, srcMip, srcSlice, dstTexture, dstMip, dstSlice, width, height);
return;
}
// rescale copy size
sint32 effectiveCopyWidth = width;
sint32 effectiveCopyHeight = height;
LatteTexture_scaleToEffectiveSize(srcTexture, &effectiveCopyWidth, &effectiveCopyHeight, 0);
sint32 effectiveSrcX = srcX;
sint32 effectiveSrcY = srcY;
LatteTexture_scaleToEffectiveSize(srcTexture, &effectiveSrcX, &effectiveSrcY, 0);
sint32 effectiveDstX = dstX;
sint32 effectiveDstY = dstY;
LatteTexture_scaleToEffectiveSize(dstTexture, &effectiveDstX, &effectiveDstY, 0);
// check if rescale is compatible
if (LatteTexture_doesEffectiveRescaleRatioMatch(dstTexture, 0, srcTexture, 0) == false)
{
sint32 effectiveWidth_src = srcTexture->overwriteInfo.hasResolutionOverwrite ? srcTexture->overwriteInfo.width : srcTexture->width;
sint32 effectiveHeight_src = srcTexture->overwriteInfo.hasResolutionOverwrite ? srcTexture->overwriteInfo.height : srcTexture->height;
sint32 effectiveWidth_dst = dstTexture->overwriteInfo.hasResolutionOverwrite ? dstTexture->overwriteInfo.width : dstTexture->width;
sint32 effectiveHeight_dst = dstTexture->overwriteInfo.hasResolutionOverwrite ? dstTexture->overwriteInfo.height : dstTexture->height;
if (cemuLog_isLoggingEnabled(LogType::TextureCache))
{
cemuLog_log(LogType::Force, "_copySlice(): Unable to sync textures with mismatching scale ratio (due to texture rule)");
float ratioWidth_src = (float)effectiveWidth_src / (float)srcTexture->width;
float ratioHeight_src = (float)effectiveHeight_src / (float)srcTexture->height;
float ratioWidth_dst = (float)effectiveWidth_dst / (float)dstTexture->width;
float ratioHeight_dst = (float)effectiveHeight_dst / (float)dstTexture->height;
cemuLog_log(LogType::Force, "Source: {:08x} origResolution {:4}/{:4} effectiveResolution {:4}/{:4} fmt {:04x} mipIndex {} ratioW/H: {:.4}/{:.4}", srcTexture->physAddress, srcTexture->width, srcTexture->height, effectiveWidth_src, effectiveHeight_src, (uint32)srcTexture->format, srcMip, ratioWidth_src, ratioHeight_src);
cemuLog_log(LogType::Force, "Destination: {:08x} origResolution {:4}/{:4} effectiveResolution {:4}/{:4} fmt {:04x} mipIndex {} ratioW/H: {:.4}/{:.4}", dstTexture->physAddress, dstTexture->width, dstTexture->height, effectiveWidth_dst, effectiveHeight_dst, (uint32)dstTexture->format, dstMip, ratioWidth_dst, ratioHeight_dst);
}
//cemuLog_logDebug(LogType::Force, "If these textures are not meant to share data you can ignore this");
return;
}
// todo - store 'lastUpdated' value per slice/mip and copy it's value when copying the slice data
g_renderer->texture_copyImageSubData(srcTexture, srcMip, effectiveSrcX, effectiveSrcY, srcSlice, dstTexture, dstMip, effectiveDstX, effectiveDstY, dstSlice, effectiveCopyWidth, effectiveCopyHeight, 1);
}
bool LatteTexture_GetSubtextureSliceAndMip(LatteTexture* baseTexture, LatteTexture* mipTexture, sint32* baseSliceIndex, sint32* baseMipIndex)
{
LatteTextureSliceMipInfo* mipTextureSliceInfo = mipTexture->sliceMipInfo + mipTexture->GetSliceMipArrayIndex(0, 0);
// todo - this can be optimized by first determining the mip level from pitch
for (sint32 mipIndex = 0; mipIndex < baseTexture->mipLevels; mipIndex++)
{
sint32 sliceCount;
if (baseTexture->Is3DTexture())
sliceCount = std::max(baseTexture->depth >> mipIndex, 1);
else
sliceCount = baseTexture->depth;
for (sint32 sliceIndex = 0; sliceIndex < sliceCount; sliceIndex++)
{
LatteTextureSliceMipInfo* sliceMipInfo = baseTexture->sliceMipInfo + baseTexture->GetSliceMipArrayIndex(sliceIndex, mipIndex);
if (sliceMipInfo->addrStart == mipTextureSliceInfo->addrStart && sliceMipInfo->subIndex == mipTextureSliceInfo->subIndex)
{
*baseSliceIndex = sliceIndex;
*baseMipIndex = mipIndex;
return true;
}
// todo - support overlapping textures with a non-zero y-offset
}
}
return false;
}
// if a texture shares memory with another texture then flag those textures as invalidated (on next use, synchronize data)
void LatteTexture_MarkDynamicTextureAsChanged(LatteTextureView* textureView, sint32 sliceIndex, sint32 mipIndex, uint64 eventCounter)
{
LatteTexture* baseTexture = textureView->baseTexture;
baseTexture->lastWriteEventCounter = eventCounter;
sint32 aSliceIndex = textureView->firstSlice + sliceIndex;
sint32 aMipIndex = textureView->firstMip + mipIndex;
LatteTextureSliceMipInfo* baseSliceMipInfo = baseTexture->sliceMipInfo + baseTexture->GetSliceMipArrayIndex(aSliceIndex, aMipIndex);
baseSliceMipInfo->lastDynamicUpdate = eventCounter;
LatteTexture_MarkConnectedTexturesForReloadFromDynamicTextures(textureView->baseTexture);
}
void LatteTexture_SyncSlice(LatteTexture* srcTexture, sint32 srcSliceIndex, sint32 srcMipIndex, LatteTexture* dstTexture, sint32 dstSliceIndex, sint32 dstMipIndex)
{
sint32 srcWidth = srcTexture->width;
sint32 srcHeight = srcTexture->height;
sint32 dstWidth = dstTexture->width;
sint32 dstHeight = dstTexture->height;
if(srcTexture->overwriteInfo.hasFormatOverwrite != dstTexture->overwriteInfo.hasFormatOverwrite)
return; // dont sync: format overwrite state needs to match. Not strictly necessary but it simplifies logic down the road
else if(srcTexture->overwriteInfo.hasFormatOverwrite && srcTexture->overwriteInfo.format != dstTexture->overwriteInfo.format)
return; // both are overwritten but with different formats
if (srcMipIndex == 0 && dstMipIndex == 0 && (srcTexture->tileMode == Latte::E_HWTILEMODE::TM_LINEAR_ALIGNED || srcTexture->tileMode == Latte::E_HWTILEMODE::TM_1D_TILED_THIN1) && srcTexture->height > dstTexture->height && (srcTexture->height % dstTexture->height) == 0)
{
bool isMatch = srcTexture->tileMode == Latte::E_HWTILEMODE::TM_LINEAR_ALIGNED;
if (srcTexture->tileMode == Latte::E_HWTILEMODE::TM_1D_TILED_THIN1 && srcTexture->width == 32)
{
// special case for CoD BO2, where 1024x32 and 32x32x32 textures share memory
isMatch = true;
}
if (isMatch && srcTexture->IsCompressedFormat() == false && dstTexture->IsCompressedFormat() == false)
{
sint32 virtualSlices = srcTexture->height / dstTexture->height;
if (dstTexture->depth == virtualSlices)
{
// special case for Ninja Gaiden
// it initializes a 24x24x24 texture array as a 24x576x1 2D texture (using tilemode 1)
sint32 copyWidth = std::min(srcWidth, dstWidth);
sint32 copyHeight = std::min(srcHeight, dstHeight);
for (sint32 slice = 0; slice < virtualSlices; slice++)
LatteTexture_CopySlice(srcTexture, srcSliceIndex, srcMipIndex, dstTexture, dstSliceIndex + slice, dstMipIndex, 0, slice * dstTexture->height, 0, 0, copyWidth, copyHeight);
}
return;
}
}
bool srcIsCompressed = srcTexture->IsCompressedFormat();
bool dstIsCompressed = dstTexture->IsCompressedFormat();
if (srcIsCompressed != dstIsCompressed)
{
// convert into unit of source texture
if (srcIsCompressed == false)
{
// destination compressed, source uncompressed (integer format)
dstWidth >>= 2;
dstHeight >>= 2;
}
else
{
// destination uncompressed (integer format), source compressed
dstWidth <<= 2;
dstHeight <<= 2;
}
}
srcWidth = std::max(srcWidth >> srcMipIndex, 1);
srcHeight = std::max(srcHeight >> srcMipIndex, 1);
dstWidth = std::max(dstWidth >> dstMipIndex, 1);
dstHeight = std::max(dstHeight >> dstMipIndex, 1);
sint32 copyWidth = std::min(srcWidth, dstWidth);
sint32 copyHeight = std::min(srcHeight, dstHeight);
LatteTexture_CopySlice(srcTexture, srcSliceIndex, srcMipIndex, dstTexture, dstSliceIndex, dstMipIndex, 0, 0, 0, 0, copyWidth, copyHeight);
}
void LatteTexture_UpdateTextureFromDynamicChanges(LatteTexture* texture)
{
// note: Currently this function assumes that only one other texture is updated per slice/mip (if multiple overlap, we should merge the one with the latest timestamp the latest of each individually)
for (auto& texRel : texture->list_compatibleRelations)
{
LatteTexture* baseTexture = texRel->baseTexture;
LatteTexture* subTexture = texRel->subTexture;
for (sint32 cMipIndex = 0; cMipIndex < texRel->mipCount; cMipIndex++)
{
sint32 mipSliceCount = texRel->sliceCount;
if (texRel->baseTexture->Is3DTexture())
{
cemu_assert_debug(cMipIndex == 0); // values above 0 need testing
mipSliceCount >>= cMipIndex;
}
for (sint32 cSliceIndex = 0; cSliceIndex < mipSliceCount; cSliceIndex++)
{
LatteTextureSliceMipInfo* baseSliceMipInfo = baseTexture->sliceMipInfo + baseTexture->GetSliceMipArrayIndex(texRel->baseSliceIndex + cSliceIndex, texRel->baseMipIndex + cMipIndex);
LatteTextureSliceMipInfo* subSliceMipInfo = subTexture->sliceMipInfo + subTexture->GetSliceMipArrayIndex(cSliceIndex, cMipIndex);
if (texture == baseTexture)
{
// baseTexture is target texture
if (baseSliceMipInfo->lastDynamicUpdate < subSliceMipInfo->lastDynamicUpdate)
{
LatteTexture_SyncSlice(subTexture, cSliceIndex, cMipIndex, baseTexture, texRel->baseSliceIndex + cSliceIndex, texRel->baseMipIndex + cMipIndex);
baseSliceMipInfo->lastDynamicUpdate = subSliceMipInfo->lastDynamicUpdate;
if(subTexture->isUpdatedOnGPU)
texture->isUpdatedOnGPU = true;
}
}
else
{
// subTexture is target texture
if (subSliceMipInfo->lastDynamicUpdate < baseSliceMipInfo->lastDynamicUpdate)
{
LatteTexture_SyncSlice(baseTexture, texRel->baseSliceIndex + cSliceIndex, texRel->baseMipIndex + cMipIndex, subTexture, cSliceIndex, cMipIndex);
subSliceMipInfo->lastDynamicUpdate = baseSliceMipInfo->lastDynamicUpdate;
if (baseTexture->isUpdatedOnGPU)
texture->isUpdatedOnGPU = true;
}
}
}
}
}
}
bool _LatteTexture_IsTileModeCompatible(LatteTexture* texture1, sint32 mipIndex1, LatteTexture* texture2, sint32 mipIndex2)
{
if (mipIndex1 == 0 && mipIndex2 == 0)
return texture1->tileMode == texture2->tileMode;
LatteTextureSliceMipInfo* texture1SliceInfo = texture1->sliceMipInfo + texture1->GetSliceMipArrayIndex(0, mipIndex1);
LatteTextureSliceMipInfo* texture2SliceInfo = texture2->sliceMipInfo + texture2->GetSliceMipArrayIndex(0, mipIndex2);
if (texture1SliceInfo->tileMode == texture2SliceInfo->tileMode)
return true;
return false;
}
bool __LatteTexture_IsBlockedFormatRelation(LatteTexture* texture1, LatteTexture* texture2)
{
if (texture1->isDepth && texture2->isDepth == false)
{
// necessary for Smash? (currently our depth to color copy always converts and the depth ends up in R only)
if (texture1->format == Latte::E_GX2SURFFMT::D32_FLOAT && Latte::GetHWFormat(texture2->format) == Latte::E_HWSURFFMT::HWFMT_8_8_8_8)
return true;
}
// Vulkan has stricter rules
if (g_renderer->GetType() == RendererAPI::Vulkan)
{
// found in Smash (Wii Fit Stage)
if (texture1->format == Latte::E_GX2SURFFMT::D32_FLOAT && Latte::GetHWFormat(texture2->format) == Latte::E_HWSURFFMT::HWFMT_8_24)
return true;
}
return false;
}
bool LatteTexture_IsBlockedFormatRelation(LatteTexture* texture1, LatteTexture* texture2)
{
if (__LatteTexture_IsBlockedFormatRelation(texture1, texture2))
return true;
return __LatteTexture_IsBlockedFormatRelation(texture2, texture1);
}
// called if two textures are known to overlap in memory
// this function then tries to figure out the details and registers the relation in texture*->list_compatibleRelations
void LatteTexture_TrackTextureRelation(LatteTexture* texture1, LatteTexture* texture2)
{
// make sure texture 2 is always at texture 1 mip level 0 or beyond
if (texture1->physAddress > texture2->physAddress)
return LatteTexture_TrackTextureRelation(texture2, texture1);
// check if this texture relation is already tracked
cemu_assert_debug(texture1->physAddress != 0);
cemu_assert_debug(texture2->physAddress != 0);
for (auto& it : texture1->list_compatibleRelations)
{
if (it->baseTexture == texture1 && it->subTexture == texture2)
return; // association already known
}
// check for blocked format combination
if (LatteTexture_IsBlockedFormatRelation(texture1, texture2))
return;
if (texture1->physAddress == texture2->physAddress && false)
{
// both textures overlap at mip level 0
cemu_assert_debug(texture1->swizzle == texture2->swizzle);
cemu_assert_debug(texture1->tileMode == texture2->tileMode);
if (LatteTexture_DoesWidthHeightMatch<false>(texture1->format, texture1->width, texture1->height, texture2->format, texture2->width, texture2->height))
{
cemu_assert_unimplemented();
}
}
else
{
sint32 baseSliceIndex;
sint32 baseMipIndex;
if (texture1->physAddress == texture2->physAddress)
{
baseSliceIndex = 0;
baseMipIndex = 0;
}
else
{
if (LatteTexture_GetSubtextureSliceAndMip(texture1, texture2, &baseSliceIndex, &baseMipIndex) == false)
{
return;
}
}
sint32 sharedMipLevels = 1;
// todo - support for multiple shared mip levels
// check if pitch is compatible
LatteTextureSliceMipInfo* texture1SliceInfo = texture1->sliceMipInfo + texture1->GetSliceMipArrayIndex(baseSliceIndex, baseMipIndex);
LatteTextureSliceMipInfo* texture2SliceInfo = texture2->sliceMipInfo + texture2->GetSliceMipArrayIndex(0, 0);
if (_LatteTexture_IsTileModeCompatible(texture1, baseMipIndex, texture2, 0) == false)
return; // not compatible
if (texture1SliceInfo->pitch != texture2SliceInfo->pitch)
return; // not compatible
// calculate compatible depth range
sint32 baseRemainingDepth = texture1->GetMipDepth(baseMipIndex) - baseSliceIndex;
cemu_assert_debug(baseRemainingDepth >= 0);
sint32 compatibleDepthRange = std::min(baseRemainingDepth, texture2->depth);
cemu_assert_debug(compatibleDepthRange > 0);
// create association
LatteTextureRelation* rel = (LatteTextureRelation*)malloc(sizeof(LatteTextureRelation));
memset(rel, 0, sizeof(LatteTextureRelation));
rel->baseTexture = texture1;
rel->subTexture = texture2;
rel->baseMipIndex = baseMipIndex;
rel->baseSliceIndex = baseSliceIndex;
rel->mipCount = sharedMipLevels;
rel->sliceCount = compatibleDepthRange;
rel->yOffset = 0; // todo
texture1->list_compatibleRelations.push_back(rel);
texture2->list_compatibleRelations.push_back(rel);
}
}
void LatteTexture_TrackDataOverlap(LatteTexture* texture, LatteTextureSliceMipInfo* sliceMipInfo, TexMemOccupancyEntry& occupancy)
{
// todo - handle tile thickness and z offset
// todo - check address range overlap
auto& occMipSliceInfo = occupancy.sliceMipInfo;
if ((sliceMipInfo->addrEnd > occMipSliceInfo->addrStart && sliceMipInfo->addrStart < occMipSliceInfo->addrEnd) == false)
return;
// check if this overlap is already tracked
for (auto& it : sliceMipInfo->list_dataOverlap)
{
if (it.destMipSliceInfo == occupancy.sliceMipInfo)
return;
}
// register texture->dest
LatteTextureSliceMipDataOverlap_t overlapEntry;
overlapEntry.destMipSliceInfo = occupancy.sliceMipInfo;
overlapEntry.destTexture = occupancy.sliceMipInfo->texture;
sliceMipInfo->list_dataOverlap.push_back(overlapEntry);
// register dest->texture
LatteTextureSliceMipDataOverlap_t overlapEntry2;
overlapEntry2.destMipSliceInfo = sliceMipInfo;
overlapEntry2.destTexture = sliceMipInfo->texture;
occupancy.sliceMipInfo->list_dataOverlap.push_back(overlapEntry2);
}
void _LatteTexture_RemoveDataOverlapTracking(LatteTexture* texture, LatteTextureSliceMipInfo* sliceMipInfo, LatteTextureSliceMipDataOverlap_t& dataOverlap)
{
LatteTexture* destTexture = dataOverlap.destTexture;
LatteTextureSliceMipInfo* destSliceMipInfo = dataOverlap.destMipSliceInfo;
// delete from dest
for (auto it = destSliceMipInfo->list_dataOverlap.begin(); it != destSliceMipInfo->list_dataOverlap.end();)
{
if (it->destTexture == texture)
it = destSliceMipInfo->list_dataOverlap.erase(it);
else if (it->destTexture == destTexture)
cemu_assert_unimplemented();
else
it++;
}
}
void LatteTexture_DeleteDataOverlapTracking(LatteTexture* texture, LatteTextureSliceMipInfo* sliceMipInfo)
{
for(auto& it : sliceMipInfo->list_dataOverlap)
_LatteTexture_RemoveDataOverlapTracking(texture, sliceMipInfo, it);
sliceMipInfo->list_dataOverlap.resize(0);
}
void LatteTexture_DeleteDataOverlapTracking(LatteTexture* texture)
{
sint32 mipLevels = texture->mipLevels;
sint32 sliceCount = texture->depth;
for (sint32 mipIndex = 0; mipIndex < mipLevels; mipIndex++)
{
sint32 mipSliceCount;
if (texture->Is3DTexture())
mipSliceCount = std::max(1, sliceCount >> mipIndex);
else
mipSliceCount = sliceCount;
for (sint32 sliceIndex = 0; sliceIndex < mipSliceCount; sliceIndex++)
{
LatteTextureSliceMipInfo* sliceMipInfo = texture->sliceMipInfo + texture->GetSliceMipArrayIndex(sliceIndex, mipIndex);
LatteTexture_DeleteDataOverlapTracking(texture, sliceMipInfo);
}
}
}
void LatteTexture_GatherTextureRelations(LatteTexture* texture)
{
for (sint32 mipIndex = 0; mipIndex < texture->mipLevels; mipIndex++)
{
sint32 mipSliceCount;
if (texture->Is3DTexture())
mipSliceCount = std::max(1, texture->depth >> mipIndex);
else
mipSliceCount = texture->depth;
for (sint32 sliceIndex = 0; sliceIndex < mipSliceCount; sliceIndex++)
{
LatteTextureSliceMipInfo* sliceMipInfo = texture->sliceMipInfo + texture->GetSliceMipArrayIndex(sliceIndex, mipIndex);
loopItrMemOccupancyBuckets(sliceMipInfo->addrStart, sliceMipInfo->addrEnd)
{
for (auto& occupancy : list_texMemOccupancyBucket[bucketIndex])
{
LatteTexture* itrTexture = occupancy.sliceMipInfo->texture;
if (itrTexture == texture)
continue; // ignore self
if (sliceMipInfo->addrEnd >= occupancy.addrStart && sliceMipInfo->addrStart < occupancy.addrEnd)
{
if (sliceMipInfo->addrStart == occupancy.addrStart && sliceMipInfo->subIndex == occupancy.sliceMipInfo->subIndex)
{
// overlapping with zero x/y offset
if (sliceMipInfo->pitch == occupancy.sliceMipInfo->pitch && LatteTexture_IsTexelSizeCompatibleFormat(texture->format, itrTexture->format)
&& sliceMipInfo->tileMode == occupancy.sliceMipInfo->tileMode &&
LatteTexture_IsFormatViewCompatible(texture->format, itrTexture->format))
{
LatteTexture_TrackTextureRelation(texture, itrTexture);
}
else
{
// pitch not compatible or format not compatible
}
}
else
{
LatteTexture_TrackDataOverlap(texture, sliceMipInfo, occupancy);
}
}
}
}
}
}
}
void LatteTexture_DeleteTextureRelations(LatteTexture* texture)
{
while (texture->list_compatibleRelations.empty() == false)
{
LatteTextureRelation* rel = texture->list_compatibleRelations[0];
rel->baseTexture->list_compatibleRelations.erase(std::find(rel->baseTexture->list_compatibleRelations.begin(), rel->baseTexture->list_compatibleRelations.end(), rel));
rel->subTexture->list_compatibleRelations.erase(std::find(rel->subTexture->list_compatibleRelations.begin(), rel->subTexture->list_compatibleRelations.end(), rel));
free(rel);
}
texture->list_compatibleRelations.clear();
}
enum VIEWCOMPATIBILITY
{
VIEW_COMPATIBLE, // subtexture can be represented as view into base texture
VIEW_BASE_TOO_SMALL, // base texture must be extended (depth or mip levels) to fit sub texture
VIEW_NOT_COMPATIBLE,
};
bool IsDimensionCompatibleForGX2View(Latte::E_DIM baseDim, Latte::E_DIM viewDim)
{
// Note that some combinations depend on the exact view/slice index and count which we currently ignore (like a 3D view of a 3D texture)
bool isCompatible =
(baseDim == viewDim) ||
(baseDim == Latte::E_DIM::DIM_CUBEMAP && viewDim == Latte::E_DIM::DIM_2D) ||
(baseDim == Latte::E_DIM::DIM_2D && viewDim == Latte::E_DIM::DIM_2D_ARRAY) ||
(baseDim == Latte::E_DIM::DIM_2D_ARRAY && viewDim == Latte::E_DIM::DIM_2D) ||
(baseDim == Latte::E_DIM::DIM_CUBEMAP && viewDim == Latte::E_DIM::DIM_2D_ARRAY) ||
(baseDim == Latte::E_DIM::DIM_2D_ARRAY && viewDim == Latte::E_DIM::DIM_CUBEMAP) ||
(baseDim == Latte::E_DIM::DIM_3D && viewDim == Latte::E_DIM::DIM_2D_ARRAY);
if(isCompatible)
return true;
// these combinations have been seen in use by games and are considered incompatible:
// (baseDim == Latte::E_DIM::DIM_2D_ARRAY && viewDim == Latte::E_DIM::DIM_3D) -> Not allowed on OpenGL
// (baseDim == Latte::E_DIM::DIM_2D && viewDim == Latte::E_DIM::DIM_2D_MSAA)
// (baseDim == Latte::E_DIM::DIM_2D && viewDim == Latte::E_DIM::DIM_1D)
// (baseDim == Latte::E_DIM::DIM_2D && viewDim == Latte::E_DIM::DIM_3D)
// (baseDim == Latte::E_DIM::DIM_3D && viewDim == Latte::E_DIM::DIM_2D)
// (baseDim == Latte::E_DIM::DIM_3D && viewDim == Latte::E_DIM::DIM_3D) -> Only compatible if the same depth and shared at mip/slice 0
// (baseDim == Latte::E_DIM::DIM_2D && viewDim == Latte::E_DIM::DIM_CUBEMAP)
// (baseDim == Latte::E_DIM::DIM_2D_MSAA && viewDim == Latte::E_DIM::DIM_2D)
// (baseDim == Latte::E_DIM::DIM_1D && viewDim == Latte::E_DIM::DIM_2D)
return false;
}
VIEWCOMPATIBILITY LatteTexture_CanTextureBeRepresentedAsView(LatteTexture* baseTexture, uint32 physAddr, sint32 width, sint32 height, sint32 pitch, Latte::E_DIM dimView, Latte::E_GX2SURFFMT format, bool isDepth, sint32 firstMip, sint32 numMip, sint32 firstSlice, sint32 numSlice, sint32& relativeMipIndex, sint32& relativeSliceIndex)
{
relativeMipIndex = 0;
relativeSliceIndex = 0;
if (baseTexture->overwriteInfo.hasFormatOverwrite)
{
// if the base format is overwritten, then we only allow aliasing if the view format matches the base format
if (baseTexture->format != format)
return VIEW_NOT_COMPATIBLE;
}
if (LatteTexture_IsFormatViewCompatible(baseTexture->format, format) == false)
return VIEW_NOT_COMPATIBLE;
if (baseTexture->physAddress == physAddr && baseTexture->pitch == pitch)
{
if (baseTexture->isDepth != isDepth)
return VIEW_NOT_COMPATIBLE; // depth and non-depth formats are never compatible (on OpenGL)
if (!LatteTexture_IsTexelSizeCompatibleFormat(baseTexture->format, format) || baseTexture->width != width || baseTexture->height != height)
return VIEW_NOT_COMPATIBLE;
// 3D views are only compatible on Vulkan if they match the base texture in regards to mip and slice count
bool isCompatible3DView = dimView == Latte::E_DIM::DIM_3D && baseTexture->dim == dimView && firstSlice == 0 && firstMip == 0 && baseTexture->mipLevels == numMip && baseTexture->depth == numSlice;
if (!isCompatible3DView && !IsDimensionCompatibleForGX2View(baseTexture->dim, dimView))
return VIEW_NOT_COMPATIBLE;
if (baseTexture->isDepth && baseTexture->format != format)
{
// depth view with different format
cemuLog_logDebug(LogType::Force, "_createMapping(): Incompatible depth view format");
return VIEW_NOT_COMPATIBLE;
}
// AMD has a bug on OpenGL where it ignores the internal format of texture views when they are bound as render targets,
// as a result we cant use texture views when they have a different format
if (baseTexture->format != format)
return VIEW_NOT_COMPATIBLE;
if ((firstMip + numMip) > baseTexture->mipLevels || (firstSlice + numSlice) > baseTexture->depth)
{
// view has more slices or mips than existing texture
return VIEW_BASE_TOO_SMALL;
}
return VIEW_COMPATIBLE;
}
else
{
if (numMip > 1)
return VIEW_NOT_COMPATIBLE;
if (baseTexture->Is3DTexture())
return VIEW_NOT_COMPATIBLE; // todo - add support for mapping views into 3D textures
// if phys address or pitch differs then it might be pointing to a mip
for (sint32 m = 0; m < baseTexture->mipLevels; m++)
{
auto sliceMipInfo = baseTexture->sliceMipInfo + baseTexture->GetSliceMipArrayIndex(0, m);
// check pitch
if(sliceMipInfo->pitch != pitch)
continue;
// check all slices
if(LatteAddrLib::TM_IsThickAndMacroTiled(baseTexture->tileMode))
continue; // todo - check only every 4th slice?
for (sint32 s=0; s<baseTexture->GetMipDepth(m); s++)
{
sliceMipInfo = baseTexture->sliceMipInfo + baseTexture->GetSliceMipArrayIndex(s, m);
if (sliceMipInfo->addrStart != physAddr || sliceMipInfo->pitch != pitch)
continue;
if (baseTexture->isDepth != isDepth)
return VIEW_NOT_COMPATIBLE;
if (baseTexture->GetMipWidth(m) != width || baseTexture->GetMipHeight(m) != height)
return VIEW_NOT_COMPATIBLE;
if (!LatteTexture_IsTexelSizeCompatibleFormat(baseTexture->format, format) )
return VIEW_NOT_COMPATIBLE;
if (!IsDimensionCompatibleForGX2View(baseTexture->dim, dimView))
return VIEW_NOT_COMPATIBLE;
if (baseTexture->isDepth && baseTexture->format != format)
{
// depth view with different format
cemuLog_logDebug(LogType::Force, "_createMapping(): Incompatible depth view format");
return VIEW_NOT_COMPATIBLE;
}
// AMD has a bug on OpenGL where it ignores the internal format of texture views when they are bound as render targets,
// as a result we cant use texture views when they have a different format
if (baseTexture->format != format)
return VIEW_NOT_COMPATIBLE;
if ((m + firstMip + numMip) > baseTexture->mipLevels || (s + firstSlice + numSlice) > baseTexture->depth)
{
relativeMipIndex = m;
relativeSliceIndex = s;
return VIEW_BASE_TOO_SMALL;
}
relativeMipIndex = m;
relativeSliceIndex = s;
return VIEW_COMPATIBLE;
}
}
}
return VIEW_NOT_COMPATIBLE;
}
// deletes any related textures that have become redundant (aka textures that can also be represented entirely as a view into the new texture)
void LatteTexture_DeleteAbsorbedSubtextures(LatteTexture* texture)
{
for(size_t i=0; i<texture->list_compatibleRelations.size(); i++)
{
LatteTextureRelation* textureRelation = texture->list_compatibleRelations[i];
LatteTexture* relatedTexture = (textureRelation->baseTexture!=texture)? textureRelation->baseTexture:textureRelation->subTexture;
sint32 relativeMipIndex;
sint32 relativeSliceIndex;
if (LatteTexture_CanTextureBeRepresentedAsView(texture, relatedTexture->physAddress, relatedTexture->width, relatedTexture->height, relatedTexture->pitch, relatedTexture->dim, relatedTexture->format, relatedTexture->isDepth, 0, relatedTexture->mipLevels, 0, relatedTexture->depth, relativeMipIndex, relativeSliceIndex) == VIEW_COMPATIBLE)
{
LatteTexture_Delete(relatedTexture);
LatteGPUState.repeatTextureInitialization = true;
}
}
}
void LatteTexture_RecreateTextureWithDifferentMipSliceCount(LatteTexture* texture, MPTR physMipAddr, sint32 newMipCount, sint32 newDepth)
{
Latte::E_DIM newDim = texture->dim;
if (newDim == Latte::E_DIM::DIM_2D && newDepth > 1)
newDim = Latte::E_DIM::DIM_2D_ARRAY;
else if (newDim == Latte::E_DIM::DIM_1D && newDepth > 1)
newDim = Latte::E_DIM::DIM_1D_ARRAY;
LatteTextureView* view = LatteTexture_CreateTexture(newDim, texture->physAddress, physMipAddr, texture->format, texture->width, texture->height, newDepth, texture->pitch, newMipCount, texture->swizzle, texture->tileMode, texture->isDepth);
cemu_assert(!(view->baseTexture->mipLevels <= 1 && physMipAddr == MPTR_NULL && newMipCount > 1));
// copy data from old texture if its dynamically updated
if (texture->isUpdatedOnGPU)
{
LatteTexture_copyData(texture, view->baseTexture, texture->mipLevels, texture->depth);
view->baseTexture->isUpdatedOnGPU = true;
}
// remove old texture
LatteTexture_Delete(texture);
// gather texture relations for new texture
LatteTexture_GatherTextureRelations(view->baseTexture);
LatteTexture_UpdateTextureFromDynamicChanges(view->baseTexture);
// todo - inherit 'isUpdatedOnGPU' flag for each mip/slice
// delete any individual smaller slices/mips that have become redundant
LatteTexture_DeleteAbsorbedSubtextures(view->baseTexture);
}
// create new texture representation
// if allowCreateNewDataTexture is true, a new texture will be created if necessary. If it is false, only existing textures may be used, except if a data-compatible version of the requested texture already exists and it's not view compatible (todo - we should differentiate between Latte compatible views and renderer compatible)
// the returned view will map to the provided mip and slice range within the created texture, this is to match the behavior of lookupSliceEx
LatteTextureView* LatteTexture_CreateMapping(MPTR physAddr, MPTR physMipAddr, sint32 width, sint32 height, sint32 depth, sint32 pitch, Latte::E_HWTILEMODE tileMode, uint32 swizzle, sint32 firstMip, sint32 numMip, sint32 firstSlice, sint32 numSlice, Latte::E_GX2SURFFMT format, Latte::E_DIM dimBase, Latte::E_DIM dimView, bool isDepth, bool allowCreateNewDataTexture)
{
if (format == Latte::E_GX2SURFFMT::INVALID_FORMAT)
{
cemuLog_logDebug(LogType::Force, "LatteTexture_CreateMapping(): Invalid format");
return nullptr;
}
// note: When creating an existing texture, we only allow mip and slice expansion at the end
cemu_assert_debug(depth);
cemu_assert_debug(!(depth > 1 && dimBase == Latte::E_DIM::DIM_2D));
cemu_assert_debug(!(numSlice > 1 && dimView == Latte::E_DIM::DIM_2D));
// todo, depth and numSlice are redundant
sint32 sliceCount = firstSlice + numSlice;
boost::container::small_vector<LatteTexture*, 16> list_overlappingTextures;
for (sint32 sliceIndex = 0; sliceIndex < sliceCount; sliceIndex++)
{
sint32 mipIndex = 0;
uint32 calcSliceAddrStart;
uint32 calcSliceSize;
sint32 calcSubSliceIndex;
LatteAddrLib::CalculateMipAndSliceAddr(physAddr, physMipAddr, format, width, height, depth, dimBase, tileMode, swizzle, 0, mipIndex, sliceIndex, &calcSliceAddrStart, &calcSliceSize, &calcSubSliceIndex);
uint32 calcSliceAddrEnd = calcSliceAddrStart + calcSliceSize;
// attempt to create view in already existing texture first (we may have to recreate the texture with new specifications)
loopItrMemOccupancyBuckets(calcSliceAddrStart, calcSliceAddrEnd)
{
for (auto& occupancy : list_texMemOccupancyBucket[bucketIndex])
{
if (calcSliceAddrEnd >= occupancy.addrStart && calcSliceAddrStart < occupancy.addrEnd)
{
if (calcSliceAddrStart == occupancy.addrStart)
{
// overlapping with zero x/y offset
if (std::find(list_overlappingTextures.begin(), list_overlappingTextures.end(), occupancy.sliceMipInfo->texture) == list_overlappingTextures.end())
{
list_overlappingTextures.push_back(occupancy.sliceMipInfo->texture);
}
}
else
{
// overlapping but not matching directly
// todo - check if they match with a y offset
}
}
}
}
}
// try to merge textures if possible
for (auto& tex : list_overlappingTextures)
{
sint32 relativeMipIndex;
sint32 relativeSliceIndex;
VIEWCOMPATIBILITY viewCompatibility = LatteTexture_CanTextureBeRepresentedAsView(tex, physAddr, width, height, pitch, dimView, format, isDepth, firstMip, numMip, firstSlice, numSlice, relativeMipIndex, relativeSliceIndex);
if (viewCompatibility == VIEW_NOT_COMPATIBLE)
{
allowCreateNewDataTexture = true;
continue;
}
if (viewCompatibility == VIEW_BASE_TOO_SMALL)
{
if (relativeMipIndex != 0 || relativeSliceIndex != 0)
{
// not yet supported
allowCreateNewDataTexture = true;
continue;
}
// new mapping has more slices/mips than known texture -> expand texture
sint32 newDepth = std::max(relativeSliceIndex + firstSlice + numSlice, std::max(depth, tex->depth));
sint32 newMipCount = std::max(relativeMipIndex + firstMip + numMip, tex->mipLevels);
uint32 newPhysMipAddr;
if ((relativeMipIndex + firstMip + numMip) > 1)
{
newPhysMipAddr = physMipAddr;
}
else
{
newPhysMipAddr = tex->physMipAddress;
}
LatteTexture_RecreateTextureWithDifferentMipSliceCount(tex, newPhysMipAddr, newMipCount, newDepth);
return LatteTexture_CreateMapping(physAddr, physMipAddr, width, height, depth, pitch, tileMode, swizzle, firstMip, numMip, firstSlice, numSlice, format, dimBase, dimView, isDepth);
}
else if(viewCompatibility == VIEW_COMPATIBLE)
{
LatteTextureView* view = tex->GetOrCreateView(dimView, format, relativeMipIndex + firstMip, numMip, relativeSliceIndex + firstSlice, numSlice);
if (relativeMipIndex != 0 || relativeSliceIndex != 0)
{
// for accesses to mips/slices using a physAddress offset we manually need to create a new view lookup
// by default views only create a lookup for the base texture physAddress
view->CreateLookupForSubTexture(relativeMipIndex, relativeSliceIndex);
#ifdef CEMU_DEBUG_ASSERT
LatteTextureView* testView = LatteTextureViewLookupCache::lookup(physAddr, width, height, depth, pitch, firstMip, numMip, firstSlice, numSlice, format, dimView);
cemu_assert(testView);
#endif
}
return view;
}
else
{
cemu_assert_debug(false);
}
}
// create new texture
if (allowCreateNewDataTexture == false)
return nullptr;
LatteTextureView* view = LatteTexture_CreateTexture(dimBase, physAddr, physMipAddr, format, width, height, depth, pitch, firstMip + numMip, swizzle, tileMode, isDepth);
LatteTexture* newTexture = view->baseTexture;
LatteTexture_GatherTextureRelations(view->baseTexture);
LatteTexture_UpdateTextureFromDynamicChanges(view->baseTexture);
// delete any individual smaller slices/mips that have become redundant
LatteTexture_DeleteAbsorbedSubtextures(view->baseTexture);
// create view
sint32 relativeMipIndex;
sint32 relativeSliceIndex;
VIEWCOMPATIBILITY viewCompatibility = LatteTexture_CanTextureBeRepresentedAsView(newTexture, physAddr, width, height, pitch, dimView, format, isDepth, firstMip, numMip, firstSlice, numSlice, relativeMipIndex, relativeSliceIndex);
cemu_assert(viewCompatibility == VIEW_COMPATIBLE);
return view->baseTexture->GetOrCreateView(dimView, format, relativeMipIndex + firstMip, numMip, relativeSliceIndex + firstSlice, numSlice);
}
LatteTextureView* LatteTC_LookupTextureByData(MPTR physAddr, sint32 width, sint32 height, sint32 pitch, sint32 firstMip, sint32 numMip, sint32 firstSlice, sint32 numSlice, sint32* searchIndex)
{
cemu_assert_debug(firstMip == 0);
sint32 cSearchIndex = 0;
loopItrMemOccupancyBuckets(physAddr, physAddr+1)
{
auto& bucket = list_texMemOccupancyBucket[bucketIndex];
for (sint32 i = 0; i < bucket.size(); i++)
{
if (bucket[i].addrStart == physAddr)
{
LatteTexture* tex = bucket[i].sliceMipInfo->texture;
if (tex->physAddress == physAddr && tex->pitch == pitch)
{
if (firstSlice >= 0 && firstSlice < (tex->depth))
{
if (cSearchIndex >= *searchIndex)
{
(*searchIndex)++;
return tex->baseView;
}
cSearchIndex++;
}
}
}
}
}
return nullptr;
}
void LatteTC_LookupTexturesByPhysAddr(MPTR physAddr, std::vector<LatteTexture*>& list_textures)
{
sint32 cSearchIndex = 0;
loopItrMemOccupancyBuckets(physAddr, physAddr + 1)
{
for (sint32 i = 0; i < list_texMemOccupancyBucket[bucketIndex].size(); i++)
{
if (list_texMemOccupancyBucket[bucketIndex][i].addrStart == physAddr)
{
LatteTexture* tex = list_texMemOccupancyBucket[bucketIndex][i].sliceMipInfo->texture;
if (tex->physAddress == physAddr)
{
vectorAppendUnique(list_textures, tex);
}
}
}
}
}
LatteTextureView* LatteTC_GetTextureSliceViewOrTryCreate(MPTR srcImagePtr, MPTR srcMipPtr, Latte::E_GX2SURFFMT srcFormat, Latte::E_HWTILEMODE srcTileMode, uint32 srcWidth, uint32 srcHeight, uint32 srcDepth, uint32 srcPitch, uint32 srcSwizzle, uint32 srcSlice, uint32 srcMip, const bool requireExactResolution)
{
LatteTextureView* sourceView;
if(requireExactResolution == false)
sourceView = LatteTextureViewLookupCache::lookupSliceMinSize(srcImagePtr, srcWidth, srcHeight, srcPitch, srcMip, srcSlice, srcFormat);
else
sourceView = LatteTextureViewLookupCache::lookupSlice(srcImagePtr, srcWidth, srcHeight, srcPitch, srcMip, srcSlice, srcFormat);
if (sourceView)
return sourceView;
return LatteTexture_CreateMapping(srcImagePtr, srcMipPtr, srcWidth, srcHeight, srcDepth, srcPitch, srcTileMode, srcSwizzle, srcMip, 1, srcSlice, 1, srcFormat, srcDepth > 1 ? Latte::E_DIM::DIM_2D_ARRAY : Latte::E_DIM::DIM_2D, Latte::E_DIM::DIM_2D, false, false);
}
void LatteTexture_UpdateDataToLatest(LatteTexture* texture)
{
if (LatteTC_HasTextureChanged(texture))
LatteTexture_ReloadData(texture);
if (texture->reloadFromDynamicTextures)
{
LatteTexture_UpdateCacheFromDynamicTextures(texture);
texture->reloadFromDynamicTextures = false;
}
}
LatteTextureSliceMipInfo* LatteTexture::GetSliceMipArrayEntry(sint32 sliceIndex, sint32 mipIndex)
{
return sliceMipInfo + GetSliceMipArrayIndex(sliceIndex, mipIndex);
}
std::vector<LatteTexture*> sAllTextures; // entries can be nullptr
std::vector<size_t> sAllTextureFreeIndices;
void _AddTextureToGlobalList(LatteTexture* tex)
{
if (sAllTextureFreeIndices.empty())
{
tex->globalListIndex = sAllTextures.size();
sAllTextures.emplace_back(tex);
return;
}
size_t index = sAllTextureFreeIndices.back();
sAllTextureFreeIndices.pop_back();
sAllTextures[index] = tex;
tex->globalListIndex = index;
}
void _RemoveTextureFromGlobalList(LatteTexture* tex)
{
cemu_assert_debug(tex->globalListIndex >= 0 && tex->globalListIndex < sAllTextures.size());
cemu_assert_debug(sAllTextures[tex->globalListIndex] == tex);
if (tex->globalListIndex + 1 == sAllTextures.size())
{
// if the index is at the end, make the list smaller instead of freeing the index
sAllTextures.pop_back();
return;
}
sAllTextures[tex->globalListIndex] = nullptr;
sAllTextureFreeIndices.emplace_back(tex->globalListIndex);
}
std::vector<LatteTexture*>& LatteTexture::GetAllTextures()
{
return sAllTextures;
}
bool LatteTexture_GX2FormatHasStencil(bool isDepth, Latte::E_GX2SURFFMT format)
{
if (!isDepth)
return false;
return format == Latte::E_GX2SURFFMT::D24_S8_UNORM ||
format == Latte::E_GX2SURFFMT::D24_S8_FLOAT ||
format == Latte::E_GX2SURFFMT::D32_S8_FLOAT;
}
LatteTexture::LatteTexture(Latte::E_DIM dim, MPTR physAddress, MPTR physMipAddress, Latte::E_GX2SURFFMT format, uint32 width, uint32 height, uint32 depth, uint32 pitch, uint32 mipLevels, uint32 swizzle,
Latte::E_HWTILEMODE tileMode, bool isDepth)
{
_AddTextureToGlobalList(this);
if (depth < 1)
depth = 1;
// setup texture object
this->physAddress = physAddress;
this->dim = dim;
this->format = format;
this->width = width;
this->height = height;
this->depth = depth;
this->swizzle = swizzle;
this->pitch = pitch;
this->mipLevels = mipLevels;
this->tileMode = tileMode;
this->isDepth = isDepth;
this->hasStencil = LatteTexture_GX2FormatHasStencil(isDepth, format);
this->physMipAddress = physMipAddress;
this->lastUpdateEventCounter = LatteTexture_getNextUpdateEventCounter();
this->lastWriteEventCounter = LatteTexture_getNextUpdateEventCounter();
// handle graphic pack overwrite rules
for (const auto& gp : GraphicPack2::GetActiveGraphicPacks())
{
for (const auto& rule : gp->GetTextureRules())
{
if (!rule.filter_settings.format_whitelist.empty() && std::find(rule.filter_settings.format_whitelist.begin(), rule.filter_settings.format_whitelist.end(), (uint32)format) == rule.filter_settings.format_whitelist.end())
continue;
if (!rule.filter_settings.format_blacklist.empty() && std::find(rule.filter_settings.format_blacklist.begin(), rule.filter_settings.format_blacklist.end(), (uint32)format) != rule.filter_settings.format_blacklist.end())
continue;
if (!rule.filter_settings.tilemode_whitelist.empty() && std::find(rule.filter_settings.tilemode_whitelist.begin(), rule.filter_settings.tilemode_whitelist.end(), (int)tileMode) == rule.filter_settings.tilemode_whitelist.end())
continue;
if (!rule.filter_settings.tilemode_blacklist.empty() && std::find(rule.filter_settings.tilemode_blacklist.begin(), rule.filter_settings.tilemode_blacklist.end(), (int)tileMode) != rule.filter_settings.tilemode_blacklist.end())
continue;
if (rule.filter_settings.width != -1 && rule.filter_settings.width != width)
continue;
if (rule.filter_settings.height != -1 && rule.filter_settings.height != height)
continue;
if (rule.filter_settings.depth != -1 && rule.filter_settings.depth != depth)
continue;
if (rule.filter_settings.inMEM1 == GraphicPack2::TextureRule::FILTER_SETTINGS::MEM1_FILTER::OUTSIDE && mmuRange_MEM1.containsAddress(this->physAddress))
continue;
if (rule.filter_settings.inMEM1 == GraphicPack2::TextureRule::FILTER_SETTINGS::MEM1_FILTER::INSIDE && !mmuRange_MEM1.containsAddress(this->physAddress))
continue;
this->overwriteInfo.width = width;
this->overwriteInfo.height = height;
this->overwriteInfo.depth = depth;
if (rule.overwrite_settings.width != -1)
{
this->overwriteInfo.hasResolutionOverwrite = true;
this->overwriteInfo.width = rule.overwrite_settings.width;
}
if (rule.overwrite_settings.height != -1)
{
this->overwriteInfo.hasResolutionOverwrite = true;
this->overwriteInfo.height = rule.overwrite_settings.height;
}
if (rule.overwrite_settings.depth != -1)
{
this->overwriteInfo.hasResolutionOverwrite = true;
this->overwriteInfo.depth = rule.overwrite_settings.depth;
}
if (rule.overwrite_settings.format != -1)
{
this->overwriteInfo.hasFormatOverwrite = true;
this->overwriteInfo.format = rule.overwrite_settings.format;
}
if (rule.overwrite_settings.lod_bias != -1)
{
this->overwriteInfo.hasLodBias = true;
this->overwriteInfo.lodBias = rule.overwrite_settings.lod_bias;
}
if (rule.overwrite_settings.relative_lod_bias != -1)
{
this->overwriteInfo.hasRelativeLodBias = true;
this->overwriteInfo.relativeLodBias = rule.overwrite_settings.relative_lod_bias;
}
if (rule.overwrite_settings.anistropic_value != -1)
{
this->overwriteInfo.anisotropicLevel = rule.overwrite_settings.anistropic_value;
}
}
}
// determine if this texture should ever be mirrored to CPU RAM
if (this->tileMode == Latte::E_HWTILEMODE::TM_LINEAR_ALIGNED)
{
this->enableReadback = true;
}
// calculate number of potential mip levels (from effective size)
sint32 effectiveWidth = width;
sint32 effectiveHeight = height;
sint32 effectiveDepth = depth;
if (this->overwriteInfo.hasResolutionOverwrite)
{
effectiveWidth = this->overwriteInfo.width;
effectiveHeight = this->overwriteInfo.height;
effectiveDepth = this->overwriteInfo.depth;
}
this->maxPossibleMipLevels = 1;
if (dim != Latte::E_DIM::DIM_3D)
{
for (sint32 i = 0; i < 20; i++)
{
if ((effectiveWidth >> i) <= 1 && (effectiveHeight >> i) <= 1)
{
this->maxPossibleMipLevels = i + 1;
break;
}
}
}
else
{
for (sint32 i = 0; i < 20; i++)
{
if ((effectiveWidth >> i) <= 1 && (effectiveHeight >> i) <= 1 && (effectiveDepth >> i) <= 1)
{
this->maxPossibleMipLevels = i + 1;
break;
}
}
}
}
LatteTexture::~LatteTexture()
{
_RemoveTextureFromGlobalList(this);
cemu_assert_debug(baseView == nullptr);
cemu_assert_debug(views.empty());
};
// sync texture data between overlapping textures
void LatteTexture_UpdateCacheFromDynamicTextures(LatteTexture* textureDest)
{
LatteTexture_UpdateTextureFromDynamicChanges(textureDest);
}
void LatteTexture_MarkConnectedTexturesForReloadFromDynamicTextures(LatteTexture* texture)
{
for (auto& it : texture->list_compatibleRelations)
{
if (texture == it->baseTexture)
it->subTexture->reloadFromDynamicTextures = true;
else
it->baseTexture->reloadFromDynamicTextures = true;
}
}
void LatteTexture_TrackTextureGPUWrite(LatteTexture* texture, uint32 slice, uint32 mip, uint64 eventCounter)
{
LatteTexture_MarkDynamicTextureAsChanged(texture->baseView, slice, mip, eventCounter);
LatteTC_ResetTextureChangeTracker(texture);
texture->isUpdatedOnGPU = true;
texture->lastUnflushedRTDrawcallIndex = LatteGPUState.drawCallCounter;
}