2019-11-24 15:27:12 +00:00
|
|
|
#![allow(deprecated)] // legacy implement_vertex macro
|
|
|
|
|
2019-04-21 08:13:05 +00:00
|
|
|
use {
|
2021-02-14 09:53:39 +00:00
|
|
|
egui::{
|
2021-03-21 16:47:03 +00:00
|
|
|
emath::Rect,
|
2021-02-14 09:53:39 +00:00
|
|
|
epaint::{Color32, Mesh},
|
|
|
|
},
|
2020-09-09 13:24:09 +00:00
|
|
|
glium::{
|
2020-09-11 06:56:47 +00:00
|
|
|
implement_vertex,
|
|
|
|
index::PrimitiveType,
|
2021-02-20 18:48:02 +00:00
|
|
|
program,
|
2020-09-11 06:56:47 +00:00
|
|
|
texture::{self, srgb_texture2d::SrgbTexture2d},
|
|
|
|
uniform,
|
2021-01-09 09:27:32 +00:00
|
|
|
uniforms::{MagnifySamplerFilter, SamplerWrapFunction},
|
2020-09-11 06:56:47 +00:00
|
|
|
Frame, Surface,
|
2020-09-09 13:24:09 +00:00
|
|
|
},
|
2019-04-21 08:13:05 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
pub struct Painter {
|
|
|
|
program: glium::Program,
|
2020-09-21 17:19:24 +00:00
|
|
|
egui_texture: Option<SrgbTexture2d>,
|
2020-09-11 06:56:47 +00:00
|
|
|
egui_texture_version: Option<u64>,
|
|
|
|
|
2020-11-18 20:38:29 +00:00
|
|
|
/// `None` means unallocated (freed) slot.
|
|
|
|
user_textures: Vec<Option<UserTexture>>,
|
2020-09-11 06:56:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Default)]
|
|
|
|
struct UserTexture {
|
|
|
|
/// Pending upload (will be emptied later).
|
|
|
|
/// This is the format glium likes.
|
|
|
|
pixels: Vec<Vec<(u8, u8, u8, u8)>>,
|
|
|
|
|
|
|
|
/// Lazily uploaded
|
2020-11-18 20:38:29 +00:00
|
|
|
gl_texture: Option<SrgbTexture2d>,
|
2019-04-21 08:13:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Painter {
|
2019-11-02 08:50:49 +00:00
|
|
|
pub fn new(facade: &dyn glium::backend::Facade) -> Painter {
|
2021-02-20 18:48:02 +00:00
|
|
|
let program = program! {
|
|
|
|
facade,
|
|
|
|
120 => {
|
|
|
|
vertex: include_str!("shader/vertex_120.glsl"),
|
|
|
|
fragment: include_str!("shader/fragment_120.glsl"),
|
|
|
|
},
|
|
|
|
140 => {
|
|
|
|
vertex: include_str!("shader/vertex_140.glsl"),
|
|
|
|
fragment: include_str!("shader/fragment_140.glsl"),
|
|
|
|
},
|
|
|
|
100 es => {
|
|
|
|
vertex: include_str!("shader/vertex_100es.glsl"),
|
|
|
|
fragment: include_str!("shader/fragment_100es.glsl"),
|
|
|
|
},
|
|
|
|
300 es => {
|
|
|
|
vertex: include_str!("shader/vertex_300es.glsl"),
|
|
|
|
fragment: include_str!("shader/fragment_300es.glsl"),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
.expect("Failed to compile shader");
|
2019-04-21 08:13:05 +00:00
|
|
|
|
|
|
|
Painter {
|
|
|
|
program,
|
2020-09-21 17:19:24 +00:00
|
|
|
egui_texture: None,
|
2020-09-11 06:56:47 +00:00
|
|
|
egui_texture_version: None,
|
|
|
|
user_textures: Default::default(),
|
2019-04-21 08:13:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-11 08:35:47 +00:00
|
|
|
pub fn upload_egui_texture(
|
2020-09-11 06:56:47 +00:00
|
|
|
&mut self,
|
|
|
|
facade: &dyn glium::backend::Facade,
|
|
|
|
texture: &egui::Texture,
|
|
|
|
) {
|
|
|
|
if self.egui_texture_version == Some(texture.version) {
|
2019-04-21 08:13:05 +00:00
|
|
|
return; // No change
|
|
|
|
}
|
|
|
|
|
2020-09-11 06:56:47 +00:00
|
|
|
let pixels: Vec<Vec<(u8, u8, u8, u8)>> = texture
|
2019-04-21 08:13:05 +00:00
|
|
|
.pixels
|
|
|
|
.chunks(texture.width as usize)
|
2020-09-11 06:56:47 +00:00
|
|
|
.map(|row| {
|
|
|
|
row.iter()
|
2021-01-03 17:03:11 +00:00
|
|
|
.map(|&a| Color32::from_white_alpha(a).to_tuple())
|
2020-09-11 06:56:47 +00:00
|
|
|
.collect()
|
|
|
|
})
|
2019-04-21 08:13:05 +00:00
|
|
|
.collect();
|
|
|
|
|
2020-09-11 06:56:47 +00:00
|
|
|
let format = texture::SrgbFormat::U8U8U8U8;
|
2019-04-21 08:13:05 +00:00
|
|
|
let mipmaps = texture::MipmapsOption::NoMipmap;
|
2020-09-21 17:19:24 +00:00
|
|
|
self.egui_texture =
|
|
|
|
Some(SrgbTexture2d::with_format(facade, pixels, format, mipmaps).unwrap());
|
2020-09-11 06:56:47 +00:00
|
|
|
self.egui_texture_version = Some(texture.version);
|
|
|
|
}
|
|
|
|
|
2020-09-21 17:19:24 +00:00
|
|
|
/// Main entry-point for painting a frame
|
2021-01-25 20:23:24 +00:00
|
|
|
pub fn paint_meshes(
|
2020-04-20 21:33:16 +00:00
|
|
|
&mut self,
|
|
|
|
display: &glium::Display,
|
2020-10-17 21:54:46 +00:00
|
|
|
pixels_per_point: f32,
|
2020-12-18 21:34:48 +00:00
|
|
|
clear_color: egui::Rgba,
|
2021-01-25 20:43:17 +00:00
|
|
|
cipped_meshes: Vec<egui::ClippedMesh>,
|
2020-10-17 21:54:46 +00:00
|
|
|
egui_texture: &egui::Texture,
|
2020-04-20 21:33:16 +00:00
|
|
|
) {
|
2020-10-17 21:54:46 +00:00
|
|
|
self.upload_egui_texture(display, egui_texture);
|
2020-09-21 17:19:24 +00:00
|
|
|
self.upload_pending_user_textures(display);
|
2019-04-21 08:13:05 +00:00
|
|
|
|
2020-04-20 21:33:16 +00:00
|
|
|
let mut target = display.draw();
|
2020-12-18 21:34:48 +00:00
|
|
|
// Verified to be gamma-correct.
|
|
|
|
target.clear_color(
|
|
|
|
clear_color[0],
|
|
|
|
clear_color[1],
|
|
|
|
clear_color[2],
|
|
|
|
clear_color[3],
|
|
|
|
);
|
2021-01-25 20:43:17 +00:00
|
|
|
for egui::ClippedMesh(clip_rect, mesh) in cipped_meshes {
|
2021-01-25 20:23:24 +00:00
|
|
|
self.paint_mesh(&mut target, display, pixels_per_point, clip_rect, &mesh)
|
2020-04-20 21:33:16 +00:00
|
|
|
}
|
|
|
|
target.finish().unwrap();
|
|
|
|
}
|
|
|
|
|
2020-04-29 05:20:27 +00:00
|
|
|
#[inline(never)] // Easier profiling
|
2021-01-25 20:23:24 +00:00
|
|
|
pub fn paint_mesh(
|
2020-04-20 21:33:16 +00:00
|
|
|
&mut self,
|
|
|
|
target: &mut Frame,
|
|
|
|
display: &glium::Display,
|
2020-10-17 21:54:46 +00:00
|
|
|
pixels_per_point: f32,
|
2020-05-05 17:12:00 +00:00
|
|
|
clip_rect: Rect,
|
2021-01-25 20:23:24 +00:00
|
|
|
mesh: &Mesh,
|
2020-04-20 21:33:16 +00:00
|
|
|
) {
|
2021-01-25 20:23:24 +00:00
|
|
|
debug_assert!(mesh.is_valid());
|
2020-08-21 16:53:43 +00:00
|
|
|
|
2019-04-21 08:13:05 +00:00
|
|
|
let vertex_buffer = {
|
|
|
|
#[derive(Copy, Clone)]
|
|
|
|
struct Vertex {
|
|
|
|
a_pos: [f32; 2],
|
2020-09-09 15:14:42 +00:00
|
|
|
a_tc: [f32; 2],
|
2020-08-29 10:04:10 +00:00
|
|
|
a_srgba: [u8; 4],
|
2019-04-21 08:13:05 +00:00
|
|
|
}
|
2020-09-09 15:14:42 +00:00
|
|
|
implement_vertex!(Vertex, a_pos, a_tc, a_srgba);
|
2019-04-21 08:13:05 +00:00
|
|
|
|
2021-01-25 20:23:24 +00:00
|
|
|
let vertices: Vec<Vertex> = mesh
|
2019-04-21 08:13:05 +00:00
|
|
|
.vertices
|
|
|
|
.iter()
|
|
|
|
.map(|v| Vertex {
|
|
|
|
a_pos: [v.pos.x, v.pos.y],
|
2020-09-09 15:14:42 +00:00
|
|
|
a_tc: [v.uv.x, v.uv.y],
|
2020-09-11 06:56:47 +00:00
|
|
|
a_srgba: v.color.to_array(),
|
2019-04-21 08:13:05 +00:00
|
|
|
})
|
|
|
|
.collect();
|
|
|
|
|
2020-09-21 17:19:24 +00:00
|
|
|
// TODO: we should probably reuse the `VertexBuffer` instead of allocating a new one each frame.
|
2019-04-21 08:13:05 +00:00
|
|
|
glium::VertexBuffer::new(display, &vertices).unwrap()
|
|
|
|
};
|
|
|
|
|
2020-09-21 17:19:24 +00:00
|
|
|
// TODO: we should probably reuse the `IndexBuffer` instead of allocating a new one each frame.
|
2019-04-21 08:13:05 +00:00
|
|
|
let index_buffer =
|
2021-04-05 08:21:17 +00:00
|
|
|
glium::IndexBuffer::new(display, PrimitiveType::TrianglesList, &mesh.indices).unwrap();
|
2019-04-21 08:13:05 +00:00
|
|
|
|
2020-10-17 21:54:46 +00:00
|
|
|
let (width_in_pixels, height_in_pixels) = display.get_framebuffer_dimensions();
|
|
|
|
let width_in_points = width_in_pixels as f32 / pixels_per_point;
|
|
|
|
let height_in_points = height_in_pixels as f32 / pixels_per_point;
|
2019-04-21 08:13:05 +00:00
|
|
|
|
2021-01-25 20:23:24 +00:00
|
|
|
if let Some(texture) = self.get_texture(mesh.texture_id) {
|
2021-01-17 13:48:59 +00:00
|
|
|
// The texture coordinates for text are so that both nearest and linear should work with the egui font texture.
|
2021-01-09 09:27:32 +00:00
|
|
|
// For user textures linear sampling is more likely to be the right choice.
|
|
|
|
let filter = MagnifySamplerFilter::Linear;
|
|
|
|
|
2020-11-18 20:38:29 +00:00
|
|
|
let uniforms = uniform! {
|
|
|
|
u_screen_size: [width_in_points, height_in_points],
|
2021-01-09 09:27:32 +00:00
|
|
|
u_sampler: texture.sampled().magnify_filter(filter).wrap_function(SamplerWrapFunction::Clamp),
|
2020-11-18 20:38:29 +00:00
|
|
|
};
|
|
|
|
|
2021-01-17 13:48:59 +00:00
|
|
|
// egui outputs colors with premultiplied alpha:
|
2020-11-18 20:38:29 +00:00
|
|
|
let color_blend_func = glium::BlendingFunction::Addition {
|
|
|
|
source: glium::LinearBlendingFactor::One,
|
|
|
|
destination: glium::LinearBlendingFactor::OneMinusSourceAlpha,
|
|
|
|
};
|
|
|
|
|
|
|
|
// 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).
|
|
|
|
let alpha_blend_func = glium::BlendingFunction::Addition {
|
|
|
|
source: glium::LinearBlendingFactor::OneMinusDestinationAlpha,
|
|
|
|
destination: glium::LinearBlendingFactor::One,
|
|
|
|
};
|
|
|
|
|
|
|
|
let blend = glium::Blend {
|
|
|
|
color: color_blend_func,
|
|
|
|
alpha: alpha_blend_func,
|
|
|
|
..Default::default()
|
|
|
|
};
|
|
|
|
|
2021-01-25 20:23:24 +00:00
|
|
|
// egui outputs mesh in both winding orders:
|
2021-01-17 01:06:26 +00:00
|
|
|
let backface_culling = glium::BackfaceCullingMode::CullingDisabled;
|
|
|
|
|
2020-11-18 20:38:29 +00:00
|
|
|
// 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;
|
|
|
|
|
2021-02-21 09:12:08 +00:00
|
|
|
// Make sure clip rect can fit within a `u32`:
|
2021-03-21 16:47:03 +00:00
|
|
|
let clip_min_x = clip_min_x.clamp(0.0, width_in_pixels as f32);
|
|
|
|
let clip_min_y = clip_min_y.clamp(0.0, height_in_pixels as f32);
|
|
|
|
let clip_max_x = clip_max_x.clamp(clip_min_x, width_in_pixels as f32);
|
|
|
|
let clip_max_y = clip_max_y.clamp(clip_min_y, height_in_pixels as f32);
|
2020-11-18 20:38:29 +00:00
|
|
|
|
|
|
|
let clip_min_x = clip_min_x.round() as u32;
|
|
|
|
let clip_min_y = clip_min_y.round() as u32;
|
|
|
|
let clip_max_x = clip_max_x.round() as u32;
|
|
|
|
let clip_max_y = clip_max_y.round() as u32;
|
|
|
|
|
|
|
|
let params = glium::DrawParameters {
|
|
|
|
blend,
|
2021-01-17 01:06:26 +00:00
|
|
|
backface_culling,
|
2020-11-18 20:38:29 +00:00
|
|
|
scissor: Some(glium::Rect {
|
|
|
|
left: clip_min_x,
|
|
|
|
bottom: height_in_pixels - clip_max_y,
|
|
|
|
width: clip_max_x - clip_min_x,
|
|
|
|
height: clip_max_y - clip_min_y,
|
|
|
|
}),
|
|
|
|
..Default::default()
|
|
|
|
};
|
|
|
|
|
|
|
|
target
|
|
|
|
.draw(
|
|
|
|
&vertex_buffer,
|
|
|
|
&index_buffer,
|
|
|
|
&self.program,
|
|
|
|
&uniforms,
|
|
|
|
¶ms,
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
}
|
2019-04-21 08:13:05 +00:00
|
|
|
}
|
2020-11-20 18:50:47 +00:00
|
|
|
|
|
|
|
// ------------------------------------------------------------------------
|
|
|
|
// user textures: this is an experimental feature.
|
2021-01-17 13:48:59 +00:00
|
|
|
// No need to implement this in your egui integration!
|
2020-11-20 18:50:47 +00:00
|
|
|
|
|
|
|
pub fn alloc_user_texture(&mut self) -> egui::TextureId {
|
|
|
|
for (i, tex) in self.user_textures.iter_mut().enumerate() {
|
|
|
|
if tex.is_none() {
|
|
|
|
*tex = Some(Default::default());
|
|
|
|
return egui::TextureId::User(i as u64);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
let id = egui::TextureId::User(self.user_textures.len() as u64);
|
|
|
|
self.user_textures.push(Some(Default::default()));
|
|
|
|
id
|
|
|
|
}
|
2021-03-13 11:41:51 +00:00
|
|
|
/// register glium texture as egui texture
|
|
|
|
/// Usable for render to image rectangle
|
|
|
|
pub fn register_glium_texture(
|
|
|
|
&mut self,
|
|
|
|
texture: glium::texture::SrgbTexture2d,
|
|
|
|
) -> egui::TextureId {
|
|
|
|
let id = self.alloc_user_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 {
|
|
|
|
pixels: vec![],
|
|
|
|
gl_texture: Some(texture),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
id
|
|
|
|
}
|
2020-11-20 18:50:47 +00:00
|
|
|
pub fn set_user_texture(
|
|
|
|
&mut self,
|
|
|
|
id: egui::TextureId,
|
|
|
|
size: (usize, usize),
|
2021-01-02 16:02:18 +00:00
|
|
|
pixels: &[Color32],
|
2020-11-20 18:50:47 +00:00
|
|
|
) {
|
|
|
|
assert_eq!(size.0 * size.1, pixels.len());
|
|
|
|
|
|
|
|
if let egui::TextureId::User(id) = id {
|
2021-02-12 16:40:53 +00:00
|
|
|
if let Some(Some(user_texture)) = self.user_textures.get_mut(id as usize) {
|
|
|
|
let pixels: Vec<Vec<(u8, u8, u8, u8)>> = pixels
|
|
|
|
.chunks(size.0 as usize)
|
|
|
|
.map(|row| row.iter().map(|srgba| srgba.to_tuple()).collect())
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
*user_texture = UserTexture {
|
|
|
|
pixels,
|
|
|
|
gl_texture: None,
|
|
|
|
};
|
2020-11-20 18:50:47 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn free_user_texture(&mut self, id: egui::TextureId) {
|
|
|
|
if let egui::TextureId::User(id) = id {
|
|
|
|
let index = id as usize;
|
|
|
|
if index < self.user_textures.len() {
|
|
|
|
self.user_textures[index] = None;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-13 11:41:51 +00:00
|
|
|
pub fn get_texture(&self, texture_id: egui::TextureId) -> Option<&SrgbTexture2d> {
|
2020-11-20 18:50:47 +00:00
|
|
|
match texture_id {
|
|
|
|
egui::TextureId::Egui => self.egui_texture.as_ref(),
|
|
|
|
egui::TextureId::User(id) => self
|
|
|
|
.user_textures
|
|
|
|
.get(id as usize)?
|
|
|
|
.as_ref()?
|
|
|
|
.gl_texture
|
|
|
|
.as_ref(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-11 08:35:47 +00:00
|
|
|
pub fn upload_pending_user_textures(&mut self, facade: &dyn glium::backend::Facade) {
|
2020-11-20 18:50:47 +00:00
|
|
|
for user_texture in &mut self.user_textures {
|
|
|
|
if let Some(user_texture) = user_texture {
|
|
|
|
if user_texture.gl_texture.is_none() {
|
|
|
|
let pixels = std::mem::take(&mut user_texture.pixels);
|
|
|
|
let format = texture::SrgbFormat::U8U8U8U8;
|
|
|
|
let mipmaps = texture::MipmapsOption::NoMipmap;
|
|
|
|
user_texture.gl_texture =
|
|
|
|
Some(SrgbTexture2d::with_format(facade, pixels, format, mipmaps).unwrap());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-04-21 08:13:05 +00:00
|
|
|
}
|