Skip to main content

display_utils/
image.rs

1// Copyright 2021 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 fsysmem2::{
6    AllocatorAllocateSharedCollectionRequest, AllocatorBindSharedCollectionRequest,
7    AllocatorSetDebugClientInfoRequest, BufferCollectionSetConstraintsRequest,
8    BufferCollectionTokenDuplicateRequest, NodeSetNameRequest,
9};
10
11use fidl::endpoints::{Proxy, create_endpoints, create_proxy};
12use fidl_fuchsia_hardware_display_types as fdisplay_types;
13use fidl_fuchsia_images2::{self as fimages2};
14use fidl_fuchsia_sysmem2::{
15    self as fsysmem2, AllocatorMarker, BufferCollectionInfo, BufferCollectionMarker,
16    BufferCollectionProxy, BufferCollectionTokenMarker, BufferCollectionTokenProxy,
17};
18use fuchsia_component::client::connect_to_protocol;
19
20use crate::controller::Coordinator;
21use crate::error::{Error, Result};
22use crate::pixel_format::PixelFormat;
23use crate::types::{BufferCollectionId, ImageId};
24
25/// Input parameters for constructing an image.
26#[derive(Clone)]
27pub struct ImageParameters {
28    /// The width dimension of the image, in pixels.
29    pub width: u32,
30
31    /// The height dimension of the image, in pixels.
32    pub height: u32,
33
34    /// Describes how individual pixels of the image will be interpreted. Determines the pixel
35    /// stride for the image buffer.
36    pub pixel_format: PixelFormat,
37
38    /// The sysmem color space standard representation. The user must take care that `color_space`
39    /// is compatible with the supplied `pixel_format`.
40    pub color_space: fimages2::ColorSpace,
41
42    /// Optional name to assign to the VMO that backs this image.
43    pub name: Option<String>,
44}
45
46/// Represents an allocated image buffer that can be assigned to a display layer.
47pub struct Image {
48    /// The ID of the image provided to the display driver.
49    pub id: ImageId,
50
51    /// The ID of the sysmem buffer collection that backs this image.
52    pub collection_id: BufferCollectionId,
53
54    /// The VMO that contains the shared image buffer.
55    pub vmo: zx::Vmo,
56
57    /// The parameters that the image was initialized with.
58    pub parameters: ImageParameters,
59
60    /// The image format constraints that resulted from the sysmem buffer negotiation. Contains the
61    /// effective image parameters.
62    pub format_constraints: fsysmem2::ImageFormatConstraints,
63
64    /// The effective buffer memory settings that resulted from the sysmem buffer negotiation.
65    pub buffer_settings: fsysmem2::BufferMemorySettings,
66
67    // The BufferCollection that backs this image.
68    proxy: BufferCollectionProxy,
69
70    // The display driver proxy that this image has been imported into.
71    coordinator: Coordinator,
72}
73
74impl Image {
75    /// Construct a new sysmem-buffer-backed image and register it with the display driver
76    /// using `image_id`. If successful, the image can be assigned to a primary layer in a
77    /// display configuration.
78    pub async fn create(
79        coordinator: Coordinator,
80        image_id: ImageId,
81        params: &ImageParameters,
82    ) -> Result<Image> {
83        let mut collection = allocate_image_buffer(coordinator.clone(), params).await?;
84        coordinator.import_image(collection.id, image_id, params.into()).await?;
85        let vmo = collection.info.buffers.as_ref().unwrap()[0]
86            .vmo
87            .as_ref()
88            .ok_or(Error::BuffersNotAllocated)?
89            .duplicate_handle(zx::Rights::SAME_RIGHTS)?;
90
91        collection.release();
92
93        Ok(Image {
94            id: image_id,
95            collection_id: collection.id,
96            vmo,
97            parameters: params.clone(),
98            format_constraints: collection
99                .info
100                .settings
101                .as_ref()
102                .unwrap()
103                .image_format_constraints
104                .as_ref()
105                .unwrap()
106                .clone(),
107            buffer_settings: collection
108                .info
109                .settings
110                .as_mut()
111                .unwrap()
112                .buffer_settings
113                .take()
114                .unwrap(),
115            proxy: collection.proxy.clone(),
116            coordinator,
117        })
118    }
119}
120
121impl Drop for Image {
122    fn drop(&mut self) {
123        let _ = self.proxy.release();
124        let _ = self.coordinator.release_buffer_collection(self.collection_id);
125    }
126}
127
128impl From<&ImageParameters> for fdisplay_types::ImageMetadata {
129    fn from(src: &ImageParameters) -> Self {
130        Self {
131            dimensions: fidl_fuchsia_math::SizeU { width: src.width, height: src.height },
132            tiling_type: fdisplay_types::IMAGE_TILING_TYPE_LINEAR,
133        }
134    }
135}
136
137impl From<ImageParameters> for fdisplay_types::ImageMetadata {
138    fn from(src: ImageParameters) -> Self {
139        fdisplay_types::ImageMetadata::from(&src)
140    }
141}
142
143// Result of `allocate_image_buffer` that automatically releases the display driver's connection to
144// the buffer collection unless `release()` is called on it. This is intended to clean up resources
145// in the early-return cases above.
146struct BufferCollection {
147    id: BufferCollectionId,
148    info: BufferCollectionInfo,
149    proxy: BufferCollectionProxy,
150    coordinator: Coordinator,
151    released: bool,
152}
153
154impl BufferCollection {
155    fn release(&mut self) {
156        self.released = true;
157    }
158}
159
160impl Drop for BufferCollection {
161    fn drop(&mut self) {
162        if !self.released {
163            let _ = self.coordinator.release_buffer_collection(self.id);
164            let _ = self.proxy.release();
165        }
166    }
167}
168
169// Allocate a sysmem buffer collection and register it with the display driver. The allocated
170// buffer can be used to construct a display layer image.
171async fn allocate_image_buffer(
172    coordinator: Coordinator,
173    params: &ImageParameters,
174) -> Result<BufferCollection> {
175    let allocator =
176        connect_to_protocol::<AllocatorMarker>().map_err(|_| Error::SysmemConnection)?;
177    {
178        let name = fuchsia_runtime::process_self().get_name()?;
179        let koid = fuchsia_runtime::process_self().koid()?;
180        allocator.set_debug_client_info(&AllocatorSetDebugClientInfoRequest {
181            name: Some(name.to_string()),
182            id: Some(koid.raw_koid()),
183            ..Default::default()
184        })?;
185    }
186    let collection_token = {
187        let (proxy, remote) = create_proxy::<BufferCollectionTokenMarker>();
188        allocator.allocate_shared_collection(AllocatorAllocateSharedCollectionRequest {
189            token_request: Some(remote),
190            ..Default::default()
191        })?;
192        proxy
193    };
194    // TODO(armansito): The priority number here is arbitrary but I don't expect there to be
195    // contention for the assigned name as this client library should be the collection's sole
196    // owner. Still, come up with a better way to assign this.
197    if let Some(ref name) = params.name {
198        collection_token.set_name(&NodeSetNameRequest {
199            priority: Some(100),
200            name: Some(name.clone()),
201            ..Default::default()
202        })?;
203    }
204
205    // Duplicate of `collection_token` to be transferred to the display driver.
206    let display_duplicate = {
207        let (local, remote) = create_endpoints::<BufferCollectionTokenMarker>();
208        collection_token.duplicate(BufferCollectionTokenDuplicateRequest {
209            rights_attenuation_mask: Some(fidl::Rights::SAME_RIGHTS),
210            token_request: Some(remote),
211            ..Default::default()
212        })?;
213        collection_token.sync().await?;
214        local
215    };
216
217    // Register the collection with the display driver.
218    let id = coordinator.import_buffer_collection(display_duplicate).await?;
219
220    // Tell sysmem to perform the buffer allocation and wait for the result. Clean up on error.
221    match allocate_image_buffer_helper(params, allocator, collection_token).await {
222        Ok((info, proxy)) => Ok(BufferCollection { id, info, proxy, coordinator, released: false }),
223        Err(error) => {
224            let _ = coordinator.release_buffer_collection(id);
225            Err(error)
226        }
227    }
228}
229
230async fn allocate_image_buffer_helper(
231    params: &ImageParameters,
232    allocator: fsysmem2::AllocatorProxy,
233    token: BufferCollectionTokenProxy,
234) -> Result<(BufferCollectionInfo, BufferCollectionProxy)> {
235    // Turn in the collection token to obtain a connection to the logical buffer collection.
236    let collection = {
237        let (local, remote) = create_endpoints::<BufferCollectionMarker>();
238        let token_client = token.into_client_end().map_err(|_| Error::SysmemConnection)?;
239        allocator.bind_shared_collection(AllocatorBindSharedCollectionRequest {
240            token: Some(token_client),
241            buffer_collection_request: Some(remote),
242            ..Default::default()
243        })?;
244        local.into_proxy()
245    };
246
247    // Set local constraints and allocate buffers.
248    collection.set_constraints(BufferCollectionSetConstraintsRequest {
249        constraints: Some(buffer_collection_constraints(params)),
250        ..Default::default()
251    })?;
252    let collection_info = {
253        let response = collection
254            .wait_for_all_buffers_allocated()
255            .await?
256            .map_err(|_| Error::BuffersNotAllocated)?;
257        response.buffer_collection_info.ok_or(Error::BuffersNotAllocated)?
258    };
259
260    // We expect there to be at least one available vmo.
261    if collection_info.buffers.as_ref().unwrap().is_empty() {
262        collection.release()?;
263        return Err(Error::BuffersNotAllocated);
264    }
265
266    Ok((collection_info, collection))
267}
268
269fn buffer_collection_constraints(
270    params: &ImageParameters,
271) -> fsysmem2::BufferCollectionConstraints {
272    let usage = fsysmem2::BufferUsage {
273        cpu: Some(fsysmem2::CPU_USAGE_READ_OFTEN | fsysmem2::CPU_USAGE_WRITE_OFTEN),
274        ..Default::default()
275    };
276
277    let buffer_memory_constraints = fsysmem2::BufferMemoryConstraints {
278        ram_domain_supported: Some(true),
279        cpu_domain_supported: Some(true),
280        ..Default::default()
281    };
282
283    // TODO(armansito): parameterize the format modifier
284    let image_constraints = fsysmem2::ImageFormatConstraints {
285        pixel_format: Some(params.pixel_format.into()),
286        pixel_format_modifier: Some(fimages2::PixelFormatModifier::Linear),
287        required_max_size: Some(fidl_fuchsia_math::SizeU {
288            width: params.width,
289            height: params.height,
290        }),
291        color_spaces: Some(vec![params.color_space]),
292        ..Default::default()
293    };
294
295    let constraints = fsysmem2::BufferCollectionConstraints {
296        min_buffer_count: Some(1),
297        usage: Some(usage),
298        buffer_memory_constraints: Some(buffer_memory_constraints),
299        image_format_constraints: Some(vec![image_constraints]),
300        ..Default::default()
301    };
302
303    constraints
304}