show pixels in window

This commit is contained in:
Vinzenz Schroeter 2024-05-09 00:46:03 +02:00
parent 2e30d91d7e
commit 64e85bcd4f
3 changed files with 2926 additions and 70 deletions

2741
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -2,9 +2,16 @@
name = "pixel-receiver-rs" name = "pixel-receiver-rs"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
publish = false
[dependencies] [dependencies]
clap = { version = "4.0", features = ["derive"] } clap = { version = "4.0", features = ["derive"] }
num = "0.4" num = "0.4"
num-derive = "0.4" num-derive = "0.4"
num-traits = "0.2" num-traits = "0.2"
image = "0.25"
pixels = "0.13"
winit = { version = "0.30", features = ["rwh_05"] }
log = "0.4"
raw-window-handle = "0.6"
env_logger = "0.11"

View file

@ -1,8 +1,25 @@
#![deny(clippy::all)]
use std::default::Default;
use std::io::ErrorKind;
use std::mem::size_of; use std::mem::size_of;
use std::net::UdpSocket; use std::net::UdpSocket;
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
use clap::Parser; use clap::Parser;
use image::GenericImage;
use log::{debug, error, info, warn};
use num_derive::FromPrimitive; use num_derive::FromPrimitive;
use pixels::wgpu::TextureFormat;
use pixels::{Pixels, PixelsBuilder, SurfaceTexture};
use winit::application::ApplicationHandler;
use winit::dpi::{LogicalSize, Size};
use winit::event::WindowEvent;
use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
use winit::platform::x11::WindowAttributesExtX11;
use winit::window::{Window, WindowId};
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
struct Cli { struct Cli {
@ -60,42 +77,51 @@ const TILE_HEIGHT: u16 = 20;
const TILE_COUNT: u16 = TILE_WIDTH * TILE_HEIGHT; const TILE_COUNT: u16 = TILE_WIDTH * TILE_HEIGHT;
const PIXEL_WIDTH: u16 = TILE_WIDTH * TILE_SIZE; const PIXEL_WIDTH: u16 = TILE_WIDTH * TILE_SIZE;
const PIXEL_HEIGHT: u16 = TILE_HEIGHT * TILE_SIZE; const PIXEL_HEIGHT: u16 = TILE_HEIGHT * TILE_SIZE;
const PIXEL_COUNT: u16 = PIXEL_WIDTH * TILE_HEIGHT; const PIXEL_COUNT: usize = PIXEL_WIDTH as usize * PIXEL_HEIGHT as usize;
fn main() -> std::io::Result<()> { static mut DISPLAY: [bool; PIXEL_COUNT] = [false; PIXEL_COUNT];
fn main() {
assert_eq!(size_of::<HdrWindow>(), 10, "invalid struct size"); assert_eq!(size_of::<HdrWindow>(), 10, "invalid struct size");
env_logger::init();
let cli = Cli::parse(); let cli = Cli::parse();
println!("running with args: {:?}", &cli); info!("running with args: {:?}", &cli);
loop { info!("display booting up");
// to emulate a hard reset, the actual main method gets called until it crashes
main2(&cli).unwrap();
}
}
fn main2(cli: &Cli) -> std::io::Result<()> { let bind = cli.bind;
println!("display booting up"); let (tx, rx) = mpsc::channel();
let thread = thread::spawn(move || {
let socket = UdpSocket::bind(bind).expect("could not bind socket");
socket
.set_nonblocking(true)
.expect("could not enter non blocking mode");
let screen = [0u8; TILE_COUNT as usize];
let socket = UdpSocket::bind(&cli.bind)?;
let mut buf = [0; 8985]; let mut buf = [0; 8985];
loop { while rx.try_recv().is_err() {
let (amount, source) = socket.recv_from(&mut buf)?; let (amount, source) = match socket.recv_from(&mut buf) {
Err(err) if err.kind() == ErrorKind::WouldBlock => {
thread::sleep(Duration::from_millis(1));
continue;
}
Ok(result) => result,
other => other.unwrap(),
};
let received = &mut buf[..amount]; let received = &mut buf[..amount];
let header = read_hdr_window(&received[..10]); let header = read_hdr_window(&received[..10]);
if let Err(err) = header { if let Err(err) = header {
println!("could not read header: {}", err); warn!("could not read header: {}", err);
continue; continue;
} }
let header = header.unwrap(); let header = header.unwrap();
let payload = &received[10..]; let payload = &received[10..];
println!( info!(
"received from {:?}: {:?} (and {} bytes of payload)", "received from {:?}: {:?} (and {} bytes of payload)",
source, source,
header, header,
@ -104,11 +130,11 @@ fn main2(cli: &Cli) -> std::io::Result<()> {
match header.command { match header.command {
DisplayCommand::CmdClear => { DisplayCommand::CmdClear => {
println!("(imagine an empty screen now)") info!("(imagine an empty screen now)")
} }
DisplayCommand::CmdHardReset => { DisplayCommand::CmdHardReset => {
println!("display shutting down"); warn!("display shutting down");
return Ok(()); return;
} }
DisplayCommand::CmdBitmapLinearWin => { DisplayCommand::CmdBitmapLinearWin => {
print_bitmap_linear_win(&header, payload); print_bitmap_linear_win(&header, payload);
@ -117,13 +143,96 @@ fn main2(cli: &Cli) -> std::io::Result<()> {
print_cp437_data(&header, payload); print_cp437_data(&header, payload);
} }
_ => { _ => {
println!( error!(
"command {:?} sent by {:?} not implemented yet", "command {:?} sent by {:?} not implemented yet",
header.command, source header.command, source
); );
} }
} }
} }
});
let event_loop = EventLoop::new().expect("could not create event loop");
event_loop.set_control_flow(ControlFlow::Poll);
let mut app = App::default();
event_loop
.run_app(&mut app)
.expect("could not run event loop");
tx.send(()).expect("could not cancel thread");
thread.join().expect("could not join threads");
}
#[derive(Default)]
struct App {
window: Option<Window>,
pixels: Option<Pixels>,
}
impl ApplicationHandler for App {
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
let size = Size::from(LogicalSize::new(PIXEL_WIDTH as f64, PIXEL_HEIGHT as f64));
let attributes = Window::default_attributes()
.with_title("pixel-receiver-rs")
.with_inner_size(size)
.with_resizable(false)
.with_visible(true);
let window = event_loop.create_window(attributes).unwrap();
self.window = Some(window);
let window = self.window.as_ref().unwrap();
self.pixels = {
let window_size = window.inner_size();
let surface_texture =
SurfaceTexture::new(window_size.width, window_size.height, &window);
Some(
PixelsBuilder::new(PIXEL_WIDTH as u32, PIXEL_HEIGHT as u32, surface_texture)
.render_texture_format(TextureFormat::Bgra8UnormSrgb)
.build()
.expect("could not create pixels"),
)
};
}
fn window_event(&mut self, event_loop: &ActiveEventLoop, _: WindowId, event: WindowEvent) {
debug!("event {:?}", event);
match event {
WindowEvent::CloseRequested => {
warn!("The close button was pressed; stopping");
event_loop.exit();
}
WindowEvent::RedrawRequested => {
let window = self.window.as_ref().unwrap();
let pixels = self.pixels.as_mut().unwrap();
let frame = pixels.frame_mut().chunks_exact_mut(4);
let mut i = 0;
for pixel in frame {
unsafe {
if i >= DISPLAY.len() {
break;
}
let color = if DISPLAY[i] {
[255u8, 255, 255, 255]
} else {
[0u8, 0, 0, 255]
};
pixel.copy_from_slice(&color);
}
i += 1;
}
debug!("drawn {} pixels", i);
pixels.render().expect("could not render");
window.request_redraw();
}
_ => (),
}
}
} }
fn read_hdr_window(buffer: &[u8]) -> Result<HdrWindow, String> { fn read_hdr_window(buffer: &[u8]) -> Result<HdrWindow, String> {
@ -152,7 +261,7 @@ fn check_payload_size(buf: &[u8], expected: usize) -> bool {
return true; return true;
} }
println!( error!(
"expected a payload length of {} but got {}", "expected a payload length of {} but got {}",
expected, actual expected, actual
); );
@ -164,24 +273,39 @@ fn print_bitmap_linear_win(header: &HdrWindow, payload: &[u8]) {
return; return;
} }
println!( info!(
"top left is offset {} tiles in x-direction and {} pixels in y-direction", "top left is offset {} tiles in x-direction and {} pixels in y-direction",
header.x, header.y header.x, header.y
); );
let mut text_repr = String::new();
for y in 0..header.h { for y in 0..header.h {
for byte_x in 0..header.w { for byte_x in 0..header.w {
let byte_index = (y * header.w + byte_x) as usize; let byte_index = (y * header.w + byte_x) as usize;
let byte = payload[byte_index]; let byte = payload[byte_index];
for e in (1u8..7u8).rev() { for pixel_x in 1u8..=8u8 {
let bitmask = 1 << e; let bit_index = 8 - pixel_x;
let char = if byte & bitmask != 0 { '█' } else { ' ' }; let bitmask = 1 << bit_index;
print!("{}", char); let is_set = byte & bitmask != 0;
let char = if is_set { '█' } else { ' ' };
text_repr.push(char);
let x = byte_x * TILE_SIZE + pixel_x as u16;
let translated_x = (x + header.x) as usize;
let translated_y = (y + header.y) as usize;
unsafe {
DISPLAY[translated_y * PIXEL_WIDTH as usize + translated_x] = is_set;
}
} }
} }
println!(); text_repr.push('\n');
} }
info!("{}", text_repr);
} }
// TODO: actually convert from CP437 // TODO: actually convert from CP437
@ -190,13 +314,17 @@ fn print_cp437_data(header: &HdrWindow, payload: &[u8]) {
return; return;
} }
println!("top left is offset by ({} | {}) tiles", header.x, header.y); info!("top left is offset by ({} | {}) tiles", header.x, header.y);
let mut str = String::new();
for y in 0..header.h { for y in 0..header.h {
for x in 0..header.w { for x in 0..header.w {
let byte_index = (y * header.w + x) as usize; let byte_index = (y * header.w + x) as usize;
print!("{}", payload[byte_index] as char) str.push(payload[byte_index] as char);
} }
println!(); str.push('\n');
} }
info!("{}", str);
} }