|
6 | 6 |
|
7 | 7 | # AtomGL |
8 | 8 |
|
9 | | -AtomGL is a display driver for a number of different display models and technologies. |
| 9 | +AtomGL is a display driver for various display models and technologies, implementing a declarative |
| 10 | +display list approach for rendering. |
10 | 11 |
|
11 | | -This component is not meant for being used directly, instead an additional layer should take care of |
12 | | -pushing updates to it. For this reason no additional NIFs are provided. |
| 12 | +This component is not meant to be used directly. Instead, an additional layer should handle pushing |
| 13 | +updates to it. For this reason, no additional NIFs are provided. |
13 | 14 |
|
14 | | -**Please, use this component with a supported AtomVM version, such as v0.6.** |
| 15 | +**Please use this component with a supported AtomVM version, such as v0.6.** |
| 16 | + |
| 17 | +## Core Concept: Display Lists |
| 18 | + |
| 19 | +AtomGL uses a **declarative display list** approach where everything rendered on the display is |
| 20 | +represented as a list of primitive items (Erlang tuples/records). Unlike imperative graphics APIs |
| 21 | +where you issue drawing commands sequentially, AtomGL replaces the entire display content with a new |
| 22 | +list on each update. |
| 23 | + |
| 24 | +### Z-Ordering |
| 25 | + |
| 26 | +Items in the display list follow a specific rendering order: |
| 27 | +- Items are rendered from **tail to head** (reverse order) |
| 28 | +- The **first item** in the list has the highest z-order (appears on top) |
| 29 | +- The **last item** has the lowest z-order (appears at the bottom) |
| 30 | + |
| 31 | +```elixir |
| 32 | +# Example: "Hello" text over a gray background |
| 33 | +items = [ |
| 34 | + {:text, 10, 20, :default16px, 0x000000, 0x808080, "Hello."}, # Drawn last (on top) |
| 35 | + {:rect, 0, 0, width, height, 0x808080} # Drawn first (background) |
| 36 | +] |
| 37 | +``` |
| 38 | + |
| 39 | +**Best Practice:** Always include a full-screen rectangle as the last item in your list to clear the |
| 40 | +screen with a background color. |
| 41 | + |
| 42 | +### Declarative vs. Imperative |
| 43 | + |
| 44 | +AtomGL's declarative approach means: |
| 45 | +- Each `update` call **completely replaces** the previous display list |
| 46 | +- There is no hidden global display state |
| 47 | +- You describe *what* should be displayed, not *how* to draw it |
| 48 | +- Perfect fit for functional programming paradigms |
| 49 | + |
| 50 | +```erlang |
| 51 | +% First update: show background |
| 52 | +BackgroundScene = [ |
| 53 | + {rect, 0, 0, 1024, 600, 16#FFFFFF} |
| 54 | +], |
| 55 | +ok = port:call(Display, {update, BackgroundScene}), |
| 56 | + |
| 57 | +% Second update: completely replaces the previous scene |
| 58 | +ColoredRectScene = [ |
| 59 | + {rect, 150, 150, 80, 80, 16#FF0000}, % Red square (top) |
| 60 | + {rect, 250, 150, 80, 80, 16#00FF00}, % Green square |
| 61 | + {rect, 350, 150, 80, 80, 16#0000FF}, % Blue square |
| 62 | + {rect, 0, 0, 1024, 600, 16#FFFFFF} % White background (bottom) |
| 63 | +], |
| 64 | +ok = port:call(Display, {update, ColoredRectScene}) |
| 65 | +``` |
| 66 | + |
| 67 | +Note: Without a background rectangle, some drivers may retain the previous image, but this is not a |
| 68 | +guaranteed feature. |
| 69 | + |
| 70 | +### Memory Efficiency |
| 71 | + |
| 72 | +A key benefit of the display list approach is **memory efficiency**. Unlike traditional graphics |
| 73 | +systems that require a complete frame buffer in memory, AtomGL drivers can: |
| 74 | +- Draw everything in smaller chunks or line by line |
| 75 | +- Evaluate display content pixel by pixel on-the-fly |
| 76 | +- Drive displays that would require more frame buffer memory than available internal RAM |
| 77 | + |
| 78 | +This makes it possible for microcontrollers with limited memory to drive high-resolution displays |
| 79 | +that would otherwise be impossible to support with traditional frame buffer approaches. |
15 | 80 |
|
16 | 81 | ## Supported Hardware |
17 | 82 |
|
18 | | -* `ilitek,ili9341` / `ilitek,ili9342c`: ILI9341 / ILI9342c: 240x320, 16 bit colors, supported |
19 | | -* `waveshare,5in65-acep-7c`: Waveshare 7-color 5.65" ACeP display module: 600x480, 7 colors + |
| 83 | +* `ilitek,ili9341` / `ilitek,ili9342c`: ILI9341 / ILI9342C - 240x320, 16-bit colors |
| 84 | +* `waveshare,5in65-acep-7c`: Waveshare 7-color 5.65" ACeP display module - 600x480, 7 colors + |
20 | 85 | software dithering |
21 | | -* `sharp,memory-lcd`: Sharp Memory LCDs: 400x240 1-bit monochromatic |
22 | | -* `solomon-systech,ssd1306`: Solomon Systech SSD1306: 128x64 1-bit monochromatic |
23 | | -* `sino-wealth,sh1106`: Sino Wealth SH1106: 128x64 1-bit monochromatic |
| 86 | +* `sharp,memory-lcd`: Sharp Memory LCDs - 400x240, 1-bit monochrome |
| 87 | +* `solomon-systech,ssd1306`: Solomon Systech SSD1306 - 128x64, 1-bit monochrome |
| 88 | +* `sino-wealth,sh1106`: Sino Wealth SH1106 - 128x64, 1-bit monochrome |
| 89 | + |
| 90 | +[SDL Linux display](sdl_display/) is also supported and can be built as an AtomVM plugin. |
| 91 | + |
| 92 | +## Platform Support |
| 93 | + |
| 94 | +AtomGL is currently compatible with **ESP32** microcontrollers. Linux with SDL is also supported for |
| 95 | +testing and development purposes. |
| 96 | + |
| 97 | +### Building for ESP32 |
| 98 | + |
| 99 | +Place AtomGL in the ESP32 components directory as a regular ESP32 component and build using the |
| 100 | +standard ESP-IDF process: |
| 101 | + |
| 102 | +```bash |
| 103 | +# Add AtomGL to your ESP32 project's components directory |
| 104 | +cd /path/to/AtomVM/src/platforms/esp32/components/ |
| 105 | +git clone [email protected]:atomvm/atomgl.git |
| 106 | + |
| 107 | +# Build with ESP-IDF |
| 108 | +idf.py build |
| 109 | +``` |
24 | 110 |
|
25 | | -[SDL Linux display](sdl_display/) is also supported and can be built as AtomVM plugin. |
| 111 | +### Building for Linux (SDL) |
| 112 | + |
| 113 | +For testing on Linux using SDL, see the [SDL display driver documentation](sdl_display/README.md). |
26 | 114 |
|
27 | 115 | ## Getting Started |
28 | 116 |
|
29 | | -1. Add this component to the ESP32 components directory |
30 | | -2. Open a display port using the right options |
31 | | -3. Start an [avm_scene](https://github.com/atomvm/avm_scene) that will push updates to the display |
| 117 | +### Basic Setup |
| 118 | + |
| 119 | +1. Open a display port using the appropriate options |
| 120 | +2. Start an [avm_scene](https://github.com/atomvm/avm_scene) to push updates to the display |
32 | 121 |
|
33 | | -### Display Option Example |
| 122 | +### Display Configuration Example |
34 | 123 |
|
35 | | -The following is an example for ILI9341: |
| 124 | +Example configuration for ILI9341: |
36 | 125 |
|
37 | 126 | ```elixir |
38 | | - [...] |
39 | | - # spi is already open |
40 | | - |
41 | | - ili_display_opts = [ |
42 | | - width: 320, |
43 | | - height: 240, |
44 | | - compatible: "ilitek,ili9341", |
45 | | - reset: 18, |
46 | | - cs: 22, |
47 | | - dc: 21, |
48 | | - backlight: 5, |
49 | | - backlight_active: :low, |
50 | | - backlight_enabled: true, |
51 | | - rotation: 1, |
52 | | - enable_tft_invon: false, |
53 | | - spi_host: spi |
| 127 | +# spi is already open |
| 128 | + |
| 129 | +ili_display_opts = [ |
| 130 | + width: 320, |
| 131 | + height: 240, |
| 132 | + compatible: "ilitek,ili9341", |
| 133 | + reset: 18, |
| 134 | + cs: 22, |
| 135 | + dc: 21, |
| 136 | + backlight: 5, |
| 137 | + backlight_active: :low, |
| 138 | + backlight_enabled: true, |
| 139 | + rotation: 1, |
| 140 | + enable_tft_invon: false, |
| 141 | + spi_host: spi |
| 142 | +] |
| 143 | + |
| 144 | +:erlang.open_port({:spawn, "display"}, display_opts) |
| 145 | +``` |
| 146 | + |
| 147 | +### Direct Port Usage |
| 148 | + |
| 149 | +For direct port communication (without avm_scene): |
| 150 | + |
| 151 | +```erlang |
| 152 | +% Create your display list |
| 153 | +DisplayList = [ |
| 154 | + {text, 10, 20, default16px, 16#000000, transparent, "Hello, World!"}, |
| 155 | + {rect, 0, 0, 320, 240, 16#FFFFFF} % White background |
| 156 | +], |
| 157 | + |
| 158 | +% Update the display |
| 159 | +port:call(DisplayPort, {update, DisplayList}) |
| 160 | +``` |
| 161 | + |
| 162 | +### Using avm_scene (Recommended) |
| 163 | + |
| 164 | +The `avm_scene` library provides a more ergonomic interface that handles the update mechanics: |
| 165 | + |
| 166 | +```elixir |
| 167 | +[...] |
| 168 | + |
| 169 | +def start_link(args, opts) do |
| 170 | + :avm_scene.start_link(__MODULE__, args, opts) |
| 171 | +end |
| 172 | + |
| 173 | +[...] |
| 174 | + |
| 175 | +def handle_info(:show_hello, %{width: width, height: height} = state) do |
| 176 | + items = [ |
| 177 | + {:text, 10, 20, :default16px, 0x000000, 0x808080, "Hello."}, |
| 178 | + {:rect, 0, 0, width, height, 0x808080} |
54 | 179 | ] |
55 | 180 |
|
56 | | - :erlang.open_port({:spawn, "display"}, display_opts) |
57 | | - [...] |
| 181 | + {:noreply, state, [{:push, items}]} |
| 182 | +end |
| 183 | + |
| 184 | +[...] |
58 | 185 | ``` |
59 | 186 |
|
60 | 187 | ## Primitives |
61 | 188 |
|
62 | | -The display driver takes care of drawing a list of primitive items. Such as: |
| 189 | +AtomGL supports the following primitive types: |
| 190 | + |
| 191 | +* **image** - Display bitmap images |
| 192 | +* **scaled_cropped_image** - Display scaled and cropped images |
| 193 | +* **rect** - Draw filled rectangles |
| 194 | +* **text** - Render text with specified font |
| 195 | + |
| 196 | +Example display list with multiple primitives: |
63 | 197 |
|
64 | 198 | ```elixir |
65 | | - rendered = [ |
66 | | - {:text, 10, 20, :default16px, 0x000000, 0x808080, "Hello."}, |
67 | | - {:rect, 0, 0, width, height, 0x808080} |
68 | | - ] |
| 199 | +rendered = [ |
| 200 | + {:text, 10, 20, :default16px, 0x000000, 0x808080, "Hello."}, |
| 201 | + {:rect, 0, 0, width, height, 0x808080} |
| 202 | +] |
69 | 203 | ``` |
70 | 204 |
|
71 | | -Following primitives are supported: |
72 | | -* image |
73 | | -* scaled_cropped_image |
74 | | -* rect |
75 | | -* text |
| 205 | +See the [primitives documentation](docs/primitives.md) for detailed information about each primitive type. |
| 206 | + |
| 207 | +## Important Notes |
| 208 | + |
| 209 | +- The display list approach means you always define the complete display state |
| 210 | +- Each update replaces the entire previous state (no incremental drawing) |
| 211 | +- Z-order is determined by position in the list (first = topmost) |
| 212 | +- Always include a background rectangle to ensure proper screen clearing |
| 213 | +- The `update` command is the only imperative operation |
| 214 | + |
| 215 | +## License |
76 | 216 |
|
77 | | -See also [documentation](docs/primitives.md) for more information. |
| 217 | +Apache-2.0 |
0 commit comments