Skip to content
Permalink
Browse files

Merge pull request #7950 from stenzek/out-of-range-efb-copies

BPStructs: Gracefully handle out-of-range EFB copies
  • Loading branch information...
stenzek committed Apr 1, 2019
2 parents a2a4694 + 378b605 commit 32e330eb35e63847929ff9cb968d04fe4964e37b
Showing with 37 additions and 6 deletions.
  1. +37 −6 Source/Core/VideoCommon/BPStructs.cpp
@@ -225,13 +225,44 @@ static void BPWritten(const BPCmd& bp)

// Here Width+1 like Height, otherwise some textures are corrupted already since the native
// resolution.
// TODO: What's the behavior of out of bound access?
srcRect.right = static_cast<int>(bpmem.copyTexSrcXY.x + bpmem.copyTexSrcWH.x + 1);
srcRect.bottom = static_cast<int>(bpmem.copyTexSrcXY.y + bpmem.copyTexSrcWH.y + 1);

UPE_Copy PE_copy = bpmem.triggerEFBCopy;
// Since the copy X and Y coordinates/sizes are 10-bit, the game can configure a copy region up
// to 1024x1024. Hardware tests have found that the number of bytes written does not depend on
// the configured stride, instead it is based on the size registers, writing beyond the length
// of a single row. The data written for the pixels which lie outside the EFB bounds does not
// wrap around instead returning different colors based on the pixel format of the EFB. This
// suggests it's not based on coordinates, but instead on memory addresses. The effect of a
// within-bounds size but out-of-bounds offset (e.g. offset 320,0, size 640,480) are the same.

// As it would be difficult to emulate the exact behavior of out-of-bounds reads, instead of
// writing the junk data, we don't write anything to RAM at all for over-sized copies, and clamp
// to the EFB borders for over-offset copies. The arcade virtual console games (e.g. 1942) are
// known for configuring these out-of-range copies.
int copy_width = srcRect.GetWidth();
int copy_height = srcRect.GetHeight();
if (srcRect.right > EFB_WIDTH || srcRect.bottom > EFB_HEIGHT)
{
WARN_LOG(VIDEO, "Oversized EFB copy: %dx%d (offset %d,%d stride %u)", copy_width, copy_height,
srcRect.left, srcRect.top, destStride);

// Adjust the copy size to fit within the EFB. So that we don't end up with a stretched image,
// instead of clamping the source rectangle, we reduce it by the over-sized amount.
if (copy_width > EFB_WIDTH)
{
srcRect.right -= copy_width - EFB_WIDTH;
copy_width = EFB_WIDTH;
}
if (copy_height > EFB_HEIGHT)
{
srcRect.bottom -= copy_height - EFB_HEIGHT;
copy_height = EFB_HEIGHT;
}
}

// Check if we are to copy from the EFB or draw to the XFB
const UPE_Copy PE_copy = bpmem.triggerEFBCopy;
if (PE_copy.copy_to_xfb == 0)
{
// bpmem.zcontrol.pixel_format to PEControl::Z24 is when the game wants to copy from ZBuffer
@@ -240,8 +271,8 @@ static void BPWritten(const BPCmd& bp)
{0, 0, 21, 22, 21, 0, 0}};
bool is_depth_copy = bpmem.zcontrol.pixel_format == PEControl::Z24;
g_texture_cache->CopyRenderTargetToTexture(
destAddr, PE_copy.tp_realFormat(), srcRect.GetWidth(), srcRect.GetHeight(), destStride,
is_depth_copy, srcRect, !!PE_copy.intensity_fmt, !!PE_copy.half_scale, 1.0f, 1.0f,
destAddr, PE_copy.tp_realFormat(), copy_width, copy_height, destStride, is_depth_copy,
srcRect, !!PE_copy.intensity_fmt, !!PE_copy.half_scale, 1.0f, 1.0f,
bpmem.triggerEFBCopy.clamp_top, bpmem.triggerEFBCopy.clamp_bottom, filter_coefficients);
}
else
@@ -271,8 +302,8 @@ static void BPWritten(const BPCmd& bp)

bool is_depth_copy = bpmem.zcontrol.pixel_format == PEControl::Z24;
g_texture_cache->CopyRenderTargetToTexture(
destAddr, EFBCopyFormat::XFB, srcRect.GetWidth(), height, destStride, is_depth_copy,
srcRect, false, false, yScale, s_gammaLUT[PE_copy.gamma], bpmem.triggerEFBCopy.clamp_top,
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());

// This stays in to signal end of a "frame"

0 comments on commit 32e330e

Please sign in to comment.
You can’t perform that action at this time.