initial commit

This commit is contained in:
Johanness
2026-05-18 00:03:06 +03:00
commit ea7e73d6a4
183 changed files with 34479 additions and 0 deletions
+499
View File
@@ -0,0 +1,499 @@
#include "AppConfig.hpp"
#include <iostream>
#include <optional>
#include <cstdint>
#include <string>
#include <string_view>
#include <vector>
namespace
{
bool parseUint32(
const char* text,
uint32_t& value)
{
uint32_t parsed = 0;
bool hasDigit = false;
for (int i = 0; text[i] != '\0'; ++i)
{
if (text[i] < '0' || text[i] > '9')
{
return false;
}
hasDigit = true;
parsed = parsed * 10u +
static_cast<uint32_t>(text[i] - '0');
}
if (!hasDigit)
{
return false;
}
value = parsed;
return true;
}
bool parseFeedIndex(
const char* text,
uint32_t maxFeeds,
uint32_t& index)
{
uint32_t parsed = 0;
if (!parseUint32(text, parsed) ||
parsed >= maxFeeds)
{
return false;
}
index = parsed;
return true;
}
bool parseGrid(
std::string_view text,
uint32_t& cols,
uint32_t& rows)
{
const size_t xPos = text.find('x');
if (xPos == std::string_view::npos)
{
return false;
}
const std::string colText(text.substr(0, xPos));
const std::string rowText(text.substr(xPos + 1));
uint32_t parsedCols = 0;
uint32_t parsedRows = 0;
if (!parseUint32(colText.c_str(), parsedCols) ||
!parseUint32(rowText.c_str(), parsedRows) ||
parsedCols == 0 ||
parsedRows == 0 ||
parsedCols > MaxFeedCount / parsedRows)
{
return false;
}
cols = parsedCols;
rows = parsedRows;
return true;
}
uint32_t feedCount(const AppConfig& config)
{
return config.gridCols * config.gridRows;
}
void applyDefaultFeeds(AppConfig& config)
{
config.feeds.assign(feedCount(config), FeedConfig{});
}
}
std::optional<FeedKind> parseFeedKind(std::string_view text)
{
if (text == "nosignal" ||
text == "none" ||
text == "fake")
{
return FeedKind::NoSignal;
}
if (text == "mxl")
{
return FeedKind::MxlPlaceholder;
}
if (text == "smpte")
{
return FeedKind::SmpteBars;
}
if (text == "mxlsdk")
{
return FeedKind::MxlSdk;
}
return std::nullopt;
}
std::optional<PresentModeConfig> parsePresentMode(std::string_view text)
{
if (text == "fifo")
{
return PresentModeConfig::Fifo;
}
if (text == "mailbox")
{
return PresentModeConfig::Mailbox;
}
if (text == "immediate")
{
return PresentModeConfig::Immediate;
}
return std::nullopt;
}
std::string_view feedKindName(FeedKind kind)
{
switch (kind)
{
case FeedKind::MxlPlaceholder:
return "mxl";
case FeedKind::SmpteBars:
return "smpte";
case FeedKind::MxlSdk:
return "mxlsdk";
case FeedKind::NoSignal:
default:
return "nosignal";
}
}
void printUsage(const char* executableName)
{
std::cout
<< "Usage: "
<< executableName
<< " [options] [feed...]\n"
<< "Options:\n"
<< " --no-fps Disable FPS logging\n"
<< " --perf Log per-frame CPU timing breakdown\n"
<< " --verbose Enable detailed startup and SDK logs\n"
<< " --grid <cols>x<rows> Set multiview grid (default 2x2, max 16 feeds)\n"
<< " --fps-cap <fps> Limit render loop FPS (default 60)\n"
<< " --present <mode> Present mode: fifo, mailbox, immediate (default mailbox)\n"
<< " --feed <idx> <kind> Set feed slot kind explicitly\n"
<< " --domain <idx> <path> Set MXL domain for feed slot\n"
<< " --flow <idx> <uuid> Set MXL flow UUID for feed slot\n"
<< " --version Print version\n"
<< " --help Print this help\n"
<< "Feed kinds: nosignal, none, fake, mxl, smpte, mxlsdk\n"
<< "Default: 2x2 grid, all slots nosignal"
<< std::endl;
}
ConfigParseResult parseAppConfig(
int argc,
char* argv[])
{
ConfigParseResult result;
for (int i = 1; i < argc; ++i)
{
if (std::string_view(argv[i]) != "--grid")
{
continue;
}
if (i + 1 >= argc ||
!parseGrid(
argv[i + 1],
result.config.gridCols,
result.config.gridRows))
{
std::cerr << "--grid must be <cols>x<rows> with 1-"
<< MaxFeedCount << " total feeds"
<< std::endl;
result.shouldExit = true;
result.exitCode = 1;
return result;
}
++i;
}
applyDefaultFeeds(result.config);
for (int i = 1; i < argc; ++i)
{
const std::string_view arg(argv[i]);
if (arg == "--help" || arg == "-h")
{
printUsage(argv[0]);
result.shouldExit = true;
result.exitCode = 0;
return result;
}
}
for (int i = 1; i < argc; ++i)
{
const std::string_view arg(argv[i]);
if (arg == "--version" || arg == "-v")
{
std::cout
<< AppName
<< " "
<< AppVersionMajor
<< "."
<< AppVersionMinor
<< "."
<< AppVersionPatch
<< std::endl;
result.shouldExit = true;
result.exitCode = 0;
return result;
}
}
std::vector<std::string_view> feedArgs;
for (int i = 1; i < argc; ++i)
{
const std::string_view arg(argv[i]);
if (arg == "--no-fps")
{
result.config.logFps = false;
continue;
}
if (arg == "--perf")
{
result.config.logPerf = true;
continue;
}
if (arg == "--verbose")
{
result.config.verbose = true;
continue;
}
if (arg == "--grid")
{
++i;
continue;
}
if (arg == "--fps-cap")
{
if (i + 1 >= argc)
{
std::cerr << "--fps-cap requires <fps>"
<< std::endl;
result.shouldExit = true;
result.exitCode = 1;
return result;
}
uint32_t fpsCap = 0;
if (!parseUint32(argv[i + 1], fpsCap) ||
fpsCap == 0 || fpsCap > 240)
{
std::cerr << "--fps-cap must be 1-240"
<< std::endl;
result.shouldExit = true;
result.exitCode = 1;
return result;
}
result.config.fpsCap = fpsCap;
++i;
continue;
}
if (arg == "--present")
{
if (i + 1 >= argc)
{
std::cerr << "--present requires <mode>"
<< std::endl;
result.shouldExit = true;
result.exitCode = 1;
return result;
}
const std::optional<PresentModeConfig> mode =
parsePresentMode(argv[i + 1]);
if (!mode.has_value())
{
std::cerr
<< "--present must be fifo, mailbox, or immediate"
<< std::endl;
result.shouldExit = true;
result.exitCode = 1;
return result;
}
result.config.presentMode = mode.value();
++i;
continue;
}
if (arg == "--feed")
{
if (i + 2 >= argc)
{
std::cerr << "--feed requires <index> <kind>"
<< std::endl;
result.shouldExit = true;
result.exitCode = 1;
return result;
}
uint32_t index = 0;
if (!parseFeedIndex(
argv[i + 1],
feedCount(result.config),
index))
{
std::cerr << "--feed index must be 0-"
<< (feedCount(result.config) - 1) << std::endl;
result.shouldExit = true;
result.exitCode = 1;
return result;
}
const std::optional<FeedKind> kind =
parseFeedKind(argv[i + 2]);
if (!kind.has_value())
{
std::cerr << "Unknown feed kind: "
<< argv[i + 2] << std::endl;
printUsage(argv[0]);
result.shouldExit = true;
result.exitCode = 1;
return result;
}
result.config.feeds[index].kind = kind.value();
i += 2;
continue;
}
if (arg == "--domain")
{
if (i + 2 >= argc)
{
std::cerr << "--domain requires <index> <path>"
<< std::endl;
result.shouldExit = true;
result.exitCode = 1;
return result;
}
uint32_t index = 0;
if (!parseFeedIndex(
argv[i + 1],
feedCount(result.config),
index))
{
std::cerr << "--domain index must be 0-"
<< (feedCount(result.config) - 1) << std::endl;
result.shouldExit = true;
result.exitCode = 1;
return result;
}
result.config.feeds[index].mxlDomain =
std::string(argv[i + 2]);
i += 2;
continue;
}
if (arg == "--flow")
{
if (i + 2 >= argc)
{
std::cerr << "--flow requires <index> <uuid>"
<< std::endl;
result.shouldExit = true;
result.exitCode = 1;
return result;
}
uint32_t index = 0;
if (!parseFeedIndex(
argv[i + 1],
feedCount(result.config),
index))
{
std::cerr << "--flow index must be 0-"
<< (feedCount(result.config) - 1) << std::endl;
result.shouldExit = true;
result.exitCode = 1;
return result;
}
result.config.feeds[index].mxlFlowId =
std::string(argv[i + 2]);
result.config.feeds[index].kind =
FeedKind::MxlSdk;
i += 2;
continue;
}
feedArgs.push_back(arg);
}
if (feedArgs.size() > result.config.feeds.size())
{
std::cerr << "Too many feed arguments" << std::endl;
printUsage(argv[0]);
result.shouldExit = true;
result.exitCode = 1;
return result;
}
size_t feedArgIndex = 0;
for (uint32_t slot = 0;
slot < result.config.feeds.size() && feedArgIndex < feedArgs.size();
++slot)
{
if (result.config.feeds[slot].kind == FeedKind::MxlSdk)
{
continue;
}
const std::optional<FeedKind> parsed =
parseFeedKind(feedArgs[feedArgIndex]);
if (!parsed.has_value())
{
std::cerr << "Unknown feed kind: "
<< feedArgs[feedArgIndex] << std::endl;
printUsage(argv[0]);
result.shouldExit = true;
result.exitCode = 1;
return result;
}
result.config.feeds[slot].kind = parsed.value();
++feedArgIndex;
}
if (feedArgIndex < feedArgs.size())
{
std::cerr << "Too many feed arguments for available slots"
<< std::endl;
printUsage(argv[0]);
result.shouldExit = true;
result.exitCode = 1;
return result;
}
return result;
}