Game loop is based on display VSYNC - typically 60 Hz

This commit is contained in:
tudnai 2022-06-21 19:05:08 -07:00
parent 20596f234f
commit 7fa288d84d
5 changed files with 97 additions and 26 deletions

View File

@ -79,6 +79,8 @@ class ViewController: NSViewController {
static var current : ViewController? = nil
var displayLink: CVDisplayLink?
@IBOutlet weak var textDisplayScroller: NSScrollView!
@IBOutlet var textDisplay: NSTextView!
@IBOutlet weak var speedometer: NSTextFieldCell!
@ -1033,15 +1035,15 @@ class ViewController: NSViewController {
var upd = RepeatingTimer(timeInterval: 1)
func newUpdateTimer( timeInterval : Double ) {
upd.kill()
upd = RepeatingTimer(timeInterval: timeInterval)
upd.eventHandler = {
self.Update()
}
upd.resume()
}
// func newUpdateTimer( timeInterval : Double ) {
// upd.kill()
// upd = RepeatingTimer(timeInterval: timeInterval)
// upd.eventHandler = {
// self.Update()
// }
// upd.resume()
// }
// Kelvin Sherlock's fix to avoid uninstalled font problems
@ -1096,6 +1098,55 @@ class ViewController: NSViewController {
// -1.0, -1.0, 0.0,
// 1.0, -1.0, 0.0
// ]
// The callback function is called everytime CVDisplayLink says its time to get a new frame.
let displayLinkOutputCallback : CVDisplayLinkOutputCallback = { (displayLink: CVDisplayLink, _ inNow: UnsafePointer<CVTimeStamp>, _ inOutputTime: UnsafePointer<CVTimeStamp>, _ flagsIn: CVOptionFlags, _ flagsOut: UnsafeMutablePointer<CVOptionFlags>, _ vController: UnsafeMutableRawPointer?) -> CVReturn in
/* The displayLinkContext is CVDisplayLink's parameter definition of the view in which we are working.
In order to access the methods of a given view we need to specify what kind of view it is as right
now the UnsafeMutablePointer<Void> just means we have a pointer to "something". To cast the pointer
such that the compiler at runtime can access the methods associated with our SwiftOpenGLView, we use
an unsafeBitCast. The definition of which states, "Returns the the bits of x, interpreted as having
type U." We may then call any of that view's methods. Here we call drawView() which we draw a
frame for rendering. */
// unsafeBitCast(displayLinkContext, SwiftOpenGLView.self).renderFrame()
/// This can be a precise FPS updater so MHz would be dead perfect pitch -- however, it makes things worse
// let now : CVTimeStamp = inNow.pointee
//// print( "RateScaler:", now.rateScalar )
// if (mySelf.last_frame_time != 0) {
// let videoTimeDiff = now.videoTime - mySelf.last_frame_time
// let currentFPS = Double(now.videoTimeScale) / Double(videoTimeDiff)
//
// fps = currentFPS
// // mySelf.setCPUClockSpeed(freq: MHz_6502)
// clk_6502_per_frm = UInt64( MHz_6502 * M / currentFPS )
// clk_6502_per_frm_set = clk_6502_per_frm
//
// }
// mySelf.last_frame_time = now.videoTime;
let mySelf = Unmanaged<ViewController>.fromOpaque(vController!).takeUnretainedValue()
mySelf.Update();
// We are going to assume that everything went well for this mock up, and pass success as the CVReturn
return kCVReturnSuccess
}
// sets a callback at every screen refresh (normally 60Hz)
func setupDisplayLink() {
// Grab the a link to the active displays, set the callback defined above, and start the link.
/* An alternative to a nested function is a global function or a closure passed as the argument--a local function
(i.e. a function defined within the class) is NOT allowed. */
// The UnsafeMutablePointer<Void>(unsafeAddressOf(self)) passes a pointer to the instance of our class.
CVDisplayLinkCreateWithActiveCGDisplays(&displayLink)
let unsafeSelf: UnsafeMutableRawPointer = Unmanaged.passUnretained(self).toOpaque()
CVDisplayLinkSetOutputCallback(displayLink!, displayLinkOutputCallback, unsafeSelf)
CVDisplayLinkStart(displayLink!)
}
override func viewDidLoad() {
super.viewDidLoad()
@ -1189,7 +1240,24 @@ class ViewController: NSViewController {
// }
// upd.resume()
newUpdateTimer( timeInterval: 1 / Double(fps) )
// newUpdateTimer( timeInterval: 1 / Double(fps) )
// let displayLink = CADisplayLink(target: self, selector: #selector(Update))
// displayLink.add(to: .current, forMode: .common)
// CVDisplayLinkOutputCallback
// Grab the a link to the active displays, set the callback defined above, and start the link.
/* An alternative to a nested function is a global function or a closure passed as the argument--a local function
(i.e. a function defined within the class) is NOT allowed. */
// The UnsafeMutablePointer<Void>(unsafeAddressOf(self)) passes a pointer to the instance of our class.
CVDisplayLinkCreateWithActiveCGDisplays(&displayLink)
let unsafeSelf: UnsafeMutableRawPointer = Unmanaged.passUnretained(self).toOpaque()
CVDisplayLinkSetOutputCallback(displayLink!, displayLinkOutputCallback, unsafeSelf)
CVDisplayLinkStart(displayLink!)
// #endif
@ -1357,7 +1425,7 @@ class ViewController: NSViewController {
// pixelTrail = pow(256, 1 / Double(fps / video_fps_divider / 3) )
// spkr_buf_size = spkr_sample_rate * 2 / spkr_fps
newUpdateTimer( timeInterval: 1 / Double(fps) )
// newUpdateTimer( timeInterval: 1 / Double(fps) )
setCPUClockSpeed(freq: MHz_6502)
// TODO: Better way to deal with speaker!!!

View File

@ -370,7 +370,7 @@ void m6502_Run() {
}
// TODO: What if we dynamically reduce or increace CPU speed?
m6502.clktime += clk_6502_per_frm;
m6502.clktime += m6502.clkfrm;
if( diskAccelerator_count ) {
if( --diskAccelerator_count <= 0 ) {

View File

@ -186,12 +186,12 @@ extern double * pdl_diffarr;
extern double mips;
extern double mhz;
#define DEFAULT_FPS 30U
#define DEFAULT_FPS 60U
#define DEF_VIDEO_DIV 1U
#define DEF_SPKR_DIV 1U
#define GAME_FPS 180U // 480U
#define GAME_VIDEO_DIV (GAME_FPS / 60U)
#define GAME_VIDEO_DIV (GAME_FPS / DEFAULT_FPS)
extern unsigned int video_fps_divider;
extern unsigned int fps;

View File

@ -84,11 +84,11 @@ int spkr_level_tema = SPKR_LEVEL_ZERO;
int spkr_last_level = SPKR_LEVEL_ZERO;
static const int ema_len_sharper = 7;
static const int ema_len_sharp = 14;
//static const int ema_len_sharper = 7;
//static const int ema_len_sharp = 14;
static const int ema_len_normal = 18;
static const int ema_len_soft = 20;
static const int ema_len_supersoft = 40;
//static const int ema_len_soft = 20;
//static const int ema_len_supersoft = 40;
int spkr_ema_len = ema_len_normal;
@ -113,6 +113,7 @@ unsigned spkr_clk = 0;
#define SPKR_BUF_SLOT(n) ( spkr_buf_size * (n) )
#define SPKR_SLOT_SIZE(n) ( spkr_buf_size * sizeof(spkr_sample_t) )
#define SPKR_SILENT_SLOT 1
const unsigned spkr_seconds = 1;
const unsigned spkr_sample_rate = 192000;
@ -122,9 +123,9 @@ int spkr_extra_buf = 0; // 26; // 800 / spkr_fps;
typedef int16_t spkr_sample_t;
const unsigned spkr_buf_size = spkr_seconds * spkr_sample_rate * SPKR_CHANNELS / DEFAULT_FPS; // stereo
const unsigned spkr_buf_alloc_size = spkr_buf_size * sizeof(spkr_sample_t);
const unsigned sample_buf_array_len = SPKR_BUF_SLOT(BUFFER_COUNT + 1);
const unsigned sample_buf_array_len = SPKR_BUF_SLOT(BUFFER_COUNT + SPKR_SILENT_SLOT);
spkr_sample_t spkr_sample_buf [ sample_buf_array_len ]; // can store up to 1 sec of sound
spkr_sample_t * spkr_samples = spkr_sample_buf + SPKR_BUF_SLOT(1); // keep 1 "empty" frame ahead
spkr_sample_t * spkr_samples = spkr_sample_buf + SPKR_BUF_SLOT(SPKR_SILENT_SLOT); // keep 1 "empty" frame ahead
unsigned spkr_sample_idx = 0;
unsigned spkr_sample_last_idx = 0;
unsigned spkr_sample_first_pwm_idx = 0;
@ -262,7 +263,7 @@ void spkr_init() {
alcMakeContextCurrent(ctx);
// Fill buffer with zeros
memset( spkr_sample_buf, 0, SPKR_SLOT_SIZE(BUFFER_COUNT + 1) ); // spkr_buf_alloc_size + spkr_extra_buf * sizeof(spkr_sample_t));
memset( spkr_sample_buf, 0, SPKR_SLOT_SIZE(BUFFER_COUNT + SPKR_SILENT_SLOT) ); // spkr_buf_alloc_size + spkr_extra_buf * sizeof(spkr_sample_t));
// memset(spkr_samples + spkr_sample_idx * sizeof(spkr_sample_t), 0, (sample_buf_array_len - spkr_sample_idx) * sizeof(spkr_sample_t));
@ -725,6 +726,7 @@ void spkr_buffer_with_pause(int buf_len) {
switch (state) {
case AL_PAUSED:
case AL_PLAYING:
// it is already playing so we can just simply feed the next sound block
alBufferData(spkr_buffers[freeBuffers], AL_FORMAT_STEREO16, spkr_samples, buf_len, spkr_sample_rate);
al_check_error();
alSourceQueueBuffers(spkr_src[SPKR_SRC_GAME_SFX], 1, &spkr_buffers[freeBuffers]);
@ -734,7 +736,8 @@ void spkr_buffer_with_pause(int buf_len) {
case AL_INITIAL:
case AL_STOPPED:
default:
alBufferData(spkr_buffers[freeBuffers], AL_FORMAT_STEREO16, spkr_sample_buf, buf_len * 2, spkr_sample_rate);
// start with a silent sound block so later on we will not roun out of buffer that easy
alBufferData(spkr_buffers[freeBuffers], AL_FORMAT_STEREO16, spkr_sample_buf, buf_len * (1 + SPKR_SILENT_SLOT), spkr_sample_rate);
al_check_error();
alSourceQueueBuffers(spkr_src[SPKR_SRC_GAME_SFX], 1, &spkr_buffers[freeBuffers]);
al_check_error();
@ -991,7 +994,7 @@ void spkr_play_disk_arm() {
if ( ( disk_sfx_enabled ) && ( clk_6502_per_frm <= FRAME(iicplus_MHz_6502) ) ) {
if ( spkr_play_disk_ioerr_time == 0 ) {
spkr_play_sfx( spkr_src[SPKR_SRC_DISK_ARM_SFX], diskarm_sfx, diskarm_sfx_len );
spkr_play_disk_arm_time = 2;
spkr_play_disk_arm_time = fps / 15;
}
}
}
@ -1000,7 +1003,7 @@ void spkr_play_disk_arm() {
void spkr_play_disk_ioerr() {
if ( ( disk_sfx_enabled ) && ( clk_6502_per_frm <= FRAME(iicplus_MHz_6502) ) ) {
spkr_playqueue_sfx( spkr_src[SPKR_SRC_DISK_IOERR_SFX], diskioerr_sfx, diskioerr_sfx_len);
spkr_play_disk_ioerr_time = 4;
spkr_play_disk_ioerr_time = fps / 10; // 4 for 30 FPS, 8 for 60 FPS
}
}

View File

@ -333,7 +333,7 @@ uint8_t woz_read() {
if ( usedBytes ) {
// static const int extraForward = 4; // we search for 7 bit high a bit further to speed up disk read...
uint64_t bitForward = (clkelpased >> 2) + extraForward;
if ( bitForward > 100000000 ) {
if ( bitForward > 1000 ) {
bitForward = 4;
}
@ -359,7 +359,7 @@ uint8_t woz_read() {
WOZread.latch = 0;
// but we do not want to miss that latch valid nibble...
// in other words synchronization is needed because of imperfect cycle calculation
if ( bitForward < 18 ) {
if ( bitForward < 18 ) { // for 30 Hz FPS 18 is better ) {
return latch;
}
}