Add doom project (very important)

This commit is contained in:
Michael Mikovsky
2026-05-07 11:16:47 -06:00
parent ef1eced02d
commit 5876c76d51
9 changed files with 49473 additions and 0 deletions
File diff suppressed because it is too large Load Diff
+2
View File
@@ -0,0 +1,2 @@
#define DOOM_IMPLEMENTATION
#include "PureDOOM.h"
+441
View File
@@ -0,0 +1,441 @@
use std::error::Error;
use std::ffi::CString;
use std::fs;
use std::os::raw::{c_char, c_int, c_uchar};
use std::path::PathBuf;
const DOOM_WIDTH: u32 = 320;
const DOOM_HEIGHT: u32 = 200;
const DOOM_FLAGS_HIDE_MOUSE_OPTIONS: c_int = 1;
const DOOM_FLAGS_HIDE_SOUND_OPTIONS: c_int = 2;
const DOOM_FLAGS_HIDE_MUSIC_OPTIONS: c_int = 4;
const EMBEDDED_DOOM1_WAD: &[u8] = include_bytes!("../doom1.wad");
unsafe extern "C" {
fn doom_set_resolution(width: c_int, height: c_int);
fn doom_init(argc: c_int, argv: *mut *mut c_char, flags: c_int);
fn doom_update();
fn doom_get_framebuffer(channels: c_int) -> *const c_uchar;
#[cfg(all(target_os = "linux", target_arch = "arm"))]
fn doom_key_down(key: c_int);
#[cfg(all(target_os = "linux", target_arch = "arm"))]
fn doom_key_up(key: c_int);
}
pub fn run() -> Result<(), Box<dyn Error>> {
let mut doom = Doom::new()?;
video::run(&mut doom)
}
pub struct Doom {
_args: Vec<CString>,
_argv: Vec<*mut c_char>,
}
impl Doom {
fn new() -> Result<Self, Box<dyn Error>> {
let args = args_with_embedded_iwad()?;
let mut args = args
.into_iter()
.map(|arg| CString::new(arg).map_err(|err| -> Box<dyn Error> { Box::new(err) }))
.collect::<Result<Vec<_>, _>>()?;
let mut argv = args
.iter_mut()
.map(|arg| arg.as_ptr() as *mut c_char)
.collect::<Vec<_>>();
unsafe {
doom_set_resolution(DOOM_WIDTH as c_int, DOOM_HEIGHT as c_int);
doom_init(
argv.len() as c_int,
argv.as_mut_ptr(),
DOOM_FLAGS_HIDE_MOUSE_OPTIONS
| DOOM_FLAGS_HIDE_SOUND_OPTIONS
| DOOM_FLAGS_HIDE_MUSIC_OPTIONS,
);
}
Ok(Self {
_args: args,
_argv: argv,
})
}
fn update(&mut self) {
unsafe {
doom_update();
}
}
#[cfg(all(target_os = "linux", target_arch = "arm"))]
fn key_down(&mut self, key: c_int) {
unsafe {
doom_key_down(key);
}
}
#[cfg(all(target_os = "linux", target_arch = "arm"))]
fn key_up(&mut self, key: c_int) {
unsafe {
doom_key_up(key);
}
}
fn framebuffer_rgb(&self) -> &'static [u8] {
let ptr = unsafe { doom_get_framebuffer(3) };
assert!(!ptr.is_null(), "Doom returned a null framebuffer");
unsafe { std::slice::from_raw_parts(ptr, (DOOM_WIDTH * DOOM_HEIGHT * 3) as usize) }
}
}
fn args_with_embedded_iwad() -> Result<Vec<String>, Box<dyn Error>> {
let mut args = std::env::args().collect::<Vec<_>>();
if has_iwad_arg(&args) {
return Ok(args);
}
let iwad = embedded_iwad_path();
if fs::read(&iwad).ok().as_deref() != Some(EMBEDDED_DOOM1_WAD) {
fs::write(&iwad, EMBEDDED_DOOM1_WAD)?;
}
args.push("-iwad".to_string());
args.push(iwad.to_string_lossy().into_owned());
Ok(args)
}
fn has_iwad_arg(args: &[String]) -> bool {
args.iter().any(|arg| arg.eq_ignore_ascii_case("-iwad"))
}
fn embedded_iwad_path() -> PathBuf {
std::env::temp_dir().join("cc2-doom-doom1.wad")
}
#[cfg(all(target_os = "linux", target_arch = "arm"))]
mod video {
use std::error::Error;
use std::io;
use std::mem;
use std::os::fd::RawFd;
use crate::doom::{DOOM_HEIGHT, DOOM_WIDTH, Doom};
use crate::framebuffer::FrameBuffer;
const DOOM_KEY_TAB: i32 = 9;
const DOOM_KEY_ENTER: i32 = 13;
const DOOM_KEY_ESCAPE: i32 = 27;
const DOOM_KEY_SPACE: i32 = 32;
const DOOM_KEY_APOSTROPHE: i32 = b'\'' as i32;
const DOOM_KEY_COMMA: i32 = b',' as i32;
const DOOM_KEY_MINUS: i32 = b'-' as i32;
const DOOM_KEY_PERIOD: i32 = b'.' as i32;
const DOOM_KEY_SLASH: i32 = b'/' as i32;
const DOOM_KEY_SEMICOLON: i32 = b';' as i32;
const DOOM_KEY_EQUALS: i32 = b'=' as i32;
const DOOM_KEY_LEFT_BRACKET: i32 = b'[' as i32;
const DOOM_KEY_RIGHT_BRACKET: i32 = b']' as i32;
const DOOM_KEY_BACKSPACE: i32 = 127;
const DOOM_KEY_CTRL: i32 = 0x80 + 0x1d;
const DOOM_KEY_LEFT_ARROW: i32 = 0xac;
const DOOM_KEY_UP_ARROW: i32 = 0xad;
const DOOM_KEY_RIGHT_ARROW: i32 = 0xae;
const DOOM_KEY_DOWN_ARROW: i32 = 0xaf;
const DOOM_KEY_SHIFT: i32 = 0x80 + 0x36;
const DOOM_KEY_ALT: i32 = 0x80 + 0x38;
const DOOM_KEY_F1: i32 = 0x80 + 0x3b;
const DOOM_KEY_F2: i32 = 0x80 + 0x3c;
const DOOM_KEY_F3: i32 = 0x80 + 0x3d;
const DOOM_KEY_F4: i32 = 0x80 + 0x3e;
const DOOM_KEY_F5: i32 = 0x80 + 0x3f;
const DOOM_KEY_F6: i32 = 0x80 + 0x40;
const DOOM_KEY_F7: i32 = 0x80 + 0x41;
const DOOM_KEY_F8: i32 = 0x80 + 0x42;
const DOOM_KEY_F9: i32 = 0x80 + 0x43;
const DOOM_KEY_F10: i32 = 0x80 + 0x44;
const DOOM_KEY_F11: i32 = 0x80 + 0x57;
const DOOM_KEY_F12: i32 = 0x80 + 0x58;
const DOOM_KEY_PAUSE: i32 = 0xff;
pub fn run(doom: &mut Doom) -> Result<(), Box<dyn Error>> {
let mut fb = FrameBuffer::new("/dev/fb0")?;
let mut keyboard = StdinKeyboard::new()?;
let offset_x = ((fb.xres as i32) - (DOOM_WIDTH as i32)) / 2;
let offset_y = ((fb.yres as i32) - (DOOM_HEIGHT as i32)) / 2;
loop {
keyboard.poll(doom)?;
doom.update();
fb.display_frame_at(
doom.framebuffer_rgb(),
DOOM_WIDTH,
DOOM_HEIGHT,
offset_x,
offset_y,
)?;
}
}
struct StdinKeyboard {
fd: RawFd,
original_termios: Option<libc::termios>,
pressed: Vec<i32>,
}
impl StdinKeyboard {
fn new() -> io::Result<Self> {
let fd = libc::STDIN_FILENO;
let original_termios = set_raw_nonblocking(fd)?;
Ok(Self {
fd,
original_termios,
pressed: Vec::new(),
})
}
fn poll(&mut self, doom: &mut Doom) -> io::Result<()> {
let mut bytes = [0u8; 64];
let len = read_available(self.fd, &mut bytes)?;
if len == 0 {
self.release_all(doom);
return Ok(());
}
self.release_all(doom);
let mut i = 0;
while i < len {
let (key, consumed) = parse_key(&bytes[i..len]);
i += consumed.max(1);
if let Some(key) = key {
doom.key_down(key);
self.pressed.push(key);
}
}
Ok(())
}
fn release_all(&mut self, doom: &mut Doom) {
for key in self.pressed.drain(..) {
doom.key_up(key);
}
}
}
impl Drop for StdinKeyboard {
fn drop(&mut self) {
if let Some(termios) = self.original_termios {
unsafe {
libc::tcsetattr(self.fd, libc::TCSANOW, &termios);
}
}
}
}
fn set_raw_nonblocking(fd: RawFd) -> io::Result<Option<libc::termios>> {
let mut termios = unsafe { mem::zeroed::<libc::termios>() };
let has_tty = unsafe { libc::tcgetattr(fd, &mut termios) } == 0;
let original = has_tty.then_some(termios);
if has_tty {
let mut raw = termios;
raw.c_lflag &= !(libc::ICANON | libc::ECHO | libc::ISIG);
raw.c_iflag &= !(libc::IXON | libc::ICRNL);
raw.c_cc[libc::VMIN] = 0;
raw.c_cc[libc::VTIME] = 0;
if unsafe { libc::tcsetattr(fd, libc::TCSANOW, &raw) } < 0 {
return Err(io::Error::last_os_error());
}
}
let flags = unsafe { libc::fcntl(fd, libc::F_GETFL) };
if flags < 0 {
return Err(io::Error::last_os_error());
}
if unsafe { libc::fcntl(fd, libc::F_SETFL, flags | libc::O_NONBLOCK) } < 0 {
return Err(io::Error::last_os_error());
}
Ok(original)
}
fn read_available(fd: RawFd, out: &mut [u8]) -> io::Result<usize> {
let result = unsafe { libc::read(fd, out.as_mut_ptr() as *mut libc::c_void, out.len()) };
if result < 0 {
let err = io::Error::last_os_error();
return match err.kind() {
io::ErrorKind::WouldBlock => Ok(0),
_ => Err(err),
};
}
Ok(result as usize)
}
fn parse_key(bytes: &[u8]) -> (Option<i32>, usize) {
if bytes.is_empty() {
return (None, 0);
}
if bytes[0] == b'\x1b' {
return parse_escape(bytes);
}
(byte_to_doom_key(bytes[0]), 1)
}
fn parse_escape(bytes: &[u8]) -> (Option<i32>, usize) {
if bytes.len() >= 3 && bytes[1] == b'[' {
return match bytes[2] {
b'A' => (Some(DOOM_KEY_UP_ARROW), 3),
b'B' => (Some(DOOM_KEY_DOWN_ARROW), 3),
b'C' => (Some(DOOM_KEY_RIGHT_ARROW), 3),
b'D' => (Some(DOOM_KEY_LEFT_ARROW), 3),
b'Z' => (Some(DOOM_KEY_TAB), 3),
b'1' | b'2' | b'3' | b'4' | b'5' | b'6' if bytes.len() >= 4 => {
parse_csi_tilde(bytes)
}
_ => (Some(DOOM_KEY_ESCAPE), 1),
};
}
if bytes.len() >= 3 && bytes[1] == b'O' {
return match bytes[2] {
b'P' => (Some(DOOM_KEY_F1), 3),
b'Q' => (Some(DOOM_KEY_F2), 3),
b'R' => (Some(DOOM_KEY_F3), 3),
b'S' => (Some(DOOM_KEY_F4), 3),
_ => (Some(DOOM_KEY_ESCAPE), 1),
};
}
(Some(DOOM_KEY_ESCAPE), 1)
}
fn parse_csi_tilde(bytes: &[u8]) -> (Option<i32>, usize) {
let tilde = bytes.iter().take(6).position(|&byte| byte == b'~');
let Some(tilde) = tilde else {
return (Some(DOOM_KEY_ESCAPE), 1);
};
let key = match &bytes[2..tilde] {
b"15" => Some(DOOM_KEY_F5),
b"17" => Some(DOOM_KEY_F6),
b"18" => Some(DOOM_KEY_F7),
b"19" => Some(DOOM_KEY_F8),
b"20" => Some(DOOM_KEY_F9),
b"21" => Some(DOOM_KEY_F10),
b"23" => Some(DOOM_KEY_F11),
b"24" => Some(DOOM_KEY_F12),
_ => None,
};
(key, tilde + 1)
}
fn byte_to_doom_key(byte: u8) -> Option<i32> {
match byte {
b'\t' => Some(DOOM_KEY_TAB),
b'\r' | b'\n' => Some(DOOM_KEY_ENTER),
0x7f | 0x08 => Some(DOOM_KEY_BACKSPACE),
0x01..=0x1a => Some((byte - 0x01 + b'a') as i32),
b' ' => Some(DOOM_KEY_SPACE),
b'\'' => Some(DOOM_KEY_APOSTROPHE),
b',' => Some(DOOM_KEY_COMMA),
b'-' => Some(DOOM_KEY_MINUS),
b'.' => Some(DOOM_KEY_PERIOD),
b'/' => Some(DOOM_KEY_SLASH),
b'0'..=b'9' => Some(byte as i32),
b';' => Some(DOOM_KEY_SEMICOLON),
b'=' => Some(DOOM_KEY_EQUALS),
b'[' => Some(DOOM_KEY_LEFT_BRACKET),
b']' => Some(DOOM_KEY_RIGHT_BRACKET),
b'a'..=b'z' => Some(byte as i32),
b'A'..=b'Z' => Some(byte.to_ascii_lowercase() as i32),
_ => None,
}
}
}
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
mod video {
use std::error::Error;
use pixels::{Pixels, SurfaceTexture};
use winit::dpi::LogicalSize;
use winit::event::{Event, WindowEvent};
use winit::event_loop::{ControlFlow, EventLoop};
use winit::window::WindowBuilder;
use crate::doom::{DOOM_HEIGHT, DOOM_WIDTH, Doom};
pub fn run(doom: &mut Doom) -> Result<(), Box<dyn Error>> {
let event_loop = EventLoop::new()?;
let window = WindowBuilder::new()
.with_title("cc2-doom")
.with_inner_size(LogicalSize::new(
(DOOM_WIDTH * 3) as f64,
(DOOM_HEIGHT * 3) as f64,
))
.with_min_inner_size(LogicalSize::new(DOOM_WIDTH as f64, DOOM_HEIGHT as f64))
.build(&event_loop)?;
let window = Box::leak(Box::new(window));
let surface = SurfaceTexture::new(DOOM_WIDTH * 3, DOOM_HEIGHT * 3, &*window);
let mut pixels = Pixels::new(DOOM_WIDTH, DOOM_HEIGHT, surface)?;
event_loop.run(move |event, elwt| {
elwt.set_control_flow(ControlFlow::Poll);
match event {
Event::AboutToWait => {
doom.update();
copy_rgb_to_rgba(doom.framebuffer_rgb(), pixels.frame_mut());
if pixels.render().is_err() {
elwt.exit();
}
}
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => elwt.exit(),
Event::WindowEvent {
event: WindowEvent::Resized(size),
..
} => {
let _ = pixels.resize_surface(size.width, size.height);
}
_ => {}
}
})?;
Ok(())
}
fn copy_rgb_to_rgba(src: &[u8], dst: &mut [u8]) {
for (rgb, rgba) in src.chunks_exact(3).zip(dst.chunks_exact_mut(4)) {
rgba[0] = rgb[0];
rgba[1] = rgb[1];
rgba[2] = rgb[2];
rgba[3] = 0xff;
}
}
}
#[cfg(not(any(
all(target_os = "linux", target_arch = "arm"),
all(target_os = "linux", target_arch = "x86_64")
)))]
mod video {
use std::error::Error;
use std::io::{Error as IoError, ErrorKind};
use crate::doom::Doom;
pub fn run(_doom: &mut Doom) -> Result<(), Box<dyn Error>> {
Err(IoError::new(ErrorKind::Unsupported, "unsupported Doom video target").into())
}
}
+388
View File
@@ -0,0 +1,388 @@
use memmap2::{MmapMut, MmapOptions};
use std::fs::{File, OpenOptions};
use std::io::{self, Error, ErrorKind};
use std::os::unix::io::AsRawFd;
const FBIOGET_VSCREENINFO: libc::c_ulong = 0x4600;
const FBIOGET_FSCREENINFO: libc::c_ulong = 0x4602;
#[repr(C)]
#[derive(Debug, Copy, Clone)]
struct FbFixScreenInfo {
id: [u8; 16],
smem_start: libc::c_ulong,
smem_len: u32,
fb_type: u32,
type_aux: u32,
visual: u32,
xpanstep: u16,
ypanstep: u16,
ywrapstep: u16,
line_length: u32,
mmio_start: libc::c_ulong,
mmio_len: u32,
accel: u32,
capabilities: u16,
reserved: [u16; 2],
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
struct FbVarScreenInfo {
xres: u32,
yres: u32,
xres_virtual: u32,
yres_virtual: u32,
xoffset: u32,
yoffset: u32,
bits_per_pixel: u32,
grayscale: u32,
red: FbBitfield,
green: FbBitfield,
blue: FbBitfield,
transp: FbBitfield,
nonstd: u32,
activate: u32,
height: u32,
width: u32,
accel_flags: u32,
pixclock: u32,
left_margin: u32,
right_margin: u32,
upper_margin: u32,
lower_margin: u32,
hsync_len: u32,
vsync_len: u32,
sync: u32,
vmode: u32,
rotate: u32,
colorspace: u32,
reserved: [u32; 4],
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
struct FbBitfield {
offset: u32,
length: u32,
msb_right: u32,
}
#[derive(Debug, Copy, Clone)]
enum PixelFormat {
Rgb565,
Native32,
Native24,
Native16,
}
pub struct FrameBuffer {
_file: File,
fb_map: MmapMut,
pub xres: u32,
pub yres: u32,
xoffset: u32,
yoffset: u32,
line_length: usize,
bytes_per_pixel: usize,
red: FbBitfield,
green: FbBitfield,
blue: FbBitfield,
transp: FbBitfield,
format: PixelFormat,
}
impl FrameBuffer {
pub fn new(device: &str) -> io::Result<Self> {
let file = OpenOptions::new().read(true).write(true).open(device)?;
let mut fix_info = unsafe { std::mem::zeroed::<FbFixScreenInfo>() };
ioctl_read(file.as_raw_fd(), FBIOGET_FSCREENINFO, &mut fix_info)?;
let mut var_info = unsafe { std::mem::zeroed::<FbVarScreenInfo>() };
ioctl_read(file.as_raw_fd(), FBIOGET_VSCREENINFO, &mut var_info)?;
let bytes_per_pixel = match var_info.bits_per_pixel {
16 => 2,
24 => 3,
32 => 4,
bpp => {
return Err(Error::new(
ErrorKind::Unsupported,
format!("unsupported framebuffer depth: {bpp} bpp"),
));
}
};
if fix_info.smem_len == 0 {
return Err(Error::new(
ErrorKind::InvalidData,
"framebuffer reports zero memory length",
));
}
let line_length = fix_info.line_length as usize;
if line_length == 0 {
return Err(Error::new(
ErrorKind::InvalidData,
"framebuffer reports zero line length",
));
}
let format = detect_format(
var_info.bits_per_pixel,
var_info.red,
var_info.green,
var_info.blue,
);
let fb_map = unsafe {
MmapOptions::new()
.len(fix_info.smem_len as usize)
.map_mut(&file)?
};
Ok(FrameBuffer {
_file: file,
fb_map,
xres: var_info.xres,
yres: var_info.yres,
xoffset: var_info.xoffset,
yoffset: var_info.yoffset,
line_length,
bytes_per_pixel,
red: var_info.red,
green: var_info.green,
blue: var_info.blue,
transp: var_info.transp,
format,
})
}
pub fn display_frame(&mut self, frame: &[u8], width: u32, height: u32) -> io::Result<()> {
self.display_frame_at(frame, width, height, 0, 0)
}
pub fn display_frame_at(
&mut self,
frame: &[u8],
width: u32,
height: u32,
offset_x: i32,
offset_y: i32,
) -> io::Result<()> {
let expected_len = rgb888_len(width, height)?;
if frame.len() < expected_len {
return Err(Error::new(
ErrorKind::InvalidInput,
format!(
"frame is too small for {width}x{height} RGB888: {} < {expected_len}",
frame.len()
),
));
}
let Some(clip) = Clip::new(width, height, self.xres, self.yres, offset_x, offset_y) else {
return Ok(());
};
let xoffset = self.xoffset as usize;
let yoffset = self.yoffset as usize;
let line_length = self.line_length;
let bpp = self.bytes_per_pixel;
let red = self.red;
let green = self.green;
let blue = self.blue;
let transp = self.transp;
let format = self.format;
for row in 0..clip.height {
let src_y = clip.src_y + row;
let dst_y = clip.dst_y + row;
let src_start = ((src_y * width + clip.src_x) * 3) as usize;
let src_end = src_start + (clip.width * 3) as usize;
let src = &frame[src_start..src_end];
let dst_start =
(dst_y as usize + yoffset) * line_length + (clip.dst_x as usize + xoffset) * bpp;
let dst_end = dst_start + clip.width as usize * bpp;
if dst_end > self.fb_map.len() {
return Err(Error::new(
ErrorKind::InvalidData,
"visible framebuffer area exceeds mapped memory",
));
}
write_rgb888_row(
src,
&mut self.fb_map[dst_start..dst_end],
format,
red,
green,
blue,
transp,
);
}
// fbdev mappings are device memory. The writes above are the update;
// msync can return EINVAL on framebuffer mappings even after pixels land.
Ok(())
}
}
fn ioctl_read<T>(fd: std::os::fd::RawFd, request: libc::c_ulong, value: &mut T) -> io::Result<()> {
let result = unsafe { libc::ioctl(fd, request.try_into().unwrap(), value as *mut T) };
if result < 0 {
Err(io::Error::last_os_error())
} else {
Ok(())
}
}
fn detect_format(
bits_per_pixel: u32,
red: FbBitfield,
green: FbBitfield,
blue: FbBitfield,
) -> PixelFormat {
match bits_per_pixel {
16 if red.offset == 11
&& red.length == 5
&& green.offset == 5
&& green.length == 6
&& blue.offset == 0
&& blue.length == 5 =>
{
PixelFormat::Rgb565
}
16 => PixelFormat::Native16,
24 => PixelFormat::Native24,
32 => PixelFormat::Native32,
_ => unreachable!("unsupported framebuffer depth was rejected earlier"),
}
}
fn rgb888_len(width: u32, height: u32) -> io::Result<usize> {
width
.checked_mul(height)
.and_then(|pixels| pixels.checked_mul(3))
.map(|len| len as usize)
.ok_or_else(|| Error::new(ErrorKind::InvalidInput, "RGB888 frame dimensions overflow"))
}
fn write_rgb888_row(
src: &[u8],
dst: &mut [u8],
format: PixelFormat,
red: FbBitfield,
green: FbBitfield,
blue: FbBitfield,
transp: FbBitfield,
) {
match format {
PixelFormat::Rgb565 => write_rgb565_row(src, dst),
PixelFormat::Native16 => write_native_row::<2>(src, dst, red, green, blue, transp),
PixelFormat::Native24 => write_native_row::<3>(src, dst, red, green, blue, transp),
PixelFormat::Native32 => write_native_row::<4>(src, dst, red, green, blue, transp),
}
}
fn write_rgb565_row(src: &[u8], dst: &mut [u8]) {
for (rgb, out) in src.chunks_exact(3).zip(dst.chunks_exact_mut(2)) {
let r = (rgb[0] as u16 >> 3) << 11;
let g = (rgb[1] as u16 >> 2) << 5;
let b = rgb[2] as u16 >> 3;
out.copy_from_slice(&(r | g | b).to_ne_bytes());
}
}
fn write_native_row<const N: usize>(
src: &[u8],
dst: &mut [u8],
red: FbBitfield,
green: FbBitfield,
blue: FbBitfield,
transp: FbBitfield,
) {
for (rgb, out) in src.chunks_exact(3).zip(dst.chunks_exact_mut(N)) {
let pixel = pack_pixel(rgb[0], rgb[1], rgb[2], red, green, blue, transp);
out.copy_from_slice(&pixel.to_ne_bytes()[..N]);
}
}
fn pack_pixel(
r: u8,
g: u8,
b: u8,
red: FbBitfield,
green: FbBitfield,
blue: FbBitfield,
transp: FbBitfield,
) -> u32 {
pack_channel(r, red) | pack_channel(g, green) | pack_channel(b, blue) | pack_alpha(transp)
}
fn pack_channel(value: u8, field: FbBitfield) -> u32 {
if field.length == 0 {
return 0;
}
let max = (1u32 << field.length) - 1;
let scaled = (value as u32 * max + 127) / 255;
scaled << field.offset
}
fn pack_alpha(field: FbBitfield) -> u32 {
if field.length == 0 {
return 0;
}
((1u32 << field.length) - 1) << field.offset
}
struct Clip {
src_x: u32,
src_y: u32,
dst_x: u32,
dst_y: u32,
width: u32,
height: u32,
}
impl Clip {
fn new(
src_width: u32,
src_height: u32,
dst_width: u32,
dst_height: u32,
offset_x: i32,
offset_y: i32,
) -> Option<Self> {
if src_width == 0 || src_height == 0 || dst_width == 0 || dst_height == 0 {
return None;
}
let src_x = offset_x.saturating_neg().max(0) as u32;
let src_y = offset_y.saturating_neg().max(0) as u32;
let dst_x = offset_x.max(0) as u32;
let dst_y = offset_y.max(0) as u32;
if src_x >= src_width || src_y >= src_height || dst_x >= dst_width || dst_y >= dst_height {
return None;
}
let width = (src_width - src_x).min(dst_width - dst_x);
let height = (src_height - src_y).min(dst_height - dst_y);
if width == 0 || height == 0 {
return None;
}
Some(Clip {
src_x,
src_y,
dst_x,
dst_y,
width,
height,
})
}
}
+7
View File
@@ -0,0 +1,7 @@
mod doom;
#[cfg(all(target_os = "linux", target_arch = "arm"))]
mod framebuffer;
fn main() -> Result<(), Box<dyn std::error::Error>> {
doom::run()
}