rk3288 android UI旋转0度 90度 180度 270度 底层 kernel 分析(android7.1.2)

一. 上层调用SurfaceFlinger.cpp 中的函数,进行旋转操作,从这个函数开始分析。

路径:frameworks\native\services\surfaceflinger\SurfaceFlinger.cpp

//初始化
void SurfaceFlinger::init() {
    //设定初始条件
    initializeDisplays();
}

void SurfaceFlinger::initializeDisplays() {
    flinger->onInitializeDisplays();
}

void SurfaceFlinger::onInitializeDisplays() {
    //关键的函数 
    setTransactionState(state, displays, 0);
}

void SurfaceFlinger::setTransactionState() {
    //设定旋转的标志位 通知函数SurfaceFlinger::onMessageReceived 来进行处理
}

 1.SurfaceFlinger::onMessageReceived 来进行处理接收到的旋转信息。

主要包括: handleMessageTransaction();
                   handleMessageInvalidate();
                   handleMessageRefresh();


void SurfaceFlinger::onMessageReceived(int32_t what) {
    ATRACE_CALL();
    switch (what) {
        case MessageQueue::INVALIDATE: {
            bool frameMissed = !mHadClientComposition &&
                    mPreviousPresentFence != Fence::NO_FENCE &&
                    mPreviousPresentFence->getSignalTime() == INT64_MAX;
            ATRACE_INT("FrameMissed", static_cast<int>(frameMissed));
            if (mPropagateBackpressure && frameMissed) {
                signalLayerUpdate();
                break;
            }

            bool refreshNeeded = handleMessageTransaction();
            refreshNeeded |= handleMessageInvalidate();
            refreshNeeded |= mRepaintEverything;
            if (refreshNeeded) {
                // Signal a refresh if a transaction modified the window state,
                // a new buffer was latched, or if HWC has requested a full
                // repaint
                signalRefresh();
            }
            break;
        }
        case MessageQueue::REFRESH: {
            handleMessageRefresh();
            break;
        }
    }
}

1.1 SurfaceFlinger::handleMessageTransaction()  主要是设定旋转的标志位,遍历所有图层,对需要作出旋转的事务进行处理。

bool SurfaceFlinger::handleMessageTransaction() {    
    //这里使用到函数setTransactionState 设定的旋转标志位 进行处理
    handleTransaction(transactionFlags);
}

void SurfaceFlinger::handleTransaction(uint32_t transactionFlags)
{
    //通过事务锁来提交事务
    handleTransactionLocked(transactionFlags);
}

void SurfaceFlinger::handleTransactionLocked(uint32_t transactionFlags)
{
    //遍历所有图层,对需要作出旋转的事务进行处理。
    hw->setProjection(state.orientation,state.viewport, state.frame);
}

1.1.1 旋转核心函数void DisplayDevice::setProjection(int orientation,const Rect& newViewport, const Rect& newFrame):获取0度 90度 180度 270度 方向标志位,生成全局变换矩阵mGlobalTransform。 

路径:frameworks\native\services\surfaceflinger\DisplayDevice.cpp

