Skip to content
Draft
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ As of writing the following features are supported:
* `alpha` (disabled by default): Respect alpha values during `PX` commands. Disabled by default as this can cause performance degradation.
* `binary-set-pixel` (disabled by default): Allows use of the `PB` command.
* `binary-sync-pixels`(disabled by default): Allows use of the `PXMULTI` command.
* `prometheus` (enabled by default): Enables the Prometheus metrics exporter. Can be disabled to compile for 32-bit targets.

To e.g. turn the VNC server off, build with

Expand Down
4 changes: 4 additions & 0 deletions breakwater-parser/src/framebuffer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,17 @@ pub trait FrameBuffer {
/// make sure x and y are in bounds
unsafe fn get_unchecked(&self, x: usize, y: usize) -> u32;

/// RGBA is always in this order within the byte.
/// However, the memory order is reversed for performance reasons (so native u32’s are laid out in memory).
/// Big endian in memory: RGBA, little endian in memory: ABGR
fn set(&self, x: usize, y: usize, rgba: u32);

/// We can *not* take an `&[u32]` for the pixel here, as `std::slice::from_raw_parts` requires the data to be
/// aligned. As the data already is stored in a buffer we can not guarantee it's correctly aligned, so let's just
/// treat the pixels as raw bytes.
///
/// Returns the coordinates where we landed after filling
// FIXME: Broken on big-endian
#[inline(always)]
fn set_multi(&self, start_x: usize, start_y: usize, pixels: &[u8]) -> (usize, usize) {
let starting_index = start_x + start_y * self.get_width();
Expand Down
11 changes: 6 additions & 5 deletions breakwater-parser/src/original.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,9 @@ impl<FB: FrameBuffer> Parser for OriginalParser<FB> {
}

while i < loop_end {
// BUG: byte order is inverted on big-endian

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops, that was a debugging comment, this should be removed

let current_command =
unsafe { (buffer.as_ptr().add(i) as *const u64).read_unaligned() };
unsafe { (buffer.as_ptr().add(i) as *const u64).read_unaligned() }.to_le();
if current_command & 0x00ff_ffff == PX_PATTERN {
i += 3;

Expand Down Expand Up @@ -189,7 +190,7 @@ impl<FB: FrameBuffer> Parser for OriginalParser<FB> {
// We don't want to return the actual (absolute) coordinates, the client should also get the result offseted
x - self.connection_x_offset,
y - self.connection_y_offset,
rgb.to_be() >> 8
rgb.swap_bytes() >> 8
)
.as_bytes(),
);
Expand All @@ -201,7 +202,7 @@ impl<FB: FrameBuffer> Parser for OriginalParser<FB> {
#[cfg(feature = "binary-set-pixel")]
if current_command & 0x0000_ffff == PB_PATTERN {
let command_bytes =
unsafe { (buffer.as_ptr().add(i + 2) as *const u64).read_unaligned() };
unsafe { (buffer.as_ptr().add(i + 2) as *const u64).read_unaligned() }.to_le();

let x = u16::from_le((command_bytes) as u16);
let y = u16::from_le((command_bytes >> 16) as u16);
Expand All @@ -217,7 +218,7 @@ impl<FB: FrameBuffer> Parser for OriginalParser<FB> {
#[cfg(feature = "binary-sync-pixels")]
if current_command & 0x00ff_ffff_ffff_ffff == PXMULTI_PATTERN {
i += "PXMULTI".len();
let header = unsafe { (buffer.as_ptr().add(i) as *const u64).read_unaligned() };
let header = unsafe { (buffer.as_ptr().add(i) as *const u64).read_unaligned() }.to_le();
i += 8;

let start_x = u16::from_le((header) as u16);
Expand Down Expand Up @@ -356,7 +357,7 @@ pub(crate) fn simd_unhex(value: *const u8) -> u32 {

#[inline(always)]
fn parse_coordinate(buffer: *const u8, current_index: &mut usize) -> (usize, bool) {
let digits = unsafe { (buffer.add(*current_index) as *const usize).read_unaligned() };
let digits = unsafe { (buffer.add(*current_index) as *const usize).read_unaligned() }.to_le();

let mut result = 0;
let mut visited = false;
Expand Down
7 changes: 4 additions & 3 deletions breakwater-parser/src/refactored.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ impl<FB: FrameBuffer> RefactoredParser<FB> {
let previous = idx;
idx += 2;

let command_bytes = unsafe { (buffer.as_ptr().add(idx) as *const u64).read_unaligned() };
let command_bytes = unsafe { (buffer.as_ptr().add(idx) as *const u64).read_unaligned() }.to_le();

let x = u16::from_le((command_bytes) as u16);
let y = u16::from_le((command_bytes >> 16) as u16);
Expand Down Expand Up @@ -182,7 +182,8 @@ impl<FB: FrameBuffer> RefactoredParser<FB> {
// We don't want to return the actual (absolute) coordinates, the client should also get the result offseted
x - self.connection_x_offset,
y - self.connection_y_offset,
rgb.to_be() >> 8
// We want big-endian order on all systems
rgb.swap_bytes() >> 8
)
.as_bytes(),
);
Expand All @@ -199,7 +200,7 @@ impl<FB: FrameBuffer> Parser for RefactoredParser<FB> {

while i < loop_end {
let current_command =
unsafe { (buffer.as_ptr().add(i) as *const u64).read_unaligned() };
unsafe { (buffer.as_ptr().add(i) as *const u64).read_unaligned() }.to_le();
if current_command & 0x00ff_ffff == PX_PATTERN {
(i, last_byte_parsed) = self.handle_pixel(buffer, i, response);
} else if cfg!(feature = "binary-set-pixel")
Expand Down
5 changes: 3 additions & 2 deletions breakwater/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ memadvise.workspace = true
ndi-sdk-sys = { workspace = true, optional = true }
number_prefix.workspace = true
page_size.workspace = true
prometheus_exporter.workspace = true
prometheus_exporter = { workspace = true, optional = true }
rusttype.workspace = true
serde_json.workspace = true
serde.workspace = true
Expand All @@ -49,13 +49,14 @@ rstest.workspace = true

[features]
# We don't enable binary-sync-pixels and binary-set-pixel by default to make it a bit harder for clients ;)
default = ["egui", "vnc"]
default = ["egui", "prometheus", "vnc"]

alpha = ["breakwater-parser/alpha"]
binary-set-pixel = ["breakwater-parser/binary-set-pixel"]
binary-sync-pixels = ["breakwater-parser/binary-sync-pixels"]
egui = ["dep:breakwater-egui-overlay", "dep:bytemuck", "dep:eframe", "dep:egui", "dep:libloading"]
native-display = ["dep:softbuffer", "dep:winit"]
prometheus = ["dep:prometheus_exporter"]
vnc = ["dep:vncserver"]
ndi = ["dep:ndi-sdk-sys"]

Expand Down
20 changes: 14 additions & 6 deletions breakwater/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,17 @@ use color_eyre::eyre::{self, Context};
use server::Server;
use tokio::sync::{broadcast, mpsc};

#[cfg(feature = "prometheus")]
use crate::prometheus_exporter::PrometheusExporter;
use crate::{
cli_args::CliArgs,
prometheus_exporter::PrometheusExporter,
sinks::{DisplaySink, ffmpeg::FfmpegSink},
statistics::{Statistics, StatisticsEvent, StatisticsInformationEvent, StatisticsSaveMode},
};

mod cli_args;
mod connection_buffer;
#[cfg(feature = "prometheus")]
mod prometheus_exporter;
mod server;
mod sinks;
Expand Down Expand Up @@ -50,6 +52,7 @@ async fn main() -> eyre::Result<()> {
// If we make the channel to big, stats will start to lag behind
// TODO: Check performance impact in real-world scenario. Maybe the statistics thread blocks the other threads
let (statistics_tx, statistics_rx) = mpsc::channel::<StatisticsEvent>(100);
#[allow(unused)]
let (statistics_information_tx, statistics_information_rx) =
broadcast::channel::<StatisticsInformationEvent>(2);
let (terminate_signal_tx, terminate_signal_rx) = broadcast::channel::<()>(1);
Expand Down Expand Up @@ -83,14 +86,18 @@ async fn main() -> eyre::Result<()> {
.await
.context("failed to start pixelflut server")?;

let mut prometheus_exporter = PrometheusExporter::new(
&args.prometheus_listen_address,
statistics_information_rx.resubscribe(),
)
.context("failed to start prometheus exporter")?;
#[cfg(feature = "prometheus")]
{
let mut prometheus_exporter = PrometheusExporter::new(
&args.prometheus_listen_address,
statistics_information_rx.resubscribe(),
)
.context("failed to start prometheus exporter")?;
}

let server_listener_thread = tokio::spawn(async move { server.start().await });
let statistics_thread = tokio::spawn(async move { statistics.run().await });
#[cfg(feature = "prometheus")]
let prometheus_exporter_thread = tokio::spawn(async move { prometheus_exporter.run().await });

let mut display_sinks = Vec::<Box<dyn DisplaySink<SharedMemoryFrameBuffer> + Send>>::new();
Expand Down Expand Up @@ -200,6 +207,7 @@ async fn main() -> eyre::Result<()> {
#[cfg(not(feature = "egui"))]
handle_ctrl_c(terminate_signal_tx).await?;

#[cfg(feature = "prometheus")]
prometheus_exporter_thread.abort();
server_listener_thread.abort();

Expand Down
Loading