From 1b57a1df4812eea393e199167a9e58002f1fa37b Mon Sep 17 00:00:00 2001 From: Lena Date: Mon, 18 Dec 2023 16:55:02 +0100 Subject: [PATCH 1/8] replace EventLoop::run with EventLoop::spawn spawn event loop instead of using js exception control flow hack --- examples/render-webgl/src/main.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/examples/render-webgl/src/main.rs b/examples/render-webgl/src/main.rs index 319ed36..6cb34e5 100644 --- a/examples/render-webgl/src/main.rs +++ b/examples/render-webgl/src/main.rs @@ -54,7 +54,8 @@ async fn run() -> Result<(), Box> { use wasm_bindgen::prelude::Closure; use wasm_bindgen::JsCast; use winit::event::{Event, WindowEvent}; - + use winit::platform::web::EventLoopExtWebSys; + use crate::scene::WasmSceneController; let events = winit::event_loop::EventLoop::new(); @@ -138,7 +139,7 @@ async fn run() -> Result<(), Box> { } // Event loop - events.run(move |event, _, control_flow| { + events.spawn(move |event, _, control_flow| { // it needs to be present let _window = &window; @@ -163,7 +164,9 @@ async fn run() -> Result<(), Box> { } _ => (), } - }) + }); + + Ok(()) } #[cfg(target_arch = "wasm32")] From 009edb2bfc783ee76d5635aa37369522f9bcb236 Mon Sep 17 00:00:00 2001 From: Speykious Date: Sat, 30 Dec 2023 18:31:21 +0100 Subject: [PATCH 2/8] Add rust-toolchain file --- rust-toolchain.toml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 rust-toolchain.toml diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..292fe49 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "stable" From 28fe33c46475cc813dd73e476e00df2eefde34ee Mon Sep 17 00:00:00 2001 From: Speykious Date: Sat, 30 Dec 2023 18:31:51 +0100 Subject: [PATCH 3/8] OpenGL example: finally, a transparent window on X11 --- examples/common/src/scene.rs | 9 +- examples/render-opengl/src/app_frame.rs | 263 ++++++++++++++++++++++++ examples/render-opengl/src/main.rs | 188 +++++++++-------- examples/render-opengl/src/opengl.rs | 119 ----------- examples/render-wgpu/src/main.rs | 2 +- 5 files changed, 370 insertions(+), 211 deletions(-) create mode 100644 examples/render-opengl/src/app_frame.rs delete mode 100644 examples/render-opengl/src/opengl.rs diff --git a/examples/common/src/scene.rs b/examples/common/src/scene.rs index 84e59d2..c30daf7 100644 --- a/examples/common/src/scene.rs +++ b/examples/common/src/scene.rs @@ -5,7 +5,6 @@ use std::time::Instant; use glam::{vec2, Vec2}; use inox2d::math::camera::Camera; use winit::event::{ElementState, MouseScrollDelta, WindowEvent}; -use winit::window::Window; pub struct ExampleSceneController { // for camera position and mouse interactions @@ -54,14 +53,10 @@ impl ExampleSceneController { self.current_elapsed = self.start.elapsed().as_secs_f32(); } - pub fn interact(&mut self, window: &Window, event: &WindowEvent, camera: &Camera) { + pub fn interact(&mut self, event: &WindowEvent, camera: &Camera) { match event { WindowEvent::CursorMoved { position, .. } => { self.mouse_pos = vec2(position.x as f32, position.y as f32); - - if self.mouse_state == ElementState::Pressed { - window.request_redraw(); - } } WindowEvent::MouseInput { state, .. } => { self.mouse_state = *state; @@ -78,8 +73,6 @@ impl ExampleSceneController { }; self.hard_scale *= 2_f32.powf(self.scroll_speed * my * 0.1); - - window.request_redraw(); } _ => (), } diff --git a/examples/render-opengl/src/app_frame.rs b/examples/render-opengl/src/app_frame.rs new file mode 100644 index 0000000..2890b3b --- /dev/null +++ b/examples/render-opengl/src/app_frame.rs @@ -0,0 +1,263 @@ +//! Low-level window initialization handling + +use std::error::Error; +use std::ffi::CString; +use std::num::NonZeroU32; + +use glow::HasContext; +use raw_window_handle::HasRawWindowHandle; +use winit::event::{Event, WindowEvent}; +use winit::event_loop::{EventLoop, EventLoopWindowTarget}; +use winit::window::{Window, WindowBuilder}; + +use glutin::config::ConfigTemplateBuilder; +use glutin::context::{ContextApi, ContextAttributesBuilder, NotCurrentContext, Version}; +use glutin::display::GetGlDisplay; +use glutin::prelude::*; +use glutin::surface::SwapInterval; + +use glutin_winit::{self, DisplayBuilder, GlWindow}; + +pub trait App { + fn resume_window(&mut self, gl: glow::Context); + fn resize(&mut self, width: i32, height: i32); + fn draw(&mut self); + fn handle_window_event(&mut self, event: WindowEvent, window_target: &EventLoopWindowTarget<()>); +} + +pub struct AppFrame { + window: Option, + event_loop: EventLoop<()>, + gl_config: glutin::config::Config, + gl_display: glutin::display::Display, + not_current_gl_context: Option, + window_builder: WindowBuilder, +} + +impl AppFrame { + pub fn init(window_builder: WindowBuilder) -> Result> { + let event_loop = EventLoop::new()?; + let mut template = ConfigTemplateBuilder::new(); + + // The template will match only the configurations supporting rendering + // to windows. + // + // XXX We force transparency only on macOS, given that EGL on X11 doesn't + // have it, but we still want to show window. The macOS situation is like + // that, because we can query only one config at a time on it, but all + // normal platforms will return multiple configs, so we can find the config + // with transparency ourselves inside the `reduce`. + if window_builder.transparent() { + template = template.with_alpha_size(8).with_transparency(cfg!(cgl_backend)); + } else { + template = template.with_transparency(false); + } + + // Only Windows requires the window to be present before creating the display. + // Other platforms don't really need one. + // + // XXX if you don't care about running on Android or so you can safely remove + // this condition and always pass the window builder. + let maydow_builder = cfg!(wgl_backend).then_some(window_builder.clone()); + + let display_builder = DisplayBuilder::new().with_window_builder(maydow_builder); + + let (window, gl_config) = display_builder.build(&event_loop, template, |configs| { + // Find the config with the maximum number of samples, so our triangle will + // be smooth. + configs + .reduce(|accum, config| { + let config_transparent = config.supports_transparency().unwrap_or(false); + let accum_transparent = accum.supports_transparency().unwrap_or(false); + + let transparency_check = { + match window_builder.transparent() { + true => config_transparent && !accum_transparent, + false => !config_transparent && accum_transparent, + } + }; + + if transparency_check || config.num_samples() > accum.num_samples() { + config + } else { + accum + } + }) + .unwrap() + })?; + + let raw_window_handle = window.as_ref().map(|window| window.raw_window_handle()); + + // XXX The display could be obtained from any object created by it, so we can + // query it from the config. + let gl_display = gl_config.display(); + + let not_current_gl_context = { + // The context creation part. It can be created before surface and that's how + // it's expected in multithreaded + multiwindow operation mode, since you + // can send NotCurrentContext, but not Surface. + let context_attributes = ContextAttributesBuilder::new().build(raw_window_handle); + + // Since glutin by default tries to create OpenGL core context, which may not be + // present we should try gles. + let fallback_context_attributes = ContextAttributesBuilder::new() + .with_context_api(ContextApi::Gles(None)) + .build(raw_window_handle); + + // There are also some old devices that support neither modern OpenGL nor GLES. + // To support these we can try and create a 2.1 context. + let legacy_context_attributes = ContextAttributesBuilder::new() + .with_context_api(ContextApi::OpenGl(Some(Version::new(2, 1)))) + .build(raw_window_handle); + + Some(unsafe { + gl_display + .create_context(&gl_config, &context_attributes) + .unwrap_or_else(|_| { + gl_display + .create_context(&gl_config, &fallback_context_attributes) + .unwrap_or_else(|_| { + gl_display + .create_context(&gl_config, &legacy_context_attributes) + .expect("failed to create context") + }) + }) + }) + }; + + Ok(Self { + window, + event_loop, + gl_config, + gl_display, + not_current_gl_context, + window_builder, + }) + } + + pub fn run(mut self, mut app: A) -> Result<(), Box> { + let mut state = None; + + self.event_loop.run(move |event, window_target| { + match event { + Event::Resumed => { + #[cfg(android_platform)] + println!("Android window available"); + + let window = self.window.take().unwrap_or_else(|| { + let window_builder = self.window_builder.clone(); + glutin_winit::finalize_window(window_target, window_builder, &self.gl_config).unwrap() + }); + + let attrs = window.build_surface_attributes(Default::default()); + let gl_surface = unsafe { + self.gl_config + .display() + .create_window_surface(&self.gl_config, &attrs) + .unwrap() + }; + + // Make it current. + let gl_context = (self.not_current_gl_context) + .take() + .unwrap() + .make_current(&gl_surface) + .unwrap(); + + // Load the OpenGL function pointers (this needs a current context in WGL) + let gl = unsafe { + glow::Context::from_loader_function(|symbol| { + self.gl_display.get_proc_address(&CString::new(symbol).unwrap()) as *const _ + }) + }; + + // Check for "GL_KHR_debug" support (not present on Apple *OS). + if gl.supported_extensions().contains("GL_KHR_debug") { + unsafe { + gl.debug_message_callback(|_src, ty, _id, sevr, msg| { + let ty = match ty { + glow::DEBUG_TYPE_ERROR => "Error: ", + glow::DEBUG_TYPE_DEPRECATED_BEHAVIOR => "Deprecated Behavior: ", + glow::DEBUG_TYPE_MARKER => "Marker: ", + glow::DEBUG_TYPE_OTHER => "", + glow::DEBUG_TYPE_POP_GROUP => "Pop Group: ", + glow::DEBUG_TYPE_PORTABILITY => "Portability: ", + glow::DEBUG_TYPE_PUSH_GROUP => "Push Group: ", + glow::DEBUG_TYPE_UNDEFINED_BEHAVIOR => "Undefined Behavior: ", + glow::DEBUG_TYPE_PERFORMANCE => "Performance: ", + ty => unreachable!("unknown debug type {ty}"), + }; + match sevr { + glow::DEBUG_SEVERITY_NOTIFICATION => tracing::debug!(target: "opengl", "{ty}{msg}"), + glow::DEBUG_SEVERITY_LOW => tracing::info!(target: "opengl", "{ty}{msg}"), + glow::DEBUG_SEVERITY_MEDIUM => tracing::warn!(target: "opengl", "{ty}{msg}"), + glow::DEBUG_SEVERITY_HIGH => tracing::error!(target: "opengl", "{ty}{msg}"), + sevr => unreachable!("unknown debug severity {sevr}"), + }; + }); + + gl.enable(glow::DEBUG_OUTPUT); + } + } + + app.resume_window(gl); + + // Try setting vsync. + if let Err(res) = + gl_surface.set_swap_interval(&gl_context, SwapInterval::Wait(NonZeroU32::new(1).unwrap())) + { + eprintln!("Error setting vsync: {res:?}"); + } + + assert!(state.replace((gl_context, gl_surface, window)).is_none()); + } + Event::Suspended => { + // This event is only raised on Android, where the backing NativeWindow for a GL + // Surface can appear and disappear at any moment. + #[cfg(android_platform)] + println!("Android window removed"); + + // Destroy the GL Surface and un-current the GL Context before ndk-glue releases + // the window back to the system. + let (gl_context, ..) = state.take().unwrap(); + assert!(self + .not_current_gl_context + .replace(gl_context.make_not_current().unwrap()) + .is_none()); + } + Event::WindowEvent { event, .. } => match event { + WindowEvent::Resized(size) => { + if size.width != 0 && size.height != 0 { + // Some platforms like EGL require resizing GL surface to update the size + // Notable platforms here are Wayland and macOS, other don't require it + // and the function is no-op, but it's wise to resize it for portability + // reasons. + if let Some((gl_context, gl_surface, _)) = &state { + gl_surface.resize( + gl_context, + NonZeroU32::new(size.width).unwrap(), + NonZeroU32::new(size.height).unwrap(), + ); + app.resize(size.width as i32, size.height as i32); + } + } + } + _ => { + app.handle_window_event(event, window_target); + } + }, + Event::AboutToWait => { + if let Some((gl_context, gl_surface, window)) = &state { + app.draw(); + window.request_redraw(); + + gl_surface.swap_buffers(gl_context).unwrap(); + } + } + _ => (), + } + })?; + + Ok(()) + } +} diff --git a/examples/render-opengl/src/main.rs b/examples/render-opengl/src/main.rs index c213b46..9a335df 100644 --- a/examples/render-opengl/src/main.rs +++ b/examples/render-opengl/src/main.rs @@ -1,24 +1,27 @@ use std::path::PathBuf; -use std::{error::Error, fs, num::NonZeroU32}; +use std::{error::Error, fs}; use inox2d::formats::inp::parse_inp; +use inox2d::model::Model; use inox2d::render::InoxRenderer; use inox2d_opengl::OpenglRenderer; use clap::Parser; use glam::Vec2; -use glutin::surface::GlSurface; -use tracing::{debug, info}; use tracing_subscriber::{filter::LevelFilter, fmt, prelude::*}; -use winit::event::{ElementState, Event, KeyEvent, WindowEvent}; +use winit::event::{ElementState, KeyEvent, WindowEvent}; use common::scene::ExampleSceneController; -use opengl::{launch_opengl_window, App}; -use winit::event_loop::ControlFlow; +use winit::event_loop::EventLoopWindowTarget; use winit::keyboard::{KeyCode, PhysicalKey}; -mod opengl; +use app_frame::App; +use winit::window::WindowBuilder; + +use crate::app_frame::AppFrame; + +mod app_frame; #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] @@ -35,96 +38,115 @@ fn main() -> Result<(), Box> { .with(LevelFilter::INFO) .init(); - info!("Parsing puppet"); + tracing::info!("Parsing puppet"); let data = fs::read(cli.inp_path).unwrap(); let model = parse_inp(data.as_slice()).unwrap(); - info!( + tracing::info!( "Successfully parsed puppet: {}", (model.puppet.meta.name.as_deref()).unwrap_or("") ); - info!("Setting up windowing and OpenGL"); - let App { - gl, - gl_ctx, - gl_surface, - gl_display, - events, - window, - } = launch_opengl_window()?; - - info!("Initializing Inox2D renderer"); - let window_size = window.inner_size(); - - let mut renderer = OpenglRenderer::new(gl)?; - renderer.prepare(&model)?; - renderer.resize(window_size.width, window_size.height); - renderer.camera.scale = Vec2::splat(0.15); - info!("Inox2D renderer initialized"); - - let mut scene_ctrl = ExampleSceneController::new(&renderer.camera, 0.5); - let mut puppet = model.puppet; - - // Event loop - events.run(move |event, elwt| { - // They need to be present - let _gl_display = &gl_display; - let _window = &window; - elwt.set_control_flow(ControlFlow::Wait); + tracing::info!("Setting up windowing and OpenGL"); + let app_frame = AppFrame::init( + WindowBuilder::new() + .with_transparent(true) + .with_resizable(true) + .with_inner_size(winit::dpi::PhysicalSize::new(600, 800)) + .with_title("Render Inochi2D Puppet (OpenGL)"), + )?; - match event { - Event::WindowEvent { - window_id: _, - event: winit::event::WindowEvent::RedrawRequested, - } => { - debug!("Redrawing"); - scene_ctrl.update(&mut renderer.camera); + app_frame.run(Inox2dOpenglExampleApp::new(model))?; - renderer.clear(); + Ok(()) +} - puppet.begin_set_params(); - let t = scene_ctrl.current_elapsed(); - puppet.set_param("Head:: Yaw-Pitch", Vec2::new(t.cos(), t.sin())); - puppet.end_set_params(); +struct Inox2dOpenglExampleApp { + on_window: Option<(OpenglRenderer, ExampleSceneController)>, + model: Model, + width: u32, + height: u32, +} - renderer.render(&puppet); +impl Inox2dOpenglExampleApp { + pub fn new(model: Model) -> Self { + Self { + on_window: None, + model, + width: 0, + height: 0, + } + } +} - gl_surface.swap_buffers(&gl_ctx).unwrap(); - window.request_redraw(); +impl App for Inox2dOpenglExampleApp { + fn resume_window(&mut self, gl: glow::Context) { + match OpenglRenderer::new(gl) { + Ok(mut renderer) => { + tracing::info!("Initializing Inox2D renderer"); + renderer.prepare(&self.model).unwrap(); + renderer.resize(self.width, self.height); + renderer.camera.scale = Vec2::splat(0.15); + tracing::info!("Inox2D renderer initialized"); + + let scene_ctrl = ExampleSceneController::new(&renderer.camera, 0.5); + self.on_window = Some((renderer, scene_ctrl)); } - Event::WindowEvent { ref event, .. } => match event { - WindowEvent::Resized(physical_size) => { - // Handle window resizing - renderer.resize(physical_size.width, physical_size.height); - gl_surface.resize( - &gl_ctx, - NonZeroU32::new(physical_size.width).unwrap(), - NonZeroU32::new(physical_size.height).unwrap(), - ); - window.request_redraw(); - } - WindowEvent::CloseRequested => elwt.exit(), - WindowEvent::KeyboardInput { - event: - KeyEvent { - //virtual_keycode: Some(VirtualKeyCode::Escape), - state: ElementState::Pressed, - physical_key: PhysicalKey::Code(KeyCode::Escape), - .. - }, - .. - } => { - info!("There is an Escape D:"); - elwt.exit(); + Err(e) => { + tracing::error!("{}", e); + self.on_window = None; + } + } + } + + fn resize(&mut self, width: i32, height: i32) { + self.width = width as u32; + self.height = height as u32; + + if let Some((renderer, _)) = &mut self.on_window { + renderer.resize(self.width, self.height); + } + } + + fn draw(&mut self) { + let Some((renderer, scene_ctrl)) = &mut self.on_window else { + return; + }; + + tracing::debug!("Redrawingggggg"); + scene_ctrl.update(&mut renderer.camera); + + renderer.clear(); + + let puppet = &mut self.model.puppet; + puppet.begin_set_params(); + let t = scene_ctrl.current_elapsed(); + puppet.set_param("Head:: Yaw-Pitch", Vec2::new(t.cos(), t.sin())); + puppet.end_set_params(); + + renderer.render(puppet); + } + + fn handle_window_event(&mut self, event: WindowEvent, elwt: &EventLoopWindowTarget<()>) { + match event { + WindowEvent::CloseRequested => elwt.exit(), + WindowEvent::KeyboardInput { + event: + KeyEvent { + state: ElementState::Pressed, + physical_key: PhysicalKey::Code(KeyCode::Escape), + .. + }, + .. + } => { + tracing::info!("There is an Escape D:"); + elwt.exit(); + } + event => { + if let Some((renderer, scene_ctrl)) = &mut self.on_window { + scene_ctrl.interact(&event, &renderer.camera) } - _ => scene_ctrl.interact(&window, event, &renderer.camera), - }, - Event::AboutToWait => { - window.request_redraw(); } - _ => (), } - })?; - Ok(()) + } } diff --git a/examples/render-opengl/src/opengl.rs b/examples/render-opengl/src/opengl.rs deleted file mode 100644 index c1cd4b0..0000000 --- a/examples/render-opengl/src/opengl.rs +++ /dev/null @@ -1,119 +0,0 @@ -use std::env; -use std::error::Error; -use std::ffi::CString; -use std::num::NonZeroU32; - -use glow::HasContext; -use glutin::context::{ContextApi, ContextAttributesBuilder, NotCurrentGlContext, PossiblyCurrentContext, Version}; -use glutin::display::{Display, GetGlDisplay}; -use glutin::prelude::{GlConfig, GlDisplay}; -use glutin::surface::{Surface, SurfaceAttributesBuilder, WindowSurface}; -use glutin_winit::ApiPreference; -use raw_window_handle::HasRawWindowHandle; -use tracing::{debug, error, info, warn}; -use winit::event_loop::EventLoop; -use winit::window::Window; - -pub struct App { - pub gl: glow::Context, - pub gl_ctx: PossiblyCurrentContext, - pub gl_surface: Surface, - pub gl_display: Display, - pub window: Window, - pub events: EventLoop<()>, -} - -pub fn launch_opengl_window() -> Result> { - if cfg!(target_os = "linux") { - // disables vsync sometimes on x11 - if env::var("vblank_mode").is_err() { - env::set_var("vblank_mode", "0"); - } - } - - let events = winit::event_loop::EventLoop::new().unwrap(); - - let window_builder = winit::window::WindowBuilder::new() - .with_transparent(true) - .with_resizable(true) - .with_inner_size(winit::dpi::PhysicalSize::new(600, 800)) - .with_title("Render Inochi2D Puppet (OpenGL)"); - - let (window, gl_config) = glutin_winit::DisplayBuilder::new() - .with_preference(ApiPreference::FallbackEgl) - .with_window_builder(Some(window_builder)) - .build(&events, <_>::default(), |configs| { - configs - .filter(|c| c.srgb_capable()) - .max_by_key(|c| c.num_samples()) - .unwrap() - })?; - - let window = window.unwrap(); // set in display builder - let raw_window_handle = window.raw_window_handle(); - let gl_display = gl_config.display(); - - let context_attributes = ContextAttributesBuilder::new() - .with_context_api(ContextApi::OpenGl(Some(Version::new(3, 1)))) - .with_profile(glutin::context::GlProfile::Core) - .build(Some(raw_window_handle)); - - let dimensions = window.inner_size(); - - let (gl_surface, gl_ctx) = { - let attrs = SurfaceAttributesBuilder::::new().build( - raw_window_handle, - NonZeroU32::new(dimensions.width).unwrap(), - NonZeroU32::new(dimensions.height).unwrap(), - ); - - let surface = unsafe { gl_display.create_window_surface(&gl_config, &attrs)? }; - let context = unsafe { gl_display.create_context(&gl_config, &context_attributes)? }.make_current(&surface)?; - (surface, context) - }; - - // Load the OpenGL function pointers - let gl = unsafe { - glow::Context::from_loader_function(|symbol| { - gl_display.get_proc_address(&CString::new(symbol).unwrap()) as *const _ - }) - }; - - // Check for "GL_KHR_debug" support (not present on Apple *OS). - if gl.supported_extensions().contains("GL_KHR_debug") { - unsafe { - gl.debug_message_callback(|_src, ty, _id, sevr, msg| { - let ty = match ty { - glow::DEBUG_TYPE_ERROR => "Error: ", - glow::DEBUG_TYPE_DEPRECATED_BEHAVIOR => "Deprecated Behavior: ", - glow::DEBUG_TYPE_MARKER => "Marker: ", - glow::DEBUG_TYPE_OTHER => "", - glow::DEBUG_TYPE_POP_GROUP => "Pop Group: ", - glow::DEBUG_TYPE_PORTABILITY => "Portability: ", - glow::DEBUG_TYPE_PUSH_GROUP => "Push Group: ", - glow::DEBUG_TYPE_UNDEFINED_BEHAVIOR => "Undefined Behavior: ", - glow::DEBUG_TYPE_PERFORMANCE => "Performance: ", - ty => unreachable!("unknown debug type {ty}"), - }; - match sevr { - glow::DEBUG_SEVERITY_NOTIFICATION => debug!(target: "opengl", "{ty}{msg}"), - glow::DEBUG_SEVERITY_LOW => info!(target: "opengl", "{ty}{msg}"), - glow::DEBUG_SEVERITY_MEDIUM => warn!(target: "opengl", "{ty}{msg}"), - glow::DEBUG_SEVERITY_HIGH => error!(target: "opengl", "{ty}{msg}"), - sevr => unreachable!("unknown debug severity {sevr}"), - }; - }); - - gl.enable(glow::DEBUG_OUTPUT); - } - } - - Ok(App { - gl, - gl_ctx, - gl_surface, - gl_display, - window, - events, - }) -} diff --git a/examples/render-wgpu/src/main.rs b/examples/render-wgpu/src/main.rs index 66580ff..f64753d 100644 --- a/examples/render-wgpu/src/main.rs +++ b/examples/render-wgpu/src/main.rs @@ -125,7 +125,7 @@ pub async fn run(model: Model) { // On macos the window needs to be redrawn manually after resizing window.request_redraw(); } - _ => scene_ctrl.interact(&window, event, &renderer.camera), + _ => scene_ctrl.interact(event, &renderer.camera), }, Event::AboutToWait => { // RedrawRequested will only trigger once, unless we manually From 720c18c3dacab869359fab395b98aa9f42f85595 Mon Sep 17 00:00:00 2001 From: Speykious Date: Mon, 1 Jan 2024 14:30:13 +0100 Subject: [PATCH 4/8] Use newer Rust module organization --- inox2d/src/{formats/mod.rs => formats.rs} | 2 +- inox2d/src/formats/inp.rs | 2 +- inox2d/src/formats/{serialize.rs => payload.rs} | 0 inox2d/src/{math/mod.rs => math.rs} | 0 inox2d/src/{nodes/mod.rs => nodes.rs} | 0 inox2d/src/{texture/mod.rs => texture.rs} | 0 6 files changed, 2 insertions(+), 2 deletions(-) rename inox2d/src/{formats/mod.rs => formats.rs} (58%) rename inox2d/src/formats/{serialize.rs => payload.rs} (100%) rename inox2d/src/{math/mod.rs => math.rs} (100%) rename inox2d/src/{nodes/mod.rs => nodes.rs} (100%) rename inox2d/src/{texture/mod.rs => texture.rs} (100%) diff --git a/inox2d/src/formats/mod.rs b/inox2d/src/formats.rs similarity index 58% rename from inox2d/src/formats/mod.rs rename to inox2d/src/formats.rs index 4a9f318..d18c7c3 100644 --- a/inox2d/src/formats/mod.rs +++ b/inox2d/src/formats.rs @@ -1,3 +1,3 @@ pub mod inp; pub mod json; -pub mod serialize; +pub mod payload; diff --git a/inox2d/src/formats/inp.rs b/inox2d/src/formats/inp.rs index 0a60372..329523d 100644 --- a/inox2d/src/formats/inp.rs +++ b/inox2d/src/formats/inp.rs @@ -8,7 +8,7 @@ use crate::model::{Model, ModelTexture, VendorData}; use crate::{read_be_u32, read_n, read_u8, read_vec}; use super::json::JsonError; -use super::serialize::{deserialize_puppet, InoxParseError}; +use super::payload::{deserialize_puppet, InoxParseError}; #[derive(Debug, thiserror::Error)] #[error("Could not parse INP file\n - {0}")] diff --git a/inox2d/src/formats/serialize.rs b/inox2d/src/formats/payload.rs similarity index 100% rename from inox2d/src/formats/serialize.rs rename to inox2d/src/formats/payload.rs diff --git a/inox2d/src/math/mod.rs b/inox2d/src/math.rs similarity index 100% rename from inox2d/src/math/mod.rs rename to inox2d/src/math.rs diff --git a/inox2d/src/nodes/mod.rs b/inox2d/src/nodes.rs similarity index 100% rename from inox2d/src/nodes/mod.rs rename to inox2d/src/nodes.rs diff --git a/inox2d/src/texture/mod.rs b/inox2d/src/texture.rs similarity index 100% rename from inox2d/src/texture/mod.rs rename to inox2d/src/texture.rs From 417fd06fe6d46789af4e2dcca6b1ba4b9ebb92f0 Mon Sep 17 00:00:00 2001 From: Speykious Date: Mon, 1 Jan 2024 17:20:17 +0100 Subject: [PATCH 5/8] 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() } From 7024c4f1955d34dccbfb5a6a1916c4bb7f89df3a Mon Sep 17 00:00:00 2001 From: Speykious Date: Mon, 1 Jan 2024 18:27:56 +0100 Subject: [PATCH 6/8] Reduce features imported by `image` We only use it for png and jpeg --- inox2d-opengl/Cargo.toml | 1 - inox2d-opengl/src/texture.rs | 43 +++--------------------------------- inox2d/Cargo.toml | 2 +- 3 files changed, 4 insertions(+), 42 deletions(-) diff --git a/inox2d-opengl/Cargo.toml b/inox2d-opengl/Cargo.toml index 531a23c..7c0f5b9 100644 --- a/inox2d-opengl/Cargo.toml +++ b/inox2d-opengl/Cargo.toml @@ -9,7 +9,6 @@ keywords = ["gamedev", "graphics", "inochi2d", "vtuber", "opengl"] categories = ["graphics", "rendering"] [dependencies] -image = "0.24.5" inox2d = { path = "../inox2d" } glam = "0.24.0" glow = { version = "0.12.1" } diff --git a/inox2d-opengl/src/texture.rs b/inox2d-opengl/src/texture.rs index b0cabbb..b28d6a8 100644 --- a/inox2d-opengl/src/texture.rs +++ b/inox2d-opengl/src/texture.rs @@ -1,19 +1,10 @@ use glow::HasContext; -use image::{ImageBuffer, ImageError, Rgba}; -use inox2d::model::ModelTexture; -use inox2d::texture::tga::TgaDecodeError; use inox2d::texture::ShallowTexture; #[derive(thiserror::Error, Debug)] -pub enum TextureError { - #[error("Could not create texture: {0}")] - Create(String), - #[error("Could not load image data for texture: {0}")] - LoadData(#[from] ImageError), - #[error("Could not load TGA texture: {0}")] - LoadTga(#[from] TgaDecodeError), -} +#[error("Could not create texture: {0}")] +pub struct TextureError(String); pub struct Texture { tex: glow::Texture, @@ -23,34 +14,6 @@ pub struct Texture { } impl Texture { - pub fn new(gl: &glow::Context, mtex: &ModelTexture) -> Result { - let img_buf = image::load_from_memory_with_format(&mtex.data, mtex.format) - .map_err(TextureError::LoadData)? - .into_rgba8(); - - Self::from_image_buffer_rgba(gl, img_buf) - } - - /// Makes an educated guess about the image format. TGA is not supported by this function. - pub fn from_memory(gl: &glow::Context, img_buf: &[u8]) -> Result { - let img_buf = image::load_from_memory(img_buf) - .map_err(TextureError::LoadData)? - .into_rgba8(); - - Self::from_image_buffer_rgba(gl, img_buf) - } - - pub fn from_image_buffer_rgba( - gl: &glow::Context, - img_buf: ImageBuffer, Vec>, - ) -> Result { - let pixels = img_buf.to_vec(); - let width = img_buf.width(); - let height = img_buf.height(); - - Self::from_raw_pixels(gl, &pixels, width, height) - } - pub fn from_shallow_texture(gl: &glow::Context, shalltex: &ShallowTexture) -> Result { Self::from_raw_pixels(gl, shalltex.pixels(), shalltex.width(), shalltex.height()) } @@ -58,7 +21,7 @@ impl Texture { pub fn from_raw_pixels(gl: &glow::Context, pixels: &[u8], width: u32, height: u32) -> Result { let bpp = 8 * (pixels.len() / (width as usize * height as usize)) as u32; - let tex = unsafe { gl.create_texture().map_err(TextureError::Create)? }; + let tex = unsafe { gl.create_texture().map_err(TextureError)? }; unsafe { gl.bind_texture(glow::TEXTURE_2D, Some(tex)); gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_MIN_FILTER, glow::LINEAR as i32); diff --git a/inox2d/Cargo.toml b/inox2d/Cargo.toml index dc873d2..53e87c1 100644 --- a/inox2d/Cargo.toml +++ b/inox2d/Cargo.toml @@ -10,7 +10,7 @@ categories = ["graphics", "rendering"] [dependencies] glam = "0.24.0" -image = "0.24.5" +image = { version = "0.24.7", default-features = false, features = ["png", "jpeg"] } indextree = "4.6.0" json = "0.12.4" owo-colors = { version = "3.5.0", optional = true } From 2514be15c255a4aa1dc13f6ee2070753602e1dae Mon Sep 17 00:00:00 2001 From: Speykious Date: Wed, 31 Jan 2024 22:59:16 +0100 Subject: [PATCH 7/8] Do not try to decode in parallel on WASM (there's no threading at all) --- inox2d-opengl/src/lib.rs | 7 +++---- inox2d-wgpu/src/lib.rs | 4 ++-- inox2d/src/texture.rs | 34 +++++++++++++++++++++++----------- 3 files changed, 28 insertions(+), 17 deletions(-) diff --git a/inox2d-opengl/src/lib.rs b/inox2d-opengl/src/lib.rs index fd6b122..d85147c 100644 --- a/inox2d-opengl/src/lib.rs +++ b/inox2d-opengl/src/lib.rs @@ -10,8 +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::texture::{decode_model_textures, TextureId}; use inox2d::math::camera::Camera; use inox2d::model::{Model, ModelTexture}; @@ -188,11 +187,11 @@ impl OpenglRenderer { fn upload_model_textures(&mut self, model_textures: &[ModelTexture]) -> Result<(), TextureError> { // decode textures in parallel - let shalltexs = parallel_decode_model_textures(model_textures.iter(), None); + let shalltexs = decode_model_textures(model_textures.iter()); // upload textures for (i, shalltex) in shalltexs.iter().enumerate() { - debug!("Uploading shallow texture {:?}", i); + tracing::debug!("Uploading shallow texture {:?}", i); let tex = texture::Texture::from_shallow_texture(&self.gl, shalltex)?; self.textures.push(tex); } diff --git a/inox2d-wgpu/src/lib.rs b/inox2d-wgpu/src/lib.rs index 6403f7d..122f1c8 100644 --- a/inox2d-wgpu/src/lib.rs +++ b/inox2d-wgpu/src/lib.rs @@ -12,7 +12,7 @@ use inox2d::render::RenderCtxKind; use encase::ShaderType; use glam::{vec3, Mat4, UVec2, Vec2, Vec3}; -use inox2d::texture::parallel_decode_model_textures; +use inox2d::texture::decode_model_textures; use tracing::warn; use wgpu::{util::DeviceExt, *}; @@ -52,7 +52,7 @@ impl Renderer { ..SamplerDescriptor::default() }); - let shalltexs = parallel_decode_model_textures(model.textures.iter(), None); + let shalltexs = decode_model_textures(model.textures.iter()); for shalltex in &shalltexs { let texture_size = wgpu::Extent3d { width: shalltex.width(), diff --git a/inox2d/src/texture.rs b/inox2d/src/texture.rs index 89c4596..51bcf03 100644 --- a/inox2d/src/texture.rs +++ b/inox2d/src/texture.rs @@ -1,5 +1,4 @@ use std::io; -use std::sync::mpsc; use image::{ImageBuffer, ImageError, ImageFormat, Rgba}; use tracing::error; @@ -86,23 +85,36 @@ fn decode_texture(mtex: ModelTexture) -> Result( +#[cfg(target_arch = "wasm32")] +pub fn decode_model_textures<'a>( model_textures: impl ExactSizeIterator, - n_threads: Option, ) -> Vec { + (model_textures.cloned()) + .map(decode_texture) + .inspect(|res| { + if let Err(e) = res { + tracing::error!("{}", e); + } + }) + .filter_map(Result::ok) + .collect::>() +} + +/// Decodes model textures in parallel, using as many threads as we can use minus one. +#[cfg(not(target_arch = "wasm32"))] +pub fn decode_model_textures<'a>( + model_textures: impl ExactSizeIterator, +) -> Vec { + use std::sync::mpsc; + // get number of optimal threads from computer - let mut max_num_threads = std::thread::available_parallelism().unwrap().get(); + let mut 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; + if num_threads > 1 { + 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(); From 568f03c71964da9c1e6ff147e053cc5d10506f41 Mon Sep 17 00:00:00 2001 From: Speykious Date: Wed, 31 Jan 2024 23:03:37 +0100 Subject: [PATCH 8/8] No question mark --- examples/render-webgl/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/render-webgl/src/main.rs b/examples/render-webgl/src/main.rs index aa27290..c81fd3f 100644 --- a/examples/render-webgl/src/main.rs +++ b/examples/render-webgl/src/main.rs @@ -157,7 +157,7 @@ async fn run() -> Result<(), Box> { } _ => (), } - })?; + }); Ok(()) }