| // Copyright 2021 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/viz/service/surfaces/surface_saved_frame.h" |
| |
| #include <algorithm> |
| #include <iterator> |
| #include <utility> |
| |
| #include "base/containers/flat_set.h" |
| #include "base/functional/bind.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "components/viz/common/frame_sinks/copy_output_request.h" |
| #include "components/viz/common/quads/compositor_frame_transition_directive.h" |
| #include "components/viz/common/quads/compositor_render_pass.h" |
| #include "components/viz/common/quads/compositor_render_pass_draw_quad.h" |
| #include "components/viz/common/quads/draw_quad.h" |
| #include "components/viz/common/transition_utils.h" |
| #include "components/viz/service/surfaces/surface.h" |
| #include "services/viz/public/mojom/compositing/compositor_render_pass_id.mojom.h" |
| #include "third_party/skia/include/core/SkBitmap.h" |
| #include "ui/gfx/geometry/point.h" |
| |
| namespace viz { |
| |
| namespace { |
| constexpr gfx::Size kDefaultTextureSizeForTesting = gfx::Size(20, 20); |
| |
| constexpr auto kResultFormat = CopyOutputRequest::ResultFormat::RGBA; |
| constexpr auto kResultDestination = |
| CopyOutputRequest::ResultDestination::kNativeTextures; |
| |
| // Returns the index of |render_pass_id| in |shared_elements| if the id |
| // corresponds to an element in the given list. Otherwise returns the size of |
| // |shared_elements| vector. |
| size_t GetSharedPassIndex( |
| const std::vector<CompositorFrameTransitionDirective::SharedElement>& |
| shared_elements, |
| CompositorRenderPassId render_pass_id) { |
| size_t shared_element_index = 0; |
| for (; shared_element_index < shared_elements.size(); |
| ++shared_element_index) { |
| if (shared_elements[shared_element_index].render_pass_id == |
| render_pass_id) { |
| break; |
| } |
| } |
| return shared_element_index; |
| } |
| |
| } // namespace |
| |
| SurfaceSavedFrame::SurfaceSavedFrame( |
| CompositorFrameTransitionDirective directive, |
| TransitionDirectiveCompleteCallback directive_finished_callback) |
| : directive_(std::move(directive)), |
| directive_finished_callback_(std::move(directive_finished_callback)) { |
| // We should only be constructing a saved frame from a save directive. |
| DCHECK_EQ(directive_.type(), CompositorFrameTransitionDirective::Type::kSave); |
| } |
| |
| SurfaceSavedFrame::~SurfaceSavedFrame() { |
| if (directive_finished_callback_) |
| std::move(directive_finished_callback_).Run(directive_); |
| } |
| |
| base::flat_set<ViewTransitionElementResourceId> |
| SurfaceSavedFrame::GetEmptyResourceIds() const { |
| base::flat_set<ViewTransitionElementResourceId> result; |
| for (auto& shared_element : directive_.shared_elements()) |
| if (shared_element.render_pass_id.is_null()) |
| result.insert(shared_element.view_transition_element_resource_id); |
| return result; |
| } |
| |
| bool SurfaceSavedFrame::IsValid() const { |
| bool result = valid_result_count_ == ExpectedResultCount(); |
| // If this saved frame is valid, then we should have a frame result. |
| DCHECK(!result || frame_result_); |
| return result; |
| } |
| |
| void SurfaceSavedFrame::RequestCopyOfOutput(Surface* surface) { |
| DCHECK(surface->HasActiveFrame()); |
| |
| const auto& active_frame = surface->GetActiveFrame(); |
| for (const auto& render_pass : active_frame.render_pass_list) { |
| if (auto request = CreateCopyRequestIfNeeded( |
| *render_pass, active_frame.render_pass_list)) { |
| surface->RequestCopyOfOutputOnActiveFrameRenderPassId(std::move(request), |
| render_pass->id); |
| copy_request_count_++; |
| } |
| } |
| |
| DCHECK_EQ(copy_request_count_, ExpectedResultCount()); |
| |
| if (copy_request_count_ == 0) { |
| InitFrameResult(); |
| std::move(directive_finished_callback_).Run(directive_); |
| } |
| } |
| |
| std::unique_ptr<CopyOutputRequest> SurfaceSavedFrame::CreateCopyRequestIfNeeded( |
| const CompositorRenderPass& render_pass, |
| const CompositorRenderPassList& render_pass_list) const { |
| auto shared_pass_index = |
| GetSharedPassIndex(directive_.shared_elements(), render_pass.id); |
| if (shared_pass_index >= directive_.shared_elements().size()) |
| return nullptr; |
| |
| RenderPassDrawData draw_data( |
| render_pass, TransitionUtils::ComputeAccumulatedOpacity(render_pass_list, |
| render_pass.id)); |
| auto request = std::make_unique<CopyOutputRequest>( |
| kResultFormat, kResultDestination, |
| base::BindOnce(&SurfaceSavedFrame::NotifyCopyOfOutputComplete, |
| weak_factory_.GetMutableWeakPtr(), ResultType::kShared, |
| shared_pass_index, draw_data)); |
| request->set_result_task_runner( |
| base::SingleThreadTaskRunner::GetCurrentDefault()); |
| return request; |
| } |
| |
| bool SurfaceSavedFrame::IsSharedElementRenderPass( |
| CompositorRenderPassId pass_id) const { |
| const auto& shared_elements = directive_.shared_elements(); |
| return GetSharedPassIndex(shared_elements, pass_id) < shared_elements.size(); |
| } |
| |
| size_t SurfaceSavedFrame::ExpectedResultCount() const { |
| base::flat_set<CompositorRenderPassId> ids; |
| for (auto& shared_element : directive_.shared_elements()) |
| if (!shared_element.render_pass_id.is_null()) |
| ids.insert(shared_element.render_pass_id); |
| return ids.size(); |
| } |
| |
| void SurfaceSavedFrame::NotifyCopyOfOutputComplete( |
| ResultType type, |
| size_t shared_index, |
| const RenderPassDrawData& data, |
| std::unique_ptr<CopyOutputResult> output_copy) { |
| DCHECK_GT(copy_request_count_, 0u); |
| // Even if we early out, we update the count since we are no longer waiting |
| // for this result. |
| if (--copy_request_count_ == 0) { |
| std::move(directive_finished_callback_).Run(directive_); |
| } |
| |
| // Return if the result is empty. |
| if (output_copy->IsEmpty()) { |
| LOG(ERROR) << "SurfaceSavedFrame copy output result for shared index " |
| << shared_index << " is empty."; |
| return; |
| } |
| |
| ++valid_result_count_; |
| if (!frame_result_) { |
| InitFrameResult(); |
| // Resize to the number of shared elements, even if some will be nullopts. |
| frame_result_->shared_results.resize(directive_.shared_elements().size()); |
| } |
| |
| OutputCopyResult* slot = nullptr; |
| if (type == ResultType::kRoot) { |
| slot = &frame_result_->root_result; |
| } else { |
| DCHECK(type == ResultType::kShared); |
| CHECK_LT(shared_index, frame_result_->shared_results.size()); |
| DCHECK(!frame_result_->shared_results[shared_index]); |
| slot = &frame_result_->shared_results[shared_index].emplace(); |
| } |
| |
| DCHECK(slot); |
| DCHECK_EQ(output_copy->size(), data.size); |
| DCHECK_EQ(output_copy->format(), CopyOutputResult::Format::RGBA); |
| if (output_copy->destination() == |
| CopyOutputResult::Destination::kSystemMemory) { |
| slot->bitmap = output_copy->ScopedAccessSkBitmap().GetOutScopedBitmap(); |
| slot->is_software = true; |
| } else { |
| auto output_copy_texture = *output_copy->GetTextureResult(); |
| slot->mailbox = output_copy_texture.planes[0].mailbox; |
| slot->sync_token = output_copy_texture.planes[0].sync_token; |
| slot->color_space = output_copy_texture.color_space; |
| |
| CopyOutputResult::ReleaseCallbacks release_callbacks = |
| output_copy->TakeTextureOwnership(); |
| // CopyOutputResults carrying RGBA format contain a single texture, there |
| // should be only one release callback: |
| DCHECK_EQ(1u, release_callbacks.size()); |
| slot->release_callback = std::move(release_callbacks[0]); |
| slot->is_software = false; |
| } |
| slot->draw_data = data; |
| } |
| |
| absl::optional<SurfaceSavedFrame::FrameResult> SurfaceSavedFrame::TakeResult() { |
| return std::exchange(frame_result_, absl::nullopt); |
| } |
| |
| void SurfaceSavedFrame::CompleteSavedFrameForTesting() { |
| SkBitmap bitmap; |
| bitmap.allocPixels( |
| SkImageInfo::MakeN32Premul(kDefaultTextureSizeForTesting.width(), |
| kDefaultTextureSizeForTesting.height())); |
| |
| InitFrameResult(); |
| frame_result_->root_result.bitmap = std::move(bitmap); |
| frame_result_->root_result.draw_data.size = kDefaultTextureSizeForTesting; |
| frame_result_->root_result.draw_data.target_transform.MakeIdentity(); |
| frame_result_->root_result.draw_data.opacity = 1.f; |
| frame_result_->root_result.is_software = true; |
| |
| frame_result_->shared_results.resize(directive_.shared_elements().size()); |
| copy_request_count_ = 0; |
| valid_result_count_ = ExpectedResultCount(); |
| weak_factory_.InvalidateWeakPtrs(); |
| DCHECK(IsValid()); |
| } |
| |
| void SurfaceSavedFrame::InitFrameResult() { |
| frame_result_.emplace(); |
| frame_result_->empty_resource_ids = GetEmptyResourceIds(); |
| } |
| |
| SurfaceSavedFrame::RenderPassDrawData::RenderPassDrawData() = default; |
| SurfaceSavedFrame::RenderPassDrawData::RenderPassDrawData( |
| const CompositorRenderPass& render_pass, |
| float opacity) |
| : opacity(opacity) { |
| // During copy request, the origin for |render_pass|'s output_rect is mapped |
| // to the origin of the texture in the result. We account for that here. |
| size = render_pass.output_rect.size(); |
| |
| target_transform = render_pass.transform_to_root_target; |
| target_transform.Translate(render_pass.output_rect.x(), |
| render_pass.output_rect.y()); |
| } |
| |
| SurfaceSavedFrame::OutputCopyResult::OutputCopyResult() = default; |
| SurfaceSavedFrame::OutputCopyResult::OutputCopyResult( |
| OutputCopyResult&& other) { |
| *this = std::move(other); |
| } |
| |
| SurfaceSavedFrame::OutputCopyResult::~OutputCopyResult() { |
| if (release_callback) |
| std::move(release_callback).Run(sync_token, /*is_lost=*/false); |
| } |
| |
| SurfaceSavedFrame::OutputCopyResult& |
| SurfaceSavedFrame::OutputCopyResult::operator=(OutputCopyResult&& other) { |
| mailbox = std::move(other.mailbox); |
| other.mailbox = gpu::Mailbox(); |
| |
| sync_token = std::move(other.sync_token); |
| other.sync_token = gpu::SyncToken(); |
| |
| color_space = std::move(other.color_space); |
| other.color_space = gfx::ColorSpace(); |
| |
| bitmap = std::move(other.bitmap); |
| other.bitmap = SkBitmap(); |
| |
| draw_data = std::move(other.draw_data); |
| |
| release_callback = std::move(other.release_callback); |
| |
| is_software = other.is_software; |
| other.is_software = false; |
| |
| return *this; |
| } |
| |
| SurfaceSavedFrame::FrameResult::FrameResult() = default; |
| |
| SurfaceSavedFrame::FrameResult::FrameResult(FrameResult&& other) = default; |
| |
| SurfaceSavedFrame::FrameResult::~FrameResult() = default; |
| |
| SurfaceSavedFrame::FrameResult& SurfaceSavedFrame::FrameResult::operator=( |
| FrameResult&& other) = default; |
| |
| } // namespace viz |