This commit is contained in:
nora 2023-04-18 15:38:14 +02:00
parent 12163d1338
commit 550b1644cb
363 changed files with 84081 additions and 16 deletions

View file

@ -0,0 +1,64 @@
# Changelog for egui_glow
All notable changes to the `egui_glow` integration will be noted in this file.
## Unreleased
## 0.21.0 - 2023-02-08
* Update to `glow` 0.12 ([#2695](https://github.com/emilk/egui/pull/2695)).
* Remove the `screen_reader` feature ([#2669](https://github.com/emilk/egui/pull/2669)).
## 0.20.1 - 2022-12-11
* Fix [docs.rs](https://docs.rs/egui_glow) build ([#2420](https://github.com/emilk/egui/pull/2420)).
## 0.20.0 - 2022-12-08
* Allow empty textures.
* Added `shader_version` variable on `EguiGlow::new` for easier cross compiling on different OpenGL | ES targets ([#1993](https://github.com/emilk/egui/pull/1993)).
## 0.19.0 - 2022-08-20
* MSRV (Minimum Supported Rust Version) is now `1.61.0` ([#1846](https://github.com/emilk/egui/pull/1846)).
* `EguiGlow::new` now takes an `EventLoopWindowTarget<E>` instead of a `winit::Window` ([#1634](https://github.com/emilk/egui/pull/1634)).
* Use `Arc` for `glow::Context` instead of `Rc` ([#1640](https://github.com/emilk/egui/pull/1640)).
* Fixed `glClear` on WebGL1 ([#1658](https://github.com/emilk/egui/pull/1658)).
* Add `Painter::intermediate_fbo` which tells callbacks where to render. This is only needed if the callbacks use their own FBO:s and need to know what to restore to.
## 0.18.1 - 2022-05-05
* Remove calls to `gl.get_error` in release builds to speed up rendering ([#1583](https://github.com/emilk/egui/pull/1583)).
## 0.18.0 - 2022-04-30
* Improved logging on rendering failures.
* Added new `NativeOptions`: `vsync`, `multisampling`, `depth_buffer`, `stencil_buffer`.
* Fixed potential scale bug when DPI scaling changes (e.g. when dragging a window between different displays) ([#1441](https://github.com/emilk/egui/pull/1441)).
* MSRV (Minimum Supported Rust Version) is now `1.60.0` ([#1467](https://github.com/emilk/egui/pull/1467)).
* `clipboard`, `links`, `winit` are now all opt-in features ([#1467](https://github.com/emilk/egui/pull/1467)).
* Added new feature `puffin` to add [`puffin profiler`](https://github.com/EmbarkStudios/puffin) scopes ([#1483](https://github.com/emilk/egui/pull/1483)).
* Removed the features `dark-light`, `default_fonts` and `persistence` ([#1542](https://github.com/emilk/egui/pull/1542)).
## 0.17.0 - 2022-02-22
* `EguiGlow::run` no longer returns the shapes to paint, but stores them internally until you call `EguiGlow::paint` ([#1110](https://github.com/emilk/egui/pull/1110)).
* Added `set_texture_filter` method to `Painter` ([#1041](https://github.com/emilk/egui/pull/1041)).
* Fixed failure to run in Chrome ([#1092](https://github.com/emilk/egui/pull/1092)).
* `EguiGlow::new` and `EguiGlow::paint` now takes `&winit::Window` ([#1151](https://github.com/emilk/egui/pull/1151)).
* Automatically detect and apply dark or light mode from system ([#1045](https://github.com/emilk/egui/pull/1045)).
## 0.16.0 - 2021-12-29
* Made winit/glutin an optional dependency ([#868](https://github.com/emilk/egui/pull/868)).
* Simplified `EguiGlow` interface ([#871](https://github.com/emilk/egui/pull/871)).
* Removed `EguiGlow::is_quit_event` ([#881](https://github.com/emilk/egui/pull/881)).
* Updated `glutin` to 0.28 ([#930](https://github.com/emilk/egui/pull/930)).
* Changed the `Painter` interface slightly ([#999](https://github.com/emilk/egui/pull/999)).
## 0.15.0 - 2021-10-24
`egui_glow` has been newly created, with feature parity to `egui_glium`.
As `glow` is a set of lower-level bindings to OpenGL, this crate is potentially less stable than `egui_glium`,
but hopefully this will one day replace `egui_glium` as the default backend for `eframe`.

View file

@ -0,0 +1,79 @@
[package]
name = "egui_glow"
version = "0.21.0"
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
description = "Bindings for using egui natively using the glow library"
edition = "2021"
rust-version = "1.65"
homepage = "https://github.com/emilk/egui/tree/master/crates/egui_glow"
license = "MIT OR Apache-2.0"
readme = "README.md"
repository = "https://github.com/emilk/egui/tree/master/crates/egui_glow"
categories = ["gui", "game-development"]
keywords = ["glow", "egui", "gui", "gamedev"]
include = [
"../LICENSE-APACHE",
"../LICENSE-MIT",
"**/*.rs",
"Cargo.toml",
"src/shader/*.glsl",
]
[package.metadata.docs.rs]
all-features = true
[features]
default = []
## For the `winit` integration:
## enable cut/copy/paste to os clipboard.
##
## if disabled a clipboard will be simulated so you can still copy/paste within the egui app.
clipboard = ["egui-winit?/clipboard"]
## For the `winit` integration:
## enable opening links in a browser when an egui hyperlink is clicked.
links = ["egui-winit?/links"]
## Enable profiling with the [`puffin`](https://docs.rs/puffin) crate.
puffin = ["dep:puffin", "egui-winit?/puffin"]
## Enable [`winit`](https://docs.rs/winit) integration.
winit = ["egui-winit"]
[dependencies]
egui = { version = "0.21.0", path = "../egui", default-features = false, features = [
"bytemuck",
] }
bytemuck = "1.7"
glow = "0.12"
memoffset = "0.6"
tracing = { version = "0.1", default-features = false, features = ["std"] }
#! ### Optional dependencies
## Enable this when generating docs.
document-features = { version = "0.2", optional = true }
# Native:
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
egui-winit = { version = "0.21.1", path = "../egui-winit", optional = true, default-features = false }
puffin = { version = "0.14", optional = true }
# Web:
[target.'cfg(target_arch = "wasm32")'.dependencies]
web-sys = { version = "0.3", features = ["console"] }
wasm-bindgen = { version = "0.2" }
[dev-dependencies]
glutin = "0.30" # examples/pure_glow
raw-window-handle = "0.5.0"
glutin-winit = "0.3.0"
[[example]]
name = "pure_glow"
required-features = ["winit", "egui/default_fonts"]

View file

@ -0,0 +1,26 @@
# egui_glow
[![Latest version](https://img.shields.io/crates/v/egui_glow.svg)](https://crates.io/crates/egui_glow)
[![Documentation](https://docs.rs/egui_glow/badge.svg)](https://docs.rs/egui_glow)
![MIT](https://img.shields.io/badge/license-MIT-blue.svg)
![Apache](https://img.shields.io/badge/license-Apache-blue.svg)
This crates provides bindings between [`egui`](https://github.com/emilk/egui) and [glow](https://crates.io/crates/glow) which allows you to:
* Render egui using glow on both native and web.
* Write cross platform native egui apps (with the `winit` feature).
To write web apps using `glow` you can use [`eframe`](https://github.com/emilk/egui/tree/master/crates/eframe) (which uses `egui_glow` for rendering).
To use on Linux, first run:
```
sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libxkbcommon-dev libssl-dev
```
This crate optionally depends on [`egui-winit`](https://github.com/emilk/egui/tree/master/crates/egui-winit).
Text the example with:
``` sh
cargo run -p egui_glow --example pure_glow --features=winit,egui/default_fonts
```

View file

@ -0,0 +1,255 @@
//! Example how to use pure `egui_glow`.
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
#![allow(unsafe_code)]
use egui_winit::winit;
/// The majority of `GlutinWindowContext` is taken from `eframe`
struct GlutinWindowContext {
window: winit::window::Window,
gl_context: glutin::context::PossiblyCurrentContext,
gl_display: glutin::display::Display,
gl_surface: glutin::surface::Surface<glutin::surface::WindowSurface>,
}
impl GlutinWindowContext {
// refactor this function to use `glutin-winit` crate eventually.
// preferably add android support at the same time.
#[allow(unsafe_code)]
unsafe fn new(event_loop: &winit::event_loop::EventLoopWindowTarget<()>) -> Self {
use egui::NumExt;
use glutin::context::NotCurrentGlContextSurfaceAccessor;
use glutin::display::GetGlDisplay;
use glutin::display::GlDisplay;
use glutin::prelude::GlSurface;
use raw_window_handle::HasRawWindowHandle;
let winit_window_builder = winit::window::WindowBuilder::new()
.with_resizable(true)
.with_inner_size(winit::dpi::LogicalSize {
width: 800.0,
height: 600.0,
})
.with_title("egui_glow example") // Keep hidden until we've painted something. See https://github.com/emilk/egui/pull/2279
.with_visible(false);
let config_template_builder = glutin::config::ConfigTemplateBuilder::new()
.prefer_hardware_accelerated(None)
.with_depth_size(0)
.with_stencil_size(0)
.with_transparency(false);
tracing::debug!("trying to get gl_config");
let (mut window, gl_config) =
glutin_winit::DisplayBuilder::new() // let glutin-winit helper crate handle the complex parts of opengl context creation
.with_preference(glutin_winit::ApiPrefence::FallbackEgl) // https://github.com/emilk/egui/issues/2520#issuecomment-1367841150
.with_window_builder(Some(winit_window_builder.clone()))
.build(
event_loop,
config_template_builder,
|mut config_iterator| {
config_iterator.next().expect(
"failed to find a matching configuration for creating glutin config",
)
},
)
.expect("failed to create gl_config");
let gl_display = gl_config.display();
tracing::debug!("found gl_config: {:?}", &gl_config);
let raw_window_handle = window.as_ref().map(|w| w.raw_window_handle());
tracing::debug!("raw window handle: {:?}", raw_window_handle);
let context_attributes =
glutin::context::ContextAttributesBuilder::new().build(raw_window_handle);
// by default, glutin will try to create a core opengl context. but, if it is not available, try to create a gl-es context using this fallback attributes
let fallback_context_attributes = glutin::context::ContextAttributesBuilder::new()
.with_context_api(glutin::context::ContextApi::Gles(None))
.build(raw_window_handle);
let not_current_gl_context = unsafe {
gl_display
.create_context(&gl_config, &context_attributes)
.unwrap_or_else(|_| {
tracing::debug!("failed to create gl_context with attributes: {:?}. retrying with fallback context attributes: {:?}",
&context_attributes,
&fallback_context_attributes);
gl_config
.display()
.create_context(&gl_config, &fallback_context_attributes)
.expect("failed to create context even with fallback attributes")
})
};
// this is where the window is created, if it has not been created while searching for suitable gl_config
let window = window.take().unwrap_or_else(|| {
tracing::debug!("window doesn't exist yet. creating one now with finalize_window");
glutin_winit::finalize_window(event_loop, winit_window_builder.clone(), &gl_config)
.expect("failed to finalize glutin window")
});
let (width, height): (u32, u32) = window.inner_size().into();
let width = std::num::NonZeroU32::new(width.at_least(1)).unwrap();
let height = std::num::NonZeroU32::new(height.at_least(1)).unwrap();
let surface_attributes =
glutin::surface::SurfaceAttributesBuilder::<glutin::surface::WindowSurface>::new()
.build(window.raw_window_handle(), width, height);
tracing::debug!(
"creating surface with attributes: {:?}",
&surface_attributes
);
let gl_surface = unsafe {
gl_display
.create_window_surface(&gl_config, &surface_attributes)
.unwrap()
};
tracing::debug!("surface created successfully: {gl_surface:?}.making context current");
let gl_context = not_current_gl_context.make_current(&gl_surface).unwrap();
gl_surface
.set_swap_interval(
&gl_context,
glutin::surface::SwapInterval::Wait(std::num::NonZeroU32::new(1).unwrap()),
)
.unwrap();
GlutinWindowContext {
window,
gl_context,
gl_display,
gl_surface,
}
}
fn window(&self) -> &winit::window::Window {
&self.window
}
fn resize(&self, physical_size: winit::dpi::PhysicalSize<u32>) {
use glutin::surface::GlSurface;
self.gl_surface.resize(
&self.gl_context,
physical_size.width.try_into().unwrap(),
physical_size.height.try_into().unwrap(),
);
}
fn swap_buffers(&self) -> glutin::error::Result<()> {
use glutin::surface::GlSurface;
self.gl_surface.swap_buffers(&self.gl_context)
}
fn get_proc_address(&self, addr: &std::ffi::CStr) -> *const std::ffi::c_void {
use glutin::display::GlDisplay;
self.gl_display.get_proc_address(addr)
}
}
fn main() {
let mut clear_color = [0.1, 0.1, 0.1];
let event_loop = winit::event_loop::EventLoopBuilder::with_user_event().build();
let (gl_window, gl) = create_display(&event_loop);
let gl = std::sync::Arc::new(gl);
let mut egui_glow = egui_glow::EguiGlow::new(&event_loop, gl.clone(), None);
event_loop.run(move |event, _, control_flow| {
let mut redraw = || {
let mut quit = false;
let repaint_after = egui_glow.run(gl_window.window(), |egui_ctx| {
egui::SidePanel::left("my_side_panel").show(egui_ctx, |ui| {
ui.heading("Hello World!");
if ui.button("Quit").clicked() {
quit = true;
}
ui.color_edit_button_rgb(&mut clear_color);
});
});
*control_flow = if quit {
winit::event_loop::ControlFlow::Exit
} else if repaint_after.is_zero() {
gl_window.window().request_redraw();
winit::event_loop::ControlFlow::Poll
} else if let Some(repaint_after_instant) =
std::time::Instant::now().checked_add(repaint_after)
{
winit::event_loop::ControlFlow::WaitUntil(repaint_after_instant)
} else {
winit::event_loop::ControlFlow::Wait
};
{
unsafe {
use glow::HasContext as _;
gl.clear_color(clear_color[0], clear_color[1], clear_color[2], 1.0);
gl.clear(glow::COLOR_BUFFER_BIT);
}
// draw things behind egui here
egui_glow.paint(gl_window.window());
// draw things on top of egui here
gl_window.swap_buffers().unwrap();
gl_window.window().set_visible(true);
}
};
match event {
// Platform-dependent event handlers to workaround a winit bug
// See: https://github.com/rust-windowing/winit/issues/987
// See: https://github.com/rust-windowing/winit/issues/1619
winit::event::Event::RedrawEventsCleared if cfg!(windows) => redraw(),
winit::event::Event::RedrawRequested(_) if !cfg!(windows) => redraw(),
winit::event::Event::WindowEvent { event, .. } => {
use winit::event::WindowEvent;
if matches!(event, WindowEvent::CloseRequested | WindowEvent::Destroyed) {
*control_flow = winit::event_loop::ControlFlow::Exit;
}
if let winit::event::WindowEvent::Resized(physical_size) = &event {
gl_window.resize(*physical_size);
} else if let winit::event::WindowEvent::ScaleFactorChanged {
new_inner_size, ..
} = &event
{
gl_window.resize(**new_inner_size);
}
let event_response = egui_glow.on_event(&event);
if event_response.repaint {
gl_window.window().request_redraw();
}
}
winit::event::Event::LoopDestroyed => {
egui_glow.destroy();
}
winit::event::Event::NewEvents(winit::event::StartCause::ResumeTimeReached {
..
}) => {
gl_window.window().request_redraw();
}
_ => (),
}
});
}
fn create_display(
event_loop: &winit::event_loop::EventLoopWindowTarget<()>,
) -> (GlutinWindowContext, glow::Context) {
let glutin_window_context = unsafe { GlutinWindowContext::new(event_loop) };
let gl = unsafe {
glow::Context::from_loader_function(|s| {
let s = std::ffi::CString::new(s)
.expect("failed to construct C string from string for gl proc address");
glutin_window_context.get_proc_address(&s)
})
};
(glutin_window_context, gl)
}

View file

@ -0,0 +1,133 @@
//! [`egui`] bindings for [`glow`](https://github.com/grovesNL/glow).
//!
//! The main types you want to look are are [`Painter`] and [`EguiGlow`].
//!
//! If you are writing an app, you may want to look at [`eframe`](https://docs.rs/eframe) instead.
//!
//! ## Feature flags
#![cfg_attr(feature = "document-features", doc = document_features::document_features!())]
//!
#![allow(clippy::float_cmp)]
#![allow(clippy::manual_range_contains)]
pub mod painter;
pub use glow;
pub use painter::{CallbackFn, Painter};
mod misc_util;
mod shader_version;
mod vao;
pub use shader_version::ShaderVersion;
#[cfg(all(not(target_arch = "wasm32"), feature = "winit"))]
pub mod winit;
#[cfg(all(not(target_arch = "wasm32"), feature = "winit"))]
pub use winit::*;
/// Check for OpenGL error and report it using `tracing::error`.
///
/// Only active in debug builds!
///
/// ``` no_run
/// # let glow_context = todo!();
/// use egui_glow::check_for_gl_error;
/// check_for_gl_error!(glow_context);
/// check_for_gl_error!(glow_context, "during painting");
/// ```
#[macro_export]
macro_rules! check_for_gl_error {
($gl: expr) => {{
if cfg!(debug_assertions) {
$crate::check_for_gl_error_impl($gl, file!(), line!(), "")
}
}};
($gl: expr, $context: literal) => {{
if cfg!(debug_assertions) {
$crate::check_for_gl_error_impl($gl, file!(), line!(), $context)
}
}};
}
/// Check for OpenGL error and report it using `tracing::error`.
///
/// WARNING: slow! Only use during setup!
///
/// ``` no_run
/// # let glow_context = todo!();
/// use egui_glow::check_for_gl_error_even_in_release;
/// check_for_gl_error_even_in_release!(glow_context);
/// check_for_gl_error_even_in_release!(glow_context, "during painting");
/// ```
#[macro_export]
macro_rules! check_for_gl_error_even_in_release {
($gl: expr) => {{
$crate::check_for_gl_error_impl($gl, file!(), line!(), "")
}};
($gl: expr, $context: literal) => {{
$crate::check_for_gl_error_impl($gl, file!(), line!(), $context)
}};
}
#[doc(hidden)]
pub fn check_for_gl_error_impl(gl: &glow::Context, file: &str, line: u32, context: &str) {
use glow::HasContext as _;
#[allow(unsafe_code)]
let error_code = unsafe { gl.get_error() };
if error_code != glow::NO_ERROR {
let error_str = match error_code {
glow::INVALID_ENUM => "GL_INVALID_ENUM",
glow::INVALID_VALUE => "GL_INVALID_VALUE",
glow::INVALID_OPERATION => "GL_INVALID_OPERATION",
glow::STACK_OVERFLOW => "GL_STACK_OVERFLOW",
glow::STACK_UNDERFLOW => "GL_STACK_UNDERFLOW",
glow::OUT_OF_MEMORY => "GL_OUT_OF_MEMORY",
glow::INVALID_FRAMEBUFFER_OPERATION => "GL_INVALID_FRAMEBUFFER_OPERATION",
glow::CONTEXT_LOST => "GL_CONTEXT_LOST",
0x8031 => "GL_TABLE_TOO_LARGE1",
0x9242 => "CONTEXT_LOST_WEBGL",
_ => "<unknown>",
};
if context.is_empty() {
tracing::error!(
"GL error, at {}:{}: {} (0x{:X}). Please file a bug at https://github.com/emilk/egui/issues",
file,
line,
error_str,
error_code,
);
} else {
tracing::error!(
"GL error, at {}:{} ({}): {} (0x{:X}). Please file a bug at https://github.com/emilk/egui/issues",
file,
line,
context,
error_str,
error_code,
);
}
}
}
// ---------------------------------------------------------------------------
/// Profiling macro for feature "puffin"
macro_rules! profile_function {
($($arg: tt)*) => {
#[cfg(feature = "puffin")]
#[cfg(not(target_arch = "wasm32"))]
puffin::profile_function!($($arg)*);
};
}
pub(crate) use profile_function;
/// Profiling macro for feature "puffin"
macro_rules! profile_scope {
($($arg: tt)*) => {
#[cfg(feature = "puffin")]
#[cfg(not(target_arch = "wasm32"))]
puffin::profile_scope!($($arg)*);
};
}
pub(crate) use profile_scope;

View file

@ -0,0 +1,40 @@
#![allow(unsafe_code)]
use glow::HasContext as _;
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))
}
}

View file

@ -0,0 +1,755 @@
#![allow(clippy::collapsible_else_if)]
#![allow(unsafe_code)]
use std::{collections::HashMap, sync::Arc};
use egui::{
emath::Rect,
epaint::{Mesh, PaintCallbackInfo, Primitive, Vertex},
};
use glow::HasContext as _;
use memoffset::offset_of;
use crate::check_for_gl_error;
use crate::misc_util::{compile_shader, link_program};
use crate::shader_version::ShaderVersion;
use crate::vao;
pub use glow::Context;
const VERT_SRC: &str = include_str!("shader/vertex.glsl");
const FRAG_SRC: &str = include_str!("shader/fragment.glsl");
trait TextureFilterExt {
fn glow_code(&self) -> u32;
}
impl TextureFilterExt for egui::TextureFilter {
fn glow_code(&self) -> u32 {
match self {
egui::TextureFilter::Linear => glow::LINEAR,
egui::TextureFilter::Nearest => glow::NEAREST,
}
}
}
/// An OpenGL painter using [`glow`].
///
/// This is responsible for painting egui and managing egui textures.
/// You can access the underlying [`glow::Context`] with [`Self::gl`].
///
/// 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 {
gl: Arc<glow::Context>,
max_texture_side: usize,
program: glow::Program,
u_screen_size: glow::UniformLocation,
u_sampler: glow::UniformLocation,
is_webgl_1: bool,
vao: crate::vao::VertexArrayObject,
srgb_textures: bool,
vbo: glow::Buffer,
element_array_buffer: glow::Buffer,
textures: HashMap<egui::TextureId, glow::Texture>,
next_native_tex_id: u64,
/// Stores outdated OpenGL textures that are yet to be deleted
textures_to_destroy: Vec<glow::Texture>,
/// Used to make sure we are destroyed correctly.
destroyed: bool,
}
/// A callback function that can be used to compose an [`egui::PaintCallback`] for custom rendering
/// with [`glow`].
///
/// The callback is passed, the [`egui::PaintCallbackInfo`] and the [`Painter`] which can be used to
/// access the OpenGL context.
///
/// # Example
///
/// See the [`custom3d_glow`](https://github.com/emilk/egui/blob/master/crates/egui_demo_app/src/apps/custom3d_wgpu.rs) demo source for a detailed usage example.
pub struct CallbackFn {
f: Box<dyn Fn(PaintCallbackInfo, &Painter) + Sync + Send>,
}
impl CallbackFn {
pub fn new<F: Fn(PaintCallbackInfo, &Painter) + Sync + Send + 'static>(callback: F) -> Self {
let f = Box::new(callback);
CallbackFn { f }
}
}
impl Painter {
/// Create painter.
///
/// Set `pp_fb_extent` to the framebuffer size to enable `sRGB` support on OpenGL ES and WebGL.
///
/// Set `shader_prefix` if you want to turn on shader workaround e.g. `"#define APPLY_BRIGHTENING_GAMMA\n"`
/// (see <https://github.com/emilk/egui/issues/794>).
///
/// # 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: Arc<glow::Context>,
shader_prefix: &str,
shader_version: Option<ShaderVersion>,
) -> Result<Painter, String> {
crate::profile_function!();
crate::check_for_gl_error_even_in_release!(&gl, "before Painter::new");
// some useful debug info. all three of them are present in gl 1.1.
unsafe {
let version = gl.get_parameter_string(glow::VERSION);
let renderer = gl.get_parameter_string(glow::RENDERER);
let vendor = gl.get_parameter_string(glow::VENDOR);
tracing::debug!(
"\nopengl version: {version}\nopengl renderer: {renderer}\nopengl vendor: {vendor}"
);
}
#[cfg(not(target_arch = "wasm32"))]
if gl.version().major < 2 {
// this checks on desktop that we are not using opengl 1.1 microsoft sw rendering context.
// ShaderVersion::get fn will segfault due to SHADING_LANGUAGE_VERSION (added in gl2.0)
return Err("egui_glow requires opengl 2.0+. ".to_owned());
}
let max_texture_side = unsafe { gl.get_parameter_i32(glow::MAX_TEXTURE_SIZE) } as usize;
let shader_version = shader_version.unwrap_or_else(|| ShaderVersion::get(&gl));
let is_webgl_1 = shader_version == ShaderVersion::Es100;
let shader_version_declaration = shader_version.version_declaration();
tracing::debug!("Shader header: {:?}.", shader_version_declaration);
let supported_extensions = gl.supported_extensions();
tracing::trace!("OpenGL extensions: {supported_extensions:?}");
let srgb_textures = shader_version == ShaderVersion::Es300 // WebGL2 always support sRGB
|| supported_extensions.iter().any(|extension| {
// EXT_sRGB, GL_ARB_framebuffer_sRGB, GL_EXT_sRGB, GL_EXT_texture_sRGB_decode, …
extension.contains("sRGB")
});
tracing::debug!("SRGB texture Support: {:?}", srgb_textures);
unsafe {
let vert = compile_shader(
&gl,
glow::VERTEX_SHADER,
&format!(
"{}\n#define NEW_SHADER_INTERFACE {}\n{}\n{}",
shader_version_declaration,
shader_version.is_new_shader_interface() as i32,
shader_prefix,
VERT_SRC
),
)?;
let frag = compile_shader(
&gl,
glow::FRAGMENT_SHADER,
&format!(
"{}\n#define NEW_SHADER_INTERFACE {}\n#define SRGB_TEXTURES {}\n{}\n{}",
shader_version_declaration,
shader_version.is_new_shader_interface() as i32,
srgb_textures as i32,
shader_prefix,
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 vbo = gl.create_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();
let stride = std::mem::size_of::<Vertex>() as i32;
let buffer_infos = vec![
vao::BufferInfo {
location: a_pos_loc,
vector_size: 2,
data_type: glow::FLOAT,
normalized: false,
stride,
offset: offset_of!(Vertex, pos) as i32,
},
vao::BufferInfo {
location: a_tc_loc,
vector_size: 2,
data_type: glow::FLOAT,
normalized: false,
stride,
offset: offset_of!(Vertex, uv) as i32,
},
vao::BufferInfo {
location: a_srgba_loc,
vector_size: 4,
data_type: glow::UNSIGNED_BYTE,
normalized: false,
stride,
offset: offset_of!(Vertex, color) as i32,
},
];
let vao = crate::vao::VertexArrayObject::new(&gl, vbo, buffer_infos);
let element_array_buffer = gl.create_buffer()?;
crate::check_for_gl_error_even_in_release!(&gl, "after Painter::new");
Ok(Painter {
gl,
max_texture_side,
program,
u_screen_size,
u_sampler,
is_webgl_1,
vao,
srgb_textures,
vbo,
element_array_buffer,
textures: Default::default(),
next_native_tex_id: 1 << 32,
textures_to_destroy: Vec::new(),
destroyed: false,
})
}
}
/// Access the shared glow context.
pub fn gl(&self) -> &Arc<glow::Context> {
&self.gl
}
pub fn max_texture_side(&self) -> usize {
self.max_texture_side
}
/// The framebuffer we use as an intermediate render target,
/// or `None` if we are painting to the screen framebuffer directly.
///
/// This is the framebuffer that is bound when [`egui::Shape::Callback`] is called,
/// and is where any callbacks should ultimately render onto.
///
/// So if in a [`egui::Shape::Callback`] you need to use an offscreen FBO, you should
/// then restore to this afterwards with
/// `gl.bind_framebuffer(glow::FRAMEBUFFER, painter.intermediate_fbo());`
#[allow(clippy::unused_self)]
pub fn intermediate_fbo(&self) -> Option<glow::Framebuffer> {
// We don't currently ever render to an offscreen buffer,
// but we may want to start to in order to do anti-aliasing on web, for instance.
None
}
unsafe fn prepare_painting(
&mut self,
[width_in_pixels, height_in_pixels]: [u32; 2],
pixels_per_point: f32,
) -> (u32, u32) {
self.gl.enable(glow::SCISSOR_TEST);
// egui outputs mesh in both winding orders
self.gl.disable(glow::CULL_FACE);
self.gl.disable(glow::DEPTH_TEST);
self.gl.color_mask(true, true, true, true);
self.gl.enable(glow::BLEND);
self.gl
.blend_equation_separate(glow::FUNC_ADD, glow::FUNC_ADD);
self.gl.blend_func_separate(
// egui outputs colors with premultiplied alpha:
glow::ONE,
glow::ONE_MINUS_SRC_ALPHA,
// Less important, but this is technically the correct alpha blend function
// when you want to make use of the framebuffer alpha (for screenshots, compositing, etc).
glow::ONE_MINUS_DST_ALPHA,
glow::ONE,
);
if !cfg!(target_arch = "wasm32") {
self.gl.disable(glow::FRAMEBUFFER_SRGB);
check_for_gl_error!(&self.gl, "FRAMEBUFFER_SRGB");
}
let width_in_points = width_in_pixels as f32 / pixels_per_point;
let height_in_points = height_in_pixels as f32 / pixels_per_point;
self.gl
.viewport(0, 0, width_in_pixels as i32, height_in_pixels as i32);
self.gl.use_program(Some(self.program));
self.gl
.uniform_2_f32(Some(&self.u_screen_size), width_in_points, height_in_points);
self.gl.uniform_1_i32(Some(&self.u_sampler), 0);
self.gl.active_texture(glow::TEXTURE0);
self.vao.bind(&self.gl);
self.gl
.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(self.element_array_buffer));
check_for_gl_error!(&self.gl, "prepare_painting");
(width_in_pixels, height_in_pixels)
}
/// You are expected to have cleared the color buffer before calling this.
pub fn paint_and_update_textures(
&mut self,
screen_size_px: [u32; 2],
pixels_per_point: f32,
clipped_primitives: &[egui::ClippedPrimitive],
textures_delta: &egui::TexturesDelta,
) {
crate::profile_function!();
for (id, image_delta) in &textures_delta.set {
self.set_texture(*id, image_delta);
}
self.paint_primitives(screen_size_px, pixels_per_point, clipped_primitives);
for &id in &textures_delta.free {
self.free_texture(id);
}
}
/// Main entry-point for painting a frame.
///
/// You should call `target.clear_color(..)` before
/// and `target.finish()` after this.
///
/// The following OpenGL features will be set:
/// - Scissor test will be enabled
/// - Cull face will be disabled
/// - Blend will be enabled
///
/// The scissor area and blend parameters will be changed.
///
/// As well as this, the following objects will be unset:
/// - Vertex Buffer
/// - Element Buffer
/// - Texture (and active texture will be set to 0)
/// - Program
///
/// Please be mindful of these effects when integrating into your program, and also be mindful
/// of the effects your program might have on this code. Look at the source if in doubt.
pub fn paint_primitives(
&mut self,
screen_size_px: [u32; 2],
pixels_per_point: f32,
clipped_primitives: &[egui::ClippedPrimitive],
) {
crate::profile_function!();
self.assert_not_destroyed();
let size_in_pixels = unsafe { self.prepare_painting(screen_size_px, pixels_per_point) };
for egui::ClippedPrimitive {
clip_rect,
primitive,
} in clipped_primitives
{
set_clip_rect(&self.gl, size_in_pixels, pixels_per_point, *clip_rect);
match primitive {
Primitive::Mesh(mesh) => {
self.paint_mesh(mesh);
}
Primitive::Callback(callback) => {
if callback.rect.is_positive() {
crate::profile_scope!("callback");
// Transform callback rect to physical pixels:
let rect_min_x = pixels_per_point * callback.rect.min.x;
let rect_min_y = pixels_per_point * callback.rect.min.y;
let rect_max_x = pixels_per_point * callback.rect.max.x;
let rect_max_y = pixels_per_point * callback.rect.max.y;
let rect_min_x = rect_min_x.round() as i32;
let rect_min_y = rect_min_y.round() as i32;
let rect_max_x = rect_max_x.round() as i32;
let rect_max_y = rect_max_y.round() as i32;
unsafe {
self.gl.viewport(
rect_min_x,
size_in_pixels.1 as i32 - rect_max_y,
rect_max_x - rect_min_x,
rect_max_y - rect_min_y,
);
}
let info = egui::PaintCallbackInfo {
viewport: callback.rect,
clip_rect: *clip_rect,
pixels_per_point,
screen_size_px,
};
if let Some(callback) = callback.callback.downcast_ref::<CallbackFn>() {
(callback.f)(info, self);
} else {
tracing::warn!("Warning: Unsupported render callback. Expected egui_glow::CallbackFn");
}
check_for_gl_error!(&self.gl, "callback");
// Restore state:
unsafe { self.prepare_painting(screen_size_px, pixels_per_point) };
}
}
}
}
unsafe {
self.vao.unbind(&self.gl);
self.gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, None);
self.gl.disable(glow::SCISSOR_TEST);
check_for_gl_error!(&self.gl, "painting");
}
}
#[inline(never)] // Easier profiling
fn paint_mesh(&mut self, mesh: &Mesh) {
debug_assert!(mesh.is_valid());
if let Some(texture) = self.texture(mesh.texture_id) {
unsafe {
self.gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.vbo));
self.gl.buffer_data_u8_slice(
glow::ARRAY_BUFFER,
bytemuck::cast_slice(&mesh.vertices),
glow::STREAM_DRAW,
);
self.gl
.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(self.element_array_buffer));
self.gl.buffer_data_u8_slice(
glow::ELEMENT_ARRAY_BUFFER,
bytemuck::cast_slice(&mesh.indices),
glow::STREAM_DRAW,
);
self.gl.bind_texture(glow::TEXTURE_2D, Some(texture));
}
unsafe {
self.gl.draw_elements(
glow::TRIANGLES,
mesh.indices.len() as i32,
glow::UNSIGNED_INT,
0,
);
}
check_for_gl_error!(&self.gl, "paint_mesh");
} else {
tracing::warn!("Failed to find texture {:?}", mesh.texture_id);
}
}
// ------------------------------------------------------------------------
pub fn set_texture(&mut self, tex_id: egui::TextureId, delta: &egui::epaint::ImageDelta) {
crate::profile_function!();
self.assert_not_destroyed();
let glow_texture = *self
.textures
.entry(tex_id)
.or_insert_with(|| unsafe { self.gl.create_texture().unwrap() });
unsafe {
self.gl.bind_texture(glow::TEXTURE_2D, Some(glow_texture));
}
match &delta.image {
egui::ImageData::Color(image) => {
assert_eq!(
image.width() * image.height(),
image.pixels.len(),
"Mismatch between texture size and texel count"
);
let data: &[u8] = bytemuck::cast_slice(image.pixels.as_ref());
self.upload_texture_srgb(delta.pos, image.size, delta.options, data);
}
egui::ImageData::Font(image) => {
assert_eq!(
image.width() * image.height(),
image.pixels.len(),
"Mismatch between texture size and texel count"
);
let data: Vec<u8> = image
.srgba_pixels(None)
.flat_map(|a| a.to_array())
.collect();
self.upload_texture_srgb(delta.pos, image.size, delta.options, &data);
}
};
}
fn upload_texture_srgb(
&mut self,
pos: Option<[usize; 2]>,
[w, h]: [usize; 2],
options: egui::TextureOptions,
data: &[u8],
) {
assert_eq!(data.len(), w * h * 4);
assert!(
w <= self.max_texture_side && h <= self.max_texture_side,
"Got a texture image of size {}x{}, but the maximum supported texture side is only {}",
w,
h,
self.max_texture_side
);
unsafe {
self.gl.tex_parameter_i32(
glow::TEXTURE_2D,
glow::TEXTURE_MAG_FILTER,
options.magnification.glow_code() as i32,
);
self.gl.tex_parameter_i32(
glow::TEXTURE_2D,
glow::TEXTURE_MIN_FILTER,
options.minification.glow_code() as i32,
);
self.gl.tex_parameter_i32(
glow::TEXTURE_2D,
glow::TEXTURE_WRAP_S,
glow::CLAMP_TO_EDGE as i32,
);
self.gl.tex_parameter_i32(
glow::TEXTURE_2D,
glow::TEXTURE_WRAP_T,
glow::CLAMP_TO_EDGE as i32,
);
check_for_gl_error!(&self.gl, "tex_parameter");
let (internal_format, src_format) = if self.is_webgl_1 {
let format = if self.srgb_textures {
glow::SRGB_ALPHA
} else {
glow::RGBA
};
(format, format)
} else if self.srgb_textures {
(glow::SRGB8_ALPHA8, glow::RGBA)
} else {
(glow::RGBA8, glow::RGBA)
};
self.gl.pixel_store_i32(glow::UNPACK_ALIGNMENT, 1);
let level = 0;
if let Some([x, y]) = pos {
self.gl.tex_sub_image_2d(
glow::TEXTURE_2D,
level,
x as _,
y as _,
w as _,
h as _,
src_format,
glow::UNSIGNED_BYTE,
glow::PixelUnpackData::Slice(data),
);
check_for_gl_error!(&self.gl, "tex_sub_image_2d");
} else {
let border = 0;
self.gl.tex_image_2d(
glow::TEXTURE_2D,
level,
internal_format as _,
w as _,
h as _,
border,
src_format,
glow::UNSIGNED_BYTE,
Some(data),
);
check_for_gl_error!(&self.gl, "tex_image_2d");
}
}
}
pub fn free_texture(&mut self, tex_id: egui::TextureId) {
if let Some(old_tex) = self.textures.remove(&tex_id) {
unsafe { self.gl.delete_texture(old_tex) };
}
}
/// Get the [`glow::Texture`] bound to a [`egui::TextureId`].
pub fn texture(&self, texture_id: egui::TextureId) -> Option<glow::Texture> {
self.textures.get(&texture_id).copied()
}
#[deprecated = "renamed 'texture'"]
pub fn get_texture(&self, texture_id: egui::TextureId) -> Option<glow::Texture> {
self.texture(texture_id)
}
#[allow(clippy::needless_pass_by_value)] // False positive
pub fn register_native_texture(&mut self, native: glow::Texture) -> egui::TextureId {
self.assert_not_destroyed();
let id = egui::TextureId::User(self.next_native_tex_id);
self.next_native_tex_id += 1;
self.textures.insert(id, native);
id
}
#[allow(clippy::needless_pass_by_value)] // False positive
pub fn replace_native_texture(&mut self, id: egui::TextureId, replacing: glow::Texture) {
if let Some(old_tex) = self.textures.insert(id, replacing) {
self.textures_to_destroy.push(old_tex);
}
}
pub fn read_screen_rgba(&self, [w, h]: [u32; 2]) -> egui::ColorImage {
let mut pixels = vec![0_u8; (w * h * 4) as usize];
unsafe {
self.gl.read_pixels(
0,
0,
w as _,
h as _,
glow::RGBA,
glow::UNSIGNED_BYTE,
glow::PixelPackData::Slice(&mut pixels),
);
}
let mut flipped = Vec::with_capacity((w * h * 4) as usize);
for row in pixels.chunks_exact((w * 4) as usize).rev() {
flipped.extend_from_slice(bytemuck::cast_slice(row));
}
egui::ColorImage {
size: [w as usize, h as usize],
pixels: flipped,
}
}
pub fn read_screen_rgb(&self, [w, h]: [u32; 2]) -> Vec<u8> {
let mut pixels = vec![0_u8; (w * h * 3) as usize];
unsafe {
self.gl.read_pixels(
0,
0,
w as _,
h as _,
glow::RGB,
glow::UNSIGNED_BYTE,
glow::PixelPackData::Slice(&mut pixels),
);
}
pixels
}
unsafe fn destroy_gl(&self) {
self.gl.delete_program(self.program);
for tex in self.textures.values() {
self.gl.delete_texture(*tex);
}
self.gl.delete_buffer(self.vbo);
self.gl.delete_buffer(self.element_array_buffer);
for t in &self.textures_to_destroy {
self.gl.delete_texture(*t);
}
}
/// This function must be called before [`Painter`] is dropped, as [`Painter`] has some OpenGL objects
/// that should be deleted.
pub fn destroy(&mut self) {
if !self.destroyed {
unsafe {
self.destroy_gl();
}
self.destroyed = true;
}
}
fn assert_not_destroyed(&self) {
assert!(!self.destroyed, "the egui glow has already been destroyed!");
}
}
pub fn clear(gl: &glow::Context, screen_size_in_pixels: [u32; 2], clear_color: [f32; 4]) {
crate::profile_function!();
unsafe {
gl.disable(glow::SCISSOR_TEST);
gl.viewport(
0,
0,
screen_size_in_pixels[0] as i32,
screen_size_in_pixels[1] as i32,
);
gl.clear_color(
clear_color[0],
clear_color[1],
clear_color[2],
clear_color[3],
);
gl.clear(glow::COLOR_BUFFER_BIT);
}
}
impl Drop for Painter {
fn drop(&mut self) {
if !self.destroyed {
tracing::warn!(
"You forgot to call destroy() on the egui glow painter. Resources will leak!"
);
}
}
}
fn set_clip_rect(
gl: &glow::Context,
size_in_pixels: (u32, u32),
pixels_per_point: f32,
clip_rect: Rect,
) {
// 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;
let clip_max_x = pixels_per_point * clip_rect.max.x;
let clip_max_y = pixels_per_point * clip_rect.max.y;
// Round to integer:
let clip_min_x = clip_min_x.round() as i32;
let clip_min_y = clip_min_y.round() as i32;
let clip_max_x = clip_max_x.round() as i32;
let clip_max_y = clip_max_y.round() as i32;
// Clamp:
let clip_min_x = clip_min_x.clamp(0, size_in_pixels.0 as i32);
let clip_min_y = clip_min_y.clamp(0, size_in_pixels.1 as i32);
let clip_max_x = clip_max_x.clamp(clip_min_x, size_in_pixels.0 as i32);
let clip_max_y = clip_max_y.clamp(clip_min_y, size_in_pixels.1 as i32);
unsafe {
gl.scissor(
clip_min_x,
size_in_pixels.1 as i32 - clip_max_y,
clip_max_x - clip_min_x,
clip_max_y - clip_min_y,
);
}
}

View file

@ -0,0 +1,41 @@
#ifdef GL_ES
precision mediump float;
#endif
uniform sampler2D u_sampler;
#if NEW_SHADER_INTERFACE
in vec4 v_rgba_in_gamma;
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_in_gamma;
varying vec2 v_tc;
#endif
// 0-1 sRGB gamma from 0-1 linear
vec3 srgb_gamma_from_linear(vec3 rgb) {
bvec3 cutoff = lessThan(rgb, vec3(0.0031308));
vec3 lower = rgb * vec3(12.92);
vec3 higher = vec3(1.055) * pow(rgb, vec3(1.0 / 2.4)) - vec3(0.055);
return mix(higher, lower, vec3(cutoff));
}
// 0-1 sRGBA gamma from 0-1 linear
vec4 srgba_gamma_from_linear(vec4 rgba) {
return vec4(srgb_gamma_from_linear(rgba.rgb), rgba.a);
}
void main() {
#if SRGB_TEXTURES
vec4 texture_in_gamma = srgba_gamma_from_linear(texture2D(u_sampler, v_tc));
#else
vec4 texture_in_gamma = texture2D(u_sampler, v_tc);
#endif
// We multiply the colors in gamma space, because that's the only way to get text to look right.
gl_FragColor = v_rgba_in_gamma * texture_in_gamma;
}

View file

@ -0,0 +1,30 @@
#if NEW_SHADER_INTERFACE
#define I in
#define O out
#define V(x) x
#else
#define I attribute
#define O varying
#define V(x) vec3(x)
#endif
#ifdef GL_ES
precision mediump float;
#endif
uniform vec2 u_screen_size;
I vec2 a_pos;
I vec4 a_srgba; // 0-255 sRGB
I vec2 a_tc;
O vec4 v_rgba_in_gamma;
O vec2 v_tc;
void main() {
gl_Position = vec4(
2.0 * a_pos.x / u_screen_size.x - 1.0,
1.0 - 2.0 * a_pos.y / u_screen_size.y,
0.0,
1.0);
v_rgba_in_gamma = a_srgba / 255.0;
v_tc = a_tc;
}

View file

@ -0,0 +1,104 @@
#![allow(unsafe_code)]
use std::convert::TryInto;
/// Helper for parsing and interpreting the OpenGL shader version.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[allow(dead_code)]
pub enum ShaderVersion {
Gl120,
/// OpenGL 1.4 or later
Gl140,
/// e.g. WebGL1
Es100,
/// e.g. WebGL2
Es300,
}
impl ShaderVersion {
pub fn get(gl: &glow::Context) -> Self {
use glow::HasContext as _;
let shading_lang_string =
unsafe { gl.get_parameter_string(glow::SHADING_LANGUAGE_VERSION) };
let shader_version = Self::parse(&shading_lang_string);
tracing::debug!(
"Shader version: {:?} ({:?}).",
shader_version,
shading_lang_string
);
shader_version
}
#[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..]
.split_once(' ')
.map_or(&glsl_ver[start..], |x| x.0);
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
}
}
/// Goes on top of the shader.
pub fn version_declaration(&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",
}
}
/// If true, use `in/out`. If `false`, use `varying` and `gl_FragColor`.
pub fn is_new_shader_interface(&self) -> bool {
match self {
Self::Gl120 | Self::Es100 => false,
Self::Es300 | Self::Gl140 => true,
}
}
pub fn is_embedded(&self) -> bool {
match self {
Self::Gl120 | Self::Gl140 => false,
Self::Es100 | Self::Es300 => true,
}
}
}
#[test]
fn test_shader_version() {
use ShaderVersion::{Es100, Es300, Gl120, Gl140};
for (s, v) in [
("1.2 OpenGL foo bar", Gl120),
("3.0", Gl140),
("0.0", Gl120),
("OpenGL ES GLSL 3.00 (WebGL2)", Es300),
("OpenGL ES GLSL 1.00 (WebGL)", Es100),
("OpenGL ES GLSL ES 1.00 foo bar", Es100),
("WebGL GLSL ES 3.00 foo bar", Es300),
("WebGL GLSL ES 3.00", Es300),
("WebGL GLSL ES 1.0 foo bar", Es100),
] {
assert_eq!(ShaderVersion::parse(s), v);
}
}

View file

@ -0,0 +1,157 @@
#![allow(unsafe_code)]
use glow::HasContext as _;
use crate::check_for_gl_error;
// ----------------------------------------------------------------------------
#[derive(Debug)]
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,
}
// ----------------------------------------------------------------------------
/// Wrapper around either Emulated VAO or GL's VAO.
pub(crate) struct VertexArrayObject {
// If `None`, we emulate VAO:s.
vao: Option<crate::glow::VertexArray>,
vbo: glow::Buffer,
buffer_infos: Vec<BufferInfo>,
}
impl VertexArrayObject {
#[allow(clippy::needless_pass_by_value)] // false positive
pub(crate) unsafe fn new(
gl: &glow::Context,
vbo: glow::Buffer,
buffer_infos: Vec<BufferInfo>,
) -> Self {
let vao = if supports_vao(gl) {
let vao = gl.create_vertex_array().unwrap();
check_for_gl_error!(gl, "create_vertex_array");
// Store state in the VAO:
gl.bind_vertex_array(Some(vao));
gl.bind_buffer(glow::ARRAY_BUFFER, Some(vbo));
for attribute in &buffer_infos {
gl.vertex_attrib_pointer_f32(
attribute.location,
attribute.vector_size,
attribute.data_type,
attribute.normalized,
attribute.stride,
attribute.offset,
);
check_for_gl_error!(gl, "vertex_attrib_pointer_f32");
gl.enable_vertex_attrib_array(attribute.location);
check_for_gl_error!(gl, "enable_vertex_attrib_array");
}
gl.bind_vertex_array(None);
Some(vao)
} else {
tracing::debug!("VAO not supported");
None
};
Self {
vao,
vbo,
buffer_infos,
}
}
pub(crate) unsafe fn bind(&self, gl: &glow::Context) {
if let Some(vao) = self.vao {
gl.bind_vertex_array(Some(vao));
check_for_gl_error!(gl, "bind_vertex_array");
} else {
gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.vbo));
check_for_gl_error!(gl, "bind_buffer");
for attribute in &self.buffer_infos {
gl.vertex_attrib_pointer_f32(
attribute.location,
attribute.vector_size,
attribute.data_type,
attribute.normalized,
attribute.stride,
attribute.offset,
);
check_for_gl_error!(gl, "vertex_attrib_pointer_f32");
gl.enable_vertex_attrib_array(attribute.location);
check_for_gl_error!(gl, "enable_vertex_attrib_array");
}
}
}
pub(crate) unsafe fn unbind(&self, gl: &glow::Context) {
if self.vao.is_some() {
gl.bind_vertex_array(None);
} else {
gl.bind_buffer(glow::ARRAY_BUFFER, None);
for attribute in &self.buffer_infos {
gl.disable_vertex_attrib_array(attribute.location);
}
}
}
}
// ----------------------------------------------------------------------------
fn supports_vao(gl: &glow::Context) -> bool {
const WEBGL_PREFIX: &str = "WebGL ";
const OPENGL_ES_PREFIX: &str = "OpenGL ES ";
let version_string = unsafe { gl.get_parameter_string(glow::VERSION) };
tracing::debug!("GL version: {:?}.", version_string);
// Examples:
// * "WebGL 2.0 (OpenGL ES 3.0 Chromium)"
// * "WebGL 2.0"
if let Some(pos) = version_string.rfind(WEBGL_PREFIX) {
let version_str = &version_string[pos + WEBGL_PREFIX.len()..];
if version_str.contains("1.0") {
// need to test OES_vertex_array_object .
let supported_extensions = gl.supported_extensions();
tracing::debug!("Supported OpenGL extensions: {:?}", supported_extensions);
supported_extensions.contains("OES_vertex_array_object")
|| supported_extensions.contains("GL_OES_vertex_array_object")
} else {
true
}
} else if version_string.contains(OPENGL_ES_PREFIX) {
// glow targets es2.0+ so we don't concern about OpenGL ES-CM,OpenGL ES-CL
if version_string.contains("2.0") {
// need to test OES_vertex_array_object .
let supported_extensions = gl.supported_extensions();
tracing::debug!("Supported OpenGL extensions: {:?}", supported_extensions);
supported_extensions.contains("OES_vertex_array_object")
|| supported_extensions.contains("GL_OES_vertex_array_object")
} else {
true
}
} else {
// 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.
let supported_extensions = gl.supported_extensions();
tracing::debug!("Supported OpenGL extensions: {:?}", supported_extensions);
supported_extensions.contains("ARB_vertex_array_object")
|| supported_extensions.contains("GL_ARB_vertex_array_object")
} else {
true
}
}
}

View file

@ -0,0 +1,92 @@
use crate::shader_version::ShaderVersion;
pub use egui_winit;
use egui_winit::winit;
pub use egui_winit::EventResponse;
/// Use [`egui`] from a [`glow`] app based on [`winit`].
pub struct EguiGlow {
pub egui_ctx: egui::Context,
pub egui_winit: egui_winit::State,
pub painter: crate::Painter,
shapes: Vec<egui::epaint::ClippedShape>,
textures_delta: egui::TexturesDelta,
}
impl EguiGlow {
/// For automatic shader version detection set `shader_version` to `None`.
pub fn new<E>(
event_loop: &winit::event_loop::EventLoopWindowTarget<E>,
gl: std::sync::Arc<glow::Context>,
shader_version: Option<ShaderVersion>,
) -> Self {
let painter = crate::Painter::new(gl, "", shader_version)
.map_err(|error| {
tracing::error!("error occurred in initializing painter:\n{}", error);
})
.unwrap();
Self {
egui_ctx: Default::default(),
egui_winit: egui_winit::State::new(event_loop),
painter,
shapes: Default::default(),
textures_delta: Default::default(),
}
}
pub fn on_event(&mut self, event: &winit::event::WindowEvent<'_>) -> EventResponse {
self.egui_winit.on_event(&self.egui_ctx, event)
}
/// Returns the `Duration` of the timeout after which egui should be repainted even if there's no new events.
///
/// Call [`Self::paint`] later to paint.
pub fn run(
&mut self,
window: &winit::window::Window,
run_ui: impl FnMut(&egui::Context),
) -> std::time::Duration {
let raw_input = self.egui_winit.take_egui_input(window);
let egui::FullOutput {
platform_output,
repaint_after,
textures_delta,
shapes,
} = self.egui_ctx.run(raw_input, run_ui);
self.egui_winit
.handle_platform_output(window, &self.egui_ctx, platform_output);
self.shapes = shapes;
self.textures_delta.append(textures_delta);
repaint_after
}
/// Paint the results of the last call to [`Self::run`].
pub fn paint(&mut self, window: &winit::window::Window) {
let shapes = std::mem::take(&mut self.shapes);
let mut textures_delta = std::mem::take(&mut self.textures_delta);
for (id, image_delta) in textures_delta.set {
self.painter.set_texture(id, &image_delta);
}
let clipped_primitives = self.egui_ctx.tessellate(shapes);
let dimensions: [u32; 2] = window.inner_size().into();
self.painter.paint_primitives(
dimensions,
self.egui_ctx.pixels_per_point(),
&clipped_primitives,
);
for id in textures_delta.free.drain(..) {
self.painter.free_texture(id);
}
}
/// Call to release the allocated graphics resources.
pub fn destroy(&mut self) {
self.painter.destroy();
}
}