| // 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 "impeller/renderer/render_target.h" | |
| #include <sstream> | |
| #include "impeller/base/strings.h" | |
| #include "impeller/base/validation.h" | |
| #include "impeller/core/allocator.h" | |
| #include "impeller/core/formats.h" | |
| #include "impeller/core/texture.h" | |
| #include "impeller/core/texture_descriptor.h" | |
| #include "impeller/renderer/context.h" | |
| namespace impeller { | |
| 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<ISize> 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<TextureType> texture_type; | |
| std::optional<SampleCount> 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; | |
| VALIDATION_LOG << "Render target has incompatible texture types: " | |
| << TextureTypeToString(texture_type.value()) << " != " | |
| << TextureTypeToString( | |
| attachment.texture->GetTextureDescriptor().type) | |
| << " on target " << ToString(); | |
| return false; | |
| } | |
| if (sample_count != | |
| attachment.texture->GetTextureDescriptor().sample_count) { | |
| passes_type_validation = false; | |
| VALIDATION_LOG << "Render target (" << ToString() | |
| << ") has incompatible sample counts."; | |
| return false; | |
| } | |
| return true; | |
| }; | |
| IterateAllAttachments(iterator); | |
| if (!passes_type_validation) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| void RenderTarget::IterateAllAttachments( | |
| const std::function<bool(const Attachment& attachment)>& 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; | |
| } | |
| } | |
| } | |
| SampleCount RenderTarget::GetSampleCount() const { | |
| if (auto found = colors_.find(0u); found != colors_.end()) { | |
| return found->second.texture->GetTextureDescriptor().sample_count; | |
| } | |
| return SampleCount::kCount1; | |
| } | |
| bool RenderTarget::HasColorAttachment(size_t index) const { | |
| if (auto found = colors_.find(index); found != colors_.end()) { | |
| return true; | |
| } | |
| return false; | |
| } | |
| std::optional<ISize> RenderTarget::GetColorAttachmentSize(size_t index) const { | |
| auto found = colors_.find(index); | |
| if (found == colors_.end()) { | |
| return std::nullopt; | |
| } | |
| return found->second.texture->GetSize(); | |
| } | |
| ISize RenderTarget::GetRenderTargetSize() const { | |
| auto size = GetColorAttachmentSize(0u); | |
| return size.has_value() ? size.value() : ISize{}; | |
| } | |
| std::shared_ptr<Texture> RenderTarget::GetRenderTargetTexture() const { | |
| auto found = colors_.find(0u); | |
| if (found == colors_.end()) { | |
| return nullptr; | |
| } | |
| return found->second.resolve_texture ? found->second.resolve_texture | |
| : found->second.texture; | |
| } | |
| PixelFormat RenderTarget::GetRenderTargetPixelFormat() const { | |
| if (auto texture = GetRenderTargetTexture(); texture != nullptr) { | |
| return texture->GetTextureDescriptor().format; | |
| } | |
| return PixelFormat::kUnknown; | |
| } | |
| size_t RenderTarget::GetMaxColorAttacmentBindIndex() const { | |
| size_t max = 0; | |
| for (const auto& color : colors_) { | |
| max = std::max(color.first, max); | |
| } | |
| return max; | |
| } | |
| RenderTarget& RenderTarget::SetColorAttachment( | |
| const ColorAttachment& attachment, | |
| size_t index) { | |
| if (attachment.IsValid()) { | |
| colors_[index] = attachment; | |
| } | |
| return *this; | |
| } | |
| RenderTarget& RenderTarget::SetDepthAttachment( | |
| std::optional<DepthAttachment> attachment) { | |
| if (!attachment.has_value()) { | |
| depth_ = std::nullopt; | |
| } else if (attachment->IsValid()) { | |
| depth_ = std::move(attachment); | |
| } | |
| return *this; | |
| } | |
| RenderTarget& RenderTarget::SetStencilAttachment( | |
| std::optional<StencilAttachment> attachment) { | |
| if (!attachment.has_value()) { | |
| stencil_ = std::nullopt; | |
| } else if (attachment->IsValid()) { | |
| stencil_ = std::move(attachment); | |
| } | |
| return *this; | |
| } | |
| const std::map<size_t, ColorAttachment>& RenderTarget::GetColorAttachments() | |
| const { | |
| return colors_; | |
| } | |
| const std::optional<DepthAttachment>& RenderTarget::GetDepthAttachment() const { | |
| return depth_; | |
| } | |
| const std::optional<StencilAttachment>& RenderTarget::GetStencilAttachment() | |
| const { | |
| return stencil_; | |
| } | |
| size_t RenderTarget::GetTotalAttachmentCount() const { | |
| size_t count = 0u; | |
| for (const auto& [_, color] : colors_) { | |
| if (color.texture) { | |
| count++; | |
| } | |
| if (color.resolve_texture) { | |
| count++; | |
| } | |
| } | |
| if (depth_.has_value()) { | |
| count++; | |
| } | |
| if (stencil_.has_value()) { | |
| count++; | |
| } | |
| return count; | |
| } | |
| std::string RenderTarget::ToString() const { | |
| std::stringstream stream; | |
| for (const auto& [index, color] : colors_) { | |
| stream << SPrintF("Color[%zu]=(%s)", index, | |
| ColorAttachmentToString(color).c_str()); | |
| } | |
| if (depth_) { | |
| stream << ","; | |
| stream << SPrintF("Depth=(%s)", | |
| DepthAttachmentToString(depth_.value()).c_str()); | |
| } | |
| if (stencil_) { | |
| stream << ","; | |
| stream << SPrintF("Stencil=(%s)", | |
| StencilAttachmentToString(stencil_.value()).c_str()); | |
| } | |
| return stream.str(); | |
| } | |
| RenderTargetAllocator::RenderTargetAllocator( | |
| std::shared_ptr<Allocator> allocator) | |
| : allocator_(std::move(allocator)) {} | |
| void RenderTargetAllocator::Start() {} | |
| void RenderTargetAllocator::End() {} | |
| RenderTarget RenderTargetAllocator::CreateOffscreen( | |
| const Context& context, | |
| ISize size, | |
| int mip_count, | |
| const std::string& label, | |
| RenderTarget::AttachmentConfig color_attachment_config, | |
| std::optional<RenderTarget::AttachmentConfig> stencil_attachment_config, | |
| const std::shared_ptr<Texture>& existing_color_texture, | |
| const std::shared_ptr<Texture>& existing_depth_stencil_texture) { | |
| if (size.IsEmpty()) { | |
| return {}; | |
| } | |
| RenderTarget target; | |
| std::shared_ptr<Texture> color0_tex; | |
| if (existing_color_texture) { | |
| color0_tex = existing_color_texture; | |
| } else { | |
| PixelFormat pixel_format = | |
| context.GetCapabilities()->GetDefaultColorFormat(); | |
| TextureDescriptor color0_tex_desc; | |
| color0_tex_desc.storage_mode = color_attachment_config.storage_mode; | |
| color0_tex_desc.format = pixel_format; | |
| color0_tex_desc.size = size; | |
| color0_tex_desc.mip_count = mip_count; | |
| color0_tex_desc.usage = | |
| TextureUsage::kRenderTarget | TextureUsage::kShaderRead; | |
| color0_tex = allocator_->CreateTexture(color0_tex_desc); | |
| if (!color0_tex) { | |
| return {}; | |
| } | |
| } | |
| color0_tex->SetLabel(SPrintF("%s Color Texture", label.c_str())); | |
| ColorAttachment color0; | |
| color0.clear_color = color_attachment_config.clear_color; | |
| color0.load_action = color_attachment_config.load_action; | |
| color0.store_action = color_attachment_config.store_action; | |
| color0.texture = color0_tex; | |
| target.SetColorAttachment(color0, 0u); | |
| if (stencil_attachment_config.has_value()) { | |
| target.SetupDepthStencilAttachments( | |
| context, *allocator_, size, false, label, | |
| stencil_attachment_config.value(), existing_depth_stencil_texture); | |
| } else { | |
| target.SetStencilAttachment(std::nullopt); | |
| target.SetDepthAttachment(std::nullopt); | |
| } | |
| return target; | |
| } | |
| RenderTarget RenderTargetAllocator::CreateOffscreenMSAA( | |
| const Context& context, | |
| ISize size, | |
| int mip_count, | |
| const std::string& label, | |
| RenderTarget::AttachmentConfigMSAA color_attachment_config, | |
| std::optional<RenderTarget::AttachmentConfig> stencil_attachment_config, | |
| const std::shared_ptr<Texture>& existing_color_msaa_texture, | |
| const std::shared_ptr<Texture>& existing_color_resolve_texture, | |
| const std::shared_ptr<Texture>& existing_depth_stencil_texture) { | |
| if (size.IsEmpty()) { | |
| return {}; | |
| } | |
| RenderTarget target; | |
| PixelFormat pixel_format = context.GetCapabilities()->GetDefaultColorFormat(); | |
| // Create MSAA color texture. | |
| std::shared_ptr<Texture> color0_msaa_tex; | |
| if (existing_color_msaa_texture) { | |
| color0_msaa_tex = existing_color_msaa_texture; | |
| } else { | |
| TextureDescriptor color0_tex_desc; | |
| color0_tex_desc.storage_mode = color_attachment_config.storage_mode; | |
| color0_tex_desc.type = TextureType::kTexture2DMultisample; | |
| color0_tex_desc.sample_count = SampleCount::kCount4; | |
| color0_tex_desc.format = pixel_format; | |
| color0_tex_desc.size = size; | |
| color0_tex_desc.usage = TextureUsage::kRenderTarget; | |
| if (context.GetCapabilities()->SupportsImplicitResolvingMSAA()) { | |
| // See below ("SupportsImplicitResolvingMSAA") for more details. | |
| color0_tex_desc.storage_mode = StorageMode::kDevicePrivate; | |
| } | |
| color0_msaa_tex = allocator_->CreateTexture(color0_tex_desc); | |
| if (!color0_msaa_tex) { | |
| VALIDATION_LOG << "Could not create multisample color texture."; | |
| return {}; | |
| } | |
| } | |
| color0_msaa_tex->SetLabel( | |
| SPrintF("%s Color Texture (Multisample)", label.c_str())); | |
| // Create color resolve texture. | |
| std::shared_ptr<Texture> color0_resolve_tex; | |
| if (existing_color_resolve_texture) { | |
| color0_resolve_tex = existing_color_resolve_texture; | |
| } else { | |
| TextureDescriptor color0_resolve_tex_desc; | |
| color0_resolve_tex_desc.storage_mode = | |
| color_attachment_config.resolve_storage_mode; | |
| color0_resolve_tex_desc.format = pixel_format; | |
| color0_resolve_tex_desc.size = size; | |
| color0_resolve_tex_desc.compression_type = CompressionType::kLossy; | |
| color0_resolve_tex_desc.usage = | |
| TextureUsage::kRenderTarget | TextureUsage::kShaderRead; | |
| color0_resolve_tex_desc.mip_count = mip_count; | |
| color0_resolve_tex = allocator_->CreateTexture(color0_resolve_tex_desc); | |
| if (!color0_resolve_tex) { | |
| VALIDATION_LOG << "Could not create color texture."; | |
| return {}; | |
| } | |
| } | |
| color0_resolve_tex->SetLabel(SPrintF("%s Color Texture", label.c_str())); | |
| // Color attachment. | |
| ColorAttachment color0; | |
| color0.clear_color = color_attachment_config.clear_color; | |
| color0.load_action = color_attachment_config.load_action; | |
| color0.store_action = color_attachment_config.store_action; | |
| color0.texture = color0_msaa_tex; | |
| color0.resolve_texture = color0_resolve_tex; | |
| if (context.GetCapabilities()->SupportsImplicitResolvingMSAA()) { | |
| // If implicit MSAA is supported, then the resolve texture is not needed | |
| // because the multisample texture is automatically resolved. We instead | |
| // provide a view of the multisample texture as the resolve texture (because | |
| // the HAL does expect a resolve texture). | |
| // | |
| // In practice, this is used for GLES 2.0 EXT_multisampled_render_to_texture | |
| // https://registry.khronos.org/OpenGL/extensions/EXT/EXT_multisampled_render_to_texture.txt | |
| color0.resolve_texture = color0_msaa_tex; | |
| } | |
| target.SetColorAttachment(color0, 0u); | |
| // Create MSAA stencil texture. | |
| if (stencil_attachment_config.has_value()) { | |
| target.SetupDepthStencilAttachments(context, *allocator_, size, true, label, | |
| stencil_attachment_config.value(), | |
| existing_depth_stencil_texture); | |
| } else { | |
| target.SetDepthAttachment(std::nullopt); | |
| target.SetStencilAttachment(std::nullopt); | |
| } | |
| return target; | |
| } | |
| void RenderTarget::SetupDepthStencilAttachments( | |
| const Context& context, | |
| Allocator& allocator, | |
| ISize size, | |
| bool msaa, | |
| const std::string& label, | |
| RenderTarget::AttachmentConfig stencil_attachment_config, | |
| const std::shared_ptr<Texture>& existing_depth_stencil_texture) { | |
| std::shared_ptr<Texture> depth_stencil_texture; | |
| if (existing_depth_stencil_texture) { | |
| depth_stencil_texture = existing_depth_stencil_texture; | |
| } else { | |
| TextureDescriptor depth_stencil_texture_desc; | |
| depth_stencil_texture_desc.storage_mode = | |
| stencil_attachment_config.storage_mode; | |
| if (msaa) { | |
| depth_stencil_texture_desc.type = TextureType::kTexture2DMultisample; | |
| depth_stencil_texture_desc.sample_count = SampleCount::kCount4; | |
| } | |
| depth_stencil_texture_desc.format = | |
| context.GetCapabilities()->GetDefaultDepthStencilFormat(); | |
| depth_stencil_texture_desc.size = size; | |
| depth_stencil_texture_desc.usage = TextureUsage::kRenderTarget; | |
| depth_stencil_texture = allocator.CreateTexture(depth_stencil_texture_desc); | |
| if (!depth_stencil_texture) { | |
| return; // Error messages are handled by `Allocator::CreateTexture`. | |
| } | |
| } | |
| DepthAttachment depth0; | |
| depth0.load_action = stencil_attachment_config.load_action; | |
| depth0.store_action = stencil_attachment_config.store_action; | |
| depth0.clear_depth = 0u; | |
| depth0.texture = depth_stencil_texture; | |
| StencilAttachment stencil0; | |
| stencil0.load_action = stencil_attachment_config.load_action; | |
| stencil0.store_action = stencil_attachment_config.store_action; | |
| stencil0.clear_stencil = 0u; | |
| stencil0.texture = std::move(depth_stencil_texture); | |
| stencil0.texture->SetLabel( | |
| SPrintF("%s Depth+Stencil Texture", label.c_str())); | |
| SetDepthAttachment(std::move(depth0)); | |
| SetStencilAttachment(std::move(stencil0)); | |
| } | |
| } // namespace impeller |