`
mmdev
  • 浏览: 12944895 次
  • 性别: Icon_minigender_1
  • 来自: 大连
文章分类
社区版块
存档分类
最新评论

在BlackBerry上使用OpenGL绘图(三):OpenGL样例结构分析

 
阅读更多

3 OpenGL样例结构分析

运行OpenGL样例之后,需要进一步分析样例中代码,才能更好地理解样例的工作机制。在开发环境中打开包“com.rim.samples.device.opengldemo”,可以看到其中有五个类,分别是Cube、CubeRenderer、OpenGLDemo、OpenGLScreen和Renderer。另外,在目录“res\img”中还有两个图像文件,BlackBerry.png和icon_jde.png,分别用于纹理帖图和应用图标。

图像文件的作用比较简单,BlackBerry.png作为纹理帖图使用,旋转立方体表面的BlackBerry图标就是通过对BlackBerry.png文件的处理实现的;icon_jde.png用作应用的图标,使应用在“Downloads”目录中显示时可以显示成一个灰色的窗口图标。

开发人员需要详细了解的是Cube、CubeRenderer、OpenGLDemo、OpenGLScreen和Renderer这五个类,和它们之间的关系。其中Renderer是定义的接口,CubeRenderer是Renderer接口的具体实现,所以Renderer接口基本不需要关注。

对于Cube、CubeRenderer、OpenGLDemo、OpenGLScreen四个类。首先可以明确的是OpenGLDemo是程序的入口,OpenGLScreen是程序进入后所显示的屏幕,这种结构对于BlackBerry开发人员来讲是非常熟悉的。然后Cube是3D模型的定义,CubeRenderer负责从Cube中获取数据,然后将数据进行转换和显示。

然而,在OpenGLDemo中,程序并没有完全按照常见的BlackBerry方式运行,对于刚接触的开人员而言可能会有一些困扰。为了更好地理解OpenGLDemo程序的运行过程,我们通过一个时序示意图来说明OpenGLDemo的初始化过程。如图18-4:

图18-4 OpenGLDemo应用的初始化过程

OpenGLDemo应用程序的初始化过程如下:

1. OpenGLDemo应用程序以OpenGLDemo类作为入口,在该类的静态main方法里新建了一个OpenGLDemo实例。

2. 在OpengLDemo的构造函数里首先判断当前设备是否支持OpenGL,如果不支持OpenGL则弹出对话框提示用户并退出应用程序,如果支持OpenGL才继续执行初始化过程。

3. 在设备支持OpenGL情况下创建一个OpenGLScreen实例。

4. 在创建OpenGLScreen实例的过程中创建了一个CubeRender实例。

5. 将第3步创建的OpenGLScreen实例显示出来。

6. 调用OpenGLDemo的EnterEventDispatcher()方法进行事件循环。

OpenGLDemo应用程序初始化的主要代码在OpenGLDemo类的构造函数OpenGLDemo()中,具体代码见代码清单18-1。

代码清单18-1

public OpenGLDemo()

{

// 检查当前设备是否支持OpenGL

if(GLUtils.isSupported())

{ // 如果支持OpenGL则新建OpenGLScreen实例并显示该实例

OpenGLScreen screen = new OpenGLScreen(new CubeRenderer());

pushScreen(screen);

}

else

{ // 如果不支持OpenGL则弹出对话框提示用户并退出应用

UiApplication.getUiApplication().invokeLater(new Runnable()

{

public void run()

{

Dialog.alert("This device does not support OpenGL, exiting OpenGL Demo application...");

System.exit(0);

}

});

}

}

从初始化过程上看,这个应用程序的基本运行框架和普通BlackBerry程序没有太大差别,其中有一个细节上的差别是在OpenGLScreen初始化过程中没有在Screen中加入子元素。OpenGLScreen的构造函数的具体代码如代码清单18-2,在这个函数中除了调用父类的构造函数和保存CuberRender实例以外并没有做其它工作。

但是这个细小的差别所带来的影响却很大,熟悉BlackBerry程序运行机制的开发人员会意识到,这个程序运行后屏幕上不会显示任何内容。一般的BlackBerry程序会在Screen的构造函数里加入字段、按钮、菜单等元素,程序通过字段显示内容,用户通过按钮或者是菜单与程序交互。那么,在OpenGLDemo应用程序中,OpenGLScreen显示出来之后不显示任何内容,样例中显示的立方体是如何出现的呢?要回答这个问题需要进一步分析OpenGLScreen的其它方法。

代码清单18-2

public OpenGLScreen(Renderer renderer)

{

super(FullScreen.DEFAULT_MENU | FullScreen.DEFAULT_CLOSE);

_renderer = renderer;

}

为了在合适的时机启动立方体的显示线程,样例开发人员实现了OpenGLScreen的onVisibilityChange()方法,该方法在程序的可视情况发生变化时会被系统调用,如点击图标打开程序时系统会调用这个方法,程序被置于后台时系统也会调用这个方法。该方法首先判断程序是进入可视状态还是进入不可视状态。如果进入可视状态的话进一步判断后台线程是否已经运行,确定后台线程没有运行的话会调用start()方法启动后台线程,如果后台线程之前曾经启动,则调用resume()方法让后台线程从暂停状态进入重新运行状态。如果程序进入不可视状态,则调用pause()方法让后台线程进行暂停状态。onVisibilityChange()方法的具体代码如代码清单18-3。

代码清单18-3

protected void onVisibilityChange(boolean visible)

{

if (visible)

{

if (!_running)

{ start(); }

else

{ resume(); }

}

else

{ pause(true); }

}

onVisibilityChange()方法中最关键的就是调用start()方法, start()方法完成了OpenGL环境的初始化工作,创建一个后台线程并进入该线程的循环状态,开始不断地对3D模型进行更新和显示。调用start()方法的主要过程如图18-5。

图18-5 调用start()方法启动后台线程

Start()方法调用的具体过程如下:

1. 当程序的可视状态发生变化时系统调用了OpenGLScreen实例的onVisibilityChange()方法。

2. 如果程序进入可视状态,同时后台线程之前没有启动过,则调用OpenGLScreen实例的start()方法开始创建并启动后台线程。

3. 创建OpenGLScreen的一个新实例,并以OpenGLScreen为主体创建和启动一个新的线程。因为OpenGLScreen类本身实现了Runnable接口, 所以它可以作为一个线程开始运行。为了更直观的说明程序运行过程,时序图中将新的OpenGLScreen线程作为一个新的实例放在CubeRenderer实例右方。

4. OpenGLScreen的run()方法开始执行,调用了initializeEGL()方法对OpenGL环境进行初始化工作。

5. 在initializeEGL()方法中创建了一个EGL11实例。

6. 通过对EGL11实例的操作完成各种初始化工作,包括EGLSurface,EGLContext和EGLDisplay的初始化工作。

7. 通过EGLContext获得了GL10实例。

8. 完成OpenGL 环境的初始化工作后调用了CubeRenderer实例的initialize()方法对CubeRenderer进行初始化工作。

9. CubeRenderer实例的initialize()方法主要内容就是调用Cube类的不同静态方法获得Cube类中对立方体模型的定义数据,包括顶点数组、法向量数组和帖图参数数组。调用之后Cube类就不起什么作用了。有关顶点数组和法向量数组等在以后的小节会进一步讨论。

10. 从第10步开始进入了while循环,只要程序仍处于可视状态,就一直重复第10步到第14步的动作。第10步完成的工作是调用CubeRenderer的update()方法,让立方体转动一定的角度。

11. 转动立方体后调用OpenGLScreen的renderFrame()方法开始绘制立方体。

12. renderFrame()方法调用了CubeRenderer的render()方法对立方体模型进行绘制。

13. 在CubeRenderer的render()方法中通过对GL10实例的操作在缓存中完成立方体模型的绘制,这一步是OpenGLDemo程序的关键,开发人员可以修改这里的代码完成自己的3D绘制。

14. CubeRenderer的render()方法是在缓存中完成了立方体的绘制,并没有真正显示到屏幕上。要将缓存中绘制好的内容显示出来,需要调用 eglCopyBuffers()或者是eglSwapBuffers()方法。两者的作用有所不同。eglCopyBuffers的作用是将缓存中的图像拷贝出来,保存到指定的变量中以供使用,而eglSwapBuffers的作用是直接将缓存切换到绑定的屏幕中。所以,程序在调用eglCopyBuffers或者是eglSwapBuffers方法之前先判断应用处于什么状态。如果处于停顿状态,比如用户点击菜单键显示了菜单,这时屏幕中的3D图形不再转动,则程序调用eglCopyBuffers方法将缓存中的图像拷贝出来保存在一个变量中,由OpenGLScreen的paint方法将这个变量中的图像显示到屏幕上。如果处于正常运行状态,则程序直接调用eglSwapBuffers方法将缓存中的内容显示出来。使用这种策略的目的是在应用处于停顿状态时停止后台的3D计算,以节省CPU使用量。同时,为了保证应用切换后依然可以显示正确的图像(比如3D图像停止转动后用户接了一个电话,在电话结束时切换回本应用),程序将最后一帧静止的图像保存下来,一旦屏幕需要刷新则显示该图像。

15. 只有跳出while循环以后才能运行到这一步, 这一步的工作是调用OpenGLScreen的unloadEGL()方法完成OpenGL环境的清理工作。

OpenGLDemo样例中的环境初始化,立方体旋转,立方体绘制,还有绘制结果的显示都发生成后台OpenGLScreen线程中。所以其关键代码在OpenGLScreen类的run()方法中,基本框架如代码清单18-4。

代码清单18-4:

public void run()

{

// 刚开始运行的时候休眠一小段时间让前台Screen完成初始化的工作

try

{ Thread.sleep(250); }

catch (InterruptedException ie)

{ }

try

{

// 调用initializeEGL()方法对OpenGL环境进行初始化工作

initializeEGL();

// 调用CubeRenderer的initialize()方法载入立方体模型的数据。

_renderer.initialize();

int throttle = 0;

// 进入while循环,直到_running变量被主线程置为false。

while (_running)

{ // 记录当前时间

throttle = (int)System.currentTimeMillis();

// 调用CubeRenderer的update()方法让立方体转动

_renderer.update();

// 对立方体进行绘制

renderFrame();

if (_idle)

{

// 将进入暂停状态,将EGLSurface中的内容拷贝到_backBuffer中

// 保存起来,在主屏幕Screen的update方法中直接使用

//_backBuffer中保留的图像。

_egl.eglCopyBuffers(_eglDisplay, _eglSurface, _backBuffer);

//其它代码省略……

}

else

{

// 一般状态,将_eglSurface中的内容显示到屏幕上。

_egl.eglSwapBuffers(_eglDisplay, _eglSurface);

//其它代码省略……

}

}

//跳出循环后清除EGL环境

unloadEGL();

}

catch(final Exception e)

{ //异常处理,当出现异常时提醒用户并退出程序。

Application.getApplication().invokeAndWait(new Runnable()

{

public void run()

{

Dialog.alert(e.toString());

}

});

System.exit(1);

}

}

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics