您认为避免绝对抽象并直接使用OpenGL ES 2.0编写是个好主意吗?
据我所知,我应该能够针对标准OpenGL在PC上进行编译,在处理上下文和连接到窗口系统的代码中仅进行小的更改。
GLuint shader = glCreateShader(/*shader type*/);
const char *shaderList[2];
shaderList[0] = GetGlobalPreambleString(); //Gets preamble for the right platform
shaderList[1] = LoadShaderFile(); //Get the actual shader file
glShaderSource(shader, 2, shaderList, NULL);
引言部分也可以包括一个特定平台的# define。当然是用户定义的。这样,您就可以为不同的平台编写条件代码(#ifdef)。
两者之间还有其他差异。例如,虽然在桌面GL 2.1中有效的ES 2.0纹理上传函数调用会正常工作,但它们不一定是最佳的。 在像所有移动系统这样的大端机器上可以上传的东西将需要来自小端桌面机器驱动程序的一些位操作。 因此,您可能希望在GL ES和桌面GL上指定不同的像素传输参数的方法。
此外,ES 2.0和桌面GL 2.1中有不同的扩展集,您将想要利用它们。尽管它们中的许多尝试相互映射(OES_framebuffer_object是EXT_framebuffer_object的子集),但您可能会遇到类似于上面提到的“不完全子集”的问题。
以下是我在商业地图和路由库运行的各种平台上实现OpenGL ES 2.0支持的经验结果。
渲染类被设计为在单独的线程中运行。它引用包含地图数据和当前视图信息的对象,并使用互斥锁来避免在绘制时读取该信息时发生冲突。它在图形内存中维护OpenGL ES向量数据的缓存。
所有渲染逻辑均以C++编写,并在以下所有平台上使用。
Windows(MFC)
使用ANGLE库:链接到libEGL.lib和libGLESv2.lib,并确保可执行文件可以访问DLLs libEGL.dll和libGLESv2.dll。 C++代码创建一个线程,以适当的速率(例如每秒25次)重新绘制图形。
Windows(.NET和WPF)
使用C++/CLI包装器创建EGL上下文并调用直接在MFC实现中使用的C++渲染代码。 C++代码创建一个线程,以适当的速率(例如每秒25次)重新绘制图形。
Windows(UWP)
在UWP应用程序代码中创建EGL上下文,并通过C++/CXX包装器调用C++渲染代码。您需要使用SwapChainPanel并创建自己的渲染循环,在不同的线程中运行。请参见GLUWP项目以获取示例代码。
Windows、Linux和Mac OS上的Qt
将QOpenGLWidget用作窗口。使用Qt OpenGL ES包装器创建EGL上下文,然后在paintGL()函数中调用C++渲染代码。
Android
创建实现android.opengl.GLSurfaceView.Renderer的渲染器类。为C++渲染对象创建JNI包装器。在onSurfaceCreated()函数中创建C++渲染对象。在onDrawFrame()函数中调用C++渲染对象的绘图函数。您需要为渲染器类导入以下库:
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import android.opengl.GLSurfaceView.Renderer;
setEGLContextClientVersion(2); // use OpenGL ES 2.0
setEGLConfigChooser(8,8,8,8,24,0);
然后创建您的渲染器类的实例并调用setRenderer来安装它。
iOS
使用METALAngle库,而不是GLKit,因为苹果已经弃用并最终将不再支持GLKit。
创建一个Objective C++渲染器类来调用您的C++ OpenGL ES绘图逻辑。
创建一个从MGLKView派生的视图类。在您的视图类的drawRect()函数中,如果渲染器对象尚不存在,则创建一个渲染器对象,然后调用其绘图函数。也就是说,您的drawRect函数应该像这样:
-(void)drawRect:(CGRect)rect
{
if (m_renderer == nil && m_my_other_data != nil)
m_renderer = [[MyRenderer alloc] init:m_my_other_data];
if (m_renderer)
[m_renderer draw];
}
MGLContext* opengl_context = [[MGLContext alloc] initWithAPI:kMGLRenderingAPIOpenGLES2];
m_view = [[MyView alloc] initWithFrame:aBounds context:opengl_context];
m_view.drawableDepthFormat = MGLDrawableDepthFormat24;
self.view = m_view;
self.preferredFramesPerSecond = 30;
Linux
在Linux上使用Qt最为简单(参见上文),但也可以使用GLFW框架。在您的应用程序类构造函数中,调用glfwCreateWindow创建一个窗口并将其存储为数据成员。调用glfwMakeContextCurrent使EGL上下文当前,然后创建一个持有渲染器类实例的数据成员;例如:
m_window = glfwCreateWindow(1024,1024,"My Window Title",nullptr,nullptr);
glfwMakeContextCurrent(m_window);
m_renderer = std::make_unique<CMyRenderer>();
bool MapWindow::Draw()
{
if (glfwWindowShouldClose(m_window))
return false;
m_renderer->Draw();
/* Swap front and back buffers */
glfwSwapBuffers(m_window);
return true;
}
你的main()函数将是:
int main(void)
{
/* Initialize the library */
if (!glfwInit())
return -1;
// Create the app.
MyApp app;
/* Draw continuously until the user closes the window */
while (app.Draw())
{
/* Poll for and process events */
glfwPollEvents();
}
glfwTerminate();
return 0;
}
着色器不兼容
各种OpenGL ES 2.0实现所接受的着色器语言存在不兼容性。我在C++代码中使用以下有条件编译的代码来克服这些问题,放在我的CompileShader函数中:
const char* preamble = "";
#if defined(_POSIX_VERSION) && !defined(ANDROID) && !defined(__ANDROID__) && !defined(__APPLE__) && !defined(__EMSCRIPTEN__)
// for Ubuntu using Qt or GLFW
preamble = "#version 100\n";
#elif defined(USING_QT) && defined(__APPLE__)
// On the Mac #version doesn't work so the precision qualifiers are suppressed.
preamble = "#define lowp\n#define mediump\n#define highp\n";
#endif
然后将 preamble
前缀添加到着色器代码中。