内容提要
图形渲染岗位高频面试题总结:一、大图纹理跨JNI传递可用AndroidBitmap_lockPixels获取直接指针,或通过AHardwareBuffer与EGLImage实现零拷贝;二、OES纹理是外部图形缓冲的只读视图,由驱动完成YUV转换与布局适配,因此常用于相机视频链路;三、双线性插值仅取4个纹素,缩小时仍属点采样,会违反采样定理而产生摩尔纹。
延伸解读
面试追问的落点:工程细节而非概念背诵
文章开头点明,面试官判断候选人是否“做过”,往往靠这类工程细节。三个问题都指向实际开发中的取舍:大图传递的拷贝次数、OES纹理的驱动适配角色、双线性插值的采样局限。读者可借此理解,图形岗位的追问常从“是什么”转向“为什么这样设计”和“边界在哪里”,准备时需关注链路中的具体环节而非仅记结论。
零拷贝的两层路径:直接指针与GPU缓冲
文章将大图纹理传递的优化分为两层。第一层用AndroidBitmap_lockPixels或GetDirectBufferAddress取得直接指针,消除Java数组与JNI中间副本,仅保留GPU上传这一次拷贝。第二层针对源本身已是GPU可采样缓冲的情况,通过AHardwareBuffer与EGLImage绑定为纹理,像素始终留在Gralloc缓冲中,连glTexImage2D都省去。两层路径对应不同前提,读者需先判断源数据是否已在GPU侧。
OES纹理的价值在驱动代劳格式适配
文章强调GL_TEXTURE_EXTERNAL_OES不是像素格式,而是外部图形缓冲的只读视图。相机或视频链路常用它,是因为底层GraphicBuffer可能是YUV多平面、厂商私有layout或带特殊stride,普通2D采样器无法解读。外部纹理把YUV→RGB转换与layout处理交给驱动,应用只需写shader。配合updateTexImage与getTransformMatrix,像素不经CPU回读,这是低延迟预览与滤镜的基础。
双线性插值不解决缩小场景的采样定理问题
文章澄清一个常见误解:双线性采样与摩尔纹不在同一维度。L_LINEAR取周围4个纹素加权平均,本质是点采样加2×2低通,主要改善放大时的锯齿。缩小时一个屏幕像素覆盖大量纹素,双线性仍只取其中4个,等价于对高频信号点采样,违反奈奎斯特采样定理,高频折叠为低频伪影,表现为细密条纹、闪烁与爬行。读者应区分放大与缩小两种情形,不能指望双线性消除摩尔纹。
Q&A
大图纹理跨JNI传递时,常规做法为什么会产生多次拷贝?
常规做法将Java层Bitmap像素通过jbyteArray或ByteBuffer传到native,再交给glTexImage2D。这条链路上通常发生三次拷贝:Bitmap.getPixels复制像素到Java数组;GetByteArrayElements可能再复制一份并在释放时写回;glTexImage2D由驱动上传至GPU。大图卡顿的根因往往是这些中间副本。
如何优化大图纹理跨JNI传递,避免不必要的拷贝?
第一层优化是取得直接指针:Android 8.0起可用AndroidBitmap_lockPixels拿到像素首地址,直接作为glTexImage2D的数据源,上传后立即解锁;传ByteBuffer.allocateDirect时用GetDirectBufferAddress同理。此路径只保留GPU上传这一次拷贝。第二层是彻底免上传:若源是GPU可采样的图形缓冲,可用AHardwareBuffer通过EGLImage绑定为纹理,像素始终留在Gralloc缓冲中,CPU侧零拷贝。
OES纹理是什么?为什么相机和视频链路常用它?
GL_TEXTURE_EXTERNAL_OES不是像素格式,而是外部生产者写入的图形缓冲在GLES中的只读视图,配套GL_OES_EGL_image_external扩展和samplerExternalOES。底层GraphicBuffer可能是YUV多平面或私有layout,普通2D采样器无法解读,外部纹理将格式适配交给驱动,在采样时完成YUV到RGB转换和layout处理。标准链路是Camera2或MediaCodec输出到Surface,由SurfaceTexture消费并包装成OES纹理,像素留在Gralloc缓冲中,无需CPU回读,从而实现低延迟。
双线性插值为什么还会出现摩尔纹?
双线性采样取周围4个纹素加权平均,本质是点采样加小范围低通,用于解决放大时的锯齿。摩尔纹来自缩小时:一个屏幕像素覆盖大量纹素,双线性仍只取其中4个,等价于对高频信号做点采样,违反奈奎斯特采样定理,高频被折叠成低频伪影,表现为细密条纹、闪烁与爬行。因此双线性采样与摩尔纹不在同一维度,开双线性并不能避免摩尔纹。
在图形渲染面试中,如何判断候选人是背过还是做过?
面试官往往通过工程细节来判断,例如大图纹理跨JNI传递时如何避免拷贝、OES纹理的使用场景、双线性插值与摩尔纹的关系等。这些问题容易连环追问,只有真正做过并深入理解底层机制的人才能回答清楚。
使用OES纹理时,如何正确更新纹理并处理方向?
调用updateTexImage()将队列中的最新帧绑定到纹理,然后用getTransformMatrix()取出的矩阵修正方向与裁剪。这样能确保采样到的图像方向正确且裁剪合适。