NSOpenGLView subclass not resizing with window - macos

I've been trying to get the NSOpenGLView to resize correctly when resizing the window, but I keep getting weird behaviour which I cannot explain. It looks like so:
Original (What it looks like at the beginning):
After resize:
This is my resize code:
- (void)reshape
{
[super reshape];
CGLLockContext([[self openGLContext] CGLContextObj]);
NSRect viewRectPoints = [self bounds];
#if SUPPORT_RETINA_RESOLUTION
NSRect viewRectPixels = [self convertRectToBacking:viewRectPoints];
#else //if !SUPPORT_RETINA_RESOLUTION
NSRect viewRectPixels = viewRectPoints;
#endif // !SUPPORT_RETINA_RESOLUTION
[self resizeWithWidth:viewRectPixels.size.width
AndHeight:viewRectPixels.size.height];
CGLUnlockContext([[self openGLContext] CGLContextObj]);
}
- (void) resizeWithWidth:(GLuint)width AndHeight:(GLuint)height
{
glViewport(0, 0, width, height);
m_width = width;
m_height = height;
}
and I'm using: "glViewport(0, 0, m_width, m_height);" whenever I call glDrawArrays();
Can anyone help?

When resizing the window you should also set the projection matrix to reflect the new dimensions. You didn't post your setup/drawing code, but usually that looks something like:
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(...); // or glFrustum(), glOrtho(), gluOrtho2D(), ...
glMatrixMode(GL_MODELVIEW);

Seriously, in these days you might want to consider to leave NSOpenGLView behind and move to CAOpenGLLayer directly.
I've had too many issues with this silly class to bother any more about NSOpenGLView and in layer backed views particularly having a CAOpenGLLayer IMHO just gives you more flexibility - and control.
(and less bugs - try getting NSOpenGLView to work in a layer backed view hierarchy under 10.9 or pre..).
Plus more functionality right out of the box - or at least under your finger tips (like built-in CVDisplayLink support).
Just my 2c.

Related

Huge drop in fps with transparent EAGLView

