use darkly::gpu::paint_target::{GpuPaintTarget, PaintPipelines};
use darkly::gpu::readback;
use darkly::gpu::test_utils::*;
fn encoder(device: &wgpu::Device) -> wgpu::CommandEncoder {
device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("test"),
})
}
fn submit(queue: &wgpu::Queue, encoder: wgpu::CommandEncoder) {
queue.submit([encoder.finish()]);
}
#[test]
fn paint_target_composite_circle() {
let (device, queue) = test_device();
let (w, h) = (128, 128);
let fmt = wgpu::TextureFormat::Rgba8Unorm;
let (tex, view) = create_test_texture(&device, &queue, w, h, &vec![0u8; (w * h * 4) as usize]);
let pipelines = PaintPipelines::new(
&device,
&queue,
&darkly::gpu::selection::selection_mask_bgl(&device),
);
let target = GpuPaintTarget::from_canvas_texture(
&tex,
&view,
fmt,
darkly::coord::CanvasRect::from_xywh(0, 0, w, h),
);
let mut enc = encoder(&device);
target.composite_circle(
&mut enc,
&pipelines,
&queue,
64.0,
64.0,
10.0,
[255, 0, 0, 255],
1.0,
);
submit(&queue, enc);
let pixels = readback_texture(&device, &queue, &tex, fmt, w, h);
let c = ((64 * w + 64) * 4) as usize;
assert_eq!(pixels[c], 255, "center R={}, expected 255", pixels[c]);
assert_eq!(pixels[c + 1], 0, "center G={}, expected 0", pixels[c + 1]);
assert_eq!(pixels[c + 2], 0, "center B={}, expected 0", pixels[c + 2]);
assert_eq!(
pixels[c + 3],
255,
"center A={}, expected 255",
pixels[c + 3]
);
assert_eq!(pixels[3], 0, "corner alpha={}, expected 0", pixels[3]);
let inside = ((57 * w + 64) * 4) as usize;
assert!(
pixels[inside + 3] > 0,
"inside circle should be non-transparent"
);
let outside = ((50 * w + 64) * 4) as usize;
assert_eq!(
pixels[outside + 3],
0,
"outside circle should be transparent, got A={}",
pixels[outside + 3]
);
}
#[test]
fn paint_target_alpha_blending() {
let (device, queue) = test_device();
let (w, h) = (128, 128);
let fmt = wgpu::TextureFormat::Rgba8Unorm;
let bg: Vec<u8> = (0..w * h).flat_map(|_| [0u8, 0, 255, 128]).collect();
let (tex, view) = create_test_texture(&device, &queue, w, h, &bg);
let pipelines = PaintPipelines::new(
&device,
&queue,
&darkly::gpu::selection::selection_mask_bgl(&device),
);
let target = GpuPaintTarget::from_canvas_texture(
&tex,
&view,
fmt,
darkly::coord::CanvasRect::from_xywh(0, 0, w, h),
);
let mut enc = encoder(&device);
target.composite_circle(
&mut enc,
&pipelines,
&queue,
64.0,
64.0,
10.0,
[255, 0, 0, 128],
1.0,
);
submit(&queue, enc);
let pixels = readback_texture(&device, &queue, &tex, fmt, w, h);
let c = ((64 * w + 64) * 4) as usize;
let (r, g, b, a) = (pixels[c], pixels[c + 1], pixels[c + 2], pixels[c + 3]);
assert!(r > 0, "blended pixel should have red, got R={r}");
assert!(b > 0, "blended pixel should have blue, got B={b}");
assert!(a > 128, "blended alpha should be > 128, got A={a}");
assert_eq!(g, 0, "green should remain 0, got G={g}");
}
#[test]
fn paint_target_r8_mask() {
let (device, queue) = test_device();
let (w, h) = (128, 128);
let fmt = wgpu::TextureFormat::R8Unorm;
let white: Vec<u8> = vec![255u8; (w * h) as usize];
let (tex, view) = create_test_texture_with_format(&device, &queue, w, h, &white, fmt);
let pipelines = PaintPipelines::new(
&device,
&queue,
&darkly::gpu::selection::selection_mask_bgl(&device),
);
let target = GpuPaintTarget::from_canvas_texture(
&tex,
&view,
fmt,
darkly::coord::CanvasRect::from_xywh(0, 0, w, h),
);
let mut enc = encoder(&device);
target.composite_circle(
&mut enc,
&pipelines,
&queue,
64.0,
64.0,
10.0,
[0, 0, 0, 255],
1.0,
);
submit(&queue, enc);
let pixels = readback_texture(&device, &queue, &tex, fmt, w, h);
let center = (64 * w + 64) as usize;
assert!(
pixels[center] < 10,
"center mask should be near 0, got {}",
pixels[center]
);
assert_eq!(
pixels[0], 255,
"corner mask should be 255, got {}",
pixels[0]
);
}
#[test]
fn paint_target_selection_masking() {
let (device, queue) = test_device();
let (w, h) = (128, 128);
let fmt = wgpu::TextureFormat::Rgba8Unorm;
let (tex, view) = create_test_texture(&device, &queue, w, h, &vec![0u8; (w * h * 4) as usize]);
let pipelines = PaintPipelines::new(
&device,
&queue,
&darkly::gpu::selection::selection_mask_bgl(&device),
);
let mut sel_data = vec![0u8; (w * h) as usize];
for y in 0..h {
for x in 0..w / 2 {
sel_data[(y * w + x) as usize] = 255;
}
}
let (_sel_tex, sel_view) = create_test_texture_with_format(
&device,
&queue,
w,
h,
&sel_data,
wgpu::TextureFormat::R8Unorm,
);
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
label: Some("test-sampler"),
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Linear,
..Default::default()
});
let sel_bind_group = pipelines.create_selection_bind_group(&device, &sel_view, &sampler);
let target = GpuPaintTarget::from_canvas_texture(
&tex,
&view,
fmt,
darkly::coord::CanvasRect::from_xywh(0, 0, w, h),
);
let mut enc = encoder(&device);
target.composite_circle_with_selection(
&mut enc,
&pipelines,
&queue,
64.0,
64.0,
30.0,
[255, 0, 0, 255],
1.0,
&sel_bind_group,
);
submit(&queue, enc);
let pixels = readback_texture(&device, &queue, &tex, fmt, w, h);
let left = ((64 * w + 48) * 4) as usize;
assert!(
pixels[left + 3] > 0,
"left (selected) should have paint, A={}",
pixels[left + 3]
);
let right = ((64 * w + 80) * 4) as usize;
assert_eq!(
pixels[right + 3],
0,
"right (unselected) should be transparent, A={}",
pixels[right + 3]
);
}
#[test]
fn readback_round_trip() {
let (device, queue) = test_device();
let (w, h) = (128, 128);
let fmt = wgpu::TextureFormat::Rgba8Unorm;
let mut data = vec![0u8; (w * h * 4) as usize];
for y in 0..h {
for x in 0..w {
let i = ((y * w + x) * 4) as usize;
data[i] = y as u8;
data[i + 1] = x as u8;
data[i + 2] = 42;
data[i + 3] = 255;
}
}
let (tex, _view) = create_test_texture(&device, &queue, w, h, &data);
let readback = readback_texture(&device, &queue, &tex, fmt, w, h);
assert_eq!(readback, data, "readback should exactly match input");
}
#[test]
fn readback_sub_rect() {
let (device, queue) = test_device();
let (w, h) = (128, 128);
let fmt = wgpu::TextureFormat::Rgba8Unorm;
let mut data = vec![0u8; (w * h * 4) as usize];
for y in 0..h {
for x in 0..w {
let i = ((y * w + x) * 4) as usize;
let (r, g, b) = match (x < 64, y < 64) {
(true, true) => (255, 0, 0),
(false, true) => (0, 255, 0),
(true, false) => (0, 0, 255),
(false, false) => (255, 255, 0),
};
data[i] = r;
data[i + 1] = g;
data[i + 2] = b;
data[i + 3] = 255;
}
}
let (tex, _view) = create_test_texture(&device, &queue, w, h, &data);
let mut enc = encoder(&device);
let request = readback::request_readback(
&device,
&mut enc,
&tex,
fmt,
darkly::coord::LayerRect::from_xywh(0, 0, 64, 64),
);
submit(&queue, enc);
let pixels = request.blocking_read(&device);
assert_eq!(pixels.len(), (64 * 64 * 4) as usize);
for y in 0..64u32 {
for x in 0..64u32 {
let i = ((y * 64 + x) * 4) as usize;
assert_eq!(
&pixels[i..i + 4],
&[255, 0, 0, 255],
"pixel ({x},{y}) should be red, got {:?}",
&pixels[i..i + 4]
);
}
}
}
#[test]
fn paint_target_composite_circle_on_offset_layer() {
let (device, queue) = test_device();
let canvas_w: u32 = 256;
let canvas_h: u32 = 256;
let layer_off_x: i32 = -100;
let layer_off_y: i32 = -100;
let (lw, lh) = (400u32, 400u32);
let fmt = wgpu::TextureFormat::Rgba8Unorm;
let (tex, view) =
create_test_texture(&device, &queue, lw, lh, &vec![0u8; (lw * lh * 4) as usize]);
let pipelines = PaintPipelines::new(
&device,
&queue,
&darkly::gpu::selection::selection_mask_bgl(&device),
);
let target = GpuPaintTarget::from_extent(
&tex,
&view,
fmt,
darkly::coord::CanvasRect::from_xywh(layer_off_x, layer_off_y, lw, lh),
darkly::coord::CanvasRect::from_xywh(0, 0, canvas_w, canvas_h),
);
let mut enc = encoder(&device);
target.composite_circle(
&mut enc,
&pipelines,
&queue,
50.0,
50.0,
10.0,
[0, 255, 0, 255],
1.0,
);
submit(&queue, enc);
let pixels = readback_texture(&device, &queue, &tex, fmt, lw, lh);
let cx_local = (50 - layer_off_x) as u32;
let cy_local = (50 - layer_off_y) as u32;
let c = ((cy_local * lw + cx_local) * 4) as usize;
assert_eq!(pixels[c], 0);
assert_eq!(
pixels[c + 1],
255,
"expected green at layer-local (150,150)"
);
assert_eq!(pixels[c + 2], 0);
assert_eq!(pixels[c + 3], 255);
let bug = ((50u32 * lw + 50) * 4) as usize;
assert_eq!(
pixels[bug + 3],
0,
"layer-local (50,50) should be untouched (would be wrong-place paint)"
);
}
#[test]
fn paint_target_fill_rect_canvas_space_on_offset_layer() {
let (device, queue) = test_device();
let canvas_w: u32 = 256;
let canvas_h: u32 = 256;
let (lw, lh) = (300u32, 300u32);
let fmt = wgpu::TextureFormat::Rgba8Unorm;
let off_x: i32 = -50;
let off_y: i32 = -50;
let (tex, view) =
create_test_texture(&device, &queue, lw, lh, &vec![0u8; (lw * lh * 4) as usize]);
let pipelines = PaintPipelines::new(
&device,
&queue,
&darkly::gpu::selection::selection_mask_bgl(&device),
);
let target = GpuPaintTarget::from_extent(
&tex,
&view,
fmt,
darkly::coord::CanvasRect::from_xywh(off_x, off_y, lw, lh),
darkly::coord::CanvasRect::from_xywh(0, 0, canvas_w, canvas_h),
);
let mut enc = encoder(&device);
target.fill_rect(
&mut enc,
&pipelines,
&queue,
darkly::coord::CanvasRect::from_xywh(10, 10, 20, 20),
[0, 0, 255, 255],
);
submit(&queue, enc);
let pixels = readback_texture(&device, &queue, &tex, fmt, lw, lh);
let lx = (15 - off_x) as u32;
let ly = (15 - off_y) as u32;
let c = ((ly * lw + lx) * 4) as usize;
assert_eq!(
pixels[c + 2],
255,
"rect interior should be blue at layer-local ({lx},{ly})"
);
assert_eq!(pixels[c + 3], 255);
let outside = ((50u32 * lw + 50) * 4) as usize;
assert_eq!(
pixels[outside + 3],
0,
"layer-local (50,50) should be untouched (canvas-space input lands at layer-local (60,60))"
);
}
#[test]
fn paint_target_fill_rect_canvas_negative_origin_on_offset_layer() {
let (device, queue) = test_device();
let canvas_w: u32 = 128;
let canvas_h: u32 = 128;
let (lw, lh) = (200u32, 200u32);
let fmt = wgpu::TextureFormat::Rgba8Unorm;
let off_x: i32 = -50;
let off_y: i32 = -50;
let (tex, view) =
create_test_texture(&device, &queue, lw, lh, &vec![0u8; (lw * lh * 4) as usize]);
let pipelines = PaintPipelines::new(
&device,
&queue,
&darkly::gpu::selection::selection_mask_bgl(&device),
);
let target = GpuPaintTarget::from_extent(
&tex,
&view,
fmt,
darkly::coord::CanvasRect::from_xywh(off_x, off_y, lw, lh),
darkly::coord::CanvasRect::from_xywh(0, 0, canvas_w, canvas_h),
);
let mut enc = encoder(&device);
target.fill_rect(
&mut enc,
&pipelines,
&queue,
darkly::coord::CanvasRect::from_xywh(-30, 10, 10, 10),
[0, 255, 0, 255],
);
submit(&queue, enc);
let pixels = readback_texture(&device, &queue, &tex, fmt, lw, lh);
let lx = (-25 - off_x) as u32;
let ly = (15 - off_y) as u32;
let c = ((ly * lw + lx) * 4) as usize;
assert_eq!(
pixels[c + 1],
255,
"rect interior should be green at layer-local ({lx},{ly})"
);
}