blob: a08898b00435915105869232f23ac7c4f0926388 [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/viz/service/display_embedder/skia_output_surface_impl.h"
#include "base/callback_helpers.h"
#include "base/synchronization/waitable_event.h"
#include "base/threading/thread_task_runner_handle.h"
#include "components/viz/common/frame_sinks/begin_frame_source.h"
#include "components/viz/common/frame_sinks/copy_output_request.h"
#include "components/viz/common/resources/resource_format_utils.h"
#include "components/viz/service/display/output_surface_client.h"
#include "components/viz/service/display/output_surface_frame.h"
#include "components/viz/service/display/resource_metadata.h"
#include "components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.h"
#include "components/viz/service/gl/gpu_service_impl.h"
#include "gpu/command_buffer/common/swap_buffers_complete_params.h"
#include "gpu/command_buffer/service/scheduler.h"
#include "ui/gl/gl_bindings.h"
#include "ui/gl/gl_context.h"
#include "ui/gl/gl_gl_api_implementation.h"
namespace viz {
namespace {
template <typename... Args>
void PostAsyncTask(
const scoped_refptr<base::SingleThreadTaskRunner>& task_runner,
const base::RepeatingCallback<void(Args...)>& callback,
Args... args) {
task_runner->PostTask(FROM_HERE, base::BindOnce(callback, args...));
}
template <typename... Args>
base::RepeatingCallback<void(Args...)> CreateSafeCallback(
const scoped_refptr<base::SingleThreadTaskRunner>& task_runner,
const base::RepeatingCallback<void(Args...)>& callback) {
return base::BindRepeating(&PostAsyncTask<Args...>, task_runner, callback);
}
} // namespace
// A helper class for fullfilling promise image on the GPU thread.
template <class FullfillContextType>
class SkiaOutputSurfaceImpl::PromiseTextureHelper {
public:
using HelperType = PromiseTextureHelper<FullfillContextType>;
PromiseTextureHelper(base::WeakPtr<SkiaOutputSurfaceImplOnGpu> impl_on_gpu,
FullfillContextType context)
: impl_on_gpu_(impl_on_gpu), context_(std::move(context)) {}
~PromiseTextureHelper() = default;
static sk_sp<SkImage> MakePromiseSkImage(
SkiaOutputSurfaceImpl* impl,
SkDeferredDisplayListRecorder* recorder,
const GrBackendFormat& backend_format,
gfx::Size size,
GrMipMapped mip_mapped,
GrSurfaceOrigin origin,
SkColorType color_type,
SkAlphaType alpha_type,
sk_sp<SkColorSpace> color_space,
FullfillContextType context) {
DCHECK_CALLED_ON_VALID_THREAD(impl->thread_checker_);
auto helper = std::make_unique<HelperType>(impl->impl_on_gpu_->weak_ptr(),
std::move(context));
auto image = recorder->makePromiseTexture(
backend_format, size.width(), size.height(), mip_mapped, origin,
color_type, alpha_type, color_space, HelperType::Fullfill,
HelperType::Release, HelperType::Done, helper.get());
if (image) {
helper->Init(impl);
helper.release();
}
return image;
}
private:
void Init(SkiaOutputSurfaceImpl* impl);
static void Fullfill(void* texture_context,
GrBackendTexture* backend_texture) {
DCHECK(texture_context);
auto* helper = static_cast<HelperType*>(texture_context);
// The fullfill is always called by SkiaOutputSurfaceImplOnGpu::SwapBuffers
// or SkiaOutputSurfaceImplOnGpu::FinishPaintRenderPass, so impl_on_gpu_
// should be always valid.
DCHECK(helper->impl_on_gpu_);
helper->impl_on_gpu_->FullfillPromiseTexture(helper->context_,
backend_texture);
}
static void Release(void* texture_context) { DCHECK(texture_context); }
static void Done(void* texture_context) {
DCHECK(texture_context);
std::unique_ptr<HelperType> helper(
static_cast<HelperType*>(texture_context));
}
base::WeakPtr<SkiaOutputSurfaceImplOnGpu> impl_on_gpu_;
// The data for calling the fullfill methods in SkiaOutputSurfaceImpl.
FullfillContextType context_;
DISALLOW_COPY_AND_ASSIGN(PromiseTextureHelper);
};
template <class T>
void SkiaOutputSurfaceImpl::PromiseTextureHelper<T>::Init(
SkiaOutputSurfaceImpl* impl) {}
// For YUVResourceMetadata, we need to record the |context_| pointer in
// |impl->yuv_resource_metadatas_|, because we have to create SkImage from YUV
// textures before drawing the ddl to a SKSurface.
// TODO(penghuang): Remove this hack when Skia supports drawing YUV textures
// directly.
template <>
void SkiaOutputSurfaceImpl::PromiseTextureHelper<YUVResourceMetadata>::Init(
SkiaOutputSurfaceImpl* impl) {
impl->yuv_resource_metadatas_.push_back(&context_);
}
SkiaOutputSurfaceImpl::SkiaOutputSurfaceImpl(
GpuServiceImpl* gpu_service,
gpu::SurfaceHandle surface_handle,
SyntheticBeginFrameSource* synthetic_begin_frame_source)
: gpu_service_(gpu_service),
surface_handle_(surface_handle),
synthetic_begin_frame_source_(synthetic_begin_frame_source),
weak_ptr_factory_(this) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
}
SkiaOutputSurfaceImpl::~SkiaOutputSurfaceImpl() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
recorder_.reset();
// Use GPU scheduler to release impl_on_gpu_ on the GPU thread, so all
// scheduled tasks for the impl_on_gpu_ will be executed, before releasing
// it. The GPU thread is the main thread of the viz process. It outlives the
// compositor thread. We don't need worry about it for now.
auto sequence_id = gpu_service_->skia_output_surface_sequence_id();
auto callback =
base::BindOnce([](std::unique_ptr<SkiaOutputSurfaceImplOnGpu>) {},
std::move(impl_on_gpu_));
gpu_service_->scheduler()->ScheduleTask(gpu::Scheduler::Task(
sequence_id, std::move(callback), std::vector<gpu::SyncToken>()));
}
void SkiaOutputSurfaceImpl::BindToClient(OutputSurfaceClient* client) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(client);
DCHECK(!client_);
client_ = client;
weak_ptr_ = weak_ptr_factory_.GetWeakPtr();
client_thread_task_runner_ = base::ThreadTaskRunnerHandle::Get();
base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL,
base::WaitableEvent::InitialState::NOT_SIGNALED);
auto sequence_id = gpu_service_->skia_output_surface_sequence_id();
auto callback = base::BindOnce(&SkiaOutputSurfaceImpl::InitializeOnGpuThread,
base::Unretained(this), &event);
gpu_service_->scheduler()->ScheduleTask(gpu::Scheduler::Task(
sequence_id, std::move(callback), std::vector<gpu::SyncToken>()));
event.Wait();
}
void SkiaOutputSurfaceImpl::EnsureBackbuffer() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
NOTIMPLEMENTED();
}
void SkiaOutputSurfaceImpl::DiscardBackbuffer() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
NOTIMPLEMENTED();
}
void SkiaOutputSurfaceImpl::BindFramebuffer() {
// TODO(penghuang): remove this method when GLRenderer is removed.
}
void SkiaOutputSurfaceImpl::SetDrawRectangle(const gfx::Rect& draw_rectangle) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
NOTIMPLEMENTED();
}
void SkiaOutputSurfaceImpl::Reshape(const gfx::Size& size,
float device_scale_factor,
const gfx::ColorSpace& color_space,
bool has_alpha,
bool use_stencil) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
recorder_.reset();
SkSurfaceCharacterization* characterization = nullptr;
base::Optional<base::WaitableEvent> event;
if (characterization_.isValid()) {
characterization_ =
characterization_.createResized(size.width(), size.height());
} else {
characterization = &characterization_;
// TODO(penghuang): avoid blocking compositor thread.
// We don't have a valid surface characterization, so we have to wait
// until reshape is finished on Gpu thread.
event.emplace(base::WaitableEvent::ResetPolicy::MANUAL,
base::WaitableEvent::InitialState::NOT_SIGNALED);
}
auto sequence_id = gpu_service_->skia_output_surface_sequence_id();
// impl_on_gpu_ is released on the GPU thread by a posted task from
// SkiaOutputSurfaceImpl::dtor. So it is safe to use base::Unretained.
auto callback =
base::BindOnce(&SkiaOutputSurfaceImplOnGpu::Reshape,
base::Unretained(impl_on_gpu_.get()), size,
device_scale_factor, color_space, has_alpha, use_stencil,
characterization, base::OptionalOrNullptr(event));
gpu_service_->scheduler()->ScheduleTask(gpu::Scheduler::Task(
sequence_id, std::move(callback), std::vector<gpu::SyncToken>()));
if (event)
event->Wait();
RecreateRecorder();
}
void SkiaOutputSurfaceImpl::SwapBuffers(OutputSurfaceFrame frame) {
NOTREACHED();
}
uint32_t SkiaOutputSurfaceImpl::GetFramebufferCopyTextureFormat() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
return GL_RGB;
}
OverlayCandidateValidator* SkiaOutputSurfaceImpl::GetOverlayCandidateValidator()
const {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
return nullptr;
}
bool SkiaOutputSurfaceImpl::IsDisplayedAsOverlayPlane() const {
return false;
}
unsigned SkiaOutputSurfaceImpl::GetOverlayTextureId() const {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
return 0;
}
gfx::BufferFormat SkiaOutputSurfaceImpl::GetOverlayBufferFormat() const {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
return gfx::BufferFormat::RGBX_8888;
}
bool SkiaOutputSurfaceImpl::HasExternalStencilTest() const {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
return false;
}
void SkiaOutputSurfaceImpl::ApplyExternalStencil() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
}
#if BUILDFLAG(ENABLE_VULKAN)
gpu::VulkanSurface* SkiaOutputSurfaceImpl::GetVulkanSurface() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
return nullptr;
}
#endif
unsigned SkiaOutputSurfaceImpl::UpdateGpuFence() {
return 0;
}
SkCanvas* SkiaOutputSurfaceImpl::BeginPaintCurrentFrame() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(recorder_);
DCHECK_EQ(current_render_pass_id_, 0u);
return recorder_->getCanvas();
}
gpu::SyncToken SkiaOutputSurfaceImpl::FinishPaintCurrentFrame() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(recorder_);
gpu::SyncToken sync_token(gpu::CommandBufferNamespace::VIZ_OUTPUT_SURFACE,
impl_on_gpu_->command_buffer_id(),
++sync_fence_release_);
sync_token.SetVerifyFlush();
auto ddl = recorder_->detach();
DCHECK(ddl);
recorder_.reset();
auto sequence_id = gpu_service_->skia_output_surface_sequence_id();
// impl_on_gpu_ is released on the GPU thread by a posted task from
// SkiaOutputSurfaceImpl::dtor. So it is safe to use base::Unretained.
auto callback =
base::BindOnce(&SkiaOutputSurfaceImplOnGpu::FinishPaintCurrentFrame,
base::Unretained(impl_on_gpu_.get()), std::move(ddl),
std::move(yuv_resource_metadatas_), sync_fence_release_);
gpu_service_->scheduler()->ScheduleTask(gpu::Scheduler::Task(
sequence_id, std::move(callback), std::move(resource_sync_tokens_)));
return sync_token;
}
sk_sp<SkImage> SkiaOutputSurfaceImpl::MakePromiseSkImage(
ResourceMetadata metadata) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(recorder_);
// Convert internal format from GLES2 to platform GL.
const auto* version_info = gpu_service_->context_for_skia()->GetVersionInfo();
metadata.backend_format = GrBackendFormat::MakeGL(
gl::GetInternalFormat(version_info,
*metadata.backend_format.getGLFormat()),
*metadata.backend_format.getGLTarget());
DCHECK(!metadata.mailbox.IsZero());
resource_sync_tokens_.push_back(metadata.sync_token);
return PromiseTextureHelper<ResourceMetadata>::MakePromiseSkImage(
this, &recorder_.value(), metadata.backend_format, metadata.size,
metadata.mip_mapped, metadata.origin, metadata.color_type,
metadata.alpha_type, metadata.color_space, std::move(metadata));
}
sk_sp<SkImage> SkiaOutputSurfaceImpl::MakePromiseSkImageFromYUV(
std::vector<ResourceMetadata> metadatas,
SkYUVColorSpace yuv_color_space) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(recorder_);
DCHECK(metadatas.size() == 2 || metadatas.size() == 3);
// TODO(penghuang): Create SkImage from YUV textures directly when it is
// supported by Skia.
YUVResourceMetadata yuv_metadata(std::move(metadatas), yuv_color_space);
// Convert internal format from GLES2 to platform GL.
const auto* version_info = gpu_service_->context_for_skia()->GetVersionInfo();
auto backend_format = GrBackendFormat::MakeGL(
gl::GetInternalFormat(version_info, GL_BGRA8_EXT), GL_TEXTURE_2D);
return PromiseTextureHelper<YUVResourceMetadata>::MakePromiseSkImage(
this, &recorder_.value(), backend_format, yuv_metadata.size(),
GrMipMapped::kNo, kTopLeft_GrSurfaceOrigin, kBGRA_8888_SkColorType,
kPremul_SkAlphaType, nullptr /* color_space */, std::move(yuv_metadata));
}
void SkiaOutputSurfaceImpl::SkiaSwapBuffers(OutputSurfaceFrame frame) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(!recorder_);
RecreateRecorder();
auto sequence_id = gpu_service_->skia_output_surface_sequence_id();
// impl_on_gpu_ is released on the GPU thread by a posted task from
// SkiaOutputSurfaceImpl::dtor. So it is safe to use base::Unretained.
auto callback =
base::BindOnce(&SkiaOutputSurfaceImplOnGpu::SwapBuffers,
base::Unretained(impl_on_gpu_.get()), std::move(frame));
gpu_service_->scheduler()->ScheduleTask(gpu::Scheduler::Task(
sequence_id, std::move(callback), std::vector<gpu::SyncToken>()));
}
SkCanvas* SkiaOutputSurfaceImpl::BeginPaintRenderPass(
const RenderPassId& id,
const gfx::Size& surface_size,
ResourceFormat format,
bool mipmap) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(gpu_service_->gr_context());
DCHECK(!current_render_pass_id_);
DCHECK(!offscreen_surface_recorder_);
DCHECK(resource_sync_tokens_.empty());
current_render_pass_id_ = id;
auto gr_context_thread_safe = gpu_service_->gr_context()->threadSafeProxy();
constexpr uint32_t flags = 0;
// LegacyFontHost will get LCD text and skia figures out what type to use.
SkSurfaceProps surface_props(flags, SkSurfaceProps::kLegacyFontHost_InitType);
int msaa_sample_count = 0;
SkColorType color_type =
ResourceFormatToClosestSkColorType(true /*gpu_compositing */, format);
SkImageInfo image_info =
SkImageInfo::Make(surface_size.width(), surface_size.height(), color_type,
kPremul_SkAlphaType, nullptr /* color_space */);
// TODO(penghuang): Figure out how to choose the right size.
constexpr size_t kCacheMaxResourceBytes = 90 * 1024 * 1024;
const auto* version_info = gpu_service_->context_for_skia()->GetVersionInfo();
unsigned int texture_storage_format = TextureStorageFormat(format);
auto backend_format = GrBackendFormat::MakeGL(
gl::GetInternalFormat(version_info, texture_storage_format),
GL_TEXTURE_2D);
auto characterization = gr_context_thread_safe->createCharacterization(
kCacheMaxResourceBytes, image_info, backend_format, msaa_sample_count,
kTopLeft_GrSurfaceOrigin, surface_props, mipmap);
DCHECK(characterization.isValid());
offscreen_surface_recorder_.emplace(characterization);
return offscreen_surface_recorder_->getCanvas();
}
gpu::SyncToken SkiaOutputSurfaceImpl::FinishPaintRenderPass() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(gpu_service_->gr_context());
DCHECK(current_render_pass_id_);
DCHECK(offscreen_surface_recorder_);
gpu::SyncToken sync_token(gpu::CommandBufferNamespace::VIZ_OUTPUT_SURFACE,
impl_on_gpu_->command_buffer_id(),
++sync_fence_release_);
sync_token.SetVerifyFlush();
auto ddl = offscreen_surface_recorder_->detach();
offscreen_surface_recorder_.reset();
DCHECK(ddl);
auto sequence_id = gpu_service_->skia_output_surface_sequence_id();
// impl_on_gpu_ is released on the GPU thread by a posted task from
// SkiaOutputSurfaceImpl::dtor. So it is safe to use base::Unretained.
auto callback = base::BindOnce(
&SkiaOutputSurfaceImplOnGpu::FinishPaintRenderPass,
base::Unretained(impl_on_gpu_.get()), current_render_pass_id_,
std::move(ddl), std::move(yuv_resource_metadatas_), sync_fence_release_);
gpu_service_->scheduler()->ScheduleTask(gpu::Scheduler::Task(
sequence_id, std::move(callback), std::move(resource_sync_tokens_)));
current_render_pass_id_ = 0;
return sync_token;
}
sk_sp<SkImage> SkiaOutputSurfaceImpl::MakePromiseSkImageFromRenderPass(
const RenderPassId& id,
const gfx::Size& size,
ResourceFormat format,
bool mipmap) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(recorder_);
// Convert internal format from GLES2 to platform GL.
const auto* version_info = gpu_service_->context_for_skia()->GetVersionInfo();
unsigned int texture_storage_format = TextureStorageFormat(format);
auto backend_format = GrBackendFormat::MakeGL(
gl::GetInternalFormat(version_info, texture_storage_format),
GL_TEXTURE_2D);
SkColorType color_type =
ResourceFormatToClosestSkColorType(true /*gpu_compositing */, format);
return PromiseTextureHelper<RenderPassId>::MakePromiseSkImage(
this, &recorder_.value(), backend_format, size,
mipmap ? GrMipMapped::kYes : GrMipMapped::kNo, kTopLeft_GrSurfaceOrigin,
color_type, kPremul_SkAlphaType, nullptr /* color_space */, id);
}
void SkiaOutputSurfaceImpl::RemoveRenderPassResource(
std::vector<RenderPassId> ids) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(!ids.empty());
auto sequence_id = gpu_service_->skia_output_surface_sequence_id();
// impl_on_gpu_ is released on the GPU thread by a posted task from
// SkiaOutputSurfaceImpl::dtor. So it is safe to use base::Unretained.
auto callback =
base::BindOnce(&SkiaOutputSurfaceImplOnGpu::RemoveRenderPassResource,
base::Unretained(impl_on_gpu_.get()), std::move(ids));
gpu_service_->scheduler()->ScheduleTask(gpu::Scheduler::Task(
sequence_id, std::move(callback), std::vector<gpu::SyncToken>()));
}
void SkiaOutputSurfaceImpl::CopyOutput(
RenderPassId id,
const gfx::Rect& copy_rect,
std::unique_ptr<CopyOutputRequest> request) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
auto sequence_id = gpu_service_->skia_output_surface_sequence_id();
auto callback = base::BindOnce(&SkiaOutputSurfaceImplOnGpu::CopyOutput,
base::Unretained(impl_on_gpu_.get()), id,
copy_rect, std::move(request));
gpu_service_->scheduler()->ScheduleTask(gpu::Scheduler::Task(
sequence_id, std::move(callback), std::vector<gpu::SyncToken>()));
}
void SkiaOutputSurfaceImpl::InitializeOnGpuThread(base::WaitableEvent* event) {
base::ScopedClosureRunner scoped_runner(
base::BindOnce(&base::WaitableEvent::Signal, base::Unretained(event)));
auto did_swap_buffer_complete_callback = base::BindRepeating(
&SkiaOutputSurfaceImpl::DidSwapBuffersComplete, weak_ptr_);
auto buffer_presented_callback =
base::BindRepeating(&SkiaOutputSurfaceImpl::BufferPresented, weak_ptr_);
impl_on_gpu_ = std::make_unique<SkiaOutputSurfaceImplOnGpu>(
gpu_service_, surface_handle_,
CreateSafeCallback(client_thread_task_runner_,
did_swap_buffer_complete_callback),
CreateSafeCallback(client_thread_task_runner_,
buffer_presented_callback));
capabilities_ = impl_on_gpu_->capabilities();
}
void SkiaOutputSurfaceImpl::RecreateRecorder() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(characterization_.isValid());
recorder_.emplace(characterization_);
// TODO(penghuang): remove the unnecessary getCanvas() call, when the
// recorder crash is fixed in skia.
recorder_->getCanvas();
}
void SkiaOutputSurfaceImpl::DidSwapBuffersComplete(
gpu::SwapBuffersCompleteParams params,
const gfx::Size& pixel_size) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(client_);
if (!params.texture_in_use_responses.empty())
client_->DidReceiveTextureInUseResponses(params.texture_in_use_responses);
if (!params.ca_layer_params.is_empty)
client_->DidReceiveCALayerParams(params.ca_layer_params);
client_->DidReceiveSwapBuffersAck();
if (needs_swap_size_notifications_)
client_->DidSwapWithSize(pixel_size);
}
void SkiaOutputSurfaceImpl::BufferPresented(
const gfx::PresentationFeedback& feedback) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(client_);
client_->DidReceivePresentationFeedback(feedback);
if (synthetic_begin_frame_source_ &&
feedback.flags & gfx::PresentationFeedback::kVSync) {
// TODO(brianderson): We should not be receiving 0 intervals.
synthetic_begin_frame_source_->OnUpdateVSyncParameters(
feedback.timestamp, feedback.interval.is_zero()
? BeginFrameArgs::DefaultInterval()
: feedback.interval);
}
}
void SkiaOutputSurfaceImpl::SetNeedsSwapSizeNotifications(
bool needs_swap_size_notifications) {
needs_swap_size_notifications_ = needs_swap_size_notifications;
}
} // namespace viz