由于各种硬件和软件的实现不同,OpenGL在各平台支持的能力和规范也有差别。
移动平台支持的OpenGL ES是OpenGL三维图形API的一个子集,它是针对手机等嵌入式设备而设计。因此,须要兼容移动平台的跨平台开拓时,只管即便选择OpenGL ES支持的API。OpenGL和OpenGL ES的早期版本是针对固定管线的,从2.0开始支持着色措辞(shading language)和可编程管线。可编程管线的API利用更灵巧,支持的特性更丰富。目前多数硬件都已经支持可编程管线,而且官方已经建议废弃固定管线API利用,在高版本中也移除了固定管线API。因此建议利用着色措辞和可编程管线API。并至少支持2.0版本。如果哀求支持的设备或系统都比较新,可以直策应用3.0或更高版本。在运用程序调用任何OpenGL函数之前,都须要首先创建一个OpenGL高下文(Context)。这个OpenGL高下文可以理解为一个非常弘大的状态机,保存了OpenGL中的各种状态,这是OpenGL实行各种指令的根本。某种程度上,OpenGL函数都是对其高下文这个状态机中工具或状态的操作。由于OpenGL高下文是一个巨大的状态机,切换高下文的开销每每比较大。不同的绘制模块又须要不同的状态和工具管理。因此在运用程序中可以创建多个不同的高下文,在不同线程中利用不同的高下文,高下文之间可以共享缓冲区,纹理等资源。应该只管即便避免反复切换高下文和修正大量状态,提高处理效率。
虽然OpenGL是跨平台的,但各平台的实现和OpenGL的环境搭建会有所不同。开拓过程中各平台的OpenGL高下文(Context)的建立和切换都有所不同。开拓者既可以利用如GLFW、GLUT等开源框架帮助完成OpenGL高下文环境的建立和管理,也可以利用各平台的原生API来完成。本文紧张先容利用各平台原生API的方法。下面分平台先容各平台的OpenGL环境建立与高下文创建。

