| // 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 "gpu_surface_gl.h" | |
| #include "flutter/fml/arraysize.h" | |
| #include "flutter/fml/logging.h" | |
| #include "flutter/fml/trace_event.h" | |
| #include "flutter/shell/common/persistent_cache.h" | |
| #include "third_party/skia/include/core/SkColorFilter.h" | |
| #include "third_party/skia/include/core/SkSurface.h" | |
| #include "third_party/skia/include/gpu/GrBackendSurface.h" | |
| #include "third_party/skia/include/gpu/GrContextOptions.h" | |
| // These are common defines present on all OpenGL headers. However, we don't | |
| // want to perform GL header reasolution on each platform we support. So just | |
| // define these upfront. It is unlikely we will need more. But, if we do, we can | |
| // add the same here. | |
| #define GPU_GL_RGBA8 0x8058 | |
| #define GPU_GL_RGBA4 0x8056 | |
| #define GPU_GL_RGB565 0x8D62 | |
| namespace flutter { | |
| // Default maximum number of budgeted resources in the cache. | |
| static const int kGrCacheMaxCount = 8192; | |
| // Default maximum number of bytes of GPU memory of budgeted resources in the | |
| // cache. | |
| static const size_t kGrCacheMaxByteSize = 512 * (1 << 20); | |
| GPUSurfaceGL::GPUSurfaceGL(GPUSurfaceGLDelegate* delegate) | |
| : delegate_(delegate), weak_factory_(this) { | |
| if (!delegate_->GLContextMakeCurrent()) { | |
| FML_LOG(ERROR) | |
| << "Could not make the context current to setup the gr context."; | |
| return; | |
| } | |
| GrContextOptions options; | |
| options.fPersistentCache = PersistentCache::GetCacheForProcess(); | |
| options.fAvoidStencilBuffers = true; | |
| // To get video playback on the widest range of devices, we limit Skia to | |
| // ES2 shading language when the ES3 external image extension is missing. | |
| options.fPreferExternalImagesOverES3 = true; | |
| // TODO(goderbauer): remove option when skbug.com/7523 is fixed. | |
| // A similar work-around is also used in shell/common/io_manager.cc. | |
| options.fDisableGpuYUVConversion = true; | |
| auto context = GrContext::MakeGL(delegate_->GetGLInterface(), options); | |
| if (context == nullptr) { | |
| FML_LOG(ERROR) << "Failed to setup Skia Gr context."; | |
| return; | |
| } | |
| context_ = std::move(context); | |
| context_->setResourceCacheLimits(kGrCacheMaxCount, kGrCacheMaxByteSize); | |
| delegate_->GLContextClearCurrent(); | |
| valid_ = true; | |
| context_owner_ = true; | |
| } | |
| GPUSurfaceGL::GPUSurfaceGL(sk_sp<GrContext> gr_context, | |
| GPUSurfaceGLDelegate* delegate) | |
| : delegate_(delegate), context_(gr_context), weak_factory_(this) { | |
| if (!delegate_->GLContextMakeCurrent()) { | |
| FML_LOG(ERROR) | |
| << "Could not make the context current to setup the gr context."; | |
| return; | |
| } | |
| delegate_->GLContextClearCurrent(); | |
| valid_ = true; | |
| context_owner_ = false; | |
| } | |
| GPUSurfaceGL::~GPUSurfaceGL() { | |
| if (!valid_) { | |
| return; | |
| } | |
| if (!delegate_->GLContextMakeCurrent()) { | |
| FML_LOG(ERROR) << "Could not make the context current to destroy the " | |
| "GrContext resources."; | |
| return; | |
| } | |
| onscreen_surface_ = nullptr; | |
| if (context_owner_) { | |
| context_->releaseResourcesAndAbandonContext(); | |
| } | |
| context_ = nullptr; | |
| delegate_->GLContextClearCurrent(); | |
| } | |
| // |Surface| | |
| bool GPUSurfaceGL::IsValid() { | |
| return valid_; | |
| } | |
| static SkColorType FirstSupportedColorType(GrContext* context, | |
| GrGLenum* format) { | |
| #define RETURN_IF_RENDERABLE(x, y) \ | |
| if (context->colorTypeSupportedAsSurface((x))) { \ | |
| *format = (y); \ | |
| return (x); \ | |
| } | |
| RETURN_IF_RENDERABLE(kRGBA_8888_SkColorType, GPU_GL_RGBA8); | |
| RETURN_IF_RENDERABLE(kARGB_4444_SkColorType, GPU_GL_RGBA4); | |
| RETURN_IF_RENDERABLE(kRGB_565_SkColorType, GPU_GL_RGB565); | |
| return kUnknown_SkColorType; | |
| } | |
| static sk_sp<SkSurface> WrapOnscreenSurface(GrContext* context, | |
| const SkISize& size, | |
| intptr_t fbo) { | |
| GrGLenum format; | |
| const SkColorType color_type = FirstSupportedColorType(context, &format); | |
| GrGLFramebufferInfo framebuffer_info = {}; | |
| framebuffer_info.fFBOID = static_cast<GrGLuint>(fbo); | |
| framebuffer_info.fFormat = format; | |
| GrBackendRenderTarget render_target(size.width(), // width | |
| size.height(), // height | |
| 0, // sample count | |
| 0, // stencil bits (TODO) | |
| framebuffer_info // framebuffer info | |
| ); | |
| sk_sp<SkColorSpace> colorspace = SkColorSpace::MakeSRGB(); | |
| SkSurfaceProps surface_props( | |
| SkSurfaceProps::InitType::kLegacyFontHost_InitType); | |
| return SkSurface::MakeFromBackendRenderTarget( | |
| context, // gr context | |
| render_target, // render target | |
| GrSurfaceOrigin::kBottomLeft_GrSurfaceOrigin, // origin | |
| color_type, // color type | |
| colorspace, // colorspace | |
| &surface_props // surface properties | |
| ); | |
| } | |
| static sk_sp<SkSurface> CreateOffscreenSurface(GrContext* context, | |
| const SkISize& size) { | |
| const SkImageInfo image_info = SkImageInfo::MakeN32( | |
| size.fWidth, size.fHeight, kOpaque_SkAlphaType, SkColorSpace::MakeSRGB()); | |
| const SkSurfaceProps surface_props( | |
| SkSurfaceProps::InitType::kLegacyFontHost_InitType); | |
| return SkSurface::MakeRenderTarget(context, SkBudgeted::kNo, image_info, 0, | |
| kBottomLeft_GrSurfaceOrigin, | |
| &surface_props); | |
| } | |
| bool GPUSurfaceGL::CreateOrUpdateSurfaces(const SkISize& size) { | |
| if (onscreen_surface_ != nullptr && | |
| size == SkISize::Make(onscreen_surface_->width(), | |
| onscreen_surface_->height())) { | |
| // Surface size appears unchanged. So bail. | |
| return true; | |
| } | |
| // We need to do some updates. | |
| TRACE_EVENT0("flutter", "UpdateSurfacesSize"); | |
| // Either way, we need to get rid of previous surface. | |
| onscreen_surface_ = nullptr; | |
| offscreen_surface_ = nullptr; | |
| if (size.isEmpty()) { | |
| FML_LOG(ERROR) << "Cannot create surfaces of empty size."; | |
| return false; | |
| } | |
| sk_sp<SkSurface> onscreen_surface, offscreen_surface; | |
| onscreen_surface = | |
| WrapOnscreenSurface(context_.get(), // GL context | |
| size, // root surface size | |
| delegate_->GLContextFBO() // window FBO ID | |
| ); | |
| if (onscreen_surface == nullptr) { | |
| // If the onscreen surface could not be wrapped. There is absolutely no | |
| // point in moving forward. | |
| FML_LOG(ERROR) << "Could not wrap onscreen surface."; | |
| return false; | |
| } | |
| if (delegate_->UseOffscreenSurface()) { | |
| offscreen_surface = CreateOffscreenSurface(context_.get(), size); | |
| if (offscreen_surface == nullptr) { | |
| FML_LOG(ERROR) << "Could not create offscreen surface."; | |
| return false; | |
| } | |
| } | |
| onscreen_surface_ = std::move(onscreen_surface); | |
| offscreen_surface_ = std::move(offscreen_surface); | |
| return true; | |
| } | |
| // |Surface| | |
| SkMatrix GPUSurfaceGL::GetRootTransformation() const { | |
| return delegate_->GLContextSurfaceTransformation(); | |
| } | |
| // |Surface| | |
| std::unique_ptr<SurfaceFrame> GPUSurfaceGL::AcquireFrame(const SkISize& size) { | |
| if (delegate_ == nullptr) { | |
| return nullptr; | |
| } | |
| if (!delegate_->GLContextMakeCurrent()) { | |
| FML_LOG(ERROR) | |
| << "Could not make the context current to acquire the frame."; | |
| return nullptr; | |
| } | |
| const auto root_surface_transformation = GetRootTransformation(); | |
| sk_sp<SkSurface> surface = | |
| AcquireRenderSurface(size, root_surface_transformation); | |
| if (surface == nullptr) { | |
| return nullptr; | |
| } | |
| surface->getCanvas()->setMatrix(root_surface_transformation); | |
| SurfaceFrame::SubmitCallback submit_callback = | |
| [weak = weak_factory_.GetWeakPtr()](const SurfaceFrame& surface_frame, | |
| SkCanvas* canvas) { | |
| return weak ? weak->PresentSurface(canvas) : false; | |
| }; | |
| return std::make_unique<SurfaceFrame>(surface, submit_callback); | |
| } | |
| bool GPUSurfaceGL::PresentSurface(SkCanvas* canvas) { | |
| if (delegate_ == nullptr || canvas == nullptr || context_ == nullptr) { | |
| return false; | |
| } | |
| if (offscreen_surface_ != nullptr) { | |
| TRACE_EVENT0("flutter", "CopyTextureOnscreen"); | |
| SkPaint paint; | |
| SkCanvas* onscreen_canvas = onscreen_surface_->getCanvas(); | |
| onscreen_canvas->clear(SK_ColorTRANSPARENT); | |
| onscreen_canvas->drawImage(offscreen_surface_->makeImageSnapshot(), 0, 0, | |
| &paint); | |
| } | |
| { | |
| TRACE_EVENT0("flutter", "SkCanvas::Flush"); | |
| onscreen_surface_->getCanvas()->flush(); | |
| } | |
| if (!delegate_->GLContextPresent()) { | |
| return false; | |
| } | |
| if (delegate_->GLContextFBOResetAfterPresent()) { | |
| auto current_size = | |
| SkISize::Make(onscreen_surface_->width(), onscreen_surface_->height()); | |
| // The FBO has changed, ask the delegate for the new FBO and do a surface | |
| // re-wrap. | |
| auto new_onscreen_surface = | |
| WrapOnscreenSurface(context_.get(), // GL context | |
| current_size, // root surface size | |
| delegate_->GLContextFBO() // window FBO ID | |
| ); | |
| if (!new_onscreen_surface) { | |
| return false; | |
| } | |
| onscreen_surface_ = std::move(new_onscreen_surface); | |
| } | |
| return true; | |
| } | |
| sk_sp<SkSurface> GPUSurfaceGL::AcquireRenderSurface( | |
| const SkISize& untransformed_size, | |
| const SkMatrix& root_surface_transformation) { | |
| const auto transformed_rect = root_surface_transformation.mapRect( | |
| SkRect::MakeWH(untransformed_size.width(), untransformed_size.height())); | |
| const auto transformed_size = | |
| SkISize::Make(transformed_rect.width(), transformed_rect.height()); | |
| if (!CreateOrUpdateSurfaces(transformed_size)) { | |
| return nullptr; | |
| } | |
| return offscreen_surface_ != nullptr ? offscreen_surface_ : onscreen_surface_; | |
| } | |
| // |Surface| | |
| GrContext* GPUSurfaceGL::GetContext() { | |
| return context_.get(); | |
| } | |
| // |Surface| | |
| flutter::ExternalViewEmbedder* GPUSurfaceGL::GetExternalViewEmbedder() { | |
| return delegate_->GetExternalViewEmbedder(); | |
| } | |
| // |Surface| | |
| bool GPUSurfaceGL::MakeRenderContextCurrent() { | |
| return delegate_->GLContextMakeCurrent(); | |
| } | |
| } // namespace flutter |