HatariWii/src/screen.c

1551 lines
43 KiB
C

/*
Hatari - screen.c
This file is distributed under the GNU General Public License, version 2
or at your option any later version. Read the file gpl.txt for details.
This code converts a 1/2/4 plane ST format screen to either 8, 16 or 32-bit PC
format. An awful lot of processing is needed to do this conversion - we
cannot simply change palettes on interrupts as it is possible with DOS.
The main code processes the palette/resolution mask tables to find exactly
which lines need to updating and the conversion routines themselves only
update 16-pixel blocks which differ from the previous frame - this gives a
large performance increase.
Each conversion routine can convert any part of the source ST screen (which
includes the overscan border, usually set to colour zero) so they can be used
for both window and full-screen mode.
Note that in Hi-Resolution we have no overscan and just two colors so we can
optimise things further.
In color mode it seems possible to display 47 lines in the bottom border
with a second 60/50 Hz switch, but most programs consider there are 45
visible lines in the bottom border only, which gives a total of 274 lines
for a screen. So not displaying the last two lines fixes garbage that could
appear in the last two lines when displaying 47 lines (Digiworld 2 by ICE,
Tyranny by DHS).
*/
const char Screen_fileid[] = "Hatari screen.c : " __DATE__ " " __TIME__;
#include <SDL.h>
#include <SDL_endian.h>
#include "main.h"
#include "configuration.h"
#include "avi_record.h"
#include "ikbd.h"
#include "log.h"
#include "m68000.h"
#include "paths.h"
#include "options.h"
#include "screen.h"
#include "control.h"
#include "convert/routines.h"
#include "resolution.h"
#include "sound.h"
#include "spec512.h"
#include "statusbar.h"
#include "vdi.h"
#include "video.h"
#include "falcon/videl.h"
#include "falcon/hostscreen.h"
#define DEBUG 0
#if DEBUG
# define DEBUGPRINT(x) printf x
#else
#ifdef GEKKO
# define DEBUGPRINT(x) //
#else
# define DEBUGPRINT(x)
#endif
#endif
/* extern for several purposes */
SDL_Surface *sdlscrn = NULL; /* The SDL screen surface */
int nScreenZoomX, nScreenZoomY; /* Zooming factors, used for scaling mouse motions */
int nBorderPixelsLeft, nBorderPixelsRight; /* Pixels in left and right border */
static int nBorderPixelsTop, nBorderPixelsBottom; /* Lines in top and bottom border */
/* extern for shortcuts and falcon/hostscreen.c */
bool bGrabMouse = false; /* Grab the mouse cursor in the window */
bool bInFullScreen = false; /* true if in full screen */
/* extern for spec512.c */
int STScreenLeftSkipBytes;
int STScreenStartHorizLine; /* Start lines to be converted */
Uint32 STRGBPalette[16]; /* Palette buffer used in conversion routines */
Uint32 ST2RGB[4096]; /* Table to convert ST 0x777 / STe 0xfff palette to PC format RGB551 (2 pixels each entry) */
/* extern for video.c */
Uint8 *pSTScreen;
FRAMEBUFFER *pFrameBuffer; /* Pointer into current 'FrameBuffer' */
static FRAMEBUFFER FrameBuffers[NUM_FRAMEBUFFERS]; /* Store frame buffer details to tell how to update */
static Uint8 *pSTScreenCopy; /* Keep track of current and previous ST screen data */
static Uint8 *pPCScreenDest; /* Destination PC buffer */
static int STScreenEndHorizLine; /* End lines to be converted */
static int PCScreenBytesPerLine;
static int STScreenWidthBytes;
static int PCScreenOffsetX; /* how many pixels to skip from left when drawing */
static int PCScreenOffsetY; /* how many pixels to skip from top when drawing */
static SDL_Rect STScreenRect; /* screen size without statusbar */
static int STScreenLineOffset[NUM_VISIBLE_LINES]; /* Offsets for ST screen lines eg, 0,160,320... */
static Uint16 HBLPalette[16], PrevHBLPalette[16]; /* Current palette for line, also copy of first line */
static void (*ScreenDrawFunctionsNormal[3])(void); /* Screen draw functions */
static void (*ScreenDrawFunctionsVDI[3])(void) =
{
ConvertVDIRes_16Colour,
ConvertVDIRes_4Colour,
ConvertVDIRes_2Colour
};
static bool bScreenContentsChanged; /* true if buffer changed and requires blitting */
static bool bScrDoubleY; /* true if double on Y */
static int ScrUpdateFlag; /* Bit mask of how to update screen */
static bool Screen_DrawFrame(bool bForceFlip);
#if WITH_SDL2
SDL_Window *sdlWindow;
static SDL_Renderer *sdlRenderer;
static SDL_Texture *sdlTexture;
void SDL_UpdateRects(SDL_Surface *screen, int numrects, SDL_Rect *rects)
{
if (sdlscrn->format->BitsPerPixel == 8)
{
sdlTexture = SDL_CreateTextureFromSurface(sdlRenderer, screen);
}
else
{
SDL_UpdateTexture(sdlTexture, NULL, screen->pixels, screen->pitch);
}
SDL_RenderClear(sdlRenderer);
SDL_RenderCopy(sdlRenderer, sdlTexture, NULL, NULL);
SDL_RenderPresent(sdlRenderer);
if (sdlscrn->format->BitsPerPixel == 8)
{
SDL_DestroyTexture(sdlTexture);
sdlTexture = NULL;
}
}
void SDL_UpdateRect(SDL_Surface *screen, Sint32 x, Sint32 y, Sint32 w, Sint32 h)
{
SDL_Rect rect = { x, y, w, h };
SDL_UpdateRects(screen, 1, &rect);
}
#endif
/*-----------------------------------------------------------------------*/
/**
* Create ST 0x777 / STe 0xfff color format to 16 or 32 bits per pixel
* conversion table. Called each time when changed resolution or to/from
* fullscreen mode.
*/
static void Screen_SetupRGBTable(void)
{
Uint16 STColor;
Uint32 RGBColor;
int r, g, b;
int rr, gg, bb;
/* Do Red, Green and Blue for all 16*16*16 = 4096 STe colors */
for (r = 0; r < 16; r++)
{
for (g = 0; g < 16; g++)
{
for (b = 0; b < 16; b++)
{
/* STe 0xfff format */
STColor = (r<<8) | (g<<4) | (b);
rr = ((r & 0x7) << 1) | ((r & 0x8) >> 3);
rr |= rr << 4;
gg = ((g & 0x7) << 1) | ((g & 0x8) >> 3);
gg |= gg << 4;
bb = ((b & 0x7) << 1) | ((b & 0x8) >> 3);
bb |= bb << 4;
RGBColor = SDL_MapRGB(sdlscrn->format, rr, gg, bb);
if (sdlscrn->format->BitsPerPixel <= 16)
{
/* As longs, for speed (write two pixels at once) */
ST2RGB[STColor] = (RGBColor<<16) | RGBColor;
}
else
{
ST2RGB[STColor] = RGBColor;
}
}
}
}
}
/*-----------------------------------------------------------------------*/
/**
* Create new palette for display.
*/
static void Screen_CreatePalette(void)
{
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
static const int endiantable[16] = {0,2,1,3,8,10,9,11,4,6,5,7,12,14,13,15};
#endif
SDL_Color sdlColors[16];
int i, j;
if (bUseHighRes)
{
/* Colors for monochrome screen mode emulation */
if (HBLPalettes[0])
{
sdlColors[0].r = sdlColors[0].g = sdlColors[0].b = 255;
sdlColors[1].r = sdlColors[1].g = sdlColors[1].b = 0;
}
else
{
sdlColors[0].r = sdlColors[0].g = sdlColors[0].b = 0;
sdlColors[1].r = sdlColors[1].g = sdlColors[1].b = 255;
}
SDL_SetColors(sdlscrn, sdlColors, 10, 2);
/*SDL_SetColors(sdlscrn, sdlColors, 0, 2);*/
}
else
{
int r, g, b;
/* Colors for STe color screen mode emulation */
for (i = 0; i < 16; i++)
{
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
j = endiantable[i];
#else
j = i;
#endif
/* normalize all to 0x1e0 */
r = HBLPalettes[i] >> 3;
g = HBLPalettes[i] << 1;
b = HBLPalettes[i] << 5;
/* move top bit of 0x1e0 to lowest in 0xf0 */
r = (r & 0xe0) | ((r & 0x100) >> 4);
g = (g & 0xe0) | ((g & 0x100) >> 4);
b = (b & 0xe0) | ((b & 0x100) >> 4);
/* Set color in table */
sdlColors[j].r = r | (r >> 4);
sdlColors[j].g = g | (g >> 4);
sdlColors[j].b = b | (b >> 4);
}
SDL_SetColors(sdlscrn, sdlColors, 10, 16);
}
}
/*-----------------------------------------------------------------------*/
/**
* Create 8-Bit palette for display if needed.
*/
static void Screen_Handle8BitPalettes(void)
{
bool bPaletteChanged = false;
int i;
/* Do need to check for 8-Bit palette change? Ie, update whole screen */
/* VDI screens and monochrome modes are ALL 8-Bit at the moment! */
if (sdlscrn->format->BitsPerPixel == 8)
{
/* If using HiRes palette update with full update flag */
if (!bUseHighRes)
{
/* Check if palette of 16 colours changed from previous frame */
for (i = 0; i < 16 && !bPaletteChanged; i++)
{
/* Check with first line palette (stored in 'Screen_ComparePaletteMask') */
if (HBLPalettes[i] != PrevHBLPalette[i])
bPaletteChanged = true;
}
}
/* Did palette change or do we require a full update? */
if (bPaletteChanged || pFrameBuffer->bFullUpdate)
{
/* Create palette, for Full-Screen of Window */
Screen_CreatePalette();
/* Make sure update whole screen */
pFrameBuffer->bFullUpdate = true;
}
}
/* Copy old palette for 8-Bit compare as this routine writes over it */
memcpy(PrevHBLPalette,HBLPalettes, sizeof(Uint16)*16);
}
/*-----------------------------------------------------------------------*/
/**
* Set screen draw functions.
*/
static void Screen_SetDrawFunctions(int nBitCount, bool bDoubleLowRes)
{
if (nBitCount == 8)
{
/* Low color */
if (bDoubleLowRes)
ScreenDrawFunctionsNormal[ST_LOW_RES] = ConvertLowRes_640x8Bit;
else
ScreenDrawFunctionsNormal[ST_LOW_RES] = ConvertLowRes_320x8Bit;
ScreenDrawFunctionsNormal[ST_MEDIUM_RES] = ConvertMediumRes_640x8Bit;
ScreenDrawFunctionsNormal[ST_HIGH_RES] = ConvertHighRes_640x8Bit;
}
else if (nBitCount <= 16)
{
/* High color */
if (bDoubleLowRes)
ScreenDrawFunctionsNormal[ST_LOW_RES] = ConvertLowRes_640x16Bit;
else
ScreenDrawFunctionsNormal[ST_LOW_RES] = ConvertLowRes_320x16Bit;
ScreenDrawFunctionsNormal[ST_MEDIUM_RES] = ConvertMediumRes_640x16Bit;
ScreenDrawFunctionsNormal[ST_HIGH_RES] = ConvertHighRes_640x8Bit;
}
else /* Assume 32 bit drawing functions */
{
/* True color */
if (bDoubleLowRes)
ScreenDrawFunctionsNormal[ST_LOW_RES] = ConvertLowRes_640x32Bit;
else
ScreenDrawFunctionsNormal[ST_LOW_RES] = ConvertLowRes_320x32Bit;
ScreenDrawFunctionsNormal[ST_MEDIUM_RES] = ConvertMediumRes_640x32Bit;
ScreenDrawFunctionsNormal[ST_HIGH_RES] = ConvertHighRes_640x8Bit;
}
}
/*-----------------------------------------------------------------------*/
/**
* Set amount of border pixels
*/
static void Screen_SetBorderPixels(int leftX, int leftY)
{
/* All screen widths need to be aligned to 16-bits */
nBorderPixelsLeft = Opt_ValueAlignMinMax(leftX/2, 16, 0, 48);
nBorderPixelsRight = nBorderPixelsLeft;
/* assertain assumption of code below */
assert(OVERSCAN_TOP < MAX_OVERSCAN_BOTTOM);
if (leftY > 2*OVERSCAN_TOP)
{
nBorderPixelsTop = OVERSCAN_TOP;
if (leftY >= OVERSCAN_TOP + MAX_OVERSCAN_BOTTOM)
nBorderPixelsBottom = MAX_OVERSCAN_BOTTOM;
else
nBorderPixelsBottom = leftY - OVERSCAN_TOP;
}
else
{
if (leftY > 0)
nBorderPixelsTop = nBorderPixelsBottom = leftY/2;
else
nBorderPixelsTop = nBorderPixelsBottom = 0;
}
}
/*-----------------------------------------------------------------------*/
/**
* store Y offset for each horizontal line in our source ST screen for
* reference in the convert functions.
*/
static void Screen_SetSTScreenOffsets(void)
{
int i;
/* Store offset to each horizontal line, uses
* nBorderPixels* variables.
*/
for (i = 0; i < NUM_VISIBLE_LINES; i++)
{
STScreenLineOffset[i] = i * SCREENBYTES_LINE;
}
}
#if WITH_SDL2
static void Screen_FreeSDL2Resources(void)
{
if (sdlTexture)
{
SDL_DestroyTexture(sdlTexture);
sdlTexture = NULL;
}
if (sdlscrn)
{
SDL_FreeSurface(sdlscrn);
sdlscrn = NULL;
}
if (sdlRenderer)
{
SDL_DestroyRenderer(sdlRenderer);
sdlRenderer = NULL;
}
if (sdlWindow)
{
SDL_DestroyWindow(sdlWindow);
sdlWindow = NULL;
}
}
#endif
/**
* Change the SDL video mode.
* @return true if mode has been changed, false if change was not necessary
*/
bool Screen_SetSDLVideoSize(int width, int height, int bitdepth, bool bForceChange)
{
Uint32 sdlVideoFlags;
#if WITH_SDL2
static int nPrevRenderScaleQuality = 0;
static bool bPrevUseVsync = false;
if (bitdepth == 0 || bitdepth == 24)
bitdepth = 32;
#endif
/* Check if we really have to change the video mode: */
if (sdlscrn != NULL && sdlscrn->w == width && sdlscrn->h == height
&& sdlscrn->format->BitsPerPixel == bitdepth && !bForceChange)
return false;
/* We can not continue recording with a different resolution */
if (Avi_AreWeRecording())
Avi_StopRecording();
#ifdef _MUDFLAP
if (sdlscrn)
{
__mf_unregister(sdlscrn->pixels, sdlscrn->pitch*sdlscrn->h, __MF_TYPE_GUESS);
}
#endif
if (bInFullScreen)
{
/* unhide the Hatari WM window for fullscreen */
Control_ReparentWindow(width, height, bInFullScreen);
}
#if WITH_SDL2
/* SDL Video attributes: */
if (bInFullScreen)
{
sdlVideoFlags = SDL_WINDOW_FULLSCREEN_DESKTOP;
}
else
{
sdlVideoFlags = 0;
}
Screen_FreeSDL2Resources();
/* Set SDL2 video hints */
if (nPrevRenderScaleQuality != ConfigureParams.Screen.nRenderScaleQuality)
{
char hint[2] = { '0' + ConfigureParams.Screen.nRenderScaleQuality, 0 };
SDL_SetHintWithPriority(SDL_HINT_RENDER_SCALE_QUALITY, hint, SDL_HINT_OVERRIDE);
nPrevRenderScaleQuality = ConfigureParams.Screen.nRenderScaleQuality;
}
if (bPrevUseVsync != ConfigureParams.Screen.bUseVsync)
{
char hint[2] = { '0' + ConfigureParams.Screen.bUseVsync, 0 };
SDL_SetHintWithPriority(SDL_HINT_RENDER_VSYNC, hint, SDL_HINT_OVERRIDE);
bPrevUseVsync = ConfigureParams.Screen.bUseVsync;
}
/* Set new video mode */
DEBUGPRINT(("SDL screen request: %d x %d @ %d (%s)\n", width, height,
bitdepth, bInFullScreen?"fullscreen":"windowed"));
sdlWindow = SDL_CreateWindow("Hatari", SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED, width, height,
sdlVideoFlags);
sdlRenderer = SDL_CreateRenderer(sdlWindow, -1, 0);
if (!sdlWindow || !sdlRenderer)
{
#ifndef GEKKO
fprintf(stderr,"Failed to create window or renderer!\n");
#else
Log_Printf(LOG_ERROR, "Failed to create window or renderer!\n");
#endif
exit(-1);
}
SDL_RenderSetLogicalSize(sdlRenderer, width, height);
if (bitdepth == 8)
{
SDL_Color cols[] = { /* Colors for the sdl-gui */
{ 0, 0, 0, 255 }, { 64, 64, 64, 255 },
{ 128, 128, 128, 255 }, { 160, 160, 160, 255 },
{ 196, 196, 196, 255 }, { 255, 255, 255, 255 },
{ 0x00, 0x40, 0x00, 255 }, { 0x00, 0xc0, 0x00, 255 },
{ 0x00, 0xe0, 0x00, 255 }
};
sdlscrn = SDL_CreateRGBSurface(0, width, height, bitdepth,
0, 0, 0, 0);
if (sdlscrn)
SDL_SetPaletteColors(sdlscrn->format->palette, cols,
128, ARRAYSIZE(cols));
}
else
{
int rm, bm, gm, pfmt;
if (bitdepth == 16)
{
rm = 0xF800;
gm = 0x07E0;
bm = 0x001F;
pfmt = SDL_PIXELFORMAT_RGB565;
}
else
{
rm = 0x00FF0000;
gm = 0x0000FF00;
bm = 0x000000FF;
pfmt = SDL_PIXELFORMAT_RGB888;
}
sdlscrn = SDL_CreateRGBSurface(0, width, height, bitdepth,
rm, gm, bm, 0);
sdlTexture = SDL_CreateTexture(sdlRenderer, pfmt,
SDL_TEXTUREACCESS_STREAMING,
width, height);
if (!sdlTexture)
{
#ifndef GEKKO
fprintf(stderr,"Failed to create texture!\n");
#else
Log_Printf(LOG_ERROR, "Failed to create texture!\n");
#endif
exit(-3);
}
}
#else /* WITH_SDL2 */
/* SDL Video attributes: */
if (bInFullScreen)
{
sdlVideoFlags = SDL_HWSURFACE|SDL_FULLSCREEN/*|SDL_DOUBLEBUF*/;
/* SDL_DOUBLEBUF helps avoiding tearing and can be faster on suitable HW,
* but it doesn't work with partial screen updates done by the ST screen
* update code or the Hatari GUI, so double buffering is disabled. */
}
else
{
sdlVideoFlags = SDL_SWSURFACE;
}
if (bitdepth <= 8)
{
sdlVideoFlags |= SDL_HWPALETTE;
}
/* Set new video mode */
DEBUGPRINT(("SDL screen request: %d x %d @ %d (%s)\n", width, h, bitdepth, bInFullScreen?"fullscreen":"windowed"));
sdlscrn = SDL_SetVideoMode(width, height, bitdepth, sdlVideoFlags);
/* By default ConfigureParams.Screen.nForceBpp and therefore
* BitCount is zero which means "SDL color depth autodetection".
* In this case the SDL_SetVideoMode() call might return
* a 24 bpp resolution
*/
if (sdlscrn && sdlscrn->format->BitsPerPixel == 24)
{
#ifndef GEKKO
fprintf(stderr, "Unsupported color depth 24, trying 32 bpp instead...\n");
#else
Log_Printf(LOG_ERROR, "Unsupported color depth 24, trying 32 bpp instead...\n");
#endif
sdlscrn = SDL_SetVideoMode(width, height, 32, sdlVideoFlags);
}
#endif /* WITH_SDL2 */
/* Exit if we can not open a screen */
if (!sdlscrn)
{
#ifndef GEKKO
fprintf(stderr, "Could not set video mode:\n %s\n", SDL_GetError() );
#else
Log_Printf(LOG_ERROR, "Could not set video mode:\n %s\n", SDL_GetError() );
#endif
SDL_Quit();
exit(-2);
}
DEBUGPRINT(("SDL screen granted: %d x %d @ %d\n", sdlscrn->w, sdlscrn->h,
sdlscrn->format->BitsPerPixel));
#ifdef _MUDFLAP
__mf_register(sdlscrn->pixels, sdlscrn->pitch*sdlscrn->h, __MF_TYPE_GUESS, "SDL pixels");
#endif
if (!bInFullScreen)
{
/* re-embed the new Hatari SDL window */
Control_ReparentWindow(width, height, bInFullScreen);
}
return true;
}
/*-----------------------------------------------------------------------*/
/**
* Initialize SDL screen surface / set resolution.
*/
#ifdef GEKKO
void Screen_SetResolution(bool bForceChange)
#else
static void Screen_SetResolution(bool bForceChange)
#endif
{
int Width, Height, nZoom, SBarHeight, BitCount, maxW, maxH;
bool bDoubleLowRes = false;
/* Bits per pixel */
if (STRes == ST_HIGH_RES || bUseVDIRes)
{
BitCount = 8;
}
else
{
BitCount = ConfigureParams.Screen.nForceBpp;
}
nBorderPixelsTop = nBorderPixelsBottom = 0;
nBorderPixelsLeft = nBorderPixelsRight = 0;
nScreenZoomX = 1;
nScreenZoomY = 1;
/* Determine which resolution to use */
if (bUseVDIRes)
{
Width = VDIWidth;
Height = VDIHeight;
}
else
{
if (STRes == ST_LOW_RES)
{
Width = 320;
Height = 200;
nZoom = 1;
}
else /* else use 640x400, also for med-rez */
{
Width = 640;
Height = 400;
nZoom = 2;
}
/* Statusbar height for doubled screen size */
SBarHeight = Statusbar_GetHeightForSize(640, 400);
Resolution_GetLimits(&maxW, &maxH, &BitCount, ConfigureParams.Screen.bKeepResolutionST);
/* Zoom if necessary, factors used for scaling mouse motions */
if (STRes == ST_LOW_RES &&
2*Width <= maxW && 2*Height+SBarHeight <= maxH)
{
nZoom = 2;
Width *= 2;
Height *= 2;
nScreenZoomX = 2;
nScreenZoomY = 2;
bDoubleLowRes = true;
}
else if (STRes == ST_MEDIUM_RES)
{
/* med-rez conversion functions want always
* to double vertically, they don't support
* skipping that (only leaving doubled lines
* black for the TV mode).
*/
nScreenZoomX = 1;
nScreenZoomY = 2;
}
/* Adjust width/height for overscan borders, if mono or VDI we have no overscan */
if (ConfigureParams.Screen.bAllowOverscan && !bUseHighRes)
{
int leftX = maxW - Width;
int leftY = maxH - (Height + Statusbar_GetHeightForSize(Width, Height));
Screen_SetBorderPixels(leftX/nZoom, leftY/nZoom);
DEBUGPRINT(("resolution limit:\n\t%d x %d\nlimited resolution:\n\t", maxW, maxH));
DEBUGPRINT(("%d * (%d + %d + %d) x (%d + %d + %d)\n", nZoom,
nBorderPixelsLeft, Width/nZoom, nBorderPixelsRight,
nBorderPixelsTop, Height/nZoom, nBorderPixelsBottom));
Width += (nBorderPixelsRight + nBorderPixelsLeft)*nZoom;
Height += (nBorderPixelsTop + nBorderPixelsBottom)*nZoom;
DEBUGPRINT(("\t= %d x %d (+ statusbar)\n", Width, Height));
}
}
Screen_SetSTScreenOffsets();
Height += Statusbar_SetHeight(Width, Height);
PCScreenOffsetX = PCScreenOffsetY = 0;
/* Video attributes: */
if (bInFullScreen && ConfigureParams.Screen.bKeepResolutionST)
{
/* use desktop resolution */
Resolution_GetDesktopSize(&maxW, &maxH);
SBarHeight = Statusbar_GetHeightForSize(maxW, maxH);
/* re-calculate statusbar height for this resolution */
Statusbar_SetHeight(maxW, maxH-SBarHeight);
/* center Atari screen to resolution */
PCScreenOffsetY = (maxH - Height)/2-20;
PCScreenOffsetX = (maxW - Width)/2;
/* and select desktop resolution */
Height = maxH;
Width = maxW;
}
if (Screen_SetSDLVideoSize(Width, Height, BitCount, bForceChange))
{
/* Re-init screen palette: */
if (sdlscrn->format->BitsPerPixel == 8)
Screen_Handle8BitPalettes(); /* Initialize new 8 bit palette */
else
Screen_SetupRGBTable(); /* Create color conversion table */
Statusbar_Init(sdlscrn);
/* screen area without the statusbar */
STScreenRect.x = 0;
STScreenRect.y = 0;
STScreenRect.w = sdlscrn->w;
STScreenRect.h = sdlscrn->h - Statusbar_GetHeight();
}
/* Set drawing functions */
Screen_SetDrawFunctions(sdlscrn->format->BitsPerPixel, bDoubleLowRes);
Screen_SetFullUpdate(); /* Cause full update of screen */
}
/*-----------------------------------------------------------------------*/
/**
* Init Screen bitmap and buffers/tables needed for ST to PC screen conversion
*/
void Screen_Init(void)
{
int i;
SDL_Surface *pIconSurf;
char sIconFileName[FILENAME_MAX];
/* Clear frame buffer structures and set current pointer */
memset(FrameBuffers, 0, NUM_FRAMEBUFFERS * sizeof(FRAMEBUFFER));
/* Allocate previous screen check workspace. We are going to double-buffer a double-buffered screen. Oh. */
for (i = 0; i < NUM_FRAMEBUFFERS; i++)
{
FrameBuffers[i].pSTScreen = malloc(MAX_VDI_BYTES);
FrameBuffers[i].pSTScreenCopy = malloc(MAX_VDI_BYTES);
if (!FrameBuffers[i].pSTScreen || !FrameBuffers[i].pSTScreenCopy)
{
#ifndef GEKKO
fprintf(stderr, "Failed to allocate frame buffer memory.\n");
#else
Log_Printf(LOG_ERROR, "Failed to allocate frame buffer memory.\n");
#endif
exit(-1);
}
}
pFrameBuffer = &FrameBuffers[0];
/* Load and set icon */
snprintf(sIconFileName, sizeof(sIconFileName), "%s%chatari-icon.bmp",
Paths_GetDataDir(), PATHSEP);
pIconSurf = SDL_LoadBMP(sIconFileName);
if (pIconSurf)
{
#if WITH_SDL2
SDL_SetColorKey(pIconSurf, SDL_TRUE, SDL_MapRGB(pIconSurf->format, 255, 255, 255));
SDL_SetWindowIcon(sdlWindow, pIconSurf);
#else
SDL_SetColorKey(pIconSurf, SDL_SRCCOLORKEY, SDL_MapRGB(pIconSurf->format, 255, 255, 255));
SDL_WM_SetIcon(pIconSurf, NULL);
#endif
SDL_FreeSurface(pIconSurf);
}
/* Set initial window resolution */
bInFullScreen = ConfigureParams.Screen.bFullScreen;
Screen_SetResolution(false);
if (bGrabMouse)
SDL_WM_GrabInput(SDL_GRAB_ON);
Video_SetScreenRasters(); /* Set rasters ready for first screen */
/* Configure some SDL stuff: */
SDL_ShowCursor(SDL_DISABLE);
}
/*-----------------------------------------------------------------------*/
/**
* Free screen bitmap and allocated resources
*/
void Screen_UnInit(void)
{
int i;
/* Free memory used for copies */
for (i = 0; i < NUM_FRAMEBUFFERS; i++)
{
free(FrameBuffers[i].pSTScreen);
free(FrameBuffers[i].pSTScreenCopy);
}
#if WITH_SDL2
Screen_FreeSDL2Resources();
#endif
}
/*-----------------------------------------------------------------------*/
/**
* Reset screen
*/
void Screen_Reset(void)
{
/* On re-boot, always correct ST resolution for monitor, eg Colour/Mono */
if (bUseVDIRes)
{
STRes = VDIRes;
}
else
{
if (bUseHighRes)
{
STRes = ST_HIGH_RES;
TTRes = TT_HIGH_RES;
}
else
{
STRes = ST_LOW_RES;
TTRes = TT_MEDIUM_RES;
}
}
/* Cause full update */
Screen_ModeChanged(false);
}
/*-----------------------------------------------------------------------*/
/**
* Set flags so screen will be TOTALLY re-drawn (clears whole of full-screen)
* next time around
*/
void Screen_SetFullUpdate(void)
{
int i;
/* Update frame buffers */
for (i = 0; i < NUM_FRAMEBUFFERS; i++)
FrameBuffers[i].bFullUpdate = true;
}
/*-----------------------------------------------------------------------*/
/**
* Clear Window display memory
*/
static void Screen_ClearScreen(void)
{
SDL_FillRect(sdlscrn, &STScreenRect, SDL_MapRGB(sdlscrn->format, 0, 0, 0));
}
/*-----------------------------------------------------------------------*/
/**
* Return true if (falcon/tt) hostscreen functions need to be used
* instead of the (st/ste) functions here.
*/
static bool Screen_UseHostScreen(void)
{
return ((ConfigureParams.System.nMachineType == MACHINE_FALCON
|| ConfigureParams.System.nMachineType == MACHINE_TT)
&& !bUseVDIRes);
}
/*-----------------------------------------------------------------------*/
/**
* Force screen redraw. Does the right thing regardless of whether
* we're in ST/STe, Falcon or TT mode. Needed when switching modes
* while emulation is paused.
*/
static void Screen_Refresh(void)
{
if (!bUseVDIRes)
{
if (ConfigureParams.System.nMachineType == MACHINE_FALCON)
{
VIDEL_renderScreen();
return;
}
else if (ConfigureParams.System.nMachineType == MACHINE_TT)
{
Video_RenderTTScreen();
return;
}
}
Screen_DrawFrame(true);
}
/*-----------------------------------------------------------------------*/
/**
* Enter Full screen mode
*/
void Screen_EnterFullScreen(void)
{
bool bWasRunning;
if (!bInFullScreen)
{
/* Hold things... */
bWasRunning = Main_PauseEmulation(false);
bInFullScreen = true;
if (Screen_UseHostScreen())
{
HostScreen_toggleFullScreen();
}
else
{
Screen_SetResolution(true);
Screen_ClearScreen(); /* Black out screen bitmap as will be invalid when return */
}
SDL_Delay(20); /* To give monitor time to change to new resolution */
if (bWasRunning)
{
/* And off we go... */
Main_UnPauseEmulation();
}
else
{
Screen_Refresh();
}
SDL_WM_GrabInput(SDL_GRAB_ON); /* Grab mouse pointer in fullscreen */
}
}
/*-----------------------------------------------------------------------*/
/**
* Return from Full screen mode back to a window
*/
void Screen_ReturnFromFullScreen(void)
{
bool bWasRunning;
if (bInFullScreen)
{
/* Hold things... */
bWasRunning = Main_PauseEmulation(false);
bInFullScreen = false;
if (Screen_UseHostScreen())
{
HostScreen_toggleFullScreen();
}
else
{
Screen_SetResolution(true);
}
SDL_Delay(20); /* To give monitor time to switch resolution */
if (bWasRunning)
{
/* And off we go... */
Main_UnPauseEmulation();
}
else
{
Screen_Refresh();
}
if (!bGrabMouse)
{
/* Un-grab mouse pointer in windowed mode */
SDL_WM_GrabInput(SDL_GRAB_OFF);
}
}
}
/*-----------------------------------------------------------------------*/
/**
* Have we changed between low/med/high res?
*/
static void Screen_DidResolutionChange(int new_res)
{
if (new_res != STRes)
{
STRes = new_res;
Screen_ModeChanged(false);
}
else
{
/* Did change overscan mode? Causes full update */
if (pFrameBuffer->OverscanModeCopy != OverscanMode)
pFrameBuffer->bFullUpdate = true;
}
}
/*-----------------------------------------------------------------------*/
/**
* Force things associated with changing between low/medium/high res.
*/
void Screen_ModeChanged(bool bForceChange)
{
if (!sdlscrn)
{
/* screen not yet initialized */
return;
}
/* Don't run this function if Videl emulation is running! */
if (ConfigureParams.System.nMachineType == MACHINE_FALCON && !bUseVDIRes)
{
VIDEL_ZoomModeChanged(bForceChange);
}
else if (ConfigureParams.System.nMachineType == MACHINE_TT && !bUseVDIRes)
{
int width, height, bpp;
Video_GetTTRes(&width, &height, &bpp);
HostScreen_setWindowSize(width, height, 8, bForceChange);
}
else
{
/* Set new display mode, if differs from current */
Screen_SetResolution(bForceChange);
Screen_SetFullUpdate();
}
if (bInFullScreen || bGrabMouse)
SDL_WM_GrabInput(SDL_GRAB_ON);
else
SDL_WM_GrabInput(SDL_GRAB_OFF);
}
/*-----------------------------------------------------------------------*/
/**
* Compare current resolution on line with previous, and set 'UpdateLine' accordingly
* Return if swap between low/medium resolution
*/
static bool Screen_CompareResolution(int y, int *pUpdateLine, int oldres)
{
/* Check if wrote to resolution register */
if (HBLPaletteMasks[y]&PALETTEMASK_RESOLUTION) /* See 'Intercept_ShifterMode_WriteByte' */
{
int newres = (HBLPaletteMasks[y]>>16)&ST_MEDIUM_RES_BIT;
/* Did resolution change? */
if (newres != (int)((pFrameBuffer->HBLPaletteMasks[y]>>16)&ST_MEDIUM_RES_BIT))
*pUpdateLine |= PALETTEMASK_UPDATERES;
else
*pUpdateLine &= ~PALETTEMASK_UPDATERES;
/* Have used any low/medium res mix? */
return (newres != (oldres&ST_MEDIUM_RES_BIT));
}
return false;
}
/*-----------------------------------------------------------------------*/
/**
* Check to see if palette changes cause screen update and keep 'HBLPalette[]' up-to-date
*/
static void Screen_ComparePalette(int y, int *pUpdateLine)
{
bool bPaletteChanged = false;
int i;
/* Did write to palette in this or previous frame? */
if (((HBLPaletteMasks[y]|pFrameBuffer->HBLPaletteMasks[y])&PALETTEMASK_PALETTE)!=0)
{
/* Check and update ones which changed */
for (i = 0; i < 16; i++)
{
if (HBLPaletteMasks[y]&(1<<i)) /* Update changes in ST palette */
HBLPalette[i] = HBLPalettes[(y*16)+i];
}
/* Now check with same palette from previous frame for any differences(may be changing palette back) */
for (i = 0; (i < 16) && (!bPaletteChanged); i++)
{
if (HBLPalette[i]!=pFrameBuffer->HBLPalettes[(y*16)+i])
bPaletteChanged = true;
}
if (bPaletteChanged)
*pUpdateLine |= PALETTEMASK_UPDATEPAL;
else
*pUpdateLine &= ~PALETTEMASK_UPDATEPAL;
}
}
/*-----------------------------------------------------------------------*/
/**
* Check for differences in Palette and Resolution from Mask table and update
* and store off which lines need updating and create full-screen palette.
* (It is very important for these routines to check for colour changes with
* the previous screen so only the very minimum parts are updated).
* Return new STRes value.
*/
static int Screen_ComparePaletteMask(int res)
{
bool bLowMedMix = false;
int LineUpdate = 0;
int y;
/* Set for monochrome? */
if (bUseHighRes)
{
OverscanMode = OVERSCANMODE_NONE;
/* Just copy mono colours, 0x777 checked also in convert/vdi2.c */
if (HBLPalettes[0] & 0x777)
{
HBLPalettes[0] = 0x777;
HBLPalettes[1] = 0x000;
}
else
{
HBLPalettes[0] = 0x000;
HBLPalettes[1] = 0x777;
}
/* Colors changed? */
if (HBLPalettes[0] != PrevHBLPalette[0])
pFrameBuffer->bFullUpdate = true;
/* Set bit to flag 'full update' */
if (pFrameBuffer->bFullUpdate)
ScrUpdateFlag = PALETTEMASK_UPDATEFULL;
else
ScrUpdateFlag = 0x00000000;
}
/* Use VDI resolution? */
if (bUseVDIRes)
{
/* Force to VDI resolution screen, without overscan */
res = VDIRes;
/* Colors changed? */
if (HBLPalettes[0] != PrevHBLPalette[0])
pFrameBuffer->bFullUpdate = true;
/* Set bit to flag 'full update' */
if (pFrameBuffer->bFullUpdate)
ScrUpdateFlag = PALETTEMASK_UPDATEFULL;
else
ScrUpdateFlag = 0x00000000;
}
/* Are in Mono? Force to monochrome and no overscan */
else if (bUseHighRes)
{
/* Force to standard hi-resolution screen, without overscan */
res = ST_HIGH_RES;
}
else /* Full colour */
{
/* Get resolution */
//res = (HBLPaletteMasks[0]>>16)&ST_RES_MASK;
/* [NP] keep only low/med bit (could be hires in case of overscan on the 1st line) */
res = (HBLPaletteMasks[0]>>16)&ST_MEDIUM_RES_BIT;
/* Do all lines - first is tagged as full-update */
for (y = 0; y < NUM_VISIBLE_LINES; y++)
{
/* Find any resolution/palette change and update palette/mask buffer */
/* ( LineUpdate has top two bits set to say if line needs updating due to palette or resolution change ) */
bLowMedMix |= Screen_CompareResolution(y, &LineUpdate, res);
Screen_ComparePalette(y,&LineUpdate);
HBLPaletteMasks[y] = (HBLPaletteMasks[y]&(~PALETTEMASK_UPDATEMASK)) | LineUpdate;
/* Copy palette and mask for next frame */
memcpy(&pFrameBuffer->HBLPalettes[y*16],HBLPalette,sizeof(short int)*16);
pFrameBuffer->HBLPaletteMasks[y] = HBLPaletteMasks[y];
}
/* Did mix/have medium resolution? */
if (bLowMedMix || (res & ST_MEDIUM_RES_BIT))
res = ST_MEDIUM_RES;
}
return res;
}
/*-----------------------------------------------------------------------*/
/**
* Update Palette Mask to show 'full-update' required. This is usually done after a resolution change
* or when going between a Window and full-screen display
*/
static void Screen_SetFullUpdateMask(void)
{
int y;
for (y = 0; y < NUM_VISIBLE_LINES; y++)
HBLPaletteMasks[y] |= PALETTEMASK_UPDATEFULL;
}
/*-----------------------------------------------------------------------*/
/**
* Set details for ST screen conversion.
*/
static void Screen_SetConvertDetails(void)
{
pSTScreen = pFrameBuffer->pSTScreen; /* Source in ST memory */
pSTScreenCopy = pFrameBuffer->pSTScreenCopy; /* Previous ST screen */
pPCScreenDest = sdlscrn->pixels; /* Destination PC screen */
PCScreenBytesPerLine = sdlscrn->pitch; /* Bytes per line */
/* Center to available framebuffer */
pPCScreenDest += PCScreenOffsetY * PCScreenBytesPerLine + PCScreenOffsetX * (sdlscrn->format->BitsPerPixel/8);
pHBLPalettes = pFrameBuffer->HBLPalettes; /* HBL palettes pointer */
/* Not in TV-Mode? Then double up on Y: */
bScrDoubleY = !(ConfigureParams.Screen.nMonitorType == MONITOR_TYPE_TV);
if (bUseVDIRes)
{
/* Select screen draw for standard or VDI display */
STScreenLeftSkipBytes = 0;
STScreenWidthBytes = VDIWidth * VDIPlanes / 8;
STScreenStartHorizLine = 0;
STScreenEndHorizLine = VDIHeight;
}
else
{
if (ConfigureParams.Screen.bAllowOverscan) /* Use borders? */
{
/* Always draw to WHOLE screen including ALL borders */
STScreenLeftSkipBytes = 0; /* Number of bytes to skip on ST screen for left (border) */
if (bUseHighRes)
{
pFrameBuffer->OverscanModeCopy = OverscanMode = OVERSCANMODE_NONE;
STScreenStartHorizLine = 0;
STScreenEndHorizLine = 400;
}
else
{
STScreenWidthBytes = SCREENBYTES_LINE; /* Number of horizontal bytes in our ST screen */
#ifdef GEKKO
/* Start the line higher with nWiiOffset at 12 by default. It prevents cutting the bottom too much. */
STScreenStartHorizLine = OVERSCAN_TOP - nBorderPixelsTop + ConfigureParams.Screen.nWiiOffset;
STScreenEndHorizLine = OVERSCAN_TOP + 200 + nBorderPixelsBottom + ConfigureParams.Screen.nWiiOffset;
#else
STScreenStartHorizLine = OVERSCAN_TOP - nBorderPixelsTop;
STScreenEndHorizLine = OVERSCAN_TOP + 200 + nBorderPixelsBottom;
#endif
}
}
else
{
/* Only draw main area and centre on Y */
STScreenLeftSkipBytes = SCREENBYTES_LEFT;
STScreenWidthBytes = SCREENBYTES_MIDDLE;
STScreenStartHorizLine = OVERSCAN_TOP;
STScreenEndHorizLine = OVERSCAN_TOP + (bUseHighRes ? 400 : 200);
}
}
}
/*-----------------------------------------------------------------------*/
/**
* Lock full-screen for drawing
*/
static bool Screen_Lock(void)
{
if (SDL_MUSTLOCK(sdlscrn))
{
if (SDL_LockSurface(sdlscrn))
{
Screen_ReturnFromFullScreen(); /* All OK? If not need to jump back to a window */
return false;
}
}
return true;
}
/*-----------------------------------------------------------------------*/
/**
* UnLock full-screen
*/
static void Screen_UnLock(void)
{
if ( SDL_MUSTLOCK(sdlscrn) )
SDL_UnlockSurface(sdlscrn);
}
/*-----------------------------------------------------------------------*/
/**
* Blit our converted ST screen to window/full-screen
*/
static void Screen_Blit(SDL_Rect *sbar_rect)
{
unsigned char *pTmpScreen;
#if 0 /* double buffering cannot be used with partial screen updates */
# if NUM_FRAMEBUFFERS > 1
if (bInFullScreen && (sdlscrn->flags & SDL_DOUBLEBUF))
{
/* Swap screen */
if (pFrameBuffer==&FrameBuffers[0])
pFrameBuffer = &FrameBuffers[1];
else
pFrameBuffer = &FrameBuffers[0];
SDL_Flip(sdlscrn);
}
else
# endif
#endif
{
int count = 1;
SDL_Rect rects[2];
rects[0] = STScreenRect;
if (sbar_rect)
{
rects[1] = *sbar_rect;
count = 2;
}
SDL_UpdateRects(sdlscrn, count, rects);
}
/* Swap copy/raster buffers in screen. */
pTmpScreen = pFrameBuffer->pSTScreenCopy;
pFrameBuffer->pSTScreenCopy = pFrameBuffer->pSTScreen;
pFrameBuffer->pSTScreen = pTmpScreen;
}
/*-----------------------------------------------------------------------*/
/**
* Draw ST screen to window/full-screen framebuffer
* @param bForceFlip Force screen update, even if contents did not change
* @return true if screen contents changed
*/
static bool Screen_DrawFrame(bool bForceFlip)
{
int new_res;
void (*pDrawFunction)(void);
static bool bPrevFrameWasSpec512 = false;
SDL_Rect *sbar_rect;
/* Scan palette/resolution masks for each line and build up palette/difference tables */
new_res = Screen_ComparePaletteMask(STRes);
/* Do require palette? Check if changed and update */
Screen_Handle8BitPalettes();
/* Did we change resolution this frame - allocate new screen if did so */
Screen_DidResolutionChange(new_res);
/* Is need full-update, tag as such */
if (pFrameBuffer->bFullUpdate)
Screen_SetFullUpdateMask();
/* restore area potentially left under overlay led
* and saved by Statusbar_OverlayBackup()
*/
Statusbar_OverlayRestore(sdlscrn);
/* Lock screen for direct screen surface format writes */
if (Screen_Lock())
{
bScreenContentsChanged = false; /* Did change (ie needs blit?) */
/* Set details */
Screen_SetConvertDetails();
/* Clear screen on full update to clear out borders and also interleaved lines */
if (pFrameBuffer->bFullUpdate && !bUseVDIRes)
Screen_ClearScreen();
/* Call drawing for full-screen */
if (bUseVDIRes)
{
pDrawFunction = ScreenDrawFunctionsVDI[VDIRes];
}
else
{
pDrawFunction = ScreenDrawFunctionsNormal[STRes];
/* Check if is Spec512 image */
if (Spec512_IsImage())
{
bPrevFrameWasSpec512 = true;
/* What mode were we in? Keep to 320xH or 640xH */
if (pDrawFunction==ConvertLowRes_320x16Bit)
pDrawFunction = ConvertLowRes_320x16Bit_Spec;
else if (pDrawFunction==ConvertLowRes_640x16Bit)
pDrawFunction = ConvertLowRes_640x16Bit_Spec;
else if (pDrawFunction==ConvertLowRes_320x32Bit)
pDrawFunction = ConvertLowRes_320x32Bit_Spec;
else if (pDrawFunction==ConvertLowRes_640x32Bit)
pDrawFunction = ConvertLowRes_640x32Bit_Spec;
else if (pDrawFunction==ConvertMediumRes_640x32Bit)
pDrawFunction = ConvertMediumRes_640x32Bit_Spec;
else if (pDrawFunction==ConvertMediumRes_640x16Bit)
pDrawFunction = ConvertMediumRes_640x16Bit_Spec;
}
else if (bPrevFrameWasSpec512)
{
/* If we switch back from Spec512 mode to normal
* screen rendering, we have to make sure to do
* a full update of the screen. */
Screen_SetFullUpdateMask();
bPrevFrameWasSpec512 = false;
}
}
if (pDrawFunction)
CALL_VAR(pDrawFunction);
/* Unlock screen */
Screen_UnLock();
/* draw overlay led(s) or statusbar after unlock */
Statusbar_OverlayBackup(sdlscrn);
sbar_rect = Statusbar_Update(sdlscrn, false);
/* Clear flags, remember type of overscan as if change need screen full update */
pFrameBuffer->bFullUpdate = false;
pFrameBuffer->OverscanModeCopy = OverscanMode;
/* And show to user */
if (bScreenContentsChanged || bForceFlip || sbar_rect)
{
Screen_Blit(sbar_rect);
}
return bScreenContentsChanged;
}
return false;
}
/*-----------------------------------------------------------------------*/
/**
* Draw ST screen to window/full-screen
*/
bool Screen_Draw(void)
{
if (!bQuitProgram && VideoBase)
{
/* And draw (if screen contents changed) */
return Screen_DrawFrame(false);
}
return false;
}
/* -------------- screen conversion routines --------------------------------
Screen conversion routines. We have a number of routines to convert ST screen
to PC format. We split these into Low, Medium and High each with 8/16-bit
versions. To gain extra speed, as almost half of the processing time can be
spent in these routines, we check for any changes from the previously
displayed frame. AdjustLinePaletteRemap() sets a flag to tell the routines
if we need to totally update a line (ie full update, or palette/res change)
or if we just can do a difference check.
We convert each screen 16 pixels at a time by use of a couple of look-up
tables. These tables convert from 2-plane format to bbp and then we can add
two of these together to get 4-planes. This keeps the tables small and thus
improves speed. We then look these bbp values up as an RGB/Index value to
copy to the screen.
*/
/*-----------------------------------------------------------------------*/
/**
* Update the STRGBPalette[] array with current colours for this raster line.
*
* Return 'ScrUpdateFlag', 0x80000000=Full update, 0x40000000=Update
* as palette changed
*/
static int AdjustLinePaletteRemap(int y)
{
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
static const int endiantable[16] = {0,2,1,3,8,10,9,11,4,6,5,7,12,14,13,15};
#endif
Uint16 *actHBLPal;
int i;
/* Copy palette and convert to RGB in display format */
actHBLPal = pHBLPalettes + (y<<4); /* offset in palette */
for (i=0; i<16; i++)
{
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
STRGBPalette[endiantable[i]] = ST2RGB[*actHBLPal++];
#else
STRGBPalette[i] = ST2RGB[*actHBLPal++];
#endif
}
ScrUpdateFlag = HBLPaletteMasks[y];
return ScrUpdateFlag;
}
/*-----------------------------------------------------------------------*/
/**
* Run updates to palette(STRGBPalette[]) until get to screen line
* we are to convert from
*/
static void Convert_StartFrame(void)
{
int y = 0;
/* Get #lines before conversion starts */
int lines = STScreenStartHorizLine;
while (lines--)
AdjustLinePaletteRemap(y++); /* Update palette */
}
/* lookup tables and conversion macros */
#include "convert/macros.h"
/* Conversion routines */
#include "convert/low320x8.c" /* LowRes To 320xH x 8-bit color */
#include "convert/low640x8.c" /* LowRes To 640xH x 8-bit color */
#include "convert/med640x8.c" /* MediumRes To 640xH x 8-bit color */
#include "convert/high640x8.c" /* HighRes To 640xH x 8-bit color */
#include "convert/low320x16.c" /* LowRes To 320xH x 16-bit color */
#include "convert/low640x16.c" /* LowRes To 640xH x 16-bit color */
#include "convert/med640x16.c" /* MediumRes To 640xH x 16-bit color */
#include "convert/low320x16_spec.c" /* LowRes Spectrum 512 To 320xH x 16-bit color */
#include "convert/low640x16_spec.c" /* LowRes Spectrum 512 To 640xH x 16-bit color */
#include "convert/med640x16_spec.c" /* MediumRes Spectrum 512 To 640xH x 16-bit color */
#include "convert/low320x32.c" /* LowRes To 320xH x 32-bit color */
#include "convert/low640x32.c" /* LowRes To 640xH x 32-bit color */
#include "convert/med640x32.c" /* MediumRes To 640xH x 32-bit color */
#include "convert/low320x32_spec.c" /* LowRes Spectrum 512 To 320xH x 32-bit color */
#include "convert/low640x32_spec.c" /* LowRes Spectrum 512 To 640xH x 32-bit color */
#include "convert/med640x32_spec.c" /* MediumRes Spectrum 512 To 640xH x 32-bit color */
#include "convert/vdi16.c" /* VDI x 16 color */
#include "convert/vdi4.c" /* VDI x 4 color */
#include "convert/vdi2.c" /* VDI x 2 color */