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
15#include "fml/closure.h"
25#include "third_party/abseil-cpp/absl/base/no_destructor.h"
26
27#define GLFW_INCLUDE_NONE
28#include "third_party/glfw/include/GLFW/glfw3.h"
29
30namespace impeller {
31
32namespace {
33std::unique_ptr<PlaygroundImpl> MakeVulkanPlayground(bool enable_validations) {
34 FML_CHECK(::glfwInit() == GLFW_TRUE);
35 PlaygroundSwitches playground_switches;
36 playground_switches.enable_vulkan_validation = enable_validations;
38 playground_switches);
39}
40
41// Returns a static instance to a playground that can be used across tests.
42const std::unique_ptr<PlaygroundImpl>& GetSharedVulkanPlayground(
43 bool enable_validations) {
44 if (enable_validations) {
45 static absl::NoDestructor<std::unique_ptr<PlaygroundImpl>>
46 vulkan_validation_playground(
47 MakeVulkanPlayground(/*enable_validations=*/true));
48 // TODO(142237): This can be removed when the thread local storage is
49 // removed.
50 static fml::ScopedCleanupClosure context_cleanup(
51 [&] { (*vulkan_validation_playground)->GetContext()->Shutdown(); });
52 return *vulkan_validation_playground;
53 } else {
54 static absl::NoDestructor<std::unique_ptr<PlaygroundImpl>>
55 vulkan_playground(MakeVulkanPlayground(/*enable_validations=*/false));
56 // TODO(142237): This can be removed when the thread local storage is
57 // removed.
58 static fml::ScopedCleanupClosure context_cleanup(
59 [&] { (*vulkan_playground)->GetContext()->Shutdown(); });
60 return *vulkan_playground;
61 }
62}
63
64std::unique_ptr<PlaygroundImpl> MakeOpenGLESPlayground(bool use_sdfs = false) {
65 FML_CHECK(::glfwInit() == GLFW_TRUE);
66 PlaygroundSwitches playground_switches;
67 playground_switches.use_angle = true;
68 playground_switches.flags.use_sdfs = use_sdfs;
71 playground_switches);
72}
73
74// Returns a static instance to an OpenGL ES playground that can be used across
75// tests.
76const std::unique_ptr<PlaygroundImpl>& GetSharedOpenGLESPlayground(
77 bool use_sdfs) {
78 if (use_sdfs) {
79 static absl::NoDestructor<std::unique_ptr<PlaygroundImpl>>
80 opengl_playground(MakeOpenGLESPlayground(/*use_sdfs=*/true));
81 static fml::ScopedCleanupClosure context_cleanup(
82 [&] { (*opengl_playground)->GetContext()->Shutdown(); });
83 return *opengl_playground;
84 } else {
85 static absl::NoDestructor<std::unique_ptr<PlaygroundImpl>>
86 opengl_playground(MakeOpenGLESPlayground(/*use_sdfs=*/false));
87 // TODO(142237): This can be removed when the thread local storage is
88 // removed.
89 static fml::ScopedCleanupClosure context_cleanup(
90 [&] { (*opengl_playground)->GetContext()->Shutdown(); });
91 return *opengl_playground;
92 }
93}
94
95} // namespace
96
97#define IMP_AIKSTEST(name) \
98 "impeller_Play_AiksTest_" #name "_Metal", \
99 "impeller_Play_AiksTest_" #name "_OpenGLES", \
100 "impeller_Play_AiksTest_" #name "_Vulkan"
101
102// If you add a new playground test to the aiks unittests and you do not want it
103// to also be a golden test, then add the test name here.
104static const std::vector<std::string> kSkipTests = {
105 // TextRotated is flakey and we can't seem to get it to stabilize on Skia
106 // Gold.
107 IMP_AIKSTEST(TextRotated),
108 // Runtime stage based tests get confused with a Metal context.
109 "impeller_Play_AiksTest_CanRenderClippedRuntimeEffects_Vulkan",
110};
111
112namespace {
113std::string GetTestName() {
114 std::string suite_name =
115 ::testing::UnitTest::GetInstance()->current_test_suite()->name();
116 std::string test_name =
117 ::testing::UnitTest::GetInstance()->current_test_info()->name();
118 std::stringstream ss;
119 ss << "impeller_" << suite_name << "_" << test_name;
120 std::string result = ss.str();
121 // Make sure there are no slashes in the test name.
122 std::replace(result.begin(), result.end(), '/', '_');
123 return result;
124}
125
126std::string GetGoldenFilename(const std::string& postfix) {
127 return GetTestName() + postfix + ".png";
128}
129} // namespace
130
132 std::unique_ptr<testing::Screenshot> screenshot,
133 const std::string& postfix) {
134 if (!screenshot || !screenshot->GetBytes()) {
135 FML_LOG(ERROR) << "Failed to collect screenshot for test " << GetTestName();
136 return false;
137 }
138 std::string test_name = GetTestName();
139 std::string filename = GetGoldenFilename(postfix);
141 test_name, filename, screenshot->GetWidth(), screenshot->GetHeight());
142 if (!screenshot->WriteToPNG(
144 FML_LOG(ERROR) << "Failed to write screenshot to " << filename;
145 return false;
146 }
147 return true;
148}
149
151 std::unique_ptr<PlaygroundImpl> test_vulkan_playground;
152 std::unique_ptr<PlaygroundImpl> test_opengl_playground;
153 std::unique_ptr<testing::Screenshotter> screenshotter;
154 ISize window_size = ISize{1024, 768};
155};
156
160
162
164 std::shared_ptr<TypographerContext> typographer_context) {
165 typographer_context_ = std::move(typographer_context);
166};
167
169 ASSERT_FALSE(dlopen("/usr/local/lib/libMoltenVK.dylib", RTLD_NOLOAD));
170
171 auto context = GetContext();
172 if (context) {
173 context->DisposeThreadLocalCachedResources();
174 }
175}
176
177namespace {
178bool DoesSupportWideGamutTests() {
179#ifdef __arm64__
180 return true;
181#else
182 return false;
183#endif
184}
185} // namespace
186
188 std::filesystem::path testing_assets_path =
190 std::filesystem::path target_path = testing_assets_path.parent_path()
191 .parent_path()
192 .parent_path()
193 .parent_path();
194 std::filesystem::path icd_path = target_path / "vk_swiftshader_icd.json";
195 setenv("VK_ICD_FILENAMES", icd_path.c_str(), 1);
196
197 std::string test_name = GetTestName();
198 PlaygroundSwitches switches;
199 switches.enable_wide_gamut =
200 test_name.find("WideGamut_") != std::string::npos;
201 switches.flags.antialiased_lines =
202 test_name.find("ExperimentAntialiasLines_") != 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::MetalScreenshotter>(switches);
214 break;
216 if (switches.enable_wide_gamut) {
217 GTEST_SKIP() << "Vulkan doesn't support wide gamut golden tests.";
218 }
219 if (switches.flags.antialiased_lines) {
220 GTEST_SKIP()
221 << "Vulkan doesn't support antialiased lines golden tests.";
222 }
223 const std::unique_ptr<PlaygroundImpl>& playground =
224 GetSharedVulkanPlayground(/*enable_validations=*/true);
225 pimpl_->screenshotter =
226 std::make_unique<testing::VulkanScreenshotter>(playground);
227 break;
228 }
230 switches.flags.use_sdfs = true;
231 [[fallthrough]];
233 if (switches.enable_wide_gamut) {
234 GTEST_SKIP() << "OpenGLES doesn't support wide gamut golden tests.";
235 }
236 if (switches.flags.antialiased_lines) {
237 GTEST_SKIP()
238 << "OpenGLES doesn't support antialiased lines golden tests.";
239 }
240 const std::unique_ptr<PlaygroundImpl>& playground =
241 GetSharedOpenGLESPlayground(switches.flags.use_sdfs);
242 ::glfwMakeContextCurrent(
243 reinterpret_cast<GLFWwindow*>(playground->GetWindowHandle()));
244 pimpl_->screenshotter =
245 std::make_unique<testing::VulkanScreenshotter>(playground);
246 break;
247 }
248 }
249
250 if (std::find(kSkipTests.begin(), kSkipTests.end(), test_name) !=
251 kSkipTests.end()) {
252 GTEST_SKIP()
253 << "GoldenPlaygroundTest doesn't support interactive playground tests "
254 "yet.";
255 }
256
258 "gpu_string", GetContext()->DescribeGpuModel());
259}
260
262 return GetParam();
263}
264
267 AiksContext renderer(GetContext(), typographer_context_);
268
269 std::unique_ptr<testing::Screenshot> screenshot;
270 Point content_scale =
271 pimpl_->screenshotter->GetPlayground().GetContentScale();
272
273 ISize physical_window_size(
274 std::round(pimpl_->window_size.width * content_scale.x),
275 std::round(pimpl_->window_size.height * content_scale.y));
276 for (int i = 0; i < 2; ++i) {
277 auto display_list = callback();
278 auto texture =
279 DisplayListToTexture(display_list, physical_window_size, renderer);
280 screenshot = pimpl_->screenshotter->MakeScreenshot(renderer, texture);
281 }
282 return SaveScreenshot(std::move(screenshot));
283}
284
286 const sk_sp<flutter::DisplayList>& list) {
287 return OpenPlaygroundHere([&list]() { return list; });
288}
289
292 AiksContext renderer(GetContext(), typographer_context_);
293 std::shared_ptr<Context> context = GetContext();
294 Point content_scale =
295 pimpl_->screenshotter->GetPlayground().GetContentScale();
296 ISize size(std::round(pimpl_->window_size.width * content_scale.x),
297 std::round(pimpl_->window_size.height * content_scale.y));
298
299 std::unique_ptr<testing::Screenshot> screenshot;
300 // Render twice so the second pass observes warmed pipeline and resource
301 // caches, matching the display list path above.
302 for (int i = 0; i < 2; ++i) {
303 RenderTargetAllocator render_target_allocator(
304 context->GetResourceAllocator());
305 RenderTarget render_target = render_target_allocator.CreateOffscreen(
306 *context, size, /*mip_count=*/1, "Golden Render Pass",
308 /*stencil_attachment_config=*/std::nullopt);
309 if (!render_target.IsValid()) {
310 return false;
311 }
312 std::shared_ptr<CommandBuffer> command_buffer =
313 context->CreateCommandBuffer();
314 if (!command_buffer) {
315 return false;
316 }
317 std::shared_ptr<RenderPass> render_pass =
318 command_buffer->CreateRenderPass(render_target);
319 if (!render_pass) {
320 return false;
321 }
322 if (!callback(*render_pass)) {
323 return false;
324 }
325 if (!render_pass->EncodeCommands()) {
326 return false;
327 }
328 if (!context->GetCommandQueue()->Submit({command_buffer}).ok()) {
329 return false;
330 }
331 screenshot = pimpl_->screenshotter->MakeScreenshot(
332 renderer, render_target.GetRenderTargetTexture());
333 }
334 return SaveScreenshot(std::move(screenshot));
335}
336
338 bool* p_open,
339 ImGuiWindowFlags flags) {
340 return false;
341}
342
344 const char* fixture_name,
345 bool enable_mipmapping) const {
346 std::shared_ptr<fml::Mapping> mapping =
348 auto result = Playground::CreateTextureForMapping(GetContext(), mapping,
349 enable_mipmapping);
350 if (result) {
351 result->SetLabel(fixture_name);
352 }
353 return result;
354}
355
357 const char* fixture_name,
358 bool enable_mipmapping) const {
359 std::shared_ptr<Texture> texture =
360 CreateTextureForFixture(fixture_name, enable_mipmapping);
362}
363
364absl::StatusOr<RuntimeStage::Map> GoldenPlaygroundTest::OpenAssetAsRuntimeStage(
365 const char* asset_name) const {
366 const std::shared_ptr<fml::Mapping> fixture =
368 if (!fixture || fixture->GetSize() == 0) {
369 return absl::NotFoundError("Asset not found or empty.");
370 }
371 return RuntimeStage::DecodeRuntimeStages(fixture);
372}
373
374std::shared_ptr<Context> GoldenPlaygroundTest::GetContext() const {
375 if (!pimpl_->screenshotter) {
376 return nullptr;
377 }
378 return pimpl_->screenshotter->GetPlayground().GetContext();
379}
380
381std::shared_ptr<Context> GoldenPlaygroundTest::MakeContext() const {
382 if (GetParam() == PlaygroundBackend::kMetal ||
383 GetParam() == PlaygroundBackend::kMetalSDF) {
384 /// On Metal we create a context for each test.
385 return GetContext();
386 } else if (GetParam() == PlaygroundBackend::kVulkan) {
387 bool enable_vulkan_validations = true;
388 FML_CHECK(!pimpl_->test_vulkan_playground)
389 << "We don't support creating multiple contexts for one test";
390 pimpl_->test_vulkan_playground =
391 MakeVulkanPlayground(enable_vulkan_validations);
392 pimpl_->screenshotter = std::make_unique<testing::VulkanScreenshotter>(
393 pimpl_->test_vulkan_playground);
394 return pimpl_->test_vulkan_playground->GetContext();
395 } else if (GetParam() == PlaygroundBackend::kOpenGLES ||
396 GetParam() == PlaygroundBackend::kOpenGLESSDF) {
397 FML_CHECK(!pimpl_->test_opengl_playground)
398 << "We don't support creating multiple contexts for one test";
399 bool use_sdfs = (GetParam() == PlaygroundBackend::kOpenGLESSDF);
400 pimpl_->test_opengl_playground = MakeOpenGLESPlayground(use_sdfs);
401 pimpl_->screenshotter = std::make_unique<testing::VulkanScreenshotter>(
402 pimpl_->test_opengl_playground);
403 return pimpl_->test_opengl_playground->GetContext();
404 } else {
405 FML_CHECK(false);
406 return nullptr;
407 }
408}
409
411 return pimpl_->screenshotter->GetPlayground().GetContentScale();
412}
413
415 return 0.0f;
416}
417
419 return pimpl_->window_size;
420}
421
423 return IRect::MakeSize(pimpl_->window_size);
424}
425
426void GoldenPlaygroundTest::GoldenPlaygroundTest::SetWindowSize(ISize size) {
427 pimpl_->window_size = size;
428}
429
431 const std::shared_ptr<Capabilities>& capabilities) {
432 return pimpl_->screenshotter->GetPlayground().SetCapabilities(capabilities);
433}
434
435std::unique_ptr<testing::Screenshot> GoldenPlaygroundTest::MakeScreenshot(
436 const sk_sp<flutter::DisplayList>& list) {
437 AiksContext renderer(GetContext(), typographer_context_);
438 Point content_scale =
439 pimpl_->screenshotter->GetPlayground().GetContentScale();
440
441 ISize physical_window_size(
442 std::round(pimpl_->window_size.width * content_scale.x),
443 std::round(pimpl_->window_size.height * content_scale.y));
444 return pimpl_->screenshotter->MakeScreenshot(
445 renderer, DisplayListToTexture(list, physical_window_size, renderer));
446}
447
449 return pimpl_->screenshotter->GetPlayground().GetRuntimeStageBackend();
450}
451
452} // 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
static bool SaveScreenshot(std::unique_ptr< testing::Screenshot > screenshot, const std::string &postfix="")
std::unique_ptr< testing::Screenshot > MakeScreenshot(const sk_sp< flutter::DisplayList > &list)
bool OpenPlaygroundHere(Picture picture)
static bool ImGuiBegin(const char *name, bool *p_open, ImGuiWindowFlags flags)
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
std::function< bool(RenderPass &pass)> SinglePassCallback
Definition playground.h:38
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
const char * name
Definition fuchsia.cc:50
#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:26
std::shared_ptr< ContextGLES > context
std::shared_ptr< RenderPass > render_pass
std::shared_ptr< CommandBuffer > command_buffer
bool antialiased_lines
When turned on DrawLine will use the experimental antialiased path.
Definition flags.h:11
bool use_sdfs
Use SDFs for rendering.
Definition flags.h:13
static constexpr TRect MakeSize(const TSize< U > &size)
Definition rect.h:150