void
WebGL2Context::BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
                               GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
                               GLbitfield mask, GLenum filter)
{
    if (IsContextLost())
        return;

    const GLbitfield validBits = LOCAL_GL_COLOR_BUFFER_BIT |
                                 LOCAL_GL_DEPTH_BUFFER_BIT |
                                 LOCAL_GL_STENCIL_BUFFER_BIT;
    if ((mask | validBits) != validBits) {
        ErrorInvalidValue("blitFramebuffer: Invalid bit set in mask.");
        return;
    }

    switch (filter) {
    case LOCAL_GL_NEAREST:
    case LOCAL_GL_LINEAR:
        break;
    default:
        ErrorInvalidEnumInfo("blitFramebuffer: Bad `filter`:", filter);
        return;
    }

    const GLbitfield depthAndStencilBits = LOCAL_GL_DEPTH_BUFFER_BIT |
                                           LOCAL_GL_STENCIL_BUFFER_BIT;
    if (mask & depthAndStencilBits &&
        filter != LOCAL_GL_NEAREST)
    {
        ErrorInvalidOperation("blitFramebuffer: DEPTH_BUFFER_BIT and"
                              " STENCIL_BUFFER_BIT can only be used with"
                              " NEAREST filtering.");
        return;
    }

    if (mBoundReadFramebuffer == mBoundDrawFramebuffer) {
        // TODO: It's actually more complicated than this. We need to check that
        // the underlying buffers are not the same, not the framebuffers
        // themselves.
        ErrorInvalidOperation("blitFramebuffer: Source and destination must"
                              " differ.");
        return;
    }

    GLsizei srcSamples;
    const webgl::FormatInfo* srcColorFormat = nullptr;
    const webgl::FormatInfo* srcDepthFormat = nullptr;
    const webgl::FormatInfo* srcStencilFormat = nullptr;

    if (mBoundReadFramebuffer) {
        if (!mBoundReadFramebuffer->ValidateAndInitAttachments("blitFramebuffer's READ_FRAMEBUFFER"))
            return;

        if (!GetFBInfoForBlit(mBoundReadFramebuffer, "READ_FRAMEBUFFER", &srcSamples,
                              &srcColorFormat, &srcDepthFormat, &srcStencilFormat))
        {
            return;
        }
    } else {
        srcSamples = 0; // Always 0.

        GetBackbufferFormats(mOptions, &srcColorFormat, &srcDepthFormat,
                             &srcStencilFormat);
    }

    GLsizei dstSamples;
    const webgl::FormatInfo* dstColorFormat = nullptr;
    const webgl::FormatInfo* dstDepthFormat = nullptr;
    const webgl::FormatInfo* dstStencilFormat = nullptr;

    if (mBoundDrawFramebuffer) {
        if (!mBoundDrawFramebuffer->ValidateAndInitAttachments("blitFramebuffer's DRAW_FRAMEBUFFER"))
            return;

        if (!GetFBInfoForBlit(mBoundDrawFramebuffer, "DRAW_FRAMEBUFFER", &dstSamples,
                              &dstColorFormat, &dstDepthFormat, &dstStencilFormat))
        {
            return;
        }
    } else {
        dstSamples = gl->Screen()->Samples();

        GetBackbufferFormats(mOptions, &dstColorFormat, &dstDepthFormat,
                             &dstStencilFormat);
    }

    if (mask & LOCAL_GL_COLOR_BUFFER_BIT) {
        const auto fnSignlessType = [](const webgl::FormatInfo* format)
                                    -> webgl::ComponentType
        {
            if (!format)
                return webgl::ComponentType::None;

            switch (format->componentType) {
            case webgl::ComponentType::UInt:
                return webgl::ComponentType::Int;

            case webgl::ComponentType::NormUInt:
                return webgl::ComponentType::NormInt;

            default:
                return format->componentType;
            }
        };

        const auto srcType = fnSignlessType(srcColorFormat);
        const auto dstType = fnSignlessType(dstColorFormat);

        if (srcType != dstType) {
            ErrorInvalidOperation("blitFramebuffer: Color buffer format component type"
                                  " mismatch.");
            return;
        }

        const bool srcIsInt = (srcType == webgl::ComponentType::Int);
        if (srcIsInt && filter != LOCAL_GL_NEAREST) {
            ErrorInvalidOperation("blitFramebuffer: Integer read buffers can only"
                                  " be filtered with NEAREST.");
            return;
        }
    }

    /* GLES 3.0.4, p199:
     *   Calling BlitFramebuffer will result in an INVALID_OPERATION error if
     *   mask includes DEPTH_BUFFER_BIT or STENCIL_BUFFER_BIT, and the source
     *   and destination depth and stencil buffer formats do not match.
     *
     * jgilbert: The wording is such that if only DEPTH_BUFFER_BIT is specified,
     * the stencil formats must match. This seems wrong. It could be a spec bug,
     * or I could be missing an interaction in one of the earlier paragraphs.
     */
    if (mask & LOCAL_GL_DEPTH_BUFFER_BIT &&
        dstDepthFormat != srcDepthFormat)
    {
        ErrorInvalidOperation("blitFramebuffer: Depth buffer formats must match"
                              " if selected.");
        return;
    }

    if (mask & LOCAL_GL_STENCIL_BUFFER_BIT &&
        dstStencilFormat != srcStencilFormat)
    {
        ErrorInvalidOperation("blitFramebuffer: Stencil buffer formats must"
                              " match if selected.");
        return;
    }

    if (dstSamples != 0) {
        ErrorInvalidOperation("blitFramebuffer: DRAW_FRAMEBUFFER may not have"
                              " multiple samples.");
        return;
    }

    if (srcSamples != 0) {
        if (mask & LOCAL_GL_COLOR_BUFFER_BIT &&
            dstColorFormat != srcColorFormat)
        {
            ErrorInvalidOperation("blitFramebuffer: Color buffer formats must"
                                  " match if selected, when reading from a"
                                  " multisampled source.");
            return;
        }

        if (dstX0 != srcX0 ||
            dstX1 != srcX1 ||
            dstY0 != srcY0 ||
            dstY1 != srcY1)
        {
            ErrorInvalidOperation("blitFramebuffer: If the source is"
                                  " multisampled, then the source and dest"
                                  " regions must match exactly.");
            return;
        }
    }

    MakeContextCurrent();
    gl->fBlitFramebuffer(srcX0, srcY0, srcX1, srcY1,
                         dstX0, dstY0, dstX1, dstY1,
                         mask, filter);
}
void
WebGL2Context::BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
                               GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
                               GLbitfield mask, GLenum filter)
{
    const GLbitfield validBits = LOCAL_GL_COLOR_BUFFER_BIT |
                                 LOCAL_GL_DEPTH_BUFFER_BIT |
                                 LOCAL_GL_STENCIL_BUFFER_BIT;
    if ((mask | validBits) != validBits) {
        ErrorInvalidValue("blitFramebuffer: Invalid bit set in mask.");
        return;
    }

    switch (filter) {
    case LOCAL_GL_NEAREST:
    case LOCAL_GL_LINEAR:
        break;
    default:
        ErrorInvalidEnumInfo("blitFramebuffer: Bad `filter`:", filter);
        return;
    }

    const GLbitfield depthAndStencilBits = LOCAL_GL_DEPTH_BUFFER_BIT |
                                           LOCAL_GL_STENCIL_BUFFER_BIT;
    if (mask & depthAndStencilBits &&
        filter != LOCAL_GL_NEAREST)
    {
        ErrorInvalidOperation("blitFramebuffer: DEPTH_BUFFER_BIT and"
                              " STENCIL_BUFFER_BIT can only be used with"
                              " NEAREST filtering.");
        return;
    }

    if (mBoundReadFramebuffer == mBoundDrawFramebuffer) {
        // TODO: It's actually more complicated than this. We need to check that
        // the underlying buffers are not the same, not the framebuffers
        // themselves.
        ErrorInvalidOperation("blitFramebuffer: Source and destination must"
                              " differ.");
        return;
    }

    GLsizei srcSamples;
    GLenum srcColorFormat;
    GLenum srcDepthFormat;
    GLenum srcStencilFormat;

    if (mBoundReadFramebuffer) {
        if (!GetFBInfoForBlit(mBoundReadFramebuffer, this, "READ_FRAMEBUFFER",
                              &srcSamples, &srcColorFormat, &srcDepthFormat,
                              &srcStencilFormat))
        {
            return;
        }
    } else {
        srcSamples = 1; // Always 1.

        // TODO: Don't hardcode these.
        srcColorFormat = mOptions.alpha ? LOCAL_GL_RGBA8 : LOCAL_GL_RGB8;

        if (mOptions.depth && mOptions.stencil) {
            srcDepthFormat = LOCAL_GL_DEPTH24_STENCIL8;
            srcStencilFormat = srcDepthFormat;
        } else {
            if (mOptions.depth) {
                srcDepthFormat = LOCAL_GL_DEPTH_COMPONENT16;
            }
            if (mOptions.stencil) {
                srcStencilFormat = LOCAL_GL_STENCIL_INDEX8;
            }
        }
    }

    GLsizei dstSamples;
    GLenum dstColorFormat;
    GLenum dstDepthFormat;
    GLenum dstStencilFormat;

    if (mBoundDrawFramebuffer) {
        if (!GetFBInfoForBlit(mBoundDrawFramebuffer, this, "DRAW_FRAMEBUFFER",
                              &dstSamples, &dstColorFormat, &dstDepthFormat,
                              &dstStencilFormat))
        {
            return;
        }
    } else {
        dstSamples = gl->Screen()->Samples();

        // TODO: Don't hardcode these.
        dstColorFormat = mOptions.alpha ? LOCAL_GL_RGBA8 : LOCAL_GL_RGB8;

        if (mOptions.depth && mOptions.stencil) {
            dstDepthFormat = LOCAL_GL_DEPTH24_STENCIL8;
            dstStencilFormat = dstDepthFormat;
        } else {
            if (mOptions.depth) {
                dstDepthFormat = LOCAL_GL_DEPTH_COMPONENT16;
            }
            if (mOptions.stencil) {
                dstStencilFormat = LOCAL_GL_STENCIL_INDEX8;
            }
        }
    }


    if (mask & LOCAL_GL_COLOR_BUFFER_BIT) {
        const GLenum srcColorType = srcColorFormat ? ValueTypeForFormat(srcColorFormat)
                                                   : 0;
        const GLenum dstColorType = dstColorFormat ? ValueTypeForFormat(dstColorFormat)
                                                   : 0;
        if (dstColorType != srcColorType) {
            ErrorInvalidOperation("blitFramebuffer: Color buffer value type"
                                  " mismatch.");
            return;
        }

        const bool srcIsInt = srcColorType == LOCAL_GL_INT ||
                              srcColorType == LOCAL_GL_UNSIGNED_INT;
        if (srcIsInt && filter != LOCAL_GL_NEAREST) {
            ErrorInvalidOperation("blitFramebuffer: Integer read buffers can only"
                                  " be filtered with NEAREST.");
            return;
        }
    }

    /* GLES 3.0.4, p199:
     *   Calling BlitFramebuffer will result in an INVALID_OPERATION error if
     *   mask includes DEPTH_BUFFER_BIT or STENCIL_BUFFER_BIT, and the source
     *   and destination depth and stencil buffer formats do not match.
     *
     * jgilbert: The wording is such that if only DEPTH_BUFFER_BIT is specified,
     * the stencil formats must match. This seems wrong. It could be a spec bug,
     * or I could be missing an interaction in one of the earlier paragraphs.
     */
    if (mask & LOCAL_GL_DEPTH_BUFFER_BIT &&
        dstDepthFormat != srcDepthFormat)
    {
        ErrorInvalidOperation("blitFramebuffer: Depth buffer formats must match"
                              " if selected.");
        return;
    }

    if (mask & LOCAL_GL_STENCIL_BUFFER_BIT &&
        dstStencilFormat != srcStencilFormat)
    {
        ErrorInvalidOperation("blitFramebuffer: Stencil buffer formats must"
                              " match if selected.");
        return;
    }

    if (dstSamples != 1) {
        ErrorInvalidOperation("blitFramebuffer: DRAW_FRAMEBUFFER may not have"
                              " multiple samples.");
        return;
    }

    if (srcSamples != 1) {
        if (mask & LOCAL_GL_COLOR_BUFFER_BIT &&
            dstColorFormat != srcColorFormat)
        {
            ErrorInvalidOperation("blitFramebuffer: Color buffer formats must"
                                  " match if selected, when reading from a"
                                  " multisampled source.");
            return;
        }

        if (dstX0 != srcX0 ||
            dstX1 != srcX1 ||
            dstY0 != srcY0 ||
            dstY1 != srcY1)
        {
            ErrorInvalidOperation("blitFramebuffer: If the source is"
                                  " multisampled, then the source and dest"
                                  " regions must match exactly.");
            return;
        }
    }

    MakeContextCurrent();
    gl->fBlitFramebuffer(srcX0, srcY0, srcX1, srcY1,
                         dstX0, dstY0, dstX1, dstY1,
                         mask, filter);
}