Repository: raphamorim/rio (language: rust)

Issue:
Render images directly from the terminal
Back in time, terminals were always text-based.

Currently, almost everyone uses a terminal on a GUI environment. It amazes me to not having a terminal that doesn't render a image if I do a "cat sunset.jpg".

It would be nice if Rio could detect such output and renders it. Or maybe offering a built-in command to display them, maybe "draw image.jpg". 


Discussion / hints:
That would be a nice to have for after the first release. Marking it as wish list.

Believe kitty terminal also does that with icat: https://sw.kovidgoyal.net/kitty/kittens/icat/
The basic protocol for images in Kitty is pretty simple:
https://sw.kovidgoyal.net/kitty/graphics-protocol/

I used it to make a simple but fully functional Chromium-based web browser that renders in Kitty and WezTerm (with Kitty protocols enabled): https://github.com/chase/awrit

There is also the iTerm2 image protocol which has support in a lot of newer terminal emulators: https://iterm2.com/documentation-images.html
> The basic protocol for images in Kitty is pretty simple:

Point of correction... it's not that "simple" on the end of the terminal emulator.

It's probably the main reason why no terminal emulator other than Kitty implements nearly half of the protocol [correctly].
> > The basic protocol for images in Kitty is pretty simple:
> 
> Point of correction... it's not that "simple" on the end of the terminal emulator.
> 
> It's probably the main reason why no terminal emulator other than Kitty implements nearly half of the protocol (correctly).

Fair enough, certainly the protocol in its entirety is complicated.

The scope of this issue isn't to support image IDs, arbitrary positions, animation etc. It's to render images directly in the terminal, and that can be done with a subset of the protocol, described within the first few paragraphs.

A minimal subset, similar to iTerm2's protocol, can be implemented: permitting only PNG filenames to be transmitted and displayed immediately at the current cursor position. Then handling a clear screen to remove said image.

Although you certainly have more experience with such things than myself, I wouldn't want the maintainer to get discouraged. Incremental improvement and all that.

> The scope of this issue isn't to support image IDs, arbitrary positions, animation etc. It's to render images directly in the terminal, and that can be done with a subset of the protocol, described within the first few paragraphs.

Defining that "subset" is where the problem begins.

> A minimal subset, similar to iTerm2's protocol, can be implemented: permitting only PNG filenames to be transmitted and displayed immediately at the current cursor position. Then handling a clear screen to remove said image.
> 
> Although you certainly have more experience with such things than myself, I wouldn't want the maintainer to get discouraged. Incremental improvement and all that.

From the *little* experience I have with the two protocols mentioned here, I'll advice the iTerm2 protocol be implemented first (because it's a lot less complex than Kitty's) and the Kitty graphics protocol should only be implemented when the project is more mature and developers/contributors are ready to take it on properly.

For some insight, check out the current state of support in Wezterm (just a search through the issues on there will do). By the way, the support is declared experimental but comparing it to the state of the iTerm2 protocol support (and how long each has been in development) makes my point clear.

_**Please Note:** The above mention/reference is not intended to talk lightly/negatively about the project or its author/developers (in fact, Wezterm is my daily driver and it's doing very well in almost every other aspect I can think of)._

The summary of all I've said is... It's better to have a [almost] complete, spec-compliant implementation of a less-capable protocol than a highly incomplete implementation of a more-capable protocol. Also, it'll be less headache for the developers.

* * *

That said, there's also sixels (requested in #46) but that has its own unique set of headaches, primarily due to the lack of a complete central/standard specification and limited access to a reference implementation (in fact, the [closest to a reference implementation](https://github.com/hackerb9/vt340test) I've come across is probably not something any relevant implementation today would want to follow head-on).

Anyways, sixels are probably still simpler to implement than the kitty graphics protocol given the limited scope.

I should note that my experience with sixels is _currently_ limited, there are others way more knowledgeable/experienced on the topic.
Hello @raphamorim !

I noticed some sixel implementation work was underway, though, I'm not sure of the state as I'm yet to test.

I just wanted to inform you about an ongoing related discussion that's quite important as I believe it's important that things are done well and correctly right from the onset.

See https://github.com/hpjansson/chafa/issues/192, you might wanna follow along and possibly chime in.

Well done and thank you.

Fix patch:
diff --git a/Cargo.lock b/Cargo.lock
index cffa4c4995..a804ecef28 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -289,6 +289,12 @@ version = "1.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
 
+[[package]]
+name = "byteorder-lite"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
+
 [[package]]
 name = "bytes"
 version = "0.3.0"
@@ -1388,6 +1394,18 @@ dependencies = [
  "tiff",
 ]
 
+[[package]]
+name = "image"
+version = "0.25.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "99314c8a2152b8ddb211f924cdae532d8c5e4c8bb54728e12fff1b0cd5963a10"
+dependencies = [
+ "bytemuck",
+ "byteorder-lite",
+ "num-traits",
+ "png",
+]
+
 [[package]]
 name = "indexmap"
 version = "2.4.0"
