Skip to content

Commit 804722a

Browse files
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
1 parent 1dbe608 commit 804722a

21 files changed

+1093
-318
lines changed

egui_glow/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ All notable changes to the `egui_glow` integration will be noted in this file.
33

44

55
## Unreleased
6+
* Make winit/glutin an optional dependency ([#868](https://github.com/emilk/egui/pull/868)).
67
* Simplify `EguiGlow` interface ([#871](https://github.com/emilk/egui/pull/871)).
78

89

egui_glow/Cargo.toml

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,24 @@ all-features = true
2323

2424
[dependencies]
2525
egui = { version = "0.15.0", path = "../egui", default-features = false, features = ["single_threaded"] }
26-
egui-winit = { version = "0.15.0", path = "../egui-winit", default-features = false, features = ["epi"] }
27-
epi = { version = "0.15.0", path = "../epi", optional = true }
2826

27+
epi = { version = "0.15.0", path = "../epi", optional = true }
2928
glow = "0.11"
30-
glutin = "0.27"
3129
memoffset = "0.6"
3230

31+
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
32+
egui-winit = { version = "0.15.0", path = "../egui-winit", default-features = false, features = ["epi"], optional = true }
33+
glutin = { version = "0.27.0", optional = true }
34+
35+
[target.'cfg(target_arch = "wasm32")'.dependencies]
36+
web-sys = { version = "0.3", features=["console"] }
37+
wasm-bindgen = { version = "0.2" }
38+
3339
[dev-dependencies]
3440
image = { version = "0.23", default-features = false, features = ["png"] }
3541

3642
[features]
37-
default = ["clipboard", "default_fonts", "links", "persistence"]
43+
default = ["clipboard", "default_fonts", "links", "persistence", "winit"]
3844

3945
# enable cut/copy/paste to OS clipboard.
4046
# if disabled a clipboard will be simulated so you can still copy/paste within the egui app.
@@ -58,3 +64,8 @@ persistence = [
5864

5965
# experimental support for a screen reader
6066
screen_reader = ["egui-winit/screen_reader"]
67+
68+
# enable glutin/winit integration.
69+
# if you want to use glow painter on web disable it.
70+
# if disabled reduce crate size and build time.
71+
winit= ["egui-winit","glutin"]

egui_glow/src/epi_backend.rs

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,7 @@
11
use crate::*;
2-
use egui::Color32;
32
#[cfg(target_os = "windows")]
43
use glutin::platform::windows::WindowBuilderExtWindows;
54

6-
impl epi::TextureAllocator for Painter {
7-
fn alloc_srgba_premultiplied(
8-
&mut self,
9-
size: (usize, usize),
10-
srgba_pixels: &[Color32],
11-
) -> egui::TextureId {
12-
let id = self.alloc_user_texture();
13-
self.set_user_texture(id, size, srgba_pixels);
14-
id
15-
}
16-
17-
fn free(&mut self, id: egui::TextureId) {
18-
self.free_user_texture(id);
19-
}
20-
}
21-
225
struct RequestRepaintEvent;
236

247
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) -> ! {
7760
event_loop.create_proxy(),
7861
)));
7962

80-
let mut painter = crate::Painter::new(&gl);
63+
let mut painter = crate::Painter::new(&gl, None)
64+
.map_err(|error| eprintln!("some OpenGL error occurred {}\n", error))
65+
.unwrap();
8166
let mut integration = egui_winit::epi::EpiIntegration::new(
8267
"egui_glow",
8368
gl_window.window(),
@@ -111,13 +96,12 @@ pub fn run(app: Box<dyn epi::App>, native_options: &epi::NativeOptions) -> ! {
11196
gl.clear_color(color[0], color[1], color[2], color[3]);
11297
gl.clear(glow::COLOR_BUFFER_BIT);
11398
}
114-
99+
painter.upload_egui_texture(&gl, &integration.egui_ctx.texture());
115100
painter.paint_meshes(
116-
&gl_window,
101+
gl_window.window().inner_size().into(),
117102
&gl,
118103
integration.egui_ctx.pixels_per_point(),
119104
clipped_meshes,
120-
&integration.egui_ctx.texture(),
121105
);
122106

123107
gl_window.swap_buffers().unwrap();

egui_glow/src/lib.rs

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -87,25 +87,32 @@
8787
#![allow(clippy::float_cmp)]
8888
#![allow(clippy::manual_range_contains)]
8989

90-
mod painter;
90+
pub mod painter;
91+
pub use glow;
9192
pub use painter::Painter;
92-
93-
#[cfg(feature = "epi")]
93+
#[cfg(feature = "winit")]
9494
mod epi_backend;
95-
#[cfg(feature = "epi")]
96-
pub use epi_backend::{run, NativeOptions};
95+
mod misc_util;
96+
mod post_process;
97+
mod shader_version;
98+
mod vao_emulate;
9799

100+
#[cfg(not(target_arch = "wasm32"))]
98101
pub use egui_winit;
102+
#[cfg(all(feature = "epi", feature = "winit"))]
103+
pub use epi_backend::{run, NativeOptions};
99104

100105
// ----------------------------------------------------------------------------
101106

102107
/// Use [`egui`] from a [`glow`] app.
108+
#[cfg(feature = "winit")]
103109
pub struct EguiGlow {
104110
pub egui_ctx: egui::CtxRef,
105111
pub egui_winit: egui_winit::State,
106112
pub painter: crate::Painter,
107113
}
108114

115+
#[cfg(feature = "winit")]
109116
impl EguiGlow {
110117
pub fn new(
111118
gl_window: &glutin::WindowedContext<glutin::PossiblyCurrent>,
@@ -114,7 +121,11 @@ impl EguiGlow {
114121
Self {
115122
egui_ctx: Default::default(),
116123
egui_winit: egui_winit::State::new(gl_window.window()),
117-
painter: crate::Painter::new(gl),
124+
painter: crate::Painter::new(gl, None)
125+
.map_err(|error| {
126+
eprintln!("some error occurred in initializing painter\n{}", error);
127+
})
128+
.unwrap(),
118129
}
119130
}
120131

@@ -158,12 +169,14 @@ impl EguiGlow {
158169
shapes: Vec<egui::epaint::ClippedShape>,
159170
) {
160171
let clipped_meshes = self.egui_ctx.tessellate(shapes);
172+
let dimensions: [u32; 2] = gl_window.window().inner_size().into();
173+
self.painter
174+
.upload_egui_texture(gl, &self.egui_ctx.texture());
161175
self.painter.paint_meshes(
162-
gl_window,
176+
dimensions,
163177
gl,
164178
self.egui_ctx.pixels_per_point(),
165179
clipped_meshes,
166-
&self.egui_ctx.texture(),
167180
);
168181
}
169182

egui_glow/src/misc_util.rs

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
#![allow(unsafe_code)]
2+
use glow::HasContext;
3+
use std::option::Option::Some;
4+
#[cfg(target_arch = "wasm32")]
5+
use wasm_bindgen::JsValue;
6+
7+
pub(crate) fn srgbtexture2d(
8+
gl: &glow::Context,
9+
is_webgl_1: bool,
10+
srgb_support: bool,
11+
data: &[u8],
12+
w: usize,
13+
h: usize,
14+
) -> glow::Texture {
15+
assert_eq!(data.len(), w * h * 4);
16+
assert!(w >= 1);
17+
assert!(h >= 1);
18+
unsafe {
19+
let tex = gl.create_texture().unwrap();
20+
gl.bind_texture(glow::TEXTURE_2D, Some(tex));
21+
22+
gl.tex_parameter_i32(
23+
glow::TEXTURE_2D,
24+
glow::TEXTURE_MAG_FILTER,
25+
glow::LINEAR as i32,
26+
);
27+
gl.tex_parameter_i32(
28+
glow::TEXTURE_2D,
29+
glow::TEXTURE_MIN_FILTER,
30+
glow::LINEAR as i32,
31+
);
32+
gl.tex_parameter_i32(
33+
glow::TEXTURE_2D,
34+
glow::TEXTURE_WRAP_S,
35+
glow::CLAMP_TO_EDGE as i32,
36+
);
37+
gl.tex_parameter_i32(
38+
glow::TEXTURE_2D,
39+
glow::TEXTURE_WRAP_T,
40+
glow::CLAMP_TO_EDGE as i32,
41+
);
42+
if is_webgl_1 {
43+
let format = if srgb_support {
44+
glow::SRGB_ALPHA
45+
} else {
46+
glow::RGBA
47+
};
48+
gl.tex_image_2d(
49+
glow::TEXTURE_2D,
50+
0,
51+
format as i32,
52+
w as i32,
53+
h as i32,
54+
0,
55+
format,
56+
glow::UNSIGNED_BYTE,
57+
Some(data),
58+
);
59+
} else {
60+
gl.tex_storage_2d(glow::TEXTURE_2D, 1, glow::SRGB8_ALPHA8, w as i32, h as i32);
61+
gl.tex_sub_image_2d(
62+
glow::TEXTURE_2D,
63+
0,
64+
0,
65+
0,
66+
w as i32,
67+
h as i32,
68+
glow::RGBA,
69+
glow::UNSIGNED_BYTE,
70+
glow::PixelUnpackData::Slice(data),
71+
);
72+
}
73+
assert_eq!(gl.get_error(), glow::NO_ERROR, "OpenGL error occurred!");
74+
tex
75+
}
76+
}
77+
78+
pub(crate) unsafe fn as_u8_slice<T>(s: &[T]) -> &[u8] {
79+
std::slice::from_raw_parts(s.as_ptr().cast::<u8>(), s.len() * std::mem::size_of::<T>())
80+
}
81+
82+
#[cfg(target_arch = "wasm32")]
83+
pub(crate) fn glow_debug_print(s: impl Into<JsValue>) {
84+
web_sys::console::log_1(&s.into());
85+
}
86+
#[cfg(not(target_arch = "wasm32"))]
87+
pub(crate) fn glow_debug_print(s: impl std::fmt::Display) {
88+
println!("{}", s);
89+
}
90+
91+
pub(crate) unsafe fn compile_shader(
92+
gl: &glow::Context,
93+
shader_type: u32,
94+
source: &str,
95+
) -> Result<glow::Shader, String> {
96+
let shader = gl.create_shader(shader_type)?;
97+
98+
gl.shader_source(shader, source);
99+
100+
gl.compile_shader(shader);
101+
102+
if gl.get_shader_compile_status(shader) {
103+
Ok(shader)
104+
} else {
105+
Err(gl.get_shader_info_log(shader))
106+
}
107+
}
108+
109+
pub(crate) unsafe fn link_program<'a, T: IntoIterator<Item = &'a glow::Shader>>(
110+
gl: &glow::Context,
111+
shaders: T,
112+
) -> Result<glow::Program, String> {
113+
let program = gl.create_program()?;
114+
115+
for shader in shaders {
116+
gl.attach_shader(program, *shader);
117+
}
118+
119+
gl.link_program(program);
120+
121+
if gl.get_program_link_status(program) {
122+
Ok(program)
123+
} else {
124+
Err(gl.get_program_info_log(program))
125+
}
126+
}
127+
///Wrapper around Emulated VAO and GL's VAO
128+
pub(crate) enum VAO {
129+
Emulated(crate::vao_emulate::EmulatedVao),
130+
Native(crate::glow::VertexArray),
131+
}
132+
133+
impl VAO {
134+
pub(crate) unsafe fn native(gl: &glow::Context) -> Self {
135+
Self::Native(gl.create_vertex_array().unwrap())
136+
}
137+
138+
pub(crate) unsafe fn emulated() -> Self {
139+
Self::Emulated(crate::vao_emulate::EmulatedVao::new())
140+
}
141+
142+
pub(crate) unsafe fn bind_vertex_array(&self, gl: &glow::Context) {
143+
match self {
144+
VAO::Emulated(vao) => vao.bind_vertex_array(gl),
145+
VAO::Native(vao) => gl.bind_vertex_array(Some(*vao)),
146+
}
147+
}
148+
149+
pub(crate) unsafe fn bind_buffer(&mut self, gl: &glow::Context, buffer: &glow::Buffer) {
150+
match self {
151+
VAO::Emulated(vao) => vao.bind_buffer(buffer),
152+
VAO::Native(_) => gl.bind_buffer(glow::ARRAY_BUFFER, Some(*buffer)),
153+
}
154+
}
155+
156+
pub(crate) unsafe fn add_new_attribute(
157+
&mut self,
158+
gl: &glow::Context,
159+
buffer_info: crate::vao_emulate::BufferInfo,
160+
) {
161+
match self {
162+
VAO::Emulated(vao) => vao.add_new_attribute(buffer_info),
163+
VAO::Native(_) => {
164+
gl.vertex_attrib_pointer_f32(
165+
buffer_info.location,
166+
buffer_info.vector_size,
167+
buffer_info.data_type,
168+
buffer_info.normalized,
169+
buffer_info.stride,
170+
buffer_info.offset,
171+
);
172+
gl.enable_vertex_attrib_array(buffer_info.location);
173+
}
174+
}
175+
}
176+
177+
pub(crate) unsafe fn unbind_vertex_array(&self, gl: &glow::Context) {
178+
match self {
179+
VAO::Emulated(vao) => vao.unbind_vertex_array(gl),
180+
VAO::Native(_) => {
181+
gl.bind_vertex_array(None);
182+
}
183+
}
184+
}
185+
}
186+
187+
pub(crate) unsafe fn need_to_emulate_vao(gl: &glow::Context) -> bool {
188+
let web_sig = "WebGL ";
189+
let es_sig = "OpenGL ES ";
190+
let version_string = gl.get_parameter_string(glow::VERSION);
191+
if let Some(pos) = version_string.rfind(web_sig) {
192+
let version_str = &version_string[pos + web_sig.len()..];
193+
glow_debug_print(format!(
194+
"detected WebGL prefix at {}:{}",
195+
pos + web_sig.len(),
196+
version_str
197+
));
198+
if version_str.contains("1.0") {
199+
//need to test OES_vertex_array_object .
200+
gl.supported_extensions()
201+
.contains("OES_vertex_array_object")
202+
} else {
203+
false
204+
}
205+
} else if let Some(pos) = version_string.rfind(es_sig) {
206+
//glow targets es2.0+ so we don't concern about OpenGL ES-CM,OpenGL ES-CL
207+
glow_debug_print(format!(
208+
"detected OpenGL ES prefix at {}:{}",
209+
pos + es_sig.len(),
210+
&version_string[pos + es_sig.len()..]
211+
));
212+
if version_string.contains("2.0") {
213+
//need to test OES_vertex_array_object .
214+
gl.supported_extensions()
215+
.contains("OES_vertex_array_object")
216+
} else {
217+
false
218+
}
219+
} else {
220+
glow_debug_print(format!("detected OpenGL:{}", version_string));
221+
//from OpenGL 3 vao into core
222+
if version_string.starts_with('2') {
223+
// I found APPLE_vertex_array_object , GL_ATI_vertex_array_object ,ARB_vertex_array_object
224+
// but APPLE's and ATI's very old extension.
225+
gl.supported_extensions()
226+
.contains("ARB_vertex_array_object")
227+
} else {
228+
false
229+
}
230+
}
231+
}

0 commit comments

Comments
 (0)