| // 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; |
| } |