textures.rs (6281B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 use super::{Gui, Document, DocumentKind}; 6 use webrender_api::debugger::DebuggerTextureContent; 7 use webrender_api::TextureCacheCategory; 8 9 pub fn texture_viewer_ui(ui: &mut egui::Ui, image: &DebuggerTextureContent, handle: &egui::TextureHandle) { 10 ui.label(format!("Size: {}x{}, Format {:?}", image.width, image.height, image.format)); 11 12 egui::ScrollArea::both().show(ui, |ui| { 13 ui.image(egui::ImageSource::Texture( 14 egui::load::SizedTexture::new( 15 handle.id(), 16 egui::vec2(image.width as f32, image.height as f32) 17 ) 18 )); 19 }); 20 } 21 22 pub fn texture_list_ui(app: &mut Gui, ui: &mut egui::Ui) { 23 texture_list_inner(app, ui, TextureCacheCategory::Atlas); 24 texture_list_inner(app, ui, TextureCacheCategory::Standalone); 25 texture_list_inner(app, ui, TextureCacheCategory::RenderTarget); 26 texture_list_inner(app, ui, TextureCacheCategory::PictureTile); 27 } 28 29 fn texture_category_query(category: TextureCacheCategory) -> &'static str { 30 match category { 31 TextureCacheCategory::Atlas => "atlas-textures", 32 TextureCacheCategory::Standalone => "standalone-textures", 33 TextureCacheCategory::PictureTile => "tile-textures", 34 TextureCacheCategory::RenderTarget => "target-textures", 35 } 36 } 37 38 fn texture_category_label(category: TextureCacheCategory) -> &'static str { 39 match category { 40 TextureCacheCategory::Atlas => "Atlases", 41 TextureCacheCategory::Standalone => "Standalone", 42 TextureCacheCategory::PictureTile => "Tiles", 43 TextureCacheCategory::RenderTarget => "Render targets", 44 } 45 } 46 47 fn texture_list_inner(app: &mut Gui, ui: &mut egui::Ui, category: TextureCacheCategory) { 48 let width = ui.available_width(); 49 50 let cursor = ui.cursor().min; 51 let refresh_rect = egui::Rect { 52 min: egui::Pos2::new(cursor.x + width - 20.0, cursor.y), 53 max: egui::Pos2::new(cursor.x + width, cursor.y + 20.0), 54 }; 55 let refresh_button = egui::widgets::Button::new("↓"); 56 if ui.place(refresh_rect, refresh_button).clicked() { 57 let query_result = app.net.get_with_query( 58 "query", &[("type", texture_category_query(category))] 59 ); 60 61 if let Ok(Some(msg)) = query_result { 62 app.data_model.preview_doc_index = None; 63 app.data_model.documents.retain(|doc| !doc_is_texture(doc, category)); 64 65 // Note: deserializing the textures takes a long time in 66 // debug builds. 67 let new_textures = serde_json::from_str(msg.as_str()).unwrap(); 68 add_textures(app, new_textures); 69 } 70 } 71 72 egui::CollapsingHeader::new(texture_category_label(category)).default_open(true).show(ui, |ui| { 73 egui::ScrollArea::vertical().show(ui, |ui| { 74 for (i, doc) in app.data_model.documents.iter().enumerate() { 75 if !doc_is_texture(doc, category) { 76 continue; 77 } 78 79 let item = egui::Button::selectable( 80 app.data_model.preview_doc_index == Some(i), 81 &doc.title, 82 ).min_size(egui::vec2(width - 20.0, 20.0)); 83 84 if ui.add(item).clicked() { 85 app.data_model.preview_doc_index = Some(i); 86 } 87 } 88 }); 89 }); 90 } 91 92 pub fn add_textures( 93 app: &mut Gui, 94 mut textures: Vec<DebuggerTextureContent>, 95 ) { 96 textures.sort_by(|a, b| a.name.cmp(&b.name)); 97 for texture in textures { 98 app.data_model.documents.push(Document { 99 title: texture.name.clone(), 100 kind: DocumentKind::Texture { 101 content: texture, 102 handle: None, 103 } 104 }); 105 } 106 } 107 108 /// Perform uploads if need be. Happens earlier in the update because it needs 109 /// access to the egui context. 110 pub fn prepare(app: &mut super::Gui, ctx: &egui::Context) { 111 if let Some(idx) = app.data_model.preview_doc_index { 112 if idx >= app.data_model.documents.len() { 113 return; 114 } 115 116 let DocumentKind::Texture { content, handle } = &mut app.data_model.documents[idx].kind else { 117 return; 118 }; 119 120 if handle.is_some() { 121 return; 122 } 123 124 if let Some(gpu_texture) = upload_texture(ctx, content) { 125 *handle = Some(gpu_texture) 126 } 127 } 128 } 129 130 fn upload_texture( 131 ctx: &egui::Context, 132 texture: &DebuggerTextureContent, 133 ) -> Option<egui::TextureHandle> { 134 use webrender_api::ImageFormat; 135 136 let color_image = match texture.format { 137 ImageFormat::RGBA8 => { 138 egui::ColorImage::from_rgba_unmultiplied( 139 [texture.width as usize, texture.height as usize], 140 &texture.data, 141 ) 142 } 143 ImageFormat::BGRA8 => { 144 // Convert BGRA to RGBA 145 let mut rgba_data = texture.data.clone(); 146 for pixel in rgba_data.chunks_exact_mut(4) { 147 pixel.swap(0, 2); // Swap B and R 148 } 149 egui::ColorImage::from_rgba_unmultiplied( 150 [texture.width as usize, texture.height as usize], 151 &rgba_data, 152 ) 153 } 154 ImageFormat::R8 => { 155 // Convert grayscale to RGBA 156 let rgba_data: Vec<u8> = texture.data.iter() 157 .flat_map(|&gray| [gray, gray, gray, 255]) 158 .collect(); 159 egui::ColorImage::from_rgba_unmultiplied( 160 [texture.width as usize, texture.height as usize], 161 &rgba_data, 162 ) 163 } 164 _ => { 165 println!("Unsupported texture format: {:?}", texture.format); 166 return None; 167 } 168 }; 169 170 Some(ctx.load_texture( 171 &texture.name, 172 color_image, 173 egui::TextureOptions::default(), 174 )) 175 } 176 177 fn doc_is_texture(doc: &Document, kind: TextureCacheCategory) -> bool { 178 match doc.kind { 179 DocumentKind::Texture { content: DebuggerTextureContent { category, .. }, .. } => { 180 category == kind 181 }, 182 _ => false, 183 } 184 }