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)).
|
||||
* Renamed `Ui::visible` to `Ui::is_visible`.
|
||||
* 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 🐛
|
||||
* Context menu now respects the theme ([#1043](https://github.com/emilk/egui/pull/1043))
|
||||
|
|
|
@ -33,7 +33,6 @@ struct ContextImpl {
|
|||
fonts: Option<Fonts>,
|
||||
memory: Memory,
|
||||
animation_manager: AnimationManager,
|
||||
latest_font_image_version: Option<u64>,
|
||||
tex_manager: WrappedTextureManager,
|
||||
|
||||
input: InputState,
|
||||
|
@ -709,17 +708,15 @@ impl Context {
|
|||
.memory
|
||||
.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_version = font_image.version;
|
||||
|
||||
if Some(font_image_version) != ctx_impl.latest_font_image_version {
|
||||
let font_image_delta = ctx_impl.fonts.as_ref().unwrap().font_image_delta();
|
||||
if let Some(font_image_delta) = font_image_delta {
|
||||
ctx_impl
|
||||
.tex_manager
|
||||
.0
|
||||
.write()
|
||||
.set(TextureId::default(), font_image.image.clone().into());
|
||||
ctx_impl.latest_font_image_version = Some(font_image_version);
|
||||
.set(TextureId::default(), font_image_delta);
|
||||
}
|
||||
|
||||
ctx_impl
|
||||
.output
|
||||
.textures_delta
|
||||
|
@ -757,7 +754,7 @@ impl Context {
|
|||
let clipped_meshes = tessellator::tessellate_shapes(
|
||||
shapes,
|
||||
tessellation_options,
|
||||
self.fonts().font_image().size(),
|
||||
self.fonts().font_image_size(),
|
||||
);
|
||||
self.write().paint_stats = paint_stats.with_clipped_meshes(&clipped_meshes);
|
||||
clipped_meshes
|
||||
|
@ -961,8 +958,8 @@ impl Context {
|
|||
.show(ui, |ui| {
|
||||
let mut font_definitions = self.fonts().definitions().clone();
|
||||
font_definitions.ui(ui);
|
||||
let font_image = self.fonts().font_image();
|
||||
font_image.ui(ui);
|
||||
let font_image_size = self.fonts().font_image_size();
|
||||
crate::introspection::font_texture_ui(ui, font_image_size);
|
||||
self.set_fonts(font_definitions);
|
||||
});
|
||||
|
||||
|
|
|
@ -1,60 +1,58 @@
|
|||
//! uis for egui types.
|
||||
use crate::*;
|
||||
|
||||
impl Widget for &epaint::FontImage {
|
||||
fn ui(self, ui: &mut Ui) -> Response {
|
||||
use epaint::Mesh;
|
||||
// Show font texture in demo Ui
|
||||
pub(crate) fn font_texture_ui(ui: &mut Ui, [width, height]: [usize; 2]) -> Response {
|
||||
use epaint::Mesh;
|
||||
|
||||
ui.vertical(|ui| {
|
||||
// Show font texture in demo Ui
|
||||
let [width, height] = self.size();
|
||||
ui.vertical(|ui| {
|
||||
let color = if ui.visuals().dark_mode {
|
||||
Color32::WHITE
|
||||
} else {
|
||||
Color32::BLACK
|
||||
};
|
||||
|
||||
ui.label(format!(
|
||||
"Texture size: {} x {} (hover to zoom)",
|
||||
width, height
|
||||
));
|
||||
if width <= 1 || height <= 1 {
|
||||
return;
|
||||
}
|
||||
let mut size = vec2(width as f32, height as f32);
|
||||
if size.x > ui.available_width() {
|
||||
size *= ui.available_width() / size.x;
|
||||
}
|
||||
let (rect, response) = ui.allocate_at_least(size, Sense::hover());
|
||||
let mut mesh = Mesh::default();
|
||||
mesh.add_rect_with_uv(
|
||||
rect,
|
||||
[pos2(0.0, 0.0), pos2(1.0, 1.0)].into(),
|
||||
Color32::WHITE,
|
||||
);
|
||||
ui.painter().add(Shape::mesh(mesh));
|
||||
ui.label(format!(
|
||||
"Texture size: {} x {} (hover to zoom)",
|
||||
width, height
|
||||
));
|
||||
if width <= 1 || height <= 1 {
|
||||
return;
|
||||
}
|
||||
let mut size = vec2(width as f32, height as f32);
|
||||
if size.x > ui.available_width() {
|
||||
size *= ui.available_width() / size.x;
|
||||
}
|
||||
let (rect, response) = ui.allocate_at_least(size, Sense::hover());
|
||||
let mut mesh = Mesh::default();
|
||||
mesh.add_rect_with_uv(rect, [pos2(0.0, 0.0), pos2(1.0, 1.0)].into(), color);
|
||||
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
|
||||
.on_hover_cursor(CursorIcon::ZoomIn)
|
||||
.on_hover_ui_at_pointer(|ui| {
|
||||
if let Some(pos) = ui.ctx().latest_pointer_pos() {
|
||||
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 v = remap_clamp(pos.y, rect.y_range(), 0.0..=tex_h);
|
||||
response
|
||||
.on_hover_cursor(CursorIcon::ZoomIn)
|
||||
.on_hover_ui_at_pointer(|ui| {
|
||||
if let Some(pos) = ui.ctx().latest_pointer_pos() {
|
||||
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 v = remap_clamp(pos.y, rect.y_range(), 0.0..=tex_h);
|
||||
|
||||
let texel_radius = 32.0;
|
||||
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 texel_radius = 32.0;
|
||||
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 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),
|
||||
);
|
||||
let mut mesh = Mesh::default();
|
||||
mesh.add_rect_with_uv(zoom_rect, uv_rect, Color32::WHITE);
|
||||
ui.painter().add(Shape::mesh(mesh));
|
||||
}
|
||||
});
|
||||
})
|
||||
.response
|
||||
}
|
||||
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),
|
||||
);
|
||||
let mut mesh = Mesh::default();
|
||||
mesh.add_rect_with_uv(zoom_rect, uv_rect, color);
|
||||
ui.painter().add(Shape::mesh(mesh));
|
||||
}
|
||||
});
|
||||
})
|
||||
.response
|
||||
}
|
||||
|
||||
impl Widget for &mut epaint::text::FontDefinitions {
|
||||
|
|
|
@ -1087,9 +1087,13 @@ pub struct PlotImage {
|
|||
|
||||
impl PlotImage {
|
||||
/// 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 {
|
||||
position,
|
||||
position: center_position,
|
||||
name: Default::default(),
|
||||
highlight: false,
|
||||
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);
|
||||
c.bench_function("tessellate_text", |b| {
|
||||
b.iter(|| {
|
||||
tessellator.tessellate_text(
|
||||
fonts.font_image().size(),
|
||||
text_shape.clone(),
|
||||
&mut mesh,
|
||||
);
|
||||
tessellator.tessellate_text(fonts.font_image_size(), text_shape.clone(), &mut mesh);
|
||||
mesh.clear();
|
||||
})
|
||||
});
|
||||
|
|
|
@ -343,10 +343,7 @@ impl Widget for &mut ItemsDemo {
|
|||
let image = PlotImage::new(
|
||||
texture,
|
||||
Value::new(0.0, 10.0),
|
||||
[
|
||||
ui.fonts().font_image().width() as f32 / 100.0,
|
||||
ui.fonts().font_image().height() as f32 / 100.0,
|
||||
],
|
||||
5.0 * vec2(texture.aspect_ratio(), 1.0),
|
||||
);
|
||||
|
||||
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());
|
||||
let clipped_meshes = integration.egui_ctx.tessellate(shapes);
|
||||
|
||||
for (id, image) in textures_delta.set {
|
||||
painter.set_texture(&display, id, &image);
|
||||
for (id, image_delta) in textures_delta.set {
|
||||
painter.set_texture(&display, id, &image_delta);
|
||||
}
|
||||
|
||||
// paint:
|
||||
|
|
|
@ -155,8 +155,8 @@ impl EguiGlium {
|
|||
let shapes = std::mem::take(&mut self.shapes);
|
||||
let mut textures_delta = std::mem::take(&mut self.textures_delta);
|
||||
|
||||
for (id, image) in textures_delta.set {
|
||||
self.painter.set_texture(display, id, &image);
|
||||
for (id, image_delta) in textures_delta.set {
|
||||
self.painter.set_texture(display, id, &image_delta);
|
||||
}
|
||||
|
||||
let clipped_meshes = self.egui_ctx.tessellate(shapes);
|
||||
|
|
|
@ -185,9 +185,9 @@ impl Painter {
|
|||
&mut self,
|
||||
facade: &dyn glium::backend::Facade,
|
||||
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) => {
|
||||
assert_eq!(
|
||||
image.width() * image.height(),
|
||||
|
@ -206,15 +206,29 @@ impl Painter {
|
|||
};
|
||||
let glium_image = glium::texture::RawImage2d {
|
||||
data: std::borrow::Cow::Owned(pixels),
|
||||
width: image.width() as _,
|
||||
height: image.height() as _,
|
||||
width: delta.image.width() as _,
|
||||
height: delta.image.height() as _,
|
||||
format: glium::texture::ClientFormat::U8U8U8U8,
|
||||
};
|
||||
let format = texture::SrgbFormat::U8U8U8U8;
|
||||
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) {
|
||||
|
|
|
@ -86,8 +86,8 @@ pub fn run(app: Box<dyn epi::App>, native_options: &epi::NativeOptions) -> ! {
|
|||
integration.update(gl_window.window());
|
||||
let clipped_meshes = integration.egui_ctx.tessellate(shapes);
|
||||
|
||||
for (id, image) in textures_delta.set {
|
||||
painter.set_texture(&gl, id, &image);
|
||||
for (id, image_delta) in textures_delta.set {
|
||||
painter.set_texture(&gl, id, &image_delta);
|
||||
}
|
||||
|
||||
// paint:
|
||||
|
|
|
@ -175,8 +175,8 @@ impl EguiGlow {
|
|||
let shapes = std::mem::take(&mut self.shapes);
|
||||
let mut textures_delta = std::mem::take(&mut self.textures_delta);
|
||||
|
||||
for (id, image) in textures_delta.set {
|
||||
self.painter.set_texture(gl, id, &image);
|
||||
for (id, image_delta) in textures_delta.set {
|
||||
self.painter.set_texture(gl, id, &image_delta);
|
||||
}
|
||||
|
||||
let clipped_meshes = self.egui_ctx.tessellate(shapes);
|
||||
|
|
|
@ -340,6 +340,7 @@ impl Painter {
|
|||
|
||||
gl.bind_texture(glow::TEXTURE_2D, Some(texture));
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
@ -386,7 +387,7 @@ impl Painter {
|
|||
&mut self,
|
||||
gl: &glow::Context,
|
||||
tex_id: egui::TextureId,
|
||||
image: &egui::ImageData,
|
||||
delta: &egui::epaint::ImageDelta,
|
||||
) {
|
||||
self.assert_not_destroyed();
|
||||
|
||||
|
@ -398,7 +399,7 @@ impl Painter {
|
|||
gl.bind_texture(glow::TEXTURE_2D, Some(glow_texture));
|
||||
}
|
||||
|
||||
match image {
|
||||
match &delta.image {
|
||||
egui::ImageData::Color(image) => {
|
||||
assert_eq!(
|
||||
image.width() * image.height(),
|
||||
|
@ -408,7 +409,7 @@ impl Painter {
|
|||
|
||||
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) => {
|
||||
assert_eq!(
|
||||
|
@ -427,12 +428,18 @@ impl Painter {
|
|||
.flat_map(|a| a.to_array())
|
||||
.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!(w >= 1 && h >= 1);
|
||||
unsafe {
|
||||
|
@ -457,38 +464,50 @@ impl Painter {
|
|||
glow::TEXTURE_WRAP_T,
|
||||
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 {
|
||||
glow::SRGB_ALPHA
|
||||
} else {
|
||||
glow::RGBA
|
||||
};
|
||||
gl.tex_image_2d(
|
||||
glow::TEXTURE_2D,
|
||||
0,
|
||||
format as i32,
|
||||
w as i32,
|
||||
h as i32,
|
||||
0,
|
||||
format,
|
||||
glow::UNSIGNED_BYTE,
|
||||
Some(data),
|
||||
);
|
||||
(format, format)
|
||||
} 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(
|
||||
glow::TEXTURE_2D,
|
||||
0,
|
||||
glow::SRGB8_ALPHA8 as i32,
|
||||
w as i32,
|
||||
h as i32,
|
||||
0,
|
||||
glow::RGBA,
|
||||
level,
|
||||
internal_format as _,
|
||||
w as _,
|
||||
h as _,
|
||||
border,
|
||||
src_format,
|
||||
glow::UNSIGNED_BYTE,
|
||||
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`].
|
||||
pub fn paint(&mut self, clipped_meshes: Vec<egui::ClippedMesh>) -> Result<(), JsValue> {
|
||||
let textures_delta = std::mem::take(&mut self.textures_delta);
|
||||
for (id, image) in textures_delta.set {
|
||||
self.painter.set_texture(id, image);
|
||||
for (id, image_delta) in textures_delta.set {
|
||||
self.painter.set_texture(id, &image_delta);
|
||||
}
|
||||
|
||||
self.painter.clear(self.app.clear_color());
|
||||
|
|
|
@ -40,8 +40,8 @@ impl WrappedGlowPainter {
|
|||
}
|
||||
|
||||
impl crate::Painter for WrappedGlowPainter {
|
||||
fn set_texture(&mut self, tex_id: egui::TextureId, image: egui::ImageData) {
|
||||
self.painter.set_texture(&self.glow_ctx, tex_id, &image);
|
||||
fn set_texture(&mut self, tex_id: egui::TextureId, delta: &egui::epaint::ImageDelta) {
|
||||
self.painter.set_texture(&self.glow_ctx, tex_id, delta);
|
||||
}
|
||||
|
||||
fn free_texture(&mut self, tex_id: egui::TextureId) {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use wasm_bindgen::prelude::JsValue;
|
||||
|
||||
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);
|
||||
|
||||
|
|
|
@ -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 vert_shader = compile_shader(
|
||||
|
@ -222,36 +215,61 @@ impl WebGlPainter {
|
|||
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_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_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_MAG_FILTER, Gl::LINEAR as _);
|
||||
|
||||
gl.bind_texture(Gl::TEXTURE_2D, Some(&gl_texture));
|
||||
|
||||
let level = 0;
|
||||
let internal_format = self.texture_format;
|
||||
let border = 0;
|
||||
let src_format = self.texture_format;
|
||||
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 {
|
||||
fn set_texture(&mut self, tex_id: egui::TextureId, image: egui::ImageData) {
|
||||
match image {
|
||||
fn set_texture(&mut self, tex_id: egui::TextureId, delta: &egui::epaint::ImageDelta) {
|
||||
match &delta.image {
|
||||
egui::ImageData::Color(image) => {
|
||||
assert_eq!(
|
||||
image.width() * image.height(),
|
||||
|
@ -281,7 +299,7 @@ impl crate::Painter for WebGlPainter {
|
|||
);
|
||||
|
||||
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) => {
|
||||
let gamma = if self.post_process.is_none() {
|
||||
|
@ -293,7 +311,7 @@ impl crate::Painter for WebGlPainter {
|
|||
.srgba_pixels(gamma)
|
||||
.flat_map(|a| a.to_array())
|
||||
.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(
|
||||
&gl,
|
||||
Gl::VERTEX_SHADER,
|
||||
|
@ -206,37 +199,61 @@ impl WebGl2Painter {
|
|||
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_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_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_MAG_FILTER, Gl::LINEAR as _);
|
||||
|
||||
gl.bind_texture(Gl::TEXTURE_2D, Some(&gl_texture));
|
||||
|
||||
let level = 0;
|
||||
let internal_format = Gl::SRGB8_ALPHA8;
|
||||
let border = 0;
|
||||
let src_format = Gl::RGBA;
|
||||
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 {
|
||||
fn set_texture(&mut self, tex_id: egui::TextureId, image: egui::ImageData) {
|
||||
match image {
|
||||
fn set_texture(&mut self, tex_id: egui::TextureId, delta: &egui::epaint::ImageDelta) {
|
||||
match &delta.image {
|
||||
egui::ImageData::Color(image) => {
|
||||
assert_eq!(
|
||||
image.width() * image.height(),
|
||||
|
@ -266,7 +283,7 @@ impl crate::Painter for WebGl2Painter {
|
|||
);
|
||||
|
||||
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) => {
|
||||
let gamma = 1.0;
|
||||
|
@ -274,7 +291,7 @@ impl crate::Painter for WebGl2Painter {
|
|||
.srgba_pixels(gamma)
|
||||
.flat_map(|a| a.to_array())
|
||||
.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()
|
||||
.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 {
|
||||
|
@ -239,3 +256,45 @@ impl From<AlphaImage> for ImageData {
|
|||
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 {
|
||||
color::{Color32, Rgba},
|
||||
image::{AlphaImage, ColorImage, ImageData},
|
||||
image::{AlphaImage, ColorImage, ImageData, ImageDelta},
|
||||
mesh::{Mesh, Mesh16, Vertex},
|
||||
shadow::Shadow,
|
||||
shape::{CircleShape, PathShape, RectShape, Shape, TextShape},
|
||||
|
@ -113,7 +113,7 @@ pub use {
|
|||
stroke::Stroke,
|
||||
tessellator::{tessellate_shapes, TessellationOptions, Tessellator},
|
||||
text::{Fonts, Galley, TextStyle},
|
||||
texture_atlas::{FontImage, TextureAtlas},
|
||||
texture_atlas::TextureAtlas,
|
||||
texture_handle::TextureHandle,
|
||||
textures::TextureManager,
|
||||
};
|
||||
|
|
|
@ -374,14 +374,12 @@ fn allocate_glyph(
|
|||
if glyph_width == 0 || glyph_height == 0 {
|
||||
UvRect::default()
|
||||
} else {
|
||||
let glyph_pos = atlas.allocate((glyph_width, glyph_height));
|
||||
|
||||
let image = atlas.image_mut();
|
||||
let (glyph_pos, image) = atlas.allocate((glyph_width, glyph_height));
|
||||
glyph.draw(|x, y, v| {
|
||||
if v > 0.0 {
|
||||
let px = glyph_pos.0 + x 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},
|
||||
Galley, LayoutJob,
|
||||
},
|
||||
FontImage, TextureAtlas,
|
||||
TextureAtlas,
|
||||
};
|
||||
|
||||
// TODO: rename
|
||||
|
@ -233,11 +233,6 @@ pub struct Fonts {
|
|||
definitions: FontDefinitions,
|
||||
fonts: BTreeMap<TextStyle, Font>,
|
||||
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>,
|
||||
}
|
||||
|
||||
|
@ -257,9 +252,9 @@ impl Fonts {
|
|||
|
||||
{
|
||||
// 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));
|
||||
atlas.image_mut().image[pos] = 255;
|
||||
image[pos] = 255;
|
||||
}
|
||||
|
||||
let atlas = Arc::new(Mutex::new(atlas));
|
||||
|
@ -283,19 +278,11 @@ impl Fonts {
|
|||
})
|
||||
.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 {
|
||||
pixels_per_point,
|
||||
definitions,
|
||||
fonts,
|
||||
atlas,
|
||||
buffered_font_image: Default::default(),
|
||||
galley_cache: Default::default(),
|
||||
}
|
||||
}
|
||||
|
@ -319,15 +306,14 @@ impl Fonts {
|
|||
(point * self.pixels_per_point).floor() / self.pixels_per_point
|
||||
}
|
||||
|
||||
/// Call each frame to get the latest available font texture data.
|
||||
pub fn font_image(&self) -> Arc<FontImage> {
|
||||
let atlas = self.atlas.lock();
|
||||
let mut buffered_texture = self.buffered_font_image.lock();
|
||||
if buffered_texture.version != atlas.image().version {
|
||||
*buffered_texture = Arc::new(atlas.image().clone());
|
||||
}
|
||||
/// Call each frame to get the change to the font texture since last call.
|
||||
pub fn font_image_delta(&self) -> Option<crate::ImageDelta> {
|
||||
self.atlas.lock().take_delta()
|
||||
}
|
||||
|
||||
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.
|
||||
|
|
|
@ -1,39 +1,40 @@
|
|||
use crate::image::AlphaImage;
|
||||
use crate::{AlphaImage, ImageDelta};
|
||||
|
||||
/// An 8-bit texture containing font data.
|
||||
#[derive(Clone, Default)]
|
||||
pub struct FontImage {
|
||||
/// e.g. a hash of the data. Use this to detect changes!
|
||||
/// If the texture changes, this too will change.
|
||||
pub version: u64,
|
||||
|
||||
/// The actual image data.
|
||||
pub image: AlphaImage,
|
||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||
struct Rectu {
|
||||
/// inclusive
|
||||
min_x: usize,
|
||||
/// inclusive
|
||||
min_y: usize,
|
||||
/// exclusive
|
||||
max_x: usize,
|
||||
/// exclusive
|
||||
max_y: usize,
|
||||
}
|
||||
|
||||
impl FontImage {
|
||||
#[inline]
|
||||
pub fn size(&self) -> [usize; 2] {
|
||||
self.image.size
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn width(&self) -> usize {
|
||||
self.image.size[0]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn height(&self) -> usize {
|
||||
self.image.size[1]
|
||||
}
|
||||
impl Rectu {
|
||||
const NOTHING: Self = Self {
|
||||
min_x: usize::MAX,
|
||||
min_y: usize::MAX,
|
||||
max_x: 0,
|
||||
max_y: 0,
|
||||
};
|
||||
const EVERYTHING: Self = Self {
|
||||
min_x: 0,
|
||||
min_y: 0,
|
||||
max_x: usize::MAX,
|
||||
max_y: usize::MAX,
|
||||
};
|
||||
}
|
||||
|
||||
/// Contains font data in an atlas, where each character occupied a small rectangle.
|
||||
///
|
||||
/// More characters can be added, possibly expanding the texture.
|
||||
#[derive(Clone, Default)]
|
||||
#[derive(Clone)]
|
||||
pub struct TextureAtlas {
|
||||
image: FontImage,
|
||||
image: AlphaImage,
|
||||
/// What part of the image that is dirty
|
||||
dirty: Rectu,
|
||||
|
||||
/// Used for when allocating new rectangles.
|
||||
cursor: (usize, usize),
|
||||
|
@ -43,25 +44,35 @@ pub struct TextureAtlas {
|
|||
impl TextureAtlas {
|
||||
pub fn new(size: [usize; 2]) -> Self {
|
||||
Self {
|
||||
image: FontImage {
|
||||
version: 0,
|
||||
image: AlphaImage::new(size),
|
||||
},
|
||||
..Default::default()
|
||||
image: AlphaImage::new(size),
|
||||
dirty: Rectu::EVERYTHING,
|
||||
cursor: (0, 0),
|
||||
row_height: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn image(&self) -> &FontImage {
|
||||
&self.image
|
||||
pub fn size(&self) -> [usize; 2] {
|
||||
self.image.size
|
||||
}
|
||||
|
||||
pub fn image_mut(&mut self) -> &mut FontImage {
|
||||
self.image.version += 1;
|
||||
&mut self.image
|
||||
/// Call to get the change to the image since last call.
|
||||
pub fn take_delta(&mut self) -> Option<ImageDelta> {
|
||||
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.
|
||||
pub fn allocate(&mut self, (w, h): (usize, usize)) -> (usize, usize) {
|
||||
/// Returns the coordinates of where the rect ended up,
|
||||
/// 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
|
||||
/// if we don't add some empty pixels between the characters.
|
||||
/// On modern high-precision GPUs this is not needed.
|
||||
|
@ -81,21 +92,31 @@ impl TextureAtlas {
|
|||
}
|
||||
|
||||
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;
|
||||
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() {
|
||||
image.size[1] *= 2; // double the height
|
||||
}
|
||||
|
||||
if image.width() * image.height() > image.pixels.len() {
|
||||
image.pixels.resize(image.width() * image.height(), 0);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::{
|
||||
emath::NumExt,
|
||||
mutex::{Arc, RwLock},
|
||||
ImageData, TextureId, TextureManager,
|
||||
ImageData, ImageDelta, TextureId, TextureManager,
|
||||
};
|
||||
|
||||
/// Used to paint images.
|
||||
|
@ -66,7 +66,16 @@ impl TextureHandle {
|
|||
|
||||
/// Assign a new image to an existing texture.
|
||||
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
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::{image::ImageData, TextureId};
|
||||
use crate::{ImageData, ImageDelta, TextureId};
|
||||
use ahash::AHashMap;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
@ -38,16 +38,19 @@ impl TextureManager {
|
|||
retain_count: 1,
|
||||
});
|
||||
|
||||
self.delta.set.insert(id, image);
|
||||
self.delta.set.insert(id, ImageDelta::full(image));
|
||||
id
|
||||
}
|
||||
|
||||
/// Assign a new image to an existing texture.
|
||||
pub fn set(&mut self, id: TextureId, image: ImageData) {
|
||||
/// Assign a new image to an existing texture,
|
||||
/// or update a region of it.
|
||||
pub fn set(&mut self, id: TextureId, delta: ImageDelta) {
|
||||
if let Some(meta) = self.metas.get_mut(&id) {
|
||||
meta.size = image.size();
|
||||
meta.bytes_per_pixel = image.bytes_per_pixel();
|
||||
self.delta.set.insert(id, image);
|
||||
if delta.is_whole() {
|
||||
meta.size = delta.image.size();
|
||||
meta.bytes_per_pixel = delta.image.bytes_per_pixel();
|
||||
}
|
||||
self.delta.set.insert(id, delta);
|
||||
} else {
|
||||
crate::epaint_assert!(
|
||||
false,
|
||||
|
@ -147,7 +150,7 @@ impl TextureMeta {
|
|||
#[must_use = "The painter must take care of this"]
|
||||
pub struct TexturesDelta {
|
||||
/// New or changed textures. Apply before painting.
|
||||
pub set: AHashMap<TextureId, ImageData>,
|
||||
pub set: AHashMap<TextureId, ImageDelta>,
|
||||
|
||||
/// Texture to free after painting.
|
||||
pub free: Vec<TextureId>,
|
||||
|
|
Loading…
Reference in a new issue