mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-19 01:16:12 +08:00
[AGC] Quake rendering progress: WAIT_REG_MEM, draw fixes, VideoOut, and HLE improvements (#68)
* [agc] WAIT_REG_MEM suspend/resume, draw packet fixes, new HLE exports, debug cleanup
Rebased onto upstream 79a7437 (par274/sharpemu, rewritten history).
- GpuWaitRegistry: DCBs suspended on unsatisfied WAIT_REG_MEM are re-polled
against guest memory on every submit; fixed 64-bit and standard packet parse
offsets, apply the mask, treat PM4 compare function 0 as "always".
- TryReadSubmittedDrawCount: accept the 5-dword ItDrawIndex2 form emitted by
DcbDrawIndex (count at +4); menu draws were silently discarded before.
- sceAgcDriverSubmitMultiDcbs: reversed ABI (rdi=address array, rsi=dword
sizes, rdx=count).
- VideoOut: vblank events, sceVideoOutGetFlipStatus, buffers registered via
sceVideoOutRegisterBuffers are valid flip targets.
- New HLE: libc stdio (fopen/fread/fseek/ftell/fclose/fgets), Dinkumware
_Getpctype ctype table, NpTrophy2 stubs, AMPR PAK sequential-read tracker,
MsgDialog lifecycle, NGS2 alt NIDs + dummy vtable for handle objects,
guarded memset intrinsic, abort()/strcasecmp null-arg recovery.
- Removed investigation-only code (INT3 breakpoints, qfont/mcpp dumps,
error-candidate printf traces, unconditional debug logs).
First rendered frame: Quake (PPSA01880) presents a 1920x1080 guest frame.
* Implemented a guarded native intrinsic (rep movsb) in DirectExecutionBackend to bypass HLE dispatch overhead, while preserving memory safety checks.
This commit is contained in:
@@ -1122,21 +1122,72 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
||||
0x75, 0xF5,
|
||||
0xC3,
|
||||
],
|
||||
// memcpy: guarded native copy. The first "rep movsb" intrinsic had no bounds/null
|
||||
// checking and crashed with a read at -1 right after a null-dst memset recovery in
|
||||
// the NGS2 audio streaming code path, so it was temporarily pulled in favor of the
|
||||
// safe C# HLE memcpy. That detour costs a full import dispatch per call - far too
|
||||
// slow for a function this hot - so this stub keeps the native leaf path and adds
|
||||
// the same guards as memset below: it silently returns dst without copying when dst
|
||||
// or src is null/low-page (< 0x10000) or outside canonical user space, or when len
|
||||
// is 0 or absurd (> 512MB).
|
||||
"Q3VBxCXhUHs" =>
|
||||
[
|
||||
0x48, 0x89, 0xF8,
|
||||
0x48, 0x89, 0xD1,
|
||||
0xF3, 0xA4,
|
||||
0xC3,
|
||||
0x48, 0x89, 0xF8, // mov rax, rdi (return dst)
|
||||
0x48, 0x81, 0xFF, 0x00, 0x00, 0x01, 0x00, // cmp rdi, 0x10000
|
||||
0x72, 0x31, // jb done
|
||||
0x49, 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, // mov r8, 0x800000000000
|
||||
0x4C, 0x39, 0xC7, // cmp rdi, r8
|
||||
0x73, 0x22, // jae done
|
||||
0x48, 0x81, 0xFE, 0x00, 0x00, 0x01, 0x00, // cmp rsi, 0x10000
|
||||
0x72, 0x19, // jb done
|
||||
0x4C, 0x39, 0xC6, // cmp rsi, r8
|
||||
0x73, 0x14, // jae done
|
||||
0x48, 0x81, 0xFA, 0x00, 0x00, 0x00, 0x20, // cmp rdx, 0x20000000
|
||||
0x77, 0x0B, // ja done
|
||||
0x48, 0x85, 0xD2, // test rdx, rdx
|
||||
0x74, 0x06, // jz done
|
||||
0x48, 0x89, 0xD1, // mov rcx, rdx
|
||||
0xFC, // cld
|
||||
0xF3, 0xA4, // rep movsb
|
||||
0xC3, // done: ret
|
||||
],
|
||||
// memset: guarded native fill. An earlier unguarded version crashed with a write AV
|
||||
// at address 0 (NGS2 audio streaming init memsets a never-populated buffer field),
|
||||
// so this one mirrors the HLE guards and silently returns dst without writing when
|
||||
// dst is null/low-page (< 0x10000), dst is outside canonical user space, or len is
|
||||
// absurd (> 512MB, e.g. the 0x27060035 / sign-extended values NGS2 passes). Routing
|
||||
// memset through the HLE trampoline instead is not viable: parse/streaming loops
|
||||
// issue hundreds of thousands of small memsets back-to-back, which crawls at
|
||||
// dispatch speed and looks like a repeating-import hang to the loop guard.
|
||||
// _sigprocmask: the HLE handler (KernelRuntimeCompatExports.Sigprocmask) is a pure
|
||||
// no-op returning 0 that never writes oldset, so this is behavior-identical. The
|
||||
// game's bundled libc queries the mask (set=NULL) once per iteration in its font/
|
||||
// parse loops - hundreds of thousands of back-to-back calls that both crawl at
|
||||
// dispatch speed and read as a repeating-import hang to the loop guard.
|
||||
"6xVpy0Fdq+I" =>
|
||||
[
|
||||
0x31, 0xC0, // xor eax, eax
|
||||
0xC3, // ret
|
||||
],
|
||||
"8zTFvBIAIN8" =>
|
||||
[
|
||||
0x49, 0x89, 0xF8,
|
||||
0x48, 0x89, 0xF0,
|
||||
0x48, 0x89, 0xD1,
|
||||
0xF3, 0xAA,
|
||||
0x4C, 0x89, 0xC0,
|
||||
0xC3,
|
||||
0x48, 0x89, 0xF8, // mov rax, rdi (return dst)
|
||||
0x48, 0x81, 0xFF, 0x00, 0x00, 0x01, 0x00, // cmp rdi, 0x10000
|
||||
0x72, 0x2B, // jb done
|
||||
0x49, 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, // mov r8, 0x800000000000
|
||||
0x4C, 0x39, 0xC7, // cmp rdi, r8
|
||||
0x73, 0x1C, // jae done
|
||||
0x48, 0x81, 0xFA, 0x00, 0x00, 0x00, 0x20, // cmp rdx, 0x20000000
|
||||
0x77, 0x13, // ja done
|
||||
0x48, 0x85, 0xD2, // test rdx, rdx
|
||||
0x74, 0x0E, // jz done
|
||||
0x48, 0x89, 0xD1, // mov rcx, rdx
|
||||
0x49, 0x89, 0xF9, // mov r9, rdi
|
||||
0x89, 0xF0, // mov eax, esi
|
||||
0xFC, // cld
|
||||
0xF3, 0xAA, // rep stosb
|
||||
0x4C, 0x89, 0xC8, // mov rax, r9
|
||||
0xC3, // done: ret
|
||||
],
|
||||
_ => default,
|
||||
};
|
||||
@@ -1355,10 +1406,25 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
||||
{
|
||||
return exportName switch
|
||||
{
|
||||
"memcpy" or
|
||||
"memmove" or
|
||||
"memset" or
|
||||
"memcmp" => true,
|
||||
// memset/memcpy excluded: the raw LLE routines have no null/bounds guard and crash
|
||||
// with an access violation on bad pointers (observed hit during Quake's CL_Init,
|
||||
// where a still-unidentified upstream bug calls memcpy/memset with a null
|
||||
// destination). Both are instead served by the guarded native intrinsics in
|
||||
// TryCreateNativeImportIntrinsic, which fail safely without leaving the leaf path.
|
||||
"memcmp" or
|
||||
// _Getpctype must come from the game's own Dinkumware libc when one is bundled:
|
||||
// it returns a pointer to that CRT's ctype bitmask table, whose bit layout
|
||||
// (_DI=0x20, _SP=0x04, _BB=0x80, ...) differs from the MSVC-style table the HLE
|
||||
// fallback used to serve. Serving the wrong layout made the bundled printf engine
|
||||
// render every Sys_Error message as an empty string (isdigit misfired during
|
||||
// %-directive parsing) and made mcpp drop 'a'-'f' from identifiers ("texture" ->
|
||||
// "txtur", the 0x80 bit reads as _BB/control there). _Getptolower/_Getptoupper
|
||||
// already resolve to the bundled module because no HLE export shadows them; this
|
||||
// keeps _Getpctype consistent with them. It is a pure accessor returning a pointer
|
||||
// to a const table, so it is also the cheapest possible LLE call - important
|
||||
// because parsers hit it once per input character.
|
||||
"_Getpctype" => true,
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user