From ee2eb6e704ee55902c395469ff923f125ae42480 Mon Sep 17 00:00:00 2001 From: bmatthieu3 Date: Thu, 13 Mar 2025 18:45:15 +0100 Subject: [PATCH] Fix NaN detection by the GPU on float textures for window platform float textures coming from BITPIX<0 fits images are sent to GPU as RGBA8UI textures. Float decoding from a vec4 rgba is done in the shader. Then this obtained decoded float can be tested against nan/inf in the shader --- src/core/al-core/src/image/fits.rs | 2 +- src/core/al-core/src/image/format.rs | 64 ++++---- src/core/al-core/src/texture/pixel.rs | 1 + src/core/src/downloader/request/allsky.rs | 89 +++++------ src/core/src/renderable/hips/d2/mod.rs | 46 ++++-- src/core/src/renderable/hips/d3/mod.rs | 23 ++- src/core/src/renderable/image/cuts.rs | 18 +-- src/core/src/renderable/image/mod.rs | 143 +++++++++++------- .../src/renderable/image/subdivide_texture.rs | 76 +++++++--- src/core/src/renderable/moc/renderer.rs | 2 - src/core/src/renderable/mod.rs | 2 - src/glsl/webgl2/fits/base.vert | 4 +- src/glsl/webgl2/fits/sampler.frag | 26 +++- src/glsl/webgl2/hips/color.glsl | 26 ++-- .../hips/rasterizer/color_to_colormap.frag | 24 +++ .../hips/raytracer/color_to_colormap.frag | 52 +++++++ .../hips3d/rasterizer/color_to_colormap.frag | 21 +++ 17 files changed, 400 insertions(+), 219 deletions(-) create mode 100644 src/glsl/webgl2/hips/rasterizer/color_to_colormap.frag create mode 100644 src/glsl/webgl2/hips/raytracer/color_to_colormap.frag create mode 100644 src/glsl/webgl2/hips3d/rasterizer/color_to_colormap.frag diff --git a/src/core/al-core/src/image/fits.rs b/src/core/al-core/src/image/fits.rs index 55e072cb..a27ff2b1 100644 --- a/src/core/al-core/src/image/fits.rs +++ b/src/core/al-core/src/image/fits.rs @@ -163,7 +163,7 @@ impl Image for Fits<'_> { ); } Data::F32(data) => { - let view = unsafe { R32F::view(&data) }; + let view = unsafe { R8UI::view(&std::slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * 4)) }; textures.tex_sub_image_3d_with_opt_array_buffer_view( offset.x, offset.y, diff --git a/src/core/al-core/src/image/format.rs b/src/core/al-core/src/image/format.rs index 43cbb37b..43137542 100644 --- a/src/core/al-core/src/image/format.rs +++ b/src/core/al-core/src/image/format.rs @@ -152,21 +152,13 @@ impl ImageFormat for RGB32F { #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] pub struct R32F; impl ImageFormat for R32F { - type P = [f32; 1]; + type P = [u8; 4]; - const NUM_CHANNELS: usize = 1; + const NUM_CHANNELS: usize = 4; - #[cfg(feature = "webgl2")] - const FORMAT: u32 = WebGlRenderingCtx::RED as u32; - #[cfg(feature = "webgl1")] - const FORMAT: u32 = WebGlRenderingCtx::LUMINANCE as u32; - - #[cfg(feature = "webgl2")] - const INTERNAL_FORMAT: i32 = WebGlRenderingCtx::R32F as i32; - #[cfg(feature = "webgl1")] - const INTERNAL_FORMAT: i32 = WebGlRenderingCtx::LUMINANCE as i32; - - const TYPE: u32 = WebGlRenderingCtx::FLOAT; + const FORMAT: u32 = WebGlRenderingCtx::RGBA as u32; + const INTERNAL_FORMAT: i32 = WebGlRenderingCtx::RGBA8 as i32; + const TYPE: u32 = WebGlRenderingCtx::UNSIGNED_BYTE; const CHANNEL_TYPE: ChannelType = ChannelType::R32F; @@ -174,41 +166,31 @@ impl ImageFormat for R32F { Ok(Bytes::Borrowed(raw_bytes)) } - type ArrayBufferView = js_sys::Float32Array; + type ArrayBufferView = js_sys::Uint8Array; unsafe fn view(s: &[::Item]) -> Self::ArrayBufferView { Self::ArrayBufferView::view(s) } } -#[cfg(feature = "webgl2")] #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] pub struct R64F; -#[cfg(feature = "webgl2")] impl ImageFormat for R64F { - type P = [f32; 1]; + type P = [u8; 4]; - const NUM_CHANNELS: usize = 1; + const NUM_CHANNELS: usize = 4; - #[cfg(feature = "webgl2")] - const FORMAT: u32 = WebGlRenderingCtx::RED as u32; - #[cfg(feature = "webgl1")] - const FORMAT: u32 = WebGlRenderingCtx::LUMINANCE as u32; + const FORMAT: u32 = WebGlRenderingCtx::RGBA as u32; + const INTERNAL_FORMAT: i32 = WebGlRenderingCtx::RGBA8 as i32; + const TYPE: u32 = WebGlRenderingCtx::UNSIGNED_BYTE; - #[cfg(feature = "webgl2")] - const INTERNAL_FORMAT: i32 = WebGlRenderingCtx::R32F as i32; - #[cfg(feature = "webgl1")] - const INTERNAL_FORMAT: i32 = WebGlRenderingCtx::LUMINANCE as i32; - - const TYPE: u32 = WebGlRenderingCtx::FLOAT; - - const CHANNEL_TYPE: ChannelType = ChannelType::R64F; + const CHANNEL_TYPE: ChannelType = ChannelType::R32F; fn decode(raw_bytes: &[u8]) -> Result, &'static str> { Ok(Bytes::Borrowed(raw_bytes)) } - type ArrayBufferView = js_sys::Float32Array; + type ArrayBufferView = js_sys::Uint8Array; unsafe fn view(s: &[::Item]) -> Self::ArrayBufferView { Self::ArrayBufferView::view(s) @@ -309,6 +291,18 @@ pub enum ChannelType { R32I, } +impl ChannelType { + pub fn is_colored(&self) -> bool { + match self { + ChannelType::RGBA32F + | ChannelType::RGB32F + | ChannelType::RGBA8U + | ChannelType::RGB8U => true, + _ => false, + } + } +} + pub const NUM_CHANNELS: usize = 9; #[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)] @@ -327,12 +321,6 @@ impl ImageFormatType { } pub fn is_colored(&self) -> bool { - match self.channel { - ChannelType::RGBA32F - | ChannelType::RGB32F - | ChannelType::RGBA8U - | ChannelType::RGB8U => true, - _ => false, - } + self.channel.is_colored() } } diff --git a/src/core/al-core/src/texture/pixel.rs b/src/core/al-core/src/texture/pixel.rs index e724078a..f5f1cdeb 100644 --- a/src/core/al-core/src/texture/pixel.rs +++ b/src/core/al-core/src/texture/pixel.rs @@ -11,6 +11,7 @@ pub trait Pixel: + Copy + std::fmt::Debug + cgmath::Zero + + cgmath::One + std::cmp::PartialEq + crate::convert::Cast; type Container: ArrayBuffer; diff --git a/src/core/src/downloader/request/allsky.rs b/src/core/src/downloader/request/allsky.rs index 9f2b5f77..fd108a47 100644 --- a/src/core/src/downloader/request/allsky.rs +++ b/src/core/src/downloader/request/allsky.rs @@ -160,47 +160,42 @@ impl From for AllskyRequest { let Fits { hdu } = Fits::from_reader(&mut reader) .map_err(|_| JsValue::from_str("Parsing fits error of allsky"))?; - //let width_allsky_px = 27 * std::cmp::min(tile_size, 64) as i32; - //let height_allsky_px = 29 * std::cmp::min(tile_size, 64) as i32; let data = hdu.get_data(); match data { InMemData::U8(data) => { Ok(handle_allsky_fits(&data, tile_size, texture_size)? - .into_iter() .map(|image| ImageType::RawR8ui { image }) .collect()) } InMemData::I16(data) => { Ok(handle_allsky_fits(&data, tile_size, texture_size)? - .into_iter() .map(|image| ImageType::RawR16i { image }) .collect()) } InMemData::I32(data) => { Ok(handle_allsky_fits(&data, tile_size, texture_size)? - .into_iter() .map(|image| ImageType::RawR32i { image }) .collect()) } - InMemData::F32(data) => { - Ok(handle_allsky_fits(&data, tile_size, texture_size)? - .into_iter() - .map(|image| ImageType::RawR32f { image }) - .collect()) - } InMemData::I64(data) => { let data = data.iter().map(|v| *v as i32).collect::>(); Ok(handle_allsky_fits(&data, tile_size, texture_size)? - .into_iter() .map(|image| ImageType::RawR32i { image }) .collect()) } + InMemData::F32(data) => { + let data = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * 4) }; + Ok(handle_allsky_fits(&data, tile_size, texture_size)? + .map(|image| ImageType::RawRgba8u { image }) + .collect()) + } InMemData::F64(data) => { let data = data.iter().map(|v| *v as f32).collect::>(); + let data = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * 4) }; + Ok(handle_allsky_fits(&data, tile_size, texture_size)? - .into_iter() - .map(|image| ImageType::RawR32f { image }) + .map(|image| ImageType::RawRgba8u { image }) .collect()) } } @@ -226,44 +221,44 @@ fn handle_allsky_file( allsky_tile_size: i32, texture_size: i32, tile_size: i32, -) -> Result>, JsValue> { +) -> Result>, JsValue> { let num_tiles_per_texture = (texture_size / tile_size) * (texture_size / tile_size); let num_tiles = num_tiles_per_texture * 12; - let mut tiles = Vec::with_capacity(num_tiles as usize); let num_allsky_tiles_per_tile = (tile_size / allsky_tile_size) * (tile_size / allsky_tile_size); let mut src_idx = 0; - for _ in 0..num_tiles { - let mut base_tile = + let tiles = (0..num_tiles) + .map(move |_| { + let mut base_tile = ImageBuffer::::allocate(&::P::BLACK, tile_size, tile_size); - for idx_tile in 0..num_allsky_tiles_per_tile { - let (x, y) = crate::utils::unmortonize(idx_tile as u64); - let dx = x * (allsky_tile_size as u32); - let dy = y * (allsky_tile_size as u32); + for idx_tile in 0..num_allsky_tiles_per_tile { + let (x, y) = crate::utils::unmortonize(idx_tile as u64); + let dx = x * (allsky_tile_size as u32); + let dy = y * (allsky_tile_size as u32); - let sx = (src_idx % 27) * allsky_tile_size; - let sy = (src_idx / 27) * allsky_tile_size; - let s = ImageBufferView { - x: sx as i32, - y: sy as i32, - w: allsky_tile_size as i32, - h: allsky_tile_size as i32, - }; - let d = ImageBufferView { - x: dx as i32, - y: dy as i32, - w: allsky_tile_size as i32, - h: allsky_tile_size as i32, - }; + let sx = (src_idx % 27) * allsky_tile_size; + let sy = (src_idx / 27) * allsky_tile_size; + let s = ImageBufferView { + x: sx as i32, + y: sy as i32, + w: allsky_tile_size as i32, + h: allsky_tile_size as i32, + }; + let d = ImageBufferView { + x: dx as i32, + y: dy as i32, + w: allsky_tile_size as i32, + h: allsky_tile_size as i32, + }; - base_tile.tex_sub(&allsky, &s, &d); + base_tile.tex_sub(&allsky, &s, &d); - src_idx += 1; - } + src_idx += 1; + } - tiles.push(base_tile); - } + base_tile + }); Ok(tiles) } @@ -272,7 +267,7 @@ fn handle_allsky_fits( allsky_data: &[<::P as Pixel>::Item], tile_size: i32, texture_size: i32, -) -> Result>, JsValue> { +) -> Result>, JsValue> { let allsky_tile_size = std::cmp::min(tile_size, 64); let width_allsky_px = 27 * allsky_tile_size; let height_allsky_px = 29 * allsky_tile_size; @@ -286,9 +281,8 @@ fn handle_allsky_fits( let allsky = ImageBuffer::::new(reversed_rows_data, width_allsky_px, height_allsky_px); - let allsky_tiles = handle_allsky_file::(allsky, allsky_tile_size, texture_size, tile_size)? - .into_iter() - .map(|image| { + let allsky_tiles_iter = handle_allsky_file::(allsky, allsky_tile_size, texture_size, tile_size)? + .map(move |image| { // The GPU does a specific transformation on the UV // for FITS tiles // We must revert this to be compatible with this GPU transformation @@ -298,10 +292,9 @@ fn handle_allsky_fits( } ImageBuffer::::new(new_image_data, tile_size, tile_size) - }) - .collect(); + }); - Ok(allsky_tiles) + Ok(allsky_tiles_iter) } use al_core::image::format::RGBA8U; diff --git a/src/core/src/renderable/hips/d2/mod.rs b/src/core/src/renderable/hips/d2/mod.rs index 943e4e60..8e94847f 100644 --- a/src/core/src/renderable/hips/d2/mod.rs +++ b/src/core/src/renderable/hips/d2/mod.rs @@ -75,13 +75,22 @@ pub fn get_raster_shader<'a>( 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, - "hips_rasterizer_raster.vert", - "hips_rasterizer_color.frag", - ) + if config.get_format().is_colored() { + if cmap.label() == "native" { + crate::shader::get_shader( + gl, + shaders, + "hips_rasterizer_raster.vert", + "hips_rasterizer_color.frag", + ) + } else { + crate::shader::get_shader( + gl, + shaders, + "hips_rasterizer_raster.vert", + "hips_rasterizer_color_to_colormap.frag", + ) + } } else { if config.tex_storing_unsigned_int { crate::shader::get_shader( @@ -115,13 +124,22 @@ pub fn get_raytracer_shader<'a>( 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", - ) + if config.get_format().is_colored() { + if cmap.label() == "native" { + crate::shader::get_shader( + gl, + shaders, + "hips_raytracer_raytracer.vert", + "hips_raytracer_color.frag", + ) + } else { + crate::shader::get_shader( + gl, + shaders, + "hips_raytracer_raytracer.vert", + "hips_raytracer_color_to_colormap.frag", + ) + } } else { if config.tex_storing_unsigned_int { crate::shader::get_shader( diff --git a/src/core/src/renderable/hips/d3/mod.rs b/src/core/src/renderable/hips/d3/mod.rs index 008707b8..1b31c381 100644 --- a/src/core/src/renderable/hips/d3/mod.rs +++ b/src/core/src/renderable/hips/d3/mod.rs @@ -52,13 +52,22 @@ pub fn get_raster_shader<'a>( 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", - ) + if config.get_format().is_colored() { + if cmap.label() == "native" { + crate::shader::get_shader( + gl, + shaders, + "hips3d_rasterizer_raster.vert", + "hips3d_rasterizer_color.frag", + ) + } else { + crate::shader::get_shader( + gl, + shaders, + "hips3d_rasterizer_raster.vert", + "hips3d_rasterizer_color_to_colormap.frag", + ) + } } else { if config.tex_storing_unsigned_int { crate::shader::get_shader( diff --git a/src/core/src/renderable/image/cuts.rs b/src/core/src/renderable/image/cuts.rs index 87f01d91..8557883f 100644 --- a/src/core/src/renderable/image/cuts.rs +++ b/src/core/src/renderable/image/cuts.rs @@ -1,23 +1,23 @@ use std::cmp::Ordering; use std::ops::Range; -pub fn first_and_last_percent(slice: &mut [T], first_percent: i32, last_percent: i32) -> Range +pub fn first_and_last_percent(slice: &mut [T], mut first_percent: i32, mut last_percent: i32) -> Range where T: PartialOrd + Copy, { + if first_percent > last_percent { + std::mem::swap(&mut first_percent, &mut last_percent); + } + let n = slice.len(); - let first_pct_idx = ((first_percent as f32) * 0.01 * (n as f32)) as usize; - let last_pct_idx = ((last_percent as f32) * 0.01 * (n as f32)) as usize; + let i1 = ((first_percent as f32) * 0.01 * (n as f32)) as usize; + let i2 = ((last_percent as f32) * 0.01 * (n as f32)) as usize; let min_val = { - let (_, min_val, _) = slice.select_nth_unstable_by(first_pct_idx, |a, b| { - a.partial_cmp(b).unwrap_or(Ordering::Greater) - }); + let (_, min_val, _) = slice.select_nth_unstable_by(i1, |a, b| a.partial_cmp(b).unwrap_or(Ordering::Greater)); *min_val }; let max_val = { - let (_, max_val, _) = slice.select_nth_unstable_by(last_pct_idx, |a, b| { - a.partial_cmp(b).unwrap_or(Ordering::Greater) - }); + let (_, max_val, _) = slice.select_nth_unstable_by(i2, |a, b| a.partial_cmp(b).unwrap_or(Ordering::Greater)); *max_val }; diff --git a/src/core/src/renderable/image/mod.rs b/src/core/src/renderable/image/mod.rs index 5f7d990e..749786d9 100644 --- a/src/core/src/renderable/image/mod.rs +++ b/src/core/src/renderable/image/mod.rs @@ -3,6 +3,7 @@ pub mod grid; pub mod subdivide_texture; use al_core::webgl_ctx::WebGlRenderingCtx; +use al_core::convert::Cast; use std::fmt::Debug; use std::marker::Unpin; use std::vec; @@ -35,6 +36,7 @@ use crate::ProjectionType; use crate::ShaderManager; use std::ops::Range; +type PixelItem = <::P as Pixel>::Item; pub struct Image { /// A reference to the GL context @@ -49,7 +51,7 @@ pub struct Image { /// Parameters extracted from the fits wcs: WCS, - blank: f32, + blank: Option, scale: f32, offset: f32, cuts: Range, @@ -75,6 +77,7 @@ use fitsrs::hdu::header::extension; use fitsrs::hdu::AsyncHDU; use futures::io::BufReader; use futures::AsyncReadExt; + impl Image { pub async fn from_reader_and_wcs( gl: &WebGlContext, @@ -103,9 +106,8 @@ impl Image { // apply bscale to the cuts let offset = offset.unwrap_or(0.0); let scale = scale.unwrap_or(1.0); - let blank = blank.unwrap_or(std::f32::NAN); - let (textures, mut cuts) = if width <= max_tex_size as u64 && height <= max_tex_size as u64 + let (textures, cuts) = if width <= max_tex_size as u64 && height <= max_tex_size as u64 { max_tex_size_x = width as usize; max_tex_size_y = height as usize; @@ -113,7 +115,7 @@ impl Image { let num_pixels_to_read = (width as usize) * (height as usize); let num_bytes_to_read = - num_pixels_to_read * std::mem::size_of::<::Item>() * F::NUM_CHANNELS; + num_pixels_to_read * std::mem::size_of::(); let mut buf = vec![0; num_bytes_to_read]; let _ = reader @@ -123,30 +125,11 @@ impl Image { // bytes aligned unsafe { - let slice = std::slice::from_raw_parts( - buf[..].as_ptr() as *const ::Item, + let data = std::slice::from_raw_parts_mut( + buf[..].as_mut_ptr() as *mut PixelItem, (num_pixels_to_read as usize) * F::NUM_CHANNELS, ); - let cuts = if F::NUM_CHANNELS == 1 { - let mut samples = slice - .iter() - .filter_map(|item| { - let t: f32 = - <::Item as al_core::convert::Cast>::cast(*item); - if t.is_nan() || t == blank { - None - } else { - Some(t) - } - }) - .collect::>(); - - cuts::first_and_last_percent(&mut samples, 1, 99) - } else { - 0.0..1.0 - }; - let texture = Texture2D::create_from_raw_pixels::( gl, width as i32, @@ -171,9 +154,52 @@ impl Image { WebGlRenderingCtx::CLAMP_TO_EDGE, ), ], - Some(slice), + Some(data), )?; + let cuts = match F::CHANNEL_TYPE { + ChannelType::R32F | ChannelType::R64F => { + let pixels = std::slice::from_raw_parts(data.as_ptr() as *const f32, data.len() / 4); + + let mut sub_pixels = pixels.iter() + .step_by(100) + .filter(|pixel| (*pixel).is_finite()) + .cloned() + .collect::>(); + + cuts::first_and_last_percent(&mut sub_pixels, 1, 99) + } + ChannelType::R8UI | ChannelType::R16I | ChannelType::R32I => { + // BLANK is only valid for those channels/BITPIX (> 0) + if let Some(blank) = blank { + let mut sub_pixels = data.iter() + .step_by(100) + .filter_map(|pixel| { + let pixel = as Cast>::cast(*pixel); + + if pixel != blank { + Some(pixel) + } else { + None + } + }) + .collect::>(); + + cuts::first_and_last_percent(&mut sub_pixels, 1, 99) + } else { + // No blank value => we consider all the values + let mut sub_pixels = data.iter() + .step_by(100) + .map(|pixel| as Cast>::cast(*pixel)) + .collect::>(); + + cuts::first_and_last_percent(&mut sub_pixels, 1, 99) + } + } + // RGB(A) images + _ => 0.0..1.0 + }; + (vec![texture], cuts) } } else { @@ -195,7 +221,7 @@ impl Image { let start = cuts.start * scale + offset; let end = cuts.end * scale + offset; - cuts = start..end; + let cuts = start..end; let num_indices = vec![]; let indices = vec![]; @@ -319,26 +345,22 @@ impl Image { gl: &WebGlContext, hdu: &mut AsyncHDU<'a, BufReader, extension::image::Image>, coo_sys: CooSystem, - //reader: &'a mut BufReader, ) -> Result where R: AsyncRead + Unpin + Debug + 'a, { - // Load the fits file + // Load the FITS file let header = hdu.get_header(); let scale = header .get_parsed::(b"BSCALE ") - .unwrap_or(Ok(1.0)) - .unwrap() as f32; + .map(|v| v.unwrap()); let offset = header .get_parsed::(b"BZERO ") - .unwrap_or(Ok(0.0)) - .unwrap() as f32; + .map(|v| v.unwrap()); let blank = header .get_parsed::(b"BLANK ") - .unwrap_or(Ok(std::f64::NAN)) - .unwrap() as f32; + .map(|v| v.unwrap()); // Create a WCS from a specific header unit let wcs = WCS::from_fits_header(header) @@ -354,9 +376,9 @@ impl Image { gl, reader, wcs, - Some(scale), - Some(offset), - Some(blank), + scale.map(|v| v as f32), + offset.map(|v| v as f32), + blank.map(|v| v as f32), coo_sys, ) .await @@ -368,9 +390,9 @@ impl Image { gl, reader, wcs, - Some(scale), - Some(offset), - Some(blank), + scale.map(|v| v as f32), + offset.map(|v| v as f32), + blank.map(|v| v as f32), coo_sys, ) .await @@ -382,9 +404,9 @@ impl Image { gl, reader, wcs, - Some(scale), - Some(offset), - Some(blank), + scale.map(|v| v as f32), + offset.map(|v| v as f32), + blank.map(|v| v as f32), coo_sys, ) .await @@ -401,9 +423,9 @@ impl Image { gl, reader, wcs, - Some(scale), - Some(offset), - Some(blank), + scale.map(|v| v as f32), + offset.map(|v| v as f32), + blank.map(|v| v as f32), coo_sys, ) .await @@ -415,9 +437,9 @@ impl Image { gl, reader, wcs, - Some(scale), - Some(offset), - Some(blank), + scale.map(|v| v as f32), + offset.map(|v| v as f32), + blank.map(|v| v as f32), coo_sys, ) .await @@ -434,9 +456,9 @@ impl Image { gl, reader, wcs, - Some(scale), - Some(offset), - Some(blank), + scale.map(|v| v as f32), + offset.map(|v| v as f32), + blank.map(|v| v as f32), coo_sys, ) .await @@ -724,15 +746,22 @@ impl Image { let texture = &self.textures[idx_tex]; let num_indices = self.num_indices[idx] as i32; - shader - .bind(&self.gl) + let shader_bound = shader + .bind(&self.gl); + + shader_bound .attach_uniforms_from(colormaps) .attach_uniforms_with_params_from(color, colormaps) .attach_uniform("opacity", opacity) .attach_uniform("tex", texture) .attach_uniform("scale", &self.scale) - .attach_uniform("offset", &self.offset) - .attach_uniform("blank", &self.blank) + .attach_uniform("offset", &self.offset); + + if let Some(blank) = self.blank { + shader_bound.attach_uniform("blank", &blank); + } + + shader_bound .bind_vertex_array_object_ref(&self.vao) .draw_elements_with_i32( WebGl2RenderingContext::TRIANGLES, diff --git a/src/core/src/renderable/image/subdivide_texture.rs b/src/core/src/renderable/image/subdivide_texture.rs index feb06bf7..486126c1 100644 --- a/src/core/src/renderable/image/subdivide_texture.rs +++ b/src/core/src/renderable/image/subdivide_texture.rs @@ -1,3 +1,4 @@ +use al_core::image::format::ChannelType; use wasm_bindgen::JsValue; use futures::AsyncReadExt; @@ -10,13 +11,16 @@ use al_core::Texture2D; use al_core::WebGlContext; use std::ops::Range; +use al_core::convert::Cast; +type PixelItem = <::P as Pixel>::Item; + pub async fn crop_image<'a, F, R>( gl: &WebGlContext, width: u64, height: u64, mut reader: R, max_tex_size: u64, - blank: f32, + blank: Option, ) -> Result<(Vec, Range), JsValue> where F: ImageFormat, @@ -67,10 +71,12 @@ where let mut pixels_written = 0; let num_pixels = width * height; - let step_x_cut = (width / 50) as usize; - let step_y_cut = (height / 50) as usize; + const PIXEL_STEP: u64 = 256; - let mut samples = vec![]; + let step_x_cut = (width / PIXEL_STEP) as usize; + let step_y_cut = (height / PIXEL_STEP) as usize; + + let mut sub_pixels = vec![]; let step_cut = step_x_cut.max(step_y_cut) + 1; @@ -101,36 +107,60 @@ where let dy = (pixels_written / width) - off_y_px; let view = unsafe { - let slice = std::slice::from_raw_parts( + let data = std::slice::from_raw_parts( buf[..num_bytes_to_read].as_ptr() as *const ::Item, (num_pixels_to_read as usize) * F::NUM_CHANNELS, ); // compute the cuts if the pixel is grayscale - if F::NUM_CHANNELS == 1 { - // fill the samples buffer - if (pixels_written / width) % (step_cut as u64) == 0 { - // We are in a good line - let xmin = pixels_written % width; + if (pixels_written / width) % (step_cut as u64) == 0 { + // We are in a good line + let xmin = pixels_written % width; - for i in (0..width).step_by(step_cut) { - if (xmin..(xmin + num_pixels_to_read)).contains(&i) { - let j = (i - xmin) as usize; + match F::CHANNEL_TYPE { + ChannelType::R32F | ChannelType::R64F => { + let pixels = std::slice::from_raw_parts(data.as_ptr() as *const f32, data.len() / 4); - let sj: f32 = <::Item as al_core::convert::Cast< - f32, - >>::cast(slice[j]); - if !sj.is_nan() { - if blank != sj { - samples.push(sj); + for i in (0..width).step_by(step_cut) { + if (xmin..(xmin + num_pixels_to_read)).contains(&i) { + let j = (i - xmin) as usize; + + if pixels[j].is_finite() { + sub_pixels.push(pixels[j]); } } } - } + }, + ChannelType::R8UI | ChannelType::R16I | ChannelType::R32I => { + if let Some(blank) = blank { + for i in (0..width).step_by(step_cut) { + if (xmin..(xmin + num_pixels_to_read)).contains(&i) { + let j = (i - xmin) as usize; + + let pixel = as Cast>::cast(data[j]); + + if pixel != blank { + sub_pixels.push(pixel); + } + } + } + } else { + for i in (0..width).step_by(step_cut) { + if (xmin..(xmin + num_pixels_to_read)).contains(&i) { + let j = (i - xmin) as usize; + + let pixel = as Cast>::cast(data[j]); + sub_pixels.push(pixel); + } + } + } + }, + // colored pixels + _ => (), } } - F::view(slice) + F::view(data) }; (&mut tex_chunks[id_t as usize]) @@ -151,8 +181,8 @@ where } } - let cuts = if F::NUM_CHANNELS == 1 { - cuts::first_and_last_percent(&mut samples, 1, 99) + let cuts = if F::CHANNEL_TYPE.is_colored() { + cuts::first_and_last_percent(&mut sub_pixels, 1, 99) } else { 0.0..1.0 }; diff --git a/src/core/src/renderable/moc/renderer.rs b/src/core/src/renderable/moc/renderer.rs index 81b76043..8f70863f 100644 --- a/src/core/src/renderable/moc/renderer.rs +++ b/src/core/src/renderable/moc/renderer.rs @@ -1,6 +1,4 @@ use crate::{healpix::coverage::HEALPixCoverage, CameraViewPort, ShaderManager}; -use web_sys::WebGl2RenderingContext; - use al_core::WebGlContext; use wasm_bindgen::JsValue; diff --git a/src/core/src/renderable/mod.rs b/src/core/src/renderable/mod.rs index fc8bb021..036a0cb1 100644 --- a/src/core/src/renderable/mod.rs +++ b/src/core/src/renderable/mod.rs @@ -225,8 +225,6 @@ impl Layers { let cdid = self.ids.get(layer).unwrap_abort(); if let Some(hips) = self.hipses.get(cdid) { - let hips_cfg = hips.get_config(); - let allsky = hips.is_allsky(); let opaque = meta.opacity == 1.0; diff --git a/src/glsl/webgl2/fits/base.vert b/src/glsl/webgl2/fits/base.vert index 1741f861..50721cf6 100644 --- a/src/glsl/webgl2/fits/base.vert +++ b/src/glsl/webgl2/fits/base.vert @@ -1,6 +1,6 @@ #version 300 es -precision lowp float; -precision mediump int; +precision highp float; +precision highp int; layout (location = 0) in vec2 ndc_pos; layout (location = 1) in vec2 uv; diff --git a/src/glsl/webgl2/fits/sampler.frag b/src/glsl/webgl2/fits/sampler.frag index cded386a..aef1ea0c 100644 --- a/src/glsl/webgl2/fits/sampler.frag +++ b/src/glsl/webgl2/fits/sampler.frag @@ -1,9 +1,9 @@ #version 300 es -precision lowp float; -precision lowp sampler2D; +precision highp float; +precision highp sampler2D; precision lowp isampler2D; precision lowp usampler2D; -precision mediump int; +precision highp int; out vec4 out_frag_color; in vec2 frag_uv; @@ -25,19 +25,31 @@ uniform float reversed; #include ./../hips/transfer_funcs.glsl; #include ./../hips/tonal_corrections.glsl; -vec4 apply_colormap_to_grayscale(float x, float a) { +vec4 apply_colormap_to_grayscale(float x) { float alpha = x * scale + offset; alpha = transfer_func(H, alpha, min_value, max_value); // apply reversed alpha = mix(alpha, 1.0 - alpha, reversed); - vec4 new_color = mix(colormap_f(alpha) * a, vec4(0.0), float(x == blank || isnan(x))); + vec4 new_color = mix(colormap_f(alpha), vec4(0.0), float(isinf(x))); return apply_tonal(new_color); } +highp float decode32(highp vec4 rgba) { + highp float Sign = 1.0 - step(128.0,rgba[0])*2.0; + highp float Exponent = 2.0 * mod(rgba[0],128.0) + step(128.0,rgba[1]) - 127.0; + if (abs(Exponent + 127.0) < 1e-3) { + return 0.0; + } + highp float Mantissa = mod(rgba[1],128.0)*65536.0 + rgba[2]*256.0 +rgba[3] + float(0x800000); + highp float Result = Sign * exp2(Exponent) * (Mantissa * exp2(-23.0 )); + return Result; +} + void main() { - vec4 color = texture(tex, frag_uv); - out_frag_color = apply_colormap_to_grayscale(color.r, color.a); + highp float value = decode32(texture(tex, frag_uv).abgr*255.0); + // reconstruct the float value + out_frag_color = apply_colormap_to_grayscale(value); out_frag_color.a = out_frag_color.a * opacity; } \ No newline at end of file diff --git a/src/glsl/webgl2/hips/color.glsl b/src/glsl/webgl2/hips/color.glsl index 000f5fe8..478ab356 100644 --- a/src/glsl/webgl2/hips/color.glsl +++ b/src/glsl/webgl2/hips/color.glsl @@ -1,15 +1,11 @@ uniform float scale; uniform float offset; uniform float blank; - uniform float min_value; uniform float max_value; uniform int H; - uniform float reversed; - uniform float size_tile_uv; - uniform int tex_storing_fits; #include ../colormaps/colormap.glsl; @@ -26,7 +22,6 @@ vec3 reverse_uv(vec3 uv) { return uv; } - vec4 get_color_from_texture(vec3 UV) { vec4 color = get_pixels(UV); @@ -40,21 +35,34 @@ vec4 get_color_from_texture(vec3 UV) { return apply_tonal(color); } -vec4 apply_colormap_to_grayscale(float x, float a) { +vec4 apply_colormap_to_grayscale(float x) { float alpha = x * scale + offset; alpha = transfer_func(H, alpha, min_value, max_value); // apply reversed alpha = mix(alpha, 1.0 - alpha, reversed); - vec4 new_color = mix(colormap_f(alpha) * a, vec4(0.0), float(x == blank || isnan(x))); + vec4 new_color = mix(colormap_f(alpha), vec4(0.0), float(isinf(x) || isnan(x))); return apply_tonal(new_color); } +highp float decode32(highp vec4 rgba) { + highp float Sign = 1.0 - step(128.0,rgba[0])*2.0; + highp float Exponent = 2.0 * mod(rgba[0],128.0) + step(128.0,rgba[1]) - 127.0; + highp float Mantissa = mod(rgba[1],128.0)*65536.0 + rgba[2]*256.0 +rgba[3] + float(0x800000); + highp float Result = Sign * exp2(Exponent) * (Mantissa * exp2(-23.0 )); + return Result; +} + vec4 get_colormap_from_grayscale_texture(vec3 UV) { // FITS data pixels are reversed along the y axis vec3 uv = mix(UV, reverse_uv(UV), float(tex_storing_fits == 1)); - vec4 color = get_pixels(uv); - return apply_colormap_to_grayscale(color.r, color.a); + float value = decode32(get_pixels(uv).abgr*255.0); + return apply_colormap_to_grayscale(value); +} + +vec4 get_colormap_from_color_texture(vec3 uv) { + float value = get_pixels(uv).r; + return apply_colormap_to_grayscale(value); } \ No newline at end of file diff --git a/src/glsl/webgl2/hips/rasterizer/color_to_colormap.frag b/src/glsl/webgl2/hips/rasterizer/color_to_colormap.frag new file mode 100644 index 00000000..51f9c0fe --- /dev/null +++ b/src/glsl/webgl2/hips/rasterizer/color_to_colormap.frag @@ -0,0 +1,24 @@ +#version 300 es +precision lowp float; +precision lowp sampler2DArray; +precision lowp isampler2DArray; +precision lowp usampler2DArray; + +uniform sampler2DArray tex; + +in vec3 frag_uv_start; +in vec3 frag_uv_end; +in float frag_blending_factor; + +out vec4 out_frag_color; +uniform float opacity; + +#include ../color.glsl; + +void main() { + vec4 color_start = get_colormap_from_color_texture(frag_uv_start); + vec4 color_end = get_colormap_from_color_texture(frag_uv_end); + + out_frag_color = mix(color_start, color_end, frag_blending_factor); + out_frag_color.a = opacity * out_frag_color.a; +} \ No newline at end of file diff --git a/src/glsl/webgl2/hips/raytracer/color_to_colormap.frag b/src/glsl/webgl2/hips/raytracer/color_to_colormap.frag new file mode 100644 index 00000000..47cb3dd7 --- /dev/null +++ b/src/glsl/webgl2/hips/raytracer/color_to_colormap.frag @@ -0,0 +1,52 @@ +#version 300 es +precision lowp float; +precision lowp sampler2DArray; +precision lowp sampler2DArray; +precision lowp isampler2DArray; +precision mediump int; + +in vec3 frag_pos; +in vec2 out_clip_pos; +out vec4 out_frag_color; + +struct Tile { + int uniq; // Healpix cell + int texture_idx; // Index in the texture buffer + float start_time; // Absolute time that the load has been done in ms + float empty; +}; + +uniform Tile textures_tiles[12]; + +uniform float opacity; +uniform sampler2DArray tex; + +struct TileColor { + Tile tile; + vec4 color; + bool found; +}; + +#include ../color.glsl; +#include ../../projection/hpx_proj.glsl; + +vec4 get_tile_color(vec3 pos) { + HashDxDy result = hash_with_dxdy(0, pos.zxy); + + int idx = result.idx; + vec2 uv = vec2(result.dy, result.dx); + Tile tile = textures_tiles[idx]; + + vec2 offset = uv; + vec3 UV = vec3(offset, float(tile.texture_idx)); + + vec4 color = get_colormap_from_color_texture(UV); + color.a *= (1.0 - tile.empty); + return color; +} + +void main() { + vec4 c = get_tile_color(normalize(frag_pos)); + out_frag_color = c; + out_frag_color.a = out_frag_color.a * opacity; +} \ No newline at end of file diff --git a/src/glsl/webgl2/hips3d/rasterizer/color_to_colormap.frag b/src/glsl/webgl2/hips3d/rasterizer/color_to_colormap.frag new file mode 100644 index 00000000..6c162578 --- /dev/null +++ b/src/glsl/webgl2/hips3d/rasterizer/color_to_colormap.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_colormap_from_color_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