use crate::gradient::RgbColor;
use crate::renderer::Size;
use crate::{Renderer, renderer};
use std::sync;
#[derive(Debug, thiserror::Error)]
pub enum HeadlessCreationError {
#[error("Failed to create adapter")]
AdapterCreation(#[from] wgpu::RequestAdapterError),
#[error("Unable to request device")]
DeviceCreation(#[from] wgpu::RequestDeviceError),
#[error("Texture width must be a multiple of {alignment}, got {actual}")]
InvalidRowLength { alignment: u32, actual: u32 },
}
#[derive(Debug)]
struct Context {
device: wgpu::Device,
queue: wgpu::Queue,
}
#[derive(Clone, Debug)]
pub struct Bitmap {
pub size: Size,
pub buffer: Vec<u8>,
}
impl Context {
async fn new() -> Result<Self, HeadlessCreationError> {
let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
backends: wgpu::Backends::all(),
..Default::default()
});
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::default(),
compatible_surface: None,
force_fallback_adapter: false,
})
.await?;
let (device, queue) = adapter
.request_device(&wgpu::DeviceDescriptor {
required_features: wgpu::Features::empty(),
required_limits: wgpu::Limits::default(),
label: None,
memory_hints: Default::default(),
trace: wgpu::Trace::default(),
})
.await
.map_err(HeadlessCreationError::from)?;
Ok(Context { device, queue })
}
}
#[derive(Debug, thiserror::Error)]
pub enum RenderError {
#[error("Failed to render intermediate texture")]
Intermediate(#[source] renderer::Error),
#[error("Error during post-processing step")]
Postprocessing(#[source] renderer::Error),
#[error("Error during readback")]
Readback(#[source] wgpu::BufferAsyncError),
}
#[derive(Debug)]
pub struct PhosphorHeadless {
device: wgpu::Device,
queue: wgpu::Queue,
renderer: Renderer,
output_texture: wgpu::Texture,
output_buffer: wgpu::Buffer,
}
impl PhosphorHeadless {
pub async fn new(size: Size<u32>) -> Result<Self, HeadlessCreationError> {
let Context { device, queue } = Context::new().await?;
let bytes_per_pixel = 4;
let bytes_per_row = bytes_per_pixel * size.width;
if bytes_per_row % wgpu::COPY_BYTES_PER_ROW_ALIGNMENT != 0 {
return Err(HeadlessCreationError::InvalidRowLength {
alignment: wgpu::COPY_BYTES_PER_ROW_ALIGNMENT / bytes_per_pixel,
actual: size.width,
});
}
let renderer = Renderer::new(&device, &wgpu::TextureFormat::Rgba8UnormSrgb, size);
let (output_texture, output_buffer) = Self::create_output_buffer_and_texture(&device, size);
Ok(PhosphorHeadless {
device,
queue,
renderer,
output_buffer,
output_texture,
})
}
fn create_output_buffer_and_texture(
device: &wgpu::Device,
size: Size,
) -> (wgpu::Texture, wgpu::Buffer) {
let output_texture = device.create_texture(&wgpu::TextureDescriptor {
size: wgpu::Extent3d {
width: size.width,
height: size.height,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8UnormSrgb,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
label: Some("output_texture"),
view_formats: &[],
});
let output_buffer = device.create_buffer(&wgpu::BufferDescriptor {
size: (size.width * size.height * 4) as u64, usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
label: Some("output_buffer"),
mapped_at_creation: false,
});
(output_texture, output_buffer)
}
pub fn resize(&mut self, size: Size) -> renderer::Result<()> {
(self.output_texture, self.output_buffer) =
Self::create_output_buffer_and_texture(&self.device, size);
self.renderer.resize(size)
}
pub fn set_xlim(&mut self, left: f32, right: f32) {
self.renderer.set_xlim(left, right)
}
pub fn set_ylim(&mut self, lower: f32, upper: f32) {
self.renderer.set_ylim(lower, upper)
}
pub fn set_intensity(&mut self, value: f32) {
self.renderer.set_intensity(value)
}
pub fn set_gamma(&mut self, value: f32) {
self.renderer.set_gamma(value)
}
pub fn set_beam_width(&mut self, width: f32) {
self.renderer.set_beam_width(width)
}
pub fn set_waveform_yt(&mut self, waveform: &[f32]) {
self.renderer.set_waveform_yt(waveform)
}
pub fn set_waveform_xy(&mut self, waveform: &[[f32; 2]]) {
self.renderer.set_waveform_xy(waveform)
}
pub fn set_lut(&mut self, lut: impl IntoIterator<Item = (f32, RgbColor<f32>)>) {
self.renderer.set_lut(lut)
}
pub fn set_decay(&mut self, decay: renderer::Decay) {
self.renderer.set_decay(decay)
}
}
#[cfg(feature = "auto-intensity")]
impl PhosphorHeadless {
pub fn auto_intensity(&mut self, value: bool) {
self.renderer.enable_auto_intensity(value)
}
pub fn reset_auto_intensity(&self, queue: &wgpu::Queue) -> renderer::Result<()> {
self.renderer.reset_auto_intensity(queue)
}
}
impl PhosphorHeadless {
pub fn render_to_buffer(&mut self) -> Result<Bitmap, RenderError> {
self.submit_render()?;
let buffer_owned: Vec<u8> = self.readback_buffer()?.to_vec();
Ok(Bitmap {
buffer: buffer_owned,
size: self.renderer.size(),
})
}
fn submit_render(&mut self) -> Result<(), RenderError> {
let intermediate_cmd = self
.renderer
.render_intermediate(&self.queue)
.map_err(RenderError::Intermediate)?;
let mut encoder = self
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("render_encoder"),
});
{
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("render_pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &self
.output_texture
.create_view(&wgpu::TextureViewDescriptor::default()),
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
});
self.renderer
.render(&mut render_pass)
.map_err(RenderError::Postprocessing)?
}
encoder.copy_texture_to_buffer(
wgpu::TexelCopyTextureInfo {
texture: &self.output_texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
},
wgpu::TexelCopyBufferInfo {
buffer: &self.output_buffer,
layout: wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(self.renderer.size().width * 4),
rows_per_image: Some(self.renderer.size().height),
},
},
wgpu::Extent3d {
width: self.renderer.size().width,
height: self.renderer.size().height,
depth_or_array_layers: 1,
},
);
self.queue.submit([intermediate_cmd, encoder.finish()]);
Ok(())
}
fn readback_buffer(&self) -> Result<Vec<u8>, RenderError> {
let data: Vec<_>;
{
let buffer_slice: wgpu::BufferSlice = self.output_buffer.slice(..);
let (sender, receiver) = sync::mpsc::channel();
buffer_slice.map_async(wgpu::MapMode::Read, move |result| {
sender.send(result).unwrap()
});
self.device.poll(wgpu::PollType::Wait).unwrap();
receiver
.try_recv()
.unwrap()
.map_err(RenderError::Readback)?;
data = buffer_slice.get_mapped_range().to_vec();
}
self.output_buffer.unmap();
Ok(data)
}
}