From 59a679af2da0ea45720b3875ead6952d23d315b9 Mon Sep 17 00:00:00 2001 From: Matthieu Baumann Date: Thu, 17 Oct 2024 17:17:46 +0200 Subject: [PATCH] wip: hips3d struct and hips enum --- examples/al-hips-cube.html | 52 ++ src/core/Cargo.toml | 6 +- src/core/al-core/src/image/bitmap.rs | 2 +- src/core/al-core/src/image/canvas.rs | 2 +- src/core/al-core/src/image/fits.rs | 1 - src/core/al-core/src/texture/array.rs | 84 +++ src/core/al-core/src/texture/mod.rs | 2 +- src/core/src/app.rs | 158 +++-- src/core/src/downloader/query.rs | 54 +- src/core/src/downloader/request/allsky.rs | 7 +- src/core/src/downloader/request/blank.rs | 1 - src/core/src/downloader/request/moc.rs | 1 - src/core/src/downloader/request/mod.rs | 2 - src/core/src/downloader/request/tile.rs | 10 +- src/core/src/lib.rs | 38 +- src/core/src/math/utils.rs | 5 +- src/core/src/renderable/hips/config.rs | 32 +- src/core/src/renderable/hips/d2/buffer.rs | 479 ++++++------- src/core/src/renderable/hips/d2/mod.rs | 170 ++--- src/core/src/renderable/hips/d2/texture.rs | 113 ++- src/core/src/renderable/hips/d3/buffer.rs | 211 ++---- src/core/src/renderable/hips/d3/mod.rs | 655 ++++++++++++++++++ src/core/src/renderable/hips/d3/texture.rs | 242 +++++-- src/core/src/renderable/hips/mod.rs | 152 ++++ src/core/src/renderable/hips/subdivide.rs | 69 ++ src/core/src/renderable/hips/uv.rs | 5 +- src/core/src/renderable/mod.rs | 132 ++-- src/core/src/tile_fetcher.rs | 30 +- src/glsl/webgl2/hips3d/rasterizer/color.frag | 21 + .../rasterizer/grayscale_to_colormap.frag | 22 + .../rasterizer/grayscale_to_colormap_i.frag | 22 + .../rasterizer/grayscale_to_colormap_u.frag | 22 + src/glsl/webgl2/hips3d/rasterizer/raster.vert | 26 + src/js/HiPS.js | 11 +- 34 files changed, 1990 insertions(+), 849 deletions(-) create mode 100644 examples/al-hips-cube.html create mode 100644 src/core/src/renderable/hips/subdivide.rs create mode 100644 src/glsl/webgl2/hips3d/rasterizer/color.frag create mode 100644 src/glsl/webgl2/hips3d/rasterizer/grayscale_to_colormap.frag create mode 100644 src/glsl/webgl2/hips3d/rasterizer/grayscale_to_colormap_i.frag create mode 100644 src/glsl/webgl2/hips3d/rasterizer/grayscale_to_colormap_u.frag create mode 100644 src/glsl/webgl2/hips3d/rasterizer/raster.vert diff --git a/examples/al-hips-cube.html b/examples/al-hips-cube.html new file mode 100644 index 000000000..b66b6e0c6 --- /dev/null +++ b/examples/al-hips-cube.html @@ -0,0 +1,52 @@ + + + + + + + +
+ + + + + \ No newline at end of file diff --git a/src/core/Cargo.toml b/src/core/Cargo.toml index 6dec0b670..7f0cc825f 100644 --- a/src/core/Cargo.toml +++ b/src/core/Cargo.toml @@ -50,13 +50,11 @@ optional = true [dependencies.healpix] package = "cdshealpix" -git = "https://github.com/cds-astro/cds-healpix-rust" -branch = "master" +version = "0.7.0" [dependencies.moclib] package = "moc" -git = "https://github.com/cds-astro/cds-moc-rust" -branch = "main" +version = "0.17.0" [dependencies.serde] version = "^1.0.183" diff --git a/src/core/al-core/src/image/bitmap.rs b/src/core/al-core/src/image/bitmap.rs index c96906d7d..cb0d56ee6 100644 --- a/src/core/al-core/src/image/bitmap.rs +++ b/src/core/al-core/src/image/bitmap.rs @@ -19,7 +19,7 @@ where } } } -use crate::texture::{Tex3D, Texture2DArray}; +use crate::texture::Tex3D; use wasm_bindgen::JsValue; impl Image for Bitmap where diff --git a/src/core/al-core/src/image/canvas.rs b/src/core/al-core/src/image/canvas.rs index 3670eb979..88b5873c1 100644 --- a/src/core/al-core/src/image/canvas.rs +++ b/src/core/al-core/src/image/canvas.rs @@ -19,7 +19,7 @@ where use crate::image::format::ImageFormat; use crate::image::Image; -use crate::texture::{Tex3D, Texture2DArray}; +use crate::texture::Tex3D; use cgmath::Vector3; use wasm_bindgen::JsValue; impl Image for Canvas diff --git a/src/core/al-core/src/image/fits.rs b/src/core/al-core/src/image/fits.rs index 300c8d594..55e072cb7 100644 --- a/src/core/al-core/src/image/fits.rs +++ b/src/core/al-core/src/image/fits.rs @@ -116,7 +116,6 @@ impl<'a> Fits<'a> { } }*/ -use crate::Texture2DArray; use crate::{image::Image, texture::Tex3D}; impl Image for Fits<'_> { fn insert_into_3d_texture( diff --git a/src/core/al-core/src/texture/array.rs b/src/core/al-core/src/texture/array.rs index b971a2be4..093a06fd5 100644 --- a/src/core/al-core/src/texture/array.rs +++ b/src/core/al-core/src/texture/array.rs @@ -2,6 +2,7 @@ use crate::image::format::ImageFormat; use web_sys::HtmlCanvasElement; use web_sys::WebGlTexture; +use crate::texture::pixel::Pixel; use crate::texture::Texture2DMeta; use crate::webgl_ctx::WebGlContext; use crate::webgl_ctx::WebGlRenderingCtx; @@ -80,6 +81,89 @@ impl Texture2DArray { .active_texture(WebGlRenderingCtx::TEXTURE0 + idx_tex_unit as u32); self } + + pub fn read_pixel(&self, x: i32, y: i32, slice_idx: i32) -> Result { + // Create and bind the framebuffer + let reader = self.gl.create_framebuffer(); + self.gl + .bind_framebuffer(WebGlRenderingCtx::FRAMEBUFFER, reader.as_ref()); + + // Attach the texture as the first color attachment + self.gl.framebuffer_texture_layer( + WebGlRenderingCtx::READ_FRAMEBUFFER, + WebGlRenderingCtx::COLOR_ATTACHMENT0, + self.texture.as_ref(), + 0, + slice_idx, + ); + + let status = self + .gl + .check_framebuffer_status(WebGlRenderingCtx::FRAMEBUFFER); + if status != WebGlRenderingCtx::FRAMEBUFFER_COMPLETE { + // Unbind the framebuffer + self.gl + .bind_framebuffer(WebGlRenderingCtx::FRAMEBUFFER, None); + // Delete the framebuffer + self.gl.delete_framebuffer(reader.as_ref()); + + Err(JsValue::from_str("incomplete framebuffer")) + } else { + // set the viewport as the FBO won't be the same dimension as the screen + let metadata = self.metadata.as_ref().unwrap_abort().borrow(); + self.gl + .viewport(0, 0, metadata.width as i32, metadata.height as i32); + + #[cfg(feature = "webgl2")] + let value = match (metadata.format, metadata.type_) { + (WebGlRenderingCtx::RED_INTEGER, WebGlRenderingCtx::UNSIGNED_BYTE) => { + let p = <[u8; 1]>::read_pixel(&self.gl, x, y)?; + Ok(serde_wasm_bindgen::to_value(&p[0])?) + } + (WebGlRenderingCtx::RED_INTEGER, WebGlRenderingCtx::SHORT) => { + let p = <[i16; 1]>::read_pixel(&self.gl, x, y)?; + Ok(serde_wasm_bindgen::to_value(&p[0])?) + } + (WebGlRenderingCtx::RED_INTEGER, WebGlRenderingCtx::INT) => { + let p = <[i32; 1]>::read_pixel(&self.gl, x, y)?; + Ok(serde_wasm_bindgen::to_value(&p[0])?) + } + (WebGlRenderingCtx::RED, WebGlRenderingCtx::FLOAT) => { + let p = <[f32; 1]>::read_pixel(&self.gl, x, y)?; + Ok(serde_wasm_bindgen::to_value(&p[0])?) + } + (WebGlRenderingCtx::RGB, WebGlRenderingCtx::UNSIGNED_BYTE) => { + let p = <[u8; 3]>::read_pixel(&self.gl, x, y)?; + Ok(serde_wasm_bindgen::to_value(&p)?) + } + (WebGlRenderingCtx::RGBA, WebGlRenderingCtx::UNSIGNED_BYTE) => { + let p = <[u8; 4]>::read_pixel(&self.gl, x, y)?; + Ok(serde_wasm_bindgen::to_value(&p)?) + } + _ => Err(JsValue::from_str( + "Pixel retrieval not implemented for that texture format.", + )), + }; + + // Unbind the framebuffer + self.gl + .bind_framebuffer(WebGlRenderingCtx::FRAMEBUFFER, None); + // Delete the framebuffer + self.gl.delete_framebuffer(reader.as_ref()); + + // set the viewport as the FBO won't be the same dimension as the screen + let canvas = self + .gl + .canvas() + .unwrap_abort() + .dyn_into::() + .unwrap_abort(); + self.gl + .viewport(0, 0, canvas.width() as i32, canvas.height() as i32); + + value + } + } } impl Drop for Texture2DArray { diff --git a/src/core/al-core/src/texture/mod.rs b/src/core/al-core/src/texture/mod.rs index f6f9efb24..4f871fd9a 100644 --- a/src/core/al-core/src/texture/mod.rs +++ b/src/core/al-core/src/texture/mod.rs @@ -328,7 +328,7 @@ impl Texture2D { // set the viewport as the FBO won't be the same dimension as the screen let metadata = self.metadata.as_ref().unwrap_abort().borrow(); self.gl - .viewport(x, y, metadata.width as i32, metadata.height as i32); + .viewport(0, 0, metadata.width as i32, metadata.height as i32); #[cfg(feature = "webgl2")] let value = match (metadata.format, metadata.type_) { diff --git a/src/core/src/app.rs b/src/core/src/app.rs index 1e0bc10f8..3d3b7b8f4 100644 --- a/src/core/src/app.rs +++ b/src/core/src/app.rs @@ -1,6 +1,7 @@ use crate::renderable::ImageLayer; use crate::tile_fetcher::HiPSLocalFiles; +use crate::renderable::hips::HiPS; use crate::{ //async_task::{BuildCatalogIndex, ParseTableTask, TaskExecutor, TaskResult, TaskType}, camera::CameraViewPort, @@ -19,7 +20,6 @@ use crate::{ tile_fetcher::TileFetcherQueue, time::DeltaTime, }; -use al_core::log::console_log; use wcs::WCS; use wasm_bindgen::prelude::*; @@ -28,7 +28,6 @@ use al_core::colormap::{Colormap, Colormaps}; use al_core::WebGlContext; use super::coosys; -use crate::Abort; use al_api::{ coo_system::CooSystem, grid::GridCfg, @@ -140,7 +139,7 @@ impl App { //gl.enable(WebGl2RenderingContext::SCISSOR_TEST); //gl.enable(WebGl2RenderingContext::CULL_FACE); - //gl.cull_face(WebGl2RenderingContext::BACK); + gl.cull_face(WebGl2RenderingContext::BACK); //gl.enable(WebGl2RenderingContext::CULL_FACE); // The tile buffer responsible for the tile requests @@ -153,7 +152,7 @@ impl App { FrameBufferObject::new(&gl, screen_size.x as usize, screen_size.y as usize)?; let _fbo_ui = FrameBufferObject::new(&gl, screen_size.x as usize, screen_size.y as usize)?; - // The surveys storing the textures of the resolved tiles + // The hipss storing the textures of the resolved tiles let layers = Layers::new(&gl, &projection)?; let time_start_blending = Time::now(); @@ -255,22 +254,23 @@ impl App { } fn look_for_new_tiles(&mut self) -> Result<(), JsValue> { - // Move the views of the different active surveys + // Move the views of the different active hipss self.tile_fetcher.clear(); - // Loop over the surveys - for survey in self.layers.values_mut_hips() { - let cfg = survey.get_config(); - - if self.camera.get_texture_depth() == 0 - && self - .downloader - .borrow() - .is_queried(&query::Allsky::new(cfg).id) - { - // do not ask for tiles if we download the allsky - continue; + // Loop over the hipss + for hips in self.layers.get_mut_hipses() { + if self.camera.get_texture_depth() == 0 { + let allsky_query = match hips { + HiPS::D2(h) => query::Allsky::new(h.get_config(), None), + HiPS::D3(h) => query::Allsky::new(h.get_config(), Some(h.get_slice() as u32)), + }; + if self.downloader.borrow().is_queried(&allsky_query.id) { + // do not ask for tiles if we download the allsky + continue; + } } + let cfg = hips.get_config(); + let min_tile_depth = cfg.delta_depth().max(cfg.get_min_depth_tile()); let mut ancestors = HashSet::new(); @@ -278,16 +278,9 @@ impl App { let root_url = cfg.get_root_url().to_string(); let format = cfg.get_format(); - if let Some(tiles_iter) = survey.look_for_new_tiles(&mut self.camera, &self.projection) - { - for tile_cell in tiles_iter.into_iter() { - self.tile_fetcher.append(query::Tile::new( - &tile_cell, - creator_did.clone(), - root_url.clone(), - format, - None, - )); + if let Some(tiles) = hips.look_for_new_tiles(&mut self.camera, &self.projection) { + for tile_cell in tiles { + self.tile_fetcher.append(hips.get_tile_query(&tile_cell)); // check if we are starting aladin lite or not. // If so we want to retrieve only the tiles in the view and access them @@ -301,15 +294,21 @@ impl App { } } // Request for ancestor - for ancestor in ancestors { - if !survey.update_priority_tile(&ancestor) { - self.tile_fetcher.append(query::Tile::new( - &ancestor, - creator_did.clone(), - root_url.clone(), - format, - None, - )); + match hips { + HiPS::D2(hips) => { + for ancestor in ancestors { + if !hips.update_priority_tile(&ancestor) { + self.tile_fetcher.append(hips.get_tile_query(&ancestor)); + } + } + } + HiPS::D3(hips) => { + let slice = hips.get_slice(); + for ancestor in ancestors { + if !hips.contains_tile(&ancestor, slice) { + self.tile_fetcher.append(hips.get_tile_query(&ancestor)); + } + } } } } @@ -345,7 +344,7 @@ impl App { colormap, &mut self.shaders, &self.camera, - self.surveys.get_view().unwrap_abort(), + self.hipss.get_view().unwrap_abort(), ); self.catalog_loaded = true; self.request_redraw = true; @@ -380,7 +379,7 @@ impl App { colormap, &mut self.shaders, &self.camera, - self.surveys.get_view().unwrap_abort(), + self.hipss.get_view().unwrap_abort(), ); self.catalog_loaded = true; self.request_redraw = true; @@ -395,7 +394,6 @@ impl App { use crate::downloader::request::Resource; use al_api::cell::HEALPixCellProjeted; -use crate::downloader::request::tile::Tile; use crate::healpix::cell::HEALPixCell; use al_api::color::ColorRGB; @@ -566,11 +564,11 @@ impl App { { // Newly available tiles must lead to // 1. Surveys must be aware of the new available tiles - //self.surveys.set_available_tiles(&available_tiles); - // 2. Get the resolved tiles and push them to the image surveys + //self.hipss.set_available_tiles(&available_tiles); + // 2. Get the resolved tiles and push them to the image hipss /*let is_there_new_available_tiles = self .downloader - .get_resolved_tiles(/*&available_tiles, */&mut self.surveys);*/ + .get_resolved_tiles(/*&available_tiles, */&mut self.hipss);*/ if self.request_for_new_tiles //&& Time::now() - self.last_time_request_for_new_tiles > DeltaTime::from(200.0) @@ -606,9 +604,8 @@ impl App { match rsc { Resource::Tile(tile) => { //if !_has_camera_zoomed { - if let Some(survey) = self.layers.get_mut_hips_from_cdid(&tile.get_hips_cdid()) - { - let cfg = survey.get_config_mut(); + if let Some(hips) = self.layers.get_mut_hips_from_cdid(&tile.get_hips_cdid()) { + let cfg = hips.get_config_mut(); if cfg.get_format() == tile.format { let delta_depth = cfg.delta_depth(); @@ -707,7 +704,17 @@ impl App { self.request_redraw = true; tile_copied = true; - survey.add_tile(&tile.cell, img, tile.time_req)?; + match hips { + HiPS::D2(hips) => { + hips.add_tile(&tile.cell, img, tile.time_req)? + } + HiPS::D3(hips) => hips.add_tile( + &tile.cell, + img, + tile.time_req, + tile.channel.unwrap() as u16, + )?, + } self.time_start_blending = Time::now(); } @@ -720,28 +727,22 @@ impl App { Resource::Allsky(allsky) => { let hips_cdid = allsky.get_hips_cdid(); - if let Some(survey) = self.layers.get_mut_hips_from_cdid(hips_cdid) { + if let Some(hips) = self.layers.get_mut_hips_from_cdid(hips_cdid) { let is_missing = allsky.missing(); if is_missing { // The allsky image is missing so we donwload all the tiles contained into // the 0's cell - let cfg = survey.get_config(); + let cfg = hips.get_config(); for texture_cell in crate::healpix::cell::ALLSKY_HPX_CELLS_D0 { for cell in texture_cell.get_tile_cells(cfg.delta_depth()) { - let query = query::Tile::new( - &cell, - cfg.get_creator_did().to_string(), - cfg.get_root_url().to_string(), - cfg.get_format(), - None, - ); + let query = hips.get_tile_query(&cell); self.tile_fetcher.append_base_tile(query); } } } else { - // tell the survey to not download tiles which order is <= 3 because the allsky + // tell the hips to not download tiles which order is <= 3 because the allsky // give them already - survey.add_allsky(allsky)?; + hips.add_allsky(allsky)?; // Once received ask for redraw self.request_redraw = true; } @@ -793,8 +794,8 @@ impl App { pub(crate) fn read_pixel(&self, pos: &Vector2, layer: &str) -> Result { if let Some(lonlat) = self.screen_to_world(pos) { - if let Some(survey) = self.layers.get_hips_from_layer(layer) { - survey.read_pixel(&lonlat, &self.camera) + if let Some(hips) = self.layers.get_hips_from_layer(layer) { + hips.read_pixel(&lonlat, &self.camera) } else if let Some(_image) = self.layers.get_image_from_layer(layer) { Err(JsValue::from_str("TODO: read pixel value")) } else { @@ -960,14 +961,14 @@ impl App { Ok(()) } - pub(crate) fn add_image_hips( + pub(crate) fn add_hips( &mut self, hips_cfg: HiPSCfg, local_files: Option, ) -> Result<(), JsValue> { let cdid = hips_cfg.properties.get_creator_did().to_string(); - let hips = self.layers.add_image_hips( + let hips = self.layers.add_hips( &self.gl, hips_cfg, &mut self.camera, @@ -1252,18 +1253,25 @@ impl App { self.layers.get_layer_cfg(layer) } - pub(crate) fn set_hips_url(&mut self, cdid: &String, new_url: String) -> Result<(), JsValue> { - self.layers.set_survey_url(cdid, new_url.clone())?; + pub(crate) fn set_hips_slice_number(&mut self, layer: &str, slice: u32) -> Result<(), JsValue> { + let hips = self + .layers + .get_mut_hips_from_layer(&layer) + .ok_or_else(|| JsValue::from_str("Layer not found"))?; - //let hips = self.layers.get_hips_from_url(&new_url).unwrap_abort(); - // Relaunch the base tiles for the survey to be ready with the new url - //self.tile_fetcher - // .launch_starting_hips_requests(hips, &mut self.downloader); + self.request_for_new_tiles = true; - Ok(()) + match hips { + HiPS::D2(_) => Err(JsValue::from_str("layer do not refers to a cube")), + HiPS::D3(hips) => { + hips.set_slice(slice as u16); + + Ok(()) + } + } } - pub(crate) fn set_image_survey_color_cfg( + pub(crate) fn set_image_hips_color_cfg( &mut self, layer: String, meta: ImageMetadata, @@ -1271,19 +1279,19 @@ impl App { let old_meta = self.layers.get_layer_cfg(&layer)?; // Set the new meta // keep the old meta data - let new_img_fmt = meta.img_format; + let new_img_ext = meta.img_format; self.layers .set_layer_cfg(layer.clone(), meta, &mut self.camera, &self.projection)?; - if old_meta.img_format != new_img_fmt { + if old_meta.img_format != new_img_ext { // The image format has been changed let hips = self .layers .get_mut_hips_from_layer(&layer) .ok_or_else(|| JsValue::from_str("Layer not found"))?; - hips.set_img_format(new_img_fmt)?; + hips.set_image_ext(new_img_ext)?; - // Relaunch the base tiles for the survey to be ready with the new url + // Relaunch the base tiles for the hips to be ready with the new url self.tile_fetcher .launch_starting_hips_requests(hips, self.downloader.clone()); @@ -1364,8 +1372,8 @@ impl App { self.request_redraw = true; } - pub(crate) fn set_survey_url(&mut self, cdid: &String, new_url: String) -> Result<(), JsValue> { - self.layers.set_survey_url(cdid, new_url) + pub(crate) fn set_hips_url(&mut self, cdid: &String, new_url: String) -> Result<(), JsValue> { + self.layers.set_hips_url(cdid, new_url) } pub(crate) fn set_catalog_opacity( diff --git a/src/core/src/downloader/query.rs b/src/core/src/downloader/query.rs index 1198883e5..314ce036b 100644 --- a/src/core/src/downloader/query.rs +++ b/src/core/src/downloader/query.rs @@ -20,19 +20,18 @@ pub struct Tile { // The total url of the query pub url: Url, pub id: QueryId, + pub channel: Option, } use crate::healpix::cell::HEALPixCell; use crate::renderable::hips::config::HiPSConfig; use crate::renderable::CreatorDid; impl Tile { - pub fn new( - cell: &HEALPixCell, - hips_cdid: String, - hips_url: String, - format: ImageFormatType, - channel: Option, - ) -> Self { + pub fn new(cell: &HEALPixCell, channel: Option, cfg: &HiPSConfig) -> Self { + let hips_cdid = cfg.get_creator_did(); + let hips_url = cfg.get_root_url(); + let format = cfg.get_format(); + let ext = format.get_ext_file(); let HEALPixCell(depth, idx) = *cell; @@ -43,22 +42,30 @@ impl Tile { // handle cube case if let Some(channel) = channel { - url.push_str(&format!("_{:?}", channel)); + if channel > 0 { + url.push_str(&format!("_{:?}", channel)); + } } // add the tile format url.push_str(&format!(".{}", ext)); - let channel = channel.unwrap_or(0); - - let id = format!("{}{}{}{}{}", hips_cdid, depth, idx, channel, ext); + let id = format!( + "{}{}{}{}{}", + hips_cdid, + depth, + idx, + channel.unwrap_or(0), + ext + ); Tile { - hips_cdid, + hips_cdid: hips_cdid.to_string(), url, cell: *cell, format, id, + channel, } } } @@ -77,6 +84,7 @@ pub struct Allsky { pub format: ImageFormatType, pub tile_size: i32, pub texture_size: i32, + pub channel: Option, // The root url of the HiPS pub hips_cdid: CreatorDid, // The total url of the query @@ -85,16 +93,31 @@ pub struct Allsky { } impl Allsky { - pub fn new(cfg: &HiPSConfig) -> Self { + pub fn new(cfg: &HiPSConfig, channel: Option) -> Self { let hips_cdid = cfg.get_creator_did().to_string(); let tile_size = cfg.get_tile_size(); let texture_size = cfg.get_texture_size(); let format = cfg.get_format(); let ext = format.get_ext_file(); - let url = format!("{}/Norder3/Allsky.{}", cfg.get_root_url(), ext); + let mut url = format!("{}/Norder3/Allsky", cfg.get_root_url()); + + // handle cube case + if let Some(channel) = channel { + if channel > 0 { + url.push_str(&format!("_{:?}", channel)); + } + } + + // add the tile format + url.push_str(&format!(".{}", ext)); - let id = format!("{}Allsky{}", cfg.get_creator_did(), ext); + let id = format!( + "{}Allsky{}{}", + cfg.get_creator_did(), + ext, + channel.unwrap_or(0) + ); Allsky { tile_size, @@ -103,6 +126,7 @@ impl Allsky { url, format, id, + channel, } } } diff --git a/src/core/src/downloader/request/allsky.rs b/src/core/src/downloader/request/allsky.rs index ec8534f01..9f2b5f776 100644 --- a/src/core/src/downloader/request/allsky.rs +++ b/src/core/src/downloader/request/allsky.rs @@ -14,6 +14,7 @@ pub struct AllskyRequest { pub url: Url, pub depth_tile: u8, pub id: QueryId, + pub channel: Option, request: Request>, } @@ -80,6 +81,7 @@ impl From for AllskyRequest { hips_cdid, texture_size, id, + channel: slice, } = query; let depth_tile = crate::math::utils::log_2_unchecked(texture_size / tile_size) as u8; @@ -212,6 +214,7 @@ impl From for AllskyRequest { depth_tile, url, request, + channel: slice, } } } @@ -306,7 +309,6 @@ use al_core::image::format::RGBA8U; use crate::time::Time; use std::cell::RefCell; use std::rc::Rc; -use std::sync::{Arc, Mutex}; pub struct Allsky { pub image: Rc>>>, pub time_req: Time, @@ -314,6 +316,7 @@ pub struct Allsky { pub hips_cdid: CreatorDid, url: Url, + pub channel: Option, } use crate::Abort; @@ -339,6 +342,7 @@ impl<'a> From<&'a AllskyRequest> for Option { hips_cdid, depth_tile, url, + channel, .. } = request; if request.is_resolved() { @@ -352,6 +356,7 @@ impl<'a> From<&'a AllskyRequest> for Option { hips_cdid: hips_cdid.clone(), url: url.clone(), depth_tile: *depth_tile, + channel: *channel, }) } else { None diff --git a/src/core/src/downloader/request/blank.rs b/src/core/src/downloader/request/blank.rs index 5e3cf02c0..3f9e14126 100644 --- a/src/core/src/downloader/request/blank.rs +++ b/src/core/src/downloader/request/blank.rs @@ -134,7 +134,6 @@ impl From for PixelMetadataRequest { use std::cell::RefCell; use std::rc::Rc; -use std::sync::{Arc, Mutex}; #[derive(Debug)] pub struct PixelMetadata { pub value: Rc>>, diff --git a/src/core/src/downloader/request/moc.rs b/src/core/src/downloader/request/moc.rs index cffcb40a5..459b148e0 100644 --- a/src/core/src/downloader/request/moc.rs +++ b/src/core/src/downloader/request/moc.rs @@ -104,7 +104,6 @@ impl From for MOCRequest { use std::cell::RefCell; use std::rc::Rc; -use std::sync::{Arc, Mutex}; pub struct Moc { pub moc: Rc>>, pub params: al_api::moc::MOC, diff --git a/src/core/src/downloader/request/mod.rs b/src/core/src/downloader/request/mod.rs index f11eae0a7..82373a887 100644 --- a/src/core/src/downloader/request/mod.rs +++ b/src/core/src/downloader/request/mod.rs @@ -10,7 +10,6 @@ pub mod tile; use crate::time::Time; use std::cell::{Cell, RefCell}; use std::rc::Rc; -use std::sync::{Arc, Mutex}; pub type Url = String; pub struct Request { data: Rc>>, @@ -27,7 +26,6 @@ pub enum ResolvedStatus { Failed, Found, } -use crate::Abort; use std::future::Future; use wasm_bindgen::JsValue; impl Request diff --git a/src/core/src/downloader/request/tile.rs b/src/core/src/downloader/request/tile.rs index 2af991c1e..9df177da6 100644 --- a/src/core/src/downloader/request/tile.rs +++ b/src/core/src/downloader/request/tile.rs @@ -10,14 +10,14 @@ use super::{Request, RequestType}; use crate::downloader::QueryId; pub struct TileRequest { + request: Request, pub id: QueryId, cell: HEALPixCell, hips_cdid: CreatorDid, url: Url, format: ImageFormatType, - - request: Request, + channel: Option, } impl From for RequestType { @@ -59,6 +59,7 @@ impl From for TileRequest { url, hips_cdid, id, + channel: slice, } = query; let url_clone = url.clone(); @@ -180,6 +181,7 @@ impl From for TileRequest { hips_cdid, url, request, + channel: slice, } } } @@ -187,12 +189,12 @@ impl From for TileRequest { use crate::time::Time; use std::cell::RefCell; use std::rc::Rc; -use std::sync::{Arc, Mutex}; pub struct Tile { pub image: Rc>>, pub time_req: Time, pub cell: HEALPixCell, pub format: ImageFormatType, + pub channel: Option, hips_cdid: CreatorDid, url: Url, } @@ -228,6 +230,7 @@ impl<'a> From<&'a TileRequest> for Option { hips_cdid, url, format, + channel, .. } = request; if request.is_resolved() { @@ -242,6 +245,7 @@ impl<'a> From<&'a TileRequest> for Option { hips_cdid: hips_cdid.clone(), url: url.clone(), format: *format, + channel: *channel, }) } else { None diff --git a/src/core/src/lib.rs b/src/core/src/lib.rs index 2fc70c226..fa85fd994 100644 --- a/src/core/src/lib.rs +++ b/src/core/src/lib.rs @@ -320,13 +320,13 @@ impl WebClient { Ok(self.app.get_norder()) } - /// Set new image surveys + /// Set new image hips /// - /// Send the image surveys to render inside the Aladin Lite view + /// Send the image hips to render inside the Aladin Lite view /// /// # Arguments /// - /// * `surveys` - A list/array of survey. A survey is a javascript object + /// * `hips` - A list/array of hips. A hips is a javascript object /// having the specific form. Please check the file in core/src/hips.rs to see /// the different semantics accepted. /// @@ -362,19 +362,19 @@ impl WebClient { /// /// # Panics /// - /// * If the surveys do not match SimpleHiPS type - /// * If the number of surveys is greater than 4. For the moment, due to the limitations - /// of WebGL2 texture units on some architectures, the total number of surveys rendered is + /// * If the hips do not match SimpleHiPS type + /// * If the number of hips is greater than 4. For the moment, due to the limitations + /// of WebGL2 texture units on some architectures, the total number of hips rendered is /// limited to 4. #[wasm_bindgen(js_name = addHiPS)] - pub fn add_image_hips( + pub fn add_hips( &mut self, hips: JsValue, files: Option, ) -> Result<(), JsValue> { - // Deserialize the survey objects that compose the survey + // Deserialize the hips objects that compose the hips let hips = serde_wasm_bindgen::from_value(hips)?; - self.app.add_image_hips(hips, files)?; + self.app.add_hips(hips, files)?; Ok(()) } @@ -410,7 +410,7 @@ impl WebClient { #[wasm_bindgen(js_name = removeLayer)] pub fn remove_layer(&mut self, layer: String) -> Result<(), JsValue> { - // Deserialize the survey objects that compose the survey + // Deserialize the hips objects that compose the hips self.app.remove_layer(&layer)?; Ok(()) @@ -418,7 +418,7 @@ impl WebClient { #[wasm_bindgen(js_name = renameLayer)] pub fn rename_layer(&mut self, layer: String, new_layer: String) -> Result<(), JsValue> { - // Deserialize the survey objects that compose the survey + // Deserialize the hips objects that compose the hips self.app.rename_layer(&layer, &new_layer) } @@ -428,7 +428,7 @@ impl WebClient { first_layer: String, second_layer: String, ) -> Result<(), JsValue> { - // Deserialize the survey objects that compose the survey + // Deserialize the hips objects that compose the hips self.app.swap_layers(&first_layer, &second_layer) } @@ -444,15 +444,15 @@ impl WebClient { // Set a new color associated with a layer #[wasm_bindgen(js_name = setImageMetadata)] - pub fn set_survey_color_cfg(&mut self, layer: String, meta: JsValue) -> Result<(), JsValue> { + pub fn set_hips_color_cfg(&mut self, layer: String, meta: JsValue) -> Result<(), JsValue> { let meta = serde_wasm_bindgen::from_value(meta)?; - self.app.set_image_survey_color_cfg(layer, meta) + self.app.set_image_hips_color_cfg(layer, meta) } - #[wasm_bindgen(js_name = setImageSurveyUrl)] - pub fn set_survey_url(&mut self, cdid: String, new_url: String) -> Result<(), JsValue> { - self.app.set_survey_url(&cdid, new_url) + #[wasm_bindgen(js_name = setSliceNumber)] + pub fn set_hips_slice_number(&mut self, layer: String, slice: u32) -> Result<(), JsValue> { + self.app.set_hips_slice_number(&layer, slice) } #[wasm_bindgen(js_name = setBackgroundColor)] @@ -963,7 +963,7 @@ impl WebClient { /// Read the pixel value /// /// The current implementation only returns the pixel value - /// of the first survey of the `layer` specified. + /// of the first hips of the `layer` specified. /// /// # Returns /// @@ -975,7 +975,7 @@ impl WebClient { /// /// * `x` - The x screen coordinate in pixels /// * `y` - The y screen coordinate in pixels - /// * `base_url` - The base url of the survey identifying it + /// * `base_url` - The base url of the hips identifying it #[wasm_bindgen(js_name = readPixel)] pub fn read_pixel(&self, x: f64, y: f64, layer: String) -> Result { let pixel = self.app.read_pixel(&Vector2::new(x, y), layer.as_str())?; diff --git a/src/core/src/math/utils.rs b/src/core/src/math/utils.rs index 7d1feff6f..6351360d6 100644 --- a/src/core/src/math/utils.rs +++ b/src/core/src/math/utils.rs @@ -73,7 +73,7 @@ impl PrimInt for i32 { pub fn log_2_unchecked(x: T) -> u32 where - T: Zero + PrimInt + std::cmp::PartialOrd + T: Zero + PrimInt + std::cmp::PartialOrd, { debug_assert!(x > T::zero()); num_bits::() as u32 - x.leading_zeros() - 1 @@ -105,7 +105,6 @@ pub fn lambert_wm1(x: f32) -> f32 { * (1.0 - 1.0 / (1.0 + ((m1 * s_div_2_root) / (1.0 + m2 * s * (m3 * s_root).exp())))) } - #[inline] pub fn ccw_tri(a: &[S; 2], b: &[S; 2], c: &[S; 2]) -> bool { // From: https://math.stackexchange.com/questions/1324179/how-to-tell-if-3-connected-points-are-connected-clockwise-or-counter-clockwise @@ -113,5 +112,5 @@ pub fn ccw_tri(a: &[S; 2], b: &[S; 2], c: &[S; 2]) -> bool { // | x2, y2, 1 | > 0 => the triangle is given in anticlockwise order // | x3, y3, 1 | - a[0]*b[1] + a[1]*c[0] + b[0]*c[1] - c[0]*b[1] - c[1]*a[0] - b[0]*a[1] >= S::zero() + a[0] * b[1] + a[1] * c[0] + b[0] * c[1] - c[0] * b[1] - c[1] * a[0] - b[0] * a[1] >= S::zero() } diff --git a/src/core/src/renderable/hips/config.rs b/src/core/src/renderable/hips/config.rs index 176b1ac03..0613dbba7 100644 --- a/src/core/src/renderable/hips/config.rs +++ b/src/core/src/renderable/hips/config.rs @@ -1,19 +1,6 @@ use al_api::hips::ImageExt; -use al_core::{image::format::ImageFormat, image::raw::ImageBuffer}; - -use al_core::{image::ImageType, pixel::Pixel}; - -use al_core::{ - image::{ - format::{R16I, R32F, R32I, R8UI}, - Image, - }, - Texture2DArray, -}; -use cgmath::Vector3; - -use al_core::image::format::{ChannelType, ImageFormatType, RGB8U, RGBA8U}; +use al_core::image::format::{ChannelType, ImageFormatType}; #[derive(Debug)] pub struct HiPSConfig { pub root_url: String, @@ -228,7 +215,7 @@ impl HiPSConfig { Ok(hips_config) } - pub fn set_image_fmt(&mut self, ext: ImageExt) -> Result<(), JsValue> { + pub fn set_image_ext(&mut self, ext: ImageExt) -> Result<(), JsValue> { let format = match ext { ImageExt::Fits => { // Check the bitpix to determine the internal format of the tiles @@ -297,17 +284,6 @@ impl HiPSConfig { self.format = format; - // Recompute if the survey will be colored or not - /*self.colored = if self.tex_storing_fits { - false - } else { - if let Some(subtypes) = &self.dataproduct_subtype { - subtypes.iter().any(|subtype| subtype == "color") - } else { - false - } - };*/ - Ok(()) } @@ -321,6 +297,10 @@ impl HiPSConfig { self.root_url = root_url; } + pub fn get_cube_depth(&self) -> Option { + self.cube_depth + } + #[inline(always)] pub fn set_fits_metadata(&mut self, bscale: f32, bzero: f32, blank: f32) { self.scale = bscale; diff --git a/src/core/src/renderable/hips/d2/buffer.rs b/src/core/src/renderable/hips/d2/buffer.rs index d654eae4c..c7fef0d57 100644 --- a/src/core/src/renderable/hips/d2/buffer.rs +++ b/src/core/src/renderable/hips/d2/buffer.rs @@ -4,11 +4,15 @@ use std::collections::HashMap; use al_core::image::format::ChannelType; +use crate::renderable::hips::HpxTile; use cgmath::Vector3; use al_api::hips::ImageExt; use al_core::webgl_ctx::WebGlRenderingCtx; +use crate::math::lonlat::LonLat; +use crate::CameraViewPort; +use crate::LonLatT; use al_core::image::format::ImageFormat; use al_core::image::format::{R16I, R32F, R32I, R64F, R8UI, RGB8U, RGBA8U}; use al_core::image::Image; @@ -16,12 +20,11 @@ use al_core::shader::{SendUniforms, ShaderBound}; use al_core::Texture2DArray; use al_core::WebGlContext; -use super::texture::{Texture, TextureUniforms}; +use super::texture::{HpxTexture2D, HpxTexture2DUniforms}; use crate::downloader::request::allsky::Allsky; use crate::healpix::cell::HEALPixCell; use crate::healpix::cell::NUM_HPX_TILES_DEPTH_ZERO; -use crate::math::lonlat::LonLatT; use crate::renderable::hips::config::HiPSConfig; use crate::time::Time; use crate::Abort; @@ -58,24 +61,24 @@ impl Ord for TextureCellItem { } } -impl From for TextureCellItem { - fn from(texture: Texture) -> Self { +impl From for TextureCellItem { + fn from(texture: HpxTexture2D) -> Self { let time_request = texture.time_request(); let cell = *texture.cell(); Self { cell, time_request } } } -impl From<&Texture> for TextureCellItem { - fn from(texture: &Texture) -> Self { +impl From<&HpxTexture2D> for TextureCellItem { + fn from(texture: &HpxTexture2D) -> Self { let time_request = texture.time_request(); let cell = *texture.cell(); Self { cell, time_request } } } -impl From<&mut Texture> for TextureCellItem { - fn from(texture: &mut Texture) -> Self { +impl From<&mut HpxTexture2D> for TextureCellItem { + fn from(texture: &mut HpxTexture2D) -> Self { let time_request = texture.time_request(); let cell = *texture.cell(); @@ -121,6 +124,7 @@ impl HEALPixCellHeap { } } +use crate::renderable::hips::HpxTileBuffer; // Fixed sized binary heap pub struct HiPS2DBuffer { // Some information about the HiPS @@ -130,8 +134,8 @@ pub struct HiPS2DBuffer { num_root_textures_available: u8, size: usize, - textures: HashMap, - base_textures: [Texture; NUM_HPX_TILES_DEPTH_ZERO], + textures: HashMap, + base_textures: [HpxTexture2D; NUM_HPX_TILES_DEPTH_ZERO], // Array of 2D textures texture_2d_array: Texture2DArray, @@ -180,110 +184,6 @@ fn create_texture_array( } impl HiPS2DBuffer { - pub fn new(gl: &WebGlContext, config: HiPSConfig) -> Result { - let size = 128 - NUM_HPX_TILES_DEPTH_ZERO; - // Ensures there is at least space for the 12 - // root textures - //debug_assert!(size >= NUM_HPX_TILES_DEPTH_ZERO); - let heap = HEALPixCellHeap::with_capacity(size); - let textures = HashMap::with_capacity(size); - - let now = Time::now(); - let base_textures = [ - Texture::new(&HEALPixCell(0, 0), 0, now), - Texture::new(&HEALPixCell(0, 1), 1, now), - Texture::new(&HEALPixCell(0, 2), 2, now), - Texture::new(&HEALPixCell(0, 3), 3, now), - Texture::new(&HEALPixCell(0, 4), 4, now), - Texture::new(&HEALPixCell(0, 5), 5, now), - Texture::new(&HEALPixCell(0, 6), 6, now), - Texture::new(&HEALPixCell(0, 7), 7, now), - Texture::new(&HEALPixCell(0, 8), 8, now), - Texture::new(&HEALPixCell(0, 9), 9, now), - Texture::new(&HEALPixCell(0, 10), 10, now), - Texture::new(&HEALPixCell(0, 11), 11, now), - ]; - let channel = config.get_format().get_channel(); - - let texture_2d_array = match channel { - ChannelType::RGBA32F => unimplemented!(), - ChannelType::RGB32F => unimplemented!(), - ChannelType::RGBA8U => create_texture_array::(gl, &config)?, - ChannelType::RGB8U => create_texture_array::(gl, &config)?, - ChannelType::R32F => create_texture_array::(gl, &config)?, - #[cfg(feature = "webgl2")] - ChannelType::R8UI => create_texture_array::(gl, &config)?, - #[cfg(feature = "webgl2")] - ChannelType::R16I => create_texture_array::(gl, &config)?, - #[cfg(feature = "webgl2")] - ChannelType::R32I => create_texture_array::(gl, &config)?, - #[cfg(feature = "webgl2")] - ChannelType::R64F => create_texture_array::(gl, &config)?, - }; - // The root textures have not been loaded - - let num_root_textures_available = 0; - let available_tiles_during_frame = false; - - Ok(HiPS2DBuffer { - config, - heap, - - size, - num_root_textures_available, - textures, - base_textures, - texture_2d_array, - available_tiles_during_frame, - }) - } - - pub fn set_format(&mut self, gl: &WebGlContext, ext: ImageExt) -> Result<(), JsValue> { - self.config.set_image_fmt(ext)?; - - let channel = self.config.get_format().get_channel(); - - self.texture_2d_array = match channel { - ChannelType::RGBA32F => unimplemented!(), - ChannelType::RGB32F => unimplemented!(), - ChannelType::RGBA8U => create_texture_array::(gl, &self.config)?, - ChannelType::RGB8U => create_texture_array::(gl, &self.config)?, - ChannelType::R32F => create_texture_array::(gl, &self.config)?, - #[cfg(feature = "webgl2")] - ChannelType::R8UI => create_texture_array::(gl, &self.config)?, - #[cfg(feature = "webgl2")] - ChannelType::R16I => create_texture_array::(gl, &self.config)?, - #[cfg(feature = "webgl2")] - ChannelType::R32I => create_texture_array::(gl, &self.config)?, - #[cfg(feature = "webgl2")] - ChannelType::R64F => create_texture_array::(gl, &self.config)?, - }; - - let now = Time::now(); - self.base_textures = [ - Texture::new(&HEALPixCell(0, 0), 0, now), - Texture::new(&HEALPixCell(0, 1), 1, now), - Texture::new(&HEALPixCell(0, 2), 2, now), - Texture::new(&HEALPixCell(0, 3), 3, now), - Texture::new(&HEALPixCell(0, 4), 4, now), - Texture::new(&HEALPixCell(0, 5), 5, now), - Texture::new(&HEALPixCell(0, 6), 6, now), - Texture::new(&HEALPixCell(0, 7), 7, now), - Texture::new(&HEALPixCell(0, 8), 8, now), - Texture::new(&HEALPixCell(0, 9), 9, now), - Texture::new(&HEALPixCell(0, 10), 10, now), - Texture::new(&HEALPixCell(0, 11), 11, now), - ]; - - self.heap.clear(); - self.textures.clear(); - //self.ready = false; - self.num_root_textures_available = 0; - self.available_tiles_during_frame = false; - - Ok(()) - } - pub fn push_allsky(&mut self, allsky: Allsky) -> Result<(), JsValue> { let Allsky { image, @@ -303,8 +203,72 @@ impl HiPS2DBuffer { Ok(()) } - // This method pushes a new downloaded tile into the buffer - // It must be ensured that the tile is not already contained into the buffer + // Check whether the buffer has a tile + // For that purpose, we first need to verify that its + // texture ancestor exists and then, it it contains the tile + pub fn contains_tile(&self, cell: &HEALPixCell) -> bool { + let dd = self.config.delta_depth(); + + let texture_cell = cell.get_texture_cell(dd); + + let tex_cell_is_root = texture_cell.is_root(); + if tex_cell_is_root { + let HEALPixCell(_, idx) = texture_cell; + self.base_textures[idx as usize].contains_tile(cell) + } else { + if let Some(texture) = self.get(&texture_cell) { + // The texture is present in the buffer + // We must check whether it contains the tile + texture.contains_tile(cell) + } else { + // The texture in which cell should be is not present + false + } + } + } + + fn is_heap_full(&self) -> bool { + // Check that there are no more than num_textures + // textures in the buffer + let num_textures_heap = self.heap.len(); + + num_textures_heap == self.size + } + + // Update the priority of the texture containing the tile + // It must be ensured that the tile is already contained in the buffer + pub fn update_priority(&mut self, cell: &HEALPixCell /*, new_fov_cell: bool*/) { + debug_assert!(self.contains_tile(cell)); + + let dd = self.config.delta_depth(); + + // Get the texture cell in which the tile has to be + let texture_cell = cell.get_texture_cell(dd); + if texture_cell.is_root() { + return; + } + + let texture = self + .textures + .get_mut(&texture_cell) + .expect("Texture cell has not been found while the buffer contains one of its tile!"); + // Reset the time the tile has been received if it is a new cell present in the fov + //if new_fov_cell { + // texture.update_start_time(Time::now()); + //} + + // MAYBE WE DO NOT NEED TO UPDATE THE TIME REQUEST IN THE BHEAP + // BECAUSE IT INTRODUCES UNECESSARY CODE COMPLEXITY + // Root textures are always in the buffer + // But other textures can be removed thanks to the heap + // data-structure. We have to update the time_request of the texture + // and push it again in the heap to update its position. + let mut tex_cell_item: TextureCellItem = texture.into(); + tex_cell_item.time_request = Time::now(); + + self.heap.update_entry(tex_cell_item); + } + pub fn push( &mut self, cell: &HEALPixCell, @@ -336,7 +300,7 @@ impl HiPS2DBuffer { } else { let idx = NUM_HPX_TILES_DEPTH_ZERO + self.heap.len(); - Texture::new(&tex_cell, idx as i32, time_request) + HpxTexture2D::new(&tex_cell, idx as i32, time_request) }; // Push it to the buffer @@ -385,102 +349,171 @@ impl HiPS2DBuffer { Ok(()) } +} - // Return if tiles did become available - pub fn reset_available_tiles(&mut self) -> bool { - let available_tiles_during_frame = self.available_tiles_during_frame; +impl HpxTileBuffer for HiPS2DBuffer { + type T = HpxTexture2D; + + fn new(gl: &WebGlContext, config: HiPSConfig) -> Result { + let size = 128 - NUM_HPX_TILES_DEPTH_ZERO; + // Ensures there is at least space for the 12 + // root textures + //debug_assert!(size >= NUM_HPX_TILES_DEPTH_ZERO); + let heap = HEALPixCellHeap::with_capacity(size); + let textures = HashMap::with_capacity(size); + + let now = Time::now(); + let base_textures = [ + HpxTexture2D::new(&HEALPixCell(0, 0), 0, now), + HpxTexture2D::new(&HEALPixCell(0, 1), 1, now), + HpxTexture2D::new(&HEALPixCell(0, 2), 2, now), + HpxTexture2D::new(&HEALPixCell(0, 3), 3, now), + HpxTexture2D::new(&HEALPixCell(0, 4), 4, now), + HpxTexture2D::new(&HEALPixCell(0, 5), 5, now), + HpxTexture2D::new(&HEALPixCell(0, 6), 6, now), + HpxTexture2D::new(&HEALPixCell(0, 7), 7, now), + HpxTexture2D::new(&HEALPixCell(0, 8), 8, now), + HpxTexture2D::new(&HEALPixCell(0, 9), 9, now), + HpxTexture2D::new(&HEALPixCell(0, 10), 10, now), + HpxTexture2D::new(&HEALPixCell(0, 11), 11, now), + ]; + let channel = config.get_format().get_channel(); + + let texture_2d_array = match channel { + ChannelType::RGBA32F => unimplemented!(), + ChannelType::RGB32F => unimplemented!(), + ChannelType::RGBA8U => create_texture_array::(gl, &config)?, + ChannelType::RGB8U => create_texture_array::(gl, &config)?, + ChannelType::R32F => create_texture_array::(gl, &config)?, + #[cfg(feature = "webgl2")] + ChannelType::R8UI => create_texture_array::(gl, &config)?, + #[cfg(feature = "webgl2")] + ChannelType::R16I => create_texture_array::(gl, &config)?, + #[cfg(feature = "webgl2")] + ChannelType::R32I => create_texture_array::(gl, &config)?, + #[cfg(feature = "webgl2")] + ChannelType::R64F => create_texture_array::(gl, &config)?, + }; + // The root textures have not been loaded + + let num_root_textures_available = 0; + let available_tiles_during_frame = false; + + Ok(HiPS2DBuffer { + config, + heap, + + size, + num_root_textures_available, + textures, + base_textures, + texture_2d_array, + available_tiles_during_frame, + }) + } + + fn set_image_ext(&mut self, gl: &WebGlContext, ext: ImageExt) -> Result<(), JsValue> { + self.config.set_image_ext(ext)?; + + let channel = self.config.get_format().get_channel(); + + self.texture_2d_array = match channel { + ChannelType::RGBA32F => unimplemented!(), + ChannelType::RGB32F => unimplemented!(), + ChannelType::RGBA8U => create_texture_array::(gl, &self.config)?, + ChannelType::RGB8U => create_texture_array::(gl, &self.config)?, + ChannelType::R32F => create_texture_array::(gl, &self.config)?, + #[cfg(feature = "webgl2")] + ChannelType::R8UI => create_texture_array::(gl, &self.config)?, + #[cfg(feature = "webgl2")] + ChannelType::R16I => create_texture_array::(gl, &self.config)?, + #[cfg(feature = "webgl2")] + ChannelType::R32I => create_texture_array::(gl, &self.config)?, + #[cfg(feature = "webgl2")] + ChannelType::R64F => create_texture_array::(gl, &self.config)?, + }; + + let now = Time::now(); + self.base_textures = [ + HpxTexture2D::new(&HEALPixCell(0, 0), 0, now), + HpxTexture2D::new(&HEALPixCell(0, 1), 1, now), + HpxTexture2D::new(&HEALPixCell(0, 2), 2, now), + HpxTexture2D::new(&HEALPixCell(0, 3), 3, now), + HpxTexture2D::new(&HEALPixCell(0, 4), 4, now), + HpxTexture2D::new(&HEALPixCell(0, 5), 5, now), + HpxTexture2D::new(&HEALPixCell(0, 6), 6, now), + HpxTexture2D::new(&HEALPixCell(0, 7), 7, now), + HpxTexture2D::new(&HEALPixCell(0, 8), 8, now), + HpxTexture2D::new(&HEALPixCell(0, 9), 9, now), + HpxTexture2D::new(&HEALPixCell(0, 10), 10, now), + HpxTexture2D::new(&HEALPixCell(0, 11), 11, now), + ]; + + self.heap.clear(); + self.textures.clear(); + //self.ready = false; + self.num_root_textures_available = 0; self.available_tiles_during_frame = false; - available_tiles_during_frame + Ok(()) } - fn is_heap_full(&self) -> bool { - // Check that there are no more than num_textures - // textures in the buffer - let num_textures_heap = self.heap.len(); + // This method pushes a new downloaded tile into the buffer + // It must be ensured that the tile is not already contained into the buffer + // Return if tiles did become available + fn reset_available_tiles(&mut self) -> bool { + let available_tiles_during_frame = self.available_tiles_during_frame; + self.available_tiles_during_frame = false; - num_textures_heap == self.size + available_tiles_during_frame } // Tell if a texture is available meaning all its sub tiles // must have been written for the GPU - pub fn contains(&self, texture_cell: &HEALPixCell) -> bool { - if let Some(t) = self.get(texture_cell) { + fn contains(&self, cell: &HEALPixCell) -> bool { + if let Some(t) = self.get(cell) { t.is_full() } else { false } } - // Check whether the buffer has a tile - // For that purpose, we first need to verify that its - // texture ancestor exists and then, it it contains the tile - pub fn contains_tile(&self, cell: &HEALPixCell) -> bool { - let dd = self.config.delta_depth(); - - let texture_cell = cell.get_texture_cell(dd); - - let tex_cell_is_root = texture_cell.is_root(); - if tex_cell_is_root { - let HEALPixCell(_, idx) = texture_cell; - self.base_textures[idx as usize].contains(cell) + /// Accessors + fn get(&self, cell: &HEALPixCell) -> Option<&Self::T> { + if cell.is_root() { + let HEALPixCell(_, idx) = cell; + Some(&self.base_textures[*idx as usize]) } else { - if let Some(texture) = self.get(&texture_cell) { - // The texture is present in the buffer - // We must check whether it contains the tile - texture.contains(cell) - } else { - // The texture in which cell should be is not present - false - } + self.textures.get(cell) } } - // Update the priority of the texture containing the tile - // It must be ensured that the tile is already contained in the buffer - pub fn update_priority(&mut self, cell: &HEALPixCell /*, new_fov_cell: bool*/) { - debug_assert!(self.contains_tile(cell)); + fn config(&self) -> &HiPSConfig { + &self.config + } - let dd = self.config.delta_depth(); + fn config_mut(&mut self) -> &mut HiPSConfig { + &mut self.config + } - // Get the texture cell in which the tile has to be - let texture_cell = cell.get_texture_cell(dd); - if texture_cell.is_root() { - return; - } + fn read_pixel(&self, pos: &LonLatT, camera: &CameraViewPort) -> Result { + // 1. Convert it to the hips frame system + let cfg = self.config(); + let camera_frame = camera.get_coo_system(); + let hips_frame = cfg.get_frame(); - let texture = self - .textures - .get_mut(&texture_cell) - .expect("Texture cell has not been found while the buffer contains one of its tile!"); - // Reset the time the tile has been received if it is a new cell present in the fov - //if new_fov_cell { - // texture.update_start_time(Time::now()); - //} + let pos: LonLatT = + crate::coosys::apply_coo_system(camera_frame, hips_frame, &pos.vector()).lonlat(); - // MAYBE WE DO NOT NEED TO UPDATE THE TIME REQUEST IN THE BHEAP - // BECAUSE IT INTRODUCES UNECESSARY CODE COMPLEXITY - // Root textures are always in the buffer - // But other textures can be removed thanks to the heap - // data-structure. We have to update the time_request of the texture - // and push it again in the heap to update its position. - let mut tex_cell_item: TextureCellItem = texture.into(); - tex_cell_item.time_request = Time::now(); + // Get the array of textures from that survey + let depth = camera.get_texture_depth().min(cfg.get_max_depth_texture()); - self.heap.update_entry(tex_cell_item); - } - - // lonlat is given in the - pub fn get_pixel_position_in_texture( - &self, - lonlat: &LonLatT, - depth: u8, - ) -> Result, JsValue> { - let (pix, dx, dy) = crate::healpix::utils::hash_with_dxdy(depth, lonlat); + // compute the tex + let (pix, dx, dy) = crate::healpix::utils::hash_with_dxdy(depth, &pos); let texture_cell = HEALPixCell(depth, pix); if let Some(texture) = self.get(&texture_cell) { - let cfg = &self.config; + let cfg = self.config(); // Index of the texture in the total set of textures let texture_idx = texture.idx(); @@ -489,24 +522,38 @@ impl HiPS2DBuffer { let texture_size = cfg.get_texture_size(); // Offset in the slice in pixels - let mut offset = Vector3::new( + let mut pos_tex = Vector3::new( (dy * (texture_size as f64)) as i32, (dx * (texture_size as f64)) as i32, texture_idx, ); // Offset in the slice in pixels - if self.config.tex_storing_fits { - let texture_size = self.config.get_texture_size() as f32; - let mut uvy = offset.y as f32 / texture_size; - uvy = self.config.size_tile_uv - + 2.0 * self.config.size_tile_uv * (uvy / self.config.size_tile_uv).floor() + if cfg.tex_storing_fits { + let texture_size = cfg.get_texture_size() as f32; + let mut uvy = pos_tex.y as f32 / texture_size; + uvy = cfg.size_tile_uv + 2.0 * cfg.size_tile_uv * (uvy / cfg.size_tile_uv).floor() - uvy; - offset.y = (uvy * texture_size) as i32; + pos_tex.y = (uvy * texture_size) as i32; + } + + let mut value = self + .texture_2d_array + .read_pixel(pos_tex.x, pos_tex.y, pos_tex.z)?; + + if cfg.tex_storing_fits { + // scale the value + let f64_v = value + .as_f64() + .ok_or_else(|| JsValue::from_str("Error unwraping the pixel read value."))?; + let scale = cfg.scale as f64; + let offset = cfg.offset as f64; + + value = JsValue::from_f64(f64_v * scale + offset); } - Ok(offset) + Ok(value) } else { Err(JsValue::from_str(&format!( "{:?} not loaded in the GPU, please wait before trying again.", @@ -514,53 +561,11 @@ impl HiPS2DBuffer { ))) } } - - /// Accessors - pub fn get(&self, texture_cell: &HEALPixCell) -> Option<&Texture> { - if texture_cell.is_root() { - let HEALPixCell(_, idx) = texture_cell; - Some(&self.base_textures[*idx as usize]) - } else { - self.textures.get(texture_cell) - } - } - - // Get the nearest parent tile found in the CPU buffer - pub fn get_nearest_parent(&self, cell: &HEALPixCell) -> Option { - if cell.is_root() { - // Root cells are in the buffer by definition - Some(*cell) - } else { - let mut parent_cell = cell.parent(); - - while !self.contains(&parent_cell) && !parent_cell.is_root() { - parent_cell = parent_cell.parent(); - } - - if self.contains(&parent_cell) { - Some(parent_cell) - } else { - None - } - } - } - - pub fn config(&self) -> &HiPSConfig { - &self.config - } - - pub fn config_mut(&mut self) -> &mut HiPSConfig { - &mut self.config - } - - pub fn get_texture_array(&self) -> &Texture2DArray { - &self.texture_2d_array - } } fn send_to_gpu( cell: &HEALPixCell, - texture: &Texture, + texture: &HpxTexture2D, image: I, texture_array: &Texture2DArray, cfg: &mut HiPSConfig, @@ -605,7 +610,7 @@ impl SendUniforms for HiPS2DBuffer { let cell = HEALPixCell(0, idx as u64); let texture = self.get(&cell).unwrap(); - let texture_uniforms = TextureUniforms::new(texture, idx as i32); + let texture_uniforms = HpxTexture2DUniforms::new(texture, idx as i32); shader.attach_uniforms_from(&texture_uniforms); } //} diff --git a/src/core/src/renderable/hips/d2/mod.rs b/src/core/src/renderable/hips/d2/mod.rs index 453a9432c..b29e59a52 100644 --- a/src/core/src/renderable/hips/d2/mod.rs +++ b/src/core/src/renderable/hips/d2/mod.rs @@ -1,12 +1,15 @@ pub mod buffer; pub mod texture; +use crate::renderable::hips::HpxTile; use al_api::hips::ImageExt; use al_api::hips::ImageMetadata; use al_core::colormap::Colormap; use al_core::colormap::Colormaps; use al_core::image::format::ChannelType; +use crate::downloader::query; + use al_core::image::Image; use al_core::shader::Shader; @@ -26,7 +29,6 @@ use crate::{math::lonlat::LonLatT, utils}; use crate::downloader::request::allsky::Allsky; use crate::healpix::{cell::HEALPixCell, coverage::HEALPixCoverage}; -use crate::math::lonlat::LonLat; use crate::renderable::utils::index_patch::DefaultPatchIndexIter; use crate::time::Time; @@ -37,7 +39,7 @@ use std::collections::HashSet; // to not be too much skewed use buffer::HiPS2DBuffer; -use texture::Texture; +use texture::HpxTexture2D; use super::raytracing::RayTracer; use super::uv::{TileCorner, TileUVW}; @@ -47,76 +49,16 @@ use cgmath::Matrix; use wasm_bindgen::JsValue; use web_sys::WebGl2RenderingContext; -const M: f64 = 280.0 * 280.0; -const N: f64 = 150.0 * 150.0; -const RAP: f64 = 0.7; - -fn is_too_large(cell: &HEALPixCell, camera: &CameraViewPort, projection: &ProjectionType) -> bool { - let vertices = cell - .vertices() - .iter() - .filter_map(|(lon, lat)| { - let vertex = crate::math::lonlat::radec_to_xyzw(Angle(*lon), Angle(*lat)); - projection.icrs_celestial_to_screen_space(&vertex, camera) - }) - .collect::>(); - - if vertices.len() < 4 { - false - } else { - let d1 = dist2(vertices[0].as_ref(), &vertices[2].as_ref()); - let d2 = dist2(vertices[1].as_ref(), &vertices[3].as_ref()); - if d1 > M || d2 > M { - true - } else if d1 < N && d2 < N { - false - } else { - let rap = if d2 > d1 { d1 / d2 } else { d2 / d1 }; - - rap < RAP - } - } -} - -fn num_subdivision(cell: &HEALPixCell, camera: &CameraViewPort, projection: &ProjectionType) -> u8 { - let d = cell.depth(); - // Subdivide all cells at least one time. - // TODO: use a single subdivision number computed from the current cells inside the view - // i.e. subdivide all cells in the view with the cell that has to be the most subdivided - let mut num_sub = 1; - if d < 2 { - num_sub = 2 - d; - } - - // Largest deformation cell among the cells of a specific depth - let largest_center_to_vertex_dist = - healpix::largest_center_to_vertex_distance(d, 0.0, healpix::TRANSITION_LATITUDE); - let smallest_center_to_vertex_dist = - healpix::largest_center_to_vertex_distance(d, 0.0, healpix::LAT_OF_SQUARE_CELL); - - let (lon, lat) = cell.center(); - let center_to_vertex_dist = healpix::largest_center_to_vertex_distance(d, lon, lat); - - let skewed_factor = (center_to_vertex_dist - smallest_center_to_vertex_dist) - / (largest_center_to_vertex_dist - smallest_center_to_vertex_dist); - - if skewed_factor > 0.25 || is_too_large(cell, camera, projection) || cell.is_on_pole() { - num_sub += 1; - } - - num_sub -} - pub struct TextureToDraw<'a, 'b> { - pub starting_texture: &'a Texture, - pub ending_texture: &'a Texture, + pub starting_texture: &'a HpxTexture2D, + pub ending_texture: &'a HpxTexture2D, pub cell: &'b HEALPixCell, } impl<'a, 'b> TextureToDraw<'a, 'b> { fn new( - starting_texture: &'a Texture, - ending_texture: &'a Texture, + starting_texture: &'a HpxTexture2D, + ending_texture: &'a HpxTexture2D, cell: &'b HEALPixCell, ) -> TextureToDraw<'a, 'b> { TextureToDraw { @@ -209,7 +151,7 @@ pub fn get_raytracer_shader<'a>( pub struct HiPS2D { //color: Color, // The image survey texture buffer - textures: HiPS2DBuffer, + buffer: HiPS2DBuffer, // The projected vertices data // For WebGL2 wasm, the data are interleaved @@ -241,6 +183,8 @@ pub struct HiPS2D { hpx_cells_in_view: Vec, } +use super::HpxTileBuffer; + impl HiPS2D { pub fn new(config: HiPSConfig, gl: &WebGlContext) -> Result { let mut vao = VertexArrayObject::new(gl); @@ -294,7 +238,7 @@ impl HiPS2D { .unbind(); let num_idx = 0; - let textures = HiPS2DBuffer::new(gl, config)?; + let buffer = HiPS2DBuffer::new(gl, config)?; let gl = gl.clone(); let footprint_moc = None; @@ -302,7 +246,7 @@ impl HiPS2D { // request the allsky texture Ok(Self { // The image survey texture buffer - textures, + buffer, num_idx, vao, @@ -382,7 +326,12 @@ impl HiPS2D { } pub fn contains_tile(&self, cell: &HEALPixCell) -> bool { - self.textures.contains_tile(cell) + self.buffer.contains_tile(cell) + } + + pub fn get_tile_query(&self, cell: &HEALPixCell) -> query::Tile { + let cfg = self.get_config(); + query::Tile::new(cell, None, cfg) } pub fn update(&mut self, camera: &mut CameraViewPort, projection: &ProjectionType) { @@ -393,7 +342,7 @@ impl HiPS2D { } // rasterizer mode - let available_tiles = self.textures.reset_available_tiles(); + let available_tiles = self.buffer.reset_available_tiles(); let new_cells_in_view = self.retrieve_cells_in_camera(camera); if new_cells_in_view || available_tiles { @@ -434,51 +383,21 @@ impl HiPS2D { self.footprint_moc.as_ref() } - pub fn set_img_format(&mut self, ext: ImageExt) -> Result<(), JsValue> { - self.textures.set_format(&self.gl, ext) + pub fn set_image_ext(&mut self, ext: ImageExt) -> Result<(), JsValue> { + self.buffer.set_image_ext(&self.gl, ext) } pub fn is_allsky(&self) -> bool { - self.textures.config().is_allsky + self.buffer.config().is_allsky } // Position given is in the camera space pub fn read_pixel( &self, - pos: &LonLatT, + p: &LonLatT, camera: &CameraViewPort, ) -> Result { - // 1. Convert it to the hips frame system - let cfg = self.textures.config(); - let camera_frame = camera.get_coo_system(); - let hips_frame = cfg.get_frame(); - - let pos = crate::coosys::apply_coo_system(camera_frame, hips_frame, &pos.vector()); - - // Get the array of textures from that survey - let tile_depth = camera.get_texture_depth().min(cfg.get_max_depth_texture()); - - let pos_tex = self - .textures - .get_pixel_position_in_texture(&pos.lonlat(), tile_depth)?; - - let slice_idx = pos_tex.z as usize; - let texture_array = self.textures.get_texture_array(); - - unimplemented!(); - /*let value = texture_array[slice_idx].read_pixel(pos_tex.x, pos_tex.y)?; - - if cfg.tex_storing_fits { - let value = value - .as_f64() - .ok_or_else(|| JsValue::from_str("Error unwraping the pixel read value."))?; - let scale = cfg.scale as f64; - let offset = cfg.offset as f64; - - Ok(JsValue::from_f64(value * scale + offset)) - } else { - Ok(value) - }*/ + self.buffer.read_pixel(p, camera) } fn recompute_vertices(&mut self, camera: &mut CameraViewPort, projection: &ProjectionType) { @@ -488,7 +407,7 @@ impl HiPS2D { self.time_tile_received.clear(); self.idx_vertices.clear(); - let cfg = self.textures.config(); + let cfg = self.buffer.config(); // Get the coo system transformation matrix let channel = cfg.get_format().get_channel(); @@ -516,10 +435,10 @@ impl HiPS2D { }; if let Some(cell) = cell { - let texture_to_draw = if self.textures.contains(cell) { - if let Some(ending_cell_in_tex) = self.textures.get(cell) { - if let Some(parent_cell) = self.textures.get_nearest_parent(cell) { - if let Some(starting_cell_in_tex) = self.textures.get(&parent_cell) { + let texture_to_draw = if self.buffer.contains(cell) { + if let Some(ending_cell_in_tex) = self.buffer.get(cell) { + if let Some(parent_cell) = self.buffer.get_nearest_parent(cell) { + if let Some(starting_cell_in_tex) = self.buffer.get(&parent_cell) { Some(TextureToDraw::new( starting_cell_in_tex, ending_cell_in_tex, @@ -544,13 +463,13 @@ impl HiPS2D { None } } else { - if let Some(parent_cell) = self.textures.get_nearest_parent(cell) { - if let Some(ending_cell_in_tex) = self.textures.get(&parent_cell) { + if let Some(parent_cell) = self.buffer.get_nearest_parent(cell) { + if let Some(ending_cell_in_tex) = self.buffer.get(&parent_cell) { if let Some(grand_parent_cell) = - self.textures.get_nearest_parent(&parent_cell) + self.buffer.get_nearest_parent(&parent_cell) { if let Some(starting_cell_in_tex) = - self.textures.get(&grand_parent_cell) + self.buffer.get(&grand_parent_cell) { Some(TextureToDraw::new( starting_cell_in_tex, @@ -595,7 +514,8 @@ impl HiPS2D { let start_time = ending_texture.start_time().as_millis(); - let num_subdivision = num_subdivision(cell, camera, projection); + let num_subdivision = + super::subdivide::num_hpxcell_subdivision(cell, camera, projection); let n_segments_by_side: usize = 1 << (num_subdivision as usize); let n_segments_by_side_f32 = n_segments_by_side as f32; @@ -693,9 +613,9 @@ impl HiPS2D { // Return a boolean to signal if the tile is present or not in the survey pub fn update_priority_tile(&mut self, cell: &HEALPixCell) -> bool { - if self.textures.contains_tile(cell) { + if self.buffer.contains_tile(cell) { // The cell is present in the survey, we update its priority - self.textures.update_priority(cell); + self.buffer.update_priority(cell); true } else { false @@ -708,22 +628,22 @@ impl HiPS2D { image: I, time_request: Time, ) -> Result<(), JsValue> { - self.textures.push(&cell, image, time_request) + self.buffer.push(&cell, image, time_request) } pub fn add_allsky(&mut self, allsky: Allsky) -> Result<(), JsValue> { - self.textures.push_allsky(allsky) + self.buffer.push_allsky(allsky) } /* Accessors */ #[inline] pub fn get_config(&self) -> &HiPSConfig { - self.textures.config() + self.buffer.config() } #[inline] pub fn get_config_mut(&mut self) -> &mut HiPSConfig { - self.textures.config_mut() + self.buffer.config_mut() } pub fn draw( @@ -737,7 +657,7 @@ impl HiPS2D { ) -> Result<(), JsValue> { // Get the coo system transformation matrix let selected_frame = camera.get_coo_system(); - let hips_cfg = self.textures.config(); + let hips_cfg = self.buffer.config(); let hips_frame = hips_cfg.get_frame(); let c = selected_frame.to(hips_frame); @@ -768,7 +688,7 @@ impl HiPS2D { let shader = shader.bind(&self.gl); shader .attach_uniforms_from(camera) - .attach_uniforms_from(&self.textures) + .attach_uniforms_from(&self.buffer) // send the cmap appart from the color config .attach_uniforms_with_params_from(cmap, colormaps) .attach_uniforms_from(color) @@ -796,7 +716,7 @@ impl HiPS2D { let shader = get_raster_shader(cmap, &self.gl, shaders, &config)?.bind(&self.gl); shader - .attach_uniforms_from(&self.textures) + .attach_uniforms_from(&self.buffer) // send the cmap appart from the color config .attach_uniforms_with_params_from(cmap, colormaps) .attach_uniforms_from(color) diff --git a/src/core/src/renderable/hips/d2/texture.rs b/src/core/src/renderable/hips/d2/texture.rs index d1ffa008a..30701825c 100644 --- a/src/core/src/renderable/hips/d2/texture.rs +++ b/src/core/src/renderable/hips/d2/texture.rs @@ -2,7 +2,7 @@ use crate::{healpix::cell::HEALPixCell, time::Time}; use std::collections::HashSet; -pub struct Texture { +pub struct HpxTexture2D { texture_cell: HEALPixCell, // Precomputed uniq number uniq: i32, @@ -36,17 +36,20 @@ pub struct Texture { use crate::renderable::hips::config::HiPSConfig; -impl Texture { - pub fn new(texture_cell: &HEALPixCell, idx: i32, time_request: Time) -> Texture { +use crate::renderable::hips::HpxTile; + +impl HpxTexture2D { + pub fn new(cell: &HEALPixCell, idx: i32, time_request: Time) -> Self { let tiles = HashSet::new(); let start_time = None; let full = false; - let texture_cell = *texture_cell; + let texture_cell = *cell; let uniq = texture_cell.uniq(); //let missing = true; let num_tiles_written = 0; - Texture { + + Self { texture_cell, uniq, time_request, @@ -55,14 +58,42 @@ impl Texture { start_time, full, num_tiles_written, - //missing, } } + pub fn is_full(&self) -> bool { + self.full + } + + pub fn idx(&self) -> i32 { + self.idx + } + + // Setter + pub fn replace(&mut self, texture_cell: &HEALPixCell, time_request: Time) { + // Cancel the tasks copying the tiles contained in the texture + // which have not yet been completed. + //self.clear_tasks_in_progress(config, exec); + + self.texture_cell = *texture_cell; + self.uniq = texture_cell.uniq(); + self.full = false; + self.start_time = None; + self.time_request = time_request; + self.tiles.clear(); + //self.missing = true; + self.num_tiles_written = 0; + } + + // Cell must be contained in the texture + pub fn contains_tile(&self, tile_cell: &HEALPixCell) -> bool { + self.is_full() || self.tiles.contains(tile_cell) + } + // Panic if cell is not contained in the texture // Do nothing if the texture is full // Return true if the tile is newly added - pub fn append(&mut self, cell: &HEALPixCell, cfg: &HiPSConfig /*, missing: bool */) { + pub fn append(&mut self, cell: &HEALPixCell, cfg: &HiPSConfig) { let texture_cell = cell.get_texture_cell(cfg.delta_depth()); debug_assert!(texture_cell == self.texture_cell); debug_assert!(!self.full); @@ -95,19 +126,12 @@ impl Texture { } } } +} - // Cell must be contained in the texture - pub fn contains(&self, cell: &HEALPixCell) -> bool { - self.is_full() || self.tiles.contains(cell) - } - - pub fn is_full(&self) -> bool { - self.full - } - +impl HpxTile for HpxTexture2D { // Getter // Returns the current time if the texture is not full - pub fn start_time(&self) -> Time { + fn start_time(&self) -> Time { if self.is_full() { self.start_time.unwrap_abort() } else { @@ -115,80 +139,49 @@ impl Texture { } } - pub fn time_request(&self) -> Time { + fn time_request(&self) -> Time { self.time_request } - pub fn cell(&self) -> &HEALPixCell { + fn cell(&self) -> &HEALPixCell { &self.texture_cell } - - pub fn idx(&self) -> i32 { - self.idx - } - - /*pub fn is_missing(&self) -> bool { - self.missing - }*/ - - // Setter - pub fn replace(&mut self, texture_cell: &HEALPixCell, time_request: Time) { - // Cancel the tasks copying the tiles contained in the texture - // which have not yet been completed. - //self.clear_tasks_in_progress(config, exec); - - self.texture_cell = *texture_cell; - self.uniq = texture_cell.uniq(); - self.full = false; - self.start_time = None; - self.time_request = time_request; - self.tiles.clear(); - //self.missing = true; - self.num_tiles_written = 0; - } - - /*pub fn clear_tasks_in_progress(&self, config: &HiPSConfig, exec: &mut TaskExecutor) { - for tile_cell in self.texture_cell.get_tile_cells(config) { - let tile = Tile::new(&tile_cell, config); - exec.remove(&TaskType::ImageTile2GpuTask(tile)); - } - }*/ } use std::cmp::Ordering; -impl PartialOrd for Texture { +impl PartialOrd for HpxTexture2D { fn partial_cmp(&self, other: &Self) -> Option { self.uniq.partial_cmp(&other.uniq) } } use crate::Abort; -impl Ord for Texture { +impl Ord for HpxTexture2D { fn cmp(&self, other: &Self) -> Ordering { self.partial_cmp(other).unwrap_abort() } } -impl PartialEq for Texture { +impl PartialEq for HpxTexture2D { fn eq(&self, other: &Self) -> bool { self.uniq == other.uniq } } -impl Eq for Texture {} +impl Eq for HpxTexture2D {} -pub struct TextureUniforms<'a> { - texture: &'a Texture, +pub struct HpxTexture2DUniforms<'a> { + texture: &'a HpxTexture2D, name: String, } -impl<'a> TextureUniforms<'a> { - pub fn new(texture: &Texture, idx_texture: i32) -> TextureUniforms { +impl<'a> HpxTexture2DUniforms<'a> { + pub fn new(texture: &'a HpxTexture2D, idx_texture: i32) -> Self { let name = format!("textures_tiles[{}].", idx_texture); - TextureUniforms { texture, name } + HpxTexture2DUniforms { texture, name } } } use al_core::shader::{SendUniforms, ShaderBound}; -impl<'a> SendUniforms for TextureUniforms<'a> { +impl<'a> SendUniforms for HpxTexture2DUniforms<'a> { fn attach_uniforms<'b>(&self, shader: &'b ShaderBound<'b>) -> &'b ShaderBound<'b> { shader .attach_uniform(&format!("{}{}", self.name, "uniq"), &self.texture.uniq) diff --git a/src/core/src/renderable/hips/d3/buffer.rs b/src/core/src/renderable/hips/d3/buffer.rs index 6e9f6a92c..f16cafca7 100644 --- a/src/core/src/renderable/hips/d3/buffer.rs +++ b/src/core/src/renderable/hips/d3/buffer.rs @@ -1,34 +1,23 @@ -use std::cmp::Ordering; -use std::collections::BinaryHeap; use std::collections::HashMap; -use al_core::image::format::ChannelType; - -use cgmath::Vector3; - -use al_api::hips::ImageExt; -use al_core::webgl_ctx::WebGlRenderingCtx; - -use al_core::image::format::ImageFormat; -use al_core::image::format::{R16I, R32F, R32I, R64F, R8UI, RGB8U, RGBA8U}; +use crate::CameraViewPort; +use crate::LonLatT; use al_core::image::Image; -use al_core::shader::{SendUniforms, ShaderBound}; -use al_core::Texture2DArray; use al_core::WebGlContext; -use super::texture::HEALPixTexturedCube; +use super::texture::HpxTexture3D; use crate::downloader::request::allsky::Allsky; use crate::healpix::cell::HEALPixCell; -use crate::healpix::cell::NUM_HPX_TILES_DEPTH_ZERO; -use crate::math::lonlat::LonLatT; use crate::renderable::hips::config::HiPSConfig; +use crate::renderable::hips::HpxTileBuffer; use crate::time::Time; use crate::Abort; use crate::JsValue; +use al_api::hips::ImageExt; // Fixed sized binary heap pub struct HiPS3DBuffer { // Some information about the HiPS - textures: HashMap, + textures: HashMap, config: HiPSConfig, num_root_textures_available: u8, @@ -56,58 +45,12 @@ impl HiPS3DBuffer { }) } - /* - pub fn set_format(&mut self, gl: &WebGlContext, ext: ImageExt) -> Result<(), JsValue> { - self.config.set_image_fmt(ext)?; - - let channel = self.config.get_format().get_channel(); - - self.texture_2d_array = match channel { - ChannelType::RGBA32F => unimplemented!(), - ChannelType::RGB32F => unimplemented!(), - ChannelType::RGBA8U => create_texture_array::(gl, &self.config)?, - ChannelType::RGB8U => create_texture_array::(gl, &self.config)?, - ChannelType::R32F => create_texture_array::(gl, &self.config)?, - #[cfg(feature = "webgl2")] - ChannelType::R8UI => create_texture_array::(gl, &self.config)?, - #[cfg(feature = "webgl2")] - ChannelType::R16I => create_texture_array::(gl, &self.config)?, - #[cfg(feature = "webgl2")] - ChannelType::R32I => create_texture_array::(gl, &self.config)?, - #[cfg(feature = "webgl2")] - ChannelType::R64F => create_texture_array::(gl, &self.config)?, - }; - - let now = Time::now(); - self.base_textures = [ - Texture::new(&HEALPixCell(0, 0), 0, now), - Texture::new(&HEALPixCell(0, 1), 1, now), - Texture::new(&HEALPixCell(0, 2), 2, now), - Texture::new(&HEALPixCell(0, 3), 3, now), - Texture::new(&HEALPixCell(0, 4), 4, now), - Texture::new(&HEALPixCell(0, 5), 5, now), - Texture::new(&HEALPixCell(0, 6), 6, now), - Texture::new(&HEALPixCell(0, 7), 7, now), - Texture::new(&HEALPixCell(0, 8), 8, now), - Texture::new(&HEALPixCell(0, 9), 9, now), - Texture::new(&HEALPixCell(0, 10), 10, now), - Texture::new(&HEALPixCell(0, 11), 11, now), - ]; - - self.heap.clear(); - self.textures.clear(); - //self.ready = false; - self.num_root_textures_available = 0; - self.available_tiles_during_frame = false; - - Ok(()) - }*/ - - pub fn push_allsky(&mut self, allsky: Allsky, slice_idx: u16) -> Result<(), JsValue> { + pub fn push_allsky(&mut self, allsky: Allsky) -> Result<(), JsValue> { let Allsky { image, time_req, depth_tile, + channel, .. } = allsky; @@ -119,7 +62,7 @@ impl HiPS3DBuffer { &HEALPixCell(depth_tile, idx as u64), image, time_req, - slice_idx, + channel.map(|c| c as u16).unwrap_or(0), )?; } } @@ -127,6 +70,10 @@ impl HiPS3DBuffer { Ok(()) } + pub fn find_nearest_slice(&self, cell: &HEALPixCell, slice: u16) -> Option { + self.get(cell).and_then(|t| t.find_nearest_slice(slice)) + } + // This method pushes a new downloaded tile into the buffer // It must be ensured that the tile is not already contained into the buffer pub fn push( @@ -140,13 +87,13 @@ impl HiPS3DBuffer { tex } else { self.textures - .insert(*cell, HEALPixTexturedCube::new(*cell, time_request)); + .insert(*cell, HpxTexture3D::new(*cell, time_request)); self.textures.get_mut(cell).unwrap() }; // copy to the 3D textured block - tex.append_slice(image, slice_idx, &self.config, &self.gl)?; + tex.append(image, slice_idx, &self.config, &self.gl)?; self.available_tiles_during_frame = true; @@ -163,91 +110,79 @@ impl HiPS3DBuffer { // Tell if a texture is available meaning all its sub tiles // must have been written for the GPU - pub fn contains(&self, texture_cell: &HEALPixCell) -> bool { - self.get(texture_cell).is_some() + pub fn contains_tile(&self, texture_cell: &HEALPixCell, slice: u16) -> bool { + self.get(texture_cell) + .map_or(false, |t| t.contains_slice(slice)) } - // lonlat is given in the - /*pub fn get_pixel_position_in_texture( - &self, - lonlat: &LonLatT, - depth: u8, - ) -> Result, JsValue> { - let (pix, dx, dy) = crate::healpix::utils::hash_with_dxdy(depth, lonlat); - let texture_cell = HEALPixCell(depth, pix); - - if let Some(texture) = self.get(&texture_cell) { - let cfg = &self.config; - - // Index of the texture in the total set of textures - let texture_idx = texture.idx(); - - // The size of the global texture containing the tiles - let texture_size = cfg.get_texture_size(); - - // Offset in the slice in pixels - let mut offset = Vector3::new( - (dy * (texture_size as f64)) as i32, - (dx * (texture_size as f64)) as i32, - texture_idx, - ); - - // Offset in the slice in pixels - if self.config.tex_storing_fits { - let texture_size = self.config.get_texture_size() as f32; - let mut uvy = offset.y as f32 / texture_size; - uvy = self.config.size_tile_uv - + 2.0 * self.config.size_tile_uv * (uvy / self.config.size_tile_uv).floor() - - uvy; - - offset.y = (uvy * texture_size) as i32; - } - - Ok(offset) - } else { - Err(JsValue::from_str(&format!( - "{:?} not loaded in the GPU, please wait before trying again.", - texture_cell - ))) - } - }*/ - /// Accessors - pub fn get(&self, cell: &HEALPixCell) -> Option<&HEALPixTexturedCube> { + pub fn get(&self, cell: &HEALPixCell) -> Option<&HpxTexture3D> { self.textures.get(cell) } - // Get the nearest parent tile found in the CPU buffer - pub fn get_nearest_parent(&self, cell: &HEALPixCell) -> Option { - if cell.is_root() { - // Root cells are in the buffer by definition - Some(*cell) - } else { - let mut parent_cell = cell.parent(); + pub fn config(&self) -> &HiPSConfig { + &self.config + } - while !self.contains(&parent_cell) && !parent_cell.is_root() { - parent_cell = parent_cell.parent(); - } + pub fn config_mut(&mut self) -> &mut HiPSConfig { + &mut self.config + } +} - if self.contains(&parent_cell) { - Some(parent_cell) - } else { - None - } - } +impl HpxTileBuffer for HiPS3DBuffer { + type T = HpxTexture3D; + + fn new(gl: &WebGlContext, config: HiPSConfig) -> Result { + let textures = HashMap::new(); + + let num_root_textures_available = 0; + let available_tiles_during_frame = false; + + let gl = gl.clone(); + Ok(Self { + config, + + num_root_textures_available, + textures, + available_tiles_during_frame, + gl, + }) } - pub fn config(&self) -> &HiPSConfig { + // Return if tiles did become available + fn reset_available_tiles(&mut self) -> bool { + let available_tiles_during_frame = self.available_tiles_during_frame; + self.available_tiles_during_frame = false; + + available_tiles_during_frame + } + + fn set_image_ext(&mut self, gl: &WebGlContext, ext: ImageExt) -> Result<(), JsValue> { + todo!(); + } + + fn read_pixel(&self, pos: &LonLatT, camera: &CameraViewPort) -> Result { + todo!(); + } + + // Tell if a texture is available meaning all its sub tiles + // must have been written for the GPU + fn contains(&self, cell: &HEALPixCell) -> bool { + self.get(cell).is_some() + } + + /// Accessors + fn get(&self, cell: &HEALPixCell) -> Option<&HpxTexture3D> { + self.textures.get(cell) + } + + fn config(&self) -> &HiPSConfig { &self.config } - pub fn config_mut(&mut self) -> &mut HiPSConfig { + fn config_mut(&mut self) -> &mut HiPSConfig { &mut self.config } - - /*pub fn get_texture_array(&self) -> &Texture2DArray { - &self.texture_2d_array - }*/ } /* diff --git a/src/core/src/renderable/hips/d3/mod.rs b/src/core/src/renderable/hips/d3/mod.rs index 140aa2f30..690c01e23 100644 --- a/src/core/src/renderable/hips/d3/mod.rs +++ b/src/core/src/renderable/hips/d3/mod.rs @@ -1,2 +1,657 @@ pub mod buffer; pub mod texture; + +use crate::renderable::hips::HpxTile; +use al_api::hips::ImageExt; +use al_api::hips::ImageMetadata; +use al_core::colormap::Colormap; +use al_core::colormap::Colormaps; +use al_core::image::format::ChannelType; + +use al_core::image::Image; + +use al_core::shader::Shader; +use al_core::webgl_ctx::GlWrapper; + +use al_core::VecData; +use al_core::VertexArrayObject; +use al_core::WebGlContext; + +use crate::math::{angle::Angle, vector::dist2}; +use crate::ProjectionType; + +use crate::camera::CameraViewPort; + +use crate::downloader::query; + +use crate::shader::ShaderManager; +use crate::{math::lonlat::LonLatT, utils}; + +use crate::downloader::request::allsky::Allsky; +use crate::healpix::{cell::HEALPixCell, coverage::HEALPixCoverage}; +use crate::renderable::utils::index_patch::DefaultPatchIndexIter; +use crate::time::Time; + +use super::config::HiPSConfig; +use std::collections::HashSet; + +// Recursively compute the number of subdivision needed for a cell +// to not be too much skewed + +use super::d2::texture::HpxTexture2D; +use buffer::HiPS3DBuffer; + +use super::raytracing::RayTracer; +use super::uv::{TileCorner, TileUVW}; + +use cgmath::Matrix; + +use wasm_bindgen::JsValue; +use web_sys::WebGl2RenderingContext; + +pub fn get_raster_shader<'a>( + cmap: &Colormap, + gl: &WebGlContext, + shaders: &'a mut ShaderManager, + config: &HiPSConfig, +) -> Result<&'a Shader, JsValue> { + if config.get_format().is_colored() && cmap.label() == "native" { + crate::shader::get_shader( + gl, + shaders, + "hips3d_rasterizer_raster.vert", + "hips3d_rasterizer_color.frag", + ) + } else { + if config.tex_storing_unsigned_int { + crate::shader::get_shader( + gl, + shaders, + "hips3d_rasterizer_raster.vert", + "hips3d_rasterizer_grayscale_to_colormap_u.frag", + ) + } else if config.tex_storing_integers { + crate::shader::get_shader( + gl, + shaders, + "hips3d_rasterizer_raster.vert", + "hips3d_rasterizer_grayscale_to_colormap_i.frag", + ) + } else { + crate::shader::get_shader( + gl, + shaders, + "hips3d_rasterizer_raster.vert", + "hips3d_rasterizer_grayscale_to_colormap.frag", + ) + } + } +} + +/* +pub fn get_raytracer_shader<'a>( + cmap: &Colormap, + gl: &WebGlContext, + shaders: &'a mut ShaderManager, + config: &HiPSConfig, +) -> Result<&'a Shader, JsValue> { + //let colored_hips = config.is_colored(); + if config.get_format().is_colored() && cmap.label() == "native" { + crate::shader::get_shader( + gl, + shaders, + "hips_raytracer_raytracer.vert", + "hips_raytracer_color.frag", + ) + } else { + if config.tex_storing_unsigned_int { + crate::shader::get_shader( + gl, + shaders, + "hips_raytracer_raytracer.vert", + "hips_raytracer_grayscale_to_colormap_u.frag", + ) + } else if config.tex_storing_integers { + crate::shader::get_shader( + gl, + shaders, + "hips_raytracer_raytracer.vert", + "hips_raytracer_grayscale_to_colormap_i.frag", + ) + } else { + crate::shader::get_shader( + gl, + shaders, + "hips_raytracer_raytracer.vert", + "hips_raytracer_grayscale_to_colormap.frag", + ) + } + } +}*/ + +pub struct HiPS3D { + //color: Color, + // The image survey texture buffer + buffer: HiPS3DBuffer, + + // The projected vertices data + // For WebGL2 wasm, the data are interleaved + //#[cfg(feature = "webgl2")] + //vertices: Vec, + //#[cfg(feature = "webgl1")] + // layout (location = 0) in vec3 position; + position: Vec, + //#[cfg(feature = "webgl1")] + // layout (location = 1) in vec3 uv_start; + uv: Vec, + idx_vertices: Vec, + + vao: VertexArrayObject, + gl: WebGlContext, + + footprint_moc: Option, + + // A buffer storing the cells in the view + hpx_cells_in_view: Vec, + + // The current slice index + slice: u16, + + num_indices: Vec, + slice_indices: Vec, + cells: Vec, +} + +use super::HpxTileBuffer; + +impl HiPS3D { + pub fn new(config: HiPSConfig, gl: &WebGlContext) -> Result { + let mut vao = VertexArrayObject::new(gl); + + let slice = 0; + + let num_indices = vec![]; + let slice_indices = vec![]; + // layout (location = 0) in vec2 lonlat; + // layout (location = 1) in vec3 position; + // layout (location = 2) in vec3 uv_start; + // layout (location = 3) in vec3 uv_end; + // layout (location = 4) in float time_tile_received; + //let vertices = vec![0.0; MAX_NUM_FLOATS_TO_DRAW]; + //let indices = vec![0_u16; MAX_NUM_INDICES_TO_DRAW]; + + //let vertices = vec![]; + let position = vec![]; + let uv = vec![]; + let idx_vertices = vec![]; + + #[cfg(feature = "webgl2")] + vao.bind_for_update() + .add_array_buffer_single( + 2, + "position", + WebGl2RenderingContext::DYNAMIC_DRAW, + VecData::(&position), + ) + .add_array_buffer_single( + 3, + "uv", + WebGl2RenderingContext::DYNAMIC_DRAW, + VecData::(&uv), + ) + // Set the element buffer + .add_element_buffer( + WebGl2RenderingContext::DYNAMIC_DRAW, + VecData::(&idx_vertices), + ) + .unbind(); + + let buffer = HiPS3DBuffer::new(gl, config)?; + + let cells = vec![]; + + let gl = gl.clone(); + let footprint_moc = None; + let hpx_cells_in_view = vec![]; + // request the allsky texture + Ok(Self { + // The image survey texture buffer + buffer, + + vao, + + gl, + + position, + uv, + idx_vertices, + + footprint_moc, + hpx_cells_in_view, + + slice, + cells, + num_indices, + slice_indices, + }) + } + + pub fn look_for_new_tiles<'a>( + &'a mut self, + camera: &'a CameraViewPort, + proj: &ProjectionType, + ) -> Option + 'a> { + // do not add tiles if the view is already at depth 0 + let cfg = self.get_config(); + let mut depth_tile = (camera.get_texture_depth() + cfg.delta_depth()) + .min(cfg.get_max_depth_tile()) + .max(cfg.get_min_depth_tile()); + let dd = cfg.delta_depth(); + + //let min_depth_tile = self.get_min_depth_tile(); + //let delta_depth = self.get_config().delta_depth(); + + //let min_bound_depth = min_depth_tile.max(delta_depth); + // do not ask to query tiles that: + // * either do not exist because < to min_depth_tile + // * either are part of a base tile already handled i.e. tiles < delta_depth + //console_log(depth_tile); + //console_log(min_bound_depth); + + //if depth_tile >= min_bound_depth { + //let depth_tile = depth_tile.max(min_bound_depth); + let survey_frame = cfg.get_frame(); + let mut already_considered_tiles = HashSet::new(); + + // raytracer is rendering and the shader only renders HPX texture cells of depth 0 + if camera.is_raytracing(proj) { + depth_tile = 0; + } + + let tile_cells_iter = camera + .get_hpx_cells(depth_tile, survey_frame) + //.flat_map(move |cell| { + // let texture_cell = cell.get_texture_cell(delta_depth); + // texture_cell.get_tile_cells(delta_depth) + //}) + .into_iter() + .flat_map(move |tile_cell| { + let tex_cell = tile_cell.get_texture_cell(dd); + tex_cell.get_tile_cells(dd) + }) + .filter(move |tile_cell| { + if already_considered_tiles.contains(tile_cell) { + return false; + } + + already_considered_tiles.insert(*tile_cell); + + if let Some(moc) = self.footprint_moc.as_ref() { + moc.intersects_cell(tile_cell) + } else { + true + } + }); + + Some(tile_cells_iter) + } + + pub fn set_slice(&mut self, slice: u16) { + self.slice = slice; + } + + pub fn get_tile_query(&self, cell: &HEALPixCell) -> query::Tile { + let cfg = self.get_config(); + query::Tile::new(cell, Some(self.get_slice() as u32), cfg) + } + + pub fn contains_tile(&self, cell: &HEALPixCell, slice: u16) -> bool { + self.buffer.contains_tile(cell, slice) + } + + pub fn draw( + &mut self, + shaders: &mut ShaderManager, + colormaps: &Colormaps, + camera: &mut CameraViewPort, + raytracer: &RayTracer, + cfg: &ImageMetadata, + proj: &ProjectionType, + ) -> Result<(), JsValue> { + //let raytracing = camera.is_raytracing(proj); + + //if raytracing { + // self.draw_internal(shaders, colormaps, camera, raytracer, cfg, proj) + //} else { + // rasterizer mode + let available_tiles = self.reset_available_tiles(); + let new_cells_in_view = self.retrieve_cells_in_camera(camera); + + if new_cells_in_view || available_tiles { + // TODO: append the vertices independently to the draw method + self.recompute_vertices(camera, proj); + } + + self.draw_internal(shaders, colormaps, camera, raytracer, cfg, proj) + //} + } + + fn recompute_vertices(&mut self, camera: &CameraViewPort, proj: &ProjectionType) { + self.cells.clear(); + self.slice_indices.clear(); + + self.position.clear(); + self.uv.clear(); + self.idx_vertices.clear(); + + self.num_indices.clear(); + + let mut off_indices = 0; + + let channel = self.get_config().get_format().get_channel(); + + for cell in &self.hpx_cells_in_view { + // filter textures that are not in the moc + let cell = if let Some(moc) = self.footprint_moc.as_ref() { + if moc.intersects_cell(&cell) { + Some(&cell) + } else { + if channel == ChannelType::RGB8U { + // Rasterizer does not render tiles that are not in the MOC + // This is not a problem for transparency rendered HiPses (FITS or PNG) + // but JPEG tiles do have black when no pixels data is found + // We therefore must draw in black for the tiles outside the HiPS MOC + Some(&cell) + } else { + None + } + } + } else { + Some(&cell) + }; + + let mut slice_contained = 0; + + if let Some(cell) = cell { + let hpx_cell_texture = if self.buffer.contains_tile(cell, self.slice) { + slice_contained = self.slice; + self.buffer.get(cell) + } else if let Some(next_slice) = self.buffer.find_nearest_slice(&cell, self.slice) { + slice_contained = next_slice; + self.buffer.get(cell) + } else if let Some(parent_cell) = self.buffer.get_nearest_parent(cell) { + // find the slice of the parent available, if possible near slice + slice_contained = self + .buffer + .find_nearest_slice(&parent_cell, self.slice) + .unwrap(); + self.buffer.get(&parent_cell) + } else { + None + }; + + if let Some(texture) = hpx_cell_texture { + self.slice_indices.push(slice_contained as usize); + self.cells.push(texture.cell().clone()); + // The slice is sure to be contained so we can unwrap + let hpx_slice_tex = texture.extract_2d_slice_texture(slice_contained).unwrap(); + + let uv_1 = TileUVW::new(cell, &hpx_slice_tex, self.get_config()); + let d01e = uv_1[TileCorner::BottomRight].x - uv_1[TileCorner::BottomLeft].x; + let d02e = uv_1[TileCorner::TopLeft].y - uv_1[TileCorner::BottomLeft].y; + + let num_subdivision = + super::subdivide::num_hpxcell_subdivision(cell, camera, proj); + + let n_segments_by_side: usize = 1 << (num_subdivision as usize); + let n_segments_by_side_f32 = n_segments_by_side as f32; + + let n_vertices_per_segment = n_segments_by_side + 1; + + let mut pos = Vec::with_capacity((n_segments_by_side + 1) * 4); + + let grid_lonlat = + healpix::nested::grid(cell.depth(), cell.idx(), n_segments_by_side as u16); + let grid_lonlat_iter = grid_lonlat.iter(); + + for (idx, &(lon, lat)) in grid_lonlat_iter.enumerate() { + let i: usize = idx / n_vertices_per_segment; + let j: usize = idx % n_vertices_per_segment; + + let hj0 = (j as f32) / n_segments_by_side_f32; + let hi0 = (i as f32) / n_segments_by_side_f32; + + let uv_end = [ + uv_1[TileCorner::BottomLeft].x + hj0 * d01e, + uv_1[TileCorner::BottomLeft].y + hi0 * d02e, + uv_1[TileCorner::BottomLeft].z, + ]; + + self.uv.extend(uv_end); + + pos.push([lon as f32, lat as f32]); + } + + let patch_indices_iter = DefaultPatchIndexIter::new( + &(0..=n_segments_by_side), + &(0..=n_segments_by_side), + n_vertices_per_segment, + ) + .flatten() + .map(|indices| { + [ + indices.0 + off_indices, + indices.1 + off_indices, + indices.2 + off_indices, + ] + }) + .flatten(); + let tmp = self.idx_vertices.len(); + self.idx_vertices.extend(patch_indices_iter); + + self.num_indices.push(self.idx_vertices.len() - tmp); + off_indices += pos.len() as u16; + + // Replace options with an arbitrary vertex + let position_iter = pos + .into_iter() + //.map(|ndc| ndc.unwrap_or([0.0, 0.0])) + .flatten(); + self.position.extend(position_iter); + } + } + } + + { + let mut vao = self.vao.bind_for_update(); + vao.update_array( + "position", + WebGl2RenderingContext::DYNAMIC_DRAW, + VecData(&self.position), + ) + .update_array( + "uv", + WebGl2RenderingContext::DYNAMIC_DRAW, + VecData(&self.uv), + ) + .update_element_array( + WebGl2RenderingContext::DYNAMIC_DRAW, + VecData(&self.idx_vertices), + ); + } + } + + fn reset_available_tiles(&mut self) -> bool { + self.buffer.reset_available_tiles() + } + + // returns a boolean if the view cells has changed with respect to the last frame + fn retrieve_cells_in_camera(&mut self, camera: &CameraViewPort) -> bool { + let cfg = self.get_config(); + // Get the coo system transformation matrix + let hips_frame = cfg.get_frame(); + let depth = camera.get_texture_depth().min(cfg.get_max_depth_texture()); + + let hpx_cells_in_view = camera.get_hpx_cells(depth, hips_frame); + let new_cells = if hpx_cells_in_view.len() != self.hpx_cells_in_view.len() { + true + } else { + !self + .hpx_cells_in_view + .iter() + .zip(hpx_cells_in_view.iter()) + .all(|(&a, &b)| a == b) + }; + + self.hpx_cells_in_view = hpx_cells_in_view; + + new_cells + } + + #[inline] + pub fn set_moc(&mut self, moc: HEALPixCoverage) { + self.footprint_moc = Some(moc); + } + + #[inline] + pub fn get_moc(&self) -> Option<&HEALPixCoverage> { + self.footprint_moc.as_ref() + } + + pub fn set_image_ext(&mut self, ext: ImageExt) -> Result<(), JsValue> { + self.buffer.set_image_ext(&self.gl, ext) + } + + pub fn is_allsky(&self) -> bool { + self.buffer.config().is_allsky + } + + // Position given is in the camera space + pub fn read_pixel( + &self, + p: &LonLatT, + camera: &CameraViewPort, + ) -> Result { + self.buffer.read_pixel(p, camera) + } + + fn draw_internal( + &self, + shaders: &mut ShaderManager, + colormaps: &Colormaps, + camera: &mut CameraViewPort, + raytracer: &RayTracer, + cfg: &ImageMetadata, + proj: &ProjectionType, + ) -> Result<(), JsValue> { + let hips_cfg = self.buffer.config(); + // Get the coo system transformation matrix + let selected_frame = camera.get_coo_system(); + let hips_frame = hips_cfg.get_frame(); + let c = selected_frame.to(hips_frame); + + let big_fov = camera.is_raytracing(proj); + if big_fov { + self.gl.enable(WebGl2RenderingContext::CULL_FACE); + } + + let ImageMetadata { + color, + opacity, + blend_cfg, + .. + } = cfg; + + let cmap = colormaps.get(color.cmap_name.as_ref()); + + let v2w = (*camera.get_m2w()) * c.transpose(); + + // The rasterizer has a buffer containing: + // - The vertices of the HEALPix cells for the most refined survey + // - The starting and ending uv for the blending animation + // - The time for each HEALPix cell at which the animation begins + // + // Each of these data can be changed at different circumstances: + // - The vertices are changed if: + // * new cells are added/removed (because new cells are added) + // to the previous frame. + // - The UVs are changed if: + // * new cells are added/removed (because new cells are added) + // * there are new available tiles for the GPU + let mut off_idx = 0; + + for (slice_idx, (cell, num_indices)) in self + .slice_indices + .iter() + .zip(self.cells.iter().zip(self.num_indices.iter())) + { + blend_cfg.enable(&self.gl, || { + let shader = get_raster_shader(cmap, &self.gl, shaders, &hips_cfg)?.bind(&self.gl); + + shader + .attach_uniform( + "tex", + self.buffer + .get(cell) + .unwrap() + .get_3d_block_from_slice(*slice_idx as u16) + .unwrap(), + ) + .attach_uniforms_with_params_from(cmap, colormaps) + .attach_uniforms_from(color) + .attach_uniforms_from(camera) + .attach_uniform("inv_model", &v2w) + .attach_uniform("opacity", opacity) + .attach_uniform("u_proj", proj) + .attach_uniforms_from(colormaps) + .bind_vertex_array_object_ref(&self.vao) + .draw_elements_with_i32( + WebGl2RenderingContext::TRIANGLES, + Some(*num_indices as i32), + WebGl2RenderingContext::UNSIGNED_SHORT, + (off_idx * std::mem::size_of::()) as i32, + ); + + off_idx += (*num_indices) as usize; + + Ok(()) + })?; + } + + if big_fov { + self.gl.disable(WebGl2RenderingContext::CULL_FACE); + } + + Ok(()) + } + + pub fn add_tile( + &mut self, + cell: &HEALPixCell, + image: I, + time_request: Time, + slice_idx: u16, + ) -> Result<(), JsValue> { + self.buffer.push(&cell, image, time_request, slice_idx) + } + + pub fn add_allsky(&mut self, allsky: Allsky) -> Result<(), JsValue> { + self.buffer.push_allsky(allsky) + } + + #[inline] + pub fn get_slice(&self) -> u16 { + self.slice + } + + /* Accessors */ + #[inline] + pub fn get_config(&self) -> &HiPSConfig { + self.buffer.config() + } + + #[inline] + pub fn get_config_mut(&mut self) -> &mut HiPSConfig { + self.buffer.config_mut() + } +} diff --git a/src/core/src/renderable/hips/d3/texture.rs b/src/core/src/renderable/hips/d3/texture.rs index b1872f897..0d6ac4155 100644 --- a/src/core/src/renderable/hips/d3/texture.rs +++ b/src/core/src/renderable/hips/d3/texture.rs @@ -1,16 +1,16 @@ +use crate::renderable::hips::d2::texture::HpxTexture2D; use crate::{healpix::cell::HEALPixCell, time::Time}; use al_core::image::format::{ - ChannelType, ImageFormatType, R16I, R32F, R32I, R64F, R8UI, RGB32F, RGB8U, RGBA32F, RGBA8U, + ChannelType, R16I, R32F, R32I, R64F, R8UI, RGB32F, RGB8U, RGBA32F, RGBA8U, }; use al_core::image::Image; use al_core::texture::Texture3D; use al_core::webgl_ctx::WebGlRenderingCtx; use cgmath::Vector3; -use std::collections::HashSet; use wasm_bindgen::JsValue; -pub struct HEALPixTexturedCube { +pub struct HpxTexture3D { tile_cell: HEALPixCell, // Precomputed uniq number uniq: i32, @@ -31,50 +31,205 @@ pub struct HEALPixTexturedCube { // We autorize 512 cubic tiles of size 32 each which allows to store max 16384 slices textures: Vec>, // A set of already inserted slices. Each cubic tiles can have 32 slices. The occupancy of the - // slices inside a cubic tile is done with a u32 mask - slices: [u32; 512], + // slices inside a cubic tile is done with a u32 mask. Limited to 16384 slices + blocks: [u32; 512], + // sorted index list of 32-length blocks that are not empty + block_indices: Vec, } use crate::renderable::hips::config::HiPSConfig; use crate::WebGlContext; -impl HEALPixTexturedCube { +use crate::renderable::hips::HpxTile; + +impl HpxTexture3D { pub fn new(tile_cell: HEALPixCell, time_request: Time) -> Self { let start_time = None; let uniq = tile_cell.uniq(); let textures = std::iter::repeat(None).take(512).collect(); - let slices = [0; 512]; - + let blocks = [0; 512]; + let block_indices = Vec::new(); Self { tile_cell, uniq, time_request, start_time, textures, - slices, + blocks, + block_indices, + } + } + + pub fn find_nearest_slice(&self, slice: u16) -> Option { + let block_idx = (slice >> 5) as usize; + + match self.block_indices.binary_search(&block_idx) { + Ok(_) => { + if self.contains_slice(slice) { + Some(slice) + } else { + // the slice is not present but we know there is one in the block + let block = self.blocks[block_idx]; + + let slice_idx = (slice & 0x1f) as u32; + + let m2 = if slice_idx == 31 { + 0 + } else { + 0xffffffff >> (slice_idx + 1) + }; + let m1 = (!m2) & !(1 << (31 - slice_idx)); + + al_core::log(&format!("m1 {:#x} m2 {:#x} {:?}", m1, m2, slice_idx)); + + let lb = ((block & m1) >> (32 - slice_idx)) as u32; + let rb = (block & m2) as u32; + + let lb_trailing_zeros = (lb.trailing_zeros() as u16).min(slice_idx as u16); + let rb_leading_zeros = (rb.leading_zeros() - slice_idx - 1) as u16; + + let no_more_left_bits = slice_idx - (lb_trailing_zeros as u32) == 0; + let no_more_right_bits = slice_idx + (rb_leading_zeros as u32) == 31; + + al_core::log(&format!( + "{:?} {:?} slice idx {:?}, {:x?} rb {:?}", + no_more_left_bits, + no_more_right_bits, + slice_idx, + lb, + lb_trailing_zeros as u32 + )); + + match (no_more_left_bits, no_more_right_bits) { + (false, false) => { + if lb_trailing_zeros <= rb_leading_zeros { + Some(slice - lb_trailing_zeros - 1) + } else { + Some(slice + rb_leading_zeros + 1) + } + } + (false, true) => { + if lb_trailing_zeros <= rb_leading_zeros { + Some(slice - lb_trailing_zeros - 1) + } else { + // explore next block + if block_idx == self.blocks.len() - 1 { + // no after block + Some(slice - lb_trailing_zeros - 1) + } else { + // get the next block + let next_block = self.blocks[block_idx + 1]; + + let num_bits_to_next_block = + next_block.leading_zeros() as u16 + rb_leading_zeros; + + if num_bits_to_next_block < lb_trailing_zeros { + Some(slice + num_bits_to_next_block + 1) + } else { + Some(slice - lb_trailing_zeros - 1) + } + } + } + } + (true, false) => { + if rb_leading_zeros <= lb_trailing_zeros { + Some(slice + rb_leading_zeros + 1) + } else { + // explore previous block + if block_idx == 0 { + // no after block + Some(slice + rb_leading_zeros + 1) + } else { + // get the next block + let prev_block = self.blocks[block_idx - 1]; + + let num_bits_from_prev_block = + prev_block.trailing_zeros() as u16 + lb_trailing_zeros; + if num_bits_from_prev_block < rb_leading_zeros { + Some(slice - num_bits_from_prev_block - 1) + } else { + Some(slice + rb_leading_zeros + 1) + } + } + } + } + (true, true) => unreachable!(), + } + } + } + Err(i) => { + match (self.block_indices.get(i - 1), self.block_indices.get(i)) { + (Some(b_idx_1), Some(b_idx_2)) => { + let b1 = self.blocks[*b_idx_1]; + let b2 = self.blocks[*b_idx_2]; + + let b1_tz = b1.trailing_zeros() as usize; + let b2_lz = b2.leading_zeros() as usize; + + let slice_b1 = ((*b_idx_1 << 5) + 32 - b1_tz - 1) as u16; + let slice_b2 = ((*b_idx_2 << 5) + b2_lz) as u16; + if slice - slice_b1 <= slice_b2 - slice { + // the nearest slice is in b1 + Some(slice_b1 as u16) + } else { + // the nearest slice is in b2 + Some(slice_b2 as u16) + } + } + (None, Some(b_idx_2)) => { + let b2 = self.blocks[*b_idx_2]; + let b2_lz = b2.leading_zeros() as usize; + + Some(((*b_idx_2 << 5) + b2_lz) as u16) + } + (Some(b_idx_1), None) => { + let b1 = self.blocks[*b_idx_1]; + let b1_tz = b1.trailing_zeros() as usize; + + Some(((*b_idx_1 << 5) + 32 - b1_tz - 1) as u16) + } + (None, None) => None, + } + } } } - // Get the good cubic texture and the slice idx inside it - pub fn get_cubic_texture_from_slice(&self, slice: u16) -> (Option<&Texture3D>, u8) { - let cube_idx = slice >> 5; + pub fn get_3d_block_from_slice(&self, slice: u16) -> Option<&Texture3D> { + let block_idx = slice >> 5; + + self.textures[block_idx as usize].as_ref() + } + + pub fn extract_2d_slice_texture(&self, slice: u16) -> Option { + // Find the good sub cube containing the slice + let block_idx = (slice >> 5) as usize; let slice_idx = (slice & 0x1f) as u8; - (self.textures[cube_idx as usize].as_ref(), slice_idx) + + // check the texture is there + if self.blocks[block_idx] & (1 << (31 - slice_idx)) != 0 { + Some(HpxTexture2D::new( + &self.tile_cell, + slice_idx as i32, + self.time_request, + )) + } else { + None + } } // Panic if cell is not contained in the texture // Do nothing if the texture is full // Return true if the tile is newly added - pub fn append_slice( + pub fn append( &mut self, image: I, slice: u16, cfg: &HiPSConfig, gl: &WebGlContext, ) -> Result<(), JsValue> { - let cube_idx = (slice >> 5) as usize; + let block_idx = (slice >> 5) as usize; - let texture = if let Some(texture) = self.textures[cube_idx as usize].as_ref() { + let texture = if let Some(texture) = self.textures[block_idx as usize].as_ref() { texture } else { let tile_size = cfg.get_tile_size(); @@ -133,16 +288,23 @@ impl HEALPixTexturedCube { Texture3D::create_empty::(gl, tile_size, tile_size, 32, params) } }; - self.textures[cube_idx] = Some(texture?); + self.textures[block_idx] = Some(texture?); - self.textures[cube_idx].as_ref().unwrap() + self.textures[block_idx].as_ref().unwrap() }; let slice_idx = slice & 0x1f; // if there is already something, do not tex sub - if self.slices[cube_idx] & (1 << slice_idx) == 0 { - image.insert_into_3d_texture(texture, &Vector3::::new(0, 0, slice_idx as i32))? + if self.blocks[block_idx] & (1 << (31 - slice_idx)) == 0 { + image.insert_into_3d_texture(texture, &Vector3::::new(0, 0, slice_idx as i32))?; + + match self.block_indices.binary_search(&block_idx) { + Ok(i) => {} // element already in vector @ `pos` + Err(i) => self.block_indices.insert(i, block_idx), + } + + self.blocks[block_idx] |= 1 << (31 - slice_idx); } self.start_time = Some(Time::now()); @@ -152,15 +314,17 @@ impl HEALPixTexturedCube { // Cell must be contained in the texture pub fn contains_slice(&self, slice: u16) -> bool { - let cube_idx = (slice >> 5) as usize; - let slice_idx = slice & 0x1f; + let block_idx = (slice >> 5) as usize; + let idx_in_block = slice & 0x1f; - self.slices[cube_idx] & (1 << slice_idx) == 1 + (self.blocks[block_idx] >> (31 - idx_in_block)) & 0x1 == 1 } +} +impl HpxTile for HpxTexture3D { // Getter // Returns the current time if the texture is not full - pub fn start_time(&self) -> Time { + fn start_time(&self) -> Time { if let Some(t) = self.start_time { t } else { @@ -168,54 +332,38 @@ impl HEALPixTexturedCube { } } - pub fn time_request(&self) -> Time { + fn time_request(&self) -> Time { self.time_request } - pub fn cell(&self) -> &HEALPixCell { + fn cell(&self) -> &HEALPixCell { &self.tile_cell } - - // Setter - /*pub fn replace(&mut self, texture_cell: &HEALPixCell, time_request: Time) { - // Cancel the tasks copying the tiles contained in the texture - // which have not yet been completed. - //self.clear_tasks_in_progress(config, exec); - - self.texture_cell = *texture_cell; - self.uniq = texture_cell.uniq(); - self.full = false; - self.start_time = None; - self.time_request = time_request; - self.tiles.clear(); - //self.missing = true; - self.num_tiles_written = 0; - }*/ } use std::cmp::Ordering; -impl PartialOrd for HEALPixTexturedCube { +impl PartialOrd for HpxTexture3D { fn partial_cmp(&self, other: &Self) -> Option { self.uniq.partial_cmp(&other.uniq) } } use crate::Abort; -impl Ord for HEALPixTexturedCube { +impl Ord for HpxTexture3D { fn cmp(&self, other: &Self) -> Ordering { self.partial_cmp(other).unwrap_abort() } } -impl PartialEq for HEALPixTexturedCube { +impl PartialEq for HpxTexture3D { fn eq(&self, other: &Self) -> bool { self.uniq == other.uniq } } -impl Eq for HEALPixTexturedCube {} +impl Eq for HpxTexture3D {} /* pub struct TextureUniforms<'a> { - texture: &'a HEALPixTexturedCube, + texture: &'a HpxTexture3D, name: String, } diff --git a/src/core/src/renderable/hips/mod.rs b/src/core/src/renderable/hips/mod.rs index b96620f10..edeb9cdfe 100644 --- a/src/core/src/renderable/hips/mod.rs +++ b/src/core/src/renderable/hips/mod.rs @@ -7,3 +7,155 @@ mod triangulation; pub mod uv; pub use d2::HiPS2D; + +use crate::downloader::request::allsky::Allsky; +use crate::renderable::HiPSConfig; +use crate::time::Time; +use crate::CameraViewPort; +use crate::HEALPixCell; +use crate::HEALPixCoverage; +use crate::LonLatT; +use crate::WebGlContext; +use al_api::hips::ImageExt; +use al_core::image::Image; +use wasm_bindgen::JsValue; + +mod subdivide; + +trait HpxTile { + // Getter + // Returns the current time if the texture is not full + fn start_time(&self) -> Time; + + fn time_request(&self) -> Time; + + fn cell(&self) -> &HEALPixCell; +} + +pub trait HpxTileBuffer { + type T: HpxTile; + + fn new(gl: &WebGlContext, config: HiPSConfig) -> Result + where + Self: Sized; + + fn set_image_ext(&mut self, gl: &WebGlContext, ext: ImageExt) -> Result<(), JsValue>; + + // Return if tiles did become available + fn reset_available_tiles(&mut self) -> bool; + + /// Accessors + fn get(&self, cell: &HEALPixCell) -> Option<&Self::T>; + + fn contains(&self, cell: &HEALPixCell) -> bool; + + // Get the nearest parent tile found in the CPU buffer + fn get_nearest_parent(&self, cell: &HEALPixCell) -> Option { + /*if cell.is_root() { + // Root cells are in the buffer by definition + Some(*cell) + } else {*/ + let mut parent_cell = cell.parent(); + + while !self.contains(&parent_cell) && !parent_cell.is_root() { + parent_cell = parent_cell.parent(); + } + + if self.contains(&parent_cell) { + Some(parent_cell) + } else { + None + } + //} + } + + fn config_mut(&mut self) -> &mut HiPSConfig; + fn config(&self) -> &HiPSConfig; + + fn read_pixel(&self, pos: &LonLatT, camera: &CameraViewPort) -> Result; +} + +use crate::downloader::query; +use crate::renderable::hips::HiPS::{D2, D3}; +use crate::renderable::HiPS3D; +use crate::ProjectionType; +pub enum HiPS { + D2(HiPS2D), + D3(HiPS3D), +} + +impl HiPS { + pub fn look_for_new_tiles( + &mut self, + camera: &CameraViewPort, + proj: &ProjectionType, + ) -> Option> { + match self { + D2(hips) => hips.look_for_new_tiles(camera, proj).map(|it| it.collect()), + D3(hips) => hips.look_for_new_tiles(camera, proj).map(|it| it.collect()), + } + } + + // Position given is in the camera space + pub fn read_pixel( + &self, + p: &LonLatT, + camera: &CameraViewPort, + ) -> Result { + match self { + D2(hips) => hips.read_pixel(p, camera), + D3(hips) => hips.read_pixel(p, camera), + } + } + + #[inline] + pub fn get_config(&self) -> &HiPSConfig { + match self { + D2(hips) => hips.get_config(), + D3(hips) => hips.get_config(), + } + } + + #[inline] + pub fn get_config_mut(&mut self) -> &mut HiPSConfig { + match self { + D2(hips) => hips.get_config_mut(), + D3(hips) => hips.get_config_mut(), + } + } + + pub fn set_image_ext(&mut self, ext: ImageExt) -> Result<(), JsValue> { + match self { + D2(hips) => hips.set_image_ext(ext), + D3(hips) => hips.set_image_ext(ext), + } + } + + #[inline] + pub fn set_moc(&mut self, moc: HEALPixCoverage) { + match self { + D2(hips) => hips.set_moc(moc), + D3(hips) => hips.set_moc(moc), + } + } + + #[inline] + pub fn get_tile_query(&self, cell: &HEALPixCell) -> query::Tile { + match self { + HiPS::D2(hips) => hips.get_tile_query(cell), + HiPS::D3(hips) => hips.get_tile_query(cell), + } + } + + #[inline] + pub fn add_allsky(&mut self, allsky: Allsky) -> Result<(), JsValue> { + match self { + HiPS::D2(hips) => hips.add_allsky(allsky), + HiPS::D3(hips) => hips.add_allsky(allsky), + } + } + + pub fn is_allsky(&self) -> bool { + self.get_config().is_allsky + } +} diff --git a/src/core/src/renderable/hips/subdivide.rs b/src/core/src/renderable/hips/subdivide.rs new file mode 100644 index 000000000..b3b635443 --- /dev/null +++ b/src/core/src/renderable/hips/subdivide.rs @@ -0,0 +1,69 @@ +use crate::camera::CameraViewPort; +use crate::math::angle::Angle; +use crate::math::projection::ProjectionType; +use crate::math::vector::dist2; +use crate::HEALPixCell; + +const M: f64 = 280.0 * 280.0; +const N: f64 = 150.0 * 150.0; +const RAP: f64 = 0.7; + +fn is_too_large(cell: &HEALPixCell, camera: &CameraViewPort, projection: &ProjectionType) -> bool { + let vertices = cell + .vertices() + .iter() + .filter_map(|(lon, lat)| { + let vertex = crate::math::lonlat::radec_to_xyzw(Angle(*lon), Angle(*lat)); + projection.icrs_celestial_to_screen_space(&vertex, camera) + }) + .collect::>(); + + if vertices.len() < 4 { + false + } else { + let d1 = dist2(vertices[0].as_ref(), &vertices[2].as_ref()); + let d2 = dist2(vertices[1].as_ref(), &vertices[3].as_ref()); + if d1 > M || d2 > M { + true + } else if d1 < N && d2 < N { + false + } else { + let rap = if d2 > d1 { d1 / d2 } else { d2 / d1 }; + + rap < RAP + } + } +} + +pub fn num_hpxcell_subdivision( + cell: &HEALPixCell, + camera: &CameraViewPort, + projection: &ProjectionType, +) -> u8 { + let d = cell.depth(); + // Subdivide all cells at least one time. + // TODO: use a single subdivision number computed from the current cells inside the view + // i.e. subdivide all cells in the view with the cell that has to be the most subdivided + let mut num_sub = 1; + if d < 2 { + num_sub = 2 - d; + } + + // Largest deformation cell among the cells of a specific depth + let largest_center_to_vertex_dist = + healpix::largest_center_to_vertex_distance(d, 0.0, healpix::TRANSITION_LATITUDE); + let smallest_center_to_vertex_dist = + healpix::largest_center_to_vertex_distance(d, 0.0, healpix::LAT_OF_SQUARE_CELL); + + let (lon, lat) = cell.center(); + let center_to_vertex_dist = healpix::largest_center_to_vertex_distance(d, lon, lat); + + let skewed_factor = (center_to_vertex_dist - smallest_center_to_vertex_dist) + / (largest_center_to_vertex_dist - smallest_center_to_vertex_dist); + + if skewed_factor > 0.25 || is_too_large(cell, camera, projection) || cell.is_on_pole() { + num_sub += 1; + } + + num_sub +} diff --git a/src/core/src/renderable/hips/uv.rs b/src/core/src/renderable/hips/uv.rs index 3a1d9b3cb..c1f2b7088 100644 --- a/src/core/src/renderable/hips/uv.rs +++ b/src/core/src/renderable/hips/uv.rs @@ -13,12 +13,13 @@ impl Deref for UV { } use super::config::HiPSConfig; -use super::d2::texture::Texture; +use super::d2::texture::HpxTexture2D; use crate::healpix::cell::HEALPixCell; +use crate::renderable::hips::HpxTile; pub struct TileUVW([Vector3; 4]); impl TileUVW { // The texture cell passed must be a child of texture - pub fn new(cell: &HEALPixCell, texture: &Texture, cfg: &HiPSConfig) -> TileUVW { + pub fn new(cell: &HEALPixCell, texture: &HpxTexture2D, cfg: &HiPSConfig) -> TileUVW { // Index of the texture in the total set of textures let texture_idx = texture.idx(); diff --git a/src/core/src/renderable/mod.rs b/src/core/src/renderable/mod.rs index 1b7fe13f9..5a116a8ae 100644 --- a/src/core/src/renderable/mod.rs +++ b/src/core/src/renderable/mod.rs @@ -14,8 +14,6 @@ use crate::tile_fetcher::TileFetcherQueue; use al_core::image::format::ChannelType; -pub use hips::HiPS2D; - pub use catalog::Manager; use al_api::color::ColorRGB; @@ -43,6 +41,9 @@ use hips::raytracing::RayTracer; use std::collections::HashMap; +use hips::d2::HiPS2D; +use hips::d3::HiPS3D; + use wasm_bindgen::JsValue; use web_sys::WebGl2RenderingContext; @@ -54,10 +55,12 @@ pub trait Renderer { pub(crate) type Id = String; // ID of an image, can be an url or a uuidv4 pub(crate) type CreatorDid = String; +use hips::HiPS; type LayerId = String; pub struct Layers { // Surveys to query - surveys: HashMap, + hipses: HashMap, + images: HashMap>, // an url can contain multiple images i.e. a fits file can contain // multiple image extensions // The meta data associated with a layer @@ -121,7 +124,8 @@ impl ImageLayer { impl Layers { pub fn new(gl: &WebGlContext, projection: &ProjectionType) -> Result { - let surveys = HashMap::new(); + let hipses = HashMap::new(); + let images = HashMap::new(); let meta = HashMap::new(); let ids = HashMap::new(); @@ -155,7 +159,7 @@ impl Layers { let background_color = DEFAULT_BACKGROUND_COLOR; Ok(Layers { - surveys, + hipses, images, meta, @@ -171,19 +175,10 @@ impl Layers { }) } - pub fn set_survey_url(&mut self, cdid: &CreatorDid, new_url: String) -> Result<(), JsValue> { - if let Some(survey) = self.surveys.get_mut(cdid) { + pub fn set_hips_url(&mut self, cdid: &CreatorDid, new_url: String) -> Result<(), JsValue> { + if let Some(hips) = self.hipses.get_mut(cdid) { // update the root_url - survey.get_config_mut().set_root_url(new_url.clone()); - - //self.surveys.insert(new_url.clone(), survey); - - // update all the layer urls - /*for id in self.ids.values_mut() { - if *id == past_url { - *id = new_url.clone(); - } - }*/ + hips.get_config_mut().set_root_url(new_url.clone()); Ok(()) } else { @@ -192,8 +187,8 @@ impl Layers { } /*pub fn reset_frame(&mut self) { - for survey in self.surveys.values_mut() { - survey.reset_frame(); + for hips in self.hips.values_mut() { + hips.reset_frame(); } }*/ @@ -221,15 +216,15 @@ impl Layers { let raytracer = &self.raytracer; let raytracing = camera.is_raytracing(projection); - // Check whether a survey to plot is allsky + // Check whether a hips to plot is allsky // if neither are, we draw a font // if there are, we do not draw nothing let render_background_color = !self.layers.iter().any(|layer| { let meta = self.meta.get(layer).unwrap_abort(); let cdid = self.ids.get(layer).unwrap_abort(); - if let Some(survey) = self.surveys.get(cdid) { - let hips_cfg = survey.get_config(); - (survey.is_allsky() || hips_cfg.get_format().get_channel() == ChannelType::RGB8U) + if let Some(hips) = self.hipses.get(cdid) { + let hips_cfg = hips.get_config(); + (hips.is_allsky() || hips_cfg.get_format().get_channel() == ChannelType::RGB8U) && meta.opacity == 1.0 } else { // image fits case @@ -268,13 +263,13 @@ impl Layers { let meta = self.meta.get(layer).expect("Meta should be found"); let id = self.ids.get(layer).expect("Url should be found"); - if let Some(survey) = self.surveys.get_mut(id) { - let hips_cfg = survey.get_config(); + if let Some(hips) = self.hipses.get_mut(id) { + let hips_cfg = hips.get_config(); - let fully_covering_survey = (survey.is_allsky() + let fully_covering_hips = (hips.is_allsky() || hips_cfg.get_format().get_channel() == ChannelType::RGB8U) && meta.opacity == 1.0; - if fully_covering_survey { + if fully_covering_hips { idx_start_layer = idx_layer; } } @@ -284,13 +279,19 @@ impl Layers { for layer in rendered_layers { let draw_opt = self.meta.get(layer).expect("Meta should be found"); if draw_opt.visible() { - // 1. Update the survey if necessary + // 1. Update the hips if necessary let id = self.ids.get(layer).expect("Url should be found"); - if let Some(survey) = self.surveys.get_mut(id) { - survey.update(camera, projection); - - // 2. Draw it if its opacity is not null - survey.draw(shaders, colormaps, camera, raytracer, draw_opt, projection)?; + if let Some(hips) = self.hipses.get_mut(id) { + match hips { + HiPS::D2(hips) => { + hips.update(camera, projection); + // 2. Draw it if its opacity is not null + hips.draw(shaders, colormaps, camera, raytracer, draw_opt, projection)?; + } + HiPS::D3(hips) => { + hips.draw(shaders, colormaps, camera, raytracer, draw_opt, projection)?; + } + } } else if let Some(images) = self.images.get_mut(id) { // 2. Draw it if its opacity is not null for image in images { @@ -338,14 +339,14 @@ impl Layers { Ok(id_layer) } else { // Resource not needed anymore - if let Some(s) = self.surveys.remove(&id) { + if let Some(hips) = self.hipses.remove(&id) { // A HiPS has been found and removed - let hips_frame = s.get_config().get_frame(); + let hips_frame = hips.get_config().get_frame(); // remove the frame camera.unregister_view_frame(hips_frame, proj); // remove the local files access from the tile fetcher - tile_fetcher.delete_hips_local_files(s.get_config().get_creator_did()); + tile_fetcher.delete_hips_local_files(hips.get_config().get_creator_did()); Ok(id_layer) } else if let Some(_) = self.images.remove(&id) { @@ -353,7 +354,7 @@ impl Layers { Ok(id_layer) } else { Err(JsValue::from_str(&format!( - "Url found {:?} is associated to no surveys.", + "Url found {:?} is associated to no 2D HiPSes.", id ))) } @@ -408,14 +409,14 @@ impl Layers { Ok(()) } - pub fn add_image_hips( + pub fn add_hips( &mut self, gl: &WebGlContext, hips: HiPSCfg, camera: &mut CameraViewPort, proj: &ProjectionType, tile_fetcher: &mut TileFetcherQueue, - ) -> Result<&HiPS2D, JsValue> { + ) -> Result<&HiPS, JsValue> { let HiPSCfg { layer, properties, @@ -444,13 +445,13 @@ impl Layers { camera.set_longitude_reversed(longitude_reversed, proj); - // 3. Add the image survey + // 3. Add the image hips let creator_did = String::from(properties.get_creator_did()); // The layer does not already exist // Let's check if no other hipses points to the // same url than `hips` let cdid_already_found = self - .surveys + .hipses .keys() .any(|hips_cdid| hips_cdid == &creator_did); @@ -469,16 +470,21 @@ impl Layers { }*/ camera.register_view_frame(cfg.get_frame(), proj); - let hips = HiPS2D::new(cfg, gl)?; - // add the frame to the camera + let hips = if cfg.get_cube_depth().is_some() { + // HiPS cube + HiPS::D3(HiPS3D::new(cfg, gl)?) + } else { + HiPS::D2(HiPS2D::new(cfg, gl)?) + }; - self.surveys.insert(creator_did.clone(), hips); + // add the frame to the camera + self.hipses.insert(creator_did.clone(), hips); } self.ids.insert(layer.clone(), creator_did.clone()); let hips = self - .surveys + .hipses .get(&creator_did) .ok_or(JsValue::from_str("HiPS not found"))?; Ok(hips) @@ -559,15 +565,15 @@ impl Layers { &mut self, layer: String, meta: ImageMetadata, - camera: &mut CameraViewPort, + camera: &CameraViewPort, projection: &ProjectionType, ) -> Result<(), JsValue> { let layer_ref = layer.as_str(); /*if let Some(meta_old) = self.meta.get(layer_ref) { if !meta_old.visible() && meta.visible() { - if let Some(survey) = self.get_mut_hips_from_layer(layer_ref) { - survey.recompute_vertices(camera, projection); + if let Some(hips) = self.get_mut_hips_from_layer(layer_ref) { + hips.recompute_vertices(camera, projection); } if let Some(images) = self.get_mut_image_from_layer(layer_ref) { @@ -587,8 +593,8 @@ impl Layers { for idx in 0..layer_idx { let cur_layer = self.layers[idx].clone(); - if let Some(survey) = self.get_mut_hips_from_layer(&cur_layer) { - survey.recompute_vertices(camera, projection); + if let Some(hips) = self.get_mut_hips_from_layer(&cur_layer) { + hips.recompute_vertices(camera, projection); } else if let Some(images) = self.get_mut_image_from_layer(&cur_layer) { for image in images { image.recompute_vertices(camera, projection)?; @@ -598,7 +604,7 @@ impl Layers { } }*/ - // Expect the image survey to be found in the hash map + // Expect the image hips to be found in the hash map self.meta.insert(layer.clone(), meta).ok_or_else(|| { JsValue::from(js_sys::Error::new(&format!("{:?} layer not found", layer))) })?; @@ -608,35 +614,27 @@ impl Layers { // Accessors // HiPSes getters - pub fn get_hips_from_layer(&self, layer: &str) -> Option<&HiPS2D> { + pub fn get_hips_from_layer(&self, layer: &str) -> Option<&HiPS> { self.ids .get(layer) - .map(|cdid| self.surveys.get(cdid)) + .map(|cdid| self.hipses.get(cdid)) .flatten() } - pub fn get_mut_hips_from_layer(&mut self, layer: &str) -> Option<&mut HiPS2D> { + pub fn get_mut_hips_from_layer(&mut self, layer: &str) -> Option<&mut HiPS> { if let Some(cdid) = self.ids.get_mut(layer) { - self.surveys.get_mut(cdid) + self.hipses.get_mut(cdid) } else { None } } - pub fn get_mut_hips_from_cdid(&mut self, cdid: &str) -> Option<&mut HiPS2D> { - self.surveys.get_mut(cdid) - } - - pub fn get_hips_from_cdid(&mut self, cdid: &str) -> Option<&HiPS2D> { - self.surveys.get(cdid) - } - - pub fn values_hips(&self) -> impl Iterator { - self.surveys.values() + pub fn get_mut_hips_from_cdid(&mut self, cdid: &str) -> Option<&mut HiPS> { + self.hipses.get_mut(cdid) } - pub fn values_mut_hips(&mut self) -> impl Iterator { - self.surveys.values_mut() + pub fn get_mut_hipses(&mut self) -> impl Iterator { + self.hipses.values_mut() } // Fits images getters diff --git a/src/core/src/tile_fetcher.rs b/src/core/src/tile_fetcher.rs index 59a7e9f14..d4216d254 100644 --- a/src/core/src/tile_fetcher.rs +++ b/src/core/src/tile_fetcher.rs @@ -1,5 +1,4 @@ use crate::downloader::{query, Downloader}; -use crate::renderable::HiPS2D; use crate::time::{DeltaTime, Time}; use crate::Abort; @@ -10,6 +9,8 @@ use std::rc::Rc; const MAX_NUM_TILE_FETCHING: usize = 8; const MAX_QUERY_QUEUE_LENGTH: usize = 100; +use crate::renderable::hips::HiPS; + pub struct TileFetcherQueue { // A stack of queries to fetch queries: VecDeque, @@ -30,7 +31,6 @@ pub struct HiPSLocalFiles { use crate::tile_fetcher::query::Tile; use crate::HEALPixCell; use al_api::hips::ImageExt; -use al_core::image::format::ImageFormatType; use wasm_bindgen::prelude::wasm_bindgen; use wasm_bindgen::JsValue; @@ -52,13 +52,7 @@ impl HiPSLocalFiles { } pub fn insert(&mut self, depth: u8, ipix: u64, ext: ImageExt, file: web_sys::File) { - let mut tiles_per_fmt = match ext { - ImageExt::Fits => &mut self.tiles[0], - ImageExt::Jpeg => &mut self.tiles[1], - ImageExt::Png => &mut self.tiles[2], - ImageExt::Webp => &mut self.tiles[3], - }; - + let tiles_per_fmt = &mut self.tiles[ext as usize]; tiles_per_fmt[depth as usize].insert(ipix, file); } @@ -190,7 +184,7 @@ impl TileFetcherQueue { pub fn launch_starting_hips_requests( &mut self, - hips: &HiPS2D, + hips: &HiPS, downloader: Rc>, ) { let cfg = hips.get_config(); @@ -221,7 +215,13 @@ impl TileFetcherQueue { //Request the allsky for the small tile size or if base tiles are not available if tile_size <= 128 || cfg.get_min_depth_tile() > 0 { // Request the allsky - downloader.borrow_mut().fetch(query::Allsky::new(cfg)); + downloader.borrow_mut().fetch(query::Allsky::new( + cfg, + match hips { + HiPS::D2(_) => None, + HiPS::D3(h) => Some(h.get_slice() as u32), + }, + )); } else if cfg.get_min_depth_tile() == 0 { #[cfg(target_arch = "wasm32")] { @@ -231,13 +231,7 @@ impl TileFetcherQueue { let min_order = cfg.get_min_depth_texture(); for tile_cell in crate::healpix::cell::ALLSKY_HPX_CELLS_D0 { - if let Ok(query) = self.check_in_file_list(query::Tile::new( - tile_cell, - hips_cdid.clone(), - hips_url.clone(), - hips_fmt, - None, - )) { + if let Ok(query) = self.check_in_file_list(hips.get_tile_query(tile_cell)) { let dl = downloader.clone(); crate::utils::set_timeout( diff --git a/src/glsl/webgl2/hips3d/rasterizer/color.frag b/src/glsl/webgl2/hips3d/rasterizer/color.frag new file mode 100644 index 000000000..6aac6811c --- /dev/null +++ b/src/glsl/webgl2/hips3d/rasterizer/color.frag @@ -0,0 +1,21 @@ +#version 300 es +precision lowp float; +precision lowp sampler3D; +precision lowp isampler3D; +precision lowp usampler3D; + +uniform sampler3D tex; + +in vec3 frag_uv; + +out vec4 out_frag_color; +uniform float opacity; + +#include ../../hips/color.glsl; + +void main() { + vec4 color = get_color_from_texture(vec3(frag_uv.xy, mod(frag_uv.z, 32.0) / 32.0)); + + out_frag_color = color; + out_frag_color.a = opacity * out_frag_color.a; +} \ No newline at end of file diff --git a/src/glsl/webgl2/hips3d/rasterizer/grayscale_to_colormap.frag b/src/glsl/webgl2/hips3d/rasterizer/grayscale_to_colormap.frag new file mode 100644 index 000000000..989366c22 --- /dev/null +++ b/src/glsl/webgl2/hips3d/rasterizer/grayscale_to_colormap.frag @@ -0,0 +1,22 @@ +#version 300 es +precision lowp float; +precision lowp sampler3D; +precision lowp isampler3D; +precision lowp usampler3D; + +uniform sampler3D tex; + +in vec3 frag_uv; + +out vec4 out_frag_color; + +#include ../../hips/color.glsl; + +uniform float opacity; + +void main() { + vec4 color = get_colormap_from_grayscale_texture(frag_uv); + + out_frag_color = color; + out_frag_color.a = out_frag_color.a * opacity; +} \ No newline at end of file diff --git a/src/glsl/webgl2/hips3d/rasterizer/grayscale_to_colormap_i.frag b/src/glsl/webgl2/hips3d/rasterizer/grayscale_to_colormap_i.frag new file mode 100644 index 000000000..399603502 --- /dev/null +++ b/src/glsl/webgl2/hips3d/rasterizer/grayscale_to_colormap_i.frag @@ -0,0 +1,22 @@ +#version 300 es +precision lowp float; +precision lowp sampler3D; +precision lowp isampler3D; +precision lowp usampler3D; + +uniform isampler3D tex; + +in vec3 frag_uv; + +out vec4 out_frag_color; + +#include ../../hips/color_i.glsl; + +uniform float opacity; + +void main() { + vec4 color = get_colormap_from_grayscale_texture(frag_uv); + + out_frag_color = color; + out_frag_color.a = out_frag_color.a * opacity; +} \ No newline at end of file diff --git a/src/glsl/webgl2/hips3d/rasterizer/grayscale_to_colormap_u.frag b/src/glsl/webgl2/hips3d/rasterizer/grayscale_to_colormap_u.frag new file mode 100644 index 000000000..8482d2453 --- /dev/null +++ b/src/glsl/webgl2/hips3d/rasterizer/grayscale_to_colormap_u.frag @@ -0,0 +1,22 @@ +#version 300 es +precision lowp float; +precision lowp sampler3D; +precision lowp isampler3D; +precision lowp usampler3D; + +uniform usampler3D tex; + +in vec3 frag_uv; + +out vec4 out_frag_color; + +#include ../../hips/color_u.glsl; + +uniform float opacity; + +void main() { + vec4 color = get_colormap_from_grayscale_texture(frag_uv); + + out_frag_color = color; + out_frag_color.a = out_frag_color.a * opacity; +} \ No newline at end of file diff --git a/src/glsl/webgl2/hips3d/rasterizer/raster.vert b/src/glsl/webgl2/hips3d/rasterizer/raster.vert new file mode 100644 index 000000000..8277a3996 --- /dev/null +++ b/src/glsl/webgl2/hips3d/rasterizer/raster.vert @@ -0,0 +1,26 @@ +#version 300 es +precision lowp float; + +layout (location = 0) in vec2 lonlat; +layout (location = 1) in vec3 uv; + +out vec3 frag_uv; + +// current time in ms +uniform mat4 inv_model; +uniform vec2 ndc_to_clip; +uniform float czf; + +#include ../../projection/projection.glsl; + +void main() { + vec3 p_xyz = lonlat2xyz(lonlat); + vec4 p_w = inv_model * vec4(p_xyz, 1.0); + // 3. Process the projection + vec2 p_clip = proj(p_w.xyz); + + vec2 p_ndc = p_clip / (ndc_to_clip * czf); + gl_Position = vec4(p_ndc, 0.0, 1.0); + + frag_uv = uv; +} \ No newline at end of file diff --git a/src/js/HiPS.js b/src/js/HiPS.js index 071a53602..78268b59f 100644 --- a/src/js/HiPS.js +++ b/src/js/HiPS.js @@ -287,7 +287,9 @@ export let HiPS = (function () { HiPS.prototype._parseProperties = function(properties) { let self = this; self.creatorDid = properties.creator_did || self.creatorDid; - // url + + // Cube depth + self.cubeDepth = properties && properties.hips_cube_depth && +properties.hips_cube_depth; // Max order self.maxOrder = @@ -704,6 +706,12 @@ export let HiPS = (function () { this.setOptions({contrast}) }; + HiPS.prototype.setSliceNumber = function(slice) { + if (this.added) { + this.view.wasm.setSliceNumber(this.layer, slice); + } + } + // Private method for updating the backend with the new meta HiPS.prototype._updateMetadata = function () { try { @@ -960,6 +968,7 @@ export let HiPS = (function () { hipsInitialFov: self.initialFov, hipsInitialRa: self.initialRa, hipsInitialDec: self.initialDec, + hipsCubeDepth: self.cubeDepth, isPlanetaryBody: self.isPlanetaryBody(), hipsBody: self.hipsBody, },