-
Notifications
You must be signed in to change notification settings - Fork 6k
[Impeller] use blit pass to resize decoded images. #54606
Changes from 16 commits
ca965f2
abf3b66
9046147
a2b6b72
6d853d0
407eca8
29ff35d
4522d3d
3163ced
2091507
df0bd0a
baec160
63930c0
c90fec8
003f13a
f6d5340
b0712bd
d039650
f02dde4
42bf31e
f4b0f3b
4d4b83c
869a9dc
7905136
e6f1ec6
1156b20
6f1ed78
855aad1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -377,17 +377,21 @@ new ContextMTL(device, command_queue, | |
| return buffer; | ||
| } | ||
|
|
||
| void ContextMTL::StoreTaskForGPU(const std::function<void()>& task) { | ||
| tasks_awaiting_gpu_.emplace_back(task); | ||
| void ContextMTL::StoreTaskForGPU(const std::function<void()>& task, | ||
| const std::function<void()>& failure) { | ||
| tasks_awaiting_gpu_.push_back(PendingTasks{task, failure}); | ||
| while (tasks_awaiting_gpu_.size() > kMaxTasksAwaitingGPU) { | ||
| tasks_awaiting_gpu_.front()(); | ||
| PendingTasks front = std::move(tasks_awaiting_gpu_.front()); | ||
| if (front.failure) { | ||
| front.failure(); | ||
| } | ||
| tasks_awaiting_gpu_.pop_front(); | ||
| } | ||
| } | ||
|
|
||
| void ContextMTL::FlushTasksAwaitingGPU() { | ||
| for (const auto& task : tasks_awaiting_gpu_) { | ||
| task(); | ||
| task.task(); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perform null checks here or wrap a null ptr in a do-nothing fml::closure in |
||
| } | ||
| tasks_awaiting_gpu_.clear(); | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,6 +15,7 @@ | |
| #include "flutter/impeller/renderer/context.h" | ||
| #include "impeller/base/strings.h" | ||
| #include "impeller/core/device_buffer.h" | ||
| #include "impeller/core/formats.h" | ||
| #include "impeller/display_list/skia_conversions.h" | ||
| #include "impeller/geometry/size.h" | ||
| #include "third_party/skia/include/core/SkAlphaType.h" | ||
|
|
@@ -26,7 +27,6 @@ | |
| #include "third_party/skia/include/core/SkPixelRef.h" | ||
| #include "third_party/skia/include/core/SkPixmap.h" | ||
| #include "third_party/skia/include/core/SkPoint.h" | ||
| #include "third_party/skia/include/core/SkSamplingOptions.h" | ||
| #include "third_party/skia/include/core/SkSize.h" | ||
|
|
||
| namespace flutter { | ||
|
|
@@ -224,61 +224,35 @@ DecompressResult ImageDecoderImpeller::DecompressTexture( | |
| bitmap = premul_bitmap; | ||
| } | ||
|
|
||
| if (bitmap->dimensions() == target_size) { | ||
| std::shared_ptr<impeller::DeviceBuffer> buffer = | ||
| bitmap_allocator->GetDeviceBuffer(); | ||
| if (!buffer) { | ||
| return DecompressResult{.decode_error = "Unable to get device buffer"}; | ||
| } | ||
| buffer->Flush(); | ||
|
|
||
| return DecompressResult{.device_buffer = std::move(buffer), | ||
| .sk_bitmap = bitmap, | ||
| .image_info = bitmap->info()}; | ||
| } | ||
|
|
||
| //---------------------------------------------------------------------------- | ||
| /// 2. If the decoded image isn't the requested target size, resize it. | ||
| /// | ||
|
|
||
| TRACE_EVENT0("impeller", "DecodeScale"); | ||
| const auto scaled_image_info = image_info.makeDimensions(target_size); | ||
|
|
||
| auto scaled_bitmap = std::make_shared<SkBitmap>(); | ||
| auto scaled_allocator = std::make_shared<ImpellerAllocator>(allocator); | ||
| scaled_bitmap->setInfo(scaled_image_info); | ||
| if (!scaled_bitmap->tryAllocPixels(scaled_allocator.get())) { | ||
| std::string decode_error( | ||
| "Could not allocate scaled bitmap for image decompression."); | ||
| FML_DLOG(ERROR) << decode_error; | ||
| return DecompressResult{.decode_error = decode_error}; | ||
| } | ||
| if (!bitmap->pixmap().scalePixels( | ||
| scaled_bitmap->pixmap(), | ||
| SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kNone))) { | ||
| FML_LOG(ERROR) << "Could not scale decoded bitmap data."; | ||
| } | ||
| scaled_bitmap->setImmutable(); | ||
|
|
||
| std::shared_ptr<impeller::DeviceBuffer> buffer = | ||
| scaled_allocator->GetDeviceBuffer(); | ||
| buffer->Flush(); | ||
|
|
||
| bitmap_allocator->GetDeviceBuffer(); | ||
| if (!buffer) { | ||
| return DecompressResult{.decode_error = "Unable to get device buffer"}; | ||
| } | ||
| buffer->Flush(); | ||
|
|
||
| std::optional<SkImageInfo> resize_info = | ||
| bitmap->dimensions() == target_size | ||
| ? std::nullopt | ||
| : std::optional<SkImageInfo>(image_info.makeDimensions(target_size)); | ||
| return DecompressResult{.device_buffer = std::move(buffer), | ||
| .sk_bitmap = scaled_bitmap, | ||
| .image_info = scaled_bitmap->info()}; | ||
| .sk_bitmap = bitmap, | ||
| .image_info = bitmap->info(), | ||
| .resize_info = resize_info}; | ||
| } | ||
|
|
||
| /// Only call this method if the GPU is available. | ||
| static std::pair<sk_sp<DlImage>, std::string> UnsafeUploadTextureToPrivate( | ||
| // static | ||
| std::pair<sk_sp<DlImage>, std::string> | ||
| ImageDecoderImpeller::UnsafeUploadTextureToPrivate( | ||
| const std::shared_ptr<impeller::Context>& context, | ||
| const std::shared_ptr<impeller::DeviceBuffer>& buffer, | ||
| const SkImageInfo& image_info) { | ||
| const SkImageInfo& image_info, | ||
| const std::optional<SkImageInfo>& resize_info, | ||
| bool create_mips) { | ||
| const auto pixel_format = | ||
| impeller::skia_conversions::ToPixelFormat(image_info.colorType()); | ||
| // mips should still be created if the source image is being resized. | ||
| const bool should_create_mips = create_mips || resize_info.has_value(); | ||
| if (!pixel_format) { | ||
| std::string decode_error(impeller::SPrintF( | ||
| "Unsupported pixel format (SkColorType=%d)", image_info.colorType())); | ||
|
|
@@ -290,7 +264,8 @@ static std::pair<sk_sp<DlImage>, std::string> UnsafeUploadTextureToPrivate( | |
| 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(); | ||
| texture_descriptor.mip_count = | ||
| should_create_mips ? texture_descriptor.size.MipCount() : 1; | ||
| texture_descriptor.compression_type = impeller::CompressionType::kLossy; | ||
|
|
||
| auto dest_texture = | ||
|
|
@@ -323,59 +298,104 @@ static std::pair<sk_sp<DlImage>, std::string> UnsafeUploadTextureToPrivate( | |
| blit_pass->SetLabel("Mipmap Blit Pass"); | ||
| blit_pass->AddCopy(impeller::DeviceBuffer::AsBufferView(buffer), | ||
| dest_texture); | ||
| if (texture_descriptor.size.MipCount() > 1) { | ||
| if (texture_descriptor.mip_count > 1) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, oops. |
||
| blit_pass->GenerateMipmap(dest_texture); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is unnecessary and wasteful. The mip chain needs to be generated for the resize texture after the base level from the dest texture has been used as the blit source. Generating a mip chain for the dest texture does nothing.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I might be misunderstanding how this works. I think what I actually need to do is blit starting from the closest larger size mip level, otherwise I'm going to drop rows of data, right?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What I think I need to do is change blit_pass_vk.cc to check if the dst size is smaller than src and if src has mipmaps. If so, for the https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkImageCopy.html specify the src mip level better. Also if we have mips, we should copy the mips too.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, I may be misunderstanding this now. Can you not use the blit pass to resize? From the docs: "The textures can be different sizes as long as the larger texture has a mipmap level that’s the same size as the smaller texture’s level 0 mipmap.". In that case, the resize must happen via a draw call.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And then you can use a any sampling mode you want. So, draw from the source to the dest and then generate the mips just once.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I just need to specify a filter in blit_pass_vk. But since I only copy mip level 0 I still need to regenerate the mip chain.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh but we use vkcopyImage hmmm
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can detect that the sizes don't match and do the full blit. Unknown if https://developer.apple.com/documentation/metal/mtlblitcommandencoder/1400754-copyfromtexture supports resizing but I could use a MPS instead.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Spoiler: it does not, switched to use MPS |
||
| } | ||
|
|
||
| std::shared_ptr<impeller::Texture> result_texture = dest_texture; | ||
| if (resize_info.has_value()) { | ||
| impeller::TextureDescriptor resize_desc; | ||
| resize_desc.storage_mode = impeller::StorageMode::kDevicePrivate; | ||
| resize_desc.format = pixel_format.value(); | ||
| resize_desc.size = {resize_info->width(), resize_info->height()}; | ||
| resize_desc.mip_count = create_mips ? resize_desc.size.MipCount() : 1; | ||
| resize_desc.compression_type = impeller::CompressionType::kLossy; | ||
|
|
||
| auto resize_texture = | ||
| context->GetResourceAllocator()->CreateTexture(resize_desc); | ||
| if (!resize_texture) { | ||
| std::string decode_error("Could not create resized Impeller texture."); | ||
| FML_DLOG(ERROR) << decode_error; | ||
| return std::make_pair(nullptr, decode_error); | ||
| } | ||
|
|
||
| blit_pass->AddCopy(/*source=*/dest_texture, /*destination=*/resize_texture); | ||
| if (resize_desc.mip_count > 1) { | ||
| blit_pass->GenerateMipmap(resize_texture); | ||
| } | ||
|
|
||
| result_texture = std::move(resize_texture); | ||
| } | ||
| blit_pass->EncodeCommands(context->GetResourceAllocator()); | ||
|
|
||
| if (!context->GetCommandQueue()->Submit({command_buffer}).ok()) { | ||
| std::string decode_error("Failed to submit blit pass command buffer."); | ||
| std::string decode_error("Failed to submit image deocding command buffer."); | ||
|
||
| FML_DLOG(ERROR) << decode_error; | ||
| return std::make_pair(nullptr, decode_error); | ||
| } | ||
|
|
||
| return std::make_pair( | ||
| impeller::DlImageImpeller::Make(std::move(dest_texture)), std::string()); | ||
| impeller::DlImageImpeller::Make(std::move(result_texture)), | ||
| std::string()); | ||
| } | ||
|
|
||
| std::pair<sk_sp<DlImage>, std::string> | ||
| ImageDecoderImpeller::UploadTextureToPrivate( | ||
| void ImageDecoderImpeller::UploadTextureToPrivate( | ||
| ImageResult result, | ||
| const std::shared_ptr<impeller::Context>& context, | ||
| const std::shared_ptr<impeller::DeviceBuffer>& buffer, | ||
| const SkImageInfo& image_info, | ||
| const std::shared_ptr<SkBitmap>& bitmap, | ||
| const std::shared_ptr<fml::SyncSwitch>& gpu_disabled_switch) { | ||
| const std::optional<SkImageInfo>& resize_info, | ||
| const std::shared_ptr<fml::SyncSwitch>& gpu_disabled_switch, | ||
| bool create_mips) { | ||
| TRACE_EVENT0("impeller", __FUNCTION__); | ||
| if (!context) { | ||
| return std::make_pair(nullptr, "No Impeller context is available"); | ||
| result(nullptr, "No Impeller context is available"); | ||
| return; | ||
| } | ||
| if (!buffer) { | ||
| return std::make_pair(nullptr, "No Impeller device buffer is available"); | ||
| result(nullptr, "No Impeller device buffer is available"); | ||
| return; | ||
| } | ||
|
|
||
| std::pair<sk_sp<DlImage>, std::string> result; | ||
| gpu_disabled_switch->Execute( | ||
| fml::SyncSwitch::Handlers() | ||
| .SetIfFalse([&result, context, buffer, image_info] { | ||
| result = UnsafeUploadTextureToPrivate(context, buffer, image_info); | ||
| }) | ||
| .SetIfTrue([&result, context, bitmap, gpu_disabled_switch] { | ||
| // create_mips is false because we already know the GPU is disabled. | ||
| result = | ||
| UploadTextureToStorage(context, bitmap, gpu_disabled_switch, | ||
| impeller::StorageMode::kHostVisible, | ||
| /*create_mips=*/false); | ||
| .SetIfFalse( | ||
| [&result, context, buffer, image_info, resize_info, create_mips] { | ||
| sk_sp<DlImage> image; | ||
| std::string decode_error; | ||
| std::tie(image, decode_error) = std::tie(image, decode_error) = | ||
| UnsafeUploadTextureToPrivate(context, buffer, image_info, | ||
| resize_info, create_mips); | ||
| result(image, decode_error); | ||
| }) | ||
| .SetIfTrue([&result, context, buffer, image_info, resize_info, | ||
| create_mips] { | ||
| // The `result` function must be copied in the capture list for each | ||
| // closure or the stack allocated callback will be cleared by the | ||
| // time to closure is executed later. | ||
| context->StoreTaskForGPU( | ||
| [result, context, buffer, image_info, resize_info, | ||
| create_mips]() { | ||
| sk_sp<DlImage> image; | ||
| std::string decode_error; | ||
| std::tie(image, decode_error) = std::tie(image, | ||
| decode_error) = | ||
| UnsafeUploadTextureToPrivate(context, buffer, image_info, | ||
| resize_info, create_mips); | ||
| result(image, decode_error); | ||
| }, | ||
| [result]() { | ||
| result(nullptr, | ||
| "Image upload failed due to loss of GPU access."); | ||
| }); | ||
| })); | ||
| return result; | ||
| } | ||
|
|
||
| std::pair<sk_sp<DlImage>, std::string> | ||
| ImageDecoderImpeller::UploadTextureToStorage( | ||
| const std::shared_ptr<impeller::Context>& context, | ||
| std::shared_ptr<SkBitmap> bitmap, | ||
| const std::shared_ptr<fml::SyncSwitch>& gpu_disabled_switch, | ||
| impeller::StorageMode storage_mode, | ||
| bool create_mips) { | ||
| std::shared_ptr<SkBitmap> bitmap) { | ||
| TRACE_EVENT0("impeller", __FUNCTION__); | ||
| if (!context) { | ||
| return std::make_pair(nullptr, "No Impeller context is available"); | ||
|
|
@@ -394,11 +414,10 @@ ImageDecoderImpeller::UploadTextureToStorage( | |
| } | ||
|
|
||
| impeller::TextureDescriptor texture_descriptor; | ||
| texture_descriptor.storage_mode = storage_mode; | ||
| texture_descriptor.storage_mode = impeller::StorageMode::kHostVisible; | ||
| texture_descriptor.format = pixel_format.value(); | ||
| texture_descriptor.size = {image_info.width(), image_info.height()}; | ||
| texture_descriptor.mip_count = | ||
| create_mips ? texture_descriptor.size.MipCount() : 1; | ||
| texture_descriptor.mip_count = 1; | ||
|
|
||
| auto texture = | ||
| context->GetResourceAllocator()->CreateTexture(texture_descriptor); | ||
|
|
@@ -421,43 +440,6 @@ ImageDecoderImpeller::UploadTextureToStorage( | |
| } | ||
|
|
||
| texture->SetLabel(impeller::SPrintF("ui.Image(%p)", texture.get()).c_str()); | ||
|
|
||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since this function is only used for gifs now we don't need to worry about mip support. |
||
| if (texture_descriptor.mip_count > 1u && create_mips) { | ||
| std::optional<std::string> decode_error; | ||
|
|
||
| // The only platform that needs mipmapping unconditionally is GL. | ||
| // GL based platforms never disable GPU access. | ||
| // This is only really needed for iOS. | ||
| gpu_disabled_switch->Execute(fml::SyncSwitch::Handlers().SetIfFalse( | ||
| [context, &texture, &decode_error] { | ||
| auto command_buffer = context->CreateCommandBuffer(); | ||
| if (!command_buffer) { | ||
| decode_error = | ||
| "Could not create command buffer for mipmap generation."; | ||
| return; | ||
| } | ||
| command_buffer->SetLabel("Mipmap Command Buffer"); | ||
|
|
||
| auto blit_pass = command_buffer->CreateBlitPass(); | ||
| if (!blit_pass) { | ||
| decode_error = "Could not create blit pass for mipmap generation."; | ||
| return; | ||
| } | ||
| blit_pass->SetLabel("Mipmap Blit Pass"); | ||
| blit_pass->GenerateMipmap(texture); | ||
| blit_pass->EncodeCommands(context->GetResourceAllocator()); | ||
| if (!context->GetCommandQueue()->Submit({command_buffer}).ok()) { | ||
| decode_error = "Failed to submit blit pass command buffer."; | ||
| return; | ||
| } | ||
| command_buffer->WaitUntilScheduled(); | ||
| })); | ||
| if (decode_error.has_value()) { | ||
| FML_DLOG(ERROR) << decode_error.value(); | ||
| return std::make_pair(nullptr, decode_error.value()); | ||
| } | ||
| } | ||
|
|
||
| return std::make_pair(impeller::DlImageImpeller::Make(std::move(texture)), | ||
| std::string()); | ||
| } | ||
|
|
@@ -509,14 +491,21 @@ void ImageDecoderImpeller::Decode(fml::RefPtr<ImageDescriptor> descriptor, | |
|
|
||
| auto upload_texture_and_invoke_result = [result, context, bitmap_result, | ||
| gpu_disabled_switch]() { | ||
| sk_sp<DlImage> image; | ||
| std::string decode_error; | ||
| std::tie(image, decode_error) = UploadTextureToPrivate( | ||
| context, bitmap_result.device_buffer, bitmap_result.image_info, | ||
| bitmap_result.sk_bitmap, gpu_disabled_switch); | ||
| result(image, decode_error); | ||
| UploadTextureToPrivate(result, context, // | ||
| bitmap_result.device_buffer, // | ||
| bitmap_result.image_info, // | ||
| bitmap_result.sk_bitmap, // | ||
| bitmap_result.resize_info, // | ||
| gpu_disabled_switch // | ||
| ); | ||
| }; | ||
| io_runner->PostTask(upload_texture_and_invoke_result); | ||
| // The I/O image uploads are not threadsafe on GLES. | ||
| if (context->GetBackendType() == | ||
| impeller::Context::BackendType::kOpenGLES) { | ||
| io_runner->PostTask(upload_texture_and_invoke_result); | ||
| } else { | ||
| upload_texture_and_invoke_result(); | ||
| } | ||
| }); | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: fml::closure?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oops, missed this one.