mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-11-01 12:45:56 +00:00
refactor(agc): take the performance overlay panel's width from its content
The panel was fixed at OverlayMetrics::Columns, 27 columns, and always kept rows for the frame-time graph. OverlayContent now carries its column count and whether it has a graph, and the panel's width, height, fit test and scale come from them. ComposeOverlay still fills 27 columns with a graph, so the existing panel is drawn as before, line for line. LayOutOverlay refuses a panel with no columns, a graph on a panel narrower than its 120 frames, and frame times on a panel without a graph. The 5x7 font gains %, +, -, _, J, Q, Y and Z, from the same HD44780 character set as its other glyphs, and OverlayFontCharacters lists every character it can draw. Tests: every listed character has a glyph no wider than 5 pixels, non-empty unless it is the space, and unlike any other; characters outside the list are refused; a 48-column panel without a graph is laid out and scaled at 1280x720, 1920x1080, 3840x2160 and 320x180; a 140-column panel at 3840x2160 shrinks from scale 5 to 4; a too-long line, a 20-column graph and frame times without a graph are refused.
This commit is contained in:
1 parent
e0b13f2990
commit
e11fb2a288
3 files changed
+113
-22
No files matched your search
@@ -14,6 +14,7 @@
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
@@ -123,10 +124,25 @@ struct OverlayRect {
|
||||
enum class OverlayColor : std::uint32_t { Background = 0, Text = 1, Graph = 2 };
|
||||
inline constexpr std::size_t OverlayColorCount = 3;
|
||||
|
||||
struct OverlayMetrics {
|
||||
static constexpr std::int32_t GlyphWidth = 5;
|
||||
static constexpr std::int32_t GlyphHeight = 7;
|
||||
static constexpr std::int32_t Advance = 6;
|
||||
static constexpr std::int32_t LinePitch = 9;
|
||||
static constexpr std::int32_t Columns = 27;
|
||||
static constexpr std::int32_t Padding = 3;
|
||||
static constexpr std::int32_t Margin = 4;
|
||||
static constexpr std::int32_t GraphHeight = 20;
|
||||
static constexpr std::int32_t GraphGap = 2;
|
||||
static constexpr std::int32_t InnerWidth = Columns * Advance - 1;
|
||||
};
|
||||
|
||||
struct OverlayContent {
|
||||
std::vector<std::string> above;
|
||||
std::vector<double> frameTimes;
|
||||
std::vector<std::string> below;
|
||||
std::int32_t columns = OverlayMetrics::Columns;
|
||||
bool graph = true;
|
||||
};
|
||||
|
||||
struct OverlayMemory {
|
||||
@@ -147,20 +163,8 @@ struct OverlayLayout {
|
||||
std::int32_t scale = 1;
|
||||
};
|
||||
|
||||
struct OverlayMetrics {
|
||||
static constexpr std::int32_t GlyphWidth = 5;
|
||||
static constexpr std::int32_t GlyphHeight = 7;
|
||||
static constexpr std::int32_t Advance = 6;
|
||||
static constexpr std::int32_t LinePitch = 9;
|
||||
static constexpr std::int32_t Columns = 27;
|
||||
static constexpr std::int32_t Padding = 3;
|
||||
static constexpr std::int32_t Margin = 4;
|
||||
static constexpr std::int32_t GraphHeight = 20;
|
||||
static constexpr std::int32_t GraphGap = 2;
|
||||
static constexpr std::int32_t InnerWidth = Columns * Advance - 1;
|
||||
};
|
||||
|
||||
const std::array<std::uint8_t, OverlayMetrics::GlyphHeight>& OverlayGlyph(char character);
|
||||
std::string_view OverlayFontCharacters();
|
||||
std::string FormatOverlayValue(double value);
|
||||
std::string FormatOverlayRate(double value);
|
||||
std::string FormatOverlayCount(double value);
|
||||
|
||||
@@ -69,19 +69,20 @@ const OverlaySettings& settings() {
|
||||
return parsed;
|
||||
}
|
||||
|
||||
std::int32_t panelWidth(std::int32_t scale) {
|
||||
return (OverlayMetrics::InnerWidth + 2 * OverlayMetrics::Padding) * scale;
|
||||
std::int32_t panelWidth(std::int32_t columns, std::int32_t scale) {
|
||||
return (columns * OverlayMetrics::Advance - 1 + 2 * OverlayMetrics::Padding) * scale;
|
||||
}
|
||||
|
||||
std::int32_t panelHeight(const OverlayContent& content, std::int32_t scale) {
|
||||
const auto lines = static_cast<std::int32_t>(content.above.size() + content.below.size());
|
||||
const auto glyphGap = OverlayMetrics::LinePitch - OverlayMetrics::GlyphHeight;
|
||||
return (2 * OverlayMetrics::Padding + lines * OverlayMetrics::LinePitch + OverlayMetrics::GraphHeight + OverlayMetrics::GraphGap - glyphGap) * scale;
|
||||
const auto graph = content.graph ? OverlayMetrics::GraphHeight + OverlayMetrics::GraphGap : 0;
|
||||
return (2 * OverlayMetrics::Padding + lines * OverlayMetrics::LinePitch + graph - glyphGap) * scale;
|
||||
}
|
||||
|
||||
bool fits(VkExtent2D extent, const OverlayContent& content, std::int32_t scale) {
|
||||
const auto margins = 2 * OverlayMetrics::Margin * scale;
|
||||
return panelWidth(scale) + margins <= static_cast<std::int32_t>(extent.width) && panelHeight(content, scale) + margins <= static_cast<std::int32_t>(extent.height);
|
||||
return panelWidth(content.columns, scale) + margins <= static_cast<std::int32_t>(extent.width) && panelHeight(content, scale) + margins <= static_cast<std::int32_t>(extent.height);
|
||||
}
|
||||
|
||||
float srgbDecode(float value) {
|
||||
@@ -289,6 +290,10 @@ const std::array<std::uint8_t, OverlayMetrics::GlyphHeight>& OverlayGlyph(char c
|
||||
static constexpr Glyph period{0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x0c};
|
||||
static constexpr Glyph colon{0x00, 0x0c, 0x0c, 0x00, 0x0c, 0x0c, 0x00};
|
||||
static constexpr Glyph slash{0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x00};
|
||||
static constexpr Glyph percent{0x18, 0x19, 0x02, 0x04, 0x08, 0x13, 0x03};
|
||||
static constexpr Glyph plus{0x00, 0x04, 0x04, 0x1f, 0x04, 0x04, 0x00};
|
||||
static constexpr Glyph minus{0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00};
|
||||
static constexpr Glyph underscore{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f};
|
||||
static constexpr Glyph a{0x0e, 0x11, 0x11, 0x11, 0x1f, 0x11, 0x11};
|
||||
static constexpr Glyph b{0x1e, 0x11, 0x11, 0x1e, 0x11, 0x11, 0x1e};
|
||||
static constexpr Glyph c{0x0e, 0x11, 0x10, 0x10, 0x10, 0x11, 0x0e};
|
||||
@@ -298,12 +303,14 @@ const std::array<std::uint8_t, OverlayMetrics::GlyphHeight>& OverlayGlyph(char c
|
||||
static constexpr Glyph g{0x0e, 0x11, 0x10, 0x17, 0x11, 0x11, 0x0f};
|
||||
static constexpr Glyph h{0x11, 0x11, 0x11, 0x1f, 0x11, 0x11, 0x11};
|
||||
static constexpr Glyph i{0x0e, 0x04, 0x04, 0x04, 0x04, 0x04, 0x0e};
|
||||
static constexpr Glyph j{0x07, 0x02, 0x02, 0x02, 0x02, 0x12, 0x0c};
|
||||
static constexpr Glyph k{0x11, 0x12, 0x14, 0x18, 0x14, 0x12, 0x11};
|
||||
static constexpr Glyph l{0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x1f};
|
||||
static constexpr Glyph m{0x11, 0x1b, 0x15, 0x15, 0x11, 0x11, 0x11};
|
||||
static constexpr Glyph n{0x11, 0x11, 0x19, 0x15, 0x13, 0x11, 0x11};
|
||||
static constexpr Glyph o{0x0e, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0e};
|
||||
static constexpr Glyph p{0x1e, 0x11, 0x11, 0x1e, 0x10, 0x10, 0x10};
|
||||
static constexpr Glyph q{0x0e, 0x11, 0x11, 0x11, 0x15, 0x12, 0x0d};
|
||||
static constexpr Glyph r{0x1e, 0x11, 0x11, 0x1e, 0x14, 0x12, 0x11};
|
||||
static constexpr Glyph s{0x0f, 0x10, 0x10, 0x0e, 0x01, 0x01, 0x1e};
|
||||
static constexpr Glyph t{0x1f, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04};
|
||||
@@ -311,12 +318,18 @@ const std::array<std::uint8_t, OverlayMetrics::GlyphHeight>& OverlayGlyph(char c
|
||||
static constexpr Glyph v{0x11, 0x11, 0x11, 0x11, 0x11, 0x0a, 0x04};
|
||||
static constexpr Glyph w{0x11, 0x11, 0x11, 0x15, 0x15, 0x15, 0x0a};
|
||||
static constexpr Glyph x{0x11, 0x11, 0x0a, 0x04, 0x0a, 0x11, 0x11};
|
||||
static constexpr Glyph y{0x11, 0x11, 0x11, 0x0a, 0x04, 0x04, 0x04};
|
||||
static constexpr Glyph z{0x1f, 0x01, 0x02, 0x04, 0x08, 0x10, 0x1f};
|
||||
if (character >= '0' && character <= '9') return digits[static_cast<std::size_t>(character - '0')];
|
||||
switch (character) {
|
||||
case ' ': return space;
|
||||
case '.': return period;
|
||||
case ':': return colon;
|
||||
case '/': return slash;
|
||||
case '%': return percent;
|
||||
case '+': return plus;
|
||||
case '-': return minus;
|
||||
case '_': return underscore;
|
||||
case 'A': return a;
|
||||
case 'B': return b;
|
||||
case 'C': return c;
|
||||
@@ -326,12 +339,14 @@ const std::array<std::uint8_t, OverlayMetrics::GlyphHeight>& OverlayGlyph(char c
|
||||
case 'G': return g;
|
||||
case 'H': return h;
|
||||
case 'I': return i;
|
||||
case 'J': return j;
|
||||
case 'K': return k;
|
||||
case 'L': return l;
|
||||
case 'M': return m;
|
||||
case 'N': return n;
|
||||
case 'O': return o;
|
||||
case 'P': return p;
|
||||
case 'Q': return q;
|
||||
case 'R': return r;
|
||||
case 'S': return s;
|
||||
case 'T': return t;
|
||||
@@ -339,10 +354,16 @@ const std::array<std::uint8_t, OverlayMetrics::GlyphHeight>& OverlayGlyph(char c
|
||||
case 'V': return v;
|
||||
case 'W': return w;
|
||||
case 'X': return x;
|
||||
case 'Y': return y;
|
||||
case 'Z': return z;
|
||||
default: throw std::runtime_error(std::string("the performance overlay font has no glyph for '") + character + "'");
|
||||
}
|
||||
}
|
||||
|
||||
std::string_view OverlayFontCharacters() {
|
||||
return " .:/%+-_0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
}
|
||||
|
||||
std::string FormatOverlayValue(double value) {
|
||||
const double clamped = std::isfinite(value) ? std::clamp(value, 0.0, 9999.0) : 0.0;
|
||||
char text[16];
|
||||
@@ -393,7 +414,7 @@ OverlayContent ComposeOverlay(const OverlayStatistics& statistics, bool gpu, boo
|
||||
const auto size = [](VkExtent2D extent) { return std::to_string(extent.width) + "X" + std::to_string(extent.height); };
|
||||
for (const std::string_view separator : {" / ", "/"}) {
|
||||
auto line = "RES: " + size(details.display) + std::string(separator) + size(details.window);
|
||||
if (line.size() > static_cast<std::size_t>(OverlayMetrics::Columns)) continue;
|
||||
if (line.size() > static_cast<std::size_t>(content.columns)) continue;
|
||||
content.below.push_back(std::move(line));
|
||||
break;
|
||||
}
|
||||
@@ -448,10 +469,12 @@ std::int32_t OverlayScale(VkExtent2D extent, const OverlayContent& content) {
|
||||
OverlayLayout LayOutOverlay(const OverlayContent& content, VkExtent2D extent, OverlayCorner corner) {
|
||||
Graphics::Require(extent.width != 0 && extent.height != 0, "the performance overlay needs a destination");
|
||||
Graphics::Require(content.frameTimes.size() <= OverlayStatistics::FrameHistory, "the performance overlay graph holds at most 120 frames");
|
||||
Graphics::Require(content.columns > 0, "the performance overlay panel needs at least one column");
|
||||
Graphics::Require(content.graph ? content.columns * OverlayMetrics::Advance - 1 >= static_cast<std::int32_t>(OverlayStatistics::FrameHistory) : content.frameTimes.empty(), "the performance overlay graph needs a graph row as wide as its 120 frames");
|
||||
OverlayLayout layout;
|
||||
const auto scale = OverlayScale(extent, content);
|
||||
layout.scale = scale;
|
||||
const auto width = panelWidth(scale);
|
||||
const auto width = panelWidth(content.columns, scale);
|
||||
const auto height = panelHeight(content, scale);
|
||||
const auto margin = OverlayMetrics::Margin * scale;
|
||||
const bool right = corner == OverlayCorner::TopRight || corner == OverlayCorner::BottomRight;
|
||||
@@ -461,7 +484,7 @@ OverlayLayout LayOutOverlay(const OverlayContent& content, VkExtent2D extent, Ov
|
||||
layout.bounds = {left, top, width, height, static_cast<std::uint32_t>(OverlayColor::Background)};
|
||||
layout.rects.push_back(layout.bounds);
|
||||
const auto text = [&](const std::string& line, std::int32_t row) {
|
||||
Graphics::Require(line.size() <= static_cast<std::size_t>(OverlayMetrics::Columns), "a performance overlay line is wider than the panel");
|
||||
Graphics::Require(line.size() <= static_cast<std::size_t>(content.columns), "a performance overlay line is wider than the panel");
|
||||
for (std::size_t column = 0; column < line.size(); ++column) {
|
||||
const auto& glyph = OverlayGlyph(line[column]);
|
||||
const auto glyphLeft = left + (OverlayMetrics::Padding + static_cast<std::int32_t>(column) * OverlayMetrics::Advance) * scale;
|
||||
@@ -487,7 +510,7 @@ OverlayLayout LayOutOverlay(const OverlayContent& content, VkExtent2D extent, Ov
|
||||
row += OverlayMetrics::LinePitch;
|
||||
}
|
||||
const auto graphBottom = top + (row + OverlayMetrics::GraphHeight) * scale;
|
||||
if (!content.frameTimes.empty()) {
|
||||
if (content.graph && !content.frameTimes.empty()) {
|
||||
double maximum = 0;
|
||||
double sum = 0;
|
||||
for (const auto time : content.frameTimes) {
|
||||
@@ -501,7 +524,7 @@ OverlayLayout LayOutOverlay(const OverlayContent& content, VkExtent2D extent, Ov
|
||||
layout.rects.push_back({left + (OverlayMetrics::Padding + first + static_cast<std::int32_t>(i)) * scale, graphBottom - pixels, scale, pixels, static_cast<std::uint32_t>(OverlayColor::Graph)});
|
||||
}
|
||||
}
|
||||
row += OverlayMetrics::GraphHeight + OverlayMetrics::GraphGap;
|
||||
if (content.graph) row += OverlayMetrics::GraphHeight + OverlayMetrics::GraphGap;
|
||||
for (const auto& line : content.below) {
|
||||
text(line, row);
|
||||
row += OverlayMetrics::LinePitch;
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
@@ -431,6 +432,67 @@ void fullPanelLayoutTests() {
|
||||
}
|
||||
}
|
||||
|
||||
void fontTests() {
|
||||
const auto characters = AgcDriver::OverlayFontCharacters();
|
||||
for (const char character : characters) {
|
||||
const auto& glyph = AgcDriver::OverlayGlyph(character);
|
||||
require(std::all_of(glyph.begin(), glyph.end(), [](std::uint8_t row) { return row <= 0x1f; }), std::string("the glyph of '") + character + "' is wider than 5 pixels");
|
||||
require(character == ' ' || std::any_of(glyph.begin(), glyph.end(), [](std::uint8_t row) { return row != 0; }), std::string("the glyph of '") + character + "' is empty");
|
||||
for (const char other : characters) {
|
||||
if (other != character) require(glyph != AgcDriver::OverlayGlyph(other), std::string("'") + character + "' and '" + other + "' have the same glyph");
|
||||
}
|
||||
}
|
||||
for (const char letter : std::string("JQYZ%+-_")) require(characters.find(letter) != std::string_view::npos, std::string("the font lists no '") + letter + "'");
|
||||
for (const char missing : std::string("qjy(>,#")) {
|
||||
bool refused = false;
|
||||
try {
|
||||
static_cast<void>(AgcDriver::OverlayGlyph(missing));
|
||||
} catch (const std::exception&) {
|
||||
refused = true;
|
||||
}
|
||||
require(refused && characters.find(missing) == std::string_view::npos, std::string("'") + missing + "' is drawn or listed although the font has no glyph for it");
|
||||
}
|
||||
}
|
||||
|
||||
void widePanelTests() {
|
||||
OverlayContent wide;
|
||||
wide.columns = 48;
|
||||
wide.graph = false;
|
||||
for (int i = 0; i < 20; ++i) wide.below.push_back(std::string(48, i % 2 == 0 ? 'W' : 'Z'));
|
||||
const VkExtent2D small{1280, 720};
|
||||
const auto layout = AgcDriver::LayOutOverlay(wide, small, OverlayCorner::TopLeft);
|
||||
const auto s = layout.scale;
|
||||
require(layout.bounds.width == (48 * OverlayMetrics::Advance - 1 + 2 * OverlayMetrics::Padding) * s, "a 48-column panel is " + std::to_string(layout.bounds.width) + " pixels wide at scale " + std::to_string(s));
|
||||
require(layout.bounds.height == (2 * OverlayMetrics::Padding + 20 * OverlayMetrics::LinePitch - (OverlayMetrics::LinePitch - OverlayMetrics::GlyphHeight)) * s, "a panel without a graph keeps the graph's rows");
|
||||
const auto text = coverage(layout, static_cast<std::uint32_t>(AgcDriver::OverlayColor::Text));
|
||||
for (std::size_t line = 0; line < wide.below.size(); ++line) requireLineDrawn(layout, text, wide.below[line], OverlayMetrics::Padding + static_cast<std::int32_t>(line) * OverlayMetrics::LinePitch);
|
||||
for (const auto& rect : layout.rects) require(rect.color != static_cast<std::uint32_t>(AgcDriver::OverlayColor::Graph), "a panel without a graph draws graph bars");
|
||||
const std::pair<VkExtent2D, std::int32_t> scales[] = {{{1280, 720}, 2}, {{1920, 1080}, 2}, {{3840, 2160}, 5}, {{320, 180}, 1}};
|
||||
for (const auto& [extent, scale] : scales) require(AgcDriver::OverlayScale(extent, wide) == scale, "a 48-column panel at " + std::to_string(extent.width) + "x" + std::to_string(extent.height) + " is drawn at scale " + std::to_string(AgcDriver::OverlayScale(extent, wide)) + ", not " + std::to_string(scale));
|
||||
auto widest = wide;
|
||||
widest.columns = 140;
|
||||
require(AgcDriver::OverlayScale({3840, 2160}, widest) == 4, "a 140-column panel at 3840x2160 is not shrunk from scale 5 to 4");
|
||||
const auto refused = [](const OverlayContent& content) {
|
||||
try {
|
||||
static_cast<void>(AgcDriver::LayOutOverlay(content, {1920, 1080}, OverlayCorner::TopLeft));
|
||||
} catch (const std::exception&) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
auto longer = wide;
|
||||
longer.below.push_back(std::string(49, 'W'));
|
||||
require(refused(longer), "a line longer than the panel's columns was drawn");
|
||||
auto graphless = wide;
|
||||
graphless.frameTimes = {16.0};
|
||||
require(refused(graphless), "frame times were laid out on a panel without a graph");
|
||||
OverlayContent cramped{{"FPS: 60.0"}, {16.0}, {}};
|
||||
cramped.columns = 20;
|
||||
require(refused(cramped), "a graph of 120 frames was laid out on a 20-column panel");
|
||||
cramped.columns = 21;
|
||||
require(!refused(cramped), "a 21-column panel cannot hold the graph");
|
||||
}
|
||||
|
||||
void sampleTests() {
|
||||
require(AgcDriver::OverlaySampleDue(std::nullopt, Start), "the first frame is not sampled");
|
||||
require(!AgcDriver::OverlaySampleDue(Start, Start + 999ms) && AgcDriver::OverlaySampleDue(Start, Start + 1000ms), "memory is sampled more than once a second, or not after a second");
|
||||
@@ -489,6 +551,8 @@ int main() {
|
||||
countFormatTests();
|
||||
detailComposeTests();
|
||||
fullPanelLayoutTests();
|
||||
fontTests();
|
||||
widePanelTests();
|
||||
sampleTests();
|
||||
costReport();
|
||||
std::cout << "performance overlay statistics, layout, palette, packet, pipeline, memory and resolution tests passed\n";
|
||||
|
||||
Reference in new issue
Block a user