Context restoration (#7662)

Add support for recovering from GL context loss. When the context is restored, the loading spinner is shown until shaders finish recompiling.

[vokoscreenNG-2023-08-25_09-39-11.webm](https://github.com/enso-org/enso/assets/1047859/cfa90ec5-72a1-41e6-bafa-177fa5e85fb2)

*While the context is missing, the loading spinner is rendered in the 0% state. (This condition will not normally be observed, except momentarily, as the browser should restore the context immediately if it is lost while the page is visible.) When we receive a new context, the spinner switches to the 90% state until restoration completes. Restoration is fast, as we don't need to do much work except recompiling shaders.*

# Important Notes
- A new debug hotkey, Ctrl+Alt+Shift+X, causes context loss for testing. Pressing it a second time causes context restoration.
- `Texture` is still a CPU-bound texture. It now uses the "immutable" `texStorage/texSubImage` API, which is a ["preferred alternative"](https://registry.khronos.org/webgl/specs/latest/2.0/#3.7.6) to the `texImage` API because it can be more efficient.
- The type for texture uniforms is now `Uniform<Option<Texture>>`. Texture uniforms are decoupled from the context.
- A new `ContextLost` error type can be returned by functions that cannot complete if the context is lost.
- Fix some crashes that could occur when context was lost.
- Clarify ownership of some rendering-related types: Externalize, and where possible eliminate, `Rc/RefCell`s.
This commit is contained in:
Kaz Wesley 2023-09-14 07:40:28 -07:00 committed by GitHub
parent 30a62b97bb
commit b9ec6d4ec3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 1444 additions and 1925 deletions

View File

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

View File

@ -141,4 +141,5 @@ broken and require further investigation.
| <kbd>ctrl</kbd> + <kbd>shift</kbd> + <kbd>arrow up</kbd> | Pop a breadcrumb without navigating. |
| <kbd>cmd</kbd> + <kbd>i</kbd> | Reload visualizations. To see the effect in the currently shown visualizations, you need to switch to another and switch back. |
| <kbd>ctrl</kbd> + <kbd>shift</kbd> + <kbd>b</kbd> | Toggle read-only mode. |
| <kbd>ctrl</kbd> + <kbd>alt</kbd> + <kbd>shift</kbd> + <kbd>x</kbd> | Toggle WebGL Context loss / restoration for testing. |
| <kbd>ctrl</kbd> + <kbd>shift</kbd> + <kbd>u</kbd> | Dump the suggestion database as JSON to the console. Available only in debug mode, and only if the component browser is open. |

View File

@ -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<RefCell<Vec<(ImString, Uuid)>>>,
shortcut_transaction: RefCell<Option<Rc<model::undo_redo::Transaction>>>,
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();
});
}
})
}
}

View File

@ -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<texture::GpuOnly, texture::Rgb, u8>;
#[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<AtlasTexture>,
pub atlas: gpu::Uniform<Option<gpu::Texture>>,
pub opacity_increase: gpu::Uniform<f32>,
pub opacity_exponent: gpu::Uniform<f32>,
context: Rc<RefCell<Option<Context>>>,
}
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<HashMap<Name, FontWithGpuData>>,
network: frp::Network,
fonts: Rc<HashMap<Name, FontWithGpuData>>,
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<Item = (Name, Font)>) -> Self {
let scene = scene();
fn new(
scene: &ensogl_core::display::Scene,
fonts: impl IntoIterator<Item = (Name, Font)>,
) -> 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<HashMap<Name, FontWithGpuData>>) {
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 ===

View File

@ -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<glyph_shape::Shape> 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<GlyphId>,
pub line_byte_offset: Cell<Byte>,
pub display_object: display::object::Instance,
pub context: Context,
pub properties: Cell<font::family::NonVariableFaceHeader>,
pub variations: RefCell<VariationAxes>,
pub x_advance: Cell<f32>,
/// 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<bool>,
glyph_id: Cell<GlyphId>,
display_object: display::object::Instance,
properties: Cell<font::family::NonVariableFaceHeader>,
variations: RefCell<VariationAxes>,
}
// === 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 [<set_ $prop:snake:lower _ $variant:snake:lower>](&self) {
self.[<set_ $prop:snake:lower>]($prop::$variant)
}
#[doc = "Checks whether the `"]
#[doc = stringify!($prop)]
#[doc = "` property is set to `"]
#[doc = stringify!($variant)]
#[doc = "`."]
pub fn [<is_ $prop:snake:lower _ $variant:snake:lower>](&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::<font::Registry>();
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,

View File

@ -72,6 +72,7 @@ features = [
'Url',
'WebGlBuffer',
'WebGlFramebuffer',
'WebglLoseContext',
'WebGlProgram',
'WebGlQuery',
'WebGlRenderingContext',

View File

@ -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<ComposerPass>,
variables : UniformScope,
context : Context,
width : i32,
height : i32,
pixel_ratio : f32,
pub struct Composer {
passes: Vec<Box<dyn pass::Instance>>,
variables: Rc<RefCell<UniformScope>>,
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<RefCell<UniformScope>>,
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<Vec<_>, 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<dyn pass::Definition>,
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<dyn pass::Definition>,
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<dyn pass::Definition>,
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);
}
}
}

View File

@ -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<Box<dyn Instance>, 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<RefCell<UniformScope>>,
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<RefCell<UniformScope>>,
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<Framebuffer, ContextLost> {
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,

View File

@ -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<Box<dyn pass::Instance>, 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<pass::Framebuffer>,
texture: Option<crate::system::gpu::data::uniform::AnyTextureUniform>,
framebuffer: pass::Framebuffer,
texture: crate::system::gpu::data::uniform::AnyTextureUniform,
#[derivative(Debug = "ignore")]
shapes_to_render: Vec<Rc<dyn AnyShapeView>>,
shapes_to_render: Vec<Box<dyn AnyShapeView>>,
/// Texture size in device pixels.
texture_size_device: Vector2<i32>,
layer: Layer,
camera_ready: Rc<Cell<bool>>,
#[derivative(Debug = "ignore")]
display_object_update_handler: Option<Rc<enso_callback::Handle>>,
}
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<Self, ContextLost> {
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<Box<dyn AnyShapeView>> =
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<dyn AnyShapeView>| {
shape.sprite().symbol.shader().program().is_some()
let is_shader_compiled = |shape: &mut Box<dyn AnyShapeView>| {
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;
}
}

View File

@ -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<T: JsTypedArrayItem> {
buffer: WebGlBuffer,
framebuffer: WebGlFramebuffer,
format: texture::AnyFormat,
item_type: texture::AnyItemType,
js_array: JsTypedArray<T>,
/// Definition of a pass that reads the color of a pixel.
#[derive(Clone, Derivative)]
#[derivative(Debug)]
pub struct PixelReadPassDef<T> {
position: Uniform<Vector2<i32>>,
threshold: Rc<Cell<usize>>,
#[derivative(Debug = "ignore")]
callback: Rc<dyn Fn(Vec<T>)>,
#[derivative(Debug = "ignore")]
sync_callback: Rc<dyn Fn()>,
}
impl<T: JsTypedArrayItem> PixelReadPassData<T> {
/// Constructor.
impl<T: JsTypedArrayItem> PixelReadPassDef<T> {
/// 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<T>,
position: Uniform<Vector2<i32>>,
callback: impl 'static + Fn(Vec<T>),
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<Cell<usize>> {
self.threshold.clone()
}
}
impl<T: JsTypedArrayItem> pass::Definition for PixelReadPassDef<T> {
fn instantiate(
&self,
instance: pass::InstanceInfo,
) -> Result<Box<dyn pass::Instance>, ContextLost> {
Ok(Box::new(PixelReadPass::new(self.clone(), instance)?))
}
}
@ -48,108 +73,85 @@ impl<T: JsTypedArrayItem> PixelReadPassData<T> {
// =====================
/// 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<T: JsTypedArrayItem> {
data: Option<PixelReadPassData<T>>,
buffer: WebGlBuffer,
framebuffer: WebGlFramebuffer,
format: texture::AnyFormat,
item_type: texture::AnyItemType,
js_array: JsTypedArray<T>,
sync: Option<WebGlSync>,
position: Uniform<Vector2<i32>>,
threshold: Rc<Cell<usize>>,
since_last_read: usize,
#[derivative(Debug = "ignore")]
callback: Option<Rc<dyn Fn(Vec<T>)>>,
#[derivative(Debug = "ignore")]
sync_callback: Option<Rc<dyn Fn()>>,
instance: pass::InstanceInfo,
#[deref]
definition: PixelReadPassDef<T>,
}
impl<T: JsTypedArrayItem> PixelReadPass<T> {
/// Constructor.
pub fn new(position: &Uniform<Vector2<i32>>) -> 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<F: Fn(Vec<T>) + '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<F: Fn() + 'static>(&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<Cell<usize>> {
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::<T>::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<T>,
instance: pass::InstanceInfo,
) -> Result<Self, ContextLost> {
let context = &instance.context;
let buffer = context.create_buffer()?;
let js_array = JsTypedArray::<T>::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::<GlEnum>().into();
let typ = data.item_type.to::<GlEnum>().into();
let format = self.format.to::<GlEnum>().into();
let typ = self.item_type.to::<GlEnum>().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<T: JsTypedArrayItem> PixelReadPass<T> {
}
#[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<T: JsTypedArrayItem> pass::Definition for PixelReadPass<T> {
fn run(&mut self, instance: &pass::Instance, update_status: UpdateStatus) {
impl<T: JsTypedArrayItem> pass::Instance for PixelReadPass<T> {
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) => (),
}
}
}

View File

@ -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<Box<dyn pass::Instance>, 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) {}
}

View File

@ -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<Box<dyn pass::Instance>, 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<Framebuffers>,
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<layer::ScissorBox>,
parent_masked: bool,
override_blend: Option<layer::BlendMode>,
) {
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<layer::ScissorBox>,
parent_masked: bool,
override_blend: Option<layer::BlendMode>,
) -> 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<Self, ContextLost> {
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 })
}
}

View File

@ -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<Box<dyn pass::Definition>>
#[derive(Debug, Clone)]
pub struct Pipeline {
passes: Rc<[Box<dyn pass::Definition>]>,
}
impl {
impl Pipeline {
/// Constructor.
pub fn new() -> Self {
default()
pub fn new(passes: Rc<[Box<dyn pass::Definition>]>) -> Self {
Self { passes }
}
/// Getter.
pub fn passes_clone(&self) -> Vec<Box<dyn pass::Definition>> {
self.passes.clone()
}
}}
impl<Pass: pass::Definition> Add<Pass> 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<dyn pass::Definition>] {
&self.passes
}
}
impl Default for Pipeline {
fn default() -> Self {
Self { passes: Rc::new([]) }
}
}

View File

@ -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<web::HtmlDivElement>,
variables: &UniformScope,
variables: &mut UniformScope,
display_mode: &Rc<Cell<glsl::codes::DisplayModes>>,
) -> 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<Dom>,
variables: UniformScope,
pub pipeline: Rc<CloneCell<render::Pipeline>>,
pub composer: Rc<RefCell<Option<render::Composer>>>,
variables: Rc<RefCell<UniformScope>>,
pub pipeline: CloneCell<render::Pipeline>,
pub composer: RefCell<Option<render::Composer>>,
}
impl Renderer {
fn new(dom: &Rc<Dom>, variables: &UniformScope) -> Self {
fn new(dom: &Rc<Dom>, variables: &Rc<RefCell<UniformScope>>) -> 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<RefCell<HashMap<TypeId, Box<dyn Any>>>>,
map: RefCell<HashMap<TypeId, Box<dyn Any>>>,
}
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<Dom>,
pub context: Rc<RefCell<Option<Context>>>,
pub variables: UniformScope,
pub variables: Rc<RefCell<UniformScope>>,
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<Cell<bool>>,
pub shader_compiler: shader::compiler::Controller,
initial_shader_compilation: Rc<Cell<TaskState>>,
initial_shader_compilation: Cell<TaskState>,
display_mode: Rc<Cell<glsl::codes::DisplayModes>>,
extensions: Extensions,
disable_context_menu: Rc<EventListenerHandle>,
disable_context_menu: EventListenerHandle,
#[derivative(Debug = "ignore")]
on_set_context: RefCell<Vec<Weak<dyn Fn(Option<&Context>)>>>,
}
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<F: 'static + Fn(Option<&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<SceneData> {
/// 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,
})
}
}

