egui_web: always use the glow painter, and remove the old WebGL code. (#1356)
* egui_web: always use the glow painter, and remove the old WebGL code. * Clean up the WebPainter trait * Clarify WebGL1 warning text in color test The glow painter became standard in egui 0.17, and I've heard no complaints! So let's simplify and go all in on glow. Part of https://github.com/emilk/egui/issues/1198
This commit is contained in:
parent
52b4ab4e18
commit
50539bd31a
18 changed files with 28 additions and 1514 deletions
|
@ -59,9 +59,7 @@ egui-winit = { version = "0.17.0", path = "../egui-winit", default-features = fa
|
|||
|
||||
# web:
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
egui_web = { version = "0.17.0", path = "../egui_web", default-features = false, features = [
|
||||
"glow",
|
||||
] }
|
||||
egui_web = { version = "0.17.0", path = "../egui_web", default-features = false }
|
||||
|
||||
|
||||
[dev-dependencies]
|
||||
|
|
|
@ -38,7 +38,7 @@ impl epi::App for ColorTest {
|
|||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
if frame.is_web() {
|
||||
ui.label(
|
||||
"NOTE: The WebGL1 backend without sRGB support does NOT pass the color test.",
|
||||
"NOTE: Some old browsers stuck on WebGL1 without sRGB support will not pass the color test.",
|
||||
);
|
||||
ui.separator();
|
||||
}
|
||||
|
|
|
@ -274,9 +274,6 @@ fn show_integration_name(ui: &mut egui::Ui, integration_info: &epi::IntegrationI
|
|||
format!("https://github.com/emilk/egui/tree/master/{}", name),
|
||||
);
|
||||
}
|
||||
name if name.starts_with("egui_web") => {
|
||||
ui.hyperlink_to(name, "https://github.com/emilk/egui/tree/master/egui_web");
|
||||
}
|
||||
name => {
|
||||
ui.label(name);
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ All notable changes to the `egui_web` integration will be noted in this file.
|
|||
|
||||
## Unreleased
|
||||
* egui code will no longer be called after panic ([#1306](https://github.com/emilk/egui/pull/1306))
|
||||
* Remove the "webgl" feature. `egui_web` now always use `glow` (which in turn wraps WebGL) ([#1356](https://github.com/emilk/egui/pull/1356)).
|
||||
|
||||
|
||||
## 0.17.0 - 2022-02-22
|
||||
|
|
|
@ -27,18 +27,12 @@ crate-type = ["cdylib", "rlib"]
|
|||
|
||||
|
||||
[features]
|
||||
default = ["default_fonts", "glow"]
|
||||
default = ["default_fonts"]
|
||||
|
||||
# If set, egui will use `include_bytes!` to bundle some fonts.
|
||||
# If you plan on specifying your own fonts you may disable this feature.
|
||||
default_fonts = ["egui/default_fonts"]
|
||||
|
||||
# Use glow as the renderer.
|
||||
glow = ["egui_glow", "egui_glow/epi"]
|
||||
|
||||
# Alternative to the glow renderer.
|
||||
webgl = []
|
||||
|
||||
# enable persisting egui memory
|
||||
persistence = ["egui/persistence", "ron", "serde"]
|
||||
|
||||
|
@ -52,7 +46,7 @@ egui = { version = "0.17.0", path = "../egui", default-features = false, feature
|
|||
"single_threaded",
|
||||
"tracing",
|
||||
] }
|
||||
egui_glow = { version = "0.17.0", path = "../egui_glow", optional = true, default-features = false }
|
||||
egui_glow = { version = "0.17.0", path = "../egui_glow", default-features = false }
|
||||
epi = { version = "0.17.0", path = "../epi" }
|
||||
|
||||
bytemuck = "1.7"
|
||||
|
@ -106,15 +100,8 @@ features = [
|
|||
"TouchEvent",
|
||||
"TouchList",
|
||||
"WebGl2RenderingContext",
|
||||
"WebGlBuffer",
|
||||
"WebglDebugRendererInfo",
|
||||
"WebGlFramebuffer",
|
||||
"WebGlProgram",
|
||||
"WebGlRenderingContext",
|
||||
"WebGlShader",
|
||||
"WebGlTexture",
|
||||
"WebGlUniformLocation",
|
||||
"WebGlVertexArrayObject",
|
||||
"WheelEvent",
|
||||
"Window",
|
||||
]
|
||||
|
|
|
@ -14,7 +14,7 @@ Check out [eframe_template](https://github.com/emilk/eframe_template) for an exa
|
|||
|
||||
## Downsides with using egui on the web
|
||||
|
||||
`egui_web` uses WebGL and WASM, and almost nothing else from the web tech stack. This has some benefits, but also produces some challanges and serious downsides.
|
||||
`egui_web` uses WebGL (via [`glow`](https://crates.io/crates/glow)) and WASM, and almost nothing else from the web tech stack. This has some benefits, but also produces some challanges and serious downsides.
|
||||
|
||||
* Rendering: Getting pixel-perfect rendering right on the web is very difficult.
|
||||
* Search: you cannot search an egui web page like you would a normal web page.
|
||||
|
|
|
@ -5,25 +5,10 @@ pub use egui::{pos2, Color32};
|
|||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
fn create_painter(canvas_id: &str) -> Result<Box<dyn Painter>, JsValue> {
|
||||
// Glow takes precedence:
|
||||
#[cfg(all(feature = "glow"))]
|
||||
return Ok(Box::new(
|
||||
fn create_painter(canvas_id: &str) -> Result<Box<dyn WebPainter>, JsValue> {
|
||||
Ok(Box::new(
|
||||
crate::glow_wrapping::WrappedGlowPainter::new(canvas_id).map_err(JsValue::from)?,
|
||||
));
|
||||
|
||||
#[cfg(all(feature = "webgl", not(feature = "glow")))]
|
||||
if let Ok(webgl2_painter) = webgl2::WebGl2Painter::new(canvas_id) {
|
||||
tracing::debug!("Using WebGL2 backend");
|
||||
Ok(Box::new(webgl2_painter))
|
||||
} else {
|
||||
tracing::debug!("Falling back to WebGL1 backend");
|
||||
let webgl1_painter = webgl1::WebGlPainter::new(canvas_id)?;
|
||||
Ok(Box::new(webgl1_painter))
|
||||
}
|
||||
|
||||
#[cfg(all(not(feature = "webgl"), not(feature = "glow")))]
|
||||
compile_error!("Either the 'glow' or 'webgl' feature of egui_web must be enabled!");
|
||||
))
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
@ -155,7 +140,7 @@ fn test_parse_query() {
|
|||
pub struct AppRunner {
|
||||
pub(crate) frame: epi::Frame,
|
||||
egui_ctx: egui::Context,
|
||||
painter: Box<dyn Painter>,
|
||||
painter: Box<dyn WebPainter>,
|
||||
pub(crate) input: WebInput,
|
||||
app: Box<dyn epi::App>,
|
||||
pub(crate) needs_repaint: std::sync::Arc<NeedRepaint>,
|
||||
|
|
|
@ -32,11 +32,19 @@ impl WrappedGlowPainter {
|
|||
}
|
||||
}
|
||||
|
||||
impl crate::Painter for WrappedGlowPainter {
|
||||
impl crate::WebPainter for WrappedGlowPainter {
|
||||
fn name(&self) -> &'static str {
|
||||
"egui_web"
|
||||
}
|
||||
|
||||
fn max_texture_side(&self) -> usize {
|
||||
self.painter.max_texture_side()
|
||||
}
|
||||
|
||||
fn canvas_id(&self) -> &str {
|
||||
&self.canvas_id
|
||||
}
|
||||
|
||||
fn set_texture(&mut self, tex_id: egui::TextureId, delta: &egui::epaint::ImageDelta) {
|
||||
self.painter.set_texture(&self.glow_ctx, tex_id, delta);
|
||||
}
|
||||
|
@ -45,18 +53,6 @@ impl crate::Painter for WrappedGlowPainter {
|
|||
self.painter.free_texture(&self.glow_ctx, tex_id);
|
||||
}
|
||||
|
||||
fn debug_info(&self) -> String {
|
||||
format!(
|
||||
"Stored canvas size: {} x {}",
|
||||
self.canvas.width(),
|
||||
self.canvas.height(),
|
||||
)
|
||||
}
|
||||
|
||||
fn canvas_id(&self) -> &str {
|
||||
&self.canvas_id
|
||||
}
|
||||
|
||||
fn clear(&mut self, clear_color: Rgba) {
|
||||
let canvas_dimension = [self.canvas.width(), self.canvas.height()];
|
||||
egui_glow::painter::clear(&self.glow_ctx, canvas_dimension, clear_color)
|
||||
|
@ -76,10 +72,6 @@ impl crate::Painter for WrappedGlowPainter {
|
|||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn name(&self) -> &'static str {
|
||||
"egui_web (glow)"
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns glow context and shader prefix.
|
||||
|
|
|
@ -15,18 +15,12 @@
|
|||
#![warn(clippy::all, rustdoc::missing_crate_level_docs, rust_2018_idioms)]
|
||||
|
||||
pub mod backend;
|
||||
#[cfg(feature = "glow")]
|
||||
mod glow_wrapping;
|
||||
mod input;
|
||||
mod painter;
|
||||
pub mod screen_reader;
|
||||
mod text_agent;
|
||||
|
||||
#[cfg(feature = "webgl")]
|
||||
pub mod webgl1;
|
||||
#[cfg(feature = "webgl")]
|
||||
pub mod webgl2;
|
||||
|
||||
pub use backend::*;
|
||||
|
||||
use egui::mutex::{Mutex, MutexGuard};
|
||||
|
@ -34,7 +28,7 @@ pub use wasm_bindgen;
|
|||
pub use web_sys;
|
||||
|
||||
use input::*;
|
||||
pub use painter::Painter;
|
||||
pub use painter::WebPainter;
|
||||
use web_sys::EventTarget;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
|
|
@ -1,18 +1,19 @@
|
|||
use wasm_bindgen::prelude::JsValue;
|
||||
|
||||
pub trait Painter {
|
||||
/// What is needed to paint egui.
|
||||
pub trait WebPainter {
|
||||
fn name(&self) -> &'static str;
|
||||
|
||||
/// Max size of one side of a texture.
|
||||
fn max_texture_side(&self) -> usize;
|
||||
|
||||
/// id of the canvas html element containing the rendering
|
||||
fn canvas_id(&self) -> &str;
|
||||
|
||||
fn set_texture(&mut self, tex_id: egui::TextureId, delta: &egui::epaint::ImageDelta);
|
||||
|
||||
fn free_texture(&mut self, tex_id: egui::TextureId);
|
||||
|
||||
fn debug_info(&self) -> String;
|
||||
|
||||
/// id of the canvas html element containing the rendering
|
||||
fn canvas_id(&self) -> &str;
|
||||
|
||||
fn clear(&mut self, clear_color: egui::Rgba);
|
||||
|
||||
fn paint_meshes(
|
||||
|
@ -21,8 +22,6 @@ pub trait Painter {
|
|||
pixels_per_point: f32,
|
||||
) -> Result<(), JsValue>;
|
||||
|
||||
fn name(&self) -> &'static str;
|
||||
|
||||
fn paint_and_update_textures(
|
||||
&mut self,
|
||||
clipped_meshes: Vec<egui::ClippedMesh>,
|
||||
|
|
|
@ -1,58 +0,0 @@
|
|||
precision mediump float;
|
||||
uniform sampler2D u_sampler;
|
||||
varying vec4 v_rgba;
|
||||
varying vec2 v_tc;
|
||||
|
||||
// 0-255 sRGB from 0-1 linear
|
||||
vec3 srgb_from_linear(vec3 rgb) {
|
||||
bvec3 cutoff = lessThan(rgb, vec3(0.0031308));
|
||||
vec3 lower = rgb * vec3(3294.6);
|
||||
vec3 higher = vec3(269.025) * pow(rgb, vec3(1.0 / 2.4)) - vec3(14.025);
|
||||
return mix(higher, lower, vec3(cutoff));
|
||||
}
|
||||
|
||||
// 0-255 sRGB from 0-1 linear
|
||||
vec4 srgba_from_linear(vec4 rgba) {
|
||||
return vec4(srgb_from_linear(rgba.rgb), 255.0 * rgba.a);
|
||||
}
|
||||
|
||||
// 0-1 linear from 0-255 sRGB
|
||||
vec3 linear_from_srgb(vec3 srgb) {
|
||||
bvec3 cutoff = lessThan(srgb, vec3(10.31475));
|
||||
vec3 lower = srgb / vec3(3294.6);
|
||||
vec3 higher = pow((srgb + vec3(14.025)) / vec3(269.025), vec3(2.4));
|
||||
return mix(higher, lower, vec3(cutoff));
|
||||
}
|
||||
|
||||
// 0-1 linear from 0-255 sRGBA
|
||||
vec4 linear_from_srgba(vec4 srgba) {
|
||||
return vec4(linear_from_srgb(srgba.rgb), srgba.a / 255.0);
|
||||
}
|
||||
|
||||
void main() {
|
||||
// We must decode the colors, since WebGL1 doesn't come with sRGBA textures:
|
||||
vec4 texture_rgba = linear_from_srgba(texture2D(u_sampler, v_tc) * 255.0);
|
||||
|
||||
/// Multiply vertex color with texture color (in linear space).
|
||||
gl_FragColor = v_rgba * texture_rgba;
|
||||
|
||||
// WebGL doesn't support linear blending in the framebuffer,
|
||||
// so we do a hack here where we change the premultiplied alpha
|
||||
// to do the multiplication in gamma space instead:
|
||||
|
||||
// Unmultiply alpha:
|
||||
if (gl_FragColor.a > 0.0) {
|
||||
gl_FragColor.rgb /= gl_FragColor.a;
|
||||
}
|
||||
|
||||
// Empiric tweak to make e.g. shadows look more like they should:
|
||||
gl_FragColor.a *= sqrt(gl_FragColor.a);
|
||||
|
||||
// To gamma:
|
||||
gl_FragColor = srgba_from_linear(gl_FragColor) / 255.0;
|
||||
|
||||
// Premultiply alpha, this time in gamma space:
|
||||
if (gl_FragColor.a > 0.0) {
|
||||
gl_FragColor.rgb *= gl_FragColor.a;
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
precision mediump float;
|
||||
uniform sampler2D u_sampler;
|
||||
varying vec4 v_rgba;
|
||||
varying vec2 v_tc;
|
||||
|
||||
void main() {
|
||||
// The texture is set up with `SRGB8_ALPHA8`, so no need to decode here!
|
||||
vec4 texture_rgba = texture2D(u_sampler, v_tc);
|
||||
|
||||
// Multiply vertex color with texture color (in linear space).
|
||||
// Linear color is written and blended in Framebuffer and converted to sRGB later
|
||||
gl_FragColor = v_rgba * texture_rgba;
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
precision mediump float;
|
||||
uniform vec2 u_screen_size;
|
||||
attribute vec2 a_pos;
|
||||
attribute vec2 a_tc;
|
||||
attribute vec4 a_srgba;
|
||||
varying vec4 v_rgba;
|
||||
varying vec2 v_tc;
|
||||
|
||||
// 0-1 linear from 0-255 sRGB
|
||||
vec3 linear_from_srgb(vec3 srgb) {
|
||||
bvec3 cutoff = lessThan(srgb, vec3(10.31475));
|
||||
vec3 lower = srgb / vec3(3294.6);
|
||||
vec3 higher = pow((srgb + vec3(14.025)) / vec3(269.025), vec3(2.4));
|
||||
return mix(higher, lower, vec3(cutoff));
|
||||
}
|
||||
|
||||
// 0-1 linear from 0-255 sRGBA
|
||||
vec4 linear_from_srgba(vec4 srgba) {
|
||||
return vec4(linear_from_srgb(srgba.rgb), srgba.a / 255.0);
|
||||
}
|
||||
|
||||
void main() {
|
||||
gl_Position = vec4(
|
||||
2.0 * a_pos.x / u_screen_size.x - 1.0,
|
||||
1.0 - 2.0 * a_pos.y / u_screen_size.y,
|
||||
0.0,
|
||||
1.0);
|
||||
// egui encodes vertex colors in gamma spaces, so we must decode the colors here:
|
||||
v_rgba = linear_from_srgba(a_srgba);
|
||||
v_tc = a_tc;
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
precision mediump float;
|
||||
uniform sampler2D u_sampler;
|
||||
varying vec2 v_tc;
|
||||
|
||||
// 0-255 sRGB from 0-1 linear
|
||||
vec3 srgb_from_linear(vec3 rgb) {
|
||||
bvec3 cutoff = lessThan(rgb, vec3(0.0031308));
|
||||
vec3 lower = rgb * vec3(3294.6);
|
||||
vec3 higher = vec3(269.025) * pow(rgb, vec3(1.0 / 2.4)) - vec3(14.025);
|
||||
return mix(higher, lower, vec3(cutoff));
|
||||
}
|
||||
|
||||
// 0-255 sRGBA from 0-1 linear
|
||||
vec4 srgba_from_linear(vec4 rgba) {
|
||||
return vec4(srgb_from_linear(rgba.rgb), 255.0 * rgba.a);
|
||||
}
|
||||
|
||||
void main() {
|
||||
gl_FragColor = texture2D(u_sampler, v_tc);
|
||||
|
||||
gl_FragColor = srgba_from_linear(gl_FragColor) / 255.0;
|
||||
|
||||
#ifdef APPLY_BRIGHTENING_GAMMA
|
||||
gl_FragColor = vec4(pow(gl_FragColor.rgb, vec3(1.0/2.2)), gl_FragColor.a);
|
||||
#endif
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
precision mediump float;
|
||||
attribute vec2 a_pos;
|
||||
varying vec2 v_tc;
|
||||
|
||||
void main() {
|
||||
gl_Position = vec4(a_pos * 2. - 1., 0.0, 1.0);
|
||||
v_tc = a_pos;
|
||||
}
|
|
@ -1,666 +0,0 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use {
|
||||
js_sys::WebAssembly,
|
||||
wasm_bindgen::{prelude::*, JsCast},
|
||||
web_sys::{
|
||||
ExtSRgb, WebGlBuffer, WebGlFramebuffer, WebGlProgram, WebGlRenderingContext, WebGlShader,
|
||||
WebGlTexture,
|
||||
},
|
||||
};
|
||||
|
||||
use egui::{emath::vec2, epaint::Color32};
|
||||
|
||||
type Gl = WebGlRenderingContext;
|
||||
|
||||
pub struct WebGlPainter {
|
||||
canvas_id: String,
|
||||
canvas: web_sys::HtmlCanvasElement,
|
||||
gl: WebGlRenderingContext,
|
||||
program: WebGlProgram,
|
||||
index_buffer: WebGlBuffer,
|
||||
pos_buffer: WebGlBuffer,
|
||||
tc_buffer: WebGlBuffer,
|
||||
color_buffer: WebGlBuffer,
|
||||
texture_format: u32,
|
||||
post_process: Option<PostProcess>,
|
||||
|
||||
textures: HashMap<egui::TextureId, WebGlTexture>,
|
||||
next_native_tex_id: u64,
|
||||
}
|
||||
|
||||
impl WebGlPainter {
|
||||
pub fn new(canvas_id: &str) -> Result<WebGlPainter, JsValue> {
|
||||
let canvas = crate::canvas_element_or_die(canvas_id);
|
||||
|
||||
let gl = canvas
|
||||
.get_context("webgl")?
|
||||
.ok_or_else(|| JsValue::from("Failed to get WebGL context"))?
|
||||
.dyn_into::<WebGlRenderingContext>()?;
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
let srgb_supported = matches!(gl.get_extension("EXT_sRGB"), Ok(Some(_)));
|
||||
|
||||
let vert_shader = compile_shader(
|
||||
&gl,
|
||||
Gl::VERTEX_SHADER,
|
||||
include_str!("shader/main_vertex_100es.glsl"),
|
||||
)?;
|
||||
let (texture_format, program, post_process) = if srgb_supported {
|
||||
let frag_shader = compile_shader(
|
||||
&gl,
|
||||
Gl::FRAGMENT_SHADER,
|
||||
include_str!("shader/main_fragment_100es.glsl"),
|
||||
)?;
|
||||
let program = link_program(&gl, [vert_shader, frag_shader].iter())?;
|
||||
|
||||
let post_process =
|
||||
PostProcess::new(gl.clone(), canvas.width() as i32, canvas.height() as i32)?;
|
||||
|
||||
(ExtSRgb::SRGB_ALPHA_EXT, program, Some(post_process))
|
||||
} else {
|
||||
let frag_shader = compile_shader(
|
||||
&gl,
|
||||
Gl::FRAGMENT_SHADER,
|
||||
include_str!("shader/fragment_100es.glsl"),
|
||||
)?;
|
||||
let program = link_program(&gl, [vert_shader, frag_shader].iter())?;
|
||||
|
||||
(Gl::RGBA, program, None)
|
||||
};
|
||||
|
||||
let index_buffer = gl.create_buffer().ok_or("failed to create index_buffer")?;
|
||||
let pos_buffer = gl.create_buffer().ok_or("failed to create pos_buffer")?;
|
||||
let tc_buffer = gl.create_buffer().ok_or("failed to create tc_buffer")?;
|
||||
let color_buffer = gl.create_buffer().ok_or("failed to create color_buffer")?;
|
||||
|
||||
Ok(WebGlPainter {
|
||||
canvas_id: canvas_id.to_owned(),
|
||||
canvas,
|
||||
gl,
|
||||
program,
|
||||
index_buffer,
|
||||
pos_buffer,
|
||||
tc_buffer,
|
||||
color_buffer,
|
||||
texture_format,
|
||||
post_process,
|
||||
textures: Default::default(),
|
||||
next_native_tex_id: 1 << 32,
|
||||
})
|
||||
}
|
||||
|
||||
fn get_texture(&self, texture_id: egui::TextureId) -> Option<&WebGlTexture> {
|
||||
self.textures.get(&texture_id)
|
||||
}
|
||||
|
||||
fn paint_mesh(&self, mesh: &egui::epaint::Mesh16) -> Result<(), JsValue> {
|
||||
debug_assert!(mesh.is_valid());
|
||||
|
||||
let mut positions: Vec<f32> = Vec::with_capacity(2 * mesh.vertices.len());
|
||||
let mut tex_coords: Vec<f32> = Vec::with_capacity(2 * mesh.vertices.len());
|
||||
let mut colors: Vec<u8> = Vec::with_capacity(4 * mesh.vertices.len());
|
||||
for v in &mesh.vertices {
|
||||
positions.push(v.pos.x);
|
||||
positions.push(v.pos.y);
|
||||
tex_coords.push(v.uv.x);
|
||||
tex_coords.push(v.uv.y);
|
||||
colors.push(v.color[0]);
|
||||
colors.push(v.color[1]);
|
||||
colors.push(v.color[2]);
|
||||
colors.push(v.color[3]);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
let gl = &self.gl;
|
||||
|
||||
let indices_memory_buffer = wasm_bindgen::memory()
|
||||
.dyn_into::<WebAssembly::Memory>()?
|
||||
.buffer();
|
||||
let indices_ptr = mesh.indices.as_ptr() as u32 / 2;
|
||||
let indices_array = js_sys::Int16Array::new(&indices_memory_buffer)
|
||||
.subarray(indices_ptr, indices_ptr + mesh.indices.len() as u32);
|
||||
|
||||
gl.bind_buffer(Gl::ELEMENT_ARRAY_BUFFER, Some(&self.index_buffer));
|
||||
gl.buffer_data_with_array_buffer_view(
|
||||
Gl::ELEMENT_ARRAY_BUFFER,
|
||||
&indices_array,
|
||||
Gl::STREAM_DRAW,
|
||||
);
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
let pos_memory_buffer = wasm_bindgen::memory()
|
||||
.dyn_into::<WebAssembly::Memory>()?
|
||||
.buffer();
|
||||
let pos_ptr = positions.as_ptr() as u32 / 4;
|
||||
let pos_array = js_sys::Float32Array::new(&pos_memory_buffer)
|
||||
.subarray(pos_ptr, pos_ptr + positions.len() as u32);
|
||||
|
||||
gl.bind_buffer(Gl::ARRAY_BUFFER, Some(&self.pos_buffer));
|
||||
gl.buffer_data_with_array_buffer_view(Gl::ARRAY_BUFFER, &pos_array, Gl::STREAM_DRAW);
|
||||
|
||||
let a_pos_loc = gl.get_attrib_location(&self.program, "a_pos");
|
||||
assert!(a_pos_loc >= 0);
|
||||
let a_pos_loc = a_pos_loc as u32;
|
||||
|
||||
let normalize = false;
|
||||
let stride = 0;
|
||||
let offset = 0;
|
||||
gl.vertex_attrib_pointer_with_i32(a_pos_loc, 2, Gl::FLOAT, normalize, stride, offset);
|
||||
gl.enable_vertex_attrib_array(a_pos_loc);
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
let tc_memory_buffer = wasm_bindgen::memory()
|
||||
.dyn_into::<WebAssembly::Memory>()?
|
||||
.buffer();
|
||||
let tc_ptr = tex_coords.as_ptr() as u32 / 4;
|
||||
let tc_array = js_sys::Float32Array::new(&tc_memory_buffer)
|
||||
.subarray(tc_ptr, tc_ptr + tex_coords.len() as u32);
|
||||
|
||||
gl.bind_buffer(Gl::ARRAY_BUFFER, Some(&self.tc_buffer));
|
||||
gl.buffer_data_with_array_buffer_view(Gl::ARRAY_BUFFER, &tc_array, Gl::STREAM_DRAW);
|
||||
|
||||
let a_tc_loc = gl.get_attrib_location(&self.program, "a_tc");
|
||||
assert!(a_tc_loc >= 0);
|
||||
let a_tc_loc = a_tc_loc as u32;
|
||||
|
||||
let normalize = false;
|
||||
let stride = 0;
|
||||
let offset = 0;
|
||||
gl.vertex_attrib_pointer_with_i32(a_tc_loc, 2, Gl::FLOAT, normalize, stride, offset);
|
||||
gl.enable_vertex_attrib_array(a_tc_loc);
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
let colors_memory_buffer = wasm_bindgen::memory()
|
||||
.dyn_into::<WebAssembly::Memory>()?
|
||||
.buffer();
|
||||
let colors_ptr = colors.as_ptr() as u32;
|
||||
let colors_array = js_sys::Uint8Array::new(&colors_memory_buffer)
|
||||
.subarray(colors_ptr, colors_ptr + colors.len() as u32);
|
||||
|
||||
gl.bind_buffer(Gl::ARRAY_BUFFER, Some(&self.color_buffer));
|
||||
gl.buffer_data_with_array_buffer_view(Gl::ARRAY_BUFFER, &colors_array, Gl::STREAM_DRAW);
|
||||
|
||||
let a_srgba_loc = gl.get_attrib_location(&self.program, "a_srgba");
|
||||
assert!(a_srgba_loc >= 0);
|
||||
let a_srgba_loc = a_srgba_loc as u32;
|
||||
|
||||
let normalize = false;
|
||||
let stride = 0;
|
||||
let offset = 0;
|
||||
gl.vertex_attrib_pointer_with_i32(
|
||||
a_srgba_loc,
|
||||
4,
|
||||
Gl::UNSIGNED_BYTE,
|
||||
normalize,
|
||||
stride,
|
||||
offset,
|
||||
);
|
||||
gl.enable_vertex_attrib_array(a_srgba_loc);
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
gl.draw_elements_with_i32(
|
||||
Gl::TRIANGLES,
|
||||
mesh.indices.len() as i32,
|
||||
Gl::UNSIGNED_SHORT,
|
||||
0,
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_texture_rgba(
|
||||
&mut self,
|
||||
tex_id: egui::TextureId,
|
||||
pos: Option<[usize; 2]>,
|
||||
[w, h]: [usize; 2],
|
||||
pixels: &[u8],
|
||||
) {
|
||||
let gl = &self.gl;
|
||||
|
||||
let gl_texture = self
|
||||
.textures
|
||||
.entry(tex_id)
|
||||
.or_insert_with(|| gl.create_texture().unwrap());
|
||||
|
||||
gl.bind_texture(Gl::TEXTURE_2D, Some(gl_texture));
|
||||
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_S, Gl::CLAMP_TO_EDGE as _);
|
||||
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_T, Gl::CLAMP_TO_EDGE as _);
|
||||
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MIN_FILTER, Gl::LINEAR as _);
|
||||
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MAG_FILTER, Gl::LINEAR as _);
|
||||
|
||||
let level = 0;
|
||||
let internal_format = self.texture_format;
|
||||
let border = 0;
|
||||
let src_format = self.texture_format;
|
||||
let src_type = Gl::UNSIGNED_BYTE;
|
||||
|
||||
gl.pixel_storei(Gl::UNPACK_ALIGNMENT, 1);
|
||||
|
||||
if let Some([x, y]) = pos {
|
||||
gl.tex_sub_image_2d_with_i32_and_i32_and_u32_and_type_and_opt_u8_array(
|
||||
Gl::TEXTURE_2D,
|
||||
level,
|
||||
x as _,
|
||||
y as _,
|
||||
w as _,
|
||||
h as _,
|
||||
src_format,
|
||||
src_type,
|
||||
Some(pixels),
|
||||
)
|
||||
.unwrap();
|
||||
} else {
|
||||
gl.tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array(
|
||||
Gl::TEXTURE_2D,
|
||||
level,
|
||||
internal_format as _,
|
||||
w as _,
|
||||
h as _,
|
||||
border,
|
||||
src_format,
|
||||
src_type,
|
||||
Some(pixels),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl epi::NativeTexture for WebGlPainter {
|
||||
type Texture = WebGlTexture;
|
||||
|
||||
fn register_native_texture(&mut self, texture: Self::Texture) -> egui::TextureId {
|
||||
let id = egui::TextureId::User(self.next_native_tex_id);
|
||||
self.next_native_tex_id += 1;
|
||||
self.textures.insert(id, texture);
|
||||
id
|
||||
}
|
||||
|
||||
fn replace_native_texture(&mut self, id: egui::TextureId, texture: Self::Texture) {
|
||||
self.textures.insert(id, texture);
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::Painter for WebGlPainter {
|
||||
fn max_texture_side(&self) -> usize {
|
||||
if let Ok(max_texture_side) = self
|
||||
.gl
|
||||
.get_parameter(web_sys::WebGlRenderingContext::MAX_TEXTURE_SIZE)
|
||||
{
|
||||
if let Some(max_texture_side) = max_texture_side.as_f64() {
|
||||
return max_texture_side as usize;
|
||||
}
|
||||
}
|
||||
|
||||
tracing::error!("Failed to query max texture size");
|
||||
|
||||
2048
|
||||
}
|
||||
|
||||
fn set_texture(&mut self, tex_id: egui::TextureId, delta: &egui::epaint::ImageDelta) {
|
||||
match &delta.image {
|
||||
egui::ImageData::Color(image) => {
|
||||
assert_eq!(
|
||||
image.width() * image.height(),
|
||||
image.pixels.len(),
|
||||
"Mismatch between texture size and texel count"
|
||||
);
|
||||
|
||||
let data: &[u8] = bytemuck::cast_slice(image.pixels.as_ref());
|
||||
self.set_texture_rgba(tex_id, delta.pos, image.size, data);
|
||||
}
|
||||
egui::ImageData::Alpha(image) => {
|
||||
let gamma = if self.post_process.is_none() {
|
||||
1.0 / 2.2 // HACK due to non-linear framebuffer blending.
|
||||
} else {
|
||||
1.0 // post process enables linear blending
|
||||
};
|
||||
let data: Vec<u8> = image
|
||||
.srgba_pixels(gamma)
|
||||
.flat_map(|a| a.to_array())
|
||||
.collect();
|
||||
self.set_texture_rgba(tex_id, delta.pos, image.size, &data);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn free_texture(&mut self, tex_id: egui::TextureId) {
|
||||
self.textures.remove(&tex_id);
|
||||
}
|
||||
|
||||
fn debug_info(&self) -> String {
|
||||
format!(
|
||||
"Stored canvas size: {} x {}\n\
|
||||
gl context size: {} x {}",
|
||||
self.canvas.width(),
|
||||
self.canvas.height(),
|
||||
self.gl.drawing_buffer_width(),
|
||||
self.gl.drawing_buffer_height(),
|
||||
)
|
||||
}
|
||||
|
||||
/// id of the canvas html element containing the rendering
|
||||
fn canvas_id(&self) -> &str {
|
||||
&self.canvas_id
|
||||
}
|
||||
|
||||
fn clear(&mut self, clear_color: egui::Rgba) {
|
||||
let gl = &self.gl;
|
||||
|
||||
gl.disable(Gl::SCISSOR_TEST);
|
||||
|
||||
let width = self.canvas.width() as i32;
|
||||
let height = self.canvas.height() as i32;
|
||||
gl.viewport(0, 0, width, height);
|
||||
|
||||
let clear_color: Color32 = clear_color.into();
|
||||
gl.clear_color(
|
||||
clear_color[0] as f32 / 255.0,
|
||||
clear_color[1] as f32 / 255.0,
|
||||
clear_color[2] as f32 / 255.0,
|
||||
clear_color[3] as f32 / 255.0,
|
||||
);
|
||||
gl.clear(Gl::COLOR_BUFFER_BIT);
|
||||
}
|
||||
|
||||
fn paint_meshes(
|
||||
&mut self,
|
||||
clipped_meshes: Vec<egui::ClippedMesh>,
|
||||
pixels_per_point: f32,
|
||||
) -> Result<(), JsValue> {
|
||||
let gl = &self.gl;
|
||||
|
||||
if let Some(ref mut post_process) = self.post_process {
|
||||
post_process.begin(self.canvas.width() as i32, self.canvas.height() as i32)?;
|
||||
}
|
||||
|
||||
gl.enable(Gl::SCISSOR_TEST);
|
||||
gl.disable(Gl::CULL_FACE); // egui is not strict about winding order.
|
||||
gl.enable(Gl::BLEND);
|
||||
gl.blend_func(Gl::ONE, Gl::ONE_MINUS_SRC_ALPHA); // premultiplied alpha
|
||||
gl.use_program(Some(&self.program));
|
||||
gl.active_texture(Gl::TEXTURE0);
|
||||
|
||||
let u_screen_size_loc = gl
|
||||
.get_uniform_location(&self.program, "u_screen_size")
|
||||
.unwrap();
|
||||
let screen_size_pixels = vec2(self.canvas.width() as f32, self.canvas.height() as f32);
|
||||
let screen_size_points = screen_size_pixels / pixels_per_point;
|
||||
gl.uniform2f(
|
||||
Some(&u_screen_size_loc),
|
||||
screen_size_points.x,
|
||||
screen_size_points.y,
|
||||
);
|
||||
|
||||
let u_sampler_loc = gl.get_uniform_location(&self.program, "u_sampler").unwrap();
|
||||
gl.uniform1i(Some(&u_sampler_loc), 0);
|
||||
|
||||
for egui::ClippedMesh(clip_rect, mesh) in clipped_meshes {
|
||||
if let Some(gl_texture) = self.get_texture(mesh.texture_id) {
|
||||
gl.bind_texture(Gl::TEXTURE_2D, Some(gl_texture));
|
||||
|
||||
let clip_min_x = pixels_per_point * clip_rect.min.x;
|
||||
let clip_min_y = pixels_per_point * clip_rect.min.y;
|
||||
let clip_max_x = pixels_per_point * clip_rect.max.x;
|
||||
let clip_max_y = pixels_per_point * clip_rect.max.y;
|
||||
let clip_min_x = clip_min_x.clamp(0.0, screen_size_pixels.x);
|
||||
let clip_min_y = clip_min_y.clamp(0.0, screen_size_pixels.y);
|
||||
let clip_max_x = clip_max_x.clamp(clip_min_x, screen_size_pixels.x);
|
||||
let clip_max_y = clip_max_y.clamp(clip_min_y, screen_size_pixels.y);
|
||||
let clip_min_x = clip_min_x.round() as i32;
|
||||
let clip_min_y = clip_min_y.round() as i32;
|
||||
let clip_max_x = clip_max_x.round() as i32;
|
||||
let clip_max_y = clip_max_y.round() as i32;
|
||||
|
||||
// scissor Y coordinate is from the bottom
|
||||
gl.scissor(
|
||||
clip_min_x,
|
||||
self.canvas.height() as i32 - clip_max_y,
|
||||
clip_max_x - clip_min_x,
|
||||
clip_max_y - clip_min_y,
|
||||
);
|
||||
|
||||
for mesh in mesh.split_to_u16() {
|
||||
self.paint_mesh(&mesh)?;
|
||||
}
|
||||
} else {
|
||||
tracing::warn!("WebGL: Failed to find texture {:?}", mesh.texture_id);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ref post_process) = self.post_process {
|
||||
post_process.end();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn name(&self) -> &'static str {
|
||||
"egui_web (WebGL1)"
|
||||
}
|
||||
}
|
||||
|
||||
struct PostProcess {
|
||||
gl: Gl,
|
||||
pos_buffer: WebGlBuffer,
|
||||
a_pos_loc: u32,
|
||||
index_buffer: WebGlBuffer,
|
||||
texture: WebGlTexture,
|
||||
texture_size: (i32, i32),
|
||||
fbo: WebGlFramebuffer,
|
||||
program: WebGlProgram,
|
||||
}
|
||||
|
||||
impl PostProcess {
|
||||
fn new(gl: Gl, width: i32, height: i32) -> Result<PostProcess, JsValue> {
|
||||
let fbo = gl
|
||||
.create_framebuffer()
|
||||
.ok_or("failed to create framebuffer")?;
|
||||
gl.bind_framebuffer(Gl::FRAMEBUFFER, Some(&fbo));
|
||||
|
||||
let texture = gl.create_texture().unwrap();
|
||||
gl.bind_texture(Gl::TEXTURE_2D, Some(&texture));
|
||||
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_S, Gl::CLAMP_TO_EDGE as i32);
|
||||
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_T, Gl::CLAMP_TO_EDGE as i32);
|
||||
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MIN_FILTER, Gl::NEAREST as i32);
|
||||
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MAG_FILTER, Gl::NEAREST as i32);
|
||||
gl.pixel_storei(Gl::UNPACK_ALIGNMENT, 1);
|
||||
gl.tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array(
|
||||
Gl::TEXTURE_2D,
|
||||
0,
|
||||
ExtSRgb::SRGB_ALPHA_EXT as i32,
|
||||
width,
|
||||
height,
|
||||
0,
|
||||
ExtSRgb::SRGB_ALPHA_EXT,
|
||||
Gl::UNSIGNED_BYTE,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
gl.framebuffer_texture_2d(
|
||||
Gl::FRAMEBUFFER,
|
||||
Gl::COLOR_ATTACHMENT0,
|
||||
Gl::TEXTURE_2D,
|
||||
Some(&texture),
|
||||
0,
|
||||
);
|
||||
|
||||
gl.bind_texture(Gl::TEXTURE_2D, None);
|
||||
gl.bind_framebuffer(Gl::FRAMEBUFFER, None);
|
||||
|
||||
let shader_prefix = if crate::webgl1_requires_brightening(&gl) {
|
||||
tracing::debug!("Enabling webkitGTK brightening workaround");
|
||||
"#define APPLY_BRIGHTENING_GAMMA"
|
||||
} else {
|
||||
""
|
||||
};
|
||||
|
||||
let vert_shader = compile_shader(
|
||||
&gl,
|
||||
Gl::VERTEX_SHADER,
|
||||
include_str!("shader/post_vertex_100es.glsl"),
|
||||
)?;
|
||||
let frag_shader = compile_shader(
|
||||
&gl,
|
||||
Gl::FRAGMENT_SHADER,
|
||||
&format!(
|
||||
"{}{}",
|
||||
shader_prefix,
|
||||
include_str!("shader/post_fragment_100es.glsl")
|
||||
),
|
||||
)?;
|
||||
let program = link_program(&gl, [vert_shader, frag_shader].iter())?;
|
||||
|
||||
let positions = vec![0u8, 0, 1, 0, 0, 1, 1, 1];
|
||||
|
||||
let indices = vec![0u8, 1, 2, 1, 2, 3];
|
||||
|
||||
let pos_buffer = gl.create_buffer().ok_or("failed to create pos_buffer")?;
|
||||
gl.bind_buffer(Gl::ARRAY_BUFFER, Some(&pos_buffer));
|
||||
gl.buffer_data_with_u8_array(Gl::ARRAY_BUFFER, &positions, Gl::STATIC_DRAW);
|
||||
gl.bind_buffer(Gl::ARRAY_BUFFER, None);
|
||||
|
||||
let a_pos_loc = gl.get_attrib_location(&program, "a_pos");
|
||||
assert!(a_pos_loc >= 0);
|
||||
let a_pos_loc = a_pos_loc as u32;
|
||||
|
||||
let index_buffer = gl.create_buffer().ok_or("failed to create index_buffer")?;
|
||||
gl.bind_buffer(Gl::ELEMENT_ARRAY_BUFFER, Some(&index_buffer));
|
||||
gl.buffer_data_with_u8_array(Gl::ELEMENT_ARRAY_BUFFER, &indices, Gl::STATIC_DRAW);
|
||||
gl.bind_buffer(Gl::ELEMENT_ARRAY_BUFFER, None);
|
||||
|
||||
Ok(PostProcess {
|
||||
gl,
|
||||
pos_buffer,
|
||||
a_pos_loc,
|
||||
index_buffer,
|
||||
texture,
|
||||
texture_size: (width, height),
|
||||
fbo,
|
||||
program,
|
||||
})
|
||||
}
|
||||
|
||||
fn begin(&mut self, width: i32, height: i32) -> Result<(), JsValue> {
|
||||
let gl = &self.gl;
|
||||
|
||||
if (width, height) != self.texture_size {
|
||||
gl.bind_texture(Gl::TEXTURE_2D, Some(&self.texture));
|
||||
gl.pixel_storei(Gl::UNPACK_ALIGNMENT, 1);
|
||||
gl.tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array(
|
||||
Gl::TEXTURE_2D,
|
||||
0,
|
||||
ExtSRgb::SRGB_ALPHA_EXT as i32,
|
||||
width,
|
||||
height,
|
||||
0,
|
||||
ExtSRgb::SRGB_ALPHA_EXT,
|
||||
Gl::UNSIGNED_BYTE,
|
||||
None,
|
||||
)?;
|
||||
gl.bind_texture(Gl::TEXTURE_2D, None);
|
||||
|
||||
self.texture_size = (width, height);
|
||||
}
|
||||
|
||||
gl.bind_framebuffer(Gl::FRAMEBUFFER, Some(&self.fbo));
|
||||
gl.clear_color(0.0, 0.0, 0.0, 0.0);
|
||||
gl.clear(Gl::COLOR_BUFFER_BIT);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn end(&self) {
|
||||
let gl = &self.gl;
|
||||
|
||||
gl.bind_framebuffer(Gl::FRAMEBUFFER, None);
|
||||
gl.disable(Gl::SCISSOR_TEST);
|
||||
|
||||
gl.use_program(Some(&self.program));
|
||||
|
||||
gl.active_texture(Gl::TEXTURE0);
|
||||
gl.bind_texture(Gl::TEXTURE_2D, Some(&self.texture));
|
||||
let u_sampler_loc = gl.get_uniform_location(&self.program, "u_sampler").unwrap();
|
||||
gl.uniform1i(Some(&u_sampler_loc), 0);
|
||||
|
||||
gl.bind_buffer(Gl::ARRAY_BUFFER, Some(&self.pos_buffer));
|
||||
gl.vertex_attrib_pointer_with_i32(self.a_pos_loc, 2, Gl::UNSIGNED_BYTE, false, 0, 0);
|
||||
gl.enable_vertex_attrib_array(self.a_pos_loc);
|
||||
|
||||
gl.bind_buffer(Gl::ELEMENT_ARRAY_BUFFER, Some(&self.index_buffer));
|
||||
|
||||
gl.draw_elements_with_i32(Gl::TRIANGLES, 6, Gl::UNSIGNED_BYTE, 0);
|
||||
|
||||
gl.bind_buffer(Gl::ELEMENT_ARRAY_BUFFER, None);
|
||||
gl.bind_buffer(Gl::ARRAY_BUFFER, None);
|
||||
gl.bind_texture(Gl::TEXTURE_2D, None);
|
||||
gl.use_program(None);
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for PostProcess {
|
||||
fn drop(&mut self) {
|
||||
let gl = &self.gl;
|
||||
gl.delete_buffer(Some(&self.pos_buffer));
|
||||
gl.delete_buffer(Some(&self.index_buffer));
|
||||
gl.delete_program(Some(&self.program));
|
||||
gl.delete_framebuffer(Some(&self.fbo));
|
||||
gl.delete_texture(Some(&self.texture));
|
||||
}
|
||||
}
|
||||
|
||||
fn compile_shader(
|
||||
gl: &WebGlRenderingContext,
|
||||
shader_type: u32,
|
||||
source: &str,
|
||||
) -> Result<WebGlShader, String> {
|
||||
let shader = gl
|
||||
.create_shader(shader_type)
|
||||
.ok_or_else(|| String::from("Unable to create shader object"))?;
|
||||
gl.shader_source(&shader, source);
|
||||
gl.compile_shader(&shader);
|
||||
|
||||
if gl
|
||||
.get_shader_parameter(&shader, Gl::COMPILE_STATUS)
|
||||
.as_bool()
|
||||
.unwrap_or(false)
|
||||
{
|
||||
Ok(shader)
|
||||
} else {
|
||||
Err(gl
|
||||
.get_shader_info_log(&shader)
|
||||
.unwrap_or_else(|| "Unknown error creating shader".into()))
|
||||
}
|
||||
}
|
||||
|
||||
fn link_program<'a, T: IntoIterator<Item = &'a WebGlShader>>(
|
||||
gl: &WebGlRenderingContext,
|
||||
shaders: T,
|
||||
) -> Result<WebGlProgram, String> {
|
||||
let program = gl
|
||||
.create_program()
|
||||
.ok_or_else(|| String::from("Unable to create shader object"))?;
|
||||
for shader in shaders {
|
||||
gl.attach_shader(&program, shader)
|
||||
}
|
||||
gl.link_program(&program);
|
||||
|
||||
if gl
|
||||
.get_program_parameter(&program, Gl::LINK_STATUS)
|
||||
.as_bool()
|
||||
.unwrap_or(false)
|
||||
{
|
||||
Ok(program)
|
||||
} else {
|
||||
Err(gl
|
||||
.get_program_info_log(&program)
|
||||
.unwrap_or_else(|| "Unknown error creating program object".into()))
|
||||
}
|
||||
}
|
|
@ -1,637 +0,0 @@
|
|||
//! Mostly a carbon-copy of `webgl1.rs`.
|
||||
use std::collections::HashMap;
|
||||
|
||||
use {
|
||||
js_sys::WebAssembly,
|
||||
wasm_bindgen::{prelude::*, JsCast},
|
||||
web_sys::{
|
||||
WebGl2RenderingContext, WebGlBuffer, WebGlFramebuffer, WebGlProgram, WebGlShader,
|
||||
WebGlTexture, WebGlVertexArrayObject,
|
||||
},
|
||||
};
|
||||
|
||||
use egui::{emath::vec2, epaint::Color32};
|
||||
|
||||
type Gl = WebGl2RenderingContext;
|
||||
|
||||
pub struct WebGl2Painter {
|
||||
canvas_id: String,
|
||||
canvas: web_sys::HtmlCanvasElement,
|
||||
gl: WebGl2RenderingContext,
|
||||
program: WebGlProgram,
|
||||
index_buffer: WebGlBuffer,
|
||||
pos_buffer: WebGlBuffer,
|
||||
tc_buffer: WebGlBuffer,
|
||||
color_buffer: WebGlBuffer,
|
||||
post_process: PostProcess,
|
||||
|
||||
textures: HashMap<egui::TextureId, WebGlTexture>,
|
||||
next_native_tex_id: u64,
|
||||
}
|
||||
|
||||
impl WebGl2Painter {
|
||||
pub fn new(canvas_id: &str) -> Result<WebGl2Painter, JsValue> {
|
||||
let canvas = crate::canvas_element_or_die(canvas_id);
|
||||
|
||||
let gl = canvas
|
||||
.get_context("webgl2")?
|
||||
.ok_or_else(|| JsValue::from("Failed to get WebGl2 context"))?
|
||||
.dyn_into::<WebGl2RenderingContext>()?;
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
let vert_shader = compile_shader(
|
||||
&gl,
|
||||
Gl::VERTEX_SHADER,
|
||||
include_str!("shader/main_vertex_100es.glsl"),
|
||||
)?;
|
||||
let frag_shader = compile_shader(
|
||||
&gl,
|
||||
Gl::FRAGMENT_SHADER,
|
||||
include_str!("shader/main_fragment_100es.glsl"),
|
||||
)?;
|
||||
|
||||
let program = link_program(&gl, [vert_shader, frag_shader].iter())?;
|
||||
let index_buffer = gl.create_buffer().ok_or("failed to create index_buffer")?;
|
||||
let pos_buffer = gl.create_buffer().ok_or("failed to create pos_buffer")?;
|
||||
let tc_buffer = gl.create_buffer().ok_or("failed to create tc_buffer")?;
|
||||
let color_buffer = gl.create_buffer().ok_or("failed to create color_buffer")?;
|
||||
|
||||
let post_process =
|
||||
PostProcess::new(gl.clone(), canvas.width() as i32, canvas.height() as i32)?;
|
||||
|
||||
Ok(WebGl2Painter {
|
||||
canvas_id: canvas_id.to_owned(),
|
||||
canvas,
|
||||
gl,
|
||||
program,
|
||||
index_buffer,
|
||||
pos_buffer,
|
||||
tc_buffer,
|
||||
color_buffer,
|
||||
post_process,
|
||||
textures: Default::default(),
|
||||
next_native_tex_id: 1 << 32,
|
||||
})
|
||||
}
|
||||
|
||||
fn get_texture(&self, texture_id: egui::TextureId) -> Option<&WebGlTexture> {
|
||||
self.textures.get(&texture_id)
|
||||
}
|
||||
|
||||
fn paint_mesh(&self, mesh: &egui::epaint::Mesh16) -> Result<(), JsValue> {
|
||||
debug_assert!(mesh.is_valid());
|
||||
|
||||
let mut positions: Vec<f32> = Vec::with_capacity(2 * mesh.vertices.len());
|
||||
let mut tex_coords: Vec<f32> = Vec::with_capacity(2 * mesh.vertices.len());
|
||||
let mut colors: Vec<u8> = Vec::with_capacity(4 * mesh.vertices.len());
|
||||
for v in &mesh.vertices {
|
||||
positions.push(v.pos.x);
|
||||
positions.push(v.pos.y);
|
||||
tex_coords.push(v.uv.x);
|
||||
tex_coords.push(v.uv.y);
|
||||
colors.push(v.color[0]);
|
||||
colors.push(v.color[1]);
|
||||
colors.push(v.color[2]);
|
||||
colors.push(v.color[3]);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
let gl = &self.gl;
|
||||
|
||||
let indices_memory_buffer = wasm_bindgen::memory()
|
||||
.dyn_into::<WebAssembly::Memory>()?
|
||||
.buffer();
|
||||
let indices_ptr = mesh.indices.as_ptr() as u32 / 2;
|
||||
let indices_array = js_sys::Int16Array::new(&indices_memory_buffer)
|
||||
.subarray(indices_ptr, indices_ptr + mesh.indices.len() as u32);
|
||||
|
||||
gl.bind_buffer(Gl::ELEMENT_ARRAY_BUFFER, Some(&self.index_buffer));
|
||||
gl.buffer_data_with_array_buffer_view(
|
||||
Gl::ELEMENT_ARRAY_BUFFER,
|
||||
&indices_array,
|
||||
Gl::STREAM_DRAW,
|
||||
);
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
let pos_memory_buffer = wasm_bindgen::memory()
|
||||
.dyn_into::<WebAssembly::Memory>()?
|
||||
.buffer();
|
||||
let pos_ptr = positions.as_ptr() as u32 / 4;
|
||||
let pos_array = js_sys::Float32Array::new(&pos_memory_buffer)
|
||||
.subarray(pos_ptr, pos_ptr + positions.len() as u32);
|
||||
|
||||
gl.bind_buffer(Gl::ARRAY_BUFFER, Some(&self.pos_buffer));
|
||||
gl.buffer_data_with_array_buffer_view(Gl::ARRAY_BUFFER, &pos_array, Gl::STREAM_DRAW);
|
||||
|
||||
let a_pos_loc = gl.get_attrib_location(&self.program, "a_pos");
|
||||
assert!(a_pos_loc >= 0);
|
||||
let a_pos_loc = a_pos_loc as u32;
|
||||
|
||||
let normalize = false;
|
||||
let stride = 0;
|
||||
let offset = 0;
|
||||
gl.vertex_attrib_pointer_with_i32(a_pos_loc, 2, Gl::FLOAT, normalize, stride, offset);
|
||||
gl.enable_vertex_attrib_array(a_pos_loc);
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
let tc_memory_buffer = wasm_bindgen::memory()
|
||||
.dyn_into::<WebAssembly::Memory>()?
|
||||
.buffer();
|
||||
let tc_ptr = tex_coords.as_ptr() as u32 / 4;
|
||||
let tc_array = js_sys::Float32Array::new(&tc_memory_buffer)
|
||||
.subarray(tc_ptr, tc_ptr + tex_coords.len() as u32);
|
||||
|
||||
gl.bind_buffer(Gl::ARRAY_BUFFER, Some(&self.tc_buffer));
|
||||
gl.buffer_data_with_array_buffer_view(Gl::ARRAY_BUFFER, &tc_array, Gl::STREAM_DRAW);
|
||||
|
||||
let a_tc_loc = gl.get_attrib_location(&self.program, "a_tc");
|
||||
assert!(a_tc_loc >= 0);
|
||||
let a_tc_loc = a_tc_loc as u32;
|
||||
|
||||
let normalize = false;
|
||||
let stride = 0;
|
||||
let offset = 0;
|
||||
gl.vertex_attrib_pointer_with_i32(a_tc_loc, 2, Gl::FLOAT, normalize, stride, offset);
|
||||
gl.enable_vertex_attrib_array(a_tc_loc);
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
let colors_memory_buffer = wasm_bindgen::memory()
|
||||
.dyn_into::<WebAssembly::Memory>()?
|
||||
.buffer();
|
||||
let colors_ptr = colors.as_ptr() as u32;
|
||||
let colors_array = js_sys::Uint8Array::new(&colors_memory_buffer)
|
||||
.subarray(colors_ptr, colors_ptr + colors.len() as u32);
|
||||
|
||||
gl.bind_buffer(Gl::ARRAY_BUFFER, Some(&self.color_buffer));
|
||||
gl.buffer_data_with_array_buffer_view(Gl::ARRAY_BUFFER, &colors_array, Gl::STREAM_DRAW);
|
||||
|
||||
let a_srgba_loc = gl.get_attrib_location(&self.program, "a_srgba");
|
||||
assert!(a_srgba_loc >= 0);
|
||||
let a_srgba_loc = a_srgba_loc as u32;
|
||||
|
||||
let normalize = false;
|
||||
let stride = 0;
|
||||
let offset = 0;
|
||||
gl.vertex_attrib_pointer_with_i32(
|
||||
a_srgba_loc,
|
||||
4,
|
||||
Gl::UNSIGNED_BYTE,
|
||||
normalize,
|
||||
stride,
|
||||
offset,
|
||||
);
|
||||
gl.enable_vertex_attrib_array(a_srgba_loc);
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
gl.draw_elements_with_i32(
|
||||
Gl::TRIANGLES,
|
||||
mesh.indices.len() as i32,
|
||||
Gl::UNSIGNED_SHORT,
|
||||
0,
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_texture_rgba(
|
||||
&mut self,
|
||||
tex_id: egui::TextureId,
|
||||
pos: Option<[usize; 2]>,
|
||||
[w, h]: [usize; 2],
|
||||
pixels: &[u8],
|
||||
) {
|
||||
let gl = &self.gl;
|
||||
|
||||
let gl_texture = self
|
||||
.textures
|
||||
.entry(tex_id)
|
||||
.or_insert_with(|| gl.create_texture().unwrap());
|
||||
|
||||
gl.bind_texture(Gl::TEXTURE_2D, Some(gl_texture));
|
||||
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_S, Gl::CLAMP_TO_EDGE as _);
|
||||
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_T, Gl::CLAMP_TO_EDGE as _);
|
||||
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MIN_FILTER, Gl::LINEAR as _);
|
||||
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MAG_FILTER, Gl::LINEAR as _);
|
||||
|
||||
let level = 0;
|
||||
let internal_format = Gl::SRGB8_ALPHA8;
|
||||
let border = 0;
|
||||
let src_format = Gl::RGBA;
|
||||
let src_type = Gl::UNSIGNED_BYTE;
|
||||
|
||||
gl.pixel_storei(Gl::UNPACK_ALIGNMENT, 1);
|
||||
|
||||
if let Some([x, y]) = pos {
|
||||
gl.tex_sub_image_2d_with_i32_and_i32_and_u32_and_type_and_opt_u8_array(
|
||||
Gl::TEXTURE_2D,
|
||||
level,
|
||||
x as _,
|
||||
y as _,
|
||||
w as _,
|
||||
h as _,
|
||||
src_format,
|
||||
src_type,
|
||||
Some(pixels),
|
||||
)
|
||||
.unwrap();
|
||||
} else {
|
||||
gl.tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array(
|
||||
Gl::TEXTURE_2D,
|
||||
level,
|
||||
internal_format as _,
|
||||
w as _,
|
||||
h as _,
|
||||
border,
|
||||
src_format,
|
||||
src_type,
|
||||
Some(pixels),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl epi::NativeTexture for WebGl2Painter {
|
||||
type Texture = WebGlTexture;
|
||||
|
||||
fn register_native_texture(&mut self, native: Self::Texture) -> egui::TextureId {
|
||||
let id = egui::TextureId::User(self.next_native_tex_id);
|
||||
self.next_native_tex_id += 1;
|
||||
self.textures.insert(id, native);
|
||||
id
|
||||
}
|
||||
|
||||
fn replace_native_texture(&mut self, id: egui::TextureId, native: Self::Texture) {
|
||||
self.textures.insert(id, native);
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::Painter for WebGl2Painter {
|
||||
fn max_texture_side(&self) -> usize {
|
||||
if let Ok(max_texture_side) = self
|
||||
.gl
|
||||
.get_parameter(web_sys::WebGl2RenderingContext::MAX_TEXTURE_SIZE)
|
||||
{
|
||||
if let Some(max_texture_side) = max_texture_side.as_f64() {
|
||||
return max_texture_side as usize;
|
||||
}
|
||||
}
|
||||
|
||||
tracing::error!("Failed to query max texture size");
|
||||
|
||||
2048
|
||||
}
|
||||
|
||||
fn set_texture(&mut self, tex_id: egui::TextureId, delta: &egui::epaint::ImageDelta) {
|
||||
match &delta.image {
|
||||
egui::ImageData::Color(image) => {
|
||||
assert_eq!(
|
||||
image.width() * image.height(),
|
||||
image.pixels.len(),
|
||||
"Mismatch between texture size and texel count"
|
||||
);
|
||||
|
||||
let data: &[u8] = bytemuck::cast_slice(image.pixels.as_ref());
|
||||
self.set_texture_rgba(tex_id, delta.pos, image.size, data);
|
||||
}
|
||||
egui::ImageData::Alpha(image) => {
|
||||
let gamma = 1.0;
|
||||
let data: Vec<u8> = image
|
||||
.srgba_pixels(gamma)
|
||||
.flat_map(|a| a.to_array())
|
||||
.collect();
|
||||
self.set_texture_rgba(tex_id, delta.pos, image.size, &data);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn free_texture(&mut self, tex_id: egui::TextureId) {
|
||||
self.textures.remove(&tex_id);
|
||||
}
|
||||
|
||||
fn debug_info(&self) -> String {
|
||||
format!(
|
||||
"Stored canvas size: {} x {}\n\
|
||||
gl context size: {} x {}",
|
||||
self.canvas.width(),
|
||||
self.canvas.height(),
|
||||
self.gl.drawing_buffer_width(),
|
||||
self.gl.drawing_buffer_height(),
|
||||
)
|
||||
}
|
||||
|
||||
/// id of the canvas html element containing the rendering
|
||||
fn canvas_id(&self) -> &str {
|
||||
&self.canvas_id
|
||||
}
|
||||
|
||||
fn clear(&mut self, clear_color: egui::Rgba) {
|
||||
let gl = &self.gl;
|
||||
|
||||
gl.disable(Gl::SCISSOR_TEST);
|
||||
|
||||
let width = self.canvas.width() as i32;
|
||||
let height = self.canvas.height() as i32;
|
||||
gl.viewport(0, 0, width, height);
|
||||
|
||||
let clear_color: Color32 = clear_color.into();
|
||||
gl.clear_color(
|
||||
clear_color[0] as f32 / 255.0,
|
||||
clear_color[1] as f32 / 255.0,
|
||||
clear_color[2] as f32 / 255.0,
|
||||
clear_color[3] as f32 / 255.0,
|
||||
);
|
||||
gl.clear(Gl::COLOR_BUFFER_BIT);
|
||||
}
|
||||
|
||||
fn paint_meshes(
|
||||
&mut self,
|
||||
clipped_meshes: Vec<egui::ClippedMesh>,
|
||||
pixels_per_point: f32,
|
||||
) -> Result<(), JsValue> {
|
||||
let gl = &self.gl;
|
||||
|
||||
self.post_process
|
||||
.begin(self.canvas.width() as i32, self.canvas.height() as i32)?;
|
||||
|
||||
gl.enable(Gl::SCISSOR_TEST);
|
||||
gl.disable(Gl::CULL_FACE); // egui is not strict about winding order.
|
||||
gl.enable(Gl::BLEND);
|
||||
gl.blend_func(Gl::ONE, Gl::ONE_MINUS_SRC_ALPHA); // premultiplied alpha
|
||||
gl.use_program(Some(&self.program));
|
||||
gl.active_texture(Gl::TEXTURE0);
|
||||
|
||||
let u_screen_size_loc = gl
|
||||
.get_uniform_location(&self.program, "u_screen_size")
|
||||
.unwrap();
|
||||
let screen_size_pixels = vec2(self.canvas.width() as f32, self.canvas.height() as f32);
|
||||
let screen_size_points = screen_size_pixels / pixels_per_point;
|
||||
gl.uniform2f(
|
||||
Some(&u_screen_size_loc),
|
||||
screen_size_points.x,
|
||||
screen_size_points.y,
|
||||
);
|
||||
|
||||
let u_sampler_loc = gl.get_uniform_location(&self.program, "u_sampler").unwrap();
|
||||
gl.uniform1i(Some(&u_sampler_loc), 0);
|
||||
|
||||
for egui::ClippedMesh(clip_rect, mesh) in clipped_meshes {
|
||||
if let Some(gl_texture) = self.get_texture(mesh.texture_id) {
|
||||
gl.bind_texture(Gl::TEXTURE_2D, Some(gl_texture));
|
||||
|
||||
let clip_min_x = pixels_per_point * clip_rect.min.x;
|
||||
let clip_min_y = pixels_per_point * clip_rect.min.y;
|
||||
let clip_max_x = pixels_per_point * clip_rect.max.x;
|
||||
let clip_max_y = pixels_per_point * clip_rect.max.y;
|
||||
let clip_min_x = clip_min_x.clamp(0.0, screen_size_pixels.x);
|
||||
let clip_min_y = clip_min_y.clamp(0.0, screen_size_pixels.y);
|
||||
let clip_max_x = clip_max_x.clamp(clip_min_x, screen_size_pixels.x);
|
||||
let clip_max_y = clip_max_y.clamp(clip_min_y, screen_size_pixels.y);
|
||||
let clip_min_x = clip_min_x.round() as i32;
|
||||
let clip_min_y = clip_min_y.round() as i32;
|
||||
let clip_max_x = clip_max_x.round() as i32;
|
||||
let clip_max_y = clip_max_y.round() as i32;
|
||||
|
||||
// scissor Y coordinate is from the bottom
|
||||
gl.scissor(
|
||||
clip_min_x,
|
||||
self.canvas.height() as i32 - clip_max_y,
|
||||
clip_max_x - clip_min_x,
|
||||
clip_max_y - clip_min_y,
|
||||
);
|
||||
|
||||
for mesh in mesh.split_to_u16() {
|
||||
self.paint_mesh(&mesh)?;
|
||||
}
|
||||
} else {
|
||||
tracing::warn!("WebGL: Failed to find texture {:?}", mesh.texture_id);
|
||||
}
|
||||
}
|
||||
|
||||
self.post_process.end();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn name(&self) -> &'static str {
|
||||
"egui_web (WebGL2)"
|
||||
}
|
||||
}
|
||||
|
||||
/// Uses a framebuffer to render everything in linear color space and convert it back to sRGB
|
||||
/// in a separate "post processing" step
|
||||
struct PostProcess {
|
||||
gl: Gl,
|
||||
pos_buffer: WebGlBuffer,
|
||||
index_buffer: WebGlBuffer,
|
||||
vao: WebGlVertexArrayObject,
|
||||
texture: WebGlTexture,
|
||||
texture_size: (i32, i32),
|
||||
fbo: WebGlFramebuffer,
|
||||
program: WebGlProgram,
|
||||
}
|
||||
|
||||
impl PostProcess {
|
||||
fn new(gl: Gl, width: i32, height: i32) -> Result<PostProcess, JsValue> {
|
||||
let fbo = gl
|
||||
.create_framebuffer()
|
||||
.ok_or("failed to create framebuffer")?;
|
||||
gl.bind_framebuffer(Gl::FRAMEBUFFER, Some(&fbo));
|
||||
|
||||
let texture = gl.create_texture().unwrap();
|
||||
gl.bind_texture(Gl::TEXTURE_2D, Some(&texture));
|
||||
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_S, Gl::CLAMP_TO_EDGE as i32);
|
||||
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_T, Gl::CLAMP_TO_EDGE as i32);
|
||||
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MIN_FILTER, Gl::NEAREST as i32);
|
||||
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MAG_FILTER, Gl::NEAREST as i32);
|
||||
gl.pixel_storei(Gl::UNPACK_ALIGNMENT, 1);
|
||||
gl.tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array(
|
||||
Gl::TEXTURE_2D,
|
||||
0,
|
||||
Gl::SRGB8_ALPHA8 as i32,
|
||||
width,
|
||||
height,
|
||||
0,
|
||||
Gl::RGBA,
|
||||
Gl::UNSIGNED_BYTE,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
gl.framebuffer_texture_2d(
|
||||
Gl::FRAMEBUFFER,
|
||||
Gl::COLOR_ATTACHMENT0,
|
||||
Gl::TEXTURE_2D,
|
||||
Some(&texture),
|
||||
0,
|
||||
);
|
||||
|
||||
gl.bind_texture(Gl::TEXTURE_2D, None);
|
||||
gl.bind_framebuffer(Gl::FRAMEBUFFER, None);
|
||||
|
||||
let vert_shader = compile_shader(
|
||||
&gl,
|
||||
Gl::VERTEX_SHADER,
|
||||
include_str!("shader/post_vertex_100es.glsl"),
|
||||
)?;
|
||||
let frag_shader = compile_shader(
|
||||
&gl,
|
||||
Gl::FRAGMENT_SHADER,
|
||||
include_str!("shader/post_fragment_100es.glsl"),
|
||||
)?;
|
||||
let program = link_program(&gl, [vert_shader, frag_shader].iter())?;
|
||||
|
||||
let vao = gl.create_vertex_array().ok_or("failed to create vao")?;
|
||||
gl.bind_vertex_array(Some(&vao));
|
||||
|
||||
let positions = vec![0u8, 0, 1, 0, 0, 1, 1, 1];
|
||||
|
||||
let indices = vec![0u8, 1, 2, 1, 2, 3];
|
||||
|
||||
let pos_buffer = gl.create_buffer().ok_or("failed to create pos_buffer")?;
|
||||
gl.bind_buffer(Gl::ARRAY_BUFFER, Some(&pos_buffer));
|
||||
gl.buffer_data_with_u8_array(Gl::ARRAY_BUFFER, &positions, Gl::STATIC_DRAW);
|
||||
|
||||
let a_pos_loc = gl.get_attrib_location(&program, "a_pos");
|
||||
assert!(a_pos_loc >= 0);
|
||||
gl.vertex_attrib_pointer_with_i32(a_pos_loc as u32, 2, Gl::UNSIGNED_BYTE, false, 0, 0);
|
||||
gl.enable_vertex_attrib_array(a_pos_loc as u32);
|
||||
|
||||
gl.bind_buffer(Gl::ARRAY_BUFFER, None);
|
||||
|
||||
let index_buffer = gl.create_buffer().ok_or("failed to create index_buffer")?;
|
||||
gl.bind_buffer(Gl::ELEMENT_ARRAY_BUFFER, Some(&index_buffer));
|
||||
gl.buffer_data_with_u8_array(Gl::ELEMENT_ARRAY_BUFFER, &indices, Gl::STATIC_DRAW);
|
||||
|
||||
gl.bind_vertex_array(None);
|
||||
gl.bind_buffer(Gl::ELEMENT_ARRAY_BUFFER, None);
|
||||
|
||||
Ok(PostProcess {
|
||||
gl,
|
||||
pos_buffer,
|
||||
index_buffer,
|
||||
vao,
|
||||
texture,
|
||||
texture_size: (width, height),
|
||||
fbo,
|
||||
program,
|
||||
})
|
||||
}
|
||||
|
||||
fn begin(&mut self, width: i32, height: i32) -> Result<(), JsValue> {
|
||||
let gl = &self.gl;
|
||||
|
||||
if (width, height) != self.texture_size {
|
||||
gl.bind_texture(Gl::TEXTURE_2D, Some(&self.texture));
|
||||
gl.pixel_storei(Gl::UNPACK_ALIGNMENT, 1);
|
||||
gl.tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array(
|
||||
Gl::TEXTURE_2D,
|
||||
0,
|
||||
Gl::SRGB8_ALPHA8 as i32,
|
||||
width,
|
||||
height,
|
||||
0,
|
||||
Gl::RGBA,
|
||||
Gl::UNSIGNED_BYTE,
|
||||
None,
|
||||
)?;
|
||||
gl.bind_texture(Gl::TEXTURE_2D, None);
|
||||
|
||||
self.texture_size = (width, height);
|
||||
}
|
||||
|
||||
gl.bind_framebuffer(Gl::FRAMEBUFFER, Some(&self.fbo));
|
||||
gl.clear_color(0.0, 0.0, 0.0, 0.0);
|
||||
gl.clear(Gl::COLOR_BUFFER_BIT);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn end(&self) {
|
||||
let gl = &self.gl;
|
||||
|
||||
gl.bind_framebuffer(Gl::FRAMEBUFFER, None);
|
||||
gl.disable(Gl::SCISSOR_TEST);
|
||||
|
||||
gl.use_program(Some(&self.program));
|
||||
|
||||
gl.active_texture(Gl::TEXTURE0);
|
||||
gl.bind_texture(Gl::TEXTURE_2D, Some(&self.texture));
|
||||
let u_sampler_loc = gl.get_uniform_location(&self.program, "u_sampler").unwrap();
|
||||
gl.uniform1i(Some(&u_sampler_loc), 0);
|
||||
|
||||
gl.bind_vertex_array(Some(&self.vao));
|
||||
|
||||
gl.draw_elements_with_i32(Gl::TRIANGLES, 6, Gl::UNSIGNED_BYTE, 0);
|
||||
|
||||
gl.bind_texture(Gl::TEXTURE_2D, None);
|
||||
gl.bind_vertex_array(None);
|
||||
gl.use_program(None);
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for PostProcess {
|
||||
fn drop(&mut self) {
|
||||
let gl = &self.gl;
|
||||
gl.delete_vertex_array(Some(&self.vao));
|
||||
gl.delete_buffer(Some(&self.pos_buffer));
|
||||
gl.delete_buffer(Some(&self.index_buffer));
|
||||
gl.delete_program(Some(&self.program));
|
||||
gl.delete_framebuffer(Some(&self.fbo));
|
||||
gl.delete_texture(Some(&self.texture));
|
||||
}
|
||||
}
|
||||
|
||||
fn compile_shader(
|
||||
gl: &WebGl2RenderingContext,
|
||||
shader_type: u32,
|
||||
source: &str,
|
||||
) -> Result<WebGlShader, String> {
|
||||
let shader = gl
|
||||
.create_shader(shader_type)
|
||||
.ok_or_else(|| String::from("Unable to create shader object"))?;
|
||||
gl.shader_source(&shader, source);
|
||||
gl.compile_shader(&shader);
|
||||
|
||||
if gl
|
||||
.get_shader_parameter(&shader, Gl::COMPILE_STATUS)
|
||||
.as_bool()
|
||||
.unwrap_or(false)
|
||||
{
|
||||
Ok(shader)
|
||||
} else {
|
||||
Err(gl
|
||||
.get_shader_info_log(&shader)
|
||||
.unwrap_or_else(|| "Unknown error creating shader".into()))
|
||||
}
|
||||
}
|
||||
|
||||
fn link_program<'a, T: IntoIterator<Item = &'a WebGlShader>>(
|
||||
gl: &WebGl2RenderingContext,
|
||||
shaders: T,
|
||||
) -> Result<WebGlProgram, String> {
|
||||
let program = gl
|
||||
.create_program()
|
||||
.ok_or_else(|| String::from("Unable to create shader object"))?;
|
||||
for shader in shaders {
|
||||
gl.attach_shader(&program, shader)
|
||||
}
|
||||
gl.link_program(&program);
|
||||
|
||||
if gl
|
||||
.get_program_parameter(&program, Gl::LINK_STATUS)
|
||||
.as_bool()
|
||||
.unwrap_or(false)
|
||||
{
|
||||
Ok(program)
|
||||
} else {
|
||||
Err(gl
|
||||
.get_program_info_log(&program)
|
||||
.unwrap_or_else(|| "Unknown error creating program object".into()))
|
||||
}
|
||||
}
|
|
@ -29,7 +29,7 @@ cargo doc --document-private-items --no-deps --all-features
|
|||
(cd epi && cargo check --no-default-features)
|
||||
(cd egui_demo_lib && cargo check --no-default-features)
|
||||
(cd egui_extras && cargo check --no-default-features)
|
||||
# (cd egui_web && cargo check --no-default-features) # we need to pick webgl or glow backend
|
||||
(cd egui_web && cargo check --no-default-features)
|
||||
# (cd egui-winit && cargo check --no-default-features) # we don't pick singlethreaded or multithreaded
|
||||
(cd egui_glium && cargo check --no-default-features)
|
||||
(cd egui_glow && cargo check --no-default-features)
|
||||
|
|
Loading…
Reference in a new issue