diff --git a/source/Main.hx b/source/Main.hx index 15c7f23aa..0f53513c5 100644 --- a/source/Main.hx +++ b/source/Main.hx @@ -6,7 +6,7 @@ import flixel.FlxState; import funkin.ui.FullScreenScaleMode; import funkin.Preferences; import funkin.util.logging.CrashHandler; -import funkin.ui.debug.FunkinCounter; +import funkin.ui.debug.FunkinDebugDisplay; import funkin.save.Save; import haxe.ui.Toolkit; #if hxvlc @@ -82,21 +82,17 @@ class Main extends Sprite setupGame(); } - var video:Video; - var netStream:NetStream; - var overlay:Sprite; - /** - * A frame counter displayed at the top left. + * The debug display at the top left. */ - public static var funkinCounter:FunkinCounter; + public static var funkinCounter:FunkinDebugDisplay; function setupGame():Void { initHaxeUI(); // addChild gets called by the user settings code. - funkinCounter = new FunkinCounter(10, 3, 0xFFFFFF); + funkinCounter = new FunkinDebugDisplay(10, 3, 0xFFFFFF); #if mobile // Add this signal so we can reposition and resize the memory and fps counter. diff --git a/source/funkin/external/windows/WinAPI.hx b/source/funkin/external/windows/WinAPI.hx index 2c0969799..edc639758 100644 --- a/source/funkin/external/windows/WinAPI.hx +++ b/source/funkin/external/windows/WinAPI.hx @@ -56,6 +56,14 @@ extern class WinAPI @:native('WINAPI_DisableWindowsGhosting') static function disableWindowsGhosting():Void; + /** + * Retrieves the current working set size (in bytes) of the process. + * + * @return The size of the working set memory used by the process. + */ + @:native('WINAPI_GetProcessMemoryWorkingSetSize') + static function getProcessMemoryWorkingSetSize():cpp.SizeT; + /** * Sets the dark mode for the active window. * diff --git a/source/funkin/external/windows/project/include/winapi.hpp b/source/funkin/external/windows/project/include/winapi.hpp index 64d21abde..cfaf699b8 100644 --- a/source/funkin/external/windows/project/include/winapi.hpp +++ b/source/funkin/external/windows/project/include/winapi.hpp @@ -38,6 +38,15 @@ void WINAPI_DisableErrorReporting(); */ void WINAPI_DisableWindowsGhosting(); +/** + * @brief Retrieves the current working set size (in bytes) of the calling process. + * + * This function queries the operating system for the amount of physical memory currently allocated to the process (its working set). + * + * @return The working set size in bytes. Returns 0 if the query fails. + */ +size_t WINAPI_GetProcessMemoryWorkingSetSize(); + /** * @brief Sets dark mode for the active window * @param enable True to enable dark mode, false to disable diff --git a/source/funkin/external/windows/project/src/winapi.cpp b/source/funkin/external/windows/project/src/winapi.cpp index 88f440846..de85a9301 100644 --- a/source/funkin/external/windows/project/src/winapi.cpp +++ b/source/funkin/external/windows/project/src/winapi.cpp @@ -41,6 +41,16 @@ void WINAPI_DisableWindowsGhosting() DisableProcessWindowsGhosting(); } +size_t WINAPI_GetProcessMemoryWorkingSetSize() +{ + PROCESS_MEMORY_COUNTERS_EX pmc; + + if (GetProcessMemoryInfo(GetCurrentProcess(), (PROCESS_MEMORY_COUNTERS*)&pmc, sizeof(pmc))) + return pmc.WorkingSetSize; + + return 0; +} + void WINAPI_SetDarkMode(bool enable) { HWND window = GetActiveWindow(); diff --git a/source/funkin/ui/debug/FunkinCounter.hx b/source/funkin/ui/debug/FunkinCounter.hx deleted file mode 100644 index e5ff2b474..000000000 --- a/source/funkin/ui/debug/FunkinCounter.hx +++ /dev/null @@ -1,141 +0,0 @@ -package funkin.ui.debug; - -import haxe.Timer; -import openfl.display.Sprite; -import openfl.text.TextField; -import openfl.text.TextFormat; -import flixel.system.debug.stats.StatsGraph; -import flixel.util.FlxStringUtil; -import funkin.util.MemoryUtil; - -class FunkinCounter extends Sprite -{ - static final FPS_UPDATE_DELAY:Int = 200; - - /** - The current frame rate, expressed using frames-per-second. - **/ - @:noCompletion - private var currentFPS(default, null):Int = 0; - - /** - The average frame rate. - **/ - @:noCompletion - private var averageFPS(default, null):Int = 0; - - /** - The lowest frame rate. - **/ - @:noCompletion - private var onePercFPS(default, null):Int = 0; - - /** - The peak of memory usage. - **/ - @:noCompletion - private var memPeak:Float = 0; - - @:noCompletion - private var times:Array = []; - - @:noCompletion - private var fpsValues:Array = []; - - @:noCompletion - private var deltaTimeout:Float = 0; - - @:noCompletion - private var frameCount:Int = 0; - - /** - * The text which displays all the fps and memory info. - */ - public var textDisplay:TextField; - - /** - * A graph which outputs fps info. - */ - public var fpsGraph:StatsGraph; - - private var canUpdateFPS:Bool = true; - - public function new(x:Float = 10, y:Float = 10, color:Int = 0x000000) - { - super(); - - this.x = x; - this.y = y; - - textDisplay = new TextField(); - textDisplay.width = 500; - textDisplay.selectable = false; - textDisplay.mouseEnabled = false; - textDisplay.defaultTextFormat = new TextFormat("Monsterrat", 12, color); - textDisplay.text = ""; - addChild(textDisplay); - - fpsGraph = new StatsGraph(0, 80, 75, 25, color, "FPS"); - fpsGraph.maxValue = FlxG.drawFramerate; - fpsGraph.minValue = 0; - addChild(fpsGraph); - - FlxG.signals.focusGained.add(() -> canUpdateFPS = true); - FlxG.signals.focusLost.add(() -> canUpdateFPS = false); - } - - @:noCompletion - private override function __enterFrame(deltaTime:Float):Void - { - final currentTime:Float = Timer.stamp() * 1000; - - times.push(currentTime); - - frameCount++; - - while (times[0] < currentTime - 1000) - times.shift(); - - if (!canUpdateFPS) return; - - if (deltaTimeout < FPS_UPDATE_DELAY) - { - deltaTimeout += deltaTime; - return; - } - - currentFPS = times.length; - - fpsGraph.update(currentFPS); - - averageFPS = Math.floor(fpsGraph.average()); - - if (onePercFPS < currentFPS - averageFPS) - { - onePercFPS = currentFPS; - } - else if (onePercFPS > currentFPS) - { - onePercFPS = currentFPS; - } - - updateDisplay(); - - deltaTimeout = 0.0; - } - - private function updateDisplay():Void - { - textDisplay.text = "FPS: " + currentFPS; - textDisplay.text += "\nAVG FPS: " + averageFPS; - textDisplay.text += "\n1% LOW FPS: " + onePercFPS; - - #if !html5 - var mem:Float = MemoryUtil.getMemoryUsed(); - - if (mem > memPeak) memPeak = mem; - - textDisplay.text += '\nRAM: ${FlxStringUtil.formatBytes(mem)} / ${FlxStringUtil.formatBytes(memPeak)}'; - #end - } -} diff --git a/source/funkin/ui/debug/FunkinDebugDisplay.hx b/source/funkin/ui/debug/FunkinDebugDisplay.hx new file mode 100644 index 000000000..6c666690e --- /dev/null +++ b/source/funkin/ui/debug/FunkinDebugDisplay.hx @@ -0,0 +1,139 @@ +package funkin.ui.debug; + +#if FLX_DEBUG +import flixel.system.debug.stats.StatsGraph; +#end +import flixel.util.FlxStringUtil; +import funkin.util.MemoryUtil; +import haxe.Timer; +import openfl.display.Sprite; +import openfl.text.TextField; +import openfl.text.TextFormat; + +/** + * A debug overlay showing useful info. + */ +class FunkinDebugDisplay extends Sprite +{ + static final FPS_UPDATE_DELAY:Int = 200; + + var canUpdate:Bool; + var deltaTimeout:Float; + var times:Array; + + #if !html5 + var gcMemPeak:Float; + var taskMemPeak:Float; + #end + + var onePercFPS:Int; + + var textDisplay:TextField; + + var fpsGraph:StatsGraph; + + public function new(x:Float = 10, y:Float = 10, color:Int = 0x000000) + { + super(); + + this.x = x; + this.y = y; + this.canUpdate = true; + this.deltaTimeout = 0.0; + this.gcMemPeak = 0.0; + this.taskMemPeak = 0.0; + this.times = []; + + textDisplay = new TextField(); + textDisplay.width = 500; + textDisplay.selectable = false; + textDisplay.mouseEnabled = false; + textDisplay.defaultTextFormat = new TextFormat("Monsterrat", 12, color, JUSTIFY); + textDisplay.antiAliasType = NORMAL; + textDisplay.sharpness = 100; + textDisplay.multiline = true; + addChild(textDisplay); + + #if FLX_DEBUG + fpsGraph = new StatsGraph(0, 80, 75, 25, color, "FPS"); + fpsGraph.maxValue = FlxG.drawFramerate; + fpsGraph.minValue = 0; + addChild(fpsGraph); + #end + + FlxG.signals.focusGained.add(function():Void { + canUpdate = true; + }); + + FlxG.signals.focusLost.add(function():Void { + canUpdate = false; + }); + + updateDisplay(); + } + + override function __enterFrame(deltaTime:Float):Void + { + if (!canUpdate) return; + + final currentTime:Float = Timer.stamp() * 1000; + + times.push(currentTime); + + while (times[0] < currentTime - 1000) + { + times.shift(); + } + + if (deltaTimeout < FPS_UPDATE_DELAY) + { + deltaTimeout += deltaTime; + return; + } + + fpsGraph.update(times.length); + + updateDisplay(times.length, Math.floor(fpsGraph.average())); + + deltaTimeout = 0.0; + } + + function updateDisplay(?currentFPS:Int = 0, ?averageFPS:Int = 0):Void + { + final info:Array = []; + + info.push('FPS: $currentFPS'); + + info.push('AVG FPS: $averageFPS'); + + if (onePercFPS < currentFPS - averageFPS) onePercFPS = currentFPS; + else if (onePercFPS > currentFPS) onePercFPS = currentFPS; + + info.push('1% LOW FPS: $onePercFPS'); + + #if !html5 + final gcMem:Float = MemoryUtil.getGCMemory(); + + if (gcMem > gcMemPeak) + { + gcMemPeak = gcMem; + } + + info.push('GC MEM: ${FlxStringUtil.formatBytes(gcMem).toLowerCase()} / ${FlxStringUtil.formatBytes(gcMemPeak).toLowerCase()}'); + + if (MemoryUtil.supportsTaskMem()) + { + final taskMem:Float = MemoryUtil.getTaskMemory(); + + if (taskMem > taskMemPeak) + { + taskMemPeak = taskMem; + } + + info.push('TASK MEM: ${FlxStringUtil.formatBytes(taskMem).toLowerCase()} / ${FlxStringUtil.formatBytes(taskMemPeak).toLowerCase()}'); + } + #end + + textDisplay.text = info.join('\n'); + } +} diff --git a/source/funkin/util/MemoryUtil.hx b/source/funkin/util/MemoryUtil.hx index 256c3b4d7..e95b10fe4 100644 --- a/source/funkin/util/MemoryUtil.hx +++ b/source/funkin/util/MemoryUtil.hx @@ -37,7 +37,7 @@ class MemoryUtil result += '\n- Source Annotation: ${#if annotate_source 'Enabled' #else 'Disabled' #end}'; #elseif js var result:String = 'JS-MNS:'; - result += '\n- Memory Used: ${getMemoryUsed()} bytes'; + result += '\n- Memory Used: ${getGCMemory()} bytes'; #else var result:String = 'Unknown GC'; #end @@ -45,11 +45,47 @@ class MemoryUtil return result; } - /** - * Calculate the total memory usage of the program, in bytes. - * @return Float - */ - public static function getMemoryUsed():Float + public static function supportsTaskMem():Bool + { + #if ((windows && cpp) || linux || android) + return true; + #else + return false; + #end + } + + public static function getTaskMemory():Float + { + #if (windows && cpp) + return funkin.external.windows.WinAPI.getProcessMemoryWorkingSetSize(); + #elseif (linux || android) + try + { + #if cpp + final content:String = sys.io.File.read('/proc/${cpp.NativeSys.sys_get_pid()}/status', false).readAll().toString(); + #else + final content:String = sys.io.File.read('/proc/self/status', false).readAll().toString(); + #end + + final regex:EReg = ~/^VmRSS:\s+(\d+)\s+kB/m; + + if (regex.match(content)) + { + final kb:Float = Std.parseFloat(regex.matched(1)); + + if (kb != Math.NaN) + { + return kb * 1024.0; + } + } + } + catch (e:Dynamic) {} + #end + + return 0.0; + } + + public static function getGCMemory():Float { return openfl.system.System.totalMemoryNumber; }