1/*
2-----------------------------------------------------------------------------
3This source file is part of OGRE
4    (Object-oriented Graphics Rendering Engine)
5For the latest info, see http://www.ogre3d.org/
6
7Copyright (c) 2000-2013 Torus Knot Software Ltd
8
9Permission is hereby granted, free of charge, to any person obtaining a copy
10of this software and associated documentation files (the "Software"), to deal
11in the Software without restriction, including without limitation the rights
12to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13copies of the Software, and to permit persons to whom the Software is
14furnished to do so, subject to the following conditions:
15
16The above copyright notice and this permission notice shall be included in
17all copies or substantial portions of the Software.
18
19THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25THE SOFTWARE.
26--------------------------------------------------------------------------*/
27
28#include "OgreEAGLWindow.h"
29
30#include "OgreEAGLSupport.h"
31#include "OgreEAGLESContext.h"
32
33#include "OgreRoot.h"
34#include "OgreWindowEventUtilities.h"
35
36#include "OgreGLESPixelFormat.h"
37#include "OgreGLESRenderSystem.h"
38
39namespace Ogre {
40    EAGLWindow::EAGLWindow(EAGLSupport *glsupport)
41        :   mClosed(false),
42            mVisible(false),
43            mIsExternal(false),
44            mUsingExternalView(false),
45            mUsingExternalViewController(false),
46            mIsContentScalingSupported(false),
47            mContentScalingFactor(1.0),
48            mCurrentOSVersion(0.0),
49            mGLSupport(glsupport),
50            mContext(NULL),
51            mWindow(nil),
52            mView(nil),
53            mViewController(nil)
54    {
55        mIsFullScreen = true;
56        mActive = true;
57        mHwGamma = false;
58
59        // Check for content scaling.  iOS 4 or later
60        mCurrentOSVersion = [[[UIDevice currentDevice] systemVersion] floatValue];
61        if(mCurrentOSVersion >= 4.0)
62            mIsContentScalingSupported = true;
63    }
64
65    EAGLWindow::~EAGLWindow()
66    {
67        destroy();
68
69        if (mContext != NULL)
70        {
71            OGRE_DELETE mContext;
72        }
73
74        mContext = NULL;
75    }
76
77    void EAGLWindow::destroy(void)
78    {
79        if (mClosed)
80        {
81            return;
82        }
83
84        mClosed = true;
85        mActive = false;
86
87        if (!mIsExternal)
88        {
89            WindowEventUtilities::_removeRenderWindow(this);
90
91            [mWindow release];
92            mWindow = nil;
93        }
94
95        if (mIsFullScreen)
96        {
97            switchFullScreen(false);
98        }
99
100        if(!mUsingExternalView)
101            [mView release];
102
103        if(!mUsingExternalViewController)
104            [mViewController release];
105    }
106
107    void EAGLWindow::setFullscreen(bool fullscreen, uint width, uint height)
108    {
109    }
110
111    void EAGLWindow::reposition(int left, int top)
112	{
113	}
114
115	void EAGLWindow::resize(unsigned int width, unsigned int height)
116	{
117        if(!mWindow) return;
118
119        Real w = mContentScalingFactor, h = mContentScalingFactor;
120
121        // Check the orientation of the view controller and adjust dimensions
122        if (UIInterfaceOrientationIsPortrait(mViewController.interfaceOrientation))
123        {
124            h *= std::max(width, height);
125            w *= std::min(width, height);
126        }
127        else
128        {
129            w *= std::max(width, height);
130            h *= std::min(width, height);
131        }
132
133        // Check if the window size really changed
134        if(mWidth == w && mHeight == h)
135            return;
136
137        // Destroy and recreate the framebuffer with new dimensions
138        mContext->destroyFramebuffer();
139
140        mWidth = w;
141        mHeight = h;
142
143        mContext->createFramebuffer();
144
145        for (ViewportList::iterator it = mViewportList.begin(); it != mViewportList.end(); ++it)
146        {
147            (*it).second->_updateDimensions();
148        }
149	}
150
151	void EAGLWindow::windowMovedOrResized()
152	{
153		CGRect frame = [mView frame];
154		mWidth = (unsigned int)frame.size.width;
155		mHeight = (unsigned int)frame.size.height;
156        mLeft = (int)frame.origin.x;
157        mTop = (int)frame.origin.y+(int)frame.size.height;
158
159        for (ViewportList::iterator it = mViewportList.begin(); it != mViewportList.end(); ++it)
160        {
161            (*it).second->_updateDimensions();
162        }
163	}
164
165    void EAGLWindow::_beginUpdate(void)
166    {
167        // Call the base class method first
168        RenderTarget::_beginUpdate();
169
170        if(mContext->mIsMultiSampleSupported && mContext->mNumSamples > 0)
171        {
172            // Bind the FSAA buffer if we're doing multisampling
173            glBindFramebufferOES(GL_FRAMEBUFFER_OES, mContext->mFSAAFramebuffer);
174            GL_CHECK_ERROR
175        }
176    }
177
178    void EAGLWindow::initNativeCreatedWindow(const NameValuePairList *miscParams)
179    {
180        // This method is called from within create() and after parameters have been parsed.
181        // If the window, view or view controller objects are nil at this point, it is safe
182        // to assume that external handles are either not being used or are invalid and
183        // we can create our own.
184        NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
185
186        uint w = 0, h = 0;
187
188        ConfigOptionMap::const_iterator opt;
189        ConfigOptionMap::const_iterator end = mGLSupport->getConfigOptions().end();
190        NameValuePairList::const_iterator param;
191
192        if ((opt = mGLSupport->getConfigOptions().find("Video Mode")) != end)
193        {
194            String val = opt->second.currentValue;
195            String::size_type pos = val.find('x');
196
197            if (pos != String::npos)
198            {
199                w = StringConverter::parseUnsignedInt(val.substr(0, pos));
200                h = StringConverter::parseUnsignedInt(val.substr(pos + 1));
201            }
202        }
203
204        // Set us up with an external window, or create our own.
205        if(!mIsExternal)
206        {
207            mWindow = [[[UIWindow alloc] initWithFrame:CGRectMake(0, 0, w, h)] retain];
208        }
209
210        OgreAssert(mWindow != nil, "EAGLWindow: Failed to create native window");
211
212        // Set up the view
213        if(!mUsingExternalView)
214        {
215            mView = [[EAGLView alloc] initWithFrame:CGRectMake(0, 0, w, h)];
216            mView.opaque = YES;
217
218            // Use the default scale factor of the screen
219            // See Apple's documentation on supporting high resolution devices for more info
220            mView.contentScaleFactor = mContentScalingFactor;
221        }
222
223        OgreAssert(mView != nil, "EAGLWindow: Failed to create view");
224
225        [mView setMWindowName:mName];
226
227        OgreAssert([mView.layer isKindOfClass:[CAEAGLLayer class]], "EAGLWindow: View's Core Animation layer is not a CAEAGLLayer. This is a requirement for using OpenGL ES for drawing.");
228
229        CAEAGLLayer *eaglLayer = (CAEAGLLayer *)mView.layer;
230        OgreAssert(eaglLayer != nil, "EAGLWindow: Failed to retrieve a pointer to the view's Core Animation layer");
231
232        eaglLayer.opaque = YES;
233        eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:
234                                        [NSNumber numberWithBool:NO], kEAGLDrawablePropertyRetainedBacking,
235                                        kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat, nil];
236        // Set up the view controller
237        if(!mUsingExternalViewController)
238        {
239            mViewController = [[EAGLViewController alloc] init];
240        }
241
242        OgreAssert(mViewController != nil, "EAGLWindow: Failed to create view controller");
243
244        if(mViewController.view != mView)
245            mViewController.view = mView;
246
247        CFDictionaryRef dict;   // TODO: Dummy dictionary for now
248        if(eaglLayer)
249        {
250            EAGLSharegroup *group = nil;
251            NameValuePairList::const_iterator option;
252
253            if ((option = miscParams->find("externalSharegroup")) != miscParams->end())
254            {
255                group = (EAGLSharegroup *)StringConverter::parseUnsignedLong(option->second);
256                LogManager::getSingleton().logMessage("iOS: Using an external EAGLSharegroup");
257            }
258
259            mContext = mGLSupport->createNewContext(dict, eaglLayer, group);
260
261            mContext->mIsMultiSampleSupported = true;
262            mContext->mNumSamples = mFSAA;
263        }
264
265        OgreAssert(mContext != nil, "EAGLWindow: Failed to create OpenGL ES context");
266
267        [mWindow addSubview:mViewController.view];
268
269        mViewController.mGLSupport = mGLSupport;
270
271        if(!mUsingExternalViewController)
272            mWindow.rootViewController = mViewController;
273
274        if(!mUsingExternalView)
275            [mView release];
276
277        [mWindow makeKeyAndVisible];
278
279        mContext->createFramebuffer();
280
281        // If content scaling is supported, the window size will be smaller than the GL pixel buffer
282        // used to render.  Report the buffer size for reference.
283        StringStream ss;
284
285        ss  << "iOS: Window created " << w << " x " << h
286            << " with backing store size " << mContext->mBackingWidth << " x " << mContext->mBackingHeight;
287        if(mIsContentScalingSupported)
288        {
289            ss << " using content scaling factor " << std::fixed << std::setprecision(1) << mContentScalingFactor;
290        }
291        LogManager::getSingleton().logMessage(ss.str());
292
293        [pool release];
294    }
295
296    void EAGLWindow::create(const String& name, uint width, uint height,
297                                bool fullScreen, const NameValuePairList *miscParams)
298    {
299        short frequency = 0;
300        bool vsync = false;
301		int left = 0;
302		int top  = 0;
303
304        mIsFullScreen = fullScreen;
305        mName = name;
306        mWidth = width;
307        mHeight = height;
308
309        // Check the configuration. This may be overridden later by the value sent via miscParams
310        ConfigOptionMap::const_iterator configOpt;
311        ConfigOptionMap::const_iterator configEnd = mGLSupport->getConfigOptions().end();
312        if ((configOpt = mGLSupport->getConfigOptions().find("Content Scaling Factor")) != configEnd)
313        {
314            mContentScalingFactor = StringConverter::parseReal(configOpt->second.currentValue);
315        }
316
317        if (miscParams)
318        {
319            NameValuePairList::const_iterator opt;
320            NameValuePairList::const_iterator end = miscParams->end();
321
322            // Note: Some platforms support AA inside ordinary windows
323            if ((opt = miscParams->find("FSAA")) != end)
324            {
325                mFSAA = StringConverter::parseUnsignedInt(opt->second);
326            }
327
328            if ((opt = miscParams->find("displayFrequency")) != end)
329            {
330                frequency = (short)StringConverter::parseInt(opt->second);
331            }
332
333            if ((opt = miscParams->find("contentScalingFactor")) != end)
334            {
335                mContentScalingFactor = StringConverter::parseReal(opt->second);
336            }
337
338            if ((opt = miscParams->find("vsync")) != end)
339            {
340                vsync = StringConverter::parseBool(opt->second);
341            }
342
343            if ((opt = miscParams->find("left")) != end)
344            {
345                left = StringConverter::parseInt(opt->second);
346            }
347
348            if ((opt = miscParams->find("top")) != end)
349            {
350                top = StringConverter::parseInt(opt->second);
351            }
352
353            if ((opt = miscParams->find("title")) != end)
354            {
355                mName = opt->second;
356            }
357
358            if ((opt = miscParams->find("externalWindowHandle")) != end)
359            {
360                mWindow = (UIWindow *)StringConverter::parseUnsignedLong(opt->second);
361                mIsExternal = true;
362                LogManager::getSingleton().logMessage("iOS: Using an external window handle");
363            }
364
365            if ((opt = miscParams->find("externalViewHandle")) != end)
366            {
367                mView = (EAGLView *)StringConverter::parseUnsignedLong(opt->second);
368                CGRect b = [mView bounds];
369                mWidth = b.size.width;
370                mHeight = b.size.height;
371                mUsingExternalView = true;
372                LogManager::getSingleton().logMessage("iOS: Using an external view handle");
373            }
374
375            if ((opt = miscParams->find("externalViewControllerHandle")) != end)
376            {
377                mViewController = (EAGLViewController *)StringConverter::parseUnsignedLong(opt->second);
378                if(mViewController.view != nil)
379                    mView = (EAGLView *)mViewController.view;
380                mUsingExternalViewController = true;
381                LogManager::getSingleton().logMessage("iOS: Using an external view controller handle");
382            }
383		}
384
385        initNativeCreatedWindow(miscParams);
386
387        left = top = 0;
388		mLeft = left;
389		mTop = top;
390
391        // Resize, taking content scaling factor into account
392        resize(mWidth, mHeight);
393
394		mActive = true;
395		mVisible = true;
396		mClosed = false;
397    }
398
399    void EAGLWindow::swapBuffers()
400    {
401        if (mClosed)
402        {
403            return;
404        }
405
406        unsigned int attachmentCount = 0;
407        GLenum attachments[3];
408        GLESRenderSystem *rs =
409            static_cast<GLESRenderSystem*>(Root::getSingleton().getRenderSystem());
410        unsigned int buffers = rs->getDiscardBuffers();
411
412        if(buffers & FBT_COLOUR)
413        {
414            attachments[attachmentCount++] = GL_COLOR_ATTACHMENT0_OES;
415        }
416        if(buffers & FBT_DEPTH)
417        {
418            attachments[attachmentCount++] = GL_DEPTH_ATTACHMENT_OES;
419        }
420        if(buffers & FBT_STENCIL)
421        {
422            attachments[attachmentCount++] = GL_STENCIL_ATTACHMENT_OES;
423        }
424
425        if(mContext->mIsMultiSampleSupported && mContext->mNumSamples > 0)
426        {
427            glDisable(GL_SCISSOR_TEST);
428            glBindFramebufferOES(GL_READ_FRAMEBUFFER_APPLE, mContext->mFSAAFramebuffer);
429            GL_CHECK_ERROR
430            glBindFramebufferOES(GL_DRAW_FRAMEBUFFER_APPLE, mContext->mViewFramebuffer);
431            GL_CHECK_ERROR
432            glResolveMultisampleFramebufferAPPLE();
433            GL_CHECK_ERROR
434            glDiscardFramebufferEXT(GL_READ_FRAMEBUFFER_APPLE, attachmentCount, attachments);
435            GL_CHECK_ERROR
436
437            glBindFramebufferOES(GL_FRAMEBUFFER_OES, mContext->mViewFramebuffer);
438            GL_CHECK_ERROR
439        }
440        else
441        {
442            glBindFramebufferOES(GL_FRAMEBUFFER_OES, mContext->mViewFramebuffer);
443            GL_CHECK_ERROR
444            glDiscardFramebufferEXT(GL_FRAMEBUFFER_OES, attachmentCount, attachments);
445            GL_CHECK_ERROR
446        }
447
448        glBindRenderbufferOES(GL_RENDERBUFFER_OES, mContext->mViewRenderbuffer);
449        GL_CHECK_ERROR
450        if ([mContext->getContext() presentRenderbuffer:GL_RENDERBUFFER_OES] == NO)
451        {
452            GL_CHECK_ERROR
453            OGRE_EXCEPT(Exception::ERR_RENDERINGAPI_ERROR,
454                        "Failed to swap buffers in ",
455                        __FUNCTION__);
456        }
457    }
458
459    void EAGLWindow::getCustomAttribute( const String& name, void* pData )
460    {
461		if( name == "GLCONTEXT" )
462		{
463			*static_cast<EAGLESContext**>(pData) = mContext;
464			return;
465		}
466
467        if( name == "SHAREGROUP" )
468		{
469            *(void**)(pData) = mContext->getContext().sharegroup;
470            return;
471		}
472
473		if( name == "WINDOW" )
474		{
475			*(void**)(pData) = mWindow;
476			return;
477		}
478
479		if( name == "VIEW" )
480		{
481			*(void**)(pData) = mViewController.view;
482			return;
483		}
484
485        if( name == "VIEWCONTROLLER" )
486		{
487            *(void**)(pData) = mViewController;
488            return;
489		}
490	}
491
492    void EAGLWindow::copyContentsToMemory(const PixelBox &dst, FrameBuffer buffer)
493    {
494        if(dst.format != PF_A8R8G8B8)
495            OGRE_EXCEPT(Exception::ERR_INVALIDPARAMS, "Only PF_A8R8G8B8 is a supported format for OpenGL ES", __FUNCTION__);
496
497        if ((dst.right > mWidth) ||
498			(dst.bottom > mHeight) ||
499			(dst.front != 0) || (dst.back != 1))
500		{
501			OGRE_EXCEPT(Exception::ERR_INVALIDPARAMS,
502                        "Invalid box.",
503                        __FUNCTION__ );
504		}
505
506		if (buffer == FB_AUTO)
507		{
508			buffer = mIsFullScreen ? FB_FRONT : FB_BACK;
509		}
510
511		// Switch context if different from current one
512		RenderSystem* rsys = Root::getSingleton().getRenderSystem();
513		rsys->_setViewport(this->getViewport(0));
514
515        // The following code is adapted from Apple Technical Q & A QA1704
516        // http://developer.apple.com/library/ios/#qa/qa1704/_index.html
517        NSInteger width = dst.getWidth(), height = dst.getHeight();
518        NSInteger dataLength = width * height * 4;
519        GLubyte *data = (GLubyte*)malloc(dataLength * sizeof(GLubyte));
520
521        // Read pixel data from the framebuffer
522        glPixelStorei(GL_PACK_ALIGNMENT, 4);
523        GL_CHECK_ERROR
524        glReadPixels((GLint)0, (GLint)(mHeight - dst.getHeight()),
525                     (GLsizei)dst.getWidth(), (GLsizei)dst.getHeight(),
526                     GL_RGBA, GL_UNSIGNED_BYTE, data);
527        GL_CHECK_ERROR
528
529        // Create a CGImage with the pixel data
530        // If your OpenGL ES content is opaque, use kCGImageAlphaNoneSkipLast to ignore the alpha channel
531        // otherwise, use kCGImageAlphaPremultipliedLast
532        CGDataProviderRef ref = CGDataProviderCreateWithData(NULL, data, dataLength, NULL);
533        CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
534        CGImageRef iref = CGImageCreate(width, height, 8, 32, width * 4, colorspace,
535                                        kCGBitmapByteOrder32Big | kCGImageAlphaPremultipliedLast,
536                                        ref, NULL, true, kCGRenderingIntentDefault);
537
538        // OpenGL ES measures data in PIXELS
539        // Create a graphics context with the target size measured in POINTS
540        NSInteger widthInPoints = 0, heightInPoints = 0;
541
542        // Set the scale parameter to your OpenGL ES view's contentScaleFactor
543        // so that you get a high-resolution snapshot when its value is greater than 1.0
544        CGFloat scale = mView.contentScaleFactor;
545        widthInPoints = width / scale;
546        heightInPoints = height / scale;
547        UIGraphicsBeginImageContextWithOptions(CGSizeMake(widthInPoints, heightInPoints), NO, scale);
548
549        CGContextRef context = UIGraphicsGetCurrentContext();
550        CGContextDrawImage(context, CGRectMake(0.0, 0.0, widthInPoints, heightInPoints), iref);
551
552        // Retrieve the UIImage from the current context
553        size_t rowSpan = dst.getWidth() * PixelUtil::getNumElemBytes(dst.format);
554        memcpy(dst.data, CGBitmapContextGetData(context), rowSpan * dst.getHeight()); // TODO: support dst.rowPitch != dst.getWidth() case
555        UIGraphicsEndImageContext();
556
557        // Clean up
558        free(data);
559        CFRelease(ref);
560        CFRelease(colorspace);
561        CGImageRelease(iref);
562    }
563}
564