Soc 离屏渲染优化 - 传输优化

前文

在序中我曾提及到如果不需要将渲染的结果进行输出, 这条数据链路的传输可以由

VPU -> CPU -> GPU -> CPU -> VPU 简化为 VPU -> GPU -> VPU

这是一个很自然而言的想法, 那么业界的大佬是否也有这种想法; 实际上早在 2011 年 Marek Szyprowski (Samsung) 就实现了
通过 V4L2 采集摄像头的画面, 然后直接用 DRM 显示到屏幕上, 全程没有 CPU 的干预.

更多技术细节可以看看何大神的文章 dma-buf 由浅入深(一) —— 最简单的 dma-buf 驱动程序

我不从事驱动开发, 没有能力也不想讲述这些内容; 故此我还是想站在应用层的角度去讲述这些事情.

有一个问题, 不止诸位有无想过?

在组装 PC 整机时, 我们会说 CPU XXX 型号, DDR XXX G, 显卡 XXX 型号显存 XXX G;
而在买手机时, 我们好像并不会讲显存有多少G;如果感到不和谐的话,请仔细回想一下.

手机上采用的大多数是 SOC 芯片, 全称 System On Chip, 顾名思义其将 CPUGPU 等诸多硬件单元都集中到了一整块芯片上.
注意其是整理密不可分, 跟 PC 这种分离式的结构大有不同.

实际上 CPUGPU 乃至 VPU 都是先接到 BUS 总线, 尔后再与 DDR 也就是内存相连.

所以某种程度上, SOC 上的显存和内存是同一会事情; 既然不同的硬件单元的内存实际上是连接在一起的.

那么是不是可以共享呢? 答案是肯定的.
请添加图片描述

DMA BUF

如果开发了比较久, 一般会接触到 mmap 这个函数, 我比较熟悉的常规操作有两种:

  • 跨进程共享内存
  • 映射文件

这固然解决了一些数据共享问题, 但更多是站在 CPU 这个角度的技术实现.

那么站在 BUS 总线这个角度, 这个角色是谁的呢? 答案是 DMA BUF.

DMA BUF 到目前为止已经有三版实现了, 何大神文章里其实是第一版, 三版分别为:

  • DRM : 最初主要针对于 V4L2 和 DRM
  • ION : 主要应用到安卓系统上, 不过目前已经慢慢停止使用
  • DMA HEAP : 目前的主流应用, Linux 和 安卓 都在逐步支持

对于应用层开发, 将其理解为一种 malloc 的方式即可, 只不过在 malloc 的过程中我们可以拿到一个 fd, 这个 fd 能够帮助我们跨设备节点实现数据共享.

有兴趣具体如何操作的同学可以参考一下 DrmAllocateMethodDmaHeapAllocateMethod

OES Texture

有了 DMA BUF 这项技术,之后需要各设备节点可以实现数据共享需要什么? 答案肯定是各端对 DMA-BUF 的支持啦.

VPU 是可以的, 这里我们不展开; 来讲讲 OpenGL ES 是如何支持的.

实际上 OpenGL ES 或者 OpenGL 是不支持通过使用 DMA BUF 导入纹理的, 因为这种方式不通用不可跨平台.

故实现此特性的实际上是在 EGL, 若 EGL 支持 EGL_EXT_image_dma_buf_import 拓展, 即可通过 EGL 的拓展接口通过 DMA BUF FD.

整个过程需要经历两个步骤, 创建 EGLImage 和绑定 EGLImageOES Texture.

创建 EGLImage

