blob: f0301b3ce5f3f4e640897b5aa2c2d373c6c6e961 [file] [log] [blame]
// Copyright 2019 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 "gpu/command_buffer/service/shared_image_backing_factory_iosurface.h"
#include "base/mac/scoped_cftyperef.h"
#include "base/memory/scoped_refptr.h"
#include "base/optional.h"
#include "components/viz/common/resources/resource_format_utils.h"
#include "components/viz/common/resources/resource_sizes.h"
#include "gpu/command_buffer/service/mailbox_manager.h"
#include "gpu/command_buffer/service/shared_context_state.h"
#include "gpu/command_buffer/service/shared_image_backing.h"
#include "gpu/command_buffer/service/shared_image_representation.h"
#include "gpu/command_buffer/service/skia_utils.h"
#include "gpu/command_buffer/service/texture_manager.h"
#include "third_party/skia/include/core/SkPromiseImageTexture.h"
#include "ui/gfx/mac/io_surface.h"
#include "ui/gl/buildflags.h"
#include "ui/gl/gl_context.h"
#include "ui/gl/gl_image_io_surface.h"
// Usage of BUILDFLAG(USE_DAWN) needs to be after the include for
// ui/gl/buildflags.h
#if BUILDFLAG(USE_DAWN)
#include <dawn_native/MetalBackend.h>
#endif // BUILDFLAG(USE_DAWN)
namespace gpu {
namespace {
struct GLFormatInfo {
bool supported = false;
// GL internal_format/format/type triplet.
GLuint internal_format = 0;
GLenum format = 0;
GLenum type = 0;
};
// Get GL format triplets and modify them to match the logic in
// gl_image_iosurface.mm
GLFormatInfo GetGLFormatInfo(viz::ResourceFormat format) {
GLFormatInfo info = {
true,
viz::GLInternalFormat(format),
viz::GLDataFormat(format),
viz::GLDataType(format),
};
if (info.internal_format == GL_ZERO || info.format == GL_ZERO ||
info.type == GL_ZERO) {
return {false, GL_ZERO, GL_ZERO, GL_ZERO};
}
switch (format) {
case viz::BGRA_8888:
info.format = GL_RGBA;
info.internal_format = GL_RGBA;
break;
// Technically we should use GL_RGB but CGLTexImageIOSurface2D() (and
// OpenGL ES 3.0, for the case) support only GL_RGBA (the hardware ignores
// the alpha channel anyway), see https://crbug.com/797347.
case viz::BGRX_1010102:
info.format = GL_RGBA;
info.internal_format = GL_RGBA;
break;
default:
break;
}
return info;
}
void FlushIOSurfaceGLOperations() {
// The CGLTexImageIOSurface2D documentation says that we need to call
// glFlush, otherwise there is the risk of a race between different
// graphics contexts.
gl::GLApi* api = gl::g_current_gl_context;
api->glFlushFn();
}
base::Optional<DawnTextureFormat> GetDawnFormat(viz::ResourceFormat format) {
switch (format) {
case viz::RED_8:
case viz::ALPHA_8:
case viz::LUMINANCE_8:
return DAWN_TEXTURE_FORMAT_R8_UNORM;
case viz::RG_88:
return DAWN_TEXTURE_FORMAT_R8_G8_UNORM;
case viz::RGBA_8888:
case viz::BGRA_8888:
return DAWN_TEXTURE_FORMAT_B8_G8_R8_A8_UNORM;
default:
return {};
}
}
} // anonymous namespace
// Representation of a SharedImageBackingIOSurface as a GL Texture.
class SharedImageRepresentationGLTextureIOSurface
: public SharedImageRepresentationGLTexture {
public:
SharedImageRepresentationGLTextureIOSurface(SharedImageManager* manager,
SharedImageBacking* backing,
MemoryTypeTracker* tracker,
gles2::Texture* texture)
: SharedImageRepresentationGLTexture(manager, backing, tracker),
texture_(texture) {
DCHECK(texture_);
}
~SharedImageRepresentationGLTextureIOSurface() override {
texture_->RemoveLightweightRef(has_context());
}
gles2::Texture* GetTexture() override { return texture_; }
bool BeginAccess(GLenum mode) override { return true; }
void EndAccess() override { FlushIOSurfaceGLOperations(); }
private:
gles2::Texture* texture_;
DISALLOW_COPY_AND_ASSIGN(SharedImageRepresentationGLTextureIOSurface);
};
// Representation of a SharedImageBackingIOSurface as a Skia Texture.
class SharedImageRepresentationSkiaIOSurface
: public SharedImageRepresentationSkia {
public:
SharedImageRepresentationSkiaIOSurface(
SharedImageManager* manager,
SharedImageBacking* backing,
scoped_refptr<SharedContextState> context_state,
sk_sp<SkPromiseImageTexture> promise_texture,
MemoryTypeTracker* tracker,
gles2::Texture* texture)
: SharedImageRepresentationSkia(manager, backing, tracker),
context_state_(std::move(context_state)),
promise_texture_(std::move(promise_texture)),
texture_(texture) {
DCHECK(texture_);
DCHECK(promise_texture_);
}
~SharedImageRepresentationSkiaIOSurface() override {
texture_->RemoveLightweightRef(has_context());
}
sk_sp<SkSurface> BeginWriteAccess(
int final_msaa_count,
const SkSurfaceProps& surface_props) override {
SkColorType sk_color_type = viz::ResourceFormatToClosestSkColorType(
/*gpu_compositing=*/true, format());
return SkSurface::MakeFromBackendTextureAsRenderTarget(
context_state_->gr_context(), promise_texture_->backendTexture(),
kTopLeft_GrSurfaceOrigin, final_msaa_count, sk_color_type,
backing()->color_space().ToSkColorSpace(), &surface_props);
}
void EndWriteAccess(sk_sp<SkSurface> surface) override {
FlushIOSurfaceGLOperations();
if (texture_->IsLevelCleared(texture_->target(), 0)) {
backing()->SetCleared();
}
}
sk_sp<SkPromiseImageTexture> BeginReadAccess() override {
return promise_texture_;
}
void EndReadAccess() override { FlushIOSurfaceGLOperations(); }
private:
scoped_refptr<SharedContextState> context_state_;
sk_sp<SkPromiseImageTexture> promise_texture_;
gles2::Texture* texture_;
};
// Representation of a SharedImageBackingIOSurface as a Dawn Texture.
#if BUILDFLAG(USE_DAWN)
class SharedImageRepresentationDawnIOSurface
: public SharedImageRepresentationDawn {
public:
SharedImageRepresentationDawnIOSurface(
SharedImageManager* manager,
SharedImageBacking* backing,
MemoryTypeTracker* tracker,
DawnDevice device,
base::ScopedCFTypeRef<IOSurfaceRef> io_surface,
DawnTextureFormat dawn_format)
: SharedImageRepresentationDawn(manager, backing, tracker),
io_surface_(std::move(io_surface)),
device_(device),
dawn_format_(dawn_format) {
DCHECK(device_);
DCHECK(io_surface_);
}
~SharedImageRepresentationDawnIOSurface() override {}
DawnTexture BeginAccess(DawnTextureUsageBit usage) final {
DawnTextureDescriptor desc;
desc.nextInChain = nullptr;
desc.format = dawn_format_;
desc.usage = usage;
desc.dimension = DAWN_TEXTURE_DIMENSION_2D;
desc.size = {size().width(), size().height(), 1};
desc.arrayLayerCount = 1;
desc.mipLevelCount = 1;
desc.sampleCount = 1;
return dawn_native::metal::WrapIOSurface(device_, &desc, io_surface_.get(),
0);
}
void EndAccess() final {
// macOS has a global GPU command queue so synchronization between APIs and
// devices is automatic. However on Metal, dawnQueueSubmit "commits" the
// Metal command buffers but they aren't "scheduled" in the global queue
// immediately. (that work seems offloaded to a different thread?)
// Wait for all the previous submitted commands to be scheduled to have
// scheduling races between commands using the IOSurface on different APIs.
// This is a blocking call but should be almost instant.
TRACE_EVENT0("gpu", "SharedImageRepresentationDawnIOSurface::EndAccess");
dawn_native::metal::WaitForCommandsToBeScheduled(device_);
}
private:
base::ScopedCFTypeRef<IOSurfaceRef> io_surface_;
DawnDevice device_;
DawnTextureFormat dawn_format_;
};
#endif // BUILDFLAG(USE_DAWN)
// Implementation of SharedImageBacking by wrapping IOSurfaces
class SharedImageBackingIOSurface : public SharedImageBacking {
public:
SharedImageBackingIOSurface(const Mailbox& mailbox,
viz::ResourceFormat format,
const gfx::Size& size,
const gfx::ColorSpace& color_space,
uint32_t usage,
base::ScopedCFTypeRef<IOSurfaceRef> io_surface,
base::Optional<DawnTextureFormat> dawn_format,
size_t estimated_size)
: SharedImageBacking(mailbox,
format,
size,
color_space,
usage,
estimated_size,
false /* is_thread_safe */),
io_surface_(std::move(io_surface)),
dawn_format_(dawn_format) {
DCHECK(io_surface_);
}
~SharedImageBackingIOSurface() final { DCHECK(!io_surface_); }
bool IsCleared() const final { return is_cleared_; }
void SetCleared() final {
if (legacy_texture_) {
legacy_texture_->SetLevelCleared(legacy_texture_->target(), 0, true);
}
is_cleared_ = true;
}
void Update() final {}
bool ProduceLegacyMailbox(MailboxManager* mailbox_manager) final {
DCHECK(io_surface_);
legacy_texture_ = GenGLTexture();
if (!legacy_texture_) {
return false;
}
mailbox_manager->ProduceTexture(mailbox(), legacy_texture_);
return true;
}
void Destroy() final {
DCHECK(io_surface_);
if (legacy_texture_) {
legacy_texture_->RemoveLightweightRef(have_context());
legacy_texture_ = nullptr;
}
io_surface_.reset();
}
protected:
std::unique_ptr<SharedImageRepresentationGLTexture> ProduceGLTexture(
SharedImageManager* manager,
MemoryTypeTracker* tracker) final {
gles2::Texture* texture = GenGLTexture();
if (!texture) {
return nullptr;
}
return std::make_unique<SharedImageRepresentationGLTextureIOSurface>(
manager, this, tracker, texture);
}
std::unique_ptr<SharedImageRepresentationSkia> ProduceSkia(
SharedImageManager* manager,
MemoryTypeTracker* tracker,
scoped_refptr<SharedContextState> context_state) override {
gles2::Texture* texture = GenGLTexture();
if (!texture) {
return nullptr;
}
GrBackendTexture backend_texture;
GetGrBackendTexture(gl::GLContext::GetCurrent()->GetVersionInfo(),
texture->target(), size(), texture->service_id(),
format(), &backend_texture);
sk_sp<SkPromiseImageTexture> promise_texture =
SkPromiseImageTexture::Make(backend_texture);
return std::make_unique<SharedImageRepresentationSkiaIOSurface>(
manager, this, std::move(context_state), promise_texture, tracker,
texture);
}
std::unique_ptr<SharedImageRepresentationDawn> ProduceDawn(
SharedImageManager* manager,
MemoryTypeTracker* tracker,
DawnDevice device) override {
#if BUILDFLAG(USE_DAWN)
if (!dawn_format_) {
LOG(ERROR) << "Format not supported for Dawn";
return nullptr;
}
return std::make_unique<SharedImageRepresentationDawnIOSurface>(
manager, this, tracker, device, io_surface_, dawn_format_.value());
#else // BUILDFLAG(USE_DAWN)
return nullptr;
#endif // BUILDFLAG(USE_DAWN)
}
private:
gles2::Texture* GenGLTexture() {
GLFormatInfo gl_info = GetGLFormatInfo(format());
DCHECK(gl_info.supported);
// Wrap the IOSurface in a GLImageIOSurface
scoped_refptr<gl::GLImageIOSurface> image(
gl::GLImageIOSurface::Create(size(), gl_info.internal_format));
if (!image->Initialize(io_surface_, gfx::GenericSharedMemoryId(),
viz::BufferFormat(format()))) {
LOG(ERROR) << "Failed to create GLImageIOSurface";
return nullptr;
}
gl::GLApi* api = gl::g_current_gl_context;
// Save the currently bound rectangle texture to reset it once we are done.
GLint old_texture_binding = 0;
api->glGetIntegervFn(GL_TEXTURE_BINDING_RECTANGLE, &old_texture_binding);
// Create a gles2 rectangle texture to bind to the IOSurface.
GLuint service_id = 0;
api->glGenTexturesFn(1, &service_id);
api->glBindTextureFn(GL_TEXTURE_RECTANGLE, service_id);
api->glTexParameteriFn(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MIN_FILTER,
GL_LINEAR);
api->glTexParameteriFn(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MAG_FILTER,
GL_LINEAR);
api->glTexParameteriFn(GL_TEXTURE_RECTANGLE, GL_TEXTURE_WRAP_S,
GL_CLAMP_TO_EDGE);
api->glTexParameteriFn(GL_TEXTURE_RECTANGLE, GL_TEXTURE_WRAP_T,
GL_CLAMP_TO_EDGE);
// Bind the GLImageIOSurface to our texture
if (!image->BindTexImage(GL_TEXTURE_RECTANGLE)) {
LOG(ERROR) << "Failed to bind GLImageIOSurface";
api->glBindTextureFn(GL_TEXTURE_RECTANGLE, old_texture_binding);
api->glDeleteTexturesFn(1, &service_id);
return nullptr;
}
// If the backing is already cleared, no need to clear it again.
gfx::Rect cleared_rect;
if (is_cleared_) {
cleared_rect = gfx::Rect(size());
}
// Manually create a gles2::Texture wrapping our driver texture.
gles2::Texture* texture = new gles2::Texture(service_id);
texture->SetLightweightRef();
texture->SetTarget(GL_TEXTURE_RECTANGLE, 1);
texture->sampler_state_.min_filter = GL_LINEAR;
texture->sampler_state_.mag_filter = GL_LINEAR;
texture->sampler_state_.wrap_t = GL_CLAMP_TO_EDGE;
texture->sampler_state_.wrap_s = GL_CLAMP_TO_EDGE;
texture->SetLevelInfo(GL_TEXTURE_RECTANGLE, 0, gl_info.internal_format,
size().width(), size().height(), 1, 0, gl_info.format,
gl_info.type, cleared_rect);
texture->SetLevelImage(GL_TEXTURE_RECTANGLE, 0, image.get(),
gles2::Texture::BOUND);
texture->SetImmutable(true);
DCHECK_EQ(image->GetInternalFormat(), gl_info.format);
api->glBindTextureFn(GL_TEXTURE_RECTANGLE, old_texture_binding);
return texture;
}
base::ScopedCFTypeRef<IOSurfaceRef> io_surface_;
base::Optional<DawnTextureFormat> dawn_format_;
bool is_cleared_ = false;
// A texture for the associated legacy mailbox.
gles2::Texture* legacy_texture_ = nullptr;
DISALLOW_COPY_AND_ASSIGN(SharedImageBackingIOSurface);
};
// Implementation of SharedImageBackingFactoryIOSurface that creates
// SharedImageBackings wrapping IOSurfaces.
SharedImageBackingFactoryIOSurface::SharedImageBackingFactoryIOSurface(
const GpuDriverBugWorkarounds& workarounds,
const GpuFeatureInfo& gpu_feature_info) {
scoped_refptr<gles2::FeatureInfo> feature_info =
new gles2::FeatureInfo(workarounds, gpu_feature_info);
feature_info->Initialize(ContextType::CONTEXT_TYPE_OPENGLES2, false,
gles2::DisallowedFeatures());
const gles2::Validators* validators = feature_info->validators();
// Precompute for each format if we can use it with GL.
for (int i = 0; i <= viz::RESOURCE_FORMAT_MAX; ++i) {
viz::ResourceFormat format = static_cast<viz::ResourceFormat>(i);
GLFormatInfo gl_info = GetGLFormatInfo(format);
format_supported_by_gl_[i] =
gl_info.supported &&
validators->texture_internal_format.IsValid(gl_info.internal_format) &&
validators->texture_format.IsValid(gl_info.format) &&
validators->pixel_type.IsValid(gl_info.type);
}
}
SharedImageBackingFactoryIOSurface::~SharedImageBackingFactoryIOSurface() =
default;
std::unique_ptr<SharedImageBacking>
SharedImageBackingFactoryIOSurface::CreateSharedImage(
const Mailbox& mailbox,
viz::ResourceFormat format,
const gfx::Size& size,
const gfx::ColorSpace& color_space,
uint32_t usage,
bool is_thread_safe) {
DCHECK(!is_thread_safe);
// Check the format is supported and for simplicity always require it to be
// supported for GL.
if (!format_supported_by_gl_[format]) {
LOG(ERROR) << "viz::ResourceFormat " << format
<< " not supported by IOSurfaces";
return nullptr;
}
// Calculate SharedImage size in bytes.
size_t estimated_size;
if (!viz::ResourceSizes::MaybeSizeInBytes(size, format, &estimated_size)) {
LOG(ERROR) << "Failed to calculate SharedImage size";
return nullptr;
}
base::ScopedCFTypeRef<IOSurfaceRef> io_surface(
gfx::CreateIOSurface(size, viz::BufferFormat(format), false));
if (!io_surface) {
LOG(ERROR) << "Failed to allocate IOSurface.";
return nullptr;
}
gfx::IOSurfaceSetColorSpace(io_surface, color_space);
return std::make_unique<SharedImageBackingIOSurface>(
mailbox, format, size, color_space, usage, std::move(io_surface),
GetDawnFormat(format), estimated_size);
}
std::unique_ptr<SharedImageBacking>
SharedImageBackingFactoryIOSurface::CreateSharedImage(
const Mailbox& mailbox,
viz::ResourceFormat format,
const gfx::Size& size,
const gfx::ColorSpace& color_space,
uint32_t usage,
base::span<const uint8_t> pixel_data) {
NOTIMPLEMENTED();
return nullptr;
}
std::unique_ptr<SharedImageBacking>
SharedImageBackingFactoryIOSurface::CreateSharedImage(
const Mailbox& mailbox,
int client_id,
gfx::GpuMemoryBufferHandle handle,
gfx::BufferFormat format,
SurfaceHandle surface_handle,
const gfx::Size& size,
const gfx::ColorSpace& color_space,
uint32_t usage) {
NOTIMPLEMENTED();
return nullptr;
}
} // namespace gpu