Multi-frame shader compilation (#3378)

This commit is contained in:
Wojciech Daniło 2022-04-12 17:56:38 +02:00 committed by GitHub
parent ef7f04297a
commit 6b7622dd45
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
52 changed files with 2809 additions and 908 deletions

View File

@ -133,7 +133,9 @@ fn init(app: &Application) {
.add(move |time_info: animation::TimeInfo| {
let _keep_alive = &navigator;
let data = generate_data((time_info.local / 1000.0).into());
let data = generate_data(
(time_info.since_animation_loop_started.unchecked_raw() / 1000.0).into(),
);
let data = Rc::new(data);
let content = serde_json::to_value(data).unwrap();
let data = Data::from(content);

View File

@ -3047,8 +3047,8 @@ fn new_graph_editor(app: &Application) -> GraphEditor {
let node_tgt_pos_anim = DEPRECATED_Animation::<Vector2<f32>>::new(network);
let x_snap_strength = DEPRECATED_Tween::new(network);
let y_snap_strength = DEPRECATED_Tween::new(network);
x_snap_strength.set_duration(300.0);
y_snap_strength.set_duration(300.0);
x_snap_strength.set_duration(300.0.ms());
y_snap_strength.set_duration(300.0.ms());
_eval <- node_tgt_pos_rt.map2(&just_pressed,
f!([model,x_snap_strength,y_snap_strength,node_tgt_pos_anim](pos,just_pressed) {

View File

@ -198,7 +198,7 @@ commands.build.rust = async function (argv) {
console.log('Minimizing the WASM binary.')
await gzip(paths.wasm.main, paths.wasm.mainGz)
const limitMb = 4.65
const limitMb = 4.67
await checkWasmSize(paths.wasm.mainGz, limitMb)
}
// Copy WASM files from temporary directory to Webpack's `dist` directory.

View File

@ -5,6 +5,7 @@ use crate::prelude::*;
use crate::animation;
use crate::data::function::Fn1;
use crate::types::unit2::Duration;
use core::f32::consts::PI;
@ -188,7 +189,7 @@ impl<T, F, OnStep, OnEnd> Debug for Animator<T, F, OnStep, OnEnd> {
#[derivative(Debug(bound = "T:Debug+Copy"))]
#[allow(missing_docs)]
pub struct AnimatorData<T, F, OnStep, OnEnd> {
pub duration: Cell<f32>,
pub duration: Cell<Duration>,
pub start_value: Cell<T>,
pub target_value: Cell<T>,
pub value: Cell<T>,
@ -208,7 +209,7 @@ where
OnEnd: Callback<EndStatus>,
{
fn new(start: T, end: T, tween_fn: F, callback: OnStep, on_end: OnEnd) -> Self {
let duration = Cell::new(1000.0);
let duration = Cell::new(1.0.s());
let value = Cell::new(start);
let start_value = Cell::new(start);
let target_value = Cell::new(end);
@ -216,7 +217,7 @@ where
Self { duration, start_value, target_value, value, active, tween_fn, callback, on_end }
}
fn step(&self, time: f32) {
fn step(&self, time: Duration) {
let sample = (time / self.duration.get()).min(1.0);
let weight = (self.tween_fn)(sample);
let value = self.start_value.get() * (1.0 - weight) + self.target_value.get() * weight;
@ -350,7 +351,7 @@ where
self.data.target_value.set(t);
}
pub fn set_duration(&self, t: f32) {
pub fn set_duration(&self, t: Duration) {
self.data.duration.set(t);
}
}
@ -422,7 +423,7 @@ where
let animation_loop = easing.animation_loop.downgrade();
move |time: animation::TimeInfo| {
if data.active.get() {
data.step(time.local)
data.step(time.since_animation_loop_started)
} else if let Some(animation_loop) = animation_loop.upgrade() {
animation_loop.set(None);
}

View File

@ -58,7 +58,7 @@ impl Easing {
) -> Self {
let frp = &self.frp;
frp::extend! { network
eval frp.set_duration ((t) animator.set_duration(*t));
eval frp.set_duration ((t) animator.set_duration((*t).ms()));
eval frp.target ((t) animator.from_now_to(*t));
eval frp.stop_and_rewind ((t) animator.stop_and_rewind_to(*t));
}

View File

@ -4,6 +4,7 @@ use crate::prelude::*;
use crate::system::web::traits::*;
use crate::system::web;
use crate::types::unit2::Duration;
use web::Closure;
@ -14,18 +15,16 @@ use web::Closure;
// ================
/// Note: the `start` field will be computed on first run. We cannot compute it upfront, as other
/// time functions, like `performance.now()` can output nor precise results. The exact results
/// time functions, like `performance.now()` can output not precise results. The exact results
/// differ across browsers and browser versions. We have even observed that `performance.now()` can
/// sometimes provide a bigger value than time provided to `requestAnimationFrame` callback later,
/// which resulted in a negative frame time.
#[derive(Clone, Copy, Debug, Default)]
#[allow(missing_docs)]
pub struct TimeInfo {
/// Start time of the animation loop.
pub start: f32,
/// The last frame time.
pub frame: f32,
/// The time which passed since the animation loop was started.
pub local: f32,
pub animation_loop_start: Duration,
pub previous_frame: Duration,
pub since_animation_loop_started: Duration,
}
impl TimeInfo {
@ -33,6 +32,12 @@ impl TimeInfo {
pub fn new() -> Self {
default()
}
/// Check whether the time info was initialized. See the documentation of the struct to learn
/// more.
pub fn is_initialized(&self) -> bool {
self.animation_loop_start != 0.0.ms()
}
}
@ -44,7 +49,7 @@ impl TimeInfo {
// === Types ===
/// Callback for `RawLoop`.
pub trait RawLoopCallback = FnMut(f32) + 'static;
pub trait RawLoopCallback = FnMut(Duration) + 'static;
// === Definition ===
@ -67,7 +72,7 @@ where Callback: RawLoopCallback
pub fn new(callback: Callback) -> Self {
let data = Rc::new(RefCell::new(RawLoopData::new(callback)));
let weak_data = Rc::downgrade(&data);
let on_frame = move |time| weak_data.upgrade().for_each(|t| t.borrow_mut().run(time));
let on_frame = move |time: f64| weak_data.upgrade().for_each(|t| t.borrow_mut().run(time));
data.borrow_mut().on_frame = Some(Closure::new(on_frame));
let handle_id = web::window.request_animation_frame_with_closure_or_panic(
data.borrow_mut().on_frame.as_ref().unwrap(),
@ -95,12 +100,13 @@ impl<Callback> RawLoopData<Callback> {
Self { callback, on_frame, handle_id }
}
/// Run the animation frame.
/// Run the animation frame. The time will be converted to [`f32`] because of performance
/// reasons. See https://hugotunius.se/2017/12/04/rust-f64-vs-f32.html to learn more.
fn run(&mut self, current_time_ms: f64)
where Callback: FnMut(f32) {
where Callback: FnMut(Duration) {
let callback = &mut self.callback;
self.handle_id = self.on_frame.as_ref().map_or(default(), |on_frame| {
callback(current_time_ms as f32);
callback((current_time_ms as f32).ms());
web::window.request_animation_frame_with_closure_or_panic(on_frame)
})
}
@ -148,7 +154,7 @@ where Callback: LoopCallback
}
/// Callback for an animation frame.
pub type OnFrame<Callback> = impl FnMut(f32);
pub type OnFrame<Callback> = impl FnMut(Duration);
fn on_frame<Callback>(
mut callback: Callback,
time_info_ref: Rc<Cell<TimeInfo>>,
@ -156,13 +162,15 @@ fn on_frame<Callback>(
where
Callback: LoopCallback,
{
move |current_time: f32| {
move |current_time: Duration| {
let _profiler = profiler::start_debug!(profiler::APP_LIFETIME, "@on_frame");
let time_info = time_info_ref.get();
let start = if time_info.start == 0.0 { current_time } else { time_info.start };
let frame = current_time - start - time_info.local;
let local = current_time - start;
let time_info = TimeInfo { start, frame, local };
let prev_time = time_info_ref.get();
let animation_loop_start =
if prev_time.is_initialized() { prev_time.animation_loop_start } else { current_time };
let since_animation_loop_started = current_time - animation_loop_start;
let previous_frame = since_animation_loop_started - prev_time.since_animation_loop_started;
let time_info =
TimeInfo { animation_loop_start, previous_frame, since_animation_loop_started };
time_info_ref.set(time_info);
callback(time_info);
}
@ -179,9 +187,9 @@ where
#[derive(Derivative)]
#[derivative(Debug(bound = ""))]
pub struct FixedFrameRateSampler<Callback> {
frame_time: f32,
local_time: f32,
time_buffer: f32,
frame_time: Duration,
local_time: Duration,
time_buffer: Duration,
#[derivative(Debug = "ignore")]
callback: Callback,
}
@ -189,7 +197,7 @@ pub struct FixedFrameRateSampler<Callback> {
impl<Callback> FixedFrameRateSampler<Callback> {
/// Constructor.
pub fn new(frame_rate: f32, callback: Callback) -> Self {
let frame_time = 1000.0 / frame_rate;
let frame_time = (1000.0 / frame_rate).ms();
let local_time = default();
let time_buffer = default();
Self { frame_time, local_time, time_buffer, callback }
@ -206,16 +214,17 @@ impl<Callback: FnOnce<(TimeInfo,)>> FnOnce<(TimeInfo,)> for FixedFrameRateSample
impl<Callback: FnMut<(TimeInfo,)>> FnMut<(TimeInfo,)> for FixedFrameRateSampler<Callback> {
extern "rust-call" fn call_mut(&mut self, args: (TimeInfo,)) -> Self::Output {
let time = args.0;
self.time_buffer += time.frame;
self.time_buffer += time.previous_frame;
loop {
if self.time_buffer < 0.0 {
if self.time_buffer < 0.0.ms() {
break;
} else {
self.time_buffer -= self.frame_time;
let start = time.start;
let frame = self.frame_time;
let local = self.local_time;
let time2 = TimeInfo { start, frame, local };
let animation_loop_start = time.animation_loop_start;
let previous_frame = self.frame_time;
let since_animation_loop_started = self.local_time;
let time2 =
TimeInfo { animation_loop_start, previous_frame, since_animation_loop_started };
self.local_time += self.frame_time;
self.callback.call_mut((time2,));
}

View File

@ -1,5 +1,5 @@
//! This module implements physics components to simulate a rubber band dynamics.
//! The components has the potential to be further developed and extended in the future into a
//! The components have the potential to be further developed and extended in the future into a
//! more sophisticated physics simulator.
use crate::prelude::*;
@ -7,6 +7,7 @@ use crate::prelude::*;
use crate::animation;
use crate::data::function::Fn0;
use crate::data::function::Fn1;
use crate::types::unit2::Duration;
@ -254,7 +255,7 @@ impl<T: Value> SimulationData<T> {
}
/// Runs a simulation step.
fn step(&mut self, delta_seconds: f32) {
fn step(&mut self, delta_seconds: Duration) {
if self.active {
let velocity = self.velocity.magnitude();
let distance = self.offset_from_target.magnitude();
@ -268,6 +269,7 @@ impl<T: Value> SimulationData<T> {
} else {
let force = self.spring_force() + self.drag_force();
let acceleration = force * (1.0 / self.mass.value);
let delta_seconds = delta_seconds.unchecked_raw();
self.velocity = self.velocity + acceleration * delta_seconds;
self.offset_from_target = self.offset_from_target + self.velocity * delta_seconds;
}
@ -419,7 +421,7 @@ impl<T: Value> SimulationDataCell<T> {
}
/// Runs a simulation step.
pub fn step(&self, delta_seconds: f32) {
pub fn step(&self, delta_seconds: Duration) {
let mut data = self.data.get();
data.step(delta_seconds);
self.data.set(data);
@ -598,7 +600,7 @@ where
}
/// Proceed with the next simulation step for the given time delta.
pub fn step(&self, delta_seconds: f32) -> bool {
pub fn step(&self, delta_seconds: Duration) -> bool {
let is_active = self.simulation.active();
if is_active {
self.simulation.step(delta_seconds);
@ -794,7 +796,7 @@ where
let data = simulator.data.clone_ref();
let animation_loop = simulator.animation_loop.downgrade();
move |time: animation::TimeInfo| {
let delta_seconds = time.frame / 1000.0;
let delta_seconds = time.previous_frame / 1000.0;
if !data.step(delta_seconds) {
if let Some(animation_loop) = animation_loop.upgrade() {
animation_loop.set(None)
@ -845,7 +847,7 @@ mod tests {
let mut data = SimulationData::<f32>::new();
data.set_value(0.0);
data.set_target_value(f32::NAN);
data.step(1.0);
data.step(1.0.ms());
assert!(data.value().is_nan());
assert!(!data.active);
}
@ -856,7 +858,7 @@ mod tests {
let mut data = SimulationData::<f32>::new();
data.set_value(f32::NAN);
data.set_target_value(0.0);
data.step(1.0);
data.step(1.0.ms());
assert_eq!(data.value(), 0.0);
assert!(!data.active);
}

View File

@ -1,4 +1,5 @@
//! A module containing the garbage [`Collector`] structure.
use crate::prelude::*;

View File

@ -75,24 +75,28 @@ impl Instance {
let native = self.context.create_framebuffer().unwrap();
let target = Context::FRAMEBUFFER;
let draw_buffers = js_sys::Array::new();
context.bind_framebuffer(target, Some(&native));
context.bind_framebuffer(*target, Some(&native));
for (index, texture) in textures.iter().enumerate() {
let texture_target = Context::TEXTURE_2D;
let attachment_point = Context::COLOR_ATTACHMENT0 + index as u32;
let attachment_point = *Context::COLOR_ATTACHMENT0 + index as u32;
let gl_texture = texture.gl_texture();
let gl_texture = Some(&gl_texture);
let level = 0;
draw_buffers.push(&attachment_point.into());
context.framebuffer_texture_2d(
target,
*target,
attachment_point,
texture_target,
*texture_target,
gl_texture,
level,
);
}
context.draw_buffers(&draw_buffers);
context.bind_framebuffer(target, None);
context.bind_framebuffer(*target, None);
let framebuffer_status = context.check_framebuffer_status(*Context::FRAMEBUFFER);
if framebuffer_status != *Context::FRAMEBUFFER_COMPLETE {
WARNING!("Framebuffer incomplete (status: {framebuffer_status}).")
}
Framebuffer { context, native }
}
}
@ -161,6 +165,6 @@ impl Deref for Framebuffer {
impl Framebuffer {
/// Bind the framebuffer to the current WebGL context.
pub fn bind(&self) {
self.context.bind_framebuffer(Context::FRAMEBUFFER, Some(&self.native));
self.context.bind_framebuffer(*Context::FRAMEBUFFER, Some(&self.native));
}
}

View File

@ -103,8 +103,8 @@ impl<T: JsTypedArrayItem> PixelReadPass<T> {
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, Some(&buffer));
context.buffer_data_with_opt_array_buffer(*target, Some(&js_array.buffer()), *usage);
let texture = match variables.get("pass_id").unwrap() {
uniform::AnyUniform::Texture(t) => t,
@ -119,15 +119,19 @@ impl<T: JsTypedArrayItem> PixelReadPass<T> {
let attachment_point = Context::COLOR_ATTACHMENT0;
let gl_texture = Some(&gl_texture);
let level = 0;
context.bind_framebuffer(target, Some(&framebuffer));
context.bind_framebuffer(*target, Some(&framebuffer));
context.framebuffer_texture_2d(
target,
attachment_point,
texture_target,
*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 {
WARNING!("Framebuffer incomplete (status: {framebuffer_status}).")
}
let data = PixelReadPassData::new(buffer, framebuffer, format, item_type, js_array);
self.data = Some(data);
}
@ -141,29 +145,33 @@ impl<T: JsTypedArrayItem> PixelReadPass<T> {
let format = data.format.to::<GlEnum>().into();
let typ = data.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(&data.framebuffer));
context.bind_buffer(*Context::PIXEL_PACK_BUFFER, Some(&data.buffer));
context
.read_pixels_with_i32(position.x, position.y, width, height, format, typ, offset)
.unwrap();
let condition = Context::SYNC_GPU_COMMANDS_COMPLETE;
let flags = 0;
let sync = context.fence_sync(condition, flags).unwrap();
let sync = context.fence_sync(*condition, flags).unwrap();
self.sync = Some(sync);
context.flush();
}
fn check_and_handle_sync(&mut self, context: &Context, sync: &WebGlSync) {
let data = self.data.as_ref().unwrap();
let status = context.get_sync_parameter(sync, Context::SYNC_STATUS);
if status == Context::SIGNALED {
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));
context.get_buffer_sub_data_with_i32_and_array_buffer_view(target, offset, buffer_view);
context.bind_buffer(*target, Some(&data.buffer));
context.get_buffer_sub_data_with_i32_and_array_buffer_view(
*target,
offset,
buffer_view,
);
if let Some(f) = &self.callback {
f(data.js_array.to_vec());
}

View File

@ -64,8 +64,8 @@ impl pass::Definition for SymbolsRenderPass {
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,
min_filter: texture::MinFilter::NEAREST,
mag_filter: texture::MagFilter::NEAREST,
..default()
};
@ -96,8 +96,8 @@ impl pass::Definition for SymbolsRenderPass {
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);
instance.context.clear_bufferfv_with_f32_array(*Context::COLOR, 0, &arr);
instance.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);
@ -108,17 +108,17 @@ impl pass::Definition for SymbolsRenderPass {
This is an internal bug that may lead to visual artifacts. Please report it."
);
}
instance.context.bind_framebuffer(Context::FRAMEBUFFER, None);
instance.context.bind_framebuffer(*Context::FRAMEBUFFER, None);
}
}
impl SymbolsRenderPass {
fn enable_scissor_test(&self, instance: &pass::Instance) {
instance.context.enable(web_sys::WebGl2RenderingContext::SCISSOR_TEST);
instance.context.enable(*Context::SCISSOR_TEST);
}
fn disable_scissor_test(&self, instance: &pass::Instance) {
instance.context.disable(web_sys::WebGl2RenderingContext::SCISSOR_TEST);
instance.context.disable(*Context::SCISSOR_TEST);
}
fn render_layer(
@ -159,15 +159,15 @@ impl SymbolsRenderPass {
} else if let Some(mask) = layer_mask {
framebuffers.mask.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);
instance.context.clear_bufferfv_with_f32_array(*Context::COLOR, 0, &arr);
instance.context.clear_bufferfv_with_f32_array(*Context::COLOR, 1, &arr);
self.render_layer(instance, &mask, scissor_stack, was_ever_masked);
let framebuffers = self.framebuffers.as_ref().unwrap();
framebuffers.layer.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);
instance.context.clear_bufferfv_with_f32_array(*Context::COLOR, 0, &arr);
instance.context.clear_bufferfv_with_f32_array(*Context::COLOR, 1, &arr);
}
self.symbol_registry.set_camera(&layer.camera());

View File

@ -25,10 +25,10 @@ use crate::display::symbol::Symbol;
use crate::system;
use crate::system::gpu::data::uniform::Uniform;
use crate::system::gpu::data::uniform::UniformScope;
use crate::system::gpu::Context;
use crate::system::gpu::ContextLostHandler;
use crate::system::web;
use crate::system::web::EventListenerHandle;
use crate::system::Context;
use crate::system::ContextLostHandler;
use enso_frp as frp;
use enso_frp::io::js::CurrentJsEvent;
@ -466,19 +466,21 @@ impl Renderer {
Self { logger, dom, variables, pipeline, composer }
}
/// 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.
fn set_context(&self, context: Option<&Context>) {
let composer = context.map(|context| {
// To learn more about the blending equations used here, please see the following
// articles:
// - http://www.realtimerendering.com/blog/gpus-prefer-premultiplication
// - https://www.khronos.org/opengl/wiki/Blending#Colors
context.enable(Context::BLEND);
context.blend_equation_separate(Context::FUNC_ADD, Context::FUNC_ADD);
context.enable(*Context::BLEND);
context.blend_equation_separate(*Context::FUNC_ADD, *Context::FUNC_ADD);
context.blend_func_separate(
Context::ONE,
Context::ONE_MINUS_SRC_ALPHA,
Context::ONE,
Context::ONE_MINUS_SRC_ALPHA,
*Context::ONE,
*Context::ONE_MINUS_SRC_ALPHA,
*Context::ONE,
*Context::ONE_MINUS_SRC_ALPHA,
);
let (width, height) = self.view_size();
@ -815,6 +817,8 @@ impl SceneData {
self
}
/// 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 fn set_context(&self, context: Option<&Context>) {
let _profiler = profiler::start_objective!(profiler::APP_LIFETIME, "@set_context");
self.symbols.set_context(context);
@ -908,7 +912,17 @@ impl SceneData {
}
pub fn render(&self) {
self.renderer.run()
self.renderer.run();
// WebGL `flush` should be called when expecting results such as queries, or at completion
// of a rendering frame. Flush tells the implementation to push all pending commands out
// for execution, flushing them out of the queue, instead of waiting for more commands to
// enqueue before sending for execution.
//
// Not flushing commands can sometimes cause context loss. To learn more, see:
// [https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/WebGL_best_practices#flush_when_expecting_results].
if let Some(context) = &*self.context.borrow() {
context.flush()
}
}
pub fn screen_to_scene_coordinates(&self, position: Vector3<f32>) -> Vector3<f32> {
@ -1030,7 +1044,7 @@ impl Scene {
}
fn init(&self) {
let context_loss_handler = crate::system::context::init_webgl_2_context(self);
let context_loss_handler = crate::system::gpu::context::init_webgl_2_context(self);
match context_loss_handler {
Err(err) => error!(self.logger, "{err}"),
Ok(handler) => *self.context_lost_handler.borrow_mut() = Some(handler),
@ -1042,8 +1056,8 @@ impl Scene {
}
}
impl system::context::Display for Scene {
fn device_context_handler(&self) -> &system::context::DeviceContextHandler {
impl system::gpu::context::Display for Scene {
fn device_context_handler(&self) -> &system::gpu::context::DeviceContextHandler {
&self.dom.layers.canvas
}
@ -1073,10 +1087,10 @@ impl Deref for Scene {
impl Scene {
#[profile(Debug)]
pub fn update(&self, t: animation::TimeInfo) {
if self.context.borrow().is_some() {
pub fn update(&self, time: animation::TimeInfo) {
if let Some(context) = &*self.context.borrow() {
debug!(self.logger, "Updating.", || {
self.frp.frame_time_source.emit(t.local);
self.frp.frame_time_source.emit(time.since_animation_loop_started.unchecked_raw());
// Please note that `update_camera` is called first as it may trigger FRP events
// which may change display objects layout.
self.update_camera(self);
@ -1085,6 +1099,7 @@ impl Scene {
self.update_shape();
self.update_symbols();
self.handle_mouse_over_and_out_events();
context.shader_compiler.run(time);
})
}
}

View File

@ -76,7 +76,6 @@ impl ShapeSystem {
material.add_input("pixel_ratio", 1.0);
material.add_input("z_zoom_1", 1.0);
material.add_input("time", 0.0);
material.add_input("symbol_id", 0);
material.add_input("display_mode", 0);
material.add_output("id", Vector4::<f32>::zero());
material

View File

@ -1,5 +1,4 @@
// === Non-Standard Linter Configuration ===
#![allow(missing_docs)]
//! GPU representation of symbols, meshes with shaders.
use crate::data::dirty::traits::*;
use crate::prelude::*;
@ -8,6 +7,8 @@ use crate::data::dirty;
use crate::debug::stats::Stats;
use crate::display;
use crate::display::symbol::geometry::primitive::mesh;
use crate::system::gpu;
use crate::system::gpu::context::native::ContextOps;
use crate::system::gpu::data::buffer::IsBuffer;
use crate::system::gpu::data::texture::class::TextureOps;
use crate::system::gpu::data::uniform::AnyPrimUniform;
@ -43,6 +44,7 @@ pub mod shader;
// === Exports ===
// ===============
/// Popular types.
pub mod types {
use super::*;
pub use geometry::types::*;
@ -155,14 +157,6 @@ impl VertexArrayObjectData {
let vao = context.create_vertex_array().unwrap();
Self { context, vao }
}
/// Binds the VAO, evaluates the provided function, and unbinds the VAO.
pub fn with<F: FnOnce() -> T, T>(&self, f: F) -> T {
self.bind();
let out = f();
self.unbind();
out
}
}
@ -277,19 +271,26 @@ shared2! { GlobalInstanceIdProvider
// === Types ===
/// Attribute scope type. Attributes can be defined in one of the supported scopes and will be
/// automatically bound to the material definition during shader compilation.
#[derive(Copy, Clone, Debug, PartialEq)]
#[allow(missing_docs)]
pub enum ScopeType {
Mesh(mesh::ScopeType),
Symbol,
Global,
}
/// A dirty flag for the symbols' geometry.
pub type GeometryDirty = dirty::SharedBool<Box<dyn Fn()>>;
/// A dirty flag for the symbols' shader.
pub type ShaderDirty = dirty::SharedBool<Box<dyn Fn()>>;
// === Bindings ====
/// All attributes and uniforms bindings of symbol with the associated Vertex Array Object.
#[derive(Clone, Debug, Default)]
pub struct Bindings {
vao: Option<VertexArrayObject>,
@ -306,30 +307,17 @@ newtype_prim! {
SymbolId(u32);
}
/// Symbol is a surface with attached `Shader`.
#[derive(Debug, Clone, CloneRef)]
/// Symbol is a [`Mesh`] with attached [`Shader`].
#[derive(Debug, Clone, CloneRef, Deref)]
pub struct Symbol {
pub id: SymbolId,
global_id_provider: GlobalInstanceIdProvider,
display_object: display::object::Instance,
surface: Mesh,
shader: Shader,
surface_dirty: GeometryDirty,
shader_dirty: ShaderDirty,
variables: UniformScope,
/// Please note that changing the uniform type to `u32` breaks node ID encoding in GLSL, as the
/// functions are declared to work on `int`s, not `uint`s. This might be improved one day.
symbol_id_uniform: Uniform<i32>,
context: Rc<RefCell<Option<Context>>>,
logger: Logger,
bindings: Rc<RefCell<Bindings>>,
stats: SymbolStats,
is_hidden: Rc<Cell<bool>>,
global_instance_id: Buffer<i32>,
#[deref]
data: Rc<SymbolData>,
shader_dirty: ShaderDirty,
shader: Shader,
}
impl Symbol {
/// Create new instance with the provided on-dirty callback.
/// Constructor.
pub fn new<OnMut: Fn() + Clone + 'static>(
stats: &Stats,
id: SymbolId,
@ -339,70 +327,24 @@ impl Symbol {
let logger = Logger::new(format!("symbol_{}", id));
let init_logger = logger.clone();
debug!(init_logger, "Initializing.", || {
let global_id_provider = global_id_provider.clone_ref();
let on_mut2 = on_mut.clone();
let surface_logger = Logger::new_sub(&logger, "surface");
let shader_dirt_logger = Logger::new_sub(&logger, "shader_dirty");
let shader_dirty = ShaderDirty::new(shader_dirt_logger, Box::new(on_mut));
let shader_logger = Logger::new_sub(&logger, "shader");
let geo_dirt_logger = Logger::new_sub(&logger, "surface_dirty");
let mat_dirt_logger = Logger::new_sub(&logger, "shader_dirty");
let surface_dirty = GeometryDirty::new(geo_dirt_logger, Box::new(on_mut2));
let shader_dirty = ShaderDirty::new(mat_dirt_logger, Box::new(on_mut));
let surface_on_mut = Box::new(f!(surface_dirty.set()));
let shader_on_mut = Box::new(f!(shader_dirty.set()));
let shader = Shader::new(shader_logger, stats, shader_on_mut);
let surface = Mesh::new(surface_logger, stats, surface_on_mut);
let variables = UniformScope::new(Logger::new_sub(&logger, "uniform_scope"));
let bindings = default();
let stats = SymbolStats::new(stats);
let context = default();
let symbol_id_uniform = variables.add_or_panic("symbol_id", (*id) as i32);
let display_object = display::object::Instance::new(logger.clone());
let is_hidden = Rc::new(Cell::new(false));
let instance_scope = surface.instance_scope();
let global_instance_id = instance_scope.add_buffer("global_instance_id");
Self {
id,
global_id_provider,
display_object,
surface,
shader,
surface_dirty,
shader_dirty,
variables,
symbol_id_uniform,
context,
logger,
bindings,
stats,
is_hidden,
global_instance_id,
}
.init()
let data = Rc::new(SymbolData::new(logger, stats, id, global_id_provider, on_mut2));
Self { data, shader_dirty, shader }
})
}
fn init(self) -> Self {
let is_hidden = &self.is_hidden;
let id = self.id;
self.display_object.set_on_hide(f_!(is_hidden.set(true)));
self.display_object.set_on_show(f__!(is_hidden.set(false)));
self.display_object.set_on_scene_layer_changed(move |_, old_layers, new_layers| {
for layer in old_layers.iter().filter_map(|t| t.upgrade()) {
layer.remove_symbol(id)
}
for layer in new_layers.iter().filter_map(|t| t.upgrade()) {
layer.add_symbol(id)
}
});
self
}
/// Create a new instance of this symbol.
pub fn new_instance(&self) -> SymbolInstance {
SymbolInstance::new(self)
}
/// 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>) {
*self.context.borrow_mut() = context.cloned();
self.surface.set_context(context);
@ -419,31 +361,19 @@ impl Symbol {
}
if self.shader_dirty.check() {
let var_bindings = self.discover_variable_bindings(global_variables);
self.shader.update(&var_bindings);
self.init_variable_bindings(&var_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();
}
})
}
}
pub fn lookup_variable<S: Str>(
&self,
name: S,
global_variables: &UniformScope,
) -> Option<ScopeType> {
let name = name.as_ref();
self.surface.lookup_variable(name).map(ScopeType::Mesh).or_else(|| {
if self.variables.contains(name) {
Some(ScopeType::Symbol)
} else if global_variables.contains(name) {
Some(ScopeType::Global)
} else {
None
}
})
}
/// Render the symbol. You should never need to call this function directly. Use the rendering
/// pipeline instead.
pub fn render(&self) {
debug!(self.logger, "Rendering.", || {
if self.is_hidden() {
@ -464,43 +394,44 @@ impl Symbol {
let count = self.surface.point_scope().size() as i32;
let instance_count = self.surface.instance_scope().size() as i32;
// println!("rendering symbol {:?}. count {}, instance count
// {}",self.id,count,instance_count);
// FIXME: we should uncomment the following code in some pedantic debug mode. It
// introduces severe performance overhead (0.8ms -> 3ms per frame)
// because it requires GPU to sync. However, we
// should maintain a "pedantic mode" in case
// something goes horribly wrong and we would like to discover what.
// // Check if we are ready to render. If we don't assert here we wil only get a
// warning // that won't tell us where things went wrong.
// {
// let framebuffer_status =
// context.check_framebuffer_status(Context::FRAMEBUFFER);
// debug_assert_eq!(
// framebuffer_status,
// Context::FRAMEBUFFER_COMPLETE,
// "Framebuffer incomplete (status: {}).",
// framebuffer_status
// )
// }
self.stats.inc_draw_call_count();
if instance_count > 0 {
context.draw_arrays_instanced(mode, first, count, instance_count);
context.draw_arrays_instanced(*mode, first, count, instance_count);
} else {
context.draw_arrays(mode, first, count);
context.draw_arrays(*mode, first, count);
}
});
}
})
}
}
impl From<&Symbol> for SymbolId {
fn from(t: &Symbol) -> Self {
t.id
/// For each variable from the shader definition, looks up its position in geometry scopes.
fn discover_variable_bindings(
&self,
global_variables: &UniformScope,
) -> Vec<shader::VarBinding> {
let var_decls = self.shader.collect_variables();
var_decls
.into_iter()
.map(|(var_name, var_decl)| {
let target = self.lookup_variable(&var_name, global_variables);
if target.is_none() {
warning!(
self.logger,
"Unable to bind variable '{var_name}' to geometry buffer."
);
}
shader::VarBinding::new(var_name, var_decl, target)
})
.collect()
}
/// 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() {
context.with_program(program, || self.with_vao(|_| f(program)));
}
}
}
@ -508,18 +439,22 @@ impl From<&Symbol> for SymbolId {
// === Visibility ===
impl Symbol {
/// Show or hide the symbol.
pub fn set_hidden(&self, b: bool) {
self.is_hidden.set(b)
}
/// Hide the symbol.
pub fn hide(&self) {
self.set_hidden(true)
}
/// Show the previously hidden symbol.
pub fn show(&self) {
self.set_hidden(false)
}
/// Check whether the symbol was hidden.
pub fn is_hidden(&self) -> bool {
self.is_hidden.get()
}
@ -528,6 +463,7 @@ impl Symbol {
// === Getters ===
#[allow(missing_docs)]
impl Symbol {
pub fn surface(&self) -> &Mesh {
&self.surface
@ -543,18 +479,136 @@ impl Symbol {
}
// === Conversions ===
impl display::Object for Symbol {
fn display_object(&self) -> &display::object::Instance {
&self.display_object
}
}
impl From<&Symbol> for SymbolId {
fn from(t: &Symbol) -> Self {
t.id
}
}
// ==================
// === SymbolData ===
// ==================
/// Internal representation of [`Symbol`]
#[derive(Debug, Clone)]
#[allow(missing_docs)]
pub struct SymbolData {
pub id: SymbolId,
global_id_provider: GlobalInstanceIdProvider,
display_object: display::object::Instance,
surface: Mesh,
surface_dirty: GeometryDirty,
variables: UniformScope,
context: RefCell<Option<Context>>,
logger: Logger,
bindings: RefCell<Bindings>,
stats: SymbolStats,
is_hidden: Rc<Cell<bool>>,
global_instance_id: Buffer<i32>,
}
impl SymbolData {
/// Create new instance with the provided on-dirty callback.
pub fn new<OnMut: Fn() + Clone + 'static>(
logger: Logger,
stats: &Stats,
id: SymbolId,
global_id_provider: &GlobalInstanceIdProvider,
on_mut: OnMut,
) -> Self {
let global_id_provider = global_id_provider.clone_ref();
let surface_logger = Logger::new_sub(&logger, "surface");
let geo_dirt_logger = Logger::new_sub(&logger, "surface_dirty");
let surface_dirty = GeometryDirty::new(geo_dirt_logger, Box::new(on_mut));
let surface_on_mut = Box::new(f!(surface_dirty.set()));
let surface = Mesh::new(surface_logger, stats, surface_on_mut);
let variables = UniformScope::new(Logger::new_sub(&logger, "uniform_scope"));
let bindings = default();
let stats = SymbolStats::new(stats);
let context = default();
let display_object = display::object::Instance::new(logger.clone());
let is_hidden = Rc::new(Cell::new(false));
let instance_scope = surface.instance_scope();
let global_instance_id = instance_scope.add_buffer("global_instance_id");
Self {
id,
global_id_provider,
display_object,
surface,
surface_dirty,
variables,
context,
bindings,
stats,
is_hidden,
global_instance_id,
logger,
}
.init()
}
fn init(self) -> Self {
let is_hidden = &self.is_hidden;
let id = self.id;
self.display_object.set_on_hide(f_!(is_hidden.set(true)));
self.display_object.set_on_show(f__!(is_hidden.set(false)));
self.display_object.set_on_scene_layer_changed(move |_, old_layers, new_layers| {
for layer in old_layers.iter().filter_map(|t| t.upgrade()) {
layer.remove_symbol(id)
}
for layer in new_layers.iter().filter_map(|t| t.upgrade()) {
layer.add_symbol(id)
}
});
self
}
/// Lookup variable by name. All mesh scopes, symbol uniform scope, and the global scope will be
/// searched.
pub fn lookup_variable<S: Str>(
&self,
name: S,
global_variables: &UniformScope,
) -> Option<ScopeType> {
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 global_variables.contains(name) => Some(ScopeType::Global),
_ => None,
}
}
}
// === Private API ===
impl Symbol {
impl SymbolData {
/// Creates a new VertexArrayObject, discovers all variable bindings from shader to geometry,
/// and initializes the VAO with the bindings.
fn init_variable_bindings(
&self,
var_bindings: &[shader::VarBinding],
global_variables: &UniformScope,
program: &gpu::shader::Program,
) {
if let Some(context) = &*self.context.borrow() {
let max_texture_units = context.get_parameter(Context::MAX_TEXTURE_IMAGE_UNITS);
let max_texture_units = context.get_parameter(*Context::MAX_TEXTURE_IMAGE_UNITS);
let max_texture_units = max_texture_units.unwrap_or_else(|num| {
let min_texture_units = 2;
error!(
@ -569,21 +623,23 @@ impl Symbol {
self.bindings.borrow_mut().vao = Some(VertexArrayObject::new(context));
self.bindings.borrow_mut().uniforms = default();
self.bindings.borrow_mut().textures = default();
self.with_program_mut(context, |this, program| {
for binding in var_bindings {
match binding.scope.as_ref() {
Some(ScopeType::Mesh(s)) =>
this.init_attribute_binding(context, program, binding, *s),
Some(_) => this.init_uniform_binding(
context,
program,
binding,
&mut texture_unit_iter,
global_variables,
),
None => {}
context.with_program(&program.native, || {
self.with_vao(|this| {
for binding in var_bindings {
match binding.scope.as_ref() {
Some(ScopeType::Mesh(scope)) =>
this.init_attribute_binding(context, program, binding, *scope),
Some(_) => this.init_uniform_binding(
context,
program,
binding,
&mut texture_unit_iter,
global_variables,
),
None => {}
}
}
}
});
});
}
}
@ -604,7 +660,7 @@ impl Symbol {
let location = location as u32;
let buffer = &scope.buffer(&binding.name).unwrap();
let is_instanced = mesh_scope_type == mesh::ScopeType::Instance;
buffer.bind(Context::ARRAY_BUFFER);
buffer.bind(*Context::ARRAY_BUFFER);
buffer.vertex_attrib_pointer(location, is_instanced);
}
}
@ -657,64 +713,19 @@ impl Symbol {
});
}
/// For each variable from the shader definition, looks up its position in geometry scopes.
fn discover_variable_bindings(
&self,
global_variables: &UniformScope,
) -> Vec<shader::VarBinding> {
let var_decls = self.shader.collect_variables();
var_decls
.into_iter()
.map(|(var_name, var_decl)| {
let target = self.lookup_variable(&var_name, global_variables);
if target.is_none() {
warning!(
self.logger,
"Unable to bind variable '{var_name}' to geometry buffer."
);
}
shader::VarBinding::new(var_name, var_decl, target)
})
.collect()
}
/// 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.program().as_ref() {
context.use_program(Some(program));
let bindings = self.bindings.borrow();
if let Some(vao) = bindings.vao.as_ref() {
vao.with(|| f(program));
}
context.use_program(None);
}
}
/// 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_mut<F: FnOnce(&Self, &WebGlProgram)>(&self, context: &Context, f: F) {
if let Some(program) = self.shader.program().as_ref() {
context.use_program(Some(program));
self.with_vao_mut(|this| f(this, program));
context.use_program(None);
}
}
fn with_vao_mut<F: FnOnce(&Self) -> T, T>(&self, f: F) -> T {
self.bindings.borrow().vao.as_ref().unwrap().bind();
fn with_vao<F: FnOnce(&Self) -> T, T>(&self, f: F) -> T {
self.with_borrowed_vao_or_warn(|vao| vao.bind());
let out = f(self);
self.bindings.borrow().vao.as_ref().unwrap().unbind();
self.with_borrowed_vao_or_warn(|vao| vao.unbind());
out
}
}
// === Conversions ===
impl display::Object for Symbol {
fn display_object(&self) -> &display::object::Instance {
&self.display_object
fn with_borrowed_vao_or_warn<T>(&self, f: impl FnOnce(&VertexArrayObject) -> T) -> Option<T> {
let out = self.bindings.borrow().vao.as_ref().map(f);
if out.is_none() {
error!(self.logger, "Vertex Array Object not found during rendering.");
}
out
}
}
@ -731,7 +742,9 @@ pub struct SymbolInstance {
rc: Rc<SymbolInstanceData>,
}
/// Internal representation of [`SymbolInstance`].
#[derive(Debug, NoCloneBecauseOfCustomDrop)]
#[allow(missing_docs)]
pub struct SymbolInstanceData {
pub symbol: Symbol,
pub instance_id: attribute::InstanceIndex,

View File

@ -6,7 +6,7 @@ use crate::prelude::*;
use crate::control::callback;
use crate::data::dirty;
use crate::debug::stats::Stats;
use crate::system::Context;
use crate::system::gpu::Context;
use enso_shapely::shared2;
use num_enum::IntoPrimitive;
@ -191,7 +191,8 @@ impl {
}.clone_ref()
}
/// Set the WebGL context. See the main architecture docs of this library to learn more.
/// 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>) {
macro_rules! set_scope_context { ($($name:ident),*) => {
$( self.scopes.$name.set_context(context); )*

View File

@ -12,7 +12,7 @@ use crate::display::symbol::Symbol;
use crate::display::symbol::SymbolId;
use crate::system::gpu::data::uniform::Uniform;
use crate::system::gpu::data::uniform::UniformScope;
use crate::system::Context;
use crate::system::gpu::Context;
use data::opt_vec::OptVec;
@ -93,7 +93,8 @@ impl SymbolRegistry {
SymbolId::new(index as u32)
}
/// Set the WebGL context. See the main architecture docs of this library to learn more.
/// 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 fn set_context(&self, context: Option<&Context>) {
*self.context.borrow_mut() = context.cloned();
for symbol in &*self.symbols.borrow() {

View File

@ -3,17 +3,17 @@
use crate::data::dirty::traits::*;
use crate::prelude::*;
use crate::system::gpu::shader::*;
use crate::control::callback;
use crate::data::dirty;
use crate::debug::stats::Stats;
use crate::display::symbol::material::Material;
use crate::display::symbol::material::VarDecl;
use crate::display::symbol::shader;
use crate::display::symbol::shader::ContextLossOrError;
use crate::display::symbol::ScopeType;
use crate::system::Context;
use crate::system::gpu::shader;
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;
@ -58,18 +58,19 @@ 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 : Option<WebGlProgram>,
shader : Option<builder::Shader>,
dirty : Dirty,
logger : Logger,
stats : Stats,
context : Option<Context>,
geometry_material : Material,
surface_material : Material,
program : Rc<RefCell<Option<shader::Program>>>,
shader_compiler_job : Option<shader_compiler::JobHandler>,
dirty : Dirty,
logger : Logger,
stats : Stats,
}
impl {
/// Set the WebGL context. See the main architecture docs of this library to learn more.
/// 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 fn set_context(&mut self, context:Option<&Context>) {
if context.is_some() {
self.dirty.set();
@ -77,8 +78,12 @@ impl {
self.context = context.cloned();
}
pub fn program(&self) -> Option<WebGlProgram> {
self.program.clone()
pub fn program(&self) -> Option<shader::Program> {
self.program.borrow().clone()
}
pub fn native_program(&self) -> Option<WebGlProgram> {
self.program.borrow().as_ref().map(|t| t.native.clone())
}
pub fn set_geometry_material<M:Into<Material>>(&mut self, material:M) {
@ -94,29 +99,30 @@ impl {
/// Creates new shader with attached callback.
pub fn new<OnMut:callback::NoArgs>(logger:Logger, stats:&Stats, on_mut:OnMut) -> Self {
stats.inc_shader_count();
let context = default();
let context = default();
let geometry_material = default();
let surface_material = default();
let program = default();
let shader = default();
let dirty_logger = Logger::new_sub(&logger,"dirty");
let dirty = Dirty::new(dirty_logger,Box::new(on_mut));
let stats = stats.clone_ref();
Self {context,geometry_material,surface_material,program,shader,dirty,logger,stats}
let surface_material = default();
let program = default();
let shader_compiler_job = default();
let dirty_logger = Logger::new_sub(&logger,"dirty");
let dirty = Dirty::new(dirty_logger,Box::new(on_mut));
let stats = stats.clone_ref();
Self {context,geometry_material,surface_material,program,shader_compiler_job,dirty,logger,stats}
}
/// Check dirty flags and update the state accordingly.
pub fn update(&mut self, bindings:&[VarBinding]) {
pub fn update<F: 'static + Fn(&[VarBinding], &shader::Program)>
(&mut self, bindings:Vec<VarBinding>, on_ready:F) {
debug!(self.logger, "Updating.", || {
if let Some(context) = &self.context {
if self.dirty.check_all() {
self.stats.inc_shader_compile_count();
let mut shader_cfg = shader::builder::ShaderConfig::new();
let mut shader_builder = shader::builder::ShaderBuilder::new();
let mut shader_cfg = builder::ShaderConfig::new();
let mut shader_builder = builder::ShaderBuilder::new();
for binding in bindings {
for binding in &bindings {
let name = &binding.name;
let tp = &binding.decl.tp;
match binding.scope {
@ -145,34 +151,33 @@ impl {
let vertex_code = self.geometry_material.code().clone();
let fragment_code = self.surface_material.code().clone();
shader_builder.compute(&shader_cfg,vertex_code,fragment_code);
let shader = shader_builder.build();
let program = compile_program(context,&shader.vertex,&shader.fragment);
match program {
Ok(program) => {
self.program = Some(program);
self.shader = Some(shader);
}
Err(ContextLossOrError::Error(err)) => error!(self.logger, "{err}"),
Err(ContextLossOrError::ContextLoss) => {}
}
let code = shader_builder.build();
*self.program.borrow_mut() = None;
let program = self.program.clone_ref();
let handler = context.shader_compiler.submit(code, move |p| {
on_ready(&bindings,&p);
*program.borrow_mut() = Some(p);
});
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::JobHandler) {
// Dropping the previous handler.
self.shader_compiler_job = Some(handler);
}
/// Traverses the shader definition and collects all attribute names.
pub fn collect_variables(&self) -> BTreeMap<String,VarDecl> {
let geometry_material_inputs = self.geometry_material.inputs().clone();
let surface_material_inputs = self.surface_material.inputs().clone();
geometry_material_inputs.into_iter().chain(surface_material_inputs).collect()
}
/// Get the generated shader, if it was already generated.
pub fn shader(&self) -> Option<builder::Shader> {
self.shader.clone()
}
}}
impl Drop for ShaderData {

View File

@ -3,6 +3,7 @@
use crate::prelude::*;
use crate::system::gpu::shader;
use crate::system::gpu::shader::glsl;
use code_builder::HasCodeRepr;
@ -10,18 +11,6 @@ use std::collections::BTreeMap;
// ==============
// === Shader ===
// ==============
#[derive(Clone, Debug)]
pub struct Shader {
pub vertex: String,
pub fragment: String,
}
// ====================
// === ShaderConfig ===
// ====================
@ -370,10 +359,10 @@ impl ShaderBuilder {
}
}
pub fn build(&self) -> Shader {
pub fn build(&self) -> shader::Code {
let vertex = self.vertex.to_code();
let fragment = self.fragment.to_code();
Shader { vertex, fragment }
shader::Code { vertex, fragment }
}
}

View File

@ -136,7 +136,7 @@ impl WorldDataWithLoop {
/// Constructor.
pub fn new() -> Self {
let data = WorldData::new();
let main_loop = MainLoop::new(Box::new(f!([data](t) data.go_to_next_frame_with_time(t))));
let main_loop = MainLoop::new(Box::new(f!([data](t) data.run_next_frame(t))));
Self { main_loop, data }
}
}
@ -291,13 +291,13 @@ impl WorldData {
/// function is more precise than time obtained from the [`window.performance().now()`] one.
/// Follow this link to learn more:
/// https://stackoverflow.com/questions/38360250/requestanimationframe-now-vs-performance-now-time-discrepancy.
pub fn go_to_next_frame_with_time(&self, time: animation::TimeInfo) {
pub fn run_next_frame(&self, time: animation::TimeInfo) {
let previous_frame_stats = self.stats.begin_frame();
if let Some(stats) = previous_frame_stats {
self.on.prev_frame_stats.run_all(&stats);
}
self.on.before_frame.run_all(time);
self.uniforms.time.set(time.local);
self.uniforms.time.set(time.since_animation_loop_started.unchecked_raw());
self.scene_dirty.unset_all();
self.default_scene.update(time);
self.garbage_collector.mouse_events_handled();

View File

@ -14,7 +14,6 @@ use crate::display::shape::primitive::system::DynamicShapeInternals;
use crate::display::symbol;
// ==============
// === Export ===
// ==============

View File

@ -277,7 +277,7 @@ impl Cursor {
let host_attached_weight = DEPRECATED_Tween::new(network);
let port_selection_layer_weight = Animation::<f32>::new(network);
host_attached_weight.set_duration(300.0);
host_attached_weight.set_duration(300.0.ms());
color_lab.set_target_value(DEFAULT_COLOR.opaque.into());
color_alpha.set_target_value(DEFAULT_COLOR.alpha);
radius.set_target_value(DEFAULT_RADIUS);

View File

@ -19,6 +19,7 @@
#![feature(type_alias_impl_trait)]
#![feature(unboxed_closures)]
#![feature(trace_macros)]
#![feature(const_trait_impl)]
// === Standard Linter Configuration ===
#![deny(non_ascii_idents)]
#![warn(unsafe_code)]
@ -63,6 +64,7 @@ pub mod prelude {
pub use super::types::*;
pub use crate::data::container::AddMut;
pub use crate::shapes_order_dependencies;
pub use crate::types::unit2::traits::*;
pub use enso_data_structures as data;
pub use enso_logger as logger;
pub use enso_logger::AnyLogger;

View File

@ -6,10 +6,6 @@
// === Export ===
// ==============
pub mod context;
pub mod gpu;
pub mod js;
pub mod web;
pub use context::Context;
pub use context::ContextLostHandler;

View File

@ -1,118 +0,0 @@
//! This module provides an abstraction for the rendering context, such as WebGL or OpenGL one.
use crate::prelude::*;
use web::traits::*;
use crate::system::web;
use web::Closure;
use web_sys::WebGl2RenderingContext;
// ===============
// === Context ===
// ===============
/// The rendering context. Currently, we support only the WebGL 2.0 context. In case we would like
/// to support other contexts, this is the type that should be changed to an enum of supported
/// contexts.
///
/// ## Context Loss
///
/// **You can lose the context AT ANY TIME! In other words, you can lose the context part way
/// through initialization. You can also lose the context immediately after calling
/// `canvas.getContext`. You can lose the context between any 2 WebGL function calls.**
///
/// The GPU is a shared resource and as such there are times when it might be taken away from your
/// program. Examples:
/// - Another web page does something that takes the GPU too long and the browser or the OS decides
/// to reset the GPU to get control back.
/// - Tow or more pages use too many resources and the browser decides to tell all the pages they
/// lost the context and then restore it only to the front page for now.
/// - The user switches graphics cards (Turns on/off one or more in the control panel) or updates
/// their driver (no reboot required on Windows7+).
/// - Too many web pages use the GPU context and the browser decides to tell some of the pages they
/// lost the context in order to allow the newly opened ones to get it.
///
/// In all these cases and more your program may lose its WebGL context. By default when a WebGL
/// program loses the context it never gets it back. To recover from a lost context you must to add
/// a lost context handler and tell it to prevent the default behavior, and then re-setup all your
/// WebGL state and re-create all your WebGL resources when the context is restored.
///
/// This process is pretty complex and touches many places of your program, including WebGL error
/// handling, shaders and programs compilation and linking, WebGL-related variables null-checkers,
/// and many others. To learn more, see https://www.khronos.org/webgl/wiki/HandlingContextLost.
pub type Context = WebGl2RenderingContext;
/// Abstraction for Device Context Handler. This name is used in OpenGL / DirectX implementations.
/// For the web target, this is simply the canvas. As we currently support WebGL 2.0 only, this is
/// simply an alias to a canvas type. See the docs of [`Context`] to learn more about future
/// extension plans.
pub type DeviceContextHandler = web::HtmlCanvasElement;
/// Handler for closures taking care of context restoration. After dropping this handler and losing
/// the context, the context will not be restored automaticaly.
#[derive(Debug)]
pub struct ContextLostHandler {
on_lost: web::EventListenerHandle,
on_restored: web::EventListenerHandle,
}
// ======================
// === ContextHandler ===
// ======================
/// Abstraction for entities which contain [`DeviceContextHandler`] and are able to handle context
/// loss. In most cases, these are top-level entities, such as a scene.
#[allow(missing_docs)]
pub trait Display: CloneRef {
fn device_context_handler(&self) -> &DeviceContextHandler;
fn set_context(&self, context: Option<&Context>);
}
// ==============
// === Errors ===
// ==============
/// Error about unsupported standard implementation, like unsupported WebGL 2.0.
#[derive(Copy, Clone, Debug)]
pub struct UnsupportedStandard(&'static str);
impl std::error::Error for UnsupportedStandard {}
impl fmt::Display for UnsupportedStandard {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} is not supported.", self.0)
}
}
// ============================
// === Initialization Utils ===
// ============================
/// Initialize WebGL 2.0 context.
pub fn init_webgl_2_context<D: Display + 'static>(
display: &D,
) -> Result<ContextLostHandler, UnsupportedStandard> {
let hdc = display.device_context_handler();
let opt_context = hdc.get_webgl2_context();
match opt_context {
None => Err(UnsupportedStandard("WebGL 2.0")),
Some(context) => {
type Handler = web::JsEventHandler;
display.set_context(Some(&context));
let lost: Handler = Closure::new(f_!(display.set_context(None)));
let restored: Handler = Closure::new(f_!(display.set_context(Some(&context))));
let on_lost = web::add_event_listener(hdc, "webglcontextlost", lost);
let on_restored = web::add_event_listener(hdc, "webglcontextrestored", restored);
Ok(ContextLostHandler { on_lost, on_restored })
}
}
}

View File

@ -5,6 +5,7 @@
// === Export ===
// ==============
pub mod context;
pub mod data;
pub mod shader;
@ -12,15 +13,12 @@ pub mod shader;
/// Common types.
pub mod types {
use web_sys::WebGl2RenderingContext;
pub use super::context::Context;
pub use super::context::ContextLostHandler;
pub use super::data::types::*;
pub use super::shader::types::*;
pub use super::data::attribute;
pub use super::data::uniform;
/// Alias for WebGl2RenderingContext.
pub type Context = WebGl2RenderingContext;
}
pub use types::*;

View File

@ -0,0 +1,745 @@
//! This module provides an abstraction for the rendering context, such as WebGL or OpenGL one.
use crate::prelude::*;
use web::traits::*;
use crate::system::gpu::data::GlEnum;
use crate::system::gpu::shader;
use crate::system::web;
use web::Closure;
use web_sys::WebGl2RenderingContext;
// ==============
// === Export ===
// ==============
pub mod extension;
pub mod native;
// ===============
// === Context ===
// ===============
/// The rendering context. Currently, we support only the WebGL 2.0 context. In case we would like
/// to support other contexts, this is the type that should be changed to an enum of supported
/// contexts.
///
/// ## Context Loss
///
/// **You can lose the context AT ANY TIME! In other words, you can lose the context part way
/// through initialization. You can also lose the context immediately after calling
/// `canvas.getContext`. You can lose the context between any 2 WebGL function calls.**
///
/// The GPU is a shared resource and as such there are times when it might be taken away from your
/// program. Examples:
/// - Another web page does something that takes the GPU too long and the browser or the OS decides
/// to reset the GPU to get control back.
/// - Tow or more pages use too many resources and the browser decides to tell all the pages they
/// lost the context and then restore it only to the front page for now.
/// - The user switches graphics cards (Turns on/off one or more in the control panel) or updates
/// their driver (no reboot required on Windows7+).
/// - Too many web pages use the GPU context and the browser decides to tell some pages they lost
/// the context in order to allow the newly opened ones to get it.
///
/// In all these cases and more your program may lose its WebGL context. By default, when a WebGL
/// program loses the context it never gets it back. To recover from a lost context you must add
/// a lost context handler and tell it to prevent the default behavior, and then re-setup all your
/// WebGL state and re-create all your WebGL resources when the context is restored.
///
/// This process is pretty complex and touches many places of your program, including WebGL error
/// handling, shaders and programs compilation and linking, WebGL-related variables null-checkers,
/// and many others. To learn more, see https://www.khronos.org/webgl/wiki/HandlingContextLost.
#[derive(Debug, Clone, CloneRef, Deref)]
pub struct Context {
rc: Rc<ContextData>,
}
/// Internal data of [`Context`].
#[derive(Debug, Deref)]
#[allow(missing_docs)]
pub struct ContextData {
#[deref]
native: native::ContextWithExtensions,
pub shader_compiler: shader::Compiler,
}
impl Context {
fn from_native(native: WebGl2RenderingContext) -> Self {
Self { rc: Rc::new(ContextData::from_native(native)) }
}
}
impl ContextData {
fn from_native(native: WebGl2RenderingContext) -> Self {
let native = native::ContextWithExtensions::from_native(native);
let shader_compiler = shader::Compiler::new(&native);
Self { native, shader_compiler }
}
}
/// Abstraction for Device Context Handler. This name is used in OpenGL / DirectX implementations.
/// For the web target, this is simply the canvas. As we currently support WebGL 2.0 only, this is
/// simply an alias to a canvas type. See the docs of [`Context`] to learn more about future
/// extension plans.
pub type DeviceContextHandler = web::HtmlCanvasElement;
/// Handler for closures taking care of context restoration. After dropping this handler and losing
/// the context, the context will not be restored automaticaly.
#[derive(Debug)]
pub struct ContextLostHandler {
on_lost: web::EventListenerHandle,
on_restored: web::EventListenerHandle,
}
// ===============
// === Display ===
// ===============
/// Abstraction for entities which contain [`DeviceContextHandler`] and are able to handle context
/// loss. In most cases, these are top-level entities, such as a scene.
#[allow(missing_docs)]
pub trait Display: CloneRef {
fn device_context_handler(&self) -> &DeviceContextHandler;
/// 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.
fn set_context(&self, context: Option<&Context>);
}
// ==============
// === Errors ===
// ==============
/// Error about unsupported standard implementation, like unsupported WebGL 2.0.
#[derive(Copy, Clone, Debug)]
pub struct UnsupportedStandard(&'static str);
impl std::error::Error for UnsupportedStandard {}
impl fmt::Display for UnsupportedStandard {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} is not supported.", self.0)
}
}
// ============================
// === Initialization Utils ===
// ============================
/// Initialize WebGL 2.0 context.
pub fn init_webgl_2_context<D: Display + 'static>(
display: &D,
) -> Result<ContextLostHandler, UnsupportedStandard> {
let hdc = display.device_context_handler();
let opt_context = hdc.get_webgl2_context();
match opt_context {
None => Err(UnsupportedStandard("WebGL 2.0")),
Some(native) => {
let context = Context::from_native(native);
type Handler = web::JsEventHandler;
display.set_context(Some(&context));
let lost: Handler = Closure::new(f_!([display]
WARNING!("Lost the WebGL context.");
display.set_context(None)
));
let restored: Handler = Closure::new(f_!([display]
WARNING!("Trying to restore the WebGL context.");
display.set_context(Some(&context))
));
let on_lost = web::add_event_listener(hdc, "webglcontextlost", lost);
let on_restored = web::add_event_listener(hdc, "webglcontextrestored", restored);
Ok(ContextLostHandler { on_lost, on_restored })
}
}
}
// =================
// === Constants ===
// =================
/// Type-safe WebGL context constants ([`u32`] values) wrapped in `GlEnum`. The list is copy-pasted
/// from [`WebGl2RenderingContext`] implementation. It should not change, as it is defined in the
/// WebGL 2.0 standard.
///
/// Please note that the [`TIMEOUT_IGNORED`] const is skipped, as it is the only [`f64`] const and
/// is not used anywhere.
macro_rules! define_constants {
($($name:ident),*$(,)?) => {
#[allow(missing_docs)]
impl Context {
$(pub const $name: GlEnum = GlEnum(WebGl2RenderingContext::$name);)*
}
};
}
define_constants![
READ_BUFFER,
UNPACK_ROW_LENGTH,
UNPACK_SKIP_ROWS,
UNPACK_SKIP_PIXELS,
PACK_ROW_LENGTH,
PACK_SKIP_ROWS,
PACK_SKIP_PIXELS,
COLOR,
DEPTH,
STENCIL,
RED,
RGB8,
RGBA8,
RGB10_A2,
TEXTURE_BINDING_3D,
UNPACK_SKIP_IMAGES,
UNPACK_IMAGE_HEIGHT,
TEXTURE_3D,
TEXTURE_WRAP_R,
MAX_3D_TEXTURE_SIZE,
UNSIGNED_INT_2_10_10_10_REV,
MAX_ELEMENTS_VERTICES,
MAX_ELEMENTS_INDICES,
TEXTURE_MIN_LOD,
TEXTURE_MAX_LOD,
TEXTURE_BASE_LEVEL,
TEXTURE_MAX_LEVEL,
MIN,
MAX,
DEPTH_COMPONENT24,
MAX_TEXTURE_LOD_BIAS,
TEXTURE_COMPARE_MODE,
TEXTURE_COMPARE_FUNC,
CURRENT_QUERY,
QUERY_RESULT,
QUERY_RESULT_AVAILABLE,
STREAM_READ,
STREAM_COPY,
STATIC_READ,
STATIC_COPY,
DYNAMIC_READ,
DYNAMIC_COPY,
MAX_DRAW_BUFFERS,
DRAW_BUFFER0,
DRAW_BUFFER1,
DRAW_BUFFER2,
DRAW_BUFFER3,
DRAW_BUFFER4,
DRAW_BUFFER5,
DRAW_BUFFER6,
DRAW_BUFFER7,
DRAW_BUFFER8,
DRAW_BUFFER9,
DRAW_BUFFER10,
DRAW_BUFFER11,
DRAW_BUFFER12,
DRAW_BUFFER13,
DRAW_BUFFER14,
DRAW_BUFFER15,
MAX_FRAGMENT_UNIFORM_COMPONENTS,
MAX_VERTEX_UNIFORM_COMPONENTS,
SAMPLER_3D,
SAMPLER_2D_SHADOW,
FRAGMENT_SHADER_DERIVATIVE_HINT,
PIXEL_PACK_BUFFER,
PIXEL_UNPACK_BUFFER,
PIXEL_PACK_BUFFER_BINDING,
PIXEL_UNPACK_BUFFER_BINDING,
FLOAT_MAT2X3,
FLOAT_MAT2X4,
FLOAT_MAT3X2,
FLOAT_MAT3X4,
FLOAT_MAT4X2,
FLOAT_MAT4X3,
SRGB,
SRGB8,
SRGB8_ALPHA8,
COMPARE_REF_TO_TEXTURE,
RGBA32F,
RGB32F,
RGBA16F,
RGB16F,
VERTEX_ATTRIB_ARRAY_INTEGER,
MAX_ARRAY_TEXTURE_LAYERS,
MIN_PROGRAM_TEXEL_OFFSET,
MAX_PROGRAM_TEXEL_OFFSET,
MAX_VARYING_COMPONENTS,
TEXTURE_2D_ARRAY,
TEXTURE_BINDING_2D_ARRAY,
R11F_G11F_B10F,
UNSIGNED_INT_10F_11F_11F_REV,
RGB9_E5,
UNSIGNED_INT_5_9_9_9_REV,
TRANSFORM_FEEDBACK_BUFFER_MODE,
MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS,
TRANSFORM_FEEDBACK_VARYINGS,
TRANSFORM_FEEDBACK_BUFFER_START,
TRANSFORM_FEEDBACK_BUFFER_SIZE,
TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN,
RASTERIZER_DISCARD,
MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS,
MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS,
INTERLEAVED_ATTRIBS,
SEPARATE_ATTRIBS,
TRANSFORM_FEEDBACK_BUFFER,
TRANSFORM_FEEDBACK_BUFFER_BINDING,
RGBA32UI,
RGB32UI,
RGBA16UI,
RGB16UI,
RGBA8UI,
RGB8UI,
RGBA32I,
RGB32I,
RGBA16I,
RGB16I,
RGBA8I,
RGB8I,
RED_INTEGER,
RGB_INTEGER,
RGBA_INTEGER,
SAMPLER_2D_ARRAY,
SAMPLER_2D_ARRAY_SHADOW,
SAMPLER_CUBE_SHADOW,
UNSIGNED_INT_VEC2,
UNSIGNED_INT_VEC3,
UNSIGNED_INT_VEC4,
INT_SAMPLER_2D,
INT_SAMPLER_3D,
INT_SAMPLER_CUBE,
INT_SAMPLER_2D_ARRAY,
UNSIGNED_INT_SAMPLER_2D,
UNSIGNED_INT_SAMPLER_3D,
UNSIGNED_INT_SAMPLER_CUBE,
UNSIGNED_INT_SAMPLER_2D_ARRAY,
DEPTH_COMPONENT32F,
DEPTH32F_STENCIL8,
FLOAT_32_UNSIGNED_INT_24_8_REV,
FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING,
FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE,
FRAMEBUFFER_ATTACHMENT_RED_SIZE,
FRAMEBUFFER_ATTACHMENT_GREEN_SIZE,
FRAMEBUFFER_ATTACHMENT_BLUE_SIZE,
FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE,
FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE,
FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE,
FRAMEBUFFER_DEFAULT,
UNSIGNED_INT_24_8,
DEPTH24_STENCIL8,
UNSIGNED_NORMALIZED,
DRAW_FRAMEBUFFER_BINDING,
READ_FRAMEBUFFER,
DRAW_FRAMEBUFFER,
READ_FRAMEBUFFER_BINDING,
RENDERBUFFER_SAMPLES,
FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER,
MAX_COLOR_ATTACHMENTS,
COLOR_ATTACHMENT1,
COLOR_ATTACHMENT2,
COLOR_ATTACHMENT3,
COLOR_ATTACHMENT4,
COLOR_ATTACHMENT5,
COLOR_ATTACHMENT6,
COLOR_ATTACHMENT7,
COLOR_ATTACHMENT8,
COLOR_ATTACHMENT9,
COLOR_ATTACHMENT10,
COLOR_ATTACHMENT11,
COLOR_ATTACHMENT12,
COLOR_ATTACHMENT13,
COLOR_ATTACHMENT14,
COLOR_ATTACHMENT15,
FRAMEBUFFER_INCOMPLETE_MULTISAMPLE,
MAX_SAMPLES,
HALF_FLOAT,
RG,
RG_INTEGER,
R8,
RG8,
R16F,
R32F,
RG16F,
RG32F,
R8I,
R8UI,
R16I,
R16UI,
R32I,
R32UI,
RG8I,
RG8UI,
RG16I,
RG16UI,
RG32I,
RG32UI,
VERTEX_ARRAY_BINDING,
R8_SNORM,
RG8_SNORM,
RGB8_SNORM,
RGBA8_SNORM,
SIGNED_NORMALIZED,
COPY_READ_BUFFER,
COPY_WRITE_BUFFER,
COPY_READ_BUFFER_BINDING,
COPY_WRITE_BUFFER_BINDING,
UNIFORM_BUFFER,
UNIFORM_BUFFER_BINDING,
UNIFORM_BUFFER_START,
UNIFORM_BUFFER_SIZE,
MAX_VERTEX_UNIFORM_BLOCKS,
MAX_FRAGMENT_UNIFORM_BLOCKS,
MAX_COMBINED_UNIFORM_BLOCKS,
MAX_UNIFORM_BUFFER_BINDINGS,
MAX_UNIFORM_BLOCK_SIZE,
MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS,
MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS,
UNIFORM_BUFFER_OFFSET_ALIGNMENT,
ACTIVE_UNIFORM_BLOCKS,
UNIFORM_TYPE,
UNIFORM_SIZE,
UNIFORM_BLOCK_INDEX,
UNIFORM_OFFSET,
UNIFORM_ARRAY_STRIDE,
UNIFORM_MATRIX_STRIDE,
UNIFORM_IS_ROW_MAJOR,
UNIFORM_BLOCK_BINDING,
UNIFORM_BLOCK_DATA_SIZE,
UNIFORM_BLOCK_ACTIVE_UNIFORMS,
UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES,
UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER,
UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER,
INVALID_INDEX,
MAX_VERTEX_OUTPUT_COMPONENTS,
MAX_FRAGMENT_INPUT_COMPONENTS,
MAX_SERVER_WAIT_TIMEOUT,
OBJECT_TYPE,
SYNC_CONDITION,
SYNC_STATUS,
SYNC_FLAGS,
SYNC_FENCE,
SYNC_GPU_COMMANDS_COMPLETE,
UNSIGNALED,
SIGNALED,
ALREADY_SIGNALED,
TIMEOUT_EXPIRED,
CONDITION_SATISFIED,
WAIT_FAILED,
SYNC_FLUSH_COMMANDS_BIT,
VERTEX_ATTRIB_ARRAY_DIVISOR,
ANY_SAMPLES_PASSED,
ANY_SAMPLES_PASSED_CONSERVATIVE,
SAMPLER_BINDING,
RGB10_A2UI,
INT_2_10_10_10_REV,
TRANSFORM_FEEDBACK,
TRANSFORM_FEEDBACK_PAUSED,
TRANSFORM_FEEDBACK_ACTIVE,
TRANSFORM_FEEDBACK_BINDING,
TEXTURE_IMMUTABLE_FORMAT,
MAX_ELEMENT_INDEX,
TEXTURE_IMMUTABLE_LEVELS,
MAX_CLIENT_WAIT_TIMEOUT_WEBGL,
DEPTH_BUFFER_BIT,
STENCIL_BUFFER_BIT,
COLOR_BUFFER_BIT,
POINTS,
LINES,
LINE_LOOP,
LINE_STRIP,
TRIANGLES,
TRIANGLE_STRIP,
TRIANGLE_FAN,
ZERO,
ONE,
SRC_COLOR,
ONE_MINUS_SRC_COLOR,
SRC_ALPHA,
ONE_MINUS_SRC_ALPHA,
DST_ALPHA,
ONE_MINUS_DST_ALPHA,
DST_COLOR,
ONE_MINUS_DST_COLOR,
SRC_ALPHA_SATURATE,
FUNC_ADD,
BLEND_EQUATION,
BLEND_EQUATION_RGB,
BLEND_EQUATION_ALPHA,
FUNC_SUBTRACT,
FUNC_REVERSE_SUBTRACT,
BLEND_DST_RGB,
BLEND_SRC_RGB,
BLEND_DST_ALPHA,
BLEND_SRC_ALPHA,
CONSTANT_COLOR,
ONE_MINUS_CONSTANT_COLOR,
CONSTANT_ALPHA,
ONE_MINUS_CONSTANT_ALPHA,
BLEND_COLOR,
ARRAY_BUFFER,
ELEMENT_ARRAY_BUFFER,
ARRAY_BUFFER_BINDING,
ELEMENT_ARRAY_BUFFER_BINDING,
STREAM_DRAW,
STATIC_DRAW,
DYNAMIC_DRAW,
BUFFER_SIZE,
BUFFER_USAGE,
CURRENT_VERTEX_ATTRIB,
FRONT,
BACK,
FRONT_AND_BACK,
CULL_FACE,
BLEND,
DITHER,
STENCIL_TEST,
DEPTH_TEST,
SCISSOR_TEST,
POLYGON_OFFSET_FILL,
SAMPLE_ALPHA_TO_COVERAGE,
SAMPLE_COVERAGE,
NO_ERROR,
INVALID_ENUM,
INVALID_VALUE,
INVALID_OPERATION,
OUT_OF_MEMORY,
CW,
CCW,
LINE_WIDTH,
ALIASED_POINT_SIZE_RANGE,
ALIASED_LINE_WIDTH_RANGE,
CULL_FACE_MODE,
FRONT_FACE,
DEPTH_RANGE,
DEPTH_WRITEMASK,
DEPTH_CLEAR_VALUE,
DEPTH_FUNC,
STENCIL_CLEAR_VALUE,
STENCIL_FUNC,
STENCIL_FAIL,
STENCIL_PASS_DEPTH_FAIL,
STENCIL_PASS_DEPTH_PASS,
STENCIL_REF,
STENCIL_VALUE_MASK,
STENCIL_WRITEMASK,
STENCIL_BACK_FUNC,
STENCIL_BACK_FAIL,
STENCIL_BACK_PASS_DEPTH_FAIL,
STENCIL_BACK_PASS_DEPTH_PASS,
STENCIL_BACK_REF,
STENCIL_BACK_VALUE_MASK,
STENCIL_BACK_WRITEMASK,
VIEWPORT,
SCISSOR_BOX,
COLOR_CLEAR_VALUE,
COLOR_WRITEMASK,
UNPACK_ALIGNMENT,
PACK_ALIGNMENT,
MAX_TEXTURE_SIZE,
MAX_VIEWPORT_DIMS,
SUBPIXEL_BITS,
RED_BITS,
GREEN_BITS,
BLUE_BITS,
ALPHA_BITS,
DEPTH_BITS,
STENCIL_BITS,
POLYGON_OFFSET_UNITS,
POLYGON_OFFSET_FACTOR,
TEXTURE_BINDING_2D,
SAMPLE_BUFFERS,
SAMPLES,
SAMPLE_COVERAGE_VALUE,
SAMPLE_COVERAGE_INVERT,
COMPRESSED_TEXTURE_FORMATS,
DONT_CARE,
FASTEST,
NICEST,
GENERATE_MIPMAP_HINT,
BYTE,
UNSIGNED_BYTE,
SHORT,
UNSIGNED_SHORT,
INT,
UNSIGNED_INT,
FLOAT,
DEPTH_COMPONENT,
ALPHA,
RGB,
RGBA,
LUMINANCE,
LUMINANCE_ALPHA,
UNSIGNED_SHORT_4_4_4_4,
UNSIGNED_SHORT_5_5_5_1,
UNSIGNED_SHORT_5_6_5,
FRAGMENT_SHADER,
VERTEX_SHADER,
MAX_VERTEX_ATTRIBS,
MAX_VERTEX_UNIFORM_VECTORS,
MAX_VARYING_VECTORS,
MAX_COMBINED_TEXTURE_IMAGE_UNITS,
MAX_VERTEX_TEXTURE_IMAGE_UNITS,
MAX_TEXTURE_IMAGE_UNITS,
MAX_FRAGMENT_UNIFORM_VECTORS,
SHADER_TYPE,
DELETE_STATUS,
LINK_STATUS,
VALIDATE_STATUS,
ATTACHED_SHADERS,
ACTIVE_UNIFORMS,
ACTIVE_ATTRIBUTES,
SHADING_LANGUAGE_VERSION,
CURRENT_PROGRAM,
NEVER,
LESS,
EQUAL,
LEQUAL,
GREATER,
NOTEQUAL,
GEQUAL,
ALWAYS,
KEEP,
REPLACE,
INCR,
DECR,
INVERT,
INCR_WRAP,
DECR_WRAP,
VENDOR,
RENDERER,
VERSION,
NEAREST,
LINEAR,
NEAREST_MIPMAP_NEAREST,
LINEAR_MIPMAP_NEAREST,
NEAREST_MIPMAP_LINEAR,
LINEAR_MIPMAP_LINEAR,
TEXTURE_MAG_FILTER,
TEXTURE_MIN_FILTER,
TEXTURE_WRAP_S,
TEXTURE_WRAP_T,
TEXTURE_2D,
TEXTURE,
TEXTURE_CUBE_MAP,
TEXTURE_BINDING_CUBE_MAP,
TEXTURE_CUBE_MAP_POSITIVE_X,
TEXTURE_CUBE_MAP_NEGATIVE_X,
TEXTURE_CUBE_MAP_POSITIVE_Y,
TEXTURE_CUBE_MAP_NEGATIVE_Y,
TEXTURE_CUBE_MAP_POSITIVE_Z,
TEXTURE_CUBE_MAP_NEGATIVE_Z,
MAX_CUBE_MAP_TEXTURE_SIZE,
TEXTURE0,
TEXTURE1,
TEXTURE2,
TEXTURE3,
TEXTURE4,
TEXTURE5,
TEXTURE6,
TEXTURE7,
TEXTURE8,
TEXTURE9,
TEXTURE10,
TEXTURE11,
TEXTURE12,
TEXTURE13,
TEXTURE14,
TEXTURE15,
TEXTURE16,
TEXTURE17,
TEXTURE18,
TEXTURE19,
TEXTURE20,
TEXTURE21,
TEXTURE22,
TEXTURE23,
TEXTURE24,
TEXTURE25,
TEXTURE26,
TEXTURE27,
TEXTURE28,
TEXTURE29,
TEXTURE30,
TEXTURE31,
ACTIVE_TEXTURE,
REPEAT,
CLAMP_TO_EDGE,
MIRRORED_REPEAT,
FLOAT_VEC2,
FLOAT_VEC3,
FLOAT_VEC4,
INT_VEC2,
INT_VEC3,
INT_VEC4,
BOOL,
BOOL_VEC2,
BOOL_VEC3,
BOOL_VEC4,
FLOAT_MAT2,
FLOAT_MAT3,
FLOAT_MAT4,
SAMPLER_2D,
SAMPLER_CUBE,
VERTEX_ATTRIB_ARRAY_ENABLED,
VERTEX_ATTRIB_ARRAY_SIZE,
VERTEX_ATTRIB_ARRAY_STRIDE,
VERTEX_ATTRIB_ARRAY_TYPE,
VERTEX_ATTRIB_ARRAY_NORMALIZED,
VERTEX_ATTRIB_ARRAY_POINTER,
VERTEX_ATTRIB_ARRAY_BUFFER_BINDING,
IMPLEMENTATION_COLOR_READ_TYPE,
IMPLEMENTATION_COLOR_READ_FORMAT,
COMPILE_STATUS,
LOW_FLOAT,
MEDIUM_FLOAT,
HIGH_FLOAT,
LOW_INT,
MEDIUM_INT,
HIGH_INT,
FRAMEBUFFER,
RENDERBUFFER,
RGBA4,
RGB5_A1,
RGB565,
DEPTH_COMPONENT16,
STENCIL_INDEX8,
DEPTH_STENCIL,
RENDERBUFFER_WIDTH,
RENDERBUFFER_HEIGHT,
RENDERBUFFER_INTERNAL_FORMAT,
RENDERBUFFER_RED_SIZE,
RENDERBUFFER_GREEN_SIZE,
RENDERBUFFER_BLUE_SIZE,
RENDERBUFFER_ALPHA_SIZE,
RENDERBUFFER_DEPTH_SIZE,
RENDERBUFFER_STENCIL_SIZE,
FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE,
FRAMEBUFFER_ATTACHMENT_OBJECT_NAME,
FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL,
FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE,
COLOR_ATTACHMENT0,
DEPTH_ATTACHMENT,
STENCIL_ATTACHMENT,
DEPTH_STENCIL_ATTACHMENT,
NONE,
FRAMEBUFFER_COMPLETE,
FRAMEBUFFER_INCOMPLETE_ATTACHMENT,
FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT,
FRAMEBUFFER_INCOMPLETE_DIMENSIONS,
FRAMEBUFFER_UNSUPPORTED,
FRAMEBUFFER_BINDING,
RENDERBUFFER_BINDING,
MAX_RENDERBUFFER_SIZE,
INVALID_FRAMEBUFFER_OPERATION,
UNPACK_FLIP_Y_WEBGL,
UNPACK_PREMULTIPLY_ALPHA_WEBGL,
CONTEXT_LOST_WEBGL,
UNPACK_COLORSPACE_CONVERSION_WEBGL,
BROWSER_DEFAULT_WEBGL
];

View File

@ -0,0 +1,78 @@
//! WebGL extensions management.
use crate::prelude::*;
use crate::system::gpu::data::GlEnum;
use web_sys::WebGl2RenderingContext;
use web_sys::WebGlProgram;
// ==================
// === Extensions ===
// ==================
/// Set of all extensions that we try to enable after acquiring the context.
#[derive(Debug, Clone, Deref)]
pub struct Extensions {
rc: Rc<ExtensionsData>,
}
impl Extensions {
/// Constructor.
pub fn init(context: &WebGl2RenderingContext) -> Self {
Self { rc: Rc::new(ExtensionsData::init(context)) }
}
}
/// Internal representation of [`Extensions`].
#[derive(Copy, Clone, Debug)]
#[allow(missing_docs)]
pub struct ExtensionsData {
pub khr_parallel_shader_compile: Option<KhrParallelShaderCompile>,
}
impl ExtensionsData {
/// Constructor.
fn init(context: &WebGl2RenderingContext) -> Self {
let khr_parallel_shader_compile = KhrParallelShaderCompile::try_init(context);
Self { khr_parallel_shader_compile }
}
}
// ================================
// === KhrParallelShaderCompile ===
// ================================
/// The `KHR_parallel_shader_compile` extension is used to poll shader compilation status without
/// blocking. To learn more, see:
/// [https://www.khronos.org/registry/webgl/extensions/KHR_parallel_shader_compile]
#[derive(Clone, Copy, Debug)]
#[allow(missing_docs)]
pub struct KhrParallelShaderCompile {
pub completion_status_khr: GlEnum,
}
impl KhrParallelShaderCompile {
/// Try to obtain the extension.
pub fn try_init(context: &WebGl2RenderingContext) -> Option<Self> {
let ext = context.get_extension("KHR_parallel_shader_compile").ok()??;
let completion_status_khr = GlEnum(
js_sys::Reflect::get(&ext, &"COMPLETION_STATUS_KHR".into()).ok()?.as_f64()? as u32,
);
Some(Self { completion_status_khr })
}
/// Asynchronously check if the job is ready. Returns [`None`] if it was impossible to get this
/// information. This can happen during context loss or driver failure.
pub fn is_ready(
&self,
context: &WebGl2RenderingContext,
program: &WebGlProgram,
) -> Option<bool> {
context.get_program_parameter(program, *self.completion_status_khr).as_bool()
}
}

View File

@ -0,0 +1,139 @@
//! Extensions for the native [`WebGl2RenderingContext`] implementation.
use crate::prelude::*;
use crate::system::gpu::context::extension::Extensions;
use crate::system::gpu::shader::Shader;
use web_sys::WebGl2RenderingContext;
use web_sys::WebGlProgram;
#[allow(missing_docs)]
pub mod traits {
pub use super::BlockingCheckStatus;
pub use super::BlockingGetErrorLog;
pub use super::ContextOps;
}
// =============================
// === ContextWithExtensions ===
// =============================
/// Native WebGL context and supported [`Extensions`].
#[derive(Clone, Debug, Deref)]
#[allow(missing_docs)]
pub struct ContextWithExtensions {
#[deref]
native: WebGl2RenderingContext,
pub extensions: Extensions,
}
impl ContextWithExtensions {
/// Constructor.
pub fn from_native(native: WebGl2RenderingContext) -> Self {
let extensions = Extensions::init(&native);
Self { native, extensions }
}
}
// ==================
// === ContextOps ===
// ==================
/// Extensions to [`WebGl2RenderingContext`].
pub trait ContextOps {
/// Combination of `use_program(Some(program))` and `self.use_program(None)`.
fn with_program<T>(&self, program: &WebGlProgram, f: impl FnOnce() -> T) -> T;
}
impl ContextOps for WebGl2RenderingContext {
fn with_program<T>(&self, program: &WebGlProgram, f: impl FnOnce() -> T) -> T {
self.use_program(Some(program));
let out = f();
self.use_program(None);
out
}
}
// ===========================
// === BlockingCheckStatus ===
// ===========================
/// Status check for the given WebGL entity. For shaders, it checks the compilation status, for
/// shader programs, the linking status, and then, returns the compilation/linking logs if any.
/// This operation is blocking and should be used only when necessary. To learn more, see:
/// [https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/WebGL_best_practices#compile_shaders_and_link_programs_in_parallel]
#[allow(missing_docs)]
pub trait BlockingCheckStatus<T> {
fn blocking_get_status_logs(&self, target: &T) -> Option<String>;
}
impl<T> BlockingCheckStatus<Shader<T>> for WebGl2RenderingContext {
fn blocking_get_status_logs(&self, target: &Shader<T>) -> Option<String> {
let status = WebGl2RenderingContext::COMPILE_STATUS;
let ok = self.get_shader_parameter(target, status).as_bool().unwrap_or(false);
(!ok).then_some(unwrap_error(self.get_shader_info_log(target)))
}
}
impl BlockingCheckStatus<WebGlProgram> for WebGl2RenderingContext {
fn blocking_get_status_logs(&self, target: &WebGlProgram) -> Option<String> {
let status = WebGl2RenderingContext::LINK_STATUS;
let ok = self.get_program_parameter(target, status).as_bool().unwrap_or(false);
(!ok).then_some(unwrap_error(self.get_program_info_log(target)))
}
}
fn unwrap_error(opt_err: Option<String>) -> String {
opt_err.unwrap_or_else(|| "Unknown error.".into())
}
// ===========================
// === BlockingGetErrorLog ===
// ===========================
/// Get a nicely formatted compilation error log for the given shader. The log will contain a
/// preview of the code where the error occurred. This operation is blocking and should be used
/// only when necessary. To learn more, see:
/// [https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/WebGL_best_practices#compile_shaders_and_link_programs_in_parallel]
#[allow(missing_docs)]
pub trait BlockingGetErrorLog {
fn blocking_format_error_log<T>(&self, shader: &Shader<T>) -> Option<String>;
}
impl BlockingGetErrorLog for WebGl2RenderingContext {
fn blocking_format_error_log<T>(&self, shader: &Shader<T>) -> Option<String> {
self.blocking_get_status_logs(shader).map(|message| {
let lines = shader.code.split('\n').collect::<Vec<&str>>();
let lines_num = lines.len();
let lines_str_len = (lines_num as f32).log10().ceil() as usize;
let lines_enum = lines.into_iter().enumerate();
let lines_with_num =
lines_enum.map(|(n, l)| format!("{1:0$} : {2}", lines_str_len, n + 1, l));
let lines_with_num = lines_with_num.collect::<Vec<String>>();
let code_with_num = lines_with_num.join("\n");
let error_loc_pfx = "ERROR: 0:";
let preview_code = if let Some(msg) = message.strip_prefix(error_loc_pfx) {
let line_num: String = msg.chars().take_while(|c| c.is_digit(10)).collect();
let line_num = line_num.parse::<usize>().unwrap() - 1;
let preview_radius = 5;
let preview_line_start = std::cmp::max(0, line_num - preview_radius);
let preview_line_end = std::cmp::min(lines_num, line_num + preview_radius);
let preview = lines_with_num[preview_line_start..preview_line_end].join("\n");
format!("...\n{}\n...", preview)
} else {
code_with_num
};
format!("{}\n{}", message, preview_code)
})
}
}

View File

@ -205,7 +205,8 @@ impl {
self.size
}
/// Set the WebGL context. See the main architecture docs of this library to learn more.
/// 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.context = context.cloned();
for buffer in &self.buffers {

View File

@ -15,7 +15,7 @@ use crate::system::gpu::data::attribute::Attribute;
use crate::system::gpu::data::buffer::item::JsBufferView;
use crate::system::gpu::data::buffer::usage::BufferUsage;
use crate::system::gpu::data::default::gpu_default;
use crate::system::Context;
use crate::system::gpu::Context;
use enso_shapely::shared;
use std::iter::Extend;
@ -187,7 +187,7 @@ impl<T:Storable> {
pub fn update(&mut self) {
info!(self.logger, "Updating.", || {
if let Some(gl) = &self.gl {
gl.context.bind_buffer(Context::ARRAY_BUFFER,Some(&gl.buffer));
gl.context.bind_buffer(*Context::ARRAY_BUFFER,Some(&gl.buffer));
if self.resize_dirty.check() {
self.upload_data(&None);
} else if self.mut_dirty.check_all() {
@ -227,6 +227,9 @@ impl<T:Storable> {
for col in 0..cols {
let lloc = loc + col as u32;
let off = col * col_byte_size;
// Please note that for performance reasons, vertex attrib 0 should always be
// enabled as an array. For more details see
// https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/WebGL_best_practices#always_enable_vertex_attrib_0_as_an_array
gl.context.enable_vertex_attrib_array(lloc);
if is_integer {
gl.context.vertex_attrib_i_pointer_with_i32(lloc,rows,item_type,stride,off);
@ -243,7 +246,8 @@ impl<T:Storable> {
}
}
/// Set the WebGL context. See the main architecture docs of this library to learn more.
/// 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.resize_dirty.set();
@ -302,7 +306,7 @@ impl<T: Storable> BufferData<T> {
// Note [Safety]
let js_array = data.js_buffer_view();
gl.context.buffer_data_with_array_buffer_view(
Context::ARRAY_BUFFER,
*Context::ARRAY_BUFFER,
&js_array,
gl_enum,
);
@ -335,7 +339,7 @@ impl<T: Storable> BufferData<T> {
// Note [Safety]
let js_array = data.js_buffer_view();
gl.context.buffer_sub_data_with_i32_and_array_buffer_view_and_src_offset_and_length(
Context::ARRAY_BUFFER,
*Context::ARRAY_BUFFER,
dst_byte_offset,
&js_array,
start_item,
@ -460,7 +464,8 @@ crate::with_all_prim_types!([[define_any_buffer] []]);
#[enum_dispatch]
#[allow(missing_docs)]
pub trait IsBuffer {
/// Set the WebGL context. See the main architecture docs of this library to learn more.
/// 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.
fn set_context(&self, context: Option<&Context>);
fn add_element(&self);
fn len(&self) -> usize;
@ -475,7 +480,8 @@ pub trait IsBuffer {
// This implementation is needed, because `enum_dispatch` library requires variant types to
// implement the trait, as it invokes trait methods explicitly on variant values.
//
// Thus we provide implementation that just redirects calls to methods defined in the Buffer itself.
// Thus, we provide implementation that just redirects calls to methods defined in the Buffer
// itself.
impl<T: Storable> IsBuffer for Buffer<T> {
fn set_context(&self, context: Option<&Context>) {
self.set_context(context)

View File

@ -3,7 +3,7 @@
use crate::prelude::*;
use crate::system::gpu::data::gl_enum::GlEnum;
use crate::system::Context;
use crate::system::gpu::Context;
@ -17,39 +17,39 @@ crate::define_singleton_enum_gl! { [GlEnum]
/// The contents are intended to be specified once by the application, and used many times
/// as the source for WebGL drawing and image specification commands.
Static = GlEnum(Context::STATIC_DRAW),
Static = Context::STATIC_DRAW,
/// Default. The contents are intended to be respecified repeatedly by the application, and
/// used many times as the source for WebGL drawing and image specification commands.
Dynamic = GlEnum(Context::DYNAMIC_DRAW),
Dynamic = Context::DYNAMIC_DRAW,
/// The contents are intended to be specified once by the application, and used at most a
/// few times as the source for WebGL drawing and image specification commands.
Stream = GlEnum(Context::STREAM_DRAW),
Stream = Context::STREAM_DRAW,
/// The contents are intended to be specified once by reading data from WebGL, and queried
/// many times by the application.
StaticRead = GlEnum(Context::STATIC_READ),
StaticRead = Context::STATIC_READ,
/// The contents are intended to be respecified repeatedly by reading data from WebGL, and
/// queried many times by the application.
DynamicRead = GlEnum(Context::DYNAMIC_READ),
DynamicRead = Context::DYNAMIC_READ,
/// The contents are intended to be specified once by reading data from WebGL, and queried
/// at most a few times by the application
StreamRead = GlEnum(Context::STREAM_READ),
StreamRead = Context::STREAM_READ,
/// The contents are intended to be specified once by reading data from WebGL, and used many
/// times as the source for WebGL drawing and image specification commands.
StaticCopy = GlEnum(Context::STATIC_COPY),
StaticCopy = Context::STATIC_COPY,
/// The contents are intended to be respecified repeatedly by reading data from WebGL, and
/// used many times as the source for WebGL drawing and image specification commands.
DynamicCopy = GlEnum(Context::DYNAMIC_COPY),
DynamicCopy = Context::DYNAMIC_COPY,
/// The contents are intended to be specified once by reading data from WebGL, and used at
/// most a few times as the source for WebGL drawing and image specification commands.
StreamCopy = GlEnum(Context::STREAM_COPY),
StreamCopy = Context::STREAM_COPY,
}
}

View File

@ -3,7 +3,7 @@
use crate::prelude::*;
use crate::system::gpu::data::prim::*;
use crate::system::Context;
use crate::system::gpu::Context;
@ -12,7 +12,8 @@ use crate::system::Context;
// ==============
/// The newtype for WebGL enums.
#[derive(Copy, Clone, Debug, Default, Display)]
#[repr(C)]
#[derive(Copy, Clone, Debug, Default, Deref, Display)]
pub struct GlEnum(pub u32);
impl From<GlEnum> for u32 {
@ -159,21 +160,21 @@ macro_rules! define_singleton_enum_gl_from {
// ================================
define_gl_enum_conversions! { [GlEnum]
bool = GlEnum(Context::BOOL),
u8 = GlEnum(Context::UNSIGNED_BYTE),
u16 = GlEnum(Context::UNSIGNED_SHORT),
u32 = GlEnum(Context::UNSIGNED_INT),
i8 = GlEnum(Context::BYTE),
i16 = GlEnum(Context::SHORT),
i32 = GlEnum(Context::INT),
f16 = GlEnum(Context::HALF_FLOAT),
f32 = GlEnum(Context::FLOAT),
f32_u24_u8_REV = GlEnum(Context::FLOAT_32_UNSIGNED_INT_24_8_REV),
u16_4_4_4_4 = GlEnum(Context::UNSIGNED_SHORT_4_4_4_4),
u16_5_5_5_1 = GlEnum(Context::UNSIGNED_SHORT_5_5_5_1),
u16_5_6_5 = GlEnum(Context::UNSIGNED_SHORT_5_6_5),
u32_f10_f11_f11_REV = GlEnum(Context::UNSIGNED_INT_10F_11F_11F_REV),
u32_24_8 = GlEnum(Context::UNSIGNED_INT_24_8),
u32_2_10_10_10_REV = GlEnum(Context::UNSIGNED_INT_2_10_10_10_REV),
u32_5_9_9_9_REV = GlEnum(Context::UNSIGNED_INT_5_9_9_9_REV),
bool = Context::BOOL,
u8 = Context::UNSIGNED_BYTE,
u16 = Context::UNSIGNED_SHORT,
u32 = Context::UNSIGNED_INT,
i8 = Context::BYTE,
i16 = Context::SHORT,
i32 = Context::INT,
f16 = Context::HALF_FLOAT,
f32 = Context::FLOAT,
f32_u24_u8_REV = Context::FLOAT_32_UNSIGNED_INT_24_8_REV,
u16_4_4_4_4 = Context::UNSIGNED_SHORT_4_4_4_4,
u16_5_5_5_1 = Context::UNSIGNED_SHORT_5_5_5_1,
u16_5_6_5 = Context::UNSIGNED_SHORT_5_6_5,
u32_f10_f11_f11_REV = Context::UNSIGNED_INT_10F_11F_11F_REV,
u32_24_8 = Context::UNSIGNED_INT_24_8,
u32_2_10_10_10_REV = Context::UNSIGNED_INT_2_10_10_10_REV,
u32_5_9_9_9_REV = Context::UNSIGNED_INT_5_9_9_9_REV,
}

View File

@ -31,15 +31,15 @@ pub struct TextureUnit(u32);
#[derive(Debug)]
pub struct TextureBindGuard {
context: Context,
target: u32,
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);
self.context.active_texture(*Context::TEXTURE0 + self.unit.to::<u32>());
self.context.bind_texture(*self.target, None);
self.context.active_texture(*Context::TEXTURE0);
}
}
@ -74,10 +74,10 @@ impl Parameters {
/// Applies the context parameters in the given context.
pub fn apply_parameters(self, context: &Context) {
let target = Context::TEXTURE_2D;
context.tex_parameteri(target, Context::TEXTURE_MIN_FILTER, self.min_filter as i32);
context.tex_parameteri(target, Context::TEXTURE_MIN_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);
context.tex_parameteri(*target, *Context::TEXTURE_MIN_FILTER, *self.min_filter as i32);
context.tex_parameteri(*target, *Context::TEXTURE_MIN_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);
}
}
@ -89,16 +89,25 @@ impl Parameters {
/// 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)]
pub enum MagFilter {
Linear = Context::LINEAR as isize,
Nearest = Context::NEAREST as isize,
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
Self::LINEAR
}
}
@ -107,20 +116,29 @@ impl Default for MagFilter {
/// 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)]
pub enum MinFilter {
Linear = Context::LINEAR as isize,
Nearest = Context::NEAREST as isize,
NearestMipmapNearest = Context::NEAREST_MIPMAP_NEAREST as isize,
LinearMipmapNearest = Context::LINEAR_MIPMAP_NEAREST as isize,
NearestMipmapLinear = Context::NEAREST_MIPMAP_LINEAR as isize,
LinearMipmapLinear = Context::LINEAR_MIPMAP_LINEAR as isize,
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
Self::LINEAR
}
}
@ -128,17 +146,26 @@ impl Default for MinFilter {
///
/// 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)]
pub enum Wrap {
Repeat = Context::REPEAT as isize,
ClampToEdge = Context::CLAMP_TO_EDGE as isize,
MirroredRepeat = Context::MIRRORED_REPEAT as isize,
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::ClampToEdge
Self::CLAMP_TO_EDGE
}
}
@ -310,7 +337,7 @@ where
let internal_format = Self::gl_internal_format();
let format = Self::gl_format().into();
let elem_type = Self::gl_elem_type();
self.context.bind_texture(target, Some(&self.gl_texture));
self.context.bind_texture(*target, Some(&self.gl_texture));
#[allow(unsafe_code)]
unsafe {
// We use unsafe array view which is used immediately, so no allocations should happen
@ -318,7 +345,7 @@ where
let view = data.js_buffer_view();
let result = self.context
.tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_array_buffer_view
(target,level,internal_format,width,height,border,format,elem_type,Some(&view));
(*target,level,internal_format,width,height,border,format,elem_type,Some(&view));
result.unwrap();
}
self.apply_texture_parameters(&self.context);
@ -371,9 +398,9 @@ impl<
self.with_content(|this| {
let context = context.clone();
let target = Context::TEXTURE_2D;
context.active_texture(Context::TEXTURE0 + unit.to::<u32>());
context.bind_texture(target, Some(&this.gl_texture));
context.active_texture(Context::TEXTURE0);
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 }
})
}

View File

@ -56,10 +56,10 @@ impl<I: InternalFormat, T: ItemType> TextureReload for Texture<GpuOnly, I, T> {
let format = Self::gl_format().into();
let elem_type = Self::gl_elem_type();
self.context().bind_texture(target, Some(self.gl_texture()));
self.context().bind_texture(*target, Some(self.gl_texture()));
self.context()
.tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array(
target,
*target,
level,
internal_format,
width,

View File

@ -62,10 +62,10 @@ impl<I: InternalFormat, T: ItemType> Texture<RemoteImage, I, T> {
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().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,
*target,
level,
internal_format,
width,
@ -103,10 +103,10 @@ impl<I: InternalFormat, T: ItemType> TextureReload for Texture<RemoteImage, I, T
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.bind_texture(*target, Some(&gl_texture));
context
.tex_image_2d_with_u32_and_u32_and_html_image_element(
target,
*target,
level,
internal_format,
format,

View File

@ -12,71 +12,71 @@ use crate::system::gpu::Context;
// ===============
crate::define_singletons_gl! { [GlEnum]
Alpha = GlEnum(Context::ALPHA),
Depth24Stencil8 = GlEnum(Context::DEPTH24_STENCIL8),
Depth32fStencil8 = GlEnum(Context::DEPTH32F_STENCIL8),
DepthComponent = GlEnum(Context::DEPTH_COMPONENT),
DepthComponent16 = GlEnum(Context::DEPTH_COMPONENT16),
DepthComponent24 = GlEnum(Context::DEPTH_COMPONENT24),
DepthComponent32f = GlEnum(Context::DEPTH_COMPONENT32F),
DepthStencil = GlEnum(Context::DEPTH_STENCIL),
Luminance = GlEnum(Context::LUMINANCE),
LuminanceAlpha = GlEnum(Context::LUMINANCE_ALPHA),
R11fG11fB10f = GlEnum(Context::R11F_G11F_B10F),
R16f = GlEnum(Context::R16F),
R16i = GlEnum(Context::R16I),
R16ui = GlEnum(Context::R16UI),
R32f = GlEnum(Context::R32F),
R32i = GlEnum(Context::R32I),
R32ui = GlEnum(Context::R32UI),
R8 = GlEnum(Context::R8),
R8i = GlEnum(Context::R8I),
R8SNorm = GlEnum(Context::R8_SNORM),
R8ui = GlEnum(Context::R8UI),
Red = GlEnum(Context::RED),
RedInteger = GlEnum(Context::RED_INTEGER),
Rg = GlEnum(Context::RG),
Rg16f = GlEnum(Context::RG16F),
Rg16i = GlEnum(Context::RG16I),
Rg16ui = GlEnum(Context::RG16UI),
Rg32f = GlEnum(Context::RG32F),
Rg32i = GlEnum(Context::RG32I),
Rg32ui = GlEnum(Context::RG32UI),
Rg8 = GlEnum(Context::RG8),
Rg8i = GlEnum(Context::RG8I),
Rg8SNorm = GlEnum(Context::RG8_SNORM),
Rg8ui = GlEnum(Context::RG8UI),
Rgb = GlEnum(Context::RGB),
Rgb10A2 = GlEnum(Context::RGB10_A2),
Rgb10A2ui = GlEnum(Context::RGB10_A2UI),
Rgb16f = GlEnum(Context::RGB16F),
Rgb16i = GlEnum(Context::RGB16I),
Rgb16ui = GlEnum(Context::RGB16UI),
Rgb32f = GlEnum(Context::RGB32F),
Rgb32i = GlEnum(Context::RGB32I),
Rgb32ui = GlEnum(Context::RGB32UI),
Rgb565 = GlEnum(Context::RGB565),
Rgb5A1 = GlEnum(Context::RGB5_A1),
Rgb8 = GlEnum(Context::RGB8),
Rgb8i = GlEnum(Context::RGB8I),
Rgb8SNorm = GlEnum(Context::RGB8_SNORM),
Rgb8ui = GlEnum(Context::RGB8UI),
Rgb9E5 = GlEnum(Context::RGB9_E5),
Rgba = GlEnum(Context::RGBA),
Rgba16f = GlEnum(Context::RGBA16F),
Rgba16i = GlEnum(Context::RGBA16I),
Rgba16ui = GlEnum(Context::RGBA16UI),
Rgba32f = GlEnum(Context::RGBA32F),
Rgba32i = GlEnum(Context::RGBA32I),
Rgba32ui = GlEnum(Context::RGBA32UI),
Rgba4 = GlEnum(Context::RGBA4),
Rgba8 = GlEnum(Context::RGBA8),
Rgba8i = GlEnum(Context::RGBA8I),
Rgba8SNorm = GlEnum(Context::RGBA8_SNORM),
Rgba8ui = GlEnum(Context::RGBA8UI),
RgbaInteger = GlEnum(Context::RGBA_INTEGER),
RgbInteger = GlEnum(Context::RGB_INTEGER),
RgInteger = GlEnum(Context::RG_INTEGER),
SRgb8 = GlEnum(Context::SRGB8),
SRgb8Alpha8 = GlEnum(Context::SRGB8_ALPHA8),
Alpha = Context::ALPHA,
Depth24Stencil8 = Context::DEPTH24_STENCIL8,
Depth32fStencil8 = Context::DEPTH32F_STENCIL8,
DepthComponent = Context::DEPTH_COMPONENT,
DepthComponent16 = Context::DEPTH_COMPONENT16,
DepthComponent24 = Context::DEPTH_COMPONENT24,
DepthComponent32f = Context::DEPTH_COMPONENT32F,
DepthStencil = Context::DEPTH_STENCIL,
Luminance = Context::LUMINANCE,
LuminanceAlpha = Context::LUMINANCE_ALPHA,
R11fG11fB10f = Context::R11F_G11F_B10F,
R16f = Context::R16F,
R16i = Context::R16I,
R16ui = Context::R16UI,
R32f = Context::R32F,
R32i = Context::R32I,
R32ui = Context::R32UI,
R8 = Context::R8,
R8i = Context::R8I,
R8SNorm = Context::R8_SNORM,
R8ui = Context::R8UI,
Red = Context::RED,
RedInteger = Context::RED_INTEGER,
Rg = Context::RG,
Rg16f = Context::RG16F,
Rg16i = Context::RG16I,
Rg16ui = Context::RG16UI,
Rg32f = Context::RG32F,
Rg32i = Context::RG32I,
Rg32ui = Context::RG32UI,
Rg8 = Context::RG8,
Rg8i = Context::RG8I,
Rg8SNorm = Context::RG8_SNORM,
Rg8ui = Context::RG8UI,
Rgb = Context::RGB,
Rgb10A2 = Context::RGB10_A2,
Rgb10A2ui = Context::RGB10_A2UI,
Rgb16f = Context::RGB16F,
Rgb16i = Context::RGB16I,
Rgb16ui = Context::RGB16UI,
Rgb32f = Context::RGB32F,
Rgb32i = Context::RGB32I,
Rgb32ui = Context::RGB32UI,
Rgb565 = Context::RGB565,
Rgb5A1 = Context::RGB5_A1,
Rgb8 = Context::RGB8,
Rgb8i = Context::RGB8I,
Rgb8SNorm = Context::RGB8_SNORM,
Rgb8ui = Context::RGB8UI,
Rgb9E5 = Context::RGB9_E5,
Rgba = Context::RGBA,
Rgba16f = Context::RGBA16F,
Rgba16i = Context::RGBA16I,
Rgba16ui = Context::RGBA16UI,
Rgba32f = Context::RGBA32F,
Rgba32i = Context::RGBA32I,
Rgba32ui = Context::RGBA32UI,
Rgba4 = Context::RGBA4,
Rgba8 = Context::RGBA8,
Rgba8i = Context::RGBA8I,
Rgba8SNorm = Context::RGBA8_SNORM,
Rgba8ui = Context::RGBA8UI,
RgbaInteger = Context::RGBA_INTEGER,
RgbInteger = Context::RGB_INTEGER,
RgInteger = Context::RG_INTEGER,
SRgb8 = Context::SRGB8,
SRgb8Alpha8 = Context::SRGB8_ALPHA8,
}

View File

@ -26,8 +26,8 @@
/// integer format, it is bypassed.
///
/// The features of some texture formats can be modified through extensions. For example, the
/// the extension `EXT_color_buffer_float` can make the group of float texture (e.g., `Rgba32f`)
/// color renderable.
/// extension `EXT_color_buffer_float` can make the group of float texture (e.g., `Rgba32f`) color
/// renderable.
#[macro_export]
macro_rules! with_texture_format_relations { ($f:ident $args:tt) => { $crate::$f! { $args
// INTERNAL_FORMAT FORMAT SAMPLER COL FILT BLEND [POSSIBLE_TYPE:BYTES_PER_TEXTURE_ELEM]

View File

@ -6,7 +6,7 @@ use crate::system::gpu::data::prim::*;
use crate::system::gpu::data::texture::*;
use enum_dispatch::*;
use crate::system::Context;
use crate::system::gpu::Context;
use enso_shapely::shared;
use upload::UniformUpload;
@ -308,7 +308,7 @@ macro_rules! define_any_texture_uniform {
impl TextureOps for AnyTextureUniform {
fn bind_texture_unit
(&self, context:&crate::display::Context, unit:TextureUnit) -> TextureBindGuard {
(&self, context:&Context, unit:TextureUnit) -> TextureBindGuard {
match self {
$(
Self::[<$storage _ $internal_format _ $item_type >](t) =>

View File

@ -1,14 +1,12 @@
// === Non-Standard Linter Configuration ===
#![allow(missing_docs)]
//! Abstractions for GPU shaders and shader programs.
use enso_prelude::*;
use enso_web::traits::*;
use crate::system::Context;
use crate::display::GlEnum;
use crate::display::ToGlEnum;
use enso_web as web;
use js_sys::Float32Array;
use web_sys::WebGlBuffer;
use enso_shapely::define_singleton_enum;
use web_sys::WebGl2RenderingContext;
use web_sys::WebGlProgram;
use web_sys::WebGlShader;
@ -17,14 +15,13 @@ use web_sys::WebGlShader;
// === Export ===
// ==============
#[warn(missing_docs)]
pub mod compiler;
pub mod glsl;
pub use compiler::Compiler;
pub use types::*;
// ===============
// === Exports ===
// ===============
/// Common types.
pub mod types {
@ -32,279 +29,93 @@ pub mod types {
pub use glsl::traits::*;
pub use glsl::Glsl;
}
pub use types::*;
// =============
// === Types ===
// =============
// ============
// === Type ===
// ============
pub type Shader = WebGlShader;
pub type Program = WebGlProgram;
// =============
// === Error ===
// =============
#[derive(Debug, Fail)]
pub enum SingleTargetError {
#[fail(display = "Unable to create {}.", target)]
Create { target: ErrorTarget },
#[fail(display = "Unable to compile {}.\n{}\n\n{}", target, message, preview_code)]
Compile { target: ErrorTarget, message: String, preview_code: String },
define_singleton_enum! {
/// The shader type. Currently, only vertex and fragment shaders are supported (in WebGL 2.0).
Type {
Vertex, Fragment
}
}
#[derive(Debug, Fail, From)]
pub enum Error {
Create {
target: ErrorTarget,
},
Compile {
js_path: String,
vertex: Option<SingleTargetError>,
fragment: Option<SingleTargetError>,
},
}
impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
impl ToGlEnum for Type {
fn to_gl_enum(&self) -> GlEnum {
match self {
Self::Create { target } => write!(f, "Unable to create {}", target),
Self::Compile { js_path, vertex, fragment } => {
let vtx_msg = vertex.as_ref().map(|t| format!("\n\n{}", t)).unwrap_or_default();
let frag_msg = fragment.as_ref().map(|t| format!("\n\n{}", t)).unwrap_or_default();
let run_msg = |n| {
format!("Run `console.log({}.{})` to inspect the {} shader.", js_path, n, n)
};
let vtx_run_msg = run_msg("vertex");
let frag_run_msg = run_msg("fragment");
let err_msg = "Unable to create shader.";
write!(f, "{}\n{}\n{}{}{}", err_msg, vtx_run_msg, frag_run_msg, vtx_msg, frag_msg)
}
}
}
}
#[derive(Copy, Clone, Debug, Fail)]
pub enum ErrorTarget {
#[fail(display = "shader")]
Shader,
#[fail(display = "program")]
Program,
}
// ===================
// === Compilation ===
// ===================
/// Compilation error containing detailed error message.
#[derive(Debug)]
pub struct CompilationError(String);
impl std::error::Error for CompilationError {}
impl Display for CompilationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
/// Abstraction for [`Shader`] and [`Program`] error handling.
pub trait CompilationTarget {
/// Check whether the target was assembled correctly. In the context of [`Shader`], it checks if
/// the compilation was successful. In the context of [`Program`], it checks whether it was
/// linked successfully. In case of lost context, this function will succeed. For more
/// information of why, see: https://www.khronos.org/webgl/wiki/HandlingContextLost.
fn check(&self, ctx: &Context) -> Result<(), CompilationError>;
}
impl CompilationTarget for Shader {
fn check(&self, ctx: &Context) -> Result<(), CompilationError> {
let status = Context::COMPILE_STATUS;
let status_ok = ctx.get_shader_parameter(self, status).as_bool().unwrap_or(false);
let context_lost = ctx.is_context_lost();
let ok = (status_ok || context_lost).then_some(());
ok.ok_or_else(|| CompilationError(unwrap_error(ctx.get_shader_info_log(self))))
}
}
impl CompilationTarget for Program {
fn check(&self, ctx: &Context) -> Result<(), CompilationError> {
let status = Context::LINK_STATUS;
let status_ok = ctx.get_program_parameter(self, status).as_bool().unwrap_or(false);
let context_lost = ctx.is_context_lost();
let ok = (status_ok || context_lost).then_some(());
ok.ok_or_else(|| CompilationError(unwrap_error(ctx.get_program_info_log(self))))
}
}
fn unwrap_error(opt_err: Option<String>) -> String {
opt_err.unwrap_or_else(|| "Unknown error.".into())
}
// ======================
// === Compile / Link ===
// ======================
pub fn compile_vertex_shader(ctx: &Context, src: &str) -> Result<Shader, SingleTargetError> {
compile_shader(ctx, Context::VERTEX_SHADER, src)
}
pub fn compile_fragment_shader(ctx: &Context, src: &str) -> Result<Shader, SingleTargetError> {
compile_shader(ctx, Context::FRAGMENT_SHADER, src)
}
pub fn compile_shader(ctx: &Context, tp: u32, src: &str) -> Result<Shader, SingleTargetError> {
let target = ErrorTarget::Shader;
let shader = ctx.create_shader(tp).ok_or(SingleTargetError::Create { target })?;
ctx.shader_source(&shader, src);
ctx.compile_shader(&shader);
match shader.check(ctx) {
Ok(_) => Ok(shader),
Err(CompilationError(message)) => {
let code: String = src.into();
let lines = code.split('\n').collect::<Vec<&str>>();
let lines_num = lines.len();
let lines_str_len = (lines_num as f32).log10().ceil() as usize;
let lines_enum = lines.into_iter().enumerate();
let lines_with_num =
lines_enum.map(|(n, l)| format!("{1:0$} : {2}", lines_str_len, n + 1, l));
let lines_with_num = lines_with_num.collect::<Vec<String>>();
let code_with_num = lines_with_num.join("\n");
let error_loc_pfx = "ERROR: 0:";
let preview_code = if let Some(msg) = message.strip_prefix(error_loc_pfx) {
let line_num: String = msg.chars().take_while(|c| c.is_digit(10)).collect();
let line_num = line_num.parse::<usize>().unwrap() - 1;
let preview_radius = 5;
let preview_line_start = std::cmp::max(0, line_num - preview_radius);
let preview_line_end = std::cmp::min(lines_num, line_num + preview_radius);
lines_with_num[preview_line_start..preview_line_end].join("\n")
} else {
code_with_num
};
Err(SingleTargetError::Compile { target, message, preview_code })
}
}
}
/// Structure representing one of two states an error, or lack of values because of a lost
/// context.
#[derive(Debug)]
pub enum ContextLossOrError {
ContextLoss,
Error(Error),
}
/// Link the provided vertex and fragment shaders into a program.
pub fn link_program(
ctx: &Context,
vert_shader: &Shader,
frag_shader: &Shader,
) -> Result<Program, ContextLossOrError> {
let target = ErrorTarget::Program;
match ctx.create_program() {
None => Err(if ctx.is_context_lost() {
ContextLossOrError::ContextLoss
} else {
ContextLossOrError::Error(Error::Create { target })
}),
Some(program) => {
ctx.attach_shader(&program, vert_shader);
ctx.attach_shader(&program, frag_shader);
ctx.link_program(&program);
Ok(program)
}
}
}
/// Compile the provided vertex and fragment shader sources and then link them into a program.
pub fn compile_program(
ctx: &Context,
vert_src: &str,
frag_src: &str,
) -> Result<Program, ContextLossOrError> {
let vert_shader = compile_vertex_shader(ctx, vert_src);
let frag_shader = compile_fragment_shader(ctx, frag_src);
match (vert_shader, frag_shader) {
(Ok(vert_shader), Ok(frag_shader)) => link_program(ctx, &vert_shader, &frag_shader),
(vert_shader, frag_shader) => {
let vertex = vert_shader.err();
let fragment = frag_shader.err();
// FIXME: this should be taken from config and refactored to a function
let path = &["enso", "debug", "shader"];
let shader_dbg = web::Reflect::get_nested_object_or_create(&web::window, path);
let shader_dbg = shader_dbg.unwrap();
let len = web::Object::keys(&shader_dbg).length();
let debug_var_name = format!("shader_{}", len);
let shader_tgt_dbg = web::Object::new();
web::Reflect::set(&shader_dbg, &(&debug_var_name).into(), &shader_tgt_dbg).ok();
web::Reflect::set(&shader_tgt_dbg, &"vertex".into(), &vert_src.into()).ok();
web::Reflect::set(&shader_tgt_dbg, &"fragment".into(), &frag_src.into()).ok();
let js_path = format!("window.{}.{}", path.join("."), debug_var_name);
Err(ContextLossOrError::Error(Error::Compile { js_path, vertex, fragment }))
Self::Vertex => GlEnum(WebGl2RenderingContext::VERTEX_SHADER),
Self::Fragment => GlEnum(WebGl2RenderingContext::FRAGMENT_SHADER),
}
}
}
// ========================
// === Managing buffers ===
// ========================
// ===============
// === Sources ===
// ===============
// TODO: The functions below might be obsolete after text is fully integrated to buffer management.
/// Set the array buffer data with floats.
pub fn set_buffer_data(gl_context: &Context, buffer: &WebGlBuffer, data: &[f32]) {
let target = Context::ARRAY_BUFFER;
gl_context.bind_buffer(target, Some(buffer));
set_bound_buffer_data(gl_context, target, data);
/// Abstractions for shader sources (vertex and fragment one).
#[allow(missing_docs)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Sources<Vertex, Fragment> {
pub vertex: Vertex,
pub fragment: Fragment,
}
/// Set data in currently bound buffer.
///
/// # Safety
/// The Float32Array::view is safe as long there are no allocations done
/// until it is destroyed. This way of creating buffers were taken from
/// wasm-bindgen examples
/// (https://rustwasm.github.io/wasm-bindgen/examples/webgl.html).
#[allow(unsafe_code)]
fn set_bound_buffer_data(gl_context: &Context, target: u32, data: &[f32]) {
let usage = Context::STATIC_DRAW;
unsafe {
let float_array = Float32Array::view(data);
gl_context.buffer_data_with_array_buffer_view(target, &float_array, usage);
/// Shader sources as a code.
pub type Code = Sources<String, String>;
/// Shader sources as compiled shaders that are not linked yet.
pub type CompiledCode = Sources<Shader<Vertex>, Shader<Fragment>>;
// ==============
// === Shader ===
// ==============
/// A compiled shader of a given type.
#[allow(missing_docs)]
#[derive(AsRef, Clone, Debug, Deref, Eq, PartialEq)]
pub struct Shader<T> {
#[as_ref]
#[deref]
pub native: WebGlShader,
pub code: String,
tp: PhantomData<T>,
}
impl<T> Shader<T> {
/// Constructor.
pub fn new(code: String, native: WebGlShader) -> Self {
let tp = default();
Self { native, code, tp }
}
}
/// Set the array buffer fragment with with floats.
pub fn set_buffer_subdata(gl_context: &Context, buffer: &WebGlBuffer, offset: usize, data: &[f32]) {
let target = Context::ARRAY_BUFFER;
gl_context.bind_buffer(target, Some(buffer));
set_bound_buffer_subdata(gl_context, target, offset as i32, data);
// ===============
// === Program ===
// ===============
/// A compiled shader program.
#[allow(missing_docs)]
#[derive(AsRef, Clone, Debug, Deref, Eq, PartialEq)]
pub struct Program {
#[as_ref]
#[deref]
pub native: WebGlProgram,
pub shader: CompiledCode,
}
/// Set subdata in currently bound buffer.
///
/// # Safety
/// The Float32Array::view is safe as long there are no allocations done
/// until it is destroyed. This way of creating buffers were taken from
/// wasm-bindgen examples
/// (https://rustwasm.github.io/wasm-bindgen/examples/webgl.html).
#[allow(unsafe_code)]
fn set_bound_buffer_subdata(gl_context: &Context, target: u32, offset: i32, data: &[f32]) {
unsafe {
let float_array = Float32Array::view(data);
gl_context.buffer_sub_data_with_i32_and_array_buffer_view(target, offset, &float_array);
impl Program {
/// Constructor.
pub fn new(shader: CompiledCode, native: WebGlProgram) -> Self {
Self { native, shader }
}
}

View File

@ -0,0 +1,465 @@
//! Asynchronous interface to WebGL GLSL shader program compilation.
//!
//! # Performance
//!
//! In order to maximize parallelism and avoid blocking, follows
//! [best practices](https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/WebGL_best_practices).
//!
//! In particular, these items from that document are implemented here:
//! - "Compile Shaders and Link Programs in parallel"
//! - "Prefer KHR_parallel_shader_compile"
//! - "Don't check shader compile status unless linking fails"
//!
//! # Context loss
//!
//! The compiler handles [context loss](https://www.khronos.org/webgl/wiki/HandlingContextLost) and
//! does not report compilation errors when the context is not available.
use crate::prelude::*;
use crate::system::gpu::context::native::traits::*;
use crate::system::web::traits::*;
use crate::animation;
use crate::display::ToGlEnum;
use crate::system::gpu::context::extension::KhrParallelShaderCompile;
use crate::system::gpu::context::native;
use crate::system::gpu::shader;
use crate::system::gpu::shader::Fragment;
use crate::system::gpu::shader::Shader;
use crate::system::gpu::shader::Vertex;
use crate::system::web;
use crate::types::unit2::Duration;
use web_sys::WebGl2RenderingContext;
// =================
// === Constants ===
// =================
/// We do not want the framerate to drop below this value when compiling shaders. Whenever we
/// discover that it drops below this threshold, no more compiler jobs (compilation or linking) will
/// be executed this frame. It does not mean, however, that the framerate will not be lower than
/// this threshold. Every frame at least one job is scheduled, and the last scheduled job can take
/// significant amount of time, causing the FPS to drop below this threshold. To learn more about
/// how jobs are scheduled, read the docs of the [`Compiler`].
const FPS_THRESHOLD: f32 = 60.0;
const FRAME_TIME_THRESHOLD: Duration = (1000.0 / FPS_THRESHOLD).ms();
/// Maximum number of parallel shader compilation jobs. Chromium (and Electron) seem to have limits
/// on the number of compile/link jobs that can be in flight. If the limits are exceeded, it applies
/// a crude form of backpressure by blocking until all pending jobs complete. We are not sure if it
/// is a feature or a bug. To learn more about our findings so far, see:
/// https://github.com/enso-org/enso/pull/3378#issuecomment-1090958946
const MAX_PARALLEL_COMPILE_JOBS: usize = 2;
// ===========
// === Job ===
// ===========
/// Compiler job. After the job is created it can be either transformed to another job, or, in case
/// that was the final job, the [`handler`] callback will be called. See the documentation of the
/// [`Compiler`] to learn more.
#[derive(Derivative, Deref)]
#[derivative(Debug)]
pub struct Job<T> {
#[derivative(Debug = "ignore")]
on_ready: Box<dyn FnOnce(shader::Program)>,
#[deref]
input: T,
handler: WeakJobHandler,
}
impl<T> Job<T> {
fn with_mod_input<S>(self, f: impl FnOnce(T) -> S) -> Job<S> {
let on_ready = self.on_ready;
let handler = self.handler;
let input = f(self.input);
Job { on_ready, input, handler }
}
}
/// A handler to a job. After the handler is dropped, the job is invalidated and will no longer be
/// scheduled for evaluation.
#[derive(Debug, Clone, CloneRef)]
pub struct JobHandler {
rc: Rc<()>,
}
/// A weak version of [`JobHandler`].
#[derive(Debug, Clone, CloneRef)]
pub struct WeakJobHandler {
weak: Weak<()>,
}
impl JobHandler {
/// Constructor.
fn new() -> Self {
Self { rc: default() }
}
/// Get weak reference to this handler.
pub fn downgrade(&self) -> WeakJobHandler {
let weak = Rc::downgrade(&self.rc);
WeakJobHandler { weak }
}
}
impl WeakJobHandler {
/// Check whether the handler was dropped.
pub fn exists(&self) -> bool {
self.weak.upgrade().is_some()
}
}
// ==================
// === KhrProgram ===
// ==================
/// A program together with the [`KhrParallelShaderCompile`] extension. Used as one of the
/// [`Compiler`] jobs to provide nice API.
#[derive(Debug)]
struct KhrProgram {
khr: KhrParallelShaderCompile,
program: shader::Program,
}
// ================
// === Compiler ===
// ================
/// Compiler job queues. See the documentation of [`Compiler`] to learn more.
#[derive(Debug, Default)]
pub struct Jobs {
compile: Vec<Job<shader::Code>>,
link: Vec<Job<shader::CompiledCode>>,
khr_completion_check: Vec<Job<KhrProgram>>,
link_check: Vec<Job<shader::Program>>,
}
/// Compiles and links GL shader programs, asynchronously. The compiler works in the following way:
/// 1. A new shader code is submitted with the [`submit`] method. A new job is created in the
/// [`Jobs::compile`] queue and the job handler is returned to the user.
///
/// 2. The following pseudo-algorithm is performed:
/// ```text
/// on_ever_frame if job queues are not empty {
/// Promote ready jobs from [`Jobs::khr_completion_check`] to [`Jobs::link_check`].
/// Loop while the current frame time is < FRAME_TIME_THRESHOLD, at least one loop this frame {
/// if [`Jobs::compile`] is not empty {
/// submit its first job to the GLSL compiler
/// move the job to the [`Jobs::link`] queue
/// } else if [`Jobs::link`] is not empty {
/// submit its first job to the GLSL linker
/// if parallel compilation is available move the job to [`Jobs::khr_completion_check`],
/// or to [`Jobs::link_check`] otherwise.
/// } else if [`Jobs::link_check`] is not empty {
/// check its first job linking status, report warnings, and call the job callback.
/// }
/// }
/// }
/// ```
#[derive(Clone, CloneRef, Debug)]
pub struct Compiler {
rc: Rc<RefCell<CompilerData>>,
}
#[derive(Debug)]
struct CompilerData {
dirty: bool,
context: native::ContextWithExtensions,
jobs: Jobs,
performance: web::Performance,
logger: Logger,
}
impl Compiler {
/// Constructor.
pub fn new(context: &native::ContextWithExtensions) -> Self {
Self { rc: Rc::new(RefCell::new(CompilerData::new(context))) }
}
/// Submit shader for compilation.
pub fn submit<F: 'static + Fn(shader::Program)>(
&self,
input: shader::Code,
on_ready: F,
) -> JobHandler {
self.rc.borrow_mut().submit(input, on_ready)
}
/// Run the compiler. This should be run on every frame.
pub fn run(&self, time: animation::TimeInfo) {
self.rc.borrow_mut().run(time)
}
}
impl CompilerData {
fn new(context: &native::ContextWithExtensions) -> Self {
let dirty = false;
let context = context.clone();
let jobs = default();
let performance = web::window.performance_or_panic();
let logger = Logger::new("Shader Compiler");
Self { dirty, context, jobs, performance, logger }
}
fn submit<F: 'static + FnOnce(shader::Program)>(
&mut self,
input: shader::Code,
on_ready: F,
) -> JobHandler {
self.dirty = true;
let strong_handler = JobHandler::new();
let handler = strong_handler.downgrade();
let on_ready = Box::new(on_ready);
let job = Job { input, handler, on_ready };
self.jobs.compile.push(job);
strong_handler
}
#[profile(Debug)]
fn run(&mut self, time: animation::TimeInfo) {
if self.dirty {
self.run_khr_completion_check_jobs();
while self.dirty {
match self.run_step() {
Ok(made_progress) => {
if !made_progress {
break;
}
let now = (self.performance.now() as f32).ms();
let current_frame_time =
now - time.animation_loop_start - time.since_animation_loop_started;
if current_frame_time > FRAME_TIME_THRESHOLD {
let msg1 =
"Shaders compilation takes more than the available frame time.";
let msg2 = "To be continued in the next frame.";
debug!(self.logger, "{msg1} {msg2}");
break;
}
}
Err(err) => {
if self.context.is_context_lost() {
break;
}
let err_msg = err.blocking_report(&self.context);
error!(self.logger, "{err_msg}");
}
}
}
}
}
/// Runs the next compiler job if there is any left and if it will not cause too many jobs being
/// run in parallel. The result [`bool`] indicates if the call to this function did any
/// progress.
#[profile(Detail)]
fn run_step(&mut self) -> Result<bool, Error> {
let ok_progress = |_| Ok(true);
let no_progress = Ok(false);
let jobs = &self.jobs;
let max_jobs = self.current_parallel_job_count() >= MAX_PARALLEL_COMPILE_JOBS;
match () {
_ if !max_jobs && !jobs.compile.is_empty() => ok_progress(self.run_next_compile_job()?),
_ if !jobs.link.is_empty() => ok_progress(self.run_next_link_job()?),
_ if !jobs.link_check.is_empty() => ok_progress(self.run_next_link_check_job()?),
_ => {
if max_jobs {
if !jobs.compile.is_empty() {
let msg1 = "Maximum number of parallel shader compiler jobs.";
let msg2 = "Skipping spawning new ones.";
debug!(self.logger, "{msg1} {msg2}");
}
} else if jobs.khr_completion_check.is_empty() {
debug!(self.logger, "All shaders compiled.");
self.dirty = false;
}
no_progress
}
}
}
/// Get the number of shader compilation jobs run in parallel. Please note, that it is
/// impossible to get separate values for compilation and linking jobs here, because checking
/// it is costly and can prevent the parallelism altogether. To learn more, see:
/// https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/WebGL_best_practices#dont_check_shader_compile_status_unless_linking_fails
fn current_parallel_job_count(&self) -> usize {
self.jobs.link.len() + self.jobs.khr_completion_check.len() + self.jobs.link_check.len()
}
#[profile(Detail)]
fn run_khr_completion_check_jobs(&mut self) {
debug!(self.logger, "Running KHR parallel shader compilation check job.");
let jobs = &mut self.jobs.khr_completion_check;
let ready_jobs =
jobs.drain_filter(|job| match job.khr.is_ready(&*self.context, &*job.program) {
Some(val) => val,
None => {
if !self.context.is_context_lost() {
REPORTABLE_WARNING!(
"context.getProgramParameter returned non bool value for KHR Parallel \
Shader Compile status check. This should never happen, however, it \
should not cause visual artifacts. Reverting to non-parallel mode."
);
}
true
}
});
self.jobs.link_check.extend(ready_jobs.map(|job| job.with_mod_input(|t| t.program)));
}
#[allow(unused_parens)]
fn run_next_compile_job(&mut self) -> Result<(), Error> {
self.with_next_job("shader compilation", (|t| &mut t.jobs.compile), |this, job| {
let vertex = this.compile_shader(Vertex, job.input.vertex)?;
let fragment = this.compile_shader(Fragment, job.input.fragment)?;
let input = shader::Sources { vertex, fragment };
let handler = job.handler;
let on_ready = job.on_ready;
let link_job = Job { input, handler, on_ready };
this.jobs.link.push(link_job);
Ok(())
})
}
#[allow(unused_parens)]
fn run_next_link_job(&mut self) -> Result<(), Error> {
self.with_next_job("shader linking", (|t| &mut t.jobs.link), |this, job| {
let shader = job.input;
let program = this.context.create_program().ok_or(Error::ProgramCreationError)?;
this.context.attach_shader(&program, &*shader.vertex);
this.context.attach_shader(&program, &*shader.fragment);
this.context.link_program(&program);
let input = shader::Program::new(shader, program);
let handler = job.handler;
let on_ready = job.on_ready;
match this.context.extensions.khr_parallel_shader_compile {
Some(khr) => {
let input = KhrProgram { khr, program: input };
this.jobs.khr_completion_check.push(Job { input, handler, on_ready })
}
None => this.jobs.link_check.push(Job { input, handler, on_ready }),
}
Ok(())
})
}
#[allow(unused_parens)]
fn run_next_link_check_job(&mut self) -> Result<(), Error> {
self.with_next_job("shader validation", (|t| &mut t.jobs.link_check), |this, job| {
let program = job.input;
let param = WebGl2RenderingContext::LINK_STATUS;
let status = this.context.get_program_parameter(&*program, param);
if !status.as_bool().unwrap_or(false) {
return Err(Error::ProgramLinkingError(program.shader));
} else {
(job.on_ready)(program);
}
Ok(())
})
}
fn with_next_job<T>(
&mut self,
label: &str,
jobs: impl FnOnce(&mut Self) -> &mut Vec<Job<T>>,
f: impl FnOnce(&mut Self, Job<T>) -> Result<(), Error>,
) -> Result<(), Error> {
match jobs(self).pop() {
None => Ok(()),
Some(job) => {
debug!(self.logger, "Running {label} job.");
if job.handler.exists() {
f(self, job)
} else {
debug!(self.logger, "Job handler dropped, skipping.");
Ok(())
}
}
}
}
fn compile_shader<T: Into<shader::Type>>(
&self,
shader_type: T,
code: String,
) -> Result<Shader<T>, Error> {
let tp = shader_type.into().to_gl_enum();
let shader = self.context.create_shader(*tp).ok_or(Error::ShaderCreationError)?;
self.context.shader_source(&shader, &code);
self.context.compile_shader(&shader);
Ok(Shader::new(code, shader))
}
}
// =============
// === Error ===
// =============
/// Compilation error types.
#[derive(Debug)]
#[allow(missing_docs)]
pub enum Error {
ShaderCreationError,
ProgramCreationError,
ProgramLinkingError(shader::CompiledCode),
}
impl Error {
/// Report the error. This function uses GPU blocking API and thus will cause a significant
/// performance hit. Use it only when necessary.
pub fn blocking_report(self, context: &WebGl2RenderingContext) -> String {
match self {
Self::ShaderCreationError => "WebGL was unable to create a new shader.".into(),
Self::ProgramCreationError => "WebGl was unable to create a new program shader.".into(),
Self::ProgramLinkingError(shader) => {
let unwrap_error = |name: &str, err: Option<String>| {
let header = format!("----- {} Shader -----", name);
err.map(|t| format!("\n\n{}\n\n{}", header, t)).unwrap_or_else(|| "".into())
};
let vertex = &shader.vertex;
let fragment = &shader.fragment;
let vertex_log = context.blocking_format_error_log(vertex);
let fragment_log = context.blocking_format_error_log(fragment);
let vertex_error = unwrap_error("Vertex", vertex_log);
let fragment_error = unwrap_error("Fragment", fragment_log);
// FIXME: this should be taken from config and refactored to a function
let dbg_path = &["enso", "debug", "shader"];
let dbg_object = web::Reflect::get_nested_object_or_create(&web::window, dbg_path);
let dbg_object = dbg_object.unwrap();
let dbg_shaders_count = web::Object::keys(&dbg_object).length();
let dbg_var_name = format!("shader_{}", dbg_shaders_count);
let dbg_shader_object = web::Object::new();
let vertex_code = vertex.code.clone().into();
let fragment_code = fragment.code.clone().into();
web::Reflect::set(&dbg_object, &(&dbg_var_name).into(), &dbg_shader_object).ok();
web::Reflect::set(&dbg_shader_object, &"vertex".into(), &vertex_code).ok();
web::Reflect::set(&dbg_shader_object, &"fragment".into(), &fragment_code).ok();
let dbg_js_path = format!("window.{}.{}", dbg_path.join("."), dbg_var_name);
let run_msg = |n| {
format!("Run `console.log({}.{})` to inspect the {} shader.", dbg_js_path, n, n)
};
let dbg_msg = format!("{}\n{}", run_msg("vertex"), run_msg("fragment"));
format!(
"Unable to compile shader.\n{}\n{}{}",
dbg_msg, vertex_error, fragment_error
)
}
}
}
}

View File

@ -203,7 +203,7 @@ impl Sampler {
let animation_cb = Box::new(move |t| prop.set(t)) as Box<dyn Fn(SpriteData)>;
let easing_animator = Animator::new(start, end, easing_function2, animation_cb, ());
let time = 0.0;
easing_animator.set_duration(2000.0);
easing_animator.set_duration(2.0.s());
Self {
color,
time,
@ -266,9 +266,9 @@ impl Example {
let _animator = animation::Loop::new(Box::new(move |time_info: animation::TimeInfo| {
left_canvas.clear();
right_canvas.clear();
sampler1.render(time_info.frame);
sampler2.render(time_info.frame);
sampler3.render(time_info.frame);
sampler1.render(time_info.previous_frame.unchecked_raw());
sampler2.render(time_info.previous_frame.unchecked_raw());
sampler3.render(time_info.previous_frame.unchecked_raw());
}) as Box<dyn FnMut(animation::TimeInfo)>);
Self { _animator }
}

View File

@ -79,9 +79,10 @@ pub fn main() {
let _keep_alive = &navigator;
i += 1;
if i == 5 {
let shader = sprite.symbol.shader().shader().unwrap();
DEBUG!("\n\nVERTEX:\n{shader.vertex}");
DEBUG!("\n\nFRAGMENT:\n{shader.fragment}");
if let Some(program) = sprite.symbol.shader().program() {
DEBUG!("\n\nVERTEX:\n{program.shader.vertex.code}");
DEBUG!("\n\nFRAGMENT:\n{program.shader.fragment.code}");
}
}
})
.forget();

View File

@ -124,7 +124,7 @@ pub fn on_frame(
let half_height = screen.height / 2.0;
if !frozen {
let t = time.local / 1000.0;
let t = time.since_animation_loop_started.unchecked_raw() / 1000.0;
let length = sprites.len() as f32;
for (i, sprite) in sprites.iter_mut().enumerate() {
let i = i as f32;

View File

@ -56,7 +56,7 @@ pub use internal::backtrace;
// === TraceCopies ===
// ===================
/// An utility for tracing all copies of CloneRef-able entity.
/// A utility for tracing all copies of CloneRef-able entity.
///
/// This structure should be added as a field to structure implementing Clone or CloneRef. It will
/// mark each copy with unique id (the original copy has id of 0). Once enabled, it will print

View File

@ -99,3 +99,9 @@ mod tests {
ERROR!("test");
}
}
/// Instruction of how to report important errors.
pub const REPORT_INSTRUCTION: &str = "We will be thankful for reporting this error here: \
https://github.com/enso-org/enso/issues. Please, provide us with as much information as possible, \
including your system specification, browser version, and a detailed description of the steps you \
made before this error happened.";

View File

@ -57,3 +57,23 @@ macro_rules! ERROR {
$crate::debug::logging::error($crate::iformat!($($arg)*))
}
}
/// A version of [`WARNING`] that informs the user how to report the error.
#[macro_export]
macro_rules! REPORTABLE_WARNING {
($($arg:tt)*) => {
let user_message = $crate::iformat!($($arg)*);
let message = format!("{} {}",user_message, $crate::debug::logging::REPORT_INSTRUCTION);
$crate::debug::logging::warn(message)
}
}
/// A version of [`ERROR`] that informs the user how to report the error.
#[macro_export]
macro_rules! REPORTABLE_ERROR {
($($arg:tt)*) => {
let user_message = $crate::iformat!($($arg)*);
let message = format!("{} {}",user_message, $crate::debug::logging::REPORT_INSTRUCTION);
$crate::debug::logging::error(message)
}
}

View File

@ -79,6 +79,7 @@ pub use lazy_static::lazy_static;
pub use num::Num;
pub use paste::paste;
pub use shrinkwraprs::Shrinkwrap;
pub use weak_table;
pub use weak_table::traits::WeakElement;
pub use weak_table::traits::WeakKey;
@ -91,6 +92,7 @@ pub use std::hash::Hasher;
use std::cell::UnsafeCell;
/// Serde reexports for the code generated by declarative macros.
///
/// They cannot be directly reexported from prelude, as the methods `serialize` and `deserialize`

View File

@ -135,10 +135,41 @@ macro_rules! define_singleton_enum {
(
$(#$meta:tt)*
$name:ident {
$( $(#$field_meta:tt)* $field:ident ),* $(,)?
$(
$(#$variant_meta:tt)*
$variant:ident $(($($variant_field:tt)*))?
),* $(,)?
}
) => {
$crate::define_singletons! { $($(#$field_meta)* $field),* }
$crate::define_singleton_enum_from! { $(#$meta)* $name {$($(#$field_meta)* $field),*}}
$(
$crate::define_singleton_enum_struct! {
$(#$variant_meta)*
$variant ($($($variant_field)*)?)
}
)*
$crate::define_singleton_enum_from! { $(#$meta)* $name {$($(#$variant_meta)* $variant),*}}
}
}
#[macro_export]
macro_rules! define_singleton_enum_struct {
( $(#$meta:tt)* $name:ident () ) => {
#[allow(missing_docs)]
#[derive(Copy,Clone,Debug,PartialEq,Eq)]
$(#$meta)*
pub struct $name;
impl Default for $name {
fn default() -> Self {
Self
}
}
};
( $(#$meta:tt)* $name:ident ($($args:tt)*) ) => {
#[allow(missing_docs)]
#[derive(Clone,Debug,PartialEq,Eq)]
$(#$meta)*
pub struct $name($($args)*);
};
}

View File

@ -2,6 +2,14 @@
// === Features ===
#![feature(trait_alias)]
#![feature(const_trait_impl)]
#![feature(auto_traits)]
#![feature(negative_impls)]
#![feature(const_fn_trait_bound)]
#![feature(const_ops)]
#![feature(const_convert)]
#![feature(const_mut_refs)]
#![feature(const_fn_floating_point_arithmetic)]
// === Standard Linter Configuration ===
#![deny(non_ascii_idents)]
#![warn(unsafe_code)]
@ -23,6 +31,7 @@ pub mod algebra;
pub mod num;
pub mod topology;
pub mod unit;
pub mod unit2;
pub use algebra::*;
pub use topology::*;

View File

@ -1,3 +1,6 @@
//! # DEPRECATED!!!
//! THIS MODULE IS DEPRECATED. USE `unit2` INSTEAD.
//!
//! Defines utilities for creating custom strongly typed units. For example, unit `Angle` could be a
//! wrapper for `f32`.
//!

631
lib/rust/types/src/unit2.rs Normal file
View File

@ -0,0 +1,631 @@
//! Utilities for creating custom strongly typed units. For example, unit `Duration` is a wrapper
//! for [`f32`] and defines time-related utilities.
//!
//! Units automatically implement a lot of traits in a generic fashion, so you can for example add
//! [`Duration`] together, or divide one [`Duration`] by a number or another [`Duration`] and get
//! [`Duration`] or a number, respectfully. You are allowed to define any combination of operators
//! and rules of how the result inference should be performed.
use std::marker::PhantomData;
/// Common traits for built-in units.
pub mod traits {
pub use super::DurationNumberOps;
pub use super::DurationOps;
}
mod ops {
pub use crate::algebra::*;
pub use std::ops::*;
}
// =====================
// === UncheckedInto ===
// =====================
/// Unchecked unit conversion. You should use it only for unit conversion definition, never in
/// unit-usage code.
#[allow(missing_docs)]
pub trait UncheckedInto<T> {
fn unchecked_into(self) -> T;
}
impl<T> const UncheckedInto<T> for T {
fn unchecked_into(self) -> T {
self
}
}
impl<V, R> const UncheckedInto<UnitData<V, R>> for R {
fn unchecked_into(self) -> UnitData<V, R> {
let repr = self;
let variant = PhantomData;
UnitData { repr, variant }
}
}
// ================
// === UnitData ===
// ================
/// Abstract unit type for the given variant. Variants are marker structs used to distinguish units.
/// For example, the [`Duration`] unit is defined as (after macro expansion):
/// ```text
/// pub type Duration = Unit<DURATION>
/// pub struct DURATION;
/// impl Variant for DURATION {
/// type Repr = f64
/// }
/// ```
pub type Unit<V> = UnitData<V, <V as Variant>::Repr>;
/// Relation between the unit variant and its internal representation. Read the docs of [`UnitData`]
/// to learn more about variants.
#[allow(missing_docs)]
pub trait Variant {
type Repr;
}
/// Internal representation of every unit.
pub struct UnitData<V, R> {
repr: R,
variant: PhantomData<V>,
}
impl<V, R: Copy> UnitData<V, R> {
/// Get the underlying value. Please note that this might result in a different value than you
/// might expect. For example, if the internal representation of a duration type is a number of
/// milliseconds, then `1.second().unchecked_raw()` will return `1000`.
pub const fn unchecked_raw(self) -> R {
self.repr
}
}
impl<V, R: Copy> Copy for UnitData<V, R> {}
impl<V, R: Copy> Clone for UnitData<V, R> {
fn clone(&self) -> Self {
*self
}
}
// =================
// === IsNotUnit ===
// =================
/// Trait used to resolve conflicts when implementing traits fot [`Unit`].
pub auto trait IsNotUnit {}
impl<V, R> !IsNotUnit for UnitData<V, R> {}
// ===============
// === Default ===
// ===============
impl<V, R: Default> Default for UnitData<V, R> {
fn default() -> Self {
R::default().unchecked_into()
}
}
// =======================
// === Debug / Display ===
// =======================
impl<V, R: std::fmt::Debug> std::fmt::Debug for UnitData<V, R> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Unit({:?})", self.repr)
}
}
impl<V, R: std::fmt::Display> std::fmt::Display for UnitData<V, R> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.repr.fmt(f)
}
}
// =====================
// === Deref / AsRef ===
// =====================
impl<V, R> AsRef<UnitData<V, R>> for UnitData<V, R> {
fn as_ref(&self) -> &UnitData<V, R> {
self
}
}
// ==========
// === Eq ===
// ==========
impl<V, R: PartialEq> Eq for UnitData<V, R> {}
impl<V, R: PartialEq> PartialEq for UnitData<V, R> {
fn eq(&self, other: &Self) -> bool {
self.repr.eq(&other.repr)
}
}
// ===========
// === Ord ===
// ===========
impl<V, R: Ord> Ord for UnitData<V, R> {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.repr.cmp(&other.repr)
}
}
impl<V, R: PartialOrd> PartialOrd for UnitData<V, R> {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
self.repr.partial_cmp(&other.repr)
}
}
// ============
// === From ===
// ============
impl<V, R> From<&UnitData<V, R>> for UnitData<V, R>
where R: Copy
{
fn from(t: &UnitData<V, R>) -> Self {
*t
}
}
// ===============
// === gen_ops ===
// ===============
/// Internal macro for defining operators for the generic [`Unit`] structure. Because Rust disallows
/// defining trait impls for structs defined in external modules and in order to provide a
/// configurable default impl, this macro generates local traits controlling the behavior of the
/// generated impls. For example, the following code:
///
/// ```text
/// gen_ops!(RevAdd, Add, add);
/// ```
///
/// will result in:
///
/// ```text
/// pub trait Add<T> {
/// type Output;
/// }
///
/// pub trait RevAdd<T> {
/// type Output;
/// }
///
/// impl<V, R> const ops::Add<UnitData<V, R>> for f32
/// where V: RevAdd<f32>, ... {
/// type Output = UnitData<<V as RevAdd<f32>>::Output, <f32 as ops::Add<R>>::Output>;
/// fn add(self, rhs: UnitData<V, R>) -> Self::Output { ... }
/// }
///
/// impl<V, R, T> const ops::Add<T> for UnitData<V, R>
/// where UnitData<V, R>: Add<T>, ... {
/// type Output = <UnitData<V, R> as Add<T>>::Output;
/// fn add(self, rhs: T) -> Self::Output { ... }
/// }
///
/// impl<V1, V2, R1, R2> const ops::Add<UnitData<V2, R2>> for UnitData<V1, R1>
/// where UnitData<V1, R1>: Add<UnitData<V2, R2>>, ... {
/// type Output = <UnitData<V1, R1> as Add<UnitData<V2, R2>>>::Output;
/// fn add(self, rhs: UnitData<V2, R2>) -> Self::Output { ... }
/// }
/// ```
///
/// Please note, that all traits in the [`ops`] module are standard Rust traits, while the ones
/// with no prefix, are custom ones. Having such an implementation and an example unit type
/// definition:
///
/// ```text
/// pub type Duration = Unit<DURATION>;
/// pub struct DURATION;
/// impl Variant for DURATION {
/// type Repr = f32;
/// }
/// ```
///
/// We can allow for adding and dividing two units simply by implementing the following traits:
///
/// ```text
/// impl Add<Duration> for Duration {
/// type Output = Duration;
/// }
///
/// impl Div<Duration> for Duration {
/// type Output = f32;
/// }
/// ```
///
/// This crate provides a nice utility for such trait impl generation. See [`define_ops`] to learn
/// more.
macro_rules! gen_ops {
($rev_trait:ident, $trait:ident, $op:ident) => {
#[allow(missing_docs)]
pub trait $trait<T> {
type Output;
}
#[allow(missing_docs)]
pub trait $rev_trait<T> {
type Output;
}
// Please note that this impl is not as generic as the following ones because Rust compiler
// is unable to compile the more generic version.
impl<V, R> const ops::$trait<UnitData<V, R>> for f32
where
R: Copy,
V: $rev_trait<f32>,
f32: ~const ops::$trait<R>,
{
type Output = UnitData<<V as $rev_trait<f32>>::Output, <f32 as ops::$trait<R>>::Output>;
fn $op(self, rhs: UnitData<V, R>) -> Self::Output {
self.$op(rhs.repr).unchecked_into()
}
}
impl<V, R, T> const ops::$trait<T> for UnitData<V, R>
where
UnitData<V, R>: $trait<T>,
R: ~const ops::$trait<T> + Copy,
T: IsNotUnit,
<R as ops::$trait<T>>::Output:
~const UncheckedInto<<UnitData<V, R> as $trait<T>>::Output>,
{
type Output = <UnitData<V, R> as $trait<T>>::Output;
fn $op(self, rhs: T) -> Self::Output {
self.repr.$op(rhs).unchecked_into()
}
}
impl<V1, V2, R1, R2> const ops::$trait<UnitData<V2, R2>> for UnitData<V1, R1>
where
UnitData<V1, R1>: $trait<UnitData<V2, R2>>,
R1: ~const ops::$trait<R2> + Copy,
R2: Copy,
<R1 as ops::$trait<R2>>::Output:
~const UncheckedInto<<UnitData<V1, R1> as $trait<UnitData<V2, R2>>>::Output>,
{
type Output = <UnitData<V1, R1> as $trait<UnitData<V2, R2>>>::Output;
fn $op(self, rhs: UnitData<V2, R2>) -> Self::Output {
self.repr.$op(rhs.repr).unchecked_into()
}
}
};
}
/// Internal helper for the [`gen_ops`] macro.
macro_rules! gen_ops_mut {
($rev_trait:ident, $trait:ident, $trait_mut:ident, $op:ident) => {
impl<V, R> const ops::$trait_mut<UnitData<V, R>> for f32
where
f32: ~const ops::$trait_mut<R>,
R: Copy,
UnitData<V, R>: $rev_trait<f32>,
{
fn $op(&mut self, rhs: UnitData<V, R>) {
self.$op(rhs.repr)
}
}
impl<V, R, T> const ops::$trait_mut<T> for UnitData<V, R>
where
T: IsNotUnit,
R: ~const ops::$trait_mut<T>,
UnitData<V, R>: $trait<T>,
{
fn $op(&mut self, rhs: T) {
self.repr.$op(rhs)
}
}
impl<V1, V2, R1, R2> const ops::$trait_mut<UnitData<V2, R2>> for UnitData<V1, R1>
where
R1: ~const ops::$trait_mut<R2>,
R2: Copy,
UnitData<V1, R1>: $trait<UnitData<V2, R2>>,
{
fn $op(&mut self, rhs: UnitData<V2, R2>) {
self.repr.$op(rhs.repr)
}
}
};
}
gen_ops!(RevAdd, Add, add);
gen_ops!(RevSub, Sub, sub);
gen_ops!(RevMul, Mul, mul);
gen_ops!(RevDiv, Div, div);
gen_ops!(SaturatingRevAdd, SaturatingAdd, saturating_add);
gen_ops!(SaturatingRevSub, SaturatingSub, saturating_sub);
gen_ops!(SaturatingRevMul, SaturatingMul, saturating_mul);
gen_ops_mut!(RevAdd, Add, AddAssign, add_assign);
gen_ops_mut!(RevSub, Sub, SubAssign, sub_assign);
gen_ops_mut!(RevMul, Mul, MulAssign, mul_assign);
gen_ops_mut!(RevDiv, Div, DivAssign, div_assign);
// ==============================
// === Unit Definition Macros ===
// ==============================
// === define ===
/// Utilities for new unit definition. For example, the following definition:
///
/// ```text
/// define!(Duration = DURATION(f32));
/// ```
///
/// will generate the following code:
///
/// ````text
/// pub type Duration = Unit<DURATION>;
/// pub struct DURATION;
/// impl Variant for DURATION {
/// type Repr = f32;
/// }
/// ```
#[macro_export]
macro_rules! define {
($(#$meta:tt)* $name:ident = $variant:ident ($tp:ident)) => {
$(#$meta)*
pub type $name = Unit<$variant>;
$(#$meta)*
#[derive(Debug, Clone, Copy)]
pub struct $variant;
impl Variant for $variant {
type Repr = $tp;
}
};
}
// === define_ops ===
/// Utilities for new unit-operations relations definition. For example, the following definition:
///
/// ```text
/// define_ops![
/// Duration [+,-] Duration = Duration,
/// Duration [*,/] f32 = Duration,
/// Duration / Duration = f32,
/// f32 * Duration = Duration,
/// ];
/// ```
///
/// will generate the following code:
///
/// ```text
/// impl Add<Duration> for Duration {
/// type Output = Duration;
/// }
/// impl Sub<Duration> for Duration {
/// type Output = Duration;
/// }
/// impl Mul<f32> for Duration {
/// type Output = Duration;
/// }
/// impl Div<f32> for Duration {
/// type Output = Duration;
/// }
/// impl Div<Duration> for Duration {
/// type Output = f32;
/// }
/// impl RevMul<Duration> for f32 {
/// type Output = Duration;
/// }
/// ```
///
/// See the documentation of the [`gen_ops`] macro to learn about such traits as [`Add`], [`Mul`],
/// or [`RevMul`] these are NOT standard Rust traits.
#[macro_export]
macro_rules! define_ops {
($($lhs:ident $op:tt $rhs:ident = $out:ident),* $(,)?) => {
$(
$crate::define_single_op_switch!{ $lhs $op $rhs = $out }
)*
};
}
/// Internal helper for the [`define_ops`] macro.
#[macro_export]
macro_rules! define_single_op_switch {
(f32 $op:tt $rhs:ident = $out:ident) => {
$crate::define_single_rev_op! {f32 $op $rhs = $out}
};
(f64 $op:tt $rhs:ident = $out:ident) => {
$crate::define_single_rev_op! {f64 $op $rhs = $out}
};
($lhs:ident $op:tt $rhs:ident = $out:ident) => {
$crate::define_single_op! {$lhs $op $rhs = $out}
};
}
/// Internal helper for the [`define_ops`] macro.
#[macro_export]
macro_rules! define_single_op {
($lhs:ident + $rhs:ident = $out:ident) => {
impl Add<$rhs> for $lhs {
type Output = $out;
}
};
($lhs:ident - $rhs:ident = $out:ident) => {
impl Sub<$rhs> for $lhs {
type Output = $out;
}
};
($lhs:ident * $rhs:ident = $out:ident) => {
impl Mul<$rhs> for $lhs {
type Output = $out;
}
};
($lhs:ident / $rhs:ident = $out:ident) => {
impl Div<$rhs> for $lhs {
type Output = $out;
}
};
($lhs:ident [$($op:tt),* $(,)?] $rhs:ident = $out:ident) => {
$(
$crate::define_single_op!{$lhs $op $rhs = $out}
)*
};
}
/// Internal helper for the [`define_ops`] macro.
#[macro_export]
macro_rules! define_single_rev_op {
($lhs:ident + $rhs:ident = $out:ident) => {
impl RevAdd<$rhs> for $lhs {
type Output = $out;
}
};
($lhs:ident - $rhs:ident = $out:ident) => {
impl RevSub<$rhs> for $lhs {
type Output = $out;
}
};
($lhs:ident * $rhs:ident = $out:ident) => {
impl RevMul<$rhs> for $lhs {
type Output = $out;
}
};
($lhs:ident / $rhs:ident = $out:ident) => {
impl RevDiv<$rhs> for $lhs {
type Output = $out;
}
};
($lhs:ident [$($op:tt),* $(,)?] $rhs:ident = $out:ident) => {
$(
$crate::define_single_rev_op!{$lhs $op $rhs = $out}
)*
};
}
// ================
// === Duration ===
// ================
define! {
/// A span of time. Duration stores milliseconds under the hood, which is a representation
/// optimized for real-time rendering and graphics processing.
///
/// Conversions between this type and the [`std::time::Duration`] are provided, however, please
/// note that [`std::time::Duration`] internal representation is optimized for different cases,
/// so losing precision is expected during the conversion.
Duration = DURATION(f32)
}
define_ops![
Duration [+,-] Duration = Duration,
Duration [*,/] f32 = Duration,
Duration / Duration = f32,
f32 * Duration = Duration,
];
/// Methods for the [`Duration`] unit.
#[allow(missing_docs)]
pub trait DurationOps {
fn ms(t: f32) -> Duration;
fn s(t: f32) -> Duration;
fn min(t: f32) -> Duration;
fn h(t: f32) -> Duration;
fn as_ms(&self) -> f32;
fn as_s(&self) -> f32;
fn as_min(&self) -> f32;
fn as_h(&self) -> f32;
}
impl const DurationOps for Duration {
fn ms(t: f32) -> Duration {
t.unchecked_into()
}
fn s(t: f32) -> Duration {
Self::ms(t * 1000.0)
}
fn min(t: f32) -> Duration {
Self::s(t * 60.0)
}
fn h(t: f32) -> Duration {
Self::min(t * 60.0)
}
fn as_ms(&self) -> f32 {
self.unchecked_raw()
}
fn as_s(&self) -> f32 {
self.as_ms() / 1000.0
}
fn as_min(&self) -> f32 {
self.as_s() / 60.0
}
fn as_h(&self) -> f32 {
self.as_min() / 60.0
}
}
/// Methods of the [`Duration`] unit as extensions for numeric types.
#[allow(missing_docs)]
pub trait DurationNumberOps {
fn ms(self) -> Duration;
fn s(self) -> Duration;
}
impl const DurationNumberOps for f32 {
fn ms(self) -> Duration {
Duration::ms(self)
}
fn s(self) -> Duration {
Duration::s(self)
}
}
impl From<std::time::Duration> for Duration {
fn from(duration: std::time::Duration) -> Self {
(duration.as_millis() as <DURATION as Variant>::Repr).ms()
}
}
impl From<Duration> for std::time::Duration {
fn from(duration: Duration) -> Self {
std::time::Duration::from_millis(duration.as_ms() as u64)
}
}

View File

@ -810,8 +810,6 @@ pub fn set_stack_trace_limit() {}
static mut START_TIME: Option<Instant> = None;
static mut TIME_OFFSET: f64 = 0.0;
// FIXME: This is strange design + no one is calling it on init ...
/// Initializes global stats of the program, like its start time. This function should be called
/// exactly once, as the first operation of a program.
///