mirror of
https://github.com/enso-org/enso.git
synced 2024-11-23 08:08:34 +03:00
Multi-frame shader compilation (#3378)
This commit is contained in:
parent
ef7f04297a
commit
6b7622dd45
@ -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);
|
||||
|
@ -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) {
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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,));
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
//! A module containing the garbage [`Collector`] structure.
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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());
|
||||
|
@ -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);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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); )*
|
||||
|
@ -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() {
|
||||
|
@ -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 {
|
||||
|
@ -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 }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
@ -14,7 +14,6 @@ use crate::display::shape::primitive::system::DynamicShapeInternals;
|
||||
use crate::display::symbol;
|
||||
|
||||
|
||||
|
||||
// ==============
|
||||
// === Export ===
|
||||
// ==============
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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 })
|
||||
}
|
||||
}
|
||||
}
|
@ -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::*;
|
||||
|
745
lib/rust/ensogl/core/src/system/gpu/context.rs
Normal file
745
lib/rust/ensogl/core/src/system/gpu/context.rs
Normal 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
|
||||
];
|
78
lib/rust/ensogl/core/src/system/gpu/context/extension.rs
Normal file
78
lib/rust/ensogl/core/src/system/gpu/context/extension.rs
Normal 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()
|
||||
}
|
||||
}
|
139
lib/rust/ensogl/core/src/system/gpu/context/native.rs
Normal file
139
lib/rust/ensogl/core/src/system/gpu/context/native.rs
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
}
|
||||
|
@ -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 }
|
||||
})
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
}
|
||||
|
@ -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]
|
||||
|
@ -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) =>
|
||||
|
@ -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 }
|
||||
}
|
||||
}
|
||||
|
465
lib/rust/ensogl/core/src/system/gpu/shader/compiler.rs
Normal file
465
lib/rust/ensogl/core/src/system/gpu/shader/compiler.rs
Normal 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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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 }
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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.";
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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`
|
||||
|
@ -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)*);
|
||||
};
|
||||
}
|
||||
|
@ -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::*;
|
||||
|
@ -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
631
lib/rust/types/src/unit2.rs
Normal 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)
|
||||
}
|
||||
}
|
@ -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.
|
||||
///
|
||||
|
Loading…
Reference in New Issue
Block a user