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"
27 
28 #include "gtest/gtest.h"
29 #include "third_party/imgui/imgui.h"
30 #include "third_party/tonic/dart_microtask_queue.h"
31 
32 namespace impeller {
33 namespace testing {
34 
36  auto fixture =
37  flutter::testing::OpenFixtureAsMapping("playground.shaderbundle");
38  auto library = flutter::gpu::ShaderLibrary::MakeFromFlatbuffer(
39  backend_type, std::move(fixture));
40  flutter::gpu::ShaderLibrary::SetOverride(library);
41 }
42 
43 // Drains the microtask queue so async Dart entry points run to completion.
44 // Returns false if a microtask produced an unhandled error. Must be called
45 // inside an isolate scope. The fixture entry points are now async (shader
46 // loading returns a Future), and on native everything they await is already
47 // available, so the work finishes as microtasks with no message loop needed.
48 static bool DrainMicrotasks() {
49  auto* queue = tonic::DartMicrotaskQueue::GetForCurrentThread();
50  if (queue == nullptr) {
51  return true;
52  }
53  queue->RunMicrotasks();
54  return queue->GetLastError() == tonic::kNoError;
55 }
56 
58  public flutter::testing::DartFixture {
59  public:
61  : settings_(CreateSettingsForFixture()),
62  vm_ref_(flutter::DartVMRef::Create(settings_)) {
63  fml::MessageLoop::EnsureInitializedForCurrentThread();
64 
65  current_task_runner_ = fml::MessageLoop::GetCurrent().GetTaskRunner();
66 
67  isolate_ = CreateDartIsolate();
68  assert(isolate_);
69  assert(isolate_->get()->GetPhase() == flutter::DartIsolate::Phase::Running);
70 
71  // Set up native callbacks.
72  //
73  // Note: The Dart isolate is configured (by
74  // `RendererDartTest::CreateDartIsolate`) to use the main thread, so
75  // there's no need for additional synchronization.
76  {
77  auto set_display_texture = [this](Dart_NativeArguments args) {
78  flutter::gpu::Texture* texture =
79  tonic::DartConverter<flutter::gpu::Texture*>::FromDart(
80  Dart_GetNativeArgument(args, 0));
81  assert(texture != nullptr); // Should always be a valid pointer.
82  received_texture_ = texture->GetTexture();
83  };
84  AddNativeCallback("SetDisplayTexture",
85  CREATE_NATIVE_ENTRY(set_display_texture));
86  }
87  }
88 
89  flutter::testing::AutoIsolateShutdown* GetIsolate() {
90  // Sneak the context into the Flutter GPU API.
91  assert(GetContext() != nullptr);
92  flutter::gpu::Context::SetOverrideContext(GetContext());
93 
94  InstantiateTestShaderLibrary(GetContext()->GetBackendType());
95 
96  return isolate_.get();
97  }
98 
99  /// @brief Run a Dart function that's expected to create a texture and pass
100  /// it back for rendering via `drawToPlayground`.
101  std::shared_ptr<Texture> GetRenderedTextureFromDart(
102  const char* dart_function_name) {
103  bool success =
104  GetIsolate()->RunInIsolateScope([this, &dart_function_name]() -> bool {
105  Dart_Handle args[] = {tonic::ToDart(GetWindowSize().width),
106  tonic::ToDart(GetWindowSize().height)};
107  if (tonic::CheckAndHandleError(
108  ::Dart_Invoke(Dart_RootLibrary(),
109  tonic::ToDart(dart_function_name), 2, args))) {
110  return false;
111  }
112  return DrainMicrotasks();
113  });
114  if (!success) {
115  FML_LOG(ERROR) << "Failed to invoke dart test function:"
116  << dart_function_name;
117  return nullptr;
118  }
119  if (!received_texture_) {
120  FML_LOG(ERROR) << "Dart test function `" << dart_function_name
121  << "` did not invoke `drawToPlaygroundSurface`.";
122  return nullptr;
123  }
124  return received_texture_;
125  }
126 
127  /// @brief Invokes a Dart function.
128  ///
129  /// Returns false if invoking the function failed or if any unhandled
130  /// exceptions were thrown.
131  bool RunDartFunction(const char* dart_function_name) {
132  return GetIsolate()->RunInIsolateScope([&dart_function_name]() -> bool {
133  if (tonic::CheckAndHandleError(
134  ::Dart_Invoke(Dart_RootLibrary(),
135  tonic::ToDart(dart_function_name), 0, nullptr))) {
136  return false;
137  }
138  return DrainMicrotasks();
139  });
140  }
141 
142  /// @brief Invokes a Dart function with the window's width and height as
143  /// arguments.
144  ///
145  /// Returns false if invoking the function failed or if any unhandled
146  /// exceptions were thrown.
147  bool RunDartFunctionWithWindowSize(const char* dart_function_name) {
148  return GetIsolate()->RunInIsolateScope(
149  [this, &dart_function_name]() -> bool {
150  Dart_Handle args[] = {tonic::ToDart(GetWindowSize().width),
151  tonic::ToDart(GetWindowSize().height)};
152  if (tonic::CheckAndHandleError(
153  ::Dart_Invoke(Dart_RootLibrary(),
154  tonic::ToDart(dart_function_name), 2, args))) {
155  return false;
156  }
157  return DrainMicrotasks();
158  });
159  }
160 
161  /// @brief Call a dart function that produces a texture and render the result
162  /// in the playground.
163  ///
164  /// If the playground isn't enabled, the function is simply run once
165  /// in order to verify that it doesn't throw any unhandled exceptions.
166  bool RenderDartToPlayground(const char* dart_function_name) {
167  if (!IsPlaygroundEnabled()) {
168  // If the playground is not enabled, run the function instead to at least
169  // verify that it doesn't crash.
170  return RunDartFunctionWithWindowSize(dart_function_name);
171  }
172 
173  auto context = GetContext();
174  assert(context != nullptr);
175 
176  //------------------------------------------------------------------------------
177  /// Prepare pipeline.
178  ///
179 
180  using TextureVS = TextureVertexShader;
181  using TextureFS = TextureFragmentShader;
182  using TexturePipelineBuilder = PipelineBuilder<TextureVS, TextureFS>;
183 
184  auto pipeline_desc =
185  TexturePipelineBuilder::MakeDefaultPipelineDescriptor(*context);
186  if (!pipeline_desc.has_value()) {
187  FML_LOG(ERROR) << "Failed to create default pipeline descriptor.";
188  return false;
189  }
190  pipeline_desc->SetSampleCount(SampleCount::kCount4);
191  pipeline_desc->SetStencilAttachmentDescriptors(std::nullopt);
192  pipeline_desc->SetDepthStencilAttachmentDescriptor(std::nullopt);
193  pipeline_desc->SetStencilPixelFormat(PixelFormat::kUnknown);
194  pipeline_desc->SetDepthPixelFormat(PixelFormat::kUnknown);
195 
196  auto pipeline =
197  context->GetPipelineLibrary()->GetPipeline(pipeline_desc).Get();
198  if (!pipeline || !pipeline->IsValid()) {
199  FML_LOG(ERROR) << "Failed to create default pipeline.";
200  return false;
201  }
202 
203  //------------------------------------------------------------------------------
204  /// Prepare vertex data.
205  ///
206 
208 
209  // Always stretch out the texture to fill the screen.
210 
211  // clang-format off
212  texture_vtx_builder.AddVertices({
213  {{-0.5, -0.5, 0.0}, {0.0, 0.0}}, // 1
214  {{ 0.5, -0.5, 0.0}, {1.0, 0.0}}, // 2
215  {{ 0.5, 0.5, 0.0}, {1.0, 1.0}}, // 3
216  {{-0.5, -0.5, 0.0}, {0.0, 0.0}}, // 1
217  {{ 0.5, 0.5, 0.0}, {1.0, 1.0}}, // 3
218  {{-0.5, 0.5, 0.0}, {0.0, 1.0}}, // 4
219  });
220  // clang-format on
221 
222  //------------------------------------------------------------------------------
223  /// Prepare sampler.
224  ///
225 
226  const auto& sampler = context->GetSamplerLibrary()->GetSampler({});
227  if (!sampler) {
228  FML_LOG(ERROR) << "Failed to create default sampler.";
229  return false;
230  }
231 
232  //------------------------------------------------------------------------------
233  /// Render to playground.
234  ///
235 
236  SinglePassCallback callback = [&](RenderPass& pass) {
237  auto texture = GetRenderedTextureFromDart(dart_function_name);
238  if (!texture) {
239  return false;
240  }
241 
242  auto buffer = HostBuffer::Create(
243  context->GetResourceAllocator(), context->GetIdleWaiter(),
244  context->GetCapabilities()->GetMinimumUniformAlignment());
245 
246  pass.SetVertexBuffer(texture_vtx_builder.CreateVertexBuffer(
247  *context->GetResourceAllocator()));
248 
249  TextureVS::UniformBuffer uniforms;
250  uniforms.mvp = Matrix();
251  TextureVS::BindUniformBuffer(pass, buffer->EmplaceUniform(uniforms));
252  TextureFS::BindTextureContents(pass, texture, sampler);
253 
254  pass.SetPipeline(pipeline);
255 
256  if (!pass.Draw().ok()) {
257  return false;
258  }
259  return true;
260  };
261  return OpenPlaygroundHere(callback);
262  }
263 
264  private:
265  std::unique_ptr<flutter::testing::AutoIsolateShutdown> CreateDartIsolate() {
266  const auto settings = CreateSettingsForFixture();
267  flutter::TaskRunners task_runners(flutter::testing::GetCurrentTestName(),
268  current_task_runner_, //
269  current_task_runner_, //
270  current_task_runner_, //
271  current_task_runner_ //
272  );
273  return flutter::testing::RunDartCodeInIsolate(
274  vm_ref_, settings, task_runners, "main", {},
275  flutter::testing::GetDefaultKernelFilePath());
276  }
277 
278  const flutter::Settings settings_;
279  flutter::DartVMRef vm_ref_;
280  fml::RefPtr<fml::TaskRunner> current_task_runner_;
281  std::unique_ptr<flutter::testing::AutoIsolateShutdown> isolate_;
282 
283  std::shared_ptr<Texture> received_texture_;
284 };
285 
287 
288 TEST_P(RendererDartTest, CanRunDartInPlaygroundFrame) {
289  SinglePassCallback callback = [&](RenderPass& pass) {
290  ImGui::Begin("Dart test", nullptr);
291  ImGui::Text(
292  "This test executes Dart code during the playground frame callback.");
293  ImGui::End();
294 
295  return RunDartFunction("sayHi");
296  };
297  ASSERT_TRUE(OpenPlaygroundHere(callback));
298 }
299 
300 /// These test entries correspond to Dart functions located in
301 /// `flutter/impeller/fixtures/dart_tests.dart`
302 
303 TEST_P(RendererDartTest, CanInstantiateFlutterGPUContext) {
304  ASSERT_TRUE(RunDartFunction("instantiateDefaultContext"));
305 }
306 
307 TEST_P(RendererDartTest, CanCreateShaderLibrary) {
308  ASSERT_TRUE(RunDartFunction("canCreateShaderLibrary"));
309 }
310 
311 TEST_P(RendererDartTest, CanReflectUniformStructs) {
312  ASSERT_TRUE(RunDartFunction("canReflectUniformStructs"));
313 }
314 
315 TEST_P(RendererDartTest, CanCreateRenderPassAndSubmit) {
316  ASSERT_TRUE(RenderDartToPlayground("canCreateRenderPassAndSubmit"));
317 }
318 
319 } // namespace testing
320 } // namespace impeller
static std::shared_ptr< HostBuffer > Create(const std::shared_ptr< Allocator > &allocator, const std::shared_ptr< const IdleWaiter > &idle_waiter, size_t minimum_uniform_alignment)
Definition: host_buffer.cc:21
bool OpenPlaygroundHere(const RenderCallback &render_callback)
Definition: playground.cc:287
bool IsPlaygroundEnabled() const
Definition: playground.cc:224
ISize GetWindowSize() const
Definition: playground.cc:267
std::function< bool(RenderPass &pass)> SinglePassCallback
Definition: playground.h:39
std::shared_ptr< Context > GetContext() const
Definition: playground.cc:126
Render passes encode render commands directed as one specific render target into an underlying comman...
Definition: render_pass.h:30
VertexBuffer CreateVertexBuffer(HostBuffer &data_host_buffer, HostBuffer &indexes_host_buffer) const
VertexBufferBuilder & AddVertices(std::initializer_list< VertexType_ > vertices)
bool RunDartFunctionWithWindowSize(const char *dart_function_name)
Invokes a Dart function with the window's width and height as arguments.
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...
bool RenderDartToPlayground(const char *dart_function_name)
Call a dart function that produces a texture and render the result in the playground.
bool RunDartFunction(const char *dart_function_name)
Invokes a Dart function.
flutter::testing::AutoIsolateShutdown * GetIsolate()
ScopedObject< Object > Create(CtorArgs &&... args)
Definition: object.h:161
static void InstantiateTestShaderLibrary(Context::BackendType backend_type)
TEST_P(AiksTest, DrawAtlasNoColor)
INSTANTIATE_PLAYGROUND_SUITE(AiksTest)
std::shared_ptr< ContextGLES > context
std::shared_ptr< PipelineGLES > pipeline
A 4x4 matrix using column-major storage.
Definition: matrix.h:37
An optional (but highly recommended) utility for creating pipelines from reflected shader information...