mirror of
https://github.com/simias/rustation.git
synced 2024-05-17 11:30:43 -04:00
Convert Rustation into a library, remove all the SDL2 and OpenGL code
The OpenGL code has been ported to the rustation-libretro repository.
This commit is contained in:
parent
910c54cd8e
commit
a16dc1a9c3
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,2 +1,3 @@
|
|||
Cargo.lock
|
||||
/target
|
||||
/roms
|
||||
|
|
562
Cargo.lock
generated
562
Cargo.lock
generated
|
@ -1,562 +0,0 @@
|
|||
[root]
|
||||
name = "rustation"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"glium 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"glium_sdl2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"sdl2 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "advapi32-sys"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"winapi 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "android_glue"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"backtrace-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"dbghelp-sys 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"debug-builders 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"kernel32-sys 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "backtrace-sys"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "0.3.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "cgl"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"gleam 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cgmath"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"num 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc-serialize 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cocoa"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bitflags 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"core-graphics 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"objc 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "color_quant"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-graphics"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"core-foundation 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dbghelp-sys"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"winapi 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "debug-builders"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "dlib"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dwmapi-sys"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"winapi 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dylib"
|
||||
version = "0.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "enum_primitive"
|
||||
version = "0.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"num 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"miniz-sys 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gcc"
|
||||
version = "0.3.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"advapi32-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gdi32-sys"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"winapi 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gif"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"color_quant 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lzw 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gl_common"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gl_generator"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"khronos_api 0.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"xml-rs 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gleam"
|
||||
version = "0.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"gl_common 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"gl_generator 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"khronos_api 0.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glium"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"backtrace 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cgmath 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"gl_common 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"gl_generator 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"glutin 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"image 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"khronos_api 0.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"nalgebra 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"smallvec 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glium_sdl2"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"glium 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"sdl2 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.2.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "glutin"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"android_glue 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cgl 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cocoa 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"core-foundation 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"core-graphics 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"dwmapi-sys 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"gdi32-sys 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"gl_common 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"gl_generator 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"kernel32-sys 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"khronos_api 0.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"objc 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"osmesa-sys 0.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"shared_library 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"shell32-sys 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"user32-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"wayland-client 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"wayland-kbd 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"wayland-window 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"x11-dl 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "image"
|
||||
version = "0.3.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"byteorder 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"enum_primitive 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"gif 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"glob 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"png 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kernel32-sys"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"winapi 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "khronos_api"
|
||||
version = "0.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "0.1.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lzw"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "malloc_buf"
|
||||
version = "0.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miniz-sys"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"gcc 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mmap"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tempdir 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nalgebra"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"num 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc-serialize 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num"
|
||||
version = "0.1.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"rand 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc-serialize 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"malloc_buf 0.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "osmesa-sys"
|
||||
version = "0.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"shared_library 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "png"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bitflags 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"flate2 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.3.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"advapi32-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-serialize"
|
||||
version = "0.3.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "sdl2"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bitflags 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"sdl2-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sdl2-sys"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shared_library"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"lazy_static 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shell32-sys"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"winapi 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "tempdir"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"rand 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"kernel32-sys 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "user32-sys"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"winapi 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-client"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bitflags 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"dlib 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-kbd"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bitflags 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"mmap 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"wayland-client 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-window"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"byteorder 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tempfile 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"wayland-client 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-build"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "x11-dl"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"dylib 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xml-rs"
|
||||
version = "0.1.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bitflags 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
10
Cargo.toml
10
Cargo.toml
|
@ -1,6 +1,7 @@
|
|||
[package]
|
||||
|
||||
name = "rustation"
|
||||
# Exported as VERSION and VERSION_CSTR in the library
|
||||
version = "0.0.1"
|
||||
authors = ["Lionel Flandrin <lionel.flandrin@gmail.com>"]
|
||||
|
||||
|
@ -14,6 +15,9 @@ keywords = ["emulator", "playstation"]
|
|||
|
||||
[dependencies]
|
||||
|
||||
glium = "0.10.0"
|
||||
glium_sdl2 = "0.8.0"
|
||||
sdl2 = "0.9.1"
|
||||
shaman = "0.1.0"
|
||||
log = "0.3.4"
|
||||
|
||||
[lib]
|
||||
name = "rustation"
|
||||
crate-type = ["rlib"]
|
||||
|
|
29
README.md
29
README.md
|
@ -7,32 +7,23 @@
|
|||
|
||||
PlayStation emulator in the Rust programing language.
|
||||
|
||||
This repository only contains the source code for the core of the
|
||||
emulator. The OpenGL renderer and the libretro interface is is the
|
||||
[rustation-libretro](https://github.com/simias/rustation-libretro)
|
||||
repository.
|
||||
|
||||
The focus of this emulator is to write code that's clean, accurate and
|
||||
hopefully easy to understand. There's no plugin infrastructure, the
|
||||
emulator is monolithic.
|
||||
|
||||
Performance is pretty poor but it should be enough to run close to
|
||||
realtime on a modern CPU (there's no framelimiting implemented at the
|
||||
moment).
|
||||
|
||||
The current frontend code is built around SDL2. The plan once the
|
||||
emulator starts to become usable is to turn it into a libretro core so
|
||||
that it could be played in
|
||||
[RetroArch](https://github.com/libretro/RetroArch) for instance.
|
||||
Performance is pretty poor at the moment but it should be enough to
|
||||
run close to realtime on a modern CPU.
|
||||
|
||||
For the time being it can only boot a few games. Crash Bandicoot
|
||||
(Japanese version) is mostly playable, although I've had random
|
||||
crashes. Some other games (like Spyro) freeze after or during the
|
||||
intro.
|
||||
|
||||
The GPU rendering is implemented using OpenGL through the [glium
|
||||
API](https://github.com/tomaka/glium), the idea is to allow things
|
||||
like increased internal resolution, texture replacement and other
|
||||
enhancements down the line. Using modern OpenGL it should be possible
|
||||
to write a flexible yet reasonably accurate renderer. At least that's
|
||||
the theory, there's quite a lot of work to do before it reaches a
|
||||
playable state.
|
||||
|
||||
If you have any questions, in particular if something in the code is
|
||||
not clear or properly commented don't hesitate to fill an issue.
|
||||
|
||||
|
@ -49,8 +40,8 @@ PlayStation. We'll see if this turns out to be a good idea...
|
|||
* Basic GTE support (ported from mednafen PSX)
|
||||
* Instruction cache
|
||||
* Interrupts
|
||||
* Very basic GPU (no semi-transparency or mask bit emulation)
|
||||
* Timers
|
||||
* Basic GPU (no semi-transparency or mask bit emulation)
|
||||
* Timers (incomplete)
|
||||
* DMA
|
||||
* Debugger
|
||||
* CDROM controller (missing many commands)
|
||||
|
@ -58,7 +49,7 @@ PlayStation. We'll see if this turns out to be a good idea...
|
|||
|
||||
## Todo list
|
||||
|
||||
* Most of the GPU
|
||||
* Many things in the GPU
|
||||
* MDEC
|
||||
* SPU
|
||||
* Memory card
|
||||
|
|
278
src/bios/db.rs
Normal file
278
src/bios/db.rs
Normal file
|
@ -0,0 +1,278 @@
|
|||
//! BIOS database, lifted from mednafen
|
||||
|
||||
use cdrom::disc::Region;
|
||||
|
||||
use shaman::digest::Digest;
|
||||
use shaman::sha2::Sha256;
|
||||
|
||||
pub struct Metadata {
|
||||
pub sha256: [u8; 32],
|
||||
pub version_major: u8,
|
||||
pub version_minor: u8,
|
||||
pub region: Region,
|
||||
/// True if this dump is known to be bad
|
||||
pub known_bad: bool,
|
||||
}
|
||||
|
||||
pub fn lookup(binary: &[u8]) -> Option<&'static Metadata> {
|
||||
|
||||
let mut hasher = Sha256::new();
|
||||
|
||||
hasher.input(binary);
|
||||
|
||||
let mut sha256 = [0; 32];
|
||||
|
||||
hasher.result(&mut sha256);
|
||||
|
||||
for md in &DATABASE {
|
||||
if md.sha256 == sha256 {
|
||||
// Found match
|
||||
return Some(md);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub static DATABASE: [Metadata; 24] = [
|
||||
Metadata {
|
||||
sha256: [0xcf, 0xc1, 0xfc, 0x38, 0xeb, 0x44, 0x2f, 0x6f,
|
||||
0x80, 0x78, 0x14, 0x52, 0x11, 0x9e, 0x93, 0x1b,
|
||||
0xca, 0xe2, 0x81, 0x00, 0xc1, 0xc9, 0x7e, 0x7e,
|
||||
0x6c, 0x5f, 0x27, 0x25, 0xbb, 0xb0, 0xf8, 0xbb],
|
||||
version_major: 1,
|
||||
version_minor: 0,
|
||||
region: Region::Japan,
|
||||
known_bad: false,
|
||||
},
|
||||
Metadata {
|
||||
sha256: [0x5e, 0xb3, 0xae, 0xe4, 0x95, 0x93, 0x75, 0x58,
|
||||
0x31, 0x2b, 0x83, 0xb5, 0x43, 0x23, 0xd7, 0x6a,
|
||||
0x4a, 0x01, 0x51, 0x90, 0xde, 0xcd, 0x40, 0x51,
|
||||
0x21, 0x4f, 0x1b, 0x6d, 0xf0, 0x6a, 0xc3, 0x4b],
|
||||
version_major: 1,
|
||||
version_minor: 1,
|
||||
region: Region::Japan,
|
||||
known_bad: false,
|
||||
},
|
||||
Metadata {
|
||||
sha256: [0x42, 0xe4, 0x12, 0x4b, 0xe7, 0x62, 0x3e, 0x2e,
|
||||
0x28, 0xb1, 0xdb, 0x0d, 0x8d, 0x42, 0x65, 0x39,
|
||||
0x64, 0x6f, 0xae, 0xe4, 0x9d, 0x74, 0xb7, 0x11,
|
||||
0x66, 0xd8, 0xba, 0x5b, 0xd7, 0xc4, 0x72, 0xed],
|
||||
version_major: 2,
|
||||
version_minor: 0,
|
||||
region: Region::NorthAmerica,
|
||||
known_bad: false,
|
||||
},
|
||||
Metadata {
|
||||
sha256: [0x0a, 0xf2, 0xbe, 0x34, 0x68, 0xd3, 0x0b, 0x60,
|
||||
0x18, 0xb3, 0xc3, 0xb0, 0xd9, 0x8b, 0x8b, 0x64,
|
||||
0x34, 0x7e, 0x25, 0x5e, 0x16, 0xd8, 0x74, 0xd5,
|
||||
0x5f, 0x03, 0x63, 0x64, 0x89, 0x73, 0xdb, 0xf0],
|
||||
version_major: 2,
|
||||
version_minor: 0,
|
||||
region: Region::Europe,
|
||||
known_bad: false,
|
||||
},
|
||||
Metadata {
|
||||
sha256: [0x6f, 0x71, 0xca, 0x1e, 0x71, 0x6d, 0xa7, 0x61,
|
||||
0xdc, 0x53, 0x18, 0x7b, 0xd3, 0x9e, 0x00, 0xc2,
|
||||
0x13, 0xf5, 0x66, 0xe5, 0x50, 0x90, 0x70, 0x8f,
|
||||
0xd3, 0xe2, 0xb4, 0xb4, 0x25, 0xc8, 0xc9, 0x89],
|
||||
version_major: 2,
|
||||
version_minor: 1,
|
||||
region: Region::Japan,
|
||||
known_bad: false,
|
||||
},
|
||||
Metadata {
|
||||
sha256: [0x6a, 0xd5, 0x52, 0x1d, 0x10, 0x5a, 0x6b, 0x86,
|
||||
0x74, 0x1f, 0x1a, 0xf8, 0xda, 0x2e, 0x6e, 0xa1,
|
||||
0xc7, 0x32, 0xd3, 0x44, 0x59, 0x94, 0x06, 0x18,
|
||||
0xc7, 0x03, 0x05, 0xa1, 0x05, 0xe8, 0xec, 0x10],
|
||||
version_major: 2,
|
||||
version_minor: 1,
|
||||
region: Region::NorthAmerica,
|
||||
known_bad: false,
|
||||
},
|
||||
Metadata {
|
||||
sha256: [0x1e, 0xfb, 0x0c, 0xfc, 0x5d, 0xb8, 0xa8, 0x75,
|
||||
0x1a, 0x88, 0x4c, 0x53, 0x12, 0xe9, 0xc6, 0x26,
|
||||
0x5c, 0xa1, 0xbc, 0x58, 0x0d, 0xc0, 0xc2, 0x66,
|
||||
0x3e, 0xb2, 0xde, 0xa3, 0xbd, 0xe9, 0xfc, 0xf7],
|
||||
version_major: 2,
|
||||
version_minor: 1,
|
||||
region: Region::Europe,
|
||||
known_bad: false,
|
||||
},
|
||||
Metadata {
|
||||
sha256: [0x0c, 0x83, 0x59, 0x87, 0x0c, 0xba, 0xc0, 0xea,
|
||||
0x09, 0x1f, 0x1c, 0x87, 0xf1, 0x88, 0xcd, 0x33,
|
||||
0x2d, 0xcc, 0x70, 0x97, 0x53, 0xb9, 0x1c, 0xaf,
|
||||
0xd9, 0xfd, 0x44, 0xa4, 0xa6, 0x18, 0x81, 0x97],
|
||||
version_major: 2,
|
||||
version_minor: 2,
|
||||
region: Region::Japan,
|
||||
known_bad: false,
|
||||
},
|
||||
Metadata {
|
||||
sha256: [0x8e, 0x03, 0x83, 0x17, 0x1e, 0x67, 0xb3, 0x3e,
|
||||
0x60, 0xd5, 0xdf, 0x63, 0x94, 0xc5, 0x88, 0x43,
|
||||
0xf3, 0xb1, 0x1c, 0x7a, 0x0b, 0x97, 0xf3, 0xbf,
|
||||
0xcc, 0x43, 0x19, 0xac, 0x2d, 0x1f, 0x9d, 0x18],
|
||||
version_major: 2,
|
||||
version_minor: 2,
|
||||
region: Region::Japan,
|
||||
known_bad: true,
|
||||
},
|
||||
Metadata {
|
||||
sha256: [0x71, 0xaf, 0x94, 0xd1, 0xe4, 0x7a, 0x68, 0xc1,
|
||||
0x1e, 0x8f, 0xdb, 0x9f, 0x83, 0x68, 0x04, 0x06,
|
||||
0x01, 0x51, 0x4a, 0x42, 0xa5, 0xa3, 0x99, 0xcd,
|
||||
0xa4, 0x8c, 0x7d, 0x3b, 0xff, 0x1e, 0x99, 0xd3],
|
||||
version_major: 2,
|
||||
version_minor: 2,
|
||||
region: Region::NorthAmerica,
|
||||
known_bad: false,
|
||||
},
|
||||
Metadata {
|
||||
sha256: [0x3d, 0x06, 0xd2, 0xc4, 0x69, 0x31, 0x3c, 0x2a,
|
||||
0x21, 0x28, 0xd2, 0x4f, 0xe2, 0xe0, 0xc7, 0x1f,
|
||||
0xf9, 0x9b, 0xc2, 0x03, 0x2b, 0xe8, 0x9a, 0x82,
|
||||
0x9a, 0x62, 0x33, 0x71, 0x87, 0xf5, 0x00, 0xb7],
|
||||
version_major: 2,
|
||||
version_minor: 2,
|
||||
region: Region::Europe,
|
||||
known_bad: false,
|
||||
},
|
||||
Metadata {
|
||||
sha256: [0x40, 0x18, 0x74, 0x9b, 0x36, 0x98, 0xb8, 0x69,
|
||||
0x43, 0x87, 0xbe, 0xeb, 0xcb, 0xab, 0xfb, 0x48,
|
||||
0x47, 0x05, 0x13, 0x06, 0x68, 0x40, 0xf9, 0x44,
|
||||
0x14, 0x59, 0xee, 0x4c, 0x9f, 0x0f, 0x39, 0xbc],
|
||||
version_major: 2,
|
||||
version_minor: 2,
|
||||
region: Region::Japan,
|
||||
known_bad: false,
|
||||
},
|
||||
Metadata {
|
||||
sha256: [0x9c, 0x04, 0x21, 0x85, 0x8e, 0x21, 0x78, 0x05,
|
||||
0xf4, 0xab, 0xe1, 0x86, 0x98, 0xaf, 0xea, 0x8d,
|
||||
0x5a, 0xa3, 0x6f, 0xf0, 0x72, 0x7e, 0xb8, 0x48,
|
||||
0x49, 0x44, 0xe0, 0x0e, 0xb5, 0xe7, 0xea, 0xdb],
|
||||
version_major: 3,
|
||||
version_minor: 0,
|
||||
region: Region::Japan,
|
||||
known_bad: false,
|
||||
},
|
||||
Metadata {
|
||||
sha256: [0x11, 0x05, 0x2b, 0x64, 0x99, 0xe4, 0x66, 0xbb,
|
||||
0xf0, 0xa7, 0x09, 0xb1, 0xf9, 0xcb, 0x68, 0x34,
|
||||
0xa9, 0x41, 0x8e, 0x66, 0x68, 0x03, 0x87, 0x91,
|
||||
0x24, 0x51, 0xe9, 0x71, 0xcf, 0x8a, 0x1f, 0xef],
|
||||
version_major: 3,
|
||||
version_minor: 0,
|
||||
region: Region::NorthAmerica,
|
||||
known_bad: false,
|
||||
},
|
||||
Metadata {
|
||||
sha256: [0x1f, 0xaa, 0xa1, 0x8f, 0xa8, 0x20, 0xa0, 0x22,
|
||||
0x5e, 0x48, 0x8d, 0x9f, 0x08, 0x62, 0x96, 0xb8,
|
||||
0xe6, 0xc4, 0x6d, 0xf7, 0x39, 0x66, 0x60, 0x93,
|
||||
0x98, 0x7f, 0xf7, 0xd8, 0xfd, 0x35, 0x2c, 0x09],
|
||||
version_major: 3,
|
||||
version_minor: 0,
|
||||
region: Region::Europe,
|
||||
known_bad: false,
|
||||
},
|
||||
Metadata {
|
||||
sha256: [0x9e, 0x1f, 0x8f, 0xb4, 0xfa, 0x35, 0x6a, 0x5a,
|
||||
0xc2, 0x9d, 0x7c, 0x72, 0x09, 0x62, 0x6d, 0xcc,
|
||||
0x1b, 0x30, 0x38, 0xc0, 0xe5, 0xa8, 0x5b, 0x0e,
|
||||
0x99, 0xd1, 0xdb, 0x96, 0x92, 0x66, 0x47, 0xca],
|
||||
version_major: 3,
|
||||
version_minor: 0,
|
||||
region: Region::Europe,
|
||||
known_bad: true,
|
||||
},
|
||||
Metadata {
|
||||
sha256: [0xe9, 0x00, 0x50, 0x4d, 0x17, 0x55, 0xf0, 0x21,
|
||||
0xf8, 0x61, 0xb8, 0x2c, 0x82, 0x58, 0xc5, 0xe6,
|
||||
0x65, 0x8c, 0x7b, 0x59, 0x2f, 0x80, 0x0c, 0xcc,
|
||||
0xd9, 0x1f, 0x5d, 0x32, 0xea, 0x38, 0x0d, 0x28],
|
||||
version_major: 4,
|
||||
version_minor: 0,
|
||||
region: Region::Japan,
|
||||
known_bad: false,
|
||||
},
|
||||
Metadata {
|
||||
sha256: [0xb3, 0xaa, 0x63, 0xcf, 0x30, 0xc8, 0x1e, 0x0a,
|
||||
0x40, 0x64, 0x17, 0x40, 0xf4, 0xa4, 0x3e, 0x25,
|
||||
0xfd, 0xa0, 0xb2, 0x1b, 0x79, 0x2f, 0xa9, 0xaa,
|
||||
0xef, 0x60, 0xce, 0x16, 0x75, 0x76, 0x14, 0x79],
|
||||
version_major: 4,
|
||||
version_minor: 1,
|
||||
region: Region::Japan,
|
||||
known_bad: false,
|
||||
},
|
||||
Metadata {
|
||||
sha256: [0x39, 0xdc, 0xc1, 0xa0, 0x71, 0x70, 0x36, 0xc9,
|
||||
0xb6, 0xac, 0x52, 0xfe, 0xfd, 0x1e, 0xe7, 0xa5,
|
||||
0x7d, 0x38, 0x08, 0xe8, 0xcf, 0xbc, 0x75, 0x58,
|
||||
0x79, 0xfa, 0x68, 0x5a, 0x0a, 0x73, 0x82, 0x78],
|
||||
version_major: 4,
|
||||
version_minor: 1,
|
||||
region: Region::NorthAmerica,
|
||||
known_bad: false,
|
||||
},
|
||||
Metadata {
|
||||
sha256: [0x5e, 0x84, 0xa9, 0x48, 0x18, 0xcf, 0x52, 0x82,
|
||||
0xf4, 0x21, 0x75, 0x91, 0xfe, 0xfd, 0x88, 0xbe,
|
||||
0x36, 0xb9, 0xb1, 0x74, 0xb3, 0xcc, 0x7c, 0xb0,
|
||||
0xbc, 0xd7, 0x51, 0x99, 0xbe, 0xb4, 0x50, 0xf1],
|
||||
version_major: 4,
|
||||
version_minor: 1,
|
||||
region: Region::Europe,
|
||||
known_bad: false,
|
||||
},
|
||||
Metadata {
|
||||
sha256: [0xb2, 0x9b, 0x4b, 0x5f, 0xcd, 0xde, 0xf3, 0x69,
|
||||
0xbd, 0x66, 0x40, 0xac, 0xac, 0xda, 0x08, 0x65,
|
||||
0xe0, 0x36, 0x6f, 0xcf, 0x7e, 0xa5, 0x4e, 0x40,
|
||||
0xb2, 0xf1, 0xa8, 0x17, 0x80, 0x04, 0xf8, 0x9a],
|
||||
version_major: 4,
|
||||
version_minor: 3,
|
||||
region: Region::Japan,
|
||||
known_bad: false,
|
||||
},
|
||||
Metadata {
|
||||
sha256: [0x5c, 0x01, 0x66, 0xda, 0x24, 0xe2, 0x7d, 0xea,
|
||||
0xa8, 0x22, 0x46, 0xde, 0x8f, 0xf0, 0x10, 0x82,
|
||||
0x67, 0xfe, 0x4b, 0xb5, 0x9f, 0x6d, 0xf0, 0xfd,
|
||||
0xec, 0x50, 0xe0, 0x5e, 0x62, 0x44, 0x8c, 0xa4],
|
||||
version_major: 4,
|
||||
version_minor: 4,
|
||||
region: Region::Europe,
|
||||
known_bad: false,
|
||||
},
|
||||
Metadata {
|
||||
sha256: [0xac, 0xa9, 0xcb, 0xfa, 0x97, 0x4b, 0x93, 0x36,
|
||||
0x46, 0xba, 0xad, 0x65, 0x56, 0xa8, 0x67, 0xec,
|
||||
0xa9, 0xb8, 0x1c, 0xe6, 0x5d, 0x8a, 0xf3, 0x43,
|
||||
0xa7, 0x84, 0x3f, 0x77, 0x75, 0xb9, 0xff, 0xc8],
|
||||
version_major: 4,
|
||||
version_minor: 5,
|
||||
region: Region::NorthAmerica,
|
||||
known_bad: false,
|
||||
},
|
||||
Metadata {
|
||||
sha256: [0x42, 0x24, 0x4b, 0x0c, 0x65, 0x08, 0x21, 0x51,
|
||||
0x97, 0x51, 0xb7, 0xe7, 0x7a, 0xd1, 0xd3, 0x22,
|
||||
0x2a, 0x01, 0x25, 0xe7, 0x55, 0x86, 0xdf, 0x2b,
|
||||
0x4e, 0x84, 0xba, 0x69, 0x3b, 0x98, 0x09, 0xdc],
|
||||
version_major: 4,
|
||||
version_minor: 5,
|
||||
region: Region::Europe,
|
||||
known_bad: false,
|
||||
},
|
||||
];
|
50
src/bios/mod.rs
Normal file
50
src/bios/mod.rs
Normal file
|
@ -0,0 +1,50 @@
|
|||
use memory::Addressable;
|
||||
|
||||
use self::db::Metadata;
|
||||
|
||||
pub mod db;
|
||||
|
||||
/// BIOS image
|
||||
pub struct Bios {
|
||||
/// BIOS memory. Boxed in order not to overflow the stack at the
|
||||
/// construction site. Might change once "placement new" is
|
||||
/// available.
|
||||
data: Box<[u8; BIOS_SIZE]>,
|
||||
metadata: &'static Metadata,
|
||||
}
|
||||
|
||||
impl Bios {
|
||||
|
||||
/// Create a BIOS image from `binary` and attempt to match it with
|
||||
/// an entry in the database. If no match can be found return
|
||||
/// `None`.
|
||||
pub fn new(binary: Box<[u8; BIOS_SIZE]>) -> Option<Bios> {
|
||||
match db::lookup(&*binary) {
|
||||
Some(metadata) => Some(Bios {
|
||||
data: binary,
|
||||
metadata: metadata,
|
||||
}),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Fetch the little endian value at `offset`
|
||||
pub fn load<T: Addressable>(&self, offset: u32) -> u32 {
|
||||
let offset = offset as usize;
|
||||
|
||||
let mut r = 0;
|
||||
|
||||
for i in 0..T::size() as usize {
|
||||
r |= (self.data[offset + i] as u32) << (8 * i)
|
||||
}
|
||||
|
||||
r
|
||||
}
|
||||
|
||||
pub fn metadata(&self) -> &'static Metadata {
|
||||
self.metadata
|
||||
}
|
||||
}
|
||||
|
||||
/// BIOS images are always 512KB in length
|
||||
pub const BIOS_SIZE: usize = 512 * 1024;
|
|
@ -21,7 +21,7 @@ pub struct Disc {
|
|||
}
|
||||
|
||||
/// Disc region coding
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum Region {
|
||||
/// Japan (NTSC): SCEI
|
||||
Japan,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use memory::Addressable;
|
||||
use timekeeper::{TimeKeeper, Peripheral, Cycles};
|
||||
use memory::interrupts::{Interrupt, InterruptState};
|
||||
use timekeeper::{Peripheral, Cycles};
|
||||
use interrupt::Interrupt;
|
||||
use shared::SharedState;
|
||||
|
||||
use self::disc::{Disc, Region, XaSector};
|
||||
use self::disc::msf::Msf;
|
||||
|
@ -118,10 +119,9 @@ impl CdRom {
|
|||
}
|
||||
|
||||
pub fn load<T: Addressable>(&mut self,
|
||||
tk: &mut TimeKeeper,
|
||||
irq_state: &mut InterruptState,
|
||||
shared: &mut SharedState,
|
||||
offset: u32) -> u32 {
|
||||
self.sync(tk, irq_state);
|
||||
self.sync(shared);
|
||||
|
||||
if T::size() != 1 {
|
||||
panic!("Unhandled CDROM load ({})", T::size());
|
||||
|
@ -140,7 +140,7 @@ impl CdRom {
|
|||
0 => self.status(),
|
||||
1 => {
|
||||
if self.response.empty() {
|
||||
println!("CDROM response FIFO underflow");
|
||||
warn!("CDROM response FIFO underflow");
|
||||
}
|
||||
|
||||
self.response.pop()
|
||||
|
@ -160,12 +160,11 @@ impl CdRom {
|
|||
}
|
||||
|
||||
pub fn store<T: Addressable>(&mut self,
|
||||
tk: &mut TimeKeeper,
|
||||
irq_state: &mut InterruptState,
|
||||
shared: &mut SharedState,
|
||||
offset: u32,
|
||||
val: u32) {
|
||||
|
||||
self.sync(tk, irq_state);
|
||||
self.sync(shared);
|
||||
|
||||
if T::size() != 1 {
|
||||
panic!("Unhandled CDROM store ({})", T::size());
|
||||
|
@ -187,7 +186,7 @@ impl CdRom {
|
|||
0 => self.set_index(val),
|
||||
1 =>
|
||||
match index {
|
||||
0 => self.command(tk, val),
|
||||
0 => self.command(shared, val),
|
||||
3 => self.mixer.cd_right_to_spu_right = val,
|
||||
_ => unimplemented(),
|
||||
},
|
||||
|
@ -214,25 +213,24 @@ impl CdRom {
|
|||
}
|
||||
}
|
||||
2 => self.mixer.cd_left_to_spu_right = val,
|
||||
3 => println!("CDROM Mixer apply {:02x}", val),
|
||||
3 => debug!("CDROM Mixer apply {:02x}", val),
|
||||
_ => unimplemented(),
|
||||
},
|
||||
_ => unimplemented(),
|
||||
}
|
||||
|
||||
self.check_async_event(irq_state);
|
||||
self.check_async_event(shared);
|
||||
}
|
||||
|
||||
pub fn sync(&mut self,
|
||||
tk: &mut TimeKeeper,
|
||||
irq_state: &mut InterruptState) {
|
||||
let delta = tk.sync(Peripheral::CdRom);
|
||||
pub fn sync(&mut self, shared: &mut SharedState) {
|
||||
|
||||
let delta = shared.tk().sync(Peripheral::CdRom);
|
||||
|
||||
// Command processing is stalled if an interrupt is active
|
||||
// XXX mednafen's code also adds a delay *after* the interrupt
|
||||
// is acknowledged before the processing restarts
|
||||
if self.irq_flags == 0 {
|
||||
self.sync_commands(tk, irq_state, delta);
|
||||
self.sync_commands(shared, delta);
|
||||
}
|
||||
|
||||
// See if have a read pending
|
||||
|
@ -242,10 +240,12 @@ impl CdRom {
|
|||
// Not yet there
|
||||
delay - delta as u32
|
||||
} else {
|
||||
println!("[{}] CDROM read sector {}", tk, self.position);
|
||||
debug!("[{}] CDROM read sector {}",
|
||||
shared.tk(),
|
||||
self.position);
|
||||
|
||||
// A sector has been read from the disc
|
||||
self.sector_read(irq_state);
|
||||
self.sector_read(shared);
|
||||
|
||||
// Prepare for the next one
|
||||
self.cycles_per_sector()
|
||||
|
@ -253,21 +253,20 @@ impl CdRom {
|
|||
|
||||
self.read_state = ReadState::Reading(next_sync);
|
||||
|
||||
tk.set_next_sync_delta_if_closer(Peripheral::CdRom,
|
||||
next_sync as Cycles);
|
||||
shared.tk().set_next_sync_delta_if_sooner(Peripheral::CdRom,
|
||||
next_sync as Cycles);
|
||||
}
|
||||
}
|
||||
|
||||
/// Synchronize the command processing state machine
|
||||
fn sync_commands(&mut self,
|
||||
tk: &mut TimeKeeper,
|
||||
irq_state: &mut InterruptState,
|
||||
shared: &mut SharedState,
|
||||
delta: Cycles) {
|
||||
|
||||
let new_command_state =
|
||||
match self.command_state {
|
||||
CommandState::Idle => {
|
||||
tk.no_sync_needed(Peripheral::CdRom);
|
||||
shared.tk().no_sync_needed(Peripheral::CdRom);
|
||||
|
||||
CommandState::Idle
|
||||
}
|
||||
|
@ -282,8 +281,8 @@ impl CdRom {
|
|||
let rx_delay = rx_delay - delta;
|
||||
let irq_delay = irq_delay - delta;
|
||||
|
||||
tk.set_next_sync_delta(Peripheral::CdRom,
|
||||
rx_delay as Cycles);
|
||||
shared.tk().set_next_sync_delta(Peripheral::CdRom,
|
||||
rx_delay as Cycles);
|
||||
|
||||
CommandState::RxPending(rx_delay,
|
||||
irq_delay,
|
||||
|
@ -297,15 +296,15 @@ impl CdRom {
|
|||
// Schedule the interrupt
|
||||
let irq_delay = irq_delay - delta as u32;
|
||||
|
||||
tk.set_next_sync_delta(Peripheral::CdRom,
|
||||
irq_delay as Cycles);
|
||||
shared.tk().set_next_sync_delta(Peripheral::CdRom,
|
||||
irq_delay as Cycles);
|
||||
|
||||
CommandState::IrqPending(irq_delay, irq_code)
|
||||
} else {
|
||||
// IRQ reached
|
||||
self.trigger_irq(irq_state, irq_code);
|
||||
self.trigger_irq(shared, irq_code);
|
||||
|
||||
tk.no_sync_needed(Peripheral::CdRom);
|
||||
shared.tk().no_sync_needed(Peripheral::CdRom);
|
||||
|
||||
CommandState::Idle
|
||||
}
|
||||
|
@ -316,15 +315,15 @@ impl CdRom {
|
|||
// Not reached the interrupt yet
|
||||
let irq_delay = irq_delay - delta as u32;
|
||||
|
||||
tk.set_next_sync_delta(Peripheral::CdRom,
|
||||
irq_delay as Cycles);
|
||||
shared.tk().set_next_sync_delta(Peripheral::CdRom,
|
||||
irq_delay as Cycles);
|
||||
|
||||
CommandState::IrqPending(irq_delay, irq_code)
|
||||
} else {
|
||||
// IRQ reached
|
||||
self.trigger_irq(irq_state, irq_code);
|
||||
self.trigger_irq(shared, irq_code);
|
||||
|
||||
tk.no_sync_needed(Peripheral::CdRom);
|
||||
shared.tk().no_sync_needed(Peripheral::CdRom);
|
||||
|
||||
CommandState::Idle
|
||||
}
|
||||
|
@ -385,7 +384,7 @@ impl CdRom {
|
|||
}
|
||||
|
||||
/// Called when a new sector has been read.
|
||||
fn sector_read(&mut self, irq_state: &mut InterruptState) {
|
||||
fn sector_read(&mut self, shared: &mut SharedState) {
|
||||
if self.pending_async_event.is_some() {
|
||||
// XXX I think it should replace the current pending event
|
||||
panic!("Sector read while an async event is pending");
|
||||
|
@ -415,20 +414,20 @@ impl CdRom {
|
|||
Some((IrqCode::SectorReady,
|
||||
Fifo::from_bytes(&[self.drive_status()])));
|
||||
|
||||
self.check_async_event(irq_state);
|
||||
self.check_async_event(shared);
|
||||
|
||||
// Move on to the next segment.
|
||||
// XXX what happens when we're at the last one?
|
||||
self.position = self.position.next();
|
||||
}
|
||||
|
||||
fn check_async_event(&mut self,
|
||||
irq_state: &mut InterruptState) {
|
||||
fn check_async_event(&mut self, shared: &mut SharedState) {
|
||||
|
||||
if let Some((code, response)) = self.pending_async_event {
|
||||
if self.irq_flags == 0 {
|
||||
// Trigger async interrupt
|
||||
self.response = response;
|
||||
self.trigger_irq(irq_state, code);
|
||||
self.trigger_irq(shared, code);
|
||||
|
||||
self.pending_async_event = None;
|
||||
}
|
||||
|
@ -461,7 +460,7 @@ impl CdRom {
|
|||
self.irq_flags & self.irq_mask != 0
|
||||
}
|
||||
|
||||
fn trigger_irq(&mut self, irq_state: &mut InterruptState, irq: IrqCode) {
|
||||
fn trigger_irq(&mut self, shared: &mut SharedState, irq: IrqCode) {
|
||||
if self.irq_flags != 0 {
|
||||
// XXX No$ says that the interrupts are stacked, i.e. the
|
||||
// next interrupt will only become active once the
|
||||
|
@ -476,7 +475,7 @@ impl CdRom {
|
|||
|
||||
if !prev_irq && self.irq() {
|
||||
// Interrupt rising edge
|
||||
irq_state.assert(Interrupt::CdRom);
|
||||
shared.irq_state().assert(Interrupt::CdRom);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -542,7 +541,7 @@ impl CdRom {
|
|||
|
||||
fn push_param(&mut self, param: u8) {
|
||||
if self.params.full() {
|
||||
println!("CDROM parameter FIFO overflow");
|
||||
warn!("CDROM parameter FIFO overflow");
|
||||
}
|
||||
|
||||
self.params.push(param);
|
||||
|
@ -559,7 +558,7 @@ impl CdRom {
|
|||
}
|
||||
|
||||
fn command(&mut self,
|
||||
tk: &mut TimeKeeper,
|
||||
shared: &mut SharedState,
|
||||
cmd: u8) {
|
||||
if !self.command_state.is_idle() {
|
||||
panic!("CDROM command while controller is busy");
|
||||
|
@ -601,8 +600,8 @@ impl CdRom {
|
|||
// Schedule the interrupt if needed
|
||||
if let CommandState::RxPending(_, irq_delay, _, _)
|
||||
= self.command_state {
|
||||
tk.set_next_sync_delta(Peripheral::CdRom,
|
||||
irq_delay as Cycles);
|
||||
shared.tk().set_next_sync_delta(Peripheral::CdRom,
|
||||
irq_delay as Cycles);
|
||||
}
|
||||
} else {
|
||||
// Schedule the command to be executed once the current
|
||||
|
@ -611,8 +610,8 @@ impl CdRom {
|
|||
}
|
||||
|
||||
if let ReadState::Reading(delay) = self.read_state {
|
||||
tk.set_next_sync_delta_if_closer(Peripheral::CdRom,
|
||||
delay as Cycles);
|
||||
shared.tk().set_next_sync_delta_if_sooner(Peripheral::CdRom,
|
||||
delay as Cycles);
|
||||
}
|
||||
|
||||
// It seems that the parameters get cleared in all cases (even
|
||||
|
@ -743,7 +742,7 @@ impl CdRom {
|
|||
/// disc
|
||||
fn cmd_pause(&mut self) -> CommandState {
|
||||
if self.read_state.is_idle() {
|
||||
println!("Pause when we're not reading");
|
||||
warn!("Pause when we're not reading");
|
||||
}
|
||||
|
||||
self.on_ack = CdRom::ack_pause;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use memory::interrupts::InterruptState;
|
||||
use interrupt::InterruptState;
|
||||
|
||||
/// Coprocessor 0: System control
|
||||
pub struct Cop0 {
|
||||
|
|
|
@ -626,8 +626,8 @@ impl Gte {
|
|||
|
||||
self.lzcr = tmp.leading_zeros() as u8;
|
||||
}
|
||||
31 => println!("Write to read-only GTE data register 31"),
|
||||
_ => panic!("Unhandled GTE data register {} {:x}", reg, val),
|
||||
31 => warn!("Write to read-only GTE data register 31"),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
189
src/cpu/mod.rs
189
src/cpu/mod.rs
|
@ -3,18 +3,17 @@ mod gte;
|
|||
|
||||
use std::fmt::{Display, Formatter, Error};
|
||||
|
||||
use memory::{Interconnect, Addressable, Byte, HalfWord, Word};
|
||||
use shared::SharedState;
|
||||
use padmemcard::gamepad;
|
||||
use gpu::renderer::Renderer;
|
||||
use interrupt::InterruptState;
|
||||
|
||||
use self::cop0::{Cop0, Exception};
|
||||
use self::gte::Gte;
|
||||
use memory::{Interconnect, Addressable, Byte, HalfWord, Word};
|
||||
use timekeeper::TimeKeeper;
|
||||
use debugger::Debugger;
|
||||
use padmemcard::gamepad;
|
||||
|
||||
/// CPU state
|
||||
pub struct Cpu {
|
||||
/// Struct used to keep track of each peripheral's emulation
|
||||
/// advancement and synchronize them when needed
|
||||
tk: TimeKeeper,
|
||||
/// The program counter register: points to the next instruction
|
||||
pc: u32,
|
||||
/// Next value for the PC, used to simulate the branch delay slot
|
||||
|
@ -66,7 +65,6 @@ impl Cpu {
|
|||
let pc = 0xbfc00000;
|
||||
|
||||
Cpu {
|
||||
tk: TimeKeeper::new(),
|
||||
pc: pc,
|
||||
next_pc: pc.wrapping_add(4),
|
||||
current_pc: 0,
|
||||
|
@ -84,12 +82,26 @@ impl Cpu {
|
|||
}
|
||||
}
|
||||
|
||||
/// Run the emulator until the start of the next frame
|
||||
pub fn run_until_next_frame(&mut self,
|
||||
shared: &mut SharedState,
|
||||
renderer: &mut Renderer) {
|
||||
let frame = shared.frame();
|
||||
|
||||
while frame == shared.frame() {
|
||||
self.run_next_instruction(shared, renderer);
|
||||
}
|
||||
}
|
||||
|
||||
/// Run a single CPU instruction and return
|
||||
pub fn run_next_instruction(&mut self, debugger: &mut Debugger) {
|
||||
pub fn run_next_instruction(&mut self,
|
||||
shared: &mut SharedState,
|
||||
renderer: &mut Renderer) {
|
||||
|
||||
// Synchronize the peripherals
|
||||
if self.tk.sync_pending() {
|
||||
self.inter.sync(&mut self.tk);
|
||||
self.tk.update_sync_pending();
|
||||
if shared.tk().sync_pending() {
|
||||
self.inter.sync(shared);
|
||||
shared.tk().update_sync_pending();
|
||||
}
|
||||
|
||||
// Save the address of the current instruction to store in
|
||||
|
@ -97,7 +109,7 @@ impl Cpu {
|
|||
self.current_pc = self.pc;
|
||||
|
||||
// Debugger entrypoint: used for code breakpoints and stepping
|
||||
debugger.pc_change(self);
|
||||
shared.debugger().pc_change(self);
|
||||
|
||||
if self.current_pc % 4 != 0 {
|
||||
// PC is not correctly aligned!
|
||||
|
@ -106,7 +118,7 @@ impl Cpu {
|
|||
}
|
||||
|
||||
// Fetch instruction at PC
|
||||
let instruction = self.fetch_instruction();
|
||||
let instruction = self.fetch_instruction(shared);
|
||||
|
||||
// Increment PC to point to the next instruction. and
|
||||
// `next_pc` to the one after that. Both values can be
|
||||
|
@ -131,16 +143,16 @@ impl Cpu {
|
|||
self.branch = false;
|
||||
|
||||
// Check for pending interrupts
|
||||
if self.cop0.irq_active(self.inter.irq_state()) {
|
||||
if self.cop0.irq_active(*shared.irq_state()) {
|
||||
if instruction.is_gte_op() {
|
||||
// GTE instructions get executed even if an interrupt
|
||||
// occurs
|
||||
self.decode_and_execute(instruction, debugger);
|
||||
self.decode_and_execute(instruction, shared, renderer);
|
||||
}
|
||||
self.exception(Exception::Interrupt);
|
||||
} else {
|
||||
// No interrupt pending, run the current instruction
|
||||
self.decode_and_execute(instruction, debugger);
|
||||
self.decode_and_execute(instruction, shared, renderer);
|
||||
}
|
||||
|
||||
// Copy the output registers as input for the next instruction
|
||||
|
@ -149,7 +161,7 @@ impl Cpu {
|
|||
|
||||
/// Fetch the instruction at `current_pc` through the instruction
|
||||
/// cache
|
||||
fn fetch_instruction(&mut self) -> Instruction {
|
||||
fn fetch_instruction(&mut self, shared: &mut SharedState) -> Instruction {
|
||||
let pc = self.current_pc;
|
||||
let cc = self.inter.cache_control();
|
||||
|
||||
|
@ -183,10 +195,10 @@ impl Cpu {
|
|||
|
||||
// Fetching takes 3 cycles + 1 per instruction on
|
||||
// average.
|
||||
self.tk.tick(3);
|
||||
shared.tk().tick(3);
|
||||
|
||||
for i in index..4 {
|
||||
self.tk.tick(1);
|
||||
shared.tk().tick(1);
|
||||
|
||||
let instruction =
|
||||
Instruction(self.inter.load_instruction(cpc));
|
||||
|
@ -211,7 +223,7 @@ impl Cpu {
|
|||
|
||||
// Cache disabled, fetch directly from memory. Takes 4
|
||||
// cycles on average.
|
||||
self.tk.tick(4);
|
||||
shared.tk().tick(4);
|
||||
|
||||
Instruction(self.inter.load_instruction(pc))
|
||||
}
|
||||
|
@ -219,17 +231,18 @@ impl Cpu {
|
|||
|
||||
/// Memory read
|
||||
fn load<T: Addressable>(&mut self,
|
||||
addr: u32,
|
||||
debugger: &mut Debugger) -> u32 {
|
||||
debugger.memory_read(self, addr);
|
||||
shared: &mut SharedState,
|
||||
addr: u32) -> u32 {
|
||||
shared.debugger().memory_read(self, addr);
|
||||
|
||||
self.inter.load::<T>(&mut self.tk, addr)
|
||||
self.inter.load::<T>(shared, addr)
|
||||
}
|
||||
|
||||
/// Memory read with as little side-effect as possible. Used for
|
||||
/// debugging.
|
||||
pub fn examine<T: Addressable>(&mut self, addr: u32) -> u32 {
|
||||
self.inter.load::<T>(&mut self.tk, addr)
|
||||
|
||||
self.inter.load::<T>(&mut SharedState::new(), addr)
|
||||
}
|
||||
|
||||
/// Memory write
|
||||
|
@ -243,15 +256,16 @@ impl Cpu {
|
|||
/// value on the bus so those devices might end up using all the
|
||||
/// bytes in the Word even for smaller widths.
|
||||
fn store<T: Addressable>(&mut self,
|
||||
shared: &mut SharedState,
|
||||
renderer: &mut Renderer,
|
||||
addr: u32,
|
||||
val: u32,
|
||||
debugger: &mut Debugger) {
|
||||
debugger.memory_write(self, addr);
|
||||
val: u32) {
|
||||
shared.debugger().memory_write(self, addr);
|
||||
|
||||
if self.cop0.cache_isolated() {
|
||||
self.cache_maintenance::<T>(addr, val);
|
||||
} else {
|
||||
self.inter.store::<T>(&mut self.tk, addr, val);
|
||||
self.inter.store::<T>(shared, renderer, addr, val);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -350,8 +364,8 @@ impl Cpu {
|
|||
self.pc
|
||||
}
|
||||
|
||||
pub fn cause(&self) -> u32 {
|
||||
self.cop0.cause(self.inter.irq_state())
|
||||
pub fn cause(&self, irq_state: InterruptState) -> u32 {
|
||||
self.cop0.cause(irq_state)
|
||||
}
|
||||
|
||||
pub fn bad(&self) -> u32 {
|
||||
|
@ -375,9 +389,10 @@ impl Cpu {
|
|||
/// Decode `instruction`'s opcode and run the function
|
||||
fn decode_and_execute(&mut self,
|
||||
instruction: Instruction,
|
||||
debugger: &mut Debugger) {
|
||||
shared: &mut SharedState,
|
||||
renderer: &mut Renderer) {
|
||||
// Simulate instruction execution time.
|
||||
self.tk.tick(1);
|
||||
shared.tk().tick(1);
|
||||
|
||||
match instruction.function() {
|
||||
0b000000 => match instruction.subfunction() {
|
||||
|
@ -426,29 +441,29 @@ impl Cpu {
|
|||
0b001101 => self.op_ori(instruction),
|
||||
0b001110 => self.op_xori(instruction),
|
||||
0b001111 => self.op_lui(instruction),
|
||||
0b010000 => self.op_cop0(instruction),
|
||||
0b010000 => self.op_cop0(instruction, shared),
|
||||
0b010001 => self.op_cop1(instruction),
|
||||
0b010010 => self.op_cop2(instruction),
|
||||
0b010011 => self.op_cop3(instruction),
|
||||
0b100000 => self.op_lb(instruction, debugger),
|
||||
0b100001 => self.op_lh(instruction, debugger),
|
||||
0b100010 => self.op_lwl(instruction, debugger),
|
||||
0b100011 => self.op_lw(instruction, debugger),
|
||||
0b100100 => self.op_lbu(instruction, debugger),
|
||||
0b100101 => self.op_lhu(instruction, debugger),
|
||||
0b100110 => self.op_lwr(instruction, debugger),
|
||||
0b101000 => self.op_sb(instruction, debugger),
|
||||
0b101001 => self.op_sh(instruction, debugger),
|
||||
0b101010 => self.op_swl(instruction, debugger),
|
||||
0b101011 => self.op_sw(instruction, debugger),
|
||||
0b101110 => self.op_swr(instruction, debugger),
|
||||
0b100000 => self.op_lb(instruction, shared),
|
||||
0b100001 => self.op_lh(instruction, shared),
|
||||
0b100010 => self.op_lwl(instruction, shared),
|
||||
0b100011 => self.op_lw(instruction, shared),
|
||||
0b100100 => self.op_lbu(instruction, shared),
|
||||
0b100101 => self.op_lhu(instruction, shared),
|
||||
0b100110 => self.op_lwr(instruction, shared),
|
||||
0b101000 => self.op_sb(instruction, shared, renderer),
|
||||
0b101001 => self.op_sh(instruction, shared, renderer),
|
||||
0b101010 => self.op_swl(instruction, shared, renderer),
|
||||
0b101011 => self.op_sw(instruction, shared, renderer),
|
||||
0b101110 => self.op_swr(instruction, shared, renderer),
|
||||
0b110000 => self.op_lwc0(instruction),
|
||||
0b110001 => self.op_lwc1(instruction),
|
||||
0b110010 => self.op_lwc2(instruction, debugger),
|
||||
0b110010 => self.op_lwc2(instruction, shared),
|
||||
0b110011 => self.op_lwc3(instruction),
|
||||
0b111000 => self.op_swc0(instruction),
|
||||
0b111001 => self.op_swc1(instruction),
|
||||
0b111010 => self.op_swc2(instruction, debugger),
|
||||
0b111010 => self.op_swc2(instruction, shared, renderer),
|
||||
0b111011 => self.op_swc3(instruction),
|
||||
_ => self.op_illegal(instruction),
|
||||
}
|
||||
|
@ -456,7 +471,7 @@ impl Cpu {
|
|||
|
||||
/// Illegal instruction
|
||||
fn op_illegal(&mut self, instruction: Instruction) {
|
||||
println!("Illegal instruction {}!", instruction);
|
||||
warn!("Illegal instruction {}!", instruction);
|
||||
self.exception(Exception::IllegalInstruction);
|
||||
}
|
||||
|
||||
|
@ -984,9 +999,9 @@ impl Cpu {
|
|||
}
|
||||
|
||||
/// Coprocessor 0 opcode
|
||||
fn op_cop0(&mut self, instruction: Instruction) {
|
||||
fn op_cop0(&mut self, instruction: Instruction, shared: &mut SharedState) {
|
||||
match instruction.cop_opcode() {
|
||||
0b00000 => self.op_mfc0(instruction),
|
||||
0b00000 => self.op_mfc0(instruction, shared),
|
||||
0b00100 => self.op_mtc0(instruction),
|
||||
0b10000 => self.op_rfe(instruction),
|
||||
_ => panic!("unhandled cop0 instruction {}", instruction)
|
||||
|
@ -994,13 +1009,13 @@ impl Cpu {
|
|||
}
|
||||
|
||||
/// Move From Coprocessor 0
|
||||
fn op_mfc0(&mut self, instruction: Instruction) {
|
||||
fn op_mfc0(&mut self, instruction: Instruction, shared: &mut SharedState) {
|
||||
let cpu_r = instruction.t();
|
||||
let cop_r = instruction.d().0;
|
||||
|
||||
let v = match cop_r {
|
||||
12 => self.cop0.sr(),
|
||||
13 => self.cop0.cause(self.inter.irq_state()),
|
||||
13 => self.cop0.cause(*shared.irq_state()),
|
||||
14 => self.cop0.epc(),
|
||||
_ => panic!("Unhandled read from cop0r{}", cop_r),
|
||||
};
|
||||
|
@ -1122,7 +1137,7 @@ impl Cpu {
|
|||
/// Load Byte (signed)
|
||||
fn op_lb(&mut self,
|
||||
instruction: Instruction,
|
||||
debugger: &mut Debugger) {
|
||||
shared: &mut SharedState) {
|
||||
|
||||
let i = instruction.imm_se();
|
||||
let t = instruction.t();
|
||||
|
@ -1131,7 +1146,7 @@ impl Cpu {
|
|||
let addr = self.reg(s).wrapping_add(i);
|
||||
|
||||
// Cast as i8 to force sign extension
|
||||
let v = self.load::<Byte>(addr, debugger) as i8;
|
||||
let v = self.load::<Byte>(shared, addr) as i8;
|
||||
|
||||
// Put the load in the delay slot
|
||||
self.load = (t, v as u32);
|
||||
|
@ -1140,7 +1155,7 @@ impl Cpu {
|
|||
/// Load Halfword (signed)
|
||||
fn op_lh(&mut self,
|
||||
instruction: Instruction,
|
||||
debugger: &mut Debugger) {
|
||||
shared: &mut SharedState) {
|
||||
|
||||
let i = instruction.imm_se();
|
||||
let t = instruction.t();
|
||||
|
@ -1149,7 +1164,7 @@ impl Cpu {
|
|||
let addr = self.reg(s).wrapping_add(i);
|
||||
|
||||
// Cast as i16 to force sign extension
|
||||
let v = self.load::<HalfWord>(addr, debugger) as i16;
|
||||
let v = self.load::<HalfWord>(shared, addr) as i16;
|
||||
|
||||
// Put the load in the delay slot
|
||||
self.load = (t, v as u32);
|
||||
|
@ -1158,7 +1173,7 @@ impl Cpu {
|
|||
/// Load Word Left (little-endian only implementation)
|
||||
fn op_lwl(&mut self,
|
||||
instruction: Instruction,
|
||||
debugger: &mut Debugger) {
|
||||
shared: &mut SharedState) {
|
||||
|
||||
let i = instruction.imm_se();
|
||||
let t = instruction.t();
|
||||
|
@ -1174,7 +1189,7 @@ impl Cpu {
|
|||
// Next we load the *aligned* word containing the first
|
||||
// addressed byte
|
||||
let aligned_addr = addr & !3;
|
||||
let aligned_word = self.load::<Word>(aligned_addr, debugger);
|
||||
let aligned_word = self.load::<Word>(shared, aligned_addr);
|
||||
|
||||
// Depending on the address alignment we fetch the 1, 2, 3 or
|
||||
// 4 *most* significant bytes and put them in the target
|
||||
|
@ -1194,7 +1209,7 @@ impl Cpu {
|
|||
/// Load Word
|
||||
fn op_lw(&mut self,
|
||||
instruction: Instruction,
|
||||
debugger: &mut Debugger) {
|
||||
shared: &mut SharedState) {
|
||||
|
||||
let i = instruction.imm_se();
|
||||
let t = instruction.t();
|
||||
|
@ -1204,7 +1219,7 @@ impl Cpu {
|
|||
|
||||
// Address must be 32bit aligned
|
||||
if addr % 4 == 0 {
|
||||
let v = self.load::<Word>(addr, debugger);
|
||||
let v = self.load::<Word>(shared, addr);
|
||||
|
||||
// Put the load in the delay slot
|
||||
self.load = (t, v);
|
||||
|
@ -1216,7 +1231,7 @@ impl Cpu {
|
|||
/// Load Byte Unsigned
|
||||
fn op_lbu(&mut self,
|
||||
instruction: Instruction,
|
||||
debugger: &mut Debugger) {
|
||||
shared: &mut SharedState) {
|
||||
|
||||
let i = instruction.imm_se();
|
||||
let t = instruction.t();
|
||||
|
@ -1224,7 +1239,7 @@ impl Cpu {
|
|||
|
||||
let addr = self.reg(s).wrapping_add(i);
|
||||
|
||||
let v = self.load::<Byte>(addr, debugger);
|
||||
let v = self.load::<Byte>(shared, addr);
|
||||
|
||||
// Put the load in the delay slot
|
||||
self.load = (t, v as u32);
|
||||
|
@ -1233,7 +1248,7 @@ impl Cpu {
|
|||
/// Load Halfword Unsigned
|
||||
fn op_lhu(&mut self,
|
||||
instruction: Instruction,
|
||||
debugger: &mut Debugger) {
|
||||
shared: &mut SharedState) {
|
||||
|
||||
let i = instruction.imm_se();
|
||||
let t = instruction.t();
|
||||
|
@ -1243,7 +1258,7 @@ impl Cpu {
|
|||
|
||||
// Address must be 16bit aligned
|
||||
if addr % 2 == 0 {
|
||||
let v = self.load::<HalfWord>(addr, debugger);
|
||||
let v = self.load::<HalfWord>(shared, addr);
|
||||
|
||||
// Put the load in the delay slot
|
||||
self.load = (t, v as u32);
|
||||
|
@ -1255,7 +1270,7 @@ impl Cpu {
|
|||
/// Load Word Right (little-endian only implementation)
|
||||
fn op_lwr(&mut self,
|
||||
instruction: Instruction,
|
||||
debugger: &mut Debugger) {
|
||||
shared: &mut SharedState) {
|
||||
|
||||
let i = instruction.imm_se();
|
||||
let t = instruction.t();
|
||||
|
@ -1271,7 +1286,7 @@ impl Cpu {
|
|||
// Next we load the *aligned* word containing the first
|
||||
// addressed byte
|
||||
let aligned_addr = addr & !3;
|
||||
let aligned_word = self.load::<Word>(aligned_addr, debugger);
|
||||
let aligned_word = self.load::<Word>(shared, aligned_addr);
|
||||
|
||||
// Depending on the address alignment we fetch the 1, 2, 3 or
|
||||
// 4 *least* significant bytes and put them in the target
|
||||
|
@ -1291,7 +1306,8 @@ impl Cpu {
|
|||
/// Store Byte
|
||||
fn op_sb(&mut self,
|
||||
instruction: Instruction,
|
||||
debugger: &mut Debugger) {
|
||||
shared: &mut SharedState,
|
||||
renderer: &mut Renderer) {
|
||||
|
||||
let i = instruction.imm_se();
|
||||
let t = instruction.t();
|
||||
|
@ -1300,13 +1316,14 @@ impl Cpu {
|
|||
let addr = self.reg(s).wrapping_add(i);
|
||||
let v = self.reg(t);
|
||||
|
||||
self.store::<Byte>(addr, v, debugger);
|
||||
self.store::<Byte>(shared, renderer, addr, v);
|
||||
}
|
||||
|
||||
/// Store Halfword
|
||||
fn op_sh(&mut self,
|
||||
instruction: Instruction,
|
||||
debugger: &mut Debugger) {
|
||||
shared: &mut SharedState,
|
||||
renderer: &mut Renderer) {
|
||||
|
||||
let i = instruction.imm_se();
|
||||
let t = instruction.t();
|
||||
|
@ -1317,7 +1334,7 @@ impl Cpu {
|
|||
|
||||
// Address must be 16bit aligned
|
||||
if addr % 2 == 0 {
|
||||
self.store::<HalfWord>(addr, v, debugger);
|
||||
self.store::<HalfWord>(shared, renderer, addr, v);
|
||||
} else {
|
||||
self.exception(Exception::StoreAddressError);
|
||||
}
|
||||
|
@ -1326,7 +1343,8 @@ impl Cpu {
|
|||
/// Store Word Left (little-endian only implementation)
|
||||
fn op_swl(&mut self,
|
||||
instruction: Instruction,
|
||||
debugger: &mut Debugger) {
|
||||
shared: &mut SharedState,
|
||||
renderer: &mut Renderer) {
|
||||
|
||||
let i = instruction.imm_se();
|
||||
let t = instruction.t();
|
||||
|
@ -1338,7 +1356,7 @@ impl Cpu {
|
|||
let aligned_addr = addr & !3;
|
||||
// Load the current value for the aligned word at the target
|
||||
// address
|
||||
let cur_mem = self.load::<Word>(aligned_addr, debugger);
|
||||
let cur_mem = self.load::<Word>(shared, aligned_addr);
|
||||
|
||||
let mem =
|
||||
match addr & 3 {
|
||||
|
@ -1349,13 +1367,14 @@ impl Cpu {
|
|||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
self.store::<Word>(addr, mem, debugger);
|
||||
self.store::<Word>(shared, renderer, addr, mem);
|
||||
}
|
||||
|
||||
/// Store Word
|
||||
fn op_sw(&mut self,
|
||||
instruction: Instruction,
|
||||
debugger: &mut Debugger) {
|
||||
shared: &mut SharedState,
|
||||
renderer: &mut Renderer) {
|
||||
|
||||
let i = instruction.imm_se();
|
||||
let t = instruction.t();
|
||||
|
@ -1366,7 +1385,7 @@ impl Cpu {
|
|||
|
||||
// Address must be 32bit aligned
|
||||
if addr % 4 == 0 {
|
||||
self.store::<Word>(addr, v, debugger);
|
||||
self.store::<Word>(shared, renderer, addr, v);
|
||||
} else {
|
||||
self.exception(Exception::StoreAddressError);
|
||||
}
|
||||
|
@ -1375,7 +1394,8 @@ impl Cpu {
|
|||
/// Store Word Right (little-endian only implementation)
|
||||
fn op_swr(&mut self,
|
||||
instruction: Instruction,
|
||||
debugger: &mut Debugger) {
|
||||
shared: &mut SharedState,
|
||||
renderer: &mut Renderer) {
|
||||
|
||||
let i = instruction.imm_se();
|
||||
let t = instruction.t();
|
||||
|
@ -1387,7 +1407,7 @@ impl Cpu {
|
|||
let aligned_addr = addr & !3;
|
||||
// Load the current value for the aligned word at the target
|
||||
// address
|
||||
let cur_mem = self.load::<Word>(aligned_addr, debugger);
|
||||
let cur_mem = self.load::<Word>(shared, aligned_addr);
|
||||
|
||||
let mem =
|
||||
match addr & 3 {
|
||||
|
@ -1398,7 +1418,7 @@ impl Cpu {
|
|||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
self.store::<Word>(addr, mem, debugger);
|
||||
self.store::<Word>(shared, renderer, addr, mem);
|
||||
}
|
||||
|
||||
/// Load Word in Coprocessor 0
|
||||
|
@ -1416,7 +1436,7 @@ impl Cpu {
|
|||
/// Load Word in Coprocessor 2
|
||||
fn op_lwc2(&mut self,
|
||||
instruction: Instruction,
|
||||
debugger: &mut Debugger) {
|
||||
shared: &mut SharedState) {
|
||||
let i = instruction.imm_se();
|
||||
let cop_r = instruction.t().0;
|
||||
let s = instruction.s();
|
||||
|
@ -1425,7 +1445,7 @@ impl Cpu {
|
|||
|
||||
// Address must be 32bit aligned
|
||||
if addr % 4 == 0 {
|
||||
let v = self.load::<Word>(addr, debugger);
|
||||
let v = self.load::<Word>(shared, addr);
|
||||
|
||||
// Send to coprocessor
|
||||
self.gte.set_data(cop_r, v);
|
||||
|
@ -1455,7 +1475,8 @@ impl Cpu {
|
|||
/// Store Word in Coprocessor 2
|
||||
fn op_swc2(&mut self,
|
||||
instruction: Instruction,
|
||||
debugger: &mut Debugger) {
|
||||
shared: &mut SharedState,
|
||||
renderer: &mut Renderer) {
|
||||
let i = instruction.imm_se();
|
||||
let cop_r = instruction.t().0;
|
||||
let s = instruction.s();
|
||||
|
@ -1465,7 +1486,7 @@ impl Cpu {
|
|||
|
||||
// Address must be 32bit aligned
|
||||
if addr % 4 == 0 {
|
||||
self.store::<Word>(addr, v, debugger);
|
||||
self.store::<Word>(shared, renderer, addr, v);
|
||||
} else {
|
||||
self.exception(Exception::LoadAddressError);
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@ use std::io::{Read, Write};
|
|||
use debugger::Debugger;
|
||||
use cpu::Cpu;
|
||||
use memory::{Byte, HalfWord, Word};
|
||||
use interrupt::InterruptState;
|
||||
|
||||
use self::reply::Reply;
|
||||
|
||||
mod reply;
|
||||
|
@ -16,12 +18,12 @@ pub struct GdbRemote {
|
|||
|
||||
impl GdbRemote {
|
||||
pub fn new(listener: &TcpListener) -> GdbRemote {
|
||||
println!("Debugger waiting for gdb connection...");
|
||||
info!("Debugger waiting for gdb connection...");
|
||||
|
||||
let remote =
|
||||
match listener.accept() {
|
||||
Ok((stream, sockaddr)) => {
|
||||
println!("Connection from {}", sockaddr);
|
||||
info!("Connection from {}", sockaddr);
|
||||
stream
|
||||
}
|
||||
Err(e) => panic!("Accept failed: {}", e),
|
||||
|
@ -75,7 +77,7 @@ impl GdbRemote {
|
|||
match r {
|
||||
Ok(b) => b,
|
||||
Err(e) => {
|
||||
println!("GDB remote error: {}", e);
|
||||
warn!("GDB remote error: {}", e);
|
||||
return PacketResult::EndOfStream;
|
||||
}
|
||||
};
|
||||
|
@ -104,7 +106,7 @@ impl GdbRemote {
|
|||
state = State::WaitForCheckSum2(b);
|
||||
}
|
||||
None => {
|
||||
println!("Got invalid GDB checksum char {}",
|
||||
warn!("Got invalid GDB checksum char {}",
|
||||
byte);
|
||||
return PacketResult::BadChecksum(packet);
|
||||
}
|
||||
|
@ -116,8 +118,8 @@ impl GdbRemote {
|
|||
let expected = (c1 << 4) | c2;
|
||||
|
||||
if expected != csum {
|
||||
println!("Got invalid GDB checksum: {:x} {:x}",
|
||||
expected, csum);
|
||||
warn!("Got invalid GDB checksum: {:x} {:x}",
|
||||
expected, csum);
|
||||
return PacketResult::BadChecksum(packet);
|
||||
}
|
||||
|
||||
|
@ -125,8 +127,8 @@ impl GdbRemote {
|
|||
return PacketResult::Ok(packet);
|
||||
}
|
||||
None => {
|
||||
println!("Got invalid GDB checksum char {}",
|
||||
byte);
|
||||
warn!("Got invalid GDB checksum char {}",
|
||||
byte);
|
||||
return PacketResult::BadChecksum(packet);
|
||||
}
|
||||
}
|
||||
|
@ -134,14 +136,14 @@ impl GdbRemote {
|
|||
}
|
||||
}
|
||||
|
||||
println!("GDB remote end of stream");
|
||||
warn!("GDB remote end of stream");
|
||||
return PacketResult::EndOfStream;
|
||||
}
|
||||
|
||||
/// Acknowledge packet reception
|
||||
fn ack(&mut self) -> GdbResult {
|
||||
if let Err(e) = self.remote.write(b"+") {
|
||||
println!("Couldn't send ACK to GDB remote: {}", e);
|
||||
warn!("Couldn't send ACK to GDB remote: {}", e);
|
||||
Err(())
|
||||
} else {
|
||||
Ok(())
|
||||
|
@ -151,7 +153,7 @@ impl GdbRemote {
|
|||
/// Request packet retransmission
|
||||
fn nack(&mut self) -> GdbResult {
|
||||
if let Err(e) = self.remote.write(b"-") {
|
||||
println!("Couldn't send NACK to GDB remote: {}", e);
|
||||
warn!("Couldn't send NACK to GDB remote: {}", e);
|
||||
Err(())
|
||||
} else {
|
||||
Ok(())
|
||||
|
@ -191,7 +193,7 @@ impl GdbRemote {
|
|||
// do we do if it's less than we expected, retransmit?
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => {
|
||||
println!("Couldn't send data to GDB remote: {}", e);
|
||||
warn!("Couldn't send data to GDB remote: {}", e);
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
|
@ -239,7 +241,9 @@ impl GdbRemote {
|
|||
cpu.lo(),
|
||||
cpu.hi(),
|
||||
cpu.bad(),
|
||||
cpu.cause(),
|
||||
// XXX We should figure out a way to get the real
|
||||
// irq_state over here...
|
||||
cpu.cause(InterruptState::new()),
|
||||
cpu.pc() ] {
|
||||
reply.push_u32(r);
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ impl Debugger {
|
|||
Err(e) => panic!("Couldn't bind GDB server TCP socket: {}", e),
|
||||
};
|
||||
|
||||
println!("Waiting for debugger on {}", bind_to);
|
||||
info!("Waiting for debugger on {}", bind_to);
|
||||
|
||||
Debugger {
|
||||
listener: listener,
|
||||
|
@ -141,7 +141,7 @@ impl Debugger {
|
|||
// executes a `load32 at` address 0, should we break? Also,
|
||||
// should we mask the region?
|
||||
if self.read_watchpoints.contains(&addr) {
|
||||
println!("Read watchpoint triggered at 0x{:08x}", addr);
|
||||
info!("Read watchpoint triggered at 0x{:08x}", addr);
|
||||
self.debug(cpu);
|
||||
}
|
||||
}
|
||||
|
@ -165,7 +165,7 @@ impl Debugger {
|
|||
pub fn memory_write(&mut self, cpu: &mut Cpu, addr: u32) {
|
||||
// XXX: same remark as memory_read for unaligned stores
|
||||
if self.write_watchpoints.contains(&addr) {
|
||||
println!("Write watchpoint triggered at 0x{:08x}", addr);
|
||||
info!("Write watchpoint triggered at 0x{:08x}", addr);
|
||||
self.debug(cpu);
|
||||
}
|
||||
}
|
||||
|
|
786
src/gpu/mod.rs
786
src/gpu/mod.rs
File diff suppressed because it is too large
Load diff
|
@ -1,771 +0,0 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
use sdl2;
|
||||
use sdl2::video::GLProfile;
|
||||
|
||||
use glium_sdl2;
|
||||
|
||||
use glium::{Program, VertexBuffer, Surface, DrawParameters, Rect, Blend};
|
||||
use glium::index;
|
||||
use glium::uniforms::{MagnifySamplerFilter, MinifySamplerFilter};
|
||||
use glium::program::ProgramCreationInput;
|
||||
use glium::texture::{Texture2d, UncompressedFloatFormat, MipmapsOption};
|
||||
use glium::texture::{Texture2dDataSource, RawImage2d, ClientFormat};
|
||||
|
||||
use super::{TextureDepth, BlendMode, DisplayDepth};
|
||||
use super::{VRAM_WIDTH_PIXELS, VRAM_HEIGHT};
|
||||
|
||||
pub struct Renderer {
|
||||
/// Glium display
|
||||
window: glium_sdl2::SDL2Facade,
|
||||
/// Texture used as the target (bound to a framebuffer object) for
|
||||
/// the render commands.
|
||||
fb_out: Texture2d,
|
||||
/// Framebuffer horizontal resolution (native: 1024)
|
||||
fb_out_x_res: u16,
|
||||
/// Framebuffer vertical resolution (native: 512)
|
||||
fb_out_y_res: u16,
|
||||
/// Texture used to store the VRAM for texture mapping
|
||||
fb_texture: Texture2d,
|
||||
/// Program used to process draw commands
|
||||
command_program: Program,
|
||||
/// Permanent vertex buffer used to store pending draw commands
|
||||
command_vertex_buffer: VertexBuffer<CommandVertex>,
|
||||
/// Current number or vertices in the command buffer
|
||||
nvertices: u32,
|
||||
/// List of queued draw commands. Each command contains a
|
||||
/// primitive type (triangle or line) and a number of *vertices*
|
||||
/// to be drawn from the `vertex_buffer`.
|
||||
command_queue: Vec<(index::PrimitiveType, u32)>,
|
||||
/// Current draw command. Will be pushed onto the `command_queue`
|
||||
/// if a new command needs to be started.
|
||||
current_command: (index::PrimitiveType, u32),
|
||||
/// Current draw offset
|
||||
offset: (i16, i16),
|
||||
/// Parameters for draw commands
|
||||
command_params: DrawParameters<'static>,
|
||||
/// Program used to display the visible part of the framebuffer
|
||||
output_program: Program,
|
||||
/// Program used to upload new textures into the framebuffer
|
||||
image_load_program: Program,
|
||||
}
|
||||
|
||||
impl Renderer {
|
||||
|
||||
pub fn new(sdl_context: &sdl2::Sdl) -> Renderer {
|
||||
use glium_sdl2::DisplayBuild;
|
||||
// Size of the framebuffer emulating the Playstation VRAM for
|
||||
// draw commands. Can be increased.
|
||||
let fb_out_x_res = VRAM_WIDTH_PIXELS as u32;
|
||||
let fb_out_y_res = VRAM_HEIGHT as u32;
|
||||
// Internal format for the framebuffer. The real console uses
|
||||
// RGB 555 + one "mask" bit which we store as alpha.
|
||||
let fb_out_format = UncompressedFloatFormat::U5U5U5U1;
|
||||
|
||||
// Video output resolution ("TV screen" size). It's not
|
||||
// directly related to the internal framebuffer resolution.
|
||||
// Only a game-configured fraction of the framebuffer is
|
||||
// displayed at any given moment, several display modes are
|
||||
// supported by the console.
|
||||
let output_width = 1024;
|
||||
let output_height = 768;
|
||||
|
||||
let video_subsystem = sdl_context.video().unwrap();
|
||||
|
||||
let gl_attr = video_subsystem.gl_attr();
|
||||
gl_attr.set_context_version(3, 3);
|
||||
gl_attr.set_context_profile(GLProfile::Core);
|
||||
|
||||
// XXX Debug context is likely to be slower, we should make
|
||||
// that configurable at some point.
|
||||
gl_attr.set_context_flags().debug().set();
|
||||
|
||||
let window =
|
||||
video_subsystem.window("Rustation", output_width, output_height)
|
||||
.position_centered()
|
||||
.build_glium()
|
||||
.ok().expect("Can't create SDL2 window");
|
||||
|
||||
// Build the program used to render GPU primitives in the
|
||||
// framebuffer
|
||||
let command_vs_src = include_str!("shaders/command_vertex.glsl");
|
||||
let command_fs_src = include_str!("shaders/command_fragment.glsl");
|
||||
|
||||
let command_program =
|
||||
Program::new(&window,
|
||||
ProgramCreationInput::SourceCode {
|
||||
vertex_shader: &command_vs_src,
|
||||
tessellation_control_shader: None,
|
||||
tessellation_evaluation_shader: None,
|
||||
geometry_shader: None,
|
||||
fragment_shader: &command_fs_src,
|
||||
transform_feedback_varyings: None,
|
||||
// Don't mess with the color correction
|
||||
outputs_srgb: true,
|
||||
uses_point_size: false,
|
||||
}).unwrap();
|
||||
|
||||
let command_vertex_buffer =
|
||||
VertexBuffer::empty_persistent(&window,
|
||||
VERTEX_BUFFER_LEN as usize)
|
||||
.unwrap();
|
||||
|
||||
// In order to have the line size scale with the internal
|
||||
// resolution upscale we need to compute the upscaling ratio.
|
||||
//
|
||||
// XXX I only use the y scaling factor since I assume that
|
||||
// both dimensions are scaled by the same ratio. Otherwise
|
||||
// we'd have to change the line thickness depending on its
|
||||
// angle and that would be tricky.
|
||||
let scaling_factor = fb_out_y_res as f32 / 512.;
|
||||
|
||||
let command_params = DrawParameters {
|
||||
// Default to full screen
|
||||
scissor: Some(Rect {
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
width: fb_out_x_res,
|
||||
height: fb_out_y_res,
|
||||
}),
|
||||
line_width: Some(scaling_factor),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// The framebuffer starts uninitialized
|
||||
let default_color = Some((0.5, 0.2, 0.1, 0.0));
|
||||
|
||||
let fb_out = Texture2d::empty_with_format(&window,
|
||||
fb_out_format,
|
||||
MipmapsOption::NoMipmap,
|
||||
fb_out_x_res,
|
||||
fb_out_y_res).unwrap();
|
||||
|
||||
fb_out.as_surface().clear(None, default_color, false, None, None);
|
||||
|
||||
|
||||
// The texture framebuffer is always at the native resolution
|
||||
// since textures can be paletted so no filtering is possible
|
||||
// on the raw data.
|
||||
let fb_texture =
|
||||
Texture2d::empty_with_format(&window,
|
||||
fb_out_format,
|
||||
MipmapsOption::NoMipmap,
|
||||
VRAM_WIDTH_PIXELS as u32,
|
||||
VRAM_HEIGHT as u32).unwrap();
|
||||
|
||||
fb_texture.as_surface().clear(None, default_color, false, None, None);
|
||||
|
||||
// Build the program used to render the framebuffer onto the output
|
||||
let output_vs_src = include_str!("shaders/output_vertex.glsl");
|
||||
let output_fs_src = include_str!("shaders/output_fragment.glsl");
|
||||
|
||||
let output_program =
|
||||
Program::new(&window,
|
||||
ProgramCreationInput::SourceCode {
|
||||
vertex_shader: &output_vs_src,
|
||||
tessellation_control_shader: None,
|
||||
tessellation_evaluation_shader: None,
|
||||
geometry_shader: None,
|
||||
fragment_shader: &output_fs_src,
|
||||
transform_feedback_varyings: None,
|
||||
outputs_srgb: true,
|
||||
uses_point_size: false,
|
||||
}).unwrap();
|
||||
|
||||
// Build the program used to upload textures to the
|
||||
// framebuffer
|
||||
let load_vs_src = include_str!("shaders/image_load_vertex.glsl");
|
||||
let load_fs_src = include_str!("shaders/image_load_fragment.glsl");
|
||||
|
||||
let image_load_program =
|
||||
Program::new(&window,
|
||||
ProgramCreationInput::SourceCode {
|
||||
vertex_shader: &load_vs_src,
|
||||
tessellation_control_shader: None,
|
||||
tessellation_evaluation_shader: None,
|
||||
geometry_shader: None,
|
||||
fragment_shader: &load_fs_src,
|
||||
transform_feedback_varyings: None,
|
||||
outputs_srgb: true,
|
||||
uses_point_size: false,
|
||||
}).unwrap();
|
||||
|
||||
Renderer {
|
||||
window: window,
|
||||
fb_out: fb_out,
|
||||
fb_out_x_res: fb_out_x_res as u16,
|
||||
fb_out_y_res: fb_out_y_res as u16,
|
||||
fb_texture: fb_texture,
|
||||
command_program: command_program,
|
||||
command_vertex_buffer: command_vertex_buffer,
|
||||
nvertices: 0,
|
||||
command_queue: Vec::new(),
|
||||
current_command: (index::PrimitiveType::TrianglesList, 0),
|
||||
offset: (0, 0),
|
||||
command_params: command_params,
|
||||
output_program: output_program,
|
||||
image_load_program: image_load_program,
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a triangle to the draw buffer
|
||||
pub fn push_triangle(&mut self, vertices: &[CommandVertex; 3]) {
|
||||
self.push_primitive(index::PrimitiveType::TrianglesList,
|
||||
vertices);
|
||||
}
|
||||
|
||||
/// Add a quad to the draw buffer
|
||||
pub fn push_quad(&mut self, vertices: &[CommandVertex; 4]) {
|
||||
self.push_triangle(&[vertices[0], vertices[1], vertices[2]]);
|
||||
self.push_triangle(&[vertices[1], vertices[2], vertices[3]]);
|
||||
}
|
||||
|
||||
/// Add a line to the draw buffer
|
||||
pub fn push_line(&mut self, vertices: &[CommandVertex; 2]) {
|
||||
self.push_primitive(index::PrimitiveType::LinesList,
|
||||
vertices);
|
||||
}
|
||||
|
||||
/// Add a primitive to the draw buffer
|
||||
fn push_primitive(&mut self,
|
||||
primitive_type: index::PrimitiveType,
|
||||
vertices: &[CommandVertex]) {
|
||||
let primitive_vertices = vertices.len() as u32;
|
||||
|
||||
// Make sure we have enough room left to queue the vertex. We
|
||||
// need to push two triangles to draw a quad, so 6 vertex
|
||||
if self.nvertices + primitive_vertices > VERTEX_BUFFER_LEN {
|
||||
// The vertex attribute buffers are full, force an early
|
||||
// draw
|
||||
self.draw();
|
||||
}
|
||||
|
||||
let (mut cmd_type, mut cmd_len) = self.current_command;
|
||||
|
||||
if primitive_type != cmd_type {
|
||||
// We have to change the primitive type. Push the current
|
||||
// command onto the queue and start a new one.
|
||||
if cmd_len > 0 {
|
||||
self.command_queue.push(self.current_command);
|
||||
}
|
||||
|
||||
cmd_type = primitive_type;
|
||||
cmd_len = 0;
|
||||
}
|
||||
|
||||
// Copy the vertices into the vertex buffer
|
||||
let start = self.nvertices as usize;
|
||||
let end = start + primitive_vertices as usize;
|
||||
|
||||
let slice = self.command_vertex_buffer.slice(start..end).unwrap();
|
||||
slice.write(vertices);
|
||||
|
||||
self.nvertices += primitive_vertices;
|
||||
self.current_command = (cmd_type, cmd_len + primitive_vertices);
|
||||
}
|
||||
|
||||
/// Fill a rectangle in memory with the given color. This method
|
||||
/// ignores the mask bit, the drawing area and the drawing offset.
|
||||
pub fn fill_rect(&mut self,
|
||||
color: [u8; 3],
|
||||
top: u16, left: u16,
|
||||
bottom: u16, right: u16) {
|
||||
// Flush any pending draw commands
|
||||
self.draw();
|
||||
|
||||
let top = top as i16;
|
||||
let left = left as i16;
|
||||
// Fill rect is inclusive
|
||||
let bottom = bottom as i16;
|
||||
let right = right as i16;
|
||||
|
||||
// Build monochrome command vertex
|
||||
let build_vertex = |x: i16, y: i16| -> CommandVertex {
|
||||
CommandVertex::new([x, y],
|
||||
color,
|
||||
BlendMode::None,
|
||||
[0; 2],
|
||||
[0; 2],
|
||||
[0; 2],
|
||||
TextureDepth::T4Bpp,
|
||||
false)
|
||||
};
|
||||
|
||||
let vertices =
|
||||
VertexBuffer::new(&self.window,
|
||||
&[build_vertex(left, top),
|
||||
build_vertex(right, top),
|
||||
build_vertex(left, bottom),
|
||||
build_vertex(right, bottom)])
|
||||
.unwrap();
|
||||
|
||||
|
||||
let mut surface = self.fb_out.as_surface();
|
||||
|
||||
let uniforms = uniform! {
|
||||
offset: [0, 0],
|
||||
fb_texture: &self.fb_texture,
|
||||
};
|
||||
|
||||
surface.draw(&vertices,
|
||||
&index::NoIndices(index::PrimitiveType::TriangleStrip),
|
||||
&self.command_program,
|
||||
&uniforms,
|
||||
&DrawParameters { ..Default::default() })
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
/// Set the value of the uniform draw offset
|
||||
pub fn set_draw_offset(&mut self, x: i16, y: i16) {
|
||||
// Force draw for the primitives with the current offset
|
||||
self.draw();
|
||||
|
||||
self.offset = (x, y);
|
||||
}
|
||||
|
||||
/// Set the drawing area. Coordinates are offsets in the
|
||||
/// PlayStation VRAM
|
||||
pub fn set_drawing_area(&mut self,
|
||||
left: u16, top: u16,
|
||||
right: u16, bottom: u16) {
|
||||
// Render any pending primitives
|
||||
self.draw();
|
||||
|
||||
let (left, top) = self.scale_coords(left, top);
|
||||
let (right, bottom) = self.scale_coords(right, bottom);
|
||||
|
||||
if left > right || bottom > top {
|
||||
// XXX What should we do here? This happens often because
|
||||
// the drawing area is set in two successive calls to set
|
||||
// the top_left and then bottom_right so the intermediate
|
||||
// value is often wrong.
|
||||
self.command_params.scissor = Some(Rect {
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
width: 0,
|
||||
height: 0,
|
||||
});
|
||||
} else {
|
||||
// Width and height are inclusive
|
||||
let width = right - left + 1;
|
||||
let height = top - bottom + 1;
|
||||
|
||||
self.command_params.scissor = Some(Rect {
|
||||
left: left,
|
||||
bottom: bottom,
|
||||
width: width,
|
||||
height: height,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw the buffered commands and reset the buffers
|
||||
pub fn draw(&mut self) {
|
||||
|
||||
// Push the last pending command if needed
|
||||
let (_, cmd_len) = self.current_command;
|
||||
|
||||
if cmd_len > 0 {
|
||||
self.command_queue.push(self.current_command);
|
||||
}
|
||||
|
||||
if self.command_queue.is_empty() {
|
||||
// Nothing to be done
|
||||
return;
|
||||
}
|
||||
|
||||
let mut surface = self.fb_out.as_surface();
|
||||
|
||||
let mut vertex_pos = 0;
|
||||
|
||||
let uniforms = uniform! {
|
||||
offset: [self.offset.0 as i32, self.offset.1 as i32],
|
||||
fb_texture: &self.fb_texture,
|
||||
};
|
||||
|
||||
for &(cmd_type, cmd_len) in &self.command_queue {
|
||||
let start = vertex_pos;
|
||||
let end = start + cmd_len as usize;
|
||||
|
||||
let vertices =
|
||||
self.command_vertex_buffer.slice(start..end)
|
||||
.unwrap();
|
||||
|
||||
surface.draw(vertices,
|
||||
&index::NoIndices(cmd_type),
|
||||
&self.command_program,
|
||||
&uniforms,
|
||||
&self.command_params).unwrap();
|
||||
|
||||
vertex_pos = end;
|
||||
}
|
||||
|
||||
// Reset the buffers
|
||||
self.nvertices = 0;
|
||||
self.command_queue.clear();
|
||||
self.current_command = (index::PrimitiveType::TrianglesList, 0);
|
||||
}
|
||||
|
||||
/// Draw the buffered commands and refresh the video output.
|
||||
pub fn display(&mut self,
|
||||
fb_x: u16, fb_y: u16,
|
||||
width: u16, height: u16,
|
||||
depth: DisplayDepth) {
|
||||
// Draw any pending commands
|
||||
self.draw();
|
||||
|
||||
let params = DrawParameters {
|
||||
blend: Blend::alpha_blending(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let mut frame = self.window.draw();
|
||||
|
||||
// We sample `fb_out` onto the screen
|
||||
let uniforms = uniform! {
|
||||
fb: &self.fb_out,
|
||||
alpha: 1.0f32,
|
||||
depth_24bpp: match depth {
|
||||
DisplayDepth::D15Bits => 0,
|
||||
DisplayDepth::D24Bits => 1,
|
||||
},
|
||||
};
|
||||
|
||||
/// Vertex definition for the video output program
|
||||
#[derive(Copy, Clone)]
|
||||
struct Vertex {
|
||||
/// Vertex position on the screen
|
||||
position: [f32; 2],
|
||||
/// Corresponding coordinate in the framebuffer
|
||||
fb_coord: [u16; 2],
|
||||
}
|
||||
|
||||
implement_vertex!(Vertex, position, fb_coord);
|
||||
|
||||
let fb_x_start = fb_x;
|
||||
let fb_x_end = fb_x + width;
|
||||
// OpenGL puts the Y axis in the opposite direction compared
|
||||
// to the PlayStation GPU coordinate system so we must start
|
||||
// at the bottom here.
|
||||
let fb_y_start = fb_y + height;
|
||||
let fb_y_end = fb_y;
|
||||
|
||||
// We render a single quad containing the texture to the
|
||||
// screen
|
||||
let vertices =
|
||||
VertexBuffer::new(&self.window,
|
||||
&[Vertex { position: [-1.0, -1.0],
|
||||
fb_coord: [fb_x_start, fb_y_start] },
|
||||
Vertex { position: [1.0, -1.0],
|
||||
fb_coord: [fb_x_end, fb_y_start] },
|
||||
Vertex { position: [-1.0, 1.0],
|
||||
fb_coord: [fb_x_start, fb_y_end] },
|
||||
Vertex { position: [1.0, 1.0],
|
||||
fb_coord: [fb_x_end, fb_y_end] }])
|
||||
.unwrap();
|
||||
|
||||
frame.draw(&vertices,
|
||||
&index::NoIndices(index::PrimitiveType::TriangleStrip),
|
||||
&self.output_program,
|
||||
&uniforms,
|
||||
¶ms).unwrap();
|
||||
|
||||
// Draw the full framebuffer at the bottom right transparently
|
||||
// We sample `fb_out` onto the screen
|
||||
let vertices =
|
||||
VertexBuffer::new(&self.window,
|
||||
&[Vertex { position: [0., -1.0],
|
||||
fb_coord: [0, 512] },
|
||||
Vertex { position: [1.0, -1.0],
|
||||
fb_coord: [1024, 512] },
|
||||
Vertex { position: [0.0, -0.5],
|
||||
fb_coord: [0, 0] },
|
||||
Vertex { position: [1.0, -0.5],
|
||||
fb_coord: [1024, 0] }])
|
||||
.unwrap();
|
||||
|
||||
// Let's use nearest neighbour interpolation for the VRAM
|
||||
// dump, doesn't make a lot of sense to interpolate linearly
|
||||
// here
|
||||
let sampler =
|
||||
self.fb_out.sampled()
|
||||
.magnify_filter(MagnifySamplerFilter::Nearest)
|
||||
.minify_filter(MinifySamplerFilter::Nearest);
|
||||
|
||||
let uniforms = uniform! {
|
||||
fb: sampler,
|
||||
alpha: 0.7f32,
|
||||
depth_24bpp: 0,
|
||||
};
|
||||
|
||||
frame.draw(&vertices,
|
||||
&index::NoIndices(index::PrimitiveType::TriangleStrip),
|
||||
&self.output_program,
|
||||
&uniforms,
|
||||
¶ms).unwrap();
|
||||
|
||||
// Flip the buffers and display the new frame
|
||||
frame.finish().unwrap();
|
||||
}
|
||||
|
||||
/// Convert coordinates in the PlayStation framebuffer to
|
||||
/// coordinates in our potentially scaled OpenGL
|
||||
/// framebuffer. Coordinates are rounded to the nearest pixel.
|
||||
fn scale_coords(&self, x: u16, y: u16) -> (u32, u32) {
|
||||
// OpenGL has (0, 0) at the bottom left, the PSX at the top
|
||||
// left so we need to complement the y coordinate
|
||||
let y = !y & 0x1ff;
|
||||
|
||||
let x = (x as u32 * self.fb_out_x_res as u32 + 512) / 1024;
|
||||
let y = (y as u32 * self.fb_out_y_res as u32 + 256) / 512;
|
||||
|
||||
(x, y)
|
||||
}
|
||||
|
||||
/// Load an image (texture, palette, ...) into the VRAM
|
||||
pub fn load_image(&mut self, load_buffer: LoadBuffer) {
|
||||
// XXX must take Mask bit into account. Should also change the
|
||||
// alpha mode to conserve the source alpha only (no blending).
|
||||
|
||||
// First we must run any pending command
|
||||
self.draw();
|
||||
|
||||
// Target coordinates in VRAM
|
||||
let (x, y) = load_buffer.top_left();
|
||||
let width = load_buffer.width();
|
||||
let height = load_buffer.height();
|
||||
|
||||
let image = load_buffer.into_texture(&self.window);
|
||||
|
||||
let params = DrawParameters {
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
/// Vertex definition for the video output program
|
||||
#[derive(Copy, Clone)]
|
||||
struct Vertex {
|
||||
/// Vertex position in VRAM
|
||||
position: [u16; 2],
|
||||
/// Coordinate in the loaded image
|
||||
image_coord: [u16; 2],
|
||||
}
|
||||
|
||||
implement_vertex!(Vertex, position, image_coord);
|
||||
|
||||
let x_start = x;
|
||||
let x_end = x + width;
|
||||
let y_start = y;
|
||||
let y_end = y + height;
|
||||
|
||||
// We render a single quad containing the image into the
|
||||
// framebuffer
|
||||
let vertices =
|
||||
VertexBuffer::new(&self.window,
|
||||
&[Vertex { position: [x_start, y_start],
|
||||
image_coord: [0, 0] },
|
||||
Vertex { position: [x_end, y_start],
|
||||
image_coord: [width, 0] },
|
||||
Vertex { position: [x_start, y_end],
|
||||
image_coord: [0, height] },
|
||||
Vertex { position: [x_end, y_end],
|
||||
image_coord: [width, height] }])
|
||||
.unwrap();
|
||||
|
||||
// First we copy the data to the texture VRAM
|
||||
let uniforms = uniform! {
|
||||
image: &image,
|
||||
};
|
||||
|
||||
let mut surface = self.fb_texture.as_surface();
|
||||
|
||||
surface.draw(&vertices,
|
||||
&index::NoIndices(index::PrimitiveType::TriangleStrip),
|
||||
&self.image_load_program,
|
||||
&uniforms,
|
||||
¶ms).unwrap();
|
||||
|
||||
// We'll also write the data to `fb_out` in case the game
|
||||
// tries to upload some image directly into the displayed
|
||||
// framebuffer.
|
||||
let mut surface = self.fb_out.as_surface();
|
||||
|
||||
surface.draw(&vertices,
|
||||
&index::NoIndices(index::PrimitiveType::TriangleStrip),
|
||||
&self.image_load_program,
|
||||
&uniforms,
|
||||
¶ms).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
/// Maximum number of vertex that can be stored in an attribute
|
||||
/// buffers
|
||||
const VERTEX_BUFFER_LEN: u32 = 64 * 1024;
|
||||
|
||||
/// Vertex definition used by the draw commands
|
||||
#[derive(Copy,Clone,Debug)]
|
||||
pub struct CommandVertex {
|
||||
/// Position in PlayStation VRAM coordinates
|
||||
position: [i16; 2],
|
||||
/// RGB color, 8bits per component
|
||||
color: [u8; 3],
|
||||
/// Texture page (base offset in VRAM used for texture lookup)
|
||||
texture_page: [u16; 2],
|
||||
/// Texture coordinates within the page
|
||||
texture_coord: [u16; 2],
|
||||
/// Color Look-Up Table (palette) coordinates in VRAM
|
||||
clut: [u16; 2],
|
||||
/// Blending mode: 0: no texture, 1: raw-texture, 2: texture-blended
|
||||
texture_blend_mode: u8,
|
||||
/// Right shift from 16bits: 0 for 16bpp textures, 1 for 8bpp, 2
|
||||
/// for 4bpp
|
||||
depth_shift: u8,
|
||||
/// True if dithering is enabled for this primitive
|
||||
dither: u8,
|
||||
}
|
||||
|
||||
implement_vertex!(CommandVertex, position, color,
|
||||
texture_page, texture_coord, clut, texture_blend_mode,
|
||||
depth_shift, dither);
|
||||
|
||||
impl CommandVertex {
|
||||
pub fn new(pos: [i16; 2],
|
||||
color: [u8; 3],
|
||||
blend_mode: BlendMode,
|
||||
texture_page: [u16; 2],
|
||||
texture_coord: [u16; 2],
|
||||
clut: [u16; 2],
|
||||
texture_depth: TextureDepth,
|
||||
dither: bool) -> CommandVertex {
|
||||
|
||||
let blend_mode =
|
||||
match blend_mode {
|
||||
BlendMode::None => 0,
|
||||
BlendMode::Raw => 1,
|
||||
BlendMode::Blended => 2,
|
||||
};
|
||||
|
||||
let depth_shift =
|
||||
match texture_depth {
|
||||
TextureDepth::T4Bpp => 2,
|
||||
TextureDepth::T8Bpp => 1,
|
||||
TextureDepth::T16Bpp => 0,
|
||||
};
|
||||
|
||||
CommandVertex {
|
||||
position: pos,
|
||||
color: color,
|
||||
texture_page: texture_page,
|
||||
texture_coord: texture_coord,
|
||||
texture_blend_mode: blend_mode,
|
||||
clut: clut,
|
||||
depth_shift: depth_shift,
|
||||
dither: dither as u8,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Buffer used to store images while they're loaded into the GPU
|
||||
/// word-by-word through GP0
|
||||
pub struct LoadBuffer {
|
||||
/// Buffer containing the individual pixels, top-left to
|
||||
/// bottom-right
|
||||
buf: Vec<u16>,
|
||||
/// Width in pixels
|
||||
width: u16,
|
||||
/// Height
|
||||
height: u16,
|
||||
/// Coordinate of the top-left corner of target
|
||||
/// location in VRAM
|
||||
top_left: (u16, u16),
|
||||
}
|
||||
|
||||
impl LoadBuffer {
|
||||
pub fn new(x: u16, y: u16, width: u16, height: u16) -> LoadBuffer {
|
||||
let size = (width as usize) * (height as usize);
|
||||
|
||||
// Round capacity up to the next even value since we always
|
||||
// upload two pixels at a time
|
||||
let size = (size + 1) & !1;
|
||||
|
||||
LoadBuffer {
|
||||
buf: Vec::with_capacity(size),
|
||||
width: width,
|
||||
height: height,
|
||||
top_left: (x, y),
|
||||
}
|
||||
}
|
||||
|
||||
/// Build an empty LoadBuffer expecting no data
|
||||
pub fn null() -> LoadBuffer {
|
||||
LoadBuffer {
|
||||
buf: Vec::new(),
|
||||
width: 0,
|
||||
height: 0,
|
||||
top_left: (0, 0),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn width(&self) -> u16 {
|
||||
self.width
|
||||
}
|
||||
|
||||
pub fn height(&self) -> u16 {
|
||||
self.height
|
||||
}
|
||||
|
||||
pub fn top_left(&self) -> (u16, u16) {
|
||||
self.top_left
|
||||
}
|
||||
|
||||
/// Called a when a new word is received in GP0. Extract the two
|
||||
/// pixels and store them in the buffer
|
||||
pub fn push_word(&mut self, word: u32) {
|
||||
// Unfortunately OpenGL puts the color components the other
|
||||
// way around: it uses BGRA 5551 while the PSX uses MRGB 1555
|
||||
// so we have to shuffle the components around
|
||||
fn shuffle_components(color: u16) -> u16 {
|
||||
let alpha = color >> 15;
|
||||
let r = (color >> 10) & 0x1f;
|
||||
let g = (color >> 5) & 0x1f;
|
||||
let b = color & 0x1f;
|
||||
|
||||
alpha | (r << 1) | (g << 6) | (b << 11)
|
||||
}
|
||||
|
||||
let p0 = shuffle_components(word as u16);
|
||||
let p1 = shuffle_components((word >> 16) as u16);
|
||||
|
||||
self.buf.push(p0);
|
||||
self.buf.push(p1);
|
||||
}
|
||||
|
||||
pub fn into_texture(self, window: &glium_sdl2::SDL2Facade) -> Texture2d {
|
||||
Texture2d::with_format(window,
|
||||
self,
|
||||
UncompressedFloatFormat::U5U5U5U1,
|
||||
MipmapsOption::NoMipmap).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Texture2dDataSource<'a> for LoadBuffer {
|
||||
type Data = u16;
|
||||
|
||||
fn into_raw(self) -> RawImage2d<'a, u16> {
|
||||
let width = self.width as u32;
|
||||
let height = self.height as u32;
|
||||
|
||||
let mut data = self.buf;
|
||||
|
||||
// We might have one pixel too many because of the padding to
|
||||
// 32bits during the upload. Glium will panic if `data` is
|
||||
// bigger than expected.
|
||||
data.truncate((width * height) as usize);
|
||||
|
||||
RawImage2d {
|
||||
data: Cow::Owned(data),
|
||||
width: width,
|
||||
height: height,
|
||||
format: ClientFormat::U5U5U5U1,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,149 +0,0 @@
|
|||
#version 330 core
|
||||
|
||||
uniform sampler2D fb_texture;
|
||||
|
||||
in vec3 frag_shading_color;
|
||||
// Texture page: base offset for texture lookup.
|
||||
flat in uvec2 frag_texture_page;
|
||||
// Texel coordinates within the page. Interpolated by OpenGL.
|
||||
in vec2 frag_texture_coord;
|
||||
// Clut coordinates in VRAM
|
||||
flat in uvec2 frag_clut;
|
||||
// 0: no texture, 1: raw-texture, 2: blended
|
||||
flat in int frag_texture_blend_mode;
|
||||
// 0: 16bpp (no clut), 1: 8bpp, 2: 4bpp
|
||||
flat in int frag_depth_shift;
|
||||
// 0: No dithering, 1: dithering enabled
|
||||
flat in int frag_dither;
|
||||
|
||||
out vec4 frag_color;
|
||||
|
||||
const int BLEND_MODE_NO_TEXTURE = 0;
|
||||
const int BLEND_MODE_RAW_TEXTURE = 1;
|
||||
const int BLEND_MODE_TEXTURE_BLEND = 2;
|
||||
|
||||
// Read a 16bpp pixel in VRAM
|
||||
vec4 vram_get_pixel(int x, int y) {
|
||||
return texelFetch(fb_texture, ivec2(x, 511 - y), 0);
|
||||
}
|
||||
|
||||
// Take a normalized color and convert it into a 16bit 1555 ABGR
|
||||
// integer in the format used internally by the Playstation GPU.
|
||||
int rebuild_color(vec4 color) {
|
||||
int a = int(floor(color.a + 0.5));
|
||||
int r = int(floor(color.r * 31. + 0.5));
|
||||
int g = int(floor(color.g * 31. + 0.5));
|
||||
int b = int(floor(color.b * 31. + 0.5));
|
||||
|
||||
return (a << 15) | (b << 10) | (g << 5) | r;
|
||||
}
|
||||
|
||||
// Texture color 0x0000 is special in the Playstation GPU, it denotes
|
||||
// a fully transparent texel (even for opaque draw commands). If you
|
||||
// want black you have to use an opaque draw command and use `0x8000`
|
||||
// instead.
|
||||
bool is_transparent(vec4 texel) {
|
||||
return rebuild_color(texel) == 0;
|
||||
}
|
||||
|
||||
// PlayStation dithering pattern. The offset is selected based on the
|
||||
// pixel position in VRAM, by blocks of 4x4 pixels. The value is added
|
||||
// to the 8bit color components before they're truncated to 5 bits.
|
||||
const int dither_pattern[16] =
|
||||
int[16](-4, 0, -3, 1,
|
||||
2, -2, 3, -1,
|
||||
-3, 1, -4, 0,
|
||||
3, -1, 2, -2);
|
||||
|
||||
void main() {
|
||||
|
||||
vec4 color;
|
||||
|
||||
if (frag_texture_blend_mode == BLEND_MODE_NO_TEXTURE) {
|
||||
color = vec4(frag_shading_color, 0.0);
|
||||
} else {
|
||||
// Look up texture
|
||||
|
||||
// Number of texel per VRAM 16bit "pixel" for the current depth
|
||||
int pix_per_hw = 1 << frag_depth_shift;
|
||||
|
||||
// 8 and 4bpp textures contain several texels per 16bit VRAM
|
||||
// "pixel"
|
||||
float tex_x_float = frag_texture_coord.x / float(pix_per_hw);
|
||||
|
||||
// Texture pages are limited to 256x256 pixels
|
||||
int tex_x = int(tex_x_float) & 0xff;
|
||||
int tex_y = int(frag_texture_coord.y) & 0xff;
|
||||
|
||||
tex_x += int(frag_texture_page.x);
|
||||
tex_y += int(frag_texture_page.y);
|
||||
|
||||
vec4 texel = vram_get_pixel(tex_x, tex_y);
|
||||
|
||||
if (frag_depth_shift > 0) {
|
||||
// 8 and 4bpp textures are paletted so we need to lookup the
|
||||
// real color in the CLUT
|
||||
|
||||
// First we need to convert the normalized color back to the
|
||||
// internal integer format since it's not a real color but 2 or
|
||||
// 4 CLUT indexes
|
||||
int icolor = rebuild_color(texel);
|
||||
|
||||
// A little bitwise magic to get the index in the CLUT. 4bpp
|
||||
// textures have 4 texels per VRAM "pixel", 8bpp have 2. We need
|
||||
// to shift the current color to find the proper part of the
|
||||
// halfword and then mask away the rest.
|
||||
|
||||
// Bits per pixel (4 or 8)
|
||||
int bpp = 16 >> frag_depth_shift;
|
||||
|
||||
// 0xf for 4bpp, 0xff for 8bpp
|
||||
int mask = ((1 << bpp) - 1);
|
||||
|
||||
// 0...3 for 4bpp, 1...2 for 8bpp
|
||||
int align = int(fract(tex_x_float) * pix_per_hw);
|
||||
|
||||
// 0, 4, 8 or 12 for 4bpp, 0 or 8 for 8bpp
|
||||
int shift = (align * bpp);
|
||||
|
||||
// Finally we have the index in the CLUT
|
||||
int index = (icolor >> shift) & mask;
|
||||
|
||||
int clut_x = int(frag_clut.x) + index;
|
||||
int clut_y = int(frag_clut.y);
|
||||
|
||||
// Look up the real color for the texel in the CLUT
|
||||
texel = vram_get_pixel(clut_x, clut_y);
|
||||
}
|
||||
|
||||
if (is_transparent(texel)) {
|
||||
// Fully transparent texel, discard
|
||||
discard;
|
||||
}
|
||||
|
||||
if (frag_texture_blend_mode == BLEND_MODE_RAW_TEXTURE) {
|
||||
color = texel;
|
||||
} else /* BLEND_MODE_TEXTURE_BLEND */ {
|
||||
// Blend the texel with the shading color. `frag_shading_color`
|
||||
// is multiplied by two so that it can be used to darken or
|
||||
// lighten the texture as needed. The result of the
|
||||
// multiplication should be saturated to 1.0 (0xff) but I think
|
||||
// OpenGL will take care of that since the output buffer holds
|
||||
// integers. The alpha/mask bit bit is taken directly from the
|
||||
// texture however.
|
||||
color = vec4(frag_shading_color * 2. * texel.rgb, texel.a);
|
||||
}
|
||||
}
|
||||
|
||||
// Dithering
|
||||
int x_dither = int(gl_FragCoord.x) & 3;
|
||||
int y_dither = int(gl_FragCoord.y) & 3;
|
||||
|
||||
// The multiplication by `frag_dither` will result in
|
||||
// `dither_offset` being 0 if dithering is disabled
|
||||
int dither_offset = dither_pattern[y_dither * 4 + x_dither] * frag_dither;
|
||||
|
||||
float dither = float(dither_offset) / 255.;
|
||||
|
||||
frag_color = color + vec4(dither, dither, dither, 0.);
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
#version 330 core
|
||||
|
||||
// Vertex shader for rendering GPU draw commands in the framebuffer
|
||||
|
||||
in ivec2 position;
|
||||
in vec3 color;
|
||||
in uvec2 texture_page;
|
||||
in uvec2 texture_coord;
|
||||
in uvec2 clut;
|
||||
in int texture_blend_mode;
|
||||
in int depth_shift;
|
||||
in int dither;
|
||||
|
||||
// Drawing offset
|
||||
uniform ivec2 offset;
|
||||
|
||||
out vec3 frag_shading_color;
|
||||
flat out uvec2 frag_texture_page;
|
||||
out vec2 frag_texture_coord;
|
||||
flat out uvec2 frag_clut;
|
||||
flat out int frag_texture_blend_mode;
|
||||
flat out int frag_depth_shift;
|
||||
flat out int frag_dither;
|
||||
|
||||
void main() {
|
||||
ivec2 pos = position + offset;
|
||||
|
||||
// Convert VRAM coordinates (0;1023, 0;511) into OpenGL coordinates
|
||||
// (-1;1, -1;1)
|
||||
float xpos = (float(pos.x) / 512) - 1.0;
|
||||
// VRAM puts 0 at the top, OpenGL at the bottom, we must mirror
|
||||
// vertically
|
||||
float ypos = 1.0 - (float(pos.y) / 256);
|
||||
|
||||
gl_Position.xyzw = vec4(xpos, ypos, 0.0, 1.0);
|
||||
|
||||
// Glium doesn't support "normalized" for now
|
||||
frag_shading_color = vec3(color / 255.);
|
||||
|
||||
// Let OpenGL interpolate the texel position
|
||||
frag_texture_coord = vec2(texture_coord);
|
||||
|
||||
frag_texture_page = texture_page;
|
||||
frag_clut = clut;
|
||||
frag_texture_blend_mode = texture_blend_mode;
|
||||
frag_depth_shift = depth_shift;
|
||||
frag_dither = dither;
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
#version 330 core
|
||||
|
||||
uniform sampler2D image;
|
||||
|
||||
in vec2 frag_image_coord;
|
||||
|
||||
out vec4 frag_color;
|
||||
|
||||
void main() {
|
||||
// The interpolation *must* be set to nearest! We can't filter
|
||||
// textures here because they could be paletted
|
||||
frag_color = texelFetch(image, ivec2(frag_image_coord), 0);
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
#version 330 core
|
||||
|
||||
// Vertex shader for uploading textures into the framebuffer
|
||||
|
||||
in uvec2 position;
|
||||
in vec2 image_coord;
|
||||
|
||||
out vec2 frag_image_coord;
|
||||
|
||||
void main() {
|
||||
// Convert VRAM position into OpenGL coordinates
|
||||
float xpos = (float(position.x) / 512) - 1.0;
|
||||
float ypos = 1.0 - (float(position.y) / 256);
|
||||
|
||||
gl_Position.xyzw = vec4(xpos, ypos, 0.0, 1.0);
|
||||
|
||||
frag_image_coord = vec2(image_coord);
|
||||
}
|
|
@ -1,62 +0,0 @@
|
|||
#version 330 core
|
||||
|
||||
// We're sampling from the internal framebuffer texture
|
||||
uniform sampler2D fb;
|
||||
uniform float alpha;
|
||||
// Framebuffer sampling: 0: Normal 16bpp mode, 1: Use 24bpp mode
|
||||
uniform int depth_24bpp;
|
||||
|
||||
in vec2 frag_fb_coord;
|
||||
|
||||
out vec4 frag_color;
|
||||
|
||||
// Take a normalized color and convert it into a 16bit 1555 ABGR
|
||||
// integer in the format used internally by the Playstation GPU.
|
||||
int rebuild_color(vec4 color) {
|
||||
int a = int(floor(color.a + 0.5));
|
||||
int r = int(floor(color.r * 31. + 0.5));
|
||||
int g = int(floor(color.g * 31. + 0.5));
|
||||
int b = int(floor(color.b * 31. + 0.5));
|
||||
|
||||
return (a << 15) | (b << 10) | (g << 5) | r;
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec3 color;
|
||||
|
||||
if (depth_24bpp == 0) {
|
||||
// Use the regular 16bpp mode, fetch directly from the framebuffer
|
||||
// texture. The alpha/mask bit is ignored here.
|
||||
color = texture(fb, frag_fb_coord).rgb;
|
||||
} else {
|
||||
// In this mode we have to interpret the framebuffer as containing
|
||||
// 24bit RGB values instead of the usual 16bits 1555.
|
||||
|
||||
ivec2 fb_size = textureSize(fb, 0);
|
||||
|
||||
int x_24 = int(frag_fb_coord.x * float(fb_size.x));
|
||||
int y = int((frag_fb_coord.y * float(fb_size.y)));
|
||||
|
||||
// The 24bit color is stored over two 16bit pixels, convert the
|
||||
// coordinates
|
||||
int x_16 = (x_24 * 3) / 2;
|
||||
|
||||
int col0 = rebuild_color(texelFetch(fb, ivec2(x_16, y), 0));
|
||||
int col1 = rebuild_color(texelFetch(fb, ivec2(x_16 + 1, y), 0));
|
||||
|
||||
int col = (col1 << 16) | col0;
|
||||
|
||||
// If we're drawing an odd 24 bit pixel we're starting in the
|
||||
// middle of a 16bit cell so we need to adjust accordingly.
|
||||
col >>= 8 * (x_24 & 1);
|
||||
|
||||
// Finally we can extract and normalize the 24bit pixel
|
||||
float b = float((col >> 16) & 0xff) / 255.;
|
||||
float g = float((col >> 8) & 0xff) / 255.;
|
||||
float r = float(col & 0xff) / 255.;
|
||||
|
||||
color = vec3(r, g, b);
|
||||
}
|
||||
|
||||
frag_color = vec4(color, alpha);
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
#version 330 core
|
||||
|
||||
// Vertex shader for rendering GPU draw commands in the framebuffer
|
||||
|
||||
in vec2 position;
|
||||
in uvec2 fb_coord;
|
||||
|
||||
out vec2 frag_fb_coord;
|
||||
|
||||
void main() {
|
||||
gl_Position.xyzw = vec4(position, 0.0, 1.0);
|
||||
|
||||
// Convert the PlayStation framebuffer coordinate into an OpenGL
|
||||
// texture coordinate
|
||||
float fb_x_coord = float(fb_coord.x) / 1024;
|
||||
float fb_y_coord = 1.0 - (float(fb_coord.y) / 512);
|
||||
|
||||
frag_fb_coord = vec2(fb_x_coord, fb_y_coord);
|
||||
}
|
113
src/gpu/renderer.rs
Normal file
113
src/gpu/renderer.rs
Normal file
|
@ -0,0 +1,113 @@
|
|||
pub trait Renderer {
|
||||
fn set_draw_offset(&mut self, x: i16, y: i16);
|
||||
fn set_draw_area(&mut self, top_left: (u16, u16), dimensions: (u16, u16));
|
||||
|
||||
fn set_display_mode(&mut self,
|
||||
top_left: (u16, u16),
|
||||
resolution: (u16, u16),
|
||||
depth_24bpp: bool);
|
||||
|
||||
fn push_line(&mut self, &PrimitiveAttributes, &[Vertex; 2]);
|
||||
fn push_triangle(&mut self, &PrimitiveAttributes, &[Vertex; 3]);
|
||||
fn push_quad(&mut self, &PrimitiveAttributes, &[Vertex; 4]);
|
||||
|
||||
fn fill_rect(&mut self,
|
||||
color: [u8; 3],
|
||||
top_left: (u16, u16),
|
||||
dimensions: (u16, u16));
|
||||
|
||||
fn load_image(&mut self,
|
||||
top_left: (u16, u16),
|
||||
dimensions: (u16, u16),
|
||||
pixel_buffer: &[u16]);
|
||||
}
|
||||
|
||||
pub struct Vertex {
|
||||
pub position: [i16; 2],
|
||||
pub color: [u8; 3],
|
||||
pub texture_coord: [u16; 2],
|
||||
}
|
||||
|
||||
impl Vertex {
|
||||
pub fn new(position: [i16; 2], color: [u8; 3]) -> Vertex {
|
||||
Vertex {
|
||||
position: position,
|
||||
color: color,
|
||||
// Unused
|
||||
texture_coord: [0, 0],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_textured(position: [i16; 2],
|
||||
color: [u8; 3],
|
||||
texture_coord: [u16; 2]) -> Vertex {
|
||||
Vertex {
|
||||
position: position,
|
||||
color: color,
|
||||
texture_coord: texture_coord,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PrimitiveAttributes {
|
||||
/// If true then the equation defined by `semi_transparency_mode`
|
||||
/// is applied to semi-transparent pixels.
|
||||
pub semi_transparent: bool,
|
||||
/// When `semi_transparent` is true this defines the blending
|
||||
/// equation for semi-transparent pixels.
|
||||
pub semi_transparency_mode: SemiTransparencyMode,
|
||||
/// Blending equation, says if the primitive is simply gouraud
|
||||
/// shaded (or monochrome since it's just a special case of
|
||||
/// gouraud shading with the same color on all vertices),
|
||||
/// texture-mapped or a mix of both (texture blending).
|
||||
pub blend_mode: BlendMode,
|
||||
/// For textured primitives this contains the coordinates of the
|
||||
/// top-left coordinates of the texture page. Texture pages are
|
||||
/// always 256x256 pixels big and wrap around in case of
|
||||
/// out-of-bound access.
|
||||
pub texture_page: [u16; 2],
|
||||
/// The PlayStation GPU supports 4 and 8bpp paletted textures and
|
||||
/// 16bits "truecolor" textures.
|
||||
pub texture_depth: TextureDepth,
|
||||
/// For 4 and 8bpp paletted textures this contains the coordinates
|
||||
/// of the first entry of the palette. The next entries will be at
|
||||
/// x + 1, x + 2 etc...
|
||||
pub clut: [u16; 2],
|
||||
/// True if the primitive is dithered.
|
||||
pub dither: bool,
|
||||
}
|
||||
|
||||
/// Primitive texturing methods
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub enum BlendMode {
|
||||
/// No texture, used
|
||||
None,
|
||||
/// Raw texture
|
||||
Raw,
|
||||
/// Texture bledend with the monochrome/shading color
|
||||
Blended,
|
||||
}
|
||||
|
||||
/// Semi-transparency modes supported by the PlayStation GPU
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub enum SemiTransparencyMode {
|
||||
/// Source / 2 + destination / 2
|
||||
Average = 0,
|
||||
/// Source + destination
|
||||
Add = 1,
|
||||
/// Destination - source
|
||||
SubstractSource = 2,
|
||||
/// Destination + source / 4
|
||||
AddQuarterSource = 3,
|
||||
}
|
||||
|
||||
/// Depth of the pixel values in a texture page
|
||||
#[derive(Clone,Copy)]
|
||||
pub enum TextureDepth {
|
||||
/// 4 bits per pixel, paletted
|
||||
T4Bpp = 0,
|
||||
/// 8 bits per pixel, paletted
|
||||
T8Bpp = 1,
|
||||
/// 16 bits per pixel, truecolor
|
||||
T16Bpp = 2,
|
||||
}
|
24
src/lib.rs
Normal file
24
src/lib.rs
Normal file
|
@ -0,0 +1,24 @@
|
|||
#[macro_use]
|
||||
extern crate log;
|
||||
extern crate shaman;
|
||||
|
||||
pub mod gpu;
|
||||
pub mod cdrom;
|
||||
pub mod bios;
|
||||
pub mod memory;
|
||||
pub mod cpu;
|
||||
pub mod shared;
|
||||
pub mod padmemcard;
|
||||
|
||||
|
||||
mod interrupt;
|
||||
mod timekeeper;
|
||||
mod spu;
|
||||
mod debugger;
|
||||
|
||||
/// Version of the rustation library set in Cargo.toml
|
||||
pub const VERSION: &'static str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
/// Like VERSION but as a `\0`-terminated C string. Useful when you
|
||||
/// need a static string in C bindings.
|
||||
pub const VERSION_CSTR: &'static str = concat!(env!("CARGO_PKG_VERSION"), '\0');
|
|
@ -1,60 +0,0 @@
|
|||
use std::path::Path;
|
||||
use std::fs::File;
|
||||
use std::io::{Result, Error, ErrorKind, Read};
|
||||
|
||||
use super::Addressable;
|
||||
|
||||
/// BIOS image
|
||||
pub struct Bios {
|
||||
/// BIOS memory. Boxed in order not to overflow the stack at the
|
||||
/// construction site. Might change once "placement new" is
|
||||
/// available.
|
||||
data: Box<[u8; BIOS_SIZE]>,
|
||||
}
|
||||
|
||||
impl Bios {
|
||||
|
||||
/// Load a BIOS image from the file located at `path`
|
||||
pub fn new(path: &Path) -> Result<Bios> {
|
||||
|
||||
let mut file = try!(File::open(path));
|
||||
|
||||
// Load the BIOS
|
||||
let mut data = Box::new([0; BIOS_SIZE]);
|
||||
let mut nread = 0;
|
||||
|
||||
while nread < BIOS_SIZE {
|
||||
nread +=
|
||||
match try!(file.read(&mut data[nread..])) {
|
||||
0 => return Err(Error::new(ErrorKind::InvalidInput,
|
||||
"BIOS file is too small")),
|
||||
n => n,
|
||||
};
|
||||
}
|
||||
|
||||
// Make sure the BIOS file is not too big, it's probably not a
|
||||
// good dump otherwise.
|
||||
if try!(file.read(&mut [0; 1])) != 0 {
|
||||
return Err(Error::new(ErrorKind::InvalidInput,
|
||||
"BIOS file is too big"));
|
||||
}
|
||||
|
||||
Ok(Bios { data: data })
|
||||
}
|
||||
|
||||
/// Fetch the little endian value at `offset`
|
||||
pub fn load<T: Addressable>(&self, offset: u32) -> u32 {
|
||||
let offset = offset as usize;
|
||||
|
||||
let mut r = 0;
|
||||
|
||||
for i in 0..T::size() as usize {
|
||||
r |= (self.data[offset + i] as u32) << (8 * i)
|
||||
}
|
||||
|
||||
r
|
||||
}
|
||||
}
|
||||
|
||||
/// BIOS images are always 512KB in length
|
||||
const BIOS_SIZE: usize = 512 * 1024;
|
|
@ -1,4 +1,5 @@
|
|||
use memory::interrupts::{Interrupt, InterruptState};
|
||||
use shared::SharedState;
|
||||
use interrupt::Interrupt;
|
||||
|
||||
/// Direct Memory Access
|
||||
pub struct Dma {
|
||||
|
@ -68,8 +69,8 @@ impl Dma {
|
|||
|
||||
/// Set the value of the interrupt register
|
||||
pub fn set_interrupt(&mut self,
|
||||
val: u32,
|
||||
irq_state: &mut InterruptState) {
|
||||
shared: &mut SharedState,
|
||||
val: u32) {
|
||||
let prev_irq = self.irq();
|
||||
|
||||
// Unknown what bits [5:0] do
|
||||
|
@ -89,7 +90,7 @@ impl Dma {
|
|||
|
||||
if !prev_irq && self.irq() {
|
||||
// Rising edge of the done interrupt
|
||||
irq_state.assert(Interrupt::Dma);
|
||||
shared.irq_state().assert(Interrupt::Dma);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -104,8 +105,9 @@ impl Dma {
|
|||
}
|
||||
|
||||
pub fn done(&mut self,
|
||||
port: Port,
|
||||
irq_state: &mut InterruptState) {
|
||||
shared: &mut SharedState,
|
||||
port: Port) {
|
||||
|
||||
self.channel_mut(port).done();
|
||||
|
||||
let prev_irq = self.irq();
|
||||
|
@ -117,7 +119,7 @@ impl Dma {
|
|||
|
||||
if !prev_irq && self.irq() {
|
||||
// Rising edge of the done interrupt
|
||||
irq_state.assert(Interrupt::Dma);
|
||||
shared.irq_state().assert(Interrupt::Dma);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
pub mod bios;
|
||||
pub mod interrupts;
|
||||
pub mod timers;
|
||||
mod ram;
|
||||
mod dma;
|
||||
|
||||
use self::bios::Bios;
|
||||
use self::ram::{Ram, ScratchPad};
|
||||
use self::dma::{Dma, Port, Direction, Step, Sync};
|
||||
use self::timers::Timers;
|
||||
use self::interrupts::InterruptState;
|
||||
use timekeeper::{TimeKeeper, Peripheral};
|
||||
|
||||
use shared::SharedState;
|
||||
use bios::Bios;
|
||||
use timekeeper::Peripheral;
|
||||
use gpu::Gpu;
|
||||
use gpu::renderer::Renderer;
|
||||
use spu::Spu;
|
||||
use cdrom::CdRom;
|
||||
use cdrom::disc::Disc;
|
||||
|
@ -19,7 +19,6 @@ use padmemcard::gamepad;
|
|||
|
||||
/// Global interconnect
|
||||
pub struct Interconnect {
|
||||
irq_state: InterruptState,
|
||||
/// Basic Input/Output memory
|
||||
bios: Bios,
|
||||
/// Main RAM
|
||||
|
@ -50,7 +49,6 @@ pub struct Interconnect {
|
|||
impl Interconnect {
|
||||
pub fn new(bios: Bios, gpu: Gpu, disc: Option<Disc>) -> Interconnect {
|
||||
Interconnect {
|
||||
irq_state: InterruptState::new(),
|
||||
bios: bios,
|
||||
ram: Ram::new(),
|
||||
scratch_pad: ScratchPad::new(),
|
||||
|
@ -66,19 +64,19 @@ impl Interconnect {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn sync(&mut self, tk: &mut TimeKeeper) {
|
||||
if tk.needs_sync(Peripheral::Gpu) {
|
||||
self.gpu.sync(tk, &mut self.irq_state);
|
||||
pub fn sync(&mut self, shared: &mut SharedState) {
|
||||
if shared.tk().needs_sync(Peripheral::Gpu) {
|
||||
self.gpu.sync(shared);
|
||||
}
|
||||
|
||||
if tk.needs_sync(Peripheral::PadMemCard) {
|
||||
self.pad_memcard.sync(tk, &mut self.irq_state);
|
||||
if shared.tk().needs_sync(Peripheral::PadMemCard) {
|
||||
self.pad_memcard.sync(shared);
|
||||
}
|
||||
|
||||
self.timers.sync(tk, &mut self.irq_state);
|
||||
self.timers.sync(shared);
|
||||
|
||||
if tk.needs_sync(Peripheral::CdRom) {
|
||||
self.cdrom.sync(tk, &mut self.irq_state);
|
||||
if shared.tk().needs_sync(Peripheral::CdRom) {
|
||||
self.cdrom.sync(shared);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -86,10 +84,6 @@ impl Interconnect {
|
|||
self.cache_control
|
||||
}
|
||||
|
||||
pub fn irq_state(&self) -> InterruptState {
|
||||
self.irq_state
|
||||
}
|
||||
|
||||
pub fn pad_profiles(&mut self) -> [&mut gamepad::Profile; 2] {
|
||||
self.pad_memcard.pad_profiles()
|
||||
}
|
||||
|
@ -113,12 +107,12 @@ impl Interconnect {
|
|||
|
||||
/// Interconnect: load value at `addr`
|
||||
pub fn load<T: Addressable>(&mut self,
|
||||
tk: &mut TimeKeeper,
|
||||
shared: &mut SharedState,
|
||||
addr: u32) -> u32 {
|
||||
// XXX Average RAM load delay, needs to do per-device tests
|
||||
// XXX This does not take the CPU pipelining into account so
|
||||
// it might be a little too slow in some cases actually.
|
||||
tk.tick(5);
|
||||
shared.tk().tick(5);
|
||||
|
||||
let abs_addr = map::mask_region(addr);
|
||||
|
||||
|
@ -141,8 +135,8 @@ impl Interconnect {
|
|||
if let Some(offset) = map::IRQ_CONTROL.contains(abs_addr) {
|
||||
return
|
||||
match offset {
|
||||
0 => self.irq_state.status() as u32,
|
||||
4 => self.irq_state.mask() as u32,
|
||||
0 => shared.irq_state().status() as u32,
|
||||
4 => shared.irq_state().mask() as u32,
|
||||
_ => panic!("Unhandled IRQ load at address {:08x}", addr),
|
||||
};
|
||||
}
|
||||
|
@ -152,19 +146,19 @@ impl Interconnect {
|
|||
}
|
||||
|
||||
if let Some(offset) = map::GPU.contains(abs_addr) {
|
||||
return self.gpu.load::<T>(tk, &mut self.irq_state, offset);
|
||||
return self.gpu.load::<T>(shared, offset);
|
||||
}
|
||||
|
||||
if let Some(offset) = map::TIMERS.contains(abs_addr) {
|
||||
return self.timers.load::<T>(tk, &mut self.irq_state, offset);
|
||||
return self.timers.load::<T>(shared, offset);
|
||||
}
|
||||
|
||||
if let Some(offset) = map::CDROM.contains(abs_addr) {
|
||||
return self.cdrom.load::<T>(tk, &mut self.irq_state, offset);
|
||||
return self.cdrom.load::<T>(shared, offset);
|
||||
}
|
||||
|
||||
if let Some(offset) = map::MDEC.contains(abs_addr) {
|
||||
println!("Unhandled load from MDEC register {:x}", offset);
|
||||
warn!("Unhandled load from MDEC register {:x}", offset);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -173,7 +167,7 @@ impl Interconnect {
|
|||
}
|
||||
|
||||
if let Some(offset) = map::PAD_MEMCARD.contains(abs_addr) {
|
||||
return self.pad_memcard.load::<T>(tk, &mut self.irq_state, offset);
|
||||
return self.pad_memcard.load::<T>(shared, offset);
|
||||
}
|
||||
|
||||
if let Some(_) = map::EXPANSION_1.contains(abs_addr) {
|
||||
|
@ -202,7 +196,8 @@ impl Interconnect {
|
|||
|
||||
/// Interconnect: store `val` into `addr`
|
||||
pub fn store<T: Addressable>(&mut self,
|
||||
tk: &mut TimeKeeper,
|
||||
shared: &mut SharedState,
|
||||
renderer: &mut Renderer,
|
||||
addr: u32,
|
||||
val: u32) {
|
||||
|
||||
|
@ -223,30 +218,29 @@ impl Interconnect {
|
|||
|
||||
if let Some(offset) = map::IRQ_CONTROL.contains(abs_addr) {
|
||||
match offset {
|
||||
0 => self.irq_state.ack(val as u16),
|
||||
4 => self.irq_state.set_mask(val as u16),
|
||||
0 => shared.irq_state().ack(val as u16),
|
||||
4 => shared.irq_state().set_mask(val as u16),
|
||||
_ => panic!("Unhandled IRQ store at address {:08x}"),
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(offset) = map::DMA.contains(abs_addr) {
|
||||
self.set_dma_reg::<T>(offset, val);
|
||||
self.set_dma_reg::<T>(shared, renderer, offset, val);
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(offset) = map::GPU.contains(abs_addr) {
|
||||
self.gpu.store::<T>(tk,
|
||||
self.gpu.store::<T>(shared,
|
||||
renderer,
|
||||
&mut self.timers,
|
||||
&mut self.irq_state,
|
||||
offset,
|
||||
val);
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(offset) = map::TIMERS.contains(abs_addr) {
|
||||
self.timers.store::<T>(tk,
|
||||
&mut self.irq_state,
|
||||
self.timers.store::<T>(shared,
|
||||
&mut self.gpu,
|
||||
offset,
|
||||
val);
|
||||
|
@ -254,11 +248,11 @@ impl Interconnect {
|
|||
}
|
||||
|
||||
if let Some(offset) = map::CDROM.contains(abs_addr) {
|
||||
return self.cdrom.store::<T>(tk, &mut self.irq_state, offset, val);
|
||||
return self.cdrom.store::<T>(shared, offset, val);
|
||||
}
|
||||
|
||||
if let Some(offset) = map::MDEC.contains(abs_addr) {
|
||||
println!("Unhandled write to MDEC register {:x}", offset);
|
||||
warn!("Unhandled write to MDEC register {:x}", offset);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -268,7 +262,7 @@ impl Interconnect {
|
|||
}
|
||||
|
||||
if let Some(offset) = map::PAD_MEMCARD.contains(abs_addr) {
|
||||
self.pad_memcard.store::<T>(tk, &mut self.irq_state, offset, val);
|
||||
self.pad_memcard.store::<T>(shared, offset, val);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -300,9 +294,9 @@ impl Interconnect {
|
|||
panic!("Bad expansion 2 base address: 0x{:08x}", val);
|
||||
},
|
||||
_ =>
|
||||
println!("Unhandled write to MEM_CONTROL register {:x}: \
|
||||
0x{:08x}",
|
||||
offset, val),
|
||||
warn!("Unhandled write to MEM_CONTROL register {:x}: \
|
||||
0x{:08x}",
|
||||
offset, val),
|
||||
}
|
||||
|
||||
let index = (offset >> 2) as usize;
|
||||
|
@ -323,7 +317,7 @@ impl Interconnect {
|
|||
}
|
||||
|
||||
if let Some(offset) = map::EXPANSION_2.contains(abs_addr) {
|
||||
println!("Unhandled write to expansion 2 register {:x}", offset);
|
||||
warn!("Unhandled write to expansion 2 register {:x}", offset);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -368,7 +362,11 @@ impl Interconnect {
|
|||
}
|
||||
|
||||
/// DMA register write
|
||||
fn set_dma_reg<T: Addressable>(&mut self, offset: u32, val: u32) {
|
||||
fn set_dma_reg<T: Addressable>(&mut self,
|
||||
shared: &mut SharedState,
|
||||
renderer: &mut Renderer,
|
||||
offset: u32,
|
||||
val: u32) {
|
||||
// Byte and Halfword writes are treated like word writes with
|
||||
// the *entire* Word value shifted by the alignment.
|
||||
let align = offset & 3;
|
||||
|
@ -403,7 +401,7 @@ impl Interconnect {
|
|||
7 => {
|
||||
match minor {
|
||||
0 => self.dma.set_control(val),
|
||||
4 => self.dma.set_interrupt(val, &mut self.irq_state),
|
||||
4 => self.dma.set_interrupt(shared, val),
|
||||
_ => panic!("Unhandled DMA write {:x}: {:08x}",
|
||||
offset, val),
|
||||
}
|
||||
|
@ -415,26 +413,29 @@ impl Interconnect {
|
|||
};
|
||||
|
||||
if let Some(port) = active_port {
|
||||
self.do_dma(port);
|
||||
self.do_dma(shared, renderer, port);
|
||||
}
|
||||
}
|
||||
|
||||
/// Execute DMA transfer for a port
|
||||
fn do_dma(&mut self, port: Port) {
|
||||
fn do_dma(&mut self,
|
||||
shared: &mut SharedState,
|
||||
renderer: &mut Renderer,
|
||||
port: Port) {
|
||||
// DMA transfer has been started, for now let's
|
||||
// process everything in one pass (i.e. no
|
||||
// chopping or priority handling)
|
||||
|
||||
match self.dma.channel(port).sync() {
|
||||
Sync::LinkedList => self.do_dma_linked_list(port),
|
||||
_ => self.do_dma_block(port),
|
||||
Sync::LinkedList => self.do_dma_linked_list(renderer, port),
|
||||
_ => self.do_dma_block(renderer, port),
|
||||
}
|
||||
|
||||
self.dma.done(port, &mut self.irq_state);
|
||||
self.dma.done(shared, port);
|
||||
}
|
||||
|
||||
/// Emulate DMA transfer for linked list synchronization mode.
|
||||
fn do_dma_linked_list(&mut self, port: Port) {
|
||||
fn do_dma_linked_list(&mut self, renderer: &mut Renderer, port: Port) {
|
||||
let channel = self.dma.channel_mut(port);
|
||||
|
||||
let mut addr = channel.base() & 0x1ffffc;
|
||||
|
@ -463,7 +464,7 @@ impl Interconnect {
|
|||
let command = self.ram.load::<Word>(addr);
|
||||
|
||||
// Send command to the GPU
|
||||
self.gpu.gp0(command);
|
||||
self.gpu.gp0(renderer, command);
|
||||
|
||||
remsz -= 1;
|
||||
}
|
||||
|
@ -483,7 +484,7 @@ impl Interconnect {
|
|||
|
||||
/// Emulate DMA transfer for Manual and Request synchronization
|
||||
/// modes.
|
||||
fn do_dma_block(&mut self, port: Port) {
|
||||
fn do_dma_block(&mut self, renderer: &mut Renderer, port: Port) {
|
||||
let channel = self.dma.channel_mut(port);
|
||||
|
||||
let increment = match channel.step() {
|
||||
|
@ -514,7 +515,7 @@ impl Interconnect {
|
|||
let src_word = self.ram.load::<Word>(cur_addr);
|
||||
|
||||
match port {
|
||||
Port::Gpu => self.gpu.gp0(src_word),
|
||||
Port::Gpu => self.gpu.gp0(renderer, src_word),
|
||||
// XXX ignore transfers to the MDEC for now
|
||||
Port::MDecIn => (),
|
||||
// XXX ignre transfers to the SPU for now
|
||||
|
@ -537,7 +538,7 @@ impl Interconnect {
|
|||
Port::MDecOut => 0,
|
||||
Port::Gpu => {
|
||||
// XXX to be implemented
|
||||
println!("DMA GPU READ");
|
||||
debug!("DMA GPU READ");
|
||||
0
|
||||
}
|
||||
Port::CdRom => self.cdrom.dma_read_word(),
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
use timekeeper::{TimeKeeper, Cycles, FracCycles, Peripheral};
|
||||
use timekeeper::{Cycles, FracCycles, Peripheral};
|
||||
use gpu::Gpu;
|
||||
use super::Addressable;
|
||||
use super::interrupts::{InterruptState, Interrupt};
|
||||
use interrupt::Interrupt;
|
||||
use shared::SharedState;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Timers {
|
||||
|
@ -27,8 +28,7 @@ impl Timers {
|
|||
}
|
||||
|
||||
pub fn store<T: Addressable>(&mut self,
|
||||
tk: &mut TimeKeeper,
|
||||
irq_state: &mut InterruptState,
|
||||
shared: &mut SharedState,
|
||||
gpu: &mut Gpu,
|
||||
offset: u32,
|
||||
val: u32) {
|
||||
|
@ -43,7 +43,7 @@ impl Timers {
|
|||
|
||||
let timer = &mut self.timers[instance as usize];
|
||||
|
||||
timer.sync(tk, irq_state);
|
||||
timer.sync(shared);
|
||||
|
||||
match offset & 0xf {
|
||||
0 => timer.set_counter(val),
|
||||
|
@ -53,15 +53,14 @@ impl Timers {
|
|||
}
|
||||
|
||||
if timer.needs_gpu() {
|
||||
gpu.sync(tk, irq_state);
|
||||
gpu.sync(shared);
|
||||
}
|
||||
|
||||
timer.reconfigure(gpu, tk);
|
||||
timer.reconfigure(shared, gpu);
|
||||
}
|
||||
|
||||
pub fn load<T: Addressable>(&mut self,
|
||||
tk: &mut TimeKeeper,
|
||||
irq_state: &mut InterruptState,
|
||||
shared: &mut SharedState,
|
||||
offset: u32) -> u32 {
|
||||
|
||||
if T::size() == 1 {
|
||||
|
@ -72,7 +71,7 @@ impl Timers {
|
|||
|
||||
let timer = &mut self.timers[instance as usize];
|
||||
|
||||
timer.sync(tk, irq_state);
|
||||
timer.sync(shared);
|
||||
|
||||
let val = match offset & 0xf {
|
||||
0 => timer.counter(),
|
||||
|
@ -87,31 +86,29 @@ impl Timers {
|
|||
/// Called by the GPU when the video timings change since it can
|
||||
/// affect the timers that use them.
|
||||
pub fn video_timings_changed(&mut self,
|
||||
tk: &mut TimeKeeper,
|
||||
irq_state: &mut InterruptState,
|
||||
shared: &mut SharedState,
|
||||
gpu: &Gpu) {
|
||||
|
||||
for t in &mut self.timers {
|
||||
if t.needs_gpu() {
|
||||
t.sync(tk, irq_state);
|
||||
t.reconfigure(gpu, tk);
|
||||
t.sync(shared);
|
||||
t.reconfigure(shared, gpu);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sync(&mut self,
|
||||
tk: &mut TimeKeeper,
|
||||
irq_state: &mut InterruptState) {
|
||||
if tk.needs_sync(Peripheral::Timer0) {
|
||||
self.timers[0].sync(tk, irq_state);
|
||||
pub fn sync(&mut self, shared: &mut SharedState) {
|
||||
|
||||
if shared.tk().needs_sync(Peripheral::Timer0) {
|
||||
self.timers[0].sync(shared);
|
||||
}
|
||||
|
||||
if tk.needs_sync(Peripheral::Timer1) {
|
||||
self.timers[1].sync(tk, irq_state);
|
||||
if shared.tk().needs_sync(Peripheral::Timer1) {
|
||||
self.timers[1].sync(shared);
|
||||
}
|
||||
|
||||
if tk.needs_sync(Peripheral::Timer2) {
|
||||
self.timers[2].sync(tk, irq_state);
|
||||
if shared.tk().needs_sync(Peripheral::Timer2) {
|
||||
self.timers[2].sync(shared);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -189,7 +186,7 @@ impl Timer {
|
|||
///
|
||||
/// If the GPU is needed for the timings it must be synchronized
|
||||
/// before this function is called.
|
||||
fn reconfigure(&mut self, gpu: &Gpu, tk: &mut TimeKeeper) {
|
||||
fn reconfigure(&mut self, shared: &mut SharedState, gpu: &Gpu) {
|
||||
|
||||
match self.clock_source.clock(self.instance) {
|
||||
Clock::SysClock => {
|
||||
|
@ -212,14 +209,14 @@ impl Timer {
|
|||
}
|
||||
}
|
||||
|
||||
self.predict_next_sync(tk);
|
||||
self.predict_next_sync(shared);
|
||||
}
|
||||
|
||||
/// Synchronize this timer.
|
||||
fn sync(&mut self,
|
||||
tk: &mut TimeKeeper,
|
||||
irq_state: &mut InterruptState) {
|
||||
let delta = tk.sync(self.instance);
|
||||
shared: &mut SharedState) {
|
||||
|
||||
let delta = shared.tk().sync(self.instance);
|
||||
|
||||
if delta == 0 {
|
||||
// The interrupt code below might glitch if it's called
|
||||
|
@ -292,7 +289,7 @@ impl Timer {
|
|||
panic!("Unhandled negate IRQ!");
|
||||
} else {
|
||||
// Pulse interrupt
|
||||
irq_state.assert(interrupt);
|
||||
shared.irq_state().assert(interrupt);
|
||||
self.interrupt = true;
|
||||
}
|
||||
} else if !self.negate_irq {
|
||||
|
@ -300,15 +297,15 @@ impl Timer {
|
|||
self.interrupt = false;
|
||||
}
|
||||
|
||||
self.predict_next_sync(tk)
|
||||
self.predict_next_sync(shared)
|
||||
}
|
||||
|
||||
fn predict_next_sync(&mut self, tk: &mut TimeKeeper) {
|
||||
fn predict_next_sync(&mut self, shared: &mut SharedState) {
|
||||
// XXX add support for wrap IRQ
|
||||
|
||||
if !self.target_irq {
|
||||
// No IRQ enabled, we don't need to be called back.
|
||||
tk.no_sync_needed(self.instance);
|
||||
shared.tk().no_sync_needed(self.instance);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -328,14 +325,14 @@ impl Timer {
|
|||
// Round up to the next CPU cycle
|
||||
let delta = FracCycles::from_fp(delta).ceil();
|
||||
|
||||
tk.set_next_sync_delta(self.instance, delta);
|
||||
shared.tk().set_next_sync_delta(self.instance, delta);
|
||||
}
|
||||
|
||||
/// Return true if the timer relies on the GPU for the clock
|
||||
/// source or synchronization
|
||||
pub fn needs_gpu(&self) -> bool {
|
||||
if self.use_sync {
|
||||
println!("Sync mode not supported!");
|
||||
warn!("Sync mode not supported!");
|
||||
}
|
||||
|
||||
self.clock_source.clock(self.instance).needs_gpu()
|
||||
|
@ -394,7 +391,7 @@ impl Timer {
|
|||
}
|
||||
|
||||
if self.use_sync {
|
||||
println!("Sync mode is not supported: {:?}", self);
|
||||
warn!("Sync mode is not supported: {:?}", self);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
//! Gamepad and memory card controller emulation
|
||||
|
||||
use memory::Addressable;
|
||||
use memory::interrupts::{Interrupt, InterruptState};
|
||||
use timekeeper::{TimeKeeper, Peripheral, Cycles};
|
||||
use interrupt::Interrupt;
|
||||
use timekeeper::{Peripheral, Cycles};
|
||||
use shared::SharedState;
|
||||
|
||||
use self::gamepad::GamePad;
|
||||
|
||||
pub mod gamepad;
|
||||
|
@ -78,11 +80,10 @@ impl PadMemCard {
|
|||
}
|
||||
|
||||
pub fn store<T: Addressable>(&mut self,
|
||||
tk: &mut TimeKeeper,
|
||||
irq_state: &mut InterruptState,
|
||||
shared: &mut SharedState,
|
||||
offset: u32,
|
||||
val: u32) {
|
||||
self.sync(tk, irq_state);
|
||||
self.sync(shared);
|
||||
|
||||
match offset {
|
||||
0 => {
|
||||
|
@ -91,7 +92,7 @@ impl PadMemCard {
|
|||
T::size());
|
||||
}
|
||||
|
||||
self.send_command(tk, val as u8);
|
||||
self.send_command(shared, val as u8);
|
||||
}
|
||||
8 => self.set_mode(val as u8),
|
||||
10 => {
|
||||
|
@ -99,7 +100,7 @@ impl PadMemCard {
|
|||
// Byte access behaves like a halfword
|
||||
panic!("Unhandled byte gamepad control access");
|
||||
}
|
||||
self.set_control(irq_state, val as u16);
|
||||
self.set_control(shared, val as u16);
|
||||
}
|
||||
14 => self.baud_div = val as u16,
|
||||
_ => panic!("Unhandled write to gamepad register {} {:04x}",
|
||||
|
@ -108,10 +109,10 @@ impl PadMemCard {
|
|||
}
|
||||
|
||||
pub fn load<T: Addressable>(&mut self,
|
||||
tk: &mut TimeKeeper,
|
||||
irq_state: &mut InterruptState,
|
||||
shared: &mut SharedState,
|
||||
offset: u32) -> u32 {
|
||||
self.sync(tk, irq_state);
|
||||
|
||||
self.sync(shared);
|
||||
|
||||
match offset {
|
||||
0 => {
|
||||
|
@ -138,21 +139,23 @@ impl PadMemCard {
|
|||
}
|
||||
|
||||
pub fn sync(&mut self,
|
||||
tk: &mut TimeKeeper,
|
||||
irq_state: &mut InterruptState) {
|
||||
let delta = tk.sync(Peripheral::Gpu);
|
||||
shared: &mut SharedState) {
|
||||
|
||||
let delta = shared.tk().sync(Peripheral::PadMemCard);
|
||||
|
||||
match self.bus {
|
||||
BusState::Idle => tk.no_sync_needed(Peripheral::PadMemCard),
|
||||
BusState::Idle =>
|
||||
shared.tk().no_sync_needed(Peripheral::PadMemCard),
|
||||
BusState::Transfer(r, dsr, delay) => {
|
||||
if delta < delay {
|
||||
let delay = delay - delta;
|
||||
self.bus = BusState::Transfer(r, dsr, delay);
|
||||
|
||||
if self.dsr_it {
|
||||
tk.set_next_sync_delta(Peripheral::PadMemCard, delay);
|
||||
shared.tk().set_next_sync_delta(Peripheral::PadMemCard,
|
||||
delay);
|
||||
} else {
|
||||
tk.no_sync_needed(Peripheral::PadMemCard);
|
||||
shared.tk().no_sync_needed(Peripheral::PadMemCard);
|
||||
}
|
||||
} else {
|
||||
// We reached the end of the transfer
|
||||
|
@ -171,6 +174,8 @@ impl PadMemCard {
|
|||
if self.dsr_it {
|
||||
if !self.interrupt {
|
||||
// Rising edge of the interrupt
|
||||
let irq_state = shared.irq_state();
|
||||
|
||||
irq_state.assert(Interrupt::PadMemCard);
|
||||
}
|
||||
|
||||
|
@ -199,7 +204,7 @@ impl PadMemCard {
|
|||
self.bus = BusState::Idle;
|
||||
}
|
||||
|
||||
tk.no_sync_needed(Peripheral::PadMemCard);
|
||||
shared.tk().no_sync_needed(Peripheral::PadMemCard);
|
||||
}
|
||||
}
|
||||
BusState::Dsr(delay) => {
|
||||
|
@ -211,7 +216,7 @@ impl PadMemCard {
|
|||
self.dsr = false;
|
||||
self.bus = BusState::Idle;
|
||||
}
|
||||
tk.no_sync_needed(Peripheral::PadMemCard);
|
||||
shared.tk().no_sync_needed(Peripheral::PadMemCard);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -221,7 +226,7 @@ impl PadMemCard {
|
|||
[ self.pad1.profile(), self.pad2.profile() ]
|
||||
}
|
||||
|
||||
fn send_command(&mut self, tk: &mut TimeKeeper, cmd: u8) {
|
||||
fn send_command(&mut self, shared: &mut SharedState, cmd: u8) {
|
||||
if !self.tx_en {
|
||||
// It should be stored in the FIFO and sent when tx_en is
|
||||
// set (I think)
|
||||
|
@ -230,7 +235,7 @@ impl PadMemCard {
|
|||
|
||||
if self.bus.is_busy() {
|
||||
// I suppose the transfer should be queued in the TX FIFO?
|
||||
println!("Gamepad command {:x} while bus is busy!", cmd);
|
||||
warn!("Gamepad command {:x} while bus is busy!", cmd);
|
||||
}
|
||||
|
||||
let (response, dsr) =
|
||||
|
@ -253,7 +258,7 @@ impl PadMemCard {
|
|||
|
||||
// XXX For now pretend that the DSR pulse follows
|
||||
// immediately after the last byte, probably not accurate.
|
||||
tk.set_next_sync_delta(Peripheral::PadMemCard, tx_duration);
|
||||
shared.tk().set_next_sync_delta(Peripheral::PadMemCard, tx_duration);
|
||||
}
|
||||
|
||||
fn stat(&self) -> u32 {
|
||||
|
@ -292,7 +297,7 @@ impl PadMemCard {
|
|||
ctrl
|
||||
}
|
||||
|
||||
fn set_control(&mut self, irq_state: &mut InterruptState, ctrl: u16) {
|
||||
fn set_control(&mut self, shared: &mut SharedState, ctrl: u16) {
|
||||
if ctrl & 0x40 != 0 {
|
||||
// Soft reset
|
||||
self.baud_div = 0;
|
||||
|
@ -324,10 +329,10 @@ impl PadMemCard {
|
|||
// which will be seen by the edge-triggered top
|
||||
// level interrupt controller. So I guess this
|
||||
// shouldn't happen?
|
||||
println!("Gamepad interrupt acknowledge while DSR is active");
|
||||
warn!("Gamepad interrupt acknowledge while DSR is active");
|
||||
|
||||
self.interrupt = true;
|
||||
irq_state.assert(Interrupt::PadMemCard);
|
||||
shared.irq_state().assert(Interrupt::PadMemCard);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
50
src/shared.rs
Normal file
50
src/shared.rs
Normal file
|
@ -0,0 +1,50 @@
|
|||
use timekeeper::TimeKeeper;
|
||||
use interrupt::InterruptState;
|
||||
use debugger::Debugger;
|
||||
|
||||
/// State shared between various modules
|
||||
pub struct SharedState {
|
||||
tk: TimeKeeper,
|
||||
debugger: Debugger,
|
||||
irq_state: InterruptState,
|
||||
frame: u32,
|
||||
}
|
||||
|
||||
impl SharedState {
|
||||
pub fn new() -> SharedState {
|
||||
SharedState {
|
||||
tk: TimeKeeper::new(),
|
||||
debugger: Debugger::new(),
|
||||
irq_state: InterruptState::new(),
|
||||
frame: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tk(&mut self) -> &mut TimeKeeper {
|
||||
&mut self.tk
|
||||
}
|
||||
|
||||
pub fn debugger(&mut self) -> &mut Debugger {
|
||||
&mut self.debugger
|
||||
}
|
||||
|
||||
pub fn irq_state(&mut self) -> &mut InterruptState {
|
||||
&mut self.irq_state
|
||||
}
|
||||
|
||||
pub fn frame(&self) -> u32 {
|
||||
self.frame
|
||||
}
|
||||
|
||||
pub fn new_frame(&mut self) {
|
||||
// It will wrap in a little more than 2 years at 60Hz
|
||||
self.frame = self.frame.wrapping_add(1);
|
||||
}
|
||||
|
||||
/// Cleanup the shared state except for the debugger
|
||||
pub fn reset(&mut self) {
|
||||
self.tk = TimeKeeper::new();
|
||||
self.irq_state = InterruptState::new();
|
||||
self.frame = 0;
|
||||
}
|
||||
}
|
|
@ -213,7 +213,7 @@ impl Spu {
|
|||
// XXX handle FIFO overflow?
|
||||
let index = self.ram_index;
|
||||
|
||||
println!("SPU RAM store {:05x}: {:04x}", index, val);
|
||||
debug!("SPU RAM store {:05x}: {:04x}", index, val);
|
||||
|
||||
self.ram[index as usize] = val;
|
||||
self.ram_index = (index + 1) & 0x3ffff;
|
||||
|
|
|
@ -37,7 +37,8 @@ impl TimeKeeper {
|
|||
pub fn new() -> TimeKeeper {
|
||||
TimeKeeper {
|
||||
now: 0,
|
||||
next_sync: Cycles::max_value(),
|
||||
// Force a sync at the start to initialize evrything
|
||||
next_sync: 0,
|
||||
timesheets: [TimeSheet::new(); 6],
|
||||
}
|
||||
}
|
||||
|
@ -64,7 +65,7 @@ impl TimeKeeper {
|
|||
|
||||
/// Set next sync *only* if it's closer than what's already
|
||||
/// configured.
|
||||
pub fn set_next_sync_delta_if_closer(&mut self,
|
||||
pub fn set_next_sync_delta_if_sooner(&mut self,
|
||||
who: Peripheral,
|
||||
delta: Cycles) {
|
||||
let date = self.now + delta;
|
||||
|
|
Loading…
Reference in a new issue