Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit d794580

Browse files
authored
[Flutter GPU] Add support for drawing Flutter GPU textures in the playground. (#52352)
1 parent 11a857e commit d794580

File tree

4 files changed

+223
-44
lines changed

4 files changed

+223
-44
lines changed

impeller/fixtures/dart_tests.dart

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ void sayHi() {
1313
print('Hi');
1414
}
1515

16+
/// Pass a texture back to the playground for rendering to the surface.
17+
@pragma('vm:external-name', 'SetDisplayTexture')
18+
external void setDisplayTexture(gpu.Texture texture);
19+
1620
@pragma('vm:entry-point')
1721
void instantiateDefaultContext() {
1822
// ignore: unused_local_variable
@@ -102,9 +106,9 @@ ByteData float32(List<double> values) {
102106
}
103107

104108
@pragma('vm:entry-point')
105-
void canCreateRenderPassAndSubmit() {
106-
final gpu.Texture? renderTexture =
107-
gpu.gpuContext.createTexture(gpu.StorageMode.devicePrivate, 100, 100);
109+
void canCreateRenderPassAndSubmit(int width, int height) {
110+
final gpu.Texture? renderTexture = gpu.gpuContext
111+
.createTexture(gpu.StorageMode.devicePrivate, width, height);
108112
assert(renderTexture != null);
109113

110114
final gpu.CommandBuffer commandBuffer = gpu.gpuContext.createCommandBuffer();
@@ -123,9 +127,9 @@ void canCreateRenderPassAndSubmit() {
123127

124128
final gpu.HostBuffer transients = gpu.gpuContext.createHostBuffer();
125129
final gpu.BufferView vertices = transients.emplace(float32(<double>[
126-
-0.5, -0.5, //
130+
-0.5, 0.5, //
131+
0.0, -0.5, //
127132
0.5, 0.5, //
128-
0.5, -0.5, //
129133
]));
130134
final gpu.BufferView vertInfoData = transients.emplace(float32(<double>[
131135
1, 0, 0, 0, // mvp
@@ -142,4 +146,6 @@ void canCreateRenderPassAndSubmit() {
142146
encoder.draw();
143147

144148
commandBuffer.submit();
149+
150+
setDisplayTexture(renderTexture);
145151
}

impeller/playground/playground.cc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,10 @@ void Playground::SetupWindow() {
148148
start_time_ = fml::TimePoint::Now().ToEpochDelta();
149149
}
150150

151+
bool Playground::IsPlaygroundEnabled() const {
152+
return switches_.enable_playground;
153+
}
154+
151155
void Playground::TeardownWindow() {
152156
if (context_) {
153157
context_->Shutdown();

impeller/playground/playground.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ class Playground {
6464

6565
void TeardownWindow();
6666

67+
bool IsPlaygroundEnabled() const;
68+
6769
Point GetCursorPosition() const;
6870

6971
ISize GetWindowSize() const;

impeller/renderer/renderer_dart_unittests.cc

Lines changed: 206 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,20 @@
1010
#include "flutter/common/task_runners.h"
1111
#include "flutter/lib/gpu/context.h"
1212
#include "flutter/lib/gpu/shader_library.h"
13+
#include "flutter/lib/gpu/texture.h"
1314
#include "flutter/runtime/dart_isolate.h"
1415
#include "flutter/runtime/dart_vm_lifecycle.h"
1516
#include "flutter/testing/dart_fixture.h"
1617
#include "flutter/testing/dart_isolate_runner.h"
18+
#include "flutter/testing/test_dart_native_resolver.h"
1719
#include "flutter/testing/testing.h"
1820
#include "fml/memory/ref_ptr.h"
21+
#include "impeller/fixtures/texture.frag.h"
22+
#include "impeller/fixtures/texture.vert.h"
1923
#include "impeller/playground/playground_test.h"
24+
#include "impeller/renderer/pipeline_library.h"
2025
#include "impeller/renderer/render_pass.h"
26+
#include "impeller/renderer/vertex_buffer_builder.h"
2127

2228
#include "gtest/gtest.h"
2329
#include "third_party/imgui/imgui.h"
@@ -46,6 +52,23 @@ class RendererDartTest : public PlaygroundTest,
4652
isolate_ = CreateDartIsolate();
4753
assert(isolate_);
4854
assert(isolate_->get()->GetPhase() == flutter::DartIsolate::Phase::Running);
55+
56+
// Set up native callbacks.
57+
//
58+
// Note: The Dart isolate is configured (by
59+
// `RendererDartTest::CreateDartIsolate`) to use the main thread, so
60+
// there's no need for additional synchronization.
61+
{
62+
auto set_display_texture = [this](Dart_NativeArguments args) {
63+
flutter::gpu::Texture* texture =
64+
tonic::DartConverter<flutter::gpu::Texture*>::FromDart(
65+
Dart_GetNativeArgument(args, 0));
66+
assert(texture != nullptr); // Should always be a valid pointer.
67+
received_texture_ = texture->GetTexture();
68+
};
69+
AddNativeCallback("SetDisplayTexture",
70+
CREATE_NATIVE_ENTRY(set_display_texture));
71+
}
4972
}
5073

5174
flutter::testing::AutoIsolateShutdown* GetIsolate() {
@@ -58,6 +81,169 @@ class RendererDartTest : public PlaygroundTest,
5881
return isolate_.get();
5982
}
6083

84+
/// @brief Run a Dart function that's expected to create a texture and pass
85+
/// it back for rendering via `drawToPlayground`.
86+
std::shared_ptr<Texture> GetRenderedTextureFromDart(
87+
const char* dart_function_name) {
88+
bool success =
89+
GetIsolate()->RunInIsolateScope([this, &dart_function_name]() -> bool {
90+
Dart_Handle args[] = {tonic::ToDart(GetWindowSize().width),
91+
tonic::ToDart(GetWindowSize().height)};
92+
if (tonic::CheckAndHandleError(
93+
::Dart_Invoke(Dart_RootLibrary(),
94+
tonic::ToDart(dart_function_name), 2, args))) {
95+
return false;
96+
}
97+
return true;
98+
});
99+
if (!success) {
100+
FML_LOG(ERROR) << "Failed to invoke dart test function:"
101+
<< dart_function_name;
102+
return nullptr;
103+
}
104+
if (!received_texture_) {
105+
FML_LOG(ERROR) << "Dart test function `" << dart_function_name
106+
<< "` did not invoke `drawToPlaygroundSurface`.";
107+
return nullptr;
108+
}
109+
return received_texture_;
110+
}
111+
112+
/// @brief Invokes a Dart function.
113+
///
114+
/// Returns false if invoking the function failed or if any unhandled
115+
/// exceptions were thrown.
116+
bool RunDartFunction(const char* dart_function_name) {
117+
return GetIsolate()->RunInIsolateScope([&dart_function_name]() -> bool {
118+
if (tonic::CheckAndHandleError(
119+
::Dart_Invoke(Dart_RootLibrary(),
120+
tonic::ToDart(dart_function_name), 0, nullptr))) {
121+
return false;
122+
}
123+
return true;
124+
});
125+
}
126+
127+
/// @brief Invokes a Dart function with the window's width and height as
128+
/// arguments.
129+
///
130+
/// Returns false if invoking the function failed or if any unhandled
131+
/// exceptions were thrown.
132+
bool RunDartFunctionWithWindowSize(const char* dart_function_name) {
133+
return GetIsolate()->RunInIsolateScope(
134+
[this, &dart_function_name]() -> bool {
135+
Dart_Handle args[] = {tonic::ToDart(GetWindowSize().width),
136+
tonic::ToDart(GetWindowSize().height)};
137+
if (tonic::CheckAndHandleError(
138+
::Dart_Invoke(Dart_RootLibrary(),
139+
tonic::ToDart(dart_function_name), 2, args))) {
140+
return false;
141+
}
142+
return true;
143+
});
144+
}
145+
146+
/// @brief Call a dart function that produces a texture and render the result
147+
/// in the playground.
148+
///
149+
/// If the playground isn't enabled, the function is simply run once
150+
/// in order to verify that it doesn't throw any unhandled exceptions.
151+
bool RenderDartToPlayground(const char* dart_function_name) {
152+
if (!IsPlaygroundEnabled()) {
153+
// If the playground is not enabled, run the function instead to at least
154+
// verify that it doesn't crash.
155+
return RunDartFunctionWithWindowSize(dart_function_name);
156+
}
157+
158+
auto context = GetContext();
159+
assert(context != nullptr);
160+
161+
//------------------------------------------------------------------------------
162+
/// Prepare pipeline.
163+
///
164+
165+
using TextureVS = TextureVertexShader;
166+
using TextureFS = TextureFragmentShader;
167+
using TexturePipelineBuilder = PipelineBuilder<TextureVS, TextureFS>;
168+
169+
auto pipeline_desc =
170+
TexturePipelineBuilder::MakeDefaultPipelineDescriptor(*context);
171+
if (!pipeline_desc.has_value()) {
172+
FML_LOG(ERROR) << "Failed to create default pipeline descriptor.";
173+
return false;
174+
}
175+
pipeline_desc->SetSampleCount(SampleCount::kCount4);
176+
pipeline_desc->SetStencilAttachmentDescriptors(std::nullopt);
177+
pipeline_desc->SetDepthStencilAttachmentDescriptor(std::nullopt);
178+
pipeline_desc->SetStencilPixelFormat(PixelFormat::kUnknown);
179+
pipeline_desc->SetDepthPixelFormat(PixelFormat::kUnknown);
180+
181+
auto pipeline =
182+
context->GetPipelineLibrary()->GetPipeline(pipeline_desc).Get();
183+
if (!pipeline || !pipeline->IsValid()) {
184+
FML_LOG(ERROR) << "Failed to create default pipeline.";
185+
return false;
186+
}
187+
188+
//------------------------------------------------------------------------------
189+
/// Prepare vertex data.
190+
///
191+
192+
VertexBufferBuilder<TextureVS::PerVertexData> texture_vtx_builder;
193+
194+
// Always stretch out the texture to fill the screen.
195+
196+
// clang-format off
197+
texture_vtx_builder.AddVertices({
198+
{{-0.5, -0.5, 0.0}, {0.0, 0.0}}, // 1
199+
{{ 0.5, -0.5, 0.0}, {1.0, 0.0}}, // 2
200+
{{ 0.5, 0.5, 0.0}, {1.0, 1.0}}, // 3
201+
{{-0.5, -0.5, 0.0}, {0.0, 0.0}}, // 1
202+
{{ 0.5, 0.5, 0.0}, {1.0, 1.0}}, // 3
203+
{{-0.5, 0.5, 0.0}, {0.0, 1.0}}, // 4
204+
});
205+
// clang-format on
206+
207+
//------------------------------------------------------------------------------
208+
/// Prepare sampler.
209+
///
210+
211+
const auto& sampler = context->GetSamplerLibrary()->GetSampler({});
212+
if (!sampler) {
213+
FML_LOG(ERROR) << "Failed to create default sampler.";
214+
return false;
215+
}
216+
217+
//------------------------------------------------------------------------------
218+
/// Render to playground.
219+
///
220+
221+
SinglePassCallback callback = [&](RenderPass& pass) {
222+
auto texture = GetRenderedTextureFromDart(dart_function_name);
223+
if (!texture) {
224+
return false;
225+
}
226+
227+
auto buffer = HostBuffer::Create(context->GetResourceAllocator());
228+
229+
pass.SetVertexBuffer(texture_vtx_builder.CreateVertexBuffer(
230+
*context->GetResourceAllocator()));
231+
232+
TextureVS::UniformBuffer uniforms;
233+
uniforms.mvp = Matrix();
234+
TextureVS::BindUniformBuffer(pass, buffer->EmplaceUniform(uniforms));
235+
TextureFS::BindTextureContents(pass, texture, sampler);
236+
237+
pass.SetPipeline(pipeline);
238+
239+
if (!pass.Draw().ok()) {
240+
return false;
241+
}
242+
return true;
243+
};
244+
return OpenPlaygroundHere(callback);
245+
}
246+
61247
private:
62248
std::unique_ptr<flutter::testing::AutoIsolateShutdown> CreateDartIsolate() {
63249
const auto settings = CreateSettingsForFixture();
@@ -76,65 +262,46 @@ class RendererDartTest : public PlaygroundTest,
76262
flutter::DartVMRef vm_ref_;
77263
fml::RefPtr<fml::TaskRunner> current_task_runner_;
78264
std::unique_ptr<flutter::testing::AutoIsolateShutdown> isolate_;
265+
266+
std::shared_ptr<Texture> received_texture_;
79267
};
80268

81269
INSTANTIATE_PLAYGROUND_SUITE(RendererDartTest);
82270

83271
TEST_P(RendererDartTest, CanRunDartInPlaygroundFrame) {
84-
auto isolate = GetIsolate();
85-
86272
SinglePassCallback callback = [&](RenderPass& pass) {
87273
ImGui::Begin("Dart test", nullptr);
88274
ImGui::Text(
89275
"This test executes Dart code during the playground frame callback.");
90276
ImGui::End();
91277

92-
return isolate->RunInIsolateScope([]() -> bool {
93-
if (tonic::CheckAndHandleError(::Dart_Invoke(
94-
Dart_RootLibrary(), tonic::ToDart("sayHi"), 0, nullptr))) {
95-
return false;
96-
}
97-
return true;
98-
});
278+
return RunDartFunction("sayHi");
99279
};
100-
OpenPlaygroundHere(callback);
280+
ASSERT_TRUE(OpenPlaygroundHere(callback));
101281
}
102282

103-
TEST_P(RendererDartTest, CanInstantiateFlutterGPUContext) {
104-
auto isolate = GetIsolate();
105-
bool result = isolate->RunInIsolateScope([]() -> bool {
106-
if (tonic::CheckAndHandleError(::Dart_Invoke(
107-
Dart_RootLibrary(), tonic::ToDart("instantiateDefaultContext"), 0,
108-
nullptr))) {
109-
return false;
110-
}
111-
return true;
112-
});
283+
/// These test entries correspond to Dart functions located in
284+
/// `flutter/impeller/fixtures/dart_tests.dart`
113285

114-
ASSERT_TRUE(result);
286+
TEST_P(RendererDartTest, CanInstantiateFlutterGPUContext) {
287+
ASSERT_TRUE(RunDartFunction("instantiateDefaultContext"));
115288
}
116289

117-
#define DART_TEST_CASE(name) \
118-
TEST_P(RendererDartTest, name) { \
119-
auto isolate = GetIsolate(); \
120-
bool result = isolate->RunInIsolateScope([]() -> bool { \
121-
if (tonic::CheckAndHandleError(::Dart_Invoke( \
122-
Dart_RootLibrary(), tonic::ToDart(#name), 0, nullptr))) { \
123-
return false; \
124-
} \
125-
return true; \
126-
}); \
127-
ASSERT_TRUE(result); \
128-
}
290+
TEST_P(RendererDartTest, CanCreateShaderLibrary) {
291+
ASSERT_TRUE(RunDartFunction("canCreateShaderLibrary"));
292+
}
129293

130-
/// These test entries correspond to Dart functions located in
131-
/// `flutter/impeller/fixtures/dart_tests.dart`
294+
TEST_P(RendererDartTest, CanReflectUniformStructs) {
295+
ASSERT_TRUE(RunDartFunction("canReflectUniformStructs"));
296+
}
132297

133-
DART_TEST_CASE(canCreateShaderLibrary);
134-
DART_TEST_CASE(canReflectUniformStructs);
135-
DART_TEST_CASE(uniformBindFailsForInvalidHostBufferOffset);
298+
TEST_P(RendererDartTest, UniformBindFailsForInvalidHostBufferOffset) {
299+
ASSERT_TRUE(RunDartFunction("uniformBindFailsForInvalidHostBufferOffset"));
300+
}
136301

137-
DART_TEST_CASE(canCreateRenderPassAndSubmit);
302+
TEST_P(RendererDartTest, CanCreateRenderPassAndSubmit) {
303+
ASSERT_TRUE(RenderDartToPlayground("canCreateRenderPassAndSubmit"));
304+
}
138305

139306
} // namespace testing
140307
} // namespace impeller

0 commit comments

Comments
 (0)