18#include "third_party/googletest/googletest/include/gtest/gtest.h"
20#define GLFW_INCLUDE_NONE
21#include "third_party/glfw/include/GLFW/glfw3.h"
38#include "third_party/imgui/backends/imgui_impl_glfw.h"
39#include "third_party/imgui/imgui.h"
45#if IMPELLER_ENABLE_VULKAN
52std::string GetTestName() {
53 std::string suite_name =
54 ::testing::UnitTest::GetInstance()->current_test_suite()->name();
55 std::string test_name =
56 ::testing::UnitTest::GetInstance()->current_test_info()->name();
58 ss <<
"impeller_" << suite_name <<
"_" << test_name;
59 std::string result = ss.str();
61 std::replace(result.begin(), result.end(),
'/',
'_');
65std::string GetGoldenFilename(
const std::string& postfix =
"") {
66 return GetTestName() + postfix +
".png";
86std::atomic<bool> Playground::glfw_initialized_ =
false;
88void Playground::InitializeGLFWOnce() {
105 static std::once_flag sOnceInitializer;
106 std::call_once(sOnceInitializer, []() {
107 ::glfwSetErrorCallback([](
int code,
const char* description) {
108 FML_LOG(ERROR) <<
"GLFW Error '" << description <<
"' (" << code <<
").";
111 glfw_initialized_ =
true;
116 if (glfw_initialized_) {
127 : backend_(backend), switches_(switches) {
128 InitializeGLFWOnce();
135 FML_CHECK(!context_) <<
"Must be called before a context is created.";
140#if __arm64__ && FML_OS_MACOSX
191 FML_CHECK(!context_) <<
"Must be called before a context is created.";
211 FML_CHECK(!context_) <<
"MakeContext can only be called once";
213 <<
"MakeContext should only be called after EnsureContextIsUnique()";
220 if (!content_context_) {
223 FML_CHECK(content_context_) <<
"Failed to create ContentContext";
225 return *content_context_;
229 if (!typographer_context_) {
232 return typographer_context_;
236 std::shared_ptr<TypographerContext> typographer_context) {
238 <<
"SetTypographerContext called after it has already been initialized";
239 typographer_context_ = std::move(typographer_context);
246#if IMPELLER_ENABLE_METAL
253#if IMPELLER_ENABLE_OPENGLES
259#if IMPELLER_ENABLE_VULKAN
268std::unique_ptr<PlaygroundImpl>& Playground::GetImpl()
const {
276void Playground::SetupContext()
const {
281 FML_LOG(WARNING) <<
"PlaygroundImpl::Create failed.";
285 context_ = impl_->GetContext();
288void Playground::SetupWindow() {
290 FML_LOG(WARNING) <<
"Asked to set up a window with no context (call "
291 "SetupContext first).";
302 if (content_context_) {
304 [[maybe_unused]]
auto result = context_->FlushCommandBuffers();
305 content_context_.reset();
308 host_buffer_.reset();
311 context_->Shutdown();
328 if ((
key == GLFW_KEY_ESCAPE) &&
action == GLFW_RELEASE) {
329 if (mods & (GLFW_MOD_CONTROL | GLFW_MOD_SUPER | GLFW_MOD_SHIFT)) {
337 return cursor_position_;
349 return GetImpl()->GetContentScale();
356void Playground::SetCursorPosition(
Point pos) {
357 cursor_position_ = pos;
361 return should_write_golden_;
365 should_write_golden_ = write_golden;
368bool Playground::RenderImage(
const RenderCallback&
callback,
375 AiksContext renderer(
context, typographer_context_);
381 write_result ?
"Golden Render Pass" :
"Playground Verification Pass";
382 RenderTargetAllocator render_target_allocator(
383 context->GetResourceAllocator());
384 RenderTarget render_target;
385 if (
context->GetCapabilities()->SupportsOffscreenMSAA()) {
386 render_target = render_target_allocator.CreateOffscreenMSAA(
387 *
context, size, 1, label +
" (MSAA)",
391 render_target = render_target_allocator.CreateOffscreen(
396 if (!render_target.IsValid()) {
402 if (write_result && !WriteGoldenImage(render_target)) {
408bool Playground::WriteGoldenImage(
const RenderTarget& render_target,
409 const std::string& postfix) {
412 FML_LOG(ERROR) <<
"Golden image has no working directory";
421 digest->AddDimension(
"gpu_string",
context->DescribeGpuModel());
423 std::string test_name = GetTestName();
425 std::unique_ptr<testing::Screenshot> screenshot =
427 context, render_target.GetRenderTargetTexture());
428 if (!screenshot || !screenshot->GetBytes()) {
429 FML_LOG(ERROR) <<
"Failed to collect screenshot for test " << test_name;
433 std::string filename = GetGoldenFilename(postfix);
434 std::string filenamepath = digest->GetFullPath(filename);
435 if (!screenshot->WriteToPNG(filenamepath)) {
436 FML_LOG(ERROR) <<
"Failed to write screenshot to " << filenamepath;
439 digest->AddImage(test_name, filename,
440 screenshot->GetWidth(), screenshot->GetHeight());
450 if (!render_callback) {
454 auto window =
reinterpret_cast<GLFWwindow*
>(impl_->GetWindowHandle());
462 bool success = RenderImage(render_callback,
false);
463 if (success && writing_golden) {
466 success = RenderImage(render_callback,
true);
474 IMGUI_CHECKVERSION();
475 ImGui::CreateContext();
477 []() { ImGui::DestroyContext(); });
478 ImGui::StyleColorsDark();
480 auto& io = ImGui::GetIO();
481 io.IniFilename =
nullptr;
482 io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
483 io.ConfigWindowsResizeFromEdges =
true;
486 ::glfwSetWindowUserPointer(
window,
this);
487 ::glfwSetWindowSizeCallback(
497 ::glfwSetCursorPosCallback(
window, [](GLFWwindow*
window,
double x,
500 ->SetCursorPosition({
static_cast<Scalar>(
x),
static_cast<Scalar>(
y)});
503 ImGui_ImplGlfw_InitForOther(
window,
true);
510 ImGui::SetNextWindowPos({10, 10});
512 ::glfwSetWindowPos(
window, 200, 100);
521 if (::glfwWindowShouldClose(
window)) {
525 ImGui_ImplGlfw_NewFrame();
531 ImGui::DockSpaceOverViewport(0, ImGui::GetMainViewport(),
532 ImGuiDockNodeFlags_PassthruCentralNode);
533 bool result = render_callback(render_target);
538 auto buffer =
context->CreateCommandBuffer();
543 buffer->SetLabel(
"ImGui Command Buffer");
547 if (color0.resolve_texture) {
548 color0.texture = color0.resolve_texture;
549 color0.resolve_texture =
nullptr;
556 auto pass = buffer->CreateRenderPass(render_target);
561 pass->SetLabel(
"ImGui Render Pass");
565 context->GetCapabilities()->GetMinimumUniformAlignment());
571 pass->EncodeCommands();
573 if (!
context->GetCommandQueue()->Submit({buffer}).ok()) {
578 if (!result || !
surface->Present()) {
595 auto buffer =
context->CreateCommandBuffer();
599 buffer->SetLabel(
"Playground Command Buffer");
601 auto pass = buffer->CreateRenderPass(render_target);
605 pass->SetLabel(
"Playground Render Pass");
607 if (!pass_callback(*pass)) {
611 pass->EncodeCommands();
612 if (!
context->GetCommandQueue()->Submit({buffer}).ok()) {
620 std::shared_ptr<fml::Mapping> mapping) {
622 if (!compressed_image) {
627 return compressed_image;
631 const std::shared_ptr<CompressedImage>& compressed) {
632 if (compressed ==
nullptr) {
640 auto image = compressed->Decode().ConvertToRGBA();
641 if (!
image.IsValid()) {
650 const std::shared_ptr<Context>&
context,
652 bool enable_mipmapping) {
656 texture_descriptor.
size = decompressed_image.
GetSize();
661 context->GetResourceAllocator()->CreateTexture(texture_descriptor);
669 FML_DLOG(ERROR) <<
"Could not create command buffer for mipmap generation.";
676 context->GetResourceAllocator()->CreateBufferWithCopy(
678 blit_pass->AddCopy(buffer_view,
texture);
679 if (enable_mipmapping) {
680 blit_pass->SetLabel(
"Mipmap Blit Pass");
681 blit_pass->GenerateMipmap(
texture);
683 blit_pass->EncodeCommands();
684 if (!
context->GetCommandQueue()->Submit({command_buffer}).ok()) {
685 FML_DLOG(ERROR) <<
"Failed to submit blit pass command buffer.";
692 const std::shared_ptr<Context>&
context,
693 std::shared_ptr<fml::Mapping> mapping,
694 bool enable_mipmapping) {
697 if (!
image.has_value()) {
705 const char* fixture_name,
706 bool enable_mipmapping)
const {
712 texture->SetLabel(fixture_name);
717 std::array<const char*, 6> fixture_names)
const {
718 std::array<DecompressedImage, 6>
images;
719 for (
size_t i = 0;
i < fixture_names.size();
i++) {
722 if (!
image.has_value()) {
732 texture_descriptor.
size =
images[0].GetSize();
736 GetContext()->GetResourceAllocator()->CreateTexture(texture_descriptor);
741 texture->SetLabel(
"Texture cube");
743 auto cmd_buffer =
GetContext()->CreateCommandBuffer();
744 auto blit_pass = cmd_buffer->CreateBlitPass();
745 for (
size_t i = 0;
i < fixture_names.size();
i++) {
747 GetContext()->GetResourceAllocator()->CreateBufferWithCopy(
753 if (!blit_pass->EncodeCommands() ||
754 !
GetContext()->GetCommandQueue()->Submit({std::move(cmd_buffer)}).ok()) {
771 const std::shared_ptr<Capabilities>& capabilities) {
772 return GetImpl()->SetCapabilities(capabilities);
777 return GetImpl()->CreateGLProcAddressResolver();
782 return GetImpl()->CreateVKProcAddressResolver();
786 GetImpl()->SetGPUDisabled(
value);
790 return GetImpl()->GetRuntimeStageBackend();
Wraps a closure that is invoked in the destructor unless released by the caller.
constexpr TimeDelta ToEpochDelta() const
static std::shared_ptr< CompressedImage > Create(std::shared_ptr< const fml::Mapping > allocation)
const std::shared_ptr< const fml::Mapping > & GetAllocation() const
const ISize & GetSize() const
static BufferView AsBufferView(std::shared_ptr< DeviceBuffer > buffer)
Create a buffer view of this entire buffer.
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, std::shared_ptr< const GpuSubmissionTracker > submission_tracker=nullptr)
void ClearStencilAttachments()
PipelineDescriptor & SetSampleCount(SampleCount samples)
void ClearDepthAttachment()
bool OpenPlaygroundHere(const RenderCallback &render_callback)
std::shared_ptr< Context > MakeContext() const
bool IsPlaygroundEnabled() const
static void OnTearDownTestEnvironment()
virtual bool EnsureContextSupportsWideGamut()
Make sure that when the context is later created that it will support wide gamuts if the platform sup...
virtual bool ShouldKeepRendering() const
bool ShouldWriteGoldenImage()
Whether this instance will write a golden image of the output from |OpenPlaygroundHere|.
static bool ShouldOpenNewPlaygrounds()
bool PlatformSupportsWideGamutTests() const
Returns true if the platform can support wide gamuts.
Point GetCursorPosition() const
void SetWindowSize(ISize size)
static std::shared_ptr< CompressedImage > LoadFixtureImageCompressed(std::shared_ptr< fml::Mapping > mapping)
ISize GetWindowSize() const
bool RenderingSupportsMSAA() const
Returns true if the rendering path supports MSAA rendering.
std::function< bool(RenderPass &pass)> SinglePassCallback
GLProcAddressResolver CreateGLProcAddressResolver() const
RuntimeStageBackend GetRuntimeStageBackend() const
void TearDownContextData()
virtual std::string GetWindowTitle() const =0
ContentContext & GetContentContext() const
std::function< bool(RenderTarget &render_target)> RenderCallback
void SetGPUDisabled(bool disabled) const
Mark the GPU as unavilable.
std::shared_ptr< TypographerContext > GetTypographerContext() const
virtual testing::GoldenDigestManager * GetGoldenDigestManager() const
bool InitializePipelineDescriptorForRendering(PipelineDescriptor &desc) const
Initializes the provided |PipelineDescriptor| with appropriate default values to match the conditions...
std::shared_ptr< Context > GetContext() const
static bool SupportsBackend(PlaygroundBackend backend)
SampleCount GetDefaultSampleCount() const
Returns the default sample count of the rendering path.
static std::shared_ptr< Texture > CreateTextureForMapping(const std::shared_ptr< Context > &context, std::shared_ptr< fml::Mapping > mapping, bool enable_mipmapping=false)
virtual void EnsureContextIsUnique()
Make sure that when the context is later created that it will not be shared with any other playground...
IRect GetWindowBounds() const
virtual std::unique_ptr< fml::Mapping > OpenAssetAsMapping(std::string asset_name) const =0
Point GetContentScale() const
void SetEnableWriteGolden(bool write_golden)
Sets a particular test to either write a golden or not, false by default.
std::shared_ptr< Texture > CreateTextureForFixture(const char *fixture_name, bool enable_mipmapping=false) const
Scalar GetSecondsElapsed() const
Get the amount of time elapsed from the start of the playground's execution.
Playground(PlaygroundBackend backend, const PlaygroundSwitches &switches)
std::function< void *(void *instance, const char *proc_name)> VKProcAddressResolver
std::function< void *(const char *proc_name)> GLProcAddressResolver
void SetTypographerContext(std::shared_ptr< TypographerContext > typographer_context)
static std::optional< DecompressedImage > DecodeImageRGBA(const std::shared_ptr< CompressedImage > &compressed)
std::shared_ptr< Texture > CreateTextureCubeForFixture(std::array< const char *, 6 > fixture_names) const
fml::Status SetCapabilities(const std::shared_ptr< Capabilities > &capabilities)
VKProcAddressResolver CreateVKProcAddressResolver() const
static std::unique_ptr< PlaygroundImpl > Create(PlaygroundBackend backend, PlaygroundSwitches switches)
static bool IsVulkanDriverPresent()
ColorAttachment GetColorAttachment(size_t index) const
Get the color attachment at [index].
static constexpr AttachmentConfig kDefaultColorAttachmentConfig
static constexpr AttachmentConfigMSAA kDefaultColorAttachmentConfigMSAA
RenderTarget & SetColorAttachment(const ColorAttachment &attachment, size_t index)
RenderTarget & SetDepthAttachment(std::optional< DepthAttachment > attachment)
RenderTarget & SetStencilAttachment(std::optional< StencilAttachment > attachment)
static std::shared_ptr< TypographerContext > Make()
static std::unique_ptr< Screenshot > MakeScreenshot(std::shared_ptr< Context > &context, const std::shared_ptr< Texture > &texture)
FlutterVulkanImage * image
FlutterDesktopBinaryReply callback
#define FML_DLOG(severity)
#define FML_LOG(severity)
#define FML_CHECK(condition)
#define FML_UNREACHABLE()
void ImGui_ImplImpeller_RenderDrawData(ImDrawData *draw_data, impeller::RenderPass &render_pass, impeller::HostBuffer &host_buffer)
bool ImGui_ImplImpeller_Init(const std::shared_ptr< impeller::Context > &context)
void ImGui_ImplImpeller_Shutdown()
std::array< MockImage, 3 > images
void SetupSwiftshaderOnce(bool use_swiftshader)
Find and setup the installable client driver for a locally built SwiftShader at known paths....
static std::shared_ptr< Texture > CreateTextureForDecompressedImage(const std::shared_ptr< Context > &context, DecompressedImage &decompressed_image, bool enable_mipmapping)
std::string PlaygroundBackendToString(PlaygroundBackend backend)
static void PlaygroundKeyCallback(GLFWwindow *window, int key, int scancode, int action, int mods)
static std::atomic_bool gShouldOpenNewPlaygrounds
std::shared_ptr< ContextGLES > context
std::shared_ptr< CommandBuffer > command_buffer
static constexpr TRect MakeSize(const TSize< U > &size)
constexpr TSize Max(const TSize &o) const
constexpr size_t MipCount() const
Return the mip count of the texture.
A lightweight object that describes the attributes of a texture that can then used an allocator to cr...