mirror of
https://github.com/TomHarte/CLK.git
synced 2024-12-27 01:31:42 +00:00
Made further attempts to improve synchronisation and interrelated timing between display and machine update.
This commit is contained in:
parent
d9a9dffe63
commit
9c89b668ae
@ -35,19 +35,29 @@ class MachineDocument: NSDocument, CSOpenGLViewDelegate, CSOpenGLViewResponderDe
|
||||
var intendedCyclesPerSecond: Int64 = 0
|
||||
private var cycleCountError: Int64 = 0
|
||||
private var lastTime: CVTimeStamp?
|
||||
final func openGLView(view: CSOpenGLView, didUpdateToTime time: CVTimeStamp) {
|
||||
private var skippedFrames = 0
|
||||
final func openGLView(view: CSOpenGLView, didUpdateToTime time: CVTimeStamp, didSkipPreviousUpdate : Bool, frequency : Double) {
|
||||
if let lastTime = lastTime {
|
||||
// perform (time passed in seconds) * (intended cycles per second), converting and
|
||||
// maintaining an error count to deal with underflow
|
||||
let videoTimeScale64 = Int64(time.videoTimeScale)
|
||||
let videoTimeCount = ((time.videoTime - lastTime.videoTime) * intendedCyclesPerSecond) + cycleCountError
|
||||
cycleCountError = videoTimeCount % videoTimeScale64
|
||||
let numberOfCycles = videoTimeCount / videoTimeScale64
|
||||
var numberOfCycles = videoTimeCount / videoTimeScale64
|
||||
|
||||
// if the emulation has fallen too far behind then silently limit the request;
|
||||
// if the emulation has fallen behind then silently limit the request;
|
||||
// some actions — e.g. the host computer waking after sleep — may give us a
|
||||
// prohibitive backlog
|
||||
runForNumberOfCycles(min(Int32(numberOfCycles), Int32(intendedCyclesPerSecond / 25)))
|
||||
if didSkipPreviousUpdate {
|
||||
skippedFrames++
|
||||
} else {
|
||||
skippedFrames = 0
|
||||
}
|
||||
|
||||
if skippedFrames > 4 {
|
||||
numberOfCycles = min(numberOfCycles, Int64(Double(intendedCyclesPerSecond) * frequency))
|
||||
}
|
||||
runForNumberOfCycles(Int32(numberOfCycles))
|
||||
}
|
||||
lastTime = time
|
||||
}
|
||||
|
@ -13,12 +13,12 @@
|
||||
|
||||
@protocol CSOpenGLViewDelegate
|
||||
/*!
|
||||
Tells the delegate that time has advanced. This method will always be called on the same queue
|
||||
as the @c CSOpenGLViewResponderDelegate methods.
|
||||
Tells the delegate that time has advanced.
|
||||
@param view The view sending the message.
|
||||
@param time The time to which time has advanced.
|
||||
@param didSkipPreviousUpdate @c YES if the previous update that would have occurred was skipped because a didUpdateToTime: call prior to that was still ongoing; @c NO otherwise.
|
||||
*/
|
||||
- (void)openGLView:(nonnull CSOpenGLView *)view didUpdateToTime:(CVTimeStamp)time;
|
||||
- (void)openGLView:(nonnull CSOpenGLView *)view didUpdateToTime:(CVTimeStamp)time didSkipPreviousUpdate:(BOOL)didSkipPreviousUpdate frequency:(double)frequency;
|
||||
|
||||
/*!
|
||||
Requests that the delegate produce an image of its current output state. May be called on
|
||||
|
@ -13,6 +13,7 @@
|
||||
@implementation CSOpenGLView {
|
||||
CVDisplayLinkRef _displayLink;
|
||||
uint32_t _updateIsOngoing;
|
||||
BOOL _hasSkipped;
|
||||
}
|
||||
|
||||
- (void)prepareOpenGL
|
||||
@ -23,7 +24,7 @@
|
||||
|
||||
// Create a display link capable of being used with all active displays
|
||||
CVDisplayLinkCreateWithActiveCGDisplays(&_displayLink);
|
||||
|
||||
|
||||
// Set the renderer output callback function
|
||||
CVDisplayLinkSetOutputCallback(_displayLink, DisplayLinkCallback, (__bridge void * __nullable)(self));
|
||||
|
||||
@ -42,25 +43,36 @@
|
||||
|
||||
static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp *now, const CVTimeStamp *outputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut, void *displayLinkContext)
|
||||
{
|
||||
CSOpenGLView *view = (__bridge CSOpenGLView *)displayLinkContext;
|
||||
[view drawAtTime:now];
|
||||
CSOpenGLView *const view = (__bridge CSOpenGLView *)displayLinkContext;
|
||||
[view drawAtTime:now frequency:CVDisplayLinkGetActualOutputVideoRefreshPeriod(displayLink)];
|
||||
return kCVReturnSuccess;
|
||||
}
|
||||
|
||||
- (void)drawAtTime:(const CVTimeStamp *)now
|
||||
- (void)drawAtTime:(const CVTimeStamp *)now frequency:(double)frequency
|
||||
{
|
||||
const uint32_t processingMask = 0x01;
|
||||
const uint32_t drawingMask = 0x02;
|
||||
|
||||
// Always post a -openGLView:didUpdateToTime:. This is the hook upon which the substantial processing occurs.
|
||||
[self.delegate openGLView:self didUpdateToTime:*now];
|
||||
if(!OSAtomicTestAndSet(processingMask, &_updateIsOngoing))
|
||||
{
|
||||
CVTimeStamp time = *now;
|
||||
BOOL didSkip = _hasSkipped;
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
|
||||
[self.delegate openGLView:self didUpdateToTime:time didSkipPreviousUpdate:didSkip frequency:frequency];
|
||||
OSAtomicTestAndClear(processingMask, &_updateIsOngoing);
|
||||
});
|
||||
_hasSkipped = NO;
|
||||
} else _hasSkipped = YES;
|
||||
|
||||
// Draw the display only if a previous draw is not still ongoing. -drawViewOnlyIfDirty: is guaranteed
|
||||
// to be safe to call concurrently with -openGLView:updateToTime: so there's no need to worry about
|
||||
// the above interrupting the below or vice versa.
|
||||
const uint32_t activityMask = 0x01;
|
||||
if(!OSAtomicTestAndSet(activityMask, &_updateIsOngoing))
|
||||
if(!OSAtomicTestAndSet(drawingMask, &_updateIsOngoing))
|
||||
{
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
|
||||
[self drawViewOnlyIfDirty:YES];
|
||||
OSAtomicTestAndClear(activityMask, &_updateIsOngoing);
|
||||
OSAtomicTestAndClear(drawingMask, &_updateIsOngoing);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user