void DisplayDevice::setProjection(int orientation,
        const Rect& newViewport, const Rect& newFrame) {
    Rect viewport(newViewport);
    Rect frame(newFrame);

#if RK_FORCE_SCALE_FULLSCREEN
    ALOGV("name =%s",getDisplayName().string());
    ALOGV(" viewport [%d %d] mViewport [%d %d]",viewport.getWidth(),viewport.getHeight(),mViewport.getWidth(),mViewport.getHeight());
    ALOGV(" frame [%d %d]", frame.getWidth(),frame.getHeight());
    ALOGV(" hw [%d %d]", getWidth(),getHeight());

    bool isVirtualScreen = mType == DisplayDevice::DISPLAY_VIRTUAL;
    if ((isVirtualScreen && frame.getWidth() > frame.getHeight()) ||
        (viewport.getWidth()== getWidth() && viewport.getHeight() == getHeight()
         && (getWidth() != frame.getWidth() || getHeight() != frame.getHeight()))
        ) {
        //If change the resolution,force scale to full screen.
        frame = Rect(0,0,getWidth(),getHeight());
        ALOGV("update frame [%d,%d]",frame.getWidth(),frame.getHeight());
    }
#endif

#if !RK_VR & RK_HW_ROTATION
    bool isHdmiScreen = mType == DisplayDevice::DISPLAY_EXTERNAL;
    if (isHdmiScreen) {
        int eInitOrientation = 0;
        bool isSfHwrotated = false;
        bool isSupportRotation = false;
        bool isPrimaryExternalSameOrientation = false;
        Rect newFrame = Rect(0,0,getWidth(),getHeight());
        Rect newFrameRotated = Rect(0,0,getHeight(),getWidth());
        float frameRatio = (float)frame.getWidth() / frame.getHeight();
        char value[PROPERTY_VALUE_MAX];

        property_get("ro.sf.hwrotation", value, "0");
        isSfHwrotated = atoi(value) != 0;
        property_get("ro.same.orientation", value, "false");
        isPrimaryExternalSameOrientation = !strcmp(value,"true");
        if(!isSfHwrotated) {
            property_get("ro.orientation.einit", value, "0");
            eInitOrientation = atoi(value) / 90;
            property_get("ro.rotation.external", value, "false");
            isSupportRotation = !strcmp(value,"true");
        }
        if (isSupportRotation && !isPrimaryExternalSameOrientation) {
            mClientOrientation = orientation;
            if (eInitOrientation % 2 == 1) {
                frame = frameRatio > 1.0 ? frame : newFrameRotated;
                ALOGI("%d,[%d,%d]",__LINE__,frame.getWidth(),frame.getHeight());
            } else {
                frame = frameRatio > 1.0 ? newFrame : frame;
                ALOGI("%d,[%d,%d]",__LINE__,frame.getWidth(),frame.getHeight());
            }
        } else if (isSupportRotation) {
            mClientOrientation = orientation;
            if (eInitOrientation % 2 == 1) {
                //frame = frameRatio > 1.0 ? frame : frame;
                ALOGI("%d,[%d,%d]",__LINE__,frame.getWidth(),frame.getHeight());
            } else {
                frame = frameRatio > 1.0 ? newFrame : newFrameRotated;
                ALOGI("%d,[%d,%d]",__LINE__,frame.getWidth(),frame.getHeight());
            }
        } else if (eInitOrientation % 2 != 0) {
            if (isPrimaryExternalSameOrientation) {
                //frame = frameRatio > 1.0 ? frame : frame;
                ALOGI("%d,[%d,%d]",__LINE__,frame.getWidth(),frame.getHeight());
            } else {
                frame = frameRatio > 1.0 ? frame : newFrameRotated;
                ALOGI("%d,[%d,%d]",__LINE__,frame.getWidth(),frame.getHeight());
            }
        } else if (eInitOrientation % 2 == 0) {
            if (isPrimaryExternalSameOrientation) {
                frame = frameRatio > 1.0 ? newFrame : frame;
                ALOGI("%d,[%d,%d]",__LINE__,frame.getWidth(),frame.getHeight());
            } else {
                frame = frameRatio > 1.0 ? newFrame : frame;
                ALOGI("%d,[%d,%d]",__LINE__,frame.getWidth(),frame.getHeight());
            }
        } else {
            frame = frameRatio > 1.0 ? newFrame : frame;
            ALOGI("%d,[%d,%d]",__LINE__,frame.getWidth(),frame.getHeight());
        }
   
        property_get("persist.orientation.vhshow", value, "false");
        if(!strcmp(value,"true")) {
            property_get("persist.orientation.vhinit", value, "0");
            eInitOrientation = atoi(value);
            if(eInitOrientation == 0) {
                int width,height;
                width = getWidth()>getHeight() ? getWidth():getHeight();
                height = width == getHeight()  ? getWidth():getHeight();
                frame = Rect(0,0,width, height);
            } else if(eInitOrientation == 1) {
                int width,height;
                width = getWidth()<getHeight() ? getWidth():getHeight();
                height = width == getHeight()  ? getWidth():getHeight();
                frame = Rect(0,0,width, height);
            }
        }
        ALOGV("update frame [%d,%d]",frame.getWidth(),frame.getHeight());
    }
    if (mType == DisplayDevice::DISPLAY_PRIMARY) {
        mClientOrientation = orientation;
        orientation = (mHardwareOrientation + orientation) % 4;
    }
#endif

    const int w = mDisplayWidth;
    const int h = mDisplayHeight;

    Transform R;
    DisplayDevice::orientationToTransfrom(orientation, w, h, &R);

    if (!frame.isValid()) {
        // the destination frame can be invalid if it has never been set,
        // in that case we assume the whole display frame.
        frame = Rect(w, h);
    }

    if (viewport.isEmpty()) {
        // viewport can be invalid if it has never been set, in that case
        // we assume the whole display size.
        // it's also invalid to have an empty viewport, so we handle that
        // case in the same way.
        viewport = Rect(w, h);
        if (R.getOrientation() & Transform::ROT_90) {
            // viewport is always specified in the logical orientation
            // of the display (ie: post-rotation).
            swap(viewport.right, viewport.bottom);
        }
    }

    dirtyRegion.set(getBounds());

    Transform TL, TP, S;
    float src_width  = viewport.width();
    float src_height = viewport.height();
    float dst_width  = frame.width();
    float dst_height = frame.height();
    if (src_width != dst_width || src_height != dst_height) {
        float sx = dst_width  / src_width;
        float sy = dst_height / src_height;
        S.set(sx, 0, 0, sy);
    }

    float src_x = viewport.left;
    float src_y = viewport.top;
    float dst_x = frame.left;
    float dst_y = frame.top;
    TL.set(-src_x, -src_y);
    TP.set(dst_x, dst_y);

    // The viewport and frame are both in the logical orientation.
    // Apply the logical translation, scale to physical size, apply the
    // physical translation and finally rotate to the physical orientation.
    mGlobalTransform = R * TP * S * TL;

#if RK_HW_ROTATION
    Transform realR;
    if (DisplayDevice::orientationToTransfrom(
            mClientOrientation, w, h, &realR) == NO_ERROR) {
        mRealGlobalTransform = realR * TP * S * TL;
    }
#endif

    const uint8_t type = mGlobalTransform.getType();
    mNeedsFiltering = (!mGlobalTransform.preserveRects() ||
            (type >= Transform::SCALE));

    mScissor = mGlobalTransform.transform(viewport);
    if (mScissor.isEmpty()) {
        mScissor = getBounds();
    }

    mOrientation = orientation;
    if (mType == DisplayType::DISPLAY_PRIMARY) {
        uint32_t transform = 0;
        switch (mOrientation) {
            case DisplayState::eOrientationDefault:
                transform = Transform::ROT_0;
                break;
            case DisplayState::eOrientation90:
                transform = Transform::ROT_90;
                break;
            case DisplayState::eOrientation180:
                transform = Transform::ROT_180;
                break;
            case DisplayState::eOrientation270:
                transform = Transform::ROT_270;
                break;
        }
        sPrimaryDisplayOrientation = transform;
    }
    mViewport = viewport;
    mFrame = frame;
}

