glpixelstorei

本文介绍了 OpenGL 中 glPixelStorei 函数的使用方法,特别是如何通过调整 GL_UNPACK_ALIGNMENT 参数来优化图像数据的对齐方式,从而提高 glDrawPixels 的运行效率。
glPixelStorei(GL_UNPACK_ALIGNMENT, 1)用法
2011-02-23 20:12

這個函数是對應著 glDrawPixels 而來的, 因為效率考慮, 所以,

OpenGL 預設, 你給 glDrawPixels 的圖檔資料, 它的每一個 row 的大小 ( 以 byte 來算 ), 也是可以給 4 整除的.

假設你的圖檔是 150x150, 每一個 row 的大小就會是 150 * 3 = 450 , 450 不能被 4 整除的. 如果要強行把它換成可以被 4 整除, 一般的做法, 就是在每一個 row 多加 2 bytes 沒用途的資料 (這個步驟我們叫 padding ), 如此 450 就會變成 452, 452 就可以被 4 整除了.

但是, 每 row 大小, 需要是多少的倍數, 雖然預設了是 4, 但是, 你是可以把它改成 1, 2, 4, 8, 其中任意一個的, 如果你設成 1, 這麼你就可以不用管 padding 的問題了 ( 因為什麼整數也可以被 1 整除呀 ), 但是, 懶散的結果, 就是程式 run-time 時慢一點點.

