1
0
mirror of https://github.com/emilk/egui.git synced 2026-08-31 05:40:03 -04:00
Files
egui/crates/egui_kittest/src/texture_to_image.rs
lucasmerlin 70a01138b7 Add egui testing library (#5166)
- closes #3491 
- closes #3926

This adds a testing library to egui based on
[kittest](https://github.com/rerun-io/kittest). Kittest is a new
[AccessKit](https://github.com/AccessKit/accesskit/)-based testing
library. The api is inspired by the js
[testing-library](https://testing-library.com/) where the idea is also
to query the dom based on accessibility attributes.
We made kittest with egui in mind but it should work with any rust gui
framework with AccessKit support.

It currently has support for:
- running the egui app, frame by frame
- building the AccessKit tree
- ergonomic queries via kittest
  - via e.g. get_by_name, get_by_role
- simulating events based on the accesskit node id
- creating arbitrary events based on Harness::input_mut
- rendering screenshots via wgpu
- snapshot tests with these screenshots

A simple test looks like this: 
```rust
fn main() {
    let mut checked = false;
    let app = |ctx: &Context| {
        CentralPanel::default().show(ctx, |ui| {
            ui.checkbox(&mut checked, "Check me!");
        });
    };

    let mut harness = Harness::builder().with_size(egui::Vec2::new(200.0, 100.0)).build(app);
    
    let checkbox = harness.get_by_name("Check me!");
    assert_eq!(checkbox.toggled(), Some(Toggled::False));
    checkbox.click();
    
    harness.run();

    let checkbox = harness.get_by_name("Check me!");
    assert_eq!(checkbox.toggled(), Some(Toggled::True));

    // You can even render the ui and do image snapshot tests
    #[cfg(all(feature = "wgpu", feature = "snapshot"))]
    egui_kittest::image_snapshot(&egui_kittest::wgpu::TestRenderer::new().render(&harness), "readme_example");
}
```

~Since getting wgpu to run in ci is a hassle, I'm taking another shot at
creating a software renderer for egui (ideally without a huge dependency
like skia)~ (this didn't work as well as I hoped and it turns out in CI
you can just run tests on a mac runner which comes with a real GPU)
 
Here is a example of a failed snapshot test in ci, it will say which
snapshot failed and upload an artifact with the before / after and diff
images:

https://github.com/emilk/egui/actions/runs/11183049487/job/31090724606?pr=5166
2024-10-22 12:39:00 +02:00

84 lines
3.0 KiB
Rust

use egui_wgpu::wgpu;
use egui_wgpu::wgpu::{Device, Extent3d, Queue, Texture};
use image::RgbaImage;
use std::iter;
use std::mem::size_of;
use std::sync::mpsc::channel;
pub(crate) fn texture_to_image(device: &Device, queue: &Queue, texture: &Texture) -> RgbaImage {
let buffer_dimensions =
BufferDimensions::new(texture.width() as usize, texture.height() as usize);
let output_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Texture to bytes output buffer"),
size: (buffer_dimensions.padded_bytes_per_row * buffer_dimensions.height) as u64,
usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Texture to bytes encoder"),
});
// Copy the data from the texture to the buffer
encoder.copy_texture_to_buffer(
texture.as_image_copy(),
wgpu::ImageCopyBuffer {
buffer: &output_buffer,
layout: wgpu::ImageDataLayout {
offset: 0,
bytes_per_row: Some(buffer_dimensions.padded_bytes_per_row as u32),
rows_per_image: None,
},
},
Extent3d {
width: texture.width(),
height: texture.height(),
depth_or_array_layers: 1,
},
);
let submission_index = queue.submit(iter::once(encoder.finish()));
// Note that we're not calling `.await` here.
let buffer_slice = output_buffer.slice(..);
// Sets the buffer up for mapping, sending over the result of the mapping back to us when it is finished.
let (sender, receiver) = channel();
buffer_slice.map_async(wgpu::MapMode::Read, move |v| drop(sender.send(v)));
// Poll the device in a blocking manner so that our future resolves.
device.poll(wgpu::Maintain::WaitForSubmissionIndex(submission_index));
receiver.recv().unwrap().unwrap();
let buffer_slice = output_buffer.slice(..);
let data = buffer_slice.get_mapped_range();
let data = data
.chunks_exact(buffer_dimensions.padded_bytes_per_row)
.flat_map(|row| row.iter().take(buffer_dimensions.unpadded_bytes_per_row))
.copied()
.collect::<Vec<_>>();
RgbaImage::from_raw(texture.width(), texture.height(), data).expect("Failed to create image")
}
struct BufferDimensions {
height: usize,
unpadded_bytes_per_row: usize,
padded_bytes_per_row: usize,
}
impl BufferDimensions {
fn new(width: usize, height: usize) -> Self {
let bytes_per_pixel = size_of::<u32>();
let unpadded_bytes_per_row = width * bytes_per_pixel;
let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT as usize;
let padded_bytes_per_row_padding = (align - unpadded_bytes_per_row % align) % align;
let padded_bytes_per_row = unpadded_bytes_per_row + padded_bytes_per_row_padding;
Self {
height,
unpadded_bytes_per_row,
padded_bytes_per_row,
}
}
}