void
WebGL2Context::GetUniformIndices(WebGLProgram* program,
                                 const dom::Sequence<nsString>& uniformNames,
                                 dom::Nullable< nsTArray<GLuint> >& retval)
{
    retval.SetNull();
    if (IsContextLost())
        return;

    if (!ValidateObject("getUniformIndices: program", program))
        return;

    if (!uniformNames.Length())
        return;

    GLuint progname = program->mGLName;
    size_t count = uniformNames.Length();
    nsTArray<GLuint>& arr = retval.SetValue();

    MakeContextCurrent();

    for (size_t n = 0; n < count; n++) {
        NS_LossyConvertUTF16toASCII name(uniformNames[n]);
        //        const GLchar* glname = name.get();
        const GLchar* glname = nullptr;
        name.BeginReading(glname);

        GLuint index = 0;
        gl->fGetUniformIndices(progname, 1, &glname, &index);
        arr.AppendElement(index);
    }
}
void
WebGL2Context::InvalidateSubFramebuffer(GLenum target, const dom::Sequence<GLenum>& attachments,
                                        GLint x, GLint y, GLsizei width, GLsizei height,
                                        ErrorResult& rv)
{
    if (IsContextLost())
        return;

    MakeContextCurrent();

    if (!ValidateFramebufferTarget(target, "framebufferRenderbuffer"))
        return;

    const WebGLFramebuffer* fb;
    bool isDefaultFB;
    switch (target) {
    case LOCAL_GL_FRAMEBUFFER:
    case LOCAL_GL_DRAW_FRAMEBUFFER:
        fb = mBoundDrawFramebuffer;
        isDefaultFB = gl->Screen()->IsDrawFramebufferDefault();
        break;

    case LOCAL_GL_READ_FRAMEBUFFER:
        fb = mBoundReadFramebuffer;
        isDefaultFB = gl->Screen()->IsReadFramebufferDefault();
        break;

    default:
        MOZ_CRASH("Bad target.");
    }

    for (size_t i = 0; i < attachments.Length(); i++) {
        if (!ValidateFramebufferAttachment(fb, attachments[i],
                                           "invalidateSubFramebuffer"))
        {
            return;
        }
    }

    // InvalidateFramebuffer is a hint to the driver. Should be OK to
    // skip calls if not supported, for example by OSX 10.9 GL
    // drivers.
    static bool invalidateFBSupported = gl->IsSupported(gl::GLFeature::invalidate_framebuffer);
    if (!invalidateFBSupported)
        return;

    if (!fb && !isDefaultFB) {
        dom::Sequence<GLenum> tmpAttachments;
        if (!TranslateDefaultAttachments(attachments, &tmpAttachments)) {
            rv.Throw(NS_ERROR_OUT_OF_MEMORY);
            return;
        }

        gl->fInvalidateSubFramebuffer(target, tmpAttachments.Length(), tmpAttachments.Elements(),
                                      x, y, width, height);
    } else {
        gl->fInvalidateSubFramebuffer(target, attachments.Length(), attachments.Elements(),
                                      x, y, width, height);
    }
}
void
WebGL2Context::InvalidateSubFramebuffer(GLenum target, const dom::Sequence<GLenum>& attachments,
                                        GLint x, GLint y, GLsizei width, GLsizei height)
{
    if (IsContextLost())
        return;

    MakeContextCurrent();

    if (!ValidateFramebufferTarget(target, "framebufferRenderbuffer"))
        return;

    const WebGLFramebuffer* fb;
    bool isDefaultFB;
    switch (target) {
    case LOCAL_GL_FRAMEBUFFER:
    case LOCAL_GL_DRAW_FRAMEBUFFER:
        fb = mBoundDrawFramebuffer;
        isDefaultFB = gl->Screen()->IsDrawFramebufferDefault();
        break;

    case LOCAL_GL_READ_FRAMEBUFFER:
        fb = mBoundReadFramebuffer;
        isDefaultFB = gl->Screen()->IsReadFramebufferDefault();
        break;

    default:
        MOZ_CRASH("Bad target.");
    }

    for (size_t i = 0; i < attachments.Length(); i++) {
        if (!ValidateFramebufferAttachment(fb, attachments[i],
                                           "invalidateSubFramebuffer"))
        {
            return;
        }
    }

    if (!fb && !isDefaultFB) {
        dom::Sequence<GLenum> tmpAttachments;
        TranslateDefaultAttachments(attachments, &tmpAttachments);
        gl->fInvalidateSubFramebuffer(target, tmpAttachments.Length(), tmpAttachments.Elements(),
                                      x, y, width, height);
    } else {
        gl->fInvalidateSubFramebuffer(target, attachments.Length(), attachments.Elements(),
                                      x, y, width, height);
    }
}
// Map attachments intended for the default buffer, to attachments for a non-
// default buffer.
static bool
TranslateDefaultAttachments(const dom::Sequence<GLenum>& in, dom::Sequence<GLenum>* out)
{
    for (size_t i = 0; i < in.Length(); i++) {
        switch (in[i]) {
            case LOCAL_GL_COLOR:
                if (!out->AppendElement(LOCAL_GL_COLOR_ATTACHMENT0, fallible)) {
                    return false;
                }
                break;

            case LOCAL_GL_DEPTH:
                if (!out->AppendElement(LOCAL_GL_DEPTH_ATTACHMENT, fallible)) {
                    return false;
                }
                break;

            case LOCAL_GL_STENCIL:
                if (!out->AppendElement(LOCAL_GL_STENCIL_ATTACHMENT, fallible)) {
                    return false;
                }
                break;
        }
    }

    return true;
}
void
WebGL2Context::GetActiveUniforms(WebGLProgram* program,
                                 const dom::Sequence<GLuint>& uniformIndices,
                                 GLenum pname,
                                 dom::Nullable< nsTArray<GLint> >& retval)
{
    retval.SetNull();
    if (IsContextLost())
        return;

    if (pname == LOCAL_GL_UNIFORM_NAME_LENGTH) {
        ErrorInvalidEnumInfo("getActiveUniforms: pname", pname);
        return;
    }

    if (!ValidateObject("getActiveUniforms: program", program))
        return;

    size_t count = uniformIndices.Length();
    if (!count)
        return;

    GLuint progname = program->mGLName;
    nsTArray<GLint>& arr = retval.SetValue();
    arr.SetLength(count);

    MakeContextCurrent();
    gl->fGetActiveUniformsiv(progname, count, uniformIndices.Elements(), pname,
                             arr.Elements());
}
void WebGL2Context::GetUniformIndices(
    const WebGLProgram& program, const dom::Sequence<nsString>& uniformNames,
    dom::Nullable<nsTArray<GLuint> >& retval) {
  const FuncScope funcScope(*this, "getUniformIndices");
  retval.SetNull();
  if (IsContextLost()) return;

  if (!ValidateObject("program", program)) return;

  if (!uniformNames.Length()) return;

  program.GetUniformIndices(uniformNames, retval);
}
void
WebGL2Context::ClearBufferfv(GLenum buffer, GLint drawbuffer, const dom::Sequence<GLfloat>& value)
{
    if (IsContextLost()) {
        return;
    }

    if (!ValidateClearBuffer("clearBufferfv", buffer, drawbuffer, value.Length())) {
        return;
    }

    ClearBufferfv_base(buffer, drawbuffer, value.Elements());
}
void
WebGLProgram::TransformFeedbackVaryings(const dom::Sequence<nsString>& varyings,
                                        GLenum bufferMode)
{
    const char funcName[] = "transformFeedbackVaryings";

    const auto& gl = mContext->gl;
    gl->MakeCurrent();

    switch (bufferMode) {
    case LOCAL_GL_INTERLEAVED_ATTRIBS:
        break;

    case LOCAL_GL_SEPARATE_ATTRIBS:
        {
            GLuint maxAttribs = 0;
            gl->GetUIntegerv(LOCAL_GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS,
                             &maxAttribs);
            if (varyings.Length() > maxAttribs) {
                mContext->ErrorInvalidValue("%s: Length of `varyings` exceeds %s.",
                                            funcName,
                                            "TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS");
                return;
            }
        }
        break;

    default:
        mContext->ErrorInvalidEnum("%s: Bad `bufferMode`: 0x%04x.", funcName, bufferMode);
        return;
    }

    ////

    mNextLink_TransformFeedbackVaryings.assign(varyings.Elements(),
                                               varyings.Elements() + varyings.Length());
    mNextLink_TransformFeedbackBufferMode = bufferMode;
}
void
WebGLProgram::GetUniformIndices(const dom::Sequence<nsString>& uniformNames,
                                dom::Nullable< nsTArray<GLuint> >& retval) const
{
    const char funcName[] = "getUniformIndices";
    if (!IsLinked()) {
        mContext->ErrorInvalidOperation("%s: `program` must be linked.", funcName);
        return;
    }

    size_t count = uniformNames.Length();
    nsTArray<GLuint>& arr = retval.SetValue();

    gl::GLContext* gl = mContext->GL();
    gl->MakeCurrent();

    for (size_t i = 0; i < count; i++) {
        const NS_LossyConvertUTF16toASCII userName(uniformNames[i]);

        nsDependentCString baseUserName;
        bool isArray;
        size_t arrayIndex;
        if (!ParseName(userName, &baseUserName, &isArray, &arrayIndex)) {
            arr.AppendElement(LOCAL_GL_INVALID_INDEX);
            continue;
        }

        webgl::UniformInfo* info;
        if (!LinkInfo()->FindUniform(baseUserName, &info)) {
            arr.AppendElement(LOCAL_GL_INVALID_INDEX);
            continue;
        }

        nsAutoCString mappedName(info->mActiveInfo->mBaseMappedName);
        if (isArray) {
            mappedName.AppendLiteral("[");
            mappedName.AppendInt(uint32_t(arrayIndex));
            mappedName.AppendLiteral("]");
        }

        const GLchar* mappedNameBytes = mappedName.BeginReading();

        GLuint index = 0;
        gl->fGetUniformIndices(mGLName, 1, &mappedNameBytes, &index);
        arr.AppendElement(index);
    }
}
// Map attachments intended for the default buffer, to attachments for a non-
// default buffer.
static void
TranslateDefaultAttachments(const dom::Sequence<GLenum>& in, dom::Sequence<GLenum>* out)
{
    for (size_t i = 0; i < in.Length(); i++) {
        switch (in[i]) {
            case LOCAL_GL_COLOR:
                out->AppendElement(LOCAL_GL_COLOR_ATTACHMENT0);
                break;
            case LOCAL_GL_DEPTH:
                out->AppendElement(LOCAL_GL_DEPTH_ATTACHMENT);
                break;
            case LOCAL_GL_STENCIL:
                out->AppendElement(LOCAL_GL_STENCIL_ATTACHMENT);
                break;
        }
    }
}
void
WebGL2Context::GetUniformIndices(WebGLProgram* program,
                                 const dom::Sequence<nsString>& uniformNames,
                                 dom::Nullable< nsTArray<GLuint> >& retval)
{
    retval.SetNull();
    if (IsContextLost())
        return;

    if (!ValidateObject("getUniformIndices: program", program))
        return;

    if (!uniformNames.Length())
        return;

    program->GetUniformIndices(uniformNames, retval);
}
void
WebGL2Context::SamplerParameterfv(WebGLSampler* sampler, GLenum pname, const dom::Sequence<GLfloat>& param)
{
    if (IsContextLost())
        return;

    if (!sampler || sampler->IsDeleted())
        return ErrorInvalidOperation("samplerParameterfv: invalid sampler");

    if (param.Length() < 1)
        return /* TODO(djg): Error message */;

    /* TODO(djg): All of these calls in ES3 only take 1 param */
    if (!ValidateSamplerParameterParams(pname, WebGLIntOrFloat(param[0]), "samplerParameterfv"))
        return;

    WebGLContextUnchecked::SamplerParameterfv(sampler, pname, param.Elements());
}
void
WebGLContext::DrawBuffers(const dom::Sequence<GLenum>& buffers)
{
    const char funcName[] = "drawBuffers";
    if (IsContextLost())
        return;

    if (mBoundDrawFramebuffer) {
        mBoundDrawFramebuffer->DrawBuffers(funcName, buffers);
        return;
    }

    // GLES 3.0.4 p186:
    // "If the GL is bound to the default framebuffer, then `n` must be 1 and the
    //  constant must be BACK or NONE. [...] If DrawBuffers is supplied with a
    //  constant other than BACK and NONE, or with a value of `n` other than 1, the
    //  error INVALID_OPERATION is generated."
    if (buffers.Length() != 1) {
        ErrorInvalidOperation("%s: For the default framebuffer, `buffers` must have a"
                              " length of 1.",
                              funcName);
        return;
    }

    switch (buffers[0]) {
    case LOCAL_GL_NONE:
    case LOCAL_GL_BACK:
        break;

    default:
        ErrorInvalidOperation("%s: For the default framebuffer, `buffers[0]` must be"
                              " BACK or NONE.",
                              funcName);
        return;
    }

    mDefaultFB_DrawBuffer0 = buffers[0];
    gl->Screen()->SetDrawBuffer(buffers[0]);
}
Exemple #14
0
void
WebGLProgram::TransformFeedbackVaryings(const dom::Sequence<nsString>& varyings,
                                        GLenum bufferMode)
{
    if (bufferMode != LOCAL_GL_INTERLEAVED_ATTRIBS &&
        bufferMode != LOCAL_GL_SEPARATE_ATTRIBS)
    {
        mContext->ErrorInvalidEnum("transformFeedbackVaryings: `bufferMode` %s is "
                                   "invalid. Must be one of gl.INTERLEAVED_ATTRIBS or "
                                   "gl.SEPARATE_ATTRIBS.",
                                   mContext->EnumName(bufferMode));
        return;
    }

    size_t varyingsCount = varyings.Length();
    if (bufferMode == LOCAL_GL_SEPARATE_ATTRIBS &&
        varyingsCount >= mContext->mGLMaxTransformFeedbackSeparateAttribs)
    {
        mContext->ErrorInvalidValue("transformFeedbackVaryings: Number of `varyings` exc"
                                    "eeds gl.MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS.");
        return;
    }

    std::vector<nsCString> asciiVaryings;
    for (size_t i = 0; i < varyingsCount; i++) {
        if (!ValidateGLSLVariableName(varyings[i], mContext, "transformFeedbackVaryings"))
            return;

        NS_LossyConvertUTF16toASCII asciiName(varyings[i]);
        asciiVaryings.push_back(asciiName);
    }

    // All validated. Translate the strings and store them until
    // program linking.
    mTransformFeedbackBufferMode = bufferMode;
    mTransformFeedbackVaryings.swap(asciiVaryings);
}
void
WebGL2Context::TransformFeedbackVaryings(WebGLProgram* program,
                                         const dom::Sequence<nsString>& varyings,
                                         GLenum bufferMode)
{
    if (IsContextLost())
        return;

    if (!ValidateObject("transformFeedbackVaryings: program", program))
        return;

    GLsizei count = varyings.Length();
    GLchar** tmpVaryings = (GLchar**) nsMemory::Alloc(count * sizeof(GLchar*));

    for (GLsizei n = 0; n < count; n++) {
        tmpVaryings[n] = (GLchar*) ToNewCString(varyings[n]);
    }

    GLuint progname = program->GLName();
    MakeContextCurrent();
    gl->fTransformFeedbackVaryings(progname, count, tmpVaryings, bufferMode);

    NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(count, tmpVaryings);
}
void
WebGLContext::DrawBuffers(const dom::Sequence<GLenum>& buffers)
{
    const size_t buffersLength = buffers.Length();

    if (buffersLength == 0) {
        return ErrorInvalidValue("drawBuffers: invalid <buffers> (buffers must not be empty)");
    }

    if (mBoundFramebuffer == 0)
    {
        // OK: we are rendering in the default framebuffer

        /* EXT_draw_buffers :
         If the GL is bound to the default framebuffer, then <buffersLength> must be 1
         and the constant must be BACK or NONE. When draw buffer zero is
         BACK, color values are written into the sole buffer for single-
         buffered contexts, or into the back buffer for double-buffered
         contexts. If DrawBuffersEXT is supplied with a constant other than
         BACK and NONE, the error INVALID_OPERATION is generated.
         */
        if (buffersLength != 1) {
            return ErrorInvalidValue("drawBuffers: invalid <buffers> (main framebuffer: buffers.length must be 1)");
        }

        MakeContextCurrent();

        if (buffers[0] == LOCAL_GL_NONE) {
            const GLenum drawBuffersCommand = LOCAL_GL_NONE;
            gl->fDrawBuffers(1, &drawBuffersCommand);
            return;
        }
        else if (buffers[0] == LOCAL_GL_BACK) {
            const GLenum drawBuffersCommand = LOCAL_GL_COLOR_ATTACHMENT0;
            gl->fDrawBuffers(1, &drawBuffersCommand);
            return;
        }
        return ErrorInvalidOperation("drawBuffers: invalid operation (main framebuffer: buffers[0] must be GL_NONE or GL_BACK)");
    }

    // OK: we are rendering in a framebuffer object

    if (buffersLength > size_t(mGLMaxDrawBuffers)) {
        /* EXT_draw_buffers :
         The maximum number of draw buffers is implementation-dependent. The
         number of draw buffers supported can be queried by calling
         GetIntegerv with the symbolic constant MAX_DRAW_BUFFERS_EXT. An
         INVALID_VALUE error is generated if <buffersLength> is greater than
         MAX_DRAW_BUFFERS_EXT.
         */
        return ErrorInvalidValue("drawBuffers: invalid <buffers> (buffers.length > GL_MAX_DRAW_BUFFERS)");
    }

    for (uint32_t i = 0; i < buffersLength; i++)
    {
        /* EXT_draw_buffers :
         If the GL is bound to a draw framebuffer object, the <i>th buffer listed
         in <bufs> must be COLOR_ATTACHMENT<i>_EXT or NONE. Specifying a
         buffer out of order, BACK, or COLOR_ATTACHMENT<m>_EXT where <m> is
         greater than or equal to the value of MAX_COLOR_ATTACHMENTS_EXT,
         will generate the error INVALID_OPERATION.
         */
        /* WEBGL_draw_buffers :
         The value of the MAX_COLOR_ATTACHMENTS_WEBGL parameter must be greater than or equal to that of the MAX_DRAW_BUFFERS_WEBGL parameter.
         */
        if (buffers[i] != LOCAL_GL_NONE &&
            buffers[i] != GLenum(LOCAL_GL_COLOR_ATTACHMENT0 + i)) {
            return ErrorInvalidOperation("drawBuffers: invalid operation (buffers[i] must be GL_NONE or GL_COLOR_ATTACHMENTi)");
        }
    }

    MakeContextCurrent();

    gl->fDrawBuffers(buffersLength, buffers.Elements());
}
void
WebGL2Context::GetActiveUniforms(JSContext* cx, const WebGLProgram& program,
                                 const dom::Sequence<GLuint>& uniformIndices,
                                 GLenum pname, JS::MutableHandleValue retval)
{
    const char funcName[] = "getActiveUniforms";
    retval.setNull();
    if (IsContextLost())
        return;

    if (!ValidateUniformEnum(this, pname, funcName))
        return;

    if (!ValidateObject("getActiveUniforms: program", program))
        return;

    const auto& numActiveUniforms = program.LinkInfo()->uniforms.size();
    for (const auto& curIndex : uniformIndices) {
        if (curIndex >= numActiveUniforms) {
            ErrorInvalidValue("%s: Too-large active uniform index queried.", funcName);
            return;
        }
    }

    const auto& count = uniformIndices.Length();

    JS::Rooted<JSObject*> array(cx, JS_NewArrayObject(cx, count));
    UniquePtr<GLint[]> samples(new GLint[count]);
    if (!array || !samples) {
        ErrorOutOfMemory("%s: Failed to allocate buffers.", funcName);
        return;
    }
    retval.setObject(*array);

    MakeContextCurrent();
    gl->fGetActiveUniformsiv(program.mGLName, count, uniformIndices.Elements(), pname,
                             samples.get());

    switch (pname) {
    case LOCAL_GL_UNIFORM_TYPE:
    case LOCAL_GL_UNIFORM_SIZE:
    case LOCAL_GL_UNIFORM_BLOCK_INDEX:
    case LOCAL_GL_UNIFORM_OFFSET:
    case LOCAL_GL_UNIFORM_ARRAY_STRIDE:
    case LOCAL_GL_UNIFORM_MATRIX_STRIDE:
        for (size_t i = 0; i < count; ++i) {
            JS::RootedValue value(cx);
            value = JS::Int32Value(samples[i]);
            if (!JS_DefineElement(cx, array, i, value, JSPROP_ENUMERATE))
                return;
        }
        break;
    case LOCAL_GL_UNIFORM_IS_ROW_MAJOR:
        for (size_t i = 0; i < count; ++i) {
            JS::RootedValue value(cx);
            value = JS::BooleanValue(samples[i]);
            if (!JS_DefineElement(cx, array, i, value, JSPROP_ENUMERATE))
                return;
        }
        break;

    default:
        MOZ_CRASH("Invalid pname");
    }
}
void
WebGLContext::DrawBuffers(const dom::Sequence<GLenum>& buffers)
{
    const char funcName[] = "drawBuffers";
    if (IsContextLost())
        return;

    if (!mBoundDrawFramebuffer) {
        // GLES 3.0.4 p186:
        // "If the GL is bound to the default framebuffer, then `n` must be 1 and the
        //  constant must be BACK or NONE. [...] If DrawBuffers is supplied with a
        //  constant other than BACK and NONE, or with a value of `n` other than 1, the
        //  error INVALID_OPERATION is generated."
        if (buffers.Length() != 1) {
            ErrorInvalidOperation("%s: For the default framebuffer, `buffers` must have a"
                                  " length of 1.",
                                  funcName);
            return;
        }

        switch (buffers[0]) {
        case LOCAL_GL_NONE:
        case LOCAL_GL_BACK:
            break;

        default:
            ErrorInvalidOperation("%s: For the default framebuffer, `buffers[0]` must be"
                                  " BACK or NONE.",
                                  funcName);
            return;
        }

        mDefaultFB_DrawBuffer0 = buffers[0];
        gl->Screen()->SetDrawBuffer(buffers[0]);
        return;
    }

    // Framebuffer object (not default framebuffer)

    if (buffers.Length() > mImplMaxDrawBuffers) {
        // "An INVALID_VALUE error is generated if `n` is greater than MAX_DRAW_BUFFERS."
        ErrorInvalidValue("%s: `buffers` must have a length <= MAX_DRAW_BUFFERS.",
                          funcName);
        return;
    }

    for (size_t i = 0; i < buffers.Length(); i++) {
        // "If the GL is bound to a draw framebuffer object, the `i`th buffer listed in
        //  bufs must be COLOR_ATTACHMENTi or NONE. Specifying a buffer out of order,
        //  BACK, or COLOR_ATTACHMENTm where `m` is greater than or equal to the value of
        // MAX_COLOR_ATTACHMENTS, will generate the error INVALID_OPERATION.

        // WEBGL_draw_buffers:
        // "The value of the MAX_COLOR_ATTACHMENTS_WEBGL parameter must be greater than or
        //  equal to that of the MAX_DRAW_BUFFERS_WEBGL parameter."
        // This means that if buffers.Length() isn't larger than MaxDrawBuffers, it won't
        // be larger than MaxColorAttachments.
        if (buffers[i] != LOCAL_GL_NONE &&
            buffers[i] != LOCAL_GL_COLOR_ATTACHMENT0 + i)
        {
            ErrorInvalidOperation("%s: `buffers[i]` must be NONE or COLOR_ATTACHMENTi.",
                                  funcName);
            return;
        }
    }

    MakeContextCurrent();

    const GLenum* ptr = nullptr;
    if (buffers.Length()) {
        ptr = buffers.Elements();
    }

    gl->fDrawBuffers(buffers.Length(), ptr);

    const auto end = ptr + buffers.Length();
    mBoundDrawFramebuffer->mDrawBuffers.assign(ptr, end);
}
void
WebGL2Context::VertexAttribI4uiv(GLuint index, const dom::Sequence<GLuint>& v)
{
  VertexAttribI4uiv(index, v.Length(), v.Elements());
}
void
WebGL2Context::GetActiveUniforms(JSContext* cx,
                                 WebGLProgram* program,
                                 const dom::Sequence<GLuint>& uniformIndices,
                                 GLenum pname,
                                 JS::MutableHandleValue retval)
{
    retval.set(JS::NullValue());
    if (IsContextLost())
        return;

    if (!ValidateUniformEnum(this, pname, "getActiveUniforms"))
        return;

    if (!ValidateObject("getActiveUniforms: program", program))
        return;

    size_t count = uniformIndices.Length();
    if (!count)
        return;

    GLuint progname = program->mGLName;
    Vector<GLint> samples;
    if (!samples.resize(count)) {
        return;
    }

    MakeContextCurrent();
    gl->fGetActiveUniformsiv(progname, count, uniformIndices.Elements(), pname,
                             samples.begin());

    JS::Rooted<JSObject*> array(cx, JS_NewArrayObject(cx, count));
    if (!array) {
        return;
    }

    switch (pname) {
    case LOCAL_GL_UNIFORM_TYPE:
    case LOCAL_GL_UNIFORM_SIZE:
    case LOCAL_GL_UNIFORM_BLOCK_INDEX:
    case LOCAL_GL_UNIFORM_OFFSET:
    case LOCAL_GL_UNIFORM_ARRAY_STRIDE:
    case LOCAL_GL_UNIFORM_MATRIX_STRIDE:
        for (uint32_t i = 0; i < count; ++i) {
            JS::RootedValue value(cx);
            value = JS::Int32Value(samples[i]);
            if (!JS_DefineElement(cx, array, i, value, JSPROP_ENUMERATE)) {
                return;
            }
        }
        break;
    case LOCAL_GL_UNIFORM_IS_ROW_MAJOR:
        for (uint32_t i = 0; i < count; ++i) {
            JS::RootedValue value(cx);
            value = JS::BooleanValue(samples[i]);
            if (!JS_DefineElement(cx, array, i, value, JSPROP_ENUMERATE)) {
                return;
            }
        }
        break;

    default:
        return;
    }

    retval.setObjectOrNull(array);
}