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:
Lionel Flandrin 2015-12-26 21:33:25 +01:00
parent 910c54cd8e
commit a16dc1a9c3
33 changed files with 1228 additions and 2397 deletions

1
.gitignore vendored
View file

@ -1,2 +1,3 @@
Cargo.lock
/target
/roms

562
Cargo.lock generated
View file

@ -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)",
]

View file

@ -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"]

View file

@ -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
View 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
View 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;

View file

@ -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,

View file

@ -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;

View file

@ -1,4 +1,4 @@
use memory::interrupts::InterruptState;
use interrupt::InterruptState;
/// Coprocessor 0: System control
pub struct Cop0 {

View file

@ -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!(),
}
}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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);
}
}

File diff suppressed because it is too large Load diff

View file

@ -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,
&params).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,
&params).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,
&params).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,
&params).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,
}
}
}

View file

@ -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.);
}

View file

@ -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;
}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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
View 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
View 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');

View file

@ -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;

View file

@ -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);
}
}
}

View file

@ -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(),

View file

@ -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);
}
}

View file

@ -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
View 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;
}
}

View file

@ -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;

View file

@ -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;