summaryrefslogtreecommitdiff
path: root/tests/OffscreenContext.mm
blob: 9a6a5e2e7d27b3ed8fc69b57e8e6cc069134bd0e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
#include "OffscreenContext.h"

#import <OpenGL/OpenGL.h>
#import <OpenGL/glu.h>      // for gluCheckExtension
#import <AppKit/AppKit.h>   // for NSOpenGL...

// Simple error reporting macros to help keep the sample code clean
#define REPORT_ERROR_AND_EXIT(desc) { std::cout << desc << "\n"; return false; }
#define NULL_ERROR_EXIT(test, desc) { if (!test) REPORT_ERROR_AND_EXIT(desc); }

struct OffscreenContext
{
  NSOpenGLContext *openGLContext;
  NSAutoreleasePool *pool;
  int width;
  int height;
  GLuint fbo;
};


OffscreenContext *create_offscreen_context(int w, int h)
{
  OffscreenContext *ctx = new OffscreenContext;
  ctx->width = w;
  ctx->height = h;

  ctx->pool = [NSAutoreleasePool new];

  /*
   * Create an OpenGL context just so that OpenGL calls will work. I'm not 
   using it for actual rendering.
  */
                                   
  NSOpenGLPixelFormatAttribute    attributes[] = {
    NSOpenGLPFAPixelBuffer,
    NSOpenGLPFANoRecovery,
    NSOpenGLPFAAccelerated,
    NSOpenGLPFADepthSize, 24,
    (NSOpenGLPixelFormatAttribute) 0
  };
  NSOpenGLPixelFormat*            pixFormat = [[[NSOpenGLPixelFormat 
                                                 alloc] initWithAttributes:attributes] autorelease];
  // Create the OpenGL context to render with (with color and depth buffers)
  ctx->openGLContext = [[NSOpenGLContext alloc] initWithFormat:pixFormat 
                   shareContext:nil];
  NULL_ERROR_EXIT(ctx->openGLContext, "Unable to create NSOpenGLContext");

  [ctx->openGLContext makeCurrentContext];

  /*
   * Test if framebuffer objects are supported
   */
  const GLubyte* strExt = glGetString(GL_EXTENSIONS);
  GLboolean fboSupported = gluCheckExtension((const GLubyte*)"GL_EXT_framebuffer_object", strExt);
  if (!fboSupported)
    REPORT_ERROR_AND_EXIT("Your system does not support framebuffer extension - unable to render scene");
  /*
   * Create an FBO
   */
  GLuint renderBuffer = 0;
  GLuint depthBuffer = 0;
  // Depth buffer to use for depth testing - optional if you're not using depth testing
  glGenRenderbuffersEXT(1, &depthBuffer);
  glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, depthBuffer);
  glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT24,  w, h);
  REPORTGLERROR("creating depth render buffer");

  // Render buffer to use for imaging
  glGenRenderbuffersEXT(1, &renderBuffer);
  glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, renderBuffer);
  glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_RGBA8, w, h);
  REPORTGLERROR("creating color render buffer");
  ctx->fbo = 0;
  glGenFramebuffersEXT(1, &ctx->fbo);
  glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, ctx->fbo);
  REPORTGLERROR("binding framebuffer");

  glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, 
                               GL_RENDERBUFFER_EXT, renderBuffer);
  REPORTGLERROR("specifying color render buffer");

  if (glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) != 
      GL_FRAMEBUFFER_COMPLETE_EXT)
    REPORT_ERROR_AND_EXIT("Problem with OpenGL framebuffer after specifying color render buffer.");

  glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, 
                               GL_RENDERBUFFER_EXT, depthBuffer);
  REPORTGLERROR("specifying depth render buffer");

  if (glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) != 
      GL_FRAMEBUFFER_COMPLETE_EXT)
    REPORT_ERROR_AND_EXIT("Problem with OpenGL framebuffer after specifying depth render buffer.");

  return ctx;
}

