blob: 8bd92333d92e2099f7327e37244508eef0f2e460 [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 "third_party/blink/renderer/platform/graphics/canvas_resource_provider.h"
#include "cc/paint/decode_stashing_image_provider.h"
#include "cc/paint/skia_paint_canvas.h"
#include "components/viz/common/resources/resource_format_utils.h"
#include "gpu/GLES2/gl2extchromium.h"
#include "gpu/command_buffer/common/capabilities.h"
#include "gpu/command_buffer/common/gpu_memory_buffer_support.h"
#include "gpu/config/gpu_driver_bug_workaround_type.h"
#include "gpu/config/gpu_feature_info.h"
#include "third_party/blink/renderer/platform/graphics/accelerated_static_bitmap_image.h"
#include "third_party/blink/renderer/platform/graphics/canvas_heuristic_parameters.h"
#include "third_party/blink/renderer/platform/graphics/canvas_resource_dispatcher.h"
#include "third_party/blink/renderer/platform/graphics/gpu/shared_gpu_context.h"
#include "third_party/blink/renderer/platform/graphics/static_bitmap_image.h"
#include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "third_party/skia/include/core/SkColorSpaceXformCanvas.h"
#include "third_party/skia/include/gpu/GrBackendSurface.h"
#include "third_party/skia/include/gpu/GrContext.h"
#include "ui/gfx/gpu_memory_buffer.h"
namespace blink {
// CanvasResourceProviderTexture
//==============================================================================
//
// * Renders to a texture managed by skia. Mailboxes are straight GL textures.
// * Layers are not overlay candidates
class CanvasResourceProviderTexture : public CanvasResourceProvider {
public:
CanvasResourceProviderTexture(
const IntSize& size,
unsigned msaa_sample_count,
const CanvasColorParams color_params,
base::WeakPtr<WebGraphicsContext3DProviderWrapper>
context_provider_wrapper,
base::WeakPtr<CanvasResourceDispatcher> resource_dispatcher)
: CanvasResourceProvider(size,
color_params,
std::move(context_provider_wrapper),
std::move(resource_dispatcher)),
msaa_sample_count_(msaa_sample_count) {}
~CanvasResourceProviderTexture() override = default;
bool IsValid() const final { return GetSkSurface() && !IsGpuContextLost(); }
bool IsAccelerated() const final { return true; }
GLuint GetBackingTextureHandleForOverwrite() override {
GrBackendTexture backend_texture = GetSkSurface()->getBackendTexture(
SkSurface::kDiscardWrite_TextureHandleAccess);
if (!backend_texture.isValid())
return 0;
GrGLTextureInfo info;
if (!backend_texture.getGLTextureInfo(&info))
return 0;
return info.fID;
}
protected:
scoped_refptr<CanvasResource> ProduceFrame() override {
TRACE_EVENT0("blink", "CanvasResourceProviderTexture::ProduceFrame");
DCHECK(GetSkSurface());
if (IsGpuContextLost())
return nullptr;
auto* gl = ContextGL();
DCHECK(gl);
if (ContextProviderWrapper()
->ContextProvider()
->GetCapabilities()
.disable_2d_canvas_copy_on_write) {
// A readback operation may alter the texture parameters, which may affect
// the compositor's behavior. Therefore, we must trigger copy-on-write
// even though we are not technically writing to the texture, only to its
// parameters.
// If this issue with readback affecting state is ever fixed, then we'll
// have to do this instead of triggering a copy-on-write:
// static_cast<AcceleratedStaticBitmapImage*>(image.get())
// ->RetainOriginalSkImageForCopyOnWrite();
GetSkSurface()->notifyContentWillChange(
SkSurface::kRetain_ContentChangeMode);
}
sk_sp<SkImage> skia_image = GetSkSurface()->makeImageSnapshot();
if (!skia_image)
return nullptr;
DCHECK(skia_image->isTextureBacked());
scoped_refptr<StaticBitmapImage> image =
StaticBitmapImage::Create(skia_image, ContextProviderWrapper());
scoped_refptr<CanvasResource> resource = CanvasResourceBitmap::Create(
image, CreateWeakPtr(), FilterQuality(), ColorParams());
if (!resource)
return nullptr;
return resource;
}
sk_sp<SkSurface> CreateSkSurface() const override {
TRACE_EVENT0("blink", "CanvasResourceProviderTexture::CreateSkSurface");
if (IsGpuContextLost())
return nullptr;
auto* gr = GetGrContext();
DCHECK(gr);
SkImageInfo info = SkImageInfo::Make(
Size().Width(), Size().Height(), ColorParams().GetSkColorType(),
kPremul_SkAlphaType, ColorParams().GetSkColorSpaceForSkSurfaces());
return SkSurface::MakeRenderTarget(gr, SkBudgeted::kNo, info,
msaa_sample_count_,
ColorParams().GetSkSurfaceProps());
}
const unsigned msaa_sample_count_;
};
// CanvasResourceProviderTextureGpuMemoryBuffer
//==============================================================================
//
// * Renders to a texture managed by skia. Mailboxes are
// gpu-accelerated platform native surfaces.
// * Layers are overlay candidates
class CanvasResourceProviderTextureGpuMemoryBuffer final
: public CanvasResourceProviderTexture {
public:
CanvasResourceProviderTextureGpuMemoryBuffer(
const IntSize& size,
unsigned msaa_sample_count,
const CanvasColorParams color_params,
base::WeakPtr<WebGraphicsContext3DProviderWrapper>
context_provider_wrapper,
base::WeakPtr<CanvasResourceDispatcher> resource_dispatcher)
: CanvasResourceProviderTexture(size,
msaa_sample_count,
color_params,
std::move(context_provider_wrapper),
std::move(resource_dispatcher)) {}
~CanvasResourceProviderTextureGpuMemoryBuffer() override = default;
private:
scoped_refptr<CanvasResource> CreateResource() final {
TRACE_EVENT0(
"blink",
"CanvasResourceProviderTextureGpuMemoreBuffer::CreateResource");
constexpr bool is_accelerated = true;
return CanvasResourceGpuMemoryBuffer::Create(
Size(), ColorParams(), ContextProviderWrapper(), CreateWeakPtr(),
FilterQuality(), is_accelerated);
}
scoped_refptr<CanvasResource> ProduceFrame() final {
TRACE_EVENT0("blink",
"CanvasResourceProviderTextureGpuMemoreBuffer::ProduceFrame");
DCHECK(GetSkSurface());
if (IsGpuContextLost())
return nullptr;
scoped_refptr<CanvasResource> output_resource = NewOrRecycledResource();
if (!output_resource) {
// GpuMemoryBuffer creation failed, fallback to Texture resource
return CanvasResourceProviderTexture::ProduceFrame();
}
sk_sp<SkImage> image = GetSkSurface()->makeImageSnapshot();
if (!image)
return nullptr;
DCHECK(image->isTextureBacked());
GrBackendTexture backend_texture = image->getBackendTexture(true);
DCHECK(backend_texture.isValid());
GrGLTextureInfo info;
if (!backend_texture.getGLTextureInfo(&info))
return nullptr;
GLuint skia_texture_id = info.fID;
output_resource->CopyFromTexture(skia_texture_id,
ColorParams().GLInternalFormat(),
ColorParams().GLType());
return output_resource;
}
};
// CanvasResourceProviderBitmap
//==============================================================================
//
// * Renders to a skia RAM-backed bitmap
// * Mailboxing is not supported : cannot be directly composited
class CanvasResourceProviderBitmap : public CanvasResourceProvider {
public:
CanvasResourceProviderBitmap(
const IntSize& size,
const CanvasColorParams color_params,
base::WeakPtr<CanvasResourceDispatcher> resource_dispatcher)
: CanvasResourceProvider(size,
color_params,
nullptr /*context_provider_wrapper*/,
std::move(resource_dispatcher)) {}
~CanvasResourceProviderBitmap() override = default;
bool IsValid() const final { return GetSkSurface(); }
bool IsAccelerated() const final { return false; }
private:
scoped_refptr<CanvasResource> ProduceFrame() override {
NOTREACHED(); // Not directly compositable.
return nullptr;
}
sk_sp<SkSurface> CreateSkSurface() const override {
TRACE_EVENT0("blink", "CanvasResourceProviderBitmap::CreateSkSurface");
SkImageInfo info = SkImageInfo::Make(
Size().Width(), Size().Height(), ColorParams().GetSkColorType(),
kPremul_SkAlphaType, ColorParams().GetSkColorSpaceForSkSurfaces());
return SkSurface::MakeRaster(info, ColorParams().GetSkSurfaceProps());
}
};
// CanvasResourceProviderRamGpuMemoryBuffer
//==============================================================================
//
// * Renders to a ram memory buffer managed by skia
// * Uses GpuMemoryBuffer to pass frames to the compositor
// * Layers are overlay candidates
class CanvasResourceProviderRamGpuMemoryBuffer final
: public CanvasResourceProviderBitmap {
public:
CanvasResourceProviderRamGpuMemoryBuffer(
const IntSize& size,
const CanvasColorParams color_params,
base::WeakPtr<CanvasResourceDispatcher> resource_dispatcher)
: CanvasResourceProviderBitmap(size,
color_params,
std::move(resource_dispatcher)) {}
~CanvasResourceProviderRamGpuMemoryBuffer() override = default;
private:
scoped_refptr<CanvasResource> CreateResource() final {
TRACE_EVENT0("blink",
"CanvasResourceProviderRamGpuMemoryBuffer::CreateResource");
constexpr bool is_accelerated = false;
return CanvasResourceGpuMemoryBuffer::Create(
Size(), ColorParams(), ContextProviderWrapper(), CreateWeakPtr(),
FilterQuality(), is_accelerated);
}
scoped_refptr<CanvasResource> ProduceFrame() final {
TRACE_EVENT0("blink",
"CanvasResourceProviderRamGpuMemoryBuffer::ProduceFrame");
DCHECK(GetSkSurface());
scoped_refptr<CanvasResource> output_resource = NewOrRecycledResource();
if (!output_resource) {
// Not compositable without a GpuMemoryBuffer
return nullptr;
}
sk_sp<SkImage> image = GetSkSurface()->makeImageSnapshot();
if (!image)
return nullptr;
DCHECK(!image->isTextureBacked());
output_resource->TakeSkImage(std::move(image));
return output_resource;
}
};
// CanvasResourceProviderSharedBitmap
//==============================================================================
//
// * Renders to a shared memory bitmap
// * Uses SharedBitmaps to pass frames directly to the compositor
class CanvasResourceProviderSharedBitmap : public CanvasResourceProviderBitmap {
public:
CanvasResourceProviderSharedBitmap(
const IntSize& size,
const CanvasColorParams color_params,
base::WeakPtr<CanvasResourceDispatcher> resource_dispatcher)
: CanvasResourceProviderBitmap(size,
color_params,
std::move(resource_dispatcher)) {
DCHECK(ResourceDispatcher());
}
~CanvasResourceProviderSharedBitmap() override = default;
private:
scoped_refptr<CanvasResource> CreateResource() final {
return CanvasResourceSharedBitmap::Create(Size(), ColorParams(),
CreateWeakPtr(), FilterQuality());
}
scoped_refptr<CanvasResource> ProduceFrame() final {
DCHECK(GetSkSurface());
scoped_refptr<CanvasResource> output_resource = NewOrRecycledResource();
if (!output_resource) {
// Not compositable without a SharedBitmap
return nullptr;
}
sk_sp<SkImage> image = GetSkSurface()->makeImageSnapshot();
if (!image)
return nullptr;
DCHECK(!image->isTextureBacked());
output_resource->TakeSkImage(std::move(image));
return output_resource;
}
base::WeakPtr<CanvasResourceDispatcher> resource_dispatcher_;
};
// CanvasResourceProvider base class implementation
//==============================================================================
enum ResourceType {
kTextureGpuMemoryBufferResourceType,
kRamGpuMemoryBufferResourceType,
kSharedBitmapResourceType,
kTextureResourceType,
kBitmapResourceType,
};
constexpr ResourceType kSoftwareCompositedFallbackList[] = {
kRamGpuMemoryBufferResourceType, kSharedBitmapResourceType,
kBitmapResourceType,
};
constexpr ResourceType kSoftwareFallbackList[] = {
kBitmapResourceType,
};
constexpr ResourceType kAcceleratedFallbackList[] = {
kTextureResourceType, kBitmapResourceType,
};
constexpr ResourceType kAcceleratedCompositedFallbackList[] = {
kTextureGpuMemoryBufferResourceType,
kTextureResourceType,
kRamGpuMemoryBufferResourceType,
kSharedBitmapResourceType,
kBitmapResourceType,
};
std::unique_ptr<CanvasResourceProvider> CanvasResourceProvider::Create(
const IntSize& size,
ResourceUsage usage,
base::WeakPtr<WebGraphicsContext3DProviderWrapper> context_provider_wrapper,
unsigned msaa_sample_count,
const CanvasColorParams& color_params,
PresentationMode presentation_mode,
base::WeakPtr<CanvasResourceDispatcher> resource_dispatcher) {
const ResourceType* resource_type_fallback_list = nullptr;
size_t list_length = 0;
switch (usage) {
case kSoftwareResourceUsage:
resource_type_fallback_list = kSoftwareFallbackList;
list_length = arraysize(kSoftwareFallbackList);
break;
case kSoftwareCompositedResourceUsage:
resource_type_fallback_list = kSoftwareCompositedFallbackList;
list_length = arraysize(kSoftwareCompositedFallbackList);
break;
case kAcceleratedResourceUsage:
resource_type_fallback_list = kAcceleratedFallbackList;
list_length = arraysize(kAcceleratedFallbackList);
break;
case kAcceleratedCompositedResourceUsage:
resource_type_fallback_list = kAcceleratedCompositedFallbackList;
list_length = arraysize(kAcceleratedCompositedFallbackList);
break;
}
std::unique_ptr<CanvasResourceProvider> provider;
for (size_t i = 0; i < list_length; ++i) {
// Note: We are deliberately not using std::move() on
// context_provider_wrapper and resource_dispatcher to ensure that the
// pointers remain valid for the next iteration of this loop if necessary.
switch (resource_type_fallback_list[i]) {
case kTextureGpuMemoryBufferResourceType:
DCHECK(SharedGpuContext::IsGpuCompositingEnabled());
if (presentation_mode !=
CanvasResourceProvider::kAllowImageChromiumPresentationMode)
continue;
if (!gpu::IsImageFromGpuMemoryBufferFormatSupported(
color_params.GetBufferFormat(),
context_provider_wrapper->ContextProvider()
->GetCapabilities())) {
continue;
}
if (!gpu::IsImageSizeValidForGpuMemoryBufferFormat(
gfx::Size(size), color_params.GetBufferFormat())) {
continue;
}
DCHECK(gpu::IsImageFormatCompatibleWithGpuMemoryBufferFormat(
color_params.GLInternalFormat(), color_params.GetBufferFormat()));
provider =
std::make_unique<CanvasResourceProviderTextureGpuMemoryBuffer>(
size, msaa_sample_count, color_params, context_provider_wrapper,
resource_dispatcher);
break;
case kRamGpuMemoryBufferResourceType:
if (presentation_mode != kAllowImageChromiumPresentationMode)
continue;
if (!gpu::IsImageSizeValidForGpuMemoryBufferFormat(
gfx::Size(size), color_params.GetBufferFormat())) {
continue;
}
provider = std::make_unique<CanvasResourceProviderRamGpuMemoryBuffer>(
size, color_params, resource_dispatcher);
break;
case kSharedBitmapResourceType:
if (!IsBitmapFormatSupported(color_params.TransferableResourceFormat()))
continue;
if (!resource_dispatcher)
continue;
provider = std::make_unique<CanvasResourceProviderSharedBitmap>(
size, color_params, resource_dispatcher);
break;
case kTextureResourceType:
DCHECK(SharedGpuContext::IsGpuCompositingEnabled());
provider = std::make_unique<CanvasResourceProviderTexture>(
size, msaa_sample_count, color_params, context_provider_wrapper,
resource_dispatcher);
break;
case kBitmapResourceType:
provider = std::make_unique<CanvasResourceProviderBitmap>(
size, color_params, resource_dispatcher);
break;
}
if (provider && provider->IsValid())
return provider;
}
return nullptr;
}
CanvasResourceProvider::CanvasImageProvider::CanvasImageProvider(
cc::ImageDecodeCache* cache,
const gfx::ColorSpace& target_color_space)
: playback_image_provider_(cache,
target_color_space,
cc::PlaybackImageProvider::Settings()) {}
CanvasResourceProvider::CanvasImageProvider::~CanvasImageProvider() = default;
cc::ImageProvider::ScopedDecodedDrawImage
CanvasResourceProvider::CanvasImageProvider::GetDecodedDrawImage(
const cc::DrawImage& draw_image) {
auto scoped_decoded_image =
playback_image_provider_.GetDecodedDrawImage(draw_image);
if (!scoped_decoded_image.needs_unlock())
return scoped_decoded_image;
if (!scoped_decoded_image.decoded_image().is_budgeted()) {
// If we have exceeded the budget, ReleaseLockedImages any locked decodes.
ReleaseLockedImages();
}
// It is safe to use base::Unretained, since decodes acquired from a provider
// must not exceed the provider's lifetime.
auto decoded_draw_image = scoped_decoded_image.decoded_image();
return ScopedDecodedDrawImage(
decoded_draw_image,
base::BindOnce(&CanvasImageProvider::CanUnlockImage,
base::Unretained(this), std::move(scoped_decoded_image)));
}
void CanvasResourceProvider::CanvasImageProvider::ReleaseLockedImages() {
locked_images_.clear();
}
void CanvasResourceProvider::CanvasImageProvider::CanUnlockImage(
ScopedDecodedDrawImage image) {
locked_images_.push_back(std::move(image));
}
CanvasResourceProvider::CanvasResourceProvider(
const IntSize& size,
const CanvasColorParams& color_params,
base::WeakPtr<WebGraphicsContext3DProviderWrapper> context_provider_wrapper,
base::WeakPtr<CanvasResourceDispatcher> resource_dispatcher)
: context_provider_wrapper_(std::move(context_provider_wrapper)),
resource_dispatcher_(resource_dispatcher),
size_(size),
color_params_(color_params),
snapshot_paint_image_id_(cc::PaintImage::GetNextId()),
weak_ptr_factory_(this) {
if (context_provider_wrapper_)
context_provider_wrapper_->AddObserver(this);
}
CanvasResourceProvider::~CanvasResourceProvider() {
if (context_provider_wrapper_)
context_provider_wrapper_->RemoveObserver(this);
}
SkSurface* CanvasResourceProvider::GetSkSurface() const {
if (!surface_)
surface_ = CreateSkSurface();
return surface_.get();
}
PaintCanvas* CanvasResourceProvider::Canvas() {
if (!canvas_) {
TRACE_EVENT0("blink", "CanvasResourceProvider::Canvas");
DCHECK(!canvas_image_provider_);
gfx::ColorSpace target_color_space =
ColorParams().NeedsSkColorSpaceXformCanvas()
? ColorParams().GetStorageGfxColorSpace()
: gfx::ColorSpace::CreateSRGB();
canvas_image_provider_.emplace(ImageDecodeCache(), target_color_space);
cc::ImageProvider* image_provider = &*canvas_image_provider_;
cc::SkiaPaintCanvas::ContextFlushes context_flushes;
if (IsAccelerated() &&
!ContextProviderWrapper()
->ContextProvider()
->GetGpuFeatureInfo()
.IsWorkaroundEnabled(gpu::DISABLE_2D_CANVAS_AUTO_FLUSH)) {
context_flushes.enable =
CanvasHeuristicParameters::kEnableGrContextFlushes;
context_flushes.max_draws_before_flush =
CanvasHeuristicParameters::kMaxDrawsBeforeContextFlush;
}
if (ColorParams().NeedsSkColorSpaceXformCanvas()) {
canvas_ = std::make_unique<cc::SkiaPaintCanvas>(
GetSkSurface()->getCanvas(), ColorParams().GetSkColorSpace(),
image_provider, context_flushes);
} else {
canvas_ = std::make_unique<cc::SkiaPaintCanvas>(
GetSkSurface()->getCanvas(), image_provider, context_flushes);
}
}
return canvas_.get();
}
void CanvasResourceProvider::OnContextDestroyed() {
if (canvas_image_provider_) {
DCHECK(canvas_);
canvas_->reset_image_provider();
canvas_image_provider_.reset();
}
}
void CanvasResourceProvider::ReleaseLockedImages() {
if (canvas_image_provider_)
canvas_image_provider_->ReleaseLockedImages();
}
scoped_refptr<StaticBitmapImage> CanvasResourceProvider::Snapshot() {
if (!IsValid())
return nullptr;
auto sk_image = GetSkSurface()->makeImageSnapshot();
auto last_snapshot_sk_image_id = snapshot_sk_image_id_;
snapshot_sk_image_id_ = sk_image->uniqueID();
if (ContextProviderWrapper()) {
return StaticBitmapImage::Create(std::move(sk_image),
ContextProviderWrapper());
}
// Ensure that a new PaintImage::ContentId is used only when the underlying
// SkImage changes. This is necessary to ensure that the same image results
// in a cache hit in cc's ImageDecodeCache.
if (snapshot_paint_image_content_id_ == PaintImage::kInvalidContentId ||
last_snapshot_sk_image_id != snapshot_sk_image_id_) {
snapshot_paint_image_content_id_ = PaintImage::GetNextContentId();
}
auto paint_image =
PaintImageBuilder::WithDefault()
.set_id(snapshot_paint_image_id_)
.set_image(std::move(sk_image), snapshot_paint_image_content_id_)
.TakePaintImage();
return StaticBitmapImage::Create(std::move(paint_image));
}
gpu::gles2::GLES2Interface* CanvasResourceProvider::ContextGL() const {
if (!context_provider_wrapper_)
return nullptr;
return context_provider_wrapper_->ContextProvider()->ContextGL();
}
GrContext* CanvasResourceProvider::GetGrContext() const {
if (!context_provider_wrapper_)
return nullptr;
return context_provider_wrapper_->ContextProvider()->GetGrContext();
}
void CanvasResourceProvider::FlushSkia() const {
GetSkSurface()->flush();
}
bool CanvasResourceProvider::IsGpuContextLost() const {
auto* gl = ContextGL();
return !gl || gl->GetGraphicsResetStatusKHR() != GL_NO_ERROR;
}
bool CanvasResourceProvider::WritePixels(const SkImageInfo& orig_info,
const void* pixels,
size_t row_bytes,
int x,
int y) {
TRACE_EVENT0("blink", "CanvasResourceProvider::WritePixels");
DCHECK(IsValid());
return GetSkSurface()->getCanvas()->writePixels(orig_info, pixels, row_bytes,
x, y);
}
void CanvasResourceProvider::Clear() {
// Clear the background transparent or opaque, as required. It would be nice
// if this wasn't required, but the canvas is currently filled with the magic
// transparency color. Can we have another way to manage this?
DCHECK(IsValid());
if (color_params_.GetOpacityMode() == kOpaque)
Canvas()->clear(SK_ColorBLACK);
else
Canvas()->clear(SK_ColorTRANSPARENT);
}
void CanvasResourceProvider::InvalidateSurface() {
canvas_ = nullptr;
canvas_image_provider_.reset();
xform_canvas_ = nullptr;
surface_ = nullptr;
}
uint32_t CanvasResourceProvider::ContentUniqueID() const {
return GetSkSurface()->generationID();
}
scoped_refptr<CanvasResource> CanvasResourceProvider::CreateResource() {
// Needs to be implemented in subclasses that use resource recycling.
NOTREACHED();
return nullptr;
}
cc::ImageDecodeCache* CanvasResourceProvider::ImageDecodeCache() {
if (context_provider_wrapper_)
return context_provider_wrapper_->ContextProvider()->ImageDecodeCache();
return &Image::SharedCCDecodeCache();
}
void CanvasResourceProvider::RecycleResource(
scoped_refptr<CanvasResource> resource) {
DCHECK(resource->HasOneRef());
if (resource_recycling_enabled_)
recycled_resources_.push_back(std::move(resource));
}
void CanvasResourceProvider::SetResourceRecyclingEnabled(bool value) {
resource_recycling_enabled_ = value;
if (!resource_recycling_enabled_)
ClearRecycledResources();
}
void CanvasResourceProvider::ClearRecycledResources() {
recycled_resources_.clear();
}
scoped_refptr<CanvasResource> CanvasResourceProvider::NewOrRecycledResource() {
if (recycled_resources_.size()) {
scoped_refptr<CanvasResource> resource =
std::move(recycled_resources_.back());
recycled_resources_.pop_back();
return resource;
}
return CreateResource();
}
} // namespace blink