当前位置: 首页 > 编程日记 > 正文

源码分析-GLSurfaceView的内部实现


GLSurfaceView类是继承自SurfaceView的,并且实现了SurfaceHolder.Callback2接口。

GLSurfaceView内部管理着一个surface,专门负责OpenGL渲染。GLSurfaceView内部通过GLThread和EGLHelper

为我们完成了EGL环境渲染和渲染线程的创建及管理,使我们只需要在外部实现渲染器Renderer

即可使用OpenGL ES进行绘图。所以,了解GLSurfaceView的内部逻辑对于我们使用OpenGL ES来绘图还是很有必要的。

GLSurfaceView的初始化

首先,我们在最外使用GLSurfaceView的时候,实例化GLSurfaceView时其会对自身进行初始化:

1
2
3
4
private void init() {
SurfaceHolder holder = getHolder();
holder.addCallback(this);
}

可以看到这里主要对自身的Holder设置了Callback2接口,然后在自身内部实现了Callback2的四个回调方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public void surfaceCreated(SurfaceHolder holder) {
mGLThread.surfaceCreated();
}
public void surfaceDestroyed(SurfaceHolder holder) {
mGLThread.surfaceDestroyed();
}
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
mGLThread.onWindowResize(w,h);
}
public void surfaceRedrawNeeded(SurfaceHolder holder) {
if(mGLThread != null) {
mGLThread.requestRenderAndWait();
}
}

可以看到四个回调方法最终都共同指向了GLThread,其实这里的GLThread是GLSurfaceView内部的绘制线程,

使绘制工作可以独立于主线程(GLThread承担了SurfaceView中大部分的工作,具体的后面再细说)。

至此,GLSurfaceView的初始化工作完成,但到这里渲染环境的初始化工作却还没有开始。

当实例化GLSurfaceView后,接着都会进行两个步骤,那就是设置OpenGL的版本号和渲染器:

1
2
this.setEGLContextClientVersion(2); //设置OpenGL的版本号2.0
setRenderer(new MyRenderer()); //设置OpenGL的渲染器

这里重点要看setRenderer()这个方法,通过这个方法我们不仅为GLSurfaceView设置了renderer,

还会对EGL环境进行初步的设置工作,最后还有调起GLThread,开启绘制线程。所以在GLSurfaceView的整个生命周期中,

setRenderer()只能被调用一次。源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void setRenderer(Renderer renderer) {
checkRenderThreadState();
if(mEGLConfigChooser == null) {
mEGLConfigChooser = new SimpleEGLConfigChooser(true);
}
if(mEGLContextFactory == null) {
mEGLContextFactory = new DefaultContextFactory();
}
if(mEGLWindowSurfaceFactory == null) {
mEGLWindowSurfaceFactory = new DefaultWindowSurfaceFactory();
}
mRenderer = renderer;
mGLThread = new GLThread(mThisWeakRef);
mGLThread.start();
}

设置渲染器时,首先会对渲染线程的状态进行检查,如果GLThread已经存在即setRenderer已经被调用过,

则抛出异常,这也是为什么setRenderer()只能在GLSurfaceView的整个生命周期中调用一次。

然后会对mEGLConfigChooser、mEGLContextFactory、mEGLWindowSurfaceFactory进行实例化,

再实例化GLThread并执行该渲染线程,其中传入的mThisWeakRef是当前GLSurfaceView的弱引用。

GLThread——GLSurfaceView内部的渲染线程

GLThread是一个继承Thread的类,主要的运行代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
while(true) {
synchronized(sGLThreadManager) {
while(true) {
......
mEglHelper.start();
......
sGLThreadManager.wait();
}
}
......
mEglHelper.createSurface();
......
gl = (GL10) mEglHelper.createGL();
......
mRenderer.onSurfaceCreated(gl, mEglHelper.getEglConfig());
......
mRenderer.onSurfaceChanged(gl, w, h);
......
mRenderer.onDrawFrame(gl);
}

其中被synchronized(sGLThreadManager)同步的代码块是用于线程之间通信的,外部线程可以停止和恢复这个线程。

这部分同步代码块的主要职责是创建EGL环境,GLThread对EGL的所有操作都是通过EglHelper来实现的。

