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 
31 namespace impeller {
32 namespace testing {
33 
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 
43  public flutter::testing::DartFixture {
44  public:
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 
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 
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
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
bool OpenPlaygroundHere(const RenderCallback &render_callback)
Definition: playground.cc:201
bool IsPlaygroundEnabled() const
Definition: playground.cc:147
ISize GetWindowSize() const
Definition: playground.cc:185
std::function< bool(RenderPass &pass)> SinglePassCallback
Definition: playground.h:50
std::shared_ptr< Context > GetContext() const
Definition: playground.cc:91
Render passes encode render commands directed as one specific render target into an underlying comman...
Definition: render_pass.h:30
VertexBuffer CreateVertexBuffer(HostBuffer &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)
A 4x4 matrix using column-major storage.
Definition: matrix.h:37
An optional (but highly recommended) utility for creating pipelines from reflected shader information...