| /* |
| * Copyright 2022 Rive |
| */ |
| |
| #include "testing_window.hpp" |
| |
| #if defined(TESTING) || defined(RIVE_TOOLS_NO_GLFW) |
| TestingWindow* TestingWindow::MakeFiddleContext(Backend, |
| Visibility, |
| const char*, |
| void* platformWindow) |
| { |
| return nullptr; |
| } |
| |
| #else |
| |
| #include "fiddle_context.hpp" |
| |
| #include <deque> |
| |
| #define GLFW_INCLUDE_NONE |
| #define GLFW_NATIVE_INCLUDE_NONE |
| #include "GLFW/glfw3.h" |
| #include <GLFW/glfw3native.h> |
| |
| using namespace rive; |
| using namespace rive::gpu; |
| |
| static std::deque<char> s_keys; |
| |
| 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) |
| { |
| s_keys.push_back(static_cast<char>(key)); |
| return; |
| } |
| switch (key) |
| { |
| case GLFW_KEY_ESCAPE: |
| glfwSetWindowShouldClose(window, GLFW_TRUE); |
| break; |
| case GLFW_KEY_TAB: |
| s_keys.push_back('\t'); |
| break; |
| case GLFW_KEY_BACKSPACE: |
| s_keys.push_back('\b'); |
| break; |
| } |
| } |
| } |
| |
| class TestingWindowFiddleContext : public TestingWindow |
| { |
| public: |
| TestingWindowFiddleContext(Backend backend, |
| Visibility visibility, |
| const char* gpuNameFilter, |
| void* platformWindow) |
| { |
| // Vulkan headless rendering doesn't need an OS window. |
| // It's convenient to run Swiftshader on CI without GLFW. |
| if (IsANGLE(backend)) |
| { |
| #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); |
| #else |
| glfwInitHint(GLFW_ANGLE_PLATFORM_TYPE, |
| GLFW_ANGLE_PLATFORM_TYPE_VULKAN); |
| #endif |
| } |
| if (!glfwInit()) |
| { |
| fprintf(stderr, "Failed to initialize GLFW.\n"); |
| abort(); |
| } |
| |
| if (!IsGL(backend)) |
| { |
| glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); |
| glfwWindowHint(GLFW_SAMPLES, 0); |
| glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); |
| } |
| else if (IsANGLE(backend)) |
| { |
| 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 (IsMSAA(backend)) |
| { |
| m_msaaSampleCount = 4; |
| glfwWindowHint(GLFW_SAMPLES, m_msaaSampleCount); |
| glfwWindowHint(GLFW_STENCIL_BITS, 8); |
| glfwWindowHint(GLFW_DEPTH_BITS, 24); |
| } |
| m_clockwiseFill = IsClockwiseFill(backend); |
| |
| 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); |
| |
| // 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(9999, |
| 9999, |
| "Rive Renderer", |
| fullscreenMonitor, |
| nullptr); |
| if (!m_glfwWindow) |
| { |
| glfwTerminate(); |
| fprintf(stderr, "Failed to create GLFW m_glfwWindow.\n"); |
| abort(); |
| } |
| glfwMakeContextCurrent(m_glfwWindow); |
| glfwSwapInterval(0); |
| |
| glfwSetKeyCallback(m_glfwWindow, key_callback); |
| |
| FiddleContextOptions fiddleOptions = { |
| .retinaDisplay = visibility == Visibility::fullscreen, |
| .synchronousShaderCompilations = true, |
| .enableReadPixels = true, |
| .disableRasterOrdering = IsAtomic(backend), |
| .coreFeaturesOnly = IsCore(backend), |
| .allowHeadlessRendering = visibility == Visibility::headless, |
| .enableVulkanValidationLayers = true, |
| .gpuNameFilter = gpuNameFilter, |
| }; |
| |
| switch (backend) |
| { |
| case Backend::rhi: |
| case Backend::coregraphics: |
| case Backend::skia: |
| break; |
| case Backend::gl: |
| case Backend::glatomic: |
| case Backend::glcw: |
| case Backend::glmsaa: |
| case Backend::angle: |
| case Backend::anglemsaa: |
| m_fiddleContext = FiddleContext::MakeGLPLS(fiddleOptions); |
| break; |
| case Backend::d3d: |
| case Backend::d3datomic: |
| m_fiddleContext = FiddleContext::MakeD3DPLS(fiddleOptions); |
| break; |
| case Backend::metal: |
| case Backend::metalcw: |
| case Backend::metalatomic: |
| m_fiddleContext = FiddleContext::MakeMetalPLS(fiddleOptions); |
| break; |
| case Backend::vk: |
| case Backend::vkcore: |
| case Backend::vkcw: |
| case Backend::moltenvk: |
| case Backend::moltenvkcore: |
| case Backend::swiftshader: |
| case Backend::swiftshadercore: |
| m_fiddleContext = FiddleContext::MakeVulkanPLS(fiddleOptions); |
| break; |
| case Backend::dawn: |
| m_fiddleContext = FiddleContext::MakeDawnPLS(fiddleOptions); |
| break; |
| } |
| |
| if (m_fiddleContext == nullptr) |
| { |
| fprintf(stderr, |
| "error: unable to create FiddleContext for %s.\n", |
| BackendName(backend)); |
| abort(); |
| } |
| // 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; |
| m_fiddleContext->onSizeChanged(m_glfwWindow, m_width, m_height, 0); |
| } |
| |
| ~TestingWindowFiddleContext() override |
| { |
| m_fiddleContext.reset(); |
| if (m_glfwWindow != nullptr) |
| { |
| glfwPollEvents(); |
| glfwDestroyWindow(m_glfwWindow); |
| glfwTerminate(); |
| } |
| } |
| |
| 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; |
| } |
| } |
| |
| 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 = m_msaaSampleCount, |
| .wireframe = options.wireframe, |
| .clockwiseFillOverride = |
| m_clockwiseFill || options.clockwiseFillOverride, |
| }; |
| 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::RenderTarget* renderTarget() const override |
| { |
| return m_fiddleContext->renderTargetOrNull(); |
| } |
| |
| void flushPLSContext() override { m_fiddleContext->flushPLSContext(); } |
| |
| bool peekKey(char& key) override |
| { |
| if (s_keys.empty()) |
| { |
| return false; |
| } |
| key = s_keys.front(); |
| s_keys.pop_front(); |
| return true; |
| } |
| |
| char getKey() override |
| { |
| char key; |
| while (!peekKey(key)) |
| { |
| glfwWaitEvents(); |
| if (glfwWindowShouldClose(m_glfwWindow)) |
| { |
| printf("Window terminated by user.\n"); |
| exit(0); |
| } |
| } |
| return key; |
| } |
| |
| bool shouldQuit() const override |
| { |
| return glfwWindowShouldClose(m_glfwWindow); |
| } |
| |
| private: |
| GLFWwindow* m_glfwWindow = nullptr; |
| int m_msaaSampleCount = 0; |
| bool m_clockwiseFill = false; |
| std::unique_ptr<FiddleContext> m_fiddleContext; |
| }; |
| |
| TestingWindow* TestingWindow::MakeFiddleContext(Backend backend, |
| Visibility visibility, |
| const char* gpuNameFilter, |
| void* platformWindow) |
| { |
| return new TestingWindowFiddleContext(backend, |
| visibility, |
| gpuNameFilter, |
| platformWindow); |
| } |
| |
| #endif |