diff --git a/CHANGELOG.md b/CHANGELOG.md index f689fab40b7..83030d5e1fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -308,6 +308,9 @@ centered and the delay before showing them was extended. - [Accurate GPU performance measurements have been implemented][6595]. It is possible now to track both the time spent on both the CPU and the GPU sides. +- [Support recovery from GL context loss][7662]. This allows the application to + continue after an interruption to rendering, such as hibernation or movement + of the application window to a display rendered by a different GPU. [3857]: https://github.com/enso-org/enso/pull/3857 [3985]: https://github.com/enso-org/enso/pull/3985 @@ -323,6 +326,7 @@ [6595]: https://github.com/enso-org/enso/pull/6595 [6487]: https://github.com/enso-org/enso/pull/6487 [6512]: https://github.com/enso-org/enso/pull/6512 +[7662]: https://github.com/enso-org/enso/pull/7662 #### Enso Standard Library diff --git a/app/gui/docs/product/shortcuts.md b/app/gui/docs/product/shortcuts.md index 3b4e2d26d09..f41e8a8faa7 100644 --- a/app/gui/docs/product/shortcuts.md +++ b/app/gui/docs/product/shortcuts.md @@ -141,4 +141,5 @@ broken and require further investigation. | ctrl + shift + arrow up | Pop a breadcrumb without navigating. | | cmd + i | Reload visualizations. To see the effect in the currently shown visualizations, you need to switch to another and switch back. | | ctrl + shift + b | Toggle read-only mode. | +| ctrl + alt + shift + x | Toggle WebGL Context loss / restoration for testing. | | ctrl + shift + u | Dump the suggestion database as JSON to the console. Available only in debug mode, and only if the component browser is open. | diff --git a/app/gui/src/presenter/project.rs b/app/gui/src/presenter/project.rs index 5db85e3f86e..5f9994378dd 100644 --- a/app/gui/src/presenter/project.rs +++ b/app/gui/src/presenter/project.rs @@ -30,6 +30,14 @@ use view::notification::logged as notification; /// We don't know how long the project opening will take, but we still want to show a fake progress /// indicator for the user. This constant represents a progress percentage that will be displayed. const OPEN_PROJECT_SPINNER_PROGRESS: f32 = 0.8; +/// When the GL context is not available, show the spinner in its farthest-from-completion state. +/// This condition will not usually be observed for more than a moment, as the GL context should not +/// be persistently lost while the window is visible. +const LOST_CONTEXT_SPINNER_PROGRESS: f32 = 0.0; +/// When the GL context has been restored and the application is preparing to resume drawing, show +/// the spinner near completion. Context restoration is much faster than initial loading; the only +/// time-consuming operation required is shader recompilation. +const RESTORING_CONTEXT_SPINNER_PROGRESS: f32 = 0.9; @@ -52,6 +60,8 @@ struct Model { available_projects: Rc>>, shortcut_transaction: RefCell>>, execution_failed_notification: notification::Notification, + /// Handle of a function that shows the loading spinner until a lost context is restored. + _context_monitor: ensogl::display::world::ContextHandler, } impl Model { @@ -83,6 +93,7 @@ impl Model { }, }; let execution_failed_notification = notification::Notification::new(options); + let context_monitor = Self::init_context_monitor(view.clone_ref()); Model { controller, module_model, @@ -95,6 +106,7 @@ impl Model { available_projects, shortcut_transaction, execution_failed_notification, + _context_monitor: context_monitor, } } @@ -311,7 +323,7 @@ impl Model { } app.hide_progress_indicator(); view.show_graph_editor(); - }) + }); } fn execution_environment_changed( @@ -336,6 +348,26 @@ impl Model { } }); } + + /// Register a [`Scene`] callback that shows the progress spinner while the WebGL Context is + /// being restored; return the handle. + fn init_context_monitor(view: view::project::View) -> ensogl::display::world::ContextHandler { + scene().on_set_context(move |context| { + if context.is_none() { + view.hide_graph_editor(); + js::app_or_panic().show_progress_indicator(LOST_CONTEXT_SPINNER_PROGRESS); + } else { + let view = view.clone_ref(); + executor::global::spawn(async move { + js::app_or_panic().show_progress_indicator(RESTORING_CONTEXT_SPINNER_PROGRESS); + let scene = scene(); + scene.prepare_to_render().await; + view.show_graph_editor(); + js::app_or_panic().hide_progress_indicator(); + }); + } + }) + } } diff --git a/lib/rust/ensogl/component/text/src/font.rs b/lib/rust/ensogl/component/text/src/font.rs index 3b518ca6c4f..09dc54e2c26 100644 --- a/lib/rust/ensogl/component/text/src/font.rs +++ b/lib/rust/ensogl/component/text/src/font.rs @@ -4,8 +4,8 @@ use crate::prelude::*; use ensogl_core::display::scene; +use ensogl_core::display::world::Context; use ensogl_core::system::gpu; -#[cfg(target_arch = "wasm32")] use ensogl_core::system::gpu::texture; use ensogl_text_msdf as msdf; use ordered_float::NotNan; @@ -937,32 +937,63 @@ profiler::metadata_logger!("GlyphCacheMiss", log_miss(GlyphCacheMiss)); // === FontWithGpuData === // ======================= -#[cfg(target_arch = "wasm32")] -type AtlasTexture = gpu::Texture; - -#[cfg(not(target_arch = "wasm32"))] -type AtlasTexture = f32; - /// A font with associated GPU-stored data. #[allow(missing_docs)] #[derive(Clone, CloneRef, Debug, Deref)] pub struct FontWithGpuData { #[deref] pub font: Font, - /// The glyph atlas. - pub atlas: gpu::Uniform, + pub atlas: gpu::Uniform>, pub opacity_increase: gpu::Uniform, pub opacity_exponent: gpu::Uniform, + context: Rc>>, } impl FontWithGpuData { - fn new(font: Font, hinting: Hinting, context: &Context) -> Self { + fn new(font: Font, hinting: Hinting) -> Self { let Hinting { opacity_increase, opacity_exponent } = hinting; - let texture = get_texture(context); - let atlas = gpu::Uniform::new(texture); let opacity_increase = gpu::Uniform::new(opacity_increase); let opacity_exponent = gpu::Uniform::new(opacity_exponent); - Self { font, atlas, opacity_exponent, opacity_increase } + let atlas = gpu::Uniform::new(default()); + let context = default(); + Self { font, atlas, opacity_exponent, opacity_increase, context } + } + + fn set_context_and_update(&self, context: Option<&Context>) { + *self.context.borrow_mut() = context.cloned(); + self.update_atlas(); + } + + /// Upload the current atlas to the GPU if it is dirty (contains more glyphs than the currently- + /// uploaded version); drop the `gpu::Texture` if context has been lost. + #[profile(Debug)] + fn update_atlas(&self) { + if let Some(context) = self.context.borrow().as_ref() { + let num_glyphs = self.font.msdf_texture().glyphs(); + let gpu_tex_glyphs = self + .atlas + .with_item(|texture| texture.as_ref().map_or_default(|texture| texture.layers())); + let texture_changed = gpu_tex_glyphs as u32 != num_glyphs; + if texture_changed { + let glyph_size = self.font.msdf_texture().size(); + let texture = gpu::Texture::new( + context, + texture::AnyInternalFormat::Rgb8, + texture::AnyItemType::u8, + glyph_size.x() as i32, + glyph_size.y() as i32, + num_glyphs as i32, + default(), + ); + if let Ok(texture) = texture.as_ref() { + self.font + .with_borrowed_msdf_texture_data(|data| texture.reload_with_content(data)); + } + self.atlas.set(texture.ok()); + } + } else { + self.atlas.set(None); + } } } @@ -973,10 +1004,11 @@ impl FontWithGpuData { // ================ /// Stores all loaded fonts. -#[derive(Debug, Clone)] -#[derive(CloneRef)] +#[derive(Clone, CloneRef, Debug)] pub struct Registry { - fonts: Rc>, + network: frp::Network, + fonts: Rc>, + set_context_handle: ensogl_core::display::world::ContextHandler, } impl Registry { @@ -1000,68 +1032,52 @@ impl Registry { self.fonts.get(&name).cloned() } - fn from_fonts(fonts: impl IntoIterator) -> Self { - let scene = scene(); + fn new( + scene: &ensogl_core::display::Scene, + fonts: impl IntoIterator, + ) -> Self { + let context = scene.context.borrow(); + let context = context.as_ref(); let scene_shape = scene.shape().value(); - let context = get_context(&scene); - let fonts = fonts + let fonts: HashMap<_, _> = fonts .into_iter() .map(|(name, font)| { - debug!("Loading font: {:?}", name); let hinting = Hinting::for_font(&name, scene_shape); - (name, FontWithGpuData::new(font, hinting, &context)) + let font = FontWithGpuData::new(font, hinting); + font.set_context_and_update(context); + (name, font) }) .collect(); let fonts = Rc::new(fonts); - Self { fonts } + let fonts_ = Rc::clone(&fonts); + let set_context_handle = scene.on_set_context(move |context| { + for font in fonts_.values() { + font.set_context_and_update(context); + } + }); + let network = frp::Network::new("font::Registry"); + let on_before_rendering = ensogl_core::animation::on_before_rendering(); + frp::extend! { network + eval_ on_before_rendering([fonts] Self::update(&fonts)); + } + Self { network, fonts, set_context_handle } + } + + fn update(fonts: impl AsRef>) { + for font in fonts.as_ref().values() { + font.update_atlas() + } } } impl scene::Extension for Registry { - fn init(_scene: &scene::Scene) -> Self { - Self::default() - } -} - -impl Default for Registry { - fn default() -> Self { + fn init(scene: &scene::Scene) -> Self { let fonts = Embedded::default().into_fonts(); - Self::from_fonts(fonts) + Self::new(scene, fonts) } } -// === Context helpers === - -#[cfg(not(target_arch = "wasm32"))] -#[derive(Clone, Copy, CloneRef, Debug, Default)] -/// Mocked version of WebGL context. -pub struct Context; - -#[cfg(not(target_arch = "wasm32"))] -fn get_context(_scene: &scene::Scene) -> Context { - Context -} - -#[cfg(not(target_arch = "wasm32"))] -fn get_texture(_context: &Context) -> AtlasTexture { - 0.0 -} - -#[cfg(target_arch = "wasm32")] -use ensogl_core::display::world::Context; - -#[cfg(target_arch = "wasm32")] -fn get_context(scene: &scene::Scene) -> Context { - scene.context.borrow().as_ref().unwrap().clone_ref() -} - -#[cfg(target_arch = "wasm32")] -fn get_texture(context: &Context) -> AtlasTexture { - gpu::Texture::new(context, (0, 0)) -} - - // =============== // === Hinting === diff --git a/lib/rust/ensogl/component/text/src/font/glyph.rs b/lib/rust/ensogl/component/text/src/font/glyph.rs index e0186e5ad4a..f1e10cfb483 100644 --- a/lib/rust/ensogl/component/text/src/font/glyph.rs +++ b/lib/rust/ensogl/component/text/src/font/glyph.rs @@ -21,8 +21,6 @@ use ensogl_core::display::symbol::geometry::SpriteSystem; use ensogl_core::display::symbol::material::Material; use ensogl_core::display::symbol::shader::builder::CodeTemplate; use ensogl_core::system::gpu::texture; -#[cfg(target_arch = "wasm32")] -use ensogl_core::system::gpu::Texture; use font::FontWithGpuData; use font::GlyphRenderInfo; use font::Style; @@ -132,13 +130,15 @@ impl display::shape::CustomSystemData for SystemData { sprite_system.unsafe_set_alignment(alignment::Dim2::left_bottom()); display::world::with_context(|t| { - t.variables.add("msdf_range", GlyphRenderInfo::MSDF_PARAMS.range as f32); - t.variables.add("msdf_size", size); + let mut variables = t.variables.borrow_mut(); + variables.add("msdf_range", GlyphRenderInfo::MSDF_PARAMS.range as f32); + variables.add("msdf_size", size); }); - symbol.variables().add_uniform_or_panic("atlas", &font.atlas); - symbol.variables().add_uniform_or_panic("opacity_increase", &font.opacity_increase); - symbol.variables().add_uniform_or_panic("opacity_exponent", &font.opacity_exponent); + let mut variables = symbol.variables.borrow_mut(); + variables.add_uniform_or_panic("atlas", &font.atlas); + variables.add_uniform_or_panic("opacity_increase", &font.opacity_increase); + variables.add_uniform_or_panic("opacity_exponent", &font.opacity_exponent); SystemData {} } @@ -161,31 +161,25 @@ pub struct Glyph { #[derive(Debug, display::Object)] pub struct GlyphData { pub view: glyph_shape::View, - pub glyph_id: Cell, pub line_byte_offset: Cell, - pub display_object: display::object::Instance, - pub context: Context, - pub properties: Cell, - pub variations: RefCell, pub x_advance: Cell, /// Indicates whether this glyph is attached to cursor. Needed for text width computation. /// Attached glyphs should not be considered part of the line during animation because they /// will be moved around, so they need to be ignored when computing the line width. pub attached_to_cursor: Cell, + glyph_id: Cell, + display_object: display::object::Instance, + properties: Cell, + variations: RefCell, } // === Face Header Properties Getters and Setters === -/// For each property, such as `Weight(Thin, ExtraLight, ...)` defines: +/// For each property, such as `Weight` defines: /// ```text /// pub fn weight(&self) -> Weight { ... } /// pub fn set_weight(&self, weight: Weight) { ... } -/// pub fn set_weight_thin(&self) { ... } -/// pub fn set_weight_extra_light(&self) { ... } -/// ... -/// pub fn is_weight_thin(&self) { ... } -/// pub fn is_weight_extra_light(&self) { ... } /// ... /// ``` /// @@ -195,7 +189,7 @@ pub struct GlyphData { /// pub fn set_properties(&self, props: font::family::NonVariableFaceHeader) { ... } /// ``` macro_rules! define_prop_setters_and_getters { - ($($prop:ident ($($variant:ident),* $(,)?)),*$(,)?) => { paste! { + ($($prop:ident),*$(,)?) => { paste! { /// Set `NonVariableFaceHeader` of the glyph. pub fn set_properties(&self, props: font::family::NonVariableFaceHeader) { @@ -222,46 +216,12 @@ macro_rules! define_prop_setters_and_getters { pub fn [<$prop:snake:lower>](&self) -> $prop { self.properties.get().[<$prop:snake:lower>] } - - $( - #[doc = "Set the `"] - #[doc = stringify!($prop)] - #[doc = "` property to `"] - #[doc = stringify!($variant)] - #[doc = "`."] - pub fn [](&self) { - self.[]($prop::$variant) - } - - #[doc = "Checks whether the `"] - #[doc = stringify!($prop)] - #[doc = "` property is set to `"] - #[doc = stringify!($variant)] - #[doc = "`."] - pub fn [](&self) -> bool { - self.properties.get().[<$prop:snake:lower>] == $prop::$variant - } - )* )* }}; } impl Glyph { - define_prop_setters_and_getters![ - Weight(Thin, ExtraLight, Light, Normal, Medium, SemiBold, Bold, ExtraBold, Black), - Style(Normal, Italic, Oblique), - Width( - UltraCondensed, - ExtraCondensed, - Condensed, - SemiCondensed, - Normal, - SemiExpanded, - Expanded, - ExtraExpanded, - UltraExpanded - ) - ]; + define_prop_setters_and_getters![Weight, Style, Width]; } @@ -382,7 +342,6 @@ impl Glyph { self.view.data.borrow().font.glyph_info(self.properties.get(), &variations, glyph_id); if let Some(glyph_info) = opt_glyph_info { self.view.atlas_index.set(glyph_info.msdf_texture_glyph_id); - self.update_atlas(); self.view.set_size(glyph_info.scale.scale(self.font_size().value)); } else { // This should not happen. Fonts contain special glyph for missing characters. @@ -401,27 +360,6 @@ impl Glyph { fn refresh(&self) { self.set_glyph_id(self.glyph_id.get()); } - - /// Check whether the CPU-bound texture changed and if so, upload it to GPU. - #[cfg(not(target_arch = "wasm32"))] - fn update_atlas(&self) {} - #[cfg(target_arch = "wasm32")] - fn update_atlas(&self) { - let data = self.view.data.borrow(); - let font = &data.font; - let glyph_size = data.font.msdf_texture().size(); - let num_glyphs = data.font.msdf_texture().glyphs() as i32; - let gpu_tex_glyphs = font.atlas.with_item(|texture| texture.storage().layers); - let texture_changed = gpu_tex_glyphs != num_glyphs; - if texture_changed { - let texture = Texture::new( - &self.context, - ((glyph_size.x() as i32, glyph_size.y() as i32), num_glyphs), - ); - font.with_borrowed_msdf_texture_data(|data| texture.reload_with_content(data)); - font.atlas.set(texture); - } - } } @@ -456,31 +394,10 @@ impl WeakGlyph { // === System === // ============== -#[cfg(not(target_arch = "wasm32"))] -#[derive(Clone, CloneRef, Debug, Default)] -#[allow(missing_copy_implementations)] -/// Mocked version of WebGL context. -pub struct Context; - -#[cfg(not(target_arch = "wasm32"))] -fn get_context(_scene: &Scene) -> Context { - Context -} - -#[cfg(target_arch = "wasm32")] -use ensogl_core::display::world::Context; - -#[cfg(target_arch = "wasm32")] -fn get_context(scene: &Scene) -> Context { - scene.context.borrow().as_ref().unwrap().clone_ref() -} - - /// A system for displaying glyphs. #[derive(Clone, CloneRef, Debug)] #[allow(missing_docs)] pub struct System { - context: Context, pub font: FontWithGpuData, } @@ -491,15 +408,13 @@ impl System { let scene = scene.as_ref(); let fonts = scene.extension::(); let font = fonts.load(font_name); - let context = get_context(scene); - Self { context, font } + Self { font } } /// Create new glyph. In the returned glyph the further parameters (position,size,character) /// may be set. #[profile(Debug)] pub fn new_glyph(&self) -> Glyph { - let context = self.context.clone(); let display_object = display::object::Instance::new_no_debug(); let font = self.font.clone_ref(); let glyph_id = default(); @@ -516,7 +431,6 @@ impl System { data: Rc::new(GlyphData { view, display_object, - context, glyph_id, line_byte_offset, properties, diff --git a/lib/rust/ensogl/core/Cargo.toml b/lib/rust/ensogl/core/Cargo.toml index 8394d8aa18b..21b776ff54e 100644 --- a/lib/rust/ensogl/core/Cargo.toml +++ b/lib/rust/ensogl/core/Cargo.toml @@ -72,6 +72,7 @@ features = [ 'Url', 'WebGlBuffer', 'WebGlFramebuffer', + 'WebglLoseContext', 'WebGlProgram', 'WebGlQuery', 'WebGlRenderingContext', diff --git a/lib/rust/ensogl/core/src/display/render/composer.rs b/lib/rust/ensogl/core/src/display/render/composer.rs index 94b9817952f..80bf5811838 100644 --- a/lib/rust/ensogl/core/src/display/render/composer.rs +++ b/lib/rust/ensogl/core/src/display/render/composer.rs @@ -6,6 +6,7 @@ use crate::system::gpu::*; use crate::display::render::pass; use crate::display::scene::UpdateStatus; +use crate::system::gpu::context::ContextLost; @@ -13,44 +14,35 @@ use crate::display::scene::UpdateStatus; // === Composer === // ================ -shared! { Composer /// Render composer is a render pipeline bound to a specific context. #[derive(Debug)] -pub struct ComposerModel { - pipeline : Pipeline, - passes : Vec, - variables : UniformScope, - context : Context, - width : i32, - height : i32, - pixel_ratio : f32, +pub struct Composer { + passes: Vec>, + variables: Rc>, + context: Context, + width: i32, + height: i32, + pixel_ratio: f32, } -impl { +impl Composer { /// Constructor - pub fn new - ( pipeline: &Pipeline - , context: &Context - , variables: &UniformScope - , width: i32 - , height: i32 - , pixel_ratio: f32 + pub fn new( + pipeline: Pipeline, + context: &Context, + variables: &Rc>, + width: i32, + height: i32, + pixel_ratio: f32, ) -> Self { - let pipeline = pipeline.clone_ref(); - let passes = default(); - let context = context.clone(); + let passes = default(); + let context = context.clone(); let variables = variables.clone_ref(); - let mut this = Self {pipeline, passes, variables, context, width, height, pixel_ratio}; - this.init_passes(); + let mut this = Self { passes, variables, context, width, height, pixel_ratio }; + this.set_pipeline(pipeline); this } - /// Set a new pipeline for this composer. - pub fn set_pipeline(&mut self, pipeline:&Pipeline) { - self.pipeline = pipeline.clone_ref(); - self.init_passes(); - } - /// Resize the composer and reinitialize all of its screen-size-dependent layers. pub fn resize(&mut self, width: i32, height: i32, pixel_ratio: f32) { if width == self.width && height == self.height && pixel_ratio == self.pixel_ratio { @@ -61,24 +53,22 @@ impl { self.width = width; self.height = height; self.pixel_ratio = pixel_ratio; - let defs = self.pipeline.passes_clone(); - for (pass, def) in self.passes.iter_mut().zip(defs) { - pass.resize(def, width, height, pixel_ratio); + for pass in &mut self.passes { + pass.resize(width, height, pixel_ratio); } } - /// Initialize all pass definitions from the [`Pipeline`]. - fn init_passes(&mut self) { - let ctx = &self.context; - let vars = &self.variables; - let width = self.width; + /// Set a new pipeline for this composer. + pub fn set_pipeline(&mut self, pipeline: Pipeline) { + let width = self.width; let height = self.height; let pixel_ratio = self.pixel_ratio; - let defs = self.pipeline.passes_clone(); - let passes = defs - .into_iter() - .map(|pass| ComposerPass::new(ctx, vars, pass, width, height, pixel_ratio)); - self.passes = passes.collect_vec(); + let defs = pipeline.passes(); + let instance = + pass::InstanceInfo::new(&self.context, &self.variables, width, height, pixel_ratio); + let passes: Result, ContextLost> = + defs.iter().map(|def| def.instantiate(instance.clone())).collect(); + self.passes = passes.unwrap_or_default(); } /// Run all the registered passes in this composer. @@ -87,74 +77,4 @@ impl { pass.run(update_status); } } -}} - - - -// ==================== -// === ComposerPass === -// ==================== - -/// A `pass::Definition` bound to a specific rendering context. -#[derive(Derivative)] -#[derivative(Debug)] -struct ComposerPass { - #[derivative(Debug = "ignore")] - pass: Box, - instance: pass::Instance, -} - -impl Deref for ComposerPass { - type Target = pass::Instance; - fn deref(&self) -> &Self::Target { - &self.instance - } -} - -impl DerefMut for ComposerPass { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.instance - } -} - -impl ComposerPass { - /// Constructor - #[allow(clippy::borrowed_box)] - pub fn new( - context: &Context, - variables: &UniformScope, - mut pass: Box, - width: i32, - height: i32, - pixel_ratio: f32, - ) -> Self { - let instance = pass::Instance::new(context, variables, width, height, pixel_ratio); - pass.initialize(&instance); - Self { pass, instance } - } - - /// Run the pass. - pub fn run(&mut self, update_status: UpdateStatus) { - self.pass.run(&self.instance, update_status); - } - - /// Update the pass for a change in screen size. Depending on the pass, this may require - /// reinitialization. - pub fn resize( - &mut self, - def: Box, - width: i32, - height: i32, - pixel_ratio: f32, - ) { - if def.is_screen_size_independent() { - self.instance.width = width; - self.instance.height = height; - self.instance.pixel_ratio = pixel_ratio; - } else { - let ctx = self.context.clone(); - let vars = mem::take(&mut self.variables); - *self = ComposerPass::new(&ctx, &vars, def, width, height, pixel_ratio); - } - } } diff --git a/lib/rust/ensogl/core/src/display/render/pass.rs b/lib/rust/ensogl/core/src/display/render/pass.rs index 63789aa3ad7..4f5f380db5d 100644 --- a/lib/rust/ensogl/core/src/display/render/pass.rs +++ b/lib/rust/ensogl/core/src/display/render/pass.rs @@ -4,7 +4,7 @@ use crate::prelude::*; use crate::system::gpu::*; use crate::display::scene::UpdateStatus; -use crate::system::gpu::data::texture::class::TextureOps; +use crate::system::gpu::context::ContextLost; @@ -12,20 +12,20 @@ use crate::system::gpu::data::texture::class::TextureOps; // === Definition === // ================== -/// Render pass definition. When used, the [`Composer`] will clone it and then will call the -/// [`init`] method before it's first usage. It will happen everytime the [`Composer`] will be -/// re-initialized (e.g. after changing scene size). Then, the function [`run`] will be called for -/// every registered pass. -#[allow(missing_docs)] -pub trait Definition: CloneBoxedForDefinition + Debug + 'static { - fn initialize(&mut self, _instance: &Instance) {} - fn run(&mut self, _instance: &Instance, update_status: UpdateStatus); - fn is_screen_size_independent(&self) -> bool { - false - } +/// Render pass definition. Supports creation of render pass instances. +pub trait Definition: Debug + 'static { + /// Return a new instance of the pass, with the specified parameters. + fn instantiate(&self, instance: InstanceInfo) -> Result, ContextLost>; } -clone_boxed!(Definition); +/// Render pass instance. +pub trait Instance: Debug + 'static { + /// Run the pass. + fn run(&mut self, update_status: UpdateStatus); + + /// Update the pass for the new screen size. + fn resize(&mut self, width: i32, height: i32, pixel_ratio: f32); +} @@ -34,27 +34,27 @@ clone_boxed!(Definition); // ================ /// Instance of a render pass. Every render pass will be initialized by the [`Composer`] before its -/// first run (see the [`Definition::run`] method. During such initialization a new [`Instance`] +/// first run (see the [`Definition::run`] method. During such initialization a new [`InstanceInfo`] /// will be created. Please note that a new instance will be generated everytime a pass will be /// instantiated (e.g. after changing the scene size). /// /// The main purpose of this structure is to provide passes with common information and utilities. /// For example, it streamlines the creation of new framebuffers and textures. #[allow(missing_docs)] -#[derive(Debug)] -pub struct Instance { - pub variables: UniformScope, +#[derive(Debug, Clone)] +pub struct InstanceInfo { + pub variables: Rc>, pub context: Context, pub width: i32, pub height: i32, pub pixel_ratio: f32, } -impl Instance { +impl InstanceInfo { /// Constructor pub fn new( context: &Context, - variables: &UniformScope, + variables: &Rc>, width: i32, height: i32, pixel_ratio: f32, @@ -81,27 +81,29 @@ impl Instance { let context = &self.context; let variables = &self.variables; let name = format!("pass_{}", output.name); - let args = (width, height); let format = output.internal_format; let item_type = output.item_type; - let params = Some(output.texture_parameters); - uniform::get_or_add_gpu_texture_dyn( - context, variables, &name, format, item_type, args, params, - ) + let texture = + Texture::new(context, format, item_type, width, height, 0, output.texture_parameters); + let uniform = variables.borrow_mut().set(name, texture.ok()).unwrap(); + uniform.into() } /// Create a new framebuffer from the provided textures. - pub fn new_framebuffer(&self, textures: &[&AnyTextureUniform]) -> Framebuffer { + pub fn new_framebuffer( + &self, + textures: &[&AnyTextureUniform], + ) -> Result { let context = self.context.clone(); - let native = self.context.create_framebuffer().unwrap(); + let native = self.context.create_framebuffer()?; let target = Context::FRAMEBUFFER; let draw_buffers = js_sys::Array::new(); context.bind_framebuffer(*target, Some(&native)); for (index, texture) in textures.iter().enumerate() { + let texture = texture.texture().ok_or(ContextLost)?; let texture_target = Context::TEXTURE_2D; let attachment_point = *Context::COLOR_ATTACHMENT0 + index as u32; - let gl_texture = texture.gl_texture(); - let gl_texture = Some(&gl_texture); + let gl_texture = Some(texture.as_gl_texture()); let level = 0; draw_buffers.push(&attachment_point.into()); context.framebuffer_texture_2d( @@ -118,7 +120,7 @@ impl Instance { if framebuffer_status != *Context::FRAMEBUFFER_COMPLETE { warn!("Framebuffer incomplete (status: {framebuffer_status}).") } - Framebuffer { context, native } + Ok(Framebuffer { context, native }) } /// Run a closure with different viewport set in context. @@ -186,7 +188,9 @@ impl OutputDefinition { // =================== /// A native WebGL framebuffer object bound to the gl context. -#[derive(Debug, Clone)] +// NOTE: This type must not derive `Clone`, as the resulting shared `native` would be deleted when +// either instance is dropped. +#[derive(Debug)] pub struct Framebuffer { context: Context, native: web_sys::WebGlFramebuffer, diff --git a/lib/rust/ensogl/core/src/display/render/passes/cache_shapes.rs b/lib/rust/ensogl/core/src/display/render/passes/cache_shapes.rs index de3987270b9..da36b0a752d 100644 --- a/lib/rust/ensogl/core/src/display/render/passes/cache_shapes.rs +++ b/lib/rust/ensogl/core/src/display/render/passes/cache_shapes.rs @@ -5,12 +5,46 @@ use crate::prelude::*; use crate::display; use crate::display::render::pass; -use crate::display::render::pass::Instance; use crate::display::scene::Layer; use crate::display::scene::UpdateStatus; use crate::display::shape::glsl::codes::DisplayModes; use crate::display::world::with_context; use crate::gui::component::AnyShapeView; +use crate::system::gpu::context::ContextLost; + + +// ========================== +// === CacheShapesPassDef === +// ========================== + +/// Definition of pass rendering cached shapes to texture. See [`CacheShapesPass`] for information +/// about pass operation. +#[derive(Debug)] +pub struct CacheShapesPassDef { + layer: Layer, +} + +impl Default for CacheShapesPassDef { + fn default() -> Self { + Self { layer: Layer::new("Cached Shapes") } + } +} + +impl CacheShapesPassDef { + /// Constructor. + pub fn new() -> Self { + default() + } +} + +impl pass::Definition for CacheShapesPassDef { + fn instantiate( + &self, + instance: pass::InstanceInfo, + ) -> Result, ContextLost> { + Ok(Box::new(CacheShapesPass::new(self.layer.clone(), instance)?)) + } +} @@ -18,7 +52,7 @@ use crate::gui::component::AnyShapeView; // === CacheShapesPass === // ======================= -/// Definition of pass rendering cached shapes to texture. +/// Instance of pass rendering cached shapes to texture. /// /// On each run it checks what not-yet-rendered shapes has compiled shaders and render their color /// and SDF information to the texture, which is stored in `pass_cached_shapes` uniform. See also @@ -31,75 +65,53 @@ use crate::gui::component::AnyShapeView; /// Once given shape system is ready to render (has shader compiled), we call "render" only on its /// symbol. There is no need to render previous shapes again, because we don't clear texture at any /// point. -#[derive(Clone, Derivative)] +#[derive(Derivative)] #[derivative(Debug)] pub struct CacheShapesPass { - framebuffer: Option, - texture: Option, + framebuffer: pass::Framebuffer, + texture: crate::system::gpu::data::uniform::AnyTextureUniform, #[derivative(Debug = "ignore")] - shapes_to_render: Vec>, + shapes_to_render: Vec>, /// Texture size in device pixels. texture_size_device: Vector2, layer: Layer, camera_ready: Rc>, #[derivative(Debug = "ignore")] display_object_update_handler: Option>, -} - -impl Default for CacheShapesPass { - fn default() -> Self { - Self::new() - } + instance: pass::InstanceInfo, } impl CacheShapesPass { /// Constructor. - pub fn new() -> Self { - Self { - framebuffer: default(), - texture: default(), - shapes_to_render: default(), - layer: Layer::new("Cached Shapes"), - texture_size_device: default(), - camera_ready: default(), - display_object_update_handler: default(), - } - } -} - - -// === [`pass::Definition`] Implementation === - -impl pass::Definition for CacheShapesPass { - fn initialize(&mut self, instance: &Instance) { + pub fn new(layer: Layer, instance: pass::InstanceInfo) -> Result { let scene = scene(); - display::world::CACHED_SHAPES_DEFINITIONS.with_borrow(|shapes| { - self.shapes_to_render = - shapes.iter().map(|def| (def.for_texture_constructor)().into()).collect() - }); + let shapes_to_render: Vec> = + display::world::CACHED_SHAPES_DEFINITIONS.with_borrow(|shapes| { + shapes.iter().map(|def| (def.for_texture_constructor)()).collect() + }); let texture_size = display::shape::primitive::system::cached::texture_size(); - self.texture_size_device = + let texture_size_device = texture_size.map(|i| ((i as f32) * instance.pixel_ratio).ceil() as i32); - - for shape in &self.shapes_to_render { + for shape in &shapes_to_render { scene.add_child(&**shape); - self.layer.add(&**shape); + layer.add(&**shape); } - self.layer.camera().set_screen(texture_size.x as f32, texture_size.y as f32); + layer.camera().set_screen(texture_size.x as f32, texture_size.y as f32); // We must call update of layer and display object hierarchy at this point, because: // 1. the [`self.layer`] is not in the Layer hierarchy, so it's not updated during routine // layers update. // 2. The pass can be re-initialized after the display object hierarchy update, but before // rendering, so the hierarchy could be outdated during rendering. - self.layer.camera().update(&scene); + layer.camera().update(&scene); scene.display_object.update(&scene); - self.layer.update(); + layer.update(); + let camera_ready = Rc::new(Cell::new(false)); // The asynchronous update of the scene's display object initiated above will eventually // set our layer's camera's transformation. Handle the camera update when all // previously-initiated FRP events finish being processed. - let handle = frp::microtasks::next_microtask({ - let camera = self.layer.camera(); - let camera_ready = Rc::clone(&self.camera_ready); + let display_object_update_handler = frp::microtasks::next_microtask({ + let camera = layer.camera(); + let camera_ready = Rc::clone(&camera_ready); move || { // Be careful to not capture variable `scene` here! The scene keeps all passes, // so we would create an Rc loop. @@ -107,47 +119,53 @@ impl pass::Definition for CacheShapesPass { camera_ready.set(true); } }); - self.display_object_update_handler = Some(Rc::new(handle)); - let output = pass::OutputDefinition::new_rgba("cached_shapes"); - let texture = - instance.new_texture(&output, self.texture_size_device.x, self.texture_size_device.y); - self.framebuffer = Some(instance.new_framebuffer(&[&texture])); - self.texture = Some(texture); + let texture = instance.new_texture(&output, texture_size_device.x, texture_size_device.y); + let framebuffer = instance.new_framebuffer(&[&texture])?; + Ok(Self { + framebuffer, + texture, + shapes_to_render, + layer, + texture_size_device, + camera_ready, + display_object_update_handler: Some(Rc::new(display_object_update_handler)), + instance, + }) } +} - fn run(&mut self, instance: &Instance, _update_status: UpdateStatus) { +impl pass::Instance for CacheShapesPass { + fn run(&mut self, _update_status: UpdateStatus) { if self.camera_ready.get() { - let is_shader_compiled = |shape: &mut Rc| { - shape.sprite().symbol.shader().program().is_some() + let is_shader_compiled = |shape: &mut Box| { + shape.sprite().symbol.shader.borrow().program().is_some() }; let mut ready_to_render = self.shapes_to_render.drain_filter(is_shader_compiled).peekable(); if ready_to_render.peek().is_some() { - if let Some(framebuffer) = self.framebuffer.as_ref() { - framebuffer.with_bound(|| { - instance.with_viewport( - self.texture_size_device.x, - self.texture_size_device.y, - || { - with_display_mode(DisplayModes::CachedShapesTexture, || { - with_context(|ctx| ctx.set_camera(&self.layer.camera())); - for shape in ready_to_render { - shape.sprite().symbol.render(); - } - }) - }, - ); - }); - } else { - reportable_error!("Impossible happened: The CacheShapesPass was run without initialized framebuffer."); - } + self.framebuffer.with_bound(|| { + self.instance.with_viewport( + self.texture_size_device.x, + self.texture_size_device.y, + || { + with_display_mode(DisplayModes::CachedShapesTexture, || { + with_context(|ctx| ctx.set_camera(&self.layer.camera())); + for shape in ready_to_render { + shape.sprite().symbol.render(); + } + }) + }, + ); + }); } } } - fn is_screen_size_independent(&self) -> bool { - true + fn resize(&mut self, width: i32, height: i32, pixel_ratio: f32) { + self.instance.width = width; + self.instance.height = height; + self.instance.pixel_ratio = pixel_ratio; } } diff --git a/lib/rust/ensogl/core/src/display/render/passes/pixel_read.rs b/lib/rust/ensogl/core/src/display/render/passes/pixel_read.rs index 25a2bc76cf6..ca1ad636d55 100644 --- a/lib/rust/ensogl/core/src/display/render/passes/pixel_read.rs +++ b/lib/rust/ensogl/core/src/display/render/passes/pixel_read.rs @@ -6,7 +6,7 @@ use crate::system::js::*; use crate::display::render::pass; use crate::display::scene::UpdateStatus; -use crate::system::gpu::data::texture::class::TextureOps; +use crate::system::gpu::context::ContextLost; use web_sys::WebGlBuffer; use web_sys::WebGlFramebuffer; @@ -14,30 +14,55 @@ use web_sys::WebGlSync; -// ========================= -// === PixelReadPassData === -// ========================= +// ======================== +// === PixelReadPassDef === +// ======================== -/// Internal state for the `PixelReadPass`. -#[derive(Clone, Debug)] -pub struct PixelReadPassData { - buffer: WebGlBuffer, - framebuffer: WebGlFramebuffer, - format: texture::AnyFormat, - item_type: texture::AnyItemType, - js_array: JsTypedArray, +/// Definition of a pass that reads the color of a pixel. +#[derive(Clone, Derivative)] +#[derivative(Debug)] +pub struct PixelReadPassDef { + position: Uniform>, + threshold: Rc>, + #[derivative(Debug = "ignore")] + callback: Rc)>, + #[derivative(Debug = "ignore")] + sync_callback: Rc, } -impl PixelReadPassData { - /// Constructor. +impl PixelReadPassDef { + /// Create a new pixel-read-pass factory. + /// + /// `callback`: Will be evaluated after a successful pixel read action. + /// `sync_callback`: Will be evaluated at the beginning of a pixel read action. + /// + /// Note: The callbacks will not be evaluated on each run of the pass, as the read is + /// asynchronous and can take longer than a single frame. pub fn new( - buffer: WebGlBuffer, - framebuffer: WebGlFramebuffer, - format: texture::AnyFormat, - item_type: texture::AnyItemType, - js_array: JsTypedArray, + position: Uniform>, + callback: impl 'static + Fn(Vec), + sync_callback: impl 'static + Fn(), ) -> Self { - Self { buffer, framebuffer, format, item_type, js_array } + let threshold = Rc::new(Cell::new(0)); + let callback = Rc::new(callback); + let sync_callback = Rc::new(sync_callback); + Self { position, threshold, callback, sync_callback } + } + + /// Returns a reference that can be used to set the threshold of how often the pass should be + /// run. Threshold of 0 means that it will be run every time. Threshold of N means that it will + /// be only run every N-th call to the `run` function. + pub fn get_threshold(&self) -> Rc> { + self.threshold.clone() + } +} + +impl pass::Definition for PixelReadPassDef { + fn instantiate( + &self, + instance: pass::InstanceInfo, + ) -> Result, ContextLost> { + Ok(Box::new(PixelReadPass::new(self.clone(), instance)?)) } } @@ -48,108 +73,85 @@ impl PixelReadPassData { // ===================== /// Reads the pixel color and stores it in the 'pass_pixel_color' variable. -#[derive(Derivative, Clone)] +#[derive(Derivative, Clone, Deref)] #[derivative(Debug)] pub struct PixelReadPass { - data: Option>, + buffer: WebGlBuffer, + framebuffer: WebGlFramebuffer, + format: texture::AnyFormat, + item_type: texture::AnyItemType, + js_array: JsTypedArray, sync: Option, - position: Uniform>, - threshold: Rc>, since_last_read: usize, - #[derivative(Debug = "ignore")] - callback: Option)>>, - #[derivative(Debug = "ignore")] - sync_callback: Option>, + instance: pass::InstanceInfo, + #[deref] + definition: PixelReadPassDef, } impl PixelReadPass { /// Constructor. - pub fn new(position: &Uniform>) -> Self { - let data = default(); - let sync = default(); - let position = position.clone_ref(); - let callback = default(); - let sync_callback = default(); - let threshold = default(); - let since_last_read = 0; - Self { data, sync, position, threshold, since_last_read, callback, sync_callback } - } - - /// Sets a callback which will be evaluated after a successful pixel read action. - /// - /// Please note that it will not be evaluated after each run of this pass, as the read is - /// performed in an asynchronous fashion and can take longer than a single frame. - pub fn set_callback) + 'static>(&mut self, f: F) { - self.callback = Some(Rc::new(f)); - } - - /// Sets a callback which will be evaluated after at the beginning of pixel read action. - /// - /// It will not be evaluated after each run of this pass as the read is performed in an - /// asynchronous fashion and can take longer than a single frame. - pub fn set_sync_callback(&mut self, f: F) { - self.sync_callback = Some(Rc::new(f)); - } - - /// Returns a reference that can be used to set the threshold of how often the pass should be - /// run. Threshold of 0 means that it will be run every time. Threshold of N means that it will - /// be only run every N-th call to the `run` function. - pub fn get_threshold(&mut self) -> Rc> { - self.threshold.clone() - } - - fn init_if_fresh(&mut self, context: &Context, variables: &UniformScope) { - if self.data.is_none() { - let buffer = context.create_buffer().unwrap(); - let js_array = JsTypedArray::::new_with_length(4); - let target = Context::PIXEL_PACK_BUFFER; - let usage = Context::DYNAMIC_READ; - context.bind_buffer(*target, Some(&buffer)); - context.buffer_data_with_opt_array_buffer(*target, Some(&js_array.buffer()), *usage); - context.bind_buffer(*target, None); - - let texture = match variables.get("pass_id").unwrap() { - AnyUniform::Texture(t) => t, - _ => panic!("Pass internal error. Unmatched types."), - }; - let format = texture.get_format(); - let item_type = texture.get_item_type(); - let gl_texture = texture.gl_texture(); - let framebuffer = context.create_framebuffer().unwrap(); - let target = Context::FRAMEBUFFER; - let texture_target = Context::TEXTURE_2D; - let attachment_point = Context::COLOR_ATTACHMENT0; - let gl_texture = Some(&gl_texture); - let level = 0; - context.bind_framebuffer(*target, Some(&framebuffer)); - context.framebuffer_texture_2d( - *target, - *attachment_point, - *texture_target, - gl_texture, - level, - ); - context.bind_framebuffer(*target, None); - let framebuffer_status = context.check_framebuffer_status(*Context::FRAMEBUFFER); - if framebuffer_status != *Context::FRAMEBUFFER_COMPLETE { - warn!("Framebuffer incomplete (status: {framebuffer_status}).") - } - let data = PixelReadPassData::new(buffer, framebuffer, format, item_type, js_array); - self.data = Some(data); + pub fn new( + definition: PixelReadPassDef, + instance: pass::InstanceInfo, + ) -> Result { + let context = &instance.context; + let buffer = context.create_buffer()?; + let js_array = JsTypedArray::::new_with_length(4); + let target = Context::PIXEL_PACK_BUFFER; + let usage = Context::DYNAMIC_READ; + context.bind_buffer(*target, Some(&buffer)); + context.buffer_data_with_opt_array_buffer(*target, Some(&js_array.buffer()), *usage); + context.bind_buffer(*target, None); + let texture = match instance.variables.borrow().get("pass_id").unwrap() { + AnyUniform::Texture(t) => t, + _ => panic!("Pass internal error. Unmatched types."), + }; + let texture = texture.texture().ok_or(ContextLost)?; + let format = texture.get_format(); + let item_type = texture.get_item_type(); + let gl_texture = Some(texture.as_gl_texture()); + let framebuffer = context.create_framebuffer()?; + let target = Context::FRAMEBUFFER; + let texture_target = Context::TEXTURE_2D; + let attachment_point = Context::COLOR_ATTACHMENT0; + let level = 0; + context.bind_framebuffer(*target, Some(&framebuffer)); + context.framebuffer_texture_2d( + *target, + *attachment_point, + *texture_target, + gl_texture, + level, + ); + context.bind_framebuffer(*target, None); + let framebuffer_status = context.check_framebuffer_status(*Context::FRAMEBUFFER); + if framebuffer_status != *Context::FRAMEBUFFER_COMPLETE { + warn!("Framebuffer incomplete (status: {framebuffer_status}).") } + Ok(Self { + buffer, + framebuffer, + format, + item_type, + js_array, + sync: default(), + since_last_read: default(), + instance, + definition, + }) } #[profile(Detail)] - fn run_not_synced(&mut self, context: &Context) { - let data = self.data.as_ref().unwrap(); + fn run_not_synced(&mut self) { + let context = &self.instance.context; let position = self.position.get(); let width = 1; let height = 1; - let format = data.format.to::().into(); - let typ = data.item_type.to::().into(); + let format = self.format.to::().into(); + let typ = self.item_type.to::().into(); let offset = 0; - context.bind_framebuffer(*Context::FRAMEBUFFER, Some(&data.framebuffer)); - context.bind_buffer(*Context::PIXEL_PACK_BUFFER, Some(&data.buffer)); + context.bind_framebuffer(*Context::FRAMEBUFFER, Some(&self.framebuffer)); + context.bind_buffer(*Context::PIXEL_PACK_BUFFER, Some(&self.buffer)); context .read_pixels_with_i32(position.x, position.y, width, height, format, typ, offset) .unwrap(); @@ -161,46 +163,54 @@ impl PixelReadPass { } #[profile(Detail)] - fn check_and_handle_sync(&mut self, context: &Context, sync: &WebGlSync) { - let data = self.data.as_ref().unwrap(); + fn check_and_handle_sync(&mut self, sync: &WebGlSync) { + let context = &self.instance.context; let status = context.get_sync_parameter(sync, *Context::SYNC_STATUS); if status == *Context::SIGNALED { context.delete_sync(Some(sync)); self.sync = None; let target = Context::PIXEL_PACK_BUFFER; let offset = 0; - let buffer_view = data.js_array.to_object(); - context.bind_buffer(*target, Some(&data.buffer)); + let buffer_view = self.js_array.to_object(); + context.bind_buffer(*target, Some(&self.buffer)); context.get_buffer_sub_data_with_i32_and_array_buffer_view( *target, offset, buffer_view, ); context.bind_buffer(*Context::PIXEL_PACK_BUFFER, None); - if let Some(f) = &self.callback { - f(data.js_array.to_vec()); - } + (self.callback)(self.js_array.to_vec()); } } } -impl pass::Definition for PixelReadPass { - fn run(&mut self, instance: &pass::Instance, update_status: UpdateStatus) { +impl pass::Instance for PixelReadPass { + fn run(&mut self, update_status: UpdateStatus) { if self.since_last_read < self.threshold.get() { self.since_last_read += 1; } else { self.since_last_read = 0; - self.init_if_fresh(&instance.context, &instance.variables); if let Some(sync) = self.sync.clone() { - self.check_and_handle_sync(&instance.context, &sync); + self.check_and_handle_sync(&sync); } let need_sync = update_status.scene_was_dirty || update_status.pointer_position_changed; if need_sync && self.sync.is_none() { - self.run_not_synced(&instance.context); - if let Some(callback) = &self.sync_callback { - callback() - } + self.run_not_synced(); + (self.sync_callback)(); } } } + + fn resize(&mut self, width: i32, height: i32, pixel_ratio: f32) { + let mut instance = self.instance.clone(); + instance.width = width; + instance.height = height; + instance.pixel_ratio = pixel_ratio; + match Self::new(self.definition.clone(), instance) { + Ok(new) => { + *self = new; + } + Err(ContextLost) => (), + } + } } diff --git a/lib/rust/ensogl/core/src/display/render/passes/screen.rs b/lib/rust/ensogl/core/src/display/render/passes/screen.rs index 68b683bf949..34e5776a5e3 100644 --- a/lib/rust/ensogl/core/src/display/render/passes/screen.rs +++ b/lib/rust/ensogl/core/src/display/render/passes/screen.rs @@ -5,7 +5,7 @@ use crate::prelude::*; use crate::display::render::pass; use crate::display::scene::UpdateStatus; use crate::display::symbol::Screen; - +use crate::system::gpu::context::ContextLost; // ======================== @@ -33,9 +33,20 @@ impl Default for ScreenRenderPass { } impl pass::Definition for ScreenRenderPass { - fn run(&mut self, _: &pass::Instance, update_status: UpdateStatus) { + fn instantiate( + &self, + _instance: pass::InstanceInfo, + ) -> Result, ContextLost> { + Ok(Box::new(self.clone())) + } +} + +impl pass::Instance for ScreenRenderPass { + fn run(&mut self, update_status: UpdateStatus) { if update_status.scene_was_dirty { self.screen.render(); } } + + fn resize(&mut self, _width: i32, _height: i32, _pixel_ratio: f32) {} } diff --git a/lib/rust/ensogl/core/src/display/render/passes/symbols.rs b/lib/rust/ensogl/core/src/display/render/passes/symbols.rs index 51952a3db79..07bae26068a 100644 --- a/lib/rust/ensogl/core/src/display/render/passes/symbols.rs +++ b/lib/rust/ensogl/core/src/display/render/passes/symbols.rs @@ -10,6 +10,46 @@ use crate::display::scene::UpdateStatus; use crate::display::symbol::MaskComposer; use crate::display::symbol::OverlayComposer; use crate::display::world; +use crate::system::gpu::context::ContextLost; + + + +// ============================ +// === SymbolsRenderPassDef === +// ============================ + +/// Defines a pass for rendering symbols. See [`SymbolRenderPass`] for pass implementation. +#[derive(Debug, Clone)] +pub struct SymbolsRenderPassDef { + layers: scene::HardcodedLayers, + mask_composer: MaskComposer, + overlay_composer: OverlayComposer, +} + +impl SymbolsRenderPassDef { + /// Constructor. + pub fn new(layers: &scene::HardcodedLayers) -> Self { + let layers = layers.clone_ref(); + let mask_composer = + MaskComposer::new("pass_mask_color", "pass_layer_color", "pass_layer_id"); + let overlay_composer = OverlayComposer::new("pass_layer_color", "pass_layer_id"); + Self { layers, mask_composer, overlay_composer } + } +} + +impl pass::Definition for SymbolsRenderPassDef { + fn instantiate( + &self, + instance: pass::InstanceInfo, + ) -> Result, ContextLost> { + Ok(Box::new(SymbolsRenderPass { + framebuffers: Framebuffers::new(&instance)?, + def: self.clone(), + context: instance.context.clone(), + instance, + })) + } +} @@ -17,114 +57,87 @@ use crate::display::world; // === SymbolsRenderPass === // ========================= -#[derive(Clone, Debug)] -struct Framebuffers { - composed: pass::Framebuffer, - mask: pass::Framebuffer, - layer: pass::Framebuffer, -} - -impl Framebuffers { - fn new(composed: pass::Framebuffer, mask: pass::Framebuffer, layer: pass::Framebuffer) -> Self { - Self { composed, mask, layer } - } -} - /// Pass for rendering all symbols. The results are stored in the 'color' and 'id' outputs. -#[derive(Clone, Debug)] +#[derive(Debug, Deref)] pub struct SymbolsRenderPass { - layers: scene::HardcodedLayers, - framebuffers: Option, - mask_composer: MaskComposer, - overlay_composer: OverlayComposer, + framebuffers: Framebuffers, + #[deref] + def: SymbolsRenderPassDef, + context: Context, + instance: pass::InstanceInfo, } -impl SymbolsRenderPass { - /// Constructor. - pub fn new(layers: &scene::HardcodedLayers) -> Self { - let layers = layers.clone_ref(); - let framebuffers = default(); - let mask_composer = - MaskComposer::new("pass_mask_color", "pass_layer_color", "pass_layer_id"); - let overlay_composer = OverlayComposer::new("pass_layer_color", "pass_layer_id"); - Self { layers, framebuffers, mask_composer, overlay_composer } - } -} - -impl pass::Definition for SymbolsRenderPass { - fn initialize(&mut self, instance: &pass::Instance) { - let rgba = texture::Rgba; - let tex_type = texture::item_type::u8; - let id_params = texture::Parameters { - min_filter: texture::MinFilter::NEAREST, - mag_filter: texture::MagFilter::NEAREST, - ..default() - }; - - let out_color = pass::OutputDefinition::new_rgba("color"); - let out_id = pass::OutputDefinition::new("id", rgba, tex_type, id_params); - let tex_color = instance.new_screen_texture(&out_color); - let tex_id = instance.new_screen_texture(&out_id); - let composed_fb = instance.new_framebuffer(&[&tex_color, &tex_id]); - - let out_mask_color = pass::OutputDefinition::new_rgba("mask_color"); - let out_mask_id = pass::OutputDefinition::new("mask_id", rgba, tex_type, id_params); - let tex_mask_color = instance.new_screen_texture(&out_mask_color); - let tex_mask_id = instance.new_screen_texture(&out_mask_id); - let mask_fb = instance.new_framebuffer(&[&tex_mask_color, &tex_mask_id]); - - let out_layer_color = pass::OutputDefinition::new_rgba("layer_color"); - let out_layer_id = pass::OutputDefinition::new("layer_id", rgba, tex_type, id_params); - let tex_layer_color = instance.new_screen_texture(&out_layer_color); - let tex_layer_id = instance.new_screen_texture(&out_layer_id); - let layer_fb = instance.new_framebuffer(&[&tex_layer_color, &tex_layer_id]); - - self.framebuffers = Some(Framebuffers::new(composed_fb, mask_fb, layer_fb)); - } - - fn run(&mut self, instance: &pass::Instance, update_status: UpdateStatus) { +impl pass::Instance for SymbolsRenderPass { + fn run(&mut self, update_status: UpdateStatus) { if update_status.scene_was_dirty { - let framebuffers = self.framebuffers.as_ref().unwrap(); - - framebuffers.composed.bind(); + self.framebuffers.composed.bind(); let arr = vec![0.0, 0.0, 0.0, 0.0]; - instance.context.clear_bufferfv_with_f32_array(*Context::COLOR, 0, &arr); - instance.context.clear_bufferfv_with_f32_array(*Context::COLOR, 1, &arr); + self.context.clear_bufferfv_with_f32_array(*Context::COLOR, 0, &arr); + self.context.clear_bufferfv_with_f32_array(*Context::COLOR, 1, &arr); let mut scissor_stack = default(); - self.render_layer(instance, &self.layers.root.clone(), &mut scissor_stack, false, None); + self.render_layer(&self.layers.root.clone(), &mut scissor_stack, false, None); if !scissor_stack.is_empty() { warn!( "The scissor stack was not cleaned properly. \ This is an internal bug that may lead to visual artifacts. Please report it." ); } - instance.context.bind_framebuffer(*Context::FRAMEBUFFER, None); + self.context.bind_framebuffer(*Context::FRAMEBUFFER, None); + } + } + + fn resize(&mut self, width: i32, height: i32, pixel_ratio: f32) { + let mut instance = self.instance.clone(); + instance.width = width; + instance.height = height; + instance.pixel_ratio = pixel_ratio; + match Framebuffers::new(&instance) { + Ok(framebuffers) => { + *self = SymbolsRenderPass { + framebuffers, + def: self.def.clone(), + context: self.context.clone(), + instance: self.instance.clone(), + }; + } + Err(ContextLost) => (), } } } impl SymbolsRenderPass { - fn enable_scissor_test(&self, instance: &pass::Instance) { - instance.context.enable(*Context::SCISSOR_TEST); + fn enable_scissor_test(&self) { + self.context.enable(*Context::SCISSOR_TEST); } - fn disable_scissor_test(&self, instance: &pass::Instance) { - instance.context.disable(*Context::SCISSOR_TEST); + fn disable_scissor_test(&self) { + self.context.disable(*Context::SCISSOR_TEST); } fn render_layer( &mut self, - instance: &pass::Instance, layer: &layer::Layer, scissor_stack: &mut Vec, parent_masked: bool, override_blend: Option, ) { + self.try_render_layer(layer, scissor_stack, parent_masked, override_blend); + } + + // Render a layer if we are currently able to render. This should not fail unless the context + // has been lost. + fn try_render_layer( + &mut self, + layer: &layer::Layer, + scissor_stack: &mut Vec, + parent_masked: bool, + override_blend: Option, + ) -> Option<()> { let has_symbols = layer.has_symbols(); if !has_symbols && !layer.has_main_pass_sublayers() { - return; + return Some(()); } let parent_scissor_box = scissor_stack.first().copied(); @@ -135,12 +148,12 @@ impl SymbolsRenderPass { if let Some(scissor_box) = scissor_box { if scissor_box_changed { if first_scissor_usage { - self.enable_scissor_test(instance) + self.enable_scissor_test() } scissor_stack.push(scissor_box); let position = scissor_box.position(); let size = scissor_box.size(); - instance.context.scissor(position.x, position.y, size.x, size.y); + self.context.scissor(position.x, position.y, size.x, size.y); } } @@ -158,53 +171,83 @@ impl SymbolsRenderPass { let zero = [0.0, 0.0, 0.0, 0.0]; if !inverted { - let framebuffers = self.framebuffers.as_ref().unwrap(); - framebuffers.mask.bind(); - instance.context.clear_bufferfv_with_f32_array(*Context::COLOR, 0, &zero); - instance.context.clear_bufferfv_with_f32_array(*Context::COLOR, 1, &zero); - self.render_layer(instance, layer, scissor_stack, was_ever_masked, override_blend); + self.framebuffers.mask.bind(); + self.context.clear_bufferfv_with_f32_array(*Context::COLOR, 0, &zero); + self.context.clear_bufferfv_with_f32_array(*Context::COLOR, 1, &zero); + self.render_layer(layer, scissor_stack, was_ever_masked, override_blend); } - let framebuffers = self.framebuffers.as_ref().unwrap(); - framebuffers.layer.bind(); - instance.context.clear_bufferfv_with_f32_array(*Context::COLOR, 0, &zero); - instance.context.clear_bufferfv_with_f32_array(*Context::COLOR, 1, &zero); + self.framebuffers.layer.bind(); + self.context.clear_bufferfv_with_f32_array(*Context::COLOR, 0, &zero); + self.context.clear_bufferfv_with_f32_array(*Context::COLOR, 1, &zero); } if has_symbols { world::with_context(|t| { t.set_camera(&layer.camera()); let blend_mode = override_blend.unwrap_or_else(|| layer.blend_mode()); - blend_mode.apply_to_context(&instance.context); + blend_mode.apply_to_context(&self.context); t.render_symbols(&layer.symbols()); }); } layer.for_each_sublayer(|layer| { if layer.flags.contains(layer::LayerFlags::MAIN_PASS_VISIBLE) { - self.render_layer(instance, &layer, scissor_stack, was_ever_masked, override_blend); + self.render_layer(&layer, scissor_stack, was_ever_masked, override_blend); } }); if let Some(layer::Mask { layer: mask_layer, inverted: true }) = layer_mask.as_ref() { let blend = layer::BlendMode::ALPHA_CUTOUT; - self.render_layer(instance, mask_layer, scissor_stack, was_ever_masked, Some(blend)); - let framebuffers = self.framebuffers.as_ref().unwrap(); - framebuffers.composed.bind(); - layer::BlendMode::PREMULTIPLIED_ALPHA_OVER.apply_to_context(&instance.context); + self.render_layer(mask_layer, scissor_stack, was_ever_masked, Some(blend)); + self.framebuffers.composed.bind(); + layer::BlendMode::PREMULTIPLIED_ALPHA_OVER.apply_to_context(&self.context); self.overlay_composer.render(); } else if is_masked { - let framebuffers = self.framebuffers.as_ref().unwrap(); - framebuffers.composed.bind(); - layer::BlendMode::PREMULTIPLIED_ALPHA_OVER.apply_to_context(&instance.context); + self.framebuffers.composed.bind(); + layer::BlendMode::PREMULTIPLIED_ALPHA_OVER.apply_to_context(&self.context); self.mask_composer.render(); } if scissor_box_changed { scissor_stack.pop(); if first_scissor_usage { - self.disable_scissor_test(instance) + self.disable_scissor_test() } } + Some(()) + } +} + + +// === Framebuffers === + +#[derive(Debug)] +struct Framebuffers { + composed: pass::Framebuffer, + mask: pass::Framebuffer, + layer: pass::Framebuffer, +} + +impl Framebuffers { + fn new(instance: &pass::InstanceInfo) -> Result { + let rgba = texture::Rgba8; + let tex_type = texture::item_type::u8; + let id_params = texture::Parameters { + min_filter: texture::MinFilter::NEAREST, + mag_filter: texture::MagFilter::NEAREST, + ..default() + }; + let framebuffer = |color, id| { + let out_color = pass::OutputDefinition::new_rgba(color); + let out_id = pass::OutputDefinition::new(id, rgba, tex_type, id_params); + let tex_color = instance.new_screen_texture(&out_color); + let tex_id = instance.new_screen_texture(&out_id); + instance.new_framebuffer(&[&tex_color, &tex_id]) + }; + let composed = framebuffer("color", "id")?; + let mask = framebuffer("mask_color", "mask_id")?; + let layer = framebuffer("layer_color", "layer_id")?; + Ok(Self { composed, mask, layer }) } } diff --git a/lib/rust/ensogl/core/src/display/render/pipeline.rs b/lib/rust/ensogl/core/src/display/render/pipeline.rs index 3df008e25e6..c8900149225 100644 --- a/lib/rust/ensogl/core/src/display/render/pipeline.rs +++ b/lib/rust/ensogl/core/src/display/render/pipeline.rs @@ -10,31 +10,27 @@ use crate::display::render::pass; // === Render Pipeline === // ======================= -shared! { Pipeline /// The pipeline is a set of subsequent passes which can consume and produce data. Please note that /// although passes are run sequentially, their dependency graph (data passing graph) can be DAG. -#[derive(Debug,Default)] -pub struct PipelineModel { - passes: Vec> +#[derive(Debug, Clone)] +pub struct Pipeline { + passes: Rc<[Box]>, } -impl { +impl Pipeline { /// Constructor. - pub fn new() -> Self { - default() + pub fn new(passes: Rc<[Box]>) -> Self { + Self { passes } } /// Getter. - pub fn passes_clone(&self) -> Vec> { - self.passes.clone() - } -}} - -impl Add for Pipeline { - type Output = Self; - fn add(self, pass: Pass) -> Self::Output { - let pass = Box::new(pass); - self.rc.borrow_mut().passes.push(pass); - self + pub fn passes(&self) -> &[Box] { + &self.passes + } +} + +impl Default for Pipeline { + fn default() -> Self { + Self { passes: Rc::new([]) } } } diff --git a/lib/rust/ensogl/core/src/display/scene.rs b/lib/rust/ensogl/core/src/display/scene.rs index 0ea12709cab..56f445d42c1 100644 --- a/lib/rust/ensogl/core/src/display/scene.rs +++ b/lib/rust/ensogl/core/src/display/scene.rs @@ -24,14 +24,15 @@ use crate::display::shape::primitive::glsl; use crate::display::style; use crate::display::style::data::DataMatch; use crate::display::symbol::Symbol; +use crate::display::uniform::UniformScope; use crate::display::world; use crate::frp::io::keyboard as frp_keyboard; use crate::system; use crate::system::gpu::context::profiler::Results; use crate::system::gpu::data::uniform::Uniform; -use crate::system::gpu::data::uniform::UniformScope; use crate::system::gpu::shader; use crate::system::gpu::Context; +use crate::system::gpu::ContextHandler; use crate::system::gpu::ContextLostHandler; use crate::system::web; use crate::system::web::EventListenerHandle; @@ -42,6 +43,7 @@ use std::any::TypeId; use web::HtmlElement; + // ============== // === Export === // ============== @@ -168,7 +170,7 @@ impl Mouse { scene_frp: &Frp, scene_object: &display::object::Instance, root: &web::dom::WithKnownShape, - variables: &UniformScope, + variables: &mut UniformScope, display_mode: &Rc>, ) -> Self { let background = PointerTarget_DEPRECATED::new(); @@ -600,7 +602,7 @@ pub struct Uniforms { impl Uniforms { /// Constructor. - pub fn new(scope: &UniformScope) -> Self { + pub fn new(scope: &mut UniformScope) -> Self { let pixel_ratio = scope.add_or_panic("pixel_ratio", 1.0); Self { pixel_ratio } } @@ -637,17 +639,17 @@ impl Dirty { /// /// Please note that the composer can be empty if the context was either not provided yet or it was /// lost. -#[derive(Clone, CloneRef, Debug)] +#[derive(Debug)] #[allow(missing_docs)] pub struct Renderer { dom: Rc, - variables: UniformScope, - pub pipeline: Rc>, - pub composer: Rc>>, + variables: Rc>, + pub pipeline: CloneCell, + pub composer: RefCell>, } impl Renderer { - fn new(dom: &Rc, variables: &UniformScope) -> Self { + fn new(dom: &Rc, variables: &Rc>) -> Self { let dom = dom.clone_ref(); let variables = variables.clone_ref(); let pipeline = default(); @@ -674,7 +676,7 @@ impl Renderer { let (width, height, pixel_ratio) = self.view_size(); let pipeline = self.pipeline.get(); - render::Composer::new(&pipeline, context, &self.variables, width, height, pixel_ratio) + render::Composer::new(pipeline, context, &self.variables, width, height, pixel_ratio) }); *self.composer.borrow_mut() = composer; } @@ -688,7 +690,7 @@ impl Renderer { /// Reload the composer pipeline. fn update_composer_pipeline(&self) { if let Some(composer) = &mut *self.composer.borrow_mut() { - composer.set_pipeline(&self.pipeline.get()); + composer.set_pipeline(self.pipeline.get()); } } @@ -942,13 +944,13 @@ impl Frp { // === Extension === // ================= -pub trait Extension: 'static + CloneRef { +pub trait Extension: 'static + CloneRef + Any { fn init(scene: &Scene) -> Self; } -#[derive(Clone, CloneRef, Debug, Default)] +#[derive(Debug, Default)] pub struct Extensions { - map: Rc>>>, + map: RefCell>>, } impl Extensions { @@ -981,12 +983,13 @@ pub struct UpdateStatus { // === SceneData === // ================= -#[derive(Debug, display::Object)] +#[derive(Derivative, display::Object)] +#[derivative(Debug)] pub struct SceneData { pub display_object: display::object::Root, pub dom: Rc, pub context: Rc>>, - pub variables: UniformScope, + pub variables: Rc>, pub mouse: Mouse, /// Keyboard that bypasses event propagation and receives all key events. Typically, this is /// appropriate for monitoring the state of modifier keys (which have a logical state @@ -1006,10 +1009,12 @@ pub struct SceneData { pub frp: Frp, pub pointer_position_changed: Rc>, pub shader_compiler: shader::compiler::Controller, - initial_shader_compilation: Rc>, + initial_shader_compilation: Cell, display_mode: Rc>, extensions: Extensions, - disable_context_menu: Rc, + disable_context_menu: EventListenerHandle, + #[derivative(Debug = "ignore")] + on_set_context: RefCell)>>>, } impl SceneData { @@ -1027,12 +1032,18 @@ impl SceneData { let dirty = Dirty::new(on_mut); let layers = world::with_context(|t| t.layers.clone_ref()); let stats = stats.clone(); - let uniforms = Uniforms::new(&variables); + let uniforms = Uniforms::new(&mut variables.borrow_mut()); let renderer = Renderer::new(&dom, &variables); let style_sheet = world::with_context(|t| t.style_sheet.clone_ref()); let frp = Frp::new(&dom.root.shape); - let mouse = Mouse::new(&frp, &display_object, &dom.root, &variables, &display_mode); - let disable_context_menu = Rc::new(web::ignore_context_menu(&dom.root)); + let mouse = Mouse::new( + &frp, + &display_object, + &dom.root, + &mut variables.borrow_mut(), + &display_mode, + ); + let disable_context_menu = web::ignore_context_menu(&dom.root); let global_keyboard = Keyboard::new(&web::window, &display_object); let network = &frp.network; let extensions = Extensions::default(); @@ -1056,6 +1067,7 @@ impl SceneData { let pointer_position_changed = default(); let shader_compiler = default(); let initial_shader_compilation = default(); + let on_set_context = default(); Self { display_object, display_mode, @@ -1078,6 +1090,7 @@ impl SceneData { initial_shader_compilation, extensions, disable_context_menu, + on_set_context, } .init() } @@ -1239,6 +1252,15 @@ impl SceneData { default() } } + + /// Register the given function to be called when the GL context is changed. The callback will + /// be unregistered when the returned handle is dropped. + #[must_use] + pub fn on_set_context)>(&self, f: F) -> ContextHandler { + let handle = Rc::new(f); + self.on_set_context.borrow_mut().push(handle.downgrade()); + ContextHandler::new(handle) + } } @@ -1357,7 +1379,7 @@ impl Scene { pub async fn next_shader_compiler_idle(&self) { if let Some(context) = &*self.context.borrow() { // Ensure the callback will be run if the queue is already idle. - context.shader_compiler.submit_probe_job(); + context.shader_compiler().submit_probe_job(); } else { return; }; @@ -1392,10 +1414,20 @@ impl system::gpu::context::Display for Rc { /// restoration, after the context was lost. See the docs of [`Context`] to learn more. fn set_context(&self, context: Option<&Context>) { let _profiler = profiler::start_objective!(profiler::APP_LIFETIME, "@set_context"); + if context.is_none() { + self.initial_shader_compilation.set(TaskState::Unstarted); + } world::with_context(|t| t.set_context(context)); *self.context.borrow_mut() = context.cloned(); self.dirty.shape.set(); self.renderer.set_context(context); + self.on_set_context.borrow_mut().retain(|handle| match handle.upgrade() { + Some(listener) => { + listener(context); + true + } + None => false, + }) } } diff --git a/lib/rust/ensogl/core/src/display/symbol/gpu.rs b/lib/rust/ensogl/core/src/display/symbol/gpu.rs index e4992b30f40..892cc59282e 100644 --- a/lib/rust/ensogl/core/src/display/symbol/gpu.rs +++ b/lib/rust/ensogl/core/src/display/symbol/gpu.rs @@ -7,10 +7,12 @@ use crate::data::dirty; use crate::debug::stats::Stats; use crate::display; use crate::display::symbol::geometry::primitive::mesh; +use crate::display::texture::TextureUnit; +use crate::display::uniform::UniformScope; use crate::system::gpu; use crate::system::gpu::context::native::ContextOps; +use crate::system::gpu::context::ContextLost; use crate::system::gpu::data::buffer::IsBuffer; -use crate::system::gpu::data::texture::class::TextureOps; use crate::system::gpu::data::uniform::AnyPrimUniform; use crate::system::gpu::data::uniform::AnyPrimUniformOps; use crate::system::gpu::data::uniform::AnyTextureUniform; @@ -19,13 +21,13 @@ use crate::system::gpu::data::uniform::AnyUniform; use enso_shapely::newtype_prim; use enso_shapely::shared2; use shader::Shader; -use shader::WeakShader; use wasm_bindgen::JsValue; use web_sys::WebGlProgram; use web_sys::WebGlUniformLocation; use web_sys::WebGlVertexArrayObject; + // ============== // === Export === // ============== @@ -83,11 +85,9 @@ impl UniformBinding { } } -type TextureUnit = u32; - /// Binds input sampler definition in shader to its location, uniform declaration and texture unit. #[derive(Clone, Debug)] -pub struct TextureBinding { +struct TextureBinding { name: String, location: WebGlUniformLocation, uniform: AnyTextureUniform, @@ -96,7 +96,7 @@ pub struct TextureBinding { impl TextureBinding { /// Create new texture binding. - pub fn new( + fn new( name: Name, location: WebGlUniformLocation, uniform: AnyTextureUniform, @@ -106,14 +106,15 @@ impl TextureBinding { Self { name, location, uniform, texture_unit } } - /// Bind texture to proper texture unit. - pub fn bind_texture_unit(&self, context: &Context) -> TextureBindGuard { - self.uniform.bind_texture_unit(context, self.texture_unit.into()) + /// Bind texture to proper texture unit. This will return `None` if the texture is not currently + /// loaded on the GPU, which can happen if the context was lost and has not been restored yet. + fn bind_texture_unit(&self) -> Option { + self.uniform.texture().map(|texture| texture.bind_texture_unit(self.texture_unit)) } /// Upload uniform value. - pub fn upload_uniform(&self, context: &Context) { - context.uniform1i(Some(&self.location), self.texture_unit as i32); + fn upload_uniform(&self, context: &Context) { + context.uniform1i(Some(&self.location), u32::from(self.texture_unit) as i32); } } @@ -125,25 +126,10 @@ impl TextureBinding { /// A safe wrapper for WebGL VertexArrayObject. It releases the VAO from GPU memory as soon as all /// references to this object are dropped. -#[derive(Clone, Debug, Deref)] -pub struct VertexArrayObject { - rc: Rc, -} - -impl VertexArrayObject { - /// Constructor - pub fn new(context: &Context) -> Self { - let rc = Rc::new(VertexArrayObjectData::new(context)); - Self { rc } - } -} - - -// === Data === - -/// Internal representation for `VertexArrayObject`. +// NOTE: This type must not derive `Clone`, as the resulting shared `vao` would be deleted when +// either instance is dropped. #[derive(Debug)] -pub struct VertexArrayObjectData { +pub struct VertexArrayObject { context: Context, vao: WebGlVertexArrayObject, } @@ -151,19 +137,19 @@ pub struct VertexArrayObjectData { // === Public API === -impl VertexArrayObjectData { - /// Creates a new VAO instance. - pub fn new(context: &Context) -> Self { +impl VertexArrayObject { + /// Creates a new VAO instance, if the context is valid. + pub fn new(context: &Context) -> Result { let context = context.clone(); - let vao = context.create_vertex_array().unwrap(); - Self { context, vao } + let vao = context.create_vertex_array().ok_or(ContextLost)?; + Ok(Self { context, vao }) } } // === Private API === -impl VertexArrayObjectData { +impl VertexArrayObject { fn bind(&self) { self.context.bind_vertex_array(Some(&self.vao)); } @@ -176,9 +162,9 @@ impl VertexArrayObjectData { // === Instances === -impl Drop for VertexArrayObjectData { +impl Drop for VertexArrayObject { fn drop(&mut self) { - self.context.delete_vertex_array(Some(&self.vao)); + self.context.delete_vertex_array(&self.vao); } } @@ -294,7 +280,7 @@ pub type WeakShaderDirty = dirty::WeakSharedBool>; // === Bindings ==== /// All attributes and uniforms bindings of symbol with the associated Vertex Array Object. -#[derive(Clone, Debug, Default)] +#[derive(Debug, Default)] pub struct Bindings { vao: Option, uniforms: Vec, @@ -317,7 +303,8 @@ pub struct Symbol { #[display_object] data: Rc, shader_dirty: ShaderDirty, - shader: Shader, + /// The GL shader. + pub shader: Rc>, } impl Symbol { @@ -333,7 +320,7 @@ impl Symbol { let on_mut2 = on_mut.clone(); let shader_dirty = ShaderDirty::new(Box::new(on_mut)); let shader_on_mut = Box::new(f!(shader_dirty.set())); - let shader = Shader::new(stats, shader_on_mut); + let shader = Rc::new(RefCell::new(Shader::new(stats, shader_on_mut))); let data = Rc::new(SymbolData::new(stats, label, id, global_id_provider, on_mut2)); Self { data, shader_dirty, shader } }) @@ -349,27 +336,25 @@ impl Symbol { pub(crate) fn set_context(&self, context: Option<&Context>) { *self.context.borrow_mut() = context.cloned(); self.surface.set_context(context); - self.shader.set_context(context); + self.shader.borrow_mut().set_context(context); } /// Check dirty flags and update the state accordingly. - pub fn update(&self, global_variables: &UniformScope) { + pub fn update(&self, global_variables: &Rc>) { if self.context.borrow().is_some() { - debug_span!("Updating.").in_scope(|| { - if self.surface_dirty.check() { - self.surface.update(); - self.surface_dirty.unset(); - } - if self.shader_dirty.check() { - let var_bindings = self.discover_variable_bindings(global_variables); - let data = self.data.clone_ref(); - let global_variables = global_variables.clone_ref(); - self.shader.update(var_bindings, move |var_bindings, program| { - data.init_variable_bindings(var_bindings, &global_variables, program) - }); - self.shader_dirty.unset(); - } - }) + if self.surface_dirty.check() { + self.surface.update(); + self.surface_dirty.unset(); + } + if self.shader_dirty.check() { + let var_bindings = self.discover_variable_bindings(&global_variables.borrow()); + let data = self.data.clone_ref(); + let global_variables = Rc::clone(global_variables); + self.shader.borrow_mut().update(var_bindings, move |var_bindings, program| { + data.init_variable_bindings(var_bindings, global_variables, program) + }); + self.shader_dirty.unset(); + } } } @@ -389,7 +374,7 @@ impl Symbol { } let textures = &self.bindings.borrow().textures; - let bound_textures_iter = textures.iter().map(|t| t.bind_texture_unit(context)); + let bound_textures_iter = textures.iter().map(|t| t.bind_texture_unit()); let _textures_keep_alive = bound_textures_iter.collect_vec(); let mode = Context::TRIANGLE_STRIP; @@ -408,6 +393,7 @@ impl Symbol { global_variables: &UniformScope, ) -> Vec { self.shader + .borrow() .collect_variables() .map(|(name, decl)| { let scope = self.lookup_variable(&name, global_variables); @@ -422,7 +408,7 @@ impl Symbol { /// Runs the provided function in a context of active program and active VAO. After the function /// is executed, both program and VAO are bound to None. fn with_program(&self, context: &Context, f: F) { - if let Some(program) = self.shader.native_program().as_ref() { + if let Some(program) = self.shader.borrow().native_program().as_ref() { context.with_program(program, || self.with_vao(|_| f(program))); } } @@ -461,14 +447,6 @@ impl Symbol { pub fn surface(&self) -> &Mesh { &self.surface } - - pub fn shader(&self) -> &Shader { - &self.shader - } - - pub fn variables(&self) -> &UniformScope { - &self.variables - } } @@ -488,7 +466,7 @@ impl From<&Symbol> for SymbolId { pub struct WeakSymbol { data: Weak, shader_dirty: WeakShaderDirty, - shader: WeakShader, + shader: Weak>, } impl WeakElement for WeakSymbol { @@ -525,16 +503,16 @@ impl WeakElement for WeakSymbol { // ================== /// Internal representation of [`Symbol`] -#[derive(Debug, Clone, display::Object)] +#[derive(Debug, display::Object)] #[allow(missing_docs)] pub struct SymbolData { pub label: &'static str, pub id: SymbolId, + pub variables: RefCell, global_id_provider: GlobalInstanceIdProvider, display_object: display::object::Instance, surface: Mesh, surface_dirty: GeometryDirty, - variables: UniformScope, context: RefCell>, bindings: RefCell, stats: SymbolStats, @@ -555,7 +533,7 @@ impl SymbolData { let surface_dirty = GeometryDirty::new(Box::new(on_mut)); let surface_on_mut = Box::new(f!(surface_dirty.set())); let surface = Mesh::new(stats, surface_on_mut); - let variables = UniformScope::new(); + let variables = default(); let bindings = default(); let stats = SymbolStats::new(stats); let context = default(); @@ -612,7 +590,7 @@ impl SymbolData { let name = name.as_ref(); match self.surface.lookup_variable(name) { Some(mesh_scope) => Some(ScopeType::Mesh(mesh_scope)), - _ if self.variables.contains(name) => Some(ScopeType::Symbol), + _ if self.variables.borrow().contains(name) => Some(ScopeType::Symbol), _ if global_variables.contains(name) => Some(ScopeType::Global), _ => None, } @@ -628,7 +606,7 @@ impl SymbolData { fn init_variable_bindings( &self, var_bindings: &[shader::VarBinding], - global_variables: &UniformScope, + global_variables: Rc>, program: &gpu::shader::Program, ) { if let Some(context) = &*self.context.borrow() { @@ -642,8 +620,8 @@ impl SymbolData { JsValue::from_f64(min_texture_units as f64) }); let max_texture_units = max_texture_units.as_f64().unwrap() as u32; - let mut texture_unit_iter = 0..max_texture_units; - self.bindings.borrow_mut().vao = Some(VertexArrayObject::new(context)); + let mut texture_unit_iter = (0..max_texture_units).map(TextureUnit::from); + self.bindings.borrow_mut().vao = VertexArrayObject::new(context).ok(); self.bindings.borrow_mut().uniforms = default(); self.bindings.borrow_mut().textures = default(); context.with_program(&program.native, || { @@ -657,7 +635,7 @@ impl SymbolData { program, binding, &mut texture_unit_iter, - global_variables, + &global_variables.borrow(), ), None => {} } @@ -704,7 +682,7 @@ impl SymbolData { opt_location.map(|location| { let uniform = match &binding.scope { - Some(ScopeType::Symbol) => self.variables.get(name), + Some(ScopeType::Symbol) => self.variables.borrow().get(name), Some(ScopeType::Global) => global_variables.get(name), _ => todo!(), }; diff --git a/lib/rust/ensogl/core/src/display/symbol/gpu/geometry/compound/screen.rs b/lib/rust/ensogl/core/src/display/symbol/gpu/geometry/compound/screen.rs index ec9ba223453..5d86e31104d 100644 --- a/lib/rust/ensogl/core/src/display/symbol/gpu/geometry/compound/screen.rs +++ b/lib/rust/ensogl/core/src/display/symbol/gpu/geometry/compound/screen.rs @@ -3,7 +3,6 @@ //! post-processing effect by applying the previous output to a screen covering geometry. use crate::prelude::*; -use crate::system::gpu::data::types::*; use crate::display::symbol::geometry::RawSprite; use crate::display::symbol::geometry::SpriteSystem; @@ -46,11 +45,6 @@ impl Screen { self.sprite_system.show(); } - /// Local variables used by the screen object. - pub fn variables(&self) -> UniformScope { - self.sprite_system.symbol().variables().clone() - } - /// Render the shape. pub fn render(&self) { self.sprite_system.render() diff --git a/lib/rust/ensogl/core/src/display/symbol/gpu/geometry/compound/sprite.rs b/lib/rust/ensogl/core/src/display/symbol/gpu/geometry/compound/sprite.rs index 1bc50663f86..7bfce5a2b56 100644 --- a/lib/rust/ensogl/core/src/display/symbol/gpu/geometry/compound/sprite.rs +++ b/lib/rust/ensogl/core/src/display/symbol/gpu/geometry/compound/sprite.rs @@ -244,7 +244,7 @@ impl SpriteSystem { let size = instance_scope.add_buffer("size"); let alignment_value = Rc::new(Cell::new(alignment)); let initial_alignment = alignment_value.get().normalized(); - let alignment = symbol.variables().add_or_panic("alignment", initial_alignment); + let alignment = symbol.variables.borrow_mut().add_or_panic("alignment", initial_alignment); stats.inc_sprite_system_count(); @@ -300,12 +300,12 @@ impl SpriteSystem { /// Sets the geometry material for all sprites in this system. pub fn set_geometry_material>(&self, material: M) { - self.symbol.shader().set_geometry_material(material); + self.symbol.shader.borrow_mut().set_geometry_material(material); } /// Sets the surface material for all sprites in this system. pub fn set_material>(&self, material: M) { - self.symbol.shader().set_material(material); + self.symbol.shader.borrow_mut().set_material(material); } } @@ -327,7 +327,7 @@ impl SpriteSystem { } fn init_shader(&self) { - let shader = self.symbol.shader(); + let mut shader = self.symbol.shader.borrow_mut(); let surface_material = Self::default_surface_material(); let geometry_material = Self::default_geometry_material(); shader.set_geometry_material(geometry_material); diff --git a/lib/rust/ensogl/core/src/display/symbol/gpu/geometry/primitive/mesh.rs b/lib/rust/ensogl/core/src/display/symbol/gpu/geometry/primitive/mesh.rs index e97e06a2e79..0dd4c2d341c 100644 --- a/lib/rust/ensogl/core/src/display/symbol/gpu/geometry/primitive/mesh.rs +++ b/lib/rust/ensogl/core/src/display/symbol/gpu/geometry/primitive/mesh.rs @@ -8,7 +8,6 @@ use crate::data::dirty; use crate::debug::stats::Stats; use crate::system::gpu::Context; -use enso_shapely::shared2; use num_enum::IntoPrimitive; @@ -29,24 +28,24 @@ pub use types::*; /// Container for all scopes owned by a mesh. #[derive(Debug)] -pub struct Scopes { +struct Scopes { /// Point Scope. A point is simply a point in space. Points are often assigned with such /// variables as 'position' or 'color'. - pub point: AttributeScope, + point: AttributeScope, /// Vertex Scope. A vertex is a reference to a point. Primitives use vertices to reference /// points. For example, the corners of a polygon, the center of a sphere, or a control vertex /// of a spline curve. Primitives can share points, while vertices are unique to a primitive. - pub vertex: AttributeScope, + vertex: AttributeScope, /// Primitive Scope. Primitives refer to a unit of geometry, lower-level than an object but /// above points. There are several different types of primitives, including polygon faces or /// Bezier/NURBS surfaces. - pub primitive: AttributeScope, + primitive: AttributeScope, /// Instance Scope. Instances are virtual copies of the same geometry. They share point, /// vertex, and primitive variables. - pub instance: AttributeScope, + instance: AttributeScope, } /// A singleton for each of scope types. @@ -88,41 +87,35 @@ macro_rules! update_scopes { // === Mesh === // ============ -// === Definition === - -shared2! { Mesh /// A polygon mesh is a collection of vertices, edges and faces that defines the shape of a /// polyhedral object. Mesh describes the shape of the display element. It consists of several /// scopes containing sets of variables. See the documentation of `Scopes` to learn more. /// /// Please note, that there are other, higher-level scopes defined by other structures, including: /// -/// - Symbol Scope -/// Object refers to the whole geometry with all of its instances. +/// - Symbol Scope Object refers to the whole geometry with all of its instances. /// -/// - Global Scope -/// Global scope is shared by all objects, and it contains some universal global variables, like -/// the current 'time' counter. +/// - Global Scope Global scope is shared by all objects, and it contains some universal global +/// variables, like the current 'time' counter. /// /// Each scope can contain named attributes which can be accessed from within materials. If the same /// name was defined in various scopes, it gets resolved to the var defined in the most specific /// scope. For example, if var 'color' was defined in both 'instance' and 'point' scope, the 'point' /// definition overlaps the other one. #[derive(Debug)] -pub struct MeshData { - scopes : Scopes, - scopes_dirty : ScopesDirty, - stats : Stats, +pub struct Mesh { + scopes: Scopes, + scopes_dirty: ScopesDirty, + stats: Stats, } -impl { +impl Mesh { /// Creates new mesh with attached dirty callback. - pub fn new - (stats:&Stats, on_mut:OnMut) -> Self { + pub fn new(stats: &Stats, on_mut: OnMut) -> Self { stats.inc_mesh_count(); - let stats = stats.clone(); - let scopes_dirty = ScopesDirty::new(Box::new(on_mut)); - let scopes = debug_span!("Initializing.").in_scope(|| { + let stats = stats.clone(); + let scopes_dirty = ScopesDirty::new(Box::new(on_mut)); + let scopes = debug_span!("Initializing.").in_scope(|| { macro_rules! new_scope { ({ $($name:ident),* } { $($uname:ident),* } ) => {$( let status_mod = ScopeType::$uname; let scs_dirty = scopes_dirty.clone_ref(); @@ -130,9 +123,9 @@ impl { let $name = AttributeScope::new(&stats, callback); )*}} new_scope! ({point,vertex,primitive,instance}{Point,Vertex,Primitive,Instance}); - Scopes {point,vertex,primitive,instance} + Scopes { point, vertex, primitive, instance } }); - Self {scopes, scopes_dirty, stats} + Self { scopes, scopes_dirty, stats } } /// Point scope accessor. @@ -156,10 +149,10 @@ impl { } /// Check dirty flags and update the state accordingly. - pub fn update(&mut self) { + pub fn update(&self) { debug_span!("Updating.").in_scope(|| { if self.scopes_dirty.check_all() { - update_scopes!{ + update_scopes! { self.{point,vertex,primitive,instance}{Point,Vertex,Primitive,Instance} } self.scopes_dirty.unset_all() @@ -169,36 +162,43 @@ impl { /// Browses all scopes and finds where a variable was defined. Scopes are browsed in a /// hierarchical order. To learn more about the ordering see the documentation of `Mesh`. - pub fn lookup_variable(&self, name:S) -> Option { + pub fn lookup_variable(&self, name: S) -> Option { let name = name.as_ref(); - if self.scopes.point . contains(name) { Some(ScopeType::Point) } - else if self.scopes.vertex . contains(name) { Some(ScopeType::Vertex) } - else if self.scopes.primitive . contains(name) { Some(ScopeType::Primitive) } - else if self.scopes.instance . contains(name) { Some(ScopeType::Instance) } - else {None} + if self.scopes.point.contains(name) { + Some(ScopeType::Point) + } else if self.scopes.vertex.contains(name) { + Some(ScopeType::Vertex) + } else if self.scopes.primitive.contains(name) { + Some(ScopeType::Primitive) + } else if self.scopes.instance.contains(name) { + Some(ScopeType::Instance) + } else { + None + } } /// Gets reference to scope based on the scope type. - pub fn scope_by_type(&self, scope_type:ScopeType) -> AttributeScope { + pub fn scope_by_type(&self, scope_type: ScopeType) -> AttributeScope { match scope_type { - ScopeType::Point => &self.scopes.point, - ScopeType::Vertex => &self.scopes.vertex, + ScopeType::Point => &self.scopes.point, + ScopeType::Vertex => &self.scopes.vertex, ScopeType::Primitive => &self.scopes.primitive, - ScopeType::Instance => &self.scopes.instance, - }.clone_ref() + ScopeType::Instance => &self.scopes.instance, + } + .clone_ref() } /// Set the GPU context. In most cases, this happens during app initialization or during context /// restoration, after the context was lost. See the docs of [`Context`] to learn more. - pub(crate) fn set_context(&self, context:Option<&Context>) { + pub(crate) fn set_context(&self, context: Option<&Context>) { macro_rules! set_scope_context { ($($name:ident),*) => { $( self.scopes.$name.set_context(context); )* }} - set_scope_context!(point,vertex,primitive,instance); + set_scope_context!(point, vertex, primitive, instance); } -}} +} -impl Drop for MeshData { +impl Drop for Mesh { fn drop(&mut self) { self.stats.dec_mesh_count(); } diff --git a/lib/rust/ensogl/core/src/display/symbol/gpu/registry.rs b/lib/rust/ensogl/core/src/display/symbol/gpu/registry.rs index fffdc840bda..68ef775ab3e 100644 --- a/lib/rust/ensogl/core/src/display/symbol/gpu/registry.rs +++ b/lib/rust/ensogl/core/src/display/symbol/gpu/registry.rs @@ -88,7 +88,7 @@ pub struct SymbolRegistry { view_projection: Uniform>, z_zoom_1: Uniform, pub display_mode: Uniform, - pub variables: UniformScope, + pub variables: Rc>, context: Rc>>, pub stats: Stats, next_id: Rc>, @@ -104,7 +104,7 @@ impl SymbolRegistry { let run_mode = default(); let dirty = Dirty::new(()); let symbols = default(); - let variables = UniformScope::new(); + let mut variables = crate::display::uniform::UniformScope::new(); let view_projection = variables.add_or_panic("view_projection", Matrix4::::identity()); let z_zoom_1 = variables.add_or_panic("z_zoom_1", 1.0); let display_mode = variables.add_or_panic("display_mode", 0); @@ -116,6 +116,7 @@ impl SymbolRegistry { let theme_manager = theme::Manager::from(&style_sheet); style::javascript::expose_to_window(&theme_manager); let layers = scene::HardcodedLayers::new(); + let variables = Rc::new(RefCell::new(variables)); Self { run_mode, symbols, diff --git a/lib/rust/ensogl/core/src/display/symbol/gpu/shader.rs b/lib/rust/ensogl/core/src/display/symbol/gpu/shader.rs index 0c18369086f..701d31d2c45 100644 --- a/lib/rust/ensogl/core/src/display/symbol/gpu/shader.rs +++ b/lib/rust/ensogl/core/src/display/symbol/gpu/shader.rs @@ -15,7 +15,6 @@ use crate::system::gpu::shader::compiler as shader_compiler; use crate::system::gpu::shader::glsl; use crate::system::gpu::Context; -use enso_shapely::shared; use web_sys::WebGlProgram; @@ -54,28 +53,30 @@ impl VarBinding { pub type Dirty = dirty::SharedBool>; -shared! { Shader /// Shader keeps track of a shader and related WebGL Program. #[derive(Debug)] -pub struct ShaderData { - context : Option, - geometry_material : Material, - surface_material : Material, - program : Rc>>, - shader_compiler_job : Option, - dirty : Dirty, - stats : Stats, - profiler : Option, +pub struct Shader { + context: Option, + geometry_material: Material, + surface_material: Material, + program: Rc>>, + shader_compiler_job: Option, + dirty: Dirty, + stats: Stats, + profiler: Option, } -impl { +impl Shader { /// Set the GPU context. In most cases, this happens during app initialization or during context /// restoration, after the context was lost. See the docs of [`Context`] to learn more. #[profile(Debug)] - pub fn set_context(&mut self, context:Option<&Context>) { + pub fn set_context(&mut self, context: Option<&Context>) { if context.is_some() { self.dirty.set(); self.profiler.get_or_insert_with(new_profiler); + } else { + self.program.take(); + self.shader_compiler_job.take(); } self.context = context.cloned(); } @@ -89,21 +90,21 @@ impl { } #[profile(Detail)] - pub fn set_geometry_material>(&mut self, material:M) { + pub fn set_geometry_material>(&mut self, material: M) { self.geometry_material = material.into(); self.dirty.set(); self.profiler.get_or_insert_with(new_profiler); } #[profile(Detail)] - pub fn set_material>(&mut self, material:M) { + pub fn set_material>(&mut self, material: M) { self.surface_material = material.into(); self.dirty.set(); self.profiler.get_or_insert_with(new_profiler); } /// Creates new shader with attached callback. - pub fn new(stats:&Stats, on_mut:OnMut) -> Self { + pub fn new(stats: &Stats, on_mut: OnMut) -> Self { stats.inc_shader_count(); let context = default(); let geometry_material = default(); @@ -112,61 +113,74 @@ impl { let shader_compiler_job = default(); let dirty = Dirty::new(Box::new(on_mut)); let stats = stats.clone_ref(); - let profiler = None; + let profiler = default(); Self { - context, geometry_material, surface_material, program, shader_compiler_job, dirty, - stats, profiler + context, + geometry_material, + surface_material, + program, + shader_compiler_job, + dirty, + stats, + profiler, } } /// Get the shader code in GLSL 310 format. The shader parameters will not be bound to any /// particular mesh and thus this code can be used for optimization purposes only. pub fn abstract_shader_code_in_glsl_310(&self) -> crate::system::gpu::shader::Code { - let bindings = self.collect_variables().map(|(name, decl)| { - let scope = if decl.tp.uniform_or_function_parameter_only() { - ScopeType::Global - } else { - ScopeType::Mesh(crate::display::symbol::geometry::primitive::mesh::ScopeType::Instance) - }; - VarBinding::new(name, decl, Some(scope)) - }).collect_vec(); + let bindings = self + .collect_variables() + .map(|(name, decl)| { + let scope = if decl.tp.uniform_or_function_parameter_only() { + ScopeType::Global + } else { + ScopeType::Mesh( + crate::display::symbol::geometry::primitive::mesh::ScopeType::Instance, + ) + }; + VarBinding::new(name, decl, Some(scope)) + }) + .collect_vec(); self.gen_gpu_code(glsl::Version::V310, &bindings) } /// Generate the final GLSL code based on the provided bindings. pub fn gen_gpu_code( - &self, - glsl_version: glsl::Version, - bindings:&[VarBinding] - ) -> crate::system::gpu::shader::Code { + &self, + glsl_version: glsl::Version, + bindings: &[VarBinding], + ) -> crate::system::gpu::shader::Code { debug_span!("Generating GPU code.").in_scope(|| { let mut shader_cfg = builder::ShaderConfig::new(glsl_version); let mut shader_builder = builder::ShaderBuilder::new(glsl_version); for binding in bindings { let name = &binding.name; - let tp = &binding.decl.tp; + let tp = &binding.decl.tp; match binding.scope { None => { - warn!("Default shader values are not implemented yet. \ - This will cause visual glitches."); - shader_cfg.add_uniform(name,tp); - }, - Some(scope_type) => match scope_type { - ScopeType::Symbol => shader_cfg.add_uniform (name,tp), - ScopeType::Global => shader_cfg.add_uniform (name,tp), - _ => shader_cfg.add_attribute (name,tp), + warn!( + "Default shader values are not implemented yet. \ + This will cause visual glitches." + ); + shader_cfg.add_uniform(name, tp); } + Some(scope_type) => match scope_type { + ScopeType::Symbol => shader_cfg.add_uniform(name, tp), + ScopeType::Global => shader_cfg.add_uniform(name, tp), + _ => shader_cfg.add_attribute(name, tp), + }, } } - self.geometry_material.outputs().iter().for_each(|(name,decl)|{ - shader_cfg.add_shared_attribute(name,&decl.tp); + self.geometry_material.outputs().iter().for_each(|(name, decl)| { + shader_cfg.add_shared_attribute(name, &decl.tp); }); shader_cfg.add_output("color", glsl::PrimType::Vec4); - self.surface_material.outputs().iter().for_each(|(name,decl)|{ - shader_cfg.add_output(name,&decl.tp); + self.surface_material.outputs().iter().for_each(|(name, decl)| { + shader_cfg.add_output(name, &decl.tp); }); let vertex_code = self.geometry_material.code().clone(); @@ -177,46 +191,49 @@ impl { } /// Check dirty flags and update the state accordingly. - pub fn update - (&mut self, bindings:Vec, on_ready:F) { - debug_span!("Updating.").in_scope(|| { - if let Some(context) = &self.context { - if self.dirty.check_all() { - self.stats.inc_shader_compile_count(); - let code = self.gen_gpu_code(glsl::Version::V300, &bindings); - - *self.program.borrow_mut() = None; - let program = self.program.clone_ref(); - let profiler = self.profiler.take().unwrap_or_else(new_profiler); - let handler = context.shader_compiler.submit(code, profiler, move |prog| { - on_ready(&bindings, &prog); - *program.borrow_mut() = Some(prog); - }); - self.cancel_previous_shader_compiler_job_and_use_new_one(handler); - self.dirty.unset_all(); - } + pub fn update( + &mut self, + bindings: Vec, + on_ready: F, + ) { + if let Some(context) = self.context.as_ref() { + if self.dirty.check_all() { + self.stats.inc_shader_compile_count(); + let code = self.gen_gpu_code(glsl::Version::V300, &bindings); + *self.program.borrow_mut() = None; + let program = self.program.clone_ref(); + let profiler = self.profiler.take().unwrap_or_else(new_profiler); + let handler = context.shader_compiler().submit(code, profiler, move |prog| { + on_ready(&bindings, &prog); + *program.borrow_mut() = Some(prog); + }); + self.cancel_previous_shader_compiler_job_and_use_new_one(handler); + self.dirty.unset_all(); } - }) + } } - fn cancel_previous_shader_compiler_job_and_use_new_one - (&mut self, handler: shader_compiler::JobHandle) { + fn cancel_previous_shader_compiler_job_and_use_new_one( + &mut self, + handler: shader_compiler::JobHandle, + ) { // Dropping the previous handler. - self.shader_compiler_job = Some(handler); + self.shader_compiler_job.replace(handler); } /// Traverses the shader definition and collects all attribute names. - pub fn collect_variables(&self) -> impl Iterator { + pub fn collect_variables(&self) -> impl Iterator { let geometry_inputs = self.geometry_material.inputs().iter(); let surface_inputs = self.surface_material.inputs().iter(); - geometry_inputs.chain(surface_inputs) + geometry_inputs + .chain(surface_inputs) .map(|(name, declaration)| (name.clone(), declaration.clone())) .collect_vec() .into_iter() } -}} +} -impl Drop for ShaderData { +impl Drop for Shader { fn drop(&mut self) { self.stats.dec_shader_count(); } diff --git a/lib/rust/ensogl/core/src/display/world.rs b/lib/rust/ensogl/core/src/display/world.rs index a8117ddd768..2aad127ca67 100644 --- a/lib/rust/ensogl/core/src/display/world.rs +++ b/lib/rust/ensogl/core/src/display/world.rs @@ -15,15 +15,15 @@ use crate::debug; use crate::debug::stats::Stats; use crate::display; use crate::display::garbage; -use crate::display::render; -use crate::display::render::cache_shapes::CacheShapesPass; -use crate::display::render::passes::SymbolsRenderPass; +use crate::display::render::cache_shapes::CacheShapesPassDef; +use crate::display::render::passes::SymbolsRenderPassDef; use crate::display::scene::DomPath; use crate::display::scene::Scene; use crate::display::scene::UpdateStatus; use crate::display::shape::primitive::glsl; use crate::display::symbol::registry::RunMode; use crate::display::symbol::registry::SymbolRegistry; +use crate::display::uniform::UniformScope; use crate::system::gpu::context::profiler::Results; use crate::system::gpu::shader; use crate::system::web; @@ -34,6 +34,7 @@ use web::JsCast; use web::JsValue; + // ============== // === Export === // ============== @@ -242,7 +243,7 @@ pub struct Uniforms { impl Uniforms { /// Constructor. - pub fn new(scope: &UniformScope) -> Self { + pub fn new(scope: &mut UniformScope) -> Self { let time = scope.add_or_panic("time", 0.0); Self { time } } @@ -436,6 +437,7 @@ pub struct WorldData { pixel_read_pass_threshold: Rc>>>, slow_frame_count: Rc>, fast_frame_count: Rc>, + restore_context: Rc>>, } impl WorldData { @@ -449,7 +451,7 @@ impl WorldData { let on_change = f!(scene_dirty.set()); let display_mode = Rc::>::default(); let default_scene = Scene::new(&stats, on_change, &display_mode); - let uniforms = Uniforms::new(&default_scene.variables); + let uniforms = Uniforms::new(&mut default_scene.variables.borrow_mut()); let debug_hotkeys_handle = default(); let garbage_collector = default(); let themes = with_context(|t| t.theme_manager.clone_ref()); @@ -459,6 +461,7 @@ impl WorldData { let pixel_read_pass_threshold = default(); let slow_frame_count = default(); let fast_frame_count = default(); + let restore_context = default(); Self { frp, @@ -476,6 +479,7 @@ impl WorldData { pixel_read_pass_threshold, slow_frame_count, fast_frame_count, + restore_context, } .init() } @@ -491,6 +495,7 @@ impl WorldData { let display_mode = self.display_mode.clone_ref(); let display_mode_uniform = with_context(|ctx| ctx.display_mode.clone_ref()); let emit_measurements_handle = self.emit_measurements_handle.clone_ref(); + let restore_context = self.restore_context.clone(); let closure: Closure = Closure::new(move |val: JsValue| { let event = val.unchecked_into::(); let digit_prefix = "Digit"; @@ -515,6 +520,19 @@ impl WorldData { enso_debug_api::LifecycleController::new().map(|api| api.quit()); } else if key == "KeyG" { enso_debug_api::open_gpu_debug_info(); + } else if key == "KeyX" && event.shift_key() { + if let Some(restore) = restore_context.take() { + restore.restore_context(); + } else if let Some(context) = scene().context.borrow().as_ref() { + if let Some(lose_context) = context.extensions.webgl_lose_context.as_ref() { + restore_context.borrow_mut().replace(lose_context.clone()); + lose_context.lose_context(); + } else { + error!("Could not lose context: Missing extension."); + } + } else { + error!("Could not lose context: Context lost."); + } } else if key.starts_with(digit_prefix) { let code_value = key.trim_start_matches(digit_prefix).parse().unwrap_or(0); if let Some(mode) = glsl::codes::DisplayModes::from_value(code_value) { @@ -532,21 +550,29 @@ impl WorldData { } fn init_composer(&self) { + self.default_scene.renderer.set_pipeline(Pipeline::new(Rc::new([ + Box::new(SymbolsRenderPassDef::new(&self.default_scene.layers)), + Box::new(ScreenRenderPass::new()), + self.init_pixel_read_pass(), + Box::new(CacheShapesPassDef::new()), + ]))); + } + + fn init_pixel_read_pass(&self) -> Box { let pointer_target_encoded = self.default_scene.mouse.pointer_target_encoded.clone_ref(); let garbage_collector = &self.garbage_collector; - let mut pixel_read_pass = PixelReadPass::::new(&self.default_scene.mouse.position); - pixel_read_pass.set_callback(f!([garbage_collector](v) { + let on_read = f!([garbage_collector](v: Vec) { pointer_target_encoded.set(Vector4::from_iterator(v.iter().map(|value| *value as u32))); garbage_collector.pixel_updated(); - })); - pixel_read_pass.set_sync_callback(f!(garbage_collector.pixel_synced())); + }); + let on_sync = f!(garbage_collector.pixel_synced()); + let pixel_read_pass = PixelReadPassDef::::new( + self.default_scene.mouse.position.clone(), + on_read, + on_sync, + ); *self.pixel_read_pass_threshold.borrow_mut() = pixel_read_pass.get_threshold().downgrade(); - let pipeline = render::Pipeline::new() - .add(SymbolsRenderPass::new(&self.default_scene.layers)) - .add(ScreenRenderPass::new()) - .add(pixel_read_pass) - .add(CacheShapesPass::new()); - self.default_scene.renderer.set_pipeline(pipeline); + Box::new(pixel_read_pass) } fn update_stats(&self, _time: Duration, gpu_perf_results: Option>) { diff --git a/lib/rust/ensogl/core/src/gui/component.rs b/lib/rust/ensogl/core/src/gui/component.rs index 3a5bab46ad5..d79575bba56 100644 --- a/lib/rust/ensogl/core/src/gui/component.rs +++ b/lib/rust/ensogl/core/src/gui/component.rs @@ -36,7 +36,7 @@ pub trait AnyShapeView: display::Object { /// Get the shape's shader code in GLSL 330 format. The shader parameters will not be bound to /// any particular mesh and thus this code can be used for optimization purposes only. fn abstract_shader_code_in_glsl_310(&self) -> crate::system::gpu::shader::Code { - self.sprite().symbol.shader().abstract_shader_code_in_glsl_310() + self.sprite().symbol.shader.borrow().abstract_shader_code_in_glsl_310() } /// The shape definition path (file:line:column). fn definition_path(&self) -> &'static str; diff --git a/lib/rust/ensogl/core/src/system/gpu.rs b/lib/rust/ensogl/core/src/system/gpu.rs index 0d9f63dced4..6fea134da13 100644 --- a/lib/rust/ensogl/core/src/system/gpu.rs +++ b/lib/rust/ensogl/core/src/system/gpu.rs @@ -14,6 +14,7 @@ pub mod shader; /// Common types. pub mod types { pub use super::context::Context; + pub use super::context::ContextHandler; pub use super::context::ContextLostHandler; pub use super::data::types::*; pub use super::shader::types::*; diff --git a/lib/rust/ensogl/core/src/system/gpu/context.rs b/lib/rust/ensogl/core/src/system/gpu/context.rs index eaf4e298893..482e4bc0950 100644 --- a/lib/rust/ensogl/core/src/system/gpu/context.rs +++ b/lib/rust/ensogl/core/src/system/gpu/context.rs @@ -64,9 +64,10 @@ pub struct Context { #[allow(missing_docs)] pub struct ContextData { #[deref] - native: native::ContextWithExtensions, - pub profiler: profiler::Profiler, - pub shader_compiler: shader::Compiler, + native: native::ContextWithExtensions, + pub profiler: profiler::Profiler, + shader_compiler: shader::Compiler, + id: u32, } impl Context { @@ -77,10 +78,17 @@ impl Context { impl ContextData { fn from_native(native: WebGl2RenderingContext) -> Self { + static NEXT_CONTEXT_ID: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0); + let id = NEXT_CONTEXT_ID.fetch_add(1, std::sync::atomic::Ordering::Release); let native = native::ContextWithExtensions::from_native(native); let profiler = profiler::Profiler::new(&native); let shader_compiler = shader::Compiler::new(&native); - Self { native, profiler, shader_compiler } + Self { native, profiler, shader_compiler, id } + } + + /// Returns the current context's shader compiler. + pub fn shader_compiler(&self) -> &shader::Compiler { + &self.shader_compiler } } @@ -90,8 +98,15 @@ impl ContextData { /// extension plans. pub type DeviceContextHandler = web::HtmlCanvasElement; + +// === Context loss === + +/// Error type returned to indicate that an operation failed due to context loss. +#[derive(Debug, Copy, Clone)] +pub struct ContextLost; + /// Handler for closures taking care of context restoration. After dropping this handler and losing -/// the context, the context will not be restored automaticaly. +/// the context, the context will not be restored automatically. #[derive(Debug)] pub struct ContextLostHandler { on_lost: web::EventListenerHandle, @@ -99,6 +114,58 @@ pub struct ContextLostHandler { } +// === Wrappers === + +#[allow(missing_docs)] +impl ContextData { + pub fn create_framebuffer(&self) -> Result { + self.native.create_framebuffer().ok_or(ContextLost) + } + + pub fn create_buffer(&self) -> Result { + self.native.create_buffer().ok_or(ContextLost) + } + + pub fn create_texture(&self) -> Result { + self.native.create_texture().ok_or(ContextLost) + } + + pub fn delete_texture(&self, texture: &web_sys::WebGlTexture) { + // Avoid WebGL errors if we delete something bound to a context that has been lost. + if self.native.is_texture(Some(texture)) { + self.native.delete_texture(Some(texture)); + } + } + + pub fn delete_vertex_array(&self, texture: &web_sys::WebGlVertexArrayObject) { + // Avoid WebGL errors if we delete something bound to a context that has been lost. + if self.native.is_vertex_array(Some(texture)) { + self.native.delete_vertex_array(Some(texture)); + } + } +} + + +// === Context Handler === + +/// Shared handle of a function that handles context loss and restoration. +#[derive(Clone, CloneRef)] +pub struct ContextHandler(Rc)>); + +impl ContextHandler { + /// Wrap the handle. + pub fn new(rc: Rc)>) -> Self { + Self(rc) + } +} + +impl Debug for ContextHandler { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("ContextHandler") + } +} + + // =============== // === Display === @@ -147,16 +214,18 @@ pub fn init_webgl_2_context( match opt_context { None => Err(UnsupportedStandard("WebGL 2.0")), Some(native) => { - let context = Context::from_native(native); - type Handler = web::JsEventHandler; + let context = Context::from_native(native.clone()); display.set_context(Some(&context)); - let lost: Handler = Closure::new(f_!([display] + type Handler = web::JsEventHandler; + let lost: Handler = Closure::new(f!([display] (e: web_sys::Event) warn!("Lost the WebGL context."); - display.set_context(None) + display.set_context(None); + e.prevent_default(); )); - let restored: Handler = Closure::new(f_!([display] - warn!("Trying to restore the WebGL context."); - display.set_context(Some(&context)) + let restored: Handler = Closure::new(f_!([display, native] + warn!("Restoring the WebGL context."); + let new_context = Context::from_native(native.clone()); + display.set_context(Some(&new_context)) )); let on_lost = web::add_event_listener(hdc, "webglcontextlost", lost); let on_restored = web::add_event_listener(hdc, "webglcontextrestored", restored); diff --git a/lib/rust/ensogl/core/src/system/gpu/context/extension.rs b/lib/rust/ensogl/core/src/system/gpu/context/extension.rs index 314a2eaee6d..7a83118abee 100644 --- a/lib/rust/ensogl/core/src/system/gpu/context/extension.rs +++ b/lib/rust/ensogl/core/src/system/gpu/context/extension.rs @@ -36,6 +36,7 @@ impl Extensions { pub struct ExtensionsData { pub khr_parallel_shader_compile: Option, pub ext_disjoint_timer_query_webgl2: Option, + pub webgl_lose_context: Option, } impl ExtensionsData { @@ -43,7 +44,8 @@ impl ExtensionsData { fn init(context: &WebGl2RenderingContext) -> Self { let khr_parallel_shader_compile = KhrParallelShaderCompile::try_init(context); let ext_disjoint_timer_query_webgl2 = ExtDisjointTimerQueryWebgl2::try_init(context); - Self { khr_parallel_shader_compile, ext_disjoint_timer_query_webgl2 } + let webgl_lose_context = WebglLoseContext::try_init(context); + Self { khr_parallel_shader_compile, ext_disjoint_timer_query_webgl2, webgl_lose_context } } } @@ -164,3 +166,36 @@ impl ExtDisjointTimerQueryWebgl2 { } } } + + + +// ======================== +// === WebglLoseContext === +// ======================== + +/// Supports losing the WebGL Context. +/// See: [https://registry.khronos.org/webgl/extensions/WEBGL_lose_context] +#[derive(Debug, Clone)] +pub struct WebglLoseContext { + ext: web_sys::WebglLoseContext, +} + +impl WebglLoseContext { + /// Try to obtain the extension. + pub fn try_init(context: &WebGl2RenderingContext) -> Option { + let ext = context.get_extension("WEBGL_lose_context").ok()??; + let ext = (*ext).clone().into(); + Some(Self { ext }) + } + + /// Lose the WebGL context. This can be useful for testing, or to eagerly release resources. + pub fn lose_context(&self) { + self.ext.lose_context(); + } + + /// Restore the WebGL context. This will only succeed if the context was lost by + /// [`lose_context`]. + pub fn restore_context(&self) { + self.ext.restore_context(); + } +} diff --git a/lib/rust/ensogl/core/src/system/gpu/context/profiler.rs b/lib/rust/ensogl/core/src/system/gpu/context/profiler.rs index a40741a42d4..4125454d205 100644 --- a/lib/rust/ensogl/core/src/system/gpu/context/profiler.rs +++ b/lib/rust/ensogl/core/src/system/gpu/context/profiler.rs @@ -107,14 +107,15 @@ impl InitializedProfiler { let available_enum = WebGl2RenderingContext::QUERY_RESULT_AVAILABLE; let disjoint_enum = self.ext.gpu_disjoint_ext; let available = self.context.get_query_parameter(query, available_enum); - let disjoint = self.context.get_parameter(*disjoint_enum).unwrap(); - let available = available.as_bool().unwrap(); - let disjoint = disjoint.as_bool().unwrap(); + let disjoint = self.context.get_parameter(*disjoint_enum); + let available = available.as_bool().unwrap_or_default(); + let disjoint = + disjoint.ok().and_then(|disjoint| disjoint.as_bool()).unwrap_or_default(); let ready = available && !disjoint; if ready { let query_result_enum = WebGl2RenderingContext::QUERY_RESULT; let result = self.context.get_query_parameter(query, query_result_enum); - let result = result.as_f64().unwrap() / 1_000_000.0; + let result = result.as_f64().unwrap_or_default() / 1_000_000.0; target.results.push_back(result); self.context.delete_query(Some(query)); target.queue.pop_front(); @@ -128,11 +129,17 @@ impl InitializedProfiler { assert!(!self.assertions.during_measurement.get()); self.assertions.during_measurement.set(true); self.assertions.measurements_per_frame.modify(|t| *t += 1); - let query = self.context.create_query().unwrap(); - self.context.begin_query(*self.ext.time_elapsed_ext, &query); - let result = f(); - self.context.end_query(*self.ext.time_elapsed_ext); - target.queue.push_back(query); + let result = if let Some(query) = self.context.create_query() { + self.context.begin_query(*self.ext.time_elapsed_ext, &query); + let result = f(); + self.context.end_query(*self.ext.time_elapsed_ext); + target.queue.push_back(query); + result + } else { + // The query can fail due to context loss. In that case, run the function without timing + // it. + f() + }; self.assertions.during_measurement.set(false); result } diff --git a/lib/rust/ensogl/core/src/system/gpu/data/buffer.rs b/lib/rust/ensogl/core/src/system/gpu/data/buffer.rs index a058402f1ca..3c71cbb2cf6 100644 --- a/lib/rust/ensogl/core/src/system/gpu/data/buffer.rs +++ b/lib/rust/ensogl/core/src/system/gpu/data/buffer.rs @@ -10,6 +10,7 @@ use crate::control::callback; use crate::data::dirty; use crate::data::seq::observable::Observable; use crate::debug::stats::Stats; +use crate::system::gpu::context::ContextLost; use crate::system::gpu::data::attribute; use crate::system::gpu::data::attribute::Attribute; use crate::system::gpu::data::buffer::item::JsBufferView; @@ -73,10 +74,10 @@ pub struct GlData { impl GlData { /// Constructor. - pub fn new(context: &Context) -> Self { - let context = context.clone(); - let buffer = create_gl_buffer(&context); - Self { context, buffer } + pub fn new(context: Context) -> Result { + let buffer = context.create_buffer()?; + let context = context; + Ok(Self { context, buffer }) } } @@ -269,9 +270,9 @@ impl { /// Set the GPU context. In most cases, this happens during app initialization or during context /// restoration, after the context was lost. See the docs of [`Context`] to learn more. pub(crate) fn set_context(&mut self, context:Option<&Context>) { - self.gl = context.map(|ctx| { + self.gl = context.and_then(|ctx| { self.resize_dirty.set(); - GlData::new(ctx) + GlData::new(ctx.clone()).ok() }); } }} @@ -414,14 +415,6 @@ impl Drop for BufferData { } -// === Utils === - -fn create_gl_buffer(context: &Context) -> WebGlBuffer { - let buffer = context.create_buffer(); - buffer.ok_or("Failed to create WebGL buffer.").unwrap() -} - - // ================= // === AnyBuffer === diff --git a/lib/rust/ensogl/core/src/system/gpu/data/texture.rs b/lib/rust/ensogl/core/src/system/gpu/data/texture.rs index 877b0429aea..b741b0c89d9 100644 --- a/lib/rust/ensogl/core/src/system/gpu/data/texture.rs +++ b/lib/rust/ensogl/core/src/system/gpu/data/texture.rs @@ -2,22 +2,366 @@ //! Follow the link to learn more about many assumptions this module was built upon: //! https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/texImage2D +use crate::prelude::*; +use crate::system::gpu::data::gl_enum::traits::*; +use crate::system::gpu::data::gl_enum::*; + +use crate::system::gpu::context::ContextLost; +use crate::system::gpu::data::buffer::item::JsBufferViewArr; +use crate::system::gpu::Context; + +use web_sys::WebGlTexture; + + // ============== // === Export === // ============== -pub mod class; -pub mod storage; pub mod types; -pub use class::*; -pub use storage::*; pub use types::*; - /// Provides smart scope for item types. pub mod item_type { pub use super::types::item_type::AnyItemType::*; } + + + +// =============== +// === Texture === +// =============== + +/// Texture bound to a GL context. +#[derive(Debug)] +pub struct Texture { + width: i32, + height: i32, + layers: i32, + gl_texture: WebGlTexture, + context: Context, + item_type: AnyItemType, + internal_format: AnyInternalFormat, +} + +impl Texture { + /// Allocate a texture on the GPU, and set its parameters. + pub fn new( + context: &Context, + internal_format: AnyInternalFormat, + item_type: AnyItemType, + width: i32, + height: i32, + layers: i32, + parameters: Parameters, + ) -> Result { + let context = context.clone(); + let gl_texture = context.create_texture()?; + let this = Self { width, height, layers, gl_texture, context, item_type, internal_format }; + this.init(parameters); + Ok(this) + } + + /// Returns the texture target. + pub fn target(&self) -> GlEnum { + match self.layers { + 0 => Context::TEXTURE_2D, + _ => Context::TEXTURE_2D_ARRAY, + } + } + + /// Returns the width, in pixels. + pub fn width(&self) -> i32 { + self.width + } + + /// Returns the height, in pixels. + pub fn height(&self) -> i32 { + self.height + } + + /// Returns the number of layers. + pub fn layers(&self) -> i32 { + self.layers + } + + /// Reloads gpu texture with data from given slice. + pub fn reload_with_content(&self, data: &[T]) { + let target = self.target(); + let level = 0; + let (xoffset, yoffset, zoffset) = default(); + let format = self.internal_format.format().to_gl_enum().into(); + let elem_type = self.item_type.to_gl_enum().into(); + let data: &[u8] = bytemuck::cast_slice(data); + self.context.bind_texture(*target, Some(&self.gl_texture)); + let error = match self.layers { + 0 => self + .context + .tex_sub_image_2d_with_i32_and_i32_and_u32_and_type_and_opt_u8_array( + *target, + level, + xoffset, + yoffset, + self.width, + self.height, + format, + elem_type, + Some(data), + ) + .err(), + _ => self + .context + .tex_sub_image_3d_with_opt_u8_array( + *target, + level, + xoffset, + yoffset, + zoffset, + self.width, + self.height, + self.layers, + format, + elem_type, + Some(data), + ) + .err(), + }; + if let Some(error) = error { + if !self.context.is_context_lost() { + error!("Error in `texSubImage`: {error:?}."); + } + } + } + + /// Bind this texture to the specified texture unit on the GPU. + pub fn bind_texture_unit(&self, unit: TextureUnit) -> TextureBindGuard { + let context = self.context.clone(); + let target = self.target(); + context.active_texture(*Context::TEXTURE0 + unit.to::()); + context.bind_texture(*target, Some(&self.gl_texture)); + context.active_texture(*Context::TEXTURE0); + TextureBindGuard { context, target, unit } + } + + /// Access the raw WebGL texture object. + pub fn as_gl_texture(&self) -> &WebGlTexture { + &self.gl_texture + } + + /// Get the format of the texture. + pub fn get_format(&self) -> AnyFormat { + self.internal_format.format() + } + + /// Get the texture's item type. + pub fn get_item_type(&self) -> AnyItemType { + self.item_type + } +} + + +// === Internal API === + +impl Texture { + /// Allocate GPU memory for the texture, and apply the specified parameters. + fn init(&self, parameters: Parameters) { + let levels = 1; + let internal_format = self.internal_format.to_gl_enum().into(); + let target = self.target(); + self.context.bind_texture(*target, Some(&self.gl_texture)); + match self.layers { + 0 => { + self.context.tex_storage_2d( + *target, + levels, + internal_format, + self.width, + self.height, + ); + } + _ => { + self.context.tex_storage_3d( + *target, + levels, + internal_format, + self.width, + self.height, + self.layers, + ); + } + } + parameters.apply_parameters(&self.context, self.target()); + } +} + +impl Drop for Texture { + fn drop(&mut self) { + self.context.delete_texture(&self.gl_texture); + } +} + + + +// =================== +// === TextureUnit === +// =================== + +/// A texture unit representation in WebGl. +#[derive(Copy, Clone, Debug, Display, From, Into)] +pub struct TextureUnit(u32); + + + +// ======================== +// === TextureBindGuard === +// ======================== + +/// Guard which unbinds texture in specific texture unit on drop. +#[derive(Debug)] +pub struct TextureBindGuard { + context: Context, + target: GlEnum, + unit: TextureUnit, +} + +impl Drop for TextureBindGuard { + fn drop(&mut self) { + self.context.active_texture(*Context::TEXTURE0 + self.unit.to::()); + self.context.bind_texture(*self.target, None); + self.context.active_texture(*Context::TEXTURE0); + } +} + + + +// ================== +// === Parameters === +// ================== + +/// Helper struct to specify texture parameters that need to be set when binding a texture. +/// +/// The essential parameters that need to be set are about how the texture will be sampled, i.e., +/// how the values of the texture are interpolated at various resolutions, and how out of bounds +/// samples are handled. +/// +/// For more background see: +/// https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/texParameter +#[derive(Copy, Clone, Debug, Default)] +pub struct Parameters { + /// Specifies the setting for the texture minification filter (`Context::TEXTURE_MIN_FILTER`). + pub min_filter: MinFilter, + /// Specifies the setting for the texture magnification filter (`Context::TEXTURE_MAG_FILTER`). + pub mag_filter: MagFilter, + /// Specifies the setting for the wrapping function for texture coordinate s + /// (`Context::TEXTURE_WRAP_S`). + pub wrap_s: Wrap, + /// Specifies the setting for the wrapping function for texture coordinate t + /// (`Context::TEXTURE_WRAP_T`). + pub wrap_t: Wrap, +} + +impl Parameters { + /// Applies the context parameters in the given context. + pub fn apply_parameters(self, context: &Context, target: GlEnum) { + context.tex_parameteri(*target, *Context::TEXTURE_MIN_FILTER, *self.min_filter as i32); + context.tex_parameteri(*target, *Context::TEXTURE_MAG_FILTER, *self.mag_filter as i32); + context.tex_parameteri(*target, *Context::TEXTURE_WRAP_S, *self.wrap_s as i32); + context.tex_parameteri(*target, *Context::TEXTURE_WRAP_T, *self.wrap_t as i32); + } +} + + +// === Parameter Types === + +/// Define a type that can represent a subset of `GlEnum` values defined in the `Context`. The +/// resulting type is not an enum, so it cannot be exhaustively pattern-matched, but conversion to a +/// `GlEnum` is zero-cost. +macro_rules! gl_enum_subset { + ($(#[$($attrs:tt)*])* $ty:ident: $base:ty, [$($value:ident),*]) => { + $(#[$($attrs)*])* + #[derive(Copy, Clone, Debug, PartialEq, Eq)] + pub struct $ty($base); + + #[allow(missing_docs)] + impl $ty { + $(pub const $value: $ty = $ty(Context::$value);)* + } + } +} + +gl_enum_subset!( + /// Valid Parameters for the `gl.TEXTURE_MAG_FILTER` texture setting. + /// + /// Specifies how values are interpolated if the texture is rendered at a resolution that is + /// *higher* than its native resolution. + MagFilter: GlEnum, + [LINEAR, NEAREST] +); + +impl Deref for MagFilter { + type Target = u32; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +// Note: The parameters implement our own default, not the WebGL one. +impl Default for MagFilter { + fn default() -> Self { + Self::LINEAR + } +} + +gl_enum_subset!( + /// Valid Parameters for the `gl.TEXTURE_MIN_FILTER` texture setting. + /// + /// Specifies how values are interpolated if the texture is rendered at a resolution that is + /// *lower* than its native resolution. + MinFilter: GlEnum, + [ + LINEAR, + NEAREST, + NEAREST_MIPMAP_NEAREST, + LINEAR_MIPMAP_NEAREST, + NEAREST_MIPMAP_LINEAR, + LINEAR_MIPMAP_LINEAR + ] +); + +impl Deref for MinFilter { + type Target = u32; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +// Note: The parameters implement our own default, not the WebGL one. +impl Default for MinFilter { + fn default() -> Self { + Self::LINEAR + } +} + +gl_enum_subset!( + /// Valid Parameters for the `gl.TEXTURE_WRAP_S` and `gl.TEXTURE_WRAP_T` texture setting. + /// + /// Specifies what happens if a texture is sampled out of bounds. + Wrap: GlEnum, + [REPEAT, CLAMP_TO_EDGE, MIRRORED_REPEAT] +); + +impl Deref for Wrap { + type Target = u32; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +// Note: The parameters implement our own default, not the WebGL one. +impl Default for Wrap { + fn default() -> Self { + Self::CLAMP_TO_EDGE + } +} diff --git a/lib/rust/ensogl/core/src/system/gpu/data/texture/class.rs b/lib/rust/ensogl/core/src/system/gpu/data/texture/class.rs deleted file mode 100644 index 01f0077af94..00000000000 --- a/lib/rust/ensogl/core/src/system/gpu/data/texture/class.rs +++ /dev/null @@ -1,450 +0,0 @@ -//! The core texture data type and related operations. - -use crate::prelude::*; -use crate::system::gpu::data::gl_enum::traits::*; -use crate::system::gpu::data::gl_enum::*; -use crate::system::gpu::data::texture::storage::*; -use crate::system::gpu::data::texture::types::*; - -use crate::system::gpu::data::buffer::item::JsBufferViewArr; -use crate::system::gpu::Context; - -use web_sys::WebGlTexture; - - - -// =================== -// === TextureUnit === -// =================== - -/// A texture unit representation in WebGl. -#[derive(Copy, Clone, Debug, Display, From, Into)] -pub struct TextureUnit(u32); - - - -// ======================== -// === TextureBindGuard === -// ======================== - -/// Guard which unbinds texture in specific texture unit on drop. -#[derive(Debug)] -pub struct TextureBindGuard { - context: Context, - target: GlEnum, - unit: TextureUnit, -} - -impl Drop for TextureBindGuard { - fn drop(&mut self) { - self.context.active_texture(*Context::TEXTURE0 + self.unit.to::()); - self.context.bind_texture(*self.target, None); - self.context.active_texture(*Context::TEXTURE0); - } -} - - - -// ================== -// === Parameters === -// ================== - -/// Helper struct to specify texture parameters that need to be set when binding a texture. -/// -/// The essential parameters that need to be set are about how the texture will be samples, i.e., -/// how the values of the texture are interpolated at various resolutions, and how out of bounds -/// samples are handled. -/// -/// For more background see: https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/texParameter -#[derive(Copy, Clone, Debug, Default)] -pub struct Parameters { - /// Specifies the setting for the texture minification filter (`Context::TEXTURE_MIN_FILTER`). - pub min_filter: MinFilter, - /// Specifies the setting for the texture magnification filter (`Context::TEXTURE_MAG_FILTER`). - pub mag_filter: MagFilter, - /// Specifies the setting for the wrapping function for texture coordinate s - /// (`Context::TEXTURE_WRAP_S`). - pub wrap_s: Wrap, - /// Specifies the setting for the wrapping function for texture coordinate t - /// (`Context::TEXTURE_WRAP_T`). - pub wrap_t: Wrap, -} - -impl Parameters { - /// Applies the context parameters in the given context. - pub fn apply_parameters(self, context: &Context, target: GlEnum) { - context.tex_parameteri(*target, *Context::TEXTURE_MIN_FILTER, *self.min_filter as i32); - context.tex_parameteri(*target, *Context::TEXTURE_MAG_FILTER, *self.mag_filter as i32); - context.tex_parameteri(*target, *Context::TEXTURE_WRAP_S, *self.wrap_s as i32); - context.tex_parameteri(*target, *Context::TEXTURE_WRAP_T, *self.wrap_t as i32); - } -} - - -// === Parameter Types === - -/// Valid Parameters for the `gl.TEXTURE_MAG_FILTER` texture setting. -/// -/// Specifies how values are interpolated if the texture is rendered at a resolution that is -/// lower than its native resolution. -#[derive(Copy, Clone, Debug)] -pub struct MagFilter(GlEnum); - -impl Deref for MagFilter { - type Target = u32; - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -#[allow(missing_docs)] -impl MagFilter { - pub const LINEAR: MagFilter = MagFilter(Context::LINEAR); - pub const NEAREST: MagFilter = MagFilter(Context::NEAREST); -} - -// Note: The parameters implement our own default, not the WebGL one. -impl Default for MagFilter { - fn default() -> Self { - Self::LINEAR - } -} - -/// Valid Parameters for the `gl.TEXTURE_MIN_FILTER` texture setting. -/// -/// Specifies how values are interpolated if the texture is rendered at a resolution that is -/// lower than its native resolution. -#[derive(Copy, Clone, Debug)] -pub struct MinFilter(GlEnum); - -impl Deref for MinFilter { - type Target = u32; - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -#[allow(missing_docs)] -impl MinFilter { - pub const LINEAR: MinFilter = MinFilter(Context::LINEAR); - pub const NEAREST: MinFilter = MinFilter(Context::NEAREST); - pub const NEAREST_MIPMAP_NEAREST: MinFilter = MinFilter(Context::NEAREST_MIPMAP_NEAREST); - pub const LINEAR_MIPMAP_NEAREST: MinFilter = MinFilter(Context::LINEAR_MIPMAP_NEAREST); - pub const NEAREST_MIPMAP_LINEAR: MinFilter = MinFilter(Context::NEAREST_MIPMAP_LINEAR); - pub const LINEAR_MIPMAP_LINEAR: MinFilter = MinFilter(Context::LINEAR_MIPMAP_LINEAR); -} - -// Note: The parameters implement our own default, not the WebGL one. -impl Default for MinFilter { - fn default() -> Self { - Self::LINEAR - } -} - -/// Valid Parameters for the `gl.TEXTURE_WRAP_S` and `gl.TEXTURE_WRAP_T` texture setting. -/// -/// Specifies what happens if a texture is sampled out of bounds. -#[derive(Copy, Clone, Debug)] -pub struct Wrap(GlEnum); - -impl Deref for Wrap { - type Target = u32; - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -#[allow(missing_docs)] -impl Wrap { - pub const REPEAT: Wrap = Wrap(Context::REPEAT); - pub const CLAMP_TO_EDGE: Wrap = Wrap(Context::CLAMP_TO_EDGE); - pub const MIRRORED_REPEAT: Wrap = Wrap(Context::MIRRORED_REPEAT); -} - -// Note: The parameters implement our own default, not the WebGL one. -impl Default for Wrap { - fn default() -> Self { - Self::CLAMP_TO_EDGE - } -} - - - -// =============== -// === Texture === -// =============== - -/// Texture bound to GL context. -#[derive(Derivative)] -#[derivative(Clone(bound = "StorageOf:Clone"))] -#[derivative(Debug(bound = "StorageOf:Debug"))] -pub struct Texture -where Storage: StorageRelation { - storage: StorageOf, - gl_texture: WebGlTexture, - context: Context, - parameters: Parameters, -} - - -// === Traits === - -/// Reloading functionality for textured. It is also used for initial data population. -pub trait TextureReload { - /// Loads or re-loads the texture data from provided source. - fn reload(&self); -} - - -// === Type Level Utils === - -impl Texture -where - S: StorageRelation, - I: InternalFormat, - T: ItemType, -{ - /// Internal format instance of this texture. - pub fn internal_format() -> AnyInternalFormat { - ::default().into() - } - - /// Format instance of this texture. - pub fn format() -> AnyFormat { - ::Format::default().into() - } - - /// Internal format of this texture as `GlEnum`. - pub fn gl_internal_format() -> i32 { - let GlEnum(u) = Self::internal_format().to_gl_enum(); - u as i32 - } - - /// Format of this texture as `GlEnum`. - pub fn gl_format() -> GlEnum { - Self::format().to_gl_enum() - } - - /// Element type of this texture as `GlEnum`. - pub fn gl_elem_type() -> u32 { - ::gl_enum().into() - } - - /// Element type of this texture. - pub fn item_type() -> AnyItemType { - ZST::().into() - } -} - - -// === Getters === - -impl Texture -where S: StorageRelation -{ - /// Getter. - pub fn gl_texture(&self) -> &WebGlTexture { - &self.gl_texture - } - - /// Getter. - pub fn context(&self) -> &Context { - &self.context - } - - /// Getter. - pub fn storage(&self) -> &StorageOf { - &self.storage - } - - /// Texture target getter. See [`StorageRelation::target`]. - pub fn target(&self) -> GlEnum { - S::target(&self.storage) - } - - /// Getter. - pub fn parameters(&self) -> &Parameters { - &self.parameters - } -} - - -// === Setters === - -impl Texture -where S: StorageRelation -{ - /// Setter. - pub fn set_parameters(&mut self, parameters: Parameters) { - self.parameters = parameters; - } -} - - -// === Constructors === - -impl, I: InternalFormat, T: ItemType> Texture -where Self: TextureReload -{ - /// Constructor. - pub fn new>>(context: &Context, provider: P) -> Self { - let this = Self::new_uninitialized(context, provider); - this.reload(); - this - } -} - - -// === Destructos === - -impl Drop for Texture -where S: StorageRelation -{ - fn drop(&mut self) { - self.context.delete_texture(Some(&self.gl_texture)); - } -} - - -// === Internal API === - -impl Texture -where S: StorageRelation -{ - /// New, uninitialized constructor. If you are not implementing a custom texture format, you - /// should probably use `new` instead. - pub fn new_uninitialized>>(context: &Context, storage: X) -> Self { - let storage = storage.into(); - let context = context.clone(); - let gl_texture = context.create_texture().unwrap(); - let parameters = default(); - Self { storage, gl_texture, context, parameters } - } - - /// Applies this textures' parameters in the given context. - pub fn apply_texture_parameters(&self, context: &Context) { - self.parameters.apply_parameters(context, self.target()); - } -} - -impl Texture -where - S: StorageRelation, - I: InternalFormat, - T: ItemType + JsBufferViewArr, -{ - /// Reloads gpu texture with data from given slice. - pub fn reload_from_memory(&self, data: &[T], width: i32, height: i32, depth: i32) { - let target = self.target(); - let level = 0; - let border = 0; - let internal_format = Self::gl_internal_format(); - let format = Self::gl_format().into(); - let elem_type = Self::gl_elem_type(); - let data: &[u8] = bytemuck::cast_slice(data); - self.context.bind_texture(*target, Some(&self.gl_texture)); - match target { - Context::TEXTURE_2D => { - self.context - .tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array( - *target, - level, - internal_format, - width, - height, - border, - format, - elem_type, - Some(data), - ) - .unwrap(); - } - Context::TEXTURE_2D_ARRAY | Context::TEXTURE_3D => { - self.context - .tex_image_3d_with_opt_u8_array( - *target, - level, - internal_format, - width, - height, - depth, - border, - format, - elem_type, - Some(data), - ) - .unwrap(); - } - _ => { - panic!("Unsupported texture target: {target:?}"); - } - } - self.apply_texture_parameters(&self.context); - } -} - - -// === Instances === - -impl, I, T> HasItem for Texture { - type Item = Texture; -} - -impl, I, T> ItemRef for Texture { - fn item(&self) -> &Self::Item { - self - } -} - - - -// ================== -// === TextureOps === -// ================== - -/// API of the texture. It is defined as trait and uses the `WithContent` mechanism in order for -/// uniforms to easily redirect the methods. -pub trait TextureOps { - /// Bind texture to a specific unit. - fn bind_texture_unit(&self, context: &Context, unit: TextureUnit) -> TextureBindGuard; - - /// Accessor. - fn gl_texture(&self) -> WebGlTexture; - - /// Accessor. - fn get_format(&self) -> AnyFormat; - - /// Accessor. - fn get_item_type(&self) -> AnyItemType; -} - -impl< - P: WithItemRef>, - S: StorageRelation, - I: InternalFormat, - T: ItemType, - > TextureOps for P -{ - fn bind_texture_unit(&self, context: &Context, unit: TextureUnit) -> TextureBindGuard { - self.with_item(|this| { - let context = context.clone(); - let target = this.target(); - context.active_texture(*Context::TEXTURE0 + unit.to::()); - context.bind_texture(*target, Some(&this.gl_texture)); - context.active_texture(*Context::TEXTURE0); - TextureBindGuard { context, target, unit } - }) - } - - fn gl_texture(&self) -> WebGlTexture { - self.with_item(|this| this.gl_texture.clone()) - } - - fn get_format(&self) -> AnyFormat { - self.with_item(|_| >::format()) - } - - fn get_item_type(&self) -> AnyItemType { - self.with_item(|_| >::item_type()) - } -} diff --git a/lib/rust/ensogl/core/src/system/gpu/data/texture/storage.rs b/lib/rust/ensogl/core/src/system/gpu/data/texture/storage.rs deleted file mode 100644 index 441685bec4d..00000000000 --- a/lib/rust/ensogl/core/src/system/gpu/data/texture/storage.rs +++ /dev/null @@ -1,47 +0,0 @@ -//! This is a root module for all texture storage definitions. - -use crate::prelude::*; - -use crate::system::gpu::Context; -use crate::system::gpu::GlEnum; - - -// ============== -// === Export === -// ============== - -pub mod gpu_only; -pub mod owned; -pub mod remote_image; - -pub use gpu_only::*; -pub use owned::*; -pub use remote_image::*; - - - -// =============== -// === Storage === -// =============== - -/// Trait describing any storage texture type. -pub trait Storage = Debug + Default + Into + PhantomInto + 'static; - -/// Type level accessor of the storage implementation for a given set of texture parameters. -pub type StorageOf = >::Storage; - -/// The storage implementation type family. -pub trait StorageRelation: Storage { - /// The storage implementation. - type Storage: Debug; - - /// Texture bind target. Usually it is `TEXTURE_2D` or `TEXTURE_2D_ARRAY`. - fn target(storage: &Self::Storage) -> GlEnum { - let _ = storage; - Context::TEXTURE_2D - } -} - -enso_shapely::define_singleton_enum! { - AnyStorage {RemoteImage, GpuOnly, Owned} -} diff --git a/lib/rust/ensogl/core/src/system/gpu/data/texture/storage/gpu_only.rs b/lib/rust/ensogl/core/src/system/gpu/data/texture/storage/gpu_only.rs deleted file mode 100644 index 932f4333079..00000000000 --- a/lib/rust/ensogl/core/src/system/gpu/data/texture/storage/gpu_only.rs +++ /dev/null @@ -1,126 +0,0 @@ -//! This module defines a texture storage which do not keep local data. It only keeps a reference -//! to GPU texture of a given size. - -use crate::system::gpu::data::texture::class::*; -use crate::system::gpu::data::texture::storage::*; -use crate::system::gpu::data::texture::types::*; - -use crate::system::gpu::data::buffer::item::JsBufferViewArr; -use crate::system::gpu::Context; -use crate::system::gpu::GlEnum; - - - -// =============== -// === GpuOnly === -// =============== - -/// Sized, uninitialized texture. -#[derive(Clone, Copy, Debug)] -pub struct GpuOnlyData { - /// Texture width. - pub width: i32, - /// Texture height. - pub height: i32, - /// Number of texture layers. When the texture is not layered, this value is 0. - pub layers: i32, -} - - -// === Instances === - -impl StorageRelation for GpuOnly { - type Storage = GpuOnlyData; - - fn target(storage: &GpuOnlyData) -> GlEnum { - match storage.layers { - 0 => Context::TEXTURE_2D, - _ => Context::TEXTURE_2D_ARRAY, - } - } -} - -impl From<(i32, i32)> for GpuOnlyData { - fn from(t: (i32, i32)) -> Self { - let (width, height) = t; - Self { width, height, layers: 0 } - } -} - -impl From<((i32, i32), i32)> for GpuOnlyData { - fn from(t: ((i32, i32), i32)) -> Self { - let ((width, height), layers) = t; - Self { width, height, layers } - } -} - - - -// === API === - -impl GpuOnlyData { - fn new(width: i32, height: i32) -> Self { - Self { width, height, layers: 0 } - } -} - -impl TextureReload for Texture { - fn reload(&self) { - let GpuOnlyData { width, height, layers } = *self.storage(); - let level = 0; - let border = 0; - let internal_format = Self::gl_internal_format(); - let format = Self::gl_format().into(); - let elem_type = Self::gl_elem_type(); - - let target = self.target(); - self.context().bind_texture(*target, Some(self.gl_texture())); - match target { - Context::TEXTURE_2D => { - self.context() - .tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array( - *target, - level, - internal_format, - width, - height, - border, - format, - elem_type, - None, - ) - .unwrap(); - } - Context::TEXTURE_2D_ARRAY | Context::TEXTURE_3D => { - self.context() - .tex_image_3d_with_opt_u8_array( - *target, - level, - internal_format, - width, - height, - layers, - border, - format, - elem_type, - None, - ) - .unwrap(); - } - _ => { - panic!("Unsupported texture target: {target:?}"); - } - } - - self.apply_texture_parameters(self.context()); - } -} - -impl Texture { - /// Reload texture with given content. The data will be copied to gpu, but the texture will not - /// take ownership. - pub fn reload_with_content(&self, data: &[T]) { - let GpuOnlyData { width, height, layers } = *self.storage(); - self.reload_from_memory(data, width, height, layers); - } -} diff --git a/lib/rust/ensogl/core/src/system/gpu/data/texture/storage/owned.rs b/lib/rust/ensogl/core/src/system/gpu/data/texture/storage/owned.rs deleted file mode 100644 index 5a41d34ff69..00000000000 --- a/lib/rust/ensogl/core/src/system/gpu/data/texture/storage/owned.rs +++ /dev/null @@ -1,54 +0,0 @@ -//! This module defines an owned texture storage type. It keeps the texture data in a local memory. - -use crate::system::gpu::data::texture::class::*; -use crate::system::gpu::data::texture::storage::*; -use crate::system::gpu::data::texture::types::*; - -use crate::system::gpu::data::buffer::item::JsBufferViewArr; -use crate::system::gpu::data::texture; - - - -// ============= -// === Owned === -// ============= - -/// Texture plain data. -#[derive(Debug)] -pub struct OwnedData { - /// An array containing texture data. - pub data: Vec, - /// Texture width. - pub width: i32, - /// Texture height. - pub height: i32, -} - - -// === Instances === - -impl StorageRelation for texture::storage::Owned { - type Storage = OwnedData; -} - -impl OwnedData { - fn new(data: Vec, width: i32, height: i32) -> Self { - Self { data, width, height } - } -} - - -// === API === - -impl TextureReload - for Texture -{ - #[allow(unsafe_code)] - fn reload(&self) { - let storage = &self.storage(); - let data = storage.data.as_slice(); - let width = storage.width; - let height = storage.height; - self.reload_from_memory(data, width, height, 0); - } -} diff --git a/lib/rust/ensogl/core/src/system/gpu/data/texture/storage/remote_image.rs b/lib/rust/ensogl/core/src/system/gpu/data/texture/storage/remote_image.rs deleted file mode 100644 index 7ae72015d7f..00000000000 --- a/lib/rust/ensogl/core/src/system/gpu/data/texture/storage/remote_image.rs +++ /dev/null @@ -1,152 +0,0 @@ -//! This module defines remote image texture storage. It is used to download image from a given URL. - -use crate::system::gpu::data::texture::class::*; -use crate::system::gpu::data::texture::storage::*; -use crate::system::gpu::data::texture::types::*; - -use crate::system::gpu::Context; -#[cfg(target_arch = "wasm32")] -use crate::system::web; - -#[cfg(target_arch = "wasm32")] -use web::Closure; -#[cfg(target_arch = "wasm32")] -use web_sys::HtmlImageElement; - - - -// =================== -// === RemoteImage === -// =================== - -/// Texture downloaded from URL. This source implies asynchronous loading. -#[derive(Debug)] -pub struct RemoteImageData { - /// An url from where the texture is downloaded. - pub url: String, -} - - -// === Instances === - -impl StorageRelation for RemoteImage { - type Storage = RemoteImageData; -} - -impl From for RemoteImageData { - fn from(s: S) -> Self { - Self::new(s) - } -} - - -// === API === - -impl RemoteImageData { - fn new(url: S) -> Self { - Self { url: url.into() } - } -} - -impl Texture { - /// Initializes default texture value. It is useful when the texture data needs to be downloaded - /// asynchronously. This method creates a mock 1px x 1px texture and uses it as a mock texture - /// until the download is complete. - pub fn init_mock(&self) { - let target = Context::TEXTURE_2D; - let level = 0; - let internal_format = Self::gl_internal_format(); - let format = Self::gl_format().into(); - let elem_type = Self::gl_elem_type(); - let width = 1; - let height = 1; - let border = 0; - let color = vec![0, 0, 255, 255]; - self.context().bind_texture(*Context::TEXTURE_2D, Some(self.gl_texture())); - self.context() - .tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array( - *target, - level, - internal_format, - width, - height, - border, - format, - elem_type, - Some(&color), - ) - .unwrap(); - } -} - -impl TextureReload for Texture { - /// Loads or re-loads the texture data from the provided url. - /// This action will be performed asynchronously. - #[allow(trivial_casts)] - #[cfg(target_arch = "wasm32")] - fn reload(&self) { - let url = &self.storage().url; - let image = HtmlImageElement::new().unwrap(); - let no_callback = >::None; - let callback_ref = Rc::new(RefCell::new(no_callback)); - let image_ref = Rc::new(RefCell::new(image)); - let callback_ref2 = callback_ref.clone(); - let image_ref_opt = image_ref.clone(); - let context = self.context().clone(); - let gl_texture = self.gl_texture().clone(); - let target = self.target(); - let parameters = *self.parameters(); - let callback: web::JsEventHandler = Closure::once(move |_| { - let _keep_alive = callback_ref2; - let image = image_ref_opt.borrow(); - let level = 0; - let internal_format = Self::gl_internal_format(); - let format = Self::gl_format().into(); - let elem_type = Self::gl_elem_type(); - context.bind_texture(*target, Some(&gl_texture)); - context - .tex_image_2d_with_u32_and_u32_and_html_image_element( - *target, - level, - internal_format, - format, - elem_type, - &image, - ) - .unwrap(); - - parameters.apply_parameters(&context, target); - }) as web::JsEventHandler; - let image = image_ref.borrow(); - request_cors_if_not_same_origin(&image, url); - image.set_src(url); - let handler = web::add_event_listener_with_bool(&image, "load", callback, true); - *callback_ref.borrow_mut() = Some(handler); - } - - #[cfg(not(target_arch = "wasm32"))] - fn reload(&self) {} -} - -// === Utils === - -/// CORS = Cross Origin Resource Sharing. It's a way for the webpage to ask the image server for -/// permission to use the image. To do this we set the crossOrigin attribute to something and then -/// when the browser tries to get the image from the server, if it's not the same domain, the -/// browser will ask for CORS permission. The string we set `cross_origin` to is sent to the server. -/// The server can look at that string and decide whether or not to give you permission. Most -/// servers that support CORS don't look at the string, they just give permission to everyone. -/// -/// **Note** -/// Why don't want to just always see the permission because asking for permission takes 2 HTTP -/// requests, so it's slower than not asking. If we know we're on the same domain or we know we -/// won't use the image for anything except img tags and or canvas2d then we don't want to set -/// crossDomain because it will make things slower. -#[cfg(target_arch = "wasm32")] -fn request_cors_if_not_same_origin(img: &HtmlImageElement, url_str: &str) { - let url = web_sys::Url::new(url_str).unwrap(); - let origin = web::window.location().origin().unwrap(); - if url.origin() != origin { - img.set_cross_origin(Some("")); - } -} diff --git a/lib/rust/ensogl/core/src/system/gpu/data/texture/types/internal_format.rs b/lib/rust/ensogl/core/src/system/gpu/data/texture/types/internal_format.rs index 11d66d15a49..8303ad2b907 100644 --- a/lib/rust/ensogl/core/src/system/gpu/data/texture/types/internal_format.rs +++ b/lib/rust/ensogl/core/src/system/gpu/data/texture/types/internal_format.rs @@ -116,3 +116,21 @@ macro_rules! generate_internal_format_instances_item { } crate::with_texture_format_relations!(generate_internal_format_instances []); + +/// Generate a function that returns the format for any `InternalFormat`. +#[macro_export] +macro_rules! generate_format_map { + ([] $( $internal_format:ident $format:ident $sampler:ident + $renderable:tt $filterable:tt $blendable:tt $elem_descs:tt)* ) => { + impl AnyInternalFormat { + /// Return the [`Format`] corresponding to this internal format. + pub fn format(self) -> AnyFormat { + match self { + $(::$internal_format => ::$format),* + } + } + } + } +} + +crate::with_texture_format_relations!(generate_format_map []); diff --git a/lib/rust/ensogl/core/src/system/gpu/data/texture/types/relations.rs b/lib/rust/ensogl/core/src/system/gpu/data/texture/types/relations.rs index f666aa8a7e6..332922624e0 100644 --- a/lib/rust/ensogl/core/src/system/gpu/data/texture/types/relations.rs +++ b/lib/rust/ensogl/core/src/system/gpu/data/texture/types/relations.rs @@ -102,7 +102,7 @@ macro_rules! with_texture_format_relations { ($f:ident $args:tt) => { $crate::$f #[macro_export] macro_rules! with_all_texture_types_cartesians { ([$f:ident] [$($out:tt)*]) => { - shapely::cartesian! { [$f] [Owned GpuOnly RemoteImage] [$($out)*] } + shapely::cartesian! { [$f] [GpuOnly] [$($out)*] } }; ([$f:ident _] $out:tt) => { $f! { $out } diff --git a/lib/rust/ensogl/core/src/system/gpu/data/uniform.rs b/lib/rust/ensogl/core/src/system/gpu/data/uniform.rs index 97c22d40b77..952e14b0ed9 100644 --- a/lib/rust/ensogl/core/src/system/gpu/data/uniform.rs +++ b/lib/rust/ensogl/core/src/system/gpu/data/uniform.rs @@ -8,9 +8,7 @@ use enum_dispatch::*; use crate::system::gpu::Context; -use enso_shapely::shared; use upload::UniformUpload; -use web_sys::WebGlTexture; use web_sys::WebGlUniformLocation; @@ -35,66 +33,65 @@ pub trait UniformValue = Sized where Uniform: Into; // === UniformScope === // ==================== -shared! { UniformScope - /// A scope containing set of uniform values. #[derive(Debug, Default)] -pub struct UniformScopeData { +pub struct UniformScope { map: HashMap, } -impl { +impl UniformScope { /// Constructor. pub fn new() -> Self { default() } /// Look up uniform by name. - pub fn get(&self, name:Name) -> Option { + pub fn get(&self, name: Name) -> Option { self.map.get(name.as_ref()).cloned() } /// Checks if uniform of a given name was defined in this scope. - pub fn contains(&self, name:Name) -> bool { + pub fn contains(&self, name: Name) -> bool { self.map.contains_key(name.as_ref()) } /// Add a new uniform with a given name and initial value. Returns `None` if the name is in use. - pub fn add(&mut self, name:Name, input:Input) -> Option> + pub fn add(&mut self, name: Name, input: Input) -> Option> where Name: Str, Input: Into>, - Value: UniformValue { - self.add_or_else(name, input, Some, |_,_,_| None) + Value: UniformValue, { + self.add_or_else(name, input, Some, |_, _, _| None) } /// Add a new uniform with a given name and initial value. Panics if the name is in use. - pub fn add_or_panic(&mut self, name:Name, input:Input) -> Uniform + pub fn add_or_panic(&mut self, name: Name, input: Input) -> Uniform where - Name:Str, + Name: Str, Input: Into>, - Value:UniformValue { - self.add_or_else(name,input,|t|{t},|name,_,_| { - panic!("Trying to override uniform '{}'.", name.as_ref()) - }) + Value: UniformValue, { + self.add_or_else( + name, + input, + |t| t, + |name, _, _| panic!("Trying to override uniform '{}'.", name.as_ref()), + ) } pub fn add_uniform(&mut self, name: Name, uniform: &Uniform) where Name: Str, - Value: UniformValue { + Value: UniformValue, { self.add::(name, uniform); } - pub fn add_uniform_or_panic(&mut self, name:Name, uniform:&Uniform) + pub fn add_uniform_or_panic(&mut self, name: Name, uniform: &Uniform) where - Name:Str, - Value:UniformValue { + Name: Str, + Value: UniformValue, { self.add_or_panic::(name, uniform); } -}} -impl UniformScopeData { /// Adds a new uniform with a given name and initial value. In case the name was already in use, /// it fires the `on_exist` function. Otherwise, it fires the `on_fresh` function on the newly /// created uniform. @@ -141,18 +138,6 @@ impl UniformScopeData { } } -impl UniformScope { - /// Gets an existing uniform or adds a new one in case it was missing. Returns `None` if the - /// uniform exists but its type does not match the requested one. - pub fn set(&self, name: Name, value: Value) -> Option> - where - Name: Str, - Value: UniformValue, - for<'t> &'t Uniform: TryFrom<&'t AnyUniform>, { - self.rc.borrow_mut().set(name, value) - } -} - // =============== @@ -169,69 +154,48 @@ impl UniformScope { // Please note that currently a special uniform 'zoom' is modified in the render loop. See // the `scene::View` implementation to learn more. -shared! { Uniform - /// An uniform value. -#[derive(Debug)] -pub struct UniformData { - value: Value, - // dirty: bool, +#[derive(Debug, CloneRef, Derivative)] +#[derivative(Clone(bound = ""))] +pub struct Uniform { + value: Rc>, } -impl { +impl Uniform { /// Constructor. - pub fn new(value:Value) -> Self { + pub fn new(value: Value) -> Self { + let value = Rc::new(RefCell::new(value)); // let dirty = true; - Self {value} + Self { value } } /// Sets the value of this uniform. - pub fn set(&mut self, value:Value) { + pub fn set(&self, value: Value) { // self.set_dirty(); - self.value = value; + *self.value.borrow_mut() = value; } /// Modifies the value of this uniform. - pub fn modify(&mut self, f: impl FnOnce(&mut Value)) { - f(&mut self.value); + pub fn modify(&self, f: impl FnOnce(&mut Value)) { + f(&mut *self.value.borrow_mut()); } - -// /// Checks whether the uniform was changed and not yet updated. -// pub fn check_dirty(&self) -> bool { -// self.dirty -// } - -// /// Sets the dirty flag. -// pub fn set_dirty(&mut self) { -// self.dirty = true; -// } -// -// /// Clears the dirty flag. -// pub fn unset_dirty(&mut self) { -// self.dirty = false; -// } } -impl { +impl Uniform { /// Reads the value of this uniform. pub fn get(&self) -> Value { - self.value.clone() + self.value.borrow().clone() } -}} +} impl Uniform { pub fn swap(&self, that: &Self) { - self.rc.borrow_mut().swap(&mut *that.rc.borrow_mut()) + if !Rc::ptr_eq(&self.value, &that.value) { + mem::swap(&mut *self.value.borrow_mut(), &mut *that.value.borrow_mut()) + } } } -impl UniformData { - pub fn swap(&mut self, that: &mut Self) { - mem::swap(self, that) - } -} - - impl From for Uniform { fn from(t: T) -> Self { Self::new(t) @@ -250,7 +214,7 @@ impl HasItem for Uniform { impl WithItemRef for Uniform { fn with_item(&self, f: impl FnOnce(&Self::Item) -> R) -> R { - f(&self.rc.borrow().value) + f(&self.value.borrow()) } } @@ -295,13 +259,7 @@ pub trait AnyPrimUniformOps { impl AnyPrimUniformOps for Uniform { fn upload(&self, context: &Context, location: &WebGlUniformLocation) { - self.rc.borrow().upload(context, location) - } -} - -impl AnyPrimUniformOps for UniformData { - fn upload(&self, context: &Context, location: &WebGlUniformLocation) { - self.value.upload_uniform(context, location) + self.value.borrow().upload_uniform(context, location) } } @@ -311,117 +269,30 @@ impl AnyPrimUniformOps for UniformData { // === AnyTextureUniform === // ========================= -macro_rules! define_any_texture_uniform { - ( [ $([$storage:ident $internal_format:ident $item_type:ident])* ] ) => { paste! { - #[allow(non_camel_case_types)] - #[derive(Clone,CloneRef,Debug)] - pub enum AnyTextureUniform { - $([<$storage _ $internal_format _ $item_type >] - (Uniform>)),* - } - - impl AnyTextureUniform { - pub fn try_swap(&self, that:&Self) -> bool { - match (self,that) { - $( - ( Self::[<$storage _ $internal_format _ $item_type >](a) - , Self::[<$storage _ $internal_format _ $item_type >](b) - ) => {a.swap(b); true}, - )* - _ => false - } - } - } - - impl TextureOps for AnyTextureUniform { - fn bind_texture_unit - (&self, context:&Context, unit:TextureUnit) -> TextureBindGuard { - match self { - $( - Self::[<$storage _ $internal_format _ $item_type >](t) => - t.bind_texture_unit(context,unit) - ),* - } - } - - fn gl_texture(&self) -> WebGlTexture { - match self { - $(Self::[<$storage _ $internal_format _ $item_type >](t) => t.gl_texture()),* - } - } - - fn get_format(&self) -> AnyFormat { - match self { - $(Self::[<$storage _ $internal_format _ $item_type >](t) => t.get_format()),* - } - } - - fn get_item_type(&self) -> AnyItemType { - match self { - $(Self::[<$storage _ $internal_format _ $item_type >](t) => t.get_item_type()),* - } - } - } - - $( - impl From>> - for AnyTextureUniform { - fn from(t:Uniform>) -> Self { - Self::[<$storage _ $internal_format _ $item_type >](t) - } - } - - impl<'t> TryFrom<&'t AnyTextureUniform> - for &'t Uniform> { - type Error = TypeMismatch; - fn try_from(value:&'t AnyTextureUniform) -> Result { - match value { - AnyTextureUniform::[<$storage _ $internal_format _ $item_type >](t) => Ok(t), - _ => Err(TypeMismatch), - } - } - } - )* - }} +#[allow(non_camel_case_types)] +#[derive(Clone, CloneRef, Debug)] +pub struct AnyTextureUniform { + texture: Uniform>, } -macro_rules! define_get_or_add_gpu_texture_dyn { - ( [ $([$internal_format:ident $item_type:ident])* ] ) => { - pub fn get_or_add_gpu_texture_dyn> - ( context : &Context - , scope : &UniformScope - , name : &str - , internal_format : AnyInternalFormat - , item_type : AnyItemType - , provider : P - , parameters : Option - ) -> AnyTextureUniform { - let provider = provider.into(); - match (internal_format,item_type) { - $((AnyInternalFormat::$internal_format, AnyItemType::$item_type) => { - let mut texture = - Texture::::new(&context,provider); - if let Some(parameters) = parameters { - texture.set_parameters(parameters); - } - let uniform = scope.set(name,texture).unwrap(); - uniform.into() - })* - _ => panic!("Invalid internal format and item type combination ({:?},{:?}).", - internal_format,item_type) - } - } +impl AnyTextureUniform { + pub fn texture(&self) -> Option> { + Ref::filter_map(self.texture.value.borrow(), |texture| texture.as_ref()).ok() } } -macro_rules! generate { - ( [ $([$internal_format:ident $item_type:ident])* ] ) => { - define_any_texture_uniform!{[ $([GpuOnly $internal_format $item_type])* ]} - define_get_or_add_gpu_texture_dyn!{[ $([$internal_format $item_type])* ]} +impl From>> for AnyTextureUniform { + fn from(texture: Uniform>) -> Self { + Self { texture } } } -crate::with_all_texture_types! ([generate _]); +impl<'t> TryFrom<&'t AnyTextureUniform> for &'t Uniform> { + type Error = TypeMismatch; + fn try_from(value: &'t AnyTextureUniform) -> Result { + Ok(&value.texture) + } +} @@ -456,13 +327,7 @@ impl> IntoAnyUniform for T { } } -impl IntoAnyUniform for Uniform> -where - S: StorageRelation, - I: InternalFormat, - T: ItemType, - Uniform>: Into, -{ +impl IntoAnyUniform for Uniform> { fn into_any_uniform(self) -> AnyUniform { AnyUniform::Texture(self.into()) } @@ -484,10 +349,7 @@ macro_rules! generate_prim_type_downcasts { crate::with_all_prim_types!([[generate_prim_type_downcasts][]]); -impl<'t, S: StorageRelation, I: InternalFormat, T: ItemType> TryFrom<&'t AnyUniform> - for &'t Uniform> -where &'t Uniform>: TryFrom<&'t AnyTextureUniform, Error = TypeMismatch> -{ +impl<'t> TryFrom<&'t AnyUniform> for &'t Uniform> { type Error = TypeMismatch; fn try_from(value: &'t AnyUniform) -> Result { match value { diff --git a/lib/rust/ensogl/core/src/system/gpu/shader/compiler.rs b/lib/rust/ensogl/core/src/system/gpu/shader/compiler.rs index 1d9fc122cf4..b5b8ce153ca 100644 --- a/lib/rust/ensogl/core/src/system/gpu/shader/compiler.rs +++ b/lib/rust/ensogl/core/src/system/gpu/shader/compiler.rs @@ -849,9 +849,10 @@ impl Controller { impl ControllerData { fn run(&mut self, context: &Context, time: animation::TimeInfo) -> bool { - let was_busy = !context.shader_compiler.idle(); - let result = context.shader_compiler.run(time); - let now_idle = context.shader_compiler.idle(); + let compiler = context.shader_compiler(); + let was_busy = !compiler.idle(); + let result = compiler.run(time); + let now_idle = compiler.idle(); if was_busy && now_idle { self.on_idle.run_all(); } diff --git a/lib/rust/ensogl/examples/custom-shape-system/src/lib.rs b/lib/rust/ensogl/examples/custom-shape-system/src/lib.rs index 20838535eaa..bc966307e71 100644 --- a/lib/rust/ensogl/examples/custom-shape-system/src/lib.rs +++ b/lib/rust/ensogl/examples/custom-shape-system/src/lib.rs @@ -74,7 +74,7 @@ pub fn main() { let _keep_alive = &navigator; i += 1; if i == 5 { - if let Some(program) = view.sprite.borrow().symbol.shader().program() { + if let Some(program) = view.sprite.borrow().symbol.shader.borrow_mut().program() { debug!("\n\nVERTEX:\n{}", program.shader.vertex.code); debug!("\n\nFRAGMENT:\n{}", program.shader.fragment.code); }