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
|
## 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)).
|
* Simplify `EguiGlow` interface ([#871](https://github.com/emilk/egui/pull/871)).
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -23,18 +23,24 @@ all-features = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
egui = { version = "0.15.0", path = "../egui", default-features = false, features = ["single_threaded"] }
|
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"
|
glow = "0.11"
|
||||||
glutin = "0.27"
|
|
||||||
memoffset = "0.6"
|
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]
|
[dev-dependencies]
|
||||||
image = { version = "0.23", default-features = false, features = ["png"] }
|
image = { version = "0.23", default-features = false, features = ["png"] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["clipboard", "default_fonts", "links", "persistence"]
|
default = ["clipboard", "default_fonts", "links", "persistence", "winit"]
|
||||||
|
|
||||||
# enable cut/copy/paste to OS clipboard.
|
# enable cut/copy/paste to OS clipboard.
|
||||||
# if disabled a clipboard will be simulated so you can still copy/paste within the egui app.
|
# 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
|
# experimental support for a screen reader
|
||||||
screen_reader = ["egui-winit/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 crate::*;
|
||||||
use egui::Color32;
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
use glutin::platform::windows::WindowBuilderExtWindows;
|
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 RequestRepaintEvent;
|
||||||
|
|
||||||
struct GlowRepaintSignal(std::sync::Mutex<glutin::event_loop::EventLoopProxy<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(),
|
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(
|
let mut integration = egui_winit::epi::EpiIntegration::new(
|
||||||
"egui_glow",
|
"egui_glow",
|
||||||
gl_window.window(),
|
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_color(color[0], color[1], color[2], color[3]);
|
||||||
gl.clear(glow::COLOR_BUFFER_BIT);
|
gl.clear(glow::COLOR_BUFFER_BIT);
|
||||||
}
|
}
|
||||||
|
painter.upload_egui_texture(&gl, &integration.egui_ctx.texture());
|
||||||
painter.paint_meshes(
|
painter.paint_meshes(
|
||||||
&gl_window,
|
gl_window.window().inner_size().into(),
|
||||||
&gl,
|
&gl,
|
||||||
integration.egui_ctx.pixels_per_point(),
|
integration.egui_ctx.pixels_per_point(),
|
||||||
clipped_meshes,
|
clipped_meshes,
|
||||||
&integration.egui_ctx.texture(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
gl_window.swap_buffers().unwrap();
|
gl_window.swap_buffers().unwrap();
|
||||||
|
|
|
@ -87,25 +87,32 @@
|
||||||
#![allow(clippy::float_cmp)]
|
#![allow(clippy::float_cmp)]
|
||||||
#![allow(clippy::manual_range_contains)]
|
#![allow(clippy::manual_range_contains)]
|
||||||
|
|
||||||
mod painter;
|
pub mod painter;
|
||||||
|
pub use glow;
|
||||||
pub use painter::Painter;
|
pub use painter::Painter;
|
||||||
|
#[cfg(feature = "winit")]
|
||||||
#[cfg(feature = "epi")]
|
|
||||||
mod epi_backend;
|
mod epi_backend;
|
||||||
#[cfg(feature = "epi")]
|
mod misc_util;
|
||||||
pub use epi_backend::{run, NativeOptions};
|
mod post_process;
|
||||||
|
mod shader_version;
|
||||||
|
mod vao_emulate;
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
pub use egui_winit;
|
pub use egui_winit;
|
||||||
|
#[cfg(all(feature = "epi", feature = "winit"))]
|
||||||
|
pub use epi_backend::{run, NativeOptions};
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
/// Use [`egui`] from a [`glow`] app.
|
/// Use [`egui`] from a [`glow`] app.
|
||||||
|
#[cfg(feature = "winit")]
|
||||||
pub struct EguiGlow {
|
pub struct EguiGlow {
|
||||||
pub egui_ctx: egui::CtxRef,
|
pub egui_ctx: egui::CtxRef,
|
||||||
pub egui_winit: egui_winit::State,
|
pub egui_winit: egui_winit::State,
|
||||||
pub painter: crate::Painter,
|
pub painter: crate::Painter,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "winit")]
|
||||||
impl EguiGlow {
|
impl EguiGlow {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
gl_window: &glutin::WindowedContext<glutin::PossiblyCurrent>,
|
gl_window: &glutin::WindowedContext<glutin::PossiblyCurrent>,
|
||||||
|
@ -114,7 +121,11 @@ impl EguiGlow {
|
||||||
Self {
|
Self {
|
||||||
egui_ctx: Default::default(),
|
egui_ctx: Default::default(),
|
||||||
egui_winit: egui_winit::State::new(gl_window.window()),
|
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>,
|
shapes: Vec<egui::epaint::ClippedShape>,
|
||||||
) {
|
) {
|
||||||
let clipped_meshes = self.egui_ctx.tessellate(shapes);
|
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(
|
self.painter.paint_meshes(
|
||||||
gl_window,
|
dimensions,
|
||||||
gl,
|
gl,
|
||||||
self.egui_ctx.pixels_per_point(),
|
self.egui_ctx.pixels_per_point(),
|
||||||
clipped_meshes,
|
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::{
|
use egui::{
|
||||||
emath::Rect,
|
emath::Rect,
|
||||||
epaint::{Color32, Mesh, Vertex},
|
epaint::{Color32, Mesh, Vertex},
|
||||||
|
TextureId,
|
||||||
};
|
};
|
||||||
|
pub use glow::Context;
|
||||||
|
|
||||||
use memoffset::offset_of;
|
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 VERT_SRC: &str = include_str!("shader/vertex.glsl");
|
||||||
const FRAG_SRC: &str = include_str!("shader/fragment.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
|
/// OpenGL painter
|
||||||
///
|
///
|
||||||
/// This struct must be destroyed with [`Painter::destroy`] before dropping, to ensure OpenGL
|
/// This struct must be destroyed with [`Painter::destroy`] before dropping, to ensure OpenGL
|
||||||
/// objects have been properly deleted and are not leaked.
|
/// objects have been properly deleted and are not leaked.
|
||||||
pub struct Painter {
|
pub struct Painter {
|
||||||
program: glow::NativeProgram,
|
program: glow::Program,
|
||||||
u_screen_size: glow::UniformLocation,
|
u_screen_size: glow::UniformLocation,
|
||||||
u_sampler: glow::UniformLocation,
|
u_sampler: glow::UniformLocation,
|
||||||
egui_texture: Option<glow::NativeTexture>,
|
egui_texture: Option<glow::Texture>,
|
||||||
egui_texture_version: Option<u64>,
|
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.
|
/// `None` means unallocated (freed) slot.
|
||||||
user_textures: Vec<Option<UserTexture>>,
|
pub(crate) user_textures: Vec<Option<UserTexture>>,
|
||||||
|
post_process: Option<PostProcess>,
|
||||||
vertex_array: glow::NativeVertexArray,
|
vertex_buffer: glow::Buffer,
|
||||||
vertex_buffer: glow::NativeBuffer,
|
element_array_buffer: glow::Buffer,
|
||||||
element_array_buffer: glow::NativeBuffer,
|
|
||||||
|
|
||||||
// Stores outdated OpenGL textures that are yet to be deleted
|
// 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.
|
// Only used in debug builds, to make sure we are destroyed correctly.
|
||||||
destroyed: bool,
|
destroyed: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct UserTexture {
|
pub(crate) struct UserTexture {
|
||||||
/// Pending upload (will be emptied later).
|
/// Pending upload (will be emptied later).
|
||||||
/// This is the format glow likes.
|
/// This is the format glow likes.
|
||||||
data: Vec<u8>,
|
pub(crate) data: Vec<u8>,
|
||||||
size: (usize, usize),
|
pub(crate) size: (usize, usize),
|
||||||
|
|
||||||
/// Lazily uploaded
|
/// Lazily uploaded
|
||||||
gl_texture: Option<glow::NativeTexture>,
|
pub(crate) gl_texture: Option<glow::Texture>,
|
||||||
}
|
|
||||||
|
|
||||||
#[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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Painter {
|
impl Painter {
|
||||||
pub fn new(gl: &glow::Context) -> Painter {
|
/// Create painter.
|
||||||
let header = ShaderVersion::get(gl).version();
|
///
|
||||||
|
/// 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 {
|
unsafe {
|
||||||
let vert = gl.create_shader(glow::VERTEX_SHADER).unwrap();
|
let vert = compile_shader(
|
||||||
gl.shader_source(vert, &format!("{}\n{}", header, VERT_SRC));
|
gl,
|
||||||
gl.compile_shader(vert);
|
glow::VERTEX_SHADER,
|
||||||
if !gl.get_shader_compile_status(vert) {
|
&format!(
|
||||||
panic!(
|
"{}\n{}\n{}",
|
||||||
"Failed to compile vertex shader: {}",
|
header,
|
||||||
gl.get_shader_info_log(vert)
|
shader_version.is_new_shader_interface(),
|
||||||
);
|
VERT_SRC
|
||||||
}
|
),
|
||||||
|
)?;
|
||||||
let frag = gl.create_shader(glow::FRAGMENT_SHADER).unwrap();
|
let frag = compile_shader(
|
||||||
gl.shader_source(frag, &format!("{}\n{}", header, FRAG_SRC));
|
gl,
|
||||||
gl.compile_shader(frag);
|
glow::FRAGMENT_SHADER,
|
||||||
if !gl.get_shader_compile_status(frag) {
|
&format!(
|
||||||
panic!(
|
"{}\n{}\n{}\n{}",
|
||||||
"Failed to compile fragment shader: {}",
|
header,
|
||||||
gl.get_shader_info_log(frag)
|
srgb_support_define,
|
||||||
);
|
shader_version.is_new_shader_interface(),
|
||||||
}
|
FRAG_SRC
|
||||||
|
),
|
||||||
let program = gl.create_program().unwrap();
|
)?;
|
||||||
gl.attach_shader(program, vert);
|
let program = link_program(gl, [vert, frag].iter())?;
|
||||||
gl.attach_shader(program, frag);
|
|
||||||
gl.link_program(program);
|
|
||||||
if !gl.get_program_link_status(program) {
|
|
||||||
panic!("{}", gl.get_program_info_log(program));
|
|
||||||
}
|
|
||||||
gl.detach_shader(program, vert);
|
gl.detach_shader(program, vert);
|
||||||
gl.detach_shader(program, frag);
|
gl.detach_shader(program, frag);
|
||||||
gl.delete_shader(vert);
|
gl.delete_shader(vert);
|
||||||
gl.delete_shader(frag);
|
gl.delete_shader(frag);
|
||||||
|
|
||||||
let u_screen_size = gl.get_uniform_location(program, "u_screen_size").unwrap();
|
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 u_sampler = gl.get_uniform_location(program, "u_sampler").unwrap();
|
||||||
|
let vertex_buffer = gl.create_buffer()?;
|
||||||
let vertex_array = gl.create_vertex_array().unwrap();
|
let element_array_buffer = gl.create_buffer()?;
|
||||||
let vertex_buffer = gl.create_buffer().unwrap();
|
|
||||||
let element_array_buffer = gl.create_buffer().unwrap();
|
|
||||||
|
|
||||||
gl.bind_vertex_array(Some(vertex_array));
|
|
||||||
gl.bind_buffer(glow::ARRAY_BUFFER, Some(vertex_buffer));
|
gl.bind_buffer(glow::ARRAY_BUFFER, Some(vertex_buffer));
|
||||||
|
|
||||||
let a_pos_loc = gl.get_attrib_location(program, "a_pos").unwrap();
|
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_tc_loc = gl.get_attrib_location(program, "a_tc").unwrap();
|
||||||
let a_srgba_loc = gl.get_attrib_location(program, "a_srgba").unwrap();
|
let a_srgba_loc = gl.get_attrib_location(program, "a_srgba").unwrap();
|
||||||
|
let mut vertex_array = if need_to_emulate_vao {
|
||||||
gl.vertex_attrib_pointer_f32(
|
crate::misc_util::VAO::emulated()
|
||||||
a_pos_loc,
|
} else {
|
||||||
2,
|
crate::misc_util::VAO::native(gl)
|
||||||
glow::FLOAT,
|
};
|
||||||
false,
|
vertex_array.bind_vertex_array(gl);
|
||||||
std::mem::size_of::<Vertex>() as i32,
|
vertex_array.bind_buffer(gl, &vertex_buffer);
|
||||||
offset_of!(Vertex, pos) as i32,
|
let stride = std::mem::size_of::<Vertex>() as i32;
|
||||||
);
|
let position_buffer_info = vao_emulate::BufferInfo {
|
||||||
gl.enable_vertex_attrib_array(a_pos_loc);
|
location: a_pos_loc,
|
||||||
|
vector_size: 2,
|
||||||
gl.vertex_attrib_pointer_f32(
|
data_type: glow::FLOAT,
|
||||||
a_tc_loc,
|
normalized: false,
|
||||||
2,
|
stride,
|
||||||
glow::FLOAT,
|
offset: offset_of!(Vertex, pos) as i32,
|
||||||
false,
|
};
|
||||||
std::mem::size_of::<Vertex>() as i32,
|
let tex_coord_buffer_info = vao_emulate::BufferInfo {
|
||||||
offset_of!(Vertex, uv) as i32,
|
location: a_tc_loc,
|
||||||
);
|
vector_size: 2,
|
||||||
gl.enable_vertex_attrib_array(a_tc_loc);
|
data_type: glow::FLOAT,
|
||||||
|
normalized: false,
|
||||||
gl.vertex_attrib_pointer_f32(
|
stride,
|
||||||
a_srgba_loc,
|
offset: offset_of!(Vertex, uv) as i32,
|
||||||
4,
|
};
|
||||||
glow::UNSIGNED_BYTE,
|
let color_buffer_info = vao_emulate::BufferInfo {
|
||||||
false,
|
location: a_srgba_loc,
|
||||||
std::mem::size_of::<Vertex>() as i32,
|
vector_size: 4,
|
||||||
offset_of!(Vertex, color) as i32,
|
data_type: glow::UNSIGNED_BYTE,
|
||||||
);
|
normalized: false,
|
||||||
gl.enable_vertex_attrib_array(a_srgba_loc);
|
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!");
|
assert_eq!(gl.get_error(), glow::NO_ERROR, "OpenGL error occurred!");
|
||||||
|
|
||||||
Painter {
|
Ok(Painter {
|
||||||
program,
|
program,
|
||||||
u_screen_size,
|
u_screen_size,
|
||||||
u_sampler,
|
u_sampler,
|
||||||
egui_texture: None,
|
egui_texture: None,
|
||||||
egui_texture_version: None,
|
egui_texture_version: None,
|
||||||
user_textures: Default::default(),
|
is_webgl_1,
|
||||||
|
is_embedded: matches!(shader_version, ShaderVersion::Es100 | ShaderVersion::Es300),
|
||||||
vertex_array,
|
vertex_array,
|
||||||
|
srgb_support,
|
||||||
|
user_textures: Default::default(),
|
||||||
|
post_process,
|
||||||
vertex_buffer,
|
vertex_buffer,
|
||||||
element_array_buffer,
|
element_array_buffer,
|
||||||
old_textures: Vec::new(),
|
old_textures: Vec::new(),
|
||||||
destroyed: false,
|
destroyed: false,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -268,16 +200,26 @@ impl Painter {
|
||||||
if self.egui_texture_version == Some(texture.version) {
|
if self.egui_texture_version == Some(texture.version) {
|
||||||
return; // No change
|
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
|
let pixels: Vec<u8> = texture
|
||||||
.pixels
|
.srgba_pixels(gamma)
|
||||||
.iter()
|
.flat_map(|a| Vec::from(a.to_array()))
|
||||||
.flat_map(|a| Vec::from(Color32::from_white_alpha(*a).to_array()))
|
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
if let Some(old_tex) = std::mem::replace(
|
if let Some(old_tex) = std::mem::replace(
|
||||||
&mut self.egui_texture,
|
&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 {
|
unsafe {
|
||||||
gl.delete_texture(old_tex);
|
gl.delete_texture(old_tex);
|
||||||
|
@ -288,7 +230,7 @@ impl Painter {
|
||||||
|
|
||||||
unsafe fn prepare_painting(
|
unsafe fn prepare_painting(
|
||||||
&mut self,
|
&mut self,
|
||||||
gl_window: &glutin::WindowedContext<glutin::PossiblyCurrent>,
|
[width_in_pixels, height_in_pixels]: [u32; 2],
|
||||||
gl: &glow::Context,
|
gl: &glow::Context,
|
||||||
pixels_per_point: f32,
|
pixels_per_point: f32,
|
||||||
) -> (u32, u32) {
|
) -> (u32, u32) {
|
||||||
|
@ -308,10 +250,6 @@ impl Painter {
|
||||||
glow::ONE,
|
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 width_in_points = width_in_pixels as f32 / pixels_per_point;
|
||||||
let height_in_points = height_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_2_f32(Some(&self.u_screen_size), width_in_points, height_in_points);
|
||||||
gl.uniform_1_i32(Some(&self.u_sampler), 0);
|
gl.uniform_1_i32(Some(&self.u_sampler), 0);
|
||||||
gl.active_texture(glow::TEXTURE0);
|
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));
|
gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(self.element_array_buffer));
|
||||||
|
|
||||||
(width_in_pixels, height_in_pixels)
|
(width_in_pixels, height_in_pixels)
|
||||||
|
@ -341,8 +278,7 @@ impl Painter {
|
||||||
///
|
///
|
||||||
/// The scissor area and blend parameters will be changed.
|
/// The scissor area and blend parameters will be changed.
|
||||||
///
|
///
|
||||||
/// As well as this, the following objects will be rebound:
|
/// As well as this, the following objects will be unset:
|
||||||
/// - Vertex Array
|
|
||||||
/// - Vertex Buffer
|
/// - Vertex Buffer
|
||||||
/// - Element Buffer
|
/// - Element Buffer
|
||||||
/// - Texture (and active texture will be set to 0)
|
/// - 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.
|
/// of the effects your program might have on this code. Look at the source if in doubt.
|
||||||
pub fn paint_meshes(
|
pub fn paint_meshes(
|
||||||
&mut self,
|
&mut self,
|
||||||
gl_window: &glutin::WindowedContext<glutin::PossiblyCurrent>,
|
inner_size: [u32; 2],
|
||||||
gl: &glow::Context,
|
gl: &glow::Context,
|
||||||
pixels_per_point: f32,
|
pixels_per_point: f32,
|
||||||
clipped_meshes: Vec<egui::ClippedMesh>,
|
clipped_meshes: Vec<egui::ClippedMesh>,
|
||||||
egui_texture: &egui::Texture,
|
|
||||||
) {
|
) {
|
||||||
|
//chimera of egui_glow and egui_web
|
||||||
self.assert_not_destroyed();
|
self.assert_not_destroyed();
|
||||||
|
|
||||||
self.upload_egui_texture(gl, egui_texture);
|
|
||||||
self.upload_pending_user_textures(gl);
|
self.upload_pending_user_textures(gl);
|
||||||
|
if let Some(ref mut post_process) = self.post_process {
|
||||||
let size_in_pixels = unsafe { self.prepare_painting(gl_window, gl, pixels_per_point) };
|
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 {
|
for egui::ClippedMesh(clip_rect, mesh) in clipped_meshes {
|
||||||
self.paint_mesh(gl, size_in_pixels, pixels_per_point, clip_rect, &mesh);
|
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!(
|
if let Some(ref post_process) = self.post_process {
|
||||||
unsafe { gl.get_error() },
|
post_process.end(gl);
|
||||||
glow::NO_ERROR,
|
}
|
||||||
"OpenGL error occurred!"
|
assert_eq!(glow::NO_ERROR, gl.get_error(), "GL error occurred!");
|
||||||
);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(never)] // Easier profiling
|
#[inline(never)] // Easier profiling
|
||||||
|
@ -385,15 +327,16 @@ impl Painter {
|
||||||
mesh: &Mesh,
|
mesh: &Mesh,
|
||||||
) {
|
) {
|
||||||
debug_assert!(mesh.is_valid());
|
debug_assert!(mesh.is_valid());
|
||||||
|
|
||||||
if let Some(texture) = self.get_texture(mesh.texture_id) {
|
if let Some(texture) = self.get_texture(mesh.texture_id) {
|
||||||
unsafe {
|
unsafe {
|
||||||
|
gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.vertex_buffer));
|
||||||
gl.buffer_data_u8_slice(
|
gl.buffer_data_u8_slice(
|
||||||
glow::ARRAY_BUFFER,
|
glow::ARRAY_BUFFER,
|
||||||
as_u8_slice(mesh.vertices.as_slice()),
|
as_u8_slice(mesh.vertices.as_slice()),
|
||||||
glow::STREAM_DRAW,
|
glow::STREAM_DRAW,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(self.element_array_buffer));
|
||||||
gl.buffer_data_u8_slice(
|
gl.buffer_data_u8_slice(
|
||||||
glow::ELEMENT_ARRAY_BUFFER,
|
glow::ELEMENT_ARRAY_BUFFER,
|
||||||
as_u8_slice(mesh.indices.as_slice()),
|
as_u8_slice(mesh.indices.as_slice()),
|
||||||
|
@ -402,7 +345,6 @@ impl Painter {
|
||||||
|
|
||||||
gl.bind_texture(glow::TEXTURE_2D, Some(texture));
|
gl.bind_texture(glow::TEXTURE_2D, Some(texture));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transform clip rect to physical pixels:
|
// Transform clip rect to physical pixels:
|
||||||
let clip_min_x = pixels_per_point * clip_rect.min.x;
|
let clip_min_x = pixels_per_point * clip_rect.min.x;
|
||||||
let clip_min_y = pixels_per_point * clip_rect.min.y;
|
let clip_min_y = pixels_per_point * clip_rect.min.y;
|
||||||
|
@ -457,7 +399,8 @@ impl Painter {
|
||||||
|
|
||||||
/// register glow texture as egui texture
|
/// register glow texture as egui texture
|
||||||
/// Usable for render to image rectangle
|
/// 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();
|
self.assert_not_destroyed();
|
||||||
|
|
||||||
let id = self.alloc_user_texture();
|
let id = self.alloc_user_texture();
|
||||||
|
@ -488,11 +431,10 @@ impl Painter {
|
||||||
pixels: &[Color32],
|
pixels: &[Color32],
|
||||||
) {
|
) {
|
||||||
self.assert_not_destroyed();
|
self.assert_not_destroyed();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
size.0 * size.1,
|
size.0 * size.1,
|
||||||
pixels.len(),
|
pixels.len(),
|
||||||
"Mismatch between texture size and texel count"
|
"Mismatch between size and texel count"
|
||||||
);
|
);
|
||||||
|
|
||||||
if let egui::TextureId::User(id) = id {
|
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();
|
self.assert_not_destroyed();
|
||||||
|
|
||||||
match texture_id {
|
match texture_id {
|
||||||
|
@ -547,6 +489,8 @@ impl Painter {
|
||||||
let data = std::mem::take(&mut user_texture.data);
|
let data = std::mem::take(&mut user_texture.data);
|
||||||
user_texture.gl_texture = Some(srgbtexture2d(
|
user_texture.gl_texture = Some(srgbtexture2d(
|
||||||
gl,
|
gl,
|
||||||
|
self.is_webgl_1,
|
||||||
|
self.srgb_support,
|
||||||
&data,
|
&data,
|
||||||
user_texture.size.0,
|
user_texture.size.0,
|
||||||
user_texture.size.1,
|
user_texture.size.1,
|
||||||
|
@ -571,7 +515,6 @@ impl Painter {
|
||||||
gl.delete_texture(t);
|
gl.delete_texture(t);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
gl.delete_vertex_array(self.vertex_array);
|
|
||||||
gl.delete_buffer(self.vertex_buffer);
|
gl.delete_buffer(self.vertex_buffer);
|
||||||
gl.delete_buffer(self.element_array_buffer);
|
gl.delete_buffer(self.element_array_buffer);
|
||||||
for t in &self.old_textures {
|
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
|
/// This function must be called before Painter is dropped, as Painter has some OpenGL objects
|
||||||
/// that should be deleted.
|
/// 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 {
|
unsafe {
|
||||||
self.destroy_gl(gl);
|
self.destroy_gl(gl);
|
||||||
|
if let Some(ref post_process) = self.post_process {
|
||||||
|
post_process.destroy(gl);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.destroyed = true;
|
self.destroyed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn assert_not_destroyed(&self) {
|
fn assert_not_destroyed(&self) {
|
||||||
debug_assert!(
|
debug_assert!(!self.destroyed, "the egui glow has already been destroyed!");
|
||||||
!self.destroyed,
|
|
||||||
"the egui glow painter 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 {
|
impl Drop for Painter {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
debug_assert!(
|
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
|
#endif
|
||||||
|
|
||||||
uniform sampler2D u_sampler;
|
uniform sampler2D u_sampler;
|
||||||
#if defined(GL_ES) || __VERSION__ < 140
|
|
||||||
varying vec4 v_rgba;
|
#ifdef NEW_SHADER_INTERFACE
|
||||||
varying vec2 v_tc;
|
|
||||||
#else
|
|
||||||
in vec4 v_rgba;
|
in vec4 v_rgba;
|
||||||
in vec2 v_tc;
|
in vec2 v_tc;
|
||||||
out vec4 f_color;
|
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
|
#endif
|
||||||
|
|
||||||
#ifdef GL_ES
|
#ifndef SRGB_SUPPORTED
|
||||||
// 0-255 sRGB from 0-1 linear
|
// 0-255 sRGB from 0-1 linear
|
||||||
vec3 srgb_from_linear(vec3 rgb) {
|
vec3 srgb_from_linear(vec3 rgb) {
|
||||||
bvec3 cutoff = lessThan(rgb, vec3(0.0031308));
|
bvec3 cutoff = lessThan(rgb, vec3(0.0031308));
|
||||||
vec3 lower = rgb * vec3(3294.6);
|
vec3 lower = rgb * vec3(3294.6);
|
||||||
vec3 higher = vec3(269.025) * pow(rgb, vec3(1.0 / 2.4)) - vec3(14.025);
|
vec3 higher = vec3(269.025) * pow(rgb, vec3(1.0 / 2.4)) - vec3(14.025);
|
||||||
return mix(higher, lower, vec3(cutoff));
|
return mix(higher, lower, vec3(cutoff));
|
||||||
}
|
}
|
||||||
|
|
||||||
vec4 srgba_from_linear(vec4 rgba) {
|
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
|
// 0-1 linear from 0-255 sRGB
|
||||||
vec3 linear_from_srgb(vec3 srgb) {
|
vec3 linear_from_srgb(vec3 srgb) {
|
||||||
bvec3 cutoff = lessThan(srgb, vec3(10.31475));
|
bvec3 cutoff = lessThan(srgb, vec3(10.31475));
|
||||||
vec3 lower = srgb / vec3(3294.6);
|
vec3 lower = srgb / vec3(3294.6);
|
||||||
vec3 higher = pow((srgb + vec3(14.025)) / vec3(269.025), vec3(2.4));
|
vec3 higher = pow((srgb + vec3(14.025)) / vec3(269.025), vec3(2.4));
|
||||||
return mix(higher, lower, vec3(cutoff));
|
return mix(higher, lower, vec3(cutoff));
|
||||||
}
|
}
|
||||||
|
|
||||||
vec4 linear_from_srgba(vec4 srgba) {
|
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() {
|
void main() {
|
||||||
#if __VERSION__ < 300
|
// We must decode the colors, since WebGL doesn't come with sRGBA textures:
|
||||||
// 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);
|
||||||
vec4 texture_rgba = linear_from_srgba(texture2D(u_sampler, v_tc) * 255.0);
|
/// Multiply vertex color with texture color (in linear space).
|
||||||
#else
|
gl_FragColor = v_rgba * texture_rgba;
|
||||||
// The texture is set up with `SRGB8_ALPHA8`, so no need to decode here!
|
|
||||||
vec4 texture_rgba = texture2D(u_sampler, v_tc);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/// Multiply vertex color with texture color (in linear space).
|
// WebGL doesn't support linear blending in the framebuffer,
|
||||||
gl_FragColor = v_rgba * texture_rgba;
|
// 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.
|
// Unmultiply alpha:
|
||||||
gl_FragColor = srgba_from_linear(v_rgba * texture_rgba) / 255.0;
|
if (gl_FragColor.a > 0.0) {
|
||||||
|
gl_FragColor.rgb /= gl_FragColor.a;
|
||||||
|
}
|
||||||
|
|
||||||
// WebGL doesn't support linear blending in the framebuffer,
|
// Empiric tweak to make e.g. shadows look more like they should:
|
||||||
// so we apply this hack to at least get a bit closer to the desired blending:
|
gl_FragColor.a *= sqrt(gl_FragColor.a);
|
||||||
gl_FragColor.a = pow(gl_FragColor.a, 1.6); // Empiric nonsense
|
|
||||||
|
// 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() {
|
void main() {
|
||||||
// The texture sampler is sRGB aware, and OpenGL already expects linear rgba output
|
// The texture sampler is sRGB aware, and OpenGL already expects linear rgba output
|
||||||
// so no need for any sRGB conversions here:
|
// so no need for any sRGB conversions here:
|
||||||
#if __VERSION__ < 140
|
|
||||||
gl_FragColor = v_rgba * texture2D(u_sampler, v_tc);
|
gl_FragColor = v_rgba * texture2D(u_sampler, v_tc);
|
||||||
#else
|
|
||||||
f_color = v_rgba * texture(u_sampler, v_tc);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
#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 I in
|
||||||
#define O out
|
#define O out
|
||||||
#define V(x) x
|
#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
|
## 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
|
## 0.15.0 - 2021-10-24
|
||||||
### Added
|
### Added
|
||||||
|
|
|
@ -28,6 +28,7 @@ crate-type = ["cdylib", "rlib"]
|
||||||
egui = { version = "0.15.0", path = "../egui", default-features = false, features = [
|
egui = { version = "0.15.0", path = "../egui", default-features = false, features = [
|
||||||
"single_threaded",
|
"single_threaded",
|
||||||
] }
|
] }
|
||||||
|
egui_glow = { version = "0.15.0",path = "../egui_glow", default-features = false, optional = true }
|
||||||
epi = { version = "0.15.0", path = "../epi" }
|
epi = { version = "0.15.0", path = "../epi" }
|
||||||
js-sys = "0.3"
|
js-sys = "0.3"
|
||||||
ron = { version = "0.7", optional = true }
|
ron = { version = "0.7", optional = true }
|
||||||
|
@ -44,6 +45,7 @@ default = ["default_fonts"]
|
||||||
default_fonts = ["egui/default_fonts"]
|
default_fonts = ["egui/default_fonts"]
|
||||||
persistence = ["egui/persistence", "ron", "serde"]
|
persistence = ["egui/persistence", "ron", "serde"]
|
||||||
screen_reader = ["tts"] # experimental
|
screen_reader = ["tts"] # experimental
|
||||||
|
glow =["egui_glow","egui_glow/epi"]
|
||||||
|
|
||||||
[dependencies.web-sys]
|
[dependencies.web-sys]
|
||||||
version = "0.3.52"
|
version = "0.3.52"
|
||||||
|
|
|
@ -5,6 +5,11 @@ pub use egui::{pos2, Color32};
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
fn create_painter(canvas_id: &str) -> Result<Box<dyn Painter>, JsValue> {
|
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) {
|
if let Ok(webgl2_painter) = webgl2::WebGl2Painter::new(canvas_id) {
|
||||||
console_log("Using WebGL2 backend");
|
console_log("Using WebGL2 backend");
|
||||||
Ok(Box::new(webgl2_painter))
|
Ok(Box::new(webgl2_painter))
|
||||||
|
@ -167,7 +172,7 @@ impl AppRunner {
|
||||||
|
|
||||||
fn integration_info(&self) -> epi::IntegrationInfo {
|
fn integration_info(&self) -> epi::IntegrationInfo {
|
||||||
epi::IntegrationInfo {
|
epi::IntegrationInfo {
|
||||||
name: "egui_web",
|
name: self.painter.name(),
|
||||||
web_info: Some(epi::WebInfo {
|
web_info: Some(epi::WebInfo {
|
||||||
web_location_hash: location_hash().unwrap_or_default(),
|
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)]
|
#![warn(clippy::all, missing_crate_level_docs, rust_2018_idioms)]
|
||||||
|
|
||||||
pub mod backend;
|
pub mod backend;
|
||||||
|
#[cfg(feature = "glow")]
|
||||||
|
mod glow_wrapping;
|
||||||
mod painter;
|
mod painter;
|
||||||
pub mod screen_reader;
|
pub mod screen_reader;
|
||||||
pub mod webgl1;
|
pub mod webgl1;
|
||||||
|
|
|
@ -17,4 +17,6 @@ pub trait Painter {
|
||||||
clipped_meshes: Vec<egui::ClippedMesh>,
|
clipped_meshes: Vec<egui::ClippedMesh>,
|
||||||
pixels_per_point: f32,
|
pixels_per_point: f32,
|
||||||
) -> Result<(), JsValue>;
|
) -> Result<(), JsValue>;
|
||||||
|
|
||||||
|
fn name(&self) -> &'static str;
|
||||||
}
|
}
|
||||||
|
|
|
@ -538,6 +538,10 @@ impl crate::Painter for WebGlPainter {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn name(&self) -> &'static str {
|
||||||
|
"egui_web(webgl1)"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct PostProcess {
|
struct PostProcess {
|
||||||
|
|
|
@ -516,6 +516,10 @@ impl crate::Painter for WebGl2Painter {
|
||||||
|
|
||||||
Ok(())
|
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
|
/// Uses a framebuffer to render everything in linear color space and convert it back to sRGB
|
||||||
|
|
Loading…
Reference in a new issue