Skip to main content

Rdp

Struct Rdp 

Source
#[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
Non-exhaustive structs could have additional fields added in future. Therefore, non-exhaustive structs cannot be constructed in external crates using the traditional Struct { .. } syntax; cannot be matched against without a wildcard ..; and struct update syntax will not work.
§cmd_start: u32

DP command FIFO start (DPC_START).

§cmd_end: u32

DP command FIFO end (DPC_END).

§cmd_current: u32

DP command FIFO current (DPC_CURRENT).

§status: u32

DP command FIFO status (DPC_STATUS): FREEZE, START/END-valid, XBUS, and (later) the busy/counter bits.

§color_image: u32

Color-image (framebuffer) base in RDRAM (Set Color Image, 0x3F).

§color_image_size: u8

Color-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: u8

Color-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: u16

Color-image width in pixels (Set Color Image width[9:0] + 1). The row stride is width * bytes_per_pixel.

§z_image: u32

Z-image base in RDRAM (Set Depth Image, 0x3E).

§prim_z: u16

Primitive 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: u16

Primitive dz (Set Primitive Depth, 0x2E), the raw 16-bit field.

§fill_color: u32

FILL-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: u16

Scissor 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: u16

Scissor upper-left y (u10.2). See Rdp::scissor_ulx.

§scissor_lrx: u16

Scissor lower-right x (u10.2). See Rdp::scissor_ulx.

§scissor_lry: u16

Scissor lower-right y (u10.2). See Rdp::scissor_ulx.

§commands_processed: u64

Count 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: u32

GCLK 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: u8

Texture-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: u8

Texture-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: u16

Texture-image width in pixels (0x3D width[9:0] + 1); the row stride of the RDRAM source during a load.

§tex_image_addr: u32

Texture-image base address in RDRAM (0x3D dramAddress[23:0]).

§tiles: [TileDescriptor; 8]

The eight tile descriptors (Set Tile / Set Tile Size / the loaders).

§combine: CombineMode

The color-combiner configuration (Set Combine Mode, 0x3C).

§other_modes: OtherModes

The render-mode / blender configuration (Set Other Modes, 0x2F).

§prim_color: u32

The primitive color, RGBA8888 (Set Prim Color, 0x3A).

§prim_lod_frac: u8

Primitive LOD fraction (Set Prim Color word-0 low byte) — a combiner mul input (R-10).

§min_level: u8

Set 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: u8

The 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: i16

Set Convert (0x2C) K4/K5 — combiner sub-B / mul inputs, raw 9-bit (K0..K3, the YUV-convert coefficients, are deferred). R-10.

§k5: i16

Set 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: u32

The environment color, RGBA8888 (Set Env Color, 0x3B).

§blend_color: u32

The blend color, RGBA8888 (Set Blend Color, 0x39).

§fog_color: u32

The fog color, RGBA8888 (Set Fog Color, 0x38).

Implementations§

Source§

impl Rdp

Source

pub fn new() -> Self

Construct at power-on.

Source

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.

Source

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_START latches the (masked) address and sets START_VALID only if it was clear — a second write while valid is ignored.
  • Writing DPC_END latches the end address, then branches on START_VALID (the wiki’s START_PENDING): if set, this is a fresh transfer — copy the pending start into DPC_CURRENT and clear START_VALID. If clear, it is an incremental transfer that continues from the current position, so DPC_CURRENT is left alone (rewinding it would reprocess already-consumed commands). On unfrozen hardware the transfer also runs; while frozen only the latch happens.
Source

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 0x000x3F; 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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Trait Implementations§

Source§

impl Clone for Rdp

Source§

fn clone(&self) -> Rdp

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Rdp

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Rdp

Source§

fn default() -> Rdp

Returns the “default value” for a type. Read more
Source§

impl<'de> Deserialize<'de> for Rdp

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
Source§

impl Serialize for Rdp

Source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>
where __S: Serializer,

Serialize this value into the given Serde serializer. Read more

Auto Trait Implementations§

§

impl Freeze for Rdp

§

impl RefUnwindSafe for Rdp

§

impl Send for Rdp

§

impl Sync for Rdp

§

impl Unpin for Rdp

§

impl UnsafeUnpin for Rdp

§

impl UnwindSafe for Rdp

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> DeserializeOwned for T
where T: for<'de> Deserialize<'de>,