1/*
2 * Copyright (c) 2010, Google Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
7 *
8 *     * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 *     * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
13 * distribution.
14 *     * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31#include "config.h"
32
33#if ENABLE(ACCELERATED_2D_CANVAS) || USE(3D_GRAPHICS)
34
35#include "DrawingBuffer.h"
36
37#include "Extensions3D.h"
38#include "GraphicsContext3D.h"
39
40namespace WebCore {
41
42#if PLATFORM(WIN) || USE(CAIRO)
43DrawingBuffer::DrawingBuffer(GraphicsContext3D* context, const IntSize& size, bool multisampleExtensionSupported, bool packedDepthStencilExtensionSupported, PreserveDrawingBuffer preserveDrawingBuffer, AlphaRequirement alpha)
44    : m_preserveDrawingBuffer(preserveDrawingBuffer)
45    , m_alpha(alpha)
46    , m_scissorEnabled(false)
47    , m_texture2DBinding(0)
48    , m_framebufferBinding(0)
49    , m_activeTextureUnit(GraphicsContext3D::TEXTURE0)
50    , m_context(context)
51    , m_size(-1, -1)
52    , m_multisampleExtensionSupported(multisampleExtensionSupported)
53    , m_packedDepthStencilExtensionSupported(packedDepthStencilExtensionSupported)
54    , m_fbo(context->createFramebuffer())
55    , m_colorBuffer(0)
56    , m_frontColorBuffer(0)
57    , m_separateFrontTexture(false)
58    , m_depthStencilBuffer(0)
59    , m_depthBuffer(0)
60    , m_stencilBuffer(0)
61    , m_multisampleFBO(0)
62    , m_multisampleColorBuffer(0)
63{
64    ASSERT(m_fbo);
65    if (!m_fbo) {
66        clear();
67        return;
68    }
69
70    // create a texture to render into
71    m_colorBuffer = context->createTexture();
72    context->bindTexture(GraphicsContext3D::TEXTURE_2D, m_colorBuffer);
73    context->texParameterf(GraphicsContext3D::TEXTURE_2D, GraphicsContext3D::TEXTURE_MAG_FILTER, GraphicsContext3D::LINEAR);
74    context->texParameterf(GraphicsContext3D::TEXTURE_2D, GraphicsContext3D::TEXTURE_MIN_FILTER, GraphicsContext3D::LINEAR);
75    context->texParameteri(GraphicsContext3D::TEXTURE_2D, GraphicsContext3D::TEXTURE_WRAP_S, GraphicsContext3D::CLAMP_TO_EDGE);
76    context->texParameteri(GraphicsContext3D::TEXTURE_2D, GraphicsContext3D::TEXTURE_WRAP_T, GraphicsContext3D::CLAMP_TO_EDGE);
77    context->bindTexture(GraphicsContext3D::TEXTURE_2D, 0);
78
79    createSecondaryBuffers();
80    reset(size);
81}
82
83DrawingBuffer::~DrawingBuffer()
84{
85    clear();
86}
87#endif
88
89// Global resource ceiling (expressed in terms of pixels) for DrawingBuffer creation and resize.
90// When this limit is set, DrawingBuffer::create() and DrawingBuffer::reset() calls that would
91// exceed the global cap will instead clear the buffer.
92static int s_maximumResourceUsePixels = 0;
93static int s_currentResourceUsePixels = 0;
94static const float s_resourceAdjustedRatio = 0.5;
95
96PassRefPtr<DrawingBuffer> DrawingBuffer::create(GraphicsContext3D* context, const IntSize& size, PreserveDrawingBuffer preserve, AlphaRequirement alpha)
97{
98    Extensions3D* extensions = context->getExtensions();
99    bool multisampleSupported = extensions->maySupportMultisampling()
100        && extensions->supports("GL_ANGLE_framebuffer_blit")
101        && extensions->supports("GL_ANGLE_framebuffer_multisample")
102        && extensions->supports("GL_OES_rgb8_rgba8");
103    if (multisampleSupported) {
104        extensions->ensureEnabled("GL_ANGLE_framebuffer_blit");
105        extensions->ensureEnabled("GL_ANGLE_framebuffer_multisample");
106        extensions->ensureEnabled("GL_OES_rgb8_rgba8");
107    }
108    bool packedDepthStencilSupported = extensions->supports("GL_OES_packed_depth_stencil");
109    if (packedDepthStencilSupported)
110        extensions->ensureEnabled("GL_OES_packed_depth_stencil");
111    RefPtr<DrawingBuffer> drawingBuffer = adoptRef(new DrawingBuffer(context, size, multisampleSupported, packedDepthStencilSupported, preserve, alpha));
112    return (drawingBuffer->m_context) ? drawingBuffer.release() : 0;
113}
114
115void DrawingBuffer::clear()
116{
117    if (!m_context)
118        return;
119
120    m_context->makeContextCurrent();
121
122    if (!m_size.isEmpty()) {
123        s_currentResourceUsePixels -= m_size.width() * m_size.height();
124        m_size = IntSize();
125    }
126
127    if (m_colorBuffer) {
128        m_context->deleteTexture(m_colorBuffer);
129        m_colorBuffer = 0;
130    }
131
132    if (m_frontColorBuffer) {
133        m_context->deleteTexture(m_frontColorBuffer);
134        m_frontColorBuffer = 0;
135    }
136
137    if (m_multisampleColorBuffer) {
138        m_context->deleteRenderbuffer(m_multisampleColorBuffer);
139        m_multisampleColorBuffer = 0;
140    }
141
142    if (m_depthStencilBuffer) {
143        m_context->deleteRenderbuffer(m_depthStencilBuffer);
144        m_depthStencilBuffer = 0;
145    }
146
147    if (m_depthBuffer) {
148        m_context->deleteRenderbuffer(m_depthBuffer);
149        m_depthBuffer = 0;
150    }
151
152    if (m_stencilBuffer) {
153        m_context->deleteRenderbuffer(m_stencilBuffer);
154        m_stencilBuffer = 0;
155    }
156
157    if (m_multisampleFBO) {
158        m_context->deleteFramebuffer(m_multisampleFBO);
159        m_multisampleFBO = 0;
160    }
161
162    if (m_fbo) {
163        m_context->deleteFramebuffer(m_fbo);
164        m_fbo = 0;
165    }
166}
167
168void DrawingBuffer::createSecondaryBuffers()
169{
170    // create a multisample FBO
171    if (multisample()) {
172        m_multisampleFBO = m_context->createFramebuffer();
173        m_context->bindFramebuffer(GraphicsContext3D::FRAMEBUFFER, m_multisampleFBO);
174        m_multisampleColorBuffer = m_context->createRenderbuffer();
175    }
176}
177
178void DrawingBuffer::resizeDepthStencil(int sampleCount)
179{
180    const GraphicsContext3D::Attributes& attributes = m_context->getContextAttributes();
181    if (attributes.depth && attributes.stencil && m_packedDepthStencilExtensionSupported) {
182        if (!m_depthStencilBuffer)
183            m_depthStencilBuffer = m_context->createRenderbuffer();
184        m_context->bindRenderbuffer(GraphicsContext3D::RENDERBUFFER, m_depthStencilBuffer);
185        if (multisample())
186            m_context->getExtensions()->renderbufferStorageMultisample(GraphicsContext3D::RENDERBUFFER, sampleCount, Extensions3D::DEPTH24_STENCIL8, m_size.width(), m_size.height());
187        else
188            m_context->renderbufferStorage(GraphicsContext3D::RENDERBUFFER, Extensions3D::DEPTH24_STENCIL8, m_size.width(), m_size.height());
189        m_context->framebufferRenderbuffer(GraphicsContext3D::FRAMEBUFFER, GraphicsContext3D::STENCIL_ATTACHMENT, GraphicsContext3D::RENDERBUFFER, m_depthStencilBuffer);
190        m_context->framebufferRenderbuffer(GraphicsContext3D::FRAMEBUFFER, GraphicsContext3D::DEPTH_ATTACHMENT, GraphicsContext3D::RENDERBUFFER, m_depthStencilBuffer);
191    } else {
192        if (attributes.depth) {
193            if (!m_depthBuffer)
194                m_depthBuffer = m_context->createRenderbuffer();
195            m_context->bindRenderbuffer(GraphicsContext3D::RENDERBUFFER, m_depthBuffer);
196            if (multisample())
197                m_context->getExtensions()->renderbufferStorageMultisample(GraphicsContext3D::RENDERBUFFER, sampleCount, GraphicsContext3D::DEPTH_COMPONENT16, m_size.width(), m_size.height());
198            else
199                m_context->renderbufferStorage(GraphicsContext3D::RENDERBUFFER, GraphicsContext3D::DEPTH_COMPONENT16, m_size.width(), m_size.height());
200            m_context->framebufferRenderbuffer(GraphicsContext3D::FRAMEBUFFER, GraphicsContext3D::DEPTH_ATTACHMENT, GraphicsContext3D::RENDERBUFFER, m_depthBuffer);
201        }
202        if (attributes.stencil) {
203            if (!m_stencilBuffer)
204                m_stencilBuffer = m_context->createRenderbuffer();
205            m_context->bindRenderbuffer(GraphicsContext3D::RENDERBUFFER, m_stencilBuffer);
206            if (multisample())
207                m_context->getExtensions()->renderbufferStorageMultisample(GraphicsContext3D::RENDERBUFFER, sampleCount, GraphicsContext3D::STENCIL_INDEX8, m_size.width(), m_size.height());
208            else
209                m_context->renderbufferStorage(GraphicsContext3D::RENDERBUFFER, GraphicsContext3D::STENCIL_INDEX8, m_size.width(), m_size.height());
210            m_context->framebufferRenderbuffer(GraphicsContext3D::FRAMEBUFFER, GraphicsContext3D::STENCIL_ATTACHMENT, GraphicsContext3D::RENDERBUFFER, m_stencilBuffer);
211        }
212    }
213    m_context->bindRenderbuffer(GraphicsContext3D::RENDERBUFFER, 0);
214}
215
216void DrawingBuffer::clearFramebuffers(GC3Dbitfield clearMask)
217{
218    m_context->bindFramebuffer(GraphicsContext3D::FRAMEBUFFER, m_multisampleFBO ? m_multisampleFBO : m_fbo);
219
220    m_context->clear(clearMask);
221
222    // The multisample fbo was just cleared, but we also need to clear the non-multisampled buffer too.
223    if (m_multisampleFBO) {
224        m_context->bindFramebuffer(GraphicsContext3D::FRAMEBUFFER, m_fbo);
225        m_context->clear(GraphicsContext3D::COLOR_BUFFER_BIT);
226        m_context->bindFramebuffer(GraphicsContext3D::FRAMEBUFFER, m_multisampleFBO);
227    }
228}
229
230// Only way to ensure that we're not getting a bad framebuffer on some AMD/OSX devices.
231// FIXME: This can be removed once renderbufferStorageMultisample starts reporting GL_OUT_OF_MEMORY properly.
232bool DrawingBuffer::checkBufferIntegrity()
233{
234    if (!m_multisampleFBO)
235        return true;
236
237    if (m_scissorEnabled)
238        m_context->disable(GraphicsContext3D::SCISSOR_TEST);
239
240    m_context->colorMask(true, true, true, true);
241
242    m_context->bindFramebuffer(GraphicsContext3D::FRAMEBUFFER, m_multisampleFBO);
243    m_context->clearColor(1.0f, 0.0f, 1.0f, 1.0f);
244    m_context->clear(GraphicsContext3D::COLOR_BUFFER_BIT);
245
246    commit(0, 0, 1, 1);
247
248    unsigned char pixel[4] = {0, 0, 0, 0};
249    m_context->readPixels(0, 0, 1, 1, GraphicsContext3D::RGBA, GraphicsContext3D::UNSIGNED_BYTE, &pixel);
250
251    if (m_scissorEnabled)
252        m_context->enable(GraphicsContext3D::SCISSOR_TEST);
253
254    return (pixel[0] == 0xFF && pixel[1] == 0x00 && pixel[2] == 0xFF && pixel[3] == 0xFF);
255}
256
257bool DrawingBuffer::reset(const IntSize& newSize)
258{
259    if (!m_context)
260        return false;
261
262    m_context->makeContextCurrent();
263
264    int maxTextureSize = 0;
265    m_context->getIntegerv(GraphicsContext3D::MAX_TEXTURE_SIZE, &maxTextureSize);
266    if (newSize.height() > maxTextureSize || newSize.width() > maxTextureSize) {
267        clear();
268        return false;
269    }
270
271    int pixelDelta = newSize.width() * newSize.height();
272    int oldSize = 0;
273    if (!m_size.isEmpty()) {
274        oldSize = m_size.width() * m_size.height();
275        pixelDelta -= oldSize;
276    }
277
278    IntSize adjustedSize = newSize;
279    if (s_maximumResourceUsePixels) {
280        while ((s_currentResourceUsePixels + pixelDelta) > s_maximumResourceUsePixels) {
281            adjustedSize.scale(s_resourceAdjustedRatio);
282            if (adjustedSize.isEmpty()) {
283                clear();
284                return false;
285            }
286            pixelDelta = adjustedSize.width() * adjustedSize.height();
287            pixelDelta -= oldSize;
288        }
289     }
290
291    const GraphicsContext3D::Attributes& attributes = m_context->getContextAttributes();
292
293    if (adjustedSize != m_size) {
294
295        unsigned internalColorFormat, colorFormat, internalRenderbufferFormat;
296        if (attributes.alpha) {
297            internalColorFormat = GraphicsContext3D::RGBA;
298            colorFormat = GraphicsContext3D::RGBA;
299            internalRenderbufferFormat = Extensions3D::RGBA8_OES;
300        } else {
301            internalColorFormat = GraphicsContext3D::RGB;
302            colorFormat = GraphicsContext3D::RGB;
303            internalRenderbufferFormat = Extensions3D::RGB8_OES;
304        }
305
306        do {
307            m_size = adjustedSize;
308            // resize multisample FBO
309            if (multisample()) {
310                int maxSampleCount = 0;
311
312                m_context->getIntegerv(Extensions3D::MAX_SAMPLES, &maxSampleCount);
313                int sampleCount = std::min(4, maxSampleCount);
314
315                m_context->bindFramebuffer(GraphicsContext3D::FRAMEBUFFER, m_multisampleFBO);
316
317                m_context->bindRenderbuffer(GraphicsContext3D::RENDERBUFFER, m_multisampleColorBuffer);
318                m_context->getExtensions()->renderbufferStorageMultisample(GraphicsContext3D::RENDERBUFFER, sampleCount, internalRenderbufferFormat, m_size.width(), m_size.height());
319
320                if (m_context->getError() == GraphicsContext3D::OUT_OF_MEMORY) {
321                    adjustedSize.scale(s_resourceAdjustedRatio);
322                    continue;
323                }
324
325                m_context->framebufferRenderbuffer(GraphicsContext3D::FRAMEBUFFER, GraphicsContext3D::COLOR_ATTACHMENT0, GraphicsContext3D::RENDERBUFFER, m_multisampleColorBuffer);
326                resizeDepthStencil(sampleCount);
327                if (m_context->checkFramebufferStatus(GraphicsContext3D::FRAMEBUFFER) != GraphicsContext3D::FRAMEBUFFER_COMPLETE) {
328                    adjustedSize.scale(s_resourceAdjustedRatio);
329                    continue;
330                }
331            }
332
333            // resize regular FBO
334            m_context->bindFramebuffer(GraphicsContext3D::FRAMEBUFFER, m_fbo);
335
336            m_context->bindTexture(GraphicsContext3D::TEXTURE_2D, m_colorBuffer);
337            m_context->texImage2D(GraphicsContext3D::TEXTURE_2D, 0, internalColorFormat, m_size.width(), m_size.height(), 0, colorFormat, GraphicsContext3D::UNSIGNED_BYTE, 0);
338
339            m_context->framebufferTexture2D(GraphicsContext3D::FRAMEBUFFER, GraphicsContext3D::COLOR_ATTACHMENT0, GraphicsContext3D::TEXTURE_2D, m_colorBuffer, 0);
340
341            // resize the front color buffer
342            if (m_separateFrontTexture) {
343                m_context->bindTexture(GraphicsContext3D::TEXTURE_2D, m_frontColorBuffer);
344                m_context->texImage2D(GraphicsContext3D::TEXTURE_2D, 0, internalColorFormat, m_size.width(), m_size.height(), 0, colorFormat, GraphicsContext3D::UNSIGNED_BYTE, 0);
345            }
346
347            m_context->bindTexture(GraphicsContext3D::TEXTURE_2D, 0);
348
349            if (!multisample())
350                resizeDepthStencil(0);
351            if (m_context->checkFramebufferStatus(GraphicsContext3D::FRAMEBUFFER) != GraphicsContext3D::FRAMEBUFFER_COMPLETE) {
352                adjustedSize.scale(s_resourceAdjustedRatio);
353                continue;
354            }
355
356#if OS(DARWIN)
357            // FIXME: This can be removed once renderbufferStorageMultisample starts reporting GL_OUT_OF_MEMORY properly on OSX.
358            if (!checkBufferIntegrity()) {
359                adjustedSize.scale(s_resourceAdjustedRatio);
360                continue;
361            }
362#endif
363
364            break;
365
366        } while (!adjustedSize.isEmpty());
367
368        pixelDelta = m_size.width() * m_size.height();
369        pixelDelta -= oldSize;
370        s_currentResourceUsePixels += pixelDelta;
371
372        if (!newSize.isEmpty() && adjustedSize.isEmpty()) {
373            clear();
374            return false;
375        }
376    }
377
378    m_context->disable(GraphicsContext3D::SCISSOR_TEST);
379    m_context->clearColor(0, 0, 0, 0);
380    m_context->colorMask(true, true, true, true);
381
382    GC3Dbitfield clearMask = GraphicsContext3D::COLOR_BUFFER_BIT;
383    if (attributes.depth) {
384        m_context->clearDepth(1.0f);
385        clearMask |= GraphicsContext3D::DEPTH_BUFFER_BIT;
386        m_context->depthMask(true);
387    }
388    if (attributes.stencil) {
389        m_context->clearStencil(0);
390        clearMask |= GraphicsContext3D::STENCIL_BUFFER_BIT;
391        m_context->stencilMaskSeparate(GraphicsContext3D::FRONT, 0xFFFFFFFF);
392    }
393
394    clearFramebuffers(clearMask);
395
396    return true;
397}
398
399void DrawingBuffer::commit(long x, long y, long width, long height)
400{
401    if (!m_context)
402        return;
403
404    if (width < 0)
405        width = m_size.width();
406    if (height < 0)
407        height = m_size.height();
408
409    m_context->makeContextCurrent();
410
411    if (m_multisampleFBO) {
412        m_context->bindFramebuffer(Extensions3D::READ_FRAMEBUFFER, m_multisampleFBO);
413        m_context->bindFramebuffer(Extensions3D::DRAW_FRAMEBUFFER, m_fbo);
414
415        if (m_scissorEnabled)
416            m_context->disable(GraphicsContext3D::SCISSOR_TEST);
417
418        // Use NEAREST, because there is no scale performed during the blit.
419        m_context->getExtensions()->blitFramebuffer(x, y, width, height, x, y, width, height, GraphicsContext3D::COLOR_BUFFER_BIT, GraphicsContext3D::NEAREST);
420
421        if (m_scissorEnabled)
422            m_context->enable(GraphicsContext3D::SCISSOR_TEST);
423    }
424
425    m_context->bindFramebuffer(GraphicsContext3D::FRAMEBUFFER, m_fbo);
426}
427
428void DrawingBuffer::restoreFramebufferBinding()
429{
430    if (!m_context || !m_framebufferBinding)
431        return;
432
433    m_context->bindFramebuffer(GraphicsContext3D::FRAMEBUFFER, m_framebufferBinding);
434}
435
436bool DrawingBuffer::multisample() const
437{
438    return m_context && m_context->getContextAttributes().antialias && m_multisampleExtensionSupported;
439}
440
441void DrawingBuffer::discardResources()
442{
443    m_colorBuffer = 0;
444    m_frontColorBuffer = 0;
445    m_multisampleColorBuffer = 0;
446
447    m_depthStencilBuffer = 0;
448    m_depthBuffer = 0;
449
450    m_stencilBuffer = 0;
451
452    m_multisampleFBO = 0;
453    m_fbo = 0;
454}
455
456void DrawingBuffer::bind()
457{
458    if (!m_context)
459        return;
460
461    m_context->bindFramebuffer(GraphicsContext3D::FRAMEBUFFER, m_multisampleFBO ? m_multisampleFBO : m_fbo);
462}
463
464} // namespace WebCore
465
466#endif
467