617 lines
19 KiB
C
617 lines
19 KiB
C
/*
|
|
* Apple // emulator for *ix
|
|
*
|
|
* This software package is subject to the GNU General Public License
|
|
* version 3 or later (your choice) as published by the Free Software
|
|
* Foundation.
|
|
*
|
|
* Copyright 2017 Aaron Culliney
|
|
*
|
|
*/
|
|
|
|
#include "common.h"
|
|
|
|
typedef struct backend_node_s {
|
|
struct backend_node_s *next;
|
|
long order;
|
|
video_backend_s *backend;
|
|
} backend_node_s;
|
|
|
|
static bool video_initialized = false;
|
|
static bool null_backend_running = true;
|
|
static backend_node_s *head = NULL;
|
|
static video_backend_s *currentBackend = NULL;
|
|
static pthread_t render_thread_id = 0;
|
|
|
|
static unsigned int cyclesFrameLast = 0;
|
|
static unsigned long dirty = 0UL;
|
|
static bool reset_scanner = false;
|
|
|
|
#if VIDEO_TRACING
|
|
static FILE *video_trace_fp = NULL;
|
|
static unsigned long frameCount = 0UL;
|
|
static unsigned long frameBegin = 0UL;
|
|
static unsigned long frameEnd = UINT_MAX;
|
|
#endif
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// video scanner & generator
|
|
|
|
// scanline text page offsets
|
|
static uint16_t vert_offset_txt[TEXT_ROWS + /*VBL:*/ 8 + /*extra:*/1] = {
|
|
0x000, 0x080, 0x100, 0x180, 0x200, 0x280, 0x300, 0x380, // 0-63 screen top
|
|
0x000, 0x080, 0x100, 0x180, 0x200, 0x280, 0x300, 0x380, // 64-127 screen middle
|
|
0x000, 0x080, 0x100, 0x180, 0x200, 0x280, 0x300, 0x380, // 128-191 screen bottom
|
|
0x000, 0x080, 0x100, 0x180, 0x200, 0x280, 0x300, 0x380, // 192-255 VBL...
|
|
0x380, // 256,257,258,259,260,261
|
|
};
|
|
|
|
// scanline hires page offsets
|
|
static uint16_t vert_offset_hgr[SCANLINES_FRAME] = {
|
|
0x0000,0x0400,0x0800,0x0C00,0x1000,0x1400,0x1800,0x1C00,// 0-63 screen top
|
|
0x0080,0x0480,0x0880,0x0C80,0x1080,0x1480,0x1880,0x1C80,
|
|
0x0100,0x0500,0x0900,0x0D00,0x1100,0x1500,0x1900,0x1D00,
|
|
0x0180,0x0580,0x0980,0x0D80,0x1180,0x1580,0x1980,0x1D80,
|
|
0x0200,0x0600,0x0A00,0x0E00,0x1200,0x1600,0x1A00,0x1E00,
|
|
0x0280,0x0680,0x0A80,0x0E80,0x1280,0x1680,0x1A80,0x1E80,
|
|
0x0300,0x0700,0x0B00,0x0F00,0x1300,0x1700,0x1B00,0x1F00,
|
|
0x0380,0x0780,0x0B80,0x0F80,0x1380,0x1780,0x1B80,0x1F80,
|
|
|
|
0x0000,0x0400,0x0800,0x0C00,0x1000,0x1400,0x1800,0x1C00,// 64-127 screen middle
|
|
0x0080,0x0480,0x0880,0x0C80,0x1080,0x1480,0x1880,0x1C80,
|
|
0x0100,0x0500,0x0900,0x0D00,0x1100,0x1500,0x1900,0x1D00,
|
|
0x0180,0x0580,0x0980,0x0D80,0x1180,0x1580,0x1980,0x1D80,
|
|
0x0200,0x0600,0x0A00,0x0E00,0x1200,0x1600,0x1A00,0x1E00,
|
|
0x0280,0x0680,0x0A80,0x0E80,0x1280,0x1680,0x1A80,0x1E80,
|
|
0x0300,0x0700,0x0B00,0x0F00,0x1300,0x1700,0x1B00,0x1F00,
|
|
0x0380,0x0780,0x0B80,0x0F80,0x1380,0x1780,0x1B80,0x1F80,
|
|
|
|
0x0000,0x0400,0x0800,0x0C00,0x1000,0x1400,0x1800,0x1C00,// 128-191 screen bottom
|
|
0x0080,0x0480,0x0880,0x0C80,0x1080,0x1480,0x1880,0x1C80,
|
|
0x0100,0x0500,0x0900,0x0D00,0x1100,0x1500,0x1900,0x1D00,
|
|
0x0180,0x0580,0x0980,0x0D80,0x1180,0x1580,0x1980,0x1D80,
|
|
0x0200,0x0600,0x0A00,0x0E00,0x1200,0x1600,0x1A00,0x1E00,
|
|
0x0280,0x0680,0x0A80,0x0E80,0x1280,0x1680,0x1A80,0x1E80,
|
|
0x0300,0x0700,0x0B00,0x0F00,0x1300,0x1700,0x1B00,0x1F00,
|
|
0x0380,0x0780,0x0B80,0x0F80,0x1380,0x1780,0x1B80,0x1F80,
|
|
|
|
0x0000,0x0400,0x0800,0x0C00,0x1000,0x1400,0x1800,0x1C00,// 192-255 VBL...
|
|
0x0080,0x0480,0x0880,0x0C80,0x1080,0x1480,0x1880,0x1C80,
|
|
0x0100,0x0500,0x0900,0x0D00,0x1100,0x1500,0x1900,0x1D00,
|
|
0x0180,0x0580,0x0980,0x0D80,0x1180,0x1580,0x1980,0x1D80,
|
|
0x0200,0x0600,0x0A00,0x0E00,0x1200,0x1600,0x1A00,0x1E00,
|
|
0x0280,0x0680,0x0A80,0x0E80,0x1280,0x1680,0x1A80,0x1E80,
|
|
0x0300,0x0700,0x0B00,0x0F00,0x1300,0x1700,0x1B00,0x1F00,
|
|
0x0380,0x0780,0x0B80,0x0F80,0x1380,0x1780,0x1B80,0x1F80,
|
|
|
|
0x0B80,0x0F80,0x1380,0x1780,0x1B80,0x1F80, // 256,257,258,259,260,261
|
|
};
|
|
|
|
// scanline horizontal offsets (UtAIIe 5-12, 5-15+)
|
|
static uint8_t scan_offset[5][CYCLES_SCANLINE] =
|
|
{
|
|
{ // 0-63 screen top
|
|
0x68,
|
|
0x68,0x69,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F,// 1-8
|
|
0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,// 9-16
|
|
0x78,0x79,0x7A,0x7B,0x7C,0x7D,0x7E,0x7F,// 17-24
|
|
0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,// 25-32
|
|
0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,// 33-40
|
|
0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,// 41-48
|
|
0x18,0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,// 49-56
|
|
0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,// 57-64
|
|
},
|
|
{ // 64-127 screen middle
|
|
0x10,
|
|
0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,
|
|
0x18,0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,
|
|
0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,
|
|
0x28,0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F,
|
|
0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,
|
|
0x38,0x39,0x3A,0x3B,0x3C,0x3D,0x3E,0x3F,
|
|
0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,
|
|
0x48,0x49,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F,
|
|
},
|
|
{ // 128-191 screen bottom
|
|
0x38,
|
|
0x38,0x39,0x3A,0x3B,0x3C,0x3D,0x3E,0x3F,
|
|
0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,
|
|
0x48,0x49,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F,
|
|
0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,
|
|
0x58,0x59,0x5A,0x5B,0x5C,0x5D,0x5E,0x5F,
|
|
0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,
|
|
0x68,0x69,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F,
|
|
0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,
|
|
},
|
|
{ // 192-255 VBL
|
|
0x60,
|
|
0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,
|
|
0x68,0x69,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F,
|
|
0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,
|
|
0x78,0x79,0x7A,0x7B,0x7C,0x7D,0x7E,0x7F,
|
|
0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,
|
|
0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,
|
|
0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,
|
|
0x18,0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,
|
|
},
|
|
{ // 256,257,258,259,260,261
|
|
0x60,
|
|
0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,
|
|
0x68,0x69,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F,
|
|
0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,
|
|
0x78,0x79,0x7A,0x7B,0x7C,0x7D,0x7E,0x7F,
|
|
0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,
|
|
0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,
|
|
0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,
|
|
0x18,0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,
|
|
},
|
|
};
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
void video_init(void) {
|
|
video_initialized = true;
|
|
|
|
ASSERT_NOT_ON_CPU_THREAD();
|
|
LOG("(re)setting render_thread_id : %lu -> %lu", (unsigned long)render_thread_id, (unsigned long)pthread_self());
|
|
render_thread_id = pthread_self();
|
|
|
|
currentBackend->init((void*)0);
|
|
}
|
|
|
|
void _video_setRenderThread(pthread_t id) {
|
|
LOG("setting render_thread_id : %lu -> %lu", (unsigned long)render_thread_id, (unsigned long)id);
|
|
render_thread_id = id;
|
|
}
|
|
|
|
bool video_isRenderThread(void) {
|
|
return (pthread_self() == render_thread_id);
|
|
}
|
|
|
|
void video_shutdown(void) {
|
|
|
|
#if MOBILE_DEVICE
|
|
// WARNING : shutdown should occur on the render thread. Platform code (iOS, Android) should ensure this is called
|
|
// from within a render pass...
|
|
assert(!render_thread_id || pthread_self() == render_thread_id);
|
|
#endif
|
|
|
|
currentBackend->shutdown();
|
|
}
|
|
|
|
void video_render(void) {
|
|
ASSERT_ON_UI_THREAD();
|
|
currentBackend->render();
|
|
}
|
|
|
|
void video_main_loop(void) {
|
|
currentBackend->main_loop();
|
|
}
|
|
|
|
void video_flashText(void) {
|
|
currentBackend->flashText();
|
|
}
|
|
|
|
bool video_isDirty(unsigned long flags) {
|
|
return !!(dirty & flags);
|
|
}
|
|
|
|
void video_setDirty(unsigned long flags) {
|
|
__sync_fetch_and_or(&dirty, flags);
|
|
if (flags & A2_DIRTY_FLAG) {
|
|
ASSERT_ON_CPU_THREAD();
|
|
video_scannerUpdate();
|
|
}
|
|
}
|
|
|
|
unsigned long video_clearDirty(unsigned long flags) {
|
|
return __sync_fetch_and_and(&dirty, ~flags);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// video scanner & generator routines
|
|
|
|
static inline uint16_t _getScannerAddress(drawpage_mode_t mode, int page, unsigned int vCount, unsigned int hCount) {
|
|
uint16_t a = 0;
|
|
if (mode == DRAWPAGE_TEXT) {
|
|
a = (page ? 0x800 : 0x400) + vert_offset_txt[vCount>>3] + scan_offset[vCount>>6][hCount];
|
|
} else {
|
|
a = (page ? 0x4000 : 0x2000) + vert_offset_hgr[vCount] + scan_offset[vCount>>6][hCount];
|
|
}
|
|
return a;
|
|
}
|
|
|
|
static drawpage_mode_t _currentMode(unsigned int vCount) {
|
|
// FIXME TODO ... this is currently incorrect in VBL for MIXED
|
|
drawpage_mode_t mode = (vCount < SCANLINES_MIX) ? video_currentMainMode(run_args.softswitches) : video_currentMixedMode(run_args.softswitches);
|
|
return mode;
|
|
}
|
|
|
|
static void _flushScanline(uint8_t *scanline, unsigned int scanrow, unsigned int scancol, unsigned int scanend) {
|
|
scan_data_t scandata = {
|
|
.scanline = &scanline[0],
|
|
.softswitches = run_args.softswitches,
|
|
.scanrow = scanrow,
|
|
.scancol = scancol,
|
|
.scanend = scanend,
|
|
};
|
|
currentBackend->flushScanline(&scandata);
|
|
}
|
|
|
|
void video_scannerReset(void) {
|
|
ASSERT_ON_CPU_THREAD();
|
|
reset_scanner = true;
|
|
}
|
|
|
|
// Call to advance the video scanner and generator when the following events occur:
|
|
// 1: Just prior writing to active video memory
|
|
// 2: Just prior toggling a video softswitch
|
|
// 3: Upon video frame completion (CYCLES_FRAME : 17030) -- FIXME TODO can we optimize this away if no change?
|
|
void video_scannerUpdate(void) {
|
|
|
|
static uint8_t scanline[CYCLES_VIS<<1] = { 0 }; // 80 columns of data ...
|
|
static unsigned int scancol = 0;
|
|
static unsigned int scanidx = 0;
|
|
static unsigned int hCount = 0;
|
|
static unsigned int vCount = 0;
|
|
|
|
ASSERT_ON_CPU_THREAD();
|
|
|
|
if (reset_scanner) {
|
|
reset_scanner = false;
|
|
cyclesFrameLast = 0;
|
|
scancol = 0;
|
|
scanidx = 0;
|
|
hCount = 0;
|
|
vCount = 0;
|
|
}
|
|
|
|
timing_checkpointCycles();
|
|
assert(cycles_video_frame >= cyclesFrameLast);
|
|
unsigned int cyclesCount = cycles_video_frame - cyclesFrameLast;
|
|
cyclesFrameLast = cycles_video_frame;
|
|
|
|
if (UNLIKELY(cyclesCount == 0)) {
|
|
return;
|
|
}
|
|
|
|
int page = video_currentPage(run_args.softswitches);
|
|
|
|
uint8_t aux = 0x0;
|
|
uint8_t mbd = 0x0;
|
|
uint16_t addr = 0x0;
|
|
drawpage_mode_t mode = _currentMode(vCount);
|
|
for (unsigned int i=0; i<cyclesCount; i++) {
|
|
const bool isVisible = ((hCount >= CYCLES_VIS_BEGIN) && (vCount < SCANLINES_VBL_BEGIN));
|
|
|
|
#if VIDEO_TRACING
|
|
char *type = "xBL";
|
|
#else
|
|
if (isVisible)
|
|
#endif
|
|
{
|
|
addr = _getScannerAddress(mode, page, vCount, hCount);
|
|
aux = apple_ii_64k[1][addr];
|
|
mbd = apple_ii_64k[0][addr];
|
|
}
|
|
|
|
if (isVisible) {
|
|
#if VIDEO_TRACING
|
|
type = "VIS";
|
|
#endif
|
|
scanline[(scancol<<1)+(scanidx<<1)+0] = aux;
|
|
scanline[(scancol<<1)+(scanidx<<1)+1] = mbd;
|
|
++scanidx;
|
|
}
|
|
|
|
#if VIDEO_TRACING
|
|
if (video_trace_fp && (frameBegin <= frameCount && frameCount <= frameEnd)) {
|
|
char buf[16] = { 0 };
|
|
|
|
uint8_t c = keys_apple2ASCII(mbd, NULL);
|
|
if (c <= 0x1F || c >= 0x7F) {
|
|
c = ' ';
|
|
}
|
|
snprintf(buf, sizeof(buf), "%c", c);
|
|
fprintf(video_trace_fp, "%03u %s %04X/0:%02X:%s ", vCount, type, addr, mbd, buf);
|
|
|
|
c = keys_apple2ASCII(aux, NULL);
|
|
if (c <= 0x1F || c >= 0x7F) {
|
|
c = ' ';
|
|
}
|
|
snprintf(buf, sizeof(buf), "%c", c);
|
|
fprintf(video_trace_fp, "/1:%02X:%s (%lu) ", aux, buf, frameCount);
|
|
|
|
vm_printSoftwitches(video_trace_fp, /*output_mem:*/false, /*output_pseudo:*/false);
|
|
fprintf(video_trace_fp, "%s", "\n");
|
|
}
|
|
#endif
|
|
|
|
++hCount;
|
|
if (hCount == CYCLES_SCANLINE) {
|
|
|
|
if (vCount < SCANLINES_VBL_BEGIN) {
|
|
// complete scanline flush ...
|
|
unsigned int scanend = scancol+scanidx;
|
|
assert(scanend == CYCLES_VIS);
|
|
_flushScanline(scanline, /*scanrow:*/vCount, scancol, scanend);
|
|
}
|
|
|
|
// begin new scanline ...
|
|
hCount = 0;
|
|
++vCount;
|
|
|
|
if (vCount == SCANLINES_FRAME) {
|
|
// begin new frame ...
|
|
assert(cyclesFrameLast >= CYCLES_FRAME);
|
|
cyclesFrameLast -= CYCLES_FRAME;
|
|
cycles_video_frame -= CYCLES_FRAME;
|
|
vCount = 0;
|
|
video_clearDirty(A2_DIRTY_FLAG);
|
|
|
|
static uint8_t textFlashCounter = 0;
|
|
textFlashCounter = (textFlashCounter+1) & 0xf;
|
|
if (!textFlashCounter) {
|
|
video_flashText();
|
|
}
|
|
|
|
// TODO FIXME : modularize these (and moar) handlers for video frame completion
|
|
MB_EndOfVideoFrame();
|
|
|
|
// UtAIIe 3-17 :
|
|
// - keyboard auto-repeat ...
|
|
// - power-up reset timing ...
|
|
|
|
currentBackend->frameComplete();
|
|
#if VIDEO_TRACING
|
|
++frameCount;
|
|
#endif
|
|
}
|
|
|
|
scancol = 0;
|
|
scanidx = 0;
|
|
|
|
mode = _currentMode(vCount);
|
|
}
|
|
}
|
|
|
|
if ((scanidx > 0) && (vCount < SCANLINES_VBL_BEGIN)) {
|
|
// incomplete scanline flush ...
|
|
unsigned int scanend = scancol+scanidx;
|
|
_flushScanline(scanline, /*scanrow:*/vCount, scancol, scanend);
|
|
|
|
scancol = scanend;
|
|
scanidx = 0;
|
|
}
|
|
}
|
|
|
|
uint16_t video_scannerAddress(bool *ptrIsVBL) {
|
|
// get video scanner read position
|
|
timing_checkpointCycles();
|
|
unsigned int hCount = cycles_video_frame % CYCLES_SCANLINE;
|
|
unsigned int vCount = (cycles_video_frame / CYCLES_SCANLINE) % SCANLINES_FRAME;
|
|
|
|
if (ptrIsVBL) {
|
|
*ptrIsVBL = (vCount >= SCANLINES_VBL_BEGIN);
|
|
}
|
|
|
|
// AppleWin : Required for ANSI STORY (end credits) vert scrolling mid-scanline mixed mode: DGR80, TEXT80, DGR80
|
|
hCount -= 2;
|
|
if ((int)hCount < 0) {
|
|
hCount += CYCLES_SCANLINE;
|
|
--vCount;
|
|
if ((int)vCount < 0) {
|
|
vCount = SCANLINES_FRAME-1;
|
|
}
|
|
}
|
|
|
|
int page = video_currentPage(run_args.softswitches);
|
|
drawpage_mode_t mode = (vCount < SCANLINES_VIS) ? video_currentMainMode(run_args.softswitches) : video_currentMixedMode(run_args.softswitches);
|
|
uint16_t addr = _getScannerAddress(mode, page, vCount, hCount);
|
|
return addr;
|
|
}
|
|
|
|
uint8_t floating_bus(void) {
|
|
uint16_t scanner_addr = video_scannerAddress(NULL);
|
|
return apple_ii_64k[0][scanner_addr];
|
|
}
|
|
|
|
#if VIDEO_TRACING
|
|
void video_scannerTraceBegin(const char *trace_file, unsigned long count) {
|
|
if (video_trace_fp) {
|
|
video_scannerTraceEnd();
|
|
}
|
|
if (trace_file) {
|
|
video_trace_fp = fopen(trace_file, "w");
|
|
frameCount = 0UL;
|
|
frameBegin = 0UL;
|
|
frameEnd = UINT_MAX;
|
|
if (count > 0) {
|
|
frameBegin = frameCount+1;
|
|
frameEnd = frameCount+count;
|
|
}
|
|
}
|
|
}
|
|
|
|
void video_scannerTraceEnd(void) {
|
|
if (video_trace_fp) {
|
|
fflush(video_trace_fp);
|
|
fclose(video_trace_fp);
|
|
video_trace_fp = NULL;
|
|
frameCount = 0UL;
|
|
frameBegin = 0UL;
|
|
frameEnd = UINT_MAX;
|
|
}
|
|
}
|
|
|
|
void video_scannerTraceCheckpoint(void) {
|
|
if (video_trace_fp) {
|
|
fflush(video_trace_fp);
|
|
}
|
|
}
|
|
|
|
bool video_scannerTraceShouldStop(void) {
|
|
return frameCount > frameEnd;
|
|
}
|
|
#endif
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// state save & restore
|
|
|
|
bool video_saveState(StateHelper_s *helper) {
|
|
bool saved = false;
|
|
int fd = helper->fd;
|
|
|
|
do {
|
|
uint8_t state = 0x0;
|
|
if (!helper->save(fd, &state, 1)) {
|
|
break;
|
|
}
|
|
LOG("SAVE (no-op) video__current_page = %02x", state);
|
|
|
|
saved = true;
|
|
} while (0);
|
|
|
|
return saved;
|
|
}
|
|
|
|
bool video_loadState(StateHelper_s *helper) {
|
|
bool loaded = false;
|
|
int fd = helper->fd;
|
|
|
|
do {
|
|
uint8_t state = 0x0;
|
|
|
|
if (!helper->load(fd, &state, 1)) {
|
|
break;
|
|
}
|
|
LOG("LOAD (no-op) video__current_page = %02x", state);
|
|
|
|
loaded = true;
|
|
} while (0);
|
|
|
|
return loaded;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Video backend registration and selection
|
|
|
|
void video_registerBackend(video_backend_s *backend, long order) {
|
|
assert(!video_initialized); // backends cannot be registered after we've picked one to use
|
|
|
|
backend_node_s *node = MALLOC(sizeof(backend_node_s));
|
|
assert(node);
|
|
node->next = NULL;
|
|
node->order = order;
|
|
node->backend = backend;
|
|
|
|
backend_node_s *p0 = NULL;
|
|
backend_node_s *p = head;
|
|
while (p && (order > p->order)) {
|
|
p0 = p;
|
|
p = p->next;
|
|
}
|
|
if (p0) {
|
|
p0->next = node;
|
|
} else {
|
|
head = node;
|
|
}
|
|
node->next = p;
|
|
|
|
currentBackend = head->backend;
|
|
}
|
|
|
|
void video_printBackends(FILE *out) {
|
|
backend_node_s *p = head;
|
|
int count = 0;
|
|
while (p) {
|
|
const char *name = p->backend->name();
|
|
if (count++) {
|
|
fprintf(out, "|");
|
|
}
|
|
fprintf(out, "%s", name);
|
|
p = p->next;
|
|
}
|
|
}
|
|
|
|
static const char *_null_backend_name(void);
|
|
void video_chooseBackend(const char *name) {
|
|
if (!name) {
|
|
name = _null_backend_name();
|
|
}
|
|
|
|
backend_node_s *p = head;
|
|
while (p) {
|
|
const char *bname = p->backend->name();
|
|
if (strcasecmp(name, bname) == 0) {
|
|
currentBackend = p->backend;
|
|
LOG("Setting current video backend to %s", name);
|
|
break;
|
|
}
|
|
p = p->next;
|
|
}
|
|
}
|
|
|
|
video_animation_s *video_getAnimationDriver(void) {
|
|
return currentBackend->anim;
|
|
}
|
|
|
|
video_backend_s *video_getCurrentBackend(void) {
|
|
return currentBackend;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// NULL video backend ...
|
|
|
|
static const char *_null_backend_name(void) {
|
|
return "none";
|
|
}
|
|
|
|
static void _null_backend_init(void *context) {
|
|
}
|
|
|
|
static void _null_backend_main_loop(void) {
|
|
while (null_backend_running) {
|
|
sleep(1);
|
|
}
|
|
}
|
|
|
|
static void _null_backend_render(void) {
|
|
}
|
|
|
|
static void _null_backend_shutdown(void) {
|
|
null_backend_running = false;
|
|
}
|
|
|
|
#if INTERFACE_CLASSIC
|
|
static void _null_backend_plotChar(const uint8_t col, const uint8_t row, const interface_colorscheme_t cs, const uint8_t c) {
|
|
}
|
|
|
|
static void _null_backend_plotLine(const uint8_t col, const uint8_t row, const interface_colorscheme_t cs, const char *message) {
|
|
}
|
|
#endif
|
|
|
|
static __attribute__((constructor)) void _init_video(void) {
|
|
static video_backend_s null_backend = { 0 };
|
|
null_backend.name = &_null_backend_name;
|
|
null_backend.init = &_null_backend_init;
|
|
null_backend.main_loop = &_null_backend_main_loop;
|
|
null_backend.render = &_null_backend_render;
|
|
null_backend.shutdown = &_null_backend_shutdown;
|
|
|
|
#if INTERFACE_CLASSIC
|
|
null_backend.plotChar = &_null_backend_plotChar;
|
|
null_backend.plotLine = &_null_backend_plotLine;
|
|
#endif
|
|
|
|
// Allow headless testing ...
|
|
null_backend.flashText = &display_flashText;
|
|
null_backend.flushScanline = &display_flushScanline;
|
|
null_backend.frameComplete = &display_frameComplete;
|
|
|
|
static video_animation_s _null_animations = { 0 };
|
|
null_backend.anim = &_null_animations;
|
|
video_registerBackend(&null_backend, VID_PRIO_NULL);
|
|
}
|
|
|