Retired Document
Important: This sample code may not represent best practices for current development. The project may use deprecated symbols and illustrate technologies and techniques that are no longer recommended.
MyOpenGLView.m
/* |
File: MyOpenGLView.m |
Abstract: MyOpenGLView.m shows how to integrate Core Image and OpenGL |
to be able to take advantage of the built it Core Image's |
filters and use the result as an OpenGL texture. |
Version: 1.0 |
Disclaimer: IMPORTANT: This Apple software is supplied to you by |
Apple Inc. ("Apple") in consideration of your agreement to the |
following terms, and your use, installation, modification or |
redistribution of this Apple software constitutes acceptance of these |
terms. If you do not agree with these terms, please do not use, |
install, modify or redistribute this Apple software. |
In consideration of your agreement to abide by the following terms, and |
subject to these terms, Apple grants you a personal, non-exclusive |
license, under Apple's copyrights in this original Apple software (the |
"Apple Software"), to use, reproduce, modify and redistribute the Apple |
Software, with or without modifications, in source and/or binary forms; |
provided that if you redistribute the Apple Software in its entirety and |
without modifications, you must retain this notice and the following |
text and disclaimers in all such redistributions of the Apple Software. |
Neither the name, trademarks, service marks or logos of Apple Inc. |
may be used to endorse or promote products derived from the Apple |
Software without specific prior written permission from Apple. Except |
as expressly stated in this notice, no other rights or licenses, express |
or implied, are granted by Apple herein, including but not limited to |
any patent rights that may be infringed by your derivative works or by |
other works in which the Apple Software may be incorporated. |
The Apple Software is provided by Apple on an "AS IS" basis. APPLE |
MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION |
THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS |
FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND |
OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. |
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL |
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, |
MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED |
AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), |
STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE |
POSSIBILITY OF SUCH DAMAGE. |
Copyright (C) 2008 Apple Inc. All Rights Reserved. |
*/ |
#import "MyOpenGLView.h" |
@implementation MyOpenGLView |
- (id)initWithFrame:(NSRect)frameRect |
{ |
// Nice antialised, hardware accelerated w/ no recovery by the software renderer. |
NSOpenGLPixelFormatAttribute attribsAntialised[] = |
{ |
NSOpenGLPFAAccelerated, |
NSOpenGLPFANoRecovery, |
NSOpenGLPFADoubleBuffer, |
NSOpenGLPFAColorSize, 24, |
NSOpenGLPFAAlphaSize, 8, |
// NOTE: for this particular case we don't need a depth buffer when drawing to the FBO, |
// if you do need it, add the appropriate depth size, |
// but we don't want to waste VRAM here. |
#if 0 |
NSOpenGLPFADepthSize, 16, |
#endif |
NSOpenGLPFAMultisample, |
NSOpenGLPFASampleBuffers, 1, |
NSOpenGLPFASamples, 4, |
nil |
}; |
// A little less requirements if the above fails. |
NSOpenGLPixelFormatAttribute attribsBasic[] = |
{ |
NSOpenGLPFAAccelerated, |
NSOpenGLPFADoubleBuffer, |
NSOpenGLPFAColorSize, 24, |
NSOpenGLPFAAlphaSize, 8, |
// NOTE: for this particular case we don't need a depth buffer when drawing to the FBO, |
// if you do need it, add the appropriate depth size, |
// but we don't want to waste VRAM here. |
#if 0 |
NSOpenGLPFADepthSize, 16, |
#endif |
nil |
}; |
pixelFormat = [[[NSOpenGLPixelFormat alloc] initWithAttributes:attribsAntialised] autorelease]; |
if (nil == pixelFormat) { |
NSLog(@"Couldn't find an FSAA pixel format, trying something more basic"); |
pixelFormat = [[[NSOpenGLPixelFormat alloc] initWithAttributes:attribsBasic] autorelease]; |
} |
self = [super initWithFrame:frameRect pixelFormat:pixelFormat]; |
if (self) |
{ |
NSOpenGLContext *ctx; |
ctx = [self openGLContext]; |
// Turn on VBL syncing for swaps |
long VBL = 1; |
[ctx setValues:&VBL forParameter:NSOpenGLCPSwapInterval]; |
} |
return self; |
} |
// Load the Image and setup the CI filter |
- (void)setupImageWithCIFilter |
{ |
// Load the image |
myCIImage = [CIImage imageWithContentsOfURL: [NSURL fileURLWithPath: |
[[NSBundle mainBundle] pathForResource: @"Flowers" ofType: @"jpg"]]]; |
[myCIImage retain]; |
// Get the size of the image we are going to need throughout |
imageRect = [myCIImage extent]; |
// Get the aspect ratio for possible scaling (e.g. texture coordinates) |
imageAspectRatio = imageRect.size.width / imageRect.size.height; |
// Create the CIFilters |
hueFilter = [CIFilter filterWithName: @"CIHueAdjust"]; |
[hueFilter setDefaults]; |
[hueFilter setValue:myCIImage forKey:@"inputImage"]; |
[hueFilter retain]; |
gammaFilter = [CIFilter filterWithName: @"CIGammaAdjust"]; |
[gammaFilter setDefaults]; |
[gammaFilter setValue:[hueFilter valueForKey:@"outputImage"] forKey:@"inputImage"]; |
[gammaFilter retain]; |
gloomFilter = [CIFilter filterWithName: @"CIGloom"]; |
[gloomFilter setDefaults]; |
[gloomFilter setValue:[gammaFilter valueForKey:@"outputImage"] forKey:@"inputImage"]; |
[gloomFilter retain]; |
} |
// Create CIContext based on OpenGL context and pixel format |
- (BOOL)createCIContext |
{ |
// Create CIContext from the OpenGL context. |
myCIcontext = [CIContext contextWithCGLContext:[[self openGLContext] CGLContextObj] |
pixelFormat:[pixelFormat CGLPixelFormatObj] |
options: nil]; |
if (!myCIcontext) |
{ |
NSLog(@"CIContext creation failed"); |
return NO; |
} |
[myCIcontext retain]; |
// Created succesfully |
return YES; |
} |
// Create or update the hardware accelerated offscreen area |
// Framebuffer object aka. FBO |
- (void)setFBO |
{ |
// If not previously setup |
// generate IDs for FBO and its associated texture |
if (!FBOid) |
{ |
// Make sure the framebuffer extenstion is supported |
const GLubyte* strExt; |
GLboolean isFBO; |
// Get the extenstion name string. |
// It is a space-delimited list of the OpenGL extenstions |
// that are supported by the current renderer |
strExt = glGetString(GL_EXTENSIONS); |
isFBO = gluCheckExtension((const GLubyte*)"GL_EXT_framebuffer_object", strExt); |
if (!isFBO) |
{ |
NSLog(@"Your system does not support framebuffer extension"); |
} |
// create FBO object |
glGenFramebuffersEXT(1, &FBOid); |
// the texture |
glGenTextures(1, &FBOTextureId); |
} |
// Bind to FBO |
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, FBOid); |
// Sanity check against maximum OpenGL texture size |
// If bigger adjust to maximum possible size |
// while maintain the aspect ratio |
GLint maxTexSize; |
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTexSize); |
if (imageRect.size.width > maxTexSize || imageRect.size.height > maxTexSize) |
{ |
if (imageAspectRatio > 1) |
{ |
imageRect.size.width = maxTexSize; |
imageRect.size.height = maxTexSize / imageAspectRatio; |
} |
else |
{ |
imageRect.size.width = maxTexSize * imageAspectRatio ; |
imageRect.size.height = maxTexSize; |
} |
} |
// Initialize FBO Texture |
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, FBOTextureId); |
// Using GL_LINEAR because we want a linear sampling for this particular case |
// if your intention is to simply get the bitmap data out of Core Image |
// you might want to use a 1:1 rendering and GL_NEAREST |
glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_LINEAR); |
glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_LINEAR); |
glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); |
glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); |
// the GPUs like the GL_BGRA / GL_UNSIGNED_INT_8_8_8_8_REV combination |
// others are also valid, but might incur a costly software translation. |
glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_RGBA, imageRect.size.width, imageRect.size.height, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, NULL); |
// and attach texture to the FBO as its color destination |
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_RECTANGLE_ARB, FBOTextureId, 0); |
// NOTE: for this particular case we don't need a depth buffer when drawing to the FBO, |
// if you do need it, make sure you add the depth size in the pixel format, and |
// you might want to do something along the lines of: |
#if 0 |
// Initialize Depth Render Buffer |
GLuint depth_rb; |
glGenRenderbuffersEXT(1, &depth_rb); |
glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, depth_rb); |
glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, imageRect.size.width, imageRect.size.height); |
// and attach it to the FBO |
glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, depth_rb); |
#endif |
// Make sure the FBO was created succesfully. |
if (GL_FRAMEBUFFER_COMPLETE_EXT != glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT)) |
{ |
NSLog(@"Framebuffer Object creation or update failed!"); |
} |
// unbind FBO |
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); |
} |
- (void)prepareOpenGL |
{ |
// Clear to black nothing fancy. |
glClearColor(0.0f, 0.0f, 0.0f, 1.0f); |
// Setup blending function |
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); |
// Enable texturing |
glEnable(GL_TEXTURE_RECTANGLE_ARB); |
// load the image and setup the Core Image filters |
[self setupImageWithCIFilter]; |
// create an OpenGL backed CIContext |
[self createCIContext]; |
// Create FBO and attached texture |
// FBO size depends on CI image extent |
// initialized in the methods called above. |
[self setFBO]; |
// Update values and trigger initial rendering of CI to FBO |
[self sliderChanged:self]; |
} |
- (void)dealloc |
{ |
// Delete the texture |
glDeleteTextures(1, &FBOTextureId); |
// and the FBO |
glDeleteFramebuffersEXT(1, &FBOid); |
[super dealloc]; |
} |
// This method actually renders with Core Image to the |
// OpenGL managed, hardware accelerated offscreen area |
- (void)renderCoreImageToFBO |
{ |
// Update values for filters |
[hueFilter setValue: [NSNumber numberWithFloat: hueAngle] forKey: @"inputAngle"]; |
[gammaFilter setValue: [NSNumber numberWithFloat: gammaPower] forKey: @"inputPower"]; |
[gloomFilter setValue: [NSNumber numberWithFloat: gloomIntensity] forKey: @"inputIntensity"]; |
[gloomFilter setValue: [NSNumber numberWithFloat: gloomRadius] forKey: @"inputRadius"]; |
// Update images |
[hueFilter setValue:myCIImage forKey:@"inputImage"]; |
[gammaFilter setValue:[hueFilter valueForKey:@"outputImage"] forKey:@"inputImage"]; |
[gloomFilter setValue:[gammaFilter valueForKey:@"outputImage"] forKey:@"inputImage"]; |
// Bind FBO |
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, FBOid); |
// set GL state |
GLint width = (GLint)ceil(imageRect.size.width); |
GLint height = (GLint)ceil(imageRect.size.height); |
// the next few calls simply map an orthographic |
// projection or screen aligned 2D area for Core Image to |
// draw into |
{ |
glViewport(0, 0, width, height); |
glMatrixMode(GL_PROJECTION); |
glLoadIdentity(); |
glOrtho(0, width, 0, height, -1, 1); |
glMatrixMode(GL_MODELVIEW); |
glLoadIdentity(); |
glClear(GL_COLOR_BUFFER_BIT /*| GL_DEPTH_BUFFER_BIT no depth buffer */); |
} |
// Render CI |
[myCIcontext drawImage: [gloomFilter valueForKey:@"outputImage"] |
atPoint: CGPointZero fromRect: imageRect]; |
// Bind to default framebuffer (unbind FBO) |
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); |
[self setNeedsDisplay:YES]; |
} |
// Render a fake reflection |
// with 3 quads, one for the floor |
// one for the reflection and one |
// for the object to be reflected. |
- (void) renderScene |
{ |
NSRect bounds = [self bounds]; |
// Setup OpenGL with a perspective projection |
// and back to 3D stuff with the depth buffer |
{ |
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); |
glViewport(0, 0, bounds.size.width, bounds.size.height); |
glMatrixMode(GL_PROJECTION); |
glLoadIdentity(); |
gluPerspective(60.0, bounds.size.width / bounds.size.height, .1, 100.0); |
glMatrixMode(GL_TEXTURE); |
glLoadIdentity(); |
// the GL_TEXTURE_RECTANGLE_ARB doesn't use normalized coordinates |
// scale the texture matrix to "increase" the texture coordinates |
// back to the image size |
glScalef(imageRect.size.width,imageRect.size.height,1.0f); |
glMatrixMode(GL_MODELVIEW); |
glLoadIdentity(); |
glTranslatef(0.0f, 0.0f, -2.0f); |
} |
// Fake reflection below floor |
// Draw the image faintly upside down |
// Using the results from Core Image stored in the texture from the FBO |
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, FBOTextureId); |
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); |
// Using GL_MODULATE to have the quad modulate the color/intensity |
// to achieve a going darker effect at the bottom. |
// Enable blending |
glEnable(GL_BLEND); |
// Draw a quad with the correct aspect ratio of the image |
glPushMatrix(); |
{ |
glScalef(imageAspectRatio,1.0f,1.0f); |
glBegin(GL_QUADS); |
{ |
glColor4f( 0.8f, 0.8f, 0.8f, 0.8f ); |
glTexCoord2f( 1.0f, 0.0f ); glVertex2f( 0.5f, 0.0f ); |
glTexCoord2f( 0.0f, 0.0f ); glVertex2f( -0.5f, 0.0f ); |
glColor4f( 0.0f, 0.0f, 0.0f, 0.0f ); |
glTexCoord2f( 0.0f, 1.0f ); glVertex2f( -0.5, -1.0f ); |
glTexCoord2f( 1.0f, 1.0f ); glVertex2f( 0.5, -1.0f ); |
} |
glEnd(); |
} |
glPopMatrix(); |
// Floor |
// Simply draw a faint quad with light gray color |
// on one side and black on the other, rotate a little |
// to achieve the right kind of look |
glPushMatrix(); |
{ |
glRotatef(20.0f, 1.0f, 0.0f, 0.0f); |
// Don't use the texture, we want just faint fray |
glBindTexture(GL_TEXTURE_RECTANGLE_ARB,0); |
// Draw a quad |
glBegin(GL_QUADS); |
{ |
glColor4f( 0.4f, 0.4f, 0.4f, 0.4f ); |
glVertex4f( -1.5, 0.0f, 1.5f, 1.0f ); |
glVertex4f( 1.5f, 0.0f, 1.5f, 1.0f ); |
glColor4f( 0.0f, 0.0f, 0.0f, 0.0f ); |
glVertex4f( 1.5f, 0.0f, -1.5f, 1.0f ); |
glVertex4f( -1.5f, 0.0f, -1.5f, 1.0f ); |
} |
glEnd(); |
} |
glPopMatrix(); |
// Disable blending |
glDisable(GL_BLEND); |
// Object on top of floor |
// Draw the image right side up |
// again using the texture from the FBO |
glBindTexture(GL_TEXTURE_RECTANGLE_ARB,FBOTextureId); |
// Using GL_REPLACE because we want the image colors |
// unaffected by the quad color. |
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); |
// Draw a quad with the correct aspect ratio of the image |
glPushMatrix(); |
{ |
glScalef(imageAspectRatio,1.0f,1.0f); |
glBegin(GL_QUADS); |
{ |
glTexCoord2f( 1.0f, 1.0f ); glVertex2f( 0.5f, 1.0f ); |
glTexCoord2f( 0.0f, 1.0f ); glVertex2f( -0.5f, 1.0f ); |
glTexCoord2f( 0.0f, 0.0f ); glVertex2f( -0.5f, 0.0f ); |
glTexCoord2f( 1.0f, 0.0f ); glVertex2f( 0.5f, 0.0f ); |
} |
glEnd(); |
} |
glPopMatrix(); |
} |
- (void)drawRect:(NSRect)theRect |
{ |
[[self openGLContext] makeCurrentContext]; |
// Render using the resulting texture |
[self renderScene]; |
// Make sure OpenGL does it thing! |
[[self openGLContext] flushBuffer]; |
} |
// Action to receive the values from the slider on the GUI |
- (IBAction)sliderChanged: (id)sender |
{ |
hueAngle = [hueAngleSlider floatValue]*M_PI/180.0f+.001f; |
gammaPower = [gammaPowerSlider floatValue]; |
gloomRadius = [gloomRadiusSlider floatValue]; |
gloomIntensity = [gloomIntensitySlider floatValue]; |
whitePointColor = [whitePointColorWell color]; |
// Render Core Image to the FBO |
[self renderCoreImageToFBO]; |
} |
- (IBAction)updateCIImage:(id)sender |
{ |
if (myCIImage) [myCIImage release]; |
if (nil != [imageView image]) |
{ |
// Load new image |
myCIImage = [CIImage imageWithData:[[imageView image] TIFFRepresentation]]; |
} else { |
// Reload default image |
myCIImage = [CIImage imageWithContentsOfURL: [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource: @"Flowers" ofType: @"jpg"]]]; |
} |
[myCIImage retain]; |
// Update geometry |
imageRect = [myCIImage extent]; |
imageAspectRatio = imageRect.size.width / imageRect.size.height; |
// Update FBO for new size and check for correctness |
[self setFBO]; |
// Render Core Image to the FBO |
[self renderCoreImageToFBO]; |
} |
@end |
Copyright © 2008 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2008-07-30