use gled_gpu::{
constants::{OUTPUT_BUFFER_SIZE, UNIVERSE_BUFFER_SIZE, UNIVERSE_SLOTS},
gpu::OutputContext,
};
use kanal::{Receiver, Sender, bounded};
use std::sync::Arc;
use tracing::warn;
use wgpu::{
Buffer, BufferDescriptor, BufferUsages, CommandEncoder, Device, MapMode, PollType,
SubmissionIndex,
};
const STAGING_POOL_SIZE: usize = 6;
const PREVIEW_CHANNEL_CAPACITY: usize = 2;
pub(crate) type PreviewOutput = Arc<Vec<u8>>;
pub(crate) use gled_protocol::state::OutputConfig;
#[derive(Debug)]
pub(crate) struct OutputFrame {
pub(crate) data: Vec<u8>,
pub(crate) config: OutputConfig,
}
#[derive(Debug)]
pub(crate) struct OutputFrames {
pub(crate) frames: Receiver<OutputFrame>,
pub(crate) wake: Receiver<()>,
pub(crate) bell: Sender<()>,
}
pub(crate) struct ExtractOutput {
output_sender: Sender<OutputFrame>,
bell: Sender<()>,
preview_sender: Sender<PreviewOutput>,
free_buffers_sender: Sender<Buffer>,
free_buffers_receiver: Receiver<Buffer>,
submit_sender: Sender<SubmissionIndex>,
submit_receiver: Option<Receiver<SubmissionIndex>>,
initialized: bool,
config: OutputConfig,
}
impl std::fmt::Debug for ExtractOutput {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ExtractOutput").finish_non_exhaustive()
}
}
impl ExtractOutput {
pub(crate) fn new() -> (Self, OutputFrames, Receiver<PreviewOutput>) {
let (output_sender, output_receiver) = bounded(STAGING_POOL_SIZE);
let (bell, wake) = bounded(1);
let (preview_sender, preview_receiver) = bounded(PREVIEW_CHANNEL_CAPACITY);
let (free_buffers_sender, free_buffers_receiver) = bounded(STAGING_POOL_SIZE);
let (submit_sender, submit_receiver) = bounded(STAGING_POOL_SIZE * 2);
(
Self {
output_sender,
bell: bell.clone(),
preview_sender,
free_buffers_sender,
free_buffers_receiver,
submit_sender,
submit_receiver: Some(submit_receiver),
initialized: false,
config: OutputConfig::default(),
},
OutputFrames {
frames: output_receiver,
wake,
bell,
},
preview_receiver,
)
}
fn ensure_initialized(&mut self, device: &Device) {
if self.initialized {
return;
}
for index in 0..STAGING_POOL_SIZE {
let buffer = device.create_buffer(&BufferDescriptor {
size: OUTPUT_BUFFER_SIZE,
usage: BufferUsages::MAP_READ | BufferUsages::COPY_DST,
label: Some(&format!("ExtractOutput staging buffer {index}")),
mapped_at_creation: false,
});
self.free_buffers_sender
.try_send(buffer)
.expect("staging pool channel has capacity for the pool");
}
if let Some(submit_receiver) = self.submit_receiver.take() {
let poll_device = device.clone();
std::thread::Builder::new()
.name("gled:output:poll".to_owned())
.spawn(move || {
#[cfg(feature = "profiling")]
profiling::register_thread!("output:poll");
while let Ok(index) = submit_receiver.recv() {
let _ = poll_device.poll(PollType::Wait {
submission_index: Some(index),
timeout: None,
});
}
})
.expect("Could not spawn output poll thread");
}
self.initialized = true;
}
pub(crate) fn notify_submitted(&self, index: SubmissionIndex) {
let _ = self.submit_sender.try_send(index);
}
pub(crate) fn set_config(&mut self, config: OutputConfig) {
let universes = &config.universes;
if !Arc::ptr_eq(universes, &self.config.universes)
&& universes != &self.config.universes
&& let Some(first_dark) = universes.iter().nth(UNIVERSE_SLOTS as usize)
{
warn!(
"The patch uses {} universes, gled renders {UNIVERSE_SLOTS} at once: \
universe {first_dark} and the ones above it get no output",
universes.len()
);
}
self.config = config;
}
pub(crate) fn slots(&self) -> usize {
self.config.universes.len().min(UNIVERSE_SLOTS as usize)
}
#[cfg_attr(feature = "profiling", profiling::function)]
pub(crate) fn run(&mut self, context: &OutputContext, encoder: &mut CommandEncoder) {
let active_len = self.slots() as u64 * UNIVERSE_BUFFER_SIZE;
if active_len == 0 {
return;
}
self.ensure_initialized(&context.gpu.device);
let Ok(Some(output_cpu)) = self.free_buffers_receiver.try_recv() else {
return;
};
encoder.copy_buffer_to_buffer(&context.output_buffer, 0, &output_cpu, 0, active_len);
let output_sender = self.output_sender.clone();
let bell = self.bell.clone();
let preview_sender = self.preview_sender.clone();
let free_buffers_sender = self.free_buffers_sender.clone();
let config = self.config.clone();
let capturable = output_cpu.clone();
encoder.map_buffer_on_submit(&output_cpu, MapMode::Read, ..active_len, move |_v| {
let output_data = capturable
.get_mapped_range(..active_len)
.expect("Could not get mapped range of output buffer")
.to_vec();
capturable.unmap();
if !preview_sender.is_full() {
let _ = preview_sender.try_send(Arc::new(output_data.clone()));
}
let _ = output_sender.try_send(OutputFrame {
data: output_data,
config,
});
let _ = bell.try_send(());
let _ = free_buffers_sender.try_send(capturable);
});
}
}