mirror of
https://github.com/emilk/egui.git
synced 2026-09-03 15:20:05 -04:00
Add Harness::new_eframe and TestRenderer trait (#5539)
Co-authored-by: Andreas Reich <r_andreas2@web.de>
This commit is contained in:
@@ -1,122 +1,152 @@
|
||||
use crate::texture_to_image::texture_to_image;
|
||||
use crate::Harness;
|
||||
use egui_wgpu::wgpu::{Backends, InstanceDescriptor, StoreOp, TextureFormat};
|
||||
use egui_wgpu::{wgpu, ScreenDescriptor};
|
||||
use eframe::epaint::TextureId;
|
||||
use egui::TexturesDelta;
|
||||
use egui_wgpu::wgpu::{Backends, StoreOp, TextureFormat};
|
||||
use egui_wgpu::{wgpu, RenderState, ScreenDescriptor, WgpuSetup};
|
||||
use image::RgbaImage;
|
||||
use std::iter::once;
|
||||
use std::sync::Arc;
|
||||
use wgpu::Maintain;
|
||||
|
||||
/// Utility to render snapshots from a [`Harness`] using [`egui_wgpu`].
|
||||
pub struct TestRenderer {
|
||||
device: wgpu::Device,
|
||||
queue: wgpu::Queue,
|
||||
dithering: bool,
|
||||
// TODO(#5506): Replace this with the setup from https://github.com/emilk/egui/pull/5506
|
||||
pub fn default_wgpu_setup() -> egui_wgpu::WgpuSetup {
|
||||
egui_wgpu::WgpuSetup::CreateNew {
|
||||
supported_backends: Backends::all(),
|
||||
device_descriptor: Arc::new(|_| wgpu::DeviceDescriptor::default()),
|
||||
power_preference: wgpu::PowerPreference::default(),
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for TestRenderer {
|
||||
pub fn create_render_state(setup: WgpuSetup) -> egui_wgpu::RenderState {
|
||||
let instance = match &setup {
|
||||
WgpuSetup::Existing { instance, .. } => instance.clone(),
|
||||
WgpuSetup::CreateNew { .. } => Default::default(),
|
||||
};
|
||||
|
||||
pollster::block_on(egui_wgpu::RenderState::create(
|
||||
&egui_wgpu::WgpuConfiguration {
|
||||
wgpu_setup: setup,
|
||||
..Default::default()
|
||||
},
|
||||
&instance,
|
||||
None,
|
||||
None,
|
||||
1,
|
||||
false,
|
||||
))
|
||||
.expect("Failed to create render state")
|
||||
}
|
||||
|
||||
/// Utility to render snapshots from a [`crate::Harness`] using [`egui_wgpu`].
|
||||
pub struct WgpuTestRenderer {
|
||||
render_state: RenderState,
|
||||
}
|
||||
|
||||
impl Default for WgpuTestRenderer {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl TestRenderer {
|
||||
/// Create a new [`TestRenderer`] using a default [`wgpu::Instance`].
|
||||
impl WgpuTestRenderer {
|
||||
/// Create a new [`WgpuTestRenderer`] with the default setup.
|
||||
pub fn new() -> Self {
|
||||
let instance = wgpu::Instance::new(InstanceDescriptor::default());
|
||||
|
||||
let adapters = instance.enumerate_adapters(Backends::all());
|
||||
let adapter = adapters.first().expect("No adapter found");
|
||||
|
||||
let (device, queue) = pollster::block_on(adapter.request_device(
|
||||
&wgpu::DeviceDescriptor {
|
||||
label: Some("Egui Device"),
|
||||
memory_hints: Default::default(),
|
||||
required_limits: Default::default(),
|
||||
required_features: Default::default(),
|
||||
},
|
||||
None,
|
||||
))
|
||||
.expect("Failed to create device");
|
||||
|
||||
Self::create(device, queue)
|
||||
}
|
||||
|
||||
/// Create a new [`TestRenderer`] using the provided [`wgpu::Device`] and [`wgpu::Queue`].
|
||||
pub fn create(device: wgpu::Device, queue: wgpu::Queue) -> Self {
|
||||
Self {
|
||||
device,
|
||||
queue,
|
||||
dithering: false,
|
||||
render_state: create_render_state(default_wgpu_setup()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Enable or disable dithering.
|
||||
/// Create a new [`WgpuTestRenderer`] with the given setup.
|
||||
pub fn from_setup(setup: WgpuSetup) -> Self {
|
||||
Self {
|
||||
render_state: create_render_state(setup),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new [`WgpuTestRenderer`] from an existing [`RenderState`].
|
||||
///
|
||||
/// Disabled by default.
|
||||
#[inline]
|
||||
pub fn with_dithering(mut self, dithering: bool) -> Self {
|
||||
self.dithering = dithering;
|
||||
self
|
||||
/// # Panics
|
||||
/// Panics if the [`RenderState`] has been used before.
|
||||
pub fn from_render_state(render_state: RenderState) -> Self {
|
||||
assert!(
|
||||
render_state
|
||||
.renderer
|
||||
.read()
|
||||
.texture(&TextureId::Managed(0))
|
||||
.is_none(),
|
||||
"The RenderState passed in has been used before, pass in a fresh RenderState instead."
|
||||
);
|
||||
Self { render_state }
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::TestRenderer for WgpuTestRenderer {
|
||||
#[cfg(feature = "eframe")]
|
||||
fn setup_eframe(&self, cc: &mut eframe::CreationContext<'_>, frame: &mut eframe::Frame) {
|
||||
cc.wgpu_render_state = Some(self.render_state.clone());
|
||||
frame.wgpu_render_state = Some(self.render_state.clone());
|
||||
}
|
||||
|
||||
/// Render the [`Harness`] and return the resulting image.
|
||||
pub fn render<State>(&self, harness: &Harness<'_, State>) -> RgbaImage {
|
||||
// We need to create a new renderer each time we render, since the renderer stores
|
||||
// textures related to the Harnesses' egui Context.
|
||||
// Calling the renderer from different Harnesses would cause problems if we store the renderer.
|
||||
let mut renderer = egui_wgpu::Renderer::new(
|
||||
&self.device,
|
||||
TextureFormat::Rgba8Unorm,
|
||||
None,
|
||||
1,
|
||||
self.dithering,
|
||||
);
|
||||
|
||||
for delta in &harness.texture_deltas {
|
||||
for (id, image_delta) in &delta.set {
|
||||
renderer.update_texture(&self.device, &self.queue, *id, image_delta);
|
||||
}
|
||||
fn handle_delta(&mut self, delta: &TexturesDelta) {
|
||||
let mut renderer = self.render_state.renderer.write();
|
||||
for (id, image) in &delta.set {
|
||||
renderer.update_texture(
|
||||
&self.render_state.device,
|
||||
&self.render_state.queue,
|
||||
*id,
|
||||
image,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let mut encoder = self
|
||||
.device
|
||||
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||
label: Some("Egui Command Encoder"),
|
||||
});
|
||||
/// Render the [`crate::Harness`] and return the resulting image.
|
||||
fn render(
|
||||
&mut self,
|
||||
ctx: &egui::Context,
|
||||
output: &egui::FullOutput,
|
||||
) -> Result<RgbaImage, String> {
|
||||
let mut renderer = self.render_state.renderer.write();
|
||||
|
||||
let size = harness.ctx.screen_rect().size() * harness.ctx.pixels_per_point();
|
||||
let mut encoder =
|
||||
self.render_state
|
||||
.device
|
||||
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||
label: Some("Egui Command Encoder"),
|
||||
});
|
||||
|
||||
let size = ctx.screen_rect().size() * ctx.pixels_per_point();
|
||||
let screen = ScreenDescriptor {
|
||||
pixels_per_point: harness.ctx.pixels_per_point(),
|
||||
pixels_per_point: ctx.pixels_per_point(),
|
||||
size_in_pixels: [size.x.round() as u32, size.y.round() as u32],
|
||||
};
|
||||
|
||||
let tessellated = harness.ctx.tessellate(
|
||||
harness.output().shapes.clone(),
|
||||
harness.ctx.pixels_per_point(),
|
||||
);
|
||||
let tessellated = ctx.tessellate(output.shapes.clone(), ctx.pixels_per_point());
|
||||
|
||||
let user_buffers = renderer.update_buffers(
|
||||
&self.device,
|
||||
&self.queue,
|
||||
&self.render_state.device,
|
||||
&self.render_state.queue,
|
||||
&mut encoder,
|
||||
&tessellated,
|
||||
&screen,
|
||||
);
|
||||
|
||||
let texture = self.device.create_texture(&wgpu::TextureDescriptor {
|
||||
label: Some("Egui Texture"),
|
||||
size: wgpu::Extent3d {
|
||||
width: screen.size_in_pixels[0],
|
||||
height: screen.size_in_pixels[1],
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: TextureFormat::Rgba8Unorm,
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
|
||||
view_formats: &[],
|
||||
});
|
||||
let texture = self
|
||||
.render_state
|
||||
.device
|
||||
.create_texture(&wgpu::TextureDescriptor {
|
||||
label: Some("Egui Texture"),
|
||||
size: wgpu::Extent3d {
|
||||
width: screen.size_in_pixels[0],
|
||||
height: screen.size_in_pixels[1],
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: TextureFormat::Rgba8Unorm,
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
|
||||
view_formats: &[],
|
||||
});
|
||||
|
||||
let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
|
||||
@@ -141,11 +171,16 @@ impl TestRenderer {
|
||||
renderer.render(&mut pass, &tessellated, &screen);
|
||||
}
|
||||
|
||||
self.queue
|
||||
self.render_state
|
||||
.queue
|
||||
.submit(user_buffers.into_iter().chain(once(encoder.finish())));
|
||||
|
||||
self.device.poll(Maintain::Wait);
|
||||
self.render_state.device.poll(Maintain::Wait);
|
||||
|
||||
texture_to_image(&self.device, &self.queue, &texture)
|
||||
Ok(texture_to_image(
|
||||
&self.render_state.device,
|
||||
&self.render_state.queue,
|
||||
&texture,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user