diff options
Diffstat (limited to 'Source/WebCore/platform/graphics/android/GraphicsContext3DInternal.cpp')
-rw-r--r-- | Source/WebCore/platform/graphics/android/GraphicsContext3DInternal.cpp | 1103 |
1 files changed, 1103 insertions, 0 deletions
diff --git a/Source/WebCore/platform/graphics/android/GraphicsContext3DInternal.cpp b/Source/WebCore/platform/graphics/android/GraphicsContext3DInternal.cpp new file mode 100644 index 0000000..7855739 --- /dev/null +++ b/Source/WebCore/platform/graphics/android/GraphicsContext3DInternal.cpp @@ -0,0 +1,1103 @@ +/* + * Copyright (C) 2011, 2012, Sony Ericsson Mobile Communications AB + * Copyright (C) 2012 Sony Mobile Communications AB + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of the Sony Ericsson Mobile Communications AB nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 SONY ERICSSON MOBILE COMMUNICATIONS AB 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 "GraphicsContext3DInternal.h" + +#include "CurrentTime.h" +#include "Frame.h" +#include "HostWindow.h" +#include "HTMLCanvasElement.h" +#include "ImageBuffer.h" +#include "ImageData.h" +#include "PlatformGraphicsContext.h" +#include "RenderLayer.h" +#include "RenderLayerBacking.h" +#include "RenderObject.h" +#include "TilesManager.h" +#include "TransformationMatrix.h" +#include "WebViewCore.h" + +#include "SkBitmap.h" +#include "SkDevice.h" +#include <binder/IBinder.h> +#include <hardware/hardware.h> +#include <private/gui/ComposerService.h> +#include <gui/IGraphicBufferAlloc.h> +#include <gui/ISurfaceComposer.h> +#include <gui/SurfaceComposerClient.h> +#include <JNIUtility.h> + +#if ENABLE(WEBGL) +namespace WebCore { + +class WebGLFPSTimer { +public: + WebGLFPSTimer() + : m_ticks(0) + { + m_startTime = currentTime(); + } + + void tick() + { + if (++m_ticks == s_sampleRate) + reset(); + } + + void reset() + { + double totalTime = currentTime() - m_startTime; + if (totalTime > 0) { + double averageFPS = m_ticks / totalTime; + LOGWEBGL("Average FPS: %0.3f", averageFPS); + } + + m_ticks = 0; + m_startTime = currentTime(); + } + +private: + long m_ticks; + double m_startTime; + + static const int s_sampleRate; +}; + +const int WebGLFPSTimer::s_sampleRate = 15; + +#define OLD_GRAPHICBUFFER_ALLOC +class FBO { +public: + static FBO* createFBO(EGLDisplay dpy, int width, int height, GraphicsContext3D::Attributes attrs); + ~FBO(); + + EGLSyncKHR sync() { return m_sync; } + void setSync(EGLSyncKHR sync) { m_sync = sync; } + + GLuint fbo() { return m_fbo; } + EGLImageKHR image() { return m_image; } + + bool isLocked() { return m_locked; } + void setLocked(bool locked) { m_locked = locked; } + + bool lockGraphicBuffer(void** ptr) { + return (m_grBuffer.get() && + (m_grBuffer->lock(GraphicBuffer::USAGE_SW_READ_RARELY, ptr) == NO_ERROR)); + } + void unlockGraphicBuffer() { + if (m_grBuffer.get()) + m_grBuffer->unlock(); + } + + int bytesPerRow() { return m_grBuffer.get() ? m_grBuffer->getStride() * 4 : 0; } + +private: + FBO(EGLDisplay dpy); + bool init(int width, int height, GraphicsContext3D::Attributes attrs); + + GLuint createTexture(EGLImageKHR image, int width, int height); + + EGLDisplay m_dpy; + GLuint m_texture; + GLuint m_fbo; + GLuint m_depthBuffer; + GLuint m_stencilBuffer; + EGLImageKHR m_image; + EGLSyncKHR m_sync; + sp<IGraphicBufferAlloc> m_graphicBufferAlloc; + sp<GraphicBuffer> m_grBuffer; + bool m_locked; +}; + + +#define CANVAS_MAX_WIDTH 1280 +#define CANVAS_MAX_HEIGHT 1280 + +bool GraphicsContext3DInternal::s_loggingEnabled = false; + +EGLint GraphicsContext3DInternal::checkEGLError(const char* s) +{ + EGLint error = eglGetError(); + if (error == EGL_SUCCESS) { + LOGWEBGL("%s() OK", s); + } + else { + LOGWEBGL("after %s() eglError = 0x%x", s, error); + } + + return error; +} + +GLint GraphicsContext3DInternal::checkGLError(const char* s) +{ + GLint error = glGetError(); + if (error == GL_NO_ERROR) { + LOGWEBGL("%s() OK", s); + } + else { + LOGWEBGL("after %s() glError (0x%x)", s, error); + } + + return error; +} + +GraphicsContext3DInternal::GraphicsContext3DInternal(HTMLCanvasElement* canvas, + GraphicsContext3D::Attributes attrs, + HostWindow* hostWindow) + : m_proxy(adoptRef(new GraphicsContext3DProxy())) + , m_compositingLayer(new WebGLLayer(m_proxy)) + , m_canvas(canvas) + , m_attrs(attrs) + , m_layerComposited(false) + , m_canvasDirty(false) + , m_width(1) + , m_height(1) + , m_maxwidth(CANVAS_MAX_WIDTH) + , m_maxheight(CANVAS_MAX_HEIGHT) + , m_dpy(EGL_NO_DISPLAY) + , m_config(0) + , m_surface(EGL_NO_SURFACE) + , m_context(EGL_NO_CONTEXT) + , m_syncThread(0) + , m_threadState(THREAD_STATE_STOPPED) + , m_syncTimer(this, &GraphicsContext3DInternal::syncTimerFired) + , m_syncRequested(false) + , m_webGLFPSTimer(0) + , m_extensions(0) + , m_contextId(0) +{ + enableLogging(); + + //Need to initialize to a NULL state so that if you FBO creation fails later on, its in a valid state during destruction + for (int i = 0; i < NUM_BUFFERS; i++) { + m_fbo[i] = NULL; + } + + LOGWEBGL("GraphicsContext3DInternal() = %p, m_compositingLayer = %p", this, m_compositingLayer); + m_proxy->setGraphicsContext(this); + + if (!m_canvas || !m_canvas->document() || !m_canvas->document()->view()) + return; + JNIEnv* env = JSC::Bindings::getJNIEnv(); + WebViewCore* core = WebViewCore::getWebViewCore(m_canvas->document()->view()); + if (!core) + return; + jobject tmp = core->getWebViewJavaObject(); + m_webView = env->NewGlobalRef(tmp); + if (!m_webView) + return; + jclass webViewClass = env->GetObjectClass(m_webView); + m_postInvalidate = env->GetMethodID(webViewClass, "postInvalidate", "()V"); + env->DeleteLocalRef(webViewClass); + if (!m_postInvalidate) + return; + + if (!initEGL()) + return; + + if (!createContext(true)) { + LOGWEBGL("Create context failed. Perform JS garbage collection and try again."); + // Probably too many contexts. Force a JS garbage collection, and then try again. + // This typically only happens in Khronos Conformance tests. + m_canvas->document()->frame()->script()->lowMemoryNotification(); + if (!createContext(true)) { + LOGWEBGL("Create context still failed: aborting."); + return; + } + } + + m_webGLFPSTimer.set(new WebGLFPSTimer()); + + const char *ext = (const char *)glGetString(GL_EXTENSIONS); + LOGWEBGL("GL_EXTENSIONS = %s", ext); + // Want to keep control of which extensions are used + String extensions = ""; + if (strstr(ext, "GL_OES_texture_npot")) + extensions.append("GL_OES_texture_npot"); + if (strstr(ext, "GL_OES_packed_depth_stencil")) + extensions.append(" GL_OES_packed_depth_stencil"); + if (strstr(ext, "GL_OES_texture_float")) + extensions.append(" GL_OES_texture_float"); + m_extensions.set(new Extensions3DAndroid(extensions)); + + // ANGLE initialization. + ShBuiltInResources resources; + ShInitBuiltInResources(&resources); + + glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &resources.MaxVertexAttribs); + glGetIntegerv(GL_MAX_VERTEX_UNIFORM_VECTORS, &resources.MaxVertexUniformVectors); + glGetIntegerv(GL_MAX_VARYING_VECTORS, &resources.MaxVaryingVectors); + glGetIntegerv(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, &resources.MaxVertexTextureImageUnits); + glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &resources.MaxCombinedTextureImageUnits); + glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &resources.MaxTextureImageUnits); + glGetIntegerv(GL_MAX_FRAGMENT_UNIFORM_VECTORS, &resources.MaxFragmentUniformVectors); + + resources.MaxDrawBuffers = 1; + m_compiler.setResources(resources); + + m_savedViewport.x = 0; + m_savedViewport.y = 0; + m_savedViewport.width = m_width; + m_savedViewport.height = m_height; + + glClearColor(0, 0, 0, 0); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + startSyncThread(); + + static int contextCounter = 1; + m_contextId = contextCounter++; +} + +GraphicsContext3DInternal::~GraphicsContext3DInternal() +{ + LOGWEBGL("~GraphicsContext3DInternal(), this = %p", this); + + stopSyncThread(); + + m_proxy->setGraphicsContext(0); + MutexLocker lock(m_fboMutex); + SkSafeUnref(m_compositingLayer); + m_compositingLayer = 0; + deleteContext(true); + + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->DeleteGlobalRef(m_webView); +} + +bool GraphicsContext3DInternal::initEGL() +{ + m_dpy = eglGetDisplay(EGL_DEFAULT_DISPLAY); + if (m_dpy == EGL_NO_DISPLAY) + return false; + + EGLint majorVersion; + EGLint minorVersion; + EGLBoolean returnValue = eglInitialize(m_dpy, &majorVersion, &minorVersion); + if (returnValue != EGL_TRUE) + return false; + + LOGWEBGL("EGL version %d.%d", majorVersion, minorVersion); + const char *s = eglQueryString(m_dpy, EGL_VENDOR); + LOGWEBGL("EGL_VENDOR = %s", s); + s = eglQueryString(m_dpy, EGL_VERSION); + LOGWEBGL("EGL_VERSION = %s", s); + s = eglQueryString(m_dpy, EGL_EXTENSIONS); + LOGWEBGL("EGL_EXTENSIONS = %s", s); + s = eglQueryString(m_dpy, EGL_CLIENT_APIS); + LOGWEBGL("EGL_CLIENT_APIS = %s", s); + + EGLint config_attribs[21]; + int p = 0; + config_attribs[p++] = EGL_BLUE_SIZE; + config_attribs[p++] = 8; + config_attribs[p++] = EGL_GREEN_SIZE; + config_attribs[p++] = 8; + config_attribs[p++] = EGL_RED_SIZE; + config_attribs[p++] = 8; + config_attribs[p++] = EGL_SURFACE_TYPE; + config_attribs[p++] = EGL_PBUFFER_BIT; + config_attribs[p++] = EGL_RENDERABLE_TYPE; + config_attribs[p++] = EGL_OPENGL_ES2_BIT; + config_attribs[p++] = EGL_ALPHA_SIZE; + config_attribs[p++] = m_attrs.alpha ? 8 : 0; + if (m_attrs.depth) { + config_attribs[p++] = EGL_DEPTH_SIZE; + config_attribs[p++] = 16; + } + if (m_attrs.stencil) { + config_attribs[p++] = EGL_STENCIL_SIZE; + config_attribs[p++] = 8; + } + // Antialiasing currently is not supported. + m_attrs.antialias = false; + config_attribs[p] = EGL_NONE; + + EGLint num_configs = 0; + return (eglChooseConfig(m_dpy, config_attribs, &m_config, 1, &num_configs) == EGL_TRUE); +} + +bool GraphicsContext3DInternal::createContext(bool createEGLContext) +{ + LOGWEBGL("createContext()"); + EGLint context_attribs[] = { + EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_NONE}; + + if (createEGLContext) { + EGLint surface_attribs[] = { + EGL_WIDTH, 1, + EGL_HEIGHT, 1, + EGL_NONE}; + m_surface = eglCreatePbufferSurface(m_dpy, m_config, surface_attribs); + m_context = eglCreateContext(m_dpy, m_config, EGL_NO_CONTEXT, context_attribs); + } + if (m_context == EGL_NO_CONTEXT) { + deleteContext(createEGLContext); + return false; + } + + makeContextCurrent(); + for (int i = 0; i < NUM_BUFFERS; i++) { + FBO* tmp = FBO::createFBO(m_dpy, m_width > 0 ? m_width : 1, m_height > 0 ? m_height : 1, m_attrs); + if (tmp == 0) { + LOGWEBGL("Failed to create FBO"); + deleteContext(createEGLContext); + return false; + } + m_fbo[i] = tmp; + m_freeBuffers.append(tmp); + } + + m_currentFBO = dequeueBuffer(); + m_boundFBO = m_currentFBO->fbo(); + m_frontFBO = 0; + glBindFramebuffer(GL_FRAMEBUFFER, m_boundFBO); + + return true; +} + +void GraphicsContext3DInternal::deleteContext(bool deleteEGLContext) +{ + LOGWEBGL("deleteContext(%s)", deleteEGLContext ? "true" : "false"); + + makeContextCurrent(); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + m_freeBuffers.clear(); + m_queuedBuffers.clear(); + m_preparedBuffers.clear(); + for (int i = 0; i < NUM_BUFFERS; i++) { + if (m_fbo[i]) { + delete m_fbo[i]; + m_fbo[i] = 0; + } + } + m_currentFBO = 0; + m_frontFBO = 0; + + eglMakeCurrent(m_dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + if (deleteEGLContext) { + if (m_surface != EGL_NO_SURFACE) { + eglDestroySurface(m_dpy, m_surface); + m_surface = EGL_NO_SURFACE; + } + if (m_context != EGL_NO_CONTEXT) { + eglDestroyContext(m_dpy, m_context); + m_context = EGL_NO_CONTEXT; + } + } +} + +void GraphicsContext3DInternal::makeContextCurrent() +{ + if (eglGetCurrentContext() != m_context && m_context != EGL_NO_CONTEXT) { + eglMakeCurrent(m_dpy, m_surface, m_surface, m_context); + } +} + +GraphicsContext3D::Attributes GraphicsContext3DInternal::getContextAttributes() +{ + return m_attrs; +} + +unsigned long GraphicsContext3DInternal::getError() +{ + if (m_syntheticErrors.size() > 0) { + ListHashSet<unsigned long>::iterator iter = m_syntheticErrors.begin(); + unsigned long err = *iter; + m_syntheticErrors.remove(iter); + return err; + } + LOGWEBGL("glGetError()"); + makeContextCurrent(); + return glGetError(); +} + +void GraphicsContext3DInternal::synthesizeGLError(unsigned long error) +{ + m_syntheticErrors.add(error); +} + +FBO* FBO::createFBO(EGLDisplay dpy, int width, int height, GraphicsContext3D::Attributes attributes) +{ + LOGWEBGL("createFBO()"); + FBO* fbo = new FBO(dpy); + + if (!fbo->init(width, height, attributes)) { + delete fbo; + return 0; + } + return fbo; +} + +FBO::FBO(EGLDisplay dpy) + : m_dpy(dpy) + , m_texture(0) + , m_fbo(0) + , m_depthBuffer(0) + , m_image(0) + , m_sync(0) + , m_grBuffer(0) + , m_locked(false) +{ +} + +bool FBO::init(int width, int height, GraphicsContext3D::Attributes attributes) +{ + // 1. Allocate a graphic buffer + sp<ISurfaceComposer> composer(ComposerService::getComposerService()); + m_graphicBufferAlloc = composer->createGraphicBufferAlloc(); + + status_t error; + + PixelFormat format = attributes.alpha ? HAL_PIXEL_FORMAT_RGBA_8888 : HAL_PIXEL_FORMAT_RGBX_8888; + + m_grBuffer = m_graphicBufferAlloc->createGraphicBuffer(width, height, format, + GRALLOC_USAGE_HW_TEXTURE, &error); + if (error != NO_ERROR) { + LOGWEBGL(" failed to allocate GraphicBuffer, error = %d", error); + return false; + } + + void *addr = 0; + if (m_grBuffer->lock(GRALLOC_USAGE_SW_WRITE_RARELY, &addr) != NO_ERROR) { + LOGWEBGL(" failed to lock the GraphicBuffer"); + return false; + } + // WebGL requires all buffers to be initialized to 0. + memset(addr, 0, width * height * 4); + m_grBuffer->unlock(); + + ANativeWindowBuffer* clientBuf = m_grBuffer->getNativeBuffer(); + if (clientBuf->handle == 0) { + LOGWEBGL(" empty handle in GraphicBuffer"); + return false; + } + + // 2. Create an EGLImage from the graphic buffer + const EGLint attrs[] = { + EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, + EGL_NONE, EGL_NONE + }; + + m_image = eglCreateImageKHR(m_dpy, + EGL_NO_CONTEXT, + EGL_NATIVE_BUFFER_ANDROID, + (EGLClientBuffer)clientBuf, + attrs); + if (GraphicsContext3DInternal::checkEGLError("eglCreateImageKHR") != EGL_SUCCESS) { + LOGWEBGL("eglCreateImageKHR() failed"); + return false; + } + + // 3. Create a texture from the EGLImage + m_texture = createTexture(m_image, width, height); + if (m_texture == 0) { + LOGWEBGL("createTexture() failed"); + return false; + } + + // 4. Create the Framebuffer Object from the texture + glGenFramebuffers(1, &m_fbo); + + if (attributes.depth) { + glGenRenderbuffers(1, &m_depthBuffer); + glBindRenderbuffer(GL_RENDERBUFFER, m_depthBuffer); + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, width, height); + if (GraphicsContext3DInternal::checkGLError("glRenderbufferStorage") != GL_NO_ERROR) + return false; + } + + if (attributes.stencil) { + glGenRenderbuffers(1, &m_stencilBuffer); + glBindRenderbuffer(GL_RENDERBUFFER, m_stencilBuffer); + glRenderbufferStorage(GL_RENDERBUFFER, GL_STENCIL_INDEX8, width, height); + if (GraphicsContext3DInternal::checkGLError("glRenderbufferStorage") != GL_NO_ERROR) + return false; + } + + glBindRenderbuffer(GL_RENDERBUFFER, 0); + + glBindFramebuffer(GL_FRAMEBUFFER, m_fbo); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_texture, 0); + if (GraphicsContext3DInternal::checkGLError("glFramebufferTexture2D") != GL_NO_ERROR) + return false; + + if (attributes.depth) { + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, m_depthBuffer); + if (GraphicsContext3DInternal::checkGLError("glFramebufferRenderbuffer") != GL_NO_ERROR) + return false; + } + + if (attributes.stencil) { + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, m_stencilBuffer); + if (GraphicsContext3DInternal::checkGLError("glFramebufferRenderbuffer") != GL_NO_ERROR) + return false; + } + + GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + if (status != GL_FRAMEBUFFER_COMPLETE) { + LOGWEBGL("Framebuffer incomplete: %d", status); + return false; + } + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + return true; +} + +FBO::~FBO() +{ + LOGWEBGL("FBO::~FBO()"); + if (m_image) { + eglDestroyImageKHR(m_dpy, m_image); + GraphicsContext3DInternal::checkEGLError("eglDestroyImageKHR"); + } + if (m_texture) + glDeleteTextures(1, &m_texture); + if (m_depthBuffer) + glDeleteRenderbuffers(1, &m_depthBuffer); + if (m_stencilBuffer) + glDeleteRenderbuffers(1, &m_stencilBuffer); + if (m_fbo) + glDeleteFramebuffers(1, &m_fbo); +} + +GLuint FBO::createTexture(EGLImageKHR image, int width, int height) +{ + LOGWEBGL("createTexture(image = %p)", image); + GLuint texture; + + glGenTextures(1, &texture); + glBindTexture(GL_TEXTURE_2D, texture); + + //glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + //glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + bool error = false; + if (image) { + glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, (GLeglImageOES)image); + error = (GraphicsContext3DInternal::checkGLError("glEGLImageTargetTexture2DOES") + != GL_NO_ERROR); + } + else { + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); + error = (GraphicsContext3DInternal::checkGLError("glTexImage2D()") != GL_NO_ERROR); + } + glBindTexture(GL_TEXTURE_2D, 0); + if (error) { + glDeleteTextures(1, &texture); + texture = 0; + } + + return texture; +} + +void GraphicsContext3DInternal::startSyncThread() +{ + LOGWEBGL("+startSyncThread()"); + MutexLocker lock(m_threadMutex); + m_threadState = THREAD_STATE_STOPPED; + m_syncThread = createThread(syncThreadStart, this, "GraphicsContext3DInternal"); + // Wait for thread to start + while (m_threadState != THREAD_STATE_RUN) { + m_threadCondition.wait(m_threadMutex); + } + LOGWEBGL("-startSyncThread()"); +} + +void GraphicsContext3DInternal::stopSyncThread() +{ + LOGWEBGL("+stopSyncThread()"); + MutexLocker lock(m_threadMutex); + if (m_syncThread) { + m_threadState = THREAD_STATE_STOP; + // Signal thread to wake up + m_threadCondition.broadcast(); + // Wait for thread to stop + while (m_threadState != THREAD_STATE_STOPPED) { + m_threadCondition.wait(m_threadMutex); + } + m_syncThread = 0; + } + LOGWEBGL("-stopSyncThread()"); +} + +void* GraphicsContext3DInternal::syncThreadStart(void* ctx) +{ + GraphicsContext3DInternal* context = static_cast<GraphicsContext3DInternal*>(ctx); + context->runSyncThread(); + + return 0; +} + +void GraphicsContext3DInternal::runSyncThread() +{ + LOGWEBGL("SyncThread: starting"); + FBO* fbo = 0; + + MutexLocker lock(m_threadMutex); + m_threadState = THREAD_STATE_RUN; + // Signal to creator that we are up and running + m_threadCondition.broadcast(); + + while (m_threadState == THREAD_STATE_RUN) { + while (m_threadState == THREAD_STATE_RUN) { + { + MutexLocker lock(m_fboMutex); + if (!m_queuedBuffers.isEmpty()) { + fbo = m_queuedBuffers.takeFirst(); + break; + } + } + m_threadCondition.wait(m_threadMutex); + } + LOGWEBGL("SyncThread: woke after waiting for FBO, fbo = %p", fbo); + if (m_threadState != THREAD_STATE_RUN) + break; + + if (fbo->sync() != EGL_NO_SYNC_KHR) { + eglClientWaitSyncKHR(m_dpy, fbo->sync(), 0, 0); + eglDestroySyncKHR(m_dpy, fbo->sync()); + fbo->setSync(EGL_NO_SYNC_KHR); + LOGWEBGL("SyncThread: returned after waiting for Sync"); + } + + { + MutexLocker lock(m_fboMutex); + m_preparedBuffers.append(fbo); + LOGWEBGL("SyncThread: prepared buffer = %p", fbo); + updateFrontBuffer(); + } + + // Invalidate the canvas region + if (m_postInvalidate) { + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_webView, m_postInvalidate); + } + } + + // Signal to calling thread that we have stopped + m_threadState = THREAD_STATE_STOPPED; + m_threadCondition.broadcast(); + LOGWEBGL("SyncThread: terminating"); +} + +PlatformLayer* GraphicsContext3DInternal::platformLayer() const +{ + LOGWEBGL("platformLayer()"); + return m_compositingLayer; +} + +void GraphicsContext3DInternal::reshape(int width, int height) +{ + LOGWEBGL("reshape(%d, %d)", width, height); + bool mustRestoreFBO = (m_boundFBO != (m_currentFBO ? m_currentFBO->fbo() : 0)); + + m_width = width > m_maxwidth ? m_maxwidth : width; + m_height = height > m_maxheight ? m_maxheight : height; + + stopSyncThread(); + makeContextCurrent(); + m_proxy->setGraphicsContext(0); + { + MutexLocker lock(m_fboMutex); + deleteContext(false); + + if (createContext(false)) { + if (!mustRestoreFBO) { + m_boundFBO = m_currentFBO->fbo(); + } + glBindFramebuffer(GL_FRAMEBUFFER, m_boundFBO); + } + } + m_proxy->setGraphicsContext(this); + startSyncThread(); +} + +void GraphicsContext3DInternal::recreateSurface() +{ + LOGWEBGL("recreateSurface()"); + if (m_currentFBO != 0) + // We already have a current surface + return; + reshape(m_width, m_height); + glViewport(m_savedViewport.x, m_savedViewport.y, m_savedViewport.width, m_savedViewport.height); +} + +void GraphicsContext3DInternal::releaseSurface() +{ + LOGWEBGL("releaseSurface(%d)", m_contextId); + if (m_currentFBO == 0) + // We don't have any current surface + return; + stopSyncThread(); + m_proxy->setGraphicsContext(0); + { + MutexLocker lock(m_fboMutex); + deleteContext(false); + } + makeContextCurrent(); + m_proxy->setGraphicsContext(this); +} + +void GraphicsContext3DInternal::syncTimerFired(Timer<GraphicsContext3DInternal>*) +{ + m_syncRequested = false; + + // Do not perform the composition step if it is an offscreen canvas + if (m_canvas->renderer()) + swapBuffers(); +} + +void GraphicsContext3DInternal::markContextChanged() +{ + if (!m_syncRequested) { + m_syncTimer.startOneShot(0); + m_syncRequested = true; + } + m_canvasDirty = true; + m_layerComposited = false; +} + +/* + * Must hold m_fboMutex when calling this function. + */ +FBO* GraphicsContext3DInternal::dequeueBuffer() +{ + LOGWEBGL("GraphicsContext3DInternal::dequeueBuffer()"); + while (m_freeBuffers.isEmpty()) { + m_fboCondition.wait(m_fboMutex); + } + FBO* fbo = m_freeBuffers.takeFirst(); + + if (fbo->sync() != EGL_NO_SYNC_KHR) { + eglClientWaitSyncKHR(m_dpy, fbo->sync(), 0, 0); + eglDestroySyncKHR(m_dpy, fbo->sync()); + fbo->setSync(EGL_NO_SYNC_KHR); + } + + return fbo; +} + +void GraphicsContext3DInternal::swapBuffers() +{ + if (s_loggingEnabled) + m_webGLFPSTimer->tick(); + + LOGWEBGL("+swapBuffers()"); + + MutexLocker lock(m_fboMutex); + FBO* fbo = m_currentFBO; + if (fbo == 0) + return; + + makeContextCurrent(); + + bool mustRestoreFBO = (m_boundFBO != fbo->fbo()); + if (mustRestoreFBO) { + glBindFramebuffer(GL_FRAMEBUFFER, fbo->fbo()); + } + + // Create the fence sync and notify the sync thread + fbo->setSync(eglCreateSyncKHR(m_dpy, EGL_SYNC_FENCE_KHR, 0)); + glFlush(); + m_queuedBuffers.append(fbo); + m_threadCondition.broadcast(); + + // Dequeue a new buffer + fbo = dequeueBuffer(); + m_currentFBO = fbo; + + if (!mustRestoreFBO) { + m_boundFBO = m_currentFBO->fbo(); + } + glBindFramebuffer(GL_FRAMEBUFFER, m_boundFBO); + m_canvasDirty = false; + m_layerComposited = true; + LOGWEBGL("-swapBuffers()"); +} + +void GraphicsContext3DInternal::updateFrontBuffer() +{ + if (!m_preparedBuffers.isEmpty()) { + if (m_frontFBO == 0) { + m_frontFBO = m_preparedBuffers.takeFirst(); + } + else if (!m_frontFBO->isLocked()) { + m_freeBuffers.append(m_frontFBO); + m_frontFBO = m_preparedBuffers.takeFirst(); + m_fboCondition.broadcast(); + } + } +} + +bool GraphicsContext3DInternal::lockFrontBuffer(EGLImageKHR& image, SkRect& rect) +{ + LOGWEBGL("GraphicsContext3DInternal::lockFrontBuffer()"); + MutexLocker lock(m_fboMutex); + FBO* fbo = m_frontFBO; + + if (!fbo || !fbo->image()) { + LOGWEBGL("-GraphicsContext3DInternal::lockFrontBuffer(), fbo = %p", fbo); + return false; + } + + fbo->setLocked(true); + image = fbo->image(); + + RenderObject* renderer = m_canvas->renderer(); + if (renderer && renderer->isBox()) { + RenderBox* box = (RenderBox*)renderer; + rect.setXYWH(box->borderLeft() + box->paddingLeft(), + box->borderTop() + box->paddingTop(), + box->contentWidth(), + box->contentHeight()); + } + + return true; +} + +void GraphicsContext3DInternal::releaseFrontBuffer() +{ + LOGWEBGL("GraphicsContext3DInternal::releaseFrontBuffer()"); + MutexLocker lock(m_fboMutex); + FBO* fbo = m_frontFBO; + + if (fbo) { + fbo->setLocked(false); + if (fbo->sync() != EGL_NO_SYNC_KHR) { + eglDestroySyncKHR(m_dpy, fbo->sync()); + } + fbo->setSync(eglCreateSyncKHR(m_dpy, EGL_SYNC_FENCE_KHR, 0)); + } + updateFrontBuffer(); +} + +void GraphicsContext3DInternal::paintRenderingResultsToCanvas(CanvasRenderingContext* context) +{ + LOGWEBGL("paintRenderingResultsToCanvas()"); + ImageBuffer* imageBuffer = context->canvas()->buffer(); + const SkBitmap& canvasBitmap = + imageBuffer->context()->platformContext()->recordingCanvas()->getDevice()->accessBitmap(false); + SkCanvas canvas(canvasBitmap); + + SkBitmap bitmap; + bitmap.setConfig(SkBitmap::kARGB_8888_Config, m_width, m_height); + bitmap.allocPixels(); + unsigned char *pixels = static_cast<unsigned char*>(bitmap.getPixels()); + glReadPixels(0, 0, m_width, m_height, GL_RGBA, GL_UNSIGNED_BYTE, pixels); + + SkRect dstRect; + dstRect.iset(0, 0, imageBuffer->size().width(), imageBuffer->size().height()); + canvas.drawBitmapRect(bitmap, 0, dstRect); +} + +PassRefPtr<ImageData> GraphicsContext3DInternal::paintRenderingResultsToImageData() +{ + LOGWEBGL("paintRenderingResultsToImageData()"); + + // Reading premultiplied alpha would involve unpremultiplying, which is lossy. + if (m_attrs.premultipliedAlpha) + return 0; + + RefPtr<ImageData> imageData = ImageData::create(IntSize(m_width, m_height)); + unsigned char* pixels = imageData->data()->data()->data(); + + glReadPixels(0, 0, m_width, m_height, GL_RGBA, GL_UNSIGNED_BYTE, pixels); + + return imageData; +} + +bool GraphicsContext3DInternal::paintCompositedResultsToCanvas(CanvasRenderingContext* context) +{ + LOGWEBGL("paintCompositedResultsToCanvas()"); + ImageBuffer* imageBuffer = context->canvas()->buffer(); + const SkBitmap& canvasBitmap = + imageBuffer->context()->platformContext()->recordingCanvas()->getDevice()->accessBitmap(false); + SkCanvas canvas(canvasBitmap); + + MutexLocker lock(m_fboMutex); + + FBO* fbo = m_frontFBO; + if (!fbo) + return false; + + SkBitmap bitmap; + bitmap.setConfig(SkBitmap::kARGB_8888_Config, m_width, m_height, fbo->bytesPerRow()); + + unsigned char* bits = NULL; + if (fbo->lockGraphicBuffer((void**)&bits)) { + bitmap.setPixels(bits); + + SkRect dstRect; + dstRect.iset(0, 0, imageBuffer->size().width(), imageBuffer->size().height()); + canvas.save(); + canvas.translate(0, SkIntToScalar(imageBuffer->size().height())); + canvas.scale(SK_Scalar1, -SK_Scalar1); + canvas.drawBitmapRect(bitmap, 0, dstRect); + canvas.restore(); + bitmap.setPixels(0); + fbo->unlockGraphicBuffer(); + } + + return true; +} + +void GraphicsContext3DInternal::bindFramebuffer(GC3Denum target, Platform3DObject buffer) +{ + LOGWEBGL("glBindFrameBuffer(%d, %d)", target, buffer); + makeContextCurrent(); + MutexLocker lock(m_fboMutex); + if (!buffer && m_currentFBO) { + buffer = m_currentFBO->fbo(); + } + glBindFramebuffer(target, buffer); + m_boundFBO = buffer; +} + +void GraphicsContext3DInternal::compileShader(Platform3DObject shader) +{ + LOGWEBGL("compileShader()"); + HashMap<Platform3DObject, ShaderSourceEntry>::iterator result = m_shaderSourceMap.find(shader); + if (result == m_shaderSourceMap.end()) { + LOGWEBGL(" shader not found"); + return; + } + ShaderSourceEntry& entry = result->second; + + int shaderType; + glGetShaderiv(shader, GL_SHADER_TYPE, &shaderType); + + ANGLEShaderType ast = shaderType == GL_VERTEX_SHADER ? + SHADER_TYPE_VERTEX : SHADER_TYPE_FRAGMENT; + + String src; + String log; + bool isValid = m_compiler.validateShaderSource(entry.source.utf8().data(), ast, src, log); + + entry.log = log; + entry.isValid = isValid; + + if (!isValid) { + LOGWEBGL(" shader validation failed"); + return; + } + int len = entry.source.length(); + CString cstr = entry.source.utf8(); + const char* s = cstr.data(); + + LOGWEBGL("glShaderSource(%s)", cstr.data()); + glShaderSource(shader, 1, &s, &len); + + LOGWEBGL("glCompileShader()"); + glCompileShader(shader); +} + +String GraphicsContext3DInternal::getShaderInfoLog(Platform3DObject shader) +{ + LOGWEBGL("getShaderInfoLog()"); + HashMap<Platform3DObject, ShaderSourceEntry>::iterator result = m_shaderSourceMap.find(shader); + + if (result == m_shaderSourceMap.end()) { + LOGWEBGL(" shader not found"); + return ""; + } + + ShaderSourceEntry entry = result->second; + + if (entry.isValid) { + LOGWEBGL(" validated shader, retrieve OpenGL log"); + GLuint shaderID = shader; + GLint logLength; + glGetShaderiv(shaderID, GL_INFO_LOG_LENGTH, &logLength); + if (!logLength) + return ""; + + char* log = 0; + if ((log = (char *)fastMalloc(logLength * sizeof(char))) == 0) + return ""; + GLsizei returnedLogLength; + glGetShaderInfoLog(shaderID, logLength, &returnedLogLength, log); + String res = String(log, returnedLogLength); + fastFree(log); + + return res; + } + else { + LOGWEBGL(" non-validated shader, use ANGLE log"); + return entry.log; + } +} + +String GraphicsContext3DInternal::getShaderSource(Platform3DObject shader) +{ + LOGWEBGL("getShaderSource()"); + HashMap<Platform3DObject, ShaderSourceEntry>::iterator result = m_shaderSourceMap.find(shader); + + if (result == m_shaderSourceMap.end()) + return ""; + + return result->second.source; +} + +void GraphicsContext3DInternal::shaderSource(Platform3DObject shader, const String& string) +{ + LOGWEBGL("shaderSource()"); + ShaderSourceEntry entry; + + entry.source = string; + + m_shaderSourceMap.set(shader, entry); +} + +void GraphicsContext3DInternal::viewport(long x, long y, unsigned long width, unsigned long height) +{ + LOGWEBGL("glViewport(%d, %d, %d, %d)", x, y, width, height); + glViewport(x, y, width, height); + m_savedViewport.x = x; + m_savedViewport.y = y; + m_savedViewport.width = width; + m_savedViewport.height = height; +} + +void GraphicsContext3DInternal::enableLogging() +{ + char value[PROPERTY_VALUE_MAX]; + property_get("debug.webgl", value, "0"); + s_loggingEnabled = atoi(value) ? true : false; +} + +} +#endif // ENABLE(WEBGL) |