Partial font texture update (#1149)
This commit is contained in:
parent
343f7da564
commit
462f181db3
24 changed files with 393 additions and 255 deletions
|
@ -20,7 +20,9 @@ NOTE: [`epaint`](epaint/CHANGELOG.md), [`eframe`](eframe/CHANGELOG.md), [`egui_w
|
||||||
* `Context` can now be cloned and stored between frames ([#1050](https://github.com/emilk/egui/pull/1050)).
|
* `Context` can now be cloned and stored between frames ([#1050](https://github.com/emilk/egui/pull/1050)).
|
||||||
* Renamed `Ui::visible` to `Ui::is_visible`.
|
* Renamed `Ui::visible` to `Ui::is_visible`.
|
||||||
* Split `Event::Text` into `Event::Text` and `Event::Paste` ([#1058](https://github.com/emilk/egui/pull/1058)).
|
* Split `Event::Text` into `Event::Text` and `Event::Paste` ([#1058](https://github.com/emilk/egui/pull/1058)).
|
||||||
* For integrations: `FontImage` has been replaced by `TexturesDelta` (found in `Output`), describing what textures were loaded and freed each frame ([#1110](https://github.com/emilk/egui/pull/1110)).
|
* For integrations:
|
||||||
|
* `FontImage` has been replaced by `TexturesDelta` (found in `Output`), describing what textures were loaded and freed each frame ([#1110](https://github.com/emilk/egui/pull/1110)).
|
||||||
|
* The painter must support partial texture updates ([#1149](https://github.com/emilk/egui/pull/1149)).
|
||||||
|
|
||||||
### Fixed 🐛
|
### Fixed 🐛
|
||||||
* Context menu now respects the theme ([#1043](https://github.com/emilk/egui/pull/1043))
|
* Context menu now respects the theme ([#1043](https://github.com/emilk/egui/pull/1043))
|
||||||
|
|
|
@ -33,7 +33,6 @@ struct ContextImpl {
|
||||||
fonts: Option<Fonts>,
|
fonts: Option<Fonts>,
|
||||||
memory: Memory,
|
memory: Memory,
|
||||||
animation_manager: AnimationManager,
|
animation_manager: AnimationManager,
|
||||||
latest_font_image_version: Option<u64>,
|
|
||||||
tex_manager: WrappedTextureManager,
|
tex_manager: WrappedTextureManager,
|
||||||
|
|
||||||
input: InputState,
|
input: InputState,
|
||||||
|
@ -709,17 +708,15 @@ impl Context {
|
||||||
.memory
|
.memory
|
||||||
.end_frame(&ctx_impl.input, &ctx_impl.frame_state.used_ids);
|
.end_frame(&ctx_impl.input, &ctx_impl.frame_state.used_ids);
|
||||||
|
|
||||||
let font_image = ctx_impl.fonts.as_ref().unwrap().font_image();
|
let font_image_delta = ctx_impl.fonts.as_ref().unwrap().font_image_delta();
|
||||||
let font_image_version = font_image.version;
|
if let Some(font_image_delta) = font_image_delta {
|
||||||
|
|
||||||
if Some(font_image_version) != ctx_impl.latest_font_image_version {
|
|
||||||
ctx_impl
|
ctx_impl
|
||||||
.tex_manager
|
.tex_manager
|
||||||
.0
|
.0
|
||||||
.write()
|
.write()
|
||||||
.set(TextureId::default(), font_image.image.clone().into());
|
.set(TextureId::default(), font_image_delta);
|
||||||
ctx_impl.latest_font_image_version = Some(font_image_version);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx_impl
|
ctx_impl
|
||||||
.output
|
.output
|
||||||
.textures_delta
|
.textures_delta
|
||||||
|
@ -757,7 +754,7 @@ impl Context {
|
||||||
let clipped_meshes = tessellator::tessellate_shapes(
|
let clipped_meshes = tessellator::tessellate_shapes(
|
||||||
shapes,
|
shapes,
|
||||||
tessellation_options,
|
tessellation_options,
|
||||||
self.fonts().font_image().size(),
|
self.fonts().font_image_size(),
|
||||||
);
|
);
|
||||||
self.write().paint_stats = paint_stats.with_clipped_meshes(&clipped_meshes);
|
self.write().paint_stats = paint_stats.with_clipped_meshes(&clipped_meshes);
|
||||||
clipped_meshes
|
clipped_meshes
|
||||||
|
@ -961,8 +958,8 @@ impl Context {
|
||||||
.show(ui, |ui| {
|
.show(ui, |ui| {
|
||||||
let mut font_definitions = self.fonts().definitions().clone();
|
let mut font_definitions = self.fonts().definitions().clone();
|
||||||
font_definitions.ui(ui);
|
font_definitions.ui(ui);
|
||||||
let font_image = self.fonts().font_image();
|
let font_image_size = self.fonts().font_image_size();
|
||||||
font_image.ui(ui);
|
crate::introspection::font_texture_ui(ui, font_image_size);
|
||||||
self.set_fonts(font_definitions);
|
self.set_fonts(font_definitions);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,60 +1,58 @@
|
||||||
//! uis for egui types.
|
//! uis for egui types.
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
impl Widget for &epaint::FontImage {
|
// Show font texture in demo Ui
|
||||||
fn ui(self, ui: &mut Ui) -> Response {
|
pub(crate) fn font_texture_ui(ui: &mut Ui, [width, height]: [usize; 2]) -> Response {
|
||||||
use epaint::Mesh;
|
use epaint::Mesh;
|
||||||
|
|
||||||
ui.vertical(|ui| {
|
ui.vertical(|ui| {
|
||||||
// Show font texture in demo Ui
|
let color = if ui.visuals().dark_mode {
|
||||||
let [width, height] = self.size();
|
Color32::WHITE
|
||||||
|
} else {
|
||||||
|
Color32::BLACK
|
||||||
|
};
|
||||||
|
|
||||||
ui.label(format!(
|
ui.label(format!(
|
||||||
"Texture size: {} x {} (hover to zoom)",
|
"Texture size: {} x {} (hover to zoom)",
|
||||||
width, height
|
width, height
|
||||||
));
|
));
|
||||||
if width <= 1 || height <= 1 {
|
if width <= 1 || height <= 1 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let mut size = vec2(width as f32, height as f32);
|
let mut size = vec2(width as f32, height as f32);
|
||||||
if size.x > ui.available_width() {
|
if size.x > ui.available_width() {
|
||||||
size *= ui.available_width() / size.x;
|
size *= ui.available_width() / size.x;
|
||||||
}
|
}
|
||||||
let (rect, response) = ui.allocate_at_least(size, Sense::hover());
|
let (rect, response) = ui.allocate_at_least(size, Sense::hover());
|
||||||
let mut mesh = Mesh::default();
|
let mut mesh = Mesh::default();
|
||||||
mesh.add_rect_with_uv(
|
mesh.add_rect_with_uv(rect, [pos2(0.0, 0.0), pos2(1.0, 1.0)].into(), color);
|
||||||
rect,
|
ui.painter().add(Shape::mesh(mesh));
|
||||||
[pos2(0.0, 0.0), pos2(1.0, 1.0)].into(),
|
|
||||||
Color32::WHITE,
|
|
||||||
);
|
|
||||||
ui.painter().add(Shape::mesh(mesh));
|
|
||||||
|
|
||||||
let (tex_w, tex_h) = (width as f32, height as f32);
|
let (tex_w, tex_h) = (width as f32, height as f32);
|
||||||
|
|
||||||
response
|
response
|
||||||
.on_hover_cursor(CursorIcon::ZoomIn)
|
.on_hover_cursor(CursorIcon::ZoomIn)
|
||||||
.on_hover_ui_at_pointer(|ui| {
|
.on_hover_ui_at_pointer(|ui| {
|
||||||
if let Some(pos) = ui.ctx().latest_pointer_pos() {
|
if let Some(pos) = ui.ctx().latest_pointer_pos() {
|
||||||
let (_id, zoom_rect) = ui.allocate_space(vec2(128.0, 128.0));
|
let (_id, zoom_rect) = ui.allocate_space(vec2(128.0, 128.0));
|
||||||
let u = remap_clamp(pos.x, rect.x_range(), 0.0..=tex_w);
|
let u = remap_clamp(pos.x, rect.x_range(), 0.0..=tex_w);
|
||||||
let v = remap_clamp(pos.y, rect.y_range(), 0.0..=tex_h);
|
let v = remap_clamp(pos.y, rect.y_range(), 0.0..=tex_h);
|
||||||
|
|
||||||
let texel_radius = 32.0;
|
let texel_radius = 32.0;
|
||||||
let u = u.at_least(texel_radius).at_most(tex_w - texel_radius);
|
let u = u.at_least(texel_radius).at_most(tex_w - texel_radius);
|
||||||
let v = v.at_least(texel_radius).at_most(tex_h - texel_radius);
|
let v = v.at_least(texel_radius).at_most(tex_h - texel_radius);
|
||||||
|
|
||||||
let uv_rect = Rect::from_min_max(
|
let uv_rect = Rect::from_min_max(
|
||||||
pos2((u - texel_radius) / tex_w, (v - texel_radius) / tex_h),
|
pos2((u - texel_radius) / tex_w, (v - texel_radius) / tex_h),
|
||||||
pos2((u + texel_radius) / tex_w, (v + texel_radius) / tex_h),
|
pos2((u + texel_radius) / tex_w, (v + texel_radius) / tex_h),
|
||||||
);
|
);
|
||||||
let mut mesh = Mesh::default();
|
let mut mesh = Mesh::default();
|
||||||
mesh.add_rect_with_uv(zoom_rect, uv_rect, Color32::WHITE);
|
mesh.add_rect_with_uv(zoom_rect, uv_rect, color);
|
||||||
ui.painter().add(Shape::mesh(mesh));
|
ui.painter().add(Shape::mesh(mesh));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.response
|
.response
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for &mut epaint::text::FontDefinitions {
|
impl Widget for &mut epaint::text::FontDefinitions {
|
||||||
|
|
|
@ -1087,9 +1087,13 @@ pub struct PlotImage {
|
||||||
|
|
||||||
impl PlotImage {
|
impl PlotImage {
|
||||||
/// Create a new image with position and size in plot coordinates.
|
/// Create a new image with position and size in plot coordinates.
|
||||||
pub fn new(texture_id: impl Into<TextureId>, position: Value, size: impl Into<Vec2>) -> Self {
|
pub fn new(
|
||||||
|
texture_id: impl Into<TextureId>,
|
||||||
|
center_position: Value,
|
||||||
|
size: impl Into<Vec2>,
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
position,
|
position: center_position,
|
||||||
name: Default::default(),
|
name: Default::default(),
|
||||||
highlight: false,
|
highlight: false,
|
||||||
texture_id: texture_id.into(),
|
texture_id: texture_id.into(),
|
||||||
|
|
|
@ -114,11 +114,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
|
||||||
let text_shape = TextShape::new(egui::Pos2::ZERO, galley);
|
let text_shape = TextShape::new(egui::Pos2::ZERO, galley);
|
||||||
c.bench_function("tessellate_text", |b| {
|
c.bench_function("tessellate_text", |b| {
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
tessellator.tessellate_text(
|
tessellator.tessellate_text(fonts.font_image_size(), text_shape.clone(), &mut mesh);
|
||||||
fonts.font_image().size(),
|
|
||||||
text_shape.clone(),
|
|
||||||
&mut mesh,
|
|
||||||
);
|
|
||||||
mesh.clear();
|
mesh.clear();
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
|
@ -343,10 +343,7 @@ impl Widget for &mut ItemsDemo {
|
||||||
let image = PlotImage::new(
|
let image = PlotImage::new(
|
||||||
texture,
|
texture,
|
||||||
Value::new(0.0, 10.0),
|
Value::new(0.0, 10.0),
|
||||||
[
|
5.0 * vec2(texture.aspect_ratio(), 1.0),
|
||||||
ui.fonts().font_image().width() as f32 / 100.0,
|
|
||||||
ui.fonts().font_image().height() as f32 / 100.0,
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let plot = Plot::new("items_demo")
|
let plot = Plot::new("items_demo")
|
||||||
|
|
|
@ -70,8 +70,8 @@ pub fn run(app: Box<dyn epi::App>, native_options: &epi::NativeOptions) -> ! {
|
||||||
integration.update(display.gl_window().window());
|
integration.update(display.gl_window().window());
|
||||||
let clipped_meshes = integration.egui_ctx.tessellate(shapes);
|
let clipped_meshes = integration.egui_ctx.tessellate(shapes);
|
||||||
|
|
||||||
for (id, image) in textures_delta.set {
|
for (id, image_delta) in textures_delta.set {
|
||||||
painter.set_texture(&display, id, &image);
|
painter.set_texture(&display, id, &image_delta);
|
||||||
}
|
}
|
||||||
|
|
||||||
// paint:
|
// paint:
|
||||||
|
|
|
@ -155,8 +155,8 @@ impl EguiGlium {
|
||||||
let shapes = std::mem::take(&mut self.shapes);
|
let shapes = std::mem::take(&mut self.shapes);
|
||||||
let mut textures_delta = std::mem::take(&mut self.textures_delta);
|
let mut textures_delta = std::mem::take(&mut self.textures_delta);
|
||||||
|
|
||||||
for (id, image) in textures_delta.set {
|
for (id, image_delta) in textures_delta.set {
|
||||||
self.painter.set_texture(display, id, &image);
|
self.painter.set_texture(display, id, &image_delta);
|
||||||
}
|
}
|
||||||
|
|
||||||
let clipped_meshes = self.egui_ctx.tessellate(shapes);
|
let clipped_meshes = self.egui_ctx.tessellate(shapes);
|
||||||
|
|
|
@ -185,9 +185,9 @@ impl Painter {
|
||||||
&mut self,
|
&mut self,
|
||||||
facade: &dyn glium::backend::Facade,
|
facade: &dyn glium::backend::Facade,
|
||||||
tex_id: egui::TextureId,
|
tex_id: egui::TextureId,
|
||||||
image: &egui::ImageData,
|
delta: &egui::epaint::ImageDelta,
|
||||||
) {
|
) {
|
||||||
let pixels: Vec<(u8, u8, u8, u8)> = match image {
|
let pixels: Vec<(u8, u8, u8, u8)> = match &delta.image {
|
||||||
egui::ImageData::Color(image) => {
|
egui::ImageData::Color(image) => {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
image.width() * image.height(),
|
image.width() * image.height(),
|
||||||
|
@ -206,15 +206,29 @@ impl Painter {
|
||||||
};
|
};
|
||||||
let glium_image = glium::texture::RawImage2d {
|
let glium_image = glium::texture::RawImage2d {
|
||||||
data: std::borrow::Cow::Owned(pixels),
|
data: std::borrow::Cow::Owned(pixels),
|
||||||
width: image.width() as _,
|
width: delta.image.width() as _,
|
||||||
height: image.height() as _,
|
height: delta.image.height() as _,
|
||||||
format: glium::texture::ClientFormat::U8U8U8U8,
|
format: glium::texture::ClientFormat::U8U8U8U8,
|
||||||
};
|
};
|
||||||
let format = texture::SrgbFormat::U8U8U8U8;
|
let format = texture::SrgbFormat::U8U8U8U8;
|
||||||
let mipmaps = texture::MipmapsOption::NoMipmap;
|
let mipmaps = texture::MipmapsOption::NoMipmap;
|
||||||
let gl_texture = SrgbTexture2d::with_format(facade, glium_image, format, mipmaps).unwrap();
|
|
||||||
|
|
||||||
self.textures.insert(tex_id, gl_texture.into());
|
if let Some(pos) = delta.pos {
|
||||||
|
// update a sub-region
|
||||||
|
if let Some(gl_texture) = self.textures.get(&tex_id) {
|
||||||
|
let rect = glium::Rect {
|
||||||
|
left: pos[0] as _,
|
||||||
|
bottom: pos[1] as _,
|
||||||
|
width: glium_image.width,
|
||||||
|
height: glium_image.height,
|
||||||
|
};
|
||||||
|
gl_texture.main_level().write(rect, glium_image);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let gl_texture =
|
||||||
|
SrgbTexture2d::with_format(facade, glium_image, format, mipmaps).unwrap();
|
||||||
|
self.textures.insert(tex_id, gl_texture.into());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn free_texture(&mut self, tex_id: egui::TextureId) {
|
pub fn free_texture(&mut self, tex_id: egui::TextureId) {
|
||||||
|
|
|
@ -86,8 +86,8 @@ pub fn run(app: Box<dyn epi::App>, native_options: &epi::NativeOptions) -> ! {
|
||||||
integration.update(gl_window.window());
|
integration.update(gl_window.window());
|
||||||
let clipped_meshes = integration.egui_ctx.tessellate(shapes);
|
let clipped_meshes = integration.egui_ctx.tessellate(shapes);
|
||||||
|
|
||||||
for (id, image) in textures_delta.set {
|
for (id, image_delta) in textures_delta.set {
|
||||||
painter.set_texture(&gl, id, &image);
|
painter.set_texture(&gl, id, &image_delta);
|
||||||
}
|
}
|
||||||
|
|
||||||
// paint:
|
// paint:
|
||||||
|
|
|
@ -175,8 +175,8 @@ impl EguiGlow {
|
||||||
let shapes = std::mem::take(&mut self.shapes);
|
let shapes = std::mem::take(&mut self.shapes);
|
||||||
let mut textures_delta = std::mem::take(&mut self.textures_delta);
|
let mut textures_delta = std::mem::take(&mut self.textures_delta);
|
||||||
|
|
||||||
for (id, image) in textures_delta.set {
|
for (id, image_delta) in textures_delta.set {
|
||||||
self.painter.set_texture(gl, id, &image);
|
self.painter.set_texture(gl, id, &image_delta);
|
||||||
}
|
}
|
||||||
|
|
||||||
let clipped_meshes = self.egui_ctx.tessellate(shapes);
|
let clipped_meshes = self.egui_ctx.tessellate(shapes);
|
||||||
|
|
|
@ -340,6 +340,7 @@ impl Painter {
|
||||||
|
|
||||||
gl.bind_texture(glow::TEXTURE_2D, Some(texture));
|
gl.bind_texture(glow::TEXTURE_2D, Some(texture));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transform clip rect to physical pixels:
|
// Transform clip rect to physical pixels:
|
||||||
let clip_min_x = pixels_per_point * clip_rect.min.x;
|
let clip_min_x = pixels_per_point * clip_rect.min.x;
|
||||||
let clip_min_y = pixels_per_point * clip_rect.min.y;
|
let clip_min_y = pixels_per_point * clip_rect.min.y;
|
||||||
|
@ -386,7 +387,7 @@ impl Painter {
|
||||||
&mut self,
|
&mut self,
|
||||||
gl: &glow::Context,
|
gl: &glow::Context,
|
||||||
tex_id: egui::TextureId,
|
tex_id: egui::TextureId,
|
||||||
image: &egui::ImageData,
|
delta: &egui::epaint::ImageDelta,
|
||||||
) {
|
) {
|
||||||
self.assert_not_destroyed();
|
self.assert_not_destroyed();
|
||||||
|
|
||||||
|
@ -398,7 +399,7 @@ impl Painter {
|
||||||
gl.bind_texture(glow::TEXTURE_2D, Some(glow_texture));
|
gl.bind_texture(glow::TEXTURE_2D, Some(glow_texture));
|
||||||
}
|
}
|
||||||
|
|
||||||
match image {
|
match &delta.image {
|
||||||
egui::ImageData::Color(image) => {
|
egui::ImageData::Color(image) => {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
image.width() * image.height(),
|
image.width() * image.height(),
|
||||||
|
@ -408,7 +409,7 @@ impl Painter {
|
||||||
|
|
||||||
let data: &[u8] = bytemuck::cast_slice(image.pixels.as_ref());
|
let data: &[u8] = bytemuck::cast_slice(image.pixels.as_ref());
|
||||||
|
|
||||||
self.upload_texture_srgb(gl, image.size, data);
|
self.upload_texture_srgb(gl, delta.pos, image.size, data);
|
||||||
}
|
}
|
||||||
egui::ImageData::Alpha(image) => {
|
egui::ImageData::Alpha(image) => {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -427,12 +428,18 @@ impl Painter {
|
||||||
.flat_map(|a| a.to_array())
|
.flat_map(|a| a.to_array())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
self.upload_texture_srgb(gl, image.size, &data);
|
self.upload_texture_srgb(gl, delta.pos, image.size, &data);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn upload_texture_srgb(&mut self, gl: &glow::Context, [w, h]: [usize; 2], data: &[u8]) {
|
fn upload_texture_srgb(
|
||||||
|
&mut self,
|
||||||
|
gl: &glow::Context,
|
||||||
|
pos: Option<[usize; 2]>,
|
||||||
|
[w, h]: [usize; 2],
|
||||||
|
data: &[u8],
|
||||||
|
) {
|
||||||
assert_eq!(data.len(), w * h * 4);
|
assert_eq!(data.len(), w * h * 4);
|
||||||
assert!(w >= 1 && h >= 1);
|
assert!(w >= 1 && h >= 1);
|
||||||
unsafe {
|
unsafe {
|
||||||
|
@ -457,38 +464,50 @@ impl Painter {
|
||||||
glow::TEXTURE_WRAP_T,
|
glow::TEXTURE_WRAP_T,
|
||||||
glow::CLAMP_TO_EDGE as i32,
|
glow::CLAMP_TO_EDGE as i32,
|
||||||
);
|
);
|
||||||
|
check_for_gl_error(gl, "tex_parameter");
|
||||||
|
|
||||||
if self.is_webgl_1 {
|
let (internal_format, src_format) = if self.is_webgl_1 {
|
||||||
let format = if self.srgb_support {
|
let format = if self.srgb_support {
|
||||||
glow::SRGB_ALPHA
|
glow::SRGB_ALPHA
|
||||||
} else {
|
} else {
|
||||||
glow::RGBA
|
glow::RGBA
|
||||||
};
|
};
|
||||||
gl.tex_image_2d(
|
(format, format)
|
||||||
glow::TEXTURE_2D,
|
|
||||||
0,
|
|
||||||
format as i32,
|
|
||||||
w as i32,
|
|
||||||
h as i32,
|
|
||||||
0,
|
|
||||||
format,
|
|
||||||
glow::UNSIGNED_BYTE,
|
|
||||||
Some(data),
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
|
(glow::SRGB8_ALPHA8, glow::RGBA)
|
||||||
|
};
|
||||||
|
|
||||||
|
gl.pixel_store_i32(glow::UNPACK_ALIGNMENT, 1);
|
||||||
|
|
||||||
|
let level = 0;
|
||||||
|
if let Some([x, y]) = pos {
|
||||||
|
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(gl, "tex_sub_image_2d");
|
||||||
|
} else {
|
||||||
|
let border = 0;
|
||||||
gl.tex_image_2d(
|
gl.tex_image_2d(
|
||||||
glow::TEXTURE_2D,
|
glow::TEXTURE_2D,
|
||||||
0,
|
level,
|
||||||
glow::SRGB8_ALPHA8 as i32,
|
internal_format as _,
|
||||||
w as i32,
|
w as _,
|
||||||
h as i32,
|
h as _,
|
||||||
0,
|
border,
|
||||||
glow::RGBA,
|
src_format,
|
||||||
glow::UNSIGNED_BYTE,
|
glow::UNSIGNED_BYTE,
|
||||||
Some(data),
|
Some(data),
|
||||||
);
|
);
|
||||||
|
check_for_gl_error(gl, "tex_image_2d");
|
||||||
}
|
}
|
||||||
check_for_gl_error(gl, "upload_texture_srgb");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -221,8 +221,8 @@ impl AppRunner {
|
||||||
/// Paint the results of the last call to [`Self::logic`].
|
/// Paint the results of the last call to [`Self::logic`].
|
||||||
pub fn paint(&mut self, clipped_meshes: Vec<egui::ClippedMesh>) -> Result<(), JsValue> {
|
pub fn paint(&mut self, clipped_meshes: Vec<egui::ClippedMesh>) -> Result<(), JsValue> {
|
||||||
let textures_delta = std::mem::take(&mut self.textures_delta);
|
let textures_delta = std::mem::take(&mut self.textures_delta);
|
||||||
for (id, image) in textures_delta.set {
|
for (id, image_delta) in textures_delta.set {
|
||||||
self.painter.set_texture(id, image);
|
self.painter.set_texture(id, &image_delta);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.painter.clear(self.app.clear_color());
|
self.painter.clear(self.app.clear_color());
|
||||||
|
|
|
@ -40,8 +40,8 @@ impl WrappedGlowPainter {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl crate::Painter for WrappedGlowPainter {
|
impl crate::Painter for WrappedGlowPainter {
|
||||||
fn set_texture(&mut self, tex_id: egui::TextureId, image: egui::ImageData) {
|
fn set_texture(&mut self, tex_id: egui::TextureId, delta: &egui::epaint::ImageDelta) {
|
||||||
self.painter.set_texture(&self.glow_ctx, tex_id, &image);
|
self.painter.set_texture(&self.glow_ctx, tex_id, delta);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn free_texture(&mut self, tex_id: egui::TextureId) {
|
fn free_texture(&mut self, tex_id: egui::TextureId) {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use wasm_bindgen::prelude::JsValue;
|
use wasm_bindgen::prelude::JsValue;
|
||||||
|
|
||||||
pub trait Painter {
|
pub trait Painter {
|
||||||
fn set_texture(&mut self, tex_id: egui::TextureId, image: egui::ImageData);
|
fn set_texture(&mut self, tex_id: egui::TextureId, delta: &egui::epaint::ImageDelta);
|
||||||
|
|
||||||
fn free_texture(&mut self, tex_id: egui::TextureId);
|
fn free_texture(&mut self, tex_id: egui::TextureId);
|
||||||
|
|
||||||
|
|
|
@ -40,13 +40,6 @@ impl WebGlPainter {
|
||||||
|
|
||||||
// --------------------------------------------------------------------
|
// --------------------------------------------------------------------
|
||||||
|
|
||||||
let egui_texture = gl.create_texture().unwrap();
|
|
||||||
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_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);
|
|
||||||
|
|
||||||
let srgb_supported = matches!(gl.get_extension("EXT_sRGB"), Ok(Some(_)));
|
let srgb_supported = matches!(gl.get_extension("EXT_sRGB"), Ok(Some(_)));
|
||||||
|
|
||||||
let vert_shader = compile_shader(
|
let vert_shader = compile_shader(
|
||||||
|
@ -222,36 +215,61 @@ impl WebGlPainter {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_texture_rgba(&mut self, tex_id: egui::TextureId, size: [usize; 2], pixels: &[u8]) {
|
fn set_texture_rgba(
|
||||||
|
&mut self,
|
||||||
|
tex_id: egui::TextureId,
|
||||||
|
pos: Option<[usize; 2]>,
|
||||||
|
[w, h]: [usize; 2],
|
||||||
|
pixels: &[u8],
|
||||||
|
) {
|
||||||
let gl = &self.gl;
|
let gl = &self.gl;
|
||||||
let gl_texture = gl.create_texture().unwrap();
|
|
||||||
gl.bind_texture(Gl::TEXTURE_2D, Some(&gl_texture));
|
let gl_texture = self
|
||||||
|
.textures
|
||||||
|
.entry(tex_id)
|
||||||
|
.or_insert_with(|| 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 _);
|
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_S, Gl::CLAMP_TO_EDGE as _);
|
||||||
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_T, Gl::CLAMP_TO_EDGE as _);
|
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_T, Gl::CLAMP_TO_EDGE as _);
|
||||||
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MIN_FILTER, Gl::LINEAR as _);
|
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MIN_FILTER, Gl::LINEAR as _);
|
||||||
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MAG_FILTER, Gl::LINEAR as _);
|
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MAG_FILTER, Gl::LINEAR as _);
|
||||||
|
|
||||||
gl.bind_texture(Gl::TEXTURE_2D, Some(&gl_texture));
|
|
||||||
|
|
||||||
let level = 0;
|
let level = 0;
|
||||||
let internal_format = self.texture_format;
|
let internal_format = self.texture_format;
|
||||||
let border = 0;
|
let border = 0;
|
||||||
let src_format = self.texture_format;
|
let src_format = self.texture_format;
|
||||||
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::TEXTURE_2D,
|
|
||||||
level,
|
|
||||||
internal_format as _,
|
|
||||||
size[0] as _,
|
|
||||||
size[1] as _,
|
|
||||||
border,
|
|
||||||
src_format,
|
|
||||||
src_type,
|
|
||||||
Some(pixels),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
self.textures.insert(tex_id, gl_texture);
|
gl.pixel_storei(Gl::UNPACK_ALIGNMENT, 1);
|
||||||
|
|
||||||
|
if let Some([x, y]) = pos {
|
||||||
|
gl.tex_sub_image_2d_with_i32_and_i32_and_u32_and_type_and_opt_u8_array(
|
||||||
|
Gl::TEXTURE_2D,
|
||||||
|
level,
|
||||||
|
x as _,
|
||||||
|
y as _,
|
||||||
|
w as _,
|
||||||
|
h as _,
|
||||||
|
src_format,
|
||||||
|
src_type,
|
||||||
|
Some(pixels),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
} else {
|
||||||
|
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 _,
|
||||||
|
w as _,
|
||||||
|
h as _,
|
||||||
|
border,
|
||||||
|
src_format,
|
||||||
|
src_type,
|
||||||
|
Some(pixels),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -271,8 +289,8 @@ impl epi::NativeTexture for WebGlPainter {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl crate::Painter for WebGlPainter {
|
impl crate::Painter for WebGlPainter {
|
||||||
fn set_texture(&mut self, tex_id: egui::TextureId, image: egui::ImageData) {
|
fn set_texture(&mut self, tex_id: egui::TextureId, delta: &egui::epaint::ImageDelta) {
|
||||||
match image {
|
match &delta.image {
|
||||||
egui::ImageData::Color(image) => {
|
egui::ImageData::Color(image) => {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
image.width() * image.height(),
|
image.width() * image.height(),
|
||||||
|
@ -281,7 +299,7 @@ impl crate::Painter for WebGlPainter {
|
||||||
);
|
);
|
||||||
|
|
||||||
let data: &[u8] = bytemuck::cast_slice(image.pixels.as_ref());
|
let data: &[u8] = bytemuck::cast_slice(image.pixels.as_ref());
|
||||||
self.set_texture_rgba(tex_id, image.size, data);
|
self.set_texture_rgba(tex_id, delta.pos, image.size, data);
|
||||||
}
|
}
|
||||||
egui::ImageData::Alpha(image) => {
|
egui::ImageData::Alpha(image) => {
|
||||||
let gamma = if self.post_process.is_none() {
|
let gamma = if self.post_process.is_none() {
|
||||||
|
@ -293,7 +311,7 @@ impl crate::Painter for WebGlPainter {
|
||||||
.srgba_pixels(gamma)
|
.srgba_pixels(gamma)
|
||||||
.flat_map(|a| a.to_array())
|
.flat_map(|a| a.to_array())
|
||||||
.collect();
|
.collect();
|
||||||
self.set_texture_rgba(tex_id, image.size, &data);
|
self.set_texture_rgba(tex_id, delta.pos, image.size, &data);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,13 +40,6 @@ impl WebGl2Painter {
|
||||||
|
|
||||||
// --------------------------------------------------------------------
|
// --------------------------------------------------------------------
|
||||||
|
|
||||||
let egui_texture = gl.create_texture().unwrap();
|
|
||||||
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_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);
|
|
||||||
|
|
||||||
let vert_shader = compile_shader(
|
let vert_shader = compile_shader(
|
||||||
&gl,
|
&gl,
|
||||||
Gl::VERTEX_SHADER,
|
Gl::VERTEX_SHADER,
|
||||||
|
@ -206,37 +199,61 @@ impl WebGl2Painter {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_texture_rgba(&mut self, tex_id: egui::TextureId, size: [usize; 2], pixels: &[u8]) {
|
fn set_texture_rgba(
|
||||||
|
&mut self,
|
||||||
|
tex_id: egui::TextureId,
|
||||||
|
pos: Option<[usize; 2]>,
|
||||||
|
[w, h]: [usize; 2],
|
||||||
|
pixels: &[u8],
|
||||||
|
) {
|
||||||
let gl = &self.gl;
|
let gl = &self.gl;
|
||||||
let gl_texture = gl.create_texture().unwrap();
|
|
||||||
gl.bind_texture(Gl::TEXTURE_2D, Some(&gl_texture));
|
let gl_texture = self
|
||||||
|
.textures
|
||||||
|
.entry(tex_id)
|
||||||
|
.or_insert_with(|| 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 _);
|
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_S, Gl::CLAMP_TO_EDGE as _);
|
||||||
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_T, Gl::CLAMP_TO_EDGE as _);
|
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_T, Gl::CLAMP_TO_EDGE as _);
|
||||||
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MIN_FILTER, Gl::LINEAR as _);
|
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MIN_FILTER, Gl::LINEAR as _);
|
||||||
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MAG_FILTER, Gl::LINEAR as _);
|
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MAG_FILTER, Gl::LINEAR as _);
|
||||||
|
|
||||||
gl.bind_texture(Gl::TEXTURE_2D, Some(&gl_texture));
|
|
||||||
|
|
||||||
let level = 0;
|
let level = 0;
|
||||||
let internal_format = Gl::SRGB8_ALPHA8;
|
let internal_format = Gl::SRGB8_ALPHA8;
|
||||||
let border = 0;
|
let border = 0;
|
||||||
let src_format = Gl::RGBA;
|
let src_format = Gl::RGBA;
|
||||||
let src_type = Gl::UNSIGNED_BYTE;
|
let src_type = Gl::UNSIGNED_BYTE;
|
||||||
gl.pixel_storei(Gl::UNPACK_ALIGNMENT, 1);
|
|
||||||
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,
|
|
||||||
size[0] as i32,
|
|
||||||
size[1] as i32,
|
|
||||||
border,
|
|
||||||
src_format,
|
|
||||||
src_type,
|
|
||||||
Some(pixels),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
self.textures.insert(tex_id, gl_texture);
|
gl.pixel_storei(Gl::UNPACK_ALIGNMENT, 1);
|
||||||
|
|
||||||
|
if let Some([x, y]) = pos {
|
||||||
|
gl.tex_sub_image_2d_with_i32_and_i32_and_u32_and_type_and_opt_u8_array(
|
||||||
|
Gl::TEXTURE_2D,
|
||||||
|
level,
|
||||||
|
x as _,
|
||||||
|
y as _,
|
||||||
|
w as _,
|
||||||
|
h as _,
|
||||||
|
src_format,
|
||||||
|
src_type,
|
||||||
|
Some(pixels),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
} else {
|
||||||
|
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 _,
|
||||||
|
w as _,
|
||||||
|
h as _,
|
||||||
|
border,
|
||||||
|
src_format,
|
||||||
|
src_type,
|
||||||
|
Some(pixels),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -256,8 +273,8 @@ impl epi::NativeTexture for WebGl2Painter {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl crate::Painter for WebGl2Painter {
|
impl crate::Painter for WebGl2Painter {
|
||||||
fn set_texture(&mut self, tex_id: egui::TextureId, image: egui::ImageData) {
|
fn set_texture(&mut self, tex_id: egui::TextureId, delta: &egui::epaint::ImageDelta) {
|
||||||
match image {
|
match &delta.image {
|
||||||
egui::ImageData::Color(image) => {
|
egui::ImageData::Color(image) => {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
image.width() * image.height(),
|
image.width() * image.height(),
|
||||||
|
@ -266,7 +283,7 @@ impl crate::Painter for WebGl2Painter {
|
||||||
);
|
);
|
||||||
|
|
||||||
let data: &[u8] = bytemuck::cast_slice(image.pixels.as_ref());
|
let data: &[u8] = bytemuck::cast_slice(image.pixels.as_ref());
|
||||||
self.set_texture_rgba(tex_id, image.size, data);
|
self.set_texture_rgba(tex_id, delta.pos, image.size, data);
|
||||||
}
|
}
|
||||||
egui::ImageData::Alpha(image) => {
|
egui::ImageData::Alpha(image) => {
|
||||||
let gamma = 1.0;
|
let gamma = 1.0;
|
||||||
|
@ -274,7 +291,7 @@ impl crate::Painter for WebGl2Painter {
|
||||||
.srgba_pixels(gamma)
|
.srgba_pixels(gamma)
|
||||||
.flat_map(|a| a.to_array())
|
.flat_map(|a| a.to_array())
|
||||||
.collect();
|
.collect();
|
||||||
self.set_texture_rgba(tex_id, image.size, &data);
|
self.set_texture_rgba(tex_id, delta.pos, image.size, &data);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -211,6 +211,23 @@ impl AlphaImage {
|
||||||
.iter()
|
.iter()
|
||||||
.map(move |&a| srgba_from_alpha_lut[a as usize])
|
.map(move |&a| srgba_from_alpha_lut[a as usize])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Clone a sub-region as a new image
|
||||||
|
pub fn region(&self, [x, y]: [usize; 2], [w, h]: [usize; 2]) -> AlphaImage {
|
||||||
|
assert!(x + w <= self.width());
|
||||||
|
assert!(y + h <= self.height());
|
||||||
|
|
||||||
|
let mut pixels = Vec::with_capacity(w * h);
|
||||||
|
for y in y..y + h {
|
||||||
|
let offset = y * self.width() + x;
|
||||||
|
pixels.extend(&self.pixels[offset..(offset + w)]);
|
||||||
|
}
|
||||||
|
assert_eq!(pixels.len(), w * h);
|
||||||
|
AlphaImage {
|
||||||
|
size: [w, h],
|
||||||
|
pixels,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::ops::Index<(usize, usize)> for AlphaImage {
|
impl std::ops::Index<(usize, usize)> for AlphaImage {
|
||||||
|
@ -239,3 +256,45 @@ impl From<AlphaImage> for ImageData {
|
||||||
Self::Alpha(image)
|
Self::Alpha(image)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// A change to an image.
|
||||||
|
///
|
||||||
|
/// Either a whole new image,
|
||||||
|
/// or an update to a rectangular region of it.
|
||||||
|
#[derive(Clone, PartialEq)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||||
|
#[must_use = "The painter must take care of this"]
|
||||||
|
pub struct ImageDelta {
|
||||||
|
/// What to set the texture to.
|
||||||
|
pub image: ImageData,
|
||||||
|
|
||||||
|
/// If `None`, set the whole texture to [`Self::image`].
|
||||||
|
/// If `Some(pos)`, update a sub-region of an already allocated texture.
|
||||||
|
pub pos: Option<[usize; 2]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ImageDelta {
|
||||||
|
/// Update the whole texture.
|
||||||
|
pub fn full(image: impl Into<ImageData>) -> Self {
|
||||||
|
Self {
|
||||||
|
image: image.into(),
|
||||||
|
pos: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update a sub-region of an existing texture.
|
||||||
|
pub fn partial(pos: [usize; 2], image: impl Into<ImageData>) -> Self {
|
||||||
|
Self {
|
||||||
|
image: image.into(),
|
||||||
|
pos: Some(pos),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Is this affecting the whole texture?
|
||||||
|
/// If `false`, this is a partial (sub-region) update.
|
||||||
|
pub fn is_whole(&self) -> bool {
|
||||||
|
self.pos.is_none()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -105,7 +105,7 @@ pub mod util;
|
||||||
|
|
||||||
pub use {
|
pub use {
|
||||||
color::{Color32, Rgba},
|
color::{Color32, Rgba},
|
||||||
image::{AlphaImage, ColorImage, ImageData},
|
image::{AlphaImage, ColorImage, ImageData, ImageDelta},
|
||||||
mesh::{Mesh, Mesh16, Vertex},
|
mesh::{Mesh, Mesh16, Vertex},
|
||||||
shadow::Shadow,
|
shadow::Shadow,
|
||||||
shape::{CircleShape, PathShape, RectShape, Shape, TextShape},
|
shape::{CircleShape, PathShape, RectShape, Shape, TextShape},
|
||||||
|
@ -113,7 +113,7 @@ pub use {
|
||||||
stroke::Stroke,
|
stroke::Stroke,
|
||||||
tessellator::{tessellate_shapes, TessellationOptions, Tessellator},
|
tessellator::{tessellate_shapes, TessellationOptions, Tessellator},
|
||||||
text::{Fonts, Galley, TextStyle},
|
text::{Fonts, Galley, TextStyle},
|
||||||
texture_atlas::{FontImage, TextureAtlas},
|
texture_atlas::TextureAtlas,
|
||||||
texture_handle::TextureHandle,
|
texture_handle::TextureHandle,
|
||||||
textures::TextureManager,
|
textures::TextureManager,
|
||||||
};
|
};
|
||||||
|
|
|
@ -374,14 +374,12 @@ fn allocate_glyph(
|
||||||
if glyph_width == 0 || glyph_height == 0 {
|
if glyph_width == 0 || glyph_height == 0 {
|
||||||
UvRect::default()
|
UvRect::default()
|
||||||
} else {
|
} else {
|
||||||
let glyph_pos = atlas.allocate((glyph_width, glyph_height));
|
let (glyph_pos, image) = atlas.allocate((glyph_width, glyph_height));
|
||||||
|
|
||||||
let image = atlas.image_mut();
|
|
||||||
glyph.draw(|x, y, v| {
|
glyph.draw(|x, y, v| {
|
||||||
if v > 0.0 {
|
if v > 0.0 {
|
||||||
let px = glyph_pos.0 + x as usize;
|
let px = glyph_pos.0 + x as usize;
|
||||||
let py = glyph_pos.1 + y as usize;
|
let py = glyph_pos.1 + y as usize;
|
||||||
image.image[(px, py)] = (v * 255.0).round() as u8;
|
image[(px, py)] = (v * 255.0).round() as u8;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ use crate::{
|
||||||
font::{Font, FontImpl},
|
font::{Font, FontImpl},
|
||||||
Galley, LayoutJob,
|
Galley, LayoutJob,
|
||||||
},
|
},
|
||||||
FontImage, TextureAtlas,
|
TextureAtlas,
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: rename
|
// TODO: rename
|
||||||
|
@ -233,11 +233,6 @@ pub struct Fonts {
|
||||||
definitions: FontDefinitions,
|
definitions: FontDefinitions,
|
||||||
fonts: BTreeMap<TextStyle, Font>,
|
fonts: BTreeMap<TextStyle, Font>,
|
||||||
atlas: Arc<Mutex<TextureAtlas>>,
|
atlas: Arc<Mutex<TextureAtlas>>,
|
||||||
|
|
||||||
/// Copy of the font image in the texture atlas.
|
|
||||||
/// This is so we can return a reference to it (the texture atlas is behind a lock).
|
|
||||||
buffered_font_image: Mutex<Arc<FontImage>>,
|
|
||||||
|
|
||||||
galley_cache: Mutex<GalleyCache>,
|
galley_cache: Mutex<GalleyCache>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -257,9 +252,9 @@ impl Fonts {
|
||||||
|
|
||||||
{
|
{
|
||||||
// Make the top left pixel fully white:
|
// Make the top left pixel fully white:
|
||||||
let pos = atlas.allocate((1, 1));
|
let (pos, image) = atlas.allocate((1, 1));
|
||||||
assert_eq!(pos, (0, 0));
|
assert_eq!(pos, (0, 0));
|
||||||
atlas.image_mut().image[pos] = 255;
|
image[pos] = 255;
|
||||||
}
|
}
|
||||||
|
|
||||||
let atlas = Arc::new(Mutex::new(atlas));
|
let atlas = Arc::new(Mutex::new(atlas));
|
||||||
|
@ -283,19 +278,11 @@ impl Fonts {
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
{
|
|
||||||
let mut atlas = atlas.lock();
|
|
||||||
let texture = atlas.image_mut();
|
|
||||||
// Make sure we seed the texture version with something unique based on the default characters:
|
|
||||||
texture.version = crate::util::hash(&texture.image);
|
|
||||||
}
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
pixels_per_point,
|
pixels_per_point,
|
||||||
definitions,
|
definitions,
|
||||||
fonts,
|
fonts,
|
||||||
atlas,
|
atlas,
|
||||||
buffered_font_image: Default::default(),
|
|
||||||
galley_cache: Default::default(),
|
galley_cache: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -319,15 +306,14 @@ impl Fonts {
|
||||||
(point * self.pixels_per_point).floor() / self.pixels_per_point
|
(point * self.pixels_per_point).floor() / self.pixels_per_point
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Call each frame to get the latest available font texture data.
|
/// Call each frame to get the change to the font texture since last call.
|
||||||
pub fn font_image(&self) -> Arc<FontImage> {
|
pub fn font_image_delta(&self) -> Option<crate::ImageDelta> {
|
||||||
let atlas = self.atlas.lock();
|
self.atlas.lock().take_delta()
|
||||||
let mut buffered_texture = self.buffered_font_image.lock();
|
}
|
||||||
if buffered_texture.version != atlas.image().version {
|
|
||||||
*buffered_texture = Arc::new(atlas.image().clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
buffered_texture.clone()
|
/// Current size of the font image
|
||||||
|
pub fn font_image_size(&self) -> [usize; 2] {
|
||||||
|
self.atlas.lock().size()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Width of this character in points.
|
/// Width of this character in points.
|
||||||
|
|
|
@ -1,39 +1,40 @@
|
||||||
use crate::image::AlphaImage;
|
use crate::{AlphaImage, ImageDelta};
|
||||||
|
|
||||||
/// An 8-bit texture containing font data.
|
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||||
#[derive(Clone, Default)]
|
struct Rectu {
|
||||||
pub struct FontImage {
|
/// inclusive
|
||||||
/// e.g. a hash of the data. Use this to detect changes!
|
min_x: usize,
|
||||||
/// If the texture changes, this too will change.
|
/// inclusive
|
||||||
pub version: u64,
|
min_y: usize,
|
||||||
|
/// exclusive
|
||||||
/// The actual image data.
|
max_x: usize,
|
||||||
pub image: AlphaImage,
|
/// exclusive
|
||||||
|
max_y: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FontImage {
|
impl Rectu {
|
||||||
#[inline]
|
const NOTHING: Self = Self {
|
||||||
pub fn size(&self) -> [usize; 2] {
|
min_x: usize::MAX,
|
||||||
self.image.size
|
min_y: usize::MAX,
|
||||||
}
|
max_x: 0,
|
||||||
|
max_y: 0,
|
||||||
#[inline]
|
};
|
||||||
pub fn width(&self) -> usize {
|
const EVERYTHING: Self = Self {
|
||||||
self.image.size[0]
|
min_x: 0,
|
||||||
}
|
min_y: 0,
|
||||||
|
max_x: usize::MAX,
|
||||||
#[inline]
|
max_y: usize::MAX,
|
||||||
pub fn height(&self) -> usize {
|
};
|
||||||
self.image.size[1]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Contains font data in an atlas, where each character occupied a small rectangle.
|
/// Contains font data in an atlas, where each character occupied a small rectangle.
|
||||||
///
|
///
|
||||||
/// More characters can be added, possibly expanding the texture.
|
/// More characters can be added, possibly expanding the texture.
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone)]
|
||||||
pub struct TextureAtlas {
|
pub struct TextureAtlas {
|
||||||
image: FontImage,
|
image: AlphaImage,
|
||||||
|
/// What part of the image that is dirty
|
||||||
|
dirty: Rectu,
|
||||||
|
|
||||||
/// Used for when allocating new rectangles.
|
/// Used for when allocating new rectangles.
|
||||||
cursor: (usize, usize),
|
cursor: (usize, usize),
|
||||||
|
@ -43,25 +44,35 @@ pub struct TextureAtlas {
|
||||||
impl TextureAtlas {
|
impl TextureAtlas {
|
||||||
pub fn new(size: [usize; 2]) -> Self {
|
pub fn new(size: [usize; 2]) -> Self {
|
||||||
Self {
|
Self {
|
||||||
image: FontImage {
|
image: AlphaImage::new(size),
|
||||||
version: 0,
|
dirty: Rectu::EVERYTHING,
|
||||||
image: AlphaImage::new(size),
|
cursor: (0, 0),
|
||||||
},
|
row_height: 0,
|
||||||
..Default::default()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn image(&self) -> &FontImage {
|
pub fn size(&self) -> [usize; 2] {
|
||||||
&self.image
|
self.image.size
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn image_mut(&mut self) -> &mut FontImage {
|
/// Call to get the change to the image since last call.
|
||||||
self.image.version += 1;
|
pub fn take_delta(&mut self) -> Option<ImageDelta> {
|
||||||
&mut self.image
|
let dirty = std::mem::replace(&mut self.dirty, Rectu::NOTHING);
|
||||||
|
if dirty == Rectu::NOTHING {
|
||||||
|
None
|
||||||
|
} else if dirty == Rectu::EVERYTHING {
|
||||||
|
Some(ImageDelta::full(self.image.clone()))
|
||||||
|
} else {
|
||||||
|
let pos = [dirty.min_x, dirty.min_y];
|
||||||
|
let size = [dirty.max_x - dirty.min_x, dirty.max_y - dirty.min_y];
|
||||||
|
let region = self.image.region(pos, size);
|
||||||
|
Some(ImageDelta::partial(pos, region))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the coordinates of where the rect ended up.
|
/// Returns the coordinates of where the rect ended up,
|
||||||
pub fn allocate(&mut self, (w, h): (usize, usize)) -> (usize, usize) {
|
/// and invalidates the region.
|
||||||
|
pub fn allocate(&mut self, (w, h): (usize, usize)) -> ((usize, usize), &mut AlphaImage) {
|
||||||
/// On some low-precision GPUs (my old iPad) characters get muddled up
|
/// On some low-precision GPUs (my old iPad) characters get muddled up
|
||||||
/// if we don't add some empty pixels between the characters.
|
/// if we don't add some empty pixels between the characters.
|
||||||
/// On modern high-precision GPUs this is not needed.
|
/// On modern high-precision GPUs this is not needed.
|
||||||
|
@ -81,21 +92,31 @@ impl TextureAtlas {
|
||||||
}
|
}
|
||||||
|
|
||||||
self.row_height = self.row_height.max(h);
|
self.row_height = self.row_height.max(h);
|
||||||
resize_to_min_height(&mut self.image.image, self.cursor.1 + self.row_height);
|
if resize_to_min_height(&mut self.image, self.cursor.1 + self.row_height) {
|
||||||
|
self.dirty = Rectu::EVERYTHING;
|
||||||
|
}
|
||||||
|
|
||||||
let pos = self.cursor;
|
let pos = self.cursor;
|
||||||
self.cursor.0 += w + PADDING;
|
self.cursor.0 += w + PADDING;
|
||||||
self.image.version += 1;
|
|
||||||
(pos.0 as usize, pos.1 as usize)
|
self.dirty.min_x = self.dirty.min_x.min(pos.0);
|
||||||
|
self.dirty.min_y = self.dirty.min_y.min(pos.1);
|
||||||
|
self.dirty.max_x = self.dirty.max_x.max(pos.0 + w);
|
||||||
|
self.dirty.max_y = self.dirty.max_y.max(pos.1 + h);
|
||||||
|
|
||||||
|
(pos, &mut self.image)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resize_to_min_height(image: &mut AlphaImage, min_height: usize) {
|
fn resize_to_min_height(image: &mut AlphaImage, min_height: usize) -> bool {
|
||||||
while min_height >= image.height() {
|
while min_height >= image.height() {
|
||||||
image.size[1] *= 2; // double the height
|
image.size[1] *= 2; // double the height
|
||||||
}
|
}
|
||||||
|
|
||||||
if image.width() * image.height() > image.pixels.len() {
|
if image.width() * image.height() > image.pixels.len() {
|
||||||
image.pixels.resize(image.width() * image.height(), 0);
|
image.pixels.resize(image.width() * image.height(), 0);
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
emath::NumExt,
|
emath::NumExt,
|
||||||
mutex::{Arc, RwLock},
|
mutex::{Arc, RwLock},
|
||||||
ImageData, TextureId, TextureManager,
|
ImageData, ImageDelta, TextureId, TextureManager,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Used to paint images.
|
/// Used to paint images.
|
||||||
|
@ -66,7 +66,16 @@ impl TextureHandle {
|
||||||
|
|
||||||
/// Assign a new image to an existing texture.
|
/// Assign a new image to an existing texture.
|
||||||
pub fn set(&mut self, image: impl Into<ImageData>) {
|
pub fn set(&mut self, image: impl Into<ImageData>) {
|
||||||
self.tex_mngr.write().set(self.id, image.into());
|
self.tex_mngr
|
||||||
|
.write()
|
||||||
|
.set(self.id, ImageDelta::full(image.into()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Assign a new image to a subregion of the whole texture.
|
||||||
|
pub fn set_partial(&mut self, pos: [usize; 2], image: impl Into<ImageData>) {
|
||||||
|
self.tex_mngr
|
||||||
|
.write()
|
||||||
|
.set(self.id, ImageDelta::partial(pos, image.into()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// width x height
|
/// width x height
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{image::ImageData, TextureId};
|
use crate::{ImageData, ImageDelta, TextureId};
|
||||||
use ahash::AHashMap;
|
use ahash::AHashMap;
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
@ -38,16 +38,19 @@ impl TextureManager {
|
||||||
retain_count: 1,
|
retain_count: 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
self.delta.set.insert(id, image);
|
self.delta.set.insert(id, ImageDelta::full(image));
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Assign a new image to an existing texture.
|
/// Assign a new image to an existing texture,
|
||||||
pub fn set(&mut self, id: TextureId, image: ImageData) {
|
/// or update a region of it.
|
||||||
|
pub fn set(&mut self, id: TextureId, delta: ImageDelta) {
|
||||||
if let Some(meta) = self.metas.get_mut(&id) {
|
if let Some(meta) = self.metas.get_mut(&id) {
|
||||||
meta.size = image.size();
|
if delta.is_whole() {
|
||||||
meta.bytes_per_pixel = image.bytes_per_pixel();
|
meta.size = delta.image.size();
|
||||||
self.delta.set.insert(id, image);
|
meta.bytes_per_pixel = delta.image.bytes_per_pixel();
|
||||||
|
}
|
||||||
|
self.delta.set.insert(id, delta);
|
||||||
} else {
|
} else {
|
||||||
crate::epaint_assert!(
|
crate::epaint_assert!(
|
||||||
false,
|
false,
|
||||||
|
@ -147,7 +150,7 @@ impl TextureMeta {
|
||||||
#[must_use = "The painter must take care of this"]
|
#[must_use = "The painter must take care of this"]
|
||||||
pub struct TexturesDelta {
|
pub struct TexturesDelta {
|
||||||
/// New or changed textures. Apply before painting.
|
/// New or changed textures. Apply before painting.
|
||||||
pub set: AHashMap<TextureId, ImageData>,
|
pub set: AHashMap<TextureId, ImageDelta>,
|
||||||
|
|
||||||
/// Texture to free after painting.
|
/// Texture to free after painting.
|
||||||
pub free: Vec<TextureId>,
|
pub free: Vec<TextureId>,
|
||||||
|
|
Loading…
Reference in a new issue