blob: dcd5e017f5496c8e2019a7b8fd59e3d96d6aa686 [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/stl_util.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/common/mailbox.h"
#include "gpu/command_buffer/common/sync_token.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkImageInfo.h"
#include "ui/gfx/color_space.h"
#include "ui/gfx/geometry/rect.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(
scoped_refptr<ContextProvider> context_provider,
TextureDeleter* texture_deleter,
ComputeWindowRectCallback window_rect_callback)
: context_provider_(std::move(context_provider)),
texture_deleter_(texture_deleter),
window_rect_callback_(std::move(window_rect_callback)) {}
GLRendererCopier::~GLRendererCopier() {
for (auto& entry : cache_)
entry.second->Free(context_provider_->ContextGL());
}
void GLRendererCopier::CopyFromTextureOrFramebuffer(
std::unique_ptr<CopyOutputRequest> request,
const gfx::Rect& output_rect,
GLenum internal_format,
GLuint framebuffer_texture,
const gfx::Size& framebuffer_texture_size,
bool flipped_source,
const gfx::ColorSpace& color_space) {
// Finalize the source subrect, as the entirety of the RenderPass's output
// optionally clamped to the requested copy area. Then, compute the result
// rect, which is the selection clamped to the maximum possible result bounds.
// If there will be zero pixels of output or the scaling ratio was not
// reasonable, do not proceed.
gfx::Rect copy_rect = output_rect;
if (request->has_area())
copy_rect.Intersect(request->area());
gfx::Rect result_rect = request->is_scaled()
? copy_output::ComputeResultRect(
gfx::Rect(copy_rect.size()),
request->scale_from(), request->scale_to())
: gfx::Rect(copy_rect.size());
if (request->has_result_selection())
result_rect.Intersect(request->result_selection());
if (result_rect.IsEmpty())
return;
// 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 &&
!request->is_scaled()) {
const gfx::Vector2d readback_offset =
window_rect_callback_.Run(result_rect + copy_rect.OffsetFromOrigin())
.OffsetFromOrigin();
StartReadbackFromFramebuffer(std::move(request), readback_offset,
flipped_source, false, result_rect,
color_space);
return;
}
// Transform the copy rect into window coordinates, the coordinate system GL
// is using for the currently-bound framebuffer.
gfx::Rect sampling_rect = window_rect_callback_.Run(copy_rect);
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:
RenderResultTexture(*request, flipped_source, color_space, source_texture,
source_texture_size, sampling_rect, result_rect,
things.get());
StartReadbackFromTexture(std::move(request), result_rect, color_space,
things.get());
break;
case ResultFormat::RGBA_TEXTURE:
RenderResultTexture(*request, flipped_source, color_space, source_texture,
source_texture_size, sampling_rect, result_rect,
things.get());
SendTextureResult(std::move(request), things->result_texture, result_rect,
color_space);
things->result_texture = 0; // SendTextureResult() took ownership.
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.
DCHECK(request->SendsResultsInCurrentSequence());
const gfx::Rect aligned_rect = RenderI420Textures(
*request, flipped_source, 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,
GLuint source_texture,
const gfx::Size& source_texture_size,
const gfx::Rect& sampling_rect,
const gfx::Rect& result_rect,
ReusableThings* things) {
DCHECK_NE(request.result_format(), ResultFormat::I420_PLANES);
EnsureTextureDefinedWithSize(context_provider_->ContextGL(),
result_rect.size(), &things->result_texture,
&things->result_texture_size);
GLScaler::Parameters params;
PopulateScalerParameters(request, source_color_space, source_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(),
things->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 {
class GLPixelBufferRGBAResult : public CopyOutputResult {
public:
GLPixelBufferRGBAResult(const gfx::Rect& result_rect,
const gfx::ColorSpace& color_space,
scoped_refptr<ContextProvider> context_provider,
GLuint transfer_buffer,
bool is_upside_down,
bool swap_red_and_blue)
: CopyOutputResult(CopyOutputResult::Format::RGBA_BITMAP, result_rect),
color_space_(color_space),
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_)
context_provider_->ContextGL()->DeleteBuffers(1, &transfer_buffer_);
}
bool ReadRGBAPlane(uint8_t* dest, int stride) const final {
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 {
for (int y = 0; y < size().height();
++y, src += src_bytes_per_row, dest += stride) {
memcpy(dest, src, src_bytes_per_row);
}
}
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_; }
const SkBitmap& AsSkBitmap() const final {
if (rect().IsEmpty())
return *cached_bitmap(); // Return "null" bitmap for empty result.
if (cached_bitmap()->readyToDraw())
return *cached_bitmap();
SkBitmap result_bitmap;
result_bitmap.allocPixels(SkImageInfo::MakeN32Premul(
size().width(), size().height(), GetRGBAColorSpace().ToSkColorSpace()));
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_;
const scoped_refptr<ContextProvider> context_provider_;
mutable GLuint transfer_buffer_;
const bool is_upside_down_;
const bool swap_red_and_blue_;
};
// Manages the execution of one asynchronous framebuffer readback and contains
// all the relevant state needed to complete a copy request. The constructor
// initiates the operation, and then at some later point either: 1) the Finish()
// method is invoked; or 2) the instance will be destroyed (cancelled) because
// the GL context is going away. Either way, the GL objects created for this
// workflow are properly cleaned-up.
//
// Motivation: In case #2, it's possible GLRendererCopier will have been
// destroyed before Finish(). However, since there are no dependencies on
// GLRendererCopier to finish the copy request, there's no reason to mess around
// with a complex WeakPtr-to-GLRendererCopier scheme.
class ReadPixelsWorkflow {
public:
// Saves all revelant state and initiates the GL asynchronous read-pixels
// workflow.
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,
scoped_refptr<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_(std::move(context_provider)) {
DCHECK(readback_format == GL_RGBA || readback_format == GL_BGRA_EXT);
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,
kRGBABytesPerPixel * result_rect.size().GetArea(), 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);
}
// The destructor is called when the callback that owns this instance is
// destroyed. That will happen either with or without a call to Finish(),
// but either way everything will be clean-up appropriately.
~ReadPixelsWorkflow() {
auto* const gl = context_provider_->ContextGL();
gl->DeleteQueriesEXT(1, &query_);
if (transfer_buffer_)
gl->DeleteBuffers(1, &transfer_buffer_);
}
GLuint query() const { return query_; }
// Callback for the asynchronous glReadPixels(). The pixels are read from the
// transfer buffer, and a CopyOutputResult is sent to the requestor.
void Finish() {
auto result = std::make_unique<GLPixelBufferRGBAResult>(
result_rect_, color_space_, context_provider_, transfer_buffer_,
flipped_source_, swap_red_and_blue_);
transfer_buffer_ = 0; // Ownerhip was transferred to the result.
if (!copy_request_->SendsResultsInCurrentSequence()) {
// Force readback into a SkBitmap now, because after PostTask we don't
// have access to |context_provider_|.
result->AsSkBitmap();
}
copy_request_->SendResult(std::move(result));
}
private:
const std::unique_ptr<CopyOutputRequest> copy_request_;
const bool flipped_source_;
const bool swap_red_and_blue_;
const gfx::Rect result_rect_;
const gfx::ColorSpace color_space_;
const scoped_refptr<ContextProvider> context_provider_;
GLuint transfer_buffer_ = 0;
GLuint query_ = 0;
};
} // namespace
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);
auto workflow = std::make_unique<ReadPixelsWorkflow>(
std::move(request), readback_offset, flipped_source,
ShouldSwapRedAndBlueForBitmapReadback() != swapped_red_and_blue,
result_rect, color_space, context_provider_, GetOptimalReadbackFormat());
const GLuint query = workflow->query();
context_provider_->ContextSupport()->SignalQuery(
query, base::BindOnce(&ReadPixelsWorkflow::Finish, std::move(workflow)));
}
void GLRendererCopier::SendTextureResult(
std::unique_ptr<CopyOutputRequest> request,
GLuint result_texture,
const gfx::Rect& result_rect,
const gfx::ColorSpace& color_space) {
DCHECK_EQ(request->result_format(), ResultFormat::RGBA_TEXTURE);
auto* const gl = context_provider_->ContextGL();
// Package the |result_texture| into a mailbox with the required
// synchronization mechanisms. This lets the requestor ensure operations
// within its own GL context will be using the texture at a point in time
// after the texture has been rendered (via GLRendererCopier's GL context).
gpu::Mailbox mailbox;
gl->ProduceTextureDirectCHROMIUM(result_texture, mailbox.name);
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_, result_texture);
request->SendResult(std::make_unique<CopyOutputTextureResult>(
result_rect, mailbox, sync_token, 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 : 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,
scoped_refptr<ContextProvider> context_provider,
GLuint transfer_buffer)
: CopyOutputResult(CopyOutputResult::Format::I420_PLANES, result_rect),
aligned_rect_(aligned_rect),
context_provider_(std::move(context_provider)),
transfer_buffer_(transfer_buffer) {}
~GLPixelBufferI420Result() final {
context_provider_->ContextGL()->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);
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) {
const auto CopyPlane = [](const uint8_t* src, int src_stride,
int row_bytes, int num_rows, uint8_t* out,
int out_stride) {
for (int i = 0; i < num_rows;
++i, src += src_stride, out += out_stride) {
memcpy(out, src, row_bytes);
}
};
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();
CopyPlane(pixels + y_start_offset, y_stride, size().width(),
size().height(), y_out, y_out_stride);
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;
CopyPlane(pixels + chroma_start_offset, chroma_stride, chroma_row_bytes,
chroma_height, u_out, u_out_stride);
pixels += chroma_stride * (aligned_rect_.height() / 2);
CopyPlane(pixels + chroma_start_offset, chroma_stride, chroma_row_bytes,
chroma_height, v_out, v_out_stride);
gl->UnmapBufferCHROMIUM(GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM);
}
gl->BindBuffer(GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM, 0);
return !!pixels;
}
private:
const gfx::Rect aligned_rect_;
const scoped_refptr<ContextProvider> context_provider_;
const GLuint transfer_buffer_;
const gfx::Vector2d i420_offset_;
};
// Like the ReadPixelsWorkflow, except for I420 planes readback. Because there
// are three separate glReadPixels operations that may complete in any order, a
// ReadI420PlanesWorkflow will receive notifications from three separate "GL
// query" callbacks. It is only after all three operations have completed that a
// fully-assembled CopyOutputResult can be sent.
//
// Please see class comments for ReadPixelsWorkflow for discussion about how GL
// context loss is handled during the workflow.
//
// Also, see class comments for GLI420Converter for an explanation of how planar
// data is packed into RGBA textures.
class ReadI420PlanesWorkflow
: public base::RefCountedThreadSafe<ReadI420PlanesWorkflow> {
public:
ReadI420PlanesWorkflow(std::unique_ptr<CopyOutputRequest> copy_request,
const gfx::Rect& aligned_rect,
const gfx::Rect& result_rect,
scoped_refptr<ContextProvider> context_provider)
: copy_request_(std::move(copy_request)),
aligned_rect_(aligned_rect),
result_rect_(result_rect),
context_provider_(std::move(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_);
const int y_plane_bytes = kRGBABytesPerPixel * y_texture_size().GetArea();
const int chroma_plane_bytes =
kRGBABytesPerPixel * chroma_texture_size().GetArea();
gl->BufferData(GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM,
y_plane_bytes + 2 * chroma_plane_bytes, nullptr,
GL_STREAM_READ);
data_offsets_ = {0, y_plane_bytes, y_plane_bytes + chroma_plane_bytes};
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 BindTransferBuffer() {
DCHECK_NE(transfer_buffer_, 0u);
context_provider_->ContextGL()->BindBuffer(
GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM, transfer_buffer_);
}
void 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 = 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(&ReadI420PlanesWorkflow::OnFinishedPlane, this, plane));
}
void UnbindTransferBuffer() {
context_provider_->ContextGL()->BindBuffer(
GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM, 0);
}
private:
friend class base::RefCountedThreadSafe<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 y_texture_size() const {
return gfx::Size(aligned_rect_.width() / kRGBABytesPerPixel,
aligned_rect_.height());
}
gfx::Size chroma_texture_size() const {
return gfx::Size(aligned_rect_.width() / kRGBABytesPerPixel / 2,
aligned_rect_.height() / 2);
}
void OnFinishedPlane(int plane) {
context_provider_->ContextGL()->DeleteQueriesEXT(1, &queries_[plane]);
queries_[plane] = 0;
// If all three readbacks have completed, send the result.
if (queries_ == std::array<GLuint, 3>{{0, 0, 0}}) {
copy_request_->SendResult(std::make_unique<GLPixelBufferI420Result>(
aligned_rect_, result_rect_, context_provider_, transfer_buffer_));
transfer_buffer_ = 0; // Ownership was transferred to the result.
}
}
const std::unique_ptr<CopyOutputRequest> copy_request_;
const gfx::Rect aligned_rect_;
const gfx::Rect result_rect_;
const scoped_refptr<ContextProvider> context_provider_;
GLuint transfer_buffer_;
std::array<int, 3> data_offsets_;
std::array<GLuint, 3> queries_;
};
} // namespace
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.
const auto workflow = base::MakeRefCounted<ReadI420PlanesWorkflow>(
std::move(request), aligned_rect, result_rect, context_provider_);
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();
}
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