View File

@ -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<Name: Str>(
fn new<Name: Str>(
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<TextureBindGuard> {
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<VertexArrayObjectData>,
}
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<Self, ContextLost> {
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<Box<dyn Fn()>>;
// === 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<VertexArrayObject>,
uniforms: Vec<UniformBinding>,
@ -317,7 +303,8 @@ pub struct Symbol {
#[display_object]
data: Rc<SymbolData>,
shader_dirty: ShaderDirty,
shader: Shader,
/// The GL shader.
pub shader: Rc<RefCell<Shader>>,
}
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<RefCell<UniformScope>>) {
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<shader::VarBinding> {
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<F: FnOnce(&WebGlProgram)>(&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<SymbolData>,
shader_dirty: WeakShaderDirty,
shader: WeakShader,
shader: Weak<RefCell<Shader>>,
}
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<UniformScope>,
global_id_provider: GlobalInstanceIdProvider,
display_object: display::object::Instance,
surface: Mesh,
surface_dirty: GeometryDirty,
variables: UniformScope,
context: RefCell<Option<Context>>,
bindings: RefCell<Bindings>,
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<RefCell<UniformScope>>,
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!(),
};

View File

@ -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()

View File

@ -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<M: Into<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<M: Into<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);

View File

@ -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<OnMut:callback::NoArgs>
(stats:&Stats, on_mut:OnMut) -> Self {
pub fn new<OnMut: callback::NoArgs>(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<S:Str>(&self, name:S) -> Option<ScopeType> {
pub fn lookup_variable<S: Str>(&self, name: S) -> Option<ScopeType> {
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();
}

View File

@ -88,7 +88,7 @@ pub struct SymbolRegistry {
view_projection: Uniform<Matrix4<f32>>,
z_zoom_1: Uniform<f32>,
pub display_mode: Uniform<i32>,
pub variables: UniformScope,
pub variables: Rc<RefCell<UniformScope>>,
context: Rc<RefCell<Option<Context>>>,
pub stats: Stats,
next_id: Rc<Cell<u32>>,
@ -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::<f32>::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,

View File

@ -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<Box<dyn FnMut()>>;
shared! { Shader
/// Shader keeps track of a shader and related WebGL Program.
#[derive(Debug)]
pub struct ShaderData {
context : Option<Context>,
geometry_material : Material,
surface_material : Material,
program : Rc<RefCell<Option<shader::Program>>>,
shader_compiler_job : Option<shader_compiler::JobHandle>,
dirty : Dirty,
stats : Stats,
profiler : Option<profiler::Debug>,
pub struct Shader {
context: Option<Context>,
geometry_material: Material,
surface_material: Material,
program: Rc<RefCell<Option<shader::Program>>>,
shader_compiler_job: Option<shader_compiler::JobHandle>,
dirty: Dirty,
stats: Stats,
profiler: Option<profiler::Debug>,
}
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<M:Into<Material>>(&mut self, material:M) {
pub fn set_geometry_material<M: Into<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<M:Into<Material>>(&mut self, material:M) {
pub fn set_material<M: Into<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<OnMut:callback::NoArgs>(stats:&Stats, on_mut:OnMut) -> Self {
pub fn new<OnMut: callback::NoArgs>(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<F: 'static + Fn(&[VarBinding], &shader::Program)>
(&mut self, bindings:Vec<VarBinding>, 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<F: 'static + FnOnce(&[VarBinding], &shader::Program)>(
&mut self,
bindings: Vec<VarBinding>,
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<Item=(String, VarDecl)> {
pub fn collect_variables(&self) -> impl Iterator<Item = (String, VarDecl)> {
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();
}

View File

@ -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<RefCell<Weak<Cell<usize>>>>,
slow_frame_count: Rc<Cell<usize>>,
fast_frame_count: Rc<Cell<usize>>,
restore_context: Rc<RefCell<Option<crate::system::gpu::context::extension::WebglLoseContext>>>,
}
impl WorldData {
@ -449,7 +451,7 @@ impl WorldData {
let on_change = f!(scene_dirty.set());
let display_mode = Rc::<Cell<glsl::codes::DisplayModes>>::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<dyn Fn(JsValue)> = Closure::new(move |val: JsValue| {
let event = val.unchecked_into::<web::KeyboardEvent>();
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<dyn pass::Definition> {
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::<u8>::new(&self.default_scene.mouse.position);
pixel_read_pass.set_callback(f!([garbage_collector](v) {
let on_read = f!([garbage_collector](v: Vec<u8>) {
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::<u8>::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<Vec<Results>>) {

View File

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

View File

@ -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::*;

View File

@ -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<web_sys::WebGlFramebuffer, ContextLost> {
self.native.create_framebuffer().ok_or(ContextLost)
}
pub fn create_buffer(&self) -> Result<web_sys::WebGlBuffer, ContextLost> {
self.native.create_buffer().ok_or(ContextLost)
}
pub fn create_texture(&self) -> Result<web_sys::WebGlTexture, ContextLost> {
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<dyn Fn(Option<&Context>)>);
impl ContextHandler {
/// Wrap the handle.
pub fn new(rc: Rc<dyn Fn(Option<&Context>)>) -> 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<D: Display + 'static>(
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<web_sys::Event>;
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);

View File

@ -36,6 +36,7 @@ impl Extensions {
pub struct ExtensionsData {
pub khr_parallel_shader_compile: Option<KhrParallelShaderCompile>,
pub ext_disjoint_timer_query_webgl2: Option<ExtDisjointTimerQueryWebgl2>,
pub webgl_lose_context: Option<WebglLoseContext>,
}
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<Self> {
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();
}
}

View File

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

View File

@ -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<Self, ContextLost> {
let buffer = context.create_buffer()?;
let context = context;
Ok(Self { context, buffer })
}
}
@ -269,9 +270,9 @@ impl<T:Storable> {
/// 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<T> Drop for BufferData<T> {
}
// === Utils ===
fn create_gl_buffer(context: &Context) -> WebGlBuffer {
let buffer = context.create_buffer();
buffer.ok_or("Failed to create WebGL buffer.").unwrap()
}
// =================
// === AnyBuffer ===

View File

@ -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<Self, ContextLost> {
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<T: JsBufferViewArr + bytemuck::Pod>(&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::<u32>());
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::<u32>());
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
}
}

View File

@ -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::<u32>());
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<Storage,InternalFormat,ItemType>:Clone"))]
#[derivative(Debug(bound = "StorageOf<Storage,InternalFormat,ItemType>:Debug"))]
pub struct Texture<Storage, InternalFormat, ItemType>
where Storage: StorageRelation<InternalFormat, ItemType> {
storage: StorageOf<Storage, InternalFormat, ItemType>,
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<S, I, T> Texture<S, I, T>
where
S: StorageRelation<I, T>,
I: InternalFormat,
T: ItemType,
{
/// Internal format instance of this texture.
pub fn internal_format() -> AnyInternalFormat {
<I>::default().into()
}
/// Format instance of this texture.
pub fn format() -> AnyFormat {
<I>::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 {
<T>::gl_enum().into()
}
/// Element type of this texture.
pub fn item_type() -> AnyItemType {
ZST::<T>().into()
}
}
// === Getters ===
impl<S, I, T> Texture<S, I, T>
where S: StorageRelation<I, T>
{
/// Getter.
pub fn gl_texture(&self) -> &WebGlTexture {
&self.gl_texture
}
/// Getter.
pub fn context(&self) -> &Context {
&self.context
}
/// Getter.
pub fn storage(&self) -> &StorageOf<S, I, T> {
&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<S, I, T> Texture<S, I, T>
where S: StorageRelation<I, T>
{
/// Setter.
pub fn set_parameters(&mut self, parameters: Parameters) {
self.parameters = parameters;
}
}
// === Constructors ===
impl<S: StorageRelation<I, T>, I: InternalFormat, T: ItemType> Texture<S, I, T>
where Self: TextureReload
{
/// Constructor.
pub fn new<P: Into<StorageOf<S, I, T>>>(context: &Context, provider: P) -> Self {
let this = Self::new_uninitialized(context, provider);
this.reload();
this
}
}
// === Destructos ===
impl<S, I, T> Drop for Texture<S, I, T>
where S: StorageRelation<I, T>
{
fn drop(&mut self) {
self.context.delete_texture(Some(&self.gl_texture));
}
}
// === Internal API ===
impl<S, I, T> Texture<S, I, T>
where S: StorageRelation<I, T>
{
/// New, uninitialized constructor. If you are not implementing a custom texture format, you
/// should probably use `new` instead.
pub fn new_uninitialized<X: Into<StorageOf<S, I, T>>>(context: &Context, storage: X) -> Self {
let storage = storage.into();
let context = context.clone();
let gl_texture = context.create_texture().unwrap();
let parameters = default();
Self { storage, gl_texture, context, parameters }
}
/// Applies this textures' parameters in the given context.
pub fn apply_texture_parameters(&self, context: &Context) {
self.parameters.apply_parameters(context, self.target());
}
}
impl<S, I, T> Texture<S, I, T>
where
S: StorageRelation<I, T>,
I: InternalFormat,
T: ItemType + JsBufferViewArr,
{
/// Reloads gpu texture with data from given slice.
pub fn reload_from_memory(&self, data: &[T], width: i32, height: i32, depth: i32) {
let target = self.target();
let level = 0;
let border = 0;
let internal_format = Self::gl_internal_format();
let format = Self::gl_format().into();
let elem_type = Self::gl_elem_type();
let data: &[u8] = bytemuck::cast_slice(data);
self.context.bind_texture(*target, Some(&self.gl_texture));
match target {
Context::TEXTURE_2D => {
self.context
.tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array(
*target,
level,
internal_format,
width,
height,
border,
format,
elem_type,
Some(data),
)
.unwrap();
}
Context::TEXTURE_2D_ARRAY | Context::TEXTURE_3D => {
self.context
.tex_image_3d_with_opt_u8_array(
*target,
level,
internal_format,
width,
height,
depth,
border,
format,
elem_type,
Some(data),
)
.unwrap();
}
_ => {
panic!("Unsupported texture target: {target:?}");
}
}
self.apply_texture_parameters(&self.context);
}
}
// === Instances ===
impl<S: StorageRelation<I, T>, I, T> HasItem for Texture<S, I, T> {
type Item = Texture<S, I, T>;
}
impl<S: StorageRelation<I, T>, I, T> ItemRef for Texture<S, I, T> {
fn item(&self) -> &Self::Item {
self
}
}
// ==================
// === TextureOps ===
// ==================
/// API of the texture. It is defined as trait and uses the `WithContent` mechanism in order for
/// uniforms to easily redirect the methods.
pub trait TextureOps {
/// Bind texture to a specific unit.
fn bind_texture_unit(&self, context: &Context, unit: TextureUnit) -> TextureBindGuard;
/// Accessor.
fn gl_texture(&self) -> WebGlTexture;
/// Accessor.
fn get_format(&self) -> AnyFormat;
/// Accessor.
fn get_item_type(&self) -> AnyItemType;
}
impl<
P: WithItemRef<Item = Texture<S, I, T>>,
S: StorageRelation<I, T>,
I: InternalFormat,
T: ItemType,
> TextureOps for P
{
fn bind_texture_unit(&self, context: &Context, unit: TextureUnit) -> TextureBindGuard {
self.with_item(|this| {
let context = context.clone();
let target = this.target();
context.active_texture(*Context::TEXTURE0 + unit.to::<u32>());
context.bind_texture(*target, Some(&this.gl_texture));
context.active_texture(*Context::TEXTURE0);
TextureBindGuard { context, target, unit }
})
}
fn gl_texture(&self) -> WebGlTexture {
self.with_item(|this| this.gl_texture.clone())
}
fn get_format(&self) -> AnyFormat {
self.with_item(|_| <Texture<S, I, T>>::format())
}
fn get_item_type(&self) -> AnyItemType {
self.with_item(|_| <Texture<S, I, T>>::item_type())
}
}

View File

@ -1,47 +0,0 @@
//! This is a root module for all texture storage definitions.
use crate::prelude::*;
use crate::system::gpu::Context;
use crate::system::gpu::GlEnum;
// ==============
// === Export ===
// ==============
pub mod gpu_only;
pub mod owned;
pub mod remote_image;
pub use gpu_only::*;
pub use owned::*;
pub use remote_image::*;
// ===============
// === Storage ===
// ===============
/// Trait describing any storage texture type.
pub trait Storage = Debug + Default + Into<AnyStorage> + PhantomInto<AnyStorage> + 'static;
/// Type level accessor of the storage implementation for a given set of texture parameters.
pub type StorageOf<S, I, T> = <S as StorageRelation<I, T>>::Storage;
/// The storage implementation type family.
pub trait StorageRelation<InternalFormat, ElemType>: Storage {
/// The storage implementation.
type Storage: Debug;
/// Texture bind target. Usually it is `TEXTURE_2D` or `TEXTURE_2D_ARRAY`.
fn target(storage: &Self::Storage) -> GlEnum {
let _ = storage;
Context::TEXTURE_2D
}
}
enso_shapely::define_singleton_enum! {
AnyStorage {RemoteImage, GpuOnly, Owned}
}

View File

@ -1,126 +0,0 @@
//! This module defines a texture storage which do not keep local data. It only keeps a reference
//! to GPU texture of a given size.
use crate::system::gpu::data::texture::class::*;
use crate::system::gpu::data::texture::storage::*;
use crate::system::gpu::data::texture::types::*;
use crate::system::gpu::data::buffer::item::JsBufferViewArr;
use crate::system::gpu::Context;
use crate::system::gpu::GlEnum;
// ===============
// === GpuOnly ===
// ===============
/// Sized, uninitialized texture.
#[derive(Clone, Copy, Debug)]
pub struct GpuOnlyData {
/// Texture width.
pub width: i32,
/// Texture height.
pub height: i32,
/// Number of texture layers. When the texture is not layered, this value is 0.
pub layers: i32,
}
// === Instances ===
impl<I, T> StorageRelation<I, T> for GpuOnly {
type Storage = GpuOnlyData;
fn target(storage: &GpuOnlyData) -> GlEnum {
match storage.layers {
0 => Context::TEXTURE_2D,
_ => Context::TEXTURE_2D_ARRAY,
}
}
}
impl From<(i32, i32)> for GpuOnlyData {
fn from(t: (i32, i32)) -> Self {
let (width, height) = t;
Self { width, height, layers: 0 }
}
}
impl From<((i32, i32), i32)> for GpuOnlyData {
fn from(t: ((i32, i32), i32)) -> Self {
let ((width, height), layers) = t;
Self { width, height, layers }
}
}
// === API ===
impl GpuOnlyData {
fn new(width: i32, height: i32) -> Self {
Self { width, height, layers: 0 }
}
}
impl<I: InternalFormat, T: ItemType> TextureReload for Texture<GpuOnly, I, T> {
fn reload(&self) {
let GpuOnlyData { width, height, layers } = *self.storage();
let level = 0;
let border = 0;
let internal_format = Self::gl_internal_format();
let format = Self::gl_format().into();
let elem_type = Self::gl_elem_type();
let target = self.target();
self.context().bind_texture(*target, Some(self.gl_texture()));
match target {
Context::TEXTURE_2D => {
self.context()
.tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array(
*target,
level,
internal_format,
width,
height,
border,
format,
elem_type,
None,
)
.unwrap();
}
Context::TEXTURE_2D_ARRAY | Context::TEXTURE_3D => {
self.context()
.tex_image_3d_with_opt_u8_array(
*target,
level,
internal_format,
width,
height,
layers,
border,
format,
elem_type,
None,
)
.unwrap();
}
_ => {
panic!("Unsupported texture target: {target:?}");
}
}
self.apply_texture_parameters(self.context());
}
}
impl<I: InternalFormat, T: ItemType + JsBufferViewArr> Texture<GpuOnly, I, T> {
/// Reload texture with given content. The data will be copied to gpu, but the texture will not
/// take ownership.
pub fn reload_with_content(&self, data: &[T]) {
let GpuOnlyData { width, height, layers } = *self.storage();
self.reload_from_memory(data, width, height, layers);
}
}

View File

@ -1,54 +0,0 @@
//! This module defines an owned texture storage type. It keeps the texture data in a local memory.
use crate::system::gpu::data::texture::class::*;
use crate::system::gpu::data::texture::storage::*;
use crate::system::gpu::data::texture::types::*;
use crate::system::gpu::data::buffer::item::JsBufferViewArr;
use crate::system::gpu::data::texture;
// =============
// === Owned ===
// =============
/// Texture plain data.
#[derive(Debug)]
pub struct OwnedData<T> {
/// An array containing texture data.
pub data: Vec<T>,
/// Texture width.
pub width: i32,
/// Texture height.
pub height: i32,
}
// === Instances ===
impl<I, T: Debug> StorageRelation<I, T> for texture::storage::Owned {
type Storage = OwnedData<T>;
}
impl<T> OwnedData<T> {
fn new(data: Vec<T>, width: i32, height: i32) -> Self {
Self { data, width, height }
}
}
// === API ===
impl<I: InternalFormat, T: ItemType + JsBufferViewArr> TextureReload
for Texture<texture::storage::Owned, I, T>
{
#[allow(unsafe_code)]
fn reload(&self) {
let storage = &self.storage();
let data = storage.data.as_slice();
let width = storage.width;
let height = storage.height;
self.reload_from_memory(data, width, height, 0);
}
}

View File

@ -1,152 +0,0 @@
//! This module defines remote image texture storage. It is used to download image from a given URL.
use crate::system::gpu::data::texture::class::*;
use crate::system::gpu::data::texture::storage::*;
use crate::system::gpu::data::texture::types::*;
use crate::system::gpu::Context;
#[cfg(target_arch = "wasm32")]
use crate::system::web;
#[cfg(target_arch = "wasm32")]
use web::Closure;
#[cfg(target_arch = "wasm32")]
use web_sys::HtmlImageElement;
// ===================
// === RemoteImage ===
// ===================
/// Texture downloaded from URL. This source implies asynchronous loading.
#[derive(Debug)]
pub struct RemoteImageData {
/// An url from where the texture is downloaded.
pub url: String,
}
// === Instances ===
impl<I, T> StorageRelation<I, T> for RemoteImage {
type Storage = RemoteImageData;
}
impl<S: Str> From<S> for RemoteImageData {
fn from(s: S) -> Self {
Self::new(s)
}
}
// === API ===
impl RemoteImageData {
fn new<S: Str>(url: S) -> Self {
Self { url: url.into() }
}
}
impl<I: InternalFormat, T: ItemType> Texture<RemoteImage, I, T> {
/// Initializes default texture value. It is useful when the texture data needs to be downloaded
/// asynchronously. This method creates a mock 1px x 1px texture and uses it as a mock texture
/// until the download is complete.
pub fn init_mock(&self) {
let target = Context::TEXTURE_2D;
let level = 0;
let internal_format = Self::gl_internal_format();
let format = Self::gl_format().into();
let elem_type = Self::gl_elem_type();
let width = 1;
let height = 1;
let border = 0;
let color = vec![0, 0, 255, 255];
self.context().bind_texture(*Context::TEXTURE_2D, Some(self.gl_texture()));
self.context()
.tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array(
*target,
level,
internal_format,
width,
height,
border,
format,
elem_type,
Some(&color),
)
.unwrap();
}
}
impl<I: InternalFormat, T: ItemType> TextureReload for Texture<RemoteImage, I, T> {
/// Loads or re-loads the texture data from the provided url.
/// This action will be performed asynchronously.
#[allow(trivial_casts)]
#[cfg(target_arch = "wasm32")]
fn reload(&self) {
let url = &self.storage().url;
let image = HtmlImageElement::new().unwrap();
let no_callback = <Option<web::EventListenerHandle>>::None;
let callback_ref = Rc::new(RefCell::new(no_callback));
let image_ref = Rc::new(RefCell::new(image));
let callback_ref2 = callback_ref.clone();
let image_ref_opt = image_ref.clone();
let context = self.context().clone();
let gl_texture = self.gl_texture().clone();
let target = self.target();
let parameters = *self.parameters();
let callback: web::JsEventHandler = Closure::once(move |_| {
let _keep_alive = callback_ref2;
let image = image_ref_opt.borrow();
let level = 0;
let internal_format = Self::gl_internal_format();
let format = Self::gl_format().into();
let elem_type = Self::gl_elem_type();
context.bind_texture(*target, Some(&gl_texture));
context
.tex_image_2d_with_u32_and_u32_and_html_image_element(
*target,
level,
internal_format,
format,
elem_type,
&image,
)
.unwrap();
parameters.apply_parameters(&context, target);
}) as web::JsEventHandler;
let image = image_ref.borrow();
request_cors_if_not_same_origin(&image, url);
image.set_src(url);
let handler = web::add_event_listener_with_bool(&image, "load", callback, true);
*callback_ref.borrow_mut() = Some(handler);
}
#[cfg(not(target_arch = "wasm32"))]
fn reload(&self) {}
}
// === Utils ===
/// CORS = Cross Origin Resource Sharing. It's a way for the webpage to ask the image server for
/// permission to use the image. To do this we set the crossOrigin attribute to something and then
/// when the browser tries to get the image from the server, if it's not the same domain, the
/// browser will ask for CORS permission. The string we set `cross_origin` to is sent to the server.
/// The server can look at that string and decide whether or not to give you permission. Most
/// servers that support CORS don't look at the string, they just give permission to everyone.
///
/// **Note**
/// Why don't want to just always see the permission because asking for permission takes 2 HTTP
/// requests, so it's slower than not asking. If we know we're on the same domain or we know we
/// won't use the image for anything except img tags and or canvas2d then we don't want to set
/// crossDomain because it will make things slower.
#[cfg(target_arch = "wasm32")]
fn request_cors_if_not_same_origin(img: &HtmlImageElement, url_str: &str) {
let url = web_sys::Url::new(url_str).unwrap();
let origin = web::window.location().origin().unwrap();
if url.origin() != origin {
img.set_cross_origin(Some(""));
}
}

View File

@ -116,3 +116,21 @@ macro_rules! generate_internal_format_instances_item {
}
crate::with_texture_format_relations!(generate_internal_format_instances []);
/// Generate a function that returns the format for any `InternalFormat`.
#[macro_export]
macro_rules! generate_format_map {
([] $( $internal_format:ident $format:ident $sampler:ident
$renderable:tt $filterable:tt $blendable:tt $elem_descs:tt)* ) => {
impl AnyInternalFormat {
/// Return the [`Format`] corresponding to this internal format.
pub fn format(self) -> AnyFormat {
match self {
$(<AnyInternalFormat>::$internal_format => <AnyFormat>::$format),*
}
}
}
}
}
crate::with_texture_format_relations!(generate_format_map []);

View File

@ -102,7 +102,7 @@ macro_rules! with_texture_format_relations { ($f:ident $args:tt) => { $crate::$f
#[macro_export]
macro_rules! with_all_texture_types_cartesians {
([$f:ident] [$($out:tt)*]) => {
shapely::cartesian! { [$f] [Owned GpuOnly RemoteImage] [$($out)*] }
shapely::cartesian! { [$f] [GpuOnly] [$($out)*] }
};
([$f:ident _] $out:tt) => {
$f! { $out }

View File

@ -8,9 +8,7 @@ use enum_dispatch::*;
use crate::system::gpu::Context;
use enso_shapely::shared;
use upload::UniformUpload;
use web_sys::WebGlTexture;
use web_sys::WebGlUniformLocation;
@ -35,66 +33,65 @@ pub trait UniformValue = Sized where Uniform<Self>: Into<AnyUniform>;
// === UniformScope ===
// ====================
shared! { UniformScope
/// A scope containing set of uniform values.
#[derive(Debug, Default)]
pub struct UniformScopeData {
pub struct UniformScope {
map: HashMap<String, AnyUniform>,
}
impl {
impl UniformScope {
/// Constructor.
pub fn new() -> Self {
default()
}
/// Look up uniform by name.
pub fn get<Name:Str>(&self, name:Name) -> Option<AnyUniform> {
pub fn get<Name: Str>(&self, name: Name) -> Option<AnyUniform> {
self.map.get(name.as_ref()).cloned()
}
/// Checks if uniform of a given name was defined in this scope.
pub fn contains<Name:Str>(&self, name:Name) -> bool {
pub fn contains<Name: Str>(&self, name: Name) -> bool {
self.map.contains_key(name.as_ref())
}
/// Add a new uniform with a given name and initial value. Returns `None` if the name is in use.
pub fn add<Name, Value, Input>(&mut self, name:Name, input:Input) -> Option<Uniform<Value>>
pub fn add<Name, Value, Input>(&mut self, name: Name, input: Input) -> Option<Uniform<Value>>
where
Name: Str,
Input: Into<Uniform<Value>>,
Value: UniformValue {
self.add_or_else(name, input, Some, |_,_,_| None)
Value: UniformValue, {
self.add_or_else(name, input, Some, |_, _, _| None)
}
/// Add a new uniform with a given name and initial value. Panics if the name is in use.
pub fn add_or_panic<Name, Value, Input>(&mut self, name:Name, input:Input) -> Uniform<Value>
pub fn add_or_panic<Name, Value, Input>(&mut self, name: Name, input: Input) -> Uniform<Value>
where
Name:Str,
Name: Str,
Input: Into<Uniform<Value>>,
Value:UniformValue {
self.add_or_else(name,input,|t|{t},|name,_,_| {
panic!("Trying to override uniform '{}'.", name.as_ref())
})
Value: UniformValue, {
self.add_or_else(
name,
input,
|t| t,
|name, _, _| panic!("Trying to override uniform '{}'.", name.as_ref()),
)
}
pub fn add_uniform<Name, Value>(&mut self, name: Name, uniform: &Uniform<Value>)
where
Name: Str,
Value: UniformValue {
Value: UniformValue, {
self.add::<Name, Value, _>(name, uniform);
}
pub fn add_uniform_or_panic<Name, Value>(&mut self, name:Name, uniform:&Uniform<Value>)
pub fn add_uniform_or_panic<Name, Value>(&mut self, name: Name, uniform: &Uniform<Value>)
where
Name:Str,
Value:UniformValue {
Name: Str,
Value: UniformValue, {
self.add_or_panic::<Name, Value, _>(name, uniform);
}
}}
impl UniformScopeData {
/// Adds a new uniform with a given name and initial value. In case the name was already in use,
/// it fires the `on_exist` function. Otherwise, it fires the `on_fresh` function on the newly
/// created uniform.
@ -141,18 +138,6 @@ impl UniformScopeData {
}
}
impl UniformScope {
/// Gets an existing uniform or adds a new one in case it was missing. Returns `None` if the
/// uniform exists but its type does not match the requested one.
pub fn set<Name, Value>(&self, name: Name, value: Value) -> Option<Uniform<Value>>
where
Name: Str,
Value: UniformValue,
for<'t> &'t Uniform<Value>: TryFrom<&'t AnyUniform>, {
self.rc.borrow_mut().set(name, value)
}
}
// ===============
@ -169,69 +154,48 @@ impl UniformScope {
// Please note that currently a special uniform 'zoom' is modified in the render loop. See
// the `scene::View` implementation to learn more.
shared! { Uniform
/// An uniform value.
#[derive(Debug)]
pub struct UniformData<Value> {
value: Value,
// dirty: bool,
#[derive(Debug, CloneRef, Derivative)]
#[derivative(Clone(bound = ""))]
pub struct Uniform<Value> {
value: Rc<RefCell<Value>>,
}
impl<Value> {
impl<Value> Uniform<Value> {
/// Constructor.
pub fn new(value:Value) -> Self {
pub fn new(value: Value) -> Self {
let value = Rc::new(RefCell::new(value));
// let dirty = true;
Self {value}
Self { value }
}
/// Sets the value of this uniform.
pub fn set(&mut self, value:Value) {
pub fn set(&self, value: Value) {
// self.set_dirty();
self.value = value;
*self.value.borrow_mut() = value;
}
/// Modifies the value of this uniform.
pub fn modify(&mut self, f: impl FnOnce(&mut Value)) {
f(&mut self.value);
pub fn modify(&self, f: impl FnOnce(&mut Value)) {
f(&mut *self.value.borrow_mut());
}
// /// Checks whether the uniform was changed and not yet updated.
// pub fn check_dirty(&self) -> bool {
// self.dirty
// }
// /// Sets the dirty flag.
// pub fn set_dirty(&mut self) {
// self.dirty = true;
// }
//
// /// Clears the dirty flag.
// pub fn unset_dirty(&mut self) {
// self.dirty = false;
// }
}
impl<Value:Clone> {
impl<Value: Clone> Uniform<Value> {
/// Reads the value of this uniform.
pub fn get(&self) -> Value {
self.value.clone()
self.value.borrow().clone()
}
}}
}
impl<Value> Uniform<Value> {
pub fn swap(&self, that: &Self) {
self.rc.borrow_mut().swap(&mut *that.rc.borrow_mut())
if !Rc::ptr_eq(&self.value, &that.value) {
mem::swap(&mut *self.value.borrow_mut(), &mut *that.value.borrow_mut())
}
}
}
impl<Value> UniformData<Value> {
pub fn swap(&mut self, that: &mut Self) {
mem::swap(self, that)
}
}
impl<T> From<T> for Uniform<T> {
fn from(t: T) -> Self {
Self::new(t)
@ -250,7 +214,7 @@ impl<T> HasItem for Uniform<T> {
impl<T> WithItemRef for Uniform<T> {
fn with_item<R>(&self, f: impl FnOnce(&Self::Item) -> R) -> R {
f(&self.rc.borrow().value)
f(&self.value.borrow())
}
}
@ -295,13 +259,7 @@ pub trait AnyPrimUniformOps {
impl<Value: UniformUpload> AnyPrimUniformOps for Uniform<Value> {
fn upload(&self, context: &Context, location: &WebGlUniformLocation) {
self.rc.borrow().upload(context, location)
}
}
impl<Value: UniformUpload> AnyPrimUniformOps for UniformData<Value> {
fn upload(&self, context: &Context, location: &WebGlUniformLocation) {
self.value.upload_uniform(context, location)
self.value.borrow().upload_uniform(context, location)
}
}
@ -311,117 +269,30 @@ impl<Value: UniformUpload> AnyPrimUniformOps for UniformData<Value> {
// === AnyTextureUniform ===
// =========================
macro_rules! define_any_texture_uniform {
( [ $([$storage:ident $internal_format:ident $item_type:ident])* ] ) => { paste! {
#[allow(non_camel_case_types)]
#[derive(Clone,CloneRef,Debug)]
pub enum AnyTextureUniform {
$([<$storage _ $internal_format _ $item_type >]
(Uniform<Texture<$storage,$internal_format,$item_type>>)),*
}
impl AnyTextureUniform {
pub fn try_swap(&self, that:&Self) -> bool {
match (self,that) {
$(
( Self::[<$storage _ $internal_format _ $item_type >](a)
, Self::[<$storage _ $internal_format _ $item_type >](b)
) => {a.swap(b); true},
)*
_ => false
}
}
}
impl TextureOps for AnyTextureUniform {
fn bind_texture_unit
(&self, context:&Context, unit:TextureUnit) -> TextureBindGuard {
match self {
$(
Self::[<$storage _ $internal_format _ $item_type >](t) =>
t.bind_texture_unit(context,unit)
),*
}
}
fn gl_texture(&self) -> WebGlTexture {
match self {
$(Self::[<$storage _ $internal_format _ $item_type >](t) => t.gl_texture()),*
}
}
fn get_format(&self) -> AnyFormat {
match self {
$(Self::[<$storage _ $internal_format _ $item_type >](t) => t.get_format()),*
}
}
fn get_item_type(&self) -> AnyItemType {
match self {
$(Self::[<$storage _ $internal_format _ $item_type >](t) => t.get_item_type()),*
}
}
}
$(
impl From<Uniform<Texture<$storage,$internal_format,$item_type>>>
for AnyTextureUniform {
fn from(t:Uniform<Texture<$storage,$internal_format,$item_type>>) -> Self {
Self::[<$storage _ $internal_format _ $item_type >](t)
}
}
impl<'t> TryFrom<&'t AnyTextureUniform>
for &'t Uniform<Texture<$storage,$internal_format,$item_type>> {
type Error = TypeMismatch;
fn try_from(value:&'t AnyTextureUniform) -> Result<Self,Self::Error> {
match value {
AnyTextureUniform::[<$storage _ $internal_format _ $item_type >](t) => Ok(t),
_ => Err(TypeMismatch),
}
}
}
)*
}}
#[allow(non_camel_case_types)]
#[derive(Clone, CloneRef, Debug)]
pub struct AnyTextureUniform {
texture: Uniform<Option<Texture>>,
}
macro_rules! define_get_or_add_gpu_texture_dyn {
( [ $([$internal_format:ident $item_type:ident])* ] ) => {
pub fn get_or_add_gpu_texture_dyn<P:Into<GpuOnlyData>>
( context : &Context
, scope : &UniformScope
, name : &str
, internal_format : AnyInternalFormat
, item_type : AnyItemType
, provider : P
, parameters : Option<Parameters>
) -> AnyTextureUniform {
let provider = provider.into();
match (internal_format,item_type) {
$((AnyInternalFormat::$internal_format, AnyItemType::$item_type) => {
let mut texture =
Texture::<GpuOnly,$internal_format,$item_type>::new(&context,provider);
if let Some(parameters) = parameters {
texture.set_parameters(parameters);
}
let uniform = scope.set(name,texture).unwrap();
uniform.into()
})*
_ => panic!("Invalid internal format and item type combination ({:?},{:?}).",
internal_format,item_type)
}
}
impl AnyTextureUniform {
pub fn texture(&self) -> Option<Ref<Texture>> {
Ref::filter_map(self.texture.value.borrow(), |texture| texture.as_ref()).ok()
}
}
macro_rules! generate {
( [ $([$internal_format:ident $item_type:ident])* ] ) => {
define_any_texture_uniform!{[ $([GpuOnly $internal_format $item_type])* ]}
define_get_or_add_gpu_texture_dyn!{[ $([$internal_format $item_type])* ]}
impl From<Uniform<Option<Texture>>> for AnyTextureUniform {
fn from(texture: Uniform<Option<Texture>>) -> Self {
Self { texture }
}
}
crate::with_all_texture_types! ([generate _]);
impl<'t> TryFrom<&'t AnyTextureUniform> for &'t Uniform<Option<Texture>> {
type Error = TypeMismatch;
fn try_from(value: &'t AnyTextureUniform) -> Result<Self, Self::Error> {
Ok(&value.texture)
}
}
@ -456,13 +327,7 @@ impl<T: Into<AnyPrimUniform>> IntoAnyUniform for T {
}
}
impl<S, I, T> IntoAnyUniform for Uniform<Texture<S, I, T>>
where
S: StorageRelation<I, T>,
I: InternalFormat,
T: ItemType,
Uniform<Texture<S, I, T>>: Into<AnyTextureUniform>,
{
impl IntoAnyUniform for Uniform<Option<Texture>> {
fn into_any_uniform(self) -> AnyUniform {
AnyUniform::Texture(self.into())
}
@ -484,10 +349,7 @@ macro_rules! generate_prim_type_downcasts {
crate::with_all_prim_types!([[generate_prim_type_downcasts][]]);
impl<'t, S: StorageRelation<I, T>, I: InternalFormat, T: ItemType> TryFrom<&'t AnyUniform>
for &'t Uniform<Texture<S, I, T>>
where &'t Uniform<Texture<S, I, T>>: TryFrom<&'t AnyTextureUniform, Error = TypeMismatch>
{
impl<'t> TryFrom<&'t AnyUniform> for &'t Uniform<Option<Texture>> {
type Error = TypeMismatch;
fn try_from(value: &'t AnyUniform) -> Result<Self, Self::Error> {
match value {

View File

@ -849,9 +849,10 @@ impl Controller {
impl ControllerData {
fn run(&mut self, context: &Context, time: animation::TimeInfo) -> bool {
let was_busy = !context.shader_compiler.idle();
let result = context.shader_compiler.run(time);
let now_idle = context.shader_compiler.idle();
let compiler = context.shader_compiler();
let was_busy = !compiler.idle();
let result = compiler.run(time);
let now_idle = compiler.idle();
if was_busy && now_idle {
self.on_idle.run_all();
}

View File

@ -74,7 +74,7 @@ pub fn main() {
let _keep_alive = &navigator;
i += 1;
if i == 5 {
if let Some(program) = view.sprite.borrow().symbol.shader().program() {
if let Some(program) = view.sprite.borrow().symbol.shader.borrow_mut().program() {
debug!("\n\nVERTEX:\n{}", program.shader.vertex.code);
debug!("\n\nFRAGMENT:\n{}", program.shader.fragment.code);
}