diff --git a/src/common/module.mk b/src/common/module.mk index 4be025053..347065488 100644 --- a/src/common/module.mk +++ b/src/common/module.mk @@ -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 diff --git a/src/debugger/Debugger.cxx b/src/debugger/Debugger.cxx index 5606bf9f2..886ea9b76 100644 --- a/src/debugger/Debugger.cxx +++ b/src/debugger/Debugger.cxx @@ -157,6 +157,8 @@ void Debugger::quit(bool exitrom) myOSystem.eventHandler().handleEvent(Event::LauncherMode, 1); else myOSystem.eventHandler().leaveDebugMode(); + + myOSystem.console().tia().clearPendingFrame(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/emucore/Console.cxx b/src/emucore/Console.cxx index becdb9a41..294b57be2 100644 --- a/src/emucore/Console.cxx +++ b/src/emucore/Console.cxx @@ -148,7 +148,6 @@ Console::Console(OSystem& osystem, unique_ptr& 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& 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(myEmulationTiming.linesPerSecond()) / myTIA->frameBufferScanlinesLastFrame(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/emucore/EmulationTiming.cxx b/src/emucore/EmulationTiming.cxx index b9651f46f..a8bef4bc2 100644 --- a/src/emucore/EmulationTiming.cxx +++ b/src/emucore/EmulationTiming.cxx @@ -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; } diff --git a/src/emucore/EmulationTiming.hxx b/src/emucore/EmulationTiming.hxx index a2e8fc57c..35a7b0350 100644 --- a/src/emucore/EmulationTiming.hxx +++ b/src/emucore/EmulationTiming.hxx @@ -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; diff --git a/src/emucore/FrameBuffer.cxx b/src/emucore/FrameBuffer.cxx index 7d842d59a..e8daa2da1 100644 --- a/src/emucore/FrameBuffer.cxx +++ b/src/emucore/FrameBuffer.cxx @@ -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); diff --git a/src/emucore/FrameBuffer.hxx b/src/emucore/FrameBuffer.hxx index c656437cd..a85d0fedf 100644 --- a/src/emucore/FrameBuffer.hxx +++ b/src/emucore/FrameBuffer.hxx @@ -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; diff --git a/src/emucore/OSystem.cxx b/src/emucore/OSystem.cxx index 45d384179..664c35c8b 100644 --- a/src/emucore/OSystem.cxx +++ b/src/emucore/OSystem.cxx @@ -30,14 +30,13 @@ #include "CheatManager.hxx" #endif -#include - #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 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) diff --git a/src/emucore/OSystem.hxx b/src/emucore/OSystem.hxx index d52956fae..b192b2b3d 100644 --- a/src/emucore/OSystem.hxx +++ b/src/emucore/OSystem.hxx @@ -40,9 +40,12 @@ class StateManager; class VideoDialog; class EmulationWorker; +#include + #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. diff --git a/src/emucore/tia/TIA.cxx b/src/emucore/tia/TIA.cxx index cf6fd6edc..0efbbd65f 100644 --- a/src/emucore/tia/TIA.cxx +++ b/src/emucore/tia/TIA.cxx @@ -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++; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/emucore/tia/TIA.hxx b/src/emucore/tia/TIA.hxx index 945ec3998..44859ef48 100644 --- a/src/emucore/tia/TIA.hxx +++ b/src/emucore/tia/TIA.hxx @@ -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. diff --git a/src/emucore/tia/frame-manager/AbstractFrameManager.cxx b/src/emucore/tia/frame-manager/AbstractFrameManager.cxx index c17952c7d..541065999 100644 --- a/src/emucore/tia/frame-manager/AbstractFrameManager.cxx +++ b/src/emucore/tia/frame-manager/AbstractFrameManager.cxx @@ -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); } diff --git a/src/emucore/tia/frame-manager/AbstractFrameManager.hxx b/src/emucore/tia/frame-manager/AbstractFrameManager.hxx index 45aef3e38..91e3edff0 100644 --- a/src/emucore/tia/frame-manager/AbstractFrameManager.hxx +++ b/src/emucore/tia/frame-manager/AbstractFrameManager.hxx @@ -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: /**