Beginning to refactor display and backend video

- Futher disentangle display, interface, and video backends
    - Backend video owns the staging/intermediate framebuffer for now
    - Add the beginnings of display update callbacks
This commit is contained in:
Aaron Culliney 2017-08-05 08:59:07 -10:00
parent 0801c9f010
commit f8b4602fca
17 changed files with 577 additions and 428 deletions

View File

@ -31,12 +31,13 @@ ASM_SRC_x86 = \
src/x86/glue.S src/x86/cpu.S
VIDEO_SRC = \
src/video/xvideo.c \
src/video/glvideo.c \
src/video/glutinput.c \
src/video/glnode.c \
src/video/glhudmodel.c \
src/video/glalert.c \
src/video/video.c \
src/video/xvideo.c \
src/video_util/matrixUtil.c \
src/video_util/modelUtil.c \
src/video_util/sourceUtil.c \

View File

@ -170,6 +170,15 @@ dnl Video ...
AC_PATH_XTRA
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"
AC_ARG_ENABLE([opengl], AS_HELP_STRING([--enable-opengl], [Enable OpenGL graphics output (autodetected)]))
AC_CHECK_HEADER(GL/glew.h, [
AC_CHECK_HEADER(GL/freeglut.h, [
@ -181,15 +190,15 @@ AC_CHECK_HEADER(GL/glew.h, [
AC_DEFINE(VIDEO_OPENGL, 1, [Building with OpenGL support])
AC_DEFINE(USE_GLUT, 1, [Use GLUT library])
VIDEO_O="src/video/glvideo.o src/video/glnode.o src/video/glalert.o src/video/glhudmodel.o src/video/glutinput.o src/video_util/matrixUtil.o src/video_util/modelUtil.o src/video_util/sourceUtil.o src/video_util/vectorUtil.o"
VIDEO_O="$VIDEO_O src/video/glvideo.o src/video/glnode.o src/video/glalert.o src/video/glhudmodel.o src/video/glutinput.o src/video_util/matrixUtil.o src/video_util/modelUtil.o src/video_util/sourceUtil.o src/video_util/vectorUtil.o"
dnl HACK there's gotta be a better way ... without this verbosity, CFLAGS are not correct (lacking -DTESTING=1 , etc) if we don't specify specific obj files for test binaries
testcpu_VIDEO_O="src/video/testcpu-glvideo.o src/video/testcpu-glnode.o src/video/testcpu-glalert.o src/video/testcpu-glhudmodel.o src/video/testcpu-glutinput.o src/video_util/testcpu-matrixUtil.o src/video_util/testcpu-modelUtil.o src/video_util/testcpu-sourceUtil.o src/video_util/testcpu-vectorUtil.o"
testdisk_VIDEO_O="src/video/testdisk-glvideo.o src/video/testdisk-glnode.o src/video/testdisk-glalert.o src/video/testdisk-glhudmodel.o src/video/testdisk-glutinput.o src/video_util/testdisk-matrixUtil.o src/video_util/testdisk-modelUtil.o src/video_util/testdisk-sourceUtil.o src/video_util/testdisk-vectorUtil.o"
testdisplay_VIDEO_O="src/video/testdisplay-glvideo.o src/video/testdisplay-glnode.o src/video/testdisplay-glalert.o src/video/testdisplay-glhudmodel.o src/video/testdisplay-glutinput.o src/video_util/testdisplay-matrixUtil.o src/video_util/testdisplay-modelUtil.o src/video_util/testdisplay-sourceUtil.o src/video_util/testdisplay-vectorUtil.o"
testprefs_VIDEO_O="src/video/testprefs-glvideo.o src/video/testprefs-glnode.o src/video/testprefs-glalert.o src/video/testprefs-glhudmodel.o src/video/testprefs-glutinput.o src/video_util/testprefs-matrixUtil.o src/video_util/testprefs-modelUtil.o src/video_util/testprefs-sourceUtil.o src/video_util/testprefs-vectorUtil.o"
testtrace_VIDEO_O="src/video/testtrace-glvideo.o src/video/testtrace-glnode.o src/video/testtrace-glalert.o src/video/testtrace-glhudmodel.o src/video/testtrace-glutinput.o src/video_util/testtrace-matrixUtil.o src/video_util/testtrace-modelUtil.o src/video_util/testtrace-sourceUtil.o src/video_util/testtrace-vectorUtil.o"
testui_VIDEO_O="src/video/testui-glvideo.o src/video/testui-glnode.o src/video/testui-glalert.o src/video/testui-glhudmodel.o src/video/testui-glutinput.o src/video_util/testui-matrixUtil.o src/video_util/testui-modelUtil.o src/video_util/testui-sourceUtil.o src/video_util/testui-vectorUtil.o"
testvm_VIDEO_O="src/video/testvm-glvideo.o src/video/testvm-glnode.o src/video/testvm-glalert.o src/video/testvm-glhudmodel.o src/video/testvm-glutinput.o src/video_util/testvm-matrixUtil.o src/video_util/testvm-modelUtil.o src/video_util/testvm-sourceUtil.o src/video_util/testvm-vectorUtil.o"
testcpu_VIDEO_O="$testcpu_VIDEO_O src/video/testcpu-glvideo.o src/video/testcpu-glnode.o src/video/testcpu-glalert.o src/video/testcpu-glhudmodel.o src/video/testcpu-glutinput.o src/video_util/testcpu-matrixUtil.o src/video_util/testcpu-modelUtil.o src/video_util/testcpu-sourceUtil.o src/video_util/testcpu-vectorUtil.o"
testdisk_VIDEO_O="$testdisk_VIDEO_O src/video/testdisk-glvideo.o src/video/testdisk-glnode.o src/video/testdisk-glalert.o src/video/testdisk-glhudmodel.o src/video/testdisk-glutinput.o src/video_util/testdisk-matrixUtil.o src/video_util/testdisk-modelUtil.o src/video_util/testdisk-sourceUtil.o src/video_util/testdisk-vectorUtil.o"
testdisplay_VIDEO_O="$testdisplay_VIDEO_O src/video/testdisplay-glvideo.o src/video/testdisplay-glnode.o src/video/testdisplay-glalert.o src/video/testdisplay-glhudmodel.o src/video/testdisplay-glutinput.o src/video_util/testdisplay-matrixUtil.o src/video_util/testdisplay-modelUtil.o src/video_util/testdisplay-sourceUtil.o src/video_util/testdisplay-vectorUtil.o"
testprefs_VIDEO_O="$testprefs_VIDEO_O src/video/testprefs-glvideo.o src/video/testprefs-glnode.o src/video/testprefs-glalert.o src/video/testprefs-glhudmodel.o src/video/testprefs-glutinput.o src/video_util/testprefs-matrixUtil.o src/video_util/testprefs-modelUtil.o src/video_util/testprefs-sourceUtil.o src/video_util/testprefs-vectorUtil.o"
testtrace_VIDEO_O="$testtrace_VIDEO_O src/video/testtrace-glvideo.o src/video/testtrace-glnode.o src/video/testtrace-glalert.o src/video/testtrace-glhudmodel.o src/video/testtrace-glutinput.o src/video_util/testtrace-matrixUtil.o src/video_util/testtrace-modelUtil.o src/video_util/testtrace-sourceUtil.o src/video_util/testtrace-vectorUtil.o"
testui_VIDEO_O="$testui_VIDEO_O src/video/testui-glvideo.o src/video/testui-glnode.o src/video/testui-glalert.o src/video/testui-glhudmodel.o src/video/testui-glutinput.o src/video_util/testui-matrixUtil.o src/video_util/testui-modelUtil.o src/video_util/testui-sourceUtil.o src/video_util/testui-vectorUtil.o"
testvm_VIDEO_O="$testvm_VIDEO_O src/video/testvm-glvideo.o src/video/testvm-glnode.o src/video/testvm-glalert.o src/video/testvm-glhudmodel.o src/video/testvm-glutinput.o src/video_util/testvm-matrixUtil.o src/video_util/testvm-modelUtil.o src/video_util/testvm-sourceUtil.o src/video_util/testvm-vectorUtil.o"
AC_MSG_RESULT([Building emulator with OpenGL support, w00t!])
])
], [

View File

@ -72,9 +72,10 @@
#include "vm.h"
#include "timing.h"
#include "cpu.h"
#include "display.h"
#include "disk.h"
#include "interface.h"
#include "display.h"
#include "video/video.h"
#include "disk.h"
#include "keys.h"
#include "joystick.h"
#include "glue.h"

View File

@ -21,25 +21,8 @@
#define DYNAMIC_SZ 11 // 7 pixels (as bytes) + 2pre + 2post
typedef enum drawpage_mode_t {
DRAWPAGE_TEXT = 1,
DRAWPAGE_HIRES,
} drawpage_mode_t;
typedef struct backend_node_s {
struct backend_node_s *next;
long order;
video_backend_s *backend;
} backend_node_s;
static backend_node_s *head = NULL;
// framebuffers
static uint8_t *video__fb = NULL;
A2Color_s colormap[256] = { { 0 } };
static pthread_t render_thread_id = 0;
static pthread_mutex_t video_scan_mutex = PTHREAD_MUTEX_INITIALIZER;
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 };
@ -48,13 +31,13 @@ static uint8_t video__int_font[3][0x4000] = { { 0 } }; // interface font
static color_mode_t color_mode = COLOR_NONE;
// Precalculated framebuffer offsets given VM addr
unsigned int video__screen_addresses[8192] = { INT_MIN };
uint8_t video__columns[8192] = { 0 };
static unsigned int video__screen_addresses[8192] = { INT_MIN };
static uint8_t video__columns[8192] = { 0 };
uint8_t video__hires_even[0x800] = { 0 };
uint8_t video__hires_odd[0x800] = { 0 };
static uint8_t video__hires_even[0x800] = { 0 };
static uint8_t video__hires_odd[0x800] = { 0 };
volatile unsigned long _vid_dirty = 0;
static volatile unsigned long _vid_dirty = 0;
// Video constants -- sourced from AppleWin
static const bool bVideoScannerNTSC = true;
@ -74,17 +57,20 @@ 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
uint8_t video__odd_colors[2] = { COLOR_LIGHT_PURPLE, COLOR_LIGHT_BLUE };
uint8_t video__even_colors[2] = { COLOR_LIGHT_GREEN, COLOR_LIGHT_RED };
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 };
static display_update_fn textCallbackFn = NULL;
static display_update_fn hiresCallbackFn = NULL;
// 40col/80col/lores/hires/dhires line offsets
unsigned short video__line_offset[TEXT_ROWS] = {
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
};
uint8_t video__dhires1[256] = {
static uint8_t video__dhires1[256] = {
0x0,0x1,0x2,0x3,0x4,0x5,0x6,0x7,0x8,0x9,0xa,0xb,0xc,0xd,0xe,0xf,
0x0,0x1,0x2,0x3,0x4,0x5,0x6,0x7,0x8,0x9,0xa,0xb,0xc,0xd,0xe,0xf,
0x0,0x1,0x2,0x3,0x4,0x5,0x6,0x7,0x8,0x9,0xa,0xb,0xc,0xd,0xe,0xf,
@ -95,7 +81,7 @@ uint8_t video__dhires1[256] = {
0x0,0x1,0x2,0x3,0x4,0x5,0x6,0x7,0x8,0x9,0xa,0xb,0xc,0xd,0xe,0xf,
};
uint8_t video__dhires2[256] = {
static uint8_t video__dhires2[256] = {
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,
0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x9,0x9,0x9,0x9,0x9,0x9,0x9,0x9,
0x2,0x2,0x2,0x2,0x2,0x2,0x2,0x2,0xa,0xa,0xa,0xa,0xa,0xa,0xa,0xa,
@ -492,16 +478,16 @@ static void video_prefsChanged(const char *domain) {
val = COLOR_INTERP;
}
color_mode = (color_mode_t)val;
video_reset();
display_reset();
}
void video_reset(void) {
void display_reset(void) {
_initialize_hires_values();
_initialize_tables_video();
video_setDirty(A2_DIRTY_FLAG);
}
void video_loadfont(int first, int quantity, const uint8_t *data, int mode) {
void display_loadFont(int first, int quantity, const uint8_t *data, int mode) {
uint8_t fg = 0;
uint8_t bg = 0;
switch (mode) {
@ -646,11 +632,11 @@ static inline void __plot_character40(const unsigned int font_off, uint8_t *fb_p
_plot_char40(/*dst*/&fb_ptr, /*src*/&font_ptr);
}
static void _plot_character40(uint16_t off, int page, int bank) {
static void _plot_character40(uint16_t off, int page, int bank, uint8_t *fb_ptr) {
uint16_t base = page ? 0x0800 : 0x0400;
uint16_t ea = base+off;
uint8_t b = apple_ii_64k[bank][ea];
__plot_character40(b<<7/* *128 */, video__fb+video__screen_addresses[off]);
__plot_character40(b<<7/* *128 */, fb_ptr+video__screen_addresses[off]);
}
static inline void __plot_character80(const unsigned int font_off, uint8_t *fb_ptr) {
@ -665,16 +651,16 @@ static inline void __plot_character80(const unsigned int font_off, uint8_t *fb_p
_plot_char80(/*dst*/&fb_ptr, /*src*/&font_ptr, SCANWIDTH);
}
static void _plot_character80(uint16_t off, int page, int bank) {
static void _plot_character80(uint16_t off, int page, int bank, uint8_t *fb_ptr) {
uint16_t base = page ? 0x0800 : 0x0400;
uint16_t ea = base+off;
{
uint8_t b = apple_ii_64k[1][ea];
__plot_character80(b<<6/* *64 */, video__fb+video__screen_addresses[off]);
__plot_character80(b<<6/* *64 */, fb_ptr+video__screen_addresses[off]);
}
{
uint8_t b = apple_ii_64k[0][ea];
__plot_character80(b<<6/* *64 */, video__fb+video__screen_addresses[off]+7);
__plot_character80(b<<6/* *64 */, fb_ptr+video__screen_addresses[off]+7);
}
}
@ -703,11 +689,11 @@ static inline void __plot_block40(const uint8_t val, uint8_t *fb_ptr) {
_plot_lores40(/*dst*/&fb_ptr, val32);
}
static void _plot_block40(uint16_t off, int page, int bank) {
static void _plot_block40(uint16_t off, int page, int bank, uint8_t *fb_ptr) {
uint16_t base = page ? 0x0800 : 0x0400;
uint16_t ea = base+off;
uint8_t b = apple_ii_64k[bank][ea];
__plot_block40(b, video__fb+video__screen_addresses[off]);
__plot_block40(b, fb_ptr+video__screen_addresses[off]);
}
static inline void __plot_block80(const uint8_t val, uint8_t *fb_ptr) {
@ -747,7 +733,7 @@ static inline uint8_t __shift_block80(uint8_t b) {
return b;
}
static void _plot_block80(uint16_t off, int page, int bank) {
static void _plot_block80(uint16_t off, int page, int bank, uint8_t *fb_ptr) {
uint16_t base = page ? 0x0800 : 0x0400;
uint16_t ea = base+off;
@ -757,20 +743,20 @@ static void _plot_block80(uint16_t off, int page, int bank) {
{
uint8_t b = apple_ii_64k[1][ea];
b = __shift_block80(b);
uint8_t *fb = video__fb+video__screen_addresses[off];
uint8_t *fb = fb_ptr+video__screen_addresses[off];
__plot_block80(b, fb);
}
// plot odd half-block from main mem
{
uint8_t b = apple_ii_64k[0][ea];
uint8_t *fb = video__fb+video__screen_addresses[off] + 7;
uint8_t *fb = fb_ptr+video__screen_addresses[off] + 7;
__plot_block80(b, fb);
}
}
static void (*_textpage_plotter(uint32_t currswitches, uint32_t txtflags))(uint16_t, int, int) {
void (*plotFn)(uint16_t, int, int) = NULL;
static void (*_textpage_plotter(uint32_t currswitches, uint32_t txtflags))(uint16_t, int, int, uint8_t*) {
void (*plotFn)(uint16_t, int, int, uint8_t*) = NULL;
if (currswitches & txtflags) {
plotFn = (currswitches & SS_80COL) ? _plot_character80 : _plot_character40;
@ -874,16 +860,59 @@ GLUE_C_WRITE(video__write_2e_text1_mixed)
// ----------------------------------------------------------------------------
// Classic interface and printing HUD messages
void interface_plotChar(uint8_t *fboff, int fb_pix_width, interface_colorscheme_t cs, uint8_t c) {
static void _display_plotChar(uint8_t *fboff, const unsigned int fbPixWidth, const interface_colorscheme_t cs, const uint8_t c) {
uint8_t *src = video__int_font[cs] + c * (FONT_GLYPH_X*FONT_GLYPH_Y);
_plot_char80(&fboff, &src, fb_pix_width);
_plot_char80(&fboff, &src, fb_pix_width);
_plot_char80(&fboff, &src, fb_pix_width);
_plot_char80(&fboff, &src, fb_pix_width);
_plot_char80(&fboff, &src, fb_pix_width);
_plot_char80(&fboff, &src, fb_pix_width);
_plot_char80(&fboff, &src, fb_pix_width);
_plot_char80(&fboff, &src, fb_pix_width);
_plot_char80(&fboff, &src, fbPixWidth);
_plot_char80(&fboff, &src, fbPixWidth);
_plot_char80(&fboff, &src, fbPixWidth);
_plot_char80(&fboff, &src, fbPixWidth);
_plot_char80(&fboff, &src, fbPixWidth);
_plot_char80(&fboff, &src, fbPixWidth);
_plot_char80(&fboff, &src, fbPixWidth);
_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) {
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);
}
}
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 (fb) {
unsigned int off = row * fbPixWidth * FONT_HEIGHT_PIXELS + x * FONT80_WIDTH_PIXELS + xAdjust;
_display_plotChar(fb+off, fbPixWidth, cs, c);
} else if (textCallbackFn) {
textCallbackFn((pixel_delta_t){ .row = row, .col = x, .b = c, .cs = cs });
}
}
}
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);
video_setDirty(FB_DIRTY_FLAG);
}
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);
assert(message_rows < 24);
int fbPixWidth = (message_cols*FONT80_WIDTH_PIXELS);
for (int row=0, idx=0; row<message_rows; row++, idx+=message_cols+1) {
_display_plotLine(fb, fbPixWidth, /*xAdjust:*/0, /*col:*/0, row, cs, &message[ idx ]);
}
}
// ----------------------------------------------------------------------------
@ -897,11 +926,11 @@ static inline void __plot_hires80_pixels(uint8_t idx, uint8_t *fb_ptr) {
*((uint32_t *)(fb_ptr+SCANWIDTH)) = b;
}
static inline void __plot_hires80(uint16_t base, uint16_t ea) {
static inline void __plot_hires80(uint16_t base, uint16_t ea, uint8_t *fb_ptr) {
ea &= ~0x1;
uint16_t memoff = ea - base;
uint8_t *fb_ptr = video__fb+video__screen_addresses[memoff];
fb_ptr = fb_ptr+video__screen_addresses[memoff];
uint8_t col = video__columns[memoff];
uint8_t b0 = 0x0;
@ -966,10 +995,10 @@ static inline void __plot_hires80(uint16_t base, uint16_t ea) {
__plot_hires80_pixels(b, fb_ptr);
}
static void _plot_hires80(uint16_t off, int page, int bank, bool is_even) {
static void _plot_hires80(uint16_t off, int page, int bank, bool is_even, uint8_t *fb_ptr) {
uint16_t base = page ? 0x4000 : 0x2000;
uint16_t ea = base+off;
__plot_hires80(base, ea);
__plot_hires80(base, ea, fb_ptr);
}
// ----------------------------------------------------------------------------
@ -1016,12 +1045,12 @@ static inline void _plot_hires_pixels(uint8_t *dst, const uint8_t *src) {
}
}
static void _plot_hires40(uint16_t off, int page, int bank, bool is_even) {
static void _plot_hires40(uint16_t off, int page, int bank, bool is_even, uint8_t *fb_ptr) {
uint16_t base = page ? 0x4000 : 0x2000;
uint16_t ea = base+off;
uint8_t b = apple_ii_64k[bank][ea];
uint8_t *fb_ptr = video__fb+video__screen_addresses[off];
fb_ptr = fb_ptr+video__screen_addresses[off];
uint8_t _buf[DYNAMIC_SZ] = { 0 };
uint8_t *color_buf = (uint8_t *)_buf; // <--- work around for -Wstrict-aliasing
@ -1099,7 +1128,7 @@ static void _plot_hires40(uint16_t off, int page, int bank, bool is_even) {
_plot_hires_pixels(fb_ptr-4, color_buf);
}
static void (*_hirespage_plotter(uint32_t currswitches))(uint16_t, int, int, bool) {
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;
}
@ -1201,93 +1230,6 @@ GLUE_C_WRITE(video__write_2e_odd1_mixed)
// ----------------------------------------------------------------------------
void video_init(void) {
assert(pthread_self() != cpu_thread_id);
LOG("(re)setting render_thread_id : %ld -> %ld", render_thread_id, pthread_self());
render_thread_id = pthread_self();
assert(!video__fb);
video__fb = MALLOC(SCANWIDTH*SCANHEIGHT*sizeof(uint8_t));
video_clear();
video_getCurrentBackend()->init((void*)0);
}
void _video_setRenderThread(pthread_t id) {
LOG("setting render_thread_id : %ld -> %ld", render_thread_id, id);
render_thread_id = id;
}
bool video_isRenderThread(void) {
return (pthread_self() == render_thread_id);
}
void video_shutdown(void) {
#if MOBILE_DEVICE
// WARNING : shutdown should occur on the render thread. Platform code (iOS, Android) should ensure this is called
// from within a render pass...
assert(!render_thread_id || pthread_self() == render_thread_id);
#endif
video_getCurrentBackend()->shutdown();
if (pthread_self() == render_thread_id) {
FREE(video__fb);
}
}
void video_render(void) {
assert(pthread_self() == render_thread_id);
video_getCurrentBackend()->render();
}
void video_main_loop(void) {
video_getCurrentBackend()->main_loop();
}
void video_clear(void) {
memset(video__fb, 0x0, sizeof(uint8_t)*SCANWIDTH*SCANHEIGHT);
video_setDirty(A2_DIRTY_FLAG);
}
bool video_saveState(StateHelper_s *helper) {
bool saved = false;
int fd = helper->fd;
do {
uint8_t state = 0x0;
if (!helper->save(fd, &state, 1)) {
break;
}
LOG("SAVE (no-op) video__current_page = %02x", state);
saved = true;
} while (0);
return saved;
}
bool video_loadState(StateHelper_s *helper) {
bool loaded = false;
int fd = helper->fd;
do {
uint8_t state = 0x0;
if (!helper->load(fd, &state, 1)) {
break;
}
LOG("LOAD (no-op) video__current_page = %02x", state);
loaded = true;
} while (0);
return loaded;
}
// ----------------------------------------------------------------------------
static inline void _currentPageAndBank(uint32_t currswitches, drawpage_mode_t mode, OUTPARM int *page, OUTPARM int *bank) {
// UTAIIe : 5-25
if (currswitches & SS_80STORE) {
@ -1305,15 +1247,21 @@ static inline void _currentPageAndBank(uint32_t currswitches, drawpage_mode_t mo
*bank = 0;
}
uint8_t *video_currentFramebuffer(void) {
return video__fb;
void display_setUpdateCallback(drawpage_mode_t mode, display_update_fn updateFn) {
if (mode == DRAWPAGE_TEXT) {
textCallbackFn = updateFn;
} else if (mode == DRAWPAGE_TEXT) {
hiresCallbackFn = updateFn;
} else {
assert(false);
}
}
uint8_t *video_scan(void) {
void display_renderStagingFramebuffer(uint8_t *stagingFB) {
#warning FIXME TODO ... this needs to scan memory in the same way as the actually //e video scanner
pthread_mutex_lock(&video_scan_mutex);
pthread_mutex_lock(&display_scan_mutex);
int page = 0;
int bank = 0;
@ -1325,22 +1273,22 @@ uint8_t *video_scan(void) {
_currentPageAndBank(mainswitches, mainDrawPageMode, &page, &bank);
if (mainDrawPageMode == DRAWPAGE_TEXT) {
void (*textMainPlotFn)(uint16_t, int, int) = _textpage_plotter(mainswitches, SS_TEXT);
void (*textMainPlotFn)(uint16_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++) {
uint16_t off = video__line_offset[y] + x;
textMainPlotFn(off, page, bank);
textMainPlotFn(off, page, bank, stagingFB);
}
}
} 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) = _hirespage_plotter(mainswitches);
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++) {
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));
hiresMainPlotFn(off, page, bank, /*even*/!(x & 1), stagingFB);
}
}
}
@ -1353,23 +1301,23 @@ uint8_t *video_scan(void) {
_currentPageAndBank(mixedswitches, mixedDrawPageMode, &page, &bank);
if (mixedDrawPageMode == DRAWPAGE_TEXT) {
void (*textMixedPlotFn)(uint16_t, int, int) = _textpage_plotter(mixedswitches, (SS_TEXT|SS_MIXED));
void (*textMixedPlotFn)(uint16_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++) {
uint16_t off = video__line_offset[y] + x;
textMixedPlotFn(off, page, bank);
textMixedPlotFn(off, page, bank, stagingFB);
}
}
} 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) = _hirespage_plotter(mixedswitches);
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++) {
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));
hiresMixedPlotFn(off, page, bank, /*even*/!(x & 1), stagingFB);
}
}
}
@ -1377,12 +1325,10 @@ uint8_t *video_scan(void) {
video_setDirty(FB_DIRTY_FLAG);
pthread_mutex_unlock(&video_scan_mutex);
return video__fb;
pthread_mutex_unlock(&display_scan_mutex);
}
void video_flashText(void) {
void display_flashText(void) {
static bool normal = false;
normal = !normal;
@ -1555,60 +1501,6 @@ uint8_t floating_bus_hibit(const bool hibit) {
return (b & ~0x80) | (hibit ? 0x80 : 0);
}
// ----------------------------------------------------------------------------
static bool null_backend_running = true;
void video_registerBackend(video_backend_s *backend, long order) {
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_lock(&mutex);
backend_node_s *node = MALLOC(sizeof(backend_node_s));
assert(node);
node->next = NULL;
node->order = order;
node->backend = backend;
backend_node_s *p0 = NULL;
backend_node_s *p = head;
while (p && (order > p->order)) {
p0 = p;
p = p->next;
}
if (p0) {
p0->next = node;
} else {
head = node;
}
node->next = p;
pthread_mutex_unlock(&mutex);
}
video_backend_s *video_getCurrentBackend(void) {
return head->backend;
}
video_animation_s *video_getAnimationDriver(void) {
return video_getCurrentBackend()->anim;
}
static void _null_backend_init(void *context) {
}
static void _null_backend_main_loop(void) {
while (null_backend_running) {
sleep(1);
}
}
static void _null_backend_render(void) {
}
static void _null_backend_shutdown(void) {
null_backend_running = false;
}
static void _init_interface(void) {
LOG("Initializing display subsystem");
_initialize_interface_fonts();
@ -1618,15 +1510,6 @@ static void _init_interface(void) {
_initialize_color();
prefs_registerListener(PREF_DOMAIN_VIDEO, &video_prefsChanged);
static video_backend_s null_backend = { 0 };
null_backend.init = &_null_backend_init;
null_backend.main_loop = &_null_backend_main_loop;
null_backend.render = &_null_backend_render;
null_backend.shutdown = &_null_backend_shutdown;
static video_animation_s _null_animations = { 0 };
null_backend.anim = &_null_animations;
video_registerBackend(&null_backend, VID_PRIO_NULL);
}
static __attribute__((constructor)) void __init_interface(void) {

View File

@ -12,74 +12,53 @@
#ifndef _DISPLAY_H_
#define _DISPLAY_H_
/*
* Color structure
*/
typedef struct A2Color_s {
uint8_t red;
uint8_t green;
uint8_t blue;
} A2Color_s;
/*
* Reference to the internal 8bit-indexed color format
*/
extern A2Color_s colormap[];
/*
* Color options
*/
typedef enum color_mode_t {
COLOR_NONE = 0,
/*LAZY_COLOR, deprecated*/
COLOR,
/*LAZY_INTERP, deprecated*/
COLOR_INTERP,
NUM_COLOROPTS
} color_mode_t;
typedef struct video_animation_s {
#if INTERFACE_TOUCH
// touch HUD functions
void (*animation_showTouchKeyboard)(void);
void (*animation_hideTouchKeyboard)(void);
void (*animation_showTouchJoystick)(void);
void (*animation_hideTouchJoystick)(void);
void (*animation_showTouchMenu)(void);
void (*animation_hideTouchMenu)(void);
#endif
// misc animations
void (*animation_showMessage)(char *message, unsigned int cols, unsigned int rows);
void (*animation_showPaused)(void);
void (*animation_showCPUSpeed)(void);
void (*animation_showDiskChosen)(int drive);
void (*animation_showTrackSector)(int drive, int track, int sect);
} video_animation_s;
/*
* Graphics mode
*/
typedef enum drawpage_mode_t {
DRAWPAGE_TEXT = 1,
DRAWPAGE_HIRES,
NUM_DRAWPAGE_MODES,
} drawpage_mode_t;
/*
* Get current animation driver
* Pixel deltas
*/
video_animation_s *video_getAnimationDriver(void);
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;
/*
* Prepare the video system, converting console to graphics mode, or
* opening X window, or whatever. This is called only once when the
* emulator is run
* Text/hires callback
*/
void video_init(void);
/*
* Enters emulator-managed main video loop--if backend rendering system requires it. Currently only used by desktop X11
* and desktop OpenGL/GLUT.
*/
void video_main_loop(void);
/*
* Shutdown video system. Should only be called on the render thread (unless render thread is in emulator-managed main
* video loop).
*/
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);
/*
* Set the render thread ID. Use with caution.
*/
void _video_setRenderThread(pthread_t id);
/*
* Check if running on render thread.
*/
bool video_isRenderThread(void);
typedef void (*display_update_fn)(pixel_delta_t);
/*
* Setup the display. This may be called multiple times in a run, and is
@ -90,7 +69,7 @@ bool video_isRenderThread(void);
* soft-switches.
*
*/
void video_reset(void);
void display_reset(void);
/*
* Set the font used by the display. QTY characters are loaded starting
@ -109,49 +88,53 @@ void video_reset(void);
* adaptors which color normal text and MouseText differently. I had one
* once for a //c.
*/
void video_loadfont(int first, int qty, const uint8_t *data, int mode);
/*
* Flushes currently set Apple //e video memory into staging framebuffer and returns pointer.
* This should only really be called from render thread or testsuite.
*/
uint8_t *video_scan(void);
/*
* Get a reference to current staging framebuffer
*/
uint8_t *video_currentFramebuffer(void);
void display_loadFont(int first, int qty, const uint8_t *data, int mode);
/*
* Toggles FLASHing text between NORMAL and INVERSE character sets.
*/
void video_flashText(void);
void display_flashText(void);
// ----------------------------------------------------------------------------
/*
* Clear the current display.
* Plot character into staging framebuffer.
*
* - Framebuffer may be NULL or should be exactly SCANWIDTH*SCANHEIGHT*sizeof(uint8_t) size
* - 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 video_clear(void);
#define A2_DIRTY_FLAG 0x1 // Apple //e video is dirty
#define FB_DIRTY_FLAG 0x2 // Internal framebuffer is dirty
void display_plotChar(uint8_t *fb, const uint8_t col, const uint8_t row, const interface_colorscheme_t cs, const uint8_t c);
/*
* True if dirty bit(s) are set for flag(s)
* Plot NULL_terminated string into staging framebuffer.
* - See display_plotChar() ...
*
*/
bool video_isDirty(unsigned long flags);
void display_plotLine(uint8_t *fb, const uint8_t col, const uint8_t row, const interface_colorscheme_t cs, const char *message);
/*
* Atomically set dirty bit(s), return previous bit(s) value
* Plot multi-line message into staging framebuffer.
* - See display_plotChar() ...
*/
unsigned long video_setDirty(unsigned long flags);
void display_plotMessage(uint8_t *fb, const interface_colorscheme_t cs, const char *message, const uint8_t message_cols, const uint8_t message_rows);
// ----------------------------------------------------------------------------
/*
* Atomically clear dirty bit(s), return previous bit(s) value
* Set TEXT/HIRES update callback(s).
*/
unsigned long video_clearDirty(unsigned long flags);
void display_setUpdateCallback(drawpage_mode_t mode, display_update_fn updateFn);
extern bool video_saveState(StateHelper_s *helper);
extern bool video_loadState(StateHelper_s *helper);
/*
* Flushes currently set Apple //e video memory into staging framebuffer.
*
* - Framebuffer should be 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);
// ----------------------------------------------------------------------------

View File

@ -20,6 +20,8 @@
int64_t (*interface_onTouchEvent)(interface_touch_event_t action, int pointer_count, int pointer_idx, float *x_coords, float *y_coords) = NULL;
#endif
static uint8_t *stagingFB = NULL;
static char disk_path[PATH_MAX] = { 0 };
// 2015/04/12 : This was legacy code for rendering the menu interfaces on desktop Linux. Portions here are resurrected
@ -120,29 +122,19 @@ static void _translate_screen_x_y(char *screen, const int xlen, const int ylen)
}
}
// ----------------------------------------------------------------------------
// Menu/HUD message printing
static void _interface_plotLine(uint8_t *fb, int fb_pix_width, int fb_pix_x_adjust, int col, int row, interface_colorscheme_t cs, const char *message) {
for (; *message; col++, message++) {
char c = *message;
unsigned int off = row * fb_pix_width * FONT_HEIGHT_PIXELS + col * FONT80_WIDTH_PIXELS + fb_pix_x_adjust;
interface_plotChar(fb+off, fb_pix_width, cs, c);
}
}
void interface_plotMessage(uint8_t *fb, interface_colorscheme_t cs, char *message, int message_cols, int message_rows) {
void interface_plotMessage(uint8_t *fb, const interface_colorscheme_t cs, char *message, const uint8_t message_cols, const uint8_t message_rows) {
_translate_screen_x_y(message, message_cols, message_rows);
int fb_pix_width = (message_cols*FONT80_WIDTH_PIXELS);
for (int row=0, idx=0; row<message_rows; row++, idx+=message_cols+1) {
_interface_plotLine(fb, fb_pix_width, 0, 0, row, cs, &message[ idx ]);
}
display_plotMessage(fb, cs, message, message_cols, message_rows);
}
// ----------------------------------------------------------------------------
// Desktop Legacy Menu Interface
#ifdef INTERFACE_CLASSIC
#if INTERFACE_CLASSIC
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) {
_translate_screen_x_y(message, message_cols, message_rows);
@ -152,19 +144,13 @@ 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) {
_interface_plotLine(fb, fb_pix_width, _INTERPOLATED_PIXEL_ADJUSTMENT_PRE, col, row, cs, &message[ idx ]);
display_plotLine(fb, col, row, cs, &message[idx]);
}
}
static struct stat statbuf = { 0 };
static int altdrive = 0;
void video_plotchar(const int col, const int row, const interface_colorscheme_t cs, const uint8_t c) {
unsigned int off = row * SCANWIDTH * FONT_HEIGHT_PIXELS + col * FONT80_WIDTH_PIXELS + _INTERPOLATED_PIXEL_ADJUSTMENT_PRE;
interface_plotChar(video_currentFramebuffer()+off, SCANWIDTH, cs, c);
video_setDirty(FB_DIRTY_FLAG);
}
void copy_and_pad_string(char *dest, const char* src, const char c, const int len, const char cap) {
const char* p = src;
char* d = dest;
@ -195,7 +181,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 ) {
_interface_plotLine(video_currentFramebuffer(), SCANWIDTH, _INTERPOLATED_PIXEL_ADJUSTMENT_PRE, x, y, cs, s);
display_plotLine(stagingFB, /*col:*/x, /*row:*/y, cs, s);
video_setDirty(FB_DIRTY_FLAG);
}
@ -217,7 +203,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(video_currentFramebuffer(), INTERFACE_SCREEN_X, TEXT_ROWS, RED_ON_BLACK, submenu, message_cols, message_rows);
_interface_plotMessageCentered(stagingFB, INTERFACE_SCREEN_X, TEXT_ROWS, RED_ON_BLACK, submenu, message_cols, message_rows);
video_setDirty(FB_DIRTY_FLAG);
}
@ -879,26 +865,26 @@ void c_interface_parameters()
{
if (temp[ j ] == '\0')
{
video_plotchar( INTERFACE_PATH_MIN + j, 5+i, 0, ' ' );
display_plotChar(stagingFB, /*col:*/INTERFACE_PATH_MIN + j, /*row:*/5+i, GREEN_ON_BLACK, ' ' );
j++;
break;
}
else
{
video_plotchar( INTERFACE_PATH_MIN + j, 5+i, 0, temp[ j ] );
display_plotChar(stagingFB, /*col:*/INTERFACE_PATH_MIN + j, /*row:*/5+i, /*cs:*/GREEN_ON_BLACK, temp[j]);
}
}
else
{
if (temp[ j ] == '\0')
{
video_plotchar( INTERFACE_PATH_MIN + j, 5+i, option==OPT_PATH,' ' );
display_plotChar(stagingFB, /*col:*/INTERFACE_PATH_MIN + j, /*row:*/5+i, (option == OPT_PATH ? GREEN_ON_BLUE : GREEN_ON_BLACK), ' ' );
j++;
break;
}
else
{
video_plotchar( INTERFACE_PATH_MIN + j, 5+i, option==OPT_PATH, temp[ j ]);
display_plotChar(stagingFB, /*col:*/INTERFACE_PATH_MIN + j, /*row:*/5+i, (option == OPT_PATH ? GREEN_ON_BLUE : GREEN_ON_BLACK), temp[j]);
}
}
@ -906,7 +892,7 @@ void c_interface_parameters()
for (; j < INTERFACE_PATH_MAX; j++)
{
video_plotchar( INTERFACE_PATH_MIN + j, 5+i, 0, ' ' );
display_plotChar(stagingFB, /*col:*/INTERFACE_PATH_MIN + j, /*row:*/5+i, GREEN_ON_BLACK, ' ');
}
}
}
@ -1103,7 +1089,7 @@ void c_interface_parameters()
else if ((ch == kESC) || c_keys_is_interface_key(ch))
{
timing_initialize();
video_reset();
display_reset();
vm_reinitializeAudio();
c_joystick_reset();
#if !TESTING
@ -1540,7 +1526,7 @@ static void *interface_thread(void *current_key)
break;
case kF7:
c_interface_debugging();
c_interface_debugging(stagingFB);
break;
case kF8:

View File

@ -24,8 +24,7 @@ typedef enum interface_colorscheme_t {
RED_ON_BLACK,
} interface_colorscheme_t;
#ifdef INTERFACE_CLASSIC
void video_plotchar(int col, int row, interface_colorscheme_t cs, uint8_t c);
#if INTERFACE_CLASSIC
void c_interface_begin(int current_key);
void c_interface_print(int x, int y, const interface_colorscheme_t cs, const char *s);
void c_interface_print_submenu_centered(char *submenu, int xlen, int ylen);
@ -35,13 +34,11 @@ void c_interface_credits();
void c_interface_exit(int ch);
void c_interface_translate_screen(char screen[24][INTERFACE_SCREEN_X+1]);
void c_interface_select_diskette(int);
void interface_setStagingFramebuffer(uint8_t *stagingFB);
#endif
// Plot character at pixel buffer offset
void interface_plotChar(uint8_t *fboff, int fb_pix_width, interface_colorscheme_t cs, uint8_t c);
// Plot message into pixel buffer
void interface_plotMessage(uint8_t *fb, interface_colorscheme_t cs, char *message, int message_cols, int message_rows);
void interface_plotMessage(uint8_t *fb, const interface_colorscheme_t cs, char *message, const uint8_t message_cols, const uint8_t message_rows);
#if INTERFACE_TOUCH
typedef enum interface_device_t {

View File

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

View File

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

View File

@ -45,8 +45,12 @@ void sha1_to_str(const uint8_t * const md, char *buf);
static inline int ASSERT_SHA(const char *SHA_STR) {
uint8_t md[SHA_DIGEST_LENGTH];
const uint8_t * const fb = video_scan();
uint8_t fb = MALLOC(SCANWIDTH*SCANHEIGHT);
display_renderStagingFramebuffer(fb);
SHA1(fb, SCANWIDTH*SCANHEIGHT, md);
FREE(fb);
sha1_to_str(md, mdstr);
ASSERT(strcasecmp(mdstr, SHA_STR) == 0);
return 0;

View File

@ -1395,8 +1395,12 @@ static int _test_disk_image_with_gzip_header(int readonly) {
do {
uint8_t md[SHA_DIGEST_LENGTH];
const uint8_t * const fb = video_scan();
uint8_t fb = MALLOC(SCANWIDTH*SCANHEIGHT);
display_renderStagingFramebuffer(fb);
SHA1(fb, SCANWIDTH*SCANHEIGHT, md);
FREE(fb);
sha1_to_str(md, mdstr);
bool matches_sha1 = (strcasecmp(mdstr, GZBAD_NIB_LOAD_SHA1) == 0);
bool matches_sha2 = (strcasecmp(mdstr, GZBAD_NIB_LOAD_SHA2) == 0);
@ -1499,8 +1503,12 @@ TEST test_reinsert_edgecase() {
c_debugger_clear_watchpoints();
c_debugger_go();
uint8_t md[SHA_DIGEST_LENGTH];
const uint8_t * const fb = video_scan();
uint8_t fb = MALLOC(SCANWIDTH*SCANHEIGHT);
display_renderStagingFramebuffer(fb);
SHA1(fb, SCANWIDTH*SCANHEIGHT, md);
FREE(fb);
sha1_to_str(md, mdstr);
ASSERT(strcmp(mdstr, BLANK_SCREEN) != 0);

View File

@ -452,7 +452,7 @@ cpu_runloop:
dbg_ticks += EXECUTION_PERIOD_NSECS;
if ((dbg_ticks % (NANOSECONDS_PER_SECOND>>1)) == 0)
{
video_flashText(); // TODO FIXME : proper FLASH timing ...
display_flashText(); // TODO FIXME : proper FLASH timing ...
}
#if DEBUG_TIMING
// collect timing statistics

View File

@ -280,11 +280,6 @@ static void glvideo_shutdown(void) {
static void glvideo_render(void) {
SCOPE_TRACE_VIDEO("glvideo render");
uint8_t *fb = video_currentFramebuffer();
if (UNLIKELY(!fb)) {
return;
}
if (UNLIKELY(prefsChanged)) {
glvideo_applyPrefs();
}
@ -316,15 +311,20 @@ 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
unsigned long wasDirty = video_clearDirty(A2_DIRTY_FLAG);
wasDirty = video_clearDirty(A2_DIRTY_FLAG);
if (wasDirty) {
fb = video_scan();
display_renderStagingFramebuffer(fb);
}
}
unsigned long wasDirty = video_clearDirty(FB_DIRTY_FLAG);
wasDirty = video_clearDirty(FB_DIRTY_FLAG);
char *pixels = (char *)crtModel->texPixels;
if (wasDirty) {
SCOPE_TRACE_VIDEO("pixel convert");

174
src/video/video.c Normal file
View File

@ -0,0 +1,174 @@
/*
* Apple // emulator for *ix
*
* This software package is subject to the GNU General Public License
* version 3 or later (your choice) as published by the Free Software
* Foundation.
*
* Copyright 2017 Aaron Culliney
*
*/
#include "common.h"
typedef struct backend_node_s {
struct backend_node_s *next;
long order;
video_backend_s *backend;
} backend_node_s;
static bool video_initialized = false;
static bool null_backend_running = true;
static backend_node_s *head = NULL;
static video_backend_s *currentBackend = NULL;
static pthread_t render_thread_id = 0;
// ----------------------------------------------------------------------------
void video_init(void) {
video_initialized = true;
assert(pthread_self() != cpu_thread_id);
LOG("(re)setting render_thread_id : %ld -> %ld", render_thread_id, pthread_self());
render_thread_id = pthread_self();
video_clear();
currentBackend->init((void*)0);
}
void _video_setRenderThread(pthread_t id) {
LOG("setting render_thread_id : %ld -> %ld", render_thread_id, id);
render_thread_id = id;
}
bool video_isRenderThread(void) {
return (pthread_self() == render_thread_id);
}
void video_shutdown(void) {
#if MOBILE_DEVICE
// WARNING : shutdown should occur on the render thread. Platform code (iOS, Android) should ensure this is called
// from within a render pass...
assert(!render_thread_id || pthread_self() == render_thread_id);
#endif
currentBackend->shutdown();
}
void video_render(void) {
assert(pthread_self() == render_thread_id);
currentBackend->render();
}
void video_main_loop(void) {
currentBackend->main_loop();
}
void video_clear(void) {
video_setDirty(A2_DIRTY_FLAG);
}
bool video_saveState(StateHelper_s *helper) {
bool saved = false;
int fd = helper->fd;
do {
uint8_t state = 0x0;
if (!helper->save(fd, &state, 1)) {
break;
}
LOG("SAVE (no-op) video__current_page = %02x", state);
saved = true;
} while (0);
return saved;
}
bool video_loadState(StateHelper_s *helper) {
bool loaded = false;
int fd = helper->fd;
do {
uint8_t state = 0x0;
if (!helper->load(fd, &state, 1)) {
break;
}
LOG("LOAD (no-op) video__current_page = %02x", state);
loaded = true;
} while (0);
return loaded;
}
// ----------------------------------------------------------------------------
// Video backend registration and selection
void video_registerBackend(video_backend_s *backend, long order) {
assert(!video_initialized); // backends cannot be registered after we've picked one to use
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_lock(&mutex);
backend_node_s *node = MALLOC(sizeof(backend_node_s));
assert(node);
node->next = NULL;
node->order = order;
node->backend = backend;
backend_node_s *p0 = NULL;
backend_node_s *p = head;
while (p && (order > p->order)) {
p0 = p;
p = p->next;
}
if (p0) {
p0->next = node;
} else {
head = node;
}
node->next = p;
currentBackend = head->backend;
pthread_mutex_unlock(&mutex);
}
video_animation_s *video_getAnimationDriver(void) {
return currentBackend->anim;
}
// ----------------------------------------------------------------------------
// NULL video backend ...
static void _null_backend_init(void *context) {
}
static void _null_backend_main_loop(void) {
while (null_backend_running) {
sleep(1);
}
}
static void _null_backend_render(void) {
}
static void _null_backend_shutdown(void) {
null_backend_running = false;
}
static __attribute__((constructor)) void _init_video(void) {
static video_backend_s null_backend = { 0 };
null_backend.init = &_null_backend_init;
null_backend.main_loop = &_null_backend_main_loop;
null_backend.render = &_null_backend_render;
null_backend.shutdown = &_null_backend_shutdown;
static video_animation_s _null_animations = { 0 };
null_backend.anim = &_null_animations;
video_registerBackend(&null_backend, VID_PRIO_NULL);
}

View File

@ -16,6 +16,104 @@
#ifndef A2_VIDEO_H
#define A2_VIDEO_H
/*
* Animation
*/
typedef struct video_animation_s {
#if INTERFACE_TOUCH
// touch HUD functions
void (*animation_showTouchKeyboard)(void);
void (*animation_hideTouchKeyboard)(void);
void (*animation_showTouchJoystick)(void);
void (*animation_hideTouchJoystick)(void);
void (*animation_showTouchMenu)(void);
void (*animation_hideTouchMenu)(void);
#endif
// misc animations
void (*animation_showMessage)(char *message, unsigned int cols, unsigned int rows);
void (*animation_showPaused)(void);
void (*animation_showCPUSpeed)(void);
void (*animation_showDiskChosen)(int drive);
void (*animation_showTrackSector)(int drive, int track, int sect);
} video_animation_s;
/*
* Prepare the video system, converting console to graphics mode, or opening X window, or whatever.
*/
void video_init(void);
/*
* Enters emulator-managed main video loop (if backend rendering system requires it). Currently only used by desktop
* X11 and desktop OpenGL/GLUT.
*/
void video_main_loop(void);
/*
* Shutdown video system. Should only be called on the render thread (unless render thread is in emulator-managed main
* video loop).
*/
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);
/*
* Set the render thread ID. Use with caution.
*/
void _video_setRenderThread(pthread_t id);
/*
* Check if the current code is running on render thread.
*/
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
/*
* True if dirty bit(s) are set for flag(s)
*/
bool video_isDirty(unsigned long flags);
/*
* Atomically set dirty bit(s), return previous bit(s) value
*/
unsigned long video_setDirty(unsigned long flags);
/*
* Atomically clear dirty bit(s), return previous bit(s) value
*/
unsigned long video_clearDirty(unsigned long flags);
/*
* State save support for video subsystem.
*/
bool video_saveState(StateHelper_s *helper);
/*
* State restore support for video subsystem.
*/
bool video_loadState(StateHelper_s *helper);
/*
* Get current animation driver
*/
video_animation_s *video_getAnimationDriver(void);
// ----------------------------------------------------------------------------
// Video Backend API
typedef struct video_backend_s {
void (*init)(void *context);
void (*main_loop)(void);
@ -24,20 +122,6 @@ typedef struct video_backend_s {
video_animation_s *anim;
} video_backend_s;
/*
* Color structure
*/
typedef struct A2Color_s {
uint8_t red;
uint8_t green;
uint8_t blue;
} A2Color_s;
/*
* Reference to the internal 8bit-indexed color format
*/
extern A2Color_s colormap[];
#if VIDEO_X11
// X11 scaling ...
typedef enum a2_video_mode_t {
@ -57,9 +141,10 @@ enum {
VID_PRIO_NULL = 100,
};
/*
* Register a video backend at the specific prioritization, regardless of user choice.
*/
void video_registerBackend(video_backend_s *backend, long prio);
video_backend_s *video_getCurrentBackend(void);
#endif /* !A2_VIDEO_H */

View File

@ -333,39 +333,55 @@ static int keysym_to_scancode(void) {
return rc;
}
// copy Apple //e video memory into XImage uint32_t buffer
static void post_image() {
// copy Apple //e video memory into XImage uint32_t buffer
uint8_t *fb = video_scan();
uint8_t index;
unsigned int count = SCANWIDTH * SCANHEIGHT;
for (unsigned int i=0, j=0; i<count; i++, j+=4)
{
index = *(fb + i);
*( (uint32_t*)(image->data + j) ) = (uint32_t)(
((uint32_t)(colormap[index].red) << red_shift) |
((uint32_t)(colormap[index].green) << green_shift) |
((uint32_t)(colormap[index].blue) << blue_shift) |
((uint32_t)0xff /* alpha */ << alpha_shift)
);
if (scale > 1)
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);
if (wasDirty) {
uint8_t index;
unsigned int count = SCANWIDTH * SCANHEIGHT;
for (unsigned int i=0, j=0; i<count; i++, j+=4)
{
j+=4;
// duplicate pixel
index = *(fb + i);
*( (uint32_t*)(image->data + j) ) = (uint32_t)(
((uint32_t)(colormap[index].red) << red_shift) |
((uint32_t)(colormap[index].green) << green_shift) |
((uint32_t)(colormap[index].blue) << blue_shift) |
((uint32_t)0xff /* alpha */ << alpha_shift)
);
if (((i+1) % SCANWIDTH) == 0)
((uint32_t)(colormap[index].red) << red_shift) |
((uint32_t)(colormap[index].green) << green_shift) |
((uint32_t)(colormap[index].blue) << blue_shift) |
((uint32_t)0xff /* alpha */ << alpha_shift)
);
if (scale > 1)
{
// duplicate entire row
int stride8 = SCANWIDTH<<3;//*8
memcpy(/* dest */image->data + j + 4, /* src */image->data + j + 4 - stride8, stride8);
j += stride8;
j+=4;
// duplicate pixel
*( (uint32_t*)(image->data + j) ) = (uint32_t)(
((uint32_t)(colormap[index].red) << red_shift) |
((uint32_t)(colormap[index].green) << green_shift) |
((uint32_t)(colormap[index].blue) << blue_shift) |
((uint32_t)0xff /* alpha */ << alpha_shift)
);
if (((i+1) % SCANWIDTH) == 0)
{
// duplicate entire row
int stride8 = SCANWIDTH<<3;//*8
memcpy(/* dest */image->data + j + 4, /* src */image->data + j + 4 - stride8, stride8);
j += stride8;
}
}
}
}
@ -859,7 +875,7 @@ static void xdriver_init(void *context) {
#endif
}
static void xdriver_shutdown(bool emulatorShuttingDown) {
static void xdriver_shutdown(void) {
_destroy_image();
}

View File

@ -804,7 +804,7 @@ GLUE_C_READ(iie_altchar_off)
{
if (softswitches & SS_ALTCHAR) {
softswitches &= ~SS_ALTCHAR;
video_loadfont(0x40,0x40,ucase_glyphs,3);
display_loadFont(0x40,0x40,ucase_glyphs,3);
video_setDirty(A2_DIRTY_FLAG);
}
return floating_bus();
@ -814,8 +814,8 @@ GLUE_C_READ(iie_altchar_on)
{
if (!(softswitches & SS_ALTCHAR)) {
softswitches |= SS_ALTCHAR;
video_loadfont(0x40,0x20,mousetext_glyphs,1);
video_loadfont(0x60,0x20,lcase_glyphs,2);
display_loadFont(0x40,0x20,mousetext_glyphs,1);
display_loadFont(0x60,0x20,lcase_glyphs,2);
video_setDirty(A2_DIRTY_FLAG);
}
return floating_bus();
@ -970,11 +970,11 @@ static void _initialize_iie_switches(void) {
}
static void _initialize_font(void) {
video_loadfont(0x00,0x40,ucase_glyphs,2);
video_loadfont(0x40,0x40,ucase_glyphs,3);
video_loadfont(0x80,0x40,ucase_glyphs,0);
video_loadfont(0xC0,0x20,ucase_glyphs,0);
video_loadfont(0xE0,0x20,lcase_glyphs,0);
display_loadFont(0x00,0x40,ucase_glyphs,2);
display_loadFont(0x40,0x40,ucase_glyphs,3);
display_loadFont(0x80,0x40,ucase_glyphs,0);
display_loadFont(0xC0,0x20,ucase_glyphs,0);
display_loadFont(0xE0,0x20,lcase_glyphs,0);
}
static void _initialize_apple_ii_memory(void) {
@ -1049,7 +1049,7 @@ static void _initialize_tables(void) {
// initialize first text & hires page, which are specially bank switched
//
// video_reset() below substitutes it's own hooks for all visible write locations affect the display, leaving our
// display_reset() below substitutes it's own hooks for all visible write locations affect the display, leaving our
// 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;
@ -1215,7 +1215,7 @@ static void _initialize_tables(void) {
cpu65_vmem_r[i] = iie_read_slot_expansion;
}
video_reset();
display_reset();
// Peripheral card slot initializations ...