Add measured FPS to OSD, squash a bunch of minor bugs.

This commit is contained in:
Christian Speckner 2018-07-30 23:19:09 +02:00
parent de24815771
commit 3a5572d3b9
13 changed files with 113 additions and 75 deletions

View File

@ -17,7 +17,8 @@ MODULE_OBJS := \
src/common/StateManager.o \
src/common/ZipHandler.o \
src/common/AudioQueue.o \
src/common/AudioSettings.o
src/common/AudioSettings.o \
src/common/FpsMeter.o
MODULE_DIRS += \
src/common

View File

@ -157,6 +157,8 @@ void Debugger::quit(bool exitrom)
myOSystem.eventHandler().handleEvent(Event::LauncherMode, 1);
else
myOSystem.eventHandler().leaveDebugMode();
myOSystem.console().tia().clearPendingFrame();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

View File

@ -148,7 +148,6 @@ Console::Console(OSystem& osystem, unique_ptr<Cartridge>& cart,
// Note that this can be overridden if a format is forced
// For example, if a PAL ROM is forced to be NTSC, it will use NTSC-like
// properties (60Hz, 262 scanlines, etc), but likely result in flicker
setTIAProperties();
if(myDisplayFormat == "NTSC")
{
myCurrentFormat = 1;
@ -180,6 +179,8 @@ Console::Console(OSystem& osystem, unique_ptr<Cartridge>& cart,
myConsoleTiming = ConsoleTiming::secam;
}
setTIAProperties();
bool joyallow4 = myOSystem.settings().getBool("joyallow4");
myOSystem.eventHandler().allowAllDirections(joyallow4);
@ -387,6 +388,7 @@ void Console::setFormat(uInt32 format)
myTIA->frameReset();
initializeVideo(); // takes care of refreshing the screen
initializeAudio(); // ensure that audio synthesis is set up to match emulation speed
myOSystem.resetFps(); // Reset FPS measurement
}
myOSystem.frameBuffer().showMessage(message);
@ -975,7 +977,8 @@ void Console::generateColorLossPalette()
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
float Console::getFramerate() const
{
return myTIA->frameBufferFrameRate();
return
static_cast<float>(myEmulationTiming.linesPerSecond()) / myTIA->frameBufferScanlinesLastFrame();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

View File

@ -134,6 +134,12 @@ uInt32 EmulationTiming::cyclesPerSecond() const
return myCyclesPerSecond;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt32 EmulationTiming::linesPerSecond() const
{
return myLinesPerSecond;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt32 EmulationTiming::audioFragmentSize() const
{
@ -181,7 +187,7 @@ void EmulationTiming::recalculate()
case ConsoleTiming::pal:
case ConsoleTiming::secam:
myCyclesPerSecond = uInt32(round(mySpeedFactor * 312 * 76 * 50) / 38);
myAudioSampleRate = uInt32(round(mySpeedFactor * 312 * 76 * 50) / 38);
break;
default:
@ -204,4 +210,6 @@ void EmulationTiming::recalculate()
myPrebufferFragmentCount,
discreteDivCeil(myMaxCyclesPerTimeslice * myAudioSampleRate, myAudioFragmentSize * myCyclesPerSecond)
) + myAudioQueueExtraFragments;
myLinesPerSecond = myCyclesPerSecond / 76;
}

View File

@ -49,6 +49,8 @@ class EmulationTiming {
uInt32 cyclesPerFrame() const;
uInt32 linesPerSecond() const;
uInt32 cyclesPerSecond() const;
uInt32 audioFragmentSize() const;
@ -82,6 +84,7 @@ class EmulationTiming {
uInt32 myAudioSampleRate;
uInt32 myAudioQueueCapacity;
uInt32 myPrebufferFragmentCount;
uInt32 myLinesPerSecond;
float mySpeedFactor;

View File

@ -229,8 +229,8 @@ FBInitStatus FrameBuffer::createDisplay(const string& title,
// Create surfaces for TIA statistics and general messages
myStatsMsg.color = kColorInfo;
myStatsMsg.w = font().getMaxCharWidth() * 30 + 3;
myStatsMsg.h = (font().getFontHeight() + 2) * 2;
myStatsMsg.w = font().getMaxCharWidth() * 40 + 3;
myStatsMsg.h = (font().getFontHeight() + 2) * 3;
if(!myStatsMsg.surface)
{
@ -361,7 +361,7 @@ void FrameBuffer::update(bool force)
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void FrameBuffer::updateInEmulationMode()
void FrameBuffer::updateInEmulationMode(float framesPerSecond)
{
// Update method that is specifically tailored to emulation mode
// Typically called from a thread, so it needs to be separate from
@ -374,7 +374,7 @@ void FrameBuffer::updateInEmulationMode()
// Show frame statistics
if(myStatsMsg.enabled)
drawFrameStats();
drawFrameStats(framesPerSecond);
myLastScanlines = myOSystem.console().tia().frameBufferScanlinesLastFrame();
myPausedCount = 0;
@ -410,39 +410,50 @@ void FrameBuffer::showMessage(const string& message, MessagePosition position,
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void FrameBuffer::drawFrameStats()
void FrameBuffer::drawFrameStats(float framesPerSecond)
{
const ConsoleInfo& info = myOSystem.console().about();
char msg[30];
uInt32 color;
const int XPOS = 2, YPOS = 0;
int xPos = XPOS;
int xPos = 2, yPos = 0;
const int dy = font().getFontHeight() + 2;
ostringstream ss;
myStatsMsg.surface->invalidate();
// draw scanlines
color = myOSystem.console().tia().frameBufferScanlinesLastFrame() != myLastScanlines ?
uInt32(kDbgColorRed) : myStatsMsg.color;
std::snprintf(msg, 30, "%3u", myOSystem.console().tia().frameBufferScanlinesLastFrame());
myStatsMsg.surface->drawString(font(), msg, xPos, YPOS,
ss
<< myOSystem.console().tia().frameBufferScanlinesLastFrame()
<< " / "
<< std::fixed << std::setprecision(1) << myOSystem.console().getFramerate()
<< "Hz => "
<< info.DisplayFormat;
myStatsMsg.surface->drawString(font(), ss.str(), xPos, yPos,
myStatsMsg.w, color, TextAlign::Left, 0, true, kBGColor);
xPos += font().getStringWidth(msg);
// draw frequency
std::snprintf(msg, 30, " => %s", info.DisplayFormat.c_str());
myStatsMsg.surface->drawString(font(), msg, xPos, YPOS,
myStatsMsg.w, myStatsMsg.color, TextAlign::Left, 0, true, kBGColor);
xPos += font().getStringWidth(msg);
yPos += dy;
ss.str("");
std::snprintf(msg, 30, " @ %5.2ffps", myOSystem.console().getFramerate());
ss
<< std::fixed << std::setprecision(1) << framesPerSecond
<< "fps @ "
<< std::fixed << std::setprecision(2) << 100 * myOSystem.settings().getFloat("speed")
<< "% speed";
myStatsMsg.surface->drawString(font(), msg, xPos, YPOS,
myStatsMsg.surface->drawString(font(), ss.str(), xPos, yPos,
myStatsMsg.w, myStatsMsg.color, TextAlign::Left, 0, true, kBGColor);
// draw bankswitching type
string bsinfo = info.BankSwitch +
(myOSystem.settings().getBool("dev.settings") ? "| Developer" : "");
myStatsMsg.surface->drawString(font(), bsinfo, XPOS, YPOS + font().getFontHeight(),
yPos += dy;
ss.str("");
ss << info.BankSwitch;
if (myOSystem.settings().getBool("dev.settings")) ss << "| Developer";
myStatsMsg.surface->drawString(font(), ss.str(), xPos, yPos,
myStatsMsg.w, myStatsMsg.color, TextAlign::Left, 0, true, kBGColor);
myStatsMsg.surface->setDstPos(myImageRect.x() + 10, myImageRect.y() + 8);

View File

@ -120,7 +120,7 @@ class FrameBuffer
/**
There is a dedicated update method for emulation mode.
*/
void updateInEmulationMode();
void updateInEmulationMode(float framesPerSecond);
/**
Shows a message onscreen.
@ -471,7 +471,7 @@ class FrameBuffer
private:
// Draws the frame stats overlay
void drawFrameStats();
void drawFrameStats(float framesPerSecond);
// Indicates the number of times the framebuffer was initialized
uInt32 myInitializedCount;

View File

@ -30,14 +30,13 @@
#include "CheatManager.hxx"
#endif
#include <chrono>
#include "FSNode.hxx"
#include "MD5.hxx"
#include "Cart.hxx"
#include "CartDetector.hxx"
#include "FrameBuffer.hxx"
#include "TIASurface.hxx"
#include "TIAConstants.hxx"
#include "Settings.hxx"
#include "PropsSet.hxx"
#include "EventHandler.hxx"
@ -60,10 +59,15 @@
using namespace std::chrono;
namespace {
constexpr uInt32 FPS_METER_QUEUE_SIZE = 100;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
OSystem::OSystem()
: myLauncherUsed(false),
myQuitLoop(false)
myQuitLoop(false),
myFpsMeter(FPS_METER_QUEUE_SIZE)
{
// Get built-in features
#ifdef SOUND_SUPPORT
@ -481,6 +485,12 @@ void OSystem::logMessage(const string& message, uInt8 level)
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void OSystem::resetFps()
{
myFpsMeter.reset();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
unique_ptr<Console> OSystem::openConsole(const FilesystemNode& romfile, string& md5)
{
@ -651,7 +661,10 @@ double OSystem::dispatchEmulation(EmulationWorker& emulationWorker)
bool framePending = tia.newFramePending();
// ... and copy it to the frame buffer. It is important to do this before
// the worker is started to avoid racing.
if (framePending) tia.renderToFrameBuffer();
if (framePending) {
myFpsMeter.render(tia.framesSinceLastRender());
tia.renderToFrameBuffer();
}
// Start emulation on a dedicated thread. It will do its own scheduling to sync 6507 and real time
// and will run until we stop the worker.
@ -665,7 +678,7 @@ double OSystem::dispatchEmulation(EmulationWorker& emulationWorker)
// Render the frame. This may block, but emulation will continue to run on the worker, so the
// audio pipeline is kept fed :)
if (framePending) myFrameBuffer->updateInEmulationMode();
if (framePending) myFrameBuffer->updateInEmulationMode(myFpsMeter.fps());
// Stop the worker and wait until it has finished
uInt64 totalCycles = emulationWorker.stop();
@ -691,11 +704,20 @@ void OSystem::mainLoop()
// The emulation worker
EmulationWorker emulationWorker;
myFpsMeter.reset(TIAConstants::initialGarbageFrames);
for(;;)
{
bool wasEmulation = myEventHandler->state() == EventHandlerState::EMULATION;
myEventHandler->poll(getTicks());
if(myQuitLoop) break; // Exit if the user wants to quit
if (!wasEmulation && myEventHandler->state() == EventHandlerState::EMULATION) {
myFpsMeter.reset();
virtualTime = high_resolution_clock::now();
}
double timesliceSeconds;
if (myEventHandler->state() == EventHandlerState::EMULATION)

View File

@ -40,9 +40,12 @@ class StateManager;
class VideoDialog;
class EmulationWorker;
#include <chrono>
#include "FSNode.hxx"
#include "FrameBufferConstants.hxx"
#include "EventHandlerConstants.hxx"
#include "FpsMeter.hxx"
#include "bspf.hxx"
#include "AudioSettings.hxx"
@ -367,6 +370,11 @@ class OSystem
*/
const string& logMessages() const { return myLogMessages; }
/**
Reset FPS measurement.
*/
void resetFps();
public:
//////////////////////////////////////////////////////////////////////
// The following methods are system-specific and can be overrided in
@ -510,6 +518,8 @@ class OSystem
string myFeatures;
string myBuildInfo;
FpsMeter myFpsMeter;
private:
/**
Creates the various framebuffers/renderers available in this system.

View File

@ -181,10 +181,9 @@ void TIA::reset()
frameReset(); // Recalculate the size of the display
}
myFrontBufferFrameRate = myFrameBufferFrameRate = 0;
myFrontBufferScanlines = myFrameBufferScanlines = 0;
myNewFramePending = false;
myFramesSinceLastRender = 0;
// Must be done last, after all other items have reset
enableFixedColors(mySettings.getBool(mySettings.getBool("dev.settings") ? "dev.debugcolors" : "plr.debugcolors"));
@ -293,8 +292,6 @@ bool TIA::save(Serializer& out) const
out.putInt(myFrameBufferScanlines);
out.putInt(myFrontBufferScanlines);
out.putDouble(myFrameBufferFrameRate);
out.putDouble(myFrontBufferFrameRate);
}
catch(...)
{
@ -366,8 +363,6 @@ bool TIA::load(Serializer& in)
myFrameBufferScanlines = in.getInt();
myFrontBufferScanlines = in.getInt();
myFrameBufferFrameRate = in.getDouble();
myFrontBufferFrameRate = in.getDouble();
}
catch(...)
{
@ -799,7 +794,7 @@ bool TIA::saveDisplay(Serializer& out) const
out.putByteArray(myFramebuffer, 160* TIAConstants::frameBufferHeight);
out.putByteArray(myBackBuffer, 160 * TIAConstants::frameBufferHeight);
out.putByteArray(myFrontBuffer, 160 * TIAConstants::frameBufferHeight);
out.putBool(myNewFramePending);
out.putInt(myFramesSinceLastRender);
}
catch(...)
{
@ -819,7 +814,7 @@ bool TIA::loadDisplay(Serializer& in)
in.getByteArray(myFramebuffer, 160 * TIAConstants::frameBufferHeight);
in.getByteArray(myBackBuffer, 160 * TIAConstants::frameBufferHeight);
in.getByteArray(myFrontBuffer, 160 * TIAConstants::frameBufferHeight);
myNewFramePending = in.getBool();
myFramesSinceLastRender = in.getInt();
}
catch(...)
{
@ -841,11 +836,12 @@ void TIA::update(DispatchResult& result, uInt64 maxCycles)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void TIA::renderToFrameBuffer()
{
if (!myNewFramePending) return;
if (myFramesSinceLastRender == 0) return;
myFramesSinceLastRender = 0;
memcpy(myFramebuffer, myFrontBuffer, 160 * TIAConstants::frameBufferHeight);
myFrameBufferFrameRate = myFrontBufferFrameRate;
myFrameBufferScanlines = myFrontBufferScanlines;
}
@ -1198,10 +1194,9 @@ void TIA::onFrameComplete()
memcpy(myFrontBuffer, myBackBuffer, 160 * TIAConstants::frameBufferHeight);
myFrontBufferFrameRate = frameRate();
myFrontBufferScanlines = scanlinesLastFrame();
myNewFramePending = true;
myFramesSinceLastRender++;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

View File

@ -207,7 +207,17 @@ class TIA : public Device
/**
Did we generate a new frame?
*/
bool newFramePending() { return myNewFramePending; }
bool newFramePending() { return myFramesSinceLastRender > 0; }
/**
* Clear any pending frames.
*/
void clearPendingFrame() { myFramesSinceLastRender = 0; }
/**
The number of frames since we did last render to the front buffer.
*/
uInt32 framesSinceLastRender() { return myFramesSinceLastRender; }
/**
Render the pending frame to the framebuffer and clear the flag.
@ -248,13 +258,6 @@ class TIA : public Device
*/
ConsoleTiming consoleTiming() const { return myConsole.timing(); }
float frameRate() const { return myFrameManager ? myFrameManager->frameRate() : 0; }
/**
The same, but for the frame in the frame buffer.
*/
float frameBufferFrameRate() const { return myFrameBufferFrameRate; }
/**
Enables/disables color-loss for PAL modes only.
@ -694,10 +697,9 @@ class TIA : public Device
// We snapshot frame statistics when the back buffer is copied to the front buffer
// and when the front buffer is copied to the frame buffer
uInt32 myFrontBufferScanlines, myFrameBufferScanlines;
float myFrontBufferFrameRate, myFrameBufferFrameRate;
// Did we emit a frame?
bool myNewFramePending;
// Frames since the last time a frame was rendered to the render buffer
uInt32 myFramesSinceLastRender;
/**
* Setting this to true injects random values into undefined reads.

View File

@ -37,8 +37,6 @@ void AbstractFrameManager::reset()
myCurrentFrameFinalLines = 0;
myPreviousFrameFinalLines = 0;
myTotalFrames = 0;
myFrameRate = 0;
myFrameRate = 60.0;
onReset();
}
@ -101,9 +99,6 @@ void AbstractFrameManager::notifyFrameComplete()
myTotalFrames++;
if (myOnFrameComplete) myOnFrameComplete();
myFrameRate = (layout() == FrameLayout::pal ? 15600.0 : 15720.0) /
myCurrentFrameFinalLines;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -130,7 +125,6 @@ bool AbstractFrameManager::save(Serializer& out) const
out.putInt(myPreviousFrameFinalLines);
out.putInt(myTotalFrames);
out.putInt(uInt32(myLayout));
out.putDouble(myFrameRate);
return onSave(out);
}
@ -155,7 +149,6 @@ bool AbstractFrameManager::load(Serializer& in)
myPreviousFrameFinalLines = in.getInt();
myTotalFrames = in.getInt();
myLayout = FrameLayout(in.getInt());
myFrameRate = float(in.getDouble());
return onLoad(in);
}

View File

@ -107,13 +107,6 @@ class AbstractFrameManager : public Serializable
*/
FrameLayout layout() const { return myLayout; }
/**
* The current frame rate. This is calculated dynamically from the number of
* scanlines in the last frames and used to control sleep time in the
* dispatch loop.
*/
float frameRate() const { return myFrameRate; }
/**
* Save state.
*/
@ -289,11 +282,6 @@ class AbstractFrameManager : public Serializable
*/
uInt32 myTotalFrames;
/**
* Frame rate (see above.)
*/
float myFrameRate;
private:
/**