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 | | | | | | | | | | | | |