summaryrefslogtreecommitdiff
path: root/src/video_out/macosx/XineOpenGLView.m
diff options
context:
space:
mode:
Diffstat (limited to 'src/video_out/macosx/XineOpenGLView.m')
-rw-r--r--src/video_out/macosx/XineOpenGLView.m755
1 files changed, 755 insertions, 0 deletions
diff --git a/src/video_out/macosx/XineOpenGLView.m b/src/video_out/macosx/XineOpenGLView.m
new file mode 100644
index 000000000..d8155dd55
--- /dev/null
+++ b/src/video_out/macosx/XineOpenGLView.m
@@ -0,0 +1,755 @@
+/*
+ * Copyright (C) 2004 the xine project
+ *
+ * This file is part of xine, a free video player.
+ *
+ * xine is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * xine 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. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ *
+ */
+
+
+/*
+#define LOG
+ */
+
+#import <Cocoa/Cocoa.h>
+#import <OpenGL/OpenGL.h>
+#import <OpenGL/gl.h>
+#import <OpenGL/glext.h>
+
+#import "XineOpenGLView.h"
+
+
+NSString *XineViewDidResizeNotification = @"XineViewDidResizeNotification";
+
+
+/* XineOpenGLView delegate methods */
+
+@protocol XineOpenGLViewDelegate
+
+- (void) mouseDown:(NSEvent *)theEvent inXineView:(XineOpenGLView *)theView;
+- (void) mouseMoved:(NSEvent *)theEvent inXineView:(XineOpenGLView *)theView;
+- (void) otherMouseDown:(NSEvent *)theEvent inXineView:(XineOpenGLView *)theView;
+- (void) rightMouseDown:(NSEvent *)theEvent inXineView:(XineOpenGLView *)theView;
+- (NSSize) xineViewWillResize:(NSSize)oldSize toSize:(NSSize)proposedSize;
+- (void) xineViewDidResize:(NSNotification *)note;
+
+@end
+
+
+@implementation XineOpenGLView
+
+- (void) setKeepsVideoAspectRatio:(BOOL)flag
+{
+ keepsVideoAspectRatio = flag;
+}
+
+- (BOOL) keepsVideoAspectRatio
+{
+ return keepsVideoAspectRatio;
+}
+
+- (void) setResizeViewOnVideoSizeChange:(BOOL)flag
+{
+ resizeViewOnVideoSizeChange = flag;
+}
+
+- (BOOL) resizeViewOnVideoSizeChange
+{
+ return resizeViewOnVideoSizeChange;
+}
+
+- (BOOL)mouseDownCanMoveWindow
+{
+ return YES;
+}
+
+- (void)passEventToDelegate:(NSEvent *)theEvent withSelector:(SEL)selector
+{
+ NSPoint point = [self convertPoint:[theEvent locationInWindow]
+ fromView:nil];
+
+ if (!NSMouseInRect(point, [self bounds], [self isFlipped])) return;
+
+ if ([delegate respondsToSelector:selector])
+ {
+ [delegate performSelector:selector
+ withObject:theEvent
+ withObject:self];
+ return;
+ }
+
+ if ([_xineController respondsToSelector:selector])
+ {
+ [_xineController performSelector:selector
+ withObject:theEvent
+ withObject:self];
+ return;
+ }
+}
+
+- (void)mouseMoved:(NSEvent *)theEvent
+{
+ [self passEventToDelegate:theEvent
+ withSelector:@selector(mouseMoved:inXineView:)];
+
+ [super mouseMoved:theEvent];
+}
+
+- (void)mouseDown:(NSEvent *)theEvent
+{
+ [self passEventToDelegate:theEvent
+ withSelector:@selector(mouseDown:inXineView:)];
+
+ [super mouseDown:theEvent];
+}
+
+- (void)rightMouseDown:(NSEvent *)theEvent
+{
+ [self passEventToDelegate:theEvent
+ withSelector:@selector(rightMouseDown:inXineView:)];
+
+ [super rightMouseDown:theEvent];
+}
+
+- (void)otherMouseDown:(NSEvent *)theEvent
+{
+ [self passEventToDelegate:theEvent
+ withSelector:@selector(otherMouseDown:inXineView:)];
+
+ [super otherMouseDown:theEvent];
+}
+
+- (NSSize)videoSize
+{
+ return NSMakeSize(video_width, video_height);
+}
+
+- (void) displayTexture {
+ if ([self lockFocusIfCanDraw])
+ {
+ [self drawRect: [self bounds]];
+ [self reloadTexture];
+ [self unlockFocus];
+ }
+}
+
+- (id) initWithFrame: (NSRect) frame
+{
+ NSOpenGLPixelFormatAttribute attribs[] = {
+ NSOpenGLPFAAccelerated,
+ NSOpenGLPFANoRecovery,
+ NSOpenGLPFADoubleBuffer,
+ NSOpenGLPFAColorSize, 24,
+ NSOpenGLPFAAlphaSize, 8,
+ NSOpenGLPFADepthSize, 24,
+ NSOpenGLPFAWindow,
+ 0
+ };
+
+ NSOpenGLPixelFormat * fmt = [[NSOpenGLPixelFormat alloc]
+ initWithAttributes: attribs];
+
+ if (!fmt)
+ {
+ NSLog (@"Cannot create NSOpenGLPixelFormat\n");
+ return nil;
+ }
+
+ self = [super initWithFrame:frame pixelFormat:fmt];
+
+ currentContext = [self openGLContext];
+ [currentContext makeCurrentContext];
+ [mutex lock];
+ [currentContext update];
+ [mutex unlock];
+
+ i_texture = 0;
+ initDone = NO;
+ isFullScreen = NO;
+ video_width = frame.size.width;
+ video_height = frame.size.height;
+ texture_buffer = nil;
+ mutex = [[NSLock alloc] init];
+ currentCursor = [NSCursor arrowCursor];
+ _xineController = nil;
+
+ [self initTextures];
+
+ /* Black background */
+ glClearColor (0.0, 0.0, 0.0, 0.0);
+
+#ifdef LOG
+ NSLog(@"XineOpenGLView: initWithFrame called");
+#endif
+
+ return self;
+}
+
+- (id) initWithCoder:(NSCoder *)coder
+{
+#ifdef LOG
+ NSLog(@"XineOpenGLView: initWithCoder called");
+#endif
+
+ self = [super initWithCoder:coder];
+
+ self = [self initWithFrame:[self frame]];
+
+ if ([coder allowsKeyedCoding])
+ {
+ keepsVideoAspectRatio = [coder decodeBoolForKey:@"keepsVideoAspectRatio"];
+ resizeViewOnVideoSizeChange = [coder decodeBoolForKey:
+ @"resizeViewOnVideoSizeChange"];
+ }
+ else
+ {
+ /* Must decode values in the same order as encodeWithCoder: */
+ [coder decodeValueOfObjCType:@encode(BOOL) at:&keepsVideoAspectRatio];
+ [coder decodeValueOfObjCType:@encode(BOOL) at:&resizeViewOnVideoSizeChange];
+ }
+
+ return self;
+}
+
+- (void) encodeWithCoder:(NSCoder *)coder
+{
+ [super encodeWithCoder:coder];
+
+ if ([coder allowsKeyedCoding])
+ {
+ [coder encodeBool:keepsVideoAspectRatio forKey:@"keepsVideoAspectRatio"];
+ [coder encodeBool:resizeViewOnVideoSizeChange
+ forKey:@"resizeViewOnVideoSizeChange"];
+ }
+ else
+ {
+ [coder encodeValueOfObjCType:@encode(BOOL) at:&keepsVideoAspectRatio];
+ [coder encodeValueOfObjCType:@encode(BOOL) at:&resizeViewOnVideoSizeChange];
+ }
+
+}
+
+- (void) dealloc {
+ if (texture_buffer)
+ free (texture_buffer);
+
+ if (fullScreenContext)
+ {
+ [NSOpenGLContext clearCurrentContext];
+ [mutex lock];
+ [fullScreenContext clearDrawable];
+ [fullScreenContext release];
+ [mutex unlock];
+ if (currentContext == fullScreenContext) currentContext = nil;
+ fullScreenContext = nil;
+ }
+
+ if (currentContext)
+ {
+ [NSOpenGLContext clearCurrentContext];
+ [mutex lock];
+ [currentContext clearDrawable];
+ [currentContext release];
+ [mutex unlock];
+ currentContext = nil;
+ }
+
+ [mutex dealloc];
+
+ // Enabling the [super dealloc] below (which should be correct behaviour)
+ // crashes -- not sure why ...
+ //
+ // [super dealloc];
+ //
+ // Maybe dealloc in main thread?
+}
+
+- (void) reshape
+{
+ [mutex lock];
+
+ if (!initDone)
+ {
+ [mutex unlock];
+ return;
+ }
+
+ [currentContext makeCurrentContext];
+
+ NSRect bounds = [self bounds];
+ glViewport (0, 0, bounds.size.width, bounds.size.height);
+
+ [mutex unlock];
+}
+
+- (void) setNormalSize
+{
+ NSSize size;
+
+ if (isFullScreen)
+ return;
+
+ size.width = video_width;
+ size.height = video_height;
+
+ [self setViewSizeInMainThread:size];
+}
+
+- (void) setHalfSize
+{
+ NSSize size;
+
+ if (isFullScreen)
+ return;
+
+ size.width = video_width / 2;
+ size.height = video_height / 2;
+
+ [self setViewSizeInMainThread:size];
+}
+
+- (void) setDoubleSize
+{
+ NSSize size;
+
+ if (isFullScreen)
+ return;
+
+ size.width = video_width * 2;
+ size.height = video_height * 2;
+
+ [self setViewSizeInMainThread:size];
+}
+
+- (void) initTextures
+{
+ [mutex lock];
+
+ [currentContext makeCurrentContext];
+
+ /* Free previous texture if any */
+ if (i_texture)
+ glDeleteTextures (1, &i_texture);
+
+ if (texture_buffer)
+ {
+ texture_buffer = realloc (texture_buffer, sizeof (char) *
+ video_width * video_height * 3);
+ }
+ else
+ {
+ texture_buffer = malloc (sizeof (char) *
+ video_width * video_height * 3);
+ }
+
+ /* Create textures */
+ glGenTextures (1, &i_texture);
+
+ glEnable (GL_TEXTURE_RECTANGLE_EXT);
+ glEnable (GL_UNPACK_CLIENT_STORAGE_APPLE);
+
+ glPixelStorei (GL_UNPACK_ALIGNMENT, 1);
+ glPixelStorei (GL_UNPACK_ROW_LENGTH, video_width);
+
+ glBindTexture (GL_TEXTURE_RECTANGLE_EXT, i_texture);
+ glTexEnvi (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
+
+ /* Use VRAM texturing */
+ glTexParameteri (GL_TEXTURE_RECTANGLE_EXT,
+ GL_TEXTURE_STORAGE_HINT_APPLE, GL_STORAGE_CACHED_APPLE);
+
+ /* Tell the driver not to make a copy of the texture but to use
+ our buffer */
+ glPixelStorei (GL_UNPACK_CLIENT_STORAGE_APPLE, GL_TRUE);
+
+ /* Linear interpolation */
+ glTexParameteri (GL_TEXTURE_RECTANGLE_EXT,
+ GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri (GL_TEXTURE_RECTANGLE_EXT,
+ GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+
+ /* I have no idea what this exactly does, but it seems to be
+ necessary for scaling */
+ glTexParameteri (GL_TEXTURE_RECTANGLE_EXT,
+ GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri (GL_TEXTURE_RECTANGLE_EXT,
+ GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+ glTexImage2D (GL_TEXTURE_RECTANGLE_EXT, 0, GL_RGBA,
+ video_width, video_height, 0,
+ GL_YCBCR_422_APPLE, GL_UNSIGNED_SHORT_8_8_APPLE,
+ texture_buffer);
+
+ initDone = YES;
+ [mutex unlock];
+}
+
+- (void) reloadTexture
+{
+ if (!initDone)
+ {
+ return;
+ }
+
+ [mutex lock];
+
+ [currentContext makeCurrentContext];
+
+ glBindTexture (GL_TEXTURE_RECTANGLE_EXT, i_texture);
+ glPixelStorei (GL_UNPACK_ROW_LENGTH, video_width);
+
+ // glTexSubImage2D is faster than glTexImage2D
+ // http://developer.apple.com/samplecode/Sample_Code/Graphics_3D/TextureRange/MainOpenGLView.m.htm
+ glTexSubImage2D (GL_TEXTURE_RECTANGLE_EXT, 0, 0, 0,
+ video_width, video_height,
+ GL_YCBCR_422_APPLE, GL_UNSIGNED_SHORT_8_8_APPLE,
+ texture_buffer);
+
+ [mutex unlock];
+}
+
+- (void) calcFullScreenAspect
+{
+ int fs_width, fs_height, x = 0, y = 0, w = 0, h = 0;
+
+ fs_width = CGDisplayPixelsWide (kCGDirectMainDisplay);
+ fs_height = CGDisplayPixelsHigh (kCGDirectMainDisplay);
+
+ switch (fullscreen_mode) {
+ case XINE_FULLSCREEN_OVERSCAN:
+ if (((float) fs_width / (float) fs_height) > ((float) video_width / (float) video_height))
+ {
+ w = (float) video_width * ((float) fs_height / (float) video_height);
+ h = fs_height;
+ x = (fs_width - w) / 2;
+ y = 0;
+ }
+ else
+ {
+ w = fs_width;
+ h = (float) video_height * ((float) fs_width / (float) video_width);
+ x = 0;
+ y = (fs_height - h) / 2;
+ }
+ break;
+
+ case XINE_FULLSCREEN_CROP:
+ if (((float) fs_width / (float) fs_height) > ((float) video_width / (float) video_height))
+ {
+ w = fs_width;
+ h = (float) video_height * ((float) fs_width / (float) video_width);
+ x = 0;
+ y = (fs_height - h) / 2;
+ }
+ else
+ {
+ w = (float) video_width * ((float) fs_height / (float) video_height);
+ h = fs_height;
+ x = (fs_width - w) / 2;
+ y = 0;
+ }
+ break;
+ }
+
+ NSLog(@"MacOSX fullscreen mode: %dx%d => %dx%d @ %d,%d\n",
+ video_width, video_height, w, h, x, y);
+
+ [mutex lock];
+ glViewport (x, y, w, h);
+ [mutex unlock];
+}
+
+- (void) goFullScreen: (XineVideoWindowFullScreenMode) mode
+{
+ [mutex lock];
+
+ /* Create the new pixel format */
+ NSOpenGLPixelFormatAttribute attribs[] =
+ {
+ NSOpenGLPFAAccelerated,
+ NSOpenGLPFANoRecovery,
+ NSOpenGLPFADoubleBuffer,
+ NSOpenGLPFAColorSize, 24,
+ NSOpenGLPFAAlphaSize, 8,
+ NSOpenGLPFADepthSize, 24,
+ NSOpenGLPFAFullScreen,
+ NSOpenGLPFAScreenMask,
+ CGDisplayIDToOpenGLDisplayMask (kCGDirectMainDisplay),
+ 0
+ };
+
+ NSOpenGLPixelFormat * fmt = [[NSOpenGLPixelFormat alloc]
+ initWithAttributes: attribs];
+
+ if (!fmt)
+ {
+ NSLog (@"Cannot create NSOpenGLPixelFormat\n");
+ return;
+ }
+
+ /* Create the new OpenGL context */
+ fullScreenContext = [[NSOpenGLContext alloc]
+ initWithFormat: fmt shareContext: nil];
+
+ if (!fullScreenContext)
+ {
+ NSLog(@"Failed to create new NSOpenGLContext\n");
+ return;
+ }
+ currentContext = fullScreenContext;
+
+ /* Capture display, switch to fullscreen */
+ if (CGCaptureAllDisplays() != CGDisplayNoErr)
+ {
+ NSLog(@"CGCaptureAllDisplays() failed\n");
+ return;
+ }
+
+ [fullScreenContext setFullScreen];
+ [fullScreenContext makeCurrentContext];
+ [mutex unlock];
+
+ fullscreen_mode = mode;
+
+ [self initTextures];
+ [self calcFullScreenAspect];
+
+ /* Redraw the last picture */
+ [self setNeedsDisplay: YES];
+
+ isFullScreen = YES;
+}
+
+- (void) exitFullScreen
+{
+ initDone = NO;
+
+ currentContext = [self openGLContext];
+
+ /* Free current OpenGL context */
+ [NSOpenGLContext clearCurrentContext];
+ [mutex lock];
+ [fullScreenContext clearDrawable];
+ [mutex unlock];
+ [fullScreenContext release];
+ fullScreenContext = nil;
+ CGReleaseAllDisplays();
+
+ [self reshape];
+ [self initTextures];
+
+ /* Redraw the last picture */
+ [self setNeedsDisplay: YES];
+
+ isFullScreen = NO;
+ initDone = YES;
+}
+
+- (void) drawQuad
+{
+ float f_x = 1.0, f_y = 1.0;
+
+ glBegin (GL_QUADS);
+ /* Top left */
+ glTexCoord2f (0.0, 0.0);
+ glVertex2f (-f_x, f_y);
+ /* Bottom left */
+ glTexCoord2f (0.0, (float) video_height);
+ glVertex2f (-f_x, -f_y);
+ /* Bottom right */
+ glTexCoord2f ((float) video_width, (float) video_height);
+ glVertex2f (f_x, -f_y);
+ /* Top right */
+ glTexCoord2f ((float) video_width, 0.0);
+ glVertex2f (f_x, f_y);
+ glEnd();
+}
+
+- (void) drawRect: (NSRect) rect
+{
+ [currentContext makeCurrentContext];
+
+ if (!initDone)
+ return;
+
+ [mutex lock];
+
+ // Swap buffers only during the vertical retrace of the monitor.
+ // http://developer.apple.com/documentation/GraphicsImaging/Conceptual/OpenGL/chap5/chapter_5_section_44.html
+
+ long params[] = { 1 };
+ CGLSetParameter (CGLGetCurrentContext(), kCGLCPSwapInterval, params);
+
+ /* Black background */
+ glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+ /* Draw */
+ glBindTexture (GL_TEXTURE_RECTANGLE_EXT, i_texture);
+ [self drawQuad];
+
+ /* Wait for the job to be done */
+ [currentContext flushBuffer];
+
+ [mutex unlock];
+}
+
+- (char *) getTextureBuffer
+{
+ return texture_buffer;
+}
+
+- (void) setVideoSize:(NSSize)size
+{
+ video_width = size.width;
+ video_height = size.height;
+
+ if (resizeViewOnVideoSizeChange)
+ [self setViewSizeInMainThread:size];
+
+#ifdef LOG
+ NSLog(@"setVideoSize called");
+#endif
+
+ [self initTextures];
+}
+
+- (void) setViewSizeInMainThread:(NSSize)size
+{
+ // Create an autorelease pool, since we're running in a xine thread that
+ // may not have a pool of its own */
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+
+ NSValue *sizeWrapper = [NSValue valueWithBytes:&size
+ objCType:@encode(NSSize)];
+
+ [self performSelectorOnMainThread:@selector(setViewSize:)
+ withObject:sizeWrapper
+ waitUntilDone:NO];
+
+#ifdef LOG
+ NSLog(@"setViewSizeInMainThread called");
+#endif
+
+ [pool release];
+}
+
+- (void) setViewSize:(NSValue *)sizeWrapper
+{
+ NSSize proposedSize, newSize, currentSize;
+
+ [sizeWrapper getValue:&proposedSize];
+ newSize = proposedSize;
+
+ currentSize = [self frame].size;
+ if (proposedSize.width == currentSize.width &&
+ proposedSize.height == currentSize.height)
+ {
+ return;
+ }
+
+ /* If our controller handles xineViewWillResize:toSize:, send the
+ * message to him first. Note that the delegate still has a chance
+ * to override the controller's resize preference ... */
+ if ([_xineController respondsToSelector:@selector(xineViewWillResize:toSize:)])
+ {
+ NSSize oldSize = [self frame].size;
+ newSize = [_xineController xineViewWillResize:oldSize toSize:proposedSize];
+ }
+
+ /* If our delegate handles xineViewWillResize:toSize:, send the
+ * message to him; otherwise, just resize ourselves */
+ if ([delegate respondsToSelector:@selector(xineViewWillResize:toSize:)])
+ {
+ NSSize oldSize = [self frame].size;
+ newSize = [delegate xineViewWillResize:oldSize toSize:proposedSize];
+ }
+
+ [self setFrameSize:newSize];
+ [self setBoundsSize:newSize];
+
+ /* Post a notification that we resized and also notify our controller */
+ /* and delegate */
+ NSNotification *note =
+ [NSNotification notificationWithName:XineViewDidResizeNotification
+ object:self];
+ [[NSNotificationCenter defaultCenter] postNotification:note];
+
+ if ([_xineController respondsToSelector:@selector(xineViewDidResize:)])
+ [_xineController xineViewDidResize:note];
+
+ if ([delegate respondsToSelector:@selector(xineViewDidResize:)])
+ [delegate xineViewDidResize:note];
+
+ if (isFullScreen)
+ [self calcFullScreenAspect];
+
+ [self initTextures];
+}
+
+- (BOOL) isFullScreen
+{
+ return isFullScreen;
+}
+
+- (id) delegate
+{
+ return delegate;
+}
+
+- (void) setDelegate:(id)aDelegate {
+ delegate = aDelegate;
+}
+
+- (BOOL)acceptsFirstResponder {
+ return YES;
+}
+
+- (void) setCurrentCursor:(NSCursor *)cursor
+{
+ currentCursor = cursor;
+ [self resetCursorRectsInMainThread];
+}
+
+- (NSCursor *) currentCursor
+{
+ return currentCursor;
+}
+
+- (void) resetCursorRectsInMainThread
+{
+ [self discardCursorRects];
+ [self performSelectorOnMainThread:@selector(resetCursorRects)
+ withObject:nil
+ waitUntilDone:NO];
+}
+
+- (void) resetCursorRects
+{
+ [self addCursorRect:[self visibleRect] cursor:currentCursor];
+ [currentCursor set];
+}
+
+- (void) setXineController:(id)controller
+{
+ _xineController = controller;
+}
+
+- (id) xineController
+{
+ return _xineController;
+}
+
+@end /* XineOpenGLView */