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 input_devices;
|
||||||
pub mod res;
|
pub mod res;
|
||||||
|
|
||||||
|
mod framebuffer;
|
||||||
pub mod prelude;
|
pub mod prelude;
|
||||||
|
|
||||||
fn is_x11_compositor_skipping_problematic() -> bool {
|
fn is_x11_compositor_skipping_problematic() -> bool {
|
||||||
|
|
|
@ -26,14 +26,14 @@
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
|
|
||||||
use byte_slice_cast::AsByteSlice;
|
|
||||||
|
|
||||||
use crate::audio::queue::AudioQueue;
|
use crate::audio::queue::AudioQueue;
|
||||||
use crate::audio::{Audio, TARGET_AUDIO_CHANNELS, TARGET_AUDIO_FREQUENCY};
|
use crate::audio::{Audio, TARGET_AUDIO_CHANNELS, TARGET_AUDIO_FREQUENCY};
|
||||||
use crate::graphics::bitmap::indexed::IndexedBitmap;
|
use crate::graphics::bitmap::indexed::IndexedBitmap;
|
||||||
use crate::graphics::font::BitmaskFont;
|
use crate::graphics::font::BitmaskFont;
|
||||||
use crate::graphics::palette::Palette;
|
use crate::graphics::palette::Palette;
|
||||||
|
use crate::prelude::WindowEvent;
|
||||||
use crate::system::event::{SystemEvent, SystemEventHandler};
|
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::keyboard::Keyboard;
|
||||||
use crate::system::input_devices::mouse::cursor::CustomMouseCursor;
|
use crate::system::input_devices::mouse::cursor::CustomMouseCursor;
|
||||||
use crate::system::input_devices::mouse::Mouse;
|
use crate::system::input_devices::mouse::Mouse;
|
||||||
|
@ -48,22 +48,48 @@ const DEFAULT_SCALE_FACTOR: u32 = 3;
|
||||||
pub struct DosLikeConfig {
|
pub struct DosLikeConfig {
|
||||||
screen_width: u32,
|
screen_width: u32,
|
||||||
screen_height: u32,
|
screen_height: u32,
|
||||||
|
fixed_screen_size: bool,
|
||||||
initial_scale_factor: u32,
|
initial_scale_factor: u32,
|
||||||
integer_scaling: bool,
|
integer_scaling: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DosLikeConfig {
|
impl Default for DosLikeConfig {
|
||||||
/// Returns a new [`DosLikeConfig`] with a default configuration.
|
/// 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 {
|
DosLikeConfig {
|
||||||
screen_width: DEFAULT_SCREEN_WIDTH,
|
screen_width,
|
||||||
screen_height: DEFAULT_SCREEN_HEIGHT,
|
screen_height,
|
||||||
initial_scale_factor: DEFAULT_SCALE_FACTOR,
|
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
|
/// Sets an integer scaling factor for the [`System`] being built to up-scale the virtual
|
||||||
/// framebuffer to when displaying it on screen.
|
/// framebuffer to when displaying it on screen.
|
||||||
|
@ -71,13 +97,6 @@ impl DosLikeConfig {
|
||||||
self.initial_scale_factor = scale_factor;
|
self.initial_scale_factor = scale_factor;
|
||||||
self
|
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 {
|
impl SystemResourcesConfig for DosLikeConfig {
|
||||||
|
@ -89,8 +108,6 @@ impl SystemResourcesConfig for DosLikeConfig {
|
||||||
audio_subsystem: &sdl2::AudioSubsystem,
|
audio_subsystem: &sdl2::AudioSubsystem,
|
||||||
mut window: sdl2::video::Window,
|
mut window: sdl2::video::Window,
|
||||||
) -> Result<Self::SystemResourcesType, SystemResourcesError> {
|
) -> 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_width = self.screen_width * self.initial_scale_factor;
|
||||||
let window_height = self.screen_height * 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) {
|
if let Err(error) = window.set_size(window_width, window_height) {
|
||||||
|
@ -104,9 +121,6 @@ impl SystemResourcesConfig for DosLikeConfig {
|
||||||
Ok(canvas) => canvas,
|
Ok(canvas) => canvas,
|
||||||
Err(error) => return Err(SystemResourcesError::SDLError(error.to_string())),
|
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
|
// TODO: newer versions of rust-sdl2 support this directly off the WindowCanvas struct
|
||||||
unsafe {
|
unsafe {
|
||||||
|
@ -120,30 +134,14 @@ impl SystemResourcesConfig for DosLikeConfig {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// create an SDL texture which we will be uploading to every frame to display the
|
// create the SDL framebuffer at the initial logical screen size
|
||||||
// application's framebuffer
|
|
||||||
|
|
||||||
let sdl_texture = match sdl_canvas.create_texture_streaming(
|
let framebuffer = SdlFramebuffer::new(&mut sdl_canvas, self.screen_width, self.screen_height, true)?;
|
||||||
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();
|
|
||||||
|
|
||||||
// create the Bitmap object that will be exposed to the application acting as the system
|
// create the Bitmap object that will be exposed to the application acting as the system
|
||||||
// backbuffer
|
// 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,
|
Ok(bmp) => bmp,
|
||||||
Err(error) => return Err(SystemResourcesError::SDLError(error.to_string())),
|
Err(error) => return Err(SystemResourcesError::SDLError(error.to_string())),
|
||||||
};
|
};
|
||||||
|
@ -182,13 +180,13 @@ impl SystemResourcesConfig for DosLikeConfig {
|
||||||
|
|
||||||
Ok(DosLike {
|
Ok(DosLike {
|
||||||
sdl_canvas,
|
sdl_canvas,
|
||||||
sdl_texture,
|
framebuffer,
|
||||||
sdl_texture_pitch,
|
scale_factor: self.initial_scale_factor,
|
||||||
texture_pixels,
|
fixed_screen_size: self.fixed_screen_size,
|
||||||
audio,
|
audio,
|
||||||
audio_queue,
|
audio_queue,
|
||||||
palette,
|
palette,
|
||||||
video: framebuffer,
|
video: screen_bitmap,
|
||||||
font,
|
font,
|
||||||
keyboard,
|
keyboard,
|
||||||
mouse,
|
mouse,
|
||||||
|
@ -201,9 +199,9 @@ impl SystemResourcesConfig for DosLikeConfig {
|
||||||
/// audio via [`Audio`] and keyboard/mouse input.
|
/// audio via [`Audio`] and keyboard/mouse input.
|
||||||
pub struct DosLike {
|
pub struct DosLike {
|
||||||
sdl_canvas: sdl2::render::WindowCanvas,
|
sdl_canvas: sdl2::render::WindowCanvas,
|
||||||
sdl_texture: sdl2::render::Texture,
|
framebuffer: SdlFramebuffer,
|
||||||
sdl_texture_pitch: usize,
|
scale_factor: u32,
|
||||||
texture_pixels: Box<[u32]>,
|
fixed_screen_size: bool,
|
||||||
|
|
||||||
/// An [`Audio`] instance that allows interacting with the system's audio output device.
|
/// An [`Audio`] instance that allows interacting with the system's audio output device.
|
||||||
pub audio: Audio,
|
pub audio: Audio,
|
||||||
|
@ -267,24 +265,8 @@ impl SystemResources for DosLike {
|
||||||
/// to fill the window (preserving aspect ratio of course).
|
/// to fill the window (preserving aspect ratio of course).
|
||||||
fn display(&mut self) -> Result<(), SystemResourcesError> {
|
fn display(&mut self) -> Result<(), SystemResourcesError> {
|
||||||
self.cursor.render(&mut self.video);
|
self.cursor.render(&mut self.video);
|
||||||
|
self.framebuffer.display_indexed_bitmap(&mut self.sdl_canvas, &self.video, &self.palette)?;
|
||||||
// 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.cursor.hide(&mut self.video);
|
self.cursor.hide(&mut self.video);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -295,6 +277,13 @@ impl SystemResources for DosLike {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_event(&mut self, event: &SystemEvent) -> Result<bool, SystemResourcesError> {
|
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) {
|
if self.keyboard.handle_event(event) {
|
||||||
return Ok(true);
|
return Ok(true);
|
||||||
}
|
}
|
||||||
|
@ -314,3 +303,21 @@ impl SystemResources for DosLike {
|
||||||
self.video.height()
|
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::device::AudioDeviceError;
|
||||||
use crate::audio::AudioError;
|
use crate::audio::AudioError;
|
||||||
use crate::system::event::SystemEvent;
|
use crate::system::event::SystemEvent;
|
||||||
|
use crate::system::framebuffer::SdlFramebufferError;
|
||||||
|
|
||||||
pub mod dos_like;
|
pub mod dos_like;
|
||||||
pub mod standard;
|
pub mod standard;
|
||||||
|
@ -12,6 +13,9 @@ pub enum SystemResourcesError {
|
||||||
#[error("SystemResources SDL error: {0}")]
|
#[error("SystemResources SDL error: {0}")]
|
||||||
SDLError(String),
|
SDLError(String),
|
||||||
|
|
||||||
|
#[error("SdlFramebufferError: {0}")]
|
||||||
|
SdlFramebufferError(#[from] SdlFramebufferError),
|
||||||
|
|
||||||
#[error("System audioerror: {0}")]
|
#[error("System audioerror: {0}")]
|
||||||
AudioError(#[from] AudioError),
|
AudioError(#[from] AudioError),
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue