| // 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 "flutter/shell/platform/windows/compositor_opengl.h" |
| |
| #include "GLES3/gl3.h" |
| #include "flutter/shell/platform/windows/flutter_windows_engine.h" |
| #include "flutter/shell/platform/windows/flutter_windows_view.h" |
| |
| namespace flutter { |
| |
| namespace { |
| |
| constexpr uint32_t kWindowFrameBufferId = 0; |
| |
| // The metadata for an OpenGL framebuffer backing store. |
| struct FramebufferBackingStore { |
| uint32_t framebuffer_id; |
| uint32_t texture_id; |
| }; |
| |
| // Based off Skia's logic: |
| // https://github.com/google/skia/blob/4738ed711e03212aceec3cd502a4adb545f38e63/src/gpu/ganesh/gl/GrGLCaps.cpp#L1963-L2116 |
| int GetSupportedTextureFormat(const impeller::DescriptionGLES* description) { |
| if (description->HasExtension("GL_EXT_texture_format_BGRA8888")) { |
| return GL_BGRA8_EXT; |
| } else if (description->HasExtension("GL_APPLE_texture_format_BGRA8888") && |
| description->GetGlVersion().IsAtLeast(impeller::Version(3, 0))) { |
| return GL_BGRA8_EXT; |
| } else { |
| return GL_RGBA8; |
| } |
| } |
| |
| } // namespace |
| |
| CompositorOpenGL::CompositorOpenGL(FlutterWindowsEngine* engine, |
| impeller::ProcTableGLES::Resolver resolver) |
| : engine_(engine), resolver_(resolver) {} |
| |
| bool CompositorOpenGL::CreateBackingStore( |
| const FlutterBackingStoreConfig& config, |
| FlutterBackingStore* result) { |
| if (!is_initialized_ && !Initialize()) { |
| return false; |
| } |
| |
| auto store = std::make_unique<FramebufferBackingStore>(); |
| |
| gl_->GenTextures(1, &store->texture_id); |
| gl_->GenFramebuffers(1, &store->framebuffer_id); |
| |
| gl_->BindFramebuffer(GL_FRAMEBUFFER, store->framebuffer_id); |
| |
| gl_->BindTexture(GL_TEXTURE_2D, store->texture_id); |
| gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); |
| gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); |
| gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); |
| gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); |
| gl_->TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, config.size.width, |
| config.size.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); |
| gl_->BindTexture(GL_TEXTURE_2D, 0); |
| |
| gl_->FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0_EXT, |
| GL_TEXTURE_2D, store->texture_id, 0); |
| |
| result->type = kFlutterBackingStoreTypeOpenGL; |
| result->open_gl.type = kFlutterOpenGLTargetTypeFramebuffer; |
| result->open_gl.framebuffer.name = store->framebuffer_id; |
| result->open_gl.framebuffer.target = format_; |
| result->open_gl.framebuffer.user_data = store.release(); |
| result->open_gl.framebuffer.destruction_callback = [](void* user_data) { |
| // Backing store destroyed in `CompositorOpenGL::CollectBackingStore`, set |
| // on FlutterCompositor.collect_backing_store_callback during engine start. |
| }; |
| return true; |
| } |
| |
| bool CompositorOpenGL::CollectBackingStore(const FlutterBackingStore* store) { |
| FML_DCHECK(is_initialized_); |
| FML_DCHECK(store->type == kFlutterBackingStoreTypeOpenGL); |
| FML_DCHECK(store->open_gl.type == kFlutterOpenGLTargetTypeFramebuffer); |
| |
| auto user_data = static_cast<FramebufferBackingStore*>( |
| store->open_gl.framebuffer.user_data); |
| |
| gl_->DeleteFramebuffers(1, &user_data->framebuffer_id); |
| gl_->DeleteTextures(1, &user_data->texture_id); |
| |
| delete user_data; |
| return true; |
| } |
| |
| bool CompositorOpenGL::Present(FlutterWindowsView* view, |
| const FlutterLayer** layers, |
| size_t layers_count) { |
| FML_DCHECK(view != nullptr); |
| |
| // 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 Clear(view); |
| } |
| |
| // 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); |
| |
| auto width = layers[0]->size.width; |
| auto height = layers[0]->size.height; |
| |
| // 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 (!view->OnFrameGenerated(width, height)) { |
| return false; |
| } |
| |
| // |OnFrameGenerated| should return false if the surface isn't valid. |
| FML_DCHECK(view->surface() != nullptr); |
| FML_DCHECK(view->surface()->IsValid()); |
| |
| egl::WindowSurface* surface = view->surface(); |
| if (!surface->MakeCurrent()) { |
| return false; |
| } |
| |
| auto source_id = layers[0]->backing_store->open_gl.framebuffer.name; |
| |
| // Disable the scissor test as it can affect blit operations. |
| // Prevents regressions like: https://github.com/flutter/flutter/issues/140828 |
| // See OpenGL specification version 4.6, section 18.3.1. |
| gl_->Disable(GL_SCISSOR_TEST); |
| |
| gl_->BindFramebuffer(GL_READ_FRAMEBUFFER, source_id); |
| gl_->BindFramebuffer(GL_DRAW_FRAMEBUFFER, kWindowFrameBufferId); |
| |
| gl_->BlitFramebuffer(0, // srcX0 |
| 0, // srcY0 |
| width, // srcX1 |
| height, // srcY1 |
| 0, // dstX0 |
| 0, // dstY0 |
| width, // dstX1 |
| height, // dstY1 |
| GL_COLOR_BUFFER_BIT, // mask |
| GL_NEAREST // filter |
| ); |
| |
| if (!surface->SwapBuffers()) { |
| return false; |
| } |
| |
| view->OnFramePresented(); |
| return true; |
| } |
| |
| bool CompositorOpenGL::Initialize() { |
| FML_DCHECK(!is_initialized_); |
| |
| egl::Manager* manager = engine_->egl_manager(); |
| if (!manager) { |
| return false; |
| } |
| |
| if (!manager->render_context()->MakeCurrent()) { |
| return false; |
| } |
| |
| gl_ = std::make_unique<impeller::ProcTableGLES>(resolver_); |
| if (!gl_->IsValid()) { |
| gl_.reset(); |
| return false; |
| } |
| |
| format_ = GetSupportedTextureFormat(gl_->GetDescription()); |
| is_initialized_ = true; |
| return true; |
| } |
| |
| bool CompositorOpenGL::Clear(FlutterWindowsView* view) { |
| FML_DCHECK(is_initialized_); |
| |
| // Check if this frame can be presented. This resizes the surface if needed. |
| if (!view->OnEmptyFrameGenerated()) { |
| return false; |
| } |
| |
| // |OnEmptyFrameGenerated| should return false if the surface isn't valid. |
| FML_DCHECK(view->surface() != nullptr); |
| FML_DCHECK(view->surface()->IsValid()); |
| |
| egl::WindowSurface* surface = view->surface(); |
| if (!surface->MakeCurrent()) { |
| return false; |
| } |
| |
| gl_->ClearColor(0.0f, 0.0f, 0.0f, 0.0f); |
| gl_->Clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); |
| |
| if (!surface->SwapBuffers()) { |
| return false; |
| } |
| |
| view->OnFramePresented(); |
| return true; |
| } |
| |
| } // namespace flutter |