From 50767933028033a26882fa2121348800e9b8938f Mon Sep 17 00:00:00 2001 From: Robert Broglia Date: Thu, 25 Jan 2024 18:53:09 -0500 Subject: [PATCH] Frame timing logic update * Imagine: Add FrameTimeSource into FrameParams * Imagine: Don't implicitly call setNeedsDraw() in Window::dispatchOnFrame() * EmuFramework: Move all frame timing logic into EmuSystemTask's thread and update it to check if the previous frame was presented before starting a new frame * EmuFramework: Cap frame skip to 4 if the previous frame is falling behind to prevent accumulation of late frames * EmuFramework: Re-implement fast/slow mode using a variable frame rate, allowing for refresh rate switching when speeding up * EmuFramework: Use single-buffered textures since only one frame is ever rendering * EmuFramework: Add EmuSystem::addThreadGroupIds() to add emulator system threads for CPU affinity/performance hint management * C64.emu: Add the emulation thread id to the thread group --- C64.emu/src/main/MainSystem.hh | 3 + EmuFramework/include/emuframework/EmuApp.hh | 7 +- .../include/emuframework/EmuSystem.hh | 13 +- .../include/emuframework/EmuSystemInlines.hh | 6 + .../include/emuframework/EmuSystemTask.hh | 23 +- .../include/emuframework/EmuTiming.hh | 5 - EmuFramework/src/EmuApp.cc | 223 +++++++++--------- EmuFramework/src/EmuSystem.cc | 19 +- EmuFramework/src/EmuSystemTask.cc | 36 +-- EmuFramework/src/EmuTiming.cc | 22 +- EmuFramework/src/EmuVideo.cc | 2 +- EmuFramework/src/OutputTimingManager.cc | 4 +- imagine/include/imagine/base/Window.hh | 13 +- imagine/include/imagine/base/baseDefs.hh | 9 +- imagine/include/imagine/time/Time.hh | 9 + imagine/src/base/common/Screen.cc | 2 +- imagine/src/base/common/Window.cc | 18 +- 17 files changed, 191 insertions(+), 223 deletions(-) diff --git a/C64.emu/src/main/MainSystem.hh b/C64.emu/src/main/MainSystem.hh index 8d662fd9c..be1675a30 100644 --- a/C64.emu/src/main/MainSystem.hh +++ b/C64.emu/src/main/MainSystem.hh @@ -92,6 +92,7 @@ public: EmuAudio *audioPtr{}; struct video_canvas_s *activeCanvas{}; const char *sysFileDir{}; + ThreadId emuThreadId{}; VicePlugin plugin{}; mutable ArchiveIO firmwareArch; std::string defaultPaletteName{}; @@ -134,6 +135,7 @@ public: makeDetachedThread( [this]() { + emuThreadId = thisThreadId(); execSem.acquire(); logMsg("starting maincpu_mainloop()"); plugin.maincpu_mainloop(); @@ -220,6 +222,7 @@ public: void renderFramebuffer(EmuVideo &); bool shouldFastForward() const; bool onVideoRenderFormatChange(EmuVideo &, PixelFormat); + void addThreadGroupIds(std::vector &ids) const { ids.emplace_back(emuThreadId); } protected: void initC64(EmuApp &app); diff --git a/EmuFramework/include/emuframework/EmuApp.hh b/EmuFramework/include/emuframework/EmuApp.hh index 76c9657ea..d435197d1 100644 --- a/EmuFramework/include/emuframework/EmuApp.hh +++ b/EmuFramework/include/emuframework/EmuApp.hh @@ -253,7 +253,8 @@ public: static std::unique_ptr makeView(ViewAttachParams, ViewID); void applyOSNavStyle(IG::ApplicationContext, bool inGame); void setCPUNeedsLowLatency(IG::ApplicationContext, bool needed); - void runFrames(EmuSystemTaskContext, EmuVideo *, EmuAudio *, int frames, bool skipForward); + void advanceFrames(FrameParams, EmuSystemTask *); + void runFrames(EmuSystemTaskContext, EmuVideo *, EmuAudio *, int frames); void skipFrames(EmuSystemTaskContext, int frames, EmuAudio *); bool skipForwardFrames(EmuSystemTaskContext, int frames); IG::Audio::Manager &audioManager() { return audioManager_; } @@ -271,7 +272,7 @@ public: IG::Viewport makeViewport(const Window &win) const; void setEmuViewOnExtraWindow(bool on, IG::Screen &); void record(FrameTimeStatEvent, SteadyClockTimePoint t = {}); - bool supportsPresentModes() const { return windowFrameTimeSource != WindowFrameTimeSource::RENDERER; } + bool supportsPresentModes() const { return windowFrameTimeSource != FrameTimeSource::renderer; } void setIntendedFrameRate(Window &, FrameTimeConfig); static std::u16string_view mainViewName(); void runBenchmarkOneShot(EmuVideo &); @@ -560,7 +561,7 @@ public: IG_UseMemberIf(Config::Input::BLUETOOTH && Config::BASE_CAN_BACKGROUND_APP, bool, keepBluetoothActive){}; IG_UseMemberIf(Config::Input::DEVICE_HOTSWAP, bool, notifyOnInputDeviceChange){true}; IG_UseMemberIf(Config::multipleScreenFrameRates, FrameRate, overrideScreenFrameRate){}; - WindowFrameTimeSource windowFrameTimeSource{WindowFrameTimeSource::AUTO}; + FrameTimeSource windowFrameTimeSource{FrameTimeSource::unset}; IG_UseMemberIf(Config::cpuAffinity, CPUAffinityMode, cpuAffinityMode){CPUAffinityMode::Auto}; IG_UseMemberIf(Config::envIsAndroid && Config::DEBUG_BUILD, bool, useNoopThread){}; IG_UseMemberIf(enableFrameTimeStats, bool, showFrameTimeStats){}; diff --git a/EmuFramework/include/emuframework/EmuSystem.hh b/EmuFramework/include/emuframework/EmuSystem.hh index d3ee34cd8..2d64a2c78 100755 --- a/EmuFramework/include/emuframework/EmuSystem.hh +++ b/EmuFramework/include/emuframework/EmuSystem.hh @@ -239,6 +239,7 @@ public: bool shouldFastForward() const; FS::FileString contentDisplayNameForPath(CStringView path) const; IG::Rotation contentRotation() const; + void addThreadGroupIds(std::vector &) const; ApplicationContext appContext() const { return appCtx; } bool isActive() const { return state == State::ACTIVE; } @@ -342,12 +343,20 @@ public: EmuSystemCreateParams, OnLoadProgressDelegate); int updateAudioFramesPerVideoFrame(); double frameRate() const { return toHz(frameTime()); } + FrameTime scaledFrameTime() const + { + auto t = std::chrono::duration_cast(frameTime()) * frameTimeMultiplier; + return std::chrono::duration_cast(t); + } + double scaledFrameRate() const + { + return toHz(std::chrono::duration_cast(frameTime()) * frameTimeMultiplier); + } void onFrameTimeChanged(); static double audioMixRate(int outputRate, double inputFrameRate, FrameTime outputFrameTime); double audioMixRate(int outputRate, FrameTime outputFrameTime) const { return audioMixRate(outputRate, frameRate(), outputFrameTime); } void configFrameTime(int outputRate, FrameTime outputFrameTime); auto advanceFramesWithTime(SteadyClockTimePoint time) { return emuTiming.advanceFramesWithTime(time); } - void setSpeedMultiplier(EmuAudio &, double speed); SteadyClockTime benchmark(EmuVideo &video); bool hasContent() const; void resetFrameTime(); @@ -385,7 +394,7 @@ protected: public: IG::OnFrameDelegate onFrameUpdate; - double targetSpeed{1.}; + double frameTimeMultiplier{1.}; static constexpr double minFrameRate = 48.; }; diff --git a/EmuFramework/include/emuframework/EmuSystemInlines.hh b/EmuFramework/include/emuframework/EmuSystemInlines.hh index 718ac4ad8..2287066e5 100644 --- a/EmuFramework/include/emuframework/EmuSystemInlines.hh +++ b/EmuFramework/include/emuframework/EmuSystemInlines.hh @@ -274,4 +274,10 @@ void EmuSystem::onStop() static_cast(this)->onStop(); } +void EmuSystem::addThreadGroupIds(std::vector &ids) const +{ + if(&MainSystem::addThreadGroupIds != &EmuSystem::addThreadGroupIds) + static_cast(this)->addThreadGroupIds(ids); +} + } diff --git a/EmuFramework/include/emuframework/EmuSystemTask.hh b/EmuFramework/include/emuframework/EmuSystemTask.hh index 834887787..84aabe708 100644 --- a/EmuFramework/include/emuframework/EmuSystemTask.hh +++ b/EmuFramework/include/emuframework/EmuSystemTask.hh @@ -17,6 +17,7 @@ #include #include +#include #include namespace EmuEx @@ -31,32 +32,20 @@ class EmuApp; class EmuSystemTask { public: - enum class Command: uint8_t + struct FrameParamsCommand { - UNSET, - RUN_FRAME, - PAUSE, - EXIT, - }; - - struct RunFrameCommand - { - EmuVideo *video{}; - EmuAudio *audio{}; - int8_t frames{}; - bool skipForward{}; - bool fastForward{}; + FrameParams params; }; struct PauseCommand {}; struct ExitCommand {}; - using CommandVariant = std::variant; + using CommandVariant = std::variant; struct CommandMessage { std::binary_semaphore *semPtr{}; - CommandVariant command{RunFrameCommand{}}; + CommandVariant command{PauseCommand{}}; void setReplySemaphore(std::binary_semaphore *semPtr_) { assert(!semPtr); semPtr = semPtr_; }; }; @@ -65,7 +54,7 @@ public: void start(); void pause(); void stop(); - void runFrame(EmuVideo *, EmuAudio *, int8_t frames, bool skipForward, bool fastForward); + void updateFrameParams(FrameParams); void sendVideoFormatChangedReply(EmuVideo &); void sendFrameFinishedReply(EmuVideo &); void sendScreenshotReply(bool success); diff --git a/EmuFramework/include/emuframework/EmuTiming.hh b/EmuFramework/include/emuframework/EmuTiming.hh index 522598e3e..174a342b9 100644 --- a/EmuFramework/include/emuframework/EmuTiming.hh +++ b/EmuFramework/include/emuframework/EmuTiming.hh @@ -33,16 +33,11 @@ public: EmuFrameTimeInfo advanceFramesWithTime(SteadyClockTimePoint); void setFrameTime(SteadyClockTime time); void reset(); - void setSpeedMultiplier(double newSpeed); protected: SteadyClockTime timePerVideoFrame{}; - SteadyClockTime timePerVideoFrameScaled{}; SteadyClockTimePoint startFrameTime{}; - double speed = 1; int64_t lastFrame = 0; - - void updateScaledFrameTime(); }; } diff --git a/EmuFramework/src/EmuApp.cc b/EmuFramework/src/EmuApp.cc index ad300ff78..d5a292316 100644 --- a/EmuFramework/src/EmuApp.cc +++ b/EmuFramework/src/EmuApp.cc @@ -515,13 +515,13 @@ void EmuApp::mainInitCommon(IG::ApplicationInitParams initParams, IG::Applicatio auto &screen = *win.screen(); if(!screen.supportsTimestamps() && (!Config::envIsLinux || screen.frameRate() < 100.)) { - windowFrameTimeSource = WindowFrameTimeSource::RENDERER; + windowFrameTimeSource = FrameTimeSource::renderer; } else { - windowFrameTimeSource = WindowFrameTimeSource::SCREEN; + windowFrameTimeSource = FrameTimeSource::screen; } - log.info("timestamp source:{}", windowFrameTimeSource == WindowFrameTimeSource::RENDERER ? "renderer" : "screen"); + log.info("timestamp source:{}", windowFrameTimeSource == FrameTimeSource::renderer ? "renderer" : "screen"); winData.viewController.placeElements(); winData.viewController.pushAndShowMainMenu(viewAttach, emuVideoLayer, emuAudio); configureSecondaryScreens(); @@ -537,81 +537,11 @@ void EmuApp::mainInitCommon(IG::ApplicationInitParams initParams, IG::Applicatio applyRenderPixelFormat(); emuVideoLayer.updateEffect(system(), videoEffectPixelFormat()); emuVideoLayer.setZoom(optionImageZoom); - system().onFrameUpdate = [this, &viewController = winData.viewController](IG::FrameParams params) - { - bool skipForward = false; - bool altSpeed = false; - auto &audio = this->audio(); - auto &sys = system(); - auto &win = viewController.emuWindow(); - if(sys.shouldFastForward()) [[unlikely]] - { - // for skipping loading on disk-based computers - altSpeed = true; - skipForward = true; - sys.setSpeedMultiplier(audio, 8.); - } - else - { - altSpeed = sys.targetSpeed != 1.; - sys.setSpeedMultiplier(audio, sys.targetSpeed); - } - auto frameInfo = sys.advanceFramesWithTime(params.timestamp); - if(!frameInfo.advanced) - { - if(enableBlankFrameInsertion) - { - viewController.drawBlankFrame = true; - win.postDraw(1); - } - return true; - } - int interval = frameInterval(); - auto videoPtr = &this->video(); - if(frameInfo.advanced + savedAdvancedFrames < interval) - { - // running at a lower target fps, skip current frames - savedAdvancedFrames += frameInfo.advanced; - videoPtr = {}; - } - else - { - savedAdvancedFrames = 0; - } - if(videoPtr) - { - if(win.isReady()) - { - if(showFrameTimeStats) - viewController.emuView.updateFrameTimeStats(frameTimeStats, params.timestamp); - record(FrameTimeStatEvent::startOfFrame, params.timestamp); - record(FrameTimeStatEvent::startOfEmulation); - } - else - { - //log.debug("previous async frame not ready yet"); - doIfUsed(frameTimeStats, [&](auto &stats) { stats.missedFrameCallbacks++; }); - } - win.setDrawEventPriority(Window::drawEventPriorityLocked); - } - EmuAudio *audioPtr = audio ? &audio : nullptr; - runTurboInputEvents(); - emuSystemTask.runFrame(videoPtr, audioPtr, frameInfo.advanced, skipForward, altSpeed); - if(videoPtr) - { - if(presentationTimeMode == PresentationTimeMode::full || - (presentationTimeMode == PresentationTimeMode::basic && interval > 1)) - { - viewController.presentTime = params.presentTime(interval); - } - doIfUsed(frameStartTimePoint, [&](auto &tp) - { - if(!hasTime(tp)) - tp = params.timestamp; - }); - } - return true; - }; + system().onFrameUpdate = [this](FrameParams params) + { + emuSystemTask.updateFrameParams(params); + return true; + }; win.onEvent = [this](Window &win, WindowEvent winEvent) { @@ -621,11 +551,7 @@ void EmuApp::mainInitCommon(IG::ApplicationInitParams initParams, IG::Applicatio [&](DrawEvent &e) { record(FrameTimeStatEvent::startOfDraw); - auto reportTime = scopeGuard([&] - { - if(viewController().isShowingEmulation()) - reportFrameWorkTime(); - }); + auto reportTime = scopeGuard([&]{ reportFrameWorkTime(); }); return viewController().drawMainWindow(win, e.params, renderer.task()); }, [&](WindowSurfaceChangeEvent &e) @@ -766,6 +692,97 @@ void EmuApp::mainInitCommon(IG::ApplicationInitParams initParams, IG::Applicatio }); } +void EmuApp::advanceFrames(FrameParams frameParams, EmuSystemTask *taskPtr) +{ + assert(hasTime(frameParams.timestamp)); + auto &sys = system(); + auto &viewCtrl = viewController(); + auto &win = viewCtrl.emuWindow(); + auto *audioPtr = audio() ? &audio() : nullptr; + auto drawIfRendererSource = scopeGuard([&](){ if(frameParams.isFromRenderer()) win.postDraw(1); }); + auto frameInfo = sys.advanceFramesWithTime(frameParams.timestamp); + if(sys.shouldFastForward()) [[unlikely]] + { + // for skipping loading on disk-based computers + if(skipForwardFrames({taskPtr}, 20)) + { + // don't write any audio while skip is in progress + audioPtr = nullptr; + } + frameInfo.advanced = 1; + } + if(!frameInfo.advanced) + { + if(enableBlankFrameInsertion) + { + viewCtrl.drawBlankFrame = true; + win.postDraw(1); + drawIfRendererSource.cancel(); + } + return; + } + int interval = frameInterval(); + bool allowFrameSkip = interval || sys.frameTimeMultiplier != 1.; + if(frameInfo.advanced + savedAdvancedFrames < interval) + { + // running at a lower target fps + savedAdvancedFrames += frameInfo.advanced; + } + else + { + savedAdvancedFrames = 0; + if(!allowFrameSkip) + { + frameInfo.advanced = 1; + } + } + assumeExpr(frameInfo.advanced > 0); + doIfUsed(frameStartTimePoint, [&](auto &tp) + { + if(!hasTime(tp)) + tp = frameParams.timestamp; + }); + EmuVideo *videoPtr = savedAdvancedFrames ? nullptr : &video(); + if(videoPtr && viewCtrl.emuWindow().isDrawing()) + { + //log.debug("previous async frame not ready yet"); + doIfUsed(frameTimeStats, [&](auto &stats) { stats.missedFrameCallbacks++; }); + if(allowFrameSkip) + { + // cap advanced frames if we're falling behind + frameInfo.advanced = std::min(frameInfo.advanced, 4); + } + else + { + reportFrameWorkTime(); + return; + } + videoPtr = nullptr; + } + if(videoPtr) + { + if(showFrameTimeStats) + { + renderer.mainTask.awaitPending(); + viewCtrl.emuView.updateFrameTimeStats(frameTimeStats, frameParams.timestamp); + } + record(FrameTimeStatEvent::startOfFrame, frameParams.timestamp); + record(FrameTimeStatEvent::startOfEmulation); + win.setDrawEventPriority(Window::drawEventPriorityLocked); + if(presentationTimeMode == PresentationTimeMode::full || + (presentationTimeMode == PresentationTimeMode::basic && interval > 1)) + { + viewCtrl.presentTime = frameParams.presentTime(interval); + } + drawIfRendererSource.cancel(); + } + runTurboInputEvents(); + //log.debug("running {} frame(s), skip:{}", frameInfo.advanced, !videoPtr); + runFrames({taskPtr}, videoPtr, audioPtr, frameInfo.advanced); + if(!videoPtr) + reportFrameWorkTime(); +} + IG::Viewport EmuApp::makeViewport(const IG::Window &win) const { IG::WindowRect viewRect = layoutBehindSystemUI ? win.bounds() : win.contentBounds(); @@ -1437,8 +1454,10 @@ void EmuApp::resetInput() void EmuApp::setRunSpeed(double speed) { assumeExpr(speed > 0.); - bool altSpeedActive = speed != 1.; - system().targetSpeed = speed; + syncEmulationThread(); + system().frameTimeMultiplier = 1. / speed; + audio().setSpeedMultiplier(speed); + configFrameTime(); } FS::PathString EmuApp::sessionConfigPath() @@ -1573,25 +1592,9 @@ FrameTimeConfig EmuApp::configFrameTime() return frameTimeConfig; } -void EmuApp::runFrames(EmuSystemTaskContext taskCtx, EmuVideo *video, EmuAudio *audio, int frames, bool skipForward) +void EmuApp::runFrames(EmuSystemTaskContext taskCtx, EmuVideo *video, EmuAudio *audio, int frames) { - if(skipForward) [[unlikely]] - { - if(skipForwardFrames(taskCtx, frames - 1)) - { - // don't write any audio while skip is in progress - audio = nullptr; - } - else - { - // restore normal speed when skip ends - system().setSpeedMultiplier(*audio, 1.); - } - } - else - { - skipFrames(taskCtx, frames - 1, audio); - } + skipFrames(taskCtx, frames - 1, audio); system().runFrame(taskCtx, video, audio); system().updateBackupMemoryCounter(); } @@ -1745,11 +1748,7 @@ void EmuApp::setEmuViewOnExtraWindow(bool on, IG::Screen &screen) [&](Input::Event &e) { return viewController().extraWindowInputEvent(e); }, [&](DrawEvent &e) { - auto reportTime = scopeGuard([&] - { - if(viewController().isShowingEmulation()) - reportFrameWorkTime(); - }); + auto reportTime = scopeGuard([&]{ reportFrameWorkTime(); }); return viewController().drawExtraWindow(win, e.params, renderer.task()); }, [&](WindowSurfaceChangeEvent &e) @@ -1927,7 +1926,13 @@ void EmuApp::applyCPUAffinity(bool active) { if(cpuAffinityMode == CPUAffinityMode::Any) return; - auto frameThreadGroup = std::array{emuSystemTask.threadId(), renderer.task().threadId()}; + auto frameThreadGroup = std::vector{emuSystemTask.threadId(), renderer.task().threadId()}; + system().addThreadGroupIds(frameThreadGroup); + for(auto [idx, id] : enumerate(frameThreadGroup)) + { + if(!id) + log.warn("invalid thread group id @ index:{}", idx); + } if(cpuAffinityMode == CPUAffinityMode::Auto && perfHintManager) { if(active) diff --git a/EmuFramework/src/EmuSystem.cc b/EmuFramework/src/EmuSystem.cc index 30346ce47..6a42545b5 100644 --- a/EmuFramework/src/EmuSystem.cc +++ b/EmuFramework/src/EmuSystem.cc @@ -106,12 +106,6 @@ DynArray EmuSystem::uncompressGzipState(std::span buff, size_t return uncompArr; } -void EmuSystem::setSpeedMultiplier(EmuAudio &emuAudio, double speed) -{ - emuTiming.setSpeedMultiplier(speed); - emuAudio.setSpeedMultiplier(speed); -} - void EmuSystem::setupContentUriPaths(CStringView uri, std::string_view displayName) { contentFileName_ = displayName; @@ -339,16 +333,19 @@ void EmuSystem::configFrameTime(int outputRate, FrameTime outputFrameTime) { if(!hasContent()) return; - configAudioRate(outputFrameTime, outputRate); - audioFramesPerVideoFrameFloat = outputRate * duration_cast(outputFrameTime).count(); - audioFramesPerVideoFrame = std::ceil(audioFramesPerVideoFrameFloat); - currentAudioFramesPerVideoFrame = audioFramesPerVideoFrameFloat; + if(frameTimeMultiplier == 1.) + { + configAudioRate(outputFrameTime, outputRate); + audioFramesPerVideoFrameFloat = outputRate * duration_cast(outputFrameTime).count(); + audioFramesPerVideoFrame = std::ceil(audioFramesPerVideoFrameFloat); + currentAudioFramesPerVideoFrame = audioFramesPerVideoFrameFloat; + } emuTiming.setFrameTime(outputFrameTime); } void EmuSystem::onFrameTimeChanged() { - log.info("frame rate changed:{}", frameRate()); + log.info("frame rate changed:{}", scaledFrameRate()); EmuApp::get(appContext()).configFrameTime(); } diff --git a/EmuFramework/src/EmuSystemTask.cc b/EmuFramework/src/EmuSystemTask.cc index 47cacf3e8..81dd16ca9 100644 --- a/EmuFramework/src/EmuSystemTask.cc +++ b/EmuFramework/src/EmuSystemTask.cc @@ -27,6 +27,10 @@ constexpr SystemLogger log{"EmuSystemTask"}; EmuSystemTask::EmuSystemTask(EmuApp &app): app{app} {} +using FrameParamsCommand = EmuSystemTask::FrameParamsCommand; +using PauseCommand = EmuSystemTask::PauseCommand; +using ExitCommand = EmuSystemTask::ExitCommand; + void EmuSystemTask::start() { if(taskThread.joinable()) @@ -39,30 +43,20 @@ void EmuSystemTask::start() bool started = true; commandPort.attach(eventLoop, [this, &started](auto msgs) { - constexpr int frameProccessLimit = 20; - const int maxFrames = app.frameInterval() ? frameProccessLimit : 1; - int fastForwardFrames{}; - RunFrameCommand runCmd{}; + FrameParams frameParams{}; for(auto msg : msgs) { bool threadIsRunning = visit(overloaded { - [&](RunFrameCommand &run) + [&](FrameParamsCommand &cmd) { - runCmd.video = run.video; - runCmd.audio = run.audio; - // accumulate the total frames from all commands in queue - if(!run.fastForward) - runCmd.frames = std::min(runCmd.frames + run.frames, maxFrames); - else - fastForwardFrames += run.frames; - runCmd.skipForward = run.skipForward; + frameParams = cmd.params; return true; }, [&](PauseCommand &) { //log.debug("got pause command"); - runCmd.frames = fastForwardFrames = 0; + frameParams = {}; assumeExpr(msg.semPtr); msg.semPtr->release(); return true; @@ -77,13 +71,8 @@ void EmuSystemTask::start() if(!threadIsRunning) return false; } - runCmd.frames = std::min(runCmd.frames + fastForwardFrames, frameProccessLimit); - if(!runCmd.frames) - return true; - assumeExpr(runCmd.frames > 0); - //log.debug("running {} frame(s)", runCmd.frames); - app.runFrames({this}, runCmd.video, runCmd.audio, - runCmd.frames, runCmd.skipForward); + if(hasTime(frameParams.timestamp)) + app.advanceFrames(frameParams, this); return true; }); sem.release(); @@ -112,12 +101,11 @@ void EmuSystemTask::stop() app.flushMainThreadMessages(); } -void EmuSystemTask::runFrame(EmuVideo *video, EmuAudio *audio, int8_t frames, bool skipForward, bool fastForward) +void EmuSystemTask::updateFrameParams(FrameParams params) { - assumeExpr(frames > 0); if(!taskThread.joinable()) [[unlikely]] return; - commandPort.send({.command = RunFrameCommand{video, audio, frames, skipForward, fastForward}}); + commandPort.send({.command = FrameParamsCommand{params}}); } void EmuSystemTask::sendVideoFormatChangedReply(EmuVideo &video) diff --git a/EmuFramework/src/EmuTiming.cc b/EmuFramework/src/EmuTiming.cc index f9c09548d..3b9f5c0fc 100644 --- a/EmuFramework/src/EmuTiming.cc +++ b/EmuFramework/src/EmuTiming.cc @@ -31,13 +31,13 @@ EmuFrameTimeInfo EmuTiming::advanceFramesWithTime(SteadyClockTimePoint time) // first frame startFrameTime = time; lastFrame = 0; - return {1}; + return {}; } - assumeExpr(timePerVideoFrameScaled.count() > 0); + assumeExpr(timePerVideoFrame.count() > 0); assumeExpr(startFrameTime.time_since_epoch().count() > 0); assumeExpr(time > startFrameTime); auto timeTotal = time - startFrameTime; - auto now = divRoundClosestPositive(timeTotal.count(), timePerVideoFrameScaled.count()); + auto now = divRoundClosestPositive(timeTotal.count(), timePerVideoFrame.count()); int elapsedFrames = now - lastFrame; lastFrame = now; return {elapsedFrames}; @@ -46,7 +46,6 @@ EmuFrameTimeInfo EmuTiming::advanceFramesWithTime(SteadyClockTimePoint time) void EmuTiming::setFrameTime(SteadyClockTime time) { timePerVideoFrame = time; - updateScaledFrameTime(); log.info("configured frame time:{} ({:g} fps)", timePerVideoFrame, toHz(time)); reset(); } @@ -56,19 +55,4 @@ void EmuTiming::reset() startFrameTime = {}; } -void EmuTiming::setSpeedMultiplier(double newSpeed) -{ - assumeExpr(newSpeed > 0.); - if(speed == newSpeed) - return; - speed = newSpeed; - updateScaledFrameTime(); - reset(); -} - -void EmuTiming::updateScaledFrameTime() -{ - timePerVideoFrameScaled = speed == 1. ? timePerVideoFrame : round(FloatSeconds{timePerVideoFrame} / speed); -} - } diff --git a/EmuFramework/src/EmuVideo.cc b/EmuFramework/src/EmuVideo.cc index be56f88b6..f40b9264e 100644 --- a/EmuFramework/src/EmuVideo.cc +++ b/EmuFramework/src/EmuVideo.cc @@ -71,7 +71,7 @@ bool EmuVideo::setFormat(IG::PixmapDesc desc, EmuSystemTaskContext taskCtx) { Gfx::TextureConfig conf{desc, samplerConfig()}; conf.colorSpace = colSpace; - vidImg = renderer().makePixmapBufferTexture(conf, bufferMode); + vidImg = renderer().makePixmapBufferTexture(conf, bufferMode, true); } else { diff --git a/EmuFramework/src/OutputTimingManager.cc b/EmuFramework/src/OutputTimingManager.cc index 0784e555e..9dca7ee2c 100644 --- a/EmuFramework/src/OutputTimingManager.cc +++ b/EmuFramework/src/OutputTimingManager.cc @@ -81,8 +81,8 @@ FrameTimeConfig OutputTimingManager::frameTimeConfig(const EmuSystem &system, st if(t.count() > 0) return {t, FrameRate(toHz(t)), 0}; else if(t == originalOption) - return {system.frameTime(), FrameRate(system.frameRate()), 0}; - return bestOutputTimeForScreen(supportedFrameRates, system.frameTime()); + return {system.scaledFrameTime(), FrameRate(system.scaledFrameRate()), 0}; + return bestOutputTimeForScreen(supportedFrameRates, system.scaledFrameTime()); } } diff --git a/imagine/include/imagine/base/Window.hh b/imagine/include/imagine/base/Window.hh index bd7d68488..059b9e96f 100644 --- a/imagine/include/imagine/base/Window.hh +++ b/imagine/include/imagine/base/Window.hh @@ -25,25 +25,15 @@ #include #include -namespace IG::Input -{ -class Event; -class KeyEvent; -} - namespace IG { -class Screen; -class ApplicationContext; -class Application; class PixelFormat; +enum class FrameTimeSource : uint8_t; class Window : public WindowImpl { public: - using FrameTimeSource = WindowFrameTimeSource; - Window(ApplicationContext, WindowConfig, InitDelegate); void show(); void dismiss(); @@ -60,6 +50,7 @@ public: int8_t setDrawEventPriority(int8_t = 0); int8_t drawEventPriority() const; bool isReady() const { return drawPhase == DrawPhase::READY; } + bool isDrawing() const { return drawPhase == DrawPhase::DRAW || drawEventPriority() == drawEventPriorityLocked; } void drawNow(bool needsSync = false); Screen *screen() const; NativeWindow nativeObject() const; diff --git a/imagine/include/imagine/base/baseDefs.hh b/imagine/include/imagine/base/baseDefs.hh index 1d50eab9e..5ced5df53 100644 --- a/imagine/include/imagine/base/baseDefs.hh +++ b/imagine/include/imagine/base/baseDefs.hh @@ -137,6 +137,8 @@ namespace IG::Input class Event; class Device; +class KeyEvent; +class MotionEvent; enum class DeviceChange: uint8_t { added, removed, updated, shown, hidden, connectError }; @@ -227,13 +229,6 @@ struct WindowDrawParams bool needsSync{}; }; -enum class WindowFrameTimeSource : uint8_t -{ - AUTO, - SCREEN, - RENDERER, -}; - enum class ScreenChange : int8_t { added, removed, frameRate }; WISE_ENUM_CLASS((SensorType, uint8_t), diff --git a/imagine/include/imagine/time/Time.hh b/imagine/include/imagine/time/Time.hh index 929786d43..b1688be7a 100644 --- a/imagine/include/imagine/time/Time.hh +++ b/imagine/include/imagine/time/Time.hh @@ -92,15 +92,24 @@ inline SteadyClockTime timeFuncDebug(auto &&func, auto &&...args) #endif } +enum class FrameTimeSource : uint8_t +{ + unset, + screen, + renderer, +}; + class FrameParams { public: SteadyClockTimePoint timestamp; SteadyClockTime frameTime; + FrameTimeSource timeSource; SteadyClockTimePoint presentTime(int frames) const; int elapsedFrames(SteadyClockTimePoint lastTimestamp) const; static int elapsedFrames(SteadyClockTimePoint timestamp, SteadyClockTimePoint lastTimestamp, SteadyClockTime frameTime); + bool isFromRenderer() const { return timeSource == FrameTimeSource::renderer; } }; using FrameRate = float; diff --git a/imagine/src/base/common/Screen.cc b/imagine/src/base/common/Screen.cc index ef747d049..06991ec94 100644 --- a/imagine/src/base/common/Screen.cc +++ b/imagine/src/base/common/Screen.cc @@ -113,7 +113,7 @@ void Screen::setActive(bool active) FrameParams Screen::makeFrameParams(SteadyClockTimePoint timestamp) const { - return {.timestamp = timestamp, .frameTime = frameTime()}; + return {.timestamp = timestamp, .frameTime = frameTime(), .timeSource = FrameTimeSource::screen}; } void Screen::postFrame() diff --git a/imagine/src/base/common/Window.cc b/imagine/src/base/common/Window.cc index a66258c7d..3ac005fa7 100644 --- a/imagine/src/base/common/Window.cc +++ b/imagine/src/base/common/Window.cc @@ -66,15 +66,15 @@ void BaseWindow::attachDrawEvent() }); } -static auto frameClock(const Window &win, WindowFrameTimeSource clock) +static auto frameClock(const Window &win, FrameTimeSource clock) { - return clock == Window::FrameTimeSource::AUTO ? win.defaultFrameTimeSource() : clock; + return clock == FrameTimeSource::unset ? win.defaultFrameTimeSource() : clock; } bool Window::addOnFrame(OnFrameDelegate del, FrameTimeSource clock, int priority) { clock = frameClock(*this, clock); - if(clock == FrameTimeSource::SCREEN) + if(clock == FrameTimeSource::screen) { return screen()->addOnFrame(del); } @@ -94,7 +94,7 @@ bool Window::addOnFrame(OnFrameDelegate del, FrameTimeSource clock, int priority bool Window::removeOnFrame(OnFrameDelegate del, FrameTimeSource clock) { clock = frameClock(*this, clock); - if(clock == FrameTimeSource::SCREEN) + if(clock == FrameTimeSource::screen) { return screen()->removeOnFrame(del); } @@ -110,9 +110,9 @@ bool Window::moveOnFrame(Window &srcWin, OnFrameDelegate del, FrameTimeSource sr return addOnFrame(del, src); } -WindowFrameTimeSource Window::defaultFrameTimeSource() const +FrameTimeSource Window::defaultFrameTimeSource() const { - return screen()->supportsTimestamps() ? FrameTimeSource::SCREEN : FrameTimeSource::RENDERER; + return screen()->supportsTimestamps() ? FrameTimeSource::screen : FrameTimeSource::renderer; } void Window::resetAppData() @@ -290,12 +290,8 @@ void Window::dispatchOnFrame() } drawPhase = DrawPhase::UPDATE; //log.debug("running {} onFrame delegates", onFrame.size()); - FrameParams frameParams{.timestamp = SteadyClock::now(), .frameTime = screen()->frameTime()}; + FrameParams frameParams{.timestamp = SteadyClock::now(), .frameTime = screen()->frameTime(), .timeSource = FrameTimeSource::renderer}; onFrame.runAll([&](OnFrameDelegate del){ return del(frameParams); }); - if(onFrame.size()) - { - setNeedsDraw(true); - } } void Window::draw(bool needsSync)