diff --git a/packages/react-native/Libraries/Image/RCTImageLoader.mm b/packages/react-native/Libraries/Image/RCTImageLoader.mm index 3caaf7bdf10a0b..86f34ec72e7331 100644 --- a/packages/react-native/Libraries/Image/RCTImageLoader.mm +++ b/packages/react-native/Libraries/Image/RCTImageLoader.mm @@ -8,6 +8,7 @@ #import #import #import +#import #import @@ -18,6 +19,7 @@ #import #import #import +#import #import #import #import @@ -45,6 +47,29 @@ static NSInteger RCTImageBytesForImage(UIImage *image) return error; } +template +using Block = T (^)(void); + +template +std::shared_future runOnMainQueue(Block block) +{ + __block std::promise promise; + RCTExecuteOnMainQueue(^{ + @try { + try { + T result = block(); + promise.set_value(result); + } catch (...) { + promise.set_exception(std::current_exception()); + } + } @catch (NSException *exception) { + auto cppException = std::runtime_error([exception.description UTF8String]); + promise.set_exception(std::make_exception_ptr(cppException)); + } + }); + return promise.get_future().share(); +} + @interface RCTImageLoader () @end @@ -84,6 +109,7 @@ @implementation RCTImageLoader { NSUInteger _activeBytes; std::mutex _loadersMutex; __weak id _redirectDelegate; + std::shared_future _screenSize; } @synthesize bridge = _bridge; @@ -107,6 +133,9 @@ + (BOOL)requiresMainQueueSetup - (instancetype)initWithRedirectDelegate:(id)redirectDelegate { if (self = [super init]) { + _screenSize = runOnMainQueue(^{ + return RCTScreenSize(); + }); _redirectDelegate = redirectDelegate; _isLoaderSetup = NO; } @@ -956,15 +985,19 @@ - (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)data // Mark these bytes as in-use self->_activeBytes += decodedImageBytes; + __block std::shared_future screenScale = runOnMainQueue(^{ + return RCTScreenScale(); + }); + // Do actual decompression on a concurrent background queue dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ if (!std::atomic_load(cancelled.get())) { // Decompress the image data (this may be CPU and memory intensive) - UIImage *image = RCTDecodeImageWithData(data, size, scale, resizeMode); + UIImage *image = RCTDecodeImageWithData(data, size, scale, resizeMode, screenScale.get()); #if RCT_DEV CGSize imagePixelSize = RCTSizeInPixels(image.size, image.scale); - CGSize screenPixelSize = RCTSizeInPixels(RCTScreenSize(), RCTScreenScale()); + CGSize screenPixelSize = RCTSizeInPixels(self->_screenSize.get(), screenScale.get()); if (imagePixelSize.width * imagePixelSize.height > screenPixelSize.width * screenPixelSize.height) { RCTLogInfo( @"[PERF ASSETS] Loading image at size %@, which is larger " diff --git a/packages/react-native/Libraries/Image/RCTImageUtils.h b/packages/react-native/Libraries/Image/RCTImageUtils.h index b430e7a5b9a740..17022f7982823d 100644 --- a/packages/react-native/Libraries/Image/RCTImageUtils.h +++ b/packages/react-native/Libraries/Image/RCTImageUtils.h @@ -61,7 +61,7 @@ RCT_EXTERN BOOL RCTUpscalingRequired( * Pass a destSize of CGSizeZero to decode the image at its original size. */ RCT_EXTERN UIImage *__nullable -RCTDecodeImageWithData(NSData *data, CGSize destSize, CGFloat destScale, RCTResizeMode resizeMode); +RCTDecodeImageWithData(NSData *data, CGSize destSize, CGFloat destScale, RCTResizeMode resizeMode, CGFloat screenScale); /** * This function takes the source data for an image and decodes just the diff --git a/packages/react-native/Libraries/Image/RCTImageUtils.mm b/packages/react-native/Libraries/Image/RCTImageUtils.mm index b7f85f83fc33a6..81682fa031a92a 100644 --- a/packages/react-native/Libraries/Image/RCTImageUtils.mm +++ b/packages/react-native/Libraries/Image/RCTImageUtils.mm @@ -256,7 +256,8 @@ BOOL RCTUpscalingRequired( } } -UIImage *__nullable RCTDecodeImageWithData(NSData *data, CGSize destSize, CGFloat destScale, RCTResizeMode resizeMode) +UIImage *__nullable +RCTDecodeImageWithData(NSData *data, CGSize destSize, CGFloat destScale, RCTResizeMode resizeMode, CGFloat screenScale) { CGImageSourceRef sourceRef = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL); if (!sourceRef) { @@ -280,7 +281,7 @@ BOOL RCTUpscalingRequired( destScale = 1; } } else if (!destScale) { - destScale = RCTScreenScale(); + destScale = screenScale; } if (resizeMode == RCTResizeModeStretch) { diff --git a/packages/react-native/React/Fabric/Surface/RCTFabricSurface.mm b/packages/react-native/React/Fabric/Surface/RCTFabricSurface.mm index 3906df778bcd03..c7e7837e7cd029 100644 --- a/packages/react-native/React/Fabric/Surface/RCTFabricSurface.mm +++ b/packages/react-native/React/Fabric/Surface/RCTFabricSurface.mm @@ -62,9 +62,14 @@ - (instancetype)initWithSurfacePresenter:(RCTSurfacePresenter *)surfacePresenter [_surfacePresenter registerSurface:self]; - [self setMinimumSize:CGSizeZero maximumSize:RCTViewportSize()]; - - [self _updateLayoutContext]; + /** + * Viewport size, and screen scale are only available on the main thread. + * Therefore, we just set the constraints on the main thread. + */ + RCTExecuteOnMainQueue(^{ + [self setMinimumSize:CGSizeZero maximumSize:RCTViewportSize()]; + [self _updateLayoutContextWithScreenScale:RCTScreenScale()]; + }); [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleContentSizeCategoryDidChangeNotification:) @@ -143,7 +148,7 @@ - (RCTSurfaceView *)view if (!_view) { _view = [[RCTSurfaceView alloc] initWithSurface:(RCTSurface *)self]; - [self _updateLayoutContext]; + [self _updateLayoutContextWithScreenScale:RCTScreenScale()]; _touchHandler = [RCTSurfaceTouchHandler new]; [_touchHandler attachToView:_view]; } @@ -170,14 +175,14 @@ - (void)_propagateStageChange } } -- (void)_updateLayoutContext +- (void)_updateLayoutContextWithScreenScale:(CGFloat)screenScale { auto layoutConstraints = _surfaceHandler->getLayoutConstraints(); layoutConstraints.layoutDirection = RCTLayoutDirection([[RCTI18nUtil sharedInstance] isRTL]); auto layoutContext = _surfaceHandler->getLayoutContext(); - layoutContext.pointScaleFactor = RCTScreenScale(); + layoutContext.pointScaleFactor = screenScale; layoutContext.swapLeftAndRightInRTL = [[RCTI18nUtil sharedInstance] isRTL] && [[RCTI18nUtil sharedInstance] doLeftAndRightSwapInRTL]; layoutContext.fontSizeMultiplier = RCTFontSizeMultiplier(); @@ -271,7 +276,7 @@ - (BOOL)synchronouslyWaitFor:(NSTimeInterval)timeout - (void)handleContentSizeCategoryDidChangeNotification:(NSNotification *)notification { - [self _updateLayoutContext]; + [self _updateLayoutContextWithScreenScale:RCTScreenScale()]; } #pragma mark - Private