diff options
| author | Jay Kwak <82421531+jkwak-work@users.noreply.github.com> | 2025-07-21 19:15:07 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-07-21 19:15:07 -0700 |
| commit | 13dd01489efd89268d15751c5299e5783015bbcd (patch) | |
| tree | 13208541f8838a16e69d38645f7eebfc76326394 | |
| parent | a77d22bf3bfc3098b01e4886c3a63fb751edc704 (diff) | |
Add command-line arguments to examples (#7835)
* Add command-line arguments to triangle example
- Add -h/--help flag to show usage information
- Add -api flag to select rendering API (d3d11 < /dev/null | d3d12|vulkan|metal|cpu|cuda|webgpu)
- Platform-specific API availability (D3D on Windows, Metal on macOS)
- Output works for both console and WIN32 applications
| -rw-r--r-- | examples/example-base/example-base.cpp | 12 | ||||
| -rw-r--r-- | examples/example-base/example-base.h | 7 | ||||
| -rw-r--r-- | examples/example-base/test-base.cpp | 190 | ||||
| -rw-r--r-- | examples/example-base/test-base.h | 9 | ||||
| -rw-r--r-- | examples/model-viewer/main.cpp | 2 | ||||
| -rw-r--r-- | examples/platform-test/main.cpp | 2 | ||||
| -rw-r--r-- | examples/ray-tracing-pipeline/main.cpp | 2 | ||||
| -rw-r--r-- | examples/ray-tracing/main.cpp | 2 | ||||
| -rw-r--r-- | examples/shader-toy/main.cpp | 2 | ||||
| -rw-r--r-- | examples/triangle/main.cpp | 2 |
10 files changed, 220 insertions, 10 deletions
diff --git a/examples/example-base/example-base.cpp b/examples/example-base/example-base.cpp index 9759e9f42..95864b0af 100644 --- a/examples/example-base/example-base.cpp +++ b/examples/example-base/example-base.cpp @@ -1,5 +1,7 @@ #include "example-base.h" +#include "slang.h" + #include <chrono> #ifdef _WIN32 @@ -23,6 +25,16 @@ Slang::Result WindowedAppBase::initializeBase( #ifdef _DEBUG deviceDesc.enableValidation = true; #endif + + slang::CompilerOptionEntry slangOptions[] = { + {slang::CompilerOptionName::EmitSpirvDirectly, {slang::CompilerOptionValueKind::Int, 1}}, + {slang::CompilerOptionName::DebugInformation, + {slang::CompilerOptionValueKind::Int, SLANG_DEBUG_INFO_LEVEL_STANDARD}}}; + deviceDesc.slang.compilerOptionEntries = slangOptions; + // When in test mode, don't include debug information to avoid altering hash values during + // testing Otherwise, include debug information for better debugging experience + deviceDesc.slang.compilerOptionEntryCount = isTestMode() ? 1 : 2; + gDevice = getRHI()->createDevice(deviceDesc); if (!gDevice) { diff --git a/examples/example-base/example-base.h b/examples/example-base/example-base.h index 06970aaba..fbb54748a 100644 --- a/examples/example-base/example-base.h +++ b/examples/example-base/example-base.h @@ -136,6 +136,13 @@ int innerMain(int argc, char** argv) TApp app; app.parseOption(argc, argv); + + if (app.shouldShowHelp()) + { + app.printUsage(argc > 0 ? argv[0] : "example"); + return 0; + } + if (SLANG_FAILED(app.initialize())) { return -1; diff --git a/examples/example-base/test-base.cpp b/examples/example-base/test-base.cpp index bfbfd6b15..3ab6fe54b 100644 --- a/examples/example-base/test-base.cpp +++ b/examples/example-base/test-base.cpp @@ -5,12 +5,111 @@ // include ordering sensitive # include <windows.h> # include <shellapi.h> +# include <io.h> +# include <fcntl.h> // clang-format on +#include <iostream> +#include <string> +#endif + +static rhi::DeviceType parseApiString(const char* apiStr) +{ + static const struct + { + const char* name; + rhi::DeviceType type; + } apiTable[] = { +#ifdef _WIN32 + {"d3d11", rhi::DeviceType::D3D11}, + {"d3d12", rhi::DeviceType::D3D12}, +#endif + {"vulkan", rhi::DeviceType::Vulkan}, +#ifdef __APPLE__ + {"metal", rhi::DeviceType::Metal}, +#endif + {"cpu", rhi::DeviceType::CPU}, + {"cuda", rhi::DeviceType::CUDA}, + {"webgpu", rhi::DeviceType::WGPU}}; + + for (auto& api : apiTable) + { + if (strcmp(apiStr, api.name) == 0) + { + return api.type; + } + } + return rhi::DeviceType::Default; // Invalid/unknown +} + +#ifdef _WIN32 +static std::string wstringToString(const std::wstring& wstr) +{ + char buffer[256]; + wcstombs(buffer, wstr.c_str(), sizeof(buffer)); + return std::string(buffer); +} +#endif + +// Simple output function that works for both console and WIN32 apps +static void printOutput(const char* text) +{ + printf("%s", text); +#ifdef _WIN32 + OutputDebugStringA(text); +#endif +} + +#ifdef _WIN32 +// For WIN32 apps, allocate a console window to show help text +static bool ensureConsoleVisible() +{ + // Check if we already have a console (running from cmd.exe or already allocated) + if (GetConsoleWindow() != NULL) + { + // We already have a console, just use it + return false; // No new window created + } + + // Check if stdout is connected to a console or redirected + HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE); + if (hStdOut != INVALID_HANDLE_VALUE) + { + DWORD fileType = GetFileType(hStdOut); + switch (fileType) + { + case FILE_TYPE_CHAR: // Console + case FILE_TYPE_DISK: // File redirection + case FILE_TYPE_PIPE: // Pipe redirection + // Output goes to console or is redirected, don't create a console + return false; + } + // FILE_TYPE_UNKNOWN or other cases: proceed to create console + } + + // No console exists and output isn't redirected, so allocate a new one + if (AllocConsole()) + { + // Redirect stdout to the console + freopen_s((FILE**)stdout, "CONOUT$", "w", stdout); + freopen_s((FILE**)stderr, "CONOUT$", "w", stderr); + freopen_s((FILE**)stdin, "CONIN$", "r", stdin); + + // Make sure cout, wcout, cin, wcin, wcerr, cerr, wclog and clog + // point to console as well + std::ios::sync_with_stdio(true); + + // Set console title + SetConsoleTitleA("Triangle Example - Help"); + return true; // New window was created + } + + return false; // Failed to create console +} #endif int TestBase::parseOption(int argc, char** argv) { - // We only make the parse in a very loose way for only extracting the test option. + // Parse command line arguments for help, API selection, and test mode #ifdef _WIN32 wchar_t** szArglist; szArglist = CommandLineToArgvW(GetCommandLineW(), &argc); @@ -19,13 +118,44 @@ int TestBase::parseOption(int argc, char** argv) for (int i = 0; i < argc; i++) { #ifdef _WIN32 - if (wcscmp(szArglist[i], L"--test-mode") == 0) + std::string arg = wstringToString(szArglist[i]); #else - if (strcmp(argv[i], "--test-mode") == 0) + std::string arg = argv[i]; #endif + + if (arg == "--test-mode" || arg == "-test-mode") { m_isTestMode = true; } + else if (arg == "-h" || arg == "--help") + { + m_showHelp = true; + } + else if ((arg == "-api" || arg == "--api") && i + 1 < argc) + { + i++; // Move to the next argument which should be the API name +#ifdef _WIN32 + std::string apiStr = wstringToString(szArglist[i]); +#else + std::string apiStr = argv[i]; +#endif + + rhi::DeviceType newType = parseApiString(apiStr.c_str()); + if (newType != rhi::DeviceType::Default) + { + m_deviceType = newType; + } + else + { +#ifdef _WIN32 + // For WIN32 apps, ensure we have a visible console window for errors too + ensureConsoleVisible(); +#endif + std::string errorMsg = "Unknown API: " + apiStr + "\n"; + printOutput(errorMsg.c_str()); + m_showHelp = true; + } + } } #ifdef _WIN32 @@ -64,3 +194,57 @@ void TestBase::printEntrypointHashes( } } } + +void TestBase::printUsage(const char* programName) const +{ +#ifdef _WIN32 + // For WIN32 apps, ensure we have a visible console window + bool createdNewWindow = ensureConsoleVisible(); +#endif + + // Use printOutput to ensure output appears in both console and debug output (for WIN32 apps) + std::string usage = "Usage: " + std::string(programName) + " [options]\n\n"; + printOutput(usage.c_str()); + printOutput("Options:\n"); + printOutput(" -h, --help Show this help message\n"); + printOutput(" -api ("); +#ifdef _WIN32 + printOutput("d3d11|d3d12|"); +#endif + printOutput("vulkan"); +#ifdef __APPLE__ + printOutput("|metal"); +#endif + printOutput("|cpu|cuda|webgpu)\n"); +#ifdef _WIN32 + printOutput(" Use a given rendering API (Default: d3d12)\n"); +#elif defined(__APPLE__) + printOutput(" Use a given rendering API (Default: metal)\n"); +#else + printOutput(" Use a given rendering API (Default: vulkan)\n"); +#endif + printOutput(" -test-mode Print hash values of compiled shader entry points " + "and skip rendering\n"); + printOutput("\n"); + printOutput("Supported APIs:\n"); +#ifdef _WIN32 + printOutput(" d3d11 - Direct3D 11\n"); + printOutput(" d3d12 - Direct3D 12\n"); +#endif + printOutput(" vulkan - Vulkan\n"); +#ifdef __APPLE__ + printOutput(" metal - Metal (macOS/iOS)\n"); +#endif + printOutput(" cpu - CPU execution\n"); + printOutput(" cuda - CUDA\n"); + printOutput(" webgpu - WebGPU\n"); + +#ifdef _WIN32 + // For WIN32 apps, only prompt if we created a new console window + if (createdNewWindow) + { + printOutput("\nPress Enter to continue or close this window...\n"); + getchar(); // Wait for user input + } +#endif +} diff --git a/examples/example-base/test-base.h b/examples/example-base/test-base.h index 293c9f0d8..2705baa26 100644 --- a/examples/example-base/test-base.h +++ b/examples/example-base/test-base.h @@ -2,6 +2,7 @@ #include "core/slang-string-util.h" #include "slang-com-ptr.h" +#include "slang-rhi.h" #include "slang.h" using Slang::ComPtr; @@ -10,7 +11,7 @@ class TestBase { public: - // Parses command line options. This example only has one option for testing purpose. + // Parses command line options including help, API selection, and test mode. int parseOption(int argc, char** argv); void printEntrypointHashes( @@ -19,8 +20,14 @@ public: ComPtr<slang::IComponentType>& composedProgram); bool isTestMode() const { return m_isTestMode; } + bool shouldShowHelp() const { return m_showHelp; } + rhi::DeviceType getDeviceType() const { return m_deviceType; } + + void printUsage(const char* programName) const; private: bool m_isTestMode = false; + bool m_showHelp = false; + rhi::DeviceType m_deviceType = rhi::DeviceType::Default; uint64_t m_globalCounter = 0; }; diff --git a/examples/model-viewer/main.cpp b/examples/model-viewer/main.cpp index 9c1c763f2..a70d52320 100644 --- a/examples/model-viewer/main.cpp +++ b/examples/model-viewer/main.cpp @@ -748,7 +748,7 @@ struct ModelViewer : WindowedAppBase // Result initialize() { - SLANG_RETURN_ON_FAIL(initializeBase("Model Viewer", 1024, 768)); + SLANG_RETURN_ON_FAIL(initializeBase("Model Viewer", 1024, 768, getDeviceType())); if (!isTestMode()) { gWindow->events.mouseMove = [this](const platform::MouseEventArgs& e) diff --git a/examples/platform-test/main.cpp b/examples/platform-test/main.cpp index 7949d90c1..a2996d2ab 100644 --- a/examples/platform-test/main.cpp +++ b/examples/platform-test/main.cpp @@ -101,7 +101,7 @@ struct PlatformTest : public WindowedAppBase Slang::Result initialize() { - SLANG_RETURN_ON_FAIL(initializeBase("platform-test", 1024, 768)); + SLANG_RETURN_ON_FAIL(initializeBase("platform-test", 1024, 768, getDeviceType())); // We may not have a window if we're running in test mode SLANG_ASSERT(isTestMode() || gWindow); diff --git a/examples/ray-tracing-pipeline/main.cpp b/examples/ray-tracing-pipeline/main.cpp index 19b74011a..a51a6fd93 100644 --- a/examples/ray-tracing-pipeline/main.cpp +++ b/examples/ray-tracing-pipeline/main.cpp @@ -295,7 +295,7 @@ struct RayTracing : public WindowedAppBase Slang::Result initialize() { - SLANG_RETURN_ON_FAIL(initializeBase("Ray Tracing Pipeline", 1024, 768)); + SLANG_RETURN_ON_FAIL(initializeBase("Ray Tracing Pipeline", 1024, 768, getDeviceType())); if (!isTestMode()) { gWindow->events.mouseMove = [this](const platform::MouseEventArgs& e) diff --git a/examples/ray-tracing/main.cpp b/examples/ray-tracing/main.cpp index 0b14aef7e..c3d7a4183 100644 --- a/examples/ray-tracing/main.cpp +++ b/examples/ray-tracing/main.cpp @@ -283,7 +283,7 @@ struct RayTracing : public WindowedAppBase Slang::Result initialize() { - SLANG_RETURN_ON_FAIL(initializeBase("Ray Tracing", 1024, 768)); + SLANG_RETURN_ON_FAIL(initializeBase("Ray Tracing", 1024, 768, getDeviceType())); if (!isTestMode()) { diff --git a/examples/shader-toy/main.cpp b/examples/shader-toy/main.cpp index 549dbef73..350853b6f 100644 --- a/examples/shader-toy/main.cpp +++ b/examples/shader-toy/main.cpp @@ -283,7 +283,7 @@ struct ShaderToyApp : public WindowedAppBase Result initialize() { - SLANG_RETURN_ON_FAIL(initializeBase("Shader Toy", 1024, 768)); + SLANG_RETURN_ON_FAIL(initializeBase("Shader Toy", 1024, 768, getDeviceType())); // We may not have a window if we're running in test mode SLANG_ASSERT(isTestMode() || gWindow); diff --git a/examples/triangle/main.cpp b/examples/triangle/main.cpp index 6f61a6c76..2fd51da46 100644 --- a/examples/triangle/main.cpp +++ b/examples/triangle/main.cpp @@ -225,7 +225,7 @@ struct HelloWorld : public WindowedAppBase { // Create a window for our application to render into. // - SLANG_RETURN_ON_FAIL(initializeBase("hello-world", 1024, 768)); + SLANG_RETURN_ON_FAIL(initializeBase("triangle", 1024, 768, getDeviceType())); // We will create objects needed to configure the "input assembler" // (IA) stage of the D3D pipeline. |
