1use super::{atlas::AtlasAllocator, sprite_cache::SpriteCache};
2use crate::{
3 color::ColorU,
4 geometry::{
5 rect::RectF,
6 vector::{vec2f, vec2i, Vector2F, Vector2I},
7 },
8 platform,
9 scene::Layer,
10 Scene,
11};
12use anyhow::{anyhow, Result};
13use cocoa::foundation::NSUInteger;
14use metal::{MTLPixelFormat, MTLResourceOptions, NSRange};
15use shaders::{ToFloat2 as _, ToUchar4 as _};
16use std::{collections::HashMap, ffi::c_void, mem, sync::Arc};
17
18const SHADERS_METALLIB: &'static [u8] =
19 include_bytes!(concat!(env!("OUT_DIR"), "/shaders.metallib"));
20const INSTANCE_BUFFER_SIZE: usize = 1024 * 1024; // This is an arbitrary decision. There's probably a more optimal value.
21
22pub struct Renderer {
23 device: metal::Device,
24 command_buffer: metal::CommandBuffer,
25 sprite_cache: SpriteCache,
26 path_stencils: AtlasAllocator,
27 quad_pipeline_state: metal::RenderPipelineState,
28 shadow_pipeline_state: metal::RenderPipelineState,
29 sprite_pipeline_state: metal::RenderPipelineState,
30 path_stencil_pipeline_state: metal::RenderPipelineState,
31 unit_vertices: metal::Buffer,
32 instances: metal::Buffer,
33}
34
35struct PathSprite {
36 layer_id: usize,
37 atlas_id: usize,
38 sprite: shaders::GPUISprite,
39}
40
41impl Renderer {
42 pub fn new(
43 device: metal::Device,
44 command_buffer: metal::CommandBuffer,
45 pixel_format: metal::MTLPixelFormat,
46 fonts: Arc<dyn platform::FontSystem>,
47 ) -> Result<Self> {
48 let library = device
49 .new_library_with_data(SHADERS_METALLIB)
50 .map_err(|message| anyhow!("error building metal library: {}", message))?;
51
52 let unit_vertices = [
53 (0., 0.).to_float2(),
54 (1., 0.).to_float2(),
55 (0., 1.).to_float2(),
56 (0., 1.).to_float2(),
57 (1., 0.).to_float2(),
58 (1., 1.).to_float2(),
59 ];
60 let unit_vertices = device.new_buffer_with_data(
61 unit_vertices.as_ptr() as *const c_void,
62 (unit_vertices.len() * mem::size_of::<shaders::vector_float2>()) as u64,
63 MTLResourceOptions::StorageModeManaged,
64 );
65 let instances = device.new_buffer(
66 INSTANCE_BUFFER_SIZE as u64,
67 MTLResourceOptions::StorageModeManaged,
68 );
69
70 let path_stencil_pixel_format = metal::MTLPixelFormat::Stencil8;
71 let path_stencil_descriptor = metal::TextureDescriptor::new();
72 path_stencil_descriptor.set_width(2048);
73 path_stencil_descriptor.set_height(2048);
74 path_stencil_descriptor.set_pixel_format(path_stencil_pixel_format);
75 path_stencil_descriptor.set_usage(metal::MTLTextureUsage::RenderTarget);
76 path_stencil_descriptor.set_storage_mode(metal::MTLStorageMode::Private);
77
78 Ok(Self {
79 device,
80 command_buffer,
81 sprite_cache: SpriteCache::new(device.clone(), vec2i(1024, 768), fonts),
82 path_stencils: AtlasAllocator::new(device.clone(), path_stencil_descriptor),
83 quad_pipeline_state: build_pipeline_state(
84 &device,
85 &library,
86 "quad",
87 "quad_vertex",
88 "quad_fragment",
89 pixel_format,
90 )?,
91 shadow_pipeline_state: build_pipeline_state(
92 &device,
93 &library,
94 "shadow",
95 "shadow_vertex",
96 "shadow_fragment",
97 pixel_format,
98 )?,
99 sprite_pipeline_state: build_pipeline_state(
100 &device,
101 &library,
102 "sprite",
103 "sprite_vertex",
104 "sprite_fragment",
105 pixel_format,
106 )?,
107 path_stencil_pipeline_state: build_stencil_pipeline_state(
108 &device,
109 &library,
110 "path_winding",
111 "path_winding_vertex",
112 "path_winding_fragment",
113 path_stencil_pixel_format,
114 )?,
115 unit_vertices,
116 instances,
117 })
118 }
119
120 pub fn render(&mut self, scene: &Scene, drawable_size: Vector2F, output: &metal::TextureRef) {
121 let mut offset = 0;
122 self.render_path_stencils(scene, &mut offset, drawable_size);
123 self.render_layers(scene, &mut offset, drawable_size, output);
124 }
125
126 fn render_path_stencils(
127 &mut self,
128 scene: &Scene,
129 offset: &mut usize,
130 drawable_size: Vector2F,
131 ) -> Vec<PathSprite> {
132 let mut stencils = Vec::new();
133 let mut vertices = Vec::<shaders::GPUIPathVertex>::new();
134 let mut current_atlas_id = None;
135 for (layer_id, layer) in scene.layers().iter().enumerate() {
136 for path in layer.paths() {
137 // Push a PathStencil struct for use later when sampling from the atlas as we draw the content of the layers
138 let size = path.bounds.size().ceil().to_i32();
139 let (atlas_id, atlas_origin) = self.path_stencils.allocate(size).unwrap();
140 stencils.push(PathSprite {
141 layer_id,
142 atlas_id,
143 sprite: shaders::GPUISprite {
144 origin: path.bounds.origin().to_float2(),
145 size: size.to_float2(),
146 atlas_origin: atlas_origin.to_float2(),
147 color: path.color.to_uchar4(),
148 },
149 });
150
151 if current_atlas_id.map_or(false, |current_atlas_id| atlas_id != current_atlas_id) {
152 self.render_path_stencils_for_atlas(
153 scene,
154 offset,
155 drawable_size,
156 vertices.as_slice(),
157 self.path_stencils.texture(atlas_id).unwrap(),
158 );
159 vertices.clear();
160 }
161
162 current_atlas_id = Some(atlas_id);
163
164 // Populate the vertices by translating them to their appropriate location in the atlas.
165 for vertex in &path.vertices {
166 vertices.push(todo!());
167 }
168 }
169 }
170
171 if let Some(atlas_id) = current_atlas_id {
172 self.render_path_stencils_for_atlas(
173 scene,
174 offset,
175 drawable_size,
176 vertices.as_slice(),
177 self.path_stencils.texture(atlas_id).unwrap(),
178 );
179 }
180
181 stencils
182 }
183
184 fn render_path_stencils_for_atlas(
185 &mut self,
186 scene: &Scene,
187 offset: &mut usize,
188 drawable_size: Vector2F,
189 vertices: &[shaders::GPUIPathVertex],
190 texture: &metal::TextureRef,
191 ) {
192 // let render_pass_descriptor = metal::RenderPassDescriptor::new();
193 // let stencil_attachment = render_pass_descriptor.stencil_attachment().unwrap();
194 // stencil_attachment.set_texture(Some(&self.path_winding_texture));
195 // stencil_attachment.set_load_action(metal::MTLLoadAction::Clear);
196 // stencil_attachment.set_store_action(metal::MTLStoreAction::Store);
197 // let winding_command_encoder = self
198 // .command_buffer
199 // .new_render_command_encoder(render_pass_descriptor);
200
201 // Dubious shit that may be valuable:
202
203 // for path in scene.paths() {
204 // winding_command_encoder.set_render_pipeline_state(&self.path_stencil_pipeline_state);
205 // winding_command_encoder.set_vertex_buffer(
206 // shaders::GPUIPathWindingVertexInputIndex_GPUIPathWindingVertexInputIndexVertices
207 // as u64,
208 // Some(&self.instances),
209 // *offset as u64,
210 // );
211 // winding_command_encoder.set_vertex_bytes(
212 // shaders::GPUIPathWindingVertexInputIndex_GPUIPathWindingVertexInputIndexViewportSize
213 // as u64,
214 // mem::size_of::<shaders::vector_float2>() as u64,
215 // [drawable_size.to_float2()].as_ptr() as *const c_void,
216 // );
217
218 // let buffer_contents = unsafe {
219 // (self.instances.contents() as *mut u8).offset(*offset as isize)
220 // as *mut shaders::GPUIPathVertex
221 // };
222
223 // for (ix, vertex) in paths.iter().flat_map(|p| &p.vertices).enumerate() {
224 // let shader_vertex = shaders::GPUIPathVertex {
225 // xy_position: vertex.xy_position.to_float2(),
226 // st_position: vertex.st_position.to_float2(),
227 // };
228 // unsafe {
229 // *(buffer_contents.offset(ix as isize)) = shader_vertex;
230 // }
231 // }
232
233 // self.instances.did_modify_range(NSRange {
234 // location: *offset as u64,
235 // length: (next_offset - *offset) as u64,
236 // });
237 // *offset = next_offset;
238
239 // winding_command_encoder.draw_primitives(
240 // metal::MTLPrimitiveType::Triangle,
241 // 0,
242 // vertex_count as u64,
243 // );
244 // winding_command_encoder.end_encoding();
245 // }
246 }
247
248 fn render_layers(
249 &mut self,
250 scene: &Scene,
251 offset: &mut usize,
252 drawable_size: Vector2F,
253 output: &metal::TextureRef,
254 ) {
255 let render_pass_descriptor = metal::RenderPassDescriptor::new();
256 let color_attachment = render_pass_descriptor
257 .color_attachments()
258 .object_at(0)
259 .unwrap();
260 color_attachment.set_texture(Some(output));
261 color_attachment.set_load_action(metal::MTLLoadAction::Clear);
262 color_attachment.set_store_action(metal::MTLStoreAction::Store);
263 color_attachment.set_clear_color(metal::MTLClearColor::new(0., 0., 0., 1.));
264 let command_encoder = self
265 .command_buffer
266 .new_render_command_encoder(render_pass_descriptor);
267
268 command_encoder.set_viewport(metal::MTLViewport {
269 originX: 0.0,
270 originY: 0.0,
271 width: drawable_size.x() as f64,
272 height: drawable_size.y() as f64,
273 znear: 0.0,
274 zfar: 1.0,
275 });
276
277 for layer in scene.layers() {
278 self.clip(scene, layer, drawable_size, command_encoder);
279 self.render_shadows(scene, layer, &mut offset, drawable_size, command_encoder);
280 self.render_quads(scene, layer, &mut offset, drawable_size, command_encoder);
281 self.render_sprites(scene, layer, &mut offset, drawable_size, command_encoder);
282 }
283
284 command_encoder.end_encoding();
285 }
286
287 fn clip(
288 &mut self,
289 scene: &Scene,
290 layer: &Layer,
291 drawable_size: Vector2F,
292 command_encoder: &metal::RenderCommandEncoderRef,
293 ) {
294 let clip_bounds = layer.clip_bounds().unwrap_or(RectF::new(
295 vec2f(0., 0.),
296 drawable_size / scene.scale_factor(),
297 )) * scene.scale_factor();
298 command_encoder.set_scissor_rect(metal::MTLScissorRect {
299 x: clip_bounds.origin_x() as NSUInteger,
300 y: clip_bounds.origin_y() as NSUInteger,
301 width: clip_bounds.width() as NSUInteger,
302 height: clip_bounds.height() as NSUInteger,
303 });
304 }
305
306 fn render_shadows(
307 &mut self,
308 scene: &Scene,
309 layer: &Layer,
310 offset: &mut usize,
311 drawable_size: Vector2F,
312 command_encoder: &metal::RenderCommandEncoderRef,
313 ) {
314 if layer.shadows().is_empty() {
315 return;
316 }
317
318 align_offset(offset);
319 let next_offset = *offset + layer.shadows().len() * mem::size_of::<shaders::GPUIShadow>();
320 assert!(
321 next_offset <= INSTANCE_BUFFER_SIZE,
322 "instance buffer exhausted"
323 );
324
325 command_encoder.set_render_pipeline_state(&self.shadow_pipeline_state);
326 command_encoder.set_vertex_buffer(
327 shaders::GPUIShadowInputIndex_GPUIShadowInputIndexVertices as u64,
328 Some(&self.unit_vertices),
329 0,
330 );
331 command_encoder.set_vertex_buffer(
332 shaders::GPUIShadowInputIndex_GPUIShadowInputIndexShadows as u64,
333 Some(&self.instances),
334 *offset as u64,
335 );
336 command_encoder.set_vertex_bytes(
337 shaders::GPUIShadowInputIndex_GPUIShadowInputIndexUniforms as u64,
338 mem::size_of::<shaders::GPUIUniforms>() as u64,
339 [shaders::GPUIUniforms {
340 viewport_size: drawable_size.to_float2(),
341 }]
342 .as_ptr() as *const c_void,
343 );
344
345 let buffer_contents = unsafe {
346 (self.instances.contents() as *mut u8).offset(*offset as isize)
347 as *mut shaders::GPUIShadow
348 };
349 for (ix, shadow) in layer.shadows().iter().enumerate() {
350 let shape_bounds = shadow.bounds * scene.scale_factor();
351 let shader_shadow = shaders::GPUIShadow {
352 origin: shape_bounds.origin().to_float2(),
353 size: shape_bounds.size().to_float2(),
354 corner_radius: shadow.corner_radius * scene.scale_factor(),
355 sigma: shadow.sigma,
356 color: shadow.color.to_uchar4(),
357 };
358 unsafe {
359 *(buffer_contents.offset(ix as isize)) = shader_shadow;
360 }
361 }
362
363 self.instances.did_modify_range(NSRange {
364 location: *offset as u64,
365 length: (next_offset - *offset) as u64,
366 });
367 *offset = next_offset;
368
369 command_encoder.draw_primitives_instanced(
370 metal::MTLPrimitiveType::Triangle,
371 0,
372 6,
373 layer.shadows().len() as u64,
374 );
375 }
376
377 fn render_quads(
378 &mut self,
379 scene: &Scene,
380 layer: &Layer,
381 offset: &mut usize,
382 drawable_size: Vector2F,
383 command_encoder: &metal::RenderCommandEncoderRef,
384 ) {
385 if layer.quads().is_empty() {
386 return;
387 }
388 align_offset(offset);
389 let next_offset = *offset + layer.quads().len() * mem::size_of::<shaders::GPUIQuad>();
390 assert!(
391 next_offset <= INSTANCE_BUFFER_SIZE,
392 "instance buffer exhausted"
393 );
394
395 command_encoder.set_render_pipeline_state(&self.quad_pipeline_state);
396 command_encoder.set_vertex_buffer(
397 shaders::GPUIQuadInputIndex_GPUIQuadInputIndexVertices as u64,
398 Some(&self.unit_vertices),
399 0,
400 );
401 command_encoder.set_vertex_buffer(
402 shaders::GPUIQuadInputIndex_GPUIQuadInputIndexQuads as u64,
403 Some(&self.instances),
404 *offset as u64,
405 );
406 command_encoder.set_vertex_bytes(
407 shaders::GPUIQuadInputIndex_GPUIQuadInputIndexUniforms as u64,
408 mem::size_of::<shaders::GPUIUniforms>() as u64,
409 [shaders::GPUIUniforms {
410 viewport_size: drawable_size.to_float2(),
411 }]
412 .as_ptr() as *const c_void,
413 );
414
415 let buffer_contents = unsafe {
416 (self.instances.contents() as *mut u8).offset(*offset as isize)
417 as *mut shaders::GPUIQuad
418 };
419 for (ix, quad) in layer.quads().iter().enumerate() {
420 let bounds = quad.bounds * scene.scale_factor();
421 let border_width = quad.border.width * scene.scale_factor();
422 let shader_quad = shaders::GPUIQuad {
423 origin: bounds.origin().to_float2(),
424 size: bounds.size().to_float2(),
425 background_color: quad
426 .background
427 .unwrap_or(ColorU::transparent_black())
428 .to_uchar4(),
429 border_top: border_width * (quad.border.top as usize as f32),
430 border_right: border_width * (quad.border.right as usize as f32),
431 border_bottom: border_width * (quad.border.bottom as usize as f32),
432 border_left: border_width * (quad.border.left as usize as f32),
433 border_color: quad
434 .border
435 .color
436 .unwrap_or(ColorU::transparent_black())
437 .to_uchar4(),
438 corner_radius: quad.corner_radius * scene.scale_factor(),
439 };
440 unsafe {
441 *(buffer_contents.offset(ix as isize)) = shader_quad;
442 }
443 }
444
445 self.instances.did_modify_range(NSRange {
446 location: *offset as u64,
447 length: (next_offset - *offset) as u64,
448 });
449 *offset = next_offset;
450
451 command_encoder.draw_primitives_instanced(
452 metal::MTLPrimitiveType::Triangle,
453 0,
454 6,
455 layer.quads().len() as u64,
456 );
457 }
458
459 fn render_sprites(
460 &mut self,
461 scene: &Scene,
462 layer: &Layer,
463 offset: &mut usize,
464 drawable_size: Vector2F,
465 command_encoder: &metal::RenderCommandEncoderRef,
466 ) {
467 if layer.glyphs().is_empty() {
468 return;
469 }
470
471 let mut sprites_by_atlas = HashMap::new();
472 for glyph in layer.glyphs() {
473 if let Some(sprite) = self.sprite_cache.render_glyph(
474 glyph.font_id,
475 glyph.font_size,
476 glyph.id,
477 glyph.origin,
478 scene.scale_factor(),
479 ) {
480 // Snap sprite to pixel grid.
481 let origin = (glyph.origin * scene.scale_factor()).floor() + sprite.offset.to_f32();
482 sprites_by_atlas
483 .entry(sprite.atlas_id)
484 .or_insert_with(Vec::new)
485 .push(shaders::GPUISprite {
486 origin: origin.to_float2(),
487 size: sprite.size.to_float2(),
488 atlas_origin: sprite.atlas_origin.to_float2(),
489 color: glyph.color.to_uchar4(),
490 });
491 }
492 }
493
494 command_encoder.set_render_pipeline_state(&self.sprite_pipeline_state);
495 command_encoder.set_vertex_buffer(
496 shaders::GPUISpriteVertexInputIndex_GPUISpriteVertexInputIndexVertices as u64,
497 Some(&self.unit_vertices),
498 0,
499 );
500 command_encoder.set_vertex_bytes(
501 shaders::GPUISpriteVertexInputIndex_GPUISpriteVertexInputIndexViewportSize as u64,
502 mem::size_of::<shaders::vector_float2>() as u64,
503 [drawable_size.to_float2()].as_ptr() as *const c_void,
504 );
505 command_encoder.set_vertex_bytes(
506 shaders::GPUISpriteVertexInputIndex_GPUISpriteVertexInputIndexAtlasSize as u64,
507 mem::size_of::<shaders::vector_float2>() as u64,
508 [self.sprite_cache.atlas_size().to_float2()].as_ptr() as *const c_void,
509 );
510
511 for (atlas_id, sprites) in sprites_by_atlas {
512 align_offset(offset);
513 let next_offset = *offset + sprites.len() * mem::size_of::<shaders::GPUISprite>();
514 assert!(
515 next_offset <= INSTANCE_BUFFER_SIZE,
516 "instance buffer exhausted"
517 );
518
519 command_encoder.set_vertex_buffer(
520 shaders::GPUISpriteVertexInputIndex_GPUISpriteVertexInputIndexSprites as u64,
521 Some(&self.instances),
522 *offset as u64,
523 );
524
525 let texture = self.sprite_cache.atlas_texture(atlas_id).unwrap();
526 command_encoder.set_fragment_texture(
527 shaders::GPUISpriteFragmentInputIndex_GPUISpriteFragmentInputIndexAtlas as u64,
528 Some(texture),
529 );
530
531 unsafe {
532 let buffer_contents = (self.instances.contents() as *mut u8)
533 .offset(*offset as isize)
534 as *mut shaders::GPUISprite;
535 std::ptr::copy_nonoverlapping(sprites.as_ptr(), buffer_contents, sprites.len());
536 }
537 self.instances.did_modify_range(NSRange {
538 location: *offset as u64,
539 length: (next_offset - *offset) as u64,
540 });
541 *offset = next_offset;
542
543 command_encoder.draw_primitives_instanced(
544 metal::MTLPrimitiveType::Triangle,
545 0,
546 6,
547 sprites.len() as u64,
548 );
549 }
550 }
551}
552
553fn align_offset(offset: &mut usize) {
554 let r = *offset % 256;
555 if r > 0 {
556 *offset += 256 - r; // Align to a multiple of 256 to make Metal happy
557 }
558}
559
560fn build_pipeline_state(
561 device: &metal::DeviceRef,
562 library: &metal::LibraryRef,
563 label: &str,
564 vertex_fn_name: &str,
565 fragment_fn_name: &str,
566 pixel_format: metal::MTLPixelFormat,
567) -> Result<metal::RenderPipelineState> {
568 let vertex_fn = library
569 .get_function(vertex_fn_name, None)
570 .map_err(|message| anyhow!("error locating vertex function: {}", message))?;
571 let fragment_fn = library
572 .get_function(fragment_fn_name, None)
573 .map_err(|message| anyhow!("error locating fragment function: {}", message))?;
574
575 let descriptor = metal::RenderPipelineDescriptor::new();
576 descriptor.set_label(label);
577 descriptor.set_vertex_function(Some(vertex_fn.as_ref()));
578 descriptor.set_fragment_function(Some(fragment_fn.as_ref()));
579 let color_attachment = descriptor.color_attachments().object_at(0).unwrap();
580 color_attachment.set_pixel_format(pixel_format);
581 color_attachment.set_blending_enabled(true);
582 color_attachment.set_rgb_blend_operation(metal::MTLBlendOperation::Add);
583 color_attachment.set_alpha_blend_operation(metal::MTLBlendOperation::Add);
584 color_attachment.set_source_rgb_blend_factor(metal::MTLBlendFactor::SourceAlpha);
585 color_attachment.set_source_alpha_blend_factor(metal::MTLBlendFactor::SourceAlpha);
586 color_attachment.set_destination_rgb_blend_factor(metal::MTLBlendFactor::OneMinusSourceAlpha);
587 color_attachment.set_destination_alpha_blend_factor(metal::MTLBlendFactor::OneMinusSourceAlpha);
588
589 device
590 .new_render_pipeline_state(&descriptor)
591 .map_err(|message| anyhow!("could not create render pipeline state: {}", message))
592}
593
594fn build_stencil_pipeline_state(
595 device: &metal::DeviceRef,
596 library: &metal::LibraryRef,
597 label: &str,
598 vertex_fn_name: &str,
599 fragment_fn_name: &str,
600 pixel_format: metal::MTLPixelFormat,
601) -> Result<metal::RenderPipelineState> {
602 let vertex_fn = library
603 .get_function(vertex_fn_name, None)
604 .map_err(|message| anyhow!("error locating vertex function: {}", message))?;
605 let fragment_fn = library
606 .get_function(fragment_fn_name, None)
607 .map_err(|message| anyhow!("error locating fragment function: {}", message))?;
608
609 let descriptor = metal::RenderPipelineDescriptor::new();
610 descriptor.set_label(label);
611 descriptor.set_vertex_function(Some(vertex_fn.as_ref()));
612 descriptor.set_fragment_function(Some(fragment_fn.as_ref()));
613 descriptor.set_stencil_attachment_pixel_format(pixel_format);
614
615 device
616 .new_render_pipeline_state(&descriptor)
617 .map_err(|message| anyhow!("could not create render pipeline state: {}", message))
618}
619
620mod shaders {
621 #![allow(non_upper_case_globals)]
622 #![allow(non_camel_case_types)]
623 #![allow(non_snake_case)]
624
625 use pathfinder_geometry::vector::Vector2I;
626
627 use crate::{color::ColorU, geometry::vector::Vector2F};
628 use std::mem;
629
630 include!(concat!(env!("OUT_DIR"), "/shaders.rs"));
631
632 pub trait ToFloat2 {
633 fn to_float2(&self) -> vector_float2;
634 }
635
636 pub trait ToUchar4 {
637 fn to_uchar4(&self) -> vector_uchar4;
638 }
639
640 impl ToFloat2 for (f32, f32) {
641 fn to_float2(&self) -> vector_float2 {
642 unsafe {
643 let mut output = mem::transmute::<_, u32>(self.1.to_bits()) as vector_float2;
644 output <<= 32;
645 output |= mem::transmute::<_, u32>(self.0.to_bits()) as vector_float2;
646 output
647 }
648 }
649 }
650
651 impl ToFloat2 for Vector2F {
652 fn to_float2(&self) -> vector_float2 {
653 unsafe {
654 let mut output = mem::transmute::<_, u32>(self.y().to_bits()) as vector_float2;
655 output <<= 32;
656 output |= mem::transmute::<_, u32>(self.x().to_bits()) as vector_float2;
657 output
658 }
659 }
660 }
661
662 impl ToFloat2 for Vector2I {
663 fn to_float2(&self) -> vector_float2 {
664 self.to_f32().to_float2()
665 }
666 }
667
668 impl ToUchar4 for ColorU {
669 fn to_uchar4(&self) -> vector_uchar4 {
670 let mut vec = self.a as vector_uchar4;
671 vec <<= 8;
672 vec |= self.b as vector_uchar4;
673 vec <<= 8;
674 vec |= self.g as vector_uchar4;
675 vec <<= 8;
676 vec |= self.r as vector_uchar4;
677 vec
678 }
679 }
680}