wip: optimisation retrieving tiles from blob + imagebitmap when supported by the browser

This commit is contained in:
bmatthieu3
2025-07-14 11:11:45 +02:00
committed by Matthieu Baumann
parent c6c17f0ba2
commit a91b2154a1
10 changed files with 197 additions and 74 deletions

View File

@@ -1,3 +1,4 @@
use crate::browser_support::BrowserFeaturesSupport;
use crate::downloader::request::moc::MOCRequest;
use crate::math::angle::ToAngle;
use crate::math::spectra::Freq;
@@ -6,6 +7,7 @@ use crate::renderable::image::Image;
use crate::renderable::ImageLayer;
use crate::tile_fetcher::HiPSLocalFiles;
use crate::Abort;
use al_core::image::bitmap::Bitmap;
use crate::{
camera::CameraViewPort,
downloader::Downloader,
@@ -108,6 +110,8 @@ pub struct App {
//img_send: async_channel::Sender<ImageLayer>,
img_recv: async_channel::Receiver<ImageLayer>,
ack_img_send: async_channel::Sender<ImageParams>,
browser_features_support: BrowserFeaturesSupport,
//ack_img_recv: async_channel::Receiver<ImageParams>,
// callbacks
//callback_position_changed: js_sys::Function,
@@ -204,6 +208,8 @@ impl App {
let dragging = false;
let time_mouse_high_vel = Time::now();
let browser_features_support = BrowserFeaturesSupport::new();
Ok(App {
gl,
//ui,
@@ -255,6 +261,8 @@ impl App {
//img_send,
img_recv,
ack_img_send,
browser_features_support
//ack_img_recv,
})
}
@@ -278,7 +286,7 @@ impl App {
}
}
hips.look_for_new_tiles(&mut self.tile_fetcher, &self.camera);
hips.look_for_new_tiles(&mut self.tile_fetcher, &self.camera, &self.browser_features_support);
}
Ok(())
@@ -575,12 +583,12 @@ impl App {
match rsc {
RequestType::Tile(tile) => {
if self.camera.has_moved() {
/*if self.camera.has_moved() {
self.downloader
.borrow_mut()
.delay(RequestType::Tile(tile));
continue;
}
}*/
if let Some(hips) = self.layers.get_mut_hips_from_cdid(&tile.hips_cdid) {
let cfg = hips.get_config();
@@ -742,6 +750,77 @@ impl App {
tile.request.time_request,
)?;
}
ImageType::ImageRgba8u {
image: Bitmap { image, .. },
}
| ImageType::ImageRgb8u {
image: Bitmap { image, .. },
} => {
let document = web_sys::window()
.unwrap_abort()
.document()
.unwrap_abort();
let canvas = document
.create_element("canvas")?
.dyn_into::<web_sys::HtmlCanvasElement>()?;
canvas.set_width(image.width());
canvas.set_height(image.height());
let context = canvas
.get_context("2d")?
.unwrap_abort()
.dyn_into::<web_sys::CanvasRenderingContext2d>()?;
// Get the data once for all for the whole image
// This takes time so better do it once and not repeatly
context.draw_image_with_image_bitmap(image, 0.0, 0.0)?;
// Cut the png in several tile images. See page 3 of
// https://aladin.cds.unistra.fr/java/DocTechHiPS3D.pdf
let tile_depth = *tile_depth;
let num_cols =
(tile_depth as f32).sqrt().floor() as u32;
let num_rows = ((tile_depth as f32)
/ (num_cols as f32))
.ceil()
as u32;
debug_assert_eq!(num_rows * num_cols, tile_depth);
let tile_size = *tile_size;
let bytes = context
.get_image_data(0_f64, 0_f64, (num_cols * tile_size) as f64, (num_rows * tile_size) as f64)?
.data().0;
let mut decoded_bytes = vec![0_u8;
(tile_size * tile_size * tile_depth)
as usize
];
let mut k = 0;
for y in 0..num_rows {
let sy = y * tile_size;
for x in 0..num_cols {
let sx = x * tile_size;
for i in sy..(sy + tile_size) {
for j in sx..(sx + tile_size) {
let id_byte = (j + i * num_cols * tile_size) * 4;
decoded_bytes[k] = bytes[id_byte as usize];
k += 1;
}
}
}
}
hips.push_tile_from_jpeg(
cell,
decoded_bytes.into_boxed_slice(),
(tile_size, tile_size, tile_depth),
tile.request.time_request,
)?;
}
ImageType::FitsRawBytes { raw_bytes, size } => hips
.push_tile_from_fits(
cell,
@@ -770,7 +849,7 @@ impl App {
// The allsky image is missing so we donwload all the tiles contained into
// the 0's cell
for base_hpx_cell in crate::healpix::cell::ALLSKY_HPX_CELLS_D0 {
let query = hips.build_tile_query(base_hpx_cell);
let query = query::Tile::new(base_hpx_cell, hips.get_config(), &self.browser_features_support);
self.tile_fetcher.append_base_tile(query);
}
} else {

View File

@@ -0,0 +1,19 @@
use wasm_bindgen::{JsCast, JsValue};
use web_sys::{window};
use js_sys::Reflect;
pub struct BrowserFeaturesSupport {
pub create_image_bitmap: bool,
}
impl BrowserFeaturesSupport {
pub fn new() -> Self {
let window = window().expect("no global `window` exists");
let create_image_bitmap = Reflect::has(&window, &JsValue::from_str("createImageBitmap"))
.unwrap_or(false);
Self {
create_image_bitmap
}
}
}

View File

@@ -8,6 +8,7 @@ pub trait Query: Sized {
}
pub type QueryId = String;
use crate::browser_support::BrowserFeaturesSupport;
use crate::healpix::cell::HEALPixFreqCell;
use al_api::hips::DataproductType;
use al_core::image::format::ImageFormatType;
@@ -72,6 +73,7 @@ pub struct Tile {
pub credentials: RequestCredentials,
pub mode: RequestMode,
pub id: QueryId,
pub create_bitmap_support: bool,
}
use crate::healpix::cell::HEALPixCell;
@@ -80,7 +82,7 @@ use crate::renderable::CreatorDid;
use crate::tile_fetcher::HiPSLocalFiles;
use web_sys::{RequestCredentials, RequestMode};
impl Tile {
pub fn new(cell: &HEALPixCell, cfg: &HiPSConfig) -> Self {
pub fn new(cell: &HEALPixCell, cfg: &HiPSConfig, browser_support: &BrowserFeaturesSupport) -> Self {
let hips_cdid = cfg.get_creator_did();
let hips_url = cfg.get_root_url();
let format = cfg.get_format();
@@ -109,10 +111,11 @@ impl Tile {
credentials,
mode,
id,
create_bitmap_support: browser_support.create_image_bitmap,
}
}
pub fn new_with_channel(cell: &HEALPixCell, channel: u32, cfg: &HiPSConfig) -> Self {
pub fn new_with_channel(cell: &HEALPixCell, channel: u32, cfg: &HiPSConfig, browser_support: &BrowserFeaturesSupport) -> Self {
let hips_cdid = cfg.get_creator_did();
let hips_url = cfg.get_root_url();
let format = cfg.get_format();
@@ -142,10 +145,11 @@ impl Tile {
credentials,
mode,
id,
create_bitmap_support: browser_support.create_image_bitmap,
}
}
pub fn new_cubic(hpx_f_cell: &HEALPixFreqCell, cfg: &HiPSConfig) -> Self {
pub fn new_cubic(hpx_f_cell: &HEALPixFreqCell, cfg: &HiPSConfig, browser_support: &BrowserFeaturesSupport) -> Self {
let hips_cdid = cfg.get_creator_did();
let hips_url = cfg.get_root_url();
let format = cfg.get_format();
@@ -183,6 +187,7 @@ impl Tile {
credentials,
mode,
id,
create_bitmap_support: browser_support.create_image_bitmap,
}
}
}

View File

@@ -151,3 +151,41 @@ async fn query_html_image(
Ok(image)
}
use wasm_bindgen::JsCast;
use web_sys::Response;
use web_sys::window;
use web_sys::RequestInit;
use web_sys::RequestMode;
async fn query_bitmap_from_blob(
url: &str,
mode: RequestMode,
credentials: RequestCredentials
) -> Result<web_sys::ImageBitmap, JsValue> {
let window = web_sys::window().unwrap_abort();
let mut opts = RequestInit::new();
opts.method("GET");
opts.mode(mode);
opts.credentials(credentials);
let request =
web_sys::Request::new_with_str_and_init(url, &opts).unwrap_abort();
let resp_value = JsFuture::from(window.fetch_with_request(&request)).await?;
// `resp_value` is a `Response` object.
debug_assert!(resp_value.is_instance_of::<Response>());
let resp: Response = resp_value.dyn_into()?;
if resp.ok() {
let blob = JsFuture::from(resp.blob()?).await?.dyn_into::<web_sys::Blob>()?;
let image_bitmap = JsFuture::from(window.create_image_bitmap_with_blob(&blob)?).await?;
Ok(image_bitmap.into())
} else {
Err(JsValue::from_str(
"Response status code not between 200-299.",
))
}
}

View File

@@ -32,6 +32,9 @@ use wasm_bindgen::JsCast;
use wasm_bindgen::JsValue;
use wasm_bindgen_futures::JsFuture;
use web_sys::{RequestInit, Response};
use al_core::image::bitmap::Bitmap;
use crate::downloader::request::query_bitmap_from_blob;
impl From<query::Tile> for TileRequest {
// Create a tile request associated to a HiPS
fn from(query: query::Tile) -> Self {
@@ -43,6 +46,7 @@ impl From<query::Tile> for TileRequest {
credentials,
mode,
id,
create_bitmap_support,
} = query;
let url_clone = url.clone();
@@ -59,26 +63,43 @@ impl From<query::Tile> for TileRequest {
} => (tile_size, tile_size, tile_depth),
};
let window = web_sys::window().unwrap_abort();
let request = match pixel_format {
PixelType::RGB8U => Request::new(async move {
// HTMLImageElement
let image = query_html_image(&url_clone, credentials).await?;
// The image has been resolved
Ok(ImageType::HTMLImageRgb8u {
image: HTMLImage::<RGB8U>::new(image),
})
if create_bitmap_support {
// optimized download of tile for GPU (using Blob + Bitmap) without creating any DOM structure
let image_bitmap = query_bitmap_from_blob(&url_clone, mode, credentials).await?;
Ok(ImageType::ImageRgb8u {
image: Bitmap::new(image_bitmap),
})
} else {
// HTMLImageElement
let image = query_html_image(&url_clone, credentials).await?;
// The image has been resolved
Ok(ImageType::HTMLImageRgb8u {
image: HTMLImage::new(image),
})
}
}),
PixelType::RGBA8U => Request::new(async move {
// HTMLImageElement
let image = query_html_image(&url_clone, credentials).await?;
// The image has been resolved
Ok(ImageType::HTMLImageRgba8u {
image: HTMLImage::<RGBA8U>::new(image),
})
if create_bitmap_support {
// optimized download of tile for GPU (using Blob + Bitmap) without creating any DOM structure
let image_bitmap = query_bitmap_from_blob(&url_clone, mode, credentials).await?;
Ok(ImageType::ImageRgba8u {
image: Bitmap::new(image_bitmap),
})
} else {
// HTMLImageElement
let image = query_html_image(&url_clone, credentials).await?;
// The image has been resolved
Ok(ImageType::HTMLImageRgba8u {
image: HTMLImage::new(image),
})
}
}),
PixelType::R32F | PixelType::R32I | PixelType::R16I | PixelType::R8U => {
Request::new(async move {
let window = web_sys::window().unwrap_abort();
let mut opts = RequestInit::new();
opts.method("GET");
opts.mode(mode);

View File

@@ -111,6 +111,7 @@ pub mod renderable;
mod shader;
mod tile_fetcher;
mod time;
mod browser_support;
use crate::{
camera::CameraViewPort, healpix::moc::SpaceMoc, math::lonlat::LonLatT, shader::ShaderManager,

View File

@@ -2,6 +2,7 @@ pub mod buffer;
pub mod texture;
use crate::app::BLENDING_ANIM_DURATION;
use crate::browser_support::BrowserFeaturesSupport;
use crate::downloader::query;
use crate::downloader::query::CellDesc;
use crate::downloader::request::allsky::AllskyRequest;
@@ -331,6 +332,7 @@ impl HiPS2D {
&mut self,
tile_fetcher: &mut TileFetcherQueue,
camera: &CameraViewPort,
browser_features_support: &BrowserFeaturesSupport
) {
// do not add tiles if the view is already at depth 0
let cfg = self.get_config();
@@ -353,7 +355,7 @@ impl HiPS2D {
};
if make_query {
Some(query::Tile::new(&tile_cell, self.get_config()))
Some(query::Tile::new(&tile_cell, self.get_config(), browser_features_support))
} else {
None
}
@@ -383,7 +385,7 @@ impl HiPS2D {
for ancestor in ancestors {
if !self.update_priority_tile(&ancestor) {
tile_fetcher.append(query::Tile::new(&ancestor, self.get_config()));
tile_fetcher.append(query::Tile::new(&ancestor, self.get_config(), browser_features_support));
}
}
}
@@ -392,11 +394,6 @@ impl HiPS2D {
self.buffer.contains_tile(cell)
}
pub fn build_tile_query(&self, cell: &HEALPixCell) -> query::Tile {
let cfg = self.get_config();
query::Tile::new(cell, cfg)
}
pub fn update(&mut self, camera: &mut CameraViewPort, projection: &ProjectionType) {
let raytracing = camera.is_raytracing(projection);

View File

@@ -1,6 +1,7 @@
pub mod cube;
pub mod texture;
use crate::browser_support::BrowserFeaturesSupport;
use crate::healpix::moc::FreqSpaceMoc;
use crate::math::angle::ToAngle;
use crate::math::lonlat::LonLatT;
@@ -288,38 +289,11 @@ impl HiPS3D {
})
}
/*pub fn build_tile_query(&self, cell: &HEALPixCell) -> query::Tile {
let cfg = self.get_config();
match cfg.dataproduct_type {
DataproductType::SpectralCube => {
// Determination of the f_order from the s_order
// From https://aladin.cds.unistra.fr/java/DocTechHiPS3D.pdf page 3
let f_max_order = cfg.max_depth_freq.unwrap_abort();
let s_max_order = cfg.max_depth_tile;
let s_order = cell.depth();
let f_order = f_max_order - (s_max_order - s_order);
let f_hash = self.freq.hash(f_order);
let cell = HEALPixFreqCell::new(*cell, f_hash, f_order);
query::Tile::new_cubic(&cell, cfg)
}
DataproductType::Cube => {
let channel_idx = (((self.freq.0 - cfg.em_min.unwrap_abort().0)
/ (cfg.em_max.unwrap_abort().0 - cfg.em_min.unwrap_abort().0))
* (cfg.get_cube_depth().unwrap_abort() as f64))
as u32;
query::Tile::new_with_channel(&cell, channel_idx, cfg)
}
_ => unreachable!(),
}
}*/
pub fn look_for_new_tiles(
&mut self,
tile_fetcher: &mut TileFetcherQueue,
camera: &CameraViewPort,
browser_features_support: &BrowserFeaturesSupport
) {
// update the cursor center before downloading new tiles
self.set_cursor_location(camera.get_center().into(), camera);
@@ -368,6 +342,7 @@ impl HiPS3D {
&tile_cell,
channel_idx as u32,
cfg,
browser_features_support
));
// check if we are starting aladin lite or not.
@@ -386,6 +361,7 @@ impl HiPS3D {
&ancestor,
channel_idx as u32,
cfg,
browser_features_support
));
}
}
@@ -440,7 +416,7 @@ impl HiPS3D {
});
for cubic_tile in cubic_tiles_iter {
tile_fetcher.append(query::Tile::new_cubic(&cubic_tile, cfg));
tile_fetcher.append(query::Tile::new_cubic(&cubic_tile, cfg, browser_features_support));
}
}
_ => unreachable!(),

View File

@@ -9,6 +9,7 @@ pub mod uv;
pub use d2::HiPS2D;
use crate::browser_support::BrowserFeaturesSupport;
use crate::renderable::HiPSConfig;
use crate::tile_fetcher::TileFetcherQueue;
use crate::CameraViewPort;
@@ -66,10 +67,11 @@ impl HiPS {
&mut self,
tile_fetcher: &mut TileFetcherQueue,
camera: &CameraViewPort,
browser_features_support: &BrowserFeaturesSupport
) {
match self {
D2(hips) => hips.look_for_new_tiles(tile_fetcher, camera),
D3(hips) => hips.look_for_new_tiles(tile_fetcher, camera),
D2(hips) => hips.look_for_new_tiles(tile_fetcher, camera, browser_features_support),
D3(hips) => hips.look_for_new_tiles(tile_fetcher, camera, browser_features_support),
}
}
@@ -111,22 +113,6 @@ impl HiPS {
}
}
/*#[inline]
pub fn set_moc(&mut self, moc: SpaceMoc) {
match self {
D2(hips) => hips.set_moc(moc),
D3(hips) => hips.set_moc(moc),
}
}*/
/*#[inline]
pub fn build_tile_query(&self, cell: &HEALPixCell) -> query::Tile {
match self {
HiPS::D2(hips) => hips.build_tile_query(cell),
HiPS::D3(hips) => hips.build_tile_query(cell),
}
}*/
pub fn is_allsky(&self) -> bool {
self.get_config().is_allsky
}

View File

@@ -227,6 +227,7 @@ impl TileFetcherQueue {
HiPS::D3(_) => (),
}
// FIXME: this still might be important to keep but for HiPS2D only
/*if cfg.get_min_depth_tile() == 0 {
for tile_cell in crate::healpix::cell::ALLSKY_HPX_CELLS_D0 {
if let Ok(query) = self.check_in_file_list(hips.build_tile_query(tile_cell)) {