#include "config.h"
#if ENABLE(ACCELERATED_2D_CANVAS) || USE(3D_GRAPHICS)
#include "DrawingBuffer.h"
#include "Extensions3D.h"
#include "GraphicsContext3D.h"
namespace WebCore {
#if PLATFORM(WIN) || USE(CAIRO)
DrawingBuffer::DrawingBuffer(GraphicsContext3D* context, const IntSize& size, bool multisampleExtensionSupported, bool packedDepthStencilExtensionSupported, PreserveDrawingBuffer preserveDrawingBuffer, AlphaRequirement alpha)
: m_preserveDrawingBuffer(preserveDrawingBuffer)
, m_alpha(alpha)
, m_scissorEnabled(false)
, m_texture2DBinding(0)
, m_framebufferBinding(0)
, m_activeTextureUnit(GraphicsContext3D::TEXTURE0)
, m_context(context)
, m_size(-1, -1)
, m_multisampleExtensionSupported(multisampleExtensionSupported)
, m_packedDepthStencilExtensionSupported(packedDepthStencilExtensionSupported)
, m_fbo(context->createFramebuffer())
, m_colorBuffer(0)
, m_frontColorBuffer(0)
, m_separateFrontTexture(false)
, m_depthStencilBuffer(0)
, m_depthBuffer(0)
, m_stencilBuffer(0)
, m_multisampleFBO(0)
, m_multisampleColorBuffer(0)
{
ASSERT(m_fbo);
if (!m_fbo) {
clear();
return;
}
m_colorBuffer = context->createTexture();
context->bindTexture(GraphicsContext3D::TEXTURE_2D, m_colorBuffer);
context->texParameterf(GraphicsContext3D::TEXTURE_2D, GraphicsContext3D::TEXTURE_MAG_FILTER, GraphicsContext3D::LINEAR);
context->texParameterf(GraphicsContext3D::TEXTURE_2D, GraphicsContext3D::TEXTURE_MIN_FILTER, GraphicsContext3D::LINEAR);
context->texParameteri(GraphicsContext3D::TEXTURE_2D, GraphicsContext3D::TEXTURE_WRAP_S, GraphicsContext3D::CLAMP_TO_EDGE);
context->texParameteri(GraphicsContext3D::TEXTURE_2D, GraphicsContext3D::TEXTURE_WRAP_T, GraphicsContext3D::CLAMP_TO_EDGE);
context->bindTexture(GraphicsContext3D::TEXTURE_2D, 0);
createSecondaryBuffers();
reset(size);
}
DrawingBuffer::~DrawingBuffer()
{
clear();
}
#endif
static int s_maximumResourceUsePixels = 0;
static int s_currentResourceUsePixels = 0;
static const float s_resourceAdjustedRatio = 0.5;
PassRefPtr<DrawingBuffer> DrawingBuffer::create(GraphicsContext3D* context, const IntSize& size, PreserveDrawingBuffer preserve, AlphaRequirement alpha)
{
Extensions3D* extensions = context->getExtensions();
bool multisampleSupported = extensions->maySupportMultisampling()
&& extensions->supports("GL_ANGLE_framebuffer_blit")
&& extensions->supports("GL_ANGLE_framebuffer_multisample")
&& extensions->supports("GL_OES_rgb8_rgba8");
if (multisampleSupported) {
extensions->ensureEnabled("GL_ANGLE_framebuffer_blit");
extensions->ensureEnabled("GL_ANGLE_framebuffer_multisample");
extensions->ensureEnabled("GL_OES_rgb8_rgba8");
}
bool packedDepthStencilSupported = extensions->supports("GL_OES_packed_depth_stencil");
if (packedDepthStencilSupported)
extensions->ensureEnabled("GL_OES_packed_depth_stencil");
RefPtr<DrawingBuffer> drawingBuffer = adoptRef(new DrawingBuffer(context, size, multisampleSupported, packedDepthStencilSupported, preserve, alpha));
return (drawingBuffer->m_context) ? drawingBuffer.release() : 0;
}
void DrawingBuffer::clear()
{
if (!m_context)
return;
m_context->makeContextCurrent();
if (!m_size.isEmpty()) {
s_currentResourceUsePixels -= m_size.width() * m_size.height();
m_size = IntSize();
}
if (m_colorBuffer) {
m_context->deleteTexture(m_colorBuffer);
m_colorBuffer = 0;
}
if (m_frontColorBuffer) {
m_context->deleteTexture(m_frontColorBuffer);
m_frontColorBuffer = 0;
}
if (m_multisampleColorBuffer) {
m_context->deleteRenderbuffer(m_multisampleColorBuffer);
m_multisampleColorBuffer = 0;
}
if (m_depthStencilBuffer) {
m_context->deleteRenderbuffer(m_depthStencilBuffer);
m_depthStencilBuffer = 0;
}
if (m_depthBuffer) {
m_context->deleteRenderbuffer(m_depthBuffer);
m_depthBuffer = 0;
}
if (m_stencilBuffer) {
m_context->deleteRenderbuffer(m_stencilBuffer);
m_stencilBuffer = 0;
}
if (m_multisampleFBO) {
m_context->deleteFramebuffer(m_multisampleFBO);
m_multisampleFBO = 0;
}
if (m_fbo) {
m_context->deleteFramebuffer(m_fbo);
m_fbo = 0;
}
}
void DrawingBuffer::createSecondaryBuffers()
{
if (multisample()) {
m_multisampleFBO = m_context->createFramebuffer();
m_context->bindFramebuffer(GraphicsContext3D::FRAMEBUFFER, m_multisampleFBO);
m_multisampleColorBuffer = m_context->createRenderbuffer();
}
}
void DrawingBuffer::resizeDepthStencil(int sampleCount)
{
const GraphicsContext3D::Attributes& attributes = m_context->getContextAttributes();
if (attributes.depth && attributes.stencil && m_packedDepthStencilExtensionSupported) {
if (!m_depthStencilBuffer)
m_depthStencilBuffer = m_context->createRenderbuffer();
m_context->bindRenderbuffer(GraphicsContext3D::RENDERBUFFER, m_depthStencilBuffer);
if (multisample())
m_context->getExtensions()->renderbufferStorageMultisample(GraphicsContext3D::RENDERBUFFER, sampleCount, Extensions3D::DEPTH24_STENCIL8, m_size.width(), m_size.height());
else
m_context->renderbufferStorage(GraphicsContext3D::RENDERBUFFER, Extensions3D::DEPTH24_STENCIL8, m_size.width(), m_size.height());
m_context->framebufferRenderbuffer(GraphicsContext3D::FRAMEBUFFER, GraphicsContext3D::STENCIL_ATTACHMENT, GraphicsContext3D::RENDERBUFFER, m_depthStencilBuffer);
m_context->framebufferRenderbuffer(GraphicsContext3D::FRAMEBUFFER, GraphicsContext3D::DEPTH_ATTACHMENT, GraphicsContext3D::RENDERBUFFER, m_depthStencilBuffer);
} else {
if (attributes.depth) {
if (!m_depthBuffer)
m_depthBuffer = m_context->createRenderbuffer();
m_context->bindRenderbuffer(GraphicsContext3D::RENDERBUFFER, m_depthBuffer);
if (multisample())
m_context->getExtensions()->renderbufferStorageMultisample(GraphicsContext3D::RENDERBUFFER, sampleCount, GraphicsContext3D::DEPTH_COMPONENT16, m_size.width(), m_size.height());
else
m_context->renderbufferStorage(GraphicsContext3D::RENDERBUFFER, GraphicsContext3D::DEPTH_COMPONENT16, m_size.width(), m_size.height());
m_context->framebufferRenderbuffer(GraphicsContext3D::FRAMEBUFFER, GraphicsContext3D::DEPTH_ATTACHMENT, GraphicsContext3D::RENDERBUFFER, m_depthBuffer);
}
if (attributes.stencil) {
if (!m_stencilBuffer)
m_stencilBuffer = m_context->createRenderbuffer();
m_context->bindRenderbuffer(GraphicsContext3D::RENDERBUFFER, m_stencilBuffer);
if (multisample())
m_context->getExtensions()->renderbufferStorageMultisample(GraphicsContext3D::RENDERBUFFER, sampleCount, GraphicsContext3D::STENCIL_INDEX8, m_size.width(), m_size.height());
else
m_context->renderbufferStorage(GraphicsContext3D::RENDERBUFFER, GraphicsContext3D::STENCIL_INDEX8, m_size.width(), m_size.height());
m_context->framebufferRenderbuffer(GraphicsContext3D::FRAMEBUFFER, GraphicsContext3D::STENCIL_ATTACHMENT, GraphicsContext3D::RENDERBUFFER, m_stencilBuffer);
}
}
m_context->bindRenderbuffer(GraphicsContext3D::RENDERBUFFER, 0);
}
void DrawingBuffer::clearFramebuffers(GC3Dbitfield clearMask)
{
m_context->bindFramebuffer(GraphicsContext3D::FRAMEBUFFER, m_multisampleFBO ? m_multisampleFBO : m_fbo);
m_context->clear(clearMask);
if (m_multisampleFBO) {
m_context->bindFramebuffer(GraphicsContext3D::FRAMEBUFFER, m_fbo);
m_context->clear(GraphicsContext3D::COLOR_BUFFER_BIT);
m_context->bindFramebuffer(GraphicsContext3D::FRAMEBUFFER, m_multisampleFBO);
}
}
bool DrawingBuffer::checkBufferIntegrity()
{
if (!m_multisampleFBO)
return true;
if (m_scissorEnabled)
m_context->disable(GraphicsContext3D::SCISSOR_TEST);
m_context->colorMask(true, true, true, true);
m_context->bindFramebuffer(GraphicsContext3D::FRAMEBUFFER, m_multisampleFBO);
m_context->clearColor(1.0f, 0.0f, 1.0f, 1.0f);
m_context->clear(GraphicsContext3D::COLOR_BUFFER_BIT);
commit(0, 0, 1, 1);
unsigned char pixel[4] = {0, 0, 0, 0};
m_context->readPixels(0, 0, 1, 1, GraphicsContext3D::RGBA, GraphicsContext3D::UNSIGNED_BYTE, &pixel);
if (m_scissorEnabled)
m_context->enable(GraphicsContext3D::SCISSOR_TEST);
return (pixel[0] == 0xFF && pixel[1] == 0x00 && pixel[2] == 0xFF && pixel[3] == 0xFF);
}
bool DrawingBuffer::reset(const IntSize& newSize)
{
if (!m_context)
return false;
m_context->makeContextCurrent();
int maxTextureSize = 0;
m_context->getIntegerv(GraphicsContext3D::MAX_TEXTURE_SIZE, &maxTextureSize);
if (newSize.height() > maxTextureSize || newSize.width() > maxTextureSize) {
clear();
return false;
}
int pixelDelta = newSize.width() * newSize.height();
int oldSize = 0;
if (!m_size.isEmpty()) {
oldSize = m_size.width() * m_size.height();
pixelDelta -= oldSize;
}
IntSize adjustedSize = newSize;
if (s_maximumResourceUsePixels) {
while ((s_currentResourceUsePixels + pixelDelta) > s_maximumResourceUsePixels) {
adjustedSize.scale(s_resourceAdjustedRatio);
if (adjustedSize.isEmpty()) {
clear();
return false;
}
pixelDelta = adjustedSize.width() * adjustedSize.height();
pixelDelta -= oldSize;
}
}
const GraphicsContext3D::Attributes& attributes = m_context->getContextAttributes();
if (adjustedSize != m_size) {
unsigned internalColorFormat, colorFormat, internalRenderbufferFormat;
if (attributes.alpha) {
internalColorFormat = GraphicsContext3D::RGBA;
colorFormat = GraphicsContext3D::RGBA;
internalRenderbufferFormat = Extensions3D::RGBA8_OES;
} else {
internalColorFormat = GraphicsContext3D::RGB;
colorFormat = GraphicsContext3D::RGB;
internalRenderbufferFormat = Extensions3D::RGB8_OES;
}
do {
m_size = adjustedSize;
if (multisample()) {
int maxSampleCount = 0;
m_context->getIntegerv(Extensions3D::MAX_SAMPLES, &maxSampleCount);
int sampleCount = std::min(4, maxSampleCount);
m_context->bindFramebuffer(GraphicsContext3D::FRAMEBUFFER, m_multisampleFBO);
m_context->bindRenderbuffer(GraphicsContext3D::RENDERBUFFER, m_multisampleColorBuffer);
m_context->getExtensions()->renderbufferStorageMultisample(GraphicsContext3D::RENDERBUFFER, sampleCount, internalRenderbufferFormat, m_size.width(), m_size.height());
if (m_context->getError() == GraphicsContext3D::OUT_OF_MEMORY) {
adjustedSize.scale(s_resourceAdjustedRatio);
continue;
}
m_context->framebufferRenderbuffer(GraphicsContext3D::FRAMEBUFFER, GraphicsContext3D::COLOR_ATTACHMENT0, GraphicsContext3D::RENDERBUFFER, m_multisampleColorBuffer);
resizeDepthStencil(sampleCount);
if (m_context->checkFramebufferStatus(GraphicsContext3D::FRAMEBUFFER) != GraphicsContext3D::FRAMEBUFFER_COMPLETE) {
adjustedSize.scale(s_resourceAdjustedRatio);
continue;
}
}
m_context->bindFramebuffer(GraphicsContext3D::FRAMEBUFFER, m_fbo);
m_context->bindTexture(GraphicsContext3D::TEXTURE_2D, m_colorBuffer);
m_context->texImage2D(GraphicsContext3D::TEXTURE_2D, 0, internalColorFormat, m_size.width(), m_size.height(), 0, colorFormat, GraphicsContext3D::UNSIGNED_BYTE, 0);
m_context->framebufferTexture2D(GraphicsContext3D::FRAMEBUFFER, GraphicsContext3D::COLOR_ATTACHMENT0, GraphicsContext3D::TEXTURE_2D, m_colorBuffer, 0);
if (m_separateFrontTexture) {
m_context->bindTexture(GraphicsContext3D::TEXTURE_2D, m_frontColorBuffer);
m_context->texImage2D(GraphicsContext3D::TEXTURE_2D, 0, internalColorFormat, m_size.width(), m_size.height(), 0, colorFormat, GraphicsContext3D::UNSIGNED_BYTE, 0);
}
m_context->bindTexture(GraphicsContext3D::TEXTURE_2D, 0);
if (!multisample())
resizeDepthStencil(0);
if (m_context->checkFramebufferStatus(GraphicsContext3D::FRAMEBUFFER) != GraphicsContext3D::FRAMEBUFFER_COMPLETE) {
adjustedSize.scale(s_resourceAdjustedRatio);
continue;
}
#if OS(DARWIN)
if (!checkBufferIntegrity()) {
adjustedSize.scale(s_resourceAdjustedRatio);
continue;
}
#endif
break;
} while (!adjustedSize.isEmpty());
pixelDelta = m_size.width() * m_size.height();
pixelDelta -= oldSize;
s_currentResourceUsePixels += pixelDelta;
if (!newSize.isEmpty() && adjustedSize.isEmpty()) {
clear();
return false;
}
}
m_context->disable(GraphicsContext3D::SCISSOR_TEST);
m_context->clearColor(0, 0, 0, 0);
m_context->colorMask(true, true, true, true);
GC3Dbitfield clearMask = GraphicsContext3D::COLOR_BUFFER_BIT;
if (attributes.depth) {
m_context->clearDepth(1.0f);
clearMask |= GraphicsContext3D::DEPTH_BUFFER_BIT;
m_context->depthMask(true);
}
if (attributes.stencil) {
m_context->clearStencil(0);
clearMask |= GraphicsContext3D::STENCIL_BUFFER_BIT;
m_context->stencilMaskSeparate(GraphicsContext3D::FRONT, 0xFFFFFFFF);
}
clearFramebuffers(clearMask);
return true;
}
void DrawingBuffer::commit(long x, long y, long width, long height)
{
if (!m_context)
return;
if (width < 0)
width = m_size.width();
if (height < 0)
height = m_size.height();
m_context->makeContextCurrent();
if (m_multisampleFBO) {
m_context->bindFramebuffer(Extensions3D::READ_FRAMEBUFFER, m_multisampleFBO);
m_context->bindFramebuffer(Extensions3D::DRAW_FRAMEBUFFER, m_fbo);
if (m_scissorEnabled)
m_context->disable(GraphicsContext3D::SCISSOR_TEST);
m_context->getExtensions()->blitFramebuffer(x, y, width, height, x, y, width, height, GraphicsContext3D::COLOR_BUFFER_BIT, GraphicsContext3D::NEAREST);
if (m_scissorEnabled)
m_context->enable(GraphicsContext3D::SCISSOR_TEST);
}
m_context->bindFramebuffer(GraphicsContext3D::FRAMEBUFFER, m_fbo);
}
void DrawingBuffer::restoreFramebufferBinding()
{
if (!m_context || !m_framebufferBinding)
return;
m_context->bindFramebuffer(GraphicsContext3D::FRAMEBUFFER, m_framebufferBinding);
}
bool DrawingBuffer::multisample() const
{
return m_context && m_context->getContextAttributes().antialias && m_multisampleExtensionSupported;
}
void DrawingBuffer::discardResources()
{
m_colorBuffer = 0;
m_frontColorBuffer = 0;
m_multisampleColorBuffer = 0;
m_depthStencilBuffer = 0;
m_depthBuffer = 0;
m_stencilBuffer = 0;
m_multisampleFBO = 0;
m_fbo = 0;
}
void DrawingBuffer::bind()
{
if (!m_context)
return;
m_context->bindFramebuffer(GraphicsContext3D::FRAMEBUFFER, m_multisampleFBO ? m_multisampleFBO : m_fbo);
}
}
#endif