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