EglHelper——创建EGL环境的帮助类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
private static class EglHelper {
/**
* Initialize EGL for a given configuration spec
*/
public void start() {
......
mEgl = (EGL10) EGLContext.getEGL();
mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
......
mEgl.eglInitialize(mEglDisplay, version);
......
mEglConfig = view.mEGLConfigChooser.chooseConfig(mEgl, mEglDisplay);
mEglContext = view.mEGLContextFactory.createContext(mEgl, mEglDisplay, mEglConfig);
}
/**
* Create an egl surface for the current SurfaceHolder surface. If a surfce
* already exists, destroy it before creating the new surface.
*/
public boolean createSurface() {
......
/*
* The window size has changed, so we need to create a new surface.
*/
destroySurfaceImp();
......
/*
* Create an EGL surface we can render into.
*/
mEglSurface = view.mEGLWindowSurfaceFactory.createWindowSurface(mEgl, mEglDisplay,
mEglConfig, view.getHolder());
......
/*
* Before we can issue GL commands, we need to make sure the context
* is current and bound to a surface.
*/
mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext);
......
}
/**
* Create a GL object for the current EGL context
*/
GL createGL() {
GL gl = mEglContext.getEGL();
GLSurfaceView view = mGLSurfaceViewWeakRef.get();
......
gl = view.mGLWrapper.wrap(gl);
......
gl = GLDebugHelper.wrap(gl, configFlags, log);
return gl;
}
......
}

所以整体看来,就是GLThread在其同步代码块中通过mEglHelper.start()对EGL环境进行初始化,

而后在同步块外部先调用mEglHelper.createSurface()为当前的Surface创建一个EGL的surface,

再通过mEglHelper.createGL()创建一个GL对象,最后就将GL对象传递给renderer的三个回调方法

onSurfaceCreated()、onSurfaceChanged()、onDrawFrame()。所以我们在最外面只需要在这三个

回调方法中实现我们真正要绘制的东西即可。

再说GLThread

我们再来看看GLThread内部其他的一些方法,在最开始的时候我们说到给holder传入一个回调接口,

而这个回调接口的四个方法的实现最终都指向了GLThread的内部方法,那么我们先来看看这四个方法

在GLThread内部的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
// GLThread的内部方法
public void surfaceCreated() {
synchronized(sGLThreadManager) {
mHasSurface = true;
mFinishedCreatingEglSurface = false;
sGLThreadManager.notifyAll();
while(mWaitingForSurface && !mFinishedCreatingEglSurface && !mExited) {
try {
sGLThreadManager.wait();
} catch (InterruptedException e) {
Thread.currentThread().interrput();
}
}
}
}
public void surfaceDestroyed() {
synchronized(sGLThreadManager) {
mHasSurface = true;
sGLThreadManager.notifyAll();
while(!mWaitingForSurface && !mExited) {
try {
sGLThreadManager.wait();
} catch (InterruptedException e) {
Thread.currentThread().interrput();
}
}
}
}
public void onWindowResize(int w, int h) {
synchronized(sGLThreadManager) {
mWidth = w;
mHeight = h;
mSizeChanged = true;
mRequestRender = true;
mRenderComplete = false;
if(Thread.currentThread() == this) {
return;
}
sGLThreadManager.notifyAll();
while(!mExited && !mPaused && !mRenderComplete && ableToDraw) {
try {
sGLThreadManager.wait();
} catch (InterruptedException e) {
Thread.currentThread().interrput();
}
}
}
}
public void requestRenderAndWait() {
synchronized(sGLThreadManager) {
if(Thread.currentThread() == this) {
return;
}
mWantRenderNotification = true;
mRequestRender = true;
mRenderComplete = false;
sGLThreadManager.notifyAll();
while(!mExited && !mPaused && !RenderComplete && ableToDraw()) {
try {
sGLThreadManager.wait();
} catch (InterruptedException e) {
Thread.currentThread().interrput();
}
}
}
}

这些供外部调用的方法其最终都只是改变GLThread的一些内部标志,然后通过sGLThreadManager.notifyAll

唤醒渲染线程,GLThread内部即通过判断这些标志位去执行对应的参数设置操作,这些参数设置操作都在

synchronized(sGLTHreadManager)同步的循环体中进行,当最后已经准备好绘制工作,则跳出内循环体,

