cranpose-render-pixels 0.22.0

Software renderer for Cranpose scenes and RGBA framebuffers
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
//! The pixel formats the renderer draws into: straight-alpha RGBA bytes, and
//! the 16-bit pixels of small displays, which it draws without a second pass
//! over a frame of bytes.

use cranpose_render_common::{
    graph_scene::RenderDiagnostics,
    rgb565::{GlyphPixel, Rgb565, Rgb565Be, Rgb565Le},
    text_canvas::TextPixelRows,
};
use cranpose_ui_graphics::BlendMode;

use crate::draw::{blend_pixel, unit_byte};

// How a format takes a gradient's colors.
#[derive(Clone, Copy, PartialEq, Eq)]
pub(crate) enum GradientColors {
    // Dithered as Skia dithers a gradient to bytes, so that a gradient lands
    // on the bytes the Compose gradient it stands in for does.
    Dithered,
    // Undithered, for a format whose steps are coarser than that dither,
    // which they would cut away.
    Plain,
}

// Puts over each pixel of `$pixels` what `$blend`, one paint, leaves over
// the pixel it names `$pixel`. The paint leaves one pixel over each pixel it
// meets, and a fill mostly meets runs of one pixel: a pixel equal to the one
// before takes what the paint left over that one, with one compare. A macro:
// a function that takes the blend as a closure compiles the closure as a
// call for each pixel on the ESP32, which costs the classic board's busier
// screen 2K instructions a frame.
macro_rules! blend_runs_of_one_pixel {
    ($pixels:expr, |$pixel:ident| $blend:expr) => {
        if let Some((first, rest)) = $pixels.split_first_mut() {
            let mut under = *first;
            {
                let $pixel = &mut *first;
                $blend;
            }
            let mut over = *first;
            for $pixel in rest {
                if *$pixel == under {
                    *$pixel = over;
                } else {
                    under = *$pixel;
                    $blend;
                    over = *$pixel;
                }
            }
        }
    };
}

pub(crate) trait Pixel: GlyphPixel {
    const GRADIENT: GradientColors;

    // A color worked out once to blend over many pixels of the format.
    type Paint: Copy;

    // The pixel an opaque color of `bytes` gives.
    fn opaque(bytes: [u8; 4]) -> Self;

    // `color`, straight-alpha unit values, to blend under `blend_mode`.
    fn paint(color: [f32; 4], blend_mode: BlendMode) -> Self::Paint;

    // `paint` over the pixel, its alpha scaled by `coverage`.
    fn blend_paint(&mut self, paint: Self::Paint, coverage: f32, diagnostics: &RenderDiagnostics);

    // `paint` over every pixel of `pixels`, which it covers whole. `table`
    // keeps what the paint left over the bytes it met before, for a format
    // that blends in floats; every call with the same table takes the same
    // paint.
    fn blend_run(
        pixels: &mut [Self],
        paint: Self::Paint,
        table: &mut Option<BlendTable>,
        diagnostics: &RenderDiagnostics,
    );

    // Paints every pixel of `pixels` with `value`.
    fn fill(pixels: &mut [Self], value: Self);

    // Paints `pixels` with the four pixels of `pattern` over and over from
    // the first.
    fn fill_pattern(pixels: &mut [Self], pattern: [Self; 4]);

    // The pixels as a text paints over them.
    fn text_rows(pixels: &mut [Self]) -> TextPixelRows<'_>;
}

impl Pixel for [u8; 4] {
    const GRADIENT: GradientColors = GradientColors::Dithered;

    type Paint = ([f32; 4], BlendMode);

    fn opaque(bytes: [u8; 4]) -> Self {
        bytes
    }

    fn paint(color: [f32; 4], blend_mode: BlendMode) -> Self::Paint {
        (color, blend_mode)
    }

    fn blend_paint(
        &mut self,
        (mut color, blend_mode): Self::Paint,
        coverage: f32,
        diagnostics: &RenderDiagnostics,
    ) {
        color[3] *= coverage;
        blend_pixel(self, color, blend_mode, diagnostics);
    }

    // Out of line: inline, the table grew the loop of a fill's rows past
    // where the compiler inlines the arrays of each row's paints.
    #[inline(never)]
    fn blend_run(
        pixels: &mut [Self],
        (color, blend_mode): Self::Paint,
        table: &mut Option<BlendTable>,
        diagnostics: &RenderDiagnostics,
    ) {
        let table = table.get_or_insert_with(BlendTable::default);
        blend_runs_of_one_pixel!(pixels, |pixel| table.blend(
            pixel,
            color,
            blend_mode,
            diagnostics
        ));
    }

    // As whole 32-bit words where the pixels are aligned for them, a store a
    // pixel instead of four.
    fn fill(pixels: &mut [Self], value: Self) {
        match bytemuck::try_cast_slice_mut::<[u8; 4], u32>(pixels) {
            Ok(words) => fill_words(words, u32::from_ne_bytes(value)),
            Err(_) => pixels.fill(value),
        }
    }

