Merge pull request #7412 from AdmiralCurtiss/xfb-stitch-in-order
Stitch together overlapping XFB regions in order of XFB copy creation.
This commit is contained in:
commit
660fb3fca0
|
@ -8,6 +8,7 @@
|
|||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#if defined(_M_X86) || defined(_M_X86_64)
|
||||
#include <pmmintrin.h>
|
||||
#endif
|
||||
|
@ -1303,6 +1304,15 @@ bool TextureCacheBase::LoadTextureFromOverlappingTextures(TCacheEntry* entry_to_
|
|||
|
||||
u32 numBlocksX = entry_to_update->native_width / tex_info.block_width;
|
||||
|
||||
// It is possible that some of the overlapping textures overlap each other.
|
||||
// This behavior has been seen with XFB copies in Rogue Leader.
|
||||
// To get the correct result, we apply the texture updates in the order the textures were
|
||||
// originally loaded. This ensures that the parts of the texture that would have been overwritten
|
||||
// in memory on real hardware get overwritten the same way here too.
|
||||
// This should work, but it may be a better idea to keep track of partial XFB copy invalidations
|
||||
// instead, which would reduce the amount of copying work here.
|
||||
std::vector<TCacheEntry*> candidates;
|
||||
|
||||
auto iter = FindOverlappingTextures(entry_to_update->addr, entry_to_update->size_in_bytes);
|
||||
while (iter.first != iter.second)
|
||||
{
|
||||
|
@ -1314,106 +1324,7 @@ bool TextureCacheBase::LoadTextureFromOverlappingTextures(TCacheEntry* entry_to_
|
|||
{
|
||||
if (entry->hash == entry->CalculateHash())
|
||||
{
|
||||
if (tex_info.is_palette_texture)
|
||||
{
|
||||
TCacheEntry* decoded_entry =
|
||||
ApplyPaletteToEntry(entry, nullptr, tex_info.full_format.tlutfmt);
|
||||
if (decoded_entry)
|
||||
{
|
||||
// Link the efb copy with the partially updated texture, so we won't apply this partial
|
||||
// update again
|
||||
entry->CreateReference(entry_to_update);
|
||||
// Mark the texture update as used, as if it was loaded directly
|
||||
entry->frameCount = FRAMECOUNT_INVALID;
|
||||
entry = decoded_entry;
|
||||
}
|
||||
else
|
||||
{
|
||||
++iter.first;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
s32 src_x, src_y, dst_x, dst_y;
|
||||
|
||||
// Note for understanding the math:
|
||||
// Normal textures can't be strided, so the 2 missing cases with src_x > 0 don't exist
|
||||
if (entry->addr >= entry_to_update->addr)
|
||||
{
|
||||
s32 block_offset = (entry->addr - entry_to_update->addr) / tex_info.bytes_per_block;
|
||||
s32 block_x = block_offset % numBlocksX;
|
||||
s32 block_y = block_offset / numBlocksX;
|
||||
src_x = 0;
|
||||
src_y = 0;
|
||||
dst_x = block_x * tex_info.block_width;
|
||||
dst_y = block_y * tex_info.block_height;
|
||||
}
|
||||
else
|
||||
{
|
||||
s32 block_offset = (entry_to_update->addr - entry->addr) / tex_info.bytes_per_block;
|
||||
s32 block_x = block_offset % numBlocksX;
|
||||
s32 block_y = block_offset / numBlocksX;
|
||||
src_x = block_x * tex_info.block_width;
|
||||
src_y = block_y * tex_info.block_height;
|
||||
dst_x = 0;
|
||||
dst_y = 0;
|
||||
}
|
||||
|
||||
u32 copy_width =
|
||||
std::min(entry->native_width - src_x, entry_to_update->native_width - dst_x);
|
||||
u32 copy_height =
|
||||
std::min(entry->native_height - src_y, entry_to_update->native_height - dst_y);
|
||||
|
||||
// If one of the textures is scaled, scale both with the current efb scaling factor
|
||||
if (entry_to_update->native_width != entry_to_update->GetWidth() ||
|
||||
entry_to_update->native_height != entry_to_update->GetHeight() ||
|
||||
entry->native_width != entry->GetWidth() || entry->native_height != entry->GetHeight())
|
||||
{
|
||||
ScaleTextureCacheEntryTo(entry_to_update,
|
||||
g_renderer->EFBToScaledX(entry_to_update->native_width),
|
||||
g_renderer->EFBToScaledY(entry_to_update->native_height));
|
||||
ScaleTextureCacheEntryTo(entry, g_renderer->EFBToScaledX(entry->native_width),
|
||||
g_renderer->EFBToScaledY(entry->native_height));
|
||||
|
||||
src_x = g_renderer->EFBToScaledX(src_x);
|
||||
src_y = g_renderer->EFBToScaledY(src_y);
|
||||
dst_x = g_renderer->EFBToScaledX(dst_x);
|
||||
dst_y = g_renderer->EFBToScaledY(dst_y);
|
||||
copy_width = g_renderer->EFBToScaledX(copy_width);
|
||||
copy_height = g_renderer->EFBToScaledY(copy_height);
|
||||
}
|
||||
|
||||
MathUtil::Rectangle<int> srcrect, dstrect;
|
||||
srcrect.left = src_x;
|
||||
srcrect.top = src_y;
|
||||
srcrect.right = (src_x + copy_width);
|
||||
srcrect.bottom = (src_y + copy_height);
|
||||
|
||||
dstrect.left = dst_x;
|
||||
dstrect.top = dst_y;
|
||||
dstrect.right = (dst_x + copy_width);
|
||||
dstrect.bottom = (dst_y + copy_height);
|
||||
|
||||
for (u32 layer = 0; layer < entry->texture->GetConfig().layers; layer++)
|
||||
{
|
||||
entry_to_update->texture->CopyRectangleFromTexture(entry->texture.get(), srcrect, layer,
|
||||
0, dstrect, layer, 0);
|
||||
}
|
||||
updated_entry = true;
|
||||
|
||||
if (tex_info.is_palette_texture)
|
||||
{
|
||||
// Remove the temporary converted texture, it won't be used anywhere else
|
||||
// TODO: It would be nice to convert and copy in one step, but this code path isn't common
|
||||
InvalidateTexture(GetTexCacheIter(entry));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Link the two textures together, so we won't apply this partial update again
|
||||
entry->CreateReference(entry_to_update);
|
||||
// Mark the texture update as used, as if it was loaded directly
|
||||
entry->frameCount = FRAMECOUNT_INVALID;
|
||||
}
|
||||
candidates.emplace_back(entry);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1425,6 +1336,111 @@ bool TextureCacheBase::LoadTextureFromOverlappingTextures(TCacheEntry* entry_to_
|
|||
++iter.first;
|
||||
}
|
||||
|
||||
std::sort(candidates.begin(), candidates.end(),
|
||||
[](const TCacheEntry* a, const TCacheEntry* b) { return a->id < b->id; });
|
||||
|
||||
for (TCacheEntry* entry : candidates)
|
||||
{
|
||||
if (tex_info.is_palette_texture)
|
||||
{
|
||||
TCacheEntry* decoded_entry =
|
||||
ApplyPaletteToEntry(entry, nullptr, tex_info.full_format.tlutfmt);
|
||||
if (decoded_entry)
|
||||
{
|
||||
// Link the efb copy with the partially updated texture, so we won't apply this partial
|
||||
// update again
|
||||
entry->CreateReference(entry_to_update);
|
||||
// Mark the texture update as used, as if it was loaded directly
|
||||
entry->frameCount = FRAMECOUNT_INVALID;
|
||||
entry = decoded_entry;
|
||||
}
|
||||
else
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
s32 src_x, src_y, dst_x, dst_y;
|
||||
|
||||
// Note for understanding the math:
|
||||
// Normal textures can't be strided, so the 2 missing cases with src_x > 0 don't exist
|
||||
if (entry->addr >= entry_to_update->addr)
|
||||
{
|
||||
s32 block_offset = (entry->addr - entry_to_update->addr) / tex_info.bytes_per_block;
|
||||
s32 block_x = block_offset % numBlocksX;
|
||||
s32 block_y = block_offset / numBlocksX;
|
||||
src_x = 0;
|
||||
src_y = 0;
|
||||
dst_x = block_x * tex_info.block_width;
|
||||
dst_y = block_y * tex_info.block_height;
|
||||
}
|
||||
else
|
||||
{
|
||||
s32 block_offset = (entry_to_update->addr - entry->addr) / tex_info.bytes_per_block;
|
||||
s32 block_x = block_offset % numBlocksX;
|
||||
s32 block_y = block_offset / numBlocksX;
|
||||
src_x = block_x * tex_info.block_width;
|
||||
src_y = block_y * tex_info.block_height;
|
||||
dst_x = 0;
|
||||
dst_y = 0;
|
||||
}
|
||||
|
||||
u32 copy_width = std::min(entry->native_width - src_x, entry_to_update->native_width - dst_x);
|
||||
u32 copy_height =
|
||||
std::min(entry->native_height - src_y, entry_to_update->native_height - dst_y);
|
||||
|
||||
// If one of the textures is scaled, scale both with the current efb scaling factor
|
||||
if (entry_to_update->native_width != entry_to_update->GetWidth() ||
|
||||
entry_to_update->native_height != entry_to_update->GetHeight() ||
|
||||
entry->native_width != entry->GetWidth() || entry->native_height != entry->GetHeight())
|
||||
{
|
||||
ScaleTextureCacheEntryTo(entry_to_update,
|
||||
g_renderer->EFBToScaledX(entry_to_update->native_width),
|
||||
g_renderer->EFBToScaledY(entry_to_update->native_height));
|
||||
ScaleTextureCacheEntryTo(entry, g_renderer->EFBToScaledX(entry->native_width),
|
||||
g_renderer->EFBToScaledY(entry->native_height));
|
||||
|
||||
src_x = g_renderer->EFBToScaledX(src_x);
|
||||
src_y = g_renderer->EFBToScaledY(src_y);
|
||||
dst_x = g_renderer->EFBToScaledX(dst_x);
|
||||
dst_y = g_renderer->EFBToScaledY(dst_y);
|
||||
copy_width = g_renderer->EFBToScaledX(copy_width);
|
||||
copy_height = g_renderer->EFBToScaledY(copy_height);
|
||||
}
|
||||
|
||||
MathUtil::Rectangle<int> srcrect, dstrect;
|
||||
srcrect.left = src_x;
|
||||
srcrect.top = src_y;
|
||||
srcrect.right = (src_x + copy_width);
|
||||
srcrect.bottom = (src_y + copy_height);
|
||||
|
||||
dstrect.left = dst_x;
|
||||
dstrect.top = dst_y;
|
||||
dstrect.right = (dst_x + copy_width);
|
||||
dstrect.bottom = (dst_y + copy_height);
|
||||
|
||||
for (u32 layer = 0; layer < entry->texture->GetConfig().layers; layer++)
|
||||
{
|
||||
entry_to_update->texture->CopyRectangleFromTexture(entry->texture.get(), srcrect, layer, 0,
|
||||
dstrect, layer, 0);
|
||||
}
|
||||
updated_entry = true;
|
||||
|
||||
if (tex_info.is_palette_texture)
|
||||
{
|
||||
// Remove the temporary converted texture, it won't be used anywhere else
|
||||
// TODO: It would be nice to convert and copy in one step, but this code path isn't common
|
||||
InvalidateTexture(GetTexCacheIter(entry));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Link the two textures together, so we won't apply this partial update again
|
||||
entry->CreateReference(entry_to_update);
|
||||
// Mark the texture update as used, as if it was loaded directly
|
||||
entry->frameCount = FRAMECOUNT_INVALID;
|
||||
}
|
||||
}
|
||||
|
||||
return updated_entry;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue