blob: 052448724993c6bfd1818fe2ffd63b00cc870603 [file] [log] [blame] [edit]
/*
* 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 "config.h"
#include "modules/webgl/WebGLFramebuffer.h"
#include "modules/webgl/WebGLRenderbuffer.h"
#include "modules/webgl/WebGLRenderingContextBase.h"
#include "modules/webgl/WebGLTexture.h"
#include "platform/NotImplemented.h"
namespace blink {
namespace {
class WebGLRenderbufferAttachment final : public WebGLFramebuffer::WebGLAttachment {
public:
static WebGLFramebuffer::WebGLAttachment* create(WebGLRenderbuffer*);
DECLARE_VIRTUAL_TRACE();
private:
explicit WebGLRenderbufferAttachment(WebGLRenderbuffer*);
WebGLRenderbufferAttachment() { }
GLsizei width() const override;
GLsizei height() const override;
GLenum format() const override;
GLenum type() const override;
WebGLSharedObject* object() const override;
bool isSharedObject(WebGLSharedObject*) const override;
bool valid() const override;
void onDetached(WebGraphicsContext3D*) override;
void attach(WebGraphicsContext3D*, GLenum target, GLenum attachment) override;
void unattach(WebGraphicsContext3D*, GLenum target, GLenum attachment) override;
Member<WebGLRenderbuffer> m_renderbuffer;
};
WebGLFramebuffer::WebGLAttachment* WebGLRenderbufferAttachment::create(WebGLRenderbuffer* renderbuffer)
{
return new WebGLRenderbufferAttachment(renderbuffer);
}
DEFINE_TRACE(WebGLRenderbufferAttachment)
{
visitor->trace(m_renderbuffer);
WebGLFramebuffer::WebGLAttachment::trace(visitor);
}
WebGLRenderbufferAttachment::WebGLRenderbufferAttachment(WebGLRenderbuffer* renderbuffer)
: m_renderbuffer(renderbuffer)
{
}
GLsizei WebGLRenderbufferAttachment::width() const
{
return m_renderbuffer->width();
}
GLsizei WebGLRenderbufferAttachment::height() const
{
return m_renderbuffer->height();
}
GLenum WebGLRenderbufferAttachment::format() const
{
GLenum format = m_renderbuffer->internalFormat();
if (format == GL_DEPTH_STENCIL_OES
&& m_renderbuffer->emulatedStencilBuffer()
&& m_renderbuffer->emulatedStencilBuffer()->internalFormat() != GL_STENCIL_INDEX8) {
return 0;
}
return format;
}
WebGLSharedObject* WebGLRenderbufferAttachment::object() const
{
return m_renderbuffer->object() ? m_renderbuffer.get() : 0;
}
bool WebGLRenderbufferAttachment::isSharedObject(WebGLSharedObject* object) const
{
return object == m_renderbuffer;
}
bool WebGLRenderbufferAttachment::valid() const
{
return m_renderbuffer->object();
}
void WebGLRenderbufferAttachment::onDetached(WebGraphicsContext3D* context)
{
m_renderbuffer->onDetached(context);
}
void WebGLRenderbufferAttachment::attach(WebGraphicsContext3D* context, GLenum target, GLenum attachment)
{
Platform3DObject object = objectOrZero(m_renderbuffer.get());
if (attachment == GL_DEPTH_STENCIL_ATTACHMENT && m_renderbuffer->emulatedStencilBuffer()) {
context->framebufferRenderbuffer(target, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, object);
context->framebufferRenderbuffer(target, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, objectOrZero(m_renderbuffer->emulatedStencilBuffer()));
} else {
context->framebufferRenderbuffer(target, attachment, GL_RENDERBUFFER, object);
}
}
void WebGLRenderbufferAttachment::unattach(WebGraphicsContext3D* context, GLenum target, GLenum attachment)
{
if (attachment == GL_DEPTH_STENCIL_ATTACHMENT) {
context->framebufferRenderbuffer(target, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, 0);
context->framebufferRenderbuffer(target, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, 0);
} else {
context->framebufferRenderbuffer(target, attachment, GL_RENDERBUFFER, 0);
}
}
GLenum WebGLRenderbufferAttachment::type() const
{
notImplemented();
return 0;
}
class WebGLTextureAttachment final : public WebGLFramebuffer::WebGLAttachment {
public:
static WebGLFramebuffer::WebGLAttachment* create(WebGLTexture*, GLenum target, GLint level);
DECLARE_VIRTUAL_TRACE();
private:
WebGLTextureAttachment(WebGLTexture*, GLenum target, GLint level);
WebGLTextureAttachment() { }
GLsizei width() const override;
GLsizei height() const override;
GLenum format() const override;
GLenum type() const override;
WebGLSharedObject* object() const override;
bool isSharedObject(WebGLSharedObject*) const override;
bool valid() const override;
void onDetached(WebGraphicsContext3D*) override;
void attach(WebGraphicsContext3D*, GLenum target, GLenum attachment) override;
void unattach(WebGraphicsContext3D*, GLenum target, GLenum attachment) override;
Member<WebGLTexture> m_texture;
GLenum m_target;
GLint m_level;
};
WebGLFramebuffer::WebGLAttachment* WebGLTextureAttachment::create(WebGLTexture* texture, GLenum target, GLint level)
{
return new WebGLTextureAttachment(texture, target, level);
}
DEFINE_TRACE(WebGLTextureAttachment)
{
visitor->trace(m_texture);
WebGLFramebuffer::WebGLAttachment::trace(visitor);
}
WebGLTextureAttachment::WebGLTextureAttachment(WebGLTexture* texture, GLenum target, GLint level)
: m_texture(texture)
, m_target(target)
, m_level(level)
{
}
GLsizei WebGLTextureAttachment::width() const
{
return m_texture->getWidth(m_target, m_level);
}
GLsizei WebGLTextureAttachment::height() const
{
return m_texture->getHeight(m_target, m_level);
}
GLenum WebGLTextureAttachment::format() const
{
return m_texture->getInternalFormat(m_target, m_level);
}
WebGLSharedObject* WebGLTextureAttachment::object() const
{
return m_texture->object() ? m_texture.get() : 0;
}
bool WebGLTextureAttachment::isSharedObject(WebGLSharedObject* object) const
{
return object == m_texture;
}
bool WebGLTextureAttachment::valid() const
{
return m_texture->object();
}
void WebGLTextureAttachment::onDetached(WebGraphicsContext3D* context)
{
m_texture->onDetached(context);
}
void WebGLTextureAttachment::attach(WebGraphicsContext3D* context, GLenum target, GLenum attachment)
{
Platform3DObject object = objectOrZero(m_texture.get());
context->framebufferTexture2D(target, attachment, m_target, object, m_level);
}
void WebGLTextureAttachment::unattach(WebGraphicsContext3D* context, GLenum target, GLenum attachment)
{
if (attachment == GL_DEPTH_STENCIL_ATTACHMENT) {
context->framebufferTexture2D(target, GL_DEPTH_ATTACHMENT, m_target, 0, m_level);
context->framebufferTexture2D(target, GL_STENCIL_ATTACHMENT, m_target, 0, m_level);
} else {
context->framebufferTexture2D(target, attachment, m_target, 0, m_level);
}
}
GLenum WebGLTextureAttachment::type() const
{
return m_texture->getType(m_target, m_level);
}
bool isColorRenderable(GLenum internalformat)
{
switch (internalformat) {
case GL_RGB:
case GL_RGBA:
case GL_SRGB_ALPHA_EXT:
case GL_R8:
case GL_R8UI:
case GL_R8I:
case GL_R16UI:
case GL_R16I:
case GL_R32UI:
case GL_R32I:
case GL_RG8:
case GL_RG8UI:
case GL_RG8I:
case GL_RG16UI:
case GL_RG16I:
case GL_RG32UI:
case GL_RG32I:
case GL_RGB8:
case GL_RGB565:
case GL_RGBA8:
case GL_SRGB8_ALPHA8:
case GL_RGB5_A1:
case GL_RGBA4:
case GL_RGB10_A2:
case GL_RGBA8UI:
case GL_RGBA8I:
case GL_RGB10_A2UI:
case GL_RGBA16UI:
case GL_RGBA16I:
case GL_RGBA32UI:
case GL_RGBA32I:
return true;
default:
return false;
}
}
bool isDepthRenderable(GLenum internalformat, bool includesDepthStencil)
{
switch (internalformat) {
case GL_DEPTH_COMPONENT:
case GL_DEPTH_COMPONENT16:
case GL_DEPTH_COMPONENT24:
case GL_DEPTH_COMPONENT32F:
return true;
case GL_DEPTH_STENCIL:
case GL_DEPTH24_STENCIL8:
case GL_DEPTH32F_STENCIL8:
return includesDepthStencil;
default:
return false;
}
}
bool isStencilRenderable(GLenum internalformat, bool includesDepthStencil)
{
switch (internalformat) {
case GL_STENCIL_INDEX8:
return true;
case GL_DEPTH_STENCIL:
case GL_DEPTH24_STENCIL8:
case GL_DEPTH32F_STENCIL8:
return includesDepthStencil;
default:
return false;
}
}
} // anonymous namespace
WebGLFramebuffer::WebGLAttachment::WebGLAttachment()
{
}
WebGLFramebuffer::WebGLAttachment::~WebGLAttachment()
{
}
WebGLFramebuffer* WebGLFramebuffer::create(WebGLRenderingContextBase* ctx)
{
return new WebGLFramebuffer(ctx);
}
WebGLFramebuffer::WebGLFramebuffer(WebGLRenderingContextBase* ctx)
: WebGLContextObject(ctx)
, m_object(ctx->webContext()->createFramebuffer())
, m_hasEverBeenBound(false)
, m_readBuffer(GL_COLOR_ATTACHMENT0)
{
}
WebGLFramebuffer::~WebGLFramebuffer()
{
// Attachments in |m_attachments| will be deleted from other places, so we
// clear it to avoid deleting those attachments in detachAndDeleteObject().
m_attachments.clear();
// See the comment in WebGLObject::detachAndDeleteObject().
detachAndDeleteObject();
}
void WebGLFramebuffer::setAttachmentForBoundFramebuffer(GLenum target, GLenum attachment, GLenum texTarget, WebGLTexture* texture, GLint level)
{
ASSERT(isBound(target));
removeAttachmentFromBoundFramebuffer(target, attachment);
if (!m_object)
return;
if (texture && texture->object()) {
m_attachments.add(attachment, WebGLTextureAttachment::create(texture, texTarget, level));
drawBuffersIfNecessary(false);
texture->onAttached();
}
}
void WebGLFramebuffer::setAttachmentForBoundFramebuffer(GLenum target, GLenum attachment, WebGLRenderbuffer* renderbuffer)
{
ASSERT(isBound(target));
removeAttachmentFromBoundFramebuffer(target, attachment);
if (!m_object)
return;
if (renderbuffer && renderbuffer->object()) {
m_attachments.add(attachment, WebGLRenderbufferAttachment::create(renderbuffer));
drawBuffersIfNecessary(false);
renderbuffer->onAttached();
}
}
void WebGLFramebuffer::attach(GLenum target, GLenum attachment, GLenum attachmentPoint)
{
ASSERT(isBound(target));
WebGLAttachment* attachmentObject = getAttachment(attachment);
if (attachmentObject)
attachmentObject->attach(context()->webContext(), target, attachmentPoint);
}
WebGLSharedObject* WebGLFramebuffer::getAttachmentObject(GLenum attachment) const
{
if (!m_object)
return nullptr;
WebGLAttachment* attachmentObject = getAttachment(attachment);
return attachmentObject ? attachmentObject->object() : nullptr;
}
bool WebGLFramebuffer::isAttachmentComplete(WebGLAttachment* attachedObject, GLenum attachment, const char** reason) const
{
ASSERT(attachedObject && attachedObject->valid());
ASSERT(reason);
GLenum internalformat = attachedObject->format();
switch (attachment) {
case GL_DEPTH_ATTACHMENT:
if (!isDepthRenderable(internalformat, context()->isWebGL2OrHigher())) {
*reason = "the internalformat of the attached image is not depth-renderable";
return false;
}
break;
case GL_STENCIL_ATTACHMENT:
if (!isStencilRenderable(internalformat, context()->isWebGL2OrHigher())) {
*reason = "the internalformat of the attached image is not stencil-renderable";
return false;
}
break;
case GL_DEPTH_STENCIL_ATTACHMENT:
ASSERT(!context()->isWebGL2OrHigher());
if (internalformat != GL_DEPTH_STENCIL_OES) {
*reason = "the internalformat of the attached image is not DEPTH_STENCIL";
return false;
}
break;
default:
ASSERT(attachment == GL_COLOR_ATTACHMENT0 || (attachment > GL_COLOR_ATTACHMENT0 && attachment < static_cast<GLenum>(GL_COLOR_ATTACHMENT0 + context()->maxColorAttachments())));
if (!isColorRenderable(internalformat)) {
*reason = "the internalformat of the attached image is not color-renderable";
return false;
}
break;
}
if (!attachedObject->width() || !attachedObject->height()) {
*reason = "attachment has a 0 dimension";
return false;
}
return true;
}
WebGLFramebuffer::WebGLAttachment* WebGLFramebuffer::getAttachment(GLenum attachment) const
{
const AttachmentMap::const_iterator it = m_attachments.find(attachment);
return (it != m_attachments.end()) ? it->value.get() : 0;
}
void WebGLFramebuffer::removeAttachmentFromBoundFramebuffer(GLenum target, GLenum attachment)
{
ASSERT(isBound(target));
if (!m_object)
return;
WebGLAttachment* attachmentObject = getAttachment(attachment);
if (attachmentObject) {
attachmentObject->onDetached(context()->webContext());
m_attachments.remove(attachment);
drawBuffersIfNecessary(false);
switch (attachment) {
case GL_DEPTH_STENCIL_ATTACHMENT:
attach(target, GL_DEPTH_ATTACHMENT, GL_DEPTH_ATTACHMENT);
attach(target, GL_STENCIL_ATTACHMENT, GL_STENCIL_ATTACHMENT);
break;
case GL_DEPTH_ATTACHMENT:
attach(target, GL_DEPTH_STENCIL_ATTACHMENT, GL_DEPTH_ATTACHMENT);
break;
case GL_STENCIL_ATTACHMENT:
attach(target, GL_DEPTH_STENCIL_ATTACHMENT, GL_STENCIL_ATTACHMENT);
break;
}
}
}
void WebGLFramebuffer::removeAttachmentFromBoundFramebuffer(GLenum target, WebGLSharedObject* attachment)
{
ASSERT(isBound(target));
if (!m_object)
return;
if (!attachment)
return;
bool checkMore = true;
while (checkMore) {
checkMore = false;
for (const auto& it : m_attachments) {
WebGLAttachment* attachmentObject = it.value.get();
if (attachmentObject->isSharedObject(attachment)) {
GLenum attachmentType = it.key;
attachmentObject->unattach(context()->webContext(), target, attachmentType);
removeAttachmentFromBoundFramebuffer(target, attachmentType);
checkMore = true;
break;
}
}
}
}
GLenum WebGLFramebuffer::colorBufferFormat() const
{
if (!m_object)
return 0;
WebGLAttachment* attachment = getAttachment(GL_COLOR_ATTACHMENT0);
if (!attachment)
return 0;
return attachment->format();
}
GLenum WebGLFramebuffer::checkStatus(const char** reason) const
{
unsigned count = 0;
GLsizei width = 0, height = 0;
WebGLAttachment* depthAttachment = nullptr;
WebGLAttachment* stencilAttachment = nullptr;
WebGLAttachment* depthStencilAttachment = nullptr;
bool isWebGL2OrHigher = context()->isWebGL2OrHigher();
for (const auto& it : m_attachments) {
WebGLAttachment* attachment = it.value.get();
if (!isAttachmentComplete(attachment, it.key, reason))
return GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT;
if (!attachment->valid()) {
*reason = "attachment is not valid";
return GL_FRAMEBUFFER_UNSUPPORTED;
}
if (!attachment->format()) {
*reason = "attachment is an unsupported format";
return GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT;
}
switch (it.key) {
case GL_DEPTH_ATTACHMENT:
depthAttachment = attachment;
break;
case GL_STENCIL_ATTACHMENT:
stencilAttachment = attachment;
break;
case GL_DEPTH_STENCIL_ATTACHMENT:
depthStencilAttachment = attachment;
break;
}
if (!isWebGL2OrHigher) {
if (!count) {
width = attachment->width();
height = attachment->height();
} else {
if (width != attachment->width() || height != attachment->height()) {
*reason = "attachments do not have the same dimensions";
return GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS;
}
}
}
++count;
}
if (!count) {
*reason = "no attachments";
return GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT;
}
// WebGL 1 specific: no conflicting DEPTH/STENCIL/DEPTH_STENCIL attachments.
if (!isWebGL2OrHigher
&& ((depthStencilAttachment && (depthAttachment || stencilAttachment))
|| (depthAttachment && stencilAttachment))) {
*reason = "conflicting DEPTH/STENCIL/DEPTH_STENCIL attachments";
return GL_FRAMEBUFFER_UNSUPPORTED;
}
if (isWebGL2OrHigher
&& (depthAttachment && stencilAttachment && depthAttachment->object() != stencilAttachment->object())) {
*reason = "both DEPTH/STENCIL attachments are present and not the same image";
return GL_FRAMEBUFFER_UNSUPPORTED;
}
return GL_FRAMEBUFFER_COMPLETE;
}
bool WebGLFramebuffer::onAccess(WebGraphicsContext3D* context3d, const char** reason)
{
if (checkStatus(reason) != GL_FRAMEBUFFER_COMPLETE)
return false;
return true;
}
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(WebGraphicsContext3D* context3d)
{
// Both the AttachmentMap and its WebGLAttachment objects are GCed
// objects and cannot be accessed, as they may have been finalized
// already during the same GC sweep.
for (const auto& attachment : m_attachments)
attachment.value->onDetached(context3d);
context3d->deleteFramebuffer(m_object);
m_object = 0;
}
bool WebGLFramebuffer::isBound(GLenum target) const
{
return (context()->getFramebufferBinding(target) == this);
}
void WebGLFramebuffer::drawBuffers(const Vector<GLenum>& bufs)
{
m_drawBuffers = bufs;
m_filteredDrawBuffers.resize(m_drawBuffers.size());
for (size_t i = 0; i < m_filteredDrawBuffers.size(); ++i)
m_filteredDrawBuffers[i] = GL_NONE;
drawBuffersIfNecessary(true);
}
void WebGLFramebuffer::drawBuffersIfNecessary(bool force)
{
if (context()->isWebGL2OrHigher()
|| context()->extensionEnabled(WebGLDrawBuffersName)) {
bool reset = force;
// This filtering works around graphics driver bugs on Mac OS X.
for (size_t i = 0; i < m_drawBuffers.size(); ++i) {
if (m_drawBuffers[i] != GL_NONE && getAttachment(m_drawBuffers[i])) {
if (m_filteredDrawBuffers[i] != m_drawBuffers[i]) {
m_filteredDrawBuffers[i] = m_drawBuffers[i];
reset = true;
}
} else {
if (m_filteredDrawBuffers[i] != GL_NONE) {
m_filteredDrawBuffers[i] = GL_NONE;
reset = true;
}
}
}
if (reset) {
context()->webContext()->drawBuffersEXT(
m_filteredDrawBuffers.size(), m_filteredDrawBuffers.data());
}
}
}
GLenum WebGLFramebuffer::getDrawBuffer(GLenum drawBuffer)
{
int index = static_cast<int>(drawBuffer - GL_DRAW_BUFFER0_EXT);
ASSERT(index >= 0);
if (index < static_cast<int>(m_drawBuffers.size()))
return m_drawBuffers[index];
if (drawBuffer == GL_DRAW_BUFFER0_EXT)
return GL_COLOR_ATTACHMENT0;
return GL_NONE;
}
bool WebGLFramebuffer::getReadBufferFormatAndType(GLenum* format, GLenum* type) const
{
if (m_readBuffer == GL_NONE)
return false;
WebGLAttachment* image = getAttachment(m_readBuffer);
if (!image)
return false;
if (format)
*format = image->format();
if (type)
*type = image->type();
return true;
}
DEFINE_TRACE(WebGLFramebuffer)
{
visitor->trace(m_attachments);
WebGLContextObject::trace(visitor);
}
}