blob: d2c3d9c419a1a4ce58dd997357a8a872b1209ebd [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/client/client_resource_provider.h"
#include "base/bind.h"
#include "base/bits.h"
#include "base/debug/stack_trace.h"
#include "base/threading/thread_task_runner_handle.h"
#include "build/build_config.h"
#include "components/viz/common/gpu/context_provider.h"
#include "components/viz/common/gpu/raster_context_provider.h"
#include "components/viz/common/resources/resource_format_utils.h"
#include "components/viz/common/resources/resource_sizes.h"
#include "components/viz/common/resources/returned_resource.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/raster_interface.h"
#include "gpu/command_buffer/common/capabilities.h"
#include "third_party/skia/include/core/SkCanvas.h"
namespace viz {
struct ClientResourceProvider::ImportedResource {
TransferableResource resource;
std::unique_ptr<SingleReleaseCallback> release_callback;
int exported_count = 0;
bool marked_for_deletion = false;
gpu::SyncToken returned_sync_token;
bool returned_lost = false;
#if DCHECK_IS_ON()
base::debug::StackTrace stack_trace;
#endif
ImportedResource(ResourceId id,
const TransferableResource& resource,
std::unique_ptr<SingleReleaseCallback> release_callback)
: resource(resource),
release_callback(std::move(release_callback)),
// If the resource is immediately deleted, it returns the same SyncToken
// it came with. The client may need to wait on that before deleting the
// backing or reusing it.
returned_sync_token(resource.mailbox_holder.sync_token) {
// Replace the |resource| id with the local id from this
// ClientResourceProvider.
this->resource.id = id;
}
~ImportedResource() = default;
ImportedResource(ImportedResource&&) = default;
ImportedResource& operator=(ImportedResource&&) = default;
};
ClientResourceProvider::ClientResourceProvider(
bool verified_sync_tokens_required)
: verified_sync_tokens_required_(verified_sync_tokens_required) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
}
ClientResourceProvider::~ClientResourceProvider() {
// If this fails, there are outstanding resources exported that should be
// lost and returned by calling ShutdownAndReleaseAllResources(), or there
// are resources that were imported without being removed by
// RemoveImportedResource(). In either case, calling
// ShutdownAndReleaseAllResources() will help, as it will report which
// resources were imported without being removed as well.
DCHECK(imported_resources_.empty());
}
gpu::SyncToken ClientResourceProvider::GenerateSyncTokenHelper(
gpu::gles2::GLES2Interface* gl) {
DCHECK(gl);
gpu::SyncToken sync_token;
gl->GenUnverifiedSyncTokenCHROMIUM(sync_token.GetData());
DCHECK(sync_token.HasData() ||
gl->GetGraphicsResetStatusKHR() != GL_NO_ERROR);
return sync_token;
}
gpu::SyncToken ClientResourceProvider::GenerateSyncTokenHelper(
gpu::raster::RasterInterface* ri) {
DCHECK(ri);
gpu::SyncToken sync_token;
ri->GenUnverifiedSyncTokenCHROMIUM(sync_token.GetData());
DCHECK(sync_token.HasData() ||
ri->GetGraphicsResetStatusKHR() != GL_NO_ERROR);
return sync_token;
}
void ClientResourceProvider::PrepareSendToParent(
const std::vector<ResourceId>& export_ids,
std::vector<TransferableResource>* list,
ContextProvider* context_provider) {
auto cb = base::BindOnce(
[](scoped_refptr<ContextProvider> context_provider,
std::vector<GLbyte*>* tokens) {
context_provider->ContextGL()->VerifySyncTokensCHROMIUM(tokens->data(),
tokens->size());
},
base::WrapRefCounted(context_provider));
PrepareSendToParentInternal(export_ids, list, std::move(cb));
}
void ClientResourceProvider::PrepareSendToParent(
const std::vector<ResourceId>& export_ids,
std::vector<TransferableResource>* list,
RasterContextProvider* context_provider) {
PrepareSendToParentInternal(
export_ids, list,
base::BindOnce(
[](scoped_refptr<RasterContextProvider> context_provider,
std::vector<GLbyte*>* tokens) {
context_provider->RasterInterface()->VerifySyncTokensCHROMIUM(
tokens->data(), tokens->size());
},
base::WrapRefCounted(context_provider)));
}
void ClientResourceProvider::PrepareSendToParentInternal(
const std::vector<ResourceId>& export_ids,
std::vector<TransferableResource>* list,
base::OnceCallback<void(std::vector<GLbyte*>* tokens)> verify_sync_tokens) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
// This function goes through the array multiple times, store the resources
// as pointers so we don't have to look up the resource id multiple times.
// Make sure the maps do not change while these vectors are alive or they
// will become invalid.
std::vector<ImportedResource*> imports;
imports.reserve(export_ids.size());
for (const ResourceId id : export_ids) {
auto it = imported_resources_.find(id);
DCHECK(it != imported_resources_.end());
imports.push_back(&it->second);
}
// Lazily create any mailboxes and verify all unverified sync tokens.
std::vector<GLbyte*> unverified_sync_tokens;
if (verified_sync_tokens_required_) {
for (ImportedResource* imported : imports) {
if (!imported->resource.is_software &&
!imported->resource.mailbox_holder.sync_token.verified_flush()) {
unverified_sync_tokens.push_back(
imported->resource.mailbox_holder.sync_token.GetData());
}
}
}
if (!unverified_sync_tokens.empty()) {
DCHECK(verified_sync_tokens_required_);
DCHECK(verify_sync_tokens);
std::move(verify_sync_tokens).Run(&unverified_sync_tokens);
}
for (ImportedResource* imported : imports) {
list->push_back(imported->resource);
imported->exported_count++;
}
}
void ClientResourceProvider::ReceiveReturnsFromParent(
std::vector<ReturnedResource> resources) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
// |imported_resources_| is a set sorted by id, so if we sort the incoming
// |resources| list by id also, we can walk through both structures in order,
// replacing things to be removed from imported_resources_ with things that
// we want to keep, making the removal of all items O(N+MlogM) instead of
// O(N*M). This algorithm is modelled after std::remove_if() but with a
// parallel walk through |resources| instead of an O(logM) lookup into
// |resources| for each step.
std::sort(resources.begin(), resources.end(),
[](const ReturnedResource& a, const ReturnedResource& b) {
return a.id < b.id;
});
auto returned_it = resources.begin();
auto returned_end = resources.end();
auto imported_keep_end_it = imported_resources_.begin();
auto imported_compare_it = imported_resources_.begin();
auto imported_end = imported_resources_.end();
std::vector<base::OnceClosure> release_callbacks;
release_callbacks.reserve(resources.size());
while (imported_compare_it != imported_end) {
if (returned_it == returned_end) {
// Nothing else to remove from |imported_resources_|.
if (imported_keep_end_it == imported_compare_it) {
// We didn't remove anything, so we're done.
break;
}
// If something was removed, we need to shift everything into the empty
// space that was made.
*imported_keep_end_it = std::move(*imported_compare_it);
++imported_keep_end_it;
++imported_compare_it;
continue;
}
const ResourceId returned_resource_id = returned_it->id;
const ResourceId imported_resource_id = imported_compare_it->first;
if (returned_resource_id != imported_resource_id) {
// They're both sorted, and everything being returned should already
// be in the imported list. So we should be able to walk through the
// resources list in order, and find each id in both sets when present.
// That means if it's not matching, we should be above it in the sorted
// order of the |imported_resources_|, allowing us to get to it by
// continuing to traverse |imported_resources_|.
DCHECK_GT(returned_resource_id, imported_resource_id);
// This means we want to keep the resource at |imported_compare_it|. So go
// to the next. If we removed anything previously and made empty space,
// fill it as we move.
if (imported_keep_end_it != imported_compare_it)
*imported_keep_end_it = std::move(*imported_compare_it);
++imported_keep_end_it;
++imported_compare_it;
continue;
}
const ReturnedResource& returned = *returned_it;
ImportedResource& imported = imported_compare_it->second;
DCHECK_GE(imported.exported_count, returned.count);
imported.exported_count -= returned.count;
imported.returned_lost |= returned.lost;
if (imported.exported_count) {
// Can't remove the imported yet so go to the next, looking for the next
// returned resource.
++returned_it;
// The same ResourceId may appear multiple times (in a row) in the
// returned set. In that case, we do not want to increment the iterators
// in |imported_resources_| yet. So we don't increment them here, and let
// the next iteration of the loop do so.
continue;
}
// Save the sync token only when the exported count is going to 0. Or IOW
// drop all by the last returned sync token.
if (returned.sync_token.HasData()) {
DCHECK(!imported.resource.is_software);
imported.returned_sync_token = returned.sync_token;
}
if (!imported.marked_for_deletion) {
// Not going to remove the imported yet so go to the next, looking for the
// next returned resource.
++returned_it;
// If we removed anything previously and made empty space, fill it as we
// move.
if (imported_keep_end_it != imported_compare_it)
*imported_keep_end_it = std::move(*imported_compare_it);
++imported_keep_end_it;
++imported_compare_it;
continue;
}
// Save the ReleaseCallback to run after iterating through internal data
// structures, in case it calls back into this class.
auto run_callback = [](std::unique_ptr<SingleReleaseCallback> cb,
const gpu::SyncToken& sync_token, bool lost) {
cb->Run(sync_token, lost);
// |cb| is destroyed when leaving scope.
};
release_callbacks.push_back(
base::BindOnce(run_callback, base::Passed(&imported.release_callback),
imported.returned_sync_token, imported.returned_lost));
// We don't want to keep this resource, so we leave |imported_keep_end_it|
// pointing to it (since it points past the end of what we're keeping). We
// do move |imported_compare_it| in order to move on to the next resource.
++imported_compare_it;
// The imported iterator is pointing at the next element already, so no need
// to increment it, and we can move on to looking for the next returned
// resource.
++returned_it;
}
// Drop anything that was moved after the |imported_end| iterator in a single
// O(N) operation.
if (imported_keep_end_it != imported_compare_it)
imported_resources_.erase(imported_keep_end_it, imported_resources_.end());
for (auto& cb : release_callbacks)
std::move(cb).Run();
}
ResourceId ClientResourceProvider::ImportResource(
const TransferableResource& resource,
std::unique_ptr<SingleReleaseCallback> release_callback) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
ResourceId id = next_id_++;
auto result = imported_resources_.emplace(
id, ImportedResource(id, resource, std::move(release_callback)));
DCHECK(result.second); // If false, the id was already in the map.
return id;
}
void ClientResourceProvider::RemoveImportedResource(ResourceId id) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
auto it = imported_resources_.find(id);
DCHECK(it != imported_resources_.end());
ImportedResource& imported = it->second;
imported.marked_for_deletion = true;
if (imported.exported_count == 0) {
imported.release_callback->Run(imported.returned_sync_token,
imported.returned_lost);
imported_resources_.erase(it);
}
}
void ClientResourceProvider::ReleaseAllExportedResources(bool lose) {
auto release_and_remove =
[lose](std::pair<ResourceId, ImportedResource>& pair) {
ImportedResource& imported = pair.second;
if (!imported.exported_count) {
// Not exported, not up for consideration to be returned here.
return false;
}
imported.exported_count = 0;
imported.returned_lost |= lose;
if (!imported.marked_for_deletion) {
// Was exported, but not removed by the client, so don't return it
// yet.
return false;
}
imported.release_callback->Run(imported.returned_sync_token,
imported.returned_lost);
// Was exported and removed by the client, so return it now.
return true;
};
// This will run |release_and_remove| on each element of |imported_resources_|
// and drop any resources from the set that it requests.
base::EraseIf(imported_resources_, release_and_remove);
}
void ClientResourceProvider::ShutdownAndReleaseAllResources() {
for (auto& pair : imported_resources_) {
ImportedResource& imported = pair.second;
#if DCHECK_IS_ON()
// If this is false, then the resource has not been removed via
// RemoveImportedResource(), and all resources should be removed before
// we resort to marking resources as lost during shutdown.
DCHECK(imported.marked_for_deletion)
<< "id: " << pair.first << " from:\n"
<< imported.stack_trace.ToString() << "===";
DCHECK(imported.exported_count) << "id: " << pair.first << " from:\n"
<< imported.stack_trace.ToString() << "===";
#endif
imported.release_callback->Run(imported.returned_sync_token,
/*is_lost=*/true);
}
imported_resources_.clear();
}
ClientResourceProvider::ScopedSkSurface::ScopedSkSurface(
GrContext* gr_context,
sk_sp<SkColorSpace> color_space,
GLuint texture_id,
GLenum texture_target,
const gfx::Size& size,
ResourceFormat format,
bool can_use_lcd_text,
int msaa_sample_count) {
GrGLTextureInfo texture_info;
texture_info.fID = texture_id;
texture_info.fTarget = texture_target;
texture_info.fFormat = TextureStorageFormat(format);
GrBackendTexture backend_texture(size.width(), size.height(),
GrMipMapped::kNo, texture_info);
SkSurfaceProps surface_props = ComputeSurfaceProps(can_use_lcd_text);
// This type is used only for gpu raster, which implies gpu compositing.
bool gpu_compositing = true;
surface_ = SkSurface::MakeFromBackendTextureAsRenderTarget(
gr_context, backend_texture, kTopLeft_GrSurfaceOrigin, msaa_sample_count,
ResourceFormatToClosestSkColorType(gpu_compositing, format), color_space,
&surface_props);
}
ClientResourceProvider::ScopedSkSurface::~ScopedSkSurface() {
if (surface_)
surface_->flush();
}
SkSurfaceProps ClientResourceProvider::ScopedSkSurface::ComputeSurfaceProps(
bool can_use_lcd_text) {
uint32_t flags = 0;
// Use unknown pixel geometry to disable LCD text.
SkSurfaceProps surface_props(flags, kUnknown_SkPixelGeometry);
if (can_use_lcd_text) {
// LegacyFontHost will get LCD text and skia figures out what type to use.
surface_props =
SkSurfaceProps(flags, SkSurfaceProps::kLegacyFontHost_InitType);
}
return surface_props;
}
void ClientResourceProvider::ValidateResource(ResourceId id) const {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(id);
DCHECK(imported_resources_.find(id) != imported_resources_.end());
}
bool ClientResourceProvider::InUseByConsumer(ResourceId id) {
auto it = imported_resources_.find(id);
DCHECK(it != imported_resources_.end());
ImportedResource& imported = it->second;
return imported.exported_count > 0 || imported.returned_lost;
}
size_t ClientResourceProvider::num_resources_for_testing() const {
return imported_resources_.size();
}
} // namespace viz