#include "flutter/shell/common/rasterizer.h"
#include "flutter/shell/common/persistent_cache.h"
#include <utility>
#include "include/core/SkEncodedImageFormat.h"
#include "include/core/SkImageEncoder.h"
#include "include/core/SkPictureRecorder.h"
#include "include/core/SkSerialProcs.h"
#include "include/core/SkSurface.h"
#include "include/core/SkSurfaceCharacterization.h"
#include "include/utils/SkBase64.h"
namespace flutter {
#ifndef GPU_DISABLED
static constexpr std::chrono::milliseconds kSkiaCleanupExpiration(15000);
#endif
static Rasterizer::DummyDelegate dummy_delegate_;
Rasterizer::Rasterizer(
TaskRunners task_runners,
std::unique_ptr<flutter::CompositorContext> compositor_context)
: Rasterizer(dummy_delegate_,
std::move(task_runners),
std::move(compositor_context)) {}
Rasterizer::Rasterizer(Delegate& delegate, TaskRunners task_runners)
: Rasterizer(delegate,
std::move(task_runners),
std::make_unique<flutter::CompositorContext>()) {}
Rasterizer::Rasterizer(
Delegate& delegate,
TaskRunners task_runners,
std::unique_ptr<flutter::CompositorContext> compositor_context)
: delegate_(delegate),
task_runners_(std::move(task_runners)),
compositor_context_(std::move(compositor_context)),
user_override_resource_cache_bytes_(false),
weak_factory_(this) {
FML_DCHECK(compositor_context_);
}
Rasterizer::~Rasterizer() = default;
fml::WeakPtr<Rasterizer> Rasterizer::GetWeakPtr() const {
return weak_factory_.GetWeakPtr();
}
void Rasterizer::Setup(std::unique_ptr<Surface> surface) {
surface_ = std::move(surface);
if (max_cache_bytes_.has_value()) {
SetResourceCacheMaxBytes(max_cache_bytes_.value(),
user_override_resource_cache_bytes_);
}
compositor_context_->OnGrContextCreated();
if (surface_->GetExternalViewEmbedder()) {
const auto platform_id =
task_runners_.GetPlatformTaskRunner()->GetTaskQueueId();
const auto gpu_id = task_runners_.GetGPUTaskRunner()->GetTaskQueueId();
gpu_thread_merger_ =
fml::MakeRefCounted<fml::GpuThreadMerger>(platform_id, gpu_id);
}
}
void Rasterizer::Teardown() {
compositor_context_->OnGrContextDestroyed();
surface_.reset();
last_layer_tree_.reset();
}
void Rasterizer::NotifyLowMemoryWarning() const {
if (!surface_) {
FML_DLOG(INFO) << "Rasterizer::PurgeCaches called with no surface.";
return;
}
auto context = surface_->GetContext();
if (!context) {
FML_DLOG(INFO) << "Rasterizer::PurgeCaches called with no GrContext.";
return;
}
context->freeGpuResources();
}
flutter::TextureRegistry* Rasterizer::GetTextureRegistry() {
return &compositor_context_->texture_registry();
}
flutter::LayerTree* Rasterizer::GetLastLayerTree() {
return last_layer_tree_.get();
}
void Rasterizer::DrawLastLayerTree() {
if (!last_layer_tree_ || !surface_) {
return;
}
DrawToSurface(*last_layer_tree_);
}
void Rasterizer::Draw(fml::RefPtr<Pipeline<flutter::LayerTree>> pipeline) {
TRACE_EVENT0("flutter", "GPURasterizer::Draw");
if (gpu_thread_merger_ && !gpu_thread_merger_->IsOnRasterizingThread()) {
return;
}
FML_DCHECK(task_runners_.GetGPUTaskRunner()->RunsTasksOnCurrentThread());
RasterStatus raster_status = RasterStatus::kFailed;
Pipeline<flutter::LayerTree>::Consumer consumer =
[&](std::unique_ptr<LayerTree> layer_tree) {
raster_status = DoDraw(std::move(layer_tree));
};
PipelineConsumeResult consume_result = pipeline->Consume(consumer);
if (raster_status == RasterStatus::kResubmit) {
auto front_continuation = pipeline->ProduceToFront();
front_continuation.Complete(std::move(resubmitted_layer_tree_));
consume_result = PipelineConsumeResult::MoreAvailable;
} else if (raster_status == RasterStatus::kEnqueuePipeline) {
consume_result = PipelineConsumeResult::MoreAvailable;
}
switch (consume_result) {
case PipelineConsumeResult::MoreAvailable: {
task_runners_.GetGPUTaskRunner()->PostTask(
[weak_this = weak_factory_.GetWeakPtr(), pipeline]() {
if (weak_this) {
weak_this->Draw(pipeline);
}
});
break;
}
default:
break;
}
}
RasterStatus Rasterizer::DoDraw(
std::unique_ptr<flutter::LayerTree> layer_tree) {
FML_DCHECK(task_runners_.GetGPUTaskRunner()->RunsTasksOnCurrentThread());
if (!layer_tree || !surface_) {
return RasterStatus::kFailed;
}
FrameTiming timing;
timing.Set(FrameTiming::kBuildStart, layer_tree->build_start());
timing.Set(FrameTiming::kBuildFinish, layer_tree->build_finish());
timing.Set(FrameTiming::kRasterStart, fml::TimePoint::Now());
PersistentCache* persistent_cache = PersistentCache::GetCacheForProcess();
persistent_cache->ResetStoredNewShaders();
RasterStatus raster_status = DrawToSurface(*layer_tree);
if (raster_status == RasterStatus::kSuccess) {
last_layer_tree_ = std::move(layer_tree);
} else if (raster_status == RasterStatus::kResubmit) {
resubmitted_layer_tree_ = std::move(layer_tree);
return raster_status;
}
if (persistent_cache->IsDumpingSkp() &&
persistent_cache->StoredNewShaders()) {
auto screenshot =
ScreenshotLastLayerTree(ScreenshotType::SkiaPicture, false);
persistent_cache->DumpSkp(*screenshot.data);
}
timing.Set(FrameTiming::kRasterFinish, fml::TimePoint::Now());
delegate_.OnFrameRasterized(timing);
if (gpu_thread_merger_) {
if (gpu_thread_merger_->DecrementLease() ==
fml::GpuThreadStatus::kUnmergedNow) {
return RasterStatus::kEnqueuePipeline;
}
}
return raster_status;
}
RasterStatus Rasterizer::DrawToSurface(flutter::LayerTree& layer_tree) {
FML_DCHECK(surface_);
auto frame = surface_->AcquireFrame(layer_tree.frame_size());
if (frame == nullptr) {
return RasterStatus::kFailed;
}
compositor_context_->ui_time().SetLapTime(layer_tree.build_time());
auto* external_view_embedder = surface_->GetExternalViewEmbedder();
sk_sp<SkSurface> embedder_root_surface;
if (external_view_embedder != nullptr) {
external_view_embedder->BeginFrame(layer_tree.frame_size(),
surface_->GetContext());
embedder_root_surface = external_view_embedder->GetRootSurface();
}
SkMatrix root_surface_transformation =
embedder_root_surface ? SkMatrix{} : surface_->GetRootTransformation();
auto root_surface_canvas = embedder_root_surface
? embedder_root_surface->getCanvas()
: frame->SkiaCanvas();
auto compositor_frame = compositor_context_->AcquireFrame(
surface_->GetContext(),
root_surface_canvas,
external_view_embedder,
root_surface_transformation,
true,
gpu_thread_merger_
);
if (compositor_frame) {
RasterStatus raster_status = compositor_frame->Raster(layer_tree, false);
if (raster_status == RasterStatus::kFailed) {
return raster_status;
}
frame->Submit();
if (external_view_embedder != nullptr) {
external_view_embedder->SubmitFrame(surface_->GetContext());
}
FireNextFrameCallbackIfPresent();
#ifndef GPU_DISABLED
if (surface_->GetContext()) {
surface_->GetContext()->performDeferredCleanup(kSkiaCleanupExpiration);
}
#endif
return raster_status;
}
return RasterStatus::kFailed;
}
static sk_sp<SkData> SerializeTypeface(SkTypeface* typeface, void* ctx) {
return typeface->serialize(SkTypeface::SerializeBehavior::kDoIncludeData);
}
static sk_sp<SkData> ScreenshotLayerTreeAsPicture(
flutter::LayerTree* tree,
flutter::CompositorContext& compositor_context) {
FML_DCHECK(tree != nullptr);
SkPictureRecorder recorder;
recorder.beginRecording(
SkRect::MakeWH(tree->frame_size().width(), tree->frame_size().height()));
SkMatrix root_surface_transformation;
root_surface_transformation.reset();
auto frame = compositor_context.AcquireFrame(
nullptr, recorder.getRecordingCanvas(), nullptr,
root_surface_transformation, false, nullptr);
frame->Raster(*tree, true);
SkSerialProcs procs = {0};
procs.fTypefaceProc = SerializeTypeface;
return recorder.finishRecordingAsPicture()->serialize(&procs);
}
static sk_sp<SkSurface> CreateSnapshotSurface(GrContext* surface_context,
const SkISize& size) {
const auto image_info = SkImageInfo::MakeN32Premul(
size.width(), size.height(), SkColorSpace::MakeSRGB());
if (surface_context) {
return SkSurface::MakeRenderTarget(surface_context,
SkBudgeted::kNo,
image_info
);
}
return SkSurface::MakeRaster(image_info);
}
static sk_sp<SkData> ScreenshotLayerTreeAsImage(
flutter::LayerTree* tree,
flutter::CompositorContext& compositor_context,
GrContext* surface_context,
bool compressed) {
auto snapshot_surface =
CreateSnapshotSurface(surface_context, tree->frame_size());
if (snapshot_surface == nullptr) {
FML_LOG(ERROR) << "Screenshot: unable to create snapshot surface";
return nullptr;
}
auto* canvas = snapshot_surface->getCanvas();
SkMatrix root_surface_transformation;
root_surface_transformation.reset();
auto frame = compositor_context.AcquireFrame(surface_context, canvas, nullptr,
root_surface_transformation,
false, nullptr);
canvas->clear(SK_ColorTRANSPARENT);
frame->Raster(*tree, true);
canvas->flush();
auto potentially_gpu_snapshot = snapshot_surface->makeImageSnapshot();
if (!potentially_gpu_snapshot) {
FML_LOG(ERROR) << "Screenshot: unable to make image screenshot";
return nullptr;
}
auto cpu_snapshot = potentially_gpu_snapshot->makeRasterImage();
if (!cpu_snapshot) {
FML_LOG(ERROR) << "Screenshot: unable to make raster image";
return nullptr;
}
if (compressed) {
return cpu_snapshot->encodeToData();
}
SkPixmap pixmap;
if (!cpu_snapshot->peekPixels(&pixmap)) {
FML_LOG(ERROR) << "Screenshot: unable to obtain bitmap pixels";
return nullptr;
}
return SkData::MakeWithCopy(pixmap.addr32(), pixmap.computeByteSize());
}
Rasterizer::Screenshot Rasterizer::ScreenshotLastLayerTree(
Rasterizer::ScreenshotType type,
bool base64_encode) {
auto* layer_tree = GetLastLayerTree();
if (layer_tree == nullptr) {
FML_LOG(ERROR) << "Last layer tree was null when screenshotting.";
return {};
}
sk_sp<SkData> data = nullptr;
GrContext* surface_context = surface_ ? surface_->GetContext() : nullptr;
switch (type) {
case ScreenshotType::SkiaPicture:
data = ScreenshotLayerTreeAsPicture(layer_tree, *compositor_context_);
break;
case ScreenshotType::UncompressedImage:
data = ScreenshotLayerTreeAsImage(layer_tree, *compositor_context_,
surface_context, false);
break;
case ScreenshotType::CompressedImage:
data = ScreenshotLayerTreeAsImage(layer_tree, *compositor_context_,
surface_context, true);
break;
}
if (data == nullptr) {
FML_LOG(ERROR) << "Screenshot data was null.";
return {};
}
if (base64_encode) {
size_t b64_size = SkBase64::Encode(data->data(), data->size(), nullptr);
auto b64_data = SkData::MakeUninitialized(b64_size);
SkBase64::Encode(data->data(), data->size(), b64_data->writable_data());
return Rasterizer::Screenshot{b64_data, layer_tree->frame_size()};
}
return Rasterizer::Screenshot{data, layer_tree->frame_size()};
}
void Rasterizer::SetNextFrameCallback(fml::closure callback) {
next_frame_callback_ = callback;
}
void Rasterizer::FireNextFrameCallbackIfPresent() {
if (!next_frame_callback_) {
return;
}
auto callback = next_frame_callback_;
next_frame_callback_ = nullptr;
callback();
}
void Rasterizer::SetResourceCacheMaxBytes(size_t max_bytes, bool from_user) {
user_override_resource_cache_bytes_ |= from_user;
if (!from_user && user_override_resource_cache_bytes_) {
return;
}
max_cache_bytes_ = max_bytes;
if (!surface_) {
return;
}
#ifndef GPU_DISABLED
GrContext* context = surface_->GetContext();
if (context) {
int max_resources;
context->getResourceCacheLimits(&max_resources, nullptr);
context->setResourceCacheLimits(max_resources, max_bytes);
}
#endif
}
std::optional<size_t> Rasterizer::GetResourceCacheMaxBytes() const {
if (!surface_) {
return std::nullopt;
}
#ifndef GPU_DISABLED
GrContext* context = surface_->GetContext();
if (context) {
size_t max_bytes;
context->getResourceCacheLimits(nullptr, &max_bytes);
return max_bytes;
}
#endif
return std::nullopt;
}
Rasterizer::Screenshot::Screenshot() {}
Rasterizer::Screenshot::Screenshot(sk_sp<SkData> p_data, SkISize p_size)
: data(std::move(p_data)), frame_size(p_size) {}
Rasterizer::Screenshot::Screenshot(const Screenshot& other) = default;
Rasterizer::Screenshot::~Screenshot() = default;
}