|
|
1.1 root 1: /*
2: Hatari - screen.c
3:
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: (SC) Simon Schubiger - most of it rewritten for Previous NeXT emulator
8: */
9:
10: #include <SDL.h>
11: #include <SDL_endian.h>
12: #include <SDL_blendmode.h>
13:
14: const char Screen_fileid[] = "Previous fast_screen.c : " __DATE__ " " __TIME__;
15:
16: #include "main.h"
17: #include "configuration.h"
18: #include "log.h"
19: #include "m68000.h"
20: #include "dimension.hpp"
21: #include "nd_mem.hpp"
22: #include "paths.h"
23: #include "screen.h"
24: #include "statusbar.h"
25: #include "video.h"
26:
27: SDL_Window* sdlWindow;
28: SDL_Surface* sdlscrn = NULL; /* The SDL screen surface */
29: int nScreenZoomX, nScreenZoomY; /* Zooming factors, used for scaling mouse motions */
30:
31: /* extern for shortcuts */
32: volatile bool bGrabMouse = false; /* Grab the mouse cursor in the window */
33: volatile bool bInFullScreen = false; /* true if in full screen */
34:
35: static const int NeXT_SCRN_WIDTH = 1120;
36: static const int NeXT_SCRN_HEIGHT = 832;
37:
38: static SDL_Thread* repaintThread;
39: static SDL_Renderer* sdlRenderer;
40: static SDL_sem* initLatch;
41: static SDL_atomic_t blitFB;
42: static SDL_atomic_t blitUI; /* When value == 1, the repaint thread will blit the sldscrn surface to the screen on the next redraw */
43: static bool doUIblit;
44: static SDL_Rect saveWindowBounds; /* Window bounds before going fullscreen. Used to restore window size & position. */
45: static void* uiBuffer; /* uiBuffer used for ui texture */
46: static void* uiBufferTmp; /* Temporary uiBuffer used by repainter */
47: static SDL_SpinLock uiBufferLock; /* Lock for concurrent access to UI buffer between m68k thread and repainter */
48: static Uint32 mask; /* green screen mask for transparent UI areas */
49: static volatile bool doRepaint = true; /* Repaint thread runs while true */
50: static SDL_Rect statusBar;
51:
52:
53: static Uint32 BW2RGB[0x400];
54: static Uint32 COL2RGB[0x10000];
55:
56: static Uint32 bw2rgb(SDL_PixelFormat* format, int bw) {
57: switch(bw & 3) {
58: case 3: return SDL_MapRGB(format, 0, 0, 0);
59: case 2: return SDL_MapRGB(format, 85, 85, 85);
60: case 1: return SDL_MapRGB(format, 170, 170, 170);
61: case 0: return SDL_MapRGB(format, 255, 255, 255);
62: default: return 0;
63: }
64: }
65:
66: static Uint32 col2rgb(SDL_PixelFormat* format, int col) {
67: int r = col & 0xF000; r >>= 12; r |= r << 4;
68: int g = col & 0x0F00; g >>= 8; g |= g << 4;
69: int b = col & 0x00F0; b >>= 4; b |= b << 4;
70: return SDL_MapRGB(format, r, g, b);
71: }
72:
73: /*
74: BW format is 2bit per pixel
75: */
76: static void blitBW(SDL_Texture* tex) {
77: void* pixels;
78: int d;
79: int pitch = (NeXT_SCRN_WIDTH + (ConfigureParams.System.bTurbo ? 0 : 32)) / 4;
80: SDL_LockTexture(tex, NULL, &pixels, &d);
81: Uint32* dst = (Uint32*)pixels;
82: for(int y = 0; y < NeXT_SCRN_HEIGHT; y++) {
83: int src = y * pitch;
84: for(int x = 0; x < NeXT_SCRN_WIDTH/4; x++, src++) {
85: int idx = NEXTVideo[src] * 4;
86: *dst++ = BW2RGB[idx+0];
87: *dst++ = BW2RGB[idx+1];
88: *dst++ = BW2RGB[idx+2];
89: *dst++ = BW2RGB[idx+3];
90: }
91: }
92: SDL_UnlockTexture(tex);
93: }
94:
95: /*
96: Color format is 4bit per pixel, big-endian: RGBx
97: */
98: static void blitColor(SDL_Texture* tex) {
99: void* pixels;
100: int d;
101: int pitch = NeXT_SCRN_WIDTH + (ConfigureParams.System.bTurbo ? 0 : 32);
102: SDL_LockTexture(tex, NULL, &pixels, &d);
103: Uint32* dst = (Uint32*)pixels;
104: for(int y = 0; y < NeXT_SCRN_HEIGHT; y++) {
105: Uint16* src = (Uint16*)NEXTColorVideo + (y*pitch);
106: for(int x = 0; x < NeXT_SCRN_WIDTH; x++) {
107: *dst++ = COL2RGB[*src++];
108: }
109: }
110: SDL_UnlockTexture(tex);
111: }
112:
113: /*
114: Dimension format is 8bit per pixel, big-endian: RRGGBBAA
115: */
116: void blitDimension(Uint32* vram, SDL_Texture* tex) {
117: #if ND_STEP
118: Uint32* src = &vram[0];
119: #else
120: Uint32* src = &vram[16];
121: #endif
122: void* pixels;
123: int d;
124: Uint32 format;
125: SDL_QueryTexture(tex, &format, &d, &d, &d);
126: SDL_LockTexture(tex, NULL, &pixels, &d);
127: Uint32* dst = (Uint32*)pixels;
128: if(SDL_BYTEORDER == SDL_BIG_ENDIAN) {
129: /* Add big-endian accelerated blit loops as needed here */
130: switch (format) {
131: default: {
132: /* fallback to SDL_MapRGB */
133: SDL_PixelFormat* pformat = SDL_AllocFormat(format);
134: for(int y = NeXT_SCRN_HEIGHT; --y >= 0;) {
135: for(int x = NeXT_SCRN_WIDTH; --x >= 0;) {
136: Uint32 v = *src++;
137: *dst++ = SDL_MapRGB(pformat, (v >> 24) & 0xFF, (v>>16) & 0xFF, (v>>8) & 0xFF);
138: }
139: src += 32;
140: }
141: SDL_FreeFormat(pformat);
142: break;
143: }
144: }
145: } else {
146: /* Add little-endian accelerated blit loops as needed here */
147: switch (format) {
148: case SDL_PIXELFORMAT_ARGB8888:
149: for(int y = NeXT_SCRN_HEIGHT; --y >= 0;) {
150: for(int x = NeXT_SCRN_WIDTH; --x >= 0;) {
151: // Uint32 LE: AABBGGRR
152: // Target: AARRGGBB
153: Uint32 v = *src++;
154: *dst++ = (v & 0xFF000000) | ((v<<16) &0x00FF0000) | (v &0x0000FF00) | ((v>>16) &0x000000FF);
155: }
156: src += 32;
157: }
158: break;
159: default: {
160: /* fallback to SDL_MapRGB */
161: SDL_PixelFormat* pformat = SDL_AllocFormat(format);
162: for(int y = NeXT_SCRN_HEIGHT; --y >= 0;) {
163: for(int x = NeXT_SCRN_WIDTH; --x >= 0;) {
164: Uint32 v = SDL_Swap32(*src++);
165: *dst++ = SDL_MapRGB(pformat, (v >> 24) & 0xFF, (v>>16) & 0xFF, (v>>8) & 0xFF);
166: }
167: src += 32;
168: }
169: SDL_FreeFormat(pformat);
170: break;
171: }
172: }
173: }
174: SDL_UnlockTexture(tex);
175: }
176:
177: /*
178: Blit NeXT framebuffer to texture.
179: */
180: static void blitScreen(SDL_Texture* tex) {
181: if (ConfigureParams.Screen.nMonitorType==MONITOR_TYPE_DIMENSION) {
182: Uint32* vram = nd_vram_for_slot(ND_SLOT(ConfigureParams.Screen.nMonitorNum));
183: if(vram)
184: blitDimension(vram, tex);
185: return;
186: }
187: if(ConfigureParams.System.bColor) {
188: blitColor(tex);
189: } else {
190: blitBW(tex);
191: }
192: }
193:
194: /*
195: Initializes SDL graphics and then enters repaint loop.
196: Loop: Blits the NeXT framebuffer to the fbTexture, blends with the GUI surface and
197: shows it.
198: */
199: static int repainter(void* unused) {
200: int width;
201: int height;
202:
203: SDL_SetThreadPriority(SDL_THREAD_PRIORITY_NORMAL);
204: SDL_GetWindowSize(sdlWindow, &width, &height);
205:
206: statusBar.x = 0;
207: statusBar.y = NeXT_SCRN_HEIGHT;
208: statusBar.w = width;
209: statusBar.h = height - NeXT_SCRN_HEIGHT;
210:
211: SDL_Texture* uiTexture;
212: SDL_Texture* fbTexture;
213:
214: Uint32 r, g, b, a;
215:
216: sdlRenderer = SDL_CreateRenderer(sdlWindow, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
217: SDL_RenderSetLogicalSize(sdlRenderer, width, height);
218:
219: uiTexture = SDL_CreateTexture(sdlRenderer, SDL_PIXELFORMAT_UNKNOWN, SDL_TEXTUREACCESS_STREAMING, width, height);
220: SDL_SetTextureBlendMode(uiTexture, SDL_BLENDMODE_BLEND);
221:
222: fbTexture = SDL_CreateTexture(sdlRenderer, SDL_PIXELFORMAT_UNKNOWN, SDL_TEXTUREACCESS_STREAMING, width, height);
223: SDL_SetTextureBlendMode(fbTexture, SDL_BLENDMODE_NONE);
224:
225: Uint32 format;
226: int d;
227: SDL_QueryTexture(uiTexture, &format, &d, &d, &d);
228: SDL_PixelFormatEnumToMasks(format, &d, &r, &g, &b, &a);
229: mask = g | a;
230: sdlscrn = SDL_CreateRGBSurface(SDL_SWSURFACE, width, height, 32, r, g, b, a);
231: uiBuffer = malloc(sdlscrn->h * sdlscrn->pitch);
232: uiBufferTmp = malloc(sdlscrn->h * sdlscrn->pitch);
233: // clear UI with mask
234: SDL_FillRect(sdlscrn, NULL, mask);
235:
236: /* Exit if we can not open a screen */
237: if (!sdlscrn) {
238: fprintf(stderr, "Could not set video mode:\n %s\n", SDL_GetError() );
239: SDL_Quit();
240: exit(-2);
241: }
242:
243: Statusbar_Init(sdlscrn);
244:
245: if (bGrabMouse) {
246: SDL_SetRelativeMouseMode(SDL_TRUE);
247: SDL_SetWindowGrab(sdlWindow, SDL_TRUE);
248: }
249:
250: /* Configure some SDL stuff: */
251: SDL_ShowCursor(SDL_DISABLE);
252:
253: /* Setup lookup tables */
254: SDL_PixelFormat* pformat = SDL_AllocFormat(format);
255: /* initialize BW lookup table */
256: for(int i = 0; i < 0x100; i++) {
257: BW2RGB[i*4+0] = bw2rgb(pformat, i>>6);
258: BW2RGB[i*4+1] = bw2rgb(pformat, i>>4);
259: BW2RGB[i*4+2] = bw2rgb(pformat, i>>2);
260: BW2RGB[i*4+3] = bw2rgb(pformat, i>>0);
261: }
262: /* initialize color lookup table */
263: for(int i = 0; i < 0x10000; i++)
264: COL2RGB[SDL_BYTEORDER == SDL_BIG_ENDIAN ? i : SDL_Swap16(i)] = col2rgb(pformat, i);
265:
266: /* Initialization done -> signal */
267: SDL_SemPost(initLatch);
268:
269: /* Start with framebuffer blit enabled */
270: SDL_AtomicSet(&blitFB, 1);
271:
272: /* Enter repaint loop */
273: while(doRepaint) {
274: bool updateFB = false;
275: bool updateUI = false;
276:
277: if (SDL_AtomicGet(&blitFB)) {
278: // Blit the NeXT framebuffer to textrue
279: blitScreen(fbTexture);
280: updateFB = true;
281: }
282:
283: // Copy UI surface to texture
284: SDL_AtomicLock(&uiBufferLock);
285: if(SDL_AtomicSet(&blitUI, 0)) {
286: // update full UI texture
287: memcpy(uiBufferTmp, uiBuffer, sdlscrn->h * sdlscrn->pitch);
288: updateUI = true;
289: }
290: SDL_AtomicUnlock(&uiBufferLock);
291:
292: if(updateUI) {
293: SDL_UpdateTexture(uiTexture, NULL, uiBufferTmp, sdlscrn->pitch);
294: }
295:
296: // Update and render UI texture
297: if (updateFB || updateUI) {
298: SDL_RenderClear(sdlRenderer);
299: // Render NeXT framebuffer texture
300: SDL_RenderCopy(sdlRenderer, fbTexture, NULL, NULL);
301: SDL_RenderCopy(sdlRenderer, uiTexture, NULL, NULL);
302: // SDL_RenderPresent sleeps until next VSYNC because of SDL_RENDERER_PRESENTVSYNC in ScreenInit
303: SDL_RenderPresent(sdlRenderer);
304: } else {
305: host_sleep_ms(10);
306: }
307: }
308: return 0;
309: }
310:
311: /*-----------------------------------------------------------------------*/
312: /**
313: * Pause Screen, pauses or resumes drawing of NeXT framebuffer
314: */
315: void Screen_Pause(bool pause) {
316: if (pause) {
317: SDL_AtomicSet(&blitFB, 0);
318: } else {
319: SDL_AtomicSet(&blitFB, 1);
320: }
321: }
322:
323: /*-----------------------------------------------------------------------*/
324: /**
325: * Init Screen, creates window and starts repaint thread
326: */
327: void Screen_Init(void) {
328: /* Set initial window resolution */
329: bInFullScreen = ConfigureParams.Screen.bFullScreen;
330: nScreenZoomX = 1;
331: nScreenZoomY = 1;
332:
333: int width = NeXT_SCRN_WIDTH;
334: int height = NeXT_SCRN_HEIGHT;
335:
336: /* Statusbar height */
337: height += Statusbar_SetHeight(width, height);
338:
339: /* Set new video mode */
340: SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear");
341:
342: fprintf(stderr, "SDL screen request: %d x %d (%s)\n", width, height, bInFullScreen ? "fullscreen" : "windowed");
343:
344: int x = SDL_WINDOWPOS_UNDEFINED;
345: if(ConfigureParams.Screen.nMonitorType == MONITOR_TYPE_DUAL) {
346: for(int i = 0; i < SDL_GetNumVideoDisplays(); i++) {
347: SDL_Rect r;
348: SDL_GetDisplayBounds(i, &r);
349: if(r.w >= width * 2) {
350: x = r.x + width + ((r.w - width * 2) / 2);
351: break;
352: }
353: if(r.x >= 0 && SDL_GetNumVideoDisplays() == 1) x = r.x + 8;
354: }
355: }
356: sdlWindow = SDL_CreateWindow(PROG_NAME, x, SDL_WINDOWPOS_UNDEFINED, width, height, 0);
357: if (!sdlWindow) {
358: fprintf(stderr,"Failed to create window: %s!\n", SDL_GetError());
359: exit(-1);
360: }
361:
362: initLatch = SDL_CreateSemaphore(0);
363: repaintThread = SDL_CreateThread(repainter, "[Previous] screen repaint", NULL);
364: SDL_SemWait(initLatch);
365: }
366:
367: void nd_sdl_destroy(void);
368:
369: /*-----------------------------------------------------------------------*/
370: /**
371: * Free screen bitmap and allocated resources
372: */
373: void Screen_UnInit(void) {
374: doRepaint = false; // stop repaint thread
375: int s;
376: SDL_WaitThread(repaintThread, &s);
377: nd_sdl_destroy();
378: }
379:
380: /*-----------------------------------------------------------------------*/
381: /**
382: * Enter Full screen mode
383: */
384: void Screen_EnterFullScreen(void) {
385: bool bWasRunning;
386:
387: if (!bInFullScreen) {
388: /* Hold things... */
389: bWasRunning = Main_PauseEmulation(false);
390: bInFullScreen = true;
391:
392: SDL_GetWindowPosition(sdlWindow, &saveWindowBounds.x, &saveWindowBounds.y);
393: SDL_GetWindowSize(sdlWindow, &saveWindowBounds.w, &saveWindowBounds.h);
394: SDL_SetWindowFullscreen(sdlWindow, SDL_WINDOW_FULLSCREEN_DESKTOP);
395: SDL_Delay(20); /* To give monitor time to change to new resolution */
396:
397: if (bWasRunning) {
398: /* And off we go... */
399: Main_UnPauseEmulation();
400: }
401: SDL_SetRelativeMouseMode(SDL_TRUE);
402: SDL_SetWindowGrab(sdlWindow, SDL_TRUE);
403: }
404: }
405:
406: /*-----------------------------------------------------------------------*/
407: /**
408: * Return from Full screen mode back to a window
409: */
410: void Screen_ReturnFromFullScreen(void) {
411: bool bWasRunning;
412:
413: if (bInFullScreen) {
414: /* Hold things... */
415: bWasRunning = Main_PauseEmulation(false);
416: bInFullScreen = false;
417:
418: SDL_SetWindowFullscreen(sdlWindow, 0);
419: SDL_Delay(20); /* To give monitor time to switch resolution */
420: SDL_SetWindowPosition(sdlWindow, saveWindowBounds.x, saveWindowBounds.y);
421: SDL_SetWindowSize(sdlWindow, saveWindowBounds.w, saveWindowBounds.h);
422:
423: if (bWasRunning) {
424: /* And off we go... */
425: Main_UnPauseEmulation();
426: }
427:
428: if (!bGrabMouse) {
429: /* Un-grab mouse pointer in windowed mode */
430: SDL_SetRelativeMouseMode(SDL_FALSE);
431: SDL_SetWindowGrab(sdlWindow, SDL_FALSE);
432: }
433: }
434: }
435:
436: /*-----------------------------------------------------------------------*/
437: /**
438: * Force things associated with changing between fullscreen/windowed
439: */
440: void Screen_ModeChanged(void) {
441: if (!sdlscrn) {
442: /* screen not yet initialized */
443: return;
444: }
445: if (bInFullScreen || bGrabMouse) {
446: SDL_SetRelativeMouseMode(SDL_TRUE);
447: SDL_SetWindowGrab(sdlWindow, SDL_TRUE);
448: } else {
449: SDL_SetRelativeMouseMode(SDL_FALSE);
450: SDL_SetWindowGrab(sdlWindow, SDL_FALSE);
451: }
452: }
453:
454:
455: /*-----------------------------------------------------------------------*/
456: /**
457: * Draw screen to window/full-screen - (SC) Just status bar updates. Screen redraw is done in repaint thread.
458: */
459:
460: static bool shieldStatusBarUpdate;
461:
462: static void statusBarUpdate(void) {
463: if(shieldStatusBarUpdate) return;
464: SDL_LockSurface(sdlscrn);
465: SDL_AtomicLock(&uiBufferLock);
466: memcpy(&((Uint8*)uiBuffer)[statusBar.y*sdlscrn->pitch], &((Uint8*)sdlscrn->pixels)[statusBar.y*sdlscrn->pitch], statusBar.h * sdlscrn->pitch);
467: SDL_AtomicSet(&blitUI, 1);
468: SDL_AtomicUnlock(&uiBufferLock);
469: SDL_UnlockSurface(sdlscrn);
470: }
471:
472: bool Update_StatusBar(void) {
473: shieldStatusBarUpdate = true;
474: Statusbar_OverlayBackup(sdlscrn);
475: Statusbar_Update(sdlscrn);
476: shieldStatusBarUpdate = false;
477:
478: statusBarUpdate();
479:
480: return !bQuitProgram;
481: }
482:
483: /*
484: Copy UI SDL surface to uiBuffer and replace mask pixels with transparent pixels for
485: UI blending with framebuffer texture.
486: */
487: static void uiUpdate(void) {
488: SDL_LockSurface(sdlscrn);
489: int count = sdlscrn->w * sdlscrn->h;
490: Uint32* dst = (Uint32*)uiBuffer;
491: Uint32* src = (Uint32*)sdlscrn->pixels;
492: SDL_AtomicLock(&uiBufferLock);
493: // poor man's green-screen - would be nice if SDL had more blending modes...
494: for(int i = count; --i >= 0; src++)
495: *dst++ = *src == mask ? 0 : *src;
496: SDL_AtomicSet(&blitUI, 1);
497: SDL_AtomicUnlock(&uiBufferLock);
498: SDL_UnlockSurface(sdlscrn);
499: }
500:
501: void SDL_UpdateRects(SDL_Surface *screen, int numrects, SDL_Rect *rects) {
502: while(numrects--) {
503: if(rects->y < NeXT_SCRN_HEIGHT) {
504: uiUpdate();
505: doUIblit = true;
506: } else {
507: if(doUIblit) {
508: uiUpdate();
509: doUIblit = false;
510: } else {
511: statusBarUpdate();
512: }
513: }
514: }
515: }
516:
517: void SDL_UpdateRect(SDL_Surface *screen, Sint32 x, Sint32 y, Sint32 w, Sint32 h) {
518: SDL_Rect rect = { x, y, w, h };
519: SDL_UpdateRects(screen, 1, &rect);
520: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.