在外循环体中调用renderer的回调方法onDrawFrame去真正实现绘制,核心代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
while(true) {
synchronized(sGLThreadManager) {
while(true) {
......
mEglHelper.start();
......
// 如果已经准备好绘制环境
if(readyToDraw()) {
// 再一次检查EglContext和EglSurface的状态,满足则跳出同步代码块的循环体
// If we don't have an EGL context, try to acquire one
if(!mHaveEglContext) {
......
if(sGLThreadManager.tryAcquireEglContextLocked(this)) {
mEglHelper.start();
}
}
if(mHaveEglSurface) {
......
break; // 跳出同步代码块的循环体
}
}
......
sGLThreadManager.wait();
}
}
......
// 调用Renderer进行绘制
mEglHelper.createSurface();
gl = (GL10) mEglHelper.createGL();
mRenderer.onSurfaceCreated(gl, mEglHelper.getEglConfig());
mRenderer.onSurfaceChanged(gl, w, h);
mRenderer.onDrawFrame(gl);
}

最后终于回到了我们的渲染器Renderer,然后,我们只需要在外部实现Renderer的三个回调方法便

可使用OpenGL ES来绘图了。当然,在我们弄懂了GLSurfaceView的原理后自己动手封装一个也是没有

问题的。不过这里其实没有说得很明白的还有EGL这部分,后面有时间将补上。


相关文章:

【POJ/算法】 3259 Wormholes(Bellman-Ford算法, SPFA ,FLoyd算法)

Bellman-Ford算法 Bellman-Ford算法的优点是可以发现负圈,缺点是时间复杂度比Dijkstra算法高。而SPFA算法是使用队列优化的Bellman-Ford版本,其在时间复杂度和编程难度上都比其他算法有优势。 Bellman-Ford算法流程分为三个阶段: 第一步&am…

进程控制概念简介 多线程上篇(三)

进程控制 进程的基本数据信息是操作系统控制管理进程的数据集合,这些信息就是用来控制进程的,此处我们说的进程控制就是进程的管理。比如进程有状态,那么进程的创建、终止,状态的切换,这都不是进程自主进行的&#xff…

Android OpenGL使用GLSurfaceView预览视频

Android OpenGL使用GLSurfaceView预览视频第一章 相关知识介绍在介绍具体的功能之前,先对一些主要的类和方法进行一些介绍,这样可以更好的理解整个程序1.1 GLSurfaceView在谷歌的官方文档中是这样解释GLSurfaceView的:An implementation of S…

【Android 基础】Animation 动画介绍和实现

转载自:http://www.cnblogs.com/yc-755909659/p/4290114.html1.Animation 动画类型Android的animation由四种类型组成:XML中alph渐变透明度动画效果scale渐变尺寸伸缩动画效果translate画面转换位置移动动画效果rotate画面转移旋转动画效果JavaCode中Alp…

【Codeforces】1111B - Average Superhero Gang Power

http://codeforces.com/problemset/problem/1111/B n 表示要输入的数据的个数 k 最每一个数据最多可以进行多少次操作 m 一共可以进行多少次操作 一次操作:删除这个数,或者给这个数加1 如果n为1的话,那么只要找出m和k的最小值加到那个数…

刷前端面经笔记(七)

1.描述一下渐进增强和优雅降级 优雅降级(graceful degradation):一开始就构建站点的完整功能,然后针对浏览器测试和修复。渐进增强(progressive enhancement):一开始只构建站点的最少特性,然后不断针对各浏览器追加功能。 2.为什么…

AR资料与连接梳理

AR引擎相关技术 ------------------------------ ARcore:https://developers.google.cn/ar/discover/ ARkit:https://developer.apple.com/arkit/ 以上重点关注,比较新有一些新的功能大家可以自行体验。 ARToolkithttp://www.artoolkit.orght…

Queues 队列

1. Definiation What is a queue? A queue is a list. With a queue, inseration is done at one end (known as rear) whereas deletion is performed at the other end (known as front). 2. Operations 指针对列 无法自定义队长 // array queue #include<iostream> u…

【HDU】1005 Number Sequence (有点可爱)

http://acm.hdu.edu.cn/showproblem.php?pid1005 A number sequence is defined as follows: f(1) 1, f(2) 1, f(n) (A * f(n - 1) B * f(n - 2)) mod 7. Given A, B, and n, you are to calculate the value of f(n). 直接递归求解f(n)的话&#xff0c;会MLE 在计算…

CNCF案例研究:奇虎360

