1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2020 Sam Lantinga <slouken@libsdl.org> 4 5 This software is provided 'as-is', without any express or implied 6 warranty. In no event will the authors be held liable for any damages 7 arising from the use of this software. 8 9 Permission is granted to anyone to use this software for any purpose, 10 including commercial applications, and to alter it and redistribute it 11 freely, subject to the following restrictions: 12 13 1. The origin of this software must not be misrepresented; you must not 14 claim that you wrote the original software. If you use this software 15 in a product, an acknowledgment in the product documentation would be 16 appreciated but is not required. 17 2. Altered source versions must be plainly marked as such, and must not be 18 misrepresented as being the original software. 19 3. This notice may not be removed or altered from any source distribution. 20*/ 21#include "../../SDL_internal.h" 22 23#if SDL_VIDEO_DRIVER_UIKIT && (SDL_VIDEO_OPENGL_ES || SDL_VIDEO_OPENGL_ES2) 24 25#include <OpenGLES/EAGLDrawable.h> 26#include <OpenGLES/ES2/glext.h> 27#import "SDL_uikitopenglview.h" 28#include "SDL_uikitwindow.h" 29 30@implementation SDL_uikitopenglview { 31 /* The renderbuffer and framebuffer used to render to this layer. */ 32 GLuint viewRenderbuffer, viewFramebuffer; 33 34 /* The depth buffer that is attached to viewFramebuffer, if it exists. */ 35 GLuint depthRenderbuffer; 36 37 GLenum colorBufferFormat; 38 39 /* format of depthRenderbuffer */ 40 GLenum depthBufferFormat; 41 42 /* The framebuffer and renderbuffer used for rendering with MSAA. */ 43 GLuint msaaFramebuffer, msaaRenderbuffer; 44 45 /* The number of MSAA samples. */ 46 int samples; 47 48 BOOL retainedBacking; 49} 50 51@synthesize context; 52@synthesize backingWidth; 53@synthesize backingHeight; 54 55+ (Class)layerClass 56{ 57 return [CAEAGLLayer class]; 58} 59 60- (instancetype)initWithFrame:(CGRect)frame 61 scale:(CGFloat)scale 62 retainBacking:(BOOL)retained 63 rBits:(int)rBits 64 gBits:(int)gBits 65 bBits:(int)bBits 66 aBits:(int)aBits 67 depthBits:(int)depthBits 68 stencilBits:(int)stencilBits 69 sRGB:(BOOL)sRGB 70 multisamples:(int)multisamples 71 context:(EAGLContext *)glcontext 72{ 73 if ((self = [super initWithFrame:frame])) { 74 const BOOL useStencilBuffer = (stencilBits != 0); 75 const BOOL useDepthBuffer = (depthBits != 0); 76 NSString *colorFormat = nil; 77 78 context = glcontext; 79 samples = multisamples; 80 retainedBacking = retained; 81 82 if (!context || ![EAGLContext setCurrentContext:context]) { 83 SDL_SetError("Could not create OpenGL ES drawable (could not make context current)"); 84 return nil; 85 } 86 87 if (samples > 0) { 88 GLint maxsamples = 0; 89 glGetIntegerv(GL_MAX_SAMPLES, &maxsamples); 90 91 /* Clamp the samples to the max supported count. */ 92 samples = MIN(samples, maxsamples); 93 } 94 95 if (sRGB) { 96 /* sRGB EAGL drawable support was added in iOS 7. */ 97 if (UIKit_IsSystemVersionAtLeast(7.0)) { 98 colorFormat = kEAGLColorFormatSRGBA8; 99 colorBufferFormat = GL_SRGB8_ALPHA8; 100 } else { 101 SDL_SetError("sRGB drawables are not supported."); 102 return nil; 103 } 104 } else if (rBits >= 8 || gBits >= 8 || bBits >= 8 || aBits > 0) { 105 /* if user specifically requests rbg888 or some color format higher than 16bpp */ 106 colorFormat = kEAGLColorFormatRGBA8; 107 colorBufferFormat = GL_RGBA8; 108 } else { 109 /* default case (potentially faster) */ 110 colorFormat = kEAGLColorFormatRGB565; 111 colorBufferFormat = GL_RGB565; 112 } 113 114 CAEAGLLayer *eaglLayer = (CAEAGLLayer *)self.layer; 115 116 eaglLayer.opaque = YES; 117 eaglLayer.drawableProperties = @{ 118 kEAGLDrawablePropertyRetainedBacking:@(retained), 119 kEAGLDrawablePropertyColorFormat:colorFormat 120 }; 121 122 /* Set the appropriate scale (for retina display support) */ 123 self.contentScaleFactor = scale; 124 125 /* Create the color Renderbuffer Object */ 126 glGenRenderbuffers(1, &viewRenderbuffer); 127 glBindRenderbuffer(GL_RENDERBUFFER, viewRenderbuffer); 128 129 if (![context renderbufferStorage:GL_RENDERBUFFER fromDrawable:eaglLayer]) { 130 SDL_SetError("Failed to create OpenGL ES drawable"); 131 return nil; 132 } 133 134 /* Create the Framebuffer Object */ 135 glGenFramebuffers(1, &viewFramebuffer); 136 glBindFramebuffer(GL_FRAMEBUFFER, viewFramebuffer); 137 138 /* attach the color renderbuffer to the FBO */ 139 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, viewRenderbuffer); 140 141 glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &backingWidth); 142 glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &backingHeight); 143 144 if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { 145 SDL_SetError("Failed creating OpenGL ES framebuffer"); 146 return nil; 147 } 148 149 /* When MSAA is used we'll use a separate framebuffer for rendering to, 150 * since we'll need to do an explicit MSAA resolve before presenting. */ 151 if (samples > 0) { 152 glGenFramebuffers(1, &msaaFramebuffer); 153 glBindFramebuffer(GL_FRAMEBUFFER, msaaFramebuffer); 154 155 glGenRenderbuffers(1, &msaaRenderbuffer); 156 glBindRenderbuffer(GL_RENDERBUFFER, msaaRenderbuffer); 157 158 glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, colorBufferFormat, backingWidth, backingHeight); 159 160 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, msaaRenderbuffer); 161 } 162 163 if (useDepthBuffer || useStencilBuffer) { 164 if (useStencilBuffer) { 165 /* Apparently you need to pack stencil and depth into one buffer. */ 166 depthBufferFormat = GL_DEPTH24_STENCIL8_OES; 167 } else if (useDepthBuffer) { 168 /* iOS only uses 32-bit float (exposed as fixed point 24-bit) 169 * depth buffers. */ 170 depthBufferFormat = GL_DEPTH_COMPONENT24_OES; 171 } 172 173 glGenRenderbuffers(1, &depthRenderbuffer); 174 glBindRenderbuffer(GL_RENDERBUFFER, depthRenderbuffer); 175 176 if (samples > 0) { 177 glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, depthBufferFormat, backingWidth, backingHeight); 178 } else { 179 glRenderbufferStorage(GL_RENDERBUFFER, depthBufferFormat, backingWidth, backingHeight); 180 } 181 182 if (useDepthBuffer) { 183 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthRenderbuffer); 184 } 185 if (useStencilBuffer) { 186 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, depthRenderbuffer); 187 } 188 } 189 190 if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { 191 SDL_SetError("Failed creating OpenGL ES framebuffer"); 192 return nil; 193 } 194 195 glBindRenderbuffer(GL_RENDERBUFFER, viewRenderbuffer); 196 197 [self setDebugLabels]; 198 } 199 200 return self; 201} 202 203- (GLuint)drawableRenderbuffer 204{ 205 return viewRenderbuffer; 206} 207 208- (GLuint)drawableFramebuffer 209{ 210 /* When MSAA is used, the MSAA draw framebuffer is used for drawing. */ 211 if (msaaFramebuffer) { 212 return msaaFramebuffer; 213 } else { 214 return viewFramebuffer; 215 } 216} 217 218- (GLuint)msaaResolveFramebuffer 219{ 220 /* When MSAA is used, the MSAA draw framebuffer is used for drawing and the 221 * view framebuffer is used as a MSAA resolve framebuffer. */ 222 if (msaaFramebuffer) { 223 return viewFramebuffer; 224 } else { 225 return 0; 226 } 227} 228 229- (void)updateFrame 230{ 231 GLint prevRenderbuffer = 0; 232 glGetIntegerv(GL_RENDERBUFFER_BINDING, &prevRenderbuffer); 233 234 glBindRenderbuffer(GL_RENDERBUFFER, viewRenderbuffer); 235 [context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer *)self.layer]; 236 237 glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &backingWidth); 238 glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &backingHeight); 239 240 if (msaaRenderbuffer != 0) { 241 glBindRenderbuffer(GL_RENDERBUFFER, msaaRenderbuffer); 242 glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, colorBufferFormat, backingWidth, backingHeight); 243 } 244 245 if (depthRenderbuffer != 0) { 246 glBindRenderbuffer(GL_RENDERBUFFER, depthRenderbuffer); 247 248 if (samples > 0) { 249 glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, depthBufferFormat, backingWidth, backingHeight); 250 } else { 251 glRenderbufferStorage(GL_RENDERBUFFER, depthBufferFormat, backingWidth, backingHeight); 252 } 253 } 254 255 glBindRenderbuffer(GL_RENDERBUFFER, prevRenderbuffer); 256} 257 258- (void)setDebugLabels 259{ 260 if (viewFramebuffer != 0) { 261 glLabelObjectEXT(GL_FRAMEBUFFER, viewFramebuffer, 0, "context FBO"); 262 } 263 264 if (viewRenderbuffer != 0) { 265 glLabelObjectEXT(GL_RENDERBUFFER, viewRenderbuffer, 0, "context color buffer"); 266 } 267 268 if (depthRenderbuffer != 0) { 269 if (depthBufferFormat == GL_DEPTH24_STENCIL8_OES) { 270 glLabelObjectEXT(GL_RENDERBUFFER, depthRenderbuffer, 0, "context depth-stencil buffer"); 271 } else { 272 glLabelObjectEXT(GL_RENDERBUFFER, depthRenderbuffer, 0, "context depth buffer"); 273 } 274 } 275 276 if (msaaFramebuffer != 0) { 277 glLabelObjectEXT(GL_FRAMEBUFFER, msaaFramebuffer, 0, "context MSAA FBO"); 278 } 279 280 if (msaaRenderbuffer != 0) { 281 glLabelObjectEXT(GL_RENDERBUFFER, msaaRenderbuffer, 0, "context MSAA renderbuffer"); 282 } 283} 284 285- (void)swapBuffers 286{ 287 if (msaaFramebuffer) { 288 const GLenum attachments[] = {GL_COLOR_ATTACHMENT0}; 289 290 glBindFramebuffer(GL_DRAW_FRAMEBUFFER, viewFramebuffer); 291 292 /* OpenGL ES 3+ provides explicit MSAA resolves via glBlitFramebuffer. 293 * In OpenGL ES 1 and 2, MSAA resolves must be done via an extension. */ 294 if (context.API >= kEAGLRenderingAPIOpenGLES3) { 295 int w = backingWidth; 296 int h = backingHeight; 297 glBlitFramebuffer(0, 0, w, h, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_NEAREST); 298 299 if (!retainedBacking) { 300 /* Discard the contents of the MSAA drawable color buffer. */ 301 glInvalidateFramebuffer(GL_READ_FRAMEBUFFER, 1, attachments); 302 } 303 } else { 304 glResolveMultisampleFramebufferAPPLE(); 305 306 if (!retainedBacking) { 307 glDiscardFramebufferEXT(GL_READ_FRAMEBUFFER, 1, attachments); 308 } 309 } 310 311 /* We assume the "drawable framebuffer" (MSAA draw framebuffer) was 312 * previously bound... */ 313 glBindFramebuffer(GL_DRAW_FRAMEBUFFER, msaaFramebuffer); 314 } 315 316 /* viewRenderbuffer should always be bound here. Code that binds something 317 * else is responsible for rebinding viewRenderbuffer, to reduce duplicate 318 * state changes. */ 319 [context presentRenderbuffer:GL_RENDERBUFFER]; 320} 321 322- (void)layoutSubviews 323{ 324 [super layoutSubviews]; 325 326 int width = (int) (self.bounds.size.width * self.contentScaleFactor); 327 int height = (int) (self.bounds.size.height * self.contentScaleFactor); 328 329 /* Update the color and depth buffer storage if the layer size has changed. */ 330 if (width != backingWidth || height != backingHeight) { 331 EAGLContext *prevContext = [EAGLContext currentContext]; 332 if (prevContext != context) { 333 [EAGLContext setCurrentContext:context]; 334 } 335 336 [self updateFrame]; 337 338 if (prevContext != context) { 339 [EAGLContext setCurrentContext:prevContext]; 340 } 341 } 342} 343 344- (void)destroyFramebuffer 345{ 346 if (viewFramebuffer != 0) { 347 glDeleteFramebuffers(1, &viewFramebuffer); 348 viewFramebuffer = 0; 349 } 350 351 if (viewRenderbuffer != 0) { 352 glDeleteRenderbuffers(1, &viewRenderbuffer); 353 viewRenderbuffer = 0; 354 } 355 356 if (depthRenderbuffer != 0) { 357 glDeleteRenderbuffers(1, &depthRenderbuffer); 358 depthRenderbuffer = 0; 359 } 360 361 if (msaaFramebuffer != 0) { 362 glDeleteFramebuffers(1, &msaaFramebuffer); 363 msaaFramebuffer = 0; 364 } 365 366 if (msaaRenderbuffer != 0) { 367 glDeleteRenderbuffers(1, &msaaRenderbuffer); 368 msaaRenderbuffer = 0; 369 } 370} 371 372- (void)dealloc 373{ 374 if (context && context == [EAGLContext currentContext]) { 375 [self destroyFramebuffer]; 376 [EAGLContext setCurrentContext:nil]; 377 } 378} 379 380@end 381 382#endif /* SDL_VIDEO_DRIVER_UIKIT */ 383 384/* vi: set ts=4 sw=4 expandtab: */ 385