Skip to content

Commit

Permalink
[GG] add an option to render Game Gear software at SMS resolution (25…
Browse files Browse the repository at this point in the history
…6x192)
  • Loading branch information
jsgroth committed Oct 1, 2024
1 parent 5ddcc0e commit fe4d673
Show file tree
Hide file tree
Showing 8 changed files with 89 additions and 47 deletions.
44 changes: 18 additions & 26 deletions backend/smsgg-core/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,13 @@ impl DerefMut for FrameBuffer {
}
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)]
pub enum SmsGgHardware {
MasterSystem,
GameGear,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, EnumDisplay, EnumFromStr)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode, EnumDisplay, EnumFromStr)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum SmsModel {
Sms1,
Expand All @@ -82,7 +82,7 @@ pub enum SmsRegion {
Domestic,
}

#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone, Copy, Encode, Decode)]
pub struct SmsGgEmulatorConfig {
pub hardware: SmsGgHardware,
pub sms_timing_mode: TimingMode,
Expand All @@ -93,6 +93,7 @@ pub struct SmsGgEmulatorConfig {
pub sms_region: SmsRegion,
pub sms_crop_vertical_border: bool,
pub sms_crop_left_border: bool,
pub gg_use_sms_resolution: bool,
pub fm_sound_unit_enabled: bool,
pub overclock_z80: bool,
}
Expand All @@ -110,9 +111,7 @@ pub struct SmsGgEmulator {
input: InputState,
audio_resampler: AudioResampler,
frame_buffer: FrameBuffer,
sms_crop_vertical_border: bool,
sms_crop_left_border: bool,
overclock_z80: bool,
config: SmsGgEmulatorConfig,
z80_cycles_remainder: u32,
vdp_cycles_remainder: u32,
frame_count: u64,
Expand All @@ -137,7 +136,7 @@ impl SmsGgEmulator {
log::info!("PSG version: {psg_version:?}");

let memory = Memory::new(rom, cartridge_ram);
let vdp = Vdp::new(vdp_version, config.remove_sprite_limit);
let vdp = Vdp::new(vdp_version, &config);
let psg = Psg::new(psg_version);
let input = InputState::new(config.sms_region);

Expand All @@ -159,9 +158,7 @@ impl SmsGgEmulator {
input,
audio_resampler: AudioResampler::new(timing_mode),
frame_buffer: FrameBuffer::new(),
sms_crop_vertical_border: config.sms_crop_vertical_border,
sms_crop_left_border: config.sms_crop_left_border,
overclock_z80: config.overclock_z80,
config,
z80_cycles_remainder: 0,
vdp_cycles_remainder: 0,
frame_count: 0,
Expand Down Expand Up @@ -190,24 +187,21 @@ impl SmsGgEmulator {
}

fn render_frame<R: Renderer>(&mut self, renderer: &mut R) -> Result<(), R::Err> {
let crop_vertical_border =
self.vdp_version.is_master_system() && self.sms_crop_vertical_border;
let crop_left_border = self.vdp_version.is_master_system() && self.sms_crop_left_border;
populate_frame_buffer(
self.vdp.frame_buffer(),
self.vdp_version,
crop_vertical_border,
crop_left_border,
self.config.sms_crop_vertical_border,
self.config.sms_crop_left_border,
&mut self.frame_buffer,
);

let viewport = self.vdp_version.viewport_size();
let frame_width = if crop_left_border {
let viewport = self.vdp.viewport();
let frame_width = if self.config.sms_crop_left_border {
viewport.width_without_border().into()
} else {
viewport.width.into()
};
let frame_height = if crop_vertical_border {
let frame_height = if self.config.sms_crop_vertical_border {
viewport.height_without_border().into()
} else {
viewport.height.into()
Expand Down Expand Up @@ -295,7 +289,7 @@ impl EmulatorTrait for SmsGgEmulator {
self.ym2413.as_mut(),
&mut self.input,
));
let (t_cycles, remainder) = if self.overclock_z80 {
let (t_cycles, remainder) = if self.config.overclock_z80 {
// Emulate a Z80 running at 2x speed by only ticking the rest of the components for
// half as many cycles
let t_cycles = t_cycles + self.z80_cycles_remainder;
Expand Down Expand Up @@ -364,17 +358,15 @@ impl EmulatorTrait for SmsGgEmulator {
}

fn reload_config(&mut self, config: &Self::Config) {
self.config = *config;

self.vdp_version = determine_vdp_version(config);
self.vdp.set_version(self.vdp_version);
self.vdp.update_config(self.vdp_version, config);

self.psg.set_version(determine_psg_version(config));

self.pixel_aspect_ratio = config.pixel_aspect_ratio;
self.vdp.set_remove_sprite_limit(config.remove_sprite_limit);
self.input.set_region(config.sms_region);
self.sms_crop_vertical_border = config.sms_crop_vertical_border;
self.sms_crop_left_border = config.sms_crop_left_border;
self.overclock_z80 = config.overclock_z80;
self.audio_resampler.update_timing_mode(self.vdp.timing_mode());
}

Expand All @@ -399,7 +391,7 @@ impl EmulatorTrait for SmsGgEmulator {
self.z80 = Z80::new();
init_z80(&mut self.z80);

self.vdp = Vdp::new(self.vdp_version, self.vdp.get_remove_sprite_limit());
self.vdp = Vdp::new(self.vdp_version, &self.config);
self.psg = Psg::new(self.psg.version());
self.input = InputState::new(self.input.region());

Expand All @@ -419,7 +411,7 @@ fn populate_frame_buffer(
crop_left_border: bool,
frame_buffer: &mut [Color],
) {
let viewport = vdp_version.viewport_size();
let viewport = vdp_buffer.viewport();

let (row_skip, row_take) = if crop_vertical_border {
(viewport.top_border_height as usize, viewport.height_without_border() as usize)
Expand Down
63 changes: 42 additions & 21 deletions backend/smsgg-core/src/vdp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
mod debug;
mod tms9918;

use crate::SmsGgEmulatorConfig;
use bincode::de::{BorrowDecoder, Decoder};
use bincode::enc::Encoder;
use bincode::error::{DecodeError, EncodeError};
Expand All @@ -28,7 +29,7 @@ pub struct ViewportSize {
}

impl ViewportSize {
const NTSC_SMS2: Self = Self {
const NTSC_SMS: Self = Self {
width: 256,
height: 224,
top: 0,
Expand All @@ -38,7 +39,7 @@ impl ViewportSize {
left_border_width: 8,
};

const PAL_SMS2: Self = Self {
const PAL_SMS: Self = Self {
width: 256,
height: 240,
top: 0,
Expand All @@ -58,6 +59,16 @@ impl ViewportSize {
left_border_width: 0,
};

const GAME_GEAR_EXPANDED: Self = Self {
width: 256,
height: 192,
top: 0,
left: 0,
top_border_height: 0,
bottom_border_height: 0,
left_border_width: 8,
};

pub fn height_without_border(self) -> u16 {
self.height - self.top_border_height - self.bottom_border_height
}
Expand Down Expand Up @@ -113,11 +124,17 @@ impl VdpVersion {
}

#[must_use]
pub const fn viewport_size(self) -> ViewportSize {
const fn viewport_size(self, gg_use_sms_resolution: bool) -> ViewportSize {
match self {
Self::NtscMasterSystem1 | Self::NtscMasterSystem2 => ViewportSize::NTSC_SMS2,
Self::PalMasterSystem1 | Self::PalMasterSystem2 => ViewportSize::PAL_SMS2,
Self::GameGear => ViewportSize::GAME_GEAR,
Self::NtscMasterSystem1 | Self::NtscMasterSystem2 => ViewportSize::NTSC_SMS,
Self::PalMasterSystem1 | Self::PalMasterSystem2 => ViewportSize::PAL_SMS,
Self::GameGear => {
if gg_use_sms_resolution {
ViewportSize::GAME_GEAR_EXPANDED
} else {
ViewportSize::GAME_GEAR
}
}
}
}
}
Expand Down Expand Up @@ -599,8 +616,11 @@ pub struct VdpBuffer {
}

impl VdpBuffer {
fn new(version: VdpVersion) -> Self {
Self { buffer: vec![0; FRAME_BUFFER_LEN], viewport: version.viewport_size() }
fn new(version: VdpVersion, gg_use_sms_resolution: bool) -> Self {
Self {
buffer: vec![0; FRAME_BUFFER_LEN],
viewport: version.viewport_size(gg_use_sms_resolution),
}
}

#[inline]
Expand All @@ -623,6 +643,10 @@ impl VdpBuffer {
pub fn iter(&self) -> FrameBufferRowIter<'_> {
FrameBufferRowIter { buffer: self, row: 0 }
}

pub fn viewport(&self) -> ViewportSize {
self.viewport
}
}

impl Encode for VdpBuffer {
Expand Down Expand Up @@ -703,28 +727,20 @@ pub enum VdpTickEffect {
}

impl Vdp {
pub fn new(version: VdpVersion, remove_sprite_limit: bool) -> Self {
pub fn new(version: VdpVersion, config: &SmsGgEmulatorConfig) -> Self {
Self {
frame_buffer: VdpBuffer::new(version),
frame_buffer: VdpBuffer::new(version, config.gg_use_sms_resolution),
registers: Registers::new(version),
vram: [0; VRAM_SIZE],
color_ram: [0; COLOR_RAM_SIZE],
scanline: 0,
dot: 0,
sprite_buffer: SpriteBuffer::new(),
remove_sprite_limit,
remove_sprite_limit: config.remove_sprite_limit,
line_counter: 0xFF,
}
}

pub fn get_remove_sprite_limit(&self) -> bool {
self.remove_sprite_limit
}

pub fn set_remove_sprite_limit(&mut self, remove_sprite_limit: bool) {
self.remove_sprite_limit = remove_sprite_limit;
}

fn read_color_ram_word(&self, address: u8) -> u16 {
if self.registers.version.is_master_system() {
self.color_ram[address as usize].into()
Expand Down Expand Up @@ -1025,6 +1041,10 @@ impl Vdp {
&self.frame_buffer
}

pub fn viewport(&self) -> ViewportSize {
self.frame_buffer.viewport
}

pub fn read_control(&mut self) -> u8 {
self.registers.read_control()
}
Expand Down Expand Up @@ -1090,9 +1110,10 @@ impl Vdp {
self.registers.version.timing_mode()
}

pub fn set_version(&mut self, version: VdpVersion) {
pub fn update_config(&mut self, version: VdpVersion, config: &SmsGgEmulatorConfig) {
self.registers.version = version;
self.frame_buffer.viewport = version.viewport_size();
self.frame_buffer.viewport = version.viewport_size(config.gg_use_sms_resolution);
self.remove_sprite_limit = config.remove_sprite_limit;
}
}

Expand Down
5 changes: 5 additions & 0 deletions frontend/jgenesis-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,10 @@ struct Args {
#[arg(long, help_heading = SMSGG_OPTIONS_HEADING)]
sms_crop_left_border: Option<bool>,

/// For Game Gear, render at SMS resolution (256x192) instead of native resolution (160x144)
#[arg(long, help_heading = SMSGG_OPTIONS_HEADING)]
gg_use_sms_resolution: Option<bool>,

/// Enable SMS FM sound unit
#[arg(long, help_heading = SMSGG_OPTIONS_HEADING)]
sms_fm_unit_enabled: Option<bool>,
Expand Down Expand Up @@ -464,6 +468,7 @@ impl Args {
sms_region,
sms_crop_vertical_border,
sms_crop_left_border,
gg_use_sms_resolution,
sms_fm_unit_enabled -> fm_sound_unit_enabled,
smsgg_overclock_z80 -> overclock_z80,
]);
Expand Down
10 changes: 10 additions & 0 deletions frontend/jgenesis-gui/src/app/smsgg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,16 @@ impl App {
self.state.help_text.insert(WINDOW, helptext::SMS_CROP_LEFT_BORDER);
}

let rect = ui
.checkbox(
&mut self.config.smsgg.gg_use_sms_resolution,
"(Game Gear) Display in SMS resolution",
)
.interact_rect;
if ui.rect_contains_pointer(rect) {
self.state.help_text.insert(WINDOW, helptext::GG_USE_SMS_RESOLUTION);
}

self.render_help_text(ui, WINDOW);
});
if !open {
Expand Down
8 changes: 8 additions & 0 deletions frontend/jgenesis-gui/src/app/smsgg/helptext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,14 @@ pub const SMS_CROP_LEFT_BORDER: HelpText = HelpText {
],
};

pub const GG_USE_SMS_RESOLUTION: HelpText = HelpText {
heading: "Game Gear Expanded Resolution",
text: &[
"If enabled, display the full 256x192 frame rendered by the VDP rather than only the center 160x144 pixels.",
"Only the center pixels display on actual hardware, so the expanded parts of the frame may contain garbage.",
],
};

pub const PSG_VERSION: HelpText = HelpText {
heading: "PSG Version",
text: &[
Expand Down
3 changes: 3 additions & 0 deletions frontend/jgenesis-native-config/src/smsgg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ pub struct SmsGgAppConfig {
pub sms_crop_vertical_border: bool,
#[serde(default)]
pub sms_crop_left_border: bool,
#[serde(default)]
pub gg_use_sms_resolution: bool,
#[serde(default = "true_fn")]
pub fm_sound_unit_enabled: bool,
#[serde(default)]
Expand Down Expand Up @@ -58,6 +60,7 @@ impl AppConfig {
sms_region: self.smsgg.sms_region,
sms_crop_vertical_border: self.smsgg.sms_crop_vertical_border,
sms_crop_left_border: self.smsgg.sms_crop_left_border,
gg_use_sms_resolution: self.smsgg.gg_use_sms_resolution,
fm_sound_unit_enabled: self.smsgg.fm_sound_unit_enabled,
overclock_z80: self.smsgg.overclock_z80,
})
Expand Down
2 changes: 2 additions & 0 deletions frontend/jgenesis-native-driver/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ pub struct SmsGgConfig {
pub sms_region: SmsRegion,
pub sms_crop_vertical_border: bool,
pub sms_crop_left_border: bool,
pub gg_use_sms_resolution: bool,
pub fm_sound_unit_enabled: bool,
pub overclock_z80: bool,
}
Expand All @@ -199,6 +200,7 @@ impl SmsGgConfig {
sms_region: self.sms_region,
sms_crop_vertical_border: self.sms_crop_vertical_border,
sms_crop_left_border: self.sms_crop_left_border,
gg_use_sms_resolution: self.gg_use_sms_resolution,
fm_sound_unit_enabled: self.fm_sound_unit_enabled,
overclock_z80: self.overclock_z80,
}
Expand Down
1 change: 1 addition & 0 deletions frontend/jgenesis-web/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ impl SmsGgWebConfig {
remove_sprite_limit: self.remove_sprite_limit,
sms_crop_left_border: self.sms_crop_left_border,
sms_crop_vertical_border: self.sms_crop_vertical_border,
gg_use_sms_resolution: false,
fm_sound_unit_enabled: self.fm_unit_enabled,
overclock_z80: false,
}
Expand Down

0 comments on commit fe4d673

Please sign in to comment.