1#![allow(
2 irrefutable_let_patterns,
3 clippy::new_without_default,
4 clippy::needless_borrowed_reference,
6)]
7#![warn(
8 trivial_casts,
9 trivial_numeric_casts,
10 unused_extern_crates,
11 unused_qualifications,
12 clippy::pattern_type_mismatch,
14)]
15
16const SHADER_SOURCE: &'static str = include_str!("../shader.wgsl");
17
18use blade_util::{BufferBelt, BufferBeltDescriptor};
19use std::{
20 collections::hash_map::{Entry, HashMap},
21 mem::size_of,
22 ptr,
23};
24
25#[repr(C)]
26#[derive(Clone, Copy, bytemuck::Zeroable, bytemuck::Pod)]
27struct Uniforms {
28 screen_size: [f32; 2],
29 padding: [f32; 2],
30}
31
32#[derive(blade_macros::ShaderData)]
33struct Globals {
34 r_uniforms: Uniforms,
35}
36
37#[derive(blade_macros::ShaderData)]
38struct Locals {
39 r_vertex_data: blade_graphics::BufferPiece,
40 r_texture: blade_graphics::TextureView,
41 r_sampler: blade_graphics::Sampler,
42}
43
44#[derive(Debug, PartialEq)]
45pub struct ScreenDescriptor {
46 pub physical_size: (u32, u32),
47 pub scale_factor: f32,
48}
49
50impl ScreenDescriptor {
51 fn logical_size(&self) -> (f32, f32) {
52 let logical_width = self.physical_size.0 as f32 / self.scale_factor;
53 let logical_height = self.physical_size.1 as f32 / self.scale_factor;
54 (logical_width, logical_height)
55 }
56}
57
58struct GuiTexture {
59 allocation: blade_graphics::Texture,
60 view: blade_graphics::TextureView,
61 sampler: blade_graphics::Sampler,
62}
63
64#[inline]
65const fn egui_texture_filter_to_blade(filter: egui::TextureFilter) -> blade_graphics::FilterMode {
66 match filter {
67 egui::TextureFilter::Nearest => blade_graphics::FilterMode::Nearest,
68 egui::TextureFilter::Linear => blade_graphics::FilterMode::Linear,
69 }
70}
71
72impl GuiTexture {
73 fn create(
74 context: &blade_graphics::Context,
75 name: &str,
76 size: blade_graphics::Extent,
77 options: egui::TextureOptions,
78 ) -> Self {
79 let format = blade_graphics::TextureFormat::Rgba8Unorm;
80 let allocation = context.create_texture(blade_graphics::TextureDesc {
81 name,
82 format,
83 size,
84 array_layer_count: 1,
85 mip_level_count: 1,
86 dimension: blade_graphics::TextureDimension::D2,
87 usage: blade_graphics::TextureUsage::COPY | blade_graphics::TextureUsage::RESOURCE,
88 sample_count: 1,
89 });
90 let view = context.create_texture_view(
91 allocation,
92 blade_graphics::TextureViewDesc {
93 name,
94 format,
95 dimension: blade_graphics::ViewDimension::D2,
96 subresources: &blade_graphics::TextureSubresources::default(),
97 },
98 );
99 let sampler = context.create_sampler(blade_graphics::SamplerDesc {
100 name,
101 address_modes: {
102 let mode = match options.wrap_mode {
103 egui::TextureWrapMode::ClampToEdge => blade_graphics::AddressMode::ClampToEdge,
104 egui::TextureWrapMode::Repeat => blade_graphics::AddressMode::Repeat,
105 egui::TextureWrapMode::MirroredRepeat => {
106 blade_graphics::AddressMode::MirrorRepeat
107 }
108 };
109 [mode; 3]
110 },
111 mag_filter: egui_texture_filter_to_blade(options.magnification),
112 min_filter: egui_texture_filter_to_blade(options.minification),
113 mipmap_filter: options
114 .mipmap_mode
115 .map(egui_texture_filter_to_blade)
116 .unwrap_or_default(),
117
118 ..Default::default()
119 });
120 Self {
121 allocation,
122 view,
123 sampler,
124 }
125 }
126
127 fn delete(self, context: &blade_graphics::Context) {
128 context.destroy_texture(self.allocation);
129 context.destroy_texture_view(self.view);
130 context.destroy_sampler(self.sampler);
131 }
132}
133
134pub struct GuiPainter {
140 pipeline: blade_graphics::RenderPipeline,
141 belt: BufferBelt,
143 textures: HashMap<egui::TextureId, GuiTexture>,
144 textures_dropped: Vec<GuiTexture>,
146 textures_to_delete: Vec<(GuiTexture, blade_graphics::SyncPoint)>,
147}
148
149impl GuiPainter {
150 pub fn destroy(&mut self, context: &blade_graphics::Context) {
152 context.destroy_render_pipeline(&mut self.pipeline);
153 self.belt.destroy(context);
154 for (_, gui_texture) in self.textures.drain() {
155 gui_texture.delete(context);
156 }
157 for gui_texture in self.textures_dropped.drain(..) {
158 gui_texture.delete(context);
159 }
160 for (gui_texture, _) in self.textures_to_delete.drain(..) {
161 gui_texture.delete(context);
162 }
163 }
164
165 #[profiling::function]
170 pub fn new(info: blade_graphics::SurfaceInfo, context: &blade_graphics::Context) -> Self {
171 let shader = context.create_shader(blade_graphics::ShaderDesc {
172 source: SHADER_SOURCE,
173 });
174 let globals_layout = <Globals as blade_graphics::ShaderData>::layout();
175 let locals_layout = <Locals as blade_graphics::ShaderData>::layout();
176 let pipeline = context.create_render_pipeline(blade_graphics::RenderPipelineDesc {
177 name: "gui",
178 data_layouts: &[&globals_layout, &locals_layout],
179 vertex: shader.at("vs_main"),
180 vertex_fetches: &[],
181 primitive: blade_graphics::PrimitiveState {
182 topology: blade_graphics::PrimitiveTopology::TriangleList,
183 ..Default::default()
184 },
185 depth_stencil: None, fragment: Some(shader.at("fs_main")),
187 color_targets: &[blade_graphics::ColorTargetState {
188 format: info.format,
189 blend: Some(blade_graphics::BlendState {
190 color: blade_graphics::BlendComponent {
191 src_factor: blade_graphics::BlendFactor::One,
192 dst_factor: blade_graphics::BlendFactor::OneMinusSrcAlpha,
193 operation: blade_graphics::BlendOperation::Add,
194 },
195 alpha: blade_graphics::BlendComponent {
196 src_factor: blade_graphics::BlendFactor::OneMinusDstAlpha,
197 dst_factor: blade_graphics::BlendFactor::One,
198 operation: blade_graphics::BlendOperation::Add,
199 },
200 }),
201 write_mask: blade_graphics::ColorWrites::all(),
202 }],
203 multisample_state: Default::default(),
204 });
205
206 let belt = BufferBelt::new(BufferBeltDescriptor {
207 memory: blade_graphics::Memory::Shared,
208 min_chunk_size: 0x1000,
209 alignment: 4,
210 });
211
212 Self {
213 pipeline,
214 belt,
215 textures: Default::default(),
216 textures_dropped: Vec::new(),
217 textures_to_delete: Vec::new(),
218 }
219 }
220
221 #[profiling::function]
222 fn triage_deletions(&mut self, context: &blade_graphics::Context) {
223 let valid_pos = self
224 .textures_to_delete
225 .iter()
226 .position(|&(_, ref sp)| !context.wait_for(sp, 0))
227 .unwrap_or_default();
228 for (texture, _) in self.textures_to_delete.drain(..valid_pos) {
229 context.destroy_texture_view(texture.view);
230 context.destroy_texture(texture.allocation);
231 }
232 }
233
234 #[profiling::function]
238 pub fn update_textures(
239 &mut self,
240 command_encoder: &mut blade_graphics::CommandEncoder,
241 textures_delta: &egui::TexturesDelta,
242 context: &blade_graphics::Context,
243 ) {
244 if textures_delta.set.is_empty() && textures_delta.free.is_empty() {
245 return;
246 }
247
248 let mut copies = Vec::new();
249 for &(texture_id, ref image_delta) in textures_delta.set.iter() {
250 let src = match image_delta.image {
251 egui::ImageData::Color(ref c) => self.belt.alloc_pod(c.pixels.as_slice(), context),
252 egui::ImageData::Font(ref a) => {
253 let color_iter = a.srgba_pixels(None);
254 let stage = self.belt.alloc(
255 (color_iter.len() * size_of::<egui::Color32>()) as u64,
256 context,
257 );
258 let mut ptr = stage.data() as *mut egui::Color32;
259 for color in color_iter {
260 unsafe {
261 ptr::write(ptr, color);
262 ptr = ptr.offset(1);
263 }
264 }
265 stage
266 }
267 };
268
269 let image_size = image_delta.image.size();
270 let extent = blade_graphics::Extent {
271 width: image_size[0] as u32,
272 height: image_size[1] as u32,
273 depth: 1,
274 };
275
276 let label = match texture_id {
277 egui::TextureId::Managed(m) => format!("egui_image_{}", m),
278 egui::TextureId::User(u) => format!("egui_user_image_{}", u),
279 };
280
281 let texture = match self.textures.entry(texture_id) {
282 Entry::Occupied(mut o) => {
283 if image_delta.pos.is_none() {
284 let texture =
285 GuiTexture::create(context, &label, extent, image_delta.options);
286 command_encoder.init_texture(texture.allocation);
287 let old = o.insert(texture);
288 self.textures_dropped.push(old);
289 }
290 o.into_mut()
291 }
292 Entry::Vacant(v) => {
293 let texture = GuiTexture::create(context, &label, extent, image_delta.options);
294 command_encoder.init_texture(texture.allocation);
295 v.insert(texture)
296 }
297 };
298
299 let dst = blade_graphics::TexturePiece {
300 texture: texture.allocation,
301 mip_level: 0,
302 array_layer: 0,
303 origin: match image_delta.pos {
304 Some([x, y]) => [x as u32, y as u32, 0],
305 None => [0; 3],
306 },
307 };
308 copies.push((src, dst, extent));
309 }
310
311 if let mut transfer = command_encoder.transfer("update egui textures") {
312 for (src, dst, extent) in copies {
313 transfer.copy_buffer_to_texture(src, 4 * extent.width, dst, extent);
314 }
315 }
316
317 for texture_id in textures_delta.free.iter() {
318 let texture = self.textures.remove(texture_id).unwrap();
319 self.textures_dropped.push(texture);
320 }
321
322 self.triage_deletions(context);
323 }
324
325 #[profiling::function]
328 pub fn paint(
329 &mut self,
330 pass: &mut blade_graphics::RenderCommandEncoder,
331 paint_jobs: &[egui::epaint::ClippedPrimitive],
332 sd: &ScreenDescriptor,
333 context: &blade_graphics::Context,
334 ) {
335 let logical_size = sd.logical_size();
336 let mut pc = pass.with(&self.pipeline);
337 pc.bind(
338 0,
339 &Globals {
340 r_uniforms: Uniforms {
341 screen_size: [logical_size.0, logical_size.1],
342 padding: [0.0; 2],
343 },
344 },
345 );
346
347 for clipped_prim in paint_jobs {
348 let clip_rect = &clipped_prim.clip_rect;
349
350 let clip_min_x = (sd.scale_factor * clip_rect.min.x)
352 .clamp(0.0, sd.physical_size.0 as f32)
353 .trunc() as i32;
354 let clip_min_y = (sd.scale_factor * clip_rect.min.y)
355 .clamp(0.0, sd.physical_size.1 as f32)
356 .trunc() as i32;
357 let clip_max_x = (sd.scale_factor * clip_rect.max.x)
358 .clamp(0.0, sd.physical_size.0 as f32)
359 .ceil() as i32;
360 let clip_max_y = (sd.scale_factor * clip_rect.max.y)
361 .clamp(0.0, sd.physical_size.1 as f32)
362 .ceil() as i32;
363
364 if clip_max_x <= clip_min_x || clip_max_y == clip_min_y {
365 continue;
366 }
367
368 pc.set_scissor_rect(&blade_graphics::ScissorRect {
369 x: clip_min_x,
370 y: clip_min_y,
371 w: (clip_max_x - clip_min_x) as u32,
372 h: (clip_max_y - clip_min_y) as u32,
373 });
374
375 if let egui::epaint::Primitive::Mesh(ref mesh) = clipped_prim.primitive {
376 let texture = self.textures.get(&mesh.texture_id).unwrap();
377 let index_buf = self.belt.alloc_pod(&mesh.indices, context);
378 let vertex_buf = self.belt.alloc_pod(&mesh.vertices, context);
379
380 pc.bind(
381 1,
382 &Locals {
383 r_vertex_data: vertex_buf,
384 r_texture: texture.view,
385 r_sampler: texture.sampler,
386 },
387 );
388
389 pc.draw_indexed(
390 index_buf,
391 blade_graphics::IndexType::U32,
392 mesh.indices.len() as u32,
393 0,
394 0,
395 1,
396 );
397 }
398 }
399 }
400
401 #[profiling::function]
403 pub fn after_submit(&mut self, sync_point: &blade_graphics::SyncPoint) {
404 self.textures_to_delete.extend(
405 self.textures_dropped
406 .drain(..)
407 .map(|texture| (texture, sync_point.clone())),
408 );
409 self.belt.flush(sync_point);
410 }
411}