VideoCommon: refactor texture cache frame buffer copies, in preparation for a future enhancement

This commit is contained in:
iwubcode
2026-10-04 01:54:15 -05:00
parent eb236466c4
commit e051e57251
5 changed files with 461 additions and 316 deletions
@@ -10,8 +10,8 @@ namespace Null
class TextureCache final : public TextureCacheBase
{
protected:
void CopyEFB(AbstractStagingTexture* dst, const EFBCopyParams& params, u32 native_width,
u32 bytes_per_row, u32 num_blocks_y, u32 memory_stride,
void CopyEFB(AbstractStagingTexture* dst, FramebufferManager*, const EFBCopyParams& params,
u32 native_width, u32 bytes_per_row, u32 num_blocks_y, u32 memory_stride,
const MathUtil::Rectangle<int>& src_rect, bool scale_by_half, bool linear_filter,
float y_scale, float gamma, bool clamp_top, bool clamp_bottom,
const std::array<u32, 3>& filter_coefficients) override
@@ -10,8 +10,8 @@ namespace SW
class TextureCache : public TextureCacheBase
{
protected:
void CopyEFB(AbstractStagingTexture* dst, const EFBCopyParams& params, u32 native_width,
u32 bytes_per_row, u32 num_blocks_y, u32 memory_stride,
void CopyEFB(AbstractStagingTexture* dst, FramebufferManager*, const EFBCopyParams& params,
u32 native_width, u32 bytes_per_row, u32 num_blocks_y, u32 memory_stride,
const MathUtil::Rectangle<int>& src_rect, bool scale_by_half, bool linear_filter,
float y_scale, float gamma, bool clamp_top, bool clamp_bottom,
const std::array<u32, 3>& filter_coefficients) override
+30 -13
View File
@@ -300,17 +300,31 @@ static void BPWritten(PixelShaderManager& pixel_shader_manager, XFStateManager&
if (destStride != 0)
{
// bpmem.zcontrol.pixel_format to PixelFormat::Z24 is when the game wants to copy from
// ZBuffer (Zbuffer uses 24-bit Format)
bool is_depth_copy = bpmem.zcontrol.pixel_format == PixelFormat::Z24;
FramebufferCopyRawData framebuffer_copy_data{
.dest_address = destAddr,
.copy_width = copy_width,
.dest_stride = destStride,
.is_depth_format = is_depth_copy,
.gamma = s_gammaLUT[PE_copy.gamma],
.src_rect = srcRect,
.clamp_top = bpmem.triggerEFBCopy.clamp_top,
.clamp_bottom = bpmem.triggerEFBCopy.clamp_bottom,
.filter_coefficients = bpmem.copyfilter.GetCoefficients()};
// Check if we are to copy from the EFB or draw to the XFB
if (PE_copy.copy_to_xfb == 0)
{
// bpmem.zcontrol.pixel_format to PixelFormat::Z24 is when the game wants to copy from
// ZBuffer (Zbuffer uses 24-bit Format)
bool is_depth_copy = bpmem.zcontrol.pixel_format == PixelFormat::Z24;
g_texture_cache->CopyRenderTargetToTexture(
destAddr, PE_copy.tp_realFormat(), copy_width, copy_height, destStride, is_depth_copy,
srcRect, PE_copy.intensity_fmt && PE_copy.auto_conv, PE_copy.half_scale, 1.0f,
s_gammaLUT[PE_copy.gamma], bpmem.triggerEFBCopy.clamp_top,
bpmem.triggerEFBCopy.clamp_bottom, bpmem.copyfilter.GetCoefficients());
framebuffer_copy_data.efbcopy_format = PE_copy.tp_realFormat();
framebuffer_copy_data.is_intensity_format = PE_copy.intensity_fmt && PE_copy.auto_conv;
framebuffer_copy_data.half_scale = PE_copy.half_scale;
framebuffer_copy_data.y_scale = 1.0;
framebuffer_copy_data.copy_height = copy_height;
auto resolved_data = g_texture_cache->ResolveFramebufferCopyData(framebuffer_copy_data);
g_texture_cache->CopyRenderTargetToTexture(std::ref(resolved_data));
}
else
{
@@ -335,11 +349,14 @@ static void BPWritten(PixelShaderManager& pixel_shader_manager, XFStateManager&
destAddr, srcRect.left, srcRect.top, srcRect.right, srcRect.bottom,
bpmem.copyTexSrcWH.x + 1, destStride, height, yScale);
bool is_depth_copy = bpmem.zcontrol.pixel_format == PixelFormat::Z24;
g_texture_cache->CopyRenderTargetToTexture(
destAddr, EFBCopyFormat::XFB, copy_width, height, destStride, is_depth_copy, srcRect,
false, false, yScale, s_gammaLUT[PE_copy.gamma], bpmem.triggerEFBCopy.clamp_top,
bpmem.triggerEFBCopy.clamp_bottom, bpmem.copyfilter.GetCoefficients());
framebuffer_copy_data.efbcopy_format = EFBCopyFormat::XFB;
framebuffer_copy_data.is_intensity_format = false;
framebuffer_copy_data.half_scale = false;
framebuffer_copy_data.y_scale = yScale;
framebuffer_copy_data.copy_height = height;
auto resolved_data = g_texture_cache->ResolveFramebufferCopyData(framebuffer_copy_data);
g_texture_cache->CopyRenderTargetToTexture(std::ref(resolved_data));
auto& system = Core::System::GetInstance();
+366 -291
View File
@@ -2145,11 +2145,7 @@ bool TextureCacheBase::CopyFilterCanOverflow(const std::array<u32, 3>& coefficie
return coefficients[0] + coefficients[1] + coefficients[2] >= 128;
}
void TextureCacheBase::CopyRenderTargetToTexture(
u32 dstAddr, EFBCopyFormat dstFormat, u32 width, u32 height, u32 dstStride, bool is_depth_copy,
const MathUtil::Rectangle<int>& srcRect, bool isIntensity, bool scaleByHalf, float y_scale,
float gamma, bool clamp_top, bool clamp_bottom,
const CopyFilterCoefficients::Values& filter_coefficients)
void TextureCacheBase::CopyRenderTargetToTexture(FramebufferCopyResolvedData& resolved_data)
{
// Emulation methods:
//
@@ -2209,71 +2205,25 @@ void TextureCacheBase::CopyRenderTargetToTexture(
//
// Disadvantage of all methods: Calling this function requires the GPU to perform a pipeline flush
// which stalls any further CPU processing.
const bool is_xfb_copy = !is_depth_copy && !isIntensity && dstFormat == EFBCopyFormat::XFB;
bool copy_to_vram = g_backend_info.bSupportsCopyToVram && !g_ActiveConfig.bDisableCopyToVRAM;
bool copy_to_ram =
!(is_xfb_copy ? g_ActiveConfig.bSkipXFBCopyToRam : g_ActiveConfig.bSkipEFBCopyToRam) ||
!copy_to_vram;
// tex_w and tex_h are the native size of the texture in the GC memory.
// The size scaled_* represents the emulated texture. Those differ
// because of upscaling and because of yscaling of XFB copies.
// For the latter, we keep the EFB resolution for the virtual XFB blit.
u32 tex_w = width;
u32 tex_h = height;
u32 scaled_tex_w = g_framebuffer_manager->EFBToScaledX(width);
u32 scaled_tex_h = g_framebuffer_manager->EFBToScaledY(height);
if (scaleByHalf)
{
tex_w /= 2;
tex_h /= 2;
scaled_tex_w /= 2;
scaled_tex_h /= 2;
}
if (!is_xfb_copy && !g_ActiveConfig.bCopyEFBScaled)
{
// No upscaling
scaled_tex_w = tex_w;
scaled_tex_h = tex_h;
}
// Get the base (in memory) format of this efb copy.
TextureFormat baseFormat = TexDecoder_GetEFBCopyBaseFormat(dstFormat);
u32 blockH = TexDecoder_GetBlockHeightInTexels(baseFormat);
const u32 blockW = TexDecoder_GetBlockWidthInTexels(baseFormat);
// Round up source height to multiple of block size
u32 actualHeight = Common::AlignUp(tex_h, blockH);
const u32 actualWidth = Common::AlignUp(tex_w, blockW);
u32 num_blocks_y = actualHeight / blockH;
const u32 num_blocks_x = actualWidth / blockW;
// RGBA takes two cache lines per block; all others take one
const u32 bytes_per_block = baseFormat == TextureFormat::RGBA8 ? 64 : 32;
const u32 bytes_per_row = num_blocks_x * bytes_per_block;
const u32 covered_range = num_blocks_y * dstStride;
auto& system = Core::System::GetInstance();
auto& memory = system.GetMemory();
u8* dst = memory.GetPointerForRange(dstAddr, covered_range);
u8* dst = memory.GetPointerForRange(resolved_data.source_data.dest_address,
resolved_data.covered_range);
if (dst == nullptr)
{
ERROR_LOG_FMT(VIDEO, "Trying to copy from EFB to invalid address {:#010x}", dstAddr);
ERROR_LOG_FMT(VIDEO, "Trying to copy from EFB to invalid address {:#010x}",
resolved_data.source_data.dest_address);
return;
}
if (g_ActiveConfig.bGraphicMods)
{
FBInfo info;
info.m_width = tex_w;
info.m_height = tex_h;
info.m_texture_format = baseFormat;
if (is_xfb_copy)
info.m_width = resolved_data.memory_width;
info.m_height = resolved_data.memory_height;
info.m_texture_format = resolved_data.memory_format;
if (resolved_data.is_xfb)
{
for (const auto& action : g_graphics_mod_manager->GetXFBActions(info))
{
@@ -2283,253 +2233,54 @@ void TextureCacheBase::CopyRenderTargetToTexture(
else
{
bool skip = false;
GraphicsModActionData::PreEFB efb{tex_w, tex_h, &skip, &scaled_tex_w, &scaled_tex_h};
GraphicsModActionData::PreEFB efb{resolved_data.memory_width, resolved_data.memory_height,
&skip, &resolved_data.scaled_memory_width,
&resolved_data.scaled_memory_height};
for (const auto& action : g_graphics_mod_manager->GetEFBActions(info))
{
action->BeforeEFB(&efb);
}
if (skip == true)
{
if (copy_to_ram)
UninitializeEFBMemory(dst, dstStride, bytes_per_row, num_blocks_y);
if (resolved_data.copy_to_ram)
{
UninitializeEFBMemory(dst, resolved_data.source_data.dest_stride,
resolved_data.bytes_per_row, resolved_data.num_blocks_y);
}
return;
}
}
}
if (dstStride < bytes_per_row)
{
// This kind of efb copy results in a scrambled image.
// I'm pretty sure no game actually wants to do this, it might be caused by a
// programming bug in the game, or a CPU/Bounding box emulation issue with dolphin.
// The copy_to_ram code path above handles this "correctly" and scrambles the image
// but the copy_to_vram code path just saves and uses unscrambled texture instead.
// To avoid a "incorrect" result, we simply skip doing the copy_to_vram code path
// so if the game does try to use the scrambled texture, dolphin will grab the scrambled
// texture (or black if copy_to_ram is also disabled) out of ram.
ERROR_LOG_FMT(VIDEO, "Memory stride too small ({} < {})", dstStride, bytes_per_row);
copy_to_vram = false;
}
// We also linear filtering for both box filtering and downsampling higher resolutions to 1x.
// TODO: This only produces perfect downsampling for 2x IR, other resolutions will need more
// complex down filtering to average all pixels and produce the correct result.
const bool linear_filter =
!is_depth_copy &&
(scaleByHalf || g_framebuffer_manager->GetEFBScale() != 1 || y_scale > 1.0f);
RcTcacheEntry entry;
if (copy_to_vram)
if (resolved_data.copy_to_vram)
{
// create the texture
const TextureConfig config(scaled_tex_w, scaled_tex_h, 1, g_framebuffer_manager->GetEFBLayers(),
1, AbstractTextureFormat::RGBA8, AbstractTextureFlag_RenderTarget,
AbstractTextureType::Texture_2DArray);
entry = AllocateCacheEntry(config);
entry = SetupCopyEntry(g_framebuffer_manager.get(), resolved_data);
if (entry)
{
entry->SetGeneralParameters(dstAddr, 0, baseFormat, is_xfb_copy);
entry->SetDimensions(tex_w, tex_h, 1);
entry->frameCount = FRAMECOUNT_INVALID;
if (is_xfb_copy)
{
entry->should_force_safe_hashing = is_xfb_copy;
entry->SetXfbCopy(dstStride);
}
else
{
entry->SetEfbCopy(dstStride);
}
entry->may_have_overlapping_textures = false;
entry->is_custom_tex = false;
CopyEFBToCacheEntry(entry, is_depth_copy, srcRect, scaleByHalf, linear_filter, dstFormat,
isIntensity, gamma, clamp_top, clamp_bottom,
GetVRAMCopyFilterCoefficients(filter_coefficients));
if (g_ActiveConfig.bGraphicMods)
{
FBInfo info;
info.m_width = tex_w;
info.m_height = tex_h;
info.m_texture_format = baseFormat;
if (!is_xfb_copy)
{
GraphicsModActionData::PostEFB efb;
for (const auto& action : g_graphics_mod_manager->GetEFBActions(info))
{
action->AfterEFB(&efb);
if (efb.material)
{
ApplyMaterialToCacheEntry(*efb.material, entry.get());
}
}
}
}
if (is_xfb_copy && (g_ActiveConfig.bDumpXFBTarget || g_ActiveConfig.bGraphicMods))
{
const std::string id = fmt::format("{}x{}", tex_w, tex_h);
if (g_ActiveConfig.bGraphicMods)
{
entry->texture_info_name = fmt::format("{}_{}", XFB_DUMP_PREFIX, id);
}
if (g_ActiveConfig.bDumpXFBTarget)
{
entry->texture->Save(fmt::format("{}{}_n{:06}_{}.png",
File::GetUserPath(D_DUMPTEXTURES_IDX), XFB_DUMP_PREFIX,
xfb_count++, id),
0);
}
}
else if (g_ActiveConfig.bDumpEFBTarget || g_ActiveConfig.bGraphicMods)
{
const std::string id = fmt::format("{}x{}_{}", tex_w, tex_h, static_cast<int>(baseFormat));
if (g_ActiveConfig.bGraphicMods)
{
entry->texture_info_name = fmt::format("{}_{}", EFB_DUMP_PREFIX, id);
}
if (g_ActiveConfig.bDumpEFBTarget)
{
static int efb_count = 0;
entry->texture->Save(fmt::format("{}{}_n{:06}_{}.png",
File::GetUserPath(D_DUMPTEXTURES_IDX), EFB_DUMP_PREFIX,
efb_count++, id),
0);
}
}
}
CopyRenderTargetToVRam(entry, g_framebuffer_manager.get(), resolved_data);
}
if (copy_to_ram)
if (resolved_data.copy_to_ram)
{
const std::array<u32, 3> coefficients = GetRAMCopyFilterCoefficients(filter_coefficients);
PixelFormat srcFormat = bpmem.zcontrol.pixel_format;
EFBCopyParams format(srcFormat, dstFormat, is_depth_copy, isIntensity,
AllCopyFilterCoefsNeeded(coefficients),
CopyFilterCanOverflow(coefficients), gamma != 1.0);
std::unique_ptr<AbstractStagingTexture> staging_texture = GetEFBCopyStagingTexture();
if (staging_texture)
{
CopyEFB(staging_texture.get(), format, tex_w, bytes_per_row, num_blocks_y, dstStride, srcRect,
scaleByHalf, linear_filter, y_scale, gamma, clamp_top, clamp_bottom, coefficients);
// We can't defer if there is no VRAM copy (since we need to update the hash).
if (!copy_to_vram || !g_ActiveConfig.bDeferEFBCopies)
{
// Immediately flush it.
WriteEFBCopyToRAM(dst, bytes_per_row / sizeof(u32), num_blocks_y, dstStride,
std::move(staging_texture));
}
else
{
// Defer the flush until later.
entry->pending_efb_copy = std::move(staging_texture);
entry->pending_efb_copy_width = bytes_per_row / sizeof(u32);
entry->pending_efb_copy_height = num_blocks_y;
m_pending_efb_copies.push_back(entry);
}
}
const bool defer_write = g_ActiveConfig.bDeferEFBCopies;
CopyRenderTargetToRam(entry, g_framebuffer_manager.get(), dst, resolved_data, defer_write);
}
else
{
if (is_xfb_copy)
if (resolved_data.is_xfb)
{
UninitializeXFBMemory(dst, dstStride, bytes_per_row, num_blocks_y);
UninitializeXFBMemory(dst, resolved_data.source_data.dest_stride, resolved_data.bytes_per_row,
resolved_data.num_blocks_y);
}
else
{
UninitializeEFBMemory(dst, dstStride, bytes_per_row, num_blocks_y);
UninitializeEFBMemory(dst, resolved_data.source_data.dest_stride, resolved_data.bytes_per_row,
resolved_data.num_blocks_y);
}
}
// Invalidate all textures, if they are either fully overwritten by our efb copy, or if they
// have a different stride than our efb copy. Partly overwritten textures with the same stride
// as our efb copy are marked to check them for partial texture updates.
// TODO: The logic to detect overlapping strided efb copies is not 100% accurate.
bool strided_efb_copy = dstStride != bytes_per_row;
auto iter = FindOverlappingTextures(dstAddr, covered_range);
while (iter.first != iter.second)
{
RcTcacheEntry& overlapping_entry = iter.first->second;
if (overlapping_entry->addr == dstAddr && overlapping_entry->is_xfb_copy)
{
for (auto& reference : overlapping_entry->references)
{
reference->reference_changed = true;
}
}
if (overlapping_entry->OverlapsMemoryRange(dstAddr, covered_range))
{
u32 overlap_range = std::min(overlapping_entry->addr + overlapping_entry->size_in_bytes,
dstAddr + covered_range) -
std::max(overlapping_entry->addr, dstAddr);
if (!copy_to_vram || overlapping_entry->memory_stride != dstStride ||
(!strided_efb_copy && overlapping_entry->size_in_bytes == overlap_range) ||
(strided_efb_copy && overlapping_entry->size_in_bytes == overlap_range &&
overlapping_entry->addr == dstAddr))
{
// Pending EFB copies which are completely covered by this new copy can simply be tossed,
// instead of having to flush them later on, since this copy will write over everything.
iter.first = InvalidateTexture(iter.first, true);
continue;
}
// We don't want to change the may_have_overlapping_textures flag on XFB container entries
// because otherwise they can't be re-used/updated, leaking textures for several frames.
if (!overlapping_entry->is_xfb_container)
overlapping_entry->may_have_overlapping_textures = true;
// There are cases (Rogue Squadron 2 / Texas Holdem on Wiiware) where
// for xfb copies the textures overlap which causes the hash of the first copy
// to be different (from when it was originally created). This has no implications
// for XFB2Tex because the underlying memory doesn't change (dummy values) but
// can affect XFB2Ram when we compare the texture cache copy hash with the
// newly computed hash
// By calculating the hash when we receive overlapping xfbs, we are able
// to mitigate this
if (overlapping_entry->is_xfb_copy && copy_to_ram)
{
overlapping_entry->hash = overlapping_entry->CalculateHash();
}
// Do not load textures by hash, if they were at least partly overwritten by an efb copy.
// In this case, comparing the hash is not enough to check, if two textures are identical.
if (overlapping_entry->textures_by_hash_iter != m_textures_by_hash.end())
{
m_textures_by_hash.erase(overlapping_entry->textures_by_hash_iter);
overlapping_entry->textures_by_hash_iter = m_textures_by_hash.end();
}
}
++iter.first;
}
if (OpcodeDecoder::g_record_fifo_data)
{
// Mark the memory behind this efb copy as dynamically generated for the Fifo log
u32 address = dstAddr;
for (u32 i = 0; i < num_blocks_y; i++)
{
Core::System::GetInstance().GetFifoRecorder().UseMemory(address, bytes_per_row,
MemoryUpdate::Type::TextureMap, true);
address += dstStride;
}
}
// Even if the copy is deferred, still compute the hash. This way if the copy is used as a texture
// in a subsequent draw before it is flushed, it will have the same hash.
if (entry)
{
const u64 hash = entry->CalculateHash();
entry->SetHashes(hash, hash);
m_textures_by_address.emplace(dstAddr, std::move(entry));
}
FinalizeFramebufferCopy(entry, resolved_data);
}
void TextureCacheBase::FlushEFBCopies()
@@ -2931,15 +2682,16 @@ void TextureCacheBase::CopyEFBToCacheEntry(RcTcacheEntry& entry, bool is_depth_c
entry->texture->FinishedRendering();
}
void TextureCacheBase::CopyEFB(AbstractStagingTexture* dst, const EFBCopyParams& params,
u32 native_width, u32 bytes_per_row, u32 num_blocks_y,
u32 memory_stride, const MathUtil::Rectangle<int>& src_rect,
bool scale_by_half, bool linear_filter, float y_scale, float gamma,
bool clamp_top, bool clamp_bottom,
const std::array<u32, 3>& filter_coefficients)
void TextureCacheBase::CopyEFB(AbstractStagingTexture* dst,
FramebufferManager* frame_buffer_manager,
const EFBCopyParams& params, u32 native_width, u32 bytes_per_row,
u32 num_blocks_y, u32 memory_stride,
const MathUtil::Rectangle<int>& src_rect, bool scale_by_half,
bool linear_filter, float y_scale, float gamma, bool clamp_top,
bool clamp_bottom, const std::array<u32, 3>& filter_coefficients)
{
// Flush EFB pokes first, as they're expected to be included.
g_framebuffer_manager->FlushEFBPokes();
frame_buffer_manager->FlushEFBPokes();
// Get the pipeline which we will be using. If the compilation failed, this will be null.
const AbstractPipeline* copy_pipeline = g_shader_cache->GetEFBCopyToRAMPipeline(params);
@@ -2949,12 +2701,12 @@ void TextureCacheBase::CopyEFB(AbstractStagingTexture* dst, const EFBCopyParams&
return;
}
const auto scaled_src_rect = g_framebuffer_manager->ConvertEFBRectangle(src_rect);
const auto scaled_src_rect = frame_buffer_manager->ConvertEFBRectangle(src_rect);
const auto framebuffer_rect = g_gfx->ConvertFramebufferRectangle(
scaled_src_rect, g_framebuffer_manager->GetEFBFramebuffer());
scaled_src_rect, frame_buffer_manager->GetEFBFramebuffer());
AbstractTexture* src_texture =
params.depth ? g_framebuffer_manager->ResolveEFBDepthTexture(framebuffer_rect) :
g_framebuffer_manager->ResolveEFBColorTexture(framebuffer_rect);
params.depth ? frame_buffer_manager->ResolveEFBDepthTexture(framebuffer_rect) :
frame_buffer_manager->ResolveEFBColorTexture(framebuffer_rect);
g_gfx->BeginUtilityDrawing();
src_texture->FinishedRendering();
@@ -2971,7 +2723,7 @@ void TextureCacheBase::CopyEFB(AbstractStagingTexture* dst, const EFBCopyParams&
u32 padding;
};
Uniforms encoder_params;
const u32 efb_height = g_framebuffer_manager->GetEFBHeight();
const u32 efb_height = frame_buffer_manager->GetEFBHeight();
const float rcp_efb_height = 1.0f / static_cast<float>(efb_height);
encoder_params.position_uniform[0] = src_rect.left;
encoder_params.position_uniform[1] = src_rect.top;
@@ -3007,6 +2759,9 @@ void TextureCacheBase::CopyEFB(AbstractStagingTexture* dst, const EFBCopyParams&
dst->CopyFromTexture(m_efb_encoding_texture.get(), encode_rect, 0, 0, encode_rect);
g_gfx->EndUtilityDrawing();
// Rebind to a non global version...
frame_buffer_manager->BindEFBFramebuffer();
// Flush if there's sufficient draws between this copy and the last.
g_vertex_manager->OnEFBCopyToRAM();
}
@@ -3288,3 +3043,323 @@ TextureCacheBase::TexPoolEntry::TexPoolEntry(std::unique_ptr<AbstractTexture> te
: texture(std::move(tex)), framebuffer(std::move(fb))
{
}
FramebufferCopyResolvedData
TextureCacheBase::ResolveFramebufferCopyData(const FramebufferCopyRawData& copy_data)
{
FramebufferCopyResolvedData result;
result.is_xfb = !copy_data.is_depth_format && !copy_data.is_intensity_format &&
copy_data.efbcopy_format == EFBCopyFormat::XFB;
result.copy_to_vram = g_backend_info.bSupportsCopyToVram && !g_ActiveConfig.bDisableCopyToVRAM;
result.copy_to_ram =
!(result.is_xfb ? g_ActiveConfig.bSkipXFBCopyToRam : g_ActiveConfig.bSkipEFBCopyToRam) ||
!result.copy_to_vram;
// memory_width and memory_height are the native size of the texture in the GC memory.
// The size scaled_* represents the emulated texture. Those differ
// because of upscaling and because of yscaling of XFB copies.
// For the latter, we keep the EFB resolution for the virtual XFB blit.
result.memory_width = copy_data.copy_width;
result.memory_height = copy_data.copy_height;
result.scaled_memory_width = g_framebuffer_manager->EFBToScaledX(copy_data.copy_width);
result.scaled_memory_height = g_framebuffer_manager->EFBToScaledY(copy_data.copy_height);
if (copy_data.half_scale)
{
result.memory_width /= 2;
result.memory_height /= 2;
result.scaled_memory_width /= 2;
result.scaled_memory_height /= 2;
}
if (!result.is_xfb && !g_ActiveConfig.bCopyEFBScaled)
{
// No upscaling
result.scaled_memory_width = result.memory_width;
result.scaled_memory_height = result.memory_height;
}
// Get the base (in memory) format of this efb copy.
result.memory_format = TexDecoder_GetEFBCopyBaseFormat(copy_data.efbcopy_format);
u32 blockH = TexDecoder_GetBlockHeightInTexels(result.memory_format);
const u32 blockW = TexDecoder_GetBlockWidthInTexels(result.memory_format);
// Round up source height to multiple of block size
u32 actualHeight = Common::AlignUp(result.memory_height, blockH);
const u32 actualWidth = Common::AlignUp(result.memory_width, blockW);
result.num_blocks_y = actualHeight / blockH;
const u32 num_blocks_x = actualWidth / blockW;
// RGBA takes two cache lines per block; all others take one
const u32 bytes_per_block = result.memory_format == TextureFormat::RGBA8 ? 64 : 32;
result.bytes_per_row = num_blocks_x * bytes_per_block;
result.covered_range = result.num_blocks_y * copy_data.dest_stride;
// We also linear filtering for both box filtering and downsampling higher resolutions to 1x.
// TODO: This only produces perfect downsampling for 2x IR, other resolutions will need more
// complex down filtering to average all pixels and produce the correct result.
result.linear_filter = !copy_data.is_depth_format &&
(copy_data.half_scale || g_framebuffer_manager->GetEFBScale() != 1 ||
copy_data.y_scale > 1.0f);
if (copy_data.dest_stride < result.bytes_per_row)
{
// This kind of efb copy results in a scrambled image.
// I'm pretty sure no game actually wants to do this, it might be caused by a
// programming bug in the game, or a CPU/Bounding box emulation issue with dolphin.
// The copy_to_ram code path above handles this "correctly" and scrambles the image
// but the copy_to_vram code path just saves and uses unscrambled texture instead.
// To avoid a "incorrect" result, we simply skip doing the copy_to_vram code path
// so if the game does try to use the scrambled texture, dolphin will grab the scrambled
// texture (or black if copy_to_ram is also disabled) out of ram.
ERROR_LOG_FMT(VIDEO, "Memory stride too small ({} < {})", copy_data.dest_stride,
result.bytes_per_row);
result.copy_to_vram = false;
}
result.pixel_format = bpmem.zcontrol.pixel_format;
// Copy the remaining fields from the raw data:
result.source_data = copy_data;
return result;
}
RcTcacheEntry TextureCacheBase::SetupCopyEntry(FramebufferManager* frame_buffer_manager,
const FramebufferCopyResolvedData& resolved_data)
{
// create the texture
const TextureConfig config(resolved_data.scaled_memory_width, resolved_data.scaled_memory_height,
1, frame_buffer_manager->GetEFBLayers(), 1,
AbstractTextureFormat::RGBA8, AbstractTextureFlag_RenderTarget,
AbstractTextureType::Texture_2DArray);
RcTcacheEntry entry = AllocateCacheEntry(config);
if (entry)
{
entry->SetGeneralParameters(resolved_data.source_data.dest_address, 0,
resolved_data.memory_format, resolved_data.is_xfb);
entry->SetDimensions(resolved_data.memory_width, resolved_data.memory_height, 1);
entry->frameCount = FRAMECOUNT_INVALID;
if (resolved_data.is_xfb)
{
entry->should_force_safe_hashing = resolved_data.is_xfb;
entry->SetXfbCopy(resolved_data.source_data.dest_stride);
}
else
{
entry->SetEfbCopy(resolved_data.source_data.dest_stride);
}
entry->may_have_overlapping_textures = false;
entry->is_custom_tex = false;
if (g_ActiveConfig.bGraphicMods)
{
if (resolved_data.is_xfb)
{
const std::string id =
fmt::format("{}x{}", resolved_data.memory_width, resolved_data.memory_height);
entry->texture_info_name = fmt::format("{}_{}", XFB_DUMP_PREFIX, id);
}
else
{
const std::string id =
fmt::format("{}x{}_{}", resolved_data.memory_width, resolved_data.memory_height,
static_cast<int>(resolved_data.memory_format));
entry->texture_info_name = fmt::format("{}_{}", EFB_DUMP_PREFIX, id);
}
}
}
return entry;
}
void TextureCacheBase::CopyRenderTargetToVRam(RcTcacheEntry& entry,
FramebufferManager* frame_buffer_manager,
const FramebufferCopyResolvedData& resolved_data)
{
CopyEFBToCacheEntry(entry, resolved_data.source_data.is_depth_format,
resolved_data.source_data.src_rect, resolved_data.source_data.half_scale,
resolved_data.linear_filter, resolved_data.source_data.efbcopy_format,
resolved_data.source_data.is_intensity_format,
resolved_data.source_data.gamma, resolved_data.source_data.clamp_top,
resolved_data.source_data.clamp_bottom,
GetVRAMCopyFilterCoefficients(resolved_data.source_data.filter_coefficients));
if (g_ActiveConfig.bGraphicMods)
{
FBInfo info;
info.m_width = resolved_data.memory_width;
info.m_height = resolved_data.memory_height;
info.m_texture_format = resolved_data.memory_format;
if (!resolved_data.is_xfb)
{
GraphicsModActionData::PostEFB efb;
for (const auto& action : g_graphics_mod_manager->GetEFBActions(info))
{
action->AfterEFB(&efb);
if (efb.material)
{
ApplyMaterialToCacheEntry(*efb.material, entry.get());
}
}
}
}
if (g_ActiveConfig.bDumpXFBTarget)
{
const std::string id =
fmt::format("{}x{}", resolved_data.memory_width, resolved_data.memory_height);
entry->texture->Save(fmt::format("{}{}_n{:06}_{}.png", File::GetUserPath(D_DUMPTEXTURES_IDX),
XFB_DUMP_PREFIX, xfb_count++, id),
0);
}
else if (g_ActiveConfig.bDumpEFBTarget)
{
const std::string id =
fmt::format("{}x{}_{}", resolved_data.memory_width, resolved_data.memory_height,
static_cast<int>(resolved_data.memory_format));
static int efb_count = 0;
entry->texture->Save(fmt::format("{}{}_n{:06}_{}.png", File::GetUserPath(D_DUMPTEXTURES_IDX),
EFB_DUMP_PREFIX, efb_count++, id),
0);
}
}
void TextureCacheBase::CopyRenderTargetToRam(RcTcacheEntry& entry,
FramebufferManager* frame_buffer_manager, u8* dst,
const FramebufferCopyResolvedData& resolved_data,
bool defer_write)
{
std::unique_ptr<AbstractStagingTexture> staging_texture = GetEFBCopyStagingTexture();
if (staging_texture)
{
const std::array<u32, 3> coefficients =
GetRAMCopyFilterCoefficients(resolved_data.source_data.filter_coefficients);
EFBCopyParams format(
resolved_data.pixel_format, resolved_data.source_data.efbcopy_format,
resolved_data.source_data.is_depth_format, resolved_data.source_data.is_intensity_format,
AllCopyFilterCoefsNeeded(coefficients), CopyFilterCanOverflow(coefficients),
resolved_data.source_data.gamma != 1.0);
CopyEFB(staging_texture.get(), frame_buffer_manager, format, resolved_data.memory_width,
resolved_data.bytes_per_row, resolved_data.num_blocks_y,
resolved_data.source_data.dest_stride, resolved_data.source_data.src_rect,
resolved_data.source_data.half_scale, resolved_data.linear_filter,
resolved_data.source_data.y_scale, resolved_data.source_data.gamma,
resolved_data.source_data.clamp_top, resolved_data.source_data.clamp_bottom,
coefficients);
// We can't defer if there is no VRAM copy (since we need to update the hash).
if (!resolved_data.copy_to_vram || !defer_write)
{
// Immediately flush it.
WriteEFBCopyToRAM(dst, resolved_data.bytes_per_row / sizeof(u32), resolved_data.num_blocks_y,
resolved_data.source_data.dest_stride, std::move(staging_texture));
}
else
{
// Defer the flush until later.
entry->pending_efb_copy = std::move(staging_texture);
entry->pending_efb_copy_width = resolved_data.bytes_per_row / sizeof(u32);
entry->pending_efb_copy_height = resolved_data.num_blocks_y;
m_pending_efb_copies.push_back(entry);
}
}
}
void TextureCacheBase::FinalizeFramebufferCopy(RcTcacheEntry& entry,
const FramebufferCopyResolvedData& resolved_data)
{
const u32 covered_range = resolved_data.num_blocks_y * resolved_data.source_data.dest_stride;
// Invalidate all textures, if they are either fully overwritten by our efb copy, or if they
// have a different stride than our efb copy. Partly overwritten textures with the same stride
// as our efb copy are marked to check them for partial texture updates.
// TODO: The logic to detect overlapping strided efb copies is not 100% accurate.
bool strided_efb_copy = resolved_data.source_data.dest_stride != resolved_data.bytes_per_row;
auto iter = FindOverlappingTextures(resolved_data.source_data.dest_address, covered_range);
while (iter.first != iter.second)
{
RcTcacheEntry& overlapping_entry = iter.first->second;
if (overlapping_entry->addr == resolved_data.source_data.dest_address &&
overlapping_entry->is_xfb_copy)
{
for (auto& reference : overlapping_entry->references)
{
reference->reference_changed = true;
}
}
if (overlapping_entry->OverlapsMemoryRange(resolved_data.source_data.dest_address,
covered_range))
{
u32 overlap_range = std::min(overlapping_entry->addr + overlapping_entry->size_in_bytes,
resolved_data.source_data.dest_address + covered_range) -
std::max(overlapping_entry->addr, resolved_data.source_data.dest_address);
if (!resolved_data.copy_to_vram ||
overlapping_entry->memory_stride != resolved_data.source_data.dest_stride ||
(!strided_efb_copy && overlapping_entry->size_in_bytes == overlap_range) ||
(strided_efb_copy && overlapping_entry->size_in_bytes == overlap_range &&
overlapping_entry->addr == resolved_data.source_data.dest_address))
{
// Pending EFB copies which are completely covered by this new copy can simply be tossed,
// instead of having to flush them later on, since this copy will write over everything.
iter.first = InvalidateTexture(iter.first, true);
continue;
}
// We don't want to change the may_have_overlapping_textures flag on XFB container entries
// because otherwise they can't be re-used/updated, leaking textures for several frames.
if (!overlapping_entry->is_xfb_container)
overlapping_entry->may_have_overlapping_textures = true;
// There are cases (Rogue Squadron 2 / Texas Holdem on Wiiware) where
// for xfb copies the textures overlap which causes the hash of the first copy
// to be different (from when it was originally created). This has no implications
// for XFB2Tex because the underlying memory doesn't change (dummy values) but
// can affect XFB2Ram when we compare the texture cache copy hash with the
// newly computed hash
// By calculating the hash when we receive overlapping xfbs, we are able
// to mitigate this
if (overlapping_entry->is_xfb_copy && resolved_data.copy_to_ram)
{
overlapping_entry->hash = overlapping_entry->CalculateHash();
}
// Do not load textures by hash, if they were at least partly overwritten by an efb copy.
// In this case, comparing the hash is not enough to check, if two textures are identical.
if (overlapping_entry->textures_by_hash_iter != m_textures_by_hash.end())
{
m_textures_by_hash.erase(overlapping_entry->textures_by_hash_iter);
overlapping_entry->textures_by_hash_iter = m_textures_by_hash.end();
}
}
++iter.first;
}
if (OpcodeDecoder::g_record_fifo_data)
{
// Mark the memory behind this efb copy as dynamically generated for the Fifo log
u32 address = resolved_data.source_data.dest_address;
for (u32 i = 0; i < resolved_data.num_blocks_y; i++)
{
Core::System::GetInstance().GetFifoRecorder().UseMemory(address, resolved_data.bytes_per_row,
MemoryUpdate::Type::TextureMap, true);
address += resolved_data.source_data.dest_stride;
}
}
// Even if the copy is deferred, still compute the hash. This way if the copy is used as a
// texture in a subsequent draw before it is flushed, it will have the same hash.
if (entry)
{
const u64 hash = entry->CalculateHash();
entry->SetHashes(hash, hash);
m_textures_by_address.emplace(resolved_data.source_data.dest_address, entry);
}
}
+61 -8
View File
@@ -33,6 +33,7 @@
class AbstractFramebuffer;
class AbstractStagingTexture;
class FramebufferManager;
class PointerWrap;
struct SamplerState;
struct VideoConfig;
@@ -98,6 +99,49 @@ struct EFBCopyParams
bool apply_gamma;
};
struct FramebufferCopyRawData
{
u32 dest_address;
u32 copy_width;
u32 copy_height;
u32 dest_stride;
EFBCopyFormat efbcopy_format;
bool is_depth_format;
bool is_intensity_format;
bool half_scale;
float y_scale;
float gamma;
MathUtil::Rectangle<int> src_rect;
bool clamp_top;
bool clamp_bottom;
CopyFilterCoefficients::Values filter_coefficients;
};
struct FramebufferCopyResolvedData
{
// Source details
FramebufferCopyRawData source_data;
PixelFormat pixel_format;
// Derived details
bool is_xfb;
bool copy_to_vram;
bool copy_to_ram;
u32 memory_width;
u32 memory_height;
u32 scaled_memory_width;
u32 scaled_memory_height;
TextureFormat memory_format;
u32 bytes_per_row;
u32 num_blocks_y;
u32 covered_range;
bool linear_filter;
};
template <>
struct fmt::formatter<EFBCopyParams>
{
@@ -285,12 +329,7 @@ public:
MathUtil::Rectangle<int>* display_rect);
virtual void BindTextures(BitSet32 used_textures, const std::array<SamplerState, 8>& samplers);
void CopyRenderTargetToTexture(u32 dstAddr, EFBCopyFormat dstFormat, u32 width, u32 height,
u32 dstStride, bool is_depth_copy,
const MathUtil::Rectangle<int>& srcRect, bool isIntensity,
bool scaleByHalf, float y_scale, float gamma, bool clamp_top,
bool clamp_bottom,
const CopyFilterCoefficients::Values& filter_coefficients);
void CopyRenderTargetToTexture(FramebufferCopyResolvedData& resolved_data);
void ScaleTextureCacheEntryTo(RcTcacheEntry& entry, u32 new_width, u32 new_height);
@@ -314,6 +353,9 @@ public:
static SamplerState GetSamplerState(u32 index, float custom_tex_scale, bool custom_tex,
bool has_arbitrary_mips);
static FramebufferCopyResolvedData
ResolveFramebufferCopyData(const FramebufferCopyRawData& copy_data);
static void RenderPaletteEntry(u32 texel_buffer_offset, const RcTcacheEntry& entry,
AbstractTexture* texture, TLUTFormat tlutfmt);
static void RenderReinterpretEntry(const RcTcacheEntry& entry, AbstractTexture* texture,
@@ -330,8 +372,9 @@ protected:
u32 aligned_height, u32 row_stride, const u8* palette,
TLUTFormat palette_format);
virtual void CopyEFB(AbstractStagingTexture* dst, const EFBCopyParams& params, u32 native_width,
u32 bytes_per_row, u32 num_blocks_y, u32 memory_stride,
virtual void CopyEFB(AbstractStagingTexture* dst, FramebufferManager* frame_buffer_manager,
const EFBCopyParams& params, u32 native_width, u32 bytes_per_row,
u32 num_blocks_y, u32 memory_stride,
const MathUtil::Rectangle<int>& src_rect, bool scale_by_half,
bool linear_filter, float y_scale, float gamma, bool clamp_top,
bool clamp_bottom, const std::array<u32, 3>& filter_coefficients);
@@ -417,6 +460,16 @@ private:
void ApplyMaterialToCacheEntry(const VideoCommon::MaterialResource& material, TCacheEntry* entry);
RcTcacheEntry SetupCopyEntry(FramebufferManager* frame_buffer_manager,
const FramebufferCopyResolvedData& resolved_data);
void CopyRenderTargetToVRam(RcTcacheEntry& entry, FramebufferManager* frame_buffer_manager,
const FramebufferCopyResolvedData& resolved_data);
void CopyRenderTargetToRam(RcTcacheEntry& entry, FramebufferManager* frame_buffer_manager,
u8* dst, const FramebufferCopyResolvedData& resolved_data,
bool defer_write);
void FinalizeFramebufferCopy(RcTcacheEntry& entry,
const FramebufferCopyResolvedData& resolved_data);
// m_textures_by_address is the authoritive version of what's actually "in" the texture cache
// but it's possible for invalidated TCache entries to live on elsewhere
TexAddrCache m_textures_by_address;