1mod common;
5mod highp;
6mod lowp;
7
8use crate::peniko::{BlendMode, Compose, ImageQuality, Mix};
9use crate::region::Region;
10use alloc::vec;
11use alloc::vec::Vec;
12use core::fmt::Debug;
13use core::iter;
14use vello_common::coarse::{Cmd, WideTile};
15use vello_common::encode::{
16 EncodedBlurredRoundedRectangle, EncodedGradient, EncodedImage, EncodedKind, EncodedPaint,
17};
18use vello_common::paint::{ImageSource, Paint, PremulColor};
19use vello_common::tile::Tile;
20
21pub(crate) const COLOR_COMPONENTS: usize = 4;
22pub(crate) const TILE_HEIGHT_COMPONENTS: usize = Tile::HEIGHT as usize * COLOR_COMPONENTS;
23pub const SCRATCH_BUF_SIZE: usize =
24 WideTile::WIDTH as usize * Tile::HEIGHT as usize * COLOR_COMPONENTS;
25
26use crate::fine::common::gradient::linear::SimdLinearKind;
27use crate::fine::common::gradient::radial::SimdRadialKind;
28use crate::fine::common::gradient::sweep::SimdSweepKind;
29use crate::fine::common::gradient::{GradientPainter, calculate_t_vals};
30use crate::fine::common::image::{FilteredImagePainter, NNImagePainter, PlainNNImagePainter};
31use crate::fine::common::rounded_blurred_rect::BlurredRoundedRectFiller;
32use crate::util::{BlendModeExt, EncodedImageExt};
33pub use highp::F32Kernel;
34pub use lowp::U8Kernel;
35use vello_common::fearless_simd::{
36 Simd, SimdBase, SimdFloat, SimdInto, f32x4, f32x8, f32x16, u8x16, u8x32, u32x4, u32x8,
37};
38use vello_common::pixmap::Pixmap;
39use vello_common::simd::Splat4thExt;
40use vello_common::util::f32_to_u8;
41
42pub type ScratchBuf<F> = [F; SCRATCH_BUF_SIZE];
43
44pub trait Numeric: Copy + Default + Clone + Debug + PartialEq + Send + Sync + 'static {
45 const ZERO: Self;
46 const ONE: Self;
47}
48
49impl Numeric for f32 {
50 const ZERO: Self = 0.0;
51 const ONE: Self = 1.0;
52}
53
54impl Numeric for u8 {
55 const ZERO: Self = 0;
56 const ONE: Self = 255;
57}
58
59pub trait NumericVec<S: Simd>: Copy + Clone + Send + Sync {
60 fn from_f32(simd: S, val: f32x16<S>) -> Self;
61 fn from_u8(simd: S, val: u8x16<S>) -> Self;
62}
63
64impl<S: Simd> NumericVec<S> for f32x16<S> {
65 #[inline(always)]
66 fn from_f32(_: S, val: Self) -> Self {
67 val
68 }
69
70 #[inline(always)]
71 fn from_u8(simd: S, val: u8x16<S>) -> Self {
72 let converted = u8_to_f32(val);
73 converted * Self::splat(simd, 1.0 / 255.0)
74 }
75}
76
77impl<S: Simd> NumericVec<S> for u8x16<S> {
78 #[inline(always)]
79 fn from_f32(simd: S, val: f32x16<S>) -> Self {
80 let v1 = f32x16::splat(simd, 255.0);
81 let v2 = f32x16::splat(simd, 0.5);
82 let mulled = val.madd(v1, v2);
83
84 f32_to_u8(mulled)
85 }
86
87 #[inline(always)]
88 fn from_u8(_: S, val: Self) -> Self {
89 val
90 }
91}
92
93#[inline(always)]
94pub(crate) fn u8_to_f32<S: Simd>(val: u8x16<S>) -> f32x16<S> {
95 let simd = val.simd;
96 let zeroes = u8x16::splat(simd, 0);
97
98 let zip1 = simd.zip_high_u8x16(val, zeroes);
99 let zip2 = simd.zip_low_u8x16(val, zeroes);
100
101 let p1 = simd.zip_low_u8x16(zip2, zeroes).reinterpret_u32().cvt_f32();
102 let p2 = simd
103 .zip_high_u8x16(zip2, zeroes)
104 .reinterpret_u32()
105 .cvt_f32();
106 let p3 = simd.zip_low_u8x16(zip1, zeroes).reinterpret_u32().cvt_f32();
107 let p4 = simd
108 .zip_high_u8x16(zip1, zeroes)
109 .reinterpret_u32()
110 .cvt_f32();
111
112 simd.combine_f32x8(simd.combine_f32x4(p1, p2), simd.combine_f32x4(p3, p4))
113}
114
115pub trait CompositeType<N: Numeric, S: Simd>: Copy + Clone + Send + Sync {
116 const LENGTH: usize;
117
118 fn from_slice(simd: S, slice: &[N]) -> Self;
119 fn from_color(simd: S, color: [N; 4]) -> Self;
120}
121
122impl<S: Simd> CompositeType<f32, S> for f32x16<S> {
123 const LENGTH: usize = 16;
124
125 #[inline(always)]
126 fn from_slice(simd: S, slice: &[f32]) -> Self {
127 <Self as SimdBase<_, _>>::from_slice(simd, slice)
128 }
129
130 #[inline(always)]
131 fn from_color(simd: S, color: [f32; 4]) -> Self {
132 Self::block_splat(f32x4::from_slice(simd, &color[..]))
133 }
134}
135
136impl<S: Simd> CompositeType<u8, S> for u8x32<S> {
137 const LENGTH: usize = 32;
138
139 #[inline(always)]
140 fn from_slice(simd: S, slice: &[u8]) -> Self {
141 <Self as SimdBase<_, _>>::from_slice(simd, slice)
142 }
143
144 #[inline(always)]
145 fn from_color(simd: S, color: [u8; 4]) -> Self {
146 u32x8::block_splat(u32x4::splat(simd, u32::from_ne_bytes(color))).reinterpret_u8()
147 }
148}
149
150pub trait FineKernel<S: Simd>: Send + Sync + 'static {
152 type Numeric: Numeric;
154 type Composite: CompositeType<Self::Numeric, S>;
156 type NumericVec: NumericVec<S>;
158
159 fn extract_color(color: PremulColor) -> [Self::Numeric; 4];
161 fn pack(simd: S, region: &mut Region<'_>, blend_buf: &[Self::Numeric]);
163 fn copy_solid(simd: S, target: &mut [Self::Numeric], color: [Self::Numeric; 4]);
165 fn gradient_painter<'a>(
167 simd: S,
168 gradient: &'a EncodedGradient,
169 t_vals: &'a [f32],
170 ) -> impl Painter + 'a {
171 simd.vectorize(
172 #[inline(always)]
173 || GradientPainter::new(simd, gradient, false, t_vals),
174 )
175 }
176 fn gradient_painter_with_undefined<'a>(
178 simd: S,
179 gradient: &'a EncodedGradient,
180 t_vals: &'a [f32],
181 ) -> impl Painter + 'a {
182 simd.vectorize(
183 #[inline(always)]
184 || GradientPainter::new(simd, gradient, true, t_vals),
185 )
186 }
187 fn plain_nn_image_painter<'a>(
192 simd: S,
193 image: &'a EncodedImage,
194 pixmap: &'a Pixmap,
195 start_x: u16,
196 start_y: u16,
197 ) -> impl Painter + 'a {
198 simd.vectorize(
199 #[inline(always)]
200 || PlainNNImagePainter::new(simd, image, pixmap, start_x, start_y),
201 )
202 }
203 fn nn_image_painter<'a>(
207 simd: S,
208 image: &'a EncodedImage,
209 pixmap: &'a Pixmap,
210 start_x: u16,
211 start_y: u16,
212 ) -> impl Painter + 'a {
213 simd.vectorize(
214 #[inline(always)]
215 || NNImagePainter::new(simd, image, pixmap, start_x, start_y),
216 )
217 }
218 fn medium_quality_image_painter<'a>(
220 simd: S,
221 image: &'a EncodedImage,
222 pixmap: &'a Pixmap,
223 start_x: u16,
224 start_y: u16,
225 ) -> impl Painter + 'a {
226 simd.vectorize(
227 #[inline(always)]
228 || FilteredImagePainter::new(simd, image, pixmap, start_x, start_y),
229 )
230 }
231 fn high_quality_image_painter<'a>(
233 simd: S,
234 image: &'a EncodedImage,
235 pixmap: &'a Pixmap,
236 start_x: u16,
237 start_y: u16,
238 ) -> impl Painter + 'a {
239 simd.vectorize(
240 #[inline(always)]
241 || FilteredImagePainter::new(simd, image, pixmap, start_x, start_y),
242 )
243 }
244 fn blurred_rounded_rectangle_painter(
246 simd: S,
247 rect: &EncodedBlurredRoundedRectangle,
248 start_x: u16,
249 start_y: u16,
250 ) -> impl Painter {
251 simd.vectorize(
252 #[inline(always)]
253 || BlurredRoundedRectFiller::new(simd, rect, start_x, start_y),
254 )
255 }
256 fn apply_mask(simd: S, dest: &mut [Self::Numeric], src: impl Iterator<Item = Self::NumericVec>);
258 fn apply_painter<'a>(simd: S, dest: &mut [Self::Numeric], painter: impl Painter + 'a);
260 fn alpha_composite_solid(
262 simd: S,
263 target: &mut [Self::Numeric],
264 src: [Self::Numeric; 4],
265 alphas: Option<&[u8]>,
266 );
267 fn alpha_composite_buffer(
269 simd: S,
270 dest: &mut [Self::Numeric],
271 src: &[Self::Numeric],
272 alphas: Option<&[u8]>,
273 );
274 fn blend(
276 simd: S,
277 dest: &mut [Self::Numeric],
278 src: impl Iterator<Item = Self::Composite>,
279 blend_mode: BlendMode,
280 alphas: Option<&[u8]>,
281 );
282}
283
284#[derive(Debug)]
286pub struct Fine<S: Simd, T: FineKernel<S>> {
287 pub(crate) wide_coords: (u16, u16),
289 pub(crate) blend_buf: Vec<ScratchBuf<T::Numeric>>,
291 pub(crate) paint_buf: ScratchBuf<T::Numeric>,
293 pub(crate) f32_buf: Vec<f32>,
295 pub(crate) simd: S,
296}
297
298impl<S: Simd, T: FineKernel<S>> Fine<S, T> {
299 pub fn new(simd: S) -> Self {
300 Self {
301 simd,
302 wide_coords: (0, 0),
303 blend_buf: vec![[T::Numeric::ZERO; SCRATCH_BUF_SIZE]],
304 f32_buf: vec![0.0; SCRATCH_BUF_SIZE / 4],
305 paint_buf: [T::Numeric::ZERO; SCRATCH_BUF_SIZE],
306 }
307 }
308
309 pub fn set_coords(&mut self, x: u16, y: u16) {
310 self.wide_coords = (x, y);
311 }
312
313 pub fn clear(&mut self, premul_color: PremulColor) {
314 let converted_color = T::extract_color(premul_color);
315 let blend_buf = self.blend_buf.last_mut().unwrap();
316
317 T::copy_solid(self.simd, blend_buf, converted_color);
318 }
319
320 pub fn pack(&self, region: &mut Region<'_>) {
321 let blend_buf = self.blend_buf.last().unwrap();
322
323 T::pack(self.simd, region, blend_buf);
324 }
325
326 pub(crate) fn run_cmd(&mut self, cmd: &Cmd, alphas: &[u8], paints: &[EncodedPaint]) {
327 match cmd {
328 Cmd::Fill(f) => {
329 self.fill(
330 usize::from(f.x),
331 usize::from(f.width),
332 &f.paint,
333 f.blend_mode
334 .unwrap_or(BlendMode::new(Mix::Normal, Compose::SrcOver)),
335 paints,
336 None,
337 );
338 }
339 Cmd::AlphaFill(s) => {
340 self.fill(
341 usize::from(s.x),
342 usize::from(s.width),
343 &s.paint,
344 s.blend_mode
345 .unwrap_or(BlendMode::new(Mix::Normal, Compose::SrcOver)),
346 paints,
347 Some(&alphas[s.alpha_idx..]),
348 );
349 }
350 Cmd::PushBuf => {
351 self.blend_buf.push([T::Numeric::ZERO; SCRATCH_BUF_SIZE]);
352 }
353 Cmd::PopBuf => {
354 self.blend_buf.pop();
355 }
356 Cmd::ClipFill(cf) => {
357 self.clip(cf.x as usize, cf.width as usize, None);
358 }
359 Cmd::ClipStrip(cs) => {
360 self.clip(
361 cs.x as usize,
362 cs.width as usize,
363 Some(&alphas[cs.alpha_idx..]),
364 );
365 }
366 Cmd::Blend(b) => self.blend(*b),
367 Cmd::Mask(m) => {
368 let start_x = self.wide_coords.0 * WideTile::WIDTH;
369 let start_y = self.wide_coords.1 * Tile::HEIGHT;
370
371 let blend_buf = self.blend_buf.last_mut().unwrap();
372
373 let width = (blend_buf.len() / (Tile::HEIGHT as usize * COLOR_COMPONENTS)) as u16;
374 let y = start_y as u32 + u32x4::from_slice(self.simd, &[0, 1, 2, 3]);
375
376 let iter = (start_x..(start_x + width)).map(|x| {
377 let x_in_range = x < m.width();
378
379 macro_rules! sample {
380 ($idx:expr) => {
381 if x_in_range && (y[$idx] as u16) < m.height() {
382 m.sample(x, y[$idx] as u16)
383 } else {
384 0
385 }
386 };
387 }
388
389 let s1 = sample!(0);
390 let s2 = sample!(1);
391 let s3 = sample!(2);
392 let s4 = sample!(3);
393
394 let samples = u8x16::from_slice(
395 self.simd,
396 &[
397 s1, s1, s1, s1, s2, s2, s2, s2, s3, s3, s3, s3, s4, s4, s4, s4,
398 ],
399 );
400 T::NumericVec::from_u8(self.simd, samples)
401 });
402
403 T::apply_mask(self.simd, blend_buf, iter);
404 }
405 Cmd::Opacity(o) => {
406 if *o != 1.0 {
407 let blend_buf = self.blend_buf.last_mut().unwrap();
408
409 T::apply_mask(
410 self.simd,
411 blend_buf,
412 iter::repeat(T::NumericVec::from_f32(
413 self.simd,
414 f32x16::splat(self.simd, *o),
415 )),
416 );
417 }
418 }
419 }
420 }
421
422 pub fn fill(
426 &mut self,
427 x: usize,
428 width: usize,
429 fill: &Paint,
430 blend_mode: BlendMode,
431 encoded_paints: &[EncodedPaint],
432 alphas: Option<&[u8]>,
433 ) {
434 let blend_buf = &mut self.blend_buf.last_mut().unwrap()[x * TILE_HEIGHT_COMPONENTS..]
435 [..TILE_HEIGHT_COMPONENTS * width];
436 let default_blend = blend_mode.is_default();
437
438 match fill {
439 Paint::Solid(color) => {
440 let color = T::extract_color(*color);
441
442 if color[3] == T::Numeric::ONE && default_blend && alphas.is_none() {
445 T::copy_solid(self.simd, blend_buf, color);
446
447 return;
448 }
449
450 if default_blend {
451 T::alpha_composite_solid(self.simd, blend_buf, color, alphas);
452 } else {
453 T::blend(
454 self.simd,
455 blend_buf,
456 iter::repeat(T::Composite::from_color(self.simd, color)),
457 blend_mode,
458 alphas,
459 );
460 }
461 }
462 Paint::Indexed(paint) => {
463 let color_buf = &mut self.paint_buf[x * TILE_HEIGHT_COMPONENTS..]
464 [..TILE_HEIGHT_COMPONENTS * width];
465
466 let encoded_paint = &encoded_paints[paint.index()];
467
468 let start_x = self.wide_coords.0 * WideTile::WIDTH + x as u16;
469 let start_y = self.wide_coords.1 * Tile::HEIGHT;
470
471 macro_rules! fill_complex_paint {
474 ($has_opacities:expr, $filler:expr) => {
475 if $has_opacities || alphas.is_some() {
476 T::apply_painter(self.simd, color_buf, $filler);
477
478 if default_blend {
479 T::alpha_composite_buffer(self.simd, blend_buf, color_buf, alphas);
480 } else {
481 T::blend(
482 self.simd,
483 blend_buf,
484 color_buf
485 .chunks_exact(T::Composite::LENGTH)
486 .map(|s| T::Composite::from_slice(self.simd, s)),
487 blend_mode,
488 alphas,
489 );
490 }
491 } else {
492 T::apply_painter(self.simd, blend_buf, $filler);
495 }
496 };
497 }
498
499 match encoded_paint {
500 EncodedPaint::BlurredRoundedRect(b) => {
501 fill_complex_paint!(
502 true,
503 T::blurred_rounded_rectangle_painter(self.simd, b, start_x, start_y)
504 );
505 }
506 EncodedPaint::Gradient(g) => {
507 let f32_buf = &mut self.f32_buf[..width * Tile::HEIGHT as usize];
513
514 match &g.kind {
515 EncodedKind::Linear(l) => {
516 calculate_t_vals(
517 self.simd,
518 SimdLinearKind::new(self.simd, *l),
519 f32_buf,
520 g,
521 start_x,
522 start_y,
523 );
524
525 fill_complex_paint!(
526 g.has_opacities,
527 T::gradient_painter(self.simd, g, f32_buf)
528 );
529 }
530 EncodedKind::Sweep(s) => {
531 calculate_t_vals(
532 self.simd,
533 SimdSweepKind::new(self.simd, s),
534 f32_buf,
535 g,
536 start_x,
537 start_y,
538 );
539
540 fill_complex_paint!(
541 g.has_opacities,
542 T::gradient_painter(self.simd, g, f32_buf)
543 );
544 }
545 EncodedKind::Radial(r) => {
546 calculate_t_vals(
547 self.simd,
548 SimdRadialKind::new(self.simd, r),
549 f32_buf,
550 g,
551 start_x,
552 start_y,
553 );
554
555 if r.has_undefined() {
556 fill_complex_paint!(
557 g.has_opacities,
558 T::gradient_painter_with_undefined(self.simd, g, f32_buf)
559 );
560 } else {
561 fill_complex_paint!(
562 g.has_opacities,
563 T::gradient_painter(self.simd, g, f32_buf)
564 );
565 }
566 }
567 }
568 }
569 EncodedPaint::Image(i) => {
570 let ImageSource::Pixmap(pixmap) = &i.source else {
571 panic!("vello_cpu doesn't support the opaque image source.");
572 };
573
574 match (i.has_skew(), i.nearest_neighbor()) {
575 (_, false) => {
576 if i.sampler.quality == ImageQuality::Medium {
577 fill_complex_paint!(
578 i.has_opacities,
579 T::medium_quality_image_painter(
580 self.simd, i, pixmap, start_x, start_y
581 )
582 );
583 } else {
584 fill_complex_paint!(
585 i.has_opacities,
586 T::high_quality_image_painter(
587 self.simd, i, pixmap, start_x, start_y
588 )
589 );
590 }
591 }
592 (false, true) => {
593 fill_complex_paint!(
594 i.has_opacities,
595 T::plain_nn_image_painter(
596 self.simd, i, pixmap, start_x, start_y
597 )
598 );
599 }
600 (true, true) => {
601 fill_complex_paint!(
602 i.has_opacities,
603 T::nn_image_painter(self.simd, i, pixmap, start_x, start_y)
604 );
605 }
606 }
607 }
608 }
609 }
610 }
611 }
612
613 fn blend(&mut self, blend_mode: BlendMode) {
614 let (source_buffer, rest) = self.blend_buf.split_last_mut().unwrap();
615 let target_buffer = rest.last_mut().unwrap();
616
617 if blend_mode.is_default() {
618 T::alpha_composite_buffer(self.simd, target_buffer, source_buffer, None);
619 } else {
620 T::blend(
621 self.simd,
622 target_buffer,
623 source_buffer
624 .chunks_exact(T::Composite::LENGTH)
625 .map(|s| T::Composite::from_slice(self.simd, s)),
626 blend_mode,
627 None,
628 );
629 }
630 }
631
632 fn clip(&mut self, x: usize, width: usize, alphas: Option<&[u8]>) {
633 let (source_buffer, rest) = self.blend_buf.split_last_mut().unwrap();
634 let target_buffer = rest.last_mut().unwrap();
635
636 let source_buffer =
637 &mut source_buffer[x * TILE_HEIGHT_COMPONENTS..][..TILE_HEIGHT_COMPONENTS * width];
638 let target_buffer =
639 &mut target_buffer[x * TILE_HEIGHT_COMPONENTS..][..TILE_HEIGHT_COMPONENTS * width];
640
641 T::alpha_composite_buffer(self.simd, target_buffer, source_buffer, alphas);
642 }
643}
644
645pub trait Painter {
649 fn paint_u8(&mut self, buf: &mut [u8]);
650 fn paint_f32(&mut self, buf: &mut [f32]);
651}
652
653pub trait PosExt<S: Simd> {
655 fn splat_pos(simd: S, pos: f32, x_advance: f32, y_advance: f32) -> Self;
656}
657
658impl<S: Simd> PosExt<S> for f32x4<S> {
659 #[inline(always)]
660 fn splat_pos(simd: S, pos: f32, _: f32, y_advance: f32) -> Self {
661 let columns: [f32; Tile::HEIGHT as usize] = [0.0, 1.0, 2.0, 3.0];
662 let column_mask: Self = columns.simd_into(simd);
663
664 column_mask.madd(Self::splat(simd, y_advance), Self::splat(simd, pos))
665 }
666}
667
668impl<S: Simd> PosExt<S> for f32x8<S> {
669 #[inline(always)]
670 fn splat_pos(simd: S, pos: f32, x_advance: f32, y_advance: f32) -> Self {
671 simd.combine_f32x4(
672 f32x4::splat_pos(simd, pos, x_advance, y_advance),
673 f32x4::splat_pos(simd, pos + x_advance, x_advance, y_advance),
674 )
675 }
676}
677
678pub(crate) struct ShaderResultF32<S: Simd> {
680 pub(crate) r: f32x8<S>,
681 pub(crate) g: f32x8<S>,
682 pub(crate) b: f32x8<S>,
683 pub(crate) a: f32x8<S>,
684}
685
686impl<S: Simd> ShaderResultF32<S> {
687 #[inline(always)]
689 pub(crate) fn get(&self) -> (f32x16<S>, f32x16<S>) {
690 let (r_1, r_2) = self.r.simd.split_f32x8(self.r);
691 let (g_1, g_2) = self.g.simd.split_f32x8(self.g);
692 let (b_1, b_2) = self.b.simd.split_f32x8(self.b);
693 let (a_1, a_2) = self.a.simd.split_f32x8(self.a);
694
695 let first = self.r.simd.combine_f32x8(
696 self.r.simd.combine_f32x4(r_1, g_1),
697 self.r.simd.combine_f32x4(b_1, a_1),
698 );
699
700 let second = self.r.simd.combine_f32x8(
701 self.r.simd.combine_f32x4(r_2, g_2),
702 self.r.simd.combine_f32x4(b_2, a_2),
703 );
704
705 (first, second)
706 }
707}
708
709mod macros {
710 macro_rules! f32x16_painter {
713 ($($type_path:tt)+) => {
714 impl<S: Simd> crate::fine::Painter for $($type_path)+ {
715 fn paint_u8(&mut self, buf: &mut [u8]) {
716 use vello_common::fearless_simd::*;
717 use crate::fine::NumericVec;
718
719 self.simd.vectorize(#[inline(always)] || {
720 for chunk in buf.chunks_exact_mut(16) {
721 let next = self.next().unwrap();
722 let converted = u8x16::<S>::from_f32(next.simd, next);
723 chunk.copy_from_slice(&converted.val);
724 }
725 })
726 }
727
728 fn paint_f32(&mut self, buf: &mut [f32]) {
729 self.simd.vectorize(#[inline(always)] || {
730 for chunk in buf.chunks_exact_mut(16) {
731 let next = self.next().unwrap();
732 chunk.copy_from_slice(&next.val);
733 }
734 })
735 }
736 }
737 };
738 }
739
740 macro_rules! u8x16_painter {
743 ($($type_path:tt)+) => {
744 impl<S: Simd> crate::fine::Painter for $($type_path)+ {
745 fn paint_u8(&mut self, buf: &mut [u8]) {
746 self.simd.vectorize(#[inline(always)] || {
747 for chunk in buf.chunks_exact_mut(16) {
748 let next = self.next().unwrap();
749 chunk.copy_from_slice(&next.val);
750 }
751 })
752 }
753
754 fn paint_f32(&mut self, buf: &mut [f32]) {
755 use vello_common::fearless_simd::*;
756 use crate::fine::NumericVec;
757
758 self.simd.vectorize(#[inline(always)] || {
759 for chunk in buf.chunks_exact_mut(16) {
760 let next = self.next().unwrap();
761 let converted = f32x16::<S>::from_u8(next.simd, next);
762 chunk.copy_from_slice(&converted.val);
763 }
764 })
765 }
766 }
767 };
768 }
769
770 pub(crate) use f32x16_painter;
771 pub(crate) use u8x16_painter;
772}