bool teardown_offscreen_context(OffscreenContext *ctx)
{
  // "un"bind my FBO
  glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);

  /*
   * Cleanup
   */
  [ctx->openGLContext clearDrawable];
  [ctx->openGLContext release];

  [ctx->pool release];
  return true;
}

bool save_framebuffer(OffscreenContext *ctx, const char *filename)
{
  /*
   * Extract the resulting rendering as an image
   */

  int samplesPerPixel = 4; // R, G, B and A
  int rowBytes = samplesPerPixel * ctx->width;
  char* bufferData = (char*)malloc(rowBytes * ctx->height);
  if (!bufferData) {
    std::cerr << "Unable to allocate buffer for image extraction.";
    return 1;
  }
  glReadPixels(0, 0, ctx->width, ctx->height, GL_BGRA, GL_UNSIGNED_BYTE, 
               bufferData);
  REPORTGLERROR("reading pixels from framebuffer");
  
  // Flip it vertically - images read from OpenGL buffers are upside-down
  char* flippedBuffer = (char*)malloc(rowBytes * ctx->height);
  if (!flippedBuffer) {
    std::cout << "Unable to allocate flipped buffer for corrected image.";
    return 1;
  }
  for (int i = 0 ; i < ctx->height ; i++) {
    bcopy(bufferData + i * rowBytes, flippedBuffer + (ctx->height - i - 1) * 
          rowBytes, rowBytes);
  }

  /*
   * Output the image to a file
   */
  CGColorSpaceRef colorSpace = 
    CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
  CGBitmapInfo bitmapInfo = kCGImageAlphaNoneSkipFirst | 
    kCGBitmapByteOrder32Little;  // XRGB Little Endian
  int bitsPerComponent = 8;
  CGContextRef contextRef = CGBitmapContextCreate(flippedBuffer,
                                                  ctx->width, ctx->height, bitsPerComponent, rowBytes, 
                                                  colorSpace, bitmapInfo);
  if (!contextRef) {
    std::cerr << "Unable to create CGContextRef.";
    return false;
  }

  CGImageRef imageRef = CGBitmapContextCreateImage(contextRef);
  if (!imageRef) {
    std::cerr <<  "Unable to create CGImageRef.";
    return false;
  }
  Boolean isDirectory = false;
  CFStringRef fname = CFStringCreateWithCString(kCFAllocatorDefault, filename, kCFStringEncodingUTF8);
  CFURLRef fileURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault,
                                                   fname, kCFURLPOSIXPathStyle, isDirectory);
  if (!fileURL) {
    std::cerr << "Unable to create file URL ref.";
    return false;
  }
  CGDataConsumerRef dataconsumer = CGDataConsumerCreateWithURL(fileURL);

  CFIndex                 fileImageIndex = 1;
  CFMutableDictionaryRef  fileDict       = NULL;
  CFStringRef             fileUTType     = kUTTypePNG;
  // Create an image destination opaque reference for authoring an image file
  CGImageDestinationRef imageDest = CGImageDestinationCreateWithDataConsumer(dataconsumer,
                                                                             fileUTType, 
                                                                             fileImageIndex, 
                                                                             fileDict);
  if (!imageDest) {
    std::cerr <<  "Unable to create CGImageDestinationRef.";
    return false;
  }
  CFIndex capacity = 1;
  CFMutableDictionaryRef imageProps = 
    CFDictionaryCreateMutable(kCFAllocatorDefault, 
                              capacity,
                              &kCFTypeDictionaryKeyCallBacks,
                              &kCFTypeDictionaryValueCallBacks);
  CGImageDestinationAddImage(imageDest, imageRef, imageProps);
  CGImageDestinationFinalize(imageDest);

  free(flippedBuffer);
  free(bufferData);
  CFRelease(imageDest);
  CFRelease(dataconsumer);
  CFRelease(fileURL);
  CFRelease(fname);
  CFRelease(imageProps);
  CGColorSpaceRelease(colorSpace);
  CGImageRelease(imageRef);
  return true;
}

void bind_offscreen_context(OffscreenContext *ctx)
{
  glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, ctx->fbo);
}
contact: Jan Huwald // Impressum