Badminton-Scoreboard/Classes/UI/UnityView.mm
2023-10-08 10:24:48 +08:00

330 lines
12 KiB
Plaintext

#include "UnityView.h"
#include "UnityAppController.h"
#include "UnityAppController+Rendering.h"
#include "OrientationSupport.h"
#include "Unity/DisplayManager.h"
#include "Unity/UnityMetalSupport.h"
#include "Unity/ObjCRuntime.h"
extern bool _renderingInited;
extern bool _unityAppReady;
extern bool _skipPresent;
@implementation UnityView
{
CGSize _surfaceSize;
}
@synthesize contentOrientation = _curOrientation;
- (void)onUpdateSurfaceSize:(CGSize)size
{
_surfaceSize = size;
CGSize systemRenderSize = CGSizeMake(size.width * self.contentScaleFactor, size.height * self.contentScaleFactor);
_curOrientation = (ScreenOrientation)UnityReportResizeView((unsigned)systemRenderSize.width, (unsigned)systemRenderSize.height, _curOrientation);
ReportSafeAreaChangeForView(self);
}
- (void)boundsUpdated
{
[self onUpdateSurfaceSize: self.bounds.size];
}
- (void)initImpl:(CGRect)frame scaleFactor:(CGFloat)scale
{
#if !PLATFORM_TVOS
self.multipleTouchEnabled = YES;
self.exclusiveTouch = YES;
#endif
self.contentScaleFactor = scale;
self.isAccessibilityElement = TRUE;
self.accessibilityTraits = UIAccessibilityTraitAllowsDirectInteraction;
#if UNITY_TVOS
_curOrientation = UNITY_TVOS_ORIENTATION;
#endif
[self onUpdateSurfaceSize: frame.size];
}
- (id)initWithFrame:(CGRect)frame scaleFactor:(CGFloat)scale;
{
if ((self = [super initWithFrame: frame]))
[self initImpl: frame scaleFactor: scale];
return self;
}
- (id)initWithFrame:(CGRect)frame
{
if ((self = [super initWithFrame: frame]))
[self initImpl: frame scaleFactor: 1.0f];
return self;
}
- (id)initFromMainScreen
{
CGRect frame = [UIScreen mainScreen].bounds;
CGFloat scale = UnityScreenScaleFactor([UIScreen mainScreen]);
if ((self = [super initWithFrame: frame]))
[self initImpl: frame scaleFactor: scale];
return self;
}
- (void)layoutSubviews
{
if (_surfaceSize.width != self.bounds.size.width || _surfaceSize.height != self.bounds.size.height)
_shouldRecreateView = YES;
[self onUpdateSurfaceSize: self.bounds.size];
for (UIView* subView in self.subviews)
{
if ([subView respondsToSelector: @selector(onUnityUpdateViewLayout)])
[subView performSelector: @selector(onUnityUpdateViewLayout)];
}
[super layoutSubviews];
}
- (void)safeAreaInsetsDidChange
{
ReportSafeAreaChangeForView(self);
}
- (void)recreateRenderingSurfaceIfNeeded
{
float requestedContentScaleFactor = UnityScreenScaleFactor([UIScreen mainScreen]);
if (abs(requestedContentScaleFactor - self.contentScaleFactor) > FLT_EPSILON)
{
self.contentScaleFactor = requestedContentScaleFactor;
[self onUpdateSurfaceSize: self.bounds.size];
}
unsigned requestedW, requestedH; UnityGetRenderingResolution(&requestedW, &requestedH);
int requestedMSAA = UnityGetDesiredMSAASampleCount(1);
int requestedSRGB = UnityGetSRGBRequested();
int requestedWideColor = UnityGetWideColorRequested();
int requestedHDR = UnityGetHDRModeRequested();
int requestedMemorylessDepth = UnityMetalMemorylessDepth();
UnityDisplaySurfaceBase* surf = GetMainDisplaySurface();
if (_shouldRecreateView == YES
|| surf->targetW != requestedW || surf->targetH != requestedH
|| surf->disableDepthAndStencil != UnityDisableDepthAndStencilBuffers()
|| surf->msaaSamples != requestedMSAA
|| surf->srgb != requestedSRGB
|| surf->wideColor != requestedWideColor
|| surf->hdr != requestedHDR
|| surf->memorylessDepth != requestedMemorylessDepth
)
{
[self recreateRenderingSurface];
}
}
- (void)recreateRenderingSurface
{
if (_renderingInited)
{
unsigned requestedW, requestedH;
UnityGetRenderingResolution(&requestedW, &requestedH);
RenderingSurfaceParams params =
{
.msaaSampleCount = UnityGetDesiredMSAASampleCount(1),
.renderW = (int)requestedW,
.renderH = (int)requestedH,
.srgb = UnityGetSRGBRequested(),
.wideColor = UnityGetWideColorRequested(),
.hdr = UnityGetHDRModeRequested(),
.metalFramebufferOnly = UnityMetalFramebufferOnly(),
.metalMemorylessDepth = UnityMetalMemorylessDepth(),
.disableDepthAndStencil = UnityDisableDepthAndStencilBuffers(),
.useCVTextureCache = 0,
};
APP_CONTROLLER_RENDER_PLUGIN_METHOD_ARG(onBeforeMainDisplaySurfaceRecreate, &params);
[GetMainDisplay() recreateSurface: params];
// actually poke unity about updated back buffer and notify that extents were changed
UnityReportBackbufferChange(GetMainDisplaySurface()->unityColorBuffer, GetMainDisplaySurface()->unityDepthBuffer);
APP_CONTROLLER_RENDER_PLUGIN_METHOD(onAfterMainDisplaySurfaceRecreate);
if (_unityAppReady)
{
// seems like ios sometimes got confused about abrupt swap chain destroy
// draw 2 times to fill "both" buffers (we assume double buffering)
// present only once to make sure correct image goes to CA
// if we are calling this from inside repaint, second draw and present will be done automatically
// please note that we still need to pretend we did come from displaylink to make sure vsync magic works
// NOTE: unity does handle "draw frame with exact same timestamp" just fine
_skipPresent = true;
if (!UnityIsPaused())
{
UnityDisplayLinkCallback(GetAppController().unityDisplayLink.timestamp);
UnityRepaint();
// we are not inside repaint so we need to draw second time ourselves
if (_viewIsRotating)
{
UnityDisplayLinkCallback(GetAppController().unityDisplayLink.timestamp);
UnityRepaint();
}
}
_skipPresent = false;
}
}
_shouldRecreateView = NO;
}
@end
@implementation UnityView (Deprecated)
- (void)recreateGLESSurfaceIfNeeded { [self recreateRenderingSurfaceIfNeeded]; }
- (void)recreateGLESSurface { [self recreateRenderingSurface]; }
@end
static Class UnityRenderingView_LayerClassMTL(id self_, SEL _cmd)
{
return NSClassFromString(@"CAMetalLayer");
}
static Class UnityRenderingView_LayerClassNULL(id self_, SEL _cmd)
{
return NSClassFromString(@"CALayer");
}
@implementation UnityRenderingView
+ (Class)layerClass
{
return nil;
}
+ (void)InitializeForAPI:(UnityRenderingAPI)api
{
IMP layerClassImpl = api == apiMetal ? (IMP)UnityRenderingView_LayerClassMTL : (IMP)UnityRenderingView_LayerClassNULL;
class_replaceMethod(object_getClass([UnityRenderingView class]), @selector(layerClass), layerClassImpl, UIView_LayerClass_Enc);
}
@end
void ReportSafeAreaChangeForView(UIView* view)
{
CGRect safeArea = ComputeSafeArea(view);
UnityReportSafeAreaChange(safeArea.origin.x, safeArea.origin.y,
safeArea.size.width, safeArea.size.height);
if (UnityDeviceHasCutout())
{
CGSize cutoutSizeRatio = GetCutoutToScreenRatio();
if (!CGSizeEqualToSize(CGSizeZero, cutoutSizeRatio))
{
const float w = ([UIScreen mainScreen].nativeBounds.size.width * cutoutSizeRatio.width);
const float h = ([UIScreen mainScreen].nativeBounds.size.height * cutoutSizeRatio.height);
// Apple's cutouts are currently centred on the horizontal, and stuck to the top of the vertical, hence this positioning.
const float x = (([UIScreen mainScreen].nativeBounds.size.width - w) / 2);
const float y = ([UIScreen mainScreen].nativeBounds.size.height - h);
UnityReportDisplayCutouts(&x, &y, &w, &h, 1);
return;
}
}
UnityReportDisplayCutouts(nullptr, nullptr, nullptr, nullptr, 0);
}
CGRect ComputeSafeArea(UIView* view)
{
CGSize screenSize = view.bounds.size;
CGRect screenRect = CGRectMake(0, 0, screenSize.width, screenSize.height);
UIEdgeInsets insets = [view safeAreaInsets];
float insetLeft = insets.left, insetBottom = insets.bottom;
float insetWidth = insetLeft + insets.right, insetHeight = insetBottom + insets.top;
#if PLATFORM_IOS
// pre-iOS 15 there is a bug with safeAreaInsets when coupled with the way unity handles forced orientation
// when we create/show new ViewController with fixed orientation, safeAreaInsets include status bar always
// alas, we did not find a good way to work around that (this can be seen even in View Debugging: Safe Area would have status bar accounted for)
// we know for sure that status bar height is 20 (at least on ios16 or older), so we can check if the safe area
// includes inset of this size while status bar should be hidden, resetting vertical insets in this case
if (@available(iOS 15, *))
{
// everything works as expected
}
else
{
bool isStatusBarHidden = false;
#if UNITY_HAS_IOSSDK_13_0
if (@available(iOS 13, *))
{
isStatusBarHidden = view.window.windowScene.statusBarManager.statusBarHidden;
}
else
#endif
{
isStatusBarHidden = [UIApplication sharedApplication].statusBarHidden;
}
if (isStatusBarHidden && fabsf(insetHeight - 20) < 1e-6f)
insetHeight = insetBottom = 0.0f;
}
#endif
// Unity uses bottom left as the origin
screenRect = CGRectOffset(screenRect, insetLeft, insetBottom);
screenRect.size.width -= insetWidth;
screenRect.size.height -= insetHeight;
const float scale = view.contentScaleFactor;
// Truncate safe area size because in some cases (for example when Display zoom is turned on)
// it might become larger than Screen.width/height which are returned as ints.
screenRect.origin.x = (unsigned)(screenRect.origin.x * scale);
screenRect.origin.y = (unsigned)(screenRect.origin.y * scale);
screenRect.size.width = (unsigned)(screenRect.size.width * scale);
screenRect.size.height = (unsigned)(screenRect.size.height * scale);
return screenRect;
}
// Apple does not provide the cutout width and height in points/pixels. They *do* however list the
// size of the cutout and screen in mm for accessory makers. We can use this information to calculate the percentage of the screen is cutout.
// This information can be found here - https://developer.apple.com/accessories/Accessory-Design-Guidelines.pdf
CGSize GetCutoutToScreenRatio()
{
switch (UnityDeviceGeneration())
{
case deviceiPhone13ProMax:
return CGSizeMake(0.373, 0.036);
case deviceiPhone13Pro:
case deviceiPhone13:
return CGSizeMake(0.4148, 0.0399);
case deviceiPhone13Mini:
return CGSizeMake(0.4644, 0.0462);
case deviceiPhone12ProMax:
return CGSizeMake(0.4897, 0.0346);
case deviceiPhone12Pro:
case deviceiPhone12:
return CGSizeMake(0.5393, 0.0379);
case deviceiPhone12Mini:
return CGSizeMake(0.604, 0.0424);
case deviceiPhone11ProMax:
return CGSizeMake(0.5057, 0.0335);
case deviceiPhone11Pro:
return CGSizeMake(0.5583, 0.037);
case deviceiPhone11:
case deviceiPhoneXR:
return CGSizeMake(0.5568, 0.0398);
case deviceiPhoneXSMax:
return CGSizeMake(0.4884, 0.0333);
case deviceiPhoneX:
case deviceiPhoneXS:
return CGSizeMake(0.5391, 0.0368);
default:
NSCAssert(!UnityDeviceHasCutout(), @"Device has a cutout, but no ratio has been added for it.");
return CGSizeZero;
}
}