diff --git a/1.7_Primdraw3D/.cargo/config.toml b/1.7_Primdraw3D/.cargo/config.toml new file mode 100644 index 0000000..8d98ba9 --- /dev/null +++ b/1.7_Primdraw3D/.cargo/config.toml @@ -0,0 +1,2 @@ +[target.mipsel-sony-psx] +runner = "mednafen" diff --git a/1.7_Primdraw3D/Cargo.toml b/1.7_Primdraw3D/Cargo.toml new file mode 100644 index 0000000..dc1d163 --- /dev/null +++ b/1.7_Primdraw3D/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "Primdraw" +version = "0.1.0" +edition = "2021" + +[dependencies] +#psx = { git = "https://github.com/ayrtonm/psx-sdk-rs.git" } +psx = { path = "../../psx-sdk-rs/psx" } diff --git a/1.7_Primdraw3D/src/main.rs b/1.7_Primdraw3D/src/main.rs new file mode 100644 index 0000000..502fca7 --- /dev/null +++ b/1.7_Primdraw3D/src/main.rs @@ -0,0 +1,343 @@ +#![no_std] +#![no_main] +#![feature(asm_experimental_arch)] + +use psx::{dma, dprintln, Framebuffer}; +use psx::constants::*; +use psx::gpu::primitives::{PolyF3}; +use psx::gpu::{Color, link_list, Packet, TexCoord, Vertex, VideoMode}; +use psx::hw::{cop0, gte, Register}; +use psx::math::{sin, cos, Rad}; +use psx::sys::gamepad::{Gamepad, Button}; + +const NTSC: bool = true; // toggle between NTSC and PAL modes and texture + // +// Attempt to convert Lameguy64 / Meido-Tek primdraw.c 3D example from C to Rust.. + +/* primdraw.c by Lameguy64 + 2014 Meido-Tek Productions. + + Demonstrates: + - Using a primitive OT to draw triangles without libgs. + - Using the GTE to rotate, translate, and project 3D primitives. + + Controls: + Start - Toggle interactive/non-interactive mode. + Select - Reset object's position and angles. + L1/L2 - Move object closer/farther. + L2/R2 - Rotate object (XY). + Up/Down/Left/Right - Rotate object (XZ/YZ). + Triangle/Cross/Square/Circle - Move object up/down/left/right. + +*/ + +pub struct Trans { + pub x: i16, + pub y: i16, + pub z: i16, +} + +pub struct Rotate { + pub x: i16, + pub y: i16, + pub z: i16, +} + +const CENTERX: i16 = 320; +const CENTERY: i16 = 120; +const OT_LENGTH: usize = 48; // breaks at 64, 50? works at 32, 40, 48, 49 +const ENABLE_GTE: u32 = 1 << 30; +const RTPS: u32 = 0x0180001; +const ONE: i16 = 1<<12; + + +#[inline(always)] +pub fn delay() { + unsafe { + core::arch::asm!( + "nop", + // "nop", + ); + } +} + +/// Convert Euler rotation vector into fixed point rotation matrix +fn rot_matrix(rotate: &Rotate, matrix: &mut [[i16; 3]; 3]) { + // Test: lets just do roll, about z axis: + let cos_z = cos(Rad(rotate.z as u16)).to_bits() as i16; + let sin_z = sin(Rad(rotate.z as u16)).to_bits() as i16; + matrix[0][0] = cos_z; + matrix[1][0] = -sin_z; + matrix[0][1] = sin_z; + matrix[1][1] = cos_z; + matrix[2][2] = ONE; + +} + +/// Sends matrix to the GTE +fn set_rot_matrix(matrix: [[i16; 3]; 3]) { + delay(); + let mut r1 = gte::RT11_12::new(); + let v1 = (ONE | matrix[0][0]) as u32 | ((matrix[0][1] as u32 ) << 28); + delay(); + r1.assign(v1).store(); + delay(); + let mut r2 = gte::RT13_21::new(); + let v2 = (matrix[0][2]) as u32 | ((matrix[1][0] as u32 ) << 28); + delay(); + r2.assign(v2).store(); + delay(); + let mut r3 = gte::RT22_23::new(); + let v3 = (ONE | matrix[1][1]) as u32 | ((matrix[1][2] as u32 ) << 28); + delay(); + r3.assign(v3).store(); + delay(); + let mut r4 = gte::RT31_32::new(); + let v4 = (matrix[2][0]) as u32 | ((matrix[2][1] as u32 ) << 28); + delay(); + r4.assign(v4).store(); + delay(); + let mut r5 = gte::RT33::new(); + let v5 = ONE | matrix[2][2]; + delay(); + r5.assign(v5).store(); + delay(); + delay(); + delay(); // 3 +} + + +fn rot_trans_pers(v0: (i16, i16, i16), sxy: &mut Vertex, p: &u32, flag: &mut u32) -> i32 { + let flag_reg = gte::FLAG::new(); + let z_reg = gte::IR0::new(); + //let z_reg = gte::SZ0::new(); + let mut vxy0 = gte::VXY0::new(); + let mut vz0 = gte::VZ0::new(); + + // Send vector v0 to GTE: + vxy0.assign((v0.1 as u32) << 16 | (v0.0) as u32).store(); + //delay(); + vz0.assign(v0.2).store(); + //delay(); + + // Send RTPS GTE command to cop2 + unsafe { + core::arch::asm! { + ".long {} & 0x1ffffff | 37 << 25 # cop2", + const RTPS, + options(nomem, nostack) + } + } + + *flag = flag_reg.to_bits(); + //let sx = gte::MAC1::new().to_bits() as i16; + let sx = gte::IR1::new().to_bits() as i16; + delay(); + //let sy = gte::MAC2::new().to_bits() as i16; + let sy = gte::IR2::new().to_bits() as i16; + delay(); + + //assert!(sxa == sx); + //assert!(sya == sy); // These asserts added just enough delay to allow this code to work ... without them, sxy is not correctly set! + // TODO: determine WHERE the correct delay needs to be added! + *sxy = Vertex(sx, sy); + z_reg.to_bits() as i32 // screen Z +} + + +#[no_mangle] +fn main() { + let mut gpu_dma = dma::GPU::new(); + + // Full Hi-res is 640 x 480 as used in primdraw.c, + // can't get a double buffer with that y res + //Framebuffer::new((0, 0), (0, 240), (320, 240), VideoMode::NTSC, Some(INDIGO)).unwrap() + let mut fb = Framebuffer::new((0, 0), (0, 240), (640, 240), VideoMode::NTSC, Some(BLUE)).unwrap(); + + let mut txt = fb.load_default_font().new_text_box((0, 0), (520, 200)); + let mut db = 0; // display buffer 0 or 1 + let mut autorotate = 0; + let mut tpressed = 0; + let mut p = 0; // idk what this is for, used by RTPS? + let mut flag = 0; // GTE status flag + + // Enable GTE: + let mut status = cop0::Status::new(); + status.assign(ENABLE_GTE).store(); + + // If gamepad init occurs before GTE enable, gamepad doesn't work ... + let mut gamepad = Gamepad::new(); + + // Set up 2 x ordering tables in a OT_LENGTH array + let mut polys_a = [const { Packet::new(PolyF3::new()) }; OT_LENGTH]; + let mut polys_b = [const { Packet::new(PolyF3::new()) }; OT_LENGTH]; + let mut my_prims: [PolyF3; 1024] = [Default::default(); 1024]; + link_list(&mut polys_a); + link_list(&mut polys_b); + + // Location and Dimensions of the object + let mut trans = Trans{ x: CENTERX, y: CENTERY, z: 0 }; + let mut rotate = Rotate{ x: 0, y: 0, z: 0 }; + + let (h, w) = (64, 64); + let vec_mesh = [ // list of triangle vertices in 3d space + (50, 50, 10), (25, 25, 10), (50, 10, 10), + //(10, 10, -1), (15, 5, -1), (20, 10, -1), + //(250, 150, 0), (225, 125, 0), (225, 110, 0), + //(50, 50, 0), (25, 25, 0), (50, 10, 0), + + ]; + // OFX, OFY, and H don't appear to be having an effect on screen position, but does on the + // reported coords? + gte::OFX::new().assign((CENTERX as u32) << 16 ).store(); // TODO: should OFX and OFY be i16? NO!, 1 sign, 15bit int, 16 frac + gte::OFY::new().assign((CENTERY as u32) << 16 ).store(); + // SetGeomScreen(CENTERX); + gte::H::new().assign(CENTERX).store(); + + + let mut matrix: [[i16; 3]; 3] = [ + [ONE, 0, 0], + [0, ONE, 0], + [0, 0, ONE], + ]; + + set_rot_matrix(matrix); + + // Main loop + loop { + dprintln!(txt, "after a"); + dprintln!(txt, " SIMPLE 3D EXAMPLE BY LAMEGUY64"); + dprintln!(txt, " 2014 MEIDO-TEK PRODUCTIONS"); + dprintln!(txt, " WWW.PSXDEV.NET"); + dprintln!(txt, "autorotate: {}", autorotate); + //dprintln!(txt, "DEBUG: {:b}", r5.to_bits()); + + let gp = gamepad.poll_p1(); + if autorotate == 0 { + if gp.pressed(Button::L1) { + trans.z -= 4; + } else if gp.pressed(Button::L2) { + trans.z += 4; + } + if gp.pressed(Button::R1) { + rotate.z -= 8; + } else if gp.pressed(Button::R2) { + rotate.z += 8; + } + + if gp.pressed(Button::Triangle) { + trans.y -= 2; + } else if gp.pressed(Button::Cross) { + trans.y += 2; + } + if gp.pressed(Button::Square) { + trans.x -= 4; + } else if gp.pressed(Button::Circle) { + trans.x += 4; + } + + if gp.pressed(Button::Up) { + rotate.y -= 8; + } else if gp.pressed(Button::Down) { + rotate.y += 8; + } + if gp.pressed(Button::Left) { + rotate.x -= 8; + } else if gp.pressed(Button::Right) { + rotate.x += 8; + } + + if gp.pressed(Button::Select) { + rotate = Rotate{x: 0, y: 0, z: 0}; + trans = Trans{x: CENTERX, y: CENTERY, z: 0}; + } + } else { // Autorotate + //rotate.y += 8; // Pan + //rotate.x += 8; // Tilt + rotate.z += 8; // Roll + } + + if gp.pressed(Button::Start) { + if tpressed == 0 { + autorotate = (autorotate + 1) & 1; + rotate = Rotate{x: 0, y: 0, z: 0}; + trans = Trans{x: CENTERX, y: CENTERY, z: 0}; + } + tpressed = 1; + } else { + tpressed = 0; + } + + // TODO: Use trig fns to covnert pitch / yaw / roll / tilt &c to rotation matrix.... + set_rot_matrix(matrix); + + /// Set Translation vector: + delay(); + gte::TRX::new().assign(trans.x as i32).store(); + delay(); + gte::TRY::new().assign(trans.y as i32).store(); + delay(); + gte::TRZ::new().assign(trans.z as i32).store(); + delay(); + delay(); // appears critical + + rot_matrix(&rotate, &mut matrix); + + db = 1 - db; + + let (display, draw) = if db == 1 { (&mut polys_a, &mut polys_b) } else { (&mut polys_b, &mut polys_a) }; + let mut debug_screen_vertex: [Vertex; 3] = [Vertex(0, 0); 3]; + + gpu_dma.send_list_and(display, || { + let mut my_prim = 0; + for i in (0..vec_mesh.len()).step_by(3) { + let mut vertices: [Vertex; 3] = my_prims[my_prim].get_vertices(); + let mut ot_z = rot_trans_pers(vec_mesh[i], &mut vertices[0], &p, &mut flag); + ot_z += rot_trans_pers(vec_mesh[i+1], &mut vertices[1], &p, &mut flag); + ot_z += rot_trans_pers(vec_mesh[i+2], &mut vertices[2], &p, &mut flag); + ot_z /= 3; + //dprintln!(txt, "debug: {:?}", flag); + my_prims[my_prim].set_vertices(vertices); // Do I need my_prims here? just an empty Poly3F? + debug_screen_vertex = vertices; + + draw[my_prim] + .contents.set_vertices(vertices) + .set_color(YELLOW); + my_prim += 1; + } + }); + dprintln!(txt, "last prim: {:?}", debug_screen_vertex); + dprintln!(txt, "FLAG: {:b}", flag); + txt.reset(); + +/* + // Render the sample vector model + t=0; + for (i=0; i 0) && (OTz < OT_LENGTH)) + AddPrim(&myOT[ActivePage][OTz-2], &myPrims[ActivePage][myPrimNum]); + + myPrimNum++; + t+=3; + } +*/ + // Wait for GPU to finish drawing and V-Blank + fb.draw_sync(); + fb.wait_vblank(); + + // Flip buffers and display + fb.dma_swap(&mut gpu_dma); + } +} diff --git a/A.3_TextureWarp/.cargo/config.toml b/A.3_TextureWarp/.cargo/config.toml new file mode 100644 index 0000000..8d98ba9 --- /dev/null +++ b/A.3_TextureWarp/.cargo/config.toml @@ -0,0 +1,2 @@ +[target.mipsel-sony-psx] +runner = "mednafen" diff --git a/A.3_TextureWarp/Cargo.toml b/A.3_TextureWarp/Cargo.toml new file mode 100644 index 0000000..6a1af90 --- /dev/null +++ b/A.3_TextureWarp/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "texturewarp" +version = "0.1.0" +edition = "2021" + +[dependencies] +#psx = { git = "https://github.com/ayrtonm/psx-sdk-rs.git" } +psx = { path = "../../psx-sdk-rs/psx" } diff --git a/A.3_TextureWarp/crate4bit.tim b/A.3_TextureWarp/crate4bit.tim new file mode 100644 index 0000000..467d15f Binary files /dev/null and b/A.3_TextureWarp/crate4bit.tim differ diff --git a/A.3_TextureWarp/crate8bit.tim b/A.3_TextureWarp/crate8bit.tim new file mode 100644 index 0000000..5730285 Binary files /dev/null and b/A.3_TextureWarp/crate8bit.tim differ diff --git a/A.3_TextureWarp/src/main.rs b/A.3_TextureWarp/src/main.rs new file mode 100644 index 0000000..1350bad --- /dev/null +++ b/A.3_TextureWarp/src/main.rs @@ -0,0 +1,157 @@ +#![no_std] +#![no_main] +#![feature(asm_experimental_arch)] + +use psx::constants::*; +use psx::include_tim; +use psx::gpu::primitives::{PolyF4, PolyFT4}; +use psx::gpu::{Bpp, Color, DrawEnv, link_list, Packet, TexCoord, Vertex, VideoMode}; +use psx::hw::gpu::GP0Command; +use psx::{dma, dprintln, Framebuffer}; +use psx::hw::{cop0, Register}; +use psx::hw::cop0::{IntSrc}; +use psx::hw::gte::{VXY0, VZ0, OFX, OFY, OTZ, VZ2, H, FLAG, IR1, IR2, IR3}; + +const ENABLE_GTE: u32 = 1 << 30; +const SQR_OP: u32 = 0x0a00428; + +#[repr(C)] +union PolyF { + flat: PolyF4, + text: PolyFT4, +} + + +pub trait SafeUnionAccess { + fn as_flat(&mut self) -> &mut PolyF4; + fn as_text(&mut self) -> &mut PolyFT4; +} +impl SafeUnionAccess for Packet { // taken from ayrtonm/psx-sdk-rs/tree/main/examples/monkey/src/main.rs + fn as_flat(&mut self) -> &mut PolyF4 { + // SAFETY: We resize the packet to hold a PolyF4 and reset the polygon's command + // to ensure that the union's PolyF4 is in a valid state when we access it + unsafe { self.resize::().contents.flat.reset_cmd() } + } + fn as_text(&mut self) -> &mut PolyFT4 { + unsafe { self.resize::().contents.text.reset_cmd() } + } +} + +impl GP0Command for PolyF {} + + +#[no_mangle] +fn main() { + let mut fb = Framebuffer::new((0, 0), (0, 240), (320, 240), VideoMode::NTSC, Some(Color::new(70,70,70))).unwrap(); + let texture_tim = include_tim!("../crate4bit.tim"); + let bpp = texture_tim.bpp; + let clt = texture_tim.clut.size; + let offset = texture_tim.bmp.offset; + + let mut gpu_dma = dma::GPU::new(); + + let mut db = 0; // display buffer 0 or 1 + + // Set up 2 x ordering tables in a 2x8 array + // using the multi-primitive approach suggested by psx-sdk-rs monkey example + // .. this is still not a primitive buffer tho + let mut ot = [const { Packet::new(PolyF { flat: PolyF4::new() }) }; 16]; + + link_list(&mut ot[0..8]); + link_list(&mut ot[8..16]); + let loaded_font = fb.load_default_font(); + let mut txt = loaded_font.new_text_box((0, 8), (100, 50)); + + let loaded_tim = fb.load_tim(texture_tim); + + // Location and Dimensions of the square + let (x, y) = (132, 132); + let (h, w) = (64, 64); + // Location of the sprite + let (sx, sy) = (148, 148); + // Texture coordinates for the sprite + let tex_coords = [(0, 0 + 48), (0, 64 + 48), (64, 0+48), (64, 64+48)].map(|(x, y)| TexCoord { x, y }); + + let mut status = cop0::Status::new(); + status.assign(ENABLE_GTE).store(); + let otz = OTZ::new(); + let mut hpos = H::new(); + hpos.assign(123).store(); + let cop2r56 = OFX::new(); + + let cop2r1 = VZ0::new(); + + let cop2r57 = OFY::new(); + let cop2r0 = VXY0::new(); + hpos.assign(200); // assign(), but don't .store() + let cop2r5 = VZ2::new(); + + let pos = H::new(); + + // SQR, set up input vector + let mut ir1 = IR1::new(); + ir1.assign(1).store(); + let mut ir2 = IR2::new(); + ir2.assign(3).store(); + let mut ir3 = IR3::new(); + ir3.assign(5).store(); + + // SQR send cop2 + unsafe { + core::arch::asm! { + "nop", + ".long {} & 0x1ffffff | 37 << 25 # cop2", + const SQR_OP, + options(nomem, nostack) + } + } + + // re-read ir1,2,3 for output vector + let out_ir1 = IR1::new(); + let out_ir2 = IR2::new(); + let out_ir3 = IR3::new(); + + let flag = FLAG::new(); + + // Main loop + loop { + let (a, b) = ot.split_at_mut(8); + let (display, draw) = if db == 1 { (a, b) } else { (b, a) }; + dprintln!(txt, "CLUT size:{:?}", clt); + dprintln!(txt, "GTE: {:?}", status.gte_enabled()); + dprintln!(txt, "OFX: {}", cop2r56.to_bits()); + dprintln!(txt, "VZ0: {}", cop2r1.to_bits()); + dprintln!(txt, "OFY: {}", cop2r57.to_bits()); + dprintln!(txt, "VXY0: {}", cop2r0.to_bits()); + dprintln!(txt, "VZ2: {}", cop2r5.to_bits()); + dprintln!(txt, "HPOS: {}", hpos.to_bits()); + dprintln!(txt, "POS: {}", pos.to_bits()); + + dprintln!(txt, "OTZ: {}", otz.to_bits()); + dprintln!(txt, "SQR In X,Y,Z: <{},{},{}>", ir1.to_bits(), ir2.to_bits(), ir3.to_bits()); + dprintln!(txt, "SQR Out X,Y,Z: <{},{},{}>", out_ir1.to_bits(), out_ir2.to_bits(), out_ir3.to_bits()); + dprintln!(txt, "FLAG: {:b}", flag.to_bits()); + txt.reset(); + gpu_dma.send_list_and(display, || { + draw[1] + // TODO: be clear about Vertex and TexCoord ordering! + .as_text().set_vertices([(sx, sy), (sx, sy+h), (sx+w, sy), (sx+w, sy+h)].map(|v| Vertex::new(v))) + .set_color(Color::new(255, 255, 255)) + .set_tex_page(loaded_tim.tex_page) + .set_tex_coords(tex_coords) + .set_clut(loaded_tim.clut.unwrap()); + draw[0] + .as_flat().set_vertices([Vertex(x, y), Vertex(x+w, y), Vertex(x, y+h), Vertex(x+w, y+h)]) + .set_color(Color::new(23, 24, 78)); + }); + + // Wait for GPU to finish drawing and V-Blank + fb.draw_sync(); + fb.wait_vblank(); + + // Flip buffers and display + fb.dma_swap(&mut gpu_dma); + // switch display / draw ot lists + db = 1 - db; + } +}