I have a Cocos2d project and I want a constant background throughout the app. In the applicationDidFinishLaunching method of its delegate, I have replaced the line:
I have changed the pixelFormat of glView from kEAGLColorFormatRGB565 to kEAGLColorFormatRGBA8. When I make that change, glView becomes transparent and I can see through it, but the fps drops dramatically. If I don't make that change, the view doesn't become transparent, but I don't see the huge drop in fps. I'm talking about a significant drop in fps, from 59.0-60.0 to about 35.0-42.0.
I am using this code right below the addSubview line above to make the view transparent:
director.openGLView.opaque = NO;
The whole applicationDidFinishLaunching method looks like this:
- (void) applicationDidFinishLaunching:(UIApplication*)application
{
// Init the window
window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Try to use CADisplayLink director
// if it fails (SDK < 3.1) use the default director
if( ! [CCDirector setDirectorType:kCCDirectorTypeDisplayLink] )
[CCDirector setDirectorType:kCCDirectorTypeDefault];
CCDirector *director = [CCDirector sharedDirector];
// Init the View Controller
viewController = [[RootViewController alloc] initWithNibName:nil bundle:nil];
viewController.wantsFullScreenLayout = YES;
//
// Create the EAGLView manually
// 1. Create a RGB565 format. Alternative: RGBA8
// 2. depth format of 0 bit. Use 16 or 24 bit for 3d effects, like CCPageTurnTransition
//
//
EAGLView *glView = [EAGLView viewWithFrame:[window bounds]
pixelFormat:kEAGLColorFormatRGBA8 // kEAGLColorFormatRGBA8
depthFormat:0 // GL_DEPTH_COMPONENT16_OES
];
// attach the openglView to the director
[director setOpenGLView:glView];
glView.opaque = NO;
// // Enables High Res mode (Retina Display) on iPhone 4 and maintains low res on all other devices
if( ! [director enableRetinaDisplay:YES] )
CCLOG(#"Retina Display Not supported");
//
// VERY IMPORTANT:
// If the rotation is going to be controlled by a UIViewController
// then the device orientation should be "Portrait".
//
// IMPORTANT:
// By default, this template only supports Landscape orientations.
// Edit the RootViewController.m file to edit the supported orientations.
//
#if GAME_AUTOROTATION == kGameAutorotationUIViewController
[director setDeviceOrientation:kCCDeviceOrientationPortrait];
#else
[director setDeviceOrientation:kCCDeviceOrientationLandscapeLeft];
#endif
[director setAnimationInterval:1.0/60];
[director setDisplayFPS:YES];
// make the OpenGLView a child of the view controller
[viewController setView:glView];
//Required in iOS6, recommended in 4 and 5
[window setRootViewController:viewController];
// make the View Controller a child of the main window, needed for iOS 4 and 5
[window addSubview: viewController.view];
[window makeKeyAndVisible];
// Default texture format for PNG/BMP/TIFF/JPEG/GIF images
// It can be RGBA8888, RGBA4444, RGB5_A1, RGB565
// You can change anytime.
[CCTexture2D setDefaultAlphaPixelFormat:kCCTexture2DPixelFormat_RGBA8888];
// Removes the startup flicker
[self removeStartupFlicker];
// Run the intro Scene
[[CCDirector sharedDirector] runWithScene: [Intro scene]];
}
Any ideas as to why this is happening? I can provide more code if need be.
I forgot to mention one other code change that I did. In CCDirector.m setGLDefaultValuesI changed this line from this:
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
To this:
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
Anytime you add an alpha channel with transparency, the OpenGL engine is going to have to perform some form (albeit default) alpha blending. This is math per pixel that it wasnt having to do in the non-RGBA (i.e. RGB 565) case. Anytime you add to the math needed by the engine frame rates are going to drop.
This is to be expected. The RGBA8888 framebuffer uses twice as much memory and the GPU needs to perform more work to render into this framebuffer. This is why RGB565 is the default format.

Two Finger Drag with IKImageView and NSScrollView in Mountain Lion

I have a Mac App that's been in the app store for a year or so now. It was first published with target SDK 10.7, Lion. Upon the update to Mountain Lion it no longer works.
The application displays large images in an IKImageView which is embedded in an NSScrollView. The purpose of putting it into a scrollview was to get two finger dragging working, rather than the user having to click to drag. Using ScrollViewWorkaround by Nicholas Riley, I was able to use two finger scrolling to show the clipped content after the user had zoomed in. Just like you see in the Preview app.
Nicholas Riley's Solution:
IKImageView and scroll bars
Now in Mountain Lion this doesn't work. After zooming in, pinch or zoom button, the image is locked in the lower left portion of the image. It won't scroll.
So the question is, what's the appropriate way to display a large image in IKImageView and have two finger dragging of the zoomed image?
Thank you,
Stateful
Well, Nicholas Riley's Solution is an ugly hack in that it addresses the wrong class; the issue isn't with NSClipView (which he subclassed, but which works just fine as is), but with IKImageView.
The issue with IKImageView is actually quite simple (God knows why Apple hasn't fixed this in what? … 7 years ...): Its size does not adjust to the size of the image it displays. Now, when you embed an IKImageView in an NSScrollView, the scroll view obviously can only adjust its scroll bars relative to the size of the embedded IKImageView, not to the image it contains. And since the size of the IKImageView always stays the same, the scroll bars won't work as expected.
The following code subclasses IKImageView and fixes this behavior. Alas, it won't fix the fact that IKImageView is crash-prone in Mountain Lion as soon as you zoom …
///////////////////// HEADER FILE - FixedIKImageView.h
#import <Quartz/Quartz.h>
#interface FixedIKImageView : IKImageView
#end
///////////////////// IMPLEMENTATION FILE - FixedIKImageView.m
#import "FixedIKImageView.h"
#implementation FixedIKImageView
- (void)awakeFromNib
{
[self setTranslatesAutoresizingMaskIntoConstraints:NO]; // compatibility with Auto Layout; without this, there could be Auto Layout error messages when we are resized (delete this line if your app does not use Auto Layout)
}
// FixedIKImageView must *only* be used embedded within an NSScrollView. This means that setFrame: should never be called explicitly from outside the scroll view. Instead, this method is overwritten here to provide the correct behavior within a scroll view. The new implementation ignores the frameRect parameter.
- (void)setFrame:(NSRect)frameRect
{
NSSize imageSize = [self imageSize];
CGFloat zoomFactor = [self zoomFactor];
NSSize clipViewSize = [[self superview] frame].size;
// The content of our scroll view (which is ourselves) should stay at least as large as the scroll clip view, so we make ourselves as large as the clip view in case our (zoomed) image is smaller. However, if our image is larger than the clip view, we make ourselves as large as the image, to make the scrollbars appear and scale appropriately.
CGFloat newWidth = (imageSize.width * zoomFactor < clipViewSize.width)? clipViewSize.width : imageSize.width * zoomFactor;
CGFloat newHeight = (imageSize.height * zoomFactor < clipViewSize.height)? clipViewSize.height : imageSize.height * zoomFactor;
[super setFrame:NSMakeRect(0, 0, newWidth - 2, newHeight - 2)]; // actually, the clip view is 1 pixel larger than the content view on each side, so we must take that into account
}
//// We forward size affecting messages to our superclass, but add [self setFrame:NSZeroRect] to update the scroll bars. We also add [self setAutoresizes:NO]. Since IKImageView, instead of using [self setAutoresizes:NO], seems to set the autoresizes instance variable to NO directly, the scrollers would not be activated again without invoking [self setAutoresizes:NO] ourselves when these methods are invoked.
- (void)setZoomFactor:(CGFloat)zoomFactor
{
[super setZoomFactor:zoomFactor];
[self setFrame:NSZeroRect];
[self setAutoresizes:NO];
}
- (void)zoomImageToRect:(NSRect)rect
{
[super zoomImageToRect:rect];
[self setFrame:NSZeroRect];
[self setAutoresizes:NO];
}
- (void)zoomIn:(id)sender
{
[super zoomIn:self];
[self setFrame:NSZeroRect];
[self setAutoresizes:NO];
}
- (void)zoomOut:(id)sender
{
[super zoomOut:self];
[self setFrame:NSZeroRect];
[self setAutoresizes:NO];
}
- (void)zoomImageToActualSize:(id)sender
{
[super zoomImageToActualSize:sender];
[self setFrame:NSZeroRect];
[self setAutoresizes:NO];
}
- (void)zoomImageToFit:(id)sender
{
[self setAutoresizes:YES]; // instead of invoking super's zoomImageToFit: method, which has problems of its own, we invoke setAutoresizes:YES, which does the same thing, but also makes sure the image stays zoomed to fit even if the scroll view is resized, which is the most intuitive behavior, anyway. Since there are no scroll bars in autoresize mode, we need not add [self setFrame:NSZeroRect].
}
- (void)setAutoresizes:(BOOL)autoresizes // As long as we autoresize, make sure that no scrollers flicker up occasionally during live update.
{
[self setHasHorizontalScroller:!autoresizes];
[self setHasVerticalScroller:!autoresizes];
[super setAutoresizes:autoresizes];
}
#end

Cocos2d how to scale a sprite without scaling layer? Or, How to scale and crop sprite/layer?

iPad App setup: SceneA contains layerA - 1024x768. Push a button in layerA, layerB drops down over top using a CCMoveTo action. LayerB is only 800x600 so you can see layerA behind it (think of an overlayed pause screen type effect). LayerB contains an 800x600 sprite that the user can zoom in on by pressing a button. The zoom effect is simply a combination of CCScaleTo and CCMoveTo to keep it centered on the part it's zooming in on. However, when the sprite scales, so does layerB overtop of layerA. Is there a way to scale the sprite within a contained window?
LayerB should use the GL_SCISSOR_TEST to trim the outside of itself. You can easily google for more information about it, it basically defines a rect and then uses glScissoron it to remove the outside. I have a class I extend when I need to do this, that goes as follows:
//
// CCNodeClip.h
//
// Created by Ignacio Orlandoni on 7/29/11.
//
#import <Foundation/Foundation.h>
#import "cocos2d.h"
#interface CCNodeClip : CCLayer {
}
-(void)preVisit;
-(void)postVisit;
#end
-
//
// CCNodeClip.m
//
// Created by Ignacio Orlandoni on 7/29/11.
//
#import "CCNodeClip.h"
#implementation CCNodeClip
-(void)visit {
[self preVisit];
[super visit];
[self postVisit];
}
-(void)preVisit {
if (!self.visible)
return;
glEnable(GL_SCISSOR_TEST);
CGPoint position = [self position];
//I don't remember if this rect really serves for both orientations, so you may need to change the order of the values here.
CGRect scissorRect = CGRectMake(position.x, position.y, [self contentSize].width, [self contentSize].height);
// CCLOG(#"Scrissor Rect: X: %02f, Y:%02f, W: %02f, H: %02f", scissorRect.origin.x, scissorRect.origin.y, scissorRect.size.width, scissorRect.size.height);
// Handle Retina
scissorRect = CC_RECT_POINTS_TO_PIXELS(scissorRect);
glScissor((GLint) scissorRect.origin.x, (GLint) scissorRect.origin.y,
(GLint) scissorRect.size.width, (GLint) scissorRect.size.height);
}
-(void)postVisit {
glDisable(GL_SCISSOR_TEST);
}
#end
With that imported into LayerB, you can now define it as a CCNodeClip instead of CCLayer.
Some links...
glScissor << cocos2d Forum
Circle shape clipping with opengl-es in cocos2d << StackOverflow
Cocos2d iPhone - Sprite cliping/mask/frame << StackOverflow
Another Cocos2D gem: ClippingNode << Learn-Cocos2d.com
As a side note...
CCScaleTo + CCMoveTo can be avoided if the anchor point for the sprite is centered, so the image stays centered in the container as it scales. (.anchorPoint = ccp(0.5, 0.5);)

EAGLView transparency frames per second

I have a Cocos2d project and I want a constant background throughout the app. In the applicationDidFinishLaunching method of its delegate, I have replaced the line:
[viewController setView:glView];
with
[[viewController view] addSubview:glView];
because I have added subviews to the RootViewController's view in it's initWithNib, and those changes are lost if the view is replaced with glView.
I have also changed the pixelFormat of glView from kEAGLColorFormatRGB565 to kEAGLColorFormatRGBA8. When I make that change, glView becomes transparent and I can see through it, but the fps drops dramatically. If I don't make that change, the view doesn't become transparent, but I don't see the huge drop in fps. I'm talking about a significant drop in fps, from 59.0-60.0 to about 35.0-42.0.
I am using this code right below the addSubview line above to make the view transparent:
glClearColor(0, 0, 0, 0);
director.openGLView.backgroundColor = [UIColor clearColor];
director.openGLView.opaque = NO;
The last two lines are the culprits; commenting them out (both, not just one) causes the large drop in fps, while commenting out the glClearColor line has no effect on fps.
The whole applicationDidFinishLaunching method looks like this:
- (void) applicationDidFinishLaunching:(UIApplication*)application {
window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
if(![CCDirector setDirectorType:kCCDirectorTypeDisplayLink] )
[CCDirector setDirectorType:kCCDirectorTypeDefault];
CCDirector *director = [CCDirector sharedDirector];
// Init the View Controller
viewController = [[RootViewController alloc] initWithNibName:nil bundle:nil];
viewController.wantsFullScreenLayout = YES;
// Create the EAGLView manually
// 1. Create a RGB565 format. Alternative: RGBA8
// 2. depth format of 0 bit. Use 16 or 24 bit for 3d effects, like CCPageTurnTransition
//
EAGLView *glView = [EAGLView viewWithFrame:[window bounds]
pixelFormat:kEAGLColorFormatRGBA8
depthFormat:0
];
// attach the openglView to the director
[director setOpenGLView:glView];
if(![director enableRetinaDisplay:YES] )
CCLOG(#"Retina Display Not supported");
#if GAME_AUTOROTATION == kGameAutorotationUIViewController
[director setDeviceOrientation:kCCDeviceOrientationPortrait];
#else
[director setDeviceOrientation:kCCDeviceOrientationPortrait];
#endif
[director setAnimationInterval:1.0/60];
[director setDisplayFPS:YES];
// make the OpenGLView a child of the view controller
[[viewController view] addSubview:glView];
//***make glView transparent***
glClearColor(0, 0, 0, 0);
director.openGLView.backgroundColor = [UIColor clearColor];
director.openGLView.opaque = NO;
// make the View Controller a child of the main window
[window addSubview:viewController.view];
[window makeKeyAndVisible];
// Default texture format for PNG/BMP/TIFF/JPEG/GIF images
// It can be RGBA8888, RGBA4444, RGB5_A1, RGB565
// You can change anytime.
[CCTexture2D setDefaultAlphaPixelFormat:kCCTexture2DPixelFormat_RGBA8888];
// Removes the startup flicker
[self removeStartupFlicker];
// Run the intro Scene
[[CCDirector sharedDirector] runWithScene:[MainMenu scene]];
}
Any ideas as to why this is happening? I can provide more code if need be.
If you're testing this on a 1st or 2nd generation device, the drop in framerate is to be expected. Nothing you can do about it. These devices are heavily fillrate-limited, and a transparent 32-bit GL view is just asking too much of the device.
If this happens on a 3rd or even 4th generation device, then there's got to be something wrong but I couldn't begin to tell what that might be.
If you're testing the performance on the Simulator, don't. It's irrelevant.

NSView Not updating?

Im working on a drag n' drop view and found some handlers for drag and drop actions on the web. I want to make it so it turns blue when the user drags a file over the drag and drop area and gray again when they exit the drag and drop area. The issues is its not updating when you drag your mouse over it or exit it. Heres some of the code:
- (void)drawRect:(NSRect)rect
{
NSRect bounds = [self bounds];
[[NSColor grayColor] set];
[NSBezierPath fillRect:bounds];
}
- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender {
NSRect bounds = [self bounds];
[[NSColor blueColor] set];
[NSBezierPath fillRect:bounds];
return NSDragOperationCopy;
}
- (void)draggingExited:(id <NSDraggingInfo>)sender {
NSRect bounds = [self bounds];
[[NSColor grayColor] set];
[NSBezierPath fillRect:bounds];
}
Thanks for any help.
Are you calling [yourView: setNeedsDisplay] anywhere?
This is how you let the drawing framework know it needs to message your UIView subclass with drawRect:, so you should do it whenever things have changed. In your case, this probably means when the mouse enters or exits the drop area.
Drawing only works when a context (like a canvas for painting) is set up for you to draw into. When the framework calls -drawRect: it has set up a drawing context for you, so drawing commands like -[NSColor set] and -[NSBezierPath fillRect:] work as you expect.
Outside of -drawRect: there is usually no drawing context set up. Using drawing commands outside of -drawRect: is like waving a paintbrush in the air; there's no canvas, so no painting happens.
In 99.99% of cases, all view drawing should be kept within -drawRect: because NSView does a lot of work that you don't want to do to get the drawing context set up correctly and efficiently.
So, how do you change your view's drawing within your -draggingEntered: and -draggingExited: methods? By side effects.
You're doing the same thing in all three cases: 1) Setting a color and 2) Drawing a rectangle. The only difference is the color changes in each method. So, why not control which color you use in -drawRect: with an ivar, like so:
- (void)draggingEntered:(id <NSDraggingInfo>)sender {
drawBlueColorIvar = YES;
// ...
}
Then in -drawRect: you do this:
- (void)drawRect:(NSRect)rect {
NSColor *color = drawBlueColorIvar ? [NSColor blueColor] : [NSColor grayColor];
[color set];
[NSBezierPath fillRect:rect];
}
(Notice I didn't use [self bounds]. It is more efficient to just draw into the "dirty" rect, when possible.)
Finally, you need some way to tell the framework that your view needs to redraw when drawBlueColorIvar changes. The framework won't draw anything unless it's told it needs to. As Chris Cooper said, you do this with [self setNeedsDisplay:YES]. This should go after any place you change drawBlueColorIvar.

Resources