mirror of
https://github.com/ARMSX2/ARMSX2.git
synced 2026-08-24 16:50:16 -07:00
GS: stop injecting replacement textures whose upload failed
CreateReplacementTexture discarded the bool from every tex->Update(). The texture object exists either way, so a failed upload was reported as a successful replacement and the game was handed a texture with UNDEFINED contents. On screen that is indistinguishable from a replacement that never loaded — missing cursors, letters cut in half — which is exactly how #442 presents. Update can genuinely fail. On Vulkan an upload needs either room in the shared streaming buffer or a dedicated staging allocation (GSTextureVK::DoUpdate), and under memory pressure either can fail; OpenGL has no equivalent staging step, which is consistent with #442 reproducing on Vulkan and not on OpenGL. It is worse here than upstream because of our CPU BC decode: a BC7 texture becomes RGBA8 at four times the size, so the allocation that has to succeed is four times larger, on exactly the packs most likely to be huge already. Drop the texture on failure so the game falls back to its original, and log once with the settings that actually help (Precache off, lower render resolution). A pack that does not fit now degrades to "not replaced" rather than "corrupt", and the failure is visible in the log instead of silent. Mip failures are treated the same way: a garbage mip is still garbage, it just only shows at distance. Defect identified by bmdhacks.
This commit is contained in:
@@ -934,8 +934,36 @@ GSTexture* GSTextureReplacements::CreateReplacementTexture(const ReplacementText
|
||||
if (!tex)
|
||||
return nullptr;
|
||||
|
||||
// Update() CAN fail, and its result was being discarded. On Vulkan an upload needs either room
|
||||
// in the shared streaming buffer or a dedicated staging allocation (GSTextureVK::DoUpdate), and
|
||||
// either can fail under memory pressure — at which point the texture exists but its contents are
|
||||
// UNDEFINED. Injecting it anyway reports success and hands the game garbage, which on screen is
|
||||
// indistinguishable from a replacement that never loaded: missing cursors, letters cut in half.
|
||||
//
|
||||
// It is worse for us than upstream because of the CPU BC decode above: a BC7 texture becomes
|
||||
// RGBA8 at four times the size, so the upload that has to succeed is four times larger. Drop the
|
||||
// texture instead, so the game falls back to its original and the pack degrades to "not
|
||||
// replaced" rather than "corrupt".
|
||||
const auto upload_failed = [&](u32 level) {
|
||||
static bool logged_once = false;
|
||||
if (!logged_once)
|
||||
{
|
||||
logged_once = true;
|
||||
Console.Error("Texture replacements: GPU upload failed (level %u, %dx%d %s). The "
|
||||
"replacement is being skipped rather than drawn with undefined contents. "
|
||||
"This usually means texture memory is exhausted — try turning Precache "
|
||||
"Texture Replacements off, or lowering the render resolution.",
|
||||
level, rtex.width, rtex.height, GSTexture::GetFormatName(rtex.format));
|
||||
}
|
||||
g_gs_device->Recycle(tex);
|
||||
};
|
||||
|
||||
// upload base level
|
||||
tex->Update(GSVector4i(0, 0, rtex.width, rtex.height), rtex.data.data(), rtex.pitch);
|
||||
if (!tex->Update(GSVector4i(0, 0, rtex.width, rtex.height), rtex.data.data(), rtex.pitch))
|
||||
{
|
||||
upload_failed(0);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// and the mips if they're present in the replacement texture
|
||||
if (!rtex.mips.empty())
|
||||
@@ -943,7 +971,13 @@ GSTexture* GSTextureReplacements::CreateReplacementTexture(const ReplacementText
|
||||
for (u32 i = 0; i < static_cast<u32>(rtex.mips.size()); i++)
|
||||
{
|
||||
const ReplacementTexture::MipData& mip = rtex.mips[i];
|
||||
tex->Update(GSVector4i(0, 0, static_cast<int>(mip.width), static_cast<int>(mip.height)), mip.data.data(), mip.pitch, i + 1);
|
||||
if (!tex->Update(GSVector4i(0, 0, static_cast<int>(mip.width), static_cast<int>(mip.height)),
|
||||
mip.data.data(), mip.pitch, i + 1))
|
||||
{
|
||||
// A garbage mip is still garbage — it just only shows at distance.
|
||||
upload_failed(i + 1);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user