1use super::*;
2
3impl WgpuTextureManager {
4 pub(super) fn convert_subrect_to_rgba(
6 texture_data: &TextureData,
7 rect: dear_imgui_rs::texture::TextureRect,
8 ) -> Option<Vec<u8>> {
9 let pixels = texture_data.pixels()?;
10 let tex_w = usize::try_from(texture_data.width()).ok()?;
11 let tex_h = usize::try_from(texture_data.height()).ok()?;
12 if tex_w == 0 || tex_h == 0 {
13 return None;
14 }
15
16 let bpp = texture_data.bytes_per_pixel();
17 let (rx, ry, rw, rh) = (
18 rect.x as usize,
19 rect.y as usize,
20 rect.w as usize,
21 rect.h as usize,
22 );
23 if rw == 0 || rh == 0 || rx >= tex_w || ry >= tex_h {
24 return None;
25 }
26
27 let rw = rw.min(tex_w.saturating_sub(rx));
29 let rh = rh.min(tex_h.saturating_sub(ry));
30
31 let mut out = vec![0u8; rw.checked_mul(rh)?.checked_mul(4)?];
32 match texture_data.format() {
33 ImGuiTextureFormat::RGBA32 => {
34 for row in 0..rh {
35 let src_off = ((ry + row) * tex_w + rx) * bpp;
36 let dst_off = row * rw * 4;
37 out[dst_off..dst_off + rw * 4]
39 .copy_from_slice(&pixels[src_off..src_off + rw * 4]);
40 }
41 }
42 ImGuiTextureFormat::Alpha8 => {
43 for row in 0..rh {
44 let src_off = ((ry + row) * tex_w + rx) * bpp; let dst_off = row * rw * 4;
46 for i in 0..rw {
47 let a = pixels[src_off + i];
48 let dst = &mut out[dst_off + i * 4..dst_off + i * 4 + 4];
49 dst.copy_from_slice(&[255, 255, 255, a]);
50 }
51 }
52 }
53 }
54 Some(out)
55 }
56
57 pub(super) fn apply_subrect_updates(
60 &mut self,
61 queue: &Queue,
62 texture_data: &TextureData,
63 texture_id: TextureId,
64 ) -> RendererResult<bool> {
65 let wgpu_tex = match self.textures.get(&texture_id) {
66 Some(t) => t,
67 None => return Ok(false),
68 };
69
70 let mut rects: Vec<dear_imgui_rs::texture::TextureRect> = texture_data.updates().collect();
73 if rects.is_empty() {
74 let r = texture_data.update_rect();
75 if r.w > 0 && r.h > 0 {
76 rects.push(r);
77 }
78 }
79 if rects.is_empty() {
80 return Ok(false);
81 }
82
83 for rect in rects {
85 if let Some(tight_rgba) = Self::convert_subrect_to_rgba(texture_data, rect) {
86 let width = rect.w as u32;
87 let height = rect.h as u32;
88 let bpp = 4u32;
89 let unpadded_bytes_per_row = width * bpp;
90 let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT; let padded_bytes_per_row = unpadded_bytes_per_row.div_ceil(align) * align;
92
93 if padded_bytes_per_row == unpadded_bytes_per_row {
94 queue.write_texture(
96 wgpu::TexelCopyTextureInfo {
97 texture: wgpu_tex.texture(),
98 mip_level: 0,
99 origin: wgpu::Origin3d {
100 x: rect.x as u32,
101 y: rect.y as u32,
102 z: 0,
103 },
104 aspect: wgpu::TextureAspect::All,
105 },
106 &tight_rgba,
107 wgpu::TexelCopyBufferLayout {
108 offset: 0,
109 bytes_per_row: Some(unpadded_bytes_per_row),
110 rows_per_image: Some(height),
111 },
112 wgpu::Extent3d {
113 width,
114 height,
115 depth_or_array_layers: 1,
116 },
117 );
118 } else {
119 let mut padded = vec![0u8; (padded_bytes_per_row * height) as usize];
121 for row in 0..height as usize {
122 let src_off = row * (unpadded_bytes_per_row as usize);
123 let dst_off = row * (padded_bytes_per_row as usize);
124 padded[dst_off..dst_off + (unpadded_bytes_per_row as usize)]
125 .copy_from_slice(
126 &tight_rgba[src_off..src_off + (unpadded_bytes_per_row as usize)],
127 );
128 }
129 queue.write_texture(
130 wgpu::TexelCopyTextureInfo {
131 texture: wgpu_tex.texture(),
132 mip_level: 0,
133 origin: wgpu::Origin3d {
134 x: rect.x as u32,
135 y: rect.y as u32,
136 z: 0,
137 },
138 aspect: wgpu::TextureAspect::All,
139 },
140 &padded,
141 wgpu::TexelCopyBufferLayout {
142 offset: 0,
143 bytes_per_row: Some(padded_bytes_per_row),
144 rows_per_image: Some(height),
145 },
146 wgpu::Extent3d {
147 width,
148 height,
149 depth_or_array_layers: 1,
150 },
151 );
152 }
153 if cfg!(debug_assertions) {
154 tracing::debug!(
155 target: "dear-imgui-wgpu",
156 "[dear-imgui-wgpu][debug] Updated texture id={} subrect x={} y={} w={} h={}",
157 texture_id.id(), rect.x, rect.y, rect.w, rect.h
158 );
159 }
160 } else {
161 if cfg!(debug_assertions) {
163 tracing::debug!(
164 target: "dear-imgui-wgpu",
165 "[dear-imgui-wgpu][debug] Skipped subrect update: no pixels available"
166 );
167 }
168 return Ok(false);
169 }
170 }
171
172 Ok(true)
173 }
174
175 pub fn update_texture_from_data_with_id(
177 &mut self,
178 device: &Device,
179 queue: &Queue,
180 texture_data: &TextureData,
181 texture_id: TextureId,
182 ) -> RendererResult<()> {
183 if self.contains_texture(texture_id) {
186 let new_texture_id = self.create_texture_from_data(device, queue, texture_data)?;
187
188 if new_texture_id != texture_id
190 && let Some(texture) = self.remove_texture(new_texture_id)
191 {
192 self.remove_texture(texture_id);
193 self.insert_texture_with_id(texture_id, texture);
194 }
195
196 Ok(())
197 } else {
198 Err(RendererError::InvalidTextureId(texture_id))
199 }
200 }
201
202 pub fn create_texture_from_data(
205 &mut self,
206 device: &Device,
207 queue: &Queue,
208 texture_data: &TextureData,
209 ) -> RendererResult<TextureId> {
210 let width = texture_data.width();
211 let height = texture_data.height();
212 let format = texture_data.format();
213
214 let pixels = texture_data
215 .pixels()
216 .ok_or_else(|| RendererError::BadTexture("No pixel data available".to_string()))?;
217
218 let (wgpu_format, converted_data, _bytes_per_pixel) = match format {
221 ImGuiTextureFormat::RGBA32 => {
222 let expected_len = usize::try_from(width)
224 .ok()
225 .and_then(|w| usize::try_from(height).ok().and_then(|h| w.checked_mul(h)))
226 .and_then(|px| px.checked_mul(4))
227 .ok_or_else(|| {
228 RendererError::BadTexture("RGBA32 texture size overflow".to_string())
229 })?;
230 if pixels.len() != expected_len {
231 return Err(RendererError::BadTexture(format!(
232 "RGBA32 texture data size mismatch: expected {} bytes, got {}",
233 expected_len,
234 pixels.len()
235 )));
236 }
237 (TextureFormat::Rgba8Unorm, pixels.to_vec(), 4u32)
238 }
239 ImGuiTextureFormat::Alpha8 => {
240 let expected_len = usize::try_from(width)
243 .ok()
244 .and_then(|w| usize::try_from(height).ok().and_then(|h| w.checked_mul(h)))
245 .ok_or_else(|| {
246 RendererError::BadTexture("Alpha8 texture size overflow".to_string())
247 })?;
248 if pixels.len() != expected_len {
249 return Err(RendererError::BadTexture(format!(
250 "Alpha8 texture data size mismatch: expected {} bytes, got {}",
251 expected_len,
252 pixels.len()
253 )));
254 }
255 let mut rgba_data = Vec::with_capacity(pixels.len() * 4);
256 for &alpha in pixels {
257 rgba_data.extend_from_slice(&[255, 255, 255, alpha]); }
259 (TextureFormat::Rgba8Unorm, rgba_data, 4u32)
260 }
261 };
262
263 if cfg!(debug_assertions) {
265 tracing::debug!(
266 target: "dear-imgui-wgpu",
267 "[dear-imgui-wgpu][debug] Create texture: {}x{} format={:?}",
268 width, height, format
269 );
270 }
271 let texture = device.create_texture(&TextureDescriptor {
272 label: Some("Dear ImGui Texture"),
273 size: Extent3d {
274 width,
275 height,
276 depth_or_array_layers: 1,
277 },
278 mip_level_count: 1,
279 sample_count: 1,
280 dimension: TextureDimension::D2,
281 format: wgpu_format,
282 usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST,
283 view_formats: &[],
284 });
285
286 let expected_size = usize::try_from(width)
288 .ok()
289 .and_then(|w| usize::try_from(height).ok().and_then(|h| w.checked_mul(h)))
290 .and_then(|px| px.checked_mul(4))
291 .ok_or_else(|| {
292 RendererError::BadTexture("Converted texture size overflow".to_string())
293 })?; if converted_data.len() != expected_size {
295 return Err(RendererError::BadTexture(format!(
296 "Converted texture data size mismatch: expected {} bytes, got {}",
297 expected_size,
298 converted_data.len()
299 )));
300 }
301
302 let bpp = 4u32;
305 let unpadded_bytes_per_row = width * bpp;
306 let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT; let padded_bytes_per_row = unpadded_bytes_per_row.div_ceil(align) * align;
308 if padded_bytes_per_row == unpadded_bytes_per_row {
309 queue.write_texture(
311 wgpu::TexelCopyTextureInfo {
312 texture: &texture,
313 mip_level: 0,
314 origin: wgpu::Origin3d::ZERO,
315 aspect: wgpu::TextureAspect::All,
316 },
317 &converted_data,
318 wgpu::TexelCopyBufferLayout {
319 offset: 0,
320 bytes_per_row: Some(unpadded_bytes_per_row),
321 rows_per_image: Some(height),
322 },
323 Extent3d {
324 width,
325 height,
326 depth_or_array_layers: 1,
327 },
328 );
329 } else {
330 let mut padded: Vec<u8> = vec![0; (padded_bytes_per_row * height) as usize];
332 for row in 0..height as usize {
333 let src_off = row * (unpadded_bytes_per_row as usize);
334 let dst_off = row * (padded_bytes_per_row as usize);
335 padded[dst_off..dst_off + (unpadded_bytes_per_row as usize)].copy_from_slice(
336 &converted_data[src_off..src_off + (unpadded_bytes_per_row as usize)],
337 );
338 }
339 queue.write_texture(
340 wgpu::TexelCopyTextureInfo {
341 texture: &texture,
342 mip_level: 0,
343 origin: wgpu::Origin3d::ZERO,
344 aspect: wgpu::TextureAspect::All,
345 },
346 &padded,
347 wgpu::TexelCopyBufferLayout {
348 offset: 0,
349 bytes_per_row: Some(padded_bytes_per_row),
350 rows_per_image: Some(height),
351 },
352 Extent3d {
353 width,
354 height,
355 depth_or_array_layers: 1,
356 },
357 );
358 if cfg!(debug_assertions) {
359 tracing::debug!(
360 target: "dear-imgui-wgpu",
361 "[dear-imgui-wgpu][debug] Upload texture with padded row pitch: unpadded={} padded={}",
362 unpadded_bytes_per_row, padded_bytes_per_row
363 );
364 }
365 }
366
367 let texture_view = texture.create_view(&TextureViewDescriptor::default());
369
370 let wgpu_texture = WgpuTexture::new(texture, texture_view);
372
373 let texture_id = self.register_texture(wgpu_texture);
375 if cfg!(debug_assertions) {
376 tracing::debug!(
377 target: "dear-imgui-wgpu",
378 "[dear-imgui-wgpu][debug] Texture registered: id={}",
379 texture_id.id()
380 );
381 }
382 Ok(texture_id)
383 }
384
385 pub fn update_texture_from_data(
387 &mut self,
388 device: &Device,
389 queue: &Queue,
390 texture_data: &TextureData,
391 ) -> RendererResult<()> {
392 let texture_id = texture_data.tex_id();
393
394 if self.contains_texture(texture_id) {
398 if self.apply_subrect_updates(queue, texture_data, texture_id)? {
400 return Ok(());
401 }
402
403 let new_texture_id = self.create_texture_from_data(device, queue, texture_data)?;
404 if new_texture_id != texture_id
405 && let Some(texture) = self.remove_texture(new_texture_id)
406 {
407 self.remove_texture(texture_id);
408 self.insert_texture_with_id(texture_id, texture);
409 }
410 } else {
411 let new_texture_id = self.create_texture_from_data(device, queue, texture_data)?;
413 if new_texture_id != texture_id
414 && let Some(texture) = self.remove_texture(new_texture_id)
415 {
416 self.insert_texture_with_id(texture_id, texture);
417 }
418 }
419
420 Ok(())
421 }
422
423 pub fn update_single_texture(
448 &mut self,
449 texture_data: &dear_imgui_rs::TextureData,
450 device: &Device,
451 queue: &Queue,
452 ) -> RendererResult<TextureUpdateResult> {
453 match texture_data.status() {
454 TextureStatus::WantCreate => {
455 let texture_id = self.create_texture_from_data(device, queue, texture_data)?;
456 Ok(TextureUpdateResult::Created { texture_id })
457 }
458 TextureStatus::WantUpdates => {
459 let internal_id = texture_data.tex_id();
460 if internal_id.is_null() || !self.contains_texture(internal_id) {
461 let texture_id = self.create_texture_from_data(device, queue, texture_data)?;
463 Ok(TextureUpdateResult::Created { texture_id })
464 } else {
465 match self.update_texture_from_data_with_id(
466 device,
467 queue,
468 texture_data,
469 internal_id,
470 ) {
471 Ok(_) => Ok(TextureUpdateResult::Updated),
472 Err(e) => Err(e),
473 }
474 }
475 }
476 TextureStatus::WantDestroy => {
477 self.destroy_texture(texture_data.tex_id());
478 Ok(TextureUpdateResult::Destroyed)
479 }
480 TextureStatus::OK | TextureStatus::Destroyed => Ok(TextureUpdateResult::NoAction),
481 }
482 }
483}