The GE's blend terms are floor((2c+1)(2f+1)/1024), which truncates, while the
hardware multiplies by f/255 and rounds, so every pass of a fixed color blend
chain came out up to a level brighter. God Eater Burst's bloom is many such
passes and ended up far too bright.
For additive FIXA/FIXB blends that aren't just 0 and 0xFFFFFF, the shader
now computes the src term exactly and lowers it by 0.5 - (2B+1)/1024, and the
blend constant is the GE's dst factor (2B+1)/512, so the hardware's rounding
of the sum lands on the GE's floor (except where the src term would go below
zero). Blend constants are passed as floats to make that possible.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With one vertex behind the near plane, the GE splits the remaining quad from
the vertex before the clipped one (gpu/probe exp43, exp44, as in SoftGPU's
Clipper.cpp). The other diagonal could put a far off-screen near-plane vertex
in both halves, so range culling dropped the whole triangle: the floor in
#16131's box test vanished. The index generator reverses triangles to flip
culling, so the batch's order is passed along.
Triangles entirely beyond the far plane are now culled even when in front of
the near plane, with the cull-distance epsilon, so z/w = 1.0000172 (exactly
1.0 to the GE) still counts as inside.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Nothing writes depth then, so the shader write is wasted. On Apple's OpenGL
it also broke Tales of Phantasia (PixelDepthRounding): once the shader writes
gl_FragDepth, a discarded fragment still writes stencil, so a color-tested
stencil pass marked the whole area and the background tiles drawn with
stencil EQUAL 0 vanished.
New FS ID bit, so both shader caches get a version bump.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On the PSP the color and alpha/stencil write masks apply to clears like to
drawing (gpu/stencil/writemask, ppsspp-re geprobe exp254). Clear-mode draws
now take the same per-channel heuristic as other draws, and a clear with RGB
masked is drawn rather than done as a hardware clear.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Environment map S and T are (N.L + 1) / 2 with L the light's vector as
lighting uses it: from the vertex to the light for point and spot lights,
a zero vector staying zero, and the half vector for a light that does
specular. Whether lighting or the light is enabled still doesn't matter
(gpu/lighting/shademap).
The vertex shader ID now carries the type and computation of the shade
mapping lights (the ubershader reads them from u_lightControl), so both
shader caches get a new version.
Fixes the hair shine in iDOLM@STER SP (#12376).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Software transform expands each point, line and rectangle to four
vertices, and gave up on the whole draw when that didn't fit
VERTEX_BUFFER_MAX. Batching only counted input vertices, so a batch over
16384 points (or 32768 line or rectangle vertices) vanished silently,
whether it came from one PRIM or several merged ones. Count the expanded
vertices when batching, and submit a PRIM too big on its own in parts.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The GLES sampler uniforms and texture slots for the control points and
weights, the Vulkan storage buffer bindings, and the u_spline_counts
uniform, which becomes padding (the C++ side already was).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The draw engine is created on the loader thread while the UI thread may
already be rendering and invoking the callback.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
These own GPU objects, memory or refcounts in their destructors (or assert
there that they were torn down), so a copy would double-free. Nothing copies
them today; this keeps it that way. The manager base classes cover every
backend's subclass.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Put the anisotropy level in the sampler key, so changing it applies on
Vulkan and D3D11.
- Release CLUT textures at shutdown on GLES and D3D11.
- Fix the depth readback viewport, which squeezed the image whenever the
read rectangle was smaller than the fbo.
- Test the computed depth, not the unset result, in the equal-depth clear
check.
- Don't read back a CLUT from a framebuffer without an fbo.
- Tolerate null entries when releasing post-shader objects and CLUT
textures after a failed creation.
- ImGe: Don't crash on a framebuffer without an fbo, or on GetVFB under the
software renderer.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
DeviceLost saves the cache and clears everything, and the next save wrote
back only what was drawn since, so each Android background/resume cycle
shrank the cache.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Flush before the queued draws would decode more than VERTEX_BUFFER_MAX
vertices. The batch was limited by index count, which doesn't bound a
sparse index range, and DecodeVerts silently stopped while DecodeInds
still emitted indices for the undecoded draws.
- Give TestBoundingBox its own scratch buffer. It used offsets in decoded_,
which can hold decoded vertices that aren't flushed yet.
- Read 32-bit indices the way the PSP does, ignoring the upper 16 bits.
IndexConverter and the fast bounding box test used all 32, so a game
setting them indexed far past the decoded vertices.
- D3D11: Flush in FinishDeferred like the other backends, since indices
are still read from PSP memory at flush time (#10095).
- Don't JIT new vertex decoders once the code space is full.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The OpenGL and Vulkan shader caches store raw shader IDs (and, for Vulkan,
pipeline keys) on disk. Add the rule to AGENTS.md and point to it from the
persisted types.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The pipeline deletion callbacks block on in-flight compiles, which use the
shader module promises that the shaders' deletion callbacks free. Queueing
the shaders first freed the promises under a pending compile.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
GLSLtoSPV takes an optional SPIRVCache, keyed on a 32-bit hash of the
source, stage and variant, plus the source length. A changed shader
simply misses. thin3d's shaders and the other fixed ones use a global
cache in PSP/SYSTEM/CACHE/vulkan_spirv.cache, loaded on first use and
saved after graphics init, when a game's cache is saved, and at
shutdown; it's flushed once it reaches 32 entries, about twice what a
session compiles, so outdated ones don't pile up. Game shaders keep
theirs in the .vkshadercache, ahead of the shader IDs so that the
compiles on load find it (version 60), and only what the session used
is saved.
A cold glslang costs about 40ms before its first shader here, and
0.3-0.9ms per shader after that.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The pipeline debug listing printed the color blend factors in the alpha slot,
which is doubly unhelpful since that branch is only taken when the alpha factors
differ from the defaults.
CompileShaderModuleAsync takes ownership of the tag but only deleted it on the
success path, leaking it whenever GLSLtoSPV failed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SGz54K3ZXa2Qzyc3aMEYyY
The failure branch inserted a null pipeline and then fell through to the normal
insert of the same key, which trips DenseHashMap's duplicate-key assert - and
_assert_msg_ is live in release builds, so a logged error became a crash.
Also skip the null entries when deleting cached pipelines.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SGz54K3ZXa2Qzyc3aMEYyY
They were OR-ed into pipelineFlags just after the CreateGraphicsPipeline call that
consumes them, so the render manager's "don't compile a pipeline that requires
depth for a non-depth renderpass type" check could never fire for game pipelines.
thin3d_vulkan.cpp sets the flag before its call, which is the intended order.
Note this can now legitimately skip some variants when loading the shader cache -
those were invalid combinations that the check was written to reject.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SGz54K3ZXa2Qzyc3aMEYyY
If reading the shader's constant buffer file failed, we'd skip writing descriptor
binding 4 but still dispatch the compute shader, which declares it - a statically
used but unwritten descriptor. It also re-read the missing file on every single
texture upload. Now we drop the scaling shaders instead, so following textures
take the CPU scaling path.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SGz54K3ZXa2Qzyc3aMEYyY
The out-of-VRAM retry path cleared plan.replaced but left plan.doReplace set.
GetMipSize() dereferences plan.replaced when doReplace is true, so the fallback
crashed instead of recovering. The common code sets both together.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SGz54K3ZXa2Qzyc3aMEYyY
Nothing has generated or used a geometry shader since the GS paths were removed
- GeometryShaderGenerator is gone, and ShaderWriter's BeginGSMain/EndGSMain had
no callers at all. Removes ShaderStage::Geometry and everything hanging off it:
the GS preambles and GSMain helpers in ShaderWriter, the stage mappings in all
three thin3d backends, the D3D11 geometry shader plumbing (curGS_, the pipeline
and module members, gs_4_0 compilation), CreateGeometryShaderD3D11, the unused
PipelineFlags::USES_GEOMETRY_SHADER and PipelineManagerVulkan's
UsesGeometryShader().
Also stop enabling the Vulkan geometryShader device feature, since we no longer
have any use for it.
Kept on purpose: the device feature is still listed in the feature dumps (like
other capabilities we don't use), and the Vulkan shader cache header keeps its
now-always-zero geometry shader count so the on-disk format stays compatible.