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