use hephaestus::backend::vello::VelloRenderer;
use hephaestus::color::{rgb8, Color};
use hephaestus::geometry::Rect;
use hephaestus::{Affine, Brush, FillRule, PickId, Renderer, SceneBuilder, WgpuRenderer};
use kurbo::Shape;
const W: u32 = 64;
const H: u32 = 64;
#[test]
fn blitting_the_render_target_into_a_bgra_surface_preserves_the_image() {
let bg: Color = rgb8(20, 22, 28);
let fill: Color = rgb8(200, 90, 40);
let (device, queue) = make_device();
let mut renderer = VelloRenderer::with_device(&device, &queue).expect("with_device init");
draw(&mut renderer, fill);
let target = device.create_texture(&wgpu::TextureDescriptor {
label: Some("window_blit.target"),
size: extent(),
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8Unorm,
usage: VelloRenderer::REQUIRED_TARGET_USAGE | wgpu::TextureUsages::TEXTURE_BINDING,
view_formats: &[],
});
let target_view = target.create_view(&wgpu::TextureViewDescriptor::default());
renderer
.render_to_texture(&target_view, W, H, bg)
.expect("texture render");
let swapchain = device.create_texture(&wgpu::TextureDescriptor {
label: Some("window_blit.swapchain"),
size: extent(),
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Bgra8Unorm,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
view_formats: &[],
});
let swapchain_view = swapchain.create_view(&wgpu::TextureViewDescriptor::default());
let blitter = wgpu::util::TextureBlitter::new(&device, wgpu::TextureFormat::Bgra8Unorm);
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("window_blit.blit"),
});
blitter.copy(&device, &mut encoder, &target_view, &swapchain_view);
queue.submit([encoder.finish()]);
let presented = read_back(&device, &queue, &swapchain);
let mut reference_renderer = VelloRenderer::new().expect("vello renderer init");
draw(&mut reference_renderer, fill);
let mut reference = vec![0u8; (W * H * 4) as usize];
reference_renderer
.render_to_buffer(W, H, bg, &mut reference)
.expect("buffer render");
for (i, (rgba, bgra)) in reference
.chunks_exact(4)
.zip(presented.chunks_exact(4))
.enumerate()
{
assert_eq!(
[rgba[2], rgba[1], rgba[0], rgba[3]],
[bgra[0], bgra[1], bgra[2], bgra[3]],
"pixel {i} diverged between the presented frame and the headless render"
);
}
}
fn draw(renderer: &mut VelloRenderer, fill: Color) {
let scene = renderer.scene();
scene.clear();
let brush: Brush = fill.into();
let rect = Rect::new(8.0, 8.0, 48.0, 40.0).to_path(0.1);
scene.fill(
FillRule::NonZero,
Affine::IDENTITY,
&brush,
None,
&rect,
PickId::Skip,
);
}
fn extent() -> wgpu::Extent3d {
wgpu::Extent3d {
width: W,
height: H,
depth_or_array_layers: 1,
}
}
fn read_back(device: &wgpu::Device, queue: &wgpu::Queue, texture: &wgpu::Texture) -> Vec<u8> {
let row_bytes = W * 4;
let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT;
let padded = row_bytes.div_ceil(align) * align;
let buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("window_blit.readback"),
size: (padded as u64) * (H 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("window_blit.copy"),
});
encoder.copy_texture_to_buffer(
wgpu::TexelCopyTextureInfo {
texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
},
wgpu::TexelCopyBufferInfo {
buffer: &buffer,
layout: wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(padded),
rows_per_image: Some(H),
},
},
extent(),
);
queue.submit(std::iter::once(encoder.finish()));
let slice = buffer.slice(..);
let (tx, rx) = futures_intrusive::channel::shared::oneshot_channel();
slice.map_async(wgpu::MapMode::Read, move |res| {
let _ = tx.send(res);
});
let _ = device.poll(wgpu::PollType::wait_indefinitely());
pollster::block_on(rx.receive())
.expect("map_async sender dropped")
.expect("map_async");
let mut out = vec![0u8; (W * H * 4) as usize];
{
let data = slice.get_mapped_range();
for y in 0..H as usize {
let start = y * padded as usize;
out[y * row_bytes as usize..(y + 1) * row_bytes as usize]
.copy_from_slice(&data[start..start + row_bytes as usize]);
}
}
buffer.unmap();
out
}
fn make_device() -> (wgpu::Device, wgpu::Queue) {
pollster::block_on(async {
let mut desc = wgpu::InstanceDescriptor::new_without_display_handle();
desc.backends = wgpu::Backends::PRIMARY;
let instance = wgpu::Instance::new(desc);
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::HighPerformance,
compatible_surface: None,
force_fallback_adapter: false,
})
.await
.expect("adapter");
adapter
.request_device(&wgpu::DeviceDescriptor {
label: Some("window_blit.device"),
required_features: wgpu::Features::empty(),
required_limits: wgpu::Limits::default(),
memory_hints: wgpu::MemoryHints::default(),
trace: wgpu::Trace::Off,
experimental_features: wgpu::ExperimentalFeatures::default(),
})
.await
.expect("device")
})
}