Android GLES渲染——OES纹理

纹理

纹理(Texture)最通常的作用是装饰 3D 物体,它就像贴纸一样贴在物体表面,丰富了物体的表面和细节。 在 OpenGLES 开发中,纹理除了用于装饰物体表面,还可以用来作为存储数据的容器。

在 OpenGLES 中,纹理实际上是一个可以被采样的复杂数据集合,是 GPU 的图像数据结构,纹理分为 2D 纹理、 立方图纹理和 3D 纹理。

2D 纹理是 OpenGLES 中最常用和最常见的纹理形式,是一个图像数据的二维数组。纹理中的一个单独数据元素称为纹素或纹理像素。

在Android GLES渲染中,最常用到的纹理主要是GL_TEXTURE_2D以及GL_TEXTURE_EXTERNAL_OES两种。前者为常见的静态贴图渲染等常用纹理,而后者是一种特殊纹理,通常用于处理固定大小和格式的视频流数据或者摄像头实时图像数据。相对于GL_TEXTURE_2D,EXTERNAL_OES纹理的最大区别,便是可以从缓存数据中直接获取数据渲染纹理多边形(Android层,即可以直接从BufferQueue中接受数据并渲染),大大提高了实时视频的处理能力

接下来本文主要介绍几种车载较为常见的平台(高通、MTK)OES纹理创建的方式。

1、Android通用方案

对于实时图像在Android层适配的场景下,通常选择Android自带的OES纹理创建方案:AHardwareBuffer创建OES纹理。对于在Android Camera2适配的取流方案上,更多是同样选择Java层的SurfaceTexture来承载Camera实时数据的纹理,SurfaceTexture的native实现即GLConsumer。如下便是SurfaceTexture的使用方式

SurfaceTexture配合MediaCodec、Camera2,可以很好的实现实时画面显示的解决方案。若针对native层取流方案(MTK的litecam、Qualcomm的AIS)的渲染,需要在native层实现OES纹理的创建。SurfaceTexture本质是GraphicBuffer的消费者,因此我们可以使用直接操作GraphicBuffer()的形式来创建OES纹理。在APIs≥26时,Android官方提供了更为方便的HardwareBuffer来操作GraphicBuffer。两者的关系在另一篇文章《Android渲染-AHardwareBuffer》已介绍,HardwareBuffer实际是GraphicBuffer的包装,其所有的函数调用本质是native层转换为GraphicBuffer后调用GraphicBuffer的接口。AHardwareBuffer创建OES纹理的流程如下:

cpp 复制代码
void avmRenderContext::CreateOESTexture(GLuint *frameBufferTexture) {
        AHardwareBuffer_Desc h_buffer_desc = {0};
        h_buffer_desc.stride = output_width*3;
        h_buffer_desc.height = output_height;
        h_buffer_desc.width = output_width;
        h_buffer_desc.layers = 1;
        //h_buffer_desc.format = 0x14;
        h_buffer_desc.format = AHARDWAREBUFFER_FORMAT_R8G8B8_UNORM;

        h_buffer_desc.usage = AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN|AHARDWAREBUFFER_USAGE_GPU_COLOR_OUTPUT;

        ret = AHardwareBuffer_allocate(&h_buffer_desc, &hardwareBuffer);
        if (ret != 0) {
            LOGE("Failed to AHardwareBuffer_allocate");
        }
        else
        {
            LOGE("success to AHardwareBuffer_allocate");
        }

        EGLint attr[] = {EGL_NONE};

        output_image = eglCreateImageKHR(display, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID, eglGetNativeClientBufferANDROID(hardwareBuffer), attr);
        if (output_image == EGL_NO_IMAGE_KHR) {
            LOGE("eglCreateImageKHR error 0x%X\n", glGetError());
            return -1;
        }

        GL_CHECK(glGenTextures(1, frameBufferTexture));
        GL_CHECK(glBindTexture(GL_TEXTURE_2D, *frameBufferTexture));
        GL_CHECK(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
        GL_CHECK(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
        GL_CHECK(glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, (GLeglImageOES)output_image));
        GL_CHECK(glGenFramebuffers(1, &frameBuffer));
        GL_CHECK(glBindFramebuffer(GL_FRAMEBUFFER, frameBuffer));
        GL_CHECK(glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, frameBufferTexture, 0));
        GL_CHECK(glGenRenderbuffers(1, &frameBufferZBuffer));
        GL_CHECK(glBindRenderbuffer(GL_RENDERBUFFER, frameBufferZBuffer));
        GL_CHECK(glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24_OES, static_cast<GLsizei>(output_width), static_cast<GLsizei>(output_height)));
        GL_CHECK(glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, frameBufferZBuffer));

        GLenum fbstatus = glCheckFramebufferStatus(GL_FRAMEBUFFER);
        if (fbstatus != GL_FRAMEBUFFER_COMPLETE) {
            LOGE("EGL: ERROR: Frambuffer complete check failed 0x%x\n", fbstatus);
        }



        ret = AHardwareBuffer_lock(hardwareBuffer,
                             AHARDWAREBUFFER_USAGE_CPU_READ_MASK,
                             -1,
                             nullptr,
                             &frameBufferTextureVirtAddr);
        if (ret != 0) {
            LOGE("Failed to AHardwareBuffer_lock");
        }
        else
        {
            LOGE("AHardwareBuffer_lock %p",frameBufferTextureVirtAddr);
            ret = AHardwareBuffer_unlock(hardwareBuffer, nullptr);
            if (ret != 0) {
                LOGE("Failed to AHardwareBuffer_unlock");
            }
        }
}

