| Flutter Impeller |

renderer_dart_unittests.cc
Go to the documentation of this file.
1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #define FML_USED_ON_EMBEDDER
6
7 #include <memory>
8
9 #include "flutter/common/settings.h"
10 #include "flutter/common/task_runners.h"
11 #include "flutter/lib/gpu/context.h"
12 #include "flutter/lib/gpu/shader_library.h"
13 #include "flutter/lib/gpu/texture.h"
14 #include "flutter/runtime/dart_isolate.h"
15 #include "flutter/runtime/dart_vm_lifecycle.h"
16 #include "flutter/testing/dart_fixture.h"
17 #include "flutter/testing/dart_isolate_runner.h"
18 #include "flutter/testing/test_dart_native_resolver.h"
19 #include "flutter/testing/testing.h"
20 #include "fml/memory/ref_ptr.h"
21 #include "impeller/fixtures/texture.frag.h"
22 #include "impeller/fixtures/texture.vert.h"
23 #include "impeller/playground/playground_test.h"
24 #include "impeller/renderer/pipeline_library.h"
25 #include "impeller/renderer/render_pass.h"
26 #include "impeller/renderer/vertex_buffer_builder.h"
27
28 #include "gtest/gtest.h"
29 #include "third_party/imgui/imgui.h"
30
31 namespace impeller {
32 namespace testing {
33
34 static void InstantiateTestShaderLibrary(Context::BackendType backend_type) {
35 auto fixture =
36 flutter::testing::OpenFixtureAsMapping("playground.shaderbundle");
37 auto library = flutter::gpu::ShaderLibrary::MakeFromFlatbuffer(
38 backend_type, std::move(fixture));
39 flutter::gpu::ShaderLibrary::SetOverride(library);
40 }
41
42 class RendererDartTest : public PlaygroundTest,
43 public flutter::testing::DartFixture {
44 public:
45 RendererDartTest()
46 : settings_(CreateSettingsForFixture()),
47 vm_ref_(flutter::DartVMRef::Create(settings_)) {
48 fml::MessageLoop::EnsureInitializedForCurrentThread();
49
50 current_task_runner_ = fml::MessageLoop::GetCurrent().GetTaskRunner();
51
52 isolate_ = CreateDartIsolate();
53 assert(isolate_);
54 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 }
72 }
73
74 flutter::testing::AutoIsolateShutdown* GetIsolate() {
75 // Sneak the context into the Flutter GPU API.
76 assert(GetContext() != nullptr);
77 flutter::gpu::Context::SetOverrideContext(GetContext());
78
79 InstantiateTestShaderLibrary(GetContext()->GetBackendType());
80
81 return isolate_.get();
82 }
83
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 context->GetIdleWaiter());
229
230 pass.SetVertexBuffer(texture_vtx_builder.CreateVertexBuffer(
231 *context->GetResourceAllocator()));
232
233 TextureVS::UniformBuffer uniforms;
234 uniforms.mvp = Matrix();
235 TextureVS::BindUniformBuffer(pass, buffer->EmplaceUniform(uniforms));
236 TextureFS::BindTextureContents(pass, texture, sampler);
237
238 pass.SetPipeline(pipeline);
239
240 if (!pass.Draw().ok()) {
241 return false;
242 }
243 return true;
244 };
245 return OpenPlaygroundHere(callback);
246 }
247
248 private:
249 std::unique_ptr<flutter::testing::AutoIsolateShutdown> CreateDartIsolate() {
250 const auto settings = CreateSettingsForFixture();
251 flutter::TaskRunners task_runners(flutter::testing::GetCurrentTestName(),
252 current_task_runner_, //
253 current_task_runner_, //
254 current_task_runner_, //
255 current_task_runner_ //
256 );
257 return flutter::testing::RunDartCodeInIsolate(
258 vm_ref_, settings, task_runners, "main", {},
259 flutter::testing::GetDefaultKernelFilePath());
260 }
261
262 const flutter::Settings settings_;
263 flutter::DartVMRef vm_ref_;
264 fml::RefPtr<fml::TaskRunner> current_task_runner_;
265 std::unique_ptr<flutter::testing::AutoIsolateShutdown> isolate_;
266
267 std::shared_ptr<Texture> received_texture_;
268 };
269
270 INSTANTIATE_PLAYGROUND_SUITE(RendererDartTest);
271
272 TEST_P(RendererDartTest, CanRunDartInPlaygroundFrame) {
273 SinglePassCallback callback = [&](RenderPass& pass) {
274 ImGui::Begin("Dart test", nullptr);
275 ImGui::Text(
276 "This test executes Dart code during the playground frame callback.");
277 ImGui::End();
278
279 return RunDartFunction("sayHi");
280 };
281 ASSERT_TRUE(OpenPlaygroundHere(callback));
282 }
283
284 /// These test entries correspond to Dart functions located in
285 /// `flutter/impeller/fixtures/dart_tests.dart`
286
287 TEST_P(RendererDartTest, CanInstantiateFlutterGPUContext) {
288 ASSERT_TRUE(RunDartFunction("instantiateDefaultContext"));
289 }
290
291 TEST_P(RendererDartTest, CanCreateShaderLibrary) {
292 ASSERT_TRUE(RunDartFunction("canCreateShaderLibrary"));
293 }
294
295 TEST_P(RendererDartTest, CanReflectUniformStructs) {
296 ASSERT_TRUE(RunDartFunction("canReflectUniformStructs"));
297 }
298
299 TEST_P(RendererDartTest, CanCreateRenderPassAndSubmit) {
300 ASSERT_TRUE(RenderDartToPlayground("canCreateRenderPassAndSubmit"));
301 }
302
303 } // namespace testing
304 } // namespace impeller
impeller::Context::BackendType
BackendType
Definition: context.h:49
impeller::HostBuffer::Create
static std::shared_ptr< HostBuffer > Create(const std::shared_ptr< Allocator > &allocator, const std::shared_ptr< const IdleWaiter > &idle_waiter)
Definition: host_buffer.cc:21
impeller::Playground::OpenPlaygroundHere
bool OpenPlaygroundHere(const RenderCallback &render_callback)
Definition: playground.cc:201
impeller::Playground::IsPlaygroundEnabled
bool IsPlaygroundEnabled() const
Definition: playground.cc:147
impeller::Playground::GetWindowSize
ISize GetWindowSize() const
Definition: playground.cc:185
impeller::Playground::SinglePassCallback
std::function< bool(RenderPass &pass)> SinglePassCallback
Definition: playground.h:50
impeller::Playground::GetContext
std::shared_ptr< Context > GetContext() const
Definition: playground.cc:91
impeller::PlaygroundTest
Definition: playground_test.h:22
impeller::RenderPass
Render passes encode render commands directed as one specific render target into an underlying comman...
Definition: render_pass.h:30
impeller::VertexBufferBuilder
Definition: vertex_buffer_builder.h:35
impeller::VertexBufferBuilder::CreateVertexBuffer
VertexBuffer CreateVertexBuffer(HostBuffer &host_buffer) const
Definition: vertex_buffer_builder.h:95
impeller::VertexBufferBuilder::AddVertices
VertexBufferBuilder & AddVertices(std::initializer_list< VertexType_ > vertices)
Definition: vertex_buffer_builder.h:81
impeller::testing::RendererDartTest
Definition: renderer_dart_unittests.cc:43
impeller::testing::RendererDartTest::RunDartFunctionWithWindowSize
bool RunDartFunctionWithWindowSize(const char *dart_function_name)
Invokes a Dart function with the window's width and height as arguments.
Definition: renderer_dart_unittests.cc:132
impeller::testing::RendererDartTest::GetRenderedTextureFromDart
std::shared_ptr< Texture > GetRenderedTextureFromDart(const char *dart_function_name)
Run a Dart function that's expected to create a texture and pass it back for rendering via drawToPlay...
Definition: renderer_dart_unittests.cc:86
impeller::testing::RendererDartTest::RenderDartToPlayground
bool RenderDartToPlayground(const char *dart_function_name)
Call a dart function that produces a texture and render the result in the playground.
Definition: renderer_dart_unittests.cc:151
impeller::testing::RendererDartTest::RunDartFunction
bool RunDartFunction(const char *dart_function_name)
Invokes a Dart function.
Definition: renderer_dart_unittests.cc:116
impeller::testing::RendererDartTest::GetIsolate
flutter::testing::AutoIsolateShutdown * GetIsolate()
Definition: renderer_dart_unittests.cc:74
impeller::testing::RendererDartTest::RendererDartTest
RendererDartTest()
Definition: renderer_dart_unittests.cc:45
flutter
Definition: dl_golden_blur_unittests.cc:16
impeller::interop::Create
ScopedObject< Object > Create(CtorArgs &&... args)
Definition: object.h:161
impeller::testing::InstantiateTestShaderLibrary
static void InstantiateTestShaderLibrary(Context::BackendType backend_type)
Definition: renderer_dart_unittests.cc:34
impeller::testing::TEST_P
TEST_P(AiksTest, DrawAtlasNoColor)
Definition: aiks_dl_atlas_unittests.cc:60
impeller::testing::INSTANTIATE_PLAYGROUND_SUITE
INSTANTIATE_PLAYGROUND_SUITE(AiksTest)
impeller
Definition: allocation.cc:12
impeller::PixelFormat::kUnknown
@ kUnknown
impeller::SampleCount::kCount4
@ kCount4
pipeline_library.h
playground_test.h
render_pass.h
impeller::Matrix
A 4x4 matrix using column-major storage.
Definition: matrix.h:37
impeller::PipelineBuilder
An optional (but highly recommended) utility for creating pipelines from reflected shader information...
Definition: pipeline_builder.h:30
vertex_buffer_builder.h