From 369f0e0aa4ffc43c3dd6634eb101408d139fea29 Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Sun, 11 Jun 2023 01:57:51 +0200 Subject: [PATCH] feat: Implement alpha support (behind feature flag) --- Cargo.toml | 1 + README.md | 6 ++---- src/framebuffer.rs | 5 +++++ src/network.rs | 15 ++++++++++----- src/parser.rs | 45 ++++++++++++++++++++++++++++++++++++--------- 5 files changed, 54 insertions(+), 18 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 45490b5..0008750 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ rstest = "0.17" [features] default = ["vnc"] vnc = ["dep:vncserver"] +alpha = [] [lib] name = "breakwater" diff --git a/README.md b/README.md index ae088c1..14f79b8 100644 --- a/README.md +++ b/README.md @@ -93,11 +93,12 @@ You can get the list of available features by looking at the [Cargo.toml](Cargo. As of writing the following features are supported * `vnc` (enabled by default): Starts a VNC server, where users can connect to. Needs `libvncserver-dev` to be installed. Please note that the VNC server offers basically no latency, but consumes quite some CPU. +* `alpha` (disabled by default): Respect alpha values during `PX` commands. Disabled by default as this can cause performance degradation. To e.g. turn the VNS server off, build with ```bash -cargo run --release --no-default-features # --features feature-to-enable +cargo run --release --no-default-features # --features alpha,vnc to explicitly enable ``` # Run in docker container @@ -153,6 +154,3 @@ The servers were connected with two 40G and one 10G links, through which traffic |-------------------------------------------------------|----------|---------------------| | [Shoreline](https://github.com/TobleMiner/shoreline) | C | 34 Gbit/s | | [Breakwater](https://github.com/sbernauer/breakwater) | Rust | 52 Gbit/s | - -# TODOs -* Implement Alpha channel feature. For performance reasons there should be a compile-time switch for it diff --git a/src/framebuffer.rs b/src/framebuffer.rs index b8302e0..80704bf 100644 --- a/src/framebuffer.rs +++ b/src/framebuffer.rs @@ -41,6 +41,11 @@ impl FrameBuffer { } } + #[inline(always)] + pub fn get_unchecked(&self, x: usize, y: usize) -> u32 { + unsafe { (*self.buffer.get())[x + y * self.width] } + } + #[inline(always)] pub fn set(&self, x: usize, y: usize, rgba: u32) { // TODO: If we make the FrameBuffer large enough (e.g. 10_000 x 10_000) we don't need to check the bounds here (x and y are max 4 digit numbers). diff --git a/src/network.rs b/src/network.rs index 20ff984..f191d71 100644 --- a/src/network.rs +++ b/src/network.rs @@ -236,11 +236,16 @@ mod test { #[case("PX 0 42 abcdef\nPX 0 42\n", "PX 0 42 abcdef\n")] #[case("PX 42 0 abcdef\nPX 42 0\n", "PX 42 0 abcdef\n")] // With alpha - // TODO: At the moment alpha channel is not supported and silently ignored (pixels are painted with 0% transparency) - #[case("PX 0 0 ffffffaa\nPX 0 0\n", "PX 0 0 ffffff\n")] - #[case("PX 0 0 abcdefaa\nPX 0 0\n", "PX 0 0 abcdef\n")] - #[case("PX 0 1 abcdefaa\nPX 0 1\n", "PX 0 1 abcdef\n")] - #[case("PX 1 0 abcdefaa\nPX 1 0\n", "PX 1 0 abcdef\n")] + #[case("PX 0 0 ffffff00\nPX 0 0\n", if cfg!(feature = "alpha") {"PX 0 0 000000\n"} else {"PX 0 0 ffffff\n"})] + #[case("PX 0 0 ffffffff\nPX 0 0\n", "PX 0 0 ffffff\n")] + #[case("PX 0 1 abcdef00\nPX 0 1\n", if cfg!(feature = "alpha") {"PX 0 1 000000\n"} else {"PX 0 1 abcdef\n"})] + #[case("PX 1 0 abcdefff\nPX 1 0\n", "PX 1 0 abcdef\n")] + #[case("PX 0 0 ffffff88\nPX 0 0\n", if cfg!(feature = "alpha") {"PX 0 0 888888\n"} else {"PX 0 0 ffffff\n"})] + #[case("PX 0 0 ffffff11\nPX 0 0\n", if cfg!(feature = "alpha") {"PX 0 0 111111\n"} else {"PX 0 0 ffffff\n"})] + #[case("PX 0 0 abcdef80\nPX 0 0\n", if cfg!(feature = "alpha") {"PX 0 0 556677\n"} else {"PX 0 0 abcdef\n"})] + // 0xab = 171, 0x88 = 136 + // (171 * 136) / 255 = 91 = 0x5b + #[case("PX 0 0 abcdef88\nPX 0 0\n", if cfg!(feature = "alpha") {"PX 0 0 5b6d7f\n"} else {"PX 0 0 abcdef\n"})] // Short commands #[case("PX 0 0 00\nPX 0 0\n", "PX 0 0 000000\n")] #[case("PX 0 0 ff\nPX 0 0\n", "PX 0 0 ffffff\n")] diff --git a/src/parser.rs b/src/parser.rs index 7643c56..ec36a45 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -10,7 +10,7 @@ Pixelflut server powered by breakwater https://github.com/sbernauer/breakwater Available commands: HELP: Show this help PX x y rrggbb: Color the pixel (x,y) with the given hexadecimal color rrggbb -PX x y rrggbbaa: Color the pixel (x,y) with the given hexadecimal color rrggbb (alpha channel is ignored for now) +PX x y rrggbbaa: Color the pixel (x,y) with the given hexadecimal color rrggbb (alpha channel is ignored by default, unless breakwater is compiled with \"--feature alpha\". This is for performance reasons) PX x y gg: Color the pixel (x,y) with the hexadecimal color gggggg. Basically this is the same as the other commands, but is a more efficient way of filling white, black or gray areas PX x y: Get the color value of the pixel (x,y) SIZE: Get the size of the drawing surface, e.g. `SIZE 1920 1080` @@ -148,13 +148,11 @@ pub async fn parse_pixelflut_commands( | (ASCII_HEXADECIMAL_VALUES[buffer[i - 6] as usize] as u32); fb.set(x, y, rgba); - if cfg!(feature = "count_pixels") { - // statistics.inc_pixels(ip); - } continue; } // ... 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 @@ -172,10 +170,42 @@ pub async fn parse_pixelflut_commands( | (ASCII_HEXADECIMAL_VALUES[buffer[i - 8] as usize] as u32); fb.set(x, y, rgba); - if cfg!(feature = "count_pixels") { - // statistics.inc_pixels(ip); + + 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 = + (ASCII_HEXADECIMAL_VALUES[buffer[i - 3] as usize] as u32) << 4 + | (ASCII_HEXADECIMAL_VALUES[buffer[i - 2] as usize] as u32); + + if alpha == 0 || x >= fb.get_width() || y >= fb.get_height() { + continue; } + let alpha_comp = 0xff - alpha; + let current = fb.get_unchecked(x, y); + let r = (ASCII_HEXADECIMAL_VALUES[buffer[i - 5] as usize] as u32) + << 4 + | (ASCII_HEXADECIMAL_VALUES[buffer[i - 4] as usize] as u32); + let g = (ASCII_HEXADECIMAL_VALUES[buffer[i - 7] as usize] as u32) + << 4 + | (ASCII_HEXADECIMAL_VALUES[buffer[i - 6] as usize] as u32); + let b = (ASCII_HEXADECIMAL_VALUES[buffer[i - 9] as usize] as u32) + << 4 + | (ASCII_HEXADECIMAL_VALUES[buffer[i - 8] as usize] as u32); + + 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; } @@ -197,9 +227,6 @@ pub async fn parse_pixelflut_commands( | (ASCII_HEXADECIMAL_VALUES[buffer[i - 2] as usize] as u32); fb.set(x, y, rgba); - if cfg!(feature = "count_pixels") { - // statistics.inc_pixels(ip); - } continue; }