|
|
1.1 root 1: /*
1.1.1.6 root 2: Hatari - screen.c
1.1 root 3:
1.1.1.6 root 4: This file is distributed under the GNU Public License, version 2 or at your
5: option any later version. Read the file gpl.txt for details.
6:
7: This code converts a 1/2/4 plane ST format screen to either 8 or 16-bit PC
1.1.1.11! root 8: format. An awful lot of processing is needed to do this conversion - we
1.1.1.6 root 9: cannot simply change palettes on interrupts as it is possible with DOS.
10: The main code processes the palette/resolution mask tables to find exactly
11: which lines need to updating and the conversion routines themselves only
12: update 16-pixel blocks which differ from the previous frame - this gives a
13: large performance increase.
14: Each conversion routine can convert any part of the source ST screen (which
15: includes the overscan border, usually set to colour zero) so they can be used
16: for both window and full-screen mode.
17: Note that in Hi-Resolution we have no overscan and just two colors so we can
18: optimise things further. Also when running in maximum speed we make sure we
19: only convert the screen every 50 times a second - inbetween frames are not
20: processed.
1.1 root 21: */
1.1.1.11! root 22: char Screen_rcsid[] = "Hatari $Id: screen.c,v 1.45 2005/10/04 09:45:14 thothy Exp $";
1.1 root 23:
24: #include <SDL.h>
1.1.1.11! root 25: #include <SDL_endian.h>
1.1 root 26:
27: #include "main.h"
1.1.1.7 root 28: #include "configuration.h"
1.1 root 29: #include "ikbd.h"
30: #include "m68000.h"
31: #include "misc.h"
32: #include "printer.h"
33: #include "screen.h"
1.1.1.11! root 34: #include "convert/routines.h"
1.1 root 35: #include "screenSnapShot.h"
36: #include "sound.h"
37: #include "spec512.h"
38: #include "vdi.h"
39: #include "video.h"
40:
41:
1.1.1.11! root 42: BOOL bGrabMouse = FALSE; /* Grab the mouse cursor in the window */
! 43: BOOL bInFullScreen=FALSE; /* TRUE if in full screen */
! 44: int STScreenLeftSkipBytes;
! 45: int STScreenStartHorizLine; /* Start lines to be converted */
! 46: int STRes=ST_LOW_RES; /* current resolution */
! 47: int PrevSTRes=ST_LOW_RES; /* previous ST resolution */
! 48: Uint32 STRGBPalette[16]; /* Palette buffer used in conversion routines */
! 49: Uint32 ST2RGB[4096]; /* Table to convert ST 0x777 / STe 0xfff palette to PC format RGB551 (2 pixels each entry) */
! 50: SDL_Surface *sdlscrn; /* The SDL screen surface */
! 51: Uint8 *pSTScreen;
! 52: FRAMEBUFFER *pFrameBuffer; /* Pointer into current 'FrameBuffer' */
! 53:
! 54: static FRAMEBUFFER FrameBuffers[NUM_FRAMEBUFFERS]; /* Store frame buffer details to tell how to update */
! 55: static Uint8 *pSTScreenCopy; /* Keep track of current and previous ST screen data */
! 56: static Uint8 *pPCScreenDest; /* Destination PC buffer */
! 57: static int STScreenEndHorizLine; /* End lines to be converted */
! 58: static int PCScreenBytesPerLine;
! 59: static int STScreenWidthBytes;
1.1 root 60:
1.1.1.11! root 61: static int STScreenLineOffset[NUM_VISIBLE_LINES]; /* Offsets for ST screen lines eg, 0,160,320... */
! 62: static Uint16 HBLPalette[16], PrevHBLPalette[16]; /* Current palette for line, also copy of first line */
1.1 root 63:
1.1.1.11! root 64: static void *ScreenDrawFunctionsNormal[4]; /* Screen draw functions */
! 65: static void *ScreenDrawFunctionsVDI[3] =
1.1.1.10 root 66: {
67: ConvertVDIRes_16Colour,
68: ConvertVDIRes_4Colour,
69: ConvertVDIRes_2Colour
70: };
71:
1.1.1.11! root 72: static BOOL bScreenContentsChanged; /* TRUE if buffer changed and requires blitting */
! 73: static BOOL bScrDoubleY; /* TRUE if double on Y */
! 74: static int ScrUpdateFlag; /* Bit mask of how to update screen */
! 75:
1.1.1.10 root 76:
77: /*-----------------------------------------------------------------------*/
78: /*
1.1.1.11! root 79: Create ST 0x777 or STe 0xfff colour format to 16-bits per pixel.
1.1.1.10 root 80: Called each time when changed resolution or to/from fullscreen mode.
81: */
82: static void Screen_SetupRGBTable(void)
83: {
1.1.1.11! root 84: Uint16 STColour, RGBColour;
! 85: Uint16 r, g, b;
1.1.1.10 root 86:
1.1.1.11! root 87: if (ConfigureParams.System.nMachineType == MACHINE_ST)
! 88: {
! 89: /* Do Red, Green and Blue for all 8*8*8 = 512 ST colours */
! 90: for(r=0; r < 8; r++)
! 91: {
! 92: for(g=0; g < 8; g++)
! 93: {
! 94: for(b=0; b < 8; b++)
! 95: {
! 96: /* ST 0x777 format */
! 97: STColour = (r<<8) | (g<<4) | (b);
! 98: RGBColour = SDL_MapRGB(sdlscrn->format, (r<<5), (g<<5), (b<<5));
! 99: /* As longs, for speed (write two pixels at once) */
! 100: ST2RGB[STColour] = (RGBColour<<16) | RGBColour;
! 101: }
! 102: }
! 103: }
! 104: }
! 105: else
1.1.1.10 root 106: {
1.1.1.11! root 107: int rr, gg, bb;
! 108: /* Do Red, Green and Blue for all 16*16*16 = 4096 STe colours */
! 109: for(r=0; r < 16; r++)
1.1.1.10 root 110: {
1.1.1.11! root 111: for(g=0; g < 16; g++)
1.1.1.10 root 112: {
1.1.1.11! root 113: for(b=0; b < 16; b++)
! 114: {
! 115: /* STe 0xfff format */
! 116: STColour = (r<<8) | (g<<4) | (b);
! 117: rr = ((r & 0x7) << 5) | ((r & 0x8) << 1);
! 118: gg = ((g & 0x7) << 5) | ((g & 0x8) << 1);
! 119: bb = ((b & 0x7) << 5) | ((b & 0x8) << 1);
! 120: RGBColour = SDL_MapRGB(sdlscrn->format, rr, gg, bb);
! 121: /* As longs, for speed (write two pixels at once) */
! 122: ST2RGB[STColour] = (RGBColour<<16) | RGBColour;
! 123: }
1.1.1.10 root 124: }
125: }
126: }
127: }
128:
129: /*-----------------------------------------------------------------------*/
130: /*
131: Create new palette for display.
132: */
133: static void Screen_CreatePalette(void)
134: {
135: #if SDL_BYTEORDER == SDL_BIG_ENDIAN
136: static const int endiantable[16] = {0,2,1,3,8,10,9,11,4,6,5,7,12,14,13,15};
137: #endif
138: SDL_Color sdlColors[16];
139: int i, j;
140:
141: if (bUseHighRes)
142: {
143: /* Colors for monochrome screen mode emulation */
144: if (HBLPalettes[0])
145: {
146: sdlColors[0].r = sdlColors[0].g = sdlColors[0].b = 255;
147: sdlColors[1].r = sdlColors[1].g = sdlColors[1].b = 0;
148: }
149: else
150: {
151: sdlColors[0].r = sdlColors[0].g = sdlColors[0].b = 0;
152: sdlColors[1].r = sdlColors[1].g = sdlColors[1].b = 255;
153: }
154: SDL_SetColors(sdlscrn, sdlColors, 10, 2);
155: /*SDL_SetColors(sdlscrn, sdlColors, 0, 2);*/
156: }
157: else
158: {
1.1.1.11! root 159: if (ConfigureParams.System.nMachineType == MACHINE_ST)
1.1.1.10 root 160: {
1.1.1.11! root 161: /* Colors for ST color screen mode emulation */
! 162: for (i=0; i<16; i++)
! 163: {
1.1.1.10 root 164: #if SDL_BYTEORDER == SDL_BIG_ENDIAN
1.1.1.11! root 165: j = endiantable[i];
1.1.1.10 root 166: #else
1.1.1.11! root 167: j = i;
1.1.1.10 root 168: #endif
1.1.1.11! root 169: sdlColors[j].r = ((HBLPalettes[i]>>8) & 0x7) << 5;
! 170: sdlColors[j].g = ((HBLPalettes[i]>>4) & 0x7) << 5;
! 171: sdlColors[j].b = ( HBLPalettes[i] & 0x7) << 5;
! 172: }
! 173: SDL_SetColors(sdlscrn, sdlColors, 10, 16);
! 174: }
! 175: else
! 176: {
! 177: int r, g, b;
! 178: /* Colors for STe color screen mode emulation */
! 179: for (i=0; i<16; i++)
! 180: {
! 181: #if SDL_BYTEORDER == SDL_BIG_ENDIAN
! 182: j = endiantable[i];
! 183: #else
! 184: j = i;
! 185: #endif
! 186: /* normalize all to 0x1e0 */
! 187: r = HBLPalettes[i] >> 3;
! 188: g = HBLPalettes[i] << 1;
! 189: b = HBLPalettes[i] << 5;
! 190: /* move top bit of 0x1e0 to lowest in 0xf0 */
! 191: sdlColors[j].r = (r & 0xe0) | ((r & 0x100) >> 4);
! 192: sdlColors[j].g = (g & 0xe0) | ((g & 0x100) >> 4);
! 193: sdlColors[j].b = (b & 0xe0) | ((b & 0x100) >> 4);
! 194: }
! 195: SDL_SetColors(sdlscrn, sdlColors, 10, 16);
1.1.1.10 root 196: }
197: }
198: }
199:
200:
201: /*-----------------------------------------------------------------------*/
202: /*
203: Create 8-Bit palette for display if needed.
204: */
205: static void Screen_Handle8BitPalettes(void)
206: {
207: BOOL bPaletteChanged=FALSE;
208: int i;
209:
210: /* Do need to check for 8-Bit palette change? Ie, update whole screen */
211: /* VDI screens and monochrome modes are ALL 8-Bit at the moment! */
212: if (sdlscrn->format->BitsPerPixel == 8)
213: {
214: /* If using HiRes palette update with full update flag */
215: if (!bUseHighRes)
216: {
217: /* Check if palette of 16 colours changed from previous frame */
218: for (i=0; i<16 && !bPaletteChanged; i++)
219: {
220: /* Check with first line palette(stored in 'Screen_ComparePaletteMask') */
221: if (HBLPalettes[i] != PrevHBLPalette[i])
222: bPaletteChanged = TRUE;
223: }
224: }
225:
226: /* Did palette change or do we require a full update? */
227: if (bPaletteChanged || pFrameBuffer->bFullUpdate)
228: {
229: /* Create palette, for Full-Screen of Window */
230: Screen_CreatePalette();
231: /* Make sure update whole screen */
232: pFrameBuffer->bFullUpdate = TRUE;
233: }
234: }
235:
236: /* Copy old palette for 8-Bit compare as this routine writes over it */
1.1.1.11! root 237: memcpy(PrevHBLPalette,HBLPalettes, sizeof(Uint16)*16);
1.1.1.10 root 238: }
239:
1.1 root 240:
241: /*-----------------------------------------------------------------------*/
242: /*
1.1.1.10 root 243: Set screen draw functions.
1.1.1.7 root 244: */
1.1.1.10 root 245: static void Screen_SetDrawFunctions(void)
246: {
247: switch (ConfigureParams.Screen.ChosenDisplayMode)
248: {
249: case DISPLAYMODE_LOWCOL_LOWRES: /* low color, low resolution */
250: ScreenDrawFunctionsNormal[ST_LOW_RES] = ConvertLowRes_320x8Bit;
251: ScreenDrawFunctionsNormal[ST_MEDIUM_RES] = ConvertMediumRes_640x8Bit;
252: ScreenDrawFunctionsNormal[ST_HIGH_RES] = ConvertHighRes_640x8Bit;
253: ScreenDrawFunctionsNormal[ST_LOWMEDIUM_MIX_RES] = ConvertMediumRes_640x8Bit;
254: break;
255: case DISPLAYMODE_LOWCOL_HIGHRES: /* low color, zoomed resolution */
256: ScreenDrawFunctionsNormal[ST_LOW_RES] = ConvertLowRes_640x8Bit;
257: ScreenDrawFunctionsNormal[ST_MEDIUM_RES] = ConvertMediumRes_640x8Bit;
258: ScreenDrawFunctionsNormal[ST_HIGH_RES] = ConvertHighRes_640x8Bit;
259: ScreenDrawFunctionsNormal[ST_LOWMEDIUM_MIX_RES] = ConvertMediumRes_640x8Bit;
260: break;
261: case DISPLAYMODE_HICOL_LOWRES: /* high color, low resolution */
262: ScreenDrawFunctionsNormal[ST_LOW_RES] = ConvertLowRes_320x16Bit;
263: ScreenDrawFunctionsNormal[ST_MEDIUM_RES] = ConvertMediumRes_640x16Bit;
264: ScreenDrawFunctionsNormal[ST_HIGH_RES] = ConvertHighRes_640x8Bit;
265: ScreenDrawFunctionsNormal[ST_LOWMEDIUM_MIX_RES] = ConvertMediumRes_640x16Bit;
266: break;
267: case DISPLAYMODE_HICOL_HIGHRES: /* high color, zoomed resolution */
268: ScreenDrawFunctionsNormal[ST_LOW_RES] = ConvertLowRes_640x16Bit;
269: ScreenDrawFunctionsNormal[ST_MEDIUM_RES] = ConvertMediumRes_640x16Bit;
270: ScreenDrawFunctionsNormal[ST_HIGH_RES] = ConvertHighRes_640x8Bit;
271: ScreenDrawFunctionsNormal[ST_LOWMEDIUM_MIX_RES] = ConvertMediumRes_640x16Bit;
272: break;
273: default:
274: fprintf(stderr, "Illegal display mode: %i\n", ConfigureParams.Screen.ChosenDisplayMode);
275: ScreenDrawFunctionsNormal[ST_LOW_RES] = NULL;
276: ScreenDrawFunctionsNormal[ST_MEDIUM_RES] = NULL;
277: ScreenDrawFunctionsNormal[ST_HIGH_RES] = NULL;
278: ScreenDrawFunctionsNormal[ST_LOWMEDIUM_MIX_RES] = NULL;
279: break;
280: }
281: }
282:
283:
284: /*-----------------------------------------------------------------------*/
285: /*
286: Initialize SDL screen surface / set resolution.
287: */
288: static void Screen_SetResolution(void)
1.1.1.7 root 289: {
290: int Width, Height, BitCount;
1.1.1.10 root 291: unsigned int sdlVideoFlags;
1.1.1.7 root 292:
1.1.1.10 root 293: /* Determine which resolution to use */
1.1.1.9 root 294: if (bUseVDIRes)
295: {
1.1.1.7 root 296: Width = VDIWidth;
297: Height = VDIHeight;
1.1.1.9 root 298: }
299: else
300: {
1.1.1.10 root 301: if (STRes == ST_LOW_RES &&
302: (ConfigureParams.Screen.ChosenDisplayMode == DISPLAYMODE_LOWCOL_LOWRES
303: || ConfigureParams.Screen.ChosenDisplayMode == DISPLAYMODE_HICOL_LOWRES))
1.1.1.9 root 304: {
1.1.1.10 root 305: Width = 320;
306: Height = 200;
307: }
308: else /* else use 640x400 */
309: {
310: Width = 640;
311: Height = 400;
1.1.1.9 root 312: }
1.1.1.7 root 313:
1.1.1.10 root 314: /* Adjust width/height for overscan borders, if mono or VDI we have no overscan */
315: if (ConfigureParams.Screen.bAllowOverscan && !bUseHighRes)
316: {
317: int nZoom = ((Width == 640) ? 2 : 1);
318: /* Add in overscan borders (if 640x200 bitmap is double on Y) */
319: Width += (OVERSCAN_LEFT+OVERSCAN_RIGHT) * nZoom;
320: Height += (OVERSCAN_TOP+OVERSCAN_BOTTOM) * nZoom;
321: }
1.1.1.7 root 322: }
323:
324: /* Bits per pixel */
1.1.1.9 root 325: if (ConfigureParams.Screen.ChosenDisplayMode == DISPLAYMODE_LOWCOL_LOWRES
326: || ConfigureParams.Screen.ChosenDisplayMode == DISPLAYMODE_LOWCOL_HIGHRES
327: || STRes == ST_HIGH_RES || bUseVDIRes)
1.1.1.7 root 328: {
329: BitCount = 8;
330: }
331: else
332: {
333: BitCount = 16;
334: }
335:
1.1.1.10 root 336: /* SDL Video attributes: */
337: if (bInFullScreen)
1.1.1.7 root 338: {
1.1.1.10 root 339: sdlVideoFlags = SDL_HWSURFACE|SDL_FULLSCREEN|SDL_HWPALETTE/*|SDL_DOUBLEBUF*/;
340: /* SDL_DOUBLEBUF is a good idea, but the GUI doesn't work with double buffered
341: * screens yet, so double buffering is currently disabled. */
1.1.1.7 root 342: }
343: else
344: {
1.1.1.10 root 345: sdlVideoFlags = SDL_SWSURFACE|SDL_HWPALETTE;
1.1.1.7 root 346: }
347:
1.1.1.10 root 348: sdlscrn = SDL_SetVideoMode(Width, Height, BitCount, sdlVideoFlags);
349: if (!sdlscrn)
1.1.1.7 root 350: {
1.1.1.10 root 351: fprintf(stderr, "Could not set video mode:\n %s\n", SDL_GetError() );
352: SDL_Quit();
353: exit(-2);
1.1.1.7 root 354: }
1.1.1.10 root 355:
356: /* Re-init screen palette: */
357: if (BitCount == 8)
358: Screen_Handle8BitPalettes(); /* Initialize new 8 bit palette */
359: else
360: Screen_SetupRGBTable(); /* Create color convertion table */
361:
362: if (!bGrabMouse)
363: SDL_WM_GrabInput(SDL_GRAB_OFF); /* Un-grab mouse pointer in windowed mode */
364:
365: Screen_SetDrawFunctions(); /* Set draw functions */
366: Screen_SetFullUpdate(); /* Cause full update of screen */
1.1.1.7 root 367: }
368:
1.1.1.8 root 369:
1.1.1.7 root 370: /*-----------------------------------------------------------------------*/
371: /*
1.1.1.11! root 372: Store Y offset for each horizontal line in our source ST screen for each reference in assembler(no multiply)
! 373: */
! 374: static void Screen_SetScreenLineOffsets(void)
! 375: {
! 376: int i;
! 377:
! 378: for(i=0; i<NUM_VISIBLE_LINES; i++)
! 379: STScreenLineOffset[i] = i * SCREENBYTES_LINE;
! 380: }
! 381:
! 382:
! 383: /*-----------------------------------------------------------------------*/
! 384: /*
1.1 root 385: Init Screen bitmap and buffers/tables needed for ST to PC screen conversion
386: */
387: void Screen_Init(void)
388: {
389: int i;
390:
391: /* Clear frame buffer structures and set current pointer */
1.1.1.10 root 392: memset(FrameBuffers, 0, NUM_FRAMEBUFFERS * sizeof(FRAMEBUFFER));
1.1 root 393:
394: /* Allocate previous screen check workspace. We are going to double-buffer a double-buffered screen. Oh. */
1.1.1.5 root 395: for(i=0; i<NUM_FRAMEBUFFERS; i++)
396: {
1.1.1.10 root 397: FrameBuffers[i].pSTScreen = (unsigned char *)malloc(((MAX_VDI_WIDTH*MAX_VDI_PLANES)/8)*MAX_VDI_HEIGHT);
398: FrameBuffers[i].pSTScreenCopy = (unsigned char *)malloc(((MAX_VDI_WIDTH*MAX_VDI_PLANES)/8)*MAX_VDI_HEIGHT);
399: if (!FrameBuffers[i].pSTScreen || !FrameBuffers[i].pSTScreenCopy)
400: {
401: fprintf(stderr, "Failed to allocate frame buffer memory.\n");
402: exit(-1);
403: }
1.1 root 404: }
405: pFrameBuffer = &FrameBuffers[0];
406:
1.1.1.10 root 407: Screen_SetResolution();
1.1 root 408:
409: Video_SetScreenRasters(); /* Set rasters ready for first screen */
410:
411: Screen_SetScreenLineOffsets(); /* Store offset to each horizontal line */
412:
1.1.1.2 root 413: /* Configure some SDL stuff: */
1.1 root 414: SDL_WM_SetCaption(PROG_NAME, "Hatari");
415: SDL_EventState(SDL_MOUSEMOTION, SDL_ENABLE);
416: SDL_EventState(SDL_MOUSEBUTTONDOWN, SDL_ENABLE);
417: SDL_EventState(SDL_MOUSEBUTTONUP, SDL_ENABLE);
1.1.1.4 root 418: SDL_ShowCursor(SDL_DISABLE);
1.1 root 419: }
420:
1.1.1.2 root 421:
1.1 root 422: /*-----------------------------------------------------------------------*/
423: /*
424: Free screen bitmap and allocated resources
425: */
426: void Screen_UnInit(void)
427: {
428: int i;
429:
1.1.1.3 root 430: /* Free memory used for copies */
1.1.1.7 root 431: for(i=0; i<NUM_FRAMEBUFFERS; i++)
432: {
1.1.1.10 root 433: free(FrameBuffers[i].pSTScreen);
434: free(FrameBuffers[i].pSTScreenCopy);
1.1 root 435: }
436: }
437:
1.1.1.2 root 438:
1.1 root 439: /*-----------------------------------------------------------------------*/
440: /*
441: Reset screen
442: */
443: void Screen_Reset(void)
444: {
445: /* On re-boot, always correct ST resolution for monitor, eg Colour/Mono */
1.1.1.5 root 446: if (bUseVDIRes)
447: {
1.1 root 448: STRes = VDIRes;
449: }
1.1.1.5 root 450: else
451: {
1.1 root 452: if (bUseHighRes)
453: STRes = ST_HIGH_RES;
454: else
455: STRes = ST_LOW_RES;
456: }
457: PrevSTRes = -1;
458: /* Cause full update */
459: Screen_SetFullUpdate();
460: }
461:
462:
463: /*-----------------------------------------------------------------------*/
464: /*
1.1.1.5 root 465: Set flags so screen will be TOTALLY re-drawn (clears whole of full-screen)
466: next time around
1.1 root 467: */
468: void Screen_SetFullUpdate(void)
469: {
470: int i;
471:
472: /* Update frame buffers */
473: for(i=0; i<NUM_FRAMEBUFFERS; i++)
474: FrameBuffers[i].bFullUpdate = TRUE;
475: }
476:
1.1.1.5 root 477:
1.1 root 478: /*-----------------------------------------------------------------------*/
479: /*
1.1.1.11! root 480: Clear Window display memory
! 481: */
! 482: static void Screen_ClearScreen(void)
! 483: {
! 484: SDL_FillRect(sdlscrn,NULL, SDL_MapRGB(sdlscrn->format, 0, 0, 0) );
! 485: }
! 486:
! 487: /*-----------------------------------------------------------------------*/
! 488: /*
1.1 root 489: Enter Full screen mode
490: */
491: void Screen_EnterFullScreen(void)
492: {
1.1.1.5 root 493: if (!bInFullScreen)
494: {
1.1 root 495: Main_PauseEmulation(); /* Hold things... */
1.1.1.2 root 496:
1.1.1.10 root 497: bInFullScreen = TRUE;
498: Screen_SetResolution();
1.1 root 499:
1.1.1.10 root 500: SDL_Delay(20); /* To give monitor time to change to new resolution */
501: Screen_ClearScreen(); /* Black out screen bitmap as will be invalid when return */
1.1.1.2 root 502:
1.1.1.10 root 503: Main_UnPauseEmulation(); /* And off we go... */
1.1.1.3 root 504:
1.1.1.4 root 505: SDL_WM_GrabInput(SDL_GRAB_ON); /* Grab mouse pointer in fullscreen */
1.1 root 506: }
507: }
508:
1.1.1.2 root 509:
1.1 root 510: /*-----------------------------------------------------------------------*/
511: /*
512: Return from Full screen mode back to a window
513: */
514: void Screen_ReturnFromFullScreen(void)
515: {
1.1.1.7 root 516: if (bInFullScreen)
517: {
1.1.1.2 root 518: Main_PauseEmulation(); /* Hold things... */
519:
520: bInFullScreen = FALSE;
1.1.1.10 root 521: Screen_SetResolution();
1.1.1.2 root 522:
1.1.1.10 root 523: SDL_Delay(20); /* To give monitor time to switch resolution */
1.1.1.2 root 524:
1.1.1.10 root 525: Main_UnPauseEmulation(); /* And off we go... */
1.1 root 526: }
527: }
528:
1.1.1.5 root 529:
1.1.1.2 root 530: /*-----------------------------------------------------------------------*/
1.1 root 531: /*
532: Have we changes beteen low/medium/high res?
533: */
534: void Screen_DidResolutionChange(void)
535: {
1.1.1.2 root 536: /* Did change res? */
1.1.1.5 root 537: if (STRes!=PrevSTRes)
538: {
1.1.1.10 root 539: /* Set new display mode, if differs from current */
540: Screen_SetResolution();
1.1 root 541: PrevSTRes = STRes;
542: }
543:
1.1.1.2 root 544: /* Did change overscan mode? Causes full update */
1.1.1.10 root 545: if (pFrameBuffer->OverscanModeCopy != OverscanMode)
1.1 root 546: pFrameBuffer->bFullUpdate = TRUE;
547: }
548:
1.1.1.2 root 549:
550: /*-----------------------------------------------------------------------*/
1.1 root 551: /*
552: Compare current resolution on line with previous, and set 'UpdateLine' accordingly
553: Also check if swap between low/medium resolution and return in 'bLowMedMix'
554: */
1.1.1.8 root 555: static void Screen_CompareResolution(int y, int *pUpdateLine, BOOL *pbLowMedMix)
1.1 root 556: {
557: int Resolution;
558:
1.1.1.2 root 559: /* Check if wrote to resolution register */
1.1.1.7 root 560: if (HBLPaletteMasks[y]&PALETTEMASK_RESOLUTION) /* See 'Intercept_ShifterMode_WriteByte' */
561: {
1.1 root 562: Resolution = (HBLPaletteMasks[y]>>16)&0x1;
1.1.1.2 root 563: /* Have used any low/medium res mix? */
1.1 root 564: if (Resolution!=(STRes&0x1))
565: *pbLowMedMix = TRUE;
1.1.1.2 root 566: /* Did change resolution */
1.1 root 567: if (Resolution!=(int)((pFrameBuffer->HBLPaletteMasks[y]>>16)&0x1))
568: *pUpdateLine |= PALETTEMASK_UPDATERES;
569: else
570: *pUpdateLine &= ~PALETTEMASK_UPDATERES;
571: }
572: }
573:
1.1.1.7 root 574:
1.1.1.2 root 575: /*-----------------------------------------------------------------------*/
1.1 root 576: /*
577: Check to see if palette changes cause screen update and keep 'HBLPalette[]' up-to-date
578: */
1.1.1.8 root 579: static void Screen_ComparePalette(int y, int *pUpdateLine)
1.1 root 580: {
581: BOOL bPaletteChanged = FALSE;
582: int i;
583:
1.1.1.2 root 584: /* Did write to palette in this or previous frame? */
1.1.1.7 root 585: if (((HBLPaletteMasks[y]|pFrameBuffer->HBLPaletteMasks[y])&PALETTEMASK_PALETTE)!=0)
586: {
1.1.1.2 root 587: /* Check and update ones which changed */
1.1.1.7 root 588: for(i=0; i<16; i++)
589: {
1.1.1.2 root 590: if (HBLPaletteMasks[y]&(1<<i)) /* Update changes in ST palette */
1.1 root 591: HBLPalette[i] = HBLPalettes[(y*16)+i];
592: }
1.1.1.2 root 593: /* Now check with same palette from previous frame for any differences(may be changing palette back) */
1.1.1.7 root 594: for(i=0; (i<16) && (!bPaletteChanged); i++)
595: {
1.1 root 596: if (HBLPalette[i]!=pFrameBuffer->HBLPalettes[(y*16)+i])
597: bPaletteChanged = TRUE;
598: }
599: if (bPaletteChanged)
600: *pUpdateLine |= PALETTEMASK_UPDATEPAL;
601: else
602: *pUpdateLine &= ~PALETTEMASK_UPDATEPAL;
603: }
604: }
605:
1.1.1.7 root 606:
1.1.1.2 root 607: /*-----------------------------------------------------------------------*/
1.1 root 608: /*
1.1.1.11! root 609: Check for differences in Palette and Resolution from Mask table and update
! 610: and store off which lines need updating and create full-screen palette.
! 611: (It is very important for these routines to check for colour changes with
! 612: the previous screen so only the very minimum parts are updated)
1.1 root 613: */
1.1.1.8 root 614: static int Screen_ComparePaletteMask(void)
1.1 root 615: {
616: BOOL bLowMedMix=FALSE;
617: int LineUpdate = 0;
618: int y;
619:
620: /* Set for monochrome? */
1.1.1.7 root 621: if (bUseHighRes)
622: {
1.1 root 623: OverscanMode = OVERSCANMODE_NONE;
624:
1.1.1.11! root 625: /* Just copy mono colours, 0x777 checked also in convert/vdi2.c */
! 626: if (HBLPalettes[0] & 0x777)
1.1.1.7 root 627: {
1.1.1.11! root 628: HBLPalettes[0] = 0x777;
! 629: HBLPalettes[1] = 0x000;
1.1 root 630: }
1.1.1.7 root 631: else
632: {
1.1.1.11! root 633: HBLPalettes[0] = 0x000;
! 634: HBLPalettes[1] = 0x777;
1.1 root 635: }
636:
1.1.1.7 root 637: /* Colors changed? */
638: if (HBLPalettes[0] != PrevHBLPalette[0])
639: pFrameBuffer->bFullUpdate = TRUE;
640:
1.1 root 641: /* Set bit to flag 'full update' */
642: if (pFrameBuffer->bFullUpdate)
643: ScrUpdateFlag = PALETTEMASK_UPDATEFULL;
644: else
645: ScrUpdateFlag = 0x00000000;
646: }
647:
648: /* Use VDI resolution? */
1.1.1.7 root 649: if (bUseVDIRes)
650: {
1.1 root 651: /* Force to VDI resolution screen, without overscan */
652: STRes = VDIRes;
653:
1.1.1.7 root 654: /* Colors changed? */
1.1 root 655: if (HBLPalettes[0]!=PrevHBLPalette[0])
656: pFrameBuffer->bFullUpdate = TRUE;
657:
658: /* Set bit to flag 'full update' */
659: if (pFrameBuffer->bFullUpdate)
660: ScrUpdateFlag = PALETTEMASK_UPDATEFULL;
661: else
662: ScrUpdateFlag = 0x00000000;
663: }
664: /* Are in Mono? Force to monochrome and no overscan */
1.1.1.7 root 665: else if (bUseHighRes)
666: {
1.1 root 667: /* Force to standard hi-resolution screen, without overscan */
668: STRes = ST_HIGH_RES;
669: }
1.1.1.7 root 670: else /* Full colour */
671: {
1.1 root 672: /* Get resolution */
673: STRes = (HBLPaletteMasks[0]>>16)&0x3;
674: /* Do all lines - first is tagged as full-update */
1.1.1.7 root 675: for(y=0; y<NUM_VISIBLE_LINES; y++)
676: {
1.1 root 677: /* Find any resolution/palette change and update palette/mask buffer */
678: /* ( LineUpdate has top two bits set to say if line needs updating due to palette or resolution change ) */
679: Screen_CompareResolution(y,&LineUpdate,&bLowMedMix);
680: Screen_ComparePalette(y,&LineUpdate);
681: HBLPaletteMasks[y] = (HBLPaletteMasks[y]&(~PALETTEMASK_UPDATEMASK)) | LineUpdate;
682: /* Copy palette and mask for next frame */
683: memcpy(&pFrameBuffer->HBLPalettes[y*16],HBLPalette,sizeof(short int)*16);
684: pFrameBuffer->HBLPaletteMasks[y] = HBLPaletteMasks[y];
685: }
686: /* Did mix resolution? */
687: if (bLowMedMix)
688: STRes = ST_LOWMEDIUM_MIX_RES;
689: }
690:
691: return(STRes);
692: }
693:
1.1.1.3 root 694:
1.1.1.2 root 695: /*-----------------------------------------------------------------------*/
1.1 root 696: /*
697: Update Palette Mask to show 'full-update' required. This is usually done after a resolution change
698: or when going between a Window and full-screen display
699: */
1.1.1.8 root 700: static void Screen_SetFullUpdateMask(void)
1.1 root 701: {
702: int y;
703:
704: for(y=0; y<NUM_VISIBLE_LINES; y++)
705: HBLPaletteMasks[y] |= PALETTEMASK_UPDATEFULL;
706: }
707:
1.1.1.7 root 708:
1.1.1.2 root 709: /*-----------------------------------------------------------------------*/
1.1 root 710: /*
1.1.1.10 root 711: Set details for ST screen conversion.
1.1 root 712: */
1.1.1.10 root 713: static void Screen_SetConvertDetails(void)
1.1 root 714: {
1.1.1.3 root 715: pSTScreen = pFrameBuffer->pSTScreen; /* Source in ST memory */
716: pSTScreenCopy = pFrameBuffer->pSTScreenCopy; /* Previous ST screen */
1.1.1.8 root 717: pPCScreenDest = sdlscrn->pixels; /* Destination PC screen */
1.1.1.3 root 718:
1.1.1.10 root 719: PCScreenBytesPerLine = sdlscrn->pitch; /* Bytes per line */
720: pHBLPalettes = pFrameBuffer->HBLPalettes; /* HBL palettes pointer */
721: bScrDoubleY = !ConfigureParams.Screen.bInterleavedScreen; /* non-interleaved? => double up on Y */
1.1.1.5 root 722:
1.1.1.10 root 723: if (bUseVDIRes)
1.1.1.5 root 724: {
1.1.1.10 root 725: /* Select screen draw for standard or VDI display */
726: STScreenLeftSkipBytes = 0;
727: STScreenWidthBytes = VDIWidth * VDIPlanes / 8;
728: STScreenStartHorizLine = 0;
729: STScreenEndHorizLine = VDIHeight;
1.1 root 730: }
731: else
732: {
1.1.1.10 root 733: if (ConfigureParams.Screen.bAllowOverscan) /* Use borders? */
1.1.1.5 root 734: {
1.1.1.10 root 735: /* Always draw to WHOLE screen including ALL borders */
736: STScreenLeftSkipBytes = 0; /* Number of bytes to skip on ST screen for left (border) */
737: STScreenStartHorizLine = 0; /* Full height */
738:
739: if (ConfigureParams.Screen.bUseHighRes)
740: {
741: pFrameBuffer->OverscanModeCopy = OverscanMode = OVERSCANMODE_NONE;
742: STScreenEndHorizLine = 400;
743: }
744: else
745: {
746: STScreenWidthBytes = SCREENBYTES_LINE; /* Number of horizontal bytes in our ST screen */
747: STScreenEndHorizLine = NUM_VISIBLE_LINES;
748: }
1.1.1.5 root 749: }
750: else
751: {
1.1.1.10 root 752: /* Only draw main area and centre on Y */
753: STScreenLeftSkipBytes = SCREENBYTES_LEFT;
754: STScreenWidthBytes = SCREENBYTES_MIDDLE;
755: STScreenStartHorizLine = OVERSCAN_TOP;
756: STScreenEndHorizLine = OVERSCAN_TOP + (bUseHighRes ? 400 : 200);
1.1 root 757: }
758: }
759: }
760:
1.1.1.2 root 761:
762: /*-----------------------------------------------------------------------*/
1.1 root 763: /*
764: Lock full-screen for drawing
765: */
1.1.1.8 root 766: static BOOL Screen_Lock(void)
1.1 root 767: {
1.1.1.7 root 768: if(SDL_MUSTLOCK(sdlscrn))
769: {
770: if(SDL_LockSurface(sdlscrn))
771: {
1.1.1.2 root 772: Screen_ReturnFromFullScreen(); /* All OK? If not need to jump back to a window */
1.1 root 773: return(FALSE);
774: }
775: }
1.1.1.2 root 776:
1.1 root 777: return(TRUE);
778: }
779:
1.1.1.2 root 780: /*-----------------------------------------------------------------------*/
1.1 root 781: /*
782: UnLock full-screen
783: */
1.1.1.8 root 784: static void Screen_UnLock(void)
1.1 root 785: {
1.1.1.2 root 786: if( SDL_MUSTLOCK(sdlscrn) )
1.1.1.3 root 787: SDL_UnlockSurface(sdlscrn);
1.1 root 788: }
789:
1.1.1.2 root 790:
791: /*-----------------------------------------------------------------------*/
1.1 root 792: /*
1.1.1.11! root 793: Swap ST Buffers, used for full-screen where have double-buffering
! 794: */
! 795: static void Screen_SwapSTBuffers(void)
! 796: {
! 797: #if NUM_FRAMEBUFFERS > 1
! 798: if (sdlscrn->flags & SDL_DOUBLEBUF)
! 799: {
! 800: if (pFrameBuffer==&FrameBuffers[0])
! 801: pFrameBuffer = &FrameBuffers[1];
! 802: else
! 803: pFrameBuffer = &FrameBuffers[0];
! 804: }
! 805: #endif
! 806: }
! 807:
! 808:
! 809: /*-----------------------------------------------------------------------*/
! 810: /*
1.1 root 811: Blit our converted ST screen to window/full-screen
1.1.1.3 root 812: Note that our source image includes all borders so if have them disabled simply blit a smaller source rectangle!
1.1 root 813: */
1.1.1.11! root 814: static void Screen_Blit(BOOL bSwapScreen)
1.1 root 815: {
1.1.1.3 root 816: /* Rectangle areas to Blit according to if overscan is enabled or not (source always includes all borders) */
1.1.1.7 root 817: /* static SDL_Rect SrcWindowBitmapSizes[] = */
818: /* { */
819: /* { OVERSCAN_LEFT,OVERSCAN_TOP, 320,200 }, /\* ST_LOW_RES *\/ */
820: /* { (OVERSCAN_LEFT<<1),(OVERSCAN_TOP<<1), 640,400 }, /\* ST_MEDIUM_RES *\/ */
821: /* { 0,0, 640,400 }, /\* ST_HIGH_RES *\/ */
822: /* { (OVERSCAN_LEFT<<1),(OVERSCAN_BOTTOM<<1), 640,400 }, /\* ST_LOWMEDIUM_MIX_RES *\/ */
823: /* }; */
824: static SDL_Rect SrcWindowBitmapSizes[] =
825: {
826: { 0,0, 320,200 }, /* ST_LOW_RES */
827: { 0,0, 640,400 }, /* ST_MEDIUM_RES */
1.1.1.5 root 828: { 0,0, 640,400 }, /* ST_HIGH_RES */
1.1.1.7 root 829: { 0,0, 640,400 }, /* ST_LOWMEDIUM_MIX_RES */
1.1 root 830: };
1.1.1.7 root 831:
832: static SDL_Rect SrcWindowOverscanBitmapSizes[] =
833: {
1.1.1.5 root 834: { 0,0, OVERSCAN_LEFT+320+OVERSCAN_RIGHT,OVERSCAN_TOP+200+OVERSCAN_BOTTOM },
835: { 0,0, (OVERSCAN_LEFT<<1)+640+(OVERSCAN_RIGHT<<1),(OVERSCAN_TOP<<1)+400+(OVERSCAN_BOTTOM<<1) },
836: { 0,0, 640,400 },
837: { 0,0, (OVERSCAN_LEFT<<1)+640+(OVERSCAN_RIGHT<<1),(OVERSCAN_TOP<<1)+400+(OVERSCAN_BOTTOM<<1) },
1.1 root 838: };
1.1.1.3 root 839:
1.1.1.10 root 840: unsigned char *pTmpScreen;
1.1.1.3 root 841: SDL_Rect *SrcRect;
1.1 root 842:
1.1.1.3 root 843: /* Blit to full screen or window? */
1.1.1.7 root 844: if (bInFullScreen)
845: {
1.1.1.11! root 846: Screen_SwapSTBuffers();
1.1.1.3 root 847: /* Swap screen */
1.1 root 848: if (bSwapScreen)
1.1.1.3 root 849: SDL_Flip(sdlscrn);
1.1 root 850: }
1.1.1.7 root 851: else
852: {
1.1.1.3 root 853: /* VDI resolution? */
1.1.1.7 root 854: if (bUseVDIRes || ConfigureParams.Screen.bUseHighRes)
855: {
1.1.1.3 root 856: /* Show VDI or mono resolution, no overscan */
857: SDL_UpdateRect(sdlscrn, 0,0,0,0);
1.1 root 858: }
1.1.1.7 root 859: else
860: {
861: /* Find rectangle to draw from... */
862: if (ConfigureParams.Screen.bAllowOverscan)
863: SrcRect = &SrcWindowOverscanBitmapSizes[STRes];
864: else
865: SrcRect = &SrcWindowBitmapSizes[STRes];
866:
867: /* Blit image */
868: SDL_UpdateRect(sdlscrn, 0,0,0,0);
1.1.1.10 root 869: //SDL_UpdateRects(sdlscrn, 1, SrcRect); /* FIXME */
1.1 root 870: }
871: }
872:
1.1.1.3 root 873: /* Swap copy/raster buffers in screen. */
1.1.1.10 root 874: pTmpScreen = pFrameBuffer->pSTScreenCopy;
1.1 root 875: pFrameBuffer->pSTScreenCopy = pFrameBuffer->pSTScreen;
1.1.1.10 root 876: pFrameBuffer->pSTScreen = pTmpScreen;
1.1 root 877: }
878:
1.1.1.3 root 879:
1.1.1.2 root 880: /*-----------------------------------------------------------------------*/
1.1 root 881: /*
882: Draw ST screen to window/full-screen framebuffer
883: */
1.1.1.11! root 884: static void Screen_DrawFrame(BOOL bForceFlip)
1.1 root 885: {
886: void *pDrawFunction;
887:
1.1.1.2 root 888: /* Scan palette/resolution masks for each line and build up palette/difference tables */
1.1 root 889: STRes = Screen_ComparePaletteMask();
1.1.1.2 root 890: /* Do require palette? Check if changed and update */
1.1 root 891: Screen_Handle8BitPalettes();
1.1.1.2 root 892: /* Did we change resolution this frame - allocate new screen if did so */
1.1 root 893: Screen_DidResolutionChange();
1.1.1.2 root 894: /* Is need full-update, tag as such */
1.1 root 895: if (pFrameBuffer->bFullUpdate)
896: Screen_SetFullUpdateMask();
897:
898: /* Lock screen ready for drawing */
1.1.1.10 root 899: if (Screen_Lock())
900: {
901: bScreenContentsChanged = FALSE; /* Did change (ie needs blit?) */
1.1.1.7 root 902: /* Set details */
1.1.1.10 root 903: Screen_SetConvertDetails();
1.1.1.9 root 904: /* Clear screen on full update to clear out borders and also interleaved lines */
1.1.1.7 root 905: if (pFrameBuffer->bFullUpdate && !bUseVDIRes)
906: Screen_ClearScreen();
907: /* Call drawing for full-screen */
1.1.1.10 root 908: if (bUseVDIRes)
909: {
910: pDrawFunction = ScreenDrawFunctionsVDI[VDIRes];
1.1 root 911: }
1.1.1.10 root 912: else
913: {
914: pDrawFunction = ScreenDrawFunctionsNormal[STRes];
1.1.1.7 root 915: /* Check if is Spec512 image */
1.1.1.10 root 916: if (Spec512_IsImage())
917: {
918: /* What mode were we in? Keep to 320xH or 640xH */
919: if (pDrawFunction==ConvertLowRes_320x16Bit)
920: pDrawFunction = ConvertSpec512_320x16Bit;
921: else if (pDrawFunction==ConvertLowRes_640x16Bit)
922: pDrawFunction = ConvertSpec512_640x16Bit;
1.1 root 923: }
924: }
925:
1.1.1.7 root 926: if (pDrawFunction)
927: CALL_VAR(pDrawFunction)
928:
1.1.1.2 root 929: /* Unlock screen */
1.1 root 930: Screen_UnLock();
1.1.1.2 root 931: /* Clear flags, remember type of overscan as if change need screen full update */
1.1 root 932: pFrameBuffer->bFullUpdate = FALSE;
933: pFrameBuffer->OverscanModeCopy = OverscanMode;
934:
1.1.1.2 root 935: /* And show to user */
1.1.1.10 root 936: if (bScreenContentsChanged || bForceFlip)
1.1 root 937: Screen_Blit(TRUE);
938:
1.1.1.3 root 939: /* Grab any animation */
940: if(bRecordingAnimation)
1.1 root 941: ScreenSnapShot_RecordFrame(bScreenContentsChanged);
942: }
943: }
944:
1.1.1.9 root 945:
1.1.1.2 root 946: /*-----------------------------------------------------------------------*/
1.1 root 947: /*
948: Draw ST screen to window/full-screen
949: */
950: void Screen_Draw(void)
951: {
1.1.1.5 root 952: if (!bQuitProgram)
953: {
954: if(VideoBase)
1.1 root 955: {
956: /* And draw(if screen contents changed) */
957: Screen_DrawFrame(FALSE);
958:
959: /* And status bar */
1.1.1.4 root 960: /*StatusBar_UpdateIcons();*/ /* Sorry - no statusbar in Hatari yet */
1.1 root 961: }
962:
1.1.1.2 root 963: /* Check printer status */
1.1 root 964: Printer_CheckIdleStatus();
965: }
966: }
1.1.1.11! root 967:
! 968:
! 969: /* -------------- screen conversion routines --------------------------------
! 970: Screen conversion routines. We have a number of routines to convert ST screen
! 971: to PC format. We split these into Low, Medium and High each with 8/16-bit
! 972: versions. To gain extra speed, as almost half of the processing time can be
! 973: spent in these routines, we check for any changes from the previously
! 974: displayed frame. AdjustLinePaletteRemap() sets a flag to tell the routines
! 975: if we need to totally update a line (ie full update, or palette/res change)
! 976: or if we just can do a difference check.
! 977: We convert each screen 16 pixels at a time by use of a couple of look-up
! 978: tables. These tables convert from 2-plane format to bbp and then we can add
! 979: two of these together to get 4-planes. This keeps the tables small and thus
! 980: improves speed. We then look these bbp values up as an RGB/Index value to
! 981: copy to the screen.
! 982: */
! 983:
! 984:
! 985: /*-----------------------------------------------------------------------
! 986: * Update the STRGBPalette[] array with current colours for this raster line.
! 987: *
! 988: * Return 'ScrUpdateFlag', 0x80000000=Full update, 0x40000000=Update
! 989: * as palette changed
! 990: */
! 991: static int AdjustLinePaletteRemap(int y)
! 992: {
! 993: #if SDL_BYTEORDER == SDL_BIG_ENDIAN
! 994: static const int endiantable[16] = {0,2,1,3,8,10,9,11,4,6,5,7,12,14,13,15};
! 995: #endif
! 996: Uint16 *actHBLPal;
! 997: int i;
! 998:
! 999: /* Copy palette and convert to RGB in display format */
! 1000: actHBLPal = pHBLPalettes + (y<<4); /* offset in palette */
! 1001: for(i=0; i<16; i++)
! 1002: {
! 1003: #if SDL_BYTEORDER == SDL_BIG_ENDIAN
! 1004: STRGBPalette[endiantable[i]] = ST2RGB[*actHBLPal++];
! 1005: #else
! 1006: STRGBPalette[i] = ST2RGB[*actHBLPal++];
! 1007: #endif
! 1008: }
! 1009: ScrUpdateFlag = HBLPaletteMasks[y];
! 1010: return ScrUpdateFlag;
! 1011: }
! 1012:
! 1013:
! 1014: /*-----------------------------------------------------------------------
! 1015: * Run updates to palette(STRGBPalette[]) until get to screen line
! 1016: * we are to convert from
! 1017: */
! 1018: static void Convert_StartFrame(void)
! 1019: {
! 1020: int y = 0;
! 1021: /* Get #lines before conversion starts */
! 1022: int lines = STScreenStartHorizLine;
! 1023: while (lines--)
! 1024: AdjustLinePaletteRemap(y++); /* Update palette */
! 1025: }
! 1026:
! 1027: /* lookup tables and conversion macros */
! 1028: #include "convert/macros.h"
! 1029:
! 1030: /* Conversion routines */
! 1031: #include "convert/low320x16.c" /* LowRes To 320xH x 16-bit colour */
! 1032: #include "convert/low640x16.c" /* LowRes To 640xH x 16-bit colour */
! 1033: #include "convert/med640x16.c" /* MediumRes To 640xH x 16-bit colour */
! 1034: #include "convert/low320x8.c" /* LowRes To 320xH x 8-bit colour */
! 1035: #include "convert/low640x8.c" /* LowRes To 640xH x 8-bit colour */
! 1036: #include "convert/med640x8.c" /* MediumRes To 640xH x 8-bit colour */
! 1037: #include "convert/high640x8.c" /* HighRes To 640xH x 8-bit colour */
! 1038: #include "convert/high640x1.c" /* HighRes To 640xH x 1-bit colour */
! 1039: #include "convert/spec320x16.c" /* Spectrum 512 To 320xH x 16-bit colour */
! 1040: #include "convert/spec640x16.c" /* Spectrum 512 To 640xH x 16-bit colour */
! 1041:
! 1042: #include "convert/vdi16.c" /* VDI x 16 colour */
! 1043: #include "convert/vdi4.c" /* VDI x 4 colour */
! 1044: #include "convert/vdi2.c" /* VDI x 2 colour */
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.