blob: 6e01576b1581281c758951d4308a0ded6a1f1a63 [file] [log] [blame]
// Copyright 2017 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/gl_renderer_copier.h"
#include <cstring>
#include <utility>
#include "base/bind.h"
#include "base/process/memory.h"
#include "base/stl_util.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "components/viz/common/frame_sinks/copy_output_request.h"
#include "components/viz/common/frame_sinks/copy_output_result.h"
#include "components/viz/common/frame_sinks/copy_output_util.h"
#include "components/viz/common/gl_i420_converter.h"
#include "components/viz/common/gl_scaler.h"
#include "components/viz/common/gpu/context_provider.h"
#include "components/viz/service/display/texture_deleter.h"
#include "gpu/GLES2/gl2extchromium.h"
#include "gpu/command_buffer/client/context_support.h"
#include "gpu/command_buffer/client/gles2_interface.h"
#include "gpu/command_buffer/client/shared_image_interface.h"
#include "gpu/command_buffer/common/mailbox.h"
#include "gpu/command_buffer/common/shared_image_usage.h"
#include "gpu/command_buffer/common/sync_token.h"
#include "third_party/libyuv/include/libyuv/planar_functions.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkImageInfo.h"
#include "ui/gfx/geometry/size.h"
// Syntactic sugar to DCHECK that two sizes are equal.
#define DCHECK_SIZE_EQ(a, b) \
DCHECK((a) == (b)) << #a " != " #b ": " << (a).ToString() \
<< " != " << (b).ToString()
namespace viz {
using ResultFormat = CopyOutputRequest::ResultFormat;
namespace {
constexpr int kRGBABytesPerPixel = 4;
// Returns the source property of the |request|, if it is set. Otherwise,
// returns an empty token. This is needed because CopyOutputRequest will crash
// if source() is called when !has_source().
base::UnguessableToken SourceOf(const CopyOutputRequest& request) {
return request.has_source() ? request.source() : base::UnguessableToken();
}
// Creates a new texture, binds it to the GL_TEXTURE_2D target, and initializes
// its default parameters.
GLuint CreateDefaultTexture2D(gpu::gles2::GLES2Interface* gl) {
GLuint result = 0;
gl->GenTextures(1, &result);
gl->BindTexture(GL_TEXTURE_2D, result);
gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
return result;
}
// Creates or re-creates a texture, only if needed, to ensure a texture of the
// given |required| size is defined. |texture| and |size| are I/O parameters,
// read to determine what to do and updated if any changes are made.
void EnsureTextureDefinedWithSize(gpu::gles2::GLES2Interface* gl,
const gfx::Size& required,
GLuint* texture,
gfx::Size* size) {
if (*texture != 0 && *size == required)
return;
if (*texture == 0) {
*texture = CreateDefaultTexture2D(gl);
} else {
gl->BindTexture(GL_TEXTURE_2D, *texture);
}
gl->TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, required.width(), required.height(),
0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
*size = required;
}
// Sets the fields of |params| to scale/transform the image in the source
// framebuffer to meet the requirements of the |request|.
void PopulateScalerParameters(const CopyOutputRequest& request,
const gfx::ColorSpace& source_color_space,
const gfx::ColorSpace& output_color_space,
bool flipped_source,
GLScaler::Parameters* params) {
params->scale_from = request.scale_from();
params->scale_to = request.scale_to();
params->source_color_space = source_color_space;
params->output_color_space = output_color_space;
// For downscaling, use the GOOD quality setting (appropriate for
// thumbnailing); and, for upscaling, use the BEST quality.
const bool is_downscale_in_both_dimensions =
request.scale_to().x() < request.scale_from().x() &&
request.scale_to().y() < request.scale_from().y();
params->quality = is_downscale_in_both_dimensions
? GLScaler::Parameters::Quality::GOOD
: GLScaler::Parameters::Quality::BEST;
params->is_flipped_source = flipped_source;
}
} // namespace
GLRendererCopier::GLRendererCopier(ContextProvider* context_provider,
TextureDeleter* texture_deleter)
: context_provider_(context_provider), texture_deleter_(texture_deleter) {}
GLRendererCopier::~GLRendererCopier() {
for (auto& entry : cache_)
entry.second->Free(context_provider_->ContextGL());
}
void GLRendererCopier::CopyFromTextureOrFramebuffer(
std::unique_ptr<CopyOutputRequest> request,
const copy_output::RenderPassGeometry& geometry,
GLenum internal_format,
GLuint framebuffer_texture,
const gfx::Size& framebuffer_texture_size,
bool flipped_source,
const gfx::ColorSpace& framebuffer_color_space) {
const gfx::Rect& result_rect = geometry.result_selection;
// If we can't convert |color_space| to a SkColorSpace for SkBitmap copy
// requests (e.g. PIECEWISE_HDR), fallback to a color transform to sRGB
// before returning the copy result.
gfx::ColorSpace dest_color_space = framebuffer_color_space;
if (!framebuffer_color_space.ToSkColorSpace() &&
request->result_format() == ResultFormat::RGBA_BITMAP) {
dest_color_space = gfx::ColorSpace::CreateSRGB();
}
// Fast-Path: If no transformation is necessary and no new textures need to be
// generated, read-back directly from the currently-bound framebuffer.
if (request->result_format() == ResultFormat::RGBA_BITMAP &&
framebuffer_color_space == dest_color_space && !request->is_scaled()) {
StartReadbackFromFramebuffer(std::move(request), geometry.readback_offset,
flipped_source, false, result_rect,
dest_color_space);
return;
}
gfx::Rect sampling_rect = geometry.sampling_bounds;
const base::UnguessableToken requester = SourceOf(*request);
std::unique_ptr<ReusableThings> things =
TakeReusableThingsOrCreate(requester);
// Determine the source texture: This is either the one attached to the
// framebuffer, or a copy made from the framebuffer. Its format will be the
// same as |internal_format|.
//
// TODO(crbug/767221): All of this (including some texture copies) wouldn't be
// necessary if we could query whether the currently-bound framebuffer has a
// texture attached to it, and just source from that texture directly (i.e.,
// using glGetFramebufferAttachmentParameteriv() and
// glGetTexLevelParameteriv(GL_TEXTURE_WIDTH/HEIGHT)).
GLuint source_texture;
gfx::Size source_texture_size;
if (framebuffer_texture != 0) {
source_texture = framebuffer_texture;
source_texture_size = framebuffer_texture_size;
} else {
auto* const gl = context_provider_->ContextGL();
if (things->fb_copy_texture == 0) {
things->fb_copy_texture = CreateDefaultTexture2D(gl);
things->fb_copy_texture_internal_format = static_cast<GLenum>(GL_NONE);
things->fb_copy_texture_size = gfx::Size();
} else {
gl->BindTexture(GL_TEXTURE_2D, things->fb_copy_texture);
}
if (things->fb_copy_texture_internal_format == internal_format &&
things->fb_copy_texture_size == sampling_rect.size()) {
// Copy the framebuffer pixels without redefining the texture.
gl->CopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, sampling_rect.x(),
sampling_rect.y(), sampling_rect.width(),
sampling_rect.height());
} else {
// Copy the framebuffer pixels into a newly-defined texture.
gl->CopyTexImage2D(GL_TEXTURE_2D, 0, internal_format, sampling_rect.x(),
sampling_rect.y(), sampling_rect.width(),
sampling_rect.height(), 0);
things->fb_copy_texture_internal_format = internal_format;
things->fb_copy_texture_size = sampling_rect.size();
}
source_texture = things->fb_copy_texture;
source_texture_size = sampling_rect.size();
sampling_rect.set_origin(gfx::Point());
}
// Revert the Y-flipping of the sampling rect coordinates for GLScaler, which
// always assumes the source offset is assuming a origin-at-top-left
// coordinate space.
if (flipped_source) {
sampling_rect.set_y(source_texture_size.height() - sampling_rect.bottom());
}
switch (request->result_format()) {
case ResultFormat::RGBA_BITMAP:
EnsureTextureDefinedWithSize(context_provider_->ContextGL(),
result_rect.size(), &things->result_texture,
&things->result_texture_size);
RenderResultTexture(*request, flipped_source, framebuffer_color_space,
dest_color_space, source_texture, source_texture_size,
sampling_rect, result_rect, things->result_texture,
things.get());
StartReadbackFromTexture(std::move(request), result_rect,
dest_color_space, things.get());
break;
case ResultFormat::RGBA_TEXTURE:
RenderAndSendTextureResult(std::move(request), flipped_source,
framebuffer_color_space, dest_color_space,
source_texture, source_texture_size,
sampling_rect, result_rect, things.get());
break;
case ResultFormat::I420_PLANES:
// The optimized single-copy path, provided by GLPixelBufferI420Result,
// requires that the result be accessed via a task in the same task runner
// sequence as the GLRendererCopier. Since I420_PLANES requests are meant
// to be VIZ-internal, this is an acceptable limitation to enforce.
if (!request->SendsResultsInCurrentSequence()) {
request->set_result_task_runner(base::SequencedTaskRunnerHandle::Get());
}
const gfx::Rect aligned_rect = RenderI420Textures(
*request, flipped_source, framebuffer_color_space, source_texture,
source_texture_size, sampling_rect, result_rect, things.get());
StartI420ReadbackFromTextures(std::move(request), aligned_rect,
result_rect, things.get());
break;
}
StashReusableThingsOrDelete(requester, std::move(things));
}
void GLRendererCopier::FreeUnusedCachedResources() {
++purge_counter_;
// Purge all cache entries that should no longer be kept alive, freeing any
// resources they held.
const auto IsTooOld = [this](const decltype(cache_)::value_type& entry) {
return static_cast<int32_t>(purge_counter_ -
entry.second->purge_count_at_last_use) >=
kKeepalivePeriod;
};
for (auto& entry : cache_) {
if (IsTooOld(entry))
entry.second->Free(context_provider_->ContextGL());
}
base::EraseIf(cache_, IsTooOld);
}
void GLRendererCopier::RenderResultTexture(
const CopyOutputRequest& request,
bool flipped_source,
const gfx::ColorSpace& source_color_space,
const gfx::ColorSpace& dest_color_space,
GLuint source_texture,
const gfx::Size& source_texture_size,
const gfx::Rect& sampling_rect,
const gfx::Rect& result_rect,
GLuint result_texture,
ReusableThings* things) {
DCHECK_NE(request.result_format(), ResultFormat::I420_PLANES);
GLScaler::Parameters params;
PopulateScalerParameters(request, source_color_space, dest_color_space,
flipped_source, &params);
if (request.result_format() == ResultFormat::RGBA_BITMAP) {
// Render the result in top-down row order, and swizzle, within the GPU so
// these things don't have to be done, less efficiently, on the CPU later.
params.flip_output = flipped_source;
params.swizzle[0] =
ShouldSwapRedAndBlueForBitmapReadback() ? GL_BGRA_EXT : GL_RGBA;
} else {
// Texture results are always in bottom-up row order.
DCHECK_EQ(request.result_format(), ResultFormat::RGBA_TEXTURE);
params.flip_output = !flipped_source;
DCHECK_EQ(params.swizzle[0], static_cast<GLenum>(GL_RGBA));
}
if (!things->scaler)
things->scaler = std::make_unique<GLScaler>(context_provider_);
if (!GLScaler::ParametersAreEquivalent(params, things->scaler->params())) {
const bool is_configured = things->scaler->Configure(params);
// GLRendererCopier should never use illegal or unsupported options, nor
// be using GLScaler with an invalid GL context.
DCHECK(is_configured);
}
const bool success = things->scaler->Scale(
source_texture, source_texture_size, sampling_rect.OffsetFromOrigin(),
result_texture, result_rect);
DCHECK(success);
}
gfx::Rect GLRendererCopier::RenderI420Textures(
const CopyOutputRequest& request,
bool flipped_source,
const gfx::ColorSpace& source_color_space,
GLuint source_texture,
const gfx::Size& source_texture_size,
const gfx::Rect& sampling_rect,
const gfx::Rect& result_rect,
ReusableThings* things) {
DCHECK_EQ(request.result_format(), ResultFormat::I420_PLANES);
// Compute required Y/U/V texture sizes and re-define them, if necessary. See
// class comments for GLI420Converter for an explanation of how planar data is
// packed into RGBA textures.
const gfx::Rect aligned_rect = GLI420Converter::ToAlignedRect(result_rect);
const gfx::Size required_luma_size(aligned_rect.width() / kRGBABytesPerPixel,
aligned_rect.height());
const gfx::Size required_chroma_size(required_luma_size.width() / 2,
required_luma_size.height() / 2);
gfx::Size u_texture_size(things->y_texture_size.width() / 2,
things->y_texture_size.height() / 2);
gfx::Size v_texture_size = u_texture_size;
auto* const gl = context_provider_->ContextGL();
EnsureTextureDefinedWithSize(gl, required_luma_size, &things->yuv_textures[0],
&things->y_texture_size);
EnsureTextureDefinedWithSize(gl, required_chroma_size,
&things->yuv_textures[1], &u_texture_size);
EnsureTextureDefinedWithSize(gl, required_chroma_size,
&things->yuv_textures[2], &v_texture_size);
GLI420Converter::Parameters params;
PopulateScalerParameters(request, source_color_space,
gfx::ColorSpace::CreateREC709(), flipped_source,
&params);
// I420 readback assumes content is in top-down row order. Also, set the
// output swizzle to match the readback format so that image bitmaps don't
// have to be byte-order-swizzled on the CPU later.
params.flip_output = flipped_source;
params.swizzle[0] = GetOptimalReadbackFormat();
if (!things->i420_converter) {
things->i420_converter =
std::make_unique<GLI420Converter>(context_provider_);
}
if (!GLI420Converter::ParametersAreEquivalent(
params, things->i420_converter->params())) {
const bool is_configured = things->i420_converter->Configure(params);
// GLRendererCopier should never use illegal or unsupported options, nor
// be using GLI420Converter with an invalid GL context.
DCHECK(is_configured);
}
const bool success = things->i420_converter->Convert(
source_texture, source_texture_size, sampling_rect.OffsetFromOrigin(),
aligned_rect, things->yuv_textures.data());
DCHECK(success);
return aligned_rect;
}
void GLRendererCopier::StartReadbackFromTexture(
std::unique_ptr<CopyOutputRequest> request,
const gfx::Rect& result_rect,
const gfx::ColorSpace& color_space,
ReusableThings* things) {
DCHECK_EQ(request->result_format(), ResultFormat::RGBA_BITMAP);
auto* const gl = context_provider_->ContextGL();
if (things->readback_framebuffer == 0) {
gl->GenFramebuffers(1, &things->readback_framebuffer);
}
gl->BindFramebuffer(GL_FRAMEBUFFER, things->readback_framebuffer);
gl->FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
things->result_texture, 0);
StartReadbackFromFramebuffer(std::move(request), gfx::Vector2d(), false,
ShouldSwapRedAndBlueForBitmapReadback(),
result_rect, color_space);
}
namespace {
// This is the type of CopyOutputResult we send for RGBA readback. The
// constructor is called during on GLRendererCopier::FinishReadPixelsWorkflow(),
// thus it always have access to the GLContext. The ReadRGBAPlane and destructor
// are called asynchronously, and thus might not have access to the GLContext if
// it has been destroyed in the meantime. We use the WeakPtr to the
// GLRendererCopier as an indicator that the GLContext is still alive. If the
// access to the GLContext is lost, we treat the copy output as failed.
class GLPixelBufferRGBAResult final : public CopyOutputResult {
public:
GLPixelBufferRGBAResult(const gfx::Rect& result_rect,
const gfx::ColorSpace& color_space,
base::WeakPtr<GLRendererCopier> copier_weak_ptr,
ContextProvider* context_provider,
GLuint transfer_buffer,
bool is_upside_down,
bool swap_red_and_blue)
: CopyOutputResult(CopyOutputResult::Format::RGBA_BITMAP,
result_rect,
/*needs_lock_for_bitmap=*/false),
color_space_(color_space),
copier_weak_ptr_(std::move(copier_weak_ptr)),
context_provider_(std::move(context_provider)),
transfer_buffer_(transfer_buffer),
is_upside_down_(is_upside_down),
swap_red_and_blue_(swap_red_and_blue) {}
~GLPixelBufferRGBAResult() final {
if (transfer_buffer_ && copier_weak_ptr_) {
context_provider_->ContextGL()->DeleteBuffers(1, &transfer_buffer_);
}
}
bool ReadRGBAPlane(uint8_t* dest, int stride) const final {
// If the GLRendererCopier is gone, this implies the display compositor
// which contains the GLContext is gone. Regard this copy output readback as
// failed.
if (!copier_weak_ptr_)
return false;
const int src_bytes_per_row = size().width() * kRGBABytesPerPixel;
DCHECK_GE(stride, src_bytes_per_row);
// No need to read from GPU memory if a cached bitmap already exists.
if (rect().IsEmpty() || cached_bitmap()->readyToDraw())
return CopyOutputResult::ReadRGBAPlane(dest, stride);
auto* const gl = context_provider_->ContextGL();
gl->BindBuffer(GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM, transfer_buffer_);
const uint8_t* pixels = static_cast<uint8_t*>(gl->MapBufferCHROMIUM(
GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM, GL_READ_ONLY));
if (pixels) {
if (is_upside_down_) {
dest += (size().height() - 1) * stride;
stride = -stride;
}
const uint8_t* src = pixels;
if (swap_red_and_blue_) {
for (int y = 0; y < size().height();
++y, src += src_bytes_per_row, dest += stride) {
for (int x = 0; x < kRGBABytesPerPixel * size().width();
x += kRGBABytesPerPixel) {
dest[x + 2] = src[x + 0];
dest[x + 1] = src[x + 1];
dest[x + 0] = src[x + 2];
dest[x + 3] = src[x + 3];
}
}
} else {
libyuv::CopyPlane(src, src_bytes_per_row, dest, stride,
src_bytes_per_row, size().height());
}
gl->UnmapBufferCHROMIUM(GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM);
}
gl->BindBuffer(GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM, 0);
return !!pixels;
}
gfx::ColorSpace GetRGBAColorSpace() const final { return color_space_; }
// This method is always called on the same sequence as the GLRendererCopier.
// This method will be inside Viz and has access to the WeakPtr of the
// GLRendererCopier to check whether we still have the access to an alive
// GLContext.
const SkBitmap& AsSkBitmap() const final {
if (rect().IsEmpty())
return *cached_bitmap(); // Return "null" bitmap for empty result.
if (cached_bitmap()->readyToDraw())
return *cached_bitmap();
if (!copier_weak_ptr_)
return *cached_bitmap();
SkBitmap result_bitmap;
// size() was clamped to render pass or framebuffer size. If we can't
// allocate it then OOM.
auto info = SkImageInfo::MakeN32Premul(
size().width(), size().height(), GetRGBAColorSpace().ToSkColorSpace());
if (!result_bitmap.tryAllocPixels(info, info.minRowBytes()))
base::TerminateBecauseOutOfMemory(info.computeMinByteSize());
ReadRGBAPlane(static_cast<uint8_t*>(result_bitmap.getPixels()),
result_bitmap.rowBytes());
*cached_bitmap() = result_bitmap;
// Now that we have a cached bitmap, no need to read from GPU memory
// anymore.
context_provider_->ContextGL()->DeleteBuffers(1, &transfer_buffer_);
transfer_buffer_ = 0;
return *cached_bitmap();
}
private:
const gfx::ColorSpace color_space_;
base::WeakPtr<GLRendererCopier> copier_weak_ptr_;
ContextProvider* context_provider_;
mutable GLuint transfer_buffer_;
const bool is_upside_down_;
const bool swap_red_and_blue_;
};
} // namespace
GLRendererCopier::ReadPixelsWorkflow::ReadPixelsWorkflow(
std::unique_ptr<CopyOutputRequest> copy_request,
const gfx::Vector2d& readback_offset,
bool flipped_source,
bool swap_red_and_blue,
const gfx::Rect& result_rect,
const gfx::ColorSpace& color_space,
ContextProvider* context_provider,
GLenum readback_format)
: copy_request(std::move(copy_request)),
flipped_source(flipped_source),
swap_red_and_blue(swap_red_and_blue),
result_rect(result_rect),
color_space(color_space),
context_provider_(context_provider) {
DCHECK(readback_format == GL_RGBA || readback_format == GL_BGRA_EXT);
DCHECK(context_provider_);
auto* const gl = context_provider_->ContextGL();
// Create a buffer for the pixel transfer.
gl->GenBuffers(1, &transfer_buffer);
gl->BindBuffer(GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM, transfer_buffer);
gl->BufferData(
GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM,
(result_rect.size().GetCheckedArea() * kRGBABytesPerPixel).ValueOrDie(),
nullptr, GL_STREAM_READ);
// Execute an asynchronous read-pixels operation, with a query that triggers
// when Finish() should be run.
gl->GenQueriesEXT(1, &query_);
gl->BeginQueryEXT(GL_ASYNC_PIXEL_PACK_COMPLETED_CHROMIUM, query_);
gl->ReadPixels(readback_offset.x(), readback_offset.y(), result_rect.width(),
result_rect.height(), readback_format, GL_UNSIGNED_BYTE,
nullptr);
gl->EndQueryEXT(GL_ASYNC_PIXEL_PACK_COMPLETED_CHROMIUM);
gl->BindBuffer(GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM, 0);
}
GLRendererCopier::ReadPixelsWorkflow::~ReadPixelsWorkflow() {
auto* const gl = context_provider_->ContextGL();
gl->DeleteQueriesEXT(1, &query_);
if (transfer_buffer)
gl->DeleteBuffers(1, &transfer_buffer);
}
// Callback for the asynchronous glReadPixels(). The pixels are read from the
// transfer buffer, and a CopyOutputResult is sent to the requestor. This would
// mark this workflow as finished, and the workflow will be cleared later.
void GLRendererCopier::FinishReadPixelsWorkflow(ReadPixelsWorkflow* workflow) {
auto result = std::make_unique<GLPixelBufferRGBAResult>(
workflow->result_rect, workflow->color_space, weak_factory_.GetWeakPtr(),
context_provider_, workflow->transfer_buffer, workflow->flipped_source,
workflow->swap_red_and_blue);
workflow->transfer_buffer = 0; // Ownerhip was transferred to the result.
if (!workflow->copy_request->SendsResultsInCurrentSequence()) {
// Force readback into a SkBitmap now, because after PostTask we don't
// have access to |context_provider_|.
auto scoped_bitmap = result->ScopedAccessSkBitmap();
auto bitmap = scoped_bitmap.bitmap();
}
workflow->copy_request->SendResult(std::move(result));
const auto it =
std::find_if(read_pixels_workflows_.begin(), read_pixels_workflows_.end(),
[workflow](auto& ptr) { return ptr.get() == workflow; });
DCHECK(it != read_pixels_workflows_.end());
read_pixels_workflows_.erase(it);
}
void GLRendererCopier::StartReadbackFromFramebuffer(
std::unique_ptr<CopyOutputRequest> request,
const gfx::Vector2d& readback_offset,
bool flipped_source,
bool swapped_red_and_blue,
const gfx::Rect& result_rect,
const gfx::ColorSpace& color_space) {
DCHECK_EQ(request->result_format(), ResultFormat::RGBA_BITMAP);
read_pixels_workflows_.emplace_back(std::make_unique<ReadPixelsWorkflow>(
std::move(request), readback_offset, flipped_source,
ShouldSwapRedAndBlueForBitmapReadback() != swapped_red_and_blue,
result_rect, color_space, context_provider_, GetOptimalReadbackFormat()));
context_provider_->ContextSupport()->SignalQuery(
read_pixels_workflows_.back()->query(),
base::BindOnce(&GLRendererCopier::FinishReadPixelsWorkflow,
weak_factory_.GetWeakPtr(),
read_pixels_workflows_.back().get()));
}
void GLRendererCopier::RenderAndSendTextureResult(
std::unique_ptr<CopyOutputRequest> request,
bool flipped_source,
const gfx::ColorSpace& source_color_space,
const gfx::ColorSpace& dest_color_space,
GLuint source_texture,
const gfx::Size& source_texture_size,
const gfx::Rect& sampling_rect,
const gfx::Rect& result_rect,
ReusableThings* things) {
DCHECK_EQ(request->result_format(), ResultFormat::RGBA_TEXTURE);
auto* sii = context_provider_->SharedImageInterface();
gpu::Mailbox mailbox = sii->CreateSharedImage(
ResourceFormat::RGBA_8888, result_rect.size(), dest_color_space,
kTopLeft_GrSurfaceOrigin, kPremul_SkAlphaType,
gpu::SHARED_IMAGE_USAGE_GLES2, gpu::kNullSurfaceHandle);
auto* gl = context_provider_->ContextGL();
gl->WaitSyncTokenCHROMIUM(sii->GenUnverifiedSyncToken().GetConstData());
GLuint texture = gl->CreateAndTexStorage2DSharedImageCHROMIUM(mailbox.name);
gl->BeginSharedImageAccessDirectCHROMIUM(
texture, GL_SHARED_IMAGE_ACCESS_MODE_READWRITE_CHROMIUM);
RenderResultTexture(*request, flipped_source, source_color_space,
dest_color_space, source_texture, source_texture_size,
sampling_rect, result_rect, texture, things);
gl->EndSharedImageAccessDirectCHROMIUM(texture);
gl->DeleteTextures(1, &texture);
gpu::SyncToken sync_token;
gl->GenSyncTokenCHROMIUM(sync_token.GetData());
// Create a callback that deletes what was created in this GL context.
// Note: There's no need to try to pool/re-use the result texture from here,
// since only clients that are trying to re-invent video capture would see any
// significant performance benefit. Instead, such clients should use the video
// capture services provided by VIZ.
auto release_callback =
texture_deleter_->GetReleaseCallback(context_provider_, mailbox);
request->SendResult(std::make_unique<CopyOutputTextureResult>(
result_rect, mailbox, sync_token, dest_color_space,
std::move(release_callback)));
}
namespace {
// Specialization of CopyOutputResult which reads I420 plane data from a GL
// pixel buffer object, and automatically deletes the pixel buffer object at
// destruction time. This provides an optimal one-copy data flow, from the pixel
// buffer into client-provided memory.
class GLPixelBufferI420Result final : public CopyOutputResult {
public:
// |aligned_rect| identifies the region of result pixels in the pixel buffer,
// while the |result_rect| is the subregion that is exposed to the client.
GLPixelBufferI420Result(const gfx::Rect& aligned_rect,
const gfx::Rect& result_rect,
base::WeakPtr<GLRendererCopier> copier_weak_ptr,
ContextProvider* context_provider,
GLuint transfer_buffer)
: CopyOutputResult(CopyOutputResult::Format::I420_PLANES,
result_rect,
/*needs_lock_for_bitmap=*/false),
aligned_rect_(aligned_rect),
copier_weak_ptr_(copier_weak_ptr),
context_provider_(context_provider),
transfer_buffer_(transfer_buffer) {
auto* const gl = context_provider_->ContextGL();
gl->BindBuffer(GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM, transfer_buffer_);
pixels_ = static_cast<uint8_t*>(gl->MapBufferCHROMIUM(
GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM, GL_READ_ONLY));
gl->BindBuffer(GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM, 0);
}
~GLPixelBufferI420Result() final {
if (copier_weak_ptr_) {
auto* const gl = context_provider_->ContextGL();
gl->BindBuffer(GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM, transfer_buffer_);
gl->UnmapBufferCHROMIUM(GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM);
gl->BindBuffer(GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM, 0);
gl->DeleteBuffers(1, &transfer_buffer_);
}
}
bool ReadI420Planes(uint8_t* y_out,
int y_out_stride,
uint8_t* u_out,
int u_out_stride,
uint8_t* v_out,
int v_out_stride) const final {
DCHECK_GE(y_out_stride, size().width());
const int chroma_row_bytes = (size().width() + 1) / 2;
DCHECK_GE(u_out_stride, chroma_row_bytes);
DCHECK_GE(v_out_stride, chroma_row_bytes);
if (!copier_weak_ptr_)
return false;
uint8_t* pixels = pixels_;
if (pixels) {
const int y_stride = aligned_rect_.width();
const gfx::Vector2d result_offset =
rect().OffsetFromOrigin() - aligned_rect_.OffsetFromOrigin();
const int y_start_offset =
result_offset.y() * y_stride + result_offset.x();
libyuv::CopyPlane(pixels + y_start_offset, y_stride, y_out, y_out_stride,
size().width(), size().height());
pixels += y_stride * aligned_rect_.height();
const int chroma_stride = aligned_rect_.width() / 2;
const int chroma_start_offset =
((result_offset.y() / 2) * chroma_stride) + (result_offset.x() / 2);
const int chroma_height = (size().height() + 1) / 2;
libyuv::CopyPlane(pixels + chroma_start_offset, chroma_stride, u_out,
u_out_stride, chroma_row_bytes, chroma_height);
pixels += chroma_stride * (aligned_rect_.height() / 2);
libyuv::CopyPlane(pixels + chroma_start_offset, chroma_stride, v_out,
v_out_stride, chroma_row_bytes, chroma_height);
}
return !!pixels;
}
private:
const gfx::Rect aligned_rect_;
base::WeakPtr<GLRendererCopier> copier_weak_ptr_;
ContextProvider* const context_provider_;
const GLuint transfer_buffer_;
uint8_t* pixels_;
};
} // namespace
GLRendererCopier::ReadI420PlanesWorkflow::ReadI420PlanesWorkflow(
std::unique_ptr<CopyOutputRequest> copy_request,
const gfx::Rect& aligned_rect,
const gfx::Rect& result_rect,
base::WeakPtr<GLRendererCopier> copier_weak_ptr,
ContextProvider* context_provider)
: copy_request(std::move(copy_request)),
aligned_rect(aligned_rect),
result_rect(result_rect),
copier_weak_ptr_(copier_weak_ptr),
context_provider_(context_provider) {
// Create a buffer for the pixel transfer: A single buffer is used and will
// contain the Y plane, then the U plane, then the V plane.
auto* const gl = context_provider_->ContextGL();
gl->GenBuffers(1, &transfer_buffer);
gl->BindBuffer(GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM, transfer_buffer);
base::CheckedNumeric<int> y_plane_bytes =
y_texture_size().GetCheckedArea() * kRGBABytesPerPixel;
base::CheckedNumeric<int> chroma_plane_bytes =
chroma_texture_size().GetCheckedArea() * kRGBABytesPerPixel;
gl->BufferData(GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM,
(y_plane_bytes + chroma_plane_bytes * 2).ValueOrDie(), nullptr,
GL_STREAM_READ);
data_offsets_ = {0, y_plane_bytes.ValueOrDie(),
(y_plane_bytes + chroma_plane_bytes).ValueOrDie()};
gl->BindBuffer(GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM, 0);
// Generate the three queries used for determining when each of the plane
// readbacks has completed.
gl->GenQueriesEXT(3, queries.data());
}
void GLRendererCopier::ReadI420PlanesWorkflow::BindTransferBuffer() {
DCHECK_NE(transfer_buffer, 0u);
context_provider_->ContextGL()->BindBuffer(
GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM, transfer_buffer);
}
void GLRendererCopier::ReadI420PlanesWorkflow::StartPlaneReadback(
int plane,
GLenum readback_format) {
DCHECK_NE(queries[plane], 0u);
auto* const gl = context_provider_->ContextGL();
gl->BeginQueryEXT(GL_ASYNC_PIXEL_PACK_COMPLETED_CHROMIUM, queries[plane]);
const gfx::Size& size = plane == 0 ? y_texture_size() : chroma_texture_size();
// Note: While a PIXEL_PACK_BUFFER is bound, OpenGL interprets the last
// argument to ReadPixels() as a byte offset within the buffer instead of
// an actual pointer in system memory.
uint8_t* offset_in_buffer = reinterpret_cast<uint8_t*>(/* byte_offset = */ 0);
offset_in_buffer += data_offsets_[plane];
gl->ReadPixels(0, 0, size.width(), size.height(), readback_format,
GL_UNSIGNED_BYTE, offset_in_buffer);
gl->EndQueryEXT(GL_ASYNC_PIXEL_PACK_COMPLETED_CHROMIUM);
context_provider_->ContextSupport()->SignalQuery(
queries[plane],
base::BindOnce(&GLRendererCopier::FinishReadI420PlanesWorkflow,
copier_weak_ptr_, this, plane));
}
void GLRendererCopier::ReadI420PlanesWorkflow::UnbindTransferBuffer() {
context_provider_->ContextGL()->BindBuffer(
GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM, 0);
}
GLRendererCopier::ReadI420PlanesWorkflow::~ReadI420PlanesWorkflow() {
auto* const gl = context_provider_->ContextGL();
if (transfer_buffer != 0)
gl->DeleteBuffers(1, &transfer_buffer);
for (GLuint& query : queries) {
if (query != 0)
gl->DeleteQueriesEXT(1, &query);
}
}
gfx::Size GLRendererCopier::ReadI420PlanesWorkflow::y_texture_size() const {
return gfx::Size(aligned_rect.width() / kRGBABytesPerPixel,
aligned_rect.height());
}
gfx::Size GLRendererCopier::ReadI420PlanesWorkflow::chroma_texture_size()
const {
return gfx::Size(aligned_rect.width() / kRGBABytesPerPixel / 2,
aligned_rect.height() / 2);
}
void GLRendererCopier::StartI420ReadbackFromTextures(
std::unique_ptr<CopyOutputRequest> request,
const gfx::Rect& aligned_rect,
const gfx::Rect& result_rect,
ReusableThings* things) {
DCHECK_EQ(request->result_format(), ResultFormat::I420_PLANES);
auto* const gl = context_provider_->ContextGL();
if (things->yuv_readback_framebuffers[0] == 0)
gl->GenFramebuffers(3, things->yuv_readback_framebuffers.data());
// Execute three asynchronous read-pixels operations, one for each plane. The
// CopyOutputRequest is passed to the ReadI420PlanesWorkflow, which will send
// the CopyOutputResult once all readback operations are complete.
read_i420_workflows_.emplace_back(std::make_unique<ReadI420PlanesWorkflow>(
std::move(request), aligned_rect, result_rect, weak_factory_.GetWeakPtr(),
context_provider_));
ReadI420PlanesWorkflow* workflow = read_i420_workflows_.back().get();
workflow->BindTransferBuffer();
for (int plane = 0; plane < 3; ++plane) {
gl->BindFramebuffer(GL_FRAMEBUFFER,
things->yuv_readback_framebuffers[plane]);
gl->FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_TEXTURE_2D, things->yuv_textures[plane], 0);
workflow->StartPlaneReadback(plane, GetOptimalReadbackFormat());
}
workflow->UnbindTransferBuffer();
}
void GLRendererCopier::FinishReadI420PlanesWorkflow(
ReadI420PlanesWorkflow* workflow,
int plane) {
context_provider_->ContextGL()->DeleteQueriesEXT(1,
&workflow->queries[plane]);
workflow->queries[plane] = 0;
// If all three readbacks have completed, send the result.
if (workflow->queries == std::array<GLuint, 3>{{0, 0, 0}}) {
workflow->copy_request->SendResult(
std::make_unique<GLPixelBufferI420Result>(
workflow->aligned_rect, workflow->result_rect,
weak_factory_.GetWeakPtr(), context_provider_,
workflow->transfer_buffer));
workflow->transfer_buffer = 0; // Ownership was transferred to the result.
const auto it =
std::find_if(read_i420_workflows_.begin(), read_i420_workflows_.end(),
[workflow](auto& ptr) { return ptr.get() == workflow; });
DCHECK(it != read_i420_workflows_.end());
read_i420_workflows_.erase(it);
}
}
std::unique_ptr<GLRendererCopier::ReusableThings>
GLRendererCopier::TakeReusableThingsOrCreate(
const base::UnguessableToken& requester) {
if (!requester.is_empty()) {
const auto it = cache_.find(requester);
if (it != cache_.end()) {
auto things = std::move(it->second);
cache_.erase(it);
return things;
}
}
return std::make_unique<ReusableThings>();
}
void GLRendererCopier::StashReusableThingsOrDelete(
const base::UnguessableToken& requester,
std::unique_ptr<ReusableThings> things) {
if (requester.is_empty()) {
things->Free(context_provider_->ContextGL());
} else {
things->purge_count_at_last_use = purge_counter_;
cache_[requester] = std::move(things);
}
}
GLenum GLRendererCopier::GetOptimalReadbackFormat() {
if (optimal_readback_format_ != GL_NONE)
return optimal_readback_format_;
// Preconditions: GetOptimalReadbackFormat() requires a valid context and a
// complete framebuffer set up. The latter must be guaranteed by all possible
// callers of this method.
auto* const gl = context_provider_->ContextGL();
if (gl->GetGraphicsResetStatusKHR() != GL_NO_ERROR)
return GL_RGBA; // No context: Just return a sane default.
DCHECK(gl->CheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
// If the GL implementation internally uses the GL_BGRA_EXT+GL_UNSIGNED_BYTE
// format+type combination, then consider that the optimal readback
// format+type. Otherwise, use GL_RGBA+GL_UNSIGNED_BYTE, which all platforms
// must support, per the GLES 2.0 spec.
GLint type = 0;
GLint readback_format = 0;
gl->GetIntegerv(GL_IMPLEMENTATION_COLOR_READ_TYPE, &type);
if (type == GL_UNSIGNED_BYTE)
gl->GetIntegerv(GL_IMPLEMENTATION_COLOR_READ_FORMAT, &readback_format);
if (readback_format != GL_BGRA_EXT)
readback_format = GL_RGBA;
optimal_readback_format_ = static_cast<GLenum>(readback_format);
return optimal_readback_format_;
}
bool GLRendererCopier::ShouldSwapRedAndBlueForBitmapReadback() {
const bool skbitmap_is_bgra = (kN32_SkColorType == kBGRA_8888_SkColorType);
const bool readback_will_be_bgra =
(GetOptimalReadbackFormat() == GL_BGRA_EXT);
return skbitmap_is_bgra != readback_will_be_bgra;
}
GLRendererCopier::ReusableThings::ReusableThings() = default;
GLRendererCopier::ReusableThings::~ReusableThings() {
// Ensure all resources were freed by this point. Resources aren't explicity
// freed here, in the destructor, because some require access to the GL
// context. See Free().
DCHECK_EQ(fb_copy_texture, 0u);
DCHECK(!scaler);
DCHECK_EQ(result_texture, 0u);
DCHECK_EQ(readback_framebuffer, 0u);
DCHECK(!i420_converter);
constexpr std::array<GLuint, 3> kAllZeros = {0, 0, 0};
DCHECK(yuv_textures == kAllZeros);
DCHECK(yuv_readback_framebuffers == kAllZeros);
}
void GLRendererCopier::ReusableThings::Free(gpu::gles2::GLES2Interface* gl) {
if (fb_copy_texture != 0) {
gl->DeleteTextures(1, &fb_copy_texture);
fb_copy_texture = 0;
fb_copy_texture_internal_format = static_cast<GLenum>(GL_NONE);
fb_copy_texture_size = gfx::Size();
}
scaler.reset();
if (result_texture != 0) {
gl->DeleteTextures(1, &result_texture);
result_texture = 0;
result_texture_size = gfx::Size();
}
if (readback_framebuffer != 0) {
gl->DeleteFramebuffers(1, &readback_framebuffer);
readback_framebuffer = 0;
}
i420_converter.reset();
if (yuv_textures[0] != 0) {
gl->DeleteTextures(3, yuv_textures.data());
yuv_textures = {0, 0, 0};
y_texture_size = gfx::Size();
}
if (yuv_readback_framebuffers[0] != 0) {
gl->DeleteFramebuffers(3, yuv_readback_framebuffers.data());
yuv_readback_framebuffers = {0, 0, 0};
}
}
} // namespace viz