Driver Context¶
A system or driver runs before a game launches and after it exits, so it does not look up the running game. It receives what it needs at two points:
- When a driver starts, it receives an
IDriverContextfromrex/driver_context.h, which serves the settings registry, the logger and the UI thread. - When a game launches, the runtime attaches it to each system. The system then receives the game interfaces it needs, and gives them back when the game exits.
Driver Start¶
IDriver::Initialize receives the IDriverContext. A driver registers its settings and adds its log categories through it.
Some host libraries, such as SDL, must run on the thread that created the window. IDriverContext::RunOnUIThread runs a function on the UI thread and waits for it, and PostToUIThread queues a function there and returns immediately.
Title Attach¶
TitleRuntime::Launch attaches the game to the systems with these interfaces:
| Type | Header | Purpose |
|---|---|---|
TitleInfo |
rex/title.h |
Holds the title ID and the VideoMode. |
IPhysicalMemory |
rex/guest.h |
Reads and writes guest physical memory. |
IPhysicalWriteWatch |
rex/guest.h |
Reports writes to watched physical pages. |
IGuestInterrupts |
rex/guest.h |
Starts host threads that can call into the game, and raises the graphics interrupt. |
IAchievementRegistry |
rex/title.h |
Lists the achievements and their icons, and reports unlocks. |
Each system receives only what it uses:
- The graphics system receives
TitleInfo,IPhysicalMemory,IPhysicalWriteWatchandIGuestInterrupts, and passes them to its driver inIGraphicsDriver::AttachTitle. - The UI system receives
TitleInfoandIAchievementRegistry, for the achievements overlay. - The audio and input systems receive nothing. The kernel calls them through their public interfaces.
Several of these calls return a ScopeGuard, an object from rex/utility.h that runs a cleanup function once when it is destroyed. Destroying the guard undoes the call.
Physical Memory¶
The Xenos GPU reads and writes memory by physical address, from 0 to 0x1FFFFFFF, which is the range of IPhysicalMemory. See Physical Memory for how physical memory relates to the guest address space.
Calls ignore guest allocation and page protection. A range that ends above 0x20000000 fails with a kMemory error, and nothing is read or written.
CopyToPhysical notifies the write listeners of the pages it writes. A write through the span that GetWritablePhysicalSpan returns notifies nobody.
Fences¶
The game and the GPU signal each other through fences, 32 bit values in memory that one side writes and the other side waits on. StoreRelease32 and LoadAcquire32 write and read a fence:
- Both are atomic 32 bit accesses in big endian order.
- The address must be 4 byte aligned. A misaligned address returns a
kMemoryerror. - A
LoadAcquire32that reads the value of aStoreRelease32sees every host write that the storing thread made before the store.
Write Watches¶
A graphics driver keeps copies of guest memory on the host GPU, such as textures and vertex data. When the game writes that memory, the copies go stale. A write watch reports which pages changed. It calls a listener on the next write to a watched physical page. See Write Watches for which writes call the listeners and which pages a watch covers.
A driver adds its listener once and then watches ranges:
sequenceDiagram
participant D as Driver
participant W as Write watch
participant G as Guest thread
D->>W: AddWriteListener
W-->>D: ScopeGuard
D->>W: WatchPhysicalWrites
G->>W: Write to watched page
W->>D: OnPhysicalWrite
D-->>W: Range to unwatch
D->>W: WatchPhysicalWrites
AddWriteListeneradds the listener and returns aScopeGuard. Destroying the guard removes the listener, so the listener must outlive the guard.WatchPhysicalWriteswatches a range of physical addresses until the next write to it. A range that ends above0x20000000is ignored.- On a write to a watched page,
OnPhysicalWrite(range, exact)runs on the writing thread, with the write watch lock held.rangeis in physical addresses and covers whole host pages. - If
exactis false, the listener returns the largest range aroundrangethat it no longer needs watched, or{0, 0xFFFFFFFF}for any range. Ifexactis true, the return value is ignored. - A watch fires once. To watch the range again, the driver calls
WatchPhysicalWritesafter the listener returns.
The Xenos drivers use write watches in two places. Shared memory, the driver's copy of guest memory on the GPU, watches each range after it uploads it, and its listener marks the written pages for upload again. The primitive processor, which converts index data the host GPU cannot read as it is, watches that index data and removes its cached conversions when the data changes.
Write Watch Lock¶
A RankedMutex carries a rank, and a thread may take it only when its rank is above every rank the thread holds. A Debug build reports a lock taken out of order. The write watch lock is a RankedMutex with the highest rank, LockRank::kLeaf, and it is not recursive. So while a thread holds it, in a listener or under LockWriteWatches, the thread must not:
- Call
AddWriteListener,WatchPhysicalWrites,LockWriteWatchesorCopyToPhysical. - Destroy a listener guard.
- Take another
RankedMutex.
LockWriteWatches takes the lock until the returned ScopeGuard is destroyed. A driver takes it while it changes the state that its listener reads, so no listener runs at the same time.
Host Threads¶
Guest code runs only on threads that the kernel created, because each needs a guest thread state and a guest stack. A driver thread that calls into the game is such a thread. IGuestInterrupts::StartHostThread(name, fn) starts one:
- The function receives a
std::stop_token. - The call returns a
Resultthat holds aScopeGuard. Destroying the guard stops the thread and waits for it to end.
The game registers a graphics interrupt callback, which the GPU calls at each vblank and at some command stream events. RaiseGraphicsInterrupt(source, cpu) runs that callback on the calling thread:
- On a thread that
StartHostThreaddid not start, it returns akRuntimeerror. - It does nothing before the game sets a callback, and nothing while the game is terminating.
- A
cpuof0xFFFFFFFFruns the callback as CPU 2.
Achievements¶
IAchievementRegistry lists the achievements of the game with their unlock state, returns their icons as PNG data, and calls a function on each unlock. Achievement::GetDescription returns description for an unlocked achievement and unachieved_description for a locked one.
Detach¶
When the game exits, the runtime detaches it from the systems, and IGraphicsDriver::DetachTitle runs. Detaching waits until every ScopeGuard from these interfaces is destroyed: the listener guards, the host thread guards and the unlock callback guards. Destroy them in DetachTitle. Memory spans from IPhysicalMemory are invalid after the detach.