blob: 78bb09f7d195afc04497918cacbfbe3e2b213f9b [file] [log] [blame]
// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/platform/graphics/offscreen_canvas_placeholder.h"
#include "base/task/single_thread_task_runner.h"
#include "third_party/blink/renderer/platform/graphics/canvas_resource.h"
#include "third_party/blink/renderer/platform/graphics/canvas_resource_dispatcher.h"
#include "third_party/blink/renderer/platform/graphics/resource_id_traits.h"
#include "third_party/blink/renderer/platform/scheduler/public/post_cross_thread_task.h"
#include "third_party/blink/renderer/platform/wtf/cross_thread_copier_base.h"
#include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h"
#include "third_party/blink/renderer/platform/wtf/hash_map.h"
#include "third_party/blink/renderer/platform/wtf/wtf.h"
namespace {
typedef HashMap<int, blink::OffscreenCanvasPlaceholder*> PlaceholderIdMap;
PlaceholderIdMap& placeholderRegistry() {
DEFINE_STATIC_LOCAL(PlaceholderIdMap, s_placeholderRegistry, ());
return s_placeholderRegistry;
}
void ReleaseFrameToDispatcher(
base::WeakPtr<blink::CanvasResourceDispatcher> dispatcher,
scoped_refptr<blink::CanvasResource> oldImage,
viz::ResourceId resourceId) {
if (dispatcher) {
dispatcher->ReclaimResource(resourceId, std::move(oldImage));
}
}
void SetSuspendAnimation(
base::WeakPtr<blink::CanvasResourceDispatcher> dispatcher,
bool suspend) {
if (dispatcher) {
dispatcher->SetSuspendAnimation(suspend);
}
}
void UpdateDispatcherFilterQuality(
base::WeakPtr<blink::CanvasResourceDispatcher> dispatcher,
cc::PaintFlags::FilterQuality filter) {
if (dispatcher) {
dispatcher->SetFilterQuality(filter);
}
}
} // unnamed namespace
namespace blink {
OffscreenCanvasPlaceholder::~OffscreenCanvasPlaceholder() {
UnregisterPlaceholderCanvas();
}
namespace {
// This function gets called when the last outstanding reference to a
// CanvasResource is released. This callback is only registered on
// resources received via SetOffscreenCanvasResource(). When the resource
// is received, its ref count may be 2 because the CanvasResourceProvider
// that created it may be holding a cached snapshot that will be released when
// copy-on-write kicks in. This is okay even if the resource provider is on a
// different thread because concurrent read access is safe. By the time the
// next frame is received by OffscreenCanvasPlaceholder, the reference held by
// CanvasResourceProvider will have been released (otherwise there wouldn't be
// a new frame). This means that all outstanding references are held on the
// same thread as the OffscreenCanvasPlaceholder at the time when
// 'placeholder_frame_' is assigned a new value. Therefore, when the last
// reference is released, we need to temporarily keep the object alive and send
// it back to its thread of origin, where it can be safely destroyed or
// recycled.
void FrameLastUnrefCallback(
base::WeakPtr<CanvasResourceDispatcher> frame_dispatcher,
scoped_refptr<base::SingleThreadTaskRunner> frame_dispatcher_task_runner,
viz::ResourceId placeholder_frame_resource_id,
scoped_refptr<CanvasResource> placeholder_frame) {
DCHECK(placeholder_frame);
DCHECK(placeholder_frame->HasOneRef());
DCHECK(frame_dispatcher_task_runner);
placeholder_frame->Transfer();
PostCrossThreadTask(
*frame_dispatcher_task_runner, FROM_HERE,
CrossThreadBindOnce(ReleaseFrameToDispatcher, frame_dispatcher,
std::move(placeholder_frame),
placeholder_frame_resource_id));
}
} // unnamed namespace
void OffscreenCanvasPlaceholder::SetOffscreenCanvasResource(
scoped_refptr<CanvasResource>&& new_frame,
viz::ResourceId resource_id) {
DCHECK(IsOffscreenCanvasRegistered());
DCHECK(new_frame);
// The following implicitly returns placeholder_frame_ to its
// CanvasResourceDispatcher, via FrameLastUnrefCallback if it was
// the last outstanding reference on this thread.
placeholder_frame_ = std::move(new_frame);
placeholder_frame_->SetLastUnrefCallback(
base::BindOnce(FrameLastUnrefCallback, frame_dispatcher_,
frame_dispatcher_task_runner_, resource_id));
if (animation_state_ == kShouldSuspendAnimation) {
bool success = PostSetSuspendAnimationToOffscreenCanvasThread(true);
DCHECK(success);
animation_state_ = kSuspendedAnimation;
} else if (animation_state_ == kShouldActivateAnimation) {
bool success = PostSetSuspendAnimationToOffscreenCanvasThread(false);
DCHECK(success);
animation_state_ = kActiveAnimation;
}
}
void OffscreenCanvasPlaceholder::SetOffscreenCanvasDispatcher(
base::WeakPtr<CanvasResourceDispatcher> dispatcher,
scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
DCHECK(IsOffscreenCanvasRegistered());
frame_dispatcher_ = std::move(dispatcher);
frame_dispatcher_task_runner_ = std::move(task_runner);
// The UpdateOffscreenCanvasFilterQuality could be called to change the filter
// quality before this function. We need to first apply the filter changes to
// the corresponding offscreen canvas.
if (filter_quality_) {
cc::PaintFlags::FilterQuality quality = filter_quality_.value();
filter_quality_ = std::nullopt;
UpdateOffscreenCanvasFilterQuality(quality);
}
}
void OffscreenCanvasPlaceholder::UpdateOffscreenCanvasFilterQuality(
cc::PaintFlags::FilterQuality filter_quality) {
DCHECK(IsOffscreenCanvasRegistered());
if (!frame_dispatcher_task_runner_) {
filter_quality_ = filter_quality;
return;
}
if (filter_quality_ == filter_quality)
return;
filter_quality_ = filter_quality;
if (frame_dispatcher_task_runner_->BelongsToCurrentThread()) {
UpdateDispatcherFilterQuality(frame_dispatcher_, filter_quality);
} else {
PostCrossThreadTask(*frame_dispatcher_task_runner_, FROM_HERE,
CrossThreadBindOnce(UpdateDispatcherFilterQuality,
frame_dispatcher_, filter_quality));
}
}
void OffscreenCanvasPlaceholder::SetSuspendOffscreenCanvasAnimation(
bool suspend) {
switch (animation_state_) {
case kActiveAnimation:
if (suspend) {
if (PostSetSuspendAnimationToOffscreenCanvasThread(suspend)) {
animation_state_ = kSuspendedAnimation;
} else {
animation_state_ = kShouldSuspendAnimation;
}
}
break;
case kSuspendedAnimation:
if (!suspend) {
if (PostSetSuspendAnimationToOffscreenCanvasThread(suspend)) {
animation_state_ = kActiveAnimation;
} else {
animation_state_ = kShouldActivateAnimation;
}
}
break;
case kShouldSuspendAnimation:
if (!suspend) {
animation_state_ = kActiveAnimation;
}
break;
case kShouldActivateAnimation:
if (suspend) {
animation_state_ = kSuspendedAnimation;
}
break;
default:
NOTREACHED();
}
}
OffscreenCanvasPlaceholder*
OffscreenCanvasPlaceholder::GetPlaceholderCanvasById(unsigned placeholder_id) {
PlaceholderIdMap::iterator it = placeholderRegistry().find(placeholder_id);
if (it == placeholderRegistry().end())
return nullptr;
return it->value;
}
void OffscreenCanvasPlaceholder::RegisterPlaceholderCanvas(
unsigned placeholder_id) {
DCHECK(!placeholderRegistry().Contains(placeholder_id));
DCHECK(!IsOffscreenCanvasRegistered());
placeholderRegistry().insert(placeholder_id, this);
placeholder_id_ = placeholder_id;
}
void OffscreenCanvasPlaceholder::UnregisterPlaceholderCanvas() {
if (!IsOffscreenCanvasRegistered())
return;
DCHECK(placeholderRegistry().find(placeholder_id_)->value == this);
placeholderRegistry().erase(placeholder_id_);
placeholder_id_ = kNoPlaceholderId;
}
bool OffscreenCanvasPlaceholder::PostSetSuspendAnimationToOffscreenCanvasThread(
bool suspend) {
if (!frame_dispatcher_task_runner_)
return false;
PostCrossThreadTask(
*frame_dispatcher_task_runner_, FROM_HERE,
CrossThreadBindOnce(SetSuspendAnimation, frame_dispatcher_, suspend));
return true;
}
} // namespace blink