Commit Graph
607 Commits
Author SHA1 Message Date
Henrik Rydgård 2de11efe4e TextureCache: stop clamping DXT decoding to bufw either
DecodeDXTBlocks limited its x loop to min(bufw, w), so when w > bufw everything
from bufw to w was simply never written - the destination is sized for w, so
those columns kept whatever the buffer held.

The software sampler doesn't do that. It addresses a block as
(v >> 2) * (texbufw >> 2) + (u >> 2), which for u past bufw runs on into the
next row's blocks, the same way the linear formats run into the next row. So the
two renderers disagreed on the same texture.

Follow the sampler: decode w texels per row and let the block index carry on,
with the range check widened to cover the blocks that reach past the last row's
stride. Shares the SourceExtent helper from the previous commit, counting 4x4
blocks rather than texels.

Rendering-visible where w > bufw for a DXT texture, which is the case that used
to leave stale contents behind.
2026-09-05 11:25:55 -06:00
Henrik Rydgård ca9d76fef3 TextureCache: bound the source by w as well as bufw, instead of clamping w
The non-DXT decode paths read w texels per row from a source whose stride, range
check and unswizzle buffer were all sized from bufw alone. w and bufw are
independent GE registers, so w > bufw is reachable, and the last row then runs
off the end of both the validated guest range and the temp buffer.

The previous commit clamped w down to bufw, which stops the overrun but is the
wrong shape twice over: the game asked for w texels and the destination is sized
for w, so the tail of every row is left holding whatever was in the buffer, and
it silently narrows a texture the hardware would have decoded in full.

Size the checks from both instead. The extent a decode touches is bufw per row
plus however far the last row reaches past its own stride - the same adjustment
TextureReplacer::ComputeHash and the GE recorder already make - so the range
check, the height it falls back to when the range is short, and the unswizzle
buffer are all computed that way now.

Pull the five copies of "resize tmpTexBuf32_, unswizzle into it" into a helper
while we're here, since they all need the same sizing. It zeroes the buffer when
w reaches past bufw, as UnswizzleFromMem only fills the stride and the tail would
otherwise be stale heap.

ComputeTextureHash has the same bufw-only assumption, but it's left alone
deliberately - changing what goes into a texture hash isn't worth the risk here.

DXT is left alone too - it clamps to minw, but there the limit is the block index
within a bufw/4 block row, so its range check already covers what it reads.
2026-09-05 10:26:14 -06:00
Henrik RydgårdandClaude Opus 5 ed5d9813bb TextureCache: fix two host-memory overruns from GE texture state
PrepareBuildTexture's mip scan stopped at the first level with a dimension of 1
*before* running the mip size check for that level, so a level like 1x256 under a
256x256 level 0 was accepted as a valid mip. The backend then sized the level from
the halved level-0 dimensions (128x128) while LoadTextureLevel re-read the real
height (256) from the GE state, writing past the allocation. Do the check first.

That check was also gated on GPU_USE_SAMPLER_LOD_CONTROL, so backends without it
did no mip dimension validation at all - there's no reason for the guard, the
result only feeds badMipSizes, so drop it.

Separately, the non-DXT decode paths read w texels per row from a source whose
stride, range check and unswizzle buffer are all computed from bufw. w and bufw
are independent GE registers, so w > bufw is reachable and read past the end of
both the validated guest range and the temp buffer. Clamp w to bufw in
DecodeTextureLevel, which is what the DXT paths have always done via minw.

Note: ungating the mip check means backends without SAMPLER_LOD_CONTROL can now
set badMipSizes where they previously didn't, which collapses such textures to a
single level. That's the intended behavior, but it is a rendering-visible change
on those backends.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DCPmm7FoQUoqrbMdhfqhQ2
2026-09-05 10:25:47 -06:00
Henrik Rydgård 89ebb8acc6 GLES: Actually apply anisotropic filtering
TextureCacheGLES passed a hardcoded 0.0f instead of key.aniso, so the
Anisotropic Filtering setting did nothing at all on the OpenGL backend, even
though GPU_USE_ANISOTROPY was advertised and D3D11/Vulkan both honor it. Looks
like it was left behind by the 2017 render manager refactor.

The queue runner now clamps to the device maximum it already queried into
maxAnisotropyLevel_ (until now unused), and only touches the parameter when the
extension is actually supported - the anisotropy branch there has been dead
since every caller passed 0.0f, so this is the first time it runs.

0.0f keeps its meaning of "don't care" for the CLUT/fragment-test/thin3d
callers; the texture cache now passes 1.0f when the setting is off, so turning
it off takes effect on already-uploaded textures instead of only new ones.

TexCache: Never use anisotropic filtering for CLUT8-indexed textures

What gets sampled for those is palette indices, depalettized by the shader
afterwards - averaging indices across an anisotropic footprint produces garbage
colors. Affects all backends, not just the GL one that just started honoring
key.aniso.

TexCache: Clear key.aniso wherever filtering is forced to nearest