    fn fill_pattern(pixels: &mut [Self], pattern: [Self; 4]) {
        match bytemuck::try_cast_slice_mut::<[u8; 4], u32>(pixels) {
            Ok(words) => repeat(words, pattern.map(u32::from_ne_bytes)),
            Err(_) => repeat(pixels, pattern),
        }
    }

    fn text_rows(pixels: &mut [Self]) -> TextPixelRows<'_> {
        TextPixelRows::Rgba(pixels)
    }
}

// A color to blend over 16-bit pixels, which blend as their bytes would and
// then keep their high bits.
#[derive(Clone, Copy)]
pub(crate) enum Paint565 {
    // Source over, the blend of nearly every drawing: the color's bytes,
    // which blend in whole numbers as a glyph's do, and its alpha.
    Over { bytes: [u8; 3], alpha: f32 },
    // Any other mode blends the unit values as the bytes' blend does.
    Unit([f32; 4], BlendMode),
}

fn paint_565(color: [f32; 4], blend_mode: BlendMode) -> Paint565 {
    match blend_mode {
        BlendMode::SrcOver => Paint565::Over {
            bytes: [
                unit_byte(color[0]),
                unit_byte(color[1]),
                unit_byte(color[2]),
            ],
            alpha: color[3],
        },
        _ => Paint565::Unit(color, blend_mode),
    }
}

fn blend_paint_565<P: Rgb565 + GlyphPixel>(
    pixel: &mut P,
    paint: Paint565,
    coverage: f32,
    diagnostics: &RenderDiagnostics,
) {
    match paint {
        Paint565::Over {
            bytes: [red, green, blue],
            alpha,
        } => pixel.blend_bytes([red, green, blue, unit_byte(alpha * coverage)]),
        Paint565::Unit(mut color, blend_mode) => {
            color[3] *= coverage;
            let mut rgba = pixel.to_rgba();
            blend_pixel(&mut rgba, color, blend_mode, diagnostics);
            *pixel = P::from_rgb(rgba);
        }
    }
}

// Two 16-bit pixels a 32-bit store: the first pixel alone when it starts
// between words, and the last when one is left over.
fn fill_565(pixels: &mut [u16], value: u16) {
    let (head, words, tail) = bytemuck::pod_align_to_mut::<u16, u32>(pixels);
    head.fill(value);
    fill_words(words, u32::from(value) * 0x0001_0001);
    tail.fill(value);
}

// The pattern of four pixels over and over from the first: two words a
// step, which the pattern's halves fill wherever the words start.
fn fill_pattern_565(pixels: &mut [u16], pattern: [u16; 4]) {
    let (head, words, tail) = bytemuck::pod_align_to_mut::<u16, u32>(pixels);
    repeat(head, pattern);
    let start = head.len();
    let after = start + 2 * words.len();
    let word =
        |first: usize| -> u32 { bytemuck::cast([pattern[first & 3], pattern[(first + 1) & 3]]) };
    let pair = [word(start), word(start + 2)];
    let (pairs, rest) = words.as_chunks_mut::<2>();
    for chunk in pairs {
        *chunk = pair;
    }
    // At most one word, at an even word of the run.
    rest.fill(pair[0]);
    for (offset, pixel) in tail.iter_mut().enumerate() {
        *pixel = pattern[(after + offset) & 3];
    }
}

// A 16-bit format: its pixels blend as their bytes would and keep their
// high bits, and a text paints into them in the format's byte order.
trait Pixel565: Rgb565 + GlyphPixel + bytemuck::Pod + PartialEq {
    fn text_rows_565(pixels: &mut [Self]) -> TextPixelRows<'_>;
}

impl Pixel565 for Rgb565Le {
    fn text_rows_565(pixels: &mut [Self]) -> TextPixelRows<'_> {
        TextPixelRows::Rgb565Le(pixels)
    }
}

impl Pixel565 for Rgb565Be {
    fn text_rows_565(pixels: &mut [Self]) -> TextPixelRows<'_> {
        TextPixelRows::Rgb565Be(pixels)
    }
}

impl<P: Pixel565> Pixel for P {
    const GRADIENT: GradientColors = GradientColors::Plain;

    type Paint = Paint565;

    fn opaque(bytes: [u8; 4]) -> Self {
        Self::from_rgb(bytes)
    }

    fn paint(color: [f32; 4], blend_mode: BlendMode) -> Self::Paint {
        paint_565(color, blend_mode)
    }

    fn blend_paint(&mut self, paint: Self::Paint, coverage: f32, diagnostics: &RenderDiagnostics) {
        blend_paint_565(self, paint, coverage, diagnostics);
    }

    // Out of line, as the bytes' run is.
    #[inline(never)]
    fn blend_run(
        pixels: &mut [Self],
        paint: Self::Paint,
        table: &mut Option<BlendTable>,
        diagnostics: &RenderDiagnostics,
    ) {
        let Paint565::Over {
            bytes: [red, green, blue],
            alpha,
        } = paint
        else {
            for pixel in pixels {
                blend_paint_565(pixel, paint, 1.0, diagnostics);
            }
            return;
        };
        let src = [red, green, blue, unit_byte(alpha)];
        let table = table.get_or_insert_with(BlendTable::default);
        blend_runs_of_one_pixel!(pixels, |pixel| table.blend_565(pixel, src));
    }

