1 #include <wx/dcbuffer.h>
2
3 #include "wxvbam.h"
4 #include "drawing.h"
5 #include "../common/ConfigManager.h"
6 #include "../common/Patch.h"
7 #include "../gb/gbPrinter.h"
8 #include "../gba/RTC.h"
9 #include "../gba/agbprint.h"
10 #include "../sdl/text.h"
11 #include "filters.h"
12 #include "../../version.h"
13
14 int emulating;
15
IMPLEMENT_DYNAMIC_CLASS(GameArea,wxPanel)16 IMPLEMENT_DYNAMIC_CLASS(GameArea, wxPanel)
17
18 GameArea::GameArea()
19 : wxPanel(), loaded(IMAGE_UNKNOWN), panel(NULL), emusys(NULL),
20 basic_width(GBAWidth), basic_height(GBAHeight), fullscreen(false),
21 paused(false), was_paused(false), rewind_time(0), do_rewind(false),
22 rewind_mem(0), pointer_blanked(false), mouse_active_time(0)
23 {
24 SetSizer(new wxBoxSizer(wxVERTICAL));
25 // all renderers prefer 32-bit
26 // well, "simple" prefers 24-bit, but that's not available for filters
27 systemColorDepth = 32;
28 hq2x_init(32);
29 Init_2xSaI(32);
30 }
31
LoadGame(const wxString & name)32 void GameArea::LoadGame(const wxString &name)
33 {
34 rom_scene_rls = wxT("-");
35 rom_scene_rls_name = wxT("-");
36 rom_name = wxT("");
37 // fex just crashes if file does not exist and it's compressed,
38 // so check first
39 wxFileName fnfn(name);
40 bool badfile = !fnfn.IsFileReadable();
41
42 // if path was relative, look for it before giving up
43 if (badfile && !fnfn.IsAbsolute())
44 {
45 wxString rp = fnfn.GetPath();
46
47 // can't really decide which dir to use, so try GBA first, then GB
48 if (!wxGetApp().GetAbsolutePath(gopts.gba_rom_dir).empty())
49 {
50 fnfn.SetPath(wxGetApp().GetAbsolutePath(gopts.gba_rom_dir) + wxT('/') + rp);
51 badfile = !fnfn.IsFileReadable();
52 }
53
54 if (badfile && !wxGetApp().GetAbsolutePath(gopts.gb_rom_dir).empty())
55 {
56 fnfn.SetPath(wxGetApp().GetAbsolutePath(gopts.gb_rom_dir) + wxT('/') + rp);
57 badfile = !fnfn.IsFileReadable();
58 }
59
60 if (badfile && !wxGetApp().GetAbsolutePath(gopts.gbc_rom_dir).empty())
61 {
62 fnfn.SetPath(wxGetApp().GetAbsolutePath(gopts.gbc_rom_dir) + wxT('/') + rp);
63 badfile = !fnfn.IsFileReadable();
64 }
65 }
66
67 // auto-conversion of wxCharBuffer to const char * seems broken
68 // so save underlying wxCharBuffer (or create one of none is used)
69 wxCharBuffer fnb(fnfn.GetFullPath().mb_fn_str());
70 const char* fn = fnb.data();
71 IMAGE_TYPE t = badfile ? IMAGE_UNKNOWN : utilFindType(fn);
72
73 if (t == IMAGE_UNKNOWN)
74 {
75 wxString s;
76 s.Printf(_("%s is not a valid ROM file"), name.c_str());
77 wxMessageDialog dlg(GetParent(), s, _("Problem loading file"), wxOK | wxICON_ERROR);
78 dlg.ShowModal();
79 return;
80 }
81
82 {
83 wxFileConfig* cfg = wxGetApp().cfg;
84
85 if (!gopts.recent_freeze)
86 {
87 gopts.recent->AddFileToHistory(name);
88 wxGetApp().frame->SetRecentAccels();
89 cfg->SetPath(wxT("/Recent"));
90 gopts.recent->Save(*cfg);
91 cfg->SetPath(wxT("/"));
92 cfg->Flush();
93 }
94 }
95
96 UnloadGame();
97 // strip extension from actual game file name
98 // FIXME: save actual file name of archive so patches & cheats can
99 // be loaded from archive
100 // FIXME: if archive name does not match game name, prepend archive
101 // name to uniquify game name
102 loaded_game = fnfn;
103 loaded_game.ClearExt();
104 loaded_game.MakeAbsolute();
105 // load patch, if enabled
106 // note that it is difficult to load from archive due to
107 // ../common/Patch.cpp depending on opening the file itself and doing
108 // lots of seeking & such. The only way would be to copy the patch
109 // out to a temporary file and load it (and can't just use
110 // AssignTempFileName because it needs correct extension)
111 // too much trouble for now, though
112 bool loadpatch = autoPatch;
113 wxFileName pfn = loaded_game;
114 int ovSaveType = 0;
115
116 if (loadpatch)
117 {
118 // SetExt may strip something off by accident, so append to text instead
119 // pfn.SetExt(wxT("ips")
120 pfn.SetFullName(pfn.GetFullName() + wxT(".ips"));
121
122 if (!pfn.IsFileReadable())
123 {
124 pfn.SetExt(wxT("ups"));
125
126 if (!pfn.IsFileReadable())
127 {
128 pfn.SetExt(wxT("ppf"));
129 loadpatch = pfn.IsFileReadable();
130 }
131 }
132 }
133
134 if (t == IMAGE_GB)
135 {
136 if (!gbLoadRom(fn))
137 {
138 wxString s;
139 s.Printf(_("Unable to load Game Boy ROM %s"), name.c_str());
140 wxMessageDialog dlg(GetParent(), s, _("Problem loading file"), wxOK | wxICON_ERROR);
141 dlg.ShowModal();
142 return;
143 }
144
145 rom_size = gbRomSize;
146
147 if (loadpatch)
148 {
149 int size = rom_size;
150 // auto-conversion of wxCharBuffer to const char * seems broken
151 // so save underlying wxCharBuffer (or create one of none is used)
152 wxCharBuffer pfnb(pfn.GetFullPath().mb_fn_str());
153 applyPatch(pfnb.data(), &gbRom, &size);
154
155 if (size != rom_size)
156 gbUpdateSizes();
157
158 rom_size = size;
159 }
160
161 // start sound; this must happen before CPU stuff
162 gb_effects_config.echo = (float)gopts.gb_echo / 100.0;
163 gb_effects_config.stereo = (float)gopts.gb_stereo / 100.0;
164 gbSoundSetDeclicking(gopts.gb_declick);
165 soundInit();
166 soundSetThrottle(throttle);
167 soundSetEnable(gopts.sound_en);
168 gbSoundSetSampleRate(!gopts.sound_qual ? 48000 :
169 44100 / (1 << (gopts.sound_qual - 1)));
170 soundSetVolume((float)gopts.sound_vol / 100.0);
171 gbGetHardwareType();
172 bool use_bios = false;
173 // auto-conversion of wxCharBuffer to const char * seems broken
174 // so save underlying wxCharBuffer (or create one of none is used)
175 const char* fn = NULL;
176 wxCharBuffer fnb;
177
178 if (gbCgbMode)
179 {
180 use_bios = useBiosFileGBC;
181 fnb = gopts.gbc_bios.mb_fn_str();
182 }
183 else
184 {
185 use_bios = useBiosFileGB;
186 fnb = gopts.gb_bios.mb_fn_str();
187 }
188
189 fn = fnb.data();
190 gbCPUInit(fn, use_bios);
191
192 if (use_bios && !useBios)
193 {
194 wxLogError(_("Could not load BIOS %s"), (gbCgbMode ? gopts.gbc_bios : gopts.gb_bios).c_str());
195 // could clear use flag & file name now, but better to force
196 // user to do it
197 }
198
199 gbReset();
200
201 if (gbBorderOn)
202 {
203 basic_width = gbBorderLineSkip = SGBWidth;
204 basic_height = SGBHeight;
205 gbBorderColumnSkip = (SGBWidth - GBWidth) / 2;
206 gbBorderRowSkip = (SGBHeight - GBHeight) / 2;
207 }
208 else
209 {
210 basic_width = gbBorderLineSkip = GBWidth;
211 basic_height = GBHeight;
212 gbBorderColumnSkip = gbBorderRowSkip = 0;
213 }
214
215 emusys = &GBSystem;
216 }
217 else /* if(t == IMAGE_GBA) */
218 {
219 if (!(rom_size = CPULoadRom(fn)))
220 {
221 wxString s;
222 s.Printf(_("Unable to load Game Boy Advance ROM %s"), name.c_str());
223 wxMessageDialog dlg(GetParent(), s, _("Problem loading file"), wxOK | wxICON_ERROR);
224 dlg.ShowModal();
225 return;
226 }
227
228 rom_crc32 = crc32(0L, rom, rom_size);
229
230 if (loadpatch)
231 {
232 // don't use real rom size or it might try to resize rom[]
233 // instead, use known size of rom[]
234 int size = 0x2000000;
235 // auto-conversion of wxCharBuffer to const char * seems broken
236 // so save underlying wxCharBuffer (or create one of none is used)
237 wxCharBuffer pfnb(pfn.GetFullPath().mb_fn_str());
238 applyPatch(pfnb.data(), &rom, &size);
239 // that means we no longer really know rom_size either <sigh>
240 }
241
242 wxFileConfig* cfg = wxGetApp().overrides;
243 wxString id = wxString((const char*)&rom[0xac], wxConvLibc, 4);
244
245 if (cfg->HasGroup(id))
246 {
247 cfg->SetPath(id);
248 rtcEnable(cfg->Read(wxT("rtcEnabled"), rtcEnabled));
249 int fsz = cfg->Read(wxT("flashSize"), (long)0);
250
251 if (fsz != 0x10000 && fsz != 0x20000)
252 fsz = 0x10000 << winFlashSize;
253
254 flashSetSize(fsz);
255 ovSaveType = cfg->Read(wxT("saveType"), cpuSaveType);
256
257 if (ovSaveType < 0 || ovSaveType > 5)
258 ovSaveType = 0;
259
260 if (ovSaveType == 0)
261 utilGBAFindSave(rom_size);
262 else
263 saveType = ovSaveType;
264
265 mirroringEnable = cfg->Read(wxT("mirroringEnabled"), (long)1);
266 cfg->SetPath(wxT("/"));
267 }
268 else
269 {
270 rtcEnable(rtcEnabled);
271 flashSetSize(0x10000 << winFlashSize);
272
273 if (cpuSaveType < 0 || cpuSaveType > 5)
274 cpuSaveType = 0;
275
276 if (cpuSaveType == 0)
277 utilGBAFindSave(rom_size);
278 else
279 saveType = cpuSaveType;
280
281 mirroringEnable = true;
282 }
283
284 doMirroring(mirroringEnable);
285 // start sound; this must happen before CPU stuff
286 gb_effects_config.echo = (float)gopts.gb_echo / 100.0;
287 gb_effects_config.stereo = (float)gopts.gb_stereo / 100.0;
288 gbSoundSetDeclicking(gopts.gb_declick);
289 soundInit();
290 soundSetThrottle(throttle);
291 soundSetEnable(gopts.sound_en);
292 gbSoundSetSampleRate(!gopts.sound_qual ? 48000 :
293 44100 / (1 << (gopts.sound_qual - 1)));
294 soundSetSampleRate(!gopts.sound_qual ? 48000 :
295 44100 / (1 << (gopts.sound_qual - 1)));
296 soundSetVolume((float)gopts.sound_vol / 100.0);
297 soundFiltering = (float)gopts.gba_sound_filter / 100.0f;
298 CPUInit(gopts.gba_bios.mb_fn_str(), useBiosFileGBA);
299
300 if (useBiosFileGBA && !useBios)
301 {
302 wxLogError(_("Could not load BIOS %s"), gopts.gba_bios.c_str());
303 // could clear use flag & file name now, but better to force
304 // user to do it
305 }
306
307 CPUReset();
308 basic_width = GBAWidth;
309 basic_height = GBAHeight;
310 emusys = &GBASystem;
311 }
312
313 if (fullScreen)
314 GameArea::ShowFullScreen(true);
315
316 loaded = t;
317 SetFrameTitle();
318 SetFocus();
319 AdjustSize(true);
320 emulating = true;
321 was_paused = true;
322 MainFrame* mf = wxGetApp().frame;
323 mf->SetJoystick();
324 mf->cmd_enable &= ~(CMDEN_GB | CMDEN_GBA);
325 mf->cmd_enable |= ONLOAD_CMDEN;
326 mf->cmd_enable |= loaded == IMAGE_GB ? CMDEN_GB : (CMDEN_GBA | CMDEN_NGDB_GBA);
327 mf->enable_menus();
328 #if (defined __WIN32__ || defined _WIN32)
329 gbSerialFunction = gbStartLink;
330 #else
331 gbSerialFunction = NULL;
332 #endif
333
334 // probably only need to do this for GB carts
335 if (winGbPrinterEnabled)
336 gbSerialFunction = gbPrinterSend;
337
338 // probably only need to do this for GBA carts
339 agbPrintEnable(agbPrint);
340 // set frame skip based on ROM type
341 systemFrameSkip = frameSkip;
342
343 if (systemFrameSkip < 0)
344 systemFrameSkip = 0;
345
346 // load battery and/or saved state
347 recompute_dirs();
348 mf->update_state_ts(true);
349 bool did_autoload = gopts.autoload_state ? LoadState() : false;
350
351 if (!did_autoload || skipSaveGameBattery)
352 {
353 wxString bname = loaded_game.GetFullName();
354 #ifndef NO_LINK
355 // MakeInstanceFilename doesn't do wxString, so just add slave ID here
356 int playerId = GetLinkPlayerId();
357
358 if (playerId >= 0)
359 {
360 bname.append(wxT('-'));
361 bname.append(wxChar(wxT('1') + playerId));
362 }
363
364 #endif
365 bname.append(wxT(".sav"));
366 wxFileName bat(batdir, bname);
367 fnb = bat.GetFullPath().mb_fn_str();
368
369 if (emusys->emuReadBattery(fnb.data()))
370 {
371 wxString msg;
372 msg.Printf(_("Loaded battery %s"), bat.GetFullPath().c_str());
373 systemScreenMessage(msg);
374
375 if (cpuSaveType == 0 && ovSaveType == 0 && t == IMAGE_GBA)
376 {
377 switch (bat.GetSize().GetValue())
378 {
379 case 0x200:
380 case 0x2000:
381 saveType = 1;
382 break;
383
384 case 0x8000:
385 saveType = 2;
386 break;
387
388 case 0x10000:
389 if (saveType == 1 || saveType == 2)
390 break;
391
392 case 0x20000:
393 saveType = 3;
394 flashSetSize(bat.GetSize().GetValue());
395 break;
396
397 default:
398 break;
399 }
400
401 SetSaveType(saveType);
402 }
403 }
404
405 // forget old save writes
406 systemSaveUpdateCounter = SYSTEM_SAVE_NOT_UPDATED;
407 }
408
409 // do an immediate rewind save
410 // even if loaded from state file: not smart enough yet to just
411 // do a reset or load from state file when # rewinds == 0
412 do_rewind = gopts.rewind_interval > 0;
413 // FIXME: backup battery file (useful if game name conflict)
414 cheats_dirty = (did_autoload && !skipSaveGameCheats) ||
415 (loaded == IMAGE_GB ? gbCheatNumber > 0 : cheatsNumber > 0);
416
417 if (gopts.autoload_cheats && (!did_autoload || skipSaveGameCheats))
418 {
419 wxFileName cfn = loaded_game;
420 // SetExt may strip something off by accident, so append to text instead
421 cfn.SetFullName(cfn.GetFullName() + wxT(".clt"));
422
423 if (cfn.IsFileReadable())
424 {
425 bool cld;
426
427 if (loaded == IMAGE_GB)
428 cld = gbCheatsLoadCheatList(cfn.GetFullPath().mb_fn_str());
429 else
430 cld = cheatsLoadCheatList(cfn.GetFullPath().mb_fn_str());
431
432 if (cld)
433 {
434 systemScreenMessage(_("Loaded cheats"));
435 cheats_dirty = false;
436 }
437 }
438 }
439
440 #ifndef NO_LINK
441
442 if (gopts.link_auto)
443 {
444 linkMode = mf->GetConfiguredLinkMode();
445 BootLink(linkMode, gopts.link_host.mb_str(wxConvUTF8), linkTimeout, linkHacks, linkNumPlayers);
446 }
447
448 #endif
449 }
450
SetFrameTitle()451 void GameArea::SetFrameTitle()
452 {
453 wxString tit = wxT("");
454
455 if (loaded != IMAGE_UNKNOWN)
456 {
457 tit.append(loaded_game.GetFullName());
458 tit.append(wxT(" - "));
459 }
460
461 tit.append(wxT("VisualBoyAdvance-M "));
462 #ifndef FINAL_BUILD
463 tit.append(_("Git:"));
464 #endif
465 #ifndef NO_LINK
466 int playerId = GetLinkPlayerId();
467
468 if (playerId >= 0)
469 {
470 tit.append(_(" player "));
471 tit.append(wxChar(wxT('1') + playerId));
472 }
473
474 #endif
475 wxGetApp().frame->SetTitle(tit);
476 }
477
recompute_dirs()478 void GameArea::recompute_dirs()
479 {
480 batdir = gopts.battery_dir;
481
482 if (!batdir.size())
483 {
484 batdir = loaded_game.GetPathWithSep();
485 }
486 else
487 {
488 batdir = wxGetApp().GetAbsolutePath(gopts.battery_dir);
489 }
490
491 statedir = gopts.state_dir;
492
493 if (!statedir.size())
494 {
495 statedir = loaded_game.GetPathWithSep();
496 }
497 else
498 {
499 statedir = wxGetApp().GetAbsolutePath(gopts.state_dir);
500 }
501
502 if (!wxIsWritable(batdir))
503 batdir = wxGetApp().GetConfigurationPath();
504
505 if (!wxIsWritable(statedir))
506 statedir = wxGetApp().GetConfigurationPath();
507 }
508
UnloadGame(bool destruct)509 void GameArea::UnloadGame(bool destruct)
510 {
511 if (!emulating)
512 return;
513
514 // last opportunity to autosave cheats
515 if (gopts.autoload_cheats && cheats_dirty)
516 {
517 wxFileName cfn = loaded_game;
518 // SetExt may strip something off by accident, so append to text instead
519 cfn.SetFullName(cfn.GetFullName() + wxT(".clt"));
520
521 if (loaded == IMAGE_GB)
522 {
523 if (!gbCheatNumber)
524 wxRemoveFile(cfn.GetFullPath());
525 else
526 gbCheatsSaveCheatList(cfn.GetFullPath().mb_fn_str());
527 }
528 else
529 {
530 if (!cheatsNumber)
531 wxRemoveFile(cfn.GetFullPath());
532 else
533 cheatsSaveCheatList(cfn.GetFullPath().mb_fn_str());
534 }
535 }
536
537 // if timer was counting down for save, go ahead and save
538 // this might not be safe, though..
539 if (systemSaveUpdateCounter > SYSTEM_SAVE_NOT_UPDATED)
540 {
541 SaveBattery(destruct);
542 }
543
544 MainFrame* mf = wxGetApp().frame;
545 #ifndef NO_FFMPEG
546 snd_rec.Stop();
547 vid_rec.Stop();
548 #endif
549 systemStopGameRecording();
550 systemStopGamePlayback();
551 debugger = false;
552 remoteCleanUp();
553 mf->cmd_enable |= CMDEN_NGDB_ANY;
554
555 if (loaded == IMAGE_GB)
556 {
557 gbCleanUp();
558 gbCheatRemoveAll();
559 }
560 else if (loaded == IMAGE_GBA)
561 {
562 CPUCleanUp();
563 cheatsDeleteAll(false);
564 }
565
566 emulating = false;
567 loaded = IMAGE_UNKNOWN;
568 emusys = NULL;
569 soundShutdown();
570
571 if (destruct)
572 return;
573
574 // in destructor, panel should be auto-deleted by wx since all panels
575 // are derived from a window attached as child to GameArea
576 if (panel)
577 panel->Delete();
578
579 panel = NULL;
580
581 // close any game-related viewer windows
582 // in destructor, viewer windows are in process of being deleted anyway
583 while (!mf->popups.empty())
584 mf->popups.front()->Close(true);
585
586 // remaining items are GUI updates that should not be needed in destructor
587 SetFrameTitle();
588 mf->cmd_enable &= UNLOAD_CMDEN_KEEP;
589 mf->update_state_ts(true);
590 mf->enable_menus();
591 mf->SetJoystick();
592 mf->ResetCheatSearch();
593
594 if (rewind_mem)
595 num_rewind_states = 0;
596 }
597
LoadState()598 bool GameArea::LoadState()
599 {
600 int slot = wxGetApp().frame->newest_state_slot();
601
602 if (slot < 1)
603 return false;
604
605 return LoadState(slot);
606 }
607
LoadState(int slot)608 bool GameArea::LoadState(int slot)
609 {
610 wxString fname;
611 fname.Printf(SAVESLOT_FMT, game_name().c_str(), slot);
612 return LoadState(wxFileName(statedir, fname));
613 }
614
LoadState(const wxFileName & fname)615 bool GameArea::LoadState(const wxFileName &fname)
616 {
617 // FIXME: first save to backup state if not backup state
618 bool ret = emusys->emuReadState(fname.GetFullPath().mb_fn_str());
619
620 if (ret && num_rewind_states)
621 {
622 MainFrame* mf = wxGetApp().frame;
623 mf->cmd_enable &= ~CMDEN_REWIND;
624 mf->enable_menus();
625 num_rewind_states = 0;
626 // do an immediate rewind save
627 // even if loaded from state file: not smart enough yet to just
628 // do a reset or load from state file when # rewinds == 0
629 do_rewind = true;
630 rewind_time = gopts.rewind_interval * 6;
631 }
632
633 if (ret)
634 {
635 // forget old save writes
636 systemSaveUpdateCounter = SYSTEM_SAVE_NOT_UPDATED;
637 // no point in blending after abrupt change
638 InterframeCleanup();
639 // frame rate calc should probably reset as well
640 was_paused = true;
641 // save state had a screen frame, so draw it
642 systemDrawScreen();
643 }
644
645 wxString msg;
646 msg.Printf(ret ? _("Loaded state %s") : _("Error loading state %s"),
647 fname.GetFullPath().c_str());
648 systemScreenMessage(msg);
649 return ret;
650 }
651
SaveState()652 bool GameArea::SaveState()
653 {
654 return SaveState(wxGetApp().frame->oldest_state_slot());
655 }
656
SaveState(int slot)657 bool GameArea::SaveState(int slot)
658 {
659 wxString fname;
660 fname.Printf(SAVESLOT_FMT, game_name().c_str(), slot);
661 return SaveState(wxFileName(statedir, fname));
662 }
663
SaveState(const wxFileName & fname)664 bool GameArea::SaveState(const wxFileName &fname)
665 {
666 // FIXME: first copy to backup state if not backup state
667 bool ret = emusys->emuWriteState(fname.GetFullPath().mb_fn_str());
668 wxGetApp().frame->update_state_ts(true);
669 wxString msg;
670 msg.Printf(ret ? _("Saved state %s") : _("Error saving state %s"),
671 fname.GetFullPath().c_str());
672 systemScreenMessage(msg);
673 return ret;
674 }
675
SaveBattery(bool quiet)676 void GameArea::SaveBattery(bool quiet)
677 {
678 // MakeInstanceFilename doesn't do wxString, so just add slave ID here
679 wxString bname = game_name();
680 #ifndef NO_LINK
681 int playerId = GetLinkPlayerId();
682
683 if (playerId >= 0)
684 {
685 bname.append(wxT('-'));
686 bname.append(wxChar(wxT('1') + playerId));
687 }
688
689 #endif
690 bname.append(wxT(".sav"));
691 wxFileName bat(batdir, bname);
692 bat.Mkdir(0777, wxPATH_MKDIR_FULL);
693 wxString fn = bat.GetFullPath();
694 // auto-conversion of wxCharBuffer to const char * seems broken
695 // so save underlying wxCharBuffer (or create one of none is used)
696 wxCharBuffer fnb = fn.mb_fn_str();
697 wxString msg;
698
699 // FIXME: add option to support ring of backups
700 // of course some games just write battery way too often for such
701 // a thing to be useful
702 if (emusys->emuWriteBattery(fnb.data()))
703 msg.Printf(_("Wrote battery %s"), fn.c_str());
704 else
705 msg.Printf(_("Error writing battery %s"), fn.c_str());
706
707 systemSaveUpdateCounter = SYSTEM_SAVE_NOT_UPDATED;
708
709 if (!quiet)
710 systemScreenMessage(msg);
711 }
712
AddBorder()713 void GameArea::AddBorder()
714 {
715 if (basic_width != GBWidth)
716 return;
717
718 basic_width = SGBWidth;
719 basic_height = SGBHeight;
720 gbBorderLineSkip = SGBWidth;
721 gbBorderColumnSkip = (SGBWidth - GBWidth) / 2;
722 gbBorderRowSkip = (SGBHeight - GBHeight) / 2;
723 AdjustSize(false);
724 wxGetApp().frame->Fit();
725 GetSizer()->Detach(panel->GetWindow());
726
727 if (panel)
728 panel->Delete();
729
730 panel = NULL;
731 }
732
DelBorder()733 void GameArea::DelBorder()
734 {
735 if (basic_width != SGBWidth)
736 return;
737
738 basic_width = GBWidth;
739 basic_height = GBHeight;
740 gbBorderLineSkip = GBWidth;
741 gbBorderColumnSkip = gbBorderRowSkip = 0;
742 AdjustSize(false);
743 wxGetApp().frame->Fit();
744 GetSizer()->Detach(panel->GetWindow());
745
746 if (panel)
747 panel->Delete();
748
749 panel = NULL;
750 }
751
AdjustMinSize()752 void GameArea::AdjustMinSize()
753 {
754 wxWindow* frame = wxGetApp().frame;
755 // note: could safely set min size to 1x or less regardless of video_scale
756 // but setting it to scaled size makes resizing to default easier
757 wxSize sz(basic_width * gopts.video_scale, basic_height * gopts.video_scale);
758 SetMinSize(sz);
759 #if wxCHECK_VERSION(2,8,8)
760 sz = frame->ClientToWindowSize(sz);
761 #else
762 sz += frame->GetSize() - frame->GetClientSize();
763 #endif
764 frame->SetMinSize(sz);
765 }
766
LowerMinSize()767 void GameArea::LowerMinSize()
768 {
769 wxWindow* frame = wxGetApp().frame;
770 wxSize sz(basic_width , basic_height);
771 SetMinSize(sz);
772 // do not take decorations into account
773 frame->SetMinSize(sz);
774 }
775
AdjustSize(bool force)776 void GameArea::AdjustSize(bool force)
777 {
778 AdjustMinSize();
779
780 if (fullscreen)
781 return;
782
783 const wxSize newsz(basic_width * gopts.video_scale, basic_height * gopts.video_scale);
784
785 if (!force)
786 {
787 wxSize sz = GetSize();
788
789 if (sz.GetWidth() >= newsz.GetWidth() && sz.GetHeight() >= newsz.GetHeight())
790 return;
791 }
792
793 SetSize(newsz);
794 GetParent()->SetClientSize(newsz);
795 wxGetApp().frame->Fit();
796 }
797
ShowFullScreen(bool full)798 void GameArea::ShowFullScreen(bool full)
799 {
800 if (full == fullscreen)
801 {
802 // in case the tlw somehow lost its mind, force it to proper mode
803 if (wxGetApp().frame->IsFullScreen() != fullscreen)
804 wxGetApp().frame->ShowFullScreen(full);
805
806 return;
807 }
808
809 fullscreen = full;
810
811 // just in case screen mode is going to change, go ahead and preemptively
812 // delete panel to be recreated immediately after resize
813 if (panel)
814 {
815 panel->Delete();
816 panel = NULL;
817 }
818
819 // Windows does not restore old window size/pos
820 // at least under Wine
821 // so store them before entering fullscreen
822 static bool cursz_valid = false;
823 static wxSize cursz;
824 static wxPoint curpos;
825 MainFrame* tlw = wxGetApp().frame;
826 int dno = wxDisplay::GetFromWindow(tlw);
827
828 if (!full)
829 {
830 if (gopts.fs_mode.w && gopts.fs_mode.h)
831 {
832 wxDisplay d(dno);
833 d.ChangeMode(wxDefaultVideoMode);
834 }
835
836 tlw->ShowFullScreen(false);
837
838 if (!cursz_valid)
839 {
840 curpos = wxDefaultPosition;
841 cursz = tlw->GetMinSize();
842 }
843
844 tlw->SetSize(cursz);
845 tlw->SetPosition(curpos);
846 AdjustMinSize();
847 }
848 else
849 {
850 // close all non-modal dialogs
851 while (!tlw->popups.empty())
852 tlw->popups.front()->Close();
853
854 // mouse stays blank whenever full-screen
855 HidePointer();
856 cursz_valid = true;
857 cursz = tlw->GetSize();
858 curpos = tlw->GetPosition();
859 LowerMinSize();
860
861 if (gopts.fs_mode.w && gopts.fs_mode.h)
862 {
863 // grglflargm
864 // stupid wx does not do fullscreen properly when video mode is
865 // changed under X11. Since it does not use xrandr to switch, it
866 // just changes the video mode without resizing the desktop. It
867 // still uses the original desktop size for fullscreen, though.
868 // It also seems to stick the top-left corner wherever it pleases,
869 // so I can't just resize the panel and expect it to show up.
870 // ^(*&^^&!!!! maybe just disable this code altogether on UNIX
871 // most 3d cards are fine with full screen size, anyway.
872 wxDisplay d(dno);
873
874 if (!d.ChangeMode(gopts.fs_mode))
875 {
876 wxLogInfo(_("Fullscreen mode %dx%d-%d@%d not supported; looking for another"),
877 gopts.fs_mode.w, gopts.fs_mode.h, gopts.fs_mode.bpp, gopts.fs_mode.refresh);
878 // specifying a mode may not work with bpp/rate of 0
879 // in particular, unix does Matches() in wrong direction
880 wxArrayVideoModes vm = d.GetModes();
881 int best_mode = -1;
882 int i;
883
884 for (i = 0; i < vm.size(); i++)
885 {
886 if (vm[i].w != gopts.fs_mode.w || vm[i].h != gopts.fs_mode.h)
887 continue;
888
889 int bpp = vm[i].bpp;
890
891 if (gopts.fs_mode.bpp && bpp == gopts.fs_mode.bpp)
892 break;
893
894 if (!gopts.fs_mode.bpp && bpp == 32)
895 {
896 gopts.fs_mode.bpp = 32;
897 break;
898 }
899
900 int bm_bpp = best_mode < 0 ? 0 : vm[i].bpp;
901
902 if (bpp == 32 && bm_bpp != 32)
903 best_mode = i;
904 else if (bpp == 24 && bm_bpp < 24)
905 best_mode = i;
906 else if (bpp == 16 && bm_bpp < 24 && bm_bpp != 16)
907 best_mode = i;
908 else if (!best_mode)
909 best_mode = i;
910 }
911
912 if (i == vm.size() && best_mode >= 0)
913 i = best_mode;
914
915 if (i == vm.size())
916 {
917 wxLogWarning(_("Fullscreen mode %dx%d-%d@%d not supported"),
918 gopts.fs_mode.w, gopts.fs_mode.h, gopts.fs_mode.bpp, gopts.fs_mode.refresh);
919 gopts.fs_mode = wxVideoMode();
920
921 for (i = 0; i < vm.size(); i++)
922 wxLogInfo(_("Valid mode: %dx%d-%d@%d"), vm[i].w,
923 vm[i].h, vm[i].bpp,
924 vm[i].refresh);
925 }
926 else
927 {
928 gopts.fs_mode.bpp = vm[i].bpp;
929 gopts.fs_mode.refresh = vm[i].refresh;
930 }
931
932 wxLogInfo(_("Chose mode %dx%d-%d@%d"),
933 gopts.fs_mode.w, gopts.fs_mode.h, gopts.fs_mode.bpp, gopts.fs_mode.refresh);
934
935 if (!d.ChangeMode(gopts.fs_mode))
936 {
937 wxLogWarning(_("Failed to change mode to %dx%d-%d@%d"),
938 gopts.fs_mode.w, gopts.fs_mode.h, gopts.fs_mode.bpp, gopts.fs_mode.refresh);
939 gopts.fs_mode.w = 0;
940 }
941 }
942 }
943
944 tlw->ShowFullScreen(true);
945 }
946 }
947
~GameArea()948 GameArea::~GameArea()
949 {
950 UnloadGame(true);
951
952 if (rewind_mem)
953 free(rewind_mem);
954
955 if (gopts.fs_mode.w && gopts.fs_mode.h && fullscreen)
956 {
957 MainFrame* tlw = wxGetApp().frame;
958 int dno = wxDisplay::GetFromWindow(tlw);
959 wxDisplay d(dno);
960 d.ChangeMode(wxDefaultVideoMode);
961 }
962 }
963
Pause()964 void GameArea::Pause()
965 {
966 if (paused)
967 return;
968
969 paused = was_paused = true;
970
971 if (loaded != IMAGE_UNKNOWN)
972 soundPause();
973 }
974
Resume()975 void GameArea::Resume()
976 {
977 if (!paused)
978 return;
979
980 paused = false;
981 SetExtraStyle(GetExtraStyle() | wxWS_EX_PROCESS_IDLE);
982
983 if (loaded != IMAGE_UNKNOWN)
984 soundResume();
985
986 SetFocus();
987 }
988
OnIdle(wxIdleEvent & event)989 void GameArea::OnIdle(wxIdleEvent &event)
990 {
991 wxString pl = wxGetApp().pending_load;
992
993 if (pl.size())
994 {
995 // sometimes this gets into a loop if LoadGame() called before
996 // clearing pending_load. weird.
997 wxGetApp().pending_load = wxEmptyString;
998 LoadGame(pl);
999 MainFrame* mf = wxGetApp().frame;
1000
1001 if (gdbBreakOnLoad)
1002 mf->GDBBreak();
1003
1004 if (debugger && loaded != IMAGE_GBA)
1005 {
1006 wxLogError(_("Not a valid GBA cartridge"));
1007 UnloadGame();
1008 }
1009 }
1010
1011 // stupid wx doesn't resize to screen size
1012 // forcing it this way just puts it in an infinite loop, though
1013 // with wx trying to resize/reposition to what it thinks is full screen
1014 // every time it detects a manual resize like this
1015 if (gopts.fs_mode.w && gopts.fs_mode.h && fullscreen)
1016 {
1017 wxSize sz = GetSize();
1018
1019 if (sz.GetWidth() != gopts.fs_mode.w || sz.GetHeight() != gopts.fs_mode.h)
1020 {
1021 wxGetApp().frame->Move(0, 84);
1022 SetSize(gopts.fs_mode.w, gopts.fs_mode.h);
1023 }
1024 }
1025
1026 #ifdef __WXMSW__
1027
1028 // Check for online updates
1029 if (gopts.onlineupdates > 0 && !emusys)
1030 {
1031 if ((wxDateTime::Now().GetTicks() - gopts.last_update) > gopts.onlineupdates * 24 * 60 * 60)
1032 {
1033 wxGetApp().frame->CheckForUpdates();
1034 }
1035 }
1036
1037 #endif
1038
1039 if (!emusys)
1040 return;
1041
1042 if (!panel)
1043 {
1044 switch (gopts.render_method)
1045 {
1046 case RND_SIMPLE:
1047 panel = new BasicDrawingPanel(this, basic_width, basic_height);
1048 break;
1049 #ifndef NO_OGL
1050
1051 case RND_OPENGL:
1052 panel = new GLDrawingPanel(this, basic_width, basic_height);
1053 break;
1054 #endif
1055 #ifndef NO_CAIRO
1056
1057 case RND_CAIRO:
1058 panel = new CairoDrawingPanel(this, basic_width, basic_height);
1059 break;
1060 #endif
1061 #ifdef __WXMSW__
1062
1063 case RND_DIRECT3D:
1064 panel = new DXDrawingPanel(this, basic_width, basic_height);
1065 break;
1066 #endif
1067 }
1068
1069 wxWindow* w = panel->GetWindow();
1070 w->SetBackgroundStyle(wxBG_STYLE_CUSTOM);
1071 w->Enable(false); // never give it the keyboard focus
1072 w->SetSize(wxSize(basic_width, basic_height));
1073
1074 if (maxScale)
1075 w->SetMaxSize(wxSize(basic_width * maxScale,
1076 basic_height * maxScale));
1077
1078 GetSizer()->Add(w, 1, gopts.retain_aspect ?
1079 (wxSHAPED | wxALIGN_CENTER) : wxEXPAND);
1080 Layout();
1081
1082 if (pointer_blanked)
1083 w->SetCursor(wxCursor(wxCURSOR_BLANK));
1084 }
1085
1086 if (!paused && (!pauseWhenInactive || wxGetApp().frame->HasFocus()))
1087 {
1088 HidePointer();
1089 event.RequestMore();
1090
1091 if (debugger)
1092 {
1093 was_paused = true;
1094 dbgMain();
1095
1096 if (!emulating)
1097 {
1098 emulating = true;
1099 UnloadGame();
1100 }
1101
1102 return;
1103 }
1104
1105 emusys->emuMain(emusys->emuCount);
1106 #ifndef NO_LINK
1107
1108 if (loaded == IMAGE_GBA && GetLinkMode() != LINK_DISCONNECTED)
1109 CheckLinkConnection();
1110
1111 #endif
1112 }
1113 else
1114 {
1115 was_paused = true;
1116
1117 if (paused)
1118 SetExtraStyle(GetExtraStyle() & ~wxWS_EX_PROCESS_IDLE);
1119 }
1120
1121 if (do_rewind && emusys->emuWriteMemState)
1122 {
1123 if (!rewind_mem)
1124 {
1125 rewind_mem = (char*)malloc(NUM_REWINDS * REWIND_SIZE);
1126 num_rewind_states = next_rewind_state = 0;
1127 }
1128
1129 if (!rewind_mem)
1130 {
1131 wxLogError(_("No memory for rewinding"));
1132 wxGetApp().frame->Close(true);
1133 return;
1134 }
1135
1136 long resize;
1137
1138 if (!emusys->emuWriteMemState(&rewind_mem[REWIND_SIZE * next_rewind_state],
1139 REWIND_SIZE, resize /* actual size */))
1140 // if you see a lot of these, maybe increase REWIND_SIZE
1141 wxLogInfo(_("Error writing rewind state"));
1142 else
1143 {
1144 if (!num_rewind_states)
1145 {
1146 MainFrame* mf = wxGetApp().frame;
1147 mf->cmd_enable |= CMDEN_REWIND;
1148 mf->enable_menus();
1149 }
1150
1151 if (num_rewind_states < NUM_REWINDS)
1152 ++num_rewind_states;
1153
1154 next_rewind_state = (next_rewind_state + 1) % NUM_REWINDS;
1155 }
1156
1157 do_rewind = false;
1158 }
1159 }
1160
1161 // Note: keys will get stuck if they are released while window has no focus
1162 // can't really do anything about it, except scan for pressed keys on
1163 // activate events. Maybe later.
1164
1165 static uint32_t bmask[NUM_KEYS] =
1166 {
1167 KEYM_UP, KEYM_DOWN, KEYM_LEFT, KEYM_RIGHT, KEYM_A, KEYM_B, KEYM_L, KEYM_R,
1168 KEYM_SELECT, KEYM_START, KEYM_MOTION_UP, KEYM_MOTION_DOWN, KEYM_MOTION_LEFT,
1169 KEYM_MOTION_RIGHT, KEYM_MOTION_IN, KEYM_MOTION_OUT, KEYM_AUTO_A, KEYM_AUTO_B, KEYM_SPEED, KEYM_CAPTURE,
1170 KEYM_GS
1171 };
1172
1173 static wxJoyKeyBinding_v keys_pressed;
1174
process_key_press(bool down,int key,int mod,int joy=0)1175 static void process_key_press(bool down, int key, int mod, int joy = 0)
1176 {
1177 // check if key is already pressed
1178 int kpno;
1179
1180 for (kpno = 0; kpno < keys_pressed.size(); kpno++)
1181 if (keys_pressed[kpno].key == key && keys_pressed[kpno].mod == mod &&
1182 keys_pressed[kpno].joy == joy)
1183 break;
1184
1185 if (kpno < keys_pressed.size())
1186 {
1187 // double press is noop
1188 if (down)
1189 return;
1190
1191 // otherwise forget it
1192 keys_pressed.erase(keys_pressed.begin() + kpno);
1193 }
1194 else
1195 {
1196 // double release is noop
1197 if (!down)
1198 return;
1199
1200 // otherwise remember it
1201 // c++0x
1202 // keys_pressed.push_back({ key, mod, joy });
1203 wxJoyKeyBinding jb = { key, mod, joy };
1204 keys_pressed.push_back(jb);
1205 }
1206
1207 // find all game keys this is bound to
1208 for (int i = 0; i < 4; i++)
1209 for (int j = 0; j < NUM_KEYS; j++)
1210 {
1211 wxJoyKeyBinding_v &b = gopts.joykey_bindings[i][j];
1212
1213 for (int k = 0; k < b.size(); k++)
1214 if (b[k].key == key && b[k].mod == mod && b[k].joy == joy)
1215 {
1216 if (down)
1217 joypress[i] |= bmask[j];
1218 else
1219 {
1220 // only release if no others pressed
1221 int k2;
1222
1223 for (k2 = 0; k2 < b.size(); k2++)
1224 {
1225 if (k == k2 || (b[k2].key == key && b[k2].mod == mod &&
1226 b[k2].joy == joy))
1227 continue;
1228
1229 for (kpno = 0; kpno < keys_pressed.size(); kpno++)
1230 if (keys_pressed[kpno].key == b[k2].key &&
1231 keys_pressed[kpno].mod == b[k2].mod &&
1232 keys_pressed[kpno].joy == b[k2].joy)
1233 break;
1234
1235 if (kpno < keys_pressed.size())
1236 break;
1237 }
1238
1239 if (k2 == b.size())
1240 joypress[i] &= ~bmask[j];
1241 }
1242
1243 break;
1244 }
1245 }
1246 }
1247
OnKeyDown(wxKeyEvent & ev)1248 void GameArea::OnKeyDown(wxKeyEvent &ev)
1249 {
1250 process_key_press(true, ev.GetKeyCode(), 0 /* ev.GetModifiers() */);
1251 ev.Skip(); // process accelerators
1252 }
1253
OnKeyUp(wxKeyEvent & ev)1254 void GameArea::OnKeyUp(wxKeyEvent &ev)
1255 {
1256 process_key_press(false, ev.GetKeyCode(), 0 /* ev.GetModifiers() */);
1257 ev.Skip(); // process accelerators
1258 }
1259
OnSDLJoy(wxSDLJoyEvent & ev)1260 void GameArea::OnSDLJoy(wxSDLJoyEvent &ev)
1261 {
1262 int key = ev.GetControlIndex();
1263 int mod = wxJoyKeyTextCtrl::DigitalButton(ev);
1264 int joy = ev.GetJoy() + 1;
1265
1266 // mutually exclusive key types unpress their opposite
1267 if (mod == WXJB_AXIS_PLUS)
1268 {
1269 process_key_press(false, key, WXJB_AXIS_MINUS, joy);
1270 process_key_press(ev.GetControlValue() != 0, key, mod, joy);
1271 }
1272 else if (mod == WXJB_AXIS_MINUS)
1273 {
1274 process_key_press(false, key, WXJB_AXIS_PLUS, joy);
1275 process_key_press(ev.GetControlValue() != 0, key, mod, joy);
1276 }
1277 else if (mod >= WXJB_HAT_FIRST && mod <= WXJB_HAT_LAST)
1278 {
1279 if (ev.GetControlValue() == 0)
1280 {
1281 process_key_press(false, key, WXJB_HAT_N, joy);
1282 process_key_press(false, key, WXJB_HAT_S, joy);
1283 process_key_press(false, key, WXJB_HAT_E, joy);
1284 process_key_press(false, key, WXJB_HAT_W, joy);
1285 }
1286 else
1287 {
1288 switch (mod)
1289 {
1290 case WXJB_HAT_N:
1291 process_key_press(ev.GetControlValue() != 0, key, WXJB_HAT_N, joy);
1292 process_key_press(false, key, WXJB_HAT_S, joy);
1293 break;
1294
1295 case WXJB_HAT_S:
1296 process_key_press(ev.GetControlValue() != 0, key, WXJB_HAT_S, joy);
1297 process_key_press(false, key, WXJB_HAT_N, joy);
1298 break;
1299
1300 case WXJB_HAT_E:
1301 process_key_press(ev.GetControlValue() != 0, key, WXJB_HAT_E, joy);
1302 process_key_press(false, key, WXJB_HAT_W, joy);
1303 break;
1304
1305 case WXJB_HAT_W:
1306 process_key_press(ev.GetControlValue() != 0, key, WXJB_HAT_W, joy);
1307 process_key_press(false, key, WXJB_HAT_E, joy);
1308 break;
1309
1310 case WXJB_HAT_NE:
1311 process_key_press(ev.GetControlValue() != 0, key, WXJB_HAT_N, joy);
1312 process_key_press(ev.GetControlValue() != 0, key, WXJB_HAT_E, joy);
1313 process_key_press(false, key, WXJB_HAT_S, joy);
1314 process_key_press(false, key, WXJB_HAT_W, joy);
1315 break;
1316
1317 case WXJB_HAT_NW:
1318 process_key_press(ev.GetControlValue() != 0, key, WXJB_HAT_N, joy);
1319 process_key_press(ev.GetControlValue() != 0, key, WXJB_HAT_W, joy);
1320 process_key_press(false, key, WXJB_HAT_S, joy);
1321 process_key_press(false, key, WXJB_HAT_E, joy);
1322 break;
1323
1324 case WXJB_HAT_SE:
1325 process_key_press(ev.GetControlValue() != 0, key, WXJB_HAT_S, joy);
1326 process_key_press(ev.GetControlValue() != 0, key, WXJB_HAT_E, joy);
1327 process_key_press(false, key, WXJB_HAT_N, joy);
1328 process_key_press(false, key, WXJB_HAT_W, joy);
1329 break;
1330
1331 case WXJB_HAT_SW:
1332 process_key_press(ev.GetControlValue() != 0, key, WXJB_HAT_S, joy);
1333 process_key_press(ev.GetControlValue() != 0, key, WXJB_HAT_W, joy);
1334 process_key_press(false, key, WXJB_HAT_N, joy);
1335 process_key_press(false, key, WXJB_HAT_E, joy);
1336 break;
1337 }
1338 }
1339 }
1340 else
1341 process_key_press(ev.GetControlValue() != 0, key, mod, joy);
1342 }
1343
BEGIN_EVENT_TABLE(GameArea,wxPanel)1344 BEGIN_EVENT_TABLE(GameArea, wxPanel)
1345 EVT_IDLE(GameArea::OnIdle)
1346 EVT_SDLJOY(GameArea::OnSDLJoy)
1347 EVT_KEY_DOWN(GameArea::OnKeyDown)
1348 EVT_KEY_UP(GameArea::OnKeyUp)
1349 // FIXME: wxGTK does not generate motion events in MainFrame (not sure
1350 // what to do about it)
1351 EVT_MOUSE_EVENTS(GameArea::MouseEvent)
1352 END_EVENT_TABLE()
1353
1354 IMPLEMENT_ABSTRACT_CLASS(DrawingPanel, wxEvtHandler)
1355
1356 DrawingPanel::DrawingPanel(int _width, int _height) :
1357 wxObject(), width(_width), height(_height), scale(1), todraw(0),
1358 pixbuf1(0), pixbuf2(0), rpi(0), nthreads(0)
1359 {
1360 memset(delta, 0xff, sizeof(delta));
1361
1362 if (gopts.filter == FF_PLUGIN)
1363 {
1364 do // do { } while(0) so break; exits entire block
1365 {
1366 // could've also just used goto & a label, I guess
1367 gopts.filter = FF_NONE; // preemptive in case of errors
1368 systemColorDepth = 32;
1369
1370 if (gopts.filter_plugin.empty())
1371 break;
1372
1373 wxFileName fpn(gopts.filter_plugin);
1374 fpn.MakeAbsolute(wxStandardPaths::Get().GetPluginsDir());
1375
1376 if (!filt_plugin.Load(fpn.GetFullPath(), wxDL_VERBATIM | wxDL_NOW))
1377 break;
1378
1379 RENDPLUG_GetInfo gi = (RENDPLUG_GetInfo)filt_plugin.GetSymbol(wxT("RenderPluginGetInfo"));
1380
1381 if (!gi)
1382 break;
1383
1384 // need to be able to write to _rpi to set Output() and Flags
1385 RENDER_PLUGIN_INFO* _rpi = gi();
1386
1387 // FIXME: maybe < RPI_VERSION, assuming future vers. back compat?
1388 if (!_rpi || (_rpi->Flags & 0xff) != RPI_VERSION ||
1389 !(_rpi->Flags & (RPI_555_SUPP | RPI_888_SUPP)))
1390 break;
1391
1392 _rpi->Flags &= ~RPI_565_SUPP;
1393
1394 if (_rpi->Flags & RPI_888_SUPP)
1395 {
1396 _rpi->Flags &= ~RPI_555_SUPP;
1397 // FIXME: should this be 32 or 24? No docs or sample source
1398 systemColorDepth = 32;
1399 }
1400 else
1401 systemColorDepth = 16;
1402
1403 if (!_rpi->Output)
1404 // note that in actual kega fusion plugins, rpi->Output is
1405 // unused (as is rpi->Handle)
1406 _rpi->Output = (RENDPLUG_Output)filt_plugin.GetSymbol(wxT("RenderPluginOutput"));
1407
1408 scale = (_rpi->Flags & RPI_OUT_SCLMSK) >> RPI_OUT_SCLSH;
1409 rpi = _rpi;
1410 gopts.filter = FF_PLUGIN; // now that there is a valid plugin
1411 }
1412 while (0);
1413 }
1414 else
1415 {
1416 scale = builtin_ff_scale(gopts.filter);
1417 #define out_16 (systemColorDepth == 16)
1418 systemColorDepth = 32;
1419 }
1420
1421 // Intialize color tables
1422 if (systemColorDepth == 32)
1423 {
1424 #if wxBYTE_ORDER == wxLITTLE_ENDIAN
1425 systemRedShift = 3;
1426 systemGreenShift = 11;
1427 systemBlueShift = 19;
1428 RGB_LOW_BITS_MASK = 0x00010101;
1429 #else
1430 systemRedShift = 27;
1431 systemGreenShift = 19;
1432 systemBlueShift = 11;
1433 RGB_LOW_BITS_MASK = 0x01010100;
1434 #endif
1435 }
1436 else
1437 {
1438 // plugins expect RGB in native byte order
1439 systemRedShift = 10;
1440 systemGreenShift = 5;
1441 systemBlueShift = 0;
1442 RGB_LOW_BITS_MASK = 0x0421;
1443 }
1444
1445 // FIXME: should be "true" for GBA carts if lcd mode selected
1446 // which means this needs to be re-run at pref change time
1447 utilUpdateSystemColorMaps(false);
1448 }
1449
PaintEv(wxPaintEvent & ev)1450 void DrawingPanel::PaintEv(wxPaintEvent &ev)
1451 {
1452 wxPaintDC dc(GetWindow());
1453
1454 if (!todraw)
1455 {
1456 // since this is set for custom background, not drawing anything
1457 // will cause garbage to be displayed, so draw a black area
1458 wxCoord w, h;
1459 dc.GetSize(&w, &h);
1460 dc.SetPen(*wxBLACK_PEN);
1461 dc.SetBrush(*wxBLACK_BRUSH);
1462 dc.DrawRectangle(0, 0, w, h);
1463 return;
1464 }
1465
1466 DrawArea(dc);
1467 DrawOSD(dc);
1468 }
1469
1470 // In order to run filters in parallel, they have to run from the method of
1471 // a wxThread-derived class
1472 // threads are only created once, if possible, to avoid thread creation
1473 // overhead every frame; mutex+cond used to signal start and sem for end
1474
1475 // when threading, there _will_ be bands for any nontrivial filter
1476 // usually the top and bottom line(s) of each region will look a little
1477 // different. To ease this a tiny bit, 2 extra lines are generated at
1478 // the top of each region, so hopefully only the bottom of the previous
1479 // region will look screwed up. The only correct way to handle this
1480 // is to draw an increasingly larger band around the seam until the
1481 // seam cover matches a line in both top & bottom regions, and then apply
1482 // cover to seam. Way too much trouble for this, though.
1483
1484 // another issue to consider is whether or not these filters are thread-
1485 // safe to begin with. The built-in ones are verifyable (I didn't verify
1486 // them, though). The plugins cannot be verified. However, unlike the MFC
1487 // interface, I will allow them to be threaded at user's discretion.
1488 class FilterThread : public wxThread
1489 {
1490 public:
FilterThread()1491 FilterThread() : wxThread(wxTHREAD_JOINABLE), lock(), sig(lock) {}
1492
1493 wxMutex lock;
1494 wxCondition sig;
1495 wxSemaphore* done;
1496
1497 // Set these params before running
1498 int nthreads, threadno;
1499 int width, height, scale;
1500 const RENDER_PLUGIN_INFO* rpi;
1501 u8* dst, *delta;
1502
1503 // set this param every round
1504 // if NULL, end thread
1505 u8* src;
1506
Entry()1507 ExitCode Entry()
1508 {
1509 // This is the band this thread will process
1510 // threadno == -1 means just do a dummy round on the border line
1511 int procy = height * threadno / nthreads;
1512 height = height * (threadno + 1) / nthreads - procy;
1513 int inbpp = systemColorDepth >> 3;
1514 int inrb = systemColorDepth == 16 ? 2 : systemColorDepth == 24 ? 0 : 1;
1515 int instride = (width + inrb) * inbpp;
1516 int outbpp = out_16 ? 2 : systemColorDepth == 24 ? 3 : 4;
1517 int outrb = systemColorDepth == 24 ? 0 : 4;
1518 int outstride = width * outbpp * scale + outrb;
1519 delta += instride * procy;
1520 // + 1 for stupid top border
1521 dst += outstride * (procy + 1) * scale;
1522
1523 while (nthreads == 1 || sig.Wait() == wxCOND_NO_ERROR)
1524 {
1525 if (!src /* && nthreads > 1 */)
1526 {
1527 lock.Unlock();
1528 return 0;
1529 }
1530
1531 // + 1 for stupid top border
1532 src += instride;
1533
1534 // interframe blending filter
1535 // definitely not thread safe by default
1536 // added procy param to provide offset into accum buffers
1537 if (gopts.ifb != IFB_NONE)
1538 {
1539 switch (gopts.ifb)
1540 {
1541 case IFB_SMART:
1542 if (systemColorDepth == 16)
1543 SmartIB(src, instride, width, procy, height);
1544 else
1545 SmartIB32(src, instride, width, procy, height);
1546
1547 break;
1548
1549 case IFB_MOTION_BLUR:
1550
1551 // FIXME: if(renderer == d3d/gl && filter == NONE) break;
1552 if (systemColorDepth == 16)
1553 MotionBlurIB(src, instride, width, procy, height);
1554 else
1555 MotionBlurIB32(src, instride, width, procy, height);
1556
1557 break;
1558 }
1559 }
1560
1561 if (gopts.filter == FF_NONE)
1562 {
1563 if (nthreads == 1)
1564 return 0;
1565
1566 done->Post();
1567 continue;
1568 }
1569
1570 src += instride * procy;
1571
1572 // naturally, any of these with accumulation buffers like those of
1573 // the IFB filters will screw up royally as well
1574 switch (gopts.filter)
1575 {
1576 case FF_2XSAI:
1577 _2xSaI32(src, instride, delta, dst, outstride, width, height);
1578 break;
1579
1580 case FF_SUPER2XSAI:
1581 Super2xSaI32(src, instride, delta, dst, outstride, width, height);
1582 break;
1583
1584 case FF_SUPEREAGLE:
1585 SuperEagle32(src, instride, delta, dst, outstride, width, height);
1586 break;
1587
1588 case FF_PIXELATE:
1589 Pixelate32(src, instride, delta, dst, outstride, width, height);
1590 break;
1591
1592 case FF_ADVMAME:
1593 AdMame2x32(src, instride, delta, dst, outstride, width, height);
1594 break;
1595
1596 case FF_BILINEAR:
1597 Bilinear32(src, instride, delta, dst, outstride, width, height);
1598 break;
1599
1600 case FF_BILINEARPLUS:
1601 BilinearPlus32(src, instride, delta, dst, outstride, width, height);
1602 break;
1603
1604 case FF_SCANLINES:
1605 Scanlines32(src, instride, delta, dst, outstride, width, height);
1606 break;
1607
1608 case FF_TV:
1609 ScanlinesTV32(src, instride, delta, dst, outstride, width, height);
1610 break;
1611
1612 case FF_LQ2X:
1613 lq2x32(src, instride, delta, dst, outstride, width, height);
1614 break;
1615
1616 case FF_SIMPLE2X:
1617 Simple2x32(src, instride, delta, dst, outstride, width, height);
1618 break;
1619
1620 case FF_SIMPLE3X:
1621 Simple3x32(src, instride, delta, dst, outstride, width, height);
1622 break;
1623
1624 case FF_SIMPLE4X:
1625 Simple4x32(src, instride, delta, dst, outstride, width, height);
1626 break;
1627
1628 case FF_HQ2X:
1629 hq2x32(src, instride, delta, dst, outstride, width, height);
1630 break;
1631
1632 case FF_HQ3X:
1633 hq3x32_32(src, instride, delta, dst, outstride, width, height);
1634 break;
1635
1636 case FF_HQ4X:
1637 hq4x32_32(src, instride, delta, dst, outstride, width, height);
1638 break;
1639
1640 case FF_XBRZ2X:
1641 xbrz2x32(src, instride, delta, dst, outstride, width, height);
1642 break;
1643
1644 case FF_XBRZ3X:
1645 xbrz3x32(src, instride, delta, dst, outstride, width, height);
1646 break;
1647
1648 case FF_XBRZ4X:
1649 xbrz4x32(src, instride, delta, dst, outstride, width, height);
1650 break;
1651
1652 case FF_XBRZ5X:
1653 xbrz5x32(src, instride, delta, dst, outstride, width, height);
1654 break;
1655
1656 case FF_XBRZ6X:
1657 xbrz6x32(src, instride, delta, dst, outstride, width, height);
1658 break;
1659
1660 case FF_PLUGIN:
1661 // MFC interface did not do plugins in parallel
1662 // Probably because it's almost certain they carry state or do
1663 // other non-thread-safe things
1664 // But the user can always turn mt off of it's not working..
1665 RENDER_PLUGIN_OUTP outdesc;
1666 outdesc.Size = sizeof(outdesc);
1667 outdesc.Flags = rpi->Flags;
1668 outdesc.SrcPtr = src;
1669 outdesc.SrcPitch = instride;
1670 outdesc.SrcW = width;
1671 // FIXME: win32 code adds to H, saying that frame isn't fully
1672 // rendered otherwise
1673 // I need to verify that statement before I go adding stuff that
1674 // may make it crash.
1675 outdesc.SrcH = height; // + scale / 2
1676 outdesc.DstPtr = dst;
1677 outdesc.DstPitch = outstride;
1678 outdesc.DstW = width * scale;
1679 // on the other hand, there is at least 1 line below, so I'll add
1680 // that to dest in case safety checks in plugin use < instead of <=
1681 outdesc.DstH = height * scale + 1; // + scale * (scale / 2)
1682 rpi->Output(&outdesc);
1683 break;
1684
1685 default:
1686 break;
1687 }
1688
1689 if (nthreads == 1)
1690 return 0;
1691
1692 done->Post();
1693 continue;
1694 }
1695 }
1696 };
1697
DrawArea(u8 ** data)1698 void DrawingPanel::DrawArea(u8** data)
1699 {
1700 // double-buffer buffer:
1701 // if filtering, this is filter output, retained for redraws
1702 // if not filtering, we still retain current image for redraws
1703 int outbpp = out_16 ? 2 : systemColorDepth == 24 ? 3 : 4;
1704 int outrb = systemColorDepth == 24 ? 0 : 4;
1705 int outstride = width * outbpp * scale + outrb;
1706
1707 if (!pixbuf2)
1708 {
1709 int allocstride = outstride, alloch = height;
1710
1711 // gb may write borders, so allocate enough for them
1712 if (width == GameArea::GBWidth && height == GameArea::GBHeight)
1713 {
1714 allocstride = GameArea::SGBWidth * outbpp * scale + outrb;
1715 alloch = GameArea::SGBHeight;
1716 }
1717
1718 pixbuf2 = (u8*)calloc(allocstride, (alloch + 2) * scale);
1719 }
1720
1721 if (gopts.filter == FF_NONE)
1722 {
1723 todraw = *data;
1724 // *data is assigned below, after old buf has been processed
1725 pixbuf1 = pixbuf2;
1726 pixbuf2 = todraw;
1727 }
1728 else
1729 todraw = pixbuf2;
1730
1731 // FIXME: filters race condition?
1732 gopts.max_threads = 1;
1733
1734 // First, apply filters, if applicable, in parallel, if enabled
1735 if (gopts.filter != FF_NONE ||
1736 gopts.ifb != FF_NONE /* FIXME: && (gopts.ifb != FF_MOTION_BLUR || !renderer_can_motion_blur) */)
1737 {
1738 if (nthreads != gopts.max_threads)
1739 {
1740 if (nthreads)
1741 {
1742 if (nthreads > 1)
1743 for (int i = 0; i < nthreads; i++)
1744 {
1745 threads[i].lock.Lock();
1746 threads[i].src = NULL;
1747 threads[i].sig.Signal();
1748 threads[i].lock.Unlock();
1749 threads[i].Wait();
1750 }
1751
1752 delete[] threads;
1753 }
1754
1755 nthreads = gopts.max_threads;
1756 threads = new FilterThread[nthreads];
1757 // first time around, no threading in order to avoid
1758 // static initializer conflicts
1759 threads[0].threadno = 0;
1760 threads[0].nthreads = 1;
1761 threads[0].width = width;
1762 threads[0].height = height;
1763 threads[0].scale = scale;
1764 threads[0].src = *data;
1765 threads[0].dst = todraw;
1766 threads[0].delta = delta;
1767 threads[0].rpi = rpi;
1768 threads[0].Entry();
1769
1770 // go ahead and start the threads up, though
1771 if (nthreads > 1)
1772 {
1773 for (int i = 0; i < nthreads; i++)
1774 {
1775 threads[i].threadno = i;
1776 threads[i].nthreads = nthreads;
1777 threads[i].width = width;
1778 threads[i].height = height;
1779 threads[i].scale = scale;
1780 threads[i].dst = todraw;
1781 threads[i].delta = delta;
1782 threads[i].rpi = rpi;
1783 threads[i].done = &filt_done;
1784 threads[i].lock.Lock();
1785 threads[i].Create();
1786 threads[i].Run();
1787 }
1788 }
1789 }
1790 else if (nthreads == 1)
1791 {
1792 threads[0].threadno = 0;
1793 threads[0].nthreads = 1;
1794 threads[0].width = width;
1795 threads[0].height = height;
1796 threads[0].scale = scale;
1797 threads[0].src = *data;
1798 threads[0].dst = todraw;
1799 threads[0].delta = delta;
1800 threads[0].rpi = rpi;
1801 threads[0].Entry();
1802 }
1803 else
1804 {
1805 for (int i = 0; i < nthreads; i++)
1806 {
1807 threads[i].lock.Lock();
1808 threads[i].src = *data;
1809 threads[i].sig.Signal();
1810 threads[i].lock.Unlock();
1811 }
1812
1813 for (int i = 0; i < nthreads; i++)
1814 filt_done.Wait();
1815 }
1816 }
1817
1818 // swap buffers now that src has been processed
1819 if (gopts.filter == FF_NONE)
1820 *data = pixbuf1;
1821
1822 // draw OSD text old-style (directly into output buffer), if needed
1823 // new style flickers too much, so we'll stick to this for now
1824 if (wxGetApp().frame->IsFullScreen() || !gopts.statusbar)
1825 {
1826 GameArea* panel = wxGetApp().frame->GetPanel();
1827
1828 if (panel->osdstat.size())
1829 drawText(todraw + outstride * (systemColorDepth != 24), outstride,
1830 10, 20, panel->osdstat.utf8_str(), showSpeedTransparent);
1831
1832 if (!disableStatusMessages && !panel->osdtext.empty())
1833 {
1834 if (systemGetClock() - panel->osdtime < OSD_TIME)
1835 {
1836 std::string message = ToString(panel->osdtext);
1837 int linelen = (width * scale - 20) / 8;
1838 int nlines = (message.length() + linelen - 1) / linelen;
1839 int cury = height - 14 - nlines * 10;
1840 char* ptr = const_cast<char*>(message.c_str());
1841
1842 while (nlines > 1)
1843 {
1844 char lchar = ptr[linelen];
1845 ptr[linelen] = 0;
1846 drawText(todraw + outstride * (systemColorDepth != 24),
1847 outstride, 10, cury, ptr,
1848 showSpeedTransparent);
1849 cury += 10;
1850 nlines--;
1851 ptr += linelen;
1852 *ptr = lchar;
1853 }
1854
1855 drawText(todraw + outstride * (systemColorDepth != 24),
1856 outstride, 10, cury, ptr,
1857 showSpeedTransparent);
1858 }
1859 else
1860 panel->osdtext.clear();
1861 }
1862 }
1863
1864 // next, draw the game
1865 wxClientDC dc(GetWindow());
1866 DrawArea(dc);
1867
1868 // finally, draw on-screen text using wx method, if possible
1869 // this method flickers too much right now
1870 if (0)
1871 DrawOSD(dc);
1872 }
1873
DrawOSD(wxWindowDC & dc)1874 void DrawingPanel::DrawOSD(wxWindowDC &dc)
1875 {
1876 // draw OSD message, if available
1877 // doing this here rather than using drawText() directly into the screen
1878 // image gives us 2 advantages:
1879 // - non-ASCII text in messages
1880 // - message is always default font size, regardless of screen stretch
1881 // and one known disadvantage:
1882 // - 3d renderers may overwrite text asynchronously
1883 // Until I go through the trouble of making this render to a bitmap, and
1884 // then using the bitmap as a texture for the 3d renderers or rendering
1885 // directly into the output like DrawText, this is only enabled for
1886 // non-3d renderers.
1887 GameArea* panel = wxGetApp().frame->GetPanel();
1888 dc.SetTextForeground(wxColour(255, 0, 0, showSpeedTransparent ? 128 : 255));
1889 dc.SetTextBackground(wxColour(0, 0, 0, 0));
1890 dc.SetUserScale(1.0, 1.0);
1891
1892 if (panel->osdstat.size())
1893 dc.DrawText(panel->osdstat, 10, 20);
1894
1895 if (!panel->osdtext.empty())
1896 {
1897 if (systemGetClock() - panel->osdtime >= OSD_TIME)
1898 {
1899 panel->osdtext.clear();
1900 wxGetApp().frame->PopStatusText();
1901 }
1902 }
1903
1904 if (!disableStatusMessages && !panel->osdtext.empty())
1905 {
1906 wxSize asz = dc.GetSize();
1907 wxString msg = panel->osdtext;
1908 int lw, lh;
1909 int mlw = asz.GetWidth() - 20;
1910 dc.GetTextExtent(msg, &lw, &lh);
1911
1912 if (lw <= mlw)
1913 dc.DrawText(msg, 10, asz.GetHeight() - 16 - lh);
1914 else
1915 {
1916 // Since font is likely proportional, the only way to
1917 // find amt of text that will fit on a line is to search
1918 wxArrayInt llen; // length of each line, in chars
1919
1920 for (int off = 0; off < msg.size();)
1921 {
1922 // One way would be to bsearch on GetTextExtent() looking for
1923 // best fit.
1924 // Another would be to use GetPartialTextExtents and search
1925 // the resulting array.
1926 // GetPartialTextExtents may be inaccurate, but calling it
1927 // once per line may be more efficient than calling
1928 // GetTextExtent log2(remaining_len) times per line.
1929 #if 1
1930 wxArrayInt clen;
1931 dc.GetPartialTextExtents(msg.substr(off), clen);
1932 #endif
1933 int lenl = 0, lenh = msg.size() - off - 1, len;
1934
1935 do
1936 {
1937 len = (lenl + lenh) / 2;
1938 #if 1
1939 lw = clen[len];
1940 #else
1941 dc.GetTextExtent(msg.substr(off, len), &lw, NULL);
1942 #endif
1943
1944 if (lw > mlw)
1945 lenh = len - 1;
1946 else if (lw < mlw)
1947 lenl = len + 1;
1948 else
1949 break;
1950 }
1951 while (lenh >= lenl);
1952
1953 if (lw <= mlw)
1954 len++;
1955
1956 off += len;
1957 llen.push_back(len);
1958 }
1959
1960 int nlines = llen.size();
1961 int cury = asz.GetHeight() - 14 - nlines * (lh + 2);
1962
1963 for (int off = 0, line = 0; line < nlines; off += llen[line], line++, cury += lh + 2)
1964 dc.DrawText(msg.substr(off, llen[line]), 10, cury);
1965 }
1966 }
1967 }
1968
~DrawingPanel()1969 DrawingPanel::~DrawingPanel()
1970 {
1971 // pixbuf1 freed by emulator
1972 if (pixbuf2)
1973 free(pixbuf2);
1974
1975 InterframeCleanup();
1976
1977 if (nthreads)
1978 {
1979 if (nthreads > 1)
1980 for (int i = 0; i < nthreads; i++)
1981 {
1982 threads[i].lock.Lock();
1983 threads[i].src = NULL;
1984 threads[i].sig.Signal();
1985 threads[i].lock.Unlock();
1986 threads[i].Wait();
1987 }
1988
1989 delete[] threads;
1990 }
1991 }
1992
IMPLEMENT_CLASS2(BasicDrawingPanel,DrawingPanel,wxPanel)1993 IMPLEMENT_CLASS2(BasicDrawingPanel, DrawingPanel, wxPanel)
1994
1995 BEGIN_EVENT_TABLE(BasicDrawingPanel, wxPanel)
1996 EVT_PAINT(BasicDrawingPanel::PaintEv2)
1997 END_EVENT_TABLE()
1998
1999 BasicDrawingPanel::BasicDrawingPanel(wxWindow* parent, int _width, int _height)
2000 : wxPanel(parent, wxID_ANY, wxPoint(0, 0), parent->GetSize(),
2001 wxFULL_REPAINT_ON_RESIZE), DrawingPanel(_width, _height)
2002 {
2003 // wxImage is 24-bit RGB, so 24-bit is preferred. Filters require
2004 // 16 or 32, though
2005 if (gopts.filter == FF_NONE && gopts.ifb == IFB_NONE)
2006 // changing from 32 to 24 does not require regenerating color tables
2007 systemColorDepth = 24;
2008 }
2009
DrawArea(wxWindowDC & dc)2010 void BasicDrawingPanel::DrawArea(wxWindowDC &dc)
2011 {
2012 wxBitmap* bm;
2013
2014 if (systemColorDepth == 24)
2015 {
2016 // never scaled, no borders, no transformations needed
2017 wxImage im(width, height, todraw, true);
2018 bm = new wxBitmap(im);
2019 }
2020 else if (out_16)
2021 {
2022 // scaled by filters, top/right borders, transform to 24-bit
2023 wxImage im(width * scale, height * scale, false);
2024 u16* src = (u16*)todraw + (width + 2) * scale; // skip top border
2025 u8* dst = im.GetData();
2026
2027 for (int y = 0; y < height * scale; y++)
2028 {
2029 for (int x = 0; x < width * scale; x++, src++)
2030 {
2031 *dst++ = ((*src >> systemRedShift) & 0x1f) << 3;
2032 *dst++ = ((*src >> systemGreenShift) & 0x1f) << 3;
2033 *dst++ = ((*src >> systemBlueShift) & 0x1f) << 3;
2034 }
2035
2036 src += 2; // skip rhs border
2037 }
2038
2039 bm = new wxBitmap(im);
2040 }
2041 else // 32-bit
2042 {
2043 // scaled by filters, top/right borders, transform to 24-bit
2044 wxImage im(width * scale, height * scale, false);
2045 u32* src = (u32*)todraw + (width + 1) * scale; // skip top border
2046 u8* dst = im.GetData();
2047
2048 for (int y = 0; y < height * scale; y++)
2049 {
2050 for (int x = 0; x < width * scale; x++, src++)
2051 {
2052 *dst++ = *src >> (systemRedShift - 3);
2053 *dst++ = *src >> (systemGreenShift - 3);
2054 *dst++ = *src >> (systemBlueShift - 3);
2055 }
2056
2057 ++src; // skip rhs border
2058 }
2059
2060 bm = new wxBitmap(im);
2061 }
2062
2063 double sx, sy;
2064 int w, h;
2065 GetClientSize(&w, &h);
2066 sx = (double)w / (double)(width * scale);
2067 sy = (double)h / (double)(height * scale);
2068 dc.SetUserScale(sx, sy);
2069 dc.DrawBitmap(*bm, 0, 0);
2070 delete bm;
2071 }
2072
2073 #ifndef NO_OGL
2074 // following 3 for vsync
2075 #ifdef __WXMAC__
2076 #include <OpenGL/OpenGL.h>
2077 #endif
2078 #ifdef __WXGTK__ // should actually check for X11, but GTK implies X11
2079 #include <GL/glx.h>
2080 #endif
2081 #ifdef __WXMSW__
2082 #include <GL/glext.h>
2083 #endif
2084
2085 IMPLEMENT_CLASS2(GLDrawingPanel, DrawingPanel, wxGLCanvas)
2086
2087 // this would be easier in 2.9
2088 BEGIN_EVENT_TABLE(GLDrawingPanel, wxGLCanvas)
2089 EVT_PAINT(GLDrawingPanel::PaintEv2)
2090 EVT_SIZE(GLDrawingPanel::OnSize)
2091 END_EVENT_TABLE()
2092
2093 // This is supposed to be the default, but DOUBLEBUFFER doesn't seem to be
2094 // turned on by default for wxGTK.
2095 static int glopts[] =
2096 {
2097 WX_GL_RGBA, WX_GL_DOUBLEBUFFER, 0
2098 };
2099
2100 #if wxCHECK_VERSION(2,9,0) || !defined(__WXMAC__)
2101 #define glc wxGLCanvas
2102 #else
2103 // shuffled parms for 2.9 indicates non-auto glcontext
2104 // before 2.9, wxMAC does not have this (but wxGTK & wxMSW do)
2105 #define glc(a,b,c,d,e,f) wxGLCanvas(a, b, d, e, f, wxEmptyString, c)
2106 #endif
2107
GLDrawingPanel(wxWindow * parent,int _width,int _height)2108 GLDrawingPanel::GLDrawingPanel(wxWindow* parent, int _width, int _height) :
2109 glc(parent, wxID_ANY, glopts, wxPoint(0, 0), parent->GetSize(),
2110 wxFULL_REPAINT_ON_RESIZE), DrawingPanel(_width, _height),
2111 did_init(false)
2112 #if wxCHECK_VERSION(2,9,0) || !defined(__WXMAC__)
2113 , ctx(this)
2114 #endif
2115 {
2116 }
2117
~GLDrawingPanel()2118 GLDrawingPanel::~GLDrawingPanel()
2119 {
2120 #if 0
2121
2122 // this should be automatically deleted w/ context
2123 // it's also unsafe if panel no longer displayed
2124 if (did_init)
2125 {
2126 #if wxCHECK_VERSION(2,9,0) || !defined(__WXMAC__)
2127 SetContext(ctx);
2128 #else
2129 SetContext();
2130 #endif
2131 glDeleteLists(vlist, 1);
2132 glDeleteTextures(1, &texid);
2133 }
2134
2135 #endif
2136 }
2137
Init()2138 void GLDrawingPanel::Init()
2139 {
2140 // taken from GTK front end almost verbatim
2141 glDisable(GL_CULL_FACE);
2142 glEnable(GL_TEXTURE_2D);
2143 glMatrixMode(GL_PROJECTION);
2144 glLoadIdentity();
2145 glOrtho(0.0, 1.0, 1.0, 0.0, 0.0, 1.0);
2146 glMatrixMode(GL_MODELVIEW);
2147 glLoadIdentity();
2148 vlist = glGenLists(1);
2149 glNewList(vlist, GL_COMPILE);
2150 glBegin(GL_TRIANGLE_STRIP);
2151 glTexCoord2f(0.0, 0.0);
2152 glVertex3i(0, 0, 0);
2153 glTexCoord2f(1.0, 0.0);
2154 glVertex3i(1, 0, 0);
2155 glTexCoord2f(0.0, 1.0);
2156 glVertex3i(0, 1, 0);
2157 glTexCoord2f(1.0, 1.0);
2158 glVertex3i(1, 1, 0);
2159 glEnd();
2160 glEndList();
2161 glGenTextures(1, &texid);
2162 glBindTexture(GL_TEXTURE_2D, texid);
2163 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
2164 gopts.bilinear ? GL_LINEAR : GL_NEAREST);
2165 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
2166 gopts.bilinear ? GL_LINEAR : GL_NEAREST);
2167 #define int_fmt out_16 ? GL_RGB5 : GL_RGB
2168 #define tex_fmt out_16 ? GL_BGRA : GL_RGBA, \
2169 out_16 ? GL_UNSIGNED_SHORT_1_5_5_5_REV : GL_UNSIGNED_BYTE
2170 #if 0
2171 texsize = width > height ? width : height;
2172 texsize *= scale;
2173 // texsize = 1 << ffs(texsize);
2174 texsize = texsize | (texsize >> 1);
2175 texsize = texsize | (texsize >> 2);
2176 texsize = texsize | (texsize >> 4);
2177 texsize = texsize | (texsize >> 8);
2178 texsize = (texsize >> 1) + 1;
2179 glTexImage2D(GL_TEXTURE_2D, 0, int_fmt, texsize, texsize, 0, tex_fmt, NULL);
2180 #else
2181 // but really, most cards support non-p2 and rect
2182 // if not, use cairo or wx renderer
2183 glTexImage2D(GL_TEXTURE_2D, 0, int_fmt, width * scale, height * scale, 0, tex_fmt, NULL);
2184 #endif
2185 glClearColor(0.0, 0.0, 0.0, 1.0);
2186 // non-portable vsync code
2187 #if defined(__WXGTK__) && defined(GLX_SGI_swap_control)
2188 static PFNGLXSWAPINTERVALSGIPROC si = NULL;
2189
2190 if (!si)
2191 si = (PFNGLXSWAPINTERVALSGIPROC)glXGetProcAddress((const GLubyte*)"glxSwapIntervalSGI");
2192
2193 if (si)
2194 si(vsync);
2195
2196 #else
2197 #if defined(__WXMSW__) && defined(WGL_EXT_swap_control)
2198 static PFNWGLSWAPINTERVALEXTPROC si = NULL;
2199
2200 if (!si)
2201 si = (PFNWGLSWAPINTERVALEXTPROC)wglGetProcAddress("wglSwapIntervalEXT");
2202
2203 if (si)
2204 si(vsync);
2205
2206 #else
2207 #ifdef __WXMAC__
2208 int swap_interval = vsync ? 1 : 0;
2209 CGLContextObj cgl_context = CGLGetCurrentContext();
2210 CGLSetParameter(cgl_context, kCGLCPSwapInterval, &swap_interval);
2211 #else
2212 //#warning no vsync support on this platform
2213 #endif
2214 #endif
2215 #endif
2216 did_init = true;
2217 }
2218
DrawArea(wxWindowDC & dc)2219 void GLDrawingPanel::DrawArea(wxWindowDC &dc)
2220 {
2221 #if wxCHECK_VERSION(2,9,0) || !defined(__WXMAC__)
2222 SetCurrent(ctx);
2223 #else
2224 SetCurrent();
2225 #endif
2226
2227 if (!did_init)
2228 Init();
2229
2230 if (todraw)
2231 {
2232 int rowlen = width * scale + (out_16 ? 2 : 1);
2233 glPixelStorei(GL_UNPACK_ROW_LENGTH, rowlen);
2234 #if wxBYTE_ORDER == wxBIG_ENDIAN
2235
2236 // FIXME: is this necessary?
2237 if (out_16)
2238 glPixelStorei(GL_UNPACK_SWAP_BYTES, GL_TRUE);
2239
2240 #endif
2241 glTexImage2D(GL_TEXTURE_2D, 0, int_fmt, width * scale, height * scale,
2242 0, tex_fmt, todraw + rowlen * (out_16 ? 2 : 4) * scale);
2243 glCallList(vlist);
2244 }
2245 else
2246 glClear(GL_COLOR_BUFFER_BIT);
2247
2248 SwapBuffers();
2249 }
2250
OnSize(wxSizeEvent & ev)2251 void GLDrawingPanel::OnSize(wxSizeEvent &ev)
2252 {
2253 if (!did_init)
2254 return;
2255
2256 int w, h;
2257 GetClientSize(&w, &h);
2258 #if wxCHECK_VERSION(2,9,0) || !defined(__WXMAC__)
2259 SetCurrent(ctx);
2260 #else
2261 SetCurrent();
2262 #endif
2263 glViewport(0, 0, w, h);
2264 ev.Skip(); // propagate to parent
2265 }
2266 #endif
2267
2268 #ifndef NO_CAIRO
2269
IMPLEMENT_CLASS(CairoDrawingPanel,DrawingPanel)2270 IMPLEMENT_CLASS(CairoDrawingPanel, DrawingPanel)
2271
2272 BEGIN_EVENT_TABLE(CairoDrawingPanel, wxPanel)
2273 EVT_PAINT(CairoDrawingPanel::PaintEv2)
2274 END_EVENT_TABLE()
2275
2276 CairoDrawingPanel::CairoDrawingPanel(wxWindow* parent, int _width, int _height)
2277 : wxPanel(parent, wxID_ANY, wxPoint(0, 0), parent->GetSize(),
2278 wxFULL_REPAINT_ON_RESIZE), DrawingPanel(_width, _height)
2279 {
2280 conv_surf = NULL;
2281
2282 // Intialize color tables in reverse order from default
2283 // probably doesn't help mmx hq3x/hq4x
2284 if (systemColorDepth == 32)
2285 {
2286 #if wxBYTE_ORDER == wxLITTLE_ENDIAN
2287 systemBlueShift = 3;
2288 systemRedShift = 19;
2289 #else
2290 systemBlueShift = 27;
2291 systemRedShift = 11;
2292 #endif
2293 }
2294
2295 // FIXME: should be "true" for GBA carts if lcd mode selected
2296 utilUpdateSystemColorMaps(false);
2297 }
2298
~CairoDrawingPanel()2299 CairoDrawingPanel::~CairoDrawingPanel()
2300 {
2301 if (conv_surf)
2302 cairo_surface_destroy(conv_surf);
2303 }
2304
2305 #include <wx/graphics.h>
2306 #ifdef __WXMSW__
2307 #include <cairo-win32.h>
2308 #include <gdiplus.h>
2309 #endif
2310 #if defined(__WXMAC__) && wxMAC_USE_CORE_GRAPHICS
2311 #include <cairo-quartz.h>
2312 #endif
2313
DrawArea(wxWindowDC & dc)2314 void CairoDrawingPanel::DrawArea(wxWindowDC &dc)
2315 {
2316 cairo_t* cr;
2317 wxGraphicsContext* gc = wxGraphicsContext::Create(dc);
2318 #ifdef __WXMSW__
2319 // not sure why this is so slow
2320 // doing this only once in constructor and resize handler doesn't seem
2321 // to help, and may be unsafe
2322 Gdiplus::Graphics* gr = (Gdiplus::Graphics*)gc->GetNativeContext();
2323 cairo_surface_t* s = cairo_win32_surface_create(gr->GetHDC());
2324 cr = cairo_create(s);
2325 cairo_surface_destroy(s);
2326 #else
2327 #ifdef __WXGTK__
2328 cr = cairo_reference((cairo_t*)gc->GetNativeContext());
2329 #else
2330 #if defined(__WXMAC__) && wxMAC_USE_CORE_GRAPHICS
2331 CGContextRef c = static_cast<CGContextRef>(gc->GetNativeContext());
2332 cairo_surface_t* s = cairo_quartz_surface_create_for_cg_context(c, width, height);
2333 cr = cairo_create(s);
2334 cairo_surface_destroy(s);
2335 #else
2336 #error Cairo rendering is not supported on this platform
2337 #endif
2338 #endif
2339 #endif
2340 cairo_surface_t* surf;
2341
2342 if (!out_16)
2343 surf = cairo_image_surface_create_for_data(todraw + 4 * width,
2344 CAIRO_FORMAT_RGB24,
2345 width, height,
2346 4 * (width + 1));
2347 else
2348 {
2349 if (!conv_surf)
2350 conv_surf = cairo_image_surface_create(CAIRO_FORMAT_RGB24,
2351 width * scale,
2352 height * scale);
2353
2354 if (!conv_surf)
2355 {
2356 wxLogError(_("Cannot create conversion buffer"));
2357 wxGetApp().frame->Close(true);
2358 }
2359
2360 surf = cairo_surface_reference(conv_surf);
2361 u16* src = (u16*)todraw + (width + 2) * scale; // skip top border
2362 u32* dst = (u32*)cairo_image_surface_get_data(surf);
2363
2364 for (int y = 0; y < height * scale; y++)
2365 {
2366 for (int x = 0; x < width * scale; x++, src++)
2367 {
2368 *dst++ = (((*src >> systemRedShift) & 0x1f) << 19) |
2369 (((*src >> systemGreenShift) & 0x1f) << 11) |
2370 (((*src >> systemBlueShift) & 0x1f) << 3);
2371 }
2372
2373 src += 2; // skip rhs border
2374 }
2375 }
2376
2377 cairo_pattern_t* pat = cairo_pattern_create_for_surface(surf);
2378 // GOOD is "similar to" bilinear, and FAST is "similar to" nearest
2379 // could also just use BILINEAR and NEAREST directly, I suppose
2380 cairo_pattern_set_filter(pat, gopts.bilinear ? CAIRO_FILTER_GOOD : CAIRO_FILTER_FAST);
2381 double sx, sy;
2382 int w, h;
2383 GetClientSize(&w, &h);
2384 sx = (double)width / (double)w;
2385 sy = (double)height / (double)h;
2386 cairo_matrix_t mat;
2387 cairo_matrix_init_scale(&mat, sx, sy);
2388 cairo_pattern_set_matrix(pat, &mat);
2389 cairo_set_source(cr, pat);
2390 cairo_paint(cr);
2391 cairo_pattern_destroy(pat);
2392 cairo_surface_destroy(surf);
2393 cairo_destroy(cr);
2394 delete gc;
2395 }
2396 #endif
2397
2398 #if defined(__WXMSW__) && !defined(NO_D3D)
2399 #define DIRECT3D_VERSION 0x0900
2400 #include <d3d9.h>
2401 //#include <Dxerr.h>
2402
IMPLEMENT_CLASS(DXDrawingPanel,DrawingPanel)2403 IMPLEMENT_CLASS(DXDrawingPanel, DrawingPanel)
2404
2405 BEGIN_EVENT_TABLE(DXDrawingPanel, wxPanel)
2406 EVT_PAINT(DXDrawingPanel::PaintEv2)
2407 END_EVENT_TABLE()
2408
2409 DXDrawingPanel::DXDrawingPanel(wxWindow* parent, int _width, int _height)
2410 : wxPanel(parent, wxID_ANY, wxPoint(0, 0), parent->GetSize(),
2411 wxFULL_REPAINT_ON_RESIZE), DrawingPanel(_width, _height)
2412 {
2413 // FIXME: implement
2414 }
2415
DrawArea(wxWindowDC & dc)2416 void DXDrawingPanel::DrawArea(wxWindowDC &dc)
2417 {
2418 // FIXME: implement
2419 }
2420 #endif
2421
2422 #ifndef NO_FFMPEG
media_err(MediaRet ret)2423 static const wxChar* media_err(MediaRet ret)
2424 {
2425 switch (ret)
2426 {
2427 case MRET_OK:
2428 return wxT("");
2429
2430 case MRET_ERR_NOMEM:
2431 return _("memory allocation error");
2432
2433 case MRET_ERR_NOCODEC:
2434 return _("error initializing codec");
2435
2436 case MRET_ERR_FERR:
2437 return _("error writing to output file");
2438
2439 case MRET_ERR_FMTGUESS:
2440 return _("can't guess output format from file name");
2441
2442 default:
2443 // case MRET_ERR_RECORDING:
2444 // case MRET_ERR_BUFSIZE:
2445 return _("programming error; aborting!");
2446 }
2447 }
2448
StartVidRecording(const wxString & fname)2449 void GameArea::StartVidRecording(const wxString &fname)
2450 {
2451 // auto-conversion of wxCharBuffer to const char * seems broken
2452 // so save underlying wxCharBuffer (or create one of none is used)
2453 wxCharBuffer fnb(fname.mb_fn_str());
2454 MediaRet ret;
2455
2456 if ((ret = vid_rec.Record(fnb.data(), basic_width, basic_height,
2457 systemColorDepth)) != MRET_OK)
2458 wxLogError(_("Unable to begin recording to %s (%s)"), fname.c_str(),
2459 media_err(ret));
2460 else
2461 {
2462 MainFrame* mf = wxGetApp().frame;
2463 mf->cmd_enable &= ~(CMDEN_NVREC | CMDEN_NREC_ANY);
2464 mf->cmd_enable |= CMDEN_VREC;
2465 mf->enable_menus();
2466 }
2467 }
2468
StopVidRecording()2469 void GameArea::StopVidRecording()
2470 {
2471 vid_rec.Stop();
2472 MainFrame* mf = wxGetApp().frame;
2473 mf->cmd_enable &= ~CMDEN_VREC;
2474 mf->cmd_enable |= CMDEN_NVREC;
2475
2476 if (!(mf->cmd_enable & (CMDEN_VREC | CMDEN_SREC)))
2477 mf->cmd_enable |= CMDEN_NREC_ANY;
2478
2479 mf->enable_menus();
2480 }
2481
StartSoundRecording(const wxString & fname)2482 void GameArea::StartSoundRecording(const wxString &fname)
2483 {
2484 // auto-conversion of wxCharBuffer to const char * seems broken
2485 // so save underlying wxCharBuffer (or create one of none is used)
2486 wxCharBuffer fnb(fname.mb_fn_str());
2487 MediaRet ret;
2488
2489 if ((ret = snd_rec.Record(fnb.data())) != MRET_OK)
2490 wxLogError(_("Unable to begin recording to %s (%s)"), fname.c_str(),
2491 media_err(ret));
2492 else
2493 {
2494 MainFrame* mf = wxGetApp().frame;
2495 mf->cmd_enable &= ~(CMDEN_NSREC | CMDEN_NREC_ANY);
2496 mf->cmd_enable |= CMDEN_SREC;
2497 mf->enable_menus();
2498 }
2499 }
2500
StopSoundRecording()2501 void GameArea::StopSoundRecording()
2502 {
2503 snd_rec.Stop();
2504 MainFrame* mf = wxGetApp().frame;
2505 mf->cmd_enable &= ~CMDEN_SREC;
2506 mf->cmd_enable |= CMDEN_NSREC;
2507
2508 if (!(mf->cmd_enable & (CMDEN_VREC | CMDEN_SREC)))
2509 mf->cmd_enable |= CMDEN_NREC_ANY;
2510
2511 mf->enable_menus();
2512 }
2513
AddFrame(const u16 * data,int length)2514 void GameArea::AddFrame(const u16* data, int length)
2515 {
2516 MediaRet ret;
2517
2518 if ((ret = vid_rec.AddFrame(data)) != MRET_OK)
2519 {
2520 wxLogError(_("Error in audio/video recording (%s); aborting"),
2521 media_err(ret));
2522 vid_rec.Stop();
2523 }
2524
2525 if ((ret = snd_rec.AddFrame(data)) != MRET_OK)
2526 {
2527 wxLogError(_("Error in audio recording (%s); aborting"), media_err(ret));
2528 snd_rec.Stop();
2529 }
2530 }
2531
AddFrame(const u8 * data)2532 void GameArea::AddFrame(const u8* data)
2533 {
2534 MediaRet ret;
2535
2536 if ((ret = vid_rec.AddFrame(data)) != MRET_OK)
2537 {
2538 wxLogError(_("Error in video recording (%s); aborting"), media_err(ret));
2539 vid_rec.Stop();
2540 }
2541 }
2542 #endif
2543
ShowPointer()2544 void GameArea::ShowPointer()
2545 {
2546 if (fullscreen)
2547 return;
2548
2549 mouse_active_time = systemGetClock();
2550
2551 if (!pointer_blanked)
2552 return;
2553
2554 pointer_blanked = false;
2555 SetCursor(wxNullCursor);
2556
2557 if (panel)
2558 panel->GetWindow()->SetCursor(wxNullCursor);
2559 }
2560
HidePointer()2561 void GameArea::HidePointer()
2562 {
2563 if (pointer_blanked)
2564 return;
2565
2566 // FIXME: make time configurable
2567 if (fullscreen || (systemGetClock() - mouse_active_time) > 3000)
2568 {
2569 pointer_blanked = true;
2570 SetCursor(wxCursor(wxCURSOR_BLANK));
2571
2572 // wxGTK requires that subwindows get the cursor as well
2573 if (panel)
2574 panel->GetWindow()->SetCursor(wxCursor(wxCURSOR_BLANK));
2575 }
2576 }
2577