context3d.cpp 207 KB
Newer Older
Pasi Keränen's avatar
Pasi Keränen committed
/****************************************************************************
**
** Copyright (C) 2015 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing/
Pasi Keränen's avatar
Pasi Keränen committed
**
** This file is part of the QtCanvas3D module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL3$
Pasi Keränen's avatar
Pasi Keränen committed
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see http://www.qt.io/terms-conditions. For further
** information use the contact form at http://www.qt.io/contact-us.
Pasi Keränen's avatar
Pasi Keränen committed
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPLv3 included in the
** packaging of this file. Please review the following information to
Pasi Keränen's avatar
Pasi Keränen committed
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or later as published by the Free
** Software Foundation and appearing in the file LICENSE.GPL included in
** the packaging of this file. Please review the following information to
Pasi Keränen's avatar
Pasi Keränen committed
** ensure the GNU General Public License version 2.0 requirements will be
** met: http://www.gnu.org/licenses/gpl-2.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include "canvasglstatedump_p.h"
#include "activeinfo3d_p.h"
Pasi Keränen's avatar
Pasi Keränen committed
#include "canvas3d_p.h"
#include "context3d_p.h"
#include "texture3d_p.h"
#include "shader3d_p.h"
#include "program3d_p.h"
#include "buffer3d_p.h"
#include "framebuffer3d_p.h"
#include "renderbuffer3d_p.h"
#include "uniformlocation_p.h"
#include "teximage3d_p.h"
#include "arrayutils_p.h"
#include "shaderprecisionformat_p.h"
#include "enumtostringmap_p.h"
#include "canvas3dcommon_p.h"
#include "contextextensions_p.h"

#include <QtGui/QOpenGLShader>
#include <QtQml/private/qv4typedarray_p.h>
#include <QtQml/private/qv4arraybuffer_p.h>
#include <QtQml/private/qjsvalue_p.h>
#include <QtCore/private/qbytedata_p.h>
Pasi Keränen's avatar
Pasi Keränen committed

QT_BEGIN_NAMESPACE
Tomi Korpipää's avatar
Tomi Korpipää committed
QT_CANVAS3D_BEGIN_NAMESPACE

Pasi Keränen's avatar
Pasi Keränen committed
/*!
 * \qmltype Context3D
 * \since QtCanvas3D 1.0
 * \ingroup qtcanvas3d-qml-types
 * \brief Provides the 3D rendering API and context.
Pasi Keränen's avatar
Pasi Keränen committed
 *
 * An uncreatable QML type that provides a WebGL-like API that can be used to draw 3D graphics to
 * the Canvas3D element. You can get it by calling \l{Canvas3D::getContext}{Canvas3D.getContext}
 * method.
 *
 * \sa Canvas3D
 */

CanvasContext::CanvasContext(QOpenGLContext *context, QSurface *surface, QQmlEngine *engine,
                             int width, int height, bool isES2, QObject *parent) :
    CanvasAbstractObject(parent),
Pasi Keränen's avatar
Pasi Keränen committed
    QOpenGLFunctions(context),
    m_engine(engine),
    m_v4engine(QQmlEnginePrivate::getV4Engine(engine)),
    m_unpackFlipYEnabled(false),
    m_unpackPremultiplyAlphaEnabled(false),
Pasi Keränen's avatar
Pasi Keränen committed
    m_glViewportRect(0, 0, width, height),
    m_devicePixelRatio(1.0),
    m_currentProgram(0),
    m_currentArrayBuffer(0),
    m_currentElementArrayBuffer(0),
    m_currentTexture2D(0),
    m_context(context),
    m_surface(surface),
Pasi Keränen's avatar
Pasi Keränen committed
    m_error(NO_ERROR),
    m_currentFramebuffer(0),
    m_map(EnumToStringMap::newInstance()),
Pasi Keranen's avatar
Pasi Keranen committed
    m_canvas(0),
    m_maxVertexAttribs(0),
    m_isOpenGLES2(isES2),
    m_stateDumpExt(0),
    m_standardDerivatives(0)
Pasi Keränen's avatar
Pasi Keränen committed
{
    m_extensions = m_context->extensions();

Pasi Keranen's avatar
Pasi Keranen committed
    int value = 0;
    glGetIntegerv(MAX_VERTEX_ATTRIBS, &value);
    m_maxVertexAttribs = uint(value);

Pasi Keränen's avatar
Pasi Keränen committed
#ifndef QT_NO_DEBUG
    const GLubyte *version = glGetString(GL_VERSION);
    qCDebug(canvas3dinfo) << "Context3D::" << __FUNCTION__
                          << "OpenGL version:" << (const char *)version;
Pasi Keränen's avatar
Pasi Keränen committed
    version = glGetString(GL_SHADING_LANGUAGE_VERSION);
    qCDebug(canvas3dinfo) << "Context3D::" << __FUNCTION__
                          << "GLSL version:" << (const char *)version;
    qCDebug(canvas3dinfo) << "Context3D::" << __FUNCTION__
                          << "EXTENSIONS: " << m_extensions;
Pasi Keränen's avatar
Pasi Keränen committed
#endif
}

/*!
 * \internal
 */
CanvasContext::~CanvasContext()
{
    qCDebug(canvas3drendering) << "Context3D::" << __FUNCTION__;
Pasi Keränen's avatar
Pasi Keränen committed
    EnumToStringMap::deleteInstance();
}

