File plugin.cpp
File List > boba_streaming_server > plugin.cpp
Go to the documentation of this file
#include "plugin.hpp"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
namespace ILLIXR {
namespace {
// NVENC requires aligned surfaces. Only the visible source rectangle is sent
// to the Quest renderer; padding remains an encoder implementation detail.
constexpr std::uint32_t kPerEyeVisibleWidth = NATIVE_STREAM_EYE_WIDTH;
constexpr std::uint32_t kPerEyeVisibleHeight = NATIVE_STREAM_EYE_HEIGHT;
constexpr std::uint32_t kPerEyeEncodeWidth = (kPerEyeVisibleWidth + 31U) & ~31U;
constexpr std::uint32_t kEncodeHeight = (kPerEyeVisibleHeight + 31U) & ~31U;
constexpr std::uint32_t kOverlayCommandStrideFloats = data_format::boba_frame_overlay::command_stride_floats;
constexpr std::uint64_t kModalTextureResendFrames = 300;
XrPosef to_xr_pose(const data_format::stereo_render_view& view) {
XrPosef pose{};
pose.orientation.w = 1.0F;
if (view.valid) {
pose = view;
}
return pose;
}
std::uint64_t system_time_ns() {
return static_cast<std::uint64_t>(
std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::system_clock::now().time_since_epoch()).count());
}
} // namespace
// ---- Shared-ring mapping and validation -----------------------------------
boba_streaming_server::mapped_file::~mapped_file() {
reset();
}
void boba_streaming_server::mapped_file::reset() {
if (data != nullptr) {
munmap(const_cast<std::uint8_t*>(data), size);
}
if (fd >= 0) {
close(fd);
}
fd = -1;
data = nullptr;
size = 0;
path.clear();
}
boba_streaming_server::boba_streaming_server(const std::string& name, phonebook* pb)
: threadloop{name, pb}
, switchboard_{pb->lookup_impl<switchboard>()}
, stereo_reader_{switchboard_->get_reader<data_format::stereo_frame>("stereo_frame")}
, frames_writer_{switchboard_->get_network_writer<data_format::compressed_frame>(
"compressed_frames", network::topic_config{network::topic_config::BOOST, network::topic_config::TCP})}
, modal_writer_{switchboard_->get_network_writer<data_format::boba_modal_texture>(
"boba_modal_texture", network::topic_config{network::topic_config::BOOST, network::topic_config::TCP})} {
spdlogger(switchboard_->get_env_char("BOBA_STREAMING_SERVER_LOG_LEVEL", "info"));
bitrate_ = std::max<std::int64_t>(1, switchboard_->get_env_int("BOBA_STREAM_BITRATE", 30'000'000));
framerate_ = std::max(1, switchboard_->get_env_int("BOBA_STREAM_FRAMERATE", 72));
plugin_logger_->info("Boba native Quest stream configured for AV1 {}x{} at {} fps / {:.1f} Mbps", kPerEyeEncodeWidth * 2,
kEncodeHeight, framerate_, static_cast<double>(bitrate_) / 1'000'000.0);
}
boba_streaming_server::~boba_streaming_server() = default;
// The threadloop polls switchboard state but yields when no new producer
// generation is available, avoiding a busy spin at the desktop frame rate.
threadloop::skip_option boba_streaming_server::_p_should_skip() {
report_metrics();
const auto now = std::chrono::steady_clock::now();
if (now < next_frame_time_) {
// Keep stop responsiveness while pacing actual submissions to NVENC.
std::this_thread::sleep_until(std::min(next_frame_time_, now + std::chrono::milliseconds(1)));
return skip_option::skip_and_yield;
}
const auto frame = stereo_reader_.get_ro_nullable();
if (frame == nullptr || frame->source_frame_id == 0 || frame->source_frame_id <= last_frame_id_) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
return skip_option::skip_and_yield;
}
return skip_option::run;
}
bool boba_streaming_server::map_file(const std::string& path, mapped_file* mapping) {
if (path.empty()) {
return false;
}
if (mapping->data != nullptr && mapping->path == path) {
return true;
}
mapping->reset();
const int fd = open(path.c_str(), O_RDONLY | O_CLOEXEC);
if (fd < 0) {
plugin_logger_->warn("Could not open Boba frame mapping {}", path);
return false;
}
struct stat status{};
if (fstat(fd, &status) != 0 || status.st_size <= 0) {
close(fd);
return false;
}
void* mapped = mmap(nullptr, static_cast<std::size_t>(status.st_size), PROT_READ, MAP_SHARED, fd, 0);
if (mapped == MAP_FAILED) {
close(fd);
return false;
}
mapping->fd = fd;
mapping->data = static_cast<const std::uint8_t*>(mapped);
mapping->size = static_cast<std::size_t>(status.st_size);
mapping->path = path;
plugin_logger_->info("Mapped Boba ring {} ({} bytes)", path, mapping->size);
return true;
}
bool boba_streaming_server::generation_matches(const mapped_file& mapping, std::uint64_t generation_offset,
std::uint64_t expected_generation) const {
if (mapping.data == nullptr || generation_offset > mapping.size ||
sizeof(std::uint64_t) > mapping.size - generation_offset) {
return false;
}
std::uint64_t generation = 0;
std::memcpy(&generation, mapping.data + generation_offset, sizeof(generation));
return generation == expected_generation;
}
bool boba_streaming_server::image_range_valid(const data_format::stereo_shared_image& image) const {
const std::uint64_t minimum_stride = static_cast<std::uint64_t>(image.width) * 4U;
const std::uint64_t required =
image.height == 0 ? 0 : static_cast<std::uint64_t>(image.row_stride_bytes) * (image.height - 1U) + minimum_stride;
return image.width > 0 && image.height > 0 && image.row_stride_bytes >= minimum_stride && image.byte_count >= required &&
image.byte_offset <= frame_mapping_.size && required <= frame_mapping_.size - image.byte_offset;
}
bool boba_streaming_server::overlay_range_valid(const data_format::stereo_overlay_command_range& range) const {
if (range.command_count == 0) {
return true;
}
if (range.command_stride_floats != kOverlayCommandStrideFloats ||
range.command_count > data_format::boba_frame_overlay::max_commands_per_eye) {
return false;
}
const std::uint64_t byte_count =
static_cast<std::uint64_t>(range.command_count) * range.command_stride_floats * sizeof(float);
return range.byte_offset <= overlay_mapping_.size && byte_count <= overlay_mapping_.size - range.byte_offset;
}
bool boba_streaming_server::modal_range_valid(const data_format::stereo_modal_overlay& modal) const {
if (!modal.visible) {
return true;
}
const std::uint64_t tight_row_bytes = static_cast<std::uint64_t>(modal.width) * 4U;
const std::uint64_t required_bytes = modal.height == 0
? 0
: static_cast<std::uint64_t>(modal.source_row_stride_bytes) * (modal.height - 1U) + tight_row_bytes;
return modal.width > 0 && modal.height > 0 && modal.width <= 8192 && modal.height <= 8192 &&
modal.source_row_stride_bytes >= tight_row_bytes && modal.byte_offset <= modal_mapping_.size &&
required_bytes <= modal_mapping_.size - modal.byte_offset;
}
std::vector<float> boba_streaming_server::copy_overlay_commands(const data_format::stereo_overlay_command_range& range) const {
const std::size_t float_count = static_cast<std::size_t>(range.command_count) * range.command_stride_floats;
std::vector<float> commands(float_count);
if (float_count != 0) {
std::memcpy(commands.data(), overlay_mapping_.data + range.byte_offset, float_count * sizeof(float));
}
return commands;
}
std::vector<std::uint8_t> boba_streaming_server::copy_modal_pixels(const data_format::stereo_modal_overlay& modal) const {
const std::size_t tight_row_bytes = static_cast<std::size_t>(modal.width) * 4U;
std::vector<std::uint8_t> rgba(tight_row_bytes * modal.height);
for (std::uint32_t row = 0; row < modal.height; ++row) {
std::memcpy(rgba.data() + static_cast<std::size_t>(row) * tight_row_bytes,
modal_mapping_.data + modal.byte_offset + static_cast<std::uint64_t>(row) * modal.source_row_stride_bytes,
tight_row_bytes);
}
return rgba;
}
// ---- Modal-texture reliability path ---------------------------------------
std::uint64_t boba_streaming_server::modal_texture_id(const std::vector<std::uint8_t>& rgba, std::uint32_t width,
std::uint32_t height) const {
// FNV-1a gives the modal a stable content identity across reconnects. A
// repeated ID therefore always refers to the same dimensions and bytes.
std::uint64_t hash = 1469598103934665603ULL;
const auto mix = [&hash](std::uint8_t value) {
hash <= value;
hash *= 1099511628211ULL;
};
for (std::size_t shift = 0; shift < sizeof(width); ++shift) {
mix(static_cast<std::uint8_t>((width >> (shift * 8U)) & 0xFFU));
mix(static_cast<std::uint8_t>((height >> (shift * 8U)) & 0xFFU));
}
for (std::uint8_t value : rgba) {
mix(value);
}
return hash == 0 ? 1 : hash;
}
void boba_streaming_server::publish_modal_texture_if_needed(const data_format::boba_modal_overlay& modal,
const std::vector<std::uint8_t>& rgba) {
if (!modal.visible) {
last_modal_visible_ = false;
modal_visible_frame_count_ = 0;
return;
}
++modal_visible_frame_count_;
const bool changed = modal.texture_id != last_modal_texture_id_;
const bool resend = changed || !last_modal_visible_ || modal_visible_frame_count_ % kModalTextureResendFrames == 0;
if (resend) {
auto update = modal_writer_.allocate();
update->texture_id = modal.texture_id;
update->width = modal.width;
update->height = modal.height;
update->rgba = rgba;
modal_writer_.put(std::move(update));
plugin_logger_->info("Published Boba modal texture id={} size={}x{} bytes={}", modal.texture_id, modal.width,
modal.height, rgba.size());
}
last_modal_texture_id_ = modal.texture_id;
last_modal_visible_ = true;
}
void boba_streaming_server::initialize_encoder() {
if (encoder_) {
return;
}
encoder_ = std::make_unique<nvenc_encoder>(kPerEyeEncodeWidth * 2, kEncodeHeight, bitrate_, framerate_, encoder_mode::color,
encoder_codec::av1);
if (!encoder_->initialize(vulkan_context{})) {
encoder_.reset();
throw std::runtime_error("Could not initialize the Boba NVENC AV1 encoder");
}
}
// ---- Encoded-frame publication --------------------------------------------
void boba_streaming_server::publish_encoded(const data_format::stereo_frame& frame, std::vector<std::uint8_t>&& encoded,
data_format::boba_frame_overlay&& overlay,
const data_format::boba_modal_overlay& modal, double encode_time_us) {
auto output = std::make_shared<data_format::compressed_frame>();
output->left_color = std::move(encoded);
output->right_color = {};
output->nalu_only = false;
output->use_depth = false;
output->use_motion_vectors = false;
output->presentation_mode = frame.presentation_mode;
output->content_aspect_ratio = static_cast<float>(frame.left.width) / static_cast<float>(frame.left.height);
output->boba_overlay = std::move(overlay);
output->boba_modal = modal;
output->pose[0] = to_xr_pose(frame.left_render_view);
output->pose[1] = to_xr_pose(frame.right_render_view);
output->fov_left = {frame.left_render_view.angle_left, frame.right_render_view.angle_left};
output->fov_right = {frame.left_render_view.angle_right, frame.right_render_view.angle_right};
output->fov_up = {frame.left_render_view.angle_up, frame.right_render_view.angle_up};
output->fov_down = {frame.left_render_view.angle_down, frame.right_render_view.angle_down};
output->sent_time = system_time_ns();
output->frame_number = frame.source_frame_id;
output->pose_id = 0;
output->encode_time = encode_time_us;
output->is_keyframe = encoder_->last_frame_was_keyframe();
output->magic = 0xdeadbeef;
metrics_bytes_ += output->left_color.size();
frames_writer_.put(std::move(output));
}
void boba_streaming_server::_p_one_iteration() {
const auto frame = stereo_reader_.get_ro_nullable();
if (frame == nullptr || frame->source_frame_id <= last_frame_id_) {
return;
}
// Mark this ID as handled even when invalid so a malformed ring slot cannot
// spin the thread indefinitely; the next Boba frame remains eligible.
if (last_frame_id_ != 0) {
metrics_source_skips_ += frame->source_frame_id - last_frame_id_ - 1;
} else {
metrics_start_ = std::chrono::steady_clock::now();
}
last_frame_id_ = frame->source_frame_id;
if (frame->format != data_format::stereo_pixel_format::rgba8_unorm ||
!map_file(frame->pixel_buffer_path, &frame_mapping_) || !image_range_valid(frame->left) ||
!image_range_valid(frame->right) || frame->left.width != frame->right.width ||
frame->left.height != frame->right.height ||
!generation_matches(frame_mapping_, frame->pixel_generation_offset, frame->source_frame_id)) {
plugin_logger_->warn("Dropping unsupported or stale Boba stereo frame {}", frame->source_frame_id);
++metrics_rejected_;
return;
}
// Copy small overlay commands before encoding so the network packet never
// references memory that Boba can recycle after this iteration.
bool overlay_generation_required = false;
if (!frame->overlay_buffer_path.empty()) {
if (!map_file(frame->overlay_buffer_path, &overlay_mapping_) ||
!generation_matches(overlay_mapping_, frame->overlay_generation_offset, frame->source_frame_id) ||
!overlay_range_valid(frame->left_overlay_commands) || !overlay_range_valid(frame->right_overlay_commands)) {
plugin_logger_->debug("Waiting for matching Boba overlay generation {}", frame->source_frame_id);
++metrics_rejected_;
return;
}
overlay_generation_required = true;
} else {
overlay_mapping_.reset();
}
data_format::boba_frame_overlay overlay{};
overlay.source_width = frame->left.width;
overlay.source_height = frame->left.height;
if (overlay_generation_required) {
overlay.left_commands = copy_overlay_commands(frame->left_overlay_commands);
overlay.right_commands = copy_overlay_commands(frame->right_overlay_commands);
}
// Modal placement belongs to every frame, while its potentially large RGBA
// texture is content-addressed and transmitted separately over TCP.
bool modal_generation_required = false;
data_format::boba_modal_overlay modal{};
std::vector<std::uint8_t> modal_pixels;
if (!frame->modal_buffer_path.empty()) {
if (!map_file(frame->modal_buffer_path, &modal_mapping_) ||
!generation_matches(modal_mapping_, frame->modal_generation_offset, frame->source_frame_id) ||
!modal_range_valid(frame->modal)) {
plugin_logger_->debug("Waiting for matching Boba modal generation {}", frame->source_frame_id);
++metrics_rejected_;
return;
}
modal_generation_required = true;
modal.visible = frame->modal.visible;
modal.left_valid = frame->modal.left_valid;
modal.right_valid = frame->modal.right_valid;
modal.width = frame->modal.width;
modal.height = frame->modal.height;
modal.width_m = frame->modal.width_m;
modal.height_m = frame->modal.height_m;
for (std::size_t index = 0; index < 4; ++index) {
modal.left_quad_pixels[index * 2] = frame->modal.left_quad_pixels[index].x();
modal.left_quad_pixels[index * 2 + 1] = frame->modal.left_quad_pixels[index].y();
modal.right_quad_pixels[index * 2] = frame->modal.right_quad_pixels[index].x();
modal.right_quad_pixels[index * 2 + 1] = frame->modal.right_quad_pixels[index].y();
}
if (modal.visible) {
modal_pixels = copy_modal_pixels(frame->modal);
modal.texture_id = modal_texture_id(modal_pixels, modal.width, modal.height);
}
} else {
modal_mapping_.reset();
if (frame->modal.visible) {
plugin_logger_->warn("Boba frame {} declares a modal without a modal ring", frame->source_frame_id);
++metrics_rejected_;
return;
}
}
// Upload directly from mmap, then validate every copied ring before NVENC
// consumes the CUDA snapshot. Ring recycling after that point is harmless.
initialize_encoder();
const auto* left = frame_mapping_.data + frame->left.byte_offset;
const auto* right = frame_mapping_.data + frame->right.byte_offset;
const std::size_t left_pitch = frame->left.row_stride_bytes;
const std::size_t right_pitch = frame->right.row_stride_bytes;
const auto start = std::chrono::steady_clock::now();
bool input_current = true;
const auto validate_input = [&] {
input_current = generation_matches(frame_mapping_, frame->pixel_generation_offset, frame->source_frame_id) &&
(!overlay_generation_required ||
generation_matches(overlay_mapping_, frame->overlay_generation_offset, frame->source_frame_id)) &&
(!modal_generation_required ||
generation_matches(modal_mapping_, frame->modal_generation_offset, frame->source_frame_id));
return input_current;
};
std::vector<std::uint8_t> encoded =
encoder_->encode_rgba_stereo(left, left_pitch, right, right_pitch, frame->left.width, frame->left.height,
frame->origin == data_format::stereo_image_origin::upper_left, validate_input);
const double encode_us = static_cast<double>(
std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count());
++metrics_encode_attempts_;
metrics_encode_us_ += encode_us;
if (!input_current) {
++metrics_rejected_;
plugin_logger_->debug("Boba ring recycled frame {} during upload; skipping before encode", frame->source_frame_id);
return;
}
if (encoded.empty()) {
plugin_logger_->warn("NVENC returned an empty frame for Boba frame {}", frame->source_frame_id);
return;
}
// Pace from this submission, with no catch-up burst after a slow send.
next_frame_time_ = start +
std::chrono::duration_cast<std::chrono::steady_clock::duration>(std::chrono::duration<double>(1.0 / framerate_));
const auto send_start = std::chrono::steady_clock::now();
publish_modal_texture_if_needed(modal, modal_pixels);
publish_encoded(*frame, std::move(encoded), std::move(overlay), modal, encode_us);
metrics_send_us_ += std::chrono::duration<double, std::micro>(std::chrono::steady_clock::now() - send_start).count();
++metrics_frames_;
}
void boba_streaming_server::report_metrics() {
const auto now = std::chrono::steady_clock::now();
const double interval = std::chrono::duration<double>(now - metrics_start_).count();
if (interval >= 1.0 && last_frame_id_ != 0) {
plugin_logger_->info(
"Boba native stream: {:.1f} fps, {:.2f} ms encode, {:.2f} ms send, {:.1f} Mbps, "
"{} rejected inputs, {} skipped source frames",
static_cast<double>(metrics_frames_) / interval,
metrics_encode_us_ / static_cast<double>(std::max<std::uint64_t>(1, metrics_encode_attempts_)) / 1000.0,
metrics_send_us_ / static_cast<double>(std::max<std::uint64_t>(1, metrics_frames_)) / 1000.0,
static_cast<double>(metrics_bytes_) * 8.0 / interval / 1'000'000.0, metrics_rejected_, metrics_source_skips_);
metrics_start_ = now;
metrics_frames_ = 0;
metrics_bytes_ = 0;
metrics_encode_us_ = 0.0;
metrics_send_us_ = 0.0;
metrics_encode_attempts_ = 0;
metrics_rejected_ = 0;
metrics_source_skips_ = 0;
}
}
} // namespace ILLIXR
using namespace ILLIXR;
PLUGIN_MAIN(boba_streaming_server)