1use eframe::egui_wgpu::{self, wgpu};
10
11pub fn apply_texture_sets(render: &egui_wgpu::RenderState, delta: &egui::TexturesDelta) {
15 let mut renderer = render.renderer.write();
16 for (id, image) in &delta.set {
17 renderer.update_texture(&render.device, &render.queue, *id, image);
18 }
19}
20
21pub fn apply_texture_frees(render: &egui_wgpu::RenderState, delta: &egui::TexturesDelta) {
23 let mut renderer = render.renderer.write();
24 for id in &delta.free {
25 renderer.free_texture(id);
26 }
27}
28
29pub fn capture(
32 render: &egui_wgpu::RenderState,
33 ctx: &egui::Context,
34 shapes: Vec<egui::epaint::ClippedShape>,
35 pixels_per_point: f32,
36 size_px: [u32; 2],
37 clear: [f32; 4],
38) -> Result<egui::ColorImage, String> {
39 let [width, height] = [size_px[0].max(1), size_px[1].max(1)];
40 let screen = egui_wgpu::ScreenDescriptor { size_in_pixels: [width, height], pixels_per_point };
41 let primitives = ctx.tessellate(shapes, pixels_per_point);
42 let device = &render.device;
43 let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("offscreen pass") });
44 let texture = device.create_texture(&wgpu::TextureDescriptor {
45 label: Some("offscreen pass"),
46 size: wgpu::Extent3d { width, height, depth_or_array_layers: 1 },
47 mip_level_count: 1,
48 sample_count: 1,
49 dimension: wgpu::TextureDimension::D2,
50 format: render.target_format,
51 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
52 view_formats: &[],
53 });
54 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
55 let user_buffers = {
56 let mut renderer = render.renderer.write();
57 let user_buffers = renderer.update_buffers(device, &render.queue, &mut encoder, &primitives, &screen);
58 let [r, g, b, a] = clear;
59 let mut pass = encoder
60 .begin_render_pass(&wgpu::RenderPassDescriptor {
61 label: Some("offscreen pass"),
62 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
63 view: &view,
64 resolve_target: None,
65 ops: wgpu::Operations {
66 load: wgpu::LoadOp::Clear(wgpu::Color { r: r.into(), g: g.into(), b: b.into(), a: a.into() }),
67 store: wgpu::StoreOp::Store,
68 },
69 depth_slice: None,
70 })],
71 ..Default::default()
72 })
73 .forget_lifetime();
74 renderer.render(&mut pass, &primitives, &screen);
75 drop(pass);
76 user_buffers
77 };
78 render.queue.submit(user_buffers.into_iter().chain(std::iter::once(encoder.finish())));
79 read_back(render, &texture)
80}
81
82fn read_back(render: &egui_wgpu::RenderState, texture: &wgpu::Texture) -> Result<egui::ColorImage, String> {
85 let (width, height) = (texture.width(), texture.height());
86 let unpadded = width as usize * 4;
87 let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT as usize;
88 let padded = unpadded.div_ceil(align) * align;
89 let device = &render.device;
90 let buffer = device.create_buffer(&wgpu::BufferDescriptor {
91 label: Some("offscreen read-back"),
92 size: (padded * height as usize) as u64,
93 usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST,
94 mapped_at_creation: false,
95 });
96 let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("offscreen read-back") });
97 encoder.copy_texture_to_buffer(
98 texture.as_image_copy(),
99 wgpu::TexelCopyBufferInfo {
100 buffer: &buffer,
101 layout: wgpu::TexelCopyBufferLayout { offset: 0, bytes_per_row: Some(padded as u32), rows_per_image: None },
102 },
103 wgpu::Extent3d { width, height, depth_or_array_layers: 1 },
104 );
105 let submission = render.queue.submit(std::iter::once(encoder.finish()));
106 let slice = buffer.slice(..);
107 let (tx, rx) = std::sync::mpsc::channel();
108 slice.map_async(wgpu::MapMode::Read, move |result| drop(tx.send(result)));
109 device
110 .poll(wgpu::PollType::Wait { submission_index: Some(submission), timeout: Some(std::time::Duration::from_secs(10)) })
111 .map_err(|e| format!("offscreen capture: device poll: {e}"))?;
112 rx.recv()
113 .map_err(|_| "offscreen capture: the read-back never mapped".to_string())?
114 .map_err(|e| format!("offscreen capture: map: {e}"))?;
115 let bgra = matches!(texture.format(), wgpu::TextureFormat::Bgra8Unorm | wgpu::TextureFormat::Bgra8UnormSrgb);
116 let data = slice.get_mapped_range();
117 let mut pixels = Vec::with_capacity(width as usize * height as usize);
118 for row in data.chunks_exact(padded) {
119 for px in row[..unpadded].chunks_exact(4) {
120 let (r, b) = if bgra { (px[2], px[0]) } else { (px[0], px[2]) };
121 pixels.push(egui::Color32::from_rgba_premultiplied(r, px[1], b, px[3]));
122 }
123 }
124 drop(data);
125 buffer.unmap();
126 Ok(egui::ColorImage {
127 size: [width as usize, height as usize],
128 source_size: egui::vec2(width as f32, height as f32),
129 pixels,
130 })
131}