From 165b66a7183ff2bcd766af5d104ea186ded287a4 Mon Sep 17 00:00:00 2001 From: Sascha Willems Date: Fri, 29 Jan 2021 15:40:52 +0100 Subject: [PATCH] Reworked command line argument handling (#804) * Reworked command line argument handling Added a new class to handle these --- base/vulkanexamplebase.cpp | 292 +++++++++++++++++++++---------------- base/vulkanexamplebase.h | 22 +++ 2 files changed, 192 insertions(+), 122 deletions(-) diff --git a/base/vulkanexamplebase.cpp b/base/vulkanexamplebase.cpp index 25d9ea5c..7951a657 100644 --- a/base/vulkanexamplebase.cpp +++ b/base/vulkanexamplebase.cpp @@ -701,84 +701,57 @@ VulkanExampleBase::VulkanExampleBase(bool enableValidation) #endif settings.validation = enableValidation; - - char* numConvPtr; - - // Parse command line arguments - for (size_t i = 0; i < args.size(); i++) - { - if (args[i] == std::string("-validation")) { - settings.validation = true; + + // Command line arguments + commandLineParser.parse(args); + if (commandLineParser.isSet("help")) { +#if defined(_WIN32) + setupConsole("Vulkan example"); +#endif + commandLineParser.printHelp(); + std::cin.get(); + exit(0); + } + if (commandLineParser.isSet("validation")) { + settings.validation = true; + } + if (commandLineParser.isSet("vsync")) { + settings.vsync = true; + } + if (commandLineParser.isSet("height")) { + height = commandLineParser.getValueAsInt("height", width); + } + if (commandLineParser.isSet("width")) { + width = commandLineParser.getValueAsInt("width", width); + } + if (commandLineParser.isSet("fullscreen")) { + settings.fullscreen = true; + } + if (commandLineParser.isSet("shaders")) { + std::string value = commandLineParser.getValueAsString("shaders", "glsl"); + if ((value != "glsl") && (value != "hlsl")) { + std::cerr << "Shader type must be one of 'glsl' or 'hlsl'\n"; } - if (args[i] == std::string("-vsync")) { - settings.vsync = true; - } - if ((args[i] == std::string("-f")) || (args[i] == std::string("--fullscreen"))) { - settings.fullscreen = true; - } - if ((args[i] == std::string("-w")) || (args[i] == std::string("--width"))) { - uint32_t w = strtol(args[i + 1], &numConvPtr, 10); - if (numConvPtr != args[i + 1]) { width = w; }; - } - if ((args[i] == std::string("-h")) || (args[i] == std::string("--height"))) { - uint32_t h = strtol(args[i + 1], &numConvPtr, 10); - if (numConvPtr != args[i + 1]) { height = h; }; - } - // Select between glsl and hlsl shaders - if ((args[i] == std::string("-s")) || (args[i] == std::string("--shaders"))) { - std::string type; - if (args.size() > i + 1) { - type = args[i + 1]; - } - if (type == "glsl" || type == "hlsl") { - shaderDir = type; - } else { - std::cerr << args[i] << " must be one of 'glsl' or 'hlsl'" << std::endl; - } - } - // Benchmark - if ((args[i] == std::string("-b")) || (args[i] == std::string("--benchmark"))) { - benchmark.active = true; - vks::tools::errorModeSilent = true; - } - // Warmup time (in seconds) - if ((args[i] == std::string("-bw")) || (args[i] == std::string("--benchwarmup"))) { - if (args.size() > i + 1) { - uint32_t num = strtol(args[i + 1], &numConvPtr, 10); - if (numConvPtr != args[i + 1]) { - benchmark.warmup = num; - } else { - std::cerr << "Warmup time for benchmark mode must be specified as a number!" << "\n"; - } - } - } - // Benchmark runtime (in seconds) - if ((args[i] == std::string("-br")) || (args[i] == std::string("--benchruntime"))) { - if (args.size() > i + 1) { - uint32_t num = strtol(args[i + 1], &numConvPtr, 10); - if (numConvPtr != args[i + 1]) { - benchmark.duration = num; - } - else { - std::cerr << "Benchmark run duration must be specified as a number!" << "\n"; - } - } - } - // Bench result save filename (overrides default) - if ((args[i] == std::string("-bf")) || (args[i] == std::string("--benchfilename"))) { - if (args.size() > i + 1) { - if (args[i + 1][0] == '-') { - std::cerr << "Filename for benchmark results must not start with a hyphen!" << "\n"; - } else { - benchmark.filename = args[i + 1]; - } - } - } - // Output frame times to benchmark result file - if ((args[i] == std::string("-bt")) || (args[i] == std::string("--benchframetimes"))) { - benchmark.outputFrameTimes = true; + else { + shaderDir = value; } } + if (commandLineParser.isSet("benchmark")) { + benchmark.active = true; + vks::tools::errorModeSilent = true; + } + if (commandLineParser.isSet("benchmarkwarmup")) { + benchmark.warmup = commandLineParser.getValueAsInt("benchmarkwarmup", benchmark.warmup); + } + if (commandLineParser.isSet("benchmarkruntime")) { + benchmark.duration = commandLineParser.getValueAsInt("benchmarkruntime", benchmark.duration); + } + if (commandLineParser.isSet("benchmarkresultfile")) { + benchmark.filename = commandLineParser.getValueAsString("benchmarkresultfile", benchmark.filename); + } + if (commandLineParser.isSet("benchmarkframetimes")) { + benchmark.outputFrameTimes = true; + } #if defined(VK_USE_PLATFORM_ANDROID_KHR) // Vulkan library is loaded dynamically on Android @@ -793,11 +766,10 @@ VulkanExampleBase::VulkanExampleBase(bool enableValidation) #endif #if defined(_WIN32) - // Enable console if validation is active - // Debug message callback will output to it + // Enable console if validation is active, debug message callback will output to it if (this->settings.validation) { - setupConsole("Vulkan validation output"); + setupConsole("Vulkan example"); } setupDPIAwareness(); #endif @@ -912,7 +884,10 @@ bool VulkanExampleBase::initVulkan() uint32_t gpuCount = 0; // Get number of available physical devices VK_CHECK_RESULT(vkEnumeratePhysicalDevices(instance, &gpuCount, nullptr)); - assert(gpuCount > 0); + if (gpuCount == 0) { + vks::tools::exitFatal("No device with Vulkan support found", -1); + return false; + } // Enumerate devices std::vector physicalDevices(gpuCount); err = vkEnumeratePhysicalDevices(instance, &gpuCount, physicalDevices.data()); @@ -929,50 +904,22 @@ bool VulkanExampleBase::initVulkan() #if !defined(VK_USE_PLATFORM_ANDROID_KHR) // GPU selection via command line argument - for (size_t i = 0; i < args.size(); i++) - { - // Select GPU - if ((args[i] == std::string("-g")) || (args[i] == std::string("-gpu"))) - { - char* endptr; - uint32_t index = strtol(args[i + 1], &endptr, 10); - if (endptr != args[i + 1]) - { - if (index > gpuCount - 1) - { - std::cerr << "Selected device index " << index << " is out of range, reverting to device 0 (use -listgpus to show available Vulkan devices)" << "\n"; - } - else - { - std::cout << "Selected Vulkan device " << index << "\n"; - selectedDevice = index; - } - }; - break; + if (commandLineParser.isSet("gpuselection")) { + uint32_t index = commandLineParser.getValueAsInt("gpuselection", 0); + if (index > gpuCount - 1) { + std::cerr << "Selected device index " << index << " is out of range, reverting to device 0 (use -listgpus to show available Vulkan devices)" << "\n"; + } else { + selectedDevice = index; } - // List available GPUs - if (args[i] == std::string("-listgpus")) - { - uint32_t gpuCount = 0; - VK_CHECK_RESULT(vkEnumeratePhysicalDevices(instance, &gpuCount, nullptr)); - if (gpuCount == 0) - { - std::cerr << "No Vulkan devices found!" << "\n"; - } - else - { - // Enumerate devices - std::cout << "Available Vulkan devices" << "\n"; - std::vector devices(gpuCount); - VK_CHECK_RESULT(vkEnumeratePhysicalDevices(instance, &gpuCount, devices.data())); - for (uint32_t j = 0; j < gpuCount; j++) { - VkPhysicalDeviceProperties deviceProperties; - vkGetPhysicalDeviceProperties(devices[j], &deviceProperties); - std::cout << "Device [" << j << "] : " << deviceProperties.deviceName << std::endl; - std::cout << " Type: " << vks::tools::physicalDeviceTypeString(deviceProperties.deviceType) << "\n"; - std::cout << " API: " << (deviceProperties.apiVersion >> 22) << "." << ((deviceProperties.apiVersion >> 12) & 0x3ff) << "." << (deviceProperties.apiVersion & 0xfff) << "\n"; - } - } + } + if (commandLineParser.isSet("gpulist")) { + std::cout << "Available Vulkan devices" << "\n"; + for (uint32_t i = 0; i < gpuCount; i++) { + VkPhysicalDeviceProperties deviceProperties; + vkGetPhysicalDeviceProperties(physicalDevices[i], &deviceProperties); + std::cout << "Device [" << i << "] : " << deviceProperties.deviceName << std::endl; + std::cout << " Type: " << vks::tools::physicalDeviceTypeString(deviceProperties.deviceType) << "\n"; + std::cout << " API: " << (deviceProperties.apiVersion >> 22) << "." << ((deviceProperties.apiVersion >> 12) & 0x3ff) << "." << (deviceProperties.apiVersion & 0xfff) << "\n"; } } #endif @@ -1036,6 +983,7 @@ void VulkanExampleBase::setupConsole(std::string title) AllocConsole(); AttachConsole(GetCurrentProcessId()); FILE *stream; + freopen_s(&stream, "CONIN$", "r", stdin); freopen_s(&stream, "CONOUT$", "w+", stdout); freopen_s(&stream, "CONOUT$", "w+", stderr); SetConsoleTitle(TEXT(title.c_str())); @@ -2759,3 +2707,103 @@ void VulkanExampleBase::setupSwapChain() } void VulkanExampleBase::OnUpdateUIOverlay(vks::UIOverlay *overlay) {} + +// Command line argument parser class + +CommandLineParser::CommandLineParser() +{ + add("help", { "--help" }, 0, "Show help"); + add("validation", {"-v", "--validation"}, 0, "Enable validation layers"); + add("vsync", {"-vs", "--vsync"}, 0, "Enable V-Sync"); + add("fullscreen", { "-f", "--fullscreen" }, 0, "Start in fullscreen mode"); + add("width", { "-w", "--width" }, 1, "Set window width"); + add("height", { "-h", "--height" }, 1, "Set window height"); + add("shaders", { "-s", "--shaders" }, 1, "Select shader type to use (glsl or hlsl)"); + add("gpuselection", { "-g", "--gpu" }, 1, "Select GPU to run on"); + add("gpulist", { "-gl", "--listgpus" }, 0, "Display a list of available Vulkan devices"); + add("benchmark", { "-b", "--benchmark" }, 0, "Run example in benchmark mode"); + add("benchmarkwarmup", { "-bw", "--benchwarmup" }, 1, "Set warmup time for benchmark mode in seconds"); + add("benchmarkruntime", { "-br", "--benchruntime" }, 1, "Set duration time for benchmark mode in seconds"); + add("benchmarkresultfile", { "-bf", "--benchfilename" }, 1, "Set file name for benchmark results"); + add("benchmarkresultframes", { "-bt", "--benchframetimes" }, 1, "Save frame times to benchmark results file"); +} + +void CommandLineParser::add(std::string name, std::vector commands, bool hasValue, std::string help) +{ + options[name].commands = commands; + options[name].help = help; + options[name].set = false; + options[name].hasValue = hasValue; + options[name].value = ""; +} + +void CommandLineParser::printHelp() +{ + std::cout << "Available command line options:\n"; + for (auto option : options) { + std::cout << " "; + for (size_t i = 0; i < option.second.commands.size(); i++) { + std::cout << option.second.commands[i]; + if (i < option.second.commands.size() - 1) { + std::cout << ", "; + } + } + std::cout << ": " << option.second.help << "\n"; + } + std::cout << "Press any key to close..."; +} + +void CommandLineParser::parse(std::vector arguments) +{ + bool printHelp = false; + // Known arguments + for (auto& option : options) { + for (auto& command : option.second.commands) { + for (size_t i = 0; i < arguments.size(); i++) { + if (strcmp(arguments[i], command.c_str()) == 0) { + option.second.set = true; + // Get value + if (option.second.hasValue) { + if (arguments.size() > i + 1) { + option.second.value = arguments[i + 1]; + } + if (option.second.value == "") { + printHelp = true; + break; + } + } + } + } + } + } + // Print help for unknown arguments or missing argument values + if (printHelp) { + options["help"].set = true; + } +} + +bool CommandLineParser::isSet(std::string name) +{ + return ((options.find(name) != options.end()) && options[name].set); +} + +std::string CommandLineParser::getValueAsString(std::string name, std::string defaultValue) +{ + assert(options.find(name) != options.end()); + std::string value = options[name].value; + return (value != "") ? value : defaultValue; +} + +int32_t CommandLineParser::getValueAsInt(std::string name, int32_t defaultValue) +{ + assert(options.find(name) != options.end()); + std::string value = options[name].value; + if (value != "") { + char* numConvPtr; + int32_t intVal = strtol(value.c_str(), &numConvPtr, 10); + return (intVal > 0) ? intVal : defaultValue; + } else { + return defaultValue; + } + return int32_t(); +} diff --git a/base/vulkanexamplebase.h b/base/vulkanexamplebase.h index 79acdf42..34bc1fd1 100644 --- a/base/vulkanexamplebase.h +++ b/base/vulkanexamplebase.h @@ -37,6 +37,7 @@ #include #include #include +#include #include #include #include @@ -71,6 +72,26 @@ #include "camera.hpp" #include "benchmark.hpp" +class CommandLineParser +{ +public: + struct CommandLineOption { + std::vector commands; + std::string value; + bool hasValue = false; + std::string help; + bool set = false; + }; + std::unordered_map options; + CommandLineParser(); + void add(std::string name, std::vector commands, bool hasValue, std::string help); + void printHelp(); + void parse(std::vector arguments); + bool isSet(std::string name); + std::string getValueAsString(std::string name, std::string defaultValue); + int32_t getValueAsInt(std::string name, int32_t defaultValue); +}; + class VulkanExampleBase { private: @@ -160,6 +181,7 @@ public: uint32_t height = 720; vks::UIOverlay UIOverlay; + CommandLineParser commandLineParser; /** @brief Last frame time measured using a high performance timer (if available) */ float frameTimer = 1.0f;