blob: bcf5cda440e01d57c58e6859f48a9829319326c1 [file] [log] [blame]
// Copyright 2022 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/modules/webcodecs/background_readback.h"
#include "base/feature_list.h"
#include "base/numerics/safe_conversions.h"
#include "base/task/bind_post_task.h"
#include "base/task/task_traits.h"
#include "base/threading/thread_checker.h"
#include "base/trace_event/common/trace_event_common.h"
#include "base/trace_event/trace_event.h"
#include "components/viz/common/gpu/raster_context_provider.h"
#include "gpu/command_buffer/client/raster_interface.h"
#include "media/base/video_frame_pool.h"
#include "media/base/video_util.h"
#include "media/base/wait_and_replace_sync_token_client.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_audio_data_init.h"
#include "third_party/blink/renderer/modules/webaudio/audio_buffer.h"
#include "third_party/blink/renderer/modules/webcodecs/video_frame_rect_util.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/graphics/gpu/shared_gpu_context.h"
#include "third_party/blink/renderer/platform/graphics/web_graphics_context_3d_provider_util.h"
#include "third_party/blink/renderer/platform/heap/cross_thread_handle.h"
#include "third_party/blink/renderer/platform/wtf/cross_thread_copier_base.h"
#include "third_party/blink/renderer/platform/wtf/cross_thread_copier_gfx.h"
#include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h"
#include "third_party/blink/renderer/platform/wtf/functional.h"
#include "third_party/blink/renderer/platform/wtf/thread_safe_ref_counted.h"
#include "third_party/perfetto/include/perfetto/tracing/track.h"
namespace {
bool CanUseRgbReadback(media::VideoFrame& frame) {
return media::IsRGB(frame.format()) && frame.HasSharedImage();
}
SkImageInfo GetImageInfoForFrame(const media::VideoFrame& frame,
const gfx::Size& size) {
SkColorType color_type =
SkColorTypeForPlane(frame.format(), media::VideoFrame::Plane::kARGB);
SkAlphaType alpha_type = kUnpremul_SkAlphaType;
return SkImageInfo::Make(size.width(), size.height(), color_type, alpha_type);
}
gpu::raster::RasterInterface* GetSharedGpuRasterInterface() {
auto wrapper = blink::SharedGpuContext::ContextProviderWrapper();
if (wrapper) {
auto* raster_provider = wrapper->ContextProvider().RasterContextProvider();
if (raster_provider)
return raster_provider->RasterInterface();
}
return nullptr;
}
} // namespace
namespace blink {
template <>
struct CrossThreadCopier<VideoFrameLayout>
: public CrossThreadCopierPassThrough<VideoFrameLayout> {
STATIC_ONLY(CrossThreadCopier);
};
template <>
struct CrossThreadCopier<base::span<uint8_t>>
: public CrossThreadCopierPassThrough<base::span<uint8_t>> {
STATIC_ONLY(CrossThreadCopier);
};
// This is a part of BackgroundReadback that lives and dies on the worker's
// thread and does all the actual work of creating GPU context and calling
// sync readback functions.
class SyncReadbackThread : public ThreadSafeRefCounted<SyncReadbackThread> {
public:
SyncReadbackThread();
scoped_refptr<media::VideoFrame> ReadbackToFrame(
scoped_refptr<media::VideoFrame> frame);
bool ReadbackToBuffer(scoped_refptr<media::VideoFrame> frame,
const gfx::Rect src_rect,
const VideoFrameLayout dest_layout,
base::span<uint8_t> dest_buffer);
private:
bool LazyInitialize();
media::VideoFramePool result_frame_pool_;
std::unique_ptr<WebGraphicsContext3DProvider> context_provider_;
THREAD_CHECKER(thread_checker_);
};
BackgroundReadback::BackgroundReadback(base::PassKey<BackgroundReadback> key,
ExecutionContext& context)
: Supplement<ExecutionContext>(context),
sync_readback_impl_(base::MakeRefCounted<SyncReadbackThread>()),
worker_task_runner_(base::ThreadPool::CreateSingleThreadTaskRunner(
{base::WithBaseSyncPrimitives()},
base::SingleThreadTaskRunnerThreadMode::DEDICATED)) {}
BackgroundReadback::~BackgroundReadback() {
worker_task_runner_->ReleaseSoon(FROM_HERE, std::move(sync_readback_impl_));
}
const char BackgroundReadback::kSupplementName[] = "BackgroundReadback";
// static
BackgroundReadback* BackgroundReadback::From(ExecutionContext& context) {
BackgroundReadback* supplement =
Supplement<ExecutionContext>::From<BackgroundReadback>(context);
if (!supplement) {
supplement = MakeGarbageCollected<BackgroundReadback>(
base::PassKey<BackgroundReadback>(), context);
Supplement<ExecutionContext>::ProvideTo(context, supplement);
}
return supplement;
}
void BackgroundReadback::ReadbackTextureBackedFrameToMemoryFrame(
scoped_refptr<media::VideoFrame> txt_frame,
ReadbackToFrameDoneCallback result_cb) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(txt_frame);
if (CanUseRgbReadback(*txt_frame)) {
ReadbackRGBTextureBackedFrameToMemory(std::move(txt_frame),
std::move(result_cb));
return;
}
ReadbackOnThread(std::move(txt_frame), std::move(result_cb));
}
void BackgroundReadback::ReadbackTextureBackedFrameToBuffer(
scoped_refptr<media::VideoFrame> txt_frame,
const gfx::Rect& src_rect,
const VideoFrameLayout& dest_layout,
base::span<uint8_t> dest_buffer,
ReadbackDoneCallback done_cb) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(txt_frame);
if (CanUseRgbReadback(*txt_frame)) {
ReadbackRGBTextureBackedFrameToBuffer(txt_frame, src_rect, dest_layout,
dest_buffer, std::move(done_cb));
return;
}
ReadbackOnThread(std::move(txt_frame), src_rect, dest_layout, dest_buffer,
std::move(done_cb));
}
void BackgroundReadback::ReadbackOnThread(
scoped_refptr<media::VideoFrame> txt_frame,
ReadbackToFrameDoneCallback result_cb) {
worker_task_runner_->PostTaskAndReplyWithResult(
FROM_HERE,
ConvertToBaseOnceCallback(
CrossThreadBindOnce(&SyncReadbackThread::ReadbackToFrame,
sync_readback_impl_, std::move(txt_frame))),
std::move(result_cb));
}
void BackgroundReadback::ReadbackOnThread(
scoped_refptr<media::VideoFrame> txt_frame,
const gfx::Rect& src_rect,
const VideoFrameLayout& dest_layout,
base::span<uint8_t> dest_buffer,
ReadbackDoneCallback done_cb) {
worker_task_runner_->PostTaskAndReplyWithResult(
FROM_HERE,
ConvertToBaseOnceCallback(CrossThreadBindOnce(
&SyncReadbackThread::ReadbackToBuffer, sync_readback_impl_,
std::move(txt_frame), src_rect, dest_layout, dest_buffer)),
std::move(done_cb));
}
void BackgroundReadback::ReadbackRGBTextureBackedFrameToMemory(
scoped_refptr<media::VideoFrame> txt_frame,
ReadbackToFrameDoneCallback result_cb) {
DCHECK(CanUseRgbReadback(*txt_frame));
SkImageInfo info = GetImageInfoForFrame(*txt_frame, txt_frame->coded_size());
const auto format = media::VideoPixelFormatFromSkColorType(
info.colorType(), media::IsOpaque(txt_frame->format()));
auto result = result_frame_pool_.CreateFrame(
format, txt_frame->coded_size(), txt_frame->visible_rect(),
txt_frame->natural_size(), txt_frame->timestamp());
auto* ri = GetSharedGpuRasterInterface();
if (!ri || !result) {
base::BindPostTaskToCurrentDefault(std::move(std::move(result_cb)))
.Run(nullptr);
return;
}
TRACE_EVENT_BEGIN("media", "ReadbackRGBTextureBackedFrameToMemory",
perfetto::Track::FromPointer(txt_frame.get()), "timestamp",
txt_frame->timestamp());
base::span<uint8_t> dst_pixels =
result->GetWritableVisiblePlaneData(media::VideoFrame::Plane::kARGB);
int rgba_stide = result->stride(media::VideoFrame::Plane::kARGB);
DCHECK_GT(rgba_stide, 0);
gfx::Point src_point;
auto shared_image = txt_frame->shared_image();
auto origin = shared_image->surface_origin();
std::unique_ptr<gpu::RasterScopedAccess> ri_access =
shared_image->BeginRasterAccess(ri, txt_frame->acquire_sync_token(),
/*readonly=*/true);
gfx::Size texture_size = txt_frame->coded_size();
ri->ReadbackARGBPixelsAsync(
shared_image->mailbox(), shared_image->GetTextureTarget(), origin,
texture_size, src_point, info, base::saturated_cast<GLuint>(rgba_stide),
dst_pixels,
blink::BindOnce(&BackgroundReadback::OnARGBPixelsFrameReadCompleted,
WrapWeakPersistent(this), std::move(result_cb), txt_frame,
std::move(result)));
media::WaitAndReplaceSyncTokenClient client(ri, std::move(ri_access));
txt_frame->UpdateReleaseSyncToken(&client);
}
void BackgroundReadback::OnARGBPixelsFrameReadCompleted(
ReadbackToFrameDoneCallback result_cb,
scoped_refptr<media::VideoFrame> txt_frame,
scoped_refptr<media::VideoFrame> result_frame,
bool success) {
TRACE_EVENT_END("media", perfetto::Track::FromPointer(txt_frame.get()),
"success", success);
if (!success) {
ReadbackOnThread(std::move(txt_frame), std::move(result_cb));
return;
}
auto* ri = GetSharedGpuRasterInterface();
result_frame->set_color_space(txt_frame->ColorSpace());
result_frame->metadata().MergeMetadataFrom(txt_frame->metadata());
result_frame->metadata().ClearTextureFrameMetadata();
std::move(result_cb).Run(ri ? std::move(result_frame) : nullptr);
}
void BackgroundReadback::ReadbackRGBTextureBackedFrameToBuffer(
scoped_refptr<media::VideoFrame> txt_frame,
const gfx::Rect& src_rect,
const VideoFrameLayout& dest_layout,
base::span<uint8_t> dest_buffer,
ReadbackDoneCallback done_cb) {
if (dest_layout.NumPlanes() != 1) {
NOTREACHED()
<< "This method shouldn't be called on anything but RGB frames";
}
auto* ri = GetSharedGpuRasterInterface();
if (!ri) {
base::BindPostTaskToCurrentDefault(std::move(std::move(done_cb)))
.Run(false);
return;
}
uint32_t offset = dest_layout.Offset(0);
uint32_t stride = dest_layout.Stride(0);
base::span<uint8_t> dst_pixels = dest_buffer.subspan(offset);
size_t max_bytes_written = stride * src_rect.height();
if (stride <= 0 || max_bytes_written > dest_buffer.size()) {
DLOG(ERROR) << "Buffer is not sufficiently large for readback";
base::BindPostTaskToCurrentDefault(std::move(std::move(done_cb)))
.Run(false);
return;
}
TRACE_EVENT_BEGIN("media", "ReadbackRGBTextureBackedFrameToBuffer",
perfetto::Track::FromPointer(txt_frame.get()), "timestamp",
txt_frame->timestamp());
SkImageInfo info = GetImageInfoForFrame(*txt_frame, src_rect.size());
gfx::Point src_point = src_rect.origin();
auto shared_image = txt_frame->shared_image();
auto origin = shared_image->surface_origin();
auto ri_access = shared_image->BeginRasterAccess(
ri, txt_frame->acquire_sync_token(), /*readonly=*/true);
gfx::Size texture_size = txt_frame->coded_size();
ri->ReadbackARGBPixelsAsync(
shared_image->mailbox(), shared_image->GetTextureTarget(), origin,
texture_size, src_point, info, base::saturated_cast<GLuint>(stride),
dst_pixels,
blink::BindOnce(&BackgroundReadback::OnARGBPixelsBufferReadCompleted,
WrapWeakPersistent(this), std::move(txt_frame), src_rect,
dest_layout, dest_buffer, std::move(done_cb)));
gpu::RasterScopedAccess::EndAccess(std::move(ri_access));
}
void BackgroundReadback::OnARGBPixelsBufferReadCompleted(
scoped_refptr<media::VideoFrame> txt_frame,
const gfx::Rect& src_rect,
const VideoFrameLayout& dest_layout,
base::span<uint8_t> dest_buffer,
ReadbackDoneCallback done_cb,
bool success) {
TRACE_EVENT_END("media", perfetto::Track::FromPointer(txt_frame.get()),
"success", success);
if (!success) {
ReadbackOnThread(std::move(txt_frame), src_rect, dest_layout, dest_buffer,
std::move(done_cb));
return;
}
if (auto* ri = GetSharedGpuRasterInterface()) {
media::WaitAndReplaceSyncTokenClient client(ri);
txt_frame->UpdateReleaseSyncToken(&client);
} else {
success = false;
}
std::move(done_cb).Run(success);
}
SyncReadbackThread::SyncReadbackThread() {
DETACH_FROM_THREAD(thread_checker_);
}
bool SyncReadbackThread::LazyInitialize() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (context_provider_)
return true;
context_provider_ = CreateRasterGraphicsContextProvider(
KURL("chrome://BackgroundReadback"),
Platform::RasterContextType::kWebCodecsReadback);
if (!context_provider_) {
DLOG(ERROR) << "Can't create context provider.";
return false;
}
if (!context_provider_->BindToCurrentSequence()) {
DLOG(ERROR) << "Can't bind context provider.";
context_provider_ = nullptr;
return false;
}
return true;
}
scoped_refptr<media::VideoFrame> SyncReadbackThread::ReadbackToFrame(
scoped_refptr<media::VideoFrame> frame) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (!LazyInitialize())
return nullptr;
auto* ri = context_provider_->RasterInterface();
return media::ReadbackTextureBackedFrameToMemorySync(*frame, ri,
&result_frame_pool_);
}
bool SyncReadbackThread::ReadbackToBuffer(
scoped_refptr<media::VideoFrame> frame,
const gfx::Rect src_rect,
const VideoFrameLayout dest_layout,
base::span<uint8_t> dest_buffer) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
TRACE_EVENT1("media", "SyncReadbackThread::ReadbackToBuffer", "timestamp",
frame->timestamp());
if (!LazyInitialize() || !frame)
return false;
auto* ri = context_provider_->RasterInterface();
if (!ri)
return false;
for (wtf_size_t i = 0; i < dest_layout.NumPlanes(); i++) {
const gfx::Size sample_size =
media::VideoFrame::SampleSize(dest_layout.Format(), i);
gfx::Rect plane_src_rect = PlaneRect(src_rect, sample_size);
uint8_t* dest_pixels = dest_buffer.subspan(dest_layout.Offset(i)).data();
if (!media::ReadbackTexturePlaneToMemorySync(*frame, i, plane_src_rect,
dest_pixels,
dest_layout.Stride(i), ri)) {
// It's possible to fail after copying some but not all planes, leaving
// the output buffer in a corrupt state D:
return false;
}
}
return true;
}
} // namespace blink