1 |
/* |
2 |
* PearPC |
3 |
* sysdisplay.cc - screen access functions for SDL |
4 |
* |
5 |
* Copyright (C) 2004 Jens v.d. Heydt (mailme@vdh-webservice.de) |
6 |
* Copyright (C) 2004 John Kelley (pearpc@kelley.ca) |
7 |
* Copyright (C) 1999-2002 Stefan Weyergraf |
8 |
* Copyright (C) 1999-2004 Sebastian Biallas (sb@biallas.net) |
9 |
* |
10 |
* This program is free software; you can redistribute it and/or modify |
11 |
* it under the terms of the GNU General Public License version 2 as |
12 |
* published by the Free Software Foundation. |
13 |
* |
14 |
* This program is distributed in the hope that it will be useful, |
15 |
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
16 |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
17 |
* GNU General Public License for more details. |
18 |
* |
19 |
* You should have received a copy of the GNU General Public License |
20 |
* along with this program; if not, write to the Free Software |
21 |
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. |
22 |
*/ |
23 |
|
24 |
#include <cstdio> |
25 |
#include <cstdlib> |
26 |
#include <cstring> |
27 |
#include <unistd.h> |
28 |
|
29 |
#include <SDL.h> |
30 |
#include <SDL_thread.h> |
31 |
|
32 |
#ifdef __WIN32__ |
33 |
// We need ChangeDisplaySettings |
34 |
#define WIN32_LEAN_AND_MEAN |
35 |
#include <windows.h> |
36 |
#undef FASTCALL |
37 |
#endif |
38 |
|
39 |
|
40 |
#include "system/display.h" |
41 |
#include "system/sysexcept.h" |
42 |
#include "system/systhread.h" |
43 |
#include "system/sysvaccel.h" |
44 |
#include "system/types.h" |
45 |
|
46 |
#include "tools/data.h" |
47 |
#include "tools/debug.h" |
48 |
#include "tools/snprintf.h" |
49 |
|
50 |
//#include "io/graphic/gcard.h" |
51 |
#include "configparser.h" |
52 |
|
53 |
//#define DPRINTF(a...) |
54 |
#define DPRINTF(a...) ht_printf("[Display/SDL]: "a) |
55 |
|
56 |
#include "syssdl.h" |
57 |
|
58 |
|
59 |
uint SDLSystemDisplay::bitsPerPixelToXBitmapPad(uint bitsPerPixel) |
60 |
{ |
61 |
if (bitsPerPixel <= 8) { |
62 |
return 8; |
63 |
} else if (bitsPerPixel <= 16) { |
64 |
return 16; |
65 |
} else { |
66 |
return 32; |
67 |
} |
68 |
} |
69 |
|
70 |
#define MASK(shift, size) (((1 << (size))-1)<<(shift)) |
71 |
|
72 |
void SDLSystemDisplay::dumpDisplayChar(const DisplayCharacteristics &chr) |
73 |
{ |
74 |
fprintf(stderr, "\tdimensions: %d x %d pixels\n", chr.width, chr.height); |
75 |
fprintf(stderr, "\tpixel size in bytes: %d\n", chr.bytesPerPixel); |
76 |
fprintf(stderr, "\tpixel size in bits: %d\n", chr.bytesPerPixel*8); |
77 |
fprintf(stderr, "\tred_mask: %08x (%d bits)\n", MASK(chr.redShift, chr.redSize), chr.redSize); |
78 |
fprintf(stderr, "\tgreen_mask: %08x (%d bits)\n", MASK(chr.greenShift, chr.greenSize), chr.greenSize); |
79 |
fprintf(stderr, "\tblue_mask: %08x (%d bits)\n", MASK(chr.blueShift, chr.blueSize), chr.blueSize); |
80 |
fprintf(stderr, "\tdepth: %d\n", chr.redSize + chr.greenSize + chr.blueSize); |
81 |
} |
82 |
|
83 |
SDLSystemDisplay::SDLSystemDisplay(const char *title, const DisplayCharacteristics &chr, int redraw_ms) |
84 |
: SystemDisplay(chr, redraw_ms) |
85 |
{ |
86 |
mTitle = strdup(title); |
87 |
|
88 |
gFrameBuffer = (byte*)malloc(mClientChar.width * |
89 |
mClientChar.height * mClientChar.bytesPerPixel); |
90 |
memset(gFrameBuffer, 0, mClientChar.width * |
91 |
mClientChar.height * mClientChar.bytesPerPixel); |
92 |
damageFrameBufferAll(); |
93 |
|
94 |
gSDLScreen = NULL; |
95 |
mSDLFrameBuffer = NULL; |
96 |
mChangingScreen = false; |
97 |
|
98 |
sys_create_mutex(&mRedrawMutex); |
99 |
} |
100 |
|
101 |
void SDLSystemDisplay::finishMenu() |
102 |
{ |
103 |
} |
104 |
|
105 |
void SDLSystemDisplay::updateTitle() |
106 |
{ |
107 |
String key; |
108 |
int key_toggle_mouse_grab = gKeyboard->getKeyConfig().key_toggle_mouse_grab; |
109 |
SystemKeyboard::convertKeycodeToString(key, key_toggle_mouse_grab); |
110 |
String curTitle; |
111 |
curTitle.assignFormat("%s - [%s %s mouse]", mTitle, key.contentChar(), (isMouseGrabbed() ? "disables" : "enables")); |
112 |
SDL_WM_SetCaption(curTitle.contentChar(), NULL); |
113 |
} |
114 |
|
115 |
int SDLSystemDisplay::toString(char *buf, int buflen) const |
116 |
{ |
117 |
return snprintf(buf, buflen, "SDL"); |
118 |
} |
119 |
|
120 |
void SDLSystemDisplay::displayShow() |
121 |
{ |
122 |
if (!isExposed()) return; |
123 |
|
124 |
int firstDamagedLine, lastDamagedLine; |
125 |
// We've got problems with races here because gcard_write1/2/4 |
126 |
// might set gDamageAreaFirstAddr, gDamageAreaLastAddr. |
127 |
// We can't use mutexes in gcard for speed reasons. So we'll |
128 |
// try to minimize the probability of loosing the race. |
129 |
if (gDamageAreaFirstAddr > gDamageAreaLastAddr+3) { |
130 |
return; |
131 |
} |
132 |
int damageAreaFirstAddr = gDamageAreaFirstAddr; |
133 |
int damageAreaLastAddr = gDamageAreaLastAddr; |
134 |
healFrameBuffer(); |
135 |
// end of race |
136 |
damageAreaLastAddr += 3; // this is a hack. For speed reasons we |
137 |
// inaccurately set gDamageAreaLastAddr |
138 |
// to the first (not last) byte accessed |
139 |
// accesses are up to 4 bytes "long". |
140 |
firstDamagedLine = damageAreaFirstAddr / (mClientChar.width * mClientChar.bytesPerPixel); |
141 |
lastDamagedLine = damageAreaLastAddr / (mClientChar.width * mClientChar.bytesPerPixel); |
142 |
// Overflow may happen, because of the hack used above |
143 |
// and others, that set lastAddr = 0xfffffff0 (damageFrameBufferAll()) |
144 |
if (lastDamagedLine >= mClientChar.height) { |
145 |
lastDamagedLine = mClientChar.height-1; |
146 |
} |
147 |
|
148 |
sys_lock_mutex(mRedrawMutex); |
149 |
|
150 |
if (SDL_MUSTLOCK(gSDLScreen)) SDL_LockSurface(gSDLScreen); |
151 |
|
152 |
sys_convert_display(mClientChar, mSDLChar, gFrameBuffer, |
153 |
(byte*)gSDLScreen->pixels, firstDamagedLine, lastDamagedLine); |
154 |
|
155 |
if (SDL_MUSTLOCK(gSDLScreen)) SDL_UnlockSurface(gSDLScreen); |
156 |
|
157 |
SDL_UpdateRect(gSDLScreen, 0, firstDamagedLine, mClientChar.width, lastDamagedLine-firstDamagedLine+1); |
158 |
|
159 |
#if 0 |
160 |
if (mSDLFrameBuffer) { // using software-mode? |
161 |
sys_convert_display(mClientChar, mSDLChar, gFrameBuffer, |
162 |
mSDLFrameBuffer, firstDamagedLine, lastDamagedLine); |
163 |
if (SDL_MUSTLOCK(gSDLScreen)) |
164 |
SDL_UnlockSurface(gSDLScreen); |
165 |
} else { |
166 |
// meaning we are in 32 bit. let sdl do a hardware-blit |
167 |
// and convert Client to HostFramebuffer Pixelformat |
168 |
SDL_Rect srcrect, dstrect; |
169 |
srcrect.x = 0; |
170 |
srcrect.y = firstDamagedLine; |
171 |
srcrect.w = mClientChar.width; |
172 |
srcrect.h = lastDamagedLine - firstDamagedLine+1; |
173 |
dstrect.x = 0; |
174 |
dstrect.y = firstDamagedLine; |
175 |
if (SDL_MUSTLOCK(gSDLScreen)) |
176 |
SDL_UnlockSurface(gSDLScreen); |
177 |
SDL_BlitSurface(mSDLClientScreen, &srcrect, gSDLScreen, &dstrect); |
178 |
} |
179 |
|
180 |
// If possible, we should use doublebuffering and SDL_Flip() |
181 |
// SDL_Flip(); |
182 |
SDL_UpdateRect(gSDLScreen, 0, firstDamagedLine, mClientChar.width, lastDamagedLine-firstDamagedLine+1); |
183 |
if (SDL_MUSTLOCK(gSDLScreen)) |
184 |
SDL_LockSurface(gSDLScreen); |
185 |
#endif |
186 |
sys_unlock_mutex(mRedrawMutex); |
187 |
} |
188 |
|
189 |
void SDLSystemDisplay::convertCharacteristicsToHost(DisplayCharacteristics &aHostChar, const DisplayCharacteristics &aClientChar) |
190 |
{ |
191 |
aHostChar = aClientChar; |
192 |
} |
193 |
|
194 |
bool SDLSystemDisplay::changeResolution(const DisplayCharacteristics &aCharacteristics) |
195 |
{ |
196 |
// We absolutely have to make sure that SDL_calls are only used |
197 |
// in the thread, that did SDL_INIT and created Surfaces etc... |
198 |
// This function behaves as a forward-function for changeResolution calls. |
199 |
// It creates an SDL_Condition and pushes a userevent (no.1) onto |
200 |
// the event queue. SDL_CondWait is used to wait for the event-thread |
201 |
// to do the actual work (in reacting on the event and calling changeResolutionREAL) |
202 |
// and finally signaling back to us, with SDL_Signal, that work is done. |
203 |
|
204 |
// AND: we have to check if the call came from another thread. |
205 |
// otherwise we would block and wait for our own thread to continue.-> endless loop |
206 |
|
207 |
mSDLChartemp = aCharacteristics; |
208 |
if (SDL_ThreadID() != mEventThreadID) { // called from a different thread than sdl eventloop |
209 |
SDL_Event ev; |
210 |
SDL_mutex *tmpmutex; |
211 |
|
212 |
//DPRINTF("Forward handler got called\n"); |
213 |
ev.type = SDL_USEREVENT; |
214 |
ev.user.code = 1; |
215 |
|
216 |
|
217 |
tmpmutex = SDL_CreateMutex(); |
218 |
mWaitcondition = SDL_CreateCond(); |
219 |
|
220 |
SDL_LockMutex(tmpmutex); |
221 |
SDL_PushEvent(&ev); |
222 |
|
223 |
SDL_CondWait(mWaitcondition, tmpmutex); |
224 |
//SDL_CondWait(mWaitcondition, tmpmutex, 5000); |
225 |
|
226 |
SDL_UnlockMutex(tmpmutex); |
227 |
SDL_DestroyMutex(tmpmutex); |
228 |
SDL_DestroyCond(mWaitcondition); |
229 |
return mChangeResRet; |
230 |
} else { |
231 |
// we can call it directly because we are in the same thread |
232 |
//ht_printf("direct call\n"); |
233 |
return changeResolutionREAL(aCharacteristics); |
234 |
} |
235 |
|
236 |
} |
237 |
|
238 |
bool SDLSystemDisplay::changeResolutionREAL(const DisplayCharacteristics &aCharacteristics) |
239 |
{ |
240 |
Uint32 videoFlags = 0; /* Flags to pass to SDL_SetVideoMode */ |
241 |
DisplayCharacteristics chr; |
242 |
|
243 |
DPRINTF("changeRes got called\n"); |
244 |
|
245 |
convertCharacteristicsToHost(chr, aCharacteristics); |
246 |
|
247 |
/* |
248 |
* From the SDL documentation: |
249 |
* "Note: The bpp parameter is the number of bits per pixel, |
250 |
* so a bpp of 24 uses the packed representation of 3 bytes/pixel. |
251 |
* For the more common 4 bytes/pixel mode, use a bpp of 32. |
252 |
* Somewhat oddly, both 15 and 16 will request a 2 bytes/pixel |
253 |
* mode, but different pixel formats." |
254 |
* |
255 |
* Because of their odd convention, we have to mess with |
256 |
* bytesPerPixel here. |
257 |
*/ |
258 |
uint bitsPerPixel; |
259 |
switch (chr.bytesPerPixel) { |
260 |
case 2: |
261 |
bitsPerPixel = 15; |
262 |
break; |
263 |
case 4: |
264 |
bitsPerPixel = 32; |
265 |
break; |
266 |
default: |
267 |
ASSERT(0); |
268 |
break; |
269 |
} |
270 |
|
271 |
DPRINTF("SDL: Changing resolution to %dx%dx%d\n", aCharacteristics.width, aCharacteristics.height,bitsPerPixel); |
272 |
|
273 |
if (mFullscreen) videoFlags |= SDL_FULLSCREEN; |
274 |
if (!SDL_VideoModeOK(chr.width, chr.height, bitsPerPixel, videoFlags)) { |
275 |
/* |
276 |
* We can't this mode in fullscreen |
277 |
* so we try if we can use it in windowed mode. |
278 |
*/ |
279 |
if (!mFullscreen) return false; |
280 |
videoFlags &= ~SDL_FULLSCREEN; |
281 |
if (!SDL_VideoModeOK(chr.width, chr.height, bitsPerPixel, videoFlags)) { |
282 |
return false; |
283 |
} |
284 |
mFullscreen = false; |
285 |
/* |
286 |
* We can use the mode in windowed mode. |
287 |
*/ |
288 |
} |
289 |
|
290 |
if (SDL_VideoModeOK(chr.width, chr.height, bitsPerPixel, videoFlags | SDL_SWSURFACE)) { |
291 |
videoFlags |= SDL_SWSURFACE; |
292 |
DPRINTF("can use SWSURFACE\n"); |
293 |
if (SDL_VideoModeOK(chr.width, chr.height, bitsPerPixel, videoFlags | SDL_HWACCEL)) { |
294 |
videoFlags |= SDL_HWACCEL; |
295 |
DPRINTF("can use HWACCEL\n"); |
296 |
} |
297 |
} |
298 |
|
299 |
mSDLChar = chr; |
300 |
mClientChar = aCharacteristics; |
301 |
|
302 |
sys_lock_mutex(mRedrawMutex); |
303 |
#if 0 |
304 |
if (gSDLScreen && SDL_MUSTLOCK(gSDLScreen)) { |
305 |
SDL_UnlockSurface(gSDLScreen); |
306 |
} |
307 |
#endif |
308 |
|
309 |
gSDLScreen = SDL_SetVideoMode(aCharacteristics.width, aCharacteristics.height, |
310 |
bitsPerPixel, videoFlags); |
311 |
|
312 |
if (!gSDLScreen) { |
313 |
// FIXME: this is really bad. |
314 |
ht_printf("SDL: FATAL: can't switch mode?!\n"); |
315 |
exit(1); |
316 |
} |
317 |
|
318 |
#ifdef __WIN32__ |
319 |
if (videoFlags & SDL_FULLSCREEN) { |
320 |
DEVMODE refresh; |
321 |
refresh.dmDisplayFrequency = chr.vsyncFrequency; |
322 |
ChangeDisplaySettings(&refresh, DM_DISPLAYFREQUENCY); |
323 |
} |
324 |
#else |
325 |
// FIXME: implement refreshrate change for other host OS |
326 |
#endif |
327 |
|
328 |
mFullscreenChanged = videoFlags & SDL_FULLSCREEN; |
329 |
if (gSDLScreen->pitch != aCharacteristics.width * aCharacteristics.bytesPerPixel) { |
330 |
// FIXME: this is really bad. |
331 |
ht_printf("SDL: FATAL: new mode has scanline gap. Trying to revert to old mode.\n"); |
332 |
exit(1); |
333 |
} |
334 |
|
335 |
gFrameBuffer = (byte*)realloc(gFrameBuffer, mClientChar.width * |
336 |
mClientChar.height * mClientChar.bytesPerPixel); |
337 |
#if 0 |
338 |
if (mSDLClientScreen) { |
339 |
// if this is a modechange, free the old surface first. |
340 |
if (SDL_MUSTLOCK(gSDLScreen)) |
341 |
SDL_UnlockSurface(gSDLScreen); |
342 |
SDL_FreeSurface(mSDLClientScreen); |
343 |
} |
344 |
|
345 |
// Init Clientsurface |
346 |
mSDLClientScreen = SDL_CreateRGBSurface(SDL_HWSURFACE, mClientChar.width, mClientChar.height, |
347 |
bitsPerPixel, 0x0000ff00, 0x00ff0000, 0xff000000, 0); |
348 |
// Mask isn't important since we only use it as a buffer and never let SDL draw with it... |
349 |
// Though it is used in 32 bit, and then the mask is ok |
350 |
|
351 |
if (!mSDLClientScreen) { |
352 |
ht_printf("SDL: FATAL: can't create surface\n"); |
353 |
exit(1); |
354 |
} |
355 |
|
356 |
gFrameBuffer = (byte*)mSDLClientScreen->pixels; |
357 |
|
358 |
// are we running in 32 bit? use sdl, else use pearpc's software convert |
359 |
if (bitsPerPixel != 32) { |
360 |
mSDLFrameBuffer = (byte*)gSDLScreen->pixels; |
361 |
} else { |
362 |
mSDLFrameBuffer = NULL; |
363 |
} |
364 |
|
365 |
// clean up |
366 |
if (SDL_MUSTLOCK(gSDLScreen)) |
367 |
SDL_LockSurface(gSDLScreen); |
368 |
if (SDL_MUSTLOCK(mSDLClientScreen)) |
369 |
SDL_LockSurface(mSDLClientScreen); |
370 |
#endif |
371 |
|
372 |
//ht_printf("SDL rmask %08x, gmask %08x, bmask %08x\n", gSDLScreen->format->Rmask, |
373 |
// gSDLScreen->format->Gmask, gSDLScreen->format->Bmask); |
374 |
// |
375 |
mSDLChar.redSize = 8 - gSDLScreen->format->Rloss; |
376 |
mSDLChar.greenSize = 8 - gSDLScreen->format->Gloss; |
377 |
mSDLChar.blueSize = 8 - gSDLScreen->format->Bloss; |
378 |
mSDLChar.redShift = gSDLScreen->format->Rshift; |
379 |
mSDLChar.greenShift = gSDLScreen->format->Gshift; |
380 |
mSDLChar.blueShift = gSDLScreen->format->Bshift; |
381 |
|
382 |
damageFrameBufferAll(); |
383 |
sys_unlock_mutex(mRedrawMutex); |
384 |
return true; |
385 |
} |
386 |
|
387 |
void SDLSystemDisplay::getHostCharacteristics(Container &modes) |
388 |
{ |
389 |
#ifdef __WIN32__ |
390 |
DEVMODE dm; |
391 |
DWORD num = 0; |
392 |
while (EnumDisplaySettings(NULL, num++, &dm)) { |
393 |
switch (dm.dmBitsPerPel) { |
394 |
case 15: |
395 |
case 16: |
396 |
dm.dmBitsPerPel = 2; |
397 |
break; |
398 |
case 32: |
399 |
dm.dmBitsPerPel = 4; |
400 |
break; |
401 |
default: |
402 |
continue; |
403 |
} |
404 |
DisplayCharacteristics *dc = new DisplayCharacteristics; |
405 |
dc->width = dm.dmPelsWidth; |
406 |
dc->height = dm.dmPelsHeight; |
407 |
dc->bytesPerPixel = dm.dmBitsPerPel; |
408 |
dc->scanLineLength = -1; |
409 |
dc->vsyncFrequency = dm.dmDisplayFrequency; |
410 |
dc->redShift = -1; |
411 |
dc->redSize = -1; |
412 |
dc->greenShift = -1; |
413 |
dc->greenSize = -1; |
414 |
dc->blueShift = -1; |
415 |
dc->blueSize = -1; |
416 |
modes.insert(dc); |
417 |
} |
418 |
#else |
419 |
#if 0 |
420 |
//ARGL, won't work |
421 |
|
422 |
SDL_Rect **modes; |
423 |
modes = SDL_ListModes(NULL, SDL_FULLSCREEN); |
424 |
|
425 |
/* Check is there are any modes available */ |
426 |
if (modes == (SDL_Rect **)0){ |
427 |
DPRINTF("No modes available!\n"); |
428 |
return; |
429 |
} |
430 |
|
431 |
/* Check if our resolution is restricted */ |
432 |
if (modes == (SDL_Rect **)-1) { |
433 |
return; |
434 |
} else { |
435 |
} |
436 |
#endif |
437 |
#endif |
438 |
} |
439 |
|
440 |
void SDLSystemDisplay::setMouseGrab(bool enable) |
441 |
{ |
442 |
if (enable == isMouseGrabbed()) return; |
443 |
SystemDisplay::setMouseGrab(enable); |
444 |
if (enable) { |
445 |
// SDL_ShowCursor(SDL_DISABLE); |
446 |
SDL_SetCursor(mInvisibleCursor); |
447 |
SDL_WM_GrabInput(SDL_GRAB_ON); |
448 |
} else { |
449 |
SDL_SetCursor(mVisibleCursor); |
450 |
SDL_WM_GrabInput(SDL_GRAB_OFF); |
451 |
// SDL_ShowCursor(SDL_ENABLE); |
452 |
} |
453 |
} |
454 |
|
455 |
void SDLSystemDisplay::initCursor() |
456 |
{ |
457 |
mVisibleCursor = SDL_GetCursor(); |
458 |
// FIXME: need a portable way of getting cursor sizes |
459 |
byte mask[64]; |
460 |
memset(mask, 0, sizeof mask); |
461 |
mInvisibleCursor = SDL_CreateCursor(mask, mask, 16, 16, 0, 0); |
462 |
} |
463 |
|
464 |
SystemDisplay *allocSystemDisplay(const char *title, const DisplayCharacteristics &chr, int redraw_ms) |
465 |
{ |
466 |
DPRINTF("Making new window %d x %d\n", chr.width, chr.height); |
467 |
return new SDLSystemDisplay(title, chr, redraw_ms); |
468 |
} |