Merge pull request #3496 from dreampiggy/temp/try_fix_promotion

Try to fix the SDAnimatedImageView playback speed issue in Promotion devices (iPhone Pro)
This commit is contained in:
DreamPiggy 2023-02-18 19:36:15 +08:00 committed by GitHub
commit 748def0a0c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 113 additions and 37 deletions

View File

@ -3,12 +3,13 @@
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objectVersion = 52;
objects = {
/* Begin PBXBuildFile section */
32892E311FAE898C00BE8320 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 32892E301FAE898C00BE8320 /* Assets.xcassets */; };
32892E351FAE89FE00BE8320 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 32892E341FAE89FD00BE8320 /* LaunchScreen.storyboard */; };
328CA3AB29980A840063950F /* WindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 328CA3AA29980A840063950F /* WindowController.m */; };
3E75A9861742DBE700DA412D /* CustomPathImages in Resources */ = {isa = PBXBuildFile; fileRef = 3E75A9851742DBE700DA412D /* CustomPathImages */; };
4314D1AA1D0E1181004B36C9 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 4314D1A91D0E1181004B36C9 /* main.m */; };
4314D1AD1D0E1181004B36C9 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 4314D1AC1D0E1181004B36C9 /* AppDelegate.m */; };
@ -136,6 +137,8 @@
327E1C604113A7CEC9AC02DB /* Pods-SDWebImage Watch Demo Extension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SDWebImage Watch Demo Extension.debug.xcconfig"; path = "../Pods/Target Support Files/Pods-SDWebImage Watch Demo Extension/Pods-SDWebImage Watch Demo Extension.debug.xcconfig"; sourceTree = "<group>"; };
32892E301FAE898C00BE8320 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
32892E341FAE89FD00BE8320 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = "<group>"; };
328CA3A929980A840063950F /* WindowController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WindowController.h; sourceTree = "<group>"; };
328CA3AA29980A840063950F /* WindowController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = WindowController.m; sourceTree = "<group>"; };
3E75A9851742DBE700DA412D /* CustomPathImages */ = {isa = PBXFileReference; lastKnownFileType = folder; path = CustomPathImages; sourceTree = SOURCE_ROOT; };
4314D1A61D0E1181004B36C9 /* SDWebImage TV Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "SDWebImage TV Demo.app"; sourceTree = BUILT_PRODUCTS_DIR; };
4314D1A91D0E1181004B36C9 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
@ -275,6 +278,8 @@
children = (
43A629D11D0DFD000089D7DD /* AppDelegate.h */,
43A629D21D0DFD000089D7DD /* AppDelegate.m */,
328CA3A929980A840063950F /* WindowController.h */,
328CA3AA29980A840063950F /* WindowController.m */,
43A629D71D0DFD000089D7DD /* ViewController.h */,
43A629D81D0DFD000089D7DD /* ViewController.m */,
43A629DA1D0DFD000089D7DD /* Assets.xcassets */,
@ -804,6 +809,7 @@
buildActionMask = 2147483647;
files = (
43A629D91D0DFD000089D7DD /* ViewController.m in Sources */,
328CA3AB29980A840063950F /* WindowController.m in Sources */,
43A629D61D0DFD000089D7DD /* main.m in Sources */,
43A629D31D0DFD000089D7DD /* AppDelegate.m in Sources */,
);

View File

@ -10,6 +10,7 @@
@interface AppDelegate : NSObject <NSApplicationDelegate>
@property (strong, nonatomic) NSWindowController *windowController;
@end

View File

