从macOS Lion(10.7)开始,Apple支持了对OpenGL 3.2 Core Profile的支持。不过Core Profile与Compatible相比有比较大幅度的改变。从主机端的API到OpenGL接口,再到GLSL(OpenGL Shading Language),这些方面都有些变化。
在主机端接口方面,首先,必须用
<OpenGL/gl3.h>
来代替原来的
<OpenGL/gl.h>
,这点很重要!
其次,在
NSOpenGLPixelFormatAttribute
对象中必须增加
NSOpenGLPFAOpenGLProfile
以及
NSOpenGLProfileVersion3_2Core
这两个属性。
在最后完成绘制后,需要调用CGL库的
CGLFlushDrawable
接口将所渲染的图形结果显示到视图或窗口上。
在OpenGL接口使用方面,必须使用VAO和VBO来存放顶点数据。然后,所有与固定功能流水线相关的接口都被移出,无法使用。因此这些被移除的功能只能通过Shader来实现。
在GLSL方面,必须显式地加上GLSL的版本号,并且至少为140(表示1.40),在基于Sandy Bridge的Intel HD Graphics 3000上默认为150(1.50)。然后,
in
、
out
代替了原来的
attribute
和
varying
关键字。在Fragment shader中,
gl_FragColor
内建变量被取消,你可以自己定义一个
out
变量作为最后像素的输出。
下面给出一个比较基本、简洁的代码样例:
以下是
MyGLView.h
头文件的内容
//
// MyGLView.h
// OpenGLShaderBasic
//
// Created by Zenny Chen on 10/4/10.
// Copyright 2010 GreenGames Studio. All rights reserved.
//
@import Cocoa;
@interface MyGLView : NSOpenGLView
{
GLuint program;
GLuint vbo, vao;
}
- (void)render;
@end
以下是
MyGLView.m
源文件的内容:
//
// MyGLView.m
// OpenGLShaderBasic
//
// Created by Zenny Chen on 10/4/10.
// Copyright 2010 GreenGames Studio. All rights reserved.
//
#import "MyGLView.h"
// 这里必须注意!<gl3.h>头文件必须被包含并取代<gl.h>,否则VAO接口会调用不正常,从而无法正确显示图形!
#import <OpenGL/gl3.h>
#define MY_PIXEL_WIDTH 128
#define MY_PIXEL_HEIGHT 128
@implementation MyGLView
// attribute index
enum
{
ATTRIB_VERTEX,
NUM_ATTRIBUTES
};
- (BOOL)compileShader:(GLuint *)shader type:(GLenum)type file:(NSString *)file
{
GLint status;
const GLchar *source;
source = (GLchar *)[NSString stringWithContentsOfFile:file encoding:NSUTF8StringEncoding error:nil].UTF8String;
if (source == NULL)
{
NSLog(@"Failed to load vertex shader");
return NO;
}
*shader = glCreateShader(type);
glShaderSource(*shader, 1, &source, NULL);
glCompileShader(*shader);
GLint logLength;
glGetShaderiv(*shader, GL_INFO_LOG_LENGTH, &logLength);
if (logLength > 0)
{
GLchar *log = (GLchar *)malloc(logLength);
glGetShaderInfoLog(*shader, logLength, &logLength, log);
NSLog(@"Shader compile log:\n%s", log);
free(log);
}
glGetShaderiv(*shader, GL_COMPILE_STATUS, &status);
if (status == 0)
{
glDeleteShader(*shader);
return NO;
}
return YES;
}
- (BOOL)linkProgram:(GLuint)prog
{
GLint status;
glLinkProgram(prog);
GLint logLength;
glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &logLength);
if (logLength > 0)
{
GLchar *log = (GLchar *)malloc(logLength);
glGetProgramInfoLog(prog, logLength, &logLength, log);
NSLog(@"Program link log:\n%s", log);
free(log);
}
glGetProgramiv(prog, GL_LINK_STATUS, &status);
if (status == 0) return NO;
return YES;
}
- (BOOL)validateProgram:(GLuint)prog
{
GLint logLength, status;
glValidateProgram(prog);
glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &logLength);
if (logLength > 0)
{
GLchar *log = (GLchar *)malloc(logLength);
glGetProgramInfoLog(prog, logLength, &logLength, log);
NSLog(@"Program validate log:\n%s", log);
free(log);
}
glGetProgramiv(prog, GL_VALIDATE_STATUS, &status);
if (status == 0) return NO;
return YES;
}
- (BOOL)loadShaders
{
GLuint vertShader, fragShader;
NSString *vertShaderPathname, *fragShaderPathname;
// create shader program
program = glCreateProgram();
// bind attribute locations
// this needs to be done prior to linking
glBindAttribLocation(program, ATTRIB_VERTEX, "inPos");
// create and compile vertex shader
vertShaderPathname = [NSBundle.mainBundle pathForResource:@"Shader" ofType:@"vsh"];
if (![self compileShader:&vertShader type:GL_VERTEX_SHADER file:vertShaderPathname])
{
NSLog(@"Failed to compile vertex shader");
return NO;
}
// create and compile fragment shader
fragShaderPathname = [NSBundle.mainBundle pathForResource:@"Shader" ofType:@"fsh"];
if (![self compileShader:&fragShader type:GL_FRAGMENT_SHADER file:fragShaderPathname])
{
NSLog(@"Failed to compile fragment shader");
return NO;
}
// attach vertex shader to program
glAttachShader(program, vertShader);
// attach fragment shader to program
glAttachShader(program, fragShader);
//glBindFragDataLocationEXT(program, 0, "myOutput");
// link program
if (![self linkProgram:program])
{
NSLog(@"Failed to link program: %d", program);
return NO;
}
// release vertex and fragment shaders
if (vertShader) {
glDeleteShader(vertShader);
}
if (fragShader) {
glDeleteShader(fragShader);
}
return YES;
}
- (instancetype)initWithFrame:(NSRect)frameRect
{
self = [super initWithFrame:frameRect];
const NSOpenGLPixelFormatAttribute attrs[] =
{
NSOpenGLPFADoubleBuffer, // 可选地,可以使用双缓冲
NSOpenGLPFAOpenGLProfile, // Must specify the 3.2 Core Profile to use OpenGL 3.2
NSOpenGLProfileVersion3_2Core,
0
};
NSOpenGLPixelFormat *pf = [NSOpenGLPixelFormat.alloc initWithAttributes:attrs];
if (pf == nil) {
NSLog(@"No OpenGL pixel format");
}
NSOpenGLContext* context = [NSOpenGLContext.alloc initWithFormat:pf shareContext:nil];
[self setPixelFormat:pf];
[pf release];
[self setOpenGLContext:context];
[context release];
return self;
}
- (void)dealloc
{
if(vbo != 0) {
glDeleteFramebuffers(1, &vbo);
}
if(vao != 0) {
glDeleteRenderbuffers(1, &vao);
}
[super dealloc];
}
- (void)prepareOpenGL
{
[super prepareOpenGL];
[self.openGLContext makeCurrentContext];
// Synchronize buffer swaps with vertical refresh rate
GLint swapInt = 1;
[self.openGLContext setValues:&swapInt forParameter:NSOpenGLCPSwapInterval];
// Drawing code here.
static const GLfloat squareVertices[] = {
-1.0f, 1.0f, 0.0f, 1.0f,
-1.0f, -1.0f, 0.0f, 1.0f,
1.0f, 1.0f, 0.0f, 1.0f,
1.0f, -1.0f, 0.0f, 1.0f
};
// In OpenGL 3.2 core profile, vertex buffer object and vertex array object must be used!
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(squareVertices), squareVertices, GL_STATIC_DRAW);
// Update attribute values
glEnableVertexAttribArray(ATTRIB_VERTEX);
glVertexAttribPointer(ATTRIB_VERTEX, 4, GL_FLOAT, 0, 0, (const GLvoid*)0);
// Load shaders and build the program
if(![self loadShaders]) return;
// Use shader program
glUseProgram(program);
glViewport(0, 0, MY_PIXEL_WIDTH, MY_PIXEL_HEIGHT);
glClearColor(0.5f, 0.5f, 0.5f, 1.0f);
}
- (void)render
{
// render
glClear(GL_COLOR_BUFFER_BIT);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glFlush();
CGLFlushDrawable([[self openGLContext] CGLContextObj]);
}
@end
以上两个文件是主要的使用OpenGL接口的样例头文件和源文件。在这个例子中,OpenGL的上下文基于一个视图(NSOpenGLView的子类)。
下面将给出Shader源代码。
先是顶点着色器:
//
// Shader.vsh
// GLSLTest
//
// Created by Zenny Chen on 4/11/10.
// Copyright GreenGames Studio 2010. All rights reserved.
//
// 在OpenGL3.2 Core Profile中,版本号必须显式地给出
#version 150
in vec4 inPos;
void main(void)
{
gl_Position = inPos;
}
其次是片段着色器:
//
// Shader.fsh
// GLSLTest
//
// Created by Zenny Chen on 4/11/10.
// Copyright GreenGames Studio 2010. All rights reserved.
//
// 在OpenGL3.2 Core Profile中,版本号必须显式地给出
#version 150
out vec4 myOutput;
void main(void)
{
//gl_FragColor = vec4(0.1, 0.8, 0.5, 1.0);
myOutput = vec4(0.1, 0.8, 0.5, 1.0);
}
下面给出
AppDelegate
中对
MyGLView
对象的具体调用。
先是
AppDelegate.h
头文件:
@import Cocoa;
@class MyGLView;
@interface AppDelegate : NSObject <NSApplicationDelegate>
{
MyGLView *glComputeObj;
}
@property (assign) IBOutlet NSWindow *window;
@end
其次是
AppDelegate.m
源文件内容:
//
// AppDelegate.m
// OpenGL4Compute
//
// Created by zenny_chen on 12-12-9.
// Copyright (c) 2012年 zenny_chen. All rights reserved.
//
#import "AppDelegate.h"
#import "MyGLView.h"
@implementation AppDelegate
- (void)dealloc
{
[super dealloc];
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
// Insert code here to initialize your application
NSView *baseView = self.window.contentView;
CGSize viewSize = baseView.frame.size;
NSButton *button = [[NSButton alloc] initWithFrame:CGRectMake(20.0f, viewSize.height - 60.0f, 60.0f, 30.0f)];
[button setButtonType:NSMomentaryPushInButton];
[button setBezelStyle:NSRoundedBezelStyle];
[button setTitle:@"Show"];
[button setTarget:self];
[button setAction:@selector(computeButtonTouched:)];
[baseView addSubview:button];
[button release];
NSLog(@"The view is: %@", [self.window.contentView class]);
}
- (void)computeButtonTouched:(id)sender
{
if(glComputeObj == nil)
{
NSView *baseView = self.window.contentView;
CGSize viewSize = baseView.frame.size;
glComputeObj = [[MyGLView alloc] initWithFrame:CGRectMake((viewSize.width - 128.0f) * 0.5f, (viewSize.height - 128.0f) * 0.5f, 128.0f, 128.0f)];
[baseView addSubview:glComputeObj];
[glComputeObj release];
}
[glComputeObj performSelector:@selector(render) withObject:nil afterDelay:0.1];
}
@end
由于此篇博文写于2012年,因此用了一些老旧的方式,比如XIB这种基于GUI的拖拽方式,而不是纯代码。不过主要逻辑都在
MyGLView.m
源文件中,各位直接参考这里头的代码即可。