mirror of
https://github.com/enso-org/enso.git
synced 2024-12-22 22:21:40 +03:00
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:
parent
30a62b97bb
commit
b9ec6d4ec3
@ -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
|
||||
|
||||
|
@ -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. |
|
||||
|
@ -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();
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -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 ===
|
||||
|
@ -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,
|
||||
|
@ -72,6 +72,7 @@ features = [
|
||||
'Url',
|
||||
'WebGlBuffer',
|
||||
'WebGlFramebuffer',
|
||||
'WebglLoseContext',
|
||||
'WebGlProgram',
|
||||
'WebGlQuery',
|
||||
'WebGlRenderingContext',
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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) => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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) {}
|
||||
}
|
||||
|
@ -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 })
|
||||
}
|
||||
}
|
||||
|
@ -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([]) }
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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!(),
|
||||
};
|
||||
|
@ -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()
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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>>) {
|
||||
|
@ -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;
|
||||
|
@ -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::*;
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 ===
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
@ -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}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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(""));
|
||||
}
|
||||
}
|
@ -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 []);
|
||||
|
@ -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 }
|
||||
|
@ -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 {
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user