Flutter Engine Uber Docs
Docs for the entire Flutter Engine repo.
 
Loading...
Searching...
No Matches
golden_playground_test_mac.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#include <dlfcn.h>
6#include <filesystem>
7#include <memory>
8
11
16#include "fml/closure.h"
27#include "third_party/abseil-cpp/absl/base/no_destructor.h"
28
29#define GLFW_INCLUDE_NONE
30#include "third_party/glfw/include/GLFW/glfw3.h"
31
32namespace impeller {
33
34namespace {
35std::unique_ptr<PlaygroundImpl> MakeVulkanPlayground(bool enable_validations) {
36 FML_CHECK(::glfwInit() == GLFW_TRUE);
37 PlaygroundSwitches playground_switches;
38 playground_switches.enable_vulkan_validation = enable_validations;
40 playground_switches);
41}
42
43// Returns a static instance to a playground that can be used across tests.
44const std::unique_ptr<PlaygroundImpl>& GetSharedVulkanPlayground(
45 bool enable_validations) {
46 if (enable_validations) {
47 static absl::NoDestructor<std::unique_ptr<PlaygroundImpl>>
48 vulkan_validation_playground(
49 MakeVulkanPlayground(/*enable_validations=*/true));
50 // TODO(142237): This can be removed when the thread local storage is
51 // removed.
52 static fml::ScopedCleanupClosure context_cleanup(
53 [&] { (*vulkan_validation_playground)->GetContext()->Shutdown(); });
54 return *vulkan_validation_playground;
55 } else {
56 static absl::NoDestructor<std::unique_ptr<PlaygroundImpl>>
57 vulkan_playground(MakeVulkanPlayground(/*enable_validations=*/false));
58 // TODO(142237): This can be removed when the thread local storage is
59 // removed.
60 static fml::ScopedCleanupClosure context_cleanup(
61 [&] { (*vulkan_playground)->GetContext()->Shutdown(); });
62 return *vulkan_playground;
63 }
64}
65
66std::unique_ptr<PlaygroundImpl> MakeOpenGLESPlayground(bool use_sdfs = false) {
67 FML_CHECK(::glfwInit() == GLFW_TRUE);
68 PlaygroundSwitches playground_switches;
69 playground_switches.use_angle = true;
70 playground_switches.flags.use_sdfs = use_sdfs;
73 playground_switches);
74}
75
76// Returns a static instance to an OpenGL ES playground that can be used across
77// tests.
78const std::unique_ptr<PlaygroundImpl>& GetSharedOpenGLESPlayground(
79 bool use_sdfs) {
80 if (use_sdfs) {
81 static absl::NoDestructor<std::unique_ptr<PlaygroundImpl>>
82 opengl_playground(MakeOpenGLESPlayground(/*use_sdfs=*/true));
83 static fml::ScopedCleanupClosure context_cleanup(
84 [&] { (*opengl_playground)->GetContext()->Shutdown(); });
85 return *opengl_playground;
86 } else {
87 static absl::NoDestructor<std::unique_ptr<PlaygroundImpl>>
88 opengl_playground(MakeOpenGLESPlayground(/*use_sdfs=*/false));
89 // TODO(142237): This can be removed when the thread local storage is
90 // removed.
91 static fml::ScopedCleanupClosure context_cleanup(
92 [&] { (*opengl_playground)->GetContext()->Shutdown(); });
93 return *opengl_playground;
94 }
95}
96
97} // namespace
98
99#define IMP_AIKSTEST(name) \
100 "impeller_Play_AiksTest_" #name "_Metal", \
101 "impeller_Play_AiksTest_" #name "_OpenGLES", \
102 "impeller_Play_AiksTest_" #name "_Vulkan"
103
104// If you add a new playground test to the aiks unittests and you do not want it
105// to also be a golden test, then add the test name here.
106static const std::vector<std::string> kSkipTests = {
107 // TextRotated is flakey and we can't seem to get it to stabilize on Skia
108 // Gold.
109 IMP_AIKSTEST(TextRotated),
110 // Runtime stage based tests get confused with a Metal context.
111 "impeller_Play_AiksTest_CanRenderClippedRuntimeEffects_Vulkan",
112};
113
114namespace {
115std::string GetTestName() {
116 std::string suite_name =
117 ::testing::UnitTest::GetInstance()->current_test_suite()->name();
118 std::string test_name =
119 ::testing::UnitTest::GetInstance()->current_test_info()->name();
120 std::stringstream ss;
121 ss << "impeller_" << suite_name << "_" << test_name;
122 std::string result = ss.str();
123 // Make sure there are no slashes in the test name.
124 std::replace(result.begin(), result.end(), '/', '_');
125 return result;
126}
127
128std::string GetGoldenFilename(const std::string& postfix) {
129 return GetTestName() + postfix + ".png";
130}
131} // namespace
132
134 std::unique_ptr<testing::Screenshot> screenshot,
135 const std::string& postfix) {
136 if (!screenshot || !screenshot->GetBytes()) {
137 FML_LOG(ERROR) << "Failed to collect screenshot for test " << GetTestName();
138 return false;
139 }
140 std::string test_name = GetTestName();
141 std::string filename = GetGoldenFilename(postfix);
143 test_name, filename, screenshot->GetWidth(), screenshot->GetHeight());
144 if (!screenshot->WriteToPNG(
146 FML_LOG(ERROR) << "Failed to write screenshot to " << filename;
147 return false;
148 }
149 return true;
150}
151
153 std::unique_ptr<PlaygroundImpl> test_vulkan_playground;
154 std::unique_ptr<PlaygroundImpl> test_opengl_playground;
155 std::unique_ptr<testing::GoldenScreenshotter> screenshotter;
156 ISize window_size = ISize{1024, 768};
157};
158
162
164
166 std::shared_ptr<TypographerContext> typographer_context) {
167 typographer_context_ = std::move(typographer_context);
168};
169
171 ASSERT_FALSE(dlopen("/usr/local/lib/libMoltenVK.dylib", RTLD_NOLOAD));
172
173 auto context = GetContext();
174 if (context) {
175 context->DisposeThreadLocalCachedResources();
176 }
177}
178
179namespace {
180bool DoesSupportWideGamutTests() {
181#ifdef __arm64__
182 return true;
183#else
184 return false;
185#endif
186}
187} // namespace
188
190 std::filesystem::path testing_assets_path =
192 std::filesystem::path target_path = testing_assets_path.parent_path()
193 .parent_path()
194 .parent_path()
195 .parent_path();
196 std::filesystem::path icd_path = target_path / "vk_swiftshader_icd.json";
197 setenv("VK_ICD_FILENAMES", icd_path.c_str(), 1);
198
199 std::string test_name = GetTestName();
200 PlaygroundSwitches switches;
201 switches.enable_wide_gamut =
202 test_name.find("WideGamut_") != std::string::npos;
203 switch (GetParam()) {
205 switches.flags.use_sdfs = true;
206 [[fallthrough]];
208 if (!DoesSupportWideGamutTests()) {
209 GTEST_SKIP()
210 << "This metal device doesn't support wide gamut golden tests.";
211 }
212 pimpl_->screenshotter =
213 std::make_unique<testing::MetalGoldenScreenshotter>(switches);
214 break;
216 if (switches.enable_wide_gamut) {
217 GTEST_SKIP() << "Vulkan doesn't support wide gamut golden tests.";
218 }
219 const std::unique_ptr<PlaygroundImpl>& playground =
220 GetSharedVulkanPlayground(/*enable_validations=*/true);
221 pimpl_->screenshotter =
222 std::make_unique<testing::VulkanGoldenScreenshotter>(playground);
223 break;
224 }
226 switches.flags.use_sdfs = true;
227 [[fallthrough]];
229 if (switches.enable_wide_gamut) {
230 GTEST_SKIP() << "OpenGLES doesn't support wide gamut golden tests.";
231 }
232 const std::unique_ptr<PlaygroundImpl>& playground =
233 GetSharedOpenGLESPlayground(switches.flags.use_sdfs);
234 ::glfwMakeContextCurrent(
235 reinterpret_cast<GLFWwindow*>(playground->GetWindowHandle()));
236 pimpl_->screenshotter =
237 std::make_unique<testing::VulkanGoldenScreenshotter>(playground);
238 break;
239 }
240 }
241
242 if (std::find(kSkipTests.begin(), kSkipTests.end(), test_name) !=
243 kSkipTests.end()) {
244 GTEST_SKIP()
245 << "GoldenPlaygroundTest doesn't support interactive playground tests "
246 "yet.";
247 }
248
250 "gpu_string", GetContext()->DescribeGpuModel());
251}
252
254 return GetParam();
255}
256
259 AiksContext renderer(GetContext(), typographer_context_);
260
261 std::unique_ptr<testing::Screenshot> screenshot;
262 Point content_scale =
263 pimpl_->screenshotter->GetPlayground().GetContentScale();
264
265 ISize physical_window_size(
266 std::round(pimpl_->window_size.width * content_scale.x),
267 std::round(pimpl_->window_size.height * content_scale.y));
268 for (int i = 0; i < 2; ++i) {
269 auto display_list = callback();
270 auto texture =
271 DisplayListToTexture(display_list, physical_window_size, renderer);
272 screenshot = pimpl_->screenshotter->MakeScreenshot(renderer, texture);
273 }
274 return SaveScreenshot(std::move(screenshot));
275}
276
278 const sk_sp<flutter::DisplayList>& list) {
279 return OpenPlaygroundHere([&list]() { return list; });
280}
281
284 AiksContext renderer(GetContext(), typographer_context_);
285 std::shared_ptr<Context> context = GetContext();
286 Point content_scale =
287 pimpl_->screenshotter->GetPlayground().GetContentScale();
288 ISize size(std::round(pimpl_->window_size.width * content_scale.x),
289 std::round(pimpl_->window_size.height * content_scale.y));
290
291 std::unique_ptr<testing::Screenshot> screenshot;
292 // Render twice so the second pass observes warmed pipeline and resource
293 // caches, matching the display list path above.
294 for (int i = 0; i < 2; ++i) {
295 RenderTargetAllocator render_target_allocator(
296 context->GetResourceAllocator());
297 RenderTarget render_target = render_target_allocator.CreateOffscreen(
298 *context, size, /*mip_count=*/1, "Golden Render Pass",
300 /*stencil_attachment_config=*/std::nullopt);
301 if (!render_target.IsValid()) {
302 return false;
303 }
304 std::shared_ptr<CommandBuffer> command_buffer =
305 context->CreateCommandBuffer();
306 if (!command_buffer) {
307 return false;
308 }
309 std::shared_ptr<RenderPass> render_pass =
310 command_buffer->CreateRenderPass(render_target);
311 if (!render_pass) {
312 return false;
313 }
314 if (!callback(*render_pass)) {
315 return false;
316 }
317 if (!render_pass->EncodeCommands()) {
318 return false;
319 }
320 if (!context->GetCommandQueue()->Submit({command_buffer}).ok()) {
321 return false;
322 }
323 screenshot = pimpl_->screenshotter->MakeScreenshot(
324 renderer, render_target.GetRenderTargetTexture());
325 }
326 return SaveScreenshot(std::move(screenshot));
327}
328
330 const char* fixture_name,
331 bool enable_mipmapping) const {
332 std::shared_ptr<fml::Mapping> mapping =
334 auto result = Playground::CreateTextureForMapping(GetContext(), mapping,
335 enable_mipmapping);
336 if (result) {
337 result->SetLabel(fixture_name);
338 }
339 return result;
340}
341
343 const char* fixture_name,
344 bool enable_mipmapping) const {
345 std::shared_ptr<Texture> texture =
346 CreateTextureForFixture(fixture_name, enable_mipmapping);
348}
349
350absl::StatusOr<RuntimeStage::Map> GoldenPlaygroundTest::OpenAssetAsRuntimeStage(
351 const char* asset_name) const {
352 const std::shared_ptr<fml::Mapping> fixture =
354 if (!fixture || fixture->GetSize() == 0) {
355 return absl::NotFoundError("Asset not found or empty.");
356 }
357 return RuntimeStage::DecodeRuntimeStages(fixture);
358}
359
360std::shared_ptr<Context> GoldenPlaygroundTest::GetContext() const {
361 if (!pimpl_->screenshotter) {
362 return nullptr;
363 }
364 return pimpl_->screenshotter->GetPlayground().GetContext();
365}
366
367std::shared_ptr<Context> GoldenPlaygroundTest::MakeContext() const {
368 if (GetParam() == PlaygroundBackend::kMetal ||
369 GetParam() == PlaygroundBackend::kMetalSDF) {
370 /// On Metal we create a context for each test.
371 return GetContext();
372 } else if (GetParam() == PlaygroundBackend::kVulkan) {
373 bool enable_vulkan_validations = true;
374 FML_CHECK(!pimpl_->test_vulkan_playground)
375 << "We don't support creating multiple contexts for one test";
376 pimpl_->test_vulkan_playground =
377 MakeVulkanPlayground(enable_vulkan_validations);
378 pimpl_->screenshotter =
379 std::make_unique<testing::VulkanGoldenScreenshotter>(
380 pimpl_->test_vulkan_playground);
381 return pimpl_->test_vulkan_playground->GetContext();
382 } else if (GetParam() == PlaygroundBackend::kOpenGLES ||
383 GetParam() == PlaygroundBackend::kOpenGLESSDF) {
384 FML_CHECK(!pimpl_->test_opengl_playground)
385 << "We don't support creating multiple contexts for one test";
386 bool use_sdfs = (GetParam() == PlaygroundBackend::kOpenGLESSDF);
387 pimpl_->test_opengl_playground = MakeOpenGLESPlayground(use_sdfs);
388 pimpl_->screenshotter =
389 std::make_unique<testing::VulkanGoldenScreenshotter>(
390 pimpl_->test_opengl_playground);
391 return pimpl_->test_opengl_playground->GetContext();
392 } else {
393 FML_CHECK(false);
394 return nullptr;
395 }
396}
397
399 return pimpl_->screenshotter->GetPlayground().GetContentScale();
400}
401
403 return 0.0f;
404}
405
407 return pimpl_->window_size;
408}
409
411 return IRect::MakeSize(pimpl_->window_size);
412}
413
414void GoldenPlaygroundTest::GoldenPlaygroundTest::SetWindowSize(ISize size) {
415 pimpl_->window_size = size;
416}
417
419 const std::shared_ptr<Capabilities>& capabilities) {
420 return pimpl_->screenshotter->GetPlayground().SetCapabilities(capabilities);
421}
422
423std::unique_ptr<testing::Screenshot> GoldenPlaygroundTest::MakeScreenshot(
424 const sk_sp<flutter::DisplayList>& list) {
425 AiksContext renderer(GetContext(), typographer_context_);
426 Point content_scale =
427 pimpl_->screenshotter->GetPlayground().GetContentScale();
428
429 ISize physical_window_size(
430 std::round(pimpl_->window_size.width * content_scale.x),
431 std::round(pimpl_->window_size.height * content_scale.y));
432 return pimpl_->screenshotter->MakeScreenshot(
433 renderer, DisplayListToTexture(list, physical_window_size, renderer));
434}
435
437 return pimpl_->screenshotter->GetPlayground().GetRuntimeStageBackend();
438}
439
441 if (!write_golden) {
442 // If we don't write a golden, we really don't have anything to do.
443 GTEST_SKIP() << "Test does not want a golden image written";
444 }
445}
446
448 PipelineDescriptor& desc) const {
449 // Match the golden harness render target: single-sampled, no depth/stencil.
450 // `ClearStencilAttachments` also resets the stencil pixel format on the
451 // pipeline, which Metal validation requires to match the target's lack of a
452 // stencil texture; `SetStencilAttachmentDescriptors(nullopt)` alone leaves
453 // the format set and trips that validation.
457 return true;
458}
459
460} // namespace impeller
Wraps a closure that is invoked in the destructor unless released by the caller.
Definition closure.h:32
static sk_sp< DlImageImpeller > Make(std::shared_ptr< Texture > texture, OwningContext owning_context=OwningContext::kIO)
RuntimeStageBackend GetRuntimeStageBackend() const
sk_sp< flutter::DlImage > CreateDlImageForFixture(const char *fixture_name, bool enable_mipmapping=false) const
fml::Status SetCapabilities(const std::shared_ptr< Capabilities > &capabilities)
void SetTypographerContext(std::shared_ptr< TypographerContext > typographer_context)
std::shared_ptr< Context > MakeContext() const
void SetEnableWriteGolden(bool write_golden)
Sets a particular test to either write a golden or not.
static bool SaveScreenshot(std::unique_ptr< testing::Screenshot > screenshot, const std::string &postfix="")
bool InitializePipelineDescriptorForRendering(PipelineDescriptor &desc) const
Initializes the provided |PipelineDescriptor| with appropriate default values to match the conditions...
std::unique_ptr< testing::Screenshot > MakeScreenshot(const sk_sp< flutter::DisplayList > &list)
bool OpenPlaygroundHere(Picture picture)
std::function< sk_sp< flutter::DisplayList >()> AiksDlPlaygroundCallback
absl::StatusOr< RuntimeStage::Map > OpenAssetAsRuntimeStage(const char *asset_name) const
std::shared_ptr< Context > GetContext() const
std::shared_ptr< Texture > CreateTextureForFixture(const char *fixture_name, bool enable_mipmapping=false) const
PipelineDescriptor & SetSampleCount(SampleCount samples)
std::function< bool(RenderPass &pass)> SinglePassCallback
Definition playground.h:44
static std::shared_ptr< Texture > CreateTextureForMapping(const std::shared_ptr< Context > &context, std::shared_ptr< fml::Mapping > mapping, bool enable_mipmapping=false)
static std::unique_ptr< PlaygroundImpl > Create(PlaygroundBackend backend, PlaygroundSwitches switches)
a wrapper around the impeller [Allocator] instance that can be used to provide caching of allocated r...
virtual RenderTarget CreateOffscreen(const Context &context, ISize size, int mip_count, std::string_view label="Offscreen", RenderTarget::AttachmentConfig color_attachment_config=RenderTarget::kDefaultColorAttachmentConfig, std::optional< RenderTarget::AttachmentConfig > stencil_attachment_config=RenderTarget::kDefaultStencilAttachmentConfig, const std::shared_ptr< Texture > &existing_color_texture=nullptr, const std::shared_ptr< Texture > &existing_depth_stencil_texture=nullptr, std::optional< PixelFormat > target_pixel_format=std::nullopt)
std::shared_ptr< Texture > GetRenderTargetTexture() const
static constexpr AttachmentConfig kDefaultColorAttachmentConfig
static absl::StatusOr< Map > DecodeRuntimeStages(const std::shared_ptr< fml::Mapping > &payload)
static GoldenDigest * Instance()
void AddDimension(const std::string &name, const std::string &value)
void AddImage(const std::string &test_name, const std::string &filename, int32_t width, int32_t height)
std::string GetFilenamePath(const std::string &filename) const
static WorkingDirectory * Instance()
#define GLFW_TRUE
FlutterDesktopBinaryReply callback
#define FML_LOG(severity)
Definition logging.h:101
#define FML_CHECK(condition)
Definition logging.h:104
#define IMP_AIKSTEST(name)
FlTexture * texture
const char * GetTestingAssetsPath()
Returns the directory containing assets shared across all tests.
std::unique_ptr< fml::Mapping > OpenFixtureAsMapping(const std::string &fixture_name)
Opens a fixture of the given file name and returns a mapping to its contents.
Definition testing.cc:58
static const std::vector< std::string > kSkipTests
float Scalar
Definition scalar.h:19
std::shared_ptr< Texture > DisplayListToTexture(const sk_sp< flutter::DisplayList > &display_list, ISize size, AiksContext &context, bool reset_host_buffer, bool generate_mips, std::optional< PixelFormat > target_pixel_format)
Render the provided display list to a texture with the given size.
PlaygroundBackend
Definition playground.h:32
std::shared_ptr< ContextGLES > context
std::shared_ptr< RenderPass > render_pass
std::shared_ptr< CommandBuffer > command_buffer
bool use_sdfs
Use SDFs for rendering.
Definition flags.h:11
std::unique_ptr< testing::GoldenScreenshotter > screenshotter
static constexpr TRect MakeSize(const TSize< U > &size)
Definition rect.h:150