UnityAppController+Rendering.mm 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. #include "UnityAppController+Rendering.h"
  2. #include "UnityAppController+ViewHandling.h"
  3. #include "Unity/InternalProfiler.h"
  4. #include "Unity/UnityMetalSupport.h"
  5. #include "Unity/DisplayManager.h"
  6. #include "Unity/EAGLContextHelper.h"
  7. #include "UI/UnityView.h"
  8. #include <dlfcn.h>
  9. // On some devices presenting render buffer may sporadically take long time to complete even with very simple scenes.
  10. // In these cases display link still fires at steady frame rate but input processing becomes stuttering.
  11. // As a workaround this switch disables display link during rendering a frame.
  12. // If you are running a GPU bound scene and experience frame drop you may want to disable this switch.
  13. #define ENABLE_DISPLAY_LINK_PAUSING 1
  14. #define ENABLE_RUNLOOP_ACCEPT_INPUT 1
  15. // _glesContextCreated was renamed to _renderingInited
  16. extern bool _renderingInited;
  17. extern bool _unityAppReady;
  18. extern bool _skipPresent;
  19. extern bool _didResignActive;
  20. static int _renderingAPI = 0;
  21. static int SelectRenderingAPIImpl();
  22. static bool _enableRunLoopAcceptInput = false;
  23. @implementation UnityAppController (Rendering)
  24. - (void)createDisplayLink
  25. {
  26. _displayLink = [CADisplayLink displayLinkWithTarget: self selector: @selector(repaintDisplayLink)];
  27. [self callbackFramerateChange: -1];
  28. [_displayLink addToRunLoop: [NSRunLoop currentRunLoop] forMode: NSRunLoopCommonModes];
  29. }
  30. - (void)destroyDisplayLink
  31. {
  32. [_displayLink invalidate];
  33. _displayLink = nil;
  34. }
  35. - (void)processTouchEvents
  36. {
  37. // On multicore devices running at 60 FPS some touch event delivery isn't properly interleaved with graphical frames.
  38. // Running additional run loop here improves event handling in those cases.
  39. // Passing here an NSDate from the past invokes run loop only once.
  40. #if ENABLE_RUNLOOP_ACCEPT_INPUT
  41. // We get "NSInternalInconsistencyException: unexpected start state" exception if there are events queued and app is
  42. // going to background at the same time. This happens when we render additional frame after receiving
  43. // applicationWillResignActive. So check if we are supposed to ignore input.
  44. bool ignoreInput = [[UIApplication sharedApplication] isIgnoringInteractionEvents];
  45. if (!ignoreInput && _enableRunLoopAcceptInput)
  46. {
  47. static NSDate* past = [NSDate dateWithTimeIntervalSince1970: 0]; // the oldest date we can get
  48. [[NSRunLoop currentRunLoop] acceptInputForMode: NSDefaultRunLoopMode beforeDate: past];
  49. }
  50. #endif
  51. }
  52. - (void)repaintDisplayLink
  53. {
  54. #if ENABLE_DISPLAY_LINK_PAUSING
  55. _displayLink.paused = YES;
  56. #endif
  57. if (!_didResignActive)
  58. {
  59. [self repaint];
  60. [self processTouchEvents];
  61. }
  62. #if ENABLE_DISPLAY_LINK_PAUSING
  63. _displayLink.paused = NO;
  64. #endif
  65. }
  66. - (void)repaint
  67. {
  68. #if UNITY_SUPPORT_ROTATION
  69. [self checkOrientationRequest];
  70. #endif
  71. [_unityView recreateRenderingSurfaceIfNeeded];
  72. [_unityView processKeyboard];
  73. UnityDeliverUIEvents();
  74. if (!UnityIsPaused())
  75. UnityRepaint();
  76. }
  77. - (void)callbackGfxInited
  78. {
  79. InitRendering();
  80. _renderingInited = true;
  81. [self shouldAttachRenderDelegate];
  82. [_renderDelegate mainDisplayInited: _mainDisplay.surface];
  83. [_unityView recreateRenderingSurface];
  84. _mainDisplay.surface->allowScreenshot = 1;
  85. }
  86. - (void)callbackPresent:(const UnityFrameStats*)frameStats
  87. {
  88. if (_skipPresent || _didResignActive)
  89. return;
  90. // metal needs special processing, because in case of airplay we need extra command buffers to present non-main screen drawables
  91. if (UnitySelectedRenderingAPI() == apiMetal)
  92. {
  93. #if UNITY_CAN_USE_METAL
  94. [[DisplayManager Instance].mainDisplay present];
  95. [[DisplayManager Instance] enumerateNonMainDisplaysWithBlock:^(DisplayConnection* conn) {
  96. PreparePresentNonMainScreenMTL((UnityDisplaySurfaceMTL*)conn.surface);
  97. }];
  98. #endif
  99. }
  100. else
  101. {
  102. [[DisplayManager Instance] present];
  103. }
  104. Profiler_FramePresent(frameStats);
  105. }
  106. - (void)callbackFramerateChange:(int)targetFPS
  107. {
  108. int maxFPS = (int)[UIScreen mainScreen].maximumFramesPerSecond;
  109. if (targetFPS <= 0)
  110. targetFPS = UnityGetTargetFPS();
  111. if (targetFPS > maxFPS)
  112. targetFPS = maxFPS;
  113. _enableRunLoopAcceptInput = (targetFPS == maxFPS && UnityDeviceCPUCount() > 1);
  114. _displayLink.preferredFramesPerSecond = targetFPS;
  115. }
  116. - (void)selectRenderingAPI
  117. {
  118. NSAssert(_renderingAPI == 0, @"[UnityAppController selectRenderingApi] called twice");
  119. _renderingAPI = SelectRenderingAPIImpl();
  120. }
  121. - (UnityRenderingAPI)renderingAPI
  122. {
  123. NSAssert(_renderingAPI != 0, @"[UnityAppController renderingAPI] called before [UnityAppController selectRenderingApi]");
  124. return (UnityRenderingAPI)_renderingAPI;
  125. }
  126. @end
  127. extern "C" void UnityGfxInitedCallback()
  128. {
  129. [GetAppController() callbackGfxInited];
  130. }
  131. extern "C" void UnityPresentContextCallback(struct UnityFrameStats const* unityFrameStats)
  132. {
  133. [GetAppController() callbackPresent: unityFrameStats];
  134. }
  135. extern "C" void UnityFramerateChangeCallback(int targetFPS)
  136. {
  137. [GetAppController() callbackFramerateChange: targetFPS];
  138. }
  139. extern "C" void UnityInitMainScreenRenderingCallback()
  140. {
  141. [GetAppController().mainDisplay initRendering];
  142. }
  143. static NSBundle* _MetalBundle = nil;
  144. static id<MTLDevice> _MetalDevice = nil;
  145. static EAGLContext* _GlesContext = nil;
  146. static bool IsMetalSupported(int /*api*/)
  147. {
  148. _MetalBundle = [NSBundle bundleWithPath: @"/System/Library/Frameworks/Metal.framework"];
  149. if (_MetalBundle)
  150. {
  151. [_MetalBundle load];
  152. _MetalDevice = ((MTLCreateSystemDefaultDeviceFunc)::dlsym(dlopen(0, RTLD_LOCAL | RTLD_LAZY), "MTLCreateSystemDefaultDevice"))();
  153. if (_MetalDevice)
  154. return true;
  155. }
  156. [_MetalBundle unload];
  157. return false;
  158. }
  159. static bool IsGlesSupported(int api)
  160. {
  161. _GlesContext = [[EAGLContext alloc] initWithAPI: (EAGLRenderingAPI)api];
  162. return _GlesContext != nil;
  163. }
  164. typedef bool(*CheckSupportedFunc)(int);
  165. static int SelectRenderingAPIImpl()
  166. {
  167. // Get list of graphics APIs to try from player settings
  168. const int kMaxAPIs = 3;
  169. int apis[kMaxAPIs];
  170. const int apiCount = UnityGetRenderingAPIs(kMaxAPIs, apis);
  171. // Go over them and try each
  172. for (int i = 0; i < apiCount; ++i)
  173. {
  174. int api = apis[i];
  175. // Metal
  176. if (api == apiMetal)
  177. {
  178. #if UNITY_CAN_USE_METAL
  179. if (!IsMetalSupported(0))
  180. continue;
  181. return api;
  182. #else
  183. continue;
  184. #endif
  185. }
  186. // GLES3
  187. if (api == apiOpenGLES3)
  188. {
  189. if (!IsGlesSupported(kEAGLRenderingAPIOpenGLES3))
  190. continue;
  191. return api;
  192. }
  193. // GLES2
  194. if (api == apiOpenGLES2)
  195. {
  196. if (!IsGlesSupported(kEAGLRenderingAPIOpenGLES2))
  197. continue;
  198. return api;
  199. }
  200. }
  201. return 0;
  202. }
  203. extern "C" NSBundle* UnityGetMetalBundle()
  204. {
  205. return _MetalBundle;
  206. }
  207. extern "C" MTLDeviceRef UnityGetMetalDevice() { return _MetalDevice; }
  208. extern "C" MTLCommandQueueRef UnityGetMetalCommandQueue() { return ((UnityDisplaySurfaceMTL*)GetMainDisplaySurface())->commandQueue; }
  209. extern "C" MTLCommandQueueRef UnityGetMetalDrawableCommandQueue() { return ((UnityDisplaySurfaceMTL*)GetMainDisplaySurface())->drawableCommandQueue; }
  210. extern "C" EAGLContext* UnityGetDataContextEAGL()
  211. {
  212. return _GlesContext;
  213. }
  214. extern "C" int UnitySelectedRenderingAPI() { return _renderingAPI; }
  215. extern "C" UnityRenderBufferHandle UnityBackbufferColor() { return GetMainDisplaySurface()->unityColorBuffer; }
  216. extern "C" UnityRenderBufferHandle UnityBackbufferDepth() { return GetMainDisplaySurface()->unityDepthBuffer; }
  217. extern "C" void DisplayManagerEndFrameRendering() { [[DisplayManager Instance] endFrameRendering]; }
  218. extern "C" void UnityPrepareScreenshot() { UnitySetRenderTarget(GetMainDisplaySurface()->unityColorBuffer, GetMainDisplaySurface()->unityDepthBuffer); }
  219. extern "C" void UnityRepaint()
  220. {
  221. @autoreleasepool
  222. {
  223. // this will handle running on metal just fine (nop)
  224. EAGLContextSetCurrentAutoRestore autorestore(GetMainDisplaySurface());
  225. Profiler_FrameStart();
  226. UnityInputProcess();
  227. UnityPlayerLoop();
  228. Profiler_FrameEnd();
  229. }
  230. }