1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-02-18 16:30:29 +00:00

Rewired the existing cause-an-update route from the OpenGLView through the best-effort updater.

This commit is contained in:
Thomas Harte 2016-06-16 18:19:23 -04:00
parent 530cf3dcbb
commit 1921a6c469
7 changed files with 44 additions and 67 deletions

View File

@ -11,3 +11,4 @@
#import "CSOpenGLView.h" #import "CSOpenGLView.h"
#import "CSAudioQueue.h" #import "CSAudioQueue.h"
#import "CSBestEffortUpdater.h"

View File

@ -18,7 +18,7 @@ class Atari2600Document: MachineDocument {
// MARK: NSDocument overrides // MARK: NSDocument overrides
override init() { override init() {
super.init() super.init()
self.intendedCyclesPerSecond = 1194720 self.bestEffortUpdater.clockRate = 1194720
} }
override class func autosavesInPlace() -> Bool { override class func autosavesInPlace() -> Bool {

View File

@ -27,7 +27,7 @@ class ElectronDocument: MachineDocument {
override func windowControllerDidLoadNib(aController: NSWindowController) { override func windowControllerDidLoadNib(aController: NSWindowController) {
super.windowControllerDidLoadNib(aController) super.windowControllerDidLoadNib(aController)
self.intendedCyclesPerSecond = 2000000 self.bestEffortUpdater.clockRate = 2000000
if let os = rom("os"), basic = rom("basic") { if let os = rom("os"), basic = rom("basic") {
self.electron.setOSROM(os) self.electron.setOSROM(os)

View File

@ -9,7 +9,7 @@
import Cocoa import Cocoa
import AudioToolbox import AudioToolbox
class MachineDocument: NSDocument, CSOpenGLViewDelegate, CSOpenGLViewResponderDelegate, NSWindowDelegate { class MachineDocument: NSDocument, CSOpenGLViewDelegate, CSOpenGLViewResponderDelegate, CSBestEffortUpdaterDelegate, NSWindowDelegate {
lazy var actionLock = NSLock() lazy var actionLock = NSLock()
lazy var drawLock = NSLock() lazy var drawLock = NSLock()
@ -34,6 +34,11 @@ class MachineDocument: NSDocument, CSOpenGLViewDelegate, CSOpenGLViewResponderDe
} }
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) { override func windowControllerDidLoadNib(aController: NSWindowController) {
super.windowControllerDidLoadNib(aController) super.windowControllerDidLoadNib(aController)
@ -66,36 +71,39 @@ class MachineDocument: NSDocument, CSOpenGLViewDelegate, CSOpenGLViewResponderDe
super.close() super.close()
} }
var intendedCyclesPerSecond: Int64 = 0 final func bestEffortUpdater(bestEffortUpdater: CSBestEffortUpdater!, runForCycles cycles: UInt, didSkipPreviousUpdate: Bool) {
private var cycleCountError: Int64 = 0 runForNumberOfCycles(Int32(cycles))
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
} }
// 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 // MARK: Utilities for children
func dataForResource(name : String, ofType type: String, inDirectory directory: String) -> NSData? { 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) { func openGLView(view: CSOpenGLView, drawViewOnlyIfDirty onlyIfDirty: Bool) {
bestEffortUpdater.update()
if drawLock.tryLock() { if drawLock.tryLock() {
self.machine().drawViewForPixelSize(view.backingSize, onlyIfDirty: onlyIfDirty) self.machine().drawViewForPixelSize(view.backingSize, onlyIfDirty: onlyIfDirty)
drawLock.unlock() drawLock.unlock()

View File

@ -18,7 +18,7 @@ class Vic20Document: MachineDocument {
// MARK: NSDocument overrides // MARK: NSDocument overrides
override init() { override init() {
super.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 // 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") { if let kernel = rom("kernel-ntsc"), basic = rom("basic"), characters = rom("characters-english") {

View File

@ -12,14 +12,6 @@
@class CSOpenGLView; @class CSOpenGLView;
@protocol CSOpenGLViewDelegate @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 Requests that the delegate produce an image of its current output state. May be called on
any queue or thread. any queue or thread.

View File

@ -12,9 +12,6 @@
@implementation CSOpenGLView { @implementation CSOpenGLView {
CVDisplayLinkRef _displayLink; CVDisplayLinkRef _displayLink;
uint32_t _updateIsOngoing;
BOOL _hasSkipped;
dispatch_queue_t _serialDispatchQueue;
} }
- (void)prepareOpenGL - (void)prepareOpenGL
@ -34,10 +31,6 @@
CGLPixelFormatObj cglPixelFormat = [[self pixelFormat] CGLPixelFormatObj]; CGLPixelFormatObj cglPixelFormat = [[self pixelFormat] CGLPixelFormatObj];
CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext(_displayLink, cglContext, cglPixelFormat); 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 // set the clear colour
[self.openGLContext makeCurrentContext]; [self.openGLContext makeCurrentContext];
glClearColor(0.0, 0.0, 0.0, 1.0); 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 - (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. // Draw the display now regardless of other activity.
[self drawViewOnlyIfDirty:YES]; [self drawViewOnlyIfDirty:YES];
} }