use damascene_core::plot::{PlotSpec, Sample, Scale, SeriesHandle, line};
use damascene_core::prelude::*;
use damascene_wgpu::Runner;
const SIZE: u32 = 200;
const FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8UnormSrgb;
fn headless_device() -> Option<(wgpu::Device, wgpu::Queue)> {
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle());
let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::default(),
compatible_surface: None,
force_fallback_adapter: false,
apply_limit_buckets: false,
}))
.ok()?;
let (device, queue) = pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor {
label: Some("plot_render_test"),
required_features: wgpu::Features::empty(),
required_limits: wgpu::Limits::default(),
experimental_features: wgpu::ExperimentalFeatures::default(),
memory_hints: wgpu::MemoryHints::Performance,
trace: wgpu::Trace::Off,
}))
.ok()?;
Some((device, queue))
}
fn render_to_pixels(
device: &wgpu::Device,
queue: &wgpu::Queue,
runner: &mut Runner,
tree: El,
) -> Vec<u8> {
runner.prepare(
device,
queue,
tree,
Rect::new(0.0, 0.0, SIZE as f32, SIZE as f32),
1.0,
);
let target = device.create_texture(&wgpu::TextureDescriptor {
label: Some("plot_test_target"),
size: wgpu::Extent3d {
width: SIZE,
height: SIZE,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: FORMAT,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
view_formats: &[],
});
let target_view = target.create_view(&wgpu::TextureViewDescriptor::default());
let unpadded = SIZE * 4;
let bytes_per_row =
unpadded.div_ceil(wgpu::COPY_BYTES_PER_ROW_ALIGNMENT) * wgpu::COPY_BYTES_PER_ROW_ALIGNMENT;
let readback = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("plot_test_readback"),
size: (bytes_per_row * SIZE) as u64,
usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
mapped_at_creation: false,
});
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("plot_test"),
});
runner.render(
device,
&mut encoder,
&target,
&target_view,
None,
wgpu::LoadOp::Clear(wgpu::Color::BLACK),
);
encoder.copy_texture_to_buffer(
wgpu::TexelCopyTextureInfo {
texture: &target,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
},
wgpu::TexelCopyBufferInfo {
buffer: &readback,
layout: wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(bytes_per_row),
rows_per_image: Some(SIZE),
},
},
wgpu::Extent3d {
width: SIZE,
height: SIZE,
depth_or_array_layers: 1,
},
);
queue.submit([encoder.finish()]);
let slice = readback.slice(..);
slice.map_async(wgpu::MapMode::Read, |r| r.expect("map readback"));
device
.poll(wgpu::PollType::wait_indefinitely())
.expect("poll");
let data = slice.get_mapped_range().unwrap();
let mut out = Vec::with_capacity((SIZE * SIZE * 4) as usize);
for row in 0..SIZE as usize {
let start = row * bytes_per_row as usize;
out.extend_from_slice(&data[start..start + unpadded as usize]);
}
drop(data);
readback.unmap();
out
}
#[test]
fn plot_composites_line_into_data_rect() {
let Some((device, queue)) = headless_device() else {
eprintln!("no GPU adapter; skipping plot_render test");
return;
};
let mut runner = Runner::new(&device, &queue, FORMAT);
runner.set_surface_size(SIZE, SIZE);
let series = SeriesHandle::new(vec![
Sample::new(0.0, 0.0),
Sample::new(1.0, 1.0),
Sample::new(2.0, 2.0),
Sample::new(3.0, 3.0),
]);
let spec = PlotSpec::new()
.x(Scale::linear())
.y(Scale::linear())
.add_mark(line(&series).width(3.0));
let tree = plot(spec).key("p");
let px = render_to_pixels(&device, &queue, &mut runner, tree);
let mut blue = 0usize;
for p in px.chunks_exact(4) {
let (r, g, b) = (p[0] as i32, p[1] as i32, p[2] as i32);
if b > 120 && b > r + 30 && b > g + 20 {
blue += 1;
}
}
assert!(
blue > 100,
"plot should composite a visible blue line into its data rect, got {blue} blue px"
);
}