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
64} // namespace
65
66#define IMP_AIKSTEST(name) \
67 "impeller_Play_AiksTest_" #name "_Metal", \
68 "impeller_Play_AiksTest_" #name "_OpenGLES", \
69 "impeller_Play_AiksTest_" #name "_Vulkan"
70
71// If you add a new playground test to the aiks unittests and you do not want it
72// to also be a golden test, then add the test name here.
73static const std::vector<std::string> kSkipTests = {
74 // TextRotated is flakey and we can't seem to get it to stabilize on Skia
75 // Gold.
76 IMP_AIKSTEST(TextRotated),
77 // Runtime stage based tests get confused with a Metal context.
78 "impeller_Play_AiksTest_CanRenderClippedRuntimeEffects_Vulkan",
79};
80
81namespace {
82std::string GetTestName() {
83 std::string suite_name =
84 ::testing::UnitTest::GetInstance()->current_test_suite()->name();
85 std::string test_name =
86 ::testing::UnitTest::GetInstance()->current_test_info()->name();
87 std::stringstream ss;
88 ss << "impeller_" << suite_name << "_" << test_name;
89 std::string result = ss.str();
90 // Make sure there are no slashes in the test name.
91 std::replace(result.begin(), result.end(), '/', '_');
92 return result;
93}
94
95std::string GetGoldenFilename(const std::string& postfix) {
96 return GetTestName() + postfix + ".png";
97}
98} // namespace
99
101 std::unique_ptr<testing::Screenshot> screenshot,
102 const std::string& postfix) {
103 if (!screenshot || !screenshot->GetBytes()) {
104 FML_LOG(ERROR) << "Failed to collect screenshot for test " << GetTestName();
105 return false;
106 }
107 std::string test_name = GetTestName();
108 std::string filename = GetGoldenFilename(postfix);
110 test_name, filename, screenshot->GetWidth(), screenshot->GetHeight());
111 if (!screenshot->WriteToPNG(
113 FML_LOG(ERROR) << "Failed to write screenshot to " << filename;
114 return false;
115 }
116 return true;
117}
118
120 std::unique_ptr<PlaygroundImpl> test_vulkan_playground;
121 std::unique_ptr<PlaygroundImpl> test_opengl_playground;
122 std::unique_ptr<testing::Screenshotter> screenshotter;
123 ISize window_size = ISize{1024, 768};
124};
125
129
131
133 std::shared_ptr<TypographerContext> typographer_context) {
134 typographer_context_ = std::move(typographer_context);
135};
136
138 ASSERT_FALSE(dlopen("/usr/local/lib/libMoltenVK.dylib", RTLD_NOLOAD));
139
140 auto context = GetContext();
141 if (context) {
142 context->DisposeThreadLocalCachedResources();
143 }
144}
145
146namespace {
147bool DoesSupportWideGamutTests() {
148#ifdef __arm64__
149 return true;
150#else
151 return false;
152#endif
153}
154} // namespace
155
157 std::filesystem::path testing_assets_path =
159 std::filesystem::path target_path = testing_assets_path.parent_path()
160 .parent_path()
161 .parent_path()
162 .parent_path();
163 std::filesystem::path icd_path = target_path / "vk_swiftshader_icd.json";
164 setenv("VK_ICD_FILENAMES", icd_path.c_str(), 1);
165
166 std::string test_name = GetTestName();
167 PlaygroundSwitches switches;
168 switches.enable_wide_gamut =
169 test_name.find("WideGamut_") != std::string::npos;
170 switches.flags.antialiased_lines =
171 test_name.find("ExperimentAntialiasLines_") != std::string::npos;
172 switch (GetParam()) {
174 switches.flags.use_sdfs = true;
175 [[fallthrough]];
177 if (!DoesSupportWideGamutTests()) {
178 GTEST_SKIP()
179 << "This metal device doesn't support wide gamut golden tests.";
180 }
181 pimpl_->screenshotter =
182 std::make_unique<testing::MetalScreenshotter>(switches);
183 break;
185 if (switches.enable_wide_gamut) {
186 GTEST_SKIP() << "Vulkan doesn't support wide gamut golden tests.";
187 }
188 if (switches.flags.antialiased_lines) {
189 GTEST_SKIP()
190 << "Vulkan doesn't support antialiased lines golden tests.";
191 }
192 const std::unique_ptr<PlaygroundImpl>& playground =
193 GetSharedVulkanPlayground(/*enable_validations=*/true);
194 pimpl_->screenshotter =
195 std::make_unique<testing::VulkanScreenshotter>(playground);
196 break;
197 }
199 if (switches.enable_wide_gamut) {
200 GTEST_SKIP() << "OpenGLES doesn't support wide gamut golden tests.";
201 }
202 if (switches.flags.antialiased_lines) {
203 GTEST_SKIP()
204 << "OpenGLES doesn't support antialiased lines golden tests.";
205 }
206 FML_CHECK(::glfwInit() == GLFW_TRUE);
207 PlaygroundSwitches playground_switches;
208 playground_switches.use_angle = true;
209 pimpl_->test_opengl_playground = PlaygroundImpl::Create(
210 PlaygroundBackend::kOpenGLES, playground_switches);
211 pimpl_->screenshotter = std::make_unique<testing::VulkanScreenshotter>(
212 pimpl_->test_opengl_playground);
213 break;
214 }
215 }
216
217 if (std::find(kSkipTests.begin(), kSkipTests.end(), test_name) !=
218 kSkipTests.end()) {
219 GTEST_SKIP()
220 << "GoldenPlaygroundTest doesn't support interactive playground tests "
221 "yet.";
222 }
223
225 "gpu_string", GetContext()->DescribeGpuModel());
226}
227
229 return GetParam();
230}
231
234 AiksContext renderer(GetContext(), typographer_context_);
235
236 std::unique_ptr<testing::Screenshot> screenshot;
237 Point content_scale =
238 pimpl_->screenshotter->GetPlayground().GetContentScale();
239
240 ISize physical_window_size(
241 std::round(pimpl_->window_size.width * content_scale.x),
242 std::round(pimpl_->window_size.height * content_scale.y));
243 for (int i = 0; i < 2; ++i) {
244 auto display_list = callback();
245 auto texture =
246 DisplayListToTexture(display_list, physical_window_size, renderer);
247 screenshot = pimpl_->screenshotter->MakeScreenshot(renderer, texture);
248 }
249 return SaveScreenshot(std::move(screenshot));
250}
251
253 const sk_sp<flutter::DisplayList>& list) {
254 return OpenPlaygroundHere([&list]() { return list; });
255}
256
259 AiksContext renderer(GetContext(), typographer_context_);
260 std::shared_ptr<Context> context = GetContext();
261 Point content_scale =
262 pimpl_->screenshotter->GetPlayground().GetContentScale();
263 ISize size(std::round(pimpl_->window_size.width * content_scale.x),
264 std::round(pimpl_->window_size.height * content_scale.y));
265
266 std::unique_ptr<testing::Screenshot> screenshot;
267 // Render twice so the second pass observes warmed pipeline and resource
268 // caches, matching the display list path above.
269 for (int i = 0; i < 2; ++i) {
270 RenderTargetAllocator render_target_allocator(
271 context->GetResourceAllocator());
272 RenderTarget render_target = render_target_allocator.CreateOffscreen(
273 *context, size, /*mip_count=*/1, "Golden Render Pass",
275 /*stencil_attachment_config=*/std::nullopt);
276 if (!render_target.IsValid()) {
277 return false;
278 }
279 std::shared_ptr<CommandBuffer> command_buffer =
280 context->CreateCommandBuffer();
281 if (!command_buffer) {
282 return false;
283 }
284 std::shared_ptr<RenderPass> render_pass =
285 command_buffer->CreateRenderPass(render_target);
286 if (!render_pass) {
287 return false;
288 }
289 if (!callback(*render_pass)) {
290 return false;
291 }
292 if (!render_pass->EncodeCommands()) {
293 return false;
294 }
295 if (!context->GetCommandQueue()->Submit({command_buffer}).ok()) {
296 return false;
297 }
298 screenshot = pimpl_->screenshotter->MakeScreenshot(
299 renderer, render_target.GetRenderTargetTexture());
300 }
301 return SaveScreenshot(std::move(screenshot));
302}
303
305 bool* p_open,
306 ImGuiWindowFlags flags) {
307 return false;
308}
309
311 const char* fixture_name,
312 bool enable_mipmapping) const {
313 std::shared_ptr<fml::Mapping> mapping =
315 auto result = Playground::CreateTextureForMapping(GetContext(), mapping,
316 enable_mipmapping);
317 if (result) {
318 result->SetLabel(fixture_name);
319 }
320 return result;
321}
322
324 const char* fixture_name,
325 bool enable_mipmapping) const {
326 std::shared_ptr<Texture> texture =
327 CreateTextureForFixture(fixture_name, enable_mipmapping);
329}
330
331absl::StatusOr<RuntimeStage::Map> GoldenPlaygroundTest::OpenAssetAsRuntimeStage(
332 const char* asset_name) const {
333 const std::shared_ptr<fml::Mapping> fixture =
335 if (!fixture || fixture->GetSize() == 0) {
336 return absl::NotFoundError("Asset not found or empty.");
337 }
338 return RuntimeStage::DecodeRuntimeStages(fixture);
339}
340
341std::shared_ptr<Context> GoldenPlaygroundTest::GetContext() const {
342 if (!pimpl_->screenshotter) {
343 return nullptr;
344 }
345 return pimpl_->screenshotter->GetPlayground().GetContext();
346}
347
348std::shared_ptr<Context> GoldenPlaygroundTest::MakeContext() const {
349 if (GetParam() == PlaygroundBackend::kMetal) {
350 /// On Metal we create a context for each test.
351 return GetContext();
352 } else if (GetParam() == PlaygroundBackend::kVulkan) {
353 bool enable_vulkan_validations = true;
354 FML_CHECK(!pimpl_->test_vulkan_playground)
355 << "We don't support creating multiple contexts for one test";
356 pimpl_->test_vulkan_playground =
357 MakeVulkanPlayground(enable_vulkan_validations);
358 pimpl_->screenshotter = std::make_unique<testing::VulkanScreenshotter>(
359 pimpl_->test_vulkan_playground);
360 return pimpl_->test_vulkan_playground->GetContext();
361 } else {
362 /// On OpenGL we create a context for each test.
363 return GetContext();
364 }
365}
366
368 return pimpl_->screenshotter->GetPlayground().GetContentScale();
369}
370
372 return 0.0f;
373}
374
376 return pimpl_->window_size;
377}
378
380 return IRect::MakeSize(pimpl_->window_size);
381}
382
383void GoldenPlaygroundTest::GoldenPlaygroundTest::SetWindowSize(ISize size) {
384 pimpl_->window_size = size;
385}
386
388 const std::shared_ptr<Capabilities>& capabilities) {
389 return pimpl_->screenshotter->GetPlayground().SetCapabilities(capabilities);
390}
391
392std::unique_ptr<testing::Screenshot> GoldenPlaygroundTest::MakeScreenshot(
393 const sk_sp<flutter::DisplayList>& list) {
394 AiksContext renderer(GetContext(), typographer_context_);
395 Point content_scale =
396 pimpl_->screenshotter->GetPlayground().GetContentScale();
397
398 ISize physical_window_size(
399 std::round(pimpl_->window_size.width * content_scale.x),
400 std::round(pimpl_->window_size.height * content_scale.y));
401 return pimpl_->screenshotter->MakeScreenshot(
402 renderer, DisplayListToTexture(list, physical_window_size, renderer));
403}
404
406 return pimpl_->screenshotter->GetPlayground().GetRuntimeStageBackend();
407}
408
409} // 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:37
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
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