JBR-8159: kill CVDisplayLink zombies (sleep / wake-up with multiple monitors in mirroring) + deal with display link thread shutdown (destroy threads at sleep) and restart when needed

This commit is contained in:
bourgesl
2025-01-25 14:35:25 +01:00
committed by jbrbot
parent 21d6f5e725
commit 72b5d54c64
7 changed files with 793 additions and 240 deletions

View File

@@ -863,7 +863,7 @@ MTLBlitLoops_CopyArea(JNIEnv *env,
MTLCommandBufferWrapper * cbwrapper =
[mtlc pullCommandBufferWrapper];
id<MTLCommandBuffer> commandbuf = [cbwrapper getCommandBuffer];
[commandbuf addCompletedHandler:^(id <MTLCommandBuffer> commandbuf) {
[commandbuf addCompletedHandler:^(id <MTLCommandBuffer> cb) {
[cbwrapper release];
}];
[commandbuf commit];

View File

@@ -90,7 +90,7 @@
@property (strong) id<MTLCommandQueue> blitCommandQueue;
@property (strong) id<MTLBuffer> vertexBuffer;
@property (readonly) NSMutableDictionary<NSNumber*, NSValue*>* displayLinks;
@property (readonly) NSMutableDictionary<NSNumber*, NSValue*>* displayLinkStates;
@property (readonly) EncoderManager * encoderManager;
@property (readonly) MTLSamplerManager * samplerManager;
@@ -111,10 +111,11 @@
+ (NSMutableDictionary*) contextStore;
+ (MTLContext*) createContextWithDeviceIfAbsent:(jint)displayID shadersLib:(NSString*)mtlShadersLib;
- (id)initWithDevice:(id<MTLDevice>)device display:(jint) displayID shadersLib:(NSString*)mtlShadersLib;
- (id)initWithDevice:(id<MTLDevice>)device shadersLib:(NSString*)mtlShadersLib;
- (void)dealloc;
- (void)handleDisplayLink:(BOOL)enabled display:(jint)display source:(const char*)src;
- (NSArray<NSNumber*>*)getDisplayLinkDisplayIds;
- (void)handleDisplayLink:(BOOL)enabled displayID:(jint)displayID source:(const char*)src;
- (void)createDisplayLinkIfAbsent: (jint)displayID;
/**
@@ -253,6 +254,7 @@
- (void)commitCommandBuffer:(BOOL)waitUntilCompleted display:(BOOL)updateDisplay;
- (void)startRedraw:(MTLLayer*)layer;
- (void)stopRedraw:(MTLLayer*)layer;
- (void)stopRedraw:(jint)displayID layer:(MTLLayer*)layer;
- (void)haltRedraw;
@end

View File

@@ -39,9 +39,13 @@
// scenarios with multiple subsequent updates.
#define KEEP_ALIVE_COUNT 4
// Min interval between 2 display link callbacks (Main thread may be busy)
// ~ 2ms (shorter than best monitor frame rate = 500 hz)
#define KEEP_ALIVE_MIN_INTERVAL 2.0 / 1000.0
#define TO_MS(x) (1000.0 * (x))
#define TO_FPS(x) (1.0 / (x))
// Min interval(s) between 2 display link callbacks as the appkit (Main)
// thread may be 'blocked' (waiting), then callbacks will be invoked in batches.
// ~ 0.250ms (shorter than best monitor frame rate = 4 khz)
#define KEEP_ALIVE_MIN_INTERVAL (0.250 / 1000.0)
// Amount of blit operations per update to make sure that everything is
// rendered into the window drawable. It does not slow things down as we
@@ -53,20 +57,36 @@ extern BOOL isDisplaySyncEnabled();
extern BOOL MTLLayer_isExtraRedrawEnabled();
extern int getBPPFromModeString(CFStringRef mode);
#define STATS_CVLINK 0
#define STATS_CVLINK 0
#define TRACE_CVLINK 0
#define TRACE_DISPLAY 0
#define TRACE_PWM_NOTIF 0
#define CHECK_CVLINK(op, source, cmd) \
{ \
CVReturn ret = (CVReturn) (cmd); \
if (ret != kCVReturnSuccess) { \
J2dTraceImpl(J2D_TRACE_ERROR, JNI_TRUE, "CVDisplayLink[%s - %s] Error: %d", \
op, (source != nil) ? source : "", ret); \
} \
#define TRACE_CVLINK 0
#define TRACE_CVLINK_WARN 0
#define TRACE_CVLINK_DEBUG 0
#define TRACE_DISPLAY 0
#define CHECK_CVLINK(op, source, dl, cmd) \
{ \
CVReturn ret = (CVReturn) (cmd); \
if (ret != kCVReturnSuccess) { \
J2dTraceImpl(J2D_TRACE_ERROR, JNI_TRUE, "CVDisplayLink[%s - %s][%p] Error: %d", \
op, (source != nil) ? source : "", dl, ret); \
} else if (TRACE_CVLINK) { \
J2dTraceImpl(J2D_TRACE_VERBOSE, JNI_TRUE, "CVDisplayLink[%s - %s][%p]: OK", \
op, (source != nil) ? source : "", dl); \
} \
}
boolean_t mtlc_IsDisplayReallyActive(CGDirectDisplayID displayID) {
return CGDisplayIsActive(displayID) & !CGDisplayIsAsleep(displayID) && CGDisplayIsOnline(displayID);
}
/* 60 fps typically => exponential smoothing on 0.5s */
static const NSTimeInterval EXP_AVG_WEIGHT = (1.0 / 30.0);
static const NSTimeInterval EXP_INV_WEIGHT = (1.0 - EXP_AVG_WEIGHT);
static struct TxtVertex verts[PGRAM_VERTEX_COUNT] = {
{{-1.0, 1.0}, {0.0, 0.0}},
{{1.0, 1.0}, {1.0, 0.0}},
@@ -80,7 +100,15 @@ typedef struct {
jint displayID;
CVDisplayLinkRef displayLink;
MTLContext* mtlc;
} DLParams;
jint redrawCount;
jint avgDisplayLinkSamples;
CFTimeInterval lastRedrawTime;
CFTimeInterval lastDisplayLinkTime;
CFTimeInterval avgDisplayLinkTime;
CFTimeInterval lastStatTime;
} MTLDisplayLinkState;
@implementation MTLCommandBufferWrapper {
id<MTLCommandBuffer> _commandBuffer;
@@ -142,13 +170,7 @@ typedef struct {
@implementation MTLContext {
MTLCommandBufferWrapper * _commandBufferWrapper;
NSMutableSet* _layers;
int _displayLinkCount;
CFTimeInterval _lastDisplayLinkTime;
CFTimeInterval _avgDisplayLinkTime;
CFTimeInterval _lastRedrawTime;
CFTimeInterval _lastStatTime;
NSMutableSet* _layers;
MTLComposite * _composite;
MTLPaint * _paint;
@@ -157,7 +179,7 @@ typedef struct {
MTLClip * _clip;
NSObject* _bufImgOp; // TODO: pass as parameter of IsoBlit
EncoderManager * _encoderManager;
EncoderManager * _encoderManager;
MTLSamplerManager * _samplerManager;
MTLStencilManager * _stencilManager;
}
@@ -171,19 +193,107 @@ typedef struct {
extern void initSamplers(id<MTLDevice> device);
+ (void)mtlc_systemOrScreenWillSleep:(NSNotification*)notification {
if (TRACE_PWM_NOTIF) {
NSLog(@"MTLContext_systemOrScreenWillSleep[%@]", [notification name]);
}
if (isDisplaySyncEnabled()) {
[ThreadUtilities performOnMainThreadNowOrLater:NO // critical
block:^(){
for (MTLContext *mtlc in [MTLContext.contextStore allValues]) {
const NSArray<NSNumber*> *displayIDs = [mtlc getDisplayLinkDisplayIds]; // old ids
if (TRACE_PWM_NOTIF) {
NSLog(@"MTLContext_systemOrScreenWillSleep: ctx=%p (%d displayLinks)",
mtlc, (int) displayIDs.count);
}
for (NSNumber* displayIDVal in displayIDs) {
const jint displayID = [displayIDVal intValue];
const BOOL active = mtlc_IsDisplayReallyActive(displayID);
if (TRACE_PWM_NOTIF) {
NSLog(@"MTLContext_systemOrScreenWillSleep: displayId=%d active=%d", displayID, active);
}
if (TRACE_DISPLAY) {
[MTLContext dumpDisplayInfo:displayID];
}
if ((notification.name == NSWorkspaceWillSleepNotification)|| !active) {
[mtlc destroyDisplayLink:displayID];
}
}
}
}];
}
}
+ (void)mtlc_systemOrScreenDidWake:(NSNotification*)notification {
if (TRACE_PWM_NOTIF) {
NSLog(@"MTLContext_systemOrScreenDidWake[%@]", [notification name]);
}
if (isDisplaySyncEnabled()) {
[ThreadUtilities performOnMainThreadNowOrLater:NO // critical
block:^(){
for (MTLContext *mtlc in [MTLContext.contextStore allValues]) {
const NSArray<NSNumber*>* displayIDs = [mtlc getDisplayLinkDisplayIds]; // old ids
if (TRACE_PWM_NOTIF) {
NSLog(@"MTLContext_systemOrScreenDidWake: ctx=%p (%d displayLinks)",
mtlc, (int)displayIDs.count);
}
for (NSNumber* displayIDVal in displayIDs) {
const jint displayID = [displayIDVal intValue];
const BOOL active = mtlc_IsDisplayReallyActive(displayID);
if (TRACE_PWM_NOTIF) {
NSLog(@"MTLContext_systemOrScreenDidWake: displayId=%d active=%d", displayID, active);
}
if (TRACE_DISPLAY) {
[MTLContext dumpDisplayInfo:displayID];
}
if (active) {
// (if needed will start a new display link thread):
[mtlc createDisplayLinkIfAbsent:displayID];
}
}
}
}];
}
}
+ (void)registerForSystemAndScreenNotifications {
static BOOL notificationRegistered = false;
if (!notificationRegistered) {
notificationRegistered = true;
NSNotificationCenter *ctr = [[NSWorkspace sharedWorkspace] notificationCenter];
Class clz = [MTLContext class];
[ctr addObserver:clz selector:@selector(mtlc_systemOrScreenWillSleep:) name:NSWorkspaceWillSleepNotification object:nil];
[ctr addObserver:clz selector:@selector(mtlc_systemOrScreenDidWake:) name:NSWorkspaceDidWakeNotification object:nil];
// NSWorkspaceScreensDidWakeNotification is first sent:
[ctr addObserver:clz selector:@selector(mtlc_systemOrScreenWillSleep:) name:NSWorkspaceScreensDidSleepNotification object:nil];
[ctr addObserver:clz selector:@selector(mtlc_systemOrScreenDidWake:) name:NSWorkspaceScreensDidWakeNotification object:nil];
}
}
+ (NSMutableDictionary*) contextStore {
static NSMutableDictionary<NSNumber*, MTLContext*> *_contextStore;
static NSMutableDictionary<id<NSCopying>, MTLContext*> *_contextStore;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_contextStore = [[NSMutableDictionary alloc] init];
});
[MTLContext registerForSystemAndScreenNotifications];
});
return _contextStore;
}
+ (MTLContext*) createContextWithDeviceIfAbsent:(jint)displayID shadersLib:(NSString*)mtlShadersLib {
// Initialization code here.
// note: the device reference is NS_RETURNS_RETAINED, should be released by the caller:
bool shouldReleaseDevice = true;
id<MTLDevice> device = CGDirectDisplayCopyCurrentMetalDevice(displayID);
if (device == nil) {
J2dRlsTraceLn1(J2D_TRACE_ERROR, "MTLContext_createContextWithDeviceIfAbsent(): Cannot create device from "
@@ -200,61 +310,73 @@ extern void initSamplers(id<MTLDevice> device);
}
}
id<NSCopying> devID = nil;
id<NSCopying> deviceID = nil;
if (@available(macOS 10.13, *)) {
devID = @(device.registryID);
deviceID = @(device.registryID);
} else {
devID = device.name;
deviceID = device.name;
}
MTLContext* mtlc = MTLContext.contextStore[devID];
MTLContext* mtlc = MTLContext.contextStore[deviceID];
if (mtlc == nil) {
mtlc = [[MTLContext alloc] initWithDevice:device display:displayID shadersLib:mtlShadersLib];
mtlc = [[MTLContext alloc] initWithDevice:device shadersLib:mtlShadersLib];
if (mtlc != nil) {
MTLContext.contextStore[devID] = mtlc;
shouldReleaseDevice = false;
// transfer ownership (objc ref):
MTLContext.contextStore[deviceID] = mtlc;
[mtlc release];
J2dRlsTraceLn4(J2D_TRACE_INFO,
"MTLContext_createContextWithDeviceIfAbsent: new context(%p) for display=%d device=\"%s\" "
"retainCount=%d", mtlc, displayID, [mtlc.device.name UTF8String], mtlc.retainCount)
"retainCount=%d", mtlc, displayID, [mtlc.device.name UTF8String],
mtlc.retainCount);
}
} else {
if (![mtlc.shadersLib isEqualToString:mtlShadersLib]) {
J2dRlsTraceLn3(J2D_TRACE_ERROR,
"MTLContext_createContextWithDeviceIfAbsent: cannot reuse context(%p) for display=%d "
"device=\"%s\", shaders lib has been changed", mtlc, displayID, [mtlc.device.name UTF8String])
[device release];
return nil;
}
[mtlc retain];
J2dRlsTraceLn4(J2D_TRACE_INFO,
"MTLContext_createContextWithDeviceIfAbsent: reuse context(%p) for display=%d device=\"%s\" "
"retainCount=%d", mtlc, displayID, [mtlc.device.name UTF8String], mtlc.retainCount)
"retainCount=%d", mtlc, displayID, [mtlc.device.name UTF8String],
mtlc.retainCount);
}
if (shouldReleaseDevice) {
[device release];
}
// (will start a new display link thread if needed):
[mtlc createDisplayLinkIfAbsent:displayID];
return mtlc;
}
+ (void) releaseContext:(MTLContext*) mtlc {
id<NSCopying> devID = nil;
id<NSCopying> deviceID = nil;
if (@available(macOS 10.13, *)) {
devID = @(mtlc.device.registryID);
deviceID = @(mtlc.device.registryID);
} else {
devID = mtlc.device.name;
deviceID = mtlc.device.name;
}
MTLContext* ctx = MTLContext.contextStore[devID];
MTLContext* ctx = MTLContext.contextStore[deviceID];
if (mtlc == ctx) {
if (mtlc.retainCount > 1) {
[mtlc release];
J2dRlsTraceLn2(J2D_TRACE_INFO, "MTLContext_releaseContext: release context(%p) retainCount=%d", mtlc, mtlc.retainCount);
} else {
[MTLContext.contextStore removeObjectForKey:devID];
// explicit halt redraw to shutdown CVDisplayLink threads before dealloc():
[mtlc haltRedraw];
// remove ownership (objc ref):
[MTLContext.contextStore removeObjectForKey:deviceID];
J2dRlsTraceLn1(J2D_TRACE_INFO, "MTLContext_releaseContext: dealloc context(%p)", mtlc);
}
} else {
J2dRlsTraceLn2(J2D_TRACE_ERROR, "MTLContext_releaseContext: cannot release context(%p) != context store(%p)",
mtlc, ctx);
}
}
- (id)initWithDevice:(id<MTLDevice>)mtlDevice display:(jint) displayID shadersLib:(NSString*)mtlShadersLib {
- (id)initWithDevice:(id<MTLDevice>)mtlDevice shadersLib:(NSString*)mtlShadersLib {
self = [super init];
if (self) {
device = mtlDevice;
@@ -288,14 +410,13 @@ extern void initSamplers(id<MTLDevice> device);
blitCommandQueue = [device newCommandQueue];
_tempTransform = [[MTLTransform alloc] init];
_displayLinkCount = 0;
_lastDisplayLinkTime = 0;
_avgDisplayLinkTime = 0;
_lastRedrawTime = 0.0;
_lastStatTime = 0.0;
if (isDisplaySyncEnabled()) {
_displayLinks = [[NSMutableDictionary alloc] init];
_displayLinkStates = [[NSMutableDictionary alloc] init];
_layers = [[NSMutableSet alloc] init];
} else {
_displayLinkStates = nil;
_layers = nil;
}
_glyphCacheLCD = [[MTLGlyphCache alloc] initWithContext:self];
_glyphCacheAA = [[MTLGlyphCache alloc] initWithContext:self];
@@ -397,67 +518,140 @@ extern void initSamplers(id<MTLDevice> device);
}
}
- (NSArray<NSNumber*>*)getDisplayLinkDisplayIds {
return [_displayLinkStates allKeys];
}
- (void)createDisplayLinkIfAbsent: (jint)displayID {
if (isDisplaySyncEnabled() && _displayLinks[@(displayID)] == nil) {
if (isDisplaySyncEnabled()) {
MTLDisplayLinkState *dlState = [self getDisplayLinkState:displayID];
if ((dlState != nil) && (dlState->displayLink != nil)) {
return;
}
if (TRACE_DISPLAY) {
[MTLContext dumpDisplayInfo:displayID];
}
CVDisplayLinkRef _displayLink;
if (TRACE_CVLINK) {
J2dRlsTraceLn2(J2D_TRACE_VERBOSE, "MTLContext_createDisplayLinkIfAbsent: "
"ctx=%p displayID=%d", self, displayID);
}
CHECK_CVLINK("CreateWithCGDisplay", nil,
CHECK_CVLINK("CreateWithCGDisplay", nil, &_displayLink,
CVDisplayLinkCreateWithCGDisplay(displayID, &_displayLink));
if (_displayLink == nil) {
J2dRlsTraceLn(J2D_TRACE_ERROR,
"MTLContext_createDisplayLinkIfAbsent: Failed to initialize CVDisplayLink.");
} else {
DLParams* dlParams = malloc(sizeof (DLParams ));
dlParams->displayID = displayID;
dlParams->displayLink = _displayLink;
dlParams->mtlc = self;
_displayLinks[@(displayID)] = [NSValue valueWithPointer:dlParams];
CHECK_CVLINK("SetOutputCallback", nil,
J2dRlsTraceLn3(J2D_TRACE_INFO, "MTLContext_destroyDisplayLinkMTLContext_createDisplayLinkIfAbsent["
"ctx=%p displayID=%d] displayLink=%p",
self, displayID, _displayLink);
bool isNewDisplayLink = false;
if (dlState == nil) {
dlState = malloc(sizeof(MTLDisplayLinkState));
isNewDisplayLink = true;
}
// update:
dlState->displayID = displayID;
dlState->displayLink = _displayLink;
dlState->mtlc = self;
dlState->redrawCount = 0;
dlState->avgDisplayLinkSamples = 0;
dlState->lastRedrawTime = 0.0;
dlState->lastDisplayLinkTime = 0.0;
dlState->avgDisplayLinkTime = 0.0;
dlState->lastStatTime = 0.0;
if (isNewDisplayLink) {
// publish fully initialized object:
_displayLinkStates[@(displayID)] = [NSValue valueWithPointer:dlState];
}
CHECK_CVLINK("SetOutputCallback", nil, &_displayLink,
CVDisplayLinkSetOutputCallback(_displayLink, &mtlDisplayLinkCallback,
(__bridge DLParams*) dlParams));
(__bridge MTLDisplayLinkState*) dlState));
}
}
}
- (void)handleDisplayLink: (BOOL)enabled display:(jint) display source:(const char*)src {
if (_displayLinks == nil) {
if (TRACE_CVLINK) {
J2dRlsTraceLn2(J2D_TRACE_VERBOSE, "MTLContext_handleDisplayLink[%s]: "
"ctx=%p - displayLinks = nil", src, self);
- (NSValue*)getDisplayLinkState: (jint)displayID {
if (_displayLinkStates == nil) {
if (TRACE_CVLINK_WARN) {
J2dRlsTraceLn2(J2D_TRACE_VERBOSE, "MTLContext_getDisplayLinkState[ctx=%p displayID=%d]: "
"displayLinkStates is nil!", self, displayID);
}
} else {
NSValue* dlParamsVal = _displayLinks[@(display)];
if (dlParamsVal == nil) {
J2dRlsTraceLn3(J2D_TRACE_ERROR, "MTLContext_handleDisplayLink[%s]: "
"ctx=%p, no display link for %d ", src, self, display);
return nil;
}
NSValue* dlStateVal = _displayLinkStates[@(displayID)];
if (dlStateVal == nil) {
if (TRACE_CVLINK_WARN) {
J2dRlsTraceLn2(J2D_TRACE_ERROR, "MTLContext_getDisplayLinkState[ctx=%p displayID=%d]: "
"dlState is nil!", self, displayID);
}
return nil;
}
return [dlStateVal pointerValue];
}
- (void)destroyDisplayLink: (jint)displayID {
if (isDisplaySyncEnabled()) {
MTLDisplayLinkState *dlState = [self getDisplayLinkState:displayID];
if (dlState == nil) {
return;
}
DLParams *dlParams = [dlParamsVal pointerValue];
CVDisplayLinkRef _displayLink = dlParams->displayLink;
if (enabled) {
if (!CVDisplayLinkIsRunning(_displayLink)) {
CHECK_CVLINK("Start", src, CVDisplayLinkStart(_displayLink));
if (TRACE_CVLINK) {
J2dRlsTraceLn2(J2D_TRACE_VERBOSE, "MTLContext_CVDisplayLinkStart[%s]: "
"ctx=%p", src, self);
}
CVDisplayLinkRef _displayLink = dlState->displayLink;
if (_displayLink == nil) {
return;
}
if (TRACE_CVLINK) {
J2dRlsTraceLn2(J2D_TRACE_VERBOSE, "MTLContext_destroyDisplayLink: "
"ctx=%p, displayID=%d", self, displayID);
}
if (CVDisplayLinkIsRunning(_displayLink)) {
CHECK_CVLINK("Stop", "destroyDisplayLink", &_displayLink,
CVDisplayLinkStop(_displayLink));
}
J2dRlsTraceLn3(J2D_TRACE_INFO, "MTLContext_destroyDisplayLink["
"ctx=%p displayID=%d] displayLink=%p",
self, displayID, _displayLink);
// Release display link thread:
CVDisplayLinkRelease(_displayLink);
dlState->displayLink = nil;
}
}
- (void)handleDisplayLink: (BOOL)enabled displayID:(jint)displayID source:(const char*)src {
MTLDisplayLinkState *dlState = [self getDisplayLinkState:displayID];
if (dlState == nil) {
return;
}
CVDisplayLinkRef _displayLink = dlState->displayLink;
if (_displayLink == nil) {
if (TRACE_CVLINK) {
J2dRlsTraceLn1(J2D_TRACE_VERBOSE, "MTLContext_handleDisplayLink[%s]: "
"displayLink is nil (disabled).", src);
}
return;
}
if (enabled) {
if (!CVDisplayLinkIsRunning(_displayLink)) {
CHECK_CVLINK("Start", src, &_displayLink,
CVDisplayLinkStart(_displayLink));
if (TRACE_CVLINK) {
J2dRlsTraceLn2(J2D_TRACE_VERBOSE, "MTLContext_CVDisplayLinkStart[%s]: "
"ctx=%p", src, self);
}
} else {
if (CVDisplayLinkIsRunning(_displayLink)) {
CHECK_CVLINK("Stop", src, CVDisplayLinkStop(_displayLink));
if (TRACE_CVLINK) {
J2dRlsTraceLn2(J2D_TRACE_VERBOSE, "MTLContext_CVDisplayLinkStop[%s]: "
"ctx=%p", src, self);
}
}
} else {
if (CVDisplayLinkIsRunning(_displayLink)) {
CHECK_CVLINK("Stop", src, &_displayLink,
CVDisplayLinkStop(_displayLink));
if (TRACE_CVLINK) {
J2dRlsTraceLn2(J2D_TRACE_VERBOSE, "MTLContext_CVDisplayLinkStop[%s]: "
"ctx=%p", src, self);
}
}
}
@@ -466,8 +660,10 @@ extern void initSamplers(id<MTLDevice> device);
- (void)dealloc {
J2dTraceLn(J2D_TRACE_INFO, "MTLContext.dealloc");
if (_displayLinks != nil) {
if (_displayLinkStates != nil) {
[self haltRedraw];
[_displayLinkStates release];
_displayLinkStates = nil;
}
[shadersLib release];
@@ -538,7 +734,7 @@ extern void initSamplers(id<MTLDevice> device);
- (MTLCommandBufferWrapper *) getCommandBufferWrapper {
if (_commandBufferWrapper == nil) {
J2dTraceLn(J2D_TRACE_VERBOSE, "MTLContext : commandBuffer is NULL");
J2dTraceLn(J2D_TRACE_VERBOSE, "MTLContext : commandBuffer is nil!");
// NOTE: Command queues are thread-safe and allow multiple outstanding command buffers to be encoded simultaneously.
_commandBufferWrapper = [[MTLCommandBufferWrapper alloc] initWithCommandBuffer:[self.commandQueue commandBuffer]];// released in [layer blitTexture]
}
@@ -814,7 +1010,7 @@ extern void initSamplers(id<MTLDevice> device);
MTLCommandBufferWrapper * cbwrapper = [self pullCommandBufferWrapper];
if (cbwrapper != nil) {
id <MTLCommandBuffer> commandbuf =[cbwrapper getCommandBuffer];
[commandbuf addCompletedHandler:^(id <MTLCommandBuffer> commandbuf) {
[commandbuf addCompletedHandler:^(id <MTLCommandBuffer> cb) {
[cbwrapper release];
}];
[commandbuf commit];
@@ -825,94 +1021,169 @@ extern void initSamplers(id<MTLDevice> device);
}
}
- (void) redraw:(NSNumber*)displayIDNum {
- (void) redraw:(NSNumber*)displayIDNumber {
AWT_ASSERT_APPKIT_THREAD;
const CFTimeInterval now = CACurrentMediaTime();
const jint displayID = [displayIDNumber intValue];
MTLDisplayLinkState *dlState = [self getDisplayLinkState:displayID];
if (dlState == nil) {
return;
}
/*
* Avoid repeated invocations by UIKit Main Thread
* if blocked while many mtlDisplayLinkCallback() are dispatched
*/
const CFTimeInterval now = CACurrentMediaTime();
const CFTimeInterval elapsed = (_lastRedrawTime != 0.0) ? (now - _lastRedrawTime) : -1.0;
const CFTimeInterval elapsed = (dlState->lastRedrawTime != 0.0) ? (now - dlState->lastRedrawTime) : -1.0;
if ((elapsed >= 0.0) && (elapsed <= KEEP_ALIVE_MIN_INTERVAL)) {
CFTimeInterval threshold = (dlState->avgDisplayLinkSamples >= 10) ?
(dlState->avgDisplayLinkTime / 20.0) : KEEP_ALIVE_MIN_INTERVAL;
if (threshold < KEEP_ALIVE_MIN_INTERVAL) {
threshold = KEEP_ALIVE_MIN_INTERVAL;
}
if ((elapsed >= 0.0) && (elapsed <= threshold)) {
if (TRACE_CVLINK) {
NSLog(@"MTLContext_redraw[displayID: %d]: %.3f < %.3f ms, skip redraw.",
displayID, TO_MS(elapsed), TO_MS(threshold));
}
return;
}
_lastRedrawTime = now;
if (TRACE_CVLINK_DEBUG) {
NSLog(@"MTLContext_redraw[displayID: %d]: elapsed: %.3f ms (> %.3f)",
displayID, TO_MS(elapsed), TO_MS(threshold));
}
dlState->lastRedrawTime = now;
// Process layers:
for (MTLLayer *layer in _layers) {
[layer setNeedsDisplay];
}
if (_displayLinkCount > 0) {
_displayLinkCount--;
} else {
if (_layers.count > 0) {
[_layers removeAllObjects];
if (layer.displayID == displayID) {
[layer setNeedsDisplay];
}
[self handleDisplayLink:NO display:[displayIDNum integerValue] source:"redraw"];
}
if (dlState->redrawCount > 0) {
dlState->redrawCount--;
} else {
// dlState->redrawCount == 0:
if (_layers.count > 0) {
for (MTLLayer *layer in _layers.allObjects) {
if (layer.displayID == displayID) {
[_layers removeObject:layer];
}
}
}
[self handleDisplayLink:NO displayID:displayID source:"redraw"];
}
}
CVReturn mtlDisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp* nowTime, const CVTimeStamp* outputTime, CVOptionFlags flagsIn, CVOptionFlags* flagsOut, void* displayLinkContext)
{
J2dTraceLn1(J2D_TRACE_VERBOSE, "MTLContext_mtlDisplayLinkCallback: ctx=%p", displayLinkContext);
@autoreleasepool {
const DLParams* dlParams = (__bridge DLParams* *)displayLinkContext;
const MTLContext* mtlc = dlParams->mtlc;
const jint displayID = dlParams->displayID;
CVReturn mtlDisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp* nowTime, const CVTimeStamp* outputTime, CVOptionFlags flagsIn, CVOptionFlags* flagsOut, void* displayLinkContext) {
JNI_COCOA_ENTER(env);
J2dTraceLn1(J2D_TRACE_VERBOSE, "MTLContext_mtlDisplayLinkCallback: ctx=%p", displayLinkContext);
MTLDisplayLinkState *dlState = (__bridge MTLDisplayLinkState*) displayLinkContext;
if (dlState == nil) {
if (TRACE_CVLINK_WARN) {
NSLog(@"MTLContext_mtlDisplayLinkCallback: dlState is nil!");
}
return kCVReturnError;
}
const MTLContext *mtlc = dlState->mtlc;
const jint displayID = dlState->displayID;
if (STATS_CVLINK) {
const CFTimeInterval now = outputTime->videoTime / (double)outputTime->videoTimeScale; // seconds
const CFTimeInterval delta = (mtlc->_lastDisplayLinkTime != 0.0) ? (now - mtlc->_lastDisplayLinkTime) : -1.0;
mtlc->_lastDisplayLinkTime = now;
const CFTimeInterval now = outputTime->videoTime / (double) outputTime->videoTimeScale; // seconds
const CFTimeInterval delta = (dlState->lastDisplayLinkTime != 0.0) ? (now - dlState->lastDisplayLinkTime)
: -1.0;
dlState->lastDisplayLinkTime = now;
const NSTimeInterval a = 1.0 / 30.0; // 60 fps typically => exponential smoothing on 0.5s:
mtlc->_avgDisplayLinkTime = delta * a + mtlc->_avgDisplayLinkTime * (1.0 - a);
dlState->avgDisplayLinkSamples++;
dlState->avgDisplayLinkTime = EXP_AVG_WEIGHT * delta + EXP_INV_WEIGHT * dlState->avgDisplayLinkTime;
if (mtlc->_lastStatTime == 0.0) {
mtlc->_lastStatTime = now;
} else if ((now - mtlc->_lastStatTime) > 1.0) {
mtlc->_lastStatTime = now;
if (dlState->lastStatTime == 0.0) {
dlState->lastStatTime = now;
} else if ((now - dlState->lastStatTime) > 1.0) {
dlState->lastStatTime = now;
// dump stats:
NSLog(@"mtlDisplayLinkCallback[%d]: avgDisplayLinkTime = %.3lf ms", displayID,
1000.0 * mtlc->_avgDisplayLinkTime);
NSLog(@"mtlDisplayLinkCallback[displayID: %d]: avg interval = %.3lf ms (%.1lf fps) on %d samples", displayID,
TO_MS(dlState->avgDisplayLinkTime), TO_FPS(dlState->avgDisplayLinkTime), dlState->avgDisplayLinkSamples);
}
}
[ThreadUtilities performOnMainThread:@selector(redraw:) on:mtlc withObject:@(displayID)
waitUntilDone:NO useJavaModes:NO]; // critical
}
const BOOL active = mtlc_IsDisplayReallyActive(displayID);
if (TRACE_CVLINK_DEBUG) {
NSLog(@"MTLContext_mtlDisplayLinkCallback: ctx=%p displayID=%d active=%d (display link = %p)",
mtlc, displayID, active, dlState->displayLink);
}
if (!active) {
if (TRACE_CVLINK) {
NSLog(@"MTLContext_mtlDisplayLinkCallback: displayId=%d active=%d "
"=> destroyDisplayLink", displayID, active);
}
if (TRACE_DISPLAY) {
[MTLContext dumpDisplayInfo:displayID];
}
[mtlc destroyDisplayLink:displayID];
} else {
[ThreadUtilities performOnMainThread:@selector(redraw:) on:mtlc withObject:@(displayID)
waitUntilDone:NO useJavaModes:NO]; // critical
}
JNI_COCOA_EXIT(env);
return kCVReturnSuccess;
}
- (void)startRedraw:(MTLLayer*)layer {
AWT_ASSERT_APPKIT_THREAD;
layer.redrawCount = REDRAW_COUNT;
J2dTraceLn2(J2D_TRACE_VERBOSE, "MTLContext_startRedraw: ctx=%p layer=%p", self, layer);
_displayLinkCount = KEEP_ALIVE_COUNT;
const jint displayID = layer.displayID;
MTLDisplayLinkState *dlState = [self getDisplayLinkState:displayID];
if (dlState == nil) {
return;
}
dlState->redrawCount = KEEP_ALIVE_COUNT;
layer.redrawCount = REDRAW_COUNT;
[_layers addObject:layer];
if (MTLLayer_isExtraRedrawEnabled()) {
// Request for redraw before starting display link to avoid rendering problem on M2 processor
[layer setNeedsDisplay];
}
[self handleDisplayLink:YES display:layer.displayID source:"startRedraw"];
[self handleDisplayLink:YES displayID:displayID source:"startRedraw"];
}
- (void)stopRedraw:(MTLLayer*) layer {
[self stopRedraw:layer.displayID layer:layer];
}
- (void)stopRedraw:(jint)displayID layer:(MTLLayer*)layer {
AWT_ASSERT_APPKIT_THREAD;
J2dTraceLn2(J2D_TRACE_VERBOSE, "MTLContext_stopRedraw: ctx=%p layer=%p", self, layer);
if (_displayLinks != nil) {
if (--layer.redrawCount <= 0) {
[_layers removeObject:layer];
layer.redrawCount = 0;
}
if ((_layers.count == 0) && (_displayLinkCount == 0)) {
[self handleDisplayLink:NO display:layer.displayID source:"stopRedraw"];
J2dTraceLn3(J2D_TRACE_VERBOSE, "MTLContext_stopRedraw: ctx=%p displayID=%d layer=%p",
self, displayID, layer);
MTLDisplayLinkState *dlState = [self getDisplayLinkState:displayID];
if (dlState == nil) {
return;
}
if (--layer.redrawCount <= 0) {
[_layers removeObject:layer];
layer.redrawCount = 0;
}
bool hasLayers = false;
if (_layers.count > 0) {
for (MTLLayer *l in _layers) {
if (l.displayID == displayID) {
hasLayers = true;
break;
}
}
}
if (!hasLayers && (dlState->redrawCount == 0)) {
[self handleDisplayLink:NO displayID:displayID source:"stopRedraw"];
}
}
- (void)haltRedraw {
if (_displayLinks != nil) {
if (_displayLinkStates != nil) {
if (TRACE_CVLINK) {
J2dRlsTraceLn1(J2D_TRACE_VERBOSE, "MTLContext_haltRedraw: ctx=%p", self);
}
@@ -922,17 +1193,19 @@ CVReturn mtlDisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp*
}
[_layers removeAllObjects];
}
_displayLinkCount = 0;
NSEnumerator<NSNumber*>* keyEnum = _displayLinks.keyEnumerator;
NSNumber* displayIDVal;
while ((displayIDVal = [keyEnum nextObject])) {
[self handleDisplayLink:NO display:[displayIDVal integerValue] source:"haltRedraw"];
DLParams *dlParams = [(NSValue*)_displayLinks[displayIDVal] pointerValue];
CVDisplayLinkRelease(dlParams->displayLink);
free(dlParams);
if (_displayLinkStates.count > 0) {
for (NSNumber* displayIDVal in [self getDisplayLinkDisplayIds]) {
const jint displayID = [displayIDVal intValue];
[self destroyDisplayLink:displayID];
MTLDisplayLinkState *dlState = [self getDisplayLinkState:displayID];
if (dlState != nil) {
// Remove reference:
[_displayLinkStates removeObjectForKey:@(displayID)];
free(dlState);
}
}
}
[_displayLinks release];
_displayLinks = NULL;
}
}

View File

@@ -59,6 +59,7 @@
- (void) startRedraw;
- (void) startRedrawIfNeeded;
- (void) stopRedraw:(BOOL)force;
- (void) stopRedraw:(MTLContext*)mtlc displayID:(jint)displayID force:(BOOL)force;
- (void) flushBuffer;
- (void) commitCommandBuffer:(MTLContext*)mtlc wait:(BOOL)waitUntilCompleted display:(BOOL)updateDisplay;
- (void) countFramePresentedCallback;

View File

@@ -39,7 +39,8 @@ static jclass jc_JavaLayer = NULL;
#define GET_MTL_LAYER_CLASS() \
GET_CLASS(jc_JavaLayer, "sun/java2d/metal/MTLLayer");
#define TRACE_DISPLAY 0
#define TRACE_DISPLAY 0
#define TRACE_DISPLAY_CHANGED 0
const NSTimeInterval DF_BLIT_FRAME_TIME=1.0/120.0;
@@ -120,7 +121,8 @@ BOOL MTLLayer_isExtraRedrawEnabled() {
if (self == nil) return self;
self.javaLayer = layer;
self.ctx = nil;
self.displayID = -1;
self.contentsGravity = kCAGravityTopLeft;
//Disable CALayer's default animation
@@ -417,14 +419,20 @@ BOOL MTLLayer_isExtraRedrawEnabled() {
}
- (void)stopRedraw:(BOOL)force {
if (isDisplaySyncEnabled()) {
[self stopRedraw:self.ctx displayID:self.displayID force:force];
}
}
- (void) stopRedraw:(MTLContext*)mtlc displayID:(jint)displayID force:(BOOL)force {
if (isDisplaySyncEnabled()) {
if (force) {
self.redrawCount = 0;
}
if (self.ctx != nil) {
if (mtlc != nil) {
[ThreadUtilities performOnMainThreadNowOrLater:NO // critical
block:^(){
[self.ctx stopRedraw:self];
[mtlc stopRedraw:displayID layer:self];
}];
}
}
@@ -557,9 +565,31 @@ Java_sun_java2d_metal_MTLLayer_validate
BMTLSDOps *bmtlsdo = (BMTLSDOps*) SurfaceData_GetOps(env, surfaceData);
layer.buffer = &bmtlsdo->pTexture;
layer.outBuffer = &bmtlsdo->pOutTexture;
layer.ctx = ((MTLSDOps *)bmtlsdo->privOps)->configInfo->context;
layer.displayID = ((MTLSDOps *)bmtlsdo->privOps)->configInfo->displayID;
layer.device = layer.ctx.device;
// Backup layer's context (device) and displayId to unregister if needed:
MTLContext* oldMtlc = layer.ctx;
NSInteger oldDisplayID = layer.displayID;
MTLContext* newMtlc = ((MTLSDOps *)bmtlsdo->privOps)->configInfo->context;
NSInteger newDisplayID = ((MTLSDOps *)bmtlsdo->privOps)->configInfo->displayID;
if (isDisplaySyncEnabled()) {
if (oldDisplayID != -1) {
if ((oldMtlc != newMtlc) || (oldDisplayID != newDisplayID)) {
if (TRACE_DISPLAY_CHANGED) {
J2dRlsTraceLn5(J2D_TRACE_INFO, "MTLLayer_validate: layer[%p] mtlc/displayID changed: "
"[%p - %d] => [%p - %d]",
layer, oldMtlc, oldDisplayID, newMtlc, newDisplayID);
}
// unregister with display link on old mtlc/display before updating layer's state:
[layer stopRedraw:oldMtlc displayID:oldDisplayID force:YES];
}
}
}
// Update layer's context (device) and displayId:
layer.ctx = newMtlc;
layer.device = layer.ctx.device;
layer.displayID = newDisplayID;
layer.pixelFormat = MTLPixelFormatBGRA8Unorm;
if (!isColorMatchingEnabled() && (layer.colorspace != nil)) {
@@ -570,6 +600,7 @@ Java_sun_java2d_metal_MTLLayer_validate
layer.drawableSize =
CGSizeMake((*layer.buffer).width,
(*layer.buffer).height);
if (isDisplaySyncEnabled()) {
[layer startRedraw];
} else {

View File

@@ -27,6 +27,7 @@
#define __THREADUTILITIES_H
#import <Foundation/Foundation.h>
#import <stdatomic.h>
#include "jni.h"
@@ -175,14 +176,21 @@ __attribute__((visibility("default")))
+ (NSString*)criticalRunLoopMode;
+ (NSString*)javaRunLoopMode;
+ (ThreadTraceContext*) getTraceContext;
+ (void) removeTraceContext;
+ (void) resetTraceContext;
+ (ThreadTraceContext*)getTraceContext;
+ (void)removeTraceContext;
+ (void)resetTraceContext;
+ (ThreadTraceContext*)recordTraceContext;
+ (ThreadTraceContext*)recordTraceContext:(NSString*) prefix;
+ (ThreadTraceContext*)recordTraceContext:(NSString*)prefix;
+ (ThreadTraceContext*)recordTraceContext:(NSString*)prefix actionId:(long)actionId useJavaModes:(BOOL)useJavaModes operation:(char*) operation;
+ (NSString*)getThreadTraceContexts;
+ (void)registerForSystemAndScreenNotifications;
+ (BOOL)isWithinPowerTransition;
+ (BOOL)nanoUpTime:(atomic_uint_least64_t*)nanotime;
+ (BOOL)nowNearTime:(NSString*)src refTime:(atomic_uint_least64_t*)refTime;
@end
JNIEXPORT void OSXAPP_SetJavaVM(JavaVM *vm);

View File

@@ -25,7 +25,6 @@
#import <AppKit/AppKit.h>
#import <objc/message.h>
#import <stdatomic.h>
#import "JNIUtilities.h"
#import "PropertiesUtilities.h"
@@ -62,6 +61,7 @@ JavaVM *jvm = NULL;
static BOOL isNSApplicationOwner = NO;
static JNIEnv *appKitEnv = NULL;
static jobject appkitThreadGroup = NULL;
static NSString* CriticalRunLoopMode = @"AWTCriticalRunLoopMode";
static NSString* JavaRunLoopMode = @"AWTRunLoopMode";
static NSArray<NSString*> *javaModes = nil;
@@ -69,13 +69,28 @@ static NSArray<NSString*> *allModesExceptJava = nil;
/* Traceability data */
static const BOOL enableTracing = NO;
static const BOOL enableTracingLog = NO;
static const BOOL enableCallStacks = NO;
static const BOOL enableRunLoopObserver = NO;
/* Traceability data */
static const BOOL TRACE_PWM = NO;
static const BOOL TRACE_PWM_EVENTS = NO;
static const BOOL TRACE_CLOCKS = NO;
/* 10s period arround reference times (sleep/wake-up...)
* to ensure all displays are awaken properly */
static const uint64_t NANOS_PER_SEC = 1000000000ULL;
static const uint64_t RDV_PERIOD = 10ULL * NANOS_PER_SEC;
/* RunLoop traceability identifier generators */
static atomic_long runLoopId = 0L;
static atomic_long mainThreadActionId = 0L;
static atomic_uint_least64_t sleepTime = 0LL;
static atomic_uint_least64_t wakeUpTime = 0LL;
static inline void attachCurrentThread(void** env) {
if ([NSThread isMainThread]) {
JavaVMAttachArgs args;
@@ -157,66 +172,71 @@ AWT_ASSERT_APPKIT_THREAD;
if (enableTracing) {
// Record thread stack now and return another copy (auto-released):
[ThreadUtilities recordTraceContext];
if (enableRunLoopObserver) {
CFRunLoopObserverRef logObserver = CFRunLoopObserverCreateWithHandler(
NULL, // CFAllocator
kCFRunLoopAllActivities, // CFOptionFlags
true, // repeats
NSIntegerMin, // order = max priority
^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
if ([[NSThread currentThread] isMainThread]) {
char* activityName = NULL;
switch (activity) {
default:
break;
case kCFRunLoopEntry:
activityName = "RunLoopEntry";
/* Increment global main RunLoop id */
runLoopId++;
break;
case kCFRunLoopBeforeTimers:
activityName = "RunLoopBeforeTimers";
break;
case kCFRunLoopBeforeSources :
activityName = "RunLoopBeforeSources";
break;
case kCFRunLoopBeforeWaiting:
activityName = "RunLoopBeforeWaiting";
break;
case kCFRunLoopAfterWaiting:
activityName = "RunLoopAfterWaiting";
break;
case kCFRunLoopExit:
activityName = "RunLoopExit";
break;
case kCFRunLoopAllActivities:
activityName = "RunLoopAllActivities";
break;
@try {
if (enableRunLoopObserver) {
CFRunLoopObserverRef logObserver = CFRunLoopObserverCreateWithHandler(
NULL, // CFAllocator
kCFRunLoopAllActivities, // CFOptionFlags
true, // repeats
NSIntegerMin, // order = max priority
^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
if ([[NSThread currentThread] isMainThread]) {
char *activityName = NULL;
switch (activity) {
default:
break;
case kCFRunLoopEntry:
activityName = "RunLoopEntry";
/* Increment global main RunLoop id */
runLoopId++;
break;
case kCFRunLoopBeforeTimers:
activityName = "RunLoopBeforeTimers";
break;
case kCFRunLoopBeforeSources :
activityName = "RunLoopBeforeSources";
break;
case kCFRunLoopBeforeWaiting:
activityName = "RunLoopBeforeWaiting";
break;
case kCFRunLoopAfterWaiting:
activityName = "RunLoopAfterWaiting";
break;
case kCFRunLoopExit:
activityName = "RunLoopExit";
break;
case kCFRunLoopAllActivities:
activityName = "RunLoopAllActivities";
break;
}
if (activityName != NULL) {
NSLog(@"RunLoop[on %s][%lu]: processing %s on mode = '%@'",
NSThread.currentThread.name.UTF8String, runLoopId, activityName,
NSRunLoop.currentRunLoop.currentMode);
}
}
}
if (activityName != NULL) {
NSLog(@"RunLoop[on %s][%lu]: processing %s on mode = '%@'",
NSThread.currentThread.name.UTF8String, runLoopId, activityName,
NSRunLoop.currentRunLoop.currentMode);
}
}
}
);
);
CFRunLoopRef runLoop = [[NSRunLoop mainRunLoop] getCFRunLoop];
CFRunLoopAddObserver(runLoop, logObserver, kCFRunLoopDefaultMode);
CFRunLoopRef runLoop = [[NSRunLoop mainRunLoop] getCFRunLoop];
CFRunLoopAddObserver(runLoop, logObserver, kCFRunLoopDefaultMode);
CFStringRef criticalModeRef = (__bridge CFStringRef)CriticalRunLoopMode;
CFRunLoopAddObserver(runLoop, logObserver, criticalModeRef);
CFStringRef criticalModeRef = (__bridge CFStringRef) CriticalRunLoopMode;
CFRunLoopAddObserver(runLoop, logObserver, criticalModeRef);
CFStringRef javaModeRef = (__bridge CFStringRef)JavaRunLoopMode;
CFRunLoopAddObserver(runLoop, logObserver, javaModeRef);
CFStringRef javaModeRef = (__bridge CFStringRef) JavaRunLoopMode;
CFRunLoopAddObserver(runLoop, logObserver, javaModeRef);
CFRelease(javaModeRef);
CFRelease(criticalModeRef);
CFRelease(logObserver);
CFRelease(javaModeRef);
CFRelease(criticalModeRef);
CFRelease(logObserver);
}
} @finally {
// Finally reset Main thread context in context store:
[ThreadUtilities resetTraceContext];
}
}
[ThreadUtilities registerForSystemAndScreenNotifications];
}
/*
@@ -226,8 +246,8 @@ AWT_ASSERT_APPKIT_THREAD;
* Note : if waiting cross-thread, possibly the stack allocated copy is accessible ?
*/
+ (void)invokeBlockCopy:(void (^)(void))blockCopy {
blockCopy();
Block_release(blockCopy);
blockCopy();
Block_release(blockCopy);
}
+ (NSString*)getCaller:(NSString*)prefixSymbol {
@@ -403,11 +423,12 @@ AWT_ASSERT_APPKIT_THREAD;
callerCtx = [ThreadUtilities recordTraceContext:nil actionId:actionId useJavaModes:useJavaModes operation:operation];
[callerCtx retain];
lwc_plog(env, "%s performOnMainThread[caller]", toCString([callerCtx description]));
if ([callerCtx callStack] != nil) {
lwc_plog(env, "%s performOnMainThread[caller]: call stack:\n%s",
[callerCtx identifier], toCString([callerCtx callStack]));
if (enableTracingLog) {
lwc_plog(env, "%s performOnMainThread[caller]", toCString([callerCtx description]));
if ([callerCtx callStack] != nil) {
lwc_plog(env, "%s performOnMainThread[caller]: call stack:\n%s",
[callerCtx identifier], toCString([callerCtx callStack]));
}
}
}
@@ -427,14 +448,16 @@ AWT_ASSERT_APPKIT_THREAD;
// Record thread stack now and return another copy (auto-released):
runCtx = [ThreadUtilities recordTraceContext];
const double latencyMs = ([runCtx timestamp] - [callerCtx timestamp]) * 1000.0;
if (enableTracingLog) {
const double latencyMs = ([runCtx timestamp] - [callerCtx timestamp]) * 1000.0;
lwc_plog(runEnv, "%s performOnMainThread[blockCopy]: latency = %.3lf ms. Calling: [%s]",
[callerCtx identifier], latencyMs, aSelector);
lwc_plog(runEnv, "%s performOnMainThread[blockCopy]: latency = %.5lf ms. Calling: [%s]",
[callerCtx identifier], latencyMs, aSelector);
if ([runCtx callStack] != nil) {
lwc_plog(runEnv, "%s performOnMainThread[blockCopy]: run stack:\n%s",
[callerCtx identifier], toCString([runCtx callStack]));
if ([runCtx callStack] != nil) {
lwc_plog(runEnv, "%s performOnMainThread[blockCopy]: run stack:\n%s",
[callerCtx identifier], toCString([runCtx callStack]));
}
}
}
const CFTimeInterval start = (enableTracing) ? CACurrentMediaTime() : 0.0;
@@ -448,10 +471,12 @@ AWT_ASSERT_APPKIT_THREAD;
setBlockingEventDispatchThread(NO);
}
if (enableTracing) {
const double elapsedMs = (CACurrentMediaTime() - start) * 1000.0;
if (elapsedMs > mtThreshold) {
lwc_plog(runEnv, "%s performOnMainThread[blockCopy]: time = %.3lf ms. Caller=[%s]",
[callerCtx identifier], elapsedMs, toCString([callerCtx caller]));
if (enableTracingLog) {
const double elapsedMs = (CACurrentMediaTime() - start) * 1000.0;
if (elapsedMs > mtThreshold) {
lwc_plog(runEnv, "%s performOnMainThread[blockCopy]: time = %.5lf ms. Caller=[%s]",
[callerCtx identifier], elapsedMs, toCString([callerCtx caller]));
}
}
[callerCtx release];
@@ -464,7 +489,7 @@ AWT_ASSERT_APPKIT_THREAD;
if (invokeDirect) {
[ThreadUtilities invokeBlockCopy:blockCopy];
} else {
if (enableTracing) {
if (enableTracingLog) {
lwc_plog(env, "%s performOnMainThread[caller]: waiting on MainThread(%s). Caller=[%s] [%s]",
[callerCtx identifier], aSelector, toCString([callerCtx caller]),
wait ? "WAIT" : "ASYNC");
@@ -472,7 +497,7 @@ AWT_ASSERT_APPKIT_THREAD;
[ThreadUtilities performSelectorOnMainThread:@selector(invokeBlockCopy:) withObject:blockCopy waitUntilDone:wait modes:runLoopModes];
if (enableTracing) {
if (enableTracingLog) {
lwc_plog(env, "%s performOnMainThread[caller]: finished on MainThread(%s). Caller=[%s] [DONE]",
[callerCtx identifier], aSelector, toCString([callerCtx caller]));
}
@@ -489,7 +514,7 @@ AWT_ASSERT_APPKIT_THREAD;
return JavaRunLoopMode;
}
+ (NSMutableDictionary*) threadContextStore {
+ (NSMutableDictionary*)threadContextStore {
static NSMutableDictionary<NSString*, ThreadTraceContext*>* _threadTraceContextPerName;
static dispatch_once_t oncePredicate;
@@ -500,7 +525,7 @@ AWT_ASSERT_APPKIT_THREAD;
return _threadTraceContextPerName;
}
+ (ThreadTraceContext*) getTraceContext {
+ (ThreadTraceContext*)getTraceContext {
const NSString* thName = [[NSThread currentThread] name];
NSMutableDictionary* allContexts = [ThreadUtilities threadContextStore];
@@ -522,19 +547,19 @@ AWT_ASSERT_APPKIT_THREAD;
[[ThreadUtilities threadContextStore] removeObjectForKey:thName];
}
+ (void) resetTraceContext {
+ (void)resetTraceContext {
[[ThreadUtilities getTraceContext] reset];
}
+ (ThreadTraceContext*) recordTraceContext {
+ (ThreadTraceContext*)recordTraceContext {
return [ThreadUtilities recordTraceContext:@"recordTraceContext"];
}
+ (ThreadTraceContext*) recordTraceContext:(NSString*) prefix {
+ (ThreadTraceContext*)recordTraceContext:(NSString*) prefix {
return [ThreadUtilities recordTraceContext:prefix actionId:-1 useJavaModes:NO operation:""];
}
+ (ThreadTraceContext*) recordTraceContext:(NSString*) prefix
+ (ThreadTraceContext*)recordTraceContext:(NSString*) prefix
actionId:(long) actionId
useJavaModes:(BOOL) useJavaModes
operation:(char*) operation
@@ -549,6 +574,218 @@ AWT_ASSERT_APPKIT_THREAD;
return [thCtx set:actionId operation:operation useJavaModes:useJavaModes caller:caller callstack:callStack];
}
+ (NSString*)getThreadTraceContexts
{
NSMutableDictionary* allContexts = [ThreadUtilities threadContextStore];
// CHECK LEAK ?
NSMutableString* dump = [[[NSMutableString alloc] initWithCapacity:4096] autorelease];
[dump appendString:@"[\n"];
for (NSString* thName in allContexts) {
ThreadTraceContext *thCtx = allContexts[thName];
[dump appendString:@"\n["];
[dump appendFormat:@"\n thread:'%@'", thName];
[dump appendString:@"\n traceContext: "];
if (thCtx == nil) {
[dump appendString:@"null"];
} else {
[dump appendString:@"["];
[dump appendFormat:@"\n %@", thCtx.description];
[dump appendString:@"\n ]"];
}
[dump appendString:@"\n] \n"];
}
[dump appendString:@"]"];
[dump retain];
return dump;
}
+ (BOOL)isWithinPowerTransition {
if (wakeUpTime != 0LL) {
// check last wake-up time:
if (nowNearTime("wake-up", &wakeUpTime)) {
return true;
}
// reset invalid time:
wakeUpTime = 0LL;
} else if (sleepTime != 0LL) {
// check last sleep time:
if (nowNearTime("sleep", &sleepTime)) {
return true;
}
// reset invalid time:
sleepTime = 0LL;
} else if (TRACE_PWM) {
NSLog(@"EAWT: isWithinPowerTransition: no times");
}
return false;
}
+ (void)_systemOrScreenWillSleep:(NSNotification*)notification {
atomic_uint_least64_t now;
if (nanoUpTime(&now))
{
// keep most-recent wake-up time (system or display):
sleepTime = now;
if (TRACE_PWM_EVENTS) {
NSLog(@"EAWT: _systemOrScreenWillSleep[%@]: sleep time = %.5lf (%.5lf)",
[notification name], 1e-9 * sleepTime,
[NSProcessInfo processInfo].systemUptime);
}
// reset wake-up time (system or display):
wakeUpTime = 0LL;
if (TRACE_CLOCKS) {
dumpClocks();
}
}
}
+ (void)_systemOrScreenDidWake:(NSNotification*)notification {
atomic_uint_least64_t now;
if (nanoUpTime(&now))
{
// keep most-recent wake-up time (system or display):
wakeUpTime = now;
if (TRACE_PWM_EVENTS) {
NSLog(@"EAWT: _systemOrScreenDidWake[%@]: wake-up time = %.5lf (%.5lf)",
[notification name], 1e-9 * wakeUpTime,
[NSProcessInfo processInfo].systemUptime);
}
// CHECK
if (sleepTime != 0LL) {
if (now > sleepTime) {
// check last sleep time:
now -= sleepTime; // delta in ns
if (TRACE_PWM_EVENTS) {
NSLog(@"EAWT: _systemOrScreenDidWake: SLEEP duration = %.5lf ms", 1e-6 * now);
}
}
}
if (TRACE_CLOCKS) {
dumpClocks();
}
}
}
+ (BOOL)nanoUpTime:(atomic_uint_least64_t*)nanotime {
return nanoUpTime(nanotime);
}
+ (BOOL)nowNearTime:(NSString*)src refTime:(atomic_uint_least64_t*)refTime {
return nowNearTime(src.UTF8String, refTime);
}
bool nowNearTime(const char* src, atomic_uint_least64_t *refTime) {
if (*refTime != 0LL) {
atomic_uint_least64_t now;
if (nanoUpTime(&now)) {
if (now < *refTime) {
// should not happen with monotonic clocks, but:
now = *refTime;
}
// check absolute delta in nanoseconds:
now -= *refTime;
if (TRACE_PWM) {
NSLog(@"EAWT: nowNearTime[%s]: delta time = %.5lf ms", src, 1e-6 * now);
}
return (now <= RDV_PERIOD);
}
}
return false;
}
bool nanoUpTime(atomic_uint_least64_t *nanotime) {
// Use a monotonic clock (linearly increasing by each tick)
// but not counting the time while sleeping.
// NOTE:CLOCK_UPTIME_RAW seems counting more elapsed time
// arround sleep/wake-up cycle than CLOCK_PROCESS_CPUTIME_ID (adopted):
return getTime_nanos(CLOCK_PROCESS_CPUTIME_ID, nanotime);
}
bool getTime_nanos(clockid_t clock_id, atomic_uint_least64_t *nanotime) {
struct timespec tp;
// Use the given clock:
int status = clock_gettime(clock_id, &tp);
if (status != 0) {
return false;
}
*nanotime = tp.tv_sec * NANOS_PER_SEC + tp.tv_nsec;
return true;
}
void dumpClocks() {
if (TRACE_CLOCKS) {
logTime_nanos(CLOCK_REALTIME);
logTime_nanos(CLOCK_MONOTONIC);
logTime_nanos(CLOCK_MONOTONIC_RAW);
logTime_nanos(CLOCK_MONOTONIC_RAW_APPROX);
logTime_nanos(CLOCK_UPTIME_RAW);
logTime_nanos(CLOCK_UPTIME_RAW_APPROX);
logTime_nanos(CLOCK_PROCESS_CPUTIME_ID);
logTime_nanos(CLOCK_THREAD_CPUTIME_ID);
}
}
void logTime_nanos(clockid_t clock_id) {
if (TRACE_CLOCKS) {
atomic_uint_least64_t now;
if (getTime_nanos(clock_id, &now)) {
const char *clock_name;
switch (clock_id) {
case CLOCK_REALTIME:
clock_name = "CLOCK_REALTIME";
break;
case CLOCK_MONOTONIC:
clock_name = "CLOCK_MONOTONIC";
break;
case CLOCK_MONOTONIC_RAW:
clock_name = "CLOCK_MONOTONIC_RAW";
break;
case CLOCK_MONOTONIC_RAW_APPROX:
clock_name = "CLOCK_MONOTONIC_RAW_APPROX";
break;
case CLOCK_UPTIME_RAW:
clock_name = "CLOCK_UPTIME_RAW";
break;
case CLOCK_UPTIME_RAW_APPROX:
clock_name = "CLOCK_UPTIME_RAW_APPROX";
break;
case CLOCK_PROCESS_CPUTIME_ID:
clock_name = "CLOCK_PROCESS_CPUTIME_ID";
break;
case CLOCK_THREAD_CPUTIME_ID:
clock_name = "CLOCK_THREAD_CPUTIME_ID";
break;
default:
clock_name = "unknown";
}
NSLog(@"EAWT: logTime_nanos[%27s] time: %.6lf s", clock_name, 1e-9 * now);
}
}
}
+ (void)registerForSystemAndScreenNotifications {
static BOOL notificationRegistered = false;
if (!notificationRegistered) {
notificationRegistered = true;
NSNotificationCenter *ctr = [[NSWorkspace sharedWorkspace] notificationCenter];
Class clz = [ThreadUtilities class];
[ctr addObserver:clz selector:@selector(_systemOrScreenWillSleep:) name:NSWorkspaceWillSleepNotification object:nil];
[ctr addObserver:clz selector:@selector(_systemOrScreenDidWake:) name:NSWorkspaceDidWakeNotification object:nil];
[ctr addObserver:clz selector:@selector(_systemOrScreenWillSleep:) name:NSWorkspaceScreensDidSleepNotification object:nil];
[ctr addObserver:clz selector:@selector(_systemOrScreenDidWake:) name:NSWorkspaceScreensDidWakeNotification object:nil];
}
}
@end
void OSXAPP_SetJavaVM(JavaVM *vm)
@@ -712,9 +949,10 @@ JNIEXPORT void lwc_plog(JNIEnv* env, const char *formatMsg, ...) {
timestamp, actionId, operation]);
}
- (NSString *)getDescription {
return [NSString stringWithFormat:@"%s useJavaModes=%d sleep=%d caller=[%@]",
[self identifier], useJavaModes, sleep, caller];
- (NSString *)description {
// creates autorelease string:
return [NSString stringWithFormat:@"%s useJavaModes=%d sleep=%d caller=[%@] callStack={\n%@}",
[self identifier], useJavaModes, sleep, caller, callStack];
}
@end