├── .cargo └── config.toml ├── .github └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── examples ├── colored-tri │ ├── Cargo.lock │ ├── Cargo.toml │ ├── build.rs │ ├── powerpc-unknown-eabi.json │ └── src │ │ └── main.rs ├── embedded-graphics-wii │ ├── Cargo.lock │ ├── Cargo.toml │ ├── README.md │ ├── build.rs │ ├── powerpc-unknown-eabi.json │ └── src │ │ ├── display.rs │ │ └── main.rs ├── ios │ ├── Cargo.lock │ ├── Cargo.toml │ ├── build.rs │ ├── powerpc-unknown-eabi.json │ └── src │ │ └── main.rs ├── obj-loading │ ├── Cargo.lock │ ├── Cargo.toml │ ├── build.rs │ ├── powerpc-unknown-eabi.json │ ├── src │ │ ├── assets │ │ │ ├── untitled.mtl │ │ │ └── untitled.obj │ │ ├── main.rs │ │ └── obj.rs │ ├── vendor │ │ └── gctex │ │ │ ├── .gitignore │ │ │ ├── Cargo.lock │ │ │ ├── Cargo.toml │ │ │ ├── README.md │ │ │ └── src │ │ │ └── lib.rs │ └── white.png ├── template │ ├── Cargo.lock │ ├── Cargo.toml │ ├── README.md │ ├── build.rs │ ├── mp3.mp3 │ ├── powerpc-unknown-eabi.json │ └── src │ │ └── main.rs └── texture-tri │ ├── Cargo.lock │ ├── Cargo.toml │ ├── build.rs │ ├── powerpc-unknown-eabi.json │ ├── src │ └── main.rs │ ├── vendor │ └── gctex │ │ ├── .gitignore │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src │ │ └── lib.rs │ └── white.png ├── ogc-sys ├── Cargo.lock ├── Cargo.toml ├── README.md ├── build.rs ├── src │ ├── inline.rs │ ├── lib.rs │ └── ogc.rs └── wrapper.h ├── powerpc-unknown-eabi.json └── src ├── aesnd.rs ├── asnd.rs ├── audio.rs ├── cache.rs ├── console.rs ├── debug.rs ├── error.rs ├── glam_impl.rs ├── gu.rs ├── gx ├── mod.rs ├── regs.rs └── types.rs ├── input ├── controller.rs ├── mod.rs ├── pad.rs └── wpad.rs ├── ios.rs ├── ios ├── dolphin.rs └── fs.rs ├── lib.rs ├── lwp.rs ├── mmio ├── command_processor.rs ├── di.rs ├── dsp.rs ├── mi.rs ├── mod.rs ├── pe.rs ├── processor_interface.rs ├── serial_interface.rs └── vi.rs ├── mp3player.rs ├── mutex.rs ├── network.rs ├── runtime.rs ├── system.rs ├── time.rs ├── tpl.rs ├── utils.rs └── video.rs /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "powerpc-unknown-eabi.json" 3 | 4 | [unstable] 5 | build-std = ["core", "alloc"] -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | # Rust CI 2 | 3 | on: [push, pull_request] 4 | 5 | name: Rust CI 6 | 7 | jobs: 8 | check: 9 | name: Check 10 | runs-on: ubuntu-latest 11 | container: 12 | image: "devkitpro/devkitppc" 13 | steps: 14 | - name: Install required packages 15 | run: | 16 | sudo apt-get update 17 | sudo apt-get install -y gcc libc6-dev nodejs clang 18 | 19 | - name: Checkout sources 20 | uses: actions/checkout@v2 21 | 22 | - name: Install nightly toolchain 23 | uses: actions-rs/toolchain@v1 24 | with: 25 | toolchain: nightly 26 | components: rust-src 27 | 28 | - name: Run cargo check 29 | uses: actions-rs/cargo@v1 30 | with: 31 | command: check 32 | 33 | lints: 34 | name: Lints 35 | runs-on: ubuntu-latest 36 | container: 37 | image: "devkitpro/devkitppc" 38 | steps: 39 | - name: Install required packages 40 | run: | 41 | sudo apt-get update 42 | sudo apt-get install -y gcc libc6-dev nodejs clang 43 | 44 | - name: Checkout sources 45 | uses: actions/checkout@v2 46 | 47 | - name: Install nightly toolchain 48 | uses: actions-rs/toolchain@v1 49 | with: 50 | toolchain: nightly 51 | override: true 52 | components: clippy, rust-src 53 | 54 | - name: Run cargo clippy 55 | uses: actions-rs/cargo@v1 56 | with: 57 | command: clippy 58 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /**/target/ 4 | 5 | # These are backup files generated by rustfmt 6 | **/*.rs.bk 7 | **/*.dol 8 | **/*.o 9 | 10 | # Temporary files created by editors 11 | **/*.swp 12 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ogc-rs" 3 | version = "0.1.1" 4 | authors = ["rust-wii"] 5 | edition = "2021" 6 | license = "MIT" 7 | readme = "README.md" 8 | description = "A Rust wrapper library for devkitPro's libogc" 9 | documentation = "https://docs.rs/ogc-rs/" 10 | homepage = "https://github.com/rust-wii/ogc-rs" 11 | repository = "https://github.com/rust-wii/ogc-rs" 12 | keywords = ["wii", "embedded", "no-std"] 13 | 14 | [lib] 15 | crate-type = ["rlib"] 16 | 17 | 18 | [features] 19 | default = ["default_alloc_handler", "default_panic_handler"] 20 | ffi = [] 21 | mmio = [] 22 | glam_compat = ["glam"] 23 | default_alloc_handler = [] 24 | default_panic_handler = [] 25 | 26 | [dependencies] 27 | bitflags = "1.3" 28 | num_enum = { version = "0.5", default-features = false } 29 | cfg-if = "1.0" 30 | libc = "0.2" 31 | ogc-sys = { path = "./ogc-sys/"} 32 | glam = { version = "0.19.0", default-features = false, features = ["libm"], optional = true } 33 | voladdress = "1.4" 34 | bit_field = "0.10.1" 35 | num-traits = { version = "0.2.19", default-features = false, features = ["libm"] } 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019-2021 rust-wii 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ogc-rs 2 | 3 | ![Crates.io](https://img.shields.io/crates/v/ogc-rs) 4 | 5 | A Rust wrapper library for devkitPro's [libogc](https://github.com/devkitPro/libogc). 6 | 7 | To get started, you'll first need to install the following dependencies on your system: 8 | * [Rust, along with rustup and cargo](https://www.rust-lang.org/tools/install) 9 | * the Clang compiler 10 | * from your local package manager, 11 | * or from [LLVM themselves](https://clang.llvm.org/get_started.html) 12 | * [devkitPro toolchain](https://devkitpro.org/wiki/Getting_Started) 13 | 14 | Then you'll need to fork this repo and `git clone` your fork into your local machine. 15 | 16 | When that's done, do the following: 17 | 18 | ```sh 19 | $ cd ogc-rs 20 | $ rustup override set nightly 21 | $ rustup component add rust-src 22 | $ cargo check 23 | ``` 24 | 25 | If everything's working properly, `cargo check` should run successfully. 26 | 27 | See the [Wii testing project](https://github.com/rust-wii/testing-project) for an example on how to use this library. 28 | 29 | ## Structure 30 | 31 | This repository is organized as follows: 32 | 33 | * `ogc-rs`: Safe, idiomatic wrapper around `ogc-sys`. 34 | * `ogc-sys`: Low-level, unsafe bindings to libogc. 35 | 36 | ## License 37 | 38 | See [LICENSE](LICENSE) for more details. 39 | -------------------------------------------------------------------------------- /examples/colored-tri/Cargo.toml: -------------------------------------------------------------------------------- 1 | 2 | [package] 3 | name = "template" 4 | version = "0.1.0" 5 | edition = "2021" 6 | 7 | [profile] 8 | dev = { panic = "abort" } 9 | release = { panic = "abort", lto = true, codegen-units = 1, strip = "symbols", opt-level = "s" } 10 | 11 | [dependencies] 12 | ogc-rs = { path = "../../", features = ["ffi"] } 13 | -------------------------------------------------------------------------------- /examples/colored-tri/build.rs: -------------------------------------------------------------------------------- 1 | use std::process::Command; 2 | fn main() { 3 | let dkp_path = std::env::var("DEVKITPRO").expect("Please set $DEVKITPRO"); 4 | println!("cargo:rustc-link-search=native={}/libogc/lib/wii", dkp_path); 5 | 6 | //checks if the build folder exists. If it does, it deletes it. 7 | let _ = std::fs::remove_dir_all("build"); 8 | 9 | let _ = std::fs::create_dir("build"); 10 | 11 | 12 | let libgcc_location = match Command::new("powerpc-eabi-gcc").arg("-print-libgcc-file-name").output() { 13 | Ok(output) => output, 14 | Err(_e) => panic!("Could not find powerpc-eabi-gcc or the libgcc on the host machine!"), 15 | }; 16 | let output = libgcc_location.stdout; 17 | let parsed_output = 18 | String::from_utf8(output).expect("powerpc-eabi-gcc command output returned a non-utf8 string.").replace("\n", ""); 19 | 20 | let _ = match Command::new("powerpc-eabi-ar").arg("x").arg(parsed_output).arg("crtresxfpr.o").arg("crtresxgpr.o").output() { 21 | Ok(output) => output, 22 | Err(_e) => panic!("powerpc-eabi-ar command failed"), 23 | }; 24 | 25 | std::fs::rename("crtresxgpr.o", "build/crtresxgpr.o").expect("Could not move crtresxgpr.o"); 26 | std::fs::rename("crtresxfpr.o", "build/crtresxfpr.o").expect("Could not move crtresxfpr.o"); 27 | 28 | println!("cargo::rustc-link-arg=build/crtresxgpr.o"); 29 | println!("cargo::rustc-link-arg=build/crtresxfpr.o"); 30 | } 31 | -------------------------------------------------------------------------------- /examples/colored-tri/powerpc-unknown-eabi.json: -------------------------------------------------------------------------------- 1 | { 2 | "arch": "powerpc", 3 | "cpu": "750", 4 | "data-layout": "E-m:e-p:32:32-Fn32-i64:64-n32", 5 | "dynamic-linking": false, 6 | "env": "newlib", 7 | "executables": true, 8 | "has-elf-tls": false, 9 | "has-rpath": true, 10 | "llvm-target": "powerpc-unknown-eabi", 11 | "linker": "powerpc-eabi-gcc", 12 | "linker-flavor": "gcc", 13 | "linker-is-gnu": true, 14 | "os": "rvl-ios", 15 | "pre-link-args": { 16 | "gcc": [ 17 | "-mrvl", 18 | "-meabi", 19 | "-mhard-float" 20 | ] 21 | }, 22 | "panic-strategy": "abort", 23 | "relocation-model": "static", 24 | "target-endian": "big", 25 | "target-family": "unix", 26 | "target-mcount": "_mcount", 27 | "target-c-int-width": "32", 28 | "target-pointer-width": "32", 29 | "vendor": "nintendo" 30 | } 31 | 32 | -------------------------------------------------------------------------------- /examples/colored-tri/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![feature(start)] 3 | 4 | use core::mem::ManuallyDrop; 5 | 6 | use ogc_rs::{ 7 | ffi::{ 8 | GX_CLR_RGBA, GX_COLOR0A0, GX_PASSCLR, GX_POS_XYZ, GX_RGBA8, GX_S16, GX_TEXCOORDNULL, 9 | GX_TEXMAP_NULL, GX_VA_CLR0, GX_VA_POS, 10 | }, 11 | gu::{Gu, RotationAxis}, 12 | gx::{types::VtxDest, CmpFn, Color, CullMode, Gx, Primitive, ProjectionType, VtxAttr}, 13 | video::Video, 14 | }; 15 | 16 | extern crate alloc; 17 | 18 | #[start] 19 | fn main(_argc: isize, _argv: *const *const u8) -> isize { 20 | let vi = Video::init(); 21 | let mut config = Video::get_preferred_mode(); 22 | 23 | Video::configure(&config); 24 | unsafe { Video::set_next_framebuffer(vi.framebuffer) }; 25 | Video::set_black(false); 26 | Video::flush(); 27 | 28 | let fifo = ManuallyDrop::new(Gx::init(256 * 1024)); 29 | // Set values to use when video is flipped / cleared 30 | Gx::set_copy_clear(Color::new(0x00, 0x00, 0x00), 0x00_FF_FF_FF); 31 | 32 | Gx::set_viewport( 33 | 0.0, 34 | 0.0, 35 | config.framebuffer_width.into(), 36 | config.embed_framebuffer_height.into(), 37 | 0., 38 | 1., 39 | ); 40 | Gx::set_disp_copy_y_scale( 41 | (config.extern_framebuffer_height / config.embed_framebuffer_height).into(), 42 | ); 43 | Gx::set_scissor( 44 | 0, 45 | 0, 46 | config.framebuffer_width.into(), 47 | config.embed_framebuffer_height.into(), 48 | ); 49 | Gx::set_disp_copy_src( 50 | 0, 51 | 0, 52 | config.framebuffer_width, 53 | config.embed_framebuffer_height, 54 | ); 55 | Gx::set_disp_copy_dst(config.framebuffer_width, config.extern_framebuffer_height); 56 | Gx::set_copy_filter( 57 | config.anti_aliasing != 0, 58 | &mut config.sample_pattern, 59 | true, 60 | &mut config.v_filter, 61 | ); 62 | 63 | let val = if config.vi_height == 2 * config.extern_framebuffer_height { 64 | false 65 | } else { 66 | true 67 | }; 68 | 69 | Gx::set_field_mode(config.field_rendering != 0, val); 70 | Gx::set_cull_mode(CullMode::None); 71 | unsafe { Gx::copy_disp(vi.framebuffer, true) }; 72 | 73 | let mut mat = [[0.; 4]; 4]; 74 | Gu::perspective(&mut mat, 60., 4. / 3., 10., 300.); 75 | Gx::load_projection_mtx(&mat, ProjectionType::Perspective); 76 | Gx::clear_vtx_desc(); 77 | Gx::set_vtx_desc(VtxAttr::Pos, VtxDest::INDEX8); 78 | Gx::set_vtx_desc(VtxAttr::Color0, VtxDest::INDEX8); 79 | Gx::set_vtx_attr_fmt(0, VtxAttr::Pos, GX_POS_XYZ, GX_S16, 0); 80 | Gx::set_vtx_attr_fmt(0, VtxAttr::Color0, GX_CLR_RGBA, GX_RGBA8, 0); 81 | 82 | let positions: [[i16; 3]; 3] = [[0, 15, 0], [-15, -15, 0], [15, -15, 0]]; 83 | let colors: [[u8; 4]; 3] = [[255, 0, 0, 255], [0, 255, 0, 255], [0, 0, 255, 255]]; 84 | 85 | Gx::set_array( 86 | GX_VA_POS, 87 | &positions, 88 | core::mem::size_of::<[i16; 3]>().try_into().unwrap(), 89 | ); 90 | 91 | Gx::set_array( 92 | GX_VA_CLR0, 93 | &colors, 94 | core::mem::size_of::<[u8; 4]>().try_into().unwrap(), 95 | ); 96 | 97 | Gx::set_num_chans(1); 98 | Gx::set_num_tex_gens(0); 99 | 100 | Gx::set_tev_order( 101 | 0, 102 | GX_TEXCOORDNULL.try_into().unwrap(), 103 | GX_TEXMAP_NULL, 104 | GX_COLOR0A0.try_into().unwrap(), 105 | ); 106 | Gx::set_tev_op(0, GX_PASSCLR.try_into().unwrap()); 107 | 108 | let mut i: u16 = 0; 109 | loop { 110 | let mut mtx = [[0.; 4]; 3]; 111 | let mut rot_mtx = [[0.; 4]; 3]; 112 | let mut mdl_mtx = [[0.; 4]; 3]; 113 | let mut mdl2_mtx = [[0.; 4]; 3]; 114 | 115 | Gu::mtx_identity(&mut mtx); 116 | Gu::mtx_identity(&mut rot_mtx); 117 | Gu::mtx_identity(&mut mdl_mtx); 118 | 119 | Gu::mtx_rotation_radians( 120 | &mut rot_mtx, 121 | RotationAxis::Y, 122 | f32::from(i) * (3.14159 / 180.), 123 | ); 124 | // Rotation + Identity = Rotation; 125 | Gu::mtx_concat(&mut rot_mtx, &mut mdl_mtx, &mut mdl2_mtx); 126 | // Rotation + Translation = Model; 127 | Gu::mtx_translation_apply(&mut mdl2_mtx, &mut mdl_mtx, (0., 0., -50.)); 128 | // Load Model 129 | Gx::load_pos_mtx_imm(&mut mdl_mtx, 0); 130 | 131 | Gx::begin(Primitive::Triangles, 0, 3); 132 | Gx::position1x8(0); 133 | Gx::color1x8(0); 134 | Gx::position1x8(1); 135 | Gx::color1x8(1); 136 | Gx::position1x8(2); 137 | Gx::color1x8(2); 138 | 139 | /* 140 | Gx::position_3i16(0, 15, 0); 141 | Gx::color_4u8(255, 0, 0, 255); 142 | Gx::position_3i16(-15, -15, 0); 143 | Gx::color_4u8(0, 255, 0, 255); 144 | Gx::position_3i16(15, -15, 0); 145 | Gx::color_4u8(0, 0, 255, 255); 146 | */ 147 | Gx::end(); 148 | 149 | Gx::draw_done(); 150 | Gx::set_z_mode(true, CmpFn::LessEq, true); 151 | Gx::set_color_update(true); 152 | unsafe { Gx::copy_disp(vi.framebuffer, true) }; 153 | Gx::flush(); 154 | 155 | Video::wait_vsync(); 156 | i += 1; 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /examples/embedded-graphics-wii/Cargo.toml: -------------------------------------------------------------------------------- 1 | 2 | [package] 3 | name = "embedded-graphics-wii" 4 | version = "0.1.0" 5 | edition = "2021" 6 | 7 | [profile] 8 | dev = { panic = "abort" } 9 | release = { panic = "abort", lto = true, codegen-units = 1, strip = "symbols", opt-level = "s" } 10 | 11 | [dependencies] 12 | ogc-rs = { path = "../../", features = ["ffi"] } 13 | embedded-graphics = "0.7" 14 | 15 | -------------------------------------------------------------------------------- /examples/embedded-graphics-wii/README.md: -------------------------------------------------------------------------------- 1 | # Embedded Graphics showcase for [ogc-rs](https://github.com/rust-wii/ogc-rs) 2 | 3 | Implements some of the [embedded-graphics](https://crates.io/crates/embedded-graphics) API for the Wii. 4 | 5 | `cargo +nightly build -Zbuild-std=core,alloc --target powerpc-unknown-eabi.json` to compile. 6 | `elf2dol` is used to convert them to a format that Wii & Gamecube can use. 7 | -------------------------------------------------------------------------------- /examples/embedded-graphics-wii/build.rs: -------------------------------------------------------------------------------- 1 | use std::process::Command; 2 | fn main() { 3 | let dkp_path = std::env::var("DEVKITPRO").expect("Please set $DEVKITPRO"); 4 | println!("cargo:rustc-link-search=native={}/libogc/lib/wii", dkp_path); 5 | 6 | //checks if the build folder exists. If it does, it deletes it. 7 | let _ = std::fs::remove_dir_all("build"); 8 | 9 | let _ = std::fs::create_dir("build"); 10 | 11 | 12 | let libgcc_location = match Command::new("powerpc-eabi-gcc").arg("-print-libgcc-file-name").output() { 13 | Ok(output) => output, 14 | Err(_e) => panic!("Could not find powerpc-eabi-gcc or the libgcc on the host machine!"), 15 | }; 16 | let output = libgcc_location.stdout; 17 | let parsed_output = 18 | String::from_utf8(output).expect("powerpc-eabi-gcc command output returned a non-utf8 string.").replace("\n", ""); 19 | 20 | let _ = match Command::new("powerpc-eabi-ar").arg("x").arg(parsed_output).arg("crtresxfpr.o").arg("crtresxgpr.o").output() { 21 | Ok(output) => output, 22 | Err(_e) => panic!("powerpc-eabi-ar command failed"), 23 | }; 24 | 25 | std::fs::rename("crtresxgpr.o", "build/crtresxgpr.o").expect("Could not move crtresxgpr.o"); 26 | std::fs::rename("crtresxfpr.o", "build/crtresxfpr.o").expect("Could not move crtresxfpr.o"); 27 | 28 | println!("cargo::rustc-link-arg=build/crtresxgpr.o"); 29 | println!("cargo::rustc-link-arg=build/crtresxfpr.o"); 30 | } 31 | -------------------------------------------------------------------------------- /examples/embedded-graphics-wii/powerpc-unknown-eabi.json: -------------------------------------------------------------------------------- 1 | { 2 | "arch": "powerpc", 3 | "cpu": "750", 4 | "data-layout": "E-m:e-p:32:32-Fn32-i64:64-n32", 5 | "dynamic-linking": false, 6 | "env": "newlib", 7 | "executables": true, 8 | "has-elf-tls": false, 9 | "has-rpath": true, 10 | "llvm-target": "powerpc-unknown-eabi", 11 | "linker": "powerpc-eabi-gcc", 12 | "linker-flavor": "gcc", 13 | "linker-is-gnu": true, 14 | "os": "rvl-ios", 15 | "pre-link-args": { 16 | "gcc": [ 17 | "-mrvl", 18 | "-meabi", 19 | "-mhard-float" 20 | ] 21 | }, 22 | "panic-strategy": "abort", 23 | "relocation-model": "static", 24 | "target-endian": "big", 25 | "target-family": "unix", 26 | "target-mcount": "_mcount", 27 | "target-c-int-width": "32", 28 | "target-pointer-width": "32", 29 | "vendor": "nintendo" 30 | } 31 | 32 | -------------------------------------------------------------------------------- /examples/embedded-graphics-wii/src/display.rs: -------------------------------------------------------------------------------- 1 | use core::{convert::TryInto, ffi::c_void}; 2 | 3 | use embedded_graphics::{ 4 | draw_target::DrawTarget, 5 | pixelcolor::Rgb888, 6 | prelude::{OriginDimensions, RgbColor, Size}, 7 | primitives::Rectangle, 8 | Pixel, 9 | }; 10 | use ogc_rs::{ 11 | ffi::{ 12 | GX_CLR_RGBA, GX_COLOR0A0, GX_F32, GX_PASSCLR, GX_PNMTX0, GX_POS_XYZ, GX_RGBA8, 13 | GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_TEX_ST, GX_VTXFMT0, 14 | }, 15 | gx::types::{Gamma, PixelFormat, VtxDest, ZFormat}, 16 | prelude::*, 17 | }; 18 | 19 | pub struct Display; 20 | impl Display { 21 | pub fn new(fifo_size: usize) -> Self { 22 | Gx::init(fifo_size); 23 | Self 24 | } 25 | 26 | pub fn flush(&self, framebuffer: *mut c_void) { 27 | Gx::draw_done(); 28 | Gx::set_z_mode(true, CmpFn::LessEq, true); 29 | unsafe { 30 | Gx::copy_disp(framebuffer, true); 31 | } 32 | } 33 | 34 | pub fn setup(&self, rc: &mut RenderConfig) { 35 | let mut ident: Mat3x4 = Mat3x4::IDENTITY; 36 | Gx::set_copy_clear(Color::with_alpha(0, 0, 0, 0), 0x00ffffff); 37 | Gx::set_pixel_fmt(PixelFormat::RGB8_Z24, ZFormat::LINEAR); 38 | Gx::set_viewport( 39 | 0.0, 40 | 0.0, 41 | rc.framebuffer_width as f32, 42 | rc.embed_framebuffer_height as f32, 43 | 0.0, 44 | 0.0, 45 | ); 46 | 47 | let yscale = 48 | Gx::get_y_scale_factor(rc.embed_framebuffer_height, rc.extern_framebuffer_height); 49 | let extern_framebuffer_height = Gx::set_disp_copy_y_scale(yscale) as u16; 50 | 51 | let half_aspect_ratio = rc.vi_height == 2 * rc.extern_framebuffer_height; 52 | 53 | Gx::set_disp_copy_src(0, 0, rc.framebuffer_width, rc.embed_framebuffer_height); 54 | Gx::set_disp_copy_dst(rc.framebuffer_width, extern_framebuffer_height); 55 | Gx::set_copy_filter( 56 | rc.anti_aliasing != 0, 57 | &mut rc.sample_pattern, 58 | true, 59 | &mut rc.v_filter, 60 | ); 61 | Gx::set_field_mode(rc.field_rendering != 0, half_aspect_ratio); 62 | Gx::set_disp_copy_gamma(Gamma::ONE_ZERO); 63 | 64 | //Clear VTX 65 | Gx::clear_vtx_desc(); 66 | Gx::inv_vtx_cache(); 67 | Gx::invalidate_tex_all(); 68 | 69 | Gx::set_vtx_desc(VtxAttr::Tex0, VtxDest::NONE); 70 | Gx::set_vtx_desc(VtxAttr::Pos, VtxDest::DIRECT); 71 | Gx::set_vtx_desc(VtxAttr::Color0, VtxDest::DIRECT); 72 | 73 | Gx::set_vtx_attr_fmt(0, VtxAttr::Pos, GX_POS_XYZ as _, GX_F32 as _, 0); 74 | Gx::set_vtx_attr_fmt(0, VtxAttr::Tex0, GX_TEX_ST as _, GX_F32 as _, 0); 75 | Gx::set_vtx_attr_fmt(0, VtxAttr::Color0, GX_CLR_RGBA as _, GX_RGBA8 as _, 0); 76 | Gx::set_z_mode(true, CmpFn::LessEq, true); 77 | 78 | Gx::set_num_chans(1); 79 | Gx::set_num_tex_gens(1); 80 | Gx::set_tev_op(GX_TEVSTAGE0 as _, GX_PASSCLR as _); 81 | Gx::set_tev_order( 82 | GX_TEVSTAGE0 as _, 83 | GX_TEXCOORD0 as _, 84 | GX_TEXMAP0 as _, 85 | GX_COLOR0A0 as _, 86 | ); 87 | ident.gu_translation_apply((0., 0., -100.)); 88 | ident.load_as_pos_mtx(GX_PNMTX0 as _); 89 | let mut perspective: Mat4 = Mat4::gu_ortho( 90 | 0.0, 91 | rc.embed_framebuffer_height as f32, 92 | 0.0, 93 | rc.framebuffer_width as f32, 94 | 0.0, 95 | 1000.0, 96 | ); 97 | perspective.load_as_proj_mat(ProjectionType::Orthographic); 98 | 99 | Gx::set_viewport( 100 | 0.0, 101 | 0.0, 102 | rc.framebuffer_width as f32, 103 | rc.embed_framebuffer_height as f32, 104 | 0.0, 105 | 1.0, 106 | ); 107 | Gx::set_blend_mode( 108 | BlendMode::Blend, 109 | BlendCtrl::SrcAlpha, 110 | BlendCtrl::InvSrcAlpha, 111 | LogicOp::Clear, 112 | ); 113 | Gx::set_alpha_update(true); 114 | Gx::set_alpha_compare(CmpFn::Greater, 0, AlphaOp::And, CmpFn::Always, 0); 115 | Gx::set_color_update(true); 116 | Gx::set_cull_mode(CullMode::None); 117 | 118 | Gx::enable_clip(); 119 | Gx::set_scissor( 120 | 0, 121 | 0, 122 | rc.framebuffer_width.into(), 123 | rc.embed_framebuffer_height.into(), 124 | ); 125 | } 126 | } 127 | 128 | impl DrawTarget for Display { 129 | type Color = Rgb888; 130 | type Error = core::convert::Infallible; 131 | 132 | fn draw_iter(&mut self, pixels: I) -> Result<(), Self::Error> 133 | where 134 | I: IntoIterator>, 135 | { 136 | for Pixel(coord, color) in pixels.into_iter() { 137 | if let Ok((x @ 0..=639, y @ 0..=527)) = coord.try_into() { 138 | let poke_x: u32 = x; 139 | let poke_y: u32 = y; 140 | Gx::poke_argb( 141 | poke_x as u16, 142 | poke_y as u16, 143 | Color::new(color.r(), color.g(), color.b()), 144 | ) 145 | } 146 | } 147 | 148 | Ok(()) 149 | } 150 | 151 | fn fill_solid(&mut self, area: &Rectangle, color: Self::Color) -> Result<(), Self::Error> { 152 | Gx::begin(Primitive::Quads, GX_VTXFMT0 as _, 4); 153 | Gx::position_3f32(area.top_left.x as _, area.top_left.y as _, 0.0); 154 | 155 | Gx::color_4u8(color.r(), color.g(), color.b(), 255); 156 | 157 | Gx::position_3f32( 158 | area.bottom_right().unwrap().x as _, 159 | area.top_left.y as _, 160 | 0.0, 161 | ); 162 | 163 | Gx::color_4u8(color.r(), color.g(), color.b(), 255); 164 | 165 | Gx::position_3f32( 166 | area.bottom_right().unwrap().x as _, 167 | area.bottom_right().unwrap().y as _, 168 | 0.0, 169 | ); 170 | 171 | Gx::color_4u8(color.r(), color.g(), color.b(), 255); 172 | 173 | Gx::position_3f32( 174 | area.top_left.x as _, 175 | area.bottom_right().unwrap().y as _, 176 | 0.0, 177 | ); 178 | 179 | Gx::color_4u8(color.r(), color.g(), color.b(), 255); 180 | 181 | Gx::end(); 182 | 183 | Ok(()) 184 | } 185 | } 186 | 187 | impl OriginDimensions for Display { 188 | fn size(&self) -> Size { 189 | Size::new(640, 528) 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /examples/embedded-graphics-wii/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![feature(start)] 3 | 4 | mod display; 5 | use crate::display::Display; 6 | 7 | extern crate alloc; 8 | 9 | use embedded_graphics::{ 10 | pixelcolor::Rgb888, 11 | prelude::{Point, Primitive, RgbColor, Size, Transform}, 12 | primitives::{PrimitiveStyle, Rectangle}, 13 | Drawable, 14 | }; 15 | 16 | use ogc_rs::prelude::*; 17 | 18 | #[start] 19 | fn main(_argc: isize, _argv: *const *const u8) -> isize { 20 | let mut video = Video::init(); 21 | Input::init(ControllerType::Gamecube); 22 | Input::init(ControllerType::Wii); 23 | 24 | let gcn_ctrl = Input::new(ControllerType::Gamecube, ControllerPort::One); 25 | let wii_ctrl = Input::new(ControllerType::Wii, ControllerPort::One); 26 | wii_ctrl 27 | .as_wpad() 28 | .set_data_format(WPadDataFormat::ButtonsAccelIR); 29 | 30 | Console::init(&video); 31 | Video::configure(&video.render_config); 32 | unsafe { 33 | Video::set_next_framebuffer(video.framebuffer); 34 | } 35 | Video::set_black(true); 36 | Video::flush(); 37 | Video::wait_vsync(); 38 | 39 | let mut wii_display = Display::new(256 * 1024); 40 | wii_display.setup(&mut video.render_config); 41 | Video::set_black(false); 42 | 43 | println!("Hello World!"); 44 | 45 | const BACKGROUND: Rectangle = Rectangle::new(Point::zero(), Size::new(640, 528)); 46 | 47 | const CENTER: Point = Point::new(640 / 2, 528 / 2); 48 | let area: Rectangle = Rectangle::with_center(CENTER, Size::new(200, 200)); 49 | 50 | const POINTER: Rectangle = Rectangle::new(Point::zero(), Size::new_equal(10)); 51 | loop { 52 | Input::update(ControllerType::Gamecube); 53 | Input::update(ControllerType::Wii); 54 | 55 | if gcn_ctrl.is_button_down(Button::Start) { 56 | break 0; 57 | } 58 | 59 | if wii_ctrl.is_button_down(Button::Home) { 60 | break 0; 61 | } 62 | Gx::set_viewport( 63 | 0.0, 64 | 0.0, 65 | video.render_config.framebuffer_width as f32, 66 | video.render_config.embed_framebuffer_height as f32, 67 | 0.0, 68 | 0.0, 69 | ); 70 | 71 | let ir = Point::new( 72 | wii_ctrl.as_wpad().ir().0 as i32, 73 | wii_ctrl.as_wpad().ir().1 as i32, 74 | ); 75 | 76 | BACKGROUND 77 | .into_styled(PrimitiveStyle::with_fill(Rgb888::WHITE)) 78 | .draw(&mut wii_display) 79 | .unwrap(); 80 | 81 | if POINTER.translate(ir).intersection(&area).size != Size::zero() { 82 | area.into_styled(PrimitiveStyle::with_fill(Rgb888::RED)) 83 | .draw(&mut wii_display) 84 | .unwrap(); 85 | if wii_ctrl.is_button_held(Button::A) { 86 | area.into_styled(PrimitiveStyle::with_fill(Rgb888::GREEN)) 87 | .draw(&mut wii_display) 88 | .unwrap(); 89 | } 90 | } else { 91 | area.into_styled(PrimitiveStyle::with_fill(Rgb888::BLUE)) 92 | .draw(&mut wii_display) 93 | .unwrap(); 94 | } 95 | 96 | POINTER 97 | .translate(ir) 98 | .into_styled(PrimitiveStyle::with_fill(Rgb888::CYAN)) 99 | .draw(&mut wii_display) 100 | .unwrap(); 101 | 102 | wii_display.flush(video.framebuffer); 103 | 104 | unsafe { 105 | Video::set_next_framebuffer(video.framebuffer); 106 | } 107 | Video::flush(); 108 | Video::wait_vsync(); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /examples/ios/Cargo.toml: -------------------------------------------------------------------------------- 1 | 2 | [package] 3 | name = "template" 4 | version = "0.1.0" 5 | edition = "2021" 6 | 7 | [profile] 8 | dev = { panic = "abort" } 9 | release = { panic = "abort", lto = true, codegen-units = 1, strip = "symbols", opt-level = "s" } 10 | 11 | [dependencies] 12 | ogc-rs = { path = "../../" } 13 | -------------------------------------------------------------------------------- /examples/ios/build.rs: -------------------------------------------------------------------------------- 1 | use std::process::Command; 2 | fn main() { 3 | let dkp_path = std::env::var("DEVKITPRO").expect("Please set $DEVKITPRO"); 4 | println!("cargo:rustc-link-search=native={}/libogc/lib/wii", dkp_path); 5 | 6 | //checks if the build folder exists. If it does, it deletes it. 7 | let _ = std::fs::remove_dir_all("build"); 8 | 9 | let _ = std::fs::create_dir("build"); 10 | 11 | 12 | let libgcc_location = match Command::new("powerpc-eabi-gcc").arg("-print-libgcc-file-name").output() { 13 | Ok(output) => output, 14 | Err(_e) => panic!("Could not find powerpc-eabi-gcc or the libgcc on the host machine!"), 15 | }; 16 | let output = libgcc_location.stdout; 17 | let parsed_output = 18 | String::from_utf8(output).expect("powerpc-eabi-gcc command output returned a non-utf8 string.").replace("\n", ""); 19 | 20 | let _ = match Command::new("powerpc-eabi-ar").arg("x").arg(parsed_output).arg("crtresxfpr.o").arg("crtresxgpr.o").output() { 21 | Ok(output) => output, 22 | Err(_e) => panic!("powerpc-eabi-ar command failed"), 23 | }; 24 | 25 | std::fs::rename("crtresxgpr.o", "build/crtresxgpr.o").expect("Could not move crtresxgpr.o"); 26 | std::fs::rename("crtresxfpr.o", "build/crtresxfpr.o").expect("Could not move crtresxfpr.o"); 27 | 28 | println!("cargo::rustc-link-arg=build/crtresxgpr.o"); 29 | println!("cargo::rustc-link-arg=build/crtresxfpr.o"); 30 | } 31 | -------------------------------------------------------------------------------- /examples/ios/powerpc-unknown-eabi.json: -------------------------------------------------------------------------------- 1 | { 2 | "arch": "powerpc", 3 | "cpu": "750", 4 | "data-layout": "E-m:e-p:32:32-Fn32-i64:64-n32", 5 | "dynamic-linking": false, 6 | "env": "newlib", 7 | "executables": true, 8 | "has-elf-tls": false, 9 | "has-rpath": true, 10 | "llvm-target": "powerpc-unknown-eabi", 11 | "linker": "powerpc-eabi-gcc", 12 | "linker-flavor": "gcc", 13 | "linker-is-gnu": true, 14 | "os": "rvl-ios", 15 | "pre-link-args": { 16 | "gcc": [ 17 | "-mrvl", 18 | "-meabi", 19 | "-mhard-float" 20 | ] 21 | }, 22 | "panic-strategy": "abort", 23 | "relocation-model": "static", 24 | "target-endian": "big", 25 | "target-family": "unix", 26 | "target-mcount": "_mcount", 27 | "target-c-int-width": "32", 28 | "target-pointer-width": "32", 29 | "vendor": "nintendo" 30 | } 31 | 32 | -------------------------------------------------------------------------------- /examples/ios/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use alloc::vec; 5 | use ogc_rs::{ 6 | ios::{self, Mode, SeekMode}, 7 | print, println, 8 | }; 9 | extern crate alloc; 10 | 11 | #[no_mangle] 12 | extern "C" fn main() { 13 | if let Ok(fd) = ios::open(c"/shared2/sys/SYSCONF", Mode::Read) { 14 | if let Ok(metadata) = ios::fs::get_file_stats_from_fd(fd) { 15 | if metadata.offset() != 0 { 16 | let _ = ios::seek(fd, 0, SeekMode::Start); 17 | } 18 | 19 | let mut bytes = vec![0; metadata.size()]; 20 | if let Ok(bytes_read) = ios::read(fd, &mut bytes) { 21 | unsafe { bytes.set_len(bytes_read.try_into().unwrap()) }; 22 | } 23 | 24 | println!("{:?}", bytes); 25 | 26 | let _ = ios::close(fd); 27 | } 28 | } 29 | loop { 30 | core::hint::spin_loop(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/obj-loading/Cargo.toml: -------------------------------------------------------------------------------- 1 | 2 | [package] 3 | name = "template" 4 | version = "0.1.0" 5 | edition = "2021" 6 | 7 | [profile] 8 | dev = { panic = "abort" } 9 | release = { panic = "abort", lto = true, codegen-units = 1, strip = "symbols", opt-level = "s" } 10 | 11 | [dependencies] 12 | gctex = {path = "./vendor/gctex", default-features = false } 13 | minipng = { version = "0.1.1", default-features = false } 14 | ogc-rs = { path = "../../", features = ["ffi", "default_panic_handler"] } 15 | -------------------------------------------------------------------------------- /examples/obj-loading/build.rs: -------------------------------------------------------------------------------- 1 | use std::process::Command; 2 | fn main() { 3 | let dkp_path = std::env::var("DEVKITPRO").expect("Please set $DEVKITPRO"); 4 | println!("cargo:rustc-link-search=native={}/libogc/lib/wii", dkp_path); 5 | 6 | //checks if the build folder exists. If it does, it deletes it. 7 | let _ = std::fs::remove_dir_all("build"); 8 | 9 | let _ = std::fs::create_dir("build"); 10 | 11 | 12 | let libgcc_location = match Command::new("powerpc-eabi-gcc").arg("-print-libgcc-file-name").output() { 13 | Ok(output) => output, 14 | Err(_e) => panic!("Could not find powerpc-eabi-gcc or the libgcc on the host machine!"), 15 | }; 16 | let output = libgcc_location.stdout; 17 | let parsed_output = 18 | String::from_utf8(output).expect("powerpc-eabi-gcc command output returned a non-utf8 string.").replace("\n", ""); 19 | 20 | let _ = match Command::new("powerpc-eabi-ar").arg("x").arg(parsed_output).arg("crtresxfpr.o").arg("crtresxgpr.o").output() { 21 | Ok(output) => output, 22 | Err(_e) => panic!("powerpc-eabi-ar command failed"), 23 | }; 24 | 25 | std::fs::rename("crtresxgpr.o", "build/crtresxgpr.o").expect("Could not move crtresxgpr.o"); 26 | std::fs::rename("crtresxfpr.o", "build/crtresxfpr.o").expect("Could not move crtresxfpr.o"); 27 | 28 | println!("cargo::rustc-link-arg=build/crtresxgpr.o"); 29 | println!("cargo::rustc-link-arg=build/crtresxfpr.o"); 30 | } 31 | -------------------------------------------------------------------------------- /examples/obj-loading/powerpc-unknown-eabi.json: -------------------------------------------------------------------------------- 1 | { 2 | "arch": "powerpc", 3 | "cpu": "750", 4 | "data-layout": "E-m:e-p:32:32-Fn32-i64:64-n32", 5 | "dynamic-linking": false, 6 | "env": "newlib", 7 | "executables": true, 8 | "has-elf-tls": false, 9 | "has-rpath": true, 10 | "llvm-target": "powerpc-unknown-eabi", 11 | "linker": "powerpc-eabi-gcc", 12 | "linker-flavor": "gcc", 13 | "linker-is-gnu": true, 14 | "os": "rvl-ios", 15 | "pre-link-args": { 16 | "gcc": [ 17 | "-mrvl", 18 | "-meabi", 19 | "-mhard-float" 20 | ] 21 | }, 22 | "panic-strategy": "abort", 23 | "relocation-model": "static", 24 | "target-endian": "big", 25 | "target-family": "unix", 26 | "target-mcount": "_mcount", 27 | "target-c-int-width": "32", 28 | "target-pointer-width": "32", 29 | "vendor": "nintendo" 30 | } 31 | 32 | -------------------------------------------------------------------------------- /examples/obj-loading/src/assets/untitled.mtl: -------------------------------------------------------------------------------- 1 | # Blender 4.2.0 MTL File: 'None' 2 | # www.blender.org 3 | 4 | newmtl Material 5 | Ns 250.000000 6 | Ka 1.000000 1.000000 1.000000 7 | Kd 0.800000 0.800000 0.800000 8 | Ks 0.500000 0.500000 0.500000 9 | Ke 0.000000 0.000000 0.000000 10 | Ni 1.450000 11 | d 1.000000 12 | illum 2 13 | -------------------------------------------------------------------------------- /examples/obj-loading/src/assets/untitled.obj: -------------------------------------------------------------------------------- 1 | # Blender 4.2.0 2 | # www.blender.org 3 | mtllib untitled.mtl 4 | o Cube 5 | v 1.000000 1.000000 -1.000000 6 | v 1.000000 -1.000000 -1.000000 7 | v 1.000000 1.000000 1.000000 8 | v 1.000000 -1.000000 1.000000 9 | v -1.000000 1.000000 -1.000000 10 | v -1.000000 -1.000000 -1.000000 11 | v -1.000000 1.000000 1.000000 12 | v -1.000000 -1.000000 1.000000 13 | vn -0.0000 1.0000 -0.0000 14 | vn -0.0000 -0.0000 1.0000 15 | vn -1.0000 -0.0000 -0.0000 16 | vn -0.0000 -1.0000 -0.0000 17 | vn 1.0000 -0.0000 -0.0000 18 | vn -0.0000 -0.0000 -1.0000 19 | vt 0.875000 0.500000 20 | vt 0.625000 0.750000 21 | vt 0.625000 0.500000 22 | vt 0.375000 1.000000 23 | vt 0.375000 0.750000 24 | vt 0.625000 0.000000 25 | vt 0.375000 0.250000 26 | vt 0.375000 0.000000 27 | vt 0.375000 0.500000 28 | vt 0.125000 0.750000 29 | vt 0.125000 0.500000 30 | vt 0.625000 0.250000 31 | vt 0.875000 0.750000 32 | vt 0.625000 1.000000 33 | s 0 34 | usemtl Material 35 | f 5/1/1 3/2/1 1/3/1 36 | f 3/2/2 8/4/2 4/5/2 37 | f 7/6/3 6/7/3 8/8/3 38 | f 2/9/4 8/10/4 6/11/4 39 | f 1/3/5 4/5/5 2/9/5 40 | f 5/12/6 2/9/6 6/7/6 41 | f 5/1/1 7/13/1 3/2/1 42 | f 3/2/2 7/14/2 8/4/2 43 | f 7/6/3 5/12/3 6/7/3 44 | f 2/9/4 4/5/4 8/10/4 45 | f 1/3/5 3/2/5 4/5/5 46 | f 5/12/6 1/3/6 2/9/6 47 | -------------------------------------------------------------------------------- /examples/obj-loading/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![feature(start)] 3 | 4 | mod obj; 5 | use core::f32::consts::PI; 6 | use core::mem::ManuallyDrop; 7 | use ogc_rs::ffi::{GX_F32, GX_NRM_XYZ, GX_TEX_ST, GX_VA_NRM, GX_VA_TEX0}; 8 | use ogc_rs::gu::RotationAxis; 9 | use ogc_rs::input::{Button, ControllerPort, ControllerType, Input}; 10 | use ogc_rs::{alloc_aligned_buffer, print}; 11 | 12 | use ogc_rs::{ 13 | ffi::{GX_COLOR0A0, GX_MODULATE, GX_POS_XYZ, GX_TEXCOORD0, GX_TEXMAP0, GX_TF_CMPR, GX_VA_POS}, 14 | gu::Gu, 15 | gx::{ 16 | types::VtxDest, CmpFn, Color, CullMode, Gx, Primitive, ProjectionType, TexFilter, Texture, 17 | VtxAttr, WrapMode, 18 | }, 19 | println, 20 | video::Video, 21 | }; 22 | 23 | extern crate alloc; 24 | use alloc::vec::Vec; 25 | const WHITE_BYTES: &[u8] = include_bytes!("../white.png"); 26 | 27 | #[repr(align(32))] 28 | #[derive(Clone, Copy)] 29 | pub struct Align32(pub T); 30 | 31 | #[start] 32 | fn main(_argc: isize, _argv: *const *const u8) -> isize { 33 | let Ok(obj) = obj::from_bytes(include_bytes!("./assets/untitled.obj")) else { 34 | panic!() 35 | }; 36 | 37 | let vi = Video::init(); 38 | let mut config = Video::get_preferred_mode(); 39 | 40 | Video::configure(&config); 41 | unsafe { Video::set_next_framebuffer(vi.framebuffer) }; 42 | Video::set_black(false); 43 | Video::flush(); 44 | 45 | let _fifo = ManuallyDrop::new(Gx::init(256 * 1024)); 46 | // Set values to use when video is flipped / cleared 47 | Gx::set_copy_clear(Color::new(0x00, 0x00, 0x00), 0x00_FF_FF_FF); 48 | 49 | Gx::set_viewport( 50 | 0.0, 51 | 0.0, 52 | config.framebuffer_width.into(), 53 | config.embed_framebuffer_height.into(), 54 | 0., 55 | 1., 56 | ); 57 | Gx::set_disp_copy_y_scale( 58 | (config.extern_framebuffer_height / config.embed_framebuffer_height).into(), 59 | ); 60 | Gx::set_scissor( 61 | 0, 62 | 0, 63 | config.framebuffer_width.into(), 64 | config.embed_framebuffer_height.into(), 65 | ); 66 | Gx::set_disp_copy_src( 67 | 0, 68 | 0, 69 | config.framebuffer_width, 70 | config.embed_framebuffer_height, 71 | ); 72 | Gx::set_disp_copy_dst(config.framebuffer_width, config.extern_framebuffer_height); 73 | Gx::set_copy_filter( 74 | config.anti_aliasing != 0, 75 | &mut config.sample_pattern, 76 | true, 77 | &mut config.v_filter, 78 | ); 79 | 80 | let val = config.vi_height != 2 * config.extern_framebuffer_height; 81 | 82 | Gx::set_field_mode(config.field_rendering != 0, val); 83 | Gx::set_cull_mode(CullMode::None); 84 | unsafe { Gx::copy_disp(vi.framebuffer, true) }; 85 | 86 | let mut mat = [[0.; 4]; 4]; 87 | Gu::perspective(&mut mat, 60., 4. / 3., 1., 1000.); 88 | Gx::load_projection_mtx(&mat, ProjectionType::Perspective); 89 | 90 | Gx::inv_vtx_cache(); 91 | Gx::clear_vtx_desc(); 92 | Gx::set_vtx_desc(VtxAttr::Pos, VtxDest::INDEX16); 93 | Gx::set_vtx_desc(VtxAttr::Nrm, VtxDest::INDEX8); 94 | Gx::set_vtx_desc(VtxAttr::Tex0, VtxDest::INDEX8); 95 | Gx::set_vtx_attr_fmt(0, VtxAttr::Pos, GX_POS_XYZ, GX_F32, 0); 96 | Gx::set_vtx_attr_fmt(0, VtxAttr::Nrm, GX_NRM_XYZ, GX_F32, 0); 97 | Gx::set_vtx_attr_fmt(0, VtxAttr::Tex0, GX_TEX_ST, GX_F32, 0); 98 | 99 | let indices: Vec<(usize, Option, Option)> = obj 100 | .indices() 101 | .unwrap() 102 | .flatten() 103 | //.map(|index| u16::try_from(index).unwrap()) 104 | .collect(); 105 | 106 | println!("{:?}", indices); 107 | 108 | let positions: Vec<[f32; 3]> = obj.vertices().unwrap().collect::>(); 109 | println!("{:?}", positions); 110 | 111 | let normals: Vec<[f32; 3]> = obj.normals().unwrap().collect(); 112 | 113 | let tex: Vec<[f32; 2]> = obj.texcoords().unwrap().collect::>(); 114 | 115 | Gx::set_array( 116 | GX_VA_POS, 117 | &positions, 118 | core::mem::size_of::<[f32; 3]>().try_into().unwrap(), 119 | ); 120 | 121 | Gx::set_array( 122 | GX_VA_NRM, 123 | &normals, 124 | core::mem::size_of::<[f32; 3]>().try_into().unwrap(), 125 | ); 126 | Gx::set_array( 127 | GX_VA_TEX0, 128 | &tex, 129 | core::mem::size_of::<[f32; 2]>().try_into().unwrap(), 130 | ); 131 | 132 | let header = minipng::decode_png_header(WHITE_BYTES).unwrap(); 133 | let mut work_buf = alloc::vec![0; header.required_bytes_rgba8bpc()]; 134 | let mut rgba_bytes = minipng::decode_png(WHITE_BYTES, &mut work_buf).unwrap(); 135 | rgba_bytes.convert_to_rgba8bpc().unwrap(); 136 | let texture_bytes = gctex::encode( 137 | gctex::TextureFormat::CMPR, 138 | rgba_bytes.pixels(), 139 | header.width(), 140 | header.height(), 141 | ); 142 | 143 | let buf = alloc_aligned_buffer(&texture_bytes); 144 | 145 | let mut texr = Texture::new( 146 | &buf, 147 | header.width().try_into().unwrap(), 148 | header.height().try_into().unwrap(), 149 | GX_TF_CMPR.try_into().unwrap(), 150 | WrapMode::Clamp, 151 | WrapMode::Clamp, 152 | false, 153 | ); 154 | texr.set_filter_mode(TexFilter::Near, TexFilter::Near); 155 | Gx::load_texture(&texr, GX_TEXMAP0.try_into().unwrap()); 156 | 157 | Gx::set_num_chans(1); 158 | Gx::set_num_tex_gens(1); 159 | 160 | Gx::set_tev_order( 161 | 0, 162 | GX_TEXCOORD0.try_into().unwrap(), 163 | GX_TEXMAP0, 164 | GX_COLOR0A0.try_into().unwrap(), 165 | ); 166 | Gx::set_tev_op(0, GX_MODULATE.try_into().unwrap()); 167 | 168 | println!("Finished Setup"); 169 | 170 | Gx::flush(); 171 | let mut i: u16 = 0; 172 | 173 | Input::init(ControllerType::Gamecube); 174 | let input = Input::new(ControllerType::Gamecube, ControllerPort::One); 175 | 176 | loop { 177 | Input::update(ControllerType::Gamecube); 178 | 179 | if input.is_button_down(Button::Start) { 180 | break 0; 181 | } 182 | 183 | Gx::inv_vtx_cache(); 184 | Gx::invalidate_tex_all(); 185 | 186 | Gx::load_texture(&texr, GX_TEXMAP0.try_into().unwrap()); 187 | 188 | Gx::set_viewport( 189 | 0.0, 190 | 0.0, 191 | config.framebuffer_width.into(), 192 | config.embed_framebuffer_height.into(), 193 | 0., 194 | 1., 195 | ); 196 | 197 | let mut mtx = [[0.; 4]; 3]; 198 | let mut rot_mtx = [[0.; 4]; 3]; 199 | let mut mdl_mtx = [[0.; 4]; 3]; 200 | let mut mdl2_mtx = [[0.; 4]; 3]; 201 | 202 | Gu::mtx_identity(&mut mtx); 203 | Gu::mtx_identity(&mut rot_mtx); 204 | Gu::mtx_identity(&mut mdl_mtx); 205 | 206 | Gu::mtx_rotation_radians(&mut rot_mtx, RotationAxis::Y, f32::from(i) * (PI / 180.)); 207 | // Rotation + Identity = Rotation; 208 | Gu::mtx_concat(&mut rot_mtx, &mut mdl_mtx, &mut mdl2_mtx); 209 | // // Rotation + Translation = Model; 210 | Gu::mtx_translation_apply(&mut mdl2_mtx, &mut mdl_mtx, (0., 0., -10.0)); 211 | 212 | // Load Model 213 | Gx::load_pos_mtx_imm(&mut mdl_mtx, 0); 214 | Gx::set_cull_mode(CullMode::None); 215 | Gx::begin(Primitive::Triangles, 0, indices.len().try_into().unwrap()); 216 | // v / vt / vn 217 | for index in indices.iter() { 218 | Gx::position1x16(u16::try_from(index.0).unwrap()); 219 | // let [x, y, z] = positions[*index]; 220 | // Gx::position_3f32(x, y, z); 221 | if let Some(idx) = index.2 { 222 | Gx::position1x8(idx.try_into().unwrap()); 223 | } else { 224 | panic!() 225 | } 226 | 227 | if let Some(idx) = index.1 { 228 | Gx::position1x8(idx.try_into().unwrap()); 229 | } else { 230 | panic!() 231 | } 232 | } 233 | 234 | Gx::end(); 235 | 236 | Gx::draw_done(); 237 | Gx::set_z_mode(true, CmpFn::LessEq, true); 238 | Gx::set_color_update(true); 239 | unsafe { Gx::copy_disp(vi.framebuffer, true) }; 240 | Gx::flush(); 241 | 242 | Video::wait_vsync(); 243 | i = i.wrapping_add(1); 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /examples/obj-loading/src/obj.rs: -------------------------------------------------------------------------------- 1 | use core::str::FromStr; 2 | 3 | pub struct Obj> { 4 | data: Data, 5 | } 6 | #[derive(Debug)] 7 | pub enum Error { 8 | InvalidUtf8, 9 | InvalidVertex, 10 | InvalidFace, 11 | InvalidNormal, 12 | InvalidTexcoord, 13 | } 14 | 15 | pub fn from_bytes>(data: Data) -> Result, Error> { 16 | //Parse bytes into string 17 | let Ok(obj) = core::str::from_utf8(data.as_ref()) else { 18 | return Err(Error::InvalidUtf8); 19 | }; 20 | 21 | let valid_face_count = obj 22 | .lines() 23 | .filter_map(|line| { 24 | if line.starts_with("f ") { 25 | let mut indices: [usize; 3] = [0usize; 3]; 26 | for (i, index) in line.splitn(4, ' ').skip(1).enumerate() { 27 | let has_slash = index.contains('/'); 28 | 29 | let idx: Option = if has_slash { 30 | let mut split = index.split('/'); 31 | 32 | split 33 | .next() 34 | .and_then(|idx| idx.parse::().ok()) 35 | .map(|idx| idx - 1) 36 | } else { 37 | index.parse::().ok().map(|idx| idx - 1) 38 | }; 39 | 40 | idx?; 41 | 42 | indices[i] = idx.expect("idx shouldn't be None"); 43 | } 44 | Some(indices) 45 | } else { 46 | None 47 | } 48 | }) 49 | .count(); 50 | 51 | if valid_face_count != obj.lines().filter(|line| line.starts_with("f ")).count() { 52 | return Err(Error::InvalidFace); 53 | } 54 | 55 | let valid_vertex_position_count = obj_iter::(obj, "v ").count(); 56 | let valid_vertex_normal_count = obj_iter::(obj, "vn ").count(); 57 | let valid_vertex_texcoord_count = obj_iter2::(obj, "vt ").count(); 58 | 59 | if valid_vertex_position_count != obj.lines().filter(|line| line.starts_with("v ")).count() { 60 | return Err(Error::InvalidVertex); 61 | } 62 | 63 | if valid_vertex_normal_count != obj.lines().filter(|line| line.starts_with("vn ")).count() { 64 | return Err(Error::InvalidNormal); 65 | } 66 | 67 | if valid_vertex_texcoord_count != obj.lines().filter(|line| line.starts_with("vt ")).count() { 68 | return Err(Error::InvalidTexcoord); 69 | } 70 | 71 | Ok(Obj { data }) 72 | } 73 | 74 | impl> Obj { 75 | pub fn vertices(&self) -> Result + '_, Error> { 76 | let Ok(obj) = core::str::from_utf8(self.data.as_ref()) else { 77 | return Err(Error::InvalidUtf8); 78 | }; 79 | 80 | Ok(obj.lines().filter_map(|line| { 81 | if line.starts_with("v ") { 82 | let mut vertices = [0.; 3]; 83 | for (i, pos) in line.splitn(4, ' ').skip(1).enumerate() { 84 | let Ok(float) = pos.parse::() else { 85 | return None; 86 | }; 87 | vertices[i] = float; 88 | } 89 | 90 | Some(vertices) 91 | } else { 92 | None 93 | } 94 | })) 95 | } 96 | 97 | pub fn indices( 98 | &self, 99 | ) -> Result, Option); 3]> + '_, Error> { 100 | let Ok(obj) = core::str::from_utf8(self.data.as_ref()) else { 101 | return Err(Error::InvalidUtf8); 102 | }; 103 | 104 | Ok(obj.lines().filter_map(|line| { 105 | if line.starts_with("f ") { 106 | let mut indices = [(0usize, None, None); 3]; 107 | for (i, index) in line.splitn(4, ' ').skip(1).enumerate() { 108 | let has_slash = index.contains('/'); 109 | 110 | let idx = if has_slash { 111 | let mut split = index.split('/'); 112 | 113 | let pos_idx = split 114 | .next() 115 | .and_then(|pos_idx| pos_idx.parse::().ok()); 116 | 117 | let tex_idx = split 118 | .next() 119 | .and_then(|pos_idx| pos_idx.parse::().ok()); 120 | 121 | let nrm_idx = split 122 | .next() 123 | .and_then(|pos_idx| pos_idx.parse::().ok()); 124 | ( 125 | pos_idx 126 | .map(|idx| idx - 1) 127 | .expect("pos_idx shouldnt be none at this point"), 128 | tex_idx.map(|idx| idx - 1), 129 | nrm_idx.map(|idx| idx - 1), 130 | ) 131 | } else { 132 | let idx = index.parse::().ok(); 133 | 134 | idx?; 135 | 136 | ( 137 | idx.map(|idx| idx - 1) 138 | .expect("pos_idx shouldn't be none at this point"), 139 | None, 140 | None, 141 | ) 142 | }; 143 | 144 | indices[i] = idx; 145 | } 146 | Some(indices) 147 | } else { 148 | None 149 | } 150 | })) 151 | } 152 | 153 | pub fn normals(&self) -> Result + '_, Error> { 154 | let Ok(obj) = core::str::from_utf8(self.data.as_ref()) else { 155 | return Err(Error::InvalidUtf8); 156 | }; 157 | Ok(obj_iter(obj, "vn ")) 158 | } 159 | 160 | pub fn texcoords(&self) -> Result + '_, Error> { 161 | let Ok(obj) = core::str::from_utf8(self.data.as_ref()) else { 162 | return Err(Error::InvalidUtf8); 163 | }; 164 | Ok(obj_iter2(obj, "vt ")) 165 | } 166 | } 167 | 168 | fn obj_iter<'a, T: Default + Copy + FromStr>( 169 | str: &'a str, 170 | prefix: &'a str, 171 | ) -> impl Iterator + 'a { 172 | str.lines().filter_map(move |line| { 173 | if line.starts_with(prefix) { 174 | let mut array = [T::default(); 3]; 175 | for (i, t) in line.splitn(4, ' ').skip(1).enumerate() { 176 | let Ok(t) = t.parse::() else { 177 | return None; 178 | }; 179 | array[i] = t; 180 | } 181 | Some(array) 182 | } else { 183 | None 184 | } 185 | }) 186 | } 187 | 188 | fn obj_iter2<'a, T: Default + Copy + FromStr>( 189 | str: &'a str, 190 | prefix: &'a str, 191 | ) -> impl Iterator + 'a { 192 | str.lines().filter_map(move |line| { 193 | if line.starts_with(prefix) { 194 | let mut array = [T::default(); 2]; 195 | for (i, t) in line.splitn(3, ' ').skip(1).enumerate() { 196 | let Ok(t) = t.parse::() else { 197 | return None; 198 | }; 199 | array[i] = t; 200 | } 201 | Some(array) 202 | } else { 203 | None 204 | } 205 | }) 206 | } 207 | -------------------------------------------------------------------------------- /examples/obj-loading/vendor/gctex/.gitignore: -------------------------------------------------------------------------------- 1 | tests/*.png 2 | -------------------------------------------------------------------------------- /examples/obj-loading/vendor/gctex/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gctex" 3 | license = "GPL-2.0-or-later" 4 | version = "0.3.8" 5 | edition = "2021" 6 | description = "gctex is a Rust crate designed for encoding and decoding texture formats used in the Nintendo GameCube and Wii games. The library provides C bindings, making it useful in both Rust and C/C++ based projects." 7 | homepage = "https://github.com/riidefi/RiiStudio/tree/master/source/gctex" 8 | repository = "https://github.com/riidefi/RiiStudio" 9 | keywords = ["gamedev", "graphics", "wii", "gamecube", "texture"] 10 | readme = "README.md" 11 | # Don't include unit tests in crate 12 | exclude=["/tests"] 13 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 14 | 15 | [features] 16 | # Enabled only for development 17 | default = ["c_api", "simd"] 18 | c_api = [] 19 | # Internal, don't enable 20 | run_bindgen = ["bindgen"] 21 | simd = ["cpp_fallback"] 22 | cpp_fallback = ["cc"] 23 | 24 | [lib] 25 | crate-type=["rlib"] 26 | 27 | [build-dependencies] 28 | bindgen = { version = "0.66", optional = true } 29 | cc = { version = "1.0.83", features = ["parallel"], optional = true } 30 | 31 | [dev-dependencies] 32 | rand = "0.8.5" 33 | image = "0.25.1" 34 | 35 | [dependencies] 36 | heapless = "0.8.0" 37 | -------------------------------------------------------------------------------- /examples/obj-loading/vendor/gctex/README.md: -------------------------------------------------------------------------------- 1 | [![crates.io](https://img.shields.io/crates/v/gctex.svg)](https://crates.io/crates/gctex) 2 | [![docs.rs](https://docs.rs/gctex/badge.svg)](https://docs.rs/gctex/) 3 | 4 | # `gctex` 5 | gctex is a Rust crate designed for encoding and decoding texture formats used in the Nintendo GameCube and Wii games. The library provides C bindings, making it useful in both Rust and C/C++ based projects. 6 | 7 | ## Usage 8 | 9 | ### Rust 10 | The following snippet demonstrates how to encode a texture in CMPR format using Rust: 11 | 12 | ```rust 13 | let src = vec![0; src_len]; 14 | let dst = gctex::encode(gctex::TextureFormat::CMPR, &src, width, height); 15 | ``` 16 | 17 | ### C# Bindings 18 | See https://github.com/riidefi/RiiStudio/tree/master/source/gctex/examples/c%23 19 | ```cs 20 | byte[] dst = new byte[dst_len]; 21 | byte[] src = new byte[src_len]; 22 | gctex.Encode(0xE /* CMPR */, dst, src, width, height); 23 | ``` 24 | 25 | ### C/C++ 26 | See https://github.com/riidefi/RiiStudio/tree/master/source/gctex/examples/c%2b%2b 27 | ```cpp 28 | #include "gctex.h" 29 | 30 | unsigned char dst[dst_len]; 31 | unsigned char src[src_len]; 32 | rii_encode_cmpr(dst, sizeof(dst), src, sizeof(src), width, height); 33 | ``` 34 | The relevant header is available in `include/gctex.h`. 35 | 36 | #### Supported Formats 37 | All supported texture formats and their respective encoding and decoding sources are listed below. 38 | 39 | | Format | Encoding Source | Decoding Source | 40 | |---------|-----------------|-----------------| 41 | | CMPR | Builtin | Dolphin Emulator (SIMD) / Rust non-SIMD fallback | 42 | | I4 | Builtin | Builtin (SIMD (SSE3)) | 43 | | I8 | Builtin | Dolphin Emulator (SIMD) / Rust non-SIMD fallback | 44 | | IA4 | Builtin | Builtin | 45 | | IA8 | Builtin | Dolphin Emulator (SIMD) / Rust non-SIMD fallback | 46 | | RGB565 | Builtin | Dolphin Emulator (SIMD) / Rust non-SIMD fallback | 47 | | RGB5A3 | Builtin | Dolphin Emulator (SIMD) / Rust non-SIMD fallback | 48 | | RGBA8 | Builtin | Builtin (SIMD (SSE3)) | 49 | | C4 | - | Dolphin Emulator / No fallback | 50 | | C8 | - | Dolphin Emulator / No fallback | 51 | | C14 | - | Dolphin Emulator / No fallback | 52 | 53 | 54 | Please note, SIMD texture decoding for I4, I8 and IA8 formats uses SSE3 instructions with a fallback to SSE2 if necessary (excepting I4), and these are implemented based on the Dolphin Emulator's texture decoding logic. 55 | 56 | #### Optional Features 57 | - To avoid needing a C++ compiler or running C++ code, unset the `cpp_fallback` feature to fallback to non-SIMD Rust implementations of I4/I8/IA8/RGB565/RGB5A3 decoding. 58 | - For debugging the `simd` feature can be disabled to use pure, standard Rust. 59 | 60 | #### License 61 | This dynamically linked library is published under GPLv2. 62 | -------------------------------------------------------------------------------- /examples/obj-loading/white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-wii/ogc-rs/ab2b71528fbff0bd065f0587a8024b7a1b502673/examples/obj-loading/white.png -------------------------------------------------------------------------------- /examples/template/Cargo.toml: -------------------------------------------------------------------------------- 1 | 2 | [package] 3 | name = "template" 4 | version = "0.1.0" 5 | edition = "2021" 6 | 7 | [profile] 8 | dev = { panic = "abort" } 9 | release = { panic = "abort", lto = true, codegen-units = 1, strip = "symbols", opt-level = "s" } 10 | 11 | [dependencies] 12 | ogc-rs = { path = "../../" } 13 | -------------------------------------------------------------------------------- /examples/template/README.md: -------------------------------------------------------------------------------- 1 | # Template Project for [ogc-rs](https://github.com/rust-wii/ogc-rs) 2 | 3 | `cargo +nightly build -Zbuild-std=core,alloc --target powerpc-unknown-eabi.json` to compile. 4 | `elf2dol` is used to convert them to a format that Wii & Gamecube can use. 5 | -------------------------------------------------------------------------------- /examples/template/build.rs: -------------------------------------------------------------------------------- 1 | use std::process::Command; 2 | fn main() { 3 | let dkp_path = std::env::var("DEVKITPRO").expect("Please set $DEVKITPRO"); 4 | println!("cargo:rustc-link-search=native={}/libogc/lib/wii", dkp_path); 5 | 6 | //checks if the build folder exists. If it does, it deletes it. 7 | let _ = std::fs::remove_dir_all("build"); 8 | 9 | let _ = std::fs::create_dir("build"); 10 | 11 | 12 | let libgcc_location = match Command::new("powerpc-eabi-gcc").arg("-print-libgcc-file-name").output() { 13 | Ok(output) => output, 14 | Err(_e) => panic!("Could not find powerpc-eabi-gcc or the libgcc on the host machine!"), 15 | }; 16 | let output = libgcc_location.stdout; 17 | let parsed_output = 18 | String::from_utf8(output).expect("powerpc-eabi-gcc command output returned a non-utf8 string.").replace("\n", ""); 19 | 20 | let _ = match Command::new("powerpc-eabi-ar").arg("x").arg(parsed_output).arg("crtresxfpr.o").arg("crtresxgpr.o").output() { 21 | Ok(output) => output, 22 | Err(_e) => panic!("powerpc-eabi-ar command failed"), 23 | }; 24 | 25 | std::fs::rename("crtresxgpr.o", "build/crtresxgpr.o").expect("Could not move crtresxgpr.o"); 26 | std::fs::rename("crtresxfpr.o", "build/crtresxfpr.o").expect("Could not move crtresxfpr.o"); 27 | 28 | println!("cargo::rustc-link-arg=build/crtresxgpr.o"); 29 | println!("cargo::rustc-link-arg=build/crtresxfpr.o"); 30 | } 31 | -------------------------------------------------------------------------------- /examples/template/mp3.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-wii/ogc-rs/ab2b71528fbff0bd065f0587a8024b7a1b502673/examples/template/mp3.mp3 -------------------------------------------------------------------------------- /examples/template/powerpc-unknown-eabi.json: -------------------------------------------------------------------------------- 1 | { 2 | "arch": "powerpc", 3 | "cpu": "750", 4 | "data-layout": "E-m:e-p:32:32-Fn32-i64:64-n32", 5 | "dynamic-linking": false, 6 | "env": "newlib", 7 | "executables": true, 8 | "has-elf-tls": false, 9 | "has-rpath": true, 10 | "llvm-target": "powerpc-unknown-eabi", 11 | "linker": "powerpc-eabi-gcc", 12 | "linker-flavor": "gcc", 13 | "linker-is-gnu": true, 14 | "os": "rvl-ios", 15 | "pre-link-args": { 16 | "gcc": [ 17 | "-mrvl", 18 | "-meabi", 19 | "-mhard-float" 20 | ] 21 | }, 22 | "panic-strategy": "abort", 23 | "relocation-model": "static", 24 | "target-endian": "big", 25 | "target-family": "unix", 26 | "target-mcount": "_mcount", 27 | "target-c-int-width": "32", 28 | "target-pointer-width": "32", 29 | "vendor": "nintendo" 30 | } 31 | 32 | -------------------------------------------------------------------------------- /examples/template/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![feature(start)] 3 | 4 | extern crate alloc; 5 | use core::mem::ManuallyDrop; 6 | 7 | use ogc_rs::{mp3player::MP3Player, prelude::*}; 8 | 9 | #[start] 10 | fn main(_argc: isize, _argv: *const *const u8) -> isize { 11 | let mp3 = include_bytes!("../mp3.mp3"); 12 | 13 | let video = Video::init(); 14 | let mut asnd = Asnd::init(); 15 | let mut player = MP3Player::new(asnd); 16 | 17 | Input::init(ControllerType::Gamecube); 18 | Input::init(ControllerType::Wii); 19 | 20 | let gcn_ctrl = Input::new(ControllerType::Gamecube, ControllerPort::One); 21 | let wii_ctrl = Input::new(ControllerType::Wii, ControllerPort::One); 22 | 23 | Console::init(&video); 24 | Video::configure(&video.render_config); 25 | unsafe { 26 | Video::set_next_framebuffer(video.framebuffer); 27 | } 28 | Video::set_black(false); 29 | Video::flush(); 30 | Video::wait_vsync(); 31 | 32 | println!("Hello World!"); 33 | 34 | player.play_buffer(mp3); 35 | 36 | loop { 37 | Input::update(ControllerType::Gamecube); 38 | Input::update(ControllerType::Wii); 39 | 40 | if gcn_ctrl.is_button_down(Button::Start) { 41 | break 0; 42 | } 43 | 44 | if wii_ctrl.is_button_down(Button::Home) { 45 | break 0; 46 | } 47 | 48 | Video::wait_vsync(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /examples/texture-tri/Cargo.toml: -------------------------------------------------------------------------------- 1 | 2 | [package] 3 | name = "template" 4 | version = "0.1.0" 5 | edition = "2021" 6 | 7 | [profile] 8 | dev = { panic = "abort" } 9 | release = { panic = "abort", lto = true, codegen-units = 1, strip = "symbols", opt-level = "s" } 10 | 11 | [dependencies] 12 | gctex = {path = "./vendor/gctex", default-features = false } 13 | minipng = { version = "0.1.1", default-features = false } 14 | ogc-rs = { path = "../../", features = ["ffi", "default_alloc_handler", "default_panic_handler"] } 15 | -------------------------------------------------------------------------------- /examples/texture-tri/build.rs: -------------------------------------------------------------------------------- 1 | use std::process::Command; 2 | fn main() { 3 | let dkp_path = std::env::var("DEVKITPRO").expect("Please set $DEVKITPRO"); 4 | println!("cargo:rustc-link-search=native={}/libogc/lib/wii", dkp_path); 5 | 6 | //checks if the build folder exists. If it does, it deletes it. 7 | let _ = std::fs::remove_dir_all("build"); 8 | 9 | let _ = std::fs::create_dir("build"); 10 | 11 | 12 | let libgcc_location = match Command::new("powerpc-eabi-gcc").arg("-print-libgcc-file-name").output() { 13 | Ok(output) => output, 14 | Err(_e) => panic!("Could not find powerpc-eabi-gcc or the libgcc on the host machine!"), 15 | }; 16 | let output = libgcc_location.stdout; 17 | let parsed_output = 18 | String::from_utf8(output).expect("powerpc-eabi-gcc command output returned a non-utf8 string.").replace("\n", ""); 19 | 20 | let _ = match Command::new("powerpc-eabi-ar").arg("x").arg(parsed_output).arg("crtresxfpr.o").arg("crtresxgpr.o").output() { 21 | Ok(output) => output, 22 | Err(_e) => panic!("powerpc-eabi-ar command failed"), 23 | }; 24 | 25 | std::fs::rename("crtresxgpr.o", "build/crtresxgpr.o").expect("Could not move crtresxgpr.o"); 26 | std::fs::rename("crtresxfpr.o", "build/crtresxfpr.o").expect("Could not move crtresxfpr.o"); 27 | 28 | println!("cargo::rustc-link-arg=build/crtresxgpr.o"); 29 | println!("cargo::rustc-link-arg=build/crtresxfpr.o"); 30 | } 31 | -------------------------------------------------------------------------------- /examples/texture-tri/powerpc-unknown-eabi.json: -------------------------------------------------------------------------------- 1 | { 2 | "arch": "powerpc", 3 | "cpu": "750", 4 | "data-layout": "E-m:e-p:32:32-Fn32-i64:64-n32", 5 | "dynamic-linking": false, 6 | "env": "newlib", 7 | "executables": true, 8 | "has-elf-tls": false, 9 | "has-rpath": true, 10 | "llvm-target": "powerpc-unknown-eabi", 11 | "linker": "powerpc-eabi-gcc", 12 | "linker-flavor": "gcc", 13 | "linker-is-gnu": true, 14 | "os": "rvl-ios", 15 | "pre-link-args": { 16 | "gcc": [ 17 | "-mrvl", 18 | "-meabi", 19 | "-mhard-float" 20 | ] 21 | }, 22 | "panic-strategy": "abort", 23 | "relocation-model": "static", 24 | "target-endian": "big", 25 | "target-family": "unix", 26 | "target-mcount": "_mcount", 27 | "target-c-int-width": "32", 28 | "target-pointer-width": "32", 29 | "vendor": "nintendo" 30 | } 31 | 32 | -------------------------------------------------------------------------------- /examples/texture-tri/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![feature(start)] 3 | 4 | use core::{alloc::Layout, mem::ManuallyDrop}; 5 | 6 | use ogc_rs::{ 7 | ffi::{ 8 | GX_CLR_RGBA, GX_COLOR0A0, GX_MODULATE, GX_PASSCLR, GX_POS_XYZ, GX_REPLACE, GX_RGBA8, 9 | GX_S16, GX_TEXCOORD0, GX_TEXMAP0, GX_TEX_ST, GX_TF_CMPR, GX_TF_RGBA8, GX_U8, GX_VA_CLR0, 10 | GX_VA_POS, GX_VA_TEX0, 11 | }, 12 | gu::{Gu, RotationAxis}, 13 | gx::{ 14 | types::VtxDest, CmpFn, Color, CullMode, Gx, Primitive, ProjectionType, TexFilter, Texture, 15 | VtxAttr, WrapMode, 16 | }, 17 | print, println, 18 | video::Video, 19 | }; 20 | 21 | extern crate alloc; 22 | use alloc::vec; 23 | const WHITE_BYTES: &[u8] = include_bytes!("../white.png"); 24 | 25 | #[start] 26 | fn main(_argc: isize, _argv: *const *const u8) -> isize { 27 | let vi = Video::init(); 28 | let mut config = Video::get_preferred_mode(); 29 | 30 | Video::configure(&config); 31 | unsafe { Video::set_next_framebuffer(vi.framebuffer) }; 32 | Video::set_black(false); 33 | Video::flush(); 34 | 35 | let fifo = ManuallyDrop::new(Gx::init(256 * 1024)); 36 | // Set values to use when video is flipped / cleared 37 | Gx::set_copy_clear(Color::new(0x00, 0x00, 0x00), 0x00_FF_FF_FF); 38 | 39 | Gx::set_viewport( 40 | 0.0, 41 | 0.0, 42 | config.framebuffer_width.into(), 43 | config.embed_framebuffer_height.into(), 44 | 0., 45 | 1., 46 | ); 47 | Gx::set_disp_copy_y_scale( 48 | (config.extern_framebuffer_height / config.embed_framebuffer_height).into(), 49 | ); 50 | Gx::set_scissor( 51 | 0, 52 | 0, 53 | config.framebuffer_width.into(), 54 | config.embed_framebuffer_height.into(), 55 | ); 56 | Gx::set_disp_copy_src( 57 | 0, 58 | 0, 59 | config.framebuffer_width, 60 | config.embed_framebuffer_height, 61 | ); 62 | Gx::set_disp_copy_dst(config.framebuffer_width, config.extern_framebuffer_height); 63 | Gx::set_copy_filter( 64 | config.anti_aliasing != 0, 65 | &mut config.sample_pattern, 66 | true, 67 | &mut config.v_filter, 68 | ); 69 | 70 | let val = if config.vi_height == 2 * config.extern_framebuffer_height { 71 | false 72 | } else { 73 | true 74 | }; 75 | 76 | Gx::set_field_mode(config.field_rendering != 0, val); 77 | Gx::set_cull_mode(CullMode::None); 78 | unsafe { Gx::copy_disp(vi.framebuffer, true) }; 79 | 80 | let mut mat = [[0.; 4]; 4]; 81 | Gu::perspective(&mut mat, 60., 4. / 3., 10., 300.); 82 | Gx::load_projection_mtx(&mat, ProjectionType::Perspective); 83 | Gx::clear_vtx_desc(); 84 | Gx::set_vtx_desc(VtxAttr::Pos, VtxDest::INDEX8); 85 | Gx::set_vtx_desc(VtxAttr::Color0, VtxDest::INDEX8); 86 | Gx::set_vtx_desc(VtxAttr::Tex0, VtxDest::INDEX8); 87 | 88 | let header = minipng::decode_png_header(WHITE_BYTES).unwrap(); 89 | let mut work_buf = vec![0u8; header.required_bytes_rgba8bpc()]; 90 | let mut rgba_bytes = minipng::decode_png(WHITE_BYTES, &mut work_buf).unwrap(); 91 | rgba_bytes.convert_to_rgba8bpc().unwrap(); 92 | let texture_bytes = gctex::encode( 93 | gctex::TextureFormat::CMPR, 94 | rgba_bytes.pixels(), 95 | header.width(), 96 | header.height(), 97 | ); 98 | 99 | let mut texr = Texture::new( 100 | &texture_bytes, 101 | header.width().try_into().unwrap(), 102 | header.height().try_into().unwrap(), 103 | GX_TF_CMPR.try_into().unwrap(), 104 | WrapMode::Clamp, 105 | WrapMode::Clamp, 106 | false, 107 | ); 108 | texr.set_filter_mode(TexFilter::Near, TexFilter::Near); 109 | 110 | Gx::load_texture(&texr, GX_TEXMAP0.try_into().unwrap()); 111 | 112 | Gx::set_vtx_attr_fmt(0, VtxAttr::Pos, GX_POS_XYZ, GX_S16, 0); 113 | Gx::set_vtx_attr_fmt(0, VtxAttr::Color0, GX_CLR_RGBA, GX_RGBA8, 0); 114 | Gx::set_vtx_attr_fmt(0, VtxAttr::Tex0, GX_TEX_ST, GX_U8, 0); 115 | let positions: [[i16; 3]; 3] = [[0, 15, 0], [-15, -15, 0], [15, -15, 0]]; 116 | let colors: [[u8; 4]; 3] = [[255, 0, 0, 255], [0, 255, 0, 255], [0, 0, 255, 255]]; 117 | let tex: [[u8; 2]; 3] = [[0, 1], [1, 0], [1, 1]]; 118 | Gx::set_array( 119 | GX_VA_POS, 120 | &positions, 121 | core::mem::size_of::<[i16; 3]>().try_into().unwrap(), 122 | ); 123 | 124 | Gx::set_array( 125 | GX_VA_CLR0, 126 | &colors, 127 | core::mem::size_of::<[u8; 4]>().try_into().unwrap(), 128 | ); 129 | Gx::set_array( 130 | GX_VA_TEX0, 131 | &tex, 132 | core::mem::size_of::<[u8; 2]>().try_into().unwrap(), 133 | ); 134 | Gx::set_num_chans(1); 135 | Gx::set_num_tex_gens(1); 136 | 137 | Gx::set_tev_order( 138 | 0, 139 | GX_TEXCOORD0.try_into().unwrap(), 140 | GX_TEXMAP0, 141 | GX_COLOR0A0.try_into().unwrap(), 142 | ); 143 | Gx::set_tev_op(0, GX_MODULATE.try_into().unwrap()); 144 | 145 | println!("Finished Setup"); 146 | 147 | let mut i: u16 = 0; 148 | loop { 149 | let mut mtx = [[0.; 4]; 3]; 150 | let mut rot_mtx = [[0.; 4]; 3]; 151 | let mut mdl_mtx = [[0.; 4]; 3]; 152 | let mut mdl2_mtx = [[0.; 4]; 3]; 153 | 154 | Gu::mtx_identity(&mut mtx); 155 | Gu::mtx_identity(&mut rot_mtx); 156 | Gu::mtx_identity(&mut mdl_mtx); 157 | 158 | Gu::mtx_rotation_radians( 159 | &mut rot_mtx, 160 | RotationAxis::Y, 161 | f32::from(i) * (3.14159 / 180.), 162 | ); 163 | // Rotation + Identity = Rotation; 164 | Gu::mtx_concat(&mut rot_mtx, &mut mdl_mtx, &mut mdl2_mtx); 165 | // Rotation + Translation = Model; 166 | Gu::mtx_translation_apply(&mut mdl2_mtx, &mut mdl_mtx, (0., 0., -50.)); 167 | // Load Model 168 | Gx::load_pos_mtx_imm(&mut mdl_mtx, 0); 169 | 170 | Gx::begin(Primitive::Triangles, 0, 3); 171 | Gx::position1x8(0); 172 | Gx::color1x8(0); 173 | Gx::position1x8(0); 174 | Gx::position1x8(1); 175 | Gx::color1x8(1); 176 | Gx::position1x8(1); 177 | Gx::position1x8(2); 178 | Gx::color1x8(2); 179 | Gx::position1x8(2); 180 | 181 | /* 182 | Gx::position_3i16(0, 15, 0); 183 | Gx::color_4u8(255, 0, 0, 255); 184 | Gx::position_3i16(-15, -15, 0); 185 | Gx::color_4u8(0, 255, 0, 255); 186 | Gx::position_3i16(15, -15, 0); 187 | Gx::color_4u8(0, 0, 255, 255); 188 | */ 189 | Gx::end(); 190 | 191 | Gx::draw_done(); 192 | Gx::set_z_mode(true, CmpFn::LessEq, true); 193 | Gx::set_color_update(true); 194 | unsafe { Gx::copy_disp(vi.framebuffer, true) }; 195 | Gx::flush(); 196 | 197 | Video::wait_vsync(); 198 | i += 1; 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /examples/texture-tri/vendor/gctex/.gitignore: -------------------------------------------------------------------------------- 1 | tests/*.png 2 | -------------------------------------------------------------------------------- /examples/texture-tri/vendor/gctex/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gctex" 3 | license = "GPL-2.0-or-later" 4 | version = "0.3.8" 5 | edition = "2021" 6 | description = "gctex is a Rust crate designed for encoding and decoding texture formats used in the Nintendo GameCube and Wii games. The library provides C bindings, making it useful in both Rust and C/C++ based projects." 7 | homepage = "https://github.com/riidefi/RiiStudio/tree/master/source/gctex" 8 | repository = "https://github.com/riidefi/RiiStudio" 9 | keywords = ["gamedev", "graphics", "wii", "gamecube", "texture"] 10 | readme = "README.md" 11 | # Don't include unit tests in crate 12 | exclude=["/tests"] 13 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 14 | 15 | [features] 16 | # Enabled only for development 17 | default = ["c_api", "simd"] 18 | c_api = [] 19 | # Internal, don't enable 20 | run_bindgen = ["bindgen"] 21 | simd = ["cpp_fallback"] 22 | cpp_fallback = ["cc"] 23 | 24 | [lib] 25 | crate-type=["rlib"] 26 | 27 | [build-dependencies] 28 | bindgen = { version = "0.66", optional = true } 29 | cc = { version = "1.0.83", features = ["parallel"], optional = true } 30 | 31 | [dev-dependencies] 32 | rand = "0.8.5" 33 | image = "0.25.1" 34 | 35 | [dependencies] 36 | heapless = "0.8.0" 37 | -------------------------------------------------------------------------------- /examples/texture-tri/vendor/gctex/README.md: -------------------------------------------------------------------------------- 1 | [![crates.io](https://img.shields.io/crates/v/gctex.svg)](https://crates.io/crates/gctex) 2 | [![docs.rs](https://docs.rs/gctex/badge.svg)](https://docs.rs/gctex/) 3 | 4 | # `gctex` 5 | gctex is a Rust crate designed for encoding and decoding texture formats used in the Nintendo GameCube and Wii games. The library provides C bindings, making it useful in both Rust and C/C++ based projects. 6 | 7 | ## Usage 8 | 9 | ### Rust 10 | The following snippet demonstrates how to encode a texture in CMPR format using Rust: 11 | 12 | ```rust 13 | let src = vec![0; src_len]; 14 | let dst = gctex::encode(gctex::TextureFormat::CMPR, &src, width, height); 15 | ``` 16 | 17 | ### C# Bindings 18 | See https://github.com/riidefi/RiiStudio/tree/master/source/gctex/examples/c%23 19 | ```cs 20 | byte[] dst = new byte[dst_len]; 21 | byte[] src = new byte[src_len]; 22 | gctex.Encode(0xE /* CMPR */, dst, src, width, height); 23 | ``` 24 | 25 | ### C/C++ 26 | See https://github.com/riidefi/RiiStudio/tree/master/source/gctex/examples/c%2b%2b 27 | ```cpp 28 | #include "gctex.h" 29 | 30 | unsigned char dst[dst_len]; 31 | unsigned char src[src_len]; 32 | rii_encode_cmpr(dst, sizeof(dst), src, sizeof(src), width, height); 33 | ``` 34 | The relevant header is available in `include/gctex.h`. 35 | 36 | #### Supported Formats 37 | All supported texture formats and their respective encoding and decoding sources are listed below. 38 | 39 | | Format | Encoding Source | Decoding Source | 40 | |---------|-----------------|-----------------| 41 | | CMPR | Builtin | Dolphin Emulator (SIMD) / Rust non-SIMD fallback | 42 | | I4 | Builtin | Builtin (SIMD (SSE3)) | 43 | | I8 | Builtin | Dolphin Emulator (SIMD) / Rust non-SIMD fallback | 44 | | IA4 | Builtin | Builtin | 45 | | IA8 | Builtin | Dolphin Emulator (SIMD) / Rust non-SIMD fallback | 46 | | RGB565 | Builtin | Dolphin Emulator (SIMD) / Rust non-SIMD fallback | 47 | | RGB5A3 | Builtin | Dolphin Emulator (SIMD) / Rust non-SIMD fallback | 48 | | RGBA8 | Builtin | Builtin (SIMD (SSE3)) | 49 | | C4 | - | Dolphin Emulator / No fallback | 50 | | C8 | - | Dolphin Emulator / No fallback | 51 | | C14 | - | Dolphin Emulator / No fallback | 52 | 53 | 54 | Please note, SIMD texture decoding for I4, I8 and IA8 formats uses SSE3 instructions with a fallback to SSE2 if necessary (excepting I4), and these are implemented based on the Dolphin Emulator's texture decoding logic. 55 | 56 | #### Optional Features 57 | - To avoid needing a C++ compiler or running C++ code, unset the `cpp_fallback` feature to fallback to non-SIMD Rust implementations of I4/I8/IA8/RGB565/RGB5A3 decoding. 58 | - For debugging the `simd` feature can be disabled to use pure, standard Rust. 59 | 60 | #### License 61 | This dynamically linked library is published under GPLv2. 62 | -------------------------------------------------------------------------------- /examples/texture-tri/white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-wii/ogc-rs/ab2b71528fbff0bd065f0587a8024b7a1b502673/examples/texture-tri/white.png -------------------------------------------------------------------------------- /ogc-sys/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ogc-sys" 3 | version = "0.1.1" 4 | authors = ["rust-wii"] 5 | edition = "2021" 6 | license = "MIT" 7 | readme = "README.md" 8 | description = "Rust bindings for devkitPro's libogc" 9 | documentation = "https://docs.rs/ogc-sys/" 10 | homepage = "https://github.com/rust-wii/ogc-rs" 11 | repository = "https://github.com/rust-wii/ogc-rs" 12 | keywords = ["wii", "embedded", "no-std"] 13 | 14 | [dependencies] 15 | libc = "0.2" 16 | 17 | [build-dependencies] 18 | bindgen = "0.69.4" 19 | doxygen-rs = "0.4" 20 | regex = "1.5" 21 | -------------------------------------------------------------------------------- /ogc-sys/README.md: -------------------------------------------------------------------------------- 1 | # ogc-sys 2 | 3 | Low-level unsafe Rust bindings for devkitPro's [libogc](https://github.com/devkitPro/libogc). -------------------------------------------------------------------------------- /ogc-sys/build.rs: -------------------------------------------------------------------------------- 1 | extern crate bindgen; 2 | 3 | use bindgen::callbacks::ParseCallbacks; 4 | use regex::Regex; 5 | use std::env; 6 | use std::process::Command; 7 | 8 | fn get_include_path(dkp_path: String) -> Vec{ 9 | let mut include = Vec::new(); 10 | //powerpc-eabi-gcc -xc -E -v /dev/null 11 | let gcc_output = match Command::new("powerpc-eabi-gcc") 12 | .arg("-xc") 13 | .arg("-E") 14 | .arg("-v") 15 | .arg("/dev/null").output() { 16 | Ok(output) => output, 17 | Err(e) => panic!("failed to get the default include paths on the host machine!\n{}", e), 18 | }; 19 | let output = gcc_output.stderr; 20 | 21 | let parsed_output = 22 | String::from_utf8(output).expect("gcc command output returned a non-utf8 string."); 23 | parsed_output.split("\n").filter(|line| line.trim().starts_with(&dkp_path) && line.contains("include")).for_each(|line| { 24 | include.push(line.trim().to_string()); 25 | }); 26 | include 27 | } 28 | 29 | fn get_clang_version() -> String { 30 | // Check if the clang version env variable exists. 31 | if env::var("CLANG_VERSION").is_err() { 32 | // Attempt to retrieve clang version through the command line. 33 | let clang_output = match Command::new("clang").arg("--version").output() { 34 | Ok(output) => output, 35 | Err(_e) => panic!("Could not find clang on the host machine!"), 36 | }; 37 | 38 | // Get the first line of the output, usually containing the version string. 39 | let output = clang_output.stdout; 40 | let parsed_output = 41 | String::from_utf8(output).expect("Clang command output returned a non-utf8 string."); 42 | let first_line = match parsed_output.lines().next() { 43 | Some(line) => line, 44 | None => panic!("Clang command output does not contain split lines."), 45 | }; 46 | 47 | // Parse the version string using Regex. 48 | 49 | let regex = Regex::new(r"(?m)\d+(?:\.\d+)+").unwrap(); 50 | let result = regex.captures(first_line).unwrap().get(0); // Attempt to join together the version string. 51 | 52 | let version = match result { 53 | Some(v) => v.as_str(), 54 | None => { 55 | panic!("Failed to parse version, please export your clang version to CLANG_VERSION") 56 | } 57 | }; 58 | 59 | // Return the final joined string. 60 | version.to_string() 61 | } else { 62 | // Clang version env variable exists, use that over parsing. 63 | env::var("CLANG_VERSION").unwrap() 64 | } 65 | } 66 | 67 | fn main() { 68 | // docs.rs and CI don't require linking or updating ogc.rs (and will always fail if we try to) 69 | if std::env::var("DOCS_RS").is_ok() || std::env::var("CI").is_ok() { 70 | return; 71 | } 72 | let dkp_path = env::var("DEVKITPRO").expect("devkitPro is needed to use this crate"); 73 | println!( 74 | "cargo:rustc-link-search=native={}/devkitPPC/powerpc-eabi/lib", 75 | dkp_path 76 | ); 77 | println!("cargo:rustc-link-search=native={}/libogc/lib/wii", dkp_path); 78 | 79 | println!("cargo:rustc-link-lib=static=c"); 80 | println!("cargo:rustc-link-lib=static=sysbase"); 81 | println!("cargo:rustc-link-lib=static=m"); 82 | println!("cargo:rustc-link-lib=static=ogc"); 83 | println!("cargo:rustc-link-lib=static=asnd"); 84 | println!("cargo:rustc-link-lib=static=mad"); 85 | println!("cargo:rustc-link-lib=static=aesnd"); 86 | 87 | //MP3Player 88 | 89 | //Wiipad 90 | println!("cargo:rustc-link-lib=static=bte"); 91 | println!("cargo:rustc-link-lib=static=wiiuse"); 92 | 93 | println!("cargo:rerun-if-changed=wrapper.h"); 94 | #[derive(Debug)] 95 | struct CBParser; 96 | impl ParseCallbacks for CBParser { 97 | fn process_comment(&self, comment: &str) -> Option { 98 | Some(doxygen_rs::transform(comment)) 99 | } 100 | fn header_file(&self, filename: &str) { 101 | println!("cargo:rerun-if-changed={}", filename); 102 | } 103 | 104 | fn include_file(&self, filename: &str) { 105 | println!("cargo:rerun-if-changed={}", filename); 106 | } 107 | 108 | fn read_env_var(&self, key: &str) { 109 | println!("cargo:rerun-if-env-changed={}", key); 110 | } 111 | } 112 | let mut bindings = bindgen::Builder::default() 113 | .header("wrapper.h") 114 | .rust_target(bindgen::RustTarget::Nightly) 115 | .use_core() 116 | .trust_clang_mangling(false) 117 | .layout_tests(false) 118 | .ctypes_prefix("::libc") 119 | .prepend_enum_name(false) 120 | .disable_untagged_union() 121 | .blocklist_type("u(8|16|32|64|128)") 122 | .blocklist_type("i(8|16|32|64|128)") 123 | .blocklist_type("f(32|64)") 124 | .clang_arg("--target=powerpc-none-eabi") 125 | .clang_arg(format!("--sysroot={}/devkitPPC/powerpc-eabi", dkp_path)) 126 | .clang_arg(format!( 127 | "-isystem{}/devkitPPC/powerpc-eabi/include", 128 | dkp_path 129 | )) 130 | .clang_arg(format!( 131 | "-isystem/usr/lib/clang/{}/include", 132 | get_clang_version() 133 | )); 134 | 135 | let includes = get_include_path(dkp_path.clone()); 136 | includes.iter().for_each(|include| { 137 | bindings = bindings.clone().clang_arg(format!("-I{}", include)); 138 | }); 139 | 140 | 141 | let bindings = bindings.clang_arg(format!("-I{}/libogc/include", dkp_path)) 142 | .clang_arg("-mfloat-abi=hard") 143 | .clang_arg("-nostdinc") 144 | .clang_arg("-Wno-macro-redefined") 145 | .clang_arg("-Wno-incompatible-library-redeclaration") 146 | .clang_arg("-DHW_RVL") 147 | .parse_callbacks(Box::new(CBParser)) 148 | .generate() 149 | .expect("Unable to generate bindings"); 150 | 151 | bindings 152 | .write_to_file("./src/ogc.rs") 153 | .expect("Unable to write bindings to file"); 154 | } 155 | -------------------------------------------------------------------------------- /ogc-sys/src/inline.rs: -------------------------------------------------------------------------------- 1 | //! Rust implementation of inline functions not generated by bindgen. 2 | 3 | use crate::wgPipe; 4 | 5 | pub unsafe fn GX_End() {} 6 | 7 | pub unsafe fn GX_Position3f32(x: f32, y: f32, z: f32) { 8 | *(*wgPipe).F32.as_mut() = x; 9 | *(*wgPipe).F32.as_mut() = y; 10 | *(*wgPipe).F32.as_mut() = z; 11 | } 12 | 13 | pub unsafe fn GX_Position3u16(x: u16, y: u16, z: u16) { 14 | *(*wgPipe).U16.as_mut() = x; 15 | *(*wgPipe).U16.as_mut() = y; 16 | *(*wgPipe).U16.as_mut() = z; 17 | } 18 | 19 | pub unsafe fn GX_Position3s16(x: i16, y: i16, z: i16) { 20 | *(*wgPipe).S16.as_mut() = x; 21 | *(*wgPipe).S16.as_mut() = y; 22 | *(*wgPipe).S16.as_mut() = z; 23 | } 24 | 25 | pub unsafe fn GX_Position3u8(x: u8, y: u8, z: u8) { 26 | *(*wgPipe).U8.as_mut() = x; 27 | *(*wgPipe).U8.as_mut() = y; 28 | *(*wgPipe).U8.as_mut() = z; 29 | } 30 | 31 | pub unsafe fn GX_Position3s8(x: i8, y: i8, z: i8) { 32 | *(*wgPipe).S8.as_mut() = x; 33 | *(*wgPipe).S8.as_mut() = y; 34 | *(*wgPipe).S8.as_mut() = z; 35 | } 36 | 37 | pub unsafe fn GX_Position2f32(x: f32, y: f32) { 38 | *(*wgPipe).F32.as_mut() = x; 39 | *(*wgPipe).F32.as_mut() = y; 40 | } 41 | 42 | pub unsafe fn GX_Position2u16(x: u16, y: u16) { 43 | *(*wgPipe).U16.as_mut() = x; 44 | *(*wgPipe).U16.as_mut() = y; 45 | } 46 | 47 | pub unsafe fn GX_Position2s16(x: i16, y: i16) { 48 | *(*wgPipe).S16.as_mut() = x; 49 | *(*wgPipe).S16.as_mut() = y; 50 | } 51 | 52 | pub unsafe fn GX_Position2u8(x: u8, y: u8) { 53 | *(*wgPipe).U8.as_mut() = x; 54 | *(*wgPipe).U8.as_mut() = y; 55 | } 56 | 57 | pub unsafe fn GX_Position2s8(x: i8, y: i8) { 58 | *(*wgPipe).S8.as_mut() = x; 59 | *(*wgPipe).S8.as_mut() = y; 60 | } 61 | 62 | pub unsafe fn GX_Position1x8(index: u8) { 63 | *(*wgPipe).U8.as_mut() = index; 64 | } 65 | 66 | pub unsafe fn GX_Position1x16(index: u16) { 67 | *(*wgPipe).U16.as_mut() = index; 68 | } 69 | 70 | pub unsafe fn GX_Normal3f32(nx: f32, ny: f32, nz: f32) { 71 | *(*wgPipe).F32.as_mut() = nx; 72 | *(*wgPipe).F32.as_mut() = ny; 73 | *(*wgPipe).F32.as_mut() = nz; 74 | } 75 | 76 | pub unsafe fn GX_Normal3s16(nx: i16, ny: i16, nz: i16) { 77 | *(*wgPipe).S16.as_mut() = nx; 78 | *(*wgPipe).S16.as_mut() = ny; 79 | *(*wgPipe).S16.as_mut() = nz; 80 | } 81 | 82 | pub unsafe fn GX_Normal3s8(nx: i8, ny: i8, nz: i8) { 83 | *(*wgPipe).S8.as_mut() = nx; 84 | *(*wgPipe).S8.as_mut() = ny; 85 | *(*wgPipe).S8.as_mut() = nz; 86 | } 87 | 88 | pub unsafe fn GX_Normal1x8(index: u8) { 89 | *(*wgPipe).U8.as_mut() = index; 90 | } 91 | 92 | pub unsafe fn GX_Normal1x16(index: u16) { 93 | *(*wgPipe).U16.as_mut() = index; 94 | } 95 | 96 | pub unsafe fn GX_Color4u8(r: u8, g: u8, b: u8, a: u8) { 97 | *(*wgPipe).U8.as_mut() = r; 98 | *(*wgPipe).U8.as_mut() = g; 99 | *(*wgPipe).U8.as_mut() = b; 100 | *(*wgPipe).U8.as_mut() = a; 101 | } 102 | 103 | pub unsafe fn GX_Color3u8(r: u8, g: u8, b: u8) { 104 | *(*wgPipe).U8.as_mut() = r; 105 | *(*wgPipe).U8.as_mut() = g; 106 | *(*wgPipe).U8.as_mut() = b; 107 | } 108 | 109 | pub unsafe fn GX_Color3f32(r: f32, g: f32, b: f32) { 110 | *(*wgPipe).U8.as_mut() = (r * 255.0) as u8; 111 | *(*wgPipe).U8.as_mut() = (g * 255.0) as u8; 112 | *(*wgPipe).U8.as_mut() = (b * 255.0) as u8; 113 | } 114 | 115 | pub unsafe fn GX_Color1u32(clr: u32) { 116 | *(*wgPipe).U32.as_mut() = clr; 117 | } 118 | 119 | pub unsafe fn GX_Color1u16(clr: u16) { 120 | *(*wgPipe).U16.as_mut() = clr; 121 | } 122 | 123 | pub unsafe fn GX_Color1x8(index: u8) { 124 | *(*wgPipe).U8.as_mut() = index; 125 | } 126 | 127 | pub unsafe fn GX_Color1x16(index: u16) { 128 | *(*wgPipe).U16.as_mut() = index; 129 | } 130 | 131 | pub unsafe fn GX_TexCoord2f32(s: f32, t: f32) { 132 | *(*wgPipe).F32.as_mut() = s; 133 | *(*wgPipe).F32.as_mut() = t; 134 | } 135 | 136 | pub unsafe fn GX_TexCoord2u16(s: u16, t: u16) { 137 | *(*wgPipe).U16.as_mut() = s; 138 | *(*wgPipe).U16.as_mut() = t; 139 | } 140 | 141 | pub unsafe fn GX_TexCoord2s16(s: i16, t: i16) { 142 | *(*wgPipe).S16.as_mut() = s; 143 | *(*wgPipe).S16.as_mut() = t; 144 | } 145 | 146 | pub unsafe fn GX_TexCoord2u8(s: u8, t: u8) { 147 | *(*wgPipe).U8.as_mut() = s; 148 | *(*wgPipe).U8.as_mut() = t; 149 | } 150 | 151 | pub unsafe fn GX_TexCoord2s8(s: i8, t: i8) { 152 | *(*wgPipe).S8.as_mut() = s; 153 | *(*wgPipe).S8.as_mut() = t; 154 | } 155 | 156 | pub unsafe fn GX_TexCoord1f32(s: f32) { 157 | *(*wgPipe).F32.as_mut() = s; 158 | } 159 | 160 | pub unsafe fn GX_TexCoord1u16(s: u16) { 161 | *(*wgPipe).U16.as_mut() = s; 162 | } 163 | 164 | pub unsafe fn GX_TexCoord1s16(s: i16) { 165 | *(*wgPipe).S16.as_mut() = s; 166 | } 167 | 168 | pub unsafe fn GX_TexCoord1u8(s: u8) { 169 | *(*wgPipe).U8.as_mut() = s; 170 | } 171 | 172 | pub unsafe fn GX_TexCoord1s8(s: i8) { 173 | *(*wgPipe).S8.as_mut() = s; 174 | } 175 | 176 | pub unsafe fn GX_TexCoord1x8(index: u8) { 177 | *(*wgPipe).U8.as_mut() = index; 178 | } 179 | 180 | pub unsafe fn GX_TexCoord1x16(index: u16) { 181 | *(*wgPipe).U16.as_mut() = index; 182 | } 183 | 184 | pub unsafe fn GX_MatrixIndex1x8(index: u8) { 185 | *(*wgPipe).U8.as_mut() = index; 186 | } -------------------------------------------------------------------------------- /ogc-sys/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_upper_case_globals)] 2 | #![allow(non_camel_case_types)] 3 | #![allow(non_snake_case)] 4 | #![no_std] 5 | 6 | include!("ogc.rs"); 7 | 8 | mod inline; 9 | pub use inline::*; 10 | -------------------------------------------------------------------------------- /ogc-sys/wrapper.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // Extras 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | //MP3Player 11 | #include 12 | 13 | // Wiimote Input 14 | #include 15 | 16 | -------------------------------------------------------------------------------- /powerpc-unknown-eabi.json: -------------------------------------------------------------------------------- 1 | { 2 | "arch": "powerpc", 3 | "cpu": "750", 4 | "data-layout": "E-m:e-p:32:32-Fn32-i64:64-n32", 5 | "dynamic-linking": false, 6 | "env": "newlib", 7 | "exe-suffix": ".elf", 8 | "executables": true, 9 | "has-rpath": true, 10 | "llvm-target": "powerpc-unknown-eabi", 11 | "linker": "powerpc-eabi-gcc", 12 | "linker-flavor": "gcc", 13 | "linker-is-gnu": true, 14 | "max-atomic-width": 32, 15 | "os": "rvl-ios", 16 | "pre-link-args": { 17 | "gcc": [ 18 | "-mrvl", 19 | "-mcpu=750", 20 | "-meabi", 21 | "-msdata=eabi", 22 | "-mhard-float" 23 | ] 24 | }, 25 | "panic-strategy": "abort", 26 | "relocation-model": "static", 27 | "target-endian": "big", 28 | "target-family": "unix", 29 | "target-mcount": "_mcount", 30 | "target-c-int-width": "32", 31 | "target-pointer-width": "32", 32 | "vendor": "nintendo" 33 | } 34 | -------------------------------------------------------------------------------- /src/aesnd.rs: -------------------------------------------------------------------------------- 1 | use core::time::Duration; 2 | 3 | use crate::ffi; 4 | use alloc::boxed::Box; 5 | use ffi::AESNDPB; 6 | use libc::c_void; 7 | 8 | #[derive(Copy, Clone)] 9 | #[repr(u32)] 10 | pub enum AudioFormat { 11 | VoiceMono8 = ffi::VOICE_MONO8, 12 | VoiceStereo8 = ffi::VOICE_STEREO8, 13 | VoiceMono16 = ffi::VOICE_MONO16, 14 | VoiceStereo16 = ffi::VOICE_STEREO16, 15 | VoiceMono8U = ffi::VOICE_MONO8_UNSIGNED, 16 | VoiceStereo8U = ffi::VOICE_STEREO8_UNSIGNED, 17 | VoiceMono16U = ffi::VOICE_MONO16_UNSIGNED, 18 | VoiceStereo16U = ffi::VOICE_STEREO16_UNSIGNED, 19 | } 20 | 21 | pub type VoiceCallback = Option>; 22 | pub type AudioCallback = Option>; 23 | 24 | pub struct Aesnd; 25 | 26 | impl Aesnd { 27 | pub fn init() -> Self { 28 | unsafe { 29 | ffi::AESND_Init(); 30 | } 31 | Self 32 | } 33 | 34 | pub fn reset() { 35 | unsafe { 36 | ffi::AESND_Reset(); 37 | } 38 | } 39 | 40 | pub fn set_pause(pause: bool) { 41 | unsafe { 42 | ffi::AESND_Pause(pause); 43 | } 44 | } 45 | 46 | pub fn pause() { 47 | Self::set_pause(true); 48 | } 49 | 50 | pub fn unpause() { 51 | Self::set_pause(false); 52 | } 53 | 54 | pub fn get_dsp_process_time() -> Duration { 55 | Duration::from_nanos(unsafe { ffi::AESND_GetDSPProcessTime().into() }) 56 | } 57 | 58 | pub fn get_dsp_process_usage() -> f32 { 59 | unsafe { ffi::AESND_GetDSPProcessUsage() } 60 | } 61 | 62 | pub fn register_audio_callback(callback: Option) { 63 | unsafe { 64 | ffi::AESND_RegisterAudioCallbackWithArg(callback, core::ptr::null_mut()); 65 | } 66 | } 67 | 68 | pub fn set_voice_stop(play_state: &mut AESNDPB, stop: bool) { 69 | unsafe { 70 | ffi::AESND_SetVoiceStop(play_state, stop); 71 | } 72 | } 73 | 74 | pub fn set_voice_mute(play_state: &mut AESNDPB, mute: bool) { 75 | unsafe { 76 | ffi::AESND_SetVoiceMute(play_state, mute); 77 | } 78 | } 79 | 80 | pub fn set_voice_loop(play_state: &mut AESNDPB, loop_: bool) { 81 | unsafe { 82 | ffi::AESND_SetVoiceLoop(play_state, loop_); 83 | } 84 | } 85 | 86 | pub fn set_voice_format(play_state: &mut AESNDPB, format: AudioFormat) { 87 | unsafe { 88 | ffi::AESND_SetVoiceFormat(play_state, format as u32); 89 | } 90 | } 91 | 92 | pub fn set_voice_stream(play_state: &mut AESNDPB, stream: bool) { 93 | unsafe { 94 | ffi::AESND_SetVoiceStream(play_state, stream); 95 | } 96 | } 97 | 98 | pub fn set_voice_frequency(play_state: &mut AESNDPB, frequency: f32) { 99 | unsafe { 100 | ffi::AESND_SetVoiceFrequency(play_state, frequency); 101 | } 102 | } 103 | 104 | pub fn set_voice_volume(play_state: &mut AESNDPB, volume: (f32, f32)) { 105 | unsafe { 106 | ffi::AESND_SetVoiceVolume( 107 | play_state, 108 | (volume.0 * 255.0) as u16, 109 | (volume.1 * 255.0) as u16, 110 | ); 111 | } 112 | } 113 | 114 | pub fn set_voice_delay(play_state: &mut AESNDPB, delay: u32) { 115 | unsafe { 116 | ffi::AESND_SetVoiceDelay(play_state, delay); 117 | } 118 | } 119 | 120 | pub fn set_voice_buffer(play_state: &mut AESNDPB, buffer: &[u8]) { 121 | //if already aligned just use the buffer. 122 | if buffer.as_ptr().align_offset(32) == 0 && buffer.len() % 32 == 0 { 123 | unsafe { 124 | ffi::AESND_SetVoiceBuffer( 125 | play_state, 126 | buffer.as_ptr() as *const c_void, 127 | buffer.len().try_into().unwrap(), 128 | ); 129 | } 130 | } else { 131 | // othersize copy and allocate a buffer for AESND :) 132 | let align_buf = crate::utils::alloc_aligned_buffer(buffer); 133 | assert!( 134 | align_buf.len() % 32 == 0, 135 | "Buffer is not padded to 32 bytes" 136 | ); 137 | unsafe { 138 | ffi::AESND_SetVoiceBuffer( 139 | play_state, 140 | align_buf.as_ptr() as *const c_void, 141 | align_buf.len().try_into().unwrap(), 142 | ); 143 | } 144 | } 145 | } 146 | 147 | pub fn play_voice( 148 | play_state: &mut AESNDPB, 149 | format: AudioFormat, 150 | buffer: &[u8], 151 | frequency: f32, 152 | delay: u32, 153 | loop_: bool, 154 | ) { 155 | if buffer.as_ptr().align_offset(32) == 0 && buffer.len() % 32 == 0 { 156 | unsafe { 157 | ffi::AESND_PlayVoice( 158 | play_state, 159 | format as u32, 160 | buffer.as_ptr() as *const c_void, 161 | buffer.len().try_into().unwrap(), 162 | frequency, 163 | delay, 164 | loop_, 165 | ); 166 | } 167 | } else { 168 | let align_buf = crate::utils::alloc_aligned_buffer(buffer); 169 | assert!( 170 | align_buf.len() % 32 == 0, 171 | "Buffer is not padded to 32 bytes" 172 | ); 173 | unsafe { 174 | ffi::AESND_PlayVoice( 175 | play_state, 176 | format as u32, 177 | align_buf.as_ptr() as *const c_void, 178 | align_buf.len().try_into().unwrap(), 179 | frequency, 180 | delay, 181 | loop_, 182 | ); 183 | } 184 | } 185 | } 186 | 187 | pub fn register_voice_callback( 188 | play_state: &mut AESNDPB, 189 | callback: Option, 190 | ) { 191 | unsafe { 192 | ffi::AESND_RegisterVoiceCallbackWithArg(play_state, callback, core::ptr::null_mut()); 193 | } 194 | } 195 | 196 | pub fn new_playstate() -> AESNDPB { 197 | unsafe { *ffi::AESND_AllocateVoiceWithArg(None, core::ptr::null_mut()) } 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /src/audio.rs: -------------------------------------------------------------------------------- 1 | //! The ``audio`` module of ``ogc-rs``. 2 | //! 3 | //! This module implements a safe wrapper around the audio functions found in ``audio.h``. 4 | 5 | use crate::ffi; 6 | use alloc::boxed::Box; 7 | use core::{convert::TryFrom, mem, ptr}; 8 | use num_enum::{IntoPrimitive, TryFromPrimitive}; 9 | 10 | /// Represents the audio service. 11 | /// No audio control can be done until an instance of this struct is created. 12 | /// This service can only be created once! 13 | pub struct Audio; 14 | 15 | /// The play state of the ``audio`` service. 16 | #[derive(IntoPrimitive, TryFromPrimitive, Debug, Eq, PartialEq)] 17 | #[repr(u32)] 18 | pub enum PlayState { 19 | Started = 1, 20 | Stopped = 0, 21 | } 22 | 23 | /// The sample rate of the ``audio`` service. 24 | #[derive(IntoPrimitive, TryFromPrimitive, Debug, Eq, PartialEq)] 25 | #[repr(u32)] 26 | pub enum SampleRate { 27 | FortyEightKhz = 1, 28 | ThirtySixKhz = 0, 29 | } 30 | 31 | /// Implementation of the audio service. 32 | impl Audio { 33 | /// Initialization of the audio service. 34 | pub fn init() -> Self { 35 | unsafe { 36 | // For now this is a mutable null pointer. 37 | // libogc is fine with this, but this should be changed in the future. 38 | ffi::AUDIO_Init(ptr::null_mut()); 39 | 40 | Self 41 | } 42 | } 43 | 44 | /// Initialize an audio DMA transfer. 45 | fn init_dma(data: &[u8]) { 46 | unsafe { 47 | // libogc has strict restrictions on data alignment and length. 48 | assert_eq!( 49 | 32, 50 | mem::align_of_val(data), 51 | "Data is not aligned correctly." 52 | ); 53 | assert_eq!(0, data.len() % 32, "Data length is not a multiple of 32."); 54 | 55 | ffi::AUDIO_InitDMA(data.as_ptr() as u32, data.len() as u32); 56 | } 57 | } 58 | 59 | /// Start the audio DMA operation. 60 | /// 61 | /// Starts to transfer the data from main memory to the audio interface through DMA. 62 | /// This call should follow the call to ``init_dma`` which is used to initialize DMA transfers. 63 | fn start_dma() { 64 | unsafe { 65 | ffi::AUDIO_StartDMA(); 66 | } 67 | } 68 | 69 | /// Stop the previously started audio DMA operation. 70 | fn stop_dma() { 71 | unsafe { 72 | ffi::AUDIO_StopDMA(); 73 | } 74 | } 75 | 76 | /// Register a user callback function for the ``audio`` streaming interface. 77 | fn register_stream_callback(callback: Box) 78 | where 79 | F: Fn(u32), 80 | { 81 | // TODO: Check if this implementation can be changed. 82 | let ptr = Box::into_raw(callback); 83 | 84 | unsafe { 85 | let code: extern "C" fn(smp_cnt: u32) = mem::transmute(ptr); 86 | // TODO: Do something with the returned callback. 87 | let _ = ffi::AUDIO_RegisterStreamCallback(Some(code)); 88 | } 89 | } 90 | 91 | /// Register a user callback function for the audio DMA interface. 92 | /// 93 | /// This callback will be called whenever the audio DMA requests new data. 94 | /// Internally the DMA buffers are double buffered. 95 | fn register_dma_callback(callback: Box) 96 | where 97 | F: Fn(), 98 | { 99 | // TODO: Check if this implementation can be changed. 100 | let ptr = Box::into_raw(callback); 101 | 102 | unsafe { 103 | let code: extern "C" fn() = mem::transmute(ptr); 104 | // TODO: Do something with the returned callback. 105 | let _ = ffi::AUDIO_RegisterDMACallback(Some(code)); 106 | } 107 | } 108 | 109 | /// Get the count of bytes, left to play, from the audio DMA interface. 110 | fn get_dma_bytes_left() -> u32 { 111 | unsafe { ffi::AUDIO_GetDMABytesLeft() } 112 | } 113 | 114 | /// Get the audio DMA flag. 115 | fn get_dma_enable_flag() -> u16 { 116 | unsafe { ffi::AUDIO_GetDMAEnableFlag() } 117 | } 118 | 119 | /// Get the DMA transfer length configured in the audio DMA interface. 120 | fn get_dma_length() -> u32 { 121 | unsafe { ffi::AUDIO_GetDMALength() } 122 | } 123 | 124 | /// Get the main memory address for the DMA operation. 125 | fn get_dma_address() -> u32 { 126 | unsafe { ffi::AUDIO_GetDMAStartAddr() } 127 | } 128 | 129 | /// Reset the stream sample count register. 130 | fn reset_sample_count() { 131 | unsafe { 132 | ffi::AUDIO_ResetStreamSampleCnt(); 133 | } 134 | } 135 | 136 | /// Set the sample count for the stream trigger. 137 | fn set_trigger_count(count: u32) { 138 | unsafe { 139 | ffi::AUDIO_SetStreamTrigger(count); 140 | } 141 | } 142 | 143 | /// Get streaming sample rate. 144 | fn get_samplerate() -> SampleRate { 145 | let r = unsafe { ffi::AUDIO_GetStreamSampleRate() }; 146 | SampleRate::try_from(r).unwrap() 147 | } 148 | 149 | /// Get the sampling rate for the DSP interface. 150 | fn get_dsp_samplerate() -> SampleRate { 151 | let r = unsafe { ffi::AUDIO_GetDSPSampleRate() }; 152 | SampleRate::try_from(r).unwrap() 153 | } 154 | 155 | /// Set the sample rate for the streaming audio interface. 156 | fn set_samplerate(samplerate: SampleRate) { 157 | unsafe { 158 | ffi::AUDIO_SetStreamSampleRate(samplerate.into()); 159 | } 160 | } 161 | 162 | /// Set the sampling rate for the DSP interface. 163 | fn set_dsp_samplerate(samplerate: SampleRate) { 164 | // TODO: Check implementation. 165 | let sample_rate: u32 = samplerate.into(); 166 | 167 | unsafe { 168 | ffi::AUDIO_SetDSPSampleRate(sample_rate as u8); 169 | } 170 | } 171 | 172 | /// Get the play state from the streaming audio interface. 173 | fn get_playstate() -> PlayState { 174 | let r = unsafe { ffi::AUDIO_GetStreamPlayState() }; 175 | PlayState::try_from(r).unwrap() 176 | } 177 | 178 | /// Set the play state for the streaming audio interface. 179 | fn set_playstate(playstate: PlayState) { 180 | unsafe { 181 | ffi::AUDIO_SetStreamPlayState(playstate.into()); 182 | } 183 | } 184 | 185 | /// Get streaming volume on the left channel. 186 | fn get_volume_left() -> u8 { 187 | unsafe { ffi::AUDIO_GetStreamVolLeft() } 188 | } 189 | 190 | /// Set streaming volume on the left channel. 191 | fn set_volume_left(volume: u8) { 192 | unsafe { ffi::AUDIO_SetStreamVolLeft(volume) } 193 | } 194 | 195 | /// Get streaming volume on the right channel. 196 | fn get_volume_right() -> u8 { 197 | unsafe { ffi::AUDIO_GetStreamVolRight() } 198 | } 199 | 200 | /// Set streaming volume on the right channel. 201 | fn set_volume_right(volume: u8) { 202 | unsafe { ffi::AUDIO_SetStreamVolRight(volume) } 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /src/cache.rs: -------------------------------------------------------------------------------- 1 | use crate::ffi; 2 | 3 | const CACHELINE_SIZE: usize = 32; 4 | 5 | /// Enable L1 data-cache. 6 | pub fn dc_enable() { 7 | unsafe { ffi::DCEnable() } 8 | } 9 | 10 | /// Disable L1 data-cache. 11 | pub fn dc_disable() { 12 | unsafe { ffi::DCDisable() } 13 | } 14 | 15 | /// Invalidate L1 data-cache. 16 | /// 17 | /// Marks the state of each data cache block as invalid without writing back 18 | /// modified cache blocks to memory. Cache access is blocked during this time. 19 | /// Bus accesses to the cache are signaled as a miss during invalidate-all 20 | /// operations. 21 | pub fn dc_flash_invalidate() { 22 | unsafe { ffi::DCFlashInvalidate() } 23 | } 24 | 25 | /// Current contents of the L1 d-cache are locked down and will not be cast out. 26 | /// 27 | /// Hits are still serviced, but misses go straight to L2 or 60x bus. Most cache 28 | /// operations, such as `dc_flush_range()`, will still execute regardless of 29 | /// whether the cache is frozen. 30 | /// 31 | /// NOTE: In PowerPC architecture jargon, this feature is referred to as 32 | /// "locking" the data cache. We use the word "freeze" to distinguish it from 33 | /// the locked cache and DMA features. 34 | pub fn dc_freeze() { 35 | unsafe { ffi::DCFreeze() } 36 | } 37 | 38 | /// Undoes actions of `dc_freeze()`. 39 | /// 40 | /// Old cache blocks will now be cast out on subsequent L1 misses. 41 | /// 42 | /// NOTE: In PowerPC architecture jargon, this feature is referred to as 43 | /// "locking" the data cache. We use the word "freeze" to distinguish it from 44 | /// the locked cache and DMA features. 45 | pub fn dc_unfreeze() { 46 | unsafe { ffi::DCUnfreeze() } 47 | } 48 | 49 | /// Enable L1 i-cache. 50 | pub fn ic_enable() { 51 | unsafe { ffi::ICEnable() } 52 | } 53 | 54 | /// Invalidate the L1 i-cache. 55 | /// 56 | /// An invalidate operation is issued that marks the state of each instruction 57 | /// cache block as invalid without writing back modified cache blocks to memory. 58 | /// 59 | /// Cache access is blocked during this time. Bus accesses to the cache are 60 | /// signaled as a miss during invalidate-all operations. 61 | pub fn ic_flash_invalidate() { 62 | unsafe { ffi::ICFlashInvalidate() } 63 | } 64 | 65 | /// Current contents of the L1 i-cache are locked down and will not be cast out. 66 | /// 67 | /// Hits are still serviced, but misses go straight to L2 or 60x bus. 68 | /// 69 | /// NOTE: In PowerPC architecture jargon, this feature is referred to as 70 | /// "locking" the data cache. We use the word "freeze" to distinguish it from 71 | /// the locked cache and DMA features. 72 | pub fn ic_freeze() { 73 | unsafe { ffi::ICFreeze() } 74 | } 75 | 76 | /// Undoes actions of `ic_freeze()`. 77 | /// 78 | /// Old cache blocks will now be cast out on subsequent L1 misses. 79 | /// 80 | /// NOTE: In PowerPC architecture jargon, this feature is referred to as 81 | /// "locking" the data cache. We use the word "freeze" to distinguish it from 82 | /// the locked cache and DMA features. 83 | pub fn ic_unfreeze() { 84 | unsafe { ffi::ICUnfreeze() } 85 | } 86 | 87 | /// Performs an instruction cache synchronization. 88 | /// 89 | /// This ensures that all instructions preceding this instruction have completed 90 | /// before this instruction completes. 91 | pub fn ic_sync() { 92 | unsafe { ffi::ICSync() } 93 | } 94 | -------------------------------------------------------------------------------- /src/console.rs: -------------------------------------------------------------------------------- 1 | //! The ``console`` module of ``ogc-rs``. 2 | //! 3 | //! This module implements a safe wrapper around the console functions. 4 | 5 | use crate::{ffi, video::Video, OgcError, Result}; 6 | use alloc::string::String; 7 | use core::ptr; 8 | 9 | /// Represents the console service. 10 | /// No console control can be done until an instance of this struct is created. 11 | /// This service can only be created once! 12 | pub struct Console; 13 | 14 | /// Implementation of the console service. 15 | impl Console { 16 | /// Initializes the console subsystem with video. 17 | pub fn init(video: &Video) -> Self { 18 | unsafe { 19 | ffi::CON_Init( 20 | video.framebuffer, 21 | 20, 22 | 20, 23 | video.render_config.framebuffer_width as i32, 24 | video.render_config.extern_framebuffer_height as i32, 25 | (video.render_config.framebuffer_width * 2) as i32, 26 | ); 27 | } 28 | 29 | Self 30 | } 31 | 32 | /// Initialize stdout console. 33 | pub fn init_stdout(xorigin: i32, yorigin: i32, width: i32, height: i32) -> Result<()> { 34 | let init = unsafe { 35 | ffi::CON_InitEx( 36 | ffi::VIDEO_GetPreferredMode(ptr::null_mut()), 37 | xorigin, 38 | yorigin, 39 | width, 40 | height, 41 | ) 42 | }; 43 | 44 | if init < 0 { 45 | Err(OgcError::Console( 46 | "Failed to allocate memory for framebuffer!".into(), 47 | )) 48 | } else { 49 | Ok(()) 50 | } 51 | } 52 | 53 | /// Enable or disable the USB gecko console. 54 | pub fn enable_gecko(channel: i32, safe: i32) { 55 | unsafe { 56 | ffi::CON_EnableGecko(channel, safe); 57 | } 58 | } 59 | 60 | /// Retrieve the columns and rows of the current console. 61 | pub fn get_metrics() -> (i32, i32) { 62 | let coords: (i32, i32) = (0, 0); 63 | 64 | unsafe { 65 | ffi::CON_GetMetrics(coords.0 as *mut i32, coords.1 as *mut i32); 66 | } 67 | 68 | coords 69 | } 70 | 71 | /// Retrieve the current cursor position of the current console. 72 | pub fn get_position() -> (i32, i32) { 73 | let coords: (i32, i32) = (0, 0); 74 | 75 | unsafe { 76 | ffi::CON_GetPosition(coords.0 as *mut i32, coords.1 as *mut i32); 77 | } 78 | 79 | coords 80 | } 81 | 82 | /// Print a formatted string to the console screen through ``printf``. 83 | pub fn print(formatted_string: &str) { 84 | // Create a buffer. 85 | let mut buffer = String::new(); 86 | 87 | // Credit to ``lemarcuspoilus`` on github for this method. 88 | let offset_to_contents = { 89 | let mut it = formatted_string.char_indices(); 90 | loop { 91 | let (i, ch) = match it.next() { 92 | Some(pair) => pair, 93 | None => return, 94 | }; 95 | match ch { 96 | '\n' | '\r' => buffer.push(ch), 97 | _ => break i, 98 | } 99 | } 100 | }; 101 | 102 | buffer.push_str(&formatted_string[offset_to_contents..]); 103 | buffer.push('\0'); 104 | 105 | // Print the buffer. 106 | unsafe { 107 | libc::printf(buffer.as_ptr()); 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/debug.rs: -------------------------------------------------------------------------------- 1 | //! The ``debug`` module of ``ogc-rs``. 2 | //! 3 | //! This module implements a safe wrapper around the debug functions. 4 | 5 | use crate::ffi; 6 | 7 | /// Default EXI channel for use in [`debug_init()`]. Channel can be 0 or 1. 8 | /// 9 | /// **Note**: Used for device type USBGecko 10 | pub const DEF_EXICHAN: u32 = ffi::GDBSTUB_DEF_CHANNEL; 11 | 12 | /// Default TCP port for use in [`debug_init()`]. 13 | /// 14 | /// **Note**: Used for device type TCP. 15 | pub const DEF_TCPPORT: u32 = ffi::GDBSTUB_DEF_TCPPORT; 16 | 17 | /// Enum for gdb stub types. 18 | #[derive(Debug, Eq, PartialEq)] 19 | #[repr(u32)] 20 | pub enum GDBStubDevice { 21 | /// device type: USBGecko 22 | Usb = ffi::GDBSTUB_DEVICE_USB, 23 | /// device type: BBA-TCP 24 | Tcp = ffi::GDBSTUB_DEVICE_TCP, 25 | } 26 | 27 | /// Performs the initialization of the debug stub. 28 | /// 29 | /// * `device_type`: type of device to use. can be either USB or TCP. 30 | /// * `channel_port`: depending on the used device this can be either the EXI 31 | /// channel or the TCP port. 32 | pub fn debug_init(device_type: GDBStubDevice, channel_port: u32) { 33 | unsafe { 34 | ffi::DEBUG_Init(device_type as _, channel_port as i32); 35 | } 36 | } 37 | 38 | /// Stub function to insert the hardware break instruction. 39 | /// 40 | /// This function is used to enter the debug stub and to connect with the host. 41 | /// The developer is free to insert this function at any position in project's 42 | /// source code. 43 | pub fn insert_break() { 44 | unsafe { 45 | ffi::_break(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | //! Custom Error Implementation for ``ogc-rs``. 2 | 3 | use alloc::string::String; 4 | use core::fmt; 5 | 6 | /// Custom Result Type that uses the error type. 7 | pub type Result = core::result::Result; 8 | 9 | /// Custom Error Type 10 | pub enum OgcError { 11 | Network(String), 12 | Audio(String), 13 | Console(String), 14 | System(String), 15 | } 16 | 17 | impl fmt::Debug for OgcError { 18 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 19 | match self { 20 | OgcError::Network(err) => write!(f, "[ OGC - Network ]: {}", err), 21 | OgcError::Audio(err) => write!(f, "[ OGC - Audio ]: {}", err), 22 | OgcError::Console(err) => write!(f, "[ OGC - Console ]: {}", err), 23 | OgcError::System(err) => write!(f, "[ OGC - System ]: {}", err), 24 | } 25 | } 26 | } 27 | 28 | impl fmt::Display for OgcError { 29 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 30 | match self { 31 | OgcError::Network(err) => write!(f, "[ OGC - Network ]: {}", err), 32 | OgcError::Audio(err) => write!(f, "[ OGC - Audio ]: {}", err), 33 | OgcError::Console(err) => write!(f, "[ OGC - Console ]: {}", err), 34 | OgcError::System(err) => write!(f, "[ OGC - System ]: {}", err), 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/glam_impl.rs: -------------------------------------------------------------------------------- 1 | use glam::{Mat4, Vec4}; 2 | use libm::tanf; 3 | 4 | pub trait GxProjection { 5 | fn orthographic_rh_gx( 6 | left: f32, 7 | right: f32, 8 | bottom: f32, 9 | top: f32, 10 | z_near: f32, 11 | z_far: f32, 12 | ) -> Self; 13 | fn perspective_rh_gx(fov_y_radians: f32, aspect_ratio: f32, z_near: f32, z_far: f32) -> Self; 14 | fn to_gx_projection(&self) -> [[f32; 4]; 4]; 15 | } 16 | 17 | impl GxProjection for Mat4 { 18 | fn orthographic_rh_gx( 19 | left: f32, 20 | right: f32, 21 | bottom: f32, 22 | top: f32, 23 | z_near: f32, 24 | z_far: f32, 25 | ) -> Self { 26 | let right_left_aspect = 1.0 / (right - left); 27 | let top_bottom_aspect = 1.0 / (top - bottom); 28 | let plane = 1.0 / (z_far - z_near); 29 | 30 | Self::from_cols( 31 | Vec4::new( 32 | 2.0 * top_bottom_aspect, 33 | 0.0, 34 | 0.0, 35 | -(top + bottom) * top_bottom_aspect, 36 | ), 37 | Vec4::new( 38 | 0.0, 39 | -(2.0) * right_left_aspect, 40 | 0., 41 | -(right + left) * right_left_aspect, 42 | ), 43 | Vec4::new(0.0, 0.0, -(1.0) * plane, -(z_far) * plane), 44 | Vec4::new(0.0, 0.0, 0.0, 1.0), 45 | ) 46 | } 47 | 48 | fn perspective_rh_gx(fov_y_radians: f32, aspect_ratio: f32, z_near: f32, z_far: f32) -> Self { 49 | let cot = 1.0 / tanf(fov_y_radians); 50 | let inv_fn = 1.0 / (z_far - z_near); 51 | 52 | Self::from_cols( 53 | Vec4::new(cot / aspect_ratio, 0.0, 0.0, 0.0), 54 | Vec4::new(0.0, cot, 0.0, 0.0), 55 | Vec4::new(0.0, 0.0, -(z_near) * inv_fn, -(z_far * z_near) * inv_fn), 56 | Vec4::new(0.0, 0.0, -1.0, 0.0), 57 | ) 58 | } 59 | 60 | fn to_gx_projection(&self) -> [[f32; 4]; 4] { 61 | [ 62 | self.x_axis.to_array(), 63 | self.y_axis.to_array(), 64 | self.z_axis.to_array(), 65 | self.w_axis.to_array(), 66 | ] 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/gx/types.rs: -------------------------------------------------------------------------------- 1 | use bit_field::BitField; 2 | 3 | pub struct PixelFormat(u8); 4 | 5 | impl PixelFormat { 6 | pub const RGB8_Z24: Self = Self(0); 7 | pub const RGBA6_Z24: Self = Self(1); 8 | pub const RGB565_Z16: Self = Self(2); 9 | pub const Z24: Self = Self(3); 10 | pub const Y8: Self = Self(4); 11 | pub const U8: Self = Self(5); 12 | pub const V8: Self = Self(6); 13 | pub const YUV420: Self = Self(7); 14 | } 15 | 16 | pub struct ZFormat(u8); 17 | 18 | impl ZFormat { 19 | pub const LINEAR: Self = Self(0); 20 | pub const NEAR: Self = Self(1); 21 | pub const MID: Self = Self(2); 22 | pub const FAR: Self = Self(3); 23 | } 24 | 25 | pub struct ZCompareLocation(bool); 26 | 27 | impl ZCompareLocation { 28 | pub const AFTER_TEXTURE: Self = Self(false); 29 | pub const BEFORE_TEXTURE: Self = Self(true); 30 | } 31 | 32 | pub struct Gamma(pub(crate) u8); 33 | 34 | impl Gamma { 35 | pub const ONE_ZERO: Self = Self(0); 36 | pub const ONE_SEVEN: Self = Self(1); 37 | pub const TWO_TWO: Self = Self(2); 38 | } 39 | 40 | pub struct VtxDest(pub(crate) u8); 41 | 42 | impl VtxDest { 43 | pub const NONE: Self = Self(0); 44 | pub const DIRECT: Self = Self(1); 45 | pub const INDEX8: Self = Self(2); 46 | pub const INDEX16: Self = Self(3); 47 | } 48 | 49 | pub struct PixelEngineControl { 50 | pixel_format: PixelFormat, 51 | z_format: ZFormat, 52 | z_comp_loc: ZCompareLocation, 53 | } 54 | 55 | impl PixelEngineControl { 56 | pub fn new() -> Self { 57 | Self { 58 | pixel_format: PixelFormat::RGBA6_Z24, 59 | z_format: ZFormat::LINEAR, 60 | z_comp_loc: ZCompareLocation::BEFORE_TEXTURE, 61 | } 62 | } 63 | 64 | pub fn to_u32(&self) -> u32 { 65 | let mut pe_ctrl = 0u32; 66 | 67 | pe_ctrl.set_bits(0..=2, self.pixel_format.0.into()); 68 | pe_ctrl.set_bits(3..=5, self.z_format.0.into()); 69 | pe_ctrl.set_bit(6, self.z_comp_loc.0); 70 | 71 | pe_ctrl 72 | } 73 | 74 | #[must_use] 75 | pub fn pixel_format(mut self, format: PixelFormat) -> Self { 76 | self.pixel_format = format; 77 | self 78 | } 79 | 80 | #[must_use] 81 | pub fn z_format(mut self, format: ZFormat) -> Self { 82 | self.z_format = format; 83 | self 84 | } 85 | 86 | #[must_use] 87 | pub fn z_comp_loc(mut self, z_comp_loc: ZCompareLocation) -> Self { 88 | self.z_comp_loc = z_comp_loc; 89 | self 90 | } 91 | } 92 | 93 | impl From for PixelEngineControl { 94 | fn from(pe_ctrl: u32) -> Self { 95 | let pix_fmt = pe_ctrl.get_bits(0..=2); 96 | let z_fmt = pe_ctrl.get_bits(3..=5); 97 | let z_comp_loc = pe_ctrl.get_bit(6); 98 | Self { 99 | pixel_format: PixelFormat(pix_fmt.try_into().unwrap()), 100 | z_format: ZFormat(z_fmt.try_into().unwrap()), 101 | z_comp_loc: ZCompareLocation(z_comp_loc), 102 | } 103 | } 104 | } 105 | 106 | impl Default for PixelEngineControl { 107 | fn default() -> Self { 108 | Self::new() 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/input/controller.rs: -------------------------------------------------------------------------------- 1 | use super::pad::PadButton; 2 | use super::wpad::WPadButton; 3 | use super::{ControllerPort, Pad, WPad}; 4 | 5 | pub enum ControllerType { 6 | Gamecube, 7 | Wii, 8 | } 9 | 10 | pub enum Button { 11 | Left, 12 | Right, 13 | Up, 14 | Down, 15 | TrigL, 16 | TrigR, 17 | TrigZ, 18 | TrigZL, 19 | TrigZR, 20 | A, 21 | B, 22 | C, 23 | X, 24 | Y, 25 | Z, 26 | One, 27 | Two, 28 | Minus, 29 | Plus, 30 | Home, 31 | Start, 32 | } 33 | 34 | impl From