From e8851ee3569d170a86e9478f3f6482136eb6ac6a Mon Sep 17 00:00:00 2001
From: griffi-gh <prasol258@gmail.com>
Date: Tue, 11 Mar 2025 10:16:46 +0100
Subject: [PATCH] mostly fix stuff

---
 hui-examples/Cargo.toml                   |  2 +-
 hui-examples/boilerplate.rs               |  4 +-
 hui-examples/examples/align_test.rs       |  2 +-
 hui-examples/examples/text_weird.rs       |  2 +-
 hui-examples/examples/ui_test_6_slider.rs | 10 +--
 hui-glium/Cargo.toml                      |  3 +-
 hui-glium/src/lib.rs                      | 46 ++++++-----
 hui-painter/src/backend.rs                | 94 +----------------------
 hui-painter/src/lib.rs                    |  1 +
 hui-painter/src/paint/buffer.rs           |  2 +
 hui-painter/src/presentation.rs           | 83 ++++++++++++++++++++
 hui-wgpu/Cargo.toml                       |  2 +-
 hui-wgpu/src/lib.rs                       | 65 +++++++++-------
 hui/Cargo.toml                            |  2 +-
 hui/src/instance.rs                       | 28 +++++--
 15 files changed, 185 insertions(+), 161 deletions(-)
 create mode 100644 hui-painter/src/presentation.rs

diff --git a/hui-examples/Cargo.toml b/hui-examples/Cargo.toml
index 6e2f202..2fdead1 100644
--- a/hui-examples/Cargo.toml
+++ b/hui-examples/Cargo.toml
@@ -7,7 +7,7 @@ publish = false
 
 [dev-dependencies]
 hui = { path = "../hui" }
-# hui-painter = { path = "../hui-painter" }
+hui-painter = { path = "../hui-painter" }
 hui-glium = { path = "../hui-glium" }
 hui-winit = { path = "../hui-winit" }
 kubi-logging = { git = "https://github.com/griffi-gh/kubi", rev = "1e051c47b64c967305e4bbbd464ef5da2cc56bbb" }
diff --git a/hui-examples/boilerplate.rs b/hui-examples/boilerplate.rs
index 2c23df9..aa2df24 100644
--- a/hui-examples/boilerplate.rs
+++ b/hui-examples/boilerplate.rs
@@ -65,9 +65,9 @@ pub fn ui<T>(
           let size = UVec2::from(display.get_framebuffer_dimensions()).as_vec2();
           draw(&mut hui, size, &mut result);
 
-          hui.end();
+          hui.end_frame();
 
-          backend.update(&hui);
+          backend.update(&hui.backend_data());
           backend.draw(&mut frame, size);
 
           frame.finish().unwrap();
diff --git a/hui-examples/examples/align_test.rs b/hui-examples/examples/align_test.rs
index ee7d4d7..9552b56 100644
--- a/hui-examples/examples/align_test.rs
+++ b/hui-examples/examples/align_test.rs
@@ -135,7 +135,7 @@ fn main() {
           ..Default::default()
         }, resolution);
 
-        hui.end();
+        hui.end_frame();
 
         backend.update(&hui);
         backend.draw(&mut frame, resolution);
diff --git a/hui-examples/examples/text_weird.rs b/hui-examples/examples/text_weird.rs
index 66e850a..a784bb1 100644
--- a/hui-examples/examples/text_weird.rs
+++ b/hui-examples/examples/text_weird.rs
@@ -106,7 +106,7 @@ fn main() {
           ..Default::default()
         }, resolution);
 
-        hui.end();
+        hui.end_frame();
 
         backend.update(&hui);
         backend.draw(&mut frame, resolution);
