blob: ffae506e143581485d79055fa97d3d47ff978b09 [file] [log] [blame]
/*
* Copyright 2022 Rive
*/
#include "testing_window.hpp"
#if defined(RIVE_TOOLS_NO_GLFW)
TestingWindow* TestingWindow::MakeFiddleContext(Backend,
const BackendParams&,
Visibility,
void* platformWindow)
{
return nullptr;
}
#else
#include "fiddle_context.hpp"
#include "common/offscreen_render_target.hpp"
#include "rive/renderer/vulkan/render_context_vulkan_impl.hpp"
#include <queue>
#define GLFW_INCLUDE_NONE
#include <GLFW/glfw3.h>
using namespace rive;
using namespace rive::gpu;
static std::queue<TestingWindow::InputEventData> inputEvents;
static float dpi = 1.0f;
static void mouse_position_callback(GLFWwindow* window,
double xPos,
double yPos)
{
inputEvents.push({TestingWindow::InputEvent::MouseMove,
static_cast<float>(xPos) * dpi,
static_cast<float>(yPos) * dpi});
}
void mouse_button_callback(GLFWwindow* window, int button, int action, int mods)
{
assert(0 <= button);
assert(button <= GLFW_MOUSE_BUTTON_LAST);
double xPos, yPos;
glfwGetCursorPos(window, &xPos, &yPos);
switch (action)
{
case GLFW_PRESS:
inputEvents.push({TestingWindow::InputEvent::MouseDown,
static_cast<float>(xPos) * dpi,
static_cast<float>(yPos) * dpi});
break;
case GLFW_RELEASE:
inputEvents.push({TestingWindow::InputEvent::MouseUp,
static_cast<float>(xPos) * dpi,
static_cast<float>(yPos) * dpi});
break;
default:
printf("Unsupported GLFW mouse button event received!\n");
break;
}
}
static void key_callback(GLFWwindow* window,
int key,
int scancode,
int action,
int mods)
{
if (action == GLFW_PRESS)
{
if (key >= GLFW_KEY_A && key <= GLFW_KEY_Z)
{
if (!(mods & GLFW_MOD_SHIFT))
{
key += 'a' - 'A';
}
}
else if (key >= GLFW_KEY_0 && key <= GLFW_KEY_9)
{
if (mods & GLFW_MOD_SHIFT)
{
switch (key)
{
// clang-format off
case GLFW_KEY_1: key = '!'; break;
case GLFW_KEY_2: key = '@'; break;
case GLFW_KEY_3: key = '#'; break;
case GLFW_KEY_4: key = '$'; break;
case GLFW_KEY_5: key = '%'; break;
case GLFW_KEY_6: key = '^'; break;
case GLFW_KEY_7: key = '&'; break;
case GLFW_KEY_8: key = '*'; break;
case GLFW_KEY_9: key = '('; break;
case GLFW_KEY_0: key = ')'; break;
// clang-format on
}
}
}
if (key < 128)
{
inputEvents.push(
{TestingWindow::InputEvent::KeyPress, static_cast<char>(key)});
return;
}
switch (key)
{
case GLFW_KEY_ESCAPE:
glfwSetWindowShouldClose(window, GLFW_TRUE);
break;
case GLFW_KEY_TAB:
inputEvents.push({TestingWindow::InputEvent::KeyPress, '\t'});
break;
case GLFW_KEY_BACKSPACE:
inputEvents.push({TestingWindow::InputEvent::KeyPress, '\b'});
break;
}
}
}
class TestingWindowFiddleContext : public TestingWindow
{
public:
TestingWindowFiddleContext(Backend backend,
const BackendParams& backendParams,
Visibility visibility,
void* platformWindow) :
m_backendParams(backendParams)
{
// Vulkan headless rendering doesn't need an OS window.
// It's convenient to run Swiftshader on CI without GLFW.
if (backend == Backend::angle)
{
#ifdef __APPLE__
glfwInitHint(GLFW_ANGLE_PLATFORM_TYPE,
GLFW_ANGLE_PLATFORM_TYPE_METAL);
#elif defined(_WIN32)
glfwInitHint(GLFW_ANGLE_PLATFORM_TYPE,
GLFW_ANGLE_PLATFORM_TYPE_D3D11);
#elif !defined(__EMSCRIPTEN__)
glfwInitHint(GLFW_ANGLE_PLATFORM_TYPE,
GLFW_ANGLE_PLATFORM_TYPE_VULKAN);
#endif
}
if (!glfwInit())
{
fprintf(stderr, "Failed to initialize GLFW.\n");
abort();
}
if (backend != Backend::gl && backend != Backend::angle)
{
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
glfwWindowHint(GLFW_SAMPLES, 0);
glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE);
}
else if (backend == Backend::angle)
{
glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_EGL_CONTEXT_API);
glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
}
else
{
glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
}
if (backendParams.msaa)
{
m_msaaSampleCount = 4;
glfwWindowHint(GLFW_SAMPLES, m_msaaSampleCount);
glfwWindowHint(GLFW_STENCIL_BITS, 8);
glfwWindowHint(GLFW_DEPTH_BITS, 24);
}
glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE);
if (visibility == Visibility::headless)
{
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
}
GLFWmonitor* fullscreenMonitor = nullptr;
if (visibility == Visibility::fullscreen)
{
glfwWindowHint(GLFW_CENTER_CURSOR, GLFW_FALSE);
glfwWindowHint(GLFW_AUTO_ICONIFY, GLFW_FALSE);
// For GLFW, the platformWindow is a fullscreen monitor index.
intptr_t fullscreenMonitorIdx =
reinterpret_cast<intptr_t>(platformWindow);
int monitorCount;
GLFWmonitor** monitors = glfwGetMonitors(&monitorCount);
if (fullscreenMonitorIdx < 0 ||
fullscreenMonitorIdx >= monitorCount)
{
fprintf(stderr,
"Monitor index out of range. Requested %zi but %i are "
"available.",
fullscreenMonitorIdx,
monitorCount);
abort();
}
fullscreenMonitor = monitors[fullscreenMonitorIdx];
}
// GLFW doesn't appear to have a way to say "make the window as big as
// the screen", but if we don't make it large enough here, the
// fullscreen window will be quarter resultion instead of full. NOTE:
// glfwGetMonitorPhysicalSize() returns bogus values.
m_glfwWindow = glfwCreateWindow(3840,
2160,
"Rive Renderer",
fullscreenMonitor,
nullptr);
if (!m_glfwWindow)
{
#ifndef __EMSCRIPTEN__
const char* errorDescription;
int errorCode = glfwGetError(&errorDescription);
fprintf(stderr,
"Failed to create GLFW window: %s\n",
errorDescription);
if (errorCode == GLFW_API_UNAVAILABLE)
{
// This means that the driver does not support the given API and
// we cannot create a window. MakeFiddleContext will detect this
// object as non-valid and clean it up and return nullptr.
return;
}
#else
// Emscripten doesn't support glfwGetError so print a generic
// message.
fprintf(stderr, "Failed to create GLFW window.\n");
#endif
glfwTerminate();
abort();
}
glfwMakeContextCurrent(m_glfwWindow);
#ifndef __EMSCRIPTEN__
glfwSwapInterval(0);
#endif
glfwSetKeyCallback(m_glfwWindow, key_callback);
glfwSetCursorPosCallback(m_glfwWindow, mouse_position_callback);
glfwSetMouseButtonCallback(m_glfwWindow, mouse_button_callback);
FiddleContextOptions fiddleOptions = {
.retinaDisplay = visibility == Visibility::fullscreen,
.shaderCompilationMode = ShaderCompilationMode::alwaysSynchronous,
.enableReadPixels = true,
.disableRasterOrdering = backendParams.atomic,
.coreFeaturesOnly = backendParams.core,
.srgb = m_backendParams.srgb,
.allowHeadlessRendering = visibility == Visibility::headless,
.enableVulkanValidationLayers =
!backendParams.disableValidationLayers,
.disableDebugCallbacks = backendParams.disableDebugCallbacks,
.gpuNameFilter = backendParams.gpuNameFilter.c_str(),
};
switch (backend)
{
case Backend::wgpu:
case Backend::rhi:
case Backend::coregraphics:
case Backend::skia:
case Backend::null:
break;
case Backend::gl:
case Backend::angle:
m_fiddleContext = FiddleContext::MakeGLPLS(fiddleOptions);
break;
case Backend::d3d:
m_fiddleContext = FiddleContext::MakeD3DPLS(fiddleOptions);
break;
case Backend::d3d12:
m_fiddleContext = FiddleContext::MakeD3D12PLS(fiddleOptions);
break;
case Backend::metal:
m_fiddleContext = FiddleContext::MakeMetalPLS(fiddleOptions);
break;
case Backend::vk:
case Backend::moltenvk:
case Backend::swiftshader:
m_fiddleContext = FiddleContext::MakeVulkanPLS(fiddleOptions);
break;
case Backend::dawn:
m_fiddleContext = FiddleContext::MakeDawnPLS(fiddleOptions);
break;
}
if (m_fiddleContext == nullptr)
{
return;
}
// On Mac we need to call glfwSetWindowSize even though we created the
// window with these same dimensions.
int w, h;
glfwGetFramebufferSize(m_glfwWindow, &w, &h);
m_width = w;
m_height = h;
dpi = m_fiddleContext->dpiScale(m_glfwWindow);
m_fiddleContext->onSizeChanged(m_glfwWindow, m_width, m_height, 0);
}
~TestingWindowFiddleContext() override
{
m_fiddleContext.reset();
if (m_glfwWindow != nullptr)
{
glfwPollEvents();
glfwDestroyWindow(m_glfwWindow);
glfwTerminate();
}
}
bool valid() const { return m_fiddleContext != nullptr; }
rive::Factory* factory() override { return m_fiddleContext->factory(); }
void resize(int width, int height) override
{
if (width != m_width || height != m_height)
{
glfwSetWindowSize(m_glfwWindow, width, height);
m_fiddleContext->onSizeChanged(m_glfwWindow, width, height, 0);
m_width = width;
m_height = height;
}
}
void hotloadShaders() override { m_fiddleContext->hotloadShaders(); }
rive::rcp<rive_tests::OffscreenRenderTarget> makeOffscreenRenderTarget(
uint32_t width,
uint32_t height,
bool riveRenderable) const override
{
#ifndef RIVE_TOOLS_NO_GL
if (auto* renderContextGL = m_fiddleContext->renderContextGLImpl())
{
return rive_tests::OffscreenRenderTarget::MakeGL(renderContextGL,
width,
height);
}
#endif
#ifdef RIVE_VULKAN
if (auto* renderContextVulkan =
m_fiddleContext->renderContextVulkanImpl())
{
return rive_tests::OffscreenRenderTarget::MakeVulkan(
renderContextVulkan->vulkanContext(),
width,
height,
riveRenderable);
}
#endif
return nullptr;
}
std::unique_ptr<rive::Renderer> beginFrame(
const FrameOptions& options) override
{
rive::gpu::RenderContext::FrameDescriptor frameDescriptor = {
.renderTargetWidth = static_cast<uint32_t>(m_width),
.renderTargetHeight = static_cast<uint32_t>(m_height),
.loadAction = options.doClear
? rive::gpu::LoadAction::clear
: rive::gpu::LoadAction::preserveRenderTarget,
.clearColor = options.clearColor,
.msaaSampleCount =
std::max(m_msaaSampleCount, options.forceMSAA ? 4 : 0),
.disableRasterOrdering = options.disableRasterOrdering,
.wireframe = options.wireframe,
.clockwiseFillOverride =
m_backendParams.clockwise || options.clockwiseFillOverride,
#ifdef WITH_RIVE_TOOLS
.synthesizedFailureType = options.synthesizedFailureType,
#endif
};
m_fiddleContext->begin(std::move(frameDescriptor));
return m_fiddleContext->makeRenderer(m_width, m_height);
}
void endFrame(std::vector<uint8_t>* pixelData) override
{
m_fiddleContext->end(m_glfwWindow, pixelData);
m_fiddleContext->tick();
glfwPollEvents();
glfwSwapBuffers(m_glfwWindow);
}
rive::gpu::RenderContext* renderContext() const override
{
return m_fiddleContext->renderContextOrNull();
}
rive::gpu::RenderContextGLImpl* renderContextGLImpl() const override
{
return m_fiddleContext->renderContextGLImpl();
}
rive::gpu::RenderTarget* renderTarget() const override
{
return m_fiddleContext->renderTargetOrNull();
}
void flushPLSContext(RenderTarget* offscreenRenderTarget) override
{
m_fiddleContext->flushPLSContext(offscreenRenderTarget);
}
bool consumeInputEvent(InputEventData& eventData) override
{
glfwPollEvents();
const bool hasNewEvent = !inputEvents.empty();
if (hasNewEvent)
{
eventData = inputEvents.front();
inputEvents.pop();
}
return hasNewEvent;
}
InputEventData waitForInputEvent() override
{
InputEventData eventData;
while (!consumeInputEvent(eventData))
{
glfwWaitEvents();
if (glfwWindowShouldClose(m_glfwWindow))
{
printf("Window terminated by user.\n");
exit(0);
}
}
return eventData;
}
bool shouldQuit() const override
{
return glfwWindowShouldClose(m_glfwWindow);
}
private:
GLFWwindow* m_glfwWindow = nullptr;
int m_msaaSampleCount = 0;
BackendParams m_backendParams;
std::unique_ptr<FiddleContext> m_fiddleContext;
};
TestingWindow* TestingWindow::MakeFiddleContext(
Backend backend,
const BackendParams& backendParams,
Visibility visibility,
void* platformWindow)
{
auto window = std::make_unique<TestingWindowFiddleContext>(backend,
backendParams,
visibility,
platformWindow);
if (!window->valid())
window = nullptr;
return window.release();
}
#endif