It was only cleared in the two places inside the AUTO_MAX_QUALITY branch, so the
TEX_FILTER_AUTO path (pixel-mapped textures, the ugly color test heuristic), the
FORCE_NEAREST setting and the replacement-texture override could all end up
requesting nearest filtering with anisotropy still on.

Doing it in the switch that applies forceFiltering covers every path, so it
can't drift apart again.

GLES: Only record the applied anisotropy, and log skipped draws

The queue runner updated tex->anisotropy even when it skipped the call because
the value was 0.0f ("don't care") - harmless while nothing ever set anisotropy,
but now it would make the tracked state disagree with GL, so a later request for
the value it thinks is set would be wrongly skipped.

Also log when a draw is skipped for a missing vertex shader. The failure is
cached per shader ID, so without it geometry silently disappears for the rest of
the session after the one-shot OSD message.
2026-09-04 10:41:15 -06:00
Henrik Rydgård 293cf0c78c Revert the logic changes from "Add new TexCache logging channel"
This partially reverts commit ee1314f803.
2026-09-02 15:56:10 -06:00
Henrik RydgårdandClaude Opus 5 016f976b1f Fix a batch of missing or wrong range validation
Memory::IsValidAddress and friends tested the extended-RAM range with
(address & 0x3F000000), i.e. at 16MB granularity, so they accepted the whole 16MB
block containing the end of RAM. That's harmless at 32MB and 64MB, but the Sora no
Kiseki SC/3rd HD remasters run with 0x04C00000, so addresses from 0x0CC00000 to
0x0CFFFFFF read as valid, and MaxSizeAtAddress then underflowed to ~4GB there -
which defeats ClampValidSizeAt and IsValidRange entirely for that window. Mask
with 0x3FFFFFFF instead, in all five helpers and the copies in MemMapFunctions.cpp.

IsValidTextureAddress's extended-RAM branch repeated the first branch's whole mask
rather than just its alignment bits, so it was dead code and extended RAM was never
accepted as a texture source.

ComputeTextureHash checked IsValidAddress(addr + sizeInRAM), i.e. only the end
address, which can land in a different valid region than the start - a VRAM texture
with a large enough computed size ends exactly at the base of RAM and "passes"
while reading far past the 8MB VRAM view. Use IsValidRange.

TextureReplacer::ComputeHash's strided path had no range check at all, unlike the
contiguous path right above it. Also clamp the pack-supplied reduce-hash factor to
1.0 - it's a reduction, and the ini parser only rejects exactly 0.

ZipExtractFileToMemory read an uninitialized zip_stat when zip_stat_index failed
(it ignored the return value) and sized a host allocation directly from the zip's
declared uncompressed size. Reached just by opening an archive.

Memory::Reinit ignored Init()'s return value, and DoState fed it a memory size
taken straight from the savestate. A bogus size made the map fail to allocate and
left base null, after which DoMemoryVoid wrote RAM through it. Validate the size,
propagate the failure, and roll back to the previous size if reinit fails.

314 pspautotests pass, all unit tests pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DCPmm7FoQUoqrbMdhfqhQ2
2026-08-30 23:05:04 +02:00
Henrik Rydgård 4d26523ba8 Note the Kitten Cannon oversized-texture case
Homebrew "Kitten Cannon" hits the bad-dimensions path with a clearly invalid
512x32768 texture, likely a noise bit in the texture size command.
2026-08-29 11:42:47 +02:00
Henrik Rydgård 46d5fae342 VSH prep: Free up kernel memory by not storing the PPGe atlas in it 2026-08-19 18:45:52 +02:00
Henrik Rydgård 14338534e7 Improve some memory address checking functions 2026-08-19 18:40:01 +02:00
Henrik Rydgård c5e4d0d90d Rename the get-memory-pointer functions to make it clear where CPU exceptions can happen. 2026-08-12 14:06:16 +02:00
Henrik Rydgård b6e7cb915a Texture cache management: include w and h in more log messages for context 2026-08-09 19:03:08 +02:00
Henrik Rydgård 459f4473af Fix crash in GE debugger
Fixes #22027
2026-08-07 23:49:30 +02:00
Henrik Rydgård 0324c7a912 Windows buildfix 2026-08-05 00:15:56 +02:00
Henrik Rydgård 0049b19fbf Fix OOB in texture replacer mip mixing and non-DXT texture decode
Vuln 17: ReplacedTexture::LoadLevelData let a KTX2/DDS file at a higher
mip level resize the shared data_ vector to its own (attacker-controlled)
mip count, so data_[mipLevel + i] indexed out of bounds and the KTX2
branch resized a different element than it wrote to. Disallow mixing
image formats across mip levels, cap the container mip count, and resize
the same element that is used as the transcode destination.

