#include "gpu_surface_gl.h"
#include "flutter/fml/logging.h"
#include "flutter/fml/size.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"
#define GPU_GL_RGBA8 0x8058
#define GPU_GL_RGBA4 0x8056
#define GPU_GL_RGB565 0x8D62
namespace flutter {
static const int kGrCacheMaxCount = 8192;
static const size_t kGrCacheMaxByteSize = 24 * (1 << 20);
GPUSurfaceGL::GPUSurfaceGL(GPUSurfaceGLDelegate* delegate,
bool render_to_surface)
: delegate_(delegate),
render_to_surface_(render_to_surface),
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;
options.fPreferExternalImagesOverES3 = true;
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();
context_owner_ = true;
valid_ = true;
}
GPUSurfaceGL::GPUSurfaceGL(sk_sp<GrContext> gr_context,
GPUSurfaceGLDelegate* delegate,
bool render_to_surface)
: delegate_(delegate),
context_(gr_context),
render_to_surface_(render_to_surface),
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();
}
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(),
size.height(),
0,
0,
framebuffer_info
);
sk_sp<SkColorSpace> colorspace = SkColorSpace::MakeSRGB();
SkSurfaceProps surface_props(
SkSurfaceProps::InitType::kLegacyFontHost_InitType);
return SkSurface::MakeFromBackendRenderTarget(
context,
render_target,
GrSurfaceOrigin::kBottomLeft_GrSurfaceOrigin,
color_type,
colorspace,
&surface_props
);
}
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())) {
return true;
}
TRACE_EVENT0("flutter", "UpdateSurfacesSize");
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(),
size,
delegate_->GLContextFBO()
);
if (onscreen_surface == nullptr) {
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;
}
SkMatrix GPUSurfaceGL::GetRootTransformation() const {
return delegate_->GLContextSurfaceTransformation();
}
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;
}
if (!render_to_surface_) {
return std::make_unique<SurfaceFrame>(
nullptr, [](const SurfaceFrame& surface_frame, SkCanvas* canvas) {
return true;
});
}
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 defined(PREVIEW)
int32_t width = onscreen_surface_->width();
int32_t height = onscreen_surface_->height();
SkImageInfo info = SkImageInfo::Make(width, height, kRGBA_8888_SkColorType, kOpaque_SkAlphaType);
int32_t pixelSize = info.bytesPerPixel();
auto pixelBuffer = std::make_unique<char[]>(width * height * pixelSize);
SkPixmap data(info, pixelBuffer.get(), width * pixelSize);
if (onscreen_surface_->readPixels(data, 0, 0)) {
delegate_->GLContextSendSurface(data.addr(), data.rowBytes() * data.height(), width, height);
} else {
FML_LOG(ERROR) << "GLContextSendSurface failed";
}
#endif
}
if (!delegate_->GLContextPresent()) {
return false;
}
if (delegate_->GLContextFBOResetAfterPresent()) {
auto current_size =
SkISize::Make(onscreen_surface_->width(), onscreen_surface_->height());
auto new_onscreen_surface =
WrapOnscreenSurface(context_.get(),
current_size,
delegate_->GLContextFBO()
);
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_;
}
GrContext* GPUSurfaceGL::GetContext() {
return context_.get();
}
flutter::ExternalViewEmbedder* GPUSurfaceGL::GetExternalViewEmbedder() {
return delegate_->GetExternalViewEmbedder();
}
bool GPUSurfaceGL::MakeRenderContextCurrent() {
return delegate_->GLContextMakeCurrent();
}
}