blob: 64dabf0bb8862bb17276e66882001e73fe76dad0 [file] [log] [blame]
/*
* Copyright (C) 2009 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "modules/webgl/WebGLFramebuffer.h"
#include "gpu/command_buffer/client/gles2_interface.h"
#include "modules/webgl/WebGLRenderbuffer.h"
#include "modules/webgl/WebGLRenderingContextBase.h"
#include "modules/webgl/WebGLTexture.h"
namespace blink {
namespace {
class WebGLRenderbufferAttachment final
: public WebGLFramebuffer::WebGLAttachment {
public:
static WebGLFramebuffer::WebGLAttachment* Create(WebGLRenderbuffer*);
virtual void Trace(blink::Visitor*);
virtual void TraceWrappers(const ScriptWrappableVisitor* visitor) const {
visitor->TraceWrappers(renderbuffer_);
}
private:
explicit WebGLRenderbufferAttachment(WebGLRenderbuffer*);
WebGLSharedObject* Object() const override;
bool IsSharedObject(WebGLSharedObject*) const override;
bool Valid() const override;
void OnDetached(gpu::gles2::GLES2Interface*) override;
void Attach(gpu::gles2::GLES2Interface*,
GLenum target,
GLenum attachment) override;
void Unattach(gpu::gles2::GLES2Interface*,
GLenum target,
GLenum attachment) override;
TraceWrapperMember<WebGLRenderbuffer> renderbuffer_;
};
WebGLFramebuffer::WebGLAttachment* WebGLRenderbufferAttachment::Create(
WebGLRenderbuffer* renderbuffer) {
return new WebGLRenderbufferAttachment(renderbuffer);
}
void WebGLRenderbufferAttachment::Trace(blink::Visitor* visitor) {
visitor->Trace(renderbuffer_);
WebGLFramebuffer::WebGLAttachment::Trace(visitor);
}
WebGLRenderbufferAttachment::WebGLRenderbufferAttachment(
WebGLRenderbuffer* renderbuffer)
: renderbuffer_(renderbuffer) {}
WebGLSharedObject* WebGLRenderbufferAttachment::Object() const {
return renderbuffer_->Object() ? renderbuffer_.Get() : nullptr;
}
bool WebGLRenderbufferAttachment::IsSharedObject(
WebGLSharedObject* object) const {
return object == renderbuffer_;
}
bool WebGLRenderbufferAttachment::Valid() const {
return renderbuffer_->Object();
}
void WebGLRenderbufferAttachment::OnDetached(gpu::gles2::GLES2Interface* gl) {
renderbuffer_->OnDetached(gl);
}
void WebGLRenderbufferAttachment::Attach(gpu::gles2::GLES2Interface* gl,
GLenum target,
GLenum attachment) {
GLuint object = ObjectOrZero(renderbuffer_.Get());
gl->FramebufferRenderbuffer(target, attachment, GL_RENDERBUFFER, object);
}
void WebGLRenderbufferAttachment::Unattach(gpu::gles2::GLES2Interface* gl,
GLenum target,
GLenum attachment) {
gl->FramebufferRenderbuffer(target, attachment, GL_RENDERBUFFER, 0);
}
class WebGLTextureAttachment final : public WebGLFramebuffer::WebGLAttachment {
public:
static WebGLFramebuffer::WebGLAttachment* Create(WebGLTexture*,
GLenum target,
GLint level,
GLint layer);
virtual void Trace(blink::Visitor*);
virtual void TraceWrappers(const ScriptWrappableVisitor* visitor) const {
visitor->TraceWrappers(texture_);
}
private:
WebGLTextureAttachment(WebGLTexture*,
GLenum target,
GLint level,
GLint layer);
WebGLSharedObject* Object() const override;
bool IsSharedObject(WebGLSharedObject*) const override;
bool Valid() const override;
void OnDetached(gpu::gles2::GLES2Interface*) override;
void Attach(gpu::gles2::GLES2Interface*,
GLenum target,
GLenum attachment) override;
void Unattach(gpu::gles2::GLES2Interface*,
GLenum target,
GLenum attachment) override;
TraceWrapperMember<WebGLTexture> texture_;
GLenum target_;
GLint level_;
GLint layer_;
};
WebGLFramebuffer::WebGLAttachment* WebGLTextureAttachment::Create(
WebGLTexture* texture,
GLenum target,
GLint level,
GLint layer) {
return new WebGLTextureAttachment(texture, target, level, layer);
}
void WebGLTextureAttachment::Trace(blink::Visitor* visitor) {
visitor->Trace(texture_);
WebGLFramebuffer::WebGLAttachment::Trace(visitor);
}
WebGLTextureAttachment::WebGLTextureAttachment(WebGLTexture* texture,
GLenum target,
GLint level,
GLint layer)
: texture_(texture), target_(target), level_(level), layer_(layer) {}
WebGLSharedObject* WebGLTextureAttachment::Object() const {
return texture_->Object() ? texture_.Get() : nullptr;
}
bool WebGLTextureAttachment::IsSharedObject(WebGLSharedObject* object) const {
return object == texture_;
}
bool WebGLTextureAttachment::Valid() const {
return texture_->Object();
}
void WebGLTextureAttachment::OnDetached(gpu::gles2::GLES2Interface* gl) {
texture_->OnDetached(gl);
}
void WebGLTextureAttachment::Attach(gpu::gles2::GLES2Interface* gl,
GLenum target,
GLenum attachment) {
GLuint object = ObjectOrZero(texture_.Get());
if (target_ == GL_TEXTURE_3D || target_ == GL_TEXTURE_2D_ARRAY) {
gl->FramebufferTextureLayer(target, attachment, object, level_, layer_);
} else {
gl->FramebufferTexture2D(target, attachment, target_, object, level_);
}
}
void WebGLTextureAttachment::Unattach(gpu::gles2::GLES2Interface* gl,
GLenum target,
GLenum attachment) {
// GL_DEPTH_STENCIL_ATTACHMENT attachment is valid in ES3.
if (target_ == GL_TEXTURE_3D || target_ == GL_TEXTURE_2D_ARRAY) {
gl->FramebufferTextureLayer(target, attachment, 0, level_, layer_);
} else {
gl->FramebufferTexture2D(target, attachment, target_, 0, level_);
}
}
} // anonymous namespace
WebGLFramebuffer::WebGLAttachment::WebGLAttachment() = default;
WebGLFramebuffer* WebGLFramebuffer::Create(WebGLRenderingContextBase* ctx) {
return new WebGLFramebuffer(ctx, false);
}
WebGLFramebuffer* WebGLFramebuffer::CreateOpaque(
WebGLRenderingContextBase* ctx) {
return new WebGLFramebuffer(ctx, true);
}
WebGLFramebuffer::WebGLFramebuffer(WebGLRenderingContextBase* ctx, bool opaque)
: WebGLContextObject(ctx),
object_(0),
has_ever_been_bound_(false),
web_gl1_depth_stencil_consistent_(true),
opaque_(opaque),
read_buffer_(GL_COLOR_ATTACHMENT0) {
ctx->ContextGL()->GenFramebuffers(1, &object_);
}
WebGLFramebuffer::~WebGLFramebuffer() {
RunDestructor();
}
void WebGLFramebuffer::SetAttachmentForBoundFramebuffer(GLenum target,
GLenum attachment,
GLenum tex_target,
WebGLTexture* texture,
GLint level,
GLint layer) {
DCHECK(object_);
DCHECK(IsBound(target));
if (Context()->IsWebGL2OrHigher()) {
if (attachment == GL_DEPTH_STENCIL_ATTACHMENT) {
SetAttachmentInternal(target, GL_DEPTH_ATTACHMENT, tex_target, texture,
level, layer);
SetAttachmentInternal(target, GL_STENCIL_ATTACHMENT, tex_target, texture,
level, layer);
} else {
SetAttachmentInternal(target, attachment, tex_target, texture, level,
layer);
}
GLuint texture_id = ObjectOrZero(texture);
// texTarget can be 0 if detaching using framebufferTextureLayer.
DCHECK(tex_target || !texture_id);
switch (tex_target) {
case 0:
case GL_TEXTURE_3D:
case GL_TEXTURE_2D_ARRAY:
Context()->ContextGL()->FramebufferTextureLayer(
target, attachment, texture_id, level, layer);
break;
default:
DCHECK_EQ(layer, 0);
Context()->ContextGL()->FramebufferTexture2D(
target, attachment, tex_target, texture_id, level);
break;
}
} else {
DCHECK_EQ(layer, 0);
SetAttachmentInternal(target, attachment, tex_target, texture, level,
layer);
switch (attachment) {
case GL_DEPTH_ATTACHMENT:
case GL_STENCIL_ATTACHMENT:
case GL_DEPTH_STENCIL_ATTACHMENT:
CommitWebGL1DepthStencilIfConsistent(target);
break;
default:
Context()->ContextGL()->FramebufferTexture2D(
target, attachment, tex_target, ObjectOrZero(texture), level);
break;
}
}
}
void WebGLFramebuffer::SetAttachmentForBoundFramebuffer(
GLenum target,
GLenum attachment,
WebGLRenderbuffer* renderbuffer) {
DCHECK(object_);
DCHECK(IsBound(target));
if (Context()->IsWebGL2OrHigher()) {
if (attachment == GL_DEPTH_STENCIL_ATTACHMENT) {
SetAttachmentInternal(target, GL_DEPTH_ATTACHMENT, renderbuffer);
SetAttachmentInternal(target, GL_STENCIL_ATTACHMENT, renderbuffer);
} else {
SetAttachmentInternal(target, attachment, renderbuffer);
}
Context()->ContextGL()->FramebufferRenderbuffer(
target, attachment, GL_RENDERBUFFER, ObjectOrZero(renderbuffer));
} else {
SetAttachmentInternal(target, attachment, renderbuffer);
switch (attachment) {
case GL_DEPTH_ATTACHMENT:
case GL_STENCIL_ATTACHMENT:
case GL_DEPTH_STENCIL_ATTACHMENT:
CommitWebGL1DepthStencilIfConsistent(target);
break;
default:
Context()->ContextGL()->FramebufferRenderbuffer(
target, attachment, GL_RENDERBUFFER, ObjectOrZero(renderbuffer));
break;
}
}
}
WebGLSharedObject* WebGLFramebuffer::GetAttachmentObject(
GLenum attachment) const {
if (!object_)
return nullptr;
WebGLAttachment* attachment_object = GetAttachment(attachment);
return attachment_object ? attachment_object->Object() : nullptr;
}
WebGLFramebuffer::WebGLAttachment* WebGLFramebuffer::GetAttachment(
GLenum attachment) const {
const AttachmentMap::const_iterator it = attachments_.find(attachment);
return (it != attachments_.end()) ? it->value.Get() : nullptr;
}
void WebGLFramebuffer::RemoveAttachmentFromBoundFramebuffer(
GLenum target,
WebGLSharedObject* attachment) {
DCHECK(IsBound(target));
if (!object_)
return;
if (!attachment)
return;
bool check_more = true;
bool is_web_gl1 = !Context()->IsWebGL2OrHigher();
bool check_web_gl1_depth_stencil = false;
while (check_more) {
check_more = false;
for (const auto& it : attachments_) {
WebGLAttachment* attachment_object = it.value.Get();
if (attachment_object->IsSharedObject(attachment)) {
GLenum attachment_type = it.key;
switch (attachment_type) {
case GL_DEPTH_ATTACHMENT:
case GL_STENCIL_ATTACHMENT:
case GL_DEPTH_STENCIL_ATTACHMENT:
if (is_web_gl1) {
check_web_gl1_depth_stencil = true;
} else {
attachment_object->Unattach(Context()->ContextGL(), target,
attachment_type);
}
break;
default:
attachment_object->Unattach(Context()->ContextGL(), target,
attachment_type);
break;
}
RemoveAttachmentInternal(target, attachment_type);
check_more = true;
break;
}
}
}
if (check_web_gl1_depth_stencil)
CommitWebGL1DepthStencilIfConsistent(target);
}
GLenum WebGLFramebuffer::CheckDepthStencilStatus(const char** reason) const {
// This function is called any time framebuffer completeness is checked, which
// makes it the most convenient place to add this check.
if (opaque_) {
if (opaque_complete_)
return GL_FRAMEBUFFER_COMPLETE;
*reason = "cannot render to a WebVR layer outside of a frame callback";
return GL_FRAMEBUFFER_UNSUPPORTED;
}
if (Context()->IsWebGL2OrHigher() || web_gl1_depth_stencil_consistent_)
return GL_FRAMEBUFFER_COMPLETE;
*reason = "conflicting DEPTH/STENCIL/DEPTH_STENCIL attachments";
return GL_FRAMEBUFFER_UNSUPPORTED;
}
bool WebGLFramebuffer::HasStencilBuffer() const {
WebGLAttachment* attachment = GetAttachment(GL_STENCIL_ATTACHMENT);
if (!attachment)
attachment = GetAttachment(GL_DEPTH_STENCIL_ATTACHMENT);
return attachment && attachment->Valid();
}
void WebGLFramebuffer::DeleteObjectImpl(gpu::gles2::GLES2Interface* gl) {
// Both the AttachmentMap and its WebGLAttachment objects are GCed
// objects and cannot be accessed after the destructor has been
// entered, as they may have been finalized already during the
// same GC sweep. These attachments' OpenGL objects will be fully
// destroyed once their JavaScript wrappers are collected.
if (!DestructionInProgress()) {
for (const auto& attachment : attachments_)
attachment.value->OnDetached(gl);
}
gl->DeleteFramebuffers(1, &object_);
object_ = 0;
}
bool WebGLFramebuffer::IsBound(GLenum target) const {
return (Context()->GetFramebufferBinding(target) == this);
}
void WebGLFramebuffer::DrawBuffers(const Vector<GLenum>& bufs) {
draw_buffers_ = bufs;
filtered_draw_buffers_.resize(draw_buffers_.size());
for (size_t i = 0; i < filtered_draw_buffers_.size(); ++i)
filtered_draw_buffers_[i] = GL_NONE;
DrawBuffersIfNecessary(true);
}
void WebGLFramebuffer::DrawBuffersIfNecessary(bool force) {
if (Context()->IsWebGL2OrHigher() ||
Context()->ExtensionEnabled(kWebGLDrawBuffersName)) {
bool reset = force;
// This filtering works around graphics driver bugs on Mac OS X.
for (size_t i = 0; i < draw_buffers_.size(); ++i) {
if (draw_buffers_[i] != GL_NONE && GetAttachment(draw_buffers_[i])) {
if (filtered_draw_buffers_[i] != draw_buffers_[i]) {
filtered_draw_buffers_[i] = draw_buffers_[i];
reset = true;
}
} else {
if (filtered_draw_buffers_[i] != GL_NONE) {
filtered_draw_buffers_[i] = GL_NONE;
reset = true;
}
}
}
if (reset) {
Context()->ContextGL()->DrawBuffersEXT(filtered_draw_buffers_.size(),
filtered_draw_buffers_.data());
}
}
}
void WebGLFramebuffer::SetAttachmentInternal(GLenum target,
GLenum attachment,
GLenum tex_target,
WebGLTexture* texture,
GLint level,
GLint layer) {
DCHECK(IsBound(target));
DCHECK(object_);
RemoveAttachmentInternal(target, attachment);
if (texture && texture->Object()) {
attachments_.insert(attachment, WebGLTextureAttachment::Create(
texture, tex_target, level, layer));
DrawBuffersIfNecessary(false);
texture->OnAttached();
}
}
void WebGLFramebuffer::SetAttachmentInternal(GLenum target,
GLenum attachment,
WebGLRenderbuffer* renderbuffer) {
DCHECK(IsBound(target));
DCHECK(object_);
RemoveAttachmentInternal(target, attachment);
if (renderbuffer && renderbuffer->Object()) {
attachments_.insert(attachment,
WebGLRenderbufferAttachment::Create(renderbuffer));
DrawBuffersIfNecessary(false);
renderbuffer->OnAttached();
}
}
void WebGLFramebuffer::RemoveAttachmentInternal(GLenum target,
GLenum attachment) {
DCHECK(IsBound(target));
DCHECK(object_);
WebGLAttachment* attachment_object = GetAttachment(attachment);
if (attachment_object) {
attachment_object->OnDetached(Context()->ContextGL());
attachments_.erase(attachment);
DrawBuffersIfNecessary(false);
}
}
void WebGLFramebuffer::CommitWebGL1DepthStencilIfConsistent(GLenum target) {
DCHECK(!Context()->IsWebGL2OrHigher());
WebGLAttachment* depth_attachment = nullptr;
WebGLAttachment* stencil_attachment = nullptr;
WebGLAttachment* depth_stencil_attachment = nullptr;
int count = 0;
for (const auto& it : attachments_) {
WebGLAttachment* attachment = it.value.Get();
DCHECK(attachment);
switch (it.key) {
case GL_DEPTH_ATTACHMENT:
depth_attachment = attachment;
++count;
break;
case GL_STENCIL_ATTACHMENT:
stencil_attachment = attachment;
++count;
break;
case GL_DEPTH_STENCIL_ATTACHMENT:
depth_stencil_attachment = attachment;
++count;
break;
default:
break;
}
}
web_gl1_depth_stencil_consistent_ = count <= 1;
if (!web_gl1_depth_stencil_consistent_)
return;
gpu::gles2::GLES2Interface* gl = Context()->ContextGL();
if (depth_attachment) {
gl->FramebufferRenderbuffer(target, GL_DEPTH_STENCIL_ATTACHMENT,
GL_RENDERBUFFER, 0);
depth_attachment->Attach(gl, target, GL_DEPTH_ATTACHMENT);
gl->FramebufferRenderbuffer(target, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER,
0);
} else if (stencil_attachment) {
gl->FramebufferRenderbuffer(target, GL_DEPTH_STENCIL_ATTACHMENT,
GL_RENDERBUFFER, 0);
gl->FramebufferRenderbuffer(target, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER,
0);
stencil_attachment->Attach(gl, target, GL_STENCIL_ATTACHMENT);
} else if (depth_stencil_attachment) {
gl->FramebufferRenderbuffer(target, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER,
0);
gl->FramebufferRenderbuffer(target, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER,
0);
depth_stencil_attachment->Attach(gl, target, GL_DEPTH_STENCIL_ATTACHMENT);
} else {
gl->FramebufferRenderbuffer(target, GL_DEPTH_STENCIL_ATTACHMENT,
GL_RENDERBUFFER, 0);
gl->FramebufferRenderbuffer(target, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER,
0);
gl->FramebufferRenderbuffer(target, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER,
0);
}
}
GLenum WebGLFramebuffer::GetDrawBuffer(GLenum draw_buffer) {
int index = static_cast<int>(draw_buffer - GL_DRAW_BUFFER0_EXT);
DCHECK_GE(index, 0);
if (index < static_cast<int>(draw_buffers_.size()))
return draw_buffers_[index];
if (draw_buffer == GL_DRAW_BUFFER0_EXT)
return GL_COLOR_ATTACHMENT0;
return GL_NONE;
}
void WebGLFramebuffer::Trace(blink::Visitor* visitor) {
visitor->Trace(attachments_);
WebGLContextObject::Trace(visitor);
}
void WebGLFramebuffer::TraceWrappers(
const ScriptWrappableVisitor* visitor) const {
for (const auto& attachment : attachments_) {
visitor->TraceWrappers(attachment.value);
}
WebGLContextObject::TraceWrappers(visitor);
}
} // namespace blink