Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
145 changes: 59 additions & 86 deletions GPU/Common/TextureCacheCommon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -625,35 +625,27 @@ std::vector<AttachCandidate> TextureCacheCommon::GetFramebufferCandidates(const

std::vector<AttachCandidate> candidates;

RasterChannel channel = Memory::IsDepthTexVRAMAddress(entry.addr) ? RasterChannel::RASTER_DEPTH : RasterChannel::RASTER_COLOR;
if (channel == RasterChannel::RASTER_DEPTH && !gstate_c.Supports(GPU_SUPPORTS_DEPTH_TEXTURE)) {
// Depth texture not supported. Don't try to match it, fall back to the memory behind..
return std::vector<AttachCandidate>();
}

const std::vector<VirtualFramebuffer *> &framebuffers = framebufferManager_->Framebuffers();

for (VirtualFramebuffer *framebuffer : framebuffers) {
FramebufferMatchInfo match = MatchFramebuffer(entry, framebuffer, texAddrOffset, channel);
switch (match.match) {
case FramebufferMatch::VALID:
candidates.push_back(AttachCandidate{ match, entry, framebuffer, channel });
break;
default:
break;
FramebufferMatchInfo match{};
if (MatchFramebuffer(entry, framebuffer, texAddrOffset, RASTER_COLOR, &match)) {
candidates.push_back(AttachCandidate{ match, entry, framebuffer, RASTER_COLOR, framebuffer->colorBindSeq });
}
match = {};
if (gstate_c.Supports(GPU_SUPPORTS_DEPTH_TEXTURE) && MatchFramebuffer(entry, framebuffer, texAddrOffset, RASTER_DEPTH, &match)) {
candidates.push_back(AttachCandidate{ match, entry, framebuffer, RASTER_DEPTH, framebuffer->depthBindSeq });
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we might want to swap these lines:

vfb->depthBindSeq = GetBindSeqCount();

I'd prefer to give color slight preference if they were bound right next to each other (I realize you checked for the overlap case, but I think it's a general thing.)

Also, I wonder if we should be trying to bind a null texture on !gstate_c.Supports(GPU_SUPPORTS_DEPTH_TEXTURE) rather than going to RAM... it feels potentially more consistent. Not sure.

-[Unknown]

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those lines will be separated (and in your preferred order) in #15858 :)

Yes, binding a null texture makes more sense, I'll do that.

}
}

if (candidates.size() > 1) {
bool depth = channel == RasterChannel::RASTER_DEPTH;

std::string cands;
for (auto &candidate : candidates) {
cands += candidate.ToString() + " ";
}

WARN_LOG_REPORT_ONCE(multifbcandidate, G3D, "GetFramebufferCandidates(%s): Multiple (%d) candidate framebuffers. First will be chosen. texaddr: %08x offset: %d (%dx%d stride %d, %s):\n%s",
depth ? "DEPTH" : "COLOR", (int)candidates.size(),
WARN_LOG_REPORT_ONCE(multifbcandidate, G3D, "GetFramebufferCandidates: Multiple (%d) candidate framebuffers. texaddr: %08x offset: %d (%dx%d stride %d, %s):\n%s",
(int)candidates.size(),
entry.addr, texAddrOffset, dimWidth(entry.dim), dimHeight(entry.dim), entry.bufw, GeTextureFormatToString(entry.format),
cands.c_str()
);
Expand All @@ -677,29 +669,22 @@ int TextureCacheCommon::GetBestCandidateIndex(const std::vector<AttachCandidate>
// a comparison function.
for (int i = 0; i < (int)candidates.size(); i++) {
const AttachCandidate &candidate = candidates[i];
int relevancy = 0;
switch (candidate.match.match) {
case FramebufferMatch::VALID:
relevancy += 1000;
break;
default:
break;
}
int relevancy = candidate.seqCount;

// Bonus point for matching stride.
if (candidate.channel == RASTER_COLOR && candidate.fb->fb_stride == candidate.entry.bufw) {
relevancy += 100;
relevancy += 1000;
}

// Bonus points for no offset.
if (candidate.match.xOffset == 0 && candidate.match.yOffset == 0) {
relevancy += 10;
relevancy += 100;
}

if (candidate.channel == RASTER_COLOR && candidate.fb->last_frame_render == gpuStats.numFlips) {
relevancy += 5;
relevancy += 50;
} else if (candidate.channel == RASTER_DEPTH && candidate.fb->last_frame_depth_render == gpuStats.numFlips) {
relevancy += 5;
relevancy += 50;
}

if (relevancy > bestRelevancy) {
Expand Down Expand Up @@ -876,12 +861,25 @@ void TextureCacheCommon::NotifyFramebuffer(VirtualFramebuffer *framebuffer, Fram
}
}

FramebufferMatchInfo TextureCacheCommon::MatchFramebuffer(
bool TextureCacheCommon::MatchFramebuffer(
const TextureDefinition &entry,
VirtualFramebuffer *framebuffer, u32 texaddrOffset, RasterChannel channel) const {
VirtualFramebuffer *framebuffer, u32 texaddrOffset, RasterChannel channel, FramebufferMatchInfo *matchInfo) const {
static const u32 MAX_SUBAREA_Y_OFFSET_SAFE = 32;

uint32_t fb_address = channel == RASTER_DEPTH ? framebuffer->z_address : framebuffer->fb_address;
uint32_t fb_stride = channel == RASTER_DEPTH ? framebuffer->z_stride : framebuffer->fb_stride;

if (channel == RASTER_DEPTH && framebuffer->z_address == framebuffer->fb_address) {
// Try to avoid silly matches to somewhat malformed buffers.
return false;
}

switch (entry.format) {
case GE_TFMT_DXT1:
case GE_TFMT_DXT3:
case GE_TFMT_DXT5:
return false;
}

u32 addr = fb_address & 0x3FFFFFFF;
u32 texaddr = entry.addr + texaddrOffset;
Expand All @@ -891,34 +889,12 @@ FramebufferMatchInfo TextureCacheCommon::MatchFramebuffer(

if (texInVRAM != fbInVRAM) {
// Shortcut. Cannot possibly be a match.
return FramebufferMatchInfo{ FramebufferMatch::NO_MATCH };
return false;
}

if (texInVRAM) {
const u32 mirrorMask = 0x00600000;

// This bit controls swizzle. The swizzles at 0x00200000 and 0x00600000 are designed
// to perfectly match reading depth as color (which one to use I think might be related
// to the bpp of the color format used when rendering to it).
// It's fairly unlikely that games would screw this up since the result will be garbage so
// we use it to filter out unlikely matches.
switch (entry.addr & mirrorMask) {
case 0x00000000:
case 0x00400000:
// Don't match the depth channel with these addresses when texturing.
if (channel == RasterChannel::RASTER_DEPTH) {
return FramebufferMatchInfo{ FramebufferMatch::NO_MATCH };
}
break;
case 0x00200000:
case 0x00600000:
// Don't match the color channel with these addresses when texturing.
if (channel == RasterChannel::RASTER_COLOR) {
return FramebufferMatchInfo{ FramebufferMatch::NO_MATCH };
}
break;
}

addr &= ~mirrorMask;
texaddr &= ~mirrorMask;
}
Expand All @@ -932,28 +908,29 @@ FramebufferMatchInfo TextureCacheCommon::MatchFramebuffer(

// If they match "exactly", it's non-CLUT and from the top left.
if (exactMatch) {
if (framebuffer->fb_stride != entry.bufw) {
WARN_LOG_ONCE(diffStrides1, G3D, "Texturing from framebuffer with different strides %d != %d", entry.bufw, framebuffer->fb_stride);
if (fb_stride != entry.bufw) {
WARN_LOG_ONCE(diffStrides1, G3D, "Texturing from framebuffer with different strides %d != %d", entry.bufw, (int)fb_stride);
}
// NOTE: This check is okay because the first texture formats are the same as the buffer formats.
if (IsTextureFormatBufferCompatible(entry.format)) {
if (TextureFormatMatchesBufferFormat(entry.format, framebuffer->format) || (framebuffer->usageFlags & FB_USAGE_BLUE_TO_ALPHA)) {
return FramebufferMatchInfo{ FramebufferMatch::VALID };
return true;
} else if (IsTextureFormat16Bit(entry.format) && IsBufferFormat16Bit(framebuffer->format)) {
WARN_LOG_ONCE(diffFormat1, G3D, "Texturing from framebuffer with reinterpretable format: %s != %s", GeTextureFormatToString(entry.format), GeBufferFormatToString(framebuffer->format));
return FramebufferMatchInfo{ FramebufferMatch::VALID, 0, 0, true, TextureFormatToBufferFormat(entry.format) };
*matchInfo = FramebufferMatchInfo{ 0, 0, true, TextureFormatToBufferFormat(entry.format) };
return true;
} else {
WARN_LOG_ONCE(diffFormat2, G3D, "Texturing from framebuffer with incompatible formats %s != %s", GeTextureFormatToString(entry.format), GeBufferFormatToString(framebuffer->format));
return FramebufferMatchInfo{ FramebufferMatch::NO_MATCH };
return false;
}
} else {
// Format incompatible, ignoring without comment. (maybe some really gnarly hacks will end up here...)
return FramebufferMatchInfo{ FramebufferMatch::NO_MATCH };
return false;
}
} else {
// Apply to buffered mode only.
if (!framebufferManager_->UseBufferedRendering()) {
return FramebufferMatchInfo{ FramebufferMatch::NO_MATCH };
return false;
}

// Check works for D16 too (???)
Expand All @@ -962,73 +939,69 @@ FramebufferMatchInfo TextureCacheCommon::MatchFramebuffer(
(channel == RASTER_COLOR && framebuffer->format == GE_FORMAT_8888 && entry.format == GE_TFMT_CLUT32) ||
(channel == RASTER_COLOR && framebuffer->format != GE_FORMAT_8888 && entry.format == GE_TFMT_CLUT16);

// To avoid ruining git blame, kept the same name as the old struct.
FramebufferMatchInfo fbInfo{ FramebufferMatch::VALID };

const u32 bitOffset = (texaddr - addr) * 8;
if (bitOffset != 0) {
const u32 pixelOffset = bitOffset / std::max(1U, (u32)textureBitsPerPixel[entry.format]);

fbInfo.yOffset = entry.bufw == 0 ? 0 : pixelOffset / entry.bufw;
fbInfo.xOffset = entry.bufw == 0 ? 0 : pixelOffset % entry.bufw;
matchInfo->yOffset = entry.bufw == 0 ? 0 : pixelOffset / entry.bufw;
matchInfo->xOffset = entry.bufw == 0 ? 0 : pixelOffset % entry.bufw;
}

if (fbInfo.yOffset + minSubareaHeight >= framebuffer->height) {
if (matchInfo->yOffset + minSubareaHeight >= framebuffer->height) {
// Can't be inside the framebuffer.
return FramebufferMatchInfo{ FramebufferMatch::NO_MATCH };
return false;
}

if (framebuffer->fb_stride != entry.bufw) {
if (fb_stride != entry.bufw) {
if (noOffset) {
WARN_LOG_ONCE(diffStrides2, G3D, "Texturing from framebuffer (matching_clut=%s) different strides %d != %d", matchingClutFormat ? "yes" : "no", entry.bufw, framebuffer->fb_stride);
WARN_LOG_ONCE(diffStrides2, G3D, "Texturing from framebuffer (matching_clut=%s) different strides %d != %d", matchingClutFormat ? "yes" : "no", entry.bufw, fb_stride);
// Continue on with other checks.
// Not actually sure why we even try here. There's no way it'll go well if the strides are different.
} else {
// Assume any render-to-tex with different bufw + offset is a render from ram.
return FramebufferMatchInfo{ FramebufferMatch::NO_MATCH };
return false;
}
}

// Check if it's in bufferWidth (which might be higher than width and may indicate the framebuffer includes the data.)
if (fbInfo.xOffset >= framebuffer->bufferWidth && fbInfo.xOffset + w <= (u32)framebuffer->fb_stride) {
if (matchInfo->xOffset >= framebuffer->bufferWidth && matchInfo->xOffset + w <= (u32)fb_stride) {
// This happens in Brave Story, see #10045 - the texture is in the space between strides, with matching stride.
return FramebufferMatchInfo{ FramebufferMatch::NO_MATCH };
return false;
}

// Trying to play it safe. Below 0x04110000 is almost always framebuffers.
// TODO: Maybe we can reduce this check and find a better way above 0x04110000?
if (fbInfo.yOffset > MAX_SUBAREA_Y_OFFSET_SAFE && addr > 0x04110000 && !PSP_CoreParameter().compat.flags().AllowLargeFBTextureOffsets) {
WARN_LOG_REPORT_ONCE(subareaIgnored, G3D, "Ignoring possible texturing from framebuffer at %08x +%dx%d / %dx%d", fb_address, fbInfo.xOffset, fbInfo.yOffset, framebuffer->width, framebuffer->height);
return FramebufferMatchInfo{ FramebufferMatch::NO_MATCH };
if (matchInfo->yOffset > MAX_SUBAREA_Y_OFFSET_SAFE && addr > 0x04110000 && !PSP_CoreParameter().compat.flags().AllowLargeFBTextureOffsets) {
WARN_LOG_REPORT_ONCE(subareaIgnored, G3D, "Ignoring possible texturing from framebuffer at %08x +%dx%d / %dx%d", fb_address, matchInfo->xOffset, matchInfo->yOffset, framebuffer->width, framebuffer->height);
return false;
}

// Check for CLUT. The framebuffer is always RGB, but it can be interpreted as a CLUT texture.
// 3rd Birthday (and a bunch of other games) render to a 16 bit clut texture.
if (matchingClutFormat) {
if (!noOffset) {
WARN_LOG_ONCE(subareaClut, G3D, "Texturing from framebuffer using CLUT with offset at %08x +%dx%d", fb_address, fbInfo.xOffset, fbInfo.yOffset);
WARN_LOG_ONCE(subareaClut, G3D, "Texturing from framebuffer using CLUT with offset at %08x +%dx%d", fb_address, matchInfo->xOffset, matchInfo->yOffset);
}
fbInfo.match = FramebufferMatch::VALID; // We check the format again later, no need to return a special value here.
return fbInfo;
return true;
} else if (IsClutFormat((GETextureFormat)(entry.format)) || IsDXTFormat((GETextureFormat)(entry.format))) {
WARN_LOG_ONCE(fourEightBit, G3D, "%s format not supported when texturing from framebuffer of format %s", GeTextureFormatToString(entry.format), GeBufferFormatToString(framebuffer->format));
return FramebufferMatchInfo{ FramebufferMatch::NO_MATCH };
return false;
}

// This is either normal or we failed to generate a shader to depalettize
if ((int)framebuffer->format == (int)entry.format || matchingClutFormat) {
if ((int)framebuffer->format != (int)entry.format) {
WARN_LOG_ONCE(diffFormat2, G3D, "Texturing from framebuffer with different formats %s != %s at %08x",
GeTextureFormatToString(entry.format), GeBufferFormatToString(framebuffer->format), fb_address);
return fbInfo;
return true;
} else {
WARN_LOG_ONCE(subarea, G3D, "Texturing from framebuffer at %08x +%dx%d", fb_address, fbInfo.xOffset, fbInfo.yOffset);
return fbInfo;
WARN_LOG_ONCE(subarea, G3D, "Texturing from framebuffer at %08x +%dx%d", fb_address, matchInfo->xOffset, matchInfo->yOffset);
return true;
}
} else {
WARN_LOG_ONCE(diffFormat2, G3D, "Texturing from framebuffer with incompatible format %s != %s at %08x",
GeTextureFormatToString(entry.format), GeBufferFormatToString(framebuffer->format), fb_address);
return FramebufferMatchInfo{ FramebufferMatch::NO_MATCH };
return false;
}
}
}
Expand Down Expand Up @@ -2158,8 +2131,8 @@ void TextureCacheCommon::ClearNextFrame() {
clearCacheNextFrame_ = true;
}

std::string AttachCandidate::ToString() {
return StringFromFormat("[C:%08x/%d Z:%08x/%d X:%d Y:%d reint: %s]", this->fb->fb_address, this->fb->fb_stride, this->fb->z_address, this->fb->z_stride, this->match.xOffset, this->match.yOffset, this->match.reinterpret ? "true" : "false");
std::string AttachCandidate::ToString() const {
return StringFromFormat("[%s seq:%d C:%08x/%d Z:%08x/%d X:%d Y:%d reint: %s]", this->channel == RASTER_COLOR ? "COLOR" : "DEPTH", this->seqCount, this->fb->fb_address, this->fb->fb_stride, this->fb->z_address, this->fb->z_stride, this->match.xOffset, this->match.yOffset, this->match.reinterpret ? "true" : "false");
}

bool TextureCacheCommon::PrepareBuildTexture(BuildTexturePlan &plan, TexCacheEntry *entry) {
Expand Down
13 changes: 3 additions & 10 deletions GPU/Common/TextureCacheCommon.h
Original file line number Diff line number Diff line change
Expand Up @@ -206,15 +206,7 @@ typedef std::map<u64, std::unique_ptr<TexCacheEntry>> TexCache;
#undef IGNORE
#endif

enum class FramebufferMatch {
// Valid, exact match.
VALID = 0,
// Not a match, remove if currently attached.
NO_MATCH,
};

struct FramebufferMatchInfo {
FramebufferMatch match;
u32 xOffset;
u32 yOffset;
bool reinterpret;
Expand All @@ -226,8 +218,9 @@ struct AttachCandidate {
TextureDefinition entry;
VirtualFramebuffer *fb;
RasterChannel channel;
int seqCount;

std::string ToString();
std::string ToString() const;
};

class FramebufferManagerCommon;
Expand Down Expand Up @@ -365,7 +358,7 @@ class TextureCacheCommon {
SamplerCacheKey GetFramebufferSamplingParams(u16 bufferWidth, u16 bufferHeight);
void UpdateMaxSeenV(TexCacheEntry *entry, bool throughMode);

FramebufferMatchInfo MatchFramebuffer(const TextureDefinition &entry, VirtualFramebuffer *framebuffer, u32 texaddrOffset, RasterChannel channel) const;
bool MatchFramebuffer(const TextureDefinition &entry, VirtualFramebuffer *framebuffer, u32 texaddrOffset, RasterChannel channel, FramebufferMatchInfo *matchInfo) const;

std::vector<AttachCandidate> GetFramebufferCandidates(const TextureDefinition &entry, u32 texAddrOffset);
int GetBestCandidateIndex(const std::vector<AttachCandidate> &candidates);
Expand Down