src/gl/OffscreenContextCGL.mm

    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
    
    #include "OffscreenContext.h"
    #include "imageutils.h"
    #include "fbo.h"
    #include <iostream>
    #include <sstream>
    
    #import <AppKit/AppKit.h>   // for NSOpenGL...
    #include <CoreServices/CoreServices.h>
    #include <sys/utsname.h>
    
    #define REPORTGLERROR(task) { GLenum tGLErr = glGetError(); if (tGLErr != GL_NO_ERROR) { std::cout << "OpenGL error " << tGLErr << " while " << task << "\n"; } }
    
    struct OffscreenContext
    {
      NSOpenGLContext *openGLContext;
      NSAutoreleasePool *pool;
      int width;
      int height;
      fbo_t *fbo;
    };
    
    #include "OffscreenContextAll.hpp"
    
    std::string offscreen_context_getinfo(OffscreenContext *)
    {
      std::stringstream out;
    
      struct utsname name;
      uname(&name);
    
      SInt32 majorVersion,minorVersion,bugFixVersion;
    
      Gestalt(gestaltSystemVersionMajor, &majorVersion);
      Gestalt(gestaltSystemVersionMinor, &minorVersion);
      Gestalt(gestaltSystemVersionBugFix, &bugFixVersion);
    
      const char *arch = "unknown";
      if (sizeof(int*) == 4) arch = "32-bit";
      else if (sizeof(int*) == 8) arch = "64-bit";
    
      out << "GL context creator: Cocoa / CGL\n"
          << "PNG generator: Core Foundation\n"
          << "OS info: Mac OS X " << majorVersion << "." << minorVersion << "." << bugFixVersion << " (" << name.machine << " kernel)\n"
          << "Machine: " << arch << "\n";
      return out.str();
    }
    
    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. 
      // Will not be used for actual rendering.
    
      NSOpenGLPixelFormatAttribute attributes[] = {
        NSOpenGLPFANoRecovery,
        NSOpenGLPFADepthSize, 24,
        NSOpenGLPFAStencilSize, 8,
    // Enable this to force software rendering
    // NSOpenGLPFARendererID, kCGLRendererGenericID,
    // Took out the acceleration requirement to be able to run the tests
    // in a non-accelerated VM.
    // NSOpenGLPFAAccelerated,
        (NSOpenGLPixelFormatAttribute) 0
      };
      NSOpenGLPixelFormat *pixFormat = [[[NSOpenGLPixelFormat alloc] initWithAttributes:attributes] autorelease];
    
      // Create and make current the OpenGL context to render with (with color and depth buffers)
      ctx->openGLContext = [[NSOpenGLContext alloc] initWithFormat:pixFormat shareContext:nil];
      if (!ctx->openGLContext) {
        std::cerr << "Unable to create NSOpenGLContext\n";
        return NULL;
      }
    
      [ctx->openGLContext makeCurrentContext];
    
      // glewInit must come after Context creation and before FBO calls.
      GLenum err = glewInit();
      if (GLEW_OK != err) {
        std::cerr << "Unable to init GLEW: " << glewGetErrorString(err) << std::endl;
        return NULL;
      }
      glew_dump();
    
      ctx->fbo = fbo_new();
      if (!fbo_init(ctx->fbo, w, h)) {
        return NULL;
      }
    
      return ctx;
    }
    
    bool teardown_offscreen_context(OffscreenContext *ctx)
    {
      fbo_unbind(ctx->fbo);
      fbo_delete(ctx->fbo);
    
      /*
       * Cleanup
       */
      [ctx->openGLContext clearDrawable];
      [ctx->openGLContext release];
    
      [ctx->pool release];
      return true;
    }
    
    /*!
      Capture framebuffer from OpenGL and write it to the given ostream
    */
    bool save_framebuffer(OffscreenContext *ctx, std::ostream &output)
    {
      if (!ctx) return false;
      // Read pixels from OpenGL
      int samplesPerPixel = 4; // R, G, B and A
      int rowBytes = samplesPerPixel * ctx->width;
      unsigned char *bufferData = (unsigned 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_RGBA, GL_UNSIGNED_BYTE, 
                   bufferData);
      REPORTGLERROR("reading pixels from framebuffer");
    
      // Flip it vertically - images read from OpenGL buffers are upside-down
      unsigned char *flippedBuffer = (unsigned char *)malloc(rowBytes * ctx->height);
      if (!flippedBuffer) {
        std::cout << "Unable to allocate flipped buffer for corrected image.";
        return 1;
      }
      flip_image(bufferData, flippedBuffer, samplesPerPixel, ctx->width, ctx->height);
    
      bool writeok = write_png(output, flippedBuffer, ctx->width, ctx->height);
    
      free(flippedBuffer);
      free(bufferData);
    
      return writeok;
    }