blob: a83dd104285f63cbaa0298cf2786e3ab137e6598 [file] [log] [blame]
// Copyright 2018 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.
#include <lib/sysmem/sysmem.h>
#include <fbl/algorithm.h>
#include <fuchsia/sysmem/c/fidl.h>
#include <lib/fidl-async/bind.h>
#include <lib/syslog/global.h>
#include <lib/zx/vmo.h>
#include <string.h>
#include <zircon/assert.h>
#include <zircon/syscalls.h>
constexpr char kTag[] = "sysmem";
namespace {
// A helper function to set the plane info for the most common YUV planar formats.
// The width and height fields of |format| must be valid before calling
// this function.
// |format|->layers and |format|->planes will be set.
// The intensity (Y in YUV) is assumed to be present at full resolution, in the
// first plane, with |y_bits_per_pixel| representing each pixel.
// The U and V planes follow. |uv_together| indicates that U and V are both located on
// the second plane; otherwise U and V are located on planes 2 and 3.
// This function assumes U and V are represented equally.
// |uv_horizontal_bits_per_pixel| indicates how many bits each pixel is represented by
// for a horizontal line only - the vertical subsampling is indicated by |uv_vertical_subsample|,
// so a UV plane that is subsampled 2x2 and U and V are 8 bit interleaved (i.e. NV12)
// (This means for every 2 Y pixels, there is one U byte and one V byte)
// would give uv_horizontal_bits_per_pixel = 4 (8 bits for U and 8 for V for every 2 pixels), and
// a uv_vertical_subsample = 2, to indicate that those 8 bits actually correspond to a set
// of 4 pixels.
// |buffer_size| is set to the total (maximum) image size, rounded up to the nearest page boundary.
void SetImagePlaneInfoPlanarYuv(fuchsia_sysmem_ImageFormat* format,
size_t* buffer_size, uint32_t y_bits_per_pixel,
uint32_t uv_horizontal_bits_per_pixel,
uint32_t uv_vertical_subsample,
bool uv_together, bool page_align_layers = false) {
uint32_t offset;
format->planes[0].byte_offset = 0;
format->planes[0].bytes_per_row = (format->width * y_bits_per_pixel) / 8;
offset = format->planes[0].bytes_per_row * format->height;
offset = page_align_layers ? fbl::round_up(offset, ZX_PAGE_SIZE) : offset;
format->planes[1].bytes_per_row = (format->width * uv_horizontal_bits_per_pixel * (uv_together ? 2 : 1)) / 8;
format->planes[1].byte_offset = offset;
offset += format->planes[1].bytes_per_row * format->height / uv_vertical_subsample;
offset = page_align_layers ? fbl::round_up(offset, ZX_PAGE_SIZE) : offset;
format->layers = 2;
if (!uv_together) {
format->layers = 3;
format->planes[2].bytes_per_row = format->planes[1].bytes_per_row;
format->planes[2].byte_offset = offset;
offset += format->planes[2].bytes_per_row * format->height / uv_vertical_subsample;
}
*buffer_size = fbl::round_up(offset, ZX_PAGE_SIZE);
}
// A helper function to set the plane info for the most common packed formats.
// The width and height fields of |format| must be valid before calling
// this function.
// |format|->layers and |format|->planes will be set.
// |buffer_size| is set to the total (maximum) image buffer size, rounded up to the nearest page boundary.
void SetImagePlaneInfoPacked(fuchsia_sysmem_ImageFormat* format,
size_t* buffer_size, uint32_t bits_per_pixel) {
format->planes[0].bytes_per_row = (format->width * bits_per_pixel) / 8;
format->layers = 1;
*buffer_size = fbl::round_up(format->height * format->planes[0].bytes_per_row, ZX_PAGE_SIZE);
}
zx_status_t PickImageFormat(const fuchsia_sysmem_BufferSpec& spec,
fuchsia_sysmem_ImageFormat* format,
size_t* buffer_size) {
// If hardware compatibility needs to be checked, do so here!
// For the simple case, just use whatever format was specified.
format->width = spec.image.min_width;
format->height = spec.image.min_height;
format->pixel_format = spec.image.pixel_format;
format->color_space = spec.image.color_space;
// Need to fill out the plane info, which depends on pixel_format:
// (More generally, it also depends on color space and BufferUsage,
// but this is a simplified version.)
switch (format->pixel_format.type) {
case fuchsia_sysmem_PixelFormatType_R8G8B8A8:
case fuchsia_sysmem_PixelFormatType_BGRA32:
SetImagePlaneInfoPacked(format, buffer_size, 32);
break;
case fuchsia_sysmem_PixelFormatType_YUY2:
SetImagePlaneInfoPacked(format, buffer_size, 16);
break;
// NV12 has an NxN Y plane and an interlaced (N/2)x(N/2) U and V plane.
case fuchsia_sysmem_PixelFormatType_NV12:
SetImagePlaneInfoPlanarYuv(format, buffer_size, 8, 4, 2, true, false);
break;
// I420 has an NxN Y plane and separate (N/2)x(N/2) U and V planes.
case fuchsia_sysmem_PixelFormatType_I420:
SetImagePlaneInfoPlanarYuv(format, buffer_size, 8, 4, 2, false, false);
break;
// M420 is interleaved version of I420, with 2 rows of Y and one row of equal size with
// 2x2 subsampled U and V. It results in 12 bits per pixel, but since it is organized
// as height * 1.5 rows, SetImagePlaneInfoPacked will not work if line padding is != 0.
case fuchsia_sysmem_PixelFormatType_M420:
SetImagePlaneInfoPacked(format, buffer_size, 12);
break;
default:
FX_LOGF(ERROR, kTag, "Unsupported pixel format %u\n", format->pixel_format.type);
// static_cast<const uint32_t>(spec.image.pixel_format));
return ZX_ERR_NOT_SUPPORTED;
}
return ZX_OK;
}
} // namespace
static zx_status_t Allocator_AllocateCollection(void* ctx,
uint32_t buffer_count,
const fuchsia_sysmem_BufferSpec* spec,
const fuchsia_sysmem_BufferUsage* usage,
fidl_txn_t* txn) {
fuchsia_sysmem_BufferCollectionInfo info;
memset(&info, 0, sizeof(info));
// Most basic usage of the allocator: create vmos with no special vendor format:
// 1) Pick which format gets used. For the simple case, just use whatever format was given.
// We also assume here that the format is an ImageFormat
ZX_ASSERT(info.format.tag == fuchsia_sysmem_BufferSpecTag_image);
zx_status_t status = PickImageFormat(*spec, &info.format.image, &info.vmo_size);
if (status != ZX_OK) {
FX_LOG(ERROR, kTag, "Failed to pick format for Buffer Collection\n");
return fuchsia_sysmem_AllocatorAllocateCollection_reply(txn, status, &info);
}
// 3) Allocate the buffers. This will be specialized for different formats.
info.buffer_count = buffer_count;
for (uint32_t i = 0; i < buffer_count; ++i) {
status = zx_vmo_create(info.vmo_size, 0, &info.vmos[i]);
if (status != ZX_OK) {
// Close the handles we created already. We do not support partial allocations.
for (uint32_t j = 0; j < i; ++j) {
zx_handle_close(info.vmos[j]);
info.vmos[j] = ZX_HANDLE_INVALID;
}
info.buffer_count = 0;
FX_LOG(ERROR, kTag, "Failed to allocate Buffer Collection\n");
return fuchsia_sysmem_AllocatorAllocateCollection_reply(txn, ZX_ERR_NO_MEMORY, &info);
}
}
// If everything is happy and allocated, can give ZX_OK:
return fuchsia_sysmem_AllocatorAllocateCollection_reply(txn, ZX_OK, &info);
}
static zx_status_t Allocator_AllocateSharedCollection(void* ctx,
uint32_t buffer_count,
const fuchsia_sysmem_BufferSpec* spec,
zx_handle_t token_peer,
fidl_txn_t* txn) {
return fuchsia_sysmem_AllocatorAllocateSharedCollection_reply(txn, ZX_ERR_NOT_SUPPORTED);
}
static zx_status_t Allocator_BindSharedCollection(void* ctx,
const fuchsia_sysmem_BufferUsage* usage,
zx_handle_t token,
fidl_txn_t* txn) {
fuchsia_sysmem_BufferCollectionInfo info;
memset(&info, 0, sizeof(info));
return fuchsia_sysmem_AllocatorBindSharedCollection_reply(txn, ZX_ERR_NOT_SUPPORTED, &info);
}
static constexpr const fuchsia_sysmem_Allocator_ops_t allocator_ops = {
.AllocateCollection = Allocator_AllocateCollection,
.AllocateSharedCollection = Allocator_AllocateSharedCollection,
.BindSharedCollection = Allocator_BindSharedCollection,
};
static zx_status_t connect(void* ctx, async_dispatcher_t* dispatcher,
const char* service_name, zx_handle_t request) {
if (!strcmp(service_name, fuchsia_sysmem_Allocator_Name)) {
return fidl_bind(dispatcher, request,
(fidl_dispatch_t*)fuchsia_sysmem_Allocator_dispatch,
ctx, &allocator_ops);
}
zx_handle_close(request);
return ZX_ERR_NOT_SUPPORTED;
}
static constexpr const char* sysmem_services[] = {
fuchsia_sysmem_Allocator_Name,
nullptr,
};
static constexpr zx_service_ops_t sysmem_ops = {
.init = nullptr,
.connect = connect,
.release = nullptr,
};
static constexpr zx_service_provider_t sysmem_service_provider = {
.version = SERVICE_PROVIDER_VERSION,
.services = sysmem_services,
.ops = &sysmem_ops,
};
const zx_service_provider_t* sysmem_get_service_provider() {
return &sysmem_service_provider;
}