mirror of
https://github.com/par274/sharpemu.git
synced 2026-08-30 13:24:19 +08:00
f4f36b558f731198e6b2ed86dad449318985285d
502 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
f4f36b558f | fix(agc): refine vertex layouts without rebasing captured inputs (#841) | ||
|
|
2b8ef7d8fa |
shader: add compact f16 arithmetic and compare lowering (#840)
Co-authored-by: Foued Attar <attar.foued@gmail.com> |
||
|
|
f8a826ec1b |
fix(agc,video): CMASK/DCC metadata state machine , remove heavy heavy motion trails in DeadCell (#836)
* fix(agc): add support for CMASK fast clear in rendering process * fix(agc): prevent stale cross-frame label writes from bypassing WAIT_REG_MEM Add frame-ID tracking to GpuWaitRegistry so that WAIT_REG_MEM in frame N+1 is not satisfied by a label written in frame N. Previously, a label that persisted in guest memory from the previous frame could satisfy a new WAIT_REG_MEM immediately, causing the waiting DCB to bypass its fence and execute out of order. Changes: - GpuWaitRegistry: add _labelFrameIds, _currentFrameId, AdvanceFrame(), IsLabelFresh(); RecordProduced now stamps the frame ID on each write - AgcExports: call AdvanceFrame() at RFlip (frame boundary); HandleSubmittedWaitRegMem checks IsLabelFresh before bypassing This is one piece of the Dead Cells character visibility fix (issue #833). The cross-frame label reuse could cause composite passes to execute before character-layer passes, resulting in invisible characters. * fix(video): simulate CMASK 'all clear' at frame boundary to prevent trails On real PS5, CMASK is reset to 'all clear' at each frame boundary, so render targets are effectively cleared. SharpEmu did not implement this behavior, causing GBuffer targets to LOAD stale data from the previous frame, resulting in visible trails/ghosting. Reset Initialized=false for all offscreen guest images at the present boundary (when _currentFrameSlot changes). This ensures every render target starts each frame with LoadOp.Clear, matching the hardware's CMASK behavior. This is the trail fix for Dead Cells (issue #833), complementing the cross-frame label staleness fix in GpuWaitRegistry. * fix(video): only reset render targets (not textures) for CMASK simulation Previous commit 89b4610 reset Initialized for ALL guest images, including textures and storage images, causing a completely black screen. Only images with a RenderPass (actual color render targets) should be reset, as CMASK 'all clear' only applies to color render targets, not sampled textures. * Revert "fix(video): simulate CMASK 'all clear' at frame boundary to prevent trails" This reverts commit 89b4610a015619848e4000372822dca1c08051ff. * fix(agc): implement CMASK state machine for render target clearing Implement CMASK (Color Mask) hardware behavior based on shadPS4's approach: 1. Track CMASK addresses from CB_COLORn_CMASK registers 2. Detect compute shaders that write to CMASK addresses (IsComputeMetaClear heuristic: no bitwise XOR in shader = clear shader) 3. Mark CMASK as 'all clear' when compute shader writes to it 4. EliminateFastClear mode now checks CMASK state before clearing This addresses the trail/ghosting issue in Dead Cells (issue #833) where 642x362 GBuffer targets were never cleared after first use, causing stale data from previous frames to persist. Reference: shadPS4's IsComputeMetaClear + EliminateFastClear implementation. * fix(agc): implement CMASK state machine for render target clearing Based on shadPS4's PM4 implementation, add CMASK (Color Mask) hardware behavior tracking: 1. TrackCmaskAddresses: Read CB_COLORn_CMASK registers to get CMASK addresses 2. CheckCmaskWrite: When DMA fill targets CMASK address with value 0, mark CMASK as 'all clear' (shadPS4's FillBuffer logic) 3. EliminateFastClear: Check CMASK state before clearing - only clear if CMASK is 'all clear', then mark as 'dirty' 4. DMA fill: Call TrackCmaskAddresses before processing to ensure CMASK addresses are registered when DMA fills happen This addresses the trail/ghosting issue in Dead Cells (issue #833) where 642x362 GBuffer targets were never cleared after first use. Reference: shadPS4's IsComputeMetaClear + EliminateFastClear + FillBuffer. * refactor(agc): remove diagnostic CMASK traces from hot path Remove 4 diagnostic TraceAgcShader calls that were added during CMASK investigation: - agc.cb_regs=[...] - allocated List<string> + string.Join on every draw - agc.cmask_track - per-slot interpolated string on every draw - agc.cmask_write - per-DMA-fill interpolated string - agc.eliminate_fast_clear - per-draw interpolated string These were debugging probes and are no longer needed. * fix(agc): sync constant-fill zero writes to Vulkan render targets Dead Cells GBuffer trails: the constant-fill compute kernel (TrySubmitConstantFillKernel) wrote zeros to guest memory but never invalided the host Vulkan GuestImageResource that backs the render target, so the GBuffer pass kept LoadOp.Load stale previous-frame pixels. After the guest write, request a guest color clear尷 ... clear for the destination address (RequestAgentColorClear projection (clear)) so the next render pass uses LoadOp.Clear. The pending clearandray mechanism already exists; the fill kernel was simply bypassing it. * Remove AGC lifecycle debug probes Remove transient render-target lifecycle and indirect draw debug instrumentation from AgcExports. The change strips the noisy console probing while keeping the active render-target tracking logic intact for draw sequencing and validation. * fix(video): clear frame-stale MRT groups at guest flip boundary On hardware a colour surface whose fast-clear metadata is in reset state reads back as the CB clear value instead of stale memory, so games can leave per-frame MRT groups uncleaned and rely on that implicit initialization. With no metadata layer, such a target keeps whatever touched it last; when the group shares addresses with the compositor's output, the entire finished previous frame bleeds through every region the new frame does not repaint (Dead Cells dungeon trails / ghosted characters). Arm a reset at the guest's own flip command - the authoritative frame boundary inside the submission stream - and let the first multi-attachment colour group of the fresh frame consume it, starting from LoadOp.Clear. One arm per flip yields exactly one reset per guest frame regardless of CPU/GPU pipelining; later groups keep load semantics so intra-frame pass chaining is untouched. CPU-backed images are never touched. Mirrors shadPS4's meta-state-driven attachment.is_clear at render-pass begin (vk_rasterizer.cpp BeginRendering). * fix(agc): correct CbColor0Cmask register offset and add EXT support CbColor0Cmask was 0x320 (CMASK_SLICE, tile_max:14) instead of 0x31F (CMASK_BASE_ADDRESS). TrackCmaskAddresses read the slice count instead of the base address, so CMASK registration always produced garbage or zero — the entire meta-state chain starved for every game. Fix: 0x320 → 0x31F. Add CbColor0CmaskBaseExt (0x398) and decode the full 48-bit address: ((ext & 0xFF) << 40) | ((low & 0x1FFFFFFF) << 8). Phase 0 probe confirmed Dead Cells programs neither CMASK nor DCC registers (all zeros) — it relies on unified memory semantics where unwritten surfaces read as zero. The register offset fix is still needed for games that do use CMASK metadata. * feat(agc,video): CMASK/DCC meta-state machine with CLEAR WORD support Replace the flat _cmaskClearedState dictionary with a proper meta-state ledger that separates registration, clearing, and dirtying: - MetaSurfaceInfo keyed by colour-buffer address (not meta address) - Reverse map _cmaskToColorBuffer for fill/compute write detection - TrackCmaskAddresses reads both CMASK (0x31F) and DCC (0x325) with EXT high-bit decoding; prefers CMASK, falls back to DCC - CLEAR_WORD captured at registration time, passed through to BeginTranslatedRenderPass as VkClearValue - EFC checks specific surface (not 'any cleared → clear slot0'), dirties only that surface (not entire table) - CheckCmaskWrite uses reverse map for O(1) lookup - Presenter bind loop queries IsMetaClearedForSurface → LoadOp.Clear - _metaStateOverridesFlipArm flag gates flip-arm for gradual retirement Phase 0 probe confirmed Dead Cells programs neither CMASK nor DCC (all registers zero) — flip-arm remains the correct fix for that game. The meta-state machine serves games that do use CMASK/DCC metadata. * fix(agc,video): implement surface clearing at guest flip boundary * refactor(video): remove flip-arm heuristic, meta-state machine fully replaces it The flip-arm (_frameColorResetArmed) was a heuristic that cleared the first multi-attachment colour group at guest flip boundary. The CMASK/DCC meta-state machine now handles all clearing: at flip time MarkAllSurfacesCleared() marks every registered surface as cleared; at bind time IsMetaClearedForSurface() triggers LoadOp.Clear and consumes the state. This replaces the flip-arm with a per-surface state machine that correctly handles both explicit clears (DMA fill, compute, EFC) and implicit frame-boundary resets. Also fixed a bug where TrackCmaskAddresses overwrote IsCleared to false on every call, defeating the frame-boundary reset. Now preserves existing IsCleared state when re-registering. * Thread-safe meta-surface state & meta-clear decode Add synchronization for metadata state: introduce _metaSurfaceGate and guard accesses to _metaSurfaces and _cmaskToColorBuffer in AgcExports to avoid concurrent-dictionary corruption and ensure small critical sections. Preserve cleared state only when metadata binding matches during re-registration. Make Gen5 texture format constants internal. Update logic to set/consume IsCleared under lock and check CMASK nearby windows safely. Decode meta clear values in VulkanVideoPresenter: skip CPU-backed targets for meta clears, add UnpackMetaClearValue and HalfToFloat to convert CLEAR_WORD0/1 into proper ClearColorValue for R8G8B8A8_UNORM and R16G16B16A16_FLOAT formats (with an 8_8_8_8 fallback). This ensures correct clear colours and thread-safe metadata handling. |
||
|
|
4a7a45d1b3 |
Fix TLS-load patcher corrupting short jumps immediately before FS:[0] reads (#838)
The linear scanner consumed leading 0x66 bytes without checking that the candidate starts on an instruction boundary. A short jump whose disp8 is 0x66 (EB 66) directly followed by a 66-prefixed FS:[0] load made the patcher treat the displacement as a prefix and write its call opcode over it, rewriting 'jmp forward past the TLS access' into 'jmp backward' -- an infinite loop. GTA V's AGC resource destructors hit exactly this shape and leaked the entire heap inside a container drain before any frame was presented. Reject candidates whose preceding byte is 0xEB: a bare EB can never be the last byte of a valid instruction, so such a position is provably mid-instruction. The outer byte scan then retries at the next offset, which is the true boundary, and the patch lands correctly. |
||
|
|
3a744c991e |
fix(agc): record write_data produced labels so WAIT_REG_MEM can resume (#834)
ApplySubmittedWriteData wrote label values to guest memory but never called GpuWaitRegistry.RecordProduced, unlike ApplySubmittedReleaseMem. A suspended WAIT_REG_MEM on a write_data label (e.g. Dead Cells frame fence 0x10243CFD8) then could never be latched or deadlock-broken: guest memory is reset to 0 for frame reuse before the next re-check, and _lastProduced had no value to replay. Record each written dword and, for increment count2, the combined 64-bit value so 32/64-bit waits latch like ReleaseMem dataSel=2. |
||
|
|
35a28f0143 |
[GUI] Estonian language (#835)
* [GUI] Estonian language Implements Estonian language support for the SharpEmu's GUI. * [GUI] Update and refine Estonian translations Correct grammatical errors, refine technical UI terminology (e.g., view layout descriptions), and improve overall phrasing for better consistency across the launcher UI. |
||
|
|
e79a1cc70a | [GUI] Polish language (#832) | ||
|
|
a2241d0e83 |
Fix/agc zero dim and storage (#828)
* fix(videoout): promote guest images to storage usage * fix(agc): treat zero-dimension indirect compute dispatches as valid no-ops |
||
|
|
fe6521f617 |
Fix unaligned BufferLoad/GlobalLoad dword access
BufferLoadDword/x2/x3/x4, BufferStoreDword/x2/x3/x4, and their GLOBAL counterparts were routed through LoadUnalignedBufferWord / StoreBufferBytes, which reconstruct every dword one byte at a time (4 bounds-checked buffer accesses per dword, each with its own OpArrayLength + OpSelect + OpAccessChain + OpLoad/OpStore, plus shift/mask/or reassembly on top). The GCN ISA guarantees these opcodes are always dword-aligned - only the byte/short/D16 variants legitimately need unaligned access, and those already have their own dedicated path (LoadSubdwordBufferValue / StoreBufferBytes with an explicit byte count). The generic dword-count loop reached by every other BufferLoad*/BufferStore*/GlobalLoad*/GlobalStore* opcode was paying the same per-byte cost for no reason. Route the dword-granularity path straight through the existing LoadBufferWord / StoreBufferWord helpers (one bounds check and one load/store per dword) instead. Measured on a compute shader with 6 BufferLoadDwordx4 instructions in its hottest basic block, this drops GPU dispatch time for that shader from ~430-448ms to ~86-92ms (~5x) with no change in output correctness - it is a pure translation inefficiency fix, independent of any specific title. |
||
|
|
034ddcc092 | fix(vmem): use ConcurrentDictionary for _pageProtections to prevent race corruption (#823) | ||
|
|
d9b599a1fd | chore: bump version to 0.0.3-release.3 (#826) v0.0.3-release.3 | ||
|
|
ad115ddcbc | VMovrelsB32 opcode fix (#825) | ||
|
|
7521295ee1 |
[Codec/Native] Real H.264 decode for sceVideodec2, fix TLS loader missing the main module (#824)
* [Codec/Native] Real H.264 decode for sceVideodec2, fix TLS loader missing the main module sceVideodec2 was a capability-only stub: the game's video pipeline worked end-to-end but never produced a picture, so intro/cinematic videos stayed black even though playback "completed" without errors (confirmed on Ghost of Yotei's intro cinematic). - Videodec2Decoder: owns an FFmpeg H.264 session per decoder handle, running decode and presentation pacing on their own threads (never the guest thread) so a whole clip isn't decoded faster than it can be displayed. Converts to BGRA and submits straight to VulkanVideoPresenter, bypassing guest memory the same way the existing Bink2 path does. - Videodec2Exports: wires the real decoder into sceVideodec2CreateDecoder/Decode/Flush/Reset/DeleteDecoder, falling back to the original no-picture stub whenever FFmpeg is unavailable or a given decoder failed to open. - VulkanVideoPresenter: decoded frames were being dropped under a single "latest wins" slot the render loop didn't always poll in time before the next frame overwrote it. Queues pending video presentations the same way guest-image flips already are. - DirectExecutionBackend: the TLS load patcher's one-shot scan missed the main game module when the entry point resolves to a separate bootstrap allocation, and never re-scanned lazily-committed pages patched in afterward -- both left FS:[0] TLS loads unpatched, causing an early mutex-spin boot stall (reproduced on Demon's Souls: stuck at import #256). Now scans both the entry point's own allocation and the standard PS5/PS4 image base, and re-scans each newly committed executable range as it's touched. * [GUI] Add a toggle for SHARPEMU_FORCE_SUBMIT_ORPHAN_PREAMBLES This flag already existed as an AGC workaround (forces queued GPU command-buffer preambles through when their target queue never picks them up, unblocking titles stuck on a WAIT_REG_MEM that never signals) but was only reachable by setting the environment variable by hand. Expose it as a checkbox next to the other env toggles, in both the global Options panel and the per-game settings panel, so it can be turned on for a specific title without touching a shell. * [GUI] Add remaining language translations for the new env toggle The previous commit only added the new key to en.json/fr.json, relying on Localization's runtime fallback to English -- but LocalizationTests.EmbeddedLanguages_ContainEveryEnglishOptionsKey requires every Options.* key to exist in every embedded language file, which broke CI on all three build jobs. Fills in the remaining 13 languages. |
||
|
|
1660111189 |
A vplayer fix (#821)
* Astrobot-video-fix * Astrobot-video-fix with updated files * Astrobot-video-fix with tests files |
||
|
|
7caf430aa9 | test(cpu): cover Gen5 native return smoke path (#818) | ||
|
|
1e96eca316 | Astrobot - Canonicalize fix and ffmpeg library on build (#817) | ||
|
|
498d402577 |
[HLE] Fix AGC OOB, kevent coalescing, and VideoOut flip/buffer-registration bugs (#808)
sceAgcAddPrimStateRegisters (NID unconfirmed, resolved from the decrypted eboot's call site) reads 32 register pairs from a buffer that sceAgcCreatePrimState only partially fills, treating the unfilled part as guest-stack garbage -- an out-of-bounds probe index sourced from that garbage was the AV. Zero the unfilled part instead. GPU interrupt kevents were being coalesced when several arrived close together, but the AGC driver's interrupt thread expects exactly one completion per delivered kevent -- coalescing silently dropped completions and wedged its dependency counters. Deliver one kevent per trigger instead, with a cap so an undrained queue can't grow unbounded. sceVideoOutGetFlipStatus left an extended region of its output struct un-zeroed; some titles poll a flag past the classic struct layout and spin forever on garbage that never clears. Zero the whole region -- flips already complete synchronously in this emulator, so it should read as not-pending anyway. sceVideoOutRegisterBuffers2 rejected any nonzero category/option, which some titles use, making them unable to ever register display buffers or flip. Treat unknown categories as the standard layout instead of failing the registration. Tested on Ghost of Yotei (PPSA26344): combined with the separate ajm-acm-audio change, gets past its audio-init stall and renders/presents a real GPU frame; neither change alone reaches that point. Tested on Cult of the Lamb: calls sceAgcAddPrimStateRegisters 39 times, 0 crashes (previously an unresolved import on this exact NID). Regression-checked on Demon's Souls (both with and without the separate native-tls-fix change, to reach further into boot), Astro Bot, Outer Wilds, Ghost of Tsushima, GTA, Minecraft, Quake: no change in behavior on any of them. |
||
|
|
4b8d45520e |
[HLE] Fix AJM/ACM no-op stubs and accept Gen5 AudioOut2 mastering/batch calls (#807)
AjmModuleUnregister/AjmFinalize were pure no-ops (always returned success without touching state); they now validate the context and actually remove the codec registration / context entry, returning a real error when the context is unknown. AjmModuleUnregister traces whether the codec was actually registered, to help spot a title unregistering something it never registered. MaxCodecType was hardcoded to 25 based on known Sony codec ids, incorrectly rejecting valid Gen5 codec types (e.g. 24). Registration is pure bookkeeping (HashSet.Add); the only real constraint is that codecType must not overflow the 32-bit instanceId it gets packed into, i.e. codecType < 2^18. Named the instanceId bit-packing constants (InstanceIdSlotBits/InstanceIdSlotMask) so the four call sites that used to hardcode 14/0x3FFF independently can't drift out of sync, and MaxCodecType's formula is self-evident. Accept sceAcmBatchInitialize/InitializeLite/Start/StartMultiple/Process as successful no-ops -- the emulator runs no ACM DSP jobs, but Scream's workers trap on int 0x41/0x42 asserts whenever a submission call reports failure. Accept sceAudioOut2MasteringInit/Set3DLatency as no-ops -- the host mixer has no mastering/object pipeline to tune, but returning failure makes titles tear down their whole ACM context and abort audio arena bring-up. Tested on Ghost of Yotei (PPSA26344): sceAudioOut2MasteringInit/Set3DLatency unresolved-import warnings go from 2 to 0, unblocking 5 audio-related guest threads that never spawned before (MovieDecoder, snd_stream_parsing_thread, snd_stream_reader_thread, Psn, NCA::PumpThread), watchdog stall errors from 1 to 0. This alone gets the title past its Scream audio-init stall into further boot, but it still hits an unrelated infinite retry loop in sceVideoOutGetFlipStatus shortly after -- fixed by the separate videoout-agc-hle-misc change; combined, the title renders and presents a real GPU frame. Regression-checked on Demon's Souls, Astro Bot, Outer Wilds, Cult of the Lamb, Ghost of Tsushima, GTA, Minecraft, Quake: 0 calls to any export touched by this change on any of them, byte-for-byte identical to upstream/main. |
||
|
|
62c3852556 |
[AGC] Fix arena/fence tracking stalls and recover lost GPU progress (#770)
* [AGC] Fix arena/fence tracking stalls and recover lost GPU progress Fix several AGC synchronization issues that could leave GPU work unsubmitted or fences permanently unsatisfied: - Recover fence writes missed during arena transitions by tracking closed arena tails and resweeping unresolved fence locations. - Fix chunk submission accounting so only actually parsed ranges are considered submitted, avoiding missed orphan recovery. - Extend arena sweeps to follow chained command ranges without duplicate submissions. - Preserve GPU state tracking across native worker memory wrappers by using canonical guest memory identity. - Fix builder-ring orphan submissions and stale ring-tail waits. - Improve frame cycling by handling arena reuse, cursor regression, and transiently unavailable builder headers. - Record conditional DCB execution (IT_COND_EXEC) instead of silently dropping the packet. These changes improve AGC progress tracking and prevent GPU fence starvation/deadlocks on titles relying on builder arenas and indirect command chains. * chore: trigger CI |
||
|
|
418eb7ecea | fix(audioout2): wire skipped-submit trace counter (#761) | ||
|
|
c086e32f3d |
Fix GuestDataPool lease leak on non-GPU compute dispatch paths (#759)
ObserveComputeDispatch only returned evaluation's pooled arrays when evaluationHandledByCpu was set. Dispatches rejected before submission (empty resource tables, oversized workgroup, compile failure) or a compute submit that got dropped instead of enqueued (workSequence == 0) never handed those buffers to a consumer that would return them, leaking one lease per occurrence and growing GuestDataPool.Shared without bound over a long session. Adds GuestDataPool.DiagnosticStats() (outstanding lease count, idle cached bytes) surfaced in the periodic [LOADER][PERF] line, to catch this class of regression going forward. Verified on Ghost of Yotei and Demon's Souls: pool_leases grew unbounded before the fix (525 in 5 min on Demon's Souls, 1114 in 180s on Yotei) and stays flat/bounded after (0-6 and 2-18 respectively), with no behavioral regression observed. |
||
|
|
9e10d7c44a |
Functions exports in kernel and NpTrophy2 (#749)
* sceNpTrophy2GetTrophyInfoArray export * sceNpTrophy2GetTrophyInfoArray comment changed * sceKernelIsTrinityMode & sceKernelGetOpenPsId exports * deleted console log in KernelIsTrinityMode * sceKernelGetOpenPsId: fix ORBIS_GEN2_ERROR_INVALID_ARGUMENT casts with uint. |
||
|
|
207441ca95 | [asset] upload transparent logo image | ||
|
|
6b8f11a468 | [asset] Add logo.psd file for future edits and modifications | ||
|
|
da0de5cf92 | chore: bump version to 0.0.3-release.2 (#756) v0.0.3-release.2 | ||
|
|
26bda041fa | [videoout] guard degenerate guest buffer ranges (#755) | ||
|
|
8eb2c1e9cb |
Renderdoc (#753)
* [videoout] added renderdoc in-app capture * [gui] added renderdoc toggle and debug group * [gui] added renderdoc strings to all languages |
||
|
|
f3d9439952 |
Fix Vulkan presenter synchronization and frame handling issues (#747)
* [VideoOut/Vulkan] Fix boot deadlock, writeback stall, and presentation bugs - Presenter thread now starts on the compute dispatch path too, fixing a boot deadlock when a title's first GPU work is compute (Ghost of Yotei G-Buffer clear). - Guest render-target format swaps between sibling pixel formats now reinterpret in place instead of recreating blank, preserving GPU-written content. - Vectorized the guest-buffer writeback scan (equal-byte skip + coarse per-page pre-check), fixing multi-second stalls on fragmented buffers that starved JobWorker completion signals. - _presentedSequence now advances on every Render() early-return path, fixing an unthrottled busy loop on stale/dropped presentations. * Remove unnecessary Silk.NET.Windowing dependency |
||
|
|
8df4039ca4 | Ampr: fix path case on Linux (#750) | ||
|
|
f36ce4084a |
[Memory/Kernel] Fix Windows allocation-granularity and mutex-resolution bugs (#748)
- Fixed guest mappings now go through a granule-aware allocator so adjacent PS5 16 KiB pages sharing a 64 KiB Windows allocation granule no longer collide and fail. - TryBackFixedRange routes free/reserved gaps through the same granule-safe path, fixing strays that stranded the rest of a granule. - NORMAL pthread mutex self-relock reverted to real EDEADLK instead of silent compatibility recursion, which was starving other threads. - TryResolveMutexState now checks the handle-keyed lookup before falling through to "not found" on a fresh, never-cached address. |
||
|
|
4b5ea6a793 |
AGC: honour DCC fast clears instead of drawing the clear quad (#738)
On GFX10 a colour clear is not a packet. The driver programs CB_COLORn_CLEAR_WORD0/1 and draws a covering quad which the colour block turns into DCC clear codes, discarding whatever the pixel shader exported. We executed that quad as an ordinary draw, so the shaded output landed in the surface instead of a clear. That alone would be a wrong-pixels bug, but the blend those quads use makes it compound. Every draw into the target blends src=ONE, dst=ONE_MINUS_SRC_ALPHA, so alpha follows a <- a_src + a_dst*(1 - a_src), whose fixed point is 1. A guest colour attachment is cleared once on first use and loaded on every pass after, so nothing ever resets it and the channel climbs until it saturates. Where such a surface is a compositing layer, the final image is ui.rgb + scene.rgb*(1 - ui.a) and a saturated alpha multiplies the scene away entirely - the scene renders correctly the whole time and is then masked to black. Recognise the clear and perform it: the attachment is reset and the quad is dropped, which reproduces the observable effect without modelling DCC block state. The reset drops the image's Initialized flag so the next render pass clears via AttachmentLoadOp.Clear, rather than enqueuing a CmdClearColorImage - the latter lands outside the following render pass, and a target cleared that way was still observed reading back its previous contents. Restricted to clear-to-zero: the reset clears to zero, so a nonzero CLEAR_WORD would be cleared to the wrong colour and is left to be drawn. Zero is zero under every encoding the register pair can carry, so the test needs no format handling. The clip-space span test is load-bearing rather than defensive. Fills sharing the vertex count, topology and blend outnumber the clears by two orders of magnitude and sit well outside the frame; treating those as clears erases the UI and blanks video surfaces. |
||
|
|
cf3bd0b4f2 | Astrobot - Vulkan fix : 0x8A (#736) | ||
|
|
5ee7cd1dfa | Icons in about section (#735) | ||
|
|
ea9be7484f |
AGC: correct GS program registers, stop dropping rect-list draws, add a missing size export (#734)
Three defects found while bringing up a PS5 title. None are title-specific. SPI_SHADER_PGM_LO_GS / HI_GS were 0x8A/0x8B, which are actually SPI_SHADER_PGM_RSRC1/RSRC2_GS. Reading them as an address produced a 58-bit value (observed live: 0x30004622C008300). The correct offsets are 0x88/0x89, consistent with SPI_SHADER_PGM_CHKSUM_GS = 0x80 and SPI_SHADER_PGM_LO_ES = 0xC8 already in the table. Draw translation dropped any RECT_LIST draw whose vertex program exported no parameters while the pixel shader had interpolated inputs. A disposition census over a real run measured this deleting ~620 of every 5000 draws (12%). Rect lists are what AMD drivers emit for clears, blits and resolves, so the guard was deleting clears and leaving previous frame contents on screen as trails. Rendering a draw whose interpolants are undefined is strictly better than deleting it. sceAgcDcbDrawIndexIndirectMultiGetSize was unimplemented while sceAgcDcbDrawIndexIndirectMulti emits an eight-dword packet. A title that sizes its command buffer from the missing export under-reserves by three dwords. NID derived with Ps5Nid.Compute, which reproduces the committed NIDs of the neighbouring exports. Tests: AgcContextRegisterTests and AgcShaderStageRegisterTests drive real PM4 packets through sceAgcDriverSubmitDcb and assert what the parser retained for the context and SH register dictionaries, via two new internal accessors; neither dictionary had any test surface, which is why register questions previously cost five-minute game runs. |
||
|
|
a8fa9c96dc |
Astrobot - Vulkan fix (#733)
* log and vulkan fix * Astrobot - Vulkan fix : OpControlBarrier |
||
|
|
c387b969e1 | [GUI] few stability patches for GUI (#732) | ||
|
|
7c9740fee8 | [GUI] Add grid settings to GUI and implement grid snapping (#729) | ||
|
|
544f588cfd |
Required imports for Astro Bot (#710)
* Required imports for Astro Bot * Add missing clone to match ValueIndexCString |
||
|
|
ecd657006a |
AGC: retain label producers and report failed release writes (#719)
* AGC: retain active label producer records
Ported from the archived silent-hill-minimal branch (274ccfdf).
RegisterLabelProducer bounded its history with RemoveRange(0, 1024) once it
reached 4096 entries, which evicts the oldest records regardless of whether
their label write has completed. Those records are not a diagnostic cache:
a suspended DCB resolves its wait_reg_mem by finding the producer that will
write the watched label. Dropping an active record hides an earlier
same-submission label write, so a legitimate in-stream fence suspends
forever and the graphics queue stops.
Compact only completed history, and let the list exceed the soft bound when
every record is still active — correctness over a diagnostic limit.
Two hardening changes over the ported version, because a suspended queue is
exactly when every record is active:
- compaction is a single order-preserving pass instead of repeated RemoveAt,
which would shift the tail per eviction (quadratic) while the label gate is
held;
- when a pass frees nothing the bound is raised to twice the current count, so
registration does not rescan the whole list on every subsequent add, and it
is reset once compaction can make progress again.
Tests: 664 pass, 0 fail (SharpEmu.Libs.Tests 598 -> 600).
* AGC: keep produced label values a suspended wait still needs
GpuWaitRegistry.RecordProduced dropped the entire produced-value table once it
reached 8192 entries:
if (_lastProduced.Count >= 8192) { _lastProduced.Clear(); }
Those entries are release state, not a cache. CollectDeadlockBroken is the only
way out for a DCB suspended on a WAIT_REG_MEM whose label the guest has since
recycled for other data: it replays the value a real producer already wrote to
that label. Clearing the table wholesale erases exactly the records live waiters
depend on, so every such waiter is stranded permanently — the suspended graphics
queue never resumes, and with it the render thread and the whole engine thread
graph park.
That is what Silent Hill (PPSA10112) hits. Its final unsatisfied waits watch
labels holding guest allocator metadata (each reads a pointer into its own
region rather than the expected 1), i.e. recycled memory, which is precisely
the case the deadlock breaker exists to handle — yet agc.deadlock_break fires 0
times across a run with 490 wait suspensions and 1068 successful release_mem
writes, because the producing values had already been cleared.
Prune only values no registered waiter is watching, and let the table exceed the
soft bound while they are all watched. Same reasoning as the label-producer
history in the preceding commit: correctness of synchronization state takes
precedence over a diagnostic bound.
Tests: 666 pass, 0 fail. The regression test fails against the previous
wholesale Clear() and passes with the prune, verified by reverting the fix.
* AGC: report release-label writes that never reach guest memory
Both release_mem handlers recorded the produced label value only on success:
if (wroteData && dataSelection is 1 or 2) GpuWaitRegistry.RecordProduced(...)
with no else. A failed write is not benign here. That packet is the producer a
suspended WAIT_REG_MEM is waiting for, so when it fails the label is neither
written nor recorded, CollectDeadlockBroken has no value to replay, and the
graphics queue stays suspended forever — the render thread and the engine
thread graph park behind it with nothing in the log to say why.
Measured on Silent Hill (PPSA10112): the fatal AccessViolation lands exactly
here, on the presenter thread, in ApplySubmittedReleaseMem's label write into a
protected guest page. The faulting variant kills the process; the
returns-false variant wedges the GPU silently. Only the first was ever visible.
Report the failure (rate-limited: first 16, then powers of two) instead of
dropping it. This does not fix the underlying write failure — it makes a
permanently suspended queue diagnosable rather than silent.
Tests: 666 pass, 0 fail.
|
||
|
|
a7ec3d5a77 |
[GUI] Add inline per-game settings and unify options styling (#728)
* [GUI] Add inline per-game settings and unify options styling * [GUI] Update options styling & add scrollable area * [GUI] Remove focus and pointover styles for options |
||
|
|
97bd8c422e |
Combined: Pad, AGC, Metal, Vulkan, Ampr, overlay, and CPU hot-path fixes (#727)
* VideoOut: track guest-flip FPS and label load/stall gaps Headline FPS follows VideoOut submit cadence rather than host presents, and long gaps show LOAD/STALL instead of a stale multi-second MS average. * Metal: skip waitUntilCompleted when the command buffer is already Completed Tiny write-back batches often finish before the wait; checking status avoids redundant ordered-queue round-trips. * Metal: opt-in long-edge drawable cap via SHARPEMU_METAL_CAP_DRAWABLE Default presentation resolution is unchanged; set SHARPEMU_METAL_CAP_DRAWABLE=1 to cap the drawable long edge at 1920. * Pad: implement scePadGetTriggerEffectState under its own NID NID znaWI0gpuo8 was mapped to sceUserServiceGetUserName as a "title-captured alias". It is not that symbol. Recomputing the NID of every catalogued name (base64 of the reversed first eight sha1 bytes of name+salt) resolves znaWI0gpuo8 to scePadGetTriggerEffectState, and sceUserServiceGetUserName hashes to 1xxcMiGu2fo instead. Auditing all 1087 export declarations the same way found this to be the only NID whose declared name is wrong. The consequence was not a missing export but a wrong one: the user-service handler rejected the pad's arguments and returned SCE_USER_SERVICE_ERROR_INVALID_PARAMETER about eighteen thousand times per run in PPSA10112, so every poll fell back to a cached button bitmask. It also hid the calls from every search for pad activity, which is why this title was believed never to touch scePad at all. The state size is taken from the caller's own frame rather than assumed: the guest points the out-param at rbp-0x30 and stores its stack cookie at rbp-0x28, leaving eight bytes for the state. Writing the sixteen the frame superficially suggests would land on the cookie and fail the guest's stack check - the same failure this codebase has already hit three times from oversized HLE writes - so the test pins the size by asserting the cookie survives. No host pad exposes DualSense adaptive-trigger feedback, so the neutral all-zero state is reported as success, which lets the caller take its normal path instead of the fallback. * Apply SDWA ABS/NEG as float sign-bit, not integer, modifiers SDWA's ABS and NEG source modifiers are floating-point sign-bit operations on GCN: ABS clears the sign bit, NEG flips it. We applied them as integer operations instead - SAbs, and a two's-complement negate of the raw bit pattern. That turns 1.0 into -4.0 and -3.0 into 1.5. UE4 compiles the final line of DrawRectangle, OutPosition.xy *= float2(1,-1), into a single V_MOV_B32 with SDWA NEG, so every UE fullscreen pass had its clip-space Y silently skewed. The canonical fullscreen triangle (1,-1) (-3,-1) (1,3) became (1,-4) (-3,-4) (1,1.5), which covers 6/11 of the viewport instead of all of it. That reproduces the measured defect exactly, on four independent quantities: hypotenuse slope 8/11, crossings of y=+1 and y=-1 at x=+7/11 and x=-9/11, and covered area 6/11 = 54.55% of the 2304x1296 viewport. It also explains why the edge was resolution-independent and identical across six unrelated shaders - it is the same instruction in every one of them. The distinguishing evidence is the transform's fixed point. A wrong scale would hold NDC -1 in x and +1 in y; the observed transform holds the opposite corner in both. Independently, and using only the measured line rather than any assumed vertex position: a wrong multiplier alone leaves a residual of -18 whatever the multiplier, and a wrong addend alone forces slope 1, not 8/11. Both terms had to be wrong at once, which only a Y-only sign-bit corruption produces. Float instructions are unaffected: GetFloatSource passes applySdwaIntegerModifiers: false and applies its own modifiers, so this path only ever fed raw-source reads - where the hardware behaviour is the sign-bit one regardless of the opcode being a bit-move. The sign bit is selected by the SDWA source-select width so a 16-bit select flips bit 15 rather than bit 31. Verified: emitted SPIR-V for the same shader changes from OpISub %uint %uint_0 %2147 to OpBitwiseXor %uint %2147 %uint_2147483648; 25-program synthetic conformance gate passes; 805 tests green; three 100-110s live runs with no crashes and no new shader failures. The end-to-end pixel re-measurement is NOT yet closed - see task #26. * gpu: size single-channel 16-bit and two-channel 8-bit formats GetFormatCompatibilityClass listed only R16Sfloat in the 16-bit class, so GetVulkanImageByteCount computed zero bytes for R16Unorm, R16SNorm, R16Uint, R16Sint and the R8G8 family. UploadGuestImageInitialData treats a zero expected size as an incompatible upload and drops it, which leaves the texture blank for the rest of the run rather than failing loudly. Silent Hill uploads R16Unorm at 144x81, 240x135, 256x256, 512x512 and 1024x1024, and every one was rejected: the guest supplied exactly width*height*2 bytes each time (23328 for 144x81) against an expected zero. Also adds R8SNorm to the 8-bit class, which was missing for the same reason. * Vulkan: enable textureCompressionBC when the device supports it Guest BC1–BC7 textures can be sampled directly when the feature is available; warn when it is not. * Share one guest image across sRGB/UNORM aliases Ported from origin/fix/view-compatible-guest-images 7fb8fdf. Rendering as sRGB and ImageLoad/Store-ing as UNORM at the same guest address are the same surface accessed through different number formats. Recreating the guest image per number format ping-pongs content between two VkImages and loses the rendered pixels on every transition; the mutable-format image now accepts the counterpart identity and serves it through alias views. The commit names AvPlayer movie copies as the pattern that needs this. Adapted for this base: GetOrCreateGuestImage has since grown resolution scaling and 3D/array support, so the alias accept is folded into the current predicate (LogicalWidth/LogicalHeight/LogicalDepth/Type) rather than the old Width/Height pair, and the storage-counterpart widening is placed before the physical-dimension computation. The helper functions it relies on (GetStorageImageFormat, IsCompatibleViewFormat) already existed. * fix(gpu): one vertex attribute per guest stream view The scalar evaluator gave every buffer_load_format instruction its own attribute location. Two things multiply those: the CFG walk visits one instruction on several paths, and an uber vertex shader fetches the same stream from every material branch. UE's larger shaders reached 56 bindings from 8 distinct views, and one reached 701 from 5. Metal caps a vertex function at 31 attributes, so MoltenVK failed the MSL compile with "'attribute' attribute parameter is out of bounds" and the surrounding vkCreateGraphicsPipelines returned ErrorInitializationFailed. Every draw using those pipelines was dropped, which is why Silent Hill: The Short Message rendered a black scene. The vertex buffer count drove Metal's buffer indices out of range too, giving the companion "cannot reserve 'buffer' resource location at index 0" failures. Key attributes by the guest stream view they read - absolute element address, record stride and format - and alias every other fetch that resolves to the same view onto that binding, so both translators map those instruction PCs to one input variable. On PPSA10112 this takes the worst shader from 56 attributes to 8 and pipeline failures from 840 to 0. * AGC: deliver compute-queue completion events at the queue fence sceAgcDriverSubmitAcb submissions never produced a completion interrupt. NotifySubmittedDcbCompleted returned early for anything that was not the graphics queue, so an ACB reaching its ordered-queue fence published nothing. UE 4.27's dynamic-resolution GPU-timing heuristic parks the game thread on exactly that interrupt, so the render side never advanced and Silent Hill: The Short Message deadlocked after its first frame. Give every queue a CompletionEventId: 0 for graphics (what it already published) and the owner handle from sceAgcDriverSubmitAcb rdi for a compute queue, which is the same value the guest passes to sceAgcDriverAddEqEvent. Publish under that ident from the existing fence point, which both PumpSubmittedQueue and ResumeSuspendedDcb already reach only after the submission is fully parsed. Delivery is synchronous on the ordered guest-action queue rather than on a ThreadPool hop with a sleep. That action runs after the logical queue has flushed and waited for its latest fence, which is the moment hardware would raise end-of-pipe. Deferring past it can only make the interrupt late and reorder it against registration changes. Gating: the per-queue completion event is unconditional, because completion interrupts do fire on real hardware and because delivery is registration-gated -- TriggerRegisteredEvents only queues onto equeues that registered this exact (ident, graphics filter) pair, and sceAgcDriverAddEqEvent is the only producer of graphics registrations. A title that never registers its ACB owner handle observes no change. SHARPEMU_AGC_SUBMIT_COMPLETION_EVENT is left to gate only the broad ident-ignoring fan-out (TriggerRegisteredEventsDistinct), which is a compatibility guess rather than hardware behavior; it also stays scoped to the graphics queue where it was measured, so enabling the flag does not newly fan out across compute queues. * fix(gpu): reflect guest CPU writes into large and render-target-aliased images Ports the still-applicable half of the archived fork's stale-texture fix (a42ccae) onto current main. Silent Hill: The Short Message (PPSA10112) shows both faces: black title-screen UI (glyph atlas frozen at its first upload) and stale/garbage rows on the brightness screen (a 3840x2160 UI sheet whose backing bytes were never re-read). 1. Write tracking is armed under a byte budget, not a resolution cap. GetOrCreateGuestImage armed GuestImageWriteTracker only when target.Width <= 1920 && target.Height <= 1080. Silent Hill renders at 3840x2160, so every one of its render targets was excluded from CPU-write tracking and no guest rewrite of one could ever invalidate it: SyncCpuWrittenGuestImages (the flip / ACQUIRE_MEM re-upload path) only ever visits ranges the tracker armed. The cap presumably existed as a perf guard, but resolution is the wrong proxy for the cost. Arming is one mprotect over the range, and the fault handler unprotects the whole range on the first store, so a write burst costs one fault regardless of size. What actually scales with the surface is the dirty re-upload: one byte[byteCount] allocation plus a full guest-memory read per dirty flip. So the guard is now a byte budget, set equal to the 128 MiB limit that SyncCpuWrittenGuestImages itself enforces before re-uploading. Above that, arming can only cost faults; it can never produce a re-upload. That is generous for 4K (RGBA8 32 MiB, RGBA16F 63 MiB, RGBA32F 127 MiB all fit) while still excluding volume textures that a resolution cap could not see at all (512^3 RGBA8 is 512 MiB behind a "512x512" surface). Both sites now also arm the exact extent recorded in _guestImageExtents (GetTextureByteCount) instead of Width*Height*depth*GetTextureBytesPerPixel, so the armed range and the range the sync path reads back are the same bytes; the old expression over-counted block-compressed and unknown formats. 2. The CPU-texture refresh path no longer gates on IsCpuBacked alone. TryCreateCpuTextureRefreshResource bailed on !guestImage.IsCpuBacked. That flag is a latch: it flips false the first time an address is used as a render target and never flips back. A surface that was rendered into once and is afterwards rewritten by the guest CPU was therefore frozen at its last GPU content forever, even when the parse thread had already shipped fresh texels for it. The gate is replaced by ShouldRefreshGuestImageFromCpu: CPU-backed, or the parse-time write generation is above zero and differs from the generation recorded by the last upload. Keeping the positive- generation requirement preserves the pure GPU-feedback case (render into an image, then sample it) that IsCpuBacked used to protect: such a surface is now tracked (change 1) but never CPU-written, so its generation stays zero and its live image is left alone. This matters more than it did in the fork, precisely because change 1 arms tracking on far more render targets. Dropping the gate outright, as the fork did, would let a target sampled under a format tag that the availability map does not match be overwritten once with whatever sits in guest memory. The existing content fingerprint still suppresses redundant uploads, and MarkSampledImagesInitialized records the uploaded generation, so a rewritten surface re-uploads exactly once per guest write burst. Not ported: the fork's third part added a PeekDirty guard to the parse-time snapshot fast path in AgcExports. It is superseded. That path now calls IsGuestImageUploadKnown, which already compares _cpuBackedUploadGenerations against the tracker's write generation - a monotonic value that survives another owner consuming the dirty flag, unlike PeekDirty, which both EvictDirtyCachedTextures and SyncCpuWrittenGuestImages clear. Guest images with no generation entry are covered instead by SyncCpuWrittenGuestImages. Adding a non-consuming PeekDirty there would also make every draw between a CPU write and the next flip fall through to a full texel re-read of the surface (33 MiB for a 4K sheet), since Track re-arms without clearing the dirty flag. The fork's promotion-path Track ("vulkan.cpu-backed-image") is likewise superseded: AgcExports already arms every sampled texture's backing extent as "agc.decoded-texture" before reading its texels. Both decisions are extracted as pure internal predicates so they can be unit-tested; the Vulkan device code around them needs a real device. * Kernel: correct equeue event delivery and waiter lifetime Ports the event-queue rework from the archived silent-hill-minimal branch (968e9606, 9e3abb12, 2be9cfbf, f157a115) onto current upstream. The equeue implementation had not been touched upstream since that branch forked, so none of it had landed. Behaviour fixed: - Per-waiter event reservation. A blocked waiter used to wake on "the queue has any pending event" (TryWake => HasPendingEvents) and then re-read the queue on resume, so a woken waiter could find the event already drained by another waiter and park again with the wake consumed. Events are now reserved to the waiter they are delivered to. - Level-triggered events are preserved instead of being cleared by an unrelated read; only events that declare clear-on-read reset their trigger state. - Queued interrupts are bound to the registration generation that produced them, so an event registered after a queue was reused cannot consume an interrupt raised for the previous registration. - Deleting an equeue now terminates its waiters instead of leaving them blocked on a handle that no longer resolves. - sceKernelTriggerUserEvent stores its third argument in the event's udata (0x18) rather than its data word (0x10). The guest reads it back with sceKernelGetEventUserData, which loads 0x18, so every triggered user event previously read back as 0. This matches the reference behaviour in shadPS4, where TriggerEvent takes udata and sceKernelGetEventUserData returns ev->udata. The upstream test asserting the data word is updated, since it encoded the inconsistency rather than the ABI. KernelPthreadState gains TryGetCurrentThreadIdentity and a new KernelSyncTraceFormatter carries the shared, opt-in diagnostic formatting the ported code calls; both are gated behind the existing trace flag and do no work when it is off. Tests: 662 pass, 0 fail (SharpEmu.Libs.Tests 588 -> 598). * Implement VOP3 0x149 V_BFE_I32 and 0x36A V_CVT_PKRTZ_F16_F32 PPSA10112 dropped three shaders per run on two unimplemented VOP3 opcodes. The translator fails a whole shader on an opcode it does not know, so each one costs a dropped draw rather than wrong pixels. 0x149 sits between 0x148 V_BFE_U32 and 0x14A V_BFI_B32, and 0x36A between 0x369 V_CVT_PKNORM_U16_F32 and 0x36D V_ADD3_U32; the surrounding table entries already match the canonical map densely on both sides of each gap. V_CVT_PKRTZ_F16_F32 needed no emitter - it was already implemented for the VOP2 form at 0x2F and its VOP3 alias at 0x12F, and only the VOP3-only opcode number was missing. V_BFE_I32 mirrors its unsigned sibling, masking offset and width to 5 bits, and differs only in extracting through a signed type so the field sign-extends. GCN defines width 0 as returning 0 where SPIR-V leaves a zero Count unspecified; VBfeU32 has the same gap, so this matches it deliberately rather than diverging - fix both together if it matters. Also widen ReportGuestPointerSplit, which filtered to an incoming value of exactly 1. The crash being hunted leaves 0x0000007000000000, whose low dword is 0, so the one detector built to catch this bug class could never have reported it. Verified: unsupported-opcode errors 3-4 per run -> 0, over a 130s run that survives to the same stage. Metal translator still lacks VBfeI32; Vulkan/MoltenVK is the macOS path so it is a divergence, not a blocker. * AGC: retain active label producer records Ported from the archived silent-hill-minimal branch (274ccfdf). RegisterLabelProducer bounded its history with RemoveRange(0, 1024) once it reached 4096 entries, which evicts the oldest records regardless of whether their label write has completed. Those records are not a diagnostic cache: a suspended DCB resolves its wait_reg_mem by finding the producer that will write the watched label. Dropping an active record hides an earlier same-submission label write, so a legitimate in-stream fence suspends forever and the graphics queue stops. Compact only completed history, and let the list exceed the soft bound when every record is still active — correctness over a diagnostic limit. Two hardening changes over the ported version, because a suspended queue is exactly when every record is active: - compaction is a single order-preserving pass instead of repeated RemoveAt, which would shift the tail per eviction (quadratic) while the label gate is held; - when a pass frees nothing the bound is raised to twice the current count, so registration does not rescan the whole list on every subsequent add, and it is reset once compaction can make progress again. Tests: 664 pass, 0 fail (SharpEmu.Libs.Tests 598 -> 600). * AGC: keep produced label values a suspended wait still needs GpuWaitRegistry.RecordProduced dropped the entire produced-value table once it reached 8192 entries: if (_lastProduced.Count >= 8192) { _lastProduced.Clear(); } Those entries are release state, not a cache. CollectDeadlockBroken is the only way out for a DCB suspended on a WAIT_REG_MEM whose label the guest has since recycled for other data: it replays the value a real producer already wrote to that label. Clearing the table wholesale erases exactly the records live waiters depend on, so every such waiter is stranded permanently — the suspended graphics queue never resumes, and with it the render thread and the whole engine thread graph park. That is what Silent Hill (PPSA10112) hits. Its final unsatisfied waits watch labels holding guest allocator metadata (each reads a pointer into its own region rather than the expected 1), i.e. recycled memory, which is precisely the case the deadlock breaker exists to handle — yet agc.deadlock_break fires 0 times across a run with 490 wait suspensions and 1068 successful release_mem writes, because the producing values had already been cleared. Prune only values no registered waiter is watching, and let the table exceed the soft bound while they are all watched. Same reasoning as the label-producer history in the preceding commit: correctness of synchronization state takes precedence over a diagnostic bound. Tests: 666 pass, 0 fail. The regression test fails against the previous wholesale Clear() and passes with the prune, verified by reverting the fix. * Correct VOP3 0x36A: V_CVT_PK_U16_U32, not V_CVT_PKRTZ_F16_F32 a2d186e mapped 0x36A to V_CVT_PKRTZ_F16_F32 on the strength of a gap in our own opcode table. That was wrong, and wrong in the worst available way: an emitter for V_CVT_PKRTZ_F16_F32 already existed, so instead of failing loudly like an unknown opcode, the mapping would have emitted float-pack semantics for an integer-pack instruction and produced silently incorrect results. LLVM is unambiguous: defm V_CVT_PK_U16_U32 : VOP3Only_Real_gfx10<0x36a>; defm V_CVT_PKRTZ_F16_F32 : VOP2_Real_gfx6_gfx7_gfx10<0x02f>; so on Gen5 the float pack is VOP2 0x2F with VOP3 alias 0x12F - both of which our table already had - and there is no VOP3-only encoding of it to add. 0x36A sits with the other integer/normalised pack conversions at 0x368/0x369/0x36B. silent-hill-minimal-rebased had this right all along. The V_BFE_I32 half of a2d186e stands: LLVM confirms 0x149, and that branch maps it identically. Lesson, since I had just warned someone else about exactly this: a gap in a table is evidence about numbering, not about identity. Inference from neighbouring entries is fine for narrowing candidates and worth nothing as a conclusion - especially when a plausible emitter already exists to swallow the mistake quietly. * AGC: report release-label writes that never reach guest memory Both release_mem handlers recorded the produced label value only on success: if (wroteData && dataSelection is 1 or 2) GpuWaitRegistry.RecordProduced(...) with no else. A failed write is not benign here. That packet is the producer a suspended WAIT_REG_MEM is waiting for, so when it fails the label is neither written nor recorded, CollectDeadlockBroken has no value to replay, and the graphics queue stays suspended forever — the render thread and the engine thread graph park behind it with nothing in the log to say why. Measured on Silent Hill (PPSA10112): the fatal AccessViolation lands exactly here, on the presenter thread, in ApplySubmittedReleaseMem's label write into a protected guest page. The faulting variant kills the process; the returns-false variant wedges the GPU silently. Only the first was ever visible. Report the failure (rate-limited: first 16, then powers of two) instead of dropping it. This does not fix the underlying write failure — it makes a permanently suspended queue diagnosable rather than silent. Tests: 666 pass, 0 fail. * CPU: gate periodic import logs and fast-path memcpy/memmove leaves SHARPEMU_LOG_IMPORT_PERIODIC=1 re-enables periodic Import# tracing. memcpy/memmove use a thin TryCopy leaf that skips register marshalling. * Ampr: cooked-id index preload and host FD LRU cache Preload the app0 APR index during bind and bound the open host-file cache so streaming reads stay responsive under large title archives. * Vulkan: abandon timed-out guest fences without blocking later frames Move fence-timeout submissions out of the blocking queue, keep GPU objects alive until the fence signals, and retire them from an abandoned list so one hung dispatch cannot re-block capacity waits. * AGC: follow command-buffer branches across arena switches Implement sceAgcCbBranch and walk INDIRECT_BUFFER so submissions that continue in a linked buffer keep their flip and end-of-frame labels. |
||
|
|
c4ae4a2059 |
Vulkan: abandon timed-out guest fences without blocking later frames (#726)
Move fence-timeout submissions out of the blocking queue, keep GPU objects alive until the fence signals, and retire them from an abandoned list so one hung dispatch cannot re-block capacity waits. |
||
|
|
93c9f14081 |
Ampr: cooked-id index preload and host FD LRU cache (#725)
Preload the app0 APR index during bind and bound the open host-file cache so streaming reads stay responsive under large title archives. |
||
|
|
532251c0c3 |
CPU: gate periodic import logs and fast-path memcpy/memmove leaves (#724)
SHARPEMU_LOG_IMPORT_PERIODIC=1 re-enables periodic Import# tracing. memcpy/memmove use a thin TryCopy leaf that skips register marshalling. |
||
|
|
816ec4ad27 |
Kernel: correct equeue event delivery and waiter lifetime (#723)
Ports the event-queue rework from the archived silent-hill-minimal branch (968e9606, 9e3abb12, 2be9cfbf, f157a115) onto current upstream. The equeue implementation had not been touched upstream since that branch forked, so none of it had landed. Behaviour fixed: - Per-waiter event reservation. A blocked waiter used to wake on "the queue has any pending event" (TryWake => HasPendingEvents) and then re-read the queue on resume, so a woken waiter could find the event already drained by another waiter and park again with the wake consumed. Events are now reserved to the waiter they are delivered to. - Level-triggered events are preserved instead of being cleared by an unrelated read; only events that declare clear-on-read reset their trigger state. - Queued interrupts are bound to the registration generation that produced them, so an event registered after a queue was reused cannot consume an interrupt raised for the previous registration. - Deleting an equeue now terminates its waiters instead of leaving them blocked on a handle that no longer resolves. - sceKernelTriggerUserEvent stores its third argument in the event's udata (0x18) rather than its data word (0x10). The guest reads it back with sceKernelGetEventUserData, which loads 0x18, so every triggered user event previously read back as 0. This matches the reference behaviour in shadPS4, where TriggerEvent takes udata and sceKernelGetEventUserData returns ev->udata. The upstream test asserting the data word is updated, since it encoded the inconsistency rather than the ABI. KernelPthreadState gains TryGetCurrentThreadIdentity and a new KernelSyncTraceFormatter carries the shared, opt-in diagnostic formatting the ported code calls; both are gated behind the existing trace flag and do no work when it is off. Tests: 662 pass, 0 fail (SharpEmu.Libs.Tests 588 -> 598). |
||
|
|
82c2c7f48c |
fix(gpu): reflect guest CPU writes into large and render-target-aliased images (#722)
Ports the still-applicable half of the archived fork's stale-texture fix
(a42ccae) onto current main. Silent Hill: The Short Message (PPSA10112)
shows both faces: black title-screen UI (glyph atlas frozen at its first
upload) and stale/garbage rows on the brightness screen (a 3840x2160 UI
sheet whose backing bytes were never re-read).
1. Write tracking is armed under a byte budget, not a resolution cap.
GetOrCreateGuestImage armed GuestImageWriteTracker only when
target.Width <= 1920 && target.Height <= 1080. Silent Hill renders at
3840x2160, so every one of its render targets was excluded from
CPU-write tracking and no guest rewrite of one could ever invalidate
it: SyncCpuWrittenGuestImages (the flip / ACQUIRE_MEM re-upload path)
only ever visits ranges the tracker armed.
The cap presumably existed as a perf guard, but resolution is the
wrong proxy for the cost. Arming is one mprotect over the range, and
the fault handler unprotects the whole range on the first store, so a
write burst costs one fault regardless of size. What actually scales
with the surface is the dirty re-upload: one byte[byteCount]
allocation plus a full guest-memory read per dirty flip. So the guard
is now a byte budget, set equal to the 128 MiB limit that
SyncCpuWrittenGuestImages itself enforces before re-uploading. Above
that, arming can only cost faults; it can never produce a re-upload.
That is generous for 4K (RGBA8 32 MiB, RGBA16F 63 MiB, RGBA32F
127 MiB all fit) while still excluding volume textures that a
resolution cap could not see at all (512^3 RGBA8 is 512 MiB behind a
"512x512" surface).
Both sites now also arm the exact extent recorded in
_guestImageExtents (GetTextureByteCount) instead of
Width*Height*depth*GetTextureBytesPerPixel, so the armed range and
the range the sync path reads back are the same bytes; the old
expression over-counted block-compressed and unknown formats.
2. The CPU-texture refresh path no longer gates on IsCpuBacked alone.
TryCreateCpuTextureRefreshResource bailed on !guestImage.IsCpuBacked.
That flag is a latch: it flips false the first time an address is
used as a render target and never flips back. A surface that was
rendered into once and is afterwards rewritten by the guest CPU was
therefore frozen at its last GPU content forever, even when the parse
thread had already shipped fresh texels for it.
The gate is replaced by ShouldRefreshGuestImageFromCpu: CPU-backed,
or the parse-time write generation is above zero and differs from the
generation recorded by the last upload. Keeping the positive-
generation requirement preserves the pure GPU-feedback case (render
into an image, then sample it) that IsCpuBacked used to protect: such
a surface is now tracked (change 1) but never CPU-written, so its
generation stays zero and its live image is left alone. This matters
more than it did in the fork, precisely because change 1 arms
tracking on far more render targets. Dropping the gate outright, as
the fork did, would let a target sampled under a format tag that the
availability map does not match be overwritten once with whatever
sits in guest memory.
The existing content fingerprint still suppresses redundant uploads,
and MarkSampledImagesInitialized records the uploaded generation, so
a rewritten surface re-uploads exactly once per guest write burst.
Not ported: the fork's third part added a PeekDirty guard to the
parse-time snapshot fast path in AgcExports. It is superseded. That path
now calls IsGuestImageUploadKnown, which already compares
_cpuBackedUploadGenerations against the tracker's write generation - a
monotonic value that survives another owner consuming the dirty flag,
unlike PeekDirty, which both EvictDirtyCachedTextures and
SyncCpuWrittenGuestImages clear. Guest images with no generation entry
are covered instead by SyncCpuWrittenGuestImages. Adding a non-consuming
PeekDirty there would also make every draw between a CPU write and the
next flip fall through to a full texel re-read of the surface (33 MiB
for a 4K sheet), since Track re-arms without clearing the dirty flag.
The fork's promotion-path Track ("vulkan.cpu-backed-image") is likewise
superseded: AgcExports already arms every sampled texture's backing
extent as "agc.decoded-texture" before reading its texels.
Both decisions are extracted as pure internal predicates so they can be
unit-tested; the Vulkan device code around them needs a real device.
|
||
|
|
531e35b6d5 |
AGC: deliver compute-queue completion events at the queue fence (#721)
sceAgcDriverSubmitAcb submissions never produced a completion interrupt. NotifySubmittedDcbCompleted returned early for anything that was not the graphics queue, so an ACB reaching its ordered-queue fence published nothing. UE 4.27's dynamic-resolution GPU-timing heuristic parks the game thread on exactly that interrupt, so the render side never advanced and Silent Hill: The Short Message deadlocked after its first frame. Give every queue a CompletionEventId: 0 for graphics (what it already published) and the owner handle from sceAgcDriverSubmitAcb rdi for a compute queue, which is the same value the guest passes to sceAgcDriverAddEqEvent. Publish under that ident from the existing fence point, which both PumpSubmittedQueue and ResumeSuspendedDcb already reach only after the submission is fully parsed. Delivery is synchronous on the ordered guest-action queue rather than on a ThreadPool hop with a sleep. That action runs after the logical queue has flushed and waited for its latest fence, which is the moment hardware would raise end-of-pipe. Deferring past it can only make the interrupt late and reorder it against registration changes. Gating: the per-queue completion event is unconditional, because completion interrupts do fire on real hardware and because delivery is registration-gated -- TriggerRegisteredEvents only queues onto equeues that registered this exact (ident, graphics filter) pair, and sceAgcDriverAddEqEvent is the only producer of graphics registrations. A title that never registers its ACB owner handle observes no change. SHARPEMU_AGC_SUBMIT_COMPLETION_EVENT is left to gate only the broad ident-ignoring fan-out (TriggerRegisteredEventsDistinct), which is a compatibility guess rather than hardware behavior; it also stays scoped to the graphics queue where it was measured, so enabling the flag does not newly fan out across compute queues. |
||
|
|
3f9bd2b92b |
AGC: follow command-buffer branches across arena switches (#720)
Implement sceAgcCbBranch and walk INDIRECT_BUFFER so submissions that continue in a linked buffer keep their flip and end-of-frame labels. |
||
|
|
eb0653eded |
fix(gpu): one vertex attribute per guest stream view (#718)
The scalar evaluator gave every buffer_load_format instruction its own attribute location. Two things multiply those: the CFG walk visits one instruction on several paths, and an uber vertex shader fetches the same stream from every material branch. UE's larger shaders reached 56 bindings from 8 distinct views, and one reached 701 from 5. Metal caps a vertex function at 31 attributes, so MoltenVK failed the MSL compile with "'attribute' attribute parameter is out of bounds" and the surrounding vkCreateGraphicsPipelines returned ErrorInitializationFailed. Every draw using those pipelines was dropped, which is why Silent Hill: The Short Message rendered a black scene. The vertex buffer count drove Metal's buffer indices out of range too, giving the companion "cannot reserve 'buffer' resource location at index 0" failures. Key attributes by the guest stream view they read - absolute element address, record stride and format - and alias every other fetch that resolves to the same view onto that binding, so both translators map those instruction PCs to one input variable. On PPSA10112 this takes the worst shader from 56 attributes to 8 and pipeline failures from 840 to 0. |
||
|
|
e1695cf87f |
Share one guest image across sRGB/UNORM aliases (#717)
Ported from origin/fix/view-compatible-guest-images 7fb8fdf. Rendering as sRGB and ImageLoad/Store-ing as UNORM at the same guest address are the same surface accessed through different number formats. Recreating the guest image per number format ping-pongs content between two VkImages and loses the rendered pixels on every transition; the mutable-format image now accepts the counterpart identity and serves it through alias views. The commit names AvPlayer movie copies as the pattern that needs this. Adapted for this base: GetOrCreateGuestImage has since grown resolution scaling and 3D/array support, so the alias accept is folded into the current predicate (LogicalWidth/LogicalHeight/LogicalDepth/Type) rather than the old Width/Height pair, and the storage-counterpart widening is placed before the physical-dimension computation. The helper functions it relies on (GetStorageImageFormat, IsCompatibleViewFormat) already existed. |