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