diff --git a/include/knativescreen.h b/include/knativescreen.h index 002bdc226..f484c36c0 100644 --- a/include/knativescreen.h +++ b/include/knativescreen.h @@ -45,6 +45,8 @@ class KNativeScreen { // if isDirty is false then the cached texture will be drawn virtual void putBitsOnWnd(U32 id, U8* bits, U32 bitsPerPixel, U32 srcPitch, S32 dstX, S32 dstY, U32 width, U32 height, U32* palette, bool isDirty) = 0; virtual void present() = 0; + virtual void beginX11Frame() {} + virtual void presentX11() { present(); } virtual bool presentedSinceLastCheck() = 0; virtual void clearTextureCache(U32 id) = 0; virtual bool canBltToScreen() = 0; @@ -72,4 +74,4 @@ class KNativeScreen { typedef std::shared_ptr KNativeScreenPtr; -#endif \ No newline at end of file +#endif diff --git a/include/ksdlfpshud.h b/include/ksdlfpshud.h new file mode 100644 index 000000000..33afa55d5 --- /dev/null +++ b/include/ksdlfpshud.h @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2012-2025 The BoxedWine Team + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef __KSDLFPSHUD_H__ +#define __KSDLFPSHUD_H__ + +#include "boxedwine.h" +#include +#include +#ifdef __EMSCRIPTEN__ +#include +#endif + +static inline const U8* kSDLFpsHudGlyph(char c) { + static const U8 zero[5] = {7, 5, 5, 5, 7}; + static const U8 one[5] = {2, 6, 2, 2, 7}; + static const U8 two[5] = {7, 1, 7, 4, 7}; + static const U8 three[5] = {7, 1, 7, 1, 7}; + static const U8 four[5] = {5, 5, 7, 1, 1}; + static const U8 five[5] = {7, 4, 7, 1, 7}; + static const U8 six[5] = {7, 4, 7, 5, 7}; + static const U8 seven[5] = {7, 1, 1, 1, 1}; + static const U8 eight[5] = {7, 5, 7, 5, 7}; + static const U8 nine[5] = {7, 5, 7, 1, 7}; + static const U8 a[5] = {7, 5, 7, 5, 5}; + static const U8 e[5] = {7, 4, 6, 4, 7}; + static const U8 f[5] = {7, 4, 6, 4, 4}; + static const U8 m[5] = {5, 7, 7, 5, 5}; + static const U8 p[5] = {6, 5, 6, 4, 4}; + static const U8 s[5] = {7, 4, 7, 1, 7}; + static const U8 percent[5] = {5, 1, 2, 4, 5}; + + switch (c) { + case '0': return zero; + case '1': return one; + case '2': return two; + case '3': return three; + case '4': return four; + case '5': return five; + case '6': return six; + case '7': return seven; + case '8': return eight; + case '9': return nine; + case 'A': return a; + case 'E': return e; + case 'F': return f; + case 'M': return m; + case 'P': return p; + case 'S': return s; + case '%': return percent; + default: return nullptr; + } +} + +static inline U64& kSDLFpsHudLastRenderTime() { + static U64 value; + return value; +} + +static inline U32& kSDLFpsHudSameCandidateFrames() { + static U32 value; + return value; +} + +static inline U32& kSDLFpsHudUnchangedFrames() { + static U32 value; + return value; +} + +static inline U32& kSDLFpsHudDisplayedSamePercent() { + static U32 value; + return value; +} + +static inline U64& kSDLFpsHudAverageStartTime() { + static U64 value; + return value; +} + +static inline U32& kSDLFpsHudAveragePresentedFrames() { + static U32 value; + return value; +} + +static inline U32& kSDLFpsHudAverageCandidateFrames() { + static U32 value; + return value; +} + +static inline U32& kSDLFpsHudAverageCounter() { + static U32 value; + return value; +} + +static inline void kSDLFpsHudRecordSameCandidate(bool unchanged) { + kSDLFpsHudSameCandidateFrames()++; + if (unchanged) { + kSDLFpsHudUnchangedFrames()++; + } +} + +static inline bool kSDLFpsHudNeedsRefresh() { + U64 lastRenderTime = kSDLFpsHudLastRenderTime(); + return KSystem::showFPS && lastRenderTime && KSystem::getMicroCounter() - lastRenderTime >= 1000000; +} + +static inline void kSDLFpsHudRecordFrameForConsole(bool presented) { +#ifdef __EMSCRIPTEN__ + if (!KSystem::showFPS) { + return; + } + + U64 now = KSystem::getMicroCounter(); + if (!kSDLFpsHudAverageStartTime()) { + kSDLFpsHudAverageStartTime() = now; + } + + kSDLFpsHudAverageCandidateFrames()++; + if (presented) { + kSDLFpsHudAveragePresentedFrames()++; + } + + U64 elapsed = now - kSDLFpsHudAverageStartTime(); + if (elapsed >= 20000000) { + U32 counter = ++kSDLFpsHudAverageCounter(); + U32 presentedFrames = kSDLFpsHudAveragePresentedFrames(); + U32 candidateFrames = kSDLFpsHudAverageCandidateFrames(); + double averageFPS = (double)presentedFrames * 1000000.0 / (double)elapsed; + EM_ASM({ + console.log("[BoxedWine] X11 average FPS sample=" + $0 + " fps=" + $1.toFixed(2) + " presentedFrames=" + $2 + " candidateFrames=" + $3); + }, counter, averageFPS, presentedFrames, candidateFrames); + + kSDLFpsHudAverageStartTime() = now; + kSDLFpsHudAveragePresentedFrames() = 0; + kSDLFpsHudAverageCandidateFrames() = 0; + } +#endif +} + +static inline int kSDLFpsHudTextWidth(const char* text, int scale) { + int width = 0; + for (const char* p = text; *p; ++p) { + width += (*p == ' ') ? 2 * scale : 4 * scale; + } + return width ? width - scale : 0; +} + +static inline void kSDLFpsHudDrawGlyph(SDL_Renderer* renderer, const U8* glyph, int x, int y, int scale) { + for (int row = 0; row < 5; ++row) { + for (int col = 0; col < 3; ++col) { + if (glyph[row] & (1 << (2 - col))) { + SDL_Rect rect = {x + col * scale, y + row * scale, scale, scale}; + SDL_RenderFillRect(renderer, &rect); + } + } + } +} + +static inline void kSDLFpsHudDrawText(SDL_Renderer* renderer, const char* text, int x, int y, int scale) { + int cursor = x; + for (const char* p = text; *p; ++p) { + if (*p == ' ') { + cursor += 2 * scale; + continue; + } + const U8* glyph = kSDLFpsHudGlyph(*p); + if (glyph) { + kSDLFpsHudDrawGlyph(renderer, glyph, cursor, y, scale); + } + cursor += 4 * scale; + } +} + +static inline void kSDLFpsHudRender(SDL_Renderer* renderer, bool showSamePercent = false) { + if (!KSystem::showFPS || !renderer) { + return; + } + + static U64 lastUpdateTime; + static U32 presentedFrames; + static U32 displayedFPS; + + U64 now = KSystem::getMicroCounter(); + if (!lastUpdateTime) { + lastUpdateTime = now; + } + presentedFrames++; + U64 elapsed = now - lastUpdateTime; + if (elapsed >= 1000000) { + displayedFPS = (U32)((presentedFrames * 1000000ULL + elapsed / 2) / elapsed); + presentedFrames = 0; + lastUpdateTime = now; + + U32 sameCandidates = kSDLFpsHudSameCandidateFrames(); + if (sameCandidates) { + kSDLFpsHudDisplayedSamePercent() = kSDLFpsHudUnchangedFrames() * 100 / sameCandidates; + } else { + kSDLFpsHudDisplayedSamePercent() = 0; + } + kSDLFpsHudSameCandidateFrames() = 0; + kSDLFpsHudUnchangedFrames() = 0; + } + kSDLFpsHudLastRenderTime() = now; + + char fpsText[16]; + snprintf(fpsText, sizeof(fpsText), "FPS %u", displayedFPS); + + char sameText[16]; + snprintf(sameText, sizeof(sameText), "SAME %u%%", kSDLFpsHudDisplayedSamePercent()); + + int outputWidth = 0; + int outputHeight = 0; + SDL_GetRendererOutputSize(renderer, &outputWidth, &outputHeight); + if (outputWidth <= 0 || outputHeight <= 0) { + return; + } + + int scale = outputWidth < 400 ? 2 : 3; + int padding = scale * 2; + int fpsTextWidth = kSDLFpsHudTextWidth(fpsText, scale); + int sameTextWidth = showSamePercent ? kSDLFpsHudTextWidth(sameText, scale) : 0; + int textWidth = sameTextWidth > fpsTextWidth ? sameTextWidth : fpsTextWidth; + int textHeight = showSamePercent ? 11 * scale : 5 * scale; + int x = outputWidth - textWidth - padding * 2; + int y = padding; + + Uint8 oldR = 0; + Uint8 oldG = 0; + Uint8 oldB = 0; + Uint8 oldA = 0; + SDL_BlendMode oldBlendMode = SDL_BLENDMODE_NONE; + SDL_GetRenderDrawColor(renderer, &oldR, &oldG, &oldB, &oldA); + SDL_GetRenderDrawBlendMode(renderer, &oldBlendMode); + + SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); + SDL_SetRenderDrawColor(renderer, 0, 0, 0, 160); + SDL_Rect bg = {x - padding, y - padding, textWidth + padding * 2, textHeight + padding * 2}; + SDL_RenderFillRect(renderer, &bg); + + SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); + kSDLFpsHudDrawText(renderer, fpsText, x, y, scale); + if (showSamePercent) { + kSDLFpsHudDrawText(renderer, sameText, x, y + 6 * scale, scale); + } + + SDL_SetRenderDrawBlendMode(renderer, oldBlendMode); + SDL_SetRenderDrawColor(renderer, oldR, oldG, oldB, oldA); +} + +#endif diff --git a/include/ksdlframeskip.h b/include/ksdlframeskip.h new file mode 100644 index 000000000..96ad7f125 --- /dev/null +++ b/include/ksdlframeskip.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2012-2025 The BoxedWine Team + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef __KSDLFRAMESKIP_H__ +#define __KSDLFRAMESKIP_H__ + +#include "boxedwine.h" + +static inline bool kSDLFrameSkipShouldRender() { +#ifdef __EMSCRIPTEN__ + U32 framesToSkip = KSystem::skipFrameFPS; + if (!framesToSkip) { + return true; + } + + static U32 lastFramesToSkip; + static U32 framesUntilRender; + if (lastFramesToSkip != framesToSkip) { + lastFramesToSkip = framesToSkip; + framesUntilRender = 0; + } + + if (framesUntilRender) { + framesUntilRender--; + return false; + } + framesUntilRender = framesToSkip; +#endif + return true; +} + +#endif diff --git a/include/ksystem.h b/include/ksystem.h index 559e03dd9..6ceec95db 100644 --- a/include/ksystem.h +++ b/include/ksystem.h @@ -96,9 +96,13 @@ class KSystem { #ifdef BOXEDWINE_MULTI_THREADED static U32 cpuAffinityCountForApp; #endif - static U32 pollRate; - static U32 skipFrameFPS; - static BWriteFile logFile; + static U32 pollRate; + static U32 skipFrameFPS; +#ifdef __EMSCRIPTEN__ + static bool skipUnchangedFrames; + static bool showFPS; +#endif + static BWriteFile logFile; static std::function watchTTY; static bool ttyPrepend; static BString exePath; diff --git a/platform/sdl/knativescreenSDL.cpp b/platform/sdl/knativescreenSDL.cpp index c4e014903..9717fabc3 100644 --- a/platform/sdl/knativescreenSDL.cpp +++ b/platform/sdl/knativescreenSDL.cpp @@ -24,8 +24,17 @@ #include "sdlcallback.h" #include "knativeinputSDL.h" #include "knativescreenSDL.h" +#ifdef __EMSCRIPTEN__ +#include "ksdlfpshud.h" +#endif #include "../../source/x11/x11.h" +#ifdef __EMSCRIPTEN__ +static void kSDLHashValue(U64& hash, U64 value) { + hash ^= value + 0x9e3779b97f4a7c15ULL + (hash << 6) + (hash >> 2); +} +#endif + KNativeScreenSDL::KNativeScreenSDL(U32 cx, U32 cy, U32 bpp, int scaleX, int scaleY, const BString& scaleQuality, U32 fullScreen, U32 vsync) { input = std::make_shared(cx, cy, scaleX, scaleY); this->bpp = bpp; @@ -188,6 +197,25 @@ void KNativeScreenSDL::clear() { } } +void KNativeScreenSDL::beginX11Frame() { +#ifdef __EMSCRIPTEN__ + compositionHash = 1469598103934665603ULL; + compositionHasContent = false; + if (KSystem::skipUnchangedFrames) { + kSDLHashValue(compositionHash, 58); + kSDLHashValue(compositionHash, 110); + kSDLHashValue(compositionHash, 165); + kSDLHashValue(compositionHash, screenWidth()); + kSDLHashValue(compositionHash, screenHeight()); + kSDLHashValue(compositionHash, input->scaleX); + kSDLHashValue(compositionHash, input->scaleY); + kSDLHashValue(compositionHash, input->scaleXOffset); + kSDLHashValue(compositionHash, input->scaleYOffset); + compositionHasContent = true; + } +#endif +} + void KNativeScreenSDL::putBitsOnWnd(U32 id, U8* bits, U32 bitsPerPixel, U32 srcPitch, S32 dstX, S32 dstY, U32 width, U32 height, U32* palette, bool isDirty) { if (!bitsPerPixel || !srcPitch) { return; @@ -249,6 +277,11 @@ void KNativeScreenSDL::putBitsOnWnd(U32 id, U8* bits, U32 bitsPerPixel, U32 srcP } bits = wnd->bits; } +#ifdef __EMSCRIPTEN__ + if (KSystem::skipUnchangedFrames && isDirty) { + wnd->contentSerial = ++nextContentSerial; + } +#endif #ifdef BOXEDWINE_RECORDER if ((Recorder::instance || Player::instance) && screenBpp() > 8) { int wndWidth = width; @@ -303,16 +336,65 @@ void KNativeScreenSDL::putBitsOnWnd(U32 id, U8* bits, U32 bitsPerPixel, U32 srcP dstrect.y = dstY * (int)input->scaleY / 100 + input->scaleYOffset; dstrect.w = wnd->sdlTextureWidth * (int)input->scaleX / 100; dstrect.h = wnd->sdlTextureHeight * (int)input->scaleY / 100; +#ifdef __EMSCRIPTEN__ + if (KSystem::skipUnchangedFrames) { + kSDLHashValue(compositionHash, id); + kSDLHashValue(compositionHash, wnd->contentSerial); + kSDLHashValue(compositionHash, (U32)dstrect.x); + kSDLHashValue(compositionHash, (U32)dstrect.y); + kSDLHashValue(compositionHash, (U32)dstrect.w); + kSDLHashValue(compositionHash, (U32)dstrect.h); + kSDLHashValue(compositionHash, wnd->sdlTextureWidth); + kSDLHashValue(compositionHash, wnd->sdlTextureHeight); + compositionHasContent = true; + } +#endif SDL_RenderCopy(renderer, wnd->sdlTexture, nullptr, &dstrect); } } void KNativeScreenSDL::present() { + presentInternal(false); +} + +void KNativeScreenSDL::presentX11() { + presentInternal(true); +} + +void KNativeScreenSDL::presentInternal(bool x11Frame) { if (KSystem::videoOption != VIDEO_NO_WINDOW) { if (showOnDraw) { showWindow(true); } +#ifdef __EMSCRIPTEN__ + if (x11Frame && KSystem::skipUnchangedFrames && renderer) { + bool unchanged = compositionHasContent && hasLastCompositionHash && lastCompositionHash == compositionHash; + if (compositionHasContent && hasLastCompositionHash) { + kSDLFpsHudRecordSameCandidate(unchanged); + } + bool hudNeedsRefresh = kSDLFpsHudNeedsRefresh(); + lastCompositionHash = compositionHash; + hasLastCompositionHash = compositionHasContent; + if (unchanged && !hudNeedsRefresh) { + kSDLFpsHudRecordFrameForConsole(false); +#ifdef BOXEDWINE_RECORDER + if (Recorder::instance) { + BOXEDWINE_MUTEX_UNLOCK(drawingMutex); + } +#endif + return; + } + } + if (x11Frame) { + kSDLFpsHudRender(renderer, KSystem::skipUnchangedFrames); + } +#endif SDL_RenderPresent(renderer); +#ifdef __EMSCRIPTEN__ + if (x11Frame) { + kSDLFpsHudRecordFrameForConsole(true); + } +#endif } presented = true; #ifdef BOXEDWINE_RECORDER diff --git a/platform/sdl/knativescreenSDL.h b/platform/sdl/knativescreenSDL.h index 63371a251..4f446a746 100644 --- a/platform/sdl/knativescreenSDL.h +++ b/platform/sdl/knativescreenSDL.h @@ -31,9 +31,12 @@ class WndCache { SDL_Texture* sdlTexture = nullptr; U32 sdlTextureHeight = 0; U32 sdlTextureWidth = 0; +#ifdef __EMSCRIPTEN__ + U64 contentSerial = 0; +#endif U8* bits = nullptr; - U32 bitsSize; + U32 bitsSize = 0; void ensureSize(U32 size) { if (bitsSize < size) { @@ -85,6 +88,8 @@ class KNativeScreenSDL : public KNativeScreen { void clear() override; void putBitsOnWnd(U32 id, U8* bits, U32 bitsPerPixel, U32 srcPitch, S32 dstX, S32 dstY, U32 width, U32 height, U32* palette, bool isDirty) override; void present() override; + void beginX11Frame() override; + void presentX11() override; bool presentedSinceLastCheck() override; void clearTextureCache(U32 id) override; bool canBltToScreen() override; @@ -130,6 +135,7 @@ class KNativeScreenSDL : public KNativeScreen { SDL_Renderer* renderer = nullptr; U32 additionalSDLWindowFlags = 0; + void presentInternal(bool x11Frame); void recreateMainWindow(); void destroyMainWindow(); @@ -147,9 +153,17 @@ class KNativeScreenSDL : public KNativeScreen { U32 recordBufferSize = 0; #endif +#ifdef __EMSCRIPTEN__ + U64 nextContentSerial = 0; + U64 compositionHash = 0; + U64 lastCompositionHash = 0; + bool compositionHasContent = false; + bool hasLastCompositionHash = false; +#endif + BOXEDWINE_MUTEX drawingMutex; }; typedef std::shared_ptr KNativeScreenSDLPtr; -#endif \ No newline at end of file +#endif diff --git a/project/emscripten/boxedwine-shell.js b/project/emscripten/boxedwine-shell.js index c82b74503..231f94237 100644 --- a/project/emscripten/boxedwine-shell.js +++ b/project/emscripten/boxedwine-shell.js @@ -7,6 +7,8 @@ let DEFAULT_LOAD_DESKTOP = false; let DEFAULT_SOUND_ENABLED = true; let DEFAULT_DISABLE_HIDE_CURSOR = false; + let DEFAULT_SHOW_FPS = false; + let DEFAULT_SKIP_UNCHANGED_FRAMES = false; let DEFAULT_APP_DIRECTORY = "/home/username/.wine/dosdevices/c:/files"; let DEFAULT_BPP = 32; let DEFAULT_FRAME_SKIP = "0"; @@ -49,6 +51,8 @@ Config.isSoundEnabled = getSound(); Config.audioFreq = getAudioFreq(); Config.disableHideCursor = getDisableHideCursor(); + Config.showFPS = getShowFPS(); + Config.skipUnchangedFrames = getSkipUnchangedFrames(); Config.bpp = getBitsPerPixel(); Config.cpu = getCPU(); Config.envProp = getEnvProp(); @@ -182,6 +186,34 @@ console.log("setting skipFrameFPS to: "+frameskip); return frameskip; } + function getShowFPS() { + var showFPS = getParameter("showFPS"); + if (!allowParameterOverride()) { + showFPS = DEFAULT_SHOW_FPS; + } else if (showFPS == "true") { + showFPS = true; + } else if (showFPS == "false") { + showFPS = false; + } else { + showFPS = DEFAULT_SHOW_FPS; + } + console.log("setting showFPS to: " + showFPS); + return showFPS; + } + function getSkipUnchangedFrames() { + var skipUnchangedFrames = getParameter("skipUnchangedFrames"); + if (!allowParameterOverride()) { + skipUnchangedFrames = DEFAULT_SKIP_UNCHANGED_FRAMES; + } else if (skipUnchangedFrames == "true") { + skipUnchangedFrames = true; + } else if (skipUnchangedFrames == "false") { + skipUnchangedFrames = false; + } else { + skipUnchangedFrames = DEFAULT_SKIP_UNCHANGED_FRAMES; + } + console.log("setting skipUnchangedFrames to: " + skipUnchangedFrames); + return skipUnchangedFrames; + } function getBitsPerPixel() { var bpp = getParameter("bpp"); if(!allowParameterOverride()){ @@ -713,6 +745,12 @@ if (Config.disableHideCursor) { params.push("-disableHideCursor"); } + if (Config.showFPS) { + params.push("-showFPS"); + } + if (Config.skipUnchangedFrames) { + params.push("-skipUnchangedFrames"); + } if(Config.bpp != DEFAULT_BPP){ params.push("-bpp"); params.push("" + Config.bpp); diff --git a/source/kernel/ksystem.cpp b/source/kernel/ksystem.cpp index 9303400ba..09c523df1 100644 --- a/source/kernel/ksystem.cpp +++ b/source/kernel/ksystem.cpp @@ -30,9 +30,13 @@ #include -bool KSystem::modesInitialized = false; -U32 KSystem::skipFrameFPS = 0; -VideoOption KSystem::videoOption = VIDEO_NORMAL; +bool KSystem::modesInitialized = false; +U32 KSystem::skipFrameFPS = 0; +#ifdef __EMSCRIPTEN__ +bool KSystem::skipUnchangedFrames = false; +bool KSystem::showFPS = false; +#endif +VideoOption KSystem::videoOption = VIDEO_NORMAL; BString KSystem::openglLib; bool KSystem::soundEnabled = true; bool KSystem::enableSoundAfterMouseClick = false; diff --git a/source/sdl/emscripten/mainloop.cpp b/source/sdl/emscripten/mainloop.cpp index 0c1fc1271..5dda026ed 100644 --- a/source/sdl/emscripten/mainloop.cpp +++ b/source/sdl/emscripten/mainloop.cpp @@ -24,6 +24,7 @@ #ifdef BOXEDWINE_MULTI_THREADED #include "knativethread.h" +#include "../../x11/x11.h" U32 getNextTimer(); void runTimers(); @@ -58,6 +59,12 @@ void mainloop() { if (nextTimer == 0) { runTimers(); } + XServer* server = XServer::getServer(true); + if (server) { + server->isDisplayDirty = true; + server->draw(); + } + KNativeSystem::tick(); if (lastTitleUpdate + 5000 < t) { lastTitleUpdate = t; diff --git a/source/sdl/startupArgs.cpp b/source/sdl/startupArgs.cpp index 695298837..48afa2329 100644 --- a/source/sdl/startupArgs.cpp +++ b/source/sdl/startupArgs.cpp @@ -227,11 +227,19 @@ std::vector StartUpArgs::buildArgs() { args.push_back(B("-automation")); args.push_back(runAutomation); } - if (skipFrameFPS) { - args.push_back(B("-skipFrameFPS")); - args.push_back(BString::valueOf(skipFrameFPS)); - } - if (cpuAffinity) { + if (skipFrameFPS) { + args.push_back(B("-skipFrameFPS")); + args.push_back(BString::valueOf(skipFrameFPS)); + } +#ifdef __EMSCRIPTEN__ + if (skipUnchangedFrames) { + args.push_back(B("-skipUnchangedFrames")); + } + if (showFPS) { + args.push_back(B("-showFPS")); + } +#endif + if (cpuAffinity) { args.push_back(B("-cpuAffinity")); args.push_back(BString::valueOf(cpuAffinity)); } @@ -293,10 +301,14 @@ bool StartUpArgs::apply() { if (KSystem::pollRate < 0) { KSystem::pollRate = 0; } - KSystem::openglLib = this->openGlLib; - KSystem::ttyPrepend = this->ttyPrepend; - KSystem::skipFrameFPS = this->skipFrameFPS; - if (!KSystem::logFile.isOpen() && this->logPath.length()) { + KSystem::openglLib = this->openGlLib; + KSystem::ttyPrepend = this->ttyPrepend; + KSystem::skipFrameFPS = this->skipFrameFPS; +#ifdef __EMSCRIPTEN__ + KSystem::skipUnchangedFrames = this->skipUnchangedFrames; + KSystem::showFPS = this->showFPS; +#endif + if (!KSystem::logFile.isOpen() && this->logPath.length()) { KSystem::logFile.createNew(this->logPath); } @@ -808,10 +820,20 @@ bool StartUpArgs::parseStartupArgs(int argc, const char **argv) { klog("ignoring -cpuAffinity"); #endif i++; - } else if (!strcmp(argv[i], "-skipFrameFPS") && i+1skipFrameFPS = atoi(argv[i+1]); - i++; - } else if (!strcmp(argv[i], "-log") && i + 1 < argc) { + } else if (!strcmp(argv[i], "-skipFrameFPS") && i+1skipFrameFPS = atoi(argv[i+1]); + if (this->skipFrameFPS > 50) { + klog("-skipFrameFPS must be between 0 and 50"); + this->skipFrameFPS = 0; + } + i++; +#ifdef __EMSCRIPTEN__ + } else if (!strcmp(argv[i], "-skipUnchangedFrames")) { + this->skipUnchangedFrames = true; + } else if (!strcmp(argv[i], "-showFPS")) { + this->showFPS = true; +#endif + } else if (!strcmp(argv[i], "-log") && i + 1 < argc) { this->logPath = BString::copy(argv[i + 1]); i++; } diff --git a/source/sdl/startupArgs.h b/source/sdl/startupArgs.h index 01deb3d83..e5a325e1c 100644 --- a/source/sdl/startupArgs.h +++ b/source/sdl/startupArgs.h @@ -98,10 +98,14 @@ class StartUpArgs { U32 audioFreq = 0; #endif VideoOption videoOption = VIDEO_NORMAL; - U32 vsync = VSYNC_DEFAULT; - bool dpiAware = false; - U32 skipFrameFPS = 0; - static U32 uiType; + U32 vsync = VSYNC_DEFAULT; + bool dpiAware = false; + U32 skipFrameFPS = 0; +#ifdef __EMSCRIPTEN__ + bool skipUnchangedFrames = false; + bool showFPS = false; +#endif + static U32 uiType; bool readyToLaunch = false; BString openGlLib; bool ttyPrepend = false; diff --git a/source/x11/xserver.cpp b/source/x11/xserver.cpp index 0a26d95f2..181a86803 100644 --- a/source/x11/xserver.cpp +++ b/source/x11/xserver.cpp @@ -18,6 +18,7 @@ #include "boxedwine.h" #include "x11.h" +#include "ksdlframeskip.h" #include "knativesystem.h" #include "ksocket.h" @@ -378,6 +379,9 @@ void XServer::draw(bool drawNow) { if (!drawNow && (now - lastDraw < 16)) { return; } + if (!drawNow && !kSDLFrameSkipShouldRender()) { + return; + } lastDraw = now; isDisplayDirty = false; } @@ -389,6 +393,7 @@ void XServer::draw(bool drawNow) { screen->getInput()->runOnUiThread([screen, this]() { bool childWasDrawn = false; + screen->beginX11Frame(); screen->clear(); root->iterateMappedChildrenBackToFront([&childWasDrawn](XWindowPtr child) { if (child->c_class == InputOutput) { @@ -397,7 +402,7 @@ void XServer::draw(bool drawNow) { } return true; }, true); - screen->present(); + screen->presentX11(); }); }