Skip to content

Commit

Permalink
Clear the view if presenting no layers
Browse files Browse the repository at this point in the history
  • Loading branch information
loic-sharma committed Dec 19, 2023
1 parent df604a1 commit c30b369
Show file tree
Hide file tree
Showing 12 changed files with 190 additions and 26 deletions.
50 changes: 42 additions & 8 deletions shell/platform/windows/compositor_opengl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ namespace flutter {

namespace {

constexpr uint32_t kWindowFrameBufferId = 0;

// The metadata for an OpenGL framebuffer backing store.
struct FramebufferBackingStore {
uint32_t framebuffer_id;
Expand Down Expand Up @@ -91,32 +93,48 @@ bool CompositorOpenGL::CollectBackingStore(const FlutterBackingStore* store) {

bool CompositorOpenGL::Present(const FlutterLayer** layers,
size_t layers_count) {
if (!engine_->view()) {
return false;
}

// Clear the view if there are no layers to present.
if (layers_count == 0) {
// Normally the compositor is initialized when the first backing store is
// created. However, on an empty frame no backing stores are created and
// the present needs to initialize the compositor.
if (!is_initialized_ && !Initialize()) {
return false;
}

return ClearSurface();
}

// TODO: Support compositing layers and platform views.
// See: https://github.com/flutter/flutter/issues/31713
FML_DCHECK(is_initialized_);
FML_DCHECK(layers_count == 1);
FML_DCHECK(layers[0]->offset.x == 0 && layers[0]->offset.y == 0);
FML_DCHECK(layers[0]->type == kFlutterLayerContentTypeBackingStore);
FML_DCHECK(layers[0]->backing_store->type == kFlutterBackingStoreTypeOpenGL);
FML_DCHECK(layers[0]->backing_store->open_gl.type ==
kFlutterOpenGLTargetTypeFramebuffer);

if (!engine_->view()) {
return false;
}

auto width = layers[0]->size.width;
auto height = layers[0]->size.height;

// Acquiring the view's framebuffer ID resizes its surface if necessary.
auto destination_id = engine_->view()->GetFrameBufferId(width, height);
auto source_id = layers[0]->backing_store->open_gl.framebuffer.name;
// Check if this frame can be presented. This resizes the surface if a resize
// is pending and |width| and |height| match the target size.
if (!engine_->view()->OnFrameGenerated(width, height)) {
return false;
}

if (!engine_->surface_manager()->MakeCurrent()) {
return false;
}

auto source_id = layers[0]->backing_store->open_gl.framebuffer.name;
gl_->BindFramebuffer(GL_READ_FRAMEBUFFER, source_id);
gl_->BindFramebuffer(GL_DRAW_FRAMEBUFFER, destination_id);
gl_->BindFramebuffer(GL_DRAW_FRAMEBUFFER, kWindowFrameBufferId);

gl_->BlitFramebuffer(0, // srcX0
0, // srcY0
Expand Down Expand Up @@ -151,4 +169,20 @@ bool CompositorOpenGL::Initialize() {
return true;
}

bool CompositorOpenGL::ClearSurface() {
FML_DCHECK(is_initialized_);

// Resize the surface if needed.
engine_->view()->OnEmptyFrameGenerated();

if (!engine_->surface_manager()->MakeCurrent()) {
return false;
}

gl_->ClearColor(0.0f, 0.0f, 0.0f, 0.0f);
gl_->Clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

return engine_->view()->SwapBuffers();
}

} // namespace flutter
3 changes: 3 additions & 0 deletions shell/platform/windows/compositor_opengl.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ class CompositorOpenGL : public Compositor {

// Initialize the compositor. This must run on the raster thread.
bool Initialize();

// Clear the view's surface and removes any previously presented layers.
bool ClearSurface();
};

} // namespace flutter
Expand Down
14 changes: 14 additions & 0 deletions shell/platform/windows/compositor_opengl_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,20 @@ TEST_F(CompositorOpenGLTest, Present) {
ASSERT_TRUE(compositor.CollectBackingStore(&backing_store));
}

TEST_F(CompositorOpenGLTest, PresentEmpty) {
UseEngineWithView();

auto compositor = CompositorOpenGL{engine(), kMockResolver};

// The context will be bound twice: first to initialize the compositor, second
// to clear the surface.
EXPECT_CALL(*surface_manager(), MakeCurrent)
.Times(2)
.WillRepeatedly(Return(true));
EXPECT_CALL(*view(), SwapBuffers).WillOnce(Return(true));
EXPECT_TRUE(compositor.Present(nullptr, 0));
}

TEST_F(CompositorOpenGLTest, HeadlessPresentIgnored) {
UseHeadlessEngine();

Expand Down
14 changes: 10 additions & 4 deletions shell/platform/windows/compositor_software.cc
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,23 @@ bool CompositorSoftware::CollectBackingStore(const FlutterBackingStore* store) {

bool CompositorSoftware::Present(const FlutterLayer** layers,
size_t layers_count) {
if (!engine_->view()) {
return false;
}

// Clear the view if there are no layers to present.
if (layers_count == 0) {
return engine_->view()->ClearSoftwareBitmap();
}

// TODO: Support compositing layers and platform views.
// See: https://github.com/flutter/flutter/issues/31713
FML_DCHECK(layers_count == 1);
FML_DCHECK(layers[0]->offset.x == 0 && layers[0]->offset.y == 0);
FML_DCHECK(layers[0]->type == kFlutterLayerContentTypeBackingStore);
FML_DCHECK(layers[0]->backing_store->type ==
kFlutterBackingStoreTypeSoftware);

if (!engine_->view()) {
return false;
}

const auto& backing_store = layers[0]->backing_store->software;

return engine_->view()->PresentSoftwareBitmap(
Expand Down
10 changes: 10 additions & 0 deletions shell/platform/windows/compositor_software_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class MockFlutterWindowsView : public FlutterWindowsView {
PresentSoftwareBitmap,
(const void* allocation, size_t row_bytes, size_t height),
(override));
MOCK_METHOD(bool, ClearSoftwareBitmap, (), (override));

private:
FML_DISALLOW_COPY_AND_ASSIGN(MockFlutterWindowsView);
Expand Down Expand Up @@ -105,6 +106,15 @@ TEST_F(CompositorSoftwareTest, Present) {
ASSERT_TRUE(compositor.CollectBackingStore(&backing_store));
}

TEST_F(CompositorSoftwareTest, PresentEmpty) {
UseEngineWithView();

auto compositor = CompositorSoftware{engine()};

EXPECT_CALL(*view(), ClearSoftwareBitmap).WillOnce(Return(true));
EXPECT_TRUE(compositor.Present(nullptr, 0));
}

TEST_F(CompositorSoftwareTest, HeadlessPresentIgnored) {
UseHeadlessEngine();

Expand Down
11 changes: 9 additions & 2 deletions shell/platform/windows/flutter_window.cc
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,13 @@ void FlutterWindow::OnResetImeComposing() {
AbortImeComposing();
}

bool FlutterWindow::OnBitmapSurfaceCleared() {
HDC dc = ::GetDC(GetWindowHandle());
bool result = ::PatBlt(dc, 0, 0, current_width_, current_height_, BLACKNESS);
::ReleaseDC(GetWindowHandle(), dc);
return result;
}

bool FlutterWindow::OnBitmapSurfaceUpdated(const void* allocation,
size_t row_bytes,
size_t height) {
Expand All @@ -334,8 +341,8 @@ bool FlutterWindow::OnBitmapSurfaceUpdated(const void* allocation,
bmi.bmiHeader.biBitCount = 32;
bmi.bmiHeader.biCompression = BI_RGB;
bmi.bmiHeader.biSizeImage = 0;
int ret = SetDIBitsToDevice(dc, 0, 0, row_bytes / 4, height, 0, 0, 0, height,
allocation, &bmi, DIB_RGB_COLORS);
int ret = ::SetDIBitsToDevice(dc, 0, 0, row_bytes / 4, height, 0, 0, 0,
height, allocation, &bmi, DIB_RGB_COLORS);
::ReleaseDC(GetWindowHandle(), dc);
return ret != 0;
}
Expand Down
3 changes: 3 additions & 0 deletions shell/platform/windows/flutter_window.h
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,9 @@ class FlutterWindow : public KeyboardManager::WindowDelegate,
// |FlutterWindowBindingHandler|
virtual void OnWindowResized() override;

// |FlutterWindowBindingHandler|
virtual bool OnBitmapSurfaceCleared() override;

// |FlutterWindowBindingHandler|
virtual bool OnBitmapSurfaceUpdated(const void* allocation,
size_t row_bytes,
Expand Down
29 changes: 25 additions & 4 deletions shell/platform/windows/flutter_windows_view.cc
Original file line number Diff line number Diff line change
Expand Up @@ -104,12 +104,28 @@ void FlutterWindowsView::SetEngine(FlutterWindowsEngine* engine) {
binding_handler_->GetDpiScale());
}

uint32_t FlutterWindowsView::GetFrameBufferId(size_t width, size_t height) {
// Called on an engine-controlled (non-platform) thread.
void FlutterWindowsView::OnEmptyFrameGenerated() {
// Called on the raster thread.
std::unique_lock<std::mutex> lock(resize_mutex_);

if (resize_status_ != ResizeState::kResizeStarted) {
return kWindowFrameBufferID;
return;
}

// Platform thread is blocked for the entire duration until the
// resize_status_ is set to kDone.
engine_->surface_manager()->ResizeSurface(
GetRenderTarget(), resize_target_width_, resize_target_height_,
binding_handler_->NeedsVSync());
resize_status_ = ResizeState::kFrameGenerated;
}

bool FlutterWindowsView::OnFrameGenerated(size_t width, size_t height) {
// Called on the raster thread.
std::unique_lock<std::mutex> lock(resize_mutex_);

if (resize_status_ != ResizeState::kResizeStarted) {
return true;
}

if (resize_target_width_ == width && resize_target_height_ == height) {
Expand All @@ -118,9 +134,10 @@ uint32_t FlutterWindowsView::GetFrameBufferId(size_t width, size_t height) {
engine_->surface_manager()->ResizeSurface(GetRenderTarget(), width, height,
binding_handler_->NeedsVSync());
resize_status_ = ResizeState::kFrameGenerated;
return true;
}

return kWindowFrameBufferID;
return false;
}

void FlutterWindowsView::UpdateFlutterCursor(const std::string& cursor_name) {
Expand Down Expand Up @@ -602,6 +619,10 @@ bool FlutterWindowsView::SwapBuffers() {
}
}

bool FlutterWindowsView::ClearSoftwareBitmap() {
return binding_handler_->OnBitmapSurfaceCleared();
}

bool FlutterWindowsView::PresentSoftwareBitmap(const void* allocation,
size_t row_bytes,
size_t height) {
Expand Down
23 changes: 18 additions & 5 deletions shell/platform/windows/flutter_windows_view.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,6 @@

namespace flutter {

// ID for the window frame buffer.
inline constexpr uint32_t kWindowFrameBufferID = 0;

// An OS-windowing neutral abstration for a Flutter view that works
// with win32 HWNDs.
class FlutterWindowsView : public WindowBindingHandlerDelegate {
Expand Down Expand Up @@ -68,11 +65,17 @@ class FlutterWindowsView : public WindowBindingHandlerDelegate {
// Swap the view's surface buffers. Must be called on the engine's raster
// thread. Returns true if the buffers were swapped.
//
// |OnFrameGenerated| or |OnEmptyFrameGenerated| must be called before this
// method.
//
// If the view is resizing, this returns false if the frame is not the target
// size. Otherwise, it unblocks the platform thread and blocks the raster
// thread until the v-blank.
virtual bool SwapBuffers();

// Callback to clear a previously presented software bitmap.
virtual bool ClearSoftwareBitmap();

// Callback for presenting a software bitmap.
virtual bool PresentSoftwareBitmap(const void* allocation,
size_t row_bytes,
Expand All @@ -87,8 +90,18 @@ class FlutterWindowsView : public WindowBindingHandlerDelegate {
// |WindowBindingHandlerDelegate|
void OnHighContrastChanged() override;

// Returns the frame buffer id for the engine to render to.
uint32_t GetFrameBufferId(size_t width, size_t height);
// Called on the raster thread when |CompositorOpenGL| receives an empty
// frame.
//
// This resizes the surface if a resize is pending.
void OnEmptyFrameGenerated();

// Called on the raster thread when |CompositorOpenGL| receives a frame.
// Returns true if the frame can be presented.
//
// This resizes the surface if a resize is pending and |width| and
// |height| match the target size.
bool OnFrameGenerated(size_t width, size_t height);

// Sets the cursor that should be used when the mouse is over the Flutter
// content. See mouse_cursor.dart for the values and meanings of cursor_name.
Expand Down
51 changes: 49 additions & 2 deletions shell/platform/windows/flutter_windows_view_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -849,8 +849,55 @@ TEST(FlutterWindowsViewTest, WindowResizeTests) {
// Wait until the platform thread has started the window resize.
metrics_sent_latch.Wait();

// Complete the window resize by requesting a buffer with the new window size.
view.GetFrameBufferId(500, 500);
// Complete the window resize by reporting a frame with the new window size.
view.OnFrameGenerated(500, 500);
resized_latch.Wait();
}

// Verify that an empty frame completes a view resize.
TEST(FlutterWindowsViewTest, TestEmptyFrameResizes) {
std::unique_ptr<FlutterWindowsEngine> engine = GetTestEngine();
EngineModifier modifier(engine.get());

auto window_binding_handler =
std::make_unique<NiceMock<MockWindowBindingHandler>>();
std::unique_ptr<MockAngleSurfaceManager> surface_manager =
std::make_unique<MockAngleSurfaceManager>();

EXPECT_CALL(*window_binding_handler.get(), NeedsVSync)
.WillOnce(Return(false));
EXPECT_CALL(
*surface_manager.get(),
ResizeSurface(_, /*width=*/500, /*height=*/500, /*enable_vsync=*/false))
.Times(1);
EXPECT_CALL(*surface_manager.get(), DestroySurface).Times(1);

FlutterWindowsView view(std::move(window_binding_handler));
modifier.SetSurfaceManager(std::move(surface_manager));
view.SetEngine(engine.get());

fml::AutoResetWaitableEvent metrics_sent_latch;
modifier.embedder_api().SendWindowMetricsEvent = MOCK_ENGINE_PROC(
SendWindowMetricsEvent,
([&metrics_sent_latch](auto engine,
const FlutterWindowMetricsEvent* event) {
metrics_sent_latch.Signal();
return kSuccess;
}));

fml::AutoResetWaitableEvent resized_latch;
std::thread([&resized_latch, &view]() {
// Start the window resize. This sends the new window metrics
// and then blocks until another thread completes the window resize.
view.OnWindowSizeChanged(500, 500);
resized_latch.Signal();
}).detach();

// Wait until the platform thread has started the window resize.
metrics_sent_latch.Wait();

// Complete the window resize by reporting a frame with the new window size.
view.OnEmptyFrameGenerated();
resized_latch.Wait();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class MockWindowBindingHandler : public WindowBindingHandler {
MOCK_METHOD(void, SetFlutterCursor, (HCURSOR cursor_name), (override));
MOCK_METHOD(void, OnCursorRectUpdated, (const Rect& rect), (override));
MOCK_METHOD(void, OnResetImeComposing, (), (override));
MOCK_METHOD(bool, OnBitmapSurfaceCleared, (), (override));
MOCK_METHOD(bool,
OnBitmapSurfaceUpdated,
(const void* allocation, size_t row_bytes, size_t height),
Expand Down
7 changes: 6 additions & 1 deletion shell/platform/windows/window_binding_handler.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,12 @@ class WindowBindingHandler {
// Invoked when the cursor/composing rect has been updated in the framework.
virtual void OnCursorRectUpdated(const Rect& rect) = 0;

// Invoked when the Embedder provides us with new bitmap data for the contents
// Invoked when the embedder clears the contents of this Flutter view.
//
// Returns whether the surface was successfully updated or not.
virtual bool OnBitmapSurfaceCleared() = 0;

// Invoked when the embedder provides us with new bitmap data for the contents
// of this Flutter view.
//
// Returns whether the surface was successfully updated or not.
Expand Down

0 comments on commit c30b369

Please sign in to comment.