PC port: Phase 2 — real threads, dual-mapped DRAM, fast3d integration

- Replace setjmp/longjmp green threads with real pthreads (8MB stacks);
  PortQueue side table (mutex+condvar) for OSMesgQueue; dedicated tick
  thread posts VI retrace + services timers (D24).
- Dual-mapped N64 DRAM: one 8MB store at V1 0x70000000 (s32-safe, all game
  RAM symbols) and V2 0x80000000 (KSEG0 mirror for offset|0x80000000
  rebuilds). Windows: CreateFileMappingW + MapViewOfFileEx; POSIX: memfd
  + MAP_FIXED. dram_syms.s pins cfb_16/_bssSegmentEnd/tlb block (D25).
- Address-width shims: PHYS_TO_K0 identity, OS_K0_TO_PHYSICAL = offset from
  V1 base, OS_PHYSICAL_TO_K0 identity; fast3d seg_addr() remaps small
  offsets into V2 (D26).
- ASLR off (-Wl,--disable-dynamicbase): absolute symbols referenced through
  pointer-typed externs (.refptr + BASE reloc) must not rebase; startup
  sanity check on image base 0x140000000 (D27).
- osPiStartDma posts its completion message on every path (fixes
  romReceiveMesg deadlock) (D29).
- SEH-free crash handler: raw fault log first, validated manual EBP-chain
  walk second (-fno-omit-frame-pointer), TerminateProcess; crashDumpThreads
  via Toolhelp32 for heartbeat hang dumps (D30).
- PD fast3d integrated (port/fast3d/), replaces gfxstub.c; real src/sched.c
  compiled; mainproc runs as a real pthread, host thread pumps SDL events.
- Docs: findings D24-D31 + Phase 2 status (section F/G).

Current state: boots through bossInitMainthreadData to langInit(), then
SIGSEGVs on the first file load — stack frame clobbered by a table of
truncated s32 pointers (D31, open). Next: attribute the write with
'set scheduler-locking on' + hardware watchpoint.
This commit is contained in:
James Dansereau committed 2026-08-21 14:42:19 -04:00
1 parent 2aad171344
commit 13ca03a6db
42 files changed
+12660 -537

No files matched your search

