Arma Reforger Explorer 1.7.0.54
Arma Reforger Code Explorer by Zeroy - Thanks to MisterOutofTime
Loading...
Searching...
No Matches
SCR_FPSDiagnosticPlugin.c
Go to the documentation of this file.
1#ifdef WORKBENCH
3 name: "FPS Diagnostic",
4 description: "Collect FPS all over the terrain and create a heatmap of it",
5 shortcut: "Ctrl+Alt+Shift+F",
6 wbModules: { "WorldEditor" },
7 awesomeFontCode: 0xF625)]
8class SCR_FPSDiagnosticPlugin : WorldEditorPlugin
9{
10 //
11 // Camera
12 //
13
14 [Attribute(category: "Camera", defvalue: "0 " + CAMERA_DEFAULT_ALTITUDE + " 0", desc: "Camera offset from terrain/ocean position (default " + CAMERA_DEFAULT_ALTITUDE + "m above terrain/ocean)")]
15 protected vector m_vPositionOffset;
16
17 [Attribute(category: "Camera", defvalue: "0", desc: "If surface is ocean, skip the check")]
18 protected bool m_bAnalyseOnOcean;
19
20 [Attribute(category: "Camera", defvalue: "0",
21 desc: "If checked, positions are randomised, otherwise they are done in order (so the area is usually preloaded)"
22 + "\nDoes not work if the total amount of positions is greater than " + SCR_Math.MAX_RANDOM)]
23 protected bool m_bRandomisePositions;
24
25 [Attribute(category: "Camera", uiwidget: UIWidgets.Slider, desc: "Camera angles in degrees from which to get FPS\nX: 0 = horizon, 90 = look straight up, -30 = look down 30°, etc\nY: 0 = North, -90/270 = West, etc\nZ: banking angle"
26 + "\n\nIf left empty, 4 cardinal direction cameras with " + CAMERA_DEFAULT_PITCH + " degrees pitch will be used", params: "-180 360")]
27 protected ref array<vector> m_aOrientations;
28
29 [Attribute(category: "Camera", defvalue: "2", desc: "Delay in seconds before capture starts (to allow proper loading and setting fullscreen)", params: "0 30 0.5", precision: 1)]
30 protected float m_fStartDelay;
31
32 [Attribute(category: "Camera", defvalue: "0.5", uiwidget: UIWidgets.Slider, desc: "wait time per scene after the camera is moved", params: "0.1 30 0.1", precision: 2)]
33 protected float m_fScenePause;
34
35 //
36 // Heatmap
37 //
38
39// [Attribute(category: "Heatmap", defvalue: "1", desc: "Generate heatmap")]
40// protected bool m_bGenerateHeatmap;
41
42 [Attribute(category: "Heatmap", defvalue: "0", uiwidget: UIWidgets.ComboBox, desc: "- Greyscale: from black to white\n- Thermal: from blue to green to red\n- Alpha: from transparent to white", enums: SCR_ParamEnumArray.FromString("Greyscale,From black to white;Thermal,From blue to green to red;Alpha,From transparent to white"))]
43 protected int m_iHeatmapColourMode;
44
45 [Attribute(category: "Heatmap", defvalue: "0", desc: "Invert pixel value (except for max value), e.g black = most dense, white = least dense instead of the opposite")]
46 protected bool m_bHeatmapValueInversion;
47
48 [Attribute(category: "Heatmap", defvalue: "1", uiwidget: UIWidgets.ComboBox, desc: "This setting can prevent a density peak from \"darkening\" the image everywhere else - it also offers to highlight pixels above the 0..2×median range", enums: SCR_ParamEnumArray.FromString("Raw value;2 × average;2 × median"))]
49 protected int m_iHeatmapMaxValueMode;
50
51 [Attribute(category: "Heatmap", defvalue: "1", desc: "Highlight values above max (when Heat Map Max Value Mode is not Raw)\n- Greyscale: red pixels\n- Thermal: white pixels")]
52 protected bool m_bHeatmapHighlightValuesAboveMax;
53
54 [Attribute(category: "Heatmap", defvalue: DEFINITION_DEFAULT.ToString(), uiwidget: UIWidgets.ComboBox, desc: "Number of detection squares in terrain height/width", enums: SCR_ParamEnumArray.FromString("4,4×4,;8,8×8,;16,16×16,;32,32×32,;64,64×64,;128,128×128,;256,256×256,;512,512×512,;1024,1024×1024,;2048,2048×2048,;4096,4096×4096,;1080,1080×1080 (wallpaper),;1440,1440×1440 (wallpaper),;2160,2160×2160 (wallpaper),"))]
55 protected int m_iHeatmapDefinition;
56
57 [Attribute(category: "Heatmap", defvalue: "1", uiwidget: UIWidgets.ComboBox, desc: "Definition multiplier to obtain a bigger image - e.g ×2 = 1 data point takes 2×2 (4) pixels\nMaximum resolution allowed: " + SCR_HeatmapHelper.MAX_RESOLUTION, enums: SCR_ParamEnumArray.FromString(SCR_HeatmapHelper.GetResolutionFactorEnum()))]
58 protected int m_iHeatmapResolutionFactor;
59
60 //
61 // Mode
62 //
63
64 [Attribute(category: "Mode", defvalue: "1", desc: "Use Game mode instead of Workbench mode (Workbench has performance overhead due to edit mode)\nIgnored when Fake Data is used")]
65 protected bool m_bUseGameMode;
66
67 [Attribute(category: "Mode", defvalue: "1", desc: "Use fullscreen (if Use Game Mode above is ticked)")]
68 protected bool m_bUseFullScreen;
69
70 //
71 // Misc
72 //
73
74 [Attribute(category: "Misc", defvalue: "0", uiwidget: UIWidgets.ComboBox, desc: "Open the heatmap/directory once generated", enums: SCR_ParamEnumArray.FromString("Open heatmap;Open heatmap directory;Open heatmap and directory"))]
75 protected int m_iOpenHeatmap;
76
77 [Attribute(category: "Misc", defvalue: "30", uiwidget: UIWidgets.Slider, desc: "Force progress bar estimate refresh and print a time estimate in the log console every X seconds - 0 = disabled (if used, progress bar still updates its estimate every percent)", params: "0 3600 30")]
78 protected int m_iTimeEstimateFrequency;
79
80 //
81 // Debug
82 //
83
84 [Attribute(category: "Debug", defvalue: "0", desc: "Use fake (randomised) data - useful to debug water detection\nIgnores Use Game Mode (Workbench itself generates the fake data)")]
85 protected bool m_bUseFakeData;
86
87 protected bool m_bInternalUseGameMode;
88 protected int m_iLastEstimateTick;
89
90 protected static const float CAMERA_DEFAULT_ALTITUDE = 7.5;
91 protected static const float CAMERA_DEFAULT_PITCH = -10;
92 protected static const int CAMERA_DEFAULT_ANGLE_COUNT = 4; // N, E, S, W
93
94 protected static const int DEFINITION_DEFAULT = 64;
95
96 protected static const float FPS_UNFOCUSED = 3.0;
97 protected static const float FPS_UNFOCUSED_DELTA = 0.01;
98 protected static const float FPS_UNFOCUSED_DURATION_MS = 0;
99 protected static const int MAX_FPS = 500;
100 protected static const float FPS_HIGH_DURATION_MS = 2000;
101 protected static const int FPS_CHECK_FREQUENCY_MS = 100;
102
103 protected static const int FPS_RANDOM_MIN = 0;
104 protected static const int FPS_RANDOM_MID = 45;
105 protected static const int FPS_RANDOM_MAX = 60;
106
107 protected static const float ESTIMATE_FACTOR = 1.10;
108
110 protected static const string OUTPUT_HEATMAP_NAME = "Heatmap_FPS_%1_%2_%3x%3.dds";
111
112 //------------------------------------------------------------------------------------------------
113 protected override void Run()
114 {
115 WorldEditor worldEditor = Workbench.GetModule(WorldEditor);
116 if (!worldEditor)
117 {
118 SCR_WorkbenchHelper.PrintDialog("World Editor is not available", level: LogLevel.ERROR);
119 return;
120 }
121
122 WorldEditorAPI worldEditorAPI = worldEditor.GetApi();
123 if (!worldEditorAPI)
124 {
125 SCR_WorkbenchHelper.PrintDialog("World Editor API is not available", level: LogLevel.ERROR);
126 return;
127 }
128
129 if (!SCR_WorldEditorToolHelper.IsWorldLoaded())
130 {
131 SCR_WorkbenchHelper.PrintDialog("Be sure to load a world (or have it saved) before using the FPS Diagnostic plugin.");
132 return;
133 }
134
135 if (worldEditor.IsPrefabEditMode())
136 {
137 SCR_WorkbenchHelper.PrintDialog("Be sure to load a world (or have it saved) before using the FPS Diagnostic plugin.");
138 return;
139 }
140
141 IEntitySource terrainEntity = SCR_WorldEditorToolHelper.GetTerrainEntitySource();
142 if (!terrainEntity)
143 {
144 SCR_WorkbenchHelper.PrintDialog("Be sure to have a terrain entity (of type GenericTerrainEntity) before using the FPS Diagnostic plugin.");
145 return;
146 }
147
148 if (Workbench.ScriptDialog("FPS Diagnostic", "", this) == 0)
149 return;
150
151 vector terrainOrigin;
152 if (!terrainEntity.Get("coords", terrainOrigin))
153 return;
154
155 vector terrainDimensions = SCR_WorldEditorToolHelper.GetTerrainDimensions();
156
157 if (m_iHeatmapDefinition < 1)
158 m_iHeatmapDefinition = DEFINITION_DEFAULT;
159
160 float pixelWidth = terrainDimensions[0] / m_iHeatmapDefinition;
161 float pixelHeight = terrainDimensions[2] / m_iHeatmapDefinition;
162
163 if (pixelWidth < 0.1 || pixelHeight < 0.1)
164 {
165 SCR_WorkbenchHelper.PrintFormatDialog("Invalid pixel size (%1×%2) - try setting a lower definition.", pixelWidth.ToString(), pixelHeight.ToString(), level: LogLevel.WARNING);
166 return;
167 }
168
169 BaseWorld world = worldEditorAPI.GetWorld();
170
171 bool isOceanEnabled = world.IsOcean();
172 float oceanBaseHeight;
173 if (isOceanEnabled)
174 oceanBaseHeight = world.GetOceanBaseHeight();
175
176 array<int> oceanIndices;
177 if (!m_bAnalyseOnOcean && isOceanEnabled)
178 oceanIndices = {};
179
180 map<int, vector> positionsMap = new map<int, vector>();
181 int pixelIndex;
182 for (int z = m_iHeatmapDefinition - 1; z >= 0; --z)
183 {
184 float zPos = terrainOrigin[2] + (z + 0.5) * pixelHeight;
185 for (int x; x < m_iHeatmapDefinition; ++x)
186 {
187 float xPos = terrainOrigin[0] + (x + 0.5) * pixelWidth;
188
189 float yPos = worldEditorAPI.GetTerrainSurfaceY(xPos, zPos);
190 if (!m_bAnalyseOnOcean && isOceanEnabled && yPos < oceanBaseHeight) // allow ocean's base height
191 oceanIndices.Insert(pixelIndex);
192
193 positionsMap.Insert(pixelIndex, { xPos, yPos, zPos });
194 ++pixelIndex;
195 }
196 }
197
198 int positionsCount = positionsMap.Count();
199 if (positionsCount < 1)
200 {
201 SCR_WorkbenchHelper.PrintDialog("No relevant positions were found - try setting a higher definition.");
202 return;
203 }
204
205 int oceanIndicesCount;
206 if (oceanIndices)
207 oceanIndicesCount = oceanIndices.Count();
208
209 int relevantPositionsCount = positionsCount - oceanIndicesCount;
210 if (relevantPositionsCount < 1)
211 {
212 SCR_WorkbenchHelper.PrintDialog("No non-water positions were found - try setting a higher definition.");
213 return;
214 }
215
216 int orientationsCount = m_aOrientations.Count();
217 if (orientationsCount < 1)
218 {
219 if (CAMERA_DEFAULT_ANGLE_COUNT < 1)
220 {
221 SCR_WorkbenchHelper.PrintDialog("No orientations provided - add at least one camera orientation.");
222 return;
223 }
224
225 for (int i; i < CAMERA_DEFAULT_ANGLE_COUNT; ++i)
226 {
227 m_aOrientations.Insert({ CAMERA_DEFAULT_PITCH, i * 360 / CAMERA_DEFAULT_ANGLE_COUNT, 0 });
228 ++orientationsCount;
229 }
230 }
231
232 int relevantScenesCount = relevantPositionsCount * orientationsCount;
233 if (relevantScenesCount < 1)
234 {
235 SCR_WorkbenchHelper.PrintDialog("No scenes created - no proper positions found.");
236 return; // safety
237 }
238
239 string captionPrefix;
240 if (m_bUseFakeData)
241 captionPrefix = "[DEBUG] ";
242
243 if (Workbench.ScriptDialog(
244 captionPrefix + "FPS Diagnostic",
245 string.Format(
246 "The plugin will process %1 positions in %2 angle(s) for a total of %3 scenes (every %4m)",
247 relevantPositionsCount,
248 orientationsCount,
249 relevantScenesCount,
250 pixelWidth.ToString(-1, 1))
251 + string.Format(
252 "\nHeatmap definition: %1×%1 stretched ×%2 to %3×%3",
253 m_iHeatmapDefinition,
254 m_iHeatmapResolutionFactor,
255 m_iHeatmapDefinition * m_iHeatmapResolutionFactor)
256 + string.Format(
257 "\n\n%1 estimated duration with %2s waiting time per scene (theoretical duration ×%3)"
258 + "\n\nDo NOT touch or close the Workbench during that time"
259 + "\nDo NOT unfocus the Workbench (unless -forceUpdate is used or you want to abort the benchmark)"
260 + "\n\nA waiting time of %4s will happen before the benchmark begins.",
261 SCR_FormatHelper.FormatTime(m_fStartDelay + relevantScenesCount * m_fScenePause * ESTIMATE_FACTOR),
262 m_fScenePause,
263 ESTIMATE_FACTOR,
264 m_fStartDelay),
265 new SCR_OKCancelWorkbenchDialog()) == 0)
266 return;
267
268 float lowestFPS = float.MAX;
269 float highestFPS;
270 float addedFPS;
271 array<float> fpsArray = {};
272 fpsArray.Resize(positionsCount * orientationsCount);
273
274 int width, height;
275 array<int> resolutionList = {};
276 resolutionList.Reserve(relevantScenesCount * 2); // not resize
277
278 int validPositionsDone;
279
280 // randomisation
281 array<int> keys = SCR_MapHelperT<int, vector>.GetKeys(positionsMap);
282 if (m_bRandomisePositions)
283 {
284 if (positionsCount <= SCR_Math.MAX_RANDOM)
285 SCR_ArrayHelperT<int>.Shuffle(keys);
286 else
287 PrintFormat("Too many (%1 > %2) positions to randomise", positionsCount, SCR_Math.MAX_RANDOM, level: LogLevel.WARNING);
288 }
289
290 // progress and estimate
291 string estimatedDuration = SCR_FormatHelper.FormatTime(relevantScenesCount * m_fScenePause * ESTIMATE_FACTOR);
292 Print("Estimated duration: " + estimatedDuration, LogLevel.NORMAL);
293
294 float prevProgress, currProgress;
295 string progressText;
296 if (!m_bInternalUseGameMode)
297 {
298 if (m_bUseFakeData)
299 progressText = "[DEBUG] Generating scenes's performance...\nEstimated time left: %1";
300 else
301 progressText = "Capturing scenes's performance...\nEstimated time left: %1";
302 }
303
304 WBProgressDialog progress;
305
306 m_bInternalUseGameMode = m_bUseGameMode && !m_bUseFakeData;
307
308 vector cameraPos, traceEnd, cameraDir;
309 int screenWidth = worldEditorAPI.GetScreenWidth();
310 int screenHeight = worldEditorAPI.GetScreenHeight();;
311 if (!m_bInternalUseGameMode) // WorkbenchCamera
312 worldEditorAPI.TraceWorldPos(screenWidth * 0.5, screenHeight * 0.5, TraceFlags.WORLD, cameraPos, traceEnd, cameraDir);
313
314 CameraBase camera;
315
316 // enter game mode
317 if (m_bInternalUseGameMode)
318 {
319 worldEditor.SwitchToGameMode(fullScreen: m_bUseFullScreen);
320
321 while (!worldEditorAPI.IsGameMode())
322 {
323 if (float.AlmostEqual(System.GetFPS(), FPS_UNFOCUSED))
324 {
325 Print("Switching to Game mode got unfocused - capture cancelled", LogLevel.ERROR);
326 worldEditor.SwitchToEditMode();
327 return;
328 }
329
330 Sleep(100); // needed
331 }
332
333 SCR_CameraManager cameraManager = SCR_CameraManager.Cast(GetGame().GetCameraManager());
334 if (!cameraManager)
335 {
336 SCR_WorkbenchHelper.PrintDialog("Camera Manager cannot be found", level: LogLevel.ERROR);
337 worldEditor.SwitchToEditMode();
338 return;
339 }
340
341 camera = CameraBase.Cast(GetGame().SpawnEntity(CameraBase));
342 if (!camera)
343 {
344 SCR_WorkbenchHelper.PrintDialog("Camera cannot be created", level: LogLevel.ERROR);
345 worldEditor.SwitchToEditMode();
346 return;
347 }
348
349 cameraManager.SetCamera(camera);
350 }
351 else
352 {
353 progress = new WBProgressDialog(string.Format(progressText, estimatedDuration), worldEditor);
354 }
355
356 if (!m_bUseFakeData && m_fStartDelay > 0)
357 {
358 PlaceCamera(worldEditorAPI, camera, terrainOrigin + m_vPositionOffset, -vector.Up);
359
360 Print("Starting in " + m_fStartDelay + "s, unfocus the Workbench to cancel", LogLevel.NORMAL);
361 if (!WaitFocused(m_fStartDelay))
362 {
363 PlaceCamera(worldEditorAPI, camera, cameraPos, cameraDir);
364 Sleep(1);
365 SCR_WorkbenchHelper.PrintDialog("Workbench was unfocused - capture cancelled");
366 if (m_bInternalUseGameMode)
367 worldEditor.SwitchToEditMode();
368
369 return;
370 }
371
372 Print("Starting", LogLevel.NORMAL);
373 }
374
375 if (float.AlmostEqual(System.GetFPS(), FPS_UNFOCUSED, FPS_UNFOCUSED_DELTA))
376 {
377 SCR_WorkbenchHelper.PrintDialog("Workbench is unfocused on start - capture cancelled");
378 if (m_bInternalUseGameMode)
379 worldEditor.SwitchToEditMode();
380
381 return;
382 }
383
384 const int startTime = System.GetTickCount();
385 m_iLastEstimateTick = startTime;
386
387 Debug.BeginTimeMeasure();
388
389 // foreach (int index, vector position : positionsMap)
390 foreach (int index : keys)
391 {
392 vector position = positionsMap.Get(index);
393
394 if (m_iTimeEstimateFrequency > 0 && System.GetTickCount(m_iLastEstimateTick) >= m_iTimeEstimateFrequency * 1000)
395 {
396 int now = System.GetTickCount();
397 int spentTime = now - startTime;
398
399 if (currProgress > 0)
400 {
401 float timePerPercentage = (spentTime - m_fStartDelay) / currProgress;
402 float estimatedRemaining = timePerPercentage * (1 - currProgress);
403
404 if (progress) // refresh text - currently the only way is to regreate the progress dialog
405 {
406 progress = new WBProgressDialog(string.Format(progressText, SCR_FormatHelper.FormatTime(estimatedRemaining * 0.001 * ESTIMATE_FACTOR)), worldEditor);
407 progress.SetProgress(currProgress);
408 }
409
411 "Progress: %1%%; Spent time: %2 Estimated remaining time: %3",
412 (currProgress * 100).ToString(-1, 2),
413 SCR_FormatHelper.FormatTime(spentTime * 0.001),
414 SCR_FormatHelper.FormatTime(estimatedRemaining * 0.001 * ESTIMATE_FACTOR),
415 level: LogLevel.NORMAL);
416
417 m_iLastEstimateTick = now;
418 }
419 }
420
421 // avoid ocean if needed
422 if (oceanIndices && oceanIndices.Contains(index))
423 {
424 for (int i; i < orientationsCount; ++i)
425 {
426 fpsArray[index * orientationsCount + i] = -1;
427 }
428
429 continue;
430 }
431
432 ++validPositionsDone;
433 currProgress = validPositionsDone / relevantPositionsCount;
434 if (currProgress - prevProgress >= 0.01) // min 1%
435 {
436 if (progress)
437 {
438 if (currProgress > 0)
439 {
440 int now = System.GetTickCount();
441 int spentTime = now - startTime;
442 float timePerPercentage = (spentTime - m_fStartDelay) / currProgress;
443 float estimatedRemaining = timePerPercentage * (1 - currProgress);
444 progress = new WBProgressDialog(string.Format(progressText, SCR_FormatHelper.FormatTime(estimatedRemaining * 0.001 * ESTIMATE_FACTOR)), worldEditor);
445 }
446
447 progress.SetProgress(currProgress); // expensive
448 }
449
450 prevProgress = currProgress;
451 }
452
453 foreach (int orientationIndex, vector orientation : m_aOrientations)
454 {
455 float sceneFPS;
456 if (m_bUseFakeData)
457 {
458 if (float.AlmostEqual(System.GetFPS(), FPS_UNFOCUSED))
459 {
460 SCR_WorkbenchHelper.PrintDialog("Workbench was unfocused - capture cancelled");
461 worldEditor.SwitchToEditMode();
462 return;
463 }
464
465 sceneFPS = SCR_Math.RandomGaussFloat(FPS_RANDOM_MIN, FPS_RANDOM_MID, FPS_RANDOM_MAX);
466 }
467 else
468 {
469 if (m_bInternalUseGameMode && !worldEditorAPI.IsGameMode())
470 {
471 SCR_WorkbenchHelper.PrintDialog("Workbench Game Mode was exited - capture cancelled");
472 return;
473 }
474
475 // Yaw Pitch Roll to Pitch Yaw Roll
476 float tmp = orientation[0];
477 orientation[0] = orientation[1];
478 orientation[1] = tmp;
479
480 PlaceCamera(worldEditorAPI, camera, position + m_vPositionOffset, orientation.AnglesToVector());
481
482 if (!WaitFocused(m_fScenePause))
483 {
484 PlaceCamera(worldEditorAPI, camera, position + m_vPositionOffset, orientation.AnglesToVector());
485 Sleep(1);
486 progress = null;
487 SCR_WorkbenchHelper.PrintDialog("Workbench was unfocused - capture cancelled");
488 if (m_bInternalUseGameMode)
489 worldEditor.SwitchToEditMode();
490
491 return;
492 }
493
494 PlaceCamera(worldEditorAPI, camera, position + m_vPositionOffset, orientation.AnglesToVector());
495 sceneFPS = System.GetFPS();
496 int waitTime = FPS_HIGH_DURATION_MS;
497 while (sceneFPS > MAX_FPS)
498 {
499 Sleep(FPS_CHECK_FREQUENCY_MS);
500 PlaceCamera(worldEditorAPI, camera, position + m_vPositionOffset, orientation.AnglesToVector());
501 sceneFPS = System.GetFPS();
502 waitTime -= FPS_CHECK_FREQUENCY_MS;
503 if (waitTime < 1)
504 {
505 worldEditorAPI.SetCamera(cameraPos, cameraDir);
506 Sleep(1);
507 SCR_WorkbenchHelper.PrintFormatDialog("FPS > %1 for at least %2! Use VSync if needed", MAX_FPS.ToString(), (FPS_HIGH_DURATION_MS * 0.001).ToString(), level: LogLevel.ERROR);
508 if (m_bInternalUseGameMode)
509 worldEditor.SwitchToEditMode();
510
511 return;
512 }
513 }
514
515 waitTime = FPS_UNFOCUSED_DURATION_MS;
516 while (float.AlmostEqual(sceneFPS, FPS_UNFOCUSED, FPS_UNFOCUSED_DELTA))
517 {
518 Sleep(FPS_CHECK_FREQUENCY_MS);
519 PlaceCamera(worldEditorAPI, camera, position + m_vPositionOffset, orientation.AnglesToVector());
520 sceneFPS = System.GetFPS();
521 waitTime -= FPS_CHECK_FREQUENCY_MS;
522 if (waitTime < 1)
523 {
524 PlaceCamera(worldEditorAPI, camera, cameraPos, cameraDir);
525 Sleep(1);
526 progress = null;
527 SCR_WorkbenchHelper.PrintDialog("Workbench was unfocused - capture cancelled");
528 if (m_bInternalUseGameMode)
529 worldEditor.SwitchToEditMode();
530
531 return;
532 }
533 }
534 }
535
536 fpsArray[index * orientationsCount + orientationIndex] = sceneFPS;
537 addedFPS += sceneFPS;
538
539 if (sceneFPS < lowestFPS)
540 lowestFPS = sceneFPS;
541 else
542 if (sceneFPS > highestFPS)
543 highestFPS = sceneFPS;
544
545 resolutionList.Insert(worldEditorAPI.GetScreenWidth());
546 resolutionList.Insert(worldEditorAPI.GetScreenHeight());
547 }
548 }
549
550 Debug.EndTimeMeasure("Measuring " + relevantScenesCount + " scenes");
551
552 if (m_bInternalUseGameMode)
553 worldEditor.SwitchToEditMode();
554 else
555 PlaceCamera(worldEditorAPI, camera, cameraPos, cameraDir);
556
557 array<float> medianArray = {};
558 medianArray.Copy(fpsArray);
559 medianArray.RemoveItem(-1);
560 medianArray.Sort();
561
562 float medianFPS;
563 if (!medianArray.IsEmpty())
564 medianFPS = medianArray[medianArray.Count() * 0.5];
565
566 Print("Average FPS: " + addedFPS / relevantScenesCount, LogLevel.NORMAL);
567 Print("Median FPS: " + medianFPS, LogLevel.NORMAL);
568 Print("Highest FPS: " + highestFPS, LogLevel.NORMAL);
569 Print("Lowest FPS: " + lowestFPS, LogLevel.NORMAL);
570
571 int resolutionListCount = resolutionList.Count();
572
573 bool differentResolutions;
574 for (int i; i < resolutionListCount; i += 2) // step 2
575 {
576 if (i == 0)
577 {
578 width = resolutionList[0];
579 height = resolutionList[1];
580 }
581 else // compare
582 {
583 if (width != resolutionList[i] || height != resolutionList[i + 1])
584 {
585 differentResolutions = true;
586 break;
587 }
588 }
589 }
590
591 if (differentResolutions)
592 {
593 int avgWidth, avgHeight;
594 for (int i; i < resolutionListCount; i += 2) // step 2
595 {
596 avgWidth += resolutionList[i];
597 avgHeight += resolutionList[i + 1];
598 }
599
600 PrintFormat("Resolution was changed during the FPS test! Started with %1×%2. Average resolution: %3×%4", screenWidth, screenHeight, avgWidth / relevantScenesCount, avgHeight / relevantScenesCount, level: LogLevel.WARNING);
601 }
602 else
603 {
604 PrintFormat("Resolution: %1×%2", width, height, level: LogLevel.NORMAL);
605 }
606
607 string colourMode;
608 if (m_iHeatmapColourMode == SCR_HeatmapHelper.COLOUR_MODE_ALPHA)
609 colourMode = "Alpha";
610 else
611 if (m_iHeatmapColourMode == SCR_HeatmapHelper.COLOUR_MODE_THERMAL)
612 colourMode = "RGB";
613 else
614// if (m_iHeatmapColourMode == SCR_HeatmapHelper.COLOUR_MODE_GREYSCALE) // default
615 colourMode = "BW";
616
617 if (m_bHeatmapValueInversion)
618 colourMode += "inv";
619
620 string worldName = SCR_WorldEditorToolHelper.GetWorldName();
621 string fileName = string.Format(OUTPUT_HEATMAP_NAME, worldName, colourMode, m_iHeatmapDefinition);
622 string absoluteFileName;
623 if (!Workbench.GetAbsolutePath(fileName, absoluteFileName, false) || !CreateImage(absoluteFileName, m_iHeatmapDefinition, fpsArray))
624 {
625 Print("Heatmap cannot be created at " + absoluteFileName, LogLevel.ERROR);
626 return;
627 }
628
629 Print("Heatmap successfully created at " + absoluteFileName, LogLevel.NORMAL);
630 absoluteFileName.Replace("/", "\\");
631 if (m_iOpenHeatmap == 1 || m_iOpenHeatmap == 2)
632 Workbench.RunCmd("explorer \"" + FilePath.StripFileName(absoluteFileName) + "\"");
633
634 if (m_iOpenHeatmap == 0 || m_iOpenHeatmap == 2)
635 Workbench.RunCmd("explorer \"file:/" + SCR_StringHelper.DOUBLE_SLASH + absoluteFileName + "\"");
636 }
637
638 //------------------------------------------------------------------------------------------------
641 protected bool WaitFocused(float secondsToWait)
642 {
643 int waitTime = secondsToWait * 1000;
644 int unfocusDuration;
645 while (waitTime > 0)
646 {
647 Sleep(FPS_CHECK_FREQUENCY_MS);
648 waitTime -= FPS_CHECK_FREQUENCY_MS;
649
650 if (!float.AlmostEqual(System.GetFPS(), FPS_UNFOCUSED, FPS_UNFOCUSED_DELTA))
651 {
652 unfocusDuration = 0;
653 continue;
654 }
655
656 unfocusDuration += FPS_CHECK_FREQUENCY_MS;
657 if (unfocusDuration >= FPS_UNFOCUSED_DURATION_MS)
658 return false;
659 }
660
661 return true;
662 }
663
664 //------------------------------------------------------------------------------------------------
665 protected void PlaceCamera(notnull WorldEditorAPI worldEditorAPI, CameraBase camera, vector position, vector direction)
666 {
667 if (m_bInternalUseGameMode)
668 {
669 World world = GetGame().GetWorld();
670 if (!world)
671 return; // leaving game mode
672
673 direction.VectorToAngles();
674
675 // Pitch Yaw Roll to Yaw Pitch Roll
676 float tmp = direction[0];
677 direction[0] = direction[1];
678 direction[1] = tmp;
679
680 camera.SetOrigin(position + m_vPositionOffset);
681 camera.SetAngles(direction);
682 }
683 else
684 {
685 worldEditorAPI.SetCamera(position + m_vPositionOffset, direction);
686 }
687 }
688
689 //------------------------------------------------------------------------------------------------
695 protected bool CreateImage(string imagePath, int definition, notnull array<float> fpsArray)
696 {
697 int definitionSq = definition * definition;
698 array<int> imageData = {}; // temporarily used to store entity count first
699 imageData.Resize(definitionSq);
700
701 int scenePerPosition = fpsArray.Count() / definitionSq;
702 if (scenePerPosition < 1)
703 {
704 PrintFormat("[SCR_FPSDiagnosticPlugin.CreateImage] scenePerPosition %1/%2 = 0!", fpsArray.Count(), definitionSq, level: LogLevel.ERROR);
705 return false;
706 }
707
708 Debug.BeginTimeMeasure();
709
710 int fpsArrayCount = fpsArray.Count();
711
712 for (int i; i < definitionSq; ++i)
713 {
714 float avgOrientationsFPS;
715 int measuresCount;
716 for (int fpsI; fpsI < scenePerPosition; ++fpsI)
717 {
718 float fps = fpsArray[i * scenePerPosition + fpsI];
719 if (fps < 0)
720 break;
721
722 avgOrientationsFPS += fps;
723
724 ++measuresCount;
725 }
726
727 if (measuresCount != scenePerPosition)
728 continue;
729
730 if (avgOrientationsFPS >= 0 && measuresCount > 0)
731 avgOrientationsFPS /= measuresCount;
732
733 imageData[i] = Math.Round(avgOrientationsFPS);
734 }
735
736 Debug.EndTimeMeasure(
737 string.Format(
738 "Processing %1 scene FPS for image creation (%2×%2 = %3 pixels)",
739 definition * scenePerPosition,
740 definition,
741 definitionSq));
742
743 if (m_bUseFakeData && definition < 16)
744 {
745 int maxValue = imageData[0];
746 for (int i; i < fpsArrayCount; i += definition)
747 {
748 string toPrint;
749 for (int j; j < definition; ++j)
750 {
751 int datum = imageData[i + j];
752 if (datum > maxValue)
753 maxValue = datum;
754
755 if (j == 0)
756 toPrint += datum.ToString(2);
757 else
758 toPrint += "," + datum.ToString(2);
759
760
761 }
762
763 Print("" + toPrint);
764 }
765
766 Print("Image made of " + fpsArrayCount + " pixels");
767 Print("Min = 0, Max = " + maxValue);
768 }
769
771 imagePath,
772 imageData,
773 m_iHeatmapColourMode,
774 m_bHeatmapValueInversion,
775 m_iHeatmapMaxValueMode,
776 m_bHeatmapHighlightValuesAboveMax,
777 m_iHeatmapResolutionFactor);
778 }
779
780 //------------------------------------------------------------------------------------------------
781 [ButtonAttribute("Diagnose", true)]
782 protected int ButtonDiagnose()
783 {
784 return 1;
785 }
786
787 //------------------------------------------------------------------------------------------------
788 [ButtonAttribute("Close")]
789 protected int ButtonClose()
790 {
791 return 0;
792 }
793}
794#endif // WORKBENCH
GenerateFlowMaps WorkbenchPlugin WorkbenchPluginAttribute("Regenerate river flow-maps", "Generate and save/overwrite river flow-maps", "", "", {"WorldEditor"}, "", 0xf773)
Definition FlowmapTool.c:59
ArmaReforgerScripted GetGame()
Definition game.c:1398
params precision
IEntity SpawnEntity(ResourceName entityResourceName, notnull IEntity slotOwner)
vector direction
vector position
SCR_DestructionSynchronizationComponentClass ScriptComponentClass int index
override void Run()
UI Textures DeployMenu Briefing conflict_HintBanner_1_UI desc
class WorkbenchDialog_AbortRetryIgnore ButtonAttribute("OK", true)
Definition Debug.c:13
Definition Math.c:13
static string GetResolutionFactorEnum(array< int > multipliers=null)
static bool CreateHeatmapImageFromData(string imagePath, notnull array< int > imageData, int colourMode=COLOUR_MODE_GREYSCALE, bool invertColour=false, int maxValueMode=MAX_MODE_RAW, bool highlightValuesAboveMax=false, int resolutionFactor=1)
static float RandomGaussFloat(float min, float mid, float max)
Definition SCR_Math.c:50
static const int MAX_RANDOM
Definition SCR_Math.c:10
static ParamEnumArray FromString(string input)
static void PrintDialog(string message, string caption="", LogLevel level=LogLevel.WARNING)
static void PrintFormatDialog(string message, string param1, string param2="", string param3="", string caption="", LogLevel level=LogLevel.WARNING)
Definition World.c:16
Definition Types.c:486
proto void Print(void var, LogLevel level=LogLevel.NORMAL)
Prints content of variable to console/log.
LogLevel
Enum with severity of the logging message.
Definition LogLevel.c:14
proto void PrintFormat(string fmt, void param1=NULL, void param2=NULL, void param3=NULL, void param4=NULL, void param5=NULL, void param6=NULL, void param7=NULL, void param8=NULL, void param9=NULL, LogLevel level=LogLevel.NORMAL)
SCR_FieldOfViewSettings Attribute
proto external string ToString()
Plain C++ pointer, no weak pointers, no memory management.
TraceFlags
Definition TraceFlags.c:13