void* CreateEGLImage(const OpenGLFeature& openGLFeature, OpenGLRenderTexture::ptr tex, AbstractDeviceAllocateMethod::ptr allocate)
{
    EGLImageKHR image = nullptr;
    if (openGLFeature._eglCreateImageKHR)
    {
        switch (tex->format)
        {
            case DataFormat::R8G8B8A8_UNORM:
            case DataFormat::R8G8B8A8_UINT:
            {
                EGLint attr[] = 
                {
                    EGL_LINUX_DRM_FOURCC_EXT, MMP_DRM_FORMAT_ABGR8888,
                    EGL_WIDTH, tex->horStride,
                    EGL_HEIGHT, tex->virStride,
                    EGL_DMA_BUF_PLANE0_FD_EXT, allocate->GetFd(),
                    EGL_DMA_BUF_PLANE0_OFFSET_EXT, 0,
                    EGL_DMA_BUF_PLANE0_PITCH_EXT, tex->horStride * 4,
                    EGL_IMAGE_PRESERVED_KHR, EGL_TRUE,
                    EGL_NONE, EGL_NONE,
                    EGL_NONE, EGL_NONE,
                    EGL_NONE
                };
                if (allocate->GetFlags() & DmaHeapAllocateMethod::kArmAFBC)
                {
                    attr[3] = tex->w; attr[5] = tex->h;
                    attr[14] = EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT; attr[15] = (MMP_AFBC_FORMAT_MOD_SPARSE | MMP_AFBC_FORMAT_MOD_BLOCK_SIZE_16x16) & 0xFFFFFFFF;
                    attr[16] = EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT; attr[17] = (0x08<<24u); // ARM 平台标志位
                }
                image = openGLFeature._eglCreateImageKHR(openGLFeature._eglDisplay, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, (EGLClientBuffer)nullptr, attr);
                break;
            }
            case DataFormat::NV12_UINT:
            {

                if (!(allocate->GetFlags() & DmaHeapAllocateMethod::kArmAFBC))
                {
                    EGLint attr[] = 
                    {
                        EGL_LINUX_DRM_FOURCC_EXT,  MMP_DRM_FORMAT_NV12,
                        EGL_WIDTH, tex->horStride,
                        EGL_HEIGHT, tex->virStride,
                        EGL_DMA_BUF_PLANE0_FD_EXT, allocate->GetFd(),
                        EGL_DMA_BUF_PLANE0_OFFSET_EXT, 0,
                        EGL_DMA_BUF_PLANE0_PITCH_EXT, tex->horStride,
                        EGL_DMA_BUF_PLANE1_FD_EXT, allocate->GetFd(),
                        EGL_DMA_BUF_PLANE1_OFFSET_EXT, tex->horStride * tex->virStride,
                        EGL_DMA_BUF_PLANE1_PITCH_EXT, tex->horStride,
                        EGL_IMAGE_PRESERVED_KHR, EGL_TRUE,
                        EGL_NONE
                    };
                    image = openGLFeature._eglCreateImageKHR(openGLFeature._eglDisplay, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, (EGLClientBuffer)nullptr, attr);
                }
                else
                {
                    // See aslo : https://github.com/rockchip-linux/mpp/issues/348
                    EGLint attr[] = 
                    {
                        EGL_LINUX_DRM_FOURCC_EXT,  MMP_DRM_FORMAT_YUV420_8BIT,
                        EGL_WIDTH, tex->w,
                        EGL_HEIGHT, tex->h,
                        EGL_DMA_BUF_PLANE0_FD_EXT, allocate->GetFd(),
                        EGL_DMA_BUF_PLANE0_OFFSET_EXT, 0,
                        EGL_DMA_BUF_PLANE0_PITCH_EXT, tex->w,
                        EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT, (MMP_AFBC_FORMAT_MOD_SPARSE | MMP_AFBC_FORMAT_MOD_BLOCK_SIZE_16x16) & 0xFFFFFFFF,
                        EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT, (0x08<<24u), // ARM 平台标志位
                        EGL_IMAGE_PRESERVED_KHR, EGL_TRUE,
                        EGL_NONE
                    };
                    image = openGLFeature._eglCreateImageKHR(openGLFeature._eglDisplay, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, (EGLClientBuffer)nullptr, attr);
                }
                break;
            }
            default:
                break;
        } 
    }
    if (image == 0 /* EGL_NO_IMAGE */)
    {
        EGLint error = openGLFeature._eglGetError();
        GL_LOG_ERROR << "eglCreateImageKHR fail , egl error is: 0x" << std::hex << std::setw(2) << std::setfill('0') << std::uppercase << error;
        assert(false);
    }
    return image;
}

