winemac: Use CVDisplayLink to limit window redrawing to the display refresh rate.
Some Windows apps cause user32 to flush the window surface much faster than the display refresh rate. The Mac driver only marks its window as needing to be redrawn and lets Cocoa decide how often to actually redraw. Unfortunately, Cocoa redraws each time through the run loop and, since the Mac driver uses a run loop source to convey messages from background threads to the main thread, it redraws after every batch of messages. On some versions of OS X, this excessive drawing provokes synchronization with the window server's buffer swaps, preventing the main thread from being responsive. Even when that doesn't happen, it's wasteful. So, we set our windows' autodisplay property to false so that Cocoa never displays windows itself. Then, we arrange to call -displayIfNeeded once per display refresh cycle using a CVDisplayLink. We maintain one CVDisplayLink per display (on demand), move windows among them as the windows change screens, start them when they acquire their first window, and stop them when they have none left. Signed-off-by: Ken Thomases <ken@codeweavers.com> Signed-off-by: Alexandre Julliard <julliard@winehq.org>
This commit is contained in:
parent
190388ed1f
commit
3beec95a09
|
@ -1,7 +1,7 @@
|
||||||
MODULE = winemac.drv
|
MODULE = winemac.drv
|
||||||
IMPORTS = uuid user32 gdi32 advapi32
|
IMPORTS = uuid user32 gdi32 advapi32
|
||||||
DELAYIMPORTS = ole32 shell32 imm32
|
DELAYIMPORTS = ole32 shell32 imm32
|
||||||
EXTRALIBS = -framework AppKit -framework Carbon -framework Security -framework OpenGL -framework IOKit
|
EXTRALIBS = -framework AppKit -framework Carbon -framework Security -framework OpenGL -framework IOKit -framework CoreVideo
|
||||||
|
|
||||||
C_SRCS = \
|
C_SRCS = \
|
||||||
clipboard.c \
|
clipboard.c \
|
||||||
|
|
|
@ -43,6 +43,8 @@ @interface WineWindow : NSPanel <NSWindowDelegate>
|
||||||
void* surface;
|
void* surface;
|
||||||
pthread_mutex_t* surface_mutex;
|
pthread_mutex_t* surface_mutex;
|
||||||
|
|
||||||
|
CGDirectDisplayID _lastDisplayID;
|
||||||
|
|
||||||
NSBezierPath* shape;
|
NSBezierPath* shape;
|
||||||
NSData* shapeData;
|
NSData* shapeData;
|
||||||
BOOL shapeChangedSinceLastDraw;
|
BOOL shapeChangedSinceLastDraw;
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#import <Carbon/Carbon.h>
|
#import <Carbon/Carbon.h>
|
||||||
|
#import <CoreVideo/CoreVideo.h>
|
||||||
|
|
||||||
#import "cocoa_window.h"
|
#import "cocoa_window.h"
|
||||||
|
|
||||||
|
@ -155,6 +156,100 @@ - (id) _displayChanged;
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
|
||||||
|
@interface WineDisplayLink : NSObject
|
||||||
|
{
|
||||||
|
CGDirectDisplayID _displayID;
|
||||||
|
CVDisplayLinkRef _link;
|
||||||
|
NSMutableSet* _windows;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (id) initWithDisplayID:(CGDirectDisplayID)displayID;
|
||||||
|
|
||||||
|
- (void) addWindow:(WineWindow*)window;
|
||||||
|
- (void) removeWindow:(WineWindow*)window;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation WineDisplayLink
|
||||||
|
|
||||||
|
static CVReturn WineDisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp* inNow, const CVTimeStamp* inOutputTime, CVOptionFlags flagsIn, CVOptionFlags* flagsOut, void* displayLinkContext);
|
||||||
|
|
||||||
|
- (id) initWithDisplayID:(CGDirectDisplayID)displayID
|
||||||
|
{
|
||||||
|
self = [super init];
|
||||||
|
if (self)
|
||||||
|
{
|
||||||
|
CVReturn status = CVDisplayLinkCreateWithCGDisplay(displayID, &_link);
|
||||||
|
if (status == kCVReturnSuccess && !_link)
|
||||||
|
status = kCVReturnError;
|
||||||
|
if (status == kCVReturnSuccess)
|
||||||
|
status = CVDisplayLinkSetOutputCallback(_link, WineDisplayLinkCallback, self);
|
||||||
|
if (status != kCVReturnSuccess)
|
||||||
|
{
|
||||||
|
[self release];
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
_displayID = displayID;
|
||||||
|
_windows = [[NSMutableSet alloc] init];
|
||||||
|
}
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void) dealloc
|
||||||
|
{
|
||||||
|
if (_link)
|
||||||
|
{
|
||||||
|
CVDisplayLinkStop(_link);
|
||||||
|
CVDisplayLinkRelease(_link);
|
||||||
|
}
|
||||||
|
[_windows release];
|
||||||
|
[super dealloc];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void) addWindow:(WineWindow*)window
|
||||||
|
{
|
||||||
|
@synchronized(self) {
|
||||||
|
BOOL needsStart = !_windows.count;
|
||||||
|
[_windows addObject:window];
|
||||||
|
if (needsStart)
|
||||||
|
CVDisplayLinkStart(_link);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void) removeWindow:(WineWindow*)window
|
||||||
|
{
|
||||||
|
@synchronized(self) {
|
||||||
|
BOOL wasRunning = _windows.count > 0;
|
||||||
|
[_windows removeObject:window];
|
||||||
|
if (wasRunning && !_windows.count)
|
||||||
|
CVDisplayLinkStop(_link);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void) fire
|
||||||
|
{
|
||||||
|
NSSet* windows;
|
||||||
|
@synchronized(self) {
|
||||||
|
windows = [_windows copy];
|
||||||
|
}
|
||||||
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
|
for (WineWindow* window in windows)
|
||||||
|
[window displayIfNeeded];
|
||||||
|
});
|
||||||
|
[windows release];
|
||||||
|
}
|
||||||
|
|
||||||
|
static CVReturn WineDisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp* inNow, const CVTimeStamp* inOutputTime, CVOptionFlags flagsIn, CVOptionFlags* flagsOut, void* displayLinkContext)
|
||||||
|
{
|
||||||
|
WineDisplayLink* link = displayLinkContext;
|
||||||
|
[link fire];
|
||||||
|
return kCVReturnSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
|
||||||
@interface WineContentView : NSView <NSTextInputClient>
|
@interface WineContentView : NSView <NSTextInputClient>
|
||||||
{
|
{
|
||||||
NSMutableArray* glContexts;
|
NSMutableArray* glContexts;
|
||||||
|
@ -592,6 +687,7 @@ + (WineWindow*) createWindowWithFeatures:(const struct macdrv_window_features*)w
|
||||||
[window setAcceptsMouseMovedEvents:YES];
|
[window setAcceptsMouseMovedEvents:YES];
|
||||||
[window setColorSpace:[NSColorSpace genericRGBColorSpace]];
|
[window setColorSpace:[NSColorSpace genericRGBColorSpace]];
|
||||||
[window setDelegate:window];
|
[window setDelegate:window];
|
||||||
|
[window setAutodisplay:NO];
|
||||||
window.hwnd = hwnd;
|
window.hwnd = hwnd;
|
||||||
window.queue = queue;
|
window.queue = queue;
|
||||||
window->savedContentMinSize = NSZeroSize;
|
window->savedContentMinSize = NSZeroSize;
|
||||||
|
@ -637,6 +733,11 @@ + (WineWindow*) createWindowWithFeatures:(const struct macdrv_window_features*)w
|
||||||
name:NSApplicationDidUnhideNotification
|
name:NSApplicationDidUnhideNotification
|
||||||
object:NSApp];
|
object:NSApp];
|
||||||
|
|
||||||
|
[[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:window
|
||||||
|
selector:@selector(checkWineDisplayLink)
|
||||||
|
name:NSWorkspaceActiveSpaceDidChangeNotification
|
||||||
|
object:[NSWorkspace sharedWorkspace]];
|
||||||
|
|
||||||
return window;
|
return window;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1207,6 +1308,7 @@ - (void) orderBelow:(WineWindow*)prev orAbove:(WineWindow*)next activate:(BOOL)a
|
||||||
if ([self level] != [other level])
|
if ([self level] != [other level])
|
||||||
[self setLevel:[other level]];
|
[self setLevel:[other level]];
|
||||||
[self orderWindow:orderingMode relativeTo:[other windowNumber]];
|
[self orderWindow:orderingMode relativeTo:[other windowNumber]];
|
||||||
|
[self checkWineDisplayLink];
|
||||||
|
|
||||||
// The above call to -[NSWindow orderWindow:relativeTo:] won't
|
// The above call to -[NSWindow orderWindow:relativeTo:] won't
|
||||||
// reorder windows which are both children of the same parent
|
// reorder windows which are both children of the same parent
|
||||||
|
@ -1225,6 +1327,7 @@ - (void) orderBelow:(WineWindow*)prev orAbove:(WineWindow*)next activate:(BOOL)a
|
||||||
if (next && [self level] < [next level])
|
if (next && [self level] < [next level])
|
||||||
[self setLevel:[next level]];
|
[self setLevel:[next level]];
|
||||||
[self orderFront:nil];
|
[self orderFront:nil];
|
||||||
|
[self checkWineDisplayLink];
|
||||||
needAdjustWindowLevels = TRUE;
|
needAdjustWindowLevels = TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1278,6 +1381,7 @@ - (void) doOrderOut
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
[self orderOut:nil];
|
[self orderOut:nil];
|
||||||
|
[self checkWineDisplayLink];
|
||||||
savedVisibleState = FALSE;
|
savedVisibleState = FALSE;
|
||||||
if (wasVisible && wasOnActiveSpace && fullscreen)
|
if (wasVisible && wasOnActiveSpace && fullscreen)
|
||||||
[controller updateFullscreenWindows];
|
[controller updateFullscreenWindows];
|
||||||
|
@ -1573,6 +1677,56 @@ - (void) endWindowDragging
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void) checkWineDisplayLink
|
||||||
|
{
|
||||||
|
NSScreen* screen = self.screen;
|
||||||
|
if (![self isVisible] || ![self isOnActiveSpace] || [self isMiniaturized] || [self isEmptyShaped])
|
||||||
|
screen = nil;
|
||||||
|
#if defined(MAC_OS_X_VERSION_10_9) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_9
|
||||||
|
if ([self respondsToSelector:@selector(occlusionState)] && !(self.occlusionState & NSWindowOcclusionStateVisible))
|
||||||
|
screen = nil;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
NSNumber* displayIDNumber = [screen.deviceDescription objectForKey:@"NSScreenNumber"];
|
||||||
|
CGDirectDisplayID displayID = [displayIDNumber unsignedIntValue];
|
||||||
|
if (displayID == _lastDisplayID)
|
||||||
|
return;
|
||||||
|
|
||||||
|
static NSMutableDictionary* displayIDToDisplayLinkMap;
|
||||||
|
if (!displayIDToDisplayLinkMap)
|
||||||
|
{
|
||||||
|
displayIDToDisplayLinkMap = [[NSMutableDictionary alloc] init];
|
||||||
|
|
||||||
|
[[NSNotificationCenter defaultCenter] addObserverForName:NSApplicationDidChangeScreenParametersNotification
|
||||||
|
object:NSApp
|
||||||
|
queue:nil
|
||||||
|
usingBlock:^(NSNotification *note){
|
||||||
|
NSMutableSet* badDisplayIDs = [NSMutableSet setWithArray:displayIDToDisplayLinkMap.allKeys];
|
||||||
|
NSSet* validDisplayIDs = [NSSet setWithArray:[[NSScreen screens] valueForKeyPath:@"deviceDescription.NSScreenNumber"]];
|
||||||
|
[badDisplayIDs minusSet:validDisplayIDs];
|
||||||
|
[displayIDToDisplayLinkMap removeObjectsForKeys:[badDisplayIDs allObjects]];
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_lastDisplayID)
|
||||||
|
{
|
||||||
|
WineDisplayLink* link = [displayIDToDisplayLinkMap objectForKey:[NSNumber numberWithUnsignedInt:_lastDisplayID]];
|
||||||
|
[link removeWindow:self];
|
||||||
|
}
|
||||||
|
if (displayID)
|
||||||
|
{
|
||||||
|
WineDisplayLink* link = [displayIDToDisplayLinkMap objectForKey:displayIDNumber];
|
||||||
|
if (!link)
|
||||||
|
{
|
||||||
|
link = [[[WineDisplayLink alloc] initWithDisplayID:displayID] autorelease];
|
||||||
|
[displayIDToDisplayLinkMap setObject:link forKey:displayIDNumber];
|
||||||
|
}
|
||||||
|
[link addWindow:self];
|
||||||
|
[self displayIfNeeded];
|
||||||
|
}
|
||||||
|
_lastDisplayID = displayID;
|
||||||
|
}
|
||||||
|
|
||||||
- (BOOL) isEmptyShaped
|
- (BOOL) isEmptyShaped
|
||||||
{
|
{
|
||||||
return (self.shapeData.length == sizeof(CGRectZero) && !memcmp(self.shapeData.bytes, &CGRectZero, sizeof(CGRectZero)));
|
return (self.shapeData.length == sizeof(CGRectZero) && !memcmp(self.shapeData.bytes, &CGRectZero, sizeof(CGRectZero)));
|
||||||
|
@ -1674,6 +1828,7 @@ - (void) checkEmptyShaped
|
||||||
self.dockTile.contentView = nil;
|
self.dockTile.contentView = nil;
|
||||||
lastDockIconSnapshot = 0;
|
lastDockIconSnapshot = 0;
|
||||||
}
|
}
|
||||||
|
[self checkWineDisplayLink];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -2046,6 +2201,16 @@ - (void)windowDidBecomeKey:(NSNotification *)notification
|
||||||
[controller windowGotFocus:self];
|
[controller windowGotFocus:self];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void) windowDidChangeOcclusionState:(NSNotification*)notification
|
||||||
|
{
|
||||||
|
[self checkWineDisplayLink];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void) windowDidChangeScreen:(NSNotification*)notification
|
||||||
|
{
|
||||||
|
[self checkWineDisplayLink];
|
||||||
|
}
|
||||||
|
|
||||||
- (void)windowDidDeminiaturize:(NSNotification *)notification
|
- (void)windowDidDeminiaturize:(NSNotification *)notification
|
||||||
{
|
{
|
||||||
WineApplicationController* controller = [WineApplicationController sharedController];
|
WineApplicationController* controller = [WineApplicationController sharedController];
|
||||||
|
@ -2072,6 +2237,7 @@ - (void)windowDidDeminiaturize:(NSNotification *)notification
|
||||||
}
|
}
|
||||||
|
|
||||||
[self windowDidResize:notification];
|
[self windowDidResize:notification];
|
||||||
|
[self checkWineDisplayLink];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void) windowDidEndLiveResize:(NSNotification *)notification
|
- (void) windowDidEndLiveResize:(NSNotification *)notification
|
||||||
|
@ -2115,6 +2281,7 @@ - (void)windowDidMiniaturize:(NSNotification *)notification
|
||||||
{
|
{
|
||||||
if (fullscreen && [self isOnActiveSpace])
|
if (fullscreen && [self isOnActiveSpace])
|
||||||
[[WineApplicationController sharedController] updateFullscreenWindows];
|
[[WineApplicationController sharedController] updateFullscreenWindows];
|
||||||
|
[self checkWineDisplayLink];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)windowDidMove:(NSNotification *)notification
|
- (void)windowDidMove:(NSNotification *)notification
|
||||||
|
|
Loading…
Reference in New Issue