2019-12-17 14:46:50 +00:00
|
|
|
#include <iostream>
|
|
|
|
#include <SDL.h>
|
2020-11-21 09:35:15 +00:00
|
|
|
#include <SDL_image.h>
|
2019-12-17 14:46:50 +00:00
|
|
|
#include <memory>
|
2020-11-14 09:14:37 +00:00
|
|
|
#include <iomanip>
|
2019-12-17 14:46:50 +00:00
|
|
|
|
|
|
|
#include "linux/interface.h"
|
2020-11-21 20:13:56 +00:00
|
|
|
#include "linux/benchmark.h"
|
2020-10-11 10:20:22 +01:00
|
|
|
|
2020-10-09 19:13:42 +01:00
|
|
|
#include "frontends/common2/configuration.h"
|
|
|
|
#include "frontends/common2/utils.h"
|
|
|
|
#include "frontends/common2/programoptions.h"
|
2020-11-13 15:37:10 +00:00
|
|
|
#include "frontends/common2/timer.h"
|
2020-12-13 19:47:30 +00:00
|
|
|
#include "frontends/common2/resources.h"
|
2020-10-11 16:34:19 +01:00
|
|
|
#include "frontends/sa2/emulator.h"
|
2020-10-15 12:55:27 +01:00
|
|
|
#include "frontends/sa2/gamepad.h"
|
2020-10-20 15:32:45 +01:00
|
|
|
#include "frontends/sa2/sdirectsound.h"
|
2020-11-14 09:29:21 +00:00
|
|
|
#include "frontends/sa2/utils.h"
|
2019-12-17 14:46:50 +00:00
|
|
|
|
|
|
|
#include "StdAfx.h"
|
2020-11-27 13:48:53 +00:00
|
|
|
#include "Core.h"
|
2019-12-17 14:46:50 +00:00
|
|
|
#include "Log.h"
|
|
|
|
#include "CPU.h"
|
|
|
|
#include "Frame.h"
|
|
|
|
#include "NTSC.h"
|
|
|
|
#include "SaveState.h"
|
|
|
|
|
2020-12-07 13:13:42 +00:00
|
|
|
// comment out to test / debug init / shutdown only
|
|
|
|
#define EMULATOR_RUN
|
2019-12-17 14:46:50 +00:00
|
|
|
|
|
|
|
namespace
|
|
|
|
{
|
2020-11-14 09:14:37 +00:00
|
|
|
int getRefreshRate()
|
2020-11-13 11:50:42 +00:00
|
|
|
{
|
|
|
|
SDL_DisplayMode current;
|
|
|
|
|
|
|
|
const int should_be_zero = SDL_GetCurrentDisplayMode(0, ¤t);
|
|
|
|
|
|
|
|
if (should_be_zero)
|
|
|
|
{
|
|
|
|
throw std::runtime_error(SDL_GetError());
|
|
|
|
}
|
|
|
|
|
|
|
|
return current.refresh_rate;
|
|
|
|
}
|
|
|
|
|
2020-11-12 20:33:04 +00:00
|
|
|
struct Data
|
|
|
|
{
|
|
|
|
Emulator * emulator;
|
|
|
|
SDL_mutex * mutex;
|
2020-11-13 15:37:10 +00:00
|
|
|
Timer * timer;
|
2020-11-12 20:33:04 +00:00
|
|
|
};
|
|
|
|
|
2020-11-13 11:50:42 +00:00
|
|
|
Uint32 emulator_callback(Uint32 interval, void *param)
|
2020-11-12 20:33:04 +00:00
|
|
|
{
|
|
|
|
Data * data = static_cast<Data *>(param);
|
2020-11-13 11:50:42 +00:00
|
|
|
SDL_LockMutex(data->mutex);
|
2020-11-13 15:37:10 +00:00
|
|
|
|
|
|
|
data->timer->tic();
|
2020-11-20 15:32:35 +00:00
|
|
|
data->emulator->execute(interval);
|
2020-11-13 15:37:10 +00:00
|
|
|
data->timer->toc();
|
|
|
|
|
2020-11-13 11:50:42 +00:00
|
|
|
SDL_UnlockMutex(data->mutex);
|
2020-11-12 20:33:04 +00:00
|
|
|
return interval;
|
|
|
|
}
|
|
|
|
|
2020-11-21 09:35:15 +00:00
|
|
|
void setApplicationIcon(const std::shared_ptr<SDL_Window> & win)
|
|
|
|
{
|
|
|
|
const std::string path = getResourcePath() + "APPLEWIN.ICO";
|
|
|
|
std::shared_ptr<SDL_Surface> icon(IMG_Load(path.c_str()), SDL_FreeSurface);
|
|
|
|
if (icon)
|
|
|
|
{
|
|
|
|
SDL_SetWindowIcon(win.get(), icon.get());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-17 14:46:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int MessageBox(HWND, const char * text, const char * caption, UINT type)
|
|
|
|
{
|
2020-11-21 20:13:56 +00:00
|
|
|
// tabs do not render properly
|
|
|
|
std::string s(text);
|
|
|
|
std::replace(s.begin(), s.end(), '\t', ' ');
|
|
|
|
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_INFORMATION, caption, s.c_str(), nullptr);
|
2019-12-17 14:46:50 +00:00
|
|
|
return IDOK;
|
|
|
|
}
|
|
|
|
|
|
|
|
void FrameDrawDiskLEDS(HDC x)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void FrameDrawDiskStatus(HDC x)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void FrameRefreshStatus(int x, bool)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2020-10-09 19:13:42 +01:00
|
|
|
void run_sdl(int argc, const char * argv [])
|
2019-12-17 14:46:50 +00:00
|
|
|
{
|
2020-10-09 19:13:42 +01:00
|
|
|
EmulatorOptions options;
|
|
|
|
options.memclear = g_nMemoryClearType;
|
|
|
|
const bool run = getEmulatorOptions(argc, argv, "SDL2", options);
|
|
|
|
|
|
|
|
if (!run)
|
|
|
|
return;
|
2019-12-17 14:46:50 +00:00
|
|
|
|
2020-10-11 09:49:50 +01:00
|
|
|
if (options.log)
|
|
|
|
{
|
|
|
|
LogInit();
|
|
|
|
}
|
|
|
|
|
|
|
|
InitializeRegistry(options);
|
|
|
|
|
2020-10-15 12:55:27 +01:00
|
|
|
Paddle::instance().reset(new Gamepad(0));
|
|
|
|
|
2020-10-11 09:49:50 +01:00
|
|
|
g_nMemoryClearType = options.memclear;
|
2020-10-11 08:26:42 +01:00
|
|
|
|
2019-12-17 14:46:50 +00:00
|
|
|
initialiseEmulator();
|
2020-10-09 19:29:33 +01:00
|
|
|
applyOptions(options);
|
|
|
|
|
2019-12-17 14:46:50 +00:00
|
|
|
const int width = GetFrameBufferWidth();
|
|
|
|
const int height = GetFrameBufferHeight();
|
|
|
|
const int sw = GetFrameBufferBorderlessWidth();
|
|
|
|
const int sh = GetFrameBufferBorderlessHeight();
|
|
|
|
|
2020-11-14 09:29:21 +00:00
|
|
|
std::cerr << std::fixed << std::setprecision(2);
|
|
|
|
|
2020-11-21 09:42:46 +00:00
|
|
|
std::shared_ptr<SDL_Window> win(SDL_CreateWindow(g_pAppTitle.c_str(), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, sw, sh, SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE), SDL_DestroyWindow);
|
2019-12-17 14:46:50 +00:00
|
|
|
if (!win)
|
|
|
|
{
|
|
|
|
std::cerr << "SDL_CreateWindow Error: " << SDL_GetError() << std::endl;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-11-21 09:35:15 +00:00
|
|
|
setApplicationIcon(win);
|
|
|
|
|
2020-11-14 11:01:05 +00:00
|
|
|
std::shared_ptr<SDL_Renderer> ren(SDL_CreateRenderer(win.get(), options.sdlDriver, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC), SDL_DestroyRenderer);
|
2019-12-17 14:46:50 +00:00
|
|
|
if (!ren)
|
|
|
|
{
|
|
|
|
std::cout << "SDL_CreateRenderer Error: " << SDL_GetError() << std::endl;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-11-11 11:20:06 +00:00
|
|
|
const Uint32 format = SDL_PIXELFORMAT_ARGB8888;
|
2020-11-14 11:01:05 +00:00
|
|
|
printRendererInfo(std::cerr, ren, format, options.sdlDriver);
|
2020-11-12 18:52:52 +00:00
|
|
|
|
2020-11-11 11:20:06 +00:00
|
|
|
std::shared_ptr<SDL_Texture> tex(SDL_CreateTexture(ren.get(), format, SDL_TEXTUREACCESS_STATIC, width, height), SDL_DestroyTexture);
|
2019-12-17 14:46:50 +00:00
|
|
|
|
2020-11-14 09:14:37 +00:00
|
|
|
const int fps = getRefreshRate();
|
2020-11-14 20:09:12 +00:00
|
|
|
std::cerr << "Video refresh rate: " << fps << " Hz, " << 1000.0 / fps << " ms" << std::endl;
|
2020-10-09 11:50:59 +01:00
|
|
|
|
2020-11-21 20:13:56 +00:00
|
|
|
Emulator emulator(win, ren, tex, options.fixedSpeed);
|
2020-11-13 15:37:10 +00:00
|
|
|
|
2020-12-07 13:13:42 +00:00
|
|
|
#ifdef EMULATOR_RUN
|
2020-11-21 20:13:56 +00:00
|
|
|
if (options.benchmark)
|
2020-11-13 11:50:42 +00:00
|
|
|
{
|
2020-11-28 10:01:49 +00:00
|
|
|
// we need to switch off vsync, otherwise FPS is limited to 60
|
|
|
|
// and it will take longer to run
|
|
|
|
const int res = SDL_GL_SetSwapInterval(0);
|
|
|
|
// if this fails, should we throw, print something or just ignore?
|
|
|
|
|
|
|
|
const auto redraw = [&emulator, res]{
|
2020-11-30 13:38:56 +00:00
|
|
|
emulator.updateTexture();
|
2020-11-28 10:01:49 +00:00
|
|
|
if (res == 0) {
|
2020-11-30 13:38:56 +00:00
|
|
|
emulator.refreshVideo();
|
2020-11-28 10:01:49 +00:00
|
|
|
}
|
2020-11-21 20:13:56 +00:00
|
|
|
};
|
|
|
|
|
2020-11-30 13:38:56 +00:00
|
|
|
const auto refresh = [redraw]{
|
2020-11-21 20:13:56 +00:00
|
|
|
NTSC_SetVideoMode( g_uVideoMode );
|
|
|
|
NTSC_VideoRedrawWholeScreen();
|
2020-11-28 10:01:49 +00:00
|
|
|
redraw();
|
2020-11-21 20:13:56 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
VideoBenchmark(redraw, refresh);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Timer global;
|
|
|
|
Timer updateTextureTimer;
|
|
|
|
Timer refreshScreenTimer;
|
|
|
|
Timer cpuTimer;
|
|
|
|
Timer eventTimer;
|
2020-11-12 19:51:45 +00:00
|
|
|
|
2020-11-21 20:13:56 +00:00
|
|
|
const std::string globalTag = ". .";
|
|
|
|
std::string updateTextureTimerTag, refreshScreenTimerTag, cpuTimerTag, eventTimerTag;
|
2020-11-12 19:51:45 +00:00
|
|
|
|
2020-11-21 20:13:56 +00:00
|
|
|
if (options.multiThreaded)
|
2020-11-13 11:50:42 +00:00
|
|
|
{
|
2020-11-21 20:13:56 +00:00
|
|
|
refreshScreenTimerTag = "0 .";
|
|
|
|
cpuTimerTag = "1 M";
|
|
|
|
eventTimerTag = "0 M";
|
2020-11-13 11:50:42 +00:00
|
|
|
if (options.looseMutex)
|
|
|
|
{
|
2020-11-21 20:13:56 +00:00
|
|
|
updateTextureTimerTag = "0 .";
|
2020-11-13 11:50:42 +00:00
|
|
|
}
|
2020-11-21 20:13:56 +00:00
|
|
|
else
|
2020-11-21 15:07:31 +00:00
|
|
|
{
|
2020-11-21 20:13:56 +00:00
|
|
|
updateTextureTimerTag = "0 M";
|
2020-11-21 15:07:31 +00:00
|
|
|
}
|
2020-11-13 15:37:10 +00:00
|
|
|
|
2020-11-21 20:13:56 +00:00
|
|
|
std::shared_ptr<SDL_mutex> mutex(SDL_CreateMutex(), SDL_DestroyMutex);
|
2020-11-13 11:50:42 +00:00
|
|
|
|
2020-11-21 20:13:56 +00:00
|
|
|
Data data;
|
|
|
|
data.mutex = mutex.get();
|
|
|
|
data.emulator = &emulator;
|
|
|
|
data.timer = &cpuTimer;
|
2020-11-13 20:12:51 +00:00
|
|
|
|
2020-11-21 20:13:56 +00:00
|
|
|
const SDL_TimerID timer = SDL_AddTimer(options.timerInterval, emulator_callback, &data);
|
2020-11-20 15:32:35 +00:00
|
|
|
|
2020-11-21 20:13:56 +00:00
|
|
|
bool quit = false;
|
|
|
|
do
|
|
|
|
{
|
|
|
|
SDL_LockMutex(data.mutex);
|
|
|
|
|
|
|
|
eventTimer.tic();
|
|
|
|
SDirectSound::writeAudio();
|
|
|
|
emulator.processEvents(quit);
|
|
|
|
eventTimer.toc();
|
|
|
|
|
|
|
|
if (options.looseMutex)
|
|
|
|
{
|
|
|
|
// loose mutex
|
|
|
|
// unlock early and let CPU run again in the timer callback
|
|
|
|
SDL_UnlockMutex(data.mutex);
|
|
|
|
// but the texture will be updated concurrently with the CPU updating the video buffer
|
|
|
|
// pixels are not atomic, so a pixel error could happen (if pixel changes while being read)
|
|
|
|
// on the positive side this will release pressure from CPU and allow for more parallelism
|
|
|
|
}
|
|
|
|
|
|
|
|
updateTextureTimer.tic();
|
2020-11-30 13:38:56 +00:00
|
|
|
emulator.updateTexture();
|
2020-11-21 20:13:56 +00:00
|
|
|
updateTextureTimer.toc();
|
|
|
|
|
|
|
|
if (!options.looseMutex)
|
|
|
|
{
|
|
|
|
// safe mutex, only unlock after texture has been updated
|
|
|
|
// this will stop the CPU for longer
|
|
|
|
SDL_UnlockMutex(data.mutex);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!options.headless)
|
|
|
|
{
|
|
|
|
refreshScreenTimer.tic();
|
2020-11-30 13:38:56 +00:00
|
|
|
emulator.refreshVideo();
|
2020-11-21 20:13:56 +00:00
|
|
|
refreshScreenTimer.toc();
|
|
|
|
}
|
|
|
|
|
|
|
|
} while (!quit);
|
|
|
|
|
|
|
|
SDL_RemoveTimer(timer);
|
|
|
|
// if the following enough to make sure the timer has finished
|
|
|
|
// and wont be called again?
|
|
|
|
SDL_LockMutex(data.mutex);
|
|
|
|
SDL_UnlockMutex(data.mutex);
|
|
|
|
}
|
|
|
|
else
|
2020-11-13 11:50:42 +00:00
|
|
|
{
|
2020-11-21 20:13:56 +00:00
|
|
|
refreshScreenTimerTag = "0 .";
|
|
|
|
cpuTimerTag = "0 .";
|
|
|
|
eventTimerTag = "0 .";
|
|
|
|
updateTextureTimerTag = "0 .";
|
2020-11-13 15:37:10 +00:00
|
|
|
|
2020-11-21 20:13:56 +00:00
|
|
|
bool quit = false;
|
2020-11-13 15:37:10 +00:00
|
|
|
|
2020-11-21 20:13:56 +00:00
|
|
|
// it does not need to be exact
|
|
|
|
const size_t oneFrame = 1000 / fps;
|
2020-11-13 15:37:10 +00:00
|
|
|
|
2020-11-21 20:13:56 +00:00
|
|
|
do
|
2020-11-21 15:07:31 +00:00
|
|
|
{
|
2020-11-21 20:13:56 +00:00
|
|
|
eventTimer.tic();
|
|
|
|
SDirectSound::writeAudio();
|
|
|
|
emulator.processEvents(quit);
|
|
|
|
eventTimer.toc();
|
|
|
|
|
|
|
|
cpuTimer.tic();
|
|
|
|
emulator.execute(oneFrame);
|
|
|
|
cpuTimer.toc();
|
|
|
|
|
|
|
|
updateTextureTimer.tic();
|
2020-11-30 13:38:56 +00:00
|
|
|
emulator.updateTexture();
|
2020-11-21 20:13:56 +00:00
|
|
|
updateTextureTimer.toc();
|
|
|
|
|
|
|
|
if (!options.headless)
|
|
|
|
{
|
|
|
|
refreshScreenTimer.tic();
|
2020-11-30 13:38:56 +00:00
|
|
|
emulator.refreshVideo();
|
2020-11-21 20:13:56 +00:00
|
|
|
refreshScreenTimer.toc();
|
|
|
|
}
|
|
|
|
} while (!quit);
|
|
|
|
}
|
2020-11-12 19:51:45 +00:00
|
|
|
|
2020-11-21 20:13:56 +00:00
|
|
|
global.toc();
|
2020-11-13 15:37:10 +00:00
|
|
|
|
2020-11-21 20:13:56 +00:00
|
|
|
const char sep[] = "], ";
|
|
|
|
std::cerr << "Global: [" << globalTag << sep << global << std::endl;
|
|
|
|
std::cerr << "Events: [" << eventTimerTag << sep << eventTimer << std::endl;
|
|
|
|
std::cerr << "Texture: [" << updateTextureTimerTag << sep << updateTextureTimer << std::endl;
|
|
|
|
std::cerr << "Screen: [" << refreshScreenTimerTag << sep << refreshScreenTimer << std::endl;
|
|
|
|
std::cerr << "CPU: [" << cpuTimerTag << sep << cpuTimer << std::endl;
|
2020-11-13 20:12:51 +00:00
|
|
|
|
2020-11-21 20:13:56 +00:00
|
|
|
const double timeInSeconds = global.getTimeInSeconds();
|
|
|
|
const double actualClock = g_nCumulativeCycles / timeInSeconds;
|
|
|
|
std::cerr << "Expected clock: " << g_fCurrentCLK6502 << " Hz, " << g_nCumulativeCycles / g_fCurrentCLK6502 << " s" << std::endl;
|
|
|
|
std::cerr << "Actual clock: " << actualClock << " Hz, " << timeInSeconds << " s" << std::endl;
|
|
|
|
SDirectSound::stop();
|
|
|
|
}
|
2020-12-07 13:13:42 +00:00
|
|
|
#endif
|
2020-11-13 15:37:10 +00:00
|
|
|
|
2019-12-17 14:46:50 +00:00
|
|
|
uninitialiseEmulator();
|
|
|
|
}
|
|
|
|
|
2020-10-09 19:13:42 +01:00
|
|
|
int main(int argc, const char * argv [])
|
2019-12-17 14:46:50 +00:00
|
|
|
{
|
|
|
|
//First we need to start up SDL, and make sure it went ok
|
2020-11-12 20:33:04 +00:00
|
|
|
const Uint32 flags = SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER | SDL_INIT_AUDIO | SDL_INIT_TIMER;
|
|
|
|
if (SDL_Init(flags) != 0)
|
2019-12-17 14:46:50 +00:00
|
|
|
{
|
|
|
|
std::cerr << "SDL_Init Error: " << SDL_GetError() << std::endl;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
int exit = 0;
|
|
|
|
|
|
|
|
try
|
|
|
|
{
|
2020-10-09 19:13:42 +01:00
|
|
|
run_sdl(argc, argv);
|
2019-12-17 14:46:50 +00:00
|
|
|
}
|
|
|
|
catch (const std::exception & e)
|
|
|
|
{
|
|
|
|
exit = 2;
|
|
|
|
std::cerr << e.what() << std::endl;
|
|
|
|
}
|
|
|
|
|
2020-10-15 12:55:27 +01:00
|
|
|
|
|
|
|
// this must happen BEFORE the SDL_Quit() as otherwise we have a double free (of the game controller).
|
|
|
|
Paddle::instance().reset();
|
2019-12-17 14:46:50 +00:00
|
|
|
SDL_Quit();
|
|
|
|
|
|
|
|
return exit;
|
|
|
|
}
|