赞
踩
渲染是音视频技术栈相关的一个非常重要的方向,视频图像在设备上的展示、各种流行的视频特效都离不开渲染技术的支持。
在 RenderDemo 这个工程示例系列,我们将为大家展示一些渲染相关的 Demo,来向大家介绍如何在 iOS/Android 平台上手一些渲染相关的开发。
这里是第一篇:用 OpenGL 画一个三角形。我们分别在 iOS 和 Android 实现了用 OpenGL 画一个三角形的 Demo。在本文中,包括如下内容:
本文福利, 免费领取C++音视频学习资料包、技术视频/代码,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,编解码,推拉流,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓
如果我们了解了 OpenGL ES 就会知道,虽然它定义了一套移动设备的图像渲染 API,但是并没有定义窗口系统。为了让 GLES 能够适配各种平台,GLES 需要与知道如何通过操作系统创建和访问窗口的库结合使用,这就有了 EGL,EGL 是 OpenGL ES 渲染 API 和本地窗口系统之间的一个中间接口层,它主要由系统制造商实现。EGL 提供如下机制:
EGL 是 OpenGL ES 与设备的桥梁,以实现让 OpenGL ES 能够在当前设备上进行绘制。
iOS 平台对 EGL 的实现是 EAGL(Embedded Apple Graphics Library),其中 CAEAGLLayer 就是一种可以支持 OpenGL ES 绘制的图层类型,我们的 Demo 里会用它作为绘制三角形的图层。
代码比较简单,我们直接上代码:
DMTriangleRenderView.m
- #import "DMTriangleRenderView.h"
- #import <OpenGLES/ES2/gl.h>
-
- // 定义顶点的数据结构:包括顶点坐标和颜色维度。
- #define PositionDimension 3
- #define ColorDimension 4
- typedef struct {
- GLfloat position[PositionDimension]; // { x, y, z }
- GLfloat color[ColorDimension]; // {r, g, b, a}
- } SceneVertex;
-
-
- @interface DMTriangleRenderView ()
- @property (nonatomic, assign) GLsizei width;
- @property (nonatomic, assign) GLsizei height;
-
- @property (nonatomic, strong) CAEAGLLayer *eaglLayer;
- @property (nonatomic, strong) EAGLContext *eaglContext;
-
- @property (nonatomic, assign) GLuint simpleProgram;
-
- @property (nonatomic, assign) GLuint renderBuffer;
- @property (nonatomic, assign) GLuint frameBuffer;
-
- @end
-
- @implementation DMTriangleRenderView
-
- #pragma mark - Setup
- - (instancetype)initWithFrame:(CGRect)frame {
- self = [super initWithFrame:frame];
- if (self) {
- _width = frame.size.width;
- _height = frame.size.height;
- [self render];
- }
- return self;
- }
-
- #pragma mark - Action
- - (void)render {
- // 1、设定 layer 的类型。
- _eaglLayer = (CAEAGLLayer *) self.layer;
- _eaglLayer.opacity = 1.0;
- _eaglLayer.drawableProperties = @{kEAGLDrawablePropertyRetainedBacking: @(NO),
- kEAGLDrawablePropertyColorFormat: kEAGLColorFormatRGBA8};
-
-
- // 2、创建 OpenGL 上下文。
- EAGLRenderingAPI api = kEAGLRenderingAPIOpenGLES2; // 使用的 OpenGL API 的版本。
- EAGLContext *context = [[EAGLContext alloc] initWithAPI:api];
- if (!context) {
- NSLog(@"Create context failed!");
- return;
- }
- BOOL r = [EAGLContext setCurrentContext:context];
- if (!r) {
- NSLog(@"setCurrentContext failed!");
- return;
- }
- _eaglContext = context;
-
-
- // 3、申请并绑定渲染缓冲区对象 RBO 用来存储即将绘制到屏幕上的图像数据。
- glGenRenderbuffers(1, &_renderBuffer); // 创建 RBO。
- glBindRenderbuffer(GL_RENDERBUFFER, _renderBuffer); // 绑定 RBO 到 OpenGL 渲染管线。
- [_eaglContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:_eaglLayer]; // 将渲染图层(_eaglLayer)的存储绑定到 RBO。
-
-
- // 4、申请并绑定帧缓冲区对象 FBO。FBO 本身不能用于渲染,只有绑定了纹理(Texture)或者渲染缓冲区(RBO)等作为附件之后才能作为渲染目标。
- glGenFramebuffers(1, &_frameBuffer); // 创建 FBO。
- glBindFramebuffer(GL_FRAMEBUFFER, _frameBuffer); // 绑定 FBO 到 OpenGL 渲染管线。
- // 将 RBO 绑定为 FBO 的一个附件,绑定后,OpenGL 对 FBO 的绘制会同步到 RBO 后再上屏。
- glFramebufferRenderbuffer(GL_FRAMEBUFFER,
- GL_COLOR_ATTACHMENT0,
- GL_RENDERBUFFER,
- _renderBuffer);
-
-
- // 5、清理窗口颜色,并设置渲染窗口。
- glClearColor(0.5, 0.5, 0.5, 1.0); // 设置渲染窗口颜色。这里是灰色。
- glClear(GL_COLOR_BUFFER_BIT); // 清空旧渲染缓存。
- glViewport(0, 0, _width, _height); // 设置渲染窗口区域。
-
-
- // 6、加载和编译 shader,并链接到着色器程序。
- if (_simpleProgram) {
- glDeleteProgram(_simpleProgram);
- _simpleProgram = 0;
- }
- // 加载和编译 shader。
- NSString *simpleVSH = [[NSBundle mainBundle] pathForResource:@"simple" ofType:@"vsh"];
- NSString *simpleFSH = [[NSBundle mainBundle] pathForResource:@"simple" ofType:@"fsh"];
- _simpleProgram = [self loadShaderWithVertexShader:simpleVSH fragmentShader:simpleFSH];
- // 链接 shader program。
- glLinkProgram(_simpleProgram);
- // 打印链接日志。
- GLint linkStatus;
- glGetProgramiv(_simpleProgram, GL_LINK_STATUS, &linkStatus);
- if (linkStatus == GL_FALSE) {
- GLint infoLength;
- glGetProgramiv(_simpleProgram, GL_INFO_LOG_LENGTH, &infoLength);
- if (infoLength > 0) {
- GLchar *infoLog = malloc(sizeof(GLchar) * infoLength);
- glGetProgramInfoLog(_simpleProgram, infoLength, NULL, infoLog);
- NSLog(@"%s", infoLog);
- free(infoLog);
- }
- }
- glUseProgram(_simpleProgram);
-
-
- // 7、根据三角形顶点信息申请顶点缓冲区对象 VBO 和拷贝顶点数据。
- // 设置三角形 3 个顶点数据,包括坐标信息和颜色信息。
- const SceneVertex vertices[] = {
- {{-0.5, 0.5, 0.0}, { 1.0, 0.0, 0.0, 1.000}}, // 左下 // 红色
- {{-0.5, -0.5, 0.0}, { 0.0, 1.0, 0.0, 1.000}}, // 右下 // 绿色
- {{ 0.5, -0.5, 0.0}, { 0.0, 0.0, 1.0, 1.000}}, // 左上 // 蓝色
- };
- // 申请并绑定 VBO。VBO 的作用是在显存中提前开辟好一块内存,用于缓存顶点数据,从而避免每次绘制时的 CPU 与 GPU 之间的内存拷贝,可以提升渲染性能。
- GLuint vertexBufferID;
- glGenBuffers(1, &vertexBufferID); // 创建 VBO。
- glBindBuffer(GL_ARRAY_BUFFER, vertexBufferID); // 绑定 VBO 到 OpenGL 渲染管线。
- // 将顶点数据 (CPU 内存) 拷贝到 VBO(GPU 显存)。
- glBufferData(GL_ARRAY_BUFFER, // 缓存块类型。
- sizeof(vertices), // 创建的缓存块尺寸。
- vertices, // 要绑定的顶点数据。
- GL_STATIC_DRAW); // 缓存块用途。
-
- // 8、绘制三角形。
- // 获取与 Shader 中对应的参数信息:
- GLuint vertexPositionLocation = glGetAttribLocation(_simpleProgram, "v_position");
- GLuint vertexColorLocation = glGetAttribLocation(_simpleProgram, "v_color");
- // 顶点位置属性。
- glEnableVertexAttribArray(vertexPositionLocation); // 启用顶点位置属性通道。
- // 关联顶点位置数据。
- glVertexAttribPointer(vertexPositionLocation, // attribute 变量的下标,范围是 [0, GL_MAX_VERTEX_ATTRIBS - 1]。
- PositionDimension, // 指顶点数组中,一个 attribute 元素变量的坐标分量是多少(如:position, 程序提供的就是 {x, y, z} 点就是 3 个坐标分量)。
- GL_FLOAT, // 数据的类型。
- GL_FALSE, // 是否进行数据类型转换。
- sizeof(SceneVertex), // 每一个数据在内存中的偏移量,如果填 0 就是每一个数据紧紧相挨着。
- (const GLvoid*) offsetof(SceneVertex, position)); // 数据的内存首地址。
- // 顶点颜色属性。
- glEnableVertexAttribArray(vertexColorLocation);
- // 关联顶点颜色数据。
- glVertexAttribPointer(vertexColorLocation,
- ColorDimension,
- GL_FLOAT,
- GL_FALSE,
- sizeof(SceneVertex),
- (const GLvoid*) offsetof(SceneVertex, color));
- // 绘制所有图元。
- glDrawArrays(GL_TRIANGLES, // 绘制的图元方式。
- 0, // 从第几个顶点下标开始绘制。
- sizeof(vertices) / sizeof(vertices[0])); // 有多少个顶点下标需要绘制。
- // 把 Renderbuffer 的内容显示到窗口系统 (CAEAGLLayer) 中。
- [_eaglContext presentRenderbuffer:GL_RENDERBUFFER];
-
-
- // 9、清理。
- glDisableVertexAttribArray(vertexColorLocation); // 关闭顶点颜色属性通道。
- glDisableVertexAttribArray(vertexPositionLocation); // 关闭顶点位置属性通道。
- glBindBuffer(GL_ARRAY_BUFFER, 0); // 解绑 VBO。
- glBindFramebuffer(GL_FRAMEBUFFER, 0); // 解绑 FBO。
- glBindRenderbuffer(GL_RENDERBUFFER, 0); // 解绑 RBO。
- }
-
- #pragma mark - Utility
- - (GLuint)loadShaderWithVertexShader:(NSString *)vert fragmentShader:(NSString *)frag {
- GLuint verShader, fragShader;
- GLuint program = glCreateProgram(); // 创建 Shader Program 对象。
-
- [self compileShader:&verShader type:GL_VERTEX_SHADER file:vert];
- [self compileShader:&fragShader type:GL_FRAGMENT_SHADER file:frag];
-
- // 装载 Vertex Shader 和 Fragment Shader。
- glAttachShader(program, verShader);
- glAttachShader(program, fragShader);
-
- glDeleteShader(verShader);
- glDeleteShader(fragShader);
-
- return program;
- }
-
- - (void)compileShader:(GLuint *)shader type:(GLenum)type file:(NSString *)file {
- NSString *content = [NSString stringWithContentsOfFile:file encoding:NSUTF8StringEncoding error:nil];
- const GLchar *source = (GLchar *) [content UTF8String];
- *shader = glCreateShader(type); // 创建一个着色器对象。
- glShaderSource(*shader, 1, &source, NULL); // 关联顶点、片元着色器的代码。
- glCompileShader(*shader); // 编译着色器代码。
-
- // 打印编译日志。
- GLint compileStatus;
- glGetShaderiv(*shader, GL_COMPILE_STATUS, &compileStatus);
- if (compileStatus == GL_FALSE) {
- GLint infoLength;
- glGetShaderiv(*shader, GL_INFO_LOG_LENGTH, &infoLength);
- if (infoLength > 0) {
- GLchar *infoLog = malloc(sizeof(GLchar) * infoLength);
- glGetShaderInfoLog(*shader, infoLength, NULL, infoLog);
- NSLog(@"%s -> %s", (type == GL_VERTEX_SHADER) ? "vertex shader" : "fragment shader", infoLog);
- free(infoLog);
- }
- }
- }
-
- #pragma mark - Override
- + (Class)layerClass {
- return [CAEAGLLayer class];
- }
-
- @end
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
上面的代码包括了这些过程:
更具体细节见上述代码及其注释。
最终我们画出的三角形如下图所示:
Android 平台自 2.0 版本之后图形系统的底层渲染均由 OpenGL ES 负责,其 EGL 架构实现如下图所示:
Android Demo 的代码由于平台 EGL 实现的原因以及对模块略作了封装,所以看起来对稍微复杂一些,包括了如下几个文件:
代码如下:
KFGLContext.java
- // ...省略 import 代码...
- public class KFGLContext {
- private Surface mSurface = null;
- private EGLDisplay mEGLDisplay = EGL_NO_DISPLAY; // 实际显示设备的抽象
- private EGLContext mEGLContext = EGL_NO_CONTEXT; // 渲染上下文
- private EGLSurface mEGLSurface = EGL_NO_SURFACE; // 存储图像的内存区域
- private EGLContext mEGLShareContext = EGL_NO_CONTEXT; // 共享渲染上下文
- private EGLConfig mEGLConfig = null; // 渲染表面的配置信息
-
- private EGLContext mLastContext = EGL_NO_CONTEXT; // 存储之前系统上下文
- private EGLDisplay mLastDisplay = EGL_NO_DISPLAY; // 存储之前系统设备
- private EGLSurface mLastSurface = EGL_NO_SURFACE; // 存储之前系统内存区域
- private boolean mIsBind = false;
-
- public KFGLContext(EGLContext shareContext) {
- mEGLShareContext = shareContext;
- // 创建 OpenGL 上下文
- _eglSetup();
- }
-
- public KFGLContext(EGLContext shareContext, Surface surface) {
- mEGLShareContext = shareContext;
- mSurface = surface;
- // 创建 OpenGL 上下文
- _eglSetup();
- }
-
- public void setSurface(Surface surface) {
- if (surface == null || surface == mSurface) {
- return;
- }
-
- // 释放渲染表面 Surface
- if (mEGLDisplay != EGL_NO_DISPLAY && mEGLSurface != EGL_NO_SURFACE) {
- eglDestroySurface(mEGLDisplay, mEGLSurface);
- }
- // 创建 Surface
- int[] surfaceAttribs = {
- EGL14.EGL_NONE
- };
- mSurface = surface;
- mEGLSurface = eglCreateWindowSurface(mEGLDisplay, mEGLConfig, mSurface, surfaceAttribs, 0);
- if (mEGLSurface == null) {
- throw new RuntimeException("surface was null");
- }
- }
-
- public EGLContext getContext() {
- return mEGLContext;
- }
-
- public Surface getSurface() {
- return mSurface;
- }
-
- public boolean swapBuffers() {
- // 将后台绘制的缓冲显示到前台
- if (mEGLDisplay != EGL_NO_DISPLAY && mEGLSurface != EGL_NO_SURFACE) {
- return eglSwapBuffers(mEGLDisplay, mEGLSurface);
- } else {
- return false;
- }
- }
-
- @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
- public void setPresentationTime(long nsecs) {
- // 设置时间戳
- if (mEGLDisplay != EGL_NO_DISPLAY && mEGLSurface != EGL_NO_SURFACE) {
- eglPresentationTimeANDROID(mEGLDisplay, mEGLSurface, nsecs);
- }
- }
-
- public void bind() {
- // 绑定当前上下文
- mLastSurface = eglGetCurrentSurface(EGL14.EGL_READ);
- mLastContext = eglGetCurrentContext();
- mLastDisplay = eglGetCurrentDisplay();
- if (!eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext)) {
- throw new RuntimeException("eglMakeCurrent failed");
- }
- mIsBind = true;
- }
-
- public void unbind() {
- if (!mIsBind) {
- return;
- }
-
- // 绑定回系统之前上下文
- if (mLastSurface != EGL_NO_SURFACE && mLastContext != EGL_NO_CONTEXT && mLastDisplay != EGL_NO_DISPLAY) {
- eglMakeCurrent(mLastDisplay, mLastSurface, mLastSurface, mLastContext);
- mLastDisplay = EGL_NO_DISPLAY;
- mLastSurface = EGL_NO_SURFACE;
- mLastContext = EGL_NO_CONTEXT;
- } else {
- if (mEGLDisplay != EGL_NO_DISPLAY) {
- eglMakeCurrent(mEGLDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
- }
- }
- mIsBind = false;
- }
-
- public void release() {
- unbind();
-
- // 释放设备、Surface
- if (mEGLDisplay != EGL_NO_DISPLAY && mEGLSurface != EGL_NO_SURFACE) {
- eglDestroySurface(mEGLDisplay, mEGLSurface);
- }
-
- if (mEGLDisplay != EGL_NO_DISPLAY && mEGLContext != EGL_NO_CONTEXT) {
- eglDestroyContext(mEGLDisplay, mEGLContext);
- }
-
- if(mEGLDisplay != EGL_NO_DISPLAY){
- eglTerminate(mEGLDisplay);
- }
-
- mSurface = null;
- mEGLShareContext = null;
-
- mEGLDisplay = null;
- mEGLContext = null;
- mEGLSurface = null;
- }
-
- private void _eglSetup() {
- // 创建设备
- mEGLDisplay = eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
- if (mEGLDisplay == EGL_NO_DISPLAY) {
- throw new RuntimeException("unable to get EGL14 display");
- }
-
- int[] version = new int[2];
- // 根据版本初始化设备
- if (!eglInitialize(mEGLDisplay, version, 0, version, 1)) {
- mEGLDisplay = null;
- throw new RuntimeException("unable to initialize EGL14");
- }
-
- // 定义 EGLConfig 属性配置,定义红、绿、蓝、透明度、深度、模板缓冲的位数
- int[] attribList = {
- EGL14.EGL_BUFFER_SIZE, 32,
- EGL14.EGL_ALPHA_SIZE, 8, // 颜色缓冲区中透明度用几位来表示
- EGL14.EGL_BLUE_SIZE, 8,
- EGL14.EGL_GREEN_SIZE, 8,
- EGL14.EGL_RED_SIZE, 8,
- EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
- EGL14.EGL_SURFACE_TYPE, EGL14.EGL_WINDOW_BIT,
- EGL14.EGL_NONE
- };
- EGLConfig[] configs = new EGLConfig[1];
- int[] numConfigs = new int[1];
- // 找到符合要求的 EGLConfig 配置
- if (!eglChooseConfig(mEGLDisplay, attribList, 0, configs, 0, configs.length,
- numConfigs, 0)) {
- throw new RuntimeException("unable to find RGB888+recordable ES2 EGL config");
- }
- mEGLConfig = configs[0];
-
- // 指定 OpenGL 使用版本
- int[] attrib_list = {
- EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
- EGL14.EGL_NONE
- };
- // 创建 GL 上下文
- mEGLContext = eglCreateContext(mEGLDisplay, mEGLConfig, mEGLShareContext != null ? mEGLShareContext : EGL_NO_CONTEXT, attrib_list, 0);
- if (mEGLContext == null) {
- throw new RuntimeException("null context");
- }
-
- // 创建 Surface 配置信息
- int[] surfaceAttribs = {
- EGL14.EGL_NONE
- };
-
- // 创建 Surface
- if (mSurface != null) {
- mEGLSurface = eglCreateWindowSurface(mEGLDisplay, mEGLConfig, mSurface,
- surfaceAttribs, 0);
- } else {
- mEGLSurface = eglCreatePbufferSurface(mEGLDisplay, configs[0], surfaceAttribs, 0);
- }
-
- if (mEGLSurface == null) {
- throw new RuntimeException("surface was null");
- }
- }
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
KFGLProgram.java
- // ...省略 import 代码...
- public class KFGLProgram {
- private static final String TAG = "KFGLProgram";
- private int mProgram = 0; // 着色器程序容器
- private int mVertexShader = 0; // 顶点着色器
- private int mFragmentShader = 0; // 片元着色器
-
- public KFGLProgram(String vertexShader, String fragmentShader) {
- _createProgram(vertexShader,fragmentShader);
- }
-
- public void release() {
- // 释放顶点、片元着色器、着色器容器
- if (mVertexShader != 0) {
- glDeleteShader(mVertexShader);
- mVertexShader = 0;
- }
-
- if (mFragmentShader != 0) {
- glDeleteShader(mFragmentShader);
- mFragmentShader = 0;
- }
-
- if (mProgram != 0) {
- glDeleteProgram(mProgram);
- mProgram = 0;
- }
- }
-
- public void use() {
- // 使用当前的着色器
- if (mProgram != 0) {
- glUseProgram(mProgram);
- }
- }
-
- public int getUniformLocation(String uniformName) {
- // 获取着色器 uniform 对应下标
- return glGetUniformLocation(mProgram, uniformName);
- }
-
- public int getAttribLocation(String uniformName) {
- // 获取着色器 attribute 变量对应下标
- return glGetAttribLocation(mProgram, uniformName);
- }
-
- private void _createProgram(String vertexSource, String fragmentSource) {
- // 创建着色器容器
- // 创建顶点、片元着色器
- mVertexShader = _loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
- mFragmentShader = _loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
-
- //
- if(mVertexShader != 0 && mFragmentShader != 0){
- // 创建一个空的着色器容器
- mProgram = GLES20.glCreateProgram();
- // 将顶点、片元着色器添加至着色器容器
- glAttachShader(mProgram, mVertexShader);
- glAttachShader(mProgram, mFragmentShader);
-
- // 链接着色器容器
- glLinkProgram(mProgram);
- int[] linkStatus = new int[1];
- glGetProgramiv(mProgram, GLES20.GL_LINK_STATUS, linkStatus, 0);
- // 获取链接状态
- if (linkStatus[0] != GLES20.GL_TRUE) {
- Log.e(TAG, "Could not link program:");
- Log.e(TAG, glGetProgramInfoLog(mProgram));
- glDeleteProgram(mProgram);
- mProgram = 0;
- }
- }
- }
-
- private int _loadShader(int shaderType, String source) {
- // 根据类型创建顶点、片元着色器
- int shader = glCreateShader(shaderType);
- // 设置着色器中的源代码
- glShaderSource(shader, source);
- // 编译着色器
- glCompileShader(shader);
-
- int[] compiled = new int[1];
- glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
- // 获取编译后状态
- if (compiled[0] != GLES20.GL_TRUE) {
- Log.e(TAG, "Could not compile shader(TYPE=" + shaderType + "):");
- Log.e(TAG, glGetShaderInfoLog(shader));
- glDeleteShader(shader);
- shader = 0;
- }
-
- return shader;
- }
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
KFSurfaceView.java
- // ...省略 import 代码...
- public class KFSurfaceView extends SurfaceView implements SurfaceHolder.Callback {
- private KFRenderListener mListener = null; // 回调
- private SurfaceHolder mHolder = null; // Surface 的抽象接口
-
- public KFSurfaceView(Context context, KFRenderListener listener) {
- super(context);
- mListener = listener;
- getHolder().addCallback(this);
- }
-
- @Override
- public void surfaceCreated(@NonNull SurfaceHolder surfaceHolder) {
- // Surface 创建
- mHolder = surfaceHolder;
- // 根据 SurfaceHolder 创建 Surface
- if(mListener != null){
- mListener.surfaceCreate(surfaceHolder.getSurface());
- }
- }
-
- @Override
- public void surfaceChanged(@NonNull SurfaceHolder surfaceHolder, int format, int width, int height) {
- // Surface 分辨率变更
- if(mListener != null){
- mListener.surfaceChanged(surfaceHolder.getSurface(),width,height);
- }
- }
-
- @Override
- public void surfaceDestroyed(@NonNull SurfaceHolder surfaceHolder) {
- // Surface 销毁
- if (mListener != null) {
- mListener.surfaceDestroy(surfaceHolder.getSurface());
- }
- }
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
KFTextureView.java
- // ...省略 import 代码...
- public class KFTextureView extends TextureView implements TextureView.SurfaceTextureListener{
- private KFRenderListener mListener = null; // 回调
- private Surface mSurface = null; // 渲染缓存
- private SurfaceTexture mSurfaceTexture = null; // 纹理缓存
-
- public KFTextureView(Context context, KFRenderListener listener) {
- super(context);
- this.setSurfaceTextureListener(this);
- mListener = listener;
- }
-
- @Override
- public void onSurfaceTextureAvailable(@NonNull SurfaceTexture surfaceTexture, int width, int height) {
- // 纹理缓存创建
- mSurfaceTexture = surfaceTexture;
- // 根据 SurfaceTexture 创建 Surface
- mSurface = new Surface(surfaceTexture);
- if (mListener != null) {
- // 创建时候回调一次分辨率变更,对应 SurfaceView 接口
- mListener.surfaceCreate(mSurface);
- mListener.surfaceChanged(mSurface,width,height);
- }
- }
-
- @Override
- public void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surfaceTexture, int width, int height) {
- // 纹理缓存变更分辨率
- if (mListener != null) {
- mListener.surfaceChanged(mSurface,width,height);
- }
- }
-
- @Override
- public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surfaceTexture) {
-
- }
-
- @Override
- public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surfaceTexture) {
- // 纹理缓存销毁
- if (mListener != null) {
- mListener.surfaceDestroy(mSurface);
- }
- if (mSurface != null) {
- mSurface.release();
- mSurface = null;
- }
- return false;
- }
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
KFRenderView.java
- // ...省略 import 代码...
- public class KFRenderView extends ViewGroup {
- // 顶点着色器
- public static String customVertexShader =
- "attribute vec4 v_position;\n" +
- "attribute vec4 v_color;\n" +
- "varying mediump vec4 f_color;\n" +
- "void main() {\n" +
- " f_color = v_color;\n" +
- " gl_Position = v_position;\n" +
- "}\n";
-
- // 片元着色器
- public static String customFragmentShader =
- "varying mediump vec4 f_color;\n" +
- "void main() {\n" +
- " gl_FragColor = f_color;\n" +
- "}\n";
-
- private KFGLContext mEGLContext = null; // OpenGL 上下文
- private EGLContext mShareContext = null; // 共享上下文
- private View mRenderView = null; // 渲染视图基类
- private int mSurfaceWidth = 0; // 渲染缓存宽
- private int mSurfaceHeight = 0; // 渲染缓存高
- private boolean mSurfaceChanged = false; // 渲染缓存是否变更
- private KFGLProgram mProgram;
- private FloatBuffer mVerticesBuffer = null; // 顶点 buffer
- private int mPositionAttribute = -1; // 顶点坐标
- private int mColorAttribute = -1; // 顶点颜色
-
- public KFRenderView(Context context, EGLContext eglContext) {
- super(context);
- mShareContext = eglContext; // 共享上下文
-
- // 1、选择实际的渲染视图
- boolean isSurfaceView = false; // TextureView 与 SurfaceView 开关
- if (isSurfaceView) {
- mRenderView = new KFSurfaceView(context, mListener);
- } else {
- mRenderView = new KFTextureView(context, mListener);
- }
-
- this.addView(mRenderView); // 添加视图到父视图
- }
-
- public void release() {
- // 释放 OpenGL 上下文、特效
- if (mEGLContext != null) {
- mEGLContext.bind();
- mEGLContext.unbind();
-
- mEGLContext.release();
- mEGLContext = null;
- }
- }
-
- public void render() {
- mProgram.use();
-
- // 设置帧缓存背景色
- glClearColor(0.5f,0.5f,0.5f,1);
- // 清空帧缓存颜色
- glClear(GLES20.GL_COLOR_BUFFER_BIT);
- // 设置渲染窗口区域
- GLES20.glViewport(0, 0, mSurfaceWidth, mSurfaceHeight);
-
- // 启用顶点着色器顶点坐标属性
- glEnableVertexAttribArray(mPositionAttribute);
- mVerticesBuffer.position(0); // 定位到第一个位置分量
- glVertexAttribPointer(
- mPositionAttribute,
- 3, // x, y, z 有 3 个分量
- GLES20.GL_FLOAT,
- false,
- 7 * 4, // 每个顶点有 xyzrgba 7 个分量,每个分量是 4 字节,所以步进为 7 * 4 字节
- mVerticesBuffer);
-
- // 启用顶点着色器顶点颜色属性
- glEnableVertexAttribArray(mColorAttribute);
- mVerticesBuffer.position(3); // 定位到第一个颜色分量
- glVertexAttribPointer(
- mColorAttribute,
- 4, // r, g, b, a 有 4 个分量
- GLES20.GL_FLOAT,
- false,
- 7 * 4, // 每个顶点有 xyzrgba 7 个分量,每个分量是 4 字节,所以步进为 7 * 4 字节
- mVerticesBuffer);
-
- // 绘制三角形
- glDrawArrays(GLES20.GL_TRIANGLES, 0, 3);
-
- // 关闭顶点着色器顶点坐标属性
- glDisableVertexAttribArray(mPositionAttribute);
-
- // 关闭顶点着色器顶点颜色属性
- glDisableVertexAttribArray(mColorAttribute);
-
- mEGLContext.swapBuffers();
- }
-
- private KFRenderListener mListener = new KFRenderListener() {
- @Override
- // 渲染缓存创建
- public void surfaceCreate(@NonNull Surface surface) {
- // 2、创建 OpenGL 上下文
- mEGLContext = new KFGLContext(mShareContext, surface);
- mEGLContext.bind();
- // 3、初始化 GL 相关环境:加载和编译 shader、链接到着色器程序、设置顶点数据
- _setupGL();
- mEGLContext.unbind();
- }
-
- @Override
- // 渲染缓存变更
- public void surfaceChanged(@NonNull Surface surface, int width, int height) {
- mSurfaceWidth = width;
- mSurfaceHeight = height;
- mSurfaceChanged = true;
- mEGLContext.bind();
- // 4、设置 OpenGL 上下文 Surface
- mEGLContext.setSurface(surface);
- // 5、绘制三角形
- render();
- mEGLContext.unbind();
- }
-
- @Override
- public void surfaceDestroy(@NonNull Surface surface) {
-
- }
- };
-
- private void _setupGL() {
- // 加载和编译 shader,并链接到着色器程序
- mProgram = new KFGLProgram(customVertexShader, customFragmentShader);
-
- // 获取与 shader 中对应的属性信息
- mPositionAttribute = mProgram.getAttribLocation("v_position");
- mColorAttribute = mProgram.getAttribLocation("v_color");
-
- // 3 个顶点,每个顶点有 7 个分量:x, y, z, r, g, b, a
- final float vertices[] = {
- -0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, // 左下 // 红色
- -0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, // 右下 // 绿色
- 0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, // 左上 // 蓝色
- };
- ByteBuffer verticesByteBuffer = ByteBuffer.allocateDirect(4 * vertices.length);
- verticesByteBuffer.order(ByteOrder.nativeOrder());
- mVerticesBuffer = verticesByteBuffer.asFloatBuffer();
- mVerticesBuffer.put(vertices);
- mVerticesBuffer.position(0);
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- // 视图变更 Size
- this.mRenderView.layout(left, top, right, bottom);
- }
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
KFRenderListener.java
- // ...省略 import 代码...
- public interface KFRenderListener {
- void surfaceCreate(@NonNull Surface surface); // 渲染缓存创建
- void surfaceChanged(@NonNull Surface surface, int width, int height); // 渲染缓存变更分辨率
- void surfaceDestroy(@NonNull Surface surface); // 渲染缓存销毁
- }
其中绘制三角形的代码实现在 KFRenderView.java 中,包括这些过程:
具体细节见上述代码及其注释。
最终我们画出的三角形如下图所示:
本文福利, 免费领取C++音视频学习资料包、技术视频/代码,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,编解码,推拉流,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。