diff --git a/impeller/fixtures/dart_tests.dart b/impeller/fixtures/dart_tests.dart index 1923dd440c8d1..2609d70f0d1c2 100644 --- a/impeller/fixtures/dart_tests.dart +++ b/impeller/fixtures/dart_tests.dart @@ -13,6 +13,10 @@ void sayHi() { print('Hi'); } +/// Pass a texture back to the playground for rendering to the surface. +@pragma('vm:external-name', 'SetDisplayTexture') +external void setDisplayTexture(gpu.Texture texture); + @pragma('vm:entry-point') void instantiateDefaultContext() { // ignore: unused_local_variable @@ -102,9 +106,9 @@ ByteData float32(List values) { } @pragma('vm:entry-point') -void canCreateRenderPassAndSubmit() { - final gpu.Texture? renderTexture = - gpu.gpuContext.createTexture(gpu.StorageMode.devicePrivate, 100, 100); +void canCreateRenderPassAndSubmit(int width, int height) { + final gpu.Texture? renderTexture = gpu.gpuContext + .createTexture(gpu.StorageMode.devicePrivate, width, height); assert(renderTexture != null); final gpu.CommandBuffer commandBuffer = gpu.gpuContext.createCommandBuffer(); @@ -123,9 +127,9 @@ void canCreateRenderPassAndSubmit() { final gpu.HostBuffer transients = gpu.gpuContext.createHostBuffer(); final gpu.BufferView vertices = transients.emplace(float32([ - -0.5, -0.5, // + -0.5, 0.5, // + 0.0, -0.5, // 0.5, 0.5, // - 0.5, -0.5, // ])); final gpu.BufferView vertInfoData = transients.emplace(float32([ 1, 0, 0, 0, // mvp @@ -142,4 +146,6 @@ void canCreateRenderPassAndSubmit() { encoder.draw(); commandBuffer.submit(); + + setDisplayTexture(renderTexture); } diff --git a/impeller/playground/playground.cc b/impeller/playground/playground.cc index ae4d7cd4dd3fb..cc21c42c4c216 100644 --- a/impeller/playground/playground.cc +++ b/impeller/playground/playground.cc @@ -148,6 +148,10 @@ void Playground::SetupWindow() { start_time_ = fml::TimePoint::Now().ToEpochDelta(); } +bool Playground::IsPlaygroundEnabled() const { + return switches_.enable_playground; +} + void Playground::TeardownWindow() { if (context_) { context_->Shutdown(); diff --git a/impeller/playground/playground.h b/impeller/playground/playground.h index b15461272f3d5..61196ef64748f 100644 --- a/impeller/playground/playground.h +++ b/impeller/playground/playground.h @@ -64,6 +64,8 @@ class Playground { void TeardownWindow(); + bool IsPlaygroundEnabled() const; + Point GetCursorPosition() const; ISize GetWindowSize() const; diff --git a/impeller/renderer/renderer_dart_unittests.cc b/impeller/renderer/renderer_dart_unittests.cc index ab69f921d96a5..fb33252a90077 100644 --- a/impeller/renderer/renderer_dart_unittests.cc +++ b/impeller/renderer/renderer_dart_unittests.cc @@ -10,14 +10,20 @@ #include "flutter/common/task_runners.h" #include "flutter/lib/gpu/context.h" #include "flutter/lib/gpu/shader_library.h" +#include "flutter/lib/gpu/texture.h" #include "flutter/runtime/dart_isolate.h" #include "flutter/runtime/dart_vm_lifecycle.h" #include "flutter/testing/dart_fixture.h" #include "flutter/testing/dart_isolate_runner.h" +#include "flutter/testing/test_dart_native_resolver.h" #include "flutter/testing/testing.h" #include "fml/memory/ref_ptr.h" +#include "impeller/fixtures/texture.frag.h" +#include "impeller/fixtures/texture.vert.h" #include "impeller/playground/playground_test.h" +#include "impeller/renderer/pipeline_library.h" #include "impeller/renderer/render_pass.h" +#include "impeller/renderer/vertex_buffer_builder.h" #include "gtest/gtest.h" #include "third_party/imgui/imgui.h" @@ -46,6 +52,23 @@ class RendererDartTest : public PlaygroundTest, isolate_ = CreateDartIsolate(); assert(isolate_); assert(isolate_->get()->GetPhase() == flutter::DartIsolate::Phase::Running); + + // Set up native callbacks. + // + // Note: The Dart isolate is configured (by + // `RendererDartTest::CreateDartIsolate`) to use the main thread, so + // there's no need for additional synchronization. + { + auto set_display_texture = [this](Dart_NativeArguments args) { + flutter::gpu::Texture* texture = + tonic::DartConverter::FromDart( + Dart_GetNativeArgument(args, 0)); + assert(texture != nullptr); // Should always be a valid pointer. + received_texture_ = texture->GetTexture(); + }; + AddNativeCallback("SetDisplayTexture", + CREATE_NATIVE_ENTRY(set_display_texture)); + } } flutter::testing::AutoIsolateShutdown* GetIsolate() { @@ -58,6 +81,169 @@ class RendererDartTest : public PlaygroundTest, return isolate_.get(); } + /// @brief Run a Dart function that's expected to create a texture and pass + /// it back for rendering via `drawToPlayground`. + std::shared_ptr GetRenderedTextureFromDart( + const char* dart_function_name) { + bool success = + GetIsolate()->RunInIsolateScope([this, &dart_function_name]() -> bool { + Dart_Handle args[] = {tonic::ToDart(GetWindowSize().width), + tonic::ToDart(GetWindowSize().height)}; + if (tonic::CheckAndHandleError( + ::Dart_Invoke(Dart_RootLibrary(), + tonic::ToDart(dart_function_name), 2, args))) { + return false; + } + return true; + }); + if (!success) { + FML_LOG(ERROR) << "Failed to invoke dart test function:" + << dart_function_name; + return nullptr; + } + if (!received_texture_) { + FML_LOG(ERROR) << "Dart test function `" << dart_function_name + << "` did not invoke `drawToPlaygroundSurface`."; + return nullptr; + } + return received_texture_; + } + + /// @brief Invokes a Dart function. + /// + /// Returns false if invoking the function failed or if any unhandled + /// exceptions were thrown. + bool RunDartFunction(const char* dart_function_name) { + return GetIsolate()->RunInIsolateScope([&dart_function_name]() -> bool { + if (tonic::CheckAndHandleError( + ::Dart_Invoke(Dart_RootLibrary(), + tonic::ToDart(dart_function_name), 0, nullptr))) { + return false; + } + return true; + }); + } + + /// @brief Invokes a Dart function with the window's width and height as + /// arguments. + /// + /// Returns false if invoking the function failed or if any unhandled + /// exceptions were thrown. + bool RunDartFunctionWithWindowSize(const char* dart_function_name) { + return GetIsolate()->RunInIsolateScope( + [this, &dart_function_name]() -> bool { + Dart_Handle args[] = {tonic::ToDart(GetWindowSize().width), + tonic::ToDart(GetWindowSize().height)}; + if (tonic::CheckAndHandleError( + ::Dart_Invoke(Dart_RootLibrary(), + tonic::ToDart(dart_function_name), 2, args))) { + return false; + } + return true; + }); + } + + /// @brief Call a dart function that produces a texture and render the result + /// in the playground. + /// + /// If the playground isn't enabled, the function is simply run once + /// in order to verify that it doesn't throw any unhandled exceptions. + bool RenderDartToPlayground(const char* dart_function_name) { + if (!IsPlaygroundEnabled()) { + // If the playground is not enabled, run the function instead to at least + // verify that it doesn't crash. + return RunDartFunctionWithWindowSize(dart_function_name); + } + + auto context = GetContext(); + assert(context != nullptr); + + //------------------------------------------------------------------------------ + /// Prepare pipeline. + /// + + using TextureVS = TextureVertexShader; + using TextureFS = TextureFragmentShader; + using TexturePipelineBuilder = PipelineBuilder; + + auto pipeline_desc = + TexturePipelineBuilder::MakeDefaultPipelineDescriptor(*context); + if (!pipeline_desc.has_value()) { + FML_LOG(ERROR) << "Failed to create default pipeline descriptor."; + return false; + } + pipeline_desc->SetSampleCount(SampleCount::kCount4); + pipeline_desc->SetStencilAttachmentDescriptors(std::nullopt); + pipeline_desc->SetDepthStencilAttachmentDescriptor(std::nullopt); + pipeline_desc->SetStencilPixelFormat(PixelFormat::kUnknown); + pipeline_desc->SetDepthPixelFormat(PixelFormat::kUnknown); + + auto pipeline = + context->GetPipelineLibrary()->GetPipeline(pipeline_desc).Get(); + if (!pipeline || !pipeline->IsValid()) { + FML_LOG(ERROR) << "Failed to create default pipeline."; + return false; + } + + //------------------------------------------------------------------------------ + /// Prepare vertex data. + /// + + VertexBufferBuilder texture_vtx_builder; + + // Always stretch out the texture to fill the screen. + + // clang-format off + texture_vtx_builder.AddVertices({ + {{-0.5, -0.5, 0.0}, {0.0, 0.0}}, // 1 + {{ 0.5, -0.5, 0.0}, {1.0, 0.0}}, // 2 + {{ 0.5, 0.5, 0.0}, {1.0, 1.0}}, // 3 + {{-0.5, -0.5, 0.0}, {0.0, 0.0}}, // 1 + {{ 0.5, 0.5, 0.0}, {1.0, 1.0}}, // 3 + {{-0.5, 0.5, 0.0}, {0.0, 1.0}}, // 4 + }); + // clang-format on + + //------------------------------------------------------------------------------ + /// Prepare sampler. + /// + + const auto& sampler = context->GetSamplerLibrary()->GetSampler({}); + if (!sampler) { + FML_LOG(ERROR) << "Failed to create default sampler."; + return false; + } + + //------------------------------------------------------------------------------ + /// Render to playground. + /// + + SinglePassCallback callback = [&](RenderPass& pass) { + auto texture = GetRenderedTextureFromDart(dart_function_name); + if (!texture) { + return false; + } + + auto buffer = HostBuffer::Create(context->GetResourceAllocator()); + + pass.SetVertexBuffer(texture_vtx_builder.CreateVertexBuffer( + *context->GetResourceAllocator())); + + TextureVS::UniformBuffer uniforms; + uniforms.mvp = Matrix(); + TextureVS::BindUniformBuffer(pass, buffer->EmplaceUniform(uniforms)); + TextureFS::BindTextureContents(pass, texture, sampler); + + pass.SetPipeline(pipeline); + + if (!pass.Draw().ok()) { + return false; + } + return true; + }; + return OpenPlaygroundHere(callback); + } + private: std::unique_ptr CreateDartIsolate() { const auto settings = CreateSettingsForFixture(); @@ -76,65 +262,46 @@ class RendererDartTest : public PlaygroundTest, flutter::DartVMRef vm_ref_; fml::RefPtr current_task_runner_; std::unique_ptr isolate_; + + std::shared_ptr received_texture_; }; INSTANTIATE_PLAYGROUND_SUITE(RendererDartTest); TEST_P(RendererDartTest, CanRunDartInPlaygroundFrame) { - auto isolate = GetIsolate(); - SinglePassCallback callback = [&](RenderPass& pass) { ImGui::Begin("Dart test", nullptr); ImGui::Text( "This test executes Dart code during the playground frame callback."); ImGui::End(); - return isolate->RunInIsolateScope([]() -> bool { - if (tonic::CheckAndHandleError(::Dart_Invoke( - Dart_RootLibrary(), tonic::ToDart("sayHi"), 0, nullptr))) { - return false; - } - return true; - }); + return RunDartFunction("sayHi"); }; - OpenPlaygroundHere(callback); + ASSERT_TRUE(OpenPlaygroundHere(callback)); } -TEST_P(RendererDartTest, CanInstantiateFlutterGPUContext) { - auto isolate = GetIsolate(); - bool result = isolate->RunInIsolateScope([]() -> bool { - if (tonic::CheckAndHandleError(::Dart_Invoke( - Dart_RootLibrary(), tonic::ToDart("instantiateDefaultContext"), 0, - nullptr))) { - return false; - } - return true; - }); +/// These test entries correspond to Dart functions located in +/// `flutter/impeller/fixtures/dart_tests.dart` - ASSERT_TRUE(result); +TEST_P(RendererDartTest, CanInstantiateFlutterGPUContext) { + ASSERT_TRUE(RunDartFunction("instantiateDefaultContext")); } -#define DART_TEST_CASE(name) \ - TEST_P(RendererDartTest, name) { \ - auto isolate = GetIsolate(); \ - bool result = isolate->RunInIsolateScope([]() -> bool { \ - if (tonic::CheckAndHandleError(::Dart_Invoke( \ - Dart_RootLibrary(), tonic::ToDart(#name), 0, nullptr))) { \ - return false; \ - } \ - return true; \ - }); \ - ASSERT_TRUE(result); \ - } +TEST_P(RendererDartTest, CanCreateShaderLibrary) { + ASSERT_TRUE(RunDartFunction("canCreateShaderLibrary")); +} -/// These test entries correspond to Dart functions located in -/// `flutter/impeller/fixtures/dart_tests.dart` +TEST_P(RendererDartTest, CanReflectUniformStructs) { + ASSERT_TRUE(RunDartFunction("canReflectUniformStructs")); +} -DART_TEST_CASE(canCreateShaderLibrary); -DART_TEST_CASE(canReflectUniformStructs); -DART_TEST_CASE(uniformBindFailsForInvalidHostBufferOffset); +TEST_P(RendererDartTest, UniformBindFailsForInvalidHostBufferOffset) { + ASSERT_TRUE(RunDartFunction("uniformBindFailsForInvalidHostBufferOffset")); +} -DART_TEST_CASE(canCreateRenderPassAndSubmit); +TEST_P(RendererDartTest, CanCreateRenderPassAndSubmit) { + ASSERT_TRUE(RenderDartToPlayground("canCreateRenderPassAndSubmit")); +} } // namespace testing } // namespace impeller