From 1921a6c46994eae42d71ac492792c003cd19a73f Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 16 Jun 2016 18:19:23 -0400 Subject: [PATCH] Rewired the existing cause-an-update route from the OpenGLView through the best-effort updater. --- .../ClockSignal-Bridging-Header.h | 1 + .../Documents/Atari2600Document.swift | 2 +- .../Documents/ElectronDocument.swift | 2 +- .../Documents/MachineDocument.swift | 71 +++++++++++-------- .../Documents/Vic20Document.swift | 2 +- .../Mac/Clock Signal/Views/CSOpenGLView.h | 8 --- .../Mac/Clock Signal/Views/CSOpenGLView.m | 25 ------- 7 files changed, 44 insertions(+), 67 deletions(-) diff --git a/OSBindings/Mac/Clock Signal/ClockSignal-Bridging-Header.h b/OSBindings/Mac/Clock Signal/ClockSignal-Bridging-Header.h index 332ae9769..8ab3cb4a6 100644 --- a/OSBindings/Mac/Clock Signal/ClockSignal-Bridging-Header.h +++ b/OSBindings/Mac/Clock Signal/ClockSignal-Bridging-Header.h @@ -11,3 +11,4 @@ #import "CSOpenGLView.h" #import "CSAudioQueue.h" +#import "CSBestEffortUpdater.h" diff --git a/OSBindings/Mac/Clock Signal/Documents/Atari2600Document.swift b/OSBindings/Mac/Clock Signal/Documents/Atari2600Document.swift index fd546a79b..0c0ba7b38 100644 --- a/OSBindings/Mac/Clock Signal/Documents/Atari2600Document.swift +++ b/OSBindings/Mac/Clock Signal/Documents/Atari2600Document.swift @@ -18,7 +18,7 @@ class Atari2600Document: MachineDocument { // MARK: NSDocument overrides override init() { super.init() - self.intendedCyclesPerSecond = 1194720 + self.bestEffortUpdater.clockRate = 1194720 } override class func autosavesInPlace() -> Bool { diff --git a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift index c9cfef538..a97e8ca3f 100644 --- a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift @@ -27,7 +27,7 @@ class ElectronDocument: MachineDocument { override func windowControllerDidLoadNib(aController: NSWindowController) { super.windowControllerDidLoadNib(aController) - self.intendedCyclesPerSecond = 2000000 + self.bestEffortUpdater.clockRate = 2000000 if let os = rom("os"), basic = rom("basic") { self.electron.setOSROM(os) diff --git a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift index d8aa45180..55c03d93a 100644 --- a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift @@ -9,7 +9,7 @@ import Cocoa import AudioToolbox -class MachineDocument: NSDocument, CSOpenGLViewDelegate, CSOpenGLViewResponderDelegate, NSWindowDelegate { +class MachineDocument: NSDocument, CSOpenGLViewDelegate, CSOpenGLViewResponderDelegate, CSBestEffortUpdaterDelegate, NSWindowDelegate { lazy var actionLock = NSLock() lazy var drawLock = NSLock() @@ -33,7 +33,12 @@ class MachineDocument: NSDocument, CSOpenGLViewDelegate, CSOpenGLViewResponderDe optionsPanel?.setIsVisible(true) } - var audioQueue : CSAudioQueue! = nil + var audioQueue: CSAudioQueue! = nil + lazy var bestEffortUpdater: CSBestEffortUpdater = { + let updater = CSBestEffortUpdater() + updater.delegate = self + return updater + }() override func windowControllerDidLoadNib(aController: NSWindowController) { super.windowControllerDidLoadNib(aController) @@ -66,36 +71,39 @@ class MachineDocument: NSDocument, CSOpenGLViewDelegate, CSOpenGLViewResponderDe super.close() } - var intendedCyclesPerSecond: Int64 = 0 - private var cycleCountError: Int64 = 0 - private var lastTime: 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 - var numberOfCycles = videoTimeCount / videoTimeScale64 - - // 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 - if didSkipPreviousUpdate { - skippedFrames++ - } else { - skippedFrames = 0 - } - - // run for at most three frames up to and until that causes overshoots in the - // permitted processing window for at least four consecutive frames, in which - // case limit to one - numberOfCycles = min(numberOfCycles, Int64(Double(intendedCyclesPerSecond) * frequency * ((skippedFrames > 4) ? 3.0 : 1.0))) - runForNumberOfCycles(Int32(numberOfCycles)) - } - lastTime = time + final func bestEffortUpdater(bestEffortUpdater: CSBestEffortUpdater!, runForCycles cycles: UInt, didSkipPreviousUpdate: Bool) { + runForNumberOfCycles(Int32(cycles)) } +// var intendedCyclesPerSecond: Int64 = 0 +// private var cycleCountError: Int64 = 0 +// private var lastTime: 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 +// var numberOfCycles = videoTimeCount / videoTimeScale64 +// +// // 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 +// if didSkipPreviousUpdate { +// skippedFrames++ +// } else { +// skippedFrames = 0 +// } +// +// // run for at most three frames up to and until that causes overshoots in the +// // permitted processing window for at least four consecutive frames, in which +// // case limit to one +// numberOfCycles = min(numberOfCycles, Int64(Double(intendedCyclesPerSecond) * frequency * ((skippedFrames > 4) ? 3.0 : 1.0))) +// runForNumberOfCycles(Int32(numberOfCycles)) +// } +// lastTime = time +// } // MARK: Utilities for children func dataForResource(name : String, ofType type: String, inDirectory directory: String) -> NSData? { @@ -115,6 +123,7 @@ class MachineDocument: NSDocument, CSOpenGLViewDelegate, CSOpenGLViewResponderDe } func openGLView(view: CSOpenGLView, drawViewOnlyIfDirty onlyIfDirty: Bool) { + bestEffortUpdater.update() if drawLock.tryLock() { self.machine().drawViewForPixelSize(view.backingSize, onlyIfDirty: onlyIfDirty) drawLock.unlock() diff --git a/OSBindings/Mac/Clock Signal/Documents/Vic20Document.swift b/OSBindings/Mac/Clock Signal/Documents/Vic20Document.swift index 875e90a08..d30276f72 100644 --- a/OSBindings/Mac/Clock Signal/Documents/Vic20Document.swift +++ b/OSBindings/Mac/Clock Signal/Documents/Vic20Document.swift @@ -18,7 +18,7 @@ class Vic20Document: MachineDocument { // MARK: NSDocument overrides override init() { super.init() - self.intendedCyclesPerSecond = 1022727 + self.bestEffortUpdater.clockRate = 1022727 // TODO: or 1108405 for PAL; see http://www.antimon.org/dl/c64/code/stable.txt if let kernel = rom("kernel-ntsc"), basic = rom("basic"), characters = rom("characters-english") { diff --git a/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.h b/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.h index f5c821fe8..4cf5ec5d4 100644 --- a/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.h +++ b/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.h @@ -12,14 +12,6 @@ @class CSOpenGLView; @protocol CSOpenGLViewDelegate -/*! - 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 didSkipPreviousUpdate:(BOOL)didSkipPreviousUpdate frequency:(double)frequency; - /*! Requests that the delegate produce an image of its current output state. May be called on any queue or thread. diff --git a/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m b/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m index ce4a19046..08a2ca5b1 100644 --- a/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m +++ b/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m @@ -12,9 +12,6 @@ @implementation CSOpenGLView { CVDisplayLinkRef _displayLink; - uint32_t _updateIsOngoing; - BOOL _hasSkipped; - dispatch_queue_t _serialDispatchQueue; } - (void)prepareOpenGL @@ -34,10 +31,6 @@ CGLPixelFormatObj cglPixelFormat = [[self pixelFormat] CGLPixelFormatObj]; CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext(_displayLink, cglContext, cglPixelFormat); - // create a serial dispatch queue - _serialDispatchQueue = dispatch_queue_create("OpenGLView", DISPATCH_QUEUE_SERIAL); -// dispatch_set_target_queue(_serialDispatchQueue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)); - // set the clear colour [self.openGLContext makeCurrentContext]; glClearColor(0.0, 0.0, 0.0, 1.0); @@ -55,24 +48,6 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt - (void)drawAtTime:(const CVTimeStamp *)now frequency:(double)frequency { - const uint32_t processingMask = 0x01; - - // Always post an -openGLView:didUpdateToTime: if a previous one isn't still ongoing. This is the hook upon which the substantial processing occurs. - if(!OSAtomicTestAndSet(processingMask, &_updateIsOngoing)) - { - CVTimeStamp time = *now; - BOOL didSkip = _hasSkipped; - dispatch_async(_serialDispatchQueue, ^{ - [self.delegate openGLView:self didUpdateToTime:time didSkipPreviousUpdate:didSkip frequency:frequency]; - OSAtomicTestAndClear(processingMask, &_updateIsOngoing); - }); - _hasSkipped = NO; - } - else - { - _hasSkipped = YES; - } - // Draw the display now regardless of other activity. [self drawViewOnlyIfDirty:YES]; }