******** 1 void DisplayDevice::setProjection(int orientation,const Rect& newViewport, const Rect& newFrame)函数生成全局变换矩阵,通过调用Transform.cpp 中的函数进行相应的矩阵变换。接下来分析在Transform.cpp 中是如何进行矩阵变换的。

路径:frameworks\native\services\surfaceflinger\Transform.cpp

(1) 可以在Transform::reset() 中对0度时的UI 进行rotation 操作,只需要改变3x3 matrix 即可。

void Transform::reset() {
    // mType = IDENTITY;
    // for(int i=0 ; i<3 ; i++) {
    //     vec3& v(mMatrix[i]);
    //     for (int j=0 ; j<3 ; j++)
    //         v[j] = ((i==j) ? 1.0f : 0.0f);
    // }
    mType = IDENTITY;
    //mType |= SCALE;
    for(int i=0 ; i<3 ; i++) {
        vec3& v(mMatrix[i]);
        if(i == 0) {
            v[0]  = 0.4f;
            v[1]  = 0.2f;
            v[2]  = 0.0f;
        }

        if(i == 1) {
            v[0]  = 0.0f;
            v[1]  = 0.4f;
            v[2]  = 0.0f;
        }
        if(i == 2) {
            v[0]  = 0.0f;
            v[1]  = 0.0f;
            v[2]  = 1.0f;
        }   
    }
}

