From c52c06001829f63f9d57b66612695df573bf1c34 Mon Sep 17 00:00:00 2001 From: Speykious Date: Mon, 1 Jan 2024 17:20:17 +0100 Subject: [PATCH] Remove rayon dependency (unneeded) --- inox2d-opengl/src/lib.rs | 16 ++--- inox2d-wgpu/src/lib.rs | 10 +-- inox2d-wgpu/src/node_bundle.rs | 6 +- inox2d/Cargo.toml | 1 - inox2d/src/formats/inp.rs | 5 +- inox2d/src/formats/payload.rs | 13 ++-- inox2d/src/model.rs | 3 +- inox2d/src/nodes/node_data.rs | 7 +- inox2d/src/texture.rs | 122 ++++++++++++++++++++++++++------- 9 files changed, 129 insertions(+), 54 deletions(-) diff --git a/inox2d-opengl/src/lib.rs b/inox2d-opengl/src/lib.rs index 48219c0..fd6b122 100644 --- a/inox2d-opengl/src/lib.rs +++ b/inox2d-opengl/src/lib.rs @@ -10,6 +10,7 @@ use std::ops::Deref; use gl_buffer::RenderCtxOpenglExt; use glam::{uvec2, Mat4, UVec2, Vec2, Vec3}; use glow::HasContext; +use inox2d::texture::{parallel_decode_model_textures, TextureId}; use tracing::{debug, error}; use inox2d::math::camera::Camera; @@ -17,7 +18,6 @@ use inox2d::model::{Model, ModelTexture}; use inox2d::nodes::node_data::{BlendMode, Composite, Part}; use inox2d::puppet::Puppet; use inox2d::render::{InoxRenderer, InoxRendererCommon, NodeRenderCtx, PartRenderCtx}; -use inox2d::texture::decode_model_textures; use self::shader::ShaderCompileError; use self::shaders::{CompositeMaskShader, CompositeShader, PartMaskShader, PartShader}; @@ -37,7 +37,7 @@ pub struct GlCache { pub blend_mode: Option, pub program: Option, pub vao: Option, - pub albedo: Option, + pub albedo: Option, } impl GlCache { @@ -97,7 +97,7 @@ impl GlCache { } } - pub fn update_albedo(&mut self, albedo: usize) -> bool { + pub fn update_albedo(&mut self, albedo: TextureId) -> bool { if let Some(prev_texture) = self.albedo.replace(albedo) { prev_texture != albedo } else { @@ -188,11 +188,11 @@ impl OpenglRenderer { fn upload_model_textures(&mut self, model_textures: &[ModelTexture]) -> Result<(), TextureError> { // decode textures in parallel - let shalltexs = decode_model_textures(model_textures); + let shalltexs = parallel_decode_model_textures(model_textures.iter(), None); // upload textures for (i, shalltex) in shalltexs.iter().enumerate() { - debug!("Uploading shallow texture {}", i); + debug!("Uploading shallow texture {:?}", i); let tex = texture::Texture::from_shallow_texture(&self.gl, shalltex)?; self.textures.push(tex); } @@ -302,9 +302,9 @@ impl OpenglRenderer { } let gl = &self.gl; - self.textures[part.tex_albedo].bind_on(gl, 0); - self.textures[part.tex_bumpmap].bind_on(gl, 1); - self.textures[part.tex_emissive].bind_on(gl, 2); + self.textures[part.tex_albedo.raw()].bind_on(gl, 0); + self.textures[part.tex_bumpmap.raw()].bind_on(gl, 1); + self.textures[part.tex_emissive.raw()].bind_on(gl, 2); } /// Clear the texture cache diff --git a/inox2d-wgpu/src/lib.rs b/inox2d-wgpu/src/lib.rs index 60d32f7..6403f7d 100644 --- a/inox2d-wgpu/src/lib.rs +++ b/inox2d-wgpu/src/lib.rs @@ -9,10 +9,10 @@ use inox2d::model::Model; use inox2d::nodes::node_data::{InoxData, MaskMode}; use inox2d::puppet::Puppet; use inox2d::render::RenderCtxKind; -use inox2d::texture::decode_model_textures; use encase::ShaderType; use glam::{vec3, Mat4, UVec2, Vec2, Vec3}; +use inox2d::texture::parallel_decode_model_textures; use tracing::warn; use wgpu::{util::DeviceExt, *}; @@ -52,7 +52,7 @@ impl Renderer { ..SamplerDescriptor::default() }); - let shalltexs = decode_model_textures(&model.textures); + let shalltexs = parallel_decode_model_textures(model.textures.iter(), None); for shalltex in &shalltexs { let texture_size = wgpu::Extent3d { width: shalltex.width(), @@ -167,9 +167,9 @@ impl Renderer { todo!() }; - render_pass.set_bind_group(1, &self.model_texture_binds[part.tex_albedo], &[]); - render_pass.set_bind_group(2, &self.model_texture_binds[part.tex_emissive], &[]); - render_pass.set_bind_group(3, &self.model_texture_binds[part.tex_bumpmap], &[]); + render_pass.set_bind_group(1, &self.model_texture_binds[part.tex_albedo.raw()], &[]); + render_pass.set_bind_group(2, &self.model_texture_binds[part.tex_emissive.raw()], &[]); + render_pass.set_bind_group(3, &self.model_texture_binds[part.tex_bumpmap.raw()], &[]); render_pass.set_bind_group( 0, diff --git a/inox2d-wgpu/src/node_bundle.rs b/inox2d-wgpu/src/node_bundle.rs index 24c216d..95ef894 100644 --- a/inox2d-wgpu/src/node_bundle.rs +++ b/inox2d-wgpu/src/node_bundle.rs @@ -64,9 +64,9 @@ fn part_bundle_for_part( uniform_group, &[(setup.uniform_alignment_needed * buffers.uniform_index_map[&uuid]) as u32], ); - encoder.set_bind_group(1, &model_texture_binds[part.tex_albedo], &[]); - encoder.set_bind_group(2, &model_texture_binds[part.tex_emissive], &[]); - encoder.set_bind_group(3, &model_texture_binds[part.tex_bumpmap], &[]); + encoder.set_bind_group(1, &model_texture_binds[part.tex_albedo.raw()], &[]); + encoder.set_bind_group(2, &model_texture_binds[part.tex_emissive.raw()], &[]); + encoder.set_bind_group(3, &model_texture_binds[part.tex_bumpmap.raw()], &[]); let node_rinf = &puppet.render_ctx.node_render_ctxs[&uuid]; if let RenderCtxKind::Part(pinf) = &node_rinf.kind { diff --git a/inox2d/Cargo.toml b/inox2d/Cargo.toml index 083fa08..dc873d2 100644 --- a/inox2d/Cargo.toml +++ b/inox2d/Cargo.toml @@ -14,7 +14,6 @@ image = "0.24.5" indextree = "4.6.0" json = "0.12.4" owo-colors = { version = "3.5.0", optional = true } -rayon = "1.7.0" thiserror = "1.0.39" tracing = "0.1.37" diff --git a/inox2d/src/formats/inp.rs b/inox2d/src/formats/inp.rs index 329523d..23f84a1 100644 --- a/inox2d/src/formats/inp.rs +++ b/inox2d/src/formats/inp.rs @@ -1,6 +1,7 @@ use std::io::{self, Read}; use std::str::Utf8Error; use std::string::FromUtf8Error; +use std::sync::Arc; use image::ImageFormat; @@ -62,13 +63,15 @@ pub fn parse_inp(mut data: R) -> Result { for _ in 0..tex_count { let tex_length = read_be_u32(&mut data)? as usize; let tex_encoding = read_u8(&mut data)?; + let format = match tex_encoding { 0 => ImageFormat::Png, // PNG 1 => ImageFormat::Tga, // TGA 2 => return Err(ParseInpError::Bc7NotSupported), n => return Err(ParseInpError::InvalidTexEncoding(n)), }; - let data = read_vec(&mut data, tex_length)?; + + let data: Arc<[u8]> = read_vec(&mut data, tex_length)?.into(); textures.push(ModelTexture { format, data }); } diff --git a/inox2d/src/formats/payload.rs b/inox2d/src/formats/payload.rs index f9210ce..58d54a7 100644 --- a/inox2d/src/formats/payload.rs +++ b/inox2d/src/formats/payload.rs @@ -21,6 +21,7 @@ use crate::puppet::{ UnknownPuppetAllowedUsersError, }; use crate::render::RenderCtx; +use crate::texture::TextureId; use super::json::{JsonError, JsonObject, SerialExtend}; @@ -111,14 +112,14 @@ fn deserialize_part(obj: &JsonObject) -> InoxParseResult { let (tex_albedo, tex_emissive, tex_bumpmap) = { let textures = obj.get_list("textures")?; - let tex_albedo = match textures.first().ok_or(InoxParseError::NoAlbedoTexture)?.as_number() { + let tex_albedo: usize = match textures.first().ok_or(InoxParseError::NoAlbedoTexture)?.as_number() { Some(val) => val .try_into() .map_err(|_| InoxParseError::JsonError(JsonError::ParseIntError("0".to_owned()).nested("textures")))?, None => return Err(InoxParseError::NoAlbedoTexture), }; - let tex_emissive = match textures.get(1).and_then(JsonValue::as_number) { + let tex_emissive: usize = match textures.get(1).and_then(JsonValue::as_number) { Some(val) => (val.try_into()) // Map u32::MAX to nothing .map(|val| if val == u32::MAX as usize { 0 } else { val }) @@ -126,7 +127,7 @@ fn deserialize_part(obj: &JsonObject) -> InoxParseResult { None => 0, }; - let tex_bumpmap = match textures.get(2).and_then(JsonValue::as_number) { + let tex_bumpmap: usize = match textures.get(2).and_then(JsonValue::as_number) { Some(val) => (val.try_into()) // Map u32::MAX to nothing .map(|val| if val == u32::MAX as usize { 0 } else { val }) @@ -140,9 +141,9 @@ fn deserialize_part(obj: &JsonObject) -> InoxParseResult { Ok(Part { draw_state: deserialize_drawable(obj)?, mesh: vals("mesh", deserialize_mesh(&obj.get_object("mesh")?))?, - tex_albedo, - tex_emissive, - tex_bumpmap, + tex_albedo: TextureId(tex_albedo), + tex_emissive: TextureId(tex_emissive), + tex_bumpmap: TextureId(tex_bumpmap), }) } diff --git a/inox2d/src/model.rs b/inox2d/src/model.rs index d313183..9adefb7 100644 --- a/inox2d/src/model.rs +++ b/inox2d/src/model.rs @@ -1,11 +1,12 @@ use std::fmt; +use std::sync::Arc; use crate::puppet::Puppet; #[derive(Clone, Debug)] pub struct ModelTexture { pub format: image::ImageFormat, - pub data: Vec, + pub data: Arc<[u8]>, } #[derive(Clone, Debug)] diff --git a/inox2d/src/nodes/node_data.rs b/inox2d/src/nodes/node_data.rs index f040e2f..87f8f8a 100644 --- a/inox2d/src/nodes/node_data.rs +++ b/inox2d/src/nodes/node_data.rs @@ -1,6 +1,7 @@ use glam::Vec3; use crate::mesh::Mesh; +use crate::texture::TextureId; use super::node::InoxNodeUuid; use super::physics::SimplePhysics; @@ -123,9 +124,9 @@ impl Drawable { pub struct Part { pub draw_state: Drawable, pub mesh: Mesh, - pub tex_albedo: usize, - pub tex_emissive: usize, - pub tex_bumpmap: usize, + pub tex_albedo: TextureId, + pub tex_emissive: TextureId, + pub tex_bumpmap: TextureId, } #[derive(Debug, Clone)] diff --git a/inox2d/src/texture.rs b/inox2d/src/texture.rs index 99d4c8b..89c4596 100644 --- a/inox2d/src/texture.rs +++ b/inox2d/src/texture.rs @@ -1,15 +1,24 @@ use std::io; +use std::sync::mpsc; -use image::{ImageBuffer, ImageFormat, Rgba}; -use rayon::prelude::{IntoParallelRefIterator, ParallelIterator}; +use image::{ImageBuffer, ImageError, ImageFormat, Rgba}; use tracing::error; use crate::model::ModelTexture; -use self::tga::{read_tga, TgaImage}; +use self::tga::{read_tga, TgaDecodeError, TgaImage}; pub mod tga; +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct TextureId(pub(crate) usize); + +impl TextureId { + pub fn raw(&self) -> usize { + self.0 + } +} + pub struct ShallowTexture { pixels: Vec, width: u32, @@ -50,29 +59,90 @@ impl From, Vec>> for ShallowTexture { } } -pub fn decode_model_textures(model_textures: &[ModelTexture]) -> Vec { - model_textures - .par_iter() - .filter_map(|mtex| { - if mtex.format == ImageFormat::Tga { - match read_tga(&mut io::Cursor::new(&mtex.data)) { - Ok(img) => Some(ShallowTexture::from(img)), - Err(e) => { - error!("{}", e); - None - } - } - } else { - let img_buf = image::load_from_memory_with_format(&mtex.data, mtex.format); - - match img_buf { - Ok(img_buf) => Some(ShallowTexture::from(img_buf.into_rgba8())), - Err(e) => { - error!("{}", e); - None +#[derive(Debug, thiserror::Error)] +enum DecodeTextureError { + #[error("Could not decode TGA texture")] + TgaDecode( + #[from] + #[source] + TgaDecodeError, + ), + + #[error("Could not decode texture")] + ImageDecode( + #[from] + #[source] + ImageError, + ), +} + +fn decode_texture(mtex: ModelTexture) -> Result { + if mtex.format == ImageFormat::Tga { + let tga_texture = read_tga(&mut io::Cursor::new(&mtex.data))?; + Ok(ShallowTexture::from(tga_texture)) + } else { + let img_buf = image::load_from_memory_with_format(&mtex.data, mtex.format)?; + Ok(ShallowTexture::from(img_buf.into_rgba8())) + } +} + +pub fn parallel_decode_model_textures<'a>( + model_textures: impl ExactSizeIterator, + n_threads: Option, +) -> Vec { + // get number of optimal threads from computer + let mut max_num_threads = std::thread::available_parallelism().unwrap().get(); + + // remove at least one thread to not torture the computer + if max_num_threads > 1 { + max_num_threads -= 1; + } + + let mut num_threads = match n_threads { + Some(n_threads) => n_threads.min(max_num_threads), + None => max_num_threads, + }; + + // do not use more threads than there are images + if num_threads > model_textures.len() { + num_threads = model_textures.len(); + } + + // use channels to get Rs back from thread computation + let (tx_all, rx_all) = mpsc::channel(); + + let mut pipes = Vec::with_capacity(num_threads); + for th in 0..num_threads { + // thread-local channel + let (tx, rx) = mpsc::channel::<(usize, ModelTexture)>(); + + let tx_all = tx_all.clone(); + std::thread::Builder::new() + .name(format!("Image Decoder Thread ({})", th)) + .spawn(move || { + // get textures from the thread-local channel, decode them, and send them to the global channel + while let Ok((i, texture)) = rx.recv() { + match decode_texture(texture) { + Ok(decoded) => tx_all.send((i, decoded)).unwrap(), + Err(e) => tracing::error!("{}", e), } } - } - }) - .collect::>() + }) + .unwrap(); + + pipes.push(tx); + } + + let n_model_textures = model_textures.len(); + + // distribute texture decoding on all threads we make available + for ((i, texture), tx) in model_textures.enumerate().zip(pipes.iter().cycle()) { + // REMINDER: the texture data is behind an arc, so it's not actually being cloned + tx.send((i, texture.clone())).unwrap(); + } + + let mut decoded = rx_all.into_iter().take(n_model_textures).collect::>(); + decoded.sort_by_key(|&(i, _)| i); + + decoded.into_iter().map(|(_, tex)| tex).collect() }