blob: c5e36b2185c00dbf2c44812007732dcdc01046ae [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use crate::FrameUsage;
use anyhow::{format_err, Context, Error};
use fidl::endpoints::{create_endpoints, ClientEnd, Proxy};
use fidl_fuchsia_sysmem::{
BufferCollectionConstraints, BufferMemoryConstraints, BufferUsage, ColorSpace, ColorSpaceType,
FormatModifier, HeapType, ImageFormatConstraints, PixelFormat as SysmemPixelFormat,
PixelFormatType,
};
use fuchsia_component::client::connect_to_service;
use fuchsia_runtime;
use fuchsia_zircon::{self as zx, AsHandleRef, Status};
use std::cmp;
pub fn linear_image_format_constraints(
width: u32,
height: u32,
pixel_type: PixelFormatType,
) -> ImageFormatConstraints {
ImageFormatConstraints {
pixel_format: SysmemPixelFormat {
type_: pixel_type,
has_format_modifier: true,
format_modifier: FormatModifier { value: fidl_fuchsia_sysmem::FORMAT_MODIFIER_LINEAR },
},
color_spaces_count: 1,
color_space: [ColorSpace { type_: ColorSpaceType::Srgb }; 32],
min_coded_width: 0,
max_coded_width: 0,
min_coded_height: 0,
max_coded_height: 0,
min_bytes_per_row: 0,
max_bytes_per_row: 0,
max_coded_width_times_coded_height: 0,
layers: 0,
coded_width_divisor: 0,
coded_height_divisor: 0,
bytes_per_row_divisor: 0,
start_offset_divisor: 0,
display_width_divisor: 0,
display_height_divisor: 0,
required_min_coded_width: width,
required_max_coded_width: width,
required_min_coded_height: height,
required_max_coded_height: height,
required_min_bytes_per_row: 0,
required_max_bytes_per_row: 0,
}
}
pub fn buffer_memory_constraints(width: u32, height: u32) -> BufferMemoryConstraints {
BufferMemoryConstraints {
min_size_bytes: width * height * 4,
max_size_bytes: std::u32::MAX,
physically_contiguous_required: false,
secure_required: false,
ram_domain_supported: true,
cpu_domain_supported: true,
inaccessible_domain_supported: false,
heap_permitted_count: 0,
heap_permitted: [HeapType::SystemRam; 32],
}
}
pub fn buffer_collection_constraints(
width: u32,
height: u32,
pixel_type: PixelFormatType,
buffer_count: u32,
frame_usage: FrameUsage,
) -> BufferCollectionConstraints {
let (usage, has_buffer_memory_constraints, image_format_constraints_count) = match frame_usage {
FrameUsage::Cpu => (
BufferUsage {
none: 0,
cpu: fidl_fuchsia_sysmem::CPU_USAGE_WRITE_OFTEN
| fidl_fuchsia_sysmem::CPU_USAGE_READ_OFTEN,
vulkan: 0,
display: 0,
video: 0,
},
true,
1,
),
FrameUsage::Gpu => {
(BufferUsage { none: 1, cpu: 0, vulkan: 0, display: 0, video: 0 }, false, 0)
}
};
BufferCollectionConstraints {
usage: usage,
min_buffer_count_for_camping: 0,
min_buffer_count_for_dedicated_slack: 0,
min_buffer_count_for_shared_slack: 0,
min_buffer_count: buffer_count,
max_buffer_count: std::u32::MAX,
has_buffer_memory_constraints: has_buffer_memory_constraints,
buffer_memory_constraints: buffer_memory_constraints(width, height),
image_format_constraints_count: image_format_constraints_count,
image_format_constraints: [linear_image_format_constraints(width, height, pixel_type); 32],
}
}
// See ImageFormatStrideBytesPerWidthPixel
pub fn stride_bytes_per_width_pixel(pixel_type: PixelFormatType) -> Result<u32, Error> {
match pixel_type {
PixelFormatType::R8G8B8A8 => Ok(4),
PixelFormatType::Bgra32 => Ok(4),
PixelFormatType::Bgr24 => Ok(3),
PixelFormatType::I420 => Ok(1),
PixelFormatType::M420 => Ok(1),
PixelFormatType::Nv12 => Ok(1),
PixelFormatType::Yuy2 => Ok(2),
PixelFormatType::Yv12 => Ok(1),
PixelFormatType::Rgb565 => Ok(2),
PixelFormatType::Rgb332 => Ok(1),
PixelFormatType::Rgb2220 => Ok(1),
PixelFormatType::L8 => Ok(1),
_ => return Err(format_err!("Unsupported format")),
}
}
fn round_up_to_align(x: u32, align: u32) -> u32 {
if align == 0 {
x
} else {
((x + align - 1) / align) * align
}
}
// See ImageFormatMinimumRowBytes
pub fn minimum_row_bytes(constraints: ImageFormatConstraints, width: u32) -> Result<u32, Error> {
if width < constraints.min_coded_width || width > constraints.max_coded_width {
return Err(format_err!("Invalid width for constraints"));
}
let bytes_per_pixel = stride_bytes_per_width_pixel(constraints.pixel_format.type_)?;
Ok(round_up_to_align(
cmp::max(bytes_per_pixel * width, constraints.min_bytes_per_row),
constraints.bytes_per_row_divisor,
))
}
pub struct BufferCollectionAllocator {
token: Option<fidl_fuchsia_sysmem::BufferCollectionTokenProxy>,
width: u32,
height: u32,
pixel_type: PixelFormatType,
usage: FrameUsage,
buffer_count: usize,
sysmem: fidl_fuchsia_sysmem::AllocatorProxy,
collection_client: Option<fidl_fuchsia_sysmem::BufferCollectionProxy>,
}
pub fn set_allocator_name(
sysmem_client: &fidl_fuchsia_sysmem::AllocatorProxy,
) -> Result<(), Error> {
let name = fuchsia_runtime::process_self().get_name()?;
let koid = fuchsia_runtime::process_self().get_koid()?;
Ok(sysmem_client.set_debug_client_info(name.to_str()?, koid.raw_koid())?)
}
impl BufferCollectionAllocator {
pub fn new(
width: u32,
height: u32,
pixel_type: PixelFormatType,
usage: FrameUsage,
buffer_count: usize,
) -> Result<BufferCollectionAllocator, Error> {
let sysmem = connect_to_service::<fidl_fuchsia_sysmem::AllocatorMarker>()?;
let _ = set_allocator_name(&sysmem);
let (local_token, local_token_request) =
create_endpoints::<fidl_fuchsia_sysmem::BufferCollectionTokenMarker>()?;
sysmem.allocate_shared_collection(local_token_request)?;
Ok(BufferCollectionAllocator {
token: Some(local_token.into_proxy()?),
width,
height,
pixel_type,
usage,
buffer_count,
sysmem,
collection_client: None,
})
}
pub fn set_name(&mut self, priority: u32, name: &str) -> Result<(), Error> {
Ok(self.token.as_ref().expect("token in set_name").set_name(priority, name)?)
}
pub async fn allocate_buffers(
&mut self,
set_constraints: bool,
) -> Result<fidl_fuchsia_sysmem::BufferCollectionInfo2, Error> {
let token = self.token.take().expect("token in allocate_buffers");
let (collection_client, collection_request) =
create_endpoints::<fidl_fuchsia_sysmem::BufferCollectionMarker>()?;
self.sysmem.bind_shared_collection(
ClientEnd::new(token.into_channel().unwrap().into_zx_channel()),
collection_request,
)?;
let collection_client = collection_client.into_proxy()?;
self.allocate_buffers_proxy(collection_client, set_constraints).await
}
async fn allocate_buffers_proxy(
&mut self,
collection_client: fidl_fuchsia_sysmem::BufferCollectionProxy,
set_constraints: bool,
) -> Result<fidl_fuchsia_sysmem::BufferCollectionInfo2, Error> {
let mut buffer_collection_constraints = crate::sysmem::buffer_collection_constraints(
self.width,
self.height,
self.pixel_type,
self.buffer_count as u32,
self.usage,
);
collection_client
.set_constraints(set_constraints, &mut buffer_collection_constraints)
.context("Sending buffer constraints to sysmem")?;
let (status, buffers) = collection_client.wait_for_buffers_allocated().await?;
self.collection_client = Some(collection_client);
if status != zx::sys::ZX_OK {
return Err(format_err!(
"Failed to wait for buffers {}({})",
Status::from_raw(status),
status
));
}
Ok(buffers)
}
pub async fn duplicate_token(
&mut self,
) -> Result<ClientEnd<fidl_fuchsia_sysmem::BufferCollectionTokenMarker>, Error> {
let (requested_token, requested_token_request) =
create_endpoints::<fidl_fuchsia_sysmem::BufferCollectionTokenMarker>()?;
self.token
.as_ref()
.expect("token in duplicate_token[duplicate]")
.duplicate(std::u32::MAX, requested_token_request)?;
self.token.as_ref().expect("tokenin duplicate_token[sync]").sync().await?;
Ok(requested_token)
}
}
impl Drop for BufferCollectionAllocator {
fn drop(&mut self) {
if let Some(collection_client) = self.collection_client.as_mut() {
collection_client
.close()
.unwrap_or_else(|err| eprintln!("collection_client.close failed with {}", err));
}
}
}
#[cfg(test)]
mod test {
use super::*;
use fidl_fuchsia_sysmem::{
AllocatorMarker, AllocatorProxy, BufferCollectionInfo2, BufferCollectionMarker,
BufferCollectionProxy, BufferCollectionRequest, BufferMemorySettings, CoherencyDomain,
SingleBufferSettings, VmoBuffer,
};
use fuchsia_async as fasync;
use futures::prelude::*;
const BUFFER_COUNT: usize = 3;
fn spawn_allocator_server() -> Result<AllocatorProxy, Error> {
let (proxy, mut stream) = fidl::endpoints::create_proxy_and_stream::<AllocatorMarker>()?;
fasync::Task::spawn(async move {
while let Some(_) = stream.try_next().await.expect("Failed to get request") {}
})
.detach();
Ok(proxy)
}
fn spawn_buffer_collection() -> Result<BufferCollectionProxy, Error> {
let (proxy, mut stream) =
fidl::endpoints::create_proxy_and_stream::<BufferCollectionMarker>()?;
fasync::Task::spawn(async move {
let mut stored_constraints = None;
while let Some(req) = stream.try_next().await.expect("Failed to get request") {
match req {
BufferCollectionRequest::SetConstraints {
has_constraints,
constraints,
control_handle: _,
} => {
if has_constraints {
stored_constraints = Some(constraints);
} else {
stored_constraints = None;
}
}
BufferCollectionRequest::WaitForBuffersAllocated { responder } => {
let constraints =
stored_constraints.expect("Expected a BufferCollectionRequest!");
let mut response = BufferCollectionInfo2 {
buffer_count: constraints.min_buffer_count,
// Everything below here is unused
settings: SingleBufferSettings {
buffer_settings: BufferMemorySettings {
size_bytes: 0,
is_physically_contiguous: false,
is_secure: false,
coherency_domain: CoherencyDomain::Cpu,
heap: HeapType::SystemRam,
},
has_image_format_constraints: false,
image_format_constraints: linear_image_format_constraints(
0,
0,
PixelFormatType::Invalid,
),
},
buffers: [
VmoBuffer { vmo: None, vmo_usable_start: 0 },
VmoBuffer { vmo: None, vmo_usable_start: 0 },
VmoBuffer { vmo: None, vmo_usable_start: 0 },
VmoBuffer { vmo: None, vmo_usable_start: 0 },
VmoBuffer { vmo: None, vmo_usable_start: 0 },
VmoBuffer { vmo: None, vmo_usable_start: 0 },
VmoBuffer { vmo: None, vmo_usable_start: 0 },
VmoBuffer { vmo: None, vmo_usable_start: 0 },
VmoBuffer { vmo: None, vmo_usable_start: 0 },
VmoBuffer { vmo: None, vmo_usable_start: 0 },
VmoBuffer { vmo: None, vmo_usable_start: 0 },
VmoBuffer { vmo: None, vmo_usable_start: 0 },
VmoBuffer { vmo: None, vmo_usable_start: 0 },
VmoBuffer { vmo: None, vmo_usable_start: 0 },
VmoBuffer { vmo: None, vmo_usable_start: 0 },
VmoBuffer { vmo: None, vmo_usable_start: 0 },
VmoBuffer { vmo: None, vmo_usable_start: 0 },
VmoBuffer { vmo: None, vmo_usable_start: 0 },
VmoBuffer { vmo: None, vmo_usable_start: 0 },
VmoBuffer { vmo: None, vmo_usable_start: 0 },
VmoBuffer { vmo: None, vmo_usable_start: 0 },
VmoBuffer { vmo: None, vmo_usable_start: 0 },
VmoBuffer { vmo: None, vmo_usable_start: 0 },
VmoBuffer { vmo: None, vmo_usable_start: 0 },
VmoBuffer { vmo: None, vmo_usable_start: 0 },
VmoBuffer { vmo: None, vmo_usable_start: 0 },
VmoBuffer { vmo: None, vmo_usable_start: 0 },
VmoBuffer { vmo: None, vmo_usable_start: 0 },
VmoBuffer { vmo: None, vmo_usable_start: 0 },
VmoBuffer { vmo: None, vmo_usable_start: 0 },
VmoBuffer { vmo: None, vmo_usable_start: 0 },
VmoBuffer { vmo: None, vmo_usable_start: 0 },
VmoBuffer { vmo: None, vmo_usable_start: 0 },
VmoBuffer { vmo: None, vmo_usable_start: 0 },
VmoBuffer { vmo: None, vmo_usable_start: 0 },
VmoBuffer { vmo: None, vmo_usable_start: 0 },
VmoBuffer { vmo: None, vmo_usable_start: 0 },
VmoBuffer { vmo: None, vmo_usable_start: 0 },
VmoBuffer { vmo: None, vmo_usable_start: 0 },
VmoBuffer { vmo: None, vmo_usable_start: 0 },
VmoBuffer { vmo: None, vmo_usable_start: 0 },
VmoBuffer { vmo: None, vmo_usable_start: 0 },
VmoBuffer { vmo: None, vmo_usable_start: 0 },
VmoBuffer { vmo: None, vmo_usable_start: 0 },
VmoBuffer { vmo: None, vmo_usable_start: 0 },
VmoBuffer { vmo: None, vmo_usable_start: 0 },
VmoBuffer { vmo: None, vmo_usable_start: 0 },
VmoBuffer { vmo: None, vmo_usable_start: 0 },
VmoBuffer { vmo: None, vmo_usable_start: 0 },
VmoBuffer { vmo: None, vmo_usable_start: 0 },
VmoBuffer { vmo: None, vmo_usable_start: 0 },
VmoBuffer { vmo: None, vmo_usable_start: 0 },
VmoBuffer { vmo: None, vmo_usable_start: 0 },
VmoBuffer { vmo: None, vmo_usable_start: 0 },
VmoBuffer { vmo: None, vmo_usable_start: 0 },
VmoBuffer { vmo: None, vmo_usable_start: 0 },
VmoBuffer { vmo: None, vmo_usable_start: 0 },
VmoBuffer { vmo: None, vmo_usable_start: 0 },
VmoBuffer { vmo: None, vmo_usable_start: 0 },
VmoBuffer { vmo: None, vmo_usable_start: 0 },
VmoBuffer { vmo: None, vmo_usable_start: 0 },
VmoBuffer { vmo: None, vmo_usable_start: 0 },
VmoBuffer { vmo: None, vmo_usable_start: 0 },
VmoBuffer { vmo: None, vmo_usable_start: 0 },
],
};
responder.send(zx::sys::ZX_OK, &mut response).expect("Failed to send");
}
_ => panic!("Unexpected request"),
}
}
})
.detach();
return Ok(proxy);
}
#[fasync::run_singlethreaded(test)]
async fn test_buffer_collection_allocator() -> std::result::Result<(), anyhow::Error> {
let alloc_proxy = spawn_allocator_server()?;
let buf_proxy = spawn_buffer_collection()?;
// don't use new() as we want to inject alloc_proxy instead of discovering it.
let mut bca = BufferCollectionAllocator {
token: None,
width: 200,
height: 200,
pixel_type: PixelFormatType::Bgra32,
usage: FrameUsage::Cpu,
buffer_count: BUFFER_COUNT,
sysmem: alloc_proxy,
collection_client: None,
};
let buffers = bca.allocate_buffers_proxy(buf_proxy, true).await?;
assert_eq!(buffers.buffer_count, BUFFER_COUNT as u32);
Ok(())
}
}