(2)可以在Transform::set() 模板中对0度时的UI 进行rotation scale  translate操作,只需要改变3x3 matrix 即可。

void Transform::set(float tx, float ty)
{
    mMatrix[2][0] = tx;
    mMatrix[2][1] = ty;
    mMatrix[2][2] = 1.0f;

    if (isZero(tx) && isZero(ty)) {
        mType &= ~TRANSLATE;
    } else {
        mType |= TRANSLATE;
    }
}

void Transform::set(float a, float b, float c, float d)
{
    mat33& M(mMatrix);
    M[0][0] = a;    M[1][0] = b;
    M[0][1] = c;    M[1][1] = d;
    M[0][2] = 0;    M[1][2] = 0;
    mType = UNKNOWN_TYPE;
}

status_t Transform::set(uint32_t flags, float w, float h)
{

    if (flags & ROT_INVALID) {
        // that's not allowed!
        reset();
        //R.mType = ROTATE;
        //R.mType |= SCALE;
        //R.mType |= TRANSLATE;
/*         mat33& M(R.mMatrix);
        M[0][0] = 0.8f;    M[1][0] = 0;    M[2][0] = 0;
        M[0][1] = 0;    M[1][1] = 0.8f;    M[2][1] = 0;
        M[0][2] = 0;    M[1][2] = 0;    M[2][2] = 1; */
        return BAD_VALUE;
    }

    Transform H, V, R;
    if (flags & ROT_90) {
        // w & h are inverted when rotating by 90 degrees
        swap(w, h);
    }

    if (flags & FLIP_H) {
        H.mType = (FLIP_H << 8) | SCALE;
        H.mType |= isZero(w) ? IDENTITY : TRANSLATE;
        mat33& M(H.mMatrix);
        M[0][0] = -1;
        M[2][0] = w;
    }

    if (flags & FLIP_V) {
        V.mType = (FLIP_V << 8) | SCALE;
        V.mType |= isZero(h) ? IDENTITY : TRANSLATE;
        mat33& M(V.mMatrix);
        M[1][1] = -1;
        M[2][1] = h;
    }

    if (flags & ROT_90) {
        const float original_w = h;
        R.mType = (ROT_90 << 8) | ROTATE;
        R.mType |= isZero(original_w) ? IDENTITY : TRANSLATE;
        mat33& M(R.mMatrix);
        M[0][0] = 0;    M[1][0] =-1;    M[2][0] = original_w;
        M[0][1] = 1;    M[1][1] = 0;
    }

    *this = (R*(H*V));
    return NO_ERROR;
}

Transform类中还有许多函数,例如求逆矩阵,就不一一详述。

以上通过设定opengl matrix 进行对UI 进行相应的操作,本人只成功进行过ratation 操作,scale 以及translate 并未成功,可能是相应设定没设定对。

******** 2 在hwcomposer.cpp 同样可以对系统UI 界面进行scale 以及translate 操作。接下来分析如果通过hwcomposer设定UI 的缩放和平移。

路径:hardware\rockchip\hwcomposer\hwcomposer.cpp

