void Layer::onDraw(const Region& clip) const
{
    if (CC_UNLIKELY(mActiveBuffer == 0)) {
        // the texture has not been created yet, this Layer has
        // in fact never been drawn into. This happens frequently with
        // SurfaceView because the WindowManager can't know when the client
        // has drawn the first time.

        // If there is nothing under us, we paint the screen in black, otherwise
        // we just skip this update.

        // figure out if there is something below us
        Region under;
        const SurfaceFlinger::LayerVector& drawingLayers(
                mFlinger->mDrawingState.layersSortedByZ);
        const size_t count = drawingLayers.size();
        for (size_t i=0 ; i<count ; ++i) {
            const sp<LayerBase>& layer(drawingLayers[i]);
            if (layer.get() == static_cast<LayerBase const*>(this))
                break;
            under.orSelf(layer->visibleRegionScreen);
        }
        // if not everything below us is covered, we plug the holes!
        Region holes(clip.subtract(under));
        if (!holes.isEmpty()) {
            clearWithOpenGL(holes, 0, 0, 0, 1);
        }
        return;
    }

    if (!isProtected()) {
        glBindTexture(GL_TEXTURE_EXTERNAL_OES, mTextureName);
        GLenum filter = GL_NEAREST;
        if (getFiltering() || needsFiltering() || isFixedSize() || isCropped()) {
            // TODO: we could be more subtle with isFixedSize()
            filter = GL_LINEAR;
        }
        glTexParameterx(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, filter);
        glTexParameterx(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, filter);
        glMatrixMode(GL_TEXTURE);
        glLoadMatrixf(mTextureMatrix);
        glMatrixMode(GL_MODELVIEW);
        glDisable(GL_TEXTURE_2D);
        glEnable(GL_TEXTURE_EXTERNAL_OES);
    } else {
        glBindTexture(GL_TEXTURE_2D, mFlinger->getProtectedTexName());
        glMatrixMode(GL_TEXTURE);
        glLoadIdentity();
        glMatrixMode(GL_MODELVIEW);
        glDisable(GL_TEXTURE_EXTERNAL_OES);
        glEnable(GL_TEXTURE_2D);
    }

    drawWithOpenGL(clip);

    glDisable(GL_TEXTURE_EXTERNAL_OES);
    glDisable(GL_TEXTURE_2D);
}
void Layer::onDraw(const Region& clip) const
{
    ATRACE_CALL();

    if (CC_UNLIKELY(mActiveBuffer == 0)) {
        // the texture has not been created yet, this Layer has
        // in fact never been drawn into. This happens frequently with
        // SurfaceView because the WindowManager can't know when the client
        // has drawn the first time.

        // If there is nothing under us, we paint the screen in black, otherwise
        // we just skip this update.

        // figure out if there is something below us
        Region under;
        const SurfaceFlinger::LayerVector& drawingLayers(
                mFlinger->mDrawingState.layersSortedByZ);
        const size_t count = drawingLayers.size();
        for (size_t i=0 ; i<count ; ++i) {
            const sp<LayerBase>& layer(drawingLayers[i]);
            if (layer.get() == static_cast<LayerBase const*>(this))
                break;
            under.orSelf(layer->visibleRegionScreen);
        }
        // if not everything below us is covered, we plug the holes!
        Region holes(clip.subtract(under));
        if (!holes.isEmpty()) {
            clearWithOpenGL(holes, 0, 0, 0, 1);
        }
        return;
    }
#ifdef QCOM_HARDWARE
    if (!qdutils::isGPUSupportedFormat(mActiveBuffer->format)) {
        clearWithOpenGL(clip, 0, 0, 0, 1);
        return;
    }
#ifdef DECIDE_TEXTURE_TARGET
    GLuint currentTextureTarget = mSurfaceTexture->getCurrentTextureTarget();
#endif
#endif
    if (!isProtected()) {
        // TODO: we could be more subtle with isFixedSize()
        const bool useFiltering = getFiltering() || needsFiltering() || isFixedSize();

        // Query the texture matrix given our current filtering mode.
        float textureMatrix[16];
        mSurfaceTexture->setFilteringEnabled(useFiltering);
        mSurfaceTexture->getTransformMatrix(textureMatrix);

        // Set things up for texturing.
#ifdef DECIDE_TEXTURE_TARGET
        glBindTexture(currentTextureTarget, mTextureName);
#else
        glBindTexture(GL_TEXTURE_EXTERNAL_OES, mTextureName);
#endif
        GLenum filter = GL_NEAREST;
        if (useFiltering) {
            filter = GL_LINEAR;
        }
#ifdef DECIDE_TEXTURE_TARGET
        glTexParameterx(currentTextureTarget, GL_TEXTURE_MAG_FILTER, filter);
        glTexParameterx(currentTextureTarget, GL_TEXTURE_MIN_FILTER, filter);
#else
        glTexParameterx(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, filter);
        glTexParameterx(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, filter);
#endif
        glMatrixMode(GL_TEXTURE);
        glLoadMatrixf(textureMatrix);
        glMatrixMode(GL_MODELVIEW);
        glDisable(GL_TEXTURE_2D);
#ifdef DECIDE_TEXTURE_TARGET
        glEnable(currentTextureTarget);
#else
        glEnable(GL_TEXTURE_EXTERNAL_OES);
#endif
    } else {
#ifdef DECIDE_TEXTURE_TARGET
        glBindTexture(currentTextureTarget, mFlinger->getProtectedTexName());
#else
        glBindTexture(GL_TEXTURE_2D, mFlinger->getProtectedTexName());
#endif
        glMatrixMode(GL_TEXTURE);
        glLoadIdentity();
        glMatrixMode(GL_MODELVIEW);
#ifdef DECIDE_TEXTURE_TARGET
        glEnable(currentTextureTarget);
#else
        glDisable(GL_TEXTURE_EXTERNAL_OES);
        glEnable(GL_TEXTURE_2D);
#endif
    }

    drawWithOpenGL(clip);

    glDisable(GL_TEXTURE_EXTERNAL_OES);
    glDisable(GL_TEXTURE_2D);
}
void Layer::onDraw(const sp<const DisplayDevice>& hw, const Region& clip) const
{
#ifdef STE_HARDWARE
    // Convert the texture to a native format if need be.
    // convert() returns immediately if no conversion is necessary.
    if (mSurfaceTexture != NULL) {
        status_t res = mSurfaceTexture->convert();
        if (res != NO_ERROR) {
            ALOGE("Layer::onDraw: texture conversion failed. "
                "Texture content for this layer will not be initialized.");
        }
    }
#endif

    ATRACE_CALL();

    if (CC_UNLIKELY(mActiveBuffer == 0)) {
        // the texture has not been created yet, this Layer has
        // in fact never been drawn into. This happens frequently with
        // SurfaceView because the WindowManager can't know when the client
        // has drawn the first time.

        // If there is nothing under us, we paint the screen in black, otherwise
        // we just skip this update.

        // figure out if there is something below us
        Region under;
        const SurfaceFlinger::LayerVector& drawingLayers(
                mFlinger->mDrawingState.layersSortedByZ);
        const size_t count = drawingLayers.size();
        for (size_t i=0 ; i<count ; ++i) {
            const sp<LayerBase>& layer(drawingLayers[i]);
            if (layer.get() == static_cast<LayerBase const*>(this))
                break;
            under.orSelf( hw->getTransform().transform(layer->visibleRegion) );
        }
        // if not everything below us is covered, we plug the holes!
        Region holes(clip.subtract(under));
        if (!holes.isEmpty()) {
            clearWithOpenGL(hw, holes, 0, 0, 0, 1);
        }
        return;
    }

    status_t err = mSurfaceTexture->doGLFenceWait();
    if (err != OK) {
        ALOGE("onDraw: failed waiting for fence: %d", err);
        // Go ahead and draw the buffer anyway; no matter what we do the screen
        // is probably going to have something visibly wrong.
    }

    bool blackOutLayer = isProtected() || (isSecure() && !hw->isSecure());

    if (!blackOutLayer) {
        // TODO: we could be more subtle with isFixedSize()
        const bool useFiltering = getFiltering() || needsFiltering(hw) || isFixedSize();

        // Query the texture matrix given our current filtering mode.
        float textureMatrix[16];
        mSurfaceTexture->setFilteringEnabled(useFiltering);
        mSurfaceTexture->getTransformMatrix(textureMatrix);

        // Set things up for texturing.
        glBindTexture(GL_TEXTURE_EXTERNAL_OES, mTextureName);
        GLenum filter = GL_NEAREST;
        if (useFiltering) {
            filter = GL_LINEAR;
        }
        glTexParameterx(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, filter);
        glTexParameterx(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, filter);
        glMatrixMode(GL_TEXTURE);
        glLoadMatrixf(textureMatrix);
        glMatrixMode(GL_MODELVIEW);
        glDisable(GL_TEXTURE_2D);
        glEnable(GL_TEXTURE_EXTERNAL_OES);
    } else {
        glBindTexture(GL_TEXTURE_2D, mFlinger->getProtectedTexName());
        glMatrixMode(GL_TEXTURE);
        glLoadIdentity();
        glMatrixMode(GL_MODELVIEW);
        glDisable(GL_TEXTURE_EXTERNAL_OES);
        glEnable(GL_TEXTURE_2D);
    }

    drawWithOpenGL(hw, clip);

    glDisable(GL_TEXTURE_EXTERNAL_OES);
    glDisable(GL_TEXTURE_2D);
}
Example #4
0
void Layer::onDraw(const sp<const DisplayDevice>& hw, const Region& clip) const
{
    ATRACE_CALL();

    if (CC_UNLIKELY(mActiveBuffer == 0)) {
        // the texture has not been created yet, this Layer has
        // in fact never been drawn into. This happens frequently with
        // SurfaceView because the WindowManager can't know when the client
        // has drawn the first time.

        // If there is nothing under us, we paint the screen in black, otherwise
        // we just skip this update.

        // figure out if there is something below us
        Region under;
        const SurfaceFlinger::LayerVector& drawingLayers(
                mFlinger->mDrawingState.layersSortedByZ);
        const size_t count = drawingLayers.size();
        for (size_t i=0 ; i<count ; ++i) {
            const sp<LayerBase>& layer(drawingLayers[i]);
            if (layer.get() == static_cast<LayerBase const*>(this))
                break;
            under.orSelf( hw->getTransform().transform(layer->visibleRegion) );
        }
        // if not everything below us is covered, we plug the holes!
        Region holes(clip.subtract(under));
        if (!holes.isEmpty()) {
            // [MTK] {{{
            // add debug log
            XLOGI("[%s] %s(i:%d): clear screen hole (l:%d, t:%d, w:%d, h:%d)",
                __func__, getName().string(), getIdentity(),
                holes.getBounds().left, holes.getBounds().top,
                holes.getBounds().width(), holes.getBounds().height());
            // [MTK] }}}
            clearWithOpenGL(hw, holes, 0, 0, 0, 1);
        }
        return;
    }

    status_t err = mSurfaceTexture->doGLFenceWait();
    if (err != OK) {
        ALOGE("onDraw: failed waiting for fence: %d", err);
        // Go ahead and draw the buffer anyway; no matter what we do the screen
        // is probably going to have something visibly wrong.
    }

    bool blackOutLayer = isProtected() || (isSecure() && !hw->isSecure());

    if (!blackOutLayer) {
        // TODO: we could be more subtle with isFixedSize()
        const bool useFiltering = getFiltering() || needsFiltering(hw) || isFixedSize();

        // Query the texture matrix given our current filtering mode.
        float textureMatrix[16];
        mSurfaceTexture->setFilteringEnabled(useFiltering);
        mSurfaceTexture->getTransformMatrix(textureMatrix);

        // Set things up for texturing.
        // [MTK] {{{
        // buffer conversion here for SF layer
        if (true == mSurfaceTexture->isAuxSlotNeedConvert()) {
            mSurfaceTexture->convertToAuxSlot(true);
        }

        if (true == mSurfaceTexture->isAuxSlotDirty()) {
            // bind to aux buffer if converted successfully
            mSurfaceTexture->bindToAuxSlot();
        } else {
            // bind to original buffer
            glBindTexture(GL_TEXTURE_EXTERNAL_OES, mTextureName);
        }
        // [MTK] }}}

        GLenum filter = GL_NEAREST;
        if (useFiltering) {
            filter = GL_LINEAR;
        }
        glTexParameterx(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, filter);
        glTexParameterx(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, filter);
        glMatrixMode(GL_TEXTURE);
        glLoadMatrixf(textureMatrix);
        glMatrixMode(GL_MODELVIEW);
        glDisable(GL_TEXTURE_2D);
        glEnable(GL_TEXTURE_EXTERNAL_OES);
    } else {
        glBindTexture(GL_TEXTURE_2D, mFlinger->getProtectedTexName());
        glMatrixMode(GL_TEXTURE);
        glLoadIdentity();
        glMatrixMode(GL_MODELVIEW);
        glDisable(GL_TEXTURE_EXTERNAL_OES);
        glEnable(GL_TEXTURE_2D);
    }

    drawWithOpenGL(hw, clip);

    glDisable(GL_TEXTURE_EXTERNAL_OES);
    glDisable(GL_TEXTURE_2D);
}
void Layer::onDraw(const sp<const DisplayDevice>& hw, const Region& clip) const
{
    ATRACE_CALL();

    if (CC_UNLIKELY(mActiveBuffer == 0)) {
        // the texture has not been created yet, this Layer has
        // in fact never been drawn into. This happens frequently with
        // SurfaceView because the WindowManager can't know when the client
        // has drawn the first time.

        // If there is nothing under us, we paint the screen in black, otherwise
        // we just skip this update.

        // figure out if there is something below us
        Region under;
        const SurfaceFlinger::LayerVector& drawingLayers(
                mFlinger->mDrawingState.layersSortedByZ);
        const size_t count = drawingLayers.size();
        for (size_t i=0 ; i<count ; ++i) {
            const sp<Layer>& layer(drawingLayers[i]);
            if (layer.get() == static_cast<Layer const*>(this))
                break;
            under.orSelf( hw->getTransform().transform(layer->visibleRegion) );
        }
        // if not everything below us is covered, we plug the holes!
        Region holes(clip.subtract(under));
        if (!holes.isEmpty()) {
            clearWithOpenGL(hw, holes, 0, 0, 0, 1);
        }
        return;
    }

    // Bind the current buffer to the GL texture, and wait for it to be
    // ready for us to draw into.
    status_t err = mSurfaceFlingerConsumer->bindTextureImage();
    if (err != NO_ERROR) {
        ALOGW("onDraw: bindTextureImage failed (err=%d)", err);
        // Go ahead and draw the buffer anyway; no matter what we do the screen
        // is probably going to have something visibly wrong.
    }

    bool canAllowGPU = false;
#ifdef QCOM_BSP
    if(isProtected()) {
        char property[PROPERTY_VALUE_MAX];
        if ((property_get("persist.gralloc.cp.level3", property, NULL) > 0) &&
                (atoi(property) == 1)) {
            canAllowGPU = true;
        }
    }
#endif

    bool blackOutLayer = isProtected() || (isSecure() && !hw->isSecure());

    RenderEngine& engine(mFlinger->getRenderEngine());

    if (!blackOutLayer || (canAllowGPU)) {
        // TODO: we could be more subtle with isFixedSize()
        const bool useFiltering = getFiltering() || needsFiltering(hw) || isFixedSize();

        // Query the texture matrix given our current filtering mode.
        float textureMatrix[16];
        mSurfaceFlingerConsumer->setFilteringEnabled(useFiltering);
        mSurfaceFlingerConsumer->getTransformMatrix(textureMatrix);

        if (mSurfaceFlingerConsumer->getTransformToDisplayInverse()) {

            /*
             * the code below applies the display's inverse transform to the texture transform
             */

            // create a 4x4 transform matrix from the display transform flags
            const mat4 flipH(-1,0,0,0,  0,1,0,0, 0,0,1,0, 1,0,0,1);
            const mat4 flipV( 1,0,0,0, 0,-1,0,0, 0,0,1,0, 0,1,0,1);
            const mat4 rot90( 0,1,0,0, -1,0,0,0, 0,0,1,0, 1,0,0,1);

            mat4 tr;
            uint32_t transform = hw->getOrientationTransform();
            if (transform & NATIVE_WINDOW_TRANSFORM_ROT_90)
                tr = tr * rot90;
            if (transform & NATIVE_WINDOW_TRANSFORM_FLIP_H)
                tr = tr * flipH;
            if (transform & NATIVE_WINDOW_TRANSFORM_FLIP_V)
                tr = tr * flipV;

            // calculate the inverse
            tr = inverse(tr);

            // and finally apply it to the original texture matrix
            const mat4 texTransform(mat4(static_cast<const float*>(textureMatrix)) * tr);
            memcpy(textureMatrix, texTransform.asArray(), sizeof(textureMatrix));
        }

        // Set things up for texturing.
        mTexture.setDimensions(mActiveBuffer->getWidth(), mActiveBuffer->getHeight());
        mTexture.setFiltering(useFiltering);
        mTexture.setMatrix(textureMatrix);

        engine.setupLayerTexturing(mTexture);
    } else {
        engine.setupLayerBlackedOut();
    }
    drawWithOpenGL(hw, clip);
    engine.disableTexturing();
}
void LayerBase::drawWithOpenGL(const Region& clip, const Texture& texture) const
{
    const DisplayHardware& hw(graphicPlane(0).displayHardware());
    const uint32_t fbHeight = hw.getHeight();
    const State& s(drawingState());
    
    // bind our texture
    TextureManager::activateTexture(texture, needsFiltering());
    uint32_t width  = texture.width; 
    uint32_t height = texture.height;

    GLenum src = mPremultipliedAlpha ? GL_ONE : GL_SRC_ALPHA;

    int renderEffect = mFlinger->getRenderEffect();
    int renderColorR = mFlinger->getRenderColorR();
    int renderColorG = mFlinger->getRenderColorG();
    int renderColorB = mFlinger->getRenderColorB();

    bool noEffect = renderEffect == 0;

    if (UNLIKELY(s.alpha < 0xFF) && noEffect) {
        const GLfloat alpha = s.alpha * (1.0f/255.0f);
        if (mPremultipliedAlpha) {
            glColor4f(alpha, alpha, alpha, alpha);
        } else {
            glColor4f(1, 1, 1, alpha);
        }
        glEnable(GL_BLEND);
        glBlendFunc(src, GL_ONE_MINUS_SRC_ALPHA);
        glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
    } else if (noEffect) {
        glColor4f(1, 1, 1, 1);
        glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
        if (needsBlending()) {
            glEnable(GL_BLEND);
            glBlendFunc(src, GL_ONE_MINUS_SRC_ALPHA);
        } else {
            glDisable(GL_BLEND);
        }
    } else {
        // Apply a render effect, which is simple color masks for now.
        GLenum env, src;
        env = GL_MODULATE;
        src = mPremultipliedAlpha ? GL_ONE : GL_SRC_ALPHA;
        const GGLfixed alpha = (s.alpha << 16)/255;
        switch (renderEffect) {
            case RENDER_EFFECT_NIGHT:
                glColor4x(alpha, alpha*0.6204, alpha*0.3018, alpha);
                break;
            case RENDER_EFFECT_TERMINAL:
                glColor4x(0, alpha, 0, alpha);
                break;
            case RENDER_EFFECT_BLUE:
                glColor4x(0, 0, alpha, alpha);
                break;
            case RENDER_EFFECT_AMBER:
                glColor4x(alpha, alpha*0.75, 0, alpha);
                break;
            case RENDER_EFFECT_SALMON:
                glColor4x(alpha, alpha*0.5, alpha*0.5, alpha);
                break;
            case RENDER_EFFECT_FUSCIA:
                glColor4x(alpha, 0, alpha*0.5, alpha);
                break;
            case RENDER_EFFECT_N1_CALIBRATED_N:
                glColor4x(alpha*renderColorR/1000, alpha*renderColorG/1000, alpha*renderColorB/1000, alpha);
                break;
            case RENDER_EFFECT_N1_CALIBRATED_R:
                glColor4x(alpha*(renderColorR-50)/1000, alpha*renderColorG/1000, alpha*(renderColorB-30)/1000, alpha);
                break;
            case RENDER_EFFECT_N1_CALIBRATED_C:
                glColor4x(alpha*renderColorR/1000, alpha*renderColorG/1000, alpha*(renderColorB+30)/1000, alpha);
                break;
            case RENDER_EFFECT_RED:
                glColor4x(alpha, 0, 0, alpha);
                break;
        }
        glEnable(GL_BLEND);
        glBlendFunc(src, GL_ONE_MINUS_SRC_ALPHA);
        glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, env);
    }

    /*
     *  compute texture coordinates
     *  here, we handle NPOT, cropping and buffer transformations
     */

    GLfloat cl, ct, cr, cb;
    if (!mBufferCrop.isEmpty()) {
        // source is cropped
        const GLfloat us = (texture.NPOTAdjust ? texture.wScale : 1.0f) / width;
        const GLfloat vs = (texture.NPOTAdjust ? texture.hScale : 1.0f) / height;
        cl = mBufferCrop.left   * us;
        ct = mBufferCrop.top    * vs;
        cr = mBufferCrop.right  * us;
        cb = mBufferCrop.bottom * vs;
    } else {
        cl = 0;
        ct = 0;
        cr = (texture.NPOTAdjust ? texture.wScale : 1.0f);
        cb = (texture.NPOTAdjust ? texture.hScale : 1.0f);
    }

    /*
     * For the buffer transformation, we apply the rotation last.
     * Since we're transforming the texture-coordinates, we need
     * to apply the inverse of the buffer transformation:
     *   inverse( FLIP_V -> FLIP_H -> ROT_90 )
     *   <=> inverse( ROT_90 * FLIP_H * FLIP_V )
     *    =  inverse(FLIP_V) * inverse(FLIP_H) * inverse(ROT_90)
     *    =  FLIP_V * FLIP_H * ROT_270
     *   <=> ROT_270 -> FLIP_H -> FLIP_V
     *
     * The rotation is performed first, in the texture coordinate space.
     *
     */

    struct TexCoords {
        GLfloat u;
        GLfloat v;
    };

    enum {
        // name of the corners in the texture map
        LB = 0, // left-bottom
        LT = 1, // left-top
        RT = 2, // right-top
        RB = 3  // right-bottom
    };

    // vertices in screen space
    int vLT = LB;
    int vLB = LT;
    int vRB = RT;
    int vRT = RB;

    // the texture's source is rotated
    uint32_t transform = mBufferTransform;
    if (transform & HAL_TRANSFORM_ROT_90) {
        vLT = RB;
        vLB = LB;
        vRB = LT;
        vRT = RT;
    }
    if (transform & HAL_TRANSFORM_FLIP_V) {
        swap(vLT, vLB);
        swap(vRT, vRB);
    }
    if (transform & HAL_TRANSFORM_FLIP_H) {
        swap(vLT, vRT);
        swap(vLB, vRB);
    }

    TexCoords texCoords[4];
    texCoords[vLT].u = cl;
    texCoords[vLT].v = ct;
    texCoords[vLB].u = cl;
    texCoords[vLB].v = cb;
    texCoords[vRB].u = cr;
    texCoords[vRB].v = cb;
    texCoords[vRT].u = cr;
    texCoords[vRT].v = ct;

    if (needsDithering()) {
        glEnable(GL_DITHER);
    } else {
        glDisable(GL_DITHER);
    }

    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
    glVertexPointer(2, GL_FLOAT, 0, mVertices);
    glTexCoordPointer(2, GL_FLOAT, 0, texCoords);

    Region::const_iterator it = clip.begin();
    Region::const_iterator const end = clip.end();
    while (it != end) {
        const Rect& r = *it++;
        const GLint sy = fbHeight - (r.top + r.height());
        glScissor(r.left, sy, r.width(), r.height());
        glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
    }
    glDisableClientState(GL_TEXTURE_COORD_ARRAY);
}