diff --git a/impeller/renderer/backend/gles/blit_pass_gles.h b/impeller/renderer/backend/gles/blit_pass_gles.h index deaf1a41835f4..6835a9261c36b 100644 --- a/impeller/renderer/backend/gles/blit_pass_gles.h +++ b/impeller/renderer/backend/gles/blit_pass_gles.h @@ -5,6 +5,7 @@ #pragma once #include "flutter/fml/macros.h" +#include "flutter/impeller/base/config.h" #include "flutter/impeller/renderer/backend/gles/reactor_gles.h" #include "flutter/impeller/renderer/blit_pass.h" #include "impeller/renderer/backend/gles/blit_command_gles.h" @@ -50,6 +51,14 @@ class BlitPassGLES final : public BlitPass { size_t destination_offset, std::string label) override; + // |BlitPass| + bool OnCopyBufferToTextureCommand(BufferView source, + std::shared_ptr destination, + IPoint destination_origin, + std::string label) override { + IMPELLER_UNIMPLEMENTED; + return false; + } // |BlitPass| bool OnGenerateMipmapCommand(std::shared_ptr texture, std::string label) override; diff --git a/impeller/renderer/backend/metal/allocator_mtl.mm b/impeller/renderer/backend/metal/allocator_mtl.mm index 2c01f65572e10..7dc44aff11519 100644 --- a/impeller/renderer/backend/metal/allocator_mtl.mm +++ b/impeller/renderer/backend/metal/allocator_mtl.mm @@ -205,6 +205,7 @@ static MTLStorageMode ToMTLStorageMode(StorageMode mode, mtl_texture_desc.storageMode = ToMTLStorageMode( desc.storage_mode, supports_memoryless_targets_, supports_uma_); + if (@available(macOS 12.5, ios 15.0, *)) { if (desc.compression_type == CompressionType::kLossy && SupportsLossyTextureCompression(device_)) { diff --git a/impeller/renderer/backend/metal/blit_command_mtl.h b/impeller/renderer/backend/metal/blit_command_mtl.h index ffdd450f16c9b..86062e9c04cde 100644 --- a/impeller/renderer/backend/metal/blit_command_mtl.h +++ b/impeller/renderer/backend/metal/blit_command_mtl.h @@ -50,4 +50,14 @@ struct BlitGenerateMipmapCommandMTL : public BlitGenerateMipmapCommand, [[nodiscard]] bool Encode(id encoder) const override; }; +struct BlitCopyBufferToTextureCommandMTL + : public BlitCopyBufferToTextureCommand, + public BlitEncodeMTL { + ~BlitCopyBufferToTextureCommandMTL() override; + + std::string GetLabel() const override; + + [[nodiscard]] bool Encode(id encoder) const override; +}; + } // namespace impeller diff --git a/impeller/renderer/backend/metal/blit_command_mtl.mm b/impeller/renderer/backend/metal/blit_command_mtl.mm index 7f589b4f54175..90c05fd15b830 100644 --- a/impeller/renderer/backend/metal/blit_command_mtl.mm +++ b/impeller/renderer/backend/metal/blit_command_mtl.mm @@ -94,6 +94,51 @@ return true; }; +BlitCopyBufferToTextureCommandMTL::~BlitCopyBufferToTextureCommandMTL() = + default; + +std::string BlitCopyBufferToTextureCommandMTL::GetLabel() const { + return label; +} + +bool BlitCopyBufferToTextureCommandMTL::Encode( + id encoder) const { + auto source_mtl = DeviceBufferMTL::Cast(*source.buffer).GetMTLBuffer(); + if (!source_mtl) { + return false; + } + + auto destination_mtl = TextureMTL::Cast(*destination).GetMTLTexture(); + if (!destination_mtl) { + return false; + } + + auto destination_origin_mtl = + MTLOriginMake(destination_origin.x, destination_origin.y, 0); + + auto image_size = destination->GetTextureDescriptor().size; + auto source_size_mtl = MTLSizeMake(image_size.width, image_size.height, 1); + + auto destination_bytes_per_pixel = + BytesPerPixelForPixelFormat(destination->GetTextureDescriptor().format); + auto destination_bytes_per_row = + source_size_mtl.width * destination_bytes_per_pixel; + auto destination_bytes_per_image = + source_size_mtl.height * destination_bytes_per_row; + + [encoder copyFromBuffer:source_mtl + sourceOffset:source.range.offset + sourceBytesPerRow:destination_bytes_per_row + sourceBytesPerImage:destination_bytes_per_image + sourceSize:source_size_mtl + toTexture:destination_mtl + destinationSlice:0 + destinationLevel:0 + destinationOrigin:destination_origin_mtl]; + + return true; +}; + BlitGenerateMipmapCommandMTL::~BlitGenerateMipmapCommandMTL() = default; std::string BlitGenerateMipmapCommandMTL::GetLabel() const { diff --git a/impeller/renderer/backend/metal/blit_pass_mtl.h b/impeller/renderer/backend/metal/blit_pass_mtl.h index 108d320d98bf0..604c4f4ba0535 100644 --- a/impeller/renderer/backend/metal/blit_pass_mtl.h +++ b/impeller/renderer/backend/metal/blit_pass_mtl.h @@ -52,6 +52,11 @@ class BlitPassMTL final : public BlitPass { IRect source_region, size_t destination_offset, std::string label) override; + // |BlitPass| + bool OnCopyBufferToTextureCommand(BufferView source, + std::shared_ptr destination, + IPoint destination_origin, + std::string label) override; // |BlitPass| bool OnGenerateMipmapCommand(std::shared_ptr texture, diff --git a/impeller/renderer/backend/metal/blit_pass_mtl.mm b/impeller/renderer/backend/metal/blit_pass_mtl.mm index 74df114143655..c18261948df91 100644 --- a/impeller/renderer/backend/metal/blit_pass_mtl.mm +++ b/impeller/renderer/backend/metal/blit_pass_mtl.mm @@ -123,6 +123,21 @@ return true; } +bool BlitPassMTL::OnCopyBufferToTextureCommand( + BufferView source, + std::shared_ptr destination, + IPoint destination_origin, + std::string label) { + auto command = std::make_unique(); + command->label = label; + command->source = std::move(source); + command->destination = std::move(destination); + command->destination_origin = destination_origin; + + commands_.emplace_back(std::move(command)); + return true; +} + // |BlitPass| bool BlitPassMTL::OnGenerateMipmapCommand(std::shared_ptr texture, std::string label) { diff --git a/impeller/renderer/backend/metal/device_buffer_mtl.h b/impeller/renderer/backend/metal/device_buffer_mtl.h index ab4914d37fc9a..ef1d5deefa1fb 100644 --- a/impeller/renderer/backend/metal/device_buffer_mtl.h +++ b/impeller/renderer/backend/metal/device_buffer_mtl.h @@ -12,9 +12,8 @@ namespace impeller { -class DeviceBufferMTL final - : public DeviceBuffer, - public BackendCast { +class DeviceBufferMTL final : public DeviceBuffer, + public BackendCast { public: DeviceBufferMTL(); diff --git a/impeller/renderer/backend/vulkan/blit_pass_vk.h b/impeller/renderer/backend/vulkan/blit_pass_vk.h index 62e52c15477bc..6aa5f8a5fc4de 100644 --- a/impeller/renderer/backend/vulkan/blit_pass_vk.h +++ b/impeller/renderer/backend/vulkan/blit_pass_vk.h @@ -5,6 +5,7 @@ #pragma once #include "flutter/fml/macros.h" +#include "flutter/impeller/base/config.h" #include "impeller/renderer/backend/vulkan/blit_command_vk.h" #include "impeller/renderer/blit_pass.h" @@ -50,6 +51,15 @@ class BlitPassVK final : public BlitPass { size_t destination_offset, std::string label) override; + // |BlitPass| + bool OnCopyBufferToTextureCommand(BufferView source, + std::shared_ptr destination, + IPoint destination_origin, + std::string label) override { + IMPELLER_UNIMPLEMENTED; + return false; + } + // |BlitPass| bool OnGenerateMipmapCommand(std::shared_ptr texture, std::string label) override; diff --git a/impeller/renderer/blit_command.h b/impeller/renderer/blit_command.h index 7fc646aa53248..236b3e2c4b15d 100644 --- a/impeller/renderer/blit_command.h +++ b/impeller/renderer/blit_command.h @@ -28,6 +28,12 @@ struct BlitCopyTextureToBufferCommand : public BlitCommand { size_t destination_offset; }; +struct BlitCopyBufferToTextureCommand : public BlitCommand { + BufferView source; + std::shared_ptr destination; + IPoint destination_origin; +}; + struct BlitGenerateMipmapCommand : public BlitCommand { std::shared_ptr texture; }; diff --git a/impeller/renderer/blit_pass.cc b/impeller/renderer/blit_pass.cc index dd53e125645b5..da826a0af4644 100644 --- a/impeller/renderer/blit_pass.cc +++ b/impeller/renderer/blit_pass.cc @@ -100,7 +100,7 @@ bool BlitPass::AddCopy(std::shared_ptr source, if (destination_offset + bytes_per_image > destination->GetDeviceBufferDescriptor().size) { VALIDATION_LOG - << "Attempted to add a texture blit with out fo bounds access."; + << "Attempted to add a texture blit with out of bounds access."; return false; } @@ -116,6 +116,30 @@ bool BlitPass::AddCopy(std::shared_ptr source, std::move(label)); } +bool BlitPass::AddCopy(BufferView source, + std::shared_ptr destination, + IPoint destination_origin, + std::string label) { + if (!destination) { + VALIDATION_LOG << "Attempted to add a texture blit with no destination."; + return false; + } + + auto bytes_per_pixel = + BytesPerPixelForPixelFormat(destination->GetTextureDescriptor().format); + auto bytes_per_image = + destination->GetTextureDescriptor().size.Area() * bytes_per_pixel; + + if (source.range.length != bytes_per_image) { + VALIDATION_LOG + << "Attempted to add a texture blit with out of bounds access."; + return false; + } + + return OnCopyBufferToTextureCommand(std::move(source), std::move(destination), + destination_origin, std::move(label)); +} + bool BlitPass::GenerateMipmap(std::shared_ptr texture, std::string label) { if (!texture) { diff --git a/impeller/renderer/blit_pass.h b/impeller/renderer/blit_pass.h index 7ac7b820e5972..767f8d2a7f099 100644 --- a/impeller/renderer/blit_pass.h +++ b/impeller/renderer/blit_pass.h @@ -61,8 +61,8 @@ class BlitPass { std::string label = ""); //---------------------------------------------------------------------------- - /// @brief Record a command to copy the contents of the texture to - /// the buffer. + /// @brief Record a command to copy the contents of the buffer to + /// the texture. /// No work is encoded into the command buffer at this time. /// /// @param[in] source The texture to read for copying. @@ -71,8 +71,8 @@ class BlitPass { /// @param[in] source_region The optional region of the source texture /// to use for copying. If not specified, the /// full size of the source texture is used. - /// @param[in] destination_offset The offset to start writing to in the - /// destination buffer. + /// @param[in] destination_origin The origin to start writing to in the + /// destination texture. /// @param[in] label The optional debug label to give the /// command. /// @@ -84,6 +84,26 @@ class BlitPass { size_t destination_offset = 0, std::string label = ""); + //---------------------------------------------------------------------------- + /// @brief Record a command to copy the contents of the buffer to + /// the texture. + /// No work is encoded into the command buffer at this time. + /// + /// @param[in] source The buffer view to read for copying. + /// @param[in] destination The texture to overwrite using the source + /// contents. + /// @param[in] destination_offset The offset to start writing to in the + /// destination buffer. + /// @param[in] label The optional debug label to give the + /// command. + /// + /// @return If the command was valid for subsequent commitment. + /// + bool AddCopy(BufferView source, + std::shared_ptr destination, + IPoint destination_origin = {}, + std::string label = ""); + //---------------------------------------------------------------------------- /// @brief Record a command to generate all mip levels for a texture. /// No work is encoded into the command buffer at this time. @@ -127,6 +147,12 @@ class BlitPass { size_t destination_offset, std::string label) = 0; + virtual bool OnCopyBufferToTextureCommand( + BufferView source, + std::shared_ptr destination, + IPoint destination_origin, + std::string label) = 0; + virtual bool OnGenerateMipmapCommand(std::shared_ptr texture, std::string label) = 0; diff --git a/impeller/renderer/device_buffer.h b/impeller/renderer/device_buffer.h index a1ae3a041fc86..a4c43c723190a 100644 --- a/impeller/renderer/device_buffer.h +++ b/impeller/renderer/device_buffer.h @@ -43,13 +43,13 @@ class DeviceBuffer : public Buffer, const DeviceBufferDescriptor& GetDeviceBufferDescriptor() const; + virtual uint8_t* OnGetContents() const = 0; + protected: const DeviceBufferDescriptor desc_; explicit DeviceBuffer(DeviceBufferDescriptor desc); - virtual uint8_t* OnGetContents() const = 0; - virtual bool OnCopyHostBuffer(const uint8_t* source, Range source_range, size_t offset) = 0; diff --git a/impeller/renderer/testing/mocks.h b/impeller/renderer/testing/mocks.h index 3714874bffb8f..2ace08e5b8658 100644 --- a/impeller/renderer/testing/mocks.h +++ b/impeller/renderer/testing/mocks.h @@ -61,7 +61,11 @@ class MockBlitPass : public BlitPass { IRect source_region, size_t destination_offset, std::string label)); - + MOCK_METHOD4(OnCopyBufferToTextureCommand, + bool(BufferView source, + std::shared_ptr destination, + IPoint destination_origin, + std::string label)); MOCK_METHOD2(OnGenerateMipmapCommand, bool(std::shared_ptr texture, std::string label)); }; diff --git a/lib/ui/BUILD.gn b/lib/ui/BUILD.gn index b360fc81ffcc8..9355c0880a326 100644 --- a/lib/ui/BUILD.gn +++ b/lib/ui/BUILD.gn @@ -261,6 +261,7 @@ if (enable_unittests) { ":ui", ":ui_unittests_fixtures", "//flutter/common", + "//flutter/impeller", "//flutter/lib/snapshot", "//flutter/shell/common:shell_test_fixture_sources", "//flutter/testing", diff --git a/lib/ui/painting/image_decoder_impeller.cc b/lib/ui/painting/image_decoder_impeller.cc index 5fa07ec4c8491..fe544bb9da7c7 100644 --- a/lib/ui/painting/image_decoder_impeller.cc +++ b/lib/ui/painting/image_decoder_impeller.cc @@ -112,15 +112,16 @@ static std::optional ToPixelFormat(SkColorType type) { return std::nullopt; } -std::shared_ptr ImageDecoderImpeller::DecompressTexture( +std::optional ImageDecoderImpeller::DecompressTexture( ImageDescriptor* descriptor, SkISize target_size, impeller::ISize max_texture_size, - bool supports_wide_gamut) { + bool supports_wide_gamut, + const std::shared_ptr& allocator) { TRACE_EVENT0("impeller", __FUNCTION__); if (!descriptor) { FML_DLOG(ERROR) << "Invalid descriptor."; - return nullptr; + return std::nullopt; } target_size.set(std::min(static_cast(max_texture_size.width), @@ -166,23 +167,25 @@ std::shared_ptr ImageDecoderImpeller::DecompressTexture( const auto pixel_format = ToPixelFormat(image_info.colorType()); if (!pixel_format.has_value()) { FML_DLOG(ERROR) << "Codec pixel format not supported by Impeller."; - return nullptr; + return std::nullopt; } auto bitmap = std::make_shared(); + bitmap->setInfo(image_info); + auto bitmap_allocator = std::make_shared(allocator); + if (descriptor->is_compressed()) { - if (!bitmap->tryAllocPixels(image_info)) { + if (!bitmap->tryAllocPixels(bitmap_allocator.get())) { FML_DLOG(ERROR) << "Could not allocate intermediate for image decompression."; - return nullptr; + return std::nullopt; } // Decode the image into the image generator's closest supported size. if (!descriptor->get_pixels(bitmap->pixmap())) { FML_DLOG(ERROR) << "Could not decompress image."; - return nullptr; + return std::nullopt; } } else if (image_info.colorType() == base_image_info.colorType()) { - bitmap->setInfo(image_info); auto pixel_ref = SkMallocPixelRef::MakeWithData( image_info, descriptor->row_bytes(), descriptor->data()); bitmap->setPixelRef(pixel_ref, 0, 0); @@ -194,17 +197,23 @@ std::shared_ptr ImageDecoderImpeller::DecompressTexture( base_image_info, descriptor->row_bytes(), descriptor->data()); temp_bitmap->setPixelRef(pixel_ref, 0, 0); - if (!bitmap->tryAllocPixels(image_info)) { + if (!bitmap->tryAllocPixels(bitmap_allocator.get())) { FML_DLOG(ERROR) << "Could not allocate intermediate for pixel conversion."; - return nullptr; + return std::nullopt; } temp_bitmap->readPixels(bitmap->pixmap()); bitmap->setImmutable(); } if (bitmap->dimensions() == target_size) { - return bitmap; + auto buffer = bitmap_allocator->GetDeviceBuffer(); + if (!buffer.has_value()) { + return std::nullopt; + } + return DecompressResult{.device_buffer = buffer.value(), + .sk_bitmap = bitmap, + .image_info = bitmap->info()}; } //---------------------------------------------------------------------------- @@ -215,10 +224,12 @@ std::shared_ptr ImageDecoderImpeller::DecompressTexture( const auto scaled_image_info = image_info.makeDimensions(target_size); auto scaled_bitmap = std::make_shared(); - if (!scaled_bitmap->tryAllocPixels(scaled_image_info)) { + auto scaled_allocator = std::make_shared(allocator); + scaled_bitmap->setInfo(scaled_image_info); + if (!scaled_bitmap->tryAllocPixels(scaled_allocator.get())) { FML_LOG(ERROR) << "Could not allocate scaled bitmap for image decompression."; - return nullptr; + return std::nullopt; } if (!bitmap->pixmap().scalePixels( scaled_bitmap->pixmap(), @@ -227,12 +238,76 @@ std::shared_ptr ImageDecoderImpeller::DecompressTexture( } scaled_bitmap->setImmutable(); - return scaled_bitmap; + auto buffer = scaled_allocator->GetDeviceBuffer(); + if (!buffer.has_value()) { + return std::nullopt; + } + return DecompressResult{.device_buffer = buffer.value(), + .sk_bitmap = scaled_bitmap, + .image_info = scaled_bitmap->info()}; +} + +sk_sp ImageDecoderImpeller::UploadTextureToPrivate( + const std::shared_ptr& context, + const std::shared_ptr& buffer, + const SkImageInfo& image_info) { + TRACE_EVENT0("impeller", __FUNCTION__); + if (!context || !buffer) { + return nullptr; + } + const auto pixel_format = ToPixelFormat(image_info.colorType()); + if (!pixel_format) { + FML_DLOG(ERROR) << "Pixel format unsupported by Impeller."; + return nullptr; + } + + impeller::TextureDescriptor texture_descriptor; + texture_descriptor.storage_mode = impeller::StorageMode::kDevicePrivate; + texture_descriptor.format = pixel_format.value(); + texture_descriptor.size = {image_info.width(), image_info.height()}; + texture_descriptor.mip_count = texture_descriptor.size.MipCount(); + + auto dest_texture = + context->GetResourceAllocator()->CreateTexture(texture_descriptor); + if (!dest_texture) { + FML_DLOG(ERROR) << "Could not create Impeller texture."; + return nullptr; + } + + dest_texture->SetLabel( + impeller::SPrintF("ui.Image(%p)", dest_texture.get()).c_str()); + + auto command_buffer = context->CreateCommandBuffer(); + if (!command_buffer) { + FML_DLOG(ERROR) << "Could not create command buffer for mipmap generation."; + return nullptr; + } + command_buffer->SetLabel("Mipmap Command Buffer"); + + auto blit_pass = command_buffer->CreateBlitPass(); + if (!blit_pass) { + FML_DLOG(ERROR) << "Could not create blit pass for mipmap generation."; + return nullptr; + } + blit_pass->SetLabel("Mipmap Blit Pass"); + blit_pass->AddCopy(buffer->AsBufferView(), dest_texture); + if (texture_descriptor.size.MipCount() > 1) { + blit_pass->GenerateMipmap(dest_texture); + } + + blit_pass->EncodeCommands(context->GetResourceAllocator()); + if (!command_buffer->SubmitCommands()) { + FML_DLOG(ERROR) << "Failed to submit blit pass command buffer."; + return nullptr; + } + + return impeller::DlImageImpeller::Make(std::move(dest_texture)); } -sk_sp ImageDecoderImpeller::UploadTexture( +sk_sp ImageDecoderImpeller::UploadTextureToShared( const std::shared_ptr& context, - std::shared_ptr bitmap) { + std::shared_ptr bitmap, + bool create_mips) { TRACE_EVENT0("impeller", __FUNCTION__); if (!context || !bitmap) { return nullptr; @@ -270,7 +345,7 @@ sk_sp ImageDecoderImpeller::UploadTexture( texture->SetLabel(impeller::SPrintF("ui.Image(%p)", texture.get()).c_str()); - if (texture_descriptor.mip_count > 1u) { + if (texture_descriptor.mip_count > 1u && create_mips) { auto command_buffer = context->CreateCommandBuffer(); if (!command_buffer) { FML_DLOG(ERROR) @@ -331,15 +406,24 @@ void ImageDecoderImpeller::Decode(fml::RefPtr descriptor, context->GetResourceAllocator()->GetMaxTextureSizeSupported(); // Always decompress on the concurrent runner. - auto bitmap = - DecompressTexture(raw_descriptor, target_size, max_size_supported, - supports_wide_gamut); - if (!bitmap) { + auto bitmap_result = DecompressTexture( + raw_descriptor, target_size, max_size_supported, + supports_wide_gamut, context->GetResourceAllocator()); + if (!bitmap_result.has_value()) { result(nullptr); return; } - auto upload_texture_and_invoke_result = [result, context, bitmap]() { - result(UploadTexture(context, bitmap)); + auto upload_texture_and_invoke_result = [result, context, + bitmap_result = + bitmap_result.value()]() { +// TODO(jonahwilliams): remove ifdef once blit from buffer to texture is +// implemented on other platforms. +#ifdef FML_OS_IOS + result(UploadTextureToPrivate(context, bitmap_result.device_buffer, + bitmap_result.image_info)); +#else + result(UploadTextureToShared(context, bitmap_result.sk_bitmap)); +#endif }; // TODO(jonahwilliams): https://github.com/flutter/flutter/issues/123058 // Technically we don't need to post tasks to the io runner, but without @@ -349,4 +433,43 @@ void ImageDecoderImpeller::Decode(fml::RefPtr descriptor, }); } +ImpellerAllocator::ImpellerAllocator( + std::shared_ptr allocator) + : allocator_(std::move(allocator)) {} + +std::optional> +ImpellerAllocator::GetDeviceBuffer() const { + return buffer_; +} + +bool ImpellerAllocator::allocPixelRef(SkBitmap* bitmap) { + const SkImageInfo& info = bitmap->info(); + if (kUnknown_SkColorType == info.colorType() || info.width() < 0 || + info.height() < 0 || !info.validRowBytes(bitmap->rowBytes())) { + return false; + } + + impeller::DeviceBufferDescriptor descriptor; + descriptor.storage_mode = impeller::StorageMode::kHostVisible; + descriptor.size = ((bitmap->height() - 1) * bitmap->rowBytes()) + + (bitmap->width() * bitmap->bytesPerPixel()); + + auto device_buffer = allocator_->CreateBuffer(descriptor); + + struct ImpellerPixelRef final : public SkPixelRef { + ImpellerPixelRef(int w, int h, void* s, size_t r) + : SkPixelRef(w, h, s, r) {} + + ~ImpellerPixelRef() override {} + }; + + auto pixel_ref = sk_sp( + new ImpellerPixelRef(info.width(), info.height(), + device_buffer->OnGetContents(), bitmap->rowBytes())); + + bitmap->setPixelRef(std::move(pixel_ref), 0, 0); + buffer_ = std::move(device_buffer); + return true; +} + } // namespace flutter diff --git a/lib/ui/painting/image_decoder_impeller.h b/lib/ui/painting/image_decoder_impeller.h index 5764410ab5930..9df6b223104d1 100644 --- a/lib/ui/painting/image_decoder_impeller.h +++ b/lib/ui/painting/image_decoder_impeller.h @@ -10,13 +10,39 @@ #include "flutter/fml/macros.h" #include "flutter/lib/ui/painting/image_decoder.h" #include "impeller/geometry/size.h" +#include "third_party/skia/include/core/SkBitmap.h" namespace impeller { class Context; +class Allocator; +class DeviceBuffer; } // namespace impeller namespace flutter { +class ImpellerAllocator : public SkBitmap::Allocator { + public: + explicit ImpellerAllocator(std::shared_ptr allocator); + + ~ImpellerAllocator() = default; + + // |Allocator| + bool allocPixelRef(SkBitmap* bitmap) override; + + std::optional> GetDeviceBuffer() + const; + + private: + std::shared_ptr allocator_; + std::optional> buffer_; +}; + +struct DecompressResult { + std::shared_ptr device_buffer; + std::shared_ptr sk_bitmap; + SkImageInfo image_info; +}; + class ImageDecoderImpeller final : public ImageDecoder { public: ImageDecoderImpeller( @@ -33,15 +59,34 @@ class ImageDecoderImpeller final : public ImageDecoder { uint32_t target_height, const ImageResult& result) override; - static std::shared_ptr DecompressTexture( + static std::optional DecompressTexture( ImageDescriptor* descriptor, SkISize target_size, impeller::ISize max_texture_size, - bool supports_wide_gamut); + bool supports_wide_gamut, + const std::shared_ptr& allocator); + + /// @brief Create a device private texture from the provided host buffer. + /// This method is only suported on the metal backend. + /// @param context The Impeller graphics context. + /// @param buffer A host buffer containing the image to be uploaded. + /// @param image_info Format information about the particular image. + /// @return A DlImage. + static sk_sp UploadTextureToPrivate( + const std::shared_ptr& context, + const std::shared_ptr& buffer, + const SkImageInfo& image_info); - static sk_sp UploadTexture( + /// @brief Create a host visible texture from the provided bitmap. + /// @param context The Impeller graphics context. + /// @param bitmap A bitmap containg the image to be uploaded. + /// @param create_mips Whether mipmaps should be generated for the given + /// image. + /// @return A DlImage. + static sk_sp UploadTextureToShared( const std::shared_ptr& context, - std::shared_ptr bitmap); + std::shared_ptr bitmap, + bool create_mips = true); private: using FutureContext = std::shared_future>; diff --git a/lib/ui/painting/image_decoder_unittests.cc b/lib/ui/painting/image_decoder_unittests.cc index 472a9d45a7bc6..35cf13a0ab6cb 100644 --- a/lib/ui/painting/image_decoder_unittests.cc +++ b/lib/ui/painting/image_decoder_unittests.cc @@ -5,6 +5,9 @@ #include "flutter/common/task_runners.h" #include "flutter/fml/mapping.h" #include "flutter/fml/synchronization/waitable_event.h" +#include "flutter/impeller/geometry/size.h" +#include "flutter/impeller/renderer/allocator.h" +#include "flutter/impeller/renderer/device_buffer.h" #include "flutter/lib/ui/painting/image_decoder.h" #include "flutter/lib/ui/painting/image_decoder_impeller.h" #include "flutter/lib/ui/painting/image_decoder_skia.h" @@ -24,6 +27,68 @@ #include "third_party/skia/include/core/SkImageInfo.h" #include "third_party/skia/include/core/SkSize.h" +namespace impeller { + +class TestImpellerDeviceBuffer : public DeviceBuffer { + public: + explicit TestImpellerDeviceBuffer(DeviceBufferDescriptor desc) + : DeviceBuffer(desc) { + bytes_ = static_cast(malloc(desc.size)); + } + + ~TestImpellerDeviceBuffer() { free(bytes_); } + + private: + std::shared_ptr AsTexture(Allocator& allocator, + const TextureDescriptor& descriptor, + uint16_t row_bytes) const override { + return nullptr; + } + + bool SetLabel(const std::string& label) override { return true; } + + bool SetLabel(const std::string& label, Range range) override { return true; } + + uint8_t* OnGetContents() const override { return bytes_; } + + bool OnCopyHostBuffer(const uint8_t* source, + Range source_range, + size_t offset) override { + for (auto i = source_range.offset; i < source_range.length; i++, offset++) { + bytes_[offset] = source[i]; + } + return true; + } + + uint8_t* bytes_; +}; + +class TestImpellerAllocator : public impeller::Allocator { + public: + TestImpellerAllocator() {} + + ~TestImpellerAllocator() = default; + + private: + uint16_t MinimumBytesPerRow(PixelFormat format) const override { return 0; } + + ISize GetMaxTextureSizeSupported() const override { + return ISize{2048, 2048}; + } + + std::shared_ptr OnCreateBuffer( + const DeviceBufferDescriptor& desc) override { + return std::make_shared(desc); + } + + std::shared_ptr OnCreateTexture( + const TextureDescriptor& desc) override { + return nullptr; + } +}; + +} // namespace impeller + namespace flutter { namespace testing { @@ -305,13 +370,15 @@ TEST_F(ImageDecoderFixtureTest, ImpellerNullColorspace) { std::move(data), image->imageInfo(), 10 * 4); #if IMPELLER_SUPPORTS_RENDERING - std::shared_ptr decompressed = + std::shared_ptr allocator = + std::make_shared(); + std::optional decompressed = ImageDecoderImpeller::DecompressTexture( descriptor.get(), SkISize::Make(100, 100), {100, 100}, - /*supports_wide_gamut=*/true); - ASSERT_TRUE(decompressed); - ASSERT_EQ(decompressed->colorType(), kRGBA_8888_SkColorType); - ASSERT_EQ(decompressed->colorSpace(), nullptr); + /*supports_wide_gamut=*/true, allocator); + ASSERT_TRUE(decompressed.has_value()); + ASSERT_EQ(decompressed->image_info.colorType(), kRGBA_8888_SkColorType); + ASSERT_EQ(decompressed->image_info.colorSpace(), nullptr); #endif // IMPELLER_SUPPORTS_RENDERING } @@ -330,14 +397,17 @@ TEST_F(ImageDecoderFixtureTest, ImpellerWideGamutDisplayP3) { std::move(generator)); #if IMPELLER_SUPPORTS_RENDERING - std::shared_ptr wide_bitmap = + std::shared_ptr allocator = + std::make_shared(); + std::optional wide_result = ImageDecoderImpeller::DecompressTexture( descriptor.get(), SkISize::Make(100, 100), {100, 100}, - /*supports_wide_gamut=*/true); - ASSERT_TRUE(wide_bitmap); - ASSERT_EQ(wide_bitmap->colorType(), kRGBA_F16_SkColorType); - ASSERT_TRUE(wide_bitmap->colorSpace()->isSRGB()); - const SkPixmap& wide_pixmap = wide_bitmap->pixmap(); + /*supports_wide_gamut=*/true, allocator); + ASSERT_TRUE(wide_result.has_value()); + ASSERT_EQ(wide_result->image_info.colorType(), kRGBA_F16_SkColorType); + ASSERT_TRUE(wide_result->image_info.colorSpace()->isSRGB()); + + const SkPixmap& wide_pixmap = wide_result->sk_bitmap->pixmap(); const uint16_t* half_ptr = static_cast(wide_pixmap.addr()); bool found_deep_red = false; for (int i = 0; i < wide_pixmap.width() * wide_pixmap.height(); ++i) { @@ -351,11 +421,15 @@ TEST_F(ImageDecoderFixtureTest, ImpellerWideGamutDisplayP3) { break; } } + ASSERT_TRUE(found_deep_red); - std::shared_ptr bitmap = ImageDecoderImpeller::DecompressTexture( - descriptor.get(), SkISize::Make(100, 100), {100, 100}, - /*supports_wide_gamut=*/false); - ASSERT_EQ(bitmap->colorType(), kRGBA_8888_SkColorType); + std::optional narrow_result = + ImageDecoderImpeller::DecompressTexture( + descriptor.get(), SkISize::Make(100, 100), {100, 100}, + /*supports_wide_gamut=*/false, allocator); + + ASSERT_TRUE(narrow_result.has_value()); + ASSERT_EQ(narrow_result->image_info.colorType(), kRGBA_8888_SkColorType); #endif // IMPELLER_SUPPORTS_RENDERING } @@ -374,13 +448,16 @@ TEST_F(ImageDecoderFixtureTest, ImpellerPixelConversion32F) { std::move(data), image->imageInfo(), 10 * 16); #if IMPELLER_SUPPORTS_RENDERING - std::shared_ptr decompressed = + std::shared_ptr allocator = + std::make_shared(); + std::optional decompressed = ImageDecoderImpeller::DecompressTexture( descriptor.get(), SkISize::Make(100, 100), {100, 100}, - /*supports_wide_gamut=*/true); - ASSERT_TRUE(decompressed); - ASSERT_EQ(decompressed->colorType(), kRGBA_F16_SkColorType); - ASSERT_EQ(decompressed->colorSpace(), nullptr); + /*supports_wide_gamut=*/true, allocator); + + ASSERT_TRUE(decompressed.has_value()); + ASSERT_EQ(decompressed->image_info.colorType(), kRGBA_F16_SkColorType); + ASSERT_EQ(decompressed->image_info.colorSpace(), nullptr); #endif // IMPELLER_SUPPORTS_RENDERING } @@ -409,14 +486,18 @@ TEST_F(ImageDecoderFixtureTest, ImpellerWideGamutDisplayP3Opaque) { std::move(generator)); #if IMPELLER_SUPPORTS_RENDERING - std::shared_ptr wide_bitmap = + std::shared_ptr allocator = + std::make_shared(); + std::optional wide_result = ImageDecoderImpeller::DecompressTexture( descriptor.get(), SkISize::Make(100, 100), {100, 100}, - /*supports_wide_gamut=*/true); - ASSERT_TRUE(wide_bitmap); - ASSERT_EQ(wide_bitmap->colorType(), kBGR_101010x_XR_SkColorType); - ASSERT_TRUE(wide_bitmap->colorSpace()->isSRGB()); - const SkPixmap& wide_pixmap = wide_bitmap->pixmap(); + /*supports_wide_gamut=*/true, allocator); + + ASSERT_TRUE(wide_result.has_value()); + ASSERT_EQ(wide_result->image_info.colorType(), kBGR_101010x_XR_SkColorType); + ASSERT_TRUE(wide_result->image_info.colorSpace()->isSRGB()); + + const SkPixmap& wide_pixmap = wide_result->sk_bitmap->pixmap(); const uint32_t* pixel_ptr = static_cast(wide_pixmap.addr()); bool found_deep_red = false; for (int i = 0; i < wide_pixmap.width() * wide_pixmap.height(); ++i) { @@ -431,10 +512,14 @@ TEST_F(ImageDecoderFixtureTest, ImpellerWideGamutDisplayP3Opaque) { } } ASSERT_TRUE(found_deep_red); - std::shared_ptr bitmap = ImageDecoderImpeller::DecompressTexture( - descriptor.get(), SkISize::Make(100, 100), {100, 100}, - /*supports_wide_gamut=*/false); - ASSERT_EQ(bitmap->colorType(), kRGBA_8888_SkColorType); + + std::optional narrow_result = + ImageDecoderImpeller::DecompressTexture( + descriptor.get(), SkISize::Make(100, 100), {100, 100}, + /*supports_wide_gamut=*/false, allocator); + + ASSERT_TRUE(narrow_result.has_value()); + ASSERT_EQ(narrow_result->image_info.colorType(), kRGBA_8888_SkColorType); #endif // IMPELLER_SUPPORTS_RENDERING } @@ -453,10 +538,15 @@ TEST_F(ImageDecoderFixtureTest, ImpellerNonWideGamut) { std::move(generator)); #if IMPELLER_SUPPORTS_RENDERING - std::shared_ptr bitmap = ImageDecoderImpeller::DecompressTexture( - descriptor.get(), SkISize::Make(600, 200), {600, 200}, - /*supports_wide_gamut=*/true); - ASSERT_EQ(bitmap->colorType(), kRGBA_8888_SkColorType); + std::shared_ptr allocator = + std::make_shared(); + std::optional result = + ImageDecoderImpeller::DecompressTexture( + descriptor.get(), SkISize::Make(600, 200), {600, 200}, + /*supports_wide_gamut=*/true, allocator); + + ASSERT_TRUE(result.has_value()); + ASSERT_EQ(result->image_info.colorType(), kRGBA_8888_SkColorType); #endif // IMPELLER_SUPPORTS_RENDERING } @@ -695,16 +785,17 @@ TEST(ImageDecoderTest, VerifySimpleDecoding) { SkISize::Make(6, 2)); #if IMPELLER_SUPPORTS_RENDERING - ASSERT_EQ(ImageDecoderImpeller::DecompressTexture( - descriptor.get(), SkISize::Make(6, 2), {100, 100}, - /*supports_wide_gamut=*/false) - ->dimensions(), - SkISize::Make(6, 2)); - ASSERT_EQ(ImageDecoderImpeller::DecompressTexture( - descriptor.get(), SkISize::Make(60, 20), {10, 10}, - /*supports_wide_gamut=*/false) - ->dimensions(), - SkISize::Make(10, 10)); + std::shared_ptr allocator = + std::make_shared(); + auto result_1 = ImageDecoderImpeller::DecompressTexture( + descriptor.get(), SkISize::Make(6, 2), {100, 100}, + /*supports_wide_gamut=*/false, allocator); + ASSERT_EQ(result_1->sk_bitmap->dimensions(), SkISize::Make(6, 2)); + + auto result_2 = ImageDecoderImpeller::DecompressTexture( + descriptor.get(), SkISize::Make(60, 20), {10, 10}, + /*supports_wide_gamut=*/false, allocator); + ASSERT_EQ(result_2->sk_bitmap->dimensions(), SkISize::Make(10, 10)); #endif // IMPELLER_SUPPORTS_RENDERING } diff --git a/lib/ui/painting/multi_frame_codec.cc b/lib/ui/painting/multi_frame_codec.cc index d2c38fcf15890..664973cfd9cf3 100644 --- a/lib/ui/painting/multi_frame_codec.cc +++ b/lib/ui/painting/multi_frame_codec.cc @@ -147,8 +147,9 @@ sk_sp MultiFrameCodec::State::GetNextFrameImage( // impeller, transfer to DlImageImpeller gpu_disable_sync_switch->Execute(fml::SyncSwitch::Handlers().SetIfFalse( [&result, &bitmap, &impeller_context_] { - result = ImageDecoderImpeller::UploadTexture( - impeller_context_, std::make_shared(bitmap)); + result = ImageDecoderImpeller::UploadTextureToShared( + impeller_context_, std::make_shared(bitmap), + /*create_mips=*/false); })); return result;