    fn fill(pixels: &mut [Self], value: Self) {
        fill_565(bytemuck::cast_slice_mut(pixels), bytemuck::cast(value));
    }

    fn fill_pattern(pixels: &mut [Self], pattern: [Self; 4]) {
        fill_pattern_565(
            bytemuck::cast_slice_mut(pixels),
            pattern.map(bytemuck::cast),
        );
    }

    fn text_rows(pixels: &mut [Self]) -> TextPixelRows<'_> {
        Self::text_rows_565(pixels)
    }
}

// What one paint leaves over each byte of each channel, kept from the first
// pixel that shows the byte: `blend_pixel` works each channel apart, so a
// byte under the paint gives one byte, whatever the other channels hold.
pub(crate) struct BlendTable {
    bytes: [[u8; 256]; 4],
    // Bit `byte % 64` of word `byte / 64` of a channel: whether `bytes`
    // holds what the paint leaves over that byte.
    known: [[u64; 4]; 4],
}

impl Default for BlendTable {
    fn default() -> Self {
        Self {
            bytes: [[0; 256]; 4],
            known: [[0; 4]; 4],
        }
    }
}

impl BlendTable {
    // `color` over `pixel` under `blend_mode`, as `blend_pixel` puts it.
    fn blend(
        &mut self,
        pixel: &mut [u8; 4],
        color: [f32; 4],
        blend_mode: BlendMode,
        diagnostics: &RenderDiagnostics,
    ) {
        let under = *pixel;
        if let Some(over) = self.look_up(under) {
            *pixel = over;
            return;
        }
        blend_pixel(pixel, color, blend_mode, diagnostics);
        self.keep::<4>(under, *pixel);
    }

    // `src` over `pixel`, a 16-bit pixel, as `blend_bytes` puts it: over
    // the bytes its channels stand for, of which it keeps the high bits.
    fn blend_565<P: Rgb565 + GlyphPixel>(&mut self, pixel: &mut P, src: [u8; 4]) {
        let under = pixel.to_rgba();
        if let Some(over) = self.look_up_565(under) {
            *pixel = P::from_rgb(over);
            return;
        }
        pixel.blend_bytes(src);
        self.keep::<3>(under, pixel.to_rgba());
    }

    // Whether the table holds what the paint left over `byte` in `channel`.
    fn knows(&self, channel: usize, byte: u8) -> bool {
        let byte = usize::from(byte);
        self.known[channel][byte / 64] & (1 << (byte % 64)) != 0
    }

    // What the table holds over `under`, `None` while it misses a channel.
    // All four are tested before any is read, which the bytes' loop runs
    // fastest with: a test that returned at the first miss cost the paths
    // screen 3% more.
    fn look_up(&self, under: [u8; 4]) -> Option<[u8; 4]> {
        (0..4)
            .all(|channel| self.knows(channel, under[channel]))
            .then(|| [0, 1, 2, 3].map(|channel| self.bytes[channel][usize::from(under[channel])]))
    }

    // What the table holds over the three color channels of `under`, with
    // its alpha, `None` while it misses one. The 16-bit loop runs fastest
    // with a test that returns at the first miss: the test of all three
    // first cost the 16-bit paths screen 44% more.
    fn look_up_565(&self, under: [u8; 4]) -> Option<[u8; 4]> {
        let mut over = under;
        for channel in 0..3 {
            if !self.knows(channel, under[channel]) {
                return None;
            }
            over[channel] = self.bytes[channel][usize::from(under[channel])];
        }
        Some(over)
    }

    // Keeps that the paint left `over` over `under` in the first
    // `CHANNELS` channels.
    fn keep<const CHANNELS: usize>(&mut self, under: [u8; 4], over: [u8; 4]) {
        for channel in 0..CHANNELS {
            let byte = usize::from(under[channel]);
            self.bytes[channel][byte] = over[channel];
            self.known[channel][byte / 64] |= 1 << (byte % 64);
        }
    }
}

// `words.fill(word)` four words a step: a step of the plain loop stores one
// word, and its counter and branch cost more than the store.
fn fill_words(words: &mut [u32], word: u32) {
    let (quads, rest) = words.as_chunks_mut::<4>();
    for quad in quads {
        *quad = [word; 4];
    }
    rest.fill(word);
}

// Four items a step: a step of the plain loop stores one item, and its
// counter and branch cost more than the store.
fn repeat<T: Copy>(items: &mut [T], pattern: [T; 4]) {
    let (quads, rest) = items.as_chunks_mut::<4>();
    for quad in quads {
        *quad = pattern;
    }
    // At most three items: a copy of a slice calls memcpy for them.
    for (item, &value) in rest.iter_mut().zip(&pattern) {
        *item = value;
    }
}