1use eframe::egui_wgpu::{self, wgpu};
11
12pub fn apply_texture_sets(render: &egui_wgpu::RenderState, delta: &egui::TexturesDelta) {
16 let mut renderer = render.renderer.write();
17 for (id, image) in &delta.set {
18 renderer.update_texture(&render.device, &render.queue, *id, image);
19 }
20}
21
22pub fn apply_texture_frees(render: &egui_wgpu::RenderState, delta: &egui::TexturesDelta) {
24 let mut renderer = render.renderer.write();
25 for id in &delta.free {
26 renderer.free_texture(id);
27 }
28}
29
30#[cfg(not(target_arch = "wasm32"))]
35pub fn capture(
36 render: &egui_wgpu::RenderState,
37 ctx: &egui::Context,
38 shapes: Vec<egui::epaint::ClippedShape>,
39 pixels_per_point: f32,
40 size_px: [u32; 2],
41 clear: [f32; 4],
42) -> Result<egui::ColorImage, String> {
43 draw(render, ctx, shapes, pixels_per_point, size_px, clear).wait(render)
44}
45
46pub struct Drawn {
49 buffer: wgpu::Buffer,
50 #[cfg_attr(target_arch = "wasm32", allow(dead_code))]
52 submission: wgpu::SubmissionIndex,
53 width: u32,
54 height: u32,
55 padded: usize,
56 bgra: bool,
57}
58
59pub fn draw(
63 render: &egui_wgpu::RenderState,
64 ctx: &egui::Context,
65 shapes: Vec<egui::epaint::ClippedShape>,
66 pixels_per_point: f32,
67 size_px: [u32; 2],
68 clear: [f32; 4],
69) -> Drawn {
70 let [width, height] = [size_px[0].max(1), size_px[1].max(1)];
71 let screen = egui_wgpu::ScreenDescriptor { size_in_pixels: [width, height], pixels_per_point };
72 let primitives = ctx.tessellate(shapes, pixels_per_point);
73 let device = &render.device;
74 let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("offscreen pass") });
75 let texture = device.create_texture(&wgpu::TextureDescriptor {
76 label: Some("offscreen pass"),
77 size: wgpu::Extent3d { width, height, depth_or_array_layers: 1 },
78 mip_level_count: 1,
79 sample_count: 1,
80 dimension: wgpu::TextureDimension::D2,
81 format: render.target_format,
82 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
83 view_formats: &[],
84 });
85 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
86 let user_buffers = {
87 let mut renderer = render.renderer.write();
88 let user_buffers = renderer.update_buffers(device, &render.queue, &mut encoder, &primitives, &screen);
89 let [r, g, b, a] = clear;
90 let mut pass = encoder
91 .begin_render_pass(&wgpu::RenderPassDescriptor {
92 label: Some("offscreen pass"),
93 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
94 view: &view,
95 resolve_target: None,
96 ops: wgpu::Operations {
97 load: wgpu::LoadOp::Clear(wgpu::Color { r: r.into(), g: g.into(), b: b.into(), a: a.into() }),
98 store: wgpu::StoreOp::Store,
99 },
100 depth_slice: None,
101 })],
102 ..Default::default()
103 })
104 .forget_lifetime();
105 renderer.render(&mut pass, &primitives, &screen);
106 drop(pass);
107 user_buffers
108 };
109 let unpadded = width as usize * 4;
111 let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT as usize;
112 let padded = unpadded.div_ceil(align) * align;
113 let buffer = device.create_buffer(&wgpu::BufferDescriptor {
114 label: Some("offscreen read-back"),
115 size: (padded * height as usize) as u64,
116 usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST,
117 mapped_at_creation: false,
118 });
119 encoder.copy_texture_to_buffer(
120 texture.as_image_copy(),
121 wgpu::TexelCopyBufferInfo {
122 buffer: &buffer,
123 layout: wgpu::TexelCopyBufferLayout { offset: 0, bytes_per_row: Some(padded as u32), rows_per_image: None },
124 },
125 wgpu::Extent3d { width, height, depth_or_array_layers: 1 },
126 );
127 let submission = render.queue.submit(user_buffers.into_iter().chain(std::iter::once(encoder.finish())));
128 let bgra = matches!(texture.format(), wgpu::TextureFormat::Bgra8Unorm | wgpu::TextureFormat::Bgra8UnormSrgb);
129 Drawn { buffer, submission, width, height, padded, bgra }
130}
131
132impl Drawn {
133 #[cfg(not(target_arch = "wasm32"))]
135 pub fn wait(self, render: &egui_wgpu::RenderState) -> Result<egui::ColorImage, String> {
136 let (tx, rx) = std::sync::mpsc::channel();
137 self.buffer.slice(..).map_async(wgpu::MapMode::Read, move |result| drop(tx.send(result)));
138 render
139 .device
140 .poll(wgpu::PollType::Wait { submission_index: Some(self.submission.clone()), timeout: Some(std::time::Duration::from_secs(10)) })
141 .map_err(|e| format!("offscreen capture: device poll: {e}"))?;
142 rx.recv()
143 .map_err(|_| "offscreen capture: the read-back never mapped".to_string())?
144 .map_err(|e| format!("offscreen capture: map: {e}"))?;
145 Ok(self.image())
146 }
147
148 pub fn on_ready(self, done: impl FnOnce(Result<egui::ColorImage, String>) + wgpu::WasmNotSend + 'static) {
153 let buffer = self.buffer.clone();
154 buffer.slice(..).map_async(wgpu::MapMode::Read, move |result| {
155 done(result.map_err(|e| format!("offscreen capture: map: {e}")).map(|()| self.image()));
156 });
157 }
158
159 fn image(&self) -> egui::ColorImage {
162 let unpadded = self.width as usize * 4;
163 let data = self.buffer.slice(..).get_mapped_range();
164 let mut pixels = Vec::with_capacity(self.width as usize * self.height as usize);
165 for row in data.chunks_exact(self.padded) {
166 for px in row[..unpadded].chunks_exact(4) {
167 let (r, b) = if self.bgra { (px[2], px[0]) } else { (px[0], px[2]) };
168 pixels.push(egui::Color32::from_rgba_premultiplied(r, px[1], b, px[3]));
169 }
170 }
171 drop(data);
172 self.buffer.unmap();
173 egui::ColorImage {
174 size: [self.width as usize, self.height as usize],
175 source_size: egui::vec2(self.width as f32, self.height as f32),
176 pixels,
177 }
178 }
179}