公司&#xff1a;奇虎360地点&#xff1a;中国北京行业&#xff1a;计算机软件 挑战 中国软件巨头奇虎360科技的搜索部门&#xff0c;so.com是中国第二大搜索引擎&#xff0c;市场份额超过35&#xff05;。该公司一直在使用传统的手动操作来部署环境&#xff0c;随着项目数量的…

C#代码实现对Windows凭据的管理

今天有个任务&#xff0c;那就是使用C#代码实现对windows凭据管理的操作。例如&#xff1a;向windows凭据管理中添加凭据、删除凭据以及查询凭据等功能。于是乎&#xff0c;就开始在网上查找。经过漫长的查询路&#xff0c;终于在一片英文博客中找到了相关代码。经过实验&#…

Android:JNI 与 NDK到底是什么

前言 在Android开发中&#xff0c;使用 NDK开发的需求正逐渐增大但很多人却搞不懂 JNI 与 NDK 到底是怎么回事今天&#xff0c;我将先介绍JNI 与 NDK & 之间的区别&#xff0c;手把手进行 NDK的使用教学&#xff0c;希望你们会喜欢 目录 1. JNI介绍 1.1 简介 定义&…

【ACM】LightOJ - 1008 Fibsieve`s Fantabulous Birthday (找规律,找...)

https://vjudge.net/problem/LightOJ-1008 题目很好理解&#xff0c;第一行表示测试样例的个数&#xff0c;接下来输入一个大于等于1的数&#xff0c;按照格式输出这个数的坐标 蓝色的是 奇数的平方&#xff1b; 红色的是 偶数的平方&#xff1b; 黄色的是对角线&#xff1a…

Computed property XXX was assigned to but it has no setter

报错视图&#xff1a; 原因&#xff1a; 组件中v-model“XXX”&#xff0c;而XXX是vuex state中的某个变量vuex中是单项流&#xff0c;v-model是vue中的双向绑定&#xff0c;但是在computed中只通过get获取参数值&#xff0c;没有set无法改变参数值解决方法&#xff1a; 1.在co…

OpenGL 矩阵变换

origin refer :http://www.songho.ca/opengl/gl_transform.html#modelviewOpenGL 矩阵变换Related Topics: OpenGL Pipeline, OpenGL Projection Matrix, OpenGL Matrix Class Download: matrixModelView.zip, matrixProjection.zipOverviewOpenGL Transform MatrixExample: GL…

2016.8.11 DataTable合并及排除重复方法

合并&#xff1a; DataTable prosxxx; DataTable pstaryyy; //将两张DataTable合成一张 foreach (DataRow dr in pstar.Rows) { pros.ImportRow(dr); } DataTable设置主键&#xff0c;并判断重复 DataTable allpros xxx; 单列设为主键&#xff1a; //设置第某列为主键 allpros.…

【ACM】LightOJ - 1010 Knights in Chessboard(不是搜索...)

https://vjudge.net/problem/LightOJ-1010 给定一个mn的棋盘&#xff0c;你想把棋子放在哪里。你必须找到棋盘上最多可以放置的骑士数量&#xff0c;这样就不会有两个骑士互相攻击。不熟悉棋手的注意&#xff0c;棋手可以在棋盘上攻击8个位置&#xff0c;如下图所示。 不论输入…

webpack-dev-server 和webapck --watch的区别

webpack-dev-server 和webapck --watch 都可以监测到代码变化 &#xff0c; 区别是&#xff1a;webpack-der-server 监测到代码变化后&#xff0c;浏览器可以看到及时更新的效果&#xff0c;但是并没有自动打包修改的代码&#xff1b; webpack --watch 在监测到代码变化后自动打…

Android 应用进行性能分析/APP/系统性能分析

如何对 Android 应用进行性能分析记录一下自己在使用DDMS的过程&#xff1a;开启AS&#xff0c;打开并运行项目 找到TOOL/选择Android Device Monitor一款 App 流畅与否安装在自己的真机里&#xff0c;玩几天就能有个大概的感性认识。不过通过专业的分析工具可以使我们更好的分…

公钥与私钥,HTTPS详解

1.公钥与私钥原理1)鲍勃有两把钥匙&#xff0c;一把是公钥&#xff0c;另一把是私钥2)鲍勃把公钥送给他的朋友们----帕蒂、道格、苏珊----每人一把。3)苏珊要给鲍勃写一封保密的信。她写完后用鲍勃的公钥加密&#xff0c;就可以达到保密的效果。4)鲍勃收信后&#xff0c;用私钥…

【ACM】杭电OJ 4704 Sum (隔板原理+组合数求和公式+费马小定理+快速幂)

http://acm.hdu.edu.cn/showproblem.php?pid4704 1.隔板原理 1~N有N个元素&#xff0c;每个元素代表一个1.分成K个数&#xff0c;即在(N-1)个空挡里放置&#xff08;K-1&#xff09;块隔板 即求组合数C(N-1,0)C(N-1,1)...C(N-1&#xff0c;N-1) 2.组合数求和公式 C(n,0)C(…

Vue 中 CSS 动画原理

下面这段代码&#xff0c;是点击按钮实现hello world显示与隐藏 <div id"root"><div v-if"show">hello world</div><button click"handleClick">按钮</button> </div> let vm new Vue({el: #root,data: {s…

【ACM】UVA - 340 Master-Mind Hints(一定要好好学英语...)

https://vjudge.net/problem/UVA-340 N 表示 密码的个数 第一行是正确的密码 下面的行直到N个0之前&#xff0c;都是猜测的序列&#xff0c;输出的括号&#xff08;A&#xff0c;B&#xff09; A表示对应位置与密码相符合的个数&#xff0c;B表示出现在序列中的数字但是位…

SLAM的前世今生

SLAM的前世 从研究生开始切入到视觉SLAM领域&#xff0c;应用背景为AR中的视觉导航与定位。 定位、定向、测速、授时是人们惆怅千年都未能完全解决的问题&#xff0c;最早的时候&#xff0c;古人只能靠夜观天象和司南来做简单的定向。直至元代&#xff0c;出于对定位的需求&a…

No resource found that matches the given name '@style/Theme.AppCompat.Light'

为什么80%的码农都做不了架构师&#xff1f;>>> Android导入项目时出现此问题的解决办法&#xff1a; 1.查看是否存在此目录&#xff08;D:\android-sdk\extras\android\support\v7\appcompat&#xff09;&#xff0c;若没有此目录&#xff0c;在项目右键Android T…

极限编程 (Extreme Programming) - 迭代计划 (Iterative Planning)

(Source: XP - Iteration Planning) 在每次迭代开始时调用迭代计划会议&#xff0c;以生成该迭代的编程任务计划。每次迭代为1到3周。 客户从发布计划中按照对客户最有价值的顺序选择用户故事进行此次迭代。还选择了要修复的失败验收测试。客户选择的用户故事的估计总计达到上次…

VINS-mono详细解读与实现

VINS-mono详细解读 VINS-mono详细解读 前言 Vins-mono是香港科技大学开源的一个VIO算法&#xff0c;https://github.com/HKUST-Aerial-Robotics/VINS-Mono&#xff0c;是用紧耦合方法实现的&#xff0c;通过单目IMU恢复出尺度&#xff0c;效果非常棒。 感谢他们开源&#x…

mysql+mycat搭建稳定高可用集群,负载均衡,主备复制,读写分离

数据库性能优化普遍采用集群方式&#xff0c;oracle集群软硬件投入昂贵&#xff0c;今天花了一天时间搭建基于mysql的集群环境。 主要思路 简单说&#xff0c;实现mysql主备复制-->利用mycat实现负载均衡。 比较了常用的读写分离方式&#xff0c;推荐mycat&#xff0c;社区活…

【UVA/Codeforces】1584 Circular Sequence / 792B Counting-out Rhyme(就是一个圈儿...)

https://vjudge.net/problem/UVA-1584 1584 Circular Sequence 输入一个字符串&#xff0c;可以以字符串中任意一个字母作为起始&#xff0c;输出字典序最小的那个字符串 两种方法&#xff0c;一种是设两个标记 【样例输入】CGAGTCAGCT 【样例输出】AGCTCGAGTC 一开始 an…

一个free异常引发的异常

有同事反馈说自己的线程不工作&#xff0c;查看堆栈发现其打印如下&#xff1a; #0 0x00007f291f72e7be in __lll_lock_wait_private () from /lib64/libc.so.6 #1 0x00007f291f6c2e4e in _L_lock_9925 () from /lib64/libc.so.6 #2 0x00007f291f6c1101 in free () from /li…