绑定 EGLImage 至 OES Texture

static void InitExternalTexture(const OpenGLFeature& openGLFeature, OpenGLRenderTexture::ptr tex, AbstractAllocateMethod::ptr alloc, uint64_t size)
{
#if MMP_PLATFORM(LINUX)
    assert(GL_TEXTURE_EXTERNAL_OES == tex->target);
    {
        static auto getDeviceAlloc = [](OpenGLRenderTexture::ptr tex, AbstractAllocateMethod::ptr alloc, uint64_t size) -> AbstractDeviceAllocateMethod::ptr
        {
            AbstractDeviceAllocateMethod::ptr deviceAlloc = alloc ? std::dynamic_pointer_cast<AbstractDeviceAllocateMethod>(alloc) : nullptr;
            if (!deviceAlloc)
            {
                if (!tex->deviceAlloc)
                {
                    tex->deviceAlloc = std::make_shared<DmaHeapAllocateMethod>();
                    tex->deviceAlloc->Malloc(size);
                }
                deviceAlloc = std::dynamic_pointer_cast<AbstractDeviceAllocateMethod>(tex->deviceAlloc);
                if (deviceAlloc && alloc)
                {
                    memcpy(deviceAlloc->GetAddress(0), alloc->GetAddress(0), size);
                }
                else
                {
                    memset(deviceAlloc->GetAddress(0), 0xFF, size);
                }
            }
            return deviceAlloc;
        };

        AbstractDeviceAllocateMethod::ptr deviceAlloc = getDeviceAlloc(tex, alloc, size);
        if (deviceAlloc && openGLFeature._glEGLImageTargetTexture2DOES)
        {
            EGLImageKHR image = CreateEGLImage(openGLFeature, tex, deviceAlloc);
            glBindTexture(tex->target, tex->texture);
            openGLFeature._glEGLImageTargetTexture2DOES(tex->target, image);
            tex->onTextureDeletes.push_back([openGLFeature, image]()
            {
                DestroyEGLImage(openGLFeature, image);
            });

            glTexParameteri(tex->target, GL_TEXTURE_WRAP_S, tex->wrapS);
            glTexParameteri(tex->target, GL_TEXTURE_WRAP_T, tex->wrapT);
            glTexParameteri(tex->target, GL_TEXTURE_MAG_FILTER, tex->magFilter);
            glTexParameteri(tex->target, GL_TEXTURE_MIN_FILTER, tex->minFilter);
            FORCE_GL_FENCE_SYNC();
        }
        else
        {
            assert(false);
        }
    }
    CHECK_GL_ERROR_IF_DEBUG();
#endif
}

这里只讲概念不讲代码, 故我不过多解释; 不过完整的代码实现你可以查阅 OpenGLInitStep.

YUV OES Texture

OpenGL ESTexture 可以是 YUV 吗? 答案是可以的, 只要 OpenGL 支持 GL_EXT_YUV_target 拓展, 或 EGL 支持 EGL_EXT_yuv_surface 拓展即可.

具体详细地说明可以参考 EGL 标准 EGL_EXT_yuv_surface.

这里主要讲述一下其使用的一些要点:

  • 当将 YUV 作为 Texture 使用时, Sampler 需要声明为 __samplerExternal2DY2YEXT, 这时采样得到的将会是 YUV 值对应 xyz
  • 当将 YUV 作为 FrameBuffer OBject 时, FrameBuffer 需要加上 layout(yuv) 的限定说明, 输出的 xyz 将会对应到 YUV
  • 当将 YUV 作为 FrameBuffer OBject 时, glClearrgb 也将对应 yuv, 故需要特殊处理

YUV 纹理是降低开销, 提升性能的有效手段.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值