一、 Windows
Windows平台由于微软的Direct3D存在,微软对OpenGL的支持并不积极。在大多数微软操作系统中所支持OpenGL版本还是1.0和1.1,仅支持固定管线API,对付当代利用OpenGL开拓的程序并不友好。不过通过OpenGL的ARB扩展机制可以让我们访问到OpenGL的高等特性接口。Windows OpenGL实现供应了名为wglGetProcAddress的函数,许可我们对指向一个由驱动程序供应的OpenGL函数的指针进行检索。不过还有一种更为快捷的方法。通过GLEW(GL Extension Wrangler)库完成这一繁琐的检索过程。只须要引入头文件glew.h和glew库并在运用程序的开始调用glewInit(),之后OpenGL 1.1以上的扩展和核心特性的所有函数指针都将自动被设置完成。
glewInit的调用须要先创建一个OpenGL高下文环境,在初始化完成后,再删除这个环境。之后重新创建一个支持WGL_ARB扩展的OpenGL高下文环境。示例代码如下:
/ 注册窗口类 /WNDCLASSEX wcex;ZeroMemory(&wcex, sizeof(WNDCLASSEX));wcex.cbSize = sizeof(WNDCLASSEX);wcex.hInstance = GetModuleHandle(NULL);wcex.lpfnWndProc = GLEW_WindowProc;wcex.lpszClassName = kszGlewInitClassName;/ 创建窗口以初始化glew /HWND hwnd = CreateWindow(kszGlewInitClassName, L"", (WS_POPUP | WS_DISABLED), CW_USEDEFAULT, CW_USEDEFAULT, 10, 10, NULL, NULL, GetModuleHandle(NULL), NULL);/ 设置像素格式 /HDC hdc = GetDC(hwnd);PIXELFORMATDESCRIPTOR pfd;memset(&pfd, 0, sizeof(PIXELFORMATDESCRIPTOR));pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR);pfd.nVersion = 1;pfd.dwFlags=PFD_DOUBLEBUFFER | PFD_SUPPORT_OPENGL | PFD_DRAW_TO_WINDOW;pfd.iPixelType = PFD_TYPE_RGBA;pfd.cColorBits = 24;pfd.cDepthBits = 32;pfd.cStencilBits = 8;pfd.iLayerType = PFD_MAIN_PLANE;int32_t nPixelFormat = ChoosePixelFormat(hdc, &pfd);BOOL bRet = SetPixelFormat(hdc, nPixelFormat, &pfd);/ 创建OpenGL高下文环境并设置为当前高下文 /HGLRC hglrc = wglCreateContext(hdc);wglMakeCurrent(hdc, hglrc);/ 初始化glew /if (glewInit()) { printf("glewInit failed"); // handle init fail ……}/ 开释当前OpenGL高下文环境 /wglMakeCurrent(NULL, NULL);wglDeleteContext(hglrc);ReleaseDC(hwnd, hdc);DestroyWindow(hwnd);UnregisterClass(kszGlewInitClassName, NULL);/ 开始选择真正的格式并创建相应的OpenGL高下文 // 再次创建窗口 /……/ 设置关心的主要属性 /int32_t pixAttribs[] = { WGL_DRAW_TO_WINDOW_ARB, GL_TRUE, // 绘制在窗口的像素格式 WGL_SUPPORT_OPENGL_ARB, GL_TRUE, // 支持OpenGL渲染 WGL_DOUBLE_BUFFER_ARB, GL_TRUE, // 支持双缓冲 WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB, // 像素格式为RGBA WGL_ACCELERATION_ARB, WGL_FULL_ACCELERATION_ARB, // 支持硬件加速 WGL_COLOR_BITS_ARB, 32, // 颜色缓冲位深32 WGL_ALPHA_BITS_ARB, 8, // alpha通道位深 8 WGL_DEPTH_BITS_ARB, 24, // 深度缓冲位深 24 WGL_STENCIL_BITS_ARB, 8, // 模板缓冲位深 8 WGL_SAMPLE_BUFFERS_ARB, GL_TRUE,// 开启多重采样MSAA WGL_SAMPLES_ARB, 4, // 4倍多重采样MSAA 0}; // 以NULL结束/ 哀求探求与我们属性相匹配的最佳格式,并取回一种/uint32_t pixCount = 0;int32_t nPixelFormat = 0;wglChoosePixelFormatARB(hdc, &pixAttribs[0], NULL, 1, &nPixelFormat, &pixCount);if (nPixelFormat != -1) { / 设置选中的最佳格式 / SetPixelFormat(hdc, nPixelFormat, &pfd);/ 创建相应的OpenGL高下文环境 / int32_t attribs[] = { WGL_CONTEXT_MAJOR_VERSION_ARB, 3, WGL_CONTEXT_MINOR_VERSION_ARB, 3, WGL_CONTEXT_FLAGS_ARB, 0, 0}; HGLRC wglrc = wglCreateContextAttribsARB(hdc, 0, attribs); if (wglrc) { wglMakeCurrent(hdc, wglrc); hglrc_ = wglrc; } else { printf("wglCreateContextAttribsARB failed"); // handle failed ……}} else { printf("ChoosePixelFormat failed"); // handle failed ……}/ 查询并打印OpenGL版本 /const GLubyte GLVersionString = glGetString(GL_VERSION);int32_t OpenGLVersion[2];glGetIntegerv(GL_MAJOR_VERSION, &OpenGLVersion[0]); glGetIntegerv(GL_MINOR_VERSION, &OpenGLVersion[1]); printf("OpenGL version " << OpenGLVersion[0] << "." << OpenGLVersion[1]);
二、 macOS
macOS供应了glut,NSOpenGL,CGL等接口来创建和管理OpenGL环境。本文以NSOpenGL为例来先容。
NSOpenGLView是一个轻量级的NSView子类封装,方便地供应了OpenGL绘制环境的创建与管理。在其内部掩护了NSOpenGLPixelFormat和NSOpenGLContext工具,用户可以方便的对其进行访问和管理。
NSOpenGLView的创建很大略,首先通过设定NSOpenGLPixelFormatAttribute属性值创建NSOpenGLPixelFormat工具,再用NSOpenGLPixelFormat创建NSOpenGLView。
static NSOpenGLPixelFormatAttribute kDefaultAttributes[] = { NSOpenGLPFADoubleBuffer, //双缓冲 NSOpenGLPFADepthSize, 24, //深度缓冲位深 NSOpenGLPFAStencilSize, 8, //模板缓冲位深 NSOpenGLPFAMultisample, //多重采样 NSOpenGLPFASampleBuffers, (NSOpenGLPixelFormatAttribute)1, //多重采样buffer NSOpenGLPFASamples, (NSOpenGLPixelFormatAttribute)4, // 多重采样数 NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core, // OpenGL3.2 0};NSOpenGLPixelFormat pixelFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes: kDefaultAttributes];NSOpenGLView renderView = [[NSOpenGLView alloc] initWithFrame:frameRect pixelFormat:pixelFormat];开拓者可以继续NSOpenGLView实现子类,并通过实现- (void)prepareOpenGL;和- (void)clearGLContext;自定义OpenGL Context初始化和删除时的一些行为。详细的绘制操作可以在-(void) drawRect: (NSRect) bounds;中实现。须要把稳的是,为担保Retina屏的显示效果,可以设置NSOpenGLView的属性wantsBestResolutionOpenGLSurface为YES。- (void)prepareOpenGL { [super prepareOpenGL]; [self ensureGLContext]; // setup OpenGL resources ……}- (void)clearGLContext { [self ensureGLContext]; // cleanup OpenGL resources …… [super clearGLContext];}-(void) drawRect: (NSRect) bounds { [self ensureGLContext]; // draw OpenGL …… [super drawRect:rect];}- (void)ensureGLContext { NSOpenGLContext context = [self openGLContext]; if ([NSOpenGLContext currentContext] != context) { [context makeCurrentContext]; }}
三、 iOS
iOS从2013年9月上线的iOS 7及同期发布的新设备开始支持OpenGL ES 3.0,Apple也是从这个韶光点开始发布了64位设备。因此目前市情上除了少量早期的iOS设备外,绝大多数iOS设备都已支持OpenGL ES 3.0。
和macOS类似,iOS也供应了封装好的UIView——GLKView,开拓者可以方便地利用此View实现OpenGL ES的绘制。此外也供应了OpenGL ES的framebuffer工具可以实现离屏渲染,或基于CAEAGLLayer实现CALayer层面的绘制。本文还是以GLKView为例进行解释。
EAGLContext glContext = [[EAGLContext alloc] initWithAPI: kEAGLRenderingAPIOpenGLES3];if (!glContext) { // OpenGL ES 3 创建失落败, 创建OpenGL ES 2 glContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];}GLKView glkView = [[GLKView alloc] initWithFrame:frameRect context:glContext];// 配置renderbuffersglkView.drawableColorFormat = GLKViewDrawableColorFormatRGBA8888;glkView.drawableDepthFormat = GLKViewDrawableDepthFormat24;glkView.drawableStencilFormat = GLKViewDrawableStencilFormat8;glkView.drawableMultisample = GLKViewDrawableMultisample4X;glkView.delegate = self; 详细的渲染操作通过GLKViewDelegate的方法- (void)glkView:(GLKView )view drawInRect:(CGRect)rect;实现 - (void)glkView:(GLKView)view drawInRect:(CGRect)rect { if ([EAGLContext currentContext] != glContext_) { [EAGLContext setCurrentContext:glContext_]; }// draw OpenGL……}
四、 Android
Android也是从2013年发面的Android 4.3开始支持OpenGL ES 3的,但比较封闭的iOS生态,真正支持OpenGL ES 3的设备并不随意马虎判断。可以通过创建OpenGL ES 3的高下文是否成功来判断。
Android也有和iOS GLKView类似的封装好的OpenGL View——GLSurfaceView,开拓 者可以直接通过GLSurfaceView的方法来创建和管理OpenGL ES高下文。也可以基于SurfaceView和EGL的接口创建自己的OpenGL高下文环境和渲染View。这里先容一种基于EGL native接口的方法(利用EGL的Java接口,调用流程也是一样的)。
EGL是图形渲染API(如OpenGL ES)与本地平台窗口系统之间的一层接口,它担保了OpenGL ES的平台独立性。供应了创建渲染表面(rendering surface)、图形高下文(graphics context),同步运用程序和平台渲染API,显示设备访问,渲染配置等管理功能。基于EGL的创建高下文环境紧张有初始化、选择和设置得当的配置、创建表面、创建高下文四个步骤。
EGLBoolean success = EGL_FALSE;EGLint err = 0;
1. EGL初始化
EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);if (EGL_NO_DISPLAY == display_) { printf("eglGetDisplay error "<< eglGetError()); return;}EGLint major=0, minor=0;if (EGL_FALSE == eglInitialize(display, &major, &minor);) { printf("eglInitialize error "<< eglGetError()); return;}
2. 选择和设置得当的配置
const EGLint configAttribs[] = { EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8, EGL_STENCIL_SIZE, 8, EGL_SAMPLE_BUFFERS, 1, EGL_SAMPLES, 4, EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_SURFACE_TYPE, EGL_WINDOW_BIT, // 基于窗口的surface EGL_NONE};EGLConfig config;EGLint numConfigs;if (EGL_FALSE == eglChooseConfig(display_, configAttribs, &config, 1, &numConfigs)) { // handle and select other configs ……}
3. 创建表面
EGLint format;if (!eglGetConfigAttrib(display_, config, EGL_NATIVE_VISUAL_ID, &format)) { printf("eglGetConfigAttrib error "<< eglGetError()); return;}ANativeWindow window; // ANativeWindow可以为SurfaceView中获取的Surface工具ANativeWindow_setBuffersGeometry(window, 0, 0, format);EGLSurface surface = eglCreateWindowSurface(display, config, window, NULL);if (!surface_) { printf("eglCreateWindowSurface error "<< eglGetError()); return;}
4. 创建高下文
EGLint contextAttribs[] = { EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE};EGLContext context = eglCreateContext(display, config, EGL_NO_CONTEXT, contextAttribs);if (context == EGL_NO_CONTEXT) {printf("eglCreateContext create OpenGL ES 3 context failed "); EGLint contextAttribs2[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE }; context = eglCreateContext(display_, config, NULL, contextAttribs2); if (context_ == EGL_NO_CONTEXT) { printf("eglCreateContext create OpenGL ES 2 context failed "); return; }}eglMakeCurrent(display, surface, surface, context)) {
至此,本文先容了几大主流平台上OpenGL原生开拓的高下文环境创建与管理。除了Windows对OpenGL的支持相对较弱,须要依赖第三方库才能便捷的利用。其它平台都可以相对较快的建立OpenGL高下文环境,乃至有封装好的View帮助开拓者快速接入。不过OpenGL制订的较早,已经不太适应当代GPU图形技能的发展了,碰着了一些问题:如当代GPU渲染管线发生了变革,不支持多线程操作,不支持异步处理等。
未来新一代的图形API Vulkan可能会取代OpenGL。Vulkan会大幅降落绘制命令开销,发送多线程性能,渲染性能更快。谷歌也已经明确Android会支持Vulkan。微软的DirectX12背后理念与Vulkan也是同等的。苹果公司则在2014年推出了自行设计的Metal API,目标也是替代OpenGL,以适应当代GPU技能,其指令开销和渲染性能等也得到大幅提升。2018年苹果已经宣告OpenGL和OpenGL ES干系API从macOS 10.14和iOS 12中废弃,今后不再掩护。开拓者今后迁移到新的图形API也是大势所趋,不过OpenGL作为主流图形API存在了超过20年,其终极消亡肯定还有很长的路。
拍乐云成立于2019年,是海内第一家视频会议背景的实时互动通信云做事供应商,汇聚了一大批专注于音频、视频、白板、网络、AI等领域的资深技能专家。通过集成 Pano SDK,企业开拓者即可在环球范围内快速实现互动小班、超级小班、双师大班、语音谈天室、视频社交、直播连麦、游戏语音、视频客服、远程医疗、办公协作等场景。
关注拍乐云 Pano ,我们将为大家分享更多的技能探索和实践履历,陪你一起发展充电。