blob: d1757bc616157545e8d4d6b7e0e34fcbfb8a5373 [file]
// 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/lib/gpu/surface.h"
#include <cstdint>
#include "flutter/lib/gpu/formats.h"
#include "flutter/lib/ui/painting/image.h"
#include "fml/logging.h"
#include "impeller/core/allocator.h"
#include "tonic/converter/dart_converter.h"
#if IMPELLER_SUPPORTS_RENDERING
#include "impeller/display_list/dl_image_impeller.h" // nogncheck
#endif
namespace flutter {
namespace gpu {
IMPLEMENT_WRAPPERTYPEINFO(flutter_gpu, Surface);
namespace {
constexpr int64_t kMaxInternalTextureReferences = 3;
} // namespace
Surface::TextureRecord::TextureRecord(
std::shared_ptr<impeller::Texture> texture,
sk_sp<DlImage> image,
impeller::ISize size,
impeller::PixelFormat format)
: texture(std::move(texture)),
image(std::move(image)),
size(size),
format(format) {}
Surface::Surface(std::shared_ptr<impeller::Context> context,
impeller::ISize size,
impeller::PixelFormat format)
: context_(std::move(context)), size_(size), format_(format) {}
Surface::~Surface() = default;
std::shared_ptr<Surface::TextureRecord> Surface::CreateTextureRecord() const {
#if !IMPELLER_SUPPORTS_RENDERING
FML_LOG(ERROR) << "Flutter GPU surfaces require Impeller rendering support.";
return nullptr;
#else
impeller::TextureDescriptor desc;
desc.storage_mode = impeller::StorageMode::kDevicePrivate;
desc.size = size_;
desc.format = format_;
desc.sample_count = impeller::SampleCount::kCount1;
desc.type = impeller::TextureType::kTexture2D;
desc.mip_count = 1;
desc.usage = {};
desc.usage |= impeller::TextureUsage::kRenderTarget;
desc.usage |= impeller::TextureUsage::kShaderRead;
auto texture = context_->GetResourceAllocator()->CreateTexture(desc, true);
if (!texture) {
FML_LOG(ERROR) << "Failed to create Flutter GPU surface texture.";
return nullptr;
}
auto image = impeller::DlImageImpeller::Make(texture);
if (!image) {
FML_LOG(ERROR) << "Failed to create Flutter GPU surface image.";
return nullptr;
}
return std::make_shared<TextureRecord>(std::move(texture), std::move(image),
size_, format_);
#endif
}
bool Surface::IsReusable(const std::shared_ptr<TextureRecord>& record,
size_t index) const {
if (current_index_.has_value() && current_index_.value() == index) {
return false;
}
return record && record->size.width == size_.width &&
record->size.height == size_.height && record->format == format_ &&
!record->acquired && !record->producer_pending.load() &&
record->image && record->image->unique() &&
static_cast<int64_t>(record->texture.use_count()) <=
kMaxInternalTextureReferences;
}
void Surface::PruneTextureRecords() {
for (size_t i = 0; i < records_.size(); i++) {
auto& record = records_[i];
if (!record) {
continue;
}
const bool is_current =
current_index_.has_value() && current_index_.value() == i;
const bool matches_surface = record->size.width == size_.width &&
record->size.height == size_.height &&
record->format == format_;
// The surface record, its image, and the Dart texture wrapper handed out
// for the completed frame may all still reference an otherwise
// unreferenced texture.
const bool has_external_references =
!record->image || !record->image->unique() ||
static_cast<int64_t>(record->texture.use_count()) >
kMaxInternalTextureReferences;
if (!is_current && !matches_surface && !record->acquired &&
!record->producer_pending.load() && !has_external_references) {
record.reset();
}
}
while (!records_.empty() && !records_.back()) {
records_.pop_back();
}
}
int Surface::AcquireNextFrame(Dart_Handle texture_wrapper) {
std::optional<size_t> available_index;
size_t index = 0;
for (const auto& record : records_) {
if (!record && !available_index.has_value()) {
available_index = index;
}
if (IsReusable(record, index)) {
record->acquired = true;
auto texture = fml::MakeRefCounted<Texture>(record->texture);
texture->AssociateWithDartWrapper(texture_wrapper);
return static_cast<int>(index);
}
index++;
}
auto record = CreateTextureRecord();
if (!record) {
return -1;
}
record->acquired = true;
if (available_index.has_value()) {
records_[available_index.value()] = record;
index = available_index.value();
} else {
records_.push_back(record);
index = records_.size() - 1u;
}
auto texture = fml::MakeRefCounted<Texture>(record->texture);
texture->AssociateWithDartWrapper(texture_wrapper);
return static_cast<int>(index);
}
Dart_Handle Surface::CreateImage(const sk_sp<DlImage>& image) const {
if (!image) {
return Dart_Null();
}
auto canvas_image = CanvasImage::Create();
canvas_image->set_image(image);
return canvas_image->CreateOuterWrapping();
}
Dart_Handle Surface::PresentFrame(size_t texture_index,
CommandBuffer& command_buffer) {
if (texture_index >= records_.size()) {
return tonic::ToDart("SurfaceFrame does not belong to this GpuSurface.");
}
auto record = records_[texture_index];
if (!record || !record->acquired) {
return tonic::ToDart(
"SurfaceFrame has already been presented or discarded.");
}
record->producer_pending.store(true);
if (!command_buffer.AddCompletionCallback(
[record]([[maybe_unused]] impeller::CommandBuffer::Status status) {
record->producer_pending.store(false);
})) {
record->producer_pending.store(false);
return tonic::ToDart(
"SurfaceFrame.present must be called before submitting the command "
"buffer.");
}
record->acquired = false;
current_index_ = texture_index;
PruneTextureRecords();
// The presented image is read separately via GetCurrentImage, so avoid
// allocating an image wrapper here on every presented frame.
return Dart_Null();
}
void Surface::DiscardFrame(size_t texture_index) {
if (texture_index >= records_.size()) {
return;
}
auto record = records_[texture_index];
if (record) {
record->acquired = false;
}
}
Dart_Handle Surface::GetCurrentImage() const {
if (!current_index_.has_value() ||
current_index_.value() >= records_.size()) {
return Dart_Null();
}
auto record = records_[current_index_.value()];
if (!record) {
return Dart_Null();
}
return CreateImage(record->image);
}
std::optional<std::string> Surface::Resize(impeller::ISize size) {
for (const auto& record : records_) {
if (record && record->acquired) {
return "GpuSurface.resize cannot be called while a SurfaceFrame is "
"acquired.";
}
}
size_ = size;
PruneTextureRecords();
return std::nullopt;
}
size_t Surface::GetBackingTextureCount() const {
size_t count = 0;
for (const auto& record : records_) {
if (record) {
count++;
}
}
return count;
}
} // namespace gpu
} // namespace flutter
//----------------------------------------------------------------------------
/// Exports
///
Dart_Handle InternalFlutterGpu_Surface_Initialize(
[[maybe_unused]] Dart_Handle wrapper,
[[maybe_unused]] flutter::gpu::Context* gpu_context,
int width,
int height,
[[maybe_unused]] int format) {
if (width <= 0 || height <= 0) {
return tonic::ToDart("GpuSurface dimensions must be greater than zero.");
}
#if !IMPELLER_SUPPORTS_RENDERING
return tonic::ToDart("GpuSurface requires Impeller rendering support.");
#else
auto pixel_format = flutter::gpu::ToImpellerPixelFormat(format);
if (pixel_format == impeller::PixelFormat::kUnknown) {
return tonic::ToDart("Unsupported GpuSurface pixel format.");
}
auto res = fml::MakeRefCounted<flutter::gpu::Surface>(
gpu_context->GetContextShared(), impeller::ISize{width, height},
pixel_format);
res->AssociateWithDartWrapper(wrapper);
return Dart_Null();
#endif
}
int InternalFlutterGpu_Surface_AcquireNextFrame(flutter::gpu::Surface* wrapper,
Dart_Handle texture_wrapper) {
return wrapper->AcquireNextFrame(texture_wrapper);
}
Dart_Handle InternalFlutterGpu_Surface_PresentFrame(
flutter::gpu::Surface* wrapper,
int texture_index,
flutter::gpu::CommandBuffer* command_buffer) {
if (texture_index < 0) {
return tonic::ToDart("Invalid SurfaceFrame texture index.");
}
return wrapper->PresentFrame(static_cast<size_t>(texture_index),
*command_buffer);
}
void InternalFlutterGpu_Surface_DiscardFrame(flutter::gpu::Surface* wrapper,
int texture_index) {
if (texture_index < 0) {
return;
}
wrapper->DiscardFrame(static_cast<size_t>(texture_index));
}
Dart_Handle InternalFlutterGpu_Surface_GetCurrentImage(
flutter::gpu::Surface* wrapper) {
return wrapper->GetCurrentImage();
}
Dart_Handle InternalFlutterGpu_Surface_Resize(flutter::gpu::Surface* wrapper,
int width,
int height) {
if (width <= 0 || height <= 0) {
return tonic::ToDart("GpuSurface dimensions must be greater than zero.");
}
auto error = wrapper->Resize(impeller::ISize{width, height});
if (error.has_value()) {
return tonic::ToDart(error.value());
}
return Dart_Null();
}
int InternalFlutterGpu_Surface_GetBackingTextureCount(
flutter::gpu::Surface* wrapper) {
return static_cast<int>(wrapper->GetBackingTextureCount());
}