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(
228  context->GetResourceAllocator(), context->GetIdleWaiter(),
229  context->GetCapabilities()->GetMinimumUniformAlignment());
230 
231  pass.SetVertexBuffer(texture_vtx_builder.CreateVertexBuffer(
232  *context->GetResourceAllocator()));
233 
234  TextureVS::UniformBuffer uniforms;
235  uniforms.mvp = Matrix();
236  TextureVS::BindUniformBuffer(pass, buffer->EmplaceUniform(uniforms));
237  TextureFS::BindTextureContents(pass, texture, sampler);
238 
239  pass.SetPipeline(pipeline);
240 
241  if (!pass.Draw().ok()) {
242  return false;
243  }
244  return true;
245  };
246  return OpenPlaygroundHere(callback);
247  }
248 
249  private:
250  std::unique_ptr<flutter::testing::AutoIsolateShutdown> CreateDartIsolate() {
251  const auto settings = CreateSettingsForFixture();
252  flutter::TaskRunners task_runners(flutter::testing::GetCurrentTestName(),
253  current_task_runner_, //
254  current_task_runner_, //
255  current_task_runner_, //
256  current_task_runner_ //
257  );
258  return flutter::testing::RunDartCodeInIsolate(
259  vm_ref_, settings, task_runners, "main", {},
260  flutter::testing::GetDefaultKernelFilePath());
261  }
262 
263  const flutter::Settings settings_;
264  flutter::DartVMRef vm_ref_;
265  fml::RefPtr<fml::TaskRunner> current_task_runner_;
266  std::unique_ptr<flutter::testing::AutoIsolateShutdown> isolate_;
267 
268  std::shared_ptr<Texture> received_texture_;
269 };
270 
272 
273 TEST_P(RendererDartTest, CanRunDartInPlaygroundFrame) {
274  SinglePassCallback callback = [&](RenderPass& pass) {
275  ImGui::Begin("Dart test", nullptr);
276  ImGui::Text(
277  "This test executes Dart code during the playground frame callback.");
278  ImGui::End();
279 
280  return RunDartFunction("sayHi");
281  };
282  ASSERT_TRUE(OpenPlaygroundHere(callback));
283 }
284 
285 /// These test entries correspond to Dart functions located in
286 /// `flutter/impeller/fixtures/dart_tests.dart`
287 
288 TEST_P(RendererDartTest, CanInstantiateFlutterGPUContext) {
289  ASSERT_TRUE(RunDartFunction("instantiateDefaultContext"));
290 }
291 
292 TEST_P(RendererDartTest, CanCreateShaderLibrary) {
293  ASSERT_TRUE(RunDartFunction("canCreateShaderLibrary"));
294 }
295 
296 TEST_P(RendererDartTest, CanReflectUniformStructs) {
297  ASSERT_TRUE(RunDartFunction("canReflectUniformStructs"));
298 }
299 
300 TEST_P(RendererDartTest, CanCreateRenderPassAndSubmit) {
301  ASSERT_TRUE(RenderDartToPlayground("canCreateRenderPassAndSubmit"));
302 }
303 
304 } // namespace testing
305 } // 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: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...