First cut at implementing conformant video scanner

This commit is contained in:
Aaron Culliney 2018-07-29 17:12:23 -07:00
parent 43ab5c8233
commit 75edac3ace
25 changed files with 1723 additions and 470 deletions

View File

@ -3914,6 +3914,7 @@
"MB_TRACING=1",
"SPEAKER_TRACING=1",
"VM_TRACING=1",
"VIDEO_TRACING=1",
"VIDEO_OPENGL=1",
);
HEADER_SEARCH_PATHS = (
@ -3949,6 +3950,7 @@
"MB_TRACING=1",
"SPEAKER_TRACING=1",
"VM_TRACING=1",
"VIDEO_TRACING=1",
"VIDEO_OPENGL=1",
"NDEBUG=1",
);
@ -4681,7 +4683,9 @@
"KEYPAD_JOYSTICK=1",
"TESTING=1",
"TEST_DISPLAY=1",
"VIDEO_TRACING=1",
"VIDEO_OPENGL=1",
"CONFORMANT_TRACKS=1",
);
HEADER_SEARCH_PATHS = (
"$(inherited)",
@ -4710,7 +4714,9 @@
"KEYPAD_JOYSTICK=1",
"TESTING=1",
"TEST_DISPLAY=1",
"VIDEO_TRACING=1",
"VIDEO_OPENGL=1",
"CONFORMANT_TRACKS=1",
"NDEBUG=1",
);
HEADER_SEARCH_PATHS = (

View File

@ -38,7 +38,6 @@ VIDEO_SRC = \
src/video/glhudmodel.c \
src/video/glalert.c \
src/video/ncvideo.c \
src/video/video.c \
src/video/xvideo.c \
src/video_util/matrixUtil.c \
src/video_util/modelUtil.c \
@ -76,6 +75,7 @@ apple2ix_SOURCES = \
src/rom.c \
src/test/sha1.c \
src/timing.c \
src/video/video.c \
src/vm.c \
src/zlib-helpers.c
@ -133,7 +133,7 @@ EXTRA_testdisk_SOURCES = $(ASM_SRC_x86) $(VIDEO_SRC) $(AUDIO_SRC)
#######################################
testdisplay_SOURCES = src/test/testdisplay.c $(A2_TEST_SOURCES)
testdisplay_CFLAGS = $(A2_TEST_CFLAGS) -DTEST_DISPLAY=1
testdisplay_CFLAGS = $(A2_TEST_CFLAGS) -DTEST_DISPLAY=1 -DVIDEO_TRACING=1
testdisplay_CCASFLAGS = $(testdisplay_CFLAGS)
testdisplay_LDFLAGS = $(apple2ix_LDFLAGS)
testdisplay_LDADD = @testdisplay_ASM_O@ @testdisplay_VIDEO_O@ @testdisplay_AUDIO_O@ @X_LIBS@
@ -153,7 +153,7 @@ EXTRA_testprefs_SOURCES = $(ASM_SRC_x86) $(VIDEO_SRC) $(AUDIO_SRC)
#######################################
testtrace_SOURCES = src/test/testtrace.c $(A2_TEST_SOURCES)
testtrace_CFLAGS = $(A2_TEST_CFLAGS) -DTEST_TRACE=1 -DCPU_TRACING=1 -DDISK_TRACING=1 -DVM_TRACING=1 -DSPEAKER_TRACING=1 -DMB_TRACING=1
testtrace_CFLAGS = $(A2_TEST_CFLAGS) -DTEST_TRACE=1 -DCPU_TRACING=1 -DDISK_TRACING=1 -DVM_TRACING=1 -DSPEAKER_TRACING=1 -DMB_TRACING=1 -DVIDEO_TRACING=1
testtrace_CCASFLAGS = $(testtrace_CFLAGS)
testtrace_LDFLAGS = $(apple2ix_LDFLAGS)
testtrace_LDADD = @testtrace_ASM_O@ @testtrace_VIDEO_O@ @testtrace_AUDIO_O@ @X_LIBS@

View File

@ -148,14 +148,14 @@ AC_PATH_XTRA
video_output="VIDEO RENDERERS:"
video_output_disabled="VIDEO RENDERERS (DISABLED):"
VIDEO_O="src/video/video.o"
testcpu_VIDEO_O="src/video/video.o"
testdisk_VIDEO_O="src/video/video.o"
testdisplay_VIDEO_O="src/video/video.o"
testprefs_VIDEO_O="src/video/video.o"
testtrace_VIDEO_O="src/video/video.o"
testui_VIDEO_O="src/video/video.o"
testvm_VIDEO_O="src/video/video.o"
VIDEO_O=""
testcpu_VIDEO_O=""
testdisk_VIDEO_O=""
testdisplay_VIDEO_O=""
testprefs_VIDEO_O=""
testtrace_VIDEO_O=""
testui_VIDEO_O=""
testvm_VIDEO_O=""
AC_ARG_ENABLE([opengl], AS_HELP_STRING([--enable-opengl], [Enable OpenGL graphics output (autodetected)]))
AS_IF([test "x$enable_opengl" != "xno"], [

View File

@ -621,13 +621,13 @@ static __attribute__((constructor)) void __init_cpu65(void) {
run_args.reset_vector = 0xFFFC;
#if CPU_TRACING
extern void (*cpu65_trace_prologue)(uint16_t, uint8_t);
extern void cpu65_trace_prologue(uint16_t, uint8_t);
run_args.cpu65_trace_prologue = cpu65_trace_prologue;
extern void (*cpu65_trace_arg)(uint16_t, uint8_t);
extern void cpu65_trace_arg(uint16_t, uint8_t);
run_args.cpu65_trace_arg = cpu65_trace_arg;
extern void (*cpu65_trace_epilogue)(uint16_t, uint8_t);
extern void cpu65_trace_epilogue(uint16_t, uint8_t);
run_args.cpu65_trace_epilogue = cpu65_trace_epilogue;
extern void (*cpu65_trace_irq)(uint16_t, uint8_t);
extern void cpu65_trace_irq(uint16_t, uint8_t);
run_args.cpu65_trace_irq = cpu65_trace_irq;
#endif
@ -826,7 +826,7 @@ GLUE_C_WRITE(cpu65_trace_epilogue)
case addr_absolute_y:
case addr_j_indirect:
case addr_j_indirect_x:
fprintf(cpu_trace_fp, "%04X:%02X%02X%02X", current_pc, run_args.cpu65_opcode, (uint8_t)arg2, (uint8_t)arg1);
fprintf(cpu_trace_fp, "%04X:%02X%02X%02X", current_pc, run_args.cpu65_opcode, (uint8_t)arg1, (uint8_t)arg2);
break;
default:
fprintf(cpu_trace_fp, "invalid opcode mode");
@ -867,11 +867,21 @@ GLUE_C_WRITE(cpu65_trace_epilogue)
char fmt[64];
if (UNLIKELY(run_args.cpu65_opcycles >= 10)) {
// occurs rarely for interrupt + opcode
snprintf(fmt, 64, "%s", " %s CY:%u totCyc:%d EA:%04X");
snprintf(fmt, 64, "%s", " %s CY:%u");
} else {
snprintf(fmt, 64, "%s", " %s CYC:%u totCyc:%d EA:%04X");
snprintf(fmt, 64, "%s", " %s CYC:%u");
}
fprintf(cpu_trace_fp, fmt, flags_buf, run_args.cpu65_opcycles, (cycles_count_total + run_args.cpu65_opcycles), run_args.cpu65_ea);
fprintf(cpu_trace_fp, fmt, flags_buf, run_args.cpu65_opcycles);
uint16_t vidAddr = video_scannerAddress(NULL);
uint8_t vidData = apple_ii_64k[0][vidAddr];
fprintf(cpu_trace_fp, " VID:%04X:%02X", vidAddr, vidData);
#if CPU_TRACING_SHOW_EA
fprintf(cpu_trace_fp, " EA:%04X", run_args.cpu65_ea);
#endif
fprintf(cpu_trace_fp, " CY+%lu", (cycles_count_total + run_args.cpu65_opcycles));
sprintf(fmt, " %s %s", opcodes_65c02[run_args.cpu65_opcode].mnemonic, disasm_templates[opcodes_65c02[run_args.cpu65_opcode].mode]);
@ -894,7 +904,7 @@ GLUE_C_WRITE(cpu65_trace_epilogue)
case addr_absolute_y:
case addr_j_indirect:
case addr_j_indirect_x:
fprintf(cpu_trace_fp, fmt, (uint8_t)arg1, (uint8_t)arg2);
fprintf(cpu_trace_fp, fmt, (uint8_t)arg2, (uint8_t)arg1);
break;
case addr_relative:
if (arg1 < 0) {

View File

@ -16,22 +16,12 @@
#include "common.h"
#include "video/video.h"
#if __clang__
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wunused-variable"
#elif __GNUC__
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wunused-variable"
# pragma GCC diagnostic ignored "-Wunused-const-variable"
#endif
#define SCANSTEP (SCANWIDTH-12)
#define SCANDSTEP (SCANWIDTH-6)
#define DYNAMIC_SZ 11 // 7 pixels (as bytes) + 2pre + 2post
A2Color_s colormap[256] = { { 0 } };
static pthread_mutex_t display_scan_mutex = PTHREAD_MUTEX_INITIALIZER;
static uint8_t video__wider_font[0x8000] = { 0 };
static uint8_t video__font[0x4000] = { 0 };
@ -46,8 +36,17 @@ static uint8_t video__columns[8192] = { 0 };
static uint8_t video__hires_even[0x800] = { 0 };
static uint8_t video__hires_odd[0x800] = { 0 };
static volatile unsigned long _vid_dirty = 0;
#define FB_SIZ (SCANWIDTH*SCANHEIGHT*sizeof(uint8_t))
#if INTERFACE_CLASSIC
static uint8_t fbInterface[FB_SIZ] = { 0 };
#endif
static uint8_t fbStaging[FB_SIZ] = { 0 };
#define FB_BASE (&fbStaging[0])
#if 0
// FIXME TODO REMOVE THESE ...
// Video constants -- sourced from AppleWin
static const bool bVideoScannerNTSC = true;
static const int kHBurstClock = 53; // clock when Color Burst starts
@ -65,20 +64,18 @@ static const int kPALVSyncLine = 264; // line when VSync starts (PAL)
static const int kVLine0State = 0x100; // V[543210CBA] = 100000000
static const int kVPresetLine = 256; // line when V state presets
static const int kVSyncLines = 4; // lines per VSync duration
#endif
static uint8_t video__odd_colors[2] = { COLOR_LIGHT_PURPLE, COLOR_LIGHT_BLUE };
static uint8_t video__even_colors[2] = { COLOR_LIGHT_GREEN, COLOR_LIGHT_RED };
// TODO FIXME : support linked list of callbacks here ...
static display_update_fn textCallbackFn = NULL;
static display_update_fn hiresCallbackFn = NULL;
static display_update_fn modeCallbackFn = NULL;
// 40col/80col/lores/hires/dhires line offsets
static uint16_t video__line_offset[TEXT_ROWS] = {
0x000, 0x080, 0x100, 0x180, 0x200, 0x280, 0x300, 0x380,
0x028, 0x0A8, 0x128, 0x1A8, 0x228, 0x2A8, 0x328, 0x3A8,
0x050, 0x0D0, 0x150, 0x1D0, 0x250, 0x2D0, 0x350, 0x3D0
// video line offsets
static uint16_t video__line_offset[TEXT_ROWS + /*VBL:*/ 8 + /*extra:*/1] = {
0x000, 0x080, 0x100, 0x180, 0x200, 0x280, 0x300, 0x380,
0x028, 0x0A8, 0x128, 0x1A8, 0x228, 0x2A8, 0x328, 0x3A8,
0x050, 0x0D0, 0x150, 0x1D0, 0x250, 0x2D0, 0x350, 0x3D0,
0x078, 0x0F8, 0x178, 0x1F8, 0x278, 0x2F8, 0x378, 0x3F8,
0x3F8
};
static uint8_t video__dhires1[256] = {
@ -332,6 +329,7 @@ static void _initialize_row_col_tables(void) {
}
}
#warning FIXME TODO : move _initialize_tables_video() to vm.c ...
static void _initialize_tables_video(void) {
// initialize text/lores & hires graphics routines
for (unsigned int y = 0; y < TEXT_ROWS; y++) {
@ -449,7 +447,7 @@ static void _initialize_color() {
#endif
}
static void video_prefsChanged(const char *domain) {
static void display_prefsChanged(const char *domain) {
long val = COLOR_INTERP;
prefs_parseLongValue(domain, PREF_COLOR_MODE, &val, /*base:*/10);
if (val < 0) {
@ -465,7 +463,6 @@ static void video_prefsChanged(const char *domain) {
void display_reset(void) {
_initialize_hires_values();
_initialize_tables_video();
video_setDirty(A2_DIRTY_FLAG);
}
void display_loadFont(const uint8_t first, const uint8_t quantity, const uint8_t *data, font_mode_t mode) {
@ -605,6 +602,7 @@ static inline void _plot_lores80(uint8_t **d, const uint32_t val) {
*((uint8_t *)(*d)) = (uint8_t)val;
}
#if 0
static inline void __plot_character40(const unsigned int font_off, uint8_t *fb_ptr) {
uint8_t *font_ptr = video__wider_font+font_off;
_plot_char40(/*dst*/&fb_ptr, /*src*/&font_ptr);
@ -628,7 +626,32 @@ static void _plot_character40(uint8_t col, uint8_t row, int page, int bank, uint
textCallbackFn((pixel_delta_t){ .row = row, .col = col, .b = b, .cs = INVALID_COLORSCHEME });
}
}
#endif
static void _plot_char40_scanline(scan_data_t *scandata) {
uint8_t *scanline = scandata->scanline;
unsigned int scanrow = scandata->scanrow;
unsigned int scancol = scandata->scancol;
unsigned int scanend = scandata->scanend;
uint16_t fb_base = video__line_offset[scanrow>>3];
uint16_t glyph_off = (scanrow&0x7);
for (unsigned int col = scancol; col < scanend; col++)
{
uint16_t fb_off = fb_base + col;
uint8_t b = scanline[(col<<1)+1]; // MBD data only
unsigned int glyph_base = (b<<7); // *128
uint8_t *fb_ptr = FB_BASE + video__screen_addresses[fb_off] + ((glyph_off<<1) * SCANWIDTH);
uint8_t *font_ptr = video__wider_font + glyph_base + (glyph_off<<4);
_plot_char40(/*dst:*/&fb_ptr, /*src:*/&font_ptr);
}
}
#if 0
static inline void __plot_character80(const unsigned int font_off, uint8_t *fb_ptr) {
uint8_t *font_ptr = video__font+font_off;
_plot_char80(/*dst*/&fb_ptr, /*src*/&font_ptr, SCANWIDTH);
@ -661,7 +684,33 @@ static void _plot_character80(uint8_t col, uint8_t row, int page, int bank, uint
}
}
}
#endif
static void _plot_char80_scanline(scan_data_t *scandata) {
uint8_t *scanline = scandata->scanline;
unsigned int scanrow = scandata->scanrow;
unsigned int scancol = scandata->scancol;
unsigned int scanend = scandata->scanend;
uint16_t fb_base = video__line_offset[scanrow>>3];
uint16_t glyph_off = (scanrow&0x7);
for (unsigned int col = scancol; col < scanend; col++)
{
uint16_t fb_off = fb_base + col;
for (unsigned int x=0; x<2; x++) {
uint8_t b = scanline[(col<<1)+x]; // AUX, MBD
unsigned int glyph_base = (b<<6); // *64
uint8_t *fb_ptr = FB_BASE + video__screen_addresses[fb_off] + (7*x) + ((glyph_off<<1) * SCANWIDTH);
uint8_t *font_ptr = video__font + glyph_base + (glyph_off<<3);
_plot_char80(/*dst:*/&fb_ptr, /*src:*/&font_ptr, SCANWIDTH);
}
}
}
#if 0
static inline void __plot_block40(const uint8_t val, uint8_t col, uint8_t *fb_ptr) {
uint8_t color = (val & 0x0F) << 4;
@ -724,7 +773,48 @@ static void _plot_block40(uint8_t col, uint8_t row, int page, int bank, uint8_t
textCallbackFn((pixel_delta_t){ .row = row, .col = col, .b = b, .cs = COLOR16 });
}
}
#endif
static void _plot_lores40_scanline(scan_data_t *scandata) {
uint8_t *scanline = scandata->scanline;
unsigned int scanrow = scandata->scanrow;
unsigned int scancol = scandata->scancol;
unsigned int scanend = scandata->scanend;
uint16_t fb_base = video__line_offset[scanrow>>3];
uint16_t block_off = (scanrow&0x7);
uint8_t hi_nyb = !!(block_off>>2); // 0,1,2,3 => 0 4,5,6,7 => 1
uint8_t lores_mask = (0x0f << (hi_nyb<<2) ); // 0x0f --or-- 0xf0
uint8_t lores_shift = ((1-hi_nyb)<<2); // -> 0xi0
for (unsigned int col = scancol; col < scanend; col++)
{
uint16_t fb_off = fb_base + col;
uint8_t *fb_ptr = FB_BASE + video__screen_addresses[fb_off] + ((block_off<<1) * SCANWIDTH);
uint8_t b = scanline[(col<<1)+1]; // MBD data only
uint8_t val = (b & lores_mask) << lores_shift;
uint32_t val32;
if (color_mode == COLOR_NONE) {
if (val != 0x0 && val != 0xf) {
uint8_t rot2 = ((col % 2) << 1); // 2 phases at double rotation
val = (val << rot2) | ((val & 0xC0) >> rot2);
}
val32 = ((val & 0x10) ? COLOR_LIGHT_WHITE : COLOR_BLACK) << 0;
val32 |= ((val & 0x20) ? COLOR_LIGHT_WHITE : COLOR_BLACK) << 8;
val32 |= ((val & 0x40) ? COLOR_LIGHT_WHITE : COLOR_BLACK) << 16;
val32 |= ((val & 0x80) ? COLOR_LIGHT_WHITE : COLOR_BLACK) << 24;
} else {
val32 = (val << 24) | (val << 16) | (val << 8) | val;
}
_plot_lores40(/*dst:*/&fb_ptr, val32);
}
}
#if 0
static inline void __plot_block80(const uint8_t val, uint8_t *fb_ptr) {
uint8_t color = (val & 0x0F) << 4;
uint32_t val32 = (color << 24) | (color << 16) | (color << 8) | color;
@ -749,7 +839,9 @@ static inline void __plot_block80(const uint8_t val, uint8_t *fb_ptr) {
fb_ptr += SCANDSTEP;
_plot_lores80(/*dst*/&fb_ptr, val32);
}
#endif
// HACK FIXME TODO ... is this correct? MONOCOLOR output appears wrong...
static inline uint8_t __shift_block80(uint8_t b) {
// plot even half-block from auxmem, rotate nybbles to match color (according to UTAIIe: 8-29)
uint8_t b0 = (b & 0x0F);
@ -762,6 +854,7 @@ static inline uint8_t __shift_block80(uint8_t b) {
return b;
}
#if 0
static void _plot_block80(uint8_t col, uint8_t row, int page, int bank, uint8_t *fb_ptr) {
assert(bank == 0); // FIXME TODO ...
uint16_t base = page ? 0x0800 : 0x0400;
@ -791,16 +884,58 @@ static void _plot_block80(uint8_t col, uint8_t row, int page, int bank, uint8_t
}
}
}
#endif
static void (*_textpage_plotter(uint32_t currswitches, uint32_t txtflags))(uint8_t, uint8_t, int, int, uint8_t*) {
void (*plotFn)(uint8_t, uint8_t, int, int, uint8_t*) = NULL;
static void _plot_lores80_scanline(scan_data_t *scandata) {
uint8_t *scanline = scandata->scanline;
unsigned int scanrow = scandata->scanrow;
unsigned int scancol = scandata->scancol;
unsigned int scanend = scandata->scanend;
uint16_t fb_base = video__line_offset[scanrow>>3];
uint16_t block_off = (scanrow&0x7);
uint8_t hi_nyb = !!(block_off>>2); // 0,1,2,3 => 0 4,5,6,7 => 1
uint8_t lores_mask = (0x0f << (hi_nyb<<2) ); // 0x0f --or-- 0xf0
uint8_t lores_shift = ((1-hi_nyb)<<2); // -> 0xi0
for (unsigned int col = scancol; col < scanend; col++)
{
uint16_t fb_off = fb_base + col;
{
uint8_t *fb_ptr = FB_BASE + video__screen_addresses[fb_off] + ((block_off<<1) * SCANWIDTH);
uint8_t b = scanline[(col<<1)+0]; // AUX
b = __shift_block80(b);
uint8_t val = (b & lores_mask) << lores_shift;
// TODO FIXME : implement monocolor ...
uint32_t val32 = (val << 24) | (val << 16) | (val << 8) | val;
_plot_lores80(/*dst:*/&fb_ptr, val32);
}
{
uint8_t *fb_ptr = FB_BASE + video__screen_addresses[fb_off] + 7 + ((block_off<<1) * SCANWIDTH);
uint8_t b = scanline[(col<<1)+1]; // MBD
uint8_t val = (b & lores_mask) << lores_shift;
// TODO FIXME : implement monocolor ...
uint32_t val32 = (val << 24) | (val << 16) | (val << 8) | val;
_plot_lores80(/*dst:*/&fb_ptr, val32);
}
}
}
static void (*_textpage_plotter(uint32_t currswitches, uint32_t txtflags))(scan_data_t*) {
void (*plotFn)(scan_data_t*) = NULL;
if (currswitches & txtflags) {
plotFn = (currswitches & SS_80COL) ? _plot_character80 : _plot_character40;
plotFn = (currswitches & SS_80COL) ? _plot_char80_scanline : _plot_char40_scanline;
} else {
assert(!(currswitches & SS_HIRES) && "must be lores graphics or programmer error");
if (!(currswitches & SS_80COL)) {
plotFn = _plot_block40;
plotFn = _plot_lores40_scanline;
if (!(currswitches & SS_DHIRES)) {
// LORES40 ...
} else {
@ -809,11 +944,11 @@ static void (*_textpage_plotter(uint32_t currswitches, uint32_t txtflags))(uint8
} else {
if (currswitches & SS_DHIRES) {
// LORES80 ...
plotFn = _plot_block80;
plotFn = _plot_lores80_scanline;
} else {
/* ??? */
LOG("!!!!!!!!!!!! what mode is this? !!!!!!!!!!!!");
plotFn = _plot_block40;
//LOG("!!!!!!!!!!!! what mode is this? !!!!!!!!!!!!");
plotFn = _plot_lores40_scanline;
#warning FIXME TODO ... verify this lores40/lores80 mode ...
}
}
@ -822,82 +957,6 @@ static void (*_textpage_plotter(uint32_t currswitches, uint32_t txtflags))(uint8
return plotFn;
}
drawpage_mode_t display_currentDrawpageMode(uint32_t currswitches) {
if (currswitches & SS_TEXT) {
return DRAWPAGE_TEXT;
} else {
if (currswitches & SS_HIRES) {
return DRAWPAGE_HIRES;
} else {
return DRAWPAGE_TEXT; // (LORES)
}
}
}
static inline drawpage_mode_t _currentMainMode(uint32_t currswitches) {
return display_currentDrawpageMode(currswitches);
}
static inline drawpage_mode_t _currentMixedMode(uint32_t currswitches) {
if (currswitches & (SS_TEXT|SS_MIXED)) {
return DRAWPAGE_TEXT;
} else {
if (currswitches & SS_HIRES) {
return DRAWPAGE_HIRES;
} else {
return DRAWPAGE_TEXT; // (LORES)
}
}
}
GLUE_C_WRITE(video__write_2e_text0)
{
run_args.base_textwrt[ea] = b;
drawpage_mode_t mode = _currentMainMode(run_args.softswitches);
if (mode == DRAWPAGE_HIRES) {
return;
}
if (!(run_args.softswitches & SS_PAGE2)) {
video_setDirty(A2_DIRTY_FLAG);
}
}
GLUE_C_WRITE(video__write_2e_text0_mixed)
{
run_args.base_textwrt[ea] = b;
drawpage_mode_t mode = _currentMixedMode(run_args.softswitches);
if (mode == DRAWPAGE_HIRES) {
return;
}
if (!(run_args.softswitches & SS_PAGE2)) {
video_setDirty(A2_DIRTY_FLAG);
}
}
GLUE_C_WRITE(video__write_2e_text1)
{
run_args.base_ramwrt[ea] = b;
drawpage_mode_t mode = _currentMainMode(run_args.softswitches);
if (mode == DRAWPAGE_HIRES) {
return;
}
if (run_args.softswitches & SS_PAGE2) {
video_setDirty(A2_DIRTY_FLAG);
}
}
GLUE_C_WRITE(video__write_2e_text1_mixed)
{
run_args.base_ramwrt[ea] = b;
drawpage_mode_t mode = _currentMixedMode(run_args.softswitches);
if (mode == DRAWPAGE_HIRES) {
return;
}
if (run_args.softswitches & SS_PAGE2) {
video_setDirty(A2_DIRTY_FLAG);
}
}
// ----------------------------------------------------------------------------
// Classic interface and printing HUD messages
@ -913,40 +972,30 @@ static void _display_plotChar(uint8_t *fboff, const unsigned int fbPixWidth, con
_plot_char80(&fboff, &src, fbPixWidth);
}
void display_plotChar(uint8_t *fb, const uint8_t col, const uint8_t row, const interface_colorscheme_t cs, const uint8_t c) {
#if INTERFACE_CLASSIC
void display_plotChar(const uint8_t col, const uint8_t row, const interface_colorscheme_t cs, const uint8_t c) {
assert(col < 80);
assert(row < 24);
if (textCallbackFn) {
textCallbackFn((pixel_delta_t){ .row = row, .col = col, .b = c, .cs = cs });
}
if (fb) {
unsigned int off = row * SCANWIDTH * FONT_HEIGHT_PIXELS + col * FONT80_WIDTH_PIXELS + _INTERPOLATED_PIXEL_ADJUSTMENT_PRE;
_display_plotChar(fb+off, SCANWIDTH, cs, c);
video_setDirty(FB_DIRTY_FLAG);
}
unsigned int off = row * SCANWIDTH * FONT_HEIGHT_PIXELS + col * FONT80_WIDTH_PIXELS + _INTERPOLATED_PIXEL_ADJUSTMENT_PRE;
_display_plotChar(fbInterface+off, SCANWIDTH, cs, c);
video_setDirty(FB_DIRTY_FLAG);
}
#endif
static void _display_plotLine(uint8_t *fb, const unsigned int fbPixWidth, const unsigned int xAdjust, const uint8_t col, const uint8_t row, const interface_colorscheme_t cs, const char *line) {
for (uint8_t x=col; *line; x++, line++) {
char c = *line;
if (textCallbackFn) {
textCallbackFn((pixel_delta_t){ .row = row, .col = x, .b = c, .cs = cs });
}
if (fb) {
unsigned int off = row * fbPixWidth * FONT_HEIGHT_PIXELS + x * FONT80_WIDTH_PIXELS + xAdjust;
_display_plotChar(fb+off, fbPixWidth, cs, c);
}
unsigned int off = row * fbPixWidth * FONT_HEIGHT_PIXELS + x * FONT80_WIDTH_PIXELS + xAdjust;
_display_plotChar(fb+off, fbPixWidth, cs, c);
}
}
void display_plotLine(uint8_t *fb, const uint8_t col, const uint8_t row, const interface_colorscheme_t cs, const char *message) {
_display_plotLine(fb, /*fbPixWidth:*/SCANWIDTH, /*xAdjust:*/_INTERPOLATED_PIXEL_ADJUSTMENT_PRE, col, row, cs, message);
#if INTERFACE_CLASSIC
void display_plotLine(const uint8_t col, const uint8_t row, const interface_colorscheme_t cs, const char *message) {
_display_plotLine(fbInterface, /*fbPixWidth:*/SCANWIDTH, /*xAdjust:*/_INTERPOLATED_PIXEL_ADJUSTMENT_PRE, col, row, cs, message);
video_setDirty(FB_DIRTY_FLAG);
}
#endif
void display_plotMessage(uint8_t *fb, const interface_colorscheme_t cs, const char *message, const uint8_t message_cols, const uint8_t message_rows) {
assert(message_cols < 80);
@ -962,24 +1011,29 @@ void display_plotMessage(uint8_t *fb, const interface_colorscheme_t cs, const ch
// Double-HIRES (HIRES80) graphics
static inline void __plot_hires80_pixels(uint8_t idx, uint8_t *fb_ptr) {
if (color_mode == COLOR_NONE) {
uint32_t b;
b = (idx & 0x1) ? COLOR_LIGHT_WHITE : COLOR_BLACK;
b |= ((idx & 0x2) ? COLOR_LIGHT_WHITE : COLOR_BLACK) << 8;
b |= ((idx & 0x4) ? COLOR_LIGHT_WHITE : COLOR_BLACK) << 16;
b |= ((idx & 0x8) ? COLOR_LIGHT_WHITE : COLOR_BLACK) << 24;
*((uint32_t *)fb_ptr) = b;
*((uint32_t *)(fb_ptr+SCANWIDTH)) = b;
return;
}
uint8_t bCurr = idx;
uint8_t b1 = video__dhires1[idx];
uint8_t b2 = video__dhires2[idx];
uint32_t b = (b2<<24) | (b1<<8);
*((uint32_t *)fb_ptr) = b;
*((uint32_t *)(fb_ptr+SCANWIDTH)) = b;
if (color_mode == COLOR_NONE) {
uint32_t b32;
b32 = (bCurr & 0x1) ? COLOR_LIGHT_WHITE : COLOR_BLACK;
b32 |= ((bCurr & 0x2) ? COLOR_LIGHT_WHITE : COLOR_BLACK) << 8;
b32 |= ((bCurr & 0x4) ? COLOR_LIGHT_WHITE : COLOR_BLACK) << 16;
b32 |= ((bCurr & 0x8) ? COLOR_LIGHT_WHITE : COLOR_BLACK) << 24;
*((uint32_t *)fb_ptr) = b32;
*((uint32_t *)(fb_ptr+SCANWIDTH)) = b32;
} else {
// TODO FIXME : handle interpolated here ...
uint32_t b32;
b32 = (bCurr & 0x1) ? bCurr : COLOR_BLACK;
b32 |= ((bCurr & 0x2) ? bCurr : COLOR_BLACK) << 8;
b32 |= ((bCurr & 0x4) ? bCurr : COLOR_BLACK) << 16;
b32 |= ((bCurr & 0x8) ? bCurr : COLOR_BLACK) << 24;
*((uint32_t *)fb_ptr) = b32;
*((uint32_t *)(fb_ptr+SCANWIDTH)) = b32;
}
}
#if 0
static inline void __plot_hires80(uint16_t base, uint16_t ea, uint8_t *fb_ptr) {
ea &= ~0x1;
@ -1055,11 +1109,87 @@ static void _plot_hires80(uint16_t off, int page, int bank, bool is_even, uint8_
uint16_t ea = base+off;
__plot_hires80(base, ea, fb_ptr);
}
#endif
static void _plot_hires80_scanline(scan_data_t *scandata) {
uint8_t *scanline = scandata->scanline;
unsigned int scanrow = scandata->scanrow;
unsigned int scancol = scandata->scancol;
unsigned int scanend = scandata->scanend;
uint16_t fb_base = video__line_offset[scanrow>>3] + (0x400 * (scanrow & 0x7));
// |AUX 0a|MBD 0b |AUX 1c |MBD 1d |AUX 2e |MBD 2f |AUX 3g |MBD 3h ...
// aaaaaaab bbbbbbcc cccccddd ddddeeee eeefffff ffgggggg ghhhhhhh ...
// 01234560 12345601 23456012 34560123 45601234 56012345 60123456
for (unsigned int col = scancol; col < scanend; col++)
{
uint16_t fb_off = fb_base + col;
uint8_t idx = (col<<1);
uint8_t is_odd = (col & 0x1);
uint8_t *fb_ptr = FB_BASE + video__screen_addresses[fb_off];
uint8_t aux = scanline[idx]; // AUX
uint8_t mbd = scanline[idx+1]; // MBD
if (!is_odd) {
// idx = 0 -> aaaa aaab bbbb 0000
// idx = 2 -> eeee eeef ffff 0000
uint8_t auxLO = (aux & (0x0F<<0)) >> 0; // 0000aaaa -> 0000aaaa
__plot_hires80_pixels(auxLO, fb_ptr);
fb_ptr += 4;
uint8_t auxHI = (aux & (0x07<<4)) >> 4; // 0aaa0000 -> 00000aaa
uint8_t mbdLO = (mbd & (0x01<<0)) << 3; // 0000000b -> 0000b000
__plot_hires80_pixels((mbdLO | auxHI), fb_ptr);
fb_ptr += 4;
uint8_t mbdXX = (mbd & (0x0F<<1)) >> 1; // 000bbbb0 -> 0000bbbb
__plot_hires80_pixels(mbdXX, fb_ptr);
fb_ptr += 4;
/*
// partial ... overwritten by next even scan ...
uint8_t mbdHI = (mbd & (0x03<<5)) >> 5; // 0bb00000 -> 0000XXbb
__plot_hires80_pixels(mbdHI, fb_ptr);
*/
} else {
// idx = 1 -> bbcc cccc cddd dddd
// idx = 3 -> ffgg gggg ghhh hhhh
fb_ptr -= 2;
uint8_t mb0 = scanline[idx-1]; // MBD-1
uint8_t mbdHI = (mb0 & (0x03<<5)) >> 5; // 0bb00000 -> 000000bb
uint8_t auxLO = (aux & (0x03<<0)) << 2; // 000000cc -> 0000cc00
__plot_hires80_pixels((auxLO | mbdHI), fb_ptr);
fb_ptr += 4;
uint8_t auxXX = (aux & (0x0F<<2)) >> 2; // 00cccc00 -> 0000cccc
__plot_hires80_pixels(auxXX, fb_ptr);
fb_ptr += 4;
uint8_t auxHI = (aux & (0x01<<6)) >> 6; // 0c000000 -> 0000000c
uint8_t mbdLO = (mbd & (0x07<<0)) << 1; // 00000ddd -> 0000ddd0
__plot_hires80_pixels((mbdLO | auxHI), fb_ptr);
fb_ptr += 4;
uint8_t mbdXX = (mbd & (0x0F<<3)) >> 3; // 0dddd000 -> 0000dddd
__plot_hires80_pixels(mbdXX, fb_ptr);
}
}
}
// ----------------------------------------------------------------------------
// Hires GRaphics
// Hires GRaphics (HIRES40)
static inline void _calculate_interp_color(uint8_t *color_buf, const unsigned int idx, const uint8_t *interp_base, const uint16_t ea) {
static inline void _calculate_interp_color(uint8_t *color_buf, const unsigned int idx, const uint8_t *interp_base, uint8_t b) {
if (color_buf[idx] != 0x0) {
return;
}
@ -1075,7 +1205,7 @@ static inline void _calculate_interp_color(uint8_t *color_buf, const unsigned in
// Calculates the color at the edge of interpolated bytes: called 4 times in little endian order (...7 0...7 0...)
if (pixL == COLOR_LIGHT_WHITE) {
if (pixR == COLOR_LIGHT_WHITE) {
pixL = apple_ii_64k[0][ea];
pixL = b;
color_buf[idx] = interp_base[pixL>>7];
} else {
color_buf[idx] = pixR;
@ -1100,6 +1230,7 @@ static inline void _plot_hires_pixels(uint8_t *dst, const uint8_t *src) {
}
}
#if 0
static void _plot_hires40(uint16_t off, int page, int bank, bool is_even, uint8_t *fb_ptr) {
assert(bank == 0); // FIXME TODO ...
uint16_t base = page ? 0x4000 : 0x2000;
@ -1174,11 +1305,107 @@ static void _plot_hires40(uint16_t off, int page, int bank, bool is_even, uint8_
_plot_hires_pixels(/*dst:*/fb_ptr-4, /*src:*/color_buf);
}
#endif
static void (*_hirespage_plotter(uint32_t currswitches))(uint16_t, int, int, bool, uint8_t*) {
return ((currswitches & SS_80COL) && (currswitches & SS_DHIRES)) ? _plot_hires80 : _plot_hires40;
static void _plot_hires40_scanline(scan_data_t *scandata) {
// FIXME TODO ... this can be further streamlined to keep track of previous data
uint8_t *scanline = scandata->scanline;
unsigned int scanrow = scandata->scanrow;
unsigned int scancol = scandata->scancol;
unsigned int scanend = scandata->scanend;
assert(scancol < scanend);
assert(scanend > 0);
uint16_t fb_base = video__line_offset[scanrow>>3] + (0x400 * (scanrow & 0x7));
for (unsigned int col = scancol; col < scanend; col++)
{
uint16_t fb_off = fb_base + col;
uint8_t *fb_ptr = FB_BASE + video__screen_addresses[fb_off];
bool is_even = !(col & 0x1);
uint8_t idx = (col<<1)+1; // MBD data only
uint8_t b = scanline[idx];
uint8_t _buf[DYNAMIC_SZ] = { 0 };
uint8_t *color_buf = (uint8_t *)_buf; // <--- work around for -Wstrict-aliasing
uint8_t *hires_ptr = NULL;
if (is_even) {
hires_ptr = (uint8_t *)&video__hires_even[b<<3];
} else {
hires_ptr = (uint8_t *)&video__hires_odd[b<<3];
}
*((uint32_t *)&color_buf[2]) = *((uint32_t *)(hires_ptr+0));
*((uint16_t *)&color_buf[6]) = *((uint16_t *)(hires_ptr+4));
*((uint8_t *)&color_buf[8]) = *((uint8_t *)(hires_ptr+6));
hires_ptr = NULL;
// copy adjacent pixel bytes
*((uint16_t *)&color_buf[0]) = *((uint16_t *)(fb_ptr-3));
*((uint16_t *)&color_buf[DYNAMIC_SZ-2]) = *((uint16_t *)(fb_ptr+15));
if (color_mode != COLOR_NONE) {
uint8_t *hires_altbase = NULL;
if (is_even) {
hires_altbase = (uint8_t *)&video__hires_odd[0];
} else {
hires_altbase = (uint8_t *)&video__hires_even[0];
}
// if right-side color is not black, re-calculate edge values
if (color_buf[DYNAMIC_SZ-2] & 0xff) {
if ((col < CYCLES_VIS-1) && (col < scanend - 1)) {
uint8_t bNext = scanline[idx+2];
if ((b & 0x40) && (bNext & 0x1)) {
*((uint16_t *)&color_buf[DYNAMIC_SZ-3]) = (uint16_t)0x3737;// COLOR_LIGHT_WHITE
}
}
}
// if left-side color is not black, re-calculate edge values
if (color_buf[1] & 0xff) {
if (col > 0) {
uint8_t bPrev = scanline[idx-2];
if ((bPrev & 0x40) && (b & 0x1)) {
*((uint16_t *)&color_buf[1]) = (uint16_t)0x3737;// COLOR_LIGHT_WHITE
}
}
}
if (color_mode == COLOR_INTERP) {
uint8_t *interp_base = NULL;
uint8_t *interp_altbase = NULL;
if (is_even) {
interp_base = (uint8_t *)&video__even_colors[0];
interp_altbase = (uint8_t *)&video__odd_colors[0];
} else {
interp_base = (uint8_t *)&video__odd_colors[0];
interp_altbase = (uint8_t *)&video__even_colors[0];
}
// calculate interpolated/bleed colors
uint8_t bl = 0x0;
if (col > 0) {
bl = scanline[idx-2];
}
_calculate_interp_color(color_buf, 1, interp_altbase, bl);
_calculate_interp_color(color_buf, 2, interp_base, b);
_calculate_interp_color(color_buf, 8, interp_base, b);
if (col < CYCLES_VIS-1) {
bl = scanline[idx+2];
}
_calculate_interp_color(color_buf, 9, interp_altbase, bl);
}
}
_plot_hires_pixels(/*dst:*/fb_ptr-4, /*src:*/color_buf);
////fb_ptr += 7;
}
}
#if 0
GLUE_C_WRITE(video__write_2e_hgr0)
{
run_args.base_hgrwrt[ea] = b;
@ -1257,105 +1484,145 @@ void display_setUpdateCallback(drawpage_mode_t mode, display_update_fn updateFn)
assert(false);
}
}
#endif
void display_renderStagingFramebuffer(uint8_t *stagingFB) {
static void (*_hirespage_plotter(uint32_t currswitches))(scan_data_t*) {
return ((currswitches & SS_80COL) && (currswitches & SS_DHIRES)) ? _plot_hires80_scanline : _plot_hires40_scanline;
}
#warning FIXME TODO ... this needs to scan memory in the same way as the actually //e video scanner
uint8_t *display_renderStagingFramebuffer(void) {
pthread_mutex_lock(&display_scan_mutex);
int page = 0;
int bank = 0;
const uint32_t mainswitches = run_args.softswitches;
// render main portion of screen ...
drawpage_mode_t mainDrawPageMode = _currentMainMode(mainswitches);
_currentPageAndBank(mainswitches, mainDrawPageMode, &page, &bank);
drawpage_mode_t mainDrawPageMode = video_currentMainMode(mainswitches);
int page = video_currentPage(mainswitches);
static uint8_t scanline[CYCLES_VIS<<1] = { 0 }; // 80 columns of data ...
if (mainDrawPageMode == DRAWPAGE_TEXT) {
void (*textMainPlotFn)(uint8_t, uint8_t, int, int, uint8_t*) = _textpage_plotter(mainswitches, SS_TEXT);
for (unsigned int y=0; y < TEXT_ROWS-4; y++) {
for (unsigned int x=0; x < TEXT_COLS; x++) {
textMainPlotFn(x, y, page, bank, stagingFB);
uint16_t base = page ? 0x0800 : 0x0400;
for (unsigned int row=0; row < TEXT_ROWS-4; row++) {
for (unsigned int col=0; col < TEXT_COLS; col++) {
uint16_t off = video__line_offset[row] + col;
uint16_t ea = base+off;
scanline[(col<<1)+0] = apple_ii_64k[1][ea]; // AUX
scanline[(col<<1)+1] = apple_ii_64k[0][ea]; // MBD
}
for (unsigned int i = 0; i < 8; i++) {
display_flushScanline(&((scan_data_t){
.scanline = &scanline[0],
.softswitches = mainswitches,
.scanrow = (row<<3) + i,
.scancol = 0,
.scanend = CYCLES_VIS,
}));
}
}
} else {
assert(!(mainswitches & SS_TEXT) && "TEXT should not be set");
assert((mainswitches & SS_HIRES) && "HIRES should be set");
void (*hiresMainPlotFn)(uint16_t, int, int, bool, uint8_t*) = _hirespage_plotter(mainswitches);
for (unsigned int y=0; y < TEXT_ROWS-4; y++) {
for (unsigned int x=0; x < TEXT_COLS; x++) {
uint16_t base = page ? 0x4000 : 0x2000;
for (unsigned int row=0; row < TEXT_ROWS-4; row++) {
for (unsigned int col=0; col < TEXT_COLS; col++) {
for (unsigned int i = 0; i < 8; i++) {
uint16_t off = video__line_offset[y] + (0x400*i) + x;
hiresMainPlotFn(off, page, bank, /*even*/!(x & 1), stagingFB);
uint16_t off = video__line_offset[row] + (0x400*i) + col;
uint16_t ea = base+off;
scanline[(col<<1)+0] = apple_ii_64k[1][ea]; // AUX
scanline[(col<<1)+1] = apple_ii_64k[0][ea]; // MBD
display_flushScanline(&((scan_data_t){
.scanline = &scanline[0],
.softswitches = mainswitches,
.scanrow = (row<<3) + i,
.scancol = 0,
.scanend = CYCLES_VIS,
}));
}
}
}
}
(void)mainswitches;
// resample current switches ... and render mixed portion of screen
const uint32_t mixedswitches = run_args.softswitches;
drawpage_mode_t mixedDrawPageMode = _currentMixedMode(mixedswitches);
_currentPageAndBank(mixedswitches, mixedDrawPageMode, &page, &bank);
drawpage_mode_t mixedDrawPageMode = video_currentMixedMode(mixedswitches);
page = video_currentPage(mixedswitches);
if (mixedDrawPageMode == DRAWPAGE_TEXT) {
void (*textMixedPlotFn)(uint8_t, uint8_t, int, int, uint8_t*) = _textpage_plotter(mixedswitches, (SS_TEXT|SS_MIXED));
for (unsigned int y=TEXT_ROWS-4; y < TEXT_ROWS; y++) {
for (unsigned int x=0; x < TEXT_COLS; x++) {
textMixedPlotFn(x, y, page, bank, stagingFB);
uint16_t base = page ? 0x0800 : 0x0400;
for (unsigned int row=TEXT_ROWS-4; row < TEXT_ROWS; row++) {
for (unsigned int col=0; col < TEXT_COLS; col++) {
uint16_t off = video__line_offset[row] + col;
uint16_t ea = base+off;
scanline[(col<<1)+0] = apple_ii_64k[1][ea]; // AUX
scanline[(col<<1)+1] = apple_ii_64k[0][ea]; // MBD
}
for (unsigned int i = 0; i < 8; i++) {
display_flushScanline(&((scan_data_t){
.scanline = &scanline[0],
.softswitches = mixedswitches,
.scanrow = (row<<3) + i,
.scancol = 0,
.scanend = CYCLES_VIS,
}));
}
}
} else {
//assert(!(mixedswitches & SS_TEXT) && "TEXT should not be set"); // TEXT may have been reset from last sample?
assert(!(mixedswitches & SS_MIXED) && "MIXED should not be set");
assert((mixedswitches & SS_HIRES) && "HIRES should be set");
void (*hiresMixedPlotFn)(uint16_t, int, int, bool, uint8_t*) = _hirespage_plotter(mixedswitches);
for (unsigned int y=TEXT_ROWS-4; y < TEXT_ROWS; y++) {
for (unsigned int x=0; x < TEXT_COLS; x++) {
uint16_t base = page ? 0x4000 : 0x2000;
for (unsigned int row=TEXT_ROWS-4; row < TEXT_ROWS; row++) {
for (unsigned int col=0; col < TEXT_COLS; col++) {
for (unsigned int i = 0; i < 8; i++) {
uint16_t off = video__line_offset[y] + (0x400*i) + x;
hiresMixedPlotFn(off, page, bank, /*even*/!(x & 1), stagingFB);
uint16_t off = video__line_offset[row] + (0x400*i) + col;
uint16_t ea = base+off;
scanline[(col<<1)+0] = apple_ii_64k[1][ea]; // AUX
scanline[(col<<1)+1] = apple_ii_64k[0][ea]; // MBD
display_flushScanline(&((scan_data_t){
.scanline = &scanline[0],
.softswitches = mixedswitches,
.scanrow = (row<<3) + i,
.scancol = 0,
.scanend = CYCLES_VIS,
}));
}
}
}
}
video_setDirty(FB_DIRTY_FLAG);
pthread_mutex_unlock(&display_scan_mutex);
return display_getCurrentFramebuffer();
}
void display_flashText(void) {
static bool normal = false;
normal = !normal;
// flash only if it's text or mixed modes.
if (run_args.softswitches & (SS_TEXT|SS_MIXED)) {
if (normal) {
colormap[ COLOR_FLASHING_BLACK].red = 0;
colormap[ COLOR_FLASHING_BLACK].green = 0;
colormap[ COLOR_FLASHING_BLACK].blue = 0;
if (normal) {
colormap[ COLOR_FLASHING_BLACK].red = 0;
colormap[ COLOR_FLASHING_BLACK].green = 0;
colormap[ COLOR_FLASHING_BLACK].blue = 0;
colormap[ COLOR_FLASHING_WHITE].red = 0xff;
colormap[ COLOR_FLASHING_WHITE].green = 0xff;
colormap[ COLOR_FLASHING_WHITE].blue = 0xff;
} else {
colormap[ COLOR_FLASHING_BLACK].red = 0xff;
colormap[ COLOR_FLASHING_BLACK].green = 0xff;
colormap[ COLOR_FLASHING_BLACK].blue = 0xff;
colormap[ COLOR_FLASHING_WHITE].red = 0xff;
colormap[ COLOR_FLASHING_WHITE].green = 0xff;
colormap[ COLOR_FLASHING_WHITE].blue = 0xff;
} else {
colormap[ COLOR_FLASHING_BLACK].red = 0xff;
colormap[ COLOR_FLASHING_BLACK].green = 0xff;
colormap[ COLOR_FLASHING_BLACK].blue = 0xff;
colormap[ COLOR_FLASHING_WHITE].red = 0;
colormap[ COLOR_FLASHING_WHITE].green = 0;
colormap[ COLOR_FLASHING_WHITE].blue = 0;
}
video_setDirty(FB_DIRTY_FLAG);
colormap[ COLOR_FLASHING_WHITE].red = 0;
colormap[ COLOR_FLASHING_WHITE].green = 0;
colormap[ COLOR_FLASHING_WHITE].blue = 0;
}
video_setDirty(FB_DIRTY_FLAG);
}
#if 0
bool video_isDirty(unsigned long flags) {
return (_vid_dirty & flags);
}
@ -1501,6 +1768,82 @@ uint8_t floating_bus_hibit(const bool hibit) {
uint8_t b = apple_ii_64k[0][scanner_addr];
return (b & ~0x80) | (hibit ? 0x80 : 0);
}
#endif
uint8_t *display_getCurrentFramebuffer(void) {
#if INTERFACE_CLASSIC
if (interface_isShowing()) {
return fbInterface;
}
#endif
return fbStaging;
}
#if TESTING
// HACK FIXME TODO ... should consolidate this into debugger ...
extern pthread_mutex_t interface_mutex;
extern pthread_cond_t cpu_thread_cond;
extern pthread_cond_t dbg_thread_cond;
uint8_t *display_waitForNextCompleteFramebuffer(void) {
int err = 0;
if ((err = pthread_cond_signal(&cpu_thread_cond))) {
LOG("pthread_cond_signal : %d", err);
}
if ((err = pthread_cond_wait(&dbg_thread_cond, &interface_mutex))) {
LOG("pthread_cond_wait : %d", err);
}
return display_getCurrentFramebuffer ();
}
#endif
void display_flushScanline(scan_data_t *scandata) {
#if TESTING
// FIXME TODO ... remove this bracing when video refactoring is done
#else
ASSERT_ON_CPU_THREAD();
#endif
unsigned int scanrow = scandata->scanrow;
unsigned int scancol = scandata->scancol;
unsigned int scanend = scandata->scanend;
assert((scanend > 0) && (scanend <= CYCLES_VIS));
assert(scancol < CYCLES_VIS);
assert(scanrow < SCANLINES_VBL_BEGIN);
const uint32_t currswitches = scandata->softswitches;
const drawpage_mode_t mode = (scanrow < SCANLINES_MIX) ? video_currentMainMode(currswitches) : video_currentMixedMode(currswitches);
void (*plotFn)(scan_data_t *) = NULL;
if (mode == DRAWPAGE_TEXT) {
const uint32_t textmask = (scanrow < SCANLINES_MIX) ? SS_TEXT : (SS_TEXT|SS_MIXED);
plotFn = _textpage_plotter(currswitches, textmask);
} else {
assert(!(currswitches & SS_TEXT) && "TEXT should not be set");
assert((currswitches & SS_HIRES) && "HIRES should be set");
plotFn = _hirespage_plotter(currswitches);
}
plotFn(scandata);
}
void display_frameComplete(void) {
ASSERT_ON_CPU_THREAD();
video_setDirty(FB_DIRTY_FLAG);
#if TESTING
// HACK FIXME TODO ... should consolidate this into debugger ...
int err = 0;
if ((err = pthread_cond_signal(&dbg_thread_cond))) {
LOG("pthread_cond_signal : %d", err);
}
if ((err = pthread_cond_wait(&cpu_thread_cond, &interface_mutex))) {
LOG("pthread_cond_wait : %d", err);
}
#endif
}
static void _init_interface(void) {
LOG("Initializing display subsystem");
@ -1510,16 +1853,10 @@ static void _init_interface(void) {
_initialize_dhires_values();
_initialize_color();
prefs_registerListener(PREF_DOMAIN_VIDEO, &video_prefsChanged);
prefs_registerListener(PREF_DOMAIN_VIDEO, &display_prefsChanged);
}
static __attribute__((constructor)) void __init_interface(void) {
emulator_registerStartupCallback(CTOR_PRIORITY_LATE, &_init_interface);
}
#if __clang__
# pragma clang diagnostic pop
#elif __GNUC__
# pragma GCC diagnostic pop
#endif

View File

@ -12,6 +12,8 @@
#ifndef _DISPLAY_H_
#define _DISPLAY_H_
#include "common.h"
/*
* Color structure
*/
@ -21,6 +23,17 @@ typedef struct A2Color_s {
uint8_t blue;
} A2Color_s;
/*
* Scanline data
*/
typedef struct scan_data_t {
uint8_t *scanline; // CYCLES_VIS<<1 == 80
uint32_t softswitches;
unsigned int scanrow; // 0 >= x < 192
unsigned int scancol; // 0 > x <= 80
unsigned int scanend; // 0 >= x < 80
} scan_data_t;
#define IDX_BLACK 0x00
#define IDX_MAGENTA 0x10
#define IDX_DARKBLUE 0x20
@ -74,21 +87,6 @@ typedef enum drawpage_mode_t {
NUM_DRAWPAGE_MODES,
} drawpage_mode_t;
/*
* Pixel deltas
*/
typedef struct pixel_delta_t {
uint8_t row; // row
uint8_t col; // col
uint8_t b; // byte
uint8_t cs; // colorscheme (interface updates only)
} pixel_delta_t;
/*
* Text/hires callback
*/
typedef void (*display_update_fn)(pixel_delta_t);
/*
* Setup the display. This may be called multiple times in a run, and is
* used when graphics parameters may have changed.
@ -126,58 +124,61 @@ void display_flashText(void);
// ----------------------------------------------------------------------------
#if INTERFACE_CLASSIC
/*
* Plot character into staging framebuffer.
*
* - Framebuffer may be NULL or should be exactly SCANWIDTH*SCANHEIGHT*sizeof(uint8_t) size
* Plot character into interface framebuffer.
* - Pixels in the framebuffer are 8bit indexed color into the colormap array
* - Triggers text update callback
* - This should only be called from video backends/interface
*/
void display_plotChar(uint8_t *fb, const uint8_t col, const uint8_t row, const interface_colorscheme_t cs, const uint8_t c);
void display_plotChar(const uint8_t col, const uint8_t row, const interface_colorscheme_t cs, const uint8_t c);
/*
* Plot NULL_terminated string into staging framebuffer.
* Plot NULL_terminated string into interface framebuffer.
* - See display_plotChar() ...
*
*/
void display_plotLine(uint8_t *fb, const uint8_t col, const uint8_t row, const interface_colorscheme_t cs, const char *message);
void display_plotLine(const uint8_t col, const uint8_t row, const interface_colorscheme_t cs, const char *message);
#endif
/*
* Plot multi-line message into staging framebuffer.
* Plot multi-line message into given framebuffer (not the interface framebuffer).
* - See display_plotChar() ...
*/
void display_plotMessage(uint8_t *fb, const interface_colorscheme_t cs, const char *message, const uint8_t message_cols, const uint8_t message_rows);
// ----------------------------------------------------------------------------
// video generation
/*
* Current dominant screen mode, TEXT or MIXED/HIRES
*/
drawpage_mode_t display_currentDrawpageMode(uint32_t currswitches);
/*
* Set TEXT/HIRES update callback(s).
*/
void display_setUpdateCallback(drawpage_mode_t mode, display_update_fn updateFn);
/*
* Flushes currently set Apple //e video memory into staging framebuffer.
*
* - Framebuffer should be exactly SCANWIDTH*SCANHEIGHT*sizeof(uint8_t) size
* Called by video backend to get the current complete staging framebuffer.
* - Framebuffer is exactly SCANWIDTH*SCANHEIGHT*sizeof(uint8_t) size
* - Pixels in the framebuffer are 8bit indexed color into the colormap array
* - This should only be called from video backends or testsuite
*/
void display_renderStagingFramebuffer(uint8_t *stagingFB);
uint8_t *display_getCurrentFramebuffer(void) CALL_ON_UI_THREAD;
/*
* Handler for toggling text flashing
*/
void display_flashText(void) CALL_ON_CPU_THREAD;
/*
* Handler for video scanner scanline completion
*/
void display_flushScanline(scan_data_t *scandata) CALL_ON_CPU_THREAD;
/*
* Handler called when entire frame is complete
*/
void display_frameComplete(void) CALL_ON_CPU_THREAD;
#if TESTING
/*
* Wait for frame complete and return staging framebuffer.
*/
uint8_t *display_waitForNextCompleteFramebuffer(void);
#endif
// ----------------------------------------------------------------------------
/*
* VBL routines
*/
uint16_t video_scanner_get_address(bool *vblBarOut);
uint8_t floating_bus(void);
uint8_t floating_bus_hibit(const bool hibit);
#define TEXT_ROWS 24
#define BEGIN_MIX 20

View File

@ -128,17 +128,11 @@ void interface_plotMessage(uint8_t *fb, const interface_colorscheme_t cs, char *
#if INTERFACE_CLASSIC
static uint8_t *stagingFB = NULL;
static bool isShowing = false;
static char disk_path[PATH_MAX] = { 0 };
void interface_setStagingFramebuffer(uint8_t *fb) {
stagingFB = fb;
}
static void _interface_plotMessageCentered(uint8_t *fb, int fb_cols, int fb_rows, interface_colorscheme_t cs, char *message, const int message_cols, const int message_rows) {
static void _interface_plotMessageCentered(int fb_cols, int fb_rows, interface_colorscheme_t cs, char *message, const int message_cols, const int message_rows) {
_translate_screen_x_y(message, message_cols, message_rows);
int col = (fb_cols - message_cols) >> 1;
int row = (fb_rows - message_rows) >> 1;
@ -146,7 +140,7 @@ static void _interface_plotMessageCentered(uint8_t *fb, int fb_cols, int fb_rows
assert(fb_pix_width == SCANWIDTH);
int row_max = row + message_rows;
for (int idx=0; row<row_max; row++, idx+=message_cols+1) {
display_plotLine(fb, col, row, cs, &message[idx]);
video_getCurrentBackend()->plotLine(col, row, cs, &message[idx]);
}
}
@ -183,8 +177,7 @@ static void pad_string(char *s, const char c, const int len) {
}
void c_interface_print( int x, int y, const interface_colorscheme_t cs, const char *s ) {
display_plotLine(stagingFB, /*col:*/x, /*row:*/y, cs, s);
video_setDirty(FB_DIRTY_FLAG);
video_getCurrentBackend()->plotLine(/*col:*/x, /*row:*/y, cs, s);
}
/* -------------------------------------------------------------------------
@ -205,8 +198,7 @@ void c_interface_translate_screen( char screen[24][INTERFACE_SCREEN_X+1] ) {
}
void c_interface_print_submenu_centered( char *submenu, const int message_cols, const int message_rows ) {
_interface_plotMessageCentered(stagingFB, INTERFACE_SCREEN_X, TEXT_ROWS, RED_ON_BLACK, submenu, message_cols, message_rows);
video_setDirty(FB_DIRTY_FLAG);
_interface_plotMessageCentered(INTERFACE_SCREEN_X, TEXT_ROWS, RED_ON_BLACK, submenu, message_cols, message_rows);
}
/* ------------------------------------------------------------------------- */
@ -290,7 +282,7 @@ void c_interface_exit(int ch)
}
else
{
video_setDirty(A2_DIRTY_FLAG);
video_setDirty(FB_DIRTY_FLAG);
}
}
@ -867,26 +859,26 @@ void c_interface_parameters()
{
if (temp[ j ] == '\0')
{
display_plotChar(stagingFB, /*col:*/INTERFACE_PATH_MIN + j, /*row:*/5+i, GREEN_ON_BLACK, ' ' );
video_getCurrentBackend()->plotChar(/*col:*/INTERFACE_PATH_MIN + j, /*row:*/5+i, GREEN_ON_BLACK, ' ' );
j++;
break;
}
else
{
display_plotChar(stagingFB, /*col:*/INTERFACE_PATH_MIN + j, /*row:*/5+i, /*cs:*/GREEN_ON_BLACK, temp[j]);
video_getCurrentBackend()->plotChar(/*col:*/INTERFACE_PATH_MIN + j, /*row:*/5+i, /*cs:*/GREEN_ON_BLACK, temp[j]);
}
}
else
{
if (temp[ j ] == '\0')
{
display_plotChar(stagingFB, /*col:*/INTERFACE_PATH_MIN + j, /*row:*/5+i, (option == OPT_PATH ? GREEN_ON_BLUE : GREEN_ON_BLACK), ' ' );
video_getCurrentBackend()->plotChar(/*col:*/INTERFACE_PATH_MIN + j, /*row:*/5+i, (option == OPT_PATH ? GREEN_ON_BLUE : GREEN_ON_BLACK), ' ' );
j++;
break;
}
else
{
display_plotChar(stagingFB, /*col:*/INTERFACE_PATH_MIN + j, /*row:*/5+i, (option == OPT_PATH ? GREEN_ON_BLUE : GREEN_ON_BLACK), temp[j]);
video_getCurrentBackend()->plotChar(/*col:*/INTERFACE_PATH_MIN + j, /*row:*/5+i, (option == OPT_PATH ? GREEN_ON_BLUE : GREEN_ON_BLACK), temp[j]);
}
}
@ -894,7 +886,7 @@ void c_interface_parameters()
for (; j < INTERFACE_PATH_MAX; j++)
{
display_plotChar(stagingFB, /*col:*/INTERFACE_PATH_MIN + j, /*row:*/5+i, GREEN_ON_BLACK, ' ');
video_getCurrentBackend()->plotChar(/*col:*/INTERFACE_PATH_MIN + j, /*row:*/5+i, GREEN_ON_BLACK, ' ');
}
}
}
@ -1538,7 +1530,7 @@ static void *interface_thread(void *data)
case kF7:
isShowing = true;
c_interface_debugging(stagingFB);
c_interface_debugging();
break;
case kF8:

View File

@ -63,7 +63,6 @@ void c_interface_exit(int ch);
void c_interface_translate_screen(char screen[24][INTERFACE_SCREEN_X+1]);
void c_interface_select_diskette(int);
bool interface_isShowing(void);
void interface_setStagingFramebuffer(uint8_t *stagingFB);
#endif
// Plot message into pixel buffer

View File

@ -80,7 +80,7 @@ struct opcode_struct
extern const struct opcode_struct *opcodes;
#ifdef INTERFACE_CLASSIC
void c_interface_debugging(uint8_t *stagingFB);
void c_interface_debugging(void);
#endif
void debugger_setInputText(const char *text, const bool deterministically);

View File

@ -1093,10 +1093,8 @@ void fb_sha1() {
uint8_t md[SHA_DIGEST_LENGTH];
char buf[(SHA_DIGEST_LENGTH*2)+1];
uint8_t *fb = MALLOC(SCANWIDTH*SCANHEIGHT*sizeof(uint8_t));
display_renderStagingFramebuffer(fb);
uint8_t *fb = display_getCurrentFramebuffer();
SHA1(fb, SCANWIDTH*SCANHEIGHT, md);
FREE(fb);
int i=0;
for (int j=0; j<SHA_DIGEST_LENGTH; j++, i+=2) {
@ -1411,7 +1409,7 @@ static void do_debug_command() {
main debugging console
------------------------------------------------------------------------- */
void c_interface_debugging(uint8_t *stagingFB) {
void c_interface_debugging(void) {
static char lex_initted = 0;
@ -1455,7 +1453,7 @@ void c_interface_debugging(uint8_t *stagingFB) {
c_interface_print(1, 1+PROMPT_Y, 0, command_line);
/* highlight cursor */
display_plotChar(stagingFB, /*col:*/1+command_pos, /*row:*/1+PROMPT_Y, GREEN_ON_BLUE, command_line[command_pos]);
display_plotChar(/*col:*/1+command_pos, /*row:*/1+PROMPT_Y, GREEN_ON_BLUE, command_line[command_pos]);
while ((ch = c_mygetch(1)) == -1)
{

View File

@ -218,7 +218,7 @@ bool emulator_loadState(int fd, int fdA, int fdB) {
assert(cpu_isPaused() && "should be paused to load state");
#endif
video_setDirty(A2_DIRTY_FLAG);
//video_setDirty(A2_DIRTY_FLAG); -- A2_DIRTY_FLAG is now reserved exclusively for CPU thread (VM) operations
do {
int version = _load_magick(fd);

View File

@ -43,19 +43,68 @@ char **test_copy_disk_paths(const char *fileName);
int test_setup_boot_disk(const char *fileName, int readonly);
void sha1_to_str(const uint8_t * const md, char *buf);
static inline int ASSERT_SHA(const char *SHA_STR) {
static inline bool _matchFramebufferSHA(const char *SHA_STR, bool is_old) {
uint8_t md[SHA_DIGEST_LENGTH];
uint8_t *fb = CALLOC(1, SCANWIDTH*SCANHEIGHT);
display_renderStagingFramebuffer(fb);
SHA1(fb, SCANWIDTH*SCANHEIGHT, md);
FREE(fb);
bool matches = false;
for (unsigned int i=0; i<2; i++) { // HACK: pump for at least 2 video frames to accommodate testing loading state
uint8_t *fb = NULL;
if (is_old) {
extern uint8_t *display_renderStagingFramebuffer(void);
fb = display_renderStagingFramebuffer();
} else {
fb = display_waitForNextCompleteFramebuffer();
}
SHA1(fb, SCANWIDTH*SCANHEIGHT, md);
sha1_to_str(md, mdstr);
ASSERT(strcasecmp(mdstr, SHA_STR) == 0);
return 0;
sha1_to_str(md, mdstr);
matches = strcasecmp(mdstr, SHA_STR) == 0;
if (matches) {
break;
}
}
return matches;
}
static inline int _assertFramebufferSHA(const char *SHA_STR, bool is_old) {
bool matches = _matchFramebufferSHA(SHA_STR, is_old);
ASSERT(matches && "check global mdstr if failed...");
PASS();
}
#define ASSERT_SHA(SHA_STR) \
do { \
int ret = _assertFramebufferSHA(SHA_STR, /*is_old:*/false); \
if (ret != 0) { \
return ret; \
} \
} while (0);
#define ASSERT_SHA_OLD(SHA_STR) \
do { \
int ret = _assertFramebufferSHA(SHA_STR, /*is_old:*/true); \
if (ret != 0) { \
return ret; \
} \
} while (0);
#define WAIT_FOR_FB_SHA(SHA_STR) \
do { \
unsigned int matchAttempts = 0; \
const unsigned int maxMatchAttempts = 10; \
do { \
bool matches = _matchFramebufferSHA(SHA_STR, /*is_old:*/false); \
if (matches) { \
break; \
} \
} while (matchAttempts++ < maxMatchAttempts); \
if (matchAttempts >= maxMatchAttempts) { \
fprintf(GREATEST_STDOUT, "DID NOT FIND SHA %s...\n", SHA_STR); \
ASSERT(0); \
} \
} while (0);
static inline int ASSERT_SHA_MEM(const char *SHA_STR, uint16_t ea, uint16_t len) {
uint8_t md[SHA_DIGEST_LENGTH];
const uint8_t * const mem = &apple_ii_64k[0][ea];
@ -78,7 +127,6 @@ static inline int BOOT_TO_DOS(void) {
ASSERT(apple_ii_64k[0][WATCHPOINT_ADDR] != TEST_FINISHED);
c_debugger_go();
ASSERT(apple_ii_64k[0][WATCHPOINT_ADDR] == TEST_FINISHED);
ASSERT_SHA(BOOT_SCREEN);
apple_ii_64k[0][WATCHPOINT_ADDR] = 0x00;
}
return 0;

View File

@ -1396,10 +1396,8 @@ static int _test_disk_image_with_gzip_header(int readonly) {
do {
uint8_t md[SHA_DIGEST_LENGTH];
uint8_t *fb = CALLOC(1, SCANWIDTH*SCANHEIGHT);
display_renderStagingFramebuffer(fb);
uint8_t *fb = display_waitForNextCompleteFramebuffer();
SHA1(fb, SCANWIDTH*SCANHEIGHT, md);
FREE(fb);
sha1_to_str(md, mdstr);
bool matches_sha1 = (strcasecmp(mdstr, GZBAD_NIB_LOAD_SHA1) == 0);
@ -1504,10 +1502,8 @@ TEST test_reinsert_edgecase() {
c_debugger_go();
uint8_t md[SHA_DIGEST_LENGTH];
uint8_t *fb = CALLOC(1, SCANWIDTH*SCANHEIGHT);
display_renderStagingFramebuffer(fb);
uint8_t *fb = display_waitForNextCompleteFramebuffer();
SHA1(fb, SCANWIDTH*SCANHEIGHT, md);
FREE(fb);
sha1_to_str(md, mdstr);
ASSERT(strcmp(mdstr, BLANK_SCREEN) != 0);

View File

@ -39,6 +39,182 @@ TEST test_boot_disk() {
PASS();
}
#if VIDEO_TRACING
# define EXPECTED_BOOT_VIDEO_TRACE_FILE_SIZE 1484244660
# define EXPECTED_BOOT_VIDEO_TRACE_FILE_SHA "26b9e21914d4047e6b190fb4f6fcb6854a4eaa25"
// NOTE that CONFORMANT_TRACKS codepaths will change the output of this tracing (just like cpu tracing)
// - Data in screen holes (e.g., 0478-047F is used by Disk ][
// - Longer boot time with CONFORMANT_TRACKS will add more frames of output
# if CONFORMANT_TRACKS
TEST test_boot_video_trace(void) {
test_setup_boot_disk("testdisplay1.dsk.gz", 1);
srandom(0);
const char *homedir = HOMEDIR;
char *traceFile = NULL;
ASPRINTF(&traceFile, "%s/a2_boot_video_trace.txt", homedir);
ASSERT(traceFile);
unlink(traceFile);
video_scannerTraceBegin(traceFile, 0);
BOOT_TO_DOS();
video_scannerTraceEnd();
do {
uint8_t md[SHA_DIGEST_LENGTH];
char mdstr0[(SHA_DIGEST_LENGTH*2)+1];
FILE *fp = fopen(traceFile, "r");
fseek(fp, 0, SEEK_END);
long expectedSize = ftell(fp);
ASSERT(expectedSize == EXPECTED_BOOT_VIDEO_TRACE_FILE_SIZE);
fseek(fp, 0, SEEK_SET);
unsigned char *buf = MALLOC(EXPECTED_BOOT_VIDEO_TRACE_FILE_SIZE);
if (fread(buf, 1, EXPECTED_BOOT_VIDEO_TRACE_FILE_SIZE, fp) != EXPECTED_BOOT_VIDEO_TRACE_FILE_SIZE) {
ASSERT(false);
}
fclose(fp); fp = NULL;
SHA1(buf, EXPECTED_BOOT_VIDEO_TRACE_FILE_SIZE, md);
FREE(buf);
sha1_to_str(md, mdstr0);
ASSERT(strcasecmp(mdstr0, EXPECTED_BOOT_VIDEO_TRACE_FILE_SHA) == 0);
} while(0);
unlink(traceFile);
FREE(traceFile);
PASS();
}
# endif
# undef EXPECTED_BOOT_VIDEO_TRACE_FILE_SIZE
# undef EXPECTED_BOOT_VIDEO_TRACE_FILE_SHA
# define EXPECTED_TRACE_40COL_FILE_SIZ 698230
# define EXPECTED_TRACE_40COL_FILE_SHA "03dd130fa58c068d2434cf7fa244f64ec058290b"
TEST test_video_trace_40col(void) {
test_setup_boot_disk("testdisplay1.dsk.gz", 1);
BOOT_TO_DOS();
ASSERT(apple_ii_64k[0][WATCHPOINT_ADDR] != TEST_FINISHED);
test_type_input("CATALOG\rPOKE7987,255:REM TRIGGER DEBUGGER\r");
c_debugger_go();
ASSERT(apple_ii_64k[0][WATCHPOINT_ADDR] == TEST_FINISHED);
apple_ii_64k[0][WATCHPOINT_ADDR] = 0x0;
const char *homedir = HOMEDIR;
char *traceFile = NULL;
ASPRINTF(&traceFile, "%s/a2_video_trace_40col.txt", homedir);
ASSERT(traceFile);
unlink(traceFile);
video_scannerTraceBegin(traceFile, 1);
debugger_setBreakCallback(&video_scannerTraceShouldStop);
c_debugger_go();
debugger_setBreakCallback(NULL);
video_scannerTraceEnd();
do {
uint8_t md[SHA_DIGEST_LENGTH];
char mdstr0[(SHA_DIGEST_LENGTH*2)+1];
FILE *fp = fopen(traceFile, "r");
ASSERT(fp);
fseek(fp, 0, SEEK_END);
long expectedSize = ftell(fp);
ASSERT(expectedSize == EXPECTED_TRACE_40COL_FILE_SIZ);
fseek(fp, 0, SEEK_SET);
unsigned char *buf = MALLOC(EXPECTED_TRACE_40COL_FILE_SIZ);
if (fread(buf, 1, EXPECTED_TRACE_40COL_FILE_SIZ, fp) != EXPECTED_TRACE_40COL_FILE_SIZ) {
ASSERT(false);
}
fclose(fp); fp = NULL;
SHA1(buf, EXPECTED_TRACE_40COL_FILE_SIZ, md);
FREE(buf);
sha1_to_str(md, mdstr0);
ASSERT(strcasecmp(mdstr0, EXPECTED_TRACE_40COL_FILE_SHA) == 0);
} while(0);
unlink(traceFile);
FREE(traceFile);
PASS();
}
# undef EXPECTED_TRACE_40COL_FILE_SIZ
# undef EXPECTED_TRACE_40COL_FILE_SHA
# define EXPECTED_TRACE_LILTEXWIN_FILE_SIZ 613920
# define EXPECTED_TRACE_LILTEXWIN_FILE_SHA "97cf740a3697d8046b0756c7c2baedc893d46237"
TEST test_video_trace_liltexwin(void) {
test_setup_boot_disk("testdisplay2.dsk.gz", 1); // boots directly into LILTEXWIN
BOOT_TO_DOS();
c_debugger_set_timeout(5);
c_debugger_clear_watchpoints();
c_debugger_go();
c_debugger_set_timeout(0);
const char *homedir = HOMEDIR;
char *traceFile = NULL;
ASPRINTF(&traceFile, "%s/a2_video_trace_liltexwin.txt", homedir);
ASSERT(traceFile);
unlink(traceFile);
video_scannerTraceBegin(traceFile, 1);
debugger_setBreakCallback(&video_scannerTraceShouldStop);
c_debugger_go();
debugger_setBreakCallback(NULL);
c_debugger_set_watchpoint(WATCHPOINT_ADDR);
video_scannerTraceEnd();
do {
uint8_t md[SHA_DIGEST_LENGTH];
char mdstr0[(SHA_DIGEST_LENGTH*2)+1];
FILE *fp = fopen(traceFile, "r");
ASSERT(fp);
fseek(fp, 0, SEEK_END);
long expectedSize = ftell(fp);
ASSERT(expectedSize == EXPECTED_TRACE_LILTEXWIN_FILE_SIZ);
fseek(fp, 0, SEEK_SET);
unsigned char *buf = MALLOC(EXPECTED_TRACE_LILTEXWIN_FILE_SIZ);
if (fread(buf, 1, EXPECTED_TRACE_LILTEXWIN_FILE_SIZ, fp) != EXPECTED_TRACE_LILTEXWIN_FILE_SIZ) {
ASSERT(false);
}
fclose(fp); fp = NULL;
SHA1(buf, EXPECTED_TRACE_LILTEXWIN_FILE_SIZ, md);
FREE(buf);
sha1_to_str(md, mdstr0);
ASSERT(strcasecmp(mdstr0, EXPECTED_TRACE_LILTEXWIN_FILE_SHA) == 0);
} while(0);
unlink(traceFile);
FREE(traceFile);
PASS();
}
# undef EXPECTED_TRACE_LILTEXWIN_FILE_SIZ
# undef EXPECTED_TRACE_LILTEXWIN_FILE_SHA
#endif
// ----------------------------------------------------------------------------
// TEXT
@ -111,7 +287,11 @@ TEST test_lores_with_80col() {
c_debugger_go();
ASSERT(apple_ii_64k[0][WATCHPOINT_ADDR] == TEST_FINISHED);
#if !CONFORMANT_TRACKS
ASSERT_SHA("7B642FF04DE03142A2CE1062C28A4D92E492EDDC");
#endif
WAIT_FOR_FB_SHA("09C732B37F9E482C8CBE38DA97C99EE640EB8913");
PASS();
}
@ -125,7 +305,10 @@ TEST test_lores_with_40col() {
c_debugger_go();
ASSERT(apple_ii_64k[0][WATCHPOINT_ADDR] == TEST_FINISHED);
ASSERT_SHA("D7DC78F5718B4CF8716614E79ADABCAB919FCE5D");
ASSERT_SHA_OLD("D7DC78F5718B4CF8716614E79ADABCAB919FCE5D");
WAIT_FOR_FB_SHA("36287232F7FD4574AA5E05F1C6CACB598C9C2903");
ASSERT_SHA ("36287232F7FD4574AA5E05F1C6CACB598C9C2903"); // stable through next frame
PASS();
}
@ -139,7 +322,11 @@ TEST test_lores_40colmix_normal() {
c_debugger_go();
ASSERT(apple_ii_64k[0][WATCHPOINT_ADDR] == TEST_FINISHED);
#if !CONFORMANT_TRACKS
ASSERT_SHA("9097A6AE967E4501B40C7CD7EEE115B8C478B345");
#endif
WAIT_FOR_FB_SHA("B460804F69A416D78462818493933BA2FFEB70C8");
PASS();
}
@ -153,7 +340,10 @@ TEST test_lores_40colmix_inverse() {
c_debugger_go();
ASSERT(apple_ii_64k[0][WATCHPOINT_ADDR] == TEST_FINISHED);
ASSERT_SHA("5256E8B96CB04F48324B587ECCCF8A435077B5DE");
ASSERT_SHA_OLD("5256E8B96CB04F48324B587ECCCF8A435077B5DE");
WAIT_FOR_FB_SHA("C2ADD78885B65F7D2FA84F999B06CB32D25EF8A0");
ASSERT_SHA ("C2ADD78885B65F7D2FA84F999B06CB32D25EF8A0"); // stable through next frame
PASS();
}
@ -167,7 +357,10 @@ TEST test_lores_80colmix_normal() {
c_debugger_go();
ASSERT(apple_ii_64k[0][WATCHPOINT_ADDR] == TEST_FINISHED);
ASSERT_SHA("9D5D5382B0A18A71DC135CAD51BEA2665ADB5FB2");
ASSERT_SHA_OLD("9D5D5382B0A18A71DC135CAD51BEA2665ADB5FB2");
WAIT_FOR_FB_SHA("610E61D466AAE88CF694B3E1029D3D4C28D1D820");
ASSERT_SHA ("610E61D466AAE88CF694B3E1029D3D4C28D1D820"); // stable through next frame
PASS();
}
@ -181,7 +374,10 @@ TEST test_lores_80colmix_inverse() {
c_debugger_go();
ASSERT(apple_ii_64k[0][WATCHPOINT_ADDR] == TEST_FINISHED);
ASSERT_SHA("7936E87BE1F920AACD43268DB288746528E89959");
ASSERT_SHA_OLD("7936E87BE1F920AACD43268DB288746528E89959");
WAIT_FOR_FB_SHA("CBAEE8961F20079BF45CD197878F1111A6E89E26");
ASSERT_SHA ("CBAEE8961F20079BF45CD197878F1111A6E89E26"); // stable through next frame
PASS();
}
@ -228,6 +424,11 @@ TEST test_hires_with_40col_page2() {
c_debugger_go();
ASSERT(apple_ii_64k[0][WATCHPOINT_ADDR] == TEST_FINISHED);
// FIXME TODO ... pump CPU until frame complete a few times ... should check All The SHAs here ...
display_waitForNextCompleteFramebuffer();
display_waitForNextCompleteFramebuffer();
ASSERT_SHA(MOIRE_SHA);
PASS();
@ -267,7 +468,7 @@ TEST test_hires_80colmix_normal() {
c_debugger_go();
ASSERT(apple_ii_64k[0][WATCHPOINT_ADDR] == TEST_FINISHED);
ASSERT_SHA("032BD68899749265EB2934A76A35D7068642824B");
ASSERT_SHA("032BD68899749265EB2934A76A35D7068642824B"); // flappy ...
PASS();
}
@ -280,7 +481,11 @@ TEST test_hires_80colmix_inverse() {
c_debugger_go();
ASSERT(apple_ii_64k[0][WATCHPOINT_ADDR] == TEST_FINISHED);
#if !CONFORMANT_TRACKS
ASSERT_SHA("FAFBB65013DA3D5173487C3F434C36A7C04DE92E");
#endif
WAIT_FOR_FB_SHA("30C0329061781FD1BFE214940F9D5EDFA5FA5F08");
PASS();
}
@ -306,13 +511,13 @@ TEST test_80col_hires() {
c_debugger_go();
ASSERT(apple_ii_64k[0][WATCHPOINT_ADDR] == TEST_FINISHED);
ASSERT_SHA("126363F103F3A24BB2EB2AEC5B4972F46F7CA24B");
ASSERT_SHA("B48F940B1566607E2557A50B98C6C87FFF0C05B2");
apple_ii_64k[0][WATCHPOINT_ADDR] = 0x00;
c_debugger_go();
ASSERT(apple_ii_64k[0][WATCHPOINT_ADDR] == TEST_FINISHED);
ASSERT_SHA("91F63831B6D1145446F0DBACE8EBD284B5690D81");
ASSERT_SHA("3BDFD6B009C090789D968AA7F928579EA31CBE3D");
PASS();
}
@ -324,13 +529,19 @@ GREATEST_SUITE(test_suite_display) {
pthread_mutex_lock(&interface_mutex);
test_thread_running = true;
GREATEST_SET_SETUP_CB(testdisplay_setup, NULL);
GREATEST_SET_TEARDOWN_CB(testdisplay_teardown, NULL);
GREATEST_SET_BREAKPOINT_CB(test_breakpoint, NULL);
// TESTS --------------------------
#if VIDEO_TRACING && CONFORMANT_TRACKS
RUN_TESTp(test_boot_video_trace);
RUN_TESTp(test_video_trace_40col);
RUN_TESTp(test_video_trace_liltexwin);
#endif
RUN_TESTp(test_boot_disk);
// text modes

View File

@ -11,23 +11,26 @@
#include "testcommon.h"
#define ABUSIVE_TESTS 1
#define TESTING_DISK "testvm1.dsk.gz"
#define BLANK_DSK "blank.dsk.gz"
#define BLANK_NIB "blank.nib.gz"
#define BLANK_PO "blank.po.gz"
#if !CONFORMANT_TRACKS
#error trace testing against baseline should be built with CONFORMANT_TRACKS ... FIXME NOW
#endif
static bool test_thread_running = false;
extern pthread_mutex_t interface_mutex; // TODO FIXME : raw access to CPU mutex because stepping debugger ...
static void testtrace_setup(void *arg) {
disk6_init(); // ensure better for tracing stability against baseline ...
test_common_setup();
apple_ii_64k[0][MIXSWITCH_ADDR] = 0x00;
apple_ii_64k[0][WATCHPOINT_ADDR] = 0x00;
apple_ii_64k[0][TESTOUT_ADDR] = 0x00;
joy_button0 = 0xff; // OpenApple
run_args.joy_button0 = 0xff; // OpenApple
test_setup_boot_disk(TESTING_DISK, 1);
if (test_do_reboot) {
cpu65_interrupt(ResetSig);
@ -65,9 +68,8 @@ TEST test_timing_overflow() {
testing_getCyclesCount = &testspeaker_getCyclesCount;
testing_cyclesOverflow = &testspeaker_cyclesOverflow;
extern volatile uint8_t emul_reinitialize;
do {
emul_reinitialize = 1;
run_args.emul_reinitialize = 1;
c_debugger_go();
if (cycles_overflowed) {
@ -235,9 +237,8 @@ TEST test_mockingboard_1() {
// This test is majorly abusive ... it creates an ~1GB file in $HOME
// ... but if it's correct, you're fairly assured the cpu/vm is working =)
#if ABUSIVE_TESTS
#define EXPECTED_CPU_TRACE_FILE_SIZE 1233878608
#define EXPECTED_CPU_TRACE_SHA "36E399BEC3A4671A9AE4145B7F01A9AD1F1CDE3B"
#define EXPECTED_CPU_TRACE_FILE_SIZE 1098663679
#define EXPECTED_CPU_TRACE_SHA "BFE90A6B7EAAB23F050874EC29A2E66EE92FE9DD"
TEST test_boot_disk_cputrace() {
const char *homedir = HOMEDIR;
char *output = NULL;
@ -279,10 +280,12 @@ TEST test_boot_disk_cputrace() {
PASS();
}
#undef EXPECTED_CPU_TRACE_FILE_SIZE
#undef EXPECTED_CPU_TRACE_SHA
#define EXPECTED_BOOT_SIZ2 555444333
#define EXPECTED_BOOT_SHA2 "A5F4114D404FDBE84691412ED85DB18E3B06EEE5"
TEST test_boot_disk_cputrace2() {
#define EXPECTED_BOOT_SHA2 "E807FDC048868FAC246B15CA0F2D5F7B2A12CA2B"
TEST test_boot_disk_cputrace2() { // Failing now due to difference in IRQ timing against baseline
test_setup_boot_disk(NSCT_DSK, 0);
const char *homedir = HOMEDIR;
@ -344,10 +347,78 @@ TEST test_boot_disk_cputrace2() {
PASS();
}
#endif
#undef EXPECTED_BOOT_SIZ2
#undef EXPECTED_BOOT_SHA2
#define EXPECTED_CPUTRACE_HELLO_FILE_SIZE 164542693
#define EXPECTED_CPUTRACE_HELLO_SHA "46A3CAE4BB3D7F0A73465DFF6EAD9181D2F958CF"
#define EXPECTED_BOOT_SIZ3 1555666777
#define EXPECTED_BOOT_SHA3 "4e90d33f165a5bedd65588ffe1d5618ba131ed61"
TEST test_boot_disk_cputrace3() {
test_setup_boot_disk("testdisplay2.dsk.gz", 0); // boots directly into LILTEXWIN
const char *homedir = HOMEDIR;
char *output = NULL;
ASPRINTF(&output, "%s/a2_cputrace.txt", homedir);
if (output) {
unlink(output);
cpu65_trace_begin(output);
}
srandom(0);
// Poll for trace file of particular size
c_debugger_clear_watchpoints();
c_debugger_set_timeout(1);
do {
c_debugger_go();
FILE *fpTrace = fopen(output, "r");
fseek(fpTrace, 0, SEEK_END);
long minSizeTrace = ftell(fpTrace);
if (minSizeTrace < EXPECTED_BOOT_SIZ3) {
fclose(fpTrace);
continue;
}
// trace has generated files of sufficient length
uint8_t md[SHA_DIGEST_LENGTH];
char mdstr0[(SHA_DIGEST_LENGTH*2)+1];
cpu65_trace_end();
truncate(output, EXPECTED_BOOT_SIZ3);
// verify trace file
do {
unsigned char *buf = MALLOC(EXPECTED_BOOT_SIZ3);
fseek(fpTrace, 0, SEEK_SET);
ASSERT(fread(buf, 1, EXPECTED_BOOT_SIZ3, fpTrace) == EXPECTED_BOOT_SIZ3);
fclose(fpTrace); fpTrace = NULL;
SHA1(buf, EXPECTED_BOOT_SIZ3, md);
FREE(buf);
sha1_to_str(md, mdstr0);
ASSERT(strcasecmp(mdstr0, EXPECTED_BOOT_SHA3) == 0);
} while (0);
break;
} while (1);
c_debugger_set_timeout(0);
disk6_eject(0);
unlink(output);
FREE(output);
PASS();
}
#undef EXPECTED_BOOT_SIZ3
#undef EXPECTED_BOOT_SHA3
#define EXPECTED_CPUTRACE_HELLO_FILE_SIZE 146562789
#define EXPECTED_CPUTRACE_HELLO_SHA "A2F1806F4539E84C5047F49038D8BC92349E33AC"
TEST test_cputrace_hello_dsk() {
test_setup_boot_disk(BLANK_DSK, 0);
@ -396,8 +467,8 @@ TEST test_cputrace_hello_dsk() {
PASS();
}
#define EXPECTED_CPUTRACE_HELLO_NIB_FILE_SIZE 19455366
#define EXPECTED_CPUTRACE_HELLO_NIB_SHA "F0D3B25E98037E82422787DD950D4F959426F258"
#define EXPECTED_CPUTRACE_HELLO_NIB_FILE_SIZE 17318711
#define EXPECTED_CPUTRACE_HELLO_NIB_SHA "4DD390FCC46A1928D967F286C33ABD18AE2B9EEC"
TEST test_cputrace_hello_nib() {
test_setup_boot_disk(BLANK_NIB, 0);
@ -658,16 +729,20 @@ GREATEST_SUITE(test_suite_trace) {
RUN_TESTp(test_timing_overflow);
RUN_TESTp(test_boot_sound);
#if NULL_AUDIO_RENDERER_IS_FIXED_FOR_THIS_TEST
RUN_TESTp(test_mockingboard_1);
#endif
#if ABUSIVE_TESTS
RUN_TESTp(test_boot_disk_cputrace);
#if CPU_TRACING_WITH_IRQ_HANDLING_SAME_AS_BASELINE
RUN_TESTp(test_boot_disk_cputrace2);
#endif
RUN_TESTp(test_boot_disk_cputrace3);
RUN_TESTp(test_cputrace_hello_dsk);
RUN_TESTp(test_cputrace_hello_nib);
RUN_TESTp(test_cputrace_hello_po);
#if VM_TRACING_FIXED
// 2016/10/01 : VM tracing is undergoing upheaval
RUN_TESTp(test_boot_disk_vmtrace);

View File

@ -69,7 +69,7 @@ static int _assert_blank_boot(void) {
ASSERT(disk6.disk[0].skew_table == skew_table_6_do);
// VM ...
ASSERT(run_args.softswitches == 0x000140d1);
ASSERT(run_args.softswitches == 0x000100d1);
ASSERT_SHA_BIN("97AADDDF5D20B793C4558A8928227F0B52565A98", apple_ii_64k[0], /*len:*/sizeof(apple_ii_64k));
ASSERT_SHA_BIN("2C82E33E964936187CA1DABF71AE6148916BD131", language_card[0], /*len:*/sizeof(language_card));
ASSERT_SHA_BIN("36F1699537024EC6017A22641FF0EC277AFFD49D", language_banks[0], /*len:*/sizeof(language_banks));
@ -147,6 +147,8 @@ TEST test_save_state_1() {
bool ret = emulator_saveState(fd);
ASSERT(ret);
ASSERT_SHA(BOOT_SCREEN);
TEMP_FAILURE_RETRY(close(fd));
FREE(savData);
@ -185,11 +187,11 @@ TEST test_load_state_1() {
ret = emulator_loadState(fdState, fdA, fdB);
ASSERT(ret);
_assert_blank_boot();
ASSERT(apple_ii_64k[0][WATCHPOINT_ADDR] != TEST_FINISHED);
ASSERT_SHA(BOOT_SCREEN);
_assert_blank_boot();
TEMP_FAILURE_RETRY(close(fdState));
TEMP_FAILURE_RETRY(close(fdA));
TEMP_FAILURE_RETRY(close(fdB));
@ -250,9 +252,6 @@ TEST test_load_A2VM_good1() {
ret = emulator_loadState(fdState, fdA, fdB);
ASSERT(ret);
// ASSERT framebuffer matches expected
ASSERT_SHA("9C654FEF2A672E16D89ED2FB80C593CD2005A026");
// Disk6 ... AVOID ASSERT()ing for non-portable things
ASSERT(disk6.motor_off == 1);
ASSERT(disk6.drive == 0);
@ -304,6 +303,9 @@ TEST test_load_A2VM_good1() {
ASSERT(run_args.cpu65_y == 0x01);
ASSERT(run_args.cpu65_sp == 0xEA);
// ASSERT framebuffer matches expected
ASSERT_SHA("9C654FEF2A672E16D89ED2FB80C593CD2005A026");
TEMP_FAILURE_RETRY(close(fdState));
TEMP_FAILURE_RETRY(close(fdA));
TEMP_FAILURE_RETRY(close(fdB));
@ -364,9 +366,6 @@ TEST test_load_A2V2_good1() {
ret = emulator_loadState(fdState, fdA, fdB);
ASSERT(ret);
// ASSERT framebuffer matches expected
ASSERT_SHA("B1CB1C5811B9C629BB077F857CC41DFA8A283E96");
// Disk6 ... AVOID ASSERT()ing for non-portable things
ASSERT(disk6.motor_off == 1);
ASSERT(disk6.drive == 0);
@ -430,6 +429,9 @@ TEST test_load_A2V2_good1() {
size_t mbSiz = sizeof(mbData);
mb_testAssertA2V2(mbData, mbSiz);
// ASSERT framebuffer matches expected
ASSERT_SHA("B1CB1C5811B9C629BB077F857CC41DFA8A283E96");
TEMP_FAILURE_RETRY(close(fdState));
TEMP_FAILURE_RETRY(close(fdA));
TEMP_FAILURE_RETRY(close(fdB));
@ -490,9 +492,6 @@ TEST test_load_A2V2_good2() {
ret = emulator_loadState(fdState, fdA, fdB);
ASSERT(ret);
// ASSERT framebuffer matches expected
ASSERT_SHA("7A60972EF2E95956249454402A42C12E7C8FBF7A");
// Disk6 ... AVOID ASSERT()ing for non-portable things
ASSERT(disk6.motor_off == 1);
ASSERT(disk6.drive == 0);
@ -556,6 +555,9 @@ TEST test_load_A2V2_good2() {
size_t mbSiz = sizeof(mbData);
mb_testAssertA2V2(mbData, mbSiz);
// ASSERT framebuffer matches expected
ASSERT_SHA("7A60972EF2E95956249454402A42C12E7C8FBF7A");
TEMP_FAILURE_RETRY(close(fdState));
TEMP_FAILURE_RETRY(close(fdA));
TEMP_FAILURE_RETRY(close(fdB));
@ -616,9 +618,6 @@ TEST test_load_A2V2_good3() {
ret = emulator_loadState(fdState, fdA, fdB);
ASSERT(ret);
// ASSERT framebuffer matches expected
ASSERT_SHA("D92EECDF3C7446097F3E884412D7911DDD968287");
// Disk6 ... AVOID ASSERT()ing for non-portable things ... in particular this a2state file contains Droid content://
// paths that will not be valid (even on the original device), so the disk6_insert() call will have failed.
// emulator_stateRestore() will have logged the fault but continued optimistically
@ -678,6 +677,9 @@ TEST test_load_A2V2_good3() {
size_t mbSiz = sizeof(mbData);
mb_testAssertA2V2(mbData, mbSiz);
// ASSERT framebuffer matches expected
ASSERT_SHA("D92EECDF3C7446097F3E884412D7911DDD968287");
TEMP_FAILURE_RETRY(close(fdState));
TEMP_FAILURE_RETRY(close(fdA));
TEMP_FAILURE_RETRY(close(fdB));

View File

@ -36,6 +36,7 @@ TEST test_boot_disk() {
test_setup_boot_disk("testvm1.dsk.gz", 1);
BOOT_TO_DOS();
ASSERT_SHA(BOOT_SCREEN);
PASS();
}
@ -2823,9 +2824,9 @@ TEST test_check_ioudis(bool flag_ioudis) {
ASM_INIT();
if (flag_ioudis) {
ASSERT((run_args.softswitches & SS_IOUDIS));
ASM_IOUDIS_ON();
} else {
ASM_IOUDIS_OFF();
ASSERT(!(run_args.softswitches & SS_IOUDIS));
}
ASM_CHECK_IOUDIS();
@ -2855,9 +2856,9 @@ TEST test_dhires_on(bool flag_ioudis/* FIXME TODO : possibly testing a existing
ASM_INIT();
if (flag_ioudis) {
ASSERT((run_args.softswitches & SS_IOUDIS));
ASM_IOUDIS_ON();
} else {
ASM_IOUDIS_OFF();
ASSERT(!(run_args.softswitches & SS_IOUDIS));
}
ASM_DHIRES_OFF();
ASM_TRIGGER_WATCHPT();
@ -2896,9 +2897,9 @@ TEST test_dhires_off(bool flag_ioudis/* FIXME TODO : possibly testing a existing
ASM_INIT();
if (flag_ioudis) {
ASSERT((run_args.softswitches & SS_IOUDIS));
ASM_IOUDIS_ON();
} else {
ASM_IOUDIS_OFF();
ASSERT(!(run_args.softswitches & SS_IOUDIS));
}
ASM_DHIRES_ON();
ASM_TRIGGER_WATCHPT();
@ -2938,9 +2939,9 @@ TEST test_check_dhires(bool flag_dhires, bool flag_ioudis/* FIXME TODO : possibl
ASM_INIT();
if (flag_ioudis) {
ASSERT((run_args.softswitches & SS_IOUDIS));
ASM_IOUDIS_ON();
} else {
ASM_IOUDIS_OFF();
ASSERT(!(run_args.softswitches & SS_IOUDIS));
}
if (flag_dhires) {

View File

@ -23,9 +23,9 @@
// cycle counting
double cycles_persec_target = CLK_6502;
unsigned long cycles_count_total = 0; // Running at spec ~1MHz, this will approach overflow in ~4000secs (for 32bit architectures)
unsigned int cycles_video_frame = 0;
int cycles_speaker_feedback = 0;
static int32_t cycles_checkpoint_count = 0;
static unsigned int cycles_this_frame = 0;
// scaling and speed adjustments
#if !MOBILE_DEVICE
@ -116,7 +116,11 @@ void reinitialize(void) {
#endif
cycles_count_total = 0;
cycles_this_frame = 0;
cycles_video_frame = 0;
#if !TEST_CPU
video_scannerReset();
#endif
#if TESTING
extern unsigned long (*testing_getCyclesCount)(void);
if (testing_getCyclesCount) {
@ -126,8 +130,6 @@ void reinitialize(void) {
vm_initialize();
video_setDirty(A2_DIRTY_FLAG);
cpu65_init();
timing_initialize();
@ -324,7 +326,6 @@ cpu_runloop:
#if DEBUG_TIMING
dbg_cycles_executed += run_args.cpu65_cycle_count;
#endif
cycles_this_frame += run_args.cpu65_cycle_count;
if (is_debugging) {
debugging_cycles -= run_args.cpu65_cycle_count;
@ -351,15 +352,12 @@ cpu_runloop:
} while (is_debugging);
MB_UpdateCycles();
timing_checkpointCycles();
// TODO : modularize MB and other peripheral card cycles/interrupts ...
speaker_flush(); // play audio
// video frame counter overflow ...
if (cycles_this_frame >= CYCLES_FRAME) {
cycles_this_frame -= CYCLES_FRAME;
MB_EndOfVideoFrame();
if (cycles_video_frame >= CYCLES_FRAME) {
video_scannerUpdate();
}
clock_gettime(CLOCK_MONOTONIC, &tj);
@ -412,11 +410,6 @@ cpu_runloop:
TRACE_CPU_END();
}
dbg_ticks += EXECUTION_PERIOD_NSECS;
if ((dbg_ticks % (NANOSECONDS_PER_SECOND>>1)) == 0)
{
display_flashText(); // TODO FIXME : proper FLASH timing ...
}
#if DEBUG_TIMING
// collect timing statistics
if (speaker_neg_feedback > cycles_speaker_feedback)
@ -509,18 +502,13 @@ void timing_stopCPU(void) {
}
}
unsigned int timing_currentVideoFrameCycles(void) {
ASSERT_ON_CPU_THREAD();
timing_checkpointCycles();
return cycles_this_frame + cycles_checkpoint_count;
}
// Called when accurate global cycle count info is needed
void timing_checkpointCycles(void) {
ASSERT_ON_CPU_THREAD();
const int32_t d = run_args.cpu65_cycle_count - cycles_checkpoint_count;
assert(d >= 0);
cycles_video_frame += d;
#if !TESTING
cycles_count_total += d;
#else

View File

@ -44,16 +44,16 @@
// HBL & VBL constants
// UtAIIe:3-13 "There are exactly 17030 (65 x 262) 6502 cycles in every television scan of an American Apple"
#define CYCLES_HBL 25
#define CYCLES_VIS_BEGIN CYCLES_HBL
#define CYCLES_VIS 40
#define CYCLES_SCANLINE (CYCLES_HBL + CYCLES_VIS) // 65
#define SCANLINES_VBL 70
#define SCANLINES_MIX (20*8) // 160
#define SCANLINES_VIS (64*3) // 192
#define CYCLES_HBL 25
#define CYCLES_VIS_BEGIN CYCLES_HBL
#define CYCLES_VIS 40
#define CYCLES_SCANLINE (CYCLES_HBL + CYCLES_VIS) // 65
#define SCANLINES_VBL 70
#define SCANLINES_MIX (20*8) // 160
#define SCANLINES_VIS (64*3) // 192
#define SCANLINES_VBL_BEGIN SCANLINES_VIS
#define SCANLINES_FRAME (SCANLINES_VBL + SCANLINES_VIS) // 262
#define CYCLES_FRAME (CYCLES_SCANLINE * SCANLINES_FRAME) // 17030
#define SCANLINES_FRAME (SCANLINES_VBL + SCANLINES_VIS) // 262
#define CYCLES_FRAME (CYCLES_SCANLINE * SCANLINES_FRAME) // 17030
#define CPU_SCALE_SLOWEST 0.25
#define CPU_SCALE_FASTEST_PIVOT 4.0
@ -64,6 +64,7 @@
#endif
extern unsigned long cycles_count_total; // cumulative cycles count from machine reset
extern unsigned int cycles_video_frame; // unprocessed video frame cycles count
extern double cycles_persec_target; // CLK_6502 * current CPU scale
extern int cycles_speaker_feedback; // current -/+ speaker requested feedback
@ -133,11 +134,6 @@ bool cpu_isPaused(void);
// ----------------------------------------------------------------------------
// Video frame and IRQ fine-grained timing.
/*
* Get current cycles count within this video frame
*/
unsigned int timing_currentVideoFrameCycles() CALL_ON_CPU_THREAD;
/*
* Checkpoints current cycle count and updates total (for timing-dependent I/O)
*/

View File

@ -252,6 +252,15 @@ static void _init_glnode_manager(void) {
glnode_backend.shutdown = &glnode_shutdownNodes;
glnode_backend.anim = &glnode_animations;
#if INTERFACE_CLASSIC
glnode_backend.plotChar = &display_plotChar;
glnode_backend.plotLine = &display_plotLine;
#endif
glnode_backend.flashText = &display_flashText;
glnode_backend.flushScanline = &display_flushScanline;
glnode_backend.frameComplete = &display_frameComplete;
#if INTERFACE_TOUCH
interface_onTouchEvent = &glnode_onTouchEvent;
#endif

View File

@ -315,22 +315,10 @@ static void glvideo_render(void) {
glUniformMatrix4fv(uniformMVPIdx, 1, GL_FALSE, mvpIdentity);
#endif
static uint8_t fb[SCANWIDTH*SCANHEIGHT*sizeof(uint8_t)];
#if INTERFACE_CLASSIC
interface_setStagingFramebuffer(fb);
#endif
unsigned long wasDirty = 0UL;
if (!cpu_isPaused()) {
// check if a2 video memory is dirty
wasDirty = video_clearDirty(A2_DIRTY_FLAG);
if (wasDirty) {
display_renderStagingFramebuffer(fb);
}
}
wasDirty = video_clearDirty(FB_DIRTY_FLAG);
unsigned long wasDirty = video_clearDirty(FB_DIRTY_FLAG);
char *pixels = (char *)crtModel->texPixels;
if (wasDirty) {
uint8_t *fb = display_getCurrentFramebuffer();
SCOPE_TRACE_VIDEO("pixel convert");
// Update texture from indexed-color Apple //e internal framebuffer
unsigned int count = SCANWIDTH * SCANHEIGHT;

View File

@ -23,6 +23,130 @@ 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) {
@ -32,8 +156,6 @@ void video_init(void) {
LOG("(re)setting render_thread_id : %lu -> %lu", (unsigned long)render_thread_id, (unsigned long)pthread_self());
render_thread_id = pthread_self();
video_clear();
currentBackend->init((void*)0);
}
@ -66,10 +188,276 @@ void video_main_loop(void) {
currentBackend->main_loop();
}
void video_clear(void) {
video_setDirty(A2_DIRTY_FLAG);
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;
@ -195,6 +583,14 @@ 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;
@ -202,6 +598,17 @@ static __attribute__((constructor)) void _init_video(void) {
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);

View File

@ -60,7 +60,7 @@ void video_shutdown(void);
/*
* Begin a render pass (only for non-emulator-managed main video). This should only be called on the render thread.
*/
void video_render(void);
void video_render(void) CALL_ON_UI_THREAD;
/*
* Set the render thread ID. Use with caution.
@ -72,11 +72,6 @@ void _video_setRenderThread(pthread_t id);
*/
bool video_isRenderThread(void);
/*
* Clear the current display.
*/
void video_clear(void);
#define A2_DIRTY_FLAG 0x1 // Apple //e video is dirty
#define FB_DIRTY_FLAG 0x2 // Internal framebuffer is dirty
@ -86,9 +81,9 @@ void video_clear(void);
bool video_isDirty(unsigned long flags);
/*
* Atomically set dirty bit(s), return previous bit(s) value
* Called primary from VM routines to flag when graphics mode or video memory data changes
*/
unsigned long video_setDirty(unsigned long flags);
void video_setDirty(unsigned long flags);
/*
* Atomically clear dirty bit(s), return previous bit(s) value
@ -110,6 +105,73 @@ bool video_loadState(StateHelper_s *helper);
*/
video_animation_s *video_getAnimationDriver(void);
// ----------------------------------------------------------------------------
// Video scanner
static inline drawpage_mode_t video_currentMainMode(uint32_t currswitches) {
if (currswitches & SS_TEXT) {
return DRAWPAGE_TEXT;
} else {
if (currswitches & SS_HIRES) {
return DRAWPAGE_HIRES;
} else {
return DRAWPAGE_TEXT; // (LORES)
}
}
}
static inline drawpage_mode_t video_currentMixedMode(uint32_t currswitches) {
if (currswitches & (SS_TEXT|SS_MIXED)) {
return DRAWPAGE_TEXT;
} else {
if (currswitches & SS_HIRES) {
return DRAWPAGE_HIRES;
} else {
return DRAWPAGE_TEXT; // (LORES)
}
}
}
static inline int video_currentPage(uint32_t currswitches) {
// UTAIIe : 5-25
if (currswitches & SS_80STORE) {
return 0;
}
return !!(currswitches & SS_PAGE2);
}
/*
* Update text flashing state
*/
void video_flashText(void);
/*
* Reset video scanner
*/
void video_scannerReset(void);
/*
* Update video scanner position and generate video output
*/
void video_scannerUpdate(void) CALL_ON_CPU_THREAD;
/*
* Get current video scanner address
*/
uint16_t video_scannerAddress(OUTPARM bool *ptrIsVBL);
/*
* Return current video scanner data
*/
uint8_t floating_bus(void);
#if VIDEO_TRACING
void video_scannerTraceBegin(const char *trace_file, unsigned long frameCount);
void video_scannerTraceEnd(void);
void video_scannerTraceCheckpoint(void);
bool video_scannerTraceShouldStop(void);
#endif
// ----------------------------------------------------------------------------
// Video Backend API
@ -120,6 +182,17 @@ typedef struct video_backend_s {
void (*main_loop)(void);
void (*render)(void);
void (*shutdown)(void);
#if INTERFACE_CLASSIC
// interface plotting callbacks
void (*plotChar)(const uint8_t col, const uint8_t row, const interface_colorscheme_t cs, const uint8_t c);
void (*plotLine)(const uint8_t col, const uint8_t row, const interface_colorscheme_t cs, const char *message);
#endif
void (*flashText)(void);
void (*flushScanline)(scan_data_t *scandata) CALL_ON_CPU_THREAD;
void (*frameComplete)(void) CALL_ON_CPU_THREAD;
video_animation_s *anim;
} video_backend_s;

View File

@ -336,23 +336,12 @@ static int keysym_to_scancode(void) {
// copy Apple //e video memory into XImage uint32_t buffer
static void post_image() {
static uint8_t fb[SCANWIDTH*SCANHEIGHT*sizeof(uint8_t)];
#if INTERFACE_CLASSIC
interface_setStagingFramebuffer(fb);
#endif
unsigned long wasDirty = 0UL;
// check if a2 video memory is dirty
wasDirty = video_clearDirty(A2_DIRTY_FLAG);
if (wasDirty) {
display_renderStagingFramebuffer(fb);
}
// now check/clear if we should redraw
wasDirty = video_clearDirty(FB_DIRTY_FLAG);
unsigned long wasDirty = video_clearDirty(FB_DIRTY_FLAG);
if (wasDirty) {
uint8_t index;
uint8_t *fb = display_getCurrentFramebuffer();
uint8_t index;
unsigned int count = SCANWIDTH * SCANHEIGHT;
for (unsigned int i=0, j=0; i<count; i++, j+=4)
{
@ -898,6 +887,18 @@ static void _init_xvideo(void) {
xvideo_backend.render = &xdriver_render;
xvideo_backend.shutdown = &xdriver_shutdown;
xvideo_backend.anim = &xdriver_animations;
#if INTERFACE_CLASSIC
xvideo_backend.plotChar = &display_plotChar;
xvideo_backend.plotLine = &display_plotLine;
#endif
xvideo_backend.flashText = &display_flashText;
xvideo_backend.flushScanline
= &display_flushScanline;
xvideo_backend.frameComplete
= &display_frameComplete;
video_registerBackend(&xvideo_backend, VID_PRIO_GRAPHICS_X);
}

185
src/vm.c
View File

@ -40,10 +40,8 @@ GLUE_BANK_READ(read_ram_lc, BASE_E000_RD);
GLUE_BANK_MAYBEWRITE(write_ram_lc, BASE_E000_WRT);
GLUE_BANK_READ(iie_read_ram_text_page0, BASE_TEXTRD);
GLUE_BANK_WRITE(iie_write_screen_hole_text_page0, BASE_TEXTWRT);
GLUE_BANK_READ(iie_read_ram_hires_page0, BASE_HGRRD);
GLUE_BANK_WRITE(iie_write_screen_hole_hires_page0, BASE_HGRWRT);
GLUE_BANK_READ(iie_read_ram_zpage_and_stack, BASE_STACKZP);
GLUE_BANK_WRITE(iie_write_ram_zpage_and_stack, BASE_STACKZP);
@ -132,6 +130,7 @@ GLUE_C_READ(iie_page2_off)
return floating_bus();
}
video_setDirty(A2_DIRTY_FLAG);
run_args.softswitches &= ~(SS_PAGE2|SS_SCREEN);
if (run_args.softswitches & SS_80STORE) {
@ -145,8 +144,6 @@ GLUE_C_READ(iie_page2_off)
}
}
video_setDirty(A2_DIRTY_FLAG);
return floating_bus();
}
@ -156,6 +153,7 @@ GLUE_C_READ(iie_page2_on)
return floating_bus();
}
video_setDirty(A2_DIRTY_FLAG);
run_args.softswitches |= SS_PAGE2;
if (run_args.softswitches & SS_80STORE) {
@ -171,8 +169,6 @@ GLUE_C_READ(iie_page2_on)
run_args.softswitches |= SS_SCREEN;
}
video_setDirty(A2_DIRTY_FLAG);
return floating_bus();
}
@ -184,8 +180,8 @@ GLUE_C_READ(iie_check_page2)
GLUE_C_READ(read_switch_graphics)
{
if (run_args.softswitches & SS_TEXT) {
run_args.softswitches &= ~SS_TEXT;
video_setDirty(A2_DIRTY_FLAG);
run_args.softswitches &= ~SS_TEXT;
}
return floating_bus();
}
@ -193,8 +189,8 @@ GLUE_C_READ(read_switch_graphics)
GLUE_C_READ(read_switch_text)
{
if (!(run_args.softswitches & SS_TEXT)) {
run_args.softswitches |= SS_TEXT;
video_setDirty(A2_DIRTY_FLAG);
run_args.softswitches |= SS_TEXT;
}
return floating_bus();
}
@ -207,8 +203,8 @@ GLUE_C_READ(iie_check_text)
GLUE_C_READ(read_switch_no_mixed)
{
if (run_args.softswitches & SS_MIXED) {
run_args.softswitches &= ~SS_MIXED;
video_setDirty(A2_DIRTY_FLAG);
run_args.softswitches &= ~SS_MIXED;
}
return floating_bus();
}
@ -216,8 +212,8 @@ GLUE_C_READ(read_switch_no_mixed)
GLUE_C_READ(read_switch_mixed)
{
if (!(run_args.softswitches & SS_MIXED)) {
run_args.softswitches |= SS_MIXED;
video_setDirty(A2_DIRTY_FLAG);
run_args.softswitches |= SS_MIXED;
}
return floating_bus();
}
@ -241,6 +237,7 @@ GLUE_C_READ(iie_hires_off)
return floating_bus();
}
video_setDirty(A2_DIRTY_FLAG);
run_args.softswitches &= ~(SS_HIRES|SS_HGRRD|SS_HGRWRT);
run_args.base_hgrrd = apple_ii_64k[0];
run_args.base_hgrwrt = apple_ii_64k[0];
@ -255,8 +252,6 @@ GLUE_C_READ(iie_hires_off)
run_args.softswitches |= SS_HGRWRT;
}
video_setDirty(A2_DIRTY_FLAG);
return floating_bus();
}
@ -266,6 +261,7 @@ GLUE_C_READ(iie_hires_on)
return floating_bus();
}
video_setDirty(A2_DIRTY_FLAG);
run_args.softswitches |= SS_HIRES;
if (run_args.softswitches & SS_80STORE) {
@ -280,8 +276,6 @@ GLUE_C_READ(iie_hires_on)
}
}
video_setDirty(A2_DIRTY_FLAG);
return floating_bus();
}
@ -290,15 +284,137 @@ GLUE_C_READ(iie_check_hires)
return (run_args.softswitches & SS_HIRES) ? 0x80 : 0x00;
}
GLUE_C_WRITE(video__write_2e_text0)
{
do {
drawpage_mode_t mode = video_currentMainMode(run_args.softswitches);
if (mode == DRAWPAGE_HIRES) {
break;
}
if (!(run_args.softswitches & SS_PAGE2)) {
video_setDirty(A2_DIRTY_FLAG);
}
} while (0);
run_args.base_textwrt[ea] = b;
}
GLUE_C_WRITE(video__write_2e_text0_mixed)
{
do {
drawpage_mode_t mode = video_currentMixedMode(run_args.softswitches);
if (mode == DRAWPAGE_HIRES) {
break;
}
if (!(run_args.softswitches & SS_PAGE2)) {
video_setDirty(A2_DIRTY_FLAG);
}
} while (0);
run_args.base_textwrt[ea] = b;
}
GLUE_C_WRITE(video__write_2e_text1)
{
do {
drawpage_mode_t mode = video_currentMainMode(run_args.softswitches);
if (mode == DRAWPAGE_HIRES) {
break;
}
if ((run_args.softswitches & SS_PAGE2) && !(run_args.softswitches & SS_80STORE)) {
video_setDirty(A2_DIRTY_FLAG);
}
} while (0);
run_args.base_ramwrt[ea] = b;
}
GLUE_C_WRITE(video__write_2e_text1_mixed)
{
do {
drawpage_mode_t mode = video_currentMixedMode(run_args.softswitches);
if (mode == DRAWPAGE_HIRES) {
break;
}
if ((run_args.softswitches & SS_PAGE2) && !(run_args.softswitches & SS_80STORE)) {
video_setDirty(A2_DIRTY_FLAG);
}
} while (0);
run_args.base_ramwrt[ea] = b;
}
GLUE_C_WRITE(video__write_2e_hgr0)
{
do {
drawpage_mode_t mode = video_currentMainMode(run_args.softswitches);
if (mode == DRAWPAGE_TEXT) {
break;
}
if (!(run_args.softswitches & SS_PAGE2)) {
video_setDirty(A2_DIRTY_FLAG);
}
} while (0);
run_args.base_hgrwrt[ea] = b;
}
GLUE_C_WRITE(video__write_2e_hgr0_mixed)
{
do {
drawpage_mode_t mode = video_currentMixedMode(run_args.softswitches);
if (mode == DRAWPAGE_TEXT) {
break;
}
if (!(run_args.softswitches & SS_PAGE2)) {
video_setDirty(A2_DIRTY_FLAG);
}
} while (0);
run_args.base_hgrwrt[ea] = b;
}
GLUE_C_WRITE(video__write_2e_hgr1)
{
do {
drawpage_mode_t mode = video_currentMainMode(run_args.softswitches);
if (mode == DRAWPAGE_TEXT) {
break;
}
if ((run_args.softswitches & SS_PAGE2) && !(run_args.softswitches & SS_80STORE)) {
video_setDirty(A2_DIRTY_FLAG);
}
} while (0);
run_args.base_ramwrt[ea] = b;
}
GLUE_C_WRITE(video__write_2e_hgr1_mixed)
{
do {
drawpage_mode_t mode = video_currentMixedMode(run_args.softswitches);
if (mode == DRAWPAGE_TEXT) {
break;
}
if ((run_args.softswitches & SS_PAGE2) && !(run_args.softswitches & SS_80STORE)) {
video_setDirty(A2_DIRTY_FLAG);
}
} while (0);
run_args.base_ramwrt[ea] = b;
}
// ----------------------------------------------------------------------------
// GC softswitches : Game Controller (joystick/paddles)
#define JOY_STEP_USEC (3300.0 / 256.0)
#define CYCLES_PER_USEC (CLK_6502 / 1000000)
#define JOY_STEP_CYCLES (JOY_STEP_USEC / CYCLES_PER_USEC)
GLUE_INLINE_READ(read_button0, JOY_BUTTON0);
GLUE_C_READ(read_button0)
{
uint8_t b0 = floating_bus() & (~0x80);
uint8_t b = run_args.joy_button0 & 0x80;
return b0 | b;
}
GLUE_INLINE_READ(read_button1, JOY_BUTTON1);
GLUE_C_READ(read_button1)
{
uint8_t b0 = floating_bus() & (~0x80);
uint8_t b = run_args.joy_button1 & 0x80;
return b0 | b;
}
GLUE_C_READ(read_button2)
{
@ -528,6 +644,7 @@ GLUE_C_READ(iie_80store_off)
return floating_bus();
}
video_setDirty(A2_DIRTY_FLAG);
run_args.softswitches &= ~(SS_80STORE|SS_TEXTRD|SS_TEXTWRT|SS_HGRRD|SS_HGRWRT);
run_args.base_textrd = apple_ii_64k[0];
@ -551,8 +668,6 @@ GLUE_C_READ(iie_80store_off)
run_args.softswitches |= SS_SCREEN;
}
video_setDirty(A2_DIRTY_FLAG);
return floating_bus();
}
@ -562,6 +677,7 @@ GLUE_C_READ(iie_80store_on)
return floating_bus();
}
video_setDirty(A2_DIRTY_FLAG);
run_args.softswitches |= SS_80STORE;
if (run_args.softswitches & SS_PAGE2) {
@ -585,7 +701,6 @@ GLUE_C_READ(iie_80store_on)
}
run_args.softswitches &= ~SS_SCREEN;
video_setDirty(A2_DIRTY_FLAG);
return floating_bus();
}
@ -601,6 +716,7 @@ GLUE_C_READ(iie_ramrd_main)
return floating_bus();
}
video_setDirty(A2_DIRTY_FLAG);
run_args.softswitches &= ~SS_RAMRD;
run_args.base_ramrd = apple_ii_64k[0];
@ -615,8 +731,6 @@ GLUE_C_READ(iie_ramrd_main)
run_args.base_hgrrd = apple_ii_64k[0];
}
video_setDirty(A2_DIRTY_FLAG);
return floating_bus();
}
@ -626,6 +740,7 @@ GLUE_C_READ(iie_ramrd_aux)
return floating_bus();
}
video_setDirty(A2_DIRTY_FLAG);
run_args.softswitches |= SS_RAMRD;
run_args.base_ramrd = apple_ii_64k[1];
@ -640,8 +755,6 @@ GLUE_C_READ(iie_ramrd_aux)
run_args.base_hgrrd = apple_ii_64k[1];
}
video_setDirty(A2_DIRTY_FLAG);
return floating_bus();
}
@ -750,8 +863,8 @@ GLUE_C_READ(iie_80col_off)
return floating_bus();
}
run_args.softswitches &= ~SS_80COL;
video_setDirty(A2_DIRTY_FLAG);
run_args.softswitches &= ~SS_80COL;
return floating_bus();
}
@ -762,8 +875,8 @@ GLUE_C_READ(iie_80col_on)
return floating_bus();
}
run_args.softswitches |= SS_80COL;
video_setDirty(A2_DIRTY_FLAG);
run_args.softswitches |= SS_80COL;
return floating_bus();
}
@ -776,9 +889,9 @@ GLUE_C_READ(iie_check_80col)
GLUE_C_READ(iie_altchar_off)
{
if (run_args.softswitches & SS_ALTCHAR) {
video_setDirty(A2_DIRTY_FLAG);
run_args.softswitches &= ~SS_ALTCHAR;
display_loadFont(/*start:*/0x40, /*qty:*/0x40, /*data:*/ucase_glyphs, FONT_MODE_FLASH);
video_setDirty(A2_DIRTY_FLAG);
}
return floating_bus();
}
@ -786,10 +899,10 @@ GLUE_C_READ(iie_altchar_off)
GLUE_C_READ(iie_altchar_on)
{
if (!(run_args.softswitches & SS_ALTCHAR)) {
video_setDirty(A2_DIRTY_FLAG);
run_args.softswitches |= SS_ALTCHAR;
display_loadFont(/*start:*/0x40, /*qty:*/0x20, /*data:*/mousetext_glyphs, FONT_MODE_MOUSETEXT);
display_loadFont(/*start:*/0x60, /*qty:*/0x20, /*data:*/lcase_glyphs, FONT_MODE_INVERSE);
video_setDirty(A2_DIRTY_FLAG);
}
return floating_bus();
}
@ -820,8 +933,8 @@ GLUE_C_READ(iie_check_ioudis)
GLUE_C_READ(iie_dhires_on)
{
if (!(run_args.softswitches & SS_DHIRES)) {
run_args.softswitches |= SS_DHIRES;
video_setDirty(A2_DIRTY_FLAG);
run_args.softswitches |= SS_DHIRES;
}
return floating_bus();
}
@ -829,8 +942,8 @@ GLUE_C_READ(iie_dhires_on)
GLUE_C_READ(iie_dhires_off)
{
if (run_args.softswitches & SS_DHIRES) {
run_args.softswitches &= ~SS_DHIRES;
video_setDirty(A2_DIRTY_FLAG);
run_args.softswitches &= ~SS_DHIRES;
}
return floating_bus();
}
@ -843,10 +956,10 @@ GLUE_C_READ(iie_check_dhires)
GLUE_C_READ(iie_check_vbl)
{
bool vbl_bar = false;
video_scanner_get_address(&vbl_bar);
bool isVBL = false;
video_scannerAddress(&isVBL);
uint8_t key = apple_ii_64k[0][0xC000];
return (key & ~0x80) | (vbl_bar ? 0x80 : 0x00);
return (key & ~0x80) | (isVBL ? 0x00 : 0x80);
}
GLUE_C_READ(iie_c3rom_peripheral)
@ -903,7 +1016,9 @@ GLUE_C_READ(iie_read_slot_expansion)
// ... Also Need moar tests ...
if (ea == 0xCFFF) {
// disable expansion ROM
return floating_bus();
}
return apple_ii_64k[1][ea];
}
@ -1020,12 +1135,12 @@ static void _initialize_tables(void) {
// write-functions in place only at the `screen holes', hence the name.
for (unsigned int i = 0x400; i < 0x800; i++) {
cpu65_vmem_r[i] = iie_read_ram_text_page0;
cpu65_vmem_w[i] = iie_write_screen_hole_text_page0;
cpu65_vmem_w[i] = video__write_2e_text0;
}
for (unsigned int i = 0x2000; i < 0x4000; i++) {
cpu65_vmem_r[i] = iie_read_ram_hires_page0;
cpu65_vmem_w[i] = iie_write_screen_hole_hires_page0;
cpu65_vmem_w[i] = video__write_2e_hgr0;
}
// softswich rom
@ -1205,7 +1320,7 @@ void vm_initialize(void) {
_initialize_iie_switches();
c_joystick_reset();
run_args.softswitches = SS_TEXT | SS_IOUDIS | SS_C3ROM | SS_LCWRT | SS_LCSEC;
run_args.softswitches = SS_TEXT | SS_BANK2;
}
void vm_reinitializeAudio(void) {