diff --git a/src/parser.rs b/src/parser.rs index f7f4044..d1470c5 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,11 +1,11 @@ use crate::framebuffer::FrameBuffer; use const_format::formatcp; use log::{info, warn}; -use std::simd::{u32x8, Simd, SimdUint}; +use std::simd::{u16x16, u32x8, u8x32, Simd, SimdPartialEq, SimdUint, ToBitMask}; use std::sync::Arc; use tokio::io::AsyncWriteExt; -pub const PARSER_LOOKAHEAD: usize = "PX 1234 1234 rrggbbaa\n".len(); // Longest possible command +pub const PARSER_LOOKAHEAD: usize = 32; // "PX 1234 1234 rrggbbaa\n".len(); // Longest possible command pub const HELP_TEXT: &[u8] = formatcp!("\ Pixelflut server powered by breakwater https://github.com/sbernauer/breakwater Available commands: @@ -75,164 +75,110 @@ pub async fn parse_pixelflut_commands( let current_command = unsafe { (buffer.as_ptr().add(i) as *const u64).read_unaligned() }; if current_command & 0x00ff_ffff == string_to_number(b"PX \0\0\0\0\0") { i += 3; - // Parse first x coordinate char - if buffer[i] >= b'0' && buffer[i] <= b'9' { - x = (buffer[i] - b'0') as usize; - i += 1; - - // Parse optional second x coordinate char - if buffer[i] >= b'0' && buffer[i] <= b'9' { - // TODO: Test bitshifts and add instead of multiplication - // i = (i << 3) + (i << 1); - // i = (i * 8) + (i * 2); - // i = 8i + 2i - // i = 10i - x = 10 * x + (buffer[i] - b'0') as usize; - i += 1; - - // Parse optional third x coordinate char - if buffer[i] >= b'0' && buffer[i] <= b'9' { - x = 10 * x + (buffer[i] - b'0') as usize; - i += 1; - - // Parse optional forth x coordinate char - if buffer[i] >= b'0' && buffer[i] <= b'9' { - x = 10 * x + (buffer[i] - b'0') as usize; - i += 1; - } - } - } - - // Separator between x and y - if buffer[i] == b' ' { - i += 1; - - // Parse first y coordinate char - if buffer[i] >= b'0' && buffer[i] <= b'9' { - y = (buffer[i] - b'0') as usize; - i += 1; + let (x, y, parsed_bytes) = simd_parse_coord(&buffer[i..i + 10]); + // dbg!(x, y, parsed_bytes); - // Parse optional second y coordinate char - if buffer[i] >= b'0' && buffer[i] <= b'9' { - y = 10 * y + (buffer[i] - b'0') as usize; - i += 1; + let mut x = x as usize; + let mut y = y as usize; + i += parsed_bytes as usize; - // Parse optional third y coordinate char - if buffer[i] >= b'0' && buffer[i] <= b'9' { - y = 10 * y + (buffer[i] - b'0') as usize; - i += 1; + x += connection_x_offset; + y += connection_y_offset; - // Parse optional forth y coordinate char - if buffer[i] >= b'0' && buffer[i] <= b'9' { - y = 10 * y + (buffer[i] - b'0') as usize; - i += 1; - } - } - } + // Separator between coordinates and color + if buffer[i] == b' ' { + i += 1; - x += connection_x_offset; - y += connection_y_offset; + // TODO: Determine what clients use more: RGB, RGBA or gg variant. + // If RGBA is used more often move the RGB code below the RGBA code - // Separator between coordinates and color - if buffer[i] == b' ' { - i += 1; + // Must be followed by 6 bytes RGB and newline or ... + if buffer[i + 6] == b'\n' { + last_byte_parsed = i + 6; + i += 7; // We can advance one byte more than normal as we use continue and therefore not get incremented at the end of the loop - // TODO: Determine what clients use more: RGB, RGBA or gg variant. - // If RGBA is used more often move the RGB code below the RGBA code + let rgba: u32 = simd_unhex(&buffer[i - 7..i + 1]); - // Must be followed by 6 bytes RGB and newline or ... - if buffer[i + 6] == b'\n' { - last_byte_parsed = i + 6; - i += 7; // We can advance one byte more than normal as we use continue and therefore not get incremented at the end of the loop + fb.set(x, y, rgba & 0x00ff_ffff); + continue; + } - let rgba: u32 = simd_unhex(&buffer[i - 7..i + 1]); + // ... or must be followed by 8 bytes RGBA and newline + #[cfg(not(feature = "alpha"))] + if buffer[i + 8] == b'\n' { + last_byte_parsed = i + 8; + i += 9; // We can advance one byte more than normal as we use continue and therefore not get incremented at the end of the loop - fb.set(x, y, rgba & 0x00ff_ffff); - continue; - } + let rgba: u32 = simd_unhex(&buffer[i - 9..i - 1]); - // ... or must be followed by 8 bytes RGBA and newline - #[cfg(not(feature = "alpha"))] - if buffer[i + 8] == b'\n' { - last_byte_parsed = i + 8; - i += 9; // We can advance one byte more than normal as we use continue and therefore not get incremented at the end of the loop + fb.set(x, y, rgba & 0x00ff_ffff); + continue; + } + #[cfg(feature = "alpha")] + if buffer[i + 8] == b'\n' { + last_byte_parsed = i + 8; + i += 9; // We can advance one byte more than normal as we use continue and therefore not get incremented at the end of the loop - let rgba: u32 = simd_unhex(&buffer[i - 9..i - 1]); + let rgba = simd_unhex(&buffer[i - 9..i - 1]); - fb.set(x, y, rgba & 0x00ff_ffff); - continue; - } - #[cfg(feature = "alpha")] - if buffer[i + 8] == b'\n' { - last_byte_parsed = i + 8; - i += 9; // We can advance one byte more than normal as we use continue and therefore not get incremented at the end of the loop + let alpha = (rgba >> 24) & 0xff; - let rgba = simd_unhex(&buffer[i - 9..i - 1]); + if alpha == 0 || x >= fb.get_width() || y >= fb.get_height() { + continue; + } - let alpha = (rgba >> 24) & 0xff; + let alpha_comp = 0xff - alpha; + let current = fb.get_unchecked(x, y); + let r = (rgba >> 16) & 0xff; + let g = (rgba >> 8) & 0xff; + let b = rgba & 0xff; - if alpha == 0 || x >= fb.get_width() || y >= fb.get_height() { - continue; - } + let r: u32 = (((current >> 24) & 0xff) * alpha_comp + r * alpha) / 0xff; + let g: u32 = (((current >> 16) & 0xff) * alpha_comp + g * alpha) / 0xff; + let b: u32 = (((current >> 8) & 0xff) * alpha_comp + b * alpha) / 0xff; - let alpha_comp = 0xff - alpha; - let current = fb.get_unchecked(x, y); - let r = (rgba >> 16) & 0xff; - let g = (rgba >> 8) & 0xff; - let b = rgba & 0xff; - - let r: u32 = - (((current >> 24) & 0xff) * alpha_comp + r * alpha) / 0xff; - let g: u32 = - (((current >> 16) & 0xff) * alpha_comp + g * alpha) / 0xff; - let b: u32 = - (((current >> 8) & 0xff) * alpha_comp + b * alpha) / 0xff; - - fb.set(x, y, r << 16 | g << 8 | b); - continue; - } + fb.set(x, y, r << 16 | g << 8 | b); + continue; + } - // ... for the efficient/lazy clients - if buffer[i + 2] == b'\n' { - last_byte_parsed = i + 2; - i += 3; // We can advance one byte more than normal as we use continue and therefore not get incremented at the end of the loop + // ... for the efficient/lazy clients + if buffer[i + 2] == b'\n' { + last_byte_parsed = i + 2; + i += 3; // We can advance one byte more than normal as we use continue and therefore not get incremented at the end of the loop - let base = simd_unhex(&buffer[i - 3..i + 5]) & 0xff; + let base = simd_unhex(&buffer[i - 3..i + 5]) & 0xff; - let rgba: u32 = base << 16 | base << 8 | base; + let rgba: u32 = base << 16 | base << 8 | base; - fb.set(x, y, rgba); + fb.set(x, y, rgba); - continue; - } - } + continue; + } + } - // End of command to read Pixel value - if buffer[i] == b'\n' { - last_byte_parsed = i; - i += 1; - if let Some(rgb) = fb.get(x, y) { - match stream - .write_all( - format!( - "PX {} {} {:06x}\n", - // We don't want to return the actual (absolute) coordinates, the client should also get the result offseted - x - connection_x_offset, - y - connection_y_offset, - rgb.to_be() >> 8 - ) - .as_bytes(), - ) - .await - { - Ok(_) => (), - Err(_) => continue, - } - } - continue; - } + // End of command to read Pixel value + if buffer[i] == b'\n' { + last_byte_parsed = i; + i += 1; + if let Some(rgb) = fb.get(x, y) { + match stream + .write_all( + format!( + "PX {} {} {:06x}\n", + // We don't want to return the actual (absolute) coordinates, the client should also get the result offseted + x - connection_x_offset, + y - connection_y_offset, + rgb.to_be() >> 8 + ) + .as_bytes(), + ) + .await + { + Ok(_) => (), + Err(_) => continue, } } + continue; } } else if current_command & 0x0000_ffff_ffff_ffff == string_to_number(b"OFFSET \0\0") { i += 7; @@ -357,6 +303,57 @@ fn simd_unhex(value: &[u8]) -> u32 { shifted.reduce_or() } +const SIMD_SPACE_CHAR: Simd = u8x32::from_array([b' '; 32]); +const SIMD_NEWLINE_CHAR: Simd = u8x32::from_array([b'\n'; 32]); +const SIMD_0_CHAR: Simd = u8x32::from_array([b'0'; 32]); +const SHUFFLE_PATTERNS: [(u8, Simd); u16::MAX as usize + 1] = + manually_calculate_shuffle_patterns(); +const DECIMAL_FACTORS_X: Simd = + u16x16::from_array([1000, 100, 10, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); +const DECIMAL_FACTORS_Y: Simd = + u16x16::from_array([0, 0, 0, 0, 1000, 100, 10, 1, 0, 0, 0, 0, 0, 0, 0, 0]); + +// Longest possible space bitmask = "1234 1234 " => 10 chars +const SPACES_BITMASK_MASK: u32 = 0b0000_0000_0000_0000_0011_1111_1111; + +// Input: 32 characters starting where the x coordinate starts, eg. "1234 4321" +// Returns: (x, y, total length of text containing " ") +#[inline(always)] +fn simd_parse_coord(value: &[u8]) -> (u16, u16, u8) { + #[cfg(debug_assertions)] + assert!(value.len() >= 10); + + // let chars = unsafe { u8x32::from_array(*value.as_ptr().cast()) }; + let chars = u8x32::from_array([ + value[0], value[1], value[2], value[3], value[4], value[5], value[6], value[7], value[8], + value[9], 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]); + // dbg!(chars); + + let digits = chars - SIMD_0_CHAR; + + // ATTENTION: Bitmask starts with LSB (so kind of wrong order) + let spaces_bitmask = chars.simd_eq(SIMD_SPACE_CHAR).to_bitmask(); + let newline_bitmask = chars.simd_eq(SIMD_NEWLINE_CHAR).to_bitmask(); + let spaces_bitmask = (spaces_bitmask | newline_bitmask) & SPACES_BITMASK_MASK; + dbg!(format!("{spaces_bitmask:016b}")); + + // SAFETY: As SHUFFLE_PATTERNS has length `u16::MAX as usize + 1` and we use a us16 to index into it it will always succeed + let (bytes_parsed, shuffle_pattern) = + unsafe { *SHUFFLE_PATTERNS.get_unchecked(spaces_bitmask as usize) }; + // TODO: This seems to be a very slow operation, research performance of a intrinsic (native) operation + let digits = digits.swizzle_dyn(shuffle_pattern); + // dbg!(digits); + + let digits = unsafe { *(&digits as *const u8x32 as *const u16x16) }; + dbg!(digits); + + let x = (digits * DECIMAL_FACTORS_X).reduce_sum(); + let y = (digits * DECIMAL_FACTORS_Y).reduce_sum(); + + (x, y, bytes_parsed) +} + pub fn check_cpu_support() { #[cfg(target_arch = "x86_64")] { @@ -376,13 +373,301 @@ pub fn check_cpu_support() { } } +// Let's add the stuff manually, we can always automate later +const fn manually_calculate_shuffle_patterns() -> [(u8, Simd); u16::MAX as usize + 1] { + let mut shuffle_patterns = [(0, u8x32::from_array([255; 32])); u16::MAX as usize + 1]; + + // 9 9 + shuffle_patterns[0b0000_0000_0000_1010] = ( + 3, + u8x32::from_array([ + 255, 255, 255, 255, 255, 255, 0, 255, // X coordinate + 255, 255, 255, 255, 255, 255, 2, 255, // y coordinate + 255, 255, 255, 255, 255, 255, 255, 255, // red + green + 255, 255, 255, 255, 255, 255, 255, 255, // blue + padding + ]), + ); + + // 9 99 + shuffle_patterns[0b0000_0000_0001_0010] = ( + 4, + u8x32::from_array([ + 255, 255, 255, 255, 255, 255, 0, 255, // X coordinate + 255, 255, 255, 255, 2, 255, 3, 255, // y coordinate + 255, 255, 255, 255, 255, 255, 255, 255, // red + green + 255, 255, 255, 255, 255, 255, 255, 255, // blue + padding + ]), + ); + + // 9 999 + shuffle_patterns[0b0000_0000_0010_0010] = ( + 5, + u8x32::from_array([ + 255, 255, 255, 255, 255, 255, 0, 255, // X coordinate + 255, 255, 2, 255, 3, 255, 4, 255, // y coordinate + 255, 255, 255, 255, 255, 255, 255, 255, // red + green + 255, 255, 255, 255, 255, 255, 255, 255, // blue + padding + ]), + ); + + // 9 9999 + shuffle_patterns[0b0000_0000_0100_0010] = ( + 6, + u8x32::from_array([ + 255, 255, 255, 255, 255, 255, 0, 255, // X coordinate + 2, 255, 3, 255, 4, 255, 5, 255, // y coordinate + 255, 255, 255, 255, 255, 255, 255, 255, // red + green + 255, 255, 255, 255, 255, 255, 255, 255, // blue + padding + ]), + ); + + // 99 9 + shuffle_patterns[0b0000_0000_0001_0100] = ( + 4, + u8x32::from_array([ + 255, 255, 255, 255, 0, 255, 1, 255, // X coordinate + 255, 255, 255, 255, 255, 255, 3, 255, // y coordinate + 255, 255, 255, 255, 255, 255, 255, 255, // red + green + 255, 255, 255, 255, 255, 255, 255, 255, // blue + padding + ]), + ); + + // 99 99 + shuffle_patterns[0b0000_0000_0010_0100] = ( + 5, + u8x32::from_array([ + 255, 255, 255, 255, 0, 255, 1, 255, // X coordinate + 255, 255, 255, 255, 3, 255, 4, 255, // y coordinate + 255, 255, 255, 255, 255, 255, 255, 255, // red + green + 255, 255, 255, 255, 255, 255, 255, 255, // blue + padding + ]), + ); + + // 99 999 + shuffle_patterns[0b0000_0000_0100_0100] = ( + 6, + u8x32::from_array([ + 255, 255, 255, 255, 0, 255, 1, 255, // X coordinate + 255, 255, 3, 255, 4, 255, 5, 255, // y coordinate + 255, 255, 255, 255, 255, 255, 255, 255, // red + green + 255, 255, 255, 255, 255, 255, 255, 255, // blue + padding + ]), + ); + + // 99 9999 + shuffle_patterns[0b0000_0000_1000_0100] = ( + 7, + u8x32::from_array([ + 255, 255, 255, 255, 0, 255, 1, 255, // X coordinate + 3, 255, 4, 255, 5, 255, 6, 255, // y coordinate + 255, 255, 255, 255, 255, 255, 255, 255, // red + green + 255, 255, 255, 255, 255, 255, 255, 255, // blue + padding + ]), + ); + + // 999 9 + shuffle_patterns[0b0000_0000_0010_1000] = ( + 5, + u8x32::from_array([ + 255, 255, 0, 255, 1, 255, 2, 255, // X coordinate + 255, 255, 255, 255, 255, 255, 4, 255, // y coordinate + 255, 255, 255, 255, 255, 255, 255, 255, // red + green + 255, 255, 255, 255, 255, 255, 255, 255, // blue + padding + ]), + ); + + // 999 99 + shuffle_patterns[0b0000_0000_0100_1000] = ( + 6, + u8x32::from_array([ + 255, 255, 0, 255, 1, 255, 2, 255, // X coordinate + 255, 255, 255, 255, 4, 255, 5, 255, // y coordinate + 255, 255, 255, 255, 255, 255, 255, 255, // red + green + 255, 255, 255, 255, 255, 255, 255, 255, // blue + padding + ]), + ); + + // 999 999 + shuffle_patterns[0b0000_0000_1000_1000] = ( + 7, + u8x32::from_array([ + 255, 255, 0, 255, 1, 255, 2, 255, // X coordinate + 255, 255, 4, 255, 5, 255, 6, 255, // y coordinate + 255, 255, 255, 255, 255, 255, 255, 255, // red + green + 255, 255, 255, 255, 255, 255, 255, 255, // blue + padding + ]), + ); + + // 999 9999 + shuffle_patterns[0b0000_0001_0000_1000] = ( + 8, + u8x32::from_array([ + 255, 255, 0, 255, 1, 255, 2, 255, // X coordinate + 4, 255, 5, 255, 6, 255, 7, 255, // y coordinate + 255, 255, 255, 255, 255, 255, 255, 255, // red + green + 255, 255, 255, 255, 255, 255, 255, 255, // blue + padding + ]), + ); + + // 9999 9 + shuffle_patterns[0b0000_0000_0101_0000] = ( + 6, + u8x32::from_array([ + 0, 255, 1, 255, 2, 255, 3, 255, // X coordinate + 255, 255, 255, 255, 255, 255, 5, 255, // y coordinate + 255, 255, 255, 255, 255, 255, 255, 255, // red + green + 255, 255, 255, 255, 255, 255, 255, 255, // blue + padding + ]), + ); + + // 9999 99 + shuffle_patterns[0b0000_0000_1001_0000] = ( + 7, + u8x32::from_array([ + 0, 255, 1, 255, 2, 255, 3, 255, // X coordinate + 255, 255, 255, 255, 5, 255, 6, 255, // y coordinate + 255, 255, 255, 255, 255, 255, 255, 255, // red + green + 255, 255, 255, 255, 255, 255, 255, 255, // blue + padding + ]), + ); + + // 9999 999 + shuffle_patterns[0b0000_0001_0001_0000] = ( + 8, + u8x32::from_array([ + 0, 255, 1, 255, 2, 255, 3, 255, // X coordinate + 255, 255, 5, 255, 6, 255, 7, 255, // y coordinate + 255, 255, 255, 255, 255, 255, 255, 255, // red + green + 255, 255, 255, 255, 255, 255, 255, 255, // blue + padding + ]), + ); + + // 9999 9999 + shuffle_patterns[0b0000_0010_0001_0000] = ( + 9, + u8x32::from_array([ + 0, 255, 1, 255, 2, 255, 3, 255, // X coordinate + 5, 255, 6, 255, 7, 255, 8, 255, // y coordinate + 255, 255, 255, 255, 255, 255, 255, 255, // red + green + 255, 255, 255, 255, 255, 255, 255, 255, // blue + padding + ]), + ); + + shuffle_patterns +} + +// // Sorry for the weird way of writing, but we need to write const code +// const fn calculate_shuffle_patterns() -> [Simd; u16::MAX as usize + 1] { +// // We default to a shift pattern of only ones, which will zero the vector when using this as shifting pattern +// // TODO: Maybe it's better to have some sort of other marker to mark invalid user input, +// // e.g. wrapping the `u8x16` in an `Option` +// let mut shuffle_patterns = [u8x16::from_array([255; 16]); u16::MAX as usize + 1]; + +// let mut x_coord_length: u8 = 1; +// let mut y_coord_length: u8 = 1; +// while x_coord_length <= 4 { +// while y_coord_length <= 4 { +// let mut spaces = [true; 16]; +// let mut spaces_index = 0; + +// let mut x_coord_length_iterator = 0; +// while x_coord_length_iterator < x_coord_length { +// spaces[spaces_index as usize] = false; +// spaces_index += 1; +// x_coord_length_iterator += 1; +// } + +// // Skip the actual space between x and y +// spaces_index += 1; + +// let mut y_coord_length_iterator: u8 = 0; +// while y_coord_length_iterator < y_coord_length { +// spaces[spaces_index as usize] = false; +// spaces_index += 1; +// y_coord_length_iterator += 1; +// } + +// let spaces_bitmask = bool_array_to_bitmask_u16(&spaces); +// shuffle_patterns[spaces_bitmask as usize] = u8x16::from_array([0; 16]); + +// y_coord_length += 1; +// } +// y_coord_length = 0; +// x_coord_length += 1; +// } + +// shuffle_patterns +// } + +// ATTENTION: Bitmask starts with LSB (so kind of wrong order) +const fn bool_array_to_bitmask_u32(bools: &[bool]) -> u32 { + assert!(bools.len() == 32); + + let mut bitmask = 0; + let mut i = 0; + while i < bools.len() { + if bools[i] { + bitmask |= 1 << i; + } + i += 1; + } + + bitmask +} + #[cfg(test)] mod test { use super::*; #[test] - fn test_from_hex_char() { + fn test_simd_unhex() { assert_eq!(simd_unhex(b"01234567"), 0x67452301); assert_eq!(simd_unhex(b"fedcba98"), 0x98badcfe); } + + #[test] + fn test_simd_parse_coord() { + assert_eq!(simd_parse_coord(b"1 2 rrggbb"), (1, 2, 3)); + assert_eq!(simd_parse_coord(b"1234 4321 rrggbb"), (1234, 4321, 9)); + for x in 0..=9999 { + for y in 0..=9999 { + let coords = format!("{x} {y}"); + let chars = format!("{coords} rrggbb"); + assert_eq!( + simd_parse_coord(chars.as_bytes()), + (x, y, coords.len() as u8) + ); + } + } + } + + #[test] + fn test_bool_vec_to_bitmask() { + assert_eq!(bool_array_to_bitmask_u32(&[false; 32]), 0); + assert_eq!(bool_array_to_bitmask_u32(&[true; 32]), u32::MAX); + assert_eq!( + bool_array_to_bitmask_u32(&[ + true, false, false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false + ]), + 1 + ); + assert_eq!( + bool_array_to_bitmask_u32(&[ + false, false, false, false, false, false, false, true, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false + ]), + 1 << 7 + ); + assert_eq!( + bool_array_to_bitmask_u32(&[ + false, false, false, false, false, false, false, false, false, false, false, false, + false, false, false, true, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false + ]), + 1 << 15 + ); + } }