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`");