Files
FreeJ/xcode/CVScreenView.mm
Xant 744b24a9ba fixed perl-layer overlay selection
- filters now works again for CVF0rLayer
2009-09-13 11:22:58 +02:00

626 lines
21 KiB
Plaintext

/* FreeJ
* (c) Copyright 2009 Andrea Guzzo <xant@dyne.org>
*
* This source code is free software; you can redistribute it and/or
* modify it under the terms of the GNU Public License as published
* by the Free Software Foundation; either version 3 of the License,
* or (at your option) any later version.
*
* This source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* Please refer to the GNU Public License for more details.
*
* You should have received a copy of the GNU Public License along with
* this source code; if not, write to:
* Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
*/
#import <CVScreenView.h>
#include <CVLayer.h>
#define _BGRA2ARGB(__buf, __size) \
{\
long *__bgra = (long *)__buf;\
for (int __i = 0; __i < __size; __i++)\
__bgra[__i] = ntohl(__bgra[__i]);\
}
static CVReturn renderCallback(CVDisplayLinkRef displayLink,
const CVTimeStamp *inNow,
const CVTimeStamp *inOutputTime,
CVOptionFlags flagsIn,
CVOptionFlags *flagsOut,
void *displayLinkContext)
{
CVReturn ret = [(CVScreenView*)displayLinkContext outputFrame:inNow->hostTime];
return ret;
}
@implementation CVScreenView : NSOpenGLView
/*
- (void)windowChangedSize:(NSNotification*)inNotification
{
// NSRect frame = [self frame];
// [self setSizeWidth:frame.size.width Height:frame.size.height];
}
*/
- (void)awakeFromNib
{
[self init];
}
- (void)start
{
Context *ctx = [freej getContext];
CVScreen *screen = (CVScreen *)ctx->screen;
screen->set_view(self);
CVDisplayLinkStart(displayLink);
[layerList setDataSource:(id)self];
[layerList registerForDraggedTypes:[NSArray arrayWithObject:@"CVLayer"]];
}
- (id)init
{
needsReshape = YES;
outFrame = NULL;
lastFrame = NULL;
exportedFrame = NULL;
lock = [[NSRecursiveLock alloc] init];
[lock retain];
[self setNeedsDisplay:NO];
[freej start];
Context *ctx = (Context *)[freej getContext];
fjScreen = (CVScreen *)ctx->screen;
CVReturn err = CVPixelBufferCreate (
NULL,
fjScreen->geo.w,
fjScreen->geo.h,
k32ARGBPixelFormat,
NULL,
&pixelBuffer
);
if (err) {
// TODO - Error messages
return nil;
}
CVPixelBufferLockBaseAddress(pixelBuffer, NULL);
exportBuffer = CVPixelBufferGetBaseAddress(pixelBuffer);
CGColorSpaceRef colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
exportCGContextRef = CGBitmapContextCreate (NULL,
ctx->screen->geo.w,
ctx->screen->geo.h,
8, // bits per component
ctx->screen->geo.w*4,
colorSpace,
kCGImageAlphaPremultipliedLast);
if (exportCGContextRef == NULL)
NSLog(@"Context not created!");
exportContext = [[CIContext contextWithCGContext:exportCGContextRef
options:[NSDictionary dictionaryWithObject: (NSString*) kCGColorSpaceGenericRGB
forKey: kCIContextOutputColorSpace]] retain];
CGColorSpaceRelease( colorSpace );
exporter = [[[QTExporter alloc] init] retain];
return self;
}
- (void)dealloc
{
CVPixelBufferUnlockBaseAddress(pixelBuffer, NULL);
CVOpenGLTextureRelease(pixelBuffer);
[ciContext release];
[currentContext release];
if (outFrame)
[outFrame release];
if (lastFrame)
[lastFrame release];
[lock release];
[exportContext release];
CGContextRelease( exportCGContextRef );
[super dealloc];
}
- (void)prepareOpenGL
{
CVReturn ret;
// Create display link
CGOpenGLDisplayMask totalDisplayMask = 0;
int virtualScreen;
GLint displayMask;
NSOpenGLPixelFormat *openGLPixelFormat = [self pixelFormat];
// we start with our view on the main display
// build up list of displays from OpenGL's pixel format
viewDisplayID = (CGDirectDisplayID)[[[[[self window] screen] deviceDescription] objectForKey:@"NSScreenNumber"] intValue];
for (virtualScreen = 0; virtualScreen < [openGLPixelFormat numberOfVirtualScreens]; virtualScreen++)
{
[openGLPixelFormat getValues:&displayMask forAttribute:NSOpenGLPFAScreenMask forVirtualScreen:virtualScreen];
totalDisplayMask |= displayMask;
}
ret = CVDisplayLinkCreateWithCGDisplay(viewDisplayID, &displayLink);
currentContext = [[self openGLContext] retain];
// Create CGColorSpaceRef
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
// Create CIContext
ciContext = [[CIContext contextWithCGLContext:(CGLContextObj)[currentContext CGLContextObj]
pixelFormat:(CGLPixelFormatObj)[[self pixelFormat] CGLPixelFormatObj]
options:[NSDictionary dictionaryWithObjectsAndKeys:
(id)colorSpace,kCIContextOutputColorSpace,
(id)colorSpace,kCIContextWorkingColorSpace,nil]] retain];
CGColorSpaceRelease(colorSpace);
//[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(windowChangedSize:) name:NSWindowDidResizeNotification object:nil];
rateCalc = [[FrameRate alloc] init];
[rateCalc retain];
GLint params[] = { 1 };
CGLSetParameter( CGLGetCurrentContext(), kCGLCPSwapInterval, params );
// Set up display link callbacks
CVDisplayLinkSetOutputCallback(displayLink, renderCallback, self);
// start asking for frames
[self start];
}
- (void) update
{
if( kCGLNoError != CGLLockContext((CGLContextObj)[[self openGLContext] CGLContextObj]) )
return;
[super update];
CGLUnlockContext((CGLContextObj)[[self openGLContext] CGLContextObj]);
}
- (void)drawRect:(NSRect)theRect
{
NSRect frame = [self frame];
NSRect bounds = [self bounds];
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
if( kCGLNoError != CGLLockContext((CGLContextObj)[[self openGLContext] CGLContextObj]) )
return;
[currentContext makeCurrentContext];
if(needsReshape) // if the view has been resized, reset the OpenGL coordinate system
{
GLfloat minX, minY, maxX, maxY;
minX = NSMinX(bounds);
minY = NSMinY(bounds);
maxX = NSMaxX(bounds);
maxY = NSMaxY(bounds);
//[self update];
if(NSIsEmptyRect([self visibleRect]))
{
glViewport(0, 0, 1, 1);
} else {
glViewport(0, 0, frame.size.width ,frame.size.height);
}
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(minX, maxX, minY, maxY, -1.0, 1.0);
glDisable(GL_DITHER);
glDisable(GL_ALPHA_TEST);
glDisable(GL_BLEND);
glDisable(GL_STENCIL_TEST);
glDisable(GL_FOG);
//glDisable(GL_TEXTURE_2D);
glDisable(GL_DEPTH_TEST);
glPixelZoom(1.0,1.0);
// clean the OpenGL context
glClearColor(0.0, 0.0, 0.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT);
needsReshape = NO;
}
// flush our output to the screen - this will render with the next beamsync
[[self openGLContext] flushBuffer];
//[super drawRect:theRect];
[self setNeedsDisplay:NO];
CGLUnlockContext((CGLContextObj)[[self openGLContext] CGLContextObj]);
[pool release];
}
- (CIImage *)exportSurface
{
CIImage *exportedSurface = nil;
void *surface = [self getSurface];
if (surface) {
CVPixelBufferRef pixelBufferOut;
//_BGRA2ARGB(layer->buffer, layer->geo.w*layer->geo.h); // XXX - expensive conversion
CVReturn cvRet = CVPixelBufferCreateWithBytes (
NULL,
fjScreen->geo.w,
fjScreen->geo.h,
k32ARGBPixelFormat,
surface,
fjScreen->geo.w*4,
NULL,
NULL,
NULL,
&pixelBufferOut
);
if (cvRet != noErr) {
// TODO - Error Messages
}
exportedSurface = [CIImage imageWithCVImageBuffer:pixelBufferOut];
CVPixelBufferRelease(pixelBufferOut);
}
return exportedSurface;
}
- (void *)getSurface
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[lock lock];
Context *ctx = (Context *)[freej getContext];
if (lastFrame) {
NSRect bounds = [self bounds];
CGRect rect = CGRectMake(0,0, ctx->screen->geo.w, ctx->screen->geo.h);
[exportContext render:lastFrame
toBitmap:exportBuffer
rowBytes:ctx->screen->geo.w*4
bounds:rect
format:kCIFormatARGB8
colorSpace:NULL];
}
[lock unlock];
[pool release];
return exportBuffer;
}
- (CVReturn)outputFrame:(uint64_t)timestamp
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
//CVTexture *textureToRelease = nil;
NSRect bounds = [self bounds];
//[lock lock];
Context *ctx = (Context *)[freej getContext];
ctx->cafudda(0.0);
if (exporter && [exporter isRunning]) {
CVTimeStamp *exportTimestamp = (CVTimeStamp *)malloc(sizeof(CVTimeStamp));
CVDisplayLinkGetCurrentTime(displayLink, exportTimestamp);
[exporter addImage:[self exportSurface] atTime:exportTimestamp];
}
if (rateCalc) {
[rateCalc tick:timestamp];
fpsString = [[NSString alloc] initWithFormat:@"%0.1lf", [rateCalc rate]];
[fpsString autorelease];
[showFps setStringValue:fpsString];
}
if( kCGLNoError != CGLLockContext((CGLContextObj)[[self openGLContext] CGLContextObj]) )
return kCVReturnError;
if (outFrame) {
CGRect cg = CGRectMake(NSMinX(bounds), NSMinY(bounds),
NSWidth(bounds), NSHeight(bounds));
[ciContext drawImage: outFrame
atPoint: cg.origin fromRect: cg];
if (lastFrame)
[lastFrame release];
lastFrame = outFrame;
outFrame = NULL;
} else {
needsReshape = YES;
}
CGLUnlockContext((CGLContextObj)[[self openGLContext] CGLContextObj]);
// XXX - we force rendering at this stage instead of delaying it setting the needsDisplay flag
// to avoid stopping displaying new frames on the output screen while system animations are in progress
// (for example, when opening a fileselction panel the CVScreen rendering would stop while the animation is in progress
// because both the actions would happen in the main application thread. Forcing rendering now makes it happen in the
// CVScreen thread
//[self setNeedsDisplay:YES]; // this will delay rendering to be done the application main thread
[self drawRect:NSZeroRect]; // this directly render the frame out in this thread
//[lock unlock];
[pool release];
return kCVReturnSuccess;
}
- (void)drawLayer:(Layer *)layer
{
//NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
CIFilter *blendFilter = nil;
CVTexture *texture = nil;
if (layer->type == Layer::GL_COCOA) {
CVLayer *cvLayer = (CVLayer *)layer;
texture = cvLayer->gl_texture();
NSString *blendMode = ((CVLayer *)layer)->blendMode;
if (blendMode)
blendFilter = [CIFilter filterWithName:[[NSString alloc] initWithFormat:@"CI%@BlendMode", blendMode]];
else
blendFilter = [CIFilter filterWithName:@"CIOverlayBlendMode"];
} else { // freej 'not-cocoa' layer type
CVPixelBufferRef pixelBufferOut;
//_BGRA2ARGB(layer->buffer, layer->geo.w*layer->geo.h); // XXX - expensive conversion
CVReturn cvRet = CVPixelBufferCreateWithBytes (
NULL,
layer->geo.w,
layer->geo.h,
k32ARGBPixelFormat,
layer->buffer,
layer->geo.w*4,
NULL,
NULL,
NULL,
&pixelBufferOut
);
if (cvRet != noErr) {
// TODO - Error Messages
}
CIImage *inputImage = [CIImage imageWithCVImageBuffer:pixelBufferOut];
texture = [[CVTexture alloc] initWithCIImage:inputImage pixelBuffer:pixelBufferOut];
// we can release our reference to the pixelBuffer now.
// The CVTexture will retain it as long as it is needed
CVPixelBufferRelease(pixelBufferOut);
blendFilter = [CIFilter filterWithName:@"CIOverlayBlendMode"];
}
[blendFilter setDefaults];
if (texture) {
if (!outFrame) {
outFrame = [[texture image] retain];
} else {
[blendFilter setValue:outFrame forKey:@"inputBackgroundImage"];
[blendFilter setValue:[texture image] forKey:@"inputImage"];
CIImage *temp = [blendFilter valueForKey:@"outputImage"];
[outFrame autorelease];
outFrame = [temp retain];
}
[texture autorelease];
}
}
- (void)setSizeWidth:(int)w Height:(int)h
{
[lock lock];
if (w != fjScreen->geo.w || h != fjScreen->geo.h) {
CVPixelBufferRelease(pixelBuffer);
CVReturn err = CVOpenGLBufferCreate (NULL, fjScreen->geo.w, fjScreen->geo.h, NULL, &pixelBuffer);
if (err != noErr) {
// TODO - Error Messages
}
CVPixelBufferRetain(pixelBuffer);
//pixelBuffer = realloc(pixelBuffer, w*h*4);
fjScreen->geo.w = w;
fjScreen->geo.h = h;
needsReshape = YES;
NSRect frame = [window frame];
frame.size.width = w;
frame.size.height = h;
[window setFrame:frame display:YES];
fjScreen->resize_w = w;
fjScreen->resize_h = h;
}
[lock unlock];
}
- (IBAction)toggleFullScreen:(id)sender
{
CGDirectDisplayID currentDisplayID = (CGDirectDisplayID)[[[[[self window] screen] deviceDescription] objectForKey:@"NSScreenNumber"] intValue];
if (fullScreen) {
CGDisplaySwitchToMode(currentDisplayID, savedMode);
SetSystemUIMode(kUIModeNormal, kUIOptionAutoShowMenuBar);
[self retain];
NSWindow *fullScreenWindow = [self window];
[self removeFromSuperview];
[myWindow setContentView:self];
[myWindow release];
[self release];
[fullScreenWindow release];
fullScreen = NO;
needsReshape = YES;
} else {
CFDictionaryRef newMode = CGDisplayBestModeForParameters(currentDisplayID, 32, fjScreen->geo.w, fjScreen->geo.h, 0);
NSAssert(newMode, @"Couldn't find display mode");
myWindow = [[self window] retain];
savedMode = CGDisplayCurrentMode(currentDisplayID);
SetSystemUIMode(kUIModeAllHidden, kUIOptionAutoShowMenuBar);
CGDisplaySwitchToMode(currentDisplayID, newMode);
NSScreen *screen = [[self window] screen];
NSWindow *newWindow = [[NSWindow alloc] initWithContentRect:[screen frame]
styleMask:NSBorderlessWindowMask
backing:NSBackingStoreBuffered
defer:NO
screen:screen];
[self retain];
[self removeFromSuperview];
[newWindow setContentView:self];
[newWindow setFrameOrigin:[screen frame].origin];
[self release];
[newWindow makeKeyAndOrderFront:sender];
[NSCursor setHiddenUntilMouseMoves:YES];
fullScreen = YES;
}
[self drawRect:NSZeroRect];
}
- (IBAction)startExport:(id)sender
{
if (exporter)
if ([exporter startExport])
[sender setTitle:@"Stop"];
}
- (IBAction)stopExport:(id)sender
{
if (exporter)
[exporter stopExport];
[sender setTitle:@"Start"];
}
- (IBAction)toggleExport:(id)sender
{
if (exporter) {
if ([exporter isRunning])
[self stopExport:sender];
else
[self startExport:sender];
}
}
- (void)setExportFileDidEnd:(NSOpenPanel *)panel returnCode:(int)returnCode contextInfo:(void *)contextInfo
{
if(returnCode == NSOKButton){
func("openFilePanel: OK");
} else if(returnCode == NSCancelButton) {
func("openFilePanel: Cancel");
return;
} else {
error("openFilePanel: Error %3d",returnCode);
return;
} // end if
NSString * tvarDirectory = [panel directory];
func("openFile directory = %@",tvarDirectory);
NSString * tvarFilename = [panel filename];
func("openFile filename = %@",tvarFilename);
if (tvarFilename) {
[exporter setOutputFile:tvarFilename];
[(NSTextField *)[(id)contextInfo nextKeyView] setStringValue:tvarFilename];
}
}
- (IBAction)setExportFile:(id)sender
{
NSSavePanel *fileSelectionPanel = [NSSavePanel savePanel];
[fileSelectionPanel
beginSheetForDirectory:nil
file:nil
modalForWindow:[sender window]
modalDelegate:self
didEndSelector:@selector(setExportFileDidEnd: returnCode: contextInfo:)
contextInfo:sender];
}
- (bool)isOpaque
{
return YES;
}
- (double)rate
{
if (rateCalc)
return [rateCalc rate];
return 0;
}
- (void)reset
{
needsReshape = YES;
[self drawRect:NSZeroRect];
}
- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView
{
return fjScreen->layers.length;
}
- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
{
Layer *lay = fjScreen->layers.pick(rowIndex+1);
if (lay)
return [NSString stringWithUTF8String:lay->name];
return nil;
}
- (NSArray *)tableView:(NSTableView *)aTableView namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination forDraggedRowsWithIndexes:(NSIndexSet *)indexSet
{
return [NSArray arrayWithObjects:@"CVLayer", @"CVLayerIndex", nil];
}
- (BOOL)tableView:(NSTableView *)aTableView writeRowsWithIndexes:(NSIndexSet *)rowIndexes toPasteboard:(NSPasteboard *)pboard
{
NSUInteger row = [rowIndexes firstIndex];
Layer *lay = fjScreen->layers.pick(row+1);
if (lay) {
[pboard addTypes:[NSArray arrayWithObjects:@"CVLayer", @"CVLayerIndex", nil] owner:(id)self];
[pboard setData:[NSData dataWithBytes:(void *)&lay length:sizeof(Layer *)] forType:@"CVLayer"];
[pboard setData:[NSData dataWithBytes:(void *)&row length:sizeof(NSUInteger)] forType:@"CVLayerIndex"];
return YES;
}
return NO;
}
- (NSDragOperation)tableView:(NSTableView *)aTableView validateDrop:(id < NSDraggingInfo >)info proposedRow:(NSInteger)row proposedDropOperation:(NSTableViewDropOperation)operation
{
NSDragOperation dragOp = NSDragOperationCopy;
if ([info draggingSource] == layerList) {
dragOp = NSDragOperationMove;
}
[aTableView setDropRow:row dropOperation:NSTableViewDropAbove];
return dragOp;
}
- (BOOL)tableView:(NSTableView *)aTableView acceptDrop:(id < NSDraggingInfo >)info row:(NSInteger)row dropOperation:(NSTableViewDropOperation)operation
{
Layer *lay = NULL;
NSUInteger srcRow;
[[[info draggingPasteboard] dataForType:@"CVLayerIndex"] getBytes:&srcRow length:sizeof(NSUInteger)];
[[[info draggingPasteboard] dataForType:@"CVLayer"] getBytes:&lay length:sizeof(Layer *)];
if (lay) {
lay->move((srcRow > row)?(row+1):row);
[layerList reloadData];
return YES;
}
return NO;
}
- (void)addLayer:(Layer *)lay
{
[layerList reloadData];
}
- (void)remLayer:(Layer *)lay
{
[layerList reloadData];
}
- (NSWindow *)getWindow
{
return window;
}
@synthesize fullScreen;
@end