| // Copyright 2013 The Flutter Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "flutter/fml/logging.h" | |
| #include "impeller/core/device_buffer_descriptor.h" | |
| #include "impeller/core/formats.h" | |
| #include "impeller/core/host_buffer.h" | |
| #include "impeller/core/sampler_descriptor.h" | |
| #include "impeller/fixtures/array.frag.h" | |
| #include "impeller/fixtures/array.vert.h" | |
| #include "impeller/fixtures/box_fade.frag.h" | |
| #include "impeller/fixtures/box_fade.vert.h" | |
| #include "impeller/fixtures/colors.frag.h" | |
| #include "impeller/fixtures/colors.vert.h" | |
| #include "impeller/fixtures/impeller.frag.h" | |
| #include "impeller/fixtures/impeller.vert.h" | |
| #include "impeller/fixtures/inactive_uniforms.frag.h" | |
| #include "impeller/fixtures/inactive_uniforms.vert.h" | |
| #include "impeller/fixtures/instanced_draw.frag.h" | |
| #include "impeller/fixtures/instanced_draw.vert.h" | |
| #include "impeller/fixtures/mipmaps.frag.h" | |
| #include "impeller/fixtures/mipmaps.vert.h" | |
| #include "impeller/fixtures/sepia.frag.h" | |
| #include "impeller/fixtures/sepia.vert.h" | |
| #include "impeller/fixtures/swizzle.frag.h" | |
| #include "impeller/fixtures/test_texture.frag.h" | |
| #include "impeller/fixtures/test_texture.vert.h" | |
| #include "impeller/fixtures/texture.frag.h" | |
| #include "impeller/fixtures/texture.vert.h" | |
| #include "impeller/geometry/path_builder.h" | |
| #include "impeller/playground/playground_test.h" | |
| #include "impeller/renderer/command.h" | |
| #include "impeller/renderer/command_buffer.h" | |
| #include "impeller/renderer/pipeline_builder.h" | |
| #include "impeller/renderer/pipeline_library.h" | |
| #include "impeller/renderer/render_pass.h" | |
| #include "impeller/renderer/render_target.h" | |
| #include "impeller/renderer/renderer.h" | |
| #include "impeller/renderer/vertex_buffer_builder.h" | |
| #include "impeller/tessellator/tessellator.h" | |
| #include "third_party/imgui/imgui.h" | |
| // TODO(zanderso): https://github.com/flutter/flutter/issues/127701 | |
| // NOLINTBEGIN(bugprone-unchecked-optional-access) | |
| namespace impeller { | |
| namespace testing { | |
| using RendererTest = PlaygroundTest; | |
| INSTANTIATE_PLAYGROUND_SUITE(RendererTest); | |
| TEST_P(RendererTest, CanCreateBoxPrimitive) { | |
| using VS = BoxFadeVertexShader; | |
| using FS = BoxFadeFragmentShader; | |
| auto context = GetContext(); | |
| ASSERT_TRUE(context); | |
| using BoxPipelineBuilder = PipelineBuilder<VS, FS>; | |
| auto desc = BoxPipelineBuilder::MakeDefaultPipelineDescriptor(*context); | |
| ASSERT_TRUE(desc.has_value()); | |
| desc->SetSampleCount(SampleCount::kCount4); | |
| desc->SetStencilAttachmentDescriptors(std::nullopt); | |
| // Vertex buffer. | |
| VertexBufferBuilder<VS::PerVertexData> vertex_builder; | |
| vertex_builder.SetLabel("Box"); | |
| vertex_builder.AddVertices({ | |
| {{100, 100, 0.0}, {0.0, 0.0}}, // 1 | |
| {{800, 100, 0.0}, {1.0, 0.0}}, // 2 | |
| {{800, 800, 0.0}, {1.0, 1.0}}, // 3 | |
| {{100, 100, 0.0}, {0.0, 0.0}}, // 1 | |
| {{800, 800, 0.0}, {1.0, 1.0}}, // 3 | |
| {{100, 800, 0.0}, {0.0, 1.0}}, // 4 | |
| }); | |
| auto bridge = CreateTextureForFixture("bay_bridge.jpg"); | |
| auto boston = CreateTextureForFixture("boston.jpg"); | |
| ASSERT_TRUE(bridge && boston); | |
| const std::unique_ptr<const Sampler>& sampler = | |
| context->GetSamplerLibrary()->GetSampler({}); | |
| ASSERT_TRUE(sampler); | |
| auto host_buffer = HostBuffer::Create(context->GetResourceAllocator()); | |
| SinglePassCallback callback = [&](RenderPass& pass) { | |
| ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize); | |
| static bool wireframe; | |
| ImGui::Checkbox("Wireframe", &wireframe); | |
| ImGui::End(); | |
| desc->SetPolygonMode(wireframe ? PolygonMode::kLine : PolygonMode::kFill); | |
| auto pipeline = context->GetPipelineLibrary()->GetPipeline(desc).Get(); | |
| assert(pipeline && pipeline->IsValid()); | |
| pass.SetCommandLabel("Box"); | |
| pass.SetPipeline(pipeline); | |
| pass.SetVertexBuffer( | |
| vertex_builder.CreateVertexBuffer(*context->GetResourceAllocator())); | |
| VS::UniformBuffer uniforms; | |
| EXPECT_EQ(pass.GetOrthographicTransform(), | |
| Matrix::MakeOrthographic(pass.GetRenderTargetSize())); | |
| uniforms.mvp = | |
| pass.GetOrthographicTransform() * Matrix::MakeScale(GetContentScale()); | |
| VS::BindUniformBuffer(pass, host_buffer->EmplaceUniform(uniforms)); | |
| FS::FrameInfo frame_info; | |
| frame_info.current_time = GetSecondsElapsed(); | |
| frame_info.cursor_position = GetCursorPosition(); | |
| frame_info.window_size.x = GetWindowSize().width; | |
| frame_info.window_size.y = GetWindowSize().height; | |
| FS::BindFrameInfo(pass, host_buffer->EmplaceUniform(frame_info)); | |
| FS::BindContents1(pass, boston, sampler); | |
| FS::BindContents2(pass, bridge, sampler); | |
| host_buffer->Reset(); | |
| return pass.Draw().ok(); | |
| }; | |
| OpenPlaygroundHere(callback); | |
| } | |
| TEST_P(RendererTest, CanRenderPerspectiveCube) { | |
| using VS = ColorsVertexShader; | |
| using FS = ColorsFragmentShader; | |
| auto context = GetContext(); | |
| ASSERT_TRUE(context); | |
| auto desc = PipelineBuilder<VS, FS>::MakeDefaultPipelineDescriptor(*context); | |
| ASSERT_TRUE(desc.has_value()); | |
| desc->SetCullMode(CullMode::kBackFace); | |
| desc->SetWindingOrder(WindingOrder::kCounterClockwise); | |
| desc->SetSampleCount(SampleCount::kCount4); | |
| desc->SetStencilAttachmentDescriptors(std::nullopt); | |
| auto pipeline = | |
| context->GetPipelineLibrary()->GetPipeline(std::move(desc)).Get(); | |
| ASSERT_TRUE(pipeline); | |
| struct Cube { | |
| VS::PerVertexData vertices[8] = { | |
| // -Z | |
| {{-1, -1, -1}, Color::Red()}, | |
| {{1, -1, -1}, Color::Yellow()}, | |
| {{1, 1, -1}, Color::Green()}, | |
| {{-1, 1, -1}, Color::Blue()}, | |
| // +Z | |
| {{-1, -1, 1}, Color::Green()}, | |
| {{1, -1, 1}, Color::Blue()}, | |
| {{1, 1, 1}, Color::Red()}, | |
| {{-1, 1, 1}, Color::Yellow()}, | |
| }; | |
| uint16_t indices[36] = { | |
| 1, 5, 2, 2, 5, 6, // +X | |
| 4, 0, 7, 7, 0, 3, // -X | |
| 4, 5, 0, 0, 5, 1, // +Y | |
| 3, 2, 7, 7, 2, 6, // -Y | |
| 5, 4, 6, 6, 4, 7, // +Z | |
| 0, 1, 3, 3, 1, 2, // -Z | |
| }; | |
| } cube; | |
| VertexBuffer vertex_buffer; | |
| { | |
| auto device_buffer = context->GetResourceAllocator()->CreateBufferWithCopy( | |
| reinterpret_cast<uint8_t*>(&cube), sizeof(cube)); | |
| vertex_buffer.vertex_buffer = { | |
| .buffer = device_buffer, | |
| .range = Range(offsetof(Cube, vertices), sizeof(Cube::vertices))}; | |
| vertex_buffer.index_buffer = { | |
| .buffer = device_buffer, | |
| .range = Range(offsetof(Cube, indices), sizeof(Cube::indices))}; | |
| vertex_buffer.vertex_count = 36; | |
| vertex_buffer.index_type = IndexType::k16bit; | |
| } | |
| const std::unique_ptr<const Sampler>& sampler = | |
| context->GetSamplerLibrary()->GetSampler({}); | |
| ASSERT_TRUE(sampler); | |
| Vector3 euler_angles; | |
| auto host_buffer = HostBuffer::Create(context->GetResourceAllocator()); | |
| SinglePassCallback callback = [&](RenderPass& pass) { | |
| static Degrees fov_y(60); | |
| static Scalar distance = 10; | |
| ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize); | |
| ImGui::SliderFloat("Field of view", &fov_y.degrees, 0, 180); | |
| ImGui::SliderFloat("Camera distance", &distance, 0, 30); | |
| ImGui::End(); | |
| pass.SetCommandLabel("Perspective Cube"); | |
| pass.SetPipeline(pipeline); | |
| pass.SetVertexBuffer(vertex_buffer); | |
| VS::UniformBuffer uniforms; | |
| Scalar time = GetSecondsElapsed(); | |
| euler_angles = Vector3(0.19 * time, 0.7 * time, 0.43 * time); | |
| uniforms.mvp = | |
| Matrix::MakePerspective(fov_y, pass.GetRenderTargetSize(), 0, 10) * | |
| Matrix::MakeTranslation({0, 0, distance}) * | |
| Matrix::MakeRotationX(Radians(euler_angles.x)) * | |
| Matrix::MakeRotationY(Radians(euler_angles.y)) * | |
| Matrix::MakeRotationZ(Radians(euler_angles.z)); | |
| VS::BindUniformBuffer(pass, host_buffer->EmplaceUniform(uniforms)); | |
| host_buffer->Reset(); | |
| return pass.Draw().ok(); | |
| }; | |
| OpenPlaygroundHere(callback); | |
| } | |
| TEST_P(RendererTest, CanRenderMultiplePrimitives) { | |
| using VS = BoxFadeVertexShader; | |
| using FS = BoxFadeFragmentShader; | |
| auto context = GetContext(); | |
| ASSERT_TRUE(context); | |
| using BoxPipelineBuilder = PipelineBuilder<VS, FS>; | |
| auto desc = BoxPipelineBuilder::MakeDefaultPipelineDescriptor(*context); | |
| ASSERT_TRUE(desc.has_value()); | |
| desc->SetSampleCount(SampleCount::kCount4); | |
| desc->SetStencilAttachmentDescriptors(std::nullopt); | |
| auto box_pipeline = | |
| context->GetPipelineLibrary()->GetPipeline(std::move(desc)).Get(); | |
| ASSERT_TRUE(box_pipeline); | |
| // Vertex buffer. | |
| VertexBufferBuilder<VS::PerVertexData> vertex_builder; | |
| vertex_builder.SetLabel("Box"); | |
| vertex_builder.AddVertices({ | |
| {{100, 100, 0.0}, {0.0, 0.0}}, // 1 | |
| {{800, 100, 0.0}, {1.0, 0.0}}, // 2 | |
| {{800, 800, 0.0}, {1.0, 1.0}}, // 3 | |
| {{100, 100, 0.0}, {0.0, 0.0}}, // 1 | |
| {{800, 800, 0.0}, {1.0, 1.0}}, // 3 | |
| {{100, 800, 0.0}, {0.0, 1.0}}, // 4 | |
| }); | |
| auto vertex_buffer = | |
| vertex_builder.CreateVertexBuffer(*context->GetResourceAllocator()); | |
| ASSERT_TRUE(vertex_buffer); | |
| auto bridge = CreateTextureForFixture("bay_bridge.jpg"); | |
| auto boston = CreateTextureForFixture("boston.jpg"); | |
| ASSERT_TRUE(bridge && boston); | |
| const std::unique_ptr<const Sampler>& sampler = | |
| context->GetSamplerLibrary()->GetSampler({}); | |
| ASSERT_TRUE(sampler); | |
| auto host_buffer = HostBuffer::Create(context->GetResourceAllocator()); | |
| SinglePassCallback callback = [&](RenderPass& pass) { | |
| for (size_t i = 0; i < 1; i++) { | |
| for (size_t j = 0; j < 1; j++) { | |
| pass.SetCommandLabel("Box"); | |
| pass.SetPipeline(box_pipeline); | |
| pass.SetVertexBuffer(vertex_buffer); | |
| FS::FrameInfo frame_info; | |
| frame_info.current_time = GetSecondsElapsed(); | |
| frame_info.cursor_position = GetCursorPosition(); | |
| frame_info.window_size.x = GetWindowSize().width; | |
| frame_info.window_size.y = GetWindowSize().height; | |
| FS::BindFrameInfo(pass, host_buffer->EmplaceUniform(frame_info)); | |
| FS::BindContents1(pass, boston, sampler); | |
| FS::BindContents2(pass, bridge, sampler); | |
| VS::UniformBuffer uniforms; | |
| EXPECT_EQ(pass.GetOrthographicTransform(), | |
| Matrix::MakeOrthographic(pass.GetRenderTargetSize())); | |
| uniforms.mvp = pass.GetOrthographicTransform() * | |
| Matrix::MakeScale(GetContentScale()) * | |
| Matrix::MakeTranslation({i * 50.0f, j * 50.0f, 0.0f}); | |
| VS::BindUniformBuffer(pass, host_buffer->EmplaceUniform(uniforms)); | |
| if (!pass.Draw().ok()) { | |
| return false; | |
| } | |
| } | |
| } | |
| host_buffer->Reset(); | |
| return true; | |
| }; | |
| OpenPlaygroundHere(callback); | |
| } | |
| TEST_P(RendererTest, CanRenderToTexture) { | |
| using VS = BoxFadeVertexShader; | |
| using FS = BoxFadeFragmentShader; | |
| auto context = GetContext(); | |
| ASSERT_TRUE(context); | |
| using BoxPipelineBuilder = PipelineBuilder<VS, FS>; | |
| auto pipeline_desc = | |
| BoxPipelineBuilder::MakeDefaultPipelineDescriptor(*context); | |
| pipeline_desc->SetSampleCount(SampleCount::kCount1); | |
| pipeline_desc->ClearDepthAttachment(); | |
| pipeline_desc->SetStencilPixelFormat(PixelFormat::kS8UInt); | |
| ASSERT_TRUE(pipeline_desc.has_value()); | |
| auto box_pipeline = | |
| context->GetPipelineLibrary()->GetPipeline(pipeline_desc).Get(); | |
| ASSERT_TRUE(box_pipeline); | |
| auto host_buffer = HostBuffer::Create(context->GetResourceAllocator()); | |
| VertexBufferBuilder<VS::PerVertexData> vertex_builder; | |
| vertex_builder.SetLabel("Box"); | |
| vertex_builder.AddVertices({ | |
| {{100, 100, 0.0}, {0.0, 0.0}}, // 1 | |
| {{800, 100, 0.0}, {1.0, 0.0}}, // 2 | |
| {{800, 800, 0.0}, {1.0, 1.0}}, // 3 | |
| {{100, 100, 0.0}, {0.0, 0.0}}, // 1 | |
| {{800, 800, 0.0}, {1.0, 1.0}}, // 3 | |
| {{100, 800, 0.0}, {0.0, 1.0}}, // 4 | |
| }); | |
| auto vertex_buffer = | |
| vertex_builder.CreateVertexBuffer(*context->GetResourceAllocator()); | |
| ASSERT_TRUE(vertex_buffer); | |
| auto bridge = CreateTextureForFixture("bay_bridge.jpg"); | |
| auto boston = CreateTextureForFixture("boston.jpg"); | |
| ASSERT_TRUE(bridge && boston); | |
| const std::unique_ptr<const Sampler>& sampler = | |
| context->GetSamplerLibrary()->GetSampler({}); | |
| ASSERT_TRUE(sampler); | |
| std::shared_ptr<RenderPass> r2t_pass; | |
| auto cmd_buffer = context->CreateCommandBuffer(); | |
| ASSERT_TRUE(cmd_buffer); | |
| { | |
| ColorAttachment color0; | |
| color0.load_action = LoadAction::kClear; | |
| color0.store_action = StoreAction::kStore; | |
| TextureDescriptor texture_descriptor; | |
| ASSERT_NE(pipeline_desc->GetColorAttachmentDescriptor(0u), nullptr); | |
| texture_descriptor.format = | |
| pipeline_desc->GetColorAttachmentDescriptor(0u)->format; | |
| texture_descriptor.storage_mode = StorageMode::kHostVisible; | |
| texture_descriptor.size = {400, 400}; | |
| texture_descriptor.mip_count = 1u; | |
| texture_descriptor.usage = | |
| static_cast<TextureUsageMask>(TextureUsage::kRenderTarget); | |
| color0.texture = | |
| context->GetResourceAllocator()->CreateTexture(texture_descriptor); | |
| ASSERT_TRUE(color0.IsValid()); | |
| color0.texture->SetLabel("r2t_target"); | |
| StencilAttachment stencil0; | |
| stencil0.load_action = LoadAction::kClear; | |
| stencil0.store_action = StoreAction::kDontCare; | |
| TextureDescriptor stencil_texture_desc; | |
| stencil_texture_desc.storage_mode = StorageMode::kDeviceTransient; | |
| stencil_texture_desc.size = texture_descriptor.size; | |
| stencil_texture_desc.format = PixelFormat::kS8UInt; | |
| stencil_texture_desc.usage = | |
| static_cast<TextureUsageMask>(TextureUsage::kRenderTarget); | |
| stencil0.texture = | |
| context->GetResourceAllocator()->CreateTexture(stencil_texture_desc); | |
| RenderTarget r2t_desc; | |
| r2t_desc.SetColorAttachment(color0, 0u); | |
| r2t_desc.SetStencilAttachment(stencil0); | |
| r2t_pass = cmd_buffer->CreateRenderPass(r2t_desc); | |
| ASSERT_TRUE(r2t_pass && r2t_pass->IsValid()); | |
| } | |
| r2t_pass->SetCommandLabel("Box"); | |
| r2t_pass->SetPipeline(box_pipeline); | |
| r2t_pass->SetVertexBuffer(vertex_buffer); | |
| FS::FrameInfo frame_info; | |
| frame_info.current_time = GetSecondsElapsed(); | |
| frame_info.cursor_position = GetCursorPosition(); | |
| frame_info.window_size.x = GetWindowSize().width; | |
| frame_info.window_size.y = GetWindowSize().height; | |
| FS::BindFrameInfo(*r2t_pass, host_buffer->EmplaceUniform(frame_info)); | |
| FS::BindContents1(*r2t_pass, boston, sampler); | |
| FS::BindContents2(*r2t_pass, bridge, sampler); | |
| VS::UniformBuffer uniforms; | |
| uniforms.mvp = Matrix::MakeOrthographic(ISize{1024, 768}) * | |
| Matrix::MakeTranslation({50.0f, 50.0f, 0.0f}); | |
| VS::BindUniformBuffer(*r2t_pass, host_buffer->EmplaceUniform(uniforms)); | |
| ASSERT_TRUE(r2t_pass->Draw().ok()); | |
| ASSERT_TRUE(r2t_pass->EncodeCommands()); | |
| } | |
| TEST_P(RendererTest, CanRenderInstanced) { | |
| if (GetParam() == PlaygroundBackend::kOpenGLES) { | |
| GTEST_SKIP_("Instancing is not supported on OpenGL."); | |
| } | |
| using VS = InstancedDrawVertexShader; | |
| using FS = InstancedDrawFragmentShader; | |
| VertexBufferBuilder<VS::PerVertexData> builder; | |
| ASSERT_EQ(Tessellator::Result::kSuccess, | |
| Tessellator{}.Tessellate( | |
| PathBuilder{} | |
| .AddRect(Rect::MakeXYWH(10, 10, 100, 100)) | |
| .TakePath(FillType::kOdd), | |
| 1.0f, | |
| [&builder](const float* vertices, size_t vertices_count, | |
| const uint16_t* indices, size_t indices_count) { | |
| for (auto i = 0u; i < vertices_count * 2; i += 2) { | |
| VS::PerVertexData data; | |
| data.vtx = {vertices[i], vertices[i + 1]}; | |
| builder.AppendVertex(data); | |
| } | |
| for (auto i = 0u; i < indices_count; i++) { | |
| builder.AppendIndex(indices[i]); | |
| } | |
| return true; | |
| })); | |
| ASSERT_NE(GetContext(), nullptr); | |
| auto pipeline = | |
| GetContext() | |
| ->GetPipelineLibrary() | |
| ->GetPipeline(PipelineBuilder<VS, FS>::MakeDefaultPipelineDescriptor( | |
| *GetContext()) | |
| ->SetSampleCount(SampleCount::kCount4) | |
| .SetStencilAttachmentDescriptors(std::nullopt)) | |
| .Get(); | |
| ASSERT_TRUE(pipeline && pipeline->IsValid()); | |
| static constexpr size_t kInstancesCount = 5u; | |
| VS::InstanceInfo<kInstancesCount> instances; | |
| for (size_t i = 0; i < kInstancesCount; i++) { | |
| instances.colors[i] = Color::Random(); | |
| } | |
| auto host_buffer = HostBuffer::Create(GetContext()->GetResourceAllocator()); | |
| ASSERT_TRUE(OpenPlaygroundHere([&](RenderPass& pass) -> bool { | |
| pass.SetPipeline(pipeline); | |
| pass.SetCommandLabel("InstancedDraw"); | |
| VS::FrameInfo frame_info; | |
| EXPECT_EQ(pass.GetOrthographicTransform(), | |
| Matrix::MakeOrthographic(pass.GetRenderTargetSize())); | |
| frame_info.mvp = | |
| pass.GetOrthographicTransform() * Matrix::MakeScale(GetContentScale()); | |
| VS::BindFrameInfo(pass, host_buffer->EmplaceUniform(frame_info)); | |
| VS::BindInstanceInfo(pass, host_buffer->EmplaceStorageBuffer(instances)); | |
| pass.SetVertexBuffer(builder.CreateVertexBuffer(*host_buffer)); | |
| pass.SetInstanceCount(kInstancesCount); | |
| pass.Draw(); | |
| host_buffer->Reset(); | |
| return true; | |
| })); | |
| } | |
| TEST_P(RendererTest, CanBlitTextureToTexture) { | |
| if (GetBackend() == PlaygroundBackend::kOpenGLES) { | |
| GTEST_SKIP() << "Mipmap test shader not supported on GLES."; | |
| } | |
| auto context = GetContext(); | |
| ASSERT_TRUE(context); | |
| using VS = MipmapsVertexShader; | |
| using FS = MipmapsFragmentShader; | |
| auto desc = PipelineBuilder<VS, FS>::MakeDefaultPipelineDescriptor(*context); | |
| ASSERT_TRUE(desc.has_value()); | |
| desc->SetSampleCount(SampleCount::kCount4); | |
| desc->SetStencilAttachmentDescriptors(std::nullopt); | |
| auto mipmaps_pipeline = | |
| context->GetPipelineLibrary()->GetPipeline(std::move(desc)).Get(); | |
| ASSERT_TRUE(mipmaps_pipeline); | |
| TextureDescriptor texture_desc; | |
| texture_desc.storage_mode = StorageMode::kHostVisible; | |
| texture_desc.format = PixelFormat::kR8G8B8A8UNormInt; | |
| texture_desc.size = {800, 600}; | |
| texture_desc.mip_count = 1u; | |
| texture_desc.usage = | |
| static_cast<TextureUsageMask>(TextureUsage::kRenderTarget) | | |
| static_cast<TextureUsageMask>(TextureUsage::kShaderRead); | |
| auto texture = context->GetResourceAllocator()->CreateTexture(texture_desc); | |
| ASSERT_TRUE(texture); | |
| auto bridge = CreateTextureForFixture("bay_bridge.jpg"); | |
| auto boston = CreateTextureForFixture("boston.jpg"); | |
| ASSERT_TRUE(bridge && boston); | |
| const std::unique_ptr<const Sampler>& sampler = | |
| context->GetSamplerLibrary()->GetSampler({}); | |
| ASSERT_TRUE(sampler); | |
| // Vertex buffer. | |
| VertexBufferBuilder<VS::PerVertexData> vertex_builder; | |
| vertex_builder.SetLabel("Box"); | |
| auto size = Point(boston->GetSize()); | |
| vertex_builder.AddVertices({ | |
| {{0, 0}, {0.0, 0.0}}, // 1 | |
| {{size.x, 0}, {1.0, 0.0}}, // 2 | |
| {{size.x, size.y}, {1.0, 1.0}}, // 3 | |
| {{0, 0}, {0.0, 0.0}}, // 1 | |
| {{size.x, size.y}, {1.0, 1.0}}, // 3 | |
| {{0, size.y}, {0.0, 1.0}}, // 4 | |
| }); | |
| auto vertex_buffer = | |
| vertex_builder.CreateVertexBuffer(*context->GetResourceAllocator()); | |
| ASSERT_TRUE(vertex_buffer); | |
| auto host_buffer = HostBuffer::Create(context->GetResourceAllocator()); | |
| Renderer::RenderCallback callback = [&](RenderTarget& render_target) { | |
| auto buffer = context->CreateCommandBuffer(); | |
| if (!buffer) { | |
| return false; | |
| } | |
| buffer->SetLabel("Playground Command Buffer"); | |
| { | |
| auto pass = buffer->CreateBlitPass(); | |
| if (!pass) { | |
| return false; | |
| } | |
| pass->SetLabel("Playground Blit Pass"); | |
| if (render_target.GetColorAttachments().empty()) { | |
| return false; | |
| } | |
| // Blit `bridge` to the top left corner of the texture. | |
| pass->AddCopy(bridge, texture); | |
| if (!pass->EncodeCommands(context->GetResourceAllocator())) { | |
| return false; | |
| } | |
| } | |
| { | |
| auto pass = buffer->CreateRenderPass(render_target); | |
| if (!pass) { | |
| return false; | |
| } | |
| pass->SetLabel("Playground Render Pass"); | |
| { | |
| pass->SetCommandLabel("Image"); | |
| pass->SetPipeline(mipmaps_pipeline); | |
| pass->SetVertexBuffer(vertex_buffer); | |
| VS::FrameInfo frame_info; | |
| EXPECT_EQ(pass->GetOrthographicTransform(), | |
| Matrix::MakeOrthographic(pass->GetRenderTargetSize())); | |
| frame_info.mvp = pass->GetOrthographicTransform() * | |
| Matrix::MakeScale(GetContentScale()); | |
| VS::BindFrameInfo(*pass, host_buffer->EmplaceUniform(frame_info)); | |
| FS::FragInfo frag_info; | |
| frag_info.lod = 0; | |
| FS::BindFragInfo(*pass, host_buffer->EmplaceUniform(frag_info)); | |
| auto& sampler = context->GetSamplerLibrary()->GetSampler({}); | |
| FS::BindTex(*pass, texture, sampler); | |
| pass->Draw(); | |
| } | |
| pass->EncodeCommands(); | |
| } | |
| if (!context->GetCommandQueue()->Submit({buffer}).ok()) { | |
| return false; | |
| } | |
| host_buffer->Reset(); | |
| return true; | |
| }; | |
| OpenPlaygroundHere(callback); | |
| } | |
| TEST_P(RendererTest, CanBlitTextureToBuffer) { | |
| if (GetBackend() == PlaygroundBackend::kOpenGLES) { | |
| GTEST_SKIP() << "Mipmap test shader not supported on GLES."; | |
| } | |
| auto context = GetContext(); | |
| ASSERT_TRUE(context); | |
| using VS = MipmapsVertexShader; | |
| using FS = MipmapsFragmentShader; | |
| auto desc = PipelineBuilder<VS, FS>::MakeDefaultPipelineDescriptor(*context); | |
| ASSERT_TRUE(desc.has_value()); | |
| desc->SetSampleCount(SampleCount::kCount4); | |
| desc->SetStencilAttachmentDescriptors(std::nullopt); | |
| auto mipmaps_pipeline = | |
| context->GetPipelineLibrary()->GetPipeline(std::move(desc)).Get(); | |
| ASSERT_TRUE(mipmaps_pipeline); | |
| auto bridge = CreateTextureForFixture("bay_bridge.jpg"); | |
| auto boston = CreateTextureForFixture("boston.jpg"); | |
| ASSERT_TRUE(bridge && boston); | |
| const std::unique_ptr<const Sampler>& sampler = | |
| context->GetSamplerLibrary()->GetSampler({}); | |
| ASSERT_TRUE(sampler); | |
| TextureDescriptor texture_desc; | |
| texture_desc.storage_mode = StorageMode::kHostVisible; | |
| texture_desc.format = PixelFormat::kR8G8B8A8UNormInt; | |
| texture_desc.size = bridge->GetTextureDescriptor().size; | |
| texture_desc.mip_count = 1u; | |
| texture_desc.usage = | |
| static_cast<TextureUsageMask>(TextureUsage::kRenderTarget) | | |
| static_cast<TextureUsageMask>(TextureUsage::kShaderWrite) | | |
| static_cast<TextureUsageMask>(TextureUsage::kShaderRead); | |
| DeviceBufferDescriptor device_buffer_desc; | |
| device_buffer_desc.storage_mode = StorageMode::kHostVisible; | |
| device_buffer_desc.size = | |
| bridge->GetTextureDescriptor().GetByteSizeOfBaseMipLevel(); | |
| auto device_buffer = | |
| context->GetResourceAllocator()->CreateBuffer(device_buffer_desc); | |
| // Vertex buffer. | |
| VertexBufferBuilder<VS::PerVertexData> vertex_builder; | |
| vertex_builder.SetLabel("Box"); | |
| auto size = Point(boston->GetSize()); | |
| vertex_builder.AddVertices({ | |
| {{0, 0}, {0.0, 0.0}}, // 1 | |
| {{size.x, 0}, {1.0, 0.0}}, // 2 | |
| {{size.x, size.y}, {1.0, 1.0}}, // 3 | |
| {{0, 0}, {0.0, 0.0}}, // 1 | |
| {{size.x, size.y}, {1.0, 1.0}}, // 3 | |
| {{0, size.y}, {0.0, 1.0}}, // 4 | |
| }); | |
| auto vertex_buffer = | |
| vertex_builder.CreateVertexBuffer(*context->GetResourceAllocator()); | |
| ASSERT_TRUE(vertex_buffer); | |
| auto host_buffer = HostBuffer::Create(context->GetResourceAllocator()); | |
| Renderer::RenderCallback callback = [&](RenderTarget& render_target) { | |
| { | |
| auto buffer = context->CreateCommandBuffer(); | |
| if (!buffer) { | |
| return false; | |
| } | |
| buffer->SetLabel("Playground Command Buffer"); | |
| auto pass = buffer->CreateBlitPass(); | |
| if (!pass) { | |
| return false; | |
| } | |
| pass->SetLabel("Playground Blit Pass"); | |
| if (render_target.GetColorAttachments().empty()) { | |
| return false; | |
| } | |
| // Blit `bridge` to the top left corner of the texture. | |
| pass->AddCopy(bridge, device_buffer); | |
| pass->EncodeCommands(context->GetResourceAllocator()); | |
| if (!context->GetCommandQueue()->Submit({buffer}).ok()) { | |
| return false; | |
| } | |
| } | |
| { | |
| auto buffer = context->CreateCommandBuffer(); | |
| if (!buffer) { | |
| return false; | |
| } | |
| buffer->SetLabel("Playground Command Buffer"); | |
| auto pass = buffer->CreateRenderPass(render_target); | |
| if (!pass) { | |
| return false; | |
| } | |
| pass->SetLabel("Playground Render Pass"); | |
| { | |
| pass->SetCommandLabel("Image"); | |
| pass->SetPipeline(mipmaps_pipeline); | |
| pass->SetVertexBuffer(vertex_buffer); | |
| VS::FrameInfo frame_info; | |
| EXPECT_EQ(pass->GetOrthographicTransform(), | |
| Matrix::MakeOrthographic(pass->GetRenderTargetSize())); | |
| frame_info.mvp = pass->GetOrthographicTransform() * | |
| Matrix::MakeScale(GetContentScale()); | |
| VS::BindFrameInfo(*pass, host_buffer->EmplaceUniform(frame_info)); | |
| FS::FragInfo frag_info; | |
| frag_info.lod = 0; | |
| FS::BindFragInfo(*pass, host_buffer->EmplaceUniform(frag_info)); | |
| const std::unique_ptr<const Sampler>& sampler = | |
| context->GetSamplerLibrary()->GetSampler({}); | |
| auto buffer_view = DeviceBuffer::AsBufferView(device_buffer); | |
| auto texture = | |
| context->GetResourceAllocator()->CreateTexture(texture_desc); | |
| if (!texture->SetContents(device_buffer->OnGetContents(), | |
| buffer_view.range.length)) { | |
| VALIDATION_LOG << "Could not upload texture to device memory"; | |
| return false; | |
| } | |
| FS::BindTex(*pass, texture, sampler); | |
| pass->Draw().ok(); | |
| } | |
| pass->EncodeCommands(); | |
| if (!context->GetCommandQueue()->Submit({buffer}).ok()) { | |
| return false; | |
| } | |
| } | |
| host_buffer->Reset(); | |
| return true; | |
| }; | |
| OpenPlaygroundHere(callback); | |
| } | |
| TEST_P(RendererTest, CanGenerateMipmaps) { | |
| if (GetBackend() == PlaygroundBackend::kOpenGLES) { | |
| GTEST_SKIP() << "Mipmap test shader not supported on GLES."; | |
| } | |
| auto context = GetContext(); | |
| ASSERT_TRUE(context); | |
| using VS = MipmapsVertexShader; | |
| using FS = MipmapsFragmentShader; | |
| auto desc = PipelineBuilder<VS, FS>::MakeDefaultPipelineDescriptor(*context); | |
| ASSERT_TRUE(desc.has_value()); | |
| desc->SetSampleCount(SampleCount::kCount4); | |
| desc->SetStencilAttachmentDescriptors(std::nullopt); | |
| auto mipmaps_pipeline = | |
| context->GetPipelineLibrary()->GetPipeline(std::move(desc)).Get(); | |
| ASSERT_TRUE(mipmaps_pipeline); | |
| auto boston = CreateTextureForFixture("boston.jpg", true); | |
| ASSERT_TRUE(boston); | |
| // Vertex buffer. | |
| VertexBufferBuilder<VS::PerVertexData> vertex_builder; | |
| vertex_builder.SetLabel("Box"); | |
| auto size = Point(boston->GetSize()); | |
| vertex_builder.AddVertices({ | |
| {{0, 0}, {0.0, 0.0}}, // 1 | |
| {{size.x, 0}, {1.0, 0.0}}, // 2 | |
| {{size.x, size.y}, {1.0, 1.0}}, // 3 | |
| {{0, 0}, {0.0, 0.0}}, // 1 | |
| {{size.x, size.y}, {1.0, 1.0}}, // 3 | |
| {{0, size.y}, {0.0, 1.0}}, // 4 | |
| }); | |
| auto vertex_buffer = | |
| vertex_builder.CreateVertexBuffer(*context->GetResourceAllocator()); | |
| ASSERT_TRUE(vertex_buffer); | |
| bool first_frame = true; | |
| auto host_buffer = HostBuffer::Create(context->GetResourceAllocator()); | |
| Renderer::RenderCallback callback = [&](RenderTarget& render_target) { | |
| const char* mip_filter_names[] = {"Nearest", "Linear"}; | |
| const MipFilter mip_filters[] = {MipFilter::kNearest, MipFilter::kLinear}; | |
| const char* min_filter_names[] = {"Nearest", "Linear"}; | |
| const MinMagFilter min_filters[] = {MinMagFilter::kNearest, | |
| MinMagFilter::kLinear}; | |
| // UI state. | |
| static int selected_mip_filter = 1; | |
| static int selected_min_filter = 0; | |
| static float lod = 4.5; | |
| ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize); | |
| ImGui::Combo("Mip filter", &selected_mip_filter, mip_filter_names, | |
| sizeof(mip_filter_names) / sizeof(char*)); | |
| ImGui::Combo("Min filter", &selected_min_filter, min_filter_names, | |
| sizeof(min_filter_names) / sizeof(char*)); | |
| ImGui::SliderFloat("LOD", &lod, 0, boston->GetMipCount() - 1); | |
| ImGui::End(); | |
| auto buffer = context->CreateCommandBuffer(); | |
| if (!buffer) { | |
| return false; | |
| } | |
| buffer->SetLabel("Playground Command Buffer"); | |
| if (first_frame) { | |
| auto pass = buffer->CreateBlitPass(); | |
| if (!pass) { | |
| return false; | |
| } | |
| pass->SetLabel("Playground Blit Pass"); | |
| pass->GenerateMipmap(boston, "Boston Mipmap"); | |
| pass->EncodeCommands(context->GetResourceAllocator()); | |
| } | |
| first_frame = false; | |
| { | |
| auto pass = buffer->CreateRenderPass(render_target); | |
| if (!pass) { | |
| return false; | |
| } | |
| pass->SetLabel("Playground Render Pass"); | |
| { | |
| pass->SetCommandLabel("Image LOD"); | |
| pass->SetPipeline(mipmaps_pipeline); | |
| pass->SetVertexBuffer(vertex_buffer); | |
| VS::FrameInfo frame_info; | |
| EXPECT_EQ(pass->GetOrthographicTransform(), | |
| Matrix::MakeOrthographic(pass->GetRenderTargetSize())); | |
| frame_info.mvp = pass->GetOrthographicTransform() * | |
| Matrix::MakeScale(GetContentScale()); | |
| VS::BindFrameInfo(*pass, host_buffer->EmplaceUniform(frame_info)); | |
| FS::FragInfo frag_info; | |
| frag_info.lod = lod; | |
| FS::BindFragInfo(*pass, host_buffer->EmplaceUniform(frag_info)); | |
| SamplerDescriptor sampler_desc; | |
| sampler_desc.mip_filter = mip_filters[selected_mip_filter]; | |
| sampler_desc.min_filter = min_filters[selected_min_filter]; | |
| const std::unique_ptr<const Sampler>& sampler = | |
| context->GetSamplerLibrary()->GetSampler(sampler_desc); | |
| FS::BindTex(*pass, boston, sampler); | |
| pass->Draw(); | |
| } | |
| pass->EncodeCommands(); | |
| } | |
| if (!context->GetCommandQueue()->Submit({buffer}).ok()) { | |
| return false; | |
| } | |
| host_buffer->Reset(); | |
| return true; | |
| }; | |
| OpenPlaygroundHere(callback); | |
| } | |
| TEST_P(RendererTest, TheImpeller) { | |
| using VS = ImpellerVertexShader; | |
| using FS = ImpellerFragmentShader; | |
| auto context = GetContext(); | |
| auto pipeline_descriptor = | |
| PipelineBuilder<VS, FS>::MakeDefaultPipelineDescriptor(*context); | |
| ASSERT_TRUE(pipeline_descriptor.has_value()); | |
| pipeline_descriptor->SetSampleCount(SampleCount::kCount4); | |
| pipeline_descriptor->SetStencilAttachmentDescriptors(std::nullopt); | |
| auto pipeline = | |
| context->GetPipelineLibrary()->GetPipeline(pipeline_descriptor).Get(); | |
| ASSERT_TRUE(pipeline && pipeline->IsValid()); | |
| auto blue_noise = CreateTextureForFixture("blue_noise.png"); | |
| SamplerDescriptor noise_sampler_desc; | |
| noise_sampler_desc.width_address_mode = SamplerAddressMode::kRepeat; | |
| noise_sampler_desc.height_address_mode = SamplerAddressMode::kRepeat; | |
| const std::unique_ptr<const Sampler>& noise_sampler = | |
| context->GetSamplerLibrary()->GetSampler(noise_sampler_desc); | |
| auto cube_map = CreateTextureCubeForFixture( | |
| {"table_mountain_px.png", "table_mountain_nx.png", | |
| "table_mountain_py.png", "table_mountain_ny.png", | |
| "table_mountain_pz.png", "table_mountain_nz.png"}); | |
| const std::unique_ptr<const Sampler>& cube_map_sampler = | |
| context->GetSamplerLibrary()->GetSampler({}); | |
| auto host_buffer = HostBuffer::Create(context->GetResourceAllocator()); | |
| SinglePassCallback callback = [&](RenderPass& pass) { | |
| auto size = pass.GetRenderTargetSize(); | |
| pass.SetPipeline(pipeline); | |
| pass.SetCommandLabel("Impeller SDF scene"); | |
| VertexBufferBuilder<VS::PerVertexData> builder; | |
| builder.AddVertices({{Point()}, | |
| {Point(0, size.height)}, | |
| {Point(size.width, 0)}, | |
| {Point(size.width, 0)}, | |
| {Point(0, size.height)}, | |
| {Point(size.width, size.height)}}); | |
| pass.SetVertexBuffer(builder.CreateVertexBuffer(*host_buffer)); | |
| VS::FrameInfo frame_info; | |
| EXPECT_EQ(pass.GetOrthographicTransform(), Matrix::MakeOrthographic(size)); | |
| frame_info.mvp = pass.GetOrthographicTransform(); | |
| VS::BindFrameInfo(pass, host_buffer->EmplaceUniform(frame_info)); | |
| FS::FragInfo fs_uniform; | |
| fs_uniform.texture_size = Point(size); | |
| fs_uniform.time = GetSecondsElapsed(); | |
| FS::BindFragInfo(pass, host_buffer->EmplaceUniform(fs_uniform)); | |
| FS::BindBlueNoise(pass, blue_noise, noise_sampler); | |
| FS::BindCubeMap(pass, cube_map, cube_map_sampler); | |
| pass.Draw().ok(); | |
| host_buffer->Reset(); | |
| return true; | |
| }; | |
| OpenPlaygroundHere(callback); | |
| } | |
| TEST_P(RendererTest, ArrayUniforms) { | |
| using VS = ArrayVertexShader; | |
| using FS = ArrayFragmentShader; | |
| auto context = GetContext(); | |
| auto pipeline_descriptor = | |
| PipelineBuilder<VS, FS>::MakeDefaultPipelineDescriptor(*context); | |
| ASSERT_TRUE(pipeline_descriptor.has_value()); | |
| pipeline_descriptor->SetSampleCount(SampleCount::kCount4); | |
| pipeline_descriptor->SetStencilAttachmentDescriptors(std::nullopt); | |
| auto pipeline = | |
| context->GetPipelineLibrary()->GetPipeline(pipeline_descriptor).Get(); | |
| ASSERT_TRUE(pipeline && pipeline->IsValid()); | |
| auto host_buffer = HostBuffer::Create(context->GetResourceAllocator()); | |
| SinglePassCallback callback = [&](RenderPass& pass) { | |
| auto size = pass.GetRenderTargetSize(); | |
| pass.SetPipeline(pipeline); | |
| pass.SetCommandLabel("Google Dots"); | |
| VertexBufferBuilder<VS::PerVertexData> builder; | |
| builder.AddVertices({{Point()}, | |
| {Point(0, size.height)}, | |
| {Point(size.width, 0)}, | |
| {Point(size.width, 0)}, | |
| {Point(0, size.height)}, | |
| {Point(size.width, size.height)}}); | |
| pass.SetVertexBuffer(builder.CreateVertexBuffer(*host_buffer)); | |
| VS::FrameInfo frame_info; | |
| EXPECT_EQ(pass.GetOrthographicTransform(), Matrix::MakeOrthographic(size)); | |
| frame_info.mvp = | |
| pass.GetOrthographicTransform() * Matrix::MakeScale(GetContentScale()); | |
| VS::BindFrameInfo(pass, host_buffer->EmplaceUniform(frame_info)); | |
| auto time = GetSecondsElapsed(); | |
| auto y_pos = [&time](float x) { | |
| return 400 + 10 * std::cos(time * 5 + x / 6); | |
| }; | |
| FS::FragInfo fs_uniform = { | |
| .circle_positions = {Point(430, y_pos(0)), Point(480, y_pos(1)), | |
| Point(530, y_pos(2)), Point(580, y_pos(3))}, | |
| .colors = {Color::MakeRGBA8(66, 133, 244, 255), | |
| Color::MakeRGBA8(219, 68, 55, 255), | |
| Color::MakeRGBA8(244, 180, 0, 255), | |
| Color::MakeRGBA8(15, 157, 88, 255)}, | |
| }; | |
| FS::BindFragInfo(pass, host_buffer->EmplaceUniform(fs_uniform)); | |
| pass.Draw(); | |
| host_buffer->Reset(); | |
| return true; | |
| }; | |
| OpenPlaygroundHere(callback); | |
| } | |
| TEST_P(RendererTest, InactiveUniforms) { | |
| using VS = InactiveUniformsVertexShader; | |
| using FS = InactiveUniformsFragmentShader; | |
| auto context = GetContext(); | |
| auto pipeline_descriptor = | |
| PipelineBuilder<VS, FS>::MakeDefaultPipelineDescriptor(*context); | |
| ASSERT_TRUE(pipeline_descriptor.has_value()); | |
| pipeline_descriptor->SetSampleCount(SampleCount::kCount4); | |
| pipeline_descriptor->SetStencilAttachmentDescriptors(std::nullopt); | |
| auto pipeline = | |
| context->GetPipelineLibrary()->GetPipeline(pipeline_descriptor).Get(); | |
| ASSERT_TRUE(pipeline && pipeline->IsValid()); | |
| auto host_buffer = HostBuffer::Create(context->GetResourceAllocator()); | |
| SinglePassCallback callback = [&](RenderPass& pass) { | |
| auto size = pass.GetRenderTargetSize(); | |
| pass.SetPipeline(pipeline); | |
| pass.SetCommandLabel("Inactive Uniform"); | |
| VertexBufferBuilder<VS::PerVertexData> builder; | |
| builder.AddVertices({{Point()}, | |
| {Point(0, size.height)}, | |
| {Point(size.width, 0)}, | |
| {Point(size.width, 0)}, | |
| {Point(0, size.height)}, | |
| {Point(size.width, size.height)}}); | |
| pass.SetVertexBuffer(builder.CreateVertexBuffer(*host_buffer)); | |
| VS::FrameInfo frame_info; | |
| EXPECT_EQ(pass.GetOrthographicTransform(), Matrix::MakeOrthographic(size)); | |
| frame_info.mvp = | |
| pass.GetOrthographicTransform() * Matrix::MakeScale(GetContentScale()); | |
| VS::BindFrameInfo(pass, host_buffer->EmplaceUniform(frame_info)); | |
| FS::FragInfo fs_uniform = {.unused_color = Color::Red(), | |
| .color = Color::Green()}; | |
| FS::BindFragInfo(pass, host_buffer->EmplaceUniform(fs_uniform)); | |
| pass.Draw().ok(); | |
| host_buffer->Reset(); | |
| return true; | |
| }; | |
| OpenPlaygroundHere(callback); | |
| } | |
| TEST_P(RendererTest, CanCreateCPUBackedTexture) { | |
| if (GetParam() == PlaygroundBackend::kOpenGLES) { | |
| GTEST_SKIP_("CPU backed textures are not supported on OpenGLES."); | |
| } | |
| auto context = GetContext(); | |
| auto allocator = context->GetResourceAllocator(); | |
| size_t dimension = 2; | |
| do { | |
| ISize size(dimension, dimension); | |
| TextureDescriptor texture_descriptor; | |
| texture_descriptor.storage_mode = StorageMode::kHostVisible; | |
| texture_descriptor.format = PixelFormat::kR8G8B8A8UNormInt; | |
| texture_descriptor.size = size; | |
| auto row_bytes = | |
| std::max(static_cast<uint16_t>(size.width * 4), | |
| allocator->MinimumBytesPerRow(texture_descriptor.format)); | |
| auto buffer_size = size.height * row_bytes; | |
| DeviceBufferDescriptor buffer_descriptor; | |
| buffer_descriptor.storage_mode = StorageMode::kHostVisible; | |
| buffer_descriptor.size = buffer_size; | |
| auto buffer = allocator->CreateBuffer(buffer_descriptor); | |
| ASSERT_TRUE(buffer); | |
| auto texture = buffer->AsTexture(*allocator, texture_descriptor, row_bytes); | |
| ASSERT_TRUE(texture); | |
| ASSERT_TRUE(texture->IsValid()); | |
| dimension *= 2; | |
| } while (dimension <= 8192); | |
| } | |
| TEST_P(RendererTest, DefaultIndexSize) { | |
| using VS = BoxFadeVertexShader; | |
| // Default to 16bit index buffer size, as this is a reasonable default and | |
| // supported on all backends without extensions. | |
| VertexBufferBuilder<VS::PerVertexData> vertex_builder; | |
| vertex_builder.AppendIndex(0u); | |
| ASSERT_EQ(vertex_builder.GetIndexType(), IndexType::k16bit); | |
| } | |
| TEST_P(RendererTest, DefaultIndexBehavior) { | |
| using VS = BoxFadeVertexShader; | |
| // Do not create any index buffer if no indices were provided. | |
| VertexBufferBuilder<VS::PerVertexData> vertex_builder; | |
| ASSERT_EQ(vertex_builder.GetIndexType(), IndexType::kNone); | |
| } | |
| TEST_P(RendererTest, VertexBufferBuilder) { | |
| // Does not create index buffer if one is provided. | |
| using VS = BoxFadeVertexShader; | |
| VertexBufferBuilder<VS::PerVertexData> vertex_builder; | |
| vertex_builder.SetLabel("Box"); | |
| vertex_builder.AddVertices({ | |
| {{100, 100, 0.0}, {0.0, 0.0}}, // 1 | |
| {{800, 100, 0.0}, {1.0, 0.0}}, // 2 | |
| {{800, 800, 0.0}, {1.0, 1.0}}, // 3 | |
| {{100, 800, 0.0}, {0.0, 1.0}}, // 4 | |
| }); | |
| vertex_builder.AppendIndex(0); | |
| vertex_builder.AppendIndex(1); | |
| vertex_builder.AppendIndex(2); | |
| vertex_builder.AppendIndex(1); | |
| vertex_builder.AppendIndex(2); | |
| vertex_builder.AppendIndex(3); | |
| ASSERT_EQ(vertex_builder.GetIndexCount(), 6u); | |
| ASSERT_EQ(vertex_builder.GetVertexCount(), 4u); | |
| } | |
| class CompareFunctionUIData { | |
| public: | |
| CompareFunctionUIData() { | |
| labels_.push_back("Never"); | |
| functions_.push_back(CompareFunction::kNever); | |
| labels_.push_back("Always"); | |
| functions_.push_back(CompareFunction::kAlways); | |
| labels_.push_back("Less"); | |
| functions_.push_back(CompareFunction::kLess); | |
| labels_.push_back("Equal"); | |
| functions_.push_back(CompareFunction::kEqual); | |
| labels_.push_back("LessEqual"); | |
| functions_.push_back(CompareFunction::kLessEqual); | |
| labels_.push_back("Greater"); | |
| functions_.push_back(CompareFunction::kGreater); | |
| labels_.push_back("NotEqual"); | |
| functions_.push_back(CompareFunction::kNotEqual); | |
| labels_.push_back("GreaterEqual"); | |
| functions_.push_back(CompareFunction::kGreaterEqual); | |
| assert(labels_.size() == functions_.size()); | |
| } | |
| const char* const* labels() const { return &labels_[0]; } | |
| int size() const { return labels_.size(); } | |
| int IndexOf(CompareFunction func) const { | |
| for (size_t i = 0; i < functions_.size(); i++) { | |
| if (functions_[i] == func) { | |
| return i; | |
| } | |
| } | |
| FML_UNREACHABLE(); | |
| return -1; | |
| } | |
| CompareFunction FunctionOf(int index) const { return functions_[index]; } | |
| private: | |
| std::vector<const char*> labels_; | |
| std::vector<CompareFunction> functions_; | |
| }; | |
| static const CompareFunctionUIData& CompareFunctionUI() { | |
| static CompareFunctionUIData data; | |
| return data; | |
| } | |
| TEST_P(RendererTest, StencilMask) { | |
| using VS = BoxFadeVertexShader; | |
| using FS = BoxFadeFragmentShader; | |
| auto context = GetContext(); | |
| ASSERT_TRUE(context); | |
| using BoxFadePipelineBuilder = PipelineBuilder<VS, FS>; | |
| auto desc = BoxFadePipelineBuilder::MakeDefaultPipelineDescriptor(*context); | |
| ASSERT_TRUE(desc.has_value()); | |
| // Vertex buffer. | |
| VertexBufferBuilder<VS::PerVertexData> vertex_builder; | |
| vertex_builder.SetLabel("Box"); | |
| vertex_builder.AddVertices({ | |
| {{100, 100, 0.0}, {0.0, 0.0}}, // 1 | |
| {{800, 100, 0.0}, {1.0, 0.0}}, // 2 | |
| {{800, 800, 0.0}, {1.0, 1.0}}, // 3 | |
| {{100, 100, 0.0}, {0.0, 0.0}}, // 1 | |
| {{800, 800, 0.0}, {1.0, 1.0}}, // 3 | |
| {{100, 800, 0.0}, {0.0, 1.0}}, // 4 | |
| }); | |
| auto vertex_buffer = | |
| vertex_builder.CreateVertexBuffer(*context->GetResourceAllocator()); | |
| ASSERT_TRUE(vertex_buffer); | |
| desc->SetSampleCount(SampleCount::kCount4); | |
| desc->SetStencilAttachmentDescriptors(std::nullopt); | |
| auto bridge = CreateTextureForFixture("bay_bridge.jpg"); | |
| auto boston = CreateTextureForFixture("boston.jpg"); | |
| ASSERT_TRUE(bridge && boston); | |
| const std::unique_ptr<const Sampler>& sampler = | |
| context->GetSamplerLibrary()->GetSampler({}); | |
| ASSERT_TRUE(sampler); | |
| static bool mirror = false; | |
| static int stencil_reference_write = 0xFF; | |
| static int stencil_reference_read = 0x1; | |
| std::vector<uint8_t> stencil_contents; | |
| static int last_stencil_contents_reference_value = 0; | |
| static int current_front_compare = | |
| CompareFunctionUI().IndexOf(CompareFunction::kLessEqual); | |
| static int current_back_compare = | |
| CompareFunctionUI().IndexOf(CompareFunction::kLessEqual); | |
| auto host_buffer = HostBuffer::Create(context->GetResourceAllocator()); | |
| Renderer::RenderCallback callback = [&](RenderTarget& render_target) { | |
| auto buffer = context->CreateCommandBuffer(); | |
| if (!buffer) { | |
| return false; | |
| } | |
| buffer->SetLabel("Playground Command Buffer"); | |
| { | |
| // Configure the stencil attachment for the test. | |
| RenderTarget::AttachmentConfig stencil_config; | |
| stencil_config.load_action = LoadAction::kLoad; | |
| stencil_config.store_action = StoreAction::kDontCare; | |
| stencil_config.storage_mode = StorageMode::kHostVisible; | |
| render_target.SetupDepthStencilAttachments( | |
| *context, *context->GetResourceAllocator(), | |
| render_target.GetRenderTargetSize(), true, "stencil", stencil_config); | |
| // Fill the stencil buffer with an checkerboard pattern. | |
| const auto target_width = render_target.GetRenderTargetSize().width; | |
| const auto target_height = render_target.GetRenderTargetSize().height; | |
| const size_t target_size = target_width * target_height; | |
| if (stencil_contents.size() != target_size || | |
| last_stencil_contents_reference_value != stencil_reference_write) { | |
| stencil_contents.resize(target_size); | |
| last_stencil_contents_reference_value = stencil_reference_write; | |
| for (int y = 0; y < target_height; y++) { | |
| for (int x = 0; x < target_width; x++) { | |
| const auto index = y * target_width + x; | |
| const auto kCheckSize = 64; | |
| const auto value = | |
| (((y / kCheckSize) + (x / kCheckSize)) % 2 == 0) * | |
| stencil_reference_write; | |
| stencil_contents[index] = value; | |
| } | |
| } | |
| } | |
| if (!render_target.GetStencilAttachment()->texture->SetContents( | |
| stencil_contents.data(), stencil_contents.size(), 0, false)) { | |
| VALIDATION_LOG << "Could not upload stencil contents to device memory"; | |
| return false; | |
| } | |
| auto pass = buffer->CreateRenderPass(render_target); | |
| if (!pass) { | |
| return false; | |
| } | |
| pass->SetLabel("Stencil Buffer"); | |
| ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize); | |
| ImGui::SliderInt("Stencil Write Value", &stencil_reference_write, 0, | |
| 0xFF); | |
| ImGui::SliderInt("Stencil Compare Value", &stencil_reference_read, 0, | |
| 0xFF); | |
| ImGui::Checkbox("Back face mode", &mirror); | |
| ImGui::ListBox("Front face compare function", ¤t_front_compare, | |
| CompareFunctionUI().labels(), CompareFunctionUI().size()); | |
| ImGui::ListBox("Back face compare function", ¤t_back_compare, | |
| CompareFunctionUI().labels(), CompareFunctionUI().size()); | |
| ImGui::End(); | |
| StencilAttachmentDescriptor front; | |
| front.stencil_compare = | |
| CompareFunctionUI().FunctionOf(current_front_compare); | |
| StencilAttachmentDescriptor back; | |
| back.stencil_compare = | |
| CompareFunctionUI().FunctionOf(current_back_compare); | |
| desc->SetStencilAttachmentDescriptors(front, back); | |
| auto pipeline = context->GetPipelineLibrary()->GetPipeline(desc).Get(); | |
| assert(pipeline && pipeline->IsValid()); | |
| pass->SetCommandLabel("Box"); | |
| pass->SetPipeline(pipeline); | |
| pass->SetStencilReference(stencil_reference_read); | |
| pass->SetVertexBuffer(vertex_buffer); | |
| VS::UniformBuffer uniforms; | |
| EXPECT_EQ(pass->GetOrthographicTransform(), | |
| Matrix::MakeOrthographic(pass->GetRenderTargetSize())); | |
| uniforms.mvp = pass->GetOrthographicTransform() * | |
| Matrix::MakeScale(GetContentScale()); | |
| if (mirror) { | |
| uniforms.mvp = Matrix::MakeScale(Vector2(-1, 1)) * uniforms.mvp; | |
| } | |
| VS::BindUniformBuffer(*pass, host_buffer->EmplaceUniform(uniforms)); | |
| FS::FrameInfo frame_info; | |
| frame_info.current_time = GetSecondsElapsed(); | |
| frame_info.cursor_position = GetCursorPosition(); | |
| frame_info.window_size.x = GetWindowSize().width; | |
| frame_info.window_size.y = GetWindowSize().height; | |
| FS::BindFrameInfo(*pass, host_buffer->EmplaceUniform(frame_info)); | |
| FS::BindContents1(*pass, boston, sampler); | |
| FS::BindContents2(*pass, bridge, sampler); | |
| if (!pass->Draw().ok()) { | |
| return false; | |
| } | |
| pass->EncodeCommands(); | |
| } | |
| if (!context->GetCommandQueue()->Submit({buffer}).ok()) { | |
| return false; | |
| } | |
| host_buffer->Reset(); | |
| return true; | |
| }; | |
| OpenPlaygroundHere(callback); | |
| } | |
| TEST_P(RendererTest, CanLookupRenderTargetProperties) { | |
| auto context = GetContext(); | |
| auto cmd_buffer = context->CreateCommandBuffer(); | |
| auto render_target_cache = std::make_shared<RenderTargetAllocator>( | |
| GetContext()->GetResourceAllocator()); | |
| auto render_target = render_target_cache->CreateOffscreen( | |
| *context, {100, 100}, /*mip_count=*/1); | |
| auto render_pass = cmd_buffer->CreateRenderPass(render_target); | |
| EXPECT_EQ(render_pass->GetSampleCount(), render_target.GetSampleCount()); | |
| EXPECT_EQ(render_pass->GetRenderTargetPixelFormat(), | |
| render_target.GetRenderTargetPixelFormat()); | |
| EXPECT_EQ(render_pass->HasStencilAttachment(), | |
| render_target.GetStencilAttachment().has_value()); | |
| EXPECT_EQ(render_pass->GetRenderTargetSize(), | |
| render_target.GetRenderTargetSize()); | |
| render_pass->EncodeCommands(); | |
| } | |
| TEST_P(RendererTest, | |
| RenderTargetCreateOffscreenMSAASetsDefaultDepthStencilFormat) { | |
| auto context = GetContext(); | |
| auto render_target_cache = std::make_shared<RenderTargetAllocator>( | |
| GetContext()->GetResourceAllocator()); | |
| RenderTarget render_target = render_target_cache->CreateOffscreenMSAA( | |
| *context, {100, 100}, /*mip_count=*/1); | |
| EXPECT_EQ(render_target.GetDepthAttachment() | |
| ->texture->GetTextureDescriptor() | |
| .format, | |
| GetContext()->GetCapabilities()->GetDefaultDepthStencilFormat()); | |
| } | |
| template <class VertexShader, class FragmentShader> | |
| std::shared_ptr<Pipeline<PipelineDescriptor>> CreateDefaultPipeline( | |
| const std::shared_ptr<Context>& context) { | |
| using TexturePipelineBuilder = PipelineBuilder<VertexShader, FragmentShader>; | |
| auto pipeline_desc = | |
| TexturePipelineBuilder::MakeDefaultPipelineDescriptor(*context); | |
| if (!pipeline_desc.has_value()) { | |
| return nullptr; | |
| } | |
| pipeline_desc->SetSampleCount(SampleCount::kCount4); | |
| pipeline_desc->SetStencilAttachmentDescriptors(std::nullopt); | |
| auto pipeline = | |
| context->GetPipelineLibrary()->GetPipeline(pipeline_desc).Get(); | |
| if (!pipeline || !pipeline->IsValid()) { | |
| return nullptr; | |
| } | |
| return pipeline; | |
| } | |
| TEST_P(RendererTest, CanSepiaToneWithSubpasses) { | |
| // The GLES framebuffer fetch implementation currently does not support this. | |
| // TODO(chinmaygarde): revisit after the GLES framebuffer fetch capabilities | |
| // are clarified. | |
| if (GetParam() == PlaygroundBackend::kOpenGLES) { | |
| GTEST_SKIP_("Not supported on GLES."); | |
| } | |
| // Define shader types | |
| using TextureVS = TextureVertexShader; | |
| using TextureFS = TextureFragmentShader; | |
| using SepiaVS = SepiaVertexShader; | |
| using SepiaFS = SepiaFragmentShader; | |
| auto context = GetContext(); | |
| ASSERT_TRUE(context); | |
| if (!context->GetCapabilities()->SupportsFramebufferFetch()) { | |
| GTEST_SKIP_( | |
| "This test uses framebuffer fetch and the backend doesn't support it."); | |
| return; | |
| } | |
| // Create pipelines. | |
| auto texture_pipeline = CreateDefaultPipeline<TextureVS, TextureFS>(context); | |
| auto sepia_pipeline = CreateDefaultPipeline<SepiaVS, SepiaFS>(context); | |
| ASSERT_TRUE(texture_pipeline); | |
| ASSERT_TRUE(sepia_pipeline); | |
| // Vertex buffer builders. | |
| VertexBufferBuilder<TextureVS::PerVertexData> texture_vtx_builder; | |
| texture_vtx_builder.AddVertices({ | |
| {{100, 100, 0.0}, {0.0, 0.0}}, // 1 | |
| {{800, 100, 0.0}, {1.0, 0.0}}, // 2 | |
| {{800, 800, 0.0}, {1.0, 1.0}}, // 3 | |
| {{100, 100, 0.0}, {0.0, 0.0}}, // 1 | |
| {{800, 800, 0.0}, {1.0, 1.0}}, // 3 | |
| {{100, 800, 0.0}, {0.0, 1.0}}, // 4 | |
| }); | |
| VertexBufferBuilder<SepiaVS::PerVertexData> sepia_vtx_builder; | |
| sepia_vtx_builder.AddVertices({ | |
| {{100, 100, 0.0}}, // 1 | |
| {{800, 100, 0.0}}, // 2 | |
| {{800, 800, 0.0}}, // 3 | |
| {{100, 100, 0.0}}, // 1 | |
| {{800, 800, 0.0}}, // 3 | |
| {{100, 800, 0.0}}, // 4 | |
| }); | |
| auto boston = CreateTextureForFixture("boston.jpg"); | |
| ASSERT_TRUE(boston); | |
| const auto& sampler = context->GetSamplerLibrary()->GetSampler({}); | |
| ASSERT_TRUE(sampler); | |
| SinglePassCallback callback = [&](RenderPass& pass) { | |
| auto buffer = HostBuffer::Create(context->GetResourceAllocator()); | |
| // Draw the texture. | |
| { | |
| pass.SetPipeline(texture_pipeline); | |
| pass.SetVertexBuffer(texture_vtx_builder.CreateVertexBuffer( | |
| *context->GetResourceAllocator())); | |
| TextureVS::UniformBuffer uniforms; | |
| uniforms.mvp = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) * | |
| Matrix::MakeScale(GetContentScale()); | |
| TextureVS::BindUniformBuffer(pass, buffer->EmplaceUniform(uniforms)); | |
| TextureFS::BindTextureContents(pass, boston, sampler); | |
| if (!pass.Draw().ok()) { | |
| return false; | |
| } | |
| } | |
| // Draw the sepia toner. | |
| { | |
| pass.SetPipeline(sepia_pipeline); | |
| pass.SetVertexBuffer(sepia_vtx_builder.CreateVertexBuffer( | |
| *context->GetResourceAllocator())); | |
| SepiaVS::UniformBuffer uniforms; | |
| uniforms.mvp = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) * | |
| Matrix::MakeScale(GetContentScale()); | |
| SepiaVS::BindUniformBuffer(pass, buffer->EmplaceUniform(uniforms)); | |
| if (!pass.Draw().ok()) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| }; | |
| OpenPlaygroundHere(callback); | |
| } | |
| TEST_P(RendererTest, CanSepiaToneThenSwizzleWithSubpasses) { | |
| // The GLES framebuffer fetch implementation currently does not support this. | |
| // TODO(chinmaygarde): revisit after the GLES framebuffer fetch capabilities | |
| // are clarified. | |
| if (GetParam() == PlaygroundBackend::kOpenGLES) { | |
| GTEST_SKIP_("Not supported on GLES."); | |
| } | |
| // Define shader types | |
| using TextureVS = TextureVertexShader; | |
| using TextureFS = TextureFragmentShader; | |
| using SwizzleVS = SepiaVertexShader; | |
| using SwizzleFS = SwizzleFragmentShader; | |
| using SepiaVS = SepiaVertexShader; | |
| using SepiaFS = SepiaFragmentShader; | |
| auto context = GetContext(); | |
| ASSERT_TRUE(context); | |
| if (!context->GetCapabilities()->SupportsFramebufferFetch()) { | |
| GTEST_SKIP_( | |
| "This test uses framebuffer fetch and the backend doesn't support it."); | |
| return; | |
| } | |
| // Create pipelines. | |
| auto texture_pipeline = CreateDefaultPipeline<TextureVS, TextureFS>(context); | |
| auto swizzle_pipeline = CreateDefaultPipeline<SwizzleVS, SwizzleFS>(context); | |
| auto sepia_pipeline = CreateDefaultPipeline<SepiaVS, SepiaFS>(context); | |
| ASSERT_TRUE(texture_pipeline); | |
| ASSERT_TRUE(swizzle_pipeline); | |
| ASSERT_TRUE(sepia_pipeline); | |
| // Vertex buffer builders. | |
| VertexBufferBuilder<TextureVS::PerVertexData> texture_vtx_builder; | |
| texture_vtx_builder.AddVertices({ | |
| {{100, 100, 0.0}, {0.0, 0.0}}, // 1 | |
| {{800, 100, 0.0}, {1.0, 0.0}}, // 2 | |
| {{800, 800, 0.0}, {1.0, 1.0}}, // 3 | |
| {{100, 100, 0.0}, {0.0, 0.0}}, // 1 | |
| {{800, 800, 0.0}, {1.0, 1.0}}, // 3 | |
| {{100, 800, 0.0}, {0.0, 1.0}}, // 4 | |
| }); | |
| VertexBufferBuilder<SepiaVS::PerVertexData> sepia_vtx_builder; | |
| sepia_vtx_builder.AddVertices({ | |
| {{100, 100, 0.0}}, // 1 | |
| {{800, 100, 0.0}}, // 2 | |
| {{800, 800, 0.0}}, // 3 | |
| {{100, 100, 0.0}}, // 1 | |
| {{800, 800, 0.0}}, // 3 | |
| {{100, 800, 0.0}}, // 4 | |
| }); | |
| auto boston = CreateTextureForFixture("boston.jpg"); | |
| ASSERT_TRUE(boston); | |
| const auto& sampler = context->GetSamplerLibrary()->GetSampler({}); | |
| ASSERT_TRUE(sampler); | |
| SinglePassCallback callback = [&](RenderPass& pass) { | |
| auto buffer = HostBuffer::Create(context->GetResourceAllocator()); | |
| // Draw the texture. | |
| { | |
| pass.SetPipeline(texture_pipeline); | |
| pass.SetVertexBuffer(texture_vtx_builder.CreateVertexBuffer( | |
| *context->GetResourceAllocator())); | |
| TextureVS::UniformBuffer uniforms; | |
| uniforms.mvp = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) * | |
| Matrix::MakeScale(GetContentScale()); | |
| TextureVS::BindUniformBuffer(pass, buffer->EmplaceUniform(uniforms)); | |
| TextureFS::BindTextureContents(pass, boston, sampler); | |
| if (!pass.Draw().ok()) { | |
| return false; | |
| } | |
| } | |
| // Draw the sepia toner. | |
| { | |
| pass.SetPipeline(sepia_pipeline); | |
| pass.SetVertexBuffer(sepia_vtx_builder.CreateVertexBuffer( | |
| *context->GetResourceAllocator())); | |
| SepiaVS::UniformBuffer uniforms; | |
| uniforms.mvp = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) * | |
| Matrix::MakeScale(GetContentScale()); | |
| SepiaVS::BindUniformBuffer(pass, buffer->EmplaceUniform(uniforms)); | |
| if (!pass.Draw().ok()) { | |
| return false; | |
| } | |
| } | |
| // Draw the swizzle. | |
| { | |
| pass.SetPipeline(swizzle_pipeline); | |
| pass.SetVertexBuffer(sepia_vtx_builder.CreateVertexBuffer( | |
| *context->GetResourceAllocator())); | |
| SwizzleVS::UniformBuffer uniforms; | |
| uniforms.mvp = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) * | |
| Matrix::MakeScale(GetContentScale()); | |
| SwizzleVS::BindUniformBuffer(pass, buffer->EmplaceUniform(uniforms)); | |
| if (!pass.Draw().ok()) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| }; | |
| OpenPlaygroundHere(callback); | |
| } | |
| } // namespace testing | |
| } // namespace impeller | |
| // NOLINTEND(bugprone-unchecked-optional-access) |