/*!
 * \internal
 */
void CanvasContext::setCanvas(Canvas *canvas)
{
    if (m_canvas != canvas) {
        if (m_canvas) {
            disconnect(m_canvas, &QQuickItem::widthChanged, this, 0);
            disconnect(m_canvas, &QQuickItem::heightChanged, this, 0);
        }


Pasi Keränen's avatar
Pasi Keränen committed
        m_canvas = canvas;
        emit canvasChanged(canvas);
        connect(m_canvas, &QQuickItem::widthChanged,
                this, &CanvasContext::drawingBufferWidthChanged);
        connect(m_canvas, &QQuickItem::heightChanged,
                this, &CanvasContext::drawingBufferHeightChanged);
/*!
 * \qmlproperty Canvas3D Context3D::canvas
 * Holds the read only reference to the Canvas3D for this Context3D.
 */
Pasi Keränen's avatar
Pasi Keränen committed
Canvas *CanvasContext::canvas()
{
    return m_canvas;
}

 * \qmlproperty int Context3D::drawingBufferWidth
 * Holds the current read-only logical pixel width of the drawing buffer. To get width in physical pixels
 * you need to multiply this with the \c devicePixelRatio.
 */
uint CanvasContext::drawingBufferWidth()
{
    uint width = 0;
    if (m_canvas)
        width = m_canvas->width();

    qCDebug(canvas3drendering) << "Context3D::" << __FUNCTION__
                               << "(): " << width;
 * \qmlproperty int Context3D::drawingBufferHeight
 * Holds the current read-only logical pixel height of the drawing buffer. To get height in physical pixels
 * you need to multiply this with the \c devicePixelRatio.
 */
uint CanvasContext::drawingBufferHeight()
{
    uint height = 0;
    if (m_canvas)
        height = m_canvas->height();

    qCDebug(canvas3drendering) << "Context3D::" << __FUNCTION__
                               << "(): " << height;
Pasi Keränen's avatar
Pasi Keränen committed
/*!
 * \internal
 */
QString CanvasContext::glEnumToString(glEnums value) const
{
    return m_map->lookUp(value);
}

/*!
 * \internal
 */
void CanvasContext::logAllGLErrors(const QString &funcName)
Pasi Keränen's avatar
Pasi Keränen committed
{
    if (!((QLoggingCategory &) canvas3dglerrors()).isDebugEnabled())
        return;

    GLenum err;
    while ((err = glGetError()) != GL_NO_ERROR) {
        qCWarning(canvas3dglerrors) << "Context3D::" << funcName
                                    << ": OpenGL ERROR: "
                                    << glEnumToString(CanvasContext::glEnums(err));
Pasi Keränen's avatar
Pasi Keränen committed
}

/*!
 * \internal
 */
void CanvasContext::setContextAttributes(const CanvasContextAttributes &attribs)
{
    m_contextAttributes.setFrom(attribs);
}

/*!
 * \internal
 */
float CanvasContext::devicePixelRatio()
{
    return m_devicePixelRatio;
}

/*!
 * \internal
 */
void CanvasContext::setDevicePixelRatio(float ratio)
{
    qCDebug(canvas3drendering) << "Context3D::" << __FUNCTION__ << "(" << ratio << ")";
Pasi Keränen's avatar
Pasi Keränen committed
    m_devicePixelRatio = ratio;
}

/*!
 * \internal
 */
QRect CanvasContext::glViewportRect() const
{
    return m_glViewportRect;
}

/*!
 * \internal
 */
GLuint CanvasContext::currentFramebuffer()
{
    if (!m_currentFramebuffer)
        return 0;

    return m_currentFramebuffer->id();
}

/*!
 * \qmlmethod ShaderPrecisionFormat Context3D::getShaderPrecisionFormat(glEnums shadertype, glEnums precisiontype)
 * Return a new ShaderPrecisionFormat describing the range and precision for the specified shader
 * numeric format.
 * \a shadertype Type of the shader, either \c Context3D.FRAGMENT_SHADER or
 * \c{Context3D.VERTEX_SHADER}.
 * \a precisiontype Can be \c{Context3D.LOW_FLOAT}, \c{Context3D.MEDIUM_FLOAT},
 * \c{Context3D.HIGH_FLOAT}, \c{Context3D.LOW_INT}, \c{Context3D.MEDIUM_INT} or
 * \c{Context3D.HIGH_INT}.
 *
 * \sa ShaderPrecisionFormat
 */
/*!
 * \internal
 */
QJSValue CanvasContext::getShaderPrecisionFormat(glEnums shadertype,
                                                 glEnums precisiontype)
Pasi Keränen's avatar
Pasi Keränen committed
{
    QString str = QString(__FUNCTION__);
    str += QStringLiteral("(shaderType:")
            + glEnumToString(shadertype)
            + QStringLiteral(", precisionType:")
            + glEnumToString(precisiontype)
            + QStringLiteral(")");

    qCDebug(canvas3drendering) << "Context3D::" << str;
Pasi Keränen's avatar
Pasi Keränen committed

    GLint range[2];
    range[0] = 1;
    range[1] = 1;
    GLint precision = 1;

    glGetShaderPrecisionFormat((GLenum)(shadertype), (GLenum)(precisiontype), range, &precision);
    logAllGLErrors(str);
Pasi Keränen's avatar
Pasi Keränen committed
    CanvasShaderPrecisionFormat *format = new CanvasShaderPrecisionFormat();
    format->setPrecision(int(precision));
    format->setRangeMin(int(range[0]));
    format->setRangeMax(int(range[1]));
    return m_engine->newQObject(format);
Pasi Keränen's avatar
Pasi Keränen committed
}

/*!
 * \qmlmethod bool Context3D::isContextLost()
 * Always returns false.
 */
/*!
 * \internal
 */
bool CanvasContext::isContextLost()
{
    qCDebug(canvas3drendering) << "Context3D::" << __FUNCTION__
                               << "(): false";
Pasi Keränen's avatar
Pasi Keränen committed
    return false;
}

/*!
 * \qmlmethod ContextAttributes Context3D::getContextAttributes()
 * Returns a copy of the actual context parameters that are used in the current context.
 */
/*!
 * \internal
 */
QJSValue CanvasContext::getContextAttributes()
Pasi Keränen's avatar
Pasi Keränen committed
{
    qCDebug(canvas3drendering) << "Context3D::" << __FUNCTION__ << "()";
Pasi Keränen's avatar
Pasi Keränen committed

    CanvasContextAttributes *attributes = new CanvasContextAttributes();
    attributes->setAlpha(m_contextAttributes.alpha());
    attributes->setDepth(m_contextAttributes.depth());
    attributes->setStencil(m_contextAttributes.stencil());
    attributes->setAntialias(m_contextAttributes.antialias());
    attributes->setPremultipliedAlpha(m_contextAttributes.premultipliedAlpha());
    attributes->setPreserveDrawingBuffer(m_contextAttributes.preserveDrawingBuffer());
    attributes->setPreferLowPowerToHighPerformance(
                m_contextAttributes.preferLowPowerToHighPerformance());
    attributes->setFailIfMajorPerformanceCaveat(
                m_contextAttributes.failIfMajorPerformanceCaveat());

    return m_engine->newQObject(attributes);
Pasi Keränen's avatar
Pasi Keränen committed
}

/*!
 * \qmlmethod void Context3D::flush()
 * Indicates to graphics driver that previously sent commands must complete within finite time.
 */
/*!
 * \internal
 */
void CanvasContext::flush()
{
    qCDebug(canvas3drendering) << "Context3D::" << __FUNCTION__
                               << "()";
Pasi Keränen's avatar
Pasi Keränen committed
    glFlush();
    logAllGLErrors(__FUNCTION__);
Pasi Keränen's avatar
Pasi Keränen committed
}

/*!
 * \qmlmethod void Context3D::finish()
 * Forces all previous 3D rendering commands to complete.
 */
/*!
 * \internal
 */
void CanvasContext::finish()
{
    qCDebug(canvas3drendering) << "Context3D::" << __FUNCTION__
                               << "()";
Pasi Keränen's avatar
Pasi Keränen committed
    glFinish();
    logAllGLErrors(__FUNCTION__);
Pasi Keränen's avatar
Pasi Keränen committed
}

/*!
 * \qmlmethod Texture3D Context3D::createTexture()
 * Create a Texture3D object and initialize a name for it as by calling \c{glGenTextures()}.
 */
/*!
 * \internal
 */
QJSValue CanvasContext::createTexture()
Pasi Keränen's avatar
Pasi Keränen committed
{
    CanvasTexture *texture = new CanvasTexture(this);
    QJSValue value = m_engine->newQObject(texture);
    qCDebug(canvas3drendering) << "Context3D::" << __FUNCTION__
                               << "():" << value.toString();
    logAllGLErrors(__FUNCTION__);
    return value;
Pasi Keränen's avatar
Pasi Keränen committed
}

/*!
 * \qmlmethod void Context3D::deleteTexture(Texture3D texture3D)
Pasi Keränen's avatar
Pasi Keränen committed
 * Deletes the given texture as if by calling \c{glDeleteTextures()}.
 * Calling this method repeatedly on the same object has no side effects.
 * \a texture3D is the Texture3D to be deleted.
Pasi Keränen's avatar
Pasi Keränen committed
 */
/*!
 * \internal
 */
void CanvasContext::deleteTexture(QJSValue texture3D)
Pasi Keränen's avatar
Pasi Keränen committed
{
    qCDebug(canvas3drendering) << "Context3D::" << __FUNCTION__
                               << "(texture:" << texture3D.toString()
                               << ")";
    CanvasTexture *texture = getAsTexture3D(texture3D);
    if (!texture) {
Pasi Keränen's avatar
Pasi Keränen committed
        texture->del();
        logAllGLErrors(__FUNCTION__);
Pasi Keränen's avatar
Pasi Keränen committed
    } else {
        m_error = INVALID_VALUE;
        qCWarning(canvas3drendering) << "Context3D::" << __FUNCTION__
                                     << ":INVALID texture handle:" << texture3D.toString();
Pasi Keränen's avatar
Pasi Keränen committed
    }
}

/*!
 * \qmlmethod void Context3D::scissor(int x, int y, int width, int height)
 * Defines a rectangle that constrains the drawing.
 * \a x is theleft edge of the rectangle.
 * \a y is the bottom edge of the rectangle.
 * \a width is the width of the rectangle.
 * \a height is the height of the rectangle.
 */
/*!
 * \internal
 */
void CanvasContext::scissor(int x, int y, int width, int height)
{
    qCDebug(canvas3drendering) << "Context3D::" << __FUNCTION__
                               << "(x:" << x
                               << ", y:" << y
                               << ", width:" << width
                               << ", height:" << height
                               << ")";
Pasi Keränen's avatar
Pasi Keränen committed

    glScissor(x, y, width, height);
    logAllGLErrors(__FUNCTION__);
Pasi Keränen's avatar
Pasi Keränen committed
}

/*!
 * \qmlmethod void Context3D::activeTexture(glEnums texture)
 * Sets the given texture unit as active. Number of texture units is implementation dependent,
 * but must be at least 8. Initially \c Context3D.TEXTURE0 is active.
 * \a texture must be one of \c Context3D.TEXTUREi values where \c i ranges from \c 0 to
 * \c{(Context3D.MAX_COMBINED_TEXTURE_IMAGE_UNITS-1)}.
 */
/*!
 * \internal
 */
void CanvasContext::activeTexture(glEnums texture)
{
    qCDebug(canvas3drendering) << "Context3D::" << __FUNCTION__
                               << "(texture:" << glEnumToString(texture)
                               << ")";
Pasi Keränen's avatar
Pasi Keränen committed
    glActiveTexture(GLenum(texture));
    logAllGLErrors(__FUNCTION__);
Pasi Keränen's avatar
Pasi Keränen committed
}

/*!
 * \qmlmethod void Context3D::bindTexture(glEnums target, Texture3D texture3D)
Pasi Keränen's avatar
Pasi Keränen committed
 * Bind a Texture3D to a texturing target.
 * \a target is the target of the active texture unit to which the Texture3D will be bound.
 * Must be either \c{Context3D.TEXTURE_2D} or \c{Context3D.TEXTURE_CUBE_MAP}.
 * \a texture3D is the Texture3D to be bound.
Pasi Keränen's avatar
Pasi Keränen committed
 */
/*!
 * \internal
 */
void CanvasContext::bindTexture(glEnums target, QJSValue texture3D)
Pasi Keränen's avatar
Pasi Keränen committed
{
    qCDebug(canvas3drendering) << "Context3D::" << __FUNCTION__
                               << "(target:" << glEnumToString(target)
                               << ", texture:" << texture3D.toString()
                               << ")";

    CanvasTexture *texture = getAsTexture3D(texture3D);
    if (target == TEXTURE_2D)
        m_currentTexture2D = texture;
    else if (target == TEXTURE_CUBE_MAP)
        m_currentTextureCubeMap = texture;

Pasi Keränen's avatar
Pasi Keränen committed
    if (texture) {
        if (!texture->isAlive()) {
            qCWarning(canvas3drendering) << "Context3D::" << __FUNCTION__
                                         << ": Trying to bind deleted texture object";
Pasi Keränen's avatar
Pasi Keränen committed
            return;
        }

        if (target == TEXTURE_2D)
            m_currentTexture2D->bind(target);
        else if (target == TEXTURE_CUBE_MAP)
            m_currentTextureCubeMap->bind(target);

Pasi Keränen's avatar
Pasi Keränen committed
    } else {
        glBindTexture(GLenum(target), 0);
    }
    logAllGLErrors(__FUNCTION__);
Pasi Keränen's avatar
Pasi Keränen committed
}

/*!
 * \qmlmethod void Context3D::generateMipmap(glEnums target)
 * Generates a complete set of mipmaps for a texture object of the currently active texture unit.
 * \a target defines the texture target to which the texture object is bound whose mipmaps will be
 * generated. Must be either \c{Context3D.TEXTURE_2D} or \c{Context3D.TEXTURE_CUBE_MAP}.
 */
/*!
 * \internal
 */
void CanvasContext::generateMipmap(glEnums target)
{
    qCDebug(canvas3drendering) << "Context3D::" << __FUNCTION__
                               << "(target:" << glEnumToString(target)
                               << ")";

    if (target == TEXTURE_2D) {
        if (!m_currentTexture2D) {
            qCWarning(canvas3drendering) << "Context3D::" << __FUNCTION__
                                         << ":INVALID_OPERATION No current TEXTURE_2D bound";
            m_error = INVALID_OPERATION;
        } else if (!m_currentTexture2D->isAlive()) {
            qCWarning(canvas3drendering) << "Context3D::" << __FUNCTION__
                                         << ":INVALID_OPERATION Currently bound TEXTURE_2D is deleted";
            m_error = INVALID_OPERATION;
        } else {
            glGenerateMipmap(target);
            logAllGLErrors(__FUNCTION__);
        }
    } else if (target == TEXTURE_CUBE_MAP) {
        if (!m_currentTextureCubeMap) {
            qCWarning(canvas3drendering) << "Context3D::" << __FUNCTION__
                                         << ":INVALID_OPERATION No current TEXTURE_CUBE_MAP bound";
            m_error = INVALID_OPERATION;
        } else if (!m_currentTextureCubeMap->isAlive()) {
            qCWarning(canvas3drendering) << "Context3D::" << __FUNCTION__
                                         << ":INVALID_OPERATION Currently bound TEXTURE_CUBE_MAP is deleted";
            m_error = INVALID_OPERATION;
        } else {
            glGenerateMipmap(target);
            logAllGLErrors(__FUNCTION__);
        }
Pasi Keränen's avatar
Pasi Keränen committed
    }
}

/*!
 * \qmlmethod bool Context3D::isTexture(Object anyObject)
 * Returns true if the given object is a valid Texture3D object.
 * \a anyObject is the object that is to be verified as a valid texture.
 */
/*!
 * \internal
 */
bool CanvasContext::isTexture(QJSValue anyObject)
Pasi Keränen's avatar
Pasi Keränen committed
{
    qCDebug(canvas3drendering) << "Context3D::" << __FUNCTION__
                               << "(anyObject:" << anyObject.toString()
                               << ")";
Pasi Keränen's avatar
Pasi Keränen committed

    CanvasTexture *texture = getAsTexture3D(anyObject);
    if (!texture)
Pasi Keränen's avatar
Pasi Keränen committed
        return false;

    return glIsTexture(texture->textureId());
}

/*!
 * \internal
 */
CanvasTexture *CanvasContext::getAsTexture3D(QJSValue anyObject)
{
    if (!anyObject.isQObject())
        return 0;

    if (!isOfType(anyObject, "QtCanvas3D::CanvasTexture"))
        return 0;
Pasi Keränen's avatar
Pasi Keränen committed

    CanvasTexture *texture = static_cast<CanvasTexture *>(anyObject.toQObject());
Pasi Keränen's avatar
Pasi Keränen committed
    if (!texture->isAlive())
        return 0;

    return texture;

Pasi Keränen's avatar
Pasi Keränen committed
}

/*!
 * \qmlmethod void Context3D::compressedTexImage2D(glEnums target, int level, glEnums internalformat, int width, int height, int border, TypedArray pixels)
 * Not supported, \c{Context3D.INVALID_OPERATION} is generated when called.
 * \a target, \a level, \a internalformat, \a width, \a height, \a border and \a pixels are ignored.
 */
/*!
 * \internal
 */
void CanvasContext::compressedTexImage2D(glEnums target, int level, glEnums internalformat,
                                         int width, int height, int border,
                                         QJSValue pixels)
Pasi Keränen's avatar
Pasi Keränen committed
{
    qCDebug(canvas3drendering) << "Context3D::" << __FUNCTION__
                               << "(target:" << glEnumToString(target)
                               << ", level:" << level
                               << ", internalformat:" << glEnumToString(internalformat)
                               << ", width:" << width
                               << ", height:" << height
                               << ", border:" << border
                               << ", pixels:" << pixels.toString()
                               << ")";
Pasi Keränen's avatar
Pasi Keränen committed

    if (target == TEXTURE_2D) {
        if (!m_currentTexture2D) {
            qCWarning(canvas3drendering) << "Context3D::" << __FUNCTION__
                                         << ":INVALID_OPERATION No current TEXTURE_2D bound";
            m_error = INVALID_OPERATION;
            return;
        } else if (!m_currentTexture2D->isAlive()) {
            qCWarning(canvas3drendering) << "Context3D::" << __FUNCTION__
                                         << ":INVALID_OPERATION Currently bound TEXTURE_2D is deleted";
            m_error = INVALID_OPERATION;
            return;
        }
    } else if (target == TEXTURE_CUBE_MAP) {
        if (!m_currentTextureCubeMap) {
            qCWarning(canvas3drendering) << "Context3D::" << __FUNCTION__
                                         << ":INVALID_OPERATION No current TEXTURE_CUBE_MAP bound";
            m_error = INVALID_OPERATION;
            return;
        } else if (!m_currentTextureCubeMap->isAlive()) {
            qCWarning(canvas3drendering) << "Context3D::" << __FUNCTION__
                                         << ":INVALID_OPERATION Currently bound TEXTURE_CUBE_MAP is deleted";
            m_error = INVALID_OPERATION;
            return;
        }
    }

    QV4::Scope scope(m_v4engine);
    QV4::Scoped<QV4::TypedArray> typedArray(scope,
                                            QJSValuePrivate::convertedToValue(m_v4engine, pixels));

    if (typedArray) {
        glCompressedTexImage2D(target,
                               level,
                               internalformat,
                               width, height, border,
                               typedArray->byteLength(),
                               (GLvoid *) typedArray->arrayData()->data());
        logAllGLErrors(__FUNCTION__);
Pasi Keränen's avatar
Pasi Keränen committed
    } else {
        qCWarning(canvas3drendering) << "Context3D::" << __FUNCTION__
                                     << ":INVALID_VALUE pixels must be TypedArray";
        m_error = INVALID_VALUE;
        return;
Pasi Keränen's avatar
Pasi Keränen committed
    }
}

/*!
 * \qmlmethod void Context3D::compressedTexSubImage2D(glEnums target, int level, int xoffset, int yoffset, int width, int height, glEnums format, TypedArray pixels)
 * Not supported, \c{Context3D.INVALID_OPERATION} is generated when called.
 * \a target, \a level, \a xoffset, \a yoffset, \a width, \a height, \a format and \a pixels are
 * ignored.
 */
/*!
 * \internal
 */
void CanvasContext::compressedTexSubImage2D(glEnums target, int level,
                                            int xoffset, int yoffset,
                                            int width, int height,
                                            glEnums format,
                                            QJSValue pixels)
Pasi Keränen's avatar
Pasi Keränen committed
{
    qCDebug(canvas3drendering) << "Context3D::" << __FUNCTION__
                               << "(target:" << glEnumToString(target)
                               << ", level:" << level
                               << ", xoffset:" << xoffset
                               << ", yoffset:" << yoffset
                               << ", width:" << width
                               << ", height:" << height
                               << ", format:" << glEnumToString(format)
                               << ", pixels:" << pixels.toString()
                               << ")";
Pasi Keränen's avatar
Pasi Keränen committed

    if (target == TEXTURE_2D) {
        if (!m_currentTexture2D) {
            qCWarning(canvas3drendering) << "Context3D::" << __FUNCTION__
                                         << ":INVALID_OPERATION No current TEXTURE_2D bound";
            m_error = INVALID_OPERATION;
            return;
        } else if (!m_currentTexture2D->isAlive()) {
            qCWarning(canvas3drendering) << "Context3D::" << __FUNCTION__
                                         << ":INVALID_OPERATION Currently bound TEXTURE_2D is deleted";
            m_error = INVALID_OPERATION;
            return;
        }
    } else if (target == TEXTURE_CUBE_MAP) {
        if (!m_currentTextureCubeMap) {
            qCWarning(canvas3drendering) << "Context3D::" << __FUNCTION__
                                         << ":INVALID_OPERATION No current TEXTURE_CUBE_MAP bound";
            m_error = INVALID_OPERATION;
            return;
        } else if (!m_currentTextureCubeMap->isAlive()) {
            qCWarning(canvas3drendering) << "Context3D::" << __FUNCTION__
                                         << ":INVALID_OPERATION Currently bound TEXTURE_CUBE_MAP is deleted";
            m_error = INVALID_OPERATION;
            return;
        }
    }

    QV4::Scope scope(m_v4engine);
    QV4::Scoped<QV4::TypedArray> typedArray(scope,
                                            QJSValuePrivate::convertedToValue(m_v4engine, pixels));

    if (typedArray) {
        glCompressedTexSubImage2D(target,
                                  level,
                                  xoffset, yoffset,
                                  width, height,
                                  format,
                                  typedArray->byteLength(),
                                  (GLvoid *) typedArray->arrayData()->data());
        logAllGLErrors(__FUNCTION__);
Pasi Keränen's avatar
Pasi Keränen committed
    } else {
        qCWarning(canvas3drendering) << "Context3D::" << __FUNCTION__
                                     << ":INVALID_VALUE pixels must be TypedArray";
        m_error = INVALID_VALUE;
        return;
Pasi Keränen's avatar
Pasi Keränen committed
    }
}

/*!
 * \qmlmethod void Context3D::copyTexImage2D(glEnums target, int level, glEnums internalformat, int x, int y, int width, int height, int border)
 * Copies pixels into currently bound 2D texture.
 * \a target specifies the target texture of the active texture unit. Must be \c{Context3D.TEXTURE_2D},
 * \c{Context3D.TEXTURE_CUBE_MAP_POSITIVE_X}, \c{Context3D.TEXTURE_CUBE_MAP_NEGATIVE_X},
 * \c{Context3D.TEXTURE_CUBE_MAP_POSITIVE_Y}, \c{Context3D.TEXTURE_CUBE_MAP_NEGATIVE_Y},
 * \c{Context3D.TEXTURE_CUBE_MAP_POSITIVE_Z}, or \c{Context3D.TEXTURE_CUBE_MAP_NEGATIVE_Z}.
 * \a level specifies the level of detail number. Level \c 0 is the base image level. Level \c n is
 * the \c{n}th mipmap reduction image.
 * \a internalformat specifies the internal format of the texture. Must be \c{Context3D.ALPHA},
 * \c{Context3D.LUMINANCE}, \c{Context3D.LUMINANCE_ALPHA}, \c{Context3D.RGB} or \c{Context3D.RGBA}.
 * \a x specifies the window coordinate of the left edge of the rectangular region of pixels to be
 * copied.
 * \a y specifies the window coordinate of the lower edge of the rectangular region of pixels to be
 * copied.
 * \a width specifies the width of the texture image. All implementations will support 2D texture
 * images that are at least 64 texels wide and cube-mapped texture images that are at least 16
 * texels wide.
 * \a height specifies the height of the texture image. All implementations will support 2D texture
 * images that are at least 64 texels high and cube-mapped texture images that are at least 16
 * texels high.
 * \a border must be \c{0}.
 */
/*!
 * \internal
 */
void CanvasContext::copyTexImage2D(glEnums target, int level, glEnums internalformat,
                                   int x, int y, int width, int height,
                                   int border)
{
    qCDebug(canvas3drendering) << "Context3D::" << __FUNCTION__
                               << "(target:" << glEnumToString(target)
                               << ", level:" << level
                               << ", internalformat:" << glEnumToString(internalformat)
                               << ", x:" << x
                               << ", y:" << y
                               << ", width:" << width
                               << ", height:" << height
                               << ", border:" << border
                               << ")";
    if (!m_currentTexture2D) {
        qCWarning(canvas3drendering) << "Context3D::" << __FUNCTION__
                                     << ":INVALID_OPERATION No current texture bound";
Pasi Keränen's avatar
Pasi Keränen committed
        m_error = INVALID_OPERATION;
    } else if (!m_currentTexture2D->isAlive()) {
        qCWarning(canvas3drendering) << "Context3D::" << __FUNCTION__
                                     << ":INVALID_OPERATION Currently bound texture is deleted";
Pasi Keränen's avatar
Pasi Keränen committed
        m_error = INVALID_OPERATION;
    } else {
        glCopyTexImage2D(target, level, internalformat, x, y, width, height, border);
        logAllGLErrors(__FUNCTION__);
Pasi Keränen's avatar
Pasi Keränen committed
    }
}

/*!
 * \qmlmethod void Context3D::copyTexSubImage2D(glEnums target, int level, int xoffset, int yoffset, int x, int y, int width, int height)
 * Copies to into a currently bound 2D texture subimage.
 * \a target specifies the target texture of the active texture unit. Must be
 * \c{Context3D.TEXTURE_2D},
Pasi Keränen's avatar
Pasi Keränen committed
 * \c{Context3D.TEXTURE_CUBE_MAP_POSITIVE_X}, \c{Context3D.TEXTURE_CUBE_MAP_NEGATIVE_X},
 * \c{Context3D.TEXTURE_CUBE_MAP_POSITIVE_Y}, \c{Context3D.TEXTURE_CUBE_MAP_NEGATIVE_Y},
 * \c{Context3D.TEXTURE_CUBE_MAP_POSITIVE_Z}, or \c{Context3D.TEXTURE_CUBE_MAP_NEGATIVE_Z}.
 * \a level specifies the level of detail number. Level \c 0 is the base image level. Level \c n is
 * the \c{n}th mipmap reduction image.
 * \a xoffset specifies the texel offset in the x direction within the texture array.
 * \a yoffset specifies the texel offset in the y direction within the texture array.
 * \a x specifies the window coordinate of the left edge of the rectangular region of pixels to be
 * copied.
 * \a y specifies the window coordinate of the lower edge of the rectangular region of pixels to be
 * copied.
 * \a width specifies the width of the texture subimage.
 * \a height specifies the height of the texture subimage.
 */
/*!
 * \internal
 */
void CanvasContext::copyTexSubImage2D(glEnums target, int level,
                                      int xoffset, int yoffset,
                                      int x, int y,
                                      int width, int height)
{
    qCDebug(canvas3drendering) << "Context3D::" << __FUNCTION__
                               << "(target:" << glEnumToString(target)
                               << ", level:" << level
                               << ", xoffset:" << xoffset
                               << ", yoffset:" << yoffset
                               << ", x:" << x
                               << ", y:" << y
                               << ", width:" << width
                               << ", height:" << height
                               << ")";
    if (!m_currentTexture2D) {
        qCWarning(canvas3drendering) << "Context3D::" << __FUNCTION__
                                     << ":INVALID_OPERATION No current texture bound";
Pasi Keränen's avatar
Pasi Keränen committed
        m_error = INVALID_OPERATION;
    } else if (!m_currentTexture2D->isAlive()) {
        qCWarning(canvas3drendering) << "Context3D::" << __FUNCTION__
                                     << ":INVALID_OPERATION Currently bound texture is deleted";
Pasi Keränen's avatar
Pasi Keränen committed
        m_error = INVALID_OPERATION;
    } else {
        copyTexSubImage2D(target, level, xoffset, yoffset, x, y, width, height);
        logAllGLErrors(__FUNCTION__);
Pasi Keränen's avatar
Pasi Keränen committed
    }
}

/*!
 * \qmlmethod void Context3D::texImage2D(glEnums target, int level, glEnums internalformat, int width, int height, int border, glEnums format, glEnums type, TypedArray pixels)
 * Specify a 2D texture image.
 * \a target specifies the target texture of the active texture unit. Must be one of:
 * \c{Context3D.TEXTURE_2D},
Pasi Keränen's avatar
Pasi Keränen committed
 * \c{Context3D.TEXTURE_CUBE_MAP_POSITIVE_X}, \c{Context3D.TEXTURE_CUBE_MAP_NEGATIVE_X},
 * \c{Context3D.TEXTURE_CUBE_MAP_POSITIVE_Y}, \c{Context3D.TEXTURE_CUBE_MAP_NEGATIVE_Y},
 * \c{Context3D.TEXTURE_CUBE_MAP_POSITIVE_Z}, or \c{Context3D.TEXTURE_CUBE_MAP_NEGATIVE_Z}.
 * \a level specifies the level of detail number. Level \c 0 is the base image level. Level \c n is
 * the \c{n}th mipmap reduction image.
 * \a internalformat specifies the internal format of the texture. Must be \c{Context3D.ALPHA},
 * \c{Context3D.LUMINANCE}, \c{Context3D.LUMINANCE_ALPHA}, \c{Context3D.RGB} or \c{Context3D.RGBA}.
 * \a width specifies the width of the texture image. All implementations will support 2D texture
 * images that are at least 64 texels wide and cube-mapped texture images that are at least 16
 * texels wide.
 * \a height specifies the height of the texture image. All implementations will support 2D texture
 * images that are at least 64 texels high and cube-mapped texture images that are at least 16
 * texels high.
 * \a border must be \c{0}.
 * \a format specifies the format of the texel data given in \a pixels, must match the value
 * of \a internalFormat.
 * \a type specifies the data type of the data given in \a pixels, must match the TypedArray type
 * of \a pixels. Must be \c{Context3D.UNSIGNED_BYTE}, \c{Context3D.UNSIGNED_SHORT_5_6_5},
 * \c{Context3D.UNSIGNED_SHORT_4_4_4_4} or \c{Context3D.UNSIGNED_SHORT_5_5_5_1}.
 * \a pixels specifies the TypedArray containing the image data. If pixels is \c{null}, a buffer
 * of sufficient size initialized to 0 is passed.
 */
/*!
 * \internal
 */
void CanvasContext::texImage2D(glEnums target, int level, glEnums internalformat,
                               int width, int height, int border,
                               glEnums format, glEnums type,
                               QJSValue pixels)
Pasi Keränen's avatar
Pasi Keränen committed
{
    qCDebug(canvas3drendering) << "Context3D::" << __FUNCTION__
                               << "(target:" << glEnumToString(target)
                               << ", level:" << level
                               << ", internalformat:" << glEnumToString(internalformat)
                               << ", width:" << width
                               << ", height:" << height
                               << ", border:" << border
                               << ", format:" << glEnumToString(format)
                               << ", type:" << glEnumToString(type)
                               << ", pixels:" << pixels.toString()
                               << ")";
    if (!m_currentTexture2D) {
        qCWarning(canvas3drendering) << "Context3D::"
                                     << __FUNCTION__
                                     << ":INVALID_OPERATION No current texture bound";
Pasi Keränen's avatar
Pasi Keränen committed
        m_error = INVALID_OPERATION;
        return;
    } else if (!m_currentTexture2D->isAlive()) {
        qCWarning(canvas3drendering) << "Context3D::" << __FUNCTION__
                                     << ":INVALID_OPERATION Currently bound texture is deleted";
Pasi Keränen's avatar
Pasi Keränen committed
        m_error = INVALID_OPERATION;
        return;
    }

    int bytesPerPixel = 0;
    uchar *srcData = 0;
    uchar *unpackedData = 0;

Pasi Keränen's avatar
Pasi Keränen committed
    bool deleteTempPixels = false;
    if (pixels.isNull()) {
Pasi Keränen's avatar
Pasi Keränen committed
        deleteTempPixels = true;
        int size = getSufficientSize(type, width, height);
        srcData = new uchar[size];
        memset(srcData, 0, size);
Pasi Keränen's avatar
Pasi Keränen committed
    }

    switch (type) {
    case UNSIGNED_BYTE: {
        switch (format) {
        case ALPHA:
            bytesPerPixel = 1;
            break;
        case RGB:
            bytesPerPixel = 3;
            break;
        case RGBA:
            bytesPerPixel = 4;
            break;
        case LUMINANCE:
            bytesPerPixel = 1;
            break;
        case LUMINANCE_ALPHA:
            bytesPerPixel = 2;
            break;
        default:
            break;
        }

        if (bytesPerPixel == 0) {
            m_error = INVALID_ENUM;
            qCWarning(canvas3drendering) << "Context3D::" << __FUNCTION__
                                         << ":INVALID_ENUM Invalid format supplied "
                                         << glEnumToString(format);
            return;
        }

        if (!srcData)
            srcData = getAsUint8ArrayRawPtr(pixels);

        if (!srcData) {
            qCWarning(canvas3drendering) << "Context3D::"
                                         << __FUNCTION__
                                         << ":INVALID_OPERATION Expected Uint8Array, received "
                                         << pixels.toString();
            m_error = INVALID_OPERATION;
            return;
        }

        unpackedData = unpackPixels(srcData, false, bytesPerPixel, width, height);
        glTexImage2D(target, level, internalformat, width, height,
                     border, format, type, unpackedData);
        logAllGLErrors(__FUNCTION__);
Pasi Keränen's avatar
Pasi Keränen committed
        break;
    case UNSIGNED_SHORT_4_4_4_4:
    case UNSIGNED_SHORT_5_6_5:
    case UNSIGNED_SHORT_5_5_5_1: {
        if (!srcData)
            srcData = getAsUint16ArrayRawPtr(pixels);

        if (!srcData) {
            qCWarning(canvas3drendering) << "Context3D::"
                                         << ":INVALID_OPERATION Expected Uint16Array, received "
                                         << pixels.toString();
Pasi Keränen's avatar
Pasi Keränen committed
            m_error = INVALID_OPERATION;
            return;
        }
        unpackedData = unpackPixels(srcData, false, 2, width, height);
        glTexImage2D(target, level, internalformat, width, height,
                     border, format, type, unpackedData);
        logAllGLErrors(__FUNCTION__);
Pasi Keränen's avatar
Pasi Keränen committed
        break;
    default:
        qCWarning(canvas3drendering) << "Context3D::"
                                     << __FUNCTION__
                                     << ":INVALID_ENUM Invalid type enum";
Pasi Keränen's avatar
Pasi Keränen committed
        m_error = INVALID_ENUM;
        break;
    }

    // Delete temp data
    if (unpackedData != srcData)
        delete unpackedData;

    if (deleteTempPixels)
        delete[] srcData;
Pasi Keränen's avatar
Pasi Keränen committed
}

/*!
 * \internal
 */
uchar *CanvasContext::getAsUint8ArrayRawPtr(QJSValue jsValue)
    QV4::Scope scope(m_v4engine);
    QV4::Scoped<QV4::TypedArray> typedArray(scope,
                                            QJSValuePrivate::convertedToValue(m_v4engine, jsValue));

    if (!typedArray)
    if (typedArray->arrayType() != QV4::Heap::TypedArray::UInt8Array)
        return 0;