Skip to content

Commit 9164c7c

Browse files
committed
[Graphics] Screenshots: Measure the time for each format/effort setting
Using `std::chrono::steady_clock` rather than insisting on the SDL functions is *definitely* fine for now: • On Windows, MSVC implements it in terms of QueryPerformanceCounter(), so it will work on 9x as well. • Also, that one `std::this_thread::sleep_for()` in the MIDI frontend has already been using it. Closes #54. Part of P0309, funded by Ember2528.
1 parent eca6b26 commit 9164c7c

File tree

5 files changed

+119
-16
lines changed

5 files changed

+119
-16
lines changed

GIAN07/WindowCtrl.cpp

Lines changed: 84 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
#include "game/snd.h"
2121
#include "game/string_format.h"
2222

23+
using namespace std::chrono_literals;
24+
2325

2426

2527
static bool ExitFnYes(INPUT_BITS key);
@@ -141,10 +143,20 @@ static void FnFormat(int_fast8_t delta);
141143
static void SetItem(bool tick = true);
142144

143145
static char TitleFormat[50];
144-
WINDOW_CHOICE Item[] = {
145-
{ TitleFormat, "", FnFormat },
146-
SubmenuExitItemForArray,
147-
};
146+
static char TitlePerf[GRP_SCREENSHOT_EFFORT_COUNT][50];
147+
static char HelpPerf[50];
148+
constinit auto Item = ([] {
149+
std::array<WINDOW_CHOICE, (2 + GRP_SCREENSHOT_EFFORT_COUNT + 2)> ret{};
150+
auto item_p = ret.begin();
151+
*(item_p++) = { TitleFormat, "", FnFormat };
152+
*(item_p++) = { "--- Performance ---" };
153+
for(const auto& title : Screenshot::TitlePerf) {
154+
*(item_p++) = WINDOW_CHOICE{ title, HelpPerf, FnFormat };
155+
}
156+
*(item_p++) = HRuleItemForArray;
157+
*(item_p++) = SubmenuExitItemForArray;
158+
return ret;
159+
})();
148160
WINDOW_MENU Menu = { std::span(Item), SetItem };
149161
static auto& ItemFormat = Item[0];
150162
} // namespace Screenshot
@@ -1076,22 +1088,87 @@ static void Main::Cfg::Grp::Screenshot::SetItem(bool)
10761088
const auto effort = ConfigDat.ScreenshotEffort.v;
10771089
#endif
10781090
char format_buf[8];
1079-
auto format_for = [&format_buf](decltype(effort) effort) -> const char * {
1091+
enum class ALIGN { LEFT, CENTER };
1092+
const auto format_for = [&format_buf](
1093+
decltype(effort) effort, ALIGN align
1094+
) -> const char * {
10801095
if(effort == 0) {
1081-
return " BMP ";
1096+
switch(align) {
1097+
case ALIGN::LEFT: return "BMP ";
1098+
case ALIGN::CENTER: return " BMP ";
1099+
}
10821100
}
10831101
strcpy(format_buf, "WebP z");
10841102
format_buf[6] = ('0' + (effort - 1));
10851103
format_buf[7] = '\0';
10861104
return &format_buf[0];
10871105
};
10881106

1089-
sprintf(TitleFormat, "Format [%s]", format_for(effort));
1107+
const auto split_into_fraction = [](auto v) {
1108+
const auto ret_int = (v / 1000);
1109+
const auto ret_frac = ((v % 1000) / 10);
1110+
return std::pair(ret_int, ret_frac);
1111+
};
1112+
1113+
sprintf(TitleFormat, "Format [%s]", format_for(effort, ALIGN::CENTER));
10901114
if(effort == 0) {
10911115
ItemFormat.Help = "Saving as uncompressed .BMP";
10921116
} else {
10931117
ItemFormat.Help = ("Lossless compression (higher = slower)");
10941118
}
1119+
1120+
for(const auto i : std::views::iota(0u, GRP_SCREENSHOT_EFFORT_COUNT)) {
1121+
auto& item = Item[2 + i];
1122+
const auto hovered = (
1123+
MainWindow.Select[MainWindow.SelectDepth] == (2 + i)
1124+
);
1125+
const auto format = format_for(i, ALIGN::LEFT);
1126+
const auto time = Grp_ScreenshotTimes[i];
1127+
EnumFlagSet(item.Flags, WINDOW_FLAGS::HIGHLIGHT, (i == effort));
1128+
if(time < 0s) {
1129+
sprintf(TitlePerf[i], "%s[ FAILED ]", format);
1130+
if(hovered) {
1131+
strcpy(HelpPerf, "https://github.com/nmlgc/ssg/issues/23");
1132+
}
1133+
} else if(time == 0s) {
1134+
sprintf(TitlePerf[i], "%s[ ? ]", format);
1135+
if(hovered) {
1136+
strcpy(HelpPerf, "Not yet measured");
1137+
}
1138+
} else {
1139+
const auto [time_int, time_frac] = split_into_fraction(
1140+
std::chrono::duration_cast<decltype(0us)>(time).count() + 5
1141+
);
1142+
sprintf(
1143+
TitlePerf[i], "%s[%5lld.%02lldms]", format, time_int, time_frac
1144+
);
1145+
if(hovered) {
1146+
constexpr auto target_ms = decltype(0ms)(FRAME_TIME_TARGET);
1147+
if(time > (target_ms * ConfigDat.FPSDivisor.v)) {
1148+
const auto fps = split_into_fraction((1000s / time));
1149+
sprintf(
1150+
HelpPerf,
1151+
"Frame rate will drop to ~%lld.%02lld FPS",
1152+
fps.first,
1153+
fps.second
1154+
);
1155+
} else {
1156+
// Easier to just hardcode the numbers than to calculate
1157+
// and format this without using floating-point variables...
1158+
static constexpr const char *FPS[3] = {
1159+
"62.5", "30", "20"
1160+
};
1161+
assert(ConfigDat.FPSDivisor.v > 0);
1162+
assert((ConfigDat.FPSDivisor.v - 1) < std::size(FPS));
1163+
sprintf(
1164+
HelpPerf,
1165+
"Frame rate will stay at %s FPS",
1166+
FPS[ConfigDat.FPSDivisor.v - 1]
1167+
);
1168+
}
1169+
}
1170+
}
1171+
}
10951172
}
10961173

