mirror of https://github.com/vitalif/openscad
208 lines
7.2 KiB
Plaintext
208 lines
7.2 KiB
Plaintext
#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);
|
|
}
|