1use crate::{RendererError, RendererResult};
7use dear_imgui_rs::{TextureData, TextureFormat as ImGuiTextureFormat, TextureId, TextureStatus};
8use std::collections::HashMap;
9use wgpu::*;
10
11#[derive(Debug, Clone)]
17pub enum TextureUpdateResult {
18 Created { texture_id: TextureId },
20 Updated,
22 Destroyed,
24 Failed,
26 NoAction,
28}
29
30impl TextureUpdateResult {
31 pub fn apply_to(self, texture_data: &mut TextureData) {
36 match self {
37 TextureUpdateResult::Created { texture_id } => {
38 texture_data.set_tex_id(texture_id);
39 texture_data.set_status(TextureStatus::OK);
40 }
41 TextureUpdateResult::Updated => {
42 texture_data.set_status(TextureStatus::OK);
43 }
44 TextureUpdateResult::Destroyed => {
45 texture_data.set_status(TextureStatus::Destroyed);
46 }
47 TextureUpdateResult::Failed => {
48 texture_data.set_status(TextureStatus::Destroyed);
49 }
50 TextureUpdateResult::NoAction => {
51 }
53 }
54 }
55}
56
57#[derive(Debug)]
61pub struct WgpuTexture {
62 pub texture: Texture,
64 pub texture_view: TextureView,
66}
67
68impl WgpuTexture {
69 pub fn new(texture: Texture, texture_view: TextureView) -> Self {
71 Self {
72 texture,
73 texture_view,
74 }
75 }
76
77 pub fn view(&self) -> &TextureView {
79 &self.texture_view
80 }
81
82 pub fn texture(&self) -> &Texture {
84 &self.texture
85 }
86}
87
88#[derive(Debug, Default)]
93pub struct WgpuTextureManager {
94 textures: HashMap<u64, WgpuTexture>,
96 next_id: u64,
98}
99
100impl WgpuTextureManager {
101 fn convert_subrect_to_rgba(
103 texture_data: &TextureData,
104 rect: dear_imgui_rs::texture::TextureRect,
105 ) -> Option<Vec<u8>> {
106 let pixels = texture_data.pixels()?;
107 let tex_w = texture_data.width() as usize;
108 let tex_h = texture_data.height() as usize;
109 if tex_w == 0 || tex_h == 0 {
110 return None;
111 }
112
113 let bpp = texture_data.bytes_per_pixel() as usize;
114 let (rx, ry, rw, rh) = (
115 rect.x as usize,
116 rect.y as usize,
117 rect.w as usize,
118 rect.h as usize,
119 );
120 if rw == 0 || rh == 0 || rx >= tex_w || ry >= tex_h {
121 return None;
122 }
123
124 let rw = rw.min(tex_w.saturating_sub(rx));
126 let rh = rh.min(tex_h.saturating_sub(ry));
127
128 let mut out = vec![0u8; rw * rh * 4];
129 match texture_data.format() {
130 ImGuiTextureFormat::RGBA32 => {
131 for row in 0..rh {
132 let src_off = ((ry + row) * tex_w + rx) * bpp;
133 let dst_off = row * rw * 4;
134 out[dst_off..dst_off + rw * 4]
136 .copy_from_slice(&pixels[src_off..src_off + rw * 4]);
137 }
138 }
139 ImGuiTextureFormat::Alpha8 => {
140 for row in 0..rh {
141 let src_off = ((ry + row) * tex_w + rx) * bpp; let dst_off = row * rw * 4;
143 for i in 0..rw {
144 let a = pixels[src_off + i];
145 let dst = &mut out[dst_off + i * 4..dst_off + i * 4 + 4];
146 dst.copy_from_slice(&[255, 255, 255, a]);
147 }
148 }
149 }
150 }
151 Some(out)
152 }
153
154 fn apply_subrect_updates(
157 &mut self,
158 queue: &Queue,
159 texture_data: &TextureData,
160 texture_id: u64,
161 ) -> RendererResult<bool> {
162 let wgpu_tex = match self.textures.get(&texture_id) {
163 Some(t) => t,
164 None => return Ok(false),
165 };
166
167 let mut rects: Vec<dear_imgui_rs::texture::TextureRect> = texture_data.updates().collect();
170 if rects.is_empty() {
171 let r = texture_data.update_rect();
172 if r.w > 0 && r.h > 0 {
173 rects.push(r);
174 }
175 }
176 if rects.is_empty() {
177 return Ok(false);
178 }
179
180 for rect in rects {
182 if let Some(tight_rgba) = Self::convert_subrect_to_rgba(texture_data, rect) {
183 let width = rect.w as u32;
184 let height = rect.h as u32;
185 let bpp = 4u32;
186 let unpadded_bytes_per_row = width * bpp;
187 let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT; let padded_bytes_per_row = unpadded_bytes_per_row.div_ceil(align) * align;
189
190 if padded_bytes_per_row == unpadded_bytes_per_row {
191 queue.write_texture(
193 wgpu::TexelCopyTextureInfo {
194 texture: wgpu_tex.texture(),
195 mip_level: 0,
196 origin: wgpu::Origin3d {
197 x: rect.x as u32,
198 y: rect.y as u32,
199 z: 0,
200 },
201 aspect: wgpu::TextureAspect::All,
202 },
203 &tight_rgba,
204 wgpu::TexelCopyBufferLayout {
205 offset: 0,
206 bytes_per_row: Some(unpadded_bytes_per_row),
207 rows_per_image: Some(height),
208 },
209 wgpu::Extent3d {
210 width,
211 height,
212 depth_or_array_layers: 1,
213 },
214 );
215 } else {
216 let mut padded = vec![0u8; (padded_bytes_per_row * height) as usize];
218 for row in 0..height as usize {
219 let src_off = row * (unpadded_bytes_per_row as usize);
220 let dst_off = row * (padded_bytes_per_row as usize);
221 padded[dst_off..dst_off + (unpadded_bytes_per_row as usize)]
222 .copy_from_slice(
223 &tight_rgba[src_off..src_off + (unpadded_bytes_per_row as usize)],
224 );
225 }
226 queue.write_texture(
227 wgpu::TexelCopyTextureInfo {
228 texture: wgpu_tex.texture(),
229 mip_level: 0,
230 origin: wgpu::Origin3d {
231 x: rect.x as u32,
232 y: rect.y as u32,
233 z: 0,
234 },
235 aspect: wgpu::TextureAspect::All,
236 },
237 &padded,
238 wgpu::TexelCopyBufferLayout {
239 offset: 0,
240 bytes_per_row: Some(padded_bytes_per_row),
241 rows_per_image: Some(height),
242 },
243 wgpu::Extent3d {
244 width,
245 height,
246 depth_or_array_layers: 1,
247 },
248 );
249 }
250 if cfg!(debug_assertions) {
251 tracing::debug!(
252 target: "dear-imgui-wgpu",
253 "[dear-imgui-wgpu][debug] Updated texture id={} subrect x={} y={} w={} h={}",
254 texture_id, rect.x, rect.y, rect.w, rect.h
255 );
256 }
257 } else {
258 if cfg!(debug_assertions) {
260 tracing::debug!(
261 target: "dear-imgui-wgpu",
262 "[dear-imgui-wgpu][debug] Skipped subrect update: no pixels available"
263 );
264 }
265 return Ok(false);
266 }
267 }
268
269 Ok(true)
270 }
271 pub fn new() -> Self {
273 Self {
274 textures: HashMap::new(),
275 next_id: 1, }
277 }
278
279 pub fn register_texture(&mut self, texture: WgpuTexture) -> u64 {
281 let id = self.next_id;
282 self.next_id += 1;
283 self.textures.insert(id, texture);
284 id
285 }
286
287 pub fn get_texture(&self, id: u64) -> Option<&WgpuTexture> {
289 self.textures.get(&id)
290 }
291
292 pub fn remove_texture(&mut self, id: u64) -> Option<WgpuTexture> {
294 self.textures.remove(&id)
295 }
296
297 pub fn contains_texture(&self, id: u64) -> bool {
299 self.textures.contains_key(&id)
300 }
301
302 pub fn insert_texture_with_id(&mut self, id: u64, texture: WgpuTexture) {
304 self.textures.insert(id, texture);
305 if id >= self.next_id {
307 self.next_id = id + 1;
308 }
309 }
310
311 pub fn destroy_texture_by_id(&mut self, id: u64) {
313 self.remove_texture(id);
314 }
315
316 pub fn update_texture_from_data_with_id(
318 &mut self,
319 device: &Device,
320 queue: &Queue,
321 texture_data: &TextureData,
322 texture_id: u64,
323 ) -> RendererResult<()> {
324 if self.contains_texture(texture_id) {
327 self.remove_texture(texture_id);
329
330 let new_texture_id = self.create_texture_from_data(device, queue, texture_data)?;
332
333 if new_texture_id != texture_id
335 && let Some(texture) = self.remove_texture(new_texture_id)
336 {
337 self.insert_texture_with_id(texture_id, texture);
338 }
339
340 Ok(())
341 } else {
342 Err(RendererError::InvalidTextureId(texture_id))
343 }
344 }
345
346 pub fn texture_count(&self) -> usize {
348 self.textures.len()
349 }
350
351 pub fn clear(&mut self) {
353 self.textures.clear();
354 self.next_id = 1;
355 }
356}
357
358impl WgpuTextureManager {
360 pub fn create_texture_from_data(
362 &mut self,
363 device: &Device,
364 queue: &Queue,
365 texture_data: &TextureData,
366 ) -> RendererResult<u64> {
367 let width = texture_data.width() as u32;
368 let height = texture_data.height() as u32;
369 let format = texture_data.format();
370
371 let pixels = texture_data
372 .pixels()
373 .ok_or_else(|| RendererError::BadTexture("No pixel data available".to_string()))?;
374
375 let (wgpu_format, converted_data, _bytes_per_pixel) = match format {
378 ImGuiTextureFormat::RGBA32 => {
379 if pixels.len() != (width * height * 4) as usize {
381 return Err(RendererError::BadTexture(format!(
382 "RGBA32 texture data size mismatch: expected {} bytes, got {}",
383 width * height * 4,
384 pixels.len()
385 )));
386 }
387 (TextureFormat::Rgba8Unorm, pixels.to_vec(), 4u32)
388 }
389 ImGuiTextureFormat::Alpha8 => {
390 if pixels.len() != (width * height) as usize {
393 return Err(RendererError::BadTexture(format!(
394 "Alpha8 texture data size mismatch: expected {} bytes, got {}",
395 width * height,
396 pixels.len()
397 )));
398 }
399 let mut rgba_data = Vec::with_capacity(pixels.len() * 4);
400 for &alpha in pixels {
401 rgba_data.extend_from_slice(&[255, 255, 255, alpha]); }
403 (TextureFormat::Rgba8Unorm, rgba_data, 4u32)
404 }
405 };
406
407 if cfg!(debug_assertions) {
409 tracing::debug!(
410 target: "dear-imgui-wgpu",
411 "[dear-imgui-wgpu][debug] Create texture: {}x{} format={:?}",
412 width, height, format
413 );
414 }
415 let texture = device.create_texture(&TextureDescriptor {
416 label: Some("Dear ImGui Texture"),
417 size: Extent3d {
418 width,
419 height,
420 depth_or_array_layers: 1,
421 },
422 mip_level_count: 1,
423 sample_count: 1,
424 dimension: TextureDimension::D2,
425 format: wgpu_format,
426 usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST,
427 view_formats: &[],
428 });
429
430 let expected_size = (width * height * 4) as usize; if converted_data.len() != expected_size {
433 return Err(RendererError::BadTexture(format!(
434 "Converted texture data size mismatch: expected {} bytes, got {}",
435 expected_size,
436 converted_data.len()
437 )));
438 }
439
440 let bpp = 4u32;
443 let unpadded_bytes_per_row = width * bpp;
444 let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT; let padded_bytes_per_row = unpadded_bytes_per_row.div_ceil(align) * align;
446 if padded_bytes_per_row == unpadded_bytes_per_row {
447 queue.write_texture(
449 wgpu::TexelCopyTextureInfo {
450 texture: &texture,
451 mip_level: 0,
452 origin: wgpu::Origin3d::ZERO,
453 aspect: wgpu::TextureAspect::All,
454 },
455 &converted_data,
456 wgpu::TexelCopyBufferLayout {
457 offset: 0,
458 bytes_per_row: Some(unpadded_bytes_per_row),
459 rows_per_image: Some(height),
460 },
461 Extent3d {
462 width,
463 height,
464 depth_or_array_layers: 1,
465 },
466 );
467 } else {
468 let mut padded: Vec<u8> = vec![0; (padded_bytes_per_row * height) as usize];
470 for row in 0..height as usize {
471 let src_off = row * (unpadded_bytes_per_row as usize);
472 let dst_off = row * (padded_bytes_per_row as usize);
473 padded[dst_off..dst_off + (unpadded_bytes_per_row as usize)].copy_from_slice(
474 &converted_data[src_off..src_off + (unpadded_bytes_per_row as usize)],
475 );
476 }
477 queue.write_texture(
478 wgpu::TexelCopyTextureInfo {
479 texture: &texture,
480 mip_level: 0,
481 origin: wgpu::Origin3d::ZERO,
482 aspect: wgpu::TextureAspect::All,
483 },
484 &padded,
485 wgpu::TexelCopyBufferLayout {
486 offset: 0,
487 bytes_per_row: Some(padded_bytes_per_row),
488 rows_per_image: Some(height),
489 },
490 Extent3d {
491 width,
492 height,
493 depth_or_array_layers: 1,
494 },
495 );
496 if cfg!(debug_assertions) {
497 tracing::debug!(
498 target: "dear-imgui-wgpu",
499 "[dear-imgui-wgpu][debug] Upload texture with padded row pitch: unpadded={} padded={}",
500 unpadded_bytes_per_row, padded_bytes_per_row
501 );
502 }
503 }
504
505 let texture_view = texture.create_view(&TextureViewDescriptor::default());
507
508 let wgpu_texture = WgpuTexture::new(texture, texture_view);
510
511 let texture_id = self.register_texture(wgpu_texture);
513 if cfg!(debug_assertions) {
514 tracing::debug!(
515 target: "dear-imgui-wgpu",
516 "[dear-imgui-wgpu][debug] Texture registered: id={}",
517 texture_id
518 );
519 }
520 Ok(texture_id)
521 }
522
523 pub fn update_texture_from_data(
525 &mut self,
526 device: &Device,
527 queue: &Queue,
528 texture_data: &TextureData,
529 ) -> RendererResult<()> {
530 let texture_id = texture_data.tex_id().id();
531
532 if self.contains_texture(texture_id) {
536 if self.apply_subrect_updates(queue, texture_data, texture_id)? {
538 return Ok(());
539 }
540
541 self.remove_texture(texture_id);
543 let new_texture_id = self.create_texture_from_data(device, queue, texture_data)?;
544 if new_texture_id != texture_id
545 && let Some(texture) = self.remove_texture(new_texture_id)
546 {
547 self.insert_texture_with_id(texture_id, texture);
548 }
549 } else {
550 let new_texture_id = self.create_texture_from_data(device, queue, texture_data)?;
552 if new_texture_id != texture_id
553 && let Some(texture) = self.remove_texture(new_texture_id)
554 {
555 self.insert_texture_with_id(texture_id, texture);
556 }
557 }
558
559 Ok(())
560 }
561
562 pub fn destroy_texture(&mut self, texture_id: TextureId) {
564 let texture_id_u64 = texture_id.id();
565 self.remove_texture(texture_id_u64);
566 }
568
569 pub fn handle_texture_updates(
577 &mut self,
578 draw_data: &dear_imgui_rs::render::DrawData,
579 device: &Device,
580 queue: &Queue,
581 ) {
582 for texture_data in draw_data.textures() {
583 let status = texture_data.status();
584 let current_tex_id = texture_data.tex_id().id();
585
586 match status {
587 TextureStatus::WantCreate => {
588 match self.create_texture_from_data(device, queue, texture_data) {
592 Ok(wgpu_texture_id) => {
593 let new_texture_id = dear_imgui_rs::TextureId::from(wgpu_texture_id);
598
599 texture_data.set_tex_id(new_texture_id);
600
601 texture_data.set_status(TextureStatus::OK);
603 }
604 Err(e) => {
605 println!(
606 "Failed to create texture for ID: {}, error: {}",
607 current_tex_id, e
608 );
609 }
610 }
611 }
612 TextureStatus::WantUpdates => {
613 let imgui_tex_id = texture_data.tex_id();
614 let internal_id = imgui_tex_id.id();
615
616 if internal_id == 0 || !self.contains_texture(internal_id) {
620 match self.create_texture_from_data(device, queue, texture_data) {
621 Ok(new_id) => {
622 texture_data.set_tex_id(dear_imgui_rs::TextureId::from(new_id));
623 texture_data.set_status(TextureStatus::OK);
624 }
625 Err(_e) => {
626 texture_data.set_status(TextureStatus::Destroyed);
628 }
629 }
630 } else {
631 if self
633 .apply_subrect_updates(queue, texture_data, internal_id)
634 .unwrap_or(false)
635 {
636 texture_data.set_status(TextureStatus::OK);
637 } else if self
638 .update_texture_from_data_with_id(
639 device,
640 queue,
641 texture_data,
642 internal_id,
643 )
644 .is_err()
645 {
646 texture_data.set_status(TextureStatus::Destroyed);
648 } else {
649 texture_data.set_status(TextureStatus::OK);
650 }
651 }
652 }
653 TextureStatus::WantDestroy => {
654 let mut can_destroy = true;
656 unsafe {
657 let raw = texture_data.as_raw();
658 if !raw.is_null() {
659 #[allow(unused_unsafe)]
661 {
662 can_destroy = (*raw).UnusedFrames > 0;
665 }
666 }
667 }
668 if can_destroy {
669 let imgui_tex_id = texture_data.tex_id();
670 let internal_id = imgui_tex_id.id();
671 self.remove_texture(internal_id);
673 texture_data.set_status(TextureStatus::Destroyed);
674 }
675 }
676 TextureStatus::OK | TextureStatus::Destroyed => {
677 }
679 }
680 }
681 }
682
683 pub fn update_single_texture(
708 &mut self,
709 texture_data: &dear_imgui_rs::TextureData,
710 device: &Device,
711 queue: &Queue,
712 ) -> Result<TextureUpdateResult, String> {
713 match texture_data.status() {
714 TextureStatus::WantCreate => {
715 match self.create_texture_from_data(device, queue, texture_data) {
716 Ok(texture_id) => Ok(TextureUpdateResult::Created {
717 texture_id: TextureId::from(texture_id),
718 }),
719 Err(e) => Err(format!("Failed to create texture: {}", e)),
720 }
721 }
722 TextureStatus::WantUpdates => {
723 let internal_id = texture_data.tex_id().id();
724 if internal_id == 0 || !self.contains_texture(internal_id) {
725 match self.create_texture_from_data(device, queue, texture_data) {
727 Ok(texture_id) => Ok(TextureUpdateResult::Created {
728 texture_id: TextureId::from(texture_id),
729 }),
730 Err(e) => Err(format!("Failed to create texture: {}", e)),
731 }
732 } else {
733 match self.update_texture_from_data_with_id(
734 device,
735 queue,
736 texture_data,
737 internal_id,
738 ) {
739 Ok(_) => Ok(TextureUpdateResult::Updated),
740 Err(_e) => Ok(TextureUpdateResult::Failed),
741 }
742 }
743 }
744 TextureStatus::WantDestroy => {
745 self.destroy_texture(texture_data.tex_id());
746 Ok(TextureUpdateResult::Destroyed)
747 }
748 TextureStatus::OK | TextureStatus::Destroyed => Ok(TextureUpdateResult::NoAction),
749 }
750 }
751}