@ -30,6 +30,12 @@
// For HEIC animated image. Animated image is new introduced in iOS 13, but it contains performance issue for now.
[[SDImageCodersManager sharedManager] addCoder:[SDImageHEICCoder sharedCoder]];
}
NSStoryboard *mainStoryboard = [NSStoryboard storyboardWithName:@"Main" bundle:nil];
NSWindowController *initialController = [mainStoryboard instantiateControllerWithIdentifier:@"MainWindowController"];
self.windowController = initialController;
[initialController showWindow:self];
[initialController.window makeKeyAndOrderFront:self];
}
- (void)applicationWillTerminate:(NSNotification *)aNotification {

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="B8D-0N-5wS">
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="21507" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="21507"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
@ -651,7 +651,7 @@
<!--Window Controller-->
<scene sceneID="R2V-B0-nI4">
<objects>
<windowController id="B8D-0N-5wS" sceneMemberID="viewController">
<windowController storyboardIdentifier="MainWindowController" id="B8D-0N-5wS" customClass="WindowController" sceneMemberID="viewController">
<window key="window" title="Window" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="IQv-IB-iLA">
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>

View File

@ -0,0 +1,17 @@
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import <Cocoa/Cocoa.h>
NS_ASSUME_NONNULL_BEGIN
@interface WindowController : NSWindowController
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,13 @@
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "WindowController.h"
@implementation WindowController
@end

View File

@ -26,7 +26,7 @@ static void * SDWebImageDownloaderOperationKey = &SDWebImageDownloaderOperationK
BOOL SDWebImageDownloaderOperationGetCompleted(id<SDWebImageDownloaderOperation> operation) {
NSCParameterAssert(operation);
NSNumber *value = objc_getAssociatedObject(operation, SDWebImageDownloaderOperationKey);
if (value) {
if (value != nil) {
return value.boolValue;
} else {
return NO;

View File

@ -15,7 +15,7 @@
@property (readonly, nonatomic, weak, nullable) id target;
@property (readonly, nonatomic, assign, nonnull) SEL selector;
@property (readonly, nonatomic) CFTimeInterval duration;
@property (readonly, nonatomic) NSTimeInterval duration; // elapsed time in seconds of previous callback. (or it's first callback, use the time between `start` and callback). Always zero when display link not running
@property (readonly, nonatomic) BOOL isRunning;
+ (nonnull instancetype)displayLinkWithTarget:(nonnull id)target selector:(nonnull SEL)sel;

View File

@ -13,15 +13,31 @@
#elif SD_IOS || SD_TV
#import <QuartzCore/QuartzCore.h>
#endif
#include <mach/mach_time.h>
#if SD_MAC
static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp *inNow, const CVTimeStamp *inOutputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut, void *displayLinkContext);
#endif
#if SD_WATCH
static CFTimeInterval CACurrentMediaTime(void)
{
mach_timebase_info_data_t timebase;
mach_timebase_info(&timebase);
uint64_t time = mach_absolute_time();
double seconds = (double)time * (double)timebase.numer / (double)timebase.denom / 1e9;
return seconds;
}
#endif
#define kSDDisplayLinkInterval 1.0 / 60
@interface SDDisplayLink ()
@property (nonatomic, assign) NSTimeInterval previousFireTime;
@property (nonatomic, assign) NSTimeInterval nextFireTime;
#if SD_MAC
@property (nonatomic, assign) CVDisplayLinkRef displayLink;
@property (nonatomic, assign) CVTimeStamp outputTime;
@ -32,7 +48,6 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt
@property (nonatomic, strong) NSTimer *displayLink;
@property (nonatomic, strong) NSRunLoop *runloop;
@property (nonatomic, copy) NSRunLoopMode runloopMode;
@property (nonatomic, assign) NSTimeInterval currentFireDate;
#endif
@end
@ -78,33 +93,47 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt
return displayLink;
}
- (CFTimeInterval)duration {
- (NSTimeInterval)duration {
NSTimeInterval duration = 0;
#if SD_MAC
CVTimeStamp outputTime = self.outputTime;
NSTimeInterval duration = 0;
double periodPerSecond = (double)outputTime.videoTimeScale * outputTime.rateScalar;
if (periodPerSecond > 0) {
duration = (double)outputTime.videoRefreshPeriod / periodPerSecond;
}
#elif SD_IOS || SD_TV
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
NSTimeInterval duration = 0;
if (@available(iOS 10.0, tvOS 10.0, *)) {
duration = self.displayLink.targetTimestamp - CACurrentMediaTime();
} else {
duration = self.displayLink.duration * self.displayLink.frameInterval;
}
#pragma clang diagnostic pop
#else
NSTimeInterval duration = 0;
if (self.displayLink.isValid && self.currentFireDate != 0) {
NSTimeInterval nextFireDate = CFRunLoopTimerGetNextFireDate((__bridge CFRunLoopTimerRef)self.displayLink);
duration = nextFireDate - self.currentFireDate;
// iOS 10+/watchOS use `nextTime`
if (@available(iOS 10.0, tvOS 10.0, watchOS 2.0, *)) {
duration = self.nextFireTime - CACurrentMediaTime();
} else {
// iOS 9 use `previousTime`
duration = CACurrentMediaTime() - self.previousFireTime;
}
#endif
if (duration <= 0) {
// When system sleep, the targetTimestamp will mass up, fallback refresh rate
if (duration < 0) {
#if SD_MAC
// Supports Pro display 120Hz
CGDirectDisplayID display = CVDisplayLinkGetCurrentCGDisplay(_displayLink);
CGDisplayModeRef mode = CGDisplayCopyDisplayMode(display);
if (mode) {
double refreshRate = CGDisplayModeGetRefreshRate(mode);
if (refreshRate > 0) {
duration = 1.0 / refreshRate;
} else {
duration = kSDDisplayLinkInterval;
}
CGDisplayModeRelease(mode);
} else {
duration = kSDDisplayLinkInterval;
}
#elif SD_IOS || SD_TV
// Fallback
duration = self.displayLink.duration;
#else
// Watch always 60Hz
duration = kSDDisplayLinkInterval;
#endif
}
return duration;
}
@ -189,24 +218,25 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt
#else
[self.displayLink invalidate];
#endif
self.previousFireTime = 0;
self.nextFireTime = 0;
}
- (void)displayLinkDidRefresh:(id)displayLink {
#if SD_MAC
// CVDisplayLink does not use runloop, but we can provide similar behavior for modes
// May use `default` runloop to avoid extra callback when in `eventTracking` (mouse drag, scroll) or `modalPanel` (modal panel)
NSString *runloopMode = self.runloopMode;
if (![runloopMode isEqualToString:NSRunLoopCommonModes] && ![runloopMode isEqualToString:NSRunLoop.mainRunLoop.currentMode]) {
return;
#if SD_IOS || SD_TV
if (@available(iOS 10.0, tvOS 10.0, *)) {
self.nextFireTime = self.displayLink.targetTimestamp;
} else {
self.previousFireTime = self.displayLink.timestamp;
}
#endif
#if SD_WATCH
self.nextFireTime = CFRunLoopTimerGetNextFireDate((__bridge CFRunLoopTimerRef)self.displayLink);
#endif
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[_target performSelector:_selector withObject:self];
#pragma clang diagnostic pop
#if SD_WATCH
self.currentFireDate = CFRunLoopTimerGetNextFireDate((__bridge CFRunLoopTimerRef)self.displayLink);
#endif
}
@end
@ -215,11 +245,16 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt
static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp *inNow, const CVTimeStamp *inOutputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut, void *displayLinkContext) {
// CVDisplayLink callback is not on main queue
SDDisplayLink *object = (__bridge SDDisplayLink *)displayLinkContext;
if (inOutputTime) {
object.outputTime = *inOutputTime;
// CVDisplayLink does not use runloop, but we can provide similar behavior for modes
// May use `default` runloop to avoid extra callback when in `eventTracking` (mouse drag, scroll) or `modalPanel` (modal panel)
NSString *runloopMode = object.runloopMode;
if (![runloopMode isEqualToString:NSRunLoopCommonModes] && ![runloopMode isEqualToString:NSRunLoop.mainRunLoop.currentMode]) {
return kCVReturnSuccess;
}
CVTimeStamp outputTime = inOutputTime ? *inOutputTime : *inNow;
__weak SDDisplayLink *weakObject = object;
dispatch_async(dispatch_get_main_queue(), ^{
weakObject.outputTime = outputTime;
[weakObject displayLinkDidRefresh:(__bridge id)(displayLink)];
});
return kCVReturnSuccess;

View File

@ -48,8 +48,6 @@
XCTestExpectation *expectation1 = [self expectationWithDescription:@"Display Link Stop"];
XCTestExpectation *expectation2 = [self expectationWithDescription:@"Display Link Start"];
SDDisplayLink *displayLink = [SDDisplayLink displayLinkWithTarget:self selector:@selector(displayLinkDidRefresh:)];
NSTimeInterval duration = displayLink.duration; // Initial value
expect(duration).equal(1.0 / 60);
[displayLink addToRunLoop:NSRunLoop.mainRunLoop forMode:NSRunLoopCommonModes];
[displayLink start];
expect(displayLink.isRunning).beTruthy();