tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }