New issue
Advanced search Search tips

Issue 603320 link

Starred by 2 users

Issue metadata

Status: Assigned
Owner:
Cc:
EstimatedDays: ----
NextAction: ----
OS: Mac
Pri: 3
Type: Bug



Sign in to add a comment

Figure out how Quartz Debug works

Project Member Reported by erikc...@chromium.org, Apr 13 2016

Issue description

It seems to know when contents has "actually" been committed to the display. This information would be very helpful for scheduling in the GPU process, and for analytics to determine the rate at which we're rendering.
 
Disassembling -[QDFramemeterServer update:] shows that they use

host_processor_info and CGSGetPerformanceData.

The latter appears to be not quite what we want, but pretty close:
"""
/*! Queries the server about its performance. This is how Quartz Debug gets the FPS meter, but not the CPU meter (for that it uses host_processor_info). Quartz Debug subtracts 25 so that it is at zero with the minimum FPS. */
CG_EXTERN CGError CGSGetPerformanceData(CGSConnectionID cid, float *outFPS, float *unk, float *unk2, float *unk3);
"""
https://github.com/rentzsch/ForceQuitUnresponsiveApps/blob/master/CGSInternal/CGSDebug.h

Not sure yet if that method is locked down, or any process can call it.
Looks like any process can call it:

"""
typedef int CGSConnectionID;
CGSConnectionID _CGSDefaultConnection(void);
CGError CGSGetPerformanceData(CGSConnectionID cid, float *outFPS, float *unk, float *unk2, float *unk3);
...
float a, b, c, d;
CGSGetPerformanceData(_CGSDefaultConnection(), &a, &b, &c, &d);
NSLog(@"%f %f %f %f", a, b, c, d);
"""

"""
2016-04-13 16:40:10.119 frame_rate_test[27937:1371128] 54.722847 163570240.000000 3779.651855 5034.785156
2016-04-13 16:40:10.119 frame_rate_test[27937:1371128] 54.719250 163559808.000000 3780.466553 5035.580566
2016-04-13 16:40:10.119 frame_rate_test[27937:1371128] 54.714527 163546096.000000 3781.223145 5036.311523
2016-04-13 16:40:10.119 frame_rate_test[27937:1371128] 54.710693 163534976.000000 3782.025635 5037.093262
2016-04-13 16:40:10.119 frame_rate_test[27937:1371128] 54.707233 163524944.000000 3782.847168 5037.896484
2016-04-13 16:40:10.119 frame_rate_test[27937:1371128] 54.703812 163515024.000000 3783.670898 5038.701660
2016-04-13 16:40:10.119 frame_rate_test[27937:1371128] 54.700424 163505184.000000 3784.496338 5039.508789
2016-04-13 16:40:10.222 frame_rate_test[27937:1371128] 54.674274 169140448.000000 3566.996582 4868.529297
"""
It looks like the ether ate one of my responses :(.

Deep in the method _CGXUpdateDisplay, the following 5 functions are called back to back:
"""
    rbx = _CGXUpdateDisplaySynchronizeSeed();
    _WSPerformanceBlitUpdate(var_53C, *(int8_t *)(r12 + 0x124) & 0xff);
    _WSUpdateSignpostSignal(0xd);
    _WSUpdateSignpostDispatchAndReset();
    _WSPostLocalNotification(0x12c, var_228, 0x8);
"""

This gives us a nice idea of things to look for.

--------------
_WSPerformanceBlitUpdate() increments gTotalUpdateCounter, which is query-able with 
CGError CGSGetPerformanceTotalUpdateCount(CGSConnectionID cid, int* b, int *c, int*d, int* e);
This seems to monotically update every 5ms.
--------------
CGError CGSGetWindowFlushSeed(CGSConnectionID cid, int window_id, int*c);

might be what we want. It returns a monotonically updating number. Each time the window is redrawn (from a view changing), it is incremented. Any time the window is obscured, and then brought back into view (but not redrawn), it is incremented.
note that window_id = [self.window performSelector:@selector(_realWindowNumber)];.

-----------------
Looking at the CAOpenGLLayer approach:

The method CAOpenGLLayer_timer_callback is added as a run loop timer. It's pretty complicated, but one of the methods it calls is _CAImageQueueGetUnconsumedImageCount. I bet we could use that to tell us if our image has been "consumed" by the window server.
Cc: sunn...@chromium.org vmi...@chromium.org
Adding sunnyps@ and vmiura@, since they had some interest in this topic.
I started trying out these methods in Chromium. 

CGSGetWindowFlushSeed always returns 0 if the content view is layer backed, so it's useless for us.

I don't know how to interpret the values from CGSGetPerformanceTotalUpdateCount(), so I'm not sure what to do with it.

CGSGetPerformanceData() works as expected, but doesn't provide very granular information. 
Alright, I think that's all the low hanging fruit. Next things to investigate would be the delightful sounding CGNotificationCenter and registering callbacks with it. Maybe do more digging in CAImageQueue, and in general, understanding the window server better.

We should probably get some tests using CGSGetPerformanceData. 
It would be nice to write a test that uses CGSGetPerformanceData to measure real WebGL performance: https://codereview.chromium.org/2089293002/
I ran across the following SPI:

"""
@interface CAContext () 
- (void)setFencePort:(mach_port_t)port;                                                                                                                                                                                                          
- (void)setFencePort:(mach_port_t)port commitHandler:(void(^)(void))block; 
"""

And this nice block of code:
"""
    [CATransaction begin];                                                         
    [CATransaction setDisableActions:YES];                                         
                                                                                   
    [m_hostingLayer setFrame:CGRectMake(layerPosition.width(), layerPosition.height(), viewSize.width(), viewSize.height())];
                                                                                   
    [CATransaction commit];                                                        
                                                                                   
    if (flushSynchronously) {                                                      
        [CATransaction flush];                                                     
#if !HAVE(COREANIMATION_FENCES)                                                    
        // We can't synchronize here if we're using fences or we'll blow the fence every time (and we don't need to).
        [CATransaction synchronize];                                               
#endif                                                                             
    }                                                                              
                                                                                   
    m_webPage.send(Messages::DrawingAreaProxy::DidUpdateGeometry());               
                                                                                   
    m_inUpdateGeometry = false;                                                    
                                                                                   
#if HAVE(COREANIMATION_FENCES)                                                     
    m_layerHostingContext->setFencePort(fencePort.sendRight());                                                                                                                                                                                      
#endif  
"""

Will investigate further.
Also this:
"""
typedef enum {                                                                     
    kCATransactionPhasePreLayout,                                                                                                                                                                                                                    
    kCATransactionPhasePreCommit,                                               
    kCATransactionPhasePostCommit,                                              
} CATransactionPhase;                                                           
                                                                                   
@interface CATransaction ()                                                     
+ (void)addCommitHandler:(void(^)(void))block forPhase:(CATransactionPhase)phase;
@end    
"""
Oooh, this looks very promising!

Sign in to add a comment