use darkly::coord::CanvasRect;
use darkly::gpu::atlas::CanvasFrame;
use darkly::gpu::paint_target::{GpuPaintTarget, PaintPipelines};
use darkly::gpu::region_store::RegionScratch;
use darkly::gpu::test_utils::*;
use darkly::gpu::transform::{
affine_inverse, affine_multiply, affine_translate, Affine2D, TransformPass, IDENTITY,
};
use darkly::layer::LayerId;
fn frame<'a>(tex: &'a wgpu::Texture, w: u32, h: u32) -> CanvasFrame<'a> {
CanvasFrame {
texture: tex,
canvas_extent: CanvasRect::from_xywh(0, 0, w, h),
}
}
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()]);
}
fn pixel_at(pixels: &[u8], w: u32, x: u32, y: u32, bpp: u32) -> &[u8] {
let offset = ((y * w + x) * bpp) as usize;
&pixels[offset..offset + bpp as usize]
}
fn setup_transform_pass(
device: &wgpu::Device,
_queue: &wgpu::Queue,
_canvas_w: u32,
_canvas_h: u32,
) -> (TransformPass, wgpu::Sampler) {
let pass = TransformPass::new(device);
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Linear,
..Default::default()
});
(pass, sampler)
}
fn make_preview_placeholder(
device: &wgpu::Device,
format: wgpu::TextureFormat,
width: u32,
height: u32,
) -> (wgpu::Texture, wgpu::TextureView) {
let tex = device.create_texture(&wgpu::TextureDescriptor {
label: Some("test-preview-placeholder"),
size: wgpu::Extent3d {
width,
height,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format,
usage: wgpu::TextureUsages::TEXTURE_BINDING
| wgpu::TextureUsages::COPY_DST
| wgpu::TextureUsages::RENDER_ATTACHMENT,
view_formats: &[],
});
let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
(tex, view)
}
#[allow(clippy::too_many_arguments)]
fn commit_to_texture(
pass: &TransformPass,
device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
queue: &wgpu::Queue,
target_tex: &wgpu::Texture,
target_view: &wgpu::TextureView,
matrix: &Affine2D,
source_origin: (i32, i32),
source_w: u32,
source_h: u32,
target_offset: (i32, i32),
target_w: u32,
target_h: u32,
canvas_w: u32,
canvas_h: u32,
) {
pass.update_uniforms(
queue,
matrix,
source_origin,
source_w,
source_h,
target_offset,
target_w,
target_h,
canvas_w,
canvas_h,
);
pass.render_commit(device, encoder, target_tex, target_view);
}
#[allow(clippy::too_many_arguments)]
fn set_floating_content_rgba(
pass: &mut TransformPass,
device: &wgpu::Device,
queue: &wgpu::Queue,
sampler: &wgpu::Sampler,
rgba_data: &[u8],
source_w: u32,
source_h: u32,
target_layer: LayerId,
canvas_w: u32,
canvas_h: u32,
target_format: wgpu::TextureFormat,
) {
let (preview_tex, preview_view) =
make_preview_placeholder(device, target_format, canvas_w, canvas_h);
let preview_blend_uniform_buf = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("test-preview-blend-uniforms"),
size: 64,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
pass.set_floating_content(
device,
queue,
sampler,
rgba_data,
source_w,
source_h,
target_layer,
target_format,
preview_tex,
preview_view,
None,
preview_blend_uniform_buf,
);
}
fn make_source_rect(
x: i32,
y: i32,
w: u32,
h: u32,
color: [u8; 4],
) -> (Vec<u8>, (i32, i32), u32, u32) {
let mut data = vec![0u8; (w * h * 4) as usize];
for py in 0..h {
for px in 0..w {
let off = ((py * w + px) * 4) as usize;
data[off..off + 4].copy_from_slice(&color);
}
}
(data, (x, y), w, h)
}
#[test]
fn transform_commit_translate() {
let (device, queue) = test_device();
let (cw, ch) = (64, 64);
let fmt = wgpu::TextureFormat::Rgba8Unorm;
let (target_tex, target_view) =
create_test_texture(&device, &queue, cw, ch, &vec![0u8; (cw * ch * 4) as usize]);
let (mut pass, sampler) = setup_transform_pass(&device, &queue, cw, ch);
let (source_data, origin, sw, sh) = make_source_rect(10, 10, 4, 4, [255, 0, 0, 255]);
set_floating_content_rgba(
&mut pass,
&device,
&queue,
&sampler,
&source_data,
sw,
sh,
LayerId::from_ffi(1),
cw,
ch,
wgpu::TextureFormat::Rgba8Unorm,
);
let matrix = affine_translate(10.0, 10.0);
let mut enc = encoder(&device);
commit_to_texture(
&pass,
&device,
&mut enc,
&queue,
&target_tex,
&target_view,
&matrix,
origin,
sw,
sh,
(0, 0),
cw,
ch,
cw,
ch,
);
submit(&queue, enc);
let pixels = readback_texture(&device, &queue, &target_tex, fmt, cw, ch);
for dy in 0..4u32 {
for dx in 0..4u32 {
let p = pixel_at(&pixels, cw, 20 + dx, 20 + dy, 4);
assert!(
p[0] > 200,
"pixel at ({},{}) should be red, got R={}",
20 + dx,
20 + dy,
p[0]
);
assert!(
p[3] > 200,
"pixel at ({},{}) should be opaque, got A={}",
20 + dx,
20 + dy,
p[3]
);
}
}
for dy in 0..4u32 {
for dx in 0..4u32 {
let p = pixel_at(&pixels, cw, 10 + dx, 10 + dy, 4);
assert_eq!(
p[3],
0,
"old position ({},{}) should be transparent, A={}",
10 + dx,
10 + dy,
p[3]
);
}
}
assert_eq!(pixel_at(&pixels, cw, 0, 0, 4)[3], 0);
}
#[test]
fn transform_commit_translate_undo() {
let (device, queue) = test_device();
let (cw, ch) = (64, 64);
let fmt = wgpu::TextureFormat::Rgba8Unorm;
let (target_tex, target_view) =
create_test_texture(&device, &queue, cw, ch, &vec![0u8; (cw * ch * 4) as usize]);
let mut store = RegionScratch::new(&device, cw, ch);
let (mut pass, sampler) = setup_transform_pass(&device, &queue, cw, ch);
let (source_data, origin, sw, sh) = make_source_rect(5, 5, 4, 4, [0, 255, 0, 255]);
set_floating_content_rgba(
&mut pass,
&device,
&queue,
&sampler,
&source_data,
sw,
sh,
LayerId::from_ffi(1),
cw,
ch,
wgpu::TextureFormat::Rgba8Unorm,
);
let mut enc = encoder(&device);
let snap = store.save_region(
&device,
&mut enc,
&frame(&target_tex, cw, ch),
fmt,
CanvasRect::from_xywh(0, 0, cw, ch),
);
submit(&queue, enc);
let matrix = affine_translate(15.0, 15.0);
let mut enc = encoder(&device);
commit_to_texture(
&pass,
&device,
&mut enc,
&queue,
&target_tex,
&target_view,
&matrix,
origin,
sw,
sh,
(0, 0),
cw,
ch,
cw,
ch,
);
submit(&queue, enc);
let mut enc = encoder(&device);
let (entry, _req) = store.commit_region(
&mut enc,
&device,
LayerId::from_ffi(1),
&frame(&target_tex, cw, ch),
&snap,
CanvasRect::from_xywh(0, 0, cw, ch),
);
submit(&queue, enc);
let pixels = readback_texture(&device, &queue, &target_tex, fmt, cw, ch);
let p = pixel_at(&pixels, cw, 20, 20, 4);
assert!(p[1] > 200, "should be green at new pos, G={}", p[1]);
let mut enc = encoder(&device);
let (_forward, _req) =
store.restore_region(&mut enc, &device, &entry, &frame(&target_tex, cw, ch));
submit(&queue, enc);
let pixels = readback_texture(&device, &queue, &target_tex, fmt, cw, ch);
assert_eq!(
pixel_at(&pixels, cw, 20, 20, 4)[3],
0,
"after undo, should be transparent"
);
assert_eq!(
pixel_at(&pixels, cw, 5, 5, 4)[3],
0,
"after undo, original pos still transparent"
);
}
#[test]
fn transform_commit_rotate_90() {
let (device, queue) = test_device();
let (cw, ch) = (64, 64);
let fmt = wgpu::TextureFormat::Rgba8Unorm;
let (target_tex, target_view) =
create_test_texture(&device, &queue, cw, ch, &vec![0u8; (cw * ch * 4) as usize]);
let (mut pass, sampler) = setup_transform_pass(&device, &queue, cw, ch);
let (sw, sh) = (5u32, 5u32);
let (ox, oy) = (10i32, 10i32);
let mut source_data = vec![0u8; (sw * sh * 4) as usize];
for py in 0..5u32 {
let off = ((py * sw + 2) * 4) as usize;
source_data[off..off + 4].copy_from_slice(&[0, 0, 255, 255]);
}
set_floating_content_rgba(
&mut pass,
&device,
&queue,
&sampler,
&source_data,
sw,
sh,
LayerId::from_ffi(1),
cw,
ch,
wgpu::TextureFormat::Rgba8Unorm,
);
let matrix: Affine2D = [0.0, 1.0, 0.0, -1.0, 0.0, 5.0];
let mut enc = encoder(&device);
commit_to_texture(
&pass,
&device,
&mut enc,
&queue,
&target_tex,
&target_view,
&matrix,
(ox, oy),
sw,
sh,
(0, 0),
cw,
ch,
cw,
ch,
);
submit(&queue, enc);
let pixels = readback_texture(&device, &queue, &target_tex, fmt, cw, ch);
for x in 10..15u32 {
let p = pixel_at(&pixels, cw, x, 12, 4);
assert!(
p[2] > 200,
"rotated line at ({},12) should be blue, B={}",
x,
p[2]
);
assert!(
p[3] > 200,
"rotated line at ({},12) should be opaque, A={}",
x,
p[3]
);
}
let p = pixel_at(&pixels, cw, 12, 10, 4);
assert_eq!(
p[3], 0,
"original vert line pos (12,10) should be clear, A={}",
p[3]
);
let p = pixel_at(&pixels, cw, 12, 11, 4);
assert_eq!(
p[3], 0,
"original vert line pos (12,11) should be clear, A={}",
p[3]
);
}
#[test]
fn paste_commit_identity() {
let (device, queue) = test_device();
let (cw, ch) = (64, 64);
let fmt = wgpu::TextureFormat::Rgba8Unorm;
let (target_tex, target_view) =
create_test_texture(&device, &queue, cw, ch, &vec![0u8; (cw * ch * 4) as usize]);
let (mut pass, sampler) = setup_transform_pass(&device, &queue, cw, ch);
let (source_data, origin, sw, sh) = make_source_rect(20, 20, 8, 8, [255, 0, 255, 255]);
set_floating_content_rgba(
&mut pass,
&device,
&queue,
&sampler,
&source_data,
sw,
sh,
LayerId::from_ffi(1),
cw,
ch,
wgpu::TextureFormat::Rgba8Unorm,
);
let mut enc = encoder(&device);
commit_to_texture(
&pass,
&device,
&mut enc,
&queue,
&target_tex,
&target_view,
&IDENTITY,
origin,
sw,
sh,
(0, 0),
cw,
ch,
cw,
ch,
);
submit(&queue, enc);
let pixels = readback_texture(&device, &queue, &target_tex, fmt, cw, ch);
let p = pixel_at(&pixels, cw, 24, 24, 4);
assert!(
p[0] > 200 && p[2] > 200,
"should be magenta, got [{},{},{},{}]",
p[0],
p[1],
p[2],
p[3]
);
assert!(p[3] > 200, "should be opaque");
assert_eq!(
pixel_at(&pixels, cw, 0, 0, 4)[3],
0,
"outside should be transparent"
);
assert_eq!(
pixel_at(&pixels, cw, 19, 20, 4)[3],
0,
"just outside should be transparent"
);
assert_eq!(
pixel_at(&pixels, cw, 28, 20, 4)[3],
0,
"just outside right should be transparent"
);
}
#[test]
fn paste_commit_undo() {
let (device, queue) = test_device();
let (cw, ch) = (64, 64);
let fmt = wgpu::TextureFormat::Rgba8Unorm;
let (target_tex, target_view) =
create_test_texture(&device, &queue, cw, ch, &vec![0u8; (cw * ch * 4) as usize]);
let mut store = RegionScratch::new(&device, cw, ch);
let (mut pass, sampler) = setup_transform_pass(&device, &queue, cw, ch);
let (source_data, origin, sw, sh) = make_source_rect(10, 10, 6, 6, [255, 255, 0, 255]);
set_floating_content_rgba(
&mut pass,
&device,
&queue,
&sampler,
&source_data,
sw,
sh,
LayerId::from_ffi(1),
cw,
ch,
wgpu::TextureFormat::Rgba8Unorm,
);
let mut enc = encoder(&device);
let snap = store.save_region(
&device,
&mut enc,
&frame(&target_tex, cw, ch),
fmt,
CanvasRect::from_xywh(0, 0, cw, ch),
);
submit(&queue, enc);
let mut enc = encoder(&device);
commit_to_texture(
&pass,
&device,
&mut enc,
&queue,
&target_tex,
&target_view,
&IDENTITY,
origin,
sw,
sh,
(0, 0),
cw,
ch,
cw,
ch,
);
submit(&queue, enc);
let mut enc = encoder(&device);
let (entry, _req) = store.commit_region(
&mut enc,
&device,
LayerId::from_ffi(1),
&frame(&target_tex, cw, ch),
&snap,
CanvasRect::from_xywh(0, 0, cw, ch),
);
submit(&queue, enc);
let pixels = readback_texture(&device, &queue, &target_tex, fmt, cw, ch);
let p = pixel_at(&pixels, cw, 12, 12, 4);
assert!(
p[0] > 200 && p[1] > 200,
"should be yellow, got [{},{},{},{}]",
p[0],
p[1],
p[2],
p[3]
);
let mut enc = encoder(&device);
let (forward, _req) =
store.restore_region(&mut enc, &device, &entry, &frame(&target_tex, cw, ch));
submit(&queue, enc);
let pixels = readback_texture(&device, &queue, &target_tex, fmt, cw, ch);
assert_eq!(
pixel_at(&pixels, cw, 12, 12, 4)[3],
0,
"after undo, should be transparent"
);
let mut enc = encoder(&device);
let (_backward, _req) =
store.restore_region(&mut enc, &device, &forward, &frame(&target_tex, cw, ch));
submit(&queue, enc);
let pixels = readback_texture(&device, &queue, &target_tex, fmt, cw, ch);
let p = pixel_at(&pixels, cw, 12, 12, 4);
assert!(p[0] > 200, "after redo, should be yellow again, R={}", p[0]);
}
#[test]
fn commit_composites_over_existing() {
let (device, queue) = test_device();
let (cw, ch) = (64, 64);
let fmt = wgpu::TextureFormat::Rgba8Unorm;
let blue: Vec<u8> = (0..cw * ch).flat_map(|_| [0u8, 0, 255, 255]).collect();
let (target_tex, target_view) = create_test_texture(&device, &queue, cw, ch, &blue);
let (mut pass, sampler) = setup_transform_pass(&device, &queue, cw, ch);
let (source_data, origin, sw, sh) = make_source_rect(10, 10, 4, 4, [255, 0, 0, 128]);
set_floating_content_rgba(
&mut pass,
&device,
&queue,
&sampler,
&source_data,
sw,
sh,
LayerId::from_ffi(1),
cw,
ch,
wgpu::TextureFormat::Rgba8Unorm,
);
let mut enc = encoder(&device);
commit_to_texture(
&pass,
&device,
&mut enc,
&queue,
&target_tex,
&target_view,
&IDENTITY,
origin,
sw,
sh,
(0, 0),
cw,
ch,
cw,
ch,
);
submit(&queue, enc);
let pixels = readback_texture(&device, &queue, &target_tex, fmt, cw, ch);
let p = pixel_at(&pixels, cw, 12, 12, 4);
assert!(
p[0] > 100 && p[0] < 180,
"blended R should be ~128, got {}",
p[0]
);
assert!(
p[2] > 80 && p[2] < 180,
"blended B should be ~127, got {}",
p[2]
);
assert_eq!(p[3], 255, "result alpha should be 255 (fully opaque bg)");
let p = pixel_at(&pixels, cw, 0, 0, 4);
assert_eq!(p, &[0, 0, 255, 255], "outside should still be blue");
}
#[test]
fn transform_commit_on_mask() {
let (device, queue) = test_device();
let (cw, ch) = (64, 64);
let mask_fmt = wgpu::TextureFormat::R8Unorm;
let (target_tex, target_view) = create_test_texture_with_format(
&device,
&queue,
cw,
ch,
&vec![0u8; (cw * ch) as usize],
mask_fmt,
);
let (mut pass, sampler) = setup_transform_pass(&device, &queue, cw, ch);
let (source_data, origin, sw, sh) = make_source_rect(10, 10, 4, 4, [255, 255, 255, 255]);
set_floating_content_rgba(
&mut pass,
&device,
&queue,
&sampler,
&source_data,
sw,
sh,
LayerId::from_ffi(1),
cw,
ch,
mask_fmt,
);
let matrix = affine_translate(5.0, 5.0);
let mut enc = encoder(&device);
commit_to_texture(
&pass,
&device,
&mut enc,
&queue,
&target_tex,
&target_view,
&matrix,
origin,
sw,
sh,
(0, 0),
cw,
ch,
cw,
ch,
);
submit(&queue, enc);
let pixels = readback_texture(&device, &queue, &target_tex, mask_fmt, cw, ch);
for dy in 0..4u32 {
for dx in 0..4u32 {
let idx = ((15 + dy) * cw + 15 + dx) as usize;
assert!(
pixels[idx] > 200,
"mask at ({},{}) should be ~255, got {}",
15 + dx,
15 + dy,
pixels[idx]
);
}
}
assert_eq!(
pixels[(10 * cw + 10) as usize],
0,
"original pos should be 0"
);
assert_eq!(pixels[0], 0, "corner should be 0");
}
#[test]
fn affine_inverse_identity() {
let inv = affine_inverse(&IDENTITY).unwrap();
for i in 0..6 {
assert!(
(inv[i] - IDENTITY[i]).abs() < 1e-6,
"inverse of identity should be identity, element {} = {}",
i,
inv[i]
);
}
}
#[test]
fn affine_inverse_translate() {
let m = affine_translate(10.0, -5.0);
let inv = affine_inverse(&m).unwrap();
assert!((inv[2] - (-10.0)).abs() < 1e-6, "inv tx = {}", inv[2]);
assert!((inv[5] - 5.0).abs() < 1e-6, "inv ty = {}", inv[5]);
}
#[test]
fn affine_multiply_translate_chain() {
let t1 = affine_translate(3.0, 4.0);
let t2 = affine_translate(7.0, 6.0);
let combined = affine_multiply(&t2, &t1);
assert!((combined[2] - 10.0).abs() < 1e-6);
assert!((combined[5] - 10.0).abs() < 1e-6);
}
#[test]
fn transform_commit_onto_offset_layer_lands_at_canvas_coords() {
let (device, queue) = test_device();
let (cw, ch) = (256u32, 256u32);
let fmt = wgpu::TextureFormat::Rgba8Unorm;
let target_off = (-50i32, -50i32);
let (target_w, target_h) = (200u32, 200u32);
let (target_tex, target_view) = create_test_texture(
&device,
&queue,
target_w,
target_h,
&vec![0u8; (target_w * target_h * 4) as usize],
);
let (mut pass, sampler) = setup_transform_pass(&device, &queue, cw, ch);
let (source_data, origin, sw, sh) = make_source_rect(10, 10, 4, 4, [0, 255, 0, 255]);
set_floating_content_rgba(
&mut pass,
&device,
&queue,
&sampler,
&source_data,
sw,
sh,
LayerId::from_ffi(1),
cw,
ch,
wgpu::TextureFormat::Rgba8Unorm,
);
let mut enc = encoder(&device);
commit_to_texture(
&pass,
&device,
&mut enc,
&queue,
&target_tex,
&target_view,
&IDENTITY,
origin,
sw,
sh,
target_off,
target_w,
target_h,
cw,
ch,
);
submit(&queue, enc);
let pixels = readback_texture(&device, &queue, &target_tex, fmt, target_w, target_h);
for dy in 0..4u32 {
for dx in 0..4u32 {
let p = pixel_at(&pixels, target_w, 60 + dx, 60 + dy, 4);
assert!(
p[1] > 200,
"layer-local ({},{}) should be green, got G={}",
60 + dx,
60 + dy,
p[1]
);
assert_eq!(
p[3],
255,
"layer-local ({},{}) alpha should be 255",
60 + dx,
60 + dy
);
}
}
for dy in 0..4u32 {
for dx in 0..4u32 {
let p = pixel_at(&pixels, target_w, 10 + dx, 10 + dy, 4);
assert_eq!(
p[3],
0,
"layer-local ({},{}) should still be transparent (would be wrong-place commit)",
10 + dx,
10 + dy
);
}
}
}
#[test]
fn cancel_floating_after_layer_grow() {
use wgpu::TextureUsages;
let (device, queue) = test_device();
let fmt = wgpu::TextureFormat::Rgba8Unorm;
let (init_w, init_h) = (256u32, 256u32);
let red: Vec<u8> = (0..init_w * init_h)
.flat_map(|_| [255u8, 0, 0, 255])
.collect();
let (initial_tex, _) = create_test_texture(&device, &queue, init_w, init_h, &red);
let initial_frame = CanvasFrame {
texture: &initial_tex,
canvas_extent: CanvasRect::from_xywh(0, 0, init_w, init_h),
};
let mut store = RegionScratch::new(&device, init_w, init_h);
let saved_canvas_rect = CanvasRect::from_xywh(50, 50, 100, 100);
let mut enc = encoder(&device);
let mut cancel_snapshot =
store.save_region(&device, &mut enc, &initial_frame, fmt, saved_canvas_rect);
submit(&queue, enc);
let (new_w, new_h) = (512u32, 512u32);
let new_tex = device.create_texture(&wgpu::TextureDescriptor {
label: Some("grown-layer"),
size: wgpu::Extent3d {
width: new_w,
height: new_h,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: fmt,
usage: TextureUsages::TEXTURE_BINDING
| TextureUsages::COPY_SRC
| TextureUsages::COPY_DST
| TextureUsages::RENDER_ATTACHMENT,
view_formats: &[],
});
let mut enc = encoder(&device);
enc.copy_texture_to_texture(
wgpu::TexelCopyTextureInfo {
texture: &initial_tex,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
},
wgpu::TexelCopyTextureInfo {
texture: &new_tex,
mip_level: 0,
origin: wgpu::Origin3d {
x: 256,
y: 256,
z: 0,
},
aspect: wgpu::TextureAspect::All,
},
wgpu::Extent3d {
width: init_w,
height: init_h,
depth_or_array_layers: 1,
},
);
store.grow_scratch_preserving(&device, &mut enc, new_w, new_h, 256, 256);
submit(&queue, enc);
cancel_snapshot.saved = CanvasRect::from_xywh(-256, -256, new_w, new_h);
let new_frame = CanvasFrame {
texture: &new_tex,
canvas_extent: CanvasRect::from_xywh(-256, -256, new_w, new_h),
};
let pipelines = PaintPipelines::new(
&device,
&queue,
&darkly::gpu::selection::selection_mask_bgl(&device),
);
let new_view = new_tex.create_view(&wgpu::TextureViewDescriptor::default());
let target = GpuPaintTarget::from_extent(
&new_tex,
&new_view,
fmt,
CanvasRect::from_xywh(-256, -256, new_w, new_h),
CanvasRect::from_xywh(0, 0, new_w, new_h),
);
let mut enc = encoder(&device);
target.fill_rect(
&mut enc,
&pipelines,
&queue,
CanvasRect::from_xywh(50, 50, 100, 100),
[0, 255, 0, 255],
);
submit(&queue, enc);
let mut enc = encoder(&device);
store.restore_from_scratch(&mut enc, &cancel_snapshot, &new_frame, saved_canvas_rect);
submit(&queue, enc);
let pixels = readback_texture(&device, &queue, &new_tex, fmt, new_w, new_h);
let p = pixel_at(&pixels, new_w, 306, 306, 4);
assert_eq!(
p,
&[255, 0, 0, 255],
"after cancel, canvas (50, 50) (= layer-local (306, 306)) must be \
restored to the pre-floating red, got {:?}",
p,
);
let q = pixel_at(&pixels, new_w, 405, 405, 4); assert_eq!(
q,
&[255, 0, 0, 255],
"near far edge of saved canvas region should also be red, got {:?}",
q,
);
let buggy = pixel_at(&pixels, new_w, 50, 50, 4);
assert_eq!(
buggy[3], 0,
"layer-local (50, 50) (canvas (-206, -206)) is in the grown area \
and must be transparent — non-zero alpha here would mean the \
restore landed at the stale pre-grow layer origin, A={}",
buggy[3],
);
}