commit
0511128fea
16 changed files with 404 additions and 116 deletions
|
@ -5,6 +5,7 @@ add_library(common2 STATIC
|
|||
configuration.cpp
|
||||
programoptions.cpp
|
||||
utils.cpp
|
||||
timer.cpp
|
||||
)
|
||||
|
||||
find_package(Boost REQUIRED
|
||||
|
|
|
@ -13,29 +13,33 @@ bool getEmulatorOptions(int argc, const char * argv [], const std::string & vers
|
|||
desc.add_options()
|
||||
("help,h", "Print this help message")
|
||||
("conf", "Save configuration on exit")
|
||||
("qt-ini", "Use Qt ini file (read only)");
|
||||
("multi-threaded,m", "Multi threaded")
|
||||
("loose-mutex,l", "Loose mutex")
|
||||
("sdl-driver", po::value<int>()->default_value(options.sdlDriver), "SDL driver")
|
||||
("timer-interval,i", po::value<int>()->default_value(options.timerInterval), "Timer interval in ms")
|
||||
("qt-ini,q", "Use Qt ini file (read only)");
|
||||
|
||||
po::options_description diskDesc("Disk");
|
||||
diskDesc.add_options()
|
||||
("d1,1", po::value<std::string>(), "Mount disk image in first drive")
|
||||
("d2,2", po::value<std::string>(), "Mount disk image in second drive")
|
||||
("d1,1", po::value<std::string>(), "Disk in 1st drive")
|
||||
("d2,2", po::value<std::string>(), "Disk in 2nd drive")
|
||||
("create,c", "Create missing disks");
|
||||
desc.add(diskDesc);
|
||||
|
||||
po::options_description snapshotDesc("Snapshot");
|
||||
snapshotDesc.add_options()
|
||||
("load-state,ls", po::value<std::string>(), "Load snapshot from file");
|
||||
("load-state,s", po::value<std::string>(), "Load snapshot from file");
|
||||
desc.add(snapshotDesc);
|
||||
|
||||
po::options_description memoryDesc("Memory");
|
||||
memoryDesc.add_options()
|
||||
("memclear,m", po::value<int>(), "Memory initialization pattern [0..7]");
|
||||
("memclear", po::value<int>()->default_value(options.memclear), "Memory initialization pattern [0..7]");
|
||||
desc.add(memoryDesc);
|
||||
|
||||
po::options_description emulatorDesc("Emulator");
|
||||
emulatorDesc.add_options()
|
||||
("log", "Log to AppleWin.log")
|
||||
("headless,hl", "Headless: disable video")
|
||||
("headless", "Headless: disable video")
|
||||
("ntsc,nt", "NTSC: execute NTSC code")
|
||||
("benchmark,b", "Benchmark emulator")
|
||||
("no-squaring", "Gamepad range is (already) a square");
|
||||
|
@ -54,6 +58,10 @@ bool getEmulatorOptions(int argc, const char * argv [], const std::string & vers
|
|||
|
||||
options.saveConfigurationOnExit = vm.count("conf");
|
||||
options.useQtIni = vm.count("qt-ini");
|
||||
options.multiThreaded = vm.count("multi-threaded");
|
||||
options.looseMutex = vm.count("loose-mutex");
|
||||
options.timerInterval = vm["timer-interval"].as<int>();
|
||||
options.sdlDriver = vm["sdl-driver"].as<int>();
|
||||
|
||||
if (vm.count("d1"))
|
||||
{
|
||||
|
@ -72,12 +80,9 @@ bool getEmulatorOptions(int argc, const char * argv [], const std::string & vers
|
|||
options.snapshot = vm["load-state"].as<std::string>();
|
||||
}
|
||||
|
||||
if (vm.count("memclear"))
|
||||
{
|
||||
const int memclear = vm["memclear"].as<int>();
|
||||
if (memclear >=0 && memclear < NUM_MIP)
|
||||
options.memclear = memclear;
|
||||
}
|
||||
const int memclear = vm["memclear"].as<int>();
|
||||
if (memclear >=0 && memclear < NUM_MIP)
|
||||
options.memclear = memclear;
|
||||
|
||||
options.benchmark = vm.count("benchmark") > 0;
|
||||
options.headless = vm.count("headless") > 0;
|
||||
|
|
|
@ -7,24 +7,30 @@ struct EmulatorOptions
|
|||
{
|
||||
std::string disk1;
|
||||
std::string disk2;
|
||||
bool createMissingDisks;
|
||||
bool createMissingDisks = false;
|
||||
|
||||
std::string snapshot;
|
||||
|
||||
int memclear;
|
||||
int memclear = 0;
|
||||
|
||||
bool log;
|
||||
bool log = false;
|
||||
|
||||
bool benchmark;
|
||||
bool headless;
|
||||
bool ntsc;
|
||||
bool benchmark = false;
|
||||
bool headless = false;
|
||||
bool ntsc = false; // only for applen
|
||||
|
||||
bool squaring; // turn the x/y range to a square
|
||||
bool squaring = true; // turn the x/y range to a square
|
||||
|
||||
bool saveConfigurationOnExit;
|
||||
bool useQtIni; // use Qt .ini file (read only)
|
||||
bool saveConfigurationOnExit = false;
|
||||
bool useQtIni = false; // use Qt .ini file (read only)
|
||||
|
||||
bool run; // false if options include "-h"
|
||||
bool run = true; // false if options include "-h"
|
||||
|
||||
bool multiThreaded = false;
|
||||
bool looseMutex = false; // whether SDL_UpdateTexture is mutex protected (from CPU)
|
||||
int timerInterval = 16; // only when multithreaded
|
||||
|
||||
int sdlDriver = -1; // default = -1 to let SDL choose
|
||||
};
|
||||
|
||||
bool getEmulatorOptions(int argc, const char * argv [], const std::string & version, EmulatorOptions & options);
|
||||
|
|
48
source/frontends/common2/timer.cpp
Normal file
48
source/frontends/common2/timer.cpp
Normal file
|
@ -0,0 +1,48 @@
|
|||
#include "frontends/common2/timer.h"
|
||||
|
||||
#include <ostream>
|
||||
#include <cmath>
|
||||
#include <iomanip>
|
||||
|
||||
Timer::Timer()
|
||||
: mySum(0)
|
||||
, mySum2(0)
|
||||
, myN(0)
|
||||
{
|
||||
tic();
|
||||
}
|
||||
|
||||
void Timer::tic()
|
||||
{
|
||||
myT0 = std::chrono::steady_clock::now();
|
||||
}
|
||||
|
||||
void Timer::toc()
|
||||
{
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
const auto micros = std::chrono::duration_cast<std::chrono::microseconds>(now - myT0).count();
|
||||
const double s = micros * 0.000001;
|
||||
mySum += s;
|
||||
mySum2 += s * s;
|
||||
++myN;
|
||||
myT0 = now;
|
||||
}
|
||||
|
||||
double Timer::getTimeInSeconds() const
|
||||
{
|
||||
return mySum;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const Timer & timer)
|
||||
{
|
||||
const int width = 10;
|
||||
const double m1 = timer.mySum / timer.myN;
|
||||
const double m2 = timer.mySum2 / timer.myN;
|
||||
const double std = std::sqrt(std::max(0.0, m2 - m1 * m1));
|
||||
const double scale = 1000;
|
||||
os << "total = " << std::setw(width) << timer.mySum * scale << " ms";
|
||||
os << ", average = " << std::setw(width) << m1 * scale << " ms";
|
||||
os << ", std = " << std::setw(width) << std * scale << " ms";
|
||||
os << ", n = " << std::setw(6) << timer.myN;
|
||||
return os;
|
||||
}
|
24
source/frontends/common2/timer.h
Normal file
24
source/frontends/common2/timer.h
Normal file
|
@ -0,0 +1,24 @@
|
|||
#include <chrono>
|
||||
#include <iosfwd>
|
||||
|
||||
class Timer
|
||||
{
|
||||
public:
|
||||
Timer();
|
||||
void tic();
|
||||
void toc();
|
||||
|
||||
double getTimeInSeconds() const;
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& os, const Timer & timer);
|
||||
|
||||
private:
|
||||
|
||||
std::chrono::time_point<std::chrono::steady_clock> myT0;
|
||||
|
||||
double mySum;
|
||||
double mySum2;
|
||||
int myN;
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const Timer & timer);
|
|
@ -90,10 +90,9 @@ namespace
|
|||
|
||||
void DirectSoundGenerator::setVolume()
|
||||
{
|
||||
LONG dwVolume = 0;
|
||||
myBuffer->GetVolume(&dwVolume);
|
||||
const qreal volume = - qreal(dwVolume) / DSBVOLUME_MIN + 1.0;
|
||||
myAudioOutput->setVolume(volume);
|
||||
const qreal logVolume = myBuffer->GetLogarithmicVolume();
|
||||
const qreal linVolume = QAudio::convertVolume(logVolume, QAudio::LogarithmicVolumeScale, QAudio::LinearVolumeScale);
|
||||
myAudioOutput->setVolume(linVolume);
|
||||
}
|
||||
|
||||
void DirectSoundGenerator::start()
|
||||
|
|
|
@ -6,6 +6,7 @@ add_executable(sa2
|
|||
emulator.cpp
|
||||
gamepad.cpp
|
||||
sdirectsound.cpp
|
||||
utils.cpp
|
||||
)
|
||||
|
||||
find_package(Boost REQUIRED
|
||||
|
|
|
@ -21,32 +21,6 @@
|
|||
namespace
|
||||
{
|
||||
|
||||
SDL_Rect refreshTexture(const std::shared_ptr<SDL_Texture> & tex)
|
||||
{
|
||||
uint8_t * data;
|
||||
int width;
|
||||
int height;
|
||||
int sx, sy;
|
||||
int sw, sh;
|
||||
|
||||
getScreenData(data, width, height, sx, sy, sw, sh);
|
||||
SDL_UpdateTexture(tex.get(), nullptr, data, width * 4);
|
||||
|
||||
SDL_Rect srect;
|
||||
srect.x = sx;
|
||||
srect.y = sy;
|
||||
srect.w = sw;
|
||||
srect.h = sh;
|
||||
|
||||
return srect;
|
||||
}
|
||||
|
||||
void renderScreen(const std::shared_ptr<SDL_Renderer> & ren, const std::shared_ptr<SDL_Texture> & tex, const SDL_Rect & srect)
|
||||
{
|
||||
SDL_RenderCopyEx(ren.get(), tex.get(), &srect, nullptr, 0.0, nullptr, SDL_FLIP_VERTICAL);
|
||||
SDL_RenderPresent(ren.get());
|
||||
}
|
||||
|
||||
void cycleVideoType(const std::shared_ptr<SDL_Window> & win)
|
||||
{
|
||||
g_eVideoType++;
|
||||
|
@ -76,20 +50,6 @@ namespace
|
|||
VideoRedrawScreen();
|
||||
}
|
||||
|
||||
int getFPS()
|
||||
{
|
||||
SDL_DisplayMode current;
|
||||
|
||||
int should_be_zero = SDL_GetCurrentDisplayMode(0, ¤t);
|
||||
|
||||
if (should_be_zero)
|
||||
{
|
||||
throw std::runtime_error(SDL_GetError());
|
||||
}
|
||||
|
||||
return current.refresh_rate;
|
||||
}
|
||||
|
||||
void processAppleKey(const SDL_KeyboardEvent & key)
|
||||
{
|
||||
// using keycode (or scan code) one takes a physical view of the keyboard
|
||||
|
@ -164,7 +124,7 @@ namespace
|
|||
if (ch)
|
||||
{
|
||||
addKeyToBuffer(ch);
|
||||
std::cerr << "Apple Key Down: " << std::hex << (int)ch << std::dec << std::endl;
|
||||
std::cerr << "SDL KeyboardEvent: " << std::hex << (int)ch << std::dec << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -179,27 +139,51 @@ Emulator::Emulator(
|
|||
: myWindow(window)
|
||||
, myRenderer(renderer)
|
||||
, myTexture(texture)
|
||||
, myFPS(getFPS())
|
||||
, myMultiplier(1)
|
||||
, myFullscreen(false)
|
||||
, myExtraCycles(0)
|
||||
{
|
||||
}
|
||||
|
||||
void Emulator::executeOneFrame()
|
||||
void Emulator::executeCycles(const int targetCycles)
|
||||
{
|
||||
const DWORD uCyclesToExecute = int(g_fCurrentCLK6502 / myFPS);
|
||||
const UINT dwClksPerFrame = NTSC_GetCyclesPerFrame();
|
||||
const bool bVideoUpdate = true;
|
||||
const DWORD uActualCyclesExecuted = CpuExecute(uCyclesToExecute, bVideoUpdate);
|
||||
g_dwCyclesThisFrame = (g_dwCyclesThisFrame + uActualCyclesExecuted) % dwClksPerFrame;
|
||||
const UINT dwClksPerFrame = NTSC_GetCyclesPerFrame();
|
||||
|
||||
GetCardMgr().GetDisk2CardMgr().UpdateDriveState(uActualCyclesExecuted);
|
||||
MB_PeriodicUpdate(uActualCyclesExecuted);
|
||||
SpkrUpdate(uActualCyclesExecuted);
|
||||
const DWORD executedCycles = CpuExecute(targetCycles + myExtraCycles, bVideoUpdate);
|
||||
|
||||
// SDL2 seems to synch with screen refresh rate so we do not need to worry about timers
|
||||
const SDL_Rect srect = refreshTexture(myTexture);
|
||||
renderScreen(myRenderer, myTexture, srect);
|
||||
g_dwCyclesThisFrame = (g_dwCyclesThisFrame + executedCycles) % dwClksPerFrame;
|
||||
GetCardMgr().GetDisk2CardMgr().UpdateDriveState(executedCycles);
|
||||
MB_PeriodicUpdate(executedCycles);
|
||||
SpkrUpdate(executedCycles);
|
||||
|
||||
myExtraCycles = targetCycles - executedCycles;
|
||||
}
|
||||
|
||||
SDL_Rect Emulator::updateTexture()
|
||||
{
|
||||
uint8_t * data;
|
||||
int width;
|
||||
int height;
|
||||
int sx, sy;
|
||||
int sw, sh;
|
||||
|
||||
getScreenData(data, width, height, sx, sy, sw, sh);
|
||||
SDL_UpdateTexture(myTexture.get(), nullptr, data, width * 4);
|
||||
|
||||
SDL_Rect srect;
|
||||
srect.x = sx;
|
||||
srect.y = sy;
|
||||
srect.w = sw;
|
||||
srect.h = sh;
|
||||
|
||||
return srect;
|
||||
}
|
||||
|
||||
void Emulator::refreshVideo(const SDL_Rect & rect)
|
||||
{
|
||||
SDL_RenderCopyEx(myRenderer.get(), myTexture.get(), &rect, nullptr, 0.0, nullptr, SDL_FLIP_VERTICAL);
|
||||
SDL_RenderPresent(myRenderer.get());
|
||||
}
|
||||
|
||||
void Emulator::processEvents(bool & quit)
|
||||
|
@ -297,7 +281,9 @@ void Emulator::processKeyDown(const SDL_KeyboardEvent & key, bool & quit)
|
|||
|
||||
processAppleKey(key);
|
||||
|
||||
#if LOGGING_VERBOSE
|
||||
std::cerr << "KEY DOWN: " << key.keysym.scancode << "," << key.keysym.sym << "," << key.keysym.mod << "," << bool(key.repeat) << std::endl;
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
|
@ -316,7 +302,11 @@ void Emulator::processKeyUp(const SDL_KeyboardEvent & key)
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#if LOGGING_VERBOSE
|
||||
std::cerr << "KEY UP: " << key.keysym.scancode << "," << key.keysym.sym << "," << key.keysym.mod << "," << bool(key.repeat) << std::endl;
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
void Emulator::processText(const SDL_TextInputEvent & text)
|
||||
|
@ -332,7 +322,7 @@ void Emulator::processText(const SDL_TextInputEvent & text)
|
|||
// not the letters
|
||||
// this is very simple, but one cannot handle CRTL-key combination.
|
||||
addKeyToBuffer(key);
|
||||
std::cerr << "Apple Text: " << std::hex << (int)key << std::dec << std::endl;
|
||||
std::cerr << "SDL TextInputEvent: " << std::hex << (int)key << std::dec << std::endl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,11 @@ public:
|
|||
const std::shared_ptr<SDL_Texture> & texture
|
||||
);
|
||||
|
||||
void executeOneFrame();
|
||||
void executeCycles(const int cycles);
|
||||
void refreshVideo();
|
||||
SDL_Rect updateTexture();
|
||||
void refreshVideo(const SDL_Rect & rect);
|
||||
|
||||
void processEvents(bool & quit);
|
||||
|
||||
private:
|
||||
|
@ -23,8 +27,8 @@ private:
|
|||
const std::shared_ptr<SDL_Window> myWindow;
|
||||
const std::shared_ptr<SDL_Renderer> myRenderer;
|
||||
const std::shared_ptr<SDL_Texture> myTexture;
|
||||
const int myFPS;
|
||||
|
||||
int myMultiplier;
|
||||
bool myFullscreen;
|
||||
int myExtraCycles;
|
||||
};
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#include <iostream>
|
||||
#include <SDL.h>
|
||||
#include <memory>
|
||||
#include <iomanip>
|
||||
|
||||
#include "linux/interface.h"
|
||||
#include "linux/windows/misc.h"
|
||||
|
@ -10,9 +11,11 @@
|
|||
#include "frontends/common2/configuration.h"
|
||||
#include "frontends/common2/utils.h"
|
||||
#include "frontends/common2/programoptions.h"
|
||||
#include "frontends/common2/timer.h"
|
||||
#include "frontends/sa2/emulator.h"
|
||||
#include "frontends/sa2/gamepad.h"
|
||||
#include "frontends/sa2/sdirectsound.h"
|
||||
#include "frontends/sa2/utils.h"
|
||||
|
||||
#include "StdAfx.h"
|
||||
#include "Common.h"
|
||||
|
@ -122,6 +125,41 @@ namespace
|
|||
RiffFinishWriteFile();
|
||||
}
|
||||
|
||||
int getRefreshRate()
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
struct Data
|
||||
{
|
||||
Emulator * emulator;
|
||||
SDL_mutex * mutex;
|
||||
Timer * timer;
|
||||
};
|
||||
|
||||
Uint32 emulator_callback(Uint32 interval, void *param)
|
||||
{
|
||||
Data * data = static_cast<Data *>(param);
|
||||
SDL_LockMutex(data->mutex);
|
||||
|
||||
data->timer->tic();
|
||||
const int uCyclesToExecute = int(g_fCurrentCLK6502 * interval * 0.001);
|
||||
data->emulator->executeCycles(uCyclesToExecute);
|
||||
data->timer->toc();
|
||||
|
||||
SDL_UnlockMutex(data->mutex);
|
||||
return interval;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int MessageBox(HWND, const char * text, const char * caption, UINT type)
|
||||
|
@ -151,7 +189,6 @@ void run_sdl(int argc, const char * argv [])
|
|||
if (!run)
|
||||
return;
|
||||
|
||||
|
||||
if (options.log)
|
||||
{
|
||||
LogInit();
|
||||
|
@ -180,6 +217,8 @@ void run_sdl(int argc, const char * argv [])
|
|||
const int sw = GetFrameBufferBorderlessWidth();
|
||||
const int sh = GetFrameBufferBorderlessHeight();
|
||||
|
||||
std::cerr << std::fixed << std::setprecision(2);
|
||||
|
||||
std::shared_ptr<SDL_Window> win(SDL_CreateWindow(g_pAppTitle.c_str(), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, sw, sh, SDL_WINDOW_SHOWN), SDL_DestroyWindow);
|
||||
if (!win)
|
||||
{
|
||||
|
@ -187,36 +226,141 @@ void run_sdl(int argc, const char * argv [])
|
|||
return;
|
||||
}
|
||||
|
||||
std::shared_ptr<SDL_Renderer> ren(SDL_CreateRenderer(win.get(), -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC), SDL_DestroyRenderer);
|
||||
std::shared_ptr<SDL_Renderer> ren(SDL_CreateRenderer(win.get(), options.sdlDriver, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC), SDL_DestroyRenderer);
|
||||
if (!ren)
|
||||
{
|
||||
std::cout << "SDL_CreateRenderer Error: " << SDL_GetError() << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_RendererInfo info;
|
||||
SDL_GetRendererInfo(ren.get(), &info);
|
||||
|
||||
std::cerr << "SDL Renderer:" << info.name << std::endl;
|
||||
for (size_t i = 0; i < info.num_texture_formats; ++i)
|
||||
{
|
||||
std::cerr << SDL_GetPixelFormatName(info.texture_formats[i]) << std::endl;
|
||||
}
|
||||
|
||||
const Uint32 format = SDL_PIXELFORMAT_ARGB8888;
|
||||
std::cerr << "Selected format: " << SDL_GetPixelFormatName(format) << std::endl;
|
||||
printRendererInfo(std::cerr, ren, format, options.sdlDriver);
|
||||
|
||||
std::shared_ptr<SDL_Texture> tex(SDL_CreateTexture(ren.get(), format, SDL_TEXTUREACCESS_STATIC, width, height), SDL_DestroyTexture);
|
||||
|
||||
const int fps = getRefreshRate();
|
||||
std::cerr << "Video refresh rate: " << fps << " Hz, " << 1000.0 / fps << " ms" << std::endl;
|
||||
Emulator emulator(win, ren, tex);
|
||||
|
||||
bool quit = false;
|
||||
do
|
||||
Timer global;
|
||||
Timer updateTextureTimer;
|
||||
Timer refreshScreenTimer;
|
||||
Timer cpuTimer;
|
||||
Timer eventTimer;
|
||||
|
||||
const std::string globalTag = ". .";
|
||||
std::string updateTextureTimerTag, refreshScreenTimerTag, cpuTimerTag, eventTimerTag;
|
||||
|
||||
if (options.multiThreaded)
|
||||
{
|
||||
SDirectSound::writeAudio();
|
||||
emulator.processEvents(quit);
|
||||
emulator.executeOneFrame();
|
||||
} while (!quit);
|
||||
refreshScreenTimerTag = "0 .";
|
||||
cpuTimerTag = "1 M";
|
||||
eventTimerTag = "0 M";
|
||||
if (options.looseMutex)
|
||||
{
|
||||
updateTextureTimerTag = "0 .";
|
||||
}
|
||||
else
|
||||
{
|
||||
updateTextureTimerTag = "0 M";
|
||||
}
|
||||
|
||||
std::shared_ptr<SDL_mutex> mutex(SDL_CreateMutex(), SDL_DestroyMutex);
|
||||
|
||||
Data data;
|
||||
data.mutex = mutex.get();
|
||||
data.emulator = &emulator;
|
||||
data.timer = &cpuTimer;
|
||||
|
||||
const SDL_TimerID timer = SDL_AddTimer(options.timerInterval, emulator_callback, &data);
|
||||
|
||||
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();
|
||||
const SDL_Rect rect = emulator.updateTexture();
|
||||
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);
|
||||
}
|
||||
|
||||
refreshScreenTimer.tic();
|
||||
emulator.refreshVideo(rect);
|
||||
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
|
||||
{
|
||||
refreshScreenTimerTag = "0 .";
|
||||
cpuTimerTag = "0 .";
|
||||
eventTimerTag = "0 .";
|
||||
updateTextureTimerTag = "0 .";
|
||||
|
||||
bool quit = false;
|
||||
const int uCyclesToExecute = int(g_fCurrentCLK6502 / fps);
|
||||
|
||||
do
|
||||
{
|
||||
eventTimer.tic();
|
||||
SDirectSound::writeAudio();
|
||||
emulator.processEvents(quit);
|
||||
eventTimer.toc();
|
||||
|
||||
cpuTimer.tic();
|
||||
emulator.executeCycles(uCyclesToExecute);
|
||||
cpuTimer.toc();
|
||||
|
||||
updateTextureTimer.tic();
|
||||
const SDL_Rect rect = emulator.updateTexture();
|
||||
updateTextureTimer.toc();
|
||||
|
||||
refreshScreenTimer.tic();
|
||||
emulator.refreshVideo(rect);
|
||||
refreshScreenTimer.toc();
|
||||
} while (!quit);
|
||||
}
|
||||
|
||||
global.toc();
|
||||
|
||||
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;
|
||||
|
||||
const double timeInSeconds = global.getTimeInSeconds();
|
||||
const double averageClock = g_nCumulativeCycles / timeInSeconds;
|
||||
std::cerr << "Expected clock: " << g_fCurrentCLK6502 << " Hz, " << timeInSeconds << " s" << std::endl;
|
||||
std::cerr << "Average clock: " << averageClock << " Hz, " << g_nCumulativeCycles / g_fCurrentCLK6502 << " s" << std::endl;
|
||||
|
||||
SDirectSound::stop();
|
||||
stopEmulator();
|
||||
|
@ -226,7 +370,8 @@ void run_sdl(int argc, const char * argv [])
|
|||
int main(int argc, const char * argv [])
|
||||
{
|
||||
//First we need to start up SDL, and make sure it went ok
|
||||
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER | SDL_INIT_AUDIO) != 0)
|
||||
const Uint32 flags = SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER | SDL_INIT_AUDIO | SDL_INIT_TIMER;
|
||||
if (SDL_Init(flags) != 0)
|
||||
{
|
||||
std::cerr << "SDL_Init Error: " << SDL_GetError() << std::endl;
|
||||
return 1;
|
||||
|
|
|
@ -22,6 +22,8 @@ namespace
|
|||
private:
|
||||
IDirectSoundBuffer * myBuffer;
|
||||
|
||||
std::vector<Uint8> myMixerBuffer;
|
||||
|
||||
SDL_AudioDeviceID myAudioDevice;
|
||||
SDL_AudioSpec myAudioSpec;
|
||||
|
||||
|
@ -29,9 +31,10 @@ namespace
|
|||
int myInitialSilence;
|
||||
|
||||
void close();
|
||||
void setVolume();
|
||||
bool isRunning();
|
||||
void writeEnoughSilence(const int ms);
|
||||
|
||||
void mixBuffer(const void * ptr, const size_t size);
|
||||
};
|
||||
|
||||
|
||||
|
@ -95,14 +98,6 @@ namespace
|
|||
myInitialSilence = std::max(0, initialSilence);
|
||||
}
|
||||
|
||||
void DirectSoundGenerator::setVolume()
|
||||
{
|
||||
LONG dwVolume = 0;
|
||||
myBuffer->GetVolume(&dwVolume);
|
||||
const double volume = - double(dwVolume) / DSBVOLUME_MIN + 1.0;
|
||||
// myAudioOutput->setVolume(volume);
|
||||
}
|
||||
|
||||
void DirectSoundGenerator::stop()
|
||||
{
|
||||
if (myAudioDevice)
|
||||
|
@ -124,6 +119,21 @@ namespace
|
|||
SDL_QueueAudio(myAudioDevice, silence.data(), silence.size());
|
||||
}
|
||||
|
||||
void DirectSoundGenerator::mixBuffer(const void * ptr, const size_t size)
|
||||
{
|
||||
const double logVolume = myBuffer->GetLogarithmicVolume();
|
||||
// same formula as QAudio::convertVolume()
|
||||
const double linVolume = logVolume > 0.99 ? 1.0 : -std::log(1.0 - logVolume) / std::log(100.0);
|
||||
|
||||
const Uint8 svolume = Uint8(linVolume * SDL_MIX_MAXVOLUME);
|
||||
|
||||
// this is a bit of a waste copy-time, but it reuses SDL to do it properly
|
||||
myMixerBuffer.resize(size);
|
||||
memset(myMixerBuffer.data(), 0, size);
|
||||
SDL_MixAudioFormat(myMixerBuffer.data(), (const Uint8*)ptr, myAudioSpec.format, size, svolume);
|
||||
SDL_QueueAudio(myAudioDevice, myMixerBuffer.data(), size);
|
||||
}
|
||||
|
||||
void DirectSoundGenerator::writeAudio()
|
||||
{
|
||||
// this is autostart as we only do for the palying buffers
|
||||
|
@ -152,11 +162,11 @@ namespace
|
|||
|
||||
if (lpvAudioPtr1 && dwAudioBytes1)
|
||||
{
|
||||
SDL_QueueAudio(myAudioDevice, lpvAudioPtr1, dwAudioBytes1);
|
||||
mixBuffer(lpvAudioPtr1, dwAudioBytes1);
|
||||
}
|
||||
if (lpvAudioPtr2 && dwAudioBytes2)
|
||||
{
|
||||
SDL_QueueAudio(myAudioDevice, lpvAudioPtr2, dwAudioBytes2);
|
||||
mixBuffer(lpvAudioPtr2, dwAudioBytes2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
37
source/frontends/sa2/utils.cpp
Normal file
37
source/frontends/sa2/utils.cpp
Normal file
|
@ -0,0 +1,37 @@
|
|||
#include "frontends/sa2/utils.h"
|
||||
#include <ostream>
|
||||
|
||||
void printRendererInfo(std::ostream & os, std::shared_ptr<SDL_Renderer> & ren, const Uint32 pixelFormat, const int selectedDriver)
|
||||
{
|
||||
SDL_RendererInfo info;
|
||||
SDL_GetRendererInfo(ren.get(), &info);
|
||||
|
||||
const size_t n = SDL_GetNumRenderDrivers();
|
||||
os << "SDL: " << n << " drivers" << std::endl;
|
||||
for(size_t i = 0; i < n; ++i)
|
||||
{
|
||||
SDL_RendererInfo info;
|
||||
SDL_GetRenderDriverInfo(i, &info);
|
||||
os << " " << i << ": " << info.name << std::endl;
|
||||
}
|
||||
|
||||
if (SDL_GetRendererInfo(ren.get(), &info) == 0)
|
||||
{
|
||||
os << "Active driver (" << selectedDriver << "): " << info.name << std::endl;
|
||||
os << " SDL_RENDERER_SOFTWARE: " << ((info.flags & SDL_RENDERER_SOFTWARE) > 0) << std::endl;
|
||||
os << " SDL_RENDERER_ACCELERATED: " << ((info.flags & SDL_RENDERER_ACCELERATED) > 0) << std::endl;
|
||||
os << " SDL_RENDERER_PRESENTVSYNC: " << ((info.flags & SDL_RENDERER_PRESENTVSYNC) > 0) << std::endl;
|
||||
os << " SDL_RENDERER_TARGETTEXTURE: " << ((info.flags & SDL_RENDERER_TARGETTEXTURE) > 0) << std::endl;
|
||||
|
||||
os << "Supported pixel formats:" << std::endl;
|
||||
for (size_t i = 0; i < info.num_texture_formats; ++i)
|
||||
{
|
||||
os << " " << SDL_GetPixelFormatName(info.texture_formats[i]) << std::endl;
|
||||
}
|
||||
os << "Selected format: " << SDL_GetPixelFormatName(pixelFormat) << std::endl;
|
||||
}
|
||||
else
|
||||
{
|
||||
os << "No Renderinfo" << std::endl;
|
||||
}
|
||||
}
|
7
source/frontends/sa2/utils.h
Normal file
7
source/frontends/sa2/utils.h
Normal file
|
@ -0,0 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <SDL.h>
|
||||
#include <memory>
|
||||
#include <iosfwd>
|
||||
|
||||
void printRendererInfo(std::ostream & os, std::shared_ptr<SDL_Renderer> & ren, const Uint32 pixelFormat, const int selectedDriver);
|
|
@ -286,11 +286,14 @@ void LoadConfiguration(void)
|
|||
if(REGLOAD(TEXT(REGVALUE_THE_FREEZES_F8_ROM), &dwTmp))
|
||||
sg_PropertySheet.SetTheFreezesF8Rom(dwTmp);
|
||||
|
||||
if(REGLOAD(TEXT(REGVALUE_SPKR_VOLUME), &dwTmp))
|
||||
SpkrSetVolume(dwTmp, 99);
|
||||
// if missing set volume low to avoid annoying beeps
|
||||
dwTmp = 70;
|
||||
REGLOAD(TEXT(REGVALUE_SPKR_VOLUME), &dwTmp);
|
||||
SpkrSetVolume(dwTmp, 99);
|
||||
|
||||
if(REGLOAD(TEXT(REGVALUE_MB_VOLUME), &dwTmp))
|
||||
MB_SetVolume(dwTmp, 99);
|
||||
dwTmp = 60;
|
||||
REGLOAD(TEXT(REGVALUE_MB_VOLUME), &dwTmp);
|
||||
MB_SetVolume(dwTmp, 99);
|
||||
|
||||
if(REGLOAD(TEXT(REGVALUE_SAVE_STATE_ON_EXIT), &dwTmp))
|
||||
g_bSaveStateOnExit = dwTmp ? true : false;
|
||||
|
|
|
@ -173,6 +173,12 @@ HRESULT IDirectSoundBuffer::GetVolume( LONG * lplVolume )
|
|||
return DS_OK;
|
||||
}
|
||||
|
||||
double IDirectSoundBuffer::GetLogarithmicVolume() const
|
||||
{
|
||||
const double volume = (double(myVolume) - DSBVOLUME_MIN) / (0.0 - DSBVOLUME_MIN);
|
||||
return volume;
|
||||
}
|
||||
|
||||
HRESULT WINAPI DirectSoundCreate(LPGUID lpGuid, LPDIRECTSOUND* ppDS, LPUNKNOWN pUnkOuter)
|
||||
{
|
||||
*ppDS = new IDirectSound();
|
||||
|
|
|
@ -111,6 +111,8 @@ class IDirectSoundBuffer : public IUnknown
|
|||
HRESULT SetVolume( LONG lVolume );
|
||||
HRESULT GetVolume( LONG * lplVolume );
|
||||
|
||||
double GetLogarithmicVolume() const; // in [0, 1]
|
||||
|
||||
HRESULT GetStatus( LPDWORD lpdwStatus );
|
||||
HRESULT Restore();
|
||||
};
|
||||
|
|
Loading…
Add table
Reference in a new issue