Skip to content

Commit 748def0

Browse files
authored
Merge pull request SDWebImage#3496 from dreampiggy/temp/try_fix_promotion
Try to fix the SDAnimatedImageView playback speed issue in Promotion devices (iPhone Pro)
2 parents 940f991 + 856ecd6 commit 748def0

File tree

10 files changed

+113
-37
lines changed

10 files changed

+113
-37
lines changed

Examples/SDWebImage Demo.xcodeproj/project.pbxproj

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@
33
archiveVersion = 1;
44
classes = {
55
};
6-
objectVersion = 46;
6+
objectVersion = 52;
77
objects = {
88

99
/* Begin PBXBuildFile section */
1010
32892E311FAE898C00BE8320 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 32892E301FAE898C00BE8320 /* Assets.xcassets */; };
1111
32892E351FAE89FE00BE8320 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 32892E341FAE89FD00BE8320 /* LaunchScreen.storyboard */; };
12+
328CA3AB29980A840063950F /* WindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 328CA3AA29980A840063950F /* WindowController.m */; };
1213
3E75A9861742DBE700DA412D /* CustomPathImages in Resources */ = {isa = PBXBuildFile; fileRef = 3E75A9851742DBE700DA412D /* CustomPathImages */; };
1314
4314D1AA1D0E1181004B36C9 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 4314D1A91D0E1181004B36C9 /* main.m */; };
1415
4314D1AD1D0E1181004B36C9 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 4314D1AC1D0E1181004B36C9 /* AppDelegate.m */; };
@@ -136,6 +137,8 @@
136137
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>"; };
137138
32892E301FAE898C00BE8320 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
138139
32892E341FAE89FD00BE8320 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = "<group>"; };
140+
328CA3A929980A840063950F /* WindowController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WindowController.h; sourceTree = "<group>"; };
141+
328CA3AA29980A840063950F /* WindowController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = WindowController.m; sourceTree = "<group>"; };
139142
3E75A9851742DBE700DA412D /* CustomPathImages */ = {isa = PBXFileReference; lastKnownFileType = folder; path = CustomPathImages; sourceTree = SOURCE_ROOT; };
140143
4314D1A61D0E1181004B36C9 /* SDWebImage TV Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "SDWebImage TV Demo.app"; sourceTree = BUILT_PRODUCTS_DIR; };
141144
4314D1A91D0E1181004B36C9 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
@@ -275,6 +278,8 @@
275278
children = (
276279
43A629D11D0DFD000089D7DD /* AppDelegate.h */,
277280
43A629D21D0DFD000089D7DD /* AppDelegate.m */,
281+
328CA3A929980A840063950F /* WindowController.h */,
282+
328CA3AA29980A840063950F /* WindowController.m */,
278283
43A629D71D0DFD000089D7DD /* ViewController.h */,
279284
43A629D81D0DFD000089D7DD /* ViewController.m */,
280285
43A629DA1D0DFD000089D7DD /* Assets.xcassets */,
@@ -804,6 +809,7 @@
804809
buildActionMask = 2147483647;
805810
files = (
806811
43A629D91D0DFD000089D7DD /* ViewController.m in Sources */,
812+
328CA3AB29980A840063950F /* WindowController.m in Sources */,
807813
43A629D61D0DFD000089D7DD /* main.m in Sources */,
808814
43A629D31D0DFD000089D7DD /* AppDelegate.m in Sources */,
809815
);

Examples/SDWebImage OSX Demo/AppDelegate.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
@interface AppDelegate : NSObject <NSApplicationDelegate>
1212

13+
@property (strong, nonatomic) NSWindowController *windowController;
1314

1415
@end
1516

