Make egui_glow painter to work on web (#868)

Add WebGL1 and WebGL2 support to glow painter.
Add "glow" feature to egui_web to use the glow painter there.
Make winit an optional part of egui_glow
This commit is contained in:
triangle drawer 2021-11-04 03:17:07 +09:00 committed by GitHub
parent 1dbe608e73
commit 804722a1ba
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 1093 additions and 318 deletions

View file

@ -3,6 +3,7 @@ All notable changes to the `egui_glow` integration will be noted in this file.
## Unreleased
* Make winit/glutin an optional dependency ([#868](https://github.com/emilk/egui/pull/868)).
* Simplify `EguiGlow` interface ([#871](https://github.com/emilk/egui/pull/871)).

View file

@ -23,18 +23,24 @@ all-features = true
[dependencies]
egui = { version = "0.15.0", path = "../egui", default-features = false, features = ["single_threaded"] }
egui-winit = { version = "0.15.0", path = "../egui-winit", default-features = false, features = ["epi"] }
epi = { version = "0.15.0", path = "../epi", optional = true }
epi = { version = "0.15.0", path = "../epi", optional = true }
glow = "0.11"
glutin = "0.27"
memoffset = "0.6"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
egui-winit = { version = "0.15.0", path = "../egui-winit", default-features = false, features = ["epi"], optional = true }
glutin = { version = "0.27.0", optional = true }
[target.'cfg(target_arch = "wasm32")'.dependencies]
web-sys = { version = "0.3", features=["console"] }
wasm-bindgen = { version = "0.2" }
[dev-dependencies]
image = { version = "0.23", default-features = false, features = ["png"] }
[features]
default = ["clipboard", "default_fonts", "links", "persistence"]
default = ["clipboard", "default_fonts", "links", "persistence", "winit"]
# enable cut/copy/paste to OS clipboard.
# if disabled a clipboard will be simulated so you can still copy/paste within the egui app.
@ -58,3 +64,8 @@ persistence = [
# experimental support for a screen reader
screen_reader = ["egui-winit/screen_reader"]
# enable glutin/winit integration.
# if you want to use glow painter on web disable it.
# if disabled reduce crate size and build time.
winit= ["egui-winit","glutin"]

View file

@ -1,24 +1,7 @@
use crate::*;
use egui::Color32;
#[cfg(target_os = "windows")]
use glutin::platform::windows::WindowBuilderExtWindows;
impl epi::TextureAllocator for Painter {
fn alloc_srgba_premultiplied(
&mut self,
size: (usize, usize),
srgba_pixels: &[Color32],
) -> egui::TextureId {
let id = self.alloc_user_texture();
self.set_user_texture(id, size, srgba_pixels);
id
}
fn free(&mut self, id: egui::TextureId) {
self.free_user_texture(id);
}
}
struct RequestRepaintEvent;
struct GlowRepaintSignal(std::sync::Mutex<glutin::event_loop::EventLoopProxy<RequestRepaintEvent>>);
@ -77,7 +60,9 @@ pub fn run(app: Box<dyn epi::App>, native_options: &epi::NativeOptions) -> ! {
event_loop.create_proxy(),
)));
let mut painter = crate::Painter::new(&gl);
let mut painter = crate::Painter::new(&gl, None)
.map_err(|error| eprintln!("some OpenGL error occurred {}\n", error))
.unwrap();
let mut integration = egui_winit::epi::EpiIntegration::new(
"egui_glow",
gl_window.window(),
@ -111,13 +96,12 @@ pub fn run(app: Box<dyn epi::App>, native_options: &epi::NativeOptions) -> ! {
gl.clear_color(color[0], color[1], color[2], color[3]);
gl.clear(glow::COLOR_BUFFER_BIT);
}
painter.upload_egui_texture(&gl, &integration.egui_ctx.texture());
painter.paint_meshes(
&gl_window,
gl_window.window().inner_size().into(),
&gl,
integration.egui_ctx.pixels_per_point(),
clipped_meshes,
&integration.egui_ctx.texture(),
);
gl_window.swap_buffers().unwrap();

View file

@ -87,25 +87,32 @@
#![allow(clippy::float_cmp)]
#![allow(clippy::manual_range_contains)]
mod painter;
pub mod painter;
pub use glow;
pub use painter::Painter;
#[cfg(feature = "epi")]
#[cfg(feature = "winit")]
mod epi_backend;
#[cfg(feature = "epi")]
pub use epi_backend::{run, NativeOptions};
mod misc_util;
mod post_process;
mod shader_version;
mod vao_emulate;
#[cfg(not(target_arch = "wasm32"))]
pub use egui_winit;
#[cfg(all(feature = "epi", feature = "winit"))]
pub use epi_backend::{run, NativeOptions};
// ----------------------------------------------------------------------------
/// Use [`egui`] from a [`glow`] app.
#[cfg(feature = "winit")]
pub struct EguiGlow {
pub egui_ctx: egui::CtxRef,
pub egui_winit: egui_winit::State,
pub painter: crate::Painter,
}
#[cfg(feature = "winit")]
impl EguiGlow {
pub fn new(
gl_window: &glutin::WindowedContext<glutin::PossiblyCurrent>,
@ -114,7 +121,11 @@ impl EguiGlow {
Self {
egui_ctx: Default::default(),
egui_winit: egui_winit::State::new(gl_window.window()),
painter: crate::Painter::new(gl),
painter: crate::Painter::new(gl, None)
.map_err(|error| {
eprintln!("some error occurred in initializing painter\n{}", error);
})
.unwrap(),
}
}
@ -158,12 +169,14 @@ impl EguiGlow {
shapes: Vec<egui::epaint::ClippedShape>,
) {
let clipped_meshes = self.egui_ctx.tessellate(shapes);
let dimensions: [u32; 2] = gl_window.window().inner_size().into();
self.painter
.upload_egui_texture(gl, &self.egui_ctx.texture());
self.painter.paint_meshes(
gl_window,
dimensions,
gl,
self.egui_ctx.pixels_per_point(),
clipped_meshes,
&self.egui_ctx.texture(),
);
}

231
egui_glow/src/misc_util.rs Normal file
View file

@ -0,0 +1,231 @@
#![allow(unsafe_code)]
use glow::HasContext;
use std::option::Option::Some;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::JsValue;
pub(crate) fn srgbtexture2d(
gl: &glow::Context,
is_webgl_1: bool,
srgb_support: bool,
data: &[u8],
w: usize,
h: usize,
) -> glow::Texture {
assert_eq!(data.len(), w * h * 4);
assert!(w >= 1);
assert!(h >= 1);
unsafe {
let tex = gl.create_texture().unwrap();
gl.bind_texture(glow::TEXTURE_2D, Some(tex));
gl.tex_parameter_i32(
glow::TEXTURE_2D,
glow::TEXTURE_MAG_FILTER,
glow::LINEAR as i32,
);
gl.tex_parameter_i32(
glow::TEXTURE_2D,
glow::TEXTURE_MIN_FILTER,
glow::LINEAR as i32,
);
gl.tex_parameter_i32(
glow::TEXTURE_2D,
glow::TEXTURE_WRAP_S,
glow::CLAMP_TO_EDGE as i32,
);
gl.tex_parameter_i32(
glow::TEXTURE_2D,
glow::TEXTURE_WRAP_T,
glow::CLAMP_TO_EDGE as i32,
);
if is_webgl_1 {
let format = if srgb_support {
glow::SRGB_ALPHA
} else {
glow::RGBA
};
gl.tex_image_2d(
glow::TEXTURE_2D,
0,
format as i32,
w as i32,
h as i32,
0,
format,
glow::UNSIGNED_BYTE,
Some(data),
);
} else {
gl.tex_storage_2d(glow::TEXTURE_2D, 1, glow::SRGB8_ALPHA8, w as i32, h as i32);
gl.tex_sub_image_2d(
glow::TEXTURE_2D,
0,
0,
0,
w as i32,
h as i32,
glow::RGBA,
glow::UNSIGNED_BYTE,
glow::PixelUnpackData::Slice(data),
);
}
assert_eq!(gl.get_error(), glow::NO_ERROR, "OpenGL error occurred!");
tex
}
}
pub(crate) unsafe fn as_u8_slice<T>(s: &[T]) -> &[u8] {
std::slice::from_raw_parts(s.as_ptr().cast::<u8>(), s.len() * std::mem::size_of::<T>())
}
#[cfg(target_arch = "wasm32")]
pub(crate) fn glow_debug_print(s: impl Into<JsValue>) {
web_sys::console::log_1(&s.into());
}
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn glow_debug_print(s: impl std::fmt::Display) {
println!("{}", s);
}
pub(crate) unsafe fn compile_shader(
gl: &glow::Context,
shader_type: u32,
source: &str,
) -> Result<glow::Shader, String> {
let shader = gl.create_shader(shader_type)?;
gl.shader_source(shader, source);
gl.compile_shader(shader);
if gl.get_shader_compile_status(shader) {
Ok(shader)
} else {
Err(gl.get_shader_info_log(shader))
}
}
pub(crate) unsafe fn link_program<'a, T: IntoIterator<Item = &'a glow::Shader>>(
gl: &glow::Context,
shaders: T,
) -> Result<glow::Program, String> {
let program = gl.create_program()?;
for shader in shaders {
gl.attach_shader(program, *shader);
}
gl.link_program(program);
if gl.get_program_link_status(program) {
Ok(program)
} else {
Err(gl.get_program_info_log(program))
}
}
///Wrapper around Emulated VAO and GL's VAO
pub(crate) enum VAO {
Emulated(crate::vao_emulate::EmulatedVao),
Native(crate::glow::VertexArray),
}
impl VAO {
pub(crate) unsafe fn native(gl: &glow::Context) -> Self {
Self::Native(gl.create_vertex_array().unwrap())
}
pub(crate) unsafe fn emulated() -> Self {
Self::Emulated(crate::vao_emulate::EmulatedVao::new())
}
pub(crate) unsafe fn bind_vertex_array(&self, gl: &glow::Context) {
match self {
VAO::Emulated(vao) => vao.bind_vertex_array(gl),
VAO::Native(vao) => gl.bind_vertex_array(Some(*vao)),
}
}
pub(crate) unsafe fn bind_buffer(&mut self, gl: &glow::Context, buffer: &glow::Buffer) {
match self {
VAO::Emulated(vao) => vao.bind_buffer(buffer),
VAO::Native(_) => gl.bind_buffer(glow::ARRAY_BUFFER, Some(*buffer)),
}
}
pub(crate) unsafe fn add_new_attribute(
&mut self,
gl: &glow::Context,
buffer_info: crate::vao_emulate::BufferInfo,
) {
match self {
VAO::Emulated(vao) => vao.add_new_attribute(buffer_info),
VAO::Native(_) => {
gl.vertex_attrib_pointer_f32(
buffer_info.location,
buffer_info.vector_size,
buffer_info.data_type,
buffer_info.normalized,
buffer_info.stride,
buffer_info.offset,
);
gl.enable_vertex_attrib_array(buffer_info.location);
}
}
}
pub(crate) unsafe fn unbind_vertex_array(&self, gl: &glow::Context) {
match self {
VAO::Emulated(vao) => vao.unbind_vertex_array(gl),
VAO::Native(_) => {
gl.bind_vertex_array(None);
}
}
}
}
pub(crate) unsafe fn need_to_emulate_vao(gl: &glow::Context) -> bool {
let web_sig = "WebGL ";
let es_sig = "OpenGL ES ";
let version_string = gl.get_parameter_string(glow::VERSION);
if let Some(pos) = version_string.rfind(web_sig) {
let version_str = &version_string[pos + web_sig.len()..];
glow_debug_print(format!(
"detected WebGL prefix at {}:{}",
pos + web_sig.len(),
version_str
));
if version_str.contains("1.0") {
//need to test OES_vertex_array_object .
gl.supported_extensions()
.contains("OES_vertex_array_object")
} else {
false
}
} else if let Some(pos) = version_string.rfind(es_sig) {
//glow targets es2.0+ so we don't concern about OpenGL ES-CM,OpenGL ES-CL
glow_debug_print(format!(
"detected OpenGL ES prefix at {}:{}",
pos + es_sig.len(),
&version_string[pos + es_sig.len()..]
));
if version_string.contains("2.0") {
//need to test OES_vertex_array_object .
gl.supported_extensions()
.contains("OES_vertex_array_object")
} else {
false
}
} else {
glow_debug_print(format!("detected OpenGL:{}", version_string));
//from OpenGL 3 vao into core
if version_string.starts_with('2') {
// I found APPLE_vertex_array_object , GL_ATI_vertex_array_object ,ARB_vertex_array_object
// but APPLE's and ATI's very old extension.
gl.supported_extensions()
.contains("ARB_vertex_array_object")
} else {
false
}
}
}

View file

@ -3,262 +3,194 @@
use egui::{
emath::Rect,
epaint::{Color32, Mesh, Vertex},
TextureId,
};
pub use glow::Context;
use memoffset::offset_of;
use std::convert::TryInto;
use glow::HasContext;
use glow::HasContext as _;
use crate::misc_util::{
as_u8_slice, compile_shader, glow_debug_print, link_program, srgbtexture2d,
};
use crate::post_process::PostProcess;
use crate::shader_version::ShaderVersion;
use crate::vao_emulate;
const VERT_SRC: &str = include_str!("shader/vertex.glsl");
const FRAG_SRC: &str = include_str!("shader/fragment.glsl");
fn srgbtexture2d(gl: &glow::Context, data: &[u8], w: usize, h: usize) -> glow::NativeTexture {
assert_eq!(data.len(), w * h * 4);
assert!(w >= 1);
assert!(h >= 1);
unsafe {
let tex = gl.create_texture().unwrap();
gl.bind_texture(glow::TEXTURE_2D, Some(tex));
// The texture coordinates for text are so that both nearest and linear should work with the egui font texture.
// For user textures linear sampling is more likely to be the right choice.
gl.tex_parameter_i32(
glow::TEXTURE_2D,
glow::TEXTURE_MAG_FILTER,
glow::LINEAR as i32,
);
gl.tex_parameter_i32(
glow::TEXTURE_2D,
glow::TEXTURE_WRAP_S,
glow::CLAMP_TO_EDGE as i32,
);
gl.tex_parameter_i32(
glow::TEXTURE_2D,
glow::TEXTURE_WRAP_T,
glow::CLAMP_TO_EDGE as i32,
);
gl.tex_storage_2d(glow::TEXTURE_2D, 1, glow::SRGB8_ALPHA8, w as i32, h as i32);
gl.tex_sub_image_2d(
glow::TEXTURE_2D,
0,
0,
0,
w as i32,
h as i32,
glow::RGBA,
glow::UNSIGNED_BYTE,
glow::PixelUnpackData::Slice(data),
);
assert_eq!(gl.get_error(), glow::NO_ERROR, "OpenGL error occurred!");
tex
}
}
unsafe fn as_u8_slice<T>(s: &[T]) -> &[u8] {
std::slice::from_raw_parts(s.as_ptr().cast::<u8>(), s.len() * std::mem::size_of::<T>())
}
/// OpenGL painter
///
/// This struct must be destroyed with [`Painter::destroy`] before dropping, to ensure OpenGL
/// objects have been properly deleted and are not leaked.
pub struct Painter {
program: glow::NativeProgram,
program: glow::Program,
u_screen_size: glow::UniformLocation,
u_sampler: glow::UniformLocation,
egui_texture: Option<glow::NativeTexture>,
egui_texture: Option<glow::Texture>,
egui_texture_version: Option<u64>,
is_webgl_1: bool,
is_embedded: bool,
vertex_array: crate::misc_util::VAO,
srgb_support: bool,
/// `None` means unallocated (freed) slot.
user_textures: Vec<Option<UserTexture>>,
vertex_array: glow::NativeVertexArray,
vertex_buffer: glow::NativeBuffer,
element_array_buffer: glow::NativeBuffer,
pub(crate) user_textures: Vec<Option<UserTexture>>,
post_process: Option<PostProcess>,
vertex_buffer: glow::Buffer,
element_array_buffer: glow::Buffer,
// Stores outdated OpenGL textures that are yet to be deleted
old_textures: Vec<glow::NativeTexture>,
old_textures: Vec<glow::Texture>,
// Only used in debug builds, to make sure we are destroyed correctly.
destroyed: bool,
}
#[derive(Default)]
struct UserTexture {
pub(crate) struct UserTexture {
/// Pending upload (will be emptied later).
/// This is the format glow likes.
data: Vec<u8>,
size: (usize, usize),
pub(crate) data: Vec<u8>,
pub(crate) size: (usize, usize),
/// Lazily uploaded
gl_texture: Option<glow::NativeTexture>,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[allow(dead_code)]
enum ShaderVersion {
Gl120,
Gl140,
Es100,
Es300,
}
impl ShaderVersion {
fn get(gl: &glow::Context) -> Self {
Self::parse(unsafe { &gl.get_parameter_string(glow::SHADING_LANGUAGE_VERSION) })
}
#[inline]
fn parse(glsl_ver: &str) -> Self {
let start = glsl_ver.find(|c| char::is_ascii_digit(&c)).unwrap();
let es = glsl_ver[..start].contains(" ES ");
let ver = glsl_ver[start..].splitn(2, ' ').next().unwrap();
let [maj, min]: [u8; 2] = ver
.splitn(3, '.')
.take(2)
.map(|x| x.parse().unwrap_or_default())
.collect::<Vec<u8>>()
.try_into()
.unwrap();
if es {
if maj >= 3 {
Self::Es300
} else {
Self::Es100
}
} else if maj > 1 || (maj == 1 && min >= 40) {
Self::Gl140
} else {
Self::Gl120
}
}
fn version(&self) -> &'static str {
match self {
Self::Gl120 => "#version 120\n",
Self::Gl140 => "#version 140\n",
Self::Es100 => "#version 100\n",
Self::Es300 => "#version 300 es\n",
}
}
}
#[test]
fn test_shader_version() {
use ShaderVersion::{Es100, Es300, Gl120, Gl140};
for (s, v) in [
("1.2 OpenGL foo bar", Gl120),
("3.0", Gl140),
("0.0", Gl120),
("OpenGL ES GLSL 3.00 (WebGL2)", Es300),
("OpenGL ES GLSL 1.00 (WebGL)", Es100),
("OpenGL ES GLSL ES 1.00 foo bar", Es100),
("WebGL GLSL ES 3.00 foo bar", Es300),
("WebGL GLSL ES 3.00", Es300),
("WebGL GLSL ES 1.0 foo bar", Es100),
] {
assert_eq!(ShaderVersion::parse(s), v);
}
pub(crate) gl_texture: Option<glow::Texture>,
}
impl Painter {
pub fn new(gl: &glow::Context) -> Painter {
let header = ShaderVersion::get(gl).version();
/// Create painter.
///
/// Set `pp_fb_extent` to the framebuffer size to enable `sRGB` support on OpenGL ES and WebGL.
/// # Errors
/// will return `Err` below cases
/// * failed to compile shader
/// * failed to create postprocess on webgl with `sRGB` support
/// * failed to create buffer
pub fn new(gl: &glow::Context, pp_fb_extent: Option<[i32; 2]>) -> Result<Painter, String> {
let need_to_emulate_vao = unsafe { crate::misc_util::need_to_emulate_vao(gl) };
let shader_version = ShaderVersion::get(gl);
let is_webgl_1 = shader_version == ShaderVersion::Es100;
let header = shader_version.version();
glow_debug_print(header);
let srgb_support = gl.supported_extensions().contains("EXT_sRGB");
let (post_process, srgb_support_define) = match (shader_version, srgb_support) {
//WebGL2 support sRGB default
(ShaderVersion::Es300, _) | (ShaderVersion::Es100, true) => unsafe {
//Add sRGB support marker for fragment shader
if let Some([width, height]) = pp_fb_extent {
glow_debug_print("WebGL with sRGB enabled so turn on post process");
//install post process to correct sRGB color
(
Some(PostProcess::new(
gl,
need_to_emulate_vao,
is_webgl_1,
width,
height,
)?),
"#define SRGB_SUPPORTED",
)
} else {
glow_debug_print("WebGL or OpenGL ES detected but PostProcess disabled because dimension is None");
(None, "")
}
},
//WebGL1 without sRGB support disable postprocess and use fallback shader
(ShaderVersion::Es100, false) => (None, ""),
//OpenGL 2.1 or above always support sRGB so add sRGB support marker
_ => (None, "#define SRGB_SUPPORTED"),
};
unsafe {
let vert = gl.create_shader(glow::VERTEX_SHADER).unwrap();
gl.shader_source(vert, &format!("{}\n{}", header, VERT_SRC));
gl.compile_shader(vert);
if !gl.get_shader_compile_status(vert) {
panic!(
"Failed to compile vertex shader: {}",
gl.get_shader_info_log(vert)
);
}
let frag = gl.create_shader(glow::FRAGMENT_SHADER).unwrap();
gl.shader_source(frag, &format!("{}\n{}", header, FRAG_SRC));
gl.compile_shader(frag);
if !gl.get_shader_compile_status(frag) {
panic!(
"Failed to compile fragment shader: {}",
gl.get_shader_info_log(frag)
);
}
let program = gl.create_program().unwrap();
gl.attach_shader(program, vert);
gl.attach_shader(program, frag);
gl.link_program(program);
if !gl.get_program_link_status(program) {
panic!("{}", gl.get_program_info_log(program));
}
let vert = compile_shader(
gl,
glow::VERTEX_SHADER,
&format!(
"{}\n{}\n{}",
header,
shader_version.is_new_shader_interface(),
VERT_SRC
),
)?;
let frag = compile_shader(
gl,
glow::FRAGMENT_SHADER,
&format!(
"{}\n{}\n{}\n{}",
header,
srgb_support_define,
shader_version.is_new_shader_interface(),
FRAG_SRC
),
)?;
let program = link_program(gl, [vert, frag].iter())?;
gl.detach_shader(program, vert);
gl.detach_shader(program, frag);
gl.delete_shader(vert);
gl.delete_shader(frag);
let u_screen_size = gl.get_uniform_location(program, "u_screen_size").unwrap();
let u_sampler = gl.get_uniform_location(program, "u_sampler").unwrap();
let vertex_array = gl.create_vertex_array().unwrap();
let vertex_buffer = gl.create_buffer().unwrap();
let element_array_buffer = gl.create_buffer().unwrap();
gl.bind_vertex_array(Some(vertex_array));
let vertex_buffer = gl.create_buffer()?;
let element_array_buffer = gl.create_buffer()?;
gl.bind_buffer(glow::ARRAY_BUFFER, Some(vertex_buffer));
let a_pos_loc = gl.get_attrib_location(program, "a_pos").unwrap();
let a_tc_loc = gl.get_attrib_location(program, "a_tc").unwrap();
let a_srgba_loc = gl.get_attrib_location(program, "a_srgba").unwrap();
gl.vertex_attrib_pointer_f32(
a_pos_loc,
2,
glow::FLOAT,
false,
std::mem::size_of::<Vertex>() as i32,
offset_of!(Vertex, pos) as i32,
);
gl.enable_vertex_attrib_array(a_pos_loc);
gl.vertex_attrib_pointer_f32(
a_tc_loc,
2,
glow::FLOAT,
false,
std::mem::size_of::<Vertex>() as i32,
offset_of!(Vertex, uv) as i32,
);
gl.enable_vertex_attrib_array(a_tc_loc);
gl.vertex_attrib_pointer_f32(
a_srgba_loc,
4,
glow::UNSIGNED_BYTE,
false,
std::mem::size_of::<Vertex>() as i32,
offset_of!(Vertex, color) as i32,
);
gl.enable_vertex_attrib_array(a_srgba_loc);
let mut vertex_array = if need_to_emulate_vao {
crate::misc_util::VAO::emulated()
} else {
crate::misc_util::VAO::native(gl)
};
vertex_array.bind_vertex_array(gl);
vertex_array.bind_buffer(gl, &vertex_buffer);
let stride = std::mem::size_of::<Vertex>() as i32;
let position_buffer_info = vao_emulate::BufferInfo {
location: a_pos_loc,
vector_size: 2,
data_type: glow::FLOAT,
normalized: false,
stride,
offset: offset_of!(Vertex, pos) as i32,
};
let tex_coord_buffer_info = vao_emulate::BufferInfo {
location: a_tc_loc,
vector_size: 2,
data_type: glow::FLOAT,
normalized: false,
stride,
offset: offset_of!(Vertex, uv) as i32,
};
let color_buffer_info = vao_emulate::BufferInfo {
location: a_srgba_loc,
vector_size: 4,
data_type: glow::UNSIGNED_BYTE,
normalized: false,
stride,
offset: offset_of!(Vertex, color) as i32,
};
vertex_array.add_new_attribute(gl, position_buffer_info);
vertex_array.add_new_attribute(gl, tex_coord_buffer_info);
vertex_array.add_new_attribute(gl, color_buffer_info);
assert_eq!(gl.get_error(), glow::NO_ERROR, "OpenGL error occurred!");
Painter {
Ok(Painter {
program,
u_screen_size,
u_sampler,
egui_texture: None,
egui_texture_version: None,
user_textures: Default::default(),
is_webgl_1,
is_embedded: matches!(shader_version, ShaderVersion::Es100 | ShaderVersion::Es300),
vertex_array,
srgb_support,
user_textures: Default::default(),
post_process,
vertex_buffer,
element_array_buffer,
old_textures: Vec::new(),
destroyed: false,
}
})
}
}
@ -268,16 +200,26 @@ impl Painter {
if self.egui_texture_version == Some(texture.version) {
return; // No change
}
let gamma = if self.is_embedded && self.post_process.is_none() {
1.0 / 2.2
} else {
1.0
};
let pixels: Vec<u8> = texture
.pixels
.iter()
.flat_map(|a| Vec::from(Color32::from_white_alpha(*a).to_array()))
.srgba_pixels(gamma)
.flat_map(|a| Vec::from(a.to_array()))
.collect();
if let Some(old_tex) = std::mem::replace(
&mut self.egui_texture,
Some(srgbtexture2d(gl, &pixels, texture.width, texture.height)),
Some(srgbtexture2d(
gl,
self.is_webgl_1,
self.srgb_support,
&pixels,
texture.width,
texture.height,
)),
) {
unsafe {
gl.delete_texture(old_tex);
@ -288,7 +230,7 @@ impl Painter {
unsafe fn prepare_painting(
&mut self,
gl_window: &glutin::WindowedContext<glutin::PossiblyCurrent>,
[width_in_pixels, height_in_pixels]: [u32; 2],
gl: &glow::Context,
pixels_per_point: f32,
) -> (u32, u32) {
@ -308,10 +250,6 @@ impl Painter {
glow::ONE,
);
let glutin::dpi::PhysicalSize {
width: width_in_pixels,
height: height_in_pixels,
} = gl_window.window().inner_size();
let width_in_points = width_in_pixels as f32 / pixels_per_point;
let height_in_points = height_in_pixels as f32 / pixels_per_point;
@ -322,9 +260,8 @@ impl Painter {
gl.uniform_2_f32(Some(&self.u_screen_size), width_in_points, height_in_points);
gl.uniform_1_i32(Some(&self.u_sampler), 0);
gl.active_texture(glow::TEXTURE0);
self.vertex_array.bind_vertex_array(gl);
gl.bind_vertex_array(Some(self.vertex_array));
gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.vertex_buffer));
gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(self.element_array_buffer));
(width_in_pixels, height_in_pixels)
@ -341,8 +278,7 @@ impl Painter {
///
/// The scissor area and blend parameters will be changed.
///
/// As well as this, the following objects will be rebound:
/// - Vertex Array
/// As well as this, the following objects will be unset:
/// - Vertex Buffer
/// - Element Buffer
/// - Texture (and active texture will be set to 0)
@ -352,27 +288,33 @@ impl Painter {
/// of the effects your program might have on this code. Look at the source if in doubt.
pub fn paint_meshes(
&mut self,
gl_window: &glutin::WindowedContext<glutin::PossiblyCurrent>,
inner_size: [u32; 2],
gl: &glow::Context,
pixels_per_point: f32,
clipped_meshes: Vec<egui::ClippedMesh>,
egui_texture: &egui::Texture,
) {
//chimera of egui_glow and egui_web
self.assert_not_destroyed();
self.upload_egui_texture(gl, egui_texture);
self.upload_pending_user_textures(gl);
let size_in_pixels = unsafe { self.prepare_painting(gl_window, gl, pixels_per_point) };
if let Some(ref mut post_process) = self.post_process {
unsafe {
post_process.begin(gl, inner_size[0] as i32, inner_size[1] as i32);
}
}
let size_in_pixels = unsafe { self.prepare_painting(inner_size, gl, pixels_per_point) };
for egui::ClippedMesh(clip_rect, mesh) in clipped_meshes {
self.paint_mesh(gl, size_in_pixels, pixels_per_point, clip_rect, &mesh);
}
unsafe {
self.vertex_array.unbind_vertex_array(gl);
gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, None);
assert_eq!(
unsafe { gl.get_error() },
glow::NO_ERROR,
"OpenGL error occurred!"
);
if let Some(ref post_process) = self.post_process {
post_process.end(gl);
}
assert_eq!(glow::NO_ERROR, gl.get_error(), "GL error occurred!");
}
}
#[inline(never)] // Easier profiling
@ -385,15 +327,16 @@ impl Painter {
mesh: &Mesh,
) {
debug_assert!(mesh.is_valid());
if let Some(texture) = self.get_texture(mesh.texture_id) {
unsafe {
gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.vertex_buffer));
gl.buffer_data_u8_slice(
glow::ARRAY_BUFFER,
as_u8_slice(mesh.vertices.as_slice()),
glow::STREAM_DRAW,
);
gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(self.element_array_buffer));
gl.buffer_data_u8_slice(
glow::ELEMENT_ARRAY_BUFFER,
as_u8_slice(mesh.indices.as_slice()),
@ -402,7 +345,6 @@ impl Painter {
gl.bind_texture(glow::TEXTURE_2D, Some(texture));
}
// Transform clip rect to physical pixels:
let clip_min_x = pixels_per_point * clip_rect.min.x;
let clip_min_y = pixels_per_point * clip_rect.min.y;
@ -457,7 +399,8 @@ impl Painter {
/// register glow texture as egui texture
/// Usable for render to image rectangle
pub fn register_glow_texture(&mut self, texture: glow::NativeTexture) -> egui::TextureId {
#[allow(clippy::needless_pass_by_value)]
pub fn register_glow_texture(&mut self, texture: glow::Texture) -> egui::TextureId {
self.assert_not_destroyed();
let id = self.alloc_user_texture();
@ -488,11 +431,10 @@ impl Painter {
pixels: &[Color32],
) {
self.assert_not_destroyed();
assert_eq!(
size.0 * size.1,
pixels.len(),
"Mismatch between texture size and texel count"
"Mismatch between size and texel count"
);
if let egui::TextureId::User(id) = id {
@ -530,7 +472,7 @@ impl Painter {
}
}
pub fn get_texture(&self, texture_id: egui::TextureId) -> Option<glow::NativeTexture> {
pub fn get_texture(&self, texture_id: egui::TextureId) -> Option<glow::Texture> {
self.assert_not_destroyed();
match texture_id {
@ -547,6 +489,8 @@ impl Painter {
let data = std::mem::take(&mut user_texture.data);
user_texture.gl_texture = Some(srgbtexture2d(
gl,
self.is_webgl_1,
self.srgb_support,
&data,
user_texture.size.0,
user_texture.size.1,
@ -571,7 +515,6 @@ impl Painter {
gl.delete_texture(t);
}
}
gl.delete_vertex_array(self.vertex_array);
gl.delete_buffer(self.vertex_buffer);
gl.delete_buffer(self.element_array_buffer);
for t in &self.old_textures {
@ -581,24 +524,39 @@ impl Painter {
/// This function must be called before Painter is dropped, as Painter has some OpenGL objects
/// that should be deleted.
pub fn destroy(&mut self, gl: &glow::Context) {
debug_assert!(!self.destroyed, "Only destroy the egui glow painter once!");
pub fn destroy(&mut self, gl: &glow::Context) {
debug_assert!(!self.destroyed, "Only destroy once!");
unsafe {
self.destroy_gl(gl);
if let Some(ref post_process) = self.post_process {
post_process.destroy(gl);
}
}
self.destroyed = true;
}
fn assert_not_destroyed(&self) {
debug_assert!(
!self.destroyed,
"the egui glow painter has already been destroyed!"
);
debug_assert!(!self.destroyed, "the egui glow has already been destroyed!");
}
}
// ported from egui_web
pub fn clear(gl: &glow::Context, dimension: [u32; 2], clear_color: egui::Rgba) {
unsafe {
gl.disable(glow::SCISSOR_TEST);
gl.viewport(0, 0, dimension[0] as i32, dimension[1] as i32);
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(glow::COLOR_BUFFER_BIT);
}
}
impl Drop for Painter {
fn drop(&mut self) {
debug_assert!(
@ -607,3 +565,39 @@ impl Drop for Painter {
);
}
}
impl epi::TextureAllocator for Painter {
fn alloc_srgba_premultiplied(
&mut self,
size: (usize, usize),
srgba_pixels: &[Color32],
) -> egui::TextureId {
let id = self.alloc_user_texture();
self.set_user_texture(id, size, srgba_pixels);
id
}
fn free(&mut self, id: egui::TextureId) {
self.free_user_texture(id);
}
}
impl epi::NativeTexture for Painter {
type Texture = glow::Texture;
fn register_native_texture(&mut self, native: Self::Texture) -> TextureId {
self.register_glow_texture(native)
}
fn replace_native_texture(&mut self, id: TextureId, replacing: Self::Texture) {
if let egui::TextureId::User(id) = id {
if let Some(Some(user_texture)) = self.user_textures.get_mut(id as usize) {
*user_texture = UserTexture {
data: vec![],
gl_texture: Some(replacing),
size: (0, 0),
};
}
}
}
}

View file

@ -0,0 +1,222 @@
#![allow(unsafe_code)]
use crate::misc_util::{compile_shader, link_program};
use crate::vao_emulate::BufferInfo;
use glow::HasContext;
/// Uses a framebuffer to render everything in linear color space and convert it back to `sRGB`
/// in a separate "post processing" step
pub(crate) struct PostProcess {
pos_buffer: glow::Buffer,
index_buffer: glow::Buffer,
vertex_array: crate::misc_util::VAO,
is_webgl_1: bool,
texture: glow::Texture,
texture_size: (i32, i32),
fbo: glow::Framebuffer,
program: glow::Program,
}
impl PostProcess {
pub(crate) unsafe fn new(
gl: &glow::Context,
need_to_emulate_vao: bool,
is_webgl_1: bool,
width: i32,
height: i32,
) -> Result<PostProcess, String> {
let fbo = gl.create_framebuffer()?;
gl.bind_framebuffer(glow::FRAMEBUFFER, Some(fbo));
let texture = gl.create_texture().unwrap();
gl.bind_texture(glow::TEXTURE_2D, Some(texture));
gl.tex_parameter_i32(
glow::TEXTURE_2D,
glow::TEXTURE_WRAP_S,
glow::CLAMP_TO_EDGE as i32,
);
gl.tex_parameter_i32(
glow::TEXTURE_2D,
glow::TEXTURE_WRAP_T,
glow::CLAMP_TO_EDGE as i32,
);
gl.tex_parameter_i32(
glow::TEXTURE_2D,
glow::TEXTURE_MIN_FILTER,
glow::NEAREST as i32,
);
gl.tex_parameter_i32(
glow::TEXTURE_2D,
glow::TEXTURE_MAG_FILTER,
glow::NEAREST as i32,
);
gl.pixel_store_i32(glow::UNPACK_ALIGNMENT, 1);
let (internal_format, format) = if is_webgl_1 {
(glow::SRGB_ALPHA, glow::SRGB_ALPHA)
} else {
(glow::SRGB8_ALPHA8, glow::RGBA)
};
gl.tex_image_2d(
glow::TEXTURE_2D,
0,
internal_format as i32,
width,
height,
0,
format,
glow::UNSIGNED_BYTE,
None,
);
let error_code = gl.get_error();
assert_eq!(
error_code,
glow::NO_ERROR,
"Error occurred in post process texture initialization. code : 0x{:x}",
error_code
);
gl.framebuffer_texture_2d(
glow::FRAMEBUFFER,
glow::COLOR_ATTACHMENT0,
glow::TEXTURE_2D,
Some(texture),
0,
);
gl.bind_texture(glow::TEXTURE_2D, None);
gl.bind_framebuffer(glow::FRAMEBUFFER, None);
let vert_shader = compile_shader(
gl,
glow::VERTEX_SHADER,
include_str!("shader/post_vertex_100es.glsl"),
)?;
let frag_shader = compile_shader(
gl,
glow::FRAGMENT_SHADER,
include_str!("shader/post_fragment_100es.glsl"),
)?;
let program = link_program(gl, [vert_shader, frag_shader].iter())?;
let positions = vec![0.0f32, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0];
let indices = vec![0u8, 1, 2, 1, 2, 3];
let pos_buffer = gl.create_buffer()?;
gl.bind_buffer(glow::ARRAY_BUFFER, Some(pos_buffer));
gl.buffer_data_u8_slice(
glow::ARRAY_BUFFER,
crate::misc_util::as_u8_slice(&positions),
glow::STATIC_DRAW,
);
let a_pos_loc = gl
.get_attrib_location(program, "a_pos")
.ok_or_else(|| "failed to get location of a_pos".to_string())?;
let mut vertex_array = if need_to_emulate_vao {
crate::misc_util::VAO::emulated()
} else {
crate::misc_util::VAO::native(gl)
};
vertex_array.bind_vertex_array(gl);
vertex_array.bind_buffer(gl, &pos_buffer);
let buffer_info_a_pos = BufferInfo {
location: a_pos_loc,
vector_size: 2,
data_type: glow::FLOAT,
normalized: false,
stride: 0,
offset: 0,
};
vertex_array.add_new_attribute(gl, buffer_info_a_pos);
let index_buffer = gl.create_buffer()?;
gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(index_buffer));
gl.buffer_data_u8_slice(glow::ELEMENT_ARRAY_BUFFER, &indices, glow::STATIC_DRAW);
gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, None);
let error_code = gl.get_error();
assert_eq!(
error_code,
glow::NO_ERROR,
"Error occurred in post process initialization. code : 0x{:x}",
error_code
);
Ok(PostProcess {
pos_buffer,
index_buffer,
vertex_array,
is_webgl_1,
texture,
texture_size: (width, height),
fbo,
program,
})
}
pub(crate) unsafe fn begin(&mut self, gl: &glow::Context, width: i32, height: i32) {
if (width, height) != self.texture_size {
gl.bind_texture(glow::TEXTURE_2D, Some(self.texture));
gl.pixel_store_i32(glow::UNPACK_ALIGNMENT, 1);
let (internal_format, format) = if self.is_webgl_1 {
(glow::SRGB_ALPHA, glow::SRGB_ALPHA)
} else {
(glow::SRGB8_ALPHA8, glow::RGBA)
};
gl.tex_image_2d(
glow::TEXTURE_2D,
0,
internal_format as i32,
width,
height,
0,
format,
glow::UNSIGNED_BYTE,
None,
);
gl.bind_texture(glow::TEXTURE_2D, None);
self.texture_size = (width, height);
}
gl.bind_framebuffer(glow::FRAMEBUFFER, Some(self.fbo));
gl.clear_color(0.0, 0.0, 0.0, 0.0);
gl.clear(glow::COLOR_BUFFER_BIT);
}
pub(crate) unsafe fn end(&self, gl: &glow::Context) {
gl.bind_framebuffer(glow::FRAMEBUFFER, None);
gl.disable(glow::SCISSOR_TEST);
gl.use_program(Some(self.program));
gl.active_texture(glow::TEXTURE0);
gl.bind_texture(glow::TEXTURE_2D, Some(self.texture));
let u_sampler_loc = gl.get_uniform_location(self.program, "u_sampler").unwrap();
gl.uniform_1_i32(Some(&u_sampler_loc), 0);
self.vertex_array.bind_vertex_array(gl);
gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(self.index_buffer));
gl.draw_elements(glow::TRIANGLES, 6, glow::UNSIGNED_BYTE, 0);
self.vertex_array.unbind_vertex_array(gl);
gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, None);
gl.bind_texture(glow::TEXTURE_2D, None);
gl.use_program(None);
}
pub(crate) unsafe fn destroy(&self, gl: &glow::Context) {
gl.delete_buffer(self.pos_buffer);
gl.delete_buffer(self.index_buffer);
gl.delete_program(self.program);
gl.delete_framebuffer(self.fbo);
gl.delete_texture(self.texture);
}
}

View file

@ -3,71 +3,75 @@ precision mediump float;
#endif
uniform sampler2D u_sampler;
#if defined(GL_ES) || __VERSION__ < 140
varying vec4 v_rgba;
varying vec2 v_tc;
#else
#ifdef NEW_SHADER_INTERFACE
in vec4 v_rgba;
in vec2 v_tc;
out vec4 f_color;
// a dirty hack applied to support webGL2
#define gl_FragColor f_color
#define texture2D texture
#else
varying vec4 v_rgba;
varying vec2 v_tc;
#endif
#ifdef GL_ES
#ifndef SRGB_SUPPORTED
// 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));
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));
}
vec4 srgba_from_linear(vec4 rgba) {
return vec4(srgb_from_linear(rgba.rgb), 255.0 * rgba.a);
return vec4(srgb_from_linear(rgba.rgb), 255.0 * rgba.a);
}
#if __VERSION__ < 300
// 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));
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));
}
vec4 linear_from_srgba(vec4 srgba) {
return vec4(linear_from_srgb(srgba.rgb), srgba.a / 255.0);
return vec4(linear_from_srgb(srgba.rgb), srgba.a / 255.0);
}
#endif
#endif
#ifdef GL_ES
void main() {
#if __VERSION__ < 300
// We must decode the colors, since WebGL doesn't come with sRGBA textures:
vec4 texture_rgba = linear_from_srgba(texture2D(u_sampler, v_tc) * 255.0);
#else
// The texture is set up with `SRGB8_ALPHA8`, so no need to decode here!
vec4 texture_rgba = texture2D(u_sampler, v_tc);
#endif
// We must decode the colors, since WebGL 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;
/// 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:
// We must gamma-encode again since WebGL doesn't support linear blending in the framebuffer.
gl_FragColor = srgba_from_linear(v_rgba * texture_rgba) / 255.0;
// Unmultiply alpha:
if (gl_FragColor.a > 0.0) {
gl_FragColor.rgb /= gl_FragColor.a;
}
// WebGL doesn't support linear blending in the framebuffer,
// so we apply this hack to at least get a bit closer to the desired blending:
gl_FragColor.a = pow(gl_FragColor.a, 1.6); // Empiric nonsense
// 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;
}
}
#else
#else
void main() {
// The texture sampler is sRGB aware, and OpenGL already expects linear rgba output
// so no need for any sRGB conversions here:
#if __VERSION__ < 140
gl_FragColor = v_rgba * texture2D(u_sampler, v_tc);
#else
f_color = v_rgba * texture(u_sampler, v_tc);
#endif
// The texture sampler is sRGB aware, and OpenGL already expects linear rgba output
// so no need for any sRGB conversions here:
gl_FragColor = v_rgba * texture2D(u_sampler, v_tc);
}
#endif
#endif

View file

@ -0,0 +1,22 @@
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.;
}

View file

@ -0,0 +1,8 @@
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;
}

View file

@ -1,4 +1,4 @@
#if !defined(GL_ES) && __VERSION__ >= 140
#ifdef NEW_SHADER_INTERFACE
#define I in
#define O out
#define V(x) x

View file

@ -0,0 +1,79 @@
#![allow(unsafe_code)]
use crate::misc_util::glow_debug_print;
use glow::HasContext;
use std::convert::TryInto;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[allow(dead_code)]
pub(crate) enum ShaderVersion {
Gl120,
Gl140,
Es100,
Es300,
}
impl ShaderVersion {
pub(crate) fn get(gl: &glow::Context) -> Self {
let shading_lang = unsafe { gl.get_parameter_string(glow::SHADING_LANGUAGE_VERSION) };
glow_debug_print(&shading_lang);
Self::parse(&shading_lang)
}
#[inline]
pub(crate) fn parse(glsl_ver: &str) -> Self {
let start = glsl_ver.find(|c| char::is_ascii_digit(&c)).unwrap();
let es = glsl_ver[..start].contains(" ES ");
let ver = glsl_ver[start..].splitn(2, ' ').next().unwrap();
let [maj, min]: [u8; 2] = ver
.splitn(3, '.')
.take(2)
.map(|x| x.parse().unwrap_or_default())
.collect::<Vec<u8>>()
.try_into()
.unwrap();
if es {
if maj >= 3 {
Self::Es300
} else {
Self::Es100
}
} else if maj > 1 || (maj == 1 && min >= 40) {
Self::Gl140
} else {
Self::Gl120
}
}
pub(crate) fn version(&self) -> &'static str {
match self {
Self::Gl120 => "#version 120\n",
Self::Gl140 => "#version 140\n",
Self::Es100 => "#version 100\n",
Self::Es300 => "#version 300 es\n",
}
}
pub(crate) fn is_new_shader_interface(&self) -> &'static str {
match self {
ShaderVersion::Es300 | ShaderVersion::Gl140 => "#define NEW_SHADER_INTERFACE\n",
_ => "",
}
}
}
#[test]
fn test_shader_version() {
use ShaderVersion::{Es100, Es300, Gl120, Gl140};
for (s, v) in [
("1.2 OpenGL foo bar", Gl120),
("3.0", Gl140),
("0.0", Gl120),
("OpenGL ES GLSL 3.00 (WebGL2)", Es300),
("OpenGL ES GLSL 1.00 (WebGL)", Es100),
("OpenGL ES GLSL ES 1.00 foo bar", Es100),
("WebGL GLSL ES 3.00 foo bar", Es300),
("WebGL GLSL ES 3.00", Es300),
("WebGL GLSL ES 1.0 foo bar", Es100),
] {
assert_eq!(ShaderVersion::parse(s), v);
}
}

View file

@ -0,0 +1,57 @@
#![allow(unsafe_code)]
use glow::HasContext;
pub(crate) struct BufferInfo {
pub location: u32, //
pub vector_size: i32,
pub data_type: u32, //GL_FLOAT,GL_UNSIGNED_BYTE
pub normalized: bool,
pub stride: i32,
pub offset: i32,
}
pub struct EmulatedVao {
buffer: Option<glow::Buffer>,
buffer_infos: Vec<BufferInfo>,
}
impl EmulatedVao {
pub(crate) fn new() -> Self {
Self {
buffer: None,
buffer_infos: vec![],
}
}
pub(crate) fn bind_buffer(&mut self, buffer: &glow::Buffer) {
let _old = self.buffer.replace(*buffer);
}
pub(crate) fn add_new_attribute(&mut self, buffer_info: BufferInfo) {
self.buffer_infos.push(buffer_info);
}
pub(crate) fn bind_vertex_array(&self, gl: &glow::Context) {
unsafe {
gl.bind_buffer(glow::ARRAY_BUFFER, self.buffer);
}
for attribute in self.buffer_infos.iter() {
unsafe {
gl.vertex_attrib_pointer_f32(
attribute.location,
attribute.vector_size,
attribute.data_type,
attribute.normalized,
attribute.stride,
attribute.offset,
);
gl.enable_vertex_attrib_array(attribute.location);
}
}
}
pub(crate) fn unbind_vertex_array(&self, gl: &glow::Context) {
for attribute in self.buffer_infos.iter() {
unsafe {
gl.disable_vertex_attrib_array(attribute.location);
}
}
unsafe {
gl.bind_buffer(glow::ARRAY_BUFFER, None);
}
}
}

View file

@ -4,7 +4,7 @@ All notable changes to the `egui_web` integration will be noted in this file.
## Unreleased
*Add feature `glow` to switch to a [`glow`](https://github.com/grovesNL/glow) based painter ([#868](https://github.com/emilk/egui/pull/868)).
## 0.15.0 - 2021-10-24
### Added

View file

@ -28,6 +28,7 @@ crate-type = ["cdylib", "rlib"]
egui = { version = "0.15.0", path = "../egui", default-features = false, features = [
"single_threaded",
] }
egui_glow = { version = "0.15.0",path = "../egui_glow", default-features = false, optional = true }
epi = { version = "0.15.0", path = "../epi" }
js-sys = "0.3"
ron = { version = "0.7", optional = true }
@ -44,6 +45,7 @@ default = ["default_fonts"]
default_fonts = ["egui/default_fonts"]
persistence = ["egui/persistence", "ron", "serde"]
screen_reader = ["tts"] # experimental
glow =["egui_glow","egui_glow/epi"]
[dependencies.web-sys]
version = "0.3.52"

View file

@ -5,6 +5,11 @@ pub use egui::{pos2, Color32};
// ----------------------------------------------------------------------------
fn create_painter(canvas_id: &str) -> Result<Box<dyn Painter>, JsValue> {
#[cfg(feature = "glow")]
return Ok(Box::new(crate::glow_wrapping::WrappedGlowPainter::new(
canvas_id,
)));
#[cfg(not(feature = "glow"))]
if let Ok(webgl2_painter) = webgl2::WebGl2Painter::new(canvas_id) {
console_log("Using WebGL2 backend");
Ok(Box::new(webgl2_painter))
@ -167,7 +172,7 @@ impl AppRunner {
fn integration_info(&self) -> epi::IntegrationInfo {
epi::IntegrationInfo {
name: "egui_web",
name: self.painter.name(),
web_info: Some(epi::WebInfo {
web_location_hash: location_hash().unwrap_or_default(),
}),

View file

@ -0,0 +1,130 @@
use crate::web_sys::{WebGl2RenderingContext, WebGlRenderingContext};
use crate::{canvas_element_or_die, console_error};
use egui::{ClippedMesh, Rgba, Texture};
use egui_glow::glow;
use epi::TextureAllocator;
use wasm_bindgen::JsValue;
use web_sys::HtmlCanvasElement;
pub(crate) struct WrappedGlowPainter {
pub(crate) gl_ctx: glow::Context,
pub(crate) canvas: HtmlCanvasElement,
pub(crate) canvas_id: String,
pub(crate) painter: egui_glow::Painter,
}
impl WrappedGlowPainter {
pub fn new(canvas_id: &str) -> Self {
let canvas = canvas_element_or_die(canvas_id);
let gl_ctx = init_glow_context_from_canvas(&canvas);
let dimension = [canvas.width() as i32, canvas.height() as i32];
let painter = egui_glow::Painter::new(&gl_ctx, Some(dimension))
.map_err(|error| {
console_error(format!(
"some error occurred in initializing glow painter\n {}",
error
))
})
.unwrap();
Self {
gl_ctx,
canvas,
canvas_id: canvas_id.to_owned(),
painter,
}
}
}
impl crate::Painter for WrappedGlowPainter {
fn as_tex_allocator(&mut self) -> &mut dyn TextureAllocator {
&mut self.painter
}
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 upload_egui_texture(&mut self, texture: &Texture) {
self.painter.upload_egui_texture(&self.gl_ctx, texture)
}
fn clear(&mut self, clear_color: Rgba) {
let canvas_dimension = [self.canvas.width(), self.canvas.height()];
egui_glow::painter::clear(&self.gl_ctx, canvas_dimension, clear_color)
}
fn paint_meshes(
&mut self,
clipped_meshes: Vec<ClippedMesh>,
pixels_per_point: f32,
) -> Result<(), JsValue> {
let canvas_dimension = [self.canvas.width(), self.canvas.height()];
self.painter.paint_meshes(
canvas_dimension,
&self.gl_ctx,
pixels_per_point,
clipped_meshes,
);
Ok(())
}
fn name(&self) -> &'static str {
"egui_web(glow)"
}
}
pub fn init_glow_context_from_canvas(canvas: &HtmlCanvasElement) -> glow::Context {
use wasm_bindgen::JsCast;
let ctx = canvas.get_context("webgl2");
if let Ok(ctx) = ctx {
crate::console_log("webgl found");
if let Some(ctx) = ctx {
crate::console_log("webgl 2 selected");
let gl_ctx = ctx.dyn_into::<web_sys::WebGl2RenderingContext>().unwrap();
glow::Context::from_webgl2_context(gl_ctx)
} else {
let ctx = canvas.get_context("webgl");
if let Ok(ctx) = ctx {
crate::console_log("falling back to webgl1");
if let Some(ctx) = ctx {
crate::console_log("webgl1 selected");
let gl_ctx = ctx.dyn_into::<web_sys::WebGlRenderingContext>().unwrap();
crate::console_log("success");
glow::Context::from_webgl1_context(gl_ctx)
} else {
panic!("tried webgl1 but can't get context");
}
} else {
panic!("tried webgl1 but can't get context");
}
}
} else {
panic!("tried webgl2 but something went wrong");
}
}
trait DummyWebGLConstructor {
fn from_webgl1_context(context: web_sys::WebGlRenderingContext) -> Self;
fn from_webgl2_context(context: web_sys::WebGl2RenderingContext) -> Self;
}
#[cfg(not(target_arch = "wasm32"))]
impl DummyWebGLConstructor for glow::Context {
fn from_webgl1_context(_context: WebGlRenderingContext) -> Self {
panic!("you cant use egui_web(glow) on native")
}
fn from_webgl2_context(_context: WebGl2RenderingContext) -> Self {
panic!("you cant use egui_web(glow) on native")
}
}

View file

@ -15,6 +15,8 @@
#![warn(clippy::all, missing_crate_level_docs, rust_2018_idioms)]
pub mod backend;
#[cfg(feature = "glow")]
mod glow_wrapping;
mod painter;
pub mod screen_reader;
pub mod webgl1;

View file

@ -17,4 +17,6 @@ pub trait Painter {
clipped_meshes: Vec<egui::ClippedMesh>,
pixels_per_point: f32,
) -> Result<(), JsValue>;
fn name(&self) -> &'static str;
}

View file

@ -538,6 +538,10 @@ impl crate::Painter for WebGlPainter {
Ok(())
}
fn name(&self) -> &'static str {
"egui_web(webgl1)"
}
}
struct PostProcess {

View file

@ -516,6 +516,10 @@ impl crate::Painter for WebGl2Painter {
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