fuchsia_framebuffer/
sysmem.rs

1// Copyright 2019 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::FrameUsage;
6use anyhow::{Context, Error, format_err};
7use fidl::endpoints::{ClientEnd, Proxy, create_endpoints};
8use fidl_fuchsia_images2::{ColorSpace, PixelFormat, PixelFormatModifier};
9use fidl_fuchsia_sysmem2::{
10    AllocatorAllocateSharedCollectionRequest, AllocatorBindSharedCollectionRequest,
11    AllocatorMarker, AllocatorProxy, AllocatorSetDebugClientInfoRequest,
12    BufferCollectionConstraints, BufferCollectionInfo, BufferCollectionMarker,
13    BufferCollectionProxy, BufferCollectionSetConstraintsRequest,
14    BufferCollectionTokenDuplicateRequest, BufferCollectionTokenMarker, BufferCollectionTokenProxy,
15    BufferMemoryConstraints, BufferUsage, CPU_USAGE_READ_OFTEN, CPU_USAGE_WRITE_OFTEN,
16    ImageFormatConstraints, NONE_USAGE, NodeSetNameRequest,
17};
18use fuchsia_component::client::connect_to_protocol;
19use std::cmp;
20
21fn linear_image_format_constraints(
22    width: u32,
23    height: u32,
24    pixel_type: PixelFormat,
25) -> ImageFormatConstraints {
26    ImageFormatConstraints {
27        pixel_format: Some(pixel_type),
28        pixel_format_modifier: Some(PixelFormatModifier::Linear),
29        color_spaces: Some(vec![ColorSpace::Srgb]),
30        required_min_size: Some(fidl_fuchsia_math::SizeU { width, height }),
31        required_max_size: Some(fidl_fuchsia_math::SizeU { width, height }),
32        ..Default::default()
33    }
34}
35
36fn buffer_memory_constraints(width: u32, height: u32) -> BufferMemoryConstraints {
37    BufferMemoryConstraints {
38        min_size_bytes: Some(width as u64 * height as u64 * 4u64),
39        physically_contiguous_required: Some(false),
40        secure_required: Some(false),
41        ram_domain_supported: Some(true),
42        cpu_domain_supported: Some(true),
43        inaccessible_domain_supported: Some(false),
44        ..Default::default()
45    }
46}
47
48fn buffer_collection_constraints(
49    width: u32,
50    height: u32,
51    pixel_format: PixelFormat,
52    buffer_count: u32,
53    frame_usage: FrameUsage,
54) -> BufferCollectionConstraints {
55    let (usage, has_buffer_memory_constraints, has_image_format_constraints) = match frame_usage {
56        FrameUsage::Cpu => (
57            BufferUsage {
58                cpu: Some(CPU_USAGE_WRITE_OFTEN | CPU_USAGE_READ_OFTEN),
59                ..Default::default()
60            },
61            true,
62            true,
63        ),
64        FrameUsage::Gpu => {
65            (BufferUsage { none: Some(NONE_USAGE), ..Default::default() }, false, false)
66        }
67    };
68    BufferCollectionConstraints {
69        usage: Some(usage),
70        min_buffer_count: Some(buffer_count),
71        buffer_memory_constraints: if has_buffer_memory_constraints {
72            Some(buffer_memory_constraints(width, height))
73        } else {
74            None
75        },
76        image_format_constraints: if has_image_format_constraints {
77            Some(vec![linear_image_format_constraints(width, height, pixel_format)])
78        } else {
79            None
80        },
81        ..Default::default()
82    }
83}
84
85// See ImageFormatStrideBytesPerWidthPixel
86fn stride_bytes_per_width_pixel(pixel_type: PixelFormat) -> Result<u32, Error> {
87    match pixel_type {
88        PixelFormat::R8G8B8A8 => Ok(4),
89        PixelFormat::B8G8R8A8 => Ok(4),
90        PixelFormat::B8G8R8 => Ok(3),
91        PixelFormat::I420 => Ok(1),
92        PixelFormat::M420 => Ok(1),
93        PixelFormat::Nv12 => Ok(1),
94        PixelFormat::Yuy2 => Ok(2),
95        PixelFormat::Yv12 => Ok(1),
96        PixelFormat::R5G6B5 => Ok(2),
97        PixelFormat::R3G3B2 => Ok(1),
98        PixelFormat::R2G2B2X2 => Ok(1),
99        PixelFormat::L8 => Ok(1),
100        _ => return Err(format_err!("Unsupported format")),
101    }
102}
103
104fn round_up_to_align(x: u32, align: u32) -> u32 {
105    if align == 0 { x } else { ((x + align - 1) / align) * align }
106}
107
108// See ImageFormatMinimumRowBytes
109pub fn minimum_row_bytes(constraints: &ImageFormatConstraints, width: u32) -> Result<u32, Error> {
110    if width < constraints.min_size.ok_or("missing min_size").unwrap().width
111        || width > constraints.max_size.ok_or("missing max_size").unwrap().width
112    {
113        return Err(format_err!("Invalid width for constraints"));
114    }
115
116    let bytes_per_pixel = stride_bytes_per_width_pixel(
117        constraints.pixel_format.ok_or("missing pixel_format").unwrap(),
118    )?;
119    Ok(round_up_to_align(
120        cmp::max(
121            bytes_per_pixel * width,
122            constraints.min_bytes_per_row.ok_or("missing min_bytes_per_row").unwrap(),
123        ),
124        constraints.bytes_per_row_divisor.ok_or("missing bytes_per_row_divisor").unwrap(),
125    ))
126}
127
128pub struct BufferCollectionAllocator {
129    token: Option<BufferCollectionTokenProxy>,
130    width: u32,
131    height: u32,
132    pixel_format: PixelFormat,
133    usage: FrameUsage,
134    buffer_count: usize,
135    sysmem: AllocatorProxy,
136    collection_client: Option<BufferCollectionProxy>,
137}
138
139pub fn set_allocator_name(sysmem_client: &AllocatorProxy) -> Result<(), Error> {
140    Ok(sysmem_client.set_debug_client_info(&AllocatorSetDebugClientInfoRequest {
141        name: Some(fuchsia_runtime::process_self().get_name()?.to_string()),
142        id: Some(fuchsia_runtime::process_self().koid()?.raw_koid()),
143        ..Default::default()
144    })?)
145}
146
147impl BufferCollectionAllocator {
148    pub fn new(
149        width: u32,
150        height: u32,
151        pixel_format: PixelFormat,
152        usage: FrameUsage,
153        buffer_count: usize,
154    ) -> Result<BufferCollectionAllocator, Error> {
155        let sysmem = connect_to_protocol::<AllocatorMarker>()?;
156
157        let _ = set_allocator_name(&sysmem);
158
159        let (local_token, local_token_request) = create_endpoints::<BufferCollectionTokenMarker>();
160
161        sysmem.allocate_shared_collection(AllocatorAllocateSharedCollectionRequest {
162            token_request: Some(local_token_request),
163            ..Default::default()
164        })?;
165
166        Ok(BufferCollectionAllocator {
167            token: Some(local_token.into_proxy()),
168            width,
169            height,
170            pixel_format,
171            usage,
172            buffer_count,
173            sysmem,
174            collection_client: None,
175        })
176    }
177
178    pub fn set_name(&mut self, priority: u32, name: &str) -> Result<(), Error> {
179        Ok(self.token.as_ref().expect("token in set_name").set_name(&NodeSetNameRequest {
180            priority: Some(priority),
181            name: Some(name.into()),
182            ..Default::default()
183        })?)
184    }
185
186    pub fn set_pixel_type(&mut self, pixel_format: PixelFormat) {
187        self.pixel_format = pixel_format;
188    }
189
190    pub async fn allocate_buffers(
191        &mut self,
192        set_constraints: bool,
193    ) -> Result<BufferCollectionInfo, Error> {
194        let token = self.token.take().expect("token in allocate_buffers");
195        let (collection_client, collection_request) = create_endpoints::<BufferCollectionMarker>();
196        self.sysmem.bind_shared_collection(AllocatorBindSharedCollectionRequest {
197            token: Some(token.into_client_end().unwrap()),
198            buffer_collection_request: Some(collection_request),
199            ..Default::default()
200        })?;
201        let collection_client = collection_client.into_proxy();
202        self.allocate_buffers_proxy(collection_client, set_constraints).await
203    }
204
205    async fn allocate_buffers_proxy(
206        &mut self,
207        collection_client: BufferCollectionProxy,
208        set_constraints: bool,
209    ) -> Result<BufferCollectionInfo, Error> {
210        let buffer_collection_constraints = buffer_collection_constraints(
211            self.width,
212            self.height,
213            self.pixel_format,
214            self.buffer_count as u32,
215            self.usage,
216        );
217        collection_client
218            .set_constraints(BufferCollectionSetConstraintsRequest {
219                constraints: if set_constraints {
220                    Some(buffer_collection_constraints)
221                } else {
222                    None
223                },
224                ..Default::default()
225            })
226            .context("Sending buffer constraints to sysmem")?;
227        let wait_result = collection_client.wait_for_all_buffers_allocated().await;
228        self.collection_client = Some(collection_client);
229        if wait_result.is_err() {
230            let error: fidl::Error = wait_result.unwrap_err();
231            return Err(format_err!("Failed to wait for buffers {}", error));
232        }
233        if wait_result.as_ref().unwrap().is_err() {
234            let error: fidl_fuchsia_sysmem2::Error = wait_result.unwrap().unwrap_err();
235            return Err(format_err!("Wait for buffers failed {:?}", error));
236        }
237        let buffers = wait_result.unwrap().unwrap().buffer_collection_info.unwrap();
238        Ok(buffers)
239    }
240
241    pub async fn duplicate_token(
242        &mut self,
243    ) -> Result<ClientEnd<BufferCollectionTokenMarker>, Error> {
244        let (requested_token, requested_token_request) =
245            create_endpoints::<BufferCollectionTokenMarker>();
246
247        self.token.as_ref().expect("token in duplicate_token[duplicate]").duplicate(
248            BufferCollectionTokenDuplicateRequest {
249                rights_attenuation_mask: Some(fidl::Rights::SAME_RIGHTS),
250                token_request: Some(requested_token_request),
251                ..Default::default()
252            },
253        )?;
254        self.token.as_ref().expect("token in duplicate_token_2[sync]").sync().await?;
255        Ok(requested_token)
256    }
257}
258
259impl Drop for BufferCollectionAllocator {
260    fn drop(&mut self) {
261        if let Some(collection_client) = self.collection_client.as_mut() {
262            collection_client
263                .release()
264                .unwrap_or_else(|err| eprintln!("collection_client.release failed with {}", err));
265        }
266    }
267}
268
269#[cfg(test)]
270mod test {
271    use super::*;
272    use fidl_fuchsia_sysmem2::{
273        BufferCollectionRequest, BufferCollectionWaitForAllBuffersAllocatedResponse,
274        BufferMemorySettings, CoherencyDomain, Heap, SingleBufferSettings, VmoBuffer,
275    };
276    use fuchsia_async as fasync;
277    use futures::prelude::*;
278
279    const BUFFER_COUNT: usize = 3;
280
281    fn spawn_allocator_server() -> Result<AllocatorProxy, Error> {
282        let (proxy, mut stream) = fidl::endpoints::create_proxy_and_stream::<AllocatorMarker>();
283
284        fasync::Task::spawn(async move {
285            while let Some(_) = stream.try_next().await.expect("Failed to get request") {}
286        })
287        .detach();
288        Ok(proxy)
289    }
290
291    fn spawn_buffer_collection() -> Result<BufferCollectionProxy, Error> {
292        let (proxy, mut stream) =
293            fidl::endpoints::create_proxy_and_stream::<BufferCollectionMarker>();
294
295        fasync::Task::spawn(async move {
296            let mut stored_constraints = None;
297            while let Some(req) = stream.try_next().await.expect("Failed to get request") {
298                match req {
299                    BufferCollectionRequest::SetConstraints { payload, control_handle: _ } => {
300                        stored_constraints = payload.constraints;
301                    }
302                    BufferCollectionRequest::WaitForAllBuffersAllocated { responder } => {
303                        let constraints =
304                            stored_constraints.take().expect("Expected SetConstraints first");
305                        let mut buffers: Vec<VmoBuffer> = vec![];
306                        for _ in 0..*constraints.min_buffer_count.as_ref().unwrap() {
307                            buffers.push(fidl_fuchsia_sysmem2::VmoBuffer { ..Default::default() });
308                        }
309                        let buffer_collection_info = BufferCollectionInfo {
310                            settings: Some(SingleBufferSettings {
311                                buffer_settings: Some(BufferMemorySettings {
312                                    size_bytes: Some(0),
313                                    is_physically_contiguous: Some(false),
314                                    is_secure: Some(false),
315                                    coherency_domain: Some(CoherencyDomain::Cpu),
316                                    heap: Some(Heap {
317                                        heap_type: Some(
318                                            bind_fuchsia_sysmem_heap::HEAP_TYPE_SYSTEM_RAM.into(),
319                                        ),
320                                        ..Default::default()
321                                    }),
322                                    ..Default::default()
323                                }),
324                                image_format_constraints: Some(linear_image_format_constraints(
325                                    0,
326                                    0,
327                                    PixelFormat::Invalid,
328                                )),
329                                ..Default::default()
330                            }),
331                            buffers: Some(buffers),
332                            ..Default::default()
333                        };
334                        let response = BufferCollectionWaitForAllBuffersAllocatedResponse {
335                            buffer_collection_info: Some(buffer_collection_info),
336                            ..Default::default()
337                        };
338                        responder.send(Ok(response)).expect("Failed to send");
339                    }
340                    _ => panic!("Unexpected request"),
341                }
342            }
343        })
344        .detach();
345
346        return Ok(proxy);
347    }
348
349    #[fasync::run_singlethreaded(test)]
350    async fn test_buffer_collection_allocator() -> std::result::Result<(), anyhow::Error> {
351        let alloc_proxy = spawn_allocator_server()?;
352        let buf_proxy = spawn_buffer_collection()?;
353
354        // don't use new() as we want to inject alloc_proxy instead of discovering it.
355        let mut bca = BufferCollectionAllocator {
356            token: None,
357            width: 200,
358            height: 200,
359            pixel_format: PixelFormat::B8G8R8A8,
360            usage: FrameUsage::Cpu,
361            buffer_count: BUFFER_COUNT,
362            sysmem: alloc_proxy,
363            collection_client: None,
364        };
365
366        let buffers = bca.allocate_buffers_proxy(buf_proxy, true).await?;
367        assert_eq!(buffers.buffers.unwrap().len(), BUFFER_COUNT);
368
369        Ok(())
370    }
371}