459 lines
13 KiB
Plaintext
459 lines
13 KiB
Plaintext
// Copyright (c) Akop Karapetyan
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
#include <OpenGL/gl.h>
|
|
#include <time.h>
|
|
|
|
#import "FBScreenView.h"
|
|
#import "AppDelegate.h"
|
|
|
|
#define HIDE_CURSOR_TIMEOUT_SECONDS 1.0f
|
|
|
|
static int powerOf2Gte(int number);
|
|
static CVReturn displayLinkCallback(CVDisplayLinkRef displayLink,
|
|
const CVTimeStamp *now, const CVTimeStamp *outputTime,
|
|
CVOptionFlags flagsIn, CVOptionFlags *flagsOut,
|
|
void *displayLinkContext);
|
|
|
|
@interface FBScreenView ()
|
|
|
|
- (void) resetProjection;
|
|
- (void) handleMouseChange:(NSPoint) position;
|
|
- (void) resetTrackingArea;
|
|
- (void) renderTexture:(BOOL) resetContext;
|
|
|
|
@end
|
|
|
|
@implementation FBScreenView
|
|
{
|
|
GLuint screenTextureId;
|
|
unsigned char *texture;
|
|
int imageWidth;
|
|
int imageHeight;
|
|
BOOL isRotated;
|
|
BOOL isFlipped;
|
|
int textureWidth;
|
|
int textureHeight;
|
|
int textureBytesPerPixel;
|
|
NSSize screenSize;
|
|
CFAbsoluteTime _lastMouseAction;
|
|
NSPoint _lastCursorPosition;
|
|
NSTrackingArea *_trackingArea;
|
|
NSRect viewBounds; // Access from non-UI thread
|
|
NSSize viewSize; // Access from non-UI thread
|
|
GLint textureFormat;
|
|
CVDisplayLinkRef displayLink;
|
|
BOOL useDisplayLink;
|
|
GLfloat backingScaleFactor;
|
|
}
|
|
|
|
#pragma mark - Initialize, Dealloc
|
|
|
|
- (void) dealloc
|
|
{
|
|
if (useDisplayLink) {
|
|
CVDisplayLinkStop(displayLink);
|
|
CVDisplayLinkRelease(displayLink);
|
|
}
|
|
|
|
glDeleteTextures(1, &screenTextureId);
|
|
free(texture);
|
|
}
|
|
|
|
- (void) awakeFromNib
|
|
{
|
|
useDisplayLink = [NSUserDefaults.standardUserDefaults boolForKey:@"useDisplayLink"];
|
|
_lastMouseAction = CFAbsoluteTimeGetCurrent();
|
|
_lastCursorPosition = NSMakePoint(-1, -1);
|
|
textureFormat = GL_UNSIGNED_SHORT_5_6_5;
|
|
backingScaleFactor = 1;
|
|
|
|
if ([self respondsToSelector:@selector(setWantsBestResolutionOpenGLSurface:)]) {
|
|
self.wantsBestResolutionOpenGLSurface = YES;
|
|
}
|
|
|
|
[self resetTrackingArea];
|
|
}
|
|
|
|
#pragma mark - Cocoa Callbacks
|
|
|
|
- (void) prepareOpenGL
|
|
{
|
|
[super prepareOpenGL];
|
|
|
|
NSLog(@"FBScreenView/prepareOpenGL");
|
|
|
|
CGLContextObj cglContext = self.openGLContext.CGLContextObj;
|
|
CGLLockContext(cglContext);
|
|
|
|
// Synchronize buffer swaps with vertical refresh rate
|
|
GLint swapInt = 1;
|
|
[self.openGLContext setValues:&swapInt
|
|
forParameter:NSOpenGLCPSwapInterval];
|
|
|
|
glClearColor(0, 0, 0, 1.0);
|
|
glClear(GL_COLOR_BUFFER_BIT);
|
|
glColor3f(0, 0, 0);
|
|
|
|
glGenTextures(1, &screenTextureId);
|
|
|
|
CGLUnlockContext(cglContext);
|
|
|
|
if (useDisplayLink) {
|
|
NSLog(@"Initializing display link...");
|
|
|
|
// Initialize display link
|
|
CVDisplayLinkCreateWithActiveCGDisplays(&displayLink);
|
|
CVDisplayLinkSetOutputCallback(displayLink, &displayLinkCallback,
|
|
(__bridge void *) self);
|
|
CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext(displayLink, cglContext,
|
|
self.pixelFormat.CGLPixelFormatObj);
|
|
CVDisplayLinkStart(displayLink);
|
|
}
|
|
}
|
|
|
|
- (void) drawRect:(NSRect) dirtyRect
|
|
{
|
|
[self.openGLContext makeCurrentContext];
|
|
CGLLockContext(self.openGLContext.CGLContextObj);
|
|
|
|
glClear(GL_COLOR_BUFFER_BIT);
|
|
|
|
if (textureWidth > 0 && textureHeight > 0) {
|
|
GLfloat coordX = (GLfloat) imageWidth / textureWidth;
|
|
GLfloat coordY = (GLfloat) imageHeight / textureHeight;
|
|
|
|
glEnable(GL_TEXTURE_2D);
|
|
glBindTexture(GL_TEXTURE_2D, screenTextureId);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
|
|
|
|
NSSize size = self.bounds.size;
|
|
CGFloat offset = 0;
|
|
|
|
glBegin(GL_QUADS);
|
|
glTexCoord2f(0.0, 0.0);
|
|
glVertex3f(-offset, 0.0, 0.0);
|
|
glTexCoord2f(coordX, 0.0);
|
|
glVertex3f(size.width + offset, 0.0, 0.0);
|
|
glTexCoord2f(coordX, coordY);
|
|
glVertex3f(size.width + offset, size.height, 0.0);
|
|
glTexCoord2f(0.0, coordY);
|
|
glVertex3f(-offset, size.height, 0.0);
|
|
glEnd();
|
|
glDisable(GL_TEXTURE_2D);
|
|
}
|
|
|
|
[self.openGLContext flushBuffer];
|
|
|
|
CGLUnlockContext(self.openGLContext.CGLContextObj);
|
|
}
|
|
|
|
- (void) reshape
|
|
{
|
|
[super reshape];
|
|
|
|
viewBounds = self.bounds;
|
|
viewSize = [self convertRectToBacking:viewBounds].size;
|
|
if ([self.window respondsToSelector:@selector(backingScaleFactor)]) {
|
|
backingScaleFactor = self.window.backingScaleFactor;
|
|
}
|
|
|
|
[self.openGLContext makeCurrentContext];
|
|
CGLLockContext(self.openGLContext.CGLContextObj);
|
|
|
|
[self.openGLContext update];
|
|
|
|
[self resetProjection];
|
|
|
|
glClear(GL_COLOR_BUFFER_BIT);
|
|
|
|
CGLUnlockContext(self.openGLContext.CGLContextObj);
|
|
}
|
|
|
|
#pragma mark - FBVideoDelegate
|
|
|
|
- (void) initTextureOfWidth:(int) width
|
|
height:(int) height
|
|
isRotated:(BOOL) rotated
|
|
isFlipped:(BOOL) flipped
|
|
bytesPerPixel:(int) bytesPerPixel
|
|
{
|
|
NSLog(@"FBScreenView/initTexture");
|
|
|
|
[self.openGLContext makeCurrentContext];
|
|
CGLLockContext(self.openGLContext.CGLContextObj);
|
|
|
|
free(texture);
|
|
imageWidth = width;
|
|
imageHeight = height;
|
|
isRotated = rotated;
|
|
isFlipped = flipped;
|
|
textureWidth = powerOf2Gte(imageWidth);
|
|
textureHeight = powerOf2Gte(imageHeight);
|
|
textureBytesPerPixel = bytesPerPixel;
|
|
screenSize = NSMakeSize((CGFloat)width, (CGFloat)height);
|
|
|
|
int texSize = textureWidth * textureHeight * bytesPerPixel;
|
|
texture = (unsigned char *) malloc(texSize);
|
|
memset(texture, 0, texSize);
|
|
|
|
glEnable(GL_TEXTURE_2D);
|
|
|
|
glBindTexture(GL_TEXTURE_2D, screenTextureId);
|
|
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
|
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB,
|
|
textureWidth, textureHeight,
|
|
0, GL_RGB, textureFormat, texture);
|
|
|
|
glDisable(GL_TEXTURE_2D);
|
|
|
|
[self resetProjection];
|
|
|
|
CGLUnlockContext(self.openGLContext.CGLContextObj);
|
|
}
|
|
|
|
- (void) renderFrame:(unsigned char *) bitmap
|
|
{
|
|
if (NSPointInRect(_lastCursorPosition, viewBounds)) {
|
|
CFAbsoluteTime interval = CFAbsoluteTimeGetCurrent() - _lastMouseAction;
|
|
if (interval > HIDE_CURSOR_TIMEOUT_SECONDS) {
|
|
if ([_delegate respondsToSelector:@selector(mouseDidIdle)])
|
|
[_delegate mouseDidIdle];
|
|
_lastCursorPosition.x = -1;
|
|
}
|
|
}
|
|
|
|
[self.openGLContext makeCurrentContext];
|
|
CGLLockContext(self.openGLContext.CGLContextObj);
|
|
|
|
unsigned char *ps = (unsigned char *) bitmap;
|
|
unsigned char *pd = (unsigned char *) texture;
|
|
|
|
int bitmapPitch = imageWidth * textureBytesPerPixel;
|
|
int texturePitch = textureWidth * textureBytesPerPixel;
|
|
for (int y = imageHeight; y--; ) {
|
|
memcpy(pd, ps, bitmapPitch);
|
|
pd += texturePitch;
|
|
ps += bitmapPitch;
|
|
}
|
|
|
|
if (!useDisplayLink)
|
|
[self renderTexture:NO];
|
|
|
|
CGLUnlockContext(self.openGLContext.CGLContextObj);
|
|
}
|
|
|
|
#pragma mark - Keyboard
|
|
|
|
- (BOOL) acceptsFirstResponder
|
|
{
|
|
return YES;
|
|
}
|
|
|
|
- (void) keyDown:(NSEvent *) theEvent
|
|
{
|
|
// NSLog(@"FBScreenView/keyDown: %@", theEvent);
|
|
[AppDelegate.sharedInstance.input keyDown:theEvent];
|
|
}
|
|
|
|
- (void) keyUp:(NSEvent *) theEvent
|
|
{
|
|
// NSLog(@"FBScreenView/keyUp: %@", theEvent);
|
|
[AppDelegate.sharedInstance.input keyUp:theEvent];
|
|
}
|
|
|
|
- (void) flagsChanged:(NSEvent *) theEvent
|
|
{
|
|
// NSLog(@"FBScreenView/flagsChanged: %@", theEvent);
|
|
[AppDelegate.sharedInstance.input flagsChanged:theEvent];
|
|
}
|
|
|
|
#pragma mark - Mouse tracking
|
|
|
|
- (void) mouseEntered:(NSEvent *) event
|
|
{
|
|
[self handleMouseChange:event.locationInWindow];
|
|
}
|
|
|
|
- (void) mouseMoved:(NSEvent *) event
|
|
{
|
|
if ([_delegate respondsToSelector:@selector(mouseDidMove:)])
|
|
[_delegate mouseDidMove:NSMakePoint(event.deltaX, event.deltaY)];
|
|
[self handleMouseChange:event.locationInWindow];
|
|
}
|
|
|
|
- (void) mouseExited:(NSEvent *) event
|
|
{
|
|
[self handleMouseChange:NSMakePoint(-1, -1)];
|
|
}
|
|
|
|
- (void) mouseDown:(NSEvent *) event
|
|
{
|
|
if ([_delegate respondsToSelector:@selector(mouseButtonStateDidChange:)])
|
|
[_delegate mouseButtonStateDidChange:event];
|
|
[self handleMouseChange:event.locationInWindow];
|
|
}
|
|
|
|
- (void) mouseUp:(NSEvent *) event
|
|
{
|
|
if ([_delegate respondsToSelector:@selector(mouseButtonStateDidChange:)])
|
|
[_delegate mouseButtonStateDidChange:event];
|
|
}
|
|
|
|
- (void) mouseDragged:(NSEvent *) event
|
|
{
|
|
if ([_delegate respondsToSelector:@selector(mouseDidMove:)])
|
|
[_delegate mouseDidMove:NSMakePoint(event.deltaX, event.deltaY)];
|
|
}
|
|
|
|
- (void) rightMouseDown:(NSEvent *) event
|
|
{
|
|
if ([_delegate respondsToSelector:@selector(mouseButtonStateDidChange:)])
|
|
[_delegate mouseButtonStateDidChange:event];
|
|
[self handleMouseChange:event.locationInWindow];
|
|
}
|
|
|
|
- (void) rightMouseUp:(NSEvent *) event
|
|
{
|
|
if ([_delegate respondsToSelector:@selector(mouseButtonStateDidChange:)])
|
|
[_delegate mouseButtonStateDidChange:event];
|
|
}
|
|
|
|
- (void) rightMouseDragged:(NSEvent *) event
|
|
{
|
|
if ([_delegate respondsToSelector:@selector(mouseDidMove:)])
|
|
[_delegate mouseDidMove:NSMakePoint(event.deltaX, event.deltaY)];
|
|
}
|
|
|
|
#pragma mark - Public methods
|
|
|
|
- (NSSize) screenSize
|
|
{
|
|
return screenSize;
|
|
}
|
|
|
|
#pragma mark - Private methods
|
|
|
|
- (void) resetTrackingArea
|
|
{
|
|
if (_trackingArea) {
|
|
[self removeTrackingArea:_trackingArea];
|
|
}
|
|
|
|
NSTrackingAreaOptions opts = NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingActiveInKeyWindow | NSTrackingInVisibleRect;
|
|
_trackingArea = [[NSTrackingArea alloc] initWithRect:NSZeroRect
|
|
options:opts
|
|
owner:self
|
|
userInfo:nil];
|
|
[self addTrackingArea:_trackingArea];
|
|
}
|
|
|
|
- (void) handleMouseChange:(NSPoint) position
|
|
{
|
|
_lastMouseAction = CFAbsoluteTimeGetCurrent();
|
|
_lastCursorPosition = [self convertPoint:position
|
|
fromView:nil];
|
|
|
|
if ([_delegate respondsToSelector:@selector(mouseStateDidChange)])
|
|
[_delegate mouseStateDidChange];
|
|
}
|
|
|
|
- (void) resetProjection
|
|
{
|
|
NSSize size = [self bounds].size;
|
|
|
|
glViewport(0, 0, size.width, size.height);
|
|
glMatrixMode(GL_PROJECTION);
|
|
glLoadIdentity();
|
|
|
|
if (!isRotated) {
|
|
glOrtho(0, size.width, size.height, 0, -1, 1);
|
|
} else {
|
|
glRotatef(isFlipped ? 270 : 90.0, 0.0, 0.0, 1.0);
|
|
glOrtho(0, size.width, size.height, 0, -1, 5);
|
|
}
|
|
|
|
glMatrixMode(GL_MODELVIEW);
|
|
}
|
|
|
|
- (void) renderTexture:(BOOL) resetContext
|
|
{
|
|
if (resetContext) {
|
|
[self.openGLContext makeCurrentContext];
|
|
CGLLockContext(self.openGLContext.CGLContextObj);
|
|
}
|
|
|
|
glClear(GL_COLOR_BUFFER_BIT);
|
|
|
|
GLfloat coordX = (GLfloat)imageWidth / textureWidth;
|
|
GLfloat coordY = (GLfloat)imageHeight / textureHeight;
|
|
|
|
glEnable(GL_TEXTURE_2D);
|
|
glBindTexture(GL_TEXTURE_2D, screenTextureId);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
|
|
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, textureWidth, textureHeight, 0,
|
|
GL_RGB, textureFormat, texture);
|
|
|
|
NSSize size = viewSize;
|
|
CGFloat offset = 0;
|
|
|
|
if (backingScaleFactor != 1) {
|
|
size = NSMakeSize((int)(size.width / backingScaleFactor),
|
|
(int)(size.height / backingScaleFactor));
|
|
}
|
|
|
|
glBegin(GL_QUADS);
|
|
glTexCoord2f(0.0, 0.0);
|
|
glVertex3f(-offset, 0.0, 0.0);
|
|
glTexCoord2f(coordX, 0.0);
|
|
glVertex3f(size.width + offset, 0.0, 0.0);
|
|
glTexCoord2f(coordX, coordY);
|
|
glVertex3f(size.width + offset, size.height, 0.0);
|
|
glTexCoord2f(0.0, coordY);
|
|
glVertex3f(-offset, size.height, 0.0);
|
|
glEnd();
|
|
glDisable(GL_TEXTURE_2D);
|
|
|
|
[self.openGLContext flushBuffer];
|
|
|
|
if (resetContext)
|
|
CGLUnlockContext(self.openGLContext.CGLContextObj);
|
|
}
|
|
|
|
@end
|
|
|
|
#pragma mark - Statics/C/C++
|
|
|
|
static int powerOf2Gte(int number)
|
|
{
|
|
int rv = 1;
|
|
while (rv < number) rv *= 2;
|
|
return rv;
|
|
}
|
|
|
|
static CVReturn displayLinkCallback(CVDisplayLinkRef displayLink,
|
|
const CVTimeStamp *now, const CVTimeStamp *outputTime,
|
|
CVOptionFlags flagsIn, CVOptionFlags *flagsOut,
|
|
void *displayLinkContext)
|
|
{
|
|
[(__bridge FBScreenView *) displayLinkContext renderTexture:YES];
|
|
return kCVReturnSuccess;
|
|
}
|