[go: nahoru, domu]

blob: 063e8f6c396e8a8c160cbedfaf27fc8751ade6ab [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// 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/gles2_external_framebuffer.h"
#include "gpu/command_buffer/service/feature_info.h"
#include "gpu/command_buffer/service/shared_image/shared_image_factory.h"
#include "gpu/command_buffer/service/shared_image/shared_image_representation.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gl/scoped_binders.h"
#include "ui/gl/scoped_restore_texture.h"
namespace gpu::gles2 {
namespace {
class ScopedRestoreRenderbuffer {
public:
explicit ScopedRestoreRenderbuffer(gl::GLApi* api) : api_(api) {
api_->glGetIntegervFn(GL_RENDERBUFFER_BINDING, &renderbuffer_);
}
~ScopedRestoreRenderbuffer() {
api_->glBindRenderbufferEXTFn(GL_RENDERBUFFER, renderbuffer_);
}
private:
const raw_ptr<gl::GLApi> api_;
GLint renderbuffer_ = 0;
};
class ScopedRestoreWindowRectangles {
public:
explicit ScopedRestoreWindowRectangles(gl::GLApi* api) : api_(api) {
api_->glGetIntegervFn(GL_WINDOW_RECTANGLE_MODE_EXT, &mode_);
GLint num_windows = 0;
api_->glGetIntegervFn(GL_NUM_WINDOW_RECTANGLES_EXT, &num_windows);
windows_.resize(4 * num_windows);
for (int i = 0; i < num_windows; ++i) {
glGetIntegeri_v(GL_WINDOW_RECTANGLE_EXT, i, &windows_[i * 4]);
}
}
~ScopedRestoreWindowRectangles() {
api_->glWindowRectanglesEXTFn(mode_, windows_.size() / 4, windows_.data());
}
private:
const raw_ptr<gl::GLApi> api_;
GLint mode_ = GL_EXCLUSIVE_EXT;
std::vector<GLint> windows_;
};
class ScopedRestoreWriteMasks {
public:
explicit ScopedRestoreWriteMasks(gl::GLApi* api) : api_(api) {
api_->glGetIntegervFn(GL_STENCIL_WRITEMASK, &stencil_front_mask_);
api_->glGetIntegervFn(GL_STENCIL_BACK_WRITEMASK, &stencil_back_mask_);
api_->glGetBooleanvFn(GL_DEPTH_WRITEMASK, &depth_mask_);
api_->glGetBooleanvFn(GL_COLOR_WRITEMASK, color_mask_);
}
~ScopedRestoreWriteMasks() {
api_->glColorMaskFn(color_mask_[0], color_mask_[1], color_mask_[2],
color_mask_[3]);
api_->glDepthMaskFn(depth_mask_);
api_->glStencilMaskSeparateFn(GL_FRONT, stencil_front_mask_);
api_->glStencilMaskSeparateFn(GL_BACK, stencil_back_mask_);
}
private:
const raw_ptr<gl::GLApi> api_;
GLboolean color_mask_[4] = {GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE};
GLboolean depth_mask_ = GL_TRUE;
GLint stencil_front_mask_ = 0xFF;
GLint stencil_back_mask_ = 0xFF;
};
class ScopedRestoreClearValues {
public:
explicit ScopedRestoreClearValues(gl::GLApi* api) : api_(api) {
api_->glGetFloatvFn(GL_COLOR_CLEAR_VALUE, clear_color_);
api_->glGetFloatvFn(GL_DEPTH_CLEAR_VALUE, &clear_depth_);
api_->glGetIntegervFn(GL_STENCIL_CLEAR_VALUE, &clear_stencil_);
}
~ScopedRestoreClearValues() {
api_->glClearColorFn(clear_color_[0], clear_color_[1], clear_color_[2],
clear_color_[3]);
api_->glClearDepthFn(clear_depth_);
api_->glClearStencilFn(clear_stencil_);
}
private:
const raw_ptr<gl::GLApi> api_;
GLfloat clear_color_[4] = {};
GLfloat clear_depth_ = 0.0f;
GLint clear_stencil_ = 0;
};
class ScopedRestoreFramebuffer {
public:
ScopedRestoreFramebuffer(gl::GLApi* api, bool supports_separate_fbo_bindings)
: api_(api),
supports_separate_fbo_bindings_(supports_separate_fbo_bindings) {
if (supports_separate_fbo_bindings_) {
api_->glGetIntegervFn(GL_DRAW_FRAMEBUFFER_BINDING, &draw_framebuffer_);
api_->glGetIntegervFn(GL_READ_FRAMEBUFFER_BINDING, &read_framebuffer_);
} else {
api_->glGetIntegervFn(GL_FRAMEBUFFER_BINDING, &draw_framebuffer_);
}
}
~ScopedRestoreFramebuffer() {
if (supports_separate_fbo_bindings_) {
api_->glBindFramebufferEXTFn(GL_DRAW_FRAMEBUFFER, draw_framebuffer_);
api_->glBindFramebufferEXTFn(GL_READ_FRAMEBUFFER, read_framebuffer_);
} else {
api_->glBindFramebufferEXTFn(GL_FRAMEBUFFER, draw_framebuffer_);
}
}
private:
const raw_ptr<gl::GLApi> api_;
const bool supports_separate_fbo_bindings_;
GLint draw_framebuffer_ = 0;
GLint read_framebuffer_ = 0;
};
} // namespace
class GLES2ExternalFramebuffer::Attachment {
public:
static std::unique_ptr<Attachment> CreateTexture(const gfx::Size& size,
GLenum format) {
gl::GLApi* const api = gl::g_current_gl_context;
gl::ScopedRestoreTexture scoped_restore(api, GL_TEXTURE_2D);
// Don't use sized formats for textures
GLenum texture_format;
switch (format) {
case GL_RGBA8:
texture_format = GL_RGBA;
break;
case GL_RGB8:
texture_format = GL_RGB;
break;
default:
texture_format = GL_RGBA;
NOTREACHED();
}
GLuint texture;
api->glGenTexturesFn(1, &texture);
api->glBindTextureFn(GL_TEXTURE_2D, texture);
api->glTexImage2DFn(GL_TEXTURE_2D, 0, texture_format, size.width(),
size.height(), 0, texture_format, GL_UNSIGNED_BYTE,
nullptr);
return std::make_unique<Attachment>(size, /*samples_count=*/0, format,
/*texture=*/texture,
/*renderbuffer=*/0);
}
static std::unique_ptr<Attachment> CreateRenderbuffer(const gfx::Size& size,
int samples_count,
GLenum format) {
gl::GLApi* const api = gl::g_current_gl_context;
ScopedRestoreRenderbuffer rb_restore(api);
GLuint renderbuffer;
api->glGenRenderbuffersEXTFn(1, &renderbuffer);
api->glBindRenderbufferEXTFn(GL_RENDERBUFFER, renderbuffer);
if (samples_count > 0) {
api->glRenderbufferStorageMultisampleFn(
GL_RENDERBUFFER, samples_count, format, size.width(), size.height());
} else {
api->glRenderbufferStorageEXTFn(GL_RENDERBUFFER, format, size.width(),
size.height());
}
return std::make_unique<Attachment>(size, samples_count, format,
/*texture=*/0,
/*renderbuffer=*/renderbuffer);
}
Attachment(const gfx::Size& size,
int samples_count,
GLenum format,
GLuint texture,
GLuint renderbuffer)
: size_(size),
samples_count_(samples_count),
format_(format),
texture_(texture),
renderbuffer_(renderbuffer) {
DCHECK_NE(!!texture, !!renderbuffer);
DCHECK(!size.IsEmpty());
DCHECK(format);
}
Attachment(const Attachment&) = delete;
Attachment(Attachment&&) = delete;
Attachment& operator=(const Attachment&) = delete;
Attachment& operator=(Attachment&&) = delete;
~Attachment() {
// No need to do anything if context was lost.
if (context_lost_)
return;
DCHECK_EQ(attach_point_, 0u);
if (texture_)
glDeleteTextures(1, &texture_);
else if (renderbuffer_)
glDeleteRenderbuffersEXT(1, &renderbuffer_);
}
void Attach(GLenum attachment) {
DCHECK_EQ(attach_point_, 0u);
attach_point_ = attachment;
if (texture_)
AttachImpl(attach_point_, /*is_texture=*/true, texture_);
else
AttachImpl(attach_point_, /*is_texture=*/false, renderbuffer_);
}
void Detach() {
DCHECK_NE(attach_point_, 0u);
if (texture_)
AttachImpl(attach_point_, /*is_texture=*/true, 0);
else
AttachImpl(attach_point_, /*is_texture=*/false, 0);
attach_point_ = 0;
}
bool NeedsResolve() { return samples_count_ > 0; }
bool Compatible(const gfx::Size size, int samples_count, GLenum format) {
return size_ == size && samples_count_ == samples_count &&
format_ == format;
}
void OnContextLost() { context_lost_ = true; }
GLenum format() const { return format_; }
int samples_count() const { return samples_count_; }
private:
void AttachImpl(GLenum attachment, bool is_texture, GLuint object) {
if (is_texture) {
glFramebufferTexture2DEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_TEXTURE_2D, object, 0);
} else {
if (attachment == GL_DEPTH_STENCIL_ATTACHMENT) {
glFramebufferRenderbufferEXT(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
GL_RENDERBUFFER, object);
glFramebufferRenderbufferEXT(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT,
GL_RENDERBUFFER, object);
} else {
glFramebufferRenderbufferEXT(GL_FRAMEBUFFER, attachment,
GL_RENDERBUFFER, object);
}
}
}
GLenum attach_point_ = 0;
bool context_lost_ = false;
const gfx::Size size_;
const int samples_count_;
const GLenum format_;
const GLuint texture_;
const GLuint renderbuffer_;
};
GLES2ExternalFramebuffer::GLES2ExternalFramebuffer(
bool passthrough,
const FeatureInfo& feature_info,
SharedImageRepresentationFactory* shared_image_representation_factory)
: passthrough_(passthrough),
shared_image_representation_factory_(
shared_image_representation_factory) {
const bool multisampled_framebuffers_supported =
feature_info.feature_flags().chromium_framebuffer_multisample;
const bool rgb8_supported = feature_info.feature_flags().oes_rgb8_rgba8;
// The only available default render buffer formats in GLES2 have very
// little precision. Don't enable multisampling unless 8-bit render
// buffer formats are available--instead fall back to 8-bit textures.
if (multisampled_framebuffers_supported && rgb8_supported) {
glGetIntegerv(GL_MAX_SAMPLES_EXT, &max_sample_count_);
}
packed_depth_stencil_ = feature_info.feature_flags().packed_depth24_stencil8;
supports_separate_fbo_bindings_ = multisampled_framebuffers_supported ||
feature_info.IsWebGL2OrES3Context();
supports_window_rectangles_ =
feature_info.feature_flags().ext_window_rectangles;
glGenFramebuffersEXT(1, &fbo_);
}
GLES2ExternalFramebuffer::~GLES2ExternalFramebuffer() {
DCHECK_EQ(fbo_, 0u);
DCHECK(attachments_.empty());
}
void GLES2ExternalFramebuffer::Destroy(bool have_context) {
if (!have_context) {
for (auto& attachment : attachments_)
attachment.second->OnContextLost();
if (shared_image_representation_)
shared_image_representation_->OnContextLost();
} else {
gl::GLApi* const api = gl::g_current_gl_context;
ScopedRestoreFramebuffer scoped_fbo_reset(api,
supports_separate_fbo_bindings_);
api->glBindFramebufferEXTFn(GL_FRAMEBUFFER, fbo_);
for (auto& attachment : attachments_)
attachment.second->Detach();
}
scoped_access_.reset();
shared_image_representation_.reset();
attachments_.clear();
if (have_context)
glDeleteFramebuffersEXT(1, &fbo_);
fbo_ = 0;
}
bool GLES2ExternalFramebuffer::AttachSharedImage(const Mailbox& mailbox,
int samples,
bool preserve,
bool need_depth,
bool need_stencil) {
ResolveAndDetach();
if (mailbox.IsZero())
return true;
if (passthrough_) {
shared_image_representation_ =
shared_image_representation_factory_->ProduceGLTexturePassthrough(
mailbox);
} else {
shared_image_representation_ =
shared_image_representation_factory_->ProduceGLTexture(mailbox);
}
if (!shared_image_representation_) {
LOG(ERROR) << "Can't produce representation";
return false;
}
if (!shared_image_representation_->format().is_single_plane() ||
(shared_image_representation_->format() !=
viz::SinglePlaneFormat::kRGBA_8888 &&
shared_image_representation_->format() !=
viz::SinglePlaneFormat::kRGBX_8888)) {
LOG(ERROR) << "Unsupported format";
return false;
}
scoped_access_ = shared_image_representation_->BeginScopedAccess(
GL_SHARED_IMAGE_ACCESS_MODE_READWRITE_CHROMIUM,
GLTextureImageRepresentationBase::AllowUnclearedAccess::kYes);
if (!scoped_access_) {
LOG(ERROR) << "Can't BeginAccess";
return false;
}
samples = std::min(samples, max_sample_count_);
const bool can_attach_directly = !samples && !preserve;
GLenum clear_flags = 0;
const auto& size = shared_image_representation_->size();
gl::GLApi* const api = gl::g_current_gl_context;
ScopedRestoreFramebuffer scoped_fbo_reset(api,
supports_separate_fbo_bindings_);
api->glBindFramebufferEXTFn(GL_FRAMEBUFFER, fbo_);
if (can_attach_directly) {
glFramebufferTexture2DEXT(
GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
shared_image_representation_->GetTextureBase()->service_id(), 0);
if (!shared_image_representation_->IsCleared())
clear_flags |= GL_COLOR_BUFFER_BIT;
} else {
const bool has_alpha = shared_image_representation_->format() ==
viz::SinglePlaneFormat::kRGBA_8888;
if (UpdateAttachment(GL_COLOR_ATTACHMENT0, size, samples,
has_alpha ? GL_RGBA8 : GL_RGB8)) {
clear_flags |= GL_COLOR_BUFFER_BIT;
}
}
// If GL_DEPTH24_STENCIL8 is supported, we prefer it.
if (packed_depth_stencil_) {
if (UpdateAttachment(
GL_DEPTH_STENCIL_ATTACHMENT, size, samples,
(need_depth || need_stencil) ? GL_DEPTH24_STENCIL8 : GL_NONE)) {
clear_flags |= GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT;
}
} else {
if (UpdateAttachment(GL_DEPTH_ATTACHMENT, size, samples,
need_depth ? GL_DEPTH_COMPONENT16 : GL_NONE)) {
clear_flags |= GL_DEPTH_BUFFER_BIT;
}
if (UpdateAttachment(GL_STENCIL_ATTACHMENT, size, samples,
need_stencil ? GL_STENCIL_INDEX8 : GL_NONE)) {
clear_flags |= GL_STENCIL_BUFFER_BIT;
}
}
GLenum status = api->glCheckFramebufferStatusEXTFn(GL_FRAMEBUFFER);
LOG_IF(DFATAL, status != GL_FRAMEBUFFER_COMPLETE)
<< "Framebuffer incomplete: " << status;
if (clear_flags) {
gl::ScopedCapability scoped_scissor(GL_SCISSOR_TEST, GL_FALSE);
absl::optional<ScopedRestoreWindowRectangles> window_rectangles_restore;
if (supports_window_rectangles_) {
window_rectangles_restore.emplace(api);
api->glWindowRectanglesEXTFn(GL_EXCLUSIVE_EXT, 0, nullptr);
}
ScopedRestoreWriteMasks write_mask_restore(api);
api->glColorMaskFn(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
api->glDepthMaskFn(GL_TRUE);
api->glStencilMaskSeparateFn(GL_FRONT, 0xFF);
api->glStencilMaskSeparateFn(GL_BACK, 0xFF);
ScopedRestoreClearValues clear_values_restore(api);
api->glClearColorFn(0, 0, 0, 0);
api->glClearDepthFn(0.0f);
api->glClearStencilFn(0);
api->glClearFn(clear_flags);
// If we attached SharedImage directly and did clear color attachment, mark
// it as cleared.
if (attachments_.find(GL_COLOR_ATTACHMENT0) == attachments_.end() &&
(clear_flags & GL_COLOR_BUFFER_BIT))
shared_image_representation_->SetCleared();
}
return true;
}
bool GLES2ExternalFramebuffer::UpdateAttachment(GLenum attachment,
const gfx::Size& size,
int samples,
GLenum format) {
if (auto old_attachment = attachments_.find(attachment);
old_attachment != attachments_.end()) {
if (old_attachment->second->Compatible(size, samples, format))
return false;
old_attachment->second->Detach();
attachments_.erase(attachment);
}
if (format) {
attachments_[attachment] =
CreateAttachment(attachment, size, samples, format);
attachments_[attachment]->Attach(attachment);
return true;
}
return false;
}
std::unique_ptr<GLES2ExternalFramebuffer::Attachment>
GLES2ExternalFramebuffer::CreateAttachment(GLenum attachment,
const gfx::Size& size,
int samples,
GLenum format) {
if (attachment == GL_COLOR_ATTACHMENT0 && samples == 0) {
return Attachment::CreateTexture(size, format);
}
return Attachment::CreateRenderbuffer(size, samples, format);
}
void GLES2ExternalFramebuffer::ResolveAndDetach() {
if (!scoped_access_) {
DCHECK(!shared_image_representation_);
return;
}
gl::GLApi* const api = gl::g_current_gl_context;
ScopedRestoreFramebuffer scoped_fbo_reset(api,
supports_separate_fbo_bindings_);
if (auto color_attachment = attachments_.find(GL_COLOR_ATTACHMENT0);
color_attachment != attachments_.end()) {
const auto& size = shared_image_representation_->size();
// If we have separate attachment, we need to blit/resolve it to shared
// image.
if (color_attachment->second->NeedsResolve()) {
DCHECK(supports_separate_fbo_bindings_);
gl::ScopedCapability scoped_scissor(GL_SCISSOR_TEST, GL_FALSE);
ScopedRestoreWriteMasks write_mask_restore(api);
api->glColorMaskFn(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
absl::optional<ScopedRestoreWindowRectangles> window_rectangles_restore;
if (supports_window_rectangles_) {
window_rectangles_restore.emplace(api);
api->glWindowRectanglesEXTFn(GL_EXCLUSIVE_EXT, 0, nullptr);
}
api->glBindFramebufferEXTFn(GL_READ_FRAMEBUFFER, fbo_);
GLuint temp_fbo;
api->glGenFramebuffersEXTFn(1, &temp_fbo);
api->glBindFramebufferEXTFn(GL_DRAW_FRAMEBUFFER, temp_fbo);
api->glFramebufferTexture2DEXTFn(
GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
shared_image_representation_->GetTextureBase()->service_id(), 0);
api->glBlitFramebufferFn(0, 0, size.width(), size.height(), 0, 0,
size.width(), size.height(), GL_COLOR_BUFFER_BIT,
GL_NEAREST);
api->glDeleteFramebuffersEXTFn(1, &temp_fbo);
} else {
api->glBindFramebufferEXTFn(GL_FRAMEBUFFER, fbo_);
gl::ScopedRestoreTexture texture(api, GL_TEXTURE_2D);
api->glBindTextureFn(
GL_TEXTURE_2D,
shared_image_representation_->GetTextureBase()->service_id());
api->glCopyTexSubImage2DFn(GL_TEXTURE_2D, 0, 0, 0, 0, 0, size.width(),
size.height());
}
// We did resolved to SharedImage, so we can mark it as cleared here.
shared_image_representation_->SetCleared();
} else {
// Detach color attachment if we were attached directly.
api->glBindFramebufferEXTFn(GL_FRAMEBUFFER, fbo_);
api->glFramebufferTexture2DEXTFn(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_TEXTURE_2D, 0, 0);
}
scoped_access_.reset();
shared_image_representation_.reset();
}
GLuint GLES2ExternalFramebuffer::GetFramebufferId() const {
return fbo_;
}
bool GLES2ExternalFramebuffer::IsSharedImageAttached() const {
return !!scoped_access_;
}
gfx::Size GLES2ExternalFramebuffer::GetSize() const {
DCHECK(IsSharedImageAttached());
return shared_image_representation_->size();
}
GLenum GLES2ExternalFramebuffer::GetColorFormat() const {
DCHECK(IsSharedImageAttached());
auto it = attachments_.find(GL_COLOR_ATTACHMENT0);
DCHECK(it != attachments_.end());
return it->second->format();
}
GLenum GLES2ExternalFramebuffer::GetDepthFormat() const {
DCHECK(IsSharedImageAttached());
if (auto it = attachments_.find(GL_DEPTH_STENCIL_ATTACHMENT);
it != attachments_.end()) {
return it->second->format();
}
if (auto it = attachments_.find(GL_DEPTH_ATTACHMENT);
it != attachments_.end()) {
return it->second->format();
}
return GL_NONE;
}
GLenum GLES2ExternalFramebuffer::GetStencilFormat() const {
DCHECK(IsSharedImageAttached());
DCHECK(IsSharedImageAttached());
if (auto it = attachments_.find(GL_DEPTH_STENCIL_ATTACHMENT);
it != attachments_.end()) {
return it->second->format();
}
if (auto it = attachments_.find(GL_STENCIL_ATTACHMENT);
it != attachments_.end()) {
return it->second->format();
}
return GL_NONE;
}
int GLES2ExternalFramebuffer::GetSamplesCount() const {
DCHECK(IsSharedImageAttached());
auto it = attachments_.find(GL_COLOR_ATTACHMENT0);
DCHECK(it != attachments_.end());
return it->second->samples_count();
}
bool GLES2ExternalFramebuffer::HasAlpha() const {
DCHECK(IsSharedImageAttached());
auto it = attachments_.find(GL_COLOR_ATTACHMENT0);
DCHECK(it != attachments_.end());
return it->second->format() == GL_RGBA8;
}
bool GLES2ExternalFramebuffer::HasDepth() const {
return GetDepthFormat() != GL_NONE;
}
bool GLES2ExternalFramebuffer::HasStencil() const {
return GetStencilFormat() != GL_NONE;
}
} // namespace gpu::gles2