| /* |
| Simple DirectMedia Layer |
| Copyright (C) 1997-2016 Sam Lantinga <slouken@libsdl.org> |
| |
| This software is provided 'as-is', without any express or implied |
| warranty. In no event will the authors be held liable for any damages |
| arising from the use of this software. |
| |
| Permission is granted to anyone to use this software for any purpose, |
| including commercial applications, and to alter it and redistribute it |
| freely, subject to the following restrictions: |
| |
| 1. The origin of this software must not be misrepresented; you must not |
| claim that you wrote the original software. If you use this software |
| in a product, an acknowledgment in the product documentation would be |
| appreciated but is not required. |
| 2. Altered source versions must be plainly marked as such, and must not be |
| misrepresented as being the original software. |
| 3. This notice may not be removed or altered from any source distribution. |
| */ |
| #include "../../SDL_internal.h" |
| |
| #if SDL_VIDEO_DRIVER_UIKIT |
| |
| #include <OpenGLES/EAGLDrawable.h> |
| #include <OpenGLES/ES2/glext.h> |
| #import "SDL_uikitopenglview.h" |
| #include "SDL_uikitwindow.h" |
| |
| @implementation SDL_uikitopenglview { |
| /* The renderbuffer and framebuffer used to render to this layer. */ |
| GLuint viewRenderbuffer, viewFramebuffer; |
| |
| /* The depth buffer that is attached to viewFramebuffer, if it exists. */ |
| GLuint depthRenderbuffer; |
| |
| GLenum colorBufferFormat; |
| |
| /* format of depthRenderbuffer */ |
| GLenum depthBufferFormat; |
| |
| /* The framebuffer and renderbuffer used for rendering with MSAA. */ |
| GLuint msaaFramebuffer, msaaRenderbuffer; |
| |
| /* The number of MSAA samples. */ |
| int samples; |
| |
| BOOL retainedBacking; |
| } |
| |
| @synthesize context; |
| @synthesize backingWidth; |
| @synthesize backingHeight; |
| |
| + (Class)layerClass |
| { |
| return [CAEAGLLayer class]; |
| } |
| |
| - (instancetype)initWithFrame:(CGRect)frame |
| scale:(CGFloat)scale |
| retainBacking:(BOOL)retained |
| rBits:(int)rBits |
| gBits:(int)gBits |
| bBits:(int)bBits |
| aBits:(int)aBits |
| depthBits:(int)depthBits |
| stencilBits:(int)stencilBits |
| sRGB:(BOOL)sRGB |
| multisamples:(int)multisamples |
| context:(EAGLContext *)glcontext |
| { |
| if ((self = [super initWithFrame:frame])) { |
| const BOOL useStencilBuffer = (stencilBits != 0); |
| const BOOL useDepthBuffer = (depthBits != 0); |
| NSString *colorFormat = nil; |
| |
| context = glcontext; |
| samples = multisamples; |
| retainedBacking = retained; |
| |
| if (!context || ![EAGLContext setCurrentContext:context]) { |
| SDL_SetError("Could not create OpenGL ES drawable (could not make context current)"); |
| return nil; |
| } |
| |
| if (samples > 0) { |
| GLint maxsamples = 0; |
| glGetIntegerv(GL_MAX_SAMPLES, &maxsamples); |
| |
| /* Clamp the samples to the max supported count. */ |
| samples = MIN(samples, maxsamples); |
| } |
| |
| if (sRGB) { |
| /* sRGB EAGL drawable support was added in iOS 7. */ |
| if (UIKit_IsSystemVersionAtLeast(7.0)) { |
| colorFormat = kEAGLColorFormatSRGBA8; |
| colorBufferFormat = GL_SRGB8_ALPHA8; |
| } else { |
| SDL_SetError("sRGB drawables are not supported."); |
| return nil; |
| } |
| } else if (rBits >= 8 || gBits >= 8 || bBits >= 8) { |
| /* if user specifically requests rbg888 or some color format higher than 16bpp */ |
| colorFormat = kEAGLColorFormatRGBA8; |
| colorBufferFormat = GL_RGBA8; |
| } else { |
| /* default case (potentially faster) */ |
| colorFormat = kEAGLColorFormatRGB565; |
| colorBufferFormat = GL_RGB565; |
| } |
| |
| CAEAGLLayer *eaglLayer = (CAEAGLLayer *)self.layer; |
| |
| eaglLayer.opaque = YES; |
| eaglLayer.drawableProperties = @{ |
| kEAGLDrawablePropertyRetainedBacking:@(retained), |
| kEAGLDrawablePropertyColorFormat:colorFormat |
| }; |
| |
| /* Set the appropriate scale (for retina display support) */ |
| self.contentScaleFactor = scale; |
| |
| /* Create the color Renderbuffer Object */ |
| glGenRenderbuffers(1, &viewRenderbuffer); |
| glBindRenderbuffer(GL_RENDERBUFFER, viewRenderbuffer); |
| |
| if (![context renderbufferStorage:GL_RENDERBUFFER fromDrawable:eaglLayer]) { |
| SDL_SetError("Failed to create OpenGL ES drawable"); |
| return nil; |
| } |
| |
| /* Create the Framebuffer Object */ |
| glGenFramebuffers(1, &viewFramebuffer); |
| glBindFramebuffer(GL_FRAMEBUFFER, viewFramebuffer); |
| |
| /* attach the color renderbuffer to the FBO */ |
| glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, viewRenderbuffer); |
| |
| glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &backingWidth); |
| glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &backingHeight); |
| |
| if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { |
| SDL_SetError("Failed creating OpenGL ES framebuffer"); |
| return nil; |
| } |
| |
| /* When MSAA is used we'll use a separate framebuffer for rendering to, |
| * since we'll need to do an explicit MSAA resolve before presenting. */ |
| if (samples > 0) { |
| glGenFramebuffers(1, &msaaFramebuffer); |
| glBindFramebuffer(GL_FRAMEBUFFER, msaaFramebuffer); |
| |
| glGenRenderbuffers(1, &msaaRenderbuffer); |
| glBindRenderbuffer(GL_RENDERBUFFER, msaaRenderbuffer); |
| |
| glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, colorBufferFormat, backingWidth, backingHeight); |
| |
| glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, msaaRenderbuffer); |
| } |
| |
| if (useDepthBuffer || useStencilBuffer) { |
| if (useStencilBuffer) { |
| /* Apparently you need to pack stencil and depth into one buffer. */ |
| depthBufferFormat = GL_DEPTH24_STENCIL8_OES; |
| } else if (useDepthBuffer) { |
| /* iOS only uses 32-bit float (exposed as fixed point 24-bit) |
| * depth buffers. */ |
| depthBufferFormat = GL_DEPTH_COMPONENT24_OES; |
| } |
| |
| glGenRenderbuffers(1, &depthRenderbuffer); |
| glBindRenderbuffer(GL_RENDERBUFFER, depthRenderbuffer); |
| |
| if (samples > 0) { |
| glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, depthBufferFormat, backingWidth, backingHeight); |
| } else { |
| glRenderbufferStorage(GL_RENDERBUFFER, depthBufferFormat, backingWidth, backingHeight); |
| } |
| |
| if (useDepthBuffer) { |
| glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthRenderbuffer); |
| } |
| if (useStencilBuffer) { |
| glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, depthRenderbuffer); |
| } |
| } |
| |
| if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { |
| SDL_SetError("Failed creating OpenGL ES framebuffer"); |
| return nil; |
| } |
| |
| glBindRenderbuffer(GL_RENDERBUFFER, viewRenderbuffer); |
| |
| [self setDebugLabels]; |
| } |
| |
| return self; |
| } |
| |
| - (GLuint)drawableRenderbuffer |
| { |
| return viewRenderbuffer; |
| } |
| |
| - (GLuint)drawableFramebuffer |
| { |
| /* When MSAA is used, the MSAA draw framebuffer is used for drawing. */ |
| if (msaaFramebuffer) { |
| return msaaFramebuffer; |
| } else { |
| return viewFramebuffer; |
| } |
| } |
| |
| - (GLuint)msaaResolveFramebuffer |
| { |
| /* When MSAA is used, the MSAA draw framebuffer is used for drawing and the |
| * view framebuffer is used as a MSAA resolve framebuffer. */ |
| if (msaaFramebuffer) { |
| return viewFramebuffer; |
| } else { |
| return 0; |
| } |
| } |
| |
| - (void)updateFrame |
| { |
| GLint prevRenderbuffer = 0; |
| glGetIntegerv(GL_RENDERBUFFER_BINDING, &prevRenderbuffer); |
| |
| glBindRenderbuffer(GL_RENDERBUFFER, viewRenderbuffer); |
| [context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer *)self.layer]; |
| |
| glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &backingWidth); |
| glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &backingHeight); |
| |
| if (msaaRenderbuffer != 0) { |
| glBindRenderbuffer(GL_RENDERBUFFER, msaaRenderbuffer); |
| glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, colorBufferFormat, backingWidth, backingHeight); |
| } |
| |
| if (depthRenderbuffer != 0) { |
| glBindRenderbuffer(GL_RENDERBUFFER, depthRenderbuffer); |
| |
| if (samples > 0) { |
| glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, depthBufferFormat, backingWidth, backingHeight); |
| } else { |
| glRenderbufferStorage(GL_RENDERBUFFER, depthBufferFormat, backingWidth, backingHeight); |
| } |
| } |
| |
| glBindRenderbuffer(GL_RENDERBUFFER, prevRenderbuffer); |
| } |
| |
| - (void)setDebugLabels |
| { |
| if (viewFramebuffer != 0) { |
| glLabelObjectEXT(GL_FRAMEBUFFER, viewFramebuffer, 0, "context FBO"); |
| } |
| |
| if (viewRenderbuffer != 0) { |
| glLabelObjectEXT(GL_RENDERBUFFER, viewRenderbuffer, 0, "context color buffer"); |
| } |
| |
| if (depthRenderbuffer != 0) { |
| if (depthBufferFormat == GL_DEPTH24_STENCIL8_OES) { |
| glLabelObjectEXT(GL_RENDERBUFFER, depthRenderbuffer, 0, "context depth-stencil buffer"); |
| } else { |
| glLabelObjectEXT(GL_RENDERBUFFER, depthRenderbuffer, 0, "context depth buffer"); |
| } |
| } |
| |
| if (msaaFramebuffer != 0) { |
| glLabelObjectEXT(GL_FRAMEBUFFER, msaaFramebuffer, 0, "context MSAA FBO"); |
| } |
| |
| if (msaaRenderbuffer != 0) { |
| glLabelObjectEXT(GL_RENDERBUFFER, msaaRenderbuffer, 0, "context MSAA renderbuffer"); |
| } |
| } |
| |
| - (void)swapBuffers |
| { |
| if (msaaFramebuffer) { |
| const GLenum attachments[] = {GL_COLOR_ATTACHMENT0}; |
| |
| glBindFramebuffer(GL_DRAW_FRAMEBUFFER, viewFramebuffer); |
| |
| /* OpenGL ES 3+ provides explicit MSAA resolves via glBlitFramebuffer. |
| * In OpenGL ES 1 and 2, MSAA resolves must be done via an extension. */ |
| if (context.API >= kEAGLRenderingAPIOpenGLES3) { |
| int w = backingWidth; |
| int h = backingHeight; |
| glBlitFramebuffer(0, 0, w, h, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_NEAREST); |
| |
| if (!retainedBacking) { |
| /* Discard the contents of the MSAA drawable color buffer. */ |
| glInvalidateFramebuffer(GL_READ_FRAMEBUFFER, 1, attachments); |
| } |
| } else { |
| glResolveMultisampleFramebufferAPPLE(); |
| |
| if (!retainedBacking) { |
| glDiscardFramebufferEXT(GL_READ_FRAMEBUFFER, 1, attachments); |
| } |
| } |
| |
| /* We assume the "drawable framebuffer" (MSAA draw framebuffer) was |
| * previously bound... */ |
| glBindFramebuffer(GL_DRAW_FRAMEBUFFER, msaaFramebuffer); |
| } |
| |
| /* viewRenderbuffer should always be bound here. Code that binds something |
| * else is responsible for rebinding viewRenderbuffer, to reduce duplicate |
| * state changes. */ |
| [context presentRenderbuffer:GL_RENDERBUFFER]; |
| } |
| |
| - (void)layoutSubviews |
| { |
| [super layoutSubviews]; |
| |
| int width = (int) (self.bounds.size.width * self.contentScaleFactor); |
| int height = (int) (self.bounds.size.height * self.contentScaleFactor); |
| |
| /* Update the color and depth buffer storage if the layer size has changed. */ |
| if (width != backingWidth || height != backingHeight) { |
| EAGLContext *prevContext = [EAGLContext currentContext]; |
| if (prevContext != context) { |
| [EAGLContext setCurrentContext:context]; |
| } |
| |
| [self updateFrame]; |
| |
| if (prevContext != context) { |
| [EAGLContext setCurrentContext:prevContext]; |
| } |
| } |
| } |
| |
| - (void)destroyFramebuffer |
| { |
| if (viewFramebuffer != 0) { |
| glDeleteFramebuffers(1, &viewFramebuffer); |
| viewFramebuffer = 0; |
| } |
| |
| if (viewRenderbuffer != 0) { |
| glDeleteRenderbuffers(1, &viewRenderbuffer); |
| viewRenderbuffer = 0; |
| } |
| |
| if (depthRenderbuffer != 0) { |
| glDeleteRenderbuffers(1, &depthRenderbuffer); |
| depthRenderbuffer = 0; |
| } |
| |
| if (msaaFramebuffer != 0) { |
| glDeleteFramebuffers(1, &msaaFramebuffer); |
| msaaFramebuffer = 0; |
| } |
| |
| if (msaaRenderbuffer != 0) { |
| glDeleteRenderbuffers(1, &msaaRenderbuffer); |
| msaaRenderbuffer = 0; |
| } |
| } |
| |
| - (void)dealloc |
| { |
| if (context && context == [EAGLContext currentContext]) { |
| [self destroyFramebuffer]; |
| [EAGLContext setCurrentContext:nil]; |
| } |
| } |
| |
| @end |
| |
| #endif /* SDL_VIDEO_DRIVER_UIKIT */ |
| |
| /* vi: set ts=4 sw=4 expandtab: */ |