1use egui::{UserData, ViewportId};
2use epaint::ColorImage;
3use std::sync::{Arc, mpsc};
4use wgpu::{BindGroupLayout, MultisampleState, StoreOp};
5
6pub struct CaptureState {
15 padding: BufferPadding,
16 pub texture: wgpu::Texture,
17 pipeline: wgpu::RenderPipeline,
18 bind_group: wgpu::BindGroup,
19}
20
21pub type CaptureReceiver = mpsc::Receiver<(ViewportId, Vec<UserData>, ColorImage)>;
22pub type CaptureSender = mpsc::Sender<(ViewportId, Vec<UserData>, ColorImage)>;
23pub use mpsc::channel as capture_channel;
24
25impl CaptureState {
26 pub fn new(device: &wgpu::Device, surface_texture: &wgpu::Texture) -> Self {
27 let shader = device.create_shader_module(wgpu::include_wgsl!("texture_copy.wgsl"));
28
29 let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
30 label: Some("texture_copy"),
31 layout: None,
32 vertex: wgpu::VertexState {
33 module: &shader,
34 entry_point: Some("vs_main"),
35 compilation_options: Default::default(),
36 buffers: &[],
37 },
38 fragment: Some(wgpu::FragmentState {
39 module: &shader,
40 entry_point: Some("fs_main"),
41 compilation_options: Default::default(),
42 targets: &[Some(surface_texture.format().into())],
43 }),
44 primitive: wgpu::PrimitiveState {
45 topology: wgpu::PrimitiveTopology::TriangleList,
46 ..Default::default()
47 },
48 depth_stencil: None,
49 multisample: MultisampleState::default(),
50 multiview: None,
51 cache: None,
52 });
53
54 let bind_group_layout = pipeline.get_bind_group_layout(0);
55
56 let (texture, padding, bind_group) =
57 Self::create_texture(device, surface_texture, &bind_group_layout);
58
59 Self {
60 padding,
61 texture,
62 pipeline,
63 bind_group,
64 }
65 }
66
67 fn create_texture(
68 device: &wgpu::Device,
69 surface_texture: &wgpu::Texture,
70 layout: &BindGroupLayout,
71 ) -> (wgpu::Texture, BufferPadding, wgpu::BindGroup) {
72 let texture = device.create_texture(&wgpu::TextureDescriptor {
73 label: Some("egui_screen_capture_texture"),
74 size: surface_texture.size(),
75 mip_level_count: surface_texture.mip_level_count(),
76 sample_count: surface_texture.sample_count(),
77 dimension: surface_texture.dimension(),
78 format: surface_texture.format(),
79 usage: wgpu::TextureUsages::RENDER_ATTACHMENT
80 | wgpu::TextureUsages::TEXTURE_BINDING
81 | wgpu::TextureUsages::COPY_SRC,
82 view_formats: &[],
83 });
84
85 let padding = BufferPadding::new(surface_texture.width());
86
87 let view = texture.create_view(&Default::default());
88
89 let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
90 layout,
91 entries: &[wgpu::BindGroupEntry {
92 binding: 0,
93 resource: wgpu::BindingResource::TextureView(&view),
94 }],
95 label: None,
96 });
97
98 (texture, padding, bind_group)
99 }
100
101 pub fn update(&mut self, device: &wgpu::Device, texture: &wgpu::Texture) {
103 if self.texture.size() != texture.size() {
104 let (new_texture, padding, bind_group) =
105 Self::create_texture(device, texture, &self.pipeline.get_bind_group_layout(0));
106 self.texture = new_texture;
107 self.padding = padding;
108 self.bind_group = bind_group;
109 }
110 }
111
112 pub fn copy_textures(
115 &mut self,
116 device: &wgpu::Device,
117 output_frame: &wgpu::SurfaceTexture,
118 encoder: &mut wgpu::CommandEncoder,
119 ) -> wgpu::Buffer {
120 debug_assert_eq!(
121 self.texture.size(),
122 output_frame.texture.size(),
123 "Texture sizes must match, `CaptureState::update` was probably not called"
124 );
125
126 #[allow(clippy::arc_with_non_send_sync, clippy::allow_attributes)] let buffer = device.create_buffer(&wgpu::BufferDescriptor {
131 label: Some("egui_screen_capture_buffer"),
132 size: (self.padding.padded_bytes_per_row * self.texture.height()) as u64,
133 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
134 mapped_at_creation: false,
135 });
136 let padding = self.padding;
137 let tex = &mut self.texture;
138
139 let tex_extent = tex.size();
140
141 encoder.copy_texture_to_buffer(
142 tex.as_image_copy(),
143 wgpu::TexelCopyBufferInfo {
144 buffer: &buffer,
145 layout: wgpu::TexelCopyBufferLayout {
146 offset: 0,
147 bytes_per_row: Some(padding.padded_bytes_per_row),
148 rows_per_image: None,
149 },
150 },
151 tex_extent,
152 );
153
154 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
155 label: Some("texture_copy"),
156 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
157 view: &output_frame.texture.create_view(&Default::default()),
158 resolve_target: None,
159 ops: wgpu::Operations {
160 load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
161 store: StoreOp::Store,
162 },
163 depth_slice: None,
164 })],
165 depth_stencil_attachment: None,
166 occlusion_query_set: None,
167 timestamp_writes: None,
168 });
169
170 pass.set_pipeline(&self.pipeline);
171 pass.set_bind_group(0, &self.bind_group, &[]);
172 pass.draw(0..3, 0..1);
173
174 buffer
175 }
176
177 pub fn read_screen_rgba(
182 &self,
183 ctx: egui::Context,
184 buffer: wgpu::Buffer,
185 data: Vec<UserData>,
186 tx: CaptureSender,
187 viewport_id: ViewportId,
188 ) {
189 #[allow(clippy::arc_with_non_send_sync, clippy::allow_attributes)] let buffer = Arc::new(buffer);
191 let buffer_clone = buffer.clone();
192 let buffer_slice = buffer_clone.slice(..);
193 let format = self.texture.format();
194 let tex_extent = self.texture.size();
195 let padding = self.padding;
196 let to_rgba = match format {
197 wgpu::TextureFormat::Rgba8Unorm => [0, 1, 2, 3],
198 wgpu::TextureFormat::Bgra8Unorm => [2, 1, 0, 3],
199 _ => {
200 log::error!(
201 "Screen can't be captured unless the surface format is Rgba8Unorm or Bgra8Unorm. Current surface format is {format:?}"
202 );
203 return;
204 }
205 };
206 buffer_slice.map_async(wgpu::MapMode::Read, move |result| {
207 if let Err(err) = result {
208 log::error!("Failed to map buffer for reading: {err}");
209 return;
210 }
211 let buffer_slice = buffer.slice(..);
212
213 let mut pixels = Vec::with_capacity((tex_extent.width * tex_extent.height) as usize);
214 for padded_row in buffer_slice
215 .get_mapped_range()
216 .chunks(padding.padded_bytes_per_row as usize)
217 {
218 let row = &padded_row[..padding.unpadded_bytes_per_row as usize];
219 for color in row.chunks(4) {
220 pixels.push(epaint::Color32::from_rgba_premultiplied(
221 color[to_rgba[0]],
222 color[to_rgba[1]],
223 color[to_rgba[2]],
224 color[to_rgba[3]],
225 ));
226 }
227 }
228 buffer.unmap();
229
230 tx.send((
231 viewport_id,
232 data,
233 ColorImage::new(
234 [tex_extent.width as usize, tex_extent.height as usize],
235 pixels,
236 ),
237 ))
238 .ok();
239 ctx.request_repaint();
240 });
241 }
242}
243
244#[derive(Copy, Clone)]
245struct BufferPadding {
246 unpadded_bytes_per_row: u32,
247 padded_bytes_per_row: u32,
248}
249
250impl BufferPadding {
251 fn new(width: u32) -> Self {
252 let bytes_per_pixel = std::mem::size_of::<u32>() as u32;
253 let unpadded_bytes_per_row = width * bytes_per_pixel;
254 let padded_bytes_per_row =
255 wgpu::util::align_to(unpadded_bytes_per_row, wgpu::COPY_BYTES_PER_ROW_ALIGNMENT);
256 Self {
257 unpadded_bytes_per_row,
258 padded_bytes_per_row,
259 }
260 }
261}