@@ -2528,7 +2546,7 @@ dependencies = [
  "corcovado",
  "cursor-icon",
  "dirs",
- "image",
+ "image 0.25.2",
  "libc",
  "log 0.4.22",
  "parking_lot",
@@ -2616,7 +2634,7 @@ dependencies = [
  "corcovado",
  "dirs",
  "futures",
- "image",
+ "image 0.25.2",
  "libc",
  "log 0.4.22",
  "notify",
@@ -2911,7 +2929,8 @@ dependencies = [
  "fontconfig-parser",
  "futures",
  "guillotiere",
- "image",
+ "image 0.24.9",
+ "image 0.25.2",
  "js-sys",
  "linked-hash-map",
  "log 0.4.22",
diff --git a/Cargo.toml b/Cargo.toml
index 3609ff9ad1..2c25d8a4fd 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -39,7 +39,7 @@ raw-window-handle = "0.6.2"
 parking_lot = { version = "0.12.3", features = ["nightly", "hardware-lock-elision"] }
 rustc-hash = "2.0.0"
 unicode-width = "0.1.13"
-image = { version = "0.24.9", default-features = false, features = ["ico"] }
+image_rs = { package = "image", version = "0.25.2", default-features = false, features = ["ico"] }
 regex = "1.10.5"
 bytemuck = { version = "1.17.0", features = [ "derive" ] }
 log = "0.4.22"
diff --git a/docs/docs/config/window.md b/docs/docs/config/window.md
index 97a58cd6cb..be6e1af0a1 100644
--- a/docs/docs/config/window.md
+++ b/docs/docs/config/window.md
@@ -61,14 +61,28 @@ blur = true
 
 ### Using image as background:
 
+If both properties `width` and `height` are occluded then background image will use the terminal width/height.
+
 ```toml
 [window.background-image]
 path = "/Users/hugoamor/Desktop/musashi.png"
 opacity = 0.5
-width = 400.0
-height = 400.0
 x = 0.0
 y = -100.0
 ```
 
 ![Demo image as background](/assets/demos/demo-background-image.png)
+
+If any property `width` or `height` are used then background image will be respected.
+
+```toml
+[window.background-image]
+path = "/Users/hugoamor/Desktop/harvest-moon.png"
+width = 1200
+height = 800
+opacity = 0.5
+x = 0.0
+y = 0.0
+```
+
+![Demo image as background](/assets/demos/demo-background-image-partial.png)
diff --git a/docs/docs/features/index.md b/docs/docs/features/index.md
index 80cf844864..3fcb84ef13 100644
--- a/docs/docs/features/index.md
+++ b/docs/docs/features/index.md
@@ -16,6 +16,7 @@ Short introduction of Rio terminal features.
 - [Multi windows](/docs/features/multi-windows)
 - [Navigation](/docs/config/navigation)
 - [Color automation for navigation](/docs/features/color-automation-for-navigation)
+- [Sixel protocol](/docs/features/sixel-protocol)
 - [Spawn or Fork](/docs/features/spawn-or-fork)
 
 Note: Many other features are in development.
diff --git a/docs/docs/features/sixel-protocol.md b/docs/docs/features/sixel-protocol.md
new file mode 100644
index 0000000000..7866ed4043
--- /dev/null
+++ b/docs/docs/features/sixel-protocol.md
@@ -0,0 +1,20 @@
+---
+title: 'Sixel protocol'
+language: 'en'
+---
+
+Sixel, short for "six pixels", is a bitmap graphics format supported by terminals and printers from DEC. It consists of a pattern six pixels high and one wide, resulting in 64 possible patterns. Each possible pattern is assigned an ASCII character, making the sixels easy to transmit on 7-bit serial links.
+
+> Demo of sixel on Rio.
+
+![Demo sixel as gif](/assets/features/demo-sixel.gif)
+
+Sixel was first introduced as a way of sending bitmap graphics to DEC dot matrix printers like the LA50. After being put into "sixel mode" the following data was interpreted to directly control six of the pins in the nine-pin print head. A string of sixel characters encodes a single 6-pixel high row of the image.
+
+![Demo sixel with timg](/assets/features/demo-sixel-2.png)
+
+The system was later re-used as a way to send bitmap data to the VT200 series and VT320 terminals when defining custom character sets. A series of sixels are used to transfer the bitmap for each character. This feature is known as soft character sets or dynamically redefinable character sets (DRCS). With the VT240, VT241, VT330, and VT340, the terminals could decode a complete sixel image to the screen, like those previously sent to printers.
+
+![Demo sixel](/assets/features/demo-sixel.png)
+
+For more information on Sixel: [en.wikipedia.org/wiki/Sixel](https://en.wikipedia.org/wiki/Sixel)
\ No newline at end of file
diff --git a/docs/docs/changelog.md b/docs/docs/releases.md
similarity index 98%
rename from docs/docs/changelog.md
rename to docs/docs/releases.md
index aaee61ee24..59c8098b25 100644
--- a/docs/docs/changelog.md
+++ b/docs/docs/releases.md
@@ -1,21 +1,25 @@
 ---
-title: 'Changelog'
+title: 'Releases'
 language: 'en'
 ---
 
-# Changelog
+# Releases
 
 ## Unreleased
 
 <!-- - Fix: MacOS Delete key doesn't work in kitty mode [#513](https://github.com/raphamorim/rio/issues/513). -->
 <!-- - Fix: Kitty keyboard protocol doesn't work with tmux [#599](https://github.com/raphamorim/rio/issues/599). -->
+- Initial support to Sixel protocol.
+- Fix on all the issues regarding whenever the font atlas reaches the limit.
+- Performance: drop extra texture creation and manipulation.
+- Improvements on `window.background-image` as respect width and height properties if were used.
 - Macos: remove grab cursor when dragging and use default instead.
 - Fix `tabs-active-highlight` config key [#618](https://github.com/raphamorim/rio/pull/618).
 - Add `tabs-active-foreground` config key [#619](https://github.com/raphamorim/rio/pull/619).
 - `use-kitty-keyboard-protocol` is now `true` as default.
 - Remove tokio runtime.
-- Allow configuring with lowercase values for enums
-- Rename `hide-cursor-when-typing` to `hide-mouse-cursor-when-typing`
+- Allow configuring with lowercase values for enums.
+- Rename `hide-cursor-when-typing` to `hide-mouse-cursor-when-typing`.
 
 ## 0.1.10
 
diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js
index 4dc864ed30..f637870838 100644
--- a/docs/docusaurus.config.js
+++ b/docs/docusaurus.config.js
@@ -130,25 +130,15 @@ const config = {
         },
         items: [
           { to: '/docs/install', label: 'Install', position: 'left' },
-          {
-            to: '/docs/config',
-            label: 'Config',
-            position: 'left',
-          },
+          { to: '/docs/config', label: 'Config', position: 'left', },
           { to: '/docs/features', label: 'Features', position: 'left' },
+          { to: '/docs/releases', label: 'Releases', position: 'left' },
           { to: '/blog', label: 'Blog', position: 'left' },
-          { to: '/docs/changelog', label: 'Changelog', position: 'left' },
           {
             href: 'https://discord.gg/zRvJjmKGwS',
             label: 'Discord',
             position: 'left',
           },
-          // {
-          //   type: 'docsVersionDropdown',
-          //   position: 'right',
-          //   // dropdownItemsAfter: [{to: '/versions', label: 'All versions'}],
-          //   dropdownActiveClassDisabled: true,
-          // },
           {
             type: 'localeDropdown',
             position: 'right',
diff --git a/docs/static/assets/demos/demo-background-image-partial.png b/docs/static/assets/demos/demo-background-image-partial.png
new file mode 100644
index 0000000000..d4642ff786
Binary files /dev/null and b/docs/static/assets/demos/demo-background-image-partial.png differ
diff --git a/docs/static/assets/features/demo-sixel-2.png b/docs/static/assets/features/demo-sixel-2.png
new file mode 100644
index 0000000000..4dbc5b5c83
Binary files /dev/null and b/docs/static/assets/features/demo-sixel-2.png differ
diff --git a/docs/static/assets/features/demo-sixel.gif b/docs/static/assets/features/demo-sixel.gif
new file mode 100644
index 0000000000..9089458332
Binary files /dev/null and b/docs/static/assets/features/demo-sixel.gif differ
diff --git a/docs/static/assets/features/demo-sixel.png b/docs/static/assets/features/demo-sixel.png
new file mode 100644
index 0000000000..590117b228
Binary files /dev/null and b/docs/static/assets/features/demo-sixel.png differ
diff --git a/frontends/rioterm/Cargo.toml b/frontends/rioterm/Cargo.toml
index 5703d54dda..3b3c265338 100644
--- a/frontends/rioterm/Cargo.toml
+++ b/frontends/rioterm/Cargo.toml
@@ -30,7 +30,7 @@ clap = { version = "4.5.9", features = ["derive"] }
 dirs = "5.0.1"
 notify = "6.1.1"
 rustc-hash = { workspace = true }
-image = { workspace = true }
+image_rs = { workspace = true }
 libc = { workspace = true }
 parking_lot = { workspace = true }
 serde = { workspace = true }
diff --git a/frontends/rioterm/src/renderer/mod.rs b/frontends/rioterm/src/renderer/mod.rs
index e9614a5f7c..14b2671be7 100644
--- a/frontends/rioterm/src/renderer/mod.rs
+++ b/frontends/rioterm/src/renderer/mod.rs
@@ -16,8 +16,8 @@ use rio_backend::config::colors::{
 };
 use rio_backend::config::Config;
 use rio_backend::sugarloaf::{
-    Content, ContentBuilder, FragmentStyle, FragmentStyleDecoration, Stretch, Style,
-    SugarCursor, Sugarloaf, UnderlineInfo, UnderlineShape, Weight,
+    Content, ContentBuilder, FragmentStyle, FragmentStyleDecoration, Graphic, Stretch,
+    Style, SugarCursor, Sugarloaf, UnderlineInfo, UnderlineShape, Weight,
 };
 use std::collections::HashMap;
 use std::ops::RangeInclusive;
@@ -325,11 +325,6 @@ impl Renderer {
                 continue;
             }
 
-            if square.flags.contains(Flags::GRAPHICS) {
-                // &self.create_graphic_sugar(square);
-                continue;
-            }
-
             let (mut style, square_content) =
                 if has_cursor && column == self.cursor.state.pos.col {
                     self.create_cursor_style(square)
@@ -337,6 +332,26 @@ impl Renderer {
                     self.create_style(square)
                 };
 
+            if square.flags.contains(Flags::GRAPHICS) {
+                // let graphics = square.graphics().map(|graphics| {
+                //     graphics
+                //         .iter()
+                //         .map(|graphic| Graphic {
+                //             id: graphic.texture.id,
+                //             offset_x: graphic.offset_x,
+                //             offset_y: graphic.offset_y,
+                //         })
+                //         .collect::<_>()
+                // });
+                // style.media = Some(graphics);
+                let graphic = &square.graphics().unwrap()[0];
+                style.media = Some(Graphic {
+                    id: graphic.texture.id,
+                    offset_x: graphic.offset_x,
+                    offset_y: graphic.offset_y,
+                });
+            }
+
             if self.hyperlink_range.is_some()
                 && square.hyperlink().is_some()
                 && self
@@ -621,10 +636,10 @@ impl Renderer {
                 has_underline_cursor = true;
             }
             CursorShape::Block => {
-                style.cursor = SugarCursor::Block(cursor_color);
+                style.cursor = Some(SugarCursor::Block(cursor_color));
             }
             CursorShape::Beam => {
-                style.cursor = SugarCursor::Caret(cursor_color);
+                style.cursor = Some(SugarCursor::Caret(cursor_color));
             }
             CursorShape::Hidden => {}
         }
diff --git a/frontends/rioterm/src/router/window/mod.rs b/frontends/rioterm/src/router/window/mod.rs
index c3cc1c5d7d..dd6008dc41 100644
--- a/frontends/rioterm/src/router/window/mod.rs
+++ b/frontends/rioterm/src/router/window/mod.rs
@@ -18,7 +18,7 @@ pub fn create_window_builder(
     config: &Config,
     #[allow(unused)] tab_id: Option<String>,
 ) -> WindowAttributes {
-    let image_icon = image::load_from_memory(LOGO_ICON).unwrap();
+    let image_icon = image_rs::load_from_memory(LOGO_ICON).unwrap();
     let icon = Icon::from_rgba(
         image_icon.to_rgba8().into_raw(),
         image_icon.width(),
diff --git a/frontends/rioterm/src/screen/mod.rs b/frontends/rioterm/src/screen/mod.rs
index a1d95a709a..57f8437942 100644
--- a/frontends/rioterm/src/screen/mod.rs
+++ b/frontends/rioterm/src/screen/mod.rs
@@ -329,6 +329,7 @@ impl Screen<'_> {
             terminal.cursor_shape = cursor;
             terminal.default_cursor_shape = cursor;
             terminal.blinking_cursor = config.blinking_cursor;
+            drop(terminal);
         }
 
         self.mouse
@@ -1052,6 +1053,7 @@ impl Screen<'_> {
             };
 
             terminal.scroll_to_pos(new_origin);
+            drop(terminal);
 
             self.search_state.display_offset_delta = 0;
             self.search_state.origin = new_origin;
@@ -1087,6 +1089,7 @@ impl Screen<'_> {
 
         // Store origin and scroll back to the match.
         terminal.scroll_display(Scroll::Delta(-self.search_state.display_offset_delta));
+        drop(terminal);
         self.search_state.origin = new_origin;
     }
 
@@ -1575,6 +1578,7 @@ impl Screen<'_> {
             terminal.vi_mode_cursor.pos = self.search_state.origin;
             terminal
                 .scroll_display(Scroll::Delta(self.search_state.display_offset_delta));
+            drop(terminal);
         }
         self.search_state.display_offset_delta = 0;
         self.sugarloaf.mark_dirty();
@@ -1640,6 +1644,7 @@ impl Screen<'_> {
                     self.search_state.focused_match = None;
                 }
             }
+            drop(terminal);
         }
 
         if should_reset_search_state {
diff --git a/frontends/rioterm/src/sequencer.rs b/frontends/rioterm/src/sequencer.rs
index 65ef496809..75bb76520c 100644
--- a/frontends/rioterm/src/sequencer.rs
+++ b/frontends/rioterm/src/sequencer.rs
@@ -1205,7 +1205,6 @@ impl Sequencer {
 
                         if let Some(route) = self.router.routes.get_mut(&window_id) {
                             route.window.screen.resize(new_size);
-                            route.window.screen.context_manager.schedule_render(60);
                         }
                     }
 
@@ -1224,8 +1223,6 @@ impl Sequencer {
                                 .window
                                 .screen
                                 .set_scale(scale, route.window.winit_window.inner_size());
-                            route.window.screen.update_content();
-                            route.request_redraw();
                         }
                     }
 
diff --git a/misc/scripts/utils/imgcat.sh b/misc/scripts/utils/imgcat.sh
old mode 100644
new mode 100755
diff --git a/rio-backend/Cargo.toml b/rio-backend/Cargo.toml
index e4ff45a4d8..1f2aa81a84 100644
--- a/rio-backend/Cargo.toml
+++ b/rio-backend/Cargo.toml
@@ -26,7 +26,7 @@ regex = { workspace = true }
 raw-window-handle = { workspace = true }
 copypasta = { version = "0.10.1", default-features = false }
 dirs = "5.0.1"
-image = { workspace = true }
+image_rs = { workspace = true }
 libc = { workspace = true }
 parking_lot = { workspace = true }
 serde = { workspace = true }
diff --git a/rio-backend/src/ansi/graphics.rs b/rio-backend/src/ansi/graphics.rs
index e63dfadb14..699a10d5b6 100644
--- a/rio-backend/src/ansi/graphics.rs
+++ b/rio-backend/src/ansi/graphics.rs
@@ -4,7 +4,7 @@
 
 use crate::config::colors::ColorRgb;
 use crate::crosswords::grid::Dimensions;
-use crate::sugarloaf::{SugarGraphicData, SugarGraphicId};
+use crate::sugarloaf::{GraphicData, GraphicId};
 use parking_lot::Mutex;
 use smallvec::SmallVec;
 use std::mem;
@@ -13,7 +13,7 @@ use std::sync::{Arc, Weak};
 #[derive(Eq, PartialEq, Clone, Debug)]
 pub struct ClearSubregion {
     /// Graphics identifier.
-    pub id: SugarGraphicId,
+    pub id: GraphicId,
 
     /// X coordinate.
     pub x: u16,
@@ -24,10 +24,10 @@ pub struct ClearSubregion {
 
 pub struct UpdateQueues {
     /// Graphics read from the PTY.
-    pub pending: Vec<SugarGraphicData>,
+    pub pending: Vec<GraphicData>,
 
     /// Graphics removed from the grid.
-    pub remove_queue: Vec<SugarGraphicId>,
+    pub remove_queue: Vec<GraphicId>,
 
     /// Subregions in a graphic to be clear.
     pub clear_subregions: Vec<ClearSubregion>,
@@ -39,7 +39,7 @@ pub const MAX_GRAPHIC_DIMENSIONS: [usize; 2] = [4096, 4096];
 #[derive(Clone, Debug)]
 pub struct TextureRef {
     /// Graphic identifier.
-    pub id: SugarGraphicId,
+    pub id: GraphicId,
 
     /// Width, in pixels, of the graphic.
     pub width: u16,
@@ -120,7 +120,7 @@ impl Drop for GraphicCell {
 #[derive(Debug)]
 pub enum TextureOperation {
     /// Remove a texture from the GPU.
-    Remove(SugarGraphicId),
+    Remove(GraphicId),
 
     /// Clear a subregion.
     ClearSubregion(ClearSubregion),
@@ -133,7 +133,7 @@ pub struct Graphics {
     pub last_id: u64,
 
     /// New graphics, received from the PTY.
-    pub pending: Vec<SugarGraphicData>,
+    pub pending: Vec<GraphicData>,
 
     /// Graphics removed from the grid.
     pub texture_operations: Arc<Mutex<Vec<TextureOperation>>>,
@@ -158,9 +158,9 @@ impl Graphics {
     }
 
     /// Generate a new graphic identifier.
-    pub fn next_id(&mut self) -> SugarGraphicId {
+    pub fn next_id(&mut self) -> GraphicId {
         self.last_id += 1;
-        SugarGraphicId(self.last_id)
+        GraphicId(self.last_id)
     }
 
     /// Get queues to update graphics in the grid.
@@ -207,8 +207,8 @@ impl Graphics {
 #[test]
 fn check_opaque_region() {
     use sugarloaf::ColorType;
-    let graphic = SugarGraphicData {
-        id: SugarGraphicId(0),
+    let graphic = GraphicData {
+        id: GraphicId(0),
         width: 10,
         height: 10,
         color_type: ColorType::Rgb,
@@ -229,8 +229,8 @@ fn check_opaque_region() {
         data
     };
 
-    let graphic = SugarGraphicData {
-        id: SugarGraphicId(0),
+    let graphic = GraphicData {
+        id: GraphicId(0),
         pixels,
         width: 10,
         height: 10,
diff --git a/rio-backend/src/ansi/sixel.rs b/rio-backend/src/ansi/sixel.rs
index 2172ba4922..87250317ee 100644
--- a/rio-backend/src/ansi/sixel.rs
+++ b/rio-backend/src/ansi/sixel.rs
@@ -27,7 +27,7 @@ use std::{fmt, mem};
 
 use crate::ansi::graphics::MAX_GRAPHIC_DIMENSIONS;
 use crate::config::colors::ColorRgb;
-use sugarloaf::{ColorType, SugarGraphicData, SugarGraphicId};
+use sugarloaf::{ColorType, GraphicData, GraphicId};
 
 use copa::Params;
 use log::trace;
@@ -506,7 +506,7 @@ impl Parser {
 
     /// Returns the final graphic to append to the grid, with the palette
     /// built in the process.
-    pub fn finish(mut self) -> Result<(SugarGraphicData, Vec<ColorRgb>), Error> {
+    pub fn finish(mut self) -> Result<(GraphicData, Vec<ColorRgb>), Error> {
         self.finish_command()?;
 
         trace!(
@@ -536,8 +536,8 @@ impl Parser {
             rgba_pixels.extend_from_slice(&pixel);
         }
 
-        let data = SugarGraphicData {
-            id: SugarGraphicId(0),
+        let data = GraphicData {
+            id: GraphicId(0),
             height: self.height,
             width: self.width,
             color_type: ColorType::Rgba,
@@ -876,7 +876,7 @@ mod tests {
                 sixel.truncate(pos);
             }
 
-            // Parse the data and get the SugarGraphicData item.
+            // Parse the data and get the GraphicData item.
             let mut parser = Parser::default();
             for byte in sixel {
                 parser.put(byte).unwrap();
diff --git a/rio-backend/src/crosswords/mod.rs b/rio-backend/src/crosswords/mod.rs
index bf47b66aee..203f953497 100644
--- a/rio-backend/src/crosswords/mod.rs
+++ b/rio-backend/src/crosswords/mod.rs
@@ -58,7 +58,7 @@ use std::ops::{Index, IndexMut, Range};
 use std::option::Option;
 use std::ptr;
 use std::sync::Arc;
-use sugarloaf::SugarGraphicData;
+use sugarloaf::GraphicData;
 use unicode_width::UnicodeWidthChar;
 use vi_mode::{ViModeCursor, ViMotion};
 
@@ -2393,11 +2393,7 @@ impl<U: EventListener> Handler for Crosswords<U> {
     }
 
     #[inline]
-    fn insert_graphic(
-        &mut self,
-        graphic: SugarGraphicData,
-        palette: Option<Vec<ColorRgb>>,
-    ) {
+    fn insert_graphic(&mut self, graphic: GraphicData, palette: Option<Vec<ColorRgb>>) {
         let cell_width = self.graphics.cell_width as usize;
         let cell_height = self.graphics.cell_height as usize;
 
@@ -2571,7 +2567,7 @@ impl<U: EventListener> Handler for Crosswords<U> {
         }
 
         // Add the graphic data to the pending queue.
-        self.graphics.pending.push(SugarGraphicData {
+        self.graphics.pending.push(GraphicData {
             id: graphic_id,
             ..graphic
         });
diff --git a/rio-backend/src/performer/handler.rs b/rio-backend/src/performer/handler.rs
index 92b288b96e..03a257f3bb 100644
--- a/rio-backend/src/performer/handler.rs
+++ b/rio-backend/src/performer/handler.rs
@@ -8,7 +8,7 @@ use log::{debug, warn};
 use std::str::FromStr;
 use std::time::Duration;
 use std::time::Instant;
-use sugarloaf::SugarGraphicData;
+use sugarloaf::GraphicData;
 
 use crate::crosswords::attr::Attr;
 
@@ -344,12 +344,7 @@ pub trait Handler {
     }
 
     /// Insert a new graphic item.
-    fn insert_graphic(
-        &mut self,
-        _data: SugarGraphicData,
-        _palette: Option<Vec<ColorRgb>>,
-    ) {
-    }
+    fn insert_graphic(&mut self, _data: GraphicData, _palette: Option<Vec<ColorRgb>>) {}
 
     /// Set hyperlink.
     fn set_hyperlink(&mut self, _: Option<Hyperlink>) {}
diff --git a/sugarloaf/Cargo.toml b/sugarloaf/Cargo.toml
index c036e2cb5f..45fce27a78 100644
--- a/sugarloaf/Cargo.toml
+++ b/sugarloaf/Cargo.toml
@@ -35,7 +35,7 @@ bytemuck = { workspace = true }
 log = { workspace = true }
 serde = { workspace = true }
 twox-hash = { version = "1.6.3", default-features = false }
-image = { workspace = true }
+image_rs = { workspace = true }
 unicode-width = { workspace = true }
 guillotiere = "0.6.2"
 ordered-float = "4.2.0"
diff --git a/sugarloaf/examples/image.rs b/sugarloaf/examples/image.rs
index 682c55a1c5..33e259585c 100644
--- a/sugarloaf/examples/image.rs
+++ b/sugarloaf/examples/image.rs
@@ -57,8 +57,8 @@ fn main() {
 
     sugarloaf.set_background_image(&sugarloaf::ImageProperties {
         path: String::from("resources/rio-colors.png"),
-        width: 400.,
-        height: 400.,
+        width: Some(400.),
+        height: Some(400.),
         x: 0.,
         y: 0.,
     });
diff --git a/sugarloaf/src/components/layer/mod.rs b/sugarloaf/src/components/layer/mod.rs
index 4bfde1e5bd..4b08d94a8e 100644
--- a/sugarloaf/src/components/layer/mod.rs
+++ b/sugarloaf/src/components/layer/mod.rs
@@ -307,11 +307,10 @@ impl LayerBrush {
         &mut self,
         encoder: &mut wgpu::CommandEncoder,
         ctx: &mut Context,
-        images: &[types::Image],
-        // transformation: [f32; 16],
-        // _scale: f32,
+        images: &[&types::Raster],
     ) {
-        let transformation: [f32; 16] = orthographic_projection(300.0, 300.0);
+        let transformation: [f32; 16] =
+            orthographic_projection(ctx.size.width, ctx.size.height);
         let device = &ctx.device;
         let queue = &ctx.queue;
 
@@ -319,22 +318,19 @@ impl LayerBrush {
         let mut raster_cache = self.raster_cache.borrow_mut();
 
         for image in images {
-            match &image {
-                types::Image::Raster { handle, bounds } => {
-                    if let Some(atlas_entry) = raster_cache.upload(
-                        device,
-                        encoder,
-                        handle,
-                        &mut self.texture_atlas,
-                    ) {
-                        add_instances(
-                            [bounds.x, bounds.y],
-                            [bounds.width, bounds.height],
-                            atlas_entry,
-                            instances,
-                        );
-                    }
-                }
+            let bounds = image.bounds;
+            if let Some(atlas_entry) = raster_cache.upload(
+                device,
+                encoder,
+                &image.handle,
+                &mut self.texture_atlas,
+            ) {
+                add_instances(
+                    [bounds.x, bounds.y],
+                    [bounds.width, bounds.height],
+                    atlas_entry,
+                    instances,
+                );
             }
         }
 
@@ -372,39 +368,30 @@ impl LayerBrush {
         self.prepare_layer += 1;
     }
 
-    pub fn prepare_ref(
+    pub fn prepare_with_handle(
         &mut self,
         encoder: &mut wgpu::CommandEncoder,
         ctx: &mut Context,
-        images: &[&types::Image],
-        // transformation: [f32; 16],
-        // _scale: f32,
+        handle: &image::Handle,
+        bounds: &Rectangle,
     ) {
-        let transformation: [f32; 16] = orthographic_projection(300.0, 300.0);
+        let transformation: [f32; 16] =
+            orthographic_projection(ctx.size.width, ctx.size.height);
         let device = &ctx.device;
         let queue = &ctx.queue;
 
         let instances: &mut Vec<Instance> = &mut Vec::new();
         let mut raster_cache = self.raster_cache.borrow_mut();
 
-        for image in images {
-            match &image {
-                types::Image::Raster { handle, bounds } => {
-                    if let Some(atlas_entry) = raster_cache.upload(
-                        device,
-                        encoder,
-                        handle,
-                        &mut self.texture_atlas,
-                    ) {
-                        add_instances(
-                            [bounds.x, bounds.y],
-                            [bounds.width, bounds.height],
-                            atlas_entry,
-                            instances,
-                        );
-                    }
-                }
-            }
+        if let Some(atlas_entry) =
+            raster_cache.upload(device, encoder, handle, &mut self.texture_atlas)
+        {
+            add_instances(
+                [bounds.x, bounds.y],
+                [bounds.width, bounds.height],
+                atlas_entry,
+                instances,
+            );
         }
 
         if instances.is_empty() {
diff --git a/sugarloaf/src/components/layer/raster.rs b/sugarloaf/src/components/layer/raster.rs
index b4669d1a23..d6a2ac09e2 100644
--- a/sugarloaf/src/components/layer/raster.rs
+++ b/sugarloaf/src/components/layer/raster.rs
@@ -1,7 +1,6 @@
 use crate::components::core::shapes::Size;
 use crate::components::layer::atlas::{self, Atlas};
 use crate::components::layer::image::{Data, Handle};
-use image as image_rs;
 
 use rustc_hash::{FxHashMap, FxHashSet};
 
@@ -51,11 +50,11 @@ pub struct Cache {
 pub fn load_image(handle: &Handle) -> image_rs::ImageResult<image_rs::DynamicImage> {
     match handle.data() {
         Data::Path(path) => {
-            let image = ::image::open(path)?;
+            let image = image_rs::ImageReader::open(path)?.decode()?;
             Ok(image)
         }
         Data::Bytes(bytes) => {
-            let image = ::image::load_from_memory(bytes)?;
+            let image = image_rs::load_from_memory(bytes)?;
             Ok(image)
         }
         Data::Rgba {
diff --git a/sugarloaf/src/components/layer/types.rs b/sugarloaf/src/components/layer/types.rs
index 2ad58d5ab5..0fa070c27d 100644
--- a/sugarloaf/src/components/layer/types.rs
+++ b/sugarloaf/src/components/layer/types.rs
@@ -2,15 +2,12 @@ use crate::components::core::image;
 // use crate::components::core::svg;
 use crate::components::core::shapes::Rectangle;
 
-/// A raster or vector image.
 #[derive(Debug, Clone)]
-pub enum Image {
-    /// A raster image.
-    Raster {
-        /// The handle of a raster image.
-        handle: image::Handle,
+/// A raster image.
+pub struct Raster {
+    /// The handle of a raster image.
+    pub handle: image::Handle,
 
-        /// The bounds of the image.
-        bounds: Rectangle,
-    },
+    /// The bounds of the image.
+    pub bounds: Rectangle,
 }
diff --git a/sugarloaf/src/components/rich_text/batch.rs b/sugarloaf/src/components/rich_text/batch.rs
index 330ecd2591..9e4fb6af13 100644
--- a/sugarloaf/src/components/rich_text/batch.rs
+++ b/sugarloaf/src/components/rich_text/batch.rs
@@ -9,16 +9,16 @@
 // Eventually the file had updates to support other features like background-color,
 // text color, underline color and etc.
 
-use crate::components::rich_text::image_cache::TextureId;
 use bytemuck::{Pod, Zeroable};
 
 /// Batch geometry vertex.
 #[repr(C)]
 #[derive(Debug, Clone, Copy, Zeroable, Pod)]
 pub struct Vertex {
-    pub pos: [f32; 4],
+    pub pos: [f32; 3],
     pub color: [f32; 4],
     pub uv: [f32; 2],
+    pub layers: [i32; 2],
 }
 
 /// Rectangle with floating point coordinates.
@@ -50,8 +50,8 @@ impl From<[f32; 4]> for Rect {
 
 #[derive(Default, Debug)]
 struct Batch {
-    image: Option<TextureId>,
-    mask: Option<TextureId>,
+    image: Option<i32>,
+    mask: Option<i32>,
     vertices: Vec<Vertex>,
     indices: Vec<u32>,
     subpix: bool,
@@ -74,8 +74,8 @@ impl Batch {
         depth: f32,
         color: &[f32; 4],
         coords: Option<&[f32; 4]>,
-        image: Option<TextureId>,
-        mask: Option<TextureId>,
+        image: Option<i32>,
+        mask: Option<i32>,
         subpix: bool,
     ) -> bool {
         if !self.vertices.is_empty() && subpix != self.subpix {
@@ -90,23 +90,10 @@ impl Batch {
             return false;
         }
         self.subpix = subpix;
-        let flags = match (has_image, has_mask) {
-            (true, true) => {
-                self.image = image;
-                self.mask = mask;
-                3.
-            }
-            (true, false) => {
-                self.image = image;
-                1.
-            }
-            (false, true) => {
-                self.mask = mask;
-                2.
-            }
-            _ => 0.,
-        };
-        self.push_rect(rect, depth, flags, color, coords);
+        self.image = image;
+        self.mask = mask;
+        let layers = [self.image.unwrap_or(0), self.mask.unwrap_or(0)];
+        self.push_rect(rect, depth, color, coords, layers);
         true
     }
 
@@ -115,9 +102,9 @@ impl Batch {
         &mut self,
         rect: &Rect,
         depth: f32,
-        flags: f32,
         color: &[f32; 4],
         coords: Option<&[f32; 4]>,
+        layers: [i32; 2],
     ) {
         let x = rect.x;
         let y = rect.y;
@@ -131,24 +118,28 @@ impl Batch {
         let b = coords[3];
         let verts = [
             Vertex {
-                pos: [x, y, depth, flags],
+                pos: [x, y, depth],
                 color: *color,
                 uv: [l, t],
+                layers,
             },
             Vertex {
-                pos: [x, y + h, depth, flags],
+                pos: [x, y + h, depth],
                 color: *color,
                 uv: [l, b],
+                layers,
             },
             Vertex {
-                pos: [x + w, y + h, depth, flags],
+                pos: [x + w, y + h, depth],
                 color: *color,
                 uv: [r, b],
+                layers,
             },
             Vertex {
-                pos: [x + w, y, depth, flags],
+                pos: [x + w, y, depth],
                 color: *color,
                 uv: [r, t],
+                layers,
             },
         ];
         let base = self.vertices.len() as u32;
@@ -166,18 +157,9 @@ impl Batch {
     #[inline]
     fn build_display_list(&self, list: &mut DisplayList) {
         let first_vertex = list.vertices.len() as u32;
-        let first_index = list.indices.len() as u32;
         list.vertices.extend_from_slice(&self.vertices);
         list.indices
             .extend(self.indices.iter().map(|i| *i + first_vertex));
-        if let Some(tex) = self.mask {
-            list.commands.push(Command::BindTexture(0, tex));
-        }
-        if let Some(tex) = self.image {
-            list.commands.push(Command::BindTexture(1, tex));
-        }
-        list.indices_to_draw
-            .push((first_index, first_index + self.indices.len() as u32));
     }
 }
 
@@ -212,12 +194,10 @@ impl BatchManager {
         depth: f32,
         color: &[f32; 4],
         coords: &[f32; 4],
-        mask: TextureId,
         subpix: bool,
     ) {
         for batch in &mut self.transparent {
-            if batch.add_rect(rect, depth, color, Some(coords), None, Some(mask), subpix)
-            {
+            if batch.add_rect(rect, depth, color, Some(coords), None, Some(1), subpix) {
                 return;
             }
         }
@@ -227,7 +207,7 @@ impl BatchManager {
             color,
             Some(coords),
             None,
-            Some(mask),
+            Some(1),
             subpix,
         );
     }
@@ -239,35 +219,20 @@ impl BatchManager {
         depth: f32,
         color: &[f32; 4],
         coords: &[f32; 4],
-        image: TextureId,
         has_alpha: bool,
     ) {
         let transparent = has_alpha || color[3] != 1.0;
         if transparent {
             for batch in &mut self.transparent {
-                if batch.add_rect(
-                    rect,
-                    depth,
-                    color,
-                    Some(coords),
-                    Some(image),
-                    None,
-                    false,
-                ) {
+                if batch.add_rect(rect, depth, color, Some(coords), Some(1), None, false)
+                {
                     return;
                 }
             }
         } else {
             for batch in &mut self.opaque {
-                if batch.add_rect(
-                    rect,
-                    depth,
-                    color,
-                    Some(coords),
-                    Some(image),
-                    None,
-                    false,
-                ) {
+                if batch.add_rect(rect, depth, color, Some(coords), Some(1), None, false)
+                {
                     return;
                 }
             }
@@ -277,7 +242,7 @@ impl BatchManager {
             depth,
             color,
             Some(coords),
-            Some(image),
+            Some(1),
             None,
             false,
         );
@@ -335,10 +300,8 @@ impl BatchManager {
 /// Resources and commands for drawing a composition.
 #[derive(Default, Debug, Clone)]
 pub struct DisplayList {
-    vertices: Vec<Vertex>,
-    indices: Vec<u32>,
-    indices_to_draw: Vec<(u32, u32)>,
-    commands: Vec<Command>,
+    pub vertices: Vec<Vertex>,
+    pub indices: Vec<u32>,
 }
 
 impl DisplayList {
@@ -348,43 +311,10 @@ impl DisplayList {
         Self::default()
     }
 
-    /// Returns the buffered vertices for the display list.
-    #[inline]
-    pub fn vertices(&self) -> &[Vertex] {
-        &self.vertices
-    }
-
-    /// Returns the buffered indices to draw.
-    #[inline]
-    pub fn indices_to_draw(&self) -> &[(u32, u32)] {
-        &self.indices_to_draw
-    }
-
-    /// Returns the buffered indices for the display list.
-    #[inline]
-    pub fn indices(&self) -> &[u32] {
-        &self.indices
-    }
-
-    /// Returns the sequence of display commands.
-    #[inline]
-    pub fn commands(&self) -> &[Command] {
-        &self.commands
-    }
-
     /// Clears the display list.
     #[inline]
     pub fn clear(&mut self) {
         self.vertices.clear();
         self.indices.clear();
-        self.commands.clear();
-        self.indices_to_draw.clear();
     }
 }
-
-/// Command in a display list.
-#[derive(Copy, Clone, Debug)]
-pub enum Command {
-    /// Bind a texture at the specified slot.
-    BindTexture(u32, TextureId),
-}
diff --git a/sugarloaf/src/components/rich_text/compositor.rs b/sugarloaf/src/components/rich_text/compositor.rs
index 18477d2890..708f2d112c 100644
--- a/sugarloaf/src/components/rich_text/compositor.rs
+++ b/sugarloaf/src/components/rich_text/compositor.rs
@@ -10,35 +10,24 @@
 // text color, underline color and etc.
 
 use crate::components::rich_text::batch::BatchManager;
-pub use crate::components::rich_text::batch::{
-    // Command, DisplayList, Pipeline, Rect, Vertex,
-    Command,
-    DisplayList,
-    Rect,
-    Vertex,
-};
-use crate::components::rich_text::image_cache::glyph::GlyphCacheSession;
-use crate::components::rich_text::image_cache::glyph::GlyphEntry;
+pub use crate::components::rich_text::batch::{DisplayList, Rect, Vertex};
+use crate::components::rich_text::image_cache::glyph::{GlyphCacheSession, GlyphEntry};
 use crate::components::rich_text::image_cache::ImageCache;
-pub use crate::components::rich_text::image_cache::{
-    AddImage,
-    ImageId,
-    ImageLocation,
-    TextureEvent,
-    TextureId,
-    // AddImage, Epoch, ImageData, ImageId, ImageLocation, TextureEvent, TextureId,
-};
+pub use crate::components::rich_text::image_cache::ImageId;
 use crate::components::rich_text::text::*;
 use crate::layout::{FragmentStyleDecoration, Line, SugarDimensions, UnderlineShape};
+use crate::sugarloaf::graphics::GraphicRenderRequest;
+use crate::Graphics;
 use crate::SugarCursor;
+use crate::{Graphic, GraphicId};
 use rustc_hash::FxHashMap;
 use std::borrow::Borrow;
+use std::collections::HashSet;
 
 pub struct ComposedRect {
     coords: [f32; 4],
     color: [f32; 4],
     has_alpha: bool,
-    image: TextureId,
 }
 
 pub enum Instruction {
@@ -70,6 +59,7 @@ pub struct CachedRunUnderline {
 
 pub struct CachedRun {
     pub glyphs_ids: Vec<u16>,
+    pub graphics: HashSet<Graphic>,
     instruction_set: FxHashMap<usize, CachedRunInstructions>,
     instruction_set_callback: Vec<InstructionCallback>,
     underline: CachedRunUnderline,
@@ -82,6 +72,7 @@ impl CachedRun {
             underline: CachedRunUnderline::default(),
             char_width,
             glyphs_ids: Vec::new(),
+            graphics: HashSet::new(),
             instruction_set_callback: Vec::new(),
             instruction_set: FxHashMap::default(),
         }
@@ -110,48 +101,39 @@ impl Compositor {
 
     /// Builds a display list for the current batched geometry and enumerates
     /// all texture events with the specified closure.
-    pub fn finish(
-        &mut self,
-        list: &mut DisplayList,
-        images: &mut ImageCache,
-        events: impl FnMut(TextureEvent),
-    ) {
-        images.drain_events(events);
+    pub fn finish(&mut self, list: &mut DisplayList) {
         self.batches.build_display_list(list);
     }
-}
 
-/// Image management.
-impl Compositor {
-    /// Adds an image to the compositor.
-    #[allow(unused)]
-    pub fn add_image(
-        &mut self,
-        images: &mut ImageCache,
-        request: AddImage,
-    ) -> Option<ImageId> {
-        images.allocate(request)
-    }
-
-    /// Returns the image associated with the specified identifier.
-    #[allow(unused)]
-    pub fn get_image(
-        &mut self,
-        images: &mut ImageCache,
-        image: ImageId,
-    ) -> Option<ImageLocation> {
-        images.get(image)
-    }
+    // Adds an image to the compositor.
+    // pub fn add_image(
+    //     &mut self,
+    //     images: &mut ImageCache,
+    //     graphic: &GraphicData,
+    // ) -> Option<ImageId> {
+    //     images.allocate(AddImage {
+    //         width: graphic.width as u16,
+    //         height: graphic.height as u16,
+    //         has_alpha: graphic.is_opaque,
+    //         data: ImageData::Borrowed(&graphic.pixels),
+    //     })
+    // }
+
+    // Returns the image associated with the specified identifier.
+    // #[allow(unused)]
+    // pub fn get_image(
+    //     &mut self,
+    //     images: &mut ImageCache,
+    //     image: ImageId,
+    // ) -> Option<ImageLocation> {
+    //     images.get(&image)
+    // }
+
+    // Removes the image from the compositor.
+    // pub fn remove_image(&mut self, images: &mut ImageCache, image: ImageId) -> bool {
+    // images.deallocate(image).is_some()
+    // }
 
-    /// Removes the image from the compositor.
-    #[allow(unused)]
-    pub fn remove_image(&mut self, images: &mut ImageCache, image: ImageId) -> bool {
-        images.deallocate(image).is_some()
-    }
-}
-
-/// Drawing.
-impl Compositor {
     /// Draws a rectangle with the specified depth and color.
     #[allow(unused)]
     pub fn draw_rect(&mut self, rect: impl Into<Rect>, depth: f32, color: &[f32; 4]) {
@@ -162,11 +144,11 @@ impl Compositor {
     #[allow(unused)]
     pub fn draw_image(
         &mut self,
-        images: &mut ImageCache,
+        images: &ImageCache,
         rect: impl Into<Rect>,
         depth: f32,
         color: &[f32; 4],
-        image: ImageId,
+        image: &ImageId,
     ) {
         if let Some(img) = images.get(image) {
             self.batches.add_image_rect(
@@ -174,21 +156,40 @@ impl Compositor {
                 depth,
                 color,
                 &[img.min.0, img.min.1, img.max.0, img.max.1],
-                img.texture_id,
                 image.has_alpha(),
             );
         }
     }
 
+    // Draws an image with the specified rectangle, depth and color.
+    // #[inline]
+    // pub fn draw_image_from_data(
+    //     &mut self,
+    //     rect: impl Into<Rect>,
+    //     coords: &[f32; 4],
+    //     has_alpha: bool,
+    // ) {
+    //     self.batches.add_image_rect(
+    //         &rect.into(),
+    //         0.0,
+    //         &[0.0, 0.0, 0.0, 0.0],
+    //         coords,
+    //         has_alpha,
+    //     );
+    // }
+
     #[inline]
+    #[allow(clippy::too_many_arguments)]
     pub fn draw_cached_run(
         &mut self,
         cache_line: &Vec<CachedRun>,
         px: f32,
         py: f32,
         depth: f32,
-        rect: SugarDimensions,
+        rect: &SugarDimensions,
         line: Line,
+        last_rendered_graphic: &mut Option<GraphicId>,
+        graphics: &mut Graphics,
     ) {
         let mut px = px;
         let subpx_bias = (0.125, 0.);
@@ -210,9 +211,21 @@ impl Compositor {
             }
 
             let advance = px - run_x;
-
             let mut index = 0;
 
+            for graphic in &cached_run.graphics {
+                if *last_rendered_graphic != Some(graphic.id) {
+                    graphics.top_layer.push(GraphicRenderRequest {
+                        id: graphic.id,
+                        pos_x: run_x - (graphic.offset_x as f32),
+                        pos_y: topline - (graphic.offset_y as f32),
+                        width: None,
+                        height: None,
+                    });
+                    *last_rendered_graphic = Some(graphic.id);
+                }
+            }
+
             for glyph in &glyphs {
                 if let Some(set) = cached_run.instruction_set.get(&index) {
                     if set.entry.is_none() {
@@ -236,7 +249,6 @@ impl Compositor {
                                     depth,
                                     &data.color,
                                     &data.coords,
-                                    data.image,
                                     data.has_alpha,
                                 );
                             }
@@ -251,7 +263,6 @@ impl Compositor {
                                     depth,
                                     &data.color,
                                     &data.coords,
-                                    data.image,
                                     data.has_alpha,
                                 );
                             }
@@ -360,7 +371,6 @@ impl Compositor {
                             depth,
                             &color,
                             &coords,
-                            img.texture_id,
                             entry.image.has_alpha(),
                         );
                         cached_run_instructions
@@ -368,7 +378,6 @@ impl Compositor {
                             .push(Instruction::Image(ComposedRect {
                                 color,
                                 coords,
-                                image: img.texture_id,
                                 has_alpha: entry.image.has_alpha(),
                             }));
                     } else {
@@ -378,14 +387,12 @@ impl Compositor {
                             depth,
                             &color,
                             &coords,
-                            img.texture_id,
                             true,
                         );
                         cached_run_instructions.instructions.push(Instruction::Mask(
                             ComposedRect {
                                 color,
                                 coords,
-                                image: img.texture_id,
                                 has_alpha: true,
                             },
                         ));
@@ -410,7 +417,7 @@ impl Compositor {
         }
 
         match style.cursor {
-            SugarCursor::Block(cursor_color) => {
+            Some(SugarCursor::Block(cursor_color)) => {
                 self.batches.add_rect(
                     &Rect::new(rect.x, style.topline, rect.width, style.line_height),
                     depth,
@@ -420,7 +427,7 @@ impl Compositor {
                     .instruction_set_callback
                     .push(InstructionCallback::BlockCursor(cursor_color));
             }
-            SugarCursor::Caret(cursor_color) => {
+            Some(SugarCursor::Caret(cursor_color)) => {
                 self.batches.add_rect(
                     &Rect::new(rect.x, style.topline, 3.0, style.line_height),
                     depth,
diff --git a/sugarloaf/src/components/rich_text/image_cache/atlas.rs b/sugarloaf/src/components/rich_text/image_cache/atlas.rs
index 0be613432e..5b783a3c74 100644
--- a/sugarloaf/src/components/rich_text/image_cache/atlas.rs
+++ b/sugarloaf/src/components/rich_text/image_cache/atlas.rs
@@ -21,16 +21,6 @@ impl AtlasAllocator {
         }
     }
 
-    /// Returns the width of the atlas.
-    pub fn width(&self) -> u16 {
-        self.width
-    }
-
-    /// Returns the height of the atlas.
-    // pub fn height(&self) -> u16 {
-    // self.height
-    // }
-
     /// Allocates a rectangle in the atlas if possible. Returns the x and y
     /// coordinates of the allocated slot.
     pub fn allocate(&mut self, width: u16, height: u16) -> Option<(u16, u16)> {
@@ -106,6 +96,7 @@ impl AtlasAllocator {
     }
 
     /// Deallocates the slot with the specified coordinates and width.
+    #[allow(unused)]
     pub fn deallocate(&mut self, x: u16, y: u16, width: u16) -> bool {
         let res = self.deallocate_impl(x, y, width).is_some();
         while self.lines.last().map(|l| l.state) == Some(0) {
@@ -115,6 +106,7 @@ impl AtlasAllocator {
         res
     }
 
+    #[allow(unused)]
     fn deallocate_impl(&mut self, x: u16, y: u16, width: u16) -> Option<()> {
         let (line_index, &line) = if y == 0 {
             self.lines
@@ -140,7 +132,7 @@ impl AtlasAllocator {
             // Otherwise, add a new free slot for the evicted rect and an
             // additional slot for the remaining space if any.
             let slot_index = self.allocate_slot(x, actual_width)?;
-            let remaining = self.width() - offset as u16;
+            let remaining = self.width - offset as u16;
             if remaining != 0 {
                 let remaining_slot_index =
                     self.allocate_slot(offset as u16, remaining)?;
@@ -227,6 +219,7 @@ impl AtlasAllocator {
         Some(line_index)
     }
 
+    #[allow(unused)]
     fn allocate_slot(&mut self, x: u16, width: u16) -> Option<u32> {
         let slot = Slot { x, width, next: !0 };
         if self.free_slot != !0 {
@@ -319,12 +312,6 @@ struct Line {
     state: u32,
 }
 
-// impl Line {
-//     fn is_empty(&self) -> bool {
-//         self.state == 0
-//     }
-// }
-
 enum FreeSlot {
     Direct(u16),
     Node(Option<u32>, u32),
diff --git a/sugarloaf/src/components/rich_text/image_cache/cache.rs b/sugarloaf/src/components/rich_text/image_cache/cache.rs
index 14cb17fd05..f686db78bd 100644
--- a/sugarloaf/src/components/rich_text/image_cache/cache.rs
+++ b/sugarloaf/src/components/rich_text/image_cache/cache.rs
@@ -1,103 +1,131 @@
+use crate::context::Context;
+
 use super::atlas::*;
 use super::*;
 
 #[derive(Default)]
+pub struct Entry {
+    allocated: bool,
+    /// X coordinate of the image in an atlas.
+    x: u16,
+    /// Y coordinate of the image in an atlas.
+    y: u16,
+    /// Width of the image.
+    width: u16,
+    /// Height of the image.
+    height: u16,
+}
+
+pub struct Atlas {
+    alloc: AtlasAllocator,
+    buffer: Vec<u8>,
+    fresh: bool,
+    dirty: bool,
+}
+
 pub struct ImageCache {
-    entries: Vec<Entry>,
-    atlases: Vec<Atlas>,
-    images: Vec<Standalone>,
-    buffered_data: Vec<u8>,
-    events: Vec<Event>,
-    free_entries: u32,
-    free_images: u32,
+    pub entries: Vec<Entry>,
+    atlas: Atlas,
     max_texture_size: u16,
+    texture: wgpu::Texture,
+    pub texture_view: wgpu::TextureView,
 }
 
+#[inline]
+pub fn buffer_size(width: u32, height: u32) -> Option<usize> {
+    (width as usize)
+        .checked_add(height as usize)?
+        .checked_add(4)
+}
+
+pub const SIZE: u16 = 3072;
+
 impl ImageCache {
     /// Creates a new image cache.
-    #[inline]
-    pub fn new(max_texture_size: u16) -> Self {
-        let max_texture_size = max_texture_size.clamp(1024, 4096);
+    pub fn new(context: &Context) -> Self {
+        let device = &context.device;
+        // let max_texture_size = max_texture_size.clamp(1024, 8192);
+        let max_texture_size = SIZE;
+
+        let texture = device.create_texture(&wgpu::TextureDescriptor {
+            label: Some("rich_text create texture"),
+            size: wgpu::Extent3d {
+                width: SIZE as u32,
+                height: SIZE as u32,
+                depth_or_array_layers: 1,
+            },
+            view_formats: &[],
+            dimension: wgpu::TextureDimension::D2,
+            format: wgpu::TextureFormat::Rgba8Unorm,
+            usage: wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::TEXTURE_BINDING,
+            mip_level_count: 1,
+            sample_count: 1,
+        });
+        let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default());
+
+        let alloc = AtlasAllocator::new(max_texture_size, max_texture_size);
+
         Self {
             entries: Vec::new(),
-            atlases: Vec::new(),
-            images: Vec::new(),
-            buffered_data: Vec::new(),
-            events: Vec::new(),
-            free_entries: END_OF_LIST,
-            free_images: END_OF_LIST,
+            atlas: Atlas {
+                alloc,
+                buffer: vec![
+                    0u8;
+                    max_texture_size as usize * max_texture_size as usize * 4
+                ],
+                fresh: true,
+                dirty: true,
+            },
             max_texture_size,
+            texture_view,
+            texture,
         }
     }
 
     /// Allocates a new image and optionally fills it with the specified data.
     pub fn allocate(&mut self, request: AddImage) -> Option<ImageId> {
-        let format = request.format;
         let width = request.width;
         let height = request.height;
-        let _req_data_size = request.format.buffer_size(width as u32, height as u32)?;
-        let use_atlas = width <= self.max_texture_size
-            && height <= (self.max_texture_size / 4)
-            && (format == PixelFormat::Rgba8 || format == PixelFormat::A8);
-        let base_flags = if request.evictable {
-            ENTRY_EVICTABLE
-        } else {
-            0
-        };
-        if !use_atlas {
-            // Simply allocate a new texture.
-            let has_alpha = request.has_alpha;
-            let entry_index = self.alloc_entry()?;
-            let image_index = self.alloc_standalone(request)?;
-            let entry = self.entries.get_mut(entry_index)?;
-            entry.generation = entry.generation.wrapping_add(1);
-            entry.flags = base_flags | ENTRY_ALLOCATED | ENTRY_STANDALONE;
-            entry.owner = image_index as u16;
-            entry.x = 0;
-            entry.y = 0;
-            entry.width = width;
-            entry.height = height;
-            return ImageId::new(entry.generation, entry_index as u32, has_alpha);
-        }
-        let mut atlas_data = self.alloc_from_atlases(format, width, height);
-        if atlas_data.is_none() {
-            atlas_data = self.alloc_from_atlases(format, width, height);
-        }
-        if atlas_data.is_none() {
-            let dim = self.max_texture_size;
-            let atlas_index = self.atlases.len();
-            if atlas_index >= MAX_ATLASES as usize {
-                return None;
-            }
-            let mut alloc = AtlasAllocator::new(dim, dim);
-            if let Some((x, y)) = alloc.allocate(width, height) {
-                let buffer = vec![0u8; dim as usize * dim as usize * 4];
-                let texture_id = TextureId::allocate();
-                self.atlases.push(Atlas {
-                    format,
-                    alloc,
-                    buffer,
-                    fresh: true,
-                    dirty: true,
-                    texture_id,
-                });
-                atlas_data = Some((atlas_index, x, y));
-            } else {
-                return None;
-            }
+
+        // Check buffer size
+        buffer_size(width as u32, height as u32)?;
+
+        // Too big to allocate
+        if !(width <= self.max_texture_size && height <= (self.max_texture_size / 4)) {
+            return None;
         }
-        let (atlas_index, x, y) = atlas_data?;
-        let entry_index = self.alloc_entry()?;
-        let entry = self.entries.get_mut(entry_index)?;
-        entry.generation = entry.generation.wrapping_add(1);
-        entry.flags = base_flags | ENTRY_ALLOCATED;
-        entry.owner = atlas_index as u16;
-        entry.x = x;
-        entry.y = y;
-        entry.width = width;
-        entry.height = height;
+
+        let atlas_data = self.atlas.alloc.allocate(width, height);
+        // if atlas_data.is_none() {
+        // return None;
+        // Grow atlas to fit
+        // self.max_texture_size += SIZE;
+        // self.atlas.fresh = true;
+        // self.atlas.dirty = true;
+        // self.entries.clear();
+        // println!("{:?}", self.max_texture_size);
+        // self.atlas.alloc = AtlasAllocator::new(self.max_texture_size, self.max_texture_size);
+        // self.atlas.buffer = vec![
+        //     0u8;
+        //     self.max_texture_size as usize * self.max_texture_size as usize * 4
+        // ];
+        // atlas_data = self.atlas.alloc.allocate(width, height);
+        // println!("{:?}", atlas_data);
+
+        // if self.max_texture_size > MAX_SIZE {
+        //     println!("should try to grow or reset atlas");
+        // }
+        // }
+        let (x, y) = atlas_data?;
+        let entry_index = self.entries.len();
+        self.entries.push(Entry {
+            allocated: true,
+            x,
+            y,
+            width,
+            height,
+        });
         if let Some(data) = request.data() {
-            let atlas = self.atlases.get_mut(atlas_index)?;
             fill(
                 x,
                 y,
@@ -105,71 +133,49 @@ impl ImageCache {
                 height,
                 data,
                 self.max_texture_size,
-                &mut atlas.buffer,
-                4,
+                &mut self.atlas.buffer,
             );
-            atlas.dirty = true;
+            self.atlas.dirty = true;
         }
-        ImageId::new(entry.generation, entry_index as u32, request.has_alpha)
+        ImageId::new(entry_index as u32, request.has_alpha)
     }
 
     // Evaluate if does make sense to deallocate from atlas and if yes, which case?
     // considering that a terminal uses a short/limited of glyphs compared to a wide text editor
     // if deallocate an image then is necessary to cleanup cache of draw_layout fn
     /// Deallocates the specified image.
+    #[allow(unused)]
     pub fn deallocate(&mut self, image: ImageId) -> Option<()> {
         let entry = self.entries.get_mut(image.index())?;
-        if entry.flags & ENTRY_ALLOCATED == 0 || entry.generation != image.generation() {
+        if !entry.allocated {
             return None;
         }
-        if entry.flags & ENTRY_STANDALONE != 0 {
-            let standalone = self.images.get_mut(entry.owner as usize)?;
-            standalone.next = self.free_images;
-            self.free_images = entry.owner as u32;
-            self.events
-                .push(Event::DestroyTexture(standalone.texture_id));
-        } else {
-            let atlas = self.atlases.get_mut(entry.owner as usize)?;
-            atlas.alloc.deallocate(entry.x, entry.y, entry.width);
-        }
-        entry.flags = 0;
-        self.free_entries = image.index() as u32;
+
+        self.atlas.alloc.deallocate(entry.x, entry.y, entry.width);
+        entry.allocated = false;
         Some(())
     }
 
     /// Retrieves the image for the specified handle and updates the epoch.
-    pub fn get(&mut self, handle: ImageId) -> Option<ImageLocation> {
-        let entry = self.entries.get_mut(handle.index())?;
-        if entry.flags & ENTRY_ALLOCATED == 0 || entry.generation != handle.generation() {
+    pub fn get(&self, handle: &ImageId) -> Option<ImageLocation> {
+        let entry = self.entries.get(handle.index())?;
+        if !entry.allocated {
             return None;
         }
-        Some(if entry.flags & ENTRY_STANDALONE != 0 {
-            let image = self.images.get(entry.owner as usize)?;
-            let texture_id = image.texture_id;
-            ImageLocation {
-                texture_id,
-                min: (0., 0.),
-                max: (1., 1.),
-            }
-        } else {
-            let atlas = self.atlases.get(entry.owner as usize)?;
-            let texture_id = atlas.texture_id;
-            let s = 1. / self.max_texture_size as f32;
-            ImageLocation {
-                texture_id,
-                min: (entry.x as f32 * s, entry.y as f32 * s),
-                max: (
-                    (entry.x + entry.width) as f32 * s,
-                    (entry.y + entry.height) as f32 * s,
-                ),
-            }
+        let s = 1. / self.max_texture_size as f32;
+        Some(ImageLocation {
+            min: (entry.x as f32 * s, entry.y as f32 * s),
+            max: (
+                (entry.x + entry.width) as f32 * s,
+                (entry.y + entry.height) as f32 * s,
+            ),
         })
     }
 
     /// Returns true if the image is valid.
     pub fn is_valid(&self, image: ImageId) -> bool {
         if let Some(entry) = self.entries.get(image.index()) {
-            entry.flags & ENTRY_ALLOCATED != 0 && entry.generation == image.generation()
+            entry.allocated
         } else {
             false
         }
@@ -181,11 +187,6 @@ impl ImageCache {
     //     if entry.flags & ENTRY_ALLOCATED == 0 {
     //         return None;
     //     }
-    //     if entry.flags & ENTRY_STANDALONE != 0 {
-    //         let image = self.images.get(entry.owner as usize)?;
-    //         let texture = image.texture.as_ref()?;
-    //         texture.update(data);
-    //     } else {
     //         let atlas = self.atlases.get_mut(entry.owner as usize)?;
     //         fill(
     //             entry.x,
@@ -198,237 +199,86 @@ impl ImageCache {
     //             4,
     //         );
     //         atlas.dirty = true;
-    //     }
     //     Some(())
     // }
 
-    pub fn drain_events(&mut self, mut f: impl FnMut(TextureEvent)) {
-        for event in self.events.drain(..) {
-            match event {
-                Event::CreateTexture(id, format, width, height, data) => {
-                    let data = match &data {
-                        Some(PendingData::Inline(data)) => data.data(),
-                        Some(PendingData::Buffered(start, end)) => {
-                            self.buffered_data.get(*start..*end)
-                        }
-                        None => None,
-                    };
-                    f(TextureEvent::CreateTexture {
-                        id,
-                        format,
-                        width,
-                        height,
-                        data,
-                    })
-                }
-                Event::UpdateTexture(id, format, region, data) => {
-                    f(TextureEvent::UpdateTexture {
-                        id,
-                        format,
-                        x: region[0],
-                        y: region[1],
-                        width: region[2],
-                        height: region[3],
-                        data: match &data {
-                            Some(PendingData::Inline(data)) => data.data().unwrap_or(&[]),
-                            Some(PendingData::Buffered(start, end)) => {
-                                self.buffered_data.get(*start..*end).unwrap_or(&[])
-                            }
-                            None => &[],
-                        },
-                    })
-                }
-                Event::DestroyTexture(id) => {
-                    f(TextureEvent::DestroyTexture(id));
-                }
-            }
-        }
-        self.buffered_data.clear();
-        for atlas in &mut self.atlases {
-            if !atlas.dirty {
-                continue;
-            }
-            if atlas.fresh {
-                f(TextureEvent::CreateTexture {
-                    id: atlas.texture_id,
-                    format: atlas.format,
-                    width: self.max_texture_size,
-                    height: self.max_texture_size,
-                    data: Some(&atlas.buffer),
-                });
-            } else {
-                f(TextureEvent::UpdateTexture {
-                    id: atlas.texture_id,
-                    format: atlas.format,
-                    x: 0,
-                    y: 0,
-                    width: self.max_texture_size,
-                    height: self.max_texture_size,
-                    data: &atlas.buffer,
-                })
-            }
-            atlas.fresh = false;
-            atlas.dirty = false;
-        }
-    }
-
-    fn alloc_from_atlases(
-        &mut self,
-        format: PixelFormat,
-        width: u16,
-        height: u16,
-    ) -> Option<(usize, u16, u16)> {
-        for (i, atlas) in self.atlases.iter_mut().enumerate() {
-            if atlas.format != format {
-                continue;
-            }
-            if let Some((x, y)) = atlas.alloc.allocate(width, height) {
-                return Some((i, x, y));
-            }
+    #[inline]
+    pub fn process_atlases(&mut self, context: &mut Context) {
+        if !self.atlas.dirty {
+            return;
         }
-        None
-    }
-
-    fn alloc_entry(&mut self) -> Option<usize> {
-        Some(if self.free_entries != END_OF_LIST {
-            self.free_entries as usize
-            // let entry = self.entries.get(index)?;
-            // self.free_entries = entry.epoch as u32;
-        } else {
-            let index = self.entries.len();
-            if index >= MAX_ENTRIES as usize {
-                return None;
-            }
-            self.entries.push(Entry::default());
-            index
-        })
-    }
-
-    fn alloc_standalone(&mut self, request: AddImage) -> Option<usize> {
-        let format = request.format;
-        let width = request.width;
-        let height = request.height;
-        let index = if self.free_images != END_OF_LIST {
-            let index = self.free_images as usize;
-            self.free_images = self.images.get(index)?.next;
-            index
-        } else {
-            let index = self.images.len();
-            self.images.push(Standalone {
-                texture_id: TextureId(0),
-                used: false,
-                next: 0,
+        if self.atlas.fresh {
+            let texture_size = wgpu::Extent3d {
+                width: (self.max_texture_size).into(),
+                height: (self.max_texture_size).into(),
+                depth_or_array_layers: 1,
+            };
+            let new_texture = context.device.create_texture(&wgpu::TextureDescriptor {
+                size: texture_size,
+                mip_level_count: 1,
+                sample_count: 1,
+                dimension: wgpu::TextureDimension::D2,
+                format: wgpu::TextureFormat::Rgba8Unorm,
+                usage: wgpu::TextureUsages::TEXTURE_BINDING
+                    | wgpu::TextureUsages::COPY_DST,
+                label: Some("rich_text::fresh atlas"),
+                view_formats: &[],
             });
-            index
-        };
-        let texture_id = TextureId::allocate();
-        let pending_data = match request.data {
-            // ImageData::None => None,
-            // ImageData::Owned(data) => Some(PendingData::Inline(ImageData::Owned(data))),
-            // ImageData::Shared(data) => Some(PendingData::Inline(ImageData::Shared(data))),
-            ImageData::Borrowed(data) => {
-                let start = self.buffered_data.len();
-                self.buffered_data.extend_from_slice(data);
-                let end = self.buffered_data.len();
-                Some(PendingData::Buffered(start, end))
-            }
-        };
-        let image = self.images.get_mut(index)?;
-        image.texture_id = texture_id;
-        image.used = true;
-        self.events.push(Event::CreateTexture(
-            texture_id,
-            format,
-            width,
-            height,
-            pending_data,
-        ));
-        Some(index)
-    }
-}
-
-#[derive(Default)]
-struct Entry {
-    /// Zero if the entry is free.
-    flags: u8,
-    /// Generation of this entry. Used to detect stale handles.
-    generation: u8,
-    /// Owner of the entry. Index into atlases or images depending
-    /// on the ENTRY_STANDALONE flag.
-    owner: u16,
-    /// X coordinate of the image in an atlas.
-    x: u16,
-    /// Y coordinate of the image in an atlas.
-    y: u16,
-    /// Width of the image.
-    width: u16,
-    /// Height of the image.
-    height: u16,
-}
 
-struct Atlas {
-    format: PixelFormat,
-    alloc: AtlasAllocator,
-    buffer: Vec<u8>,
-    fresh: bool,
-    dirty: bool,
-    texture_id: TextureId,
-}
-
-struct Standalone {
-    texture_id: TextureId,
-    used: bool,
-    next: u32,
-}
-
-#[allow(clippy::enum_variant_names)]
-enum Event {
-    CreateTexture(TextureId, PixelFormat, u16, u16, Option<PendingData>),
-    #[allow(unused)]
-    UpdateTexture(TextureId, PixelFormat, [u16; 4], Option<PendingData>),
-    DestroyTexture(TextureId),
-}
-
-enum PendingData {
-    #[allow(unused)]
-    Inline(ImageData<'static>),
-    Buffered(usize, usize),
-}
-
-#[derive(Default)]
-#[allow(unused)]
-struct DirtyRect {
-    min: (u16, u16),
-    max: (u16, u16),
-    empty: bool,
-}
-
-#[allow(unused)]
-impl DirtyRect {
-    fn new() -> Self {
-        let mut this = Self::default();
-        this.clear();
-        this
-    }
+            context.queue.write_texture(
+                // Tells wgpu where to copy the pixel data
+                wgpu::ImageCopyTexture {
+                    texture: &new_texture,
+                    mip_level: 0,
+                    origin: wgpu::Origin3d::ZERO,
+                    aspect: wgpu::TextureAspect::All,
+                },
+                // The actual pixel data
+                &self.atlas.buffer,
+                // The layout of the texture
+                wgpu::ImageDataLayout {
+                    offset: 0,
+                    bytes_per_row: Some((self.max_texture_size * 4).into()),
+                    rows_per_image: Some((self.max_texture_size).into()),
+                },
+                texture_size,
+            );
 
-    fn clear(&mut self) {
-        self.empty = true;
-        self.min = (u16::MAX, u16::MAX);
-        self.max = (u16::MIN, u16::MIN);
-    }
+            self.texture = new_texture;
+            self.texture_view = self
+                .texture
+                .create_view(&wgpu::TextureViewDescriptor::default());
+        } else {
+            let texture_size = wgpu::Extent3d {
+                width: (self.max_texture_size).into(),
+                height: (self.max_texture_size).into(),
+                depth_or_array_layers: 1,
+            };
 
-    fn add(&mut self, x: u16, y: u16, width: u16, height: u16) {
-        self.empty = false;
-        let xmax = x + width;
-        let ymax = y + height;
-        self.min.0 = self.min.0.min(x);
-        self.min.1 = self.min.1.min(y);
-        self.max.0 = self.max.0.max(xmax);
-        self.max.1 = self.max.1.max(ymax);
+            context.queue.write_texture(
+                // Tells wgpu where to copy the pixel data
+                wgpu::ImageCopyTexture {
+                    texture: &self.texture,
+                    mip_level: 0,
+                    origin: wgpu::Origin3d { x: 0, y: 0, z: 0 },
+                    aspect: wgpu::TextureAspect::All,
+                },
+                // The actual pixel data
+                &self.atlas.buffer,
+                // The layout of the texture
+                wgpu::ImageDataLayout {
+                    offset: 0,
+                    bytes_per_row: Some((self.max_texture_size * 4).into()),
+                    rows_per_image: Some((self.max_texture_size).into()),
+                },
+                texture_size,
+            );
+        }
+        self.atlas.fresh = false;
+        self.atlas.dirty = false;
     }
 }
 
-#[allow(clippy::too_many_arguments)]
 fn fill(
     x: u16,
     y: u16,
@@ -437,9 +287,8 @@ fn fill(
     image: &[u8],
     target_width: u16,
     target: &mut [u8],
-    channels: u16,
 ) -> Option<()> {
-    let channels = channels as usize;
+    let channels = 4;
     let image_pitch = width as usize * channels;
     let buffer_pitch = target_width as usize * channels;
     let mut offset = y as usize * buffer_pitch + x as usize * channels;
diff --git a/sugarloaf/src/components/rich_text/image_cache/glyph.rs b/sugarloaf/src/components/rich_text/image_cache/glyph.rs
index 1c1865f803..45d185d861 100644
--- a/sugarloaf/src/components/rich_text/image_cache/glyph.rs
+++ b/sugarloaf/src/components/rich_text/image_cache/glyph.rs
@@ -1,5 +1,4 @@
 use super::cache::ImageCache;
-use super::PixelFormat;
 use super::{AddImage, ImageData, ImageId, ImageLocation};
 use core::borrow::Borrow;
 use core::hash::{Hash, Hasher};
@@ -108,7 +107,7 @@ pub struct GlyphCacheSession<'a> {
 
 impl<'a> GlyphCacheSession<'a> {
     pub fn get_image(&mut self, image: ImageId) -> Option<ImageLocation> {
-        self.images.get(image)
+        self.images.get(&image)
     }
 
     #[inline]
@@ -144,11 +143,9 @@ impl<'a> GlyphCacheSession<'a> {
             let w = p.width as u16;
             let h = p.height as u16;
             let req = AddImage {
-                format: PixelFormat::Rgba8,
                 width: w,
                 height: h,
                 has_alpha: true,
-                evictable: true,
                 data: ImageData::Borrowed(&self.scaled_image.data),
             };
             let image = self.images.allocate(req)?;
@@ -160,6 +157,7 @@ impl<'a> GlyphCacheSession<'a> {
                 image,
                 is_bitmap: self.scaled_image.content == Content::Color,
             };
+
             self.entry.glyphs.insert(key, entry);
             return Some(entry);
         }
diff --git a/sugarloaf/src/components/rich_text/image_cache/mod.rs b/sugarloaf/src/components/rich_text/image_cache/mod.rs
index eecd631b27..21d45b1abd 100644
--- a/sugarloaf/src/components/rich_text/image_cache/mod.rs
+++ b/sugarloaf/src/components/rich_text/image_cache/mod.rs
@@ -2,61 +2,27 @@ mod atlas;
 mod cache;
 pub mod glyph;
 
-use std::sync::atomic::{AtomicU64, Ordering};
-
-#[derive(Copy, Clone, PartialEq, Eq, Debug)]
-pub enum PixelFormat {
-    A8,
-    Rgba8,
-}
-
-impl PixelFormat {
-    pub fn buffer_size(&self, width: u32, height: u32) -> Option<usize> {
-        let mult = match self {
-            Self::A8 => 1,
-            Self::Rgba8 => 4,
-        };
-        (width as usize)
-            .checked_add(height as usize)?
-            .checked_add(mult)
-    }
-}
+use std::sync::Arc;
 
 pub use cache::ImageCache;
 pub use glyph::GlyphCache;
 
-/// Identifier for a texture in GPU memory.
-#[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Hash, Debug)]
-pub struct TextureId(pub u64);
-
-impl TextureId {
-    fn allocate() -> Self {
-        static COUNTER: AtomicU64 = AtomicU64::new(1);
-        Self(COUNTER.fetch_add(1, Ordering::Relaxed))
-    }
-}
-
 /// Identifier for an image in a cache.
 #[derive(Copy, Clone, PartialEq, Eq, Debug)]
 pub struct ImageId(u32);
 
 impl ImageId {
-    fn new(generation: u8, index: u32, alpha: bool) -> Option<Self> {
+    fn new(index: u32, alpha: bool) -> Option<Self> {
         if index & ID_INDEX_MASK != index {
             return None;
         }
         let mut handle = index & ID_INDEX_MASK;
-        handle |= (generation as u32) << 24;
         if alpha {
             handle |= ID_ALPHA_BIT
         }
         Some(Self(handle))
     }
 
-    fn generation(self) -> u8 {
-        (self.0 >> 24) as u8
-    }
-
     fn index(self) -> usize {
         (self.0 & ID_INDEX_MASK) as usize
     }
@@ -70,8 +36,6 @@ impl ImageId {
 /// Location of an image in a texture.
 #[derive(Copy, Clone)]
 pub struct ImageLocation {
-    /// Texture that contains the image.
-    pub texture_id: TextureId,
     /// Minimum x and y texture coordinates.
     pub min: (f32, f32),
     /// Maximum x and y texture coordinates.
@@ -79,18 +43,14 @@ pub struct ImageLocation {
 }
 
 /// Data describing a request for caching an image.
-#[derive(Clone, Copy)]
+#[derive(Clone)]
 pub struct AddImage<'a> {
-    /// Format of the image data.
-    pub format: PixelFormat,
     /// Width of the image.
     pub width: u16,
     /// Height of the image.
     pub height: u16,
     /// True if the image makes use of an alpha channel.
     pub has_alpha: bool,
-    /// True if the cache can evict this image.
-    pub evictable: bool,
     /// The actual image data.
     pub data: ImageData<'a>,
 }
@@ -102,12 +62,14 @@ impl<'a> AddImage<'a> {
 }
 
 /// Representations of image data for submission to a cache.
-#[derive(Clone, Copy)]
+#[derive(Clone)]
 pub enum ImageData<'a> {
     // None,
     Borrowed(&'a [u8]),
-    // Owned(Vec<u8>),
-    // Shared(Arc<Vec<u8>>),
+    #[allow(unused)]
+    Owned(Vec<u8>),
+    #[allow(unused)]
+    Shared(Arc<Vec<u8>>),
 }
 
 impl<'a> ImageData<'a> {
@@ -116,50 +78,11 @@ impl<'a> ImageData<'a> {
             // Self::None => return None,
             // Self::Borrowed(data) => *data,
             Self::Borrowed(data) => data,
-            // Self::Owned(data) => data,
-            // Self::Shared(data) => &*data,
+            Self::Owned(data) => data,
+            Self::Shared(data) => data,
         })
     }
 }
 
-/// Event that describes a change in an image cache.
-#[derive(Copy, Clone)]
-#[allow(clippy::enum_variant_names)]
-pub enum TextureEvent<'a> {
-    /// Texture creation event.
-    CreateTexture {
-        id: TextureId,
-        format: PixelFormat,
-        width: u16,
-        height: u16,
-        data: Option<&'a [u8]>,
-    },
-    /// Texture update event.
-    UpdateTexture {
-        id: TextureId,
-        format: PixelFormat,
-        x: u16,
-        y: u16,
-        width: u16,
-        height: u16,
-        data: &'a [u8],
-    },
-    /// Texture destruction event.
-    DestroyTexture(TextureId),
-}
-
-/// Limit on number of atlases before image allocation fails.
-const MAX_ATLASES: u16 = 256;
-
-/// Limit on total number of images.
-const MAX_ENTRIES: u32 = 0x007FFFFF;
-
-/// Sentinel for end of free list.
-const END_OF_LIST: u32 = !0;
-
-const ID_INDEX_MASK: u32 = MAX_ENTRIES;
+const ID_INDEX_MASK: u32 = 0x007FFFFF;
 const ID_ALPHA_BIT: u32 = 0x00800000;
-
-const ENTRY_ALLOCATED: u8 = 1;
-const ENTRY_STANDALONE: u8 = 2;
-const ENTRY_EVICTABLE: u8 = 4;
diff --git a/sugarloaf/src/components/rich_text/mod.rs b/sugarloaf/src/components/rich_text/mod.rs
index 7a61f07140..366b4e8e6f 100644
--- a/sugarloaf/src/components/rich_text/mod.rs
+++ b/sugarloaf/src/components/rich_text/mod.rs
@@ -9,14 +9,13 @@ use crate::components::rich_text::image_cache::{GlyphCache, ImageCache};
 use crate::context::Context;
 use crate::font::FontLibraryData;
 use crate::layout::SugarDimensions;
-use compositor::{
-    CachedRun, Command, Compositor, DisplayList, Rect, TextureEvent, TextureId, Vertex,
-};
+use crate::sugarloaf::graphics::GraphicRenderRequest;
+use crate::Graphics;
+use compositor::{CachedRun, Compositor, DisplayList, Rect, Vertex};
 use rustc_hash::FxHashMap;
 use std::{borrow::Cow, mem};
 use text::{Glyph, TextRunStyle};
 use wgpu::util::DeviceExt;
-use wgpu::Texture;
 
 // Note: currently it's using Indexed drawing instead of Instance drawing could be worth to
 // evaluate if would make sense move to instance drawing instead
@@ -38,23 +37,19 @@ pub const BLEND: Option<wgpu::BlendState> = Some(wgpu::BlendState {
 
 pub struct RichTextBrush {
     vertex_buffer: wgpu::Buffer,
-    bind_group: wgpu::BindGroup,
-    sampler: wgpu::Sampler,
-    color_texture_view: wgpu::TextureView,
-    mask_texture_view: wgpu::TextureView,
+    constant_bind_group: wgpu::BindGroup,
+    layout_bind_group: wgpu::BindGroup,
+    layout_bind_group_layout: wgpu::BindGroupLayout,
     transform: wgpu::Buffer,
-    bind_group_layout: wgpu::BindGroupLayout,
     pipeline: wgpu::RenderPipeline,
-    textures: FxHashMap<TextureId, Texture>,
     index_buffer: wgpu::Buffer,
     index_buffer_size: u64,
     current_transform: [f32; 16],
     comp: Compositor,
     draw_layout_cache: DrawLayoutCache,
     dlist: DisplayList,
-    bind_group_needs_update: bool,
-    first_run: bool,
     supported_vertex_buffer: usize,
+    textures_version: usize,
     images: ImageCache,
     glyphs: GlyphCache,
 }
@@ -74,7 +69,7 @@ impl RichTextBrush {
         });
 
         // Create pipeline layout
-        let bind_group_layout =
+        let constant_bind_group_layout =
             device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
                 label: None,
                 entries: &[
@@ -94,32 +89,6 @@ impl RichTextBrush {
                     },
                     wgpu::BindGroupLayoutEntry {
                         binding: 1,
-                        visibility: wgpu::ShaderStages::VERTEX
-                            | wgpu::ShaderStages::FRAGMENT,
-                        ty: wgpu::BindingType::Texture {
-                            multisampled: false,
-                            view_dimension: wgpu::TextureViewDimension::D2,
-                            sample_type: wgpu::TextureSampleType::Float {
-                                filterable: true,
-                            },
-                        },
-                        count: None,
-                    },
-                    wgpu::BindGroupLayoutEntry {
-                        binding: 2,
-                        visibility: wgpu::ShaderStages::VERTEX
-                            | wgpu::ShaderStages::FRAGMENT,
-                        ty: wgpu::BindingType::Texture {
-                            sample_type: wgpu::TextureSampleType::Float {
-                                filterable: true,
-                            },
-                            view_dimension: wgpu::TextureViewDimension::D2,
-                            multisampled: false,
-                        },
-                        count: None,
-                    },
-                    wgpu::BindGroupLayoutEntry {
-                        binding: 3,
                         visibility: wgpu::ShaderStages::VERTEX
                             | wgpu::ShaderStages::FRAGMENT,
                         ty: wgpu::BindingType::Sampler(
@@ -130,53 +99,38 @@ impl RichTextBrush {
                 ],
             });
 
+        let layout_bind_group_layout =
+            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
+                label: None,
+                entries: &[wgpu::BindGroupLayoutEntry {
+                    binding: 0,
+                    visibility: wgpu::ShaderStages::FRAGMENT,
+                    ty: wgpu::BindingType::Texture {
+                        sample_type: wgpu::TextureSampleType::Float { filterable: true },
+                        view_dimension: wgpu::TextureViewDimension::D2,
+                        multisampled: false,
+                    },
+                    count: None,
+                }],
+            });
+
         let pipeline_layout =
             device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
                 label: None,
                 push_constant_ranges: &[],
-                bind_group_layouts: &[&bind_group_layout],
+                bind_group_layouts: &[
+                    &constant_bind_group_layout,
+                    &layout_bind_group_layout,
+                ],
             });
 
-        let color_texture = device.create_texture(&wgpu::TextureDescriptor {
-            label: Some("rich_text create color_texture"),
-            size: wgpu::Extent3d {
-                width: context.size.width as u32,
-                height: context.size.height as u32,
-                depth_or_array_layers: 1,
-            },
-            view_formats: &[],
-            dimension: wgpu::TextureDimension::D2,
-            format: wgpu::TextureFormat::Rgba8Unorm,
-            usage: wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::TEXTURE_BINDING,
-            mip_level_count: 1,
-            sample_count: 1,
-        });
-        let color_texture_view =
-            color_texture.create_view(&wgpu::TextureViewDescriptor::default());
-
-        let mask_texture = device.create_texture(&wgpu::TextureDescriptor {
-            label: Some("rich_text create mask_texture"),
-            size: wgpu::Extent3d {
-                width: context.size.width as u32,
-                height: context.size.height as u32,
-                depth_or_array_layers: 1,
-            },
-            view_formats: &[],
-            dimension: wgpu::TextureDimension::D2,
-            format: wgpu::TextureFormat::R8Unorm,
-            usage: wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::TEXTURE_BINDING,
-            mip_level_count: 1,
-            sample_count: 1,
-        });
-        let mask_texture_view =
-            mask_texture.create_view(&wgpu::TextureViewDescriptor::default());
+        let images = ImageCache::new(context);
 
         let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
             address_mode_u: wgpu::AddressMode::ClampToEdge,
             address_mode_v: wgpu::AddressMode::ClampToEdge,
             address_mode_w: wgpu::AddressMode::ClampToEdge,
-            // mag_filter: wgpu::FilterMode::Nearest,
-            mag_filter: wgpu::FilterMode::Linear,
+            mag_filter: wgpu::FilterMode::Nearest,
             min_filter: wgpu::FilterMode::Nearest,
             mipmap_filter: wgpu::FilterMode::Nearest,
             lod_min_clamp: 0f32,
@@ -184,9 +138,8 @@ impl RichTextBrush {
             ..Default::default()
         });
 
-        // Create bind group
-        let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
-            layout: &bind_group_layout,
+        let constant_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
+            layout: &constant_bind_group_layout,
             entries: &[
                 wgpu::BindGroupEntry {
                     binding: 0,
@@ -198,18 +151,19 @@ impl RichTextBrush {
                 },
                 wgpu::BindGroupEntry {
                     binding: 1,
-                    resource: wgpu::BindingResource::TextureView(&color_texture_view),
-                },
-                wgpu::BindGroupEntry {
-                    binding: 2,
-                    resource: wgpu::BindingResource::TextureView(&mask_texture_view),
-                },
-                wgpu::BindGroupEntry {
-                    binding: 3,
                     resource: wgpu::BindingResource::Sampler(&sampler),
                 },
             ],
-            label: Some("rich_text::Pipeline uniforms"),
+            label: Some("rich_text::constant_bind_group"),
+        });
+
+        let layout_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
+            layout: &layout_bind_group_layout,
+            entries: &[wgpu::BindGroupEntry {
+                binding: 0,
+                resource: wgpu::BindingResource::TextureView(&images.texture_view),
+            }],
+            label: Some("rich_text::layout_bind_group"),
         });
 
         let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
@@ -232,9 +186,10 @@ impl RichTextBrush {
                     // https://docs.rs/wgpu/latest/wgpu/enum.VertexStepMode.html
                     step_mode: wgpu::VertexStepMode::Vertex,
                     attributes: &wgpu::vertex_attr_array!(
-                        0 => Float32x4,
+                        0 => Float32x3,
                         1 => Float32x4,
                         2 => Float32x2,
+                        3 => Sint32x2,
                     ),
                 }],
             },
@@ -248,7 +203,15 @@ impl RichTextBrush {
                     write_mask: wgpu::ColorWrites::ALL,
                 })],
             }),
-            primitive: wgpu::PrimitiveState::default(),
+            primitive: wgpu::PrimitiveState {
+                topology: wgpu::PrimitiveTopology::TriangleList,
+                strip_index_format: None,
+                front_face: wgpu::FrontFace::Ccw,
+                cull_mode: None,
+                polygon_mode: wgpu::PolygonMode::Fill,
+                unclipped_depth: false,
+                conservative: false,
+            },
             depth_stencil: None,
             multisample: wgpu::MultisampleState::default(),
             multiview: None,
@@ -261,7 +224,7 @@ impl RichTextBrush {
             mapped_at_creation: false,
         });
 
-        let index_buffer_size: &[u32] = bytemuck::cast_slice(dlist.indices());
+        let index_buffer_size: &[u32] = bytemuck::cast_slice(&dlist.indices);
         let index_buffer_size = index_buffer_size.len() as u64;
         let index_buffer = device.create_buffer(&wgpu::BufferDescriptor {
             label: Some("rich_text::Indices Buffer"),
@@ -271,24 +234,20 @@ impl RichTextBrush {
         });
 
         RichTextBrush {
-            bind_group_layout,
+            layout_bind_group,
+            layout_bind_group_layout,
+            constant_bind_group,
             index_buffer_size,
             index_buffer,
-            color_texture_view,
-            mask_texture_view,
-            sampler,
-            textures: FxHashMap::default(),
             comp: Compositor::new(),
-            images: ImageCache::new(2048),
+            images,
+            textures_version: 0,
             glyphs: GlyphCache::new(),
             draw_layout_cache: DrawLayoutCache::default(),
             dlist,
-            bind_group,
             transform,
             pipeline,
             vertex_buffer,
-            first_run: true,
-            bind_group_needs_update: true,
             supported_vertex_buffer,
             current_transform,
         }
@@ -302,8 +261,9 @@ impl RichTextBrush {
     #[inline]
     pub fn prepare(
         &mut self,
-        ctx: &mut Context,
+        context: &mut crate::context::Context,
         state: &crate::sugarloaf::state::SugarState,
+        graphics: &mut Graphics,
     ) {
         // let start = std::time::Instant::now();
 
@@ -328,14 +288,16 @@ impl RichTextBrush {
             &state.compositors.advanced.render_data,
             state.current.layout.style.screen_position,
             font_library,
-            state.current.layout.dimensions,
+            &state.current.layout.dimensions,
+            graphics,
         );
         self.draw_layout_cache.clear_on_demand();
-        // let duration = start.elapsed();
-        // println!(" - rich_text::prepare::draw_layout() is: {:?}", duration);
 
         self.dlist.clear();
-        self.finish_composition(ctx);
+        self.images.process_atlases(context);
+        self.comp.finish(&mut self.dlist);
+        // let duration = start.elapsed();
+        // println!(" - rich_text::prepare::draw_layout() is: {:?}", duration);
 
         // let duration = start.elapsed();
         // println!(" - rich_text::prepare() is: {:?}", duration);
@@ -372,11 +334,8 @@ impl RichTextBrush {
         rpass: &mut wgpu::RenderPass<'pass>,
     ) {
         // let start = std::time::Instant::now();
-        let vertices: &[Vertex] = self.dlist.vertices();
-        let indices: &[u32] = self.dlist.indices();
-
         // There's nothing to render
-        if vertices.is_empty() {
+        if self.dlist.vertices.is_empty() {
             return;
         }
 
@@ -393,10 +352,10 @@ impl RichTextBrush {
             self.current_transform = transform;
         }
 
-        if vertices.len() > self.supported_vertex_buffer {
+        if self.dlist.vertices.len() > self.supported_vertex_buffer {
             self.vertex_buffer.destroy();
 
-            self.supported_vertex_buffer = vertices.len();
+            self.supported_vertex_buffer = self.dlist.vertices.len();
             self.vertex_buffer = ctx.device.create_buffer(&wgpu::BufferDescriptor {
                 label: Some("sugarloaf::rich_text::Pipeline instances"),
                 size: mem::size_of::<Vertex>() as u64
@@ -406,12 +365,12 @@ impl RichTextBrush {
             });
         }
 
-        let vertices_bytes: &[u8] = bytemuck::cast_slice(vertices);
+        let vertices_bytes: &[u8] = bytemuck::cast_slice(&self.dlist.vertices);
         if !vertices_bytes.is_empty() {
             queue.write_buffer(&self.vertex_buffer, 0, vertices_bytes);
         }
 
-        let indices_raw: &[u8] = bytemuck::cast_slice(indices);
+        let indices_raw: &[u8] = bytemuck::cast_slice(&self.dlist.indices);
         let indices_raw_size = indices_raw.len() as u64;
 
         if self.index_buffer_size >= indices_raw_size {
@@ -434,246 +393,31 @@ impl RichTextBrush {
             self.index_buffer_size = size;
         }
 
-        let mut color_texture_updated: Option<&TextureId> = None;
-        let mut mask_texture_updated: Option<&TextureId> = None;
-
-        for command in self.dlist.commands() {
-            match command {
-                Command::BindTexture(unit, id) => {
-                    if self.bind_group_needs_update {
-                        match unit {
-                            // color_texture
-                            0 => {
-                                // if color_texture_updated.is_none() {
-                                if let Some(texture) = self.textures.get(id) {
-                                    log::info!("rich_text::BindTexture, set color_texture_view {:?} {:?}", unit, id);
-                                    self.color_texture_view = texture.create_view(
-                                        &wgpu::TextureViewDescriptor::default(),
-                                    );
-                                    color_texture_updated = Some(id);
-                                }
-                                // }
-                            }
-                            // mask_texture
-                            1 => {
-                                // if mask_texture_updated.is_none() {
-                                if let Some(texture) = self.textures.get(id) {
-                                    log::info!("rich_text::BindTexture, set mask_texture_view {:?} {:?}", unit, id);
-                                    self.mask_texture_view = texture.create_view(
-                                        &wgpu::TextureViewDescriptor::default(),
-                                    );
-                                    mask_texture_updated = Some(id);
-                                }
-                                // }
-                            }
-                            _ => {
-                                // Noop
-                            }
-                        }
-                    };
-                }
-            }
-        }
-
-        // Ensure texture views are not empty in the first run
-        if self.first_run && mask_texture_updated.is_none() {
-            if let Some(texture) = self
-                .textures
-                .get(color_texture_updated.unwrap_or(&TextureId(0)))
-            {
-                self.mask_texture_view =
-                    texture.create_view(&wgpu::TextureViewDescriptor::default());
-            }
-        }
-        if self.first_run && color_texture_updated.is_none() {
-            if let Some(texture) = self
-                .textures
-                .get(mask_texture_updated.unwrap_or(&TextureId(0)))
-            {
-                self.color_texture_view =
-                    texture.create_view(&wgpu::TextureViewDescriptor::default());
-            }
-        }
-
-        if self.bind_group_needs_update {
-            self.bind_group = ctx.device.create_bind_group(&wgpu::BindGroupDescriptor {
-                layout: &self.bind_group_layout,
-                entries: &[
-                    wgpu::BindGroupEntry {
+        if self.textures_version != self.images.entries.len() {
+            self.textures_version = self.images.entries.len();
+            self.layout_bind_group =
+                ctx.device.create_bind_group(&wgpu::BindGroupDescriptor {
+                    layout: &self.layout_bind_group_layout,
+                    entries: &[wgpu::BindGroupEntry {
                         binding: 0,
-                        resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
-                            buffer: &self.transform,
-                            offset: 0,
-                            size: None,
-                        }),
-                    },
-                    wgpu::BindGroupEntry {
-                        binding: 1,
                         resource: wgpu::BindingResource::TextureView(
-                            &self.color_texture_view,
+                            &self.images.texture_view,
                         ),
-                    },
-                    wgpu::BindGroupEntry {
-                        binding: 2,
-                        resource: wgpu::BindingResource::TextureView(
-                            &self.mask_texture_view,
-                        ),
-                    },
-                    wgpu::BindGroupEntry {
-                        binding: 3,
-                        resource: wgpu::BindingResource::Sampler(&self.sampler),
-                    },
-                ],
-                label: Some("rich_text::Pipeline uniforms"),
-            });
+                    }],
+                    label: Some("rich_text::Pipeline uniforms"),
+                });
         }
 
         rpass.set_pipeline(&self.pipeline);
-        rpass.set_bind_group(0, &self.bind_group, &[]);
+        rpass.set_bind_group(0, &self.constant_bind_group, &[]);
+        rpass.set_bind_group(1, &self.layout_bind_group, &[]);
         rpass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
         rpass.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint32);
-
-        // Draw the specified range of indexed triangles.
-        for items in self.dlist.indices_to_draw() {
-            rpass.draw_indexed(items.0..items.1, 0, 0..1);
-        }
-
-        self.bind_group_needs_update = false;
-        self.first_run = false;
+        rpass.draw_indexed(0..(self.dlist.indices.len() as u32), 0, 0..1);
 
         // let duration = start.elapsed();
         // println!(" - rich_text::render() is: {:?}", duration);
     }
-
-    #[inline]
-    fn finish_composition(&mut self, ctx: &mut Context) {
-        self.comp
-            .finish(&mut self.dlist, &mut self.images, |event| {
-                match event {
-                    TextureEvent::CreateTexture {
-                        id,
-                        format,
-                        width,
-                        height,
-                        data,
-                    } => {
-                        log::info!(
-                            "rich_text::CreateTexture with id ({:?}) and format {:?}",
-                            id,
-                            format
-                        );
-                        let texture_size = wgpu::Extent3d {
-                            width: width.into(),
-                            height: height.into(),
-                            depth_or_array_layers: 1,
-                        };
-                        let texture =
-                            ctx.device.create_texture(&wgpu::TextureDescriptor {
-                                size: texture_size,
-                                mip_level_count: 1,
-                                sample_count: 1,
-                                dimension: wgpu::TextureDimension::D2,
-                                format: match format {
-                                    image_cache::PixelFormat::A8 => {
-                                        wgpu::TextureFormat::R8Unorm
-                                    }
-                                    image_cache::PixelFormat::Rgba8 => {
-                                        wgpu::TextureFormat::Rgba8Unorm
-                                    }
-                                },
-                                usage: wgpu::TextureUsages::TEXTURE_BINDING
-                                    | wgpu::TextureUsages::COPY_DST,
-                                label: Some("rich_text::Cache"),
-                                view_formats: &[],
-                            });
-
-                        if let Some(data) = data {
-                            self.bind_group_needs_update = true;
-                            let channels = match format {
-                                // Mask
-                                image_cache::PixelFormat::A8 => 1,
-                                // Color
-                                image_cache::PixelFormat::Rgba8 => 4,
-                            };
-
-                            ctx.queue.write_texture(
-                                // Tells wgpu where to copy the pixel data
-                                wgpu::ImageCopyTexture {
-                                    texture: &texture,
-                                    mip_level: 0,
-                                    origin: wgpu::Origin3d::ZERO,
-                                    aspect: wgpu::TextureAspect::All,
-                                },
-                                // The actual pixel data
-                                data,
-                                // The layout of the texture
-                                wgpu::ImageDataLayout {
-                                    offset: 0,
-                                    bytes_per_row: Some((width * channels).into()),
-                                    rows_per_image: Some(height.into()),
-                                },
-                                texture_size,
-                            );
-                        }
-
-                        self.textures.insert(id, texture);
-                    }
-                    TextureEvent::UpdateTexture {
-                        id,
-                        format,
-                        x,
-                        y,
-                        width,
-                        height,
-                        data,
-                    } => {
-                        log::info!("rich_text::UpdateTexture id ({:?})", id);
-                        if let Some(texture) = self.textures.get(&id) {
-                            self.bind_group_needs_update = true;
-                            let texture_size = wgpu::Extent3d {
-                                width: width.into(),
-                                height: height.into(),
-                                depth_or_array_layers: 1,
-                            };
-
-                            let channels = match format {
-                                // Mask
-                                image_cache::PixelFormat::A8 => 1,
-                                // Color
-                                image_cache::PixelFormat::Rgba8 => 4,
-                            };
-
-                            ctx.queue.write_texture(
-                                // Tells wgpu where to copy the pixel data
-                                wgpu::ImageCopyTexture {
-                                    texture,
-                                    mip_level: 0,
-                                    origin: wgpu::Origin3d {
-                                        x: u32::from(x),
-                                        y: u32::from(y),
-                                        z: 0,
-                                    },
-                                    aspect: wgpu::TextureAspect::All,
-                                },
-                                // The actual pixel data
-                                data,
-                                // The layout of the texture
-                                wgpu::ImageDataLayout {
-                                    offset: 0,
-                                    bytes_per_row: Some((width * channels).into()),
-                                    rows_per_image: Some(height.into()),
-                                },
-                                texture_size,
-                            );
-                        }
-                    }
-                    TextureEvent::DestroyTexture(id) => {
-                        log::info!("rich_text::DestroyTexture id ({:?})", id);
-                        self.textures.remove(&id);
-                    }
-                }
-            });
-    }
 }
 
 #[derive(Default)]
@@ -694,7 +438,7 @@ impl DrawLayoutCache {
 
     #[inline]
     fn clear_on_demand(&mut self) {
-        if self.inner.len() > 1024 {
+        if self.inner.len() > 128 {
             self.inner.clear();
         }
     }
@@ -705,14 +449,14 @@ impl DrawLayoutCache {
     }
 }
 
-#[inline]
 fn draw_layout(
     comp: &mut compositor::Compositor,
     caches: (&mut ImageCache, &mut GlyphCache, &mut DrawLayoutCache),
     render_data: &crate::layout::RenderData,
     pos: (f32, f32),
     font_library: &FontLibraryData,
-    rect: SugarDimensions,
+    rect: &SugarDimensions,
+    graphics: &mut Graphics,
 ) {
     // let start = std::time::Instant::now();
     let (x, y) = pos;
@@ -721,28 +465,38 @@ fn draw_layout(
     let mut glyphs = Vec::new();
     let mut current_font = 0;
     let mut current_font_size = 0.0;
-    let mut current_font_coords: Vec<i16> = Vec::with_capacity(0);
+    let mut current_font_coords: &[i16] = &[0, 0, 0, 0];
     if let Some(line) = render_data.lines().next() {
         if let Some(run) = line.runs().next() {
             current_font = *run.font();
             current_font_size = run.font_size();
-            current_font_coords = run.normalized_coords().to_vec();
+            current_font_coords = run.normalized_coords();
         }
     }
 
     let mut session = glyphs_cache.session(
         image_cache,
         font_library[current_font].as_ref(),
-        &current_font_coords,
+        current_font_coords,
         current_font_size,
     );
 
+    let mut last_rendered_graphic = None;
     for line in render_data.lines() {
         let hash = line.hash();
         let mut px = x + line.offset();
         let py = line.baseline() + y;
         if let Some(data) = draw_layout_cache.get(&hash) {
-            comp.draw_cached_run(data, px, py, depth, rect, line);
+            comp.draw_cached_run(
+                data,
+                px,
+                py,
+                depth,
+                rect,
+                line,
+                &mut last_rendered_graphic,
+                graphics,
+            );
             continue;
         }
 
@@ -795,10 +549,28 @@ fn draw_layout(
                 );
 
                 current_font = font;
-                current_font_coords = style.font_coords.to_vec();
+                current_font_coords = style.font_coords;
                 current_font_size = style.font_size;
             }
 
+            if let Some(graphic) = run.media() {
+                if last_rendered_graphic != Some(graphic.id) {
+                    let offset_x = graphic.offset_x as f32;
+                    let offset_y = graphic.offset_y as f32;
+
+                    graphics.top_layer.push(GraphicRenderRequest {
+                        id: graphic.id,
+                        pos_x: run_x - offset_x,
+                        pos_y: style.topline - offset_y,
+                        width: None,
+                        height: None,
+                    });
+                    last_rendered_graphic = Some(graphic.id);
+                }
+
+                cached_run.graphics.insert(graphic);
+            }
+
             comp.draw_run(
                 &mut session,
                 Rect::new(run_x, py, style.advance, 1.),
@@ -833,7 +605,7 @@ fn fetch_dimensions(
     let (image_cache, glyphs_cache) = caches;
     let mut current_font = 0;
     let mut current_font_size = 0.0;
-    let mut current_font_coords: Vec<i16> = Vec::with_capacity(0);
+    let mut current_font_coords: Vec<i16> = Vec::with_capacity(4);
     if let Some(line) = render_data.lines().next() {
         if let Some(run) = line.runs().next() {
             current_font = *run.font();
diff --git a/sugarloaf/src/components/rich_text/rich_text.wgsl b/sugarloaf/src/components/rich_text/rich_text.wgsl
index 78ebc8301e..c06f07dccf 100644
--- a/sugarloaf/src/components/rich_text/rich_text.wgsl
+++ b/sugarloaf/src/components/rich_text/rich_text.wgsl
@@ -3,23 +3,23 @@ struct Globals {
 }
 
 @group(0) @binding(0) var<uniform> globals: Globals;
-@group(0) @binding(1) var font_color_tex: texture_2d<f32>;
-@group(0) @binding(2) var font_mask_tex: texture_2d<f32>;
-@group(0) @binding(3) var font_sampler: sampler;
+@group(0) @binding(1) var font_sampler: sampler;
+@group(1) @binding(0) var font_texture: texture_2d<f32>;
 
 struct VertexInput {
     @builtin(vertex_index) vertex_index: u32,
     @location(0) v_pos: vec4<f32>,
     @location(1) v_color: vec4<f32>,
     @location(2) v_uv: vec2<f32>,
+    @location(3) layers: vec2<i32>,
 }
 
 struct VertexOutput {
     @builtin(position) position: vec4<f32>,
     @location(0) f_color: vec4<f32>,
     @location(1) f_uv: vec2<f32>,
-    @location(2) f_use_tex: i32,
-    @location(3) f_use_mask: i32,
+    @location(2) color_layer: i32,
+    @location(3) mask_layer: i32,
 }
 
 @vertex
@@ -27,37 +27,23 @@ fn vs_main(input: VertexInput) -> VertexOutput {
     var out: VertexOutput;
     out.f_color = input.v_color;
     out.f_uv = input.v_uv;
+    out.color_layer = input.layers.x;
+    out.mask_layer = input.layers.y;
 
-    var use_tex: i32 = 0;
-    var use_mask: i32 = 0;
-
-    var flags: i32 = i32(input.v_pos.w);
-    if (flags == 1) {
-        use_tex = 1;
-    } else if (flags == 2) {
-        use_mask = 1;
-    } else if (flags == 3) {
-        use_tex = 1;
-        use_mask = 1;
-    }
-
-    out.f_use_tex = use_tex;
-    out.f_use_mask = use_mask;
     out.position = globals.transform * vec4<f32>(input.v_pos.xy, 0.0, 1.0);
     return out;
 }
 
 @fragment
 fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
-    // return vec4<f32>(input.f_color.xyz, 1.0);
     var out: vec4<f32> = input.f_color;
 
-    if input.f_use_tex > 0 {
-        out = textureSampleLevel(font_color_tex, font_sampler, input.f_uv, 0.0);
+    if input.color_layer > 0 {
+        out = textureSampleLevel(font_texture, font_sampler, input.f_uv, 0.0);
     }
 
-    if input.f_use_mask > 0 {
-        out = vec4<f32>(out.xyz, textureSampleLevel(font_mask_tex, font_sampler, input.f_uv, 0.0).x);
+    if input.mask_layer > 0 {
+        out = vec4<f32>(out.xyz, textureSampleLevel(font_texture, font_sampler, input.f_uv, 0.0).x);
     }
 
     return out;
diff --git a/sugarloaf/src/components/rich_text/text.rs b/sugarloaf/src/components/rich_text/text.rs
index 4dfd7a0ccf..7d72f76313 100644
--- a/sugarloaf/src/components/rich_text/text.rs
+++ b/sugarloaf/src/components/rich_text/text.rs
@@ -39,7 +39,7 @@ pub struct TextRunStyle<'a> {
     /// Underline style.
     pub decoration_color: Option<[f32; 4]>,
     /// Cursor style.
-    pub cursor: SugarCursor,
+    pub cursor: Option<SugarCursor>,
 }
 
 /// Positioned glyph in a text run.
diff --git a/sugarloaf/src/layout/builder_data.rs b/sugarloaf/src/layout/builder_data.rs
index dee784af9c..08c76c6e94 100644
--- a/sugarloaf/src/layout/builder_data.rs
+++ b/sugarloaf/src/layout/builder_data.rs
@@ -9,7 +9,7 @@
 // This file had updates to support color, underline_color, background_color
 // and other functionalities
 
-use crate::sugarloaf::primitives::SugarCursor;
+use crate::{sugarloaf::primitives::SugarCursor, Graphic};
 use std::hash::{Hash, Hasher};
 use swash::text::cluster::CharInfo;
 use swash::Setting;
@@ -33,10 +33,6 @@ pub struct FragmentData {
 /// Data that describes an item.
 #[derive(Copy, Debug, Clone)]
 pub struct ItemData {
-    // Script of the item.
-    // pub script: Script,
-    // Bidi level of the item.
-    // pub level: u8,
     /// Offset of the text.
     pub start: usize,
     /// End of the text.
@@ -219,7 +215,9 @@ pub struct FragmentStyle {
     /// Decoration color.
     pub decoration_color: Option<[f32; 4]>,
     /// Cursor style.
-    pub cursor: SugarCursor,
+    pub cursor: Option<SugarCursor>,
+    /// Media
+    pub media: Option<Graphic>,
 }
 
 impl Default for FragmentStyle {
@@ -233,9 +231,10 @@ impl Default for FragmentStyle {
             line_spacing: 1.,
             color: [1.0, 1.0, 1.0, 1.0],
             background_color: None,
-            cursor: SugarCursor::Disabled,
+            cursor: None,
             decoration: None,
             decoration_color: None,
+            media: None,
         }
     }
 }
@@ -284,24 +283,24 @@ impl Hash for FragmentStyle {
             }
         }
         match self.cursor {
-            SugarCursor::Disabled => {
+            None => {
                 0.hash(state);
             }
-            SugarCursor::Block(color) => {
+            Some(SugarCursor::Block(color)) => {
                 1.hash(state);
                 color[0].to_bits().hash(state);
                 color[1].to_bits().hash(state);
                 color[2].to_bits().hash(state);
                 color[3].to_bits().hash(state);
             }
-            SugarCursor::Caret(color) => {
+            Some(SugarCursor::Caret(color)) => {
                 2.hash(state);
                 color[0].to_bits().hash(state);
                 color[1].to_bits().hash(state);
                 color[2].to_bits().hash(state);
                 color[3].to_bits().hash(state);
             }
-            SugarCursor::Underline(color) => {
+            Some(SugarCursor::Underline(color)) => {
                 3.hash(state);
                 color[0].to_bits().hash(state);
                 color[1].to_bits().hash(state);
@@ -309,5 +308,7 @@ impl Hash for FragmentStyle {
                 color[3].to_bits().hash(state);
             }
         };
+
+        self.media.hash(state);
     }
 }
diff --git a/sugarloaf/src/layout/render_data.rs b/sugarloaf/src/layout/render_data.rs
index 374c13919c..5bb9bd0704 100644
--- a/sugarloaf/src/layout/render_data.rs
+++ b/sugarloaf/src/layout/render_data.rs
@@ -14,6 +14,7 @@ use super::layout_data::*;
 use crate::layout::builder_data::FragmentStyleDecoration;
 use crate::layout::FragmentStyle;
 use crate::sugarloaf::primitives::SugarCursor;
+use crate::{Graphic, GraphicId};
 use core::iter::DoubleEndedIterator;
 use core::ops::Range;
 use swash::shape::{cluster::Glyph as ShapedGlyph, Shaper};
@@ -25,6 +26,7 @@ use swash::{GlyphId, NormalizedCoord};
 pub struct RenderData {
     pub data: LayoutData,
     last_line: u32,
+    pub graphics: std::collections::HashSet<GraphicId>,
     pub last_cached_run: RunCacheEntry,
     pub line_data: LineLayoutData,
 }
@@ -151,6 +153,10 @@ impl RenderData {
             }
             let clusters_end = self.data.clusters.len() as u32;
 
+            if let Some(graphic) = cached_run.span.media {
+                self.graphics.insert(graphic.id);
+            }
+
             self.data.runs.push(RunData {
                 coords: (coords_start, coords_end),
                 clusters: (clusters_start, clusters_end),
@@ -258,6 +264,11 @@ impl RenderData {
                             details: detailed_clusters,
                         });
                     }
+
+                    if let Some(graphic) = styles[last_span].media {
+                        self.graphics.insert(graphic.id);
+                    }
+
                     self.last_cached_run.runs.push(CachedRunData {
                         span: styles[last_span],
                         line,
@@ -347,6 +358,11 @@ impl RenderData {
             return;
         }
         self.data.last_span = last_span;
+
+        if let Some(graphic) = styles[last_span].media {
+            self.graphics.insert(graphic.id);
+        }
+
         let run_data = RunData {
             span: styles[last_span],
             line,
@@ -444,6 +460,11 @@ impl<'a> Run<'a> {
         self.run.span
     }
 
+    #[inline]
+    pub fn media(&self) -> Option<Graphic> {
+        self.run.span.media
+    }
+
     /// Returns the font for the run.
     #[inline]
     pub fn font(&self) -> &usize {
@@ -469,7 +490,7 @@ impl<'a> Run<'a> {
 
     /// Returns the cursor
     #[inline]
-    pub fn cursor(&self) -> SugarCursor {
+    pub fn cursor(&self) -> Option<SugarCursor> {
         self.run.span.cursor
     }
 
diff --git a/sugarloaf/src/lib.rs b/sugarloaf/src/lib.rs
index 4a1142d031..ee33199691 100644
--- a/sugarloaf/src/lib.rs
+++ b/sugarloaf/src/lib.rs
@@ -8,9 +8,7 @@ pub use swash::{Stretch, Style, Weight};
 
 pub use crate::sugarloaf::{
     compositors::SugarCompositors,
-    graphics::{
-        ColorType, SugarGraphic, SugarGraphicData, SugarGraphicId, SugarloafGraphics,
-    },
+    graphics::{ColorType, Graphic, GraphicData, GraphicId, Graphics},
     primitives::*,
     Sugarloaf, SugarloafErrors, SugarloafRenderer, SugarloafWindow, SugarloafWindowSize,
     SugarloafWithErrors,
diff --git a/sugarloaf/src/sugarloaf.rs b/sugarloaf/src/sugarloaf.rs
index 39844eece1..1cdb6056cd 100644
--- a/sugarloaf/src/sugarloaf.rs
+++ b/sugarloaf/src/sugarloaf.rs
@@ -10,6 +10,7 @@ use crate::components::rich_text::RichTextBrush;
 use crate::components::text;
 use crate::font::{fonts::SugarloafFont, FontLibrary};
 use crate::layout::SugarloafLayout;
+use crate::sugarloaf::graphics::{BottomLayer, GraphicData, GraphicId, Graphics};
 use crate::sugarloaf::layer::types;
 use crate::{context::Context, Content, Object};
 use ab_glyph::{self, PxScale};
@@ -28,7 +29,8 @@ pub struct Sugarloaf<'a> {
     rich_text_brush: RichTextBrush,
     state: state::SugarState,
     pub background_color: wgpu::Color,
-    pub background_image: Option<types::Image>,
+    pub background_image: Option<ImageProperties>,
+    graphics: Graphics,
 }
 
 #[derive(Debug)]
@@ -138,6 +140,7 @@ impl Sugarloaf<'_> {
             rect_brush,
             rich_text_brush,
             text_brush,
+            graphics: Graphics::default(),
         };
 
         Ok(instance)
@@ -186,13 +189,16 @@ impl Sugarloaf<'_> {
     #[inline]
     pub fn set_background_image(&mut self, image: &ImageProperties) -> &mut Self {
         let handle = Handle::from_path(image.path.to_owned());
-        self.background_image = Some(layer::types::Image::Raster {
-            handle,
-            bounds: Rectangle {
-                width: image.width,
-                height: image.height,
-                x: image.x,
-                y: image.y,
+        self.graphics.bottom_layer = Some(BottomLayer {
+            should_fit: image.width.is_none() && image.height.is_none(),
+            data: types::Raster {
+                handle,
+                bounds: Rectangle {
+                    width: image.width.unwrap_or(self.ctx.size.width),
+                    height: image.height.unwrap_or(self.ctx.size.height),
+                    x: image.x,
+                    y: image.y,
+                },
             },
         });
         self
@@ -213,38 +219,28 @@ impl Sugarloaf<'_> {
         self.state.clean_screen();
     }
 
-    // #[inline]
-    // pub fn text(
-    //     &mut self,
-    //     position: (f32, f32),
-    //     content: String,
-    //     font_size: f32,
-    //     color: [f32; 4],
-    //     single_line: bool,
-    // ) {
-    //     self.state.compute_block(SugarBlock {
-    //         rects: vec![],
-    //         text: Some(SugarText {
-    //             position,
-    //             content,
-    //             font_id: 0,
-    //             font_size,
-    //             color,
-    //             single_line,
-    //         }),
-    //     });
-    // }
-
     #[inline]
     pub fn resize(&mut self, width: u32, height: u32) {
         self.ctx.resize(width, height);
         self.state.compute_layout_resize(width, height);
+        if let Some(bottom_layer) = &mut self.graphics.bottom_layer {
+            if bottom_layer.should_fit {
+                bottom_layer.data.bounds.width = self.ctx.size.width;
+                bottom_layer.data.bounds.height = self.ctx.size.height;
+            }
+        }
     }
 
     #[inline]
     pub fn rescale(&mut self, scale: f32) {
         self.ctx.scale = scale;
         self.state.compute_layout_rescale(scale);
+        if let Some(bottom_layer) = &mut self.graphics.bottom_layer {
+            if bottom_layer.should_fit {
+                bottom_layer.data.bounds.width = self.ctx.size.width;
+                bottom_layer.data.bounds.height = self.ctx.size.height;
+            }
+        }
     }
 
     #[inline]
@@ -253,13 +249,13 @@ impl Sugarloaf<'_> {
     }
 
     #[inline]
-    pub fn add_graphic(&mut self, graphic: crate::SugarGraphicData) {
-        self.state.graphics.add(graphic);
+    pub fn add_graphic(&mut self, graphic_data: GraphicData) {
+        self.graphics.insert(graphic_data);
     }
 
     #[inline]
-    pub fn remove_graphic(&mut self, graphic_id: &crate::SugarGraphicId) {
-        self.state.graphics.remove(graphic_id);
+    pub fn remove_graphic(&mut self, graphic_id: &GraphicId) {
+        self.graphics.remove(graphic_id);
     }
 
     #[inline]
@@ -277,6 +273,7 @@ impl Sugarloaf<'_> {
             &mut self.text_brush,
             &mut self.rect_brush,
             &mut self.ctx,
+            &mut self.graphics,
         ) {
             self.clean_state();
             return;
@@ -292,12 +289,27 @@ impl Sugarloaf<'_> {
                     .texture
                     .create_view(&wgpu::TextureViewDescriptor::default());
 
-                if let Some(bg_image) = &self.background_image {
-                    self.layer_brush.prepare_ref(
-                        &mut encoder,
-                        &mut self.ctx,
-                        &[bg_image],
-                    );
+                if let Some(layer) = &self.graphics.bottom_layer {
+                    self.layer_brush
+                        .prepare(&mut encoder, &mut self.ctx, &[&layer.data]);
+                }
+
+                if self.graphics.has_graphics_on_top_layer() {
+                    for request in &self.graphics.top_layer {
+                        if let Some(entry) = self.graphics.get(&request.id) {
+                            self.layer_brush.prepare_with_handle(
+                                &mut encoder,
+                                &mut self.ctx,
+                                &entry.handle,
+                                &Rectangle {
+                                    width: request.width.unwrap_or(entry.width),
+                                    height: request.height.unwrap_or(entry.height),
+                                    x: request.pos_x,
+                                    y: request.pos_y,
+                                },
+                            );
+                        }
+                    }
                 }
 
                 {
@@ -317,21 +329,35 @@ impl Sugarloaf<'_> {
                             depth_stencil_attachment: None,
                         });
 
-                    if self.background_image.is_some() {
+                    if self.graphics.bottom_layer.is_some() {
                         self.layer_brush.render(0, &mut rpass, None);
                     }
 
                     self.rich_text_brush
                         .render(&mut self.ctx, &self.state, &mut rpass);
 
+                    if self.graphics.has_graphics_on_top_layer() {
+                        let range_request = if self.graphics.bottom_layer.is_some() {
+                            1..(self.graphics.top_layer.len() + 1)
+                        } else {
+                            0..self.graphics.top_layer.len()
+                        };
+                        for request in range_request {
+                            self.layer_brush.render(request, &mut rpass, None);
+                        }
+                    }
+
                     self.rect_brush
                         .render(&mut rpass, &self.state, &mut self.ctx);
 
                     self.text_brush.render(&mut self.ctx, &mut rpass);
                 }
 
-                if self.background_image.is_some() {
+                if self.graphics.bottom_layer.is_some()
+                    || self.graphics.has_graphics_on_top_layer()
+                {
                     self.layer_brush.end_frame();
+                    self.graphics.clear_top_layer();
                 }
 
                 self.ctx.queue.submit(Some(encoder.finish()));
diff --git a/sugarloaf/src/sugarloaf/compositors/elementary.rs b/sugarloaf/src/sugarloaf/compositors/elementary.rs
index b989e0ea7c..92c3c9a1c7 100644
--- a/sugarloaf/src/sugarloaf/compositors/elementary.rs
+++ b/sugarloaf/src/sugarloaf/compositors/elementary.rs
@@ -11,7 +11,7 @@ use crate::Text;
 
 #[allow(unused)]
 struct GraphicRect {
-    id: graphics::SugarGraphicId,
+    id: graphics::GraphicId,
     height: u16,
     width: u16,
     pos_x: f32,
diff --git a/sugarloaf/src/sugarloaf/graphics.rs b/sugarloaf/src/sugarloaf/graphics.rs
index 854436e8ba..f8ca0cdf80 100644
--- a/sugarloaf/src/sugarloaf/graphics.rs
+++ b/sugarloaf/src/sugarloaf/graphics.rs
@@ -3,71 +3,89 @@
 // This source code is licensed under the MIT license found in the
 // LICENSE file in the root directory of this source tree.
 
-use crate::components::core::image::Handle;
+use crate::sugarloaf::types;
+use crate::sugarloaf::Handle;
 use rustc_hash::FxHashMap;
 
-pub struct SugarGraphicEntry {
-    pub id: SugarGraphicId,
+pub struct GraphicDataEntry {
     pub handle: Handle,
+    pub width: f32,
+    pub height: f32,
+}
+
+#[derive(Debug)]
+pub struct GraphicRenderRequest {
+    pub id: GraphicId,
+    pub pos_x: f32,
+    pub pos_y: f32,
+    pub width: Option<f32>,
+    pub height: Option<f32>,
+}
+
+pub struct BottomLayer {
+    pub data: types::Raster,
+    pub should_fit: bool,
 }
 
 #[derive(Default)]
-pub struct SugarloafGraphics {
-    inner: FxHashMap<SugarGraphicId, SugarGraphicEntry>,
+pub struct Graphics {
+    inner: FxHashMap<GraphicId, GraphicDataEntry>,
+    pub bottom_layer: Option<BottomLayer>,
+    pub top_layer: Vec<GraphicRenderRequest>,
 }
 
-impl SugarloafGraphics {
+impl Graphics {
     #[inline]
-    pub fn is_empty(&self) -> bool {
-        self.inner.is_empty()
+    pub fn has_graphics_on_top_layer(&self) -> bool {
+        !self.top_layer.is_empty()
     }
 
     #[inline]
-    pub fn get_mut(&mut self, id: &SugarGraphicId) -> Option<&mut SugarGraphicEntry> {
-        self.inner.get_mut(id)
+    pub fn clear_top_layer(&mut self) {
+        self.top_layer.clear();
     }
 
     #[inline]
-    pub fn get(&mut self, id: &SugarGraphicId) -> Option<&SugarGraphicEntry> {
+    pub fn get(&self, id: &GraphicId) -> Option<&GraphicDataEntry> {
         self.inner.get(id)
     }
 
     #[inline]
-    pub fn keys(&self) -> Vec<SugarGraphicId> {
-        self.inner.keys().cloned().collect::<Vec<_>>()
-    }
+    pub fn insert(&mut self, graphic_data: GraphicData) {
+        if self.inner.contains_key(&graphic_data.id) {
+            return;
+        }
 
-    #[inline]
-    pub fn add(&mut self, graphic_data: SugarGraphicData) {
-        let handle = Handle::from_pixels(
-            graphic_data.width as u32,
-            graphic_data.height as u32,
-            graphic_data.pixels.clone(),
+        self.inner.insert(
+            graphic_data.id,
+            GraphicDataEntry {
+                handle: Handle::from_pixels(
+                    graphic_data.width as u32,
+                    graphic_data.height as u32,
+                    graphic_data.pixels,
+                ),
+                width: graphic_data.width as f32,
+                height: graphic_data.height as f32,
+            },
         );
-        self.inner
-            .entry(graphic_data.id)
-            .or_insert(SugarGraphicEntry {
-                id: graphic_data.id,
-                handle,
-            });
     }
 
     #[inline]
-    pub fn remove(&mut self, graphic_id: &SugarGraphicId) {
+    pub fn remove(&mut self, graphic_id: &GraphicId) {
         self.inner.remove(graphic_id);
     }
 }
 
-#[derive(Debug, Clone, Copy)]
-pub struct SugarGraphic {
-    pub id: SugarGraphicId,
-    pub width: u16,
-    pub height: u16,
+#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
+pub struct Graphic {
+    pub id: GraphicId,
+    pub offset_x: u16,
+    pub offset_y: u16,
 }
 
 /// Unique identifier for every graphic added to a grid.
 #[derive(Eq, PartialEq, Clone, Debug, Copy, Hash, PartialOrd, Ord)]
-pub struct SugarGraphicId(pub u64);
+pub struct GraphicId(pub u64);
 
 /// Specifies the format of the pixel data.
 #[derive(Eq, PartialEq, Clone, Debug, Copy)]
@@ -81,9 +99,9 @@ pub enum ColorType {
 
 /// Defines a single graphic read from the PTY.
 #[derive(Eq, PartialEq, Clone, Debug)]
-pub struct SugarGraphicData {
+pub struct GraphicData {
     /// Graphics identifier.
-    pub id: SugarGraphicId,
+    pub id: GraphicId,
 
     /// Width, in pixels, of the graphic.
     pub width: usize,
@@ -101,7 +119,7 @@ pub struct SugarGraphicData {
     pub is_opaque: bool,
 }
 
-impl SugarGraphicData {
+impl GraphicData {
     /// Check if the image may contain transparent pixels. If it returns
     /// `false`, it is guaranteed that there are no transparent pixels.
     #[inline]
diff --git a/sugarloaf/src/sugarloaf/primitives.rs b/sugarloaf/src/sugarloaf/primitives.rs
index 62f97995b4..61c9239db5 100644
--- a/sugarloaf/src/sugarloaf/primitives.rs
+++ b/sugarloaf/src/sugarloaf/primitives.rs
@@ -6,13 +6,11 @@
 use crate::sugarloaf::Rect;
 use serde::Deserialize;
 
-#[derive(Debug, Default, PartialEq, Copy, Clone)]
+#[derive(Debug, PartialEq, Copy, Clone)]
 pub enum SugarCursor {
     Block([f32; 4]),
     Caret([f32; 4]),
     Underline([f32; 4]),
-    #[default]
-    Disabled,
 }
 
 #[derive(Copy, PartialEq, Default, Debug, Clone)]
@@ -26,10 +24,10 @@ pub struct SugarloafStyle {
 pub struct ImageProperties {
     #[serde(default = "String::default")]
     pub path: String,
-    #[serde(default = "f32::default")]
-    pub width: f32,
-    #[serde(default = "f32::default")]
-    pub height: f32,
+    #[serde(default = "Option::default")]
+    pub width: Option<f32>,
+    #[serde(default = "Option::default")]
+    pub height: Option<f32>,
     #[serde(default = "f32::default")]
     pub x: f32,
     #[serde(default = "f32::default")]
diff --git a/sugarloaf/src/sugarloaf/state.rs b/sugarloaf/src/sugarloaf/state.rs
index 1c5166a6e8..1cd064612d 100644
--- a/sugarloaf/src/sugarloaf/state.rs
+++ b/sugarloaf/src/sugarloaf/state.rs
@@ -4,9 +4,9 @@
 // LICENSE file in the root directory of this source tree.
 
 use super::compositors::SugarCompositors;
-use super::graphics::SugarloafGraphics;
 use crate::font::FontLibrary;
 use crate::sugarloaf::{text, RectBrush, RichTextBrush, SugarloafLayout};
+use crate::Graphics;
 use crate::{Content, Object};
 
 #[derive(Debug, PartialEq)]
@@ -27,8 +27,6 @@ pub struct SugarState {
     pub current: SugarTree,
     latest_change: SugarTreeDiff,
     pub compositors: SugarCompositors,
-    // TODO: Decide if graphics should be in SugarTree or SugarState
-    pub graphics: SugarloafGraphics,
 }
 
 impl SugarState {
@@ -39,7 +37,6 @@ impl SugarState {
     ) -> SugarState {
         let mut state = SugarState {
             compositors: SugarCompositors::new(font_library),
-            graphics: SugarloafGraphics::default(),
             // First time computing changes should obtain dimensions
             current: SugarTree {
                 layout: initial_layout,
@@ -148,6 +145,7 @@ impl SugarState {
         elementary_brush: &mut text::GlyphBrush<()>,
         rect_brush: &mut RectBrush,
         context: &mut super::Context,
+        graphics: &mut Graphics,
     ) -> bool {
         #[cfg(not(feature = "always_dirty"))]
         if self.latest_change == SugarTreeDiff::Equal {
@@ -155,7 +153,7 @@ impl SugarState {
             return false;
         }
 
-        advance_brush.prepare(context, self);
+        advance_brush.prepare(context, self, graphics);
         rect_brush.resize(context);
 
         // Elementary renderer is used for everything else in sugarloaf


Test patch: