]> Shamusworld >> Repos - apple2/blob - src/apple2.cpp
Conversion of V65C02 to pointers, misc. whitespace cleanups.
[apple2] / src / apple2.cpp
1 //
2 // Apple 2 SDL Portable Apple Emulator
3 //
4 // by James Hammons
5 // © 2018 Underground Software
6 //
7 // Parts loosely inspired by AppleWin by Tom Charlesworth which was based on
8 // AppleWin by Oliver Schmidt which was based on AppleWin by Michael O'Brien.
9 // :-) Some parts (mainly TV rendering) are derived from ApplePC. Too bad it
10 // was closed source--it could have been *the* premier Apple II emulator out
11 // there.
12 //
13 // JLH = James Hammons <jlhamm@acm.org>
14 //
15 // WHO  WHEN        WHAT
16 // ---  ----------  -----------------------------------------------------------
17 // JLH  11/12/2005  Initial port to SDL
18 // JLH  11/18/2005  Wired up graphic soft switches
19 // JLH  12/02/2005  Setup timer subsystem for more accurate time keeping
20 // JLH  12/12/2005  Added preliminary state saving support
21 // JLH  09/24/2013  Added //e support
22 //
23
24 // STILL TO DO:
25 //
26 // - Port to SDL [DONE]
27 // - Weed out unneeded functions [DONE]
28 // - Disk I/O [DONE]
29 // - 128K IIe related stuff [DONE]
30 // - State loading/saving [DONE]
31 // - GUI goodies
32 //
33 // BUGS:
34 //
35 // - Having a directory in the ${disks} directory causes a segfault in floppy
36 //
37
38 #include "apple2.h"
39
40 #include <SDL2/SDL.h>
41 #include <fstream>
42 #include <iomanip>
43 #include <iostream>
44 #include <stdio.h>
45 #include <stdlib.h>
46 #include <string>
47 #include <time.h>
48 #include "ay8910.h"
49 #include "firmware.h"
50 #include "floppy.h"
51 #include "log.h"
52 #include "mos6522via.h"
53 #include "mmu.h"
54 #include "settings.h"
55 #include "sound.h"
56 #include "timing.h"
57 #include "video.h"
58 #include "gui/gui.h"
59 #include "gui/diskselector.h"
60
61 // Debug and misc. defines
62
63 #define THREADED_65C02
64 #define CPU_THREAD_OVERFLOW_COMPENSATION
65 #define DEBUG_LC
66 //#define CPU_CLOCK_CHECKING
67 //#define THREAD_DEBUGGING
68 #define SOFT_SWITCH_DEBUGGING
69
70 // Global variables
71
72 uint8_t ram[0x10000], rom[0x10000];                     // RAM & ROM spaces
73 uint8_t ram2[0x10000];                                          // Auxillary RAM
74 V65C02REGS mainCPU;                                                     // v65C02 execution context
75 uint8_t appleType = APPLE_TYPE_IIE;
76 FloppyDrive floppyDrive;
77 bool powerStateChangeRequested = false;
78 uint64_t frameCycleStart;
79
80 #if 0
81 uint32_t frameTicks = 0;
82 uint32_t frameTime[60];
83 #else
84 uint64_t frameTicks = 0;
85 uint64_t frameTime[60];
86 #endif
87 uint32_t frameTimePtr = 0;
88
89 // Exported variables
90
91 uint8_t lastKeyPressed = 0;
92 bool keyDown = false;
93 bool openAppleDown = false;
94 bool closedAppleDown = false;
95 bool store80Mode = false;
96 bool vbl = false;
97 bool slotCXROM = false;
98 bool slotC3ROM = false;
99 bool ramrd = false;
100 bool ramwrt = false;
101 bool altzp = false;
102 bool ioudis = true;
103 bool dhires = false;
104 // Language card state (ROM read, no write)
105 uint8_t lcState = 0x02;
106
107 static bool running = true;                                     // Machine running state flag...
108 #if 0
109 static uint32_t startTicks;
110 #else
111 static uint64_t startTicks;
112 #endif
113 static bool pauseMode = false;
114 static bool fullscreenDebounce = false;
115 //static bool capsLock = false;
116 //static bool capsLockDebounce = false;
117 static bool resetKeyDown = false;
118 static int8_t hideMouseTimeout = 60;
119
120 // Vars to handle the //e's 2-key rollover
121 static SDL_Keycode keysHeld[2];
122 static uint8_t keysHeldAppleCode[2];
123 static uint8_t keyDownCount = 0;
124 static uint8_t keyDelay = 0;
125
126 // Local functions
127
128 static void SaveApple2State(const char * filename);
129 static bool LoadApple2State(const char * filename);
130 static void ResetApple2State(void);
131 static void AppleTimer(uint16_t);
132
133 // Local timer callback functions
134
135 static void FrameCallback(void);
136 static void BlinkTimer(void);
137
138 #ifdef THREADED_65C02
139 // Test of threaded execution of 6502
140 static SDL_Thread * cpuThread = NULL;
141 static SDL_cond * cpuCond = NULL;
142 static SDL_sem * mainSem = NULL;
143 static bool cpuFinished = false;
144
145 // NB: Apple //e Manual sez 6502 is running @ 1,022,727 Hz
146 //     This is a lie. At the end of each 65 cycle line, there is an elongated
147 //     cycle (the so-called 'long' cycle) that throws the calcs out of whack.
148 //     So actually, it's supposed to be 1,020,484.32 Hz
149
150 // Let's try a thread...
151 //
152 // Here's how it works: Execute 1 frame's worth, then sleep. Other stuff wakes
153 // it up.
154 //
155 static uint32_t sampleCount;
156 static uint64_t sampleClock, lastSampleClock;
157 int CPUThreadFunc(void * data)
158 {
159         // Mutex must be locked for conditional to work...
160         // Also, must be created in the thread that uses it...
161         SDL_mutex * cpuMutex = SDL_CreateMutex();
162
163 #ifdef CPU_THREAD_OVERFLOW_COMPENSATION
164 //      float overflow = 0.0;
165 #endif
166
167         do
168         {
169 uint64_t cpuFrameTickStart = SDL_GetPerformanceCounter();
170 uint64_t oldClock = mainCPU.clock;
171 sampleCount = 0;
172 sampleClock = lastSampleClock = mainCPU.clock;
173 // decrement mainSem...
174 #ifdef THREAD_DEBUGGING
175 WriteLog("CPU: SDL_SemWait(mainSem);\n");
176 #endif
177                 SDL_SemWait(mainSem);
178
179                 // There are exactly 800 slices of 21.333 cycles per frame, so it works
180                 // out evenly.
181                 // [Actually, seems it's 786 slices of 21.666 cycles per frame]
182
183                 // Set our frame cycle counter to the correct # of cycles at the start
184                 // of this frame
185                 frameCycleStart = mainCPU.clock - mainCPU.overflow;
186 #ifdef THREAD_DEBUGGING
187 WriteLog("CPU: Execute65C02(&mainCPU, cycles);\n");
188 #endif
189 //              for(int i=0; i<786; i++)
190                 for(int i=0; i<262; i++)
191                 {
192 //                      uint32_t cycles = 21;
193 //                      overflow += 0.666666667;
194
195 //                      if (overflow > 1.0)
196 //                      {
197 //                              cycles++;
198 //                              overflow -= 1.0;
199 //                      }
200
201                         // If the CTRL+Reset key combo is being held, make sure the RESET
202                         // line stays asserted:
203                         if (resetKeyDown)
204                                 mainCPU.cpuFlags |= V65C02_ASSERT_LINE_RESET;
205
206 //                      Execute65C02(&mainCPU, cycles);
207                         Execute65C02(&mainCPU, 65);
208 //                      WriteSampleToBuffer();
209
210                         // According to "Understanding The Apple IIe", VBL asserted after
211                         // the last byte of the screen is read and let go on the first read
212                         // of the first byte of the screen. We now know that the screen
213                         // starts on line #6 and ends on line #197 (of the vertical
214                         // counter--actual VBLANK happens on lines 230 thru 233).
215 //                      vbl = ((i > 17) && (i < 592) ? true : false);
216                         vbl = ((i >= 6) && (i <= 197) ? true : false);
217                 }
218
219 WriteLog("*** Frame ran for %d cycles (%.3lf µs, %d samples).\n", mainCPU.clock - oldClock, ((double)(SDL_GetPerformanceCounter() - cpuFrameTickStart) * 1000000.0) / (double)SDL_GetPerformanceFrequency(), sampleCount);
220 //      frameTicks = ((SDL_GetPerformanceCounter() - startTicks) * 1000) / SDL_GetPerformanceFrequency();
221 /*
222 Other timings from UTA2E:
223 Power-up reset                          32.6 msec / 512 horizontal scans
224 Flash cycle                                     1.87 Hz / Vertical freq./32
225 Delay before auto repeat        534-801 msec / 32-48 vertical scans
226 Auto repeat frequency           15 Hz / Vertical freq./4
227 Vertical frequency                      59.94 Hz (actually, 59.92 Hz [59.92274339401])
228 Horizontal frequency            15,734 Hz (actually, 15700 Hz)
229 1 NTSC frame = 17030 cycles (N.B.: this works out to 1021800 cycles per sec.)
230 NTSC clock frequency ("composite" freq.) is 1.02048432 MHz, which is 14.31818 x (65 / (65 x 14 + 2)) MHz.
231 1 line = 65 cycles
232 70 blank lines for top margin, 192 lines for screen, (35 & 35?)
233 VA-C,V0-5 is upcounter starting at 011111010 ($FA) to 111111111 ($1FF)
234 Horizontal counter is upcounter resets to 0000000, then jumps to 1000000 &
235 counts up to 1111111 (bit 7 is Horizontal Preset Enable, which resets the counter when it goes low, the rest are H0-5)
236
237 pg. 3-24 says one cycle before VBL the counters will be at
238 010111111/1111111 (that doesn't look right to me...)
239
240 Video address bits:
241
242 A0 <- H0
243 A1 <- H1
244 A2 <- H2
245 A3 <- SUM-A3
246 A4 <- SUM-A4
247 A5 <- SUM-A5
248 A6 <- SUM-A6
249 A7 <- V0
250 A8 <- V1
251 A9 <- V2
252
253 SUMS are calculated like so:
254
255   1        1       0       1
256           H5      H4      H3
257   V4      V3      V4      V3
258 ------------------------------
259 SUM-A6  SUM-A5  SUM-A4  SUM-A3
260
261 In TEXT mode, A10 == (80STORE' * PAGE2)', A11 == 80STORE' * PAGE2
262 A12-A15 == 0
263
264 In HIRES mode, A13 == (PAGE2 * 80STORE')', A14 == PAGE2 * 80STORE'
265 A10 == VA, A11 == VB, A12 == VC, A15 == 0
266
267 N.B.: VA-C are lower bits than V5-0
268
269 HC, from 00, 0 to 23 is the HBL interval, with horizontal retrace occuring between cycles 8 and 11.
270 VC, from line 0-5 and 198-261 is the VBL interval, with vertical retrace occuring between lines 230-233
271
272 */
273
274 #ifdef THREAD_DEBUGGING
275 WriteLog("CPU: SDL_mutexP(cpuMutex);\n");
276 #endif
277                 SDL_mutexP(cpuMutex);
278                 // increment mainSem...
279 #ifdef THREAD_DEBUGGING
280 WriteLog("CPU: SDL_SemPost(mainSem);\n");
281 #endif
282                 SDL_SemPost(mainSem);
283 #ifdef THREAD_DEBUGGING
284 WriteLog("CPU: SDL_CondWait(cpuCond, cpuMutex);\n");
285 #endif
286                 SDL_CondWait(cpuCond, cpuMutex);
287
288 #ifdef THREAD_DEBUGGING
289 WriteLog("CPU: SDL_mutexV(cpuMutex);\n");
290 #endif
291                 SDL_mutexV(cpuMutex);
292         }
293         while (!cpuFinished);
294
295         SDL_DestroyMutex(cpuMutex);
296
297         return 0;
298 }
299 #endif
300
301
302 //
303 // Request a change in the power state of the emulated Apple
304 //
305 void SetPowerState(void)
306 {
307         powerStateChangeRequested = true;
308 }
309
310
311 //
312 // Load a file into RAM/ROM image space
313 //
314 bool LoadImg(char * filename, uint8_t * ram, int size)
315 {
316         FILE * fp = fopen(filename, "rb");
317
318         if (fp == NULL)
319                 return false;
320
321         fread(ram, 1, size, fp);
322         fclose(fp);
323
324         return true;
325 }
326
327
328 const uint8_t stateHeader[19] = "APPLE2SAVESTATE1.1";
329 static void SaveApple2State(const char * filename)
330 {
331         WriteLog("Main: Saving Apple2 state...\n");
332         FILE * file = fopen(filename, "wb");
333
334         if (!file)
335         {
336                 WriteLog("Could not open file \"%s\" for writing!\n", filename);
337                 return;
338         }
339
340         // Write out header
341         fwrite(stateHeader, 1, 18, file);
342
343         // Write out CPU state
344         fwrite(&mainCPU, 1, sizeof(mainCPU), file);
345
346         // Write out main memory
347         fwrite(ram, 1, 0x10000, file);
348         fwrite(ram2, 1, 0x10000, file);
349
350         // Write out state variables
351         fputc((uint8_t)keyDown, file);
352         fputc((uint8_t)openAppleDown, file);
353         fputc((uint8_t)closedAppleDown, file);
354         fputc((uint8_t)store80Mode, file);
355         fputc((uint8_t)vbl, file);
356         fputc((uint8_t)slotCXROM, file);
357         fputc((uint8_t)slotC3ROM, file);
358         fputc((uint8_t)ramrd, file);
359         fputc((uint8_t)ramwrt, file);
360         fputc((uint8_t)altzp, file);
361         fputc((uint8_t)ioudis, file);
362         fputc((uint8_t)dhires, file);
363         fputc((uint8_t)flash, file);
364         fputc((uint8_t)textMode, file);
365         fputc((uint8_t)mixedMode, file);
366         fputc((uint8_t)displayPage2, file);
367         fputc((uint8_t)hiRes, file);
368         fputc((uint8_t)alternateCharset, file);
369         fputc((uint8_t)col80Mode, file);
370         fputc(lcState, file);
371
372         // Write out floppy state
373         floppyDrive.SaveState(file);
374         fclose(file);
375 }
376
377
378 static bool LoadApple2State(const char * filename)
379 {
380         WriteLog("Main: Loading Apple2 state...\n");
381         FILE * file = fopen(filename, "rb");
382
383         if (!file)
384         {
385                 WriteLog("Could not open file \"%s\" for reading!\n", filename);
386                 return false;
387         }
388
389         uint8_t buffer[18];
390         fread(buffer, 1, 18, file);
391
392         // Sanity check...
393         if (memcmp(buffer, stateHeader, 18) != 0)
394         {
395                 fclose(file);
396                 WriteLog("File \"%s\" is not a valid Apple2 save state file!\n", filename);
397                 return false;
398         }
399
400         // Read CPU state
401         fread(&mainCPU, 1, sizeof(mainCPU), file);
402
403         // Read main memory
404         fread(ram, 1, 0x10000, file);
405         fread(ram2, 1, 0x10000, file);
406
407         // Read in state variables
408         keyDown = (bool)fgetc(file);
409         openAppleDown = (bool)fgetc(file);
410         closedAppleDown = (bool)fgetc(file);
411         store80Mode = (bool)fgetc(file);
412         vbl = (bool)fgetc(file);
413         slotCXROM = (bool)fgetc(file);
414         slotC3ROM = (bool)fgetc(file);
415         ramrd = (bool)fgetc(file);
416         ramwrt = (bool)fgetc(file);
417         altzp = (bool)fgetc(file);
418         ioudis = (bool)fgetc(file);
419         dhires = (bool)fgetc(file);
420         flash = (bool)fgetc(file);
421         textMode = (bool)fgetc(file);
422         mixedMode = (bool)fgetc(file);
423         displayPage2 = (bool)fgetc(file);
424         hiRes = (bool)fgetc(file);
425         alternateCharset = (bool)fgetc(file);
426         col80Mode = (bool)fgetc(file);
427         lcState = fgetc(file);
428
429         // Read in floppy state
430         floppyDrive.LoadState(file);
431
432         fclose(file);
433
434         // Make sure things are in a sane state before execution :-P
435         mainCPU.RdMem = AppleReadMem;
436         mainCPU.WrMem = AppleWriteMem;
437         mainCPU.Timer = AppleTimer;
438         ResetMMUPointers();
439
440         return true;
441 }
442
443
444 static void ResetApple2State(void)
445 {
446         keyDown = false;
447         openAppleDown = false;
448         closedAppleDown = false;
449         store80Mode = false;
450         vbl = false;
451         slotCXROM = false;
452         slotC3ROM = false;
453         ramrd = false;
454         ramwrt = false;
455         altzp = false;
456         ioudis = true;
457         dhires = false;
458         lcState = 0x02;
459         ResetMMUPointers();
460         ResetMBVIAs();
461 #ifdef USE_NEW_AY8910
462         AYReset(0);
463         AYReset(1);
464 #else
465         AY8910_reset(0);
466         AY8910_reset(1);
467 #endif
468
469         // Without this, you can wedge the system :-/
470         memset(ram, 0, 0x10000);
471         memset(ram2, 0, 0x10000);
472         mainCPU.cpuFlags |= V65C02_ASSERT_LINE_RESET;
473 }
474
475
476 static double cyclesForSample = 0;
477 static void AppleTimer(uint16_t cycles)
478 {
479         // Handle PHI2 clocked stuff here...
480         bool via1T1HitZero = (mbvia[0].timer1counter <= cycles ? true : false);
481         bool via2T1HitZero = (mbvia[1].timer1counter <= cycles ? true : false);
482
483         mbvia[0].timer1counter -= cycles;
484         mbvia[0].timer2counter -= cycles;
485         mbvia[1].timer1counter -= cycles;
486         mbvia[1].timer2counter -= cycles;
487
488         if (via1T1HitZero)
489         {
490                 if (mbvia[0].acr & 0x40)
491                 {
492                         mbvia[0].timer1counter += mbvia[0].timer1latch;
493
494                         if (mbvia[0].ier & 0x40)
495                         {
496                                 mbvia[0].ifr |= (0x80 | 0x40);
497                                 mainCPU.cpuFlags |= V65C02_ASSERT_LINE_IRQ;
498                         }
499                 }
500                 else
501                 {
502                         mbvia[0].ier &= 0x3F; // Disable T1 interrupt (VIA #1)
503                 }
504         }
505
506         if (via2T1HitZero)
507         {
508                 if (mbvia[1].acr & 0x40)
509                 {
510                         mbvia[1].timer1counter += mbvia[1].timer1latch;
511
512                         if (mbvia[1].ier & 0x40)
513                         {
514                                 mbvia[1].ifr |= (0x80 | 0x40);
515                                 mainCPU.cpuFlags |= V65C02_ASSERT_LINE_NMI;
516                         }
517                 }
518                 else
519                 {
520                         mbvia[1].ier &= 0x3F; // Disable T1 interrupt (VIA #2)
521                 }
522         }
523
524 #if 1
525         // Handle sound
526         // 21.26009 cycles per sample @ 48000 (running @ 1,020,484.32 Hz)
527         // Noooooope.  We need ~801 cycles per frame.  Averaging about 786, so missing 15 or so.
528         // 16.688154500083 ms = 1 frame
529         cyclesForSample += (double)cycles;
530
531         if (cyclesForSample >= 21.26009)
532         {
533 #if 0
534 sampleClock = mainCPU.clock;
535 WriteLog("    cyclesForSample = %lf (%d samples, cycles=%d)\n", cyclesForSample, sampleClock - lastSampleClock, cycles);
536 sampleCount++;
537 lastSampleClock = sampleClock;
538 #endif
539                 WriteSampleToBuffer();
540                 cyclesForSample -= 21.26009;
541         }
542 #endif
543 }
544
545
546 #ifdef CPU_CLOCK_CHECKING
547 uint8_t counter = 0;
548 uint32_t totalCPU = 0;
549 uint64_t lastClock = 0;
550 #endif
551 //
552 // Main loop
553 //
554 int main(int /*argc*/, char * /*argv*/[])
555 {
556         InitLog("./apple2.log");
557         LoadSettings();
558         srand(time(NULL));                      // Initialize RNG
559
560 #if 0
561 // Make some timing/address tables
562
563         for(uint32_t line=0; line<262; line++)
564         {
565                 WriteLog("LINE %03i: ", line);
566
567                 for(uint32_t col=0; col<65; col++)
568                 {
569                         // Convert these to H/V counters
570                         uint32_t hcount = col - 1;
571
572                         // HC sees zero twice:
573                         if (hcount == 0xFFFFFFFF)
574                                 hcount = 0;
575
576                         uint32_t vcount = line + 0xFA;
577
578                         // Now do the address calculations
579                         uint32_t sum = 0xD + ((hcount & 0x38) >> 3)
580                                 + (((vcount & 0xC0) >> 6) | ((vcount & 0xC0) >> 4));
581                         uint32_t address = ((vcount & 0x38) << 4) | ((sum & 0x0F) << 3) | (hcount & 0x07);
582
583                         // Add in particulars for the gfx mode we're in...
584                         if (false)
585                                 // non hires
586                                 address |= (!(!store80Mode && displayPage2) ? 0x400 : 0)
587                                         | (!store80Mode && displayPage2 ? 0x800 : 0);
588                         else
589                                 // hires
590                                 address |= (!(!store80Mode && displayPage2) ? 0x2000: 0)
591                                         | (!store80Mode && displayPage2 ? 0x4000 : 0)
592                                         | ((vcount & 0x07) << 10);
593
594                         WriteLog("$%04X ", address);
595                 }
596
597                 WriteLog("\n");
598         }
599
600 #endif
601
602         // Zero out memory
603         memset(ram, 0, 0x10000);
604         memset(rom, 0, 0x10000);
605         memset(ram2, 0, 0x10000);
606
607         // Set up MMU
608         SetupAddressMap();
609         ResetMMUPointers();
610
611         // Set up Mockingboard
612         memset(&mbvia[0], 0, sizeof(MOS6522VIA));
613         memset(&mbvia[1], 0, sizeof(MOS6522VIA));
614 //(at some point this shite will have to go into the state file...)
615 #ifdef USE_NEW_AY8910
616         AYInit();
617 #else
618         AY8910_InitAll(1020484, 48000);
619 #endif
620
621         // Set up V65C02 execution context
622         memset(&mainCPU, 0, sizeof(V65C02REGS));
623         mainCPU.RdMem = AppleReadMem;
624         mainCPU.WrMem = AppleWriteMem;
625         mainCPU.Timer = AppleTimer;
626         mainCPU.cpuFlags |= V65C02_ASSERT_LINE_RESET;
627
628         if (!LoadImg(settings.BIOSPath, rom + 0xC000, 0x4000))
629         {
630                 WriteLog("Could not open file '%s'!\n", settings.BIOSPath);
631                 return -1;
632         }
633
634         WriteLog("About to initialize video...\n");
635
636         if (!InitVideo())
637         {
638                 printf("Could not init screen: aborting!\n");
639                 return -1;
640         }
641
642         GUI::Init(sdlRenderer);
643
644         WriteLog("About to initialize audio...\n");
645         SoundInit();
646
647         if (settings.autoStateSaving)
648         {
649                 // Load last state from file...
650                 if (!LoadApple2State(settings.autoStatePath))
651                         WriteLog("Unable to use Apple2 state file \"%s\"!\n", settings.autoStatePath);
652         }
653
654 /*
655 So, how to run this then?  Right now, we have two separate threads, one for the CPU and one for audio.  The screen refresh is tied to the CPU thread.
656
657 To do this properly, however, we need to execute approximately 1,020,484.32 cycles per second, and we need to tie the AY/regular sound to this rate.  Video should happen still approximately 60 times a second, even though the real thing has a frame rate of 59.92 Hz.
658
659 Right now, the speed of the CPU is tied to the host system's refresh rate (which seems to be somewhere around 59.9 Hz).  Under this regime, the sound thread is starved much of the time, which means there's a timing mismatch somewhere between the sound thread and the CPU thread and the video (main) thread.
660
661 Other considerations: Even though we know the exact amount of cycles for one frame (17030 cycles to be exact), because the video frame rate is slightly under 60 (~59.92) the amount of time those cycles take can't be tied to the host system refresh rate, as they won't be the same (it will have about 8,000 or so more cycles in one second than it should have using 60 frames per second as the base frequency).  However, we do know that the system clock is 14.318180 MHz, and we do know that 1 out of every 65 cycles will take 2 extra ticks of the system clock (cycles are normally 14 ticks of the system clock).  So, by virtue of this, we know how long one frame is in seconds exactly (which would be (((65 * 14) + 2) * 262) / 14318180 = 16.688154500083 milliseconds).
662
663 So we need to decouple the CPU thread from the host video thread, and have the CPU frame run at its rate so that it will complete its running in its alloted time.  We also need to have a little bit of cushion for the sound thread, so that its buffer doesn't starve.  Assuming we get the timing correct, it will pull ahead and fall behind and all average out in the end.
664
665
666 */
667
668         running = true;
669         InitializeEventList();
670         // Set frame to fire at 1/60 s interval
671         SetCallbackTime(FrameCallback, 16666.66666667);
672         // Set up blinking at 1/4 s intervals
673         SetCallbackTime(BlinkTimer, 250000);
674         startTicks = SDL_GetTicks();
675
676 #ifdef THREADED_65C02
677         // Kick off the CPU...
678         cpuCond = SDL_CreateCond();
679         mainSem = SDL_CreateSemaphore(1);
680         cpuThread = SDL_CreateThread(CPUThreadFunc, NULL, NULL);
681 //Hmm... CPU does POST (+1), wait, then WAIT (-1)
682 //      SDL_sem * mainMutex = SDL_CreateMutex();
683 #endif
684
685         WriteLog("Entering main loop...\n");
686
687         while (running)
688         {
689 #ifdef CPU_CLOCK_CHECKING
690                 double timeToNextEvent = GetTimeToNextEvent();
691 #endif
692 #ifndef THREADED_65C02
693                 Execute65C02(&mainCPU, USEC_TO_M6502_CYCLES(timeToNextEvent));
694
695         #ifdef CPU_CLOCK_CHECKING
696 totalCPU += USEC_TO_M6502_CYCLES(timeToNextEvent);
697         #endif
698 #endif
699                 HandleNextEvent();
700         }
701
702 #ifdef THREADED_65C02
703 WriteLog("Main: cpuFinished = true;\n");
704         cpuFinished = true;
705
706 WriteLog("Main: SDL_SemWait(mainSem);\n");
707         // Only do this if NOT in power off/emulation paused mode!
708         if (!pauseMode)
709                 // Should lock until CPU thread is waiting...
710                 SDL_SemWait(mainSem);
711 #endif
712
713 WriteLog("Main: SDL_CondSignal(cpuCond);\n");
714         SDL_CondSignal(cpuCond);//thread is probably asleep, so wake it up
715 WriteLog("Main: SDL_WaitThread(cpuThread, NULL);\n");
716         SDL_WaitThread(cpuThread, NULL);
717 WriteLog("Main: SDL_DestroyCond(cpuCond);\n");
718         SDL_DestroyCond(cpuCond);
719         SDL_DestroySemaphore(mainSem);
720
721         // Autosave state here, if requested...
722         if (settings.autoStateSaving)
723                 SaveApple2State(settings.autoStatePath);
724
725         floppyDrive.SaveImage(0);
726         floppyDrive.SaveImage(1);
727
728         SoundDone();
729         VideoDone();
730         SaveSettings();
731         LogDone();
732
733         return 0;
734 }
735
736
737 //
738 // Apple //e scancodes. Tables are normal (0), +CTRL (1), +SHIFT (2),
739 // +CTRL+SHIFT (3). Order of keys is:
740 // Delete, left, tab, down, up, return, right, escape
741 // Space, single quote, comma, minus, period, slash
742 // Numbers 0-9
743 // Semicolon, equals, left bracket, backslash, right bracket, backquote
744 // Letters a-z (lowercase)
745 //
746 // N.B.: The Apple //e keyboard maps its shift characters like most modern US
747 //       keyboards, so this table should suffice for the shifted keys just
748 //       fine.
749 //
750 uint8_t apple2e_keycode[4][56] = {
751         {       // Normal
752                 0x7F, 0x08, 0x09, 0x0A, 0x0B, 0x0D, 0x15, 0x1B,
753                 0x20, 0x27, 0x2C, 0x2D, 0x2E, 0x2F,
754                 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
755                 0x3B, 0x3D, 0x5B, 0x5C, 0x5D, 0x60,
756                 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A,
757                 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74,
758                 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A
759         },
760         {       // With CTRL held
761                 0x7F, 0x08, 0x09, 0x0A, 0x0B, 0x0D, 0x15, 0x1B,
762                 0x20, 0x27, 0x2C, 0x1F, 0x2E, 0x2F,
763                 0x30, 0x31, 0x00, 0x33, 0x34, 0x35, 0x1E, 0x37, 0x38, 0x39,
764                 0x3B, 0x3D, 0x1B, 0x1C, 0x1D, 0x60,
765                 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A,
766                 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14,
767                 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A
768         },
769         {       // With Shift held
770                 0x7F, 0x08, 0x09, 0x0A, 0x0B, 0x0D, 0x15, 0x1B,
771                 0x20, 0x22, 0x3C, 0x5F, 0x3E, 0x3F,
772                 0x29, 0x21, 0x40, 0x23, 0x24, 0x25, 0x5E, 0x26, 0x2A, 0x28,
773                 0x3A, 0x2B, 0x7B, 0x7C, 0x7D, 0x7E,
774                 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A,
775                 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54,
776                 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A
777         },
778         {       // With CTRL+Shift held
779                 0x7F, 0x08, 0x09, 0x0A, 0x0B, 0x0D, 0x15, 0x1B,
780                 0x20, 0x22, 0x3C, 0x1F, 0x3E, 0x3F,
781                 0x29, 0x21, 0x00, 0x23, 0x24, 0x25, 0x1E, 0x26, 0x2A, 0x28,
782                 0x3A, 0x2B, 0x1B, 0x1C, 0x1D, 0x7E,
783                 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A,
784                 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14,
785                 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A
786         }
787 };
788
789 static uint32_t frameCount = 0;
790 static void FrameCallback(void)
791 {
792         SDL_Event event;
793         uint8_t keyIndex;
794
795         frameTimePtr = (frameTimePtr + 1) % 60;
796         frameTime[frameTimePtr] = startTicks;
797
798         while (SDL_PollEvent(&event))
799         {
800                 switch (event.type)
801                 {
802                 case SDL_KEYDOWN:
803                         // We do our own repeat handling thank you very much! :-)
804                         if (event.key.repeat != 0)
805                                 break;
806
807                         // Use CTRL+SHIFT+Q to exit, as well as the usual window decoration
808                         // method
809                         if ((event.key.keysym.mod & KMOD_CTRL)
810                                 && (event.key.keysym.mod & KMOD_SHIFT)
811                                 && (event.key.keysym.sym == SDLK_q))
812                         {
813                                 running = false;
814                                 // We return here, because we don't want to pick up any
815                                 // spurious keypresses with our exit sequence.
816                                 return;
817                         }
818
819                         // CTRL+RESET key emulation (mapped to CTRL+HOME)
820                         if ((event.key.keysym.mod & KMOD_CTRL)
821                                 && (event.key.keysym.sym == SDLK_HOME))
822                         {
823 //seems to leave the machine in an inconsistent state vis-a-vis the language card... [does it anymore?]
824                                 resetKeyDown = true;
825                                 break;
826                         }
827
828                         // There has GOT to be a better way off mapping SDLKs to our
829                         // keyindex. But for now, this should suffice.
830                         keyIndex = 0xFF;
831
832                         switch (event.key.keysym.sym)
833                         {
834                         case SDLK_BACKSPACE:    keyIndex =  0; break;
835                         case SDLK_LEFT:         keyIndex =  1; break;
836                         case SDLK_TAB:          keyIndex =  2; break;
837                         case SDLK_DOWN:         keyIndex =  3; break;
838                         case SDLK_UP:           keyIndex =  4; break;
839                         case SDLK_RETURN:       keyIndex =  5; break;
840                         case SDLK_RIGHT:        keyIndex =  6; break;
841                         case SDLK_ESCAPE:       keyIndex =  7; break;
842                         case SDLK_SPACE:        keyIndex =  8; break;
843                         case SDLK_QUOTE:        keyIndex =  9; break;
844                         case SDLK_COMMA:        keyIndex = 10; break;
845                         case SDLK_MINUS:        keyIndex = 11; break;
846                         case SDLK_PERIOD:       keyIndex = 12; break;
847                         case SDLK_SLASH:        keyIndex = 13; break;
848                         case SDLK_0:            keyIndex = 14; break;
849                         case SDLK_1:            keyIndex = 15; break;
850                         case SDLK_2:            keyIndex = 16; break;
851                         case SDLK_3:            keyIndex = 17; break;
852                         case SDLK_4:            keyIndex = 18; break;
853                         case SDLK_5:            keyIndex = 19; break;
854                         case SDLK_6:            keyIndex = 20; break;
855                         case SDLK_7:            keyIndex = 21; break;
856                         case SDLK_8:            keyIndex = 22; break;
857                         case SDLK_9:            keyIndex = 23; break;
858                         case SDLK_SEMICOLON:    keyIndex = 24; break;
859                         case SDLK_EQUALS:       keyIndex = 25; break;
860                         case SDLK_LEFTBRACKET:  keyIndex = 26; break;
861                         case SDLK_BACKSLASH:    keyIndex = 27; break;
862                         case SDLK_RIGHTBRACKET: keyIndex = 28; break;
863                         case SDLK_BACKQUOTE:    keyIndex = 29; break;
864                         case SDLK_a:            keyIndex = 30; break;
865                         case SDLK_b:            keyIndex = 31; break;
866                         case SDLK_c:            keyIndex = 32; break;
867                         case SDLK_d:            keyIndex = 33; break;
868                         case SDLK_e:            keyIndex = 34; break;
869                         case SDLK_f:            keyIndex = 35; break;
870                         case SDLK_g:            keyIndex = 36; break;
871                         case SDLK_h:            keyIndex = 37; break;
872                         case SDLK_i:            keyIndex = 38; break;
873                         case SDLK_j:            keyIndex = 39; break;
874                         case SDLK_k:            keyIndex = 40; break;
875                         case SDLK_l:            keyIndex = 41; break;
876                         case SDLK_m:            keyIndex = 42; break;
877                         case SDLK_n:            keyIndex = 43; break;
878                         case SDLK_o:            keyIndex = 44; break;
879                         case SDLK_p:            keyIndex = 45; break;
880                         case SDLK_q:            keyIndex = 46; break;
881                         case SDLK_r:            keyIndex = 47; break;
882                         case SDLK_s:            keyIndex = 48; break;
883                         case SDLK_t:            keyIndex = 49; break;
884                         case SDLK_u:            keyIndex = 50; break;
885                         case SDLK_v:            keyIndex = 51; break;
886                         case SDLK_w:            keyIndex = 52; break;
887                         case SDLK_x:            keyIndex = 53; break;
888                         case SDLK_y:            keyIndex = 54; break;
889                         case SDLK_z:            keyIndex = 55; break;
890                         }
891
892                         // Stuff the key in if we have a valid one...
893                         if (keyIndex != 0xFF)
894                         {
895                                 // Handle Shift, CTRL, & Shift+CTRL combos
896                                 uint8_t table = 0;
897
898                                 if (event.key.keysym.mod & KMOD_CTRL)
899                                         table |= 1;
900
901                                 if (event.key.keysym.mod & KMOD_SHIFT)
902                                         table |= 2;
903
904                                 lastKeyPressed = apple2e_keycode[table][keyIndex];
905                                 keyDown = true;
906
907                                 // Handle Caps Lock
908                                 if ((SDL_GetModState() & KMOD_CAPS)
909                                         && (lastKeyPressed >= 0x61) && (lastKeyPressed <= 0x7A))
910                                         lastKeyPressed -= 0x20;
911
912                                 // Handle key repeat if the key hasn't been held
913 //                              if (keyDelay == 0)
914                                         keyDelay = 15;
915
916                                 keyDownCount++;
917
918                                 // Buffer the key held. Note that the last key is always
919                                 // stuffed into keysHeld[0].
920                                 if (keyDownCount >= 2)
921                                 {
922                                         keysHeld[1] = keysHeld[0];
923                                         keysHeldAppleCode[1] = keysHeldAppleCode[0];
924
925                                         if (keyDownCount > 2)
926                                                 keyDownCount = 2;
927                                 }
928
929                                 keysHeld[0] = event.key.keysym.sym;
930                                 keysHeldAppleCode[0] = lastKeyPressed;
931                                 break;
932                         }
933
934                         if (event.key.keysym.sym == SDLK_PAUSE)
935                         {
936                                 pauseMode = !pauseMode;
937
938                                 if (pauseMode)
939                                 {
940                                         SoundPause();
941                                         SpawnMessage("*** PAUSED ***");
942                                 }
943                                 else
944                                 {
945                                         SoundResume();
946                                         SpawnMessage("*** RESUME ***");
947                                 }
948                         }
949                         // Buttons 0 & 1
950                         else if (event.key.keysym.sym == SDLK_LALT)
951                                 openAppleDown = true;
952                         else if (event.key.keysym.sym == SDLK_RALT)
953                                 closedAppleDown = true;
954                         // Toggle the disassembly process
955                         else if (event.key.keysym.sym == SDLK_F11)
956                         {
957                                 dumpDis = !dumpDis;
958                                 SpawnMessage("Trace: %s", (dumpDis ? "ON" : "off"));
959                         }
960                         else if (event.key.keysym.sym == SDLK_F12)
961                         {
962                                 logAYInternal = !logAYInternal;
963                                 SpawnMessage("AY Trace: %s", (logAYInternal ? "ON" : "off"));
964                         }
965
966 /*else if (event.key.keysym.sym == SDLK_F9)
967 {
968         floppyDrive.CreateBlankImage(0);
969 //      SpawnMessage("Image cleared...");
970 }//*/
971 /*else if (event.key.keysym.sym == SDLK_F10)
972 {
973         floppyDrive.SwapImages();
974 //      SpawnMessage("Image swapped...");
975 }//*/
976
977                         else if (event.key.keysym.sym == SDLK_F2)
978                                 TogglePalette();
979                         else if (event.key.keysym.sym == SDLK_F3)
980                                 CycleScreenTypes();
981                         else if (event.key.keysym.sym == SDLK_F4)
982                                 ToggleTickDisplay();
983                         else if (event.key.keysym.sym == SDLK_F5)
984                         {
985                                 VolumeDown();
986                                 char volStr[19] = "[****************]";
987
988                                 for(int i=GetVolume(); i<16; i++)
989                                         volStr[1 + i] = '-';
990
991                                 SpawnMessage("Volume: %s", volStr);
992                         }
993                         else if (event.key.keysym.sym == SDLK_F6)
994                         {
995                                 VolumeUp();
996                                 char volStr[19] = "[****************]";
997
998                                 for(int i=GetVolume(); i<16; i++)
999                                         volStr[1 + i] = '-';
1000
1001                                 SpawnMessage("Volume: %s", volStr);
1002                         }
1003                         else if (event.key.keysym.sym == SDLK_F7)
1004                         {
1005                                 // 4th root of 2 is ~1.18920711500272 (~1.5 dB)
1006                                 maxVolume /= 1.4142135f;        // This attenuates by ~3 dB
1007                                 SpawnMessage("MB Volume: %d", (int)maxVolume);
1008                         }
1009                         else if (event.key.keysym.sym == SDLK_F8)
1010                         {
1011                                 maxVolume *= 1.4142135f;
1012                                 SpawnMessage("MB Volume: %d", (int)maxVolume);
1013                         }
1014                         else if (event.key.keysym.sym == SDLK_F12)
1015                         {
1016                                 if (!fullscreenDebounce)
1017                                 {
1018                                         ToggleFullScreen();
1019                                         fullscreenDebounce = true;
1020                                 }
1021                         }
1022
1023                         break;
1024
1025                 case SDL_KEYUP:
1026                         if (event.key.keysym.sym == SDLK_F12)
1027                                 fullscreenDebounce = false;
1028                         // Paddle buttons 0 & 1
1029                         else if (event.key.keysym.sym == SDLK_LALT)
1030                                 openAppleDown = false;
1031                         else if (event.key.keysym.sym == SDLK_RALT)
1032                                 closedAppleDown = false;
1033                         else if ((event.key.keysym.mod & KMOD_CTRL)
1034                                 && (event.key.keysym.sym == SDLK_HOME))
1035                                 resetKeyDown = false;
1036                         else
1037                         {
1038                                 // Handle key buffering 'key up' event (2 key rollover)
1039                                 if ((keyDownCount == 1) && (event.key.keysym.sym == keysHeld[0]))
1040                                 {
1041                                         keyDownCount--;
1042                                         keyDelay = 0;   // Reset key delay
1043                                 }
1044                                 else if (keyDownCount == 2)
1045                                 {
1046                                         if (event.key.keysym.sym == keysHeld[0])
1047                                         {
1048                                                 keyDownCount--;
1049                                                 keysHeld[0] = keysHeld[1];
1050                                                 keysHeldAppleCode[0] = keysHeldAppleCode[1];
1051                                         }
1052                                         else if (event.key.keysym.sym == keysHeld[1])
1053                                         {
1054                                                 keyDownCount--;
1055                                         }
1056                                 }
1057                         }
1058
1059                         break;
1060
1061                 case SDL_MOUSEBUTTONDOWN:
1062                         GUI::MouseDown(event.motion.x, event.motion.y, event.motion.state);
1063                         break;
1064
1065                 case SDL_MOUSEBUTTONUP:
1066                         GUI::MouseUp(event.motion.x, event.motion.y, event.motion.state);
1067                         break;
1068
1069                 case SDL_MOUSEMOTION:
1070                         GUI::MouseMove(event.motion.x, event.motion.y, event.motion.state);
1071
1072                         // Handle mouse showing when the mouse is hidden...
1073                         if (hideMouseTimeout == -1)
1074                                 SDL_ShowCursor(1);
1075
1076                         hideMouseTimeout = 60;
1077                         break;
1078
1079                 case SDL_WINDOWEVENT:
1080                         if (event.window.event == SDL_WINDOWEVENT_LEAVE)
1081                                 GUI::MouseMove(0, 0, 0);
1082
1083                         break;
1084
1085                 case SDL_QUIT:
1086                         running = false;
1087                 }
1088         }
1089
1090         // Hide the mouse if it's been 1s since the last time it was moved
1091         // N.B.: Should disable mouse hiding if it's over the GUI...
1092         if ((hideMouseTimeout > 0) && !(GUI::sidebarState == SBS_SHOWN || DiskSelector::showWindow == true))
1093                 hideMouseTimeout--;
1094         else if (hideMouseTimeout == 0)
1095         {
1096                 hideMouseTimeout--;
1097                 SDL_ShowCursor(0);
1098         }
1099
1100         // Stuff the Apple keyboard buffer, if any keys are pending
1101         // N.B.: May have to simulate the key repeat delay too [yup, sure do]
1102         //       According to "Understanding the Apple IIe", the initial delay is
1103         //       between 32 & 48 jiffies and the repeat is every 4 jiffies.
1104         if (keyDownCount > 0)
1105         {
1106                 keyDelay--;
1107
1108                 if (keyDelay == 0)
1109                 {
1110                         keyDelay = 3;
1111                         lastKeyPressed = keysHeldAppleCode[0];
1112                         keyDown = true;
1113                 }
1114         }
1115
1116         // Handle power request from the GUI
1117         if (powerStateChangeRequested)
1118         {
1119                 if (GUI::powerOnState)
1120                 {
1121                         pauseMode = false;
1122                         // Unlock the CPU thread...
1123                         SDL_SemPost(mainSem);
1124                 }
1125                 else
1126                 {
1127                         pauseMode = true;
1128                         // Should lock until CPU thread is waiting...
1129                         SDL_SemWait(mainSem);
1130                         ResetApple2State();
1131                 }
1132
1133                 powerStateChangeRequested = false;
1134         }
1135
1136         // Render the Apple screen + GUI overlay
1137         RenderAppleScreen(sdlRenderer);
1138         GUI::Render(sdlRenderer);
1139         SDL_RenderPresent(sdlRenderer);
1140         SetCallbackTime(FrameCallback, 16666.66666667);
1141
1142 #ifdef CPU_CLOCK_CHECKING
1143 //We know it's stopped, so we can get away with this...
1144 counter++;
1145 if (counter == 60)
1146 {
1147         uint64_t clock = GetCurrentV65C02Clock();
1148 //totalCPU += (uint32_t)(clock - lastClock);
1149
1150         printf("Executed %u cycles...\n", (uint32_t)(clock - lastClock));
1151         lastClock = clock;
1152 //      totalCPU = 0;
1153         counter = 0;
1154 }
1155 #endif
1156
1157 // This is the problem: If you set the interval to 16, it runs faster than
1158 // 1/60s per frame.  If you set it to 17, it runs slower.  What we need is to
1159 // have it do 16 for one frame, then 17 for two others.  Then it should average
1160 // out to 1/60s per frame every 3 frames.  [And now it does!]
1161 // Maybe we need a higher resolution timer, as the SDL_GetTicks() (in ms) seems
1162 // to jitter all over the place...
1163         frameCount = (frameCount + 1) % 3;
1164 //      uint32_t waitFrameTime = 17 - (frameCount == 0 ? 1 : 0);
1165         // Get number of ticks burned in this frame, for displaying elsewhere
1166 #if 0
1167         frameTicks = SDL_GetTicks() - startTicks;
1168 #else
1169         frameTicks = ((SDL_GetPerformanceCounter() - startTicks) * 1000) / SDL_GetPerformanceFrequency();
1170 #endif
1171
1172         // Wait for next frame...
1173 //      while (SDL_GetTicks() - startTicks < waitFrameTime)
1174 //              SDL_Delay(1);
1175
1176 #if 0
1177         startTicks = SDL_GetTicks();
1178 #else
1179         startTicks = SDL_GetPerformanceCounter();
1180 #endif
1181 #if 0
1182         uint64_t cpuCycles = GetCurrentV65C02Clock();
1183         uint32_t cyclesBurned = (uint32_t)(cpuCycles - lastCPUCycles);
1184         WriteLog("FrameCallback: used %i cycles\n", cyclesBurned);
1185         lastCPUCycles = cpuCycles
1186 #endif
1187
1188 //let's wait, then signal...
1189 //works longer, but then still falls behind... [FIXED, see above]
1190 #ifdef THREADED_65C02
1191         if (!pauseMode)
1192                 SDL_CondSignal(cpuCond);//OK, let the CPU go another frame...
1193 #endif
1194 }
1195
1196
1197 static void BlinkTimer(void)
1198 {
1199         // Set up blinking at 1/4 sec intervals
1200         flash = !flash;
1201         SetCallbackTime(BlinkTimer, 250000);
1202 }
1203
1204
1205 /*
1206 Next problem is this: How to have events occur and synchronize with the rest
1207 of the threads?
1208
1209   o Have the CPU thread manage the timer mechanism? (need to have a method of carrying
1210     remainder CPU cycles over...)
1211
1212 One way would be to use a fractional accumulator, then subtract 1 every
1213 time it overflows. Like so:
1214
1215 double overflow = 0;
1216 uint32_t time = 20;
1217 while (!done)
1218 {
1219         Execute6808(&soundCPU, time);
1220         overflow += 0.289115646;
1221         if (overflow > 1.0)
1222         {
1223                 overflow -= 1.0;
1224                 time = 21;
1225         }
1226         else
1227                 time = 20;
1228 }
1229 */
1230