#[non_exhaustive]pub struct Rdp {Show 37 fields
pub cmd_start: u32,
pub cmd_end: u32,
pub cmd_current: u32,
pub status: u32,
pub color_image: u32,
pub color_image_size: u8,
pub color_image_format: u8,
pub color_image_width: u16,
pub z_image: u32,
pub prim_z: u16,
pub prim_dz: u16,
pub fill_color: u32,
pub scissor_ulx: u16,
pub scissor_uly: u16,
pub scissor_lrx: u16,
pub scissor_lry: u16,
pub commands_processed: u64,
pub stall: u32,
pub tex_image_format: u8,
pub tex_image_size: u8,
pub tex_image_width: u16,
pub tex_image_addr: u32,
pub tiles: [TileDescriptor; 8],
pub combine: CombineMode,
pub other_modes: OtherModes,
pub prim_color: u32,
pub prim_lod_frac: u8,
pub min_level: u8,
pub max_level: u8,
pub k4: i16,
pub k5: i16,
pub key_center: [u8; 3],
pub key_scale: [u8; 3],
pub key_width: [u16; 3],
pub env_color: u32,
pub blend_color: u32,
pub fog_color: u32,
/* private fields */
}Expand description
RDP state (skeleton).
Holds the command-FIFO pointers, the current render mode (other-modes),
scissor rectangle, the color-image / Z-image RDRAM addresses, the
texture-image source registers, the eight tile descriptors, and TMEM. The
texel loads and the sampler/combiner that consume this state land in the rest
of the sprint. #[non_exhaustive]: this render state grows every sprint
(other-modes and the combiner latches are still to come), so adding a field
must not be a breaking change. Construct via Rdp::new; the workspace
never uses a struct literal.
Fields (Non-exhaustive)§
This struct is marked as non-exhaustive
Struct { .. } syntax; cannot be matched against without a wildcard ..; and struct update syntax will not work.cmd_start: u32DP command FIFO start (DPC_START).
cmd_end: u32DP command FIFO end (DPC_END).
cmd_current: u32DP command FIFO current (DPC_CURRENT).
status: u32DP command FIFO status (DPC_STATUS): FREEZE, START/END-valid, XBUS,
and (later) the busy/counter bits.
color_image: u32Color-image (framebuffer) base in RDRAM (Set Color Image, 0x3F).
color_image_size: u8Color-image pixel size code (Set Color Image size[1:0]): 0 = 4-bit,
1 = 8-bit, 2 = 16-bit, 3 = 32-bit. Bytes-per-pixel derive from it.
color_image_format: u8Color-image pixel format code (Set Color Image format[2:0]); the same
format enumeration as textures. Stored for later pipeline stages — the
FILL path writes the raw fill value and does not consult it.
color_image_width: u16Color-image width in pixels (Set Color Image width[9:0] + 1). The row
stride is width * bytes_per_pixel.
z_image: u32Z-image base in RDRAM (Set Depth Image, 0x3E).
prim_z: u16Primitive depth z (Set Primitive Depth, 0x2E): the s15.3 depth used when
Set Other Modes z_source_sel selects it (and the only depth source for
rectangle commands). Stored as the raw 16-bit field.
prim_dz: u16Primitive dz (Set Primitive Depth, 0x2E), the raw 16-bit field.
fill_color: u32FILL-mode color register (Set Fill Color, 0x37): a 32-bit value written
verbatim to the color image. Its interpretation depends on the pixel size
— one RGBA32, two RGBA16 (even pixel = upper half, odd = lower), or four
8-bit values repeating every four pixels.
scissor_ulx: u16Scissor rectangle (Set Scissor, 0x2D), the four u10.2 screen
coordinates that bound every primitive: upper-left (x, y) and lower-right
(x, y). Pixels outside it are neither processed nor written.
scissor_uly: u16Scissor upper-left y (u10.2). See Rdp::scissor_ulx.
scissor_lrx: u16Scissor lower-right x (u10.2). See Rdp::scissor_ulx.
scissor_lry: u16Scissor lower-right y (u10.2). See Rdp::scissor_ulx.
commands_processed: u64Count of commands the FIFO decoder has retired. A retired-work tally,
not a cycle position: nothing schedules against it (the residue
invariant governs only master_ticks), it is derived from the command
stream, and it exists so tests can witness that the decoder consumed the
number of commands it should. Wraps rather than panicking.
stall: u32GCLK cycles the pipeline is currently stalled, counted down one per
tick; while non-zero the FIFO does not advance. Set by the sync
commands to their documented fixed stalls (SYNC_LOAD_GCLK etc.). This
is a stall countdown, not a cycle position — it is decremented, nothing
derives a clock from it, and it does not touch the derive-don’t-increment
rule (only master_ticks is ever incremented; ADR 0006).
tex_image_format: u8Texture-image (Set Texture Image, 0x3D) format code — the RDRAM source
for texture loads. The wiki notes this format has no effect on any
operation (only the tile format matters); stored for completeness.
tex_image_size: u8Texture-image texel size code (0x3D size[1:0]): 4/8/16/32bpp = 0/1/2/3. Drives the per-texel address stride during a load.
tex_image_width: u16Texture-image width in pixels (0x3D width[9:0] + 1); the row stride of the RDRAM source during a load.
tex_image_addr: u32Texture-image base address in RDRAM (0x3D dramAddress[23:0]).
tiles: [TileDescriptor; 8]The eight tile descriptors (Set Tile / Set Tile Size / the loaders).
combine: CombineModeThe color-combiner configuration (Set Combine Mode, 0x3C).
other_modes: OtherModesThe render-mode / blender configuration (Set Other Modes, 0x2F).
prim_color: u32The primitive color, RGBA8888 (Set Prim Color, 0x3A).
prim_lod_frac: u8Primitive LOD fraction (Set Prim Color word-0 low byte) — a combiner mul
input (R-10).
min_level: u8Set Prim Color min_level (word-0 bits 12:8): the floor the LOD is held
at while magnifying (Angrylion lodfrac_lodtile_signals). R-13.
max_level: u8The current primitive’s level[2:0] (triangle command bits 53:51) — the
number of mip levels past the base tile. Per-primitive render state, set
by the triangle decode, exactly as Angrylion holds it (rasterizer.c
state[wid].max_level = (ewdata[0] >> 19) & 7). R-13.
k4: i16Set Convert (0x2C) K4/K5 — combiner sub-B / mul inputs, raw 9-bit
(K0..K3, the YUV-convert coefficients, are deferred). R-10.
k5: i16Set Convert K5 (see k4).
key_center: [u8; 3]Chroma-key center per channel [r, g, b] (Set Key R 0x2B / Set Key GB
0x2A) — the combiner RGB sub-B input (select 6). 0..=255. R-10.
key_scale: [u8; 3]Chroma-key scale per channel [r, g, b] (Set Key R/GB) — the combiner
RGB mul input (select 6). 0..=255. R-10.
key_width: [u16; 3]Chroma-key width per channel [r, g, b] (Set Key R/GB, 12-bit) — the
half-width of the key window, consumed by the key_en chroma-key alpha compare
(chroma_key_min), not the combiner mux. R-10.
env_color: u32The environment color, RGBA8888 (Set Env Color, 0x3B).
blend_color: u32The blend color, RGBA8888 (Set Blend Color, 0x39).
fog_color: u32The fog color, RGBA8888 (Set Fog Color, 0x38).
Implementations§
Source§impl Rdp
impl Rdp
Sourcepub const fn dpc_read(&self, offset: u32) -> u32
pub const fn dpc_read(&self, offset: u32) -> u32
Read a DP command register by word offset within the 0x0410_0000
block: 0 DPC_START, 1 DPC_END, 2 DPC_CURRENT, 3 DPC_STATUS. The
clock/busy/counter registers (4..=7) are not modeled and read zero.
Sourcepub const fn dpc_write(&mut self, offset: u32, value: u32)
pub const fn dpc_write(&mut self, offset: u32, value: u32)
Write a DP command register (word offsets as in Rdp::dpc_read).
The FIFO uses a double-latch pinned by n64-systemtest’s RSP STATUS: start-valid and documented in the N64brew wiki (Reality Display
Processor Interface, the DPC_END section):
- Writing
DPC_STARTlatches the (masked) address and setsSTART_VALIDonly if it was clear — a second write while valid is ignored. - Writing
DPC_ENDlatches the end address, then branches onSTART_VALID(the wiki’sSTART_PENDING): if set, this is a fresh transfer — copy the pending start intoDPC_CURRENTand clearSTART_VALID. If clear, it is an incremental transfer that continues from the current position, soDPC_CURRENTis left alone (rewinding it would reprocess already-consumed commands). On unfrozen hardware the transfer also runs; while frozen only the latch happens.
Sourcepub fn tick<B: VideoBus>(&mut self, bus: &mut B)
pub fn tick<B: VideoBus>(&mut self, bus: &mut B)
Advance the RDP by one rasterization step: decode the command at
DPC_CURRENT and consume its whole length, so the FIFO drains one
command per scheduler tick rather than in a burst.
Hot path: keep allocation-free. No-op while the FIFO is empty
(DPC_CURRENT >= DPC_END) or the DP is frozen (DPC_STATUS.FREEZE).
The command length comes from command::command_len_words, which
recognizes every opcode 0x00–0x3F; consuming the exact length is what
keeps a multi-word primitive from desyncing the pointer. Today the
decoder only advances and counts — no primitive is rasterized yet.
Commands are read from RDRAM (the XBUS bit clear). The XBUS/DMEM
command source is not yet wired: the rdpq microcode that drives us DMAs
its list to RDRAM, so the RDRAM path is the one exercised. With XBUS
set the decoder stalls rather than mis-reading RDRAM as the command
stream — decoding DMEM commands out of RDRAM would treat parameter data
as opcodes and desync.
Dispatch so far (dispatch) covers the four sync commands and the FILL
pipeline (Set Color Image, Set Fill Color, Set Scissor, Fill Rectangle).
Everything else is still recognized-and-consumed only.
This is the whole-step entry point and is not deprecated. It is what a
caller uses when it already holds the bus and has nothing to decide — tests,
and any future embedder. Bus::rdp_tick in rustyn64-core instead calls the two
halves directly, because it must know whether the step needs the bus before
arranging one: the arranging is a core::mem::take of this whole struct.
Splitting is worth it only for that caller, which is why the convenient form
stays.
Sourcepub fn tick_without_bus(&mut self) -> Option<NeedsBus>
pub fn tick_without_bus(&mut self) -> Option<NeedsBus>
The part of a step that needs no bus access, returning None when it
finished the step on its own and Some(NeedsBus) when work remains.
This advances state, despite the Option return: on the stalling path it
burns one GCLK. It is named tick_* rather than is_* for that reason — in
this codebase tick means advance by one step, as in Bus::rsp_tick and
Cpu::tick_at.
Split out so a caller can decide whether to pay for bus access before
arranging it. Bus::rdp_tick moves this whole struct out of the Bus with
core::mem::take to satisfy the borrow checker — 344 bytes read and written,
plus a default written back, on every RCP step — and on most steps the
answer here is None, so that shuffle bought nothing
(docs/performance.md §“The Bus split-borrow moves 1.35 GB a frame”).
It lives here rather than in the Bus because it is a statement about this
chip’s early-outs: if they change, this changes with them, in the same file.
Rdp::tick calls it too, so there is one implementation and no way for the
two to disagree.
Returns NeedsBus when the step still has work that requires RDRAM. That
token is the only way to reach Rdp::tick_with_bus, so the two halves cannot
be called out of order — the preconditions are carried by the type rather than
by a comment or an assertion.
Sourcepub fn tick_with_bus<B: VideoBus>(&mut self, _proof: NeedsBus, bus: &mut B)
pub fn tick_with_bus<B: VideoBus>(&mut self, _proof: NeedsBus, bus: &mut B)
The remainder of a step, once Rdp::tick_without_bus has handed back a
NeedsBus.
Reachable only with a NeedsBus, which Rdp::tick_without_bus hands out
exactly when the FIFO is non-empty and the pipeline is neither frozen nor
stalled. Those preconditions are therefore not asserted here: an assertion that
cannot fire is dead code that reads like a safeguard.
The token is taken by value, not by reference, so it is consumed: one
tick_without_bus authorizes exactly one bus half. Relaxing this to
&NeedsBus would let a caller hold one and re-enter after the state it
attested to had changed, which is the whole property being bought here.
Sourcepub fn blend_cycle(cycle: BlendCycle, inp: &BlendInputs) -> [u8; 3]
pub fn blend_cycle(cycle: BlendCycle, inp: &BlendInputs) -> [u8; 3]
Evaluate one blender cycle: (P * a0 + M * (a1 + 1)) >> 5, the divide-free
form the hardware uses whenever the result is not an anti-aliased edge
(N64brew …/Blender; parallel-rdp shaders/blender.h). a0 = A >> 3 and
a1 = B >> 3 map the 8-bit alpha selects to the 5-bit blend weights, and
the + 1 on the M term is real hardware, not a rounding fudge.
The color selects (P, M) pick an RGB triple; the alpha selects (A,
B) pick a scalar weight — B’s 1 − A case complements the resolved A
weight, so A is computed first and handed to blend_b_input. The result
is masked to 8 bits, not clamped: the reference casts through u8 and
re-masks (blender.h:142), so an over-range blend wraps exactly as hardware
does — software is expected to keep a0 + a1 + 1 ≈ 32.
The final-cycle early-return fast paths (opaque passthrough, color_on_cvg),
the anti-aliased divider path, alpha-compare, dither, and Z are open
residual R-11; blend_cycle always takes the no-divide branch for now.
Sourcepub fn blend(&self, inp: BlendInputs) -> [u8; 3]
pub fn blend(&self, inp: BlendInputs) -> [u8; 3]
Evaluate the whole blender for a pixel: blend cycle 0 alone in 1-cycle mode, or cycle 0’s RGB fed back as the pixel color into cycle 1 in 2-cycle mode (N64brew …/Blender).
Only pixel.rgb chains between cycles — pixel.a is deliberately left
unchanged, so both cycles’ A/B alpha selects see the original combiner
alpha. This matches the reference, which reassigns pixel_color.rgb only
before the second blender() call (parallel-rdp memory_interfacing.h:536);
the blender produces no alpha of its own (blender.h returns u8x3).
Precondition: only valid for cycle types 0 (1-cycle) and 1 (2-cycle). Copy
(2) and Fill (3) bypass the blender on hardware — the pixel comes straight from
the texel copy / fill register — so the pixel pipeline (T-33-004) must gate on
cycle_type and not route those modes through here. This method is not given a
fabricated Copy/Fill result, because the honest contract is “not called”, not
“called and returns something”; for cycle type 0 it correctly runs cycle 0 once.
Sourcepub fn depth_test(
z: i32,
dz: i32,
dz_compressed: i32,
coverage_count: i32,
inp: &DepthInputs,
) -> DepthResult
pub fn depth_test( z: i32, dz: i32, dz_compressed: i32, coverage_count: i32, inp: &DepthInputs, ) -> DepthResult
The per-pixel depth test and coverage/blend derivation — a faithful port of
ParaLLEl-RDP’s depth_test.h (the Angrylion-parity reference).
z/dz are this pixel’s decompressed 18-bit depth and its raw delta;
dz_compressed is dz’s 4-bit log2; coverage_count is this pixel’s
span coverage. DepthInputs carries the Z-buffer read and the render-mode
flags. When z_compare is off the pixel always passes; otherwise the four Z
modes (opaque/interpenetrating/transparent/decal) apply, with the
coplanar/precision-factor handling of the stored dz. Interpenetrating mode
can reduce the returned coverage_count. No buffer is touched here — the
caller (the pixel pipeline, PR-B) reads/writes the Z buffer and applies the
result; today this has no runtime caller, so the oracle is unchanged.
Sourcepub fn zbuffer_read<B: VideoBus>(&self, x: u32, y: u32, bus: &B) -> (u16, u8)
pub fn zbuffer_read<B: VideoBus>(&self, x: u32, y: u32, bus: &B) -> (u16, u8)
Read the Z-buffer entry at (x, y) as (compressed_z, dz).
The 16-bit halfword holds the 14-bit compressed z in bits 15:2 and the
high two bits of the 4-bit dz in bits 1:0; the low two bits of
dz come from the RDRAM hidden bits — matching ParaLLEl-RDP’s
load_vram_depth. dz is returned as the 4-bit value 0..=15.
Sourcepub fn zbuffer_write<B: VideoBus>(
&self,
x: u32,
y: u32,
z: i32,
dz: u8,
bus: &mut B,
)
pub fn zbuffer_write<B: VideoBus>( &self, x: u32, y: u32, z: i32, dz: u8, bus: &mut B, )
Write the Z-buffer entry at (x, y): compress the 18-bit z, pack it with
dz’s high two bits into the halfword, and store dz’s low two bits in the
RDRAM hidden bits — matching ParaLLEl-RDP’s store_vram_depth. dz is the
4-bit compressed delta (0..=15); z is the 18-bit UNORM depth.
Sourcepub fn fetch_texel(&self, tile: &TileDescriptor, s: u32, t: u32) -> [u8; 4]
pub fn fetch_texel(&self, tile: &TileDescriptor, s: u32, t: u32) -> [u8; 4]
Sample one texel from tile at tile-relative integer coords (s, t),
returning RGBA8888. The fetch half of the texture pipeline; the
clamp/mirror/mask/shift wrapper and the filter/combiner are T-32-004 /
Sprint 3. Decodes every listed texel format (RGBA16/32, IA16/8/4, I8/4,
CI8/4 via the TLUT), matched to the ParaLLEl-RDP read layout.
TMEM is read as a natural big-endian byte array with the odd-row
32-bit-word swap ^= (t & 1) << 2 — the same convention the loads use, so
the endian twiddles ParaLLEl-RDP applies to its host-word storage are
intentionally absent here too. An unsupported format/size is transparent
black.
Sourcepub fn tmem_byte(&self, offset: usize) -> u8
pub fn tmem_byte(&self, offset: usize) -> u8
Read one byte of TMEM.
offset is a byte address (0..TMEM_SIZE), masked into the 4 KiB
space — not the 64-bit-word address that Set Tile’s tmem_addr /
line use; a word address must be multiplied by 8 first (word 0x100 =
byte 0x800). An unwritten (lazily-unallocated) TMEM reads as zero.