Seregon/StratoSDK

StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.

Rust/27.3 KB/No license
crates/strato-ui-renderer/src/platform/mac/rendering/metal/frame_capture.rs
1use metal::{MTLPixelFormat, MTLStorageMode};
2use pathfinder_geometry::vector::Vector2F;
3use strato_ui_core::platform::CapturedFrame;
4 
5#[cfg(test)]
6#[path = "frame_capture_tests.rs"]
7mod tests;
8 
9/// Captures a rendered frame from a Metal texture and returns the raw BGRA pixel data.
10///
11/// The data is returned in Metal's native BGRA format to avoid an expensive
12/// pixel-format conversion on the render thread. Consumers that need RGBA
13/// should call `CapturedFrame::ensure_rgba()`.
14///
15/// # Arguments
16/// * `texture` - The Metal texture containing the rendered frame
17/// * `size` - The dimensions of the texture (width, height)
18///
19/// # Returns
20/// * `Some(CapturedFrame)` containing the RGBA pixel data if successful
21/// * `None` if the texture dimensions are invalid
22pub fn capture_frame(texture: &metal::TextureRef, size: Vector2F) -> Option<CapturedFrame> {
23 let width = size.x() as usize;
24 let height = size.y() as usize;
25 
26 if width == 0 || height == 0 {
27 log::warn!("Invalid texture dimensions: {}x{}", width, height);
28 return None;
29 }
30 
31 let bytes_per_row = width * 4;
32 let buffer_size = bytes_per_row * height;
33 
34 let mut pixel_data: Vec<u8> = vec![0u8; buffer_size];
35 
36 let region = metal::MTLRegion {
37 origin: metal::MTLOrigin { x: 0, y: 0, z: 0 },
38 size: metal::MTLSize {
39 width: width as u64,
40 height: height as u64,
41 depth: 1,
42 },
43 };
44 
45 texture.get_bytes(
46 pixel_data.as_mut_ptr() as *mut std::ffi::c_void,
47 bytes_per_row as u64,
48 region,
49 0,
50 );
51 
52 Some(CapturedFrame::new_bgra(
53 width as u32,
54 height as u32,
55 pixel_data,
56 ))
57}
58 
59#[cfg(test)]
60pub(crate) fn convert_bgra_to_rgba(data: &mut [u8]) {
61 for chunk in data.chunks_exact_mut(4) {
62 chunk.swap(0, 2);
63 }
64}
65 
66/// Creates an off-screen Metal texture
67///
68/// This is a utility function for headless/off-screen rendering scenarios where
69/// you need to render to a texture rather than a window drawable. Currently unused
70/// but kept for future headless capture or visual regression testing support.
71///
72/// # Arguments
73/// * `device` - The Metal device to create the texture on
74/// * `width` - The width of the texture in pixels
75/// * `height` - The height of the texture in pixels
76/// * `pixel_format` - The pixel format (should match the drawable format)
77///
78/// # Returns
79/// * A new Metal texture that can be rendered to and read back from
80#[allow(dead_code)]
81pub fn create_capture_texture(
82 device: &metal::Device,
83 width: u64,
84 height: u64,
85 pixel_format: MTLPixelFormat,
86) -> metal::Texture {
87 let texture_descriptor = metal::TextureDescriptor::new();
88 texture_descriptor.set_pixel_format(pixel_format);
89 texture_descriptor.set_width(width);
90 texture_descriptor.set_height(height);
91 texture_descriptor.set_depth(1);
92 texture_descriptor.set_mipmap_level_count(1);
93 texture_descriptor.set_sample_count(1);
94 texture_descriptor.set_array_length(1);
95 
96 // Set usage flags for rendering and reading
97 texture_descriptor
98 .set_usage(metal::MTLTextureUsage::RenderTarget | metal::MTLTextureUsage::ShaderRead);
99 
100 // Use managed storage mode so we can read it back
101 texture_descriptor.set_storage_mode(MTLStorageMode::Managed);
102 
103 device.new_texture(&texture_descriptor)
104}
105