Compare commits

..

4 commits

Author SHA1 Message Date
Lionel Flandrin
56d31456ee Don't crash on unimplemented GPU commands
Fixes a crash in DewPrism when the game sends bogus 0xffffffff commands
to the GPU.
2024-11-23 18:56:55 +00:00
Lionel Flandrin
be548a855a Implement internal upscaling 2024-11-09 19:28:06 +00:00
Lionel Flandrin
39a07768b0 Start implementing internal resolution upscaling 2024-10-24 20:17:37 +01:00
Lionel Flandrin
074c663dc6 Fix warnings with rust 1.81.0 2024-10-24 14:31:34 +01:00
8 changed files with 385 additions and 145 deletions

View file

@ -7,11 +7,13 @@ pub trait Bitwise {
fn set_bit(&mut self, bitpos: u8, v: bool);
/// Sets the given bit in self to 1
#[test]
fn set_bit_h(&mut self, bitpos: u8) {
self.set_bit(bitpos, true)
}
/// Sets the given bit in self to 0
#[test]
fn set_bit_l(&mut self, bitpos: u8) {
self.set_bit(bitpos, false)
}

View file

@ -393,22 +393,27 @@ impl Context {
}
fn get_geometry(&self) -> libretro::GameGeometry {
let upscaling = options::CoreOptions::internal_upscale_factor();
let upscale_shift = options::CoreOptions::internal_upscale_factor();
let vram_display_mode = options::CoreOptions::display_full_vram();
let (w, h) = vram_display_mode.max_resolution();
let aspect_ratio = vram_display_mode.aspect_ratio();
let w = w as u32;
let h = h as u32;
let max_width;
let max_height;
if w <= 640 && h <= 576 {
max_width = (640 * upscaling) as libc::c_uint;
max_height = (576 * upscaling) as libc::c_uint;
max_width = (640 << upscale_shift) as libc::c_uint;
max_height = (576 << upscale_shift) as libc::c_uint;
} else {
// Normally only used in "weird" modes such as "show full VRAM"
max_width = (w * upscaling) as libc::c_uint;
max_height = (h * upscaling) as libc::c_uint;
//
// Does not scale with upscale_shift
max_width = w as libc::c_uint;
max_height = h as libc::c_uint;
}
libretro::GameGeometry {
@ -693,6 +698,11 @@ impl libretro::Context for Context {
.gpu
.set_rasterizer_option(RasterizerOption::DrawPolygons(draw_poly));
let upscale_shift = options::CoreOptions::internal_upscale_factor();
self.psx
.gpu
.set_rasterizer_option(RasterizerOption::UpscaleShift(upscale_shift));
self.psx
.cd
.set_cd_loading_speed(options::CoreOptions::cd_speed() / 2);
@ -1062,7 +1072,7 @@ pub enum VRamDisplayMode {
}
impl VRamDisplayMode {
fn max_resolution(self) -> (u32, u32) {
fn max_resolution(self) -> (u16, u16) {
match self {
// Maximum resolution supported by the PlayStation video output is 640x576. That high a
// vertical resolution would mean no blanking however, so it doesn't make a lot of
@ -1116,8 +1126,8 @@ mod options {
libretro_variables!(
pub struct CoreOptions (prefix = "rustation") {
internal_upscale_factor: u32, parse_upscale
=> "Internal upscaling factor; 1x (native)";
internal_upscale_factor: u8, parse_upscale
=> "Internal upscaling factor; 1x (native)|2x|4x";
internal_color_depth: u8, parse_u8
=> "Internal color depth; dithered 15bpp (native)|24bpp";
display_full_vram: VRamDisplayMode, parse_full_vram
@ -1178,10 +1188,12 @@ mod options {
}
}
fn parse_upscale(opt: &str) -> Result<u32, <u32 as FromStr>::Err> {
fn parse_upscale(opt: &str) -> Result<u8, <u8 as FromStr>::Err> {
let num = opt.trim_matches(|c: char| !c.is_numeric());
num.parse()
let n: u8 = num.parse()?;
Ok(n.ilog2() as u8)
}
fn parse_percent(opt: &str) -> Result<f32, <u32 as FromStr>::Err> {

View file

@ -22,9 +22,11 @@ pub struct Metadata {
///
/// This value can be set to `None` if the correct address has not been determined for this
/// particular BIOS.
#[allow(dead_code)]
pub animation_jump_hook: Option<u32>,
/// Method used to patch the BIOS to enable the debug UART or `None` if the method hasn't been
/// found.
#[allow(dead_code)]
pub patch_debug_uart: Option<fn(&mut Bios)>,
}

View file

@ -3,7 +3,10 @@
//! The timings code is copied from mednafen
use super::cop0::Exception;
use super::{cop0, debugger, map, AccessWidth, Addressable, CycleCount, Psx};
use super::{cop0, map, AccessWidth, Addressable, CycleCount, Psx};
#[cfg(feature = "debugger")]
use super::debugger;
use std::fmt;
@ -268,7 +271,10 @@ pub fn run_next_instruction(psx: &mut Psx) {
psx.cpu.branch = false;
// Debugger entrypoint: used for code breakpoints and stepping
debugger::pc_change(psx);
#[cfg(feature = "debugger")]
{
debugger::pc_change(psx);
}
if psx.cpu.current_pc % 4 != 0 {
// PC is not correctly aligned!
@ -428,7 +434,10 @@ fn store<T: Addressable>(psx: &mut Psx, addr: u32, v: T) {
return;
}
debugger::memory_write(psx, addr);
#[cfg(feature = "debugger")]
{
debugger::memory_write(psx, addr);
}
psx.store(addr, v);
}
@ -439,7 +448,10 @@ fn load<T: Addressable>(psx: &mut Psx, addr: u32, from_lwc: bool) -> (T, u8) {
// Any pending load must terminate before we attempt to start a new one
psx.cpu.load_sync();
debugger::memory_read(psx, addr);
#[cfg(feature = "debugger")]
{
debugger::memory_read(psx, addr);
}
// The Scratch Pad is the CPU data cache, it therefore has very low latency and needs to be
// special-cased
@ -621,12 +633,15 @@ fn op_syscall(psx: &mut Psx, _: Instruction) {
/// Break
fn op_break(psx: &mut Psx, _: Instruction) {
if psx.cpu.debug_on_break {
info!("BREAK instruction while debug_on_break is active");
debugger::trigger_break(psx);
} else {
exception(psx, Exception::Break);
#[cfg(feature = "debugger")]
{
if psx.cpu.debug_on_break {
info!("BREAK instruction while debug_on_break is active");
debugger::trigger_break(psx);
return;
}
}
exception(psx, Exception::Break);
}
/// Block if the current DIV(U) or MULT(U) instruction has not yet finished

View file

@ -771,7 +771,7 @@ fn cmd_nop(psx: &mut Psx) {
/// Placeholder function
fn cmd_unimplemented(psx: &mut Psx) {
unimplemented!("GPU command {:08x}", psx.gpu.command_fifo.pop());
warn!("GPU command {:08x}", psx.gpu.command_fifo.pop());
}
/// LUT for all GP0 commands (indexed by opcode, bits[31:24] of the first command word)

View file

@ -10,13 +10,15 @@ use crate::psx::gpu::commands::{vram_access_dimensions, Shaded};
use crate::psx::gpu::commands::{NoShading, Position, Transparent};
use crate::psx::gpu::commands::{NoTexture, Opaque, ShadingMode, TextureBlending, TextureRaw};
use crate::psx::gpu::commands::{TextureMode, TransparencyMode};
use crate::BoxArray;
use crate::VRamDisplayMode;
use crate::psx::gpu::{DisplayMode, DrawMode, MaskSettings, TextureWindow, TransparencyFunction};
use fixed_point::{FpCoord, FpVar};
use serde::de::{self, Deserialize, Deserializer, SeqAccess, Visitor};
use serde::ser::{Serialize, SerializeTuple, Serializer};
use std::cmp::{max, min};
use std::fmt;
use std::marker::PhantomData;
#[derive(serde::Serialize, serde::Deserialize, Debug)]
enum State {
@ -31,7 +33,7 @@ enum State {
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Rasterizer {
vram: BoxArray<Pixel, VRAM_PIXELS>,
vram: VRam,
state: State,
/// Frame currently being drawn
#[serde(skip)]
@ -81,7 +83,6 @@ pub struct Rasterizer {
/// position.
/// * `[_; 4]`: y % 4 to select the right position in the dithering pattern based on the vram
/// position.
///
/// * `[_; 0x200]`: input value, from 0x000 to 0x1ff. Values above 0xff are saturated to 0xff
#[serde(with = "serialize_dither_table")]
dither_table: [[[u8; 0x200]; 4]; 4],
@ -106,7 +107,7 @@ pub struct Rasterizer {
impl Rasterizer {
pub fn new() -> Rasterizer {
Rasterizer {
vram: BoxArray::from_vec(vec![Pixel::black(); 1024 * 512]),
vram: VRam::with_upscale_shift(0),
state: State::WaitingForCommand,
cur_frame: Frame::new(0, 0),
clip_x_min: 0,
@ -209,11 +210,15 @@ impl Rasterizer {
let p1 = Pixel::from_mbgr1555((*v >> 16) as u16);
for &p in [p0, p1].iter() {
let vram_off = store.target_vram_offset();
let (x, y) = store.target_vram_offset();
let target = &mut self.vram[vram_off];
if self.mask_settings.can_draw_to(*target) {
*target = self.mask_settings.mask(p);
let target = self.vram.native_pixel(x, y);
if self.mask_settings.can_draw_to(target) {
self.vram.set_native_pixel(
x,
y,
self.mask_settings.mask(p),
);
}
if store.next().is_none() {
@ -318,9 +323,40 @@ impl Rasterizer {
}
RasterizerOption::Wireframe(v) => self.draw_wireframe = v,
RasterizerOption::DrawPolygons(v) => self.draw_polygons = v,
RasterizerOption::UpscaleShift(v) => self.set_upscale_shift(v),
}
}
pub fn set_upscale_shift(&mut self, upscale_shift: u8) {
if self.vram.upscale_shift == upscale_shift {
return;
}
self.clip_x_min >>= self.vram.upscale_shift;
self.clip_y_min >>= self.vram.upscale_shift;
self.clip_x_max >>= self.vram.upscale_shift;
self.clip_x_max >>= self.vram.upscale_shift;
self.clip_x_min <<= upscale_shift;
self.clip_y_min <<= upscale_shift;
self.clip_x_max <<= upscale_shift;
self.clip_x_max <<= upscale_shift;
// The clip is inclusive, so we need to offset when upscaling
self.clip_x_max += (1 << self.vram.upscale_shift) - 1;
self.clip_y_max += (1 << self.vram.upscale_shift) - 1;
let mut vram = VRam::with_upscale_shift(upscale_shift);
for y in 0..512 {
for x in 0..1024 {
vram.set_native_pixel(x, y, self.vram.native_pixel(x, y));
}
}
self.vram = vram;
}
/// Called when we should output a line to the output buffer
pub fn finish_line(&mut self, line: u16) {
if self.vram_display_mode != VRamDisplayMode::Native {
@ -349,9 +385,9 @@ impl Rasterizer {
}
fn output_line(&mut self, x_start: u16, vram_y: u16, frame_y: u16) {
let x_start = x_start as u32;
let frame_y = frame_y as u32;
let vram_y = vram_y as i32;
let x_start = u32::from(x_start) << self.vram.upscale_shift;
let frame_y = u32::from(frame_y) << self.vram.upscale_shift;
let vram_y = i32::from(vram_y) << self.vram.upscale_shift;
if frame_y >= self.cur_frame.height {
// Out-of-frame. This should only happen if the video mode changed within the current
@ -359,7 +395,9 @@ impl Rasterizer {
return;
}
let width = min(self.cur_frame.width, self.display_mode.xres() as u32);
let xres = u32::from(self.display_mode.xres()) << self.vram.upscale_shift;
let width = min(self.cur_frame.width, xres);
if self.display_mode.output_24bpp() {
// GPU is in 24bpp mode, we need to do some bitwise magic to recreate the values
@ -395,9 +433,12 @@ impl Rasterizer {
}
} else {
// GPU outputs pixels "normally", 15bpp native
for x in 0..width {
let p = self.read_pixel((x_start + x) as i32, vram_y);
self.cur_frame.set_pixel(x, frame_y, p.to_rgb888());
for y in 0..(1i32 << self.vram.upscale_shift) {
for x in 0..width {
let p = self.read_pixel((x_start + x) as i32, vram_y + y);
self.cur_frame
.set_pixel(x, frame_y + (y as u32), p.to_rgb888());
}
}
}
}
@ -453,7 +494,10 @@ impl Rasterizer {
height -= 1;
}
(width as u32, height as u32)
let w = u32::from(width) << self.vram.upscale_shift;
let h = u32::from(height) << self.vram.upscale_shift;
(w, h)
}
mode => {
let (w, h) = mode.max_resolution();
@ -464,37 +508,42 @@ impl Rasterizer {
// It's possible for this to fail if `vram_display_mode` was just switched and the
// current frame doesn't have the proper dimensions. In this case we're going to return
// a partially drawn leftover frame which is probably not too important.
if self.cur_frame.width >= w && self.cur_frame.height >= h {
for i in 0..((w * h) as usize) {
self.cur_frame.pixels[i] = match mode {
VRamDisplayMode::Native => unreachable!(),
VRamDisplayMode::Full16bpp => self.vram[i].to_rgb888(),
VRamDisplayMode::Full8bpp => {
let p = self.vram[i >> 1].vram_bytes();
if self.cur_frame.width >= u32::from(w) && self.cur_frame.height >= u32::from(h) {
for y in 0..h {
for x in 0..w {
let px_off = usize::from(y) * usize::from(w) + usize::from(x);
self.cur_frame.pixels[px_off] = match mode {
VRamDisplayMode::Native => unreachable!(),
VRamDisplayMode::Full16bpp => {
self.vram.native_pixel(x, y).to_rgb888()
}
VRamDisplayMode::Full8bpp => {
let p = self.vram.native_pixel(x >> 1, y).vram_bytes();
let g = p[i & 1];
let g = p[usize::from(x & 1)];
Pixel::from_rgb(g, g, g).0
}
VRamDisplayMode::Full4bpp => {
let p = self.vram[i >> 2].to_mbgr1555();
let n = [
(p & 0xf) as u8,
((p >> 4) & 0xf) as u8,
((p >> 8) & 0xf) as u8,
((p >> 12) & 0xf) as u8,
];
Pixel::from_rgb(g, g, g).0
}
VRamDisplayMode::Full4bpp => {
let p = self.vram.native_pixel(x >> 2, y).to_mbgr1555();
let n = [
(p & 0xf) as u8,
((p >> 4) & 0xf) as u8,
((p >> 8) & 0xf) as u8,
((p >> 12) & 0xf) as u8,
];
let g = n[i & 3];
let g = g | g << 4;
let g = n[usize::from(x & 3)];
let g = g | g << 4;
Pixel::from_rgb(g, g, g).0
}
};
Pixel::from_rgb(g, g, g).0
}
};
}
}
}
(w, h)
(u32::from(w), u32::from(h))
}
};
@ -513,23 +562,17 @@ impl Rasterizer {
/// Create a new frame with the given `width` and `height` and containing the pixels in the VRAM
/// region locatied at `left`x`top`. Used to implement VRAM reads
fn copy_vram_rect(&mut self, left: u16, top: u16, width: u16, height: u16) -> Frame {
let left = left as u32;
let top = top as u32;
let height = height as u32;
let width = width as u32;
let mut frame = Frame::new(width, height);
let mut frame = Frame::new(u32::from(width), u32::from(height));
for y in 0..height {
let y_off = ((top + y) & 0x1ff) * 1024;
let vram_y = (top + y) & 0x1ff;
for x in 0..width {
let x_off = (left + x) & 0x3ff;
let vram_pos = y_off + x_off;
let vram_x = (left + x) & 0x3ff;
let p = self.vram[vram_pos as usize];
let p = self.vram.native_pixel(vram_x, vram_y);
// XXX I reuse Frame here for simplicity but since VRAM loads are 16 bits we waste
// half the storage here.
frame.set_pixel(x, y, p.to_mbgr1555() as u32);
frame.set_pixel(u32::from(x), u32::from(y), u32::from(p.to_mbgr1555()));
}
}
@ -616,13 +659,21 @@ impl Rasterizer {
}
fn read_pixel(&self, x: i32, y: i32) -> Pixel {
debug_assert!((0..1024).contains(&x), "x out of bounds ({})", x);
debug_assert!((0..1024).contains(&y), "y out of bounds ({})", y);
debug_assert!(
(0..(1024 << self.vram.upscale_shift)).contains(&x),
"x out of bounds ({})",
x
);
debug_assert!(
(0..(1024 << self.vram.upscale_shift)).contains(&y),
"y out of bounds ({})",
y
);
let y = y & 0x1ff;
let vram_off = y * 1024 + x;
let y = (y & ((0x200 << self.vram.upscale_shift) - 1)) as u32;
let x = x as u32;
self.vram[vram_off as usize]
self.vram.pixel(x, y)
}
fn draw_pixel<Transparency, Texture>(&mut self, x: i32, y: i32, mut color: Pixel)
@ -630,18 +681,25 @@ impl Rasterizer {
Transparency: TransparencyMode,
Texture: TextureMode,
{
debug_assert!((0..1024).contains(&x), "x out of bounds ({})", x);
debug_assert!((0..1024).contains(&y), "y out of bounds ({})", y);
debug_assert!(
(0..(1024 << self.vram.upscale_shift)).contains(&x),
"x out of bounds ({})",
x
);
debug_assert!(
(0..(1024 << self.vram.upscale_shift)).contains(&y),
"y out of bounds ({})",
y
);
// Apparently the PlayStation GPU supports 2MB VRAM (1024x1024, used in some arcade
// machines apparently) but the bottom half isn't installed so it wraps around.
let y = y & 0x1ff;
let y = (y & ((0x200 << self.vram.upscale_shift) - 1)) as u32;
let x = x as u32;
let vram_off = y * 1024 + x;
let bg_pixel = self.vram.pixel(x, y);
let vram_pixel = &mut self.vram[vram_off as usize];
if !self.mask_settings.can_draw_to(*vram_pixel) {
if !self.mask_settings.can_draw_to(bg_pixel) {
// Masked
return;
}
@ -652,7 +710,6 @@ impl Rasterizer {
Transparency::is_transparent() && (!Texture::is_textured() || color.mask());
if is_transparent {
let bg_pixel = *vram_pixel;
let mode = self.tex_mapper.draw_mode.transparency_mode();
// XXX if we wanted to be extra-accurate we might want to truncate the color here to
@ -665,14 +722,12 @@ impl Rasterizer {
color.set_mask();
}
} else if self.force_transparency {
let bg_pixel = *vram_pixel;
color.apply_transparency(bg_pixel, TransparencyFunction::Average);
}
color = self.mask_settings.mask(color);
*vram_pixel = color;
self.vram.set_pixel(x, y, color);
}
fn draw_triangle<Transparency, Texture, Shading>(&mut self, mut vertices: [Vertex; 3])
@ -720,7 +775,7 @@ impl Rasterizer {
let y_min = a.position.y;
let y_max = c.position.y;
if y_max - y_min >= 512 {
if y_max - y_min >= (512 << self.vram.upscale_shift) {
// Triangle is too tall, give up
return;
}
@ -749,7 +804,7 @@ impl Rasterizer {
let x_max = vertices.iter().map(|v| v.position.x).max().unwrap();
if x_max - x_min >= 1024 {
if x_max - x_min >= (1024 << self.vram.upscale_shift) {
// Triangle is too large, give up
return;
}
@ -1092,28 +1147,34 @@ impl Rasterizer {
(0, 0)
};
/* We always draw rects at native res */
let clip_x_min = self.clip_x_min >> self.vram.upscale_shift;
let clip_y_min = self.clip_y_min >> self.vram.upscale_shift;
let clip_x_max = self.clip_x_max >> self.vram.upscale_shift;
let clip_y_max = self.clip_y_max >> self.vram.upscale_shift;
let mut x_start = origin.x();
let x_end = min(x_start + width, self.clip_x_max + 1);
let x_end = min(x_start + width, clip_x_max + 1);
let mut y_start = origin.y();
let y_end = min(y_start + height, self.clip_y_max + 1);
let y_end = min(y_start + height, clip_y_max + 1);
if x_start < self.clip_x_min {
if x_start < clip_x_min {
if Texture::is_textured() {
let skip = (self.clip_x_min - x_start) * u_inc;
let skip = (clip_x_min - x_start) * u_inc;
u_start = u_start.wrapping_add(skip as u8);
}
x_start = self.clip_x_min;
x_start = clip_x_min;
}
if y_start < self.clip_y_min {
if y_start < clip_y_min {
if Texture::is_textured() {
let skip = (self.clip_y_min - y_start) * u_inc;
let skip = (clip_y_min - y_start) * u_inc;
v = v.wrapping_add(skip as u8);
}
y_start = self.clip_y_min;
y_start = clip_y_min;
}
if x_end <= x_start || y_end <= y_start {
@ -1141,18 +1202,32 @@ impl Rasterizer {
let texel = self.get_texel(u, v);
// If the pixel is equal to 0 (including mask bit) then we don't draw it
if !texel.is_nul() {
if Texture::is_raw_texture() {
self.draw_pixel::<Transparency, Texture>(x, y, texel);
} else {
// Texture blending: the final color is a combination of the texel and
// the solid color. Rect are never dithered.
let blend = self.blend(texel, origin.color);
self.draw_pixel::<Transparency, Texture>(x, y, blend);
for y in
(y << self.vram.upscale_shift)..((y + 1) << self.vram.upscale_shift)
{
for x in
(x << self.vram.upscale_shift)..((x + 1) << self.vram.upscale_shift)
{
if Texture::is_raw_texture() {
self.draw_pixel::<Transparency, Texture>(x, y, texel);
} else {
// Texture blending: the final color is a combination of the texel and
// the solid color. Rect are never dithered.
let blend = self.blend(texel, origin.color);
self.draw_pixel::<Transparency, Texture>(x, y, blend);
}
}
}
}
} else {
// No texture
self.draw_pixel::<Transparency, Texture>(x, y, color);
for y in (y << self.vram.upscale_shift)..((y + 1) << self.vram.upscale_shift) {
for x in
(x << self.vram.upscale_shift)..((x + 1) << self.vram.upscale_shift)
{
self.draw_pixel::<Transparency, Texture>(x, y, color);
}
}
}
u = u.wrapping_add(u_inc as u8);
}
@ -1188,7 +1263,7 @@ impl Rasterizer {
return;
}
if dx >= 1024 || dy >= 512 {
if dx >= (1024 << self.vram.upscale_shift) || dy >= (512 << self.vram.upscale_shift) {
// Line is too long, ignore
return;
}
@ -1637,7 +1712,7 @@ impl TextureMapper {
}
}
pub fn set_clut(&mut self, clut: u32, vram: &[Pixel; 1024 * 512]) {
pub fn set_clut(&mut self, clut: u32, vram: &VRam) {
let pts = self.pixel_to_texel_shift as u32;
if pts == 0 {
// We're in "truecolor" mode, the palette is not in use
@ -1656,13 +1731,13 @@ impl TextureMapper {
self.clut_tag = tag;
let clut_x = (clut & 0x3f) << 4;
let clut_y = (clut >> 6) & 0x1ff;
let clut_x = ((clut & 0x3f) << 4) as u16;
let clut_y = ((clut >> 6) & 0x1ff) as u16;
let clut_off = (clut_y * 1024 + clut_x) as usize;
// let clut_off = (clut_y * 1024 + clut_x) as usize;
// 256 for 8bpp, 16 for 4bpp
let nentries = 256 >> ((pts - 1) * 4);
let nentries = 256u16 >> ((pts - 1) * 4);
// Dino Crisis 2 sets a weird palette when displaying the Superintendent hologram:
//
@ -1671,7 +1746,7 @@ impl TextureMapper {
// Naturally this goes out of bounds, but I presume that we're supposed to wrap around (not
// tested on real hardware)
for i in 0..nentries {
self.clut_cache[i] = vram[(clut_off + i) % (1024 * 512)];
self.clut_cache[i as usize] = vram.native_pixel((clut_x + i) & 0x3ff, clut_y)
}
}
@ -1717,7 +1792,7 @@ impl TextureMapper {
self.v_offset += tp_y;
}
pub fn get_texel(&mut self, u: u8, v: u8, vram: &[Pixel; 1024 * 512]) -> Pixel {
pub fn get_texel(&mut self, u: u8, v: u8, vram: &VRam) -> Pixel {
let pts = u16::from(self.pixel_to_texel_shift);
let fb_u = u16::from(u & self.u_mask) + self.u_offset;
let fb_v = u16::from(v & self.v_mask) + self.v_offset;
@ -1741,19 +1816,18 @@ impl TextureMapper {
let cacheline = &mut self.texture_cache[usize::from(cache_index)];
let fb_off = u32::from(fb_y) * 1024 + u32::from(fb_x);
let tag = fb_off & !3;
let tag = (u32::from(fb_y) * 1024 + u32::from(fb_x)) & !3;
if cacheline.tag != tag {
// Need to fetch the cacheline
cacheline.tag = tag;
let tag = tag as usize;
cacheline.pixels.copy_from_slice(&vram[tag..(tag + 4)])
for i in 0..4u16 {
cacheline.pixels[usize::from(i)] = vram.native_pixel((fb_x & !3) + i, fb_y);
}
}
let raw = cacheline.pixels[(fb_off & 3) as usize];
let raw = cacheline.pixels[usize::from(fb_x & 3)];
if pts == 0 {
// True color mode, we return the texture value as-is
@ -2053,6 +2127,9 @@ where
vertex.position.x = extend_to_i32(vertex.position.x as u32, 11);
vertex.position.y = extend_to_i32(vertex.position.y as u32, 11);
vertex.position.x <<= rasterizer.vram.upscale_shift;
vertex.position.y <<= rasterizer.vram.upscale_shift;
if Texture::is_textured() {
if v == 0 {
clut = params[index];
@ -2144,6 +2221,9 @@ where
vertex.position.x = extend_to_i32(vertex.position.x as u32, 11);
vertex.position.y = extend_to_i32(vertex.position.y as u32, 11);
vertex.position.x <<= rasterizer.vram.upscale_shift;
vertex.position.y <<= rasterizer.vram.upscale_shift;
if Texture::is_textured() {
if v == 0 {
clut = params[index];
@ -2284,6 +2364,8 @@ where
v.position.x += rasterizer.draw_offset_x;
v.position.y += rasterizer.draw_offset_y;
v.position.x <<= rasterizer.vram.upscale_shift;
v.position.y <<= rasterizer.vram.upscale_shift;
(opcode as u8, v)
}
@ -2301,6 +2383,9 @@ where
end_vertex.position.x += rasterizer.draw_offset_x;
end_vertex.position.y += rasterizer.draw_offset_y;
end_vertex.position.x <<= rasterizer.vram.upscale_shift;
end_vertex.position.y <<= rasterizer.vram.upscale_shift;
rasterizer.draw_line::<Transparency, Shading>(start_vertex, end_vertex.clone());
rasterizer.state = State::PolyLine(opcode, end_vertex);
@ -2323,6 +2408,8 @@ where
index += 1;
start_vertex.position.x += rasterizer.draw_offset_x;
start_vertex.position.y += rasterizer.draw_offset_y;
start_vertex.position.x <<= rasterizer.vram.upscale_shift;
start_vertex.position.y <<= rasterizer.vram.upscale_shift;
let mut end_vertex = Vertex::new(0);
@ -2337,6 +2424,9 @@ where
end_vertex.position.x += rasterizer.draw_offset_x;
end_vertex.position.y += rasterizer.draw_offset_y;
end_vertex.position.x <<= rasterizer.vram.upscale_shift;
end_vertex.position.y <<= rasterizer.vram.upscale_shift;
rasterizer.draw_line::<Transparency, Shading>(start_vertex, end_vertex);
}
@ -2365,11 +2455,11 @@ impl VRamStore {
}
}
fn target_vram_offset(&self) -> usize {
let x = (self.x & 0x3ff) as usize;
let y = (self.y & 0x1ff) as usize;
fn target_vram_offset(&self) -> (u16, u16) {
let x = self.x & 0x3ff;
let y = self.y & 0x1ff;
y * 1024 + x
(x, y)
}
fn next(&mut self) -> Option<()> {
@ -2394,10 +2484,10 @@ fn cmd_vram_copy(rasterizer: &mut Rasterizer, params: &[u32]) {
let dst = params[2];
let dim = params[3];
let src_x = (src & 0x3ff) as i32;
let src_y = ((src >> 16) & 0x3ff) as i32;
let dst_x = (dst & 0x3ff) as i32;
let dst_y = ((dst >> 16) & 0x3ff) as i32;
let src_x = ((src & 0x3ff) as i32) << rasterizer.vram.upscale_shift;
let src_y = (((src >> 16) & 0x3ff) as i32) << rasterizer.vram.upscale_shift;
let dst_x = ((dst & 0x3ff) as i32) << rasterizer.vram.upscale_shift;
let dst_y = (((dst >> 16) & 0x3ff) as i32) << rasterizer.vram.upscale_shift;
let (width, height) = vram_access_dimensions(dim, false);
@ -2405,23 +2495,27 @@ fn cmd_vram_copy(rasterizer: &mut Rasterizer, params: &[u32]) {
// different buffer?
rasterizer.tex_mapper.cache_invalidate();
let xmask = (0x400 << rasterizer.vram.upscale_shift) - 1;
let ymask = (0x200 << rasterizer.vram.upscale_shift) - 1;
for y in 0..height {
let sy = (y + src_y) & 0x1ff;
let ty = (y + dst_y) & 0x1ff;
let sy = (y + src_y) & ymask;
let ty = (y + dst_y) & ymask;
for x in (0..width).step_by(128) {
// The use of a 128px intermediate buffer is taken from mednafen
// XXX should we scale with the upscale_shift?
let mut copy_buf: [Pixel; 128] = [Pixel(0); 128];
let w = std::cmp::min(width - x, 128);
for dx in 0..w {
let sx = (src_x + x + dx) & 0x3ff;
let sx = (src_x + x + dx) & xmask;
copy_buf[dx as usize] = rasterizer.read_pixel(sx, sy);
}
for dx in 0..w {
let tx = (dst_x + x + dx) & 0x3ff;
let tx = (dst_x + x + dx) & xmask;
let p = copy_buf[dx as usize];
@ -2475,11 +2569,11 @@ fn cmd_fill_rect(rasterizer: &mut Rasterizer, params: &[u32]) {
let dst = params[1];
let dim = params[2];
let start_x = dst & 0x3f0;
let start_y = (dst >> 16) & 0x3ff;
let start_x = (dst & 0x3f0) as u16;
let start_y = ((dst >> 16) & 0x3ff) as u16;
let width = ((dim & 0x3ff) + 0xf) & !0xf;
let height = (dim >> 16) & 0x1ff;
let width = (((dim & 0x3ff) + 0xf) & !0xf) as u16;
let height = ((dim >> 16) & 0x1ff) as u16;
// XXX Pretty sure there's no dithering for this commands
let color = rasterizer.truncate_color(color);
@ -2497,8 +2591,7 @@ fn cmd_fill_rect(rasterizer: &mut Rasterizer, params: &[u32]) {
// XXX Probably worth adding a test just in case.
let x_pos = (start_x + x) & 1023;
let vram_index = y_pos * 1024 + x_pos;
rasterizer.vram[vram_index as usize] = color;
rasterizer.vram.set_native_pixel(x_pos, y_pos, color);
}
}
}
@ -2510,15 +2603,19 @@ fn cmd_tex_window(rasterizer: &mut Rasterizer, params: &[u32]) {
fn cmd_clip_top_left(rasterizer: &mut Rasterizer, params: &[u32]) {
let clip = params[0];
rasterizer.clip_x_min = (clip & 0x3ff) as i32;
rasterizer.clip_y_min = ((clip >> 10) & 0x3ff) as i32;
rasterizer.clip_x_min = ((clip & 0x3ff) as i32) << rasterizer.vram.upscale_shift;
rasterizer.clip_y_min = (((clip >> 10) & 0x3ff) as i32) << rasterizer.vram.upscale_shift;
}
fn cmd_clip_bot_right(rasterizer: &mut Rasterizer, params: &[u32]) {
let clip = params[0];
rasterizer.clip_x_max = (clip & 0x3ff) as i32;
rasterizer.clip_y_max = ((clip >> 10) & 0x3ff) as i32;
rasterizer.clip_x_max = ((clip & 0x3ff) as i32) << rasterizer.vram.upscale_shift;
rasterizer.clip_y_max = (((clip >> 10) & 0x3ff) as i32) << rasterizer.vram.upscale_shift;
// The clip is inclusive, so we need to offset when upscaling
rasterizer.clip_x_max += (1 << rasterizer.vram.upscale_shift) - 1;
rasterizer.clip_y_max += (1 << rasterizer.vram.upscale_shift) - 1;
}
fn cmd_draw_mode(rasterizer: &mut Rasterizer, params: &[u32]) {
@ -2551,7 +2648,7 @@ fn cmd_clear_cache(rasterizer: &mut Rasterizer, _params: &[u32]) {
/// Placeholder function
fn cmd_unimplemented(_rasterizer: &mut Rasterizer, params: &[u32]) {
unimplemented!("GPU command {:08x}", params[0]);
warn!("GPU command {:08x}", params[0]);
}
/// LUT for all GP0 commands (indexed by opcode, bits[31:24] of the first command word)
@ -3599,9 +3696,6 @@ pub static GP0_COMMANDS: [CommandHandler; 0x100] = [
},
];
/// Total number of pixels in the VRAM
const VRAM_PIXELS: usize = 1024 * 512;
mod serialize_dither_table {
use serde::{de::Deserializer, ser::Serializer};
@ -3642,3 +3736,116 @@ impl CacheLine {
self.tag = CacheLine::TAG_INVALID;
}
}
struct VRam {
pixels: Vec<Pixel>,
/// Upscale shift value. 0 for native.
upscale_shift: u8,
}
impl VRam {
fn with_upscale_shift(upscale_shift: u8) -> VRam {
VRam {
pixels: vec![Pixel::black(); (1024 << upscale_shift) * (512 << upscale_shift)],
upscale_shift,
}
}
/// Returns the pixel at x, y where x an y are in native 1x coordinates.
fn native_pixel(&self, x: u16, y: u16) -> Pixel {
self.pixel(
u32::from(x) << self.upscale_shift,
u32::from(y) << self.upscale_shift,
)
}
/// Sets the pixel at x, y where x an y are in native 1x coordinates.
fn set_native_pixel(&mut self, x: u16, y: u16, p: Pixel) {
for yo in 0..(1 << self.upscale_shift) {
for xo in 0..(1 << self.upscale_shift) {
self.set_pixel(
(u32::from(x) << self.upscale_shift) + xo,
(u32::from(y) << self.upscale_shift) + yo,
p,
)
}
}
}
/// Returns the pixel at x, y where x and y are in upscaled coordinates
fn pixel(&self, x: u32, y: u32) -> Pixel {
let x = x as usize;
let y = y as usize;
self.pixels[(1024 << self.upscale_shift) * y + x]
}
/// Sets the pixel at x, y where x and y are in upscaled coordinates
fn set_pixel(&mut self, x: u32, y: u32, p: Pixel) {
let x = x as usize;
let y = y as usize;
self.pixels[(1024 << self.upscale_shift) * y + x] = p
}
}
impl Serialize for VRam {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut tup = serializer.serialize_tuple(1024 * 512)?;
for y in 0..512 {
for x in 0..1024 {
tup.serialize_element(&self.native_pixel(x, y))?;
}
}
tup.end()
}
}
impl<'de> Deserialize<'de> for VRam {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct VRamVisitor {
phantom: PhantomData<VRam>,
}
impl<'de> Visitor<'de> for VRamVisitor {
type Value = VRam;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "an array of 1024 * 512 pixels")
}
fn visit_seq<S>(self, mut seq: S) -> std::result::Result<VRam, S::Error>
where
S: SeqAccess<'de>,
{
let mut vram = VRam::with_upscale_shift(0);
for y in 0..512 {
for x in 0..1024 {
let p = match seq.next_element()? {
Some(p) => p,
None => return Err(de::Error::invalid_length(y * 1024 + x, &self)),
};
vram.set_native_pixel(x as u16, y as u16, p);
}
}
Ok(vram)
}
}
let visitor = VRamVisitor {
phantom: PhantomData,
};
deserializer.deserialize_tuple(1024 * 512, visitor)
}
}

View file

@ -227,6 +227,7 @@ pub enum RasterizerOption {
DitherForceDisable(bool),
Wireframe(bool),
DrawPolygons(bool),
UpscaleShift(u8),
}
/// Buffer containing one rendered frame

View file

@ -4,6 +4,7 @@ pub mod bios;
pub mod cd;
pub mod cop0;
pub mod cpu;
#[cfg(feature = "debugger")]
pub mod debugger;
mod dma;
pub mod gpu;