Skip to content

Commit

Permalink
Make egui_glow painter to work on web (#868)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
KentaTheBugMaker authored Nov 3, 2021
1 parent 1dbe608 commit 804722a
Show file tree
Hide file tree
Showing 21 changed files with 1,093 additions and 318 deletions.
1 change: 1 addition & 0 deletions egui_glow/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)).


Expand Down
19 changes: 15 additions & 4 deletions egui_glow/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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"]
26 changes: 5 additions & 21 deletions egui_glow/src/epi_backend.rs
Original file line number Diff line number Diff line change
@@ -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>>);
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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();
Expand Down
29 changes: 21 additions & 8 deletions egui_glow/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>,
Expand All @@ -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(),
}
}

Expand Down Expand Up @@ -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(),
);
}

Expand Down
231 changes: 231 additions & 0 deletions egui_glow/src/misc_util.rs
Original file line number Diff line number Diff line change
@@ -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
}
}
}
Loading

0 comments on commit 804722a

Please sign in to comment.