| /* | |
| * Copyright 2022 Rive | |
| */ | |
| #include "testing_window.hpp" | |
| #include "rive/rive_types.hpp" | |
| #include <string> | |
| #include <sstream> | |
| #ifdef _WIN32 | |
| #include <windows.h> | |
| #endif | |
| #if defined(_WIN32) && !defined(RIVE_UNREAL) | |
| extern "C" | |
| { | |
| // https://stackoverflow.com/questions/68469954/how-to-choose-specific-gpu-when-create-opengl-context: | |
| // | |
| // OpenGL, or rather the Win32 GDI integration of it, doesn't offer means | |
| // to explicitly select the desired device. However the drivers of Nvidia | |
| // and AMD offer a workaround to have programs select, that they prefer to | |
| // execute on the discrete GPU rather than the CPU integrated one. | |
| // | |
| // These also appear to select the discrete "Arc" GPU on an Intel system, | |
| // and to influence the GPU selection on D3D11. | |
| __declspec(dllexport) uint32_t NvOptimusEnablement = 1; | |
| __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; | |
| } | |
| #endif | |
| // Call TestingWindow::Destroy if you want to delete the window singleton | |
| TestingWindow* s_TestingWindow = nullptr; | |
| TestingWindow::Backend TestingWindow::s_Backend = TestingWindow::Backend::null; | |
| TestingWindow::Target TestingWindow::s_Target = | |
| #if defined(__EMSCRIPTEN__) | |
| TestingWindow::Target::webbrowser; | |
| #elif defined(RIVE_ANDROID) | |
| TestingWindow::Target::android; | |
| #elif defined(RIVE_IOS_SIMULATOR) | |
| TestingWindow::Target::iossim; | |
| #elif defined(RIVE_IOS) | |
| TestingWindow::Target::ios; | |
| #else | |
| TestingWindow::Target::host; | |
| #endif | |
| const char* TestingWindow::BackendName(Backend backend) | |
| { | |
| switch (backend) | |
| { | |
| case Backend::gl: | |
| return "gl"; | |
| case Backend::d3d: | |
| return "d3d"; | |
| case Backend::d3d12: | |
| return "d3d12"; | |
| case Backend::metal: | |
| return "metal"; | |
| case Backend::vk: | |
| return "vk"; | |
| case Backend::moltenvk: | |
| return "moltenvk"; | |
| case Backend::swiftshader: | |
| return "swiftshader"; | |
| case Backend::angle: | |
| return "angle"; | |
| case Backend::dawn: | |
| return "dawn"; | |
| case Backend::wgpu: | |
| return "wgpu"; | |
| case Backend::external: | |
| return "external"; | |
| case Backend::coregraphics: | |
| return "coregraphics"; | |
| case Backend::skia: | |
| return "skia"; | |
| case Backend::canvas2d: | |
| return "canvas2d"; | |
| case Backend::svg: | |
| return "svg"; | |
| case Backend::null: | |
| return "null"; | |
| case Backend::invalid: | |
| return "invalid"; | |
| } | |
| RIVE_UNREACHABLE(); | |
| } | |
| static std::vector<std::string> split(const char* str, char delimiter) | |
| { | |
| std::stringstream ss(str); | |
| std::string token; | |
| std::vector<std::string> tokens; | |
| while (std::getline(ss, token, delimiter)) | |
| { | |
| tokens.push_back(token); | |
| } | |
| return tokens; | |
| } | |
| TestingWindow::Backend TestingWindow::TryParseBackend(const char* name, | |
| BackendParams* params) | |
| { | |
| *params = {}; | |
| // Backends can come in the form <backendName>, or | |
| // <gpuNameFilter>/<backendName>. | |
| std::vector<std::string> tokens = split(name, '/'); | |
| assert(!tokens.empty()); | |
| params->gpuNameFilter = | |
| tokens.size() > 1 ? tokens[tokens.size() - 2].c_str() : ""; | |
| const std::string nameStr = tokens.back(); | |
| const auto nameStartsWith = [&](std::string str) { | |
| return nameStr.rfind(str, 0) == 0; | |
| }; | |
| const auto nameEndsWith = [&](std::string str) { | |
| auto result = nameStr.rfind(str); | |
| return result != std::string::npos && | |
| result == nameStr.size() - str.size(); | |
| }; | |
| if (nameStartsWith("angle")) | |
| { | |
| if (nameStartsWith("anglemsaa")) | |
| params->msaa = true; | |
| if (nameEndsWith("_mtl") || nameEndsWith("_metal")) | |
| params->angleRenderer = ANGLERenderer::metal; | |
| else if (nameEndsWith("_d3d") || nameEndsWith("_d3d11")) | |
| params->angleRenderer = ANGLERenderer::d3d11; | |
| else if (nameEndsWith("_vk") || nameEndsWith("_vulkan")) | |
| params->angleRenderer = ANGLERenderer::vk; | |
| else if (nameEndsWith("_gles")) | |
| params->angleRenderer = ANGLERenderer::gles; | |
| else if (nameEndsWith("_gl")) | |
| params->angleRenderer = ANGLERenderer::gl; | |
| return Backend::angle; | |
| } | |
| if (nameStr == "gl") | |
| { | |
| return Backend::gl; | |
| } | |
| if (nameStr == "glatomic") | |
| { | |
| params->atomic = true; | |
| return Backend::gl; | |
| } | |
| if (nameStr == "glcw") | |
| { | |
| params->clockwise = true; | |
| return Backend::gl; | |
| } | |
| if (nameStr == "glmsaa") | |
| { | |
| params->msaa = true; | |
| return Backend::gl; | |
| } | |
| if (nameStr == "d3d") | |
| { | |
| return Backend::d3d; | |
| } | |
| if (nameStr == "d3datomic") | |
| { | |
| params->atomic = true; | |
| return Backend::d3d; | |
| } | |
| if (nameStr == "d3dmsaa") | |
| { | |
| params->msaa = true; | |
| return Backend::d3d; | |
| } | |
| if (nameStr == "d3d12") | |
| { | |
| return Backend::d3d12; | |
| } | |
| if (nameStr == "d3d12atomic") | |
| { | |
| params->atomic = true; | |
| return Backend::d3d12; | |
| } | |
| if (nameStr == "d3d12msaa") | |
| { | |
| params->msaa = true; | |
| return Backend::d3d12; | |
| } | |
| if (nameStr == "metal") | |
| { | |
| return Backend::metal; | |
| } | |
| if (nameStr == "metalcw") | |
| { | |
| params->clockwise = true; | |
| return Backend::metal; | |
| } | |
| if (nameStr == "metalatomic") | |
| { | |
| params->atomic = true; | |
| return Backend::metal; | |
| } | |
| if (nameStr == "metalmsaa") | |
| { | |
| params->msaa = true; | |
| return Backend::metal; | |
| } | |
| if (nameStr == "vulkan" || nameStr == "vk") | |
| { | |
| return Backend::vk; | |
| } | |
| if (nameStr == "vulkanatomic" || nameStr == "vkatomic") | |
| { | |
| params->atomic = true; | |
| return Backend::vk; | |
| } | |
| if (nameStr == "vulkanmsaa" || nameStr == "vkmsaa") | |
| { | |
| params->msaa = true; | |
| return Backend::vk; | |
| } | |
| if (nameStr == "vulkancore" || nameStr == "vkcore") | |
| { | |
| params->core = true; | |
| return Backend::vk; | |
| } | |
| if (nameStr == "vulkanmsaacore" || nameStr == "vkmsaacore") | |
| { | |
| params->core = true; | |
| params->msaa = true; | |
| return Backend::vk; | |
| } | |
| if (nameStr == "vulkansrgb" || nameStr == "vksrgb") | |
| { | |
| params->srgb = true; | |
| return Backend::vk; | |
| } | |
| if (nameStr == "vkcw") | |
| { | |
| params->clockwise = true; | |
| return Backend::vk; | |
| } | |
| if (nameStr == "vkcwatomic" || nameStr == "vkcwa") | |
| { | |
| params->clockwise = true; | |
| params->atomic = true; | |
| return Backend::vk; | |
| } | |
| if (nameStr == "moltenvk" || nameStr == "mvk") | |
| { | |
| return Backend::moltenvk; | |
| } | |
| if (nameStr == "moltenvkcore" || nameStr == "mvkcore") | |
| { | |
| params->core = true; | |
| return Backend::moltenvk; | |
| } | |
| if (nameStr == "swiftshader" || nameStr == "sw") | |
| { | |
| return Backend::swiftshader; | |
| } | |
| if (nameStr == "swiftshadercore" || nameStr == "swcore") | |
| { | |
| params->core = true; | |
| return Backend::swiftshader; | |
| } | |
| if (nameStr == "dawn") | |
| { | |
| return Backend::dawn; | |
| } | |
| if (nameStr == "dawnmsaa") | |
| { | |
| params->msaa = true; | |
| return Backend::dawn; | |
| } | |
| if (nameStr == "wgpu") | |
| { | |
| return Backend::wgpu; | |
| } | |
| if (nameStr == "wgpucw") | |
| { | |
| params->clockwise = true; | |
| return Backend::wgpu; | |
| } | |
| if (nameStr == "wgpumsaa") | |
| { | |
| params->msaa = true; | |
| return Backend::wgpu; | |
| } | |
| if (nameStr == "coregraphics") | |
| { | |
| return Backend::coregraphics; | |
| } | |
| if (nameStr == "skia") | |
| { | |
| return Backend::skia; | |
| } | |
| if (nameStr == "external") | |
| { | |
| return Backend::external; | |
| } | |
| if (nameStr == "canvas2d" || nameStr == "c2d") | |
| { | |
| return Backend::canvas2d; | |
| } | |
| if (nameStr == "svg") | |
| { | |
| return Backend::svg; | |
| } | |
| if (nameStr == "null") | |
| { | |
| return Backend::null; | |
| } | |
| return Backend::invalid; | |
| } | |
| TestingWindow::Backend TestingWindow::ParseBackend(const char* name, | |
| BackendParams* params) | |
| { | |
| Backend backend = TryParseBackend(name, params); | |
| if (backend == Backend::invalid) | |
| { | |
| fprintf(stderr, "'%s': invalid TestingWindow::Backend\n", name); | |
| abort(); | |
| } | |
| return backend; | |
| } | |
| static void set_environment_variable(const char* name, const char* value) | |
| { | |
| #ifndef PLATFORM_NO_ENV_API | |
| if (const char* existingValue = getenv(name)) | |
| { | |
| printf("warning: %s=%s already set. Overriding with %s=%s\n", | |
| name, | |
| existingValue, | |
| name, | |
| value); | |
| } | |
| #ifdef _WIN32 | |
| SetEnvironmentVariableA(name, value); | |
| #else | |
| setenv(name, value, /*overwrite=*/true); | |
| #endif | |
| #endif | |
| } | |
| TestingWindow* TestingWindow::Init(Backend backend, | |
| const BackendParams& backendParams, | |
| Visibility visibility, | |
| void* platformWindow) | |
| { | |
| if (s_TestingWindow != nullptr) | |
| { | |
| s_Backend = backend; | |
| return s_TestingWindow; | |
| } | |
| #if defined(_WIN32) && !defined(RIVE_UNREAL) | |
| // Set our backdoor GPU selection variables in case the API doesn't | |
| // allow us to select explicitly. | |
| const char* nameFilter = backendParams.gpuNameFilter.c_str(); | |
| if (const char* gpuFromEnv = getenv("RIVE_GPU"); gpuFromEnv != nullptr) | |
| { | |
| // Override the program's GPU filter with one from the environment if | |
| // it's set. | |
| nameFilter = gpuFromEnv; | |
| } | |
| // "i" and "integrated" are special-case gpuNameFilters that mean "use | |
| // the integrated GPU". | |
| bool wantIntegratedGPU = static_cast<uint32_t>( | |
| strcmp(nameFilter, "integrated") == 0 || strcmp(nameFilter, "i") == 0); | |
| NvOptimusEnablement = !wantIntegratedGPU; | |
| AmdPowerXpressRequestHighPerformance = !wantIntegratedGPU; | |
| #endif | |
| switch (backend) | |
| { | |
| case Backend::gl: | |
| case Backend::angle: | |
| #ifndef RIVE_TOOLS_NO_GLFW | |
| if (backend != Backend::angle || visibility != Visibility::headless) | |
| { | |
| s_TestingWindow = | |
| TestingWindow::MakeFiddleContext(backend, | |
| backendParams, | |
| visibility, | |
| platformWindow); | |
| } | |
| else | |
| #endif | |
| { | |
| s_TestingWindow = | |
| MakeEGL(backend, backendParams, platformWindow); | |
| } | |
| break; | |
| case Backend::vk: | |
| case Backend::moltenvk: | |
| case Backend::swiftshader: | |
| if (backend == Backend::moltenvk) | |
| { | |
| // Use the MoltenVK built by | |
| // packages/runtime/renderer/make_moltenvk.sh | |
| constexpr static char kMoltenVKICD[] = | |
| "../renderer/dependencies/MoltenVK/Package/Release/" | |
| "MoltenVK/dynamic/dylib/macOS/MoltenVK_icd.json"; | |
| set_environment_variable("VK_ICD_FILENAMES", kMoltenVKICD); | |
| } | |
| else if (backend == Backend::swiftshader) | |
| { | |
| // Use the swiftshader built by | |
| // packages/runtime/renderer/make_swiftshader.sh | |
| constexpr static char kSwiftShaderICD[] = | |
| #ifdef __APPLE__ | |
| "../renderer/dependencies/SwiftShader/build/Darwin/" | |
| "vk_swiftshader_icd.json"; | |
| #elif defined(_WIN32) | |
| "../renderer/dependencies/SwiftShader/build/Windows/" | |
| "vk_swiftshader_icd.json"; | |
| #else | |
| "../renderer/dependencies/SwiftShader/build/Linux/" | |
| "vk_swiftshader_icd.json"; | |
| #endif | |
| set_environment_variable("VK_ICD_FILENAMES", kSwiftShaderICD); | |
| } | |
| #ifdef RIVE_ANDROID | |
| if (platformWindow != nullptr) | |
| { | |
| s_TestingWindow = | |
| TestingWindow::MakeAndroidVulkan(backendParams, | |
| platformWindow); | |
| break; | |
| } | |
| #endif | |
| // Hook for platforms without rive_vk_bootstrap. | |
| if (auto* platformVulkanWindow = | |
| TestingWindow::MakePlatformVulkan(backendParams)) | |
| { | |
| s_TestingWindow = platformVulkanWindow; | |
| break; | |
| } | |
| if (visibility == Visibility::headless) | |
| { | |
| s_TestingWindow = | |
| TestingWindow::MakeVulkanTexture(backendParams); | |
| } | |
| else | |
| { | |
| s_TestingWindow = | |
| TestingWindow::MakeFiddleContext(backend, | |
| backendParams, | |
| visibility, | |
| platformWindow); | |
| } | |
| break; | |
| case Backend::metal: | |
| #if defined(__APPLE__) && !defined(RIVE_UNREAL) | |
| if (visibility == Visibility::headless) | |
| { | |
| s_TestingWindow = | |
| TestingWindow::MakeMetalTexture(backendParams); | |
| break; | |
| } | |
| #endif | |
| s_TestingWindow = TestingWindow::MakeFiddleContext(backend, | |
| backendParams, | |
| visibility, | |
| platformWindow); | |
| break; | |
| case Backend::d3d: | |
| case Backend::d3d12: | |
| case Backend::dawn: | |
| s_TestingWindow = TestingWindow::MakeFiddleContext(backend, | |
| backendParams, | |
| visibility, | |
| platformWindow); | |
| break; | |
| case Backend::wgpu: | |
| s_TestingWindow = TestingWindow::MakeWGPU(backendParams); | |
| break; | |
| case Backend::external: | |
| break; | |
| case Backend::coregraphics: | |
| s_TestingWindow = MakeCoreGraphics(); | |
| break; | |
| case Backend::skia: | |
| s_TestingWindow = MakeSkia(); | |
| break; | |
| case Backend::canvas2d: | |
| s_TestingWindow = MakeCanvas2D(); | |
| break; | |
| case Backend::svg: | |
| s_TestingWindow = MakeSVG(); | |
| break; | |
| case Backend::null: | |
| s_TestingWindow = MakeNULL(); | |
| break; | |
| case Backend::invalid: | |
| break; | |
| } | |
| if (!s_TestingWindow) | |
| { | |
| fprintf(stderr, | |
| "Failed to create testing window for Backend::%s\n", | |
| BackendName(backend)); | |
| abort(); | |
| } | |
| s_Backend = backend; | |
| return s_TestingWindow; | |
| } | |
| TestingWindow* TestingWindow::Get() | |
| { | |
| assert(s_TestingWindow); // Call Init() first! | |
| return s_TestingWindow; | |
| } | |
| void TestingWindow::Set(TestingWindow* inWindow) | |
| { | |
| assert(inWindow); | |
| s_TestingWindow = inWindow; | |
| } | |
| void TestingWindow::Destroy() | |
| { | |
| assert(s_TestingWindow); | |
| delete s_TestingWindow; | |
| s_TestingWindow = nullptr; | |
| } | |
| // Default no-op; platforms with their own impl define | |
| // RIVE_PLATFORM_TESTING_WINDOW. | |
| #ifndef RIVE_PLATFORM_TESTING_WINDOW | |
| TestingWindow* TestingWindow::MakePlatformVulkan(const BackendParams&) | |
| { | |
| return nullptr; | |
| } | |
| #endif |