(1) 平移(offset)

              int xoffset = 300;(向右偏移300像素点)
              int yoffset = 200;(向下偏移200像素点)

将 :      hd->w_scale * sf_layer->displayFrame.left, hd->h_scale * sf_layer->displayFrame.top,
              hd->w_scale * sf_layer->displayFrame.right, hd->h_scale * sf_layer->displayFrame.bottom);

修改为:
              hd->w_scale * sf_layer->displayFrame.left + xoffset, hd->h_scale * sf_layer->displayFrame.top + yoffset,
              hd->w_scale * sf_layer->displayFrame.right + xoffset, hd->h_scale * sf_layer->displayFrame.bottom + yoffset);

int DrmHwcLayer::InitFromHwcLayer(struct hwc_context_t *ctx, int display, hwc_layer_1_t *sf_layer, Importer *importer,
                                  const gralloc_module_t *gralloc, bool bClone) {


      if(bClone) {
          //int panle_height = hd->rel_yres + hd->v_total;
          //int y_offset =  (panle_height - panle_height * 3 / 147) / 2 + panle_height * 3 / 147;
          int y_offset = hd->v_total;
          display_frame = DrmHwcRect<int>(
          hd->w_scale * sf_layer->displayFrame.left, hd->h_scale * sf_layer-                    >displayFrame.top + y_offset,
          hd->w_scale * sf_layer->displayFrame.right, hd->h_scale * sf_layer->displayFrame.bottom + y_offset);
      }else {
          if(stereo == FPS_3D)
          {
              int y_offset = hd->v_total;
              display_frame = DrmHwcRect<int>(
              hd->w_scale * sf_layer->displayFrame.left, hd->h_scale * sf_layer->displayFrame.top,
              hd->w_scale * sf_layer->displayFrame.right, hd->h_scale * sf_layer->displayFrame.bottom + y_offset);
          }else {
              //设定平移的参数,以图像左上角为原点,向右向下为正
              int xoffset = 300;
              int yoffset = 200;
              display_frame = DrmHwcRect<int>(

              //hd->w_scale * sf_layer->displayFrame.left, hd->h_scale * sf_layer->displayFrame.top,
              //hd->w_scale * sf_layer->displayFrame.right, hd->h_scale * sf_layer->displayFrame.bottom);
              hd->w_scale * sf_layer->displayFrame.left + xoffset, hd->h_scale * sf_layer->displayFrame.top + yoffset,
              hd->w_scale * sf_layer->displayFrame.right + xoffset, hd->h_scale * sf_layer->displayFrame.bottom + yoffset);
          }
      }
}

(2) 缩放(scale)

将 :      hd->w_scale = (float)mode.h_display() / hd->framebuffer_width;
              hd->h_scale = (float)mode.v_display() / hd->framebuffer_height;

修改为:
               hd->w_scale = 1.0;(如果缩小一半,就设定为0.5)
               hd->h_scale = 1.0;(如果缩小一半,就设定为0.5)

static int hwc_prepare(hwc_composer_device_1_t *dev, size_t num_displays,
                       hwc_display_contents_1_t **display_contents) {

    update_hdmi_output_format(ctx, connector, i, hd);
    update_display_bestmode(hd, i, connector);
    DrmMode mode = connector->best_mode();
    connector->set_current_mode(mode);
    hd->rel_xres = mode.h_display();
    hd->rel_yres = mode.v_display();
    hd->v_total = mode.v_total();
    //hd->w_scale = (float)mode.h_display() / hd->framebuffer_width;
    //hd->h_scale = (float)mode.v_display() / hd->framebuffer_height;
    hd->w_scale = 1.0;
    hd->h_scale = 1.0;
    int fbSize = hd->framebuffer_width * hd->framebuffer_height;
    //get plane size for display
    std::vector<PlaneGroup *>& plane_groups = ctx->drm.GetPlaneGroups();
    hd->iPlaneSize = 0;
    hd->is_interlaced = (mode.interlaced()>0) ? true:false;
    for (std::vector<PlaneGroup *> ::const_iterator iter = plane_groups.begin();
        iter != plane_groups.end(); ++iter)
}

 1.2 SurfaceFlinger::handleMessageInvalidate()  主要是更新layer 对象,等待vsync到来,调用   

    rebuildLayerStacks();
    setUpHWComposer();
    doDebugFlashRegions();
    doComposition();
    postComposition(refreshStartTime);

    将图像合成并显示。

