乐趣区

关于android:实践解析-如何用-OpenGL-实现跨平台应用高效渲染

OpenGL(Open Graphics Library 开放式图形库)是一个定义了跨编程语言、跨平台的编程接口规格的业余图形程序接口。它可用于三维、二维图形图像的渲染,是一个功能强大,调用不便的底层图形库。在一个 RTC 利用中,因视频渲染或算法解决的须要,OpenGL 是一种高效的渲染或解决实现形式。OpenGL 的高效实现在 Windows、Linux 和 macOS 都有相应反对。此外,挪动平台 iOS 和 Android 都能反对 OpenGL ES(OpenGL for Embedded Systems)。利用 OpenGL 进行跨平台的开发是十分不便的。本文就来分享一下如何利用 OpenGL 实现跨平台利用高效渲染。

因为各种硬件和软件的实现不同,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("glewInitfailed\n");
    // handleinit 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_tattribs[] = {
      WGL_CONTEXT_MAJOR_VERSION_ARB, 3,
      WGL_CONTEXT_MINOR_VERSION_ARB, 3,
      WGL_CONTEXT_FLAGS_ARB, 0,
       0};
    HGLRCwglrc = wglCreateContextAttribsARB(hdc, 0, attribs);
    if(wglrc) {wglMakeCurrent(hdc, wglrc);
       hglrc_ = wglrc;
    } else {printf("wglCreateContextAttribsARBfailed\n");
        //handle failed
        ……
    }
} else {printf("ChoosePixelFormatfailed\n");
    // handlefailed
    ……
}
/* 查问并打印 OpenGL 版本 */
const GLubyte *GLVersionString = glGetString(GL_VERSION);
int32_t OpenGLVersion[2];
glGetIntegerv(GL_MAJOR_VERSION, &OpenGLVersion[0]);
glGetIntegerv(GL_MINOR_VERSION,&OpenGLVersion[1]);
printf("OpenGLversion %d.%d\n", OpenGLVersion[0], OpenGLVersion[1]);

二、macOS

macOS 提供了 glut,NSOpenGL,CGL 等接口来创立和治理 OpenGL 环境。本文以 NSOpenGL 为例来介绍。

NSOpenGLView 是一个轻量级的 NSView 子类封装,不便地提供了 OpenGL 绘制环境的创立与治理。在其外部保护了 NSOpenGLPixelFormat 和 NSOpenGLContext 对象,用户能够不便的对其进行拜访和治理。

NSOpenGLView 的创立很简略,首先通过设定 NSOpenGLPixel FormatAttribute 属性值创立 NSOpenGLPixelFormat 对象,再用 NS OpenGLPixelFormat 创立 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 = [[NSOpenGLPixelFormatalloc] 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];
    // setupOpenGL 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];
// 配置 renderbuffers
glkView.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*)viewdrawInRect:(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 的创立上下文环境次要有初始化、抉择和设置适合的配置、创立外表、创立上下文四个步骤。

  1. EGL 初始化
EGLBoolean success = EGL_FALSE;
EGLint err = 0;
EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
if (EGL_NO_DISPLAY == display_) {printf("eglGetDisplay error %d\n",eglGetError());
    return;
}
EGLint major=0, minor=0;
if (EGL_FALSE == eglInitialize(display, &major, &minor);) {printf("eglInitialize error %d\n", eglGetError());
    return;
}
  1. 抉择和设置适合的配置
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 andselect other configs
    ……
}
  1. 创立外表
EGLint format;
if (!eglGetConfigAttrib(display_, config,EGL_NATIVE_VISUAL_ID, &format)) {printf("eglGetConfigAttrib error %d\n", 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 %d\n", eglGetError());
    return;
}
  1. 创立上下文
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 contextfailed\n");
    EGLintcontextAttribs2[] = {
       EGL_CONTEXT_CLIENT_VERSION, 2,
        EGL_NONE
    };
    context = eglCreateContext(display_,config, NULL, contextAttribs2);
    if (context_== EGL_NO_CONTEXT) {printf("eglCreateContextcreate OpenGL ES 2 context failed\n");
        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 年,其最终沦亡必定还有很长的路。

退出移动版