From 14cf4a68575de8b1315ec4c1680103234beff846 Mon Sep 17 00:00:00 2001 From: Bernardo Eilert Trevisan Date: Mon, 1 Aug 2022 18:36:03 +0000 Subject: [PATCH 01/19] Update the GLFW example callbacks. --- examples/glfw/FlutterEmbedderGLFW.cc | 112 ++++++++++++++++++++++++++- 1 file changed, 110 insertions(+), 2 deletions(-) diff --git a/examples/glfw/FlutterEmbedderGLFW.cc b/examples/glfw/FlutterEmbedderGLFW.cc index 8b17b25958fc1..fb878d5c3a040 100644 --- a/examples/glfw/FlutterEmbedderGLFW.cc +++ b/examples/glfw/FlutterEmbedderGLFW.cc @@ -6,13 +6,26 @@ #include #include +#define GLFW_EXPOSE_NATIVE_EGL +#define GLFW_INCLUDE_GLEXT + +#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; + +std::list damage_history_; static_assert(FLUTTER_ENGINE_VERSION == 1, "This Flutter Embedder was authored against the stable Flutter " @@ -82,6 +95,26 @@ void GLFWwindowSizeCallback(GLFWwindow* window, int width, int height) { &event); } +std::array static RectToInts(EGLDisplay display, + EGLSurface surface, + 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; +} + +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); +} + bool RunFlutter(GLFWwindow* window, const std::string& project_path, const std::string& icudtl_path) { @@ -96,16 +129,91 @@ bool RunFlutter(GLFWwindow* window, glfwMakeContextCurrent(nullptr); // is this even a thing? return true; }; - config.open_gl.present = [](void* userdata) -> bool { - glfwSwapBuffers(static_cast(userdata)); + config.open_gl.present_with_info = + [](void* userdata, const FlutterPresentInfo* info) -> bool { + PFNEGLSETDAMAGEREGIONKHRPROC set_damage_region_ = + reinterpret_cast( + eglGetProcAddress("eglSetDamageRegionKHR")); + PFNEGLSWAPBUFFERSWITHDAMAGEEXTPROC swap_buffers_with_damage_ = + reinterpret_cast( + eglGetProcAddress("eglSwapBuffersWithDamageKHR")); + + GLFWwindow* window = static_cast(userdata); + EGLDisplay display = glfwGetEGLDisplay(); + EGLSurface surface = glfwGetEGLSurface(window); + + auto buffer_rects = RectToInts( + display, surface, info->buffer_damage.damage[0]); + set_damage_region_(display, surface, buffer_rects.data(), 1); + + // Swap buffers with frame damage + auto frame_rects = RectToInts(display, surface, info->frame_damage.damage[0]); + swap_buffers_with_damage_(display, surface, frame_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(); + } + std::cout << "Buffer Damage: " << info->buffer_damage.damage[0].left << ", " + << info->buffer_damage.damage[0].top << ", " + << info->buffer_damage.damage[0].right << ", " + << info->buffer_damage.damage[0].bottom << std::endl; + std::cout << "Frame Damage: " << info->frame_damage.damage[0].left << ", " + << info->frame_damage.damage[0].top << ", " + << info->frame_damage.damage[0].right << ", " + << info->frame_damage.damage[0].bottom << std::endl; return true; }; config.open_gl.fbo_callback = [](void*) -> uint32_t { return 0; // FBO0 }; + config.open_gl.fbo_with_damage_callback = + [](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 + GLFWwindow* window = static_cast(userdata); + EGLDisplay display = glfwGetEGLDisplay(); + EGLSurface surface = glfwGetEGLSurface(window); + + 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. + } + std::cout << "Buffer age: " << age << std::endl; + + existing_damage->num_rects = 1; + std::array existing_damage_rect = {FlutterRect{0, 0, kInitialWindowWidth, kInitialWindowHeight}}; + existing_damage->damage = existing_damage_rect.data(); + + 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) { + std::cout << "Damage in history: " << i->left << ", " << i->top << ", " + << i->right << ", " << i->bottom << std::endl; + 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); + } + } + } + + std::cout << "Existing Damage: " << existing_damage->damage[0].left + << ", " << existing_damage->damage[0].top << ", " + << existing_damage->damage[0].right << ", " + << existing_damage->damage[0].bottom << std::endl; + }; 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"; From 10dd868723bf6bfee9467ead2a1d8348061d7315 Mon Sep 17 00:00:00 2001 From: Bernardo Eilert Trevisan Date: Mon, 1 Aug 2022 18:36:30 +0000 Subject: [PATCH 02/19] Update the dart example. --- examples/glfw/main.dart | 47 ++++------------------------------------- 1 file changed, 4 insertions(+), 43 deletions(-) diff --git a/examples/glfw/main.dart b/examples/glfw/main.dart index f1e74ab50be7c..652db9ffd404a 100644 --- a/examples/glfw/main.dart +++ b/examples/glfw/main.dart @@ -5,6 +5,8 @@ import 'package:flutter/material.dart'; import 'package:flutter/foundation.dart' show debugDefaultTargetPlatformOverride; +import 'package:flutter_spinkit/flutter_spinkit.dart'; + void main() { // This is a hack to make Flutter think you are running on Google Fuchsia, @@ -55,19 +57,6 @@ class MyHomePage extends StatefulWidget { } class _MyHomePageState extends State { - int _counter = 0; - - void _incrementCounter() { - setState(() { - // This call to setState tells the Flutter framework that something has - // changed in this State, which causes it to rerun the build method below - // so that the display can reflect the updated values. If we changed - // _counter without calling setState(), then the build method would not be - // called again, and so nothing would appear to happen. - _counter++; - }); - } - @override Widget build(BuildContext context) { // This method is rerun every time setState is called, for instance as done @@ -85,38 +74,10 @@ class _MyHomePageState extends State { body: Center( // Center is a layout widget. It takes a single child and positions it // in the middle of the parent. - child: Column( - // Column is also a layout widget. It takes a list of children and - // arranges them vertically. By default, it sizes itself to fit its - // children horizontally, and tries to be as tall as its parent. - // - // Invoke "debug painting" (press "p" in the console, choose the - // "Toggle Debug Paint" action from the Flutter Inspector in Android - // Studio, or the "Toggle Debug Paint" command in Visual Studio Code) - // to see the wireframe for each widget. - // - // Column has various properties to control how it sizes itself and - // how it positions its children. Here we use mainAxisAlignment to - // center the children vertically; the main axis here is the vertical - // axis because Columns are vertical (the cross axis would be - // horizontal). - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - 'You have pushed the button this many times:', - ), - Text( - '$_counter', - style: Theme.of(context).textTheme.headline4, - ), - ], + child: RepaintBoundary( + child: SpinKitRotatingCircle(color: Colors.blue, size: 50.0), ), ), - floatingActionButton: FloatingActionButton( - onPressed: _incrementCounter, - tooltip: 'Increment', - child: Icon(Icons.add), - ), // This trailing comma makes auto-formatting nicer for build methods. ); } } From fb92e45cc4b29c7b0f529da9e4e8199fce58e7f8 Mon Sep 17 00:00:00 2001 From: Bernardo Eilert Trevisan Date: Mon, 1 Aug 2022 18:37:15 +0000 Subject: [PATCH 03/19] Update CMakeLists --- examples/glfw/CMakeLists.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/glfw/CMakeLists.txt b/examples/glfw/CMakeLists.txt index 53bca275dc673..2563bd8bee05a 100644 --- a/examples/glfw/CMakeLists.txt +++ b/examples/glfw/CMakeLists.txt @@ -11,8 +11,10 @@ option(GLFW_BUILD_EXAMPLES "" OFF) option(GLFW_BUILD_TESTS "" OFF) option(GLFW_BUILD_DOCS "" OFF) option(GLFW_INSTALL "" OFF) +find_package(OpenGL REQUIRED COMPONENTS EGL) +include_directories(${OPENGL_INCLUDE_DIRS}) add_subdirectory(${CMAKE_SOURCE_DIR}/../../../third_party/glfw glfw) -target_link_libraries(flutter_glfw glfw) +target_link_libraries(flutter_glfw glfw OpenGL::EGL) include_directories(${CMAKE_SOURCE_DIR}/../../../third_party/glfw/include) ############################################################ From 3edbb40fbcfb8d16652d0fad3742f60d8574b925 Mon Sep 17 00:00:00 2001 From: Bernardo Eilert Trevisan Date: Mon, 1 Aug 2022 18:37:48 +0000 Subject: [PATCH 04/19] Update library dependencies in the BUILD file. --- examples/glfw/BUILD.gn | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/glfw/BUILD.gn b/examples/glfw/BUILD.gn index 2ff312c0ee8b5..4e3bcbfa95516 100644 --- a/examples/glfw/BUILD.gn +++ b/examples/glfw/BUILD.gn @@ -14,5 +14,7 @@ if (build_embedder_examples) { "//flutter/shell/platform/embedder:embedder", "//third_party/glfw", ] + + libs = [ "EGL" ] } } From 31839d1cb1a69e0e2ef7ad0e88dfa1e2f8e06340 Mon Sep 17 00:00:00 2001 From: Bernardo Eilert Trevisan Date: Mon, 1 Aug 2022 20:05:07 +0000 Subject: [PATCH 05/19] Documenting. --- examples/glfw/FlutterEmbedderGLFW.cc | 94 ++++++++++++++++++---------- 1 file changed, 60 insertions(+), 34 deletions(-) diff --git a/examples/glfw/FlutterEmbedderGLFW.cc b/examples/glfw/FlutterEmbedderGLFW.cc index fb878d5c3a040..7bbfaf7816732 100644 --- a/examples/glfw/FlutterEmbedderGLFW.cc +++ b/examples/glfw/FlutterEmbedderGLFW.cc @@ -13,6 +13,7 @@ #include #include #include +#include #include "GLFW/glfw3.h" #include "GLFW/glfw3native.h" #include "embedder.h" @@ -25,6 +26,8 @@ static const size_t kInitialWindowHeight = 600; // 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 damage_history_; static_assert(FLUTTER_ENGINE_VERSION == 1, @@ -95,7 +98,9 @@ void GLFWwindowSizeCallback(GLFWwindow* window, int width, int height) { &event); } -std::array static RectToInts(EGLDisplay display, +// 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(EGLDisplay display, EGLSurface surface, const FlutterRect rect) { EGLint height; @@ -108,13 +113,24 @@ std::array static RectToInts(EGLDisplay display, return res; } -void JoinFlutterRect(FlutterRect* rect, FlutterRect additional_rect) { +// 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) { @@ -131,58 +147,75 @@ bool RunFlutter(GLFWwindow* window, }; config.open_gl.present_with_info = [](void* userdata, const FlutterPresentInfo* info) -> bool { - PFNEGLSETDAMAGEREGIONKHRPROC set_damage_region_ = - reinterpret_cast( - eglGetProcAddress("eglSetDamageRegionKHR")); - PFNEGLSWAPBUFFERSWITHDAMAGEEXTPROC swap_buffers_with_damage_ = - reinterpret_cast( - eglGetProcAddress("eglSwapBuffersWithDamageKHR")); - + // Get the display and surface variables. GLFWwindow* window = static_cast(userdata); EGLDisplay display = glfwGetEGLDisplay(); EGLSurface surface = glfwGetEGLSurface(window); - auto buffer_rects = RectToInts( - display, surface, info->buffer_damage.damage[0]); - set_damage_region_(display, surface, buffer_rects.data(), 1); + // Get list of extensions. + const char* extensions = eglQueryString(display, EGL_EXTENSIONS); - // Swap buffers with frame damage - auto frame_rects = RectToInts(display, surface, info->frame_damage.damage[0]); - swap_buffers_with_damage_(display, surface, frame_rects.data(), 1); + // 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( + display, surface, 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(); } - std::cout << "Buffer Damage: " << info->buffer_damage.damage[0].left << ", " - << info->buffer_damage.damage[0].top << ", " - << info->buffer_damage.damage[0].right << ", " - << info->buffer_damage.damage[0].bottom << std::endl; - std::cout << "Frame Damage: " << info->frame_damage.damage[0].left << ", " - << info->frame_damage.damage[0].top << ", " - << info->frame_damage.damage[0].right << ", " - << info->frame_damage.damage[0].bottom << std::endl; - return true; + + if (swap_buffers_with_damage_) { + // Swap buffers with frame damage. + auto frame_rects = RectToInts(display, surface, 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.fbo_with_damage_callback = [](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 + // Get the display and surface variables. GLFWwindow* window = static_cast(userdata); EGLDisplay display = glfwGetEGLDisplay(); EGLSurface surface = glfwGetEGLSurface(window); + // 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. } - std::cout << "Buffer age: " << age << std::endl; existing_damage->num_rects = 1; std::array existing_damage_rect = {FlutterRect{0, 0, kInitialWindowWidth, kInitialWindowHeight}}; @@ -193,8 +226,6 @@ bool RunFlutter(GLFWwindow* window, // join up to (age - 1) last rects from damage history for (auto i = damage_history_.rbegin(); i != damage_history_.rend() && age > 0; ++i, --age) { - std::cout << "Damage in history: " << i->left << ", " << i->top << ", " - << i->right << ", " << i->bottom << std::endl; if (i == damage_history_.rbegin()) { if (i != damage_history_.rend()) { existing_damage->damage[0] = {i->left, i->top, i->right, i->bottom}; @@ -204,11 +235,6 @@ bool RunFlutter(GLFWwindow* window, } } } - - std::cout << "Existing Damage: " << existing_damage->damage[0].left - << ", " << existing_damage->damage[0].top << ", " - << existing_damage->damage[0].right << ", " - << existing_damage->damage[0].bottom << std::endl; }; config.open_gl.gl_proc_resolver = [](void*, const char* name) -> void* { return reinterpret_cast(glfwGetProcAddress(name)); From 1125309ecc1d4776a72b39626c6917162e21aa42 Mon Sep 17 00:00:00 2001 From: Bernardo Eilert Trevisan Date: Mon, 1 Aug 2022 20:05:37 +0000 Subject: [PATCH 06/19] Formatting. --- examples/glfw/FlutterEmbedderGLFW.cc | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/examples/glfw/FlutterEmbedderGLFW.cc b/examples/glfw/FlutterEmbedderGLFW.cc index 7bbfaf7816732..fd026bcaa316f 100644 --- a/examples/glfw/FlutterEmbedderGLFW.cc +++ b/examples/glfw/FlutterEmbedderGLFW.cc @@ -12,8 +12,8 @@ #include #include #include -#include #include +#include #include "GLFW/glfw3.h" #include "GLFW/glfw3native.h" #include "embedder.h" @@ -158,9 +158,8 @@ bool RunFlutter(GLFWwindow* window, // 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")); + set_damage_region_ = reinterpret_cast( + eglGetProcAddress("eglSetDamageRegionKHR")); } // Retrieve the swap buffers with damage function. @@ -177,8 +176,8 @@ bool RunFlutter(GLFWwindow* window, if (set_damage_region_) { // Set the buffer damage as the damage region. - auto buffer_rects = RectToInts( - display, surface, info->buffer_damage.damage[0]); + auto buffer_rects = + RectToInts(display, surface, info->buffer_damage.damage[0]); set_damage_region_(display, surface, buffer_rects.data(), 1); } @@ -190,7 +189,8 @@ bool RunFlutter(GLFWwindow* window, if (swap_buffers_with_damage_) { // Swap buffers with frame damage. - auto frame_rects = RectToInts(display, surface, info->frame_damage.damage[0]); + auto frame_rects = + RectToInts(display, surface, 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 @@ -202,7 +202,8 @@ bool RunFlutter(GLFWwindow* window, return 0; // FBO0 }; config.open_gl.fbo_with_damage_callback = - [](void* userdata, intptr_t fbo_id, FlutterDamage* existing_damage) -> void { + [](void* userdata, intptr_t fbo_id, + FlutterDamage* existing_damage) -> void { // Get the display and surface variables. GLFWwindow* window = static_cast(userdata); EGLDisplay display = glfwGetEGLDisplay(); @@ -218,7 +219,8 @@ bool RunFlutter(GLFWwindow* window, } existing_damage->num_rects = 1; - std::array existing_damage_rect = {FlutterRect{0, 0, kInitialWindowWidth, kInitialWindowHeight}}; + std::array existing_damage_rect = { + FlutterRect{0, 0, kInitialWindowWidth, kInitialWindowHeight}}; existing_damage->damage = existing_damage_rect.data(); if (age > 1) { From acc691ec720df71aa8a219a380e59377502909a9 Mon Sep 17 00:00:00 2001 From: Bernardo Eilert Trevisan Date: Mon, 1 Aug 2022 20:44:26 +0000 Subject: [PATCH 07/19] Update the callback name. --- examples/glfw/FlutterEmbedderGLFW.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/glfw/FlutterEmbedderGLFW.cc b/examples/glfw/FlutterEmbedderGLFW.cc index fd026bcaa316f..e27808038eca5 100644 --- a/examples/glfw/FlutterEmbedderGLFW.cc +++ b/examples/glfw/FlutterEmbedderGLFW.cc @@ -201,7 +201,7 @@ bool RunFlutter(GLFWwindow* window, config.open_gl.fbo_callback = [](void*) -> uint32_t { return 0; // FBO0 }; - config.open_gl.fbo_with_damage_callback = + config.open_gl.populate_existing_damage_callback = [](void* userdata, intptr_t fbo_id, FlutterDamage* existing_damage) -> void { // Get the display and surface variables. From 7f75cf992f7ca6ea5c671eae9cd9a9610a460df1 Mon Sep 17 00:00:00 2001 From: Bernardo Eilert Trevisan Date: Tue, 2 Aug 2022 16:46:13 +0000 Subject: [PATCH 08/19] Move glfw_drm example to a new folder. --- examples/glfw/BUILD.gn | 2 - examples/glfw/CMakeLists.txt | 4 +- examples/glfw/FlutterEmbedderGLFW.cc | 142 +-------------------------- examples/glfw/main.dart | 47 ++++++++- 4 files changed, 47 insertions(+), 148 deletions(-) diff --git a/examples/glfw/BUILD.gn b/examples/glfw/BUILD.gn index 4e3bcbfa95516..2ff312c0ee8b5 100644 --- a/examples/glfw/BUILD.gn +++ b/examples/glfw/BUILD.gn @@ -14,7 +14,5 @@ if (build_embedder_examples) { "//flutter/shell/platform/embedder:embedder", "//third_party/glfw", ] - - libs = [ "EGL" ] } } diff --git a/examples/glfw/CMakeLists.txt b/examples/glfw/CMakeLists.txt index 2563bd8bee05a..53bca275dc673 100644 --- a/examples/glfw/CMakeLists.txt +++ b/examples/glfw/CMakeLists.txt @@ -11,10 +11,8 @@ option(GLFW_BUILD_EXAMPLES "" OFF) option(GLFW_BUILD_TESTS "" OFF) option(GLFW_BUILD_DOCS "" OFF) option(GLFW_INSTALL "" OFF) -find_package(OpenGL REQUIRED COMPONENTS EGL) -include_directories(${OPENGL_INCLUDE_DIRS}) add_subdirectory(${CMAKE_SOURCE_DIR}/../../../third_party/glfw glfw) -target_link_libraries(flutter_glfw glfw OpenGL::EGL) +target_link_libraries(flutter_glfw glfw) include_directories(${CMAKE_SOURCE_DIR}/../../../third_party/glfw/include) ############################################################ diff --git a/examples/glfw/FlutterEmbedderGLFW.cc b/examples/glfw/FlutterEmbedderGLFW.cc index e27808038eca5..8b17b25958fc1 100644 --- a/examples/glfw/FlutterEmbedderGLFW.cc +++ b/examples/glfw/FlutterEmbedderGLFW.cc @@ -6,29 +6,13 @@ #include #include -#define GLFW_EXPOSE_NATIVE_EGL -#define GLFW_INCLUDE_GLEXT - -#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; - -// Keeps track of the most recent frame damages so that existing damage can -// be easily computed. -std::list damage_history_; static_assert(FLUTTER_ENGINE_VERSION == 1, "This Flutter Embedder was authored against the stable Flutter " @@ -98,39 +82,6 @@ void GLFWwindowSizeCallback(GLFWwindow* window, int width, int height) { &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(EGLDisplay display, - EGLSurface surface, - 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) { @@ -145,103 +96,16 @@ bool RunFlutter(GLFWwindow* window, glfwMakeContextCurrent(nullptr); // is this even a thing? return true; }; - config.open_gl.present_with_info = - [](void* userdata, const FlutterPresentInfo* info) -> bool { - // Get the display and surface variables. - GLFWwindow* window = static_cast(userdata); - EGLDisplay display = glfwGetEGLDisplay(); - EGLSurface surface = glfwGetEGLSurface(window); - - // 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(display, surface, 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(display, surface, 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.present = [](void* userdata) -> bool { + glfwSwapBuffers(static_cast(userdata)); + return true; }; config.open_gl.fbo_callback = [](void*) -> uint32_t { return 0; // FBO0 }; - config.open_gl.populate_existing_damage_callback = - [](void* userdata, intptr_t fbo_id, - FlutterDamage* existing_damage) -> void { - // Get the display and surface variables. - GLFWwindow* window = static_cast(userdata); - EGLDisplay display = glfwGetEGLDisplay(); - EGLSurface surface = glfwGetEGLSurface(window); - - // 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; - std::array existing_damage_rect = { - FlutterRect{0, 0, kInitialWindowWidth, kInitialWindowHeight}}; - existing_damage->damage = existing_damage_rect.data(); - - 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"; diff --git a/examples/glfw/main.dart b/examples/glfw/main.dart index 652db9ffd404a..f1e74ab50be7c 100644 --- a/examples/glfw/main.dart +++ b/examples/glfw/main.dart @@ -5,8 +5,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/foundation.dart' show debugDefaultTargetPlatformOverride; -import 'package:flutter_spinkit/flutter_spinkit.dart'; - void main() { // This is a hack to make Flutter think you are running on Google Fuchsia, @@ -57,6 +55,19 @@ class MyHomePage extends StatefulWidget { } class _MyHomePageState extends State { + int _counter = 0; + + void _incrementCounter() { + setState(() { + // This call to setState tells the Flutter framework that something has + // changed in this State, which causes it to rerun the build method below + // so that the display can reflect the updated values. If we changed + // _counter without calling setState(), then the build method would not be + // called again, and so nothing would appear to happen. + _counter++; + }); + } + @override Widget build(BuildContext context) { // This method is rerun every time setState is called, for instance as done @@ -74,10 +85,38 @@ class _MyHomePageState extends State { body: Center( // Center is a layout widget. It takes a single child and positions it // in the middle of the parent. - child: RepaintBoundary( - child: SpinKitRotatingCircle(color: Colors.blue, size: 50.0), + child: Column( + // Column is also a layout widget. It takes a list of children and + // arranges them vertically. By default, it sizes itself to fit its + // children horizontally, and tries to be as tall as its parent. + // + // Invoke "debug painting" (press "p" in the console, choose the + // "Toggle Debug Paint" action from the Flutter Inspector in Android + // Studio, or the "Toggle Debug Paint" command in Visual Studio Code) + // to see the wireframe for each widget. + // + // Column has various properties to control how it sizes itself and + // how it positions its children. Here we use mainAxisAlignment to + // center the children vertically; the main axis here is the vertical + // axis because Columns are vertical (the cross axis would be + // horizontal). + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'You have pushed the button this many times:', + ), + Text( + '$_counter', + style: Theme.of(context).textTheme.headline4, + ), + ], ), ), + floatingActionButton: FloatingActionButton( + onPressed: _incrementCounter, + tooltip: 'Increment', + child: Icon(Icons.add), + ), // This trailing comma makes auto-formatting nicer for build methods. ); } } From ceb6d03600e5e28517c2b5841499457633791031 Mon Sep 17 00:00:00 2001 From: Bernardo Eilert Trevisan Date: Tue, 2 Aug 2022 17:00:41 +0000 Subject: [PATCH 09/19] Create new example folder for drm_embedder. --- examples/glfw_drm/BUILD.gn | 20 ++ examples/glfw_drm/CMakeLists.txt | 35 +++ examples/glfw_drm/FlutterEmbedderGLFW.cc | 328 +++++++++++++++++++++++ examples/glfw_drm/README.md | 37 +++ examples/glfw_drm/main.dart | 83 ++++++ examples/glfw_drm/run.sh | 30 +++ 6 files changed, 533 insertions(+) create mode 100644 examples/glfw_drm/BUILD.gn create mode 100644 examples/glfw_drm/CMakeLists.txt create mode 100644 examples/glfw_drm/FlutterEmbedderGLFW.cc create mode 100644 examples/glfw_drm/README.md create mode 100644 examples/glfw_drm/main.dart create mode 100755 examples/glfw_drm/run.sh diff --git a/examples/glfw_drm/BUILD.gn b/examples/glfw_drm/BUILD.gn new file mode 100644 index 0000000000000..4e3bcbfa95516 --- /dev/null +++ b/examples/glfw_drm/BUILD.gn @@ -0,0 +1,20 @@ +# 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. + +import("//flutter/examples/examples.gni") + +if (build_embedder_examples) { + executable("glfw") { + output_name = "embedder_example" + + sources = [ "FlutterEmbedderGLFW.cc" ] + + deps = [ + "//flutter/shell/platform/embedder:embedder", + "//third_party/glfw", + ] + + libs = [ "EGL" ] + } +} diff --git a/examples/glfw_drm/CMakeLists.txt b/examples/glfw_drm/CMakeLists.txt new file mode 100644 index 0000000000000..2563bd8bee05a --- /dev/null +++ b/examples/glfw_drm/CMakeLists.txt @@ -0,0 +1,35 @@ +cmake_minimum_required(VERSION 3.15) +project(FlutterEmbedderGLFW) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11" ) + +add_executable(flutter_glfw FlutterEmbedderGLFW.cc) + +############################################################ +# GLFW +############################################################ +option(GLFW_BUILD_EXAMPLES "" OFF) +option(GLFW_BUILD_TESTS "" OFF) +option(GLFW_BUILD_DOCS "" OFF) +option(GLFW_INSTALL "" OFF) +find_package(OpenGL REQUIRED COMPONENTS EGL) +include_directories(${OPENGL_INCLUDE_DIRS}) +add_subdirectory(${CMAKE_SOURCE_DIR}/../../../third_party/glfw glfw) +target_link_libraries(flutter_glfw glfw OpenGL::EGL) +include_directories(${CMAKE_SOURCE_DIR}/../../../third_party/glfw/include) + +############################################################ +# Flutter Engine +############################################################ +# This is assuming you've built a local version of the Flutter Engine. If you +# downloaded yours is from the internet you'll have to change this. +include_directories(${CMAKE_SOURCE_DIR}/../../shell/platform/embedder) +find_library(FLUTTER_LIB flutter_engine PATHS ${CMAKE_SOURCE_DIR}/../../../out/host_debug_unopt) +target_link_libraries(flutter_glfw ${FLUTTER_LIB}) + +# Copy the flutter library here since the shared library +# name is `./libflutter_engine.dylib`. +add_custom_command( + TARGET flutter_glfw POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + ${FLUTTER_LIB} + ${CMAKE_CURRENT_BINARY_DIR}) diff --git a/examples/glfw_drm/FlutterEmbedderGLFW.cc b/examples/glfw_drm/FlutterEmbedderGLFW.cc new file mode 100644 index 0000000000000..e27808038eca5 --- /dev/null +++ b/examples/glfw_drm/FlutterEmbedderGLFW.cc @@ -0,0 +1,328 @@ +// 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 "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 damage_history_; + +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(); + 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; + 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(EGLDisplay display, + EGLSurface surface, + 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 { + // Get the display and surface variables. + GLFWwindow* window = static_cast(userdata); + EGLDisplay display = glfwGetEGLDisplay(); + EGLSurface surface = glfwGetEGLSurface(window); + + // 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(display, surface, 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(display, surface, 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_callback = + [](void* userdata, intptr_t fbo_id, + FlutterDamage* existing_damage) -> void { + // Get the display and surface variables. + GLFWwindow* window = static_cast(userdata); + EGLDisplay display = glfwGetEGLDisplay(); + EGLSurface surface = glfwGetEGLSurface(window); + + // 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; + std::array existing_damage_rect = { + FlutterRect{0, 0, kInitialWindowWidth, kInitialWindowHeight}}; + existing_damage->damage = existing_damage_rect.data(); + + 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 " + << 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; + + 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; +} diff --git a/examples/glfw_drm/README.md b/examples/glfw_drm/README.md new file mode 100644 index 0000000000000..a6b4018a2a998 --- /dev/null +++ b/examples/glfw_drm/README.md @@ -0,0 +1,37 @@ +# Flutter Embedder Engine GLFW Example +## Description +This is an example of how to use Flutter Engine Embedder in order to get a +Flutter project rendering in a new host environment. The intended audience is +people who need to support host environment other than the ones already provided +by Flutter. This is an advanced topic and not intended for beginners. + +In this example we are demonstrating rendering a Flutter project inside of the GUI +library [GLFW](https://www.glfw.org/). For more information about using the +embedder you can read the wiki article [Custom Flutter Engine Embedders](https://github.com/flutter/flutter/wiki/Custom-Flutter-Engine-Embedders). + +The key difference between this example and the other GLFW example in this +folder is that the present example implements dirty region management (i.e. +rendering only the regions that have changed between frames as opposed to always +rendering the entire frame). For more information on the implementation of +dirty region management within the Embedder API, see [DRM Embedder](https://flutter.dev/go/drm-embedder). + +## Running Instructions +The following example was tested on Linux but with a bit of tweaking should be +able to run on other *nix platforms and Windows. However, because this example +uses the EGL library, it is not compatible with MacOS platforms. + +The example has the following dependencies: + * [GLFW](https://www.glfw.org/) - This can be installed by running `sudo apt-get install libglfw3` + * [CMake](https://cmake.org/) - This can be installed by running `sudo apt-get install cmake` + * [EGL](https://docs.mesa3d.org/egl.html) - This can be installed by running `sudo apt-get install libglfw3-dev` + * [Flutter](https://flutter.dev/) - This can be installed from the [Flutter webpage](https://flutter.dev/docs/get-started/install) + * [Flutter Engine](https://flutter.dev) - This can be built or downloaded, see [Custom Flutter Engine Embedders](https://github.com/flutter/flutter/wiki/Custom-Flutter-Engine-Embedders) for more information. + +In order to **build** and **run** the example you should be able to go into this directory and run +`./run.sh`. + +## Troubleshooting +There are a few things you might have to tweak in order to get your build working: + * Flutter Engine Location - Inside the `CMakeList.txt` file you will see that it is set up to search for the header and library for the Flutter Engine in specific locations, those might not be the location of your Flutter Engine. + * Pixel Ratio - If the project runs but is drawing at the wrong scale you may have to tweak the `kPixelRatio` variable in `FlutterEmbedderGLFW.cc` file. + * GLFW Location - Inside the `CMakeLists.txt` we are searching for the GLFW library, if CMake can't find it you may have to edit that. diff --git a/examples/glfw_drm/main.dart b/examples/glfw_drm/main.dart new file mode 100644 index 0000000000000..652db9ffd404a --- /dev/null +++ b/examples/glfw_drm/main.dart @@ -0,0 +1,83 @@ +// 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. + +import 'package:flutter/material.dart'; +import 'package:flutter/foundation.dart' + show debugDefaultTargetPlatformOverride; +import 'package:flutter_spinkit/flutter_spinkit.dart'; + + +void main() { + // This is a hack to make Flutter think you are running on Google Fuchsia, + // otherwise you will get an error about running from an unsupported platform. + debugDefaultTargetPlatformOverride = TargetPlatform.fuchsia; + runApp(MyApp()); +} + +class MyApp extends StatelessWidget { + // This widget is the root of your application. + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Flutter Demo', + theme: ThemeData( + // This is the theme of your application. + // + // Try running your application with "flutter run". You'll see the + // application has a blue toolbar. Then, without quitting the app, try + // changing the primarySwatch below to Colors.green and then invoke + // "hot reload" (press "r" in the console where you ran "flutter run", + // or simply save your changes to "hot reload" in a Flutter IDE). + // Notice that the counter didn't reset back to zero; the application + // is not restarted. + primarySwatch: Colors.blue, + ), + home: MyHomePage(title: 'Flutter Demo Home Page'), + ); + } +} + +class MyHomePage extends StatefulWidget { + MyHomePage({Key? key, required this.title}) : super(key: key); + + // This widget is the home page of your application. It is stateful, meaning + // that it has a State object (defined below) that contains fields that affect + // how it looks. + + // This class is the configuration for the state. It holds the values (in this + // case the title) provided by the parent (in this case the App widget) and + // used by the build method of the State. Fields in a Widget subclass are + // always marked "final". + + final String title; + + @override + _MyHomePageState createState() => _MyHomePageState(); +} + +class _MyHomePageState extends State { + @override + Widget build(BuildContext context) { + // This method is rerun every time setState is called, for instance as done + // by the _incrementCounter method above. + // + // The Flutter framework has been optimized to make rerunning build methods + // fast, so that you can just rebuild anything that needs updating rather + // than having to individually change instances of widgets. + return Scaffold( + appBar: AppBar( + // Here we take the value from the MyHomePage object that was created by + // the App.build method, and use it to set our appbar title. + title: Text(widget.title), + ), + body: Center( + // Center is a layout widget. It takes a single child and positions it + // in the middle of the parent. + child: RepaintBoundary( + child: SpinKitRotatingCircle(color: Colors.blue, size: 50.0), + ), + ), + ); + } +} diff --git a/examples/glfw_drm/run.sh b/examples/glfw_drm/run.sh new file mode 100755 index 0000000000000..70a4f41fafc0d --- /dev/null +++ b/examples/glfw_drm/run.sh @@ -0,0 +1,30 @@ +#!/bin/bash +set -e # Exit if any program returns an error. + +################################################################# +# Make the host C++ project. +################################################################# +if [ ! -d debug ]; then + mkdir debug +fi +cd debug +cmake -DCMAKE_BUILD_TYPE=Debug .. +make + +################################################################# +# Make the guest Flutter project. +################################################################# +if [ ! -d myapp ]; then + flutter create myapp +fi +cd myapp +cp ../../main.dart lib/main.dart +flutter build bundle \ + --local-engine-src-path ../../../../../ \ + --local-engine=host_debug_unopt +cd - + +################################################################# +# Run the Flutter Engine Embedder +################################################################# +./flutter_glfw ./myapp ../../../../third_party/icu/common/icudtl.dat From 581ee79837adc0217a23076e44a59341df3ef936 Mon Sep 17 00:00:00 2001 From: Bernardo Eilert Trevisan Date: Wed, 3 Aug 2022 22:15:13 +0000 Subject: [PATCH 10/19] Add the flutter spinkit dependency --- examples/glfw_drm/run.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/glfw_drm/run.sh b/examples/glfw_drm/run.sh index 70a4f41fafc0d..0ae097e4cc990 100755 --- a/examples/glfw_drm/run.sh +++ b/examples/glfw_drm/run.sh @@ -16,6 +16,9 @@ make ################################################################# if [ ! -d myapp ]; then flutter create myapp + cd myapp + flutter pub add flutter_spinkit + cd .. fi cd myapp cp ../../main.dart lib/main.dart From 6bfaea5b53b02043c63ab9630f87c1f078e1edc6 Mon Sep 17 00:00:00 2001 From: Bernardo Eilert Trevisan Date: Wed, 3 Aug 2022 22:25:31 +0000 Subject: [PATCH 11/19] Update BUILD files. --- BUILD.gn | 1 + examples/glfw_drm/BUILD.gn | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/BUILD.gn b/BUILD.gn index a4dd6ca59ce58..06260eb43cbe1 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -83,6 +83,7 @@ group("flutter") { public_deps += [ "//flutter/examples/glfw", "//flutter/examples/vulkan_glfw", + "//flutter/examples/glfw_drm", ] } diff --git a/examples/glfw_drm/BUILD.gn b/examples/glfw_drm/BUILD.gn index 4e3bcbfa95516..4a9712ffba7af 100644 --- a/examples/glfw_drm/BUILD.gn +++ b/examples/glfw_drm/BUILD.gn @@ -6,7 +6,7 @@ import("//flutter/examples/examples.gni") if (build_embedder_examples) { executable("glfw") { - output_name = "embedder_example" + output_name = "embedder_example_drm" sources = [ "FlutterEmbedderGLFW.cc" ] From c9c480d1d464e824d5d0527cb0e24779b36b616a Mon Sep 17 00:00:00 2001 From: Bernardo Eilert Trevisan Date: Wed, 3 Aug 2022 22:28:22 +0000 Subject: [PATCH 12/19] Do not compile glfw_drm example if running it on mac. --- BUILD.gn | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/BUILD.gn b/BUILD.gn index 06260eb43cbe1..c6df7adfc855e 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -83,8 +83,13 @@ group("flutter") { public_deps += [ "//flutter/examples/glfw", "//flutter/examples/vulkan_glfw", - "//flutter/examples/glfw_drm", ] + + if (!is_mac) { + public_deps += [ + "//flutter/examples/glfw_drm", + ] + } } # If enbaled, compile the SDK / snapshot. From e6ca4ef5b8389336ae4d4f8c116ac4b8a9b38245 Mon Sep 17 00:00:00 2001 From: Bernardo Eilert Trevisan Date: Wed, 3 Aug 2022 22:41:16 +0000 Subject: [PATCH 13/19] Update the executable name for the glfw_drm example. --- examples/glfw_drm/BUILD.gn | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/glfw_drm/BUILD.gn b/examples/glfw_drm/BUILD.gn index 4a9712ffba7af..784e5654e5e69 100644 --- a/examples/glfw_drm/BUILD.gn +++ b/examples/glfw_drm/BUILD.gn @@ -5,7 +5,7 @@ import("//flutter/examples/examples.gni") if (build_embedder_examples) { - executable("glfw") { + executable("glfw_drm") { output_name = "embedder_example_drm" sources = [ "FlutterEmbedderGLFW.cc" ] From 71f4820d46b92db68c62e9fe8415b5187c89c488 Mon Sep 17 00:00:00 2001 From: Bernardo Eilert Trevisan Date: Wed, 3 Aug 2022 22:43:31 +0000 Subject: [PATCH 14/19] Avoid looking for the surface and display every time since they are always the same. --- examples/glfw_drm/FlutterEmbedderGLFW.cc | 41 ++++++++++-------------- 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/examples/glfw_drm/FlutterEmbedderGLFW.cc b/examples/glfw_drm/FlutterEmbedderGLFW.cc index e27808038eca5..2646464b59dac 100644 --- a/examples/glfw_drm/FlutterEmbedderGLFW.cc +++ b/examples/glfw_drm/FlutterEmbedderGLFW.cc @@ -30,6 +30,9 @@ static const int kMaxHistorySize = 10; // be easily computed. std::list damage_history_; +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 " @@ -100,11 +103,9 @@ void GLFWwindowSizeCallback(GLFWwindow* window, int width, int height) { // 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(EGLDisplay display, - EGLSurface surface, - const FlutterRect rect) { +static std::array RectToInts(const FlutterRect rect) { EGLint height; - eglQuerySurface(display, surface, EGL_HEIGHT, &height); + eglQuerySurface(display_, surface_, EGL_HEIGHT, &height); std::array res{ static_cast(rect.left), height - static_cast(rect.bottom), @@ -147,13 +148,8 @@ bool RunFlutter(GLFWwindow* window, }; config.open_gl.present_with_info = [](void* userdata, const FlutterPresentInfo* info) -> bool { - // Get the display and surface variables. - GLFWwindow* window = static_cast(userdata); - EGLDisplay display = glfwGetEGLDisplay(); - EGLSurface surface = glfwGetEGLSurface(window); - // Get list of extensions. - const char* extensions = eglQueryString(display, EGL_EXTENSIONS); + const char* extensions = eglQueryString(display_, EGL_EXTENSIONS); // Retrieve the set damage region function. PFNEGLSETDAMAGEREGIONKHRPROC set_damage_region_ = nullptr; @@ -176,9 +172,8 @@ bool RunFlutter(GLFWwindow* window, if (set_damage_region_) { // Set the buffer damage as the damage region. - auto buffer_rects = - RectToInts(display, surface, info->buffer_damage.damage[0]); - set_damage_region_(display, surface, buffer_rects.data(), 1); + auto buffer_rects = RectToInts(info->buffer_damage.damage[0]); + set_damage_region_(display_, surface_, buffer_rects.data(), 1); } // Add frame damage to damage history @@ -189,13 +184,12 @@ bool RunFlutter(GLFWwindow* window, if (swap_buffers_with_damage_) { // Swap buffers with frame damage. - auto frame_rects = - RectToInts(display, surface, info->frame_damage.damage[0]); - return swap_buffers_with_damage_(display, surface, frame_rects.data(), 1); + 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); + return eglSwapBuffers(display_, surface_); } }; config.open_gl.fbo_callback = [](void*) -> uint32_t { @@ -204,16 +198,11 @@ bool RunFlutter(GLFWwindow* window, config.open_gl.populate_existing_damage_callback = [](void* userdata, intptr_t fbo_id, FlutterDamage* existing_damage) -> void { - // Get the display and surface variables. - GLFWwindow* window = static_cast(userdata); - EGLDisplay display = glfwGetEGLDisplay(); - EGLSurface surface = glfwGetEGLSurface(window); - // 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); + eglQuerySurface(display_, surface_, EGL_BUFFER_AGE_EXT, &age); } else { age = 4; // Virtually no driver should have a swapchain length > 4. } @@ -267,7 +256,7 @@ bool RunFlutter(GLFWwindow* window, } void printUsage() { - std::cout << "usage: embedder_example " + std::cout << "usage: embedder_example_drm " << std::endl; } @@ -307,6 +296,10 @@ int main(int argc, const char* argv[]) { 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; From f42d759488dbbbc183f54755c6b30ddb9dff37dc Mon Sep 17 00:00:00 2001 From: Bernardo Eilert Trevisan Date: Wed, 3 Aug 2022 22:54:45 +0000 Subject: [PATCH 15/19] Formatting. --- examples/glfw_drm/FlutterEmbedderGLFW.cc | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/examples/glfw_drm/FlutterEmbedderGLFW.cc b/examples/glfw_drm/FlutterEmbedderGLFW.cc index 2646464b59dac..3523b82263c8e 100644 --- a/examples/glfw_drm/FlutterEmbedderGLFW.cc +++ b/examples/glfw_drm/FlutterEmbedderGLFW.cc @@ -185,7 +185,8 @@ bool RunFlutter(GLFWwindow* window, 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); + 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. @@ -256,8 +257,9 @@ bool RunFlutter(GLFWwindow* window, } void printUsage() { - std::cout << "usage: embedder_example_drm " - << std::endl; + std::cout + << "usage: embedder_example_drm " + << std::endl; } void GLFW_ErrorCallback(int error, const char* description) { From b42dc00a80f1ca4fe9c1fbbb1979b3d67d56ea86 Mon Sep 17 00:00:00 2001 From: Bernardo Eilert Trevisan Date: Wed, 3 Aug 2022 23:12:59 +0000 Subject: [PATCH 16/19] GN formatting. --- BUILD.gn | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/BUILD.gn b/BUILD.gn index c6df7adfc855e..97c2c4fe9c38a 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -86,9 +86,7 @@ group("flutter") { ] if (!is_mac) { - public_deps += [ - "//flutter/examples/glfw_drm", - ] + public_deps += [ "//flutter/examples/glfw_drm" ] } } From 0c76b745a7dd8a9f6de7bd4e2949545d77fe344a Mon Sep 17 00:00:00 2001 From: Bernardo Eilert Trevisan Date: Wed, 10 Aug 2022 22:07:38 +0000 Subject: [PATCH 17/19] Update the way the user malloc's the existing damage --- examples/glfw_drm/FlutterEmbedderGLFW.cc | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/examples/glfw_drm/FlutterEmbedderGLFW.cc b/examples/glfw_drm/FlutterEmbedderGLFW.cc index 3523b82263c8e..7573800cf124c 100644 --- a/examples/glfw_drm/FlutterEmbedderGLFW.cc +++ b/examples/glfw_drm/FlutterEmbedderGLFW.cc @@ -14,6 +14,7 @@ #include #include #include +#include #include "GLFW/glfw3.h" #include "GLFW/glfw3native.h" #include "embedder.h" @@ -30,6 +31,9 @@ static const int kMaxHistorySize = 10; // 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_; @@ -148,6 +152,9 @@ bool RunFlutter(GLFWwindow* window, }; config.open_gl.present_with_info = [](void* userdata, const FlutterPresentInfo* info) -> bool { + // Free the existing damage that was allocated to this frame. + free(existing_damage_map_[info->fbo_id]); + // Get list of extensions. const char* extensions = eglQueryString(display_, EGL_EXTENSIONS); @@ -209,9 +216,13 @@ bool RunFlutter(GLFWwindow* window, } existing_damage->num_rects = 1; - std::array existing_damage_rect = { - FlutterRect{0, 0, kInitialWindowWidth, kInitialWindowHeight}}; - existing_damage->damage = existing_damage_rect.data(); + + // 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; From 938d74ec378d0b8bfcdf962a052d4b799b825742 Mon Sep 17 00:00:00 2001 From: Bernardo Eilert Trevisan Date: Wed, 10 Aug 2022 23:14:19 +0000 Subject: [PATCH 18/19] Update comment --- examples/glfw_drm/main.dart | 3 --- 1 file changed, 3 deletions(-) diff --git a/examples/glfw_drm/main.dart b/examples/glfw_drm/main.dart index 652db9ffd404a..d8ad9221f8e4c 100644 --- a/examples/glfw_drm/main.dart +++ b/examples/glfw_drm/main.dart @@ -59,9 +59,6 @@ class MyHomePage extends StatefulWidget { class _MyHomePageState extends State { @override Widget build(BuildContext context) { - // This method is rerun every time setState is called, for instance as done - // by the _incrementCounter method above. - // // The Flutter framework has been optimized to make rerunning build methods // fast, so that you can just rebuild anything that needs updating rather // than having to individually change instances of widgets. From 0db51f4c9f890c2b0967ac4397ce7c9162bfec09 Mon Sep 17 00:00:00 2001 From: Bernardo Eilert Trevisan Date: Fri, 12 Aug 2022 18:54:37 +0000 Subject: [PATCH 19/19] Update example and add gitignore. --- examples/glfw_drm/.gitignore | 1 + examples/glfw_drm/FlutterEmbedderGLFW.cc | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 examples/glfw_drm/.gitignore diff --git a/examples/glfw_drm/.gitignore b/examples/glfw_drm/.gitignore new file mode 100644 index 0000000000000..b7443460fb263 --- /dev/null +++ b/examples/glfw_drm/.gitignore @@ -0,0 +1 @@ +debug/ diff --git a/examples/glfw_drm/FlutterEmbedderGLFW.cc b/examples/glfw_drm/FlutterEmbedderGLFW.cc index 7573800cf124c..d75811092f927 100644 --- a/examples/glfw_drm/FlutterEmbedderGLFW.cc +++ b/examples/glfw_drm/FlutterEmbedderGLFW.cc @@ -203,7 +203,7 @@ bool RunFlutter(GLFWwindow* window, config.open_gl.fbo_callback = [](void*) -> uint32_t { return 0; // FBO0 }; - config.open_gl.populate_existing_damage_callback = + 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