Regenerado con scripts/actualizar-standalone.py --llimphi. Vendoriza además
shared/{foreign-lottie,grafo}. Quedan fuera los crates acoplados al workspace
madre (wasm-*, video-plane, voxel-app/studio, allichay, plugin-host, shuma-term).
cargo check --workspace verde (112 miembros).
135 lines
5.0 KiB
Rust
135 lines
5.0 KiB
Rust
//! Correctitud del `scissor` del GPU-directo (habilitador de damage /
|
|
//! partial-present): un `flush` con `scissor(x,y,w,h)` + `LoadOp::Load` sólo
|
|
//! pinta dentro del rect y **preserva** el resto del `view`. Es la propiedad que
|
|
//! el runtime necesita para re-pintar únicamente la región sucia (un cursor, un
|
|
//! spinner) sin tocar la pantalla entera. Certificado por texto (§8), llvmpipe.
|
|
|
|
use llimphi_hal::{wgpu, Hal};
|
|
use llimphi_raster::gpu::{GpuBatch, GpuPipelines};
|
|
use llimphi_raster::peniko::Color;
|
|
|
|
const N: u32 = 16;
|
|
const FMT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8Unorm;
|
|
|
|
fn read_all(hal: &Hal, target: &wgpu::Texture) -> Vec<u8> {
|
|
let unpadded = N as usize * 4;
|
|
let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT as usize;
|
|
let padded = unpadded.div_ceil(align) * align;
|
|
let buf = hal.device.create_buffer(&wgpu::BufferDescriptor {
|
|
label: Some("scissor-readback"),
|
|
size: (padded * N as usize) as u64,
|
|
usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST,
|
|
mapped_at_creation: false,
|
|
});
|
|
let mut enc = hal
|
|
.device
|
|
.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
|
|
enc.copy_texture_to_buffer(
|
|
wgpu::TexelCopyTextureInfo {
|
|
texture: target,
|
|
mip_level: 0,
|
|
origin: wgpu::Origin3d::ZERO,
|
|
aspect: wgpu::TextureAspect::All,
|
|
},
|
|
wgpu::TexelCopyBufferInfo {
|
|
buffer: &buf,
|
|
layout: wgpu::TexelCopyBufferLayout {
|
|
offset: 0,
|
|
bytes_per_row: Some(padded as u32),
|
|
rows_per_image: Some(N),
|
|
},
|
|
},
|
|
wgpu::Extent3d { width: N, height: N, depth_or_array_layers: 1 },
|
|
);
|
|
hal.queue.submit(std::iter::once(enc.finish()));
|
|
let slice = buf.slice(..);
|
|
let (tx, rx) = std::sync::mpsc::channel();
|
|
slice.map_async(wgpu::MapMode::Read, move |r| {
|
|
let _ = tx.send(r);
|
|
});
|
|
let _ = hal.device.poll(wgpu::PollType::wait_indefinitely());
|
|
rx.recv().unwrap().unwrap();
|
|
let data = slice.get_mapped_range();
|
|
let mut out = Vec::with_capacity((N * N * 4) as usize);
|
|
for row in 0..N as usize {
|
|
let s = row * padded;
|
|
out.extend_from_slice(&data[s..s + unpadded]);
|
|
}
|
|
drop(data);
|
|
buf.unmap();
|
|
out
|
|
}
|
|
|
|
fn px(buf: &[u8], x: u32, y: u32) -> [u8; 4] {
|
|
let o = ((y * N + x) * 4) as usize;
|
|
[buf[o], buf[o + 1], buf[o + 2], buf[o + 3]]
|
|
}
|
|
|
|
#[test]
|
|
fn scissor_pinta_solo_la_region_sucia_y_preserva_el_resto() {
|
|
let hal = pollster::block_on(Hal::new_headless(true)).expect("hal");
|
|
let pipelines = GpuPipelines::new(&hal.device, FMT);
|
|
let target = hal.device.create_texture(&wgpu::TextureDescriptor {
|
|
label: Some("scissor-target"),
|
|
size: wgpu::Extent3d { width: N, height: N, depth_or_array_layers: 1 },
|
|
mip_level_count: 1,
|
|
sample_count: 1,
|
|
dimension: wgpu::TextureDimension::D2,
|
|
format: FMT,
|
|
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
|
|
view_formats: &[],
|
|
});
|
|
let view = target.create_view(&wgpu::TextureViewDescriptor::default());
|
|
|
|
let mut enc = hal
|
|
.device
|
|
.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
|
|
|
|
// 1) Pintar TODO de azul (estado "previo" del view).
|
|
let mut base = GpuBatch::new(&pipelines);
|
|
base.add_rect(0.0, 0.0, N as f32, N as f32, Color::from_rgba8(0, 0, 255, 255));
|
|
base.flush(
|
|
&hal.device,
|
|
&hal.queue,
|
|
&mut enc,
|
|
&view,
|
|
(N as f32, N as f32),
|
|
wgpu::LoadOp::Clear(wgpu::Color::BLACK),
|
|
);
|
|
|
|
// 2) Un rect rojo full-viewport PERO con scissor a [4,4]-[12,12] y Load:
|
|
// debe pintar rojo sólo ahí y dejar el azul intacto afuera.
|
|
let mut dirty = GpuBatch::new(&pipelines);
|
|
dirty.add_rect(0.0, 0.0, N as f32, N as f32, Color::from_rgba8(255, 0, 0, 255));
|
|
dirty.scissor(4, 4, 8, 8);
|
|
dirty.flush(
|
|
&hal.device,
|
|
&hal.queue,
|
|
&mut enc,
|
|
&view,
|
|
(N as f32, N as f32),
|
|
wgpu::LoadOp::Load,
|
|
);
|
|
hal.queue.submit(std::iter::once(enc.finish()));
|
|
let _ = hal.device.poll(wgpu::PollType::wait_indefinitely());
|
|
|
|
let buf = read_all(&hal, &target);
|
|
|
|
// Centro (8,8): dentro del scissor → rojo.
|
|
let c = px(&buf, 8, 8);
|
|
assert!(c[0] > 200 && c[2] < 60, "centro debería ser rojo, fue {c:?}");
|
|
// Esquinas fuera del scissor → azul preservado.
|
|
for (x, y) in [(1, 1), (14, 1), (1, 14), (14, 14)] {
|
|
let p = px(&buf, x, y);
|
|
assert!(
|
|
p[2] > 200 && p[0] < 60,
|
|
"({x},{y}) fuera del scissor debería seguir azul, fue {p:?}"
|
|
);
|
|
}
|
|
// Justo fuera del borde del scissor (3,3) azul; justo dentro (4,4) rojo.
|
|
let out = px(&buf, 3, 3);
|
|
assert!(out[2] > 200 && out[0] < 60, "(3,3) borde-fuera debería ser azul, fue {out:?}");
|
|
let inn = px(&buf, 5, 5);
|
|
assert!(inn[0] > 200 && inn[2] < 60, "(5,5) borde-dentro debería ser rojo, fue {inn:?}");
|
|
}
|