bool SurfaceFlinger::handleMessageInvalidate() {
    ATRACE_CALL();
    //对多个图层进行处理
    return handlePageFlip();
}

bool SurfaceFlinger::handlePageFlip()
{
   //对需要更新对象更新对应区域
   const Region dirty(layer->latchBuffer(visibleRegions));
}

1.3 SF 收到vsync 信号调用void SurfaceFlinger::handleMessageRefresh() 函数。

void SurfaceFlinger::handleMessageRefresh() {
    //遍历所有的图层,检测是否有图层发生变化,如果发生变化,SF最终还是会调用        
    //SurfaceFlinger::onMessageReceived()函数 
    preComposition();
    //1.重建所有显示设备得图层,组成新的列表
    //2.计算每个图层在设备上的可见区域
    //3.计算设备需要更新的区域
    rebuildLayerStacks();
    //调用hardware\rockchip\hwcomposer\hwcomposer.cpp HWComposer对象中图层对象列表以及图层属性
    //将每种HWComposer中的所有图层的类型都设置为HWC_FRAMEBUFFER
    setUpHWComposer();
    doDebugFlashRegions();
    //合成所有图层,生成 OpenGL 纹理图像
    //如果不支持HWC ,则用GPU 合成,eglSwapBuffers 来显示
    doComposition();
    //将图像传递给物理屏幕
    postComposition(refreshStartTime);
}

1.3.1  SurfaceFlinger::rebuildLayerStacks() 函数根据旋转标志位,计算图层可见区域。

