Skip to main content
← PSoXide SDK guides and examples · SDK f2fec1c301f1 · All Cargo features enabled; check feature gates before use.

psx_gte/
ops.rs

1//! GTE function-op wrappers.
2//!
3//! Each op corresponds to one COP2 cofun instruction. The constants
4//! below are the exact 32-bit encodings PSX-SPX documents -- the same
5//! bits the MIPS CPU would dispatch on hardware and the same bits the
6//! emulator's `Gte::execute` decodes. On host we hand them straight
7//! to the per-thread software GTE in [`crate::host`]; on MIPS we emit
8//! them as `.word` instructions so LLVM's integrated assembler can't
9//! reject the COP2 mnemonics it doesn't recognise.
10//!
11//! Encoding (per PSX-SPX, "GTE Coprocessor Opcodes"):
12//!
13//! ```text
14//!   bits 31..26 = 0x12 (010010)      -- COP2
15//!   bit  25     = 1                   -- `cop2 cofun` prefix
16//!   bit  19     = sf                  -- fraction-shift
17//!   bits 18..17 = mx                  -- MVMVA matrix select
18//!   bits 16..15 = vx                  -- MVMVA vector select
19//!   bits 14..13 = cv                  -- MVMVA translation select
20//!   bit  10     = lm                  -- IR lower-bound mode
21//!   bits 5..0   = opcode              -- function id
22//! ```
23//!
24//! The `0x4A000000` base (`0x12<<26 | 1<<25`) is common to every op.
25
26#[cfg(target_arch = "mips")]
27use core::arch::asm;
28
29/// Emit one cofun instruction. On MIPS this is a `.word` with the
30/// encoding baked into the immediate; on host it executes against the
31/// thread-local software Gte.
32macro_rules! cofun {
33    ($instr:expr) => {{
34        #[cfg(target_arch = "mips")]
35        // SAFETY: every `$instr` passed in this module is a `0x4A..` COP2
36        // cofun encoding, which reads and writes GTE registers only. It
37        // writes no CPU register, needs no stack (`nostack`), and the asm
38        // conservatively omits `nomem` even though the op never touches RAM.
39        // The wrappers' `# Safety` input-register preconditions only decide
40        // whether the result is meaningful; stale inputs give garbage
41        // numbers, not undefined behaviour.
42        unsafe {
43            asm!(
44                ".word {instr}",
45                instr = const $instr,
46                options(nostack, preserves_flags),
47            )
48        }
49        #[cfg(not(target_arch = "mips"))]
50        {
51            $crate::host::execute($instr);
52        }
53    }};
54}
55
56/// Emit the deprecated forwarder that keeps an op's old mnemonic name
57/// compiling after its rename. `$note` is the full deprecation note, spelled
58/// out at each use because `#[deprecated]` only takes a string literal.
59macro_rules! renamed_op {
60    ($old:ident => $new:ident, $note:literal) => {
61        #[doc = concat!("Renamed to [`", stringify!($new), "`].")]
62        ///
63        /// # Safety
64        #[doc = concat!("See [`", stringify!($new), "`].")]
65        #[deprecated(note = $note)]
66        #[inline(always)]
67        pub unsafe fn $old() {
68            // SAFETY: same contract as the renamed function.
69            unsafe { $new() }
70        }
71    };
72}
73
74/// Perspective-project V0 (`RTPS`, `sf=1, lm=0`).
75///
76/// # Safety
77/// Assumes V0, RT, TR, OFX/OFY, H, DQA/DQB are loaded.
78#[doc(alias = "RTPS")]
79#[doc(alias = "RotTransPers")]
80#[inline(always)]
81pub unsafe fn project_single() {
82    cofun!(crate::encoding::command::RTPS)
83}
84
85/// Perspective-project V0, V1 and V2 in sequence (`RTPT`, `sf=1, lm=0`).
86///
87/// # Safety
88/// See [`project_single`].
89#[doc(alias = "RTPT")]
90#[doc(alias = "RotTransPers3")]
91#[inline(always)]
92pub unsafe fn project_triple() {
93    cofun!(crate::encoding::command::RTPT)
94}
95
96/// Winding of the three SXY entries (`NCLIP`): the Z component of
97/// `(SXY1-SXY0) × (SXY2-SXY0)` into MAC0.
98///
99/// # Safety
100/// See [`project_single`].
101#[doc(alias = "NCLIP")]
102#[doc(alias = "NormalClip")]
103#[inline(always)]
104pub unsafe fn screen_winding() {
105    cofun!(crate::encoding::command::NCLIP)
106}
107
108/// Outer product of IR with the rotation matrix diagonal (`OP`, `sf=1`).
109///
110/// # Safety
111/// See [`project_single`].
112#[doc(alias = "OP")]
113#[doc(alias = "OuterProduct12")]
114#[inline(always)]
115pub unsafe fn outer_product() {
116    cofun!(crate::encoding::command::OP)
117}
118
119/// Average SZ1..SZ3 weighted by ZSF3 into OTZ and MAC0 (`AVSZ3`).
120///
121/// # Safety
122/// See [`project_single`].
123#[doc(alias = "AVSZ3")]
124#[doc(alias = "AverageZ3")]
125#[inline(always)]
126pub unsafe fn average_z3() {
127    cofun!(crate::encoding::command::AVSZ3)
128}
129
130/// Average SZ0..SZ3 weighted by ZSF4 into OTZ and MAC0 (`AVSZ4`).
131///
132/// # Safety
133/// See [`project_single`].
134#[doc(alias = "AVSZ4")]
135#[doc(alias = "AverageZ4")]
136#[inline(always)]
137pub unsafe fn average_z4() {
138    cofun!(crate::encoding::command::AVSZ4)
139}
140
141/// Square the current IR vector into MAC1/2/3 (`SQR`, `sf=1`).
142///
143/// # Safety
144/// See [`project_single`].
145#[doc(alias = "SQR")]
146#[doc(alias = "Square12")]
147#[inline(always)]
148pub unsafe fn square() {
149    cofun!(crate::encoding::command::SQR)
150}
151
152/// Square the current IR vector into MAC1/2/3 without the fractional
153/// right shift (`SQR`, `sf=0`).
154///
155/// This form is useful for integer vector lengths: the MAC registers retain
156/// the exact component squares while the CPU prepares a reciprocal scale.
157///
158/// # Safety
159/// IR1, IR2 and IR3 must contain the vector to square.
160#[doc(alias = "SQR")]
161#[doc(alias = "Square0")]
162#[inline(always)]
163pub unsafe fn square_unshifted() {
164    cofun!(crate::encoding::command::SQR_UNSHIFTED)
165}
166
167/// Light, color and depth-cue one vertex normal (`NCDS`, `sf=1, lm=0`).
168///
169/// # Safety
170/// See [`project_single`].
171#[doc(alias = "NCDS")]
172#[doc(alias = "NormalColorDpq")]
173#[inline(always)]
174pub unsafe fn light_color_depth_single() {
175    cofun!(crate::encoding::command::NCDS)
176}
177
178/// Light and color one vertex normal, no depth cue (`NCCS`).
179///
180/// # Safety
181/// See [`project_single`].
182#[doc(alias = "NCCS")]
183#[doc(alias = "NormalColorCol")]
184#[inline(always)]
185pub unsafe fn light_color_single() {
186    cofun!(crate::encoding::command::NCCS)
187}
188
189/// Light one vertex normal, without the RGBC modulate (`NCS`).
190///
191/// # Safety
192/// See [`project_single`].
193#[doc(alias = "NCS")]
194#[doc(alias = "NormalColor")]
195#[inline(always)]
196pub unsafe fn light_single() {
197    cofun!(crate::encoding::command::NCS)
198}
199
200/// [`light_color_depth_single`] for V0, V1 and V2 (`NCDT`).
201///
202/// # Safety
203/// See [`project_single`].
204#[doc(alias = "NCDT")]
205#[doc(alias = "NormalColorDpq3")]
206#[inline(always)]
207pub unsafe fn light_color_depth_triple() {
208    cofun!(crate::encoding::command::NCDT)
209}
210
211/// [`light_single`] for V0, V1 and V2 (`NCT`).
212///
213/// # Safety
214/// See [`project_single`].
215#[doc(alias = "NCT")]
216#[doc(alias = "NormalColor3")]
217#[inline(always)]
218pub unsafe fn light_triple() {
219    cofun!(crate::encoding::command::NCT)
220}
221
222/// [`light_color_single`] for V0, V1 and V2 (`NCCT`).
223///
224/// # Safety
225/// See [`project_single`].
226#[doc(alias = "NCCT")]
227#[doc(alias = "NormalColorCol3")]
228#[inline(always)]
229pub unsafe fn light_color_triple() {
230    cofun!(crate::encoding::command::NCCT)
231}
232
233/// Depth-cue RGBC toward the far color by IR0 (`DPCS`).
234///
235/// # Safety
236/// See [`project_single`].
237#[doc(alias = "DPCS")]
238#[doc(alias = "DpqColor")]
239#[inline(always)]
240pub unsafe fn depth_cue_single() {
241    cofun!(crate::encoding::command::DPCS)
242}
243
244/// [`depth_cue_single`] run three times against the RGB FIFO (`DPCT`).
245///
246/// # Safety
247/// See [`project_single`].
248#[doc(alias = "DPCT")]
249#[doc(alias = "DpqColor3")]
250#[inline(always)]
251pub unsafe fn depth_cue_triple() {
252    cofun!(crate::encoding::command::DPCT)
253}
254
255/// Interpolate IR toward the far color by IR0, then push the color FIFO
256/// (`INTPL`).
257///
258/// # Safety
259/// See [`project_single`].
260#[doc(alias = "INTPL")]
261#[inline(always)]
262pub unsafe fn interpolate_far_color() {
263    cofun!(crate::encoding::command::INTPL)
264}
265
266/// Depth-cue a lit color: `RGBC*IR` toward FC by IR0 (`DCPL`).
267///
268/// # Safety
269/// See [`project_single`].
270#[doc(alias = "DCPL")]
271#[doc(alias = "DpqColorLight")]
272#[inline(always)]
273pub unsafe fn depth_cue_light() {
274    cofun!(crate::encoding::command::DCPL)
275}
276
277/// Light IR through the light color matrix and modulate by RGBC (`CC`).
278///
279/// # Safety
280/// See [`project_single`].
281#[doc(alias = "CC")]
282#[doc(alias = "ColorCol")]
283#[inline(always)]
284pub unsafe fn color_color() {
285    cofun!(crate::encoding::command::CC)
286}
287
288/// [`color_color`] followed by a depth cue toward the far color (`CDP`).
289///
290/// # Safety
291/// See [`project_single`].
292#[doc(alias = "CDP")]
293#[doc(alias = "ColorDpq")]
294#[inline(always)]
295pub unsafe fn color_depth_cue() {
296    cofun!(crate::encoding::command::CDP)
297}
298
299/// Scale the IR vector by IR0 into MAC, then push the color FIFO
300/// (`GPF`, `sf=1`).
301///
302/// # Safety
303/// See [`project_single`].
304#[doc(alias = "GPF")]
305#[inline(always)]
306pub unsafe fn scale_vector() {
307    cofun!(crate::encoding::command::GPF)
308}
309
310/// Scale the IR vector by IR0 into MAC, then push the color FIFO, without
311/// the fractional right shift (`GPF`, `sf=0`).
312///
313/// # Safety
314/// IR0 through IR3 must contain the scalar and vector inputs.
315#[doc(alias = "GPF")]
316#[inline(always)]
317pub unsafe fn scale_vector_unshifted() {
318    cofun!(crate::encoding::command::GPF_UNSHIFTED)
319}
320
321/// Add the IR vector scaled by IR0 to MAC, then push the color FIFO
322/// (`GPL`, `sf=1`).
323///
324/// # Safety
325/// See [`project_single`].
326#[doc(alias = "GPL")]
327#[inline(always)]
328pub unsafe fn scale_vector_accumulate() {
329    cofun!(crate::encoding::command::GPL)
330}
331
332/// Rotate V0 by RT and add TR, without the perspective divide
333/// (`MVMVA` with `mx=RT, vx=V0, cv=TR, sf=1, lm=0`).
334///
335/// # Safety
336/// See [`project_single`].
337#[doc(alias = "MVMVA")]
338#[doc(alias = "RotTrans")]
339#[inline(always)]
340pub unsafe fn rotate_translate_v0() {
341    cofun!(crate::encoding::command::ROTATE_TRANSLATE_V0)
342}
343
344/// Rotate V0 by RT with the far color as the translation
345/// (`MVMVA` with `mx=RT, vx=V0, cv=FC, sf=1, lm=0`).
346///
347/// PSX-SPX documents this `cv=FC` combination as hardware-bugged
348/// (the first matrix-row product is discarded mid-accumulate), which makes
349/// it a sharp emulator-vs-silicon conformance check rather than a useful
350/// transform.
351///
352/// # Safety
353/// See [`project_single`].
354#[doc(alias = "MVMVA")]
355#[inline(always)]
356pub unsafe fn rotate_v0_far_color() {
357    cofun!(crate::encoding::command::ROTATE_V0_FAR_COLOR)
358}
359
360// Deprecated forwarders for the mnemonic names these ops had before the
361// naming convention (sdk/docs/NAMING.md).
362renamed_op!(rtps => project_single, "renamed to `project_single`");
363renamed_op!(rtpt => project_triple, "renamed to `project_triple`");
364renamed_op!(nccs => light_color_single, "renamed to `light_color_single`");
365renamed_op!(ncct => light_color_triple, "renamed to `light_color_triple`");