Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions shell/common/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ if (current_toolchain == host_toolchain) {
":shell_unittests_gpu_configuration",
"$flutter_root/common",
"$flutter_root/flow",
"$flutter_root/lib/ui:ui",
"$flutter_root/shell",
"$flutter_root/testing:dart",
"$flutter_root/testing:opengl",
Expand Down
18 changes: 18 additions & 0 deletions shell/common/fixtures/shell_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:convert' show utf8;
import 'dart:isolate';
import 'dart:typed_data';
import 'dart:ui';

void main() {}
Expand Down Expand Up @@ -57,3 +59,19 @@ void testCanLaunchSecondaryIsolate() {
Isolate.spawn(secondaryIsolateMain, 'Hello from root isolate.');
notifyNative();
}

@pragma('vm:entry-point')
void testSkiaResourceCacheSendsResponse() {
final PlatformMessageResponseCallback callback = (ByteData data) {
notifyNative();
};
const String json = '''{
"method": "Skia.setResourceCacheMaxBytes",
"args": 10000
}''';
window.sendPlatformMessage(
'flutter/skia',
Uint8List.fromList(utf8.encode(json)).buffer.asByteData(),
callback,
);
}
33 changes: 32 additions & 1 deletion shell/common/rasterizer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ Rasterizer::Rasterizer(
: 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_);
}
Expand All @@ -55,6 +56,10 @@ fml::WeakPtr<Rasterizer> Rasterizer::GetWeakPtr() const {

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();
}

Expand Down Expand Up @@ -355,7 +360,20 @@ void Rasterizer::FireNextFrameCallbackIfPresent() {
callback();
}

void Rasterizer::SetResourceCacheMaxBytes(int max_bytes) {
void Rasterizer::SetResourceCacheMaxBytes(size_t max_bytes, bool from_user) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This API is timing sensitive and depends on the surface already being acquired. It would be better if the cache size in bytes was instead stored in an std::optional<size_t> on the rasterizer.. Then, here and in Rasterizer::Setup, you could use value_or(some_default) to set the cache size of context of the surface. Similarly, the getter would then just the cache size from the optional ivar instead of depending on the surface being present. This scheme would also make the setting survive rasterizer teardown and re-setup.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

user_override_resource_cache_bytes_ |= from_user;

if (!from_user && user_override_resource_cache_bytes_) {
// We should not update the setting here if a user has explicitly set a
// value for this over the flutter/skia channel.
return;
}

max_cache_bytes_ = max_bytes;
if (!surface_) {
return;
}

GrContext* context = surface_->GetContext();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Making this call after Rasterizer::Teardown but before the next Setup will dereference a null pointer.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

if (context) {
int max_resources;
Expand All @@ -364,6 +382,19 @@ void Rasterizer::SetResourceCacheMaxBytes(int max_bytes) {
}
}

std::optional<size_t> Rasterizer::GetResourceCacheMaxBytes() const {
if (!surface_) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The validity of this call still depends on the the presence of a surface. Why not just access the optional and return the size_t instead of an optional? Now that the value is maintained between teardowns and setups, the contexts cache size should always be consistent with that in the optional.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if this is called before the first setup, and before anyone has attempted to set the metrics?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Return kGrCacheMaxByteSize maybe?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But that's not really accurate, and that symbol is defined as an implementation detail of gl_surface_cpu.cc. It's different on Fuchsia for instance, where they override it in another place.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok then. Bad idea. Thanks for clarifying.

return std::nullopt;
}
GrContext* context = surface_->GetContext();
if (context) {
size_t max_bytes;
context->getResourceCacheLimits(nullptr, &max_bytes);
return max_bytes;
}
return std::nullopt;
}

Rasterizer::Screenshot::Screenshot() {}

Rasterizer::Screenshot::Screenshot(sk_sp<SkData> p_data, SkISize p_size)
Expand Down
22 changes: 21 additions & 1 deletion shell/common/rasterizer.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#define SHELL_COMMON_RASTERIZER_H_

#include <memory>
#include <optional>

