[Impeller] kick off registration and initial PSO compilation of runtime effect earlier. (flutter/engine#52381)

I thought about changing this API so that it blocks on compilation, but I think that isn't necesasary as the workload can happen while the UI thread is building. Plus, even blocking on shader completion does not guarantee that we won't need to compile a variant anyway.

While I was at it I moved the descriptor sets computation to the runtime effect constructor.

With this change the pipleine variant used during the frame is produced much faster than the initial 12ms compilation.

![image](https://github.com/flutter/engine/assets/8975114/42626676-0a71-4503-a3e9-4109c36fbbef)

Fixes https://github.com/flutter/flutter/issues/113719
Fixes https://github.com/flutter/flutter/issues/141222
This commit is contained in:
Jonah Williams
2024-04-25 13:23:56 -07:00
committed by GitHub
parent b14463a57b
commit 9dbe934a9b
18 changed files with 208 additions and 98 deletions

View File

@@ -348,6 +348,8 @@ std::shared_ptr<RuntimeStageData::Shader> Reflector::GenerateRuntimeStageData()
uniform_description.name = compiler_->get_name(var.self);
uniform_description.location = compiler_->get_decoration(
var.self, spv::Decoration::DecorationLocation);
uniform_description.binding =
compiler_->get_decoration(var.self, spv::Decoration::DecorationBinding);
uniform_description.type = spir_type.basetype;
uniform_description.rows = spir_type.vecsize;
uniform_description.columns = spir_type.columns;
@@ -410,6 +412,7 @@ std::shared_ptr<RuntimeStageData::Shader> Reflector::GenerateRuntimeStageData()
.name = ubo.name,
.location = 64, // Magic constant that must match the descriptor set
// location for fragment programs.
.binding = 64,
.type = spirv_cross::SPIRType::Struct,
.struct_layout = std::move(struct_layout),
.struct_float_count = float_count,

View File

@@ -48,6 +48,7 @@ enum class SourceLanguage {
struct UniformDescription {
std::string name;
size_t location = 0u;
size_t binding = 0u;
spirv_cross::SPIRType::BaseType type = spirv_cross::SPIRType::BaseType::Float;
size_t rows = 0u;
size_t columns = 0u;

View File

@@ -39,6 +39,8 @@ struct RuntimeUniformDimensions {
struct RuntimeUniformDescription {
std::string name;
size_t location = 0u;
/// Location, but for Vulkan.
size_t binding = 0u;
RuntimeUniformType type = RuntimeUniformType::kFloat;
RuntimeUniformDimensions dimensions = {};
size_t bit_width = 0u;

View File

@@ -4,6 +4,7 @@
#include "impeller/entity/contents/runtime_effect_contents.h"
#include <algorithm>
#include <future>
#include <memory>
@@ -19,6 +20,7 @@
#include "impeller/renderer/pipeline_library.h"
#include "impeller/renderer/render_pass.h"
#include "impeller/renderer/shader_function.h"
#include "impeller/renderer/vertex_descriptor.h"
namespace impeller {
@@ -65,18 +67,22 @@ static std::shared_ptr<ShaderMetadata> MakeShaderMetadata(
return metadata;
}
bool RuntimeEffectContents::Render(const ContentContext& renderer,
const Entity& entity,
RenderPass& pass) const {
bool RuntimeEffectContents::BootstrapShader(
const ContentContext& renderer) const {
if (!RegisterShader(renderer)) {
return false;
}
ContentContextOptions options;
options.color_attachment_pixel_format =
renderer.GetContext()->GetCapabilities()->GetDefaultColorFormat();
return !!CreatePipeline(renderer, options);
}
bool RuntimeEffectContents::RegisterShader(
const ContentContext& renderer) const {
const std::shared_ptr<Context>& context = renderer.GetContext();
const std::shared_ptr<ShaderLibrary>& library = context->GetShaderLibrary();
//--------------------------------------------------------------------------
/// Get or register shader.
///
// TODO(113719): Register the shader function earlier.
std::shared_ptr<const ShaderFunction> function = library->GetFunction(
runtime_stage_->GetEntrypoint(), ShaderStage::kFragment);
@@ -123,29 +129,75 @@ bool RuntimeEffectContents::Render(const ContentContext& renderer,
runtime_stage_->SetClean();
}
return true;
}
//--------------------------------------------------------------------------
/// Set up the command. Defer setting up the pipeline until the descriptor set
/// layouts are known from the uniforms.
///
std::shared_ptr<Pipeline<PipelineDescriptor>>
RuntimeEffectContents::CreatePipeline(const ContentContext& renderer,
ContentContextOptions options) const {
const std::shared_ptr<Context>& context = renderer.GetContext();
const std::shared_ptr<ShaderLibrary>& library = context->GetShaderLibrary();
const std::shared_ptr<const Capabilities>& caps = context->GetCapabilities();
const auto color_attachment_format = caps->GetDefaultColorFormat();
const auto stencil_attachment_format = caps->GetDefaultDepthStencilFormat();
using VS = RuntimeEffectVertexShader;
PipelineDescriptor desc;
desc.SetLabel("Runtime Stage");
desc.AddStageEntrypoint(
library->GetFunction(VS::kEntrypointName, ShaderStage::kVertex));
desc.AddStageEntrypoint(library->GetFunction(runtime_stage_->GetEntrypoint(),
ShaderStage::kFragment));
std::shared_ptr<VertexDescriptor> vertex_descriptor =
std::make_shared<VertexDescriptor>();
vertex_descriptor->SetStageInputs(VS::kAllShaderStageInputs,
VS::kInterleavedBufferLayout);
vertex_descriptor->RegisterDescriptorSetLayouts(VS::kDescriptorSetLayouts);
vertex_descriptor->RegisterDescriptorSetLayouts(
runtime_stage_->GetDescriptorSetLayouts().data(),
runtime_stage_->GetDescriptorSetLayouts().size());
desc.SetVertexDescriptor(std::move(vertex_descriptor));
desc.SetColorAttachmentDescriptor(
0u, {.format = color_attachment_format, .blending_enabled = true});
desc.SetStencilAttachmentDescriptors(StencilAttachmentDescriptor{});
desc.SetStencilPixelFormat(stencil_attachment_format);
desc.SetDepthStencilAttachmentDescriptor(DepthAttachmentDescriptor{});
desc.SetDepthPixelFormat(stencil_attachment_format);
options.ApplyToPipelineDescriptor(desc);
auto pipeline = context->GetPipelineLibrary()->GetPipeline(desc).Get();
if (!pipeline) {
VALIDATION_LOG << "Failed to get or create runtime effect pipeline.";
return nullptr;
}
return pipeline;
}
bool RuntimeEffectContents::Render(const ContentContext& renderer,
const Entity& entity,
RenderPass& pass) const {
const std::shared_ptr<Context>& context = renderer.GetContext();
const std::shared_ptr<ShaderLibrary>& library = context->GetShaderLibrary();
//--------------------------------------------------------------------------
/// Get or register shader. Flutter will do this when the runtime effect
/// is first loaded, but this check is added to supporting testing of the
/// Aiks API and non-flutter usage of Impeller.
///
if (!RegisterShader(renderer)) {
return false;
}
//--------------------------------------------------------------------------
/// Fragment stage uniforms.
///
std::vector<DescriptorSetLayout> descriptor_set_layouts;
BindFragmentCallback bind_callback = [this, &renderer, &context,
&descriptor_set_layouts](
RenderPass& pass) {
descriptor_set_layouts.clear();
BindFragmentCallback bind_callback = [this, &renderer,
&context](RenderPass& pass) {
size_t minimum_sampler_index = 100000000;
size_t buffer_index = 0;
size_t buffer_offset = 0;
@@ -173,9 +225,10 @@ bool RuntimeEffectContents::Render(const ContentContext& renderer,
Context::BackendType::kVulkan)
<< "Uniform " << uniform.name
<< " had unexpected type kFloat for Vulkan backend.";
size_t alignment =
std::max(uniform.bit_width / 8, DefaultUniformAlignment());
auto buffer_view = renderer.GetTransientsBuffer().Emplace(
BufferView buffer_view = renderer.GetTransientsBuffer().Emplace(
uniform_data_->data() + buffer_offset, uniform.GetSize(),
alignment);
@@ -184,7 +237,7 @@ bool RuntimeEffectContents::Render(const ContentContext& renderer,
uniform_slot.ext_res_0 = uniform.location;
pass.BindResource(ShaderStage::kFragment,
DescriptorType::kUniformBuffer, uniform_slot,
metadata, buffer_view);
metadata, std::move(buffer_view));
buffer_index++;
buffer_offset += uniform.GetSize();
break;
@@ -192,15 +245,12 @@ bool RuntimeEffectContents::Render(const ContentContext& renderer,
case kStruct: {
FML_DCHECK(renderer.GetContext()->GetBackendType() ==
Context::BackendType::kVulkan);
descriptor_set_layouts.emplace_back(DescriptorSetLayout{
static_cast<uint32_t>(uniform.location),
DescriptorType::kUniformBuffer,
ShaderStage::kFragment,
});
ShaderUniformSlot uniform_slot;
uniform_slot.name = uniform.name.c_str();
uniform_slot.binding = uniform.location;
// TODO(jonahwilliams): rewrite this to emplace directly into
// HostBuffer.
std::vector<float> uniform_buffer;
uniform_buffer.reserve(uniform.struct_layout.size());
size_t uniform_byte_index = 0u;
@@ -214,16 +264,15 @@ bool RuntimeEffectContents::Render(const ContentContext& renderer,
FML_UNREACHABLE();
}
}
size_t alignment = std::max(sizeof(float) * uniform_buffer.size(),
DefaultUniformAlignment());
auto buffer_view = renderer.GetTransientsBuffer().Emplace(
BufferView buffer_view = renderer.GetTransientsBuffer().Emplace(
reinterpret_cast<const void*>(uniform_buffer.data()),
sizeof(float) * uniform_buffer.size(), alignment);
pass.BindResource(ShaderStage::kFragment,
DescriptorType::kUniformBuffer, uniform_slot,
ShaderMetadata{}, buffer_view);
ShaderMetadata{}, std::move(buffer_view));
}
}
}
@@ -243,20 +292,7 @@ bool RuntimeEffectContents::Render(const ContentContext& renderer,
SampledImageSlot image_slot;
image_slot.name = uniform.name.c_str();
uint32_t sampler_binding_location = 0u;
if (!descriptor_set_layouts.empty()) {
sampler_binding_location =
descriptor_set_layouts.back().binding + 1;
}
descriptor_set_layouts.emplace_back(DescriptorSetLayout{
sampler_binding_location,
DescriptorType::kSampledImage,
ShaderStage::kFragment,
});
image_slot.binding = sampler_binding_location;
image_slot.binding = uniform.binding;
image_slot.texture_index = uniform.location - minimum_sampler_index;
pass.BindResource(ShaderStage::kFragment,
DescriptorType::kSampledImage, image_slot,
@@ -273,47 +309,15 @@ bool RuntimeEffectContents::Render(const ContentContext& renderer,
};
/// Now that the descriptor set layouts are known, get the pipeline.
using VS = RuntimeEffectVertexShader;
PipelineBuilderCallback pipeline_callback = [&](ContentContextOptions
options) {
// Pipeline creation callback for the cache handler to call.
auto create_callback =
[&]() -> std::shared_ptr<Pipeline<PipelineDescriptor>> {
PipelineDescriptor desc;
desc.SetLabel("Runtime Stage");
desc.AddStageEntrypoint(
library->GetFunction(VS::kEntrypointName, ShaderStage::kVertex));
desc.AddStageEntrypoint(library->GetFunction(
runtime_stage_->GetEntrypoint(), ShaderStage::kFragment));
auto vertex_descriptor = std::make_shared<VertexDescriptor>();
vertex_descriptor->SetStageInputs(VS::kAllShaderStageInputs,
VS::kInterleavedBufferLayout);
vertex_descriptor->RegisterDescriptorSetLayouts(
VS::kDescriptorSetLayouts);
vertex_descriptor->RegisterDescriptorSetLayouts(
descriptor_set_layouts.data(), descriptor_set_layouts.size());
desc.SetVertexDescriptor(std::move(vertex_descriptor));
desc.SetColorAttachmentDescriptor(
0u, {.format = color_attachment_format, .blending_enabled = true});
desc.SetStencilAttachmentDescriptors(StencilAttachmentDescriptor{});
desc.SetStencilPixelFormat(stencil_attachment_format);
desc.SetDepthStencilAttachmentDescriptor(DepthAttachmentDescriptor{});
desc.SetDepthPixelFormat(stencil_attachment_format);
options.ApplyToPipelineDescriptor(desc);
auto pipeline = context->GetPipelineLibrary()->GetPipeline(desc).Get();
if (!pipeline) {
VALIDATION_LOG << "Failed to get or create runtime effect pipeline.";
return nullptr;
}
return pipeline;
};
return renderer.GetCachedRuntimeEffectPipeline(
runtime_stage_->GetEntrypoint(), options, create_callback);
};
PipelineBuilderCallback pipeline_callback =
[&](ContentContextOptions options) {
// Pipeline creation callback for the cache handler to call.
return renderer.GetCachedRuntimeEffectPipeline(
runtime_stage_->GetEntrypoint(), options,
[&]() { return CreatePipeline(renderer, options); });
};
return ColorSourceContents::DrawGeometry<VS>(renderer, entity, pass,
pipeline_callback,

View File

@@ -35,7 +35,16 @@ class RuntimeEffectContents final : public ColorSourceContents {
const Entity& entity,
RenderPass& pass) const override;
/// Load the runtime effect and ensure a default PSO is initialized.
bool BootstrapShader(const ContentContext& renderer) const;
private:
bool RegisterShader(const ContentContext& renderer) const;
std::shared_ptr<Pipeline<PipelineDescriptor>> CreatePipeline(
const ContentContext& renderer,
ContentContextOptions options) const;
std::shared_ptr<RuntimeStage> runtime_stage_;
std::shared_ptr<std::vector<uint8_t>> uniform_data_;
std::vector<TextureInput> texture_inputs_;

View File

@@ -2242,6 +2242,20 @@ TEST_P(EntityTest, RuntimeEffectCanSuccessfullyRender) {
.has_value());
}
TEST_P(EntityTest, RuntimeEffectCanPrecache) {
auto runtime_stages =
OpenAssetAsRuntimeStage("runtime_stage_example.frag.iplr");
auto runtime_stage =
runtime_stages[PlaygroundBackendToRuntimeStageBackend(GetBackend())];
ASSERT_TRUE(runtime_stage);
ASSERT_TRUE(runtime_stage->IsDirty());
auto contents = std::make_shared<RuntimeEffectContents>();
contents->SetRuntimeStage(runtime_stage);
EXPECT_TRUE(contents->BootstrapShader(*GetContentContext()));
}
TEST_P(EntityTest, RuntimeEffectSetsRightSizeWhenUniformIsStruct) {
if (GetBackend() != PlaygroundBackend::kVulkan) {
GTEST_SKIP() << "Test only applies to Vulkan";

View File

@@ -10,6 +10,7 @@
#include "fml/mapping.h"
#include "impeller/base/validation.h"
#include "impeller/core/runtime_types.h"
#include "impeller/core/shader_types.h"
#include "impeller/runtime_stage/runtime_stage_flatbuffers.h"
#include "runtime_stage_types_flatbuffers.h"
@@ -93,6 +94,7 @@ RuntimeStage::RuntimeStage(const fb::RuntimeStage* runtime_stage,
RuntimeUniformDescription desc;
desc.name = i->name()->str();
desc.location = i->location();
desc.binding = i->binding();
desc.type = ToType(i->type());
desc.dimensions = RuntimeUniformDimensions{
static_cast<size_t>(i->rows()), static_cast<size_t>(i->columns())};
@@ -103,8 +105,9 @@ RuntimeStage::RuntimeStage(const fb::RuntimeStage* runtime_stage,
desc.struct_layout.push_back(static_cast<uint8_t>(byte_type));
}
}
desc.binding = i->binding();
desc.struct_float_count = i->struct_float_count();
uniforms_.emplace_back(std::move(desc));
uniforms_.push_back(std::move(desc));
}
}
@@ -114,6 +117,22 @@ RuntimeStage::RuntimeStage(const fb::RuntimeStage* runtime_stage,
[payload = payload_](auto, auto) {} //
);
for (const auto& uniform : GetUniforms()) {
if (uniform.type == kStruct) {
descriptor_set_layouts_.push_back(DescriptorSetLayout{
static_cast<uint32_t>(uniform.location),
DescriptorType::kUniformBuffer,
ShaderStage::kFragment,
});
} else if (uniform.type == kSampledImage) {
descriptor_set_layouts_.push_back(DescriptorSetLayout{
static_cast<uint32_t>(uniform.binding),
DescriptorType::kSampledImage,
ShaderStage::kFragment,
});
}
}
is_valid_ = true;
}
@@ -160,4 +179,9 @@ void RuntimeStage::SetClean() {
is_dirty_ = false;
}
const std::vector<DescriptorSetLayout>& RuntimeStage::GetDescriptorSetLayouts()
const {
return descriptor_set_layouts_;
}
} // namespace impeller

View File

@@ -12,6 +12,7 @@
#include "flutter/fml/mapping.h"
#include "flutter/impeller/core/runtime_types.h"
#include "impeller/core/shader_types.h"
#include "runtime_stage_types_flatbuffers.h"
namespace impeller {
@@ -35,6 +36,8 @@ class RuntimeStage {
const std::vector<RuntimeUniformDescription>& GetUniforms() const;
const std::vector<DescriptorSetLayout>& GetDescriptorSetLayouts() const;
const std::string& GetEntrypoint() const;
const RuntimeUniformDescription* GetUniform(const std::string& name) const;
@@ -51,6 +54,7 @@ class RuntimeStage {
std::string entrypoint_;
std::shared_ptr<fml::Mapping> code_mapping_;
std::vector<RuntimeUniformDescription> uniforms_;
std::vector<DescriptorSetLayout> descriptor_set_layouts_;
bool is_valid_ = false;
bool is_dirty_ = true;

View File

@@ -30,6 +30,7 @@ enum StructByteType:uint8 {
table UniformDescription {
name: string;
location: uint64;
binding: uint64;
type: UniformDataType;
bit_width: uint64;
rows: uint64;

View File

@@ -11,17 +11,12 @@
#include "flutter/assets/asset_manager.h"
#include "flutter/fml/trace_event.h"
#include "flutter/impeller/runtime_stage/runtime_stage.h"
#include "flutter/lib/ui/dart_wrapper.h"
#include "flutter/lib/ui/ui_dart_state.h"
#include "flutter/lib/ui/window/platform_configuration.h"
#include "impeller/core/runtime_types.h"
#include "third_party/skia/include/core/SkString.h"
#include "third_party/tonic/converter/dart_converter.h"
#include "third_party/tonic/dart_args.h"
#include "third_party/tonic/dart_binding_macros.h"
#include "third_party/tonic/dart_library_natives.h"
#include "third_party/tonic/typed_data/typed_list.h"
namespace flutter {
@@ -44,10 +39,10 @@ static std::string RuntimeStageBackendToString(
std::string FragmentProgram::initFromAsset(const std::string& asset_name) {
FML_TRACE_EVENT("flutter", "FragmentProgram::initFromAsset", "asset",
asset_name);
std::shared_ptr<AssetManager> asset_manager = UIDartState::Current()
->platform_configuration()
->client()
->GetAssetManager();
UIDartState* ui_dart_state = UIDartState::Current();
std::shared_ptr<AssetManager> asset_manager =
ui_dart_state->platform_configuration()->client()->GetAssetManager();
std::unique_ptr<fml::Mapping> data = asset_manager->GetAsMapping(asset_name);
if (data == nullptr) {
return std::string("Asset '") + asset_name + std::string("' not found");
@@ -61,8 +56,10 @@ std::string FragmentProgram::initFromAsset(const std::string& asset_name) {
std::string("' does not contain any shader data.");
}
auto backend = UIDartState::Current()->GetRuntimeStageBackend();
auto runtime_stage = runtime_stages[backend];
impeller::RuntimeStageBackend backend =
ui_dart_state->GetRuntimeStageBackend();
std::shared_ptr<impeller::RuntimeStage> runtime_stage =
runtime_stages[backend];
if (!runtime_stage) {
std::ostringstream stream;
stream << "Asset '" << asset_name
@@ -90,6 +87,16 @@ std::string FragmentProgram::initFromAsset(const std::string& asset_name) {
}
if (UIDartState::Current()->IsImpellerEnabled()) {
// Spawn (but do not block on) a task that will load the runtime stage and
// populate an initial shader variant.
auto snapshot_controller = UIDartState::Current()->GetSnapshotDelegate();
ui_dart_state->GetTaskRunners().GetRasterTaskRunner()->PostTask(
[runtime_stage, snapshot_controller]() {
if (!snapshot_controller) {
return;
}
snapshot_controller->CacheRuntimeStage(runtime_stage);
});
runtime_effect_ = DlRuntimeEffect::MakeImpeller(std::move(runtime_stage));
} else {
const auto& code_mapping = runtime_stage->GetCodeMapping();
@@ -110,6 +117,7 @@ std::string FragmentProgram::initFromAsset(const std::string& asset_name) {
if (Dart_IsError(ths)) {
Dart_PropagateError(ths);
}
Dart_Handle result = Dart_SetField(ths, tonic::ToDart("_samplerCount"),
Dart_NewInteger(sampled_image_count));
if (Dart_IsError(result)) {

View File

@@ -71,6 +71,12 @@ class SnapshotDelegate {
SkISize picture_size) = 0;
virtual sk_sp<SkImage> ConvertToRasterImage(sk_sp<SkImage> image) = 0;
/// Load and compile and initial PSO for the provided [runtime_stage].
///
/// Impeller only.
virtual void CacheRuntimeStage(
const std::shared_ptr<impeller::RuntimeStage>& runtime_stage) = 0;
};
} // namespace flutter

View File

@@ -432,6 +432,12 @@ sk_sp<SkImage> Rasterizer::ConvertToRasterImage(sk_sp<SkImage> image) {
return snapshot_controller_->ConvertToRasterImage(image);
}
// |SnapshotDelegate|
void Rasterizer::CacheRuntimeStage(
const std::shared_ptr<impeller::RuntimeStage>& runtime_stage) {
snapshot_controller_->CacheRuntimeStage(runtime_stage);
}
fml::Milliseconds Rasterizer::GetFrameBudget() const {
return delegate_.GetFrameBudget();
};

View File

@@ -649,6 +649,10 @@ class Rasterizer final : public SnapshotDelegate,
// |SnapshotDelegate|
sk_sp<SkImage> ConvertToRasterImage(sk_sp<SkImage> image) override;
// |SnapshotDelegate|
void CacheRuntimeStage(
const std::shared_ptr<impeller::RuntimeStage>& runtime_stage) override;
// |Stopwatch::Delegate|
/// Time limit for a smooth frame.
///

View File

@@ -44,6 +44,9 @@ class SnapshotController {
virtual sk_sp<SkImage> ConvertToRasterImage(sk_sp<SkImage> image) = 0;
virtual void CacheRuntimeStage(
const std::shared_ptr<impeller::RuntimeStage>& runtime_stage) = 0;
protected:
explicit SnapshotController(const Delegate& delegate);
const Delegate& GetDelegate() { return delegate_; }

View File

@@ -70,6 +70,17 @@ sk_sp<DlImage> SnapshotControllerImpeller::DoMakeRasterSnapshot(
return nullptr;
}
void SnapshotControllerImpeller::CacheRuntimeStage(
const std::shared_ptr<impeller::RuntimeStage>& runtime_stage) {
impeller::RuntimeEffectContents runtime_effect;
runtime_effect.SetRuntimeStage(runtime_stage);
auto context = GetDelegate().GetAiksContext();
if (!context) {
return;
}
runtime_effect.BootstrapShader(context->GetContentContext());
}
sk_sp<SkImage> SnapshotControllerImpeller::ConvertToRasterImage(
sk_sp<SkImage> image) {
FML_UNREACHABLE();

View File

@@ -6,6 +6,7 @@
#define FLUTTER_SHELL_COMMON_SNAPSHOT_CONTROLLER_IMPELLER_H_
#include "flutter/shell/common/snapshot_controller.h"
#include "impeller/runtime_stage/runtime_stage.h"
namespace flutter {
@@ -20,6 +21,9 @@ class SnapshotControllerImpeller : public SnapshotController {
sk_sp<SkImage> ConvertToRasterImage(sk_sp<SkImage> image) override;
void CacheRuntimeStage(
const std::shared_ptr<impeller::RuntimeStage>& runtime_stage) override;
private:
sk_sp<DlImage> DoMakeRasterSnapshot(const sk_sp<DisplayList>& display_list,
SkISize size);

View File

@@ -160,4 +160,7 @@ sk_sp<SkImage> SnapshotControllerSkia::ConvertToRasterImage(
return result->skia_image();
}
void SnapshotControllerSkia::CacheRuntimeStage(
const std::shared_ptr<impeller::RuntimeStage>& runtime_stage) {}
} // namespace flutter

View File

@@ -20,6 +20,9 @@ class SnapshotControllerSkia : public SnapshotController {
virtual sk_sp<SkImage> ConvertToRasterImage(sk_sp<SkImage> image) override;
void CacheRuntimeStage(
const std::shared_ptr<impeller::RuntimeStage>& runtime_stage) override;
private:
sk_sp<DlImage> DoMakeRasterSnapshot(
SkISize size,