diff --git a/hui-examples/examples/ui_test_6_slider.rs b/hui-examples/examples/ui_test_6_slider.rs
index a353a02..283cb33 100644
--- a/hui-examples/examples/ui_test_6_slider.rs
+++ b/hui-examples/examples/ui_test_6_slider.rs
@@ -1,5 +1,4 @@
 use hui::{
-  draw::TextureFormat,
   element::{
     br::Break,
     container::Container,
@@ -12,6 +11,7 @@ use hui::{
   layout::{Alignment, Direction},
   size,
 };
+use hui_painter::texture::SourceTextureFormat;
 
 #[derive(Signal)]
 enum CounterSignal {
@@ -27,7 +27,7 @@ const IMAGE_DATA: &[u8] = include_bytes!("../assets/icons/visual-studio-code-ico
 ui_main!(
   "hUI: Internal input test",
   init: |ui| {
-    let image = ui.add_image(TextureFormat::Rgba, IMAGE_DATA, 32);
+    let image = ui.add_image(SourceTextureFormat::RGBA8, IMAGE_DATA, 32);
     (0, image)
   },
   run: |ui, size, &mut (ref mut counter, image)| {
@@ -41,11 +41,11 @@ ui_main!(
       .with_wrap(true)
       .with_children(|ui| {
         Text::new(format!("Number of images: {counter}"))
-          .with_text_size(32)
+          .with_text_size(32.)
           .add_child(ui);
         Break.add_child(ui);
         Text::new("Absolute tracking slider:")
-          .with_text_size(16)
+          .with_text_size(16.)
           .add_child(ui);
         Break.add_child(ui);
         Slider::new(*counter as f32 / 100.)
@@ -56,7 +56,7 @@ ui_main!(
           .add_child(ui);
         Break.add_child(ui);
         Text::new("Relative tracking slider (Experimental):")
-          .with_text_size(16)
+          .with_text_size(16.)
           .add_child(ui);
         Break.add_child(ui);
         Slider::new(*counter as f32 / 100.)
diff --git a/hui-glium/Cargo.toml b/hui-glium/Cargo.toml
index 29ed901..b007e47 100644
--- a/hui-glium/Cargo.toml
+++ b/hui-glium/Cargo.toml
@@ -16,7 +16,8 @@ include = [
 ]
 
 [dependencies]
-hui = { version = "=0.1.0-alpha.6", path = "../hui", default-features = false }
+# hui = { version = "=0.1.0-alpha.6", path = "../hui", default-features = false }
+hui-painter = { version = "=0.1.0-alpha.6", path = "../hui-painter", default-features = false }
 glium = { version = "0.36", default-features = false }
 glam = "0.30"
 log = "0.4"
diff --git a/hui-glium/src/lib.rs b/hui-glium/src/lib.rs
index b9fef49..323889d 100644
--- a/hui-glium/src/lib.rs
+++ b/hui-glium/src/lib.rs
@@ -21,7 +21,7 @@ use glium::{
   implement_vertex,
   uniform,
 };
-use hui::UiInstance;
+use hui_painter::{backend::BackendData, paint::buffer::Vertex, presentation::PresentatationBackendData, texture::TextureAtlasBackendData};
 
 const VERTEX_SHADER_GLES3: &str = include_str!("../shaders/vertex.es.vert");
 const FRAGMENT_SHADER_GLES3: &str = include_str!("../shaders/fragment.es.frag");
@@ -31,14 +31,14 @@ const FRAGMENT_SHADER_150: &str = include_str!("../shaders/fragment.150.frag");
 
 #[derive(Clone, Copy)]
 #[repr(C)]
-struct Vertex {
+struct GlVertex {
   position: [f32; 2],
   color: [f32; 4],
   uv: [f32; 2],
 }
 
-impl From<UiVertex> for Vertex {
-  fn from(v: UiVertex) -> Self {
+impl From<Vertex> for GlVertex {
+  fn from(v: Vertex) -> Self {
     Self {
       position: v.position.to_array(),
       color: v.color.to_array(),
@@ -47,10 +47,10 @@ impl From<UiVertex> for Vertex {
   }
 }
 
-implement_vertex!(Vertex, position, color, uv);
+implement_vertex!(GlVertex, position, color, uv);
 
 struct BufferPair {
-  pub vertex_buffer: glium::VertexBuffer<Vertex>,
+  pub vertex_buffer: glium::VertexBuffer<GlVertex>,
   pub index_buffer: glium::IndexBuffer<u32>,
   pub vertex_count: usize,
   pub index_count: usize,
@@ -67,7 +67,7 @@ impl BufferPair {
     }
   }
 
-  pub fn new_with_data<F: Facade>(facade: &F, vtx: &[Vertex], idx: &[u32]) -> Self {
+  pub fn new_with_data<F: Facade>(facade: &F, vtx: &[GlVertex], idx: &[u32]) -> Self {
     log::debug!("init ui buffers (data)...");
     Self {
       vertex_buffer: VertexBuffer::dynamic(facade, vtx).unwrap(),
@@ -78,7 +78,7 @@ impl BufferPair {
   }
 
   pub fn ensure_buffer_size(&mut self, need_vtx: usize, need_idx: usize) {
-    let current_vtx_size = self.vertex_buffer.get_size() / std::mem::size_of::<Vertex>();
+    let current_vtx_size = self.vertex_buffer.get_size() / std::mem::size_of::<GlVertex>();
     let current_idx_size = self.index_buffer.get_size() / std::mem::size_of::<u32>();
     //log::debug!("current vtx size: {}, current idx size: {}", current_vtx_size, current_idx_size);
     if current_vtx_size >= need_vtx && current_idx_size >= need_idx {
@@ -102,7 +102,7 @@ impl BufferPair {
     }
   }
 
-  pub fn write_data(&mut self, vtx: &[Vertex], idx: &[u32]) {
+  pub fn write_data(&mut self, vtx: &[GlVertex], idx: &[u32]) {
     //log::trace!("uploading {} vertices and {} indices", vtx.len(), idx.len());
 
     self.vertex_count = vtx.len();
@@ -129,7 +129,9 @@ pub struct GliumUiRenderer {
   context: Rc<Context>,
   program: glium::Program,
   ui_texture: Option<Texture2d>,
+  ui_texture_version: u64,
   buffer_pair: Option<BufferPair>,
+  buffer_hash: u64,
 }
 
 impl GliumUiRenderer {
@@ -142,22 +144,25 @@ impl GliumUiRenderer {
       },
       context: Rc::clone(facade.get_context()),
       ui_texture: None,
+      ui_texture_version: 0,
       buffer_pair: None,
+      buffer_hash: 0,
     }
   }
 
-  fn update_buffers(&mut self, call: &UiDrawCall) {
-    log::trace!("updating ui buffers (tris: {})", call.indices.len() / 3);
-    let data_vtx = &call.vertices.iter().copied().map(Vertex::from).collect::<Vec<_>>()[..];
-    let data_idx = &call.indices[..];
+  fn update_buffers(&mut self, data: &PresentatationBackendData) {
+    log::trace!("updating ui buffers (tris: {})", data.buffer.indices.len() / 3);
+    let data_vtx = &data.buffer.vertices.iter().copied().map(GlVertex::from).collect::<Vec<_>>()[..];
+    let data_idx = &data.buffer.indices[..];
     if let Some(buffer) = &mut self.buffer_pair {
       buffer.write_data(data_vtx, data_idx);
-    } else if !call.indices.is_empty() {
+    } else if !data.buffer.indices.is_empty() {
       self.buffer_pair = Some(BufferPair::new_with_data(&self.context, data_vtx, data_idx));
     }
+    self.buffer_hash = data.hash;
   }
 
-  fn update_texture_atlas(&mut self, atlas: &TextureAtlasMeta) {
+  fn update_texture_atlas(&mut self, atlas: &TextureAtlasBackendData) {
     log::trace!("updating ui atlas texture");
     self.ui_texture = Some(Texture2d::new(
       &self.context,
@@ -166,14 +171,15 @@ impl GliumUiRenderer {
         (atlas.size.x, atlas.size.y)
       )
     ).unwrap());
+    self.ui_texture_version = atlas.version;
   }
 
-  pub fn update(&mut self, instance: &UiInstance) {
-    if self.ui_texture.is_none() || instance.backend_atlas().modified {
-      self.update_texture_atlas(&instance.backend_atlas());
+  pub fn update(&mut self, data: &BackendData) {
+    if self.ui_texture_version != data.atlas.version {
+      self.update_texture_atlas(&data.atlas);
     }
-    if self.buffer_pair.is_none() || instance.backend_paint_buffer().0 {
-      self.update_buffers(instance.backend_paint_buffer().1);
+    if self.buffer_hash != data.presentation.hash {
+      self.update_buffers(&data.presentation);
     }
   }
 
diff --git a/hui-painter/src/backend.rs b/hui-painter/src/backend.rs
index 643fdf9..9cd2084 100644
--- a/hui-painter/src/backend.rs
+++ b/hui-painter/src/backend.rs
@@ -1,83 +1,9 @@
 use crate::{
-  paint::{buffer::PaintBuffer, command::{PaintCommand, PaintRoot}},
+  presentation::{Presentatation, PresentatationBackendData},
   texture::TextureAtlasBackendData,
   PainterInstance,
 };
 
-pub struct Presentatation {
-  current_buffer: PaintBuffer,
-  cur_hash: Option<u64>,
-  prev_hash: Option<u64>,
-  version_counter: u64,
-}
-
-impl Presentatation {
-  pub fn new() -> Self {
-    Self {
-      current_buffer: PaintBuffer::new(),
-      cur_hash: None,
-      prev_hash: None,
-      version_counter: 0,
-    }
-  }
-
-  /// If the paint command has changed since the last draw call, draw it and return true.\
-  /// Otherwise, returns false.
-  pub fn draw(&mut self, painter: &mut PainterInstance, cmd: &impl PaintRoot) -> bool {
-    self.prev_hash = self.cur_hash;
-    self.cur_hash = Some(cmd.cache_hash());
-
-    if self.prev_hash == self.cur_hash {
-      return false;
-    }
-
-    self.current_buffer.clear();
-    cmd.paint_root(painter, &mut self.current_buffer);
-
-    self.version_counter = self.version_counter.wrapping_add(1);
-
-    true
-  }
-
-  /// Get the current paint buffer
-  pub fn buffer(&self) -> &PaintBuffer {
-    &self.current_buffer
-  }
-
-  /// Get the complete backend data for the current presentation
-  ///
-  /// It contains the current paint buffer and the hash of the presentation\
-  /// Unlike the `TextureAtlasBackendData`, the version is non-incremental
-  pub fn backend_data(&self) -> PresentatationBackendData {
-    PresentatationBackendData {
-      buffer: &self.current_buffer,
-      version: self.version_counter,
-      hash: self.cur_hash.unwrap_or(0),
-    }
-  }
-}
-
-impl Default for Presentatation {
-  fn default() -> Self {
-    Self::new()
-  }
-}
-
-/// Backend data for the Presentation
-#[derive(Clone, Copy)]
-pub struct PresentatationBackendData<'a> {
-  /// The current paint buffer
-  pub buffer: &'a PaintBuffer,
-
-  /// The version of the presentation
-  ///
-  /// This is incremented every time the buffer hash changes
-  pub version: u64,
-
-  /// Unique hash of current paint buffer commands
-  pub hash: u64,
-}
-
 #[derive(Clone, Copy)]
 pub struct BackendData<'a> {
   pub presentation: PresentatationBackendData<'a>,
@@ -93,22 +19,4 @@ impl PainterInstance {
   }
 }
 
-// pub trait HasPainter {
-//   fn painter(&self) -> &PainterInstance;
-//   fn painter_mut(&self) -> &mut PainterInstance;
-// }
 
-// pub trait PresentFrontend: HasPainter {
-//   fn commands(&self) -> &dyn PaintCommand;
-
-//   fn present(&self, backend: &mut dyn PresentBackend) {
-//     backend.presentation().draw(
-//       self.painter_mut(),
-//       self.commands(),
-//     );
-//   }
-// }
-
-pub trait RenderBackend {
-  fn presentation(&self) -> &mut Presentatation;
-}
diff --git a/hui-painter/src/lib.rs b/hui-painter/src/lib.rs
index 758884d..9cd62dc 100644
--- a/hui-painter/src/lib.rs
+++ b/hui-painter/src/lib.rs
@@ -3,6 +3,7 @@ pub mod texture;
 pub mod text;
 pub mod util;
 pub mod backend;
+pub mod presentation;
 
 use text::FontManager;
 use texture::TextureAtlas;
diff --git a/hui-painter/src/paint/buffer.rs b/hui-painter/src/paint/buffer.rs
index 84f1a34..d30a3a1 100644
--- a/hui-painter/src/paint/buffer.rs
+++ b/hui-painter/src/paint/buffer.rs
@@ -1,5 +1,7 @@
 use glam::{Vec2, Vec4};
 
+#[derive(Clone, Copy)]
+#[repr(C)]
 pub struct Vertex {
   pub position: Vec2, //Vec3,
   pub uv: Vec2,
diff --git a/hui-painter/src/presentation.rs b/hui-painter/src/presentation.rs
new file mode 100644
index 0000000..fd3f362
--- /dev/null
+++ b/hui-painter/src/presentation.rs
@@ -0,0 +1,83 @@
+use crate::{
+  PainterInstance,
+  paint::{
+    buffer::PaintBuffer,
+    command::PaintRoot,
+  },
+};
+
+pub struct Presentatation {
+  current_buffer: PaintBuffer,
+  cur_hash: Option<u64>,
+  prev_hash: Option<u64>,
+  version_counter: u64,
+}
+
+impl Presentatation {
+  pub fn new() -> Self {
+    Self {
+      current_buffer: PaintBuffer::new(),
+      cur_hash: None,
+      prev_hash: None,
+      version_counter: 0,
+    }
+  }
+
+  /// If the paint command has changed since the last draw call, draw it and return true.\
+  /// Otherwise, returns false.
+  pub fn draw(&mut self, painter: &mut PainterInstance, cmd: &impl PaintRoot) -> bool {
+    self.prev_hash = self.cur_hash;
+    self.cur_hash = Some(cmd.cache_hash());
+
+    if self.prev_hash == self.cur_hash {
+      return false;
+    }
+
+    self.current_buffer.clear();
+    cmd.paint_root(painter, &mut self.current_buffer);
+
+    self.version_counter = self.version_counter.wrapping_add(1);
+
+    true
+  }
+
+  /// Get the current paint buffer
+  pub fn buffer(&self) -> &PaintBuffer {
+    &self.current_buffer
+  }
+
+  /// Get the complete backend data for the current presentation
+  ///
+  /// It contains the current paint buffer and the hash of the presentation\
+  /// Unlike the `TextureAtlasBackendData`, the version is non-incremental
+  pub fn backend_data(&self) -> PresentatationBackendData {
+    PresentatationBackendData {
+      buffer: &self.current_buffer,
+      version: self.version_counter,
+      hash: self.cur_hash.unwrap_or(0),
+    }
+  }
+}
+
+impl Default for Presentatation {
+  fn default() -> Self {
+    Self::new()
+  }
+}
+
+
+/// Backend data for the Presentation
+#[derive(Clone, Copy)]
+pub struct PresentatationBackendData<'a> {
+  /// The current paint buffer
+  pub buffer: &'a PaintBuffer,
+
+  /// The version of the presentation
+  ///
+  /// This is incremented every time the buffer hash changes
+  pub version: u64,
+
+  /// Unique hash of current paint buffer commands
+  pub hash: u64,
+}
+
diff --git a/hui-wgpu/Cargo.toml b/hui-wgpu/Cargo.toml
index 9ffe9e7..75ff0c0 100644
--- a/hui-wgpu/Cargo.toml
+++ b/hui-wgpu/Cargo.toml
@@ -16,7 +16,7 @@ include = [
 ]
 
 [dependencies]
-hui = { version = "=0.1.0-alpha.6", path = "../hui", default-features = false }
+hui-painter = { version = "=0.1.0-alpha.6", path = "../hui-painter", default-features = false }
 wgpu = { version = "24", default-features = false, features = ["wgsl"]}
 bytemuck = "1.15"
 log = "0.4"
diff --git a/hui-wgpu/src/lib.rs b/hui-wgpu/src/lib.rs
index 389f5d0..a881022 100644
--- a/hui-wgpu/src/lib.rs
+++ b/hui-wgpu/src/lib.rs
@@ -1,5 +1,10 @@
 use glam::{vec2, Vec2};
-use hui::{draw::{TextureAtlasMeta, UiDrawCall, UiVertex}, UiInstance};
+use hui_painter::{
+  backend::BackendData,
+  paint::buffer::Vertex,
+  presentation::PresentatationBackendData,
+  texture::TextureAtlasBackendData
+};
 
 const DEFAULT_BUFFER_SIZE: u64 = 1024;
 const DEFAULT_TEXTURE_SIZE: u32 = 512;
@@ -27,8 +32,8 @@ impl WgpuVertex {
   };
 }
 
-impl From<UiVertex> for WgpuVertex {
-  fn from(v: UiVertex) -> Self {
+impl From<Vertex> for WgpuVertex {
+  fn from(v: Vertex) -> Self {
     Self {
       position: v.position.to_array(),
       uv: v.uv.to_array(),
@@ -38,7 +43,9 @@ impl From<UiVertex> for WgpuVertex {
 }
 
 pub struct WgpuUiRenderer {
-  pub modified: bool,
+  // pub modified: bool,
+  pub last_buf_hash: u64,
+  pub last_img_version: u64,
   pub vertex_buffer: wgpu::Buffer,
   pub index_buffer: wgpu::Buffer,
   pub vertex_count: usize,
@@ -184,7 +191,8 @@ impl WgpuUiRenderer {
     });
 
     Self {
-      modified: true,
+      last_buf_hash: 0,
+      last_img_version: 0,
       vertex_buffer,
       index_buffer,
       vertex_count: 0,
@@ -198,8 +206,8 @@ impl WgpuUiRenderer {
     }
   }
 
-  fn update_buffers(&mut self, call: &UiDrawCall, queue: &wgpu::Queue, device: &wgpu::Device, resolution: Vec2) {
-    let data_vtx = call.vertices.iter()
+  fn update_buffers(&mut self, present_data: &PresentatationBackendData, queue: &wgpu::Queue, device: &wgpu::Device, resolution: Vec2) {
+    let data_vtx = present_data.buffer.vertices.iter()
       .copied()
       .map(|x| {
         let mut v = x;
@@ -208,13 +216,13 @@ impl WgpuUiRenderer {
       })
       .map(WgpuVertex::from)
       .collect::<Vec<_>>();
-    let data_idx = &call.indices[..];
+    let data_idx = &present_data.buffer.indices[..];
 
     let data_vtx_view = bytemuck::cast_slice(&data_vtx);
     let data_idx_view = bytemuck::cast_slice(data_idx);
 
-    self.vertex_count = call.vertices.len();
-    self.index_count = call.indices.len();
+    self.vertex_count = present_data.buffer.vertices.len();
+    self.index_count = present_data.buffer.indices.len();
 
     if data_vtx.is_empty() || data_idx.is_empty() {
       return
@@ -240,11 +248,13 @@ impl WgpuUiRenderer {
 
     queue.write_buffer(&self.vertex_buffer, 0, data_vtx_view);
     queue.write_buffer(&self.index_buffer, 0, data_idx_view);
+
+    self.last_buf_hash = present_data.hash;
   }
 
-  fn update_texture(&mut self, meta: TextureAtlasMeta, queue: &wgpu::Queue, device: &wgpu::Device) {
+  fn update_texture(&mut self, atlas: &TextureAtlasBackendData, queue: &wgpu::Queue, device: &wgpu::Device) {
     //TODO URGENCY:HIGH resize texture if needed
-    if meta.data.len() as u32 > (self.texture.size().width * self.texture.size().height * 4) {
+    if atlas.data.len() as u32 > (self.texture.size().width * self.texture.size().height * 4) {
       self.texture.destroy();
       // unimplemented!("texture resize not implemented");
       self.texture = device.create_texture(&wgpu::TextureDescriptor {
@@ -263,44 +273,41 @@ impl WgpuUiRenderer {
       });
     }
     queue.write_texture(
-      wgpu::ImageCopyTexture {
+      wgpu::TexelCopyTextureInfo {
         texture: &self.texture,
         mip_level: 0,
         origin: wgpu::Origin3d::ZERO,
         aspect: wgpu::TextureAspect::All,
       },
-      meta.data,
-      wgpu::ImageDataLayout {
+      atlas.data,
+      wgpu::TexelCopyBufferLayout {
         offset: 0,
-        bytes_per_row: Some(meta.size.x * 4),
-        rows_per_image: Some(meta.size.y),
+        bytes_per_row: Some(atlas.size.x * 4),
+        rows_per_image: Some(atlas.size.y),
       },
       wgpu::Extent3d {
-        width: meta.size.x,
-        height: meta.size.y,
+        width: atlas.size.x,
+        height: atlas.size.y,
         depth_or_array_layers: 1,
       }
     );
+
+    self.last_img_version = atlas.version;
   }
 
   pub fn update(
     &mut self,
-    instance: &UiInstance,
+    data: &BackendData,
     queue: &wgpu::Queue,
     device: &wgpu::Device,
     resolution: Vec2,
   ) {
-    let (modified, call) = instance.backend_paint_buffer();
-    if self.modified || modified {
-      self.update_buffers(call, queue, device, resolution);
+    if data.presentation.hash != self.last_buf_hash {
+      self.update_buffers(&data.presentation, queue, device, resolution);
     }
-
-    let meta = instance.backend_atlas();
-    if self.modified || meta.modified {
-      self.update_texture(meta, queue, device);
+    if data.atlas.version != self.last_img_version {
+      self.update_texture(&data.atlas, queue, device);
     }
-
-    self.modified = false;
   }
 
   pub fn draw(
diff --git a/hui/Cargo.toml b/hui/Cargo.toml
index b1c0897..d29c481 100644
--- a/hui/Cargo.toml
+++ b/hui/Cargo.toml
@@ -33,7 +33,7 @@ image = { version = "0.25", default-features = false, optional = true }
 rustc-hash = "2.0"
 
 [features]
-default = ["el_all", "image", "builtin_font", "derive"]
+default = ["el_all", "derive"]
 
 ## Enable derive macros
 derive = ["dep:hui-derive"]
diff --git a/hui/src/instance.rs b/hui/src/instance.rs
index 59a085e..a84465c 100644
--- a/hui/src/instance.rs
+++ b/hui/src/instance.rs
@@ -1,5 +1,5 @@
 use hui_painter::{
-  backend::{BackendData, Presentatation}, paint::{buffer::PaintBuffer, command::{PaintCommand, PaintList, PaintRoot}}, text::FontHandle, texture::{SourceTextureFormat, TextureAtlasBackendData, TextureHandle}, PainterInstance
+  backend::BackendData, paint::command::{PaintCommand, PaintList}, presentation::Presentatation, text::FontHandle, texture::{SourceTextureFormat, TextureHandle}, PainterInstance
 };
 use crate::{
   element::{MeasureContext, ProcessContext, UiElement},
@@ -17,13 +17,18 @@ use crate::{
 /// In most cases, you should only have one instance of this struct, but multiple instances are allowed\
 /// (Please note that it's possible to render multiple UI "roots" using a single instance)
 pub struct UiInstance {
+  // TODO Do not own Presentation/Painter
   painter: PainterInstance,
+  presentation: Presentatation,
   paint_commands: PaintList,
   stateful_state: StateRepo,
   events: EventQueue,
   input: UiInputState,
   signal: SignalStore,
   font_stack: FontStack,
+
+  /// Set to true if present has been called since the last begin_frame
+  frame_presented: bool,
 }
 
 impl UiInstance {
@@ -33,12 +38,14 @@ impl UiInstance {
   pub fn new() -> Self {
     UiInstance {
       painter: PainterInstance::new(),
+      presentation: Presentatation::new(),
       paint_commands: PaintList::default(),
       font_stack: FontStack::new(),
       stateful_state: StateRepo::new(),
       events: EventQueue::new(),
       input: UiInputState::new(),
       signal: SignalStore::new(),
+      frame_presented: false,
     }
   }
 
@@ -165,12 +172,10 @@ impl UiInstance {
     });
   }
 
-  /// Reset the state from the previous frame, and prepare the UI for layout and processing\
-  /// You must call this function at the start of the frame, before adding any elements\
+  /// Reset the state from the previous frame, and prepare the UI for layout and processing
   ///
-  /// ## Panics:
-  /// If called twice in a row (for example, if you forget to call [`UiInstance::end`])\
-  /// This is an indication of a bug in your code and should be fixed.
+  /// - You must call this function at the start of the frame, before adding any elements
+  /// - Make sure to provide all of the events that happened since the last frame before calling this function, to avoid a 1-frame delay in event processing
   pub fn begin_frame(&mut self) {
     //first, drain and process the event queue
     self.input.update_state(&mut self.events);
@@ -182,6 +187,17 @@ impl UiInstance {
     self.paint_commands.clear();
   }
 
+  /// End rendering the current frame and present it
+  ///
+  /// You must call this function sometime at the end of the frame, after adding all elements but before rendering, but before running the render backend
+  pub fn end_frame(&mut self) {
+    self.presentation.draw(&mut self.painter, &self.paint_commands);
+  }
+
+  pub fn backend_data(&self) -> BackendData {
+    self.painter.backend_data(&self.presentation)
+  }
+
   /// Push a platform event to the UI event queue
   ///
   /// You should call this function *before* calling [`UiInstance::begin`] or after calling [`UiInstance::end`]\