#include "flutter/common/settings.h"
#include "flutter/common/task_runners.h"
Expand Down Expand Up @@ -374,8 +375,25 @@ class Rasterizer final {
///
/// @param[in] max_bytes The maximum byte size of resource that may be
/// cached for GPU rendering.
/// @param[in] from_user Whether this request was from user code, e.g. via
/// the flutter/skia message channel, in which case
/// it should not be overridden by the platform.
///
void SetResourceCacheMaxBytes(int max_bytes);
void SetResourceCacheMaxBytes(size_t max_bytes, bool from_user);

//----------------------------------------------------------------------------
/// @brief The current value of Skia's resource cache size, if a surface
/// is present.
///
/// @attention This cache does not describe the entirety of GPU resources
/// that may be cached. The `RasterCache` also holds very large
/// GPU resources.
///
/// @see `RasterCache`
///
/// @return The size of Skia's resource cache, if available.
///
std::optional<size_t> GetResourceCacheMaxBytes() const;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per discussion above, this can now be just size_t GetResourceCacheMaxBytes() const;


private:
Delegate& delegate_;
Expand All @@ -384,6 +402,8 @@ class Rasterizer final {
std::unique_ptr<flutter::CompositorContext> compositor_context_;
std::unique_ptr<flutter::LayerTree> last_layer_tree_;
fml::closure next_frame_callback_;
bool user_override_resource_cache_bytes_;
std::optional<size_t> max_cache_bytes_;
fml::WeakPtrFactory<Rasterizer> weak_factory_;

void DoDraw(std::unique_ptr<flutter::LayerTree> layer_tree);
Expand Down
20 changes: 17 additions & 3 deletions shell/common/shell.cc
Original file line number Diff line number Diff line change
Expand Up @@ -682,6 +682,16 @@ void Shell::OnPlatformViewSetViewportMetrics(const ViewportMetrics& metrics) {
FML_DCHECK(is_setup_);
FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());

// This is the formula Android uses.
// https://android.googlesource.com/platform/frameworks/base/+/master/libs/hwui/renderthread/CacheManager.cpp#41
size_t max_bytes = metrics.physical_width * metrics.physical_height * 12 * 4;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can move this to a constant in rasterizer and use std::optionals default as discussed above.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I'm clear on which part should be the constant. Just the 12 * 4 part?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe a static method on rasterizer that takes width and height?

task_runners_.GetGPUTaskRunner()->PostTask(
[rasterizer = rasterizer_->GetWeakPtr(), max_bytes] {
if (rasterizer) {
rasterizer->SetResourceCacheMaxBytes(max_bytes, false);
}
});

task_runners_.GetUITaskRunner()->PostTask(
[engine = engine_->GetWeakPtr(), metrics]() {
if (engine) {
Expand Down Expand Up @@ -939,10 +949,14 @@ void Shell::HandleEngineSkiaMessage(fml::RefPtr<PlatformMessage> message) {
return;

task_runners_.GetGPUTaskRunner()->PostTask(
[rasterizer = rasterizer_->GetWeakPtr(),
max_bytes = args->value.GetInt()] {
[rasterizer = rasterizer_->GetWeakPtr(), max_bytes = args->value.GetInt(),
response = std::move(message->response())] {
if (rasterizer) {
rasterizer->SetResourceCacheMaxBytes(max_bytes);
rasterizer->SetResourceCacheMaxBytes(static_cast<size_t>(max_bytes),
true);
}
if (response) {
response->CompleteEmpty();
}
});
}
Expand Down
15 changes: 15 additions & 0 deletions shell/common/shell_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,21 @@ ShellTest::ShellTest()

ShellTest::~ShellTest() = default;

void ShellTest::SendEnginePlatformMessage(
Shell* shell,
fml::RefPtr<PlatformMessage> message) {
fml::AutoResetWaitableEvent latch;
fml::TaskRunner::RunNowOrPostTask(
shell->GetTaskRunners().GetPlatformTaskRunner(),
[shell, &latch, message = std::move(message)]() {
if (auto engine = shell->weak_engine_) {
engine->HandlePlatformMessage(std::move(message));
}
latch.Signal();
});
latch.Wait();
}

void ShellTest::SetSnapshotsAndAssets(Settings& settings) {
if (!assets_dir_.is_valid()) {
return;
Expand Down
4 changes: 4 additions & 0 deletions shell/common/shell_test.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

#include "flutter/common/settings.h"
#include "flutter/fml/macros.h"
#include "flutter/lib/ui/window/platform_message.h"
#include "flutter/shell/common/run_configuration.h"
#include "flutter/shell/common/shell.h"
#include "flutter/shell/common/thread_host.h"
Expand All @@ -32,6 +33,9 @@ class ShellTest : public ThreadTest {
TaskRunners task_runners);
TaskRunners GetTaskRunnersForFixture();

void SendEnginePlatformMessage(Shell* shell,
fml::RefPtr<PlatformMessage> message);

void AddNativeCallback(std::string name, Dart_NativeFunction callback);

static void PlatformViewNotifyCreated(
Expand Down
129 changes: 124 additions & 5 deletions shell/common/shell_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -264,11 +264,11 @@ TEST_F(ShellTest, NoNeedToReportTimingsByDefault) {
ASSERT_FALSE(GetNeedsReportTimings(shell.get()));

// This assertion may or may not be the direct result of needs_report_timings_
// being false. The count could be 0 simply because we just cleared unreported
// timings by reporting them. Hence this can't replace the
// ASSERT_FALSE(GetNeedsReportTimings(shell.get())) check. We added this
// assertion for an additional confidence that we're not pushing back to
// unreported timings unnecessarily.
// being false. The count could be 0 simply because we just cleared
// unreported timings by reporting them. Hence this can't replace the
// ASSERT_FALSE(GetNeedsReportTimings(shell.get())) check. We added
// this assertion for an additional confidence that we're not pushing
// back to unreported timings unnecessarily.
//
// Conversely, do not assert UnreportedTimingsCount(shell.get()) to be
// positive in any tests. Otherwise those tests will be flaky as the clearing
Expand Down Expand Up @@ -600,5 +600,124 @@ TEST_F(ShellTest, WaitForFirstFrameInlined) {
ASSERT_FALSE(event.WaitWithTimeout(fml::TimeDelta::FromMilliseconds(1000)));
}

TEST_F(ShellTest, SetResourceCacheSize) {
Settings settings = CreateSettingsForFixture();
auto task_runner = GetThreadTaskRunner();
TaskRunners task_runners("test", task_runner, task_runner, task_runner,
task_runner);
std::unique_ptr<Shell> shell =
CreateShell(std::move(settings), std::move(task_runners));

// Create the surface needed by rasterizer
PlatformViewNotifyCreated(shell.get());

auto configuration = RunConfiguration::InferFromSettings(settings);
configuration.SetEntrypoint("emptyMain");

RunEngine(shell.get(), std::move(configuration));
PumpOneFrame(shell.get());

EXPECT_EQ(shell->GetRasterizer()->GetResourceCacheMaxBytes().value_or(0U),
static_cast<size_t>(24 * (1 << 20)));

fml::TaskRunner::RunNowOrPostTask(
shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell]() {
shell->GetPlatformView()->SetViewportMetrics(
{1.0, 400, 200, 0, 0, 0, 0, 0, 0, 0, 0});
});
PumpOneFrame(shell.get());

EXPECT_EQ(shell->GetRasterizer()->GetResourceCacheMaxBytes().value_or(0U),
3840000U);

std::string request_json = R"json({
"method": "Skia.setResourceCacheMaxBytes",
"args": 10000
})json";
std::vector<uint8_t> data(request_json.begin(), request_json.end());
auto platform_message = fml::MakeRefCounted<PlatformMessage>(
"flutter/skia", std::move(data), nullptr);
SendEnginePlatformMessage(shell.get(), std::move(platform_message));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you make sure a reply is received for this message? I think that's broken right now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no reply on this mesage. It's not implemented at all. What's the use case for it?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See b/137134910 for details. Without a reply applications can't await setting this, so have no visibility into when the change has taken effect. I think sending an empty message around here would fix it:

rasterizer->SetResourceCacheMaxBytes(max_bytes);

message->response->CompleteEmpty() should be enough.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added an empty reply and a test for it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in b50cab1

PumpOneFrame(shell.get());
EXPECT_EQ(shell->GetRasterizer()->GetResourceCacheMaxBytes().value_or(0U),
10000U);

fml::TaskRunner::RunNowOrPostTask(
shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell]() {
shell->GetPlatformView()->SetViewportMetrics(
{1.0, 800, 400, 0, 0, 0, 0, 0, 0, 0, 0});
});
PumpOneFrame(shell.get());

EXPECT_EQ(shell->GetRasterizer()->GetResourceCacheMaxBytes().value_or(0U),
10000U);
}

TEST_F(ShellTest, SetResourceCacheSizeEarly) {
Settings settings = CreateSettingsForFixture();
auto task_runner = GetThreadTaskRunner();
TaskRunners task_runners("test", task_runner, task_runner, task_runner,
task_runner);
std::unique_ptr<Shell> shell =
CreateShell(std::move(settings), std::move(task_runners));

fml::TaskRunner::RunNowOrPostTask(
shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell]() {
shell->GetPlatformView()->SetViewportMetrics(
{1.0, 400, 200, 0, 0, 0, 0, 0, 0, 0, 0});
});
PumpOneFrame(shell.get());

// Create the surface needed by rasterizer
PlatformViewNotifyCreated(shell.get());

auto configuration = RunConfiguration::InferFromSettings(settings);
configuration.SetEntrypoint("emptyMain");

RunEngine(shell.get(), std::move(configuration));
PumpOneFrame(shell.get());

EXPECT_EQ(shell->GetRasterizer()->GetResourceCacheMaxBytes().value_or(0),
static_cast<size_t>(3840000U));
}

TEST_F(ShellTest, SetResourceCacheSizeNotifiesDart) {
Settings settings = CreateSettingsForFixture();
auto task_runner = GetThreadTaskRunner();
TaskRunners task_runners("test", task_runner, task_runner, task_runner,
task_runner);
std::unique_ptr<Shell> shell =
CreateShell(std::move(settings), std::move(task_runners));

fml::TaskRunner::RunNowOrPostTask(
shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell]() {
shell->GetPlatformView()->SetViewportMetrics(
{1.0, 400, 200, 0, 0, 0, 0, 0, 0, 0, 0});
});
PumpOneFrame(shell.get());

// Create the surface needed by rasterizer
PlatformViewNotifyCreated(shell.get());

auto configuration = RunConfiguration::InferFromSettings(settings);
configuration.SetEntrypoint("testSkiaResourceCacheSendsResponse");

EXPECT_EQ(shell->GetRasterizer()->GetResourceCacheMaxBytes().value_or(0),
static_cast<size_t>(3840000U));

fml::AutoResetWaitableEvent latch;
AddNativeCallback("NotifyNative", CREATE_NATIVE_ENTRY([&latch](auto args) {
latch.Signal();
}));

RunEngine(shell.get(), std::move(configuration));
PumpOneFrame(shell.get());

latch.Wait();

EXPECT_EQ(shell->GetRasterizer()->GetResourceCacheMaxBytes().value_or(0),
static_cast<size_t>(10000U));
}

} // namespace testing
} // namespace flutter
5 changes: 4 additions & 1 deletion shell/gpu/gpu_surface_gl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@ 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);
// The shell will dynamically increase or decrease this cache based on the
// viewport size, unless a user has specifically requested a size on the Skia
// system channel.
static const size_t kGrCacheMaxByteSize = 24 * (1 << 20);

GPUSurfaceGL::GPUSurfaceGL(GPUSurfaceGLDelegate* delegate)
: delegate_(delegate), weak_factory_(this) {
Expand Down