+6
View File
@@ -117,3 +117,9 @@ assets/obseg/em/data/under.seg
scripts/local/
scripts/make/local/
scripts/__pycache__/generate_prop_model_c.cpython-310.pyc
# PC port debug/session artifacts
_run.log
ge007.crash.log
.pi/
run_out.log
+55 -4
View File
@@ -148,6 +148,23 @@ configure_file(
"${CMAKE_SOURCE_DIR}/port/include/hoststring.h.in"
"${CMAKE_BINARY_DIR}/port/include/hoststring.h" @ONLY)
# Host <stdlib.h> path for port/shim/stdlib.h. The N64 include/stdlib.h
# typedefs lldiv_t/ldiv_t, which conflict with the host's when both land in
# one C++ TU (fast3d): MinGW's own sec_api/stdlib_s.h does `#include
# <stdlib.h>`, which resolves to the N64 header via -I. The shim routes C++
# TUs to the host header by absolute path (same trick as hoststring.h).
get_filename_component(PORT_HOST_STDLIB_H "${_PORT_GCC_DIR}/../include/stdlib.h" ABSOLUTE)
configure_file(
"${CMAKE_SOURCE_DIR}/port/include/hoststdlib.h.in"
"${CMAKE_BINARY_DIR}/port/include/hoststdlib.h" @ONLY)
# Host <stddef.h> path for port/shim/stddef.h (same reason as stdlib: the N64
# stub shadows the host header in C++ TUs, hiding max_align_t from cstddef).
get_filename_component(PORT_HOST_STDDDEF_H "${_PORT_GCC_DIR}/../include/stddef.h" ABSOLUTE)
configure_file(
"${CMAKE_SOURCE_DIR}/port/include/hoststddef.h.in"
"${CMAKE_BINARY_DIR}/port/include/hoststddef.h" @ONLY)
# ----------------------------------------------------------------------------
# Compiler definitions & flags
# ----------------------------------------------------------------------------
@@ -170,6 +187,11 @@ add_compile_options(
-fwrapv
-include "versioninfo.h"
)
# Keep frame pointers: dbghelp's StackWalk64 (crash handler + thread dumps)
# cannot read DWARF unwind info on MinGW, so the EBP chain is the only way to
# get a real backtrace of a hung/crashed game thread.
add_compile_options(-fno-omit-frame-pointer)
if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
add_compile_options(-Og)
endif()
@@ -201,6 +223,18 @@ add_compile_options(
add_compile_options($<$<COMPILE_LANGUAGE:C>:-Wno-pointer-sign>)
add_compile_options($<$<COMPILE_LANGUAGE:CXX>:-Wno-unused-function>)
# ASLR must be OFF: dram_syms.s defines absolute symbols (cfb_16 @ 0x70000000,
# _bssSegmentEnd @ 0x70050000) that the game references through pointer-typed
# externs (src/bondgame.h: `extern u32 *_bssSegmentEnd`). On x86-64/PE, taking
# their address emits a .refptr slot holding the absolute value PLUS a base
# relocation; with ASLR the loader rebases it to runtime_base+0x70050000 =
# garbage. With DYNAMICBASE disabled the image loads at its preferred base
# (0x140000000) and the relocations are no-ops. main.c sanity-checks the load
# base at startup.
if(MINGW)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--disable-dynamicbase")
endif()
# port/shim first: PC-only shadow headers (bondtypes.h, bondconstants.h,
# PR/gbi.h) that fix N64-compiler assumptions without touching src/ or
# include/. Inert in the N64 build (its Makefile never sees port/shim).
@@ -244,7 +278,10 @@ endif()
list(APPEND LIBS "${GL_LIBRARY}")
if(NOT EXTRA_LIBRARIES)
if(WIN32)
if(MINGW)
# MinGW: pthreads for the real-OS-thread kernel (port/src/libultra.c).
set(EXTRA_LIBRARIES dbghelp -static-libstdc++ pthread)
elseif(WIN32)
set(EXTRA_LIBRARIES dbghelp -static-libstdc++)
else()
set(EXTRA_LIBRARIES stdc++ m dl pthread)
@@ -258,7 +295,8 @@ list(APPEND LIBS "${EXTRA_LIBRARIES}")
# Port layer (shims + software RSP).
file(GLOB_RECURSE SRC_PORT "${CMAKE_SOURCE_DIR}/port/src/*.c")
file(GLOB_RECURSE SRC_PORT_CXX "${CMAKE_SOURCE_DIR}/port/src/*.cpp")
file(GLOB_RECURSE SRC_FAST3D "${CMAKE_SOURCE_DIR}/port/fast3d/*.cpp")
file(GLOB_RECURSE SRC_FAST3D "${CMAKE_SOURCE_DIR}/port/fast3d/*.cpp"
"${CMAKE_SOURCE_DIR}/port/fast3d/glad/*.c")
# Generated absolute ROM asset symbols for this region (see
# scripts/gen_romassets.py). Defines every obseg/ramrom/music label and the
@@ -269,6 +307,10 @@ if(NOT EXISTS ${SRC_ROMASSETS})
message(FATAL_ERROR "Missing ${SRC_ROMASSETS} — run: python3 scripts/gen_romassets.py ${ASSET_REGION}")
endif()
# Absolute symbols inside the reserved N64-DRAM region (port/src/dram.c):
# cfb_16 (replaces src/cfb.c) and _bssSegmentEnd (mempool start marker).
set(SRC_DRAM_SYMS "${CMAKE_SOURCE_DIR}/port/src/dram_syms.s")
# Game code — compiled unmodified.
file(GLOB_RECURSE SRC_GAME "${CMAKE_SOURCE_DIR}/src/game/*.c")
@@ -344,21 +386,29 @@ list(PREPEND SRC_ASSETS "${CMAKE_SOURCE_DIR}")
# extern decls in bondgame.h / audi.c).
#
# EXCLUDED (hardware drivers, replaced by port/ shims):
# sched.c crash.c rmon.c usb.c tlb_*.c pi.c vi.c motor.c
# crash.c rmon.c usb.c tlb_*.c pi.c vi.c motor.c
# (there is no src/spectrum.c in this decomp)
# All of src/libultra/io/*.c and src/libultrare/io/*.c EXCEPT vitbl.c.
# motor.c / pfsinit.c / pfsisplug.c are NOT compiled: joy.c calls
# osMotorInit/Start/Stop and osPfsInit/osPfsIsPlug only for accessory
# (Rumble Pak / Memory Pak) detection, so they are shimmed as no-ops in
# port/src/libultra.c (see B2 — saves are EEPROM, not PFS).
#
# sched.c IS compiled (Phase 2): the game's real scheduler runs on the
# cooperative thread kernel; its hardware leaf calls (osSpTaskLoad/StartGo,
# osDpSetNextBuffer, osVi*) are shimmed in port/src/libultra.c, which runs
# the software RSP inline and posts the RSP_DONE/RDP_DONE messages.
set(SRC_ENGINE
src/init.c
src/sched.c
src/audi.c
src/snd.c
src/music.c
src/joy.c
src/fr.c
src/cfb.c
# src/cfb.c — cfb_16 now lives at 0x80000000 in the reserved DRAM region
# (port/src/dram_syms.s) so OS_K0_TO_PHYSICAL(cfb_16[0]) is a small offset
# that fast3d can map back; a host-BSS array would truncate to garbage.
src/stacks.c
src/boss.c
src/mema.c
@@ -377,6 +427,7 @@ set(SRC
${SRC_GAME}
${SRC_PORT}
${SRC_ROMASSETS}
${SRC_DRAM_SYMS}
${SRC_PORT_CXX}
${SRC_FAST3D}
${SRC_LIBAUDIO}
+185
View File
@@ -733,3 +733,188 @@ validation, 12 MB mapped at 0x10000000, window renders for the full test
duration. Asset symbol spot-checks against the ROM confirm correct offsets
(e.g. music bank BE seqCount=63 @0x419790, first track offset 508 → Mno_music
@0x41998C; sfx/instrument banks start with GE's `B1` header).
### F. Phase 2 runtime findings (threads, DRAM, addresses)
Phase 2 replaced the Phase-1 demo loop with the real `mainproc()` on real OS
threads, compiled GE's real `src/sched.c`, and brought in PD's fast3d software
RSP (`port/fast3d/`). The boot path now runs: ROM map → DRAM reserve → video
init → mainThread (real pthread) → bossEntry → bossInitMainthreadData through
mempool init, VI init, rspInit, joyInit + controller-init timers, stanInit,
gameInit — and currently dies inside `langInit()` (D31).
* **D24 — setjmp/longjmp green threads are unusable on MinGW x64.** The first
kernel used setjmp/longjmp context switches; under Windows x64 + MinGW the
longjmp path corrupts FPU/MC state when it interacts with SEH unwinding
(observed: STATUS_DATATYPE_MISALIGNMENT-class crash inside a resumed
"thread" with garbage register state). PD's port does not use setjmp at all.
**Resolved:** `port/src/libultra.c` now runs every game thread as a real
pthread (`PortThread` side table, 8 MB stacks). Message queues keep the N64
`OSMesgQueue` layout and get a `PortQueue` side table (mutex + condvar, max
64 queues); all osSendMesg/osRecvMesg paths lock it. A dedicated tick thread
posts one VI retrace per frame (NTSC 60 Hz) and services the software timers
(both OS_MESG_NOBLOCK — the tick thread can never deadlock). `idleThread` is
intercepted by ID and parked; `mainproc` runs as a real pthread so the host
main thread is free to pump SDL/OS events (`videoPumpEvents()` in the host
loop — on Windows WndProc only runs on the window-creating thread). The GL
context is made current on shedThread per frame via
`gfx_sdl_make_context_current()`. Verified live: VI retrace flowing, all 4
controller-init timers fire, bossmq loop completes.
* **D25 — dual-mapped N64 DRAM region.** Game code needs two incompatible
address forms of the same RAM: `osVirtualToPhysical`/s32 arithmetic wants
live host pointers that fit in a positive s32, while `offset | 0x80000000`
rebuilds (bg.c:3184/3322, propobj.c:8578/8699, title.c:476) want a KSEG0
view at 0x80000000. **Resolved:** one 8 MB backing store mapped twice —
V1 @ 0x70000000 (all game RAM symbols live here) and V2 @ 0x80000000
(byte-identical mirror). `port/src/dram.c`: Windows uses
`CreateFileMappingW(INVALID_HANDLE_VALUE,…)` + two `MapViewOfFileEx(…, base)`
calls (the documented kernel32 API that maps a section at an exact address;
`NtMapViewOfSection` fails with STATUS_MEMORY_NOT_ALLOCATED 0xc0000045 and
segfaults on wrong SECTION_INHERIT values — do not retry it); POSIX uses
memfd + two MAP_SHARED|MAP_FIXED mmaps. Both views sanity-checked to alias.
`port/src/dram_syms.s` pins the absolute symbols: `cfb_16` @ 0x70000000
(0x4B000), `_bssSegmentEnd` @ 0x70050000 (mempool start), tlb block end @
0x702F4400 (= page_align_down(0x803AB400) − 93×0x2000, the exact N64 value;
the tlbmanage stub returns it so `mempInit`'s pool size is correct — a NULL
return here made the mempool spin in `while(1)` at src/memp.c:164).
* **D26 — address-width shims (K0 sign-extension).** N64 K0 addresses have bit
31 set; passing them through s32 parameters sign-extends to invalid x86-64
pointers. With the dual map, live RAM sits below 0x80000000 so identity is
safe: `port/shim/PR/R4300.h` → `PHYS_TO_K0(x) = (x)`; `port/shim/PR/os.h` →
`OS_K0_TO_PHYSICAL(x) = (u32)((char *)x - 0x70000000)` (GBI w1 words then
carry small offsets) and `OS_PHYSICAL_TO_K0(x) = (x)`. fast3d's `seg_addr()`
(gfx_pc.cpp) resolves both forms: full host pointers pass through, values <
0x800000 get +0x80000000 (landing in V2). Same remap applied to the
G_MW_SEGMENT handler.
* **D27 — ASLR must be OFF for this exe.** `src/bondgame.h:8` declares
`extern u32 *_bssSegmentEnd;` (pointer type), so `&_bssSegmentEnd` in game
code emits a `.refptr._bssSegmentEnd` slot holding the absolute value with a
BASE relocation. Under ASLR the loader rebases it to runtime_base +
0x70050000 = garbage, and `mempCheckMemflagTokens` then spins/AVs with
poolAreaStart ≈ 0xF0xxxxxx. (On MIPS `&abs_symbol` is the symbol value
itself; x86-64/PE cannot do that.) **Resolved:** MINGW link flag
`-Wl,--disable-dynamicbase` (note: `--disable-dynamic-base` is NOT
recognized by GNU ld 2.47); the image now loads at its preferred base
0x140000000 and relocations are no-ops. `main.c` fails loudly at startup if
`sysImageBase() != 0x140000000ul` so any future ASLR regression is a clean
error, not silent DRAM corruption.
* **D28 — ninja stale-object hazard with new shim headers.** If a shim header
did not exist when a .obj was last built, it is absent from that object's
.d dependency file and ninja will NOT rebuild the TU after the header
appears — the old (unshimmed) code silently persists. Observed: boss.c.obj
still contained `or $0x80000000,%eax` (original PHYS_TO_K0) days after the
shim landed. **Rule:** whenever anything under `port/shim/` changes, delete
all `.obj` files and full-rebuild. Verify with `gcc -E` using the exact
ninja flags (`ninja -C build-pc -t commands <tgt>`) plus objdump of the
rebuilt object.
* **D29 — `osPiStartDma` must post its completion message on every path.** The
N64 PI posts the caller's OSMesgPI to mq when a DMA completes; GE never
inspects the message but always blocks on it (`romReceiveMesg()` →
`osRecvMesg(OS_MESG_BLOCK)` in ramrom.c:44). The shim did the memcpy
synchronously and posted nothing → mainThread deadlocked in the first file
load. **Resolved:** post unconditionally after `piServiceDma()` (also for
skipped/dropped DMAs) with OS_MESG_BLOCK, posting the caller's OSMesgPI.
* **D30 — crash handler without SEH.** MinGW GCC 16 has no `__try`/`__except`
and does not recognize `-fseh-exceptions`, so the unhandled-exception filter
cannot rely on structured exceptions to protect symbolication. **Resolved:**
`port/src/crash.c` phase 1 writes raw fault info (registers, modules) to
`ge007.crash.log` without touching dbghelp (SymInitialize/StackWalk64 can
allocate and re-fault inside the filter); phase 2 does a validated manual
EBP-chain walk (`-fno-omit-frame-pointer` on all TUs; each frame is [saved
RBP, return address], stop if saved_fp ≤ fp or outside thread stack limits).
`TerminateProcess` instead of abort (SIGABRT can re-enter the exception
machinery). `crashDumpThreads()` (Toolhelp32 snapshot + SuspendThread +
GetThreadContext + StackWalk64 per thread) is called from the heartbeat
error dump with TID→name matching; StackWalk64 cannot read DWARF unwind info
on MinGW, so multi-frame traces depend entirely on the EBP chain. Symbols
are recovered offline: `addr2line -e build-pc/ge007.x86_64.exe -f -C
<base+rel>` (the log's "rel" = address − actual load base).
* **D31 — langInit SIGSEGV: stack frame clobbered by a truncated-pointer table (OPEN).**
After D24–D29, mainThread reaches `langInit()` and dies on the **first** file
load (`_fileNameLoadToBank(LnameX_lookuptable[LGUN][…])` → index 670 →
`fileIndexLoadToBank` → `mempAllocBytesInBank` → `load_resource`). Crash
signature: RIP = 0x140330003 (inside `.bss`, in `g_Props`), RSP only ~0xD8
below langInit's entry RSP (a *shallow* chain — the stack pointer itself is
fine), and the frame region above load_resource's return address is filled
with 8-byte slots `{u32, 0x00000001}` where each u32 is the **low 32 bits of
a live .bss pointer**, alternating between `g_Props` (0x14033xxxx) and
`resource_lookup_data_array` (0x14034xxxx). The crash is a `ret` popping one
of these truncated pointers as the return address (observed RIP equals the
table value exactly, high word 0x00000001). Something in the load chain is
storing 64-bit pointers into s32/int storage (fine on 32-bit MIPS,
truncating on x86-64) and that storage overlaps a live frame.
Established facts (all under gdb, `gdb -batch -ex "handle SIGSEGV stop"`):
* All arguments at `load_resource` entry are valid: ptrdata=0x702aa400
(mempool), srcfile->hw_address=0x108ed250, rom_size=0x720, source=
0x702f3ce0. `romCopy` (PI shim memcpy) completes.
* `zlib_inflate()` itself **completes normally** (`finish` → returns 0,
rz_wp=3872; the RZ stream is plain deflate after a 2-byte header, so
endianness is not an issue in the bitstream). At its entry rz_outbuf =
ptrdata ✓, rz_inbuf = source+2 ✓, and the huft table base (tl) sits inside
load_resource's 8 KB local `buffer` (rz_hlist).
* **Not** a huft-table overflow: max observed `rz_hufts` = 797 entries ×
4 B (`struct huft {u8 e; u8 b; union{u16 n; struct huft *t} v;}`) = 3188 B
< 8448 B buffer.
* Ruled out by code inspection: `mempAddEntryOfSizeToBank` (only touches
pool pos/prevpos), `fileGetIndex` (reads only), `decompressdata` epilogue
(returns rz_wp).
* `load_resource` prologue: `push rbp/rdi/rsi/rbx; mov $0x2128,%eax;
call ___chkstk_ms; sub %rax,%rsp; lea 0x80(%rsp),%rbp` — RBP is a **fake
frame pointer** (RSP+0x80); the real return address is at [RBP+0x20E8].
The compiler reuses pushed-register slots as locals (verified: the saved-
rbx slot [RBP+0x20D0] legitimately receives `buffer+12`). Watchpoints on
RBP-relative offsets are therefore easy to misplace — compute from entry
RSP instead.
* Hardware watchpoint on the real retaddr slot (set at the first
instruction, where [RSP] = retaddr): first write changes it from a valid
.text return address to **`buffer+12`** (a pointer into load_resource's
own local array), reported RIP at the prologue boundary (`push %rdi`) —
i.e. either a concurrent write by another thread (hardware watchpoints are
global) or an 8-byte-attributed access. A `thread apply all bt` at that
moment showed every other game thread parked (RtlUserThreadStart / sleep
syscalls; gdb could not unwind their stacks further), so the writer is not
yet identified.
Next steps: re-run the watchpoint with **`set scheduler-locking on`**
(only mainThread runs → any hit is its own) to attribute the write to an
exact instruction; if it is load_resource's own spill, that is a GCC
miscompilation → compile ob.c at -O0/-Og or add a PORT-gated hook. Also try
multiple watchpoints on consecutive slots to catch a sequential table fill
mid-flight, and grep the disassembly of the whole chain for stores through
s32-typed pointers (`movl …(%reg)` where %reg holds a truncated pointer).
### G. Phase 2 status (current)
Done: PD fast3d integrated (`port/fast3d/`, replaces the deleted gfxstub.c);
GE's real `src/sched.c` compiled (shedThread runs `__scMain`); real pthread
kernel (D24); dual-mapped DRAM + address shims (D25/D26); ASLR off with startup
sanity check (D27); PI completion messages (D29); SEH-free crash handler with
thread dumps (D30). The process now boots, maps the ROM, opens the window,
runs `mainproc()` → `bossInitMainthreadData` through controller init / timers /
mempool / VI / rsp / stan / game init — and SIGSEGVs inside `langInit()`'s
first file load (D31). No frames rendered yet.
Reference — the file-load chain (for D31 debugging):
`langInit` (language.c:230) loads 7 text banks via
`_fileNameLoadToBank(LnameX_lookuptable[bank][j_text_trigger], …, 0x100,
MEMPOOL_PERMANENT)` → `fileGetIndex` (strcmp over file_resource_table) →
`fileIndexLoadToBank(index, …)` (ob.c:216): if poolRemaining==0 take
`mempGetBankSizeLeft(bank)`; `ptrdata = mempAllocBytesInBank(…)`; hw_address
nonzero → `load_resource(ptrdata, poolRemaining, &file_resource_table[index],
&resource_lookup_data_array[index])` (ob.c:37): `source = (ptrdata + bytes) −
((rom_size+7)&−8)`; `romCopy(source, hw_address, rom_size)`;
`decompressdata(source, ptrdata, buffer)` with `u8 buffer[0x2100]` on the
stack → sets rz_inbuf=source+2 / rz_outbuf=ptrdata / rz_hlist=buffer →
`zlib_inflate()` (src/game/zlib.c:668). rom_size for index 670 = 0x720,
decompressed size = 3872 (0xF20). `file_resource_table` is compiled
(`assets/obseg/file_resource_table.inc.c`) with hw_address = cart addresses;
rom_size = adjacent-marker differences computed in obInit.
Debugging techniques that worked: gdb **launch** mode (`gdb -batch -ex
"handle SIGSEGV stop" -ex run …` — attach fails with error 87); breakpoint
chains through init functions to bracket the crash; `finish` to let a suspect
function complete and inspect its return; hardware watchpoints on exact stack
slots (compute offsets from entry RSP, not RBP — see D31); `addr2line` for
offline symbolication; objdump + `gcc -E` with exact ninja flags to verify
shim expansion in compiled code.
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 Emill, MaikelChan
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+63
View File
@@ -0,0 +1,63 @@
/*
* GE adaptation header for fast3d (see docs/PCPortResearch.md).
*
* fast3d was written against Perfect Dark's extended GBI set
* (pd_port/src/include/gbiex.h + PD's include/PR/gbi.h). GoldenEye shares the
* same Rare "Indy" engine family but its extension header is
* include/gbi_extension.h, which defines G_TRI4 (0xb1) and GE's custom
* combine/RM modes — but NOT the PD-specific opcodes below.
*
* GE never emits the PD-only opcodes (its RSP ucode is rsp/graphics/gmain.s,
* which has no handling for them), so the corresponding dispatch cases in
* gfx_pc.cpp are dead code for this port; they only need to COMPILE. The
* values mirror PD's gbiex.h exactly so that if a ROM ever does emit one,
* behaviour matches the sibling port rather than hitting the fatal "unknown
* opcode" path.
*
* G_COL (7) is "new in PD": GE fills vertex colours directly in Vtx.cn[]
* and its ucode has no colour-table DMA, so that case is dead here too.
*/
#ifndef FAST3D_GBIEX_H
#define FAST3D_GBIEX_H
#include <PR/gbi.h>
#include "include/gbi_extension.h" /* G_TRI4 (0xb1), GE's own extensions */
/* PD extension opcodes (pd_port/src/include/gbiex.h). */
#define G_COL 7 /* vertex colour DMA — PD-only */
#define G_SETFB_EXT 0x21
#define G_SETTIMG_FB_EXT 0x23
#define G_INVALTEXCACHE_EXT 0x34
#define G_TEXRECT_WIDE_EXT 0x37
#define G_FILLRECT_WIDE_EXT 0x38
#define G_SETGRAYSCALE_EXT 0x39
#define G_EXTRAGEOMETRYMODE_EXT 0x3a
#define G_SETINTENSITY_EXT 0x40
#define G_COPYFB_EXT 0x41
#define G_IMAGERECT_EXT 0x42
#define G_RDPFLUSH_EXT 0x43
#define G_CLEAR_DEPTH_EXT 0x44
#define G_SETSUBPIXELOFFSET_EXT 0x45
/* Extra geometry/aspect bits used by the PD ucode (dead for GE). */
#define G_ASPECT_LEFT_EXT 0x00000010
#define G_ASPECT_RIGHT_EXT 0x00000020
#define G_ASPECT_WIDE_EXT 0x00000040
#define G_ASPECT_CENTER_EXT (G_ASPECT_LEFT_EXT | G_ASPECT_RIGHT_EXT)
#define G_ASPECT_MODE_EXT (G_ASPECT_CENTER_EXT | G_ASPECT_WIDE_EXT)
#define G_NO_CLIPPING_EXT 0x00000100
#define G_MODULATE_EXT 0x00000200
#define G_TF_BLUR_EXT (1 << G_MDSFT_TEXTFILT)
/* Big-endian read helpers (pd_port/src/include/platform.h). The host is
* little-endian, so a "BE" value in ROM data must be byte-swapped. */
#if defined(__GNUC__) || defined(__clang__)
#define PD_BSWAP16(x) __builtin_bswap16(x)
#define PD_BSWAP32(x) __builtin_bswap32(x)
#else
#error "Implement PD_BSWAP macros for your compiler."
#endif
#define PD_BE16(x) PD_BSWAP16(x)
#define PD_BE32(x) PD_BSWAP32(x)
#endif /* FAST3D_GBIEX_H */
+30 -38
View File
@@ -1,29 +1,18 @@
#ifndef GFX_API_H
#define GFX_API_H
/*
* Public API for the software RSP (fast3d).
*
* This is the interface the rest of the port (video.c / gesched.c) uses to
* drive the RSP. It mirrors the PD port's port/fast3d/gfx_api.h.
*
* STATUS: scaffolding — the implementation (gfx_pc.cpp etc.) is brought in
* during Phase 2 by adapting the PD fast3d. See port/fast3d/README.md.
*/
#ifndef __cplusplus
#include <stdint.h>
#include <stdbool.h>
#endif
#include <PR/gbi.h>
#include "gfx_rendering_api.h"
#include "gfx_window_manager_api.h"
#ifdef __cplusplus
extern "C" {
#endif
struct GfxRenderingAPI;
struct GfxWindowManagerAPI;
struct XYWidthHeight {
int16_t x, y;
uint32_t width, height;
};
struct GfxDimensions {
float internal_mul;
@@ -34,33 +23,36 @@ struct GfxDimensions {
struct GfxInitSettings {
struct GfxWindowManagerAPI *wapi;
struct GfxRenderingAPI *rapi;
/* window title / size / fullscreen / etc. — see PD gfx_window_manager_api.h */
const char *title;
uint32_t width, height;
int fullscreen;
int vsync;
struct GfxWindowInitSettings window_settings;
};
/* Current draw-area dimensions (before any scaling). */
extern struct GfxDimensions gfx_current_dimensions;
extern struct GfxDimensions gfx_current_window_dimensions; // The dimensions of the window
extern struct GfxDimensions
gfx_current_dimensions; // The dimensions of the draw area the game draws to, before scaling (if applicable)
extern struct XYWidthHeight
gfx_current_game_window_viewport; // The area of the window the game is drawn to, (0, 0) is top-left corner
extern uint32_t gfx_msaa_level;
extern struct XYWidthHeight gfx_current_native_viewport; // The internal/native video mode of the game
extern float gfx_current_native_aspect; // The aspect ratio of the above mode
extern bool gfx_framebuffers_enabled;
extern bool gfx_detail_textures_enabled;
void gfx_init(const struct GfxInitSettings *settings);
void gfx_destroy(void);
/* Frame boundary. */
struct GfxRenderingAPI* gfx_get_current_rendering_api(void);
void gfx_start_frame(void);
void gfx_run(Gfx* commands);
void gfx_end_frame(void);
void gfx_set_target_fps(int);
void gfx_set_texture_filter(enum FilteringMode mode);
void gfx_set_mipmap_filter(enum MipmapFilteringMode mode);
void gfx_texture_cache_clear(void);
void gfx_texture_cache_delete(const uint8_t *orig_addr);
void gfx_texture_cache_delete_range(const uint8_t *start, const uint8_t *end);
int gfx_create_framebuffer(uint32_t width, uint32_t height, int upscale, int autoresize);
void gfx_resize_framebuffer(int fb, uint32_t width, uint32_t height, int upscale, int autoresize);
void gfx_set_framebuffer(int fb, float noise_scale) ;
void gfx_reset_framebuffer(void);
void gfx_copy_framebuffer(int fb_dst, int fb_src, int left, int top, int use_back);
/* Run the RSP over a display list (the core of the emulation). */
void gfx_run(Gfx *commands);
/* Options. */
void gfx_set_target_fps(int fps);
void gfx_set_texture_filter(int mode);
void gfx_set_mipmap_filter(int mode);
#ifdef __cplusplus
}
#endif
#endif /* GFX_API_H */
+59
View File
@@ -0,0 +1,59 @@
#include "gfx_cc.h"
void gfx_cc_get_features(uint64_t shader_id0, uint32_t shader_id1, struct CCFeatures* cc_features) {
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 2; j++) {
for (int k = 0; k < 4; k++) {
cc_features->c[i][j][k] = (shader_id0 >> (i * 32 + j * 16 + k * 4)) & 0xf;
}
}
}
cc_features->opt_alpha = (shader_id1 & SHADER_OPT_ALPHA) != 0;
cc_features->opt_fog = (shader_id1 & SHADER_OPT_FOG) != 0;
cc_features->opt_texture_edge = (shader_id1 & SHADER_OPT_TEXTURE_EDGE) != 0;
cc_features->opt_noise = (shader_id1 & SHADER_OPT_NOISE) != 0;
cc_features->opt_2cyc = (shader_id1 & SHADER_OPT_2CYC) != 0;
cc_features->opt_alpha_threshold = (shader_id1 & SHADER_OPT_ALPHA_THRESHOLD) != 0;
cc_features->opt_invisible = (shader_id1 & SHADER_OPT_INVISIBLE) != 0;
cc_features->opt_grayscale = (shader_id1 & SHADER_OPT_GRAYSCALE) != 0;
cc_features->opt_blur = (shader_id1 & SHADER_OPT_BLUR) != 0;
cc_features->clamp[0][0] = (shader_id1 & SHADER_OPT_TEXEL0_CLAMP_S);
cc_features->clamp[0][1] = (shader_id1 & SHADER_OPT_TEXEL0_CLAMP_T);
cc_features->clamp[1][0] = (shader_id1 & SHADER_OPT_TEXEL1_CLAMP_S);
cc_features->clamp[1][1] = (shader_id1 & SHADER_OPT_TEXEL1_CLAMP_T);
cc_features->used_textures[0] = false;
cc_features->used_textures[1] = false;
cc_features->num_inputs = 0;
for (int c = 0; c < 2; c++) {
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 4; j++) {
if (cc_features->c[c][i][j] >= SHADER_INPUT_1 && cc_features->c[c][i][j] <= SHADER_INPUT_7) {
if (cc_features->c[c][i][j] > cc_features->num_inputs) {
cc_features->num_inputs = cc_features->c[c][i][j];
}
}
if (cc_features->c[c][i][j] == SHADER_TEXEL0 || cc_features->c[c][i][j] == SHADER_TEXEL0A) {
cc_features->used_textures[0] = true;
}
if (cc_features->c[c][i][j] == SHADER_TEXEL1 || cc_features->c[c][i][j] == SHADER_TEXEL1A) {
cc_features->used_textures[1] = true;
}
}
}
}
for (int c = 0; c < 2; c++) {
cc_features->do_single[c][0] = cc_features->c[c][0][2] == SHADER_0;
cc_features->do_single[c][1] = cc_features->c[c][1][2] == SHADER_0;
cc_features->do_multiply[c][0] = cc_features->c[c][0][1] == SHADER_0 && cc_features->c[c][0][3] == SHADER_0;
cc_features->do_multiply[c][1] = cc_features->c[c][1][1] == SHADER_0 && cc_features->c[c][1][3] == SHADER_0;
cc_features->do_mix[c][0] = cc_features->c[c][0][1] == cc_features->c[c][0][3];
cc_features->do_mix[c][1] = cc_features->c[c][1][1] == cc_features->c[c][1][3];
cc_features->color_alpha_same[c] =
((shader_id0 >> c * 32) & 0xffff) == ((shader_id0 >> (c * 32 + 16)) & 0xffff);
}
}
+75
View File
@@ -0,0 +1,75 @@
#ifndef GFX_CC_H
#define GFX_CC_H
#include <stdint.h>
#include <stdbool.h>
#ifdef __cplusplus
#include <compare>
#endif
enum {
SHADER_0,
SHADER_INPUT_1,
SHADER_INPUT_2,
SHADER_INPUT_3,
SHADER_INPUT_4,
SHADER_INPUT_5,
SHADER_INPUT_6,
SHADER_INPUT_7,
SHADER_TEXEL0,
SHADER_TEXEL0A,
SHADER_TEXEL1,
SHADER_TEXEL1A,
SHADER_1,
SHADER_COMBINED,
SHADER_NOISE,
SHADER_LOD_FRAC,
};
#define SHADER_OPT_ALPHA (1 << 0)
#define SHADER_OPT_FOG (1 << 1)
#define SHADER_OPT_TEXTURE_EDGE (1 << 2)
#define SHADER_OPT_NOISE (1 << 3)
#define SHADER_OPT_2CYC (1 << 4)
#define SHADER_OPT_ALPHA_THRESHOLD (1 << 5)
#define SHADER_OPT_INVISIBLE (1 << 6)
#define SHADER_OPT_GRAYSCALE (1 << 7)
#define SHADER_OPT_TEXEL0_CLAMP_S (1 << 8)
#define SHADER_OPT_TEXEL0_CLAMP_T (1 << 9)
#define SHADER_OPT_TEXEL1_CLAMP_S (1 << 10)
#define SHADER_OPT_TEXEL1_CLAMP_T (1 << 11)
#define SHADER_OPT_BLUR (1 << 12)
struct ColorCombinerKey {
uint64_t combine_mode;
uint64_t options;
auto operator<=>(const ColorCombinerKey&) const = default;
};
#define SHADER_MAX_TEXTURES 2
#define SHADER_FIRST_TEXTURE 0
struct CCFeatures {
uint8_t c[2][2][4];
bool opt_alpha;
bool opt_fog;
bool opt_texture_edge;
bool opt_noise;
bool opt_2cyc;
bool opt_alpha_threshold;
bool opt_invisible;
bool opt_grayscale;
bool opt_blur;
bool used_textures[2];
bool clamp[2][2];
int num_inputs;
bool do_single[2][2];
bool do_multiply[2][2];
bool do_mix[2][2];
bool color_alpha_same[2];
};
void gfx_cc_get_features(uint64_t shader_id0, uint32_t shader_id1, struct CCFeatures* cc_features);
#endif
File diff suppressed because it is too large. Load diff
+8
View File
@@ -0,0 +1,8 @@
#ifndef GFX_OPENGL_H
#define GFX_OPENGL_H
#include "gfx_rendering_api.h"
extern struct GfxRenderingAPI gfx_opengl_api;
#endif
File diff suppressed because it is too large. Load diff
+60
View File
@@ -0,0 +1,60 @@
#ifndef GFX_PC_H
#define GFX_PC_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <unordered_map>
#include <list>
#include <cstddef>
#include <PR/gbi.h>
#include "system.h"
#define SCREEN_WIDTH ((int32_t)gfx_current_native_viewport.width)
#define SCREEN_HEIGHT ((int32_t)gfx_current_native_viewport.height)
extern uintptr_t gfxFramebuffer;
struct GfxRenderingAPI;
struct GfxWindowManagerAPI;
struct TextureCacheKey {
const uint8_t* texture_addr;
const uint8_t* palette_addrs[2];
uint8_t fmt, siz;
uint8_t palette_index;
bool operator==(const TextureCacheKey&) const noexcept = default;
struct Hasher {
size_t operator()(const TextureCacheKey& key) const noexcept {
uintptr_t addr = (uintptr_t)key.texture_addr;
return (size_t)(addr ^ (addr >> 5));
}
};
};
typedef std::unordered_map<TextureCacheKey, struct TextureCacheValue, TextureCacheKey::Hasher> TextureCacheMap;
typedef std::pair<const TextureCacheKey, struct TextureCacheValue> TextureCacheNode;
struct TextureCacheValue {
uint32_t texture_id;
uint8_t cms, cmt;
bool linear_filter;
std::list<struct TextureCacheMapIter>::iterator lru_location;
};
struct TextureCacheMapIter {
TextureCacheMap::iterator it;
};
extern "C" {
#include "gfx_api.h"
}
#endif
+61
View File
@@ -0,0 +1,61 @@
#ifndef GFX_RENDERING_API_H
#define GFX_RENDERING_API_H
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
struct ShaderProgram;
struct GfxClipParameters {
bool z_is_from_0_to_1;
bool invert_y;
};
enum FilteringMode { FILTER_NONE, FILTER_LINEAR, FILTER_THREE_POINT };
enum MipmapFilteringMode { MIPMAP_DISABLED, MIPMAP_NEAREST, MIPMAP_LINEAR };
struct GfxRenderingAPI {
const char* (*get_name)(void);
int (*get_max_texture_size)(void);
struct GfxClipParameters (*get_clip_parameters)(void);
void (*unload_shader)(struct ShaderProgram* old_prg);
void (*load_shader)(struct ShaderProgram* new_prg);
struct ShaderProgram* (*create_and_load_new_shader)(uint64_t shader_id0, uint32_t shader_id1);
struct ShaderProgram* (*lookup_shader)(uint64_t shader_id0, uint32_t shader_id1);
void (*shader_get_info)(struct ShaderProgram* prg, uint8_t* num_inputs, bool used_textures[2]);
void (*clear_shaders)(void);
uint32_t (*new_texture)(void);
void (*select_texture)(int tile, uint32_t texture_id, bool linear_filter);
void (*upload_texture)(const uint8_t* rgba32_buf, uint32_t width, uint32_t height, bool gen_mipmaps);
void (*set_sampler_parameters)(int sampler, bool linear_filter, uint32_t cms, uint32_t cmt, bool mipmaps);
void (*set_depth_mode)(bool depth_test, bool depth_update, bool depth_compare, bool depth_source_prim, uint16_t zmode);
void (*set_depth_range)(float znear, float zfar);
void (*set_viewport)(int x, int y, int width, int height);
void (*set_scissor)(int x, int y, int width, int height);
void (*set_use_alpha)(bool use_alpha, bool modulate);
void (*draw_triangles)(float buf_vbo[], size_t buf_vbo_len, size_t buf_vbo_num_tris);
void (*init)(void);
void (*on_resize)(void);
void (*start_frame)(void);
void (*end_frame)(void);
void (*finish_render)(void);
int (*create_framebuffer)();
void (*update_framebuffer_parameters)(int fb_id, uint32_t width, uint32_t height, uint32_t msaa_level,
bool opengl_invert_y, bool render_target, bool has_depth_buffer,
bool can_extract_depth);
bool (*start_draw_to_framebuffer)(int fb_id, float noise_scale);
void (*copy_framebuffer)(int fb_dst, int fb_src, int left, int top, bool flip_y, bool use_back);
void (*clear_framebuffer)(bool clear_color, bool clear_depth);
void (*resolve_msaa_color_buffer)(int fb_id_target, int fb_id_source);
void* (*get_framebuffer_texture_id)(int fb_id);
void (*select_texture_fb)(int fb_id);
void (*delete_texture)(uint32_t texID);
void (*set_texture_filter)(enum FilteringMode mode);
enum FilteringMode (*get_texture_filter)(void);
void (*set_mipmap_filter)(enum MipmapFilteringMode mode);
void (*set_anisotropy_level)(int);
int (*get_max_anisotropy_level)(void);
};
#endif
+7
View File
@@ -0,0 +1,7 @@
#ifndef GFX_SCREEN_CONFIG_H
#define GFX_SCREEN_CONFIG_H
#define DESIRED_SCREEN_WIDTH 640
#define DESIRED_SCREEN_HEIGHT 480
#endif
+24
View File
@@ -0,0 +1,24 @@
#ifndef GFX_SDL_H
#define GFX_SDL_H
#include "gfx_window_manager_api.h"
#ifdef __cplusplus
extern "C" {
#endif
extern struct GfxWindowManagerAPI gfx_sdl;
/* Make the window's GL context current on the calling thread (rendering
* runs on the game's scheduler thread, not the thread that created it). */
void gfx_sdl_make_context_current(void);
/* Refresh the cached drawable size after a window resize (called by the
* host thread's event pump). */
void gfx_sdl_update_cached_size(void);
#ifdef __cplusplus
}
#endif
#endif
+488
View File
@@ -0,0 +1,488 @@
#include <stdio.h>
#include <SDL.h>
#include <time.h>
#include "platform.h"
#include "system.h"
#include "gfx_window_manager_api.h"
#include "gfx_screen_config.h"
static SDL_Window* wnd;
static SDL_GLContext ctx;
static SDL_Renderer* renderer;
static int sdl_to_lus_table[512];
static bool vsync_enabled = true;
// OTRTODO: These are redundant. Info can be queried from SDL.
static int window_width = DESIRED_SCREEN_WIDTH;
static int window_height = DESIRED_SCREEN_HEIGHT;
static uint32_t fullscreen_flag = SDL_WINDOW_FULLSCREEN_DESKTOP;
static bool fullscreen_state;
static bool maximized_state;
static bool is_running = true;
static void (*on_fullscreen_changed_callback)(bool is_now_fullscreen);
// GE port: frame pacing is owned by the cooperative thread kernel's vsync
// tick (libultra.c posts one VI retrace per frame at the console rate), so
// fast3d's internal busy-wait sync is disabled to avoid double-pacing. The
// GL swap still honors display vsync (vsync_enabled below).
static int target_fps = 0;
static uint64_t previous_time;
static uint64_t qpc_freq;
#define FRAME_INTERVAL_US_NUMERATOR 1000000
#define FRAME_INTERVAL_US_DENOMINATOR (target_fps)
static int32_t gfx_sdl_get_maximized_state(void) {
return (int32_t)maximized_state;
}
static int32_t gfx_sdl_get_fullscreen_state(void) {
return (int32_t)fullscreen_state;
}
static int32_t gfx_sdl_get_fullscreen_flag_mode(void) {
return fullscreen_flag == SDL_WINDOW_FULLSCREEN_DESKTOP ? 0 : 1;
}
static void gfx_sdl_set_fullscreen_flag(int32_t mode) {
switch (mode) {
case 0: {
fullscreen_flag = SDL_WINDOW_FULLSCREEN_DESKTOP;
} break;
case 1: {
fullscreen_flag = SDL_WINDOW_FULLSCREEN;
} break;
}
}
static void set_fullscreen(bool on, bool call_callback) {
fullscreen_state = on;
SDL_SetWindowFullscreen(wnd, on ? fullscreen_flag : 0);
if (call_callback && on_fullscreen_changed_callback) {
on_fullscreen_changed_callback(on);
}
}
static void set_maximize_window(bool on) {
maximized_state = on;
if (on) {
SDL_MaximizeWindow(wnd);
} else {
SDL_RestoreWindow (wnd);
}
}
static void gfx_sdl_get_active_window_refresh_rate(uint32_t* refresh_rate) {
int display_in_use = SDL_GetWindowDisplayIndex(wnd);
SDL_DisplayMode mode;
SDL_GetCurrentDisplayMode(display_in_use, &mode);
*refresh_rate = mode.refresh_rate;
}
static void gfx_sdl_init(const struct GfxWindowInitSettings *set) {
window_width = set->width;
window_height = set->height;
#ifdef SDL_HINT_VIDEO_HIGHDPI_DISABLED
if (!set->allow_hidpi) {
// HiDPI control, if available
SDL_SetHint(SDL_HINT_VIDEO_HIGHDPI_DISABLED, "1");
#if defined(PLATFORM_WIN32) && defined(SDL_HINT_WINDOWS_DPI_AWARENESS)
// if HiDPI is disabled, declare ourselves DPI aware to get 1:1 window size on Windows
SDL_SetHint(SDL_HINT_WINDOWS_DPI_AWARENESS, "permonitor");
#endif
}
#endif
if (SDL_Init(SDL_INIT_VIDEO) != 0) {
sysFatalError("Could not init SDL:\n%s", SDL_GetError());
}
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
if (sysArgCheck("--debug-gl")) {
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG);
}
int posX = set->x;
int posY = set->y;
int display_in_use = SDL_GetWindowDisplayIndex(wnd);
if (display_in_use < 0) { // Fallback to default if out of bounds
posX = SDL_WINDOWPOS_UNDEFINED;
posY = SDL_WINDOWPOS_UNDEFINED;
}
if (set->centered) {
SDL_DisplayMode mode = {};
SDL_GetCurrentDisplayMode(0, &mode);
posX = mode.w / 2 - window_width / 2;
posY = mode.h / 2 - window_height / 2;
}
if (set->fullscreen_is_exclusive) {
fullscreen_flag = SDL_WINDOW_FULLSCREEN;
}
// we will unhide the window once the GL context is successfully created
Uint32 flags = SDL_WINDOW_HIDDEN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_OPENGL;
// if fullscreen was requested, start the window in fullscreen right away
if (set->fullscreen) {
flags |= fullscreen_flag;
fullscreen_state = true;
}
if (set->maximized) {
flags |= SDL_WINDOW_MAXIMIZED;
maximized_state = true;
}
#ifdef SDL_WINDOW_ALLOW_HIGHDPI
if (set->allow_hidpi) {
flags |= SDL_WINDOW_ALLOW_HIGHDPI;
}
#endif
// ideally we need 3.0 compat
// if that doesn't work, try 3.2 core in case we're on mac, 2.1 compat as a last resort
static u32 glver[][3] = {
{ 0, 0, 0 }, // for command line override
{ 3, 0, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY }, // 3.0: default, has all the features required
{ 4, 1, SDL_GL_CONTEXT_PROFILE_CORE }, // 4.1core: macs only have core profile and this is the latest
{ 3, 2, SDL_GL_CONTEXT_PROFILE_CORE }, // 3.2core: older macs will only have this at best
{ 3, 0, SDL_GL_CONTEXT_PROFILE_ES }, // es3: don't really support ES properly, but we can try
{ 2, 1, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY }, // 2.1: absolute last resort, will still require GLSL130 as an extension
};
u32 verstart = 1;
const u32 verend = sizeof(glver) / sizeof(*glver);
const char *verstr = sysArgGetString("--gl-version");
if (verstr && *verstr) {
// user override
glver[0][2] = strstr(verstr, "core") ? SDL_GL_CONTEXT_PROFILE_CORE :
(strstr(verstr, "es") ? SDL_GL_CONTEXT_PROFILE_ES :
SDL_GL_CONTEXT_PROFILE_COMPATIBILITY);
sscanf(verstr, "%d.%d", &glver[0][0], &glver[0][1]);
if (glver[0][0] >= 1 && glver[0][0] <= 4 && glver[0][1] < 9) {
verstart = 0;
}
}
ctx = NULL;
u32 vmin = 0, vmaj = 0, vprof = SDL_GL_CONTEXT_PROFILE_COMPATIBILITY;
const char *vprofstr = "";
for (u32 i = verstart; i < verend && !ctx; ++i) {
vmaj = glver[i][0];
vmin = glver[i][1];
vprof = glver[i][2];
vprofstr = (vprof == SDL_GL_CONTEXT_PROFILE_CORE ? "core" :
(vprof == SDL_GL_CONTEXT_PROFILE_ES ? "es" : ""));
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, vmaj);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, vmin);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, vprof);
wnd = SDL_CreateWindow(set->title, posX, posY, window_width, window_height, flags);
if (!wnd) {
sysLogPrintf(LOG_WARNING, "SDL: could not open SDL window for GL%d.%d%s:\n%s", vmaj, vmin, vprofstr, SDL_GetError());
continue;
}
ctx = SDL_GL_CreateContext(wnd);
if (!ctx) {
sysLogPrintf(LOG_WARNING, "SDL: could not create GL%d.%d%s context: %s", vmaj, vmin, vprofstr, SDL_GetError());
SDL_DestroyWindow(wnd);
wnd = nullptr;
}
}
if (!wnd || !ctx) {
sysFatalError("Could not open SDL window with an OpenGL context of any supported version:\n%s", SDL_GetError());
} else {
sysLogPrintf(LOG_NOTE, "SDL: created GL%d.%d%s context", vmaj, vmin, vprofstr);
}
SDL_GL_MakeCurrent(wnd, ctx);
SDL_GL_SetSwapInterval(1);
SDL_ShowWindow(wnd);
qpc_freq = SDL_GetPerformanceFrequency();
}
static void gfx_sdl_close(void) {
is_running = false;
}
static void gfx_sdl_set_fullscreen_changed_callback(void (*on_fullscreen_changed)(bool is_now_fullscreen)) {
on_fullscreen_changed_callback = on_fullscreen_changed;
}
static void gfx_sdl_set_fullscreen(bool enable) {
set_fullscreen(enable, true);
}
static void gfx_sdl_set_fullscreen_exclusive(bool enable) {
const uint32_t newflag = enable ? SDL_WINDOW_FULLSCREEN : SDL_WINDOW_FULLSCREEN_DESKTOP;
if (fullscreen_flag != newflag) {
fullscreen_flag = newflag;
// reset fullscreen to take new value into account if it already is in fullscreen
if (fullscreen_state) {
fullscreen_state = false;
set_fullscreen(enable, true);
}
}
}
static void gfx_sdl_set_maximize_window(bool enable) {
set_maximize_window(enable);
}
static void gfx_sdl_set_cursor_visibility(bool visible) {
if (visible) {
SDL_ShowCursor(SDL_ENABLE);
} else {
SDL_ShowCursor(SDL_DISABLE);
}
}
static void get_centered_positions_native(int32_t width, int32_t height, int32_t *posX, int32_t *posY) {
const int disp_idx = SDL_GetWindowDisplayIndex(wnd);
SDL_DisplayMode mode = {};
SDL_GetDesktopDisplayMode(disp_idx, &mode);
*posX = mode.w / 2 - width / 2;
*posY = mode.h / 2 - height / 2;
}
static void gfx_sdl_get_centered_positions(int32_t width, int32_t height, int32_t *posX, int32_t *posY) {
const int disp_idx = SDL_GetWindowDisplayIndex(wnd);
SDL_DisplayMode mode = {};
SDL_GetCurrentDisplayMode(disp_idx, &mode);
*posX = mode.w / 2 - width / 2;
*posY = mode.h / 2 - height / 2;
}
static void gfx_sdl_set_closest_resolution(int32_t width, int32_t height, bool should_center) {
const SDL_DisplayMode mode = {.w = width, .h = height};
const int disp_idx = SDL_GetWindowDisplayIndex(wnd);
SDL_DisplayMode closest = {};
if (SDL_GetClosestDisplayMode(disp_idx, &mode, &closest)) {
SDL_SetWindowDisplayMode(wnd, &closest);
SDL_SetWindowSize(wnd, closest.w, closest.h);
if (should_center) {
int32_t posX = 0;
int32_t posY = 0;
get_centered_positions_native(closest.w, closest.h, &posX, &posY);
SDL_SetWindowPosition(wnd, posX, posY);
}
}
}
static void gfx_sdl_set_dimensions(uint32_t width, uint32_t height, int32_t posX, int32_t posY) {
SDL_SetWindowSize(wnd, width, height);
SDL_SetWindowPosition(wnd, posX, posY);
}
static void gfx_sdl_get_dimensions(uint32_t* width, uint32_t* height, int32_t* posX, int32_t* posY) {
SDL_GL_GetDrawableSize(wnd, static_cast<int*>((void*)width), static_cast<int*>((void*)height));
SDL_GetWindowPosition(wnd, static_cast<int*>(posX), static_cast<int*>(posY));
}
static void gfx_sdl_handle_events(void) {
SDL_Event event;
while (SDL_PollEvent(&event)) {
switch (event.type) {
case SDL_KEYDOWN:
if (event.key.keysym.sym == SDLK_RETURN && (event.key.keysym.mod & KMOD_ALT)) {
// alt-enter received, switch fullscreen state
set_fullscreen(!fullscreen_state, true);
} else if (event.key.keysym.sym == SDLK_ESCAPE) {
// GE's own menus use the keyboard; ESC on an empty event
// queue is our quit key (matches the Phase 1 demo loop).
exit(0);
}
break;
case SDL_WINDOWEVENT:
if (event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) {
SDL_GL_GetDrawableSize(wnd, &window_width, &window_height);
if (!fullscreen_state) {
maximized_state = SDL_GetWindowFlags(wnd) & SDL_WINDOW_MAXIMIZED ? true : false;
}
} else if (event.window.event == SDL_WINDOWEVENT_CLOSE &&
event.window.windowID == SDL_GetWindowID(wnd)) {
// We listen specifically for main window close because closing main window
// on macOS does not trigger SDL_Quit.
exit(0);
}
break;
case SDL_QUIT:
exit(0);
break;
}
}
}
/* The window/context are created on the host main thread, but the game's
* scheduler thread (shedThread) does all rendering. Make the context current
* there before each frame. extern "C": called from C (port/src/video.c). */
extern "C" void gfx_sdl_make_context_current(void) {
if (wnd && ctx) {
SDL_GL_MakeCurrent(wnd, ctx);
}
}
/* The host thread owns SDL event pumping (see videoPumpEvents); when it sees
* a window resize it calls this to refresh the cached drawable size that
* fast3d uses for scaling. */
extern "C" void gfx_sdl_update_cached_size(void) {
if (wnd) {
SDL_GL_GetDrawableSize(wnd, &window_width, &window_height);
}
}
static bool gfx_sdl_start_frame(void) {
return true;
}
static uint64_t qpc_to_100ns(uint64_t qpc) {
return qpc / qpc_freq * 10000000 + qpc % qpc_freq * 10000000 / qpc_freq;
}
static inline void sync_framerate_with_timer(void) {
uint64_t t;
t = qpc_to_100ns(SDL_GetPerformanceCounter());
const int64_t next = previous_time + 10 * FRAME_INTERVAL_US_NUMERATOR / FRAME_INTERVAL_US_DENOMINATOR;
int64_t left = next - t;
// We want to exit a bit early, so we can busy-wait the rest to never miss the deadline
left -= 15000UL;
if (left > 0) {
sysSleep(left);
}
do {
sysCpuRelax();
t = qpc_to_100ns(SDL_GetPerformanceCounter());
} while ((int64_t)t < next);
t = qpc_to_100ns(SDL_GetPerformanceCounter());
if (left > 0 && t - next < 10000) {
// In case it takes some time for the application to wake up after sleep,
// or inaccurate timer,
// don't let that slow down the framerate.
t = next;
}
previous_time = t;
}
static void gfx_sdl_swap_buffers_begin(void) {
if (target_fps) {
sync_framerate_with_timer();
}
SDL_GL_SwapWindow(wnd);
}
static void gfx_sdl_swap_buffers_end(void) {
}
static double gfx_sdl_get_time(void) {
return SDL_GetPerformanceCounter() / (double)qpc_freq;
}
static int32_t gfx_sdl_get_target_fps(void) {
return target_fps;
}
static void gfx_sdl_set_target_fps(int fps) {
target_fps = fps;
}
static bool gfx_sdl_can_disable_vsync(void) {
return true;
}
static void *gfx_sdl_get_window_handle(void) {
return (void *)wnd;
}
static void gfx_sdl_set_window_title(const char *title) {
SDL_SetWindowTitle(wnd, title);
}
static int gfx_sdl_get_swap_interval(void) {
return SDL_GL_GetSwapInterval();
}
static bool gfx_sdl_set_swap_interval(int interval) {
const bool success = SDL_GL_SetSwapInterval(interval) >= 0;
vsync_enabled = success && (interval != 0);
if (!success) {
sysLogPrintf(LOG_WARNING, "SDL: failed to set vsync %d: %s", interval, SDL_GetError());
}
return success;
}
int gfx_sdl_get_display_mode(int modenum, int *out_w, int *out_h) {
const int display_in_use = SDL_GetWindowDisplayIndex(wnd);
SDL_DisplayMode sdlmode;
if (SDL_GetDisplayMode(display_in_use, modenum, &sdlmode) == 0) {
*out_w = sdlmode.w;
*out_h = sdlmode.h;
return 1;
}
return 0;
}
int gfx_sdl_get_current_display_mode(int *out_w, int *out_h) {
const int display_in_use = SDL_GetWindowDisplayIndex(wnd);
SDL_DisplayMode sdlmode;
if (SDL_GetCurrentDisplayMode(display_in_use, &sdlmode) == 0) {
*out_w = sdlmode.w;
*out_h = sdlmode.h;
return 1;
}
return 0;
}
int gfx_sdl_get_num_display_modes(void) {
const int display_in_use = SDL_GetWindowDisplayIndex(wnd);
return SDL_GetNumDisplayModes(display_in_use);
}
struct GfxWindowManagerAPI gfx_sdl = {
gfx_sdl_init,
gfx_sdl_close,
gfx_sdl_get_display_mode,
gfx_sdl_get_current_display_mode,
gfx_sdl_get_num_display_modes,
gfx_sdl_get_fullscreen_state,
gfx_sdl_set_fullscreen_changed_callback,
gfx_sdl_set_fullscreen,
gfx_sdl_set_fullscreen_exclusive,
gfx_sdl_set_fullscreen_flag,
gfx_sdl_get_fullscreen_flag_mode,
gfx_sdl_get_maximized_state,
gfx_sdl_set_maximize_window,
gfx_sdl_get_active_window_refresh_rate,
gfx_sdl_set_cursor_visibility,
gfx_sdl_set_closest_resolution,
gfx_sdl_set_dimensions,
gfx_sdl_get_dimensions,
gfx_sdl_get_centered_positions,
gfx_sdl_handle_events,
gfx_sdl_start_frame,
gfx_sdl_swap_buffers_begin,
gfx_sdl_swap_buffers_end,
gfx_sdl_get_time,
gfx_sdl_get_target_fps,
gfx_sdl_set_target_fps,
gfx_sdl_can_disable_vsync,
gfx_sdl_get_window_handle,
gfx_sdl_set_window_title,
gfx_sdl_get_swap_interval,
gfx_sdl_set_swap_interval,
};
+54
View File
@@ -0,0 +1,54 @@
#ifndef GFX_WINDOW_MANAGER_API_H
#define GFX_WINDOW_MANAGER_API_H
#include <stdint.h>
#include <stdbool.h>
struct GfxWindowInitSettings {
const char *title;
uint32_t width;
uint32_t height;
int32_t x;
int32_t y;
bool fullscreen;
bool fullscreen_is_exclusive;
bool maximized;
bool centered;
bool allow_hidpi;
};
struct GfxWindowManagerAPI {
void (*init)(const struct GfxWindowInitSettings *settings);
void (*close)(void);
int (*get_display_mode)(int modenum, int *out_w, int *out_h);
int (*get_current_display_mode)(int *out_w, int *out_h);
int (*get_num_display_modes)(void);
int32_t (*get_fullscreen_state)(void);
void (*set_fullscreen_changed_callback)(void (*on_fullscreen_changed)(bool is_now_fullscreen));
void (*set_fullscreen)(bool enable);
void (*set_fullscreen_exclusive)(bool exc);
void (*set_fullscreen_flag)(int32_t mode);
int32_t (*get_fullscreen_flag_mode)(void);
int32_t (*get_maximized_state)(void);
void (*set_maximize)(bool enable);
void (*get_active_window_refresh_rate)(uint32_t* refresh_rate);
void (*set_cursor_visibility)(bool visible);
void (*set_closest_resolution)(int32_t width, int32_t height, bool should_center);
void (*set_dimensions)(uint32_t width, uint32_t height, int32_t posX, int32_t posY);
void (*get_dimensions)(uint32_t* width, uint32_t* height, int32_t* posX, int32_t* posY);
void (*get_centered_positions)(int32_t width, int32_t height, int32_t *posX, int32_t *posY);
void (*handle_events)(void);
bool (*start_frame)(void);
void (*swap_buffers_begin)(void);
void (*swap_buffers_end)(void);
double (*get_time)(void); // For debug
int32_t (*get_target_fps)(void);
void (*set_target_fps)(int fps);
bool (*can_disable_vsync)(void);
void *(*get_window_handle)(void);
void (*set_window_title)(const char *);
int (*get_swap_interval)(void);
bool (*set_swap_interval)(int);
};
#endif
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+311
View File
@@ -0,0 +1,311 @@
#ifndef __khrplatform_h_
#define __khrplatform_h_
/*
** Copyright (c) 2008-2018 The Khronos Group Inc.
**
** Permission is hereby granted, free of charge, to any person obtaining a
** copy of this software and/or associated documentation files (the
** "Materials"), to deal in the Materials without restriction, including
** without limitation the rights to use, copy, modify, merge, publish,
** distribute, sublicense, and/or sell copies of the Materials, and to
** permit persons to whom the Materials are furnished to do so, subject to
** the following conditions:
**
** The above copyright notice and this permission notice shall be included
** in all copies or substantial portions of the Materials.
**
** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
*/
/* Khronos platform-specific types and definitions.
*
* The master copy of khrplatform.h is maintained in the Khronos EGL
* Registry repository at https://github.com/KhronosGroup/EGL-Registry
* The last semantic modification to khrplatform.h was at commit ID:
* 67a3e0864c2d75ea5287b9f3d2eb74a745936692
*
* Adopters may modify this file to suit their platform. Adopters are
* encouraged to submit platform specific modifications to the Khronos
* group so that they can be included in future versions of this file.
* Please submit changes by filing pull requests or issues on
* the EGL Registry repository linked above.
*
*
* See the Implementer's Guidelines for information about where this file
* should be located on your system and for more details of its use:
* http://www.khronos.org/registry/implementers_guide.pdf
*
* This file should be included as
* #include <KHR/khrplatform.h>
* by Khronos client API header files that use its types and defines.
*
* The types in khrplatform.h should only be used to define API-specific types.
*
* Types defined in khrplatform.h:
* khronos_int8_t signed 8 bit
* khronos_uint8_t unsigned 8 bit
* khronos_int16_t signed 16 bit
* khronos_uint16_t unsigned 16 bit
* khronos_int32_t signed 32 bit
* khronos_uint32_t unsigned 32 bit
* khronos_int64_t signed 64 bit
* khronos_uint64_t unsigned 64 bit
* khronos_intptr_t signed same number of bits as a pointer
* khronos_uintptr_t unsigned same number of bits as a pointer
* khronos_ssize_t signed size
* khronos_usize_t unsigned size
* khronos_float_t signed 32 bit floating point
* khronos_time_ns_t unsigned 64 bit time in nanoseconds
* khronos_utime_nanoseconds_t unsigned time interval or absolute time in
* nanoseconds
* khronos_stime_nanoseconds_t signed time interval in nanoseconds
* khronos_boolean_enum_t enumerated boolean type. This should
* only be used as a base type when a client API's boolean type is
* an enum. Client APIs which use an integer or other type for
* booleans cannot use this as the base type for their boolean.
*
* Tokens defined in khrplatform.h:
*
* KHRONOS_FALSE, KHRONOS_TRUE Enumerated boolean false/true values.
*
* KHRONOS_SUPPORT_INT64 is 1 if 64 bit integers are supported; otherwise 0.
* KHRONOS_SUPPORT_FLOAT is 1 if floats are supported; otherwise 0.
*
* Calling convention macros defined in this file:
* KHRONOS_APICALL
* KHRONOS_APIENTRY
* KHRONOS_APIATTRIBUTES
*
* These may be used in function prototypes as:
*
* KHRONOS_APICALL void KHRONOS_APIENTRY funcname(
* int arg1,
* int arg2) KHRONOS_APIATTRIBUTES;
*/
#if defined(__SCITECH_SNAP__) && !defined(KHRONOS_STATIC)
# define KHRONOS_STATIC 1
#endif
/*-------------------------------------------------------------------------
* Definition of KHRONOS_APICALL
*-------------------------------------------------------------------------
* This precedes the return type of the function in the function prototype.
*/
#if defined(KHRONOS_STATIC)
/* If the preprocessor constant KHRONOS_STATIC is defined, make the
* header compatible with static linking. */
# define KHRONOS_APICALL
#elif defined(_WIN32)
# define KHRONOS_APICALL __declspec(dllimport)
#elif defined (__SYMBIAN32__)
# define KHRONOS_APICALL IMPORT_C
#elif defined(__ANDROID__)
# define KHRONOS_APICALL __attribute__((visibility("default")))
#else
# define KHRONOS_APICALL
#endif
/*-------------------------------------------------------------------------
* Definition of KHRONOS_APIENTRY
*-------------------------------------------------------------------------
* This follows the return type of the function and precedes the function
* name in the function prototype.
*/
#if defined(_WIN32) && !defined(_WIN32_WCE) && !defined(__SCITECH_SNAP__)
/* Win32 but not WinCE */
# define KHRONOS_APIENTRY __stdcall
#else
# define KHRONOS_APIENTRY
#endif
/*-------------------------------------------------------------------------
* Definition of KHRONOS_APIATTRIBUTES
*-------------------------------------------------------------------------
* This follows the closing parenthesis of the function prototype arguments.
*/
#if defined (__ARMCC_2__)
#define KHRONOS_APIATTRIBUTES __softfp
#else
#define KHRONOS_APIATTRIBUTES
#endif
/*-------------------------------------------------------------------------
* basic type definitions
*-----------------------------------------------------------------------*/
#if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__GNUC__) || defined(__SCO__) || defined(__USLC__)
/*
* Using <stdint.h>
*/
#include <stdint.h>
typedef int32_t khronos_int32_t;
typedef uint32_t khronos_uint32_t;
typedef int64_t khronos_int64_t;
typedef uint64_t khronos_uint64_t;
#define KHRONOS_SUPPORT_INT64 1
#define KHRONOS_SUPPORT_FLOAT 1
/*
* To support platform where unsigned long cannot be used interchangeably with
* inptr_t (e.g. CHERI-extended ISAs), we can use the stdint.h intptr_t.
* Ideally, we could just use (u)intptr_t everywhere, but this could result in
* ABI breakage if khronos_uintptr_t is changed from unsigned long to
* unsigned long long or similar (this results in different C++ name mangling).
* To avoid changes for existing platforms, we restrict usage of intptr_t to
* platforms where the size of a pointer is larger than the size of long.
*/
#if defined(__SIZEOF_LONG__) && defined(__SIZEOF_POINTER__)
#if __SIZEOF_POINTER__ > __SIZEOF_LONG__
#define KHRONOS_USE_INTPTR_T
#endif
#endif
#elif defined(__VMS ) || defined(__sgi)
/*
* Using <inttypes.h>
*/
#include <inttypes.h>
typedef int32_t khronos_int32_t;
typedef uint32_t khronos_uint32_t;
typedef int64_t khronos_int64_t;
typedef uint64_t khronos_uint64_t;
#define KHRONOS_SUPPORT_INT64 1
#define KHRONOS_SUPPORT_FLOAT 1
#elif defined(_WIN32) && !defined(__SCITECH_SNAP__)
/*
* Win32
*/
typedef __int32 khronos_int32_t;
typedef unsigned __int32 khronos_uint32_t;
typedef __int64 khronos_int64_t;
typedef unsigned __int64 khronos_uint64_t;
#define KHRONOS_SUPPORT_INT64 1
#define KHRONOS_SUPPORT_FLOAT 1
#elif defined(__sun__) || defined(__digital__)
/*
* Sun or Digital
*/
typedef int khronos_int32_t;
typedef unsigned int khronos_uint32_t;
#if defined(__arch64__) || defined(_LP64)
typedef long int khronos_int64_t;
typedef unsigned long int khronos_uint64_t;
#else
typedef long long int khronos_int64_t;
typedef unsigned long long int khronos_uint64_t;
#endif /* __arch64__ */
#define KHRONOS_SUPPORT_INT64 1
#define KHRONOS_SUPPORT_FLOAT 1
#elif 0
/*
* Hypothetical platform with no float or int64 support
*/
typedef int khronos_int32_t;
typedef unsigned int khronos_uint32_t;
#define KHRONOS_SUPPORT_INT64 0
#define KHRONOS_SUPPORT_FLOAT 0
#else
/*
* Generic fallback
*/
#include <stdint.h>
typedef int32_t khronos_int32_t;
typedef uint32_t khronos_uint32_t;
typedef int64_t khronos_int64_t;
typedef uint64_t khronos_uint64_t;
#define KHRONOS_SUPPORT_INT64 1
#define KHRONOS_SUPPORT_FLOAT 1
#endif
/*
* Types that are (so far) the same on all platforms
*/
typedef signed char khronos_int8_t;
typedef unsigned char khronos_uint8_t;
typedef signed short int khronos_int16_t;
typedef unsigned short int khronos_uint16_t;
/*
* Types that differ between LLP64 and LP64 architectures - in LLP64,
* pointers are 64 bits, but 'long' is still 32 bits. Win64 appears
* to be the only LLP64 architecture in current use.
*/
#ifdef KHRONOS_USE_INTPTR_T
typedef intptr_t khronos_intptr_t;
typedef uintptr_t khronos_uintptr_t;
#elif defined(_WIN64)
typedef signed long long int khronos_intptr_t;
typedef unsigned long long int khronos_uintptr_t;
#else
typedef signed long int khronos_intptr_t;
typedef unsigned long int khronos_uintptr_t;
#endif
#if defined(_WIN64)
typedef signed long long int khronos_ssize_t;
typedef unsigned long long int khronos_usize_t;
#else
typedef signed long int khronos_ssize_t;
typedef unsigned long int khronos_usize_t;
#endif
#if KHRONOS_SUPPORT_FLOAT
/*
* Float type
*/
typedef float khronos_float_t;
#endif
#if KHRONOS_SUPPORT_INT64
/* Time types
*
* These types can be used to represent a time interval in nanoseconds or
* an absolute Unadjusted System Time. Unadjusted System Time is the number
* of nanoseconds since some arbitrary system event (e.g. since the last
* time the system booted). The Unadjusted System Time is an unsigned
* 64 bit value that wraps back to 0 every 584 years. Time intervals
* may be either signed or unsigned.
*/
typedef khronos_uint64_t khronos_utime_nanoseconds_t;
typedef khronos_int64_t khronos_stime_nanoseconds_t;
#endif
/*
* Dummy value used to pad enum types to 32 bits.
*/
#ifndef KHRONOS_MAX_ENUM
#define KHRONOS_MAX_ENUM 0x7FFFFFFF
#endif
/*
* Enumerated boolean type
*
* Values other than zero should be considered to be true. Therefore
* comparisons should not be made against KHRONOS_TRUE.
*/
typedef enum {
KHRONOS_FALSE = 0,
KHRONOS_TRUE = 1,
KHRONOS_BOOLEAN_ENUM_FORCE_SIZE = KHRONOS_MAX_ENUM
} khronos_boolean_enum_t;
#endif /* __khrplatform_h_ */
+26
View File
@@ -0,0 +1,26 @@
#ifndef PORT_CRASH_H
#define PORT_CRASH_H
/*
* Crash handler: installs an unhandled-exception filter (Windows) or signal
* handlers (POSIX) that print a symbolicated backtrace and write
* ge007.crash.log, then abort. Call crashInit() before running game code.
*/
#ifdef __cplusplus
extern "C" {
#endif
void crashInit(void);
void crashShutdown(void);
/* Unwind every thread in the process and log a short symbolicated
* backtrace for each (labelled via tids/names when known). For diagnosing
* hangs: call from the kernel heartbeat. */
void crashDumpThreads(const unsigned long *tids, const char **names, int count);
#ifdef __cplusplus
}
#endif
#endif /* PORT_CRASH_H */
+13
View File
@@ -0,0 +1,13 @@
#ifndef PORT_DRAM_H
#define PORT_DRAM_H
/*
* Reserve the N64-DRAM region: one 8 MB backing store mapped at BOTH
* 0x70000000 (s32-safe "virtual" view, where game RAM symbols live) and
* 0x80000000 (KSEG0 mirror). See port/src/dram.c. Must be called before any
* game thread runs; the absolute symbols in port/src/dram_syms.s point into
* the V1 view.
*/
void *dramReserve(void);
#endif /* PORT_DRAM_H */
+8
View File
@@ -0,0 +1,8 @@
#ifndef PORT_HOSTSTDDDEF_H
#define PORT_HOSTSTDDDEF_H
/* Generated by CMake from hoststddef.h.in — do not edit.
* Includes the HOST <stddef.h> by absolute path so that port/shim/stddef.h
* can bypass the N64 include/stddef.h (an empty stub whose guard otherwise
* prevents the host header's max_align_t/size_t from reaching C++ TUs). */
#include "@PORT_HOST_STDDDEF_H@"
#endif
+8
View File
@@ -0,0 +1,8 @@
#ifndef PORT_HOSTSTDLIB_H
#define PORT_HOSTSTDLIB_H
/* Generated by CMake from hoststdlib.h.in — do not edit.
* Includes the HOST <stdlib.h> by absolute path so that port/shim/stdlib.h
* can bypass the N64 include/stdlib.h (whose lldiv_t/ldiv_t typedefs clash
* with the host's in C++ TUs). See docs/PCPortResearch.md §11. */
#include "@PORT_HOST_STDLIB_H@"
#endif
+9
View File
@@ -31,6 +31,14 @@
#define PLATFORM_64BIT 1
#endif
#if defined(_M_IX86) || defined(__i386__)
#define PLATFORM_X86 1
#elif defined(_M_X64) || defined(__x86_64__)
#define PLATFORM_X86_64 1
#elif defined(__aarch64__)
#define PLATFORM_ARM 1
#endif
/* Constructor attribute: run before main(). */
#if defined(_MSC_VER)
#define PD_CONSTRUCTOR
@@ -44,6 +52,7 @@
enum LogLevel {
LOG_ERROR = 0,
LOG_WARNING,
LOG_NOTE,
LOG_INFO,
LOG_DEBUG,
};
+34
View File
@@ -17,6 +17,12 @@ extern "C" {
/* Monotonic microseconds since an arbitrary origin. */
uint64_t sysGetMicroseconds(void);
/*
* Base address the executable was loaded at (for turning runtime addresses
* into file offsets for addr2line; ASLR moves the image on Windows).
*/
uintptr_t sysImageBase(void);
/* Wall-clock seconds since the epoch (for timestamps). */
int64_t sysGetTime(void);
/* Sleep for the given number of microseconds. */
@@ -27,6 +33,13 @@ void sysSleep(uint32_t micros);
void sysLogPrintf(enum LogLevel level, const char *fmt, ...)
__attribute__((format(printf, 2, 3)));
/*
* Print a fatal error message and terminate the process. Used by fast3d for
* unrecoverable conditions (bad Gfx command, OOM in the texture cache, ...).
*/
void sysFatalError(const char *fmt, ...)
__attribute__((format(printf, 1, 2))) __attribute__((noreturn));
/* --- Paths -------------------------------------------------------------- */
/*
@@ -38,10 +51,31 @@ const char *sysResolvePath(const char *path);
/* Return the directory containing the executable. */
const char *sysGetExeDir(void);
/* --- Command line ------------------------------------------------------- */
/* Store the host argv (call once from main). */
void sysSetArgs(int argc, char **argv);
/* Nonzero if the given argument (e.g. "--debug-gl") was passed. */
int sysArgCheck(const char *arg);
/* Value following the given argument on the command line, or NULL. */
const char *sysArgGetString(const char *arg);
/* --- CPU ---------------------------------------------------------------- */
/* Briefly yield/relax the CPU (busy-wait hint). Used by fast3d's frame sync. */
void sysCpuRelax(void);
/* --- Lifecycle ---------------------------------------------------------- */
void sysExit(int code);
/*
* Start the cooperative thread kernel (green threads + vsync tick). Call
* once from main() before running any game code; after this, blocking
* osRecvMesg() calls in the game suspend/resume threads.
*/
void portKernelInit(void);
#ifdef __cplusplus
}
#endif
+22 -17
View File
@@ -2,14 +2,16 @@
#define PORT_VIDEO_H
/*
* Video: SDL2 window + OpenGL context + frame pacing.
* Modelled on the PD port's port/include/video.h.
* Video: SDL2 window + OpenGL context + frame pacing, on top of fast3d's
* window-manager / rendering APIs (port/fast3d).
*
* The game's VI (osViSetMode / osViVSyncCallback / osViWaitVSync) is mapped
* onto this layer. The software RSP (fast3d) renders into the GL context that
* this layer owns.
* The game's VI (osViSetMode / osViSwapBuffer / ...) is mapped onto this
* layer by the libultra shims; the software RSP (fast3d) renders into the GL
* context owned here.
*/
#include <SDL.h>
#include <PR/ultratypes.h>
#include <PR/gbi.h>
@@ -21,24 +23,27 @@ extern "C" {
int videoInit(void);
void videoDestroy(void);
/* Frame boundary hooks, driven by the port scheduler (gesched.c). */
/* Frame boundary hooks, driven by the SP task shim (libultra.c). These run
* on the game's scheduler thread. */
void videoStartFrame(void);
void videoSubmitCommands(Gfx *cmds); /* runs the software RSP on the list */
void videoEndFrame(void);
/* Pump window events; returns 1 when the app should quit. Used by the
* Phase 1 demo loop until the scheduler owns the frame boundary. */
int videoHandleEvents(void);
/* Host-thread SDL event pump: keeps the window responsive (Windows only
* dispatches messages to the creating thread) and handles quit. Called in a
* loop from main(). Exits the process on QUIT/ESC/close. */
void videoPumpEvents(void);
/* VSync / framerate control. */
void videoSetVsync(int enabled);
void videoSetFramerateLimit(int fps); /* 0 = unlimited */
/* The game's native video mode (NTSC 640x480, PAL 640x400). fast3d scales
* N64 screen coordinates into window pixels using this. */
void videoUpdateNativeResolution(s32 w, s32 h);
s32 videoGetNativeWidth(void);
s32 videoGetNativeHeight(void);
/* Window state queries (kept in sync with the options menu). */
int videoGetFullscreen(void);
void videoSetFullscreen(int enabled);
int videoGetMaximize(void);
void videoSetMaximize(int enabled);
/* Offscreen framebuffers (fast3d GL FBOs) + texture cache control. */
s32 videoCreateFramebuffer(u32 w, u32 h, s32 upscale, s32 autoresize);
void videoCopyFramebuffer(s32 dst, s32 src, s32 left, s32 top);
void videoResetTextureCache(void);
/* Current FPS (measured). */
float videoGetFPS(void);
+31
View File
@@ -0,0 +1,31 @@
/*
* PC port shim for PR/R4300.h (see docs/PCPortResearch.md).
*
* On the N64, PHYS_TO_K0 turns a physical address into a KSEG0 virtual
* address by OR'ing in 0x80000000. On the PC port the game's working RAM
* lives in the s32-safe DRAM view at 0x70000000 (see port/src/dram.c):
* addresses with bit 31 set would sign-extend to invalid pointers when they
* pass through s32 parameters (e.g. mempCheckMemflagTokens in src/memp.c).
* The KSEG0 view at 0x80000000 still exists as a byte-identical mirror for
* code that rebuilds pointers with `offset | 0x80000000`, and "physical"
* addresses are offsets from the V1 base (see the PR/os.h shim).
*
* So PHYS_TO_K0 is the identity here: its argument is already a live host
* pointer in the V1 view.
*
* Inert in the N64 build (no -DPORT): pure pass-through.
*/
#ifndef _PORT_SHIM_R4300_H_
#define _PORT_SHIM_R4300_H_
#if defined(PORT)
# include "include/PR/R4300.h"
# undef PHYS_TO_K0
# define PHYS_TO_K0(x) ((u32)(x)) /* identity: V1 view is already virtual */
#else
# include <PR/R4300.h>
#endif
#endif /* _PORT_SHIM_R4300_H_ */
+43
View File
@@ -0,0 +1,43 @@
/*
* PC port shim for PR/os.h — DRAM address conversions (see docs/PCPortResearch.md).
*
* The game's working RAM lives in the s32-safe DRAM view at 0x70000000
* ("V1"), with a byte-identical KSEG0 mirror at 0x80000000 ("V2"); see
* port/src/dram.c. Addresses with bit 31 set sign-extend to invalid
* pointers through s32 parameters (src/memp.c), so live pointers must stay
* in V1, while `offset | 0x80000000` rebuilds (src/game/bg.c) must land in
* V2. The two views share one backing store, so both are the same data.
*
* "Physical" addresses in GBI words and ROM data are therefore OFFSETS FROM
* THE V1 BASE, and the conversions become:
*
* OS_K0_TO_PHYSICAL(x) = (u32)((char *)x - 0x70000000)
* small offset P; fast3d's seg_addr() resolves it with +0x80000000,
* landing in V2. `P | 0x80000000` (bg.c et al.) also lands in V2.
* OS_PHYSICAL_TO_K0(x) = x (identity)
* Callers pass EITHER live V1 pointers (bondview2.c, front.c) — which
* then go into GBI words as full addresses that fast3d passes through
* — OR small physical offsets, which fast3d remaps to V2. Both forms
* resolve correctly.
* osVirtualToPhysical / osPhysicalToVirtual stay the identity (port/src/
* libultra.c): V1 addresses are already live host pointers < 4 GB.
*
* Inert in the N64 build (no -DPORT): pure pass-through.
*/
#ifndef _PORT_SHIM_OS_H_
#define _PORT_SHIM_OS_H_
#if defined(PORT)
# include "include/PR/os.h"
# undef OS_K0_TO_PHYSICAL
# define OS_K0_TO_PHYSICAL(x) ((u32)((char *)(x) - 0x70000000))
# undef OS_PHYSICAL_TO_K0
# define OS_PHYSICAL_TO_K0(x) ((void *)(x))
#else
# include <PR/os.h>
#endif
#endif /* _PORT_SHIM_OS_H_ */
+19
View File
@@ -0,0 +1,19 @@
/*
* PC port shim for <stdarg.h>.
*
* The N64 decomp's include/stdarg.h does `#include <ultra64.h>` (the console
* dev-kit convenience: one header pulls in all of libultra). On the PC that
* means any HOST header that internally includes <stdarg.h> (SDL_stdinc.h,
* parts of MinGW's own headers, ...) drags PR/os.h into the middle of host
* header parsing. If MinGW's <errno.h> has already run (it defines the
* `errno` macro), PR/os.h's controller structs — which have fields literally
* named `errno` — fail to parse ("field '_errno' declared as a function").
*
* Parse the N64 header with the errno macro suppressed; restore it after.
* Inert in the N64 build (port/shim is not on its include path).
*/
#pragma push_macro("errno")
#undef errno
#include "include/stdarg.h"
#pragma pop_macro("errno")
+30
View File
@@ -0,0 +1,30 @@
/*
* PC port shim for <stddef.h>.
*
* The N64 decomp ships an essentially empty include/stddef.h (just its
* `_STDDEF_H_` guard). In C game code that is harmless. In C++ TUs (fast3d)
* it shadows the host header, so libstdc++'s <cstddef> never sees the host's
* max_align_t/size_t and fails to compile. Route C++ TUs to the host header
* by absolute path (generated hoststddef.h — same trick as
* port/shim/string.h / port/shim/stdlib.h).
*
* Inert in the N64 build (port/shim is not on its include path).
*/
#if defined(__cplusplus)
/* MinGW's <stdint.h> does:
* #define __need_wint_t
* #define __need_wchar_t
* #include <stddef.h>
* and never undefs the __need_* macros. stddef.h then refuses to run its
* main body (which defines max_align_t) for the rest of the TU, breaking
* libstdc++'s <cstddef>. Clear the poison before pulling in the host header.
*/
#undef __need_wchar_t
#undef __need_size_t
#undef __need_ptrdiff_t
#undef __need_NULL
#undef __need_wint_t
#include "hoststddef.h"
#else
#include "include/stddef.h"
#endif
+20
View File
@@ -0,0 +1,20 @@
/*
* PC port shim for <stdlib.h>.
*
* The N64 decomp ships include/stdlib.h (a libc stub: lldiv_t/ldiv_t typedefs
* + a couple of prototypes). C game code gets it, exactly as on the console.
*
* In C++ TUs (fast3d) it must NOT be used: MinGW's own headers pull in the
* host <stdlib.h> through their include chains (sec_api/stdlib_s.h does
* `#include <stdlib.h>`, which resolves to the N64 header via -I), and the
* lldiv_t/ldiv_t typedefs then conflict with the host's. The shim routes C++
* TUs to the host header by absolute path (generated hoststdlib.h, same
* trick as port/shim/string.h — see docs/PCPortResearch.md §11 D12).
*
* Inert in the N64 build (port/shim is not on its include path).
*/
#if defined(__cplusplus)
#include "hoststdlib.h"
#else
#include "include/stdlib.h"
#endif
+408 -14
View File
@@ -1,26 +1,420 @@
/*
* Crash handler / stack traces.
*
* Catches signals (SIGSEGV etc.) and prints a backtrace + the game's state,
* to make the inevitable porting crashes debuggable.
* Catches access violations (Windows) / fatal signals (POSIX) and prints a
* backtrace with file:line info (build with -g; MinGW emits PDBs so symbols
* resolve), to make the inevitable porting crashes debuggable.
*
* Modelled on the PD port's port/src/crash.c (~360 lines).
*
* STATUS: scaffolding stub — implement during Phase 1.
* Adapted from the PD port's port/src/crash.c.
*/
#include <stdio.h>
#include <stdlib.h>
#ifndef _GNU_SOURCE
#define _GNU_SOURCE 1
#endif
#include "platform.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <PR/ultratypes.h>
#include "system.h"
#include "platform.h"
#include "crash.h"
#define CRASH_LOG_FNAME "ge007.crash.log"
#define CRASH_MAX_MSG 8192
#define CRASH_MAX_SYM 256
#define CRASH_MAX_FRAMES 32
#define CRASH_MSG(...) \
if (msglen < CRASH_MAX_MSG) msglen += snprintf(msg + msglen, CRASH_MAX_MSG - msglen, __VA_ARGS__)
#if defined(PLATFORM_WINDOWS)
#include <windows.h>
#include <dbghelp.h>
#include <tlhelp32.h>
#include <inttypes.h>
#include <excpt.h>
static LPTOP_LEVEL_EXCEPTION_FILTER prevExFilter;
static void *crashGetModuleBase(const void *addr)
{
HMODULE h = NULL;
GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, addr, &h);
return (void *)h;
}
/*
* Phase 1 — raw crash info, NO dbghelp. Every call here is a plain Win32
* query; nothing allocates or walks memory, so this cannot re-fault even if
* the heap or a stack is corrupted. Written to console + file BEFORE any
* risky symbolication.
*/
static void crashStackTraceRaw(char *msg, PEXCEPTION_POINTERS exinfo)
{
CONTEXT context = *exinfo->ContextRecord;
DWORD msglen = 0;
CRASH_MSG("EXCEPTION: 0x%08lx\n", exinfo->ExceptionRecord->ExceptionCode);
#if defined(PLATFORM_X86_64)
CRASH_MSG("PC: %p (Rip=%p Rsp=%p Rbp=%p)\n",
exinfo->ExceptionRecord->ExceptionAddress,
(void *)context.Rip, (void *)context.Rsp, (void *)context.Rbp);
/* FPU state: for STATUS_FLOATING_POINT_* we want to know whether the
* exception masks were actually cleared (MxCsr bits 7..10 / x87 CW
* mask bits) or this is a stale status bit. */
CRASH_MSG("MxCsr=0x%08lx x87CW=0x%04x x87SW=0x%04x\n",
(unsigned long)context.MxCsr,
(unsigned)(context.FloatSave.ControlWord & 0xffff),
(unsigned)(context.FloatSave.StatusWord & 0xffff));
PVOID low = NULL, high = NULL;
if (GetCurrentThreadStackLimits(low, high)) {
CRASH_MSG("thread stack: %p..%p Rsp=%p used=%llu bytes\n",
low, high, (void *)context.Rsp,
(unsigned long long)((uintptr_t)high - (uintptr_t)context.Rsp));
}
#else
CRASH_MSG("PC: %p\n", exinfo->ExceptionRecord->ExceptionAddress);
#endif
CRASH_MSG("MODULE: [%p]\n", crashGetModuleBase(exinfo->ExceptionRecord->ExceptionAddress));
CRASH_MSG("MAIN MODULE: [%p]\n", crashGetModuleBase(crashInit));
}
/*
* Phase 2 — backtrace by walking the EBP chain manually. The build keeps
* frame pointers (-fno-omit-frame-pointer), so every frame is a pair of
* qwords at RBP: [saved RBP, return address]. No dbghelp, no allocation:
* each pointer is validated against the thread's stack bounds BEFORE being
* dereferenced, and the chain must strictly ascend (x64 stacks grow down),
* so a corrupted chain terminates instead of re-faulting. Addresses are
* symbolicated offline with addr2line when needed.
*/
static void crashStackTraceSym(char *msg, PEXCEPTION_POINTERS exinfo)
{
CONTEXT context = *exinfo->ContextRecord;
DWORD msglen = 0;
#if defined(PLATFORM_X86_64)
PVOID low = NULL, high = NULL;
if (!GetCurrentThreadStackLimits(low, high)) {
snprintf(msg, CRASH_MAX_MSG, "\nBACKTRACE: (no stack limits)\n");
return;
}
uintptr_t fp = context.Rbp;
int i = 0;
CRASH_MSG("\nBACKTRACE:\n#00: %p (crash PC)\n", exinfo->ExceptionRecord->ExceptionAddress);
while (i < CRASH_MAX_FRAMES - 1) {
/* The frame record must live inside the committed stack. */
if (fp < (uintptr_t)low || fp > (uintptr_t)high) break;
const uintptr_t *frame = (const uintptr_t *)fp;
const uintptr_t saved_fp = frame[0];
const uintptr_t ret_addr = frame[1];
CRASH_MSG("#%02d: %p", i + 1, (void *)ret_addr);
const uintptr_t modbase = (uintptr_t)crashGetModuleBase((void *)ret_addr);
if (modbase) {
CRASH_MSG(" [%p]+%llx", (void *)modbase,
(unsigned long long)(ret_addr - modbase));
}
CRASH_MSG("\n");
/* Chain must ascend; stop on the first frame that doesn't. */
if (saved_fp <= fp || saved_fp > (uintptr_t)high) break;
fp = saved_fp;
++i;
}
if (i == CRASH_MAX_FRAMES - 1) {
CRASH_MSG("...\n");
}
#else
snprintf(msg, CRASH_MAX_MSG, "\nBACKTRACE: not implemented on this arch\n");
#endif
}
static void crashWriteLog(const char *msg, int append)
{
FILE *f = fopen(CRASH_LOG_FNAME, append ? "ab" : "wb");
if (f) {
if (!append) fprintf(f, "Crash!\n\n");
fprintf(f, "%s", msg);
fclose(f);
}
}
/*
* Thread dump: unwind every thread in this process and print a short
* backtrace for each, labelled with the game-thread name when known.
* Called from the kernel heartbeat when no frame has rendered for a while,
* so a hang shows WHERE every thread is stuck instead of as silence.
*
* tids/names: parallel arrays (may be NULL/0). Threads not in the list are
* labelled by TID only. Uses SuspendThread + GetThreadContext + StackWalk64;
* each thread is resumed before the next is touched, so a hang in one walk
* cannot wedge the others.
*/
void crashDumpThreads(const unsigned long *tids, const char **names, int count)
{
HANDLE process = GetCurrentProcess();
HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
if (snap == INVALID_HANDLE_VALUE) return;
/* Function symbols only — line tables are slow to load and the dump
* prints function+offset, which is enough to find a hang. */
SymSetOptions(SymGetOptions() | SYMOPT_DEBUG);
SymInitialize(process, NULL, TRUE);
THREADENTRY32 te;
te.dwSize = sizeof(te);
if (!Thread32First(snap, &te)) {
CloseHandle(snap);
return;
}
do {
if (te.th32OwnerProcessID != GetCurrentProcessId()) continue;
char label[64] = "?";
int matched = 0;
for (int i = 0; i < count; ++i) {
if (tids && tids[i] == te.th32ThreadID) {
snprintf(label, sizeof(label), "%s", names ? names[i] : "?");
matched = 1;
break;
}
}
/* When a specific thread list is given, skip everything else —
* walking ~20 system threads with symbol resolution is slow and
* buries the interesting frames. */
if (count > 0 && !matched) continue;
HANDLE hth = OpenThread(THREAD_SUSPEND_RESUME | THREAD_GET_CONTEXT | THREAD_QUERY_INFORMATION, FALSE, te.th32ThreadID);
if (!hth) continue;
CONTEXT ctx;
memset(&ctx, 0, sizeof(ctx));
ctx.ContextFlags = CONTEXT_FULL;
DWORD susps = SuspendThread(hth);
if (susps != (DWORD)-1 && GetThreadContext(hth, &ctx)) {
sysLogPrintf(LOG_ERROR, " [thread %lu '%s']", te.th32ThreadID, label);
#if defined(PLATFORM_X86_64)
STACKFRAME64 sf;
memset(&sf, 0, sizeof(sf));
sf.AddrPC.Offset = ctx.Rip;
sf.AddrPC.Mode = AddrModeFlat;
sf.AddrFrame.Offset = ctx.Rbp;
sf.AddrFrame.Mode = AddrModeFlat;
sf.AddrStack.Offset = ctx.Rsp;
sf.AddrStack.Mode = AddrModeFlat;
char symbuf[sizeof(SYMBOL_INFO) + CRASH_MAX_SYM * sizeof(TCHAR)];
PSYMBOL_INFO sym = (PSYMBOL_INFO)symbuf;
sym->SizeOfStruct = sizeof(*sym);
sym->MaxNameLen = CRASH_MAX_SYM;
DWORD64 disp64 = 0;
for (int i = 0; i < 8; ++i) {
if (!StackWalk64(IMAGE_FILE_MACHINE_AMD64, process, hth, &sf, &ctx, NULL,
SymFunctionTableAccess64, SymGetModuleBase64, NULL))
break;
if (sym->SizeOfStruct == 0) {
sym->SizeOfStruct = sizeof(*sym);
sym->MaxNameLen = CRASH_MAX_SYM;
}
const char *where = "?";
if (SymFromAddr(process, sf.AddrPC.Offset, &disp64, sym)) {
static char linebuf[CRASH_MAX_FRAMES][512];
snprintf(linebuf[i % CRASH_MAX_FRAMES], sizeof(linebuf[0]), "%s+%llx",
sym->Name, (unsigned long long)disp64);
where = linebuf[i % CRASH_MAX_FRAMES];
}
sysLogPrintf(LOG_ERROR, " #%d %p %s", i,
(void *)(uintptr_t)sf.AddrPC.Offset, where);
}
#endif
}
ResumeThread(hth);
CloseHandle(hth);
} while (Thread32Next(snap, &te));
CloseHandle(snap);
SymCleanup(process);
}
static LONG __stdcall crashHandler(PEXCEPTION_POINTERS exinfo)
{
char msg[CRASH_MAX_MSG + 1] = { 0 };
if (IsDebuggerPresent()) {
if (prevExFilter) {
return prevExFilter(exinfo);
}
return EXCEPTION_CONTINUE_EXECUTION;
}
/* Phase 1: raw info, guaranteed to survive. */
crashStackTraceRaw(msg, exinfo);
sysLogPrintf(LOG_ERROR, "FATAL: %s", msg);
fflush(stderr);
fflush(stdout);
crashWriteLog(msg, 0);
/* Phase 2: EBP-chain backtrace (validated reads only — see
* crashStackTraceSym). The raw data is already on disk either way. */
{
char sym[CRASH_MAX_MSG + 1] = { 0 };
crashStackTraceSym(sym, exinfo);
if (sym[0]) {
sysLogPrintf(LOG_ERROR, "%s", sym);
crashWriteLog(sym, 1);
}
}
/* Terminate directly — abort() raises SIGABRT which can re-enter the
* exception machinery while we are already inside it. */
TerminateProcess(GetCurrentProcess(), exinfo->ExceptionRecord->ExceptionCode);
return EXCEPTION_CONTINUE_EXECUTION;
}
#elif defined(PLATFORM_LINUX)
#include <ucontext.h>
#include <signal.h>
#include <execinfo.h>
#include <unistd.h>
#include <ctype.h>
#include <dlfcn.h>
#include <sys/fcntl.h>
static struct sigaction prevSigAction;
static void *crashGetModuleBase(const void *addr)
{
Dl_info info;
if (dladdr(addr, &info)) {
return info.dli_fbase;
}
return NULL;
}
static void crashStackTrace(char *msg, int sig, void *pc)
{
unsigned msglen = 0;
void *frames[CRASH_MAX_FRAMES] = { NULL };
const int nframes = backtrace(frames, CRASH_MAX_FRAMES);
if (nframes <= 0) {
CRASH_MSG("no information\n");
return;
}
char **strings = backtrace_symbols(frames, nframes);
CRASH_MSG("SIGNAL: %d\n", sig);
CRASH_MSG("PC: ");
if (pc) {
CRASH_MSG("%p\n", pc);
} else if (strings) {
CRASH_MSG("%s\n", strings[0]);
} else {
CRASH_MSG("%p\n", frames[0]);
}
CRASH_MSG("MODULE: %p\n", crashGetModuleBase(frames[0]));
CRASH_MSG("MAIN MODULE: %p\n", crashGetModuleBase(crashInit));
CRASH_MSG("\nBACKTRACE:\n");
int i;
for (i = 0; i < nframes; ++i) {
CRASH_MSG("#%02d: ", i);
if (strings && strings[i]) {
CRASH_MSG("%s\n", strings[i]);
} else {
CRASH_MSG("%p\n", frames[i]);
}
}
if (i == CRASH_MAX_FRAMES) {
CRASH_MSG("...\n");
} else if (i <= 1) {
CRASH_MSG("no information\n");
}
free(strings);
}
static void crashHandler(int sig, siginfo_t *siginfo, void *ctx)
{
char msg[CRASH_MAX_MSG + 1] = { 0 };
void *pc = NULL;
if (ctx) {
ucontext_t *ucontext = (ucontext_t *)ctx;
#ifdef PLATFORM_X86
pc = (void *)ucontext->uc_mcontext.gregs[REG_EIP];
#elif defined(PLATFORM_X86_64)
pc = (void *)ucontext->uc_mcontext.gregs[REG_RIP];
#endif
}
sysLogPrintf(LOG_ERROR, "FATAL: Crashed: PC=%p SIGNAL=%d", pc, sig);
fflush(stderr);
fflush(stdout);
crashStackTrace(msg, sig, pc);
{
FILE *f = fopen(CRASH_LOG_FNAME, "wb");
if (f) {
fprintf(f, "Crash!\n\n%s", msg);
fclose(f);
}
}
sysFatalError("Crash!\n\n%s", msg);
}
#endif /* PLATFORM_WINDOWS / PLATFORM_LINUX */
int g_CrashEnabled = 0;
void crashInit(void)
{
/* TODO(Phase 1):
* - install signal handlers (SIGSEGV, SIGFPE, SIGILL, SIGBUS)
* - on crash: print backtrace (dbghelp on Windows, backtrace() on POSIX)
* - dump relevant game state (current level, frame, etc.)
*/
sysLogPrintf(LOG_INFO, "crashInit: TODO (Phase 1)");
#if defined(PLATFORM_WINDOWS)
SetErrorMode(SEM_FAILCRITICALERRORS);
prevExFilter = SetUnhandledExceptionFilter(crashHandler);
g_CrashEnabled = 1;
#elif defined(PLATFORM_LINUX)
struct sigaction sigact = { 0 };
sigact.sa_flags = SA_SIGINFO | SA_ONSTACK;
sigact.sa_sigaction = crashHandler;
sigaction(SIGSEGV, &sigact, &prevSigAction);
sigaction(SIGABRT, &sigact, &prevSigAction);
sigaction(SIGBUS, &sigact, &prevSigAction);
sigaction(SIGILL, &sigact, &prevSigAction);
g_CrashEnabled = 1;
#endif
}
void crashShutdown(void)
{
if (!g_CrashEnabled) {
return;
}
#if defined(PLATFORM_WINDOWS)
if (prevExFilter) {
SetUnhandledExceptionFilter(prevExFilter);
}
#elif defined(PLATFORM_LINUX)
sigaction(SIGSEGV, &prevSigAction, NULL);
sigaction(SIGABRT, &prevSigAction, NULL);
sigaction(SIGBUS, &prevSigAction, NULL);
sigaction(SIGILL, &prevSigAction, NULL);
#endif
g_CrashEnabled = 0;
}
+133
View File
@@ -0,0 +1,133 @@
/*
* dram.c — a slice of N64 "DRAM" in host memory, mapped twice.
*
* GE's game code mixes two address conventions that cannot both hold on an
* x64 host with a single mapping:
*
* 1. Pointers that pass through s32 (e.g. mempCheckMemflagTokens(s32, s32)
* in src/memp.c). Any address with bit 31 set sign-extends to an
* invalid 0xffffffffXXXXXXXX pointer when stored back into a u8*.
* => the game's working RAM must live BELOW 0x80000000.
*
* 2. "Physical" addresses that the game ORs with 0x80000000 to make them
* dereferenceable (e.g. `texnum = *((u16 *)(temp.word | 0x80000000))`
* in src/game/bg.c, where temp.word is a GBI w1 value produced by
* OS_K0_TO_PHYSICAL). => a KSEG0 view at 0x80000000 must exist and
* mirror the working RAM.
*
* The port therefore maps ONE 8 MB section at two addresses:
*
* V1 @ 0x70000000 "virtual" view — s32-safe; ALL game RAM symbols live
* here (cfb_16, _bssSegmentEnd/mempool start, tlbBlock/
* mempool end). Mempool pointers etc. are 0x70xxxxxx:
* positive as s32, safe through every 32-bit path.
* V2 @ 0x80000000 KSEG0 view — byte-identical mirror of V1 (same section
* backing), for code that rebuilds pointers with
* `offset | 0x80000000`.
*
* The port-side header shims (port/shim/PR/R4300.h, port/shim/PR/os.h) make
* the conversion macros consistent with this layout:
* PHYS_TO_K0(x) = x (identity)
* OS_K0_TO_PHYSICAL(x) = (u32)((char*)x - V1_BASE) (small offset)
* so GBI w1 words carry small offsets that fast3d's seg_addr() resolves by
* adding 0x80000000 (landing in V2, same data), and osVirtualToPhysical(x)
* stays the identity (V1 addresses are already live host pointers < 4 GB).
*
* Layout (NTSC US .stacks sits at 0x803AB400, MAPPING_TABLE_COUNT=93 pages,
* so the N64 tlb block is 0x803AB400 - 93*0x2000 = 0x802F4400 — kept as the
* mempool end for fidelity):
*
* +0x000000 cfb_16 (2 x 320x240x2 = 0x4B000, from src/cfb.c)
* +0x050000 _bssSegmentEnd -> mempool start (boss.c:217)
* ... game heap (mempools / dyn / audio buffers)
* +0x2F4400 tlb block -> mempool end (tlbmanageGetTlbAllocatedBlock)
* +0x800000 end of region (8 MB, like an 8-MB-RAM N64)
*/
#include "platform.h"
#include "system.h"
#if defined(PLATFORM_WINDOWS)
#include <windows.h>
#else
#define _GNU_SOURCE /* memfd_create — must precede all system headers */
#include <sys/mman.h>
#include <unistd.h>
#endif
#define DRAM_V1_BASE 0x70000000UL /* s32-safe "virtual" view */
#define DRAM_K0_BASE 0x80000000UL /* KSEG0 mirror view */
#define DRAM_SIZE 0x00800000UL /* 8 MB */
void *dramReserve(void)
{
#if defined(PLATFORM_WINDOWS)
/* One anonymous section, two views at fixed addresses. */
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(sa);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = FALSE;
HANDLE hSec = CreateFileMappingW(INVALID_HANDLE_VALUE, &sa, PAGE_READWRITE,
(DWORD)(DRAM_SIZE >> 32),
(DWORD)(DRAM_SIZE & 0xFFFFFFFF), NULL);
if (!hSec) {
sysFatalError("dram: CreateFileMapping failed (%lu)", GetLastError());
}
void *v1 = MapViewOfFileEx(hSec, FILE_MAP_ALL_ACCESS, 0, 0, DRAM_SIZE,
(void *)DRAM_V1_BASE);
if (v1 != (void *)DRAM_V1_BASE) {
sysFatalError("dram: could not map DRAM at %p (err %lu)",
(void *)DRAM_V1_BASE, GetLastError());
}
void *v2 = MapViewOfFileEx(hSec, FILE_MAP_ALL_ACCESS, 0, 0, DRAM_SIZE,
(void *)DRAM_K0_BASE);
if (v2 != (void *)DRAM_K0_BASE) {
sysFatalError("dram: could not map KSEG0 mirror at %p (err %lu)",
(void *)DRAM_K0_BASE, GetLastError());
}
/* Sanity: the views must alias the same backing store. */
((volatile char *)v1)[0] = 0x5A;
if (((volatile char *)v2)[0] != 0x5A) {
sysFatalError("dram: V1/V2 views do not share a backing store");
}
((volatile char *)v1)[0] = 0;
CloseHandle(hSec);
return v1;
#else
/* memfd + two MAP_SHARED mmaps = one backing store, two views. */
int fd = memfd_create("ge007_dram", 0);
if (fd < 0) {
sysFatalError("dram: memfd_create failed");
}
if (ftruncate(fd, DRAM_SIZE) != 0) {
sysFatalError("dram: ftruncate failed");
}
void *v1 = mmap((void *)DRAM_V1_BASE, DRAM_SIZE, PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_FIXED, fd, 0);
if (v1 == MAP_FAILED || v1 != (void *)DRAM_V1_BASE) {
sysFatalError("dram: could not map DRAM at %p", (void *)DRAM_V1_BASE);
}
void *v2 = mmap((void *)DRAM_K0_BASE, DRAM_SIZE, PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_FIXED, fd, 0);
if (v2 == MAP_FAILED || v2 != (void *)DRAM_K0_BASE) {
sysFatalError("dram: could not map KSEG0 mirror at %p",
(void *)DRAM_K0_BASE);
}
((volatile char *)v1)[0] = 0x5A;
if (((volatile char *)v2)[0] != 0x5A) {
sysFatalError("dram: V1/V2 views do not share a backing store");
}
((volatile char *)v1)[0] = 0;
close(fd);
return v1;
#endif
}
+18
View File
@@ -0,0 +1,18 @@
/*
* dram_syms.s — absolute symbols inside the "virtual" (s32-safe) DRAM view.
* See port/src/dram.c: one 8 MB backing store is mapped at BOTH 0x70000000
* (V1, where these live) and 0x80000000 (V2, the KSEG0 mirror). These are
* committed host addresses, so they are live pointers on the PC.
*
* cfb_16 replaces src/cfb.c (excluded from the build): the two
* 320x240x16-bit framebuffers, 0x4B000 bytes total.
* _bssSegmentEnd end-of-.bss marker; boss.c:217 starts the mempools at
* PHYS_TO_K0(osVirtualToPhysical(&_bssSegmentEnd)).
*/
.section .data
.global cfb_16
.set cfb_16, 0x70000000
.global _bssSegmentEnd
.set _bssSegmentEnd, 0x70050000
-110
View File
@@ -1,110 +0,0 @@
/*
* Port RCP scheduler.
*
* Replaces the hardware task submission in src/sched.c. On the N64,
* __scExec() hands a gfx/audio task to the RSP via osSpTaskLoad/StartGo.
* Here, instead of writing RSP registers, we:
* - gfx task: call videoSubmitCommands(display_list) -> the software RSP
* - audio task: feed the mixed buffer to the audio layer
* and reproduce the gfx/audio interleaving + VSync pacing the game expects.
*
* Modelled on the PD port's port/src/pdsched.c (~405 lines).
*
* STATUS: scaffolding stub — implement during Phase 2.
*
* NOTE: GE's src/sched.c has speed-graph / profiling hooks
* (speedgraphMarkerHandler) and a slightly different task model than PD.
* Reproduce the exact gfx/audio/VSync behaviour; see
* docs/PCPortResearch.md §9.3.
*/
#include <PR/ultratypes.h>
#include <PR/os.h>
#include <PR/gbi.h>
#include "sched.h"
#include "fr.h" /* NUM_VIDEO_FRAME_BUFFERS */
#include "platform.h"
#include "video.h"
#include "audio.h"
#include "system.h"
/*
* Scheduler globals (normally defined in src/sched.c, which is EXCLUDED from
* the PC build). init.c's schedulerInitThread() references os_scheduler and
* gfxClient, so the port provides them here (matching src/sched.c:70-71).
*/
OSSched os_scheduler;
OSScClient gfxClient[3];
/*
* Scheduler/VI globals (normally defined in src/sched.c, EXCLUDED). fr.c
* references these; the port provides them so it links. Values are the
* sched.c initializers.
*/
s32 g_schedViCurrentFrameBuffer = 0;
s32 g_ViChangeVideoModes[NUM_VIDEO_FRAME_BUFFERS] = {0, 0};
OSViMode g_ViModes[NUM_VIDEO_FRAME_BUFFERS];
OSViMode *g_ViModePtrs[NUM_VIDEO_FRAME_BUFFERS];
/*
* Performance counters (normally in src/sched.c). speed_graph.c reads them
* via get_counters().
*/
static u32 g_DisplayPerformanceCounters[4]; // clock, cmc, pipe, tmem
u32 *get_counters(void) { return g_DisplayPerformanceCounters; }
/* Debug stderr control (normally in src/sched.c). boss.c calls it. */
void permit_stderr(u32 flag) { (void)flag; }
/*
* Scheduler API (declared in sched.h, normally defined in src/sched.c which
* is EXCLUDED from the PC build). The port provides these so engine code that
* calls osCreateScheduler/osScAddClient/osScGetCmdQ links. The real work —
* driving the software RSP per frame — happens in geschedRunFrame().
*/
void osCreateScheduler(OSSched *s, void *stack, u8 mode, u32 numFields)
{
/* TODO(Phase 2): initialise the port scheduler state in *s. */
(void)s; (void)stack; (void)mode; (void)numFields;
sysLogPrintf(LOG_INFO, "osCreateScheduler: TODO (Phase 2)");
}
void osScAddClient(OSSched *s, OSScClient *c, OSMesgQueue *msgQ, OSScClient *next)
{
/* TODO(Phase 2): register a gfx/audio client. */
(void)s; (void)c; (void)msgQ; (void)next;
}
void osScRemoveClient(OSSched *s, OSScClient *c)
{
(void)s; (void)c;
}
OSMesgQueue *osScGetCmdQ(OSSched *s)
{
/* TODO(Phase 2): return the scheduler's command queue. */
(void)s;
return NULL;
}
void geschedInit(void)
{
/* TODO(Phase 2): set up the scheduler + gfx client, mirroring
* schedulerInitThread() in src/init.c but with the port's frame hooks. */
sysLogPrintf(LOG_INFO, "geschedInit: TODO (Phase 2)");
}
/*
* Per-frame driver. Called by the main loop (or the VI vsync callback shim).
* Sequence (mirrors PD's pdsched.c):
* videoStartFrame()
* ... game builds the display list ...
* videoSubmitCommands(cmds)
* videoEndFrame()
*/
void geschedRunFrame(void *displayList)
{
videoStartFrame();
videoSubmitCommands((Gfx *)displayList);
audioEndFrame();
videoEndFrame();
}
-35
View File
@@ -1,35 +0,0 @@
/*
* Software-RSP placeholder (Phase 2).
*
* port/fast3d/ currently contains only the API header (gfx_api.h); the real
* implementation arrives in Phase 2 by adapting the PD port's fast3d. video.c
* calls these entry points, so they must exist to link. Every function is a
* no-op: frames are submitted and dropped until the RSP emulator lands.
*
* DELETE THIS FILE when the real fast3d sources are added (they define the
* same symbols; keeping both would be a duplicate-definition link error).
*/
#include <stdint.h>
#include <stdbool.h>
#include <PR/gbi.h>
/* port/fast3d is not on the include path; use the relative form. */
#include "../fast3d/gfx_api.h"
struct GfxDimensions gfx_current_dimensions = {
1.0f, /* internal_mul */
640, 480, /* width, height */
1.5f, /* aspect_ratio */
};
void gfx_init(const struct GfxInitSettings *settings) { (void)settings; }
void gfx_destroy(void) {}
void gfx_start_frame(void) {}
void gfx_end_frame(void) {}
void gfx_run(Gfx *commands) { (void)commands; }
void gfx_set_target_fps(int fps) { (void)fps; }
void gfx_set_texture_filter(int mode) { (void)mode; }
void gfx_set_mipmap_filter(int mode) { (void)mode; }
+729 -127
View File
File diff suppressed because it is too large. Load diff
+50 -28
View File
@@ -5,43 +5,49 @@
* On the PC we:
* 1. set up system / config / fs / rom
* 2. load the ROM and map it at the cart base (0x10000000)
* 3. init video (SDL2 + GL), audio, input
* 4. hand control to the game's mainproc() / bossEntry()
*
* Modelled on the PD port's port/src/main.c.
*
* STATUS: Phase 1 — ROM loads and maps, a window opens and clears each frame.
* Step 4 (mainproc) is deferred until the software RSP (fast3d) and the
* scheduler can service the game's rendering/audio threads; see
* docs/PCPortResearch.md.
* 3. init video (SDL2 + GL via fast3d), audio, input
* 4. start the thread kernel and run the game's mainproc() as a real OS
* thread (it IS the N64 mainThread) — which runs bossEntry(), the real
* game loop. The game's own scheduler (src/sched.c) drives frames; see
* docs/PCPortResearch.md.
* 5. the host main thread then owns SDL event pumping for the lifetime of
* the process (Windows only dispatches window messages to the creating
* thread, and every game thread can be blocked on a queue).
*/
#include <stdlib.h>
#include <stdio.h>
#include <PR/ultratypes.h>
#include <PR/os.h>
#include "platform.h"
#include "system.h"
#include "config.h"
#include "fs.h"
#include "romdata.h"
#include "dram.h"
#include "video.h"
#include "audio.h"
#include "input.h"
#include "mixer.h"
#include "crash.h"
#include "thread_config.h"
/* Defined in the game (src/init.c). The port calls into the real game entry. */
extern void mainproc(void *args);
extern OSThread mainThread; /* src/init.c:75 */
int main(int argc, char **argv)
{
(void)argc;
(void)argv;
sysSetArgs(argc, argv);
sysLogPrintf(LOG_INFO, "GoldenEye 007 PC port starting "
"(%s, %s)", GE007_ROMID, GE007_VERSION_HASH);
/* Crash handler first, so any failure below is debuggable. */
crashInit();
/* 1. Platform + config + filesystem. */
configLoad();
@@ -52,6 +58,25 @@ int main(int argc, char **argv)
return 1;
}
/* 2a. Sanity: the image MUST have loaded at its preferred base.
* dram_syms.s absolute symbols are referenced through pointer-typed
* externs, which on x86-64/PE become .refptr slots with BASE
* relocations. The build disables ASLR (--disable-dynamic-base) so the
* loader loads at 0x140000000 and those relocations are no-ops; if we
* ever got relocated, every such slot would be silently corrupted.
* Fail loudly instead. */
if (sysImageBase() != 0x140000000ul) {
sysLogPrintf(LOG_ERROR,
"image loaded at %p, expected preferred base 0x140000000; "
"absolute DRAM symbols would be corrupted (ASLR must be off)",
(void *)sysImageBase());
return 1;
}
/* 2b. Reserve the N64-DRAM region: s32-safe view @ 0x70000000 (cfb_16,
* mempools) + KSEG0 mirror @ 0x80000000 (see port/src/dram.c). */
dramReserve();
/* 3. Video / audio / input. */
if (videoInit() != 0) {
sysLogPrintf(LOG_ERROR, "videoInit failed");
@@ -61,27 +86,24 @@ int main(int argc, char **argv)
mixerInit();
inputInit();
/* 4. Phase 1 demo loop: clear colour until the RSP/scheduler can drive
* real frames (Phase 2). Press ESC or close the window to quit.
*
* When fast3d lands, replace this with mainproc(NULL) — defined in
* src/init.c (compiled for the PC; see A3 in docs/PCPortResearch.md).
* init.c's N64-only init() is never called; its hardware deps are
* stubbed in port/src/n64stubs.c and the libultra OS calls are shimmed
* in port/src/libultra.c.
*/
sysLogPrintf(LOG_INFO, "Phase 1: ROM mapped at 0x%08X (%u bytes); "
"demo loop running — ESC to quit",
/* 4. Run the game. mainproc() runs as the N64 mainThread (a real OS
* thread with its own stack); it creates the rmon/idle/scheduler/
* audio threads and never returns in practice. */
sysLogPrintf(LOG_INFO, "ROM mapped at 0x%08X (%u bytes); starting game",
(unsigned)0x10000000, romdataGetRomSize());
portKernelInit();
osCreateThread(&mainThread, MAIN_THREAD_ID, &mainproc, NULL, NULL,
MAIN_THREAD_PRIORITY);
osStartThread(&mainThread);
/* 5. Host thread: pump SDL events until the window is closed / ESC.
* videoPumpEvents() exits the process on quit. */
for (;;) {
if (videoHandleEvents())
break;
videoStartFrame();
videoSubmitCommands(NULL); /* no-op until fast3d lands */
videoEndFrame();
videoPumpEvents();
sysSleep(8);
}
/* Clean up. */
/* Unreachable in practice; clean up if we ever get here. */
inputDestroy();
mixerDestroy();
audioDestroy();
+33 -3
View File
@@ -21,6 +21,7 @@
#include <string.h>
#include <PR/ultratypes.h>
#include <PR/os.h>
#include <tlb_manage.h>
/* --- Segment start/end getters (normally linker-script symbols) --------- */
@@ -44,8 +45,11 @@ u32 get_inflateSegmentRomEnd(void) { return 0; }
* What remains here are pure-RAM segment symbols that have no ROM presence:
* the host's own .bss/.csegment ends and the N64 vaddr markers (only used by
* the never-run N64 boot path on the PC).
*
* _bssSegmentEnd is NOT here: it is an ABSOLUTE symbol in dram_syms.s
* (0x70050000, inside the s32-safe DRAM view — boss.c starts the mempools
* at PHYS_TO_K0(osVirtualToPhysical(&_bssSegmentEnd)); see port/src/dram.c).
*/
u32 *_bssSegmentEnd = NULL;
u32 *_csegmentSegmentStart = NULL;
u32 *_csegmentSegmentEnd = NULL;
u32 *_inflateSegmentVaddrStart = NULL;
@@ -71,7 +75,15 @@ void resolve_TLBaddress_for_InvalidHit(void) { /* no-op */ }
/*
* tlb_manage.c is excluded (it manages the N64's 64-entry TLB for on-demand
* ROM segment loading). boss.c still calls two of its functions from
* bossInitMainthreadData(); inert on the PC.
* bossInitMainthreadData().
*
* tlbmanageGetTlbAllocatedBlock() is NOT inert: boss.c:218 uses it as the
* END of the mempool area (mempCheckMemflagTokens(start, block - start)).
* On the N64 it is page_align_down(&sp_boot) - MAPPING_TABLE_COUNT*PAGE_SIZE
* = 0x803AB400 - 93*0x2000 = 0x802F4400 (US .stacks). We keep that exact
* OFFSET (+0x2F4400 from the DRAM base) but express it in the s32-safe V1
* view (see dram.c): 0x70000000 + 0x2F4400 = 0x702F4400, so [start, block)
* is live host memory and survives the s32 paths in src/memp.c.
*/
void tlbmanageEstablishManagementTable(void) { /* no-op */ }
void tlbmanageResetCurrentEntriesCount(void) { /* no-op */ }
@@ -79,7 +91,10 @@ void tlbmanageTranslateLoadRomFromTlbAddress(u32 address)
{
(void)address;
}
u8 (*tlbmanageGetTlbAllocatedBlock(void))[TLB_BLOCK_SIZE] { return NULL; }
u8 (*tlbmanageGetTlbAllocatedBlock(void))[TLB_BLOCK_SIZE]
{
return (u8 (*)[TLB_BLOCK_SIZE])0x702F4400;
}
/* --- K&R libc helpers (IDO provided these; the host libc does not) ------- */
/* Signatures mirror include/PR/os.h:983 / include/bstring.h exactly. */
@@ -121,3 +136,18 @@ void osSyncPrintf(const char *fmt, ...)
vfprintf(stderr, fmt, ap);
va_end(ap);
}
/* --- VI manager (src/libultrare/io/vimgr.c, not built for PC) ----------- */
/* sched.c's osCreateScheduler() calls osCreateViManager(OS_PRIORITY_VIMGR). */
/* The N64 VI manager thread serviced video interrupts; on the port the */
/* cooperative kernel posts the retrace message itself (port/src/libultra.c), */
/* so this is a no-op. */
void osCreateViManager(OSPri pri) { (void)pri; }
/* --- Crash screen (src/crash.c, not built for PC) ------------------------ */
/* src/crash.c is the game's TLB-fault diagnostics + rmon-driven crash */
/* screen renderer. It depends on rmon and the N64 exception path, neither of */
/* which exists here; real host faults are caught by port/src/crash.c instead.*/
/* sched.c only calls this for the debug stderr overlay (off at boot), so a */
/* no-op is safe. */
void crashRenderFrame(u16 *buffer) { (void)buffer; }
+113 -17
View File
@@ -1,9 +1,7 @@
/*
* Platform primitives: time, logging, paths, exit.
*
* Modelled on the PD port's port/src/system.c (~315 lines).
*
* STATUS: scaffolding stub — implement during Phase 1.
* Modelled on the PD port's port/src/system.c.
*/
#include <stdio.h>
@@ -20,40 +18,72 @@
#include <windows.h>
#include <io.h>
#else
#define _POSIX_C_SOURCE 199309L
#include <unistd.h>
#include <time.h>
#endif
#include "system.h"
/* --- Time --------------------------------------------------------------- */
#if defined(PLATFORM_WINDOWS)
static LARGE_INTEGER g_qpcFreq;
#endif
uint64_t sysGetMicroseconds(void)
{
/* TODO(Phase 1): use a monotonic clock.
* - Windows: QueryPerformanceCounter
* - POSIX: clock_gettime(CLOCK_MONOTONIC)
*/
#if defined(PLATFORM_WINDOWS)
static int inited = 0;
LARGE_INTEGER c;
if (!inited) {
QueryPerformanceFrequency(&g_qpcFreq);
inited = 1;
}
QueryPerformanceCounter(&c);
return (uint64_t)((double)c.QuadPart * 1000000.0 / (double)g_qpcFreq.QuadPart);
#else
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (uint64_t)ts.tv_sec * 1000000ull + (uint64_t)(ts.tv_nsec / 1000);
#endif
}
uintptr_t sysImageBase(void)
{
#if defined(PLATFORM_WINDOWS)
return (uintptr_t)GetModuleHandleW(NULL);
#else
/* TODO: parse /proc/self/maps for the first executable mapping. */
return 0;
#endif
}
int64_t sysGetTime(void)
{
/* TODO(Phase 1): wall clock (time(NULL)). */
return 0;
return (int64_t)time(NULL);
}
void sysSleep(uint32_t micros)
{
/* TODO(Phase 1): Sleep() / usleep(). */
(void)micros;
#if defined(PLATFORM_WINDOWS)
Sleep((DWORD)((micros + 999) / 1000));
#else
struct timespec req;
req.tv_sec = micros / 1000000u;
req.tv_nsec = (long)(micros % 1000000u) * 1000L;
while (nanosleep(&req, &req) != 0) { /* EINTR: keep sleeping the rest */
}
#endif
}
/* --- Logging ------------------------------------------------------------ */
void sysLogPrintf(enum LogLevel level, const char *fmt, ...)
{
static const char *tags[] = { "ERROR", "WARN ", "INFO ", "DEBUG" };
static const char *tags[] = { "ERROR", "WARN ", "NOTE ", "INFO ", "DEBUG" };
va_list ap;
if ((int)level >= 5) level = LOG_DEBUG;
fprintf(stderr, "[%s] ", tags[level]);
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
@@ -61,17 +91,83 @@ void sysLogPrintf(enum LogLevel level, const char *fmt, ...)
fprintf(stderr, "\n");
}
void sysFatalError(const char *fmt, ...)
{
va_list ap;
fflush(stdout);
fflush(stderr);
fprintf(stderr, "[FATAL] ");
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
fprintf(stderr, "\n");
fflush(stderr);
abort();
}
/* --- Command line ------------------------------------------------------- */
static int g_argc = 0;
static char **g_argv = NULL;
void sysSetArgs(int argc, char **argv)
{
g_argc = argc;
g_argv = argv;
}
int sysArgCheck(const char *arg)
{
for (int i = 1; i < g_argc; ++i) {
if (g_argv[i] && strcmp(g_argv[i], arg) == 0)
return 1;
}
return 0;
}
const char *sysArgGetString(const char *arg)
{
for (int i = 1; i < g_argc; ++i) {
if (g_argv[i] && strcmp(g_argv[i], arg) == 0) {
if (i + 1 < g_argc)
return g_argv[i + 1];
}
}
return NULL;
}
/* --- CPU ---------------------------------------------------------------- */
#if defined(PLATFORM_WINDOWS)
#define PORT_DO_YIELD() YieldProcessor()
#elif defined(__x86_64__) || defined(_M_X64) || defined(__i386__) || defined(_M_IX86)
#include <immintrin.h>
#define PORT_DO_YIELD() _mm_pause()
#else
#define PORT_DO_YIELD() do { } while (0)
#endif
void sysCpuRelax(void)
{
PORT_DO_YIELD();
}
/* --- Paths -------------------------------------------------------------- */
static char exeDir[512] = ".";
const char *sysGetExeDir(void)
{
/* TODO(Phase 1): resolve the executable's directory.
* - Windows: GetModuleFileName
* - Linux: readlink /proc/self/exe
* - macOS: _NSGetExecutablePath
*/
#if defined(PLATFORM_WINDOWS)
if (exeDir[0] == '.' && exeDir[1] == 0) {
DWORD n = GetModuleFileNameA(NULL, exeDir, sizeof(exeDir) - 1);
if (n) {
char *slash = strrchr(exeDir, '\\');
if (slash)
*slash = 0;
}
}
#endif
return exeDir;
}
+157 -144
View File
@@ -1,198 +1,211 @@
/*
* Video: SDL2 window + OpenGL context + frame pacing.
* Video: SDL2 window + OpenGL context + frame pacing on top of fast3d.
*
* Owns the GL context that the software RSP (fast3d) renders into. Drives the
* frame boundary hooks called by the port scheduler (gesched.c).
* The window itself lives in port/fast3d/gfx_sdl2.cpp (the wapi backend);
* this file wires the rendering API up, owns frame boundaries and FPS stats,
* and exposes the small surface the libultra VI shims need.
*
* Phase 1: real window + GL context + clear colour, driven by the demo loop
* in main.c. gfx_* entry points are still the no-ops in gfxstub.c until the
* PD-derived fast3d lands (Phase 2); videoStartFrame/EndFrame do the GL work
* directly so a visible frame exists before that.
* Modelled on the PD port's port/src/video.c (slimmed: no options menu).
*/
#include <SDL.h>
#include <GL/gl.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <PR/ultratypes.h>
#include <PR/gbi.h>
#include "platform.h"
#include "system.h"
#include "config.h"
#include "video.h"
/* fast3d entry points (see port/fast3d/gfx_api.h). */
extern void gfx_init(const void *settings);
extern void gfx_destroy(void);
extern void gfx_start_frame(void);
extern void gfx_run(void *commands);
extern void gfx_end_frame(void);
#include "../fast3d/gfx_api.h"
#include "../fast3d/gfx_sdl.h"
#include "../fast3d/gfx_opengl.h"
static int initDone = 0;
static int vidVsync = 1;
static int vidFpsLimit = 0;
static int vidFullscreen = 0;
static int vidMaximize = 0;
/* GE's internal resolution: NTSC LAN1 is 640x480; PAL LAN1 shows a
* 640x400 area. The window opens at the native size (1:1) by default. */
#ifdef REFRESH_PAL
#define GE_NATIVE_W 640
#define GE_NATIVE_H 400
#else
#define GE_NATIVE_W 640
#define GE_NATIVE_H 480
#endif
static SDL_Window *vidWindow = NULL;
static SDL_GLContext vidGL = NULL;
static struct GfxWindowManagerAPI *wmAPI;
static struct GfxRenderingAPI *renderingAPI;
static int initDone = 0;
/* FPS accounting. */
static uint64_t fpsLastTick = 0;
static int fpsFrames = 0;
static float fpsValue = 0.0f;
static u32 frames = 0;
static double fpsWindowStart = 0.0;
static int fpsNumFrames = 0;
static float vidAvgFPS = 0.f;
int videoInit(void)
{
if (initDone)
return 0;
wmAPI = &gfx_sdl;
renderingAPI = &gfx_opengl_api;
if (SDL_Init(SDL_INIT_VIDEO) != 0) {
sysLogPrintf(LOG_ERROR, "videoInit: SDL_Init failed: %s",
SDL_GetError());
return -1;
}
gfx_current_native_viewport.width = GE_NATIVE_W;
gfx_current_native_viewport.height = GE_NATIVE_H;
gfx_current_native_aspect = (float)GE_NATIVE_W / (float)GE_NATIVE_H;
gfx_framebuffers_enabled = true;
gfx_detail_textures_enabled = false;
gfx_msaa_level = 1;
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
/* fast3d will want a modern context once it lands; 3.0 core is the floor
* the PD port targets. */
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
struct GfxInitSettings set = {
.wapi = wmAPI,
.rapi = renderingAPI,
.window_settings = {
.title = "GoldenEye 007",
.width = GE_NATIVE_W,
.height = GE_NATIVE_H,
.x = 100,
.y = 100,
.fullscreen = false,
.fullscreen_is_exclusive = false,
.maximized = false,
.centered = true,
.allow_hidpi = false,
},
};
int w = 640, h = 480; /* GE's internal resolution (NTSC) */
Uint32 flags = SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE;
if (vidFullscreen)
flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
gfx_init(&set);
vidWindow = SDL_CreateWindow("GoldenEye 007 (PC port)",
SDL_WINDOWPOS_CENTERED,
SDL_WINDOWPOS_CENTERED, w, h, flags);
if (!vidWindow) {
sysLogPrintf(LOG_ERROR, "videoInit: SDL_CreateWindow failed: %s",
SDL_GetError());
return -1;
}
if (vidMaximize)
SDL_MaximizeWindow(vidWindow);
/* VSync on; fast3d paces the window itself. */
wmAPI->set_swap_interval(1);
vidGL = SDL_GL_CreateContext(vidWindow);
if (!vidGL) {
sysLogPrintf(LOG_ERROR, "videoInit: SDL_GL_CreateContext failed: %s",
SDL_GetError());
return -1;
}
SDL_GL_MakeCurrent(vidWindow, vidGL);
SDL_GL_SetSwapInterval(vidVsync ? 1 : 0);
gfx_set_texture_filter(FILTER_LINEAR);
gfx_set_mipmap_filter(MIPMAP_LINEAR);
/* Phase 1 clear colour: dark blue-grey so the window is visibly alive. */
glClearColor(0.10f, 0.12f, 0.16f, 1.0f);
glViewport(0, 0, w, h);
/* fast3d hooks (no-ops until Phase 2). */
gfx_init(NULL);
fpsLastTick = sysGetMicroseconds();
initDone = 1;
sysLogPrintf(LOG_INFO, "videoInit: %dx%d window + GL context up", w, h);
sysLogPrintf(LOG_INFO, "video: %dx%d window (native %dx%d)",
(int)gfx_current_dimensions.width, (int)gfx_current_dimensions.height,
GE_NATIVE_W, GE_NATIVE_H);
return 0;
}
void videoDestroy(void)
{
if (!initDone)
return;
gfx_destroy();
if (vidGL) {
SDL_GL_DeleteContext(vidGL);
vidGL = NULL;
if (initDone) {
gfx_destroy();
initDone = 0;
}
if (vidWindow) {
SDL_DestroyWindow(vidWindow);
vidWindow = NULL;
}
SDL_Quit();
initDone = 0;
}
/*
* Pump window events. Returns 1 when the app should quit (window close or
* ESC). Called by the demo loop until the port scheduler owns the frame
* boundary (Phase 2).
*/
int videoHandleEvents(void)
{
SDL_Event e;
while (SDL_PollEvent(&e)) {
if (e.type == SDL_QUIT)
return 1;
if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_ESCAPE)
return 1;
}
return 0;
}
void videoStartFrame(void)
{
if (!initDone)
if (!initDone) {
return;
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
/* Rendering runs on the game's scheduler thread; the GL context was
* created on the host main thread. */
gfx_sdl_make_context_current();
gfx_start_frame();
}
/*
* Host-thread SDL event pump.
*
* On Windows, window messages are only dispatched when the thread that
* CREATED the window pumps them — and every game thread can be blocked on a
* message queue at any time. So the host main thread (which created the
* window in videoInit) must keep pumping; otherwise the window goes
* "Not Responding" and ESC/close never arrive. fast3d's own handle_events
* (which runs during rendering) remains as a backstop.
*/
void videoPumpEvents(void)
{
if (!initDone) {
return;
}
SDL_Event ev;
while (SDL_PollEvent(&ev)) {
switch (ev.type) {
case SDL_QUIT:
sysLogPrintf(LOG_INFO, "video: quit requested");
exit(0);
break;
case SDL_KEYDOWN:
if (ev.key.keysym.sym == SDLK_ESCAPE) {
sysLogPrintf(LOG_INFO, "video: ESC -> quit");
exit(0);
}
break;
case SDL_WINDOWEVENT:
if (ev.window.event == SDL_WINDOWEVENT_CLOSE) {
sysLogPrintf(LOG_INFO, "video: window closed");
exit(0);
} else if (ev.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) {
gfx_sdl_update_cached_size();
}
break;
default:
break;
}
}
}
void videoSubmitCommands(Gfx *cmds)
{
if (initDone)
gfx_run(cmds);
if (!initDone) {
return;
}
gfx_run(cmds);
}
void videoEndFrame(void)
{
if (!initDone)
if (!initDone) {
return;
}
gfx_end_frame();
if (vidWindow)
SDL_GL_SwapWindow(vidWindow);
/* FPS accounting. */
fpsFrames++;
uint64_t now = sysGetMicroseconds();
if (now - fpsLastTick >= 1000000) {
fpsValue = (float)fpsFrames * 1000000.0f / (float)(now - fpsLastTick);
fpsFrames = 0;
fpsLastTick = now;
++frames;
++fpsNumFrames;
double now = wmAPI->get_time();
if (fpsWindowStart == 0.0) {
fpsWindowStart = now;
}
if (now - fpsWindowStart >= 1.0) {
vidAvgFPS = (float)(fpsNumFrames / (now - fpsWindowStart));
fpsNumFrames = 0;
fpsWindowStart = now;
}
}
void videoSetVsync(int enabled)
float videoGetFPS(void)
{
vidVsync = enabled;
if (initDone && vidWindow)
SDL_GL_SetSwapInterval(vidVsync ? 1 : 0);
return vidAvgFPS;
}
void videoSetFramerateLimit(int fps) { vidFpsLimit = fps; /* TODO Phase 2 */ }
int videoGetFullscreen(void) { return vidFullscreen; }
void videoSetFullscreen(int enabled)
{
vidFullscreen = enabled;
if (initDone && vidWindow)
SDL_SetWindowFullscreen(vidWindow,
enabled ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0);
}
int videoGetMaximize(void) { return vidMaximize; }
void videoSetMaximize(int enabled)
{
vidMaximize = enabled;
if (initDone && vidWindow) {
if (enabled)
SDL_MaximizeWindow(vidWindow);
else
SDL_RestoreWindow(vidWindow);
}
}
float videoGetFPS(void) { return fpsValue; }
PD_CONSTRUCTOR static void videoConfigInit(void)
void videoUpdateNativeResolution(s32 w, s32 h)
{
configRegisterInt("Video.Vsync", &vidVsync, 0, 1);
configRegisterInt("Video.FramerateLimit", &vidFpsLimit, 0, 240);
configRegisterInt("Video.Fullscreen", &vidFullscreen, 0, 1);
configRegisterInt("Video.Maximize", &vidMaximize, 0, 1);
if (w <= 0 || h <= 0) {
return;
}
gfx_current_native_viewport.width = w;
gfx_current_native_viewport.height = h;
gfx_current_native_aspect = (float)w / (float)h;
}
s32 videoGetNativeWidth(void) { return gfx_current_native_viewport.width; }
s32 videoGetNativeHeight(void) { return gfx_current_native_viewport.height; }
s32 videoCreateFramebuffer(u32 w, u32 h, s32 upscale, s32 autoresize)
{
return gfx_create_framebuffer(w, h, upscale, autoresize);
}
void videoCopyFramebuffer(s32 dst, s32 src, s32 left, s32 top)
{
/* assume immediate copies always read the front buffer */
gfx_copy_framebuffer(dst, src, left, top, false);
}
void videoResetTextureCache(void)
{
gfx_texture_cache_clear();
}