// 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 #include #include #include #include #include #include #include #include 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 boundry. 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 boundry. 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 seperate (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(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; }