diff --git a/codec/encoder/core/inc/svc_motion_estimate.h b/codec/encoder/core/inc/svc_motion_estimate.h index 7400c28c..1d339477 100644 --- a/codec/encoder/core/inc/svc_motion_estimate.h +++ b/codec/encoder/core/inc/svc_motion_estimate.h @@ -67,7 +67,7 @@ uint32_t uiSatdCost; /* satd + lm * nbits */ uint32_t uiSadCostThreshold; int32_t iCurMeBlockPixX; int32_t iCurMeBlockPixY; -uint8_t uiPixel; /* PIXEL_WxH */ +uint8_t uiBlockSize; /* BLOCK_WxH */ uint8_t uiReserved; uint8_t* pEncMb; @@ -147,7 +147,14 @@ bool CheckDirectionalMvFalse(PSampleSadSatdCostFunc pSad, void * vpMe, const SMVUnitXY ksMinMv, const SMVUnitXY ksMaxMv, const int32_t kiEncStride, const int32_t kiRefStride, int32_t& iBestSadCost); -inline void SetMvWithinMvRange( const int32_t kiMbWidth, const int32_t kiMbHeight, const int32_t kiMbX, const int32_t kiMbY, +void LineFullSearch_c( PSampleSadSatdCostFunc pSad, void *vpMe, + uint16_t* pMvdTable, const int32_t kiFixedMvd, + const int32_t kiEncStride, const int32_t kiRefStride, + const int32_t kiMinPos, const int32_t kiMaxPos, + const bool bVerticalSearch ); +void WelsMotionCrossSearch(SWelsFuncPtrList *pFuncList, SDqLayer* pCurLayer, SWelsME * pMe, const SSlice* pSlice); + +inline void SetMvWithinIntegerMvRange( const int32_t kiMbWidth, const int32_t kiMbHeight, const int32_t kiMbX, const int32_t kiMbY, const int32_t kiMaxMvRange, SMVUnitXY* pMvMin, SMVUnitXY* pMvMax) { diff --git a/codec/encoder/core/src/encoder_ext.cpp b/codec/encoder/core/src/encoder_ext.cpp index 52f74dd4..188b0b9f 100644 --- a/codec/encoder/core/src/encoder_ext.cpp +++ b/codec/encoder/core/src/encoder_ext.cpp @@ -2499,7 +2499,7 @@ static inline void SetNormalCodingFunc(SWelsFuncPtrList* pFuncList) void PreprocessSliceCoding (sWelsEncCtx* pCtx) { SDqLayer* pCurLayer = pCtx->pCurDqLayer; - const bool kbBaseAvail = pCurLayer->bBaseLayerAvailableFlag; + //const bool kbBaseAvail = pCurLayer->bBaseLayerAvailableFlag; const bool kbHighestSpatialLayer = (pCtx->pSvcParam->iSpatialLayerNum == (pCurLayer->sLayerInfo.sNalHeaderExt.uiDependencyId + 1)); SWelsFuncPtrList* pFuncList = pCtx->pFuncList; diff --git a/codec/encoder/core/src/md.cpp b/codec/encoder/core/src/md.cpp index fab3dc71..6eaf8ea4 100644 --- a/codec/encoder/core/src/md.cpp +++ b/codec/encoder/core/src/md.cpp @@ -536,7 +536,7 @@ inline void MeRefineQuarPixel (SWelsFuncPtrList* pFunc, SWelsME* pMe, SMeRefineP int32_t iCurCost; uint8_t* pEncMb = pMe->pEncMb; uint8_t* pTmp = NULL; - const uint8_t kuiPixel = pMe->uiPixel; + const uint8_t kuiPixel = pMe->uiBlockSize; pSampleAvg[kiAvgIndex] (pMeRefine->pQuarPixTmp, ME_REFINE_BUF_STRIDE, pParams->pSrcA[0], ME_REFINE_BUF_STRIDE, pParams->pSrcB[0], pParams->iStrideA, kiHeight); @@ -603,7 +603,7 @@ void MeRefineFracPixel (sWelsEncCtx* pEncCtx, uint8_t* pMemPredInterMb, SWelsME* && (pFunc->sSampleDealingFuncs.pfMdCost == pFunc->sSampleDealingFuncs.pfSampleSatd)) { iBestCost = pMe->uSadPredISatd.uiSatd + COST_MVD (pMe->pMvdCost, iMvx - pMe->sMvp.iMvX, iMvy - pMe->sMvp.iMvY); } else { - iBestCost = pFunc->sSampleDealingFuncs.pfMeCost[pMe->uiPixel] (pEncData, kiStrideEnc, pRef, kiStrideRef) + + iBestCost = pFunc->sSampleDealingFuncs.pfMeCost[pMe->uiBlockSize] (pEncData, kiStrideEnc, pRef, kiStrideRef) + COST_MVD (pMe->pMvdCost, iMvx - pMe->sMvp.iMvX, iMvy - pMe->sMvp.iMvY); } @@ -614,7 +614,7 @@ void MeRefineFracPixel (sWelsEncCtx* pEncCtx, uint8_t* pMemPredInterMb, SWelsME* //step 1: get [iWidth][iHeight+1] half pixel from vertical filter //===========================(0, -2)==============================// - iCurCost = pFunc->sSampleDealingFuncs.pfMeCost[pMe->uiPixel] (pEncData, kiStrideEnc, pMeRefine->pHalfPixV, + iCurCost = pFunc->sSampleDealingFuncs.pfMeCost[pMe->uiBlockSize] (pEncData, kiStrideEnc, pMeRefine->pHalfPixV, ME_REFINE_BUF_STRIDE) + COST_MVD (pMe->pMvdCost, iMvx - pMe->sMvp.iMvX, iMvy - 2 - pMe->sMvp.iMvY); if (iCurCost < iBestCost) { @@ -623,7 +623,7 @@ void MeRefineFracPixel (sWelsEncCtx* pEncCtx, uint8_t* pMemPredInterMb, SWelsME* pBestPredInter = pMeRefine->pHalfPixV; } //===========================(0, 2)==============================// - iCurCost = pFunc->sSampleDealingFuncs.pfMeCost[pMe->uiPixel] (pEncData, kiStrideEnc, + iCurCost = pFunc->sSampleDealingFuncs.pfMeCost[pMe->uiBlockSize] (pEncData, kiStrideEnc, pMeRefine->pHalfPixV + ME_REFINE_BUF_STRIDE, ME_REFINE_BUF_STRIDE) + COST_MVD (pMe->pMvdCost, iMvx - pMe->sMvp.iMvX, iMvy + 2 - pMe->sMvp.iMvY); if (iCurCost < iBestCost) { @@ -636,7 +636,7 @@ void MeRefineFracPixel (sWelsEncCtx* pEncCtx, uint8_t* pMemPredInterMb, SWelsME* //step 2: get [iWidth][iHeight+1] half pixel from horizon filter //===========================(-2, 0)==============================// - iCurCost = pFunc->sSampleDealingFuncs.pfMeCost[pMe->uiPixel] (pEncData, kiStrideEnc, pMeRefine->pHalfPixH, + iCurCost = pFunc->sSampleDealingFuncs.pfMeCost[pMe->uiBlockSize] (pEncData, kiStrideEnc, pMeRefine->pHalfPixH, ME_REFINE_BUF_STRIDE) + COST_MVD (pMe->pMvdCost, iMvx - 2 - pMe->sMvp.iMvX, iMvy - pMe->sMvp.iMvY); if (iCurCost < iBestCost) { @@ -645,7 +645,7 @@ void MeRefineFracPixel (sWelsEncCtx* pEncCtx, uint8_t* pMemPredInterMb, SWelsME* pBestPredInter = pMeRefine->pHalfPixH; } //===========================(2, 0)===============================// - iCurCost = pFunc->sSampleDealingFuncs.pfMeCost[pMe->uiPixel] (pEncData, kiStrideEnc, pMeRefine->pHalfPixH + 1, + iCurCost = pFunc->sSampleDealingFuncs.pfMeCost[pMe->uiBlockSize] (pEncData, kiStrideEnc, pMeRefine->pHalfPixH + 1, ME_REFINE_BUF_STRIDE) + COST_MVD (pMe->pMvdCost, iMvx + 2 - pMe->sMvp.iMvX, iMvy - pMe->sMvp.iMvY); if (iCurCost < iBestCost) { diff --git a/codec/encoder/core/src/svc_base_layer_md.cpp b/codec/encoder/core/src/svc_base_layer_md.cpp index 333057b5..b7d26bf5 100644 --- a/codec/encoder/core/src/svc_base_layer_md.cpp +++ b/codec/encoder/core/src/svc_base_layer_md.cpp @@ -359,7 +359,7 @@ void WelsMdInterInit (sWelsEncCtx* pEncCtx, SSlice* pSlice, SMB* pCurMb, const i ST32 (&pCurMb->sP16x16Mv, 0); ST32 (&pCurLayer->pDecPic->sMvList[kiMbXY], 0); - SetMvWithinMvRange( kiMbWidth, kiMbHeight, kiMbX, kiMbY, CAMERA_STARTMV_RANGE, &(pSlice->sMvStartMin), &(pSlice->sMvStartMax)); + SetMvWithinIntegerMvRange( kiMbWidth, kiMbHeight, kiMbX, kiMbY, CAMERA_STARTMV_RANGE, &(pSlice->sMvStartMin), &(pSlice->sMvStartMax)); } int32_t WelsMdI16x16 (SWelsFuncPtrList* pFunc, SDqLayer* pCurDqLayer, SMbCache* pMbCache, int32_t iLambda) { @@ -975,7 +975,7 @@ static inline void InitMe(const SWelsMD& sWelsMd, const int32_t iBlockSize, uint { sWelsMe.iCurMeBlockPixX = sWelsMd.iMbPixX; sWelsMe.iCurMeBlockPixY = sWelsMd.iMbPixY; - sWelsMe.uiPixel = iBlockSize; + sWelsMe.uiBlockSize = iBlockSize; sWelsMe.pMvdCost = sWelsMd.pMvdCost; sWelsMe.pEncMb = pEnc; diff --git a/codec/encoder/core/src/svc_motion_estimate.cpp b/codec/encoder/core/src/svc_motion_estimate.cpp index 3bff4c80..a11af6c3 100644 --- a/codec/encoder/core/src/svc_motion_estimate.cpp +++ b/codec/encoder/core/src/svc_motion_estimate.cpp @@ -81,7 +81,7 @@ void WelsMotionEstimateSearch (SWelsFuncPtrList* pFuncList, void* pLplayer, void MeEndIntepelSearch(pMe); } - pFuncList->pfCalculateSatd( pFuncList->sSampleDealingFuncs.pfSampleSatd[pMe->uiPixel], pMe, iStrideEnc, iStrideRef ); + pFuncList->pfCalculateSatd( pFuncList->sSampleDealingFuncs.pfSampleSatd[pMe->uiBlockSize], pMe, iStrideEnc, iStrideRef ); } /*! @@ -96,7 +96,7 @@ void WelsMotionEstimateSearch (SWelsFuncPtrList* pFuncList, void* pLplayer, void */ bool WelsMotionEstimateInitialPoint (SWelsFuncPtrList* pFuncList, SWelsME* pMe, SSlice* pSlice, int32_t iStrideEnc, int32_t iStrideRef) { - PSampleSadSatdCostFunc pSad = pFuncList->sSampleDealingFuncs.pfSampleSad[pMe->uiPixel]; + PSampleSadSatdCostFunc pSad = pFuncList->sSampleDealingFuncs.pfSampleSad[pMe->uiBlockSize]; const uint16_t* kpMvdCost = pMe->pMvdCost; uint8_t* const kpEncMb = pMe->pEncMb; int16_t iMvc0, iMvc1; @@ -197,7 +197,7 @@ bool WelsMeSadCostSelect (int32_t* iSadCost, const uint16_t* kpMvdCost, int32_t* void WelsMotionEstimateIterativeSearch (SWelsFuncPtrList* pFuncList, SWelsME* pMe, const int32_t kiStrideEnc, const int32_t kiStrideRef, uint8_t* pFref) { - PSample4SadCostFunc pSad = pFuncList->sSampleDealingFuncs.pfSample4Sad[pMe->uiPixel]; + PSample4SadCostFunc pSad = pFuncList->sSampleDealingFuncs.pfSample4Sad[pMe->uiBlockSize]; uint8_t* const kpEncMb = pMe->pEncMb; const uint16_t* kpMvdCost = pMe->pMvdCost; @@ -255,7 +255,7 @@ bool CheckDirectionalMv(PSampleSadSatdCostFunc pSad, void * vpMe, const int16_t kiMvY = pMe->sDirectionalMv.iMvY; //Check MV from scrolling detection - if ( (BLOCK_16x16!=pMe->uiPixel) //scrolled_MV with P16x16 is checked SKIP checking function + if ( (BLOCK_16x16!=pMe->uiBlockSize) //scrolled_MV with P16x16 is checked SKIP checking function && ( kiMvX | kiMvY ) //(0,0) checked in ordinary initial point checking && CheckMvInRange( pMe->sDirectionalMv, ksMinMv, ksMaxMv ) ) { @@ -278,4 +278,70 @@ bool CheckDirectionalMvFalse(PSampleSadSatdCostFunc pSad, void * vpMe, return false; } +void LineFullSearch_c( PSampleSadSatdCostFunc pSad, void *vpMe, + uint16_t* pMvdTable, const int32_t kiFixedMvd, + const int32_t kiEncStride, const int32_t kiRefStride, + const int32_t kiMinPos, const int32_t kiMaxPos, + const bool bVerticalSearch ) +{ + SWelsME *pMe = static_cast(vpMe); + const int32_t kiCurMeBlockPix = bVerticalSearch?pMe->iCurMeBlockPixY:pMe->iCurMeBlockPixX; + const int32_t kiStride = bVerticalSearch?kiRefStride:1; + uint8_t* pRef = &pMe->pColoRefMb[(kiMinPos - kiCurMeBlockPix)*kiStride]; + uint16_t* pMvdCost = &(pMvdTable[kiMinPos<<2]); + uint32_t uiBestCost = 0xFFFFFFFF; + int32_t iBestPos = 0; + + for ( int32_t iTargetPos = kiMinPos; iTargetPos < kiMaxPos; ++ iTargetPos ) { + uint8_t* const kpEncMb = pMe->pEncMb; + uint32_t uiSadCost = pSad( kpEncMb, kiEncStride, pRef, kiRefStride ) + (kiFixedMvd + *pMvdCost); + if (uiSadCost < uiBestCost) { + uiBestCost = uiSadCost; + iBestPos = iTargetPos; + } + pRef += kiStride; + pMvdCost+=4; + } + + if (uiBestCost < pMe->uiSadCost) { + SMVUnitXY sBestMv; + sBestMv.iMvX = bVerticalSearch?0:(iBestPos - kiCurMeBlockPix); + sBestMv.iMvY = bVerticalSearch?(iBestPos - kiCurMeBlockPix):0; + UpdateMeResults( sBestMv, uiBestCost, &pMe->pColoRefMb[sBestMv.iMvY*kiStride], pMe ); + } +} + +void WelsMotionCrossSearch(SWelsFuncPtrList *pFuncList, SDqLayer* pCurLayer, SWelsME * pMe, + const SSlice* pSlice) +{ + //TODO: + //PMOTION_VERFULL_SEARCH VerticalFullSearchFunc = pFuncList->VerticalFullSearch_c; + //PMOTION_HORFULL_SEARCH HorizontalFullSearchFunc = pFuncList->HorizontalFullSearch_c; + const int32_t kiEncStride = pCurLayer->iEncStride[0]; + const int32_t kiRefStride = pCurLayer->pRefPic->iLineSize[0]; + + const int32_t iCurMeBlockPixX = pMe->iCurMeBlockPixX; + const int32_t iCurMeBlockQpelPixX = ((iCurMeBlockPixX)<<2); + const int32_t iCurMeBlockPixY = pMe->iCurMeBlockPixY; + const int32_t iCurMeBlockQpelPixY = ((iCurMeBlockPixY)<<2); + uint16_t* pMvdCostX = pMe->pMvdCost - iCurMeBlockQpelPixX - pMe->sMvp.iMvX;//do the offset here instead of in the search + uint16_t* pMvdCostY = pMe->pMvdCost - iCurMeBlockQpelPixY - pMe->sMvp.iMvY;//do the offset here instead of in the search + + //vertical search + LineFullSearch_c( pFuncList->sSampleDealingFuncs.pfSampleSad[pMe->uiBlockSize], pMe, + pMvdCostY, pMvdCostX[ iCurMeBlockQpelPixX ], + kiEncStride, kiRefStride, + iCurMeBlockPixY + pSlice->sMvStartMin.iMvY, + iCurMeBlockPixY + pSlice->sMvStartMax.iMvY, true ); + + //horizontal search + if (pMe->uiSadCost >= pMe->uiSadCostThreshold) { + LineFullSearch_c( pFuncList->sSampleDealingFuncs.pfSampleSad[pMe->uiBlockSize], pMe, + pMvdCostX, pMvdCostY[ iCurMeBlockQpelPixY ], + kiEncStride, kiRefStride, + iCurMeBlockPixX + pSlice->sMvStartMin.iMvX, + iCurMeBlockPixX + pSlice->sMvStartMax.iMvX, + false ); + } +} } // namespace WelsSVCEnc diff --git a/test/encoder/EncUT_MotionEstimate.cpp b/test/encoder/EncUT_MotionEstimate.cpp index dd099eed..8c18adb2 100644 --- a/test/encoder/EncUT_MotionEstimate.cpp +++ b/test/encoder/EncUT_MotionEstimate.cpp @@ -104,13 +104,13 @@ TEST_F(MotionEstimateTest, TestDiamondSearch) CopyTargetBlock( m_pSrcBlock, 16, sTargetMv, m_iWidth, pRefPicCenter); //clean the sMe status - sMe.uiPixel = rand()%5; + sMe.uiBlockSize = rand()%5; sMe.pEncMb = m_pSrcBlock; sMe.pRefMb = pRefPicCenter; sMe.sMv.iMvX = sMe.sMv.iMvY = 0; sMe.uiSadCost = sMe.uiSatdCost = kiMaxBlock16Sad; WelsMotionEstimateIterativeSearch (&sFuncList, &sMe, m_iMaxSearchBlock, - m_iWidth, pRefPicCenter); + m_iWidth, pRefPicCenter); //the last selection may be affected by MVDcost, that is when (0,0) will be better //when comparing (1,1) and (1,0), due to the difference between MVD cost, it is possible that (1,0) is selected while the best match is (1,1) @@ -123,3 +123,129 @@ TEST_F(MotionEstimateTest, TestDiamondSearch) } } } + + +TEST_F(MotionEstimateTest, TestVerticalSearch) +{ + const int32_t kiMaxBlock16Sad = 72000;//a rough number + SWelsFuncPtrList sFuncList; + SWelsME sMe; + + srand((uint32_t)time(NULL)); + const uint8_t kuiQp = rand()%52; + InitMe(kuiQp, 648, m_uiMvdTableSize, m_pMvdCostTable, &sMe); + + SMVUnitXY sTargetMv; + WelsInitSampleSadFunc( &sFuncList, 0 );//test c functions + + uint8_t *pRefPicCenter = m_pRefPic+(m_iHeight/2)*m_iWidth+(m_iWidth/2); + sMe.iCurMeBlockPixX = (m_iWidth/2); + sMe.iCurMeBlockPixY = (m_iHeight/2); + + bool bDataGeneratorSucceed = false; + bool bFoundMatch = false; + int32_t iTryTimes=100; + + sTargetMv.iMvX = 0; + sTargetMv.iMvY = WELS_MAX(INTPEL_NEEDED_MARGIN, rand()%m_iHeight-INTPEL_NEEDED_MARGIN); + bDataGeneratorSucceed = false; + bFoundMatch = false; + while (!bFoundMatch && (iTryTimes--)>0) { + if (!YUVPixelDataGenerator( m_pRefPic, m_iWidth, m_iHeight, m_iWidth )) + continue; + + bDataGeneratorSucceed = true; + CopyTargetBlock( m_pSrcBlock, 16, sTargetMv, m_iWidth, pRefPicCenter); + + //clean the sMe status + sMe.uiBlockSize = rand()%5; + sMe.pEncMb = m_pSrcBlock; + sMe.pRefMb = pRefPicCenter; + sMe.pColoRefMb = pRefPicCenter; + sMe.sMv.iMvX = sMe.sMv.iMvY = 0; + sMe.uiSadCost = sMe.uiSatdCost = kiMaxBlock16Sad; + const int32_t iCurMeBlockPixX = sMe.iCurMeBlockPixX; + const int32_t iCurMeBlockQpelPixX = ((iCurMeBlockPixX)<<2); + const int32_t iCurMeBlockPixY = sMe.iCurMeBlockPixY; + const int32_t iCurMeBlockQpelPixY = ((iCurMeBlockPixY)<<2); + uint16_t* pMvdCostX = sMe.pMvdCost - iCurMeBlockQpelPixX - sMe.sMvp.iMvX; //do the offset here + uint16_t* pMvdCostY = sMe.pMvdCost - iCurMeBlockQpelPixY - sMe.sMvp.iMvY; + LineFullSearch_c ( sFuncList.sSampleDealingFuncs.pfSampleSad[sMe.uiBlockSize], &sMe, + pMvdCostY, pMvdCostX[ iCurMeBlockQpelPixX ], + m_iMaxSearchBlock, m_iWidth, + INTPEL_NEEDED_MARGIN, + m_iHeight-INTPEL_NEEDED_MARGIN, true ); + + //the last selection may be affected by MVDcost, that is when smaller MvY will be better + bFoundMatch = (sMe.sMv.iMvX==0 + &&(sMe.sMv.iMvY==sTargetMv.iMvY||abs(sMe.sMv.iMvY) 0); + //it is possible that ref at differnt position is identical, but that should be under a low probability + } +} +TEST_F(MotionEstimateTest, TestHorizontalSearch) +{ + const int32_t kiMaxBlock16Sad = 72000;//a rough number + SWelsFuncPtrList sFuncList; + SWelsME sMe; + + srand((uint32_t)time(NULL)); + const uint8_t kuiQp = rand()%52; + InitMe(kuiQp, 648, m_uiMvdTableSize, m_pMvdCostTable, &sMe); + + SMVUnitXY sTargetMv; + WelsInitSampleSadFunc( &sFuncList, 0 );//test c functions + + uint8_t *pRefPicCenter = m_pRefPic+(m_iHeight/2)*m_iWidth+(m_iWidth/2); + sMe.iCurMeBlockPixX = (m_iWidth/2); + sMe.iCurMeBlockPixY = (m_iHeight/2); + + bool bDataGeneratorSucceed = false; + bool bFoundMatch = false; + int32_t iTryTimes=100; + + sTargetMv.iMvX = WELS_MAX(INTPEL_NEEDED_MARGIN, rand()%m_iWidth-INTPEL_NEEDED_MARGIN); + sTargetMv.iMvY = 0; + bDataGeneratorSucceed = false; + bFoundMatch = false; + while (!bFoundMatch && (iTryTimes--)>0) { + if (!YUVPixelDataGenerator( m_pRefPic, m_iWidth, m_iHeight, m_iWidth )) + continue; + + bDataGeneratorSucceed = true; + CopyTargetBlock( m_pSrcBlock, 16, sTargetMv, m_iWidth, pRefPicCenter); + + //clean the sMe status + sMe.uiBlockSize = rand()%5; + sMe.pEncMb = m_pSrcBlock; + sMe.pRefMb = pRefPicCenter; + sMe.pColoRefMb = pRefPicCenter; + sMe.sMv.iMvX = sMe.sMv.iMvY = 0; + sMe.uiSadCost = sMe.uiSatdCost = kiMaxBlock16Sad; + const int32_t iCurMeBlockPixX = sMe.iCurMeBlockPixX; + const int32_t iCurMeBlockQpelPixX = ((iCurMeBlockPixX)<<2); + const int32_t iCurMeBlockPixY = sMe.iCurMeBlockPixY; + const int32_t iCurMeBlockQpelPixY = ((iCurMeBlockPixY)<<2); + uint16_t* pMvdCostX = sMe.pMvdCost - iCurMeBlockQpelPixX - sMe.sMvp.iMvX; //do the offset here + uint16_t* pMvdCostY = sMe.pMvdCost - iCurMeBlockQpelPixY - sMe.sMvp.iMvY; + LineFullSearch_c ( sFuncList.sSampleDealingFuncs.pfSampleSad[sMe.uiBlockSize], &sMe, + pMvdCostX, pMvdCostY[ iCurMeBlockQpelPixY ], + m_iMaxSearchBlock, m_iWidth, + INTPEL_NEEDED_MARGIN, + m_iWidth-INTPEL_NEEDED_MARGIN, false ); + + //the last selection may be affected by MVDcost, that is when smaller MvY will be better + bFoundMatch = (sMe.sMv.iMvY==0 + &&(sMe.sMv.iMvX==sTargetMv.iMvX||abs(sMe.sMv.iMvX) 0); + //it is possible that ref at differnt position is identical, but that should be under a low probability + } +} \ No newline at end of file