// 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 #include #include #define GLFW_EXPOSE_NATIVE_EGL #define GLFW_INCLUDE_GLEXT #include #include #include #include #include #include #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; static constexpr FlutterViewId kImplicitViewId = 0; // Keeps track of the most recent frame damages so that existing damage can // be easily computed. std::list damage_history_; // Keeps track of the existing damage associated with each FBO ID std::unordered_map 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::high_resolution_clock::now().time_since_epoch()) .count(); // This example only supports a single window, therefore we assume the pointer // event occurred in the only view, the implicit view. event.view_id = kImplicitViewId; FlutterEngineSendPointerEvent( reinterpret_cast(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; // This example only supports a single window, therefore we assume the event // occurred in the only view, the implicit view. event.view_id = kImplicitViewId; FlutterEngineSendWindowMetricsEvent( reinterpret_cast(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 RectToInts(const FlutterRect rect) { EGLint height; eglQuerySurface(display_, surface_, EGL_HEIGHT, &height); std::array res{ static_cast(rect.left), height - static_cast(rect.bottom), static_cast(rect.right) - static_cast(rect.left), static_cast(rect.bottom) - static_cast(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(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( 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( eglGetProcAddress("eglSwapBuffersWithDamageEXT")); } else if (HasExtension(extensions, "EGL_KHR_swap_buffers_with_damage")) { swap_buffers_with_damage_ = reinterpret_cast( 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( 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(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 " << 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; }