diff --git a/eframe/examples/custom_3d.rs b/eframe/examples/custom_3d.rs index f86d444e..45dedabb 100644 --- a/eframe/examples/custom_3d.rs +++ b/eframe/examples/custom_3d.rs @@ -75,7 +75,7 @@ impl MyApp { let callback = egui::PaintCallback { rect, - callback: std::sync::Arc::new(move |render_ctx| { + callback: std::sync::Arc::new(move |_info, render_ctx| { if let Some(painter) = render_ctx.downcast_ref::() { rotating_triangle.lock().paint(painter.gl(), angle); } else { diff --git a/egui/src/lib.rs b/egui/src/lib.rs index 129afaa1..2a097849 100644 --- a/egui/src/lib.rs +++ b/egui/src/lib.rs @@ -307,8 +307,8 @@ pub use epaint::{ color, mutex, text::{FontData, FontDefinitions, FontFamily, FontId, FontTweak}, textures::TexturesDelta, - AlphaImage, ClippedPrimitive, Color32, ColorImage, ImageData, Mesh, PaintCallback, Rgba, - Rounding, Shape, Stroke, TextureHandle, TextureId, + AlphaImage, ClippedPrimitive, Color32, ColorImage, ImageData, Mesh, PaintCallback, + PaintCallbackInfo, Rgba, Rounding, Shape, Stroke, TextureHandle, TextureId, }; pub mod text { diff --git a/egui_glow/src/painter.rs b/egui_glow/src/painter.rs index 69d5123d..ebbf94ce 100644 --- a/egui_glow/src/painter.rs +++ b/egui_glow/src/painter.rs @@ -37,12 +37,12 @@ pub struct Painter { u_sampler: glow::UniformLocation, is_webgl_1: bool, is_embedded: bool, - vertex_array: crate::vao::VAO, + vao: crate::vao::VertexArrayObject, srgb_support: bool, /// The filter used for subsequent textures. texture_filter: TextureFilter, post_process: Option, - vertex_buffer: glow::Buffer, + vbo: glow::Buffer, element_array_buffer: glow::Buffer, textures: HashMap, @@ -100,11 +100,6 @@ impl Painter { let max_texture_side = unsafe { gl.get_parameter_i32(glow::MAX_TEXTURE_SIZE) } as usize; - let support_vao = crate::vao::supports_vao(&gl); - if !support_vao { - tracing::debug!("VAO not supported"); - } - let shader_version = ShaderVersion::get(&gl); let is_webgl_1 = shader_version == ShaderVersion::Es100; let header = shader_version.version(); @@ -122,7 +117,6 @@ impl Painter { Some(PostProcess::new( gl.clone(), shader_prefix, - support_vao, is_webgl_1, width, height, @@ -173,47 +167,44 @@ impl Painter { gl.delete_shader(frag); let u_screen_size = gl.get_uniform_location(program, "u_screen_size").unwrap(); let u_sampler = gl.get_uniform_location(program, "u_sampler").unwrap(); - let vertex_buffer = gl.create_buffer()?; - let element_array_buffer = gl.create_buffer()?; - gl.bind_buffer(glow::ARRAY_BUFFER, Some(vertex_buffer)); + + 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 mut vertex_array = if support_vao { - crate::vao::VAO::native(&gl) - } else { - crate::vao::VAO::emulated() - }; - vertex_array.bind_vertex_array(&gl); - vertex_array.bind_buffer(&gl, &vertex_buffer); + let stride = std::mem::size_of::() as i32; - let position_buffer_info = vao::BufferInfo { - location: a_pos_loc, - vector_size: 2, - data_type: glow::FLOAT, - normalized: false, - stride, - offset: offset_of!(Vertex, pos) as i32, - }; - let tex_coord_buffer_info = vao::BufferInfo { - location: a_tc_loc, - vector_size: 2, - data_type: glow::FLOAT, - normalized: false, - stride, - offset: offset_of!(Vertex, uv) as i32, - }; - let color_buffer_info = vao::BufferInfo { - location: a_srgba_loc, - vector_size: 4, - data_type: glow::UNSIGNED_BYTE, - normalized: false, - stride, - offset: offset_of!(Vertex, color) as i32, - }; - vertex_array.add_new_attribute(&gl, position_buffer_info); - vertex_array.add_new_attribute(&gl, tex_coord_buffer_info); - vertex_array.add_new_attribute(&gl, color_buffer_info); + 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()?; + check_for_gl_error!(&gl, "after Painter::new"); Ok(Painter { @@ -224,11 +215,11 @@ impl Painter { u_sampler, is_webgl_1, is_embedded: matches!(shader_version, ShaderVersion::Es100 | ShaderVersion::Es300), - vertex_array, + vao, srgb_support, texture_filter: Default::default(), post_process, - vertex_buffer, + vbo, element_array_buffer, textures: Default::default(), #[cfg(feature = "epi")] @@ -256,9 +247,13 @@ impl Painter { 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(glow::FUNC_ADD); + self.gl + .blend_equation_separate(glow::FUNC_ADD, glow::FUNC_ADD); self.gl.blend_func_separate( // egui outputs colors with premultiplied alpha: glow::ONE, @@ -285,8 +280,8 @@ impl Painter { .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.vertex_array.bind_vertex_array(&self.gl); + self.vao.bind(&self.gl); self.gl .bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(self.element_array_buffer)); @@ -380,7 +375,13 @@ impl Painter { ); } - callback.call(self); + let info = egui::PaintCallbackInfo { + rect: callback.rect, + pixels_per_point, + screen_size_px: inner_size, + }; + + callback.call(&info, self); check_for_gl_error!(&self.gl, "callback"); @@ -395,8 +396,9 @@ impl Painter { } } } + unsafe { - self.vertex_array.unbind_vertex_array(&self.gl); + self.vao.unbind(&self.gl); self.gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, None); if let Some(ref post_process) = self.post_process { @@ -414,8 +416,7 @@ impl Painter { debug_assert!(mesh.is_valid()); if let Some(texture) = self.get_texture(mesh.texture_id) { unsafe { - self.gl - .bind_buffer(glow::ARRAY_BUFFER, Some(self.vertex_buffer)); + self.gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.vbo)); self.gl.buffer_data_u8_slice( glow::ARRAY_BUFFER, bytemuck::cast_slice(&mesh.vertices), @@ -600,7 +601,7 @@ impl Painter { for tex in self.textures.values() { self.gl.delete_texture(*tex); } - self.gl.delete_buffer(self.vertex_buffer); + 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); diff --git a/egui_glow/src/post_process.rs b/egui_glow/src/post_process.rs index 97b2e0c9..e35028a3 100644 --- a/egui_glow/src/post_process.rs +++ b/egui_glow/src/post_process.rs @@ -10,7 +10,7 @@ pub(crate) struct PostProcess { gl: std::rc::Rc, pos_buffer: glow::Buffer, index_buffer: glow::Buffer, - vertex_array: crate::vao::VAO, + vao: crate::vao::VertexArrayObject, is_webgl_1: bool, texture: glow::Texture, texture_size: (i32, i32), @@ -22,7 +22,6 @@ impl PostProcess { pub(crate) unsafe fn new( gl: std::rc::Rc, shader_prefix: &str, - need_to_emulate_vao: bool, is_webgl_1: bool, width: i32, height: i32, @@ -125,22 +124,18 @@ impl PostProcess { 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::vao::VAO::emulated() - } else { - crate::vao::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 vao = crate::vao::VertexArrayObject::new( + &gl, + pos_buffer, + vec![BufferInfo { + location: a_pos_loc, + vector_size: 2, + data_type: glow::FLOAT, + normalized: false, + stride: 0, + offset: 0, + }], + ); let index_buffer = gl.create_buffer()?; gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(index_buffer)); @@ -153,7 +148,7 @@ impl PostProcess { gl, pos_buffer, index_buffer, - vertex_array, + vao, is_webgl_1, texture, texture_size: (width, height), @@ -212,13 +207,13 @@ impl PostProcess { .get_uniform_location(self.program, "u_sampler") .unwrap(); self.gl.uniform_1_i32(Some(&u_sampler_loc), 0); - self.vertex_array.bind_vertex_array(&self.gl); + self.vao.bind(&self.gl); self.gl .bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(self.index_buffer)); self.gl .draw_elements(glow::TRIANGLES, 6, glow::UNSIGNED_BYTE, 0); - self.vertex_array.unbind_vertex_array(&self.gl); + self.vao.unbind(&self.gl); self.gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, None); self.gl.bind_texture(glow::TEXTURE_2D, None); self.gl.use_program(None); diff --git a/egui_glow/src/vao.rs b/egui_glow/src/vao.rs index d3a5d896..b88aefeb 100644 --- a/egui_glow/src/vao.rs +++ b/egui_glow/src/vao.rs @@ -18,35 +18,67 @@ pub(crate) struct BufferInfo { // ---------------------------------------------------------------------------- -pub struct EmulatedVao { - buffer: Option, +/// Wrapper around either Emulated VAO or GL's VAO. +pub(crate) struct VertexArrayObject { + // If `None`, we emulate VAO:s. + vao: Option, + vbo: glow::Buffer, buffer_infos: Vec, } -impl EmulatedVao { - pub(crate) fn new() -> Self { +impl VertexArrayObject { + #[allow(clippy::needless_pass_by_value)] // false positive + pub(crate) unsafe fn new( + gl: &glow::Context, + vbo: glow::Buffer, + buffer_infos: Vec, + ) -> 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 { - buffer: None, - buffer_infos: vec![], + vao, + vbo, + buffer_infos, } } - 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); + 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 { - dbg!(attribute); - unsafe { + + for attribute in &self.buffer_infos { gl.vertex_attrib_pointer_f32( attribute.location, attribute.vector_size, @@ -62,87 +94,21 @@ impl EmulatedVao { } } - pub(crate) fn unbind_vertex_array(&self, gl: &glow::Context) { - for attribute in &self.buffer_infos { - unsafe { + 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); } } - unsafe { - gl.bind_buffer(glow::ARRAY_BUFFER, None); - } } } // ---------------------------------------------------------------------------- -/// Wrapper around either Emulated VAO and GL's VAO -pub(crate) enum VAO { - Emulated(crate::vao::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::EmulatedVao::new()) - } - - pub(crate) unsafe fn bind_vertex_array(&self, gl: &glow::Context) { - match self { - VAO::Emulated(emulated_vao) => emulated_vao.bind_vertex_array(gl), - VAO::Native(vao) => { - gl.bind_vertex_array(Some(*vao)); - check_for_gl_error!(gl, "bind_vertex_array"); - } - } - } - - pub(crate) unsafe fn bind_buffer(&mut self, gl: &glow::Context, buffer: &glow::Buffer) { - match self { - VAO::Emulated(emulated_vao) => emulated_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::BufferInfo, - ) { - match self { - VAO::Emulated(emulated_vao) => emulated_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(emulated_vao) => emulated_vao.unbind_vertex_array(gl), - VAO::Native(_) => { - gl.bind_vertex_array(None); - } - } - } -} - -// ---------------------------------------------------------------------------- - -/// If returned true no need to emulate vao -pub(crate) fn supports_vao(gl: &glow::Context) -> bool { +fn supports_vao(gl: &glow::Context) -> bool { const WEBGL_PREFIX: &str = "WebGL "; const OPENGL_ES_PREFIX: &str = "OpenGL ES "; diff --git a/epaint/src/lib.rs b/epaint/src/lib.rs index 1e27b55f..ab559f89 100644 --- a/epaint/src/lib.rs +++ b/epaint/src/lib.rs @@ -31,7 +31,10 @@ pub use { image::{AlphaImage, ColorImage, ImageData, ImageDelta}, mesh::{Mesh, Mesh16, Vertex}, shadow::Shadow, - shape::{CircleShape, PaintCallback, PathShape, RectShape, Rounding, Shape, TextShape}, + shape::{ + CircleShape, PaintCallback, PaintCallbackInfo, PathShape, RectShape, Rounding, Shape, + TextShape, + }, stats::PaintStats, stroke::Stroke, tessellator::{tessellate_shapes, TessellationOptions, Tessellator}, diff --git a/epaint/src/shape.rs b/epaint/src/shape.rs index 57d3cef1..4fbd8457 100644 --- a/epaint/src/shape.rs +++ b/epaint/src/shape.rs @@ -642,6 +642,52 @@ fn dashes_from_line( // ---------------------------------------------------------------------------- +/// Information passed along with [`PaintCallback`] ([`Shape::Callback`]). +pub struct PaintCallbackInfo { + /// Viewport in points. + pub rect: Rect, + + /// Pixels per point. + pub pixels_per_point: f32, + + /// Full size of the screen, in pixels. + pub screen_size_px: [u32; 2], +} + +impl PaintCallbackInfo { + /// Physical pixel offset for left side of the viewport. + #[inline] + pub fn viewport_left_px(&self) -> f32 { + self.rect.min.x * self.pixels_per_point + } + + /// Physical pixel offset for top side of the viewport. + #[inline] + pub fn viewport_top_px(&self) -> f32 { + self.rect.min.y * self.pixels_per_point + } + + /// Physical pixel offset for bottom side of the viewport. + /// + /// This is what `glViewport` etc expects for the y axis. + #[inline] + pub fn viewport_from_bottom_px(&self) -> f32 { + self.screen_size_px[1] as f32 - self.rect.max.y * self.pixels_per_point + } + + /// Viewport width in physical pixels. + #[inline] + pub fn viewport_width_px(&self) -> f32 { + self.rect.width() * self.pixels_per_point + } + + /// Viewport width in physical pixels. + #[inline] + pub fn viewport_height_px(&self) -> f32 { + self.rect.height() * self.pixels_per_point + } +} + /// If you want to paint some 3D shapes inside an egui region, you can use this. /// /// This is advanced usage, and is backend specific. @@ -650,21 +696,22 @@ pub struct PaintCallback { /// Where to paint. pub rect: Rect, - /// Paint something custom using. + /// Paint something custom (e.g. 3D stuff). /// /// The argument is the render context, and what it contains depends on the backend. /// In `eframe` it will be `egui_glow::Painter`. /// /// The rendering backend is responsible for first setting the active viewport to [`Self::rect`]. - /// The rendering backend is also responsible for restoring any state it needs, + /// + /// The rendering backend is also responsible for restoring any state, /// such as the bound shader program and vertex array. - pub callback: std::sync::Arc, + pub callback: std::sync::Arc, } impl PaintCallback { #[inline] - pub fn call(&self, render_ctx: &dyn std::any::Any) { - (self.callback)(render_ctx); + pub fn call(&self, info: &PaintCallbackInfo, render_ctx: &dyn std::any::Any) { + (self.callback)(info, render_ctx); } }