Vuln 18: DecodeTextureLevel only validated the start address for
non-DXT textures, so guest-controlled w/h/bufw could drive reads past
mapped RAM. Validate the needed range like the DXT path does and clamp
the height; ReadIndexedTex now takes the clamped w/h.
2026-08-01 13:16:22 +02:00
Henrik Rydgård 986eee7ef3 Remove the global variable "useEmuThread", add some VK error checking for compute shader compiles 2026-07-18 23:45:27 +02:00
Henrik Rydgård d7c8d6bd80 Clean up cachekey handling in texture replacer 2026-07-18 18:00:51 +02:00
Henrik Rydgård 90d922226f Correct maxSeenV handling 2026-07-18 12:31:01 +02:00
Henrik Rydgård 63331fd6d1 Remove DeleteTexture function, only made the code less clear 2026-07-18 12:08:50 +02:00
Henrik Rydgård ee1314f803 Add new TexCache logging channel 2026-07-18 11:57:56 +02:00
Henrik Rydgård f3a5b2dd18 Correct bugs in the secondary texture cache 2026-07-18 11:57:48 +02:00
Henrik Rydgård cff09041c9 Completely rework the flow in TextureCacheCommon::ApplyTexture 2026-07-18 11:57:48 +02:00
Henrik Rydgård b9b2b2b61e Reindent some code, do checks in a more logical order 2026-07-18 11:57:48 +02:00
Henrik Rydgård 1ffdc52111 Remove another "next" variable, cleanup 2026-07-18 11:57:48 +02:00
Henrik Rydgård 3b58242063 More cleanup 2026-07-16 20:54:58 +02:00
Henrik Rydgård ff89e4da4d Simplify 2026-07-16 20:38:27 +02:00
Henrik Rydgård 31c32c0ee0 Remove the "nextFramebufferTexture_" variable 2026-07-16 20:30:01 +02:00
Henrik Rydgård 40bce17f53 Some cleanup 2026-07-16 20:27:44 +02:00
Henrik Rydgård 130fad6876 Split out a function to finish up binding of framebuffers 2026-07-16 20:15:51 +02:00
Henrik Rydgård 0528646ea3 Invert the relationship between the two functions 2026-07-16 19:08:18 +02:00
Henrik Rydgård a4b4489d30 Move code around 2026-07-16 19:08:18 +02:00
Henrik Rydgård e81055db16 Remove the need to call SetTexture() externally. 2026-07-16 18:32:47 +02:00
Henrik Rydgård 92ec6a992b Remove "pixelMapped" from gstate_c. 2026-07-16 17:45:22 +02:00
Henrik Rydgård 6f4f0c41b4 Split ApplyTexture into ApplyTexture and ApplySampler 2026-07-16 17:45:22 +02:00
Henrik Rydgård 2e012b8966 Unify the "BindSampler" function between the backends 2026-07-16 17:45:22 +02:00
Henrik Rydgård 66014870d2 Remove unnecessary "DirtyLastShader" mechanism. 2026-07-16 15:47:59 +02:00
Henrik Rydgård 57e6bafb62 Remove largely ineffective and nearly not functional "low memory mode". Unlikely to save us. 2026-07-16 15:28:40 +02:00
Henrik Rydgård 672b549109 Make GetFramebufferSamplingParams a loose function. 2026-07-16 15:28:33 +02:00
Henrik Rydgård d2ae8af2bf Start splitting apart Texture vs Sampler binding code 2026-07-16 15:03:23 +02:00
Henrik Rydgård 615815e9ab Minor code cleanup 2026-07-16 15:03:23 +02:00
Henrik Rydgård ea95eb420d Clean up GetFramebufferSamplingParams 2026-07-16 15:03:20 +02:00
Henrik Rydgård c278fdf5a3 Rename some vars, move a function from header to cpp 2026-07-16 15:03:20 +02:00
Henrik Rydgård 90c48671d7 D3D11: Remove support for BGRA textures and preferred framebuffer formats 2026-07-03 16:20:22 +02:00
Henrik Rydgård 1b2f87bf1a Rename GPUgstate to GEState 2026-07-02 20:34:09 +02:00
Henrik Rydgård ebc564a465 Centralize handling of another GPU flag 2026-06-27 13:50:03 +02:00
Henrik Rydgård 64e55324c7 Fix initialization bug in TextureCacheCommon 2026-06-14 14:02:09 +02:00
Henrik Rydgård 536c4ace73 Fix the ugly line on Ridge Racer lens flares
Turns out we were not updating one column and row of the lens flare
image, which is done in a strange way by the game (CLUT in framebuffer).

Some math of the game is a little off and for some reason it's not
noticeable on hardware.
2026-06-14 01:09:33 +02:00
Henrik Rydgård 7c1f052b87 ImGe debugger fix 2026-06-13 17:09:06 +02:00
Henrik Rydgård d6e0dd1478 Always enable the secondary texture cache 2026-06-13 13:34:41 +02:00
Henrik Rydgård 40a345bff8 Fix a filtering issue, enable this for Fushigi no Dungeon 4. 2026-06-12 23:52:50 +02:00
Henrik Rydgård 484b94c3f3 Build and warning fixes 2026-06-12 17:28:52 +02:00