blob: 22253b19a47dc046be93f6e6a9a70383a0ea7584 [file] [log] [blame] [edit]
/*
* 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;
const char* TestingWindow::BackendName(Backend backend)
{
switch (backend)
{
case TestingWindow::Backend::gl:
return "gl";
case TestingWindow::Backend::d3d:
return "d3d";
case TestingWindow::Backend::d3d12:
return "d3d12";
case TestingWindow::Backend::metal:
return "metal";
case TestingWindow::Backend::vk:
return "vk";
case TestingWindow::Backend::moltenvk:
return "moltenvk";
case TestingWindow::Backend::swiftshader:
return "swiftshader";
case TestingWindow::Backend::angle:
return "angle";
case TestingWindow::Backend::dawn:
return "dawn";
case TestingWindow::Backend::wgpu:
return "wgpu";
case Backend::rhi:
return "rhi";
case TestingWindow::Backend::coregraphics:
return "coregraphics";
case TestingWindow::Backend::skia:
return "skia";
case TestingWindow::Backend::null:
return "null";
}
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::ParseBackend(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 == "d3d12")
{
return Backend::d3d12;
}
if (nameStr == "d3d12atomic")
{
params->atomic = 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 == "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 == "wgpu")
{
return Backend::wgpu;
}
if (nameStr == "wgpucw")
{
params->clockwise = true;
return Backend::wgpu;
}
if (nameStr == "wgpumsaa")
{
params->msaa = true;
return Backend::wgpu;
}
if (nameStr == "rhi")
{
return Backend::rhi;
}
if (nameStr == "coregraphics")
{
return Backend::coregraphics;
}
if (nameStr == "skia")
{
return Backend::skia;
}
if (nameStr == "null")
{
return Backend::null;
}
fprintf(stderr, "'%s': invalid TestingWindow::Backend\n", name);
abort();
}
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)
{
assert((backend == Backend::rhi) == (s_TestingWindow != nullptr));
#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
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__)
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::rhi:
break;
case Backend::coregraphics:
s_TestingWindow = MakeCoreGraphics();
break;
case Backend::skia:
s_TestingWindow = MakeSkia();
break;
case Backend::null:
s_TestingWindow = MakeNULL();
break;
}
if (!s_TestingWindow)
{
fprintf(stderr,
"Failed to create testing window for Backend::%s\n",
BackendName(backend));
abort();
}
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;
}