Skip to content

add dirty region management #331

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ project("flutter_elinux" LANGUAGES CXX C)

# Build options.
option(BACKEND_TYPE "Select WAYLAND, DRM-GBM, DRM-EGLSTREAM, or X11 as the display backend type" WAYLAND)
option(USE_DIRTY_REGION_MANAGEMENT "Use Flutter dirty region management" ON)
option(USE_GLES3 "Use OpenGL ES3 (default is OpenGL ES2)" OFF)
option(ENABLE_EGL_ALPHA_COMPONENT_OF_COLOR_BUFFER "Enable alpha component of the EGL color buffer" ON)
option(ENABLE_VSYNC "Enable embedder vsync" OFF)
Expand Down
5 changes: 5 additions & 0 deletions cmake/build.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,11 @@ else()
"src/flutter/shell/platform/linux_embedded/window/renderer/window_decorations_wayland.cc")
endif()

# Use flutter dirty region management
if(USE_DIRTY_REGION_MANAGEMENT)
add_definitions(-DUSE_OPENGL_DIRTY_REGION_MANAGEMENT)
endif()

# OpenGL ES version.
if(USE_GLES3)
add_definitions(-DUSE_GLES3)
Expand Down
20 changes: 20 additions & 0 deletions src/flutter/shell/platform/linux_embedded/flutter_elinux_engine.cc
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,33 @@ FlutterRendererConfig GetRendererConfig() {
}
return host->view()->ClearCurrent();
};
#if defined(USE_OPENGL_DIRTY_REGION_MANAGEMENT)
config.open_gl.fbo_reset_after_present = false;
config.open_gl.present_with_info =
[](void* user_data, const FlutterPresentInfo* info) -> bool {
auto host = static_cast<FlutterELinuxEngine*>(user_data);
if (!host->view()) {
return false;
}
return host->view()->PresentWithInfo(info);
};
config.open_gl.populate_existing_damage =
[](void* user_data, const intptr_t fbo_id,
FlutterDamage* existing_damage) -> void {
auto host = static_cast<FlutterELinuxEngine*>(user_data);
if (host->view()) {
host->view()->PopulateExistingDamage(fbo_id, existing_damage);
}
};
#else
config.open_gl.present = [](void* user_data) -> bool {
auto host = static_cast<FlutterELinuxEngine*>(user_data);
if (!host->view()) {
return false;
}
return host->view()->Present();
};
#endif
config.open_gl.fbo_callback = [](void* user_data) -> uint32_t {
auto host = static_cast<FlutterELinuxEngine*>(user_data);
if (!host->view()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,15 @@ bool FlutterELinuxView::Present() {
return GetRenderSurfaceTarget()->GLContextPresent(0);
}

bool FlutterELinuxView::PresentWithInfo(const FlutterPresentInfo* info) {
return GetRenderSurfaceTarget()->GLContextPresentWithInfo(info);
}

void FlutterELinuxView::PopulateExistingDamage(const intptr_t fbo_id,
FlutterDamage* existing_damage) {
GetRenderSurfaceTarget()->PopulateExistingDamage(fbo_id, existing_damage);
}

uint32_t FlutterELinuxView::GetOnscreenFBO() {
return GetRenderSurfaceTarget()->GLContextFBO();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ class FlutterELinuxView : public WindowBindingHandlerDelegate {
bool MakeCurrent();
bool ClearCurrent();
bool Present();
bool PresentWithInfo(const FlutterPresentInfo* info);
void PopulateExistingDamage(const intptr_t fbo_id,
FlutterDamage* existing_damage);
uint32_t GetOnscreenFBO();
bool MakeResourceCurrent();

Expand Down
15 changes: 13 additions & 2 deletions src/flutter/shell/platform/linux_embedded/surface/egl_utils.cc
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@

// Copyright 2021 Sony Corporation. All rights reserved.
// Copyright 2023 Sony Corporation. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "flutter/shell/platform/linux_embedded/surface/egl_utils.h"

#include <EGL/egl.h>

#include <cstring>
#include <string>
#include <vector>

Expand Down Expand Up @@ -40,4 +41,14 @@ std::string get_egl_error_cause() {
return nullptr;
}

} // namespace flutter
// Auxiliary function used to check if the given list of extensions contains the
// requested extension name.
bool has_egl_extension(const char* extensions, const char* name) {
const char* r = std::strstr(extensions, name);
auto len = std::strlen(name);

// check that the extension name is terminated by space or null terminator
return r != nullptr && (r[len] == ' ' || r[len] == 0);
}

} // namespace flutter
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2021 Sony Corporation. All rights reserved.
// Copyright 2023 Sony Corporation. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

Expand All @@ -10,6 +10,7 @@
namespace flutter {

std::string get_egl_error_cause();
bool has_egl_extension(const char* extensions, const char* name);

} // namespace flutter

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2021 Sony Corporation. All rights reserved.
// Copyright 2023 Sony Corporation. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

Expand All @@ -9,10 +9,41 @@

namespace flutter {

constexpr size_t kInitialWindowWidthPx = 1280;
constexpr size_t kInitialWindowHeightPx = 720;

// Maximum damage history - for triple buffering we need to store damage for
// last two frames; Some Android devices (Pixel 4) use quad buffering.
constexpr const int kMaxHistorySize = 10;

ELinuxEGLSurface::ELinuxEGLSurface(EGLSurface surface,
EGLDisplay display,
EGLContext context)
: surface_(surface), display_(display), context_(context){};
: surface_(surface),
display_(display),
context_(context),
width_px_(kInitialWindowWidthPx),
height_px_(kInitialWindowHeightPx) {
const char* extensions = eglQueryString(display_, EGL_EXTENSIONS);

if (has_egl_extension(extensions, "EGL_KHR_partial_update")) {
eglSetDamageRegionKHR_ = reinterpret_cast<PFNEGLSETDAMAGEREGIONKHRPROC>(
eglGetProcAddress("eglSetDamageRegionKHR"));
}

if (has_egl_extension(extensions, "EGL_EXT_swap_buffers_with_damage")) {
eglSwapBuffersWithDamageEXT_ =
reinterpret_cast<PFNEGLSWAPBUFFERSWITHDAMAGEEXTPROC>(
eglGetProcAddress("eglSwapBuffersWithDamageEXT"));
} else if (has_egl_extension(extensions,
"EGL_KHR_swap_buffers_with_damage")) {
eglSwapBuffersWithDamageEXT_ =
reinterpret_cast<PFNEGLSWAPBUFFERSWITHDAMAGEEXTPROC>(
eglGetProcAddress("eglSwapBuffersWithDamageKHR"));
} else {
// do nothing.
}
};

ELinuxEGLSurface::~ELinuxEGLSurface() {
if (surface_ != EGL_NO_SURFACE) {
Expand All @@ -28,6 +59,12 @@ bool ELinuxEGLSurface::IsValid() const {
return surface_ != EGL_NO_SURFACE;
}

void ELinuxEGLSurface::SurfaceResize(const size_t width_px,
const size_t height_px) {
width_px_ = width_px;
height_px_ = height_px;
}

bool ELinuxEGLSurface::MakeCurrent() const {
if (eglMakeCurrent(display_, surface_, surface_, context_) != EGL_TRUE) {
ELINUX_LOG(ERROR) << "Failed to make the EGL context current: "
Expand All @@ -54,11 +91,119 @@ bool ELinuxEGLSurface::MakeCurrent() const {

bool ELinuxEGLSurface::SwapBuffers() const {
if (eglSwapBuffers(display_, surface_) != EGL_TRUE) {
ELINUX_LOG(ERROR) << "Failed to swap the EGL buffer: "
<< get_egl_error_cause();
ELINUX_LOG(ERROR) << "eglSwapBuffers failed: " << get_egl_error_cause();
return false;
}
return true;
}

// Reference of dirty region management:
// https://github.com/flutter/engine/blob/main/examples/glfw_drm/FlutterEmbedderGLFW.cc

bool ELinuxEGLSurface::SwapBuffers(const FlutterPresentInfo* info) {
// Free the existing damage that was allocated to this frame.
if (existing_damage_map_.find(info->fbo_id) != existing_damage_map_.end() &&
existing_damage_map_[info->fbo_id] != nullptr) {
free(existing_damage_map_[info->fbo_id]);
existing_damage_map_[info->fbo_id] = nullptr;
}

// Set the buffer damage as the damage region.
if (eglSetDamageRegionKHR_) {
auto buffer_rects = RectToInts(info->buffer_damage.damage[0]);
if (eglSetDamageRegionKHR_(display_, surface_, buffer_rects.data(), 1) !=
EGL_TRUE) {
ELINUX_LOG(ERROR) << "eglSetDamageRegionKHR failed: "
<< get_egl_error_cause();
return false;
}
}

// 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 (eglSwapBuffersWithDamageEXT_) {
auto frame_rects = RectToInts(info->frame_damage.damage[0]);
if (eglSwapBuffersWithDamageEXT_(display_, surface_, frame_rects.data(),
1) != EGL_TRUE) {
ELINUX_LOG(ERROR) << "eglSwapBuffersWithDamageEXT failed: "
<< get_egl_error_cause();
return false;
}
} else {
// If the required extensions for partial repaint were not provided, do
// full repaint.
if (eglSwapBuffers(display_, surface_) != EGL_TRUE) {
ELINUX_LOG(ERROR) << "eglSwapBuffers failed: " << get_egl_error_cause();
return false;
}
}

return true;
}

void ELinuxEGLSurface::PopulateExistingDamage(const intptr_t fbo_id,
FlutterDamage* existing_damage) {
// Given the FBO age, create existing damage region by joining all frame
// damages since FBO was last used
EGLint age = 0;
if (eglQuerySurface(display_, surface_, EGL_BUFFER_AGE_EXT, &age) !=
EGL_TRUE ||
age == 0) {
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, static_cast<double>(width_px_), static_cast<double>(height_px_)};
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 {
// Auxiliary function to union the damage regions comprised by two
// FlutterRect element. It saves the result of this join in the rect
// variable.
FlutterRect* rect = &(existing_damage->damage[0]);
const FlutterRect additional_rect = *i;

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 transform a FlutterRect into the format that is
// expected by the EGL functions (i.e. array of EGLint).
std::array<EGLint, 4> ELinuxEGLSurface::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;
}

} // namespace flutter
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
// Copyright 2021 Sony Corporation. All rights reserved.
// Copyright 2023 Sony Corporation. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef FLUTTER_SHELL_PLATFORM_LINUX_EMBEDDED_SURFACE_ELINUX_EGL_SURFACE_H_
#define FLUTTER_SHELL_PLATFORM_LINUX_EMBEDDED_SURFACE_ELINUX_EGL_SURFACE_H_

#include <EGL/egl.h>
#include <EGL/eglext.h>

#include <array>
#include <list>
#include <unordered_map>

#include "flutter/shell/platform/embedder/embedder.h"

namespace flutter {

Expand All @@ -17,14 +24,38 @@ class ELinuxEGLSurface {

bool IsValid() const;

void SurfaceResize(const size_t width_px, const size_t height_px);

bool MakeCurrent() const;

bool SwapBuffers() const;

bool SwapBuffers(const FlutterPresentInfo* info);

void PopulateExistingDamage(const intptr_t fbo_id,
FlutterDamage* existing_damage);

private:
// Auxiliary function used to transform a FlutterRect into the format that is
// expected by the EGL functions (i.e. array of EGLint).
std::array<EGLint, 4> RectToInts(const FlutterRect rect);

EGLDisplay display_;
EGLSurface surface_;
EGLContext context_;

size_t width_px_;
size_t height_px_;

PFNEGLSETDAMAGEREGIONKHRPROC eglSetDamageRegionKHR_ = nullptr;
PFNEGLSWAPBUFFERSWITHDAMAGEEXTPROC eglSwapBuffersWithDamageEXT_ = nullptr;

// 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_;
};

} // namespace flutter
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2021 Sony Corporation. All rights reserved.
// Copyright 2023 Sony Corporation. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

Expand Down Expand Up @@ -31,6 +31,7 @@ bool SurfaceBase::SetNativeWindow(NativeWindow* window) {

bool SurfaceBase::OnScreenSurfaceResize(const size_t width_px,
const size_t height_px) {
onscreen_surface_->SurfaceResize(width_px, height_px);
if (!native_window_->Resize(width_px, height_px)) {
ELINUX_LOG(ERROR) << "Failed to resize.";
return false;
Expand All @@ -44,6 +45,7 @@ bool SurfaceBase::OnScreenSurfaceResize(const size_t width_px,
onscreen_surface_ = nullptr;
return false;
}
onscreen_surface_->SurfaceResize(width_px, height_px);
}
return true;
};
Expand Down
Loading