Add user texture support to egui_glium and egui_web backends
This commit is contained in:
parent
02ef0cd9d5
commit
d49aec4079
5 changed files with 321 additions and 114 deletions
|
@ -5,6 +5,8 @@
|
||||||
//! Egui can be used as a library, but you can also use it as a framework to write apps in.
|
//! Egui can be used as a library, but you can also use it as a framework to write apps in.
|
||||||
//! This module defined the `App` trait that can be implemented and used with the `egui_web` and `egui_glium` crates.
|
//! This module defined the `App` trait that can be implemented and used with the `egui_web` and `egui_glium` crates.
|
||||||
|
|
||||||
|
// TODO: move egui/src/app.rs to own crate, e.g. egui_framework ?
|
||||||
|
|
||||||
use crate::Ui;
|
use crate::Ui;
|
||||||
|
|
||||||
/// Implement this trait to write apps that can be compiled both natively using the [`egui_glium`](https://crates.io/crates/egui_glium) crate,
|
/// Implement this trait to write apps that can be compiled both natively using the [`egui_glium`](https://crates.io/crates/egui_glium) crate,
|
||||||
|
@ -54,6 +56,13 @@ pub trait Backend {
|
||||||
/// Signal the backend that we'd like to exit the app now.
|
/// Signal the backend that we'd like to exit the app now.
|
||||||
/// This does nothing for web apps.
|
/// This does nothing for web apps.
|
||||||
fn quit(&mut self) {}
|
fn quit(&mut self) {}
|
||||||
|
|
||||||
|
/// Allocate a user texture (EXPERIMENTAL!)
|
||||||
|
fn new_texture_srgba_premultiplied(
|
||||||
|
&mut self,
|
||||||
|
size: (usize, usize),
|
||||||
|
pixels: &[crate::Srgba],
|
||||||
|
) -> crate::TextureId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A place where you can store custom data in a way that persists when you restart the app.
|
/// A place where you can store custom data in a way that persists when you restart the app.
|
||||||
|
|
|
@ -5,7 +5,10 @@ use crate::{
|
||||||
*,
|
*,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use egui::app::{App, Backend, RunMode, Storage};
|
pub use egui::{
|
||||||
|
app::{App, Backend, RunMode, Storage},
|
||||||
|
Srgba,
|
||||||
|
};
|
||||||
|
|
||||||
const EGUI_MEMORY_KEY: &str = "egui";
|
const EGUI_MEMORY_KEY: &str = "egui";
|
||||||
const WINDOW_KEY: &str = "window";
|
const WINDOW_KEY: &str = "window";
|
||||||
|
@ -14,14 +17,16 @@ pub struct GliumBackend {
|
||||||
frame_times: egui::MovementTracker<f32>,
|
frame_times: egui::MovementTracker<f32>,
|
||||||
quit: bool,
|
quit: bool,
|
||||||
run_mode: RunMode,
|
run_mode: RunMode,
|
||||||
|
painter: Painter,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GliumBackend {
|
impl GliumBackend {
|
||||||
pub fn new(run_mode: RunMode) -> Self {
|
pub fn new(run_mode: RunMode, painter: Painter) -> Self {
|
||||||
Self {
|
Self {
|
||||||
frame_times: egui::MovementTracker::new(1000, 1.0),
|
frame_times: egui::MovementTracker::new(1000, 1.0),
|
||||||
quit: false,
|
quit: false,
|
||||||
run_mode,
|
run_mode,
|
||||||
|
painter,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,6 +51,14 @@ impl Backend for GliumBackend {
|
||||||
fn quit(&mut self) {
|
fn quit(&mut self) {
|
||||||
self.quit = true;
|
self.quit = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn new_texture_srgba_premultiplied(
|
||||||
|
&mut self,
|
||||||
|
size: (usize, usize),
|
||||||
|
pixels: &[Srgba],
|
||||||
|
) -> egui::TextureId {
|
||||||
|
self.painter.new_user_texture(size, pixels)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Run an egui app
|
/// Run an egui app
|
||||||
|
@ -81,12 +94,11 @@ pub fn run(
|
||||||
let mut ctx = egui::Context::new();
|
let mut ctx = egui::Context::new();
|
||||||
*ctx.memory() = egui::app::get_value(&storage, EGUI_MEMORY_KEY).unwrap_or_default();
|
*ctx.memory() = egui::app::get_value(&storage, EGUI_MEMORY_KEY).unwrap_or_default();
|
||||||
|
|
||||||
let mut painter = Painter::new(&display);
|
|
||||||
let mut raw_input = make_raw_input(&display);
|
let mut raw_input = make_raw_input(&display);
|
||||||
|
|
||||||
// used to keep track of time for animations
|
// used to keep track of time for animations
|
||||||
let start_time = Instant::now();
|
let start_time = Instant::now();
|
||||||
let mut runner = GliumBackend::new(run_mode);
|
let mut runner = GliumBackend::new(run_mode, Painter::new(&display));
|
||||||
let mut clipboard = init_clipboard();
|
let mut clipboard = init_clipboard();
|
||||||
|
|
||||||
event_loop.run(move |event, _, control_flow| {
|
event_loop.run(move |event, _, control_flow| {
|
||||||
|
@ -105,7 +117,9 @@ pub fn run(
|
||||||
let frame_time = (Instant::now() - egui_start).as_secs_f64() as f32;
|
let frame_time = (Instant::now() - egui_start).as_secs_f64() as f32;
|
||||||
runner.frame_times.add(raw_input.time, frame_time);
|
runner.frame_times.add(raw_input.time, frame_time);
|
||||||
|
|
||||||
painter.paint_jobs(&display, paint_jobs, &ctx.texture());
|
runner
|
||||||
|
.painter
|
||||||
|
.paint_jobs(&display, paint_jobs, &ctx.texture());
|
||||||
|
|
||||||
if runner.quit {
|
if runner.quit {
|
||||||
*control_flow = glutin::event_loop::ControlFlow::Exit
|
*control_flow = glutin::event_loop::ControlFlow::Exit
|
||||||
|
|
|
@ -4,18 +4,35 @@ use {
|
||||||
egui::{
|
egui::{
|
||||||
math::clamp,
|
math::clamp,
|
||||||
paint::{PaintJobs, Triangles},
|
paint::{PaintJobs, Triangles},
|
||||||
Rect,
|
Rect, Srgba,
|
||||||
},
|
},
|
||||||
glium::{
|
glium::{
|
||||||
implement_vertex, index::PrimitiveType, program, texture, uniform,
|
implement_vertex,
|
||||||
uniforms::SamplerWrapFunction, Frame, Surface,
|
index::PrimitiveType,
|
||||||
|
program,
|
||||||
|
texture::{self, srgb_texture2d::SrgbTexture2d},
|
||||||
|
uniform,
|
||||||
|
uniforms::SamplerWrapFunction,
|
||||||
|
Frame, Surface,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct Painter {
|
pub struct Painter {
|
||||||
program: glium::Program,
|
program: glium::Program,
|
||||||
texture: texture::texture2d::Texture2d,
|
egui_texture: SrgbTexture2d,
|
||||||
current_texture_version: Option<u64>,
|
egui_texture_version: Option<u64>,
|
||||||
|
|
||||||
|
user_textures: Vec<UserTexture>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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
|
||||||
|
texture: Option<SrgbTexture2d>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Painter {
|
impl Painter {
|
||||||
|
@ -63,7 +80,7 @@ impl Painter {
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
// glium expects linear rgba
|
// glium expects linear rgba
|
||||||
f_color = v_rgba * texture(u_sampler, v_tc).r;
|
f_color = v_rgba * texture(u_sampler, v_tc);
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
},
|
},
|
||||||
|
@ -109,7 +126,7 @@ impl Painter {
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
// glium expects linear rgba
|
// glium expects linear rgba
|
||||||
gl_FragColor = v_rgba * texture2D(u_sampler, v_tc).r;
|
gl_FragColor = v_rgba * texture2D(u_sampler, v_tc);
|
||||||
}
|
}
|
||||||
",
|
",
|
||||||
},
|
},
|
||||||
|
@ -155,7 +172,7 @@ impl Painter {
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
// glium expects linear rgba
|
// glium expects linear rgba
|
||||||
gl_FragColor = v_rgba * texture2D(u_sampler, v_tc).r;
|
gl_FragColor = v_rgba * texture2D(u_sampler, v_tc);
|
||||||
}
|
}
|
||||||
",
|
",
|
||||||
},
|
},
|
||||||
|
@ -163,34 +180,69 @@ impl Painter {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let pixels = vec![vec![255u8, 0u8], vec![0u8, 255u8]];
|
let pixels = vec![vec![255u8, 0u8], vec![0u8, 255u8]];
|
||||||
let format = texture::UncompressedFloatFormat::U8;
|
let format = texture::SrgbFormat::U8U8U8U8;
|
||||||
let mipmaps = texture::MipmapsOption::NoMipmap;
|
let mipmaps = texture::MipmapsOption::NoMipmap;
|
||||||
let texture =
|
let egui_texture = SrgbTexture2d::with_format(facade, pixels, format, mipmaps).unwrap();
|
||||||
texture::texture2d::Texture2d::with_format(facade, pixels, format, mipmaps).unwrap();
|
|
||||||
|
|
||||||
Painter {
|
Painter {
|
||||||
program,
|
program,
|
||||||
texture,
|
egui_texture,
|
||||||
current_texture_version: None,
|
egui_texture_version: None,
|
||||||
|
user_textures: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn upload_texture(&mut self, facade: &dyn glium::backend::Facade, texture: &egui::Texture) {
|
pub fn new_user_texture(&mut self, size: (usize, usize), pixels: &[Srgba]) -> egui::TextureId {
|
||||||
if self.current_texture_version == Some(texture.version) {
|
assert_eq!(size.0 * size.1, pixels.len());
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
let id = egui::TextureId::User(self.user_textures.len() as u64);
|
||||||
|
self.user_textures.push(UserTexture {
|
||||||
|
pixels,
|
||||||
|
texture: None,
|
||||||
|
});
|
||||||
|
id
|
||||||
|
}
|
||||||
|
|
||||||
|
fn upload_egui_texture(
|
||||||
|
&mut self,
|
||||||
|
facade: &dyn glium::backend::Facade,
|
||||||
|
texture: &egui::Texture,
|
||||||
|
) {
|
||||||
|
if self.egui_texture_version == Some(texture.version) {
|
||||||
return; // No change
|
return; // No change
|
||||||
}
|
}
|
||||||
|
|
||||||
let pixels: Vec<Vec<u8>> = texture
|
let pixels: Vec<Vec<(u8, u8, u8, u8)>> = texture
|
||||||
.pixels
|
.pixels
|
||||||
.chunks(texture.width as usize)
|
.chunks(texture.width as usize)
|
||||||
.map(|row| row.to_vec())
|
.map(|row| {
|
||||||
|
row.iter()
|
||||||
|
.map(|&a| Srgba::white_alpha(a).to_tuple())
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let format = texture::UncompressedFloatFormat::U8;
|
let format = texture::SrgbFormat::U8U8U8U8;
|
||||||
let mipmaps = texture::MipmapsOption::NoMipmap;
|
let mipmaps = texture::MipmapsOption::NoMipmap;
|
||||||
self.texture =
|
self.egui_texture = SrgbTexture2d::with_format(facade, pixels, format, mipmaps).unwrap();
|
||||||
texture::texture2d::Texture2d::with_format(facade, pixels, format, mipmaps).unwrap();
|
self.egui_texture_version = Some(texture.version);
|
||||||
self.current_texture_version = Some(texture.version);
|
}
|
||||||
|
|
||||||
|
fn upload_user_textures(&mut self, facade: &dyn glium::backend::Facade) {
|
||||||
|
for user_texture in &mut self.user_textures {
|
||||||
|
if user_texture.texture.is_none() {
|
||||||
|
let pixels = std::mem::take(&mut user_texture.pixels);
|
||||||
|
let format = texture::SrgbFormat::U8U8U8U8;
|
||||||
|
let mipmaps = texture::MipmapsOption::NoMipmap;
|
||||||
|
user_texture.texture =
|
||||||
|
Some(SrgbTexture2d::with_format(facade, pixels, format, mipmaps).unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn paint_jobs(
|
pub fn paint_jobs(
|
||||||
|
@ -199,7 +251,8 @@ impl Painter {
|
||||||
jobs: PaintJobs,
|
jobs: PaintJobs,
|
||||||
texture: &egui::Texture,
|
texture: &egui::Texture,
|
||||||
) {
|
) {
|
||||||
self.upload_texture(display, texture);
|
self.upload_egui_texture(display, texture);
|
||||||
|
self.upload_user_textures(display);
|
||||||
|
|
||||||
let mut target = display.draw();
|
let mut target = display.draw();
|
||||||
target.clear_color(0.0, 0.0, 0.0, 0.0);
|
target.clear_color(0.0, 0.0, 0.0, 0.0);
|
||||||
|
@ -209,6 +262,18 @@ impl Painter {
|
||||||
target.finish().unwrap();
|
target.finish().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_texture(&self, texture_id: egui::TextureId) -> &SrgbTexture2d {
|
||||||
|
match texture_id {
|
||||||
|
egui::TextureId::Egui => &self.egui_texture,
|
||||||
|
egui::TextureId::User(id) => {
|
||||||
|
let id = id as usize;
|
||||||
|
assert!(id < self.user_textures.len());
|
||||||
|
let texture = self.user_textures[id].texture.as_ref();
|
||||||
|
texture.expect("Should have been uploaded")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[inline(never)] // Easier profiling
|
#[inline(never)] // Easier profiling
|
||||||
fn paint_job(
|
fn paint_job(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
@ -234,7 +299,7 @@ impl Painter {
|
||||||
.map(|v| Vertex {
|
.map(|v| Vertex {
|
||||||
a_pos: [v.pos.x, v.pos.y],
|
a_pos: [v.pos.x, v.pos.y],
|
||||||
a_tc: [v.uv.x, v.uv.y],
|
a_tc: [v.uv.x, v.uv.y],
|
||||||
a_srgba: v.color.0,
|
a_srgba: v.color.to_array(),
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
@ -251,9 +316,11 @@ impl Painter {
|
||||||
let width_points = width_pixels as f32 / pixels_per_point;
|
let width_points = width_pixels as f32 / pixels_per_point;
|
||||||
let height_points = height_pixels as f32 / pixels_per_point;
|
let height_points = height_pixels as f32 / pixels_per_point;
|
||||||
|
|
||||||
|
let texture = self.get_texture(triangles.texture_id);
|
||||||
|
|
||||||
let uniforms = uniform! {
|
let uniforms = uniform! {
|
||||||
u_screen_size: [width_points, height_points],
|
u_screen_size: [width_points, height_points],
|
||||||
u_sampler: self.texture.sampled().wrap_function(SamplerWrapFunction::Clamp),
|
u_sampler: texture.sampled().wrap_function(SamplerWrapFunction::Clamp),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Egui outputs colors with premultiplied alpha:
|
// Egui outputs colors with premultiplied alpha:
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
pub use egui::app::{App, Backend, RunMode, WebInfo};
|
pub use egui::{
|
||||||
|
app::{App, Backend, RunMode, WebInfo},
|
||||||
|
Srgba,
|
||||||
|
};
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@ -102,6 +105,14 @@ impl Backend for WebBackend {
|
||||||
fn fps(&self) -> f32 {
|
fn fps(&self) -> f32 {
|
||||||
1.0 / self.frame_times.mean_time_interval().unwrap_or_default()
|
1.0 / self.frame_times.mean_time_interval().unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn new_texture_srgba_premultiplied(
|
||||||
|
&mut self,
|
||||||
|
size: (usize, usize),
|
||||||
|
pixels: &[Srgba],
|
||||||
|
) -> egui::TextureId {
|
||||||
|
self.painter.new_user_texture(size, pixels)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
|
@ -12,18 +12,99 @@ use egui::{
|
||||||
|
|
||||||
type Gl = WebGlRenderingContext;
|
type Gl = WebGlRenderingContext;
|
||||||
|
|
||||||
|
const VERTEX_SHADER_SOURCE: &str = r#"
|
||||||
|
precision mediump float;
|
||||||
|
uniform vec2 u_screen_size;
|
||||||
|
attribute vec2 a_pos;
|
||||||
|
attribute vec2 a_tc;
|
||||||
|
attribute vec4 a_srgba;
|
||||||
|
varying vec4 v_rgba;
|
||||||
|
varying vec2 v_tc;
|
||||||
|
|
||||||
|
// 0-1 linear from 0-255 sRGB
|
||||||
|
vec3 linear_from_srgb(vec3 srgb) {
|
||||||
|
bvec3 cutoff = lessThan(srgb, vec3(10.31475));
|
||||||
|
vec3 lower = srgb / vec3(3294.6);
|
||||||
|
vec3 higher = pow((srgb + vec3(14.025)) / vec3(269.025), vec3(2.4));
|
||||||
|
return mix(higher, lower, vec3(cutoff));
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 linear_from_srgba(vec4 srgba) {
|
||||||
|
return vec4(linear_from_srgb(srgba.rgb), srgba.a / 255.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = linear_from_srgba(a_srgba);
|
||||||
|
v_tc = a_tc;
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
const FRAGMENT_SHADER_SOURCE: &str = r#"
|
||||||
|
precision mediump float;
|
||||||
|
uniform sampler2D u_sampler;
|
||||||
|
varying vec4 v_rgba;
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 srgba_from_linear(vec4 rgba) {
|
||||||
|
return vec4(srgb_from_linear(rgba.rgb), 255.0 * rgba.a);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 0-1 linear from 0-255 sRGB
|
||||||
|
vec3 linear_from_srgb(vec3 srgb) {
|
||||||
|
bvec3 cutoff = lessThan(srgb, vec3(10.31475));
|
||||||
|
vec3 lower = srgb / vec3(3294.6);
|
||||||
|
vec3 higher = pow((srgb + vec3(14.025)) / vec3(269.025), vec3(2.4));
|
||||||
|
return mix(higher, lower, vec3(cutoff));
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 linear_from_srgba(vec4 srgba) {
|
||||||
|
return vec4(linear_from_srgb(srgba.rgb), srgba.a / 255.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec4 texture_rgba = linear_from_srgba(texture2D(u_sampler, v_tc) * 255.0);
|
||||||
|
gl_FragColor = srgba_from_linear(v_rgba * texture_rgba) / 255.0;
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
pub struct Painter {
|
pub struct Painter {
|
||||||
canvas_id: String,
|
canvas_id: String,
|
||||||
canvas: web_sys::HtmlCanvasElement,
|
canvas: web_sys::HtmlCanvasElement,
|
||||||
gl: WebGlRenderingContext,
|
gl: WebGlRenderingContext,
|
||||||
texture: WebGlTexture,
|
|
||||||
program: WebGlProgram,
|
program: WebGlProgram,
|
||||||
index_buffer: WebGlBuffer,
|
index_buffer: WebGlBuffer,
|
||||||
pos_buffer: WebGlBuffer,
|
pos_buffer: WebGlBuffer,
|
||||||
tc_buffer: WebGlBuffer,
|
tc_buffer: WebGlBuffer,
|
||||||
color_buffer: WebGlBuffer,
|
color_buffer: WebGlBuffer,
|
||||||
tex_size: (u16, u16),
|
|
||||||
current_texture_version: Option<u64>,
|
egui_texture: WebGlTexture,
|
||||||
|
egui_texture_version: Option<u64>,
|
||||||
|
|
||||||
|
user_textures: Vec<UserTexture>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct UserTexture {
|
||||||
|
size: (usize, usize),
|
||||||
|
|
||||||
|
/// Pending upload (will be emptied later).
|
||||||
|
pixels: Vec<u8>,
|
||||||
|
|
||||||
|
/// Lazily uploaded
|
||||||
|
texture: Option<WebGlTexture>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Painter {
|
impl Painter {
|
||||||
|
@ -48,77 +129,15 @@ impl Painter {
|
||||||
|
|
||||||
// --------------------------------------------------------------------
|
// --------------------------------------------------------------------
|
||||||
|
|
||||||
let gl_texture = gl.create_texture().unwrap();
|
let egui_texture = gl.create_texture().unwrap();
|
||||||
gl.bind_texture(Gl::TEXTURE_2D, Some(&gl_texture));
|
gl.bind_texture(Gl::TEXTURE_2D, Some(&egui_texture));
|
||||||
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_S, Gl::CLAMP_TO_EDGE as i32);
|
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_S, Gl::CLAMP_TO_EDGE as i32);
|
||||||
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_T, Gl::CLAMP_TO_EDGE as i32);
|
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_T, Gl::CLAMP_TO_EDGE as i32);
|
||||||
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MIN_FILTER, Gl::LINEAR as i32);
|
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MIN_FILTER, Gl::LINEAR as i32);
|
||||||
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MAG_FILTER, Gl::LINEAR as i32);
|
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MAG_FILTER, Gl::LINEAR as i32);
|
||||||
|
|
||||||
// --------------------------------------------------------------------
|
let vert_shader = compile_shader(&gl, Gl::VERTEX_SHADER, VERTEX_SHADER_SOURCE)?;
|
||||||
|
let frag_shader = compile_shader(&gl, Gl::FRAGMENT_SHADER, FRAGMENT_SHADER_SOURCE)?;
|
||||||
let vert_shader = compile_shader(
|
|
||||||
&gl,
|
|
||||||
Gl::VERTEX_SHADER,
|
|
||||||
r#"
|
|
||||||
precision mediump float;
|
|
||||||
uniform vec2 u_screen_size;
|
|
||||||
attribute vec2 a_pos;
|
|
||||||
attribute vec2 a_tc;
|
|
||||||
attribute vec4 a_srgba;
|
|
||||||
varying vec4 v_rgba;
|
|
||||||
varying vec2 v_tc;
|
|
||||||
|
|
||||||
// 0-1 linear from 0-255 sRGB
|
|
||||||
vec3 linear_from_srgb(vec3 srgb) {
|
|
||||||
bvec3 cutoff = lessThan(srgb, vec3(10.31475));
|
|
||||||
vec3 lower = srgb / vec3(3294.6);
|
|
||||||
vec3 higher = pow((srgb + vec3(14.025)) / vec3(269.025), vec3(2.4));
|
|
||||||
return mix(higher, lower, vec3(cutoff));
|
|
||||||
}
|
|
||||||
|
|
||||||
vec4 linear_from_srgba(vec4 srgba) {
|
|
||||||
return vec4(linear_from_srgb(srgba.rgb), srgba.a / 255.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 = linear_from_srgba(a_srgba);
|
|
||||||
v_tc = a_tc;
|
|
||||||
}
|
|
||||||
"#,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let frag_shader = compile_shader(
|
|
||||||
&gl,
|
|
||||||
Gl::FRAGMENT_SHADER,
|
|
||||||
r#"
|
|
||||||
precision mediump float;
|
|
||||||
uniform sampler2D u_sampler;
|
|
||||||
varying vec4 v_rgba;
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
|
|
||||||
vec4 srgba_from_linear(vec4 rgba) {
|
|
||||||
return vec4(srgb_from_linear(rgba.rgb), 255.0 * rgba.a);
|
|
||||||
}
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
gl_FragColor = srgba_from_linear(v_rgba * texture2D(u_sampler, v_tc).a) / 255.0;
|
|
||||||
}
|
|
||||||
"#,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let program = link_program(&gl, [vert_shader, frag_shader].iter())?;
|
let program = link_program(&gl, [vert_shader, frag_shader].iter())?;
|
||||||
let index_buffer = gl.create_buffer().ok_or("failed to create index_buffer")?;
|
let index_buffer = gl.create_buffer().ok_or("failed to create index_buffer")?;
|
||||||
|
@ -130,14 +149,14 @@ impl Painter {
|
||||||
canvas_id: canvas_id.to_owned(),
|
canvas_id: canvas_id.to_owned(),
|
||||||
canvas,
|
canvas,
|
||||||
gl,
|
gl,
|
||||||
texture: gl_texture,
|
|
||||||
program,
|
program,
|
||||||
index_buffer,
|
index_buffer,
|
||||||
pos_buffer,
|
pos_buffer,
|
||||||
tc_buffer,
|
tc_buffer,
|
||||||
color_buffer,
|
color_buffer,
|
||||||
tex_size: (0, 0),
|
egui_texture,
|
||||||
current_texture_version: None,
|
egui_texture_version: None,
|
||||||
|
user_textures: Default::default(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -146,18 +165,52 @@ impl Painter {
|
||||||
&self.canvas_id
|
&self.canvas_id
|
||||||
}
|
}
|
||||||
|
|
||||||
fn upload_texture(&mut self, texture: &Texture) {
|
pub fn new_user_texture(
|
||||||
if self.current_texture_version == Some(texture.version) {
|
&mut self,
|
||||||
|
size: (usize, usize),
|
||||||
|
srgba_pixels: &[Srgba],
|
||||||
|
) -> egui::TextureId {
|
||||||
|
assert_eq!(size.0 * size.1, srgba_pixels.len());
|
||||||
|
|
||||||
|
let mut pixels: Vec<u8> = Vec::with_capacity(srgba_pixels.len() * 4);
|
||||||
|
for srgba in srgba_pixels {
|
||||||
|
pixels.push(srgba.r());
|
||||||
|
pixels.push(srgba.g());
|
||||||
|
pixels.push(srgba.b());
|
||||||
|
pixels.push(srgba.a());
|
||||||
|
}
|
||||||
|
|
||||||
|
let id = egui::TextureId::User(self.user_textures.len() as u64);
|
||||||
|
self.user_textures.push(UserTexture {
|
||||||
|
size,
|
||||||
|
pixels,
|
||||||
|
texture: None,
|
||||||
|
});
|
||||||
|
id
|
||||||
|
}
|
||||||
|
|
||||||
|
fn upload_egui_texture(&mut self, texture: &Texture) {
|
||||||
|
if self.egui_texture_version == Some(texture.version) {
|
||||||
return; // No change
|
return; // No change
|
||||||
}
|
}
|
||||||
|
|
||||||
let gl = &self.gl;
|
let mut pixels: Vec<u8> = Vec::with_capacity(texture.pixels.len() * 4);
|
||||||
gl.bind_texture(Gl::TEXTURE_2D, Some(&self.texture));
|
for &alpha in &texture.pixels {
|
||||||
|
let srgba = Srgba::white_alpha(alpha);
|
||||||
|
pixels.push(srgba.r());
|
||||||
|
pixels.push(srgba.g());
|
||||||
|
pixels.push(srgba.b());
|
||||||
|
pixels.push(srgba.a());
|
||||||
|
}
|
||||||
|
|
||||||
|
let gl = &self.gl;
|
||||||
|
gl.bind_texture(Gl::TEXTURE_2D, Some(&self.egui_texture));
|
||||||
|
|
||||||
|
// TODO: https://developer.mozilla.org/en-US/docs/Web/API/EXT_sRGB
|
||||||
let level = 0;
|
let level = 0;
|
||||||
let internal_format = Gl::ALPHA;
|
let internal_format = Gl::RGBA;
|
||||||
let border = 0;
|
let border = 0;
|
||||||
let src_format = Gl::ALPHA;
|
let src_format = Gl::RGBA;
|
||||||
let src_type = Gl::UNSIGNED_BYTE;
|
let src_type = Gl::UNSIGNED_BYTE;
|
||||||
gl.tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array(
|
gl.tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array(
|
||||||
Gl::TEXTURE_2D,
|
Gl::TEXTURE_2D,
|
||||||
|
@ -168,22 +221,74 @@ impl Painter {
|
||||||
border,
|
border,
|
||||||
src_format,
|
src_format,
|
||||||
src_type,
|
src_type,
|
||||||
Some(&texture.pixels),
|
Some(&pixels),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
self.tex_size = (texture.width as u16, texture.height as u16);
|
self.egui_texture_version = Some(texture.version);
|
||||||
self.current_texture_version = Some(texture.version);
|
}
|
||||||
|
|
||||||
|
fn upload_user_textures(&mut self) {
|
||||||
|
let gl = &self.gl;
|
||||||
|
|
||||||
|
for user_texture in &mut self.user_textures {
|
||||||
|
if user_texture.texture.is_none() {
|
||||||
|
let pixels = std::mem::take(&mut user_texture.pixels);
|
||||||
|
|
||||||
|
let gl_texture = gl.create_texture().unwrap();
|
||||||
|
gl.bind_texture(Gl::TEXTURE_2D, Some(&gl_texture));
|
||||||
|
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_S, Gl::CLAMP_TO_EDGE as i32);
|
||||||
|
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_T, Gl::CLAMP_TO_EDGE as i32);
|
||||||
|
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MIN_FILTER, Gl::LINEAR as i32);
|
||||||
|
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MAG_FILTER, Gl::LINEAR as i32);
|
||||||
|
|
||||||
|
gl.bind_texture(Gl::TEXTURE_2D, Some(&gl_texture));
|
||||||
|
|
||||||
|
// TODO: https://developer.mozilla.org/en-US/docs/Web/API/EXT_sRGB
|
||||||
|
let level = 0;
|
||||||
|
let internal_format = Gl::RGBA;
|
||||||
|
let border = 0;
|
||||||
|
let src_format = Gl::RGBA;
|
||||||
|
let src_type = Gl::UNSIGNED_BYTE;
|
||||||
|
gl.tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array(
|
||||||
|
Gl::TEXTURE_2D,
|
||||||
|
level,
|
||||||
|
internal_format as i32,
|
||||||
|
user_texture.size.0 as i32,
|
||||||
|
user_texture.size.1 as i32,
|
||||||
|
border,
|
||||||
|
src_format,
|
||||||
|
src_type,
|
||||||
|
Some(&pixels),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
user_texture.texture = Some(gl_texture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_texture(&self, texture_id: egui::TextureId) -> &WebGlTexture {
|
||||||
|
match texture_id {
|
||||||
|
egui::TextureId::Egui => &self.egui_texture,
|
||||||
|
egui::TextureId::User(id) => {
|
||||||
|
let id = id as usize;
|
||||||
|
assert!(id < self.user_textures.len());
|
||||||
|
let texture = self.user_textures[id].texture.as_ref();
|
||||||
|
texture.expect("Should have been uploaded")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn paint_jobs(
|
pub fn paint_jobs(
|
||||||
&mut self,
|
&mut self,
|
||||||
bg_color: Srgba,
|
bg_color: Srgba,
|
||||||
jobs: PaintJobs,
|
jobs: PaintJobs,
|
||||||
texture: &Texture,
|
egui_texture: &Texture,
|
||||||
pixels_per_point: f32,
|
pixels_per_point: f32,
|
||||||
) -> Result<(), JsValue> {
|
) -> Result<(), JsValue> {
|
||||||
self.upload_texture(texture);
|
self.upload_egui_texture(egui_texture);
|
||||||
|
self.upload_user_textures();
|
||||||
|
|
||||||
let gl = &self.gl;
|
let gl = &self.gl;
|
||||||
|
|
||||||
|
@ -192,7 +297,6 @@ impl Painter {
|
||||||
gl.blend_func(Gl::ONE, Gl::ONE_MINUS_SRC_ALPHA); // premultiplied alpha
|
gl.blend_func(Gl::ONE, Gl::ONE_MINUS_SRC_ALPHA); // premultiplied alpha
|
||||||
gl.use_program(Some(&self.program));
|
gl.use_program(Some(&self.program));
|
||||||
gl.active_texture(Gl::TEXTURE0);
|
gl.active_texture(Gl::TEXTURE0);
|
||||||
gl.bind_texture(Gl::TEXTURE_2D, Some(&self.texture));
|
|
||||||
|
|
||||||
let u_screen_size_loc = gl
|
let u_screen_size_loc = gl
|
||||||
.get_uniform_location(&self.program, "u_screen_size")
|
.get_uniform_location(&self.program, "u_screen_size")
|
||||||
|
@ -224,6 +328,8 @@ impl Painter {
|
||||||
gl.clear(Gl::COLOR_BUFFER_BIT);
|
gl.clear(Gl::COLOR_BUFFER_BIT);
|
||||||
|
|
||||||
for (clip_rect, triangles) in jobs {
|
for (clip_rect, triangles) in jobs {
|
||||||
|
gl.bind_texture(Gl::TEXTURE_2D, Some(self.get_texture(triangles.texture_id)));
|
||||||
|
|
||||||
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;
|
||||||
let clip_max_x = pixels_per_point * clip_rect.max.x;
|
let clip_max_x = pixels_per_point * clip_rect.max.x;
|
||||||
|
|
Loading…
Reference in a new issue