Examples/SDWebImage OSX Demo/AppDelegate.m

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@ - (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
3030
// For HEIC animated image. Animated image is new introduced in iOS 13, but it contains performance issue for now.
3131
[[SDImageCodersManager sharedManager] addCoder:[SDImageHEICCoder sharedCoder]];
3232
}
33+
34+
NSStoryboard *mainStoryboard = [NSStoryboard storyboardWithName:@"Main" bundle:nil];
35+
NSWindowController *initialController = [mainStoryboard instantiateControllerWithIdentifier:@"MainWindowController"];
36+
self.windowController = initialController;
37+
[initialController showWindow:self];
38+
[initialController.window makeKeyAndOrderFront:self];
3339
}
3440

3541
- (void)applicationWillTerminate:(NSNotification *)aNotification {

Examples/SDWebImage OSX Demo/Base.lproj/Main.storyboard

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
<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">
2+
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="21507" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
33
<dependencies>
44
<deployment identifier="macosx"/>
5-
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/>
5+
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="21507"/>
66
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
77
</dependencies>
88
<scenes>
@@ -651,7 +651,7 @@
651651
<!--Window Controller-->
652652
<scene sceneID="R2V-B0-nI4">
653653
<objects>
654-
<windowController id="B8D-0N-5wS" sceneMemberID="viewController">
654+
<windowController storyboardIdentifier="MainWindowController" id="B8D-0N-5wS" customClass="WindowController" sceneMemberID="viewController">
655655
<window key="window" title="Window" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="IQv-IB-iLA">
656656
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
657657
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/*
2+
* This file is part of the SDWebImage package.
3+
* (c) Olivier Poitrey <[email protected]>
4+
*
5+
* For the full copyright and license information, please view the LICENSE
6+
* file that was distributed with this source code.
7+
*/
8+
9+
#import <Cocoa/Cocoa.h>
10+
11+
NS_ASSUME_NONNULL_BEGIN
12+
13+
@interface WindowController : NSWindowController
14+
15+
@end
16+
17+
NS_ASSUME_NONNULL_END
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/*
2+
* This file is part of the SDWebImage package.
3+
* (c) Olivier Poitrey <[email protected]>
4+
*
5+
* For the full copyright and license information, please view the LICENSE
6+
* file that was distributed with this source code.
7+
*/
8+
9+
#import "WindowController.h"
10+
11+
@implementation WindowController
12+
13+
@end

SDWebImage/Core/SDWebImageDownloader.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
BOOL SDWebImageDownloaderOperationGetCompleted(id<SDWebImageDownloaderOperation> operation) {
2727
NSCParameterAssert(operation);
2828
NSNumber *value = objc_getAssociatedObject(operation, SDWebImageDownloaderOperationKey);
29-
if (value) {
29+
if (value != nil) {
3030
return value.boolValue;
3131
} else {
3232
return NO;

SDWebImage/Private/SDDisplayLink.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
@property (readonly, nonatomic, weak, nullable) id target;
1717
@property (readonly, nonatomic, assign, nonnull) SEL selector;
18-
@property (readonly, nonatomic) CFTimeInterval duration;
18+
@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
1919
@property (readonly, nonatomic) BOOL isRunning;
2020

2121
+ (nonnull instancetype)displayLinkWithTarget:(nonnull id)target selector:(nonnull SEL)sel;

SDWebImage/Private/SDDisplayLink.m

Lines changed: 64 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,31 @@
1313
#elif SD_IOS || SD_TV
1414
#import <QuartzCore/QuartzCore.h>
1515
#endif
16+
#include <mach/mach_time.h>
1617

1718
#if SD_MAC
1819
static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp *inNow, const CVTimeStamp *inOutputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut, void *displayLinkContext);
1920
#endif
2021

22+
#if SD_WATCH
23+
static CFTimeInterval CACurrentMediaTime(void)
24+
{
25+
mach_timebase_info_data_t timebase;
26+
mach_timebase_info(&timebase);
27+
28+
uint64_t time = mach_absolute_time();
29+
double seconds = (double)time * (double)timebase.numer / (double)timebase.denom / 1e9;
30+
return seconds;
31+
}
32+
#endif
33+
2134
#define kSDDisplayLinkInterval 1.0 / 60
2235

2336
@interface SDDisplayLink ()
2437

38+
@property (nonatomic, assign) NSTimeInterval previousFireTime;
39+
@property (nonatomic, assign) NSTimeInterval nextFireTime;
40+
2541
#if SD_MAC
2642
@property (nonatomic, assign) CVDisplayLinkRef displayLink;
2743
@property (nonatomic, assign) CVTimeStamp outputTime;
@@ -32,7 +48,6 @@ @interface SDDisplayLink ()
3248
@property (nonatomic, strong) NSTimer *displayLink;
3349
@property (nonatomic, strong) NSRunLoop *runloop;
3450
@property (nonatomic, copy) NSRunLoopMode runloopMode;
35-
@property (nonatomic, assign) NSTimeInterval currentFireDate;
3651
#endif
3752

3853
@end
@@ -78,33 +93,47 @@ + (instancetype)displayLinkWithTarget:(id)target selector:(SEL)sel {
7893
return displayLink;
7994
}
8095

81-
- (CFTimeInterval)duration {
96+
- (NSTimeInterval)duration {
97+
NSTimeInterval duration = 0;
8298
#if SD_MAC
8399
CVTimeStamp outputTime = self.outputTime;
84-
NSTimeInterval duration = 0;
85100
double periodPerSecond = (double)outputTime.videoTimeScale * outputTime.rateScalar;
86101
if (periodPerSecond > 0) {
87102
duration = (double)outputTime.videoRefreshPeriod / periodPerSecond;
88103
}
89-
#elif SD_IOS || SD_TV
90-
#pragma clang diagnostic push
91-
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
92-
NSTimeInterval duration = 0;
93-
if (@available(iOS 10.0, tvOS 10.0, *)) {
94-
duration = self.displayLink.targetTimestamp - CACurrentMediaTime();
95-
} else {
96-
duration = self.displayLink.duration * self.displayLink.frameInterval;
97-
}
98-
#pragma clang diagnostic pop
99104
#else
100-
NSTimeInterval duration = 0;
101-
if (self.displayLink.isValid && self.currentFireDate != 0) {
102-
NSTimeInterval nextFireDate = CFRunLoopTimerGetNextFireDate((__bridge CFRunLoopTimerRef)self.displayLink);
103-
duration = nextFireDate - self.currentFireDate;
105+
// iOS 10+/watchOS use `nextTime`
106+
if (@available(iOS 10.0, tvOS 10.0, watchOS 2.0, *)) {
107+
duration = self.nextFireTime - CACurrentMediaTime();
108+
} else {
109+
// iOS 9 use `previousTime`
110+
duration = CACurrentMediaTime() - self.previousFireTime;
104111
}
105112
#endif
106-
if (duration <= 0) {
113+
// When system sleep, the targetTimestamp will mass up, fallback refresh rate
114+
if (duration < 0) {
115+
#if SD_MAC
116+
// Supports Pro display 120Hz
117+
CGDirectDisplayID display = CVDisplayLinkGetCurrentCGDisplay(_displayLink);
118+
CGDisplayModeRef mode = CGDisplayCopyDisplayMode(display);
119+
if (mode) {
120+
double refreshRate = CGDisplayModeGetRefreshRate(mode);
121+
if (refreshRate > 0) {
122+
duration = 1.0 / refreshRate;
123+
} else {
124+
duration = kSDDisplayLinkInterval;
125+
}
126+
CGDisplayModeRelease(mode);
127+
} else {
128+
duration = kSDDisplayLinkInterval;
129+
}
130+
#elif SD_IOS || SD_TV
131+
// Fallback
132+
duration = self.displayLink.duration;
133+
#else
134+
// Watch always 60Hz
107135
duration = kSDDisplayLinkInterval;
136+
#endif
108137
}
109138
return duration;
110139
}
@@ -189,24 +218,25 @@ - (void)stop {
189218
#else
190219
[self.displayLink invalidate];
191220
#endif
221+
self.previousFireTime = 0;
222+
self.nextFireTime = 0;
192223
}
193224

194225
- (void)displayLinkDidRefresh:(id)displayLink {
195-
#if SD_MAC
196-
// CVDisplayLink does not use runloop, but we can provide similar behavior for modes
197-
// May use `default` runloop to avoid extra callback when in `eventTracking` (mouse drag, scroll) or `modalPanel` (modal panel)
198-
NSString *runloopMode = self.runloopMode;
199-
if (![runloopMode isEqualToString:NSRunLoopCommonModes] && ![runloopMode isEqualToString:NSRunLoop.mainRunLoop.currentMode]) {
200-
return;
226+
#if SD_IOS || SD_TV
227+
if (@available(iOS 10.0, tvOS 10.0, *)) {
228+
self.nextFireTime = self.displayLink.targetTimestamp;
229+
} else {
230+
self.previousFireTime = self.displayLink.timestamp;
201231
}
202232
#endif
233+
#if SD_WATCH
234+
self.nextFireTime = CFRunLoopTimerGetNextFireDate((__bridge CFRunLoopTimerRef)self.displayLink);
235+
#endif
203236
#pragma clang diagnostic push
204237
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
205238
[_target performSelector:_selector withObject:self];
206239
#pragma clang diagnostic pop
207-
#if SD_WATCH
208-
self.currentFireDate = CFRunLoopTimerGetNextFireDate((__bridge CFRunLoopTimerRef)self.displayLink);
209-
#endif
210240
}
211241

212242
@end
@@ -215,11 +245,16 @@ - (void)displayLinkDidRefresh:(id)displayLink {
215245
static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp *inNow, const CVTimeStamp *inOutputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut, void *displayLinkContext) {
216246
// CVDisplayLink callback is not on main queue
217247
SDDisplayLink *object = (__bridge SDDisplayLink *)displayLinkContext;
218-
if (inOutputTime) {
219-
object.outputTime = *inOutputTime;
248+
// CVDisplayLink does not use runloop, but we can provide similar behavior for modes
249+
// May use `default` runloop to avoid extra callback when in `eventTracking` (mouse drag, scroll) or `modalPanel` (modal panel)
250+
NSString *runloopMode = object.runloopMode;
251+
if (![runloopMode isEqualToString:NSRunLoopCommonModes] && ![runloopMode isEqualToString:NSRunLoop.mainRunLoop.currentMode]) {
252+
return kCVReturnSuccess;
220253
}
254+
CVTimeStamp outputTime = inOutputTime ? *inOutputTime : *inNow;
221255
__weak SDDisplayLink *weakObject = object;
222256
dispatch_async(dispatch_get_main_queue(), ^{
257+
weakObject.outputTime = outputTime;
223258
[weakObject displayLinkDidRefresh:(__bridge id)(displayLink)];
224259
});
225260
return kCVReturnSuccess;

Tests/Tests/SDUtilsTests.m

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,6 @@ - (void)testSDDisplayLink {
4848
XCTestExpectation *expectation1 = [self expectationWithDescription:@"Display Link Stop"];
4949
XCTestExpectation *expectation2 = [self expectationWithDescription:@"Display Link Start"];
5050
SDDisplayLink *displayLink = [SDDisplayLink displayLinkWithTarget:self selector:@selector(displayLinkDidRefresh:)];
51-
NSTimeInterval duration = displayLink.duration; // Initial value
52-
expect(duration).equal(1.0 / 60);
5351
[displayLink addToRunLoop:NSRunLoop.mainRunLoop forMode:NSRunLoopCommonModes];
5452
[displayLink start];
5553
expect(displayLink.isRunning).beTruthy();

0 commit comments

Comments
 (0)