diff --git a/engine/src/flutter/impeller/aiks/aiks_unittests.cc b/engine/src/flutter/impeller/aiks/aiks_unittests.cc index ecc3b4fa0ec..14d9b8d5a7f 100644 --- a/engine/src/flutter/impeller/aiks/aiks_unittests.cc +++ b/engine/src/flutter/impeller/aiks/aiks_unittests.cc @@ -127,6 +127,17 @@ TEST_F(AiksTest, CanRenderGroupOpacity) { canvas.Restore(); + // ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); +} + +TEST_F(AiksTest, CanPerformFullScreenMSAA) { + Canvas canvas; + + Paint red; + red.color = Color::Red(); + + canvas.DrawCircle({250, 250}, 125, red); + ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); } diff --git a/engine/src/flutter/impeller/aiks/canvas.cc b/engine/src/flutter/impeller/aiks/canvas.cc index 1c11d886ba1..c4fab50f5f9 100644 --- a/engine/src/flutter/impeller/aiks/canvas.cc +++ b/engine/src/flutter/impeller/aiks/canvas.cc @@ -91,6 +91,15 @@ void Canvas::DrawPath(Path path, Paint paint) { GetCurrentPass().AddEntity(std::move(entity)); } +void Canvas::DrawRect(Rect rect, Paint paint) { + DrawPath(PathBuilder{}.AddRect(rect).CreatePath(), std::move(paint)); +} + +void Canvas::DrawCircle(Point center, Scalar radius, Paint paint) { + DrawPath(PathBuilder{}.AddCircle(center, radius).CreatePath(), + std::move(paint)); +} + void Canvas::SaveLayer(Paint paint, std::optional bounds) { GetCurrentPass().SetDelegate( std::make_unique(std::move(paint))); @@ -189,10 +198,6 @@ size_t Canvas::GetStencilDepth() const { return xformation_stack_.back().stencil_depth; } -void Canvas::DrawRect(Rect rect, Paint paint) { - DrawPath(PathBuilder{}.AddRect(rect).CreatePath(), std::move(paint)); -} - void Canvas::Save(bool create_subpass) { auto entry = CanvasStackEntry{}; if (create_subpass) { diff --git a/engine/src/flutter/impeller/aiks/canvas.h b/engine/src/flutter/impeller/aiks/canvas.h index c7b16fcdaf6..6fa9de69b10 100644 --- a/engine/src/flutter/impeller/aiks/canvas.h +++ b/engine/src/flutter/impeller/aiks/canvas.h @@ -53,6 +53,8 @@ class Canvas { void DrawRect(Rect rect, Paint paint); + void DrawCircle(Point center, Scalar radius, Paint paint); + void DrawImage(std::shared_ptr image, Point offset, Paint paint); void DrawImageRect(std::shared_ptr image, diff --git a/engine/src/flutter/impeller/base/BUILD.gn b/engine/src/flutter/impeller/base/BUILD.gn index 3a510cac0d8..498ce4bef1a 100644 --- a/engine/src/flutter/impeller/base/BUILD.gn +++ b/engine/src/flutter/impeller/base/BUILD.gn @@ -12,6 +12,8 @@ impeller_component("base") { "config.h", "strings.cc", "strings.h", + "validation.cc", + "validation.h", ] } diff --git a/engine/src/flutter/impeller/base/base.h b/engine/src/flutter/impeller/base/base.h index dc6e687dcfb..c9a222db801 100644 --- a/engine/src/flutter/impeller/base/base.h +++ b/engine/src/flutter/impeller/base/base.h @@ -5,3 +5,4 @@ #pragma once #include "impeller/base/strings.h" +#include "impeller/base/validation.h" diff --git a/engine/src/flutter/impeller/base/validation.cc b/engine/src/flutter/impeller/base/validation.cc new file mode 100644 index 00000000000..d0b113aee12 --- /dev/null +++ b/engine/src/flutter/impeller/base/validation.cc @@ -0,0 +1,28 @@ +// 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/impeller/base/validation.h" + +#include "flutter/fml/logging.h" + +namespace impeller { + +ValidationLog::ValidationLog() = default; + +ValidationLog::~ValidationLog() { + FML_LOG(ERROR) << stream_.str(); + ImpellerValidationBreak(); +} + +std::ostream& ValidationLog::GetStream() { + return stream_; +} + +void ImpellerValidationBreak() { + // Nothing to do. Exists for the debugger. + FML_LOG(ERROR) << "Break on " << __FUNCTION__ + << " to inspect point of failure."; +} + +} // namespace impeller diff --git a/engine/src/flutter/impeller/base/validation.h b/engine/src/flutter/impeller/base/validation.h new file mode 100644 index 00000000000..f19471e1eea --- /dev/null +++ b/engine/src/flutter/impeller/base/validation.h @@ -0,0 +1,31 @@ +// 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. + +#pragma once + +#include + +#include "flutter/fml/macros.h" + +namespace impeller { + +class ValidationLog { + public: + ValidationLog(); + + ~ValidationLog(); + + std::ostream& GetStream(); + + private: + std::ostringstream stream_; + + FML_DISALLOW_COPY_ASSIGN_AND_MOVE(ValidationLog); +}; + +void ImpellerValidationBreak(); + +} // namespace impeller + +#define VALIDATION_LOG ::impeller::ValidationLog{}.GetStream() diff --git a/engine/src/flutter/impeller/playground/playground.mm b/engine/src/flutter/impeller/playground/playground.mm index 848d28fb47b..afff6bbdb71 100644 --- a/engine/src/flutter/impeller/playground/playground.mm +++ b/engine/src/flutter/impeller/playground/playground.mm @@ -144,23 +144,44 @@ bool Playground::OpenPlaygroundHere(Renderer::RenderCallback render_callback) { return false; } - TextureDescriptor color0_tex; - color0_tex.format = PixelFormat::kB8G8R8A8UNormInt; - color0_tex.size = { + TextureDescriptor msaa_tex_desc; + msaa_tex_desc.type = TextureType::k2DMultisample; + msaa_tex_desc.sample_count = SampleCount::kCount4; + msaa_tex_desc.format = PixelFormat::kB8G8R8A8UNormInt; + msaa_tex_desc.size = { static_cast(current_drawable.texture.width), static_cast(current_drawable.texture.height)}; - color0_tex.usage = static_cast(TextureUsage::kRenderTarget); + msaa_tex_desc.usage = static_cast(TextureUsage::kRenderTarget); + + auto msaa_tex = + renderer_.GetContext()->GetPermanentsAllocator()->CreateTexture( + StorageMode::kDeviceTransient, msaa_tex_desc); + if (!msaa_tex) { + FML_LOG(ERROR) << "Could not allocate MSAA resolve texture."; + return false; + } + + msaa_tex->SetLabel("PlaygroundMainColor4xMSAA"); + + TextureDescriptor onscreen_tex_desc; + onscreen_tex_desc.format = PixelFormat::kB8G8R8A8UNormInt; + onscreen_tex_desc.size = msaa_tex_desc.size; + onscreen_tex_desc.usage = + static_cast(TextureUsage::kRenderTarget); ColorAttachment color0; - color0.texture = - std::make_shared(color0_tex, current_drawable.texture); + color0.texture = msaa_tex; color0.clear_color = Color::DarkSlateGray(); color0.load_action = LoadAction::kClear; - color0.store_action = StoreAction::kStore; + color0.store_action = StoreAction::kMultisampleResolve; + color0.resolve_texture = std::make_shared( + onscreen_tex_desc, current_drawable.texture); TextureDescriptor stencil0_tex; + stencil0_tex.type = TextureType::k2DMultisample; + stencil0_tex.sample_count = SampleCount::kCount4; stencil0_tex.format = PixelFormat::kD32FloatS8UNormInt; - stencil0_tex.size = color0_tex.size; + stencil0_tex.size = msaa_tex_desc.size; stencil0_tex.usage = static_cast(TextureUsage::kRenderTarget); auto stencil_texture = diff --git a/engine/src/flutter/impeller/renderer/backend/metal/allocator_mtl.mm b/engine/src/flutter/impeller/renderer/backend/metal/allocator_mtl.mm index b331fdcfe31..a9b1c6acebc 100644 --- a/engine/src/flutter/impeller/renderer/backend/metal/allocator_mtl.mm +++ b/engine/src/flutter/impeller/renderer/backend/metal/allocator_mtl.mm @@ -111,6 +111,12 @@ std::shared_ptr AllocatorMTL::CreateTexture( } auto mtl_texture_desc = ToMTLTextureDescriptor(desc); + + if (!mtl_texture_desc) { + FML_DLOG(ERROR) << "Texture descriptor was invalid."; + return nullptr; + } + mtl_texture_desc.storageMode = ToMTLStorageMode(mode); auto texture = [device_ newTextureWithDescriptor:mtl_texture_desc]; if (!texture) { diff --git a/engine/src/flutter/impeller/renderer/backend/metal/formats_mtl.h b/engine/src/flutter/impeller/renderer/backend/metal/formats_mtl.h index 5788312eb57..9ebf1904775 100644 --- a/engine/src/flutter/impeller/renderer/backend/metal/formats_mtl.h +++ b/engine/src/flutter/impeller/renderer/backend/metal/formats_mtl.h @@ -206,6 +206,8 @@ constexpr MTLStoreAction ToMTLStoreAction(StoreAction action) { return MTLStoreActionDontCare; case StoreAction::kStore: return MTLStoreActionStore; + case StoreAction::kMultisampleResolve: + return MTLStoreActionMultisampleResolve; } return MTLStoreActionDontCare; } @@ -216,6 +218,8 @@ constexpr StoreAction FromMTLStoreAction(MTLStoreAction action) { return StoreAction::kDontCare; case MTLStoreActionStore: return StoreAction::kStore; + case MTLStoreActionMultisampleResolve: + return StoreAction::kMultisampleResolve; default: break; } @@ -249,6 +253,16 @@ constexpr MTLClearColor ToMTLClearColor(const Color& color) { return MTLClearColorMake(color.red, color.green, color.blue, color.alpha); } +constexpr MTLTextureType ToMTLTextureType(TextureType type) { + switch (type) { + case TextureType::k2D: + return MTLTextureType2D; + case TextureType::k2DMultisample: + return MTLTextureType2DMultisample; + } + return MTLTextureType2D; +} + MTLRenderPipelineColorAttachmentDescriptor* ToMTLRenderPipelineColorAttachmentDescriptor( ColorAttachmentDescriptor descriptor); diff --git a/engine/src/flutter/impeller/renderer/backend/metal/formats_mtl.mm b/engine/src/flutter/impeller/renderer/backend/metal/formats_mtl.mm index e32121b2c66..3d88d59c525 100644 --- a/engine/src/flutter/impeller/renderer/backend/metal/formats_mtl.mm +++ b/engine/src/flutter/impeller/renderer/backend/metal/formats_mtl.mm @@ -78,8 +78,13 @@ MTLDepthStencilDescriptor* ToMTLDepthStencilDescriptor( } MTLTextureDescriptor* ToMTLTextureDescriptor(const TextureDescriptor& desc) { + if (!desc.IsValid()) { + return nil; + } auto mtl_desc = [[MTLTextureDescriptor alloc] init]; + mtl_desc.textureType = ToMTLTextureType(desc.type); mtl_desc.pixelFormat = ToMTLPixelFormat(desc.format); + mtl_desc.sampleCount = static_cast(desc.sample_count); mtl_desc.width = desc.size.width; mtl_desc.height = desc.size.height; mtl_desc.mipmapLevelCount = desc.mip_count; diff --git a/engine/src/flutter/impeller/renderer/backend/metal/pipeline_library_mtl.mm b/engine/src/flutter/impeller/renderer/backend/metal/pipeline_library_mtl.mm index 05437b8eb8a..9dcd5c53494 100644 --- a/engine/src/flutter/impeller/renderer/backend/metal/pipeline_library_mtl.mm +++ b/engine/src/flutter/impeller/renderer/backend/metal/pipeline_library_mtl.mm @@ -53,6 +53,8 @@ static MTLRenderPipelineDescriptor* GetMTLRenderPipelineDescriptor( descriptor.stencilAttachmentPixelFormat = ToMTLPixelFormat(desc.GetStencilPixelFormat()); + descriptor.sampleCount = 4u; + return descriptor; } diff --git a/engine/src/flutter/impeller/renderer/backend/metal/render_pass_mtl.mm b/engine/src/flutter/impeller/renderer/backend/metal/render_pass_mtl.mm index 12499878396..972820c2eb2 100644 --- a/engine/src/flutter/impeller/renderer/backend/metal/render_pass_mtl.mm +++ b/engine/src/flutter/impeller/renderer/backend/metal/render_pass_mtl.mm @@ -17,6 +17,33 @@ namespace impeller { +static bool ConfigureResolveTextureAttachment( + const Attachment& desc, + MTLRenderPassAttachmentDescriptor* attachment) { + if (desc.store_action == StoreAction::kMultisampleResolve && + !desc.resolve_texture) { + VALIDATION_LOG << "Resolve store action specified on attachment but no " + "resolve texture was specified."; + return false; + } + + if (desc.resolve_texture && + desc.store_action != StoreAction::kMultisampleResolve) { + VALIDATION_LOG << "Resolve store action specified but there was no " + "resolve attachment."; + return false; + } + + if (!desc.resolve_texture) { + return true; + } + + attachment.resolveTexture = + TextureMTL::Cast(*desc.resolve_texture).GetMTLTexture(); + + return true; +} + static bool ConfigureAttachment(const Attachment& desc, MTLRenderPassAttachmentDescriptor* attachment) { if (!desc.texture) { @@ -26,6 +53,11 @@ static bool ConfigureAttachment(const Attachment& desc, attachment.texture = TextureMTL::Cast(*desc.texture).GetMTLTexture(); attachment.loadAction = ToMTLLoadAction(desc.load_action); attachment.storeAction = ToMTLStoreAction(desc.store_action); + + if (!ConfigureResolveTextureAttachment(desc, attachment)) { + return false; + } + return true; } @@ -69,8 +101,8 @@ static MTLRenderPassDescriptor* ToMTLRenderPassDescriptor( for (const auto& color : colors) { if (!ConfigureColorAttachment(color.second, result.colorAttachments[color.first])) { - FML_DLOG(ERROR) << "Could not configure color attachment at index " - << color.first; + VALIDATION_LOG << "Could not configure color attachment at index " + << color.first; return nil; } } @@ -79,7 +111,7 @@ static MTLRenderPassDescriptor* ToMTLRenderPassDescriptor( if (depth.has_value() && !ConfigureDepthAttachment(depth.value(), result.depthAttachment)) { - FML_DLOG(ERROR) << "Could not configure depth attachment."; + VALIDATION_LOG << "Could not configure depth attachment."; return nil; } @@ -87,7 +119,7 @@ static MTLRenderPassDescriptor* ToMTLRenderPassDescriptor( if (stencil.has_value() && !ConfigureStencilAttachment(stencil.value(), result.stencilAttachment)) { - FML_DLOG(ERROR) << "Could not configure stencil attachment."; + VALIDATION_LOG << "Could not configure stencil attachment."; return nil; } @@ -99,7 +131,7 @@ RenderPassMTL::RenderPassMTL(id buffer, RenderTarget target) buffer_(buffer), desc_(ToMTLRenderPassDescriptor(GetRenderTarget())), transients_buffer_(HostBuffer::Create()) { - if (!buffer_ || !desc_) { + if (!buffer_ || !desc_ || !render_target_.IsValid()) { return; } is_valid_ = true; @@ -202,8 +234,7 @@ struct PassBindingsCache { [encoder_ setFragmentBufferOffset:offset atIndex:index]; return true; default: - FML_DCHECK(false) - << "Cannot update buffer offset of an unknown stage."; + VALIDATION_LOG << "Cannot update buffer offset of an unknown stage."; return false; } return true; @@ -217,7 +248,7 @@ struct PassBindingsCache { [encoder_ setFragmentBuffer:buffer offset:offset atIndex:index]; return true; default: - FML_DCHECK(false) << "Cannot bind buffer to unknown shader stage."; + VALIDATION_LOG << "Cannot bind buffer to unknown shader stage."; return false; } return false; @@ -239,7 +270,7 @@ struct PassBindingsCache { [encoder_ setFragmentTexture:texture atIndex:index]; return true; default: - FML_DCHECK(false) << "Cannot bind buffer to unknown shader stage."; + VALIDATION_LOG << "Cannot bind buffer to unknown shader stage."; return false; } return false; @@ -263,7 +294,7 @@ struct PassBindingsCache { [encoder_ setFragmentSamplerState:sampler atIndex:index]; return true; default: - FML_DCHECK(false) << "Cannot bind buffer to unknown shader stage."; + VALIDATION_LOG << "Cannot bind buffer to unknown shader stage."; return false; } return false; @@ -360,7 +391,7 @@ bool RenderPassMTL::EncodeCommands(Allocator& allocator, fml::closure pop_debug_marker = [encoder]() { [encoder popDebugGroup]; }; for (const auto& command : commands_) { if (command.index_count == 0u) { - FML_DLOG(ERROR) << "Zero index count in render pass command."; + VALIDATION_LOG << "Zero index count in render pass command."; continue; } diff --git a/engine/src/flutter/impeller/renderer/formats.h b/engine/src/flutter/impeller/renderer/formats.h index 10608fa8bea..72c49a1a71b 100644 --- a/engine/src/flutter/impeller/renderer/formats.h +++ b/engine/src/flutter/impeller/renderer/formats.h @@ -89,6 +89,27 @@ enum class LoadAction { enum class StoreAction { kDontCare, kStore, + kMultisampleResolve, +}; + +enum class TextureType { + k2D, + k2DMultisample, +}; + +constexpr bool IsMultisampleCapable(TextureType type) { + switch (type) { + case TextureType::k2D: + return false; + case TextureType::k2DMultisample: + return true; + } + return false; +} + +enum class SampleCount { + kCount1 = 1, + kCount4 = 4, }; using TextureUsageMask = uint64_t; @@ -320,7 +341,8 @@ struct StencilAttachmentDescriptor { }; struct Attachment { - std::shared_ptr texture = nullptr; + std::shared_ptr texture; + std::shared_ptr resolve_texture; LoadAction load_action = LoadAction::kDontCare; StoreAction store_action = StoreAction::kStore; diff --git a/engine/src/flutter/impeller/renderer/render_pass.h b/engine/src/flutter/impeller/renderer/render_pass.h index 588250a6312..05e3b62eca2 100644 --- a/engine/src/flutter/impeller/renderer/render_pass.h +++ b/engine/src/flutter/impeller/renderer/render_pass.h @@ -59,11 +59,11 @@ class RenderPass { virtual bool EncodeCommands(Allocator& transients_allocator) const = 0; protected: + const RenderTarget render_target_; + RenderPass(RenderTarget target); private: - const RenderTarget render_target_; - FML_DISALLOW_COPY_AND_ASSIGN(RenderPass); }; diff --git a/engine/src/flutter/impeller/renderer/render_target.cc b/engine/src/flutter/impeller/renderer/render_target.cc index 922f81a71ec..666ddece17c 100644 --- a/engine/src/flutter/impeller/renderer/render_target.cc +++ b/engine/src/flutter/impeller/renderer/render_target.cc @@ -4,7 +4,7 @@ #include "impeller/renderer/render_target.h" -#include "impeller/base/strings.h" +#include "impeller/base/base.h" #include "impeller/renderer/allocator.h" #include "impeller/renderer/context.h" #include "impeller/renderer/texture.h" @@ -15,6 +15,92 @@ RenderTarget::RenderTarget() = default; RenderTarget::~RenderTarget() = default; +bool RenderTarget::IsValid() const { + // Validate that there is a color attachment at zero index. + if (!HasColorAttachment(0u)) { + VALIDATION_LOG + << "Render target does not have color attachment at index 0."; + return false; + } + + // Validate that all attachments are of the same size. + { + std::optional size; + bool sizes_are_same = true; + auto iterator = [&](const Attachment& attachment) -> bool { + if (!size.has_value()) { + size = attachment.texture->GetSize(); + } + if (size != attachment.texture->GetSize()) { + sizes_are_same = false; + return false; + } + return true; + }; + IterateAllAttachments(iterator); + if (!sizes_are_same) { + VALIDATION_LOG + << "Sizes of all render target attachments are not the same."; + return false; + } + } + + // Validate that all attachments are of the same type and sample counts. + { + std::optional texture_type; + std::optional sample_count; + bool passes_type_validation = true; + auto iterator = [&](const Attachment& attachment) -> bool { + if (!texture_type.has_value() || !sample_count.has_value()) { + texture_type = attachment.texture->GetTextureDescriptor().type; + sample_count = attachment.texture->GetTextureDescriptor().sample_count; + } + + if (texture_type != attachment.texture->GetTextureDescriptor().type) { + passes_type_validation = false; + return false; + } + + if (sample_count != + attachment.texture->GetTextureDescriptor().sample_count) { + passes_type_validation = false; + return false; + } + + return true; + }; + IterateAllAttachments(iterator); + if (!passes_type_validation) { + VALIDATION_LOG << "Render target texture types are not of the same type " + "and sample count."; + return false; + } + } + + return true; +} + +void RenderTarget::IterateAllAttachments( + std::function iterator) const { + for (const auto& color : colors_) { + if (!iterator(color.second)) { + return; + } + } + + if (depth_.has_value()) { + if (!iterator(depth_.value())) { + return; + } + } + + if (stencil_.has_value()) { + if (!iterator(stencil_.value())) { + return; + } + } +} + bool RenderTarget::HasColorAttachment(size_t index) const { if (auto found = colors_.find(index); found != colors_.end()) { return true; diff --git a/engine/src/flutter/impeller/renderer/render_target.h b/engine/src/flutter/impeller/renderer/render_target.h index 9b00c82fbd7..3ce7a5a480e 100644 --- a/engine/src/flutter/impeller/renderer/render_target.h +++ b/engine/src/flutter/impeller/renderer/render_target.h @@ -4,6 +4,7 @@ #pragma once +#include #include #include @@ -25,6 +26,8 @@ class RenderTarget { ~RenderTarget(); + bool IsValid() const; + bool HasColorAttachment(size_t index) const; ISize GetRenderTargetSize() const; @@ -49,6 +52,9 @@ class RenderTarget { std::map colors_; std::optional depth_; std::optional stencil_; + + void IterateAllAttachments( + std::function iterator) const; }; } // namespace impeller diff --git a/engine/src/flutter/impeller/renderer/texture_descriptor.h b/engine/src/flutter/impeller/renderer/texture_descriptor.h index dfaaab36cc7..d453ae28508 100644 --- a/engine/src/flutter/impeller/renderer/texture_descriptor.h +++ b/engine/src/flutter/impeller/renderer/texture_descriptor.h @@ -13,11 +13,13 @@ namespace impeller { struct TextureDescriptor { + TextureType type = TextureType::k2D; PixelFormat format = PixelFormat::kUnknown; ISize size; size_t mip_count = 1u; // Size::MipCount is usually appropriate. TextureUsageMask usage = static_cast(TextureUsage::kShaderRead); + SampleCount sample_count = SampleCount::kCount1; constexpr size_t GetSizeOfBaseMipLevel() const { if (!IsValid()) { @@ -33,11 +35,16 @@ struct TextureDescriptor { return size.width * BytesPerPixelForPixelFormat(format); } - bool IsValid() const { + constexpr bool SamplingOptionsAreValid() const { + const auto count = static_cast(sample_count); + return IsMultisampleCapable(type) ? count > 1 : count == 1; + } + + constexpr bool IsValid() const { return format != PixelFormat::kUnknown && // size.IsPositive() && // - mip_count >= 1u // - ; + mip_count >= 1u && // + SamplingOptionsAreValid(); } };