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:
parent
1dbe608e73
commit
804722a1ba
21 changed files with 1093 additions and 318 deletions
|
@ -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)).
|
||||
|
||||
|
||||
|
|
|
@ -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"]
|
|
@ -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();
|
||||
|
|
|
@ -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
231
egui_glow/src/misc_util.rs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
222
egui_glow/src/post_process.rs
Normal file
222
egui_glow/src/post_process.rs
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
22
egui_glow/src/shader/post_fragment_100es.glsl
Normal file
22
egui_glow/src/shader/post_fragment_100es.glsl
Normal 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.;
|
||||
}
|
8
egui_glow/src/shader/post_vertex_100es.glsl
Normal file
8
egui_glow/src/shader/post_vertex_100es.glsl
Normal 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;
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
#if !defined(GL_ES) && __VERSION__ >= 140
|
||||
#ifdef NEW_SHADER_INTERFACE
|
||||
#define I in
|
||||
#define O out
|
||||
#define V(x) x
|
||||
|
|
79
egui_glow/src/shader_version.rs
Normal file
79
egui_glow/src/shader_version.rs
Normal 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);
|
||||
}
|
||||
}
|
57
egui_glow/src/vao_emulate.rs
Normal file
57
egui_glow/src/vao_emulate.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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(),
|
||||
}),
|
||||
|
|
130
egui_web/src/glow_wrapping.rs
Normal file
130
egui_web/src/glow_wrapping.rs
Normal 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")
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -17,4 +17,6 @@ pub trait Painter {
|
|||
clipped_meshes: Vec<egui::ClippedMesh>,
|
||||
pixels_per_point: f32,
|
||||
) -> Result<(), JsValue>;
|
||||
|
||||
fn name(&self) -> &'static str;
|
||||
}
|
||||
|
|
|
@ -538,6 +538,10 @@ impl crate::Painter for WebGlPainter {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn name(&self) -> &'static str {
|
||||
"egui_web(webgl1)"
|
||||
}
|
||||
}
|
||||
|
||||
struct PostProcess {
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue