@kofany/beamterm-terx 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +439 -0
- package/beamterm_renderer.d.ts +315 -0
- package/beamterm_renderer.js +5 -0
- package/beamterm_renderer_bg.js +1908 -0
- package/beamterm_renderer_bg.wasm +0 -0
- package/package.json +38 -0
package/README.md
ADDED
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
## beamterm - A WebGL2 Terminal Renderer
|
|
2
|
+
|
|
3
|
+
[![Crate Badge]][Crate] [![NPM Badge]][NPM] [![API Badge]][API] [![Deps.rs
|
|
4
|
+
Badge]][Deps.rs]
|
|
5
|
+
|
|
6
|
+
A high-performance terminal rendering system for web browsers, targeting sub-millisecond render
|
|
7
|
+
times. **beamterm** is a terminal renderer, not a full terminal emulator - it handles the display
|
|
8
|
+
layer while you provide the terminal logic.
|
|
9
|
+
|
|
10
|
+
### [Live Demos][demos]
|
|
11
|
+
|
|
12
|
+
Check out [**interactive examples**][demos] showcasing both pure Rust applications and JavaScript/TypeScript
|
|
13
|
+
integrations.
|
|
14
|
+
|
|
15
|
+
## Key Features
|
|
16
|
+
|
|
17
|
+
- **Single Draw Call** - Renders entire terminal (e.g., 200×80 cells) in one instanced draw
|
|
18
|
+
- **Flexible Font Atlases** - Static pre-generated atlases or dynamic on-demand rasterization with LRU caching
|
|
19
|
+
- **Unicode and Emoji Support** - Complete Unicode support with grapheme clustering
|
|
20
|
+
- **Selection Support** - Mouse-driven text selection with clipboard integration (Block/Linear modes)
|
|
21
|
+
- **Optional JS/TS Bindings** - Provides a [JavaScript/TypeScript API](js/README.md) for easy integration
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
## Performance
|
|
25
|
+
|
|
26
|
+
beamterm targets sub-millisecond render times across a wide range of hardware:
|
|
27
|
+
|
|
28
|
+
| Metric | Target (Low-end) | Achieved (2019 hardware) |
|
|
29
|
+
|---------------------------------|------------------|--------------------------|
|
|
30
|
+
| Render Time† | <1ms @ 16k cells | <1ms @ 45k cells |
|
|
31
|
+
| Draw Calls | 1 per frame | 1 per frame |
|
|
32
|
+
| Memory Usage | ~8.9MB | ~8.9MB |
|
|
33
|
+
| Update Bandwidth (full refresh) | ~8 MB/s @ 60 FPS | ~22 MB/s @ 60 FPS |
|
|
34
|
+
|
|
35
|
+
[](https://junkdog.github.io/beamterm/canvas_waves/?atlas_size=10)
|
|
36
|
+
|
|
37
|
+
The screenshot shows [Ratzilla's][rz] "canvas waves" demo running in a 426×106 terminal (45,156 cells),
|
|
38
|
+
maintaining sub-millisecond render times on 2019-era hardware (i9-9900K / RTX 2070).
|
|
39
|
+
|
|
40
|
+
† *Includes Ratatui buffer translation, GPU buffer uploads, and draw call execution.*
|
|
41
|
+
|
|
42
|
+
[rz]: https://github.com/orhun/ratzilla
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
## System Architecture
|
|
46
|
+
|
|
47
|
+
The renderer consists of three crates:
|
|
48
|
+
|
|
49
|
+
**`beamterm-atlas`** - Generates GPU-optimized static font atlases from TTF/OTF files. Automatically
|
|
50
|
+
calculates cell dimensions, supports font styles (normal/bold/italic), and outputs packed
|
|
51
|
+
texture data.
|
|
52
|
+
|
|
53
|
+
**`beamterm-data`** - Provides shared data structures and efficient binary serialization. Features
|
|
54
|
+
versioned format with header validation and cross-platform encoding.
|
|
55
|
+
|
|
56
|
+
**`beamterm-renderer`** - The WebGL2 rendering engine. Implements instanced rendering with optimized
|
|
57
|
+
buffer management and state tracking for consistent sub-millisecond performance.
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
## Font Atlas Types
|
|
61
|
+
|
|
62
|
+
beamterm supports two font atlas strategies, each with different trade-offs:
|
|
63
|
+
|
|
64
|
+
### Static Font Atlas (Default)
|
|
65
|
+
|
|
66
|
+
Pre-generated atlas loaded from a binary `.atlas` file. Best when character sets are
|
|
67
|
+
known and consistent rendering is required.
|
|
68
|
+
|
|
69
|
+
**Usage:**
|
|
70
|
+
```rust
|
|
71
|
+
// Uses embedded default atlas
|
|
72
|
+
let terminal = Terminal::builder("#canvas").build()?;
|
|
73
|
+
|
|
74
|
+
// Or load a custom atlas
|
|
75
|
+
let atlas_data = FontAtlasData::from_binary(include_bytes!("hack-8pt.atlas"))?;
|
|
76
|
+
let terminal = Terminal::builder("#canvas")
|
|
77
|
+
.font_atlas(atlas_data)
|
|
78
|
+
.build()?;
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Generate custom atlases with the `beamterm-atlas` CLI tool (see [beamterm-atlas/README.md](beamterm-atlas/README.md)).
|
|
82
|
+
|
|
83
|
+
### Dynamic Font Atlas
|
|
84
|
+
|
|
85
|
+
Rasterizes glyphs on-demand using the browser's Canvas API. Handles unpredictable content and can
|
|
86
|
+
use any system font without pre-generation.
|
|
87
|
+
|
|
88
|
+
**Usage:**
|
|
89
|
+
```rust
|
|
90
|
+
let terminal = Terminal::builder("#canvas")
|
|
91
|
+
.dynamic_font_atlas(&["JetBrains Mono", "Fira Code"], 16.0)
|
|
92
|
+
.build()?;
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**How it works:**
|
|
96
|
+
- Glyphs are rasterized on first use via `OffscreenCanvas`
|
|
97
|
+
- ASCII characters (0x20-0x7E) are pre-loaded at startup
|
|
98
|
+
- Double-width characters (emoji, CJK) automatically use two consecutive texture slots
|
|
99
|
+
- 4096 total glyph slots (2048 single-width + 1024 double-width)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
## Architecture Overview
|
|
103
|
+
|
|
104
|
+
The architecture leverages GPU instancing to reuse a single quad geometry across all terminal cells,
|
|
105
|
+
with per-instance data providing position, character, and color information. All rendering state is
|
|
106
|
+
encapsulated in a Vertex Array Object (VAO), enabling single-draw-call rendering with minimal CPU
|
|
107
|
+
overhead. The 2D texture array maximizes cache efficiency by packing related glyphs into vertical
|
|
108
|
+
strips within each layer.
|
|
109
|
+
|
|
110
|
+
### Buffer Management Strategy
|
|
111
|
+
|
|
112
|
+
The renderer employs several optimization strategies:
|
|
113
|
+
|
|
114
|
+
1. **VAO Encapsulation**: All vertex state is captured in a single VAO, minimizing state changes
|
|
115
|
+
2. **Separate Static/Dynamic**: Geometry and positions rarely change; only cell content is dynamic
|
|
116
|
+
3. **Aligned Packing**: All structures use explicit alignment for optimal GPU access
|
|
117
|
+
4. **Batch Updates**: Cell updates are batched and uploaded in a single operation
|
|
118
|
+
5. **Immutable Storage**: 2D texture array uses `texStorage3D` for driver optimization hints
|
|
119
|
+
|
|
120
|
+
These strategies combined enable the renderer to achieve consistent sub-millisecond frame times even
|
|
121
|
+
for large terminals (200×80 cells = 16,000 instances).
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
## Terminal Renderer API
|
|
125
|
+
|
|
126
|
+
The renderer provides a high-level `Terminal` struct that encapsulates the complete rendering system:
|
|
127
|
+
|
|
128
|
+
### Quick Start
|
|
129
|
+
|
|
130
|
+
```rust
|
|
131
|
+
use beamterm_renderer::{Terminal, CellData, FontStyle, GlyphEffect};
|
|
132
|
+
|
|
133
|
+
// Create terminal with default font atlas
|
|
134
|
+
let mut terminal = Terminal::builder("#canvas").build()?;
|
|
135
|
+
|
|
136
|
+
// Update cells and render
|
|
137
|
+
let cells: Vec<CellData> = ...;
|
|
138
|
+
terminal.update_cells(cells.into_iter())?;
|
|
139
|
+
terminal.render_frame()?;
|
|
140
|
+
|
|
141
|
+
// Handle resize
|
|
142
|
+
terminal.resize(new_width, new_height)?;
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Selection and Mouse Input
|
|
146
|
+
|
|
147
|
+
The renderer supports mouse-driven text selection with automatic clipboard integration:
|
|
148
|
+
|
|
149
|
+
```rust
|
|
150
|
+
// Enable default selection handler
|
|
151
|
+
let terminal = Terminal::builder("#canvas")
|
|
152
|
+
.default_mouse_input_handler(SelectionMode::Linear, true)
|
|
153
|
+
.build()?;
|
|
154
|
+
|
|
155
|
+
// Or implement custom mouse handling
|
|
156
|
+
let terminal = Terminal::builder("#canvas")
|
|
157
|
+
.mouse_input_handler(|event, grid| {
|
|
158
|
+
// Custom handler logic
|
|
159
|
+
})
|
|
160
|
+
.build()?;
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
### TerminalGrid
|
|
165
|
+
Main rendering component managing the terminal display. Handles shader programs, cell data, GPU
|
|
166
|
+
buffers, and rendering state.
|
|
167
|
+
|
|
168
|
+
### FontAtlas
|
|
169
|
+
Manages the 2D texture array containing all font glyphs. Provides character-to-glyph ID
|
|
170
|
+
mapping with fast ASCII optimization. Supports loading default or custom font atlases.
|
|
171
|
+
|
|
172
|
+
### Cell Data Structure
|
|
173
|
+
|
|
174
|
+
Each terminal cell requires:
|
|
175
|
+
- **symbol**: Character or grapheme to display (`&str`)
|
|
176
|
+
- **style**: `FontStyle` enum (Normal, Bold, Italic, BoldItalic)
|
|
177
|
+
- **effect**: `GlyphEffect` enum (None, Underline, Strikethrough)
|
|
178
|
+
- **fg/bg**: Colors as 32-bit ARGB values (`0xAARRGGBB`)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
## Font Atlas 2D Texture Array Architecture
|
|
182
|
+
|
|
183
|
+
Both atlas types use a WebGL 2D texture array where each layer contains a 1×32 grid of glyphs.
|
|
184
|
+
However, they differ significantly in how glyphs are addressed and organized.
|
|
185
|
+
|
|
186
|
+
### Static Atlas: Style-Encoded Glyph IDs
|
|
187
|
+
|
|
188
|
+
The static atlas uses 16-bit glyph IDs with style information encoded directly in the ID.
|
|
189
|
+
This allows the GPU to compute texture coordinates from the ID alone.
|
|
190
|
+
|
|
191
|
+
| Layer Range | Style | Glyph ID Range | Total Layers |
|
|
192
|
+
|-------------|----------------|----------------|--------------|
|
|
193
|
+
| 0-31 | Normal | 0x0000-0x03FF | 32 |
|
|
194
|
+
| 32-63 | Bold | 0x0400-0x07FF | 32 |
|
|
195
|
+
| 64-95 | Italic | 0x0800-0x0BFF | 32 |
|
|
196
|
+
| 96-127 | Bold+Italic | 0x0C00-0x0FFF | 32 |
|
|
197
|
+
| 128-255 | Emoji (2-wide) | 0x1000-0x1FFF | 128 |
|
|
198
|
+
|
|
199
|
+
Each font style reserves exactly 32 layers (1024 glyph slots), creating gaps if fewer glyphs are used.
|
|
200
|
+
Emoji layers start at layer 128, regardless of how many base glyphs are actually defined.
|
|
201
|
+
|
|
202
|
+
**Texture lookup mask:** `0x1FFF` (13 bits) - includes style bits for layer calculation.
|
|
203
|
+
|
|
204
|
+
#### Glyph ID Encoding (Static Atlas)
|
|
205
|
+
|
|
206
|
+
The glyph ID is a 16-bit value that efficiently packs both the base glyph identifier
|
|
207
|
+
and style information (such as weight, style flags, etc.) into a single value. This
|
|
208
|
+
packed representation is passed directly to the GPU.
|
|
209
|
+
|
|
210
|
+
#### Glyph ID Bit Layout (16-bit)
|
|
211
|
+
|
|
212
|
+
| Bit(s) | Flag Name | Hex Mask | Binary Mask | Description |
|
|
213
|
+
|--------|---------------|----------|-----------------------|---------------------------|
|
|
214
|
+
| 0-9 | GLYPH_ID | `0x03FF` | `0000_0011_1111_1111` | Base glyph identifier |
|
|
215
|
+
| 10 | BOLD | `0x0400` | `0000_0100_0000_0000` | Bold font style* |
|
|
216
|
+
| 11 | ITALIC | `0x0800` | `0000_1000_0000_0000` | Italic font style* |
|
|
217
|
+
| 12 | EMOJI | `0x1000` | `0001_0000_0000_0000` | Emoji character flag |
|
|
218
|
+
| 13 | UNDERLINE | `0x2000` | `0010_0000_0000_0000` | Underline effect |
|
|
219
|
+
| 14 | STRIKETHROUGH | `0x4000` | `0100_0000_0000_0000` | Strikethrough effect |
|
|
220
|
+
| 15 | RESERVED | `0x8000` | `1000_0000_0000_0000` | Reserved for future use |
|
|
221
|
+
|
|
222
|
+
*When the EMOJI flag (bit 12) is set, bits 10-11 are **not** used for bold/italic styling (emoji
|
|
223
|
+
render in a single style). Instead, these bits contribute to the layer offset calculation, expanding
|
|
224
|
+
the addressable emoji range to 4096 glyph slots (128 layers × 32 glyphs/layer).
|
|
225
|
+
|
|
226
|
+
**Note:** For layer coordinate calculation, only bits 0-12 are used (mask `0x1FFF`). The UNDERLINE and
|
|
227
|
+
STRIKETHROUGH flags (bits 13-14) are purely rendering effects and don't affect texture atlas positioning.
|
|
228
|
+
|
|
229
|
+
#### ID to 2D Array Position Examples
|
|
230
|
+
|
|
231
|
+
| Character | Style | Glyph ID | Calculation | Result |
|
|
232
|
+
|-----------|-------------|----------|------------------------|-----------------------|
|
|
233
|
+
| ' ' (32) | Normal | 0x0020 | 32÷32=1, 32%32=0 | Layer 1, Position 0 |
|
|
234
|
+
| 'A' (65) | Normal | 0x0041 | 65÷32=2, 65%32=1 | Layer 2, Position 1 |
|
|
235
|
+
| 'A' (65) | Bold+Italic | 0x0C41 | 3137÷32=98, 3137%32=1 | Layer 98, Position 1 |
|
|
236
|
+
| '€' | Normal | 0x0080 | Mapped to ID 128 | Layer 4, Position 0 |
|
|
237
|
+
| '中' (L) | Normal | 0x014A | 330÷32=10, 330%32=10 | Layer 10, Position 10 |
|
|
238
|
+
| '中' (R) | Normal | 0x014B | 331÷32=10, 331%32=11 | Layer 10, Position 11 |
|
|
239
|
+
| '🚀' (L) | Emoji | 0x1000 | 4096÷32=128, 4096%32=0 | Layer 128, Position 0 |
|
|
240
|
+
| '🚀' (R) | Emoji | 0x1001 | 4097÷32=128, 4097%32=1 | Layer 128, Position 1 |
|
|
241
|
+
|
|
242
|
+
The consistent modular arithmetic ensures that style variants maintain the same vertical position
|
|
243
|
+
within their respective layers, improving texture cache coherence. Double-width glyphs (both fullwidth
|
|
244
|
+
characters and emoji) are rendered into two consecutive glyph slots (left and right halves), each
|
|
245
|
+
occupying one cell position in the atlas.
|
|
246
|
+
|
|
247
|
+
#### Double-Width Glyphs
|
|
248
|
+
|
|
249
|
+
Both emoji and fullwidth characters (e.g., CJK ideographs) occupy two consecutive terminal cells.
|
|
250
|
+
The atlas stores these as left/right half-pairs with consecutive glyph IDs:
|
|
251
|
+
|
|
252
|
+
- **Fullwidth glyphs** are assigned IDs after all halfwidth glyphs, aligned to even boundaries
|
|
253
|
+
for efficient texture packing. The renderer distinguishes them via `halfwidth_glyphs_per_layer`.
|
|
254
|
+
- **Emoji glyphs** use the EMOJI flag (bit 12) and occupy the 0x1000-0x1FFF ID range.
|
|
255
|
+
|
|
256
|
+
Both types are rasterized at 2× cell width, then split into left (even ID) and right (odd ID) halves.
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
### ASCII Optimization
|
|
260
|
+
|
|
261
|
+
Non-ASCII character lookups use a HashMap to find their glyph IDs. ASCII characters (0-127) bypass
|
|
262
|
+
the HashMap lookup entirely through direct bit manipulation. For ASCII input, the glyph ID is computed
|
|
263
|
+
as `char_code | style_bits`, providing zero-overhead character mapping. This approach optimizes for
|
|
264
|
+
the common case while maintaining full Unicode capability.
|
|
265
|
+
|
|
266
|
+
### Dynamic Atlas: Flat Slot Addressing
|
|
267
|
+
|
|
268
|
+
The dynamic atlas uses a simpler flat addressing scheme with 12-bit slot IDs. Font styles are
|
|
269
|
+
tracked separately in a cache rather than encoded in the slot ID.
|
|
270
|
+
|
|
271
|
+
| Slot Range | Purpose | Capacity |
|
|
272
|
+
|-------------|-----------------------------|-----------------------------|
|
|
273
|
+
| 0-94 | ASCII (Normal style only) | 95 pre-allocated slots |
|
|
274
|
+
| 95-2047 | Normal glyphs (any style) | 1953 LRU-managed slots |
|
|
275
|
+
| 2048-4095 | Wide glyphs (emoji, CJK) | 1024 glyphs × 2 slots each |
|
|
276
|
+
|
|
277
|
+
**Key differences from static atlas:**
|
|
278
|
+
- **No style encoding in ID**: 'A' _italic_ and 'A' _bold_ occupy separate slots rather than computed IDs (0x0041 vs 0x0441)
|
|
279
|
+
- **LRU eviction**: When a region fills up, least-recently-used glyphs are evicted and re-rasterized on next access
|
|
280
|
+
- **On-demand rasterization**: Glyphs are rendered via `OffscreenCanvas` when first encountered
|
|
281
|
+
- **Texture lookup mask:** `0x0FFF` (12 bits) - flat slot index without style bits
|
|
282
|
+
|
|
283
|
+
**Slot to texture coordinate:**
|
|
284
|
+
```
|
|
285
|
+
layer = slot / 32
|
|
286
|
+
position = slot % 32
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
ASCII characters (0x20-0x7E) in _normal_ style are pre-loaded at startup and occupy fixed slots 0-94, requiring
|
|
290
|
+
no HashMap lookup for mapping. All other characters and styles are dynamically managed.
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
## GPU Buffer Architecture
|
|
294
|
+
|
|
295
|
+
The renderer uses six buffers managed through a Vertex Array Object (VAO) to achieve
|
|
296
|
+
single-draw-call rendering. Each buffer serves a specific purpose in the instanced
|
|
297
|
+
rendering pipeline, with careful attention to memory alignment and update patterns.
|
|
298
|
+
|
|
299
|
+
### Buffer Layout Summary
|
|
300
|
+
|
|
301
|
+
| Buffer | Type | Size | Usage | Update Freq | Purpose |
|
|
302
|
+
|-----------------------|------|--------------|----------------|-------------|-------------------|
|
|
303
|
+
| **Vertex** | VBO | 64 bytes | `STATIC_DRAW` | Never | Quad geometry |
|
|
304
|
+
| **Index** | IBO | 6 bytes | `STATIC_DRAW` | Never | Triangle indices |
|
|
305
|
+
| **Instance Position** | VBO | 4 bytes/cell | `STATIC_DRAW` | On resize | Grid coordinates |
|
|
306
|
+
| **Instance Cell** | VBO | 8 bytes/cell | `DYNAMIC_DRAW` | Per frame | Glyph ID + colors |
|
|
307
|
+
| **Vertex UBO** | UBO | 80 bytes | `STATIC_DRAW` | On resize | Projection matrix |
|
|
308
|
+
| **Fragment UBO** | UBO | 32 bytes | `STATIC_DRAW` | On resize | Cell metadata |
|
|
309
|
+
|
|
310
|
+
All vertex buffers are encapsulated within a single Vertex Array Object (VAO), enabling state-free
|
|
311
|
+
rendering with a single draw call.
|
|
312
|
+
|
|
313
|
+
The **Instance Position** and **Instance Cell** buffers are recreated when the terminal size changes,
|
|
314
|
+
|
|
315
|
+
### Vertex Attribute Bindings
|
|
316
|
+
|
|
317
|
+
| Location | Attribute | Type | Components | Divisor | Source Buffer |
|
|
318
|
+
|----------|-------------|---------|------------------|---------|-------------------|
|
|
319
|
+
| 0 | Position | `vec2` | x, y | 0 | Vertex |
|
|
320
|
+
| 1 | TexCoord | `vec2` | u, v | 0 | Vertex |
|
|
321
|
+
| 2 | InstancePos | `uvec2` | grid_x, grid_y | 1 | Instance Position |
|
|
322
|
+
| 3 | PackedData | `uvec2` | glyph_id, colors | 1 | Instance Cell |
|
|
323
|
+
|
|
324
|
+
### Instance Data Packing
|
|
325
|
+
|
|
326
|
+
The 8-byte `CellDynamic` structure is tightly packed to minimize bandwidth:
|
|
327
|
+
|
|
328
|
+
```
|
|
329
|
+
Byte Layout: [0][1][2][3][4][5][6][7]
|
|
330
|
+
└┬─┘ └──┬──┘ └──┬──┘
|
|
331
|
+
Glyph ID FG RGB BG RGB
|
|
332
|
+
(16-bit) (24-bit) (24-bit)
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
This layout enables the GPU to fetch all cell data in a single 64-bit read, with the glyph
|
|
336
|
+
ID encoding both the texture coordinate and style information as described in the [Glyph ID Bit
|
|
337
|
+
Layout](#glyph-id-bit-layout-16-bit) section.
|
|
338
|
+
|
|
339
|
+
### Memory Layout and Performance
|
|
340
|
+
|
|
341
|
+
For a typical 12×18 pixel font with ~5100 glyphs:
|
|
342
|
+
|
|
343
|
+
| Component | Size | Details |
|
|
344
|
+
|----------------------|-----------|--------------------------------------------|
|
|
345
|
+
| **2D Texture Array** | ~8.7 MB | 32(12+2)×(18+2)×160 RGBA (32 glyphs/layer) |
|
|
346
|
+
| **Vertex Buffers** | ~200 KB | For 200×80 terminal |
|
|
347
|
+
| **Cache Efficiency** | Good | Sequential glyphs in same layer |
|
|
348
|
+
| **Memory Access** | Coalesced | 64-bit aligned instance data |
|
|
349
|
+
|
|
350
|
+
The 1×32 grid layout ensures that adjacent terminal cells often access the same texture layer,
|
|
351
|
+
maximizing GPU cache hits. ASCII characters (the most common) are packed into the first 4 layers,
|
|
352
|
+
providing optimal memory locality for typical terminal content.
|
|
353
|
+
|
|
354
|
+
### Shader Pipeline
|
|
355
|
+
|
|
356
|
+
The renderer uses a branchless shader pipeline optimized for instanced rendering:
|
|
357
|
+
|
|
358
|
+
#### Vertex Shader (`cell.vert`)
|
|
359
|
+
Transforms cell geometry from grid space to screen space using per-instance attributes. The shader:
|
|
360
|
+
|
|
361
|
+
- Calculates cell position by multiplying grid coordinates with cell size
|
|
362
|
+
- Applies orthographic projection for pixel-perfect rendering
|
|
363
|
+
- Extracts glyph ID and RGB colors from packed instance data
|
|
364
|
+
- Passes pre-extracted colors as `flat` varyings to fragment shader
|
|
365
|
+
|
|
366
|
+
Color extraction is performed in the vertex shader rather than the fragment shader to work around
|
|
367
|
+
ANGLE bugs affecting uint bit operations on certain GPU drivers (AMD, Qualcomm).
|
|
368
|
+
|
|
369
|
+
#### Fragment Shader (`cell.frag`)
|
|
370
|
+
Performs the core rendering logic with efficient 2D array texture lookups:
|
|
371
|
+
|
|
372
|
+
- Uses pre-extracted glyph ID and colors from vertex shader
|
|
373
|
+
- Masks glyph ID with a configurable uniform (`0x1FFF` for static atlas, `0x0FFF` for dynamic) to compute layer index
|
|
374
|
+
- Computes layer index and vertical position using bit operations
|
|
375
|
+
- Samples from 2D texture array using direct layer indexing
|
|
376
|
+
- Detects emoji glyphs via bit 12 for special color handling
|
|
377
|
+
- Applies underline/strikethrough effects via bits 13-14
|
|
378
|
+
- Blends foreground/background colors with glyph alpha for anti-aliasing
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
### WebGL2 Feature Dependencies
|
|
382
|
+
|
|
383
|
+
The renderer requires WebGL2 for:
|
|
384
|
+
- **2D Texture Arrays** (`TEXTURE_2D_ARRAY`, `texStorage3D`, `texSubImage3D`)
|
|
385
|
+
- **Instanced Rendering** (`drawElementsInstanced`, `vertexAttribDivisor`)
|
|
386
|
+
- **Advanced Buffers** (`UNIFORM_BUFFER`, `vertexAttribIPointer`)
|
|
387
|
+
- **Vertex Array Objects** (`createVertexArray`)
|
|
388
|
+
|
|
389
|
+
## Build and Deployment
|
|
390
|
+
|
|
391
|
+
### Development Setup
|
|
392
|
+
```bash
|
|
393
|
+
# Install Rust toolchain
|
|
394
|
+
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
|
395
|
+
rustup target add wasm32-unknown-unknown
|
|
396
|
+
|
|
397
|
+
# Install tools
|
|
398
|
+
cargo install wasm-pack trunk
|
|
399
|
+
|
|
400
|
+
# Development server
|
|
401
|
+
trunk serve
|
|
402
|
+
|
|
403
|
+
# Production build
|
|
404
|
+
trunk build --release
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
## Design Decisions
|
|
408
|
+
|
|
409
|
+
### Why 1×32 Grid Per Layer?
|
|
410
|
+
|
|
411
|
+
- **GPU compatibility**: Single-column layout provides consistent memory access patterns
|
|
412
|
+
- **Simplified math**: Position within layer is just a matter of `glyph_id & 0x1F`
|
|
413
|
+
- **Cache efficiency**: Sequential glyphs (e.g., ASCII characters) are vertically contiguous
|
|
414
|
+
within the same layer, improving texture cache hit rates
|
|
415
|
+
|
|
416
|
+
### Why Separate Style Encoding?
|
|
417
|
+
|
|
418
|
+
- Avoids duplicating glyph definitions
|
|
419
|
+
- Enables runtime style switching without texture lookups
|
|
420
|
+
- Maintains consistent coordinates for style variants
|
|
421
|
+
|
|
422
|
+
## Limitations
|
|
423
|
+
|
|
424
|
+
- Maximum 1024 base glyphs (10-bit addressing)
|
|
425
|
+
- Fixed 4 style variants per glyph
|
|
426
|
+
- Monospace fonts only
|
|
427
|
+
- Single font family and font size per atlas
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
[API Badge]: https://docs.rs/beamterm-renderer/badge.svg
|
|
432
|
+
[API]: https://docs.rs/beamterm-renderer
|
|
433
|
+
[Crate Badge]: https://img.shields.io/crates/v/beamterm-renderer.svg
|
|
434
|
+
[Crate]: https://crates.io/crates/beamterm-renderer
|
|
435
|
+
[Deps.rs Badge]: https://deps.rs/repo/github/junkdog/beamterm-renderer/status.svg
|
|
436
|
+
[Deps.rs]: https://deps.rs/repo/github/junkdog/beamterm-renderer
|
|
437
|
+
[demos]: https://junkdog.github.io/beamterm/
|
|
438
|
+
[npm]: https://www.npmjs.com/package/@beamterm/renderer
|
|
439
|
+
[NPM Badge]: https://img.shields.io/npm/v/@beamterm/renderer.svg
|