| // Copyright 2013 The Flutter Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include <cassert> | |
| #include <chrono> | |
| #include <iostream> | |
| #define GLFW_EXPOSE_NATIVE_EGL | |
| #define GLFW_INCLUDE_GLEXT | |
| #include <EGL/egl.h> | |
| #include <EGL/eglext.h> | |
| #include <array> | |
| #include <cstring> | |
| #include <list> | |
| #include <unordered_map> | |
| #include "GLFW/glfw3.h" | |
| #include "GLFW/glfw3native.h" | |
| #include "embedder.h" | |
| // This value is calculated after the window is created. | |
| static double g_pixelRatio = 1.0; | |
| static const size_t kInitialWindowWidth = 800; | |
| static const size_t kInitialWindowHeight = 600; | |
| // Maximum damage history - for triple buffering we need to store damage for | |
| // last two frames; Some Android devices (Pixel 4) use quad buffering. | |
| static const int kMaxHistorySize = 10; | |
| // Keeps track of the most recent frame damages so that existing damage can | |
| // be easily computed. | |
| std::list<FlutterRect> damage_history_; | |
| // Keeps track of the existing damage associated with each FBO ID | |
| std::unordered_map<intptr_t, FlutterRect*> existing_damage_map_; | |
| EGLDisplay display_; | |
| EGLSurface surface_; | |
| static_assert(FLUTTER_ENGINE_VERSION == 1, | |
| "This Flutter Embedder was authored against the stable Flutter " | |
| "API at version 1. There has been a serious breakage in the " | |
| "API. Please read the ChangeLog and take appropriate action " | |
| "before updating this assertion"); | |
| void GLFWcursorPositionCallbackAtPhase(GLFWwindow* window, | |
| FlutterPointerPhase phase, | |
| double x, | |
| double y) { | |
| FlutterPointerEvent event = {}; | |
| event.struct_size = sizeof(event); | |
| event.phase = phase; | |
| event.x = x * g_pixelRatio; | |
| event.y = y * g_pixelRatio; | |
| event.timestamp = | |
| std::chrono::duration_cast<std::chrono::microseconds>( | |
| std::chrono::high_resolution_clock::now().time_since_epoch()) | |
| .count(); | |
| FlutterEngineSendPointerEvent( | |
| reinterpret_cast<FlutterEngine>(glfwGetWindowUserPointer(window)), &event, | |
| 1); | |
| } | |
| void GLFWcursorPositionCallback(GLFWwindow* window, double x, double y) { | |
| GLFWcursorPositionCallbackAtPhase(window, FlutterPointerPhase::kMove, x, y); | |
| } | |
| void GLFWmouseButtonCallback(GLFWwindow* window, | |
| int key, | |
| int action, | |
| int mods) { | |
| if (key == GLFW_MOUSE_BUTTON_1 && action == GLFW_PRESS) { | |
| double x, y; | |
| glfwGetCursorPos(window, &x, &y); | |
| GLFWcursorPositionCallbackAtPhase(window, FlutterPointerPhase::kDown, x, y); | |
| glfwSetCursorPosCallback(window, GLFWcursorPositionCallback); | |
| } | |
| if (key == GLFW_MOUSE_BUTTON_1 && action == GLFW_RELEASE) { | |
| double x, y; | |
| glfwGetCursorPos(window, &x, &y); | |
| GLFWcursorPositionCallbackAtPhase(window, FlutterPointerPhase::kUp, x, y); | |
| glfwSetCursorPosCallback(window, nullptr); | |
| } | |
| } | |
| static void GLFWKeyCallback(GLFWwindow* window, | |
| int key, | |
| int scancode, | |
| int action, | |
| int mods) { | |
| if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) { | |
| glfwSetWindowShouldClose(window, GLFW_TRUE); | |
| } | |
| } | |
| void GLFWwindowSizeCallback(GLFWwindow* window, int width, int height) { | |
| FlutterWindowMetricsEvent event = {}; | |
| event.struct_size = sizeof(event); | |
| event.width = width * g_pixelRatio; | |
| event.height = height * g_pixelRatio; | |
| event.pixel_ratio = g_pixelRatio; | |
| FlutterEngineSendWindowMetricsEvent( | |
| reinterpret_cast<FlutterEngine>(glfwGetWindowUserPointer(window)), | |
| &event); | |
| } | |
| // Auxiliary function used to transform a FlutterRect into the format that is | |
| // expected by the EGL functions (i.e. array of EGLint). | |
| static std::array<EGLint, 4> RectToInts(const FlutterRect rect) { | |
| EGLint height; | |
| eglQuerySurface(display_, surface_, EGL_HEIGHT, &height); | |
| std::array<EGLint, 4> res{ | |
| static_cast<int>(rect.left), height - static_cast<int>(rect.bottom), | |
| static_cast<int>(rect.right) - static_cast<int>(rect.left), | |
| static_cast<int>(rect.bottom) - static_cast<int>(rect.top)}; | |
| return res; | |
| } | |
| // Auxiliary function to union the damage regions comprised by two FlutterRect | |
| // element. It saves the result of this join in the rect variable. | |
| static void JoinFlutterRect(FlutterRect* rect, FlutterRect additional_rect) { | |
| rect->left = std::min(rect->left, additional_rect.left); | |
| rect->top = std::min(rect->top, additional_rect.top); | |
| rect->right = std::max(rect->right, additional_rect.right); | |
| rect->bottom = std::max(rect->bottom, additional_rect.bottom); | |
| } | |
| // Auxiliary function used to check if the given list of extensions contains the | |
| // requested extension name. | |
| static bool HasExtension(const char* extensions, const char* name) { | |
| const char* r = strstr(extensions, name); | |
| auto len = strlen(name); | |
| // check that the extension name is terminated by space or null terminator | |
| return r != nullptr && (r[len] == ' ' || r[len] == 0); | |
| } | |
| bool RunFlutter(GLFWwindow* window, | |
| const std::string& project_path, | |
| const std::string& icudtl_path) { | |
| FlutterRendererConfig config = {}; | |
| config.type = kOpenGL; | |
| config.open_gl.struct_size = sizeof(config.open_gl); | |
| config.open_gl.make_current = [](void* userdata) -> bool { | |
| glfwMakeContextCurrent(static_cast<GLFWwindow*>(userdata)); | |
| return true; | |
| }; | |
| config.open_gl.clear_current = [](void*) -> bool { | |
| glfwMakeContextCurrent(nullptr); // is this even a thing? | |
| return true; | |
| }; | |
| config.open_gl.present_with_info = | |
| [](void* userdata, const FlutterPresentInfo* info) -> bool { | |
| // Free the existing damage that was allocated to this frame. | |
| if (existing_damage_map_[info->fbo_id] != nullptr) { | |
| free(existing_damage_map_[info->fbo_id]); | |
| existing_damage_map_[info->fbo_id] = nullptr; | |
| } | |
| // Get list of extensions. | |
| const char* extensions = eglQueryString(display_, EGL_EXTENSIONS); | |
| // Retrieve the set damage region function. | |
| PFNEGLSETDAMAGEREGIONKHRPROC set_damage_region_ = nullptr; | |
| if (HasExtension(extensions, "EGL_KHR_partial_update")) { | |
| set_damage_region_ = reinterpret_cast<PFNEGLSETDAMAGEREGIONKHRPROC>( | |
| eglGetProcAddress("eglSetDamageRegionKHR")); | |
| } | |
| // Retrieve the swap buffers with damage function. | |
| PFNEGLSWAPBUFFERSWITHDAMAGEEXTPROC swap_buffers_with_damage_ = nullptr; | |
| if (HasExtension(extensions, "EGL_EXT_swap_buffers_with_damage")) { | |
| swap_buffers_with_damage_ = | |
| reinterpret_cast<PFNEGLSWAPBUFFERSWITHDAMAGEEXTPROC>( | |
| eglGetProcAddress("eglSwapBuffersWithDamageEXT")); | |
| } else if (HasExtension(extensions, "EGL_KHR_swap_buffers_with_damage")) { | |
| swap_buffers_with_damage_ = | |
| reinterpret_cast<PFNEGLSWAPBUFFERSWITHDAMAGEEXTPROC>( | |
| eglGetProcAddress("eglSwapBuffersWithDamageKHR")); | |
| } | |
| if (set_damage_region_) { | |
| // Set the buffer damage as the damage region. | |
| auto buffer_rects = RectToInts(info->buffer_damage.damage[0]); | |
| set_damage_region_(display_, surface_, buffer_rects.data(), 1); | |
| } | |
| // Add frame damage to damage history | |
| damage_history_.push_back(info->frame_damage.damage[0]); | |
| if (damage_history_.size() > kMaxHistorySize) { | |
| damage_history_.pop_front(); | |
| } | |
| if (swap_buffers_with_damage_) { | |
| // Swap buffers with frame damage. | |
| auto frame_rects = RectToInts(info->frame_damage.damage[0]); | |
| return swap_buffers_with_damage_(display_, surface_, frame_rects.data(), | |
| 1); | |
| } else { | |
| // If the required extensions for partial repaint were not provided, do | |
| // full repaint. | |
| return eglSwapBuffers(display_, surface_); | |
| } | |
| }; | |
| config.open_gl.fbo_callback = [](void*) -> uint32_t { | |
| return 0; // FBO0 | |
| }; | |
| config.open_gl.populate_existing_damage = | |
| [](void* userdata, intptr_t fbo_id, | |
| FlutterDamage* existing_damage) -> void { | |
| // Given the FBO age, create existing damage region by joining all frame | |
| // damages since FBO was last used | |
| EGLint age; | |
| if (glfwExtensionSupported("GL_EXT_buffer_age") == GLFW_TRUE) { | |
| eglQuerySurface(display_, surface_, EGL_BUFFER_AGE_EXT, &age); | |
| } else { | |
| age = 4; // Virtually no driver should have a swapchain length > 4. | |
| } | |
| existing_damage->num_rects = 1; | |
| // Allocate the array of rectangles for the existing damage. | |
| existing_damage_map_[fbo_id] = static_cast<FlutterRect*>( | |
| malloc(sizeof(FlutterRect) * existing_damage->num_rects)); | |
| existing_damage_map_[fbo_id][0] = | |
| FlutterRect{0, 0, kInitialWindowWidth, kInitialWindowHeight}; | |
| existing_damage->damage = existing_damage_map_[fbo_id]; | |
| if (age > 1) { | |
| --age; | |
| // join up to (age - 1) last rects from damage history | |
| for (auto i = damage_history_.rbegin(); | |
| i != damage_history_.rend() && age > 0; ++i, --age) { | |
| if (i == damage_history_.rbegin()) { | |
| if (i != damage_history_.rend()) { | |
| existing_damage->damage[0] = {i->left, i->top, i->right, i->bottom}; | |
| } | |
| } else { | |
| JoinFlutterRect(&(existing_damage->damage[0]), *i); | |
| } | |
| } | |
| } | |
| }; | |
| config.open_gl.gl_proc_resolver = [](void*, const char* name) -> void* { | |
| return reinterpret_cast<void*>(glfwGetProcAddress(name)); | |
| }; | |
| config.open_gl.fbo_reset_after_present = true; | |
| // This directory is generated by `flutter build bundle`. | |
| std::string assets_path = project_path + "/build/flutter_assets"; | |
| FlutterProjectArgs args = { | |
| .struct_size = sizeof(FlutterProjectArgs), | |
| .assets_path = assets_path.c_str(), | |
| .icu_data_path = | |
| icudtl_path.c_str(), // Find this in your bin/cache directory. | |
| }; | |
| FlutterEngine engine = nullptr; | |
| FlutterEngineResult result = | |
| FlutterEngineRun(FLUTTER_ENGINE_VERSION, &config, // renderer | |
| &args, window, &engine); | |
| if (result != kSuccess || engine == nullptr) { | |
| std::cout << "Could not run the Flutter Engine." << std::endl; | |
| return false; | |
| } | |
| glfwSetWindowUserPointer(window, engine); | |
| GLFWwindowSizeCallback(window, kInitialWindowWidth, kInitialWindowHeight); | |
| return true; | |
| } | |
| void printUsage() { | |
| std::cout | |
| << "usage: embedder_example_drm <path to project> <path to icudtl.dat>" | |
| << std::endl; | |
| } | |
| void GLFW_ErrorCallback(int error, const char* description) { | |
| std::cout << "GLFW Error: (" << error << ") " << description << std::endl; | |
| } | |
| int main(int argc, const char* argv[]) { | |
| if (argc != 3) { | |
| printUsage(); | |
| return 1; | |
| } | |
| std::string project_path = argv[1]; | |
| std::string icudtl_path = argv[2]; | |
| glfwSetErrorCallback(GLFW_ErrorCallback); | |
| int result = glfwInit(); | |
| if (result != GLFW_TRUE) { | |
| std::cout << "Could not initialize GLFW." << std::endl; | |
| return EXIT_FAILURE; | |
| } | |
| #if defined(__linux__) | |
| glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_EGL_CONTEXT_API); | |
| #endif | |
| GLFWwindow* window = glfwCreateWindow( | |
| kInitialWindowWidth, kInitialWindowHeight, "Flutter", NULL, NULL); | |
| if (window == nullptr) { | |
| std::cout << "Could not create GLFW window." << std::endl; | |
| return EXIT_FAILURE; | |
| } | |
| int framebuffer_width, framebuffer_height; | |
| glfwGetFramebufferSize(window, &framebuffer_width, &framebuffer_height); | |
| g_pixelRatio = framebuffer_width / kInitialWindowWidth; | |
| // Get the display and surface variables. | |
| display_ = glfwGetEGLDisplay(); | |
| surface_ = glfwGetEGLSurface(window); | |
| bool run_result = RunFlutter(window, project_path, icudtl_path); | |
| if (!run_result) { | |
| std::cout << "Could not run the Flutter engine." << std::endl; | |
| return EXIT_FAILURE; | |
| } | |
| glfwSetKeyCallback(window, GLFWKeyCallback); | |
| glfwSetWindowSizeCallback(window, GLFWwindowSizeCallback); | |
| glfwSetMouseButtonCallback(window, GLFWmouseButtonCallback); | |
| while (!glfwWindowShouldClose(window)) { | |
| glfwWaitEvents(); | |
| } | |
| glfwDestroyWindow(window); | |
| glfwTerminate(); | |
| return EXIT_SUCCESS; | |
| } |