GLES2.0中文API-glDrawArrays

glDrawArrays函数用于从数组数据中渲染图元。该函数接受模式、起始索引和渲染索引数作为参数,用于构造几何图元序列。通过预先指定顶点、法线和颜色数组,可以使用此函数高效地构建图元序列。注意,若当前程序对象无效,渲染结果将不确定。

名称

glDrawArrays - 从数组数据中渲染图元

C规范

void glDrawArrays(GLenum mode

                               GLint first,

                               GLsizei count);

参数

mode

指定要渲染的图元类型。 接受符号常量GL_POINTSGL_LINE_STRIPGL_LINE_LOOPGL_LINESGL_TRIANGLE_STRIPGL_TRIANGLE_FANGL_TRIANGLES

first

指定已启用阵列中的起始索引。

count

指定要渲染的索引数。

描述

glDrawArrays指定了几个子例程调用的几何图元。你可以使用glVertexAttribPointer预先指定单独的顶点,法线和颜色数组,而不是调用GL过程来传递每个单独的顶点属性并使用它们通过单次调用glDrawArrays来构造图元序列。

当调用glDrawArrays时,它使用每个启用数组中的计数顺序元素来构造几何图元序列,从元素first开始。mode指定构造什么类型的图元以及数组元素如何构造这些图元。

要启用和禁用通用顶点属性数组,请调用glEnableVertexAttribArrayglDisableVertexAttribArray

注意

如果glUseProgram设置的当前程序对象无效,则渲染结果未定义。 但是,这种情况不会产生错误。

错误

GL_INVALID_ENUMmode不是一个可接收的值

GL_INVALID_VALUEcount是负数

GL_INVALID_FRAMEBUFFER_OPERATION:如果当前绑定的帧缓冲区不是帧缓冲区完成状态(即glCheckFramebufferStatus的返回值不是GL_FRAMEBUFFER_COMPLETE)。

另见

glCheckFramebufferStatusglEnableVertexAttribArrayglDisableVertexAttribArrayglDrawElementsglUseProgramglVertexAttribPointer

版权

https://www.khronos.org/registry/OpenGL-Refpages/es2.0/xhtml/glDrawArrays.xml

https://blog.youkuaiyun.com/flycatdeng

Copyright © 1991-2006 Silicon Graphics, Inc.本文档的许可是根据SGI Free Software B License.详见http://oss.sgi.com/projects/FreeB/.