void SurfaceFlinger::rebuildLayerStacks() {
    if (displayDevice->isDisplayOn()) {
        SurfaceFlinger::computeVisibleRegions(layers,
            displayDevice->getLayerStack(), dirtyRegion,
            opaqueRegion);
}

void SurfaceFlinger::computeVisibleRegions(
        const LayerVector& currentLayers, uint32_t layerStack,
        Region& outDirtyRegion, Region& outOpaqueRegion)
{
    // compute the opaque region
    const int32_t layerOrientation = s.active.transform.getOrientation();
    if (s.alpha == 1.0f && !translucent &&
                        ((layerOrientation & Transform::ROT_INVALID) == false)) {
        // the opaque region is the layer's footprint
           opaqueRegion = visibleRegion;
    }
}

1.3.2  SurfaceFlinger::doComposition() 函数 主要用来合成图层,使用hwc 以及不使用hwc 如何进行处理。最终都会循环调用layer 的draw() 进行绘制图形。

void SurfaceFlinger::doComposition() {
    // repaint the framebuffer (if needed)
    doDisplayComposition(hw, dirtyRegion);

}

void SurfaceFlinger::doDisplayComposition(const sp<const DisplayDevice>& hw,
        const Region& inDirtyRegion)
{   
    if (!doComposeSurfaces(hw, dirtyRegion)) return;
}

bool SurfaceFlinger::doComposeSurfaces(
        const sp<const DisplayDevice>& displayDevice, const Region& dirty)
{
    //调用opengl 生成纹理
    layer->draw(displayDevice, clip);
}

二. SurfaceFlinger.cpp 中的函数最终都会调用Layer.cpp中的函数进行绘制图像。接下来分析Layer.cpp 中是如何对图像进行渲染的。

路径:frameworks\native\services\surfaceflinger\Layer.cpp

 1.void Layer::draw(const sp<const DisplayDevice>& hw, const Region& clip) 函数为0度 90度 180度 270度设置相应的纹理坐标,然后调用调用opengl 进行渲染。

void Layer::draw(const sp<const DisplayDevice>& hw, const Region& clip) const {
    onDraw(hw, clip, false);
}

void Layer::onDraw(const sp<const DisplayDevice>& hw, const Region& clip,
        bool useIdentityTransform) const
{
    //opengl 利用纹理坐标与顶点坐标得到layer 的显示内容以及区域
    drawWithOpenGL(hw, clip, useIdentityTransform);
}

void Layer::drawWithOpenGL(const sp<const DisplayDevice>& hw,
        const Region& /* clip */, bool useIdentityTransform) const {
    computeGeometry(hw, mMesh, useIdentityTransform);
}

void Layer::computeGeometry(const sp<const DisplayDevice>& hw, Mesh& mesh,
        bool useIdentityTransform) const
{
    const Layer::State& s(getDrawingState());
#if RK_HW_ROTATION
    Transform tr(hw->getTransform());
    const Transform identity;
#else
    const Transform tr(hw->getTransform());
#endif
    const uint32_t hw_h = hw->getHeight();
    Rect win(s.active.w, s.active.h);

    D("mDrawingScreenshot : %d, useIdentityTransform : %d.", mDrawingScreenshot, useIdentityTransform);
#if RK_HW_ROTATION
    if (mDrawingScreenshot) {
        computeHWGeometry(tr, identity, hw);
    }
#endif

    if (!s.crop.isEmpty()) {
        win.intersect(s.crop, &win);
    }
    // subtract the transparent region and snap to the bounds
    win = reduce(win, s.activeTransparentRegion);

    vec2 lt = vec2(win.left, win.top);
    vec2 lb = vec2(win.left, win.bottom);
    vec2 rb = vec2(win.right, win.bottom);
    vec2 rt = vec2(win.right, win.top);

    if (!useIdentityTransform) {
        lt = s.active.transform.transform(lt);
        lb = s.active.transform.transform(lb);
        rb = s.active.transform.transform(rb);
        rt = s.active.transform.transform(rt);
    }

    if (!s.finalCrop.isEmpty()) {
        boundPoint(&lt, s.finalCrop);
        boundPoint(&lb, s.finalCrop);
        boundPoint(&rb, s.finalCrop);
        boundPoint(&rt, s.finalCrop);
    }

    Mesh::VertexArray<vec2> position(mesh.getPositionArray<vec2>());
    position[0] = tr.transform(lt);
    position[1] = tr.transform(lb);
    position[2] = tr.transform(rb);
    position[3] = tr.transform(rt);
    for (size_t i=0 ; i<4 ; i++) {
        position[i].y = hw_h - position[i].y;
    }
    //position[2].y = 200;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android 7.1.2(也被称为Nougat)是Android操作系统的一个版本,它的源代码是公开可用的。源码包含了构建和运行Android操作系统所需的所有代码文件和资源。 Android操作系统是一个基于Linux内核的开源平台,用于移动设备和嵌入式系统。它提供了一个统一的操作系统环境,使开发者能够轻松地构建和定制自己的Android设备。 Android 7.1.2源码提供了一套开发工具和框架,使开发者能够创建功能丰富、稳定和安全的应用程序。该版本中引入了一些新的功能和改进,例如通知栏的增强、多任务处理的改进、Doze模式的优化等,以提供更好的用户体验。 通过查看源代码,开发者可以深入了解Android操作系统的内部工作原理,并理解其中的各个组件和模块是如何相互协作的。例如,开发者可以研究Android的用户界面框架、应用程序生命周期管理、通信和存储机制等。 修复和优化Android 7.1.2源码也是可能的,因为它是开源的。开发者可以根据自己的需求和想法对操作系统进行修改,并参与到Android社区中,与其他开发者分享和贡献代码。 总之,Android 7.1.2源码为开发者提供了一个定制和构建Android应用的平台。通过深入了解源码,开发者能够创建出更加出色和创新的应用程序,同时也有机会参与到Android开源社区中,为整个生态系统的发展做出贡献。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值