├── .gitignore ├── .gitmodules ├── Cargo.lock ├── Cargo.toml ├── README.md └── src ├── debugger ├── bios.rs ├── gdb │ ├── mod.rs │ └── reply.rs └── mod.rs ├── lib.rs ├── libretro.rs ├── renderer ├── mod.rs └── shaders │ ├── command_fragment.glsl │ ├── command_vertex.glsl │ ├── image_load_fragment.glsl │ ├── image_load_vertex.glsl │ ├── output_fragment.glsl │ └── output_vertex.glsl ├── retrogl ├── buffer.rs ├── error.rs ├── framebuffer.rs ├── mod.rs ├── program.rs ├── shader.rs ├── texture.rs ├── types.rs └── vertex.rs ├── retrolog.rs ├── savestate.rs └── vcd.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "rustation"] 2 | path = rustation 3 | url = https://github.com/simias/rustation.git 4 | branch = libretro 5 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "arrayref" 5 | version = "0.3.4" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | 8 | [[package]] 9 | name = "arrayvec" 10 | version = "0.4.1" 11 | source = "registry+https://github.com/rust-lang/crates.io-index" 12 | dependencies = [ 13 | "nodrop 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", 14 | "odds 0.2.25 (registry+https://github.com/rust-lang/crates.io-index)", 15 | ] 16 | 17 | [[package]] 18 | name = "bitflags" 19 | version = "0.4.0" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | 22 | [[package]] 23 | name = "bitflags" 24 | version = "0.7.0" 25 | source = "registry+https://github.com/rust-lang/crates.io-index" 26 | 27 | [[package]] 28 | name = "bitflags" 29 | version = "0.9.1" 30 | source = "registry+https://github.com/rust-lang/crates.io-index" 31 | 32 | [[package]] 33 | name = "cdimage" 34 | version = "0.1.0" 35 | dependencies = [ 36 | "arrayref 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 37 | "bitflags 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 38 | "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", 39 | ] 40 | 41 | [[package]] 42 | name = "conv" 43 | version = "0.3.3" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | dependencies = [ 46 | "custom_derive 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", 47 | ] 48 | 49 | [[package]] 50 | name = "custom_derive" 51 | version = "0.1.7" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | 54 | [[package]] 55 | name = "gl" 56 | version = "0.6.3" 57 | source = "registry+https://github.com/rust-lang/crates.io-index" 58 | dependencies = [ 59 | "gl_generator 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", 60 | ] 61 | 62 | [[package]] 63 | name = "gl_generator" 64 | version = "0.5.5" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | dependencies = [ 67 | "khronos_api 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 68 | "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 69 | "xml-rs 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", 70 | ] 71 | 72 | [[package]] 73 | name = "kernel32-sys" 74 | version = "0.2.2" 75 | source = "registry+https://github.com/rust-lang/crates.io-index" 76 | dependencies = [ 77 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 78 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 79 | ] 80 | 81 | [[package]] 82 | name = "khronos_api" 83 | version = "1.0.1" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | 86 | [[package]] 87 | name = "lazy_static" 88 | version = "0.2.8" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | 91 | [[package]] 92 | name = "libc" 93 | version = "0.2.31" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | 96 | [[package]] 97 | name = "log" 98 | version = "0.3.8" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | 101 | [[package]] 102 | name = "magenta" 103 | version = "0.1.1" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | dependencies = [ 106 | "conv 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 107 | "magenta-sys 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 108 | ] 109 | 110 | [[package]] 111 | name = "magenta-sys" 112 | version = "0.1.1" 113 | source = "registry+https://github.com/rust-lang/crates.io-index" 114 | dependencies = [ 115 | "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 116 | ] 117 | 118 | [[package]] 119 | name = "nodrop" 120 | version = "0.1.9" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | dependencies = [ 123 | "odds 0.2.25 (registry+https://github.com/rust-lang/crates.io-index)", 124 | ] 125 | 126 | [[package]] 127 | name = "odds" 128 | version = "0.2.25" 129 | source = "registry+https://github.com/rust-lang/crates.io-index" 130 | 131 | [[package]] 132 | name = "rand" 133 | version = "0.3.16" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | dependencies = [ 136 | "libc 0.2.31 (registry+https://github.com/rust-lang/crates.io-index)", 137 | "magenta 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 138 | ] 139 | 140 | [[package]] 141 | name = "redox_syscall" 142 | version = "0.1.31" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | 145 | [[package]] 146 | name = "rustation" 147 | version = "0.0.3" 148 | dependencies = [ 149 | "arrayvec 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", 150 | "cdimage 0.1.0", 151 | "lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 152 | "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 153 | "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", 154 | "shaman 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 155 | ] 156 | 157 | [[package]] 158 | name = "rustation-retro" 159 | version = "0.1.0" 160 | dependencies = [ 161 | "arrayvec 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", 162 | "cdimage 0.1.0", 163 | "gl 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", 164 | "libc 0.2.31 (registry+https://github.com/rust-lang/crates.io-index)", 165 | "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 166 | "rustation 0.0.3", 167 | "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", 168 | "time 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)", 169 | ] 170 | 171 | [[package]] 172 | name = "rustc-serialize" 173 | version = "0.3.24" 174 | source = "registry+https://github.com/rust-lang/crates.io-index" 175 | 176 | [[package]] 177 | name = "shaman" 178 | version = "0.1.0" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | dependencies = [ 181 | "rand 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)", 182 | "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", 183 | ] 184 | 185 | [[package]] 186 | name = "time" 187 | version = "0.1.38" 188 | source = "registry+https://github.com/rust-lang/crates.io-index" 189 | dependencies = [ 190 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 191 | "libc 0.2.31 (registry+https://github.com/rust-lang/crates.io-index)", 192 | "redox_syscall 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)", 193 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 194 | ] 195 | 196 | [[package]] 197 | name = "winapi" 198 | version = "0.2.8" 199 | source = "registry+https://github.com/rust-lang/crates.io-index" 200 | 201 | [[package]] 202 | name = "winapi-build" 203 | version = "0.1.1" 204 | source = "registry+https://github.com/rust-lang/crates.io-index" 205 | 206 | [[package]] 207 | name = "xml-rs" 208 | version = "0.6.1" 209 | source = "registry+https://github.com/rust-lang/crates.io-index" 210 | dependencies = [ 211 | "bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", 212 | ] 213 | 214 | [metadata] 215 | "checksum arrayref 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "0fd1479b7c29641adbd35ff3b5c293922d696a92f25c8c975da3e0acbc87258f" 216 | "checksum arrayvec 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1bc765131a4e4aa3158ca408b09a1513667598655277bedf13e2f21577491c16" 217 | "checksum bitflags 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8dead7461c1127cf637931a1e50934eb6eee8bff2f74433ac7909e9afcee04a3" 218 | "checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d" 219 | "checksum bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5" 220 | "checksum conv 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "78ff10625fd0ac447827aa30ea8b861fead473bb60aeb73af6c1c58caf0d1299" 221 | "checksum custom_derive 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "ef8ae57c4978a2acd8b869ce6b9ca1dfe817bff704c220209fdef2c0b75a01b9" 222 | "checksum gl 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "97543cffb3e39377ecb7156651d906f8284f459a460aef973936412364a4202b" 223 | "checksum gl_generator 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e7acbf2ba3d52e9e1ad96a84362129e9c1aa0af55ebfc86a91004e1b83eca61c" 224 | "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" 225 | "checksum khronos_api 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d5a08e2a31d665af8f1ca437eab6d00a93c9d62a549f73f9ed8fc2e55b5a91a7" 226 | "checksum lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3b37545ab726dd833ec6420aaba8231c5b320814b9029ad585555d2a03e94fbf" 227 | "checksum libc 0.2.31 (registry+https://github.com/rust-lang/crates.io-index)" = "d1419b2939a0bc44b77feb34661583c7546b532b192feab36249ab584b86856c" 228 | "checksum log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "880f77541efa6e5cc74e76910c9884d9859683118839d6a1dc3b11e63512565b" 229 | "checksum magenta 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4bf0336886480e671965f794bc9b6fce88503563013d1bfb7a502c81fe3ac527" 230 | "checksum magenta-sys 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "40d014c7011ac470ae28e2f76a02bfea4a8480f73e701353b49ad7a8d75f4699" 231 | "checksum nodrop 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "52cd74cd09beba596430cc6e3091b74007169a56246e1262f0ba451ea95117b2" 232 | "checksum odds 0.2.25 (registry+https://github.com/rust-lang/crates.io-index)" = "c3df9b730298cea3a1c3faa90b7e2f9df3a9c400d0936d6015e6165734eefcba" 233 | "checksum rand 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)" = "eb250fd207a4729c976794d03db689c9be1d634ab5a1c9da9492a13d8fecbcdf" 234 | "checksum redox_syscall 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)" = "8dde11f18c108289bef24469638a04dce49da56084f2d50618b226e47eb04509" 235 | "checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" 236 | "checksum shaman 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3b1a48b7e97a07b8f5771159e6914d8459557f8f833a60c4bd041f03736844f5" 237 | "checksum time 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)" = "d5d788d3aa77bc0ef3e9621256885555368b47bd495c13dd2e7413c89f845520" 238 | "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" 239 | "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" 240 | "checksum xml-rs 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e1945e12e16b951721d7976520b0832496ef79c31602c7a29d950de79ba74621" 241 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rustation-retro" 3 | 4 | # This version is not the one used in the libretro core "system info", 5 | # instead we take the one from the rustation dependency 6 | version = "0.1.0" 7 | 8 | authors = ["Lionel Flandrin "] 9 | 10 | description = "Libretro implementation for the Rustation PlayStation emulator" 11 | 12 | license = "GPL-2.0+" 13 | keywords = ["emulator", "playstation"] 14 | 15 | [features] 16 | trace = [ "rustation/trace" ] 17 | 18 | [lib] 19 | name = "rustation_retro" 20 | crate-type = ["dylib"] 21 | 22 | [dependencies] 23 | libc = "0.2" 24 | gl = "0.6" 25 | log = "0.3" 26 | arrayvec = "0.4" 27 | rustc-serialize = "0.3" 28 | time = "0.1" 29 | 30 | [dependencies.rustation] 31 | path = "rustation" 32 | 33 | [dependencies.cdimage] 34 | path = "rustation/cdimage" 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Libretro frontend Rustation 2 | 3 | This is an implementation of the [libretro](http://www.libretro.com/) 4 | API for the [Rustation PlayStation 5 | emulator](https://github.com/simias/rustation). 6 | 7 | If you want to submit an issue please do so in the main Rustation 8 | repository. I'm going to try to gather everything there. 9 | 10 | ## Build 11 | 12 | You'll need [Rust and its package manager 13 | Cargo](https://www.rust-lang.org/). Cargo will take care of 14 | downloading the various dependencies used by this crate. 15 | 16 | The Rustation source code is a git submodule of this repository so 17 | you'll want to clone it with: 18 | 19 | ``` 20 | git clone --recursive https://github.com/simias/rustation-libretro.git 21 | ``` 22 | 23 | Then you should be able to build the libretro core from the 24 | `rustation-libretro` directory using: 25 | 26 | ``` 27 | cargo build --release 28 | ``` 29 | 30 | The core will be in the `target/release/` directory. It should be 31 | named `librustation_retro.so` on unix. 32 | 33 | To run the core you'll need a [libretro 34 | frontend](http://wiki.libretro.com/index.php?title=Frontends), I'm 35 | mostly using 36 | [RetroArch](http://wiki.libretro.com/index.php?title=RetroArch) but 37 | please do submit an issue if there's a compatibility problem with an 38 | other frontend. 39 | 40 | ## BIOS files 41 | 42 | You'll need to put a PlayStation BIOS in the "system directory" of the 43 | frontend you're using. 44 | 45 | Rustation-libretro will search for files in this directory until it 46 | finds a valid BIOS for the game's region. Consult your frontend's 47 | documentation to figure out where it puts the system 48 | directory. Alternatively check the logs after loading a game to see 49 | where Rustation-libretro is looking for the BIOS, you should see a 50 | message similar to: 51 | 52 | ``` 53 | Looking for a BIOS for region in "/some/directory" 54 | ``` 55 | 56 | You'll need different BIOSes for the japanese, north-american, and 57 | european regions since PlayStation games are region-locked. You can 58 | just put all the BIOS files in the system directory and let 59 | Rustation-libretro figure out which one to use for the game you're 60 | using. 61 | 62 | If for some reason Rustation-libretro doesn't seem to pick up on your 63 | BIOS file check the logs to see why. The BIOS must match one of the 64 | entries in Rustation's internal database (see `src/bios/db.rs` in 65 | Rustation's source code) otherwise it'll be ignored. If the BIOS 66 | you're using is not part of the database chances are it's a bad dump. 67 | -------------------------------------------------------------------------------- /src/debugger/bios.rs: -------------------------------------------------------------------------------- 1 | use rustation::cpu::Cpu; 2 | use rustation::memory::map::mask_region; 3 | 4 | /// Called every time the PC changes when BIOS call logging is 5 | /// enabled 6 | pub fn check_bios_call(cpu: &mut Cpu) { 7 | let pc = mask_region(cpu.pc()); 8 | 9 | if BIOS_VECTOR_ADDR.contains(&pc) { 10 | // We're in a BIOS vector call 11 | let vector = pc; 12 | // $t1 contains the function number 13 | let func = cpu.regs()[9]; 14 | let ra = cpu.regs()[31]; 15 | 16 | let &(name, param_handlers) = match vector { 17 | 0xa0 => vectors::BIOS_VECTOR_A.get(func as usize), 18 | 0xb0 => vectors::BIOS_VECTOR_B.get(func as usize), 19 | 0xc0 => vectors::BIOS_VECTOR_C.get(func as usize), 20 | _ => None 21 | }.unwrap_or(&("unknown", &[])); 22 | 23 | let mut params = String::new(); 24 | let mut first = true; 25 | 26 | for (i, ph) in param_handlers.iter().enumerate() { 27 | // XXX handle stack parameters when needed 28 | assert!(i <= 3); 29 | 30 | if first { 31 | first = false; 32 | } else { 33 | params.push_str(", "); 34 | } 35 | 36 | let reg = cpu.regs()[4 + i]; 37 | 38 | params.push_str(&ph(cpu, reg)); 39 | } 40 | 41 | 42 | debug!("BIOS call 0x{:02x}[0x{:02x}](RA = 0x{:08x}): {}({})", 43 | vector, func, ra, name, params); 44 | } 45 | } 46 | 47 | /// The addresses of the three BIOS vectors. In order to call a BIOS 48 | /// function the game sets the function number in R9 before jumping to 49 | /// the function's vector. 50 | const BIOS_VECTOR_ADDR: [u32; 3] = [0xa0, 0xb0, 0xc0]; 51 | 52 | mod vectors { 53 | use rustation::cpu::Cpu; 54 | use rustation::memory::Byte; 55 | 56 | type ParamHandler = fn (&mut Cpu, reg: u32) -> String; 57 | 58 | /// Return true if c is a printable ASCII character (including 59 | /// whitespace) 60 | fn is_printable(c: char) -> bool { 61 | c >= ' ' && c <= '~' 62 | } 63 | 64 | fn display_char(c: char) -> String { 65 | match c { 66 | '\\' => "\\\\".into(), 67 | '"' => "\"".into(), 68 | '\'' => "'".into(), 69 | _ => { 70 | if is_printable(c) { 71 | format!("{}", c) 72 | } else { 73 | match c { 74 | '\n' => "\\n".into(), 75 | '\t' => "\\t".into(), 76 | _ => format!("\\x{:02x}", c as u8), 77 | } 78 | } 79 | } 80 | } 81 | } 82 | 83 | fn char_t(_cpu: &mut Cpu, reg: u32) -> String { 84 | let c = reg as u8 as char; 85 | 86 | format!("'{}'", display_char(c)) 87 | } 88 | 89 | fn hex(_cpu: &mut Cpu, reg: u32) -> String { 90 | format!("0x{:x}", reg) 91 | } 92 | 93 | fn uint_t(_cpu: &mut Cpu, reg: u32) -> String { 94 | format!("{}", reg) 95 | } 96 | 97 | fn size_t(_cpu: &mut Cpu, reg: u32) -> String { 98 | format!("{}", reg) 99 | } 100 | 101 | fn int_t(_cpu: &mut Cpu, reg: u32) -> String { 102 | format!("{}", reg as i32) 103 | } 104 | 105 | fn ptr(_cpu: &mut Cpu, reg: u32) -> String { 106 | if reg == 0 { 107 | "(null)".into() 108 | } else { 109 | format!("&0x{:08x}", reg) 110 | } 111 | } 112 | 113 | fn func_ptr(_cpu: &mut Cpu, reg: u32) -> String { 114 | if reg == 0 { 115 | "(null)()".into() 116 | } else { 117 | format!("&0x{:08x}()", reg) 118 | } 119 | } 120 | 121 | fn cstr(cpu: &mut Cpu, reg: u32) -> String { 122 | 123 | if reg == 0 { 124 | return "(null)".into(); 125 | } 126 | 127 | let mut p = reg; 128 | let mut s = String::new(); 129 | 130 | s.push('"'); 131 | 132 | /* Limit the size of strings to avoid spamming a huge message for 133 | * long or buggy strings */ 134 | for _ in 0..32 { 135 | let b = cpu.examine::(p) as u8; 136 | 137 | if b == 0 { 138 | /* End of string */ 139 | s.push('"'); 140 | return s; 141 | } 142 | 143 | let c = b as char; 144 | 145 | s.push_str(&display_char(c)); 146 | p = p.wrapping_add(1); 147 | } 148 | 149 | /* Truncate the string*/ 150 | s.push_str("[...]\""); 151 | 152 | s 153 | } 154 | 155 | fn event_class(_cpu: &mut Cpu, reg: u32) -> String { 156 | let c = 157 | match reg { 158 | 0...0xf => format!("MC:{:x}", reg), 159 | 0xf0000001 => "VBLANK IRQ".into(), 160 | 0xf0000002 => "GPU IRQ1".into(), 161 | 0xf0000003 => "CDROM IRQ".into(), 162 | 0xf0000004 => "DMA IRQ".into(), 163 | 0xf0000005 => "TIMER0 IRQ".into(), 164 | 0xf0000006 => "TIMER1/2 IRQ".into(), 165 | 0xf0000008 => "PADMEMCARD IRQ".into(), 166 | 0xf0000009 => "SPU IRQ".into(), 167 | 0xf000000A => "PIO IRQ".into(), 168 | 0xf000000B => "SIO IRQ".into(), 169 | 0xf0000010 => "Exception!".into(), 170 | 0xf0000011 => "MEMCARD event 1".into(), 171 | 0xf0000012 => "MEMCARD event 2".into(), 172 | 0xf0000013 => "MEMCARD event 3".into(), 173 | 174 | 0xf2000000 => "PCLK counter ".into(), 175 | 0xf2000001 => "HBLANK counter".into(), 176 | 0xf2000002 => "SYSCLK/8 counter".into(), 177 | 0xf2000003 => "VBLANK counter".into(), 178 | 179 | 0xf3000000...0xf3ffffff => 180 | format!("User event 0x{:x}", reg & 0xffffff), 181 | 182 | 0xf4000001 => "MEMCARD BIOS event".into(), 183 | 0xf4000002 => "Libmath event".into(), 184 | 185 | 0xff000000...0xffffffff => 186 | format!("Thread event 0x{:x}", reg & 0xffffff), 187 | 188 | _ => 189 | format!("UNKNOWN EVENT 0x{:x}", reg) 190 | }; 191 | 192 | format!("Class {} [0x{:x}]", c, reg) 193 | } 194 | 195 | fn event_spec(_cpu: &mut Cpu, reg: u32) -> String { 196 | 197 | // XXX This looks like it should be a bitfield but I'm not 198 | // sure that it is? In particular codes like 0x8001 make no 199 | // sense. 200 | let spec = 201 | match reg { 202 | 0x0001 => "Counter is 0", 203 | 0x0002 => "Interrupt", 204 | 0x0004 => "End of I/O", 205 | 0x0008 => "File closed", 206 | 0x0010 => "Command acknowledged", 207 | 0x0020 => "Command completed", 208 | 0x0040 => "Data ready", 209 | 0x0080 => "Data end", 210 | 0x0100 => "Timeout", 211 | 0x0200 => "Unknown command", 212 | 0x0400 => "End of read buffer", 213 | 0x0800 => "End of write buffer", 214 | 0x1000 => "General interrupt", 215 | 0x2000 => "New device", 216 | 0x4000 => "System call", 217 | 0x8000 => "Error", 218 | 0x8001 => "Write error", 219 | 0x0301 => "Libmath domain error", 220 | 0x0302 => "Libmath range error", 221 | _ => "Unknown", 222 | }; 223 | 224 | format!("Spec {} [0x{:x}]", spec, reg) 225 | } 226 | 227 | fn void(_cpu: &mut Cpu, _reg: u32) -> String { 228 | "void".into() 229 | } 230 | 231 | /// BIOS vector A functions, lifted from No$ 232 | pub static BIOS_VECTOR_A: [(&'static str, &'static [ParamHandler]); 0xb5] = [ 233 | ("FileOpen", &[cstr, hex]), 234 | ("FileSeek", &[int_t, hex, hex]), 235 | ("FileRead", &[int_t, ptr, hex]), 236 | ("FileWrite", &[int_t, cstr, hex]), 237 | ("FileClose", &[int_t]), 238 | ("FileIoctl", &[int_t, hex, hex]), 239 | ("exit", &[uint_t]), 240 | ("FileGetDeviceFlag", &[int_t]), 241 | ("FileGetc", &[int_t]), 242 | ("FilePutc", &[char_t, int_t]), 243 | ("todigit", &[char_t]), 244 | ("atof", &[cstr]), 245 | ("strtoul", &[cstr, ptr, int_t]), 246 | ("strtol", &[cstr, ptr, int_t]), 247 | ("abs", &[int_t]), 248 | ("labs", &[int_t]), 249 | ("atoi", &[cstr]), 250 | ("atol", &[cstr]), 251 | ("atob", &[cstr, ptr]), 252 | ("SaveState", &[ptr]), 253 | ("RestoreState", &[ptr, uint_t]), 254 | ("strcat", &[cstr, cstr]), 255 | ("strncat", &[cstr, cstr, size_t]), 256 | ("strcmp", &[cstr, cstr]), 257 | ("strncmp", &[cstr, cstr, size_t]), 258 | ("strcpy", &[ptr, cstr]), 259 | ("strncpy", &[ptr, cstr, size_t]), 260 | ("strlen", &[cstr]), 261 | ("index", &[cstr, char_t]), 262 | ("rindex", &[cstr, char_t]), 263 | ("strchr", &[cstr, char_t]), 264 | ("strrchr", &[cstr, char_t]), 265 | ("strpbrk", &[cstr, ptr]), 266 | ("strspn", &[cstr, ptr]), 267 | ("strcspn", &[cstr, ptr]), 268 | ("strtok", &[cstr, ptr]), 269 | ("strstr", &[cstr, cstr]), 270 | ("toupper", &[char_t]), 271 | ("tolower", &[char_t]), 272 | ("bcopy", &[ptr, ptr, hex]), 273 | ("bzero", &[ptr, hex]), 274 | ("bcmp", &[ptr, ptr, size_t]), 275 | ("memcpy", &[ptr, ptr, size_t]), 276 | ("memset", &[ptr, char_t, size_t]), 277 | ("memmove", &[ptr, ptr, size_t]), 278 | ("memcmp", &[ptr, ptr, size_t]), 279 | ("memchr", &[ptr, char_t, size_t]), 280 | ("rand", &[void]), 281 | ("srand", &[uint_t]), 282 | ("qsort", &[ptr, size_t, size_t, func_ptr]), 283 | ("strtod", &[cstr, ptr]), 284 | ("malloc", &[size_t]), 285 | ("free", &[ptr]), 286 | ("lsearch", &[ptr, ptr, ptr, size_t, func_ptr]), 287 | ("bsearch", &[ptr, ptr, size_t, size_t, func_ptr]), 288 | ("calloc", &[size_t, size_t]), 289 | ("realloc", &[ptr, size_t]), 290 | ("InitHeap", &[hex, size_t]), 291 | ("SystemErrorExit", &[uint_t]), 292 | ("std_in_getchar", &[void]), 293 | ("std_out_putchar", &[char_t]), 294 | ("std_in_gets", &[ptr]), 295 | ("std_out_puts", &[cstr]), 296 | ("printf", &[cstr]), 297 | ("SystemErrorUnresolvedException", &[void]), 298 | ("LoadExeHeader", &[cstr, ptr]), 299 | ("LoadExeFile", &[cstr, ptr]), 300 | ("DoExecute", &[ptr, hex, hex]), 301 | ("FlushCache", &[void]), 302 | ("init_a0_b0_c0_vectors", &[void]), 303 | ("GPU_dw", &[uint_t, uint_t, uint_t, uint_t, ptr]), 304 | ("gpu_send_dma", &[uint_t, uint_t, uint_t, uint_t, ptr]), 305 | ("SendGP1Command", &[hex]), 306 | ("GPU_cw", &[hex]), 307 | ("GPU_cwp", &[ptr, size_t]), 308 | ("send_gpu_linked_list", &[ptr]), 309 | ("gpu_abort_dma", &[void]), 310 | ("GetGPUStatus", &[void]), 311 | ("gpu_sync", &[void]), 312 | ("SystemError", &[]), 313 | ("SystemError", &[]), 314 | ("LoadAndExecute", &[cstr, hex, hex]), 315 | ("GetSysSp", &[void]), 316 | ("SystemError", &[]), 317 | ("CdInit", &[void]), 318 | ("_bu_init", &[void]), 319 | ("CdRemove", &[void]), 320 | ("unknown", &[]), 321 | ("unknown", &[]), 322 | ("unknown", &[]), 323 | ("unknown", &[]), 324 | ("dev_tty_init", &[void]), 325 | ("dev_tty_open", &[uint_t, cstr, hex]), 326 | ("dev_tty_in_out", &[uint_t, hex]), 327 | ("dev_tty_ioctl", &[uint_t, hex, hex]), 328 | ("dev_cd_open", &[uint_t, cstr, hex]), 329 | ("dev_cd_read", &[uint_t, ptr, size_t]), 330 | ("dev_cd_close", &[uint_t]), 331 | ("dev_cd_firstfile", &[uint_t, cstr, hex]), 332 | ("dev_cd_nextfile", &[uint_t, uint_t]), 333 | ("dev_cd_chdir", &[uint_t, cstr]), 334 | ("dev_card_open", &[uint_t, cstr, hex]), 335 | ("dev_card_read", &[uint_t, ptr, size_t]), 336 | ("dev_card_write", &[uint_t, ptr, size_t]), 337 | ("dev_card_close", &[uint_t]), 338 | ("dev_card_firstfile", &[uint_t, cstr, hex]), 339 | ("dev_card_nextfile", &[uint_t, uint_t]), 340 | ("dev_card_erase", &[uint_t, cstr]), 341 | ("dev_card_undelete", &[uint_t, cstr]), 342 | ("dev_card_format", &[uint_t]), 343 | ("dev_card_rename", &[uint_t, cstr, uint_t, cstr]), 344 | ("unknown", &[]), 345 | ("_bu_init", &[void]), 346 | ("CdInit", &[void]), 347 | ("CdRemove", &[void]), 348 | ("unknown", &[]), 349 | ("unknown", &[]), 350 | ("unknown", &[]), 351 | ("unknown", &[]), 352 | ("unknown", &[]), 353 | ("CdAsyncSeekL", &[ptr]), 354 | ("unknown", &[]), 355 | ("unknown", &[]), 356 | ("unknown", &[]), 357 | ("CdAsyncGetStatus", &[ptr]), 358 | ("unknown", &[]), 359 | ("CdAsyncReadSector", &[uint_t, ptr, hex]), 360 | ("unknown", &[]), 361 | ("unknown", &[]), 362 | ("CdAsyncSetMode", &[hex]), 363 | ("unknown", &[]), 364 | ("unknown", &[]), 365 | ("unknown", &[]), 366 | ("unknown", &[]), 367 | ("unknown", &[]), 368 | ("unknown", &[]), 369 | ("unknown", &[]), 370 | ("unknown", &[]), 371 | ("unknown", &[]), 372 | ("unknown", &[]), 373 | ("unknown", &[]), 374 | ("unknown", &[]), 375 | ("unknown", &[]), 376 | ("unknown", &[]), 377 | ("CdromIoIrqFunc1", &[void]), 378 | ("CdromDmaIrqFunc1", &[void]), 379 | ("CdromIoIrqFunc2", &[void]), 380 | ("CdromDmaIrqFunc2", &[void]), 381 | ("CdromGetInt5errCode", &[ptr, ptr]), 382 | ("CdInitSubFunc", &[void]), 383 | ("AddCDROMDevice", &[void]), 384 | ("AddMemCardDevice", &[void]), 385 | ("AddDuartTtyDevice", &[void]), 386 | ("AddDummyTtyDevice", &[void]), 387 | ("SystemError", &[]), 388 | ("SystemError", &[]), 389 | ("SetConf", &[uint_t, uint_t, ptr]), 390 | ("GetConf", &[ptr, ptr, ptr]), 391 | ("SetCdromIrqAutoAbort", &[uint_t, hex]), 392 | ("SetMemSize", &[uint_t]), 393 | ("WarmBoot", &[void]), 394 | ("SystemErrorBootOrDiskFailure", &[cstr, hex]), 395 | ("EnqueueCdIntr", &[void]), 396 | ("DequeueCdIntr", &[void]), 397 | ("CdGetLbn", &[cstr]), 398 | ("CdReadSector", &[size_t, uint_t, ptr]), 399 | ("CdGetStatus", &[void]), 400 | ("bu_callback_okay", &[]), 401 | ("bu_callback_err_write", &[]), 402 | ("bu_callback_err_busy", &[]), 403 | ("bu_callback_err_eject", &[]), 404 | ("_card_info", &[uint_t]), 405 | ("_card_async_load_directory", &[uint_t]), 406 | ("set_card_auto_format", &[hex]), 407 | ("bu_callback_err_prev_write", &[]), 408 | ("card_write_test", &[uint_t]), 409 | ("unknown", &[]), 410 | ("unknown", &[]), 411 | ("ioabort_raw", &[uint_t]), 412 | ("unknown", &[]), 413 | ("GetSystemInfo", &[hex]), 414 | ]; 415 | 416 | /// BIOS vector B functions, lifted from No$ 417 | pub static BIOS_VECTOR_B: [(&'static str, &'static [ParamHandler]); 0x5e] = [ 418 | ("alloc_kernel_memory", &[size_t]), 419 | ("free_kernel_memory", &[ptr]), 420 | ("init_timer", &[uint_t, hex, hex]), 421 | ("get_timer", &[uint_t]), 422 | ("enable_timer_irq", &[uint_t]), 423 | ("disable_timer_irq", &[uint_t]), 424 | ("restart_timer", &[uint_t]), 425 | ("DeliverEvent", &[event_class, event_spec]), 426 | ("OpenEvent", &[event_class, event_spec, hex, func_ptr]), 427 | ("CloseEvent", &[uint_t]), 428 | ("WaitEvent", &[uint_t]), 429 | ("TestEvent", &[uint_t]), 430 | ("EnableEvent", &[uint_t]), 431 | ("DisableEvent", &[uint_t]), 432 | ("OpenThread", &[ptr, ptr, ptr]), 433 | ("CloseThread", &[ptr]), 434 | ("ChangeThread", &[ptr]), 435 | ("unknown", &[]), 436 | ("InitPad", &[ptr, size_t, ptr, size_t]), 437 | ("StartPad", &[void]), 438 | ("StopPad", &[void]), 439 | ("OutdatedPadInitAndStart", &[hex, ptr, hex, hex]), 440 | ("OutdatedPadGetButtons", &[void]), 441 | ("ReturnFromException", &[void]), 442 | ("SetDefaultExitFromException", &[void]), 443 | ("SetCustomExitFromException", &[ptr]), 444 | ("unknown", &[]), 445 | ("unknown", &[]), 446 | ("unknown", &[]), 447 | ("unknown", &[]), 448 | ("unknown", &[]), 449 | ("unknown", &[]), 450 | ("UnDeliverEvent", &[event_class, event_spec]), 451 | ("unknown", &[]), 452 | ("unknown", &[]), 453 | ("unknown", &[]), 454 | ("unknown", &[]), 455 | ("unknown", &[]), 456 | ("unknown", &[]), 457 | ("unknown", &[]), 458 | ("unknown", &[]), 459 | ("unknown", &[]), 460 | ("unknown", &[]), 461 | ("unknown", &[]), 462 | ("unknown", &[]), 463 | ("unknown", &[]), 464 | ("unknown", &[]), 465 | ("unknown", &[]), 466 | ("unknown", &[]), 467 | ("unknown", &[]), 468 | ("FileOpen", &[cstr, hex]), 469 | ("FileSeek", &[uint_t, size_t, hex]), 470 | ("FileRead", &[uint_t, ptr, size_t]), 471 | ("FileWrite", &[uint_t, ptr, size_t]), 472 | ("FileClose", &[uint_t]), 473 | ("FileIoctl", &[uint_t, hex, hex]), 474 | ("exit", &[uint_t]), 475 | ("FileGetDeviceFlag", &[uint_t]), 476 | ("FileGetc", &[uint_t]), 477 | ("FilePutc", &[char_t, uint_t]), 478 | ("std_in_getchar", &[void]), 479 | ("std_out_putchar", &[char_t]), 480 | ("std_in_gets", &[ptr]), 481 | ("std_out_puts", &[ptr]), 482 | ("chdir", &[cstr]), 483 | ("FormatDevice", &[cstr]), 484 | ("firstfile", &[cstr, hex]), 485 | ("nextfile", &[cstr, hex]), 486 | ("FileRename", &[cstr, cstr]), 487 | ("FileDelete", &[cstr]), 488 | ("FileUndelete", &[cstr]), 489 | ("AddDevice", &[ptr]), 490 | ("RemoveDevice", &[cstr]), 491 | ("PrintInstalledDevices", &[void]), 492 | ("InitCard", &[hex]), 493 | ("StartCard", &[void]), 494 | ("StopCard", &[void]), 495 | ("_card_info_subfunc", &[uint_t]), 496 | ("write_card_sector", &[uint_t, uint_t, ptr]), 497 | ("read_card_sector", &[uint_t, uint_t, ptr]), 498 | ("allow_new_card", &[void]), 499 | ("Krom2RawAdd", &[hex]), 500 | ("SystemError", &[]), 501 | ("Krom2Offset", &[hex]), 502 | ("GetLastError", &[void]), 503 | ("GetLastFileError", &[uint_t]), 504 | ("GetC0Table", &[void]), 505 | ("GetB0Table", &[void]), 506 | ("get_bu_callback_port", &[void]), 507 | ("testdevice", &[cstr]), 508 | ("SystemError", &[]), 509 | ("ChangeClearPad", &[uint_t]), 510 | ("get_card_status", &[uint_t]), 511 | ("wait_card_status", &[uint_t]), 512 | ]; 513 | 514 | /// BIOS vector C functions, lifted from No$ 515 | pub static BIOS_VECTOR_C: [(&'static str, &'static [ParamHandler]); 0x1e] = [ 516 | ("EnqueueTimerAndVblankIrqs", &[uint_t]), 517 | ("EnqueueSyscallHandler", &[uint_t]), 518 | ("SysEnqIntRP", &[uint_t, ptr]), 519 | ("SysDeqIntRP", &[uint_t, ptr]), 520 | ("get_free_EvCB_slot", &[void]), 521 | ("get_free_TCB_slot", &[void]), 522 | ("ExceptionHandler", &[void]), 523 | ("InstallExceptionHandlers", &[void]), 524 | ("SysInitMemory", &[ptr, size_t]), 525 | ("SysInitKernelVariables", &[void]), 526 | ("ChangeClearRCnt", &[uint_t, hex]), 527 | ("SystemError", &[]), 528 | ("InitDefInt", &[uint_t]), 529 | ("SetIrqAutoAck", &[uint_t, hex]), 530 | ("unknown", &[]), 531 | ("unknown", &[]), 532 | ("unknown", &[]), 533 | ("unknown", &[]), 534 | ("InstallDevices", &[hex]), 535 | ("FlushStdInOutPut", &[void]), 536 | ("unknown", &[]), 537 | ("tty_cdevinput", &[uint_t, char_t]), 538 | ("tty_cdevscan", &[void]), 539 | ("tty_circgetc", &[uint_t]), 540 | ("tty_circputc", &[char_t, uint_t]), 541 | ("ioabort", &[cstr, cstr]), 542 | ("set_card_find_mode", &[hex]), 543 | ("KernelRedirect", &[hex]), 544 | ("AdjustA0Table", &[void]), 545 | ("get_card_find_mode", &[void]), 546 | ]; 547 | } 548 | -------------------------------------------------------------------------------- /src/debugger/gdb/mod.rs: -------------------------------------------------------------------------------- 1 | use std::net::{TcpListener, TcpStream}; 2 | use std::io::{Read, Write}; 3 | 4 | use rustation::cpu::Cpu; 5 | use rustation::memory::{Byte, HalfWord, Word}; 6 | 7 | use debugger::Debugger; 8 | 9 | use self::reply::Reply; 10 | 11 | mod reply; 12 | 13 | pub type GdbResult = Result<(), ()>; 14 | 15 | pub struct GdbRemote { 16 | remote: TcpStream, 17 | } 18 | 19 | impl GdbRemote { 20 | pub fn new(listener: &TcpListener) -> GdbRemote { 21 | info!("Debugger waiting for gdb connection..."); 22 | 23 | let remote = 24 | match listener.accept() { 25 | Ok((stream, sockaddr)) => { 26 | info!("Connection from {}", sockaddr); 27 | stream 28 | } 29 | Err(e) => panic!("Accept failed: {}", e), 30 | }; 31 | 32 | GdbRemote { 33 | remote: remote, 34 | } 35 | } 36 | 37 | // Serve a single remote request 38 | pub fn serve(&mut self, 39 | debugger: &mut Debugger, 40 | cpu: &mut Cpu) -> GdbResult { 41 | 42 | match self.next_packet() { 43 | PacketResult::Ok(packet) => { 44 | try!(self.ack()); 45 | self.handle_packet(debugger, cpu, &packet) 46 | } 47 | PacketResult::BadChecksum(_) => { 48 | // Request retransmission 49 | self.nack() 50 | } 51 | PacketResult::EndOfStream => { 52 | // Session over 53 | Err(()) 54 | } 55 | } 56 | } 57 | 58 | /// Attempt to return a single GDB packet. 59 | fn next_packet(&mut self) -> PacketResult { 60 | 61 | // Parser state machine 62 | enum State { 63 | WaitForStart, 64 | InPacket, 65 | WaitForCheckSum, 66 | WaitForCheckSum2(u8), 67 | }; 68 | 69 | let mut state = State::WaitForStart; 70 | 71 | let mut packet = Vec::new(); 72 | let mut csum = 0u8; 73 | 74 | for r in (&self.remote).bytes() { 75 | 76 | let byte = 77 | match r { 78 | Ok(b) => b, 79 | Err(e) => { 80 | warn!("GDB remote error: {}", e); 81 | return PacketResult::EndOfStream; 82 | } 83 | }; 84 | 85 | match state { 86 | State::WaitForStart => { 87 | if byte == b'$' { 88 | // Start of packet 89 | state = State::InPacket; 90 | } 91 | } 92 | State::InPacket => { 93 | if byte == b'#' { 94 | // End of packet 95 | state = State::WaitForCheckSum; 96 | } else { 97 | // Append byte to the packet 98 | packet.push(byte); 99 | // Update checksum 100 | csum = csum.wrapping_add(byte); 101 | } 102 | } 103 | State::WaitForCheckSum => { 104 | match ascii_hex(byte) { 105 | Some(b) => { 106 | state = State::WaitForCheckSum2(b); 107 | } 108 | None => { 109 | warn!("Got invalid GDB checksum char {}", 110 | byte); 111 | return PacketResult::BadChecksum(packet); 112 | } 113 | } 114 | } 115 | State::WaitForCheckSum2(c1) => { 116 | match ascii_hex(byte) { 117 | Some(c2) => { 118 | let expected = (c1 << 4) | c2; 119 | 120 | if expected != csum { 121 | warn!("Got invalid GDB checksum: {:x} {:x}", 122 | expected, csum); 123 | return PacketResult::BadChecksum(packet); 124 | } 125 | 126 | // Checksum is good, we're done! 127 | return PacketResult::Ok(packet); 128 | } 129 | None => { 130 | warn!("Got invalid GDB checksum char {}", 131 | byte); 132 | return PacketResult::BadChecksum(packet); 133 | } 134 | } 135 | } 136 | } 137 | } 138 | 139 | warn!("GDB remote end of stream"); 140 | return PacketResult::EndOfStream; 141 | } 142 | 143 | /// Acknowledge packet reception 144 | fn ack(&mut self) -> GdbResult { 145 | if let Err(e) = self.remote.write(b"+") { 146 | warn!("Couldn't send ACK to GDB remote: {}", e); 147 | Err(()) 148 | } else { 149 | Ok(()) 150 | } 151 | } 152 | 153 | /// Request packet retransmission 154 | fn nack(&mut self) -> GdbResult { 155 | if let Err(e) = self.remote.write(b"-") { 156 | warn!("Couldn't send NACK to GDB remote: {}", e); 157 | Err(()) 158 | } else { 159 | Ok(()) 160 | } 161 | } 162 | 163 | fn handle_packet(&mut self, 164 | debugger: &mut Debugger, 165 | cpu: &mut Cpu, 166 | packet: &[u8]) -> GdbResult { 167 | 168 | let command = packet[0]; 169 | let args = &packet[1..]; 170 | 171 | let res = 172 | match command { 173 | b'?' => self.send_status(), 174 | b'm' => self.read_memory(cpu, args), 175 | b'g' => self.read_registers(cpu), 176 | b'c' => self.resume(debugger, cpu, args), 177 | b's' => self.step(debugger, cpu, args), 178 | b'Z' => self.add_breakpoint(debugger, args), 179 | b'z' => self.del_breakpoint(debugger, args), 180 | // Send empty response for unsupported packets 181 | _ => self.send_empty_reply(), 182 | }; 183 | 184 | // Check for errors 185 | try!(res); 186 | 187 | Ok(()) 188 | } 189 | 190 | fn send_reply(&mut self, reply: Reply) -> GdbResult { 191 | match self.remote.write(&reply.into_packet()) { 192 | // XXX Should we check the number of bytes written? What 193 | // do we do if it's less than we expected, retransmit? 194 | Ok(_) => Ok(()), 195 | Err(e) => { 196 | warn!("Couldn't send data to GDB remote: {}", e); 197 | Err(()) 198 | } 199 | } 200 | } 201 | 202 | fn send_empty_reply(&mut self) -> GdbResult { 203 | self.send_reply(Reply::new()) 204 | } 205 | 206 | fn send_string(&mut self, string: &[u8]) -> GdbResult { 207 | let mut reply = Reply::new(); 208 | 209 | reply.push(string); 210 | 211 | self.send_reply(reply) 212 | } 213 | 214 | fn send_error(&mut self) -> GdbResult { 215 | // GDB remote doesn't specify what the error codes should 216 | // be. Should be bother coming up with our own convention? 217 | self.send_string(b"E00") 218 | } 219 | 220 | pub fn send_status(&mut self) -> GdbResult { 221 | // Maybe we should return different values depending on the 222 | // cause of the "break"? 223 | self.send_string(b"S00") 224 | } 225 | 226 | pub fn send_ok(&mut self) -> GdbResult { 227 | self.send_string(b"OK") 228 | } 229 | 230 | fn read_registers(&mut self, cpu: &mut Cpu) -> GdbResult { 231 | 232 | let mut reply = Reply::new(); 233 | 234 | // Send general purpose registers 235 | for &r in cpu.regs() { 236 | reply.push_u32(r); 237 | } 238 | 239 | // Send control registers 240 | for &r in &[ cpu.sr(), 241 | cpu.lo(), 242 | cpu.hi(), 243 | cpu.bad(), 244 | // XXX We should figure out a way to get the real 245 | // irq_state over here to get the real CAUSE 246 | // register 247 | 0xbadbad, 248 | cpu.pc() ] { 249 | reply.push_u32(r); 250 | } 251 | 252 | // GDB expects 73 registers for the MIPS architecture: the 38 253 | // above plus all the floating point registers. Since the 254 | // playstation doesn't support those we just return `x`s to 255 | // notify GDB that those registers are unavailable. 256 | // 257 | // The doc says that it's normally used for core dumps however 258 | // (when the value of a register can't be found in a trace) so 259 | // I'm not sure it's the right thing to do. If it causes 260 | // problems we might just return 0 (or some sane default 261 | // value) instead. 262 | for _ in 38..73 { 263 | reply.push(b"xxxxxxxx"); 264 | } 265 | 266 | self.send_reply(reply) 267 | } 268 | 269 | /// Read a region of memory. The packet format should be 270 | /// `ADDR,LEN`, both in hexadecimal 271 | fn read_memory(&mut self, 272 | cpu: &mut Cpu, 273 | args: &[u8]) -> GdbResult { 274 | 275 | let mut reply = Reply::new(); 276 | 277 | let (addr, len) = try!(parse_addr_len(args)); 278 | 279 | if len == 0 { 280 | // Should we reply with an empty string here? Probably 281 | // doesn't matter 282 | return self.send_error(); 283 | } 284 | 285 | // We can now fetch the data. First we handle the case where 286 | // addr is not aligned using an ad-hoc heuristic. A better way 287 | // to do this might be to figure out which peripheral we're 288 | // accessing and select the most meaningful access width. 289 | let align = addr % 4; 290 | 291 | let sent = 292 | match align { 293 | 1|3 => { 294 | // If we fall on the first or third byte of a word 295 | // we use byte accesses until we reach the next 296 | // word or the end of the requested length 297 | let count = ::std::cmp::min(len, 4 - align); 298 | 299 | for i in 0..count { 300 | let b = cpu.examine::(addr.wrapping_add(i)); 301 | reply.push_u8(b as u8); 302 | } 303 | count 304 | } 305 | 2 => { 306 | if len == 1 { 307 | // Only one byte to read 308 | reply.push_u8(cpu.examine::(addr) as u8); 309 | 1 310 | } else { 311 | reply.push_u16(cpu.examine::(addr) as u16); 312 | 2 313 | } 314 | } 315 | _ => 0, 316 | }; 317 | 318 | let addr = addr.wrapping_add(sent); 319 | let len = len - sent; 320 | 321 | // We can now deal with the word-aligned portion of the 322 | // transfer (if any). It's possible that addr is not word 323 | // aligned here if we entered the case "align == 2, len == 1" 324 | // above but it doesn't matter because in this case "nwords" 325 | // will be 0 so nothing will be fetched. 326 | let nwords = len / 4; 327 | 328 | for i in 0..nwords { 329 | reply.push_u32(cpu.examine::(addr + i * 4)); 330 | } 331 | 332 | // See if we have anything remaining 333 | let addr = addr.wrapping_add(nwords * 4); 334 | let rem = len - nwords * 4; 335 | 336 | match rem { 337 | 1|3 => { 338 | for i in 0..rem { 339 | let b = cpu.examine::(addr.wrapping_add(i)); 340 | reply.push_u8(b as u8); 341 | } 342 | } 343 | 2 => { 344 | reply.push_u16(cpu.examine::(addr) as u16); 345 | } 346 | _ => () 347 | } 348 | 349 | self.send_reply(reply) 350 | } 351 | 352 | /// Continue execution 353 | fn resume(&mut self, 354 | debugger: &mut Debugger, 355 | cpu: &mut Cpu, 356 | args: &[u8]) -> GdbResult { 357 | 358 | if args.len() > 0 { 359 | // If an address is provided we restart from there 360 | let addr = try!(parse_hex(args)); 361 | 362 | cpu.force_pc(addr); 363 | } 364 | 365 | // Tell the debugger we want to resume execution. 366 | debugger.resume(); 367 | 368 | Ok(()) 369 | } 370 | 371 | // Step works exactly like continue except that we're only 372 | // supposed to execute a single instruction. 373 | fn step(&mut self, 374 | debugger: &mut Debugger, 375 | cpu: &mut Cpu, 376 | args: &[u8]) -> GdbResult { 377 | 378 | debugger.set_step(); 379 | 380 | self.resume(debugger, cpu, args) 381 | } 382 | 383 | // Add a breakpoint or watchpoint 384 | fn add_breakpoint(&mut self, 385 | debugger: &mut Debugger, 386 | args: &[u8]) -> GdbResult { 387 | 388 | // Check if the request contains a command list 389 | if args.iter().any(|&b| b == b';') { 390 | // Not sure if I should signal an error or send an empty 391 | // reply here to signal that command lists are not 392 | // supported. I think GDB will think that we don't support 393 | // this breakpoint type *at all* if we return an empty 394 | // reply. I don't know how it handles errors however. 395 | return self.send_error(); 396 | }; 397 | 398 | let (btype, addr, kind) = try!(parse_breakpoint(args)); 399 | 400 | // Only kind "4" makes sense for us: 32bits standard MIPS mode 401 | // breakpoint. The MIPS-specific kinds are defined here: 402 | // https://sourceware.org/gdb/onlinedocs/gdb/MIPS-Breakpoint-Kinds.html 403 | if kind != b'4' { 404 | // Same question as above, should I signal an error? 405 | return self.send_error(); 406 | } 407 | 408 | match btype { 409 | b'0' => debugger.add_breakpoint(addr), 410 | b'2' => debugger.add_write_watchpoint(addr), 411 | b'3' => debugger.add_read_watchpoint(addr), 412 | // Unsupported breakpoint type 413 | _ => return self.send_empty_reply(), 414 | } 415 | 416 | self.send_ok() 417 | } 418 | 419 | // Delete a breakpoint or watchpoint 420 | fn del_breakpoint(&mut self, 421 | debugger: &mut Debugger, 422 | args: &[u8]) -> GdbResult { 423 | 424 | let (btype, addr, kind) = try!(parse_breakpoint(args)); 425 | 426 | // Only 32bits standard MIPS mode breakpoint supported 427 | if kind != b'4' { 428 | return self.send_error(); 429 | } 430 | 431 | match btype { 432 | b'0' => debugger.del_breakpoint(addr), 433 | b'2' => debugger.del_write_watchpoint(addr), 434 | b'3' => debugger.del_read_watchpoint(addr), 435 | // Unsupported breakpoint type 436 | _ => return self.send_empty_reply(), 437 | } 438 | 439 | self.send_ok() 440 | } 441 | 442 | } 443 | 444 | enum PacketResult { 445 | Ok(Vec), 446 | BadChecksum(Vec), 447 | EndOfStream, 448 | } 449 | 450 | /// Get the value of an integer encoded in single lowercase 451 | /// hexadecimal ASCII digit. Return None if the character is not valid 452 | /// hexadecimal 453 | fn ascii_hex(b: u8) -> Option { 454 | if b >= b'0' && b <= b'9' { 455 | Some(b - b'0') 456 | } else if b >= b'a' && b <= b'f' { 457 | Some(10 + (b - b'a')) 458 | } else { 459 | // Invalid 460 | None 461 | } 462 | } 463 | 464 | /// Parse an hexadecimal string and return the value as an 465 | /// integer. Return `None` if the string is invalid. 466 | fn parse_hex(hex: &[u8]) -> Result { 467 | let mut v = 0u32; 468 | 469 | for &b in hex { 470 | v <<= 4; 471 | 472 | v |= 473 | match ascii_hex(b) { 474 | Some(h) => h as u32, 475 | // Bad hex 476 | None => return Err(()), 477 | }; 478 | } 479 | 480 | Ok(v) 481 | } 482 | 483 | /// Parse a string in the format `addr,len` (both as hexadecimal 484 | /// strings) and return the values as a tuple. Returns `None` if 485 | /// the format is bogus. 486 | fn parse_addr_len(args: &[u8]) -> Result<(u32, u32), ()> { 487 | 488 | // split around the comma 489 | let args: Vec<_> = args.split(|&b| b == b',').collect(); 490 | 491 | if args.len() != 2 { 492 | // Invalid number of arguments 493 | return Err(()); 494 | } 495 | 496 | let addr = args[0]; 497 | let len = args[1]; 498 | 499 | if addr.len() == 0 || len.len() == 0 { 500 | // Missing parameter 501 | return Err(()); 502 | } 503 | 504 | // Parse address 505 | let addr = try!(parse_hex(addr)); 506 | let len = try!(parse_hex(len)); 507 | 508 | Ok((addr, len)) 509 | } 510 | 511 | /// Parse breakpoint arguments: the format is 512 | /// `type,addr,kind`. Returns the three parameters in a tuple or an 513 | /// error if a format error has been encountered. 514 | fn parse_breakpoint(args: &[u8]) -> Result<(u8, u32, u8), ()> { 515 | // split around the comma 516 | let args: Vec<_> = args.split(|&b| b == b',').collect(); 517 | 518 | if args.len() != 3 { 519 | // Invalid number of arguments 520 | return Err(()); 521 | } 522 | 523 | let btype = args[0]; 524 | let addr = args[1]; 525 | let kind = args[2]; 526 | 527 | if btype.len() != 1 || kind.len() != 1 { 528 | // Type and kind should only be one character each 529 | return Err(()); 530 | } 531 | 532 | let btype = btype[0]; 533 | let kind = kind[0]; 534 | 535 | let addr = try!(parse_hex(addr)); 536 | 537 | Ok((btype, addr, kind)) 538 | } 539 | -------------------------------------------------------------------------------- /src/debugger/gdb/reply.rs: -------------------------------------------------------------------------------- 1 | /// GDB remote reply 2 | pub struct Reply { 3 | /// Packet data 4 | data: Vec, 5 | /// Checksum 6 | csum: u8, 7 | } 8 | 9 | impl Reply { 10 | pub fn new() -> Reply { 11 | // 32bytes is probably sufficient for the majority of replies 12 | let mut data = Vec::with_capacity(32); 13 | 14 | // Each reply begins with a dollar sign 15 | data.push(b'$'); 16 | 17 | Reply { 18 | data: data, 19 | csum: 0, 20 | } 21 | } 22 | 23 | pub fn push(&mut self, data: &[u8]) { 24 | // Update checksum 25 | for &b in data { 26 | self.csum = self.csum.wrapping_add(b); 27 | 28 | if b == b'$' || b == b'$' { 29 | panic!("Invalid char in GDB response"); 30 | } 31 | } 32 | 33 | self.data.extend(data.iter().cloned()); 34 | } 35 | 36 | pub fn push_u8(&mut self, byte: u8) { 37 | let to_hex = b"0123456789abcdef"; 38 | 39 | self.push(&[ 40 | to_hex[(byte >> 4) as usize], 41 | to_hex[(byte & 0xf) as usize], 42 | ]) 43 | } 44 | 45 | /// Push an u16 as 2 little endian bytes 46 | pub fn push_u16(&mut self, v: u16) { 47 | for i in 0..2 { 48 | self.push_u8((v >> (i * 8)) as u8); 49 | } 50 | } 51 | 52 | /// Push an u32 as 4 little endian bytes 53 | pub fn push_u32(&mut self, v: u32) { 54 | for i in 0..4 { 55 | self.push_u8((v >> (i * 8)) as u8); 56 | } 57 | } 58 | 59 | /// Finalize the reply: append the checksum and return the 60 | /// complete packet 61 | pub fn into_packet(mut self) -> Vec { 62 | // End of packet 63 | self.data.push(b'#'); 64 | // Append checksum. 65 | let csum = self.csum; 66 | self.push_u8(csum); 67 | 68 | self.data 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/debugger/mod.rs: -------------------------------------------------------------------------------- 1 | use std::net::TcpListener; 2 | 3 | use rustation::debugger::Debugger as DebuggerInterface; 4 | use rustation::memory::map::mask_region; 5 | use rustation::cpu::Cpu; 6 | 7 | use self::gdb::GdbRemote; 8 | 9 | mod gdb; 10 | mod bios; 11 | 12 | /// Rustation-libretro debugger, based on the GDB remote serial 13 | /// interface 14 | pub struct Debugger { 15 | /// Listener waiting for remote connections 16 | listener: TcpListener, 17 | /// Holds the current client connection 18 | client: Option, 19 | /// Internal state: set to true when the remote requests that the 20 | /// execution should resume 21 | resume: bool, 22 | /// If a single step is requested this flag is set 23 | step: bool, 24 | /// Vector containing all active breakpoint addresses 25 | breakpoints: Vec, 26 | /// Vector containing all active read watchpoints 27 | read_watchpoints: Vec, 28 | /// Vector containing all active write watchpoints 29 | write_watchpoints: Vec, 30 | /// If true we additionally log BIOS calls 31 | log_bios_calls: bool, 32 | } 33 | 34 | impl Debugger { 35 | pub fn new() -> Debugger { 36 | let bind_to = "127.0.0.1:9001"; 37 | 38 | // XXX The bind address/port should be configurable 39 | let listener = 40 | match TcpListener::bind(bind_to) { 41 | Ok(l) => l, 42 | Err(e) => panic!("Couldn't bind GDB server TCP socket: {}", e), 43 | }; 44 | 45 | info!("Waiting for debugger on {}", bind_to); 46 | 47 | Debugger { 48 | listener: listener, 49 | client: None, 50 | resume: true, 51 | step: false, 52 | breakpoints: Vec::new(), 53 | read_watchpoints: Vec::new(), 54 | write_watchpoints: Vec::new(), 55 | log_bios_calls: false, 56 | } 57 | } 58 | 59 | pub fn set_log_bios_calls(&mut self, enable: bool) { 60 | self.log_bios_calls = enable; 61 | } 62 | 63 | fn debug(&mut self, cpu: &mut Cpu) { 64 | // If stepping was requested we can reset the flag here, this 65 | // way we won't "double step" if we're entering debug mode for 66 | // an other reason (data watchpoint for instance) 67 | self.step = false; 68 | 69 | let mut client = 70 | match self.client.take() { 71 | Some(mut c) => { 72 | // Notify the remote that we're halted and waiting 73 | // for instructions. I ignore errors here for 74 | // simplicity, if the connection hung up for some 75 | // reason we'll figure it out soon enough. 76 | let _ = c.send_status(); 77 | c 78 | } 79 | None => GdbRemote::new(&self.listener), 80 | }; 81 | 82 | // We loop as long as the remote debugger doesn't tell us to 83 | // continue 84 | self.resume = false; 85 | 86 | while !self.resume { 87 | // Inner debugger loop: handle client requests until it 88 | // requests that the execution resumes or an error is 89 | // encountered 90 | if let Err(_) = client.serve(self, cpu) { 91 | // We encountered an error with the remote client: we 92 | // wait for a new connection 93 | client = GdbRemote::new(&self.listener); 94 | } 95 | } 96 | 97 | // Before we resume execution we store the current client 98 | self.client = Some(client); 99 | } 100 | 101 | fn resume(&mut self) { 102 | self.resume = true; 103 | } 104 | 105 | fn set_step(&mut self) { 106 | self.step = true; 107 | } 108 | 109 | /// Add a breakpoint that will trigger when the instruction at 110 | /// `addr` is about to be executed. 111 | fn add_breakpoint(&mut self, addr: u32) { 112 | let addr = mask_region(addr); 113 | 114 | // Make sure we're not adding the same address twice 115 | if !self.breakpoints.contains(&addr) { 116 | self.breakpoints.push(addr); 117 | } 118 | } 119 | 120 | /// Delete breakpoint at `addr`. Does nothing if there was no 121 | /// breakpoint set for this address. 122 | fn del_breakpoint(&mut self, addr: u32) { 123 | let addr = mask_region(addr); 124 | 125 | self.breakpoints.retain(|&a| a != addr); 126 | } 127 | 128 | /// Add a breakpoint that will trigger when the CPU attempts to 129 | /// read from `addr` 130 | fn add_read_watchpoint(&mut self, addr: u32) { 131 | let addr = mask_region(addr); 132 | 133 | // Make sure we're not adding the same address twice 134 | if !self.read_watchpoints.contains(&addr) { 135 | self.read_watchpoints.push(addr); 136 | } 137 | } 138 | 139 | /// Delete read watchpoint at `addr`. Does nothing if there was no 140 | /// breakpoint set for this address. 141 | fn del_read_watchpoint(&mut self, addr: u32) { 142 | let addr = mask_region(addr); 143 | 144 | self.read_watchpoints.retain(|&a| a != addr); 145 | } 146 | 147 | /// Add a breakpoint that will trigger when the CPU attempts to 148 | /// write to `addr` 149 | fn add_write_watchpoint(&mut self, addr: u32) { 150 | let addr = mask_region(addr); 151 | 152 | // Make sure we're not adding the same address twice 153 | if !self.write_watchpoints.contains(&addr) { 154 | self.write_watchpoints.push(addr); 155 | } 156 | } 157 | 158 | /// Delete write watchpoint at `addr`. Does nothing if there was no 159 | /// breakpoint set for this address. 160 | fn del_write_watchpoint(&mut self, addr: u32) { 161 | let addr = mask_region(addr); 162 | 163 | self.write_watchpoints.retain(|&a| a != addr); 164 | } 165 | } 166 | 167 | impl DebuggerInterface for Debugger { 168 | /// Signal a "break" which will put the emulator in debug mode at 169 | /// the next instruction 170 | fn trigger_break(&mut self) { 171 | self.set_step(); 172 | } 173 | 174 | /// Called by the CPU when it's about to execute a new 175 | /// instruction. This function is called before *all* CPU 176 | /// instructions so it needs to be as fast as possible. 177 | fn pc_change(&mut self, cpu: &mut Cpu) { 178 | let pc = mask_region(cpu.pc()); 179 | 180 | if self.log_bios_calls { 181 | bios::check_bios_call(cpu); 182 | } 183 | 184 | // Check if stepping was requested or if we encountered a 185 | // breakpoint 186 | if self.step || self.breakpoints.contains(&pc) { 187 | self.debug(cpu); 188 | } 189 | } 190 | 191 | /// Called by the CPU when it's about to load a value from memory. 192 | fn memory_read(&mut self, cpu: &mut Cpu, addr: u32) { 193 | let addr = mask_region(addr); 194 | 195 | // XXX: how should we handle unaligned watchpoints? For 196 | // instance if we have a watchpoint on address 1 and the CPU 197 | // executes a `load32 at` address 0, should we break? Also, 198 | // should we mask the region? 199 | if self.read_watchpoints.contains(&addr) { 200 | info!("Read watchpoint triggered at 0x{:08x}", addr); 201 | self.debug(cpu); 202 | } 203 | } 204 | 205 | /// Called by the CPU when it's about to write a value to memory. 206 | fn memory_write(&mut self, cpu: &mut Cpu, addr: u32) { 207 | let addr = mask_region(addr); 208 | 209 | // XXX: same remark as memory_read for unaligned stores 210 | if self.write_watchpoints.contains(&addr) { 211 | info!("Write watchpoint triggered at 0x{:08x}", addr); 212 | self.debug(cpu); 213 | } 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // XXX temporarily necessary to remove annoying warnings about the 2 | // cstring! macro in rustc 1.10.0 which don't have a simple 3 | // workaround. Will be removed once we find a better way to silence 4 | // them. 5 | #![allow(const_err)] 6 | 7 | #[macro_use] 8 | pub mod libretro; 9 | #[macro_use] 10 | mod retrogl; 11 | mod retrolog; 12 | mod renderer; 13 | mod savestate; 14 | mod debugger; 15 | mod vcd; 16 | 17 | use std::path::{Path, PathBuf}; 18 | use std::fs::File; 19 | use std::io::Read; 20 | use std::str::FromStr; 21 | 22 | use libc::{c_char, c_uint}; 23 | 24 | use rustc_serialize::{Encodable, Encoder, Decodable, Decoder}; 25 | 26 | use rustation::cdrom::disc::{Disc, Region}; 27 | use rustation::bios::{Bios, BIOS_SIZE}; 28 | use rustation::bios::db::Metadata; 29 | use rustation::gpu::{Gpu, VideoClock}; 30 | use rustation::memory::Interconnect; 31 | use rustation::cpu::Cpu; 32 | use rustation::padmemcard::gamepad::{Button, ButtonState, DigitalProfile}; 33 | use rustation::shared::SharedState; 34 | use rustation::parallel_io::exe_loader; 35 | use rustation::tracer; 36 | 37 | use cdimage::cue::Cue; 38 | 39 | use debugger::Debugger; 40 | 41 | #[macro_use] 42 | extern crate log; 43 | extern crate libc; 44 | extern crate gl; 45 | extern crate rustation; 46 | extern crate arrayvec; 47 | extern crate cdimage; 48 | extern crate rustc_serialize; 49 | extern crate time; 50 | 51 | /// Static system information sent to the frontend on request 52 | const SYSTEM_INFO: libretro::SystemInfo = libretro::SystemInfo { 53 | library_name: cstring!("Rustation"), 54 | library_version: rustation::VERSION_CSTR as *const _ as *const c_char, 55 | valid_extensions: cstring!("cue|exe|psexe|psx"), 56 | need_fullpath: false, 57 | block_extract: false, 58 | }; 59 | 60 | /// Emulator context 61 | struct Context { 62 | retrogl: retrogl::RetroGl, 63 | cpu: Cpu, 64 | shared_state: SharedState, 65 | debugger: Debugger, 66 | disc_path: PathBuf, 67 | video_clock: VideoClock, 68 | /// When true the internal FPS monitoring in enabled 69 | monitor_internal_fps: bool, 70 | /// Cached value for the maximum savestate size in bytes 71 | savestate_max_len: usize, 72 | /// If true we log the counters at the end of each frame 73 | log_frame_counters: bool, 74 | /// If true we trigger the debugger when Pause/Break is pressed 75 | debug_on_key: bool, 76 | } 77 | 78 | impl Context { 79 | fn new(disc: &Path) -> Result { 80 | info!("Using Rustation {}", rustation::VERSION); 81 | 82 | let (mut cpu, video_clock) = 83 | match exe_loader::ExeLoader::load_file(disc) { 84 | Ok(l) => try!(Context::load_exe(l)), 85 | // Not an EXE, load as a disc 86 | Err(exe_loader::Error::UnknownFormat) => 87 | try!(Context::load_disc(disc)), 88 | Err(e) => { 89 | error!("Couldn't load EXE file: {:?}", e); 90 | return Err(()) 91 | } 92 | }; 93 | 94 | let shared_state = SharedState::new(); 95 | let retrogl = try!(retrogl::RetroGl::new(video_clock)); 96 | 97 | if CoreVariables::enable_debug_uart() { 98 | let result = 99 | cpu.interconnect_mut().bios_mut().enable_debug_uart(); 100 | 101 | match result { 102 | Ok(_) => info!("BIOS patched to enable debug UART"), 103 | Err(_) => warn!("Couldn't patch BIOS to enable debug UART"), 104 | } 105 | } 106 | 107 | let mut context = 108 | Context { 109 | retrogl: retrogl, 110 | cpu: cpu, 111 | shared_state: shared_state, 112 | debugger: Debugger::new(), 113 | disc_path: disc.to_path_buf(), 114 | video_clock: video_clock, 115 | monitor_internal_fps: false, 116 | savestate_max_len: 0, 117 | log_frame_counters: false, 118 | debug_on_key: false, 119 | }; 120 | 121 | libretro::Context::refresh_variables(&mut context); 122 | 123 | let max_len = try!(context.compute_savestate_max_length()); 124 | 125 | context.savestate_max_len = max_len; 126 | 127 | context.setup_controllers(); 128 | 129 | if CoreVariables::debug_on_reset() { 130 | context.trigger_break(); 131 | } 132 | 133 | Ok(context) 134 | } 135 | 136 | /// Initialize the controllers connected to the emulated console 137 | fn setup_controllers(&mut self) { 138 | // XXX for now I only hardcode a digital pad in slot 1 139 | // (leaving slot 0 disconnected). 140 | self.cpu.interconnect_mut() 141 | .pad_memcard_mut() 142 | .gamepads_mut()[0] 143 | .set_profile(Box::new(DigitalProfile::new())); 144 | } 145 | 146 | fn compute_savestate_max_length(&mut self) -> Result { 147 | // In order to get the full size we're just going to use a 148 | // dummy Write struct which will just count how many bytes are 149 | // being written 150 | struct WriteCounter(usize); 151 | 152 | impl ::std::io::Write for WriteCounter { 153 | fn write(&mut self, buf: &[u8]) -> ::std::io::Result { 154 | let len = buf.len(); 155 | 156 | self.0 += len; 157 | 158 | Ok(len) 159 | } 160 | 161 | fn flush(&mut self) -> ::std::io::Result<()> { 162 | Ok(()) 163 | } 164 | } 165 | 166 | let mut counter = WriteCounter(0); 167 | 168 | try!(self.save_state(&mut counter)); 169 | 170 | let len = counter.0; 171 | 172 | // Our savestate format has variable length, in particular we 173 | // have the GPU's load_buffer which can grow to 1MB in the 174 | // worst case scenario (the entire VRAM). I'm going to be 175 | // optimistic here and give us 512KB of "headroom", that 176 | // should be enough 99% of the time, hopefully. 177 | let len = len + 512 * 1024; 178 | 179 | Ok(len) 180 | } 181 | 182 | fn save_state(&self, writer: &mut ::std::io::Write) -> Result<(), ()> { 183 | 184 | let mut encoder = 185 | match savestate::Encoder::new(writer) { 186 | Ok(encoder) => encoder, 187 | Err(e) => { 188 | warn!("Couldn't create savestate encoder: {:?}", e); 189 | return Err(()) 190 | } 191 | }; 192 | 193 | match self.encode(&mut encoder) { 194 | Ok(_) => Ok(()), 195 | Err(e) => { 196 | warn!("Couldn't serialize emulator state: {:?}", e); 197 | Err(()) 198 | } 199 | } 200 | } 201 | 202 | fn load_state(&mut self, reader: &mut ::std::io::Read) -> Result<(), ()> { 203 | let mut decoder = 204 | match savestate::Decoder::new(reader) { 205 | Ok(decoder) => decoder, 206 | Err(e) => { 207 | warn!("Couldn't create savestate decoder: {:?}", e); 208 | return Err(()) 209 | } 210 | }; 211 | 212 | // I don't implement Decodable for Context itself because I 213 | // don't want to create a brand new instance. Things like the 214 | // debugger or disc path don't need to be reset 215 | let decoded = 216 | decoder.read_struct("Context", 4, |d| { 217 | let cpu = try!(d.read_struct_field("cpu", 0, 218 | Decodable::decode)); 219 | 220 | let retrogl = try!(d.read_struct_field("retrogl", 1, 221 | Decodable::decode)); 222 | 223 | let video_clock = try!(d.read_struct_field("video_clock", 2, 224 | Decodable::decode)); 225 | 226 | let shared_state = try!(d.read_struct_field("shared_state", 3, 227 | Decodable::decode)); 228 | 229 | Ok((cpu, retrogl, video_clock, shared_state)) 230 | }); 231 | 232 | let (cpu, retrogl, video_clock, shared_state) = 233 | match decoded { 234 | Ok(d) => d, 235 | Err(e) => { 236 | warn!("Couldn't decode savestate: {:?}", e); 237 | return Err(()) 238 | } 239 | }; 240 | 241 | // The savestate doesn't contain the BIOS, only the metadata 242 | // describing which BIOS was used when the savestate was made 243 | // (in order to save space and not redistribute the BIOS with 244 | // savestate files). So let's find it back and reload it. 245 | let bios_md = self.cpu.interconnect().bios().metadata(); 246 | 247 | // Convert sha256 to a hex string for pretty printing 248 | let sha256_hex: String = 249 | bios_md.sha256.iter() 250 | .fold(String::new(), |s, b| s + &format!("{:02x}", b)); 251 | 252 | info!("Loading savestate BIOS: {:?} (SHA256: {})", 253 | bios_md, sha256_hex); 254 | 255 | let bios = 256 | match Context::find_bios(|md| { md.sha256 == bios_md.sha256 }) { 257 | Some(b) => b, 258 | None => { 259 | error!("Couldn't find the savestate BIOS, bailing out"); 260 | return Err(()); 261 | } 262 | }; 263 | 264 | let gl_is_valid = self.retrogl.is_valid(); 265 | 266 | // Save the disc before we replace everything 267 | let disc = self.cpu.interconnect_mut().cdrom_mut().remove_disc(); 268 | 269 | self.cpu = cpu; 270 | self.retrogl = retrogl; 271 | self.video_clock = video_clock; 272 | self.shared_state = shared_state; 273 | 274 | self.cpu.interconnect_mut().set_bios(bios); 275 | self.cpu.interconnect_mut().cdrom_mut().set_disc(disc); 276 | 277 | self.setup_controllers(); 278 | 279 | // If we had a valid GL context before the load we can 280 | // directly reload everything. Otherwise it'll be done when 281 | // the frontend calls context_reset 282 | if gl_is_valid { 283 | self.retrogl.context_reset(); 284 | } 285 | 286 | info!("Savestate load successful"); 287 | 288 | Ok(()) 289 | } 290 | 291 | fn load_exe(loader: exe_loader::ExeLoader) 292 | -> Result<(Cpu, VideoClock), ()> { 293 | let region = 294 | match loader.region() { 295 | Some(r) => { 296 | info!("Detected EXE region: {:?}", r); 297 | r 298 | } 299 | None => { 300 | warn!("Couldn't establish EXE file region, \ 301 | defaulting to NorthAmerica"); 302 | Region::NorthAmerica 303 | } 304 | }; 305 | 306 | // In order for the EXE loader to word correctly without any 307 | // disc we need to patch the BIOS, so let's make sure that the 308 | // animation_jump_hook is available 309 | let bios_predicate = |md: &Metadata| { 310 | md.region == region && md.animation_jump_hook.is_some() 311 | }; 312 | 313 | let mut bios = 314 | match Context::find_bios(bios_predicate) { 315 | Some(b) => b, 316 | None => { 317 | error!("Couldn't find a BIOS, bailing out"); 318 | return Err(()); 319 | } 320 | }; 321 | 322 | if let Err(_) = loader.patch_bios(&mut bios) { 323 | error!("EXE loader couldn't patch the BIOS, giving up"); 324 | return Err(()); 325 | } 326 | 327 | let video_clock = 328 | match region { 329 | Region::Europe => VideoClock::Pal, 330 | Region::NorthAmerica => VideoClock::Ntsc, 331 | Region::Japan => VideoClock::Ntsc, 332 | }; 333 | 334 | let gpu = Gpu::new(video_clock); 335 | let mut inter = Interconnect::new(bios, gpu, None); 336 | 337 | // Plug the EXE loader in the Parallel I/O port 338 | inter.parallel_io_mut().set_module(Box::new(loader)); 339 | 340 | Ok((Cpu::new(inter), video_clock)) 341 | } 342 | 343 | fn load_disc(disc: &Path) -> Result<(Cpu, VideoClock), ()> { 344 | 345 | let image = 346 | match Cue::new(disc) { 347 | Ok(c) => c, 348 | Err(e) => { 349 | error!("Couldn't load {}: {}", disc.to_string_lossy(), e); 350 | return Err(()); 351 | } 352 | }; 353 | 354 | let disc = 355 | match Disc::new(Box::new(image)) { 356 | Ok(d) => d, 357 | Err(e) => { 358 | error!("Couldn't load {}: {}", disc.to_string_lossy(), e); 359 | return Err(()); 360 | } 361 | }; 362 | 363 | let serial = disc.serial_number(); 364 | let region = disc.region(); 365 | 366 | info!("Disc serial number: {}", serial); 367 | info!("Detected disc region: {:?}", region); 368 | 369 | let mut bios = 370 | match Context::find_bios(|md| { md.region == region }) { 371 | Some(b) => b, 372 | None => { 373 | error!("Couldn't find a BIOS, bailing out"); 374 | return Err(()); 375 | } 376 | }; 377 | 378 | let bios_menu = CoreVariables::bios_menu(); 379 | 380 | // Skipping BIOS animations seems to break the BIOS menu, so 381 | // we ignore this setting when the menu is requested. 382 | if CoreVariables::skip_bios_animation() && !bios_menu { 383 | match bios.patch_boot_animation() { 384 | Ok(_) => info!("Patched BIOS to skip boot animation"), 385 | Err(_) => warn!("Failed to patch BIOS to skip boot animations"), 386 | } 387 | } 388 | 389 | let video_clock = 390 | match region { 391 | Region::Europe => VideoClock::Pal, 392 | Region::NorthAmerica => VideoClock::Ntsc, 393 | Region::Japan => VideoClock::Ntsc, 394 | }; 395 | 396 | // If we're asked to boot straight to the BIOS menu we pretend 397 | // no disc is present. 398 | let disc = 399 | if bios_menu { 400 | None 401 | } else { 402 | Some(disc) 403 | }; 404 | 405 | let gpu = Gpu::new(video_clock); 406 | let inter = Interconnect::new(bios, gpu, disc); 407 | 408 | Ok((Cpu::new(inter), video_clock)) 409 | } 410 | 411 | /// Attempt to find a BIOS for `region` in the system directory 412 | fn find_bios(predicate: F) -> Option 413 | where F: Fn(&Metadata) -> bool { 414 | let system_directory = 415 | match libretro::get_system_directory() { 416 | Some(dir) => dir, 417 | // libretro.h says that when the system directory is not 418 | // provided "it's up to the implementation to find a 419 | // suitable directory" but I'm not sure what to put 420 | // here. Maybe "."? I'd rather give an explicit error 421 | // message instead. 422 | None => { 423 | error!("The frontend didn't give us a system directory, \ 424 | no BIOS can be loaded"); 425 | return None; 426 | } 427 | }; 428 | 429 | info!("Looking for a suitable BIOS in {:?}", system_directory); 430 | 431 | let dir = 432 | match ::std::fs::read_dir(&system_directory) { 433 | Ok(d) => d, 434 | Err(e) => { 435 | error!("Can't read directory {:?}: {}", 436 | system_directory, e); 437 | return None; 438 | } 439 | }; 440 | 441 | for entry in dir { 442 | match entry { 443 | Ok(entry) => { 444 | let path = entry.path(); 445 | 446 | match entry.metadata() { 447 | Ok(md) => { 448 | if !md.is_file() { 449 | debug!("Ignoring {:?}: not a file", path); 450 | } else if md.len() != BIOS_SIZE as u64 { 451 | debug!("Ignoring {:?}: bad size", path); 452 | } else { 453 | let bios = Context::try_bios(&predicate, &path); 454 | 455 | if bios.is_some() { 456 | // Found a valid BIOS! 457 | return bios; 458 | } 459 | } 460 | } 461 | Err(e) => 462 | warn!("Ignoring {:?}: can't get file metadata: {}", 463 | path, e) 464 | } 465 | } 466 | Err(e) => warn!("Error while reading directory: {}", e), 467 | } 468 | } 469 | 470 | None 471 | } 472 | 473 | /// Attempt to read and load the BIOS at `path` 474 | fn try_bios(predicate: F, path: &Path) -> Option 475 | where F: Fn(&Metadata) -> bool { 476 | 477 | let mut file = 478 | match File::open(&path) { 479 | Ok(f) => f, 480 | Err(e) => { 481 | warn!("Can't open {:?}: {}", path, e); 482 | return None; 483 | } 484 | }; 485 | 486 | // Load the BIOS 487 | let mut data = Box::new([0; BIOS_SIZE]); 488 | let mut nread = 0; 489 | 490 | while nread < BIOS_SIZE { 491 | nread += 492 | match file.read(&mut data[nread..]) { 493 | Ok(0) => { 494 | warn!("Short read while loading {:?}", path); 495 | return None; 496 | } 497 | Ok(n) => n, 498 | Err(e) => { 499 | warn!("Error while reading {:?}: {}", path, e); 500 | return None; 501 | } 502 | }; 503 | } 504 | 505 | match Bios::new(data) { 506 | Some(bios) => { 507 | let md = bios.metadata(); 508 | 509 | info!("Found BIOS DB entry for {:?}: {:?}", path, md); 510 | 511 | if md.known_bad { 512 | warn!("Ignoring {:?}: known bad dump", path); 513 | None 514 | } else if !predicate(md) { 515 | info!("Ignoring {:?}: rejected by predicate", path); 516 | None 517 | } else { 518 | info!("Using BIOS {:?} ({:?})", path, md); 519 | Some(bios) 520 | } 521 | } 522 | None => { 523 | debug!("Ignoring {:?}: not a known PlayStation BIOS", path); 524 | None 525 | } 526 | } 527 | } 528 | 529 | fn poll_controllers(&mut self) { 530 | // XXX we only support pad 0 for now 531 | let pad = self.cpu.interconnect_mut() 532 | .pad_memcard_mut() 533 | .gamepads_mut()[0] 534 | .profile_mut(); 535 | 536 | for &(retrobutton, psxbutton) in &BUTTON_MAP { 537 | let state = 538 | if libretro::button_pressed(0, retrobutton) { 539 | ButtonState::Pressed 540 | } else { 541 | ButtonState::Released 542 | }; 543 | 544 | pad.set_button_state(psxbutton, state); 545 | } 546 | } 547 | 548 | /// Trigger a breakpoint in the debugger 549 | fn trigger_break(&mut self) { 550 | rustation::debugger::Debugger::trigger_break(&mut self.debugger); 551 | } 552 | } 553 | 554 | impl Drop for Context { 555 | fn drop(&mut self) { 556 | if cfg!(feature = "trace") { 557 | // Dump the trace before destroying everything 558 | let path = VCD_TRACE_PATH; 559 | 560 | let trace = tracer::remove_trace(); 561 | 562 | if trace.is_empty() { 563 | warn!("Empty trace, ignoring"); 564 | } else { 565 | info!("Dumping VCD trace file to {}", path); 566 | 567 | let mut vcd_file = File::create(path).unwrap(); 568 | 569 | let content = &*self.disc_path.to_string_lossy(); 570 | 571 | let bios_md = self.cpu.interconnect().bios().metadata(); 572 | let bios_desc = format!("{:?}", bios_md); 573 | 574 | vcd::dump_trace(&mut vcd_file, content, &bios_desc, trace); 575 | } 576 | } 577 | } 578 | } 579 | 580 | impl libretro::Context for Context { 581 | 582 | fn render_frame(&mut self) { 583 | self.poll_controllers(); 584 | 585 | let debug_request = 586 | self.debug_on_key && 587 | libretro::key_pressed(0, libretro::Key::Pause); 588 | 589 | if debug_request { 590 | self.trigger_break(); 591 | } 592 | 593 | let cpu = &mut self.cpu; 594 | let shared_state = &mut self.shared_state; 595 | let debugger = &mut self.debugger; 596 | 597 | self.retrogl.render_frame(|renderer| { 598 | cpu.run_until_next_frame(debugger, shared_state, renderer); 599 | }); 600 | 601 | let counters = shared_state.counters_mut(); 602 | 603 | if self.log_frame_counters { 604 | debug!("Frame counters:"); 605 | debug!(" CPU interrupt count: {}", counters.cpu_interrupt.get()); 606 | } 607 | 608 | if self.monitor_internal_fps { 609 | let frame_count = counters.frame.get(); 610 | 611 | if frame_count >= INTERNAL_FPS_SAMPLE_PERIOD { 612 | // We compute the internal FPS relative to the 613 | // full-speed video output FPS. 614 | let video_fps = video_output_framerate(self.video_clock); 615 | 616 | let internal_frame_count = counters.framebuffer_swap.get(); 617 | 618 | let internal_fps = 619 | (internal_frame_count as f32 * video_fps) 620 | / INTERNAL_FPS_SAMPLE_PERIOD as f32; 621 | 622 | libretro_message!(100, "Internal FPS: {:.2}", internal_fps); 623 | 624 | counters.frame.reset(); 625 | counters.framebuffer_swap.reset(); 626 | } 627 | } else { 628 | // Keep those counters to 0 so that we don't get wild 629 | // values if logging is enabled. 630 | counters.frame.reset(); 631 | counters.framebuffer_swap.reset(); 632 | } 633 | } 634 | 635 | fn get_system_av_info(&self) -> libretro::SystemAvInfo { 636 | let upscaling = CoreVariables::internal_upscale_factor(); 637 | 638 | get_av_info(self.video_clock, upscaling) 639 | } 640 | 641 | fn refresh_variables(&mut self) { 642 | self.monitor_internal_fps = CoreVariables::display_internal_fps(); 643 | self.log_frame_counters = CoreVariables::log_frame_counters(); 644 | self.debug_on_key = CoreVariables::debug_on_key(); 645 | self.cpu.set_debug_on_break(CoreVariables::debug_on_break()); 646 | self.debugger.set_log_bios_calls(CoreVariables::log_bios_calls()); 647 | 648 | self.retrogl.refresh_variables(); 649 | } 650 | 651 | fn reset(&mut self) { 652 | match Context::load_disc(&self.disc_path) { 653 | Ok((cpu, video_clock)) => { 654 | info!("Game reset"); 655 | self.cpu = cpu; 656 | self.video_clock = video_clock; 657 | self.shared_state = SharedState::new(); 658 | 659 | if CoreVariables::debug_on_reset() { 660 | self.trigger_break(); 661 | } 662 | }, 663 | Err(_) => warn!("Couldn't reset game"), 664 | } 665 | } 666 | 667 | fn gl_context_reset(&mut self) { 668 | self.retrogl.context_reset(); 669 | } 670 | 671 | fn gl_context_destroy(&mut self) { 672 | self.retrogl.context_destroy(); 673 | } 674 | 675 | fn serialize_size(&self) -> usize { 676 | self.savestate_max_len 677 | } 678 | 679 | fn serialize(&self, mut buf: &mut [u8]) -> Result<(), ()> { 680 | self.save_state(&mut buf) 681 | } 682 | 683 | fn unserialize(&mut self, mut buf: &[u8]) -> Result<(), ()> { 684 | self.load_state(&mut buf) 685 | } 686 | } 687 | 688 | impl Encodable for Context { 689 | fn encode(&self, s: &mut S) -> Result<(), S::Error> { 690 | s.emit_struct("Context", 4, |s| { 691 | try!(s.emit_struct_field("cpu", 0, 692 | |s| self.cpu.encode(s))); 693 | try!(s.emit_struct_field("retrogl", 1, 694 | |s| self.retrogl.encode(s))); 695 | try!(s.emit_struct_field("video_clock", 2, 696 | |s| self.video_clock.encode(s))); 697 | try!(s.emit_struct_field("shared_state", 3, 698 | |s| self.shared_state.encode(s))); 699 | 700 | Ok(()) 701 | }) 702 | } 703 | } 704 | 705 | /// Init function, guaranteed called only once (unlike `retro_init`) 706 | fn init() { 707 | retrolog::init(); 708 | } 709 | 710 | /// Called when a game is loaded and a new context must be built 711 | fn load_game(disc: PathBuf) -> Option> { 712 | info!("Loading {:?}", disc); 713 | 714 | Context::new(&disc).ok() 715 | .map(|c| Box::new(c) as Box) 716 | } 717 | 718 | libretro_variables!( 719 | struct CoreVariables (prefix = "rustation") { 720 | internal_upscale_factor: u32, parse_upscale 721 | => "Internal upscaling factor; \ 722 | 1x (native)|2x|3x|4x|5x|6x|7x|8x|9x|10x", 723 | internal_color_depth: u8, parse_color_depth 724 | => "Internal color depth; dithered 16bpp (native)|32bpp", 725 | scale_dither: bool, parse_bool 726 | => "Scale dithering pattern with internal resolution; \ 727 | enabled|disabled", 728 | wireframe: bool, parse_bool 729 | => "Wireframe mode; disabled|enabled", 730 | bios_menu: bool, parse_bool 731 | => "Boot to BIOS menu; disabled|enabled", 732 | skip_bios_animation: bool, parse_bool 733 | => "Skip BIOS boot animations; disabled|enabled", 734 | display_internal_fps: bool, parse_bool 735 | => "Display internal FPS; disabled|enabled", 736 | log_frame_counters: bool, parse_bool 737 | => "Log frame counters; disabled|enabled", 738 | enable_debug_uart: bool, parse_bool 739 | => "Enable debug UART in the BIOS; disabled|enabled", 740 | debug_on_break: bool, parse_bool 741 | => "Trigger debugger on BREAK instructions; disabled|enabled", 742 | debug_on_key: bool, parse_bool 743 | => "Trigger debugger when Pause/Break is pressed; disabled|enabled", 744 | debug_on_reset: bool, parse_bool 745 | => "Trigger debugger when starting or resetting the emulator; \ 746 | disabled|enabled", 747 | log_bios_calls: bool, parse_bool 748 | => "Log BIOS calls; disabled|enabled", 749 | }); 750 | 751 | fn parse_upscale(opt: &str) -> Result::Err> { 752 | let num = opt.trim_matches(|c: char| !c.is_numeric()); 753 | 754 | num.parse() 755 | } 756 | 757 | fn parse_color_depth(opt: &str) -> Result::Err> { 758 | let num = opt.trim_matches(|c: char| !c.is_numeric()); 759 | 760 | num.parse() 761 | } 762 | 763 | fn parse_bool(opt: &str) -> Result { 764 | match opt { 765 | "true" | "enabled" | "on" => Ok(true), 766 | "false" | "disabled" | "off" => Ok(false), 767 | _ => Err(()), 768 | } 769 | } 770 | 771 | fn init_variables() { 772 | CoreVariables::register(); 773 | } 774 | 775 | // Precise FPS values for the video output for the given 776 | // VideoClock. It's actually possible to configure the PlayStation GPU 777 | // to output with NTSC timings with the PAL clock (and vice-versa) 778 | // which would make this code invalid but it wouldn't make a lot of 779 | // sense for a game to do that. 780 | fn video_output_framerate(std: VideoClock) -> f32 { 781 | match std { 782 | // 53.690MHz GPU clock frequency, 263 lines per field, 783 | // 3413 cycles per line 784 | VideoClock::Ntsc => 59.81, 785 | // 53.222MHz GPU clock frequency, 314 lines per field, 786 | // 3406 cycles per line 787 | VideoClock::Pal => 49.76, 788 | } 789 | } 790 | 791 | fn get_av_info(std: VideoClock, upscaling: u32) -> libretro::SystemAvInfo { 792 | 793 | // Maximum resolution supported by the PlayStation video 794 | // output is 640x480 795 | let max_width = (640 * upscaling) as c_uint; 796 | let max_height = (480 * upscaling) as c_uint; 797 | 798 | libretro::SystemAvInfo { 799 | geometry: libretro::GameGeometry { 800 | // The base resolution will be overriden using 801 | // ENVIRONMENT_SET_GEOMETRY before rendering a frame so 802 | // this base value is not really important 803 | base_width: max_width, 804 | base_height: max_height, 805 | max_width: max_width, 806 | max_height: max_height, 807 | aspect_ratio: 4./3., 808 | }, 809 | timing: libretro::SystemTiming { 810 | fps: video_output_framerate(std) as f64, 811 | sample_rate: 44_100. 812 | } 813 | } 814 | } 815 | 816 | /// Libretro to PlayStation button mapping. Libretro's mapping is 817 | /// based on the SNES controller so libretro's A button matches the 818 | /// PlayStation's Circle button. 819 | const BUTTON_MAP: [(libretro::JoyPadButton, Button); 14] = 820 | [(libretro::JoyPadButton::Up, Button::DUp), 821 | (libretro::JoyPadButton::Down, Button::DDown), 822 | (libretro::JoyPadButton::Left, Button::DLeft), 823 | (libretro::JoyPadButton::Right, Button::DRight), 824 | (libretro::JoyPadButton::Start, Button::Start), 825 | (libretro::JoyPadButton::Select, Button::Select), 826 | (libretro::JoyPadButton::A, Button::Circle), 827 | (libretro::JoyPadButton::B, Button::Cross), 828 | (libretro::JoyPadButton::Y, Button::Square), 829 | (libretro::JoyPadButton::X, Button::Triangle), 830 | (libretro::JoyPadButton::L, Button::L1), 831 | (libretro::JoyPadButton::R, Button::R1), 832 | (libretro::JoyPadButton::L2, Button::L2), 833 | (libretro::JoyPadButton::R2, Button::R2)]; 834 | 835 | /// Number of output frames over which the internal FPS is averaged 836 | const INTERNAL_FPS_SAMPLE_PERIOD: u32 = 32; 837 | 838 | /// Hardcoded path for the generated VCD file when tracing is 839 | /// enabled. XXX Should probably be changed for Windows, maybe made 840 | /// configurable somehow? 841 | const VCD_TRACE_PATH: &'static str = "/tmp/rustation-trace.vcd"; 842 | -------------------------------------------------------------------------------- /src/renderer/mod.rs: -------------------------------------------------------------------------------- 1 | use gl; 2 | use gl::types::{GLuint, GLint, GLsizei, GLenum, GLfloat}; 3 | use arrayvec::ArrayVec; 4 | use libc::c_uint; 5 | use rustation::gpu::renderer::{Renderer, Vertex, PrimitiveAttributes}; 6 | use rustation::gpu::renderer::{TextureDepth, BlendMode, SemiTransparencyMode}; 7 | use rustation::gpu::{VRAM_WIDTH_PIXELS, VRAM_HEIGHT}; 8 | 9 | use retrogl::DrawConfig; 10 | use retrogl::error::{Error, get_error}; 11 | use retrogl::buffer::DrawBuffer; 12 | use retrogl::shader::{Shader, ShaderType}; 13 | use retrogl::program::Program; 14 | use retrogl::types::GlType; 15 | use retrogl::texture::Texture; 16 | use retrogl::framebuffer::Framebuffer; 17 | 18 | use CoreVariables; 19 | 20 | use libretro; 21 | 22 | pub struct GlRenderer { 23 | /// Buffer used to handle PlayStation GPU draw commands 24 | command_buffer: DrawBuffer, 25 | /// Primitive type for the vertices in the command buffers 26 | /// (TRIANGLES or LINES) 27 | command_draw_mode: GLenum, 28 | /// Temporary buffer holding vertices for semi-transparent draw 29 | /// commands. 30 | semi_transparent_vertices: Vec, 31 | /// Transparency mode for semi-transparent commands 32 | semi_transparency_mode: SemiTransparencyMode, 33 | /// Polygon mode (for wireframe) 34 | command_polygon_mode: GLenum, 35 | /// Buffer used to draw to the frontend's framebuffer 36 | output_buffer: DrawBuffer, 37 | /// Buffer used to copy textures from `fb_texture` to `fb_out` 38 | image_load_buffer: DrawBuffer, 39 | /// Texture used to store the VRAM for texture mapping 40 | config: DrawConfig, 41 | /// Framebuffer used as a shader input for texturing draw commands 42 | fb_texture: Texture, 43 | /// Framebuffer used as an output when running draw commands 44 | fb_out: Texture, 45 | /// Depth buffer for fb_out 46 | fb_out_depth: Texture, 47 | /// Current resolution of the frontend's framebuffer 48 | frontend_resolution: (u32, u32), 49 | /// Current internal resolution upscaling factor 50 | internal_upscaling: u32, 51 | /// Current internal color depth 52 | internal_color_depth: u8, 53 | /// Counter for preserving primitive draw order in the z-buffer 54 | /// since we draw semi-transparent primitives out-of-order. 55 | primitive_ordering: i16, 56 | } 57 | 58 | impl GlRenderer { 59 | pub fn from_config(config: DrawConfig) -> Result { 60 | 61 | let upscaling = CoreVariables::internal_upscale_factor(); 62 | let depth = CoreVariables::internal_color_depth(); 63 | let scale_dither = CoreVariables::scale_dither(); 64 | let wireframe = CoreVariables::wireframe(); 65 | 66 | info!("Building OpenGL state ({}x internal res., {}bpp)", 67 | upscaling, depth); 68 | 69 | let opaque_command_buffer = 70 | try!(GlRenderer::build_buffer( 71 | include_str!("shaders/command_vertex.glsl"), 72 | include_str!("shaders/command_fragment.glsl"), 73 | 2048, 74 | true)); 75 | 76 | let output_buffer = 77 | try!(GlRenderer::build_buffer( 78 | include_str!("shaders/output_vertex.glsl"), 79 | include_str!("shaders/output_fragment.glsl"), 80 | 4, 81 | false)); 82 | 83 | let image_load_buffer = 84 | try!(GlRenderer::build_buffer( 85 | include_str!("shaders/image_load_vertex.glsl"), 86 | include_str!("shaders/image_load_fragment.glsl"), 87 | 4, 88 | false)); 89 | 90 | let native_width = VRAM_WIDTH_PIXELS as u32; 91 | let native_height = VRAM_HEIGHT as u32; 92 | 93 | // Texture holding the raw VRAM texture contents. We can't 94 | // meaningfully upscale it since most games use paletted 95 | // textures. 96 | let fb_texture = 97 | try!(Texture::new(native_width, native_height, gl::RGB5_A1)); 98 | 99 | if depth > 16 { 100 | // Dithering is superfluous when we increase the internal 101 | // color depth 102 | try!(opaque_command_buffer.disable_attribute("dither")); 103 | } 104 | 105 | let dither_scaling = 106 | if scale_dither { 107 | upscaling 108 | } else { 109 | 1 110 | }; 111 | 112 | let command_draw_mode = 113 | if wireframe { 114 | gl::LINE 115 | } else { 116 | gl::FILL 117 | }; 118 | 119 | try!(opaque_command_buffer.program() 120 | .uniform1ui("dither_scaling", dither_scaling)); 121 | 122 | let texture_storage = 123 | match depth { 124 | 16 => gl::RGB5_A1, 125 | 32 => gl::RGBA8, 126 | _ => panic!("Unsupported depth {}", depth), 127 | }; 128 | 129 | let fb_out = try!(Texture::new(native_width * upscaling, 130 | native_height * upscaling, 131 | texture_storage)); 132 | 133 | let fb_out_depth = try!(Texture::new(fb_out.width(), 134 | fb_out.height(), 135 | gl::DEPTH_COMPONENT32F)); 136 | 137 | let mut state = GlRenderer { 138 | command_buffer: opaque_command_buffer, 139 | command_draw_mode: gl::TRIANGLES, 140 | semi_transparent_vertices: Vec::with_capacity(2048), 141 | semi_transparency_mode: SemiTransparencyMode::Average, 142 | command_polygon_mode: command_draw_mode, 143 | output_buffer: output_buffer, 144 | image_load_buffer: image_load_buffer, 145 | config: config, 146 | fb_texture: fb_texture, 147 | fb_out: fb_out, 148 | fb_out_depth: fb_out_depth, 149 | frontend_resolution: (0, 0), 150 | internal_upscaling: upscaling, 151 | internal_color_depth: depth, 152 | primitive_ordering: 0, 153 | }; 154 | 155 | // Yet an other copy of this 1MB array to make the borrow 156 | // checker happy... 157 | let vram_contents = state.config.vram.clone(); 158 | 159 | // Load the VRAM contents into the textures 160 | try!(state.upload_textures((0, 0), 161 | (VRAM_WIDTH_PIXELS, VRAM_HEIGHT), 162 | &vram_contents)); 163 | 164 | Ok(state) 165 | } 166 | 167 | fn build_buffer(vertex_shader: &str, 168 | fragment_shader: &str, 169 | capacity: usize, 170 | lifo: bool) -> Result, Error> 171 | where T: ::retrogl::vertex::Vertex { 172 | 173 | let vs = try!(Shader::new(vertex_shader, ShaderType::Vertex)); 174 | 175 | let fs = try!(Shader::new(fragment_shader, ShaderType::Fragment)); 176 | 177 | let program = try!(Program::new(vs, fs)); 178 | 179 | DrawBuffer::new(capacity, program, lifo) 180 | } 181 | 182 | fn draw(&mut self) -> Result<(), Error> { 183 | 184 | if self.command_buffer.empty() { 185 | // Nothing to be done 186 | return Ok(()) 187 | } 188 | 189 | unsafe { 190 | // XXX No semi-transparency support for now 191 | gl::BlendFuncSeparate(gl::ONE, 192 | gl::ZERO, 193 | gl::ONE, 194 | gl::ZERO); 195 | gl::Disable(gl::BLEND); 196 | } 197 | 198 | let (x, y) = self.config.draw_offset; 199 | 200 | try!(self.command_buffer.program().uniform2i("offset", 201 | x as GLint, 202 | y as GLint)); 203 | 204 | // XXX implement me 205 | try!(self.command_buffer.program().uniform1ui("tex_x_mask", 0xff)); 206 | try!(self.command_buffer.program().uniform1ui("tex_x_or", 0)); 207 | try!(self.command_buffer.program().uniform1ui("tex_y_mask", 0xff)); 208 | try!(self.command_buffer.program().uniform1ui("tex_y_or", 0)); 209 | 210 | // We use texture unit 0 211 | try!(self.command_buffer.program().uniform1i("fb_texture", 0)); 212 | 213 | // Bind the out framebuffer 214 | let _fb = Framebuffer::new_with_depth(&self.fb_out, &self.fb_out_depth); 215 | 216 | unsafe { 217 | gl::Clear(gl::DEPTH_BUFFER_BIT); 218 | } 219 | 220 | // First we draw the opaque vertices 221 | try!(self.command_buffer.program() 222 | .uniform1ui("draw_semi_transparent", 0)); 223 | 224 | try!(self.command_buffer.draw(self.command_draw_mode)); 225 | 226 | try!(self.command_buffer.clear()); 227 | 228 | // Then the semi-transparent vertices 229 | if !self.semi_transparent_vertices.is_empty() { 230 | 231 | // Emulation of the various PSX blending mode using a 232 | // combination of constant alpha/color (to emulate 233 | // constant 1/4 and 1/2 factors) and blending equation. 234 | let (blend_func, blend_src, blend_dst) = 235 | match self.semi_transparency_mode { 236 | SemiTransparencyMode::Average => 237 | (gl::FUNC_ADD, 238 | // Set to 0.5 with gl::BlendColor 239 | gl::CONSTANT_ALPHA, 240 | gl::CONSTANT_ALPHA), 241 | SemiTransparencyMode::Add => 242 | (gl::FUNC_ADD, 243 | gl::ONE, 244 | gl::ONE), 245 | SemiTransparencyMode::SubstractSource => 246 | (gl::FUNC_REVERSE_SUBTRACT, 247 | gl::ONE, 248 | gl::ONE), 249 | SemiTransparencyMode::AddQuarterSource => 250 | (gl::FUNC_ADD, 251 | // Set to 0.25 with gl::BlendColor 252 | gl::CONSTANT_COLOR, 253 | gl::ONE), 254 | }; 255 | 256 | unsafe { 257 | gl::BlendFuncSeparate(blend_src, 258 | blend_dst, 259 | gl::ONE, 260 | gl::ZERO); 261 | 262 | gl::BlendEquationSeparate(blend_func, gl::FUNC_ADD); 263 | 264 | gl::Enable(gl::BLEND); 265 | } 266 | 267 | try!(self.command_buffer.program() 268 | .uniform1ui("draw_semi_transparent", 1)); 269 | 270 | try!(self.command_buffer 271 | .push_slice(&self.semi_transparent_vertices)); 272 | 273 | try!(self.command_buffer.draw(self.command_draw_mode)); 274 | 275 | try!(self.command_buffer.clear()); 276 | self.semi_transparent_vertices.clear(); 277 | } 278 | 279 | self.primitive_ordering = 0; 280 | 281 | Ok(()) 282 | } 283 | 284 | fn apply_scissor(&mut self) { 285 | let (x, y) = self.config.draw_area_top_left; 286 | let (w, h) = self.config.draw_area_dimensions; 287 | 288 | let upscale = self.internal_upscaling as GLsizei; 289 | 290 | // We need to scale those to match the internal resolution if 291 | // upscaling is enabled 292 | let x = (x as GLsizei) * upscale; 293 | let y = (y as GLsizei) * upscale; 294 | let w = (w as GLsizei) * upscale; 295 | let h = (h as GLsizei) * upscale; 296 | 297 | unsafe { 298 | gl::Scissor(x, y, w, h); 299 | } 300 | } 301 | 302 | fn bind_libretro_framebuffer(&mut self) { 303 | let (f_w, f_h) = self.frontend_resolution; 304 | let (w, h) = self.config.display_resolution; 305 | 306 | let upscale = self.internal_upscaling; 307 | 308 | // XXX scale w and h when implementing increased internal 309 | // resolution 310 | let w = (w as u32) * upscale; 311 | let h = (h as u32) * upscale; 312 | 313 | if w != f_w || h != f_h { 314 | // We need to change the frontend's resolution 315 | let geometry = libretro::GameGeometry { 316 | base_width: w as c_uint, 317 | base_height: h as c_uint, 318 | // Max parameters are ignored by this call 319 | max_width: 0, 320 | max_height: 0, 321 | // Is this accurate? 322 | aspect_ratio: 4./3., 323 | }; 324 | 325 | info!("Target framebuffer size: {}x{}", w, h); 326 | 327 | libretro::set_geometry(&geometry); 328 | 329 | self.frontend_resolution = (w, h); 330 | } 331 | 332 | // Bind the output framebuffer provided by the frontend 333 | let fbo = libretro::hw_context::get_current_framebuffer() as GLuint; 334 | 335 | unsafe { 336 | gl::BindFramebuffer(gl::DRAW_FRAMEBUFFER, fbo); 337 | gl::Viewport(0, 0, w as GLsizei, h as GLsizei); 338 | } 339 | } 340 | 341 | fn upload_textures(&mut self, 342 | top_left: (u16, u16), 343 | dimensions: (u16, u16), 344 | pixel_buffer: &[u16]) -> Result<(), Error> { 345 | 346 | try!(self.fb_texture.set_sub_image(top_left, 347 | dimensions, 348 | gl::RGBA, 349 | gl::UNSIGNED_SHORT_1_5_5_5_REV, 350 | pixel_buffer)); 351 | 352 | try!(self.image_load_buffer.clear()); 353 | 354 | let x_start = top_left.0; 355 | let x_end = x_start + dimensions.0; 356 | let y_start = top_left.1; 357 | let y_end = y_start + dimensions.1; 358 | 359 | try!(self.image_load_buffer.push_slice( 360 | &[ImageLoadVertex { position: [x_start, y_start] }, 361 | ImageLoadVertex { position: [x_end, y_start] }, 362 | ImageLoadVertex { position: [x_start, y_end] }, 363 | ImageLoadVertex { position: [x_end, y_end] }, 364 | ])); 365 | 366 | try!(self.image_load_buffer.program().uniform1i("fb_texture", 0)); 367 | 368 | unsafe { 369 | gl::Disable(gl::SCISSOR_TEST); 370 | gl::Disable(gl::BLEND); 371 | gl::PolygonMode(gl::FRONT_AND_BACK, gl::FILL); 372 | } 373 | 374 | // Bind the output framebuffer 375 | let _fb = Framebuffer::new(&self.fb_out); 376 | 377 | try!(self.image_load_buffer.draw(gl::TRIANGLE_STRIP)); 378 | 379 | unsafe { 380 | gl::PolygonMode(gl::FRONT_AND_BACK, self.command_polygon_mode); 381 | gl::Enable(gl::SCISSOR_TEST); 382 | } 383 | 384 | get_error() 385 | } 386 | 387 | pub fn draw_config(&self) -> &DrawConfig { 388 | &self.config 389 | } 390 | 391 | pub fn prepare_render(&mut self) { 392 | 393 | self.apply_scissor(); 394 | 395 | // In case we're upscaling we need to increase the line width 396 | // proportionally 397 | unsafe { 398 | gl::LineWidth(self.internal_upscaling as GLfloat); 399 | gl::PolygonMode(gl::FRONT_AND_BACK, self.command_polygon_mode); 400 | gl::Enable(gl::SCISSOR_TEST); 401 | gl::Enable(gl::DEPTH_TEST); 402 | gl::DepthFunc(gl::LEQUAL); 403 | // Used for PSX GPU command blending 404 | gl::BlendColor(0.25, 0.25, 0.25, 0.5); 405 | } 406 | 407 | // Bind `fb_texture` to texture unit 0 408 | self.fb_texture.bind(gl::TEXTURE0); 409 | } 410 | 411 | pub fn refresh_variables(&mut self) -> bool { 412 | let upscaling = CoreVariables::internal_upscale_factor(); 413 | let depth = CoreVariables::internal_color_depth(); 414 | let scale_dither = CoreVariables::scale_dither(); 415 | let wireframe = CoreVariables::wireframe(); 416 | 417 | let rebuild_fb_out = 418 | upscaling != self.internal_upscaling || 419 | depth != self.internal_color_depth; 420 | 421 | if rebuild_fb_out { 422 | 423 | if depth > 16 { 424 | self.command_buffer.disable_attribute("dither").unwrap() 425 | } else { 426 | self.command_buffer.enable_attribute("dither").unwrap() 427 | } 428 | 429 | let native_width = VRAM_WIDTH_PIXELS as u32; 430 | let native_height = VRAM_HEIGHT as u32; 431 | 432 | let w = native_width * upscaling; 433 | let h = native_height * upscaling; 434 | 435 | let texture_storage = 436 | match depth { 437 | 16 => gl::RGB5_A1, 438 | 32 => gl::RGBA8, 439 | _ => panic!("Unsupported depth {}", depth), 440 | }; 441 | 442 | let fb_out = Texture::new(w, h, texture_storage).unwrap(); 443 | 444 | self.fb_out = fb_out; 445 | 446 | let vram_contents = self.config.vram.clone(); 447 | 448 | // This is a bit wasteful since it'll re-upload the data 449 | // to `fb_texture` even though we haven't touched it but 450 | // this code is not very performance-critical anyway. 451 | self.upload_textures((0, 0), 452 | (VRAM_WIDTH_PIXELS, VRAM_HEIGHT), 453 | &*vram_contents).unwrap(); 454 | 455 | self.fb_out_depth = 456 | Texture::new(w, h, gl::DEPTH_COMPONENT32F).unwrap(); 457 | } 458 | 459 | let dither_scaling = 460 | if scale_dither { 461 | upscaling 462 | } else { 463 | 1 464 | }; 465 | 466 | self.command_buffer.program() 467 | .uniform1ui("dither_scaling", dither_scaling).unwrap(); 468 | 469 | self.command_polygon_mode = 470 | if wireframe { 471 | gl::LINE 472 | } else { 473 | gl::FILL 474 | }; 475 | 476 | unsafe { 477 | gl::LineWidth(upscaling as GLfloat); 478 | } 479 | 480 | // If the scaling factor has changed the frontend should be 481 | // reconfigured. We can't do that here because it could 482 | // destroy the OpenGL context which would destroy `self` 483 | let reconfigure_frontend = self.internal_upscaling != upscaling; 484 | 485 | self.internal_upscaling = upscaling; 486 | self.internal_color_depth = depth; 487 | 488 | return reconfigure_frontend 489 | } 490 | 491 | pub fn finalize_frame(&mut self) { 492 | // Draw pending commands 493 | self.draw().unwrap(); 494 | 495 | // We can now render to the frontend's buffer. 496 | self.bind_libretro_framebuffer(); 497 | 498 | // Bind `fb_out` to texture unit 1 499 | self.fb_out.bind(gl::TEXTURE1); 500 | 501 | // First we draw the visible part of fb_out 502 | unsafe { 503 | gl::Disable(gl::SCISSOR_TEST); 504 | gl::Disable(gl::DEPTH_TEST); 505 | gl::Disable(gl::BLEND); 506 | gl::PolygonMode(gl::FRONT_AND_BACK, gl::FILL); 507 | } 508 | 509 | let (fb_x_start, fb_y_start) = self.config.display_top_left; 510 | let (fb_width, fb_height) = self.config.display_resolution; 511 | 512 | let fb_x_end = fb_x_start + fb_width; 513 | let fb_y_end = fb_y_start + fb_height; 514 | 515 | self.output_buffer.clear().unwrap(); 516 | self.output_buffer.push_slice( 517 | &[OutputVertex { position: [-1., -1.], 518 | fb_coord: [fb_x_start, fb_y_end] }, 519 | OutputVertex { position: [1., -1.], 520 | fb_coord: [fb_x_end, fb_y_end] }, 521 | OutputVertex { position: [-1., 1.], 522 | fb_coord: [fb_x_start, fb_y_start] }, 523 | OutputVertex { position: [1., 1.], 524 | fb_coord: [fb_x_end, fb_y_start] }]) 525 | .unwrap(); 526 | 527 | let depth_24bpp = self.config.display_24bpp as GLint; 528 | 529 | self.output_buffer.program() 530 | .uniform1i("fb", 1).unwrap(); 531 | self.output_buffer.program() 532 | .uniform1i("depth_24bpp", depth_24bpp).unwrap(); 533 | self.output_buffer.program() 534 | .uniform1ui("internal_upscaling", self.internal_upscaling).unwrap(); 535 | 536 | self.output_buffer.draw(gl::TRIANGLE_STRIP).unwrap(); 537 | 538 | // Cleanup OpenGL context before returning to the frontend 539 | unsafe { 540 | gl::Disable(gl::BLEND); 541 | gl::BlendColor(0., 0., 0., 0.); 542 | gl::BlendEquationSeparate(gl::FUNC_ADD, gl::FUNC_ADD); 543 | gl::BlendFuncSeparate(gl::ONE, 544 | gl::ZERO, 545 | gl::ONE, 546 | gl::ZERO); 547 | gl::ActiveTexture(gl::TEXTURE0); 548 | gl::BindTexture(gl::TEXTURE_2D, 0); 549 | gl::UseProgram(0); 550 | gl::BindVertexArray(0); 551 | gl::BindFramebuffer(gl::DRAW_FRAMEBUFFER, 0); 552 | gl::LineWidth(1.); 553 | } 554 | 555 | libretro::gl_frame_done(self.frontend_resolution.0, 556 | self.frontend_resolution.1) 557 | } 558 | 559 | /// Check if a new primitive's attributes are somehow incompatible 560 | /// with the ones currently buffered, in which case we must force 561 | /// a draw to flush the buffers. 562 | fn maybe_force_draw(&mut self, 563 | nvertices: usize, 564 | draw_mode: GLenum, 565 | attributes: &PrimitiveAttributes) { 566 | let force_draw = 567 | // Check if we have enough room left in the buffer 568 | self.command_buffer.remaining_capacity() < nvertices || 569 | // Check if we're changing the draw mode (line <=> triangle) 570 | self.command_draw_mode != draw_mode || 571 | // Check if we're changing the semi-transparency mode 572 | (attributes.semi_transparent && 573 | !self.semi_transparent_vertices.is_empty() && 574 | self.semi_transparency_mode != attributes.semi_transparency_mode); 575 | 576 | if force_draw { 577 | self.draw().unwrap(); 578 | 579 | // Update the state machine for the next primitive 580 | self.command_draw_mode = draw_mode; 581 | } 582 | 583 | if attributes.semi_transparent { 584 | self.semi_transparency_mode = attributes.semi_transparency_mode; 585 | } 586 | } 587 | } 588 | 589 | impl Renderer for GlRenderer { 590 | fn set_draw_offset(&mut self, x: i16, y: i16) { 591 | // Finish drawing anything with the current offset 592 | self.draw().unwrap(); 593 | self.config.draw_offset = (x, y) 594 | } 595 | 596 | fn set_draw_area(&mut self, top_left: (u16, u16), dimensions: (u16, u16)) { 597 | // Finish drawing anything in the current area 598 | self.draw().unwrap(); 599 | 600 | self.config.draw_area_top_left = top_left; 601 | self.config.draw_area_dimensions = dimensions; 602 | 603 | self.apply_scissor(); 604 | } 605 | 606 | fn set_display_mode(&mut self, 607 | top_left: (u16, u16), 608 | resolution: (u16, u16), 609 | depth_24bpp: bool) { 610 | self.config.display_top_left = top_left; 611 | self.config.display_resolution = resolution; 612 | self.config.display_24bpp = depth_24bpp; 613 | } 614 | 615 | fn push_line(&mut self, 616 | attributes: &PrimitiveAttributes, 617 | vertices: &[Vertex; 2]) { 618 | 619 | self.maybe_force_draw(2, gl::LINES, attributes); 620 | 621 | let z = self.primitive_ordering; 622 | 623 | self.primitive_ordering += 1; 624 | 625 | let iter = 626 | vertices.iter().map(|v| 627 | CommandVertex::from_vertex(attributes, v, z)); 628 | 629 | if attributes.semi_transparent { 630 | self.semi_transparent_vertices.extend(iter); 631 | } else { 632 | let v: ArrayVec<[_; 2]> = iter.collect(); 633 | 634 | self.command_buffer.push_slice(&v).unwrap(); 635 | } 636 | } 637 | 638 | fn push_triangle(&mut self, 639 | attributes: &PrimitiveAttributes, 640 | vertices: &[Vertex; 3]) { 641 | 642 | self.maybe_force_draw(3, gl::TRIANGLES, attributes); 643 | 644 | let z = self.primitive_ordering; 645 | 646 | self.primitive_ordering += 1; 647 | 648 | let v: ArrayVec<[_; 3]> = 649 | vertices.iter().map(|v| 650 | CommandVertex::from_vertex(attributes, v, z)) 651 | .collect(); 652 | 653 | let needs_opaque_draw = 654 | !attributes.semi_transparent || 655 | // Textured semi-transparent polys can contain opaque 656 | // texels (when bit 15 of the color is set to 657 | // 0). Therefore they're drawn twice, once for the opaque 658 | // texels and once for the semi-transparent ones 659 | attributes.blend_mode != BlendMode::None; 660 | 661 | if needs_opaque_draw { 662 | self.command_buffer.push_slice(&v).unwrap(); 663 | } 664 | 665 | if attributes.semi_transparent { 666 | self.semi_transparent_vertices.extend_from_slice(&v); 667 | } 668 | } 669 | 670 | fn push_quad(&mut self, 671 | attributes: &PrimitiveAttributes, 672 | vertices: &[Vertex; 4]) { 673 | 674 | self.maybe_force_draw(6, gl::TRIANGLES, attributes); 675 | 676 | let z = self.primitive_ordering; 677 | 678 | self.primitive_ordering += 1; 679 | 680 | let v: ArrayVec<[_; 4]> = 681 | vertices.iter().map(|v| 682 | CommandVertex::from_vertex(attributes, v, z)) 683 | .collect(); 684 | 685 | let needs_opaque_draw = 686 | !attributes.semi_transparent || 687 | // Textured semi-transparent polys can contain opaque 688 | // texels (when bit 15 of the color is set to 689 | // 0). Therefore they're drawn twice, once for the opaque 690 | // texels and once for the semi-transparent ones 691 | attributes.blend_mode != BlendMode::None; 692 | 693 | if needs_opaque_draw { 694 | self.command_buffer.push_slice(&v[0..3]).unwrap(); 695 | self.command_buffer.push_slice(&v[1..4]).unwrap(); 696 | } 697 | 698 | if attributes.semi_transparent { 699 | self.semi_transparent_vertices.extend_from_slice(&v[0..3]); 700 | self.semi_transparent_vertices.extend_from_slice(&v[1..4]); 701 | } 702 | } 703 | 704 | fn fill_rect(&mut self, 705 | color: [u8; 3], 706 | top_left: (u16, u16), 707 | dimensions: (u16, u16)) { 708 | // Draw pending commands 709 | self.draw().unwrap(); 710 | 711 | // Fill rect ignores the draw area. Save the previous scissor 712 | // settings and reconfigure the scissor box to the fill 713 | // rectangle insteadd. 714 | let draw_area_top_left = self.config.draw_area_top_left; 715 | let draw_area_dimensions = self.config.draw_area_dimensions; 716 | 717 | self.config.draw_area_top_left = top_left; 718 | self.config.draw_area_dimensions = dimensions; 719 | 720 | self.apply_scissor(); 721 | 722 | // ClearColor takes normalized floating point color components 723 | let clear_color: ArrayVec<[_; 3]> = 724 | color.iter().map(|&c| (c as f32) / 255.) 725 | .collect(); 726 | 727 | { 728 | // Bind the out framebuffer 729 | let _fb = Framebuffer::new(&self.fb_out); 730 | 731 | unsafe { 732 | gl::ClearColor(clear_color[0], 733 | clear_color[1], 734 | clear_color[2], 735 | // XXX Not entirely sure what happens 736 | // to the mask bit in fill_rect. No$ 737 | // seems to say that it's set to 0. 738 | 0.); 739 | gl::Clear(gl::COLOR_BUFFER_BIT); 740 | } 741 | } 742 | 743 | // Reconfigure the draw area 744 | self.config.draw_area_top_left = draw_area_top_left; 745 | self.config.draw_area_dimensions = draw_area_dimensions; 746 | 747 | self.apply_scissor(); 748 | } 749 | 750 | fn load_image(&mut self, 751 | top_left: (u16, u16), 752 | resolution: (u16, u16), 753 | pixel_buffer: &[u16]) { 754 | self.draw().unwrap(); 755 | 756 | let x_start = top_left.0 as usize; 757 | let y_start = top_left.1 as usize; 758 | 759 | let w = resolution.0 as usize; 760 | let h = resolution.1 as usize; 761 | 762 | // Update the VRAM buffer (this way we won't lose the textures 763 | // if the GL context gets destroyed) 764 | for y in 0..h { 765 | for x in 0..w { 766 | let fb_x = x_start + x; 767 | let fb_y = y_start + y; 768 | 769 | let fb_w = VRAM_WIDTH_PIXELS as usize; 770 | 771 | let fb_index = fb_y * fb_w + fb_x; 772 | let buffer_index = y * w + x; 773 | 774 | self.config.vram[fb_index] = pixel_buffer[buffer_index]; 775 | } 776 | } 777 | 778 | self.upload_textures(top_left, resolution, pixel_buffer).unwrap(); 779 | } 780 | } 781 | 782 | #[derive(Default, Debug, Clone, Copy)] 783 | struct CommandVertex { 784 | /// Position in PlayStation VRAM coordinates 785 | position: [i16; 3], 786 | /// RGB color, 8bits per component 787 | color: [u8; 3], 788 | /// Texture coordinates within the page 789 | texture_coord: [u16; 2], 790 | /// Texture page (base offset in VRAM used for texture lookup) 791 | texture_page: [u16; 2], 792 | /// Color Look-Up Table (palette) coordinates in VRAM 793 | clut: [u16; 2], 794 | /// Blending mode: 0: no texture, 1: raw-texture, 2: texture-blended 795 | texture_blend_mode: u8, 796 | /// Right shift from 16bits: 0 for 16bpp textures, 1 for 8bpp, 2 797 | /// for 4bpp 798 | depth_shift: u8, 799 | /// True if dithering is enabled for this primitive 800 | dither: u8, 801 | /// 0: primitive is opaque, 1: primitive is semi-transparent 802 | semi_transparent: u8, 803 | } 804 | 805 | implement_vertex!(CommandVertex, 806 | position, color, texture_page, 807 | texture_coord, clut, texture_blend_mode, 808 | depth_shift, dither, semi_transparent); 809 | 810 | impl CommandVertex { 811 | fn from_vertex(attributes: &PrimitiveAttributes, 812 | v: &Vertex, 813 | z: i16) -> CommandVertex { 814 | CommandVertex { 815 | position: [v.position[0], v.position[1], z], 816 | color: v.color, 817 | texture_coord: v.texture_coord, 818 | texture_page: attributes.texture_page, 819 | clut: attributes.clut, 820 | texture_blend_mode: match attributes.blend_mode { 821 | BlendMode::None => 0, 822 | BlendMode::Raw => 1, 823 | BlendMode::Blended => 2, 824 | }, 825 | depth_shift: match attributes.texture_depth { 826 | TextureDepth::T4Bpp => 2, 827 | TextureDepth::T8Bpp => 1, 828 | TextureDepth::T16Bpp => 0, 829 | }, 830 | dither: attributes.dither as u8, 831 | semi_transparent: attributes.semi_transparent as u8, 832 | } 833 | } 834 | } 835 | 836 | struct OutputVertex { 837 | /// Vertex position on the screen 838 | position: [f32; 2], 839 | /// Corresponding coordinate in the framebuffer 840 | fb_coord: [u16; 2], 841 | } 842 | 843 | implement_vertex!(OutputVertex, 844 | position, fb_coord); 845 | 846 | struct ImageLoadVertex { 847 | /// Vertex position in VRAM 848 | position: [u16; 2], 849 | } 850 | 851 | implement_vertex!(ImageLoadVertex, 852 | position); 853 | -------------------------------------------------------------------------------- /src/renderer/shaders/command_fragment.glsl: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | uniform sampler2D fb_texture; 4 | 5 | // Scaling to apply to the dither pattern 6 | uniform uint dither_scaling; 7 | // 0: Only draw opaque pixels, 1: only draw semi-transparent pixels 8 | uniform uint draw_semi_transparent; 9 | // Texture window X mask 10 | uniform uint tex_x_mask; 11 | // Texture window X OR value 12 | uniform uint tex_x_or; 13 | // Texture window Y mask 14 | uniform uint tex_y_mask; 15 | // Texture window Y OR value 16 | uniform uint tex_y_or; 17 | 18 | in vec3 frag_shading_color; 19 | // Texture page: base offset for texture lookup. 20 | flat in uvec2 frag_texture_page; 21 | // Texel coordinates within the page. Interpolated by OpenGL. 22 | in vec2 frag_texture_coord; 23 | // Clut coordinates in VRAM 24 | flat in uvec2 frag_clut; 25 | // 0: no texture, 1: raw-texture, 2: blended 26 | flat in uint frag_texture_blend_mode; 27 | // 0: 16bpp (no clut), 1: 8bpp, 2: 4bpp 28 | flat in uint frag_depth_shift; 29 | // 0: No dithering, 1: dithering enabled 30 | flat in uint frag_dither; 31 | // 0: Opaque primitive, 1: semi-transparent primitive 32 | flat in uint frag_semi_transparent; 33 | 34 | out vec4 frag_color; 35 | 36 | const uint BLEND_MODE_NO_TEXTURE = 0U; 37 | const uint BLEND_MODE_RAW_TEXTURE = 1U; 38 | const uint BLEND_MODE_TEXTURE_BLEND = 2U; 39 | 40 | // Read a pixel in VRAM 41 | vec4 vram_get_pixel(uint x, uint y) { 42 | return texelFetch(fb_texture, ivec2(x & 0x3ffU, y & 0x1ffU), 0); 43 | } 44 | 45 | // Take a normalized color and convert it into a 16bit 1555 ABGR 46 | // integer in the format used internally by the Playstation GPU. 47 | uint rebuild_psx_color(vec4 color) { 48 | uint a = uint(floor(color.a + 0.5)); 49 | uint r = uint(floor(color.r * 31. + 0.5)); 50 | uint g = uint(floor(color.g * 31. + 0.5)); 51 | uint b = uint(floor(color.b * 31. + 0.5)); 52 | 53 | return (a << 15) | (b << 10) | (g << 5) | r; 54 | } 55 | 56 | // Texture color 0x0000 is special in the Playstation GPU, it denotes 57 | // a fully transparent texel (even for opaque draw commands). If you 58 | // want black you have to use an opaque draw command and use `0x8000` 59 | // instead. 60 | bool is_transparent(vec4 texel) { 61 | return rebuild_psx_color(texel) == 0U; 62 | } 63 | 64 | // PlayStation dithering pattern. The offset is selected based on the 65 | // pixel position in VRAM, by blocks of 4x4 pixels. The value is added 66 | // to the 8bit color components before they're truncated to 5 bits. 67 | const int dither_pattern[16] = 68 | int[16](-4, 0, -3, 1, 69 | 2, -2, 3, -1, 70 | -3, 1, -4, 0, 71 | 3, -1, 2, -2); 72 | 73 | void main() { 74 | 75 | vec4 color; 76 | 77 | if (frag_texture_blend_mode == BLEND_MODE_NO_TEXTURE) { 78 | color = vec4(frag_shading_color, 0.); 79 | } else { 80 | // Look up texture 81 | 82 | // Number of texel per VRAM 16bit "pixel" for the current depth 83 | uint pix_per_hw = 1U << frag_depth_shift; 84 | 85 | // Texture pages are limited to 256x256 pixels 86 | uint tex_x = uint(frag_texture_coord.x) & 0xffU; 87 | uint tex_y = uint(frag_texture_coord.y) & 0xffU; 88 | 89 | // Texture window adjustments 90 | tex_x = (tex_x & tex_x_mask) | tex_x_or; 91 | tex_y = (tex_y & tex_y_mask) | tex_y_or; 92 | 93 | // Adjust x coordinate based on the texel color depth. 94 | uint tex_x_pix = tex_x / pix_per_hw; 95 | 96 | tex_x_pix += frag_texture_page.x; 97 | tex_y += frag_texture_page.y; 98 | 99 | vec4 texel = vram_get_pixel(tex_x_pix, tex_y); 100 | 101 | if (frag_depth_shift > 0U) { 102 | // 8 and 4bpp textures are paletted so we need to lookup the 103 | // real color in the CLUT 104 | 105 | uint icolor = rebuild_psx_color(texel); 106 | 107 | // A little bitwise magic to get the index in the CLUT. 4bpp 108 | // textures have 4 texels per VRAM "pixel", 8bpp have 2. We need 109 | // to shift the current color to find the proper part of the 110 | // halfword and then mask away the rest. 111 | 112 | // Bits per pixel (4 or 8) 113 | uint bpp = 16U >> frag_depth_shift; 114 | 115 | // 0xf for 4bpp, 0xff for 8bpp 116 | uint mask = ((1U << bpp) - 1U); 117 | 118 | // 0...3 for 4bpp, 0...1 for 8bpp 119 | uint align = tex_x & ((1U << frag_depth_shift) - 1U); 120 | 121 | // 0, 4, 8 or 12 for 4bpp, 0 or 8 for 8bpp 122 | uint shift = (align * bpp); 123 | 124 | // Finally we have the index in the CLUT 125 | uint index = (icolor >> shift) & mask; 126 | 127 | uint clut_x = frag_clut.x + index; 128 | uint clut_y = frag_clut.y; 129 | 130 | // Look up the real color for the texel in the CLUT 131 | texel = vram_get_pixel(clut_x, clut_y); 132 | } 133 | 134 | // texel color 0x0000 is always fully transparent (even for opaque 135 | // draw commands) 136 | if (is_transparent(texel)) { 137 | // Fully transparent texel, discard 138 | discard; 139 | } 140 | 141 | // Bit 15 (stored in the alpha) is used as a flag for 142 | // semi-transparency, but only if this is a semi-transparent draw 143 | // command 144 | uint transparency_flag = uint(floor(texel.a + 0.5)); 145 | 146 | uint is_texel_semi_transparent = transparency_flag & frag_semi_transparent; 147 | 148 | if (is_texel_semi_transparent != draw_semi_transparent) { 149 | // We're not drawing those texels in this pass, discard 150 | discard; 151 | } 152 | 153 | if (frag_texture_blend_mode == BLEND_MODE_RAW_TEXTURE) { 154 | color = texel; 155 | } else /* BLEND_MODE_TEXTURE_BLEND */ { 156 | // Blend the texel with the shading color. `frag_shading_color` 157 | // is multiplied by two so that it can be used to darken or 158 | // lighten the texture as needed. The result of the 159 | // multiplication should be saturated to 1.0 (0xff) but I think 160 | // OpenGL will take care of that since the output buffer holds 161 | // integers. The alpha/mask bit bit is taken directly from the 162 | // texture however. 163 | color = vec4(frag_shading_color * 2. * texel.rgb, texel.a); 164 | } 165 | } 166 | 167 | // 4x4 dithering pattern scaled by `dither_scaling` 168 | uint x_dither = (uint(gl_FragCoord.x) / dither_scaling) & 3U; 169 | uint y_dither = (uint(gl_FragCoord.y) / dither_scaling) & 3U; 170 | 171 | // The multiplication by `frag_dither` will result in 172 | // `dither_offset` being 0 if dithering is disabled 173 | int dither_offset = 174 | dither_pattern[y_dither * 4U + x_dither] * int(frag_dither); 175 | 176 | float dither = float(dither_offset) / 255.; 177 | 178 | frag_color = color + vec4(dither, dither, dither, 0.); 179 | } 180 | -------------------------------------------------------------------------------- /src/renderer/shaders/command_vertex.glsl: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | // Vertex shader for rendering GPU draw commands in the framebuffer 4 | 5 | in ivec3 position; 6 | in uvec3 color; 7 | in uvec2 texture_page; 8 | in uvec2 texture_coord; 9 | in uvec2 clut; 10 | in uint texture_blend_mode; 11 | in uint depth_shift; 12 | in uint dither; 13 | in uint semi_transparent; 14 | 15 | // Drawing offset 16 | uniform ivec2 offset; 17 | 18 | out vec3 frag_shading_color; 19 | flat out uvec2 frag_texture_page; 20 | out vec2 frag_texture_coord; 21 | flat out uvec2 frag_clut; 22 | flat out uint frag_texture_blend_mode; 23 | flat out uint frag_depth_shift; 24 | flat out uint frag_dither; 25 | flat out uint frag_semi_transparent; 26 | 27 | void main() { 28 | ivec2 pos = position.xy + offset; 29 | 30 | // Convert VRAM coordinates (0;1023, 0;511) into OpenGL coordinates 31 | // (-1;1, -1;1) 32 | float xpos = (float(pos.x) / 512) - 1.0; 33 | float ypos = (float(pos.y) / 256) - 1.0; 34 | 35 | // position.z increases as the primitives near the camera so we 36 | // reverse the order to match the common GL convention 37 | float zpos = 1.0 - (float(position.z) / 32768.); 38 | 39 | gl_Position.xyzw = vec4(xpos, ypos, zpos, 1.0); 40 | 41 | // Glium doesn't support "normalized" for now 42 | frag_shading_color = vec3(color) / 255.; 43 | 44 | // Let OpenGL interpolate the texel position 45 | frag_texture_coord = vec2(texture_coord); 46 | 47 | frag_texture_page = texture_page; 48 | frag_clut = clut; 49 | frag_texture_blend_mode = texture_blend_mode; 50 | frag_depth_shift = depth_shift; 51 | frag_dither = dither; 52 | frag_semi_transparent = semi_transparent; 53 | } 54 | -------------------------------------------------------------------------------- /src/renderer/shaders/image_load_fragment.glsl: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | uniform sampler2D fb_texture; 4 | 5 | in vec2 frag_fb_coord; 6 | 7 | out vec4 frag_color; 8 | 9 | // Read a pixel in VRAM 10 | vec4 vram_get_pixel(int x, int y) { 11 | return texelFetch(fb_texture, ivec2(x, y), 0); 12 | } 13 | 14 | void main() { 15 | frag_color = vram_get_pixel(int(frag_fb_coord.x), int(frag_fb_coord.y)); 16 | } 17 | -------------------------------------------------------------------------------- /src/renderer/shaders/image_load_vertex.glsl: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | // Vertex shader for uploading textures from the VRAM texture buffer 4 | // into the output framebuffer 5 | 6 | // The position in the input and output framebuffer are the same 7 | in uvec2 position; 8 | 9 | out vec2 frag_fb_coord; 10 | 11 | void main() { 12 | // Convert VRAM position into OpenGL coordinates 13 | float xpos = (float(position.x) / 512) - 1.0; 14 | float ypos = (float(position.y) / 256) - 1.0; 15 | 16 | gl_Position.xyzw = vec4(xpos, ypos, 0.0, 1.0); 17 | 18 | // frag_fb_coord will remain in PlayStation fb coordinates for 19 | // texelFetch 20 | frag_fb_coord = vec2(position); 21 | } 22 | -------------------------------------------------------------------------------- /src/renderer/shaders/output_fragment.glsl: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | // We're sampling from the internal framebuffer texture 4 | uniform sampler2D fb; 5 | // Framebuffer sampling: 0: Normal 16bpp mode, 1: Use 24bpp mode 6 | uniform int depth_24bpp; 7 | // Internal resolution upscaling factor. Necessary for proper 24bpp 8 | // display since we need to know how the pixels are laid out in RAM. 9 | uniform uint internal_upscaling; 10 | 11 | in vec2 frag_fb_coord; 12 | 13 | out vec4 frag_color; 14 | 15 | // Take a normalized color and convert it into a 16bit 1555 ABGR 16 | // integer in the format used internally by the Playstation GPU. 17 | int rebuild_color(vec4 color) { 18 | int a = int(floor(color.a + 0.5)); 19 | int r = int(floor(color.r * 31. + 0.5)); 20 | int g = int(floor(color.g * 31. + 0.5)); 21 | int b = int(floor(color.b * 31. + 0.5)); 22 | 23 | return (a << 15) | (b << 10) | (g << 5) | r; 24 | } 25 | 26 | void main() { 27 | vec3 color; 28 | 29 | if (depth_24bpp == 0) { 30 | // Use the regular 16bpp mode, fetch directly from the framebuffer 31 | // texture. The alpha/mask bit is ignored here. 32 | color = texture(fb, frag_fb_coord).rgb; 33 | } else { 34 | // In this mode we have to interpret the framebuffer as containing 35 | // 24bit RGB values instead of the usual 16bits 1555. 36 | ivec2 fb_size = textureSize(fb, 0); 37 | 38 | int x_24 = int(frag_fb_coord.x * float(fb_size.x)); 39 | int y = int(frag_fb_coord.y * float(fb_size.y)); 40 | 41 | int x_native = x_24 / int(internal_upscaling); 42 | 43 | x_24 = x_native * int(internal_upscaling); 44 | 45 | // The 24bit color is stored over two 16bit pixels, convert the 46 | // coordinates 47 | int x0_16 = (x_24 * 3) / 2; 48 | 49 | // Move on to the next pixel at native resolution 50 | int x1_16 = x0_16 + int(internal_upscaling); 51 | 52 | int col0 = rebuild_color(texelFetch(fb, ivec2(x0_16, y), 0)); 53 | int col1 = rebuild_color(texelFetch(fb, ivec2(x1_16, y), 0)); 54 | 55 | int col = (col1 << 16) | col0; 56 | 57 | // If we're drawing an odd 24 bit pixel we're starting in the 58 | // middle of a 16bit cell so we need to adjust accordingly. 59 | col >>= 8 * (x_native & 1); 60 | 61 | // Finally we can extract and normalize the 24bit pixel 62 | float b = float((col >> 16) & 0xff) / 255.; 63 | float g = float((col >> 8) & 0xff) / 255.; 64 | float r = float(col & 0xff) / 255.; 65 | 66 | color = vec3(r, g, b); 67 | } 68 | 69 | frag_color = vec4(color, 1.0); 70 | } 71 | -------------------------------------------------------------------------------- /src/renderer/shaders/output_vertex.glsl: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | // Vertex shader for rendering GPU draw commands in the framebuffer 4 | 5 | in vec2 position; 6 | in uvec2 fb_coord; 7 | 8 | out vec2 frag_fb_coord; 9 | 10 | void main() { 11 | gl_Position.xyzw = vec4(position, 0.0, 1.0); 12 | 13 | // Convert the PlayStation framebuffer coordinate into an OpenGL 14 | // texture coordinate 15 | float fb_x_coord = float(fb_coord.x) / 1024; 16 | float fb_y_coord = float(fb_coord.y) / 512; 17 | 18 | frag_fb_coord = vec2(fb_x_coord, fb_y_coord); 19 | } 20 | -------------------------------------------------------------------------------- /src/retrogl/buffer.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | use std::mem::size_of; 3 | use std::ptr; 4 | 5 | use gl; 6 | use gl::types::{GLint, GLuint, GLsizeiptr, GLintptr, GLsizei, GLenum}; 7 | 8 | use retrogl::error::{Error, error_or, get_error}; 9 | use retrogl::vertex::{Vertex, VertexArrayObject}; 10 | use retrogl::program::Program; 11 | use retrogl::types::Kind; 12 | 13 | pub struct DrawBuffer { 14 | /// OpenGL name for this buffer 15 | id: GLuint, 16 | /// Vertex Array Object containing the bindings for this 17 | /// buffer. I'm assuming that each VAO will only use a single 18 | /// buffer for simplicity. 19 | vao: VertexArrayObject, 20 | /// Program used to draw this buffer 21 | program: Program, 22 | /// Number of elements T that the vertex buffer can hold 23 | capacity: usize, 24 | /// Marker for the type of our buffer's contents 25 | contains: PhantomData, 26 | /// Current number of entries in the buffer 27 | len: usize, 28 | /// If true newer items are added *before* older ones 29 | /// (i.e. they'll be drawn first) 30 | lifo: bool, 31 | } 32 | 33 | impl DrawBuffer { 34 | 35 | pub fn new(capacity: usize, 36 | program: Program, 37 | lifo: bool) -> Result, Error> { 38 | 39 | let vao = try!(VertexArrayObject::new()); 40 | 41 | let mut id = 0; 42 | 43 | unsafe { 44 | // Generate the buffer object 45 | gl::GenBuffers(1, &mut id); 46 | }; 47 | 48 | let mut buf = DrawBuffer { 49 | vao: vao, 50 | program: program, 51 | capacity: capacity, 52 | id: id, 53 | contains: PhantomData::, 54 | len: 0, 55 | lifo: lifo, 56 | }; 57 | 58 | try!(buf.clear()); 59 | 60 | try!(buf.bind_attributes()); 61 | 62 | error_or(buf) 63 | } 64 | 65 | /// Specify the vertex attriute layout and bind them to the VAO 66 | fn bind_attributes(&self)-> Result<(), Error> { 67 | self.vao.bind(); 68 | 69 | // ARRAY_BUFFER is captured by VertexAttribPointer 70 | self.bind(); 71 | 72 | let attributes = T::attributes(); 73 | 74 | let element_size = size_of::() as GLint; 75 | 76 | for attr in attributes { 77 | 78 | let index = 79 | match self.program.find_attribute(attr.name) { 80 | Ok(i) => i, 81 | // Don't error out if the shader doesn't use this 82 | // attribute, it could be caused by shader 83 | // optimization if the attribute is unused for 84 | // some reason. 85 | Err(Error::InvalidValue) => continue, 86 | Err(e) => return Err(e), 87 | }; 88 | 89 | unsafe { gl::EnableVertexAttribArray(index) }; 90 | 91 | // This captures the buffer so that we don't have to bind it 92 | // when we draw later on, we'll just have to bind the vao. 93 | match Kind::from_type(attr.ty) { 94 | Kind::Integer => 95 | unsafe { 96 | gl::VertexAttribIPointer(index, 97 | attr.components, 98 | attr.ty, 99 | element_size, 100 | attr.gl_offset()) 101 | }, 102 | Kind::Float => 103 | unsafe { 104 | gl::VertexAttribPointer(index, 105 | attr.components, 106 | attr.ty, 107 | gl::FALSE, 108 | element_size, 109 | attr.gl_offset()) 110 | }, 111 | Kind::Double => 112 | unsafe { 113 | gl::VertexAttribLPointer(index, 114 | attr.components, 115 | attr.ty, 116 | element_size, 117 | attr.gl_offset()) 118 | }, 119 | } 120 | } 121 | 122 | get_error() 123 | } 124 | 125 | pub fn enable_attribute(&self, attr: &str) -> Result<(), Error> { 126 | let index = try!(self.program.find_attribute(attr)); 127 | 128 | self.vao.bind(); 129 | unsafe { 130 | gl::EnableVertexAttribArray(index); 131 | } 132 | 133 | get_error() 134 | } 135 | 136 | pub fn disable_attribute(&self, attr: &str) -> Result<(), Error> { 137 | let index = try!(self.program.find_attribute(attr)); 138 | 139 | self.vao.bind(); 140 | unsafe { 141 | gl::DisableVertexAttribArray(index); 142 | } 143 | 144 | get_error() 145 | } 146 | 147 | pub fn empty(&self) -> bool { 148 | self.len == 0 149 | } 150 | } 151 | 152 | impl DrawBuffer { 153 | 154 | pub fn program(&self) -> &Program { 155 | &self.program 156 | } 157 | 158 | /// Orphan the buffer (to avoid synchronization) and allocate a 159 | /// new one. 160 | /// 161 | /// https://www.opengl.org/wiki/Buffer_Object_Streaming 162 | pub fn clear(&mut self) -> Result<(), Error> { 163 | self.bind(); 164 | 165 | unsafe { 166 | // Compute the size of the buffer 167 | let element_size = size_of::(); 168 | 169 | let storage_size = (self.capacity * element_size) as GLsizeiptr; 170 | 171 | gl::BufferData(gl::ARRAY_BUFFER, 172 | storage_size, 173 | ptr::null(), 174 | gl::DYNAMIC_DRAW); 175 | } 176 | 177 | self.len = 0; 178 | 179 | get_error() 180 | } 181 | 182 | /// Bind the buffer to the current VAO 183 | pub fn bind(&self) { 184 | unsafe { 185 | gl::BindBuffer(gl::ARRAY_BUFFER, self.id); 186 | } 187 | } 188 | 189 | pub fn push_slice(&mut self, 190 | slice: &[T]) -> Result<(), Error> { 191 | let n = slice.len(); 192 | 193 | if n > self.remaining_capacity() { 194 | return Err(Error::OutOfMemory); 195 | } 196 | 197 | let element_size = size_of::(); 198 | 199 | let offset = 200 | match self.lifo { 201 | false => self.len, 202 | true => self.capacity - self.len - n, 203 | }; 204 | 205 | let offset_bytes = offset * element_size; 206 | 207 | let size_bytes = n * element_size; 208 | 209 | self.bind(); 210 | 211 | unsafe { 212 | gl::BufferSubData(gl::ARRAY_BUFFER, 213 | offset_bytes as GLintptr, 214 | size_bytes as GLintptr, 215 | slice.as_ptr() as *const _); 216 | } 217 | 218 | try!(get_error()); 219 | 220 | self.len += n; 221 | 222 | Ok(()) 223 | } 224 | 225 | pub fn draw(&mut self, mode: GLenum) -> Result<(), Error> { 226 | self.vao.bind(); 227 | self.program.bind(); 228 | 229 | let first = 230 | match self.lifo { 231 | false => 0, 232 | true => self.remaining_capacity() as GLint, 233 | }; 234 | 235 | unsafe { gl::DrawArrays(mode, first, self.len as GLsizei) }; 236 | 237 | get_error() 238 | } 239 | 240 | pub fn remaining_capacity(&self) -> usize { 241 | self.capacity - self.len 242 | } 243 | } 244 | 245 | impl Drop for DrawBuffer { 246 | fn drop(&mut self) { 247 | unsafe { 248 | gl::DeleteBuffers(1, &self.id); 249 | } 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /src/retrogl/error.rs: -------------------------------------------------------------------------------- 1 | use gl; 2 | use gl::types::GLenum; 3 | 4 | use retrogl::shader::ShaderType; 5 | 6 | /// OpenGL errors 7 | #[derive(Copy, Clone, PartialEq, Eq, Debug)] 8 | pub enum Error { 9 | /// Error codes returned by glGetError 10 | InvalidEnum, 11 | InvalidValue, 12 | InvalidOperation, 13 | InvalidFramebufferOperatior, 14 | OutOfMemory, 15 | /// In case we encounter an unknown OpenGL error code 16 | Unknown(GLenum), 17 | /// When shader compilation fails 18 | BadShader(ShaderType), 19 | /// When program linking fails 20 | BadProgram, 21 | /// When using a bad/unknown uniform 22 | BadUniform, 23 | } 24 | 25 | pub fn get_error() -> Result<(), Error> { 26 | match unsafe { gl::GetError() } { 27 | gl::NO_ERROR => Ok(()), 28 | gl::INVALID_ENUM => Err(Error::InvalidEnum), 29 | gl::INVALID_VALUE => Err(Error::InvalidValue), 30 | gl::INVALID_OPERATION => Err(Error::InvalidOperation), 31 | gl::INVALID_FRAMEBUFFER_OPERATION => 32 | Err(Error::InvalidFramebufferOperatior), 33 | gl::OUT_OF_MEMORY => Err(Error::OutOfMemory), 34 | n => Err(Error::Unknown(n)), 35 | } 36 | } 37 | 38 | /// Return `Ok(v)` if no OpenGL error flag is active 39 | pub fn error_or(v: T) -> Result { 40 | get_error().map(|_| v) 41 | } 42 | -------------------------------------------------------------------------------- /src/retrogl/framebuffer.rs: -------------------------------------------------------------------------------- 1 | use gl; 2 | use gl::types::{GLuint, GLsizei}; 3 | 4 | use retrogl::error::{Error, error_or}; 5 | use retrogl::texture::Texture; 6 | 7 | pub struct Framebuffer<'a> { 8 | id: GLuint, 9 | _color_texture: &'a Texture, 10 | } 11 | 12 | impl<'a> Framebuffer<'a> { 13 | pub fn new<'n>(color_texture: &'n Texture) 14 | -> Result, Error> { 15 | 16 | let mut id = 0; 17 | 18 | unsafe { 19 | gl::GenFramebuffers(1, &mut id); 20 | } 21 | 22 | let fb = Framebuffer { 23 | id: id, 24 | _color_texture: color_texture, 25 | }; 26 | 27 | fb.bind(); 28 | 29 | unsafe { 30 | gl::FramebufferTexture(gl::DRAW_FRAMEBUFFER, 31 | gl::COLOR_ATTACHMENT0, 32 | color_texture.id(), 33 | 0); 34 | 35 | gl::DrawBuffers(1, &gl::COLOR_ATTACHMENT0); 36 | gl::Viewport(0, 37 | 0, 38 | color_texture.width() as GLsizei, 39 | color_texture.height() as GLsizei); 40 | } 41 | 42 | error_or(fb) 43 | } 44 | 45 | pub fn new_with_depth<'n>(color_texture: &'n Texture, 46 | depth_texture: &'n Texture) 47 | -> Result, Error> { 48 | let fb = try!(Framebuffer::new(color_texture)); 49 | 50 | unsafe { 51 | gl::FramebufferTexture(gl::DRAW_FRAMEBUFFER, 52 | gl::DEPTH_ATTACHMENT, 53 | depth_texture.id(), 54 | 0); 55 | } 56 | 57 | error_or(fb) 58 | } 59 | 60 | pub fn bind(&self) { 61 | unsafe { 62 | gl::BindFramebuffer(gl::DRAW_FRAMEBUFFER, self.id); 63 | } 64 | } 65 | } 66 | 67 | impl<'a> Drop for Framebuffer<'a> { 68 | fn drop(&mut self) { 69 | unsafe { 70 | gl::DeleteFramebuffers(1, &self.id); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/retrogl/mod.rs: -------------------------------------------------------------------------------- 1 | //! PlayStation OpenGL 3.3 renderer playing nice with libretro 2 | 3 | use rustc_serialize::{Encodable, Encoder, Decodable, Decoder}; 4 | 5 | use gl; 6 | 7 | use rustation::gpu::VideoClock; 8 | use rustation::gpu::renderer::Renderer; 9 | use rustation::gpu::{VRAM_WIDTH_PIXELS, VRAM_HEIGHT}; 10 | use CoreVariables; 11 | 12 | use libretro; 13 | 14 | use renderer::GlRenderer; 15 | 16 | #[macro_use] 17 | pub mod vertex; 18 | pub mod error; 19 | pub mod types; 20 | pub mod buffer; 21 | pub mod texture; 22 | pub mod framebuffer; 23 | pub mod shader; 24 | pub mod program; 25 | 26 | pub struct RetroGl { 27 | state: GlState, 28 | video_clock: VideoClock, 29 | } 30 | 31 | impl RetroGl { 32 | pub fn new(video_clock: VideoClock) -> Result { 33 | if !libretro::set_pixel_format(libretro::PixelFormat::Xrgb8888) { 34 | error!("Can't set pixel format"); 35 | return Err(()); 36 | } 37 | 38 | if !libretro::hw_context::init() { 39 | error!("Failed to init hardware context"); 40 | return Err(()); 41 | } 42 | 43 | // The VRAM's bootup contents are undefined 44 | let vram = vec![0xdead; VRAM_PIXELS]; 45 | 46 | let config = DrawConfig { 47 | display_top_left: (0, 0), 48 | display_resolution: (1024, 512), 49 | display_24bpp: false, 50 | draw_area_top_left: (0, 0), 51 | draw_area_dimensions: (0, 0), 52 | draw_offset: (0, 0), 53 | vram: vram, 54 | }; 55 | 56 | Ok(RetroGl { 57 | // No context until `context_reset` is called 58 | state: GlState::Invalid(config), 59 | video_clock: video_clock, 60 | }) 61 | } 62 | 63 | pub fn context_reset(&mut self) { 64 | info!("OpenGL context reset"); 65 | 66 | // Should I call this at every reset? Does it matter? 67 | gl::load_with(|s| { 68 | libretro::hw_context::get_proc_address(s) as *const _ 69 | }); 70 | 71 | let config = 72 | match self.state { 73 | GlState::Valid(ref r) => r.draw_config().clone(), 74 | GlState::Invalid(ref c) => c.clone(), 75 | }; 76 | 77 | match GlRenderer::from_config(config) { 78 | Ok(r) => self.state = GlState::Valid(r), 79 | Err(e) => panic!("Couldn't create RetroGL state: {:?}", e), 80 | } 81 | } 82 | 83 | pub fn context_destroy(&mut self) { 84 | info!("OpenGL context destroy"); 85 | 86 | let config = 87 | match self.state { 88 | GlState::Valid(ref r) => r.draw_config().clone(), 89 | // Looks like we didn't have an OpenGL context anyway... 90 | GlState::Invalid(_) => return, 91 | }; 92 | 93 | self.state = GlState::Invalid(config); 94 | } 95 | 96 | pub fn render_frame(&mut self, emulate: F) 97 | where F: FnOnce(&mut Renderer) { 98 | 99 | let renderer = 100 | match self.state { 101 | GlState::Valid(ref mut r) => r, 102 | GlState::Invalid(_) => 103 | panic!("Attempted to render a frame without GL context"), 104 | }; 105 | 106 | renderer.prepare_render(); 107 | 108 | emulate(renderer); 109 | 110 | renderer.finalize_frame(); 111 | } 112 | 113 | pub fn refresh_variables(&mut self) { 114 | let renderer = 115 | match self.state { 116 | GlState::Valid(ref mut r) => r, 117 | // Nothing to be done if we don't have a GL context 118 | GlState::Invalid(_) => return, 119 | }; 120 | 121 | let reconfigure_frontend = renderer.refresh_variables(); 122 | 123 | if reconfigure_frontend { 124 | // The resolution has changed, we must tell the frontend 125 | // to change its format 126 | 127 | let upscaling = CoreVariables::internal_upscale_factor(); 128 | 129 | let av_info = ::get_av_info(self.video_clock, 130 | upscaling); 131 | 132 | // This call can potentially (but not necessarily) call 133 | // `context_destroy` and `context_reset` to reinitialize 134 | // the entire OpenGL context, so beware. 135 | let ok = unsafe { 136 | libretro::set_system_av_info(&av_info) 137 | }; 138 | 139 | if !ok { 140 | // Some frontends might not support changing the video 141 | // settings at runtime, if that's the case we continue 142 | // with the old settings. The new config will be 143 | // applied on reset. 144 | warn!("Couldn't change frontend resolution"); 145 | warn!("Try resetting to enable the new configuration"); 146 | } 147 | } 148 | } 149 | 150 | /// Return true if we're holding a valid GL context 151 | pub fn is_valid(&self) -> bool { 152 | match self.state { 153 | GlState::Valid(_) => true, 154 | _ => false, 155 | } 156 | } 157 | } 158 | 159 | impl Encodable for RetroGl { 160 | fn encode(&self, s: &mut S) -> Result<(), S::Error> { 161 | s.emit_struct("RetroGl", 2, |s| { 162 | let draw_config = 163 | match self.state { 164 | GlState::Valid(ref r) => r.draw_config(), 165 | GlState::Invalid(ref d) => d, 166 | }; 167 | 168 | try!(s.emit_struct_field("draw_config", 0, 169 | |s| draw_config.encode(s))); 170 | try!(s.emit_struct_field("video_clock", 1, 171 | |s| self.video_clock.encode(s))); 172 | 173 | Ok(()) 174 | }) 175 | } 176 | } 177 | 178 | impl Decodable for RetroGl { 179 | fn decode(d: &mut D) -> Result { 180 | d.read_struct("RetroGl", 2, |d| { 181 | let draw_config = try!(d.read_struct_field("draw_config", 0, 182 | Decodable::decode)); 183 | let video_clock = try!(d.read_struct_field("video_clock", 1, 184 | Decodable::decode)); 185 | 186 | Ok(RetroGl{ 187 | state: GlState::Invalid(draw_config), 188 | video_clock: video_clock, 189 | }) 190 | }) 191 | } 192 | } 193 | 194 | /// State machine dealing with OpenGL context 195 | /// destruction/reconstruction 196 | enum GlState { 197 | /// OpenGL context is ready 198 | Valid(GlRenderer), 199 | /// OpenGL context has been destroy (or is not yet created) 200 | Invalid(DrawConfig), 201 | } 202 | 203 | #[derive(RustcEncodable, RustcDecodable, Clone)] 204 | pub struct DrawConfig { 205 | pub display_top_left: (u16, u16), 206 | pub display_resolution: (u16, u16), 207 | pub display_24bpp: bool, 208 | pub draw_offset: (i16, i16), 209 | pub draw_area_top_left: (u16, u16), 210 | pub draw_area_dimensions: (u16, u16), 211 | /// VRAM is stored in a Vec instead of a `Box<[u16; VRAM_PIXELS]>` 212 | /// because with the Box rustc seems to miss an optimization and 213 | /// puts a temporary array on the stack which overflows on 214 | /// plaftforms with a shallow stack (Windows for instance). 215 | pub vram: Vec, 216 | } 217 | 218 | const VRAM_PIXELS: usize = VRAM_WIDTH_PIXELS as usize * VRAM_HEIGHT as usize; 219 | -------------------------------------------------------------------------------- /src/retrogl/program.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::CString; 2 | 3 | use gl; 4 | use gl::types::{GLint, GLuint, GLsizei}; 5 | use std::collections::HashMap; 6 | 7 | use retrogl::shader::Shader; 8 | use retrogl::error::{Error, error_or, get_error}; 9 | 10 | pub struct Program { 11 | id: GLuint, 12 | /// Hash map of all the active uniforms in this program 13 | uniforms: UniformMap, 14 | } 15 | 16 | impl Program { 17 | pub fn new(vertex_shader: Shader, 18 | fragment_shader: Shader) -> Result { 19 | let id = unsafe { gl::CreateProgram() }; 20 | 21 | vertex_shader.attach_to(id); 22 | fragment_shader.attach_to(id); 23 | 24 | unsafe { gl::LinkProgram(id) }; 25 | 26 | vertex_shader.detach_from(id); 27 | fragment_shader.detach_from(id); 28 | 29 | // Check if the program linking was successful 30 | let mut status = gl::FALSE as GLint; 31 | unsafe { gl::GetProgramiv(id, gl::LINK_STATUS, &mut status) }; 32 | 33 | if status == gl::TRUE as GLint { 34 | let uniforms = try!(load_program_uniforms(id)); 35 | 36 | // There shouldn't be anything in glGetError but let's 37 | // check to make sure. 38 | error_or(Program { 39 | id: id, 40 | uniforms: uniforms 41 | }) 42 | } else { 43 | error!("OpenGL program linking failed"); 44 | 45 | match get_program_info_log(id) { 46 | Some(s) => error!("Program info log:\n{}", s), 47 | None => error!("No program info log") 48 | } 49 | 50 | Err(Error::BadProgram) 51 | } 52 | } 53 | 54 | pub fn find_attribute(&self, attr: &str) -> Result { 55 | let cstr = CString::new(attr).unwrap(); 56 | 57 | let index = unsafe { gl::GetAttribLocation(self.id, cstr.as_ptr()) }; 58 | 59 | if index < 0 { 60 | error!("Couldn't find attribute \"{}\" in program", attr); 61 | try!(get_error()); 62 | // Probably shouldn't be reached, but just in case... 63 | return Err(Error::InvalidValue); 64 | } 65 | 66 | error_or(index as GLuint) 67 | } 68 | 69 | pub fn bind(&self) { 70 | unsafe { gl::UseProgram(self.id) }; 71 | } 72 | 73 | fn uniform(&self, name: &str) -> Result { 74 | let e = self.uniforms.get(name) 75 | .map(|&u| u) 76 | .ok_or(Error::BadUniform); 77 | 78 | if e.is_err() { 79 | warn!("Attempted to access unknown uniform {}", name); 80 | } 81 | 82 | e 83 | } 84 | 85 | pub fn uniform1i(&self, name: &str, i: GLint) -> Result<(), Error> { 86 | self.bind(); 87 | 88 | self.uniform(name) 89 | .map(|u| unsafe { gl::Uniform1i(u, i) }) 90 | } 91 | 92 | pub fn uniform1ui(&self, name: &str, i: GLuint) -> Result<(), Error> { 93 | self.bind(); 94 | 95 | self.uniform(name) 96 | .map(|u| unsafe { gl::Uniform1ui(u, i) }) 97 | } 98 | 99 | pub fn uniform2i(&self, 100 | name: &str, 101 | a: GLint, 102 | b: GLint) -> Result<(), Error> { 103 | self.bind(); 104 | 105 | self.uniform(name) 106 | .map(|u| unsafe { gl::Uniform2i(u, a, b) }) 107 | } 108 | } 109 | 110 | impl Drop for Program { 111 | fn drop(&mut self) { 112 | unsafe { gl::DeleteProgram(self.id) }; 113 | } 114 | } 115 | 116 | fn get_program_info_log(id: GLuint) -> Option { 117 | let mut log_len = 0 as GLint; 118 | 119 | unsafe { 120 | gl::GetProgramiv(id, gl::INFO_LOG_LENGTH, &mut log_len); 121 | } 122 | 123 | if log_len <= 0 { 124 | return None 125 | } 126 | 127 | let mut log = vec![0u8; log_len as usize]; 128 | 129 | unsafe { 130 | gl::GetProgramInfoLog(id, 131 | log.len() as GLsizei, 132 | &mut log_len, 133 | log.as_mut_ptr() as *mut _); 134 | } 135 | 136 | if log_len <= 0 { 137 | return None 138 | } 139 | 140 | // The length returned by GetShaderInfoLog *excludes* 141 | // the ending \0 unlike the call to GetShaderiv above 142 | // so we can get rid of it by truncating here. 143 | log.truncate(log_len as usize); 144 | 145 | Some(String::from_utf8_lossy(&log).into_owned()) 146 | } 147 | 148 | type UniformMap = HashMap; 149 | 150 | // Return a hashmap of all uniform names contained in `program` with 151 | // their corresponding location. 152 | fn load_program_uniforms(program: GLuint) -> Result { 153 | let mut n_uniforms = 0; 154 | 155 | unsafe { 156 | gl::GetProgramiv(program, 157 | gl::ACTIVE_UNIFORMS, 158 | &mut n_uniforms as *mut GLuint as *mut _); 159 | } 160 | 161 | let mut uniforms = HashMap::with_capacity(n_uniforms as usize); 162 | 163 | // Figure out how long a uniform game can be 164 | let mut max_name_len = 0; 165 | 166 | unsafe { 167 | gl::GetProgramiv(program, 168 | gl::ACTIVE_UNIFORM_MAX_LENGTH, 169 | &mut max_name_len); 170 | } 171 | 172 | try!(get_error()); 173 | 174 | for u in 0..n_uniforms { 175 | // Retrieve the name of this uniform 176 | let mut name = vec![0; max_name_len as usize]; 177 | let mut len = 0; 178 | // XXX we might want to validate those at some point 179 | let mut size = 0; 180 | let mut ty = 0; 181 | 182 | unsafe { 183 | gl::GetActiveUniform(program, 184 | u, 185 | name.len() as GLsizei, 186 | &mut len, 187 | &mut size, 188 | &mut ty, 189 | name.as_mut_ptr() as *mut _); 190 | } 191 | 192 | if len <= 0 { 193 | warn!("Ignoring uniform name with size {}", len); 194 | continue; 195 | } 196 | 197 | // Retrieve the location of this uniform 198 | let location = unsafe { 199 | // GetActiveUniform puts a \0-terminated c-string in 200 | // `name` so we can use that directly. 201 | gl::GetUniformLocation(program, name.as_ptr() as *const _) 202 | }; 203 | 204 | name.truncate(len as usize); 205 | let name = String::from_utf8(name).unwrap(); 206 | 207 | if location < 0 { 208 | warn!("Uniform \"{}\" doesn't have a location", name); 209 | continue; 210 | } 211 | 212 | uniforms.insert(name, location); 213 | } 214 | 215 | error_or(uniforms) 216 | } 217 | -------------------------------------------------------------------------------- /src/retrogl/shader.rs: -------------------------------------------------------------------------------- 1 | use gl; 2 | use gl::types::{GLint, GLuint, GLenum}; 3 | 4 | use retrogl::error::{Error, error_or}; 5 | 6 | pub struct Shader { 7 | id: GLuint, 8 | } 9 | 10 | impl Shader { 11 | pub fn new(source: &str, shader_type: ShaderType) -> Result { 12 | let id = unsafe { gl::CreateShader(shader_type.into_gl()) }; 13 | 14 | unsafe { 15 | gl::ShaderSource(id, 16 | 1, 17 | [source.as_ptr()].as_ptr() as *const *const _, 18 | [source.len() as GLint].as_ptr()); 19 | gl::CompileShader(id); 20 | } 21 | 22 | // Check if the compilation was successful 23 | let mut status = gl::FALSE as GLint; 24 | unsafe { gl::GetShaderiv(id, gl::COMPILE_STATUS, &mut status) }; 25 | 26 | if status == gl::TRUE as GLint { 27 | // There shouldn't be anything in glGetError but let's 28 | // check to make sure. 29 | error_or(Shader { 30 | id: id, 31 | }) 32 | } else { 33 | error!("{:?} shader compilation failed:\n{}", shader_type, source); 34 | 35 | match get_shader_info_log(id) { 36 | Some(s) => error!("Shader info log:\n{}", s), 37 | None => error!("No shader info log") 38 | } 39 | 40 | Err(Error::BadShader(shader_type)) 41 | } 42 | } 43 | 44 | pub fn attach_to(&self, program: GLuint) { 45 | unsafe { 46 | gl::AttachShader(program, self.id); 47 | } 48 | } 49 | 50 | pub fn detach_from(&self, program: GLuint) { 51 | unsafe { 52 | gl::DetachShader(program, self.id); 53 | } 54 | } 55 | } 56 | 57 | impl Drop for Shader { 58 | fn drop(&mut self) { 59 | unsafe { gl::DeleteShader(self.id) }; 60 | } 61 | } 62 | 63 | #[derive(Copy, Clone, PartialEq, Eq, Debug)] 64 | pub enum ShaderType { 65 | Vertex, 66 | Fragment, 67 | } 68 | 69 | impl ShaderType { 70 | fn into_gl(self) -> GLenum { 71 | match self { 72 | ShaderType::Vertex => gl::VERTEX_SHADER, 73 | ShaderType::Fragment => gl::FRAGMENT_SHADER, 74 | } 75 | } 76 | } 77 | 78 | fn get_shader_info_log(id: GLuint) -> Option { 79 | let mut log_len = 0 as GLint; 80 | 81 | unsafe { 82 | gl::GetShaderiv(id, gl::INFO_LOG_LENGTH, &mut log_len); 83 | } 84 | 85 | if log_len <= 0 { 86 | return None 87 | } 88 | 89 | let mut log = vec![0u8; log_len as usize]; 90 | 91 | unsafe { 92 | gl::GetShaderInfoLog(id, 93 | log_len, 94 | &mut log_len, 95 | log.as_mut_ptr() as *mut _); 96 | } 97 | 98 | if log_len <= 0 { 99 | return None 100 | } 101 | 102 | // The length returned by GetShaderInfoLog *excludes* 103 | // the ending \0 unlike the call to GetShaderiv above 104 | // so we can get rid of it by truncating here. 105 | log.truncate(log_len as usize); 106 | 107 | Some(String::from_utf8_lossy(&log).into_owned()) 108 | } 109 | -------------------------------------------------------------------------------- /src/retrogl/texture.rs: -------------------------------------------------------------------------------- 1 | use gl; 2 | use gl::types::{GLint, GLuint, GLenum, GLsizei}; 3 | 4 | use retrogl::error::{Error, error_or, get_error}; 5 | 6 | pub struct Texture { 7 | id: GLuint, 8 | width: u32, 9 | height: u32, 10 | } 11 | 12 | impl Texture { 13 | pub fn new(width: u32, 14 | height: u32, 15 | internal_format: GLenum) -> Result { 16 | let mut id = 0; 17 | 18 | unsafe { 19 | gl::GenTextures(1, &mut id); 20 | gl::BindTexture(gl::TEXTURE_2D, id); 21 | gl::TexStorage2D(gl::TEXTURE_2D, 22 | 1, 23 | internal_format, 24 | width as GLsizei, 25 | height as GLsizei); 26 | } 27 | 28 | error_or(Texture { 29 | id: id, 30 | width: width, 31 | height: height, 32 | }) 33 | } 34 | 35 | pub fn bind(&self, texture_unit: GLenum) { 36 | unsafe { 37 | gl::ActiveTexture(texture_unit); 38 | gl::BindTexture(gl::TEXTURE_2D, self.id); 39 | } 40 | } 41 | 42 | pub fn set_sub_image(&self, 43 | top_left: (u16, u16), 44 | resolution: (u16, u16), 45 | format: GLenum, 46 | ty: GLenum, 47 | data: &[T]) -> Result<(), Error> { 48 | 49 | if data.len() != (resolution.0 as usize * resolution.1 as usize) { 50 | panic!("Invalid texture sub_image size"); 51 | } 52 | 53 | unsafe { 54 | gl::PixelStorei(gl::UNPACK_ALIGNMENT, 1); 55 | gl::BindTexture(gl::TEXTURE_2D, self.id); 56 | gl::TexSubImage2D(gl::TEXTURE_2D, 57 | 0, 58 | top_left.0 as GLint, 59 | top_left.1 as GLint, 60 | resolution.0 as GLsizei, 61 | resolution.1 as GLsizei, 62 | format, 63 | ty, 64 | data.as_ptr() as *const _); 65 | } 66 | 67 | get_error() 68 | } 69 | 70 | pub unsafe fn id(&self) -> GLuint { 71 | self.id 72 | } 73 | 74 | pub fn width(&self) -> u32 { 75 | self.width 76 | } 77 | 78 | pub fn height(&self) -> u32 { 79 | self.height 80 | } 81 | } 82 | 83 | impl Drop for Texture { 84 | fn drop(&mut self) { 85 | unsafe { 86 | gl::DeleteTextures(1, &self.id); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/retrogl/types.rs: -------------------------------------------------------------------------------- 1 | use gl; 2 | use gl::types::{GLint, GLenum}; 3 | 4 | pub trait GlType { 5 | /// Return the GL type associated to this rust type (BYTE, FLOAT, 6 | /// UNSIGNED_SHORT etc...) 7 | fn attribute_type() -> GLenum; 8 | /// Return the number of components 9 | fn components() -> GlComponents; 10 | } 11 | 12 | /// GL types in vertex attributes and uniforms can have between 1 and 13 | /// 4 components. I put then in an enum to make it easier to match on 14 | /// them. 15 | #[derive(Copy, Clone, PartialEq, Eq)] 16 | pub enum GlComponents { 17 | Single = 1, 18 | Pair = 2, 19 | Triple = 3, 20 | Quad = 4, 21 | } 22 | 23 | #[derive(Copy, Clone, PartialEq, Eq)] 24 | pub enum Kind { 25 | Integer, 26 | Float, 27 | Double, 28 | } 29 | 30 | impl Kind { 31 | pub fn from_type(t: GLenum) -> Kind { 32 | match t { 33 | gl::BYTE | gl::UNSIGNED_BYTE | gl::SHORT | 34 | gl::UNSIGNED_SHORT | gl::INT | gl::UNSIGNED_INT 35 | => Kind::Integer, 36 | gl::FLOAT => Kind::Float, 37 | gl::DOUBLE => Kind::Double, 38 | _ => panic!("Kind of GL type {} not known", t), 39 | } 40 | } 41 | } 42 | 43 | impl GlComponents { 44 | pub fn into_gl(self) -> GLint { 45 | self as GLint 46 | } 47 | } 48 | 49 | impl GlType for u32 { 50 | fn attribute_type() -> GLenum { 51 | gl::UNSIGNED_INT 52 | } 53 | 54 | fn components() -> GlComponents { 55 | GlComponents::Single 56 | } 57 | } 58 | 59 | impl GlType for [u8; 3] { 60 | fn attribute_type() -> GLenum { 61 | gl::UNSIGNED_BYTE 62 | } 63 | 64 | fn components() -> GlComponents { 65 | GlComponents::Triple 66 | } 67 | } 68 | 69 | impl GlType for [i16; 2] { 70 | fn attribute_type() -> GLenum { 71 | gl::SHORT 72 | } 73 | 74 | fn components() -> GlComponents { 75 | GlComponents::Pair 76 | } 77 | } 78 | 79 | impl GlType for [i16; 3] { 80 | fn attribute_type() -> GLenum { 81 | gl::SHORT 82 | } 83 | 84 | fn components() -> GlComponents { 85 | GlComponents::Triple 86 | } 87 | } 88 | 89 | impl GlType for [u16; 2] { 90 | fn attribute_type() -> GLenum { 91 | gl::UNSIGNED_SHORT 92 | } 93 | 94 | fn components() -> GlComponents { 95 | GlComponents::Pair 96 | } 97 | } 98 | 99 | impl GlType for u8 { 100 | fn attribute_type() -> GLenum { 101 | gl::UNSIGNED_BYTE 102 | } 103 | 104 | fn components() -> GlComponents { 105 | GlComponents::Single 106 | } 107 | } 108 | 109 | impl GlType for [f32; 2] { 110 | fn attribute_type() -> GLenum { 111 | gl::FLOAT 112 | } 113 | 114 | fn components() -> GlComponents { 115 | GlComponents::Pair 116 | } 117 | } 118 | 119 | impl GlType for [f32; 3] { 120 | fn attribute_type() -> GLenum { 121 | gl::FLOAT 122 | } 123 | 124 | fn components() -> GlComponents { 125 | GlComponents::Triple 126 | } 127 | } 128 | 129 | impl GlType for [f32; 4] { 130 | fn attribute_type() -> GLenum { 131 | gl::FLOAT 132 | } 133 | 134 | fn components() -> GlComponents { 135 | GlComponents::Quad 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/retrogl/vertex.rs: -------------------------------------------------------------------------------- 1 | use gl; 2 | use gl::types::{GLint, GLuint, GLenum, GLvoid}; 3 | 4 | use retrogl::error::{Error, error_or}; 5 | 6 | pub struct VertexArrayObject { 7 | id: GLuint, 8 | } 9 | 10 | impl VertexArrayObject { 11 | pub fn new() -> Result { 12 | let mut id = 0; 13 | 14 | unsafe { 15 | gl::GenVertexArrays(1, &mut id); 16 | } 17 | 18 | error_or(VertexArrayObject { 19 | id: id, 20 | }) 21 | } 22 | 23 | pub fn bind(&self) { 24 | unsafe { 25 | gl::BindVertexArray(self.id); 26 | } 27 | } 28 | 29 | } 30 | 31 | impl Drop for VertexArrayObject { 32 | fn drop(&mut self) { 33 | unsafe { 34 | gl::DeleteVertexArrays(1, &self.id); 35 | } 36 | } 37 | } 38 | 39 | pub trait Vertex { 40 | fn attributes() -> Vec; 41 | } 42 | 43 | pub struct Attribute { 44 | pub name: &'static str, 45 | pub offset: usize, 46 | /// Attribute type (BYTE, UNSIGNED_SHORT, FLOAT etc...) 47 | pub ty: GLenum, 48 | pub components: GLint, 49 | } 50 | 51 | impl Attribute { 52 | /// For some reason VertexAttribXPointer takes the offset as a 53 | /// pointer... 54 | pub fn gl_offset(&self) -> *const GLvoid { 55 | self.offset as *const _ 56 | } 57 | } 58 | 59 | /// Retrieve the offset of `$field` in struct `$st` 60 | macro_rules! offset_of { 61 | ($st: ident, $field: ident) => ({ 62 | let null_instance: &$st = unsafe { ::std::mem::transmute(0usize) }; 63 | let offset: usize = 64 | unsafe { 65 | ::std::mem::transmute(&null_instance.$field) 66 | }; 67 | 68 | offset 69 | }) 70 | } 71 | 72 | /// Build an Attribute for `$field` in struct `$st` 73 | macro_rules! build_attribute { 74 | ($st: ident, $field: ident) => ({ 75 | /// Helper function used to build an Attribute from a struct 76 | /// field. The first parameter is *not* a valid pointer, it's just 77 | /// here in order to get the proper generic type T 78 | fn build(_invalid: *const T, 79 | name: &'static str, 80 | offset: usize) 81 | -> $crate::retrogl::vertex::Attribute { 82 | 83 | $crate::retrogl::vertex::Attribute { 84 | name: name, 85 | offset: offset, 86 | ty: T::attribute_type(), 87 | components: T::components().into_gl(), 88 | } 89 | } 90 | 91 | let null_instance: &$st = unsafe { ::std::mem::transmute(0usize) }; 92 | build(&null_instance.$field, stringify!($field), offset_of!($st, $field)) 93 | }) 94 | } 95 | 96 | /// Inspired by glium, implement the Vertex trait for fields `$field` 97 | /// of struct `$st` 98 | macro_rules! implement_vertex { 99 | ($st:ident, $($field:ident),+$(,)*) => ( 100 | impl $crate::retrogl::vertex::Vertex for $st { 101 | fn attributes() -> Vec<$crate::retrogl::vertex::Attribute> { 102 | vec![$(build_attribute!($st, $field)),+] 103 | } 104 | } 105 | ) 106 | } 107 | -------------------------------------------------------------------------------- /src/retrolog.rs: -------------------------------------------------------------------------------- 1 | //! Logger implementation using libretro as a backend 2 | 3 | use log; 4 | use libretro; 5 | 6 | use std::io::{Write, stderr}; 7 | 8 | struct RetroLogger; 9 | 10 | impl log::Log for RetroLogger { 11 | fn enabled(&self, _: &log::LogMetadata) -> bool { 12 | true 13 | } 14 | 15 | fn log(&self, record: &log::LogRecord) { 16 | if self.enabled(record.metadata()) { 17 | let s = ::std::fmt::format(*record.args()); 18 | 19 | let lvl = 20 | match record.level() { 21 | log::LogLevel::Error => libretro::log::Level::Error, 22 | log::LogLevel::Warn => libretro::log::Level::Warn, 23 | log::LogLevel::Info => libretro::log::Level::Info, 24 | log::LogLevel::Debug => libretro::log::Level::Debug, 25 | // Nothing below Debug in libretro 26 | log::LogLevel::Trace => libretro::log::Level::Debug, 27 | }; 28 | 29 | libretro::log::log(lvl, &s); 30 | } 31 | } 32 | } 33 | 34 | struct StdErrLogger; 35 | 36 | impl log::Log for StdErrLogger { 37 | fn enabled(&self, _: &log::LogMetadata) -> bool { 38 | true 39 | } 40 | 41 | fn log(&self, record: &log::LogRecord) { 42 | if self.enabled(record.metadata()) { 43 | let _ = 44 | writeln!(&mut stderr(), 45 | "{} - {}", 46 | record.level(), 47 | record.args()); 48 | } 49 | } 50 | } 51 | 52 | pub fn init() { 53 | let retrolog_ok = libretro::log::init(); 54 | 55 | log::set_logger(|max_log_level| { 56 | // XXX Should we make this configurable? 57 | max_log_level.set(log::LogLevelFilter::max()); 58 | 59 | if retrolog_ok { 60 | Box::new(RetroLogger) 61 | } else { 62 | Box::new(StdErrLogger) 63 | } 64 | }).unwrap(); 65 | 66 | if retrolog_ok { 67 | info!("Logging initialized"); 68 | } else { 69 | warn!("Couldn't initialize libretro logging, using stderr"); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/savestate.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | 3 | pub struct Encoder<'a> { 4 | writer: &'a mut io::Write, 5 | } 6 | 7 | impl<'a> Encoder<'a> { 8 | pub fn new(writer: &'a mut io::Write) -> Result, Error> { 9 | 10 | let mut encoder = Encoder { 11 | writer: writer 12 | }; 13 | 14 | // Magic 15 | try!(encoder.write_bytes(MAGIC)); 16 | 17 | // It's pointless to store a version here since savestates 18 | // will probably break every time we make a significant change 19 | // to the core of the emulator. 20 | 21 | Ok(encoder) 22 | } 23 | 24 | fn write_bytes(&mut self, b: &[u8]) -> Result<(), Error> { 25 | match self.writer.write_all(b) { 26 | Ok(_) => Ok(()), 27 | Err(e) => Err(Error::IoError(e)), 28 | } 29 | } 30 | } 31 | 32 | impl<'a> ::rustc_serialize::Encoder for Encoder<'a> { 33 | 34 | type Error = Error; 35 | 36 | fn emit_nil(&mut self) -> Result<(), Error> { 37 | self.emit_str("nil") 38 | } 39 | 40 | fn emit_usize(&mut self, v: usize) -> Result<(), Error> { 41 | if v as u32 as usize != v { 42 | Err(Error::USizeOverflow(v)) 43 | } else { 44 | self.emit_u32(v as u32) 45 | } 46 | } 47 | 48 | fn emit_u64(&mut self, v: u64) -> Result<(), Error> { 49 | let b = [ 50 | v as u8, 51 | (v >> 8) as u8, 52 | (v >> 16) as u8, 53 | (v >> 24) as u8, 54 | (v >> 32) as u8, 55 | (v >> 40) as u8, 56 | (v >> 48) as u8, 57 | (v >> 56) as u8, 58 | ]; 59 | 60 | self.write_bytes(&b) 61 | } 62 | 63 | fn emit_u32(&mut self, v: u32) -> Result<(), Error> { 64 | let b = [ 65 | v as u8, 66 | (v >> 8) as u8, 67 | (v >> 16) as u8, 68 | (v >> 24) as u8, 69 | ]; 70 | 71 | self.write_bytes(&b) 72 | } 73 | 74 | fn emit_u16(&mut self, v: u16) -> Result<(), Error> { 75 | let b = [ 76 | v as u8, 77 | (v >> 8) as u8, 78 | ]; 79 | 80 | self.write_bytes(&b) 81 | } 82 | 83 | fn emit_u8(&mut self, v: u8) -> Result<(), Error> { 84 | self.write_bytes(&[v]) 85 | } 86 | 87 | fn emit_isize(&mut self, v: isize) -> Result<(), Error> { 88 | if v as i32 as isize != v { 89 | Err(Error::ISizeOverflow(v)) 90 | } else { 91 | self.emit_i32(v as i32) 92 | } 93 | } 94 | 95 | fn emit_i64(&mut self, v: i64) -> Result<(), Error> { 96 | self.emit_u64(v as u64) 97 | } 98 | 99 | fn emit_i32(&mut self, v: i32) -> Result<(), Error> { 100 | self.emit_u32(v as u32) 101 | } 102 | 103 | fn emit_i16(&mut self, v: i16) -> Result<(), Error> { 104 | self.emit_u16(v as u16) 105 | } 106 | 107 | fn emit_i8(&mut self, v: i8) -> Result<(), Error> { 108 | self.emit_u8(v as u8) 109 | } 110 | 111 | fn emit_bool(&mut self, v: bool) -> Result<(), Error> { 112 | self.emit_u8(v as u8) 113 | } 114 | 115 | fn emit_f64(&mut self, _: f64) -> Result<(), Error> { 116 | panic!("f64 serialization") 117 | } 118 | 119 | fn emit_f32(&mut self, _: f32) -> Result<(), Error> { 120 | panic!("f32 serialization") 121 | } 122 | 123 | fn emit_char(&mut self, v: char) -> Result<(), Error> { 124 | self.emit_u32(v as u32) 125 | } 126 | 127 | fn emit_str(&mut self, v: &str) -> Result<(), Error> { 128 | // Convert into bytes 129 | let s = v.as_bytes(); 130 | 131 | let len = s.len(); 132 | 133 | if len > STRING_MAX_LEN { 134 | return Err(Error::StringTooLong(len)); 135 | } 136 | 137 | try!(self.emit_usize(len)); 138 | try!(self.write_bytes(s)); 139 | 140 | Ok(()) 141 | } 142 | 143 | fn emit_enum(&mut self, name: &str, f: F) -> Result<(), Error> 144 | where F: FnOnce(&mut Self) -> Result<(), Error> { 145 | try!(self.emit_str(name)); 146 | 147 | f(self) 148 | } 149 | 150 | fn emit_enum_variant(&mut self, 151 | v_name: &str, 152 | _v_id: usize, 153 | _len: usize, 154 | f: F) -> Result<(), Error> 155 | where F: FnOnce(&mut Self) -> Result<(), Error> { 156 | 157 | // We store the name instead of the ID in order not to end up 158 | // with messed up state if an enum gets reordered or something 159 | try!(self.emit_str(v_name)); 160 | 161 | f(self) 162 | } 163 | 164 | fn emit_enum_variant_arg(&mut self, 165 | a_idx: usize, 166 | f: F) -> Result<(), Error> 167 | where F: FnOnce(&mut Self) -> Result<(), Error> { 168 | try!(self.emit_usize(a_idx)); 169 | 170 | f(self) 171 | } 172 | 173 | fn emit_enum_struct_variant(&mut self, 174 | _v_name: &str, 175 | _v_id: usize, 176 | _len: usize, 177 | _f: F) -> Result<(), Error> 178 | where F: FnOnce(&mut Self) -> Result<(), Error> { 179 | panic!() 180 | } 181 | 182 | fn emit_enum_struct_variant_field(&mut self, 183 | _f_name: &str, 184 | _f_idx: usize, 185 | _f: F) -> Result<(), Error> 186 | where F: FnOnce(&mut Self) -> Result<(), Error> { 187 | panic!() 188 | } 189 | 190 | fn emit_struct(&mut self, 191 | name: &str, 192 | _: usize, 193 | f: F) -> Result<(), Error> 194 | where F: FnOnce(&mut Self) -> Result<(), Error> { 195 | 196 | try!(self.emit_str(name)); 197 | 198 | f(self) 199 | } 200 | 201 | fn emit_struct_field(&mut self, 202 | f_name: &str, 203 | _: usize, 204 | f: F) -> Result<(), Error> 205 | where F: FnOnce(&mut Self) -> Result<(), Error> { 206 | 207 | try!(self.emit_str(f_name)); 208 | 209 | f(self) 210 | } 211 | 212 | fn emit_tuple(&mut self, len: usize, f: F) -> Result<(), Error> 213 | where F: FnOnce(&mut Self) -> Result<(), Error> { 214 | 215 | self.emit_seq(len, f) 216 | } 217 | 218 | fn emit_tuple_arg(&mut self, idx: usize, f: F) -> Result<(), Error> 219 | where F: FnOnce(&mut Self) -> Result<(), Error> { 220 | 221 | self.emit_seq_elt(idx, f) 222 | } 223 | 224 | fn emit_tuple_struct(&mut self, _name: &str, _len: usize, _f: F) -> Result<(), Error> 225 | where F: FnOnce(&mut Self) -> Result<(), Error> { 226 | panic!() 227 | } 228 | 229 | fn emit_tuple_struct_arg(&mut self, _f_idx: usize, _f: F) -> Result<(), Error> 230 | where F: FnOnce(&mut Self) -> Result<(), Error> { 231 | panic!() 232 | } 233 | 234 | fn emit_option(&mut self, f: F) -> Result<(), Error> 235 | where F: FnOnce(&mut Self) -> Result<(), Error> { 236 | 237 | f(self) 238 | } 239 | 240 | fn emit_option_none(&mut self) -> Result<(), Error> { 241 | self.emit_bool(false) 242 | } 243 | 244 | fn emit_option_some(&mut self, f: F) -> Result<(), Error> 245 | where F: FnOnce(&mut Self) -> Result<(), Error> { 246 | 247 | try!(self.emit_bool(true)); 248 | 249 | f(self) 250 | } 251 | 252 | fn emit_seq(&mut self, len: usize, f: F) -> Result<(), Error> 253 | where F: FnOnce(&mut Self) -> Result<(), Error> { 254 | 255 | try!(self.emit_usize(len)); 256 | 257 | f(self) 258 | } 259 | 260 | fn emit_seq_elt(&mut self, _: usize, f: F) -> Result<(), Error> 261 | where F: FnOnce(&mut Self) -> Result<(), Error> { 262 | 263 | f(self) 264 | } 265 | 266 | fn emit_map(&mut self, len: usize, f: F) -> Result<(), Error> 267 | where F: FnOnce(&mut Self) -> Result<(), Error> { 268 | 269 | try!(self.emit_usize(len)); 270 | 271 | f(self) 272 | } 273 | 274 | fn emit_map_elt_key(&mut self, _idx: usize, _f: F) -> Result<(), Error> 275 | where F: FnOnce(&mut Self) -> Result<(), Error> { 276 | panic!() 277 | } 278 | 279 | fn emit_map_elt_val(&mut self, _idx: usize, _f: F) -> Result<(), Error> 280 | where F: FnOnce(&mut Self) -> Result<(), Error> { 281 | panic!() 282 | } 283 | } 284 | 285 | /// Rustation savestate format deserializer 286 | pub struct Decoder<'a> { 287 | reader: &'a mut io::Read, 288 | } 289 | 290 | impl<'a> Decoder<'a> { 291 | pub fn new(reader: &'a mut io::Read) -> Result, Error> { 292 | 293 | let mut decoder = Decoder { 294 | reader: reader, 295 | }; 296 | 297 | // Check that the magic is valid 298 | let mut magic = [0; 4]; 299 | 300 | try!(decoder.read_bytes(&mut magic)); 301 | 302 | if magic != MAGIC { 303 | Err(Error::BadMagic) 304 | } else { 305 | Ok(decoder) 306 | } 307 | } 308 | 309 | fn read_bytes(&mut self, b: &mut [u8]) -> Result<(), Error> { 310 | match self.reader.read_exact(b) { 311 | Ok(_) => Ok(()), 312 | Err(e) => Err(Error::IoError(e)), 313 | } 314 | } 315 | 316 | /// Validate that an expected symbol matches the file value 317 | fn validate_symbol(&mut self, expected: &str) -> Result<(), Error> { 318 | use rustc_serialize::Decoder; 319 | 320 | let s = try!(self.read_str()); 321 | 322 | if s != expected { 323 | Err(Error::BadSymbol(expected.into(), s)) 324 | } else { 325 | Ok(()) 326 | } 327 | } 328 | } 329 | 330 | impl<'a> ::rustc_serialize::Decoder for Decoder<'a> { 331 | type Error = Error; 332 | 333 | fn read_nil(&mut self) -> Result<(), Error> { 334 | self.validate_symbol("nil") 335 | } 336 | 337 | fn read_usize(&mut self) -> Result { 338 | // usize are stored like u32s 339 | self.read_u32().map(|v| v as usize) 340 | } 341 | 342 | fn read_u64(&mut self) -> Result { 343 | let mut b = [0; 8]; 344 | 345 | try!(self.read_bytes(&mut b)); 346 | 347 | let mut v = 0; 348 | 349 | for &b in b.iter().rev() { 350 | v <<= 8; 351 | v |= b as u64; 352 | } 353 | 354 | Ok(v) 355 | } 356 | 357 | fn read_u32(&mut self) -> Result { 358 | let mut b = [0; 4]; 359 | 360 | try!(self.read_bytes(&mut b)); 361 | 362 | let mut v = 0; 363 | 364 | for &b in b.iter().rev() { 365 | v <<= 8; 366 | v |= b as u32; 367 | } 368 | 369 | Ok(v) 370 | } 371 | 372 | fn read_u16(&mut self) -> Result { 373 | let mut b = [0; 2]; 374 | 375 | try!(self.read_bytes(&mut b)); 376 | 377 | let mut v = 0; 378 | 379 | for &b in b.iter().rev() { 380 | v <<= 8; 381 | v |= b as u16; 382 | } 383 | 384 | Ok(v) 385 | } 386 | 387 | fn read_u8(&mut self) -> Result { 388 | let mut b = [0]; 389 | 390 | try!(self.read_bytes(&mut b)); 391 | 392 | Ok(b[0]) 393 | } 394 | 395 | fn read_isize(&mut self) -> Result { 396 | self.read_usize().map(|v| v as isize) 397 | } 398 | 399 | fn read_i64(&mut self) -> Result { 400 | self.read_u64().map(|v| v as i64) 401 | } 402 | 403 | fn read_i32(&mut self) -> Result { 404 | self.read_u32().map(|v| v as i32) 405 | } 406 | 407 | fn read_i16(&mut self) -> Result { 408 | self.read_u16().map(|v| v as i16) 409 | } 410 | 411 | fn read_i8(&mut self) -> Result { 412 | self.read_u8().map(|v| v as i8) 413 | } 414 | 415 | fn read_bool(&mut self) -> Result { 416 | match try!(self.read_u8()) { 417 | 0 => Ok(false), 418 | 1 => Ok(true), 419 | n => Err(Error::BadBool(n)), 420 | } 421 | } 422 | 423 | fn read_f64(&mut self) -> Result { 424 | panic!() 425 | } 426 | 427 | fn read_f32(&mut self) -> Result { 428 | panic!() 429 | } 430 | 431 | fn read_char(&mut self) -> Result { 432 | let c = try!(self.read_u32()); 433 | 434 | ::std::char::from_u32(c).ok_or(Error::BadChar(c)) 435 | } 436 | 437 | fn read_str(&mut self) -> Result { 438 | // First read the string length 439 | let len = try!(self.read_usize()); 440 | 441 | if len > STRING_MAX_LEN { 442 | return Err(Error::StringTooLong(len)); 443 | } 444 | 445 | let mut buf = vec![0; len]; 446 | 447 | // Now we can read the string itself 448 | try!(self.read_bytes(&mut buf)); 449 | 450 | // Finally we can convert the bytes to a String 451 | String::from_utf8(buf).map_err(|e| Error::BadString(e)) 452 | } 453 | 454 | fn read_enum(&mut self, name: &str, f: F) -> Result 455 | where F: FnOnce(&mut Self) -> Result { 456 | 457 | try!(self.validate_symbol(name)); 458 | 459 | f(self) 460 | } 461 | 462 | fn read_enum_variant(&mut self, 463 | names: &[&str], 464 | mut f: F) -> Result 465 | where F: FnMut(&mut Self, usize) -> Result { 466 | 467 | let name = try!(self.read_str()); 468 | 469 | match names.iter().position(|n| *n == name) { 470 | Some(id) => f(self, id), 471 | None => Err(Error::BadEnumVariant(name)), 472 | } 473 | } 474 | 475 | fn read_enum_variant_arg(&mut self, 476 | a_idx: usize, 477 | f: F) -> Result 478 | where F: FnOnce(&mut Self) -> Result { 479 | 480 | let id = try!(self.read_usize()); 481 | 482 | if id == a_idx { 483 | f(self) 484 | } else { 485 | Err(Error::BadEnumVariantId(a_idx, id)) 486 | } 487 | } 488 | 489 | fn read_enum_struct_variant(&mut self, 490 | _names: &[&str], 491 | _f: F) -> Result 492 | where F: FnMut(&mut Self, usize) -> Result { 493 | panic!() 494 | } 495 | 496 | fn read_enum_struct_variant_field(&mut self, 497 | _f_name: &str, 498 | _f_idx: usize, 499 | _f: F) -> Result 500 | where F: FnOnce(&mut Self) -> Result { 501 | panic!() 502 | } 503 | 504 | fn read_struct(&mut self, 505 | s_name: &str, 506 | _: usize, 507 | f: F) -> Result 508 | where F: FnOnce(&mut Self) -> Result { 509 | 510 | try!(self.validate_symbol(s_name)); 511 | 512 | f(self) 513 | } 514 | 515 | fn read_struct_field(&mut self, 516 | f_name: &str, 517 | _: usize, 518 | f: F) -> Result 519 | where F: FnOnce(&mut Self) -> Result { 520 | 521 | try!(self.validate_symbol(f_name)); 522 | 523 | f(self) 524 | } 525 | 526 | fn read_tuple(&mut self, len: usize, f: F) -> Result 527 | where F: FnOnce(&mut Self) -> Result { 528 | 529 | self.read_seq(|d, l| { 530 | if l == len { 531 | f(d) 532 | } else { 533 | Err(Error::BadTupleLength(len, l)) 534 | } 535 | }) 536 | } 537 | 538 | fn read_tuple_arg(&mut self, a_idx: usize, f: F) -> Result 539 | where F: FnOnce(&mut Self) -> Result { 540 | 541 | self.read_seq_elt(a_idx, f) 542 | } 543 | 544 | fn read_tuple_struct(&mut self, _s_name: &str, _len: usize, _f: F) -> Result 545 | where F: FnOnce(&mut Self) -> Result { 546 | panic!() 547 | } 548 | 549 | fn read_tuple_struct_arg(&mut self, _a_idx: usize, _f: F) -> Result 550 | where F: FnOnce(&mut Self) -> Result { 551 | panic!() 552 | } 553 | 554 | fn read_option(&mut self, mut f: F) -> Result 555 | where F: FnMut(&mut Self, bool) -> Result { 556 | 557 | let is_some = try!(self.read_bool()); 558 | 559 | f(self, is_some) 560 | } 561 | 562 | fn read_seq(&mut self, f: F) -> Result 563 | where F: FnOnce(&mut Self, usize) -> Result { 564 | 565 | let len = try!(self.read_usize()); 566 | 567 | f(self, len) 568 | } 569 | 570 | fn read_seq_elt(&mut self, _: usize, f: F) -> Result 571 | where F: FnOnce(&mut Self) -> Result { 572 | 573 | // XXX I assume reads are done sequentially starting from 0, 574 | // so I ignore idx 575 | 576 | f(self) 577 | } 578 | 579 | fn read_map(&mut self, _f: F) -> Result 580 | where F: FnOnce(&mut Self, usize) -> Result { 581 | panic!() 582 | } 583 | 584 | fn read_map_elt_key(&mut self, _idx: usize, _f: F) -> Result 585 | where F: FnOnce(&mut Self) -> Result { 586 | panic!() 587 | } 588 | 589 | fn read_map_elt_val(&mut self, _idx: usize, _f: F) -> Result 590 | where F: FnOnce(&mut Self) -> Result { 591 | panic!() 592 | } 593 | 594 | fn error(&mut self, err: &str) -> Error { 595 | Error::ApplicationError(err.into()) 596 | } 597 | 598 | } 599 | 600 | #[derive(Debug)] 601 | /// Error type used by the encoder and decoder 602 | pub enum Error { 603 | /// Savestate format has invalid magic 604 | BadMagic, 605 | /// Error while reading or writing the savestate 606 | IoError(io::Error), 607 | /// Encountered an unexpected symbol: `(expected, got)` 608 | BadSymbol(String, String), 609 | /// String conversion failed 610 | BadString(::std::string::FromUtf8Error), 611 | /// usize is too big to be serialized 612 | USizeOverflow(usize), 613 | /// isize is too big to be serialized 614 | ISizeOverflow(isize), 615 | /// Error reported by application 616 | ApplicationError(String), 617 | /// Attempted to encode or decode an unreasonably large string 618 | StringTooLong(usize), 619 | /// Encountered unknown enum variant while decoding 620 | BadEnumVariant(String), 621 | /// Encountered an unexpected enum variant argument id while 622 | /// decoding: `(expected, got)` 623 | BadEnumVariantId(usize, usize), 624 | /// Encountered an invalid char while decoding 625 | BadChar(u32), 626 | /// Encountered an invalid tuple length while decoding: 627 | /// `(expected, got)` 628 | BadTupleLength(usize, usize), 629 | /// Encountered an invalid bool while decoding 630 | BadBool(u8), 631 | } 632 | 633 | /// "Magic" string stored in the header to indentify the file format 634 | pub const MAGIC: &'static [u8] = b"RSXB"; 635 | /// Maximum string length accepted by the format. This is especially 636 | /// useful while decoding a bogus savestate, we don't want to allocate 637 | /// a huge string only to discover that there's a missmatch later. 638 | pub const STRING_MAX_LEN: usize = 1024 * 1024; 639 | 640 | 641 | #[test] 642 | fn test_serialize_deserialize() { 643 | use rustc_serialize::{Encodable, Decodable}; 644 | 645 | #[derive(RustcDecodable, RustcEncodable, Debug, PartialEq, Eq)] 646 | enum Enum { 647 | A, 648 | B, 649 | C, 650 | } 651 | 652 | #[derive(RustcDecodable, RustcEncodable, Debug, PartialEq, Eq)] 653 | enum EnumArgs { 654 | X(u32), 655 | Y(String, u8), 656 | Z(Vec, Enum, i16), 657 | } 658 | 659 | #[derive(RustcDecodable, RustcEncodable, Debug, PartialEq, Eq)] 660 | struct Struct { 661 | field: u32, 662 | field2: i32, 663 | } 664 | 665 | #[derive(RustcDecodable, RustcEncodable, Debug, PartialEq, Eq)] 666 | struct TestStruct { 667 | data_int: u8, 668 | data_str: String, 669 | data_vector: Vec, 670 | data_struct: Struct, 671 | data_enum: Enum, 672 | data_enum_args: EnumArgs, 673 | data_tuple: (u64, char, bool, ()), 674 | data_option: Option, 675 | } 676 | 677 | let object = TestStruct { 678 | data_int: 1, 679 | data_str: "homura".to_string(), 680 | data_vector: vec![2, 3, 4, 5], 681 | data_struct: Struct { 682 | field: 0x42, 683 | field2: -1, 684 | }, 685 | data_enum: Enum::B, 686 | data_enum_args: EnumArgs::Z(vec!['@', 'a', 'é', 'π'], Enum::C, -3), 687 | data_tuple: (1234, '!', true, ()), 688 | data_option: Some(-4335), 689 | }; 690 | 691 | let mut serialized = Vec::new(); 692 | 693 | { 694 | let mut encoder = Encoder::new(&mut serialized).unwrap(); 695 | 696 | object.encode(&mut encoder).unwrap(); 697 | } 698 | 699 | let mut reader: &[u8] = &serialized; 700 | 701 | let mut decoder = Decoder::new(&mut reader).unwrap(); 702 | 703 | let decoded: TestStruct = Decodable::decode(&mut decoder).unwrap(); 704 | 705 | assert_eq!(decoded, object); 706 | } 707 | -------------------------------------------------------------------------------- /src/vcd.rs: -------------------------------------------------------------------------------- 1 | use std::io::Write; 2 | 3 | use std::collections::HashMap; 4 | use rustation::tracer::Module; 5 | 6 | pub fn dump_trace(w: &mut Write, 7 | content: &str, 8 | bios: &str, 9 | trace: HashMap<&'static str, Module>) { 10 | 11 | write_header(w, content, bios); 12 | 13 | let mut cur_id: u32 = 0; 14 | 15 | let mut log = Vec::new(); 16 | 17 | for (name, m) in trace.iter() { 18 | let v = m.variables(); 19 | 20 | if v.is_empty() { 21 | continue; 22 | } 23 | 24 | let scope = format!("$scope module {} $end\n", name); 25 | write_str(w, &scope); 26 | 27 | for (v_name, v) in v.iter() { 28 | 29 | let id = cur_id; 30 | cur_id += 1; 31 | 32 | let var = format!("$var wire {} {} {} $end\n", 33 | v.size(), id, v_name); 34 | write_str(w, &var); 35 | 36 | // Scalars (1bit values) don't have space between the 37 | // value and identifier in the VCD dump format 38 | let is_scalar = v.size() == 1; 39 | 40 | for &(date, value) in v.log() { 41 | log.push((date, id, is_scalar, value)); 42 | } 43 | } 44 | 45 | write_str(w, "$upscope $end\n"); 46 | } 47 | 48 | // Sort log by date 49 | log.sort_by_key(|v| v.0); 50 | 51 | let mut cur_date = 0; 52 | 53 | write_str(w, "#0\n"); 54 | 55 | for &(date, id, is_scalar, value) in log.iter() { 56 | if date != cur_date { 57 | let d = format!("#{}\n", date); 58 | write_str(w, &d); 59 | cur_date = date; 60 | } 61 | 62 | let v = 63 | if is_scalar { 64 | format!("{}{}\n", value, id) 65 | } else { 66 | // Apparently only binary is supported... 67 | format!("b{:b} {}\n", value, id) 68 | }; 69 | write_str(w, &v); 70 | } 71 | } 72 | 73 | fn write_header(w: &mut Write, 74 | content: &str, 75 | bios: &str) { 76 | // Write the current date 77 | let now = ::time::now(); 78 | 79 | // Replace $ with something else not to configure the VCD 80 | // parser in the unlikely situation we end up with a VCD 81 | // directive in a file name 82 | let content = content.replace('$', " "); 83 | let bios = bios.replace('$', " "); 84 | 85 | // Put a comment at the top of the file with the content and 86 | // BIOS information 87 | let comment = format!("$comment\n Tracing {}\n BIOS: {}\n$end\n", 88 | content, bios); 89 | 90 | write_str(w, &comment); 91 | 92 | let months = [ "January", "February", 93 | "March", "April", 94 | "May", "June", 95 | "July", "August", 96 | "September", "October", 97 | "November", "December" ]; 98 | 99 | let date = format!("$date\n {} {}, {} {:02}:{:02}:{:02}\n$end\n", 100 | months[now.tm_mon as usize], 101 | now.tm_mday, 102 | now.tm_year + 1900, 103 | now.tm_hour, 104 | now.tm_min, 105 | now.tm_sec); 106 | write_str(w, &date); 107 | 108 | let version = format!("$version\n Rustation {}\n$end\n", 109 | ::rustation::VERSION); 110 | write_str(w, &version); 111 | 112 | // For now I hardcode the PSX CPU clock period 113 | let period_ps = 1_000_000_000_000f64 / 114 | ::rustation::cpu::CPU_FREQ_HZ as f64; 115 | 116 | let period_ps = period_ps.round() as u32; 117 | 118 | let timescale = format!("$timescale\n {} ps\n$end\n", 119 | period_ps); 120 | write_str(w, ×cale); 121 | } 122 | 123 | fn write_str(w: &mut Write, s: &str) { 124 | w.write_all(s.as_bytes()).unwrap(); 125 | } 126 | --------------------------------------------------------------------------------