| // Copyright 2024 The Android Open Source Project |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "GrallocEmulated.h" |
| |
| #include <cutils/log.h> |
| |
| #include <optional> |
| #include <unordered_map> |
| |
| #include "drm_fourcc.h" |
| |
| namespace gfxstream { |
| namespace { |
| |
| static constexpr int numFds = 0; |
| static constexpr int numInts = 1; |
| |
| #define DRM_FORMAT_R8_BLOB fourcc_code('9', '9', '9', '9') |
| |
| template <typename T, typename N> |
| T DivideRoundUp(T n, N divisor) { |
| const T div = static_cast<T>(divisor); |
| const T q = n / div; |
| return n % div == 0 ? q : q + 1; |
| } |
| |
| template <typename T, typename N> |
| T Align(T number, N n) { |
| return DivideRoundUp(number, n) * n; |
| } |
| |
| std::optional<uint32_t> GlFormatToDrmFormat(uint32_t glFormat) { |
| switch (glFormat) { |
| case kGlRGB: |
| return DRM_FORMAT_BGR888; |
| case kGlRGB565: |
| return DRM_FORMAT_BGR565; |
| case kGlRGBA: |
| return DRM_FORMAT_ABGR8888; |
| } |
| return std::nullopt; |
| } |
| |
| std::optional<uint32_t> AhbToDrmFormat(uint32_t ahbFormat) { |
| switch (ahbFormat) { |
| case GFXSTREAM_AHB_FORMAT_R8G8B8A8_UNORM: |
| return DRM_FORMAT_ABGR8888; |
| case GFXSTREAM_AHB_FORMAT_R8G8B8X8_UNORM: |
| return DRM_FORMAT_XBGR8888; |
| case GFXSTREAM_AHB_FORMAT_R8G8B8_UNORM: |
| return DRM_FORMAT_BGR888; |
| /* |
| * Confusingly, AHARDWAREBUFFER_FORMAT_RGB_565 is defined as: |
| * |
| * "16-bit packed format that has 5-bit R, 6-bit G, and 5-bit B components, in that |
| * order, from the most-sigfinicant bits to the least-significant bits." |
| * |
| * so the order of the components is intentionally not flipped between the pixel |
| * format and the DRM format. |
| */ |
| case GFXSTREAM_AHB_FORMAT_R5G6B5_UNORM: |
| return DRM_FORMAT_RGB565; |
| case GFXSTREAM_AHB_FORMAT_B8G8R8A8_UNORM: |
| return DRM_FORMAT_ARGB8888; |
| case GFXSTREAM_AHB_FORMAT_BLOB: |
| return DRM_FORMAT_R8_BLOB; |
| case GFXSTREAM_AHB_FORMAT_R8_UNORM: |
| return DRM_FORMAT_R8; |
| case GFXSTREAM_AHB_FORMAT_YV12: |
| return DRM_FORMAT_YVU420; |
| case GFXSTREAM_AHB_FORMAT_R16G16B16A16_FLOAT: |
| return DRM_FORMAT_ABGR16161616F; |
| case GFXSTREAM_AHB_FORMAT_R10G10B10A2_UNORM: |
| return DRM_FORMAT_ABGR2101010; |
| case GFXSTREAM_AHB_FORMAT_Y8Cb8Cr8_420: |
| return DRM_FORMAT_NV12; |
| } |
| return std::nullopt; |
| } |
| |
| struct DrmFormatPlaneInfo { |
| uint32_t horizontalSubsampling; |
| uint32_t verticalSubsampling; |
| uint32_t bytesPerPixel; |
| }; |
| struct DrmFormatInfo { |
| uint32_t androidFormat; |
| uint32_t virglFormat; |
| bool isYuv; |
| uint32_t horizontalAlignmentPixels; |
| uint32_t verticalAlignmentPixels; |
| std::vector<DrmFormatPlaneInfo> planes; |
| }; |
| const std::unordered_map<uint32_t, DrmFormatInfo>& GetDrmFormatInfoMap() { |
| static const auto* kFormatInfoMap = new std::unordered_map<uint32_t, DrmFormatInfo>({ |
| {DRM_FORMAT_ABGR8888, |
| { |
| .androidFormat = GFXSTREAM_AHB_FORMAT_R8G8B8A8_UNORM, |
| .virglFormat = VIRGL_FORMAT_R8G8B8A8_UNORM, |
| .isYuv = false, |
| .horizontalAlignmentPixels = 1, |
| .verticalAlignmentPixels = 1, |
| .planes = |
| { |
| { |
| .horizontalSubsampling = 1, |
| .verticalSubsampling = 1, |
| .bytesPerPixel = 4, |
| }, |
| }, |
| }}, |
| {DRM_FORMAT_ARGB8888, |
| { |
| .androidFormat = GFXSTREAM_AHB_FORMAT_B8G8R8A8_UNORM, |
| .virglFormat = VIRGL_FORMAT_B8G8R8A8_UNORM, |
| .isYuv = false, |
| .horizontalAlignmentPixels = 1, |
| .verticalAlignmentPixels = 1, |
| .planes = |
| { |
| { |
| .horizontalSubsampling = 1, |
| .verticalSubsampling = 1, |
| .bytesPerPixel = 4, |
| }, |
| }, |
| }}, |
| {DRM_FORMAT_BGR888, |
| { |
| .androidFormat = GFXSTREAM_AHB_FORMAT_R8G8B8_UNORM, |
| .virglFormat = VIRGL_FORMAT_R8G8B8_UNORM, |
| .isYuv = false, |
| .horizontalAlignmentPixels = 1, |
| .verticalAlignmentPixels = 1, |
| .planes = |
| { |
| { |
| .horizontalSubsampling = 1, |
| .verticalSubsampling = 1, |
| .bytesPerPixel = 3, |
| }, |
| }, |
| }}, |
| {DRM_FORMAT_BGR565, |
| { |
| .androidFormat = GFXSTREAM_AHB_FORMAT_R5G6B5_UNORM, |
| .virglFormat = VIRGL_FORMAT_B5G6R5_UNORM, |
| .isYuv = false, |
| .horizontalAlignmentPixels = 1, |
| .verticalAlignmentPixels = 1, |
| .planes = |
| { |
| { |
| .horizontalSubsampling = 1, |
| .verticalSubsampling = 1, |
| .bytesPerPixel = 2, |
| }, |
| }, |
| }}, |
| {DRM_FORMAT_R8, |
| { |
| .androidFormat = GFXSTREAM_AHB_FORMAT_R8_UNORM, |
| .virglFormat = VIRGL_FORMAT_R8_UNORM, |
| .isYuv = false, |
| .horizontalAlignmentPixels = 1, |
| .verticalAlignmentPixels = 1, |
| .planes = |
| { |
| { |
| .horizontalSubsampling = 1, |
| .verticalSubsampling = 1, |
| .bytesPerPixel = 1, |
| }, |
| }, |
| }}, |
| {DRM_FORMAT_R8_BLOB, |
| { |
| .androidFormat = GFXSTREAM_AHB_FORMAT_BLOB, |
| .virglFormat = VIRGL_FORMAT_R8_UNORM, |
| .isYuv = false, |
| .horizontalAlignmentPixels = 1, |
| .verticalAlignmentPixels = 1, |
| .planes = |
| { |
| { |
| .horizontalSubsampling = 1, |
| .verticalSubsampling = 1, |
| .bytesPerPixel = 1, |
| }, |
| }, |
| }}, |
| {DRM_FORMAT_ABGR16161616F, |
| { |
| .androidFormat = GFXSTREAM_AHB_FORMAT_R16G16B16A16_FLOAT, |
| .virglFormat = VIRGL_FORMAT_R16G16B16A16_FLOAT, |
| .isYuv = false, |
| .horizontalAlignmentPixels = 1, |
| .verticalAlignmentPixels = 1, |
| .planes = |
| { |
| { |
| .horizontalSubsampling = 1, |
| .verticalSubsampling = 1, |
| .bytesPerPixel = 8, |
| }, |
| }, |
| }}, |
| {DRM_FORMAT_ABGR2101010, |
| { |
| .androidFormat = GFXSTREAM_AHB_FORMAT_R10G10B10A2_UNORM, |
| .virglFormat = VIRGL_FORMAT_R10G10B10A2_UNORM, |
| .isYuv = false, |
| .horizontalAlignmentPixels = 1, |
| .verticalAlignmentPixels = 1, |
| .planes = |
| { |
| { |
| .horizontalSubsampling = 1, |
| .verticalSubsampling = 1, |
| .bytesPerPixel = 4, |
| }, |
| }, |
| }}, |
| {DRM_FORMAT_NV12, |
| { |
| .androidFormat = GFXSTREAM_AHB_FORMAT_Y8Cb8Cr8_420, |
| .virglFormat = VIRGL_FORMAT_NV12, |
| .isYuv = true, |
| .horizontalAlignmentPixels = 2, |
| .verticalAlignmentPixels = 1, |
| .planes = |
| { |
| { |
| .horizontalSubsampling = 1, |
| .verticalSubsampling = 1, |
| .bytesPerPixel = 1, |
| }, |
| { |
| .horizontalSubsampling = 2, |
| .verticalSubsampling = 2, |
| .bytesPerPixel = 2, |
| }, |
| }, |
| }}, |
| {DRM_FORMAT_YVU420, |
| { |
| .androidFormat = GFXSTREAM_AHB_FORMAT_YV12, |
| .virglFormat = VIRGL_FORMAT_YV12, |
| .isYuv = true, |
| .horizontalAlignmentPixels = 32, |
| .verticalAlignmentPixels = 1, |
| .planes = |
| { |
| { |
| .horizontalSubsampling = 1, |
| .verticalSubsampling = 1, |
| .bytesPerPixel = 1, |
| }, |
| { |
| .horizontalSubsampling = 2, |
| .verticalSubsampling = 2, |
| .bytesPerPixel = 1, |
| }, |
| { |
| .horizontalSubsampling = 2, |
| .verticalSubsampling = 2, |
| .bytesPerPixel = 1, |
| }, |
| }, |
| }}, |
| }); |
| return *kFormatInfoMap; |
| } |
| |
| } // namespace |
| |
| EmulatedAHardwareBuffer::EmulatedAHardwareBuffer(uint32_t width, uint32_t height, |
| uint32_t drmFormat, VirtGpuResourcePtr resource) |
| : mRefCount(1), mWidth(width), mHeight(height), mDrmFormat(drmFormat), mResource(resource) {} |
| |
| EmulatedAHardwareBuffer::~EmulatedAHardwareBuffer() {} |
| |
| uint32_t EmulatedAHardwareBuffer::getResourceId() const { return mResource->getResourceHandle(); } |
| |
| uint32_t EmulatedAHardwareBuffer::getWidth() const { return mWidth; } |
| |
| uint32_t EmulatedAHardwareBuffer::getHeight() const { return mHeight; } |
| |
| int EmulatedAHardwareBuffer::getAndroidFormat() const { |
| const auto& formatInfosMap = GetDrmFormatInfoMap(); |
| auto formatInfoIt = formatInfosMap.find(mDrmFormat); |
| if (formatInfoIt == formatInfosMap.end()) { |
| ALOGE("Unhandled DRM format:%u", mDrmFormat); |
| return -1; |
| } |
| const auto& formatInfo = formatInfoIt->second; |
| return formatInfo.androidFormat; |
| } |
| |
| uint32_t EmulatedAHardwareBuffer::getDrmFormat() const { return mDrmFormat; } |
| |
| AHardwareBuffer* EmulatedAHardwareBuffer::asAHardwareBuffer() { |
| return reinterpret_cast<AHardwareBuffer*>(this); |
| } |
| |
| buffer_handle_t EmulatedAHardwareBuffer::asBufferHandle() { |
| return reinterpret_cast<buffer_handle_t>(this); |
| } |
| |
| EGLClientBuffer EmulatedAHardwareBuffer::asEglClientBuffer() { |
| return reinterpret_cast<EGLClientBuffer>(this); |
| } |
| |
| void EmulatedAHardwareBuffer::acquire() { ++mRefCount; } |
| |
| void EmulatedAHardwareBuffer::release() { |
| --mRefCount; |
| if (mRefCount == 0) { |
| delete this; |
| } |
| } |
| |
| int EmulatedAHardwareBuffer::lock(uint8_t** ptr) { |
| if (!mMapped) { |
| mMapped = mResource->createMapping(); |
| if (!mMapped) { |
| ALOGE("Failed to lock EmulatedAHardwareBuffer: failed to create mapping."); |
| return -1; |
| } |
| |
| mResource->transferFromHost(0, 0, mWidth, mHeight); |
| mResource->wait(); |
| } |
| |
| *ptr = (*mMapped)->asRawPtr(); |
| return 0; |
| } |
| |
| int EmulatedAHardwareBuffer::lockPlanes(std::vector<Gralloc::LockedPlane>* ahbPlanes) { |
| uint8_t* data = 0; |
| int ret = lock(&data); |
| if (ret) { |
| return ret; |
| } |
| |
| const auto& formatInfosMap = GetDrmFormatInfoMap(); |
| auto formatInfoIt = formatInfosMap.find(mDrmFormat); |
| if (formatInfoIt == formatInfosMap.end()) { |
| ALOGE("Failed to lock: failed to find format info for drm format:%u", mDrmFormat); |
| return -1; |
| } |
| const auto& formatInfo = formatInfoIt->second; |
| |
| const uint32_t alignedWidth = Align(mWidth, formatInfo.horizontalAlignmentPixels); |
| const uint32_t alignedHeight = Align(mHeight, formatInfo.verticalAlignmentPixels); |
| uint32_t cumulativeSize = 0; |
| for (const DrmFormatPlaneInfo& planeInfo : formatInfo.planes) { |
| const uint32_t planeWidth = DivideRoundUp(alignedWidth, planeInfo.horizontalSubsampling); |
| const uint32_t planeHeight = DivideRoundUp(alignedHeight, planeInfo.verticalSubsampling); |
| const uint32_t planeBpp = planeInfo.bytesPerPixel; |
| const uint32_t planeStrideBytes = planeWidth * planeBpp; |
| const uint32_t planeSizeBytes = planeHeight * planeStrideBytes; |
| ahbPlanes->emplace_back(Gralloc::LockedPlane{ |
| .data = data + cumulativeSize, |
| .pixelStrideBytes = planeBpp, |
| .rowStrideBytes = planeStrideBytes, |
| }); |
| cumulativeSize += planeSizeBytes; |
| } |
| |
| if (mDrmFormat == DRM_FORMAT_NV12) { |
| const auto& uPlane = (*ahbPlanes)[1]; |
| auto vPlane = uPlane; |
| vPlane.data += 1; |
| |
| ahbPlanes->push_back(vPlane); |
| } else if (mDrmFormat == DRM_FORMAT_YVU420) { |
| // Note: lockPlanes() always returns Y, then U, then V but YV12 is Y, then V, then U. |
| auto& plane1 = (*ahbPlanes)[1]; |
| auto& plane2 = (*ahbPlanes)[2]; |
| std::swap(plane1, plane2); |
| } |
| |
| return 0; |
| } |
| |
| int EmulatedAHardwareBuffer::unlock() { |
| if (!mMapped) { |
| ALOGE("Failed to unlock EmulatedAHardwareBuffer: never locked?"); |
| return -1; |
| } |
| mResource->transferToHost(0, 0, mWidth, mHeight); |
| mResource->wait(); |
| mMapped.reset(); |
| return 0; |
| } |
| |
| EmulatedGralloc::EmulatedGralloc() {} |
| |
| uint32_t EmulatedGralloc::createColorBuffer(void*, int width, int height, uint32_t glFormat) { |
| auto drmFormat = GlFormatToDrmFormat(glFormat); |
| if (!drmFormat) { |
| ALOGE("Unhandled format"); |
| return -1; |
| } |
| |
| auto ahb = allocate(width, height, *drmFormat); |
| if (ahb == nullptr) { |
| return -1; |
| } |
| |
| EmulatedAHardwareBuffer* rahb = reinterpret_cast<EmulatedAHardwareBuffer*>(ahb); |
| |
| mOwned.emplace_back(rahb); |
| |
| return rahb->getResourceId(); |
| } |
| |
| int EmulatedGralloc::allocate(uint32_t width, uint32_t height, uint32_t ahbFormat, uint64_t usage, |
| AHardwareBuffer** outputAhb) { |
| (void)usage; |
| |
| auto drmFormat = AhbToDrmFormat(ahbFormat); |
| if (!drmFormat) { |
| ALOGE("Unhandled AHB format:%u", ahbFormat); |
| return -1; |
| } |
| |
| *outputAhb = allocate(width, height, *drmFormat); |
| if (*outputAhb == nullptr) { |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| AHardwareBuffer* EmulatedGralloc::allocate(uint32_t width, uint32_t height, uint32_t drmFormat) { |
| ALOGE("Allocating AHB w:%u, h:%u, format %u", width, height, drmFormat); |
| |
| auto device = VirtGpuDevice::getInstance(); |
| if (!device) { |
| ALOGE("Failed to allocate: no virtio gpu device."); |
| return nullptr; |
| } |
| |
| const auto& formatInfosMap = GetDrmFormatInfoMap(); |
| auto formatInfoIt = formatInfosMap.find(drmFormat); |
| if (formatInfoIt == formatInfosMap.end()) { |
| ALOGE("Failed to allocate: failed to find format info for drm format:%u", drmFormat); |
| return nullptr; |
| } |
| const auto& formatInfo = formatInfoIt->second; |
| |
| const uint32_t alignedWidth = Align(width, formatInfo.horizontalAlignmentPixels); |
| const uint32_t alignedHeight = Align(height, formatInfo.verticalAlignmentPixels); |
| uint32_t stride = 0; |
| uint32_t size = 0; |
| for (uint32_t i = 0; i < formatInfo.planes.size(); i++) { |
| const DrmFormatPlaneInfo& planeInfo = formatInfo.planes[i]; |
| const uint32_t planeWidth = DivideRoundUp(alignedWidth, planeInfo.horizontalSubsampling); |
| const uint32_t planeHeight = DivideRoundUp(alignedHeight, planeInfo.verticalSubsampling); |
| const uint32_t planeBpp = planeInfo.bytesPerPixel; |
| const uint32_t planeStrideBytes = planeWidth * planeBpp; |
| const uint32_t planeSizeBytes = planeHeight * planeStrideBytes; |
| size += planeSizeBytes; |
| if (i == 0) stride = planeStrideBytes; |
| } |
| |
| const uint32_t bind = (drmFormat == DRM_FORMAT_R8_BLOB || drmFormat == DRM_FORMAT_NV12 || |
| drmFormat == DRM_FORMAT_YVU420) |
| ? VIRGL_BIND_LINEAR |
| : VIRGL_BIND_RENDER_TARGET; |
| |
| auto resource = device->createResource(width, height, stride, size, formatInfo.virglFormat, |
| PIPE_TEXTURE_2D, bind); |
| if (!resource) { |
| ALOGE("Failed to allocate: failed to create virtio resource."); |
| return nullptr; |
| } |
| |
| resource->wait(); |
| |
| return reinterpret_cast<AHardwareBuffer*>( |
| new EmulatedAHardwareBuffer(width, height, drmFormat, std::move(resource))); |
| } |
| |
| void EmulatedGralloc::acquire(AHardwareBuffer* ahb) { |
| auto* rahb = reinterpret_cast<EmulatedAHardwareBuffer*>(ahb); |
| rahb->acquire(); |
| } |
| |
| void EmulatedGralloc::release(AHardwareBuffer* ahb) { |
| auto* rahb = reinterpret_cast<EmulatedAHardwareBuffer*>(ahb); |
| rahb->release(); |
| } |
| |
| int EmulatedGralloc::lock(AHardwareBuffer* ahb, uint8_t** ptr) { |
| auto* rahb = reinterpret_cast<EmulatedAHardwareBuffer*>(ahb); |
| return rahb->lock(ptr); |
| } |
| |
| int EmulatedGralloc::lockPlanes(AHardwareBuffer* ahb, std::vector<LockedPlane>* ahbPlanes) { |
| auto* rahb = reinterpret_cast<EmulatedAHardwareBuffer*>(ahb); |
| return rahb->lockPlanes(ahbPlanes); |
| } |
| |
| int EmulatedGralloc::unlock(AHardwareBuffer* ahb) { |
| auto* rahb = reinterpret_cast<EmulatedAHardwareBuffer*>(ahb); |
| return rahb->unlock(); |
| } |
| |
| uint32_t EmulatedGralloc::getHostHandle(const native_handle_t* handle) { |
| const auto* ahb = reinterpret_cast<const EmulatedAHardwareBuffer*>(handle); |
| return ahb->getResourceId(); |
| } |
| |
| uint32_t EmulatedGralloc::getHostHandle(const AHardwareBuffer* handle) { |
| const auto* ahb = reinterpret_cast<const EmulatedAHardwareBuffer*>(handle); |
| return ahb->getResourceId(); |
| } |
| |
| const native_handle_t* EmulatedGralloc::getNativeHandle(const AHardwareBuffer* ahb) { |
| return reinterpret_cast<const native_handle_t*>(ahb); |
| } |
| |
| int EmulatedGralloc::getFormat(const native_handle_t* handle) { |
| const auto* ahb = reinterpret_cast<const EmulatedAHardwareBuffer*>(handle); |
| return ahb->getAndroidFormat(); |
| } |
| |
| int EmulatedGralloc::getFormat(const AHardwareBuffer* handle) { |
| const auto* ahb = reinterpret_cast<const EmulatedAHardwareBuffer*>(handle); |
| return ahb->getAndroidFormat(); |
| } |
| |
| uint32_t EmulatedGralloc::getFormatDrmFourcc(const AHardwareBuffer* handle) { |
| const auto* ahb = reinterpret_cast<const EmulatedAHardwareBuffer*>(handle); |
| return ahb->getDrmFormat(); |
| } |
| |
| uint32_t EmulatedGralloc::getWidth(const AHardwareBuffer* handle) { |
| const auto* ahb = reinterpret_cast<const EmulatedAHardwareBuffer*>(handle); |
| return ahb->getWidth(); |
| } |
| |
| uint32_t EmulatedGralloc::getHeight(const AHardwareBuffer* handle) { |
| const auto* ahb = reinterpret_cast<const EmulatedAHardwareBuffer*>(handle); |
| return ahb->getHeight(); |
| } |
| |
| size_t EmulatedGralloc::getAllocatedSize(const native_handle_t*) { |
| ALOGE("Unimplemented."); |
| return 0; |
| } |
| |
| size_t EmulatedGralloc::getAllocatedSize(const AHardwareBuffer*) { |
| ALOGE("Unimplemented."); |
| return 0; |
| } |
| |
| int EmulatedGralloc::getId(const AHardwareBuffer* ahb, uint64_t* id) { |
| const auto* rahb = reinterpret_cast<const EmulatedAHardwareBuffer*>(ahb); |
| *id = rahb->getResourceId(); |
| return 0; |
| } |
| |
| Gralloc* createPlatformGralloc(int /*deviceFd*/) { |
| return new EmulatedGralloc(); |
| } |
| |
| } // namespace gfxstream |