update DosLike system resource with variable screen size support
also re-did DosLikeConfig at the same time, making usage a bit clearer
This commit is contained in:
parent
e18238405c
commit
7143430769
123
ggdt/src/system/framebuffer.rs
Normal file
123
ggdt/src/system/framebuffer.rs
Normal file
|
@ -0,0 +1,123 @@
|
|||
use byte_slice_cast::AsByteSlice;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::graphics::bitmap::indexed::IndexedBitmap;
|
||||
use crate::graphics::bitmap::rgb::RgbaBitmap;
|
||||
use crate::graphics::palette::Palette;
|
||||
|
||||
pub fn calculate_logical_screen_size(window_width: u32, window_height: u32, scale_factor: u32) -> (u32, u32) {
|
||||
let logical_width = (window_width as f32 / scale_factor as f32).ceil() as u32;
|
||||
let logical_height = (window_height as f32 / scale_factor as f32).ceil() as u32;
|
||||
(logical_width, logical_height)
|
||||
}
|
||||
|
||||
const SCREEN_TEXTURE_PIXEL_SIZE: usize = 4; // 32-bit ARGB format
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum SdlFramebufferError {
|
||||
#[error("SdlFramebufferError SDL error: {0}")]
|
||||
SDLError(String),
|
||||
}
|
||||
|
||||
/// Internal structure to manager the SDL renderer/canvas and texture bits surrounding how we display our
|
||||
/// [`SystemResources`] implementation managed [`Bitmap`][Bitmap] backbuffer to the actual screen.
|
||||
///
|
||||
/// [Bitmap]: crate::graphics::bitmap::Bitmap
|
||||
pub struct SdlFramebuffer {
|
||||
sdl_texture: sdl2::render::Texture,
|
||||
sdl_texture_pitch: usize,
|
||||
intermediate_texture: Option<Box<[u32]>>,
|
||||
}
|
||||
|
||||
// TODO: i'm not totally happy with this implementation. i don't like the two display methods and how the caller
|
||||
// can technically call the wrong method. while i'm quite sure this won't happen in practice, it still feels
|
||||
// like a bad design which we could do better. but this is simple for now, so i'll leave it for the time being.
|
||||
// since this is all not in a public module, that seems like less of a big deal to me as well. for now.
|
||||
impl SdlFramebuffer {
|
||||
pub fn new(
|
||||
canvas: &mut sdl2::render::WindowCanvas,
|
||||
logical_screen_width: u32,
|
||||
logical_screen_height: u32,
|
||||
create_intermediate_texture: bool,
|
||||
) -> Result<Self, SdlFramebufferError> {
|
||||
// this sets up screen/window resolution independant rendering on the SDL-side of things
|
||||
// which we may or may not actually need, but this ALSO changes the way that SDL reports things
|
||||
// like mouse cursor coordinates. so we benefit from setting the canvas logical screen size
|
||||
// to always match our window size, even when we are using variable screen sizes.
|
||||
if let Err(error) = canvas.set_logical_size(logical_screen_width, logical_screen_height) {
|
||||
return Err(SdlFramebufferError::SDLError(error.to_string()));
|
||||
}
|
||||
|
||||
let sdl_texture = match canvas.create_texture_streaming(
|
||||
Some(sdl2::pixels::PixelFormatEnum::ARGB8888),
|
||||
logical_screen_width,
|
||||
logical_screen_height,
|
||||
) {
|
||||
Ok(texture) => texture,
|
||||
Err(error) => return Err(SdlFramebufferError::SDLError(error.to_string())),
|
||||
};
|
||||
let sdl_texture_pitch = sdl_texture.query().width as usize * SCREEN_TEXTURE_PIXEL_SIZE;
|
||||
|
||||
let intermediate_texture = if create_intermediate_texture {
|
||||
// create a raw 32-bit RGBA buffer that will be used as the temporary source for
|
||||
// SDL texture uploads each frame. necessary as applications are dealing with 8-bit indexed
|
||||
// bitmaps, not 32-bit RGBA pixels, so this temporary buffer is where we convert the final
|
||||
// application framebuffer to 32-bit RGBA pixels before it is uploaded to the SDL texture
|
||||
let texture_pixels_size =
|
||||
(logical_screen_width * logical_screen_height) as usize * SCREEN_TEXTURE_PIXEL_SIZE;
|
||||
Some(vec![0u32; texture_pixels_size].into_boxed_slice())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(SdlFramebuffer { sdl_texture, sdl_texture_pitch, intermediate_texture })
|
||||
}
|
||||
|
||||
pub fn display_indexed_bitmap(
|
||||
&mut self,
|
||||
canvas: &mut sdl2::render::WindowCanvas,
|
||||
src: &IndexedBitmap,
|
||||
palette: &Palette,
|
||||
) -> Result<(), SdlFramebufferError> {
|
||||
let intermediate_texture = &mut self.intermediate_texture.as_mut().expect(
|
||||
"Calls to display_indexed_bitmap should only occur on SdlFramebuffers with an intermediate_texture",
|
||||
);
|
||||
|
||||
src.copy_as_argb_to(intermediate_texture, palette);
|
||||
|
||||
let texture_pixels = intermediate_texture.as_byte_slice();
|
||||
if let Err(error) = self.sdl_texture.update(None, texture_pixels, self.sdl_texture_pitch) {
|
||||
return Err(SdlFramebufferError::SDLError(error.to_string()));
|
||||
}
|
||||
canvas.clear();
|
||||
if let Err(error) = canvas.copy(&self.sdl_texture, None, None) {
|
||||
return Err(SdlFramebufferError::SDLError(error));
|
||||
}
|
||||
canvas.present();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn display(
|
||||
&mut self,
|
||||
canvas: &mut sdl2::render::WindowCanvas,
|
||||
src: &RgbaBitmap,
|
||||
) -> Result<(), SdlFramebufferError> {
|
||||
assert!(
|
||||
self.intermediate_texture.is_none(),
|
||||
"Calls to display should only occur on SdlFramebuffers without an intermediate_texture"
|
||||
);
|
||||
|
||||
let texture_pixels = src.pixels().as_byte_slice();
|
||||
if let Err(error) = self.sdl_texture.update(None, texture_pixels, self.sdl_texture_pitch) {
|
||||
return Err(SdlFramebufferError::SDLError(error.to_string()));
|
||||
}
|
||||
canvas.clear();
|
||||
if let Err(error) = canvas.copy(&self.sdl_texture, None, None) {
|
||||
return Err(SdlFramebufferError::SDLError(error));
|
||||
}
|
||||
canvas.present();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -8,6 +8,7 @@ pub mod event;
|
|||
pub mod input_devices;
|
||||
pub mod res;
|
||||
|
||||
mod framebuffer;
|
||||
pub mod prelude;
|
||||
|
||||
fn is_x11_compositor_skipping_problematic() -> bool {
|
||||
|
|
|
@ -26,14 +26,14 @@
|
|||
//! ```
|
||||
//!
|
||||
|
||||
use byte_slice_cast::AsByteSlice;
|
||||
|
||||
use crate::audio::queue::AudioQueue;
|
||||
use crate::audio::{Audio, TARGET_AUDIO_CHANNELS, TARGET_AUDIO_FREQUENCY};
|
||||
use crate::graphics::bitmap::indexed::IndexedBitmap;
|
||||
use crate::graphics::font::BitmaskFont;
|
||||
use crate::graphics::palette::Palette;
|
||||
use crate::prelude::WindowEvent;
|
||||
use crate::system::event::{SystemEvent, SystemEventHandler};
|
||||
use crate::system::framebuffer::{calculate_logical_screen_size, SdlFramebuffer};
|
||||
use crate::system::input_devices::keyboard::Keyboard;
|
||||
use crate::system::input_devices::mouse::cursor::CustomMouseCursor;
|
||||
use crate::system::input_devices::mouse::Mouse;
|
||||
|
@ -48,22 +48,48 @@ const DEFAULT_SCALE_FACTOR: u32 = 3;
|
|||
pub struct DosLikeConfig {
|
||||
screen_width: u32,
|
||||
screen_height: u32,
|
||||
fixed_screen_size: bool,
|
||||
initial_scale_factor: u32,
|
||||
integer_scaling: bool,
|
||||
}
|
||||
|
||||
impl DosLikeConfig {
|
||||
impl Default for DosLikeConfig {
|
||||
/// Returns a new [`DosLikeConfig`] with a default configuration.
|
||||
pub fn new() -> Self {
|
||||
fn default() -> Self {
|
||||
DosLikeConfig::fixed_screen_size(DEFAULT_SCREEN_WIDTH, DEFAULT_SCREEN_HEIGHT, false)
|
||||
}
|
||||
}
|
||||
|
||||
impl DosLikeConfig {
|
||||
/// Creates a configuration that will use a fixed screen size at a set scaling factor. Any window resizing
|
||||
/// will simply scale up or down the final image on screen, but the application will always use the same
|
||||
/// logical screen resolution, `screen_width` and `screen_height`, at runtime.
|
||||
pub fn fixed_screen_size(screen_width: u32, screen_height: u32, integer_scaling: bool) -> Self {
|
||||
DosLikeConfig {
|
||||
screen_width: DEFAULT_SCREEN_WIDTH,
|
||||
screen_height: DEFAULT_SCREEN_HEIGHT,
|
||||
screen_width,
|
||||
screen_height,
|
||||
initial_scale_factor: DEFAULT_SCALE_FACTOR,
|
||||
integer_scaling: false,
|
||||
integer_scaling,
|
||||
fixed_screen_size: true,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: add customization ability for setting different screen dimensions instead of it being hardcoded
|
||||
/// Creates a configuration that allows the screen size to be automatically updated at runtime to match the
|
||||
/// current window size, including any arbitrary user window resizing. The final image on screen will always be
|
||||
/// scaled up by the factor given. The logical screen size at runtime (as seen by the application code) is
|
||||
/// always based on:
|
||||
///
|
||||
/// `logical_screen_width = ceil(window_width / scale_factor)`
|
||||
/// `logical_screen_height = ceil(window_height / scale_factor)`
|
||||
pub fn variable_screen_size(initial_width: u32, initial_height: u32) -> Self {
|
||||
DosLikeConfig {
|
||||
screen_width: initial_width,
|
||||
screen_height: initial_height,
|
||||
initial_scale_factor: DEFAULT_SCALE_FACTOR,
|
||||
integer_scaling: false,
|
||||
fixed_screen_size: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets an integer scaling factor for the [`System`] being built to up-scale the virtual
|
||||
/// framebuffer to when displaying it on screen.
|
||||
|
@ -71,13 +97,6 @@ impl DosLikeConfig {
|
|||
self.initial_scale_factor = scale_factor;
|
||||
self
|
||||
}
|
||||
|
||||
/// Enables or disables restricting the final rendered output to always be integer scaled,
|
||||
/// even if that result will not fully fill the area of the window.
|
||||
pub fn integer_scaling(mut self, enable: bool) -> Self {
|
||||
self.integer_scaling = enable;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl SystemResourcesConfig for DosLikeConfig {
|
||||
|
@ -89,8 +108,6 @@ impl SystemResourcesConfig for DosLikeConfig {
|
|||
audio_subsystem: &sdl2::AudioSubsystem,
|
||||
mut window: sdl2::video::Window,
|
||||
) -> Result<Self::SystemResourcesType, SystemResourcesError> {
|
||||
let texture_pixel_size = 4; // 32-bit ARGB format
|
||||
|
||||
let window_width = self.screen_width * self.initial_scale_factor;
|
||||
let window_height = self.screen_height * self.initial_scale_factor;
|
||||
if let Err(error) = window.set_size(window_width, window_height) {
|
||||
|
@ -104,9 +121,6 @@ impl SystemResourcesConfig for DosLikeConfig {
|
|||
Ok(canvas) => canvas,
|
||||
Err(error) => return Err(SystemResourcesError::SDLError(error.to_string())),
|
||||
};
|
||||
if let Err(error) = sdl_canvas.set_logical_size(self.screen_width, self.screen_height) {
|
||||
return Err(SystemResourcesError::SDLError(error.to_string()));
|
||||
};
|
||||
|
||||
// TODO: newer versions of rust-sdl2 support this directly off the WindowCanvas struct
|
||||
unsafe {
|
||||
|
@ -120,30 +134,14 @@ impl SystemResourcesConfig for DosLikeConfig {
|
|||
);
|
||||
}
|
||||
|
||||
// create an SDL texture which we will be uploading to every frame to display the
|
||||
// application's framebuffer
|
||||
// create the SDL framebuffer at the initial logical screen size
|
||||
|
||||
let sdl_texture = match sdl_canvas.create_texture_streaming(
|
||||
Some(sdl2::pixels::PixelFormatEnum::ARGB8888),
|
||||
self.screen_width,
|
||||
self.screen_height,
|
||||
) {
|
||||
Ok(texture) => texture,
|
||||
Err(error) => return Err(SystemResourcesError::SDLError(error.to_string())),
|
||||
};
|
||||
let sdl_texture_pitch = (sdl_texture.query().width * texture_pixel_size) as usize;
|
||||
|
||||
// create a raw 32-bit RGBA buffer that will be used as the temporary source for
|
||||
// SDL texture uploads each frame. necessary as applications are dealing with 8-bit indexed
|
||||
// bitmaps, not 32-bit RGBA pixels, so this temporary buffer is where we convert the final
|
||||
// application framebuffer to 32-bit RGBA pixels before it is uploaded to the SDL texture
|
||||
let texture_pixels_size = (self.screen_width * self.screen_height * texture_pixel_size) as usize;
|
||||
let texture_pixels = vec![0u32; texture_pixels_size].into_boxed_slice();
|
||||
let framebuffer = SdlFramebuffer::new(&mut sdl_canvas, self.screen_width, self.screen_height, true)?;
|
||||
|
||||
// create the Bitmap object that will be exposed to the application acting as the system
|
||||
// backbuffer
|
||||
|
||||
let framebuffer = match IndexedBitmap::new(self.screen_width, self.screen_height) {
|
||||
let screen_bitmap = match IndexedBitmap::new(self.screen_width, self.screen_height) {
|
||||
Ok(bmp) => bmp,
|
||||
Err(error) => return Err(SystemResourcesError::SDLError(error.to_string())),
|
||||
};
|
||||
|
@ -182,13 +180,13 @@ impl SystemResourcesConfig for DosLikeConfig {
|
|||
|
||||
Ok(DosLike {
|
||||
sdl_canvas,
|
||||
sdl_texture,
|
||||
sdl_texture_pitch,
|
||||
texture_pixels,
|
||||
framebuffer,
|
||||
scale_factor: self.initial_scale_factor,
|
||||
fixed_screen_size: self.fixed_screen_size,
|
||||
audio,
|
||||
audio_queue,
|
||||
palette,
|
||||
video: framebuffer,
|
||||
video: screen_bitmap,
|
||||
font,
|
||||
keyboard,
|
||||
mouse,
|
||||
|
@ -201,9 +199,9 @@ impl SystemResourcesConfig for DosLikeConfig {
|
|||
/// audio via [`Audio`] and keyboard/mouse input.
|
||||
pub struct DosLike {
|
||||
sdl_canvas: sdl2::render::WindowCanvas,
|
||||
sdl_texture: sdl2::render::Texture,
|
||||
sdl_texture_pitch: usize,
|
||||
texture_pixels: Box<[u32]>,
|
||||
framebuffer: SdlFramebuffer,
|
||||
scale_factor: u32,
|
||||
fixed_screen_size: bool,
|
||||
|
||||
/// An [`Audio`] instance that allows interacting with the system's audio output device.
|
||||
pub audio: Audio,
|
||||
|
@ -267,24 +265,8 @@ impl SystemResources for DosLike {
|
|||
/// to fill the window (preserving aspect ratio of course).
|
||||
fn display(&mut self) -> Result<(), SystemResourcesError> {
|
||||
self.cursor.render(&mut self.video);
|
||||
|
||||
// convert application framebuffer to 32-bit RGBA pixels, and then upload it to the SDL
|
||||
// texture so it will be displayed on screen
|
||||
|
||||
self.video.copy_as_argb_to(&mut self.texture_pixels, &self.palette);
|
||||
|
||||
let texture_pixels = self.texture_pixels.as_byte_slice();
|
||||
if let Err(error) = self.sdl_texture.update(None, texture_pixels, self.sdl_texture_pitch) {
|
||||
return Err(SystemResourcesError::SDLError(error.to_string()));
|
||||
}
|
||||
self.sdl_canvas.clear();
|
||||
if let Err(error) = self.sdl_canvas.copy(&self.sdl_texture, None, None) {
|
||||
return Err(SystemResourcesError::SDLError(error));
|
||||
}
|
||||
self.sdl_canvas.present();
|
||||
|
||||
self.framebuffer.display_indexed_bitmap(&mut self.sdl_canvas, &self.video, &self.palette)?;
|
||||
self.cursor.hide(&mut self.video);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -295,6 +277,13 @@ impl SystemResources for DosLike {
|
|||
}
|
||||
|
||||
fn handle_event(&mut self, event: &SystemEvent) -> Result<bool, SystemResourcesError> {
|
||||
if let SystemEvent::Window(WindowEvent::SizeChanged(width, height)) = event {
|
||||
if !self.fixed_screen_size {
|
||||
self.resize_screen(*width as u32, *height as u32)?;
|
||||
}
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
if self.keyboard.handle_event(event) {
|
||||
return Ok(true);
|
||||
}
|
||||
|
@ -314,3 +303,21 @@ impl SystemResources for DosLike {
|
|||
self.video.height()
|
||||
}
|
||||
}
|
||||
|
||||
impl DosLike {
|
||||
fn resize_screen(&mut self, new_width: u32, new_height: u32) -> Result<(), SystemResourcesError> {
|
||||
let (logical_width, logical_height) = calculate_logical_screen_size(new_width, new_height, self.scale_factor);
|
||||
|
||||
let framebuffer = SdlFramebuffer::new(&mut self.sdl_canvas, logical_width, logical_height, true)?;
|
||||
|
||||
let screen_bitmap = match IndexedBitmap::new(logical_width, logical_height) {
|
||||
Ok(bmp) => bmp,
|
||||
Err(error) => return Err(SystemResourcesError::SDLError(error.to_string())),
|
||||
};
|
||||
|
||||
self.framebuffer = framebuffer;
|
||||
self.video = screen_bitmap;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ use thiserror::Error;
|
|||
use crate::audio::device::AudioDeviceError;
|
||||
use crate::audio::AudioError;
|
||||
use crate::system::event::SystemEvent;
|
||||
use crate::system::framebuffer::SdlFramebufferError;
|
||||
|
||||
pub mod dos_like;
|
||||
pub mod standard;
|
||||
|
@ -12,6 +13,9 @@ pub enum SystemResourcesError {
|
|||
#[error("SystemResources SDL error: {0}")]
|
||||
SDLError(String),
|
||||
|
||||
#[error("SdlFramebufferError: {0}")]
|
||||
SdlFramebufferError(#[from] SdlFramebufferError),
|
||||
|
||||
#[error("System audioerror: {0}")]
|
||||
AudioError(#[from] AudioError),
|
||||
|
||||
|
|
Loading…
Reference in a new issue