Appendix · C11 · Advanced research and implementation
Implementing ray tracing in KytyPS5
Trace the implemented BVH32/BVH64 path from RDNA operands to software intersection on the host GPU, then learn how to investigate unsupported forms and validate complete guest workloads.
Before this page: One draw to GPU completion · Shaders: RDNA 2 → SPIR-V · The program cache. View the learning route.
KytyPS5 at d2413fc2ebd91d1b7234197c2dd5bd48e1cb6a51 (7 October 2026) implements full-float BVH32 and BVH64 node intersections as ordinary SPIR-V shader work. The decoder, IR, BDA memory path and GPU regression fixtures are present. This appendix separates that implemented subset from research proposals, including an optional Vulkan ray-query backend. Source inspection and synthetic fixtures do not establish every console format, hardware numerical behavior or game compatibility.
Read shader translation, C9: program cache, C10: memory and ownership, D3: submission/completion and D4: regression discipline first. This is a multi-stage emulator/compiler project, not a “turn on the Vulkan extension” task. Keep the original raster path working throughout.
1. What the current emulator actually does
Verified in the pinned checkout: BVH instructions pass through normal decoding, translation, compilation and dispatch. The guest shader retains traversal loops, result consumers and ordinary writes around each intersection. The host executes the intersection helper with shader arithmetic and raw guest-memory reads; it does not build a Vulkan acceleration structure for this path.
| Boundary | Current behavior | Reading checkpoint |
|---|---|---|
DecodeMimg in src/graphics/shader/recompiler/frontend/decode/ImageOps.cpp | E6 and E7 share Opcode::IMAGE_BVH_INTERSECT_RAY; opcode_id retains the variant. Ray operands occupy 11 or 12 dwords; results occupy four. | Require full-float ray data, R128 and dmask 0xF. Contiguous and three-dword NSA forms work. A16, D16 and incomplete NSA forms are rejected. |
Decoder::DecodeProgram and TranslateProgram | Continue through instructions after the intersection and build the ordinary CFG/IR. | Read the stores and S_ENDPGM following BVH in TestRayTracingInstructions. |
Translator::EmitMemory in frontend/translate/Memory.cpp | Emits IR::ValueOpcode::BvhIntersect with raw descriptor words, ray operands, EXEC and a one/two-dword node-pointer flag. | The descriptor stays a GPU value. Resource tracking sets uses_dma; this is not an ordinary sampled image or sampler. |
src/graphics/shader/recompiler/backend/spirv/spirvEmitterBvh.cpp | EmitBvhIntersect calls a shared generated helper. Triangle handles triangle selectors 0–3; Boxes handles FP16/FP32 box nodes 4–5. | Follow descriptor validation, bounds, BDA page lookup, packed results and inactive-lane behavior together. |
ProgramCache::Get and renderer/renderCompute.cpp | Prepare a compiled program, BDA resources, bindings and the normal compute dispatch. | BVH programs use the same cache and ordering paths as other supported shaders. |
tests/ShaderRayTracingTests.inc and tests/ShaderRayTracingGpuTests.inc | Decode/IR/SPIR-V checks plus GPU-output fixtures for intersections and local FLAT stack apertures. | Read expected output words, high-pointer and cross-page cases. These are source tests, not a captured game-validation result. |
The tested execution examples are compute shaders. Do not infer comprehensive stage or game support from the shared opcode. Additional node forms, compressed operands, control-flow patterns and numerical cases need their own evidence and tests. The rest of this chapter explains how to extend this concrete implementation and how to evaluate a separate hardware acceleration proposal.
2. The mental model: one instruction is not one scene trace
A ray can be written P(t) = O + tD. O is its origin, D its direction, and a permitted interval limits which t values count. D need not have unit length. A bounding volume hierarchy, or BVH, rejects groups of geometry before testing individual primitives. An axis-aligned bounding box (AABB) is one common bounding volume; triangles are one possible leaf primitive.
Keep four operations distinct: build creates a hierarchy; update/refit changes it after geometry moves; traversal chooses which nodes to visit; intersection tests a ray against a node's bounds or primitives. Shading interprets the accepted result afterward. An implementation may run some of these in shaders and some in special hardware.
Possible guest workload (must be established from evidence)
CPU job / guest command stream
-> geometry or instance update
-> compute builds/refits guest BVH bytes
-> traversal shader:
choose a guest node
execute node-intersection instruction
inspect returned words, update traversal state, repeat
evaluate/filter a candidate and write results
-> denoise / composite / later image consumers
Portable Vulkan RT path
host geometry + instances
-> driver builds opaque host acceleration structures
-> ray query or ray-tracing pipeline traverses host structures
-> host-defined result interface
The difficult boundary is the middle of that diagram. A guest may observe intermediate node pointers, traversal ordering and packed results, not merely a final closest hit. Substituting a whole-scene query for one guest instruction can skip the guest's control flow, candidate filtering and side effects. Even identical final hit distance on a simple triangle does not prove those programs equivalent.
Vulkan distinguishes inline ray queries from dedicated ray-tracing pipelines. Both use implementation-owned acceleration structures. Design inference: neither interface promises access to the guest's internal BVH nodes or a portable way to install arbitrary console BVH bytes. This rules out a generic raw-byte reinterpretation shortcut; it does not rule out a carefully proven higher-level translation. See the Khronos ray-tracing guide.
3. Establish the RDNA 2 contract before inventing an IR opcode
Public AMD ISA: RDNA 2 exposes IMAGE_BVH_INTERSECT_RAY and IMAGE_BVH64_INTERSECT_RAY. Box-node operations test four boxes and return distance-sorted child information; triangle-node operations return triangle-intersection information. Inputs include VGPR ray/node data and an SGPR resource descriptor. The 64 variant widens the node address; A16 reduces direction/inverse-direction components, not every input. Section 8.2.10 and its register-content table are the starting reference, not a complete console software ABI. AMD RDNA 2 ISA guide, document 70648.
| Public instruction form | A16 | Input VGPR dwords | Output VGPR dwords | Resource SGPR dwords |
|---|---|---|---|---|
| BVH, 32-bit node input | 0 | 11 | 4 | 4 |
| BVH, 32-bit node input | 1 | 8 | 4 | 4 |
| BVH64 | 0 | 12 | 4 | 4 |
| BVH64 | 1 | 9 | 4 | 4 |
These are operand payload widths, not machine-code instruction lengths. Cross-check contiguous and non-sequential-address (NSA) forms with the LLVM GFX1030 assembler reference. AMD's public PC documentation is evidence for the instruction family; confirm the console program's actual usage independently. Do not import later-generation BVH8 or other new instruction behavior into RDNA 2 by name similarity.
Write a contract ledger with an explicit unknown column
| Question | Evidence to gather | What a premature guess breaks |
|---|---|---|
| Which bytes encode the opcode and operands? | Raw shader words, aligned instruction boundaries, independent assembly/disassembly and decoder fixtures. | False detection inside literals; wrong next PC; incorrect NSA source registers. |
| What does the resource descriptor mean? | Public fields where specified, SGPR producer chain, descriptor writes and controlled address changes. | Treating an image-like encoding as an ordinary texture; wrong base/stride/type interpretation. |
| What is the node pointer? | Integer operations before the instruction, memory reads by builders/traversal, and byte-range validation. | Confusing a tagged index, relative offset and full guest address. |
| How are nodes packed? | Builder stores and consumer loads from the same versioned allocation, plus geometric consistency. | Wrong bounds, child tags, triangle ordering, padding or instance transforms. |
| How are four result words interpreted? | Trace every live result use: bit masks, integer comparisons, float conversions, stack pushes and branches. | Returning t/u/v/id in a convenient invented layout instead of the guest's encoding. |
| Which numerical cases matter? | Controlled parallel rays, signed zeros, shared edges, ties, packed precision and range endpoints. | Holes, flicker, wrong nearest primitive or nontermination. |
| What happens in inactive lanes? | Guest EXEC behavior, destination merge and surrounding subgroup operations. | Inactive memory accesses, destroyed old register values or incorrect wave cooperation. |
The ledger should distinguish “documented,” “observed,” “inferred” and “unknown.” A TODO is preferable to a fabricated bitfield. A name such as IntersectNode can be useful for design discussion, but it is not proof that the four returned words are ordinary floating-point hit coordinates.
4. Decode the real encoding examples already in the tests
Verified source: DecodeMimg in src/graphics/shader/recompiler/frontend/decode/ImageOps.cpp extracts the opcode and extension length as follows. This small excerpt is about this repository's decoder, not a complete BVH implementation.
opcode = ((word0 >> 18) & 0x7F) | ((word0 & 1) << 7) nsa_dwords = (word0 >> 1) & 3 word_count = 2 + nsa_dwords pc_bytes = word_index * 4
| Words from current regression fixtures | Decoded observation |
|---|---|
F1989F01 00081014 | Opcode E6 = 230; NSA extension count 0; two dwords = eight instruction bytes. |
F19C9F01 00081014 | Opcode E7 = 231; NSA extension count 0. The fixture labels this BVH64. |
F1989F07 00071001 3D3E3C15 38095851 00003A39 | Opcode E6; NSA extension count 3; five dwords = 20 instruction bytes. Extension words carry register selectors, not additional opcodes. |
For word0 = 0xF1989F01: low opcode field = 0x66 = 102 = binary 0110 0110 bit 0 contributes 0x80 = 128 = binary 1000 0000 combined opcode = 0xE6 = 230 = binary 1110 0110 Little-endian memory for that first word: 01 9F 98 F1 Printed dword text is F1989F01, not the byte sequence to paste unchanged.
A parser must first know that this is an instruction boundary. The current regression includes a BVH-looking scalar literal and both contiguous and NSA instructions followed by real stores. Any additional scanner must distinguish literals, operand words, selector storage and unreachable data from instruction starts; add explicit fixtures for each claimed case.
Current decoder policy: E6/E7 are fully decoded and later instructions remain available to the CFG. The public A16 operand widths above describe the ISA, but Kyty currently rejects A16, D16, a missing R128 bit, dmask other than 0xF, and NSA counts of one or two. TestRayTracingInstructions checks complete decoding, a BVH-shaped scalar literal, generated IR/SPIR-V and unsupported encodings; fatal-path tests are conditional on non-Windows builds. Extend malformed-input coverage without weakening the supported-form checks.
5. Build an evidence dossier before patching a game
Research workflow proposal: work with software and captures you are authorized to inspect. This chapter does not require leaked SDK headers, extracted keys, protection bypasses or redistributing a game's executable/assets. Use public specifications, open implementations, original fixtures and permitted local observations. Keep title-specific raw captures private unless you have permission to share them; a minimized synthetic fixture is preferable for a public regression.
Record each conclusion alongside the artifact that supports it. A CPU patch whose bytes and branch target were validated is still not proof that RT was disabled safely, much less implemented. Avoid broad renderer flags: they may select allocation, lighting, streaming and synchronization paths together. “The crash moved” is useful triage evidence, not a compatibility result.
Study identity emulator full SHA, branch, compiler, executable + matching symbols title/version and executable SHA-256 (local record) host OS, GPU, driver, enabled features, configuration exact scene/save/camera/frame route and warm/cold cache state Dispatch identity guest queue + submit ID + PM4 offset shader hash + original code bytes + stage + wave size dispatch dimensions + user-data register words BVH instruction byte-PC + raw words + operand register map Data provenance guest allocation identity + generation + valid mapped extent descriptor/root-pointer producer and current values last CPU/GPU writer + ordering point captured node/geometry/output byte ranges and capture completion tick Claim observed fact / hypothesis / competing explanation smallest experiment that distinguishes them expected result / actual result / confidence / remaining unknowns
Keep shader-relative PCs, CPU guest addresses, host PCs, file offsets and GPU buffer offsets in separate fields. “Address 0x100” without its coordinate system is not reproducible evidence. Hashing bytes establishes identity, not correctness; do not confuse the two.
6. Capture one bounded workload, not every frame's memory
6.1 Establish the earliest observable boundary
Start with the normal compile and dispatch path. Original shader dumping occurs before translation in pipelineCache.cpp; decoded and IR dumps follow their phases in ShaderRecompiler.cpp. Find BvhIntersect in the IR and bvh_intersect/get_bda_pointer in emitted SPIR-V. An unsupported encoding can still fail before a complete dump is produced. With --graphics-debug-dump true, direct-dispatch diagnostics include the shader hash and recording tick.
These current CLI names can collect useful evidence. This Windows PowerShell example is for an authorized manual run from the emulator root using an existing compatible build. Replace the executable/content paths, inspect the current --help, and run with deliberate scope. The chapter does not execute it for you.
.\_Build\windows\kyty_emulator.exe --game "D:\Games\Example\eboot.bin" --redzone --printf-direction File --printf-output-file _Build/rt-study.log --shader-log-direction File --shader-log-folder _Build/rt-shaders --command-buffer-dump true --command-buffer-dump-folder _Build/rt-pm4 --graphics-debug-dump true --vulkan-validation true --shader-validation true
Diagnostic output can be large and timing-sensitive. Use a fresh output location, a short route and bounded logging. Keep validation enabled during correctness work. Do not benchmark this diagnostic run. The actual screenshot and route matter: a live process and an advancing title-bar counter do not prove an RT image exists.
6.2 Add narrowly targeted instrumentation when existing logs stop too early
- Select a shader and submission. Record hash plus original bytes; a hash collision or shader relocation must not attach the wrong capture. Capture a small number of calls, not an unbounded per-lane stream.
- Capture state before dispatch. Save raw compute registers, user data, dimensions and referenced descriptor bytes. BVH descriptor operands stay in the shader; an empty image list does not mean the dispatch has no indirect node-memory reads.
- Capture GPU-produced inputs coherently. Find the actual producer and complete the required copy/readback before interpreting a CPU snapshot. An unchanged CPU mirror is not proof that the GPU did not update the BVH.
- Trace one invocation through the existing helper. Restrict a debug buffer by shader, dispatch, workgroup, lane and instruction PC; record input/result words and a bounded number of node visits. Keep a dropped-record counter.
- Capture consumers. Record which buffer/image fields the next shader reads. A plausible intersection tuple is insufficient if the output is packed or indexed incorrectly.
The software helper is an implementation to validate, not an independent hardware oracle. Reference answers must come from a lawful reference execution, a documented semantic case, or independently calculated fixtures with explicitly limited claims. Repeating the same unproven formula on CPU and GPU only proves agreement between two copies of that formula.
6.3 Know which layer each tool can see
- Ghidra / a CPU debugger: guest x86-64 orchestration, library calls, allocation lifetimes, argument construction and command generation. GPU machine code stored in a data blob must not be analyzed as x86.
- RDNA-aware assembler/disassembler: instruction boundaries and register syntax. Pin architecture and tool version; “it disassembles” is not proof of console-specific node layout.
- Vulkan validation / graphics captures: emitted host resources and commands. The current software path appears as ordinary shader work; a capture need not contain native ray-query commands, and validation cannot certify guest semantics.
- Radeon Raytracing Analyzer: useful for supported host Vulkan/DX12 RT captures and acceleration structures. Its capture path is a host-driver facility, not a decoder for arbitrary console dumps. Check its version, GPU/API and capture restrictions. See AMD RRA and its capture workflow.
7. Reverse-engineer the game's CPU-side setup in Ghidra
This is a repeatable investigation method, not an assertion about one game's engine. The aim is to connect who allocates and writes data to the shader that consumes it. Useful strings and function names are leads; they are not contracts.
- Confirm the exact program. Record executable hash, title version, module and loader base. Validate the mapping of a known instruction against the bytes in your run. Use D2's ELF address worksheet to translate runtime addresses to module-relative addresses and file offsets.
- Separate guest and host ABIs. Interpret guest call sites using the actual SysV-style boundary; do not infer parameters from Windows host-register conventions. Correct a function signature only when multiple call sites and data uses support it. Record guessed types as guessed.
- Find an anchor. Start at an observed command-buffer submission, descriptor write, allocation or shader-creation call. Strings containing “ray,” “shadow,” “reflection” or “BVH” can help, but a missing string means little in stripped or optimized code.
- Walk backward from the submitted descriptor/root. Identify the object containing it, constructor/allocation path, relevant fields, setters and per-frame update. Track indirect calls and job-system payloads, not only the immediate caller.
- Walk forward from the builder inputs. Follow geometry addresses, index format/stride, counts, transforms and output/scratch ranges into command generation. Check whether a generic compute dispatch—not a named RT function—does the build.
- Identify consumers and cleanup. Trace hit/output buffers through filtering/compositing and find when allocations are recycled. Record synchronization objects and who owns each lifetime transition.
- Validate one field at a time. Change a controlled scene variable or use a minimized harness, then compare the predicted field/range. Correlation across one frame is not enough for a layout definition.
A useful function note is more than a decompiler screenshot
candidate function: descriptive local name, original address and module callers: direct, indirect, worker-job entry arguments: register/stack origin, widths, pointer ownership fields read/written: offset, width, value range, evidence outputs: allocation/range, descriptor, command words synchronization: producer completion, consumer wait, reuse point confidence: confirmed / inferred / unknown counterexample: condition that would disprove the current interpretation
For example, a 64-bit field copied into a shader user-data slot might be a descriptor-table address, a BVH base, a scratch pointer or a pointer to another pointer. Follow the GPU consumer before naming it blas_address. Likewise, an allocation size that scales with triangle count suggests a relationship but does not establish its binary layout.
A per-game hook is a possible research tool, but a fragile long-term emulator interface. If later recognizing a builder or traversal routine, use verified version/shape checks and a conservative fallback. Never identify a routine solely by a nearby string or a fixed RVA copied from another version.
8. Reverse-engineer the GPU shader around the BVH instruction
Start from the original shader bytes, stage metadata and register snapshot for the selected dispatch. Build the control-flow graph without discarding the tail after the first BVH operation. Keep a raw-word listing alongside the disassembly, including literal and NSA words, so instruction offsets remain auditable.
8.1 Backward slice: where did each input come from?
For every source VGPR and descriptor SGPR, walk definitions backward through copies, scalar/vector loads, address arithmetic, select/phi nodes, lane reads and function calls. Note whether a value is uniform for the wave, per-lane, or only uniform on one path. The initial inputs may come from user data, an SRT walk, an ordinary buffer or a global-memory address.
- Separate ray construction from node selection. Camera rays, shadow rays and secondary rays may all use the same traversal code with different inputs.
- Record the ray interval and space. Is the origin already transformed? Is the direction unnormalized? Is an inverse direction supplied rather than computed at the instruction?
- Track how the node pointer is tagged, masked or shifted. Keep integer arithmetic as integer arithmetic; a decompiler's convenient pointer cast can conceal encoding.
- Recover NSA register order explicitly. A contiguous register-range assumption can silently mix unrelated live values into the ray.
- Identify stack/scratch and EXEC/VCC use. If traversal is wave-cooperative, replacing it with independent scalar work may change behavior.
8.2 Forward slice: what is observable?
Follow all four destination words until they are dead, stored, used as an address or consumed by a branch. Look for distance comparisons, invalid-child tests, stack updates, triangle-index extraction and barycentric interpolation. Label bitcasts separately from numeric conversions. A word whose bits encode a pointer must not be treated as a floating-point distance because it happens to occupy a VGPR.
Determine which behavior belongs to the instruction and which belongs to surrounding shader code: traversal order, nearest-hit selection, instance transforms, mask tests, opacity/alpha rejection and procedural intersection handling may occur at different levels. Implementing both the instruction's effect and a duplicated host-side version of the shader's filtering can be just as wrong as omitting filtering.
8.3 Expect compiler work beyond the two BVH opcodes
Calls/returns, indirect branches, stack operations, LDS, atomics and ordinary memory instructions around BVH must work too. FlatStackApertures tests wave32/wave64 private/shared aperture aliasing, inactive lanes and invalid offsets. Those cases cover specific memory behavior; they do not prove every traversal shader or control-flow shape.
Minimize by dataflow, not by deleting everything that looks unrelated. Preserve definitions feeding the BVH op, result consumers, active-lane masks and required memory/synchronization. Keep one original failing program as a local integration case, then derive a small redistributable shader fixture whose expected outputs can be explained without the whole game.
9. Recover guest BVH layouts from producers and consumers
Unknown until established: do not publish a “PS5 BVH struct” assembled from plausible fields. Public AMD GPURT is useful comparison material, but its versions, hardware-generation branches and client conventions are not proof of a console engine's format. The GPURT repository and its intersection helpers illustrate explicit pointer decoding and versioned traversal machinery. Pin the revision used for research and retain licensing/provenance for any reused code.
9.1 Read the layout the current helper implements
The following is a reading of DefineBvhIntersect, not a claim that every console builder uses this contract. Let d[0..3] be the descriptor and node the one- or two-dword unsigned node value:
kind = node & 7 index = node >> 3 base = (uint64(d[0]) | (uint64(d[1] & 0xff) << 32)) << 8 last_index = index + (kind == 5 ? 1 : 0) index_limit = uint64(d[2]) | (uint64(d[3] & 0x3ff) << 32) node_address = base + (index << 6) descriptor type: d[3] >> 28 == 8 barycentric results: d[3] bit 24 sort box children: d[1] bit 31 box exit growth: (d[1] >> 23) & 0xff
Kinds 0–3 choose one of four triangles in a 64-byte block of five float3 vertices and a flag word. Kind 4 has four child words and packed FP16 bounds in one block; kind 5 uses FP32 bounds across two blocks. The helper requires last_index <= index_limit, an allowed kind and a last block address below 248, then translates each required block. BVH64 preserves the high node-index bits; narrowing to 32 bits changes the address.
BvhIntersections builds both box forms, triangle selectors, sorted/unsorted results, barycentric/ID modes, growth, high node pointers, invalid values and a cross-page FP32 node. Use those concrete layouts as the baseline to compare with a captured producer; keep unknown builder metadata and additional formats explicit.
9.2 A field-recovery method
- Locate a coherent root and owned range. Record allocation generation, base, extent, writer and capture tick. Validate integer addition and bounds before dereferencing candidate pointers.
- Find the producer. Disassemble the node builder's stores or CPU serializer. Store width, offset and value expression constrain the candidate layout more strongly than a float-looking hexdump.
- Match the consumer. Verify that traversal reads the same bytes and interprets the same tag/offset. A buffer may contain a header, nodes, sideband IDs and scratch; not all bytes belong to nodes.
- Use controlled geometry. One triangle, two separated triangles, one transformed instance and a changed count isolate different fields. Compare multiple captures with one changed input.
- Check geometry invariants. Bounds should enclose the corresponding primitive in the correct space. Children should resolve into valid owned ranges. IDs should map consistently to source geometry. These are filters for hypotheses, not standalone proof.
- Test representation variants. Empty trees, internal nodes, leaves, instances, compressed bounds, copies, refits and serialization can have different layouts or metadata. Keep format versions explicit.
For each field, retain byte offset, width, endianness, signedness/float representation, producer expression, consumer expression, observed samples and unresolved bits. When two hypotheses fit, write a distinguishing experiment rather than picking the simpler struct.
9.3 A toy pointer example—deliberately not RDNA encoding
Suppose an invented fixture uses two low bits as a tag, the remaining bits as a 64-byte node index, and a separately supplied allocation base. This example teaches address-domain discipline; none of these bit positions or units is claimed for PS5.
packed = 0x49 = 73 = binary 0100 1001 tag = packed & 3 = 1 index = packed >> 2 = 18 = 0x12 offset = index * 64 = 1152 = 0x480 base = 0x100000000 guest node address = 0x100000480 Required before a 64-byte read: offset <= allocation_size 64 <= allocation_size - offset base + offset does not overflow the address type allocation generation and ownership still match
A real format might instead store a relative byte offset, a shifted address, a different tag or extra high bits. Replace this toy formula only when evidence supports the actual one. A guest node address, a host buffer device address and a Vulkan acceleration-structure address are distinct domains even if all three are represented by 64-bit integers.
10. Build a small reference oracle for the GPU backend
Independent validation artifact: a standalone CPU reference with a documented input/output contract and no implicit access to a running process. Start with mathematical ray/box and ray/triangle cases, then add a separately tested guest-layout reader and result packer. Keep the separation visible: correct geometry math does not validate pointer decoding or the guest's packed return format.
10.1 Ray/box intervals, including parallel axes
For a finite, nonzero direction component, intersect the ray with the axis slab by calculating the two t values for its minimum/maximum planes, sorting them and intersecting that interval with the current ray interval. If the direction component is zero, the axis either imposes no extra bound (origin inside the slab) or rejects the ray (origin outside). This explicit branch avoids a toy implementation's accidental 0 × infinity NaN.
Illustrative mathematical reference, NOT hardware-accurate RDNA pseudocode:
lo = ray.t_min
hi = ray.t_max
for axis in x, y, z:
if direction[axis] == 0:
if origin[axis] < box.min[axis] or origin[axis] > box.max[axis]:
return MISS
continue
a = (box.min[axis] - origin[axis]) / direction[axis]
b = (box.max[axis] - origin[axis]) / direction[axis]
lo = max(lo, min(a, b))
hi = min(hi, max(a, b))
if lo > hi:
return MISS
return INTERVAL(lo, hi)
Fixture: O = (0,0,0), D = (0,0,2), box minimum (−1,−1,2), maximum (1,1,4), t interval [0,10]. The intersection interval is [1,2], not [2,4]. Normalizing D without changing t would change the program's result. With O.x = 2 and D.x = 0, the ray misses the x slab. With the origin inside the box, negative entry distances must be clipped according to the fixture's ray interval.
This reference intentionally uses a simple finite-input policy and explicit inclusive endpoints. The guest instruction's precise treatment of signed zero, denormals, NaNs, infinity, near/far boundaries, reciprocals and ties needs separate evidence. Do not add an arbitrary epsilon to make a regression pass. Keep intermediate bit patterns where precision or packing matters.
10.2 A triangle case you can solve by hand
Take A = (0,0,0), B = (1,0,0), C = (0,1,0), O = (0.25,0.25,1), D = (0,0,−1). In a simple two-sided mathematical test the hit is t = 1, u = 0.25, v = 0.25, with weight 1 − u − v = 0.5 on A. An origin at (0.75,0.75,1) misses because u + v > 1. These are original teaching fixtures, not captured hardware results.
Develop separate cases for front/back facing, zero-area triangles, shared edges, very thin geometry, equal-distance overlaps and non-unit directions. A conventional floating-point triangle routine is a starting oracle for uncomplicated inputs, not a substitute for measured edge behavior or the guest's result encoding. For host Vulkan traversal, candidate ordering is not generally fixed and watertightness constraints matter; see the Vulkan traversal specification.
10.3 Preserve t across instance transforms
For an invertible affine world-to-object transform, transform O as a point and D as a vector. Then M(O + tD) = M(O) + t M_linear(D). The same parameter t remains meaningful if the transformed direction is not silently renormalized. Handle normal transformation separately from ray direction. Negative scale may change facing; singular transforms need an explicit contract, not division by zero.
Example: doubling the object-space z coordinate changes the previous ray to O.z = 2 and D.z = −2; its intersection with z = 0 still has t = 1. Renormalizing D to −1 would report t = 2. This is a useful regression before discussing packed hardware nodes.
10.4 The result packer needs its own tests
Use a semantic CPU result such as {hit, t, u, v, primitive_id} only inside the reference tool. Add a distinct conversion to the observed four-word guest result when that encoding is established. Test pointer/tag bits, sentinels, sorting/ties, integer IDs and float bitcasts independently. Preserve inactive destination registers rather than filling every lane with zeros. Missing nodes and unsupported formats should report an explicit diagnostic in the research harness, not silently masquerade as geometric misses.
11. Choose the boundary you can implement faithfully
| Strategy | What it preserves / requires | Recommended role |
|---|---|---|
| Software lowering of individual guest BVH operations | Runs inside the translated shader, reads guest-format nodes and returns the instruction's results. Requires the node/descriptor contract and numerical behavior; can preserve guest traversal and surrounding side effects. | The implemented backend for the supported subset. GPU ALU work, not a CPU callback per ray. |
| Recognize a complete traversal operation and use a host ray query | Requires recoverable geometry/instances and a proven mapping of all observable results, filtering and side effects. Cannot generally expose guest internal-node traversal order. | Optional optimized path after equivalence tests. Recognition must be conservative and versioned. |
| Replace a higher-level guest RT pipeline | Requires a verified guest API/runtime boundary, callable shader behavior, payloads, scheduling and geometry mapping. A compiled compute shader may not retain such a boundary. | Separate major project; do not assume every game is using an interceptable DXR-style API. |
| Skip dispatch, force misses or disable game RT | Changes guest work or selects a different workload. Can hide a fault while leaving results missing. | Explicit compatibility workaround or controlled experiment only; never an implementation claim. |
Current architecture: software lowering preserves the guest instruction boundary. Use that implementation as the starting point for coverage work, while retaining independent oracles for numerical and layout validation. A hardware path is a separate optimization proposal whose equivalence must be established.
A possible hybrid accelerates known operations while retaining the software implementation for unrecognized forms. The host RT feature bit selects an available mechanism, not proof of equivalence. Never let the optimized path accept a workload merely because the GPU advertises ray queries. If unsupported state is encountered, use a verified fallback or produce an explicit unsupported result; do not mix partial old and new outputs.
12. Follow and extend the actual compiler pipeline
12.1 Decode and control flow
frontend/decode/ImageOps.cpp maps E6/E7 to one semantic opcode and retains opcode_id to choose 11 or 12 address dwords. ShaderDecoder.cpp continues decoding the complete program. The full-float/R128/dmask/NSA checks are part of the supported contract, not optional validation to remove when a new shader fails. Follow failures through frontend/cfg and frontend/translate rather than emitting an empty shader.
12.2 The implemented IR interface
This excerpt comes from Translator::EmitMemory in src/graphics/shader/recompiler/frontend/translate/Memory.cpp. Both variants return four raw words; the instruction flag records the node-pointer width.
const auto result = ir.Emit(IR::ValueOpcode::BvhIntersect,
{ConstructU32x4(inst.src1, 4), MakeImageAddress(inst, inst.src0), ir.GetExec()},
inst.image_address_components - 10u);
for (uint32_t component = 0; component < 4; ++component) {
WriteOperand(OffsetOperand(inst.dst, component),
ir.Emit(IR::ValueOpcode::CompositeExtractU32x4, {result, IR::Value(component)}));
}
The descriptor is a four-dword GPU value. ResourceTracking.cpp marks BvhIntersect as using DMA rather than inventing a sampled-image resource. Value.cpp includes it in memory-effect handling. Trace WriteOperand and EXEC handling with the emitter: inactive lanes must preserve their destination registers and must not perform invalid node loads. Overlapping input/output registers and writes between intersections deserve independent regressions.
12.3 Software SPIR-V lowering
backend/spirv/spirvEmitterBvh.cpp supplies EmitBvhIntersect and DefineBvhIntersect. The emitter builds one shared function for the module. It validates descriptor type, node kind, index bounds, the 48-bit address limit and ray values, then resolves node blocks through GetBdaPointer. FP32 box nodes need two separately translated 64-byte blocks, which matters when the node crosses guest pages with noncontiguous host backing.
Triangle selects vertices for triangle kinds 0–3, performs a dominant-axis projection with explicit non-contracted arithmetic and packs either barycentric numerators or triangle identity/acceptance data. Boxes unpacks FP16 or FP32 bounds, evaluates the four children and applies the descriptor sorting/growth policy. Read these functions and BvhIntersections together before changing precision, tie handling, sentinels or packing.
The full helper call is guarded by EXEC. Invalid descriptor/node/ray inputs and unavailable translated blocks use the helper’s defined invalid-result words; this behavior must be checked against guest evidence before extending it to new formats. A structurally valid SPIR-V module does not establish numerical equivalence to console hardware.
12.4 Extend the supported contract through all callers
There is no BVH-specific empty-program return in the current cache or compute dispatcher. A supported program reaches the normal module, binding and dispatch path. An extension must update decoding, operand construction, IR effects, resource requirements, emission and tests together. If future selectable backends change lowering, distinguish their program/pipeline compatibility or require rebuilding the relevant cache state.
13. Follow descriptors, memory and output tracking
BVH nodes are read through the shared raw guest-memory path. The shader still owns its traversal and produces ordinary output-buffer writes. A CPU descriptor snapshot alone cannot describe every node that the GPU will follow.
| Integration area | Current responsibility and extension checkpoint |
|---|---|
ir/passes/ResourceTracking.cpp | BvhIntersect sets uses_dma. Preserve GPU descriptor operands rather than classifying the BVH as an ordinary texture. |
ir/passes/SrtWalker.cpp and ResourceMaterialization.cpp | Refresh the surrounding program’s descriptor/SRT dependencies. GPU-native values must remain GPU-native where the plan supports them. |
ir/passes/BindingLayout.cpp | Include BDA page-table/fault bindings when required; keep ordinary buffers, shader data, GDS and optional shared-memory storage consistent. |
renderer/pipeline/descriptors.cpp | Prepare ordinary input/output resources, synchronize dependencies and commit bindings using the current command buffer. |
renderer/renderContext.cpp, renderer/cache/bufferCache.cpp, pageManager.cpp | Prepare BDA mappings and honor tracked ownership, aliasing, dirty ranges and completion lifetimes. |
renderer/renderCompute.cpp | Call PrepareBda when program.info.uses_dma, preserve dispatch dimensions and apply shader-write hazard barriers. |
The first three rows are under src/graphics/shader/recompiler/; renderer/page-manager paths are under src/graphics/host_gpu/. FLAT private/shared apertures also matter to traversal stacks: inspect spirvEmitterMemory.cpp and FlatStackApertures for their aliasing and bounds behavior.
For any future cached host-BVH representation, track allocation identity, content changes and GPU lifetime explicitly. The existing sparse-upload rule—zero-fill owned holes—does not by itself establish the right result for a missing BVH node. Validate residency/fault behavior and downstream output rather than treating a quiet fault path as proof of a correct trace.
14. Add a Vulkan hardware path only behind a proven translation boundary
14.1 Negotiate capabilities; do not assume Vulkan 1.3 implies RT
The current src/graphics/presentation/window/vulkanWindow.cpp already deals with buffer device addresses, but this is not ray-query support. A proposed query backend must enumerate the needed extensions/dependencies for the chosen API version, query feature structures and enable only supported features at device creation. Check accelerationStructure and rayQuery separately, and read the relevant properties/limits. Dedicated RT pipelines add a different feature path and are not required merely to use inline queries. See ray-query features and acceleration-structure features.
Design at least three visible outcomes: verified software backend available; verified hardware backend available for this workload; unsupported. Log the reason for refusal. Do not silently downgrade to the old skipped-dispatch workaround and present it as successful RT. Any future configuration name in the implementation plan is a proposal; this appendix does not introduce an existing --ray-tracing CLI option.
14.2 Recover build inputs, not opaque bytes
For a higher-level optimization, obtain geometry, indices, per-geometry metadata, instance transforms and flags from a verified producer or a validated guest-layout decoder. Preserve guest-visible primitive and instance identity through a mapping table. Host build order, compacted offsets and internal node IDs are not guest IDs.
Host acceleration structures are opaque. Copying guest BVH bytes into a buffer with the correct usage flag does not build one. Serialization/capture-replay facilities likewise do not establish compatibility with a console format. Even a valid host AS address need not equal its backing buffer's device address plus offset. Keep separate typed handles/addresses and query the correct value. See acceleration structures and AS device-address rules.
14.3 A host build checklist
- Construct the supported triangle/AABB geometry description and, separately, instance data. Verify vertex/index formats, counts, strides, transforms and range bounds.
- Query build sizes for that geometry and chosen build/update mode. Allocate appropriately flagged backing and scratch buffers using the queried requirements, not guessed per-triangle sizes.
- Create the host AS object and record its lifetime dependencies. Apply the device's scratch-address alignment requirement; a buffer allocation's base alignment does not justify arbitrary scratch suballocation offsets.
- Record the build, establish the required dependency before building a TLAS that references the BLAS, and then establish the dependency before traversal.
- Keep build inputs and scratch alive through their GPU use. Keep referenced BLAS objects alive for TLAS use, including later queries. Reuse or retire only after the relevant last-use completion.
- For refit/update, validate the API's update preconditions and supported geometry changes. Rebuild when they do not hold. For compaction/copy, update address mappings and dependent instances with correct ordering.
This is a design checklist, not copy-ready Vulkan code. Use the exact valid-usage and access rules for your selected build path. In particular, a multi-build call does not implicitly order its builds. See build-command requirements and queried limits/alignment.
14.4 Translate a proven whole-query contract
SPIR-V ray queries have an object lifecycle: initialize, proceed, inspect/confirm or generate permitted candidate intersections, then inspect the committed result. An acceleration-structure address conversion accepts a valid host AS address, not an arbitrary guest pointer. The exact instruction, operand and capability rules are in SPV_KHR_ray_query.
Conceptual host-query path; not a direct translation of a single BVH opcode:
resolve verified guest scene/version -> host AS + ID mapping
initialize query with equivalent origin, direction, interval and flags
while query can proceed:
apply required candidate handling at the correct semantic boundary
convert committed result into the proven guest-visible whole-query result
execute remaining guest-visible side effects
Prove what happens for misses, opaque versus non-opaque candidates, alpha-tested geometry, procedural AABBs, instance masks, facing, negative transforms and termination behavior. If guest code observes internal node IDs or relies on intermediate traversal results, this whole-query substitution is not justified. Candidate order differences can affect side-effectful filters even when the final closest hit is similar. Keep the instruction-level fallback.
Do not map host primitive indices directly to guest packed node pointers. Preserve an explicit translation to the guest identity domain, and prove the relevant result fields. A hardcoded “triangle 0” is useful in the first fixture only; it is not an acceptable general mapping.
15. Synchronization, residency and replay are part of correctness
Design invariant: a ray must see the intended version of its geometry/BVH, and its consumers must see the intended output version. A correct intersection routine on stale bytes is still an emulator bug. Separate shader-to-shader ordering, host readback visibility, queue ownership and resource retirement; one completion counter does not express all four.
15.1 Write the dependency chain for each backend
Guest-format software path: producer writes geometry/nodes -> make relevant writes visible to the translated traversal's shader reads -> traversal writes hit/output resources -> make those writes visible to filtering/compositing consumers Host acceleration-structure path: geometry/instance producer -> host AS build reads its inputs -> BLAS build dependency before dependent TLAS build -> build writes visible to the actual query/ray-tracing shader stage -> hit/output writes visible to later consumers
For ordinary storage buffers, derive masks from the real producer and consumer stages/accesses, not a universal “RT barrier.” A compute shader using a ray query is still a compute-stage consumer, not automatically a ray-tracing-pipeline-stage consumer. Image consumers also require appropriate subresource layout handling. Across queues, include semaphore ordering and ownership transfers where required. Use the Khronos synchronization examples alongside the AS build command's specific access rules.
Do not hide a missing dependency with an unconditional device-idle wait and call the feature finished. Such a wait can help a bounded diagnostic experiment, but it changes scheduling and may conceal both performance and lifetime bugs. Replace it with a justified dependency before claiming a production implementation.
15.2 Respect the current scheduler's preparation boundary
Verified source: CommandScheduler::Wait can submit the current work and begin another command buffer. The wrapper can remain stable while its Vulkan handle changes. PopPendingOperations observes the master timeline before running eligible deferred operations. Any new RT preparation that reads back metadata, allocates streaming storage or waits must fit those existing rules.
Acquire the current Vulkan handle after operations that may restart the scheduler; retain resources referenced by recorded commands until their last GPU use completes. New AS builds and scratch allocations must not be destroyed when a temporary C++ preparation object leaves scope. Read D3's completion trace and reproduce the lifetime lab before extending this layer.
15.3 Do not turn a residency retry into duplicate side effects
Proposed correctness requirement: if a backend discovers an unavailable page after some shader work has executed, blindly replaying the dispatch can duplicate atomics, append-buffer writes or other side effects. Simply suppressing a GPU fault or marking it handled cannot establish correct output.
Prefer making required backing resident before the supported operation. If the design needs replay, define a transaction: identify all externally visible writes, snapshot or otherwise make them rollback-safe, populate backing, restore the pre-attempt state, refresh aliases/bindings and retry under a bounded policy. If the complete side-effect set cannot be identified—including global data and counters—do not advertise that workload as safely replayable. This is an implementation requirement, not a claim that the existing fault manager already provides that transaction.
Keep invalid addresses distinct from valid-but-unavailable backing. Unknown node formats, out-of-range pointers and corrupted graphs should not all be routed through an infinite “retry and zero” loop. Use bounded traversal and diagnostics in the research harness; a debug step limit must report failure, not quietly convert an unfinished traversal into a miss.
16. Cache identity must include content and lifetime, not only an address
Proposed model: track separate code identity, descriptor/specialization identity, guest allocation generation, content version and host-backend representation. The exact data structures should fit the existing caches, but the following mistakes must be impossible.
| Event | What must be reconsidered |
|---|---|
| A guest allocation is freed and another uses the same address. | Generation changes; an old BVH/host-AS mapping must not be reused merely because the base address matches. |
| GPU builder refits nodes in place. | Content/version dependencies change even if CPU dirty tracking did not observe a write. |
| Geometry stays fixed but instance transforms or masks change. | Dependent TLAS/query state may be stale while BLAS geometry remains reusable. |
| A future software/hardware backend selection changes. | Program/pipeline compatibility needs distinct keys or invalidation if lowering changes. |
| A descriptor changes while the shader code is identical. | Materialized resources and specialization must be refreshed; code hash alone is insufficient. |
| AS compaction/copy changes the host representation/address. | Dependent references, ID maps, lifetime dependencies and already queued users must be tracked. |
Illustrative identity, not an existing KytyPS5 struct:
GuestBvhIdentity {
allocation_id, allocation_generation,
root_offset, format_version,
content_version, geometry_version, instance_version
}
BackendCompatibility {
backend_kind, lowering_version,
required_host_features, precision_policy
}
Do not put every dynamic byte in the shader pipeline key: that can create unbounded recompilation without fixing coherency. Separate “must recompile” from “must rebind,” “must update/rebuild host AS” and “must wait.” Likewise, a CPU hash of stale node bytes is not a valid content-version tracker for GPU-written memory.
At this snapshot the program cache retains resource-plan analysis and mutable snapshot storage; new permutations retranslate temporary IR. New RT metadata must respect that ownership. Borrowed stage pointers are not independently owned immutable copies. If retained across asynchronous work, prove their lifetime or copy a stable representation deliberately.
17. A validation ladder that can catch believable wrong answers
17.1 Use multiple independent oracles
| Layer | Required cases | What passing establishes |
|---|---|---|
| Decode | E6/E7, contiguous/NSA, A16 variants, literals that look like opcodes, branch tails, malformed/truncated buffers, stage policy. | Correct identification, operand order and instruction boundaries—not intersection. |
| Layout | Node kinds, base/offset units, bounds, high address bits, generation reuse, headers/sideband data, endianness and unsupported tags. | Correct reading of the supported format—not whole traversal. |
| Primitive math | Hit/miss, inside box, parallel axes, signed zero, non-unit direction, near/far endpoints, triangle edges/degeneracy and ties. | Agreement with the stated numerical oracle on these inputs. |
| Guest result words | All four output words, child ordering, miss sentinels, IDs, bitcasts, inactive lanes and overlapping source/destination registers. | The guest-visible instruction result, not just a visually similar hit. |
| Shader execution | Traversal loops, calls, EXEC divergence, multiple lanes/waves, ordinary writes/atomics and output consumers. | The supported reduced program executes rather than being skipped. |
| Runtime memory | GPU-only node writes, aliases, sparse/unavailable backing, unmap/reuse, scratch wrap, refit/copy and in-flight destruction. | Version, visibility and lifetime invariants for these schedules. |
| Hardware optimization | Software/reference versus host-query results, flags, ID mappings, masks, instances, non-opaque/procedural handling and unsupported fallback. | Equivalence for the recognized workload subset. |
| Game integration | Repeatable scene, RT-dependent output inspection, camera/animation changes, reload and extended use. | Evidence for that title/version/route—not universal support. |
Use exact comparisons for integer fields and address/ID mappings. Choose floating-point acceptance criteria from the contract and numerical analysis; do not apply one loose tolerance to every output. Keep both deterministic hand-solvable cases and difficult boundary cases. An image-difference threshold alone can hide a broken low-energy effect; inspect intermediate hit/mask buffers as well.
17.2 Runnable teaching checks included with this appendix
raytracing-reference.cjs checks the displayed opcode arithmetic, the toy packed-pointer formula, and simple finite-input ray/box, ray/triangle and transform examples. It deliberately implements neither a complete RDNA decoder nor a console BVH layout. JavaScript's binary64 arithmetic is not a model of the instruction's floating-point precision. No games, Vulkan or GPU execution are used.
From the learning repository: node docs/labs/raytracing-reference.cjs
The script fails on an unexpected result and prints its assertion count on success. Treat it as a check that the lesson's worked examples are internally consistent. Hardware/guest behavior still needs the independent evidence described above. Add a counterexample to a tempting incorrect optimization—for example, direction normalization without t compensation.
17.3 Extend the existing upstream tests deliberately
From the emulator root, after configuring a compatible full test build with its dependencies, these are existing target and CTest names at the pinned revision:
cmake --build _Build/windows --target shader_cfg_tests resource_tracking_tests shader_recompiler_compute_tests ctest --test-dir _Build/windows -R "^(shader_cfg|resource_tracking|shader_recompiler_compute)$" --output-on-failure
These commands validate the current implementation and future changes; this documentation task does not claim to have run the full emulator tests. The compute executable includes host-GPU coverage and needs its real environment. ShaderRayTracingTests.inc belongs to shader_cfg; ShaderRayTracingGpuTests.inc is included by the compute harness and defines BvhIntersections and FlatStackApertures. There is no separately registered raytracing_tests target. Do not invent a GoogleTest filter for these executables.
Add instruction-execution fixtures near the shader tests, resource-provenance cases near the resource tests and real GPU-output/lifetime cases in the compute harness. Preserve existing non-RT tests and rasterization scenes. A future backend should pass both a device with the required RT capabilities and its explicit unsupported/software path; never use a skipped test as evidence that the feature works there.
18. Diagnose failures at the first wrong boundary
| Observation | Investigate first | Do not conclude |
|---|---|---|
| A BVH-containing program fails to compile. | Truncated decoding, unsupported calls/control flow, missing operand/resource semantics, invalid IR or backend capabilities. | That the Vulkan driver is the original cause. |
| Every ray misses. | Whether dispatch ran; root/descriptor mapping; coordinate space; interval; masks; stale/empty build; packed result interpretation. | That increasing tMax or forcing success is a fix. |
| Hits appear on the wrong object. | Guest-to-host instance/primitive mapping, index offsets, transforms and reused allocations. | That visually nearby geometry is equivalent. |
| Static scenes work, animation fails. | GPU-written updates, refit/rebuild policy, instance dependencies, content versions and synchronization. | That the intersection equation alone is wrong. |
| Edge flicker or light leaks. | Precision, shared-edge rules, t interval, facing, transform consistency and candidate rejection. | That an arbitrary epsilon is harmless. |
| Failure appears only without a forced wait. | Producer/consumer dependency, scheduler restart, resource lifetime and cross-queue ordering. | That permanent full-device serialization is a correct final design. |
| A page retry changes counters or output twice. | Partial side effects, rollback completeness, stale aliases and replay eligibility. | That suppressing the fault preserves behavior. |
| Host capture has no RT commands. | Software lowering versus a proposed host-query backend; inspect ordinary compute work and emitted helpers. | That no guest RT-related work exists; software lowering may use ordinary compute. |
Measure cost only after correctness
Record build/refit time, shader compilation, traversal time, upload/readback volume, forced waits, node visits, rays, cache hits, AS memory, scratch high-water mark and deferred-resource backlog. Normalize comparisons by the same scene, ray count and settings. Separate cold compilation from warm runtime; count CPU time and GPU time independently.
For the software path, investigate node fetch locality, divergence, register pressure, spills and repeated descriptor work. For a hardware path, measure conversion and AS build cost as well as query cost. A faster query can still make the whole frame slower if geometry conversion/readback dominates. Use existing --profile instrumentation deliberately; do not infer success from average FPS while the effect is disabled or incorrect.
19. A reviewable implementation sequence
Each milestone should leave the default emulator behavior stable and have a small, inspectable diff. Names for future helpers, modes and tests below are proposals, not existing APIs. Do not promise a calendar deadline before the guest-format evidence exists.
| Milestone | Deliverable | Exit gate |
|---|---|---|
| M0 · Inventory | One shader/dispatch dossier; current execution chain; opcode/format coverage matrix; artifact provenance. | Every assumption is labeled and the first blocked semantic question is explicit. |
| M1 · Decode | Extend the current E6/E7 decoder only for established additional forms; preserve exact operands and the CFG tail. | Positive/negative/truncated cases pass; no non-RT regressions. |
| M2 · Reference | Independent CPU node reader, math oracle and guest result packer for one established format. | Known-good vectors plus unknown/invalid cases; no invented layout fields. |
| M3 · Software GPU instruction | Extend the existing BvhIntersect and SPIR-V helper for the verified additional operation. | Output words and inactive lanes match the reference; validation is clean. |
| M4 · Reduced traversal shader | Complete loop/call/dataflow execution with ordinary side effects intact. | Repeated deterministic output; no skip, fake miss or omitted stores. |
| M5 · Runtime integration | Coherent GPU-produced BVHs, resource generations, cache policy and completion-based lifetime. | Update/reuse/alias/scratch-wrap tests; no hidden global-wait requirement. |
| M6 · Controlled scene | One scene with known geometry and an inspectable RT-dependent output. | Correct masks/IDs/distances under camera and instance movement. |
| M7 · Optional hardware optimization | Proven traversal recognition, host-AS build/cache and equivalent query lowering. | Differential agreement and conservative fallback for unrecognized semantics. |
| M8 · Game coverage and release policy | Title/version/route matrix, feature reporting, diagnostics, performance and fallback documentation. | Claims match actual supported variants, stages, devices and observed runs. |
The first useful contribution may be M0 or M1, not a frame with reflections. Keep format recovery, numerical semantics, shader compiler changes and host resource-management changes separable enough to review. Do not include unrelated rendering “fixes” just because they make one game progress during the experiment.
A concrete first week of work, without promising a one-week implementation
- Read the decoder, translation, resource tracking, BVH emitter and dispatch paths; explain which tests assert execution.
- Reproduce the three encoding examples and the false-positive cases. Write the unsupported-variant matrix.
- Choose a lawful reduced workload; record the descriptor/root producer and each live result consumer.
- Recover one node form with producer/consumer agreement, or document precisely why it is still unknown.
- Build hand-solvable reference vectors and a review note. Compare with the existing GPU fixtures, then propose the smallest missing-coverage patch.
If node layout or result encoding remains unknown, the next deliverable is a discriminating experiment—not an implementation filled with guessed fields.
20. What remains unknown, and what “ray tracing supported” should mean
This appendix does not establish every console BVH format, proprietary runtime ABI, builder version, packed return rule or hardware numerical edge case. It does not prove that a generic Vulkan ray query can replace a particular game's traversal routine. It does not run a game or add execution support to the emulator.
A defensible eventual status report names the subset: supported instructions/forms/stages/node formats, how unsupported work is handled, tested host devices, covered title versions/scenes, correctness evidence and known performance limits. “RT enabled” is too broad if only one opcode form or one node kind works. “Game reaches menu” is not RT validation; a quiet log is not execution validation.
Appendix completion checkpoint
Finish with the developer evidence record. Keep raw game material out of a public report where permission is absent; include minimized tests, precise source references and the scope of human review required by the upstream contribution policy.
21. Source map and primary references
Local paths below are relative to the emulator root. Online links pin the inspected KytyPS5 revision; public vendor/spec links can evolve, so record the version used for implementation. The current-state analysis is based on local source, not an assertion that future main still behaves identically.
| Responsibility | Existing source anchor |
|---|---|
| Instruction decoding and operand policy | src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.cpp, ShaderDecoder.h, ImageOps.cpp in that decode directory. |
| Compiler pipeline | src/graphics/shader/recompiler/ShaderRecompiler.cpp / ShaderRecompiler.h. |
| Translation dispatch and IR effects | src/graphics/shader/recompiler/frontend/translate/Memory.cpp, src/graphics/shader/recompiler/ir/Value.cpp, src/graphics/shader/recompiler/ir/opcodes/ValueOpcodes.cpp. |
| Resource provenance and binding shape | src/graphics/shader/recompiler/ir/passes/ResourceTracking.cpp, SrtWalker.cpp, ResourceMaterialization.cpp, BindingLayout.cpp in that passes directory. |
| SPIR-V emission | src/graphics/shader/recompiler/backend/spirv/spirvEmitterBvh.cpp, SpirvEmitter.cpp and the memory/flow helpers in that directory. |
| Programs and descriptors | src/graphics/host_gpu/renderer/pipeline/pipelineCache.cpp, src/graphics/host_gpu/renderer/pipeline/descriptors.cpp. |
| Dispatch and synchronization | src/graphics/host_gpu/renderer/renderCompute.cpp, src/graphics/host_gpu/renderer/commandScheduler.cpp, src/graphics/host_gpu/renderer/pipeline/shaderResourceBarrier.cpp. |
| Ownership, residency and faults | src/graphics/host_gpu/renderer/renderContext.cpp, src/graphics/host_gpu/renderer/cache/bufferCache.cpp, src/graphics/host_gpu/renderer/cache/faultManager.cpp, src/graphics/host_gpu/pageManager.cpp. |
| Host features | src/graphics/presentation/window/vulkanWindow.cpp, src/graphics/host_gpu/graphicContext.h. |
| Tests | tests/ShaderRayTracingTests.inc, tests/ShaderRayTracingGpuTests.inc, tests/shaderCfgTests.cpp, tests/ShaderRecompilerComputeTests.cpp, and root CMakeLists.txt. |
- Pinned KytyPS5 compiler pipeline and decode and IR regression fixtures.
- AMD RDNA 2 ISA, document 70648: consult section 8.2.10 and the associated encoding/register descriptions. Newer ISA generations are not substitutes.
- LLVM GFX1030 syntax (including BVH address operands): an independent assembler-facing cross-check.
- AMD GPURT: open implementation research; pin a revision and hardware-generation path, and do not assume console compatibility.
- Khronos ray-tracing guide, traversal semantics and acceleration structures.
- Khronos SPIR-V ray-query extension: capabilities and object/instruction rules for the optional host-query backend.
Return to the coverage map. This appendix describes the implemented software subset and a plan for extending and validating it; the proposed hardware backend remains separate.