How to avoid momentary stretching on autorotation

2019-02-07 01:23发布

This has been bugging me recently. It is quite straightforward to put together an OpenGL ES app that supports both portrait and landscape. But during autorotation, the system seems to just forcibly stretch the render buffer to the new dimensions once, autorotate, and then call the usual -layoutSubviews -> -resizeFromLayer: etc. so the drawables can be adjusted to the new viewport dimensions.

Most apps I've seen that support both portrait and landscape seem to settle for this easy approach. But I wonder if I can do better...

Perhaps I should intercept the autorotation before it happens (using UIViewController's usual methods), "expand" once the render buffer to a perfect square of the longest screen size (e.g., 1136px x 1136px on an iPhone 5) - so that it 'bleeds' off screen, perform the autorrotation (no renderbuffer size change and hence no stretching, just as when you switch between e.g. two landscape orientations), and finally adjust the framebuffer again to dispose the invisible, "wasted" margins off screen? (of course I could have a square framebuffer all along, but that would be inefficient fillrate-wise)

Any suggestions? Any better way to accomplish this that I haven't thought about?

EDIT

I modified my -resizeFromLayer: code as follows:

CGRect bounds = [layer bounds];

// Enlarge layer to square of the longest side:
if (bounds.size.width < bounds.size.height) {
    bounds.size.width = bounds.size.height;
}
else{
    bounds.size.height = bounds.size.width;
}

[layer setBounds:bounds];


// Adjust size to match view's layer:
[_mainContext renderbufferStorage:GL_RENDERBUFFER
                         fromDrawable:(CAEAGLLayer*) [_view layer]];

// Query the new size:
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH,  &_backingWidth);
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &_backingHeight);

// (etc., you know the drill...)

...And it works! As it stands right now, I have a wasteful, off-screen-bleeding square renderbuffer all along, but this is just a proof of concept. On production code, I would perform said resizing-to-square just before autorotation, and resize back to screen dimensions afterwards.

1条回答
走好不送
2楼-- · 2019-02-07 01:53

I managed to fix this fairly easily in my 3D app by fetching the view size from the GLKView presentationLayer. This will give the current view size during the rotation animation, and the correct projection matrix can be calculated based on this size during the update step. This prevents any incorrect aspect ratio distortion.

If you want to try this, you can add the following lines to the OpenGL Game template in Xcode:

- (void)update
{
    // get the size from the presentation layer, so that animating does not squish
    CALayer *presentationLayer = [self.view.layer presentationLayer];
    CGSize layerSize = presentationLayer.bounds.size;
    float aspect = fabsf(layerSize.width / layerSize.height);
    GLKMatrix4 projectionMatrix = GLKMatrix4MakePerspective(GLKMathDegreesToRadians(65.0f), aspect, 0.1f, 100.0f);

    ...
}
查看更多
登录 后发表回答