Android,OpenGL ES,图形学
package com.android.example.cameraappxjava.util; import android.graphics.ImageFormat; import android.media.Image; import android.opengl.GLES20; import android.opengl.GLSurfaceView; import android.util.Log; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.FloatBuffer; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.opengles.GL10; /** * GLES 2.0 相机渲染器:处理 YUV_420_888 预览 * */ public class CameraGLRenderer implements GLSurfaceView.Renderer { private static final String TAG = "CameraGLRenderer"; private static final int TEXTURE_COUNT = 3; // Y/U/V 3个纹理(GLES 2.0 支持) // -------------------------- 1. GLES 2.0 兼容配置(无任何不支持API-------------------------- /** * 顶点着色器(GLES 2.0 标准语法,必加精度声明) */ private static final String VERTEX_SHADER = "attribute vec4 vPosition;\n" + // 顶点坐标(输入) "attribute vec2 vTexCoord;\n" + // 纹理坐标(输入) "varying vec2 texCoord;\n" + // 传递纹理坐标到片段着色器 "void main() {\n" + " gl_Position = vPosition;\n" + // 全屏顶点位置(-1~1 覆盖屏幕) " texCoord = vTexCoord;\n" + // 传递纹理坐标 "}"; /** * 片段着色器(GLES 2.0 兼容:用 GL_LUMINANCE 单通道格式,无 GL_RED) */ private static final String FRAGMENT_SHADER = "precision mediump float;\n" + // GLES 2.0 必须声明精度(中等精度平衡性能) "varying vec2 texCoord;\n" + // 从顶点着色器接收的纹理坐标 "uniform sampler2D yTex;\n" + // Y通道纹理采样器(纹理单元0) "uniform sampler2D uTex;\n" + // U通道纹理采样器(纹理单元1) "uniform sampler2D vTex;\n" + // V通道纹理采样器(纹理单元2) "void main() {\n" + // GLES 2.0 兼容:读取 GL_LUMINANCE 纹理的 r 通道(亮度值) " float y = texture2D(yTex, texCoord).r;\n" + " float u = texture2D(uTex, texCoord).r - 0.5;\n" + // U/V 偏移 0.5(YUV 标准) " float v = texture2D(vTex, texCoord).r - 0.5;\n" + // BT.601 YUV转RGB 公式(手机相机通用,避免偏色) " float r = y + 1.402 * v;\n" + " float g = y - 0.34414 * u - 0.71414 * v;\n" + " float b = y + 1.772 * u;\n" + // 限制 RGB 范围 0~1(避免颜色溢出,GLES 2.0 支持 clamp 函数) " r = clamp(r, 0.0, 1.0);\n" + " g = clamp(g, 0.0, 1.0);\n" + " b = clamp(b, 0.0, 1.0);\n" + " gl_FragColor = vec4(r, g, b, 1.0);\n" + // 输出 RGB 颜色(不透明) "}"; /** * 全屏顶点坐标(GLES 2.0 标准坐标,顺序:左上→左下→右上→右下) */ private static final float[] VERTEX_COORDS = { -1.0f, 1.0f, 0.0f, // 左上 -1.0f, -1.0f, 0.0f, // 左下 1.0f, 1.0f, 0.0f, // 右上 1.0f, -1.0f, 0.0f // 右下 }; /** * 纹理坐标(GLES 2.0 兼容,适配竖屏预览,解决画面颠倒) * 映射规则:纹理坐标 → 屏幕坐标(确保竖屏显示正常) */ private static final float[] TEX_COORDS = { 0.0f, 1.0f, // 纹理左上 → 屏幕左上 1.0f, 1.0f, // 纹理左下 → 屏幕左下 0.0f, 0.0f, // 纹理右上 → 屏幕右上 1.0f, 0.0f // 纹理右下 → 屏幕右下 }; // -------------------------- 2. 动态变量(新增:手动记录纹理尺寸,替代GL查询) -------------------------- private int mShaderProgram; // GLES 2.0 着色器程序ID private int[] mTextureIds = new int[TEXTURE_COUNT]; // Y/U/V 纹理ID(GPU资源) private FloatBuffer mVertexBuffer; // 顶点坐标缓冲区(GLES 2.0 要求Buffer格式) private FloatBuffer mTexBuffer; // 纹理坐标缓冲区(GLES 2.0 要求Buffer格式) private int mViewWidth, mViewHeight; // GLSurfaceView 宽高(渲染视口尺寸) // 关键:手动记录 Y/U/V 纹理的宽高(替代 GLES 2.0 不支持的 glGetTexLevelParameteriv) private int mYTexWidth = 0, mYTexHeight = 0; // Y纹理尺寸 private int mUTexWidth = 0, mUTexHeight = 0; // U纹理尺寸(Y的1/2) private int mVTexWidth = 0, mVTexHeight = 0; // V纹理尺寸(Y的1/2) // YUV 数据线程安全管理(避免相机线程与渲染线程竞争) private final Object mYuvLock = new Object(); private Image mPendingImage; // 待处理的相机Image(从Camera2接收) private byte[] mYData, mUData, mVData; // 提取后的 Y/U/V 字节数据 private int mYuvWidth, mYuvHeight; // 相机输出的 YUV 帧宽高 // -------------------------- 3. 对外接口(无修改,直接复用) -------------------------- /** * 设置相机预览Image(线程安全,GLES 2.0/3.0 通用) * * @param image 相机输出的 YUV_420_888 格式Image(必须关闭,避免内存泄漏) */ public void setYUVData(Image image) { Log.w(TAG, "调用setYUVData方法"); if (image == null || image.getFormat() != ImageFormat.YUV_420_888) { Log.w(TAG, "无效Image:格式非 YUV_420_888 或 Image为空"); if (image != null) image.close(); // 必须关闭,避免相机缓冲区泄漏 return; } synchronized (mYuvLock) { // 先关闭之前未处理的Image(防止缓冲区堆积导致卡顿) if (mPendingImage != null) { mPendingImage.close(); Log.d(TAG, "关闭未处理的PendingImage,避免内存泄漏"); } mPendingImage = image; // 存储新的待处理Image } } /** * 释放所有资源(Activity/Fragment 销毁时调用,避免内存泄漏) */ public void release() { synchronized (mYuvLock) { // 1. 关闭待处理的Image if (mPendingImage != null) { mPendingImage.close(); mPendingImage = null; } // 2. 释放CPU端 YUV 数据 mYData = null; mUData = null; mVData = null; mYuvWidth = 0; mYuvHeight = 0; // 3. 重置手动记录的纹理尺寸 mYTexWidth = mYTexHeight = 0; mUTexWidth = mUTexHeight = 0; mVTexWidth = mVTexHeight = 0; } // 4. 释放 GLES 2.0 GPU 资源(纹理+着色器程序) if (mTextureIds != null) { GLES20.glDeleteTextures(TEXTURE_COUNT, mTextureIds, 0); mTextureIds = null; } if (mShaderProgram != 0) { GLES20.glDeleteProgram(mShaderProgram); mShaderProgram = 0; } // 5. 释放缓冲区(帮助GC回收) mVertexBuffer = null; mTexBuffer = null; Log.d(TAG, "所有资源释放完成(GLES 2.0 兼容)"); } // -------------------------- 4. GLES 2.0 生命周期回调(无任何不支持API-------------------------- /** * 初始化回调:GLSurfaceView 首次创建时调用(仅1次) * 作用:初始化OpenGL环境、编译着色器、创建纹理、准备坐标缓冲区 */ @Override public void onSurfaceCreated(GL10 gl, EGLConfig config) { Log.d(TAG, "onSurfaceCreated(GLES 2.0):初始化OpenGL环境"); // GLES 2.0 基础配置:禁用混合(避免透明层干扰预览)、黑色背景 GLES20.glDisable(GLES20.GL_BLEND); GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // 准备坐标缓冲区(GLES 2.0 仅支持 Buffer 格式,不支持直接用数组) mVertexBuffer = createFloatBuffer(VERTEX_COORDS); mTexBuffer = createFloatBuffer(TEX_COORDS); // 编译 GLES 2.0 着色器程序(创建渲染"画笔") mShaderProgram = compileShaderProgram(VERTEX_SHADER, FRAGMENT_SHADER); if (mShaderProgram == 0) { Log.e(TAG, "着色器程序创建失败(GLES 2.0),预览不可用"); return; } // 创建 Y/U/V 3个纹理(GLES 2.0 2D纹理),配置基础参数 GLES20.glGenTextures(TEXTURE_COUNT, mTextureIds, 0); initTexture(mTextureIds[0]); // 初始化 Y 纹理 initTexture(mTextureIds[1]); // 初始化 U 纹理 initTexture(mTextureIds[2]); // 初始化 V 纹理 Log.d(TAG, "GLES 2.0 初始化完成,纹理ID:Y=" + mTextureIds[0] + ", U=" + mTextureIds[1] + ", V=" + mTextureIds[2]); } /** * 尺寸变化回调:GLSurfaceView 宽高改变时调用(如屏幕旋转) * 作用:设置渲染视口(画面显示范围),确保全屏渲染 */ @Override public void onSurfaceChanged(GL10 gl, int width, int height) { mViewWidth = width; mViewHeight = height; // GLES 2.0 设置视口:渲染范围 = GLSurfaceView 全屏(左上角(0,0),宽高=View宽高) GLES20.glViewport(0, 0, width, height); Log.d(TAG, "onSurfaceChanged(GLES 2.0):视口尺寸=" + width + "x" + height); } /** * 帧渲染回调:每帧调用1次(渲染线程执行,核心渲染逻辑) * 流程:处理待处理Image → 上传YUV数据到纹理 → 绑定着色器 → 执行渲染 */ @Override public void onDrawFrame(GL10 gl) { Log.e(TAG, "调用onDrawFrame方法"); // 1. 处理待处理的Image(线程安全,提取Y/U/V数据) boolean hasNewData = processPendingImage(); if (!hasNewData) { // 无新数据:清除屏幕为黑色,避免显示上一帧残留 GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); return; } // 2. 清除上一帧画面(避免画面重叠) GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); // 3. 激活 GLES 2.0 着色器程序(使用"画笔") GLES20.glUseProgram(mShaderProgram); // 4. 上传 Y/U/V 数据到对应纹理(手动判断纹理尺寸,替代GL查询) uploadTexture(mTextureIds[0], mYData, mYuvWidth, mYuvHeight, true); // Y纹理 uploadTexture(mTextureIds[1], mUData, mYuvWidth / 2, mYuvHeight / 2, false); // U纹理(1/2尺寸) uploadTexture(mTextureIds[2], mVData, mYuvWidth / 2, mYuvHeight / 2, false); // V纹理(1/2尺寸) // 5. 绑定纹理到着色器采样器(让"画笔"找到"画布") bindTextureToSampler(); // 6. 传递顶点/纹理坐标(告诉"画笔"画在哪里) passVertexAndTexCoord(); // 7. 执行渲染:GLES 2.0 支持 GL_TRIANGLE_STRIP,4个顶点画2个三角形覆盖全屏 GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, VERTEX_COORDS.length / 3); // 8. 禁用顶点/纹理坐标输入(避免后续渲染干扰) int vPositionLoc = GLES20.glGetAttribLocation(mShaderProgram, "vPosition"); int vTexCoordLoc = GLES20.glGetAttribLocation(mShaderProgram, "vTexCoord"); GLES20.glDisableVertexAttribArray(vPositionLoc); GLES20.glDisableVertexAttribArray(vTexCoordLoc); } private void passVertexAndTexCoord() { int vPositionLoc = GLES20.glGetAttribLocation(mShaderProgram, "vPosition"); GLES20.glEnableVertexAttribArray(vPositionLoc); GLES20.glVertexAttribPointer( vPositionLoc, 3, GLES20.GL_FLOAT, false, 3 * 4, mVertexBuffer ); int vTexCoordLoc = GLES20.glGetAttribLocation(mShaderProgram, "vTexCoord"); GLES20.glEnableVertexAttribArray(vTexCoordLoc); GLES20.glVertexAttribPointer( vTexCoordLoc, 2, GLES20.GL_FLOAT, false, 2 * 4, mTexBuffer ); } // -------------------------- 5. GLES 2.0 辅助方法(无任何不支持API-------------------------- /** * 创建 FloatBuffer:将 Java float 数组转为 GLES 2.0 支持的 Buffer 格式 * * @param array 原始 float 数组(顶点/纹理坐标) * @return GLES 2.0 可识别的 FloatBuffer */ private FloatBuffer createFloatBuffer(float[] array) { if (array == null || array.length == 0) return null; // 1. 分配直接内存(避免JVM GC移动,提升OpenGL访问效率) ByteBuffer byteBuffer = ByteBuffer.allocateDirect(array.length * 4); // 1float=4字节 // 2. 设置字节序(必须与硬件一致,否则数据错乱) byteBuffer.order(ByteOrder.nativeOrder()); // 3. 转换为 FloatBuffer 并写入数据 FloatBuffer floatBuffer = byteBuffer.asFloatBuffer(); floatBuffer.put(array); // 4. 重置读指针(从缓冲区开头开始读取) floatBuffer.position(0); return floatBuffer; } /** * 编译 GLES 2.0 着色器程序:编译顶点+片段着色器,链接为可执行程序 * * @param vertexCode 顶点着色器代码 * @param fragmentCode 片段着色器代码 * @return 着色器程序ID(0 表示失败) */ private int compileShaderProgram(String vertexCode, String fragmentCode) { // 1. 编译顶点着色器(GLES 2.0) int vertexShader = compileSingleShader(GLES20.GL_VERTEX_SHADER, vertexCode); if (vertexShader == 0) return 0; // 2. 编译片段着色器(GLES 2.0) int fragmentShader = compileSingleShader(GLES20.GL_FRAGMENT_SHADER, fragmentCode); if (fragmentShader == 0) { GLES20.glDeleteShader(vertexShader); // 清理已编译的顶点着色器 return 0; } // 3. 链接着色器程序(GLES 2.0) int program = GLES20.glCreateProgram(); GLES20.glAttachShader(program, vertexShader); // 绑定顶点着色器 GLES20.glAttachShader(program, fragmentShader); // 绑定片段着色器 GLES20.glLinkProgram(program); // 执行链接 // 4. 检查链接结果(GLES 2.0) int[] linkStatus = new int[1]; GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0); if (linkStatus[0] != GLES20.GL_TRUE) { Log.e(TAG, "着色器链接失败(GLES 2.0):" + GLES20.glGetProgramInfoLog(program)); GLES20.glDeleteProgram(program); // 清理无效程序 program = 0; } // 5. 清理中间着色器(程序已链接,单个着色器可删除) GLES20.glDeleteShader(vertexShader); GLES20.glDeleteShader(fragmentShader); return program; } /** * 编译单个 GLES 2.0 着色器:编译顶点/片段着色器代码 * * @param shaderType 着色器类型(GL_VERTEX_SHADER / GL_FRAGMENT_SHADER) * @param shaderCode 着色器代码 * @return 着色器ID(0 表示失败) */ private int compileSingleShader(int shaderType, String shaderCode) { // 1. 创建着色器对象(GLES 2.0) int shader = GLES20.glCreateShader(shaderType); if (shader == 0) { Log.e(TAG, "创建着色器失败(GLES 2.0),类型=" + (shaderType == GLES20.GL_VERTEX_SHADER ? "顶点" : "片段")); return 0; } // 2. 绑定着色器代码并编译(GLES 2.0GLES20.glShaderSource(shader, shaderCode); GLES20.glCompileShader(shader); // 3. 检查编译结果(GLES 2.0) int[] compileStatus = new int[1]; GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compileStatus, 0); if (compileStatus[0] != GLES20.GL_TRUE) { Log.e(TAG, (shaderType == GLES20.GL_VERTEX_SHADER ? "顶点" : "片段") + "着色器编译失败(GLES 2.0):" + GLES20.glGetShaderInfoLog(shader)); GLES20.glDeleteShader(shader); // 清理无效着色器 shader = 0; } return shader; } /** * 初始化 GLES 2.0 纹理参数:配置过滤、边缘处理,确保画面清晰无重复 * * @param textureId 纹理ID(Y/U/V 纹理) */ private void initTexture(int textureId) { if (textureId == 0) return; // 绑定纹理(选中GPU"画布",GLES 2.0 必须先绑定再配置) GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId); // 1. 纹理过滤:缩小时线性过滤(画面平滑,避免锯齿) GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR); // 2. 纹理过滤:放大时线性过滤(画面平滑,避免像素块) GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); // 3. 纹理边缘:水平方向超出范围时"夹紧"(不重复显示,避免边缘错乱) GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); // 4. 纹理边缘:垂直方向超出范围时"夹紧" GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); // 解绑纹理(避免后续误操作其他纹理) GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0); } /** * 处理待处理Image:从 mPendingImage 提取 Y/U/V 数据(线程安全) * * @return true=有新数据,false=无新数据 */ private boolean processPendingImage() { Image image = null; synchronized (mYuvLock) { if (mPendingImage == null) { return false; // 无待处理数据 } // 取出待处理Image(释放锁,避免长时间占用) image = mPendingImage; mPendingImage = null; } try { // 1. 提取Image的宽高和Planes(YUV_420_888 格式固定3个Planes) mYuvWidth = image.getWidth(); mYuvHeight = image.getHeight(); Log.e(TAG, "YUV的宽高:"+mYuvWidth+"×"+mYuvHeight); Image.Plane[] planes = image.getPlanes(); if (planes.length < 3) { Log.e(TAG, "Image Planes 数量不足3,无法提取 YUV 数据"); return false; } // 2. 提取 Y 通道数据(Plane[0]:Y通道,无交错) ByteBuffer yBuffer = planes[0].getBuffer(); mYData = byteBufferToByteArray(yBuffer); // 3. 提取 U/V 通道数据(区分 Semi-Planar 和 Planar 模式) if (planes[1].getPixelStride() == 2) { // 模式1:Semi-Planar(UV 交错存储在 Plane[1],Plane[2] 无数据) ByteBuffer uvBuffer = planes[1].getBuffer(); int uvLength = uvBuffer.remaining() / 2; // UV 总长度 = Y 长度 / 2 mUData = new byte[uvLength]; mVData = new byte[uvLength]; // 提取 U(偶数索引)和 V(奇数索引) for (int i = 0; i < uvLength; i++) { mUData[i] = uvBuffer.get(i * 2); // U:第02、4...字节 mVData[i] = uvBuffer.get(i * 2 + 1); // V:第1、3、5...字节 } } else { // 模式2:Planar(UV 分别存储在 Plane[1] 和 Plane[2],无交错) ByteBuffer uBuffer = planes[1].getBuffer(); ByteBuffer vBuffer = planes[2].getBuffer(); mUData = byteBufferToByteArray(uBuffer); mVData = byteBufferToByteArray(vBuffer); } // 4. 验证 YUV 数据长度(避免后续渲染错误) int expectedYLength = mYuvWidth * mYuvHeight; int expectedUVLength = (mYuvWidth / 2) * (mYuvHeight / 2); if (mYData.length != expectedYLength || mUData.length != expectedUVLength || mVData.length != expectedUVLength) { Log.w(TAG, "YUV 数据长度不匹配,重置为正确长度"); mYData = new byte[expectedYLength]; mUData = new byte[expectedUVLength]; mVData = new byte[expectedUVLength]; return false; } Log.d(TAG, "处理 Image 完成(GLES 2.0):YUV 尺寸=" + mYuvWidth + "x" + mYuvHeight + ",数据长度 Y=" + mYData.length + ", U=" + mUData.length); return true; } catch (Exception e) { Log.e(TAG, "处理 Image 异常(GLES 2.0):" + e.getMessage(), e); return false; } finally { // 必须关闭 Image(释放相机缓冲区,避免卡顿的核心!) if (image != null) { image.close(); } } } private byte[] byteBufferToByteArray(ByteBuffer buffer) { if (buffer == null | buffer.remaining() == 0) return new byte[0]; int originalPos = buffer.position(); byte[] data = new byte[buffer.remaining()]; buffer.get(data); buffer.position(originalPos); return data; } /** * 上传数据到 GLES 2.0 纹理(核心:手动记录纹理尺寸,替代 GL 查询) * * @param textureId 纹理ID * @param data 待上传的字节数据(Y/U/V) * @param width 纹理宽度 * @param height 纹理高度 * @param isYTexture 是否为 Y 纹理(用于区分尺寸记录变量) */ private void uploadTexture(int textureId, byte[] data, int width, int height, boolean isYTexture) { if (textureId == 0 || data == null || width <= 0 || height <= 0) { Log.w(TAG, "上传纹理参数无效(GLES 2.0):textureId=" + textureId + ", width=" + width + ", height=" + height); return; } // 绑定纹理(GLES 2.0 必须先绑定再操作) GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId); // 关键:设置像素对齐为 1(YUV 数据无字节对齐,避免数据错位) GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, 1); // 手动判断纹理是否已创建(替代 GLES 2.0 不支持的 glGetTexLevelParameteriv) boolean isTextureCreated = false; if (isYTexture) { isTextureCreated = (mYTexWidth == width && mYTexHeight == height); } else { // U/V 纹理尺寸相同,共用一套判断 isTextureCreated = (mUTexWidth == width && mUTexHeight == height); } ByteBuffer dataBuffer = ByteBuffer.wrap(data); if (!isTextureCreated) { // 首次创建纹理:调用 glTexImage2D(分配GPU内存) GLES20.glTexImage2D( GLES20.GL_TEXTURE_2D, 0, // 2D纹理,基础层级(固定为0GLES20.GL_LUMINANCE, // GLES 2.0 核心:单通道亮度格式 width, height, 0, // 纹理宽高,边界宽度(必须为0GLES20.GL_LUMINANCE, // 数据格式:与内部格式一致 GLES20.GL_UNSIGNED_BYTE, // 数据类型:无符号字节(YUV 数据类型) dataBuffer // 待上传的 Y/U/V 数据 ); // 更新手动记录的纹理尺寸(下次判断用) if (isYTexture) { mYTexWidth = width; mYTexHeight = height; Log.d(TAG, "创建 Y 纹理(GLES 2.0):尺寸=" + width + "x" + height); } else { mUTexWidth = width; mUTexHeight = height; Log.d(TAG, "创建 U/V 纹理(GLES 2.0):尺寸=" + width + "x" + height); } } else { // 复用纹理:调用 glTexSubImage2D(仅更新数据,不重新分配GPU内存,效率更高) GLES20.glTexSubImage2D( GLES20.GL_TEXTURE_2D, 0, // 2D纹理,基础层级 0, 0, // 数据起始坐标(x=0, y=0,全屏更新) width, height, // 数据宽高(与纹理尺寸一致) GLES20.GL_LUMINANCE, // 数据格式:与创建时一致 GLES20.GL_UNSIGNED_BYTE, // 数据类型:与创建时一致 dataBuffer // 待更新的 Y/U/V 数据 ); } // 解绑纹理(避免后续误操作) GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0); } /** * 绑定纹理到 GLES 2.0 着色器采样器:将 Y/U/V 纹理与着色器的 uniform 变量关联 */ private void bindTextureToSampler() { // 1. 绑定 Y 纹理到采样器 yTex(纹理单元0GLES20.glActiveTexture(GLES20.GL_TEXTURE0); // 激活纹理单元0GLES 2.0 必须先激活) GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureIds[0]); // 绑定 Y 纹理 // 关联采样器:将纹理单元0 与 着色器的 yTex 变量绑定 int yTexLoc = GLES20.glGetUniformLocation(mShaderProgram, "yTex"); GLES20.glUniform1i(yTexLoc, 0); // 2. 绑定 U 纹理到采样器 uTex(纹理单元1) GLES20.glActiveTexture(GLES20.GL_TEXTURE1); // 激活纹理单元1 GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureIds[1]); // 绑定 U 纹理 int uTexLoc = GLES20.glGetUniformLocation(mShaderProgram, "uTex"); GLES20.glUniform1i(uTexLoc, 1); // 3. 绑定 V 纹理到采样器 vTex(纹理单元2GLES20.glActiveTexture(GLES20.GL_TEXTURE2); //激活纹理单元2 GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureIds[2]); int vTexLoc = GLES20.glGetUniformLocation(mShaderProgram, "vTex"); GLES20.glUniform1i(vTexLoc, 2); // 添加错误检查 if (yTexLoc == -1 || uTexLoc == -1 || vTexLoc == -1) { Log.e(TAG, "纹理采样器绑定失败: " + "yTex=" + yTexLoc + " uTex=" + uTexLoc + " vTex=" + vTexLoc); } } } 我给代码发给你,结合这两部分代码你来找出问题
最新发布
09-23
<think> 根据提供的两部分代码(MainActivity2和CameraGLRenderer),分析`onDrawFrame`未被调用的原因: ### 关键问题定位: 1. **`requestRender()`调用未生效** - 在`MainActivity2`中,预览回调里调用了`glSurfaceView.requestRender()`,但`CameraGLRenderer.onDrawFrame`未执行。 - **可能原因**:`GLSurfaceView`的渲染线程未启动或阻塞。 2. **`GLSurfaceView`生命周期问题** - `onResume()`中调用了`glSurfaceView.onResume()`,但若`GLSurfaceView`的渲染线程未正确初始化,会导致渲染循环未启动。 3. **数据传递链路中断** - `CameraGLRenderer.processPendingImage()`返回`false`(无新数据),跳过渲染: ```java boolean hasNewData = processPendingImage(); if (!hasNewData) { GLES20.glClear(...); // 清屏但不渲染 return; } ``` - 可能原因: - `setYUVData()`未被调用(检查预览回调是否触发)。 - `mPendingImage`未正确传递(线程同步问题)。 4. **OpenGL初始化失败** - `CameraGLRenderer.onSurfaceCreated()`中若着色器编译失败(`mShaderProgram=0`),会跳过后续渲染。 --- ### 验证步骤(基于代码日志): 1. **确认预览回调触发** - 在`MainActivity2`的预览回调中添加日志: ```java previewImageReader.setOnImageAvailableListener(reader -> { Log.d(TAG, "预览帧到达"); // 添加此行 // ...原有逻辑 }, backgroundHandler); ``` - 如果日志未打印,说明`ImageReader`未收到数据(相机配置问题)。 2. **检查`setYUVData()`执行** - `CameraGLRenderer.setYUVData()`中有日志: ```java Log.w(TAG, "调用setYUVData方法"); ``` - 若此日志未出现,说明数据未传递到渲染器。 3. **检查`processPendingImage()`结果** - `CameraGLRenderer.onDrawFrame()`开头有日志: ```java Log.e(TAG, "调用onDrawFrame方法"); ``` - 观察此日志: - 若未打印 → `GLSurfaceView`渲染未启动(生命周期问题)。 - 若打印但`hasNewData=false` → 数据未到达`mPendingImage`。 4. **验证OpenGL初始化** - `CameraGLRenderer.onSurfaceCreated()`中日志: ```java Log.d(TAG, "onSurfaceCreated(GLES 2.0):初始化OpenGL环境"); ``` - 若此日志未出现,说明`GLSurfaceView`未创建OpenGL上下文。 --- ### 修复方向: #### 问题1:预览回调未触发 - **可能原因**:`ImageReader`未正确绑定到相机输出。 **验证**:在`MainActivity2.createCameraPreviewSession()`中确认`outputSurfaces`包含`previewSurface`(来自`previewImageReader`)。 #### 问题2:`onDrawFrame`未被调用(渲染线程未启动) - **强制渲染测试**:在`MainActivity2.onResume()`末尾添加: ```java glSurfaceView.post(() -> glSurfaceView.requestRender()); ``` - 若此时`onDrawFrame`被调用,说明问题在`requestRender()`链路;否则是`GLSurfaceView`内部问题。 #### 问题3:`mPendingImage`未更新 - **线程竞争**:`setYUVData()`和`onDrawFrame()`在不同线程执行(相机线程 vs GL线程),但已用`synchronized`保护。 - **数据提取失败**:在`processPendingImage()`中: ```java Log.e(TAG, "YUV的宽高:" + mYuvWidth + "×" + mYuvHeight); // 确认数据有效 ``` 若宽高为0,说明`Image`无效。 --- ### 优先排查点: 1. **检查`GLSurfaceView`的日志顺序**: - `onSurfaceCreated` → `onSurfaceChanged` → `onDrawFrame` 应依次出现。 - 若缺失,检查`glSurfaceView.onResume()`是否在`onResume()`中调用。 2. **确认预览回调触发**: - 在`MainActivity2`的预览回调中添加日志,确保相机数据到达。 3. **检查着色器编译日志**: - `CameraGLRenderer.compileShaderProgram()`中若编译失败会打印错误日志,需确保着色器无错误。 §§相关问题§§ 1. 在Android相机开发中,如何确保`ImageReader`的回调稳定接收YUV数据? 2. 使用`GLSurfaceView.RENDERMODE_WHEN_DIRTY`时,哪些场景会导致`requestRender()`失效? 3. 多线程环境下,如何避免OpenGL渲染线程与相机回调线程的数据竞争? 4. 当OpenGL着色器编译失败时,有哪些常见的错误原因和调试方法? 5. Android GLSurfaceView的生命周期方法(onResume/onPause)与Activity生命周期不同步会导致什么问题? </think> 根据提供的 `MainActivity2` 和 `CameraGLRenderer` 代码,分析 `onDrawFrame` 未被调用的核心问题如下: ### 根本原因分析 1. **渲染触发机制失效** `MainActivity2` 中的预览回调通过 `glSurfaceView.requestRender()` 触发渲染,但日志显示 `CameraGLRenderer.setYUVData()` 被调用后 `onDrawFrame` 未被调用,说明渲染请求未生效。 2. **GLSurfaceView 生命周期问题** `MainActivity2.onResume()` 中调用 `glSurfaceView.onResume()` 的时序问题: ```java // 正确顺序:先启动后台线程再恢复GLSurfaceView startBackgroundThread(); // 第2步 glSurfaceView.onResume(); // 第3步(当前代码在第3步) openCamera(); // 第4步 ``` **风险**:若 `glSurfaceView.onResume()` 在后台线程初始化前调用,可能导致 GL 渲染线程未就绪。 3. **数据传递阻塞** `CameraGLRenderer.processPendingImage()` 存在严重问题: ```java // 错误实现:直接操作原始ByteBuffer导致数据错乱 mUData[i] = uvBuffer.get(i * 2); mVData[i] = uvBuffer.get(i * 2 + 1); ``` **后果**: - 修改 `uvBuffer` 的 position 导致后续 `mVData` 提取失败 - 触发 `IllegalStateException` 使 `processPendingImage()` 返回 `false` - `onDrawFrame()` 提前退出(清屏不渲染) 4. **纹理尺寸验证缺陷** ```java // 错误的YUV长度验证(未考虑stride/padding) int expectedYLength = mYuvWidth * mYuvHeight; if (mYData.length != expectedYLength) // 实际数据可能包含padding ``` **结果**:验证失败导致丢弃有效帧数据。 --- ### 具体修复方案 #### 1. 修复数据提取逻辑(关键) 修改 `processPendingImage()` 中的 UV 提取方式: ```java // 修复方案:复制缓冲区避免污染原始数据 ByteBuffer uvBuffer = planes[1].getBuffer().duplicate(); // 创建副本 uvBuffer.position(0); // 重置位置 byte[] interleaved = new byte[uvBuffer.remaining()]; uvBuffer.get(interleaved); // 安全复制到字节数组 // 分离UV通道 mUData = new byte[interleaved.length/2]; mVData = new byte[interleaved.length/2]; for (int i=0; i<interleaved.length/2; i++) { mUData[i] = interleaved[i*2]; mVData[i] = interleaved[i*2+1]; } ``` #### 2. 移除无效的长度验证 删除以下容易误判的检查: ```java // 删除这段代码(Android的YUV数据可能含padding) int expectedYLength = mYuvWidth * mYuvHeight; int expectedUVLength = (mYuvWidth/2) * (mYuvHeight/2); if (mYData.length != expectedYLength || ...) { // 此验证会导致有效数据被丢弃 } ``` #### 3. 调整生命周期时序 修改 `MainActivity2.onResume()`: ```java @Override protected void onResume() { super.onResume(); startBackgroundThread(); // 先确保后台线程启动 // 延迟恢复GLSurfaceView(确保GL线程就绪) new Handler(Looper.getMainLooper()).postDelayed(() -> { glSurfaceView.onResume(); openCamera(); }, 100); } ``` #### 4. 增强错误日志 在 `onDrawFrame()` 添加调试信息: ```java public void onDrawFrame(GL10 gl) { Log.e(TAG, "调用onDrawFrame"); boolean hasNewData = processPendingImage(); Log.d(TAG, "是否有新数据: " + hasNewData); // 添加状态日志 if (!hasNewData) { Log.w(TAG, "无新数据,跳过渲染"); // 明确记录原因 GLES20.glClear(...); return; } ... } ``` --- ### 问题验证流程 1. 检查日志顺序: - 确认 `setYUVData` 被调用后立即输出 "调用setYUVData方法" - 观察 `onDrawFrame` 日志是否出现 2. 验证数据流: - 若 `onDrawFrame` 出现但 `hasNewData=false`,检查 `processPendingImage()` 的异常日志 - 重点监控 `YUV的宽高` 日志是否正常输出 3. 测试强制渲染: ```java // 在MainActivity2的onResume末尾添加测试代码 glSurfaceView.post(() -> { for (int i=0; i<5; i++) { glSurfaceView.requestRender(); Log.d("TEST", "强制触发渲染#" + i); } }); ``` 若此时 `onDrawFrame` 被调用5次,证明渲染机制正常,问题在数据传递链路。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值