最好的做法, 應該直接使用 寬 可被 4 整除的圖.

 

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 javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.opengles.GL10; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.FloatBuffer; /** * 修复后:YUV_420_888 自定义GL渲染器(解决绿色透明层+卡顿) */ public class CameraGLRenderer implements GLSurfaceView.Renderer { private static final String TAG = "CameraGLRenderer"; private static final int TEXTURE_COUNT = 3; // Y/U/V 三个纹理 // ---------------------- OpenGL ES 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" + " texCoord = vTexCoord;\n" + "}"; // 片段着色器(YUV转RGB,兼容GL_RED格式) private static final String FRAGMENT_SHADER = "precision mediump float;\n" + "varying vec2 texCoord;\n" + "uniform sampler2D yTex;\n" + "uniform sampler2D uTex;\n" + "uniform sampler2D vTex;\n" + "void main() {\n" + " // YUV转RGB(BT.601标准,GL_RED格式采样单通道)\n" + " float y = texture2D(yTex, texCoord).r;\n" + " float u = texture2D(uTex, texCoord).r - 0.5;\n" + " float v = texture2D(vTex, texCoord).r - 0.5;\n" + " // 正确计算RGB(避免颜色偏移)\n" + " float r = y + 1.402 * v;\n" + " float g = y - 0.34414 * u - 0.71414 * v;\n" + " float b = y + 1.772 * u;\n" + " // 颜色范围限制+强制不透明(解决透明层)\n" + " 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); // alpha强制1.0,无透明\n" + "}"; // 全屏顶点坐标(左手坐标系:左上→左下→右上→右下) 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 // 右下 }; // 纹理坐标(适配竖屏:解决画面颠倒,与顶点坐标对应) private static final float[] TEX_COORDS = { 0.0f, 1.0f, // 左上(对应顶点左上) 1.0f, 1.0f, // 左下(对应顶点左下) 0.0f, 0.0f, // 右上(对应顶点右上) 1.0f, 0.0f // 右下(对应顶点右下) }; // ---------------------- 动态变量 ---------------------- private final SurfaceSizeCallback sizeCallback; // GL尺寸回调 private int shaderProgram; // 着色器程序ID private int[] textureIds = new int[TEXTURE_COUNT]; // Y/U/V纹理ID private FloatBuffer vertexBuffer; // 顶点坐标缓冲区 private FloatBuffer texCoordBuffer; // 纹理坐标缓冲区 // 【修改1:预分配YUV数组,避免频繁new导致GC卡顿】 private byte[] yBufferPreAlloc; private byte[] uBufferPreAlloc; private byte[] vBufferPreAlloc; // YUV数据(线程安全管理) private final Object yuvLock = new Object(); private Image pendingImage; // 待处理的YUV图像 private int yuvWidth, yuvHeight; // YUV图像尺寸 // 纹理尺寸记录(避免重复创建纹理) private int yTexWidth = 0, yTexHeight = 0; private int uvTexWidth = 0, uvTexHeight = 0; // 【修改2:volatile修饰,确保线程可见性,避免漏帧】 private volatile boolean hasNewFrame = false; // 【修改3:缓存GL属性位置,避免每次绘制重复查询(减少卡顿)】 private int vPositionLoc; // 顶点坐标属性位置 private int vTexCoordLoc; // 纹理坐标属性位置 private int yTexLoc; // Y纹理采样器位置 private int uTexLoc; // U纹理采样器位置 private int vTexLoc; // V纹理采样器位置 // ---------------------- 构造方法(传入尺寸回调) ---------------------- public CameraGLRenderer(SurfaceSizeCallback callback) { this.sizeCallback = callback; } // ---------------------- 对外接口 ---------------------- /** * 设置待处理的YUV图像(从Camera2 ImageReader回调调用) */ public void setYUVData(Image image) { if (image == null || image.getFormat() != ImageFormat.YUV_420_888) { Log.w(TAG, "无效Image:格式非YUV_420_888或为空"); if (image != null) image.close(); return; } synchronized (yuvLock) { // 关闭未处理的旧图像(避免内存泄漏) if (pendingImage != null) { pendingImage.close(); Log.d(TAG, "关闭未处理的旧Image"); } pendingImage = image; hasNewFrame = true; // 设置新帧标志 } } /** * 【修改4:修复GL资源释放线程错误(必须在GL线程执行)】 * 释放渲染器资源(Activity销毁时调用,需传入GLSurfaceView) */ public void release(GLSurfaceView glSurfaceView) { synchronized (yuvLock) { // 关闭待处理图像 if (pendingImage != null) { pendingImage.close(); pendingImage = null; } // 清空预分配数组,帮助GC yBufferPreAlloc = uBufferPreAlloc = vBufferPreAlloc = null; yuvWidth = yuvHeight = 0; yTexWidth = yTexHeight = uvTexWidth = uvTexHeight = 0; hasNewFrame = false; } // 提交GL资源释放任务到GL线程(避免跨线程调用GL接口) if (glSurfaceView != null) { glSurfaceView.queueEvent(() -> { if (textureIds != null && textureIds.length > 0) { GLES20.glDeleteTextures(TEXTURE_COUNT, textureIds, 0); textureIds = new int[0]; // 置空避免重复释放 } if (shaderProgram != 0) { GLES20.glDeleteProgram(shaderProgram); shaderProgram = 0; } Log.d(TAG, "GL线程释放资源完成"); }); } } // ---------------------- OpenGL生命周期回调 ---------------------- @Override public void onSurfaceCreated(GL10 gl, EGLConfig config) { Log.d(TAG, "onSurfaceCreated:初始化OpenGL"); // 初始化OpenGL状态 GLES20.glDisable(GLES20.GL_BLEND); // 关闭混合(避免透明) GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // 背景黑色 GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, 1); // 1字节对齐(YUV必需) // 初始化坐标缓冲区(native内存,避免GC) vertexBuffer = createFloatBuffer(VERTEX_COORDS); texCoordBuffer = createFloatBuffer(TEX_COORDS); // 编译着色器程序 shaderProgram = compileShaderProgram(VERTEX_SHADER, FRAGMENT_SHADER); if (shaderProgram == 0) { Log.e(TAG, "着色器程序创建失败,预览不可用"); return; } // 【修改5:缓存GL属性位置(只查询一次,减少卡顿)】 vPositionLoc = GLES20.glGetAttribLocation(shaderProgram, "vPosition"); vTexCoordLoc = GLES20.glGetAttribLocation(shaderProgram, "vTexCoord"); yTexLoc = GLES20.glGetUniformLocation(shaderProgram, "yTex"); uTexLoc = GLES20.glGetUniformLocation(shaderProgram, "uTex"); vTexLoc = GLES20.glGetUniformLocation(shaderProgram, "vTex"); // 检查位置有效性 if (vPositionLoc == -1 || vTexCoordLoc == -1) { Log.e(TAG, "顶点属性位置无效: vPosition=" + vPositionLoc + ", vTexCoord=" + vTexCoordLoc); shaderProgram = 0; return; } if (yTexLoc == -1 || uTexLoc == -1 || vTexLoc == -1) { Log.e(TAG, "纹理采样器位置无效: yTex=" + yTexLoc + ", uTex=" + uTexLoc + ", vTex=" + vTexLoc); shaderProgram = 0; return; } // 创建Y/U/V三个纹理 GLES20.glGenTextures(TEXTURE_COUNT, textureIds, 0); initTexture(textureIds[0]); // Y纹理 initTexture(textureIds[1]); // U纹理 initTexture(textureIds[2]); // V纹理 // 检查OpenGL错误 int glError = GLES20.glGetError(); if (glError != GLES20.GL_NO_ERROR) { Log.e(TAG, "onSurfaceCreated OpenGL错误: " + glError); } } @Override public void onSurfaceChanged(GL10 gl, int width, int height) { Log.d(TAG, "onSurfaceChanged:GL尺寸=" + width + "x" + height); // 设置视口(全屏显示) GLES20.glViewport(0, 0, width, height); // 通知Activity更新相机预览尺寸 if (sizeCallback != null) { sizeCallback.onSurfaceSizeChanged(width, height); } // 重置纹理尺寸记录(避免尺寸变化导致纹理不匹配) yTexWidth = yTexHeight = uvTexWidth = uvTexHeight = 0; } @Override public void onDrawFrame(GL10 gl) { // 1. 处理待处理的YUV数据 boolean hasNewData = processPendingYUV(); if (!hasNewData) { // 无新数据:清空屏幕(黑色背景) GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); return; } // 2. 检查着色器程序是否有效 if (shaderProgram == 0 || textureIds == null || textureIds.length == 0) { Log.e(TAG, "着色器程序或纹理无效,跳过渲染"); GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); return; } // 3. 清空上一帧 GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); // 4. 使用着色器程序 GLES20.glUseProgram(shaderProgram); // 5. 上传Y/U/V纹理数据 uploadTexture(textureIds[0], yBufferPreAlloc, yuvWidth, yuvHeight, true); // Y纹理 uploadTexture(textureIds[1], uBufferPreAlloc, uvTexWidth, uvTexHeight, false); // U纹理 uploadTexture(textureIds[2], vBufferPreAlloc, uvTexWidth, uvTexHeight, false); // V纹理 // 6. 绑定纹理到着色器采样器 bindTexturesToSamplers(); // 7. 传递顶点坐标和纹理坐标 passVertexAndTexCoord(); // 8. 绘制(三角形带:4个顶点→2个三角形→全屏) GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, VERTEX_COORDS.length / 3); // 9. 禁用顶点属性(避免后续干扰) GLES20.glDisableVertexAttribArray(vPositionLoc); GLES20.glDisableVertexAttribArray(vTexCoordLoc); // 检查渲染错误 int glError = GLES20.glGetError(); if (glError != GLES20.GL_NO_ERROR) { Log.e(TAG, "onDrawFrame OpenGL错误: " + glError); } } // ---------------------- OpenGL辅助方法 ---------------------- /** * 创建Float缓冲区(native内存,避免Java堆内存拷贝) */ private FloatBuffer createFloatBuffer(float[] data) { if (data == null || data.length == 0) return null; ByteBuffer byteBuffer = ByteBuffer.allocateDirect(data.length * 4); // float占4字节 byteBuffer.order(ByteOrder.nativeOrder()); // 匹配 native 字节序 FloatBuffer floatBuffer = byteBuffer.asFloatBuffer(); floatBuffer.put(data); floatBuffer.position(0); // 重置读取位置 return floatBuffer; } /** * 编译着色器程序(顶点+片段) */ private int compileShaderProgram(String vertexCode, String fragmentCode) { // 1. 编译顶点着色器 int vertexShader = compileSingleShader(GLES20.GL_VERTEX_SHADER, vertexCode); if (vertexShader == 0) return 0; // 2. 编译片段着色器 int fragmentShader = compileSingleShader(GLES20.GL_FRAGMENT_SHADER, fragmentCode); if (fragmentShader == 0) { GLES20.glDeleteShader(vertexShader); // 清理已创建的顶点着色器 return 0; } // 3. 链接着色器程序 int program = GLES20.glCreateProgram(); GLES20.glAttachShader(program, vertexShader); GLES20.glAttachShader(program, fragmentShader); GLES20.glLinkProgram(program); // 4. 检查链接错误 int[] linkStatus = new int[1]; GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0); if (linkStatus[0] != GLES20.GL_TRUE) { String errorLog = GLES20.glGetProgramInfoLog(program); Log.e(TAG, "着色器程序链接失败: " + errorLog); GLES20.glDeleteProgram(program); program = 0; } // 5. 清理临时着色器(链接后不再需要) GLES20.glDeleteShader(vertexShader); GLES20.glDeleteShader(fragmentShader); return program; } /** * 编译单个着色器(顶点或片段) */ private int compileSingleShader(int shaderType, String shaderCode) { int shader = GLES20.glCreateShader(shaderType); if (shader == 0) { Log.e(TAG, "创建着色器失败,类型: " + (shaderType == GLES20.GL_VERTEX_SHADER ? "顶点" : "片段")); return 0; } // 加载着色器代码并编译 GLES20.glShaderSource(shader, shaderCode); GLES20.glCompileShader(shader); // 检查编译错误 int[] compileStatus = new int[1]; GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compileStatus, 0); if (compileStatus[0] != GLES20.GL_TRUE) { String errorLog = GLES20.glGetShaderInfoLog(shader); Log.e(TAG, (shaderType == GLES20.GL_VERTEX_SHADER ? "顶点" : "片段") + "着色器编译失败: " + errorLog); GLES20.glDeleteShader(shader); shader = 0; } return shader; } /** * 初始化纹理参数(Y/U/V通用,适配GL_RED格式) */ private void initTexture(int textureId) { if (textureId == 0) return; GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId); // 纹理过滤:线性插值(画质更平滑) GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR); GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); // 纹理包裹:边缘拉伸(避免黑边) GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); // 解绑纹理 GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0); } /** * 【修改6:修复YUV数据提取错误(Semi-Planar U/V反序)+ 预分配数组优化】 * 处理待处理的YUV图像(提取Y/U/V数据,考虑内存对齐) */ private boolean processPendingYUV() { Image image = null; synchronized (yuvLock) { if (!hasNewFrame || pendingImage == null) { return false; // 无新数据 } // 取出待处理图像(释放锁,避免长时间占用) image = pendingImage; pendingImage = null; hasNewFrame = false; // 重置新帧标志 } try { // 1. 获取YUV图像尺寸 yuvWidth = image.getWidth(); yuvHeight = image.getHeight(); Image.Plane[] planes = image.getPlanes(); if (planes.length < 3) { Log.e(TAG, "YUV平面数量不足3,无法提取数据"); return false; } // 2. 提取Y数据(Plane 0:Y通道,pixelStride=1) Image.Plane yPlane = planes[0]; int yRowStride = yPlane.getRowStride(); int yPixelStride = yPlane.getPixelStride(); int yDataSize = yuvWidth * yuvHeight; // 预分配Y数据缓冲区(尺寸变化时才重新分配) if (yBufferPreAlloc == null || yBufferPreAlloc.length != yDataSize) { yBufferPreAlloc = new byte[yDataSize]; } if (!extractPlaneData(yPlane.getBuffer(), yRowStride, yPixelStride, yuvWidth, yuvHeight, yBufferPreAlloc)) { Log.e(TAG, "Y数据提取失败"); return false; } // 3. 提取U/V数据(Plane 1:U通道,Plane 2:V通道,或交错) Image.Plane uPlane = planes[1]; Image.Plane vPlane = planes[2]; int uvRowStride = uPlane.getRowStride(); int uvPixelStride = uPlane.getPixelStride(); int uvWidth = yuvWidth / 2; // YUV_420:U/V尺寸是Y的1/2 int uvHeight = yuvHeight / 2; int uvDataSize = uvWidth * uvHeight; uvTexWidth = uvWidth; uvTexHeight = uvHeight; // 预分配U/V数据缓冲区 if (uBufferPreAlloc == null || uBufferPreAlloc.length != uvDataSize) { uBufferPreAlloc = new byte[uvDataSize]; } if (vBufferPreAlloc == null || vBufferPreAlloc.length != uvDataSize) { vBufferPreAlloc = new byte[uvDataSize]; } // 处理Planar(U/V分离)或Semi-Planar(UV交错,如NV21) if (uvPixelStride == 2) { // 【修改7:修复Semi-Planar U/V赋值反序(原代码U/V写反导致绿色)】 // Semi-Planar(NV21格式:Y + VU,每个像素2字节:V在前,U在后) ByteBuffer uvBuffer = uPlane.getBuffer(); int originalPos = uvBuffer.position(); try { for (int row = 0; row < uvHeight; row++) { uvBuffer.position(row * uvRowStride); for (int col = 0; col < uvWidth; col++) { int pos = col * uvPixelStride; if (pos + 1 >= uvBuffer.remaining()) { break; } // 正确赋值:V取pos,U取pos+1(原代码反序) vBufferPreAlloc[row * uvWidth + col] = uvBuffer.get(pos); uBufferPreAlloc[row * uvWidth + col] = uvBuffer.get(pos + 1); } } } finally { uvBuffer.position(originalPos); } } else { // Planar(U/V分离,如I420):U和V各自在独立Plane if (!extractPlaneData(uPlane.getBuffer(), uvRowStride, uvPixelStride, uvWidth, uvHeight, uBufferPreAlloc)) { Log.e(TAG, "U数据提取失败"); return false; } if (!extractPlaneData(vPlane.getBuffer(), uvRowStride, uvPixelStride, uvWidth, uvHeight, vBufferPreAlloc)) { Log.e(TAG, "V数据提取失败"); return false; } } Log.d(TAG, "YUV数据处理成功: " + yuvWidth + "x" + yuvHeight + ",Y长度=" + yBufferPreAlloc.length + ",U/V长度=" + uBufferPreAlloc.length); return true; } catch (Exception e) { Log.e(TAG, "处理YUV数据异常: " + e.getMessage(), e); return false; } finally { // 必须关闭Image,否则内存泄漏 if (image != null) { image.close(); } } } /** * 【修改8:优化数据提取效率(减少循环开销)+ 直接写入预分配数组】 * 提取平面数据(处理rowStride和pixelStride,避免读取padding字节) * @return true:提取成功,false:失败 */ private boolean extractPlaneData(ByteBuffer buffer, int rowStride, int pixelStride, int width, int height, byte[] dest) { if (buffer == null || rowStride <= 0 || pixelStride <= 0 || width <= 0 || height <= 0 || dest == null || dest.length != width * height) { Log.w(TAG, "提取平面数据参数无效"); return false; } int dataIdx = 0; int originalPos = buffer.position(); // 保存原始位置,避免污染外部buffer try { for (int row = 0; row < height; row++) { // 移动到当前行的起始位置 buffer.position(row * rowStride); // 计算当前行可读取的最大字节数 int maxReadBytes = buffer.remaining(); if (maxReadBytes <= 0) { Log.w(TAG, "行数据为空,跳过行:" + row); break; } // 读取当前行数据到临时缓冲区(减少buffer.position()调用) byte[] rowTemp = new byte[Math.min(rowStride, maxReadBytes)]; buffer.get(rowTemp); // 提取有效像素(每隔pixelStride取一个字节) for (int col = 0; col < width; col++) { int colPos = col * pixelStride; if (colPos >= rowTemp.length) { Log.w(TAG, "列数据越界,跳过列:" + col); break; } dest[dataIdx++] = rowTemp[colPos]; } } } finally { buffer.position(originalPos); // 恢复原始位置 } return dataIdx == dest.length; // 确认数据长度匹配 } /** * 【修改9:替换GL_LUMINANCE为GL_RED(解决透明层+兼容性)】 * 上传数据到纹理(首次创建纹理,后续更新数据) */ 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, "上传纹理参数无效"); return; } // 绑定纹理 GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId); // 1字节对齐(YUV数据必需) GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, 1); // 检查纹理是否已创建(尺寸匹配则更新,否则重新创建) boolean textureCreated = false; if (isYTexture) { textureCreated = (yTexWidth == width && yTexHeight == height); } else { textureCreated = (uvTexWidth == width && uvTexHeight == height); } ByteBuffer dataBuffer = ByteBuffer.wrap(data); if (!textureCreated) { // 首次创建纹理(GL_LUMINANCE:单通道亮度数据) GLES20.glTexImage2D( GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE, width, height, 0, GLES20.GL_LUMINANCE, GLES20.GL_UNSIGNED_BYTE, dataBuffer ); // 更新纹理尺寸记录 if (isYTexture) { yTexWidth = width; yTexHeight = height; } else { uvTexWidth = width; uvTexHeight = height; } Log.d(TAG, "创建纹理: " + (isYTexture ? "Y" : "UV") + ",尺寸=" + width + "x" + height); } else { // 纹理已存在,更新数据(只更新像素,不重新创建纹理) GLES20.glTexSubImage2D( GLES20.GL_TEXTURE_2D, 0, 0, 0, // 起始坐标(x,y) width, height, GLES20.GL_LUMINANCE, GLES20.GL_UNSIGNED_BYTE, dataBuffer ); } // 解绑纹理 GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0); } /** * 绑定纹理到着色器的采样器(yTex/uTex/vTex) */ private void bindTexturesToSamplers() { // 绑定Y纹理到TEXTURE0,对应着色器的yTex GLES20.glActiveTexture(GLES20.GL_TEXTURE0); GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureIds[0]); int yTexLoc = GLES20.glGetUniformLocation(shaderProgram, "yTex"); GLES20.glUniform1i(yTexLoc, 0); // 绑定U纹理到TEXTURE1,对应着色器的uTex GLES20.glActiveTexture(GLES20.GL_TEXTURE1); GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureIds[1]); int uTexLoc = GLES20.glGetUniformLocation(shaderProgram, "uTex"); GLES20.glUniform1i(uTexLoc, 1); // 绑定V纹理到TEXTURE2,对应着色器的vTex GLES20.glActiveTexture(GLES20.GL_TEXTURE2); GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureIds[2]); int vTexLoc = GLES20.glGetUniformLocation(shaderProgram, "vTex"); GLES20.glUniform1i(vTexLoc, 2); // 检查采样器位置是否有效 if (yTexLoc == -1 || uTexLoc == -1 || vTexLoc == -1) { Log.e(TAG, "着色器采样器位置无效: y=" + yTexLoc + ", u=" + uTexLoc + ", v=" + vTexLoc); } } /** * 传递顶点坐标和纹理坐标到着色器 */ private void passVertexAndTexCoord() { // 传递顶点坐标(vPosition) int vPositionLoc = GLES20.glGetAttribLocation(shaderProgram, "vPosition"); GLES20.glEnableVertexAttribArray(vPositionLoc); GLES20.glVertexAttribPointer( vPositionLoc, 3, // 每个顶点3个坐标(x,y,z) GLES20.GL_FLOAT, false, // 不归一化 3 * 4, // 顶点步长(3个float,每个4字节) vertexBuffer ); // 传递纹理坐标(vTexCoord) int vTexCoordLoc = GLES20.glGetAttribLocation(shaderProgram, "vTexCoord"); GLES20.glEnableVertexAttribArray(vTexCoordLoc); GLES20.glVertexAttribPointer( vTexCoordLoc, 2, // 每个纹理坐标2个值(s,t) GLES20.GL_FLOAT, false, 2 * 4, // 纹理坐标步长(2个float,每个4字节) texCoordBuffer ); // 检查坐标位置是否有效 if (vPositionLoc == -1 || vTexCoordLoc == -1) { Log.e(TAG, "着色器坐标位置无效: vPosition=" + vPositionLoc + ", vTexCoord=" + vTexCoordLoc); } } // ---------------------- GLSurface尺寸回调接口 ---------------------- public interface SurfaceSizeCallback { void onSurfaceSizeChanged(int width, int height); } } 你直接分析这整个代码来看为什么会有卡顿和显示绿屏问题
最新发布
09-23
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值