Go to file
Foued Attar e17fb6fed1
Fix for DS,Little Nightmares, Little Nightmares III and Astro Bot shader (#913)
* shader(decode): fill MIMG float-atomic and SOP1 opcode gaps

Gen5ShaderTranslator's opcode name tables were missing several real
RDNA2 opcodes that Ghost of Yotei's shaders use: MIMG 0x1D-0x1F
(ImageAtomicFcmpswap/Fmin/Fmax) and SOP1 0x15/0x1B/0x1C/0x1E/0x34/0x3B
(SFlbitI32B32, SBitset0/1 B32/B64, SAbsI32, SBitreplicateB64B32).
Without names these decoded as unknown instructions and aborted
translation before the pipeline ever reached GPU submission.

* shader(resources): accept float-numeric-class atomic image descriptors

ResourceMaterializer rejected any atomic image descriptor whose guest
format wasn't exactly Format32Uint, but real atomic-float images
(ImageAtomicFmin/Fmax) arrive with a float-numeric-class format. Relax
the check to also accept formats whose SampledNumericClass is Float,
and default the null-descriptor's NumericClass to the image's own
class for atomics instead of always Float.

Also surface the actual materialization failure reason in
ShaderProgramCache's error message instead of a generic string, so
each subsequent blocking bug could be identified from the log without
extra instrumentation.

* shader(bindings): add a dedicated atomic-float image binding range

BindingLayout only had a single Atomic-Uint binding range (32-36), so
any atomic image with a float numeric class had nowhere to bind and
ForImage() returned null for it. Add AtomicFloatBinding (37-40),
bump ImageBindingCount 36->41, and split Describe()'s atomic range
into its Uint and Float sub-ranges to match.

Also add an Atomic flag to ShaderImageShape (TargetRegisterWords.cs)
so downstream view-format resolution can distinguish atomic images
from ordinary sampled/storage ones.

* spirv: emit ImageAtomicFmin/Fmax and DsSwizzleB32, fill SOP1 ALU gaps

Vulkan has no native float image-atomic-min/max instruction, so emit
them the way KytyPS5 does: OpImageTexelPointer (typed uint) ->
OpAtomicLoad -> compare-and-swap loop (OpPhi/OpFOrdLessThan/OpSelect
on the bitcast float value) -> OpAtomicCompareExchange, returning the
pre-exchange value from EmitAtomicWordUpdate (now non-void, valid SSA
at the loop's merge block).

Add DsSwizzleB32 (bit-mode/quad-mode lane shuffle via
GroupNonUniformShuffle) to the LDS-opcode switch and to
UsesSubgroupShuffle(), and add the SFlbitI32B32/SAbsI32/SBitset0B32/
SBitset1B32 SOP1 cases so the newly-decoded opcodes from
Gen5ShaderTranslator actually translate to SPIR-V instead of hitting
the generic "unsupported instruction" fallback.

* spirv: declare atomic image OpTypeImage with a Uint sampled type

DeclareImageClass previously derived the SPIR-V OpTypeImage's sampled
type purely from the binding's logical NumericClass, so an
atomic-float image (logical class Float, backed by our new
compare-and-swap emission on a uint texel pointer) still declared its
OpTypeImage as float-sampled. That violates
VUID-StandaloneSpirv-Image-04965: the R32ui image format and a float
sampled type disagree on numeric class. Force the sampled type (and
format, already correct as R32ui) to Uint whenever the image is
atomic, regardless of its logical numeric class.

* gpu: back atomic-float images with a real Uint VkImageView

The SPIR-V side now declares atomic images as Uint (OpTypeImage), but
the actual VkImageView bound at dispatch time was still created with
the descriptor's own float-numeric-class format, tripping
VUID-vkCmdDispatch-format-07753 (the shader's declared sampled type
must match the bound view's format numeric class).

Fix both resolution paths:
- VulkanVideoPresenter.Descriptors.cs: NumericClassOf() now forces
  Uint for any atomic image (this covers the null-descriptor
  fallback path), and ShapeOf() forwards the Atomic flag.
- ImageRequestBuilders.Texture.cs: the real, non-null descriptor path
  resolves its view format independently from the descriptor's raw
  GuestPixelFormat and was untouched by the above. Extend the
  existing Sint-storage override (which already forces such images to
  Format.R32Uint) to also cover shape.Atomic.

Confirmed via a fresh Vulkan-validation-layer run against Ghost of
Yotei that VUID-vkCmdDispatch-format-07753 no longer appears.

* fix(shader): reconcile atomic SPIR-V emitters with upstream

* spirv: fix ImageGetResinfo querying a non-existent sampled-image combo

ImageGetResinfo never goes through UsesSampler() (it only matches
ImageSample*/ImageGather* opcodes), so TryResolveLayoutImage always
resolves imageObject as the raw OpTypeImage value for this
instruction, never a sampled-image combo. The size-query code still
called OpImage on it whenever resource.IsStorage was false, expecting
to extract a combo that was never built, which spirv-val rejects:
"Expected Sample Image to be of type OpTypeSampleImage". Just use
imageObject directly in both branches.

This surfaced only after the earlier atomic-image fixes let Ghost of
Yotei's shader compilation get this far; it is an unrelated,
pre-existing bug in the base PR.

* gpu: remap compute workgroup axes past the Z-dimension device limit

Ghost of Yotei dispatches a compute shader with NUM_THREAD (1,1,256).
Vulkan only guarantees maxComputeWorkGroupSize >= (1024, 1024, 64):
the Z axis has a much tighter guaranteed limit than X/Y, and this ran
into it for real: "LocalSize Z (256) exceeds device limit
maxComputeWorkGroupSize[2] (64)" at vkCreateComputePipelines, even
though the NVIDIA RTX 5070 here allows up to 1024 total invocations.

Fix by remapping logical GNM axes onto physical SPIR-V/dispatch axes
at shader-compile time: the largest NUM_THREAD axis always lands on
physical X (most headroom), the smallest on physical Z (tightest
limit). Gen5SpirvTranslator.ComputeWorkgroupAxisOrder computes this
permutation purely from the three thread-group sizes, so:

- The translator uses it to declare the physical LocalSize execution
  mode and to read gl_LocalInvocationId/gl_WorkGroupId's components
  back into the correct logical VGPR/system-register slots, so guest
  shader code (which only ever sees logical TID/WorkGroupId values)
  is unaffected by the remap.
- RenderExecutor.Compute.cs recomputes the identical permutation from
  the same thread-group sizes (input.ThreadsX/Y/Z) to reorder
  vkCmdDispatch's group counts the same way, with no new metadata
  plumbing between the shader compiler and the dispatch path.

* shader(resources): drop redundant ImageHandle ScalarBufferWord guard; add opt-in resource-tracker diagnostics

GetHandleSource had a blanket rejection of any ImageHandle whose dword
was a ScalarBufferWord, predating ValidateSource/RuntimeValueValidator
being able to generically resolve such dwords at bind time (the same
mechanism MaterializationSources already uses for buffer descriptors).
Drop the redundant, narrower guard and let ValidateSource be the
single source of truth, so legitimately resolvable image descriptors
loaded from a scalar buffer (not just the two hardcoded heap/direct
shapes TryMakeIndirectImage/TryMakeDirectImage recognize) aren't
rejected outright.

This did not resolve Ghost of Yotei's current blocker: its compute
shader at hash=0xCECFBCC210CB2372 pc=0x59C resolves an image
descriptor's byte offset through a loop-carried Phi (a hash-table/
linear-probe style bindless material lookup) that is not loop-
invariant, so it genuinely cannot be pre-evaluated as a single
per-dispatch runtime value the way MakeRuntimeBufferSource/
RuntimeValueEvaluator expect. That needs real GPU-side dynamic
descriptor indexing support, which doesn't exist yet in this
resource-tracking pass.

Also add SHARPEMU_RESOURCE_TRACKER_DIAG=1, gating verbose stderr
tracing in TryMakeIndirectImage's rejection paths and
RuntimeValueValidator's node-validation failures. This is what
localized the loop-carried Phi as the actual root cause; keeping it
opt-in and off by default costs nothing at runtime.

* shader(resources): degrade control-dependent bindless image/sampler descriptors to null instead of failing compilation

Ghost of Yotei's material lookup resolves an image descriptor's
offset through a loop-carried phi (a hash-table/linear-probe bindless
scheme) that RuntimeValueValidator correctly can't treat as a single
compile-time source - this resource tracker has no GPU-side dynamic
descriptor indexing to fall back on. Track *why* validation failed
(RuntimeValueValidator.ControlDependent, set when the failure traces
to a non-invariant phi) and, for image/sampler handles specifically,
degrade to a null descriptor instead of hard-failing shader
recompilation. Buffer handles and any other failure reason still fail
as before.

This ports KytyPS5's fix for the identical case
(feat/shader-control-dependent-descriptor, "shader: fall back to a
null image when its descriptor is control-dependent") rather than
attempting full dynamic descriptor indexing support, which doesn't
exist in either codebase.

Also add SHARPEMU_RESOURCE_TRACKER_DIAG tracing in
RuntimeValueEvaluator's evaluation failure paths (per-source-dword,
flattened table reads, and raw memory reads), used to localize the
next blocker down this same path.

* gpu: keep image shader types compatible with Vulkan views

* shader: decode and emit VOP1 0x39 (VFfbhU32, find-first-bit-high)

Another decode gap in the same family as the earlier MIMG/SOP1 fixes:
VOP1 0x39 was missing from the opcode name table, aborting
translation of Ghost of Yotei's next compute shader before it reached
GPU submission. Ported the fix straight from our own tree's
Gen5ShaderTranslator.cs/Gen5SpirvTranslator.Alu.cs, which already
decodes and emits it: reverse the bits then use the GLSL FindILsb
extended instruction, since RDNA reports the leading-zero count with
the architecturally required 0xFFFFFFFF sentinel for a zero source.

* shader(resources): treat a null-based SRT raw read as zero instead of faulting

A bindless pointer the guest leaves unbound for a dispatch resolves
its address-handle dwords to zero; EvaluateRawRead's raw-address
branch (as opposed to the buffer-descriptor branch) then dereferenced
that near-null address directly, and the guest memory reader
correctly refused it as unmapped, failing the whole materialization
with a generic "reason=Other". Ghost of Yotei's next compute shader
hit exactly this: a flattened SRT table read through a null-resolved
48-bit pointer plus a small immediate offset landed at address 0x14.

Treat any raw-address read below a small guest null-page threshold
(0x10000) as reading zero, so the value (and whatever descriptor it
feeds) collapses to null instead of faulting materialization. Ported
directly from KytyPS5's identical fix for the identical symptom
("shader: treat a null-based SRT constant read as zero").

* shader: decode/emit VOPC 64-bit compares and the 64-bit LDS atomics

Ghost of Yotei's cs 0x80003AF600 emits V_CMPX_NE_U64 (VOPC 0xF5) and DS_ADD_U64/DS_OR_B64 (0x40/0x4A), which the decoder refused. The VOPC 0xE0-0xE7 / 0xF0-0xF7 forms are the same V_CMP/V_CMPX U64 operations the VOP3 table already names and the emitter already lowers, so mirror them into the two-operand VOPC table.

The 64-bit LDS atomics sit just ahead of DS_WRITE_B64. LDS is modelled as an array of dwords, so each is lowered to the two 32-bit atomics that make it up. Neither opcode returns the old value, which makes the split exact: only the final memory state is observable. OR is bitwise-independent, so two 32-bit ORs are the 64-bit OR; ADD folds exactly one carry per low-dword wrap into the high dword, so the high word converges on the true wrap count regardless of lane interleaving. The RTN variants cannot be split without losing the returned pair's atomicity, so they stay undecoded and keep failing loudly.

* shader: decode and emit VOP3-encoded 64-bit VOPC compares (0xE0-E7, 0xF0-F7)

Ghost of Yotei's material-lookup shader issues V_CMP_*_U64/V_CMPX_*_U64
through the VOP3 encoding (opcode < 0x100, the same numbering VOPC
uses), which DecodeVop3's own opcode table never listed - it only
handled VOP3-native opcodes >= 0x101. Ported the fix straight from our
own tree, which already special-cases this exact sub-range (see the
"VOP3-encoded 64-bit VOPC" comment there): decode 0xE0-E7/0xF0-F7 to
the VCmp*U64/VCmpx*U64 names, and emit them as a 64-bit integer
compare (or a constant true/false for the F/T pseudo-compares),
reusing the existing generic VCmpx exec-mask handling.

* shader: implement GFX10 double-precision conversions and arithmetic

Decode VOP1 0x04/0x0F/0x16/0x2F/0x30/0x34 and their VOP3-encoded forms (V_CVT_F64_I32, V_CVT_F64_U32, V_CVT_F32_F64, V_RCP_F64, V_RSQ_F64, V_SQRT_F64) and emit them as real SPIR-V doubles. A 64-bit result is split across the destination register pair: the low word goes through the common destination store and the high word is stored alongside it, both under the EXEC guard. V_MUL_F64 (VOP3 0x165) and V_FMA_F64 (VOP3 0x14C) are added as well.

Source absolute/negate modifiers and the VOP3 output modifier and clamp are honoured. FP64 inline constants and literals are rejected explicitly rather than read as the wrong bits, following the gfx10 expansion rules. The shaderFloat64 device feature is enabled only when the physical device offers it, and the Float64 capability is declared only for modules that use a double.

Ghost of Yotei compute shader 0x8000393600 (hash 0xB90E2024732C6111) now translates and compiles instead of stopping at decode.

* shader: decode and emit VOP2 V_PK_FMAC_F16

V_PK_FMAC_F16 (VOP2 0x3C) reads the destination register as the addend for both packed f16 lanes and writes D = S0 * S1 + D back as a packed pair. Reuse the existing single-rounding fused-f16 path per lane and merge the two halves, so the result matches the hardware's f16 fused behaviour.

Ghost of Yotei compute shader 0x8000393600 reaches this opcode after the decoder is past the f64 gap.

* shader: decode and emit VOP3 V_MAD_I32_I24

V_MAD_I32_I24 (VOP3 0x142) differs from the already-handled V_MAD_U32_U24 only in that both multiplicands are signed 24-bit values. Sign-extend each operand from bit 23, multiply with 32-bit wrap and add the third source.

Ghost of Yotei compute shader 0x8000393600 reaches this opcode after the decoder is past the f64 gap.

* shader(resources): address runtime-fetched buffer descriptors via the device-address page table

A V# whose four dwords are all read from a runtime scalar-memory address (a descriptor-array entry indexed by a loop counter, as in Ghost of Yotei's cs 0x80003F2A00 at pc=0x1B4) cannot be bound ahead of time, so resource tracking used to fail the whole shader. Its dword reads are not control-dependent in any other way: they are real guest memory loads emitted at runtime through the device-address page table.

Detect that case in ResourceTracker and mark the access RuntimeBufferDescriptor instead of building a dense buffer resource. The SPIR-V backend then builds the guest address from the descriptor's runtime base (SGPR pair), the MUBUF offset/soffset/vector offset and, when indexed, the descriptor's stride, and stores/loads through the existing physical-storage-buffer path. num_records/stride bound the access so a null or empty descriptor binds nothing, matching the static path's range check. Formatted, typed and atomic accesses keep failing loudly because their descriptor format cannot be reconstructed from the raw words.

This completes the former d8959888aafd2552 dispatch.

* shader(resources): recognize runtime V# descriptors built from SRT reads and loop phis

The first runtime-descriptor detection only accepted four direct raw ScalarAddressWord dwords. Ghost of Yotei's cs 0x8000289300 at pc=0x78C builds its V# from a loop-carried phi that merges two SBufferLoadDwordx4 reads of the SRT buffer at a dynamic offset, so the access was still rejected even though the descriptor is a real runtime value.

Make the detection generic: a BufferHandle is a runtime descriptor when each dword is a plan-time-known value (constant, user data, shader base, flattened table word) or a guest-memory read, recursing through phis, selects and operations, and at least one leaf is a raw read. A cycle through a phi is accepted the same way the read planner treats cyclic phis. Anything else keeps hard-failing.

The raw BufferLoadUshort at pc=0x78C now lowers through the device-address page table. The formatted BufferLoadFormatX at pc=0x18F4 correctly escalates to BoundedCandidateTable, which is still unimplemented and is now the next required feature.

* shader(resources): plan bounded runtime V# candidate tables

* shader(resources): materialize bounded runtime V# candidate tables

* shader(resources): address a runtime scalar buffer V# through the device page table

* shader(resources): model the guest V# separately from its Vulkan lowering; real 64-bit LDS atomics

Replace MemoryAccessInfo.RuntimeBufferDescriptor with a GuestBufferDescriptor that carries the V#'s provenance (Static or Runtime) and, for static descriptors, the dense-buffer index. The backend now chooses an explicit BufferLoweringStrategy from the descriptor and the access shape via GuestBufferDescriptor.ChooseStrategy, instead of hard-coding a single path: NativeBinding for compile-time descriptors, PhysicalStorageBuffer for a runtime descriptor used by a raw dword/subword access, and BoundedCandidateTable for a runtime descriptor whose format/type cannot be reconstructed from the raw words. The last one is not implemented and fails loudly with a precise reason, which is now a single, discoverable place rather than a boolean scattered through the translator.

DS_ADD_U64/DS_OR_B64 are now emitted as true 64-bit workgroup atomics when the device exposes shaderSharedInt64Atomics (queried through VkPhysicalDeviceShaderAtomicInt64Features, declared with the Int64Atomics capability, addressed through a 64-bit LDS view). The previous two-32-bit split is kept only as a documented fallback for devices without the feature; it is not atomic as a pair. Device setup reports the capability once.

* shader/image: accept 11_11_10 UNORM sampling and 8-bit sRGB render targets

The unified image format 30 is 11_11_10_UNORM, a packed 11/11/10 normalized float format the rendering side already knows as GuestPixelFormat.Bits11_11_10UNorm (mapped to VK_FORMAT_B10G11R11_UFLOAT_PACK32). It was missing from the shader-side sampled-format table, so cs 0x8000265D00 failed specialization with 'sampled image descriptor 7 uses unsupported format 30'.

Bits8Srgb and Bits8_8Srgb also had RenderTargetBytesPerElement = 0 while their element sizes are 1 and 2 bytes, which made TryGetTextureSize reject a RenderTarget-tiled 8-bit sRGB surface (tile mode 27) that the guest creates. Their render-target footprint is the same as the matching UNORM formats, and the host view already substitutes R8/R8G8 UNORM, so give them the real byte counts.

* shader: allow inline constants and literals as VOP3P packed-f16 operands

The VOP3P packed-f16 path only accepted register operands and refused constants, so cs 0x80003AF600 stopped at V_PK_FMA_F16 with src[128]. An inline constant or literal is a single f32 value rather than a packed register pair, so there is no half to select: both lanes take the exactly-converted f16 of that constant, with the lane's negate modifier still applied. Keeps the register path unchanged.

* shader: support Yotei f16 shifts bitsets and safe LDS atomics

* shader(vulkan): dispatch bounded runtime V# candidate tables

* shader: support additional LDS operations

* shader: support RDNA LDS store encoding

* shader: handle LDS operands and RDNA buffer cache op

* shader: extend strict GFX9 buffer and ALU decoding

* [VideoOut] added output mode events and profiling fixes

* [Gpu] fixed image, render, and dispatch handling

* shader: track SDWA and runtime buffer values

* [Shader] expanded Gen5 resource and interpolation support

* gpu: reconcile PR893 metadata and indirect dispatch

* gpu(commands): consume unknown NOP marker ids instead of failing the submission

Demon's Souls emits markers 0x6EC and 0x80C, which the interpreter treated as
fatal. The packets carry no work for the ids without a known side effect
(flips, user-data marker context), so they are consumed and each id is
reported once (command_stream.marker_ignored). voidborn, which gets much
further on this title, likewise ignores NOP marker payloads it has no
handler for.

* kernel: implement sceKernelGetOperationMode (PS5 base console, no submode)

The import was unresolved, so callers polling it (Demon's Souls does it at very
high frequency) got an unresolved-import result instead of the mode. Behaviour
matches KytyPS5 KernelGetOperationMode: *mode = 2, *submode = 0.

* loader: protect red-zone functions with enclosing patch spans

The red-zone patcher only built a trampoline span forward from the faulting
instruction, so it gave up whenever the next instruction was a branch target,
touched RSP, or changed control flow. Those refusals never reached FailedSites,
and the summary line reported sites=11472/11472 failed=0 while 777 faultable
instructions inside red-zone functions stayed unprotected.

Demon's Souls died on one of them. A GPU write-tracking fault at 0x800AD91F8
dispatched on the guest stack with RSP unshifted, which let Windows overwrite
the 128 bytes below RSP; the function reloaded its pointer from [rsp-0x10],
got 0, and faulted writing through it. The fault census confirms the same RSP
in the faulting frame as in the crash report.

Add TryBuildEnclosingPatchSpan: when the forward search refuses, take the
span's bytes from before the faulting instruction so the span ends at that
instruction and the one after it is never rewritten, which keeps every existing
branch into it valid. Two invariants make this safe and both are exercised by
the new tests:

  - the RSP shift brackets only the faultable core, so a relocated
    RSP-relative access still reads the slot it meant to;
  - a span may not start before the end of the previous site, because a
    backward span can otherwise reach instructions an earlier site already
    replaced with its own jump.

Patch() also refuses overlapping sites outright rather than corrupting guest
code, and the scan now reports unrelocatable sites by category.

Unprotected sites drop from 777 to 270. The title runs past the main menu into
its opening cinematic and stops on an unrelated shader resource error.

The fiber tests move into a shared collection: they mutate the same static
FiberExports state as FiberExportsTests, and xUnit runs separate classes in
parallel, which made them fail intermittently.

* cpu: count guest faults and make access-violation reports readable

Guest code is commonly mapped PAGE_EXECUTE, which no host read can satisfy, so
IsReadableHostRange refused it and the whole call-site section of the access
violation report was skipped: every crash in such a title printed "Could not
read code at RIP" and nothing else. Read those windows through the guest memory
view instead, and print the faulting region so a future refusal explains
itself.

Add a census of the recoverable faults taken on guest threads, by guest RIP and
by the handler that resolved them. Windows has no alternate exception-dispatch
stack, so each of those faults overwrites the 128 bytes below RSP; knowing which
guest instructions fault is what turns a red-zone corruption from a hypothesis
into a measurement. Counters are allocated once per RIP and are inert unless
SHARPEMU_FAULT_CENSUS is set, since the handler is hot.

Also fix the guest context transfer frame: it published the stack bounds in the
wrong order, storing the low address in NT_TIB.StackBase and the high one in
StackLimit on every fiber switch.

Note: these two files also carried uncommitted work from earlier in this
worktree (guest stack sizing, the 22-qword transfer frame, import and exception
paths); that work is included here rather than separated, because the hunks
interleave.

* gpu: stop handing image ownership to a buffer that was only bound

Binding a formatted writable buffer over an image's memory invalidated every
image covering that range. The shader only declares that it may write there, so
this fired on every bind of every draw, and when the range belonged to a render
target the image was refreshed from guest memory the GPU never wrote - its
tiled backing read as linear, over the frame that had just been rendered.

A Demon's Souls capture showed 412 buffer-dirty refreshes and 207 GB of image
traffic. Suppressing the two bind-time call sites removed the banding entirely
and took the frame time from 7471 ms to 4272 ms, with GPU occupancy going from
29% to 45%; buffer-dirty refreshes dropped to zero.

Writes that actually happened still invalidate, from GuestBufferCache's fill and
DMA-copy paths, which is what the image ownership tests cover. The declared-write
case needs a reconciliation after the submission, once the GPU-modified ranges
are known; that does not exist yet, so
SHARPEMU_SPECULATIVE_IMAGE_INVALIDATION=1 restores the old behaviour meanwhile.

Note: these two files also carried uncommitted work from earlier in this
worktree.

* shader: name the instruction behind an undefined descriptor dword

A descriptor dword that resolves to an undefined value fails the resource plan
with nothing to act on. Undefined nodes are interned per type, so one shared
node stands for every instruction the builder could not model, and the builder
has about twenty places that produce one: the message names a category where it
should name an opcode.

Record the instruction the builder is translating, and under
SHARPEMU_RESOURCE_TRACKER_DIAG intern undefined nodes per instruction as well.
Revisiting a block reaches the same instruction, so values stay as stable across
visits as before, and the rejection can walk the failing dword back to the
undefined leaves it rests on and name them. Without the diagnostic the interning
and the message are unchanged.

A first attempt recorded every undefined created while an instruction was in
flight, which listed 1410 instructions for one shader because many are created
legitimately, for SCC among others. Only the leaves the failing value actually
depends on are reported.

Test counts are unchanged at 614 passed and 95 pre-existing failures.

* shader: carry over the Gen5 translation work from the port

Pre-existing work in this worktree, recorded here rather than authored in this
session: VOP3B carry/borrow paths for VSubCoCiU32 and VSubrevCoCiU32, the high
opcode bit for MIMG and the ray-tracing names it selects, DS/GDS flags, the
wave32 EXEC mask, and the scalar value graph and runtime evaluator changes that
go with them, across the Vulkan and Metal back ends.

* gpu: carry over the AGC and VideoOut work from the port

Pre-existing work in this worktree, recorded here rather than authored in this
session: tracked image caching in place of unused snapshots, depth/stencil
handling, snapshot reuse for feedback loops, read-only depth, the Vulkan
feedback target resolver, the GuestDepth and draw recording paths, GPU barriers
and image cache tracking, the storage resolver, BDA and GPU memory tracking in
VideoOut, and the submission and indirect dispatch corrections.

* hle: carry over the library export work from the port

Pre-existing work in this worktree, recorded here rather than authored in this
session: an HTTP connect timeout, an offline MAC, NP signalling context, the
asynchronous NP stubs and entitlement key, NP WebAPI context deletion, a CFW
stub, and the added aliases for sceImeKeyboardGetInfo, unlink, sched_yield,
scePadResetOrientation, scePadIsRemoteController and the solo scheduling
attributes, along with the Ampr file registry and kernel compatibility exports.

* cpu: carry over the guest execution and memory work from the port

Pre-existing work in this worktree, recorded here rather than authored in this
session: the guest stack size the pthread attribute asks for and its cap, the
PthreadYield fallback, the multi-thread stall watchdog, import and handoff
instrumentation, the host view and guest space owner changes, and the physical
virtual memory and runtime adjustments that go with them.

* base: verified pre-rebase gameplay baseline

* fix: materialize indirect shader image resources

* gpu,shader: Yotei bring-up changes (GPT session, unreviewed)

Work from a separate GPT session aimed at Ghost of Yotei, committed as-is so
it can be reviewed block by block. It clearly improves Demon's Souls rendering
(the scene becomes visible), but review found problems to fix before a PR:

- S_BITREPLICATE_B64_B32 copies the dword into both halves; the ISA doubles
  each bit (bit i -> bits 2i, 2i+1). Wrong in the translator and the graph.
- FindMatchingMipLevel no longer matches 2D array views of 3D volumes; the
  test expectation was flipped from 0 to -1 instead of keeping the case.
- VectorCompareResourceTests lost five passing tests, replaced by
  compile-only smoke tests.
- 11_11_10 UNorm is mapped to a packed-float Vulkan format.
- Unknown BufferLoadFormat descriptors now read zeros / null images silently.
- Undefined scalar values are interned per instruction for every title.

Also adds 16-bit integer VOPC compares, V_RSQ_F16, IMAGE_GATHER4_C_LZ,
IMAGE_SAMPLE_A decode, Yotei base entitlements, richer image-creation
failure messages and flip-source diagnostics.

* diagnostics(renderdoc): capture automatically after N guest flips

SHARPEMU_RENDERDOC_CAPTURE_AFTER_FLIPS=N requests one capture once N guest
flip boundaries have been seen, so an unattended run can capture without the
F12 key. The count is kept here because the flip image version is per display
buffer, not a global frame index.

* fix(import-guard): recognize polling progress boundaries (#895)

* port shader GDS byte reads and float atomics

* port kernel debug exception exports

* Revert "port kernel debug exception exports"

This reverts commit 71d605e82c82a4a921862bfc865a104305cf746d.

* Revert "port shader GDS byte reads and float atomics"

This reverts commit 8f4e7664d7fc81a28ba5c47c86cbc03dba61d47b.

* [usbd] implement event timeout

* [usbd] add initialization stubs

* [share] add feature and overlay exports

* [kernel] implement pthread cond signalto

* [font] add renderer and kerning exports

* [agc] implement ACB rewind exports

* [share] add running status export

* [stubs] add game service exports

* [kernel] implement high resolution timer events

* [gpu] support polygon primitive rendering

* chore: bump version to 0.0.4

* [kernel] implement debug exception exports

* [vulkan] adopt synchronization2 submission and barriers

* docs: update upstream README

* Revert "[vulkan] adopt synchronization2 submission and barriers"

This reverts commit 7e44f90d624c05ccac3ba8d14f94bfa63c225b1d.

* Reapply "[vulkan] adopt synchronization2 submission and barriers"

This reverts commit 66b456dd2a4562b081970404034ab3cc3135f66a.

* Revert "[kernel] implement high resolution timer events"

This reverts commit faf105d9a0f93120f9954ff30890701683d79a67.

* Reapply "[kernel] implement high resolution timer events"

This reverts commit 9858506307cfdc43e875180f21727bea936ac888.

* Revert "[kernel] implement pthread cond signalto"

This reverts commit 83ad3b0ac313b01c7d1493e685423a5a419b8766.

* Reapply "[kernel] implement pthread cond signalto"

This reverts commit 4bc31dabf7885687dc3777aefede6b4122f936a4.

* [ampr] fall back to executable-local index cache

* [ampr] probe cache directory writability

* [media] keep host movies progressing when Vulkan bindings lag

* [media] release stalled host movies independently of rendering

* [vulkan] reuse pipeline caches across emulator builds

* [media] propagate stalled movie EOF to the guest

* [ampr] return guest path ids from resolve exports

* Revert "[vulkan] reuse pipeline caches across emulator builds"

This reverts commit f0f8e21382b402b510645acb66cf122ad765cd4a.

* [vulkan] reuse pipeline caches across emulator builds

* Revert "[vulkan] reuse pipeline caches across emulator builds"

This reverts commit fd667b6320b74be1e5d71b37a3c29fc11af713bb.

* fix(media): honor full Bink movie duration

* fix(vulkan): schedule wave64 barriers uniformly

* fix(vulkan): emit wave64 loop merge before branch

* fix(vulkan): predicate wave64 image stores

* Revert "fix(vulkan): predicate wave64 image stores"

This reverts commit 5842298743822d1b128e8b4dcb3f9d0a8fd03c35.

* Revert "fix(vulkan): emit wave64 loop merge before branch"

This reverts commit d2b98e0a8439e41c8faf04335320f202740fe7f9.

* Revert "fix(vulkan): schedule wave64 barriers uniformly"

This reverts commit 976d5a5957080de6568417c9d12235570b1503ef.

* baseline: preserve good Demon's Souls rendering

* Revert "[vulkan] adopt synchronization2 submission and barriers"

This reverts commit 7e44f90d624c05ccac3ba8d14f94bfa63c225b1d.

* fix(ampr): keep APR resolve ids unique on path-hash collisions

The APR resolve exports now return the 31-bit FNV hash of the guest path
(upstream 5c347d2c). Demon's Souls ships 13 same-hash file pairs under
/app0/ alone, and it resolves 32 colliding paths before the main menu.
Colliding files shared one id and read each other's bytes, and the
first level load after the menu never finished.

Hand out the hash only while it names one host file, keep it in an
authoritative table that later alias publishes cannot poison, and fall
back to a collision-safe Register() handle otherwise. Log both the
fallback and reads of unknown ids, which previously failed silently.

* fix(media): size the Bink watchdog to the movie, restore Gen4 export

c77746bd reverted the duration-based Bink watchdog to a fixed 90 s, and
MediaFramePlayback also capped every movie (AvPlayer too) at 120 s.
Demon's Souls ships a 171 s intro, 117-138 s attract movies and 756 s
credits, which both bounds cut short. Arm the watchdog for the movie
length plus 60 s (minimum 90 s, no upper bound) and drop the fixed
playback cap, as in the pre-rebase baseline.

Also keep cfwBSQyr5Ys exported for Gen4 as the baseline did.

* chore: trim added comments, keep upstream's cfwBSQyr5Ys target

Restore upstream's APR ABI comment verbatim, keep each added comment to
one line, and leave sceKernelDebugWriteCppExceptionInfo Gen5-only as
upstream declares it.

* fix(vulkan): query image levels only when the result is read

ImageGetResinfo emitted OpImageQueryLevels for every sampled image even
when the dmask never reads component 3. On a 3D image, the unused query
crashes NVIDIA's pipeline compiler inside vkCreateComputePipelines:
Demon's Souls compute shader 0xC553F37E46481A6B (dmask 3) takes the
process down right after the first frame.

* fix(shader): reconcile branch resource planning with upstream contracts

- Keep upstream's 42-binding image ABI. A float min/max image atomic
  binds as the uint atomic image its compare-exchange loop already uses.
- A null-page read turns into zero only after a null base pointer fails
  to read, not for every address below 0x10000.
- An empty V# reads as zero; overrunning a bound buffer stays a failure.
- A wave-indexed image table reads its mask through the clean reader,
  like every other slot its descriptor depends on.
- A buffer load through a lane-varying address stays undefined, and a
  constant exec_lo write keeps s_cbranch_execz decidable.
- An image descriptor from a scalar buffer must be one contiguous
  record, and a vector access through a draw-evaluable V# keeps its
  explicit binding instead of the runtime-descriptor path.

* fix(shader): drop the null-page read rule, encode GDS at bit 17 in tests

KytyPS5 does not turn a null SRT pointer read into zero: an unmapped
read fails materialization, which FailedResourceReadsRemainFatal pins.
Remove the rule.

GFX10 DS encodes GDS at bit 17 with OP at 25:18; bit 16 is GFX9's
position (LLVM DS_Real_gfx10). The decoder already reads bit 17, so the
DataShare tests set GDS there, and the LDS wave-counter tests clear it.

* fix(vulkan): keep bind-time image invalidation coherent with GPU contents

Binding a formatted writable buffer over an image marks the image
buffer-modified, so its next use refreshes it from the buffer. The
buffer was only synchronized from the image for read-only views, so
over a render target a writable view refreshed the image from stale
guest bytes. That is the Demon's Souls banding 48cd723a worked around
by skipping the invalidation altogether.

Synchronize writable formatted views from the image as well, and at bind
time invalidate every covered image except one whose only current copy
is still on the GPU and could not be copied into the buffer. Drop the
SHARPEMU_SPECULATIVE_IMAGE_INVALIDATION switch.

* test(shader): expect the header immediate in the indirect selector offset

The materializer reads the selector word at selector * stride +
SelectorOffset, so the offset must include the material read's
immediate. With the header immediate 4 and the +4 in the address
arithmetic it is 8; the test expected 4 since it was written.

* chore: remove session diagnostics

Drop the ad-hoc instrumentation added while bringing Demon's Souls and
Ghost of Yotei up:
- SHARPEMU_RESOURCE_TRACKER_DIAG traces in the resource tracker, dense
  image tables, runtime evaluator and validator
- unconditional materialize_snapshot_probe warnings and the
  SHARPEMU_TRACE_INDIRECT_IMAGES wave-table trace
- the guest fault census (SHARPEMU_FAULT_CENSUS / SHARPEMU_FAULT_WATCH)
- red-zone patcher address trace, unprotected-site file and short-jump
  estimate (SHARPEMU_REDZONE_TRACE / SHARPEMU_REDZONE_SITES_FILE)
- RenderDoc capture after N flips and the flip-source refresh traces

The red-zone coverage counters and the readable access-violation report
stay.

* fix(kernel): sparse virtual reservations and HLE compatibility fixes

- sceKernelReserveVirtualRange: keep very large non-fixed reservations
  in the guest mapping table without one contiguous host placeholder.
- Event flags: read names and write handles through the kernel
  compatibility helpers so read-only image pages work.
- sceAjmInitialize: only validate the 32-bit reserved argument.
- Pthread: only free opaque objects the compatibility layer allocated.
- Route libc memset to the HLE implementation (int 45h path in LLE).

* fix(gpu): tiling, color targets, VideoOut pipelines and primitive stage masks

- Tile geometry and tiler shader fixes for detiling.
- Color target request fixes for 3D slice ranges.
- VideoOut descriptor, draw recording and pipeline fixes.
- Accept the compact primitive-shader stage masks (0x2000, 0x2030)
  and report the rejected state when draws are skipped.

* fix(shader): Little Nightmares III decoding and translation

- Decode S_SCRATCH_STORE_DWORDX2 (SMRD op 0x16) as a scalar scratch
  store instead of a load.
- Raise the decoder instruction limit to the maximum shader size.
- Stop linear decoding at S_SETPC_B64 and treat the tail as
  unreachable; find fused GS pairs across the full 1 MiB object and
  resolve continuation image descriptors from its entry SGPRs.
- Translate S_SETPC_B64 in linearised fused programs as a marker.
- Implement S_MUL_HI_I32 and V_DOT2C_F32_F16.

* fix(shader): Astro Bot debugger stubs and wave32 primitive pipelines

- S_CBRANCH_CDBG* only fire under an attached GPU debugger; do not let
  them extend the decoded program past S_ENDPGM into debugger stubs
  that load descriptors from devkit-only addresses.
- Stop decoding after an unconditional backward S_BRANCH with no
  pending forward target instead of reading the "sl00" metadata
  trailer as instructions.
- Ignore the GS_W32_EN/VS_W32_EN wave-size bits when matching
  primitive-shader stage masks; graphics stages always compile as
  wave32, so these draws were skipped for no reason.

* fix(kernel): keep sparse reservations out of the macOS graphics window

macOS reserves 0x10_0000_0000..0x70_0000_0000 for graphics memory, so a
sparse reservation starting at 0x40_0000_0000 could not be mapped there
(LargeVirtualReservationFallsBackToSparseGuestRange failed on osx-x64).
Start the sparse aperture at 0x70_0000_0000 on macOS.

* fix(shader): declare guest 1D textures as 1D images

Since #903 the image request builder binds guest 1D textures through
1D Vulkan views, but the SPIR-V translator still declared them as 2D.
Sampling a 1D view through a 2D image type is invalid, and on NVIDIA
Astro Bot's final composite pass wrote black to the flip buffer.
Declare Dim1D/Dim1DArray as SPIR-V 1D images with Sampled1D/Image1D,
matching upstream.

* revert(gpu): fail on written device-address ranges that cannot be planned

The Little Nightmares III work skipped unplannable written ranges on the
host and allowed those stores unconditionally in the shader. Stores whose
pages were not already in the BDA page table were then dropped silently.
Restore upstream's fatal and remove UnplannableWrittenMemoryIndices, so
each such shader is reported and fixed on its own.

* fix(shader): route FLAT accesses through the LDS and scratch apertures

GFX10 sends a FLAT access to LDS or scratch when bits 63:48 of its
address match the shared or private aperture (SH_MEM_BASES), and to
global memory otherwise. src_shared_base/src_private_base decoded to 0,
so an LDS pointer became a device address the host could not plan
(Little Nightmares traversal stack, shader 0x9669394374AAE5BC).

- Give the apertures distinct high dwords no guest address can reach,
  consistent for 32-bit (high dword) and 64-bit (address) reads.
- Keep aperture identity in the scalar value graph (MemoryAperture).
- The device-address planner classifies a FLAT access whose high dword
  derives from an aperture and gives it no device range; plain device
  pointers keep the range and the fatal.
- The SPIR-V backend routes each lane by bits 63:48 to LDS, scratch or
  the page-table global path.

* fix(shader): use the PS5 apertures and prove clamped FLAT addresses local

Little Nightmares builds its BVH traversal-stack addresses without
src_shared_base: v_med3_u32(0x80000000, 0x70000000, sp) clamps the high
dword, and the pop path reads the 0x7xxxxxxx entries with ds_read and
the 0x8xxxxxxx ones with scratch_load. These are the PS5 shared and
private apertures, so:

- SharedApertureHigh = 0x70000000, PrivateApertureHigh = 0x80000000;
  the backend routes a FLAT lane by the top nibble of its high dword.
- Model v_med3_u32/v_med3_i32 in the scalar value graph.
- The planner bounds the high dword (Phi/Select/UMin/UMax, cycles
  ignored since each yields one of its inputs) and classifies it local
  when the bounds stay inside the apertures.
- Record the EXEC mask of each global/flat access: a select on that
  same mask is a lane-masked register write whose inactive arm never
  reaches a lane that accesses memory.

Little Nightmares shaders 0x9669..., 0xE9F1... and 0x392B... now plan
without the removed unplanned-write skip.

* feat(shader): emulate the GFX10 BVH node test for image_bvh_intersect_ray

Follows AMD GPURT's RT IP 1.1 fallback: triangle nodes 0/1 with barycentric
swizzle, FP16/FP32 box slab test with grow, NaN rule and sorting network.
Nodes are read through the device-address page table.

* fix(gpu): move a depth surface sampled as color into a color image

A texture whose format reads neither the depth nor the stencil plane
reinterprets the memory; Little Nightmares samples a D32S8 surface as BGRA8.

* fix(shader): make s_barrier order buffer and image memory within the workgroup

Guest code publishes stores to its workgroup with s_waitcnt vmcnt(0) +
s_barrier; the host barrier only ordered shared memory.

* fix(gpu): match sampler border color and filtering to the sampled view

Border colors were always integer, which Vulkan leaves undefined on float
views (an opaque-white border can read back as black). The numeric class
now comes from the bound view formats; integer views also drop filtering.

* fix(shader): decode the VOP1 and VOP3 half-precision transcendentals

Silent Hill's pixel shaders use v_log_f16; add rcp, sqrt, log, exp,
floor, ceil, trunc, rndne, fract, sin and cos for f16 in both encodings.

* fix(shader): decode v_min3_f16, v_max3_f16 and v_med3_f16

Silent Hill's pixel shaders use v_min3_f16; the draw was skipped.

* fix(agc): give a fused shader the larger user SGPR count of its halves

sceAgcFuseShaderHalves kept the back half's SPI_SHADER_PGM_RSRC2_GS, so a
front half reading its vertex tables from user SGPRs 26-29 saw only 12
(Silent Hill: front 30, back 12) and the draw faulted on null tables.
A vertex program without input semantics no longer needs the tables, and
the null-table and PRT-mapping messages name the registers involved.

* fix(shader): read buffers past the binding budget through their device descriptors

A Silent Hill compute shader binds more than 32 distinct buffers. Past the
budget an untyped access reads its V# from its SGPRs at run time, as the
hardware does, instead of failing the whole shader.

* fix(gpu): treat a compute constant fill of DCC metadata as a fast clear

Unreal fast-clears DCC render targets with a compute shader that stores one
dword from a constant buffer into every metadata record (0x40404040 clears
to 0,0,0,1). The fill only reached plain memory, so Silent Hill's
translucency layer stayed 0 and its composite multiplied the scene by 0.
The exact instruction sequence is recognised and routed to the existing
DCC clear tracking.

* fix(gpu): invalidate every image under a bound writable formatted buffer

The bound-write path skipped images whose only current copy was on the GPU.
Such an image kept stale contents after the guest rewrote it through the
buffer: Dead Cells lost its background and Demon's Souls showed wrong
surfaces. Bisected to ed3de27b; upstream invalidates unconditionally, and
52fbe275 already synchronizes writable views from the image, which fixed the
banding the exception was meant to hide.

---------

Co-authored-by: ParantezTech <12572227+par274@users.noreply.github.com>
Co-authored-by: Mathias <06mathias6@gmail.com>
2026-09-27 20:43:07 +03:00
.github [readme] new screenshots 2026-09-20 05:00:39 +03:00
LICENSES [GUI] Redesign options page (#699) 2026-07-30 12:58:22 +03:00
assets/images [asset] upload transparent logo image 2026-08-03 10:43:43 +03:00
docs [GUI/CLI] Add option to disable Vulkan implicit layers (#900) 2026-09-23 16:08:19 +03:00
scripts Alpha Rebuild Progress (#887) 2026-09-20 04:09:27 +03:00
src Fix for DS,Little Nightmares, Little Nightmares III and Astro Bot shader (#913) 2026-09-27 20:43:07 +03:00
tests Fix for DS,Little Nightmares, Little Nightmares III and Astro Bot shader (#913) 2026-09-27 20:43:07 +03:00
tools Alpha Rebuild Progress (#887) 2026-09-20 04:09:27 +03:00
.editorconfig [dotnet] configure local nuget package cache 2026-07-04 19:12:18 +03:00
.gitignore [AGC] Fix arena/fence tracking stalls and recover lost GPU progress (#770) 2026-08-07 14:37:09 +03:00
CONTRIBUTING.md Enhance contribution guidelines with PR expectations 2026-07-23 01:40:00 +03:00
Directory.Build.props chore: bump version to 0.0.4-release.2 (#908) 2026-09-26 01:14:40 +03:00
Directory.Packages.props Alpha Rebuild Progress (#887) 2026-09-20 04:09:27 +03:00
LICENSE.txt initial commit 2026-03-11 15:48:28 +03:00
README.md [readme] update 2026-09-22 04:29:46 +03:00
REUSE.toml [GUI] Redesign options page (#699) 2026-07-30 12:58:22 +03:00
SharpEmu.slnx Sdl backend (#670) 2026-07-28 03:33:26 +03:00
global.json [dotnet] remove lock files (#419) 2026-07-19 01:26:01 +03:00
nuget.config [dotnet] configure local nuget package cache 2026-07-04 19:12:18 +03:00

README.md

SharpEmu

An experimental PlayStation 5 emulator for Windows, Linux and macOS.


Join our Discord

Join our official Discord server for development updates, compatibility discussions, support, and community chat.


Support SharpEmu


[!NOTE]
SharpEmu supports Windows x64, Linux x64, and macOS x64. Apple Silicon Macs can run the macOS x64 build through Rosetta 2, and Windows on ARM devices (e.g. Snapdragon) can run the Windows x64 build through Windows' built-in x64 emulation.

[!WARNING]
SharpEmu is an experimental PS5 emulator developed from scratch in C#. The current focus is on accuracy and infrastructure setup rather than game-specific compatibility.

Info

SharpEmu is an emulator project currently in its early stages of development.

This project is developed purely for research and educational purposes. There are no commercial goals associated with it. We enjoy learning about system architecture and reverse engineering.

SharpEmu focuses exclusively on the PlayStation 5.
Our goal is not to emulate PS4 games, as there is already an excellent emulator dedicated to that platform: ShadPS4.

Games Tested

Demons Souls Remake Tomb Raider V Remastered
DeS screenshot Tomb Raider V
Hades Dead Cells
Hades Dead Cells
PAC-MAN World Re-PAC Cult of the Lamb
Pac-Man Cult of the Lamb

Status

The emulator can currently load the eboot.bin of real games, execute native CPU instructions, and partially handle gpu-related functionality. Included 3D games.

SharpEmu supports Windows, Linux, and macOS hosts. Video output uses Vulkan on Windows and Linux, and MoltenVK on macOS. Platform support is still experimental, so compatibility and performance vary by game, operating system, and GPU driver.

Using

Download the release archive for your operating system, extract it, and launch SharpEmu with the path to a legally obtained game's eboot.bin.

Or command line;

Windows PowerShell:

.\SharpEmu.exe "C:\path\to\game\eboot.bin" --log-to-file

Linux and macOS:

chmod +x ./SharpEmu

./SharpEmu "/path/to/game/eboot.bin" --log-to-file

SharpEmu supports environment variables that can be configured from the GUI. Undocumented variables are listed here: docs/sharpemu-gui-undocumented-env-vars.md

You can set them per game or globally in the GUI, or pass them directly through the CLI.

A Vulkan-capable GPU and current graphics driver are required. The macOS release includes the MoltenVK Vulkan implementation.

[!IMPORTANT]
This project does not support or condone piracy.
All games used during development and testing are dumped from consoles that we personally own.
Users are expected to use legally obtained copies of their games.

Build

  1. Install the .NET SDK version specified in global.json.
  2. Clone the repository: git clone https://github.com/sharpemu/sharpemu.git
  3. Open the solution file (SharpEmu.slnx) in VSCode.
  4. Build the project: dotnet build or dotnet publish
  5. Build artifacts will be located in the artifacts directory.

Disclaimer

SharpEmu is an experimental emulator intended for research and educational purposes.

This project does not contain any copyrighted system firmware, game data, or proprietary PlayStation assets.

Special Thanks

The following projects were extremely helpful during development:

  • ShadPS4
    Helped with understanding the basic architecture of the PlayStation 4.

  • Kyty
    One of the few PS5 emulator projects available and very useful for studying native code execution.

  • Ryujinx
    Provided valuable references for filesystem handling and low-level C# implementation patterns.

License

Support

Support SharpEmu via GitHub Sponsors or cryptocurrency. Every contribution helps fund ongoing development and long-term maintenance. GitHub Sponsors is the preferred way to support the project, but cryptocurrency donations are also appreciated.

ETH/USDT

0xF315F5d986c790bB3A58DbE60F1B2760997dEd82

BTC

bc1qmr9k8899njys5ny63xsues4jgmkk96erslrkmv

Contributing

Before opening an issue or pull request, please read our contribution guidelines:

CONTRIBUTING.md

The guide covers:

  • Coding style and formatting
  • AI-assisted contributions
  • Pull request expectations
  • Testing guidelines
  • Legal and reverse engineering policy