2、MTK DMA Buffer

由于MTK本身自带Linux + Android的双系统架构,因此针对MTK的渲染,还可以使用DMA buffer创建OES纹理,并渲染。按照MTK的文档,具体的实现方式如下:

C++ 复制代码
static struct IMEM_BUF_INFO display_output_buf;
RESULT allocIonBuffer(struct IMEM_BUF_INFO *buf, int size)
{
	memset(buf, 0, sizeof(struct IMEM_BUF_INFO));
	if(ion_alloc_count == 0){
		int ret = open_imem_drv();
	    if (ret <= 0) {
	        LOGE("open ion fail\n");
	        return MERR_UNKNOWN;
	    }
	}
	buf->size = size;
	if (alloc_virt_buf(buf) < 0) {
		return MERR_UNKNOWN;
	}
	if (map_phy_addr(buf)) {
        LOGE("map phy buf fail\n");
		free_virt_buf(buf);
        return MERR_UNKNOWN;
	}
	ion_alloc_count++;
	return MOK;
}
RESULT generateOESOutputTexture(EGL_CTX *egl,int screenWidth, int screenHeight, uintptr_t *virtAddr)
{
	uint32_t aling_size = (screenWidth * screenHeight * 4 + L1_CACHE_BYTES - 1) & ~(L1_CACHE_BYTES - 1);
    allocIonBuffer(&display_output_buf, aling_size);
	*virtAddr = display_output_buf.virtAddr;

   //2、生成输出的oes纹理
	EGLint img_attrs[] = {
        EGL_WIDTH, screenWidth,
        EGL_HEIGHT, screenHeight,
        EGL_LINUX_DRM_FOURCC_EXT, DRM_FORMAT_BGR888,
        EGL_DMA_BUF_PLANE0_FD_EXT,  display_output_buf.memID,
        EGL_DMA_BUF_PLANE0_OFFSET_EXT, 0,
        EGL_DMA_BUF_PLANE0_PITCH_EXT, screenWidth * 3,
        EGL_NONE
    };

    GL_CHECK(glGenTextures(1, &displayframeBufferTexture));

    displayframeBufferImage = eglCreateImageKHR(egl->display, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, 0, img_attrs);
    EGLint egl_err = eglGetError();
	if(EGL_SUCCESS != egl_err){
		LOGE("eglCreateImageKHR error egl_err = 0x%x\n", egl_err);
		//return ;
	}

    GL_CHECK(glBindTexture(GL_TEXTURE_2D, displayframeBufferTexture));
    GL_CHECK(glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
    GL_CHECK(glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
    GL_CHECK(glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, displayframeBufferImage));
    GL_CHECK(glGenFramebuffers(1, &displayframeBuffer));
    glBindFramebuffer(GL_FRAMEBUFFER, displayframeBuffer);

    //color buffer
    GL_CHECK(glFramebufferTexture2D(GL_FRAMEBUFFER,  //target
                                    GL_COLOR_ATTACHMENT0,               //attachment
                                    GL_TEXTURE_2D,                      // textarget
                                    displayframeBufferTexture,              // texture
                                    0));                                 // level

    // Z-Buffer
    GL_CHECK(glGenRenderbuffers(1, &displayframeBufferZBuffer));
    GL_CHECK(glBindRenderbuffer(GL_RENDERBUFFER, displayframeBufferZBuffer));
    GL_CHECK(glRenderbufferStorage(GL_RENDERBUFFER,  GL_DEPTH_COMPONENT24_OES, screenWidth, screenHeight));

    GL_CHECK(glFramebufferRenderbuffer(GL_FRAMEBUFFER,
                                       GL_DEPTH_ATTACHMENT,
                                       GL_RENDERBUFFER,
                                       displayframeBufferZBuffer));

    GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
    if (status != GL_FRAMEBUFFER_COMPLETE){
        LOGE("Framebuffer is NOT complete.\n");
    }else{
        LOGI("Framebuffer COMPLETE.\n");
    }
    return 0;
}

3、Qualcomm

目前基于车载领域的高通平台,通常存在两种系统集成方案:单Android系统和QNX+Android双系统。对于QNX系统而言,其图形显示主要由Screen控制,类似于Android的SurfaceView。因此,在QNX系统同样拥有直接通过Screen创建OES纹理的方案,其效果类似于Android直接从BufferQueue渲染,直接从Screen buffer渲染纹理。至于如何在QNX Screen做渲染,已在另外两篇文章《QNX Screen-Hardware Rendering》《QNX SCREEN-Software Rendering》做了详细介绍,此处仅做简单描述。 QNX创建OES纹理的方式,主要是通过EGL_NATIVE_PIXMAP_KHR枚举,创建native Screen buffer,附加于PIXMAP容器,创建OES纹理,简单的代码示例如下:

C++ 复制代码
///////创建native screen buffer
ret = screen_create_buffer(&pstCtx->buffer);
///////设置buffer属性,此处不在赘述
ret = screen_set_buffer_property_iv(pstCtx->buffer, SCREEN_PROPERTY_FORMAT, &fmt);
.......
///////创建pixmap
ret = screen_create_pixmap(&pstCtx->pixmap, pstCtx->pixmap_screen_ctx);
////// 将native screen buffer 绑定到pixmap上
screen_attach_pixmap_buffer(pstCtx->pixmap, pstCtx->buffer);
/////  创建EGLImage
imageKHR = (EGLImageKHR)eglCreateImageKHR(egl_display, NULL, EGL_NATIVE_PIXMAP_KHR, (EGLClientBUffer)pstCtx->pixmap, NULL);
///// 生成并绑定OES纹理
GL_CHECK(glGenTextures(1, &pstCtx->textures));
GL_CHECK(glBindTexture(GL_TEXTURE_EXTERNAL_OES, pstCtx->textures));

GL_CHECK(glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES), imageKHR));

不仅如此,类似于MTK,高通同样提供了一种内存映射的方式创建OES纹理的方案。经过实践,该方案同样可以在高通平台的QNX、Android上使用:

C++ 复制代码
///// 创建pmem handle
pstCtx->mem_fd = nullptr;
pstCtx->mem_ptr = pmem_malloc_ext_v2(buffer_size,
                                            PMEM_CAMERA_ID,
                                            PMEM_FLAGS_SHMEM | PMEM_FLAGS_PHYS_NON_CONTIG | PMEM_FLAGS_CACHE_NONE,
                                            PMEM_ALIGNMENT_4K,
                                            0,
                                            &pstCtx->mem_fd,
                                            NULL);
////// 设置elg attributes
EGLint attr[] = {
                EGL_WIDTH,buffer_width,
                EGL_HEIGHT,buffer_height,
                EGL_IMAGE_FORMAT_QCOM,buffer_format,
                EGL_IMAGE_EXT_BUFFER_DESCRIPTOR_LOW_QCOM,(((intptr_t)(pstCtx->mem_fd))&0xFFFFFFFF),
                EGL_IMAGE_EXT_BUFFER_DESCRIPTOR_HIGH_QCOM,((intptr_t)(pstCtx->mem_fd)>>32),
                EGL_IMAGE_EXT_BUFFER_SIZE_QCOM,buffer_size,
                EGL_IMAGE_EXT_BUFFER_STRIDE_QCOM,buffer_stride,
                EGL_IMAGE_EXT_BUFFER_MEMORY_TYPE_QCOM,EGL_IMAGE_EXT_BUFFER_MEMORY_TYPE_PMEM_QCOM,
                EGL_IMAGE_EXT_BUFFER_PLANE0_OFFSET_QCOM, 0, 
                EGL_NONE};
///// 创建EGLImage
pstCtx->output_image = eglCreateImageKHR(pstCtx->egl_disp,pstCtx->egl_ctx,EGL_NEW_IMAGE_QCOM,(EGLClientBuffer)0,attr);

GL_CHECK(glGenTextures(1, &pstCtx->frameBufferTexture));
GL_CHECK(glBindTexture(GL_TEXTURE_2D, pstCtx->frameBufferTexture));
GL_CHECK(glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
GL_CHECK(glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
GL_CHECK(glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, (GLeglImageOES)pstCtx->output_image));

如上,便是目前再工作中遇到的常见的车载平台中使用的OES纹理创建的方案,做归纳整理。

相关推荐
IManiy3 分钟前
总结之Vibe Coding:后端骨架
后端
ikoala6 分钟前
Codex 怎么买、怎么充值?先把这两套计费搞清楚
前端·javascript·后端
前端Hardy36 分钟前
一个时代结束了:npm 终于对 install 脚本下手了
前端·javascript·后端
damaoyou38 分钟前
Cog3DRangeImagePlaneEstimatorTool完全指南
后端
Nturmoils1 小时前
分页别写太顺手,LIMIT 背后还有排序和边界
数据库·后端
神奇小汤圆1 小时前
国产版“Codex”初体验,智谱ZCode很强啊!
后端
站大爷IP1 小时前
Python里的“赋值”到底是什么意思?
后端
鹅城剑仙2 小时前
Spring Boot 微服务架构设计与最佳实践
spring boot·后端·微服务
Full Stack Developme3 小时前
Spring Integration 教程
java·后端·spring
爱勇宝3 小时前
AI 时代,前端工程师的话语权正在下降?
前端·后端