blob: 4a627df949e8191377a6f3ec5b3bfd4e47cf45dc [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 "cc/resources/display_resource_provider.h"
#include "base/trace_event/trace_event.h"
#include "components/viz/common/gpu/context_provider.h"
#include "components/viz/common/resources/resource_format_utils.h"
#include "components/viz/common/resources/shared_bitmap_manager.h"
#include "gpu/command_buffer/client/gles2_interface.h"
#include "third_party/skia/include/gpu/GrBackendSurface.h"
using gpu::gles2::GLES2Interface;
namespace cc {
static GLint GetActiveTextureUnit(GLES2Interface* gl) {
GLint active_unit = 0;
gl->GetIntegerv(GL_ACTIVE_TEXTURE, &active_unit);
return active_unit;
}
class ScopedSetActiveTexture {
public:
ScopedSetActiveTexture(GLES2Interface* gl, GLenum unit)
: gl_(gl), unit_(unit) {
DCHECK_EQ(GL_TEXTURE0, GetActiveTextureUnit(gl_));
if (unit_ != GL_TEXTURE0)
gl_->ActiveTexture(unit_);
}
~ScopedSetActiveTexture() {
// Active unit being GL_TEXTURE0 is effectively the ground state.
if (unit_ != GL_TEXTURE0)
gl_->ActiveTexture(GL_TEXTURE0);
}
private:
GLES2Interface* gl_;
GLenum unit_;
};
namespace {
// The resource id in DisplayResourceProvider starts from 2 to avoid
// conflicts with id from LayerTreeResourceProvider.
const unsigned int kDisplayInitialResourceId = 2;
} // namespace
DisplayResourceProvider::DisplayResourceProvider(
viz::ContextProvider* compositor_context_provider,
viz::SharedBitmapManager* shared_bitmap_manager)
: ResourceProvider(compositor_context_provider),
next_id_(kDisplayInitialResourceId),
shared_bitmap_manager_(shared_bitmap_manager) {}
DisplayResourceProvider::~DisplayResourceProvider() {
while (!children_.empty())
DestroyChildInternal(children_.begin(), FOR_SHUTDOWN);
GLES2Interface* gl = ContextGL();
if (gl)
gl->Finish();
}
#if defined(OS_ANDROID)
void DisplayResourceProvider::SendPromotionHints(
const OverlayCandidateList::PromotionHintInfoMap& promotion_hints) {
GLES2Interface* gl = ContextGL();
if (!gl)
return;
for (const auto& id : wants_promotion_hints_set_) {
const ResourceMap::iterator it = resources_.find(id);
if (it == resources_.end())
continue;
if (it->second.marked_for_deletion)
continue;
const viz::internal::Resource* resource = LockForRead(id);
DCHECK(resource->wants_promotion_hint);
// Insist that this is backed by a GPU texture.
if (resource->is_gpu_resource_type()) {
DCHECK(resource->gl_id);
auto iter = promotion_hints.find(id);
bool promotable = iter != promotion_hints.end();
gl->OverlayPromotionHintCHROMIUM(resource->gl_id, promotable,
promotable ? iter->second.x() : 0,
promotable ? iter->second.y() : 0,
promotable ? iter->second.width() : 0,
promotable ? iter->second.height() : 0);
}
UnlockForRead(id);
}
}
void DisplayResourceProvider::DeletePromotionHint(ResourceMap::iterator it,
DeleteStyle style) {
viz::internal::Resource* resource = &it->second;
// If this resource was interested in promotion hints, then remove it from
// the set of resources that we'll notify.
if (resource->wants_promotion_hint)
wants_promotion_hints_set_.erase(it->first);
}
bool DisplayResourceProvider::IsBackedBySurfaceTexture(viz::ResourceId id) {
viz::internal::Resource* resource = GetResource(id);
return resource->is_backed_by_surface_texture;
}
bool DisplayResourceProvider::WantsPromotionHintForTesting(viz::ResourceId id) {
return wants_promotion_hints_set_.count(id) > 0;
}
size_t DisplayResourceProvider::CountPromotionHintRequestsForTesting() {
return wants_promotion_hints_set_.size();
}
#endif
bool DisplayResourceProvider::IsOverlayCandidate(viz::ResourceId id) {
viz::internal::Resource* resource = GetResource(id);
return resource->is_overlay_candidate;
}
viz::ResourceType DisplayResourceProvider::GetResourceType(viz::ResourceId id) {
return GetResource(id)->type;
}
gfx::BufferFormat DisplayResourceProvider::GetBufferFormat(viz::ResourceId id) {
viz::internal::Resource* resource = GetResource(id);
return resource->buffer_format;
}
void DisplayResourceProvider::WaitSyncToken(viz::ResourceId id) {
viz::internal::Resource* resource = GetResource(id);
WaitSyncTokenInternal(resource);
#if defined(OS_ANDROID)
// Now that the resource is synced, we may send it a promotion hint. We could
// sync all |wants_promotion_hint| resources elsewhere, and send 'no' to all
// resources that weren't used. However, there's no real advantage.
if (resource->wants_promotion_hint)
wants_promotion_hints_set_.insert(id);
#endif // OS_ANDROID
}
DisplayResourceProvider::ScopedBatchReturnResources::ScopedBatchReturnResources(
DisplayResourceProvider* resource_provider)
: resource_provider_(resource_provider) {
resource_provider_->SetBatchReturnResources(true);
}
DisplayResourceProvider::ScopedBatchReturnResources::
~ScopedBatchReturnResources() {
resource_provider_->SetBatchReturnResources(false);
}
void DisplayResourceProvider::SetBatchReturnResources(bool batch) {
DCHECK_NE(batch_return_resources_, batch);
batch_return_resources_ = batch;
if (!batch) {
for (const auto& resources : batched_returning_resources_) {
ChildMap::iterator child_it = children_.find(resources.first);
DCHECK(child_it != children_.end());
DeleteAndReturnUnusedResourcesToChild(child_it, NORMAL, resources.second);
}
batched_returning_resources_.clear();
}
}
DisplayResourceProvider::Child::Child()
: marked_for_deletion(false), needs_sync_tokens(true) {}
DisplayResourceProvider::Child::Child(const Child& other) = default;
DisplayResourceProvider::Child::~Child() = default;
int DisplayResourceProvider::CreateChild(
const ReturnCallback& return_callback) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
Child child_info;
child_info.return_callback = return_callback;
int child = next_child_++;
children_[child] = child_info;
return child;
}
void DisplayResourceProvider::SetChildNeedsSyncTokens(int child_id,
bool needs) {
ChildMap::iterator it = children_.find(child_id);
DCHECK(it != children_.end());
it->second.needs_sync_tokens = needs;
}
void DisplayResourceProvider::DestroyChild(int child_id) {
ChildMap::iterator it = children_.find(child_id);
DCHECK(it != children_.end());
DestroyChildInternal(it, NORMAL);
}
void DisplayResourceProvider::DestroyChildInternal(ChildMap::iterator it,
DeleteStyle style) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
Child& child = it->second;
DCHECK(style == FOR_SHUTDOWN || !child.marked_for_deletion);
ResourceIdArray resources_for_child;
for (ResourceIdMap::iterator child_it = child.child_to_parent_map.begin();
child_it != child.child_to_parent_map.end(); ++child_it) {
viz::ResourceId id = child_it->second;
resources_for_child.push_back(id);
}
child.marked_for_deletion = true;
DeleteAndReturnUnusedResourcesToChild(it, style, resources_for_child);
}
void DisplayResourceProvider::DeleteAndReturnUnusedResourcesToChild(
ChildMap::iterator child_it,
DeleteStyle style,
const ResourceIdArray& unused) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(child_it != children_.end());
Child* child_info = &child_it->second;
if (unused.empty() && !child_info->marked_for_deletion)
return;
std::vector<viz::ReturnedResource> to_return;
to_return.reserve(unused.size());
std::vector<viz::ReturnedResource*> need_synchronization_resources;
std::vector<GLbyte*> unverified_sync_tokens;
std::vector<size_t> to_return_indices_unverified;
GLES2Interface* gl = ContextGL();
for (viz::ResourceId local_id : unused) {
ResourceMap::iterator it = resources_.find(local_id);
CHECK(it != resources_.end());
viz::internal::Resource& resource = it->second;
// TODO(xing.xu): remove locked_for_write.
DCHECK(!resource.locked_for_write);
viz::ResourceId child_id = resource.id_in_child;
DCHECK(child_info->child_to_parent_map.count(child_id));
bool is_lost = resource.lost ||
(resource.is_gpu_resource_type() && lost_context_provider_);
if (resource.exported_count > 0 || resource.lock_for_read_count > 0) {
if (style != FOR_SHUTDOWN) {
// Defer this resource deletion.
resource.marked_for_deletion = true;
continue;
}
// We can't postpone the deletion, so we'll have to lose it.
is_lost = true;
} else if (!ReadLockFenceHasPassed(&resource)) {
// TODO(dcastagna): see if it's possible to use this logic for
// the branch above too, where the resource is locked or still exported.
if (style != FOR_SHUTDOWN && !child_info->marked_for_deletion) {
// Defer this resource deletion.
resource.marked_for_deletion = true;
continue;
}
// We can't postpone the deletion, so we'll have to lose it.
is_lost = true;
}
if (resource.is_gpu_resource_type() &&
resource.filter != resource.original_filter) {
DCHECK(resource.target);
DCHECK(resource.gl_id);
DCHECK(gl);
gl->BindTexture(resource.target, resource.gl_id);
gl->TexParameteri(resource.target, GL_TEXTURE_MIN_FILTER,
resource.original_filter);
gl->TexParameteri(resource.target, GL_TEXTURE_MAG_FILTER,
resource.original_filter);
resource.SetLocallyUsed();
}
viz::ReturnedResource returned;
returned.id = child_id;
returned.sync_token = resource.sync_token();
returned.count = resource.imported_count;
returned.lost = is_lost;
to_return.push_back(returned);
if (resource.is_gpu_resource_type() && child_info->needs_sync_tokens) {
if (resource.needs_sync_token()) {
need_synchronization_resources.push_back(&to_return.back());
} else if (returned.sync_token.HasData() &&
!returned.sync_token.verified_flush()) {
// Before returning any sync tokens, they must be verified. Store an
// index into |to_return| instead of a pointer as vectors may realloc
// and move their data.
to_return_indices_unverified.push_back(to_return.size() - 1);
}
}
child_info->child_to_parent_map.erase(child_id);
resource.imported_count = 0;
#if defined(OS_ANDROID)
DeletePromotionHint(it, style);
#endif
DeleteResourceInternal(it, style);
}
for (size_t i : to_return_indices_unverified)
unverified_sync_tokens.push_back(to_return[i].sync_token.GetData());
gpu::SyncToken new_sync_token;
if (!need_synchronization_resources.empty()) {
DCHECK(child_info->needs_sync_tokens);
DCHECK(gl);
gl->GenUnverifiedSyncTokenCHROMIUM(new_sync_token.GetData());
unverified_sync_tokens.push_back(new_sync_token.GetData());
}
if (!unverified_sync_tokens.empty()) {
DCHECK(child_info->needs_sync_tokens);
DCHECK(gl);
gl->VerifySyncTokensCHROMIUM(unverified_sync_tokens.data(),
unverified_sync_tokens.size());
}
// Set sync token after verification.
for (viz::ReturnedResource* returned : need_synchronization_resources)
returned->sync_token = new_sync_token;
if (!to_return.empty())
child_info->return_callback.Run(to_return);
if (child_info->marked_for_deletion &&
child_info->child_to_parent_map.empty()) {
children_.erase(child_it);
}
}
void DisplayResourceProvider::ReceiveFromChild(
int child,
const std::vector<viz::TransferableResource>& resources) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
GLES2Interface* gl = ContextGL();
Child& child_info = children_.find(child)->second;
DCHECK(!child_info.marked_for_deletion);
for (std::vector<viz::TransferableResource>::const_iterator it =
resources.begin();
it != resources.end(); ++it) {
ResourceIdMap::iterator resource_in_map_it =
child_info.child_to_parent_map.find(it->id);
if (resource_in_map_it != child_info.child_to_parent_map.end()) {
viz::internal::Resource* resource =
GetResource(resource_in_map_it->second);
resource->marked_for_deletion = false;
resource->imported_count++;
continue;
}
if ((!it->is_software && !gl) ||
(it->is_software && !shared_bitmap_manager_)) {
TRACE_EVENT0(
"cc", "DisplayResourceProvider::ReceiveFromChild dropping invalid");
std::vector<viz::ReturnedResource> to_return;
to_return.push_back(it->ToReturnedResource());
child_info.return_callback.Run(to_return);
continue;
}
viz::ResourceId local_id = next_id_++;
viz::internal::Resource* resource = nullptr;
if (it->is_software) {
resource = InsertResource(
local_id,
viz::internal::Resource(it->size, viz::internal::Resource::DELEGATED,
viz::ResourceTextureHint::kDefault,
viz::ResourceType::kBitmap, viz::RGBA_8888,
it->color_space));
resource->has_shared_bitmap_id = true;
resource->shared_bitmap_id = it->mailbox_holder.mailbox;
} else {
resource = InsertResource(
local_id,
viz::internal::Resource(it->size, viz::internal::Resource::DELEGATED,
viz::ResourceTextureHint::kDefault,
viz::ResourceType::kTexture, it->format,
it->color_space));
resource->target = it->mailbox_holder.texture_target;
resource->filter = it->filter;
resource->original_filter = it->filter;
resource->min_filter = it->filter;
resource->buffer_format = it->buffer_format;
resource->mailbox = it->mailbox_holder.mailbox;
resource->UpdateSyncToken(it->mailbox_holder.sync_token);
resource->read_lock_fences_enabled = it->read_lock_fences_enabled;
resource->is_overlay_candidate = it->is_overlay_candidate;
#if defined(OS_ANDROID)
resource->is_backed_by_surface_texture = it->is_backed_by_surface_texture;
resource->wants_promotion_hint = it->wants_promotion_hint;
#endif
}
resource->child_id = child;
// Don't allocate a texture for a child.
resource->allocated = true;
resource->imported_count = 1;
resource->id_in_child = it->id;
child_info.child_to_parent_map[it->id] = local_id;
}
}
void DisplayResourceProvider::DeclareUsedResourcesFromChild(
int child,
const viz::ResourceIdSet& resources_from_child) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
ChildMap::iterator child_it = children_.find(child);
DCHECK(child_it != children_.end());
Child& child_info = child_it->second;
DCHECK(!child_info.marked_for_deletion);
ResourceIdArray unused;
for (ResourceIdMap::iterator it = child_info.child_to_parent_map.begin();
it != child_info.child_to_parent_map.end(); ++it) {
viz::ResourceId local_id = it->second;
bool resource_is_in_use = resources_from_child.count(it->first) > 0;
if (!resource_is_in_use)
unused.push_back(local_id);
}
DeleteAndReturnUnusedResourcesToChild(child_it, NORMAL, unused);
}
const ResourceProvider::ResourceIdMap&
DisplayResourceProvider::GetChildToParentMap(int child) const {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
ChildMap::const_iterator it = children_.find(child);
DCHECK(it != children_.end());
DCHECK(!it->second.marked_for_deletion);
return it->second.child_to_parent_map;
}
GLenum DisplayResourceProvider::BindForSampling(viz::ResourceId resource_id,
GLenum unit,
GLenum filter) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
GLES2Interface* gl = ContextGL();
ResourceMap::iterator it = resources_.find(resource_id);
DCHECK(it != resources_.end());
viz::internal::Resource* resource = &it->second;
DCHECK(resource->lock_for_read_count);
// TODO(xing.xu): remove locked_for_write.
DCHECK(!resource->locked_for_write);
ScopedSetActiveTexture scoped_active_tex(gl, unit);
GLenum target = resource->target;
gl->BindTexture(target, resource->gl_id);
GLenum min_filter = filter;
if (filter == GL_LINEAR) {
switch (resource->mipmap_state) {
case viz::internal::Resource::INVALID:
break;
case viz::internal::Resource::GENERATE:
DCHECK(compositor_context_provider_);
DCHECK(
compositor_context_provider_->ContextCapabilities().texture_npot);
gl->GenerateMipmap(target);
resource->mipmap_state = viz::internal::Resource::VALID;
// fall-through
case viz::internal::Resource::VALID:
min_filter = GL_LINEAR_MIPMAP_LINEAR;
break;
}
}
if (min_filter != resource->min_filter) {
gl->TexParameteri(target, GL_TEXTURE_MIN_FILTER, min_filter);
resource->min_filter = min_filter;
}
if (filter != resource->filter) {
gl->TexParameteri(target, GL_TEXTURE_MAG_FILTER, filter);
resource->filter = filter;
}
return target;
}
bool DisplayResourceProvider::InUse(viz::ResourceId id) {
viz::internal::Resource* resource = GetResource(id);
return resource->lock_for_read_count > 0 || resource->lost;
}
DisplayResourceProvider::ScopedReadLockGL::ScopedReadLockGL(
DisplayResourceProvider* resource_provider,
viz::ResourceId resource_id)
: resource_provider_(resource_provider), resource_id_(resource_id) {
const viz::internal::Resource* resource =
resource_provider->LockForRead(resource_id);
texture_id_ = resource->gl_id;
target_ = resource->target;
size_ = resource->size;
color_space_ = resource->color_space;
}
const viz::internal::Resource* DisplayResourceProvider::LockForRead(
viz::ResourceId id) {
viz::internal::Resource* resource = GetResource(id);
// TODO(xing.xu): remove locked_for_write.
DCHECK(!resource->locked_for_write)
<< "locked for write: " << resource->locked_for_write;
DCHECK_EQ(resource->exported_count, 0);
// Uninitialized! Call SetPixels or LockForWrite first.
DCHECK(resource->allocated);
// Mailbox sync_tokens must be processed by a call to WaitSyncToken() prior to
// calling LockForRead().
DCHECK_NE(viz::internal::Resource::NEEDS_WAIT,
resource->synchronization_state());
if (resource->is_gpu_resource_type() && !resource->gl_id) {
DCHECK(resource->origin != viz::internal::Resource::INTERNAL);
DCHECK(!resource->mailbox.IsZero());
GLES2Interface* gl = ContextGL();
DCHECK(gl);
resource->gl_id =
gl->CreateAndConsumeTextureCHROMIUM(resource->mailbox.name);
resource->SetLocallyUsed();
}
if (!resource->pixels && resource->has_shared_bitmap_id &&
shared_bitmap_manager_) {
std::unique_ptr<viz::SharedBitmap> bitmap =
shared_bitmap_manager_->GetSharedBitmapFromId(
resource->size, resource->shared_bitmap_id);
if (bitmap) {
resource->SetSharedBitmap(bitmap.get());
resource->owned_shared_bitmap = std::move(bitmap);
}
}
resource->lock_for_read_count++;
if (resource->read_lock_fences_enabled) {
if (current_read_lock_fence_.get())
current_read_lock_fence_->Set();
resource->read_lock_fence = current_read_lock_fence_;
}
return resource;
}
void DisplayResourceProvider::UnlockForRead(viz::ResourceId id) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
ResourceMap::iterator it = resources_.find(id);
CHECK(it != resources_.end());
viz::internal::Resource* resource = &it->second;
DCHECK_GT(resource->lock_for_read_count, 0);
DCHECK_EQ(resource->exported_count, 0);
resource->lock_for_read_count--;
if (resource->marked_for_deletion && !resource->lock_for_read_count) {
if (!resource->child_id) {
// The resource belongs to this ResourceProvider, so it can be destroyed.
#if defined(OS_ANDROID)
DeletePromotionHint(it, NORMAL);
#endif
DeleteResourceInternal(it, NORMAL);
} else {
if (batch_return_resources_) {
batched_returning_resources_[resource->child_id].push_back(id);
} else {
ChildMap::iterator child_it = children_.find(resource->child_id);
ResourceIdArray unused;
unused.push_back(id);
DeleteAndReturnUnusedResourcesToChild(child_it, NORMAL, unused);
}
}
}
}
bool DisplayResourceProvider::ReadLockFenceHasPassed(
const viz::internal::Resource* resource) {
return !resource->read_lock_fence || resource->read_lock_fence->HasPassed();
}
DisplayResourceProvider::ScopedReadLockGL::~ScopedReadLockGL() {
resource_provider_->UnlockForRead(resource_id_);
}
DisplayResourceProvider::ScopedSamplerGL::ScopedSamplerGL(
DisplayResourceProvider* resource_provider,
viz::ResourceId resource_id,
GLenum filter)
: resource_lock_(resource_provider, resource_id),
unit_(GL_TEXTURE0),
target_(resource_provider->BindForSampling(resource_id, unit_, filter)) {}
DisplayResourceProvider::ScopedSamplerGL::ScopedSamplerGL(
DisplayResourceProvider* resource_provider,
viz::ResourceId resource_id,
GLenum unit,
GLenum filter)
: resource_lock_(resource_provider, resource_id),
unit_(unit),
target_(resource_provider->BindForSampling(resource_id, unit_, filter)) {}
DisplayResourceProvider::ScopedSamplerGL::~ScopedSamplerGL() = default;
DisplayResourceProvider::ScopedReadLockSkImage::ScopedReadLockSkImage(
DisplayResourceProvider* resource_provider,
viz::ResourceId resource_id)
: resource_provider_(resource_provider), resource_id_(resource_id) {
const viz::internal::Resource* resource =
resource_provider->LockForRead(resource_id);
if (resource_provider_->resource_sk_image_.find(resource_id) !=
resource_provider_->resource_sk_image_.end()) {
// Use cached sk_image.
sk_image_ =
resource_provider_->resource_sk_image_.find(resource_id)->second;
} else if (resource->gl_id) {
GrGLTextureInfo texture_info;
texture_info.fID = resource->gl_id;
texture_info.fTarget = resource->target;
GrBackendTexture backend_texture(
resource->size.width(), resource->size.height(),
ToGrPixelConfig(resource->format), texture_info);
sk_image_ = SkImage::MakeFromTexture(
resource_provider->compositor_context_provider_->GrContext(),
backend_texture, kTopLeft_GrSurfaceOrigin, kPremul_SkAlphaType,
nullptr);
} else if (resource->pixels) {
SkBitmap sk_bitmap;
resource_provider->PopulateSkBitmapWithResource(&sk_bitmap, resource);
sk_bitmap.setImmutable();
sk_image_ = SkImage::MakeFromBitmap(sk_bitmap);
} else {
// During render process shutdown, ~RenderMessageFilter which calls
// ~HostSharedBitmapClient (which deletes shared bitmaps from child)
// can race with OnBeginFrameDeadline which draws a frame.
// In these cases, shared bitmaps (and this read lock) won't be valid.
// Renderers need to silently handle locks failing until this race
// is fixed. DCHECK that this is the only case where there are no pixels.
DCHECK(!resource->shared_bitmap_id.IsZero());
}
}
DisplayResourceProvider::ScopedReadLockSkImage::~ScopedReadLockSkImage() {
resource_provider_->UnlockForRead(resource_id_);
}
DisplayResourceProvider::ScopedReadLockSoftware::ScopedReadLockSoftware(
DisplayResourceProvider* resource_provider,
viz::ResourceId resource_id)
: resource_provider_(resource_provider), resource_id_(resource_id) {
const viz::internal::Resource* resource =
resource_provider->LockForRead(resource_id);
resource_provider->PopulateSkBitmapWithResource(&sk_bitmap_, resource);
}
DisplayResourceProvider::ScopedReadLockSoftware::~ScopedReadLockSoftware() {
resource_provider_->UnlockForRead(resource_id_);
}
DisplayResourceProvider::SynchronousFence::SynchronousFence(
gpu::gles2::GLES2Interface* gl)
: gl_(gl), has_synchronized_(true) {}
DisplayResourceProvider::SynchronousFence::~SynchronousFence() = default;
void DisplayResourceProvider::SynchronousFence::Set() {
has_synchronized_ = false;
}
bool DisplayResourceProvider::SynchronousFence::HasPassed() {
if (!has_synchronized_) {
has_synchronized_ = true;
Synchronize();
}
return true;
}
void DisplayResourceProvider::SynchronousFence::Wait() {
HasPassed();
}
void DisplayResourceProvider::SynchronousFence::Synchronize() {
TRACE_EVENT0("cc", "DisplayResourceProvider::SynchronousFence::Synchronize");
gl_->Finish();
}
} // namespace cc