10971174
#ifdef SUPPORT_GRP_API

game/graphics.cpp

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
#include <webp/encode.h>
1414

1515
uint8_t Grp_FPSDivisor = 0;
16+
std::chrono::steady_clock::duration Grp_ScreenshotTimes[
17+
GRP_SCREENSHOT_EFFORT_COUNT
18+
];
1619

1720
// Paletted graphics //
1821
// ----------------- //
@@ -212,17 +215,26 @@ bool Grp_ScreenshotSave(
212215
PIXEL_SIZE_BASE<unsigned int> size,
213216
PIXELFORMAT format,
214217
std::span<BGRA> palette,
215-
std::span<const std::byte> pixels
218+
std::span<const std::byte> pixels,
219+
const std::chrono::steady_clock::time_point t_start
216220
)
217221
{
222+
constexpr auto DURATION_FAILED = std::chrono::steady_clock::duration(-1);
223+
218224
auto ret = false;
219-
const auto effort = Grp_ScreenshotEffort;
225+
auto effort = Grp_ScreenshotEffort;
220226
if(effort != 0) {
221227
ret = ScreenshotSaveWebP(size, format, palette, pixels, (effort - 1));
228+
if(!ret) {
229+
Grp_ScreenshotTimes[effort] = DURATION_FAILED;
230+
}
222231
}
223232
if(!ret) {
233+
effort = 0;
224234
ret = ScreenshotSaveBMP(size, format, palette, pixels);
225235
}
236+
const auto t_end = std::chrono::steady_clock::now();
237+
Grp_ScreenshotTimes[effort] = (ret ? (t_end - t_start) : DURATION_FAILED);
226238
return ret;
227239
}
228240
// -----------

game/graphics.h

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,16 +119,23 @@ constexpr uint8_t GRP_SCREENSHOT_EFFORT_MAX = (GRP_SCREENSHOT_EFFORT_COUNT - 1);
119119

120120
extern const uint8_t& Grp_ScreenshotEffort;
121121

122+
// 0 = not yet tried, -1 = last attempt failed.
123+
extern std::chrono::steady_clock::duration Grp_ScreenshotTimes[
124+
GRP_SCREENSHOT_EFFORT_COUNT
125+
];
126+
122127
// Required to enable the screenshot feature as a whole.
123128
void Grp_ScreenshotSetPrefix(std::u8string_view prefix);
124129

125130
// Saves the given bitmap in the screenshot format to a file with the
126-
// screenshot prefix.
131+
// screenshot prefix. [t_start] represents the very beginning of the backend's
132+
// capturing process.
127133
bool Grp_ScreenshotSave(
128134
PIXEL_SIZE_BASE<unsigned int> size,
129135
PIXELFORMAT format,
130136
std::span<BGRA> palette,
131-
std::span<const std::byte> pixels
137+
std::span<const std::byte> pixels,
138+
const std::chrono::steady_clock::time_point t_start
132139
);
133140
// -----------
134141

platform/sdl/graphics_sdl.cpp

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -988,7 +988,9 @@ PIXELFORMAT GrpBackend_PixelFormat(void)
988988
void GrpBackend_PaletteGet(PALETTE& pal) {}
989989
bool GrpBackend_PaletteSet(const PALETTE& pal) { return false; }
990990

991-
void SaveSurfaceAsScreenshot(SDL_Surface *src)
991+
void SaveSurfaceAsScreenshot(
992+
SDL_Surface *src, const std::chrono::steady_clock::time_point t_start
993+
)
992994
{
993995
assert(src);
994996
assert(src->pitch >= 0);
@@ -1022,18 +1024,22 @@ void SaveSurfaceAsScreenshot(SDL_Surface *src)
10221024
const auto pixels = std::span(
10231025
static_cast<std::byte *>(src->pixels), (src->h * src->pitch)
10241026
);
1025-
Grp_ScreenshotSave(size, maybe_format.value(), {}, pixels);
1027+
Grp_ScreenshotSave(size, maybe_format.value(), {}, pixels, t_start);
10261028
if(SDL_MUSTLOCK(src)) {
10271029
SDL_UnlockSurface(src);
10281030
}
10291031
}
10301032

10311033
void TakeScreenshot(void)
10321034
{
1035+
// The rendering itself should not impact screenshot timing.
1036+
SDL_FlushRenderer(Renderer);
1037+
const auto t_start = std::chrono::steady_clock::now();
1038+
10331039
if(SoftwareRenderer) {
10341040
// Software rendering is the ideal case for screenshots, because we
10351041
// already have a system-memory surface we can save.
1036-
SaveSurfaceAsScreenshot(SoftwareSurface);
1042+
SaveSurfaceAsScreenshot(SoftwareSurface, t_start);
10371043
return;
10381044
}
10391045

@@ -1094,7 +1100,7 @@ void TakeScreenshot(void)
10941100
SDL_UnlockSurface(src);
10951101
}
10961102
#endif
1097-
SaveSurfaceAsScreenshot(src);
1103+
SaveSurfaceAsScreenshot(src, t_start);
10981104
}
10991105

11001106
void GrpBackend_Flip(bool take_screenshot)

platform/windows_vintage/DD_UTY.CPP

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@ bool DDrawSaveScreenshot(IDirectDrawSurface *surf)
194194
if(!surf) {
195195
return false;
196196
}
197+
const auto t_start = std::chrono::steady_clock::now();
197198
std::array<BGRA, BMP_PALETTE_SIZE_MAX> bgra_memory;
198199
const auto palette = [surf, &bgra_memory]() -> std::span<BGRA> {
199200
IDirectDrawPalette* pal = nullptr;
@@ -257,7 +258,7 @@ bool DDrawSaveScreenshot(IDirectDrawSurface *surf)
257258
format = { PIXELFORMAT::BGRX8888 };
258259
}
259260

260-
return Grp_ScreenshotSave(size, format, palette, pixels);
261+
return Grp_ScreenshotSave(size, format, palette, pixels, t_start);
261262
}
262263

263264

0 commit comments

Comments
 (0)