From 1752d8d7cd75b497f4ba2ba14a92a6380f4fdcd8 Mon Sep 17 00:00:00 2001
From: Yin Zhao <yin.zhao@huawei.com>
Date: Tue, 30 Jul 2019 21:49:42 +0800
Subject: [PATCH] JVET-O0050 encapsulated by JVET_O0050_LOCAL_DUAL_TREE

---
 source/Lib/CommonLib/AdaptiveLoopFilter.cpp   |   5 +-
 source/Lib/CommonLib/CodingStructure.cpp      | 191 +++++++++-
 source/Lib/CommonLib/CodingStructure.h        |  13 +
 source/Lib/CommonLib/CommonDef.h              |   4 +
 source/Lib/CommonLib/ContextModelling.cpp     |  17 +
 source/Lib/CommonLib/ContextModelling.h       |   3 +
 source/Lib/CommonLib/Contexts.cpp             |  10 +
 source/Lib/CommonLib/Contexts.h               |   3 +
 source/Lib/CommonLib/LoopFilter.cpp           |   4 +
 source/Lib/CommonLib/TypeDef.h                |  20 ++
 source/Lib/CommonLib/Unit.cpp                 |  24 +-
 source/Lib/CommonLib/Unit.h                   |  10 +
 source/Lib/CommonLib/UnitPartitioner.cpp      |  50 +++
 source/Lib/CommonLib/UnitPartitioner.h        |  19 +
 source/Lib/CommonLib/UnitTools.cpp            |  33 ++
 source/Lib/CommonLib/UnitTools.h              |   3 +
 .../Lib/CommonLib/dtrace_blockstatistics.cpp  |  12 +
 .../Lib/CommonLib/x86/AdaptiveLoopFilterX86.h |  10 +-
 source/Lib/DecoderLib/CABACReader.cpp         | 166 ++++++++-
 source/Lib/DecoderLib/CABACReader.h           |   3 +
 source/Lib/DecoderLib/DecCu.cpp               |   7 +
 source/Lib/EncoderLib/CABACWriter.cpp         | 168 ++++++++-
 source/Lib/EncoderLib/CABACWriter.h           |   3 +
 source/Lib/EncoderLib/EncCu.cpp               | 329 +++++++++++++++++-
 source/Lib/EncoderLib/EncCu.h                 |   8 +
 source/Lib/EncoderLib/EncModeCtrl.cpp         |  53 +++
 source/Lib/EncoderLib/InterSearch.cpp         |  16 +
 source/Lib/EncoderLib/IntraSearch.cpp         |  89 +++++
 source/Lib/EncoderLib/IntraSearch.h           |  16 +
 29 files changed, 1271 insertions(+), 18 deletions(-)

diff --git a/source/Lib/CommonLib/AdaptiveLoopFilter.cpp b/source/Lib/CommonLib/AdaptiveLoopFilter.cpp
index f99bf8b3b..34dd64d77 100644
--- a/source/Lib/CommonLib/AdaptiveLoopFilter.cpp
+++ b/source/Lib/CommonLib/AdaptiveLoopFilter.cpp
@@ -1107,10 +1107,13 @@ void AdaptiveLoopFilter::filterBlk(AlfClassifier** classifier, const PelUnitBuf
           for( blkX=0; blkX<4; blkX+=2 )
           {
             Position pos(j + blkDst.x + blkX, i + blkDst.y + blkY);
-#if JVET_O0090_ALF_CHROMA_FILTER_ALTERNATIVES_CTB
+#if JVET_O0090_ALF_CHROMA_FILTER_ALTERNATIVES_CTB && !JVET_O0050_LOCAL_DUAL_TREE
             const CodingUnit* cu = isDualTree ? cs.getCU(pos, CH_C) : cs.getCU(recalcPosition(nChromaFormat, CH_C, CH_L, pos), CH_L);
 #else
             CodingUnit* cu = isDualTree ? cs.getCU(pos, CH_C) : cs.getCU(recalcPosition(nChromaFormat, CH_C, CH_L, pos), CH_L);
+#endif
+#if JVET_O0050_LOCAL_DUAL_TREE
+            cu = cu->isSepTree() ? cs.getCU( pos, CH_C ) : cu;
 #endif
             *flags++ = cu->ipcm ? 1 : 0;
           }
diff --git a/source/Lib/CommonLib/CodingStructure.cpp b/source/Lib/CommonLib/CodingStructure.cpp
index 36d3109af..a20ca1ca3 100644
--- a/source/Lib/CommonLib/CodingStructure.cpp
+++ b/source/Lib/CommonLib/CodingStructure.cpp
@@ -86,7 +86,10 @@ CodingStructure::CodingStructure(CUCache& cuCache, PUCache& puCache, TUCache& tu
 
   m_motionBuf     = nullptr;
   features.resize( NUM_ENC_FEATURES );
-
+#if JVET_O0050_LOCAL_DUAL_TREE
+  treeType = TREE_D;
+  modeType = MODE_TYPE_ALL;
+#endif
 }
 
 void CodingStructure::destroy()
@@ -183,14 +186,102 @@ void CodingStructure::setDecomp(const UnitArea &_area, const bool _isCoded /*= t
   }
 }
 
+#if JVET_O0050_LOCAL_DUAL_TREE
+const int CodingStructure::signalModeCons( const PartSplit split, Partitioner &partitioner, const ModeType modeTypeParent ) const
+{
+  if( CS::isDualITree( *this ) || modeTypeParent != MODE_TYPE_ALL )
+    return 0;
+
+  int width = partitioner.currArea().lwidth();
+  int height = partitioner.currArea().lheight();
+
+  //0: not constrain
+  //1: constrain with intra, no need signaling the flag
+  //2: constrain with intra or inter, need signaling the flag
+  if( width * height == 64 )
+  {
+    if( split == CU_QUAD_SPLIT || split == CU_TRIH_SPLIT || split == CU_TRIV_SPLIT ) // qt or tt
+      return slice->isIntra() ? 1 : 1; //only intra mode allowed for child nodes (have 4x4)
+    else // bt
+      return slice->isIntra() ? 1 : 2;
+  }
+  else if( width * height == 128 )
+  {
+    if( split == CU_TRIH_SPLIT || split == CU_TRIV_SPLIT )
+      return slice->isIntra() ? 1 : 2;
+    else
+      return 0;
+  }
+  else
+  {
+    return 0;
+  }
+}
+
+void CodingStructure::clearCuPuTuIdxMap( const UnitArea &_area, uint32_t numCu, uint32_t numPu, uint32_t numTu, uint32_t* pOffset )
+{
+  UnitArea clippedArea = clipArea( _area, *picture );
+  uint32_t numCh = ::getNumberValidChannels( _area.chromaFormat );
+  for( uint32_t i = 0; i < numCh; i++ )
+  {
+    const CompArea &_selfBlk = area.blocks[i];
+    const CompArea     &_blk = clippedArea.blocks[i];
+
+    const UnitScale& scale = unitScale[_blk.compID];
+    const Area scaledSelf = scale.scale( _selfBlk );
+    const Area scaledBlk = scale.scale( _blk );
+    const size_t offset = rsAddr( scaledBlk.pos(), scaledSelf.pos(), scaledSelf.width );
+    unsigned *idxPtrCU = m_cuIdx[i] + offset;
+    AreaBuf<uint32_t>( idxPtrCU, scaledSelf.width, scaledBlk.size() ).fill( 0 );
+
+    unsigned *idxPtrPU = m_puIdx[i] + offset;
+    AreaBuf<uint32_t>( idxPtrPU, scaledSelf.width, scaledBlk.size() ).fill( 0 );
+
+    unsigned *idxPtrTU = m_tuIdx[i] + offset;
+    AreaBuf<uint32_t>( idxPtrTU, scaledSelf.width, scaledBlk.size() ).fill( 0 );
+  }
+
+  //pop cu/pu/tus
+  for( int i = m_numTUs; i > numTu; i-- )
+  {
+    m_tuCache.cache( tus.back() );
+    tus.pop_back();
+    m_numTUs--;
+  }
+  for( int i = m_numPUs; i > numPu; i-- )
+  {
+    m_puCache.cache( pus.back() );
+    pus.pop_back();
+    m_numPUs--;
+  }
+  for( int i = m_numCUs; i > numCu; i-- )
+  {
+    m_cuCache.cache( cus.back() );
+    cus.pop_back();
+    m_numCUs--;
+  }
+  for( int i = 0; i < 3; i++ )
+  {
+    m_offsets[i] = pOffset[i];
+  }
+}
+#endif
 
 
 CodingUnit* CodingStructure::getCU( const Position &pos, const ChannelType effChType )
 {
   const CompArea &_blk = area.blocks[effChType];
 
+#if JVET_O0050_LOCAL_DUAL_TREE
+  if( !_blk.contains( pos ) || (treeType == TREE_C && effChType == CHANNEL_TYPE_LUMA) )
+#else
   if( !_blk.contains( pos ) )
+#endif
   {
+#if JVET_O0050_LOCAL_DUAL_TREE
+    if( treeType == TREE_C && effChType == CHANNEL_TYPE_LUMA )
+      CHECK( parent->treeType != TREE_D, "wrong parent treeType " );
+#endif
     if( parent ) return parent->getCU( pos, effChType );
     else         return nullptr;
   }
@@ -207,8 +298,16 @@ const CodingUnit* CodingStructure::getCU( const Position &pos, const ChannelType
 {
   const CompArea &_blk = area.blocks[effChType];
 
+#if JVET_O0050_LOCAL_DUAL_TREE
+  if( !_blk.contains( pos ) || (treeType == TREE_C && effChType == CHANNEL_TYPE_LUMA) )
+#else
   if( !_blk.contains( pos ) )
+#endif
   {
+#if JVET_O0050_LOCAL_DUAL_TREE
+    if( treeType == TREE_C && effChType == CHANNEL_TYPE_LUMA )
+      CHECK( parent->treeType != TREE_D, "wrong parent treeType" );
+#endif
     if( parent ) return parent->getCU( pos, effChType );
     else         return nullptr;
   }
@@ -289,6 +388,10 @@ TransformUnit* CodingStructure::getTU( const Position &pos, const ChannelType ef
             while( !tus[idx - 1 + extraIdx]->blocks[getFirstComponentOfChannel( effChType )].contains( pos ) )
             {
               extraIdx++;
+#if JVET_O0050_LOCAL_DUAL_TREE
+              CHECK( tus[idx - 1 + extraIdx]->cu->treeType == TREE_C, "tu searched by position points to a chroma tree CU" );
+              CHECK( extraIdx > 3, "extraIdx > 3" );
+#endif
             }
           }
         }
@@ -330,6 +433,10 @@ const TransformUnit * CodingStructure::getTU( const Position &pos, const Channel
             while ( !tus[idx - 1 + extraIdx]->blocks[getFirstComponentOfChannel( effChType )].contains(pos) )
             {
               extraIdx++;
+#if JVET_O0050_LOCAL_DUAL_TREE
+              CHECK( tus[idx - 1 + extraIdx]->cu->treeType == TREE_C, "tu searched by position points to a chroma tree CU" );
+              CHECK( extraIdx > 3, "extraIdx > 3" );
+#endif
             }
           }
         }
@@ -355,6 +462,10 @@ CodingUnit& CodingStructure::addCU( const UnitArea &unit, const ChannelType chTy
   cu->firstTU   = nullptr;
   cu->lastTU    = nullptr;
   cu->chType    = chType;
+#if JVET_O0050_LOCAL_DUAL_TREE
+  cu->treeType = treeType;
+  cu->modeType = modeType;
+#endif
 
   CodingUnit *prevCU = m_numCUs > 0 ? cus.back() : nullptr;
 
@@ -551,8 +662,45 @@ CUTraverser CodingStructure::traverseCUs( const UnitArea& unit, const ChannelTyp
 {
   CodingUnit* firstCU = getCU( isLuma( effChType ) ? unit.lumaPos() : unit.chromaPos(), effChType );
   CodingUnit* lastCU = firstCU;
-
+#if JVET_O0050_LOCAL_DUAL_TREE
+  if( !CS::isDualITree( *this ) ) //for a more generalized separate tree
+  {
+    bool bContinue = true;
+    CodingUnit* currCU = firstCU;
+    while( bContinue )
+    {
+      if( currCU == nullptr )
+      {
+        bContinue = false;
+        lastCU = currCU;
+      }
+      else if( currCU->chType != effChType )
+      {
+        lastCU = currCU;
+        currCU = currCU->next;
+      }
+      else
+      {
+        if( unit.contains( *currCU ) )
+        {
+          lastCU = currCU;
+          currCU = currCU->next;
+        }
+        else
+        {
+          bContinue = false;
+          lastCU = currCU;
+        }
+      }
+    }
+  }
+  else
+  {
+#endif
   do { } while( lastCU && ( lastCU = lastCU->next ) && unit.contains( *lastCU ) );
+#if JVET_O0050_LOCAL_DUAL_TREE
+  }
+#endif
 
   return CUTraverser( firstCU, lastCU );
 }
@@ -784,6 +932,11 @@ void CodingStructure::initSubStructure( CodingStructure& subStruct, const Channe
 
   subStruct.motionLut = motionLut;
 
+#if JVET_O0050_LOCAL_DUAL_TREE
+  subStruct.treeType  = treeType;
+  subStruct.modeType  = modeType;
+#endif
+
   subStruct.initStructData( currQP[_chType], isLossless );
 
   if( isTuEnc )
@@ -872,8 +1025,11 @@ void CodingStructure::useSubStructure( const CodingStructure& subStruct, const C
         {
           // add an analogue CU into own CU store
           const UnitArea &cuPatch = *pcu;
-
+#if JVET_O0050_LOCAL_DUAL_TREE
+          CodingUnit &cu = addCU( cuPatch, pcu->chType );
+#else
           CodingUnit &cu = addCU( cuPatch, chType );
+#endif
 
           // copy the CU info from subPatch
           cu = *pcu;
@@ -891,8 +1047,11 @@ void CodingStructure::useSubStructure( const CodingStructure& subStruct, const C
         {
           // add an analogue PU into own PU store
           const UnitArea &puPatch = *ppu;
-
+#if JVET_O0050_LOCAL_DUAL_TREE
+          PredictionUnit &pu = addPU( puPatch, ppu->chType );
+#else   
           PredictionUnit &pu = addPU( puPatch, chType );
+#endif
 
           // copy the PU info from subPatch
           pu = *ppu;
@@ -903,8 +1062,11 @@ void CodingStructure::useSubStructure( const CodingStructure& subStruct, const C
       {
         // add an analogue TU into own TU store
         const UnitArea &tuPatch = *ptu;
-
+#if JVET_O0050_LOCAL_DUAL_TREE
+        TransformUnit &tu = addTU( tuPatch, ptu->chType );
+#else
         TransformUnit &tu = addTU( tuPatch, chType );
+#endif
 
         // copy the TU info from subPatch
         tu = *ptu;
@@ -936,8 +1098,11 @@ void CodingStructure::useSubStructure( const CodingStructure& subStruct, const C
     {
       // add an analogue CU into own CU store
       const UnitArea &cuPatch = *pcu;
-
+#if JVET_O0050_LOCAL_DUAL_TREE
+      CodingUnit &cu = addCU( cuPatch, pcu->chType );
+#else
       CodingUnit &cu = addCU( cuPatch, chType );
+#endif
 
       // copy the CU info from subPatch
       cu = *pcu;
@@ -955,8 +1120,11 @@ void CodingStructure::useSubStructure( const CodingStructure& subStruct, const C
     {
       // add an analogue PU into own PU store
       const UnitArea &puPatch = *ppu;
-
+#if JVET_O0050_LOCAL_DUAL_TREE
+      PredictionUnit &pu = addPU( puPatch, ppu->chType );
+#else   
       PredictionUnit &pu = addPU( puPatch, chType );
+#endif
 
       // copy the PU info from subPatch
       pu = *ppu;
@@ -967,8 +1135,11 @@ void CodingStructure::useSubStructure( const CodingStructure& subStruct, const C
   {
     // add an analogue TU into own TU store
     const UnitArea &tuPatch = *ptu;
-
+#if JVET_O0050_LOCAL_DUAL_TREE
+    TransformUnit &tu = addTU( tuPatch, ptu->chType );
+#else
     TransformUnit &tu = addTU( tuPatch, chType );
+#endif
 
     // copy the TU info from subPatch
     tu = *ptu;
@@ -1420,7 +1591,11 @@ IbcLumaCoverage CodingStructure::getIbcLumaCoverage(const CompArea& chromaArea)
     for (SizeType x = 0; x < lumaArea.width; x += MIN_PU_SIZE)
     {
       Position pos = lumaArea.offset(x, y);
+#if JVET_O0050_LOCAL_DUAL_TREE
+      if (picture->cs->getMotionInfo(pos).isInter && picture->cs->getMotionInfo(pos).isIBCmot)
+#else
       if (picture->cs->getMotionInfo(pos).isInter) // need to change if inter slice allows dualtree
+#endif
       {
         ibcArea += unitAreaSubBlock;
       }
diff --git a/source/Lib/CommonLib/CodingStructure.h b/source/Lib/CommonLib/CodingStructure.h
index aa41d9611..8adfbc068 100644
--- a/source/Lib/CommonLib/CodingStructure.h
+++ b/source/Lib/CommonLib/CodingStructure.h
@@ -171,6 +171,10 @@ public:
   uint64_t      fracBits;
   Distortion  dist;
   Distortion  interHad;
+#if JVET_O0050_LOCAL_DUAL_TREE
+  TreeType    treeType; //because partitioner can not go deep to tu and cu coding (e.g., addCU()), need another variable for indicating treeType
+  ModeType    modeType;
+#endif
 
   void initStructData  (const int &QP = MAX_INT, const bool &_isLosses = false, const bool &skipMotBuf = false);
   void initSubStructure(      CodingStructure& cs, const ChannelType chType, const UnitArea &subArea, const bool &isTuEnc);
@@ -182,6 +186,15 @@ public:
   void clearTUs();
   void clearPUs();
   void clearCUs();
+#if JVET_O0050_LOCAL_DUAL_TREE
+  const int signalModeCons( const PartSplit split, Partitioner &partitioner, const ModeType modeTypeParent ) const;
+  void clearCuPuTuIdxMap  ( const UnitArea &_area, uint32_t numCu, uint32_t numPu, uint32_t numTu, uint32_t* pOffset );
+  void getNumCuPuTuOffset ( uint32_t* pArray )
+  {
+    pArray[0] = m_numCUs;     pArray[1] = m_numPUs;     pArray[2] = m_numTUs;
+    pArray[3] = m_offsets[0]; pArray[4] = m_offsets[1]; pArray[5] = m_offsets[2];
+  }
+#endif
 
 
 private:
diff --git a/source/Lib/CommonLib/CommonDef.h b/source/Lib/CommonLib/CommonDef.h
index ec7e37790..e72c01bba 100644
--- a/source/Lib/CommonLib/CommonDef.h
+++ b/source/Lib/CommonLib/CommonDef.h
@@ -447,6 +447,10 @@ static const int SBT_MAX_SIZE =                                    64; ///< maxi
 static const int SBT_NUM_SL =                                      10; ///< maximum number of historical PU decision saved for a CU
 static const int SBT_NUM_RDO =                                      2; ///< maximum number of SBT mode tried for a PU
 
+#if JVET_O0050_LOCAL_DUAL_TREE
+static const int NUM_INTER_CU_INFO_SAVE =                           8; ///< maximum number of inter cu information saved for fast algorithm
+#endif
+
 static const int IBC_MAX_CAND_SIZE = 16; // max block size for ibc search
 static const int IBC_NUM_CANDIDATES = 64; ///< Maximum number of candidates to store/test
 static const int CHROMA_REFINEMENT_CANDIDATES = 8; /// 8 candidates BV to choose from
diff --git a/source/Lib/CommonLib/ContextModelling.cpp b/source/Lib/CommonLib/ContextModelling.cpp
index 5db6cf368..79872a0bb 100644
--- a/source/Lib/CommonLib/ContextModelling.cpp
+++ b/source/Lib/CommonLib/ContextModelling.cpp
@@ -134,7 +134,24 @@ void CoeffCodingContext::initSubblock( int SubsetId, bool sigGroupFlag )
 }
 
 
+#if JVET_O0050_LOCAL_DUAL_TREE
+unsigned DeriveCtx::CtxModeConsFlag( const CodingStructure& cs, Partitioner& partitioner )
+{
+  assert( partitioner.chType == CHANNEL_TYPE_LUMA );
+  const Position pos = partitioner.currArea().blocks[partitioner.chType];
+  const unsigned curSliceIdx = cs.slice->getIndependentSliceIdx();
+  const unsigned curTileIdx = cs.picture->brickMap->getBrickIdxRsMap( partitioner.currArea().lumaPos() );
+
+  // get left depth
+  const CodingUnit* cuLeft = cs.getCURestricted( pos.offset( -1, 0 ), pos, curSliceIdx, curTileIdx, partitioner.chType );
+
+  // get above depth
+  const CodingUnit* cuAbove = cs.getCURestricted( pos.offset( 0, -1 ), pos, curSliceIdx, curTileIdx, partitioner.chType );
 
+  unsigned ctxId = ((cuAbove && cuAbove->predMode == MODE_INTRA) || (cuLeft && cuLeft->predMode == MODE_INTRA)) ? 1 : 0;
+  return ctxId;
+}
+#endif
 
 
 void DeriveCtx::CtxSplit( const CodingStructure& cs, Partitioner& partitioner, unsigned& ctxSpl, unsigned& ctxQt, unsigned& ctxHv, unsigned& ctxHorBt, unsigned& ctxVerBt, bool* _canSplit /*= nullptr */ )
diff --git a/source/Lib/CommonLib/ContextModelling.h b/source/Lib/CommonLib/ContextModelling.h
index 41190b2e1..2c86e0604 100644
--- a/source/Lib/CommonLib/ContextModelling.h
+++ b/source/Lib/CommonLib/ContextModelling.h
@@ -501,6 +501,9 @@ public:
 namespace DeriveCtx
 {
 void     CtxSplit     ( const CodingStructure& cs, Partitioner& partitioner, unsigned& ctxSpl, unsigned& ctxQt, unsigned& ctxHv, unsigned& ctxHorBt, unsigned& ctxVerBt, bool* canSplit = nullptr );
+#if JVET_O0050_LOCAL_DUAL_TREE
+unsigned CtxModeConsFlag( const CodingStructure& cs, Partitioner& partitioner );
+#endif
 #if JVET_O0193_REMOVE_TR_DEPTH_IN_CBF_CTX
 unsigned CtxQtCbf     ( const ComponentID compID, const bool prevCbf = false, const int ispIdx = 0 );
 #else
diff --git a/source/Lib/CommonLib/Contexts.cpp b/source/Lib/CommonLib/Contexts.cpp
index 45325644e..9e030c432 100644
--- a/source/Lib/CommonLib/Contexts.cpp
+++ b/source/Lib/CommonLib/Contexts.cpp
@@ -240,6 +240,16 @@ const CtxSet ContextSetCfg::Split12Flag = ContextSetCfg::addCtxSet
   {  12,  12,  12,  12, },
 });
 
+#if JVET_O0050_LOCAL_DUAL_TREE
+const CtxSet ContextSetCfg::ModeConsFlag = ContextSetCfg::addCtxSet
+({
+  { 192, 168, },
+  { 179, 139, },
+  { CNU, CNU, },
+  {   5,   2, },
+});
+#endif
+
 const CtxSet ContextSetCfg::SkipFlag = ContextSetCfg::addCtxSet
 ({
   { 197, 214, 216, },
diff --git a/source/Lib/CommonLib/Contexts.h b/source/Lib/CommonLib/Contexts.h
index db203e802..73b92b6fd 100644
--- a/source/Lib/CommonLib/Contexts.h
+++ b/source/Lib/CommonLib/Contexts.h
@@ -201,6 +201,9 @@ public:
   static const CtxSet   SplitQtFlag;
   static const CtxSet   SplitHvFlag;
   static const CtxSet   Split12Flag;
+#if JVET_O0050_LOCAL_DUAL_TREE
+  static const CtxSet   ModeConsFlag;
+#endif
   static const CtxSet   SkipFlag;
   static const CtxSet   MergeFlag;
   static const CtxSet   RegularMergeFlag;
diff --git a/source/Lib/CommonLib/LoopFilter.cpp b/source/Lib/CommonLib/LoopFilter.cpp
index e0e76f57c..0313b9d99 100644
--- a/source/Lib/CommonLib/LoopFilter.cpp
+++ b/source/Lib/CommonLib/LoopFilter.cpp
@@ -655,7 +655,11 @@ unsigned LoopFilter::xGetBoundaryStrengthSingle ( const CodingUnit& cu, const De
   }
 
   const TransformUnit& tuQ = *cuQ.cs->getTU(posQ, cuQ.chType);
+#if JVET_O0050_LOCAL_DUAL_TREE
+  const TransformUnit& tuP = *cuP.cs->getTU(posP, cuQ.chType); //based on chType of the current cu, because cuQ.chType and cuP.chType are not the same when local dual-tree is applied
+#else
   const TransformUnit& tuP = *cuP.cs->getTU(posP, cuP.chType);
+#endif
   const PreCalcValues& pcv = *cu.cs->pcv;
   const unsigned rasterIdx = getRasterIdx( Position{ localPos.x,  localPos.y }, pcv );
   if (m_aapucBS[edgeDir][rasterIdx] && (cuP.firstPU->mhIntraFlag || cuQ.firstPU->mhIntraFlag))
diff --git a/source/Lib/CommonLib/TypeDef.h b/source/Lib/CommonLib/TypeDef.h
index 811be421c..d99eeb8f2 100644
--- a/source/Lib/CommonLib/TypeDef.h
+++ b/source/Lib/CommonLib/TypeDef.h
@@ -89,6 +89,7 @@
 #define JVET_N0288_PROPOSAL1                              1   // JVET-N0288 Proposal 1
 
 #define JVET_O0090_ALF_CHROMA_FILTER_ALTERNATIVES_CTB     1 // JVET-O0090 test 2: CTB selection of ALF alternative chroma filters
+#define JVET_O0050_LOCAL_DUAL_TREE                        1 // JVET-O0050: avoid small intra chroma block by a "local dual-tree" technique
 
 #define JVET_O0216_ALF_COEFF_EG3                          1 // JVET-O0216/O0302/O0648: using EG3 for ALF coefficients coding
 
@@ -381,6 +382,9 @@ typedef       uint32_t            Intermediate_UInt; ///< used as intermediate v
 #endif
 
 typedef       uint64_t          SplitSeries;       ///< used to encoded the splits that caused a particular CU size
+#if JVET_O0050_LOCAL_DUAL_TREE
+typedef       uint64_t          ModeTypeSeries;    ///< used to encoded the ModeType at different split depth
+#endif
 
 typedef       uint64_t        Distortion;        ///< distortion measurement
 
@@ -502,6 +506,22 @@ enum ChannelType
   MAX_NUM_CHANNEL_TYPE = 2
 };
 
+#if JVET_O0050_LOCAL_DUAL_TREE
+enum TreeType
+{
+  TREE_D = 0, //default tree status (for single-tree slice, TREE_D means joint tree; for dual-tree I slice, TREE_D means TREE_L for luma and TREE_C for chroma)
+  TREE_L = 1, //separate tree only contains luma (may split)
+  TREE_C = 2, //separate tree only contains chroma (not split), to avoid small chroma block
+};
+
+enum ModeType
+{
+  MODE_TYPE_ALL = 0, //all modes can try
+  MODE_TYPE_INTER = 1, //can try inter
+  MODE_TYPE_INTRA = 2, //can try intra, ibc, palette
+};
+#endif
+
 #define CH_L CHANNEL_TYPE_LUMA
 #define CH_C CHANNEL_TYPE_CHROMA
 
diff --git a/source/Lib/CommonLib/Unit.cpp b/source/Lib/CommonLib/Unit.cpp
index 74a3cc0ec..b83315d6d 100644
--- a/source/Lib/CommonLib/Unit.cpp
+++ b/source/Lib/CommonLib/Unit.cpp
@@ -287,7 +287,11 @@ CodingUnit& CodingUnit::operator=( const CodingUnit& other )
   smvdMode        = other.smvdMode;
   ispMode           = other.ispMode;
   mipFlag           = other.mipFlag;
-
+#if JVET_O0050_LOCAL_DUAL_TREE
+  treeType          = other.treeType;
+  modeType          = other.modeType;
+  modeTypeSeries    = other.modeTypeSeries;
+#endif
   return *this;
 }
 
@@ -325,14 +329,30 @@ void CodingUnit::initData()
   smvdMode        = 0;
   ispMode           = 0;
   mipFlag           = false;
+#if JVET_O0050_LOCAL_DUAL_TREE
+  treeType          = TREE_D;
+  modeType          = MODE_TYPE_ALL;
+  modeTypeSeries    = 0;
+#endif
+}
+
+#if JVET_O0050_LOCAL_DUAL_TREE
+const bool CodingUnit::isSepTree() const
+{
+  return treeType != TREE_D || CS::isDualITree( *cs );
 }
+#endif
 
 #if JVET_O1124_ALLOW_CCLM_COND
 const bool CodingUnit::checkCCLMAllowed() const
 {
   bool allowCCLM = false;
 
-  if( chType != CHANNEL_TYPE_CHROMA ) //single tree
+#if JVET_O0050_LOCAL_DUAL_TREE
+  if( !CS::isDualITree( *cs ) ) //single tree I slice or non-I slice (Note: judging chType is no longer equivalent to checking dual-tree I slice since the local dual-tree is introduced)
+#else
+  if( chType != CHANNEL_TYPE_CHROMA ) //single tree I slice or non-I slice
+#endif
   {
     allowCCLM = true;
   }
diff --git a/source/Lib/CommonLib/Unit.h b/source/Lib/CommonLib/Unit.h
index 166454005..7c8370303 100644
--- a/source/Lib/CommonLib/Unit.h
+++ b/source/Lib/CommonLib/Unit.h
@@ -298,6 +298,11 @@ struct CodingUnit : public UnitArea
   int8_t          chromaQpAdj;
   int8_t          qp;
   SplitSeries    splitSeries;
+#if JVET_O0050_LOCAL_DUAL_TREE
+  TreeType       treeType;
+  ModeType       modeType;
+  ModeTypeSeries modeTypeSeries;
+#endif
   bool           skip;
   bool           mmvdSkip;
   bool           affine;
@@ -353,6 +358,11 @@ struct CodingUnit : public UnitArea
 #if JVET_O1124_ALLOW_CCLM_COND
   const bool        checkCCLMAllowed() const;
 #endif
+#if JVET_O0050_LOCAL_DUAL_TREE
+  const bool        isSepTree() const;
+  const bool        isConsInter() const { return modeType == MODE_TYPE_INTER; }
+  const bool        isConsIntra() const { return modeType == MODE_TYPE_INTRA; }
+#endif
 };
 
 // ---------------------------------------------------------------------------
diff --git a/source/Lib/CommonLib/UnitPartitioner.cpp b/source/Lib/CommonLib/UnitPartitioner.cpp
index 3309c07d9..acc12ded5 100644
--- a/source/Lib/CommonLib/UnitPartitioner.cpp
+++ b/source/Lib/CommonLib/UnitPartitioner.cpp
@@ -106,6 +106,29 @@ SplitSeries Partitioner::getSplitSeries() const
   return splitSeries;
 }
 
+#if JVET_O0050_LOCAL_DUAL_TREE
+ModeTypeSeries Partitioner::getModeTypeSeries() const
+{
+  ModeTypeSeries modeTypeSeries = 0;
+  int depth = 0;
+
+  for( const auto &level : m_partStack )
+  {
+    if( level.split == CTU_LEVEL ) continue;
+    else modeTypeSeries += static_cast<int>(level.modeType) << (depth * 3);
+
+    depth++;
+  }
+
+  return modeTypeSeries;
+}
+
+bool Partitioner::isSepTree( const CodingStructure &cs )
+{
+  return treeType != TREE_D || CS::isDualITree( cs );
+}
+#endif
+
 void Partitioner::setCUData( CodingUnit& cu )
 {
   cu.depth       = currDepth;
@@ -113,6 +136,9 @@ void Partitioner::setCUData( CodingUnit& cu )
   cu.mtDepth     = currMtDepth;
   cu.qtDepth     = currQtDepth;
   cu.splitSeries = getSplitSeries();
+#if JVET_O0050_LOCAL_DUAL_TREE
+  cu.modeTypeSeries = getModeTypeSeries();
+#endif
 }
 
 void Partitioner::copyState( const Partitioner& other )
@@ -234,6 +260,10 @@ void QTBTPartitioner::initCtu( const UnitArea& ctuArea, const ChannelType _chTyp
 
   m_partStack.clear();
   m_partStack.push_back( PartLevel( CTU_LEVEL, Partitioning{ ctuArea } ) );
+#if JVET_O0050_LOCAL_DUAL_TREE
+  treeType = TREE_D;
+  modeType = MODE_TYPE_ALL;
+#endif
 }
 
 void QTBTPartitioner::splitCurrArea( const PartSplit split, const CodingStructure& cs )
@@ -249,14 +279,23 @@ void QTBTPartitioner::splitCurrArea( const PartSplit split, const CodingStructur
   {
   case CU_QUAD_SPLIT:
     m_partStack.push_back( PartLevel( split, PartitionerImpl::getCUSubPartitions( currArea(), cs ) ) );
+#if JVET_O0050_LOCAL_DUAL_TREE
+    m_partStack.back().modeType = modeType;
+#endif
     break;
   case CU_HORZ_SPLIT:
   case CU_VERT_SPLIT:
     m_partStack.push_back( PartLevel( split, PartitionerImpl::getCUSubPartitions( currArea(), cs, split ) ) );
+#if JVET_O0050_LOCAL_DUAL_TREE
+    m_partStack.back().modeType = modeType;
+#endif
     break;
   case CU_TRIH_SPLIT:
   case CU_TRIV_SPLIT:
     m_partStack.push_back( PartLevel( split, PartitionerImpl::getCUSubPartitions( currArea(), cs, split ) ) );
+#if JVET_O0050_LOCAL_DUAL_TREE
+    m_partStack.back().modeType = modeType;
+#endif
     break;
   case TU_MAX_TR_SPLIT:
     m_partStack.push_back( PartLevel( split, PartitionerImpl::getMaxTuTiling( currArea(), cs ) ) );
@@ -354,6 +393,13 @@ void QTBTPartitioner::canSplit( const CodingStructure &cs, bool& canNo, bool& ca
   if( lastSplit != CTU_LEVEL && lastSplit != CU_QUAD_SPLIT ) canQt = false;
   if( area.width <= minQtSize )                              canQt = false;
   if( chType == CHANNEL_TYPE_CHROMA && areaC.width <= MIN_DUALTREE_CHROMA_WIDTH ) canQt = false;
+#if JVET_O0050_LOCAL_DUAL_TREE
+  if( treeType == TREE_C )
+  {
+    canQt = canBh = canTh = canBv = canTv = false;
+    return;
+  }
+#endif
   if( implicitSplit != CU_DONT_SPLIT )
   {
     canNo = canTh = canTv = false;
@@ -935,7 +981,11 @@ void PartitionerImpl::getTUIntraSubPartitions( Partitioning &sub, const UnitArea
   uint32_t nPartitions;
   uint32_t splitDimensionSize = CU::getISPSplitDim( tuArea.lumaSize().width, tuArea.lumaSize().height, splitType );
 
+#if JVET_O0050_LOCAL_DUAL_TREE
+  bool isDualTree = CS::isDualITree( cs ) || cs.treeType != TREE_D;
+#else
   bool isDualTree = CS::isDualITree( cs );
+#endif
 
   if( splitType == TU_1D_HORZ_SPLIT )
   {
diff --git a/source/Lib/CommonLib/UnitPartitioner.h b/source/Lib/CommonLib/UnitPartitioner.h
index 4fbe68f31..917c23a1b 100644
--- a/source/Lib/CommonLib/UnitPartitioner.h
+++ b/source/Lib/CommonLib/UnitPartitioner.h
@@ -94,6 +94,9 @@ struct PartLevel
   bool         canQtSplit;
   bool         qgEnable;
   bool         qgChromaEnable;
+#if JVET_O0050_LOCAL_DUAL_TREE
+  int          modeType;
+#endif
 
   PartLevel();
   PartLevel( const PartSplit _split, const Partitioning&  _parts );
@@ -123,6 +126,10 @@ public:
 
   unsigned currImplicitBtDepth;
   ChannelType chType;
+#if JVET_O0050_LOCAL_DUAL_TREE
+  TreeType treeType;
+  ModeType modeType;
+#endif
 
   virtual ~Partitioner                    () { }
 
@@ -134,6 +141,9 @@ public:
   const bool currQgChromaEnable           () const { return currPartLevel().qgChromaEnable; }
 
   SplitSeries getSplitSeries              () const;
+#if JVET_O0050_LOCAL_DUAL_TREE
+  ModeTypeSeries getModeTypeSeries        () const;
+#endif
 
   virtual void initCtu                    ( const UnitArea& ctuArea, const ChannelType _chType, const Slice& slice )    = 0;
   virtual void splitCurrArea              ( const PartSplit split, const CodingStructure &cs )                          = 0;
@@ -150,6 +160,11 @@ public:
   virtual bool canSplit                   ( const PartSplit split,                          const CodingStructure &cs ) = 0;
   virtual bool isSplitImplicit            ( const PartSplit split,                          const CodingStructure &cs ) = 0;
   virtual PartSplit getImplicitSplit      (                                                 const CodingStructure &cs ) = 0;
+#if JVET_O0050_LOCAL_DUAL_TREE
+  bool isSepTree                          ( const CodingStructure &cs );
+  bool isConsInter                        () { return modeType == MODE_TYPE_INTER; }
+  bool isConsIntra                        () { return modeType == MODE_TYPE_INTRA; }
+#endif
 };
 
 class AdaptiveDepthPartitioner : public Partitioner
@@ -189,6 +204,10 @@ public:
     chType       = _initialState.chType;
 #if _DEBUG
     m_currArea   = _initialState.currArea();
+#endif
+#if JVET_O0050_LOCAL_DUAL_TREE
+    treeType     = _initialState.treeType;
+    modeType     = _initialState.modeType;
 #endif
   }
 
diff --git a/source/Lib/CommonLib/UnitTools.cpp b/source/Lib/CommonLib/UnitTools.cpp
index 3aec86d1c..1756e8f8c 100644
--- a/source/Lib/CommonLib/UnitTools.cpp
+++ b/source/Lib/CommonLib/UnitTools.cpp
@@ -63,7 +63,11 @@ bool CS::isDualITree( const CodingStructure &cs )
 
 UnitArea CS::getArea( const CodingStructure &cs, const UnitArea &area, const ChannelType chType )
 {
+#if JVET_O0050_LOCAL_DUAL_TREE
+  return isDualITree( cs ) || cs.treeType != TREE_D ? area.singleChan( chType ) : area;
+#else
   return isDualITree( cs ) ? area.singleChan( chType ) : area;
+#endif
 }
 void CS::setRefinedMotionField(CodingStructure &cs)
 {
@@ -170,7 +174,11 @@ uint32_t CU::getIntraSizeIdx(const CodingUnit &cu)
 bool CU::isLastSubCUOfCtu( const CodingUnit &cu )
 {
   const SPS &sps      = *cu.cs->sps;
+#if JVET_O0050_LOCAL_DUAL_TREE
+  const Area cuAreaY = cu.isSepTree() ? Area( recalcPosition( cu.chromaFormat, cu.chType, CHANNEL_TYPE_LUMA, cu.blocks[cu.chType].pos() ), recalcSize( cu.chromaFormat, cu.chType, CHANNEL_TYPE_LUMA, cu.blocks[cu.chType].size() ) ) : (const Area&)cu.Y();
+#else
   const Area cuAreaY = CS::isDualITree( *cu.cs ) ? Area( recalcPosition( cu.chromaFormat, cu.chType, CHANNEL_TYPE_LUMA, cu.blocks[cu.chType].pos() ), recalcSize( cu.chromaFormat, cu.chType, CHANNEL_TYPE_LUMA, cu.blocks[cu.chType].size() ) ) : ( const Area& ) cu.Y();
+#endif
 
   return ( ( ( ( cuAreaY.x + cuAreaY.width  ) & cu.cs->pcv->maxCUWidthMask  ) == 0 || cuAreaY.x + cuAreaY.width  == sps.getPicWidthInLumaSamples()  ) &&
            ( ( ( cuAreaY.y + cuAreaY.height ) & cu.cs->pcv->maxCUHeightMask ) == 0 || cuAreaY.y + cuAreaY.height == sps.getPicHeightInLumaSamples() ) );
@@ -235,6 +243,15 @@ PartSplit CU::getSplitAtDepth( const CodingUnit& cu, const unsigned depth )
   else   { THROW( "Unknown split mode"    ); return CU_QUAD_SPLIT; }
 }
 
+#if JVET_O0050_LOCAL_DUAL_TREE
+ModeType CU::getModeTypeAtDepth( const CodingUnit& cu, const unsigned depth )
+{
+  ModeType modeType = ModeType( (cu.modeTypeSeries >> (depth * 3)) & 0x07 );
+  CHECK( depth > cu.depth, " depth is wrong" );
+  return modeType;
+}
+#endif
+
 bool CU::hasNonTsCodedBlock( const CodingUnit& cu )
 {
   bool hasAnyNonTSCoded = false;
@@ -772,7 +789,11 @@ void PU::getIntraChromaCandModes( const PredictionUnit &pu, unsigned modeList[NU
 #else
     Position topLeftPos = pu.blocks[pu.chType].lumaPos();
     Position refPos = topLeftPos.offset( pu.blocks[pu.chType].lumaSize().width >> 1, pu.blocks[pu.chType].lumaSize().height >> 1 );
+#if JVET_O0050_LOCAL_DUAL_TREE
+    const PredictionUnit *lumaPU = pu.cu->isSepTree() ? pu.cs->picture->cs->getPU( refPos, CHANNEL_TYPE_LUMA ) : &pu;
+#else
     const PredictionUnit *lumaPU = CS::isDualITree( *pu.cs ) ? pu.cs->picture->cs->getPU( refPos, CHANNEL_TYPE_LUMA ) : &pu;
+#endif
     const uint32_t lumaMode = PU::getIntraDirLuma( *lumaPU );
 #endif
     for( int i = 0; i < 4; i++ )
@@ -850,7 +871,11 @@ uint32_t PU::getFinalIntraMode( const PredictionUnit &pu, const ChannelType &chT
 #else
     Position topLeftPos = pu.blocks[pu.chType].lumaPos();
     Position refPos = topLeftPos.offset( pu.blocks[pu.chType].lumaSize().width >> 1, pu.blocks[pu.chType].lumaSize().height >> 1 );
+#if JVET_O0050_LOCAL_DUAL_TREE
+    const PredictionUnit &lumaPU = pu.cu->isSepTree() ? *pu.cs->picture->cs->getPU( refPos, CHANNEL_TYPE_LUMA ) : *pu.cs->getPU( topLeftPos, CHANNEL_TYPE_LUMA );
+#else
     const PredictionUnit &lumaPU = CS::isDualITree( *pu.cs ) ? *pu.cs->picture->cs->getPU( refPos, CHANNEL_TYPE_LUMA ) : *pu.cs->getPU( topLeftPos, CHANNEL_TYPE_LUMA );
+#endif
 
     uiIntraMode = PU::getIntraDirLuma( lumaPU );
 #endif
@@ -867,7 +892,11 @@ uint32_t PU::getCoLocatedIntraLumaMode( const PredictionUnit &pu )
 {
   Position topLeftPos = pu.blocks[pu.chType].lumaPos();
   Position refPos = topLeftPos.offset( pu.blocks[pu.chType].lumaSize().width >> 1, pu.blocks[pu.chType].lumaSize().height >> 1 );
+#if JVET_O0050_LOCAL_DUAL_TREE
+  const PredictionUnit &lumaPU = pu.cu->isSepTree() ? *pu.cs->picture->cs->getPU( refPos, CHANNEL_TYPE_LUMA ) : *pu.cs->getPU( topLeftPos, CHANNEL_TYPE_LUMA );
+#else
   const PredictionUnit &lumaPU = CS::isDualITree( *pu.cs ) ? *pu.cs->picture->cs->getPU( refPos, CHANNEL_TYPE_LUMA ) : *pu.cs->getPU( topLeftPos, CHANNEL_TYPE_LUMA );
+#endif
 
   return PU::getIntraDirLuma( lumaPU );
 }
@@ -4552,6 +4581,10 @@ bool TU::getCbf( const TransformUnit &tu, const ComponentID &compID )
 
 bool TU::getCbfAtDepth(const TransformUnit &tu, const ComponentID &compID, const unsigned &depth)
 {
+#if JVET_O0050_LOCAL_DUAL_TREE
+  if( !tu.blocks[compID].valid() )
+    CHECK( tu.cbf[compID] != 0, "cbf must be 0 if the component is not available" );
+#endif
   return ((tu.cbf[compID] >> depth) & 1) == 1;
 }
 
diff --git a/source/Lib/CommonLib/UnitTools.h b/source/Lib/CommonLib/UnitTools.h
index 4423e858d..504558051 100644
--- a/source/Lib/CommonLib/UnitTools.h
+++ b/source/Lib/CommonLib/UnitTools.h
@@ -76,6 +76,9 @@ namespace CU
   void addPUs                         (      CodingUnit& cu);
 
   PartSplit getSplitAtDepth           (const CodingUnit& cu, const unsigned depth);
+#if JVET_O0050_LOCAL_DUAL_TREE
+  ModeType  getModeTypeAtDepth        (const CodingUnit& cu, const unsigned depth);
+#endif
 
   bool hasNonTsCodedBlock             (const CodingUnit& cu);
   uint32_t getNumNonZeroCoeffNonTs         ( const CodingUnit& cu, const bool lumaFlag = true, const bool chromaFlag = true );
diff --git a/source/Lib/CommonLib/dtrace_blockstatistics.cpp b/source/Lib/CommonLib/dtrace_blockstatistics.cpp
index d5a82c9d4..38f9221ce 100644
--- a/source/Lib/CommonLib/dtrace_blockstatistics.cpp
+++ b/source/Lib/CommonLib/dtrace_blockstatistics.cpp
@@ -752,7 +752,11 @@ void writeAllData(const CodingStructure& cs, const UnitArea& ctuArea)
           }
         }
 
+#if JVET_O0050_LOCAL_DUAL_TREE
+        if( !(cu.chromaFormat == CHROMA_400 || (cu.isSepTree() && cu.chType == CHANNEL_TYPE_LUMA)) )
+#else
         if (!(cu.chromaFormat == CHROMA_400 || (CS::isDualITree(*cu.cs) && cu.chType == CHANNEL_TYPE_LUMA)))
+#endif
         {
           DTRACE_BLOCK_SCALAR_CHROMA(g_trace_ctx, D_BLOCK_STATISTICS_ALL, tu, GetBlockStatisticName(BlockStatistic::Cbf_Cb), tu.cbf[COMPONENT_Cb]);
           DTRACE_BLOCK_SCALAR_CHROMA(g_trace_ctx, D_BLOCK_STATISTICS_ALL, tu, GetBlockStatisticName(BlockStatistic::Cbf_Cr), tu.cbf[COMPONENT_Cr]);
@@ -844,7 +848,11 @@ void writeAllCodedData(const CodingStructure & cs, const UnitArea & ctuArea)
             {
               DTRACE_BLOCK_SCALAR(g_trace_ctx, D_BLOCK_STATISTICS_CODED, pu, GetBlockStatisticName(BlockStatistic::Luma_IntraMode), PU::getFinalIntraMode(pu, ChannelType(chType)));
             }
+#if JVET_O0050_LOCAL_DUAL_TREE
+            if (!(pu.chromaFormat == CHROMA_400 || (pu.cu->isSepTree() && pu.chType == CHANNEL_TYPE_LUMA)))
+#else
             if (!(pu.chromaFormat == CHROMA_400 || (CS::isDualITree(*pu.cs) && pu.chType == CHANNEL_TYPE_LUMA)))
+#endif
             {
               DTRACE_BLOCK_SCALAR_CHROMA(g_trace_ctx, D_BLOCK_STATISTICS_CODED, pu, GetBlockStatisticName(BlockStatistic::Chroma_IntraMode), PU::getFinalIntraMode(pu, CHANNEL_TYPE_CHROMA));
             }
@@ -1030,7 +1038,11 @@ void writeAllCodedData(const CodingStructure & cs, const UnitArea & ctuArea)
             DTRACE_BLOCK_SCALAR(g_trace_ctx, D_BLOCK_STATISTICS_CODED, tu, GetBlockStatisticName(BlockStatistic::Cbf_Y), tu.cbf[COMPONENT_Y]);
             DTRACE_BLOCK_SCALAR( g_trace_ctx, D_BLOCK_STATISTICS_CODED, tu, GetBlockStatisticName( BlockStatistic::MTSIdx ), tu.mtsIdx );
           }
+#if JVET_O0050_LOCAL_DUAL_TREE
+          if (!(cu.chromaFormat == CHROMA_400 || (cu.isSepTree() && cu.chType == CHANNEL_TYPE_LUMA)))
+#else
           if (!(cu.chromaFormat == CHROMA_400 || (CS::isDualITree(*cu.cs) && cu.chType == CHANNEL_TYPE_LUMA)))
+#endif
           {
             DTRACE_BLOCK_SCALAR_CHROMA(g_trace_ctx, D_BLOCK_STATISTICS_CODED, tu, GetBlockStatisticName(BlockStatistic::Cbf_Cb), tu.cbf[COMPONENT_Cb]);
             DTRACE_BLOCK_SCALAR_CHROMA(g_trace_ctx, D_BLOCK_STATISTICS_CODED, tu, GetBlockStatisticName(BlockStatistic::Cbf_Cr), tu.cbf[COMPONENT_Cr]);
diff --git a/source/Lib/CommonLib/x86/AdaptiveLoopFilterX86.h b/source/Lib/CommonLib/x86/AdaptiveLoopFilterX86.h
index 609a37c8f..632e18a61 100644
--- a/source/Lib/CommonLib/x86/AdaptiveLoopFilterX86.h
+++ b/source/Lib/CommonLib/x86/AdaptiveLoopFilterX86.h
@@ -496,10 +496,13 @@ static void simdFilter5x5Blk(AlfClassifier** classifier, const PelUnitBuf &recDs
           for( blkX=0; blkX<8; blkX+=2 )
           {
             Position pos(j + blkDst.x + blkX, i + blkDst.y + blkY);
-#if JVET_O0090_ALF_CHROMA_FILTER_ALTERNATIVES_CTB
+#if JVET_O0090_ALF_CHROMA_FILTER_ALTERNATIVES_CTB && !JVET_O0050_LOCAL_DUAL_TREE
             const CodingUnit* cu = isDualTree ? cs.getCU(pos, CH_C) : cs.getCU(recalcPosition(nChromaFormat, CH_C, CH_L, pos), CH_L);
 #else
             CodingUnit* cu = isDualTree ? cs.getCU(pos, CH_C) : cs.getCU(recalcPosition(nChromaFormat, CH_C, CH_L, pos), CH_L);
+#endif
+#if JVET_O0050_LOCAL_DUAL_TREE
+            cu = cu->isSepTree() ? cs.getCU( pos, CH_C ) : cu;
 #endif
             if(cu != NULL) 
             {
@@ -899,10 +902,13 @@ static void simdFilter7x7Blk(AlfClassifier** classifier, const PelUnitBuf &recDs
           for( blkX=0; blkX<8; blkX+=2 )
           {
             Position pos(j + blkDst.x + blkX, i + blkDst.y + blkY);
-#if JVET_O0090_ALF_CHROMA_FILTER_ALTERNATIVES_CTB
+#if JVET_O0090_ALF_CHROMA_FILTER_ALTERNATIVES_CTB && !JVET_O0050_LOCAL_DUAL_TREE
             const CodingUnit* cu = isDualTree ? cs.getCU(pos, CH_C) : cs.getCU(recalcPosition(nChromaFormat, CH_C, CH_L, pos), CH_L);
 #else
             CodingUnit* cu = isDualTree ? cs.getCU(pos, CH_C) : cs.getCU(recalcPosition(nChromaFormat, CH_C, CH_L, pos), CH_L);
+#endif
+#if JVET_O0050_LOCAL_DUAL_TREE
+            cu = cu->isSepTree() ? cs.getCU( pos, CH_C ) : cu;
 #endif
             if( cu != NULL) 
             {
diff --git a/source/Lib/DecoderLib/CABACReader.cpp b/source/Lib/DecoderLib/CABACReader.cpp
index f36c9684e..cdd4ccde9 100644
--- a/source/Lib/DecoderLib/CABACReader.cpp
+++ b/source/Lib/DecoderLib/CABACReader.cpp
@@ -139,6 +139,10 @@ bool CABACReader::coding_tree_unit( CodingStructure& cs, const UnitArea& area, i
   Partitioner *partitioner = PartitionerFactory::get( *cs.slice );
 
   partitioner->initCtu( area, CH_L, *cs.slice );
+#if JVET_O0050_LOCAL_DUAL_TREE
+  cs.treeType = partitioner->treeType = TREE_D;
+  cs.modeType = partitioner->modeType = MODE_TYPE_ALL;
+#endif
 
 
   sao( cs, ctuRsAddr );
@@ -449,7 +453,11 @@ bool CABACReader::coding_tree( CodingStructure& cs, Partitioner& partitioner, CU
   }
 
   // Reset delta QP coding flag and ChromaQPAdjustemt coding flag
+#if JVET_O0050_LOCAL_DUAL_TREE
+  if (partitioner.isSepTree(cs) && pPartitionerChroma != nullptr)
+#else
   if (CS::isDualITree(cs) && pPartitionerChroma != nullptr)
+#endif
   {
     if (pps.getUseDQP() && pPartitionerChroma->currQgEnable())
     {
@@ -573,6 +581,17 @@ bool CABACReader::coding_tree( CodingStructure& cs, Partitioner& partitioner, CU
       }
       else
       {
+#if JVET_O0050_LOCAL_DUAL_TREE
+        const ModeType modeTypeParent = partitioner.modeType;
+        cs.modeType = partitioner.modeType = mode_constraint( cs, partitioner, splitMode ); //change for child nodes
+        //decide chroma split or not
+        bool chromaNotSplit = modeTypeParent == MODE_TYPE_ALL && partitioner.modeType == MODE_TYPE_INTRA;
+        CHECK( chromaNotSplit && partitioner.chType != CHANNEL_TYPE_LUMA, "chType must be luma" );
+        if( partitioner.treeType == TREE_D )
+        {
+          cs.treeType = partitioner.treeType = chromaNotSplit ? TREE_L : TREE_D;
+        }
+#endif
       partitioner.splitCurrArea( splitMode, cs );
       do
       {
@@ -583,6 +602,26 @@ bool CABACReader::coding_tree( CodingStructure& cs, Partitioner& partitioner, CU
       } while( partitioner.nextPart( cs ) );
 
       partitioner.exitCurrSplit();
+#if JVET_O0050_LOCAL_DUAL_TREE
+      if( chromaNotSplit )
+      {
+        CHECK( partitioner.chType != CHANNEL_TYPE_LUMA, "must be luma status" );
+        partitioner.chType = CHANNEL_TYPE_CHROMA;
+        cs.treeType = partitioner.treeType = TREE_C;
+
+        if( !lastSegment && cs.picture->blocks[partitioner.chType].contains( partitioner.currArea().blocks[partitioner.chType].pos() ) )
+        {
+          lastSegment = coding_tree( cs, partitioner, cuCtx );
+        }
+
+        //recover treeType
+        partitioner.chType = CHANNEL_TYPE_LUMA;
+        cs.treeType = partitioner.treeType = TREE_D;
+      }
+
+      //recover ModeType
+      cs.modeType = partitioner.modeType = modeTypeParent;
+#endif
       }
       if (startShareThisLevel == 1)
         shareStateDec = NO_SHARE;
@@ -594,6 +633,9 @@ bool CABACReader::coding_tree( CodingStructure& cs, Partitioner& partitioner, CU
   partitioner.setCUData( cu );
   cu.slice   = cs.slice;
   cu.tileIdx = cs.picture->brickMap->getBrickIdxRsMap( currArea.lumaPos() );
+#if JVET_O0050_LOCAL_DUAL_TREE
+  CHECK( cu.cs->treeType != partitioner.treeType, "treeType mismatch" );
+#endif
 
   // Predict QP on start of quantization group
   if( cuCtx.qgStart )
@@ -602,7 +644,11 @@ bool CABACReader::coding_tree( CodingStructure& cs, Partitioner& partitioner, CU
     cuCtx.qp = CU::predictQP( cu, cuCtx.qp );
   }
 
+#if JVET_O0050_LOCAL_DUAL_TREE
+  if (pps.getUseDQP() && partitioner.isSepTree(cs) && isChroma(cu.chType))
+#else
   if (pps.getUseDQP() && CS::isDualITree(cs) && isChroma(cu.chType))
+#endif
   {
     const Position chromaCentral(cu.chromaPos().offset(cu.chromaSize().width >> 1, cu.chromaSize().height >> 1));
     const Position lumaRefPos(chromaCentral.x << getComponentScaleX(COMPONENT_Cb, cu.chromaFormat), chromaCentral.y << getComponentScaleY(COMPONENT_Cb, cu.chromaFormat));
@@ -626,6 +672,28 @@ bool CABACReader::coding_tree( CodingStructure& cs, Partitioner& partitioner, CU
   return isLastCtu;
 }
 
+#if JVET_O0050_LOCAL_DUAL_TREE
+ModeType CABACReader::mode_constraint( CodingStructure& cs, Partitioner &partitioner, PartSplit splitMode )
+{
+  int val = cs.signalModeCons( splitMode, partitioner, partitioner.modeType );
+  if( val == 2 )
+  {
+    int ctxIdx = DeriveCtx::CtxModeConsFlag( cs, partitioner );
+    bool flag = m_BinDecoder.decodeBin( Ctx::ModeConsFlag( ctxIdx ) );
+    DTRACE( g_trace_ctx, D_SYNTAX, "mode_cons_flag() flag=%d\n", flag );
+    return flag ? MODE_TYPE_INTRA : MODE_TYPE_INTER;
+  }
+  else if( val == 1 )
+  {
+    return MODE_TYPE_INTRA;
+  }
+  else
+  {
+    return partitioner.modeType;
+  }
+}
+#endif
+
 PartSplit CABACReader::split_cu_mode( CodingStructure& cs, Partitioner &partitioner )
 {
   RExt__DECODER_DEBUG_BIT_STATISTICS_CREATE_SET_SIZE2( STATS__CABAC_BITS__SPLIT_FLAG, partitioner.currArea().blocks[partitioner.chType].size(), partitioner.chType );
@@ -717,6 +785,10 @@ PartSplit CABACReader::split_cu_mode( CodingStructure& cs, Partitioner &partitio
 bool CABACReader::coding_unit( CodingUnit &cu, Partitioner &partitioner, CUCtx& cuCtx )
 {
   CodingStructure& cs = *cu.cs;
+#if JVET_O0050_LOCAL_DUAL_TREE
+  CHECK( cu.treeType != partitioner.treeType || cu.modeType != partitioner.modeType, "treeType or modeType mismatch" );
+  DTRACE( g_trace_ctx, D_SYNTAX, "coding_unit() treeType=%d modeType=%d\n", cu.treeType, cu.modeType );
+#endif
   // transquant bypass flag
   if( cs.pps->getTransquantBypassEnabledFlag() )
   {
@@ -781,7 +853,11 @@ void CABACReader::cu_skip_flag( CodingUnit& cu )
 {
   RExt__DECODER_DEBUG_BIT_STATISTICS_CREATE_SET( STATS__CABAC_BITS__SKIP_FLAG );
 
+#if JVET_O0050_LOCAL_DUAL_TREE
+  if ((cu.slice->isIntra() || cu.isConsIntra()) && cu.cs->slice->getSPS()->getIBCFlag())
+#else
   if (cu.slice->isIntra() && cu.cs->slice->getSPS()->getIBCFlag())
+#endif
   {
     cu.skip = false;
     cu.rootCbf = false;
@@ -810,6 +886,12 @@ void CABACReader::cu_skip_flag( CodingUnit& cu )
   {
     return;
   }
+#if JVET_O0050_LOCAL_DUAL_TREE
+  if( !cu.cs->slice->getSPS()->getIBCFlag() && cu.isConsIntra() )
+  {
+    return;
+  }
+#endif
   unsigned ctxId  = DeriveCtx::CtxSkipFlag(cu);
   unsigned skip   = m_BinDecoder.decodeBin( Ctx::SkipFlag(ctxId) );
 
@@ -818,9 +900,17 @@ void CABACReader::cu_skip_flag( CodingUnit& cu )
   if (skip && cu.cs->slice->getSPS()->getIBCFlag())
   {
 #if JVET_O1161_IBC_MAX_SIZE
+#if JVET_O0050_LOCAL_DUAL_TREE
+    if (cu.lwidth() < 128 && cu.lheight() < 128 && !cu.isConsInter()) // disable IBC mode larger than 64x64 and disable IBC when only allowing inter mode
+#else
     if (cu.lwidth() < 128 && cu.lheight() < 128) // disable IBC mode larger than 64x64
+#endif
+#else
+#if JVET_O0050_LOCAL_DUAL_TREE
+    if ((cu.lwidth() < 128 || cu.lheight() < 128) && !cu.isConsInter()) // disable IBC mode larger than 64x64 and disable IBC when only allowing inter mode
 #else
     if (cu.lwidth() < 128 || cu.lheight() < 128) // disable 128x128 IBC mode
+#endif
 #endif
     {
       if ( cu.lwidth() == 4 && cu.lheight() == 4 )
@@ -979,7 +1069,19 @@ void CABACReader::pred_mode( CodingUnit& cu )
   if (cu.cs->slice->getSPS()->getIBCFlag())
 #endif
   {
+#if JVET_O0050_LOCAL_DUAL_TREE
+    if( cu.isConsInter() )
+    {
+      cu.predMode = MODE_INTER;
+      return;
+    }
+#endif
+
+#if JVET_O0050_LOCAL_DUAL_TREE
+    if ( cu.cs->slice->isIntra() || ( cu.lwidth() == 4 && cu.lheight() == 4 ) || cu.isConsIntra() )
+#else
     if ( cu.cs->slice->isIntra() || ( cu.lwidth() == 4 && cu.lheight() == 4 ) )
+#endif
     {
       cu.predMode = MODE_INTRA;
 #if JVET_O1161_IBC_MAX_SIZE
@@ -1021,6 +1123,13 @@ void CABACReader::pred_mode( CodingUnit& cu )
   }
   else
   {
+#if JVET_O0050_LOCAL_DUAL_TREE
+    if( cu.isConsIntra() || cu.isConsInter() )
+    {
+      cu.predMode = cu.isConsIntra() ? MODE_INTRA : MODE_INTER;
+      return;
+    }
+#endif
     if ( cu.cs->slice->isIntra() || ( cu.lwidth() == 4 && cu.lheight() == 4 ) || m_BinDecoder.decodeBin( Ctx::PredMode( DeriveCtx::CtxPredModeFlag( cu ) ) ) )
     {
       cu.predMode = MODE_INTRA;
@@ -1318,7 +1427,11 @@ void CABACReader::intra_luma_pred_modes( CodingUnit &cu )
 
 void CABACReader::intra_chroma_pred_modes( CodingUnit& cu )
 {
+#if JVET_O0050_LOCAL_DUAL_TREE
+  if( cu.chromaFormat == CHROMA_400 || ( cu.isSepTree() && cu.chType == CHANNEL_TYPE_LUMA ) )
+#else
   if( cu.chromaFormat == CHROMA_400 || ( CS::isDualITree( *cu.cs ) && cu.chType == CHANNEL_TYPE_LUMA ) )
+#endif
   {
     return;
   }
@@ -1512,7 +1625,11 @@ bool CABACReader::end_of_ctu( CodingUnit& cu, CUCtx& cuCtx )
 
   if ( ( ( rbPos.x & cu.cs->pcv->maxCUWidthMask  ) == 0 || rbPos.x == sps.getPicWidthInLumaSamples () )
     && ( ( rbPos.y & cu.cs->pcv->maxCUHeightMask ) == 0 || rbPos.y == sps.getPicHeightInLumaSamples() )
+#if JVET_O0050_LOCAL_DUAL_TREE
+    && ( !cu.isSepTree() || cu.chromaFormat == CHROMA_400 || isChroma( cu.chType ) )
+#else
     && ( !CS::isDualITree( *cu.cs ) || cu.chromaFormat == CHROMA_400 || isChroma( cu.chType ) )
+#endif
       )
   {
     cuCtx.isDQPCoded = ( cu.cs->pps->getUseDQP() && !cuCtx.isDQPCoded );
@@ -2084,15 +2201,21 @@ void CABACReader::MHIntra_flag(PredictionUnit& pu)
 void CABACReader::pcm_samples( TransformUnit& tu )
 {
   CHECK( !tu.cu->ipcm, "pcm mode expected" );
-
+#if !JVET_O0050_LOCAL_DUAL_TREE
   const CodingStructure *cs = tu.cs;
+#endif
   const ChannelType chType = tu.chType;
 
   const SPS&        sps       = *tu.cu->cs->sps;
   tu.depth                    = 0;
 
+#if JVET_O0050_LOCAL_DUAL_TREE
+  ComponentID compStr = (tu.cu->isSepTree() && !isLuma( chType )) ? COMPONENT_Cb : COMPONENT_Y;
+  ComponentID compEnd = (tu.cu->isSepTree() && isLuma( chType )) ? COMPONENT_Y : COMPONENT_Cr;
+#else
   ComponentID compStr = (CS::isDualITree(*cs) && !isLuma(chType)) ? COMPONENT_Cb: COMPONENT_Y;
   ComponentID compEnd = (CS::isDualITree(*cs) && isLuma(chType)) ? COMPONENT_Y : COMPONENT_Cr;
+#endif
   for( ComponentID compID = compStr; compID <= compEnd; compID = ComponentID(compID+1) )
   {
     PelBuf          samples     = tu.getPcmbuf( compID );
@@ -2161,7 +2284,11 @@ void CABACReader::transform_tree( CodingStructure &cs, Partitioner &partitioner,
   const bool chromaCbfISP = area.blocks[COMPONENT_Cb].valid() && cu.ispMode && !split;
 
   // cbf_cb & cbf_cr
+#if JVET_O0050_LOCAL_DUAL_TREE
+  if( area.chromaFormat != CHROMA_400 && area.blocks[COMPONENT_Cb].valid() && ( !cu.isSepTree() || partitioner.chType == CHANNEL_TYPE_CHROMA ) && ( !cu.ispMode || chromaCbfISP ) )
+#else
   if( area.chromaFormat != CHROMA_400 && area.blocks[COMPONENT_Cb].valid() && ( !CS::isDualITree( cs ) || partitioner.chType == CHANNEL_TYPE_CHROMA ) && ( !cu.ispMode || chromaCbfISP ) )
+#endif
   {
     const int cbfDepth = chromaCbfISP ? trDepth - 1 : trDepth;
     if (!max_tu_split)
@@ -2176,7 +2303,11 @@ void CABACReader::transform_tree( CodingStructure &cs, Partitioner &partitioner,
       }
     }
   }
+#if JVET_O0050_LOCAL_DUAL_TREE
+  else if( cu.isSepTree() )
+#else
   else if( CS::isDualITree( cs ) )
+#endif
   {
     chromaCbfs = ChromaCbfs( false );
   }
@@ -2442,7 +2573,11 @@ void CABACReader::transform_unit( TransformUnit& tu, CUCtx& cuCtx, ChromaCbfs& c
   const bool chromaCbfISP = area.blocks[COMPONENT_Cb].valid() && cu.ispMode;
 
   // cbf_cb & cbf_cr
+#if JVET_O0050_LOCAL_DUAL_TREE
+  if (area.chromaFormat != CHROMA_400 && area.blocks[COMPONENT_Cb].valid() && (!cu.isSepTree() || partitioner.chType == CHANNEL_TYPE_CHROMA) && (!cu.ispMode || chromaCbfISP))
+#else
   if (area.chromaFormat != CHROMA_400 && area.blocks[COMPONENT_Cb].valid() && (!CS::isDualITree(cs) || partitioner.chType == CHANNEL_TYPE_CHROMA) && (!cu.ispMode || chromaCbfISP))
+#endif
   {
     const int cbfDepth = chromaCbfISP ? trDepth - 1 : trDepth;
     {
@@ -2453,7 +2588,11 @@ void CABACReader::transform_unit( TransformUnit& tu, CUCtx& cuCtx, ChromaCbfs& c
         chromaCbfs.Cr = cbf_comp(cs, area.blocks[COMPONENT_Cr], cbfDepth, chromaCbfs.Cb);
     }
   }
+#if JVET_O0050_LOCAL_DUAL_TREE
+  else if (cu.isSepTree())
+#else
   else if (CS::isDualITree(cs))
+#endif
   {
     chromaCbfs = ChromaCbfs(false);
   }
@@ -2526,7 +2665,11 @@ void CABACReader::transform_unit( TransformUnit& tu, CUCtx& cuCtx, ChromaCbfs& c
   {
     if( cu.cs->pps->getUseDQP() && !cuCtx.isDQPCoded )
     {
+#if JVET_O0050_LOCAL_DUAL_TREE
+      if (!tu.cu->isSepTree() || isLuma(tu.chType))
+#else
       if (!CS::isDualITree(*tu.cs) || isLuma(tu.chType))
+#endif
       {
         cu_qp_delta(cu, cuCtx.qp, cu.qp);
         cuCtx.qp = cu.qp;
@@ -2861,7 +3004,11 @@ void CABACReader::residual_lfnst_mode( CodingUnit& cu )
 #endif
 {
   if( cu.ispMode != NOT_INTRA_SUBPARTITIONS || cu.mipFlag == true ||
+#if JVET_O0050_LOCAL_DUAL_TREE
+    ( cu.isSepTree() && cu.chType == CHANNEL_TYPE_CHROMA && std::min( cu.blocks[ 1 ].width, cu.blocks[ 1 ].height ) < 4 ) )
+#else
     ( CS::isDualITree( *cu.cs ) && cu.chType == CHANNEL_TYPE_CHROMA && std::min( cu.blocks[ 1 ].width, cu.blocks[ 1 ].height ) < 4 ) )
+#endif
   {
     return;
   }
@@ -2870,15 +3017,24 @@ void CABACReader::residual_lfnst_mode( CodingUnit& cu )
 
   if( cu.cs->sps->getUseLFNST() && CU::isIntra( cu ) && !CU::isLosslessCoded( cu ) )
   {
+#if JVET_O0050_LOCAL_DUAL_TREE
+    const bool lumaFlag              = cu.isSepTree() ? (   isLuma( cu.chType ) ? true : false ) : true;
+    const bool chromaFlag            = cu.isSepTree() ? ( isChroma( cu.chType ) ? true : false ) : true;
+#else
     const bool lumaFlag              = CS::isDualITree( *cu.cs ) ? (   isLuma( cu.chType ) ? true : false ) : true;
     const bool chromaFlag            = CS::isDualITree( *cu.cs ) ? ( isChroma( cu.chType ) ? true : false ) : true;
+#endif
     bool nonZeroCoeffNonTs;
 #if JVET_O0094_LFNST_ZERO_PRIM_COEFFS
     bool nonZeroCoeffNonTsCorner8x8 = ( lumaFlag && cuCtx.violatesLfnstConstrained[CHANNEL_TYPE_LUMA] ) || (chromaFlag && cuCtx.violatesLfnstConstrained[CHANNEL_TYPE_CHROMA] );
 #else
     bool nonZeroCoeffNonTsCorner8x8 = CU::getNumNonZeroCoeffNonTsCorner8x8( cu, lumaFlag, chromaFlag ) > 0;
 #endif
+#if JVET_O0050_LOCAL_DUAL_TREE
+    const int  nonZeroCoeffThr       = cu.isSepTree() ? ( isLuma( cu.chType ) ? LFNST_SIG_NZ_LUMA : LFNST_SIG_NZ_CHROMA ) : LFNST_SIG_NZ_LUMA + LFNST_SIG_NZ_CHROMA;
+#else
     const int  nonZeroCoeffThr       = CS::isDualITree( *cu.cs ) ? ( isLuma( cu.chType ) ? LFNST_SIG_NZ_LUMA : LFNST_SIG_NZ_CHROMA ) : LFNST_SIG_NZ_LUMA + LFNST_SIG_NZ_CHROMA;
+#endif
     nonZeroCoeffNonTs = CU::getNumNonZeroCoeffNonTs( cu, lumaFlag, chromaFlag ) > nonZeroCoeffThr;
 #if JVET_O0368_LFNST_WITH_DCT2_ONLY
     const bool isNonDCT2 = (TU::getCbf(*cu.firstTU, ComponentID(COMPONENT_Y)) && cu.firstTU->mtsIdx != MTS_DCT2_DCT2);
@@ -2900,9 +3056,17 @@ void CABACReader::residual_lfnst_mode( CodingUnit& cu )
 
   unsigned cctx = 0;
 #if JVET_O0368_LFNST_WITH_DCT2_ONLY
+#if JVET_O0050_LOCAL_DUAL_TREE
+  if ( cu.isSepTree() ) cctx++;
+#else
   if ( CS::isDualITree(*cu.cs) ) cctx++;
+#endif
+#else
+#if JVET_O0050_LOCAL_DUAL_TREE
+  if( cu.firstTU->mtsIdx < MTS_DST7_DST7 && cu.isSepTree() ) cctx++;
 #else
   if( cu.firstTU->mtsIdx < MTS_DST7_DST7 && CS::isDualITree( *cu.cs ) ) cctx++;
+#endif
 #endif
 
   uint32_t idxLFNST = m_BinDecoder.decodeBin( Ctx::LFNSTIdx( cctx ) );
diff --git a/source/Lib/DecoderLib/CABACReader.h b/source/Lib/DecoderLib/CABACReader.h
index 947702d7f..f64b6df1e 100644
--- a/source/Lib/DecoderLib/CABACReader.h
+++ b/source/Lib/DecoderLib/CABACReader.h
@@ -73,6 +73,9 @@ public:
   // coding (quad)tree (clause 7.3.8.4)
   bool        coding_tree               ( CodingStructure&              cs,     Partitioner&    pm,       CUCtx& cuCtx, Partitioner* pPartitionerChroma = nullptr, CUCtx* pCuCtxChroma = nullptr);
   PartSplit   split_cu_mode             ( CodingStructure&              cs,     Partitioner&    pm );
+#if JVET_O0050_LOCAL_DUAL_TREE
+  ModeType    mode_constraint           ( CodingStructure&              cs,     Partitioner&    pm,       const PartSplit splitMode );
+#endif
 
   // coding unit (clause 7.3.8.5)
   bool        coding_unit               ( CodingUnit&                   cu,     Partitioner&    pm,       CUCtx& cuCtx );
diff --git a/source/Lib/DecoderLib/DecCu.cpp b/source/Lib/DecoderLib/DecCu.cpp
index 13367e597..29b4e11f8 100644
--- a/source/Lib/DecoderLib/DecCu.cpp
+++ b/source/Lib/DecoderLib/DecCu.cpp
@@ -409,11 +409,18 @@ void DecCu::xDecodePCMTexture(TransformUnit &tu, const ComponentID compID)
 */
 void DecCu::xReconPCM(TransformUnit &tu)
 {
+#if !JVET_O0050_LOCAL_DUAL_TREE
   const CodingStructure *cs = tu.cs;
+#endif
   const ChannelType chType = tu.chType;
 
+#if JVET_O0050_LOCAL_DUAL_TREE
+  ComponentID compStr = (tu.cu->isSepTree() && !isLuma( chType )) ? COMPONENT_Cb : COMPONENT_Y;
+  ComponentID compEnd = (tu.cu->isSepTree() && isLuma( chType )) ? COMPONENT_Y : COMPONENT_Cr;
+#else
   ComponentID compStr = (CS::isDualITree(*cs) && !isLuma(chType)) ? COMPONENT_Cb: COMPONENT_Y;
   ComponentID compEnd = (CS::isDualITree(*cs) && isLuma(chType)) ? COMPONENT_Y : COMPONENT_Cr;
+#endif
   for( ComponentID compID = compStr; compID <= compEnd; compID = ComponentID(compID+1) )
   {
 
diff --git a/source/Lib/EncoderLib/CABACWriter.cpp b/source/Lib/EncoderLib/CABACWriter.cpp
index 8ee4580fd..e1a7f8196 100644
--- a/source/Lib/EncoderLib/CABACWriter.cpp
+++ b/source/Lib/EncoderLib/CABACWriter.cpp
@@ -396,7 +396,11 @@ void CABACWriter::coding_tree(const CodingStructure& cs, Partitioner& partitione
     cuCtx.isChromaQpAdjCoded  = false;
   }
   // Reset delta QP coding flag and ChromaQPAdjustemt coding flag
+#if JVET_O0050_LOCAL_DUAL_TREE
+  if (partitioner.isSepTree(cs) && pPartitionerChroma != nullptr)
+#else
   if (CS::isDualITree(cs) && pPartitionerChroma != nullptr)
+#endif
   {
     if (pps.getUseDQP() && pPartitionerChroma->currQgEnable())
     {
@@ -461,6 +465,19 @@ void CABACWriter::coding_tree(const CodingStructure& cs, Partitioner& partitione
       }
       else
       {
+#if JVET_O0050_LOCAL_DUAL_TREE
+        const ModeType modeTypeParent = partitioner.modeType;
+        const ModeType modeTypeChild = CU::getModeTypeAtDepth( cu, partitioner.currDepth );
+        mode_constraint( splitMode, cs, partitioner, modeTypeChild );
+        partitioner.modeType = modeTypeChild;
+
+        bool chromaNotSplit = modeTypeParent == MODE_TYPE_ALL && modeTypeChild == MODE_TYPE_INTRA ? true : false;
+        CHECK( chromaNotSplit && partitioner.chType != CHANNEL_TYPE_LUMA, "chType must be luma" );
+        if( partitioner.treeType == TREE_D )
+        {
+          partitioner.treeType = chromaNotSplit ? TREE_L : TREE_D;
+        }
+#endif
       partitioner.splitCurrArea( splitMode, cs );
 
       do
@@ -472,6 +489,24 @@ void CABACWriter::coding_tree(const CodingStructure& cs, Partitioner& partitione
       } while( partitioner.nextPart( cs ) );
 
       partitioner.exitCurrSplit();
+#if JVET_O0050_LOCAL_DUAL_TREE
+      if( chromaNotSplit )
+      {
+        CHECK( partitioner.chType != CHANNEL_TYPE_LUMA, "must be luma status" );
+        partitioner.chType = CHANNEL_TYPE_CHROMA;
+        partitioner.treeType = TREE_C;
+
+        if( cs.picture->blocks[partitioner.chType].contains( partitioner.currArea().blocks[partitioner.chType].pos() ) )
+        {
+          coding_tree( cs, partitioner, cuCtx );
+        }
+
+        //recover
+        partitioner.chType = CHANNEL_TYPE_LUMA;
+        partitioner.treeType = TREE_D;
+      }
+      partitioner.modeType = modeTypeParent;
+#endif
       }
       return;
   }
@@ -482,6 +517,9 @@ void CABACWriter::coding_tree(const CodingStructure& cs, Partitioner& partitione
     cuCtx.qgStart = false;
     cuCtx.qp = CU::predictQP( cu, cuCtx.qp );
   }
+#if JVET_O0050_LOCAL_DUAL_TREE
+  CHECK( cu.treeType != partitioner.treeType, "treeType mismatch" );
+#endif
 
 
   // coding unit
@@ -491,6 +529,30 @@ void CABACWriter::coding_tree(const CodingStructure& cs, Partitioner& partitione
   DTRACE_BLOCK_REC_COND( ( !isEncoding() ), cs.picture->getRecoBuf( cu ), cu, cu.predMode );
 }
 
+#if JVET_O0050_LOCAL_DUAL_TREE
+void CABACWriter::mode_constraint( const PartSplit split, const CodingStructure& cs, Partitioner& partitioner, const ModeType modeType )
+{
+  CHECK( split == CU_DONT_SPLIT, "splitMode shall not be no split" );
+  int val = cs.signalModeCons( split, partitioner, partitioner.modeType );
+  if( val == 2 )
+  {
+    CHECK( modeType == MODE_TYPE_ALL, "shall not be no constraint case" );
+    bool flag = modeType == MODE_TYPE_INTRA;
+    int ctxIdx = DeriveCtx::CtxModeConsFlag( cs, partitioner );
+    m_BinEncoder.encodeBin( flag, Ctx::ModeConsFlag( ctxIdx ) );
+    DTRACE( g_trace_ctx, D_SYNTAX, "mode_cons_flag() flag=%d\n", flag );
+  }
+  else if( val == 1 )
+  {
+    assert( modeType == MODE_TYPE_INTRA );
+  }
+  else
+  {
+    assert( modeType == partitioner.modeType );
+  }
+}
+#endif
+
 void CABACWriter::split_cu_mode( const PartSplit split, const CodingStructure& cs, Partitioner& partitioner )
 {
   bool canNo, canQt, canBh, canBv, canTh, canTv;
@@ -573,6 +635,9 @@ void CABACWriter::split_cu_mode( const PartSplit split, const CodingStructure& c
 
 void CABACWriter::coding_unit( const CodingUnit& cu, Partitioner& partitioner, CUCtx& cuCtx )
 {
+#if JVET_O0050_LOCAL_DUAL_TREE
+  DTRACE( g_trace_ctx, D_SYNTAX, "coding_unit() treeType=%d modeType=%d\n", cu.treeType, cu.modeType );
+#endif
   CodingStructure& cs = *cu.cs;
   // transquant bypass flag
   if( cs.pps->getTransquantBypassEnabledFlag() )
@@ -648,7 +713,11 @@ void CABACWriter::cu_skip_flag( const CodingUnit& cu )
 {
   unsigned ctxId = DeriveCtx::CtxSkipFlag( cu );
 
+#if JVET_O0050_LOCAL_DUAL_TREE
+  if ((cu.slice->isIntra() || cu.isConsIntra()) && cu.cs->slice->getSPS()->getIBCFlag())
+#else
   if (cu.slice->isIntra() && cu.cs->slice->getSPS()->getIBCFlag())
+#endif
   {
 #if JVET_O1161_IBC_MAX_SIZE
     if (cu.lwidth() < 128 && cu.lheight() < 128) // disable IBC mode larger than 64x64
@@ -665,15 +734,29 @@ void CABACWriter::cu_skip_flag( const CodingUnit& cu )
   {
     return;
   }
+#if JVET_O0050_LOCAL_DUAL_TREE
+  if( !cu.cs->slice->getSPS()->getIBCFlag() && cu.isConsIntra() )
+  {
+    return;
+  }
+#endif
   m_BinEncoder.encodeBin( ( cu.skip ), Ctx::SkipFlag( ctxId ) );
 
   DTRACE( g_trace_ctx, D_SYNTAX, "cu_skip_flag() ctx=%d skip=%d\n", ctxId, cu.skip ? 1 : 0 );
   if (cu.skip && cu.cs->slice->getSPS()->getIBCFlag())
   {
 #if JVET_O1161_IBC_MAX_SIZE
+#if JVET_O0050_LOCAL_DUAL_TREE
+    if (cu.lwidth() < 128 && cu.lheight() < 128 && !cu.isConsInter()) // // disable IBC mode larger than 64x64 and disable IBC when only allowing inter mode
+#else
     if (cu.lwidth() < 128 && cu.lheight() < 128) // disable IBC mode larger than 64x64
+#endif
+#else
+#if JVET_O0050_LOCAL_DUAL_TREE
+    if ((cu.lwidth() < 128 || cu.lheight() < 128) && !cu.isConsInter()) // // disable 128x128 IBC mode and disable IBC when only allowing inter mode
 #else
     if (cu.lwidth() < 128 || cu.lheight() < 128) // disable 128x128 IBC mode
+#endif
 #endif
     {
       if ( cu.lwidth() == 4 && cu.lheight() == 4 )
@@ -746,7 +829,19 @@ void CABACWriter::pred_mode( const CodingUnit& cu )
   if (cu.cs->slice->getSPS()->getIBCFlag())
 #endif
   {
+#if JVET_O0050_LOCAL_DUAL_TREE
+    if( cu.isConsInter() )
+    {
+      assert( CU::isInter( cu ) );
+      return;
+    }
+#endif
+
+#if JVET_O0050_LOCAL_DUAL_TREE
+    if ( cu.cs->slice->isIntra() || ( cu.lwidth() == 4 && cu.lheight() == 4 ) || cu.isConsIntra() )
+#else
     if ( cu.cs->slice->isIntra() || ( cu.lwidth() == 4 && cu.lheight() == 4 ) )
+#endif
     {
 #if JVET_O1161_IBC_MAX_SIZE
       if (cu.lwidth() < 128 && cu.lheight() < 128) // disable IBC mode larger than 64x64
@@ -760,6 +855,12 @@ void CABACWriter::pred_mode( const CodingUnit& cu )
     }
     else
     {
+#if JVET_O0050_LOCAL_DUAL_TREE
+      if( cu.isConsInter() )
+      {
+        return;
+      }
+#endif
       m_BinEncoder.encodeBin((CU::isIntra(cu)), Ctx::PredMode(DeriveCtx::CtxPredModeFlag(cu)));
       if (!CU::isIntra(cu))
       {
@@ -777,6 +878,13 @@ void CABACWriter::pred_mode( const CodingUnit& cu )
   }
   else
   {
+#if JVET_O0050_LOCAL_DUAL_TREE
+    if( cu.isConsIntra() || cu.isConsInter() )
+    {
+      assert( (cu.isConsIntra() && cu.predMode == MODE_INTRA) || (cu.isConsInter() && cu.predMode == MODE_INTER) );
+      return;
+    }
+#endif
     if ( cu.cs->slice->isIntra() || ( cu.lwidth() == 4 && cu.lheight() == 4 ) )
     {
       return;
@@ -1203,7 +1311,11 @@ void CABACWriter::intra_luma_pred_mode( const PredictionUnit& pu )
 
 void CABACWriter::intra_chroma_pred_modes( const CodingUnit& cu )
 {
+#if JVET_O0050_LOCAL_DUAL_TREE
+  if( cu.chromaFormat == CHROMA_400 || ( cu.isSepTree() && cu.chType == CHANNEL_TYPE_LUMA ) )
+#else
   if( cu.chromaFormat == CHROMA_400 || ( CS::isDualITree( *cu.cs ) && cu.chType == CHANNEL_TYPE_LUMA ) )
+#endif
   {
     return;
   }
@@ -1400,7 +1512,11 @@ void CABACWriter::end_of_ctu( const CodingUnit& cu, CUCtx& cuCtx )
   const bool    isLastSubCUOfCtu  = CU::isLastSubCUOfCtu( cu );
 
   if ( isLastSubCUOfCtu
+#if JVET_O0050_LOCAL_DUAL_TREE
+    && ( !cu.isSepTree() || cu.chromaFormat == CHROMA_400 || isChroma( cu.chType ) )
+#else
     && ( !CS::isDualITree( *cu.cs ) || cu.chromaFormat == CHROMA_400 || isChroma( cu.chType ) )
+#endif
       )
   {
     cuCtx.isDQPCoded = ( cu.cs->pps->getUseDQP() && !cuCtx.isDQPCoded );
@@ -1431,6 +1547,9 @@ void CABACWriter::end_of_ctu( const CodingUnit& cu, CUCtx& cuCtx )
 
 void CABACWriter::prediction_unit( const PredictionUnit& pu )
 {
+#if JVET_O0050_LOCAL_DUAL_TREE
+  CHECK( pu.cu->treeType == TREE_C, "cannot be chroma CU" );
+#endif
 #if ENABLE_SPLIT_PARALLELISM || ENABLE_WPP_PARALLELISM
   CHECK( pu.cacheUsed, "Processing a PU that should be in cache!" );
   CHECK( pu.cu->cacheUsed, "Processing a CU that should be in cache!" );
@@ -1983,12 +2102,18 @@ void CABACWriter::pcm_samples( const TransformUnit& tu )
   CHECK( !tu.cu->ipcm, "pcm mode expected" );
 
   const SPS&        sps       = *tu.cu->cs->sps;
-
+#if !JVET_O0050_LOCAL_DUAL_TREE
   const CodingStructure *cs = tu.cs;
+#endif
   const ChannelType chType = tu.chType;
 
+#if JVET_O0050_LOCAL_DUAL_TREE
+  ComponentID compStr = (tu.cu->isSepTree() && !isLuma( chType )) ? COMPONENT_Cb : COMPONENT_Y;
+  ComponentID compEnd = (tu.cu->isSepTree() && isLuma( chType )) ? COMPONENT_Y : COMPONENT_Cr;
+#else
   ComponentID compStr = (CS::isDualITree(*cs) && !isLuma(chType)) ? COMPONENT_Cb: COMPONENT_Y;
   ComponentID compEnd = (CS::isDualITree(*cs) && isLuma(chType)) ? COMPONENT_Y : COMPONENT_Cr;
+#endif
   for( ComponentID compID = compStr; compID <= compEnd; compID = ComponentID(compID+1) )
   {
     const CPelBuf   samples     = tu.getPcmbuf( compID );
@@ -2057,7 +2182,11 @@ void CABACWriter::transform_tree( const CodingStructure& cs, Partitioner& partit
 
 #if !JVET_O0596_CBF_SIG_ALIGN_TO_SPEC
   // cbf_cb & cbf_cr
+#if JVET_O0050_LOCAL_DUAL_TREE
+  if( area.chromaFormat != CHROMA_400 && area.blocks[COMPONENT_Cb].valid() && ( !cu.isSepTree() || partitioner.chType == CHANNEL_TYPE_CHROMA ) && ( !cu.ispMode || chromaCbfISP ) )
+#else
   if( area.chromaFormat != CHROMA_400 && area.blocks[COMPONENT_Cb].valid() && ( !CS::isDualITree( cs ) || partitioner.chType == CHANNEL_TYPE_CHROMA ) && ( !cu.ispMode || chromaCbfISP ) )
+#endif
   {
     {
       unsigned cbfDepth = chromaCbfISP ? trDepth - 1 : trDepth;
@@ -2076,7 +2205,11 @@ void CABACWriter::transform_tree( const CodingStructure& cs, Partitioner& partit
       }
     }
   }
+#if JVET_O0050_LOCAL_DUAL_TREE
+  else if( cu.isSepTree() )
+#else
   else if( CS::isDualITree( cs ) )
+#endif
   {
     chromaCbfs = ChromaCbfs( false );
   }
@@ -2297,7 +2430,11 @@ void CABACWriter::transform_unit( const TransformUnit& tu, CUCtx& cuCtx, ChromaC
   CHECK(tu.depth != trDepth, " transform unit should be not be futher partitioned");
 
   // cbf_cb & cbf_cr
+#if JVET_O0050_LOCAL_DUAL_TREE
+  if (area.chromaFormat != CHROMA_400 && area.blocks[COMPONENT_Cb].valid() && (!cu.isSepTree() || partitioner.chType == CHANNEL_TYPE_CHROMA) && (!cu.ispMode || chromaCbfISP))
+#else
   if (area.chromaFormat != CHROMA_400 && area.blocks[COMPONENT_Cb].valid() && (!CS::isDualITree(cs) || partitioner.chType == CHANNEL_TYPE_CHROMA) && (!cu.ispMode || chromaCbfISP))
+#endif
   {
     {
       unsigned cbfDepth = chromaCbfISP ? trDepth - 1 : trDepth;
@@ -2316,7 +2453,11 @@ void CABACWriter::transform_unit( const TransformUnit& tu, CUCtx& cuCtx, ChromaC
       }
     }
   }
+#if JVET_O0050_LOCAL_DUAL_TREE
+  else if (cu.isSepTree())
+#else
   else if (CS::isDualITree(cs))
+#endif
   {
     chromaCbfs = ChromaCbfs(false);
   }
@@ -2397,7 +2538,11 @@ void CABACWriter::transform_unit( const TransformUnit& tu, CUCtx& cuCtx, ChromaC
   {
     if( cu.cs->pps->getUseDQP() && !cuCtx.isDQPCoded )
     {
+#if JVET_O0050_LOCAL_DUAL_TREE
+      if (!tu.cu->isSepTree() || isLuma(tu.chType))
+#else
       if (!CS::isDualITree(*tu.cs) || isLuma(tu.chType))
+#endif
       {
         cu_qp_delta(cu, cuCtx.qp, cu.qp);
         cuCtx.qp = cu.qp;
@@ -2730,22 +2875,35 @@ void CABACWriter::explicit_rdpcm_mode( const TransformUnit& tu, ComponentID comp
 void CABACWriter::residual_lfnst_mode( const CodingUnit& cu, CUCtx& cuCtx )
 {
   if( cu.ispMode != NOT_INTRA_SUBPARTITIONS || cu.mipFlag == true ||
+#if JVET_O0050_LOCAL_DUAL_TREE
+    ( cu.isSepTree() && cu.chType == CHANNEL_TYPE_CHROMA && std::min( cu.blocks[ 1 ].width, cu.blocks[ 1 ].height ) < 4 ) )
+#else
     ( CS::isDualITree( *cu.cs ) && cu.chType == CHANNEL_TYPE_CHROMA && std::min( cu.blocks[ 1 ].width, cu.blocks[ 1 ].height ) < 4 ) )
+#endif
   {
     return;
   }
 
   if( cu.cs->sps->getUseLFNST() && CU::isIntra( cu ) && !CU::isLosslessCoded( cu ) )
   {
+#if JVET_O0050_LOCAL_DUAL_TREE
+    const bool lumaFlag                   = cu.isSepTree() ? (   isLuma( cu.chType ) ? true : false ) : true;
+    const bool chromaFlag                 = cu.isSepTree() ? ( isChroma( cu.chType ) ? true : false ) : true;
+#else
     const bool lumaFlag                   = CS::isDualITree( *cu.cs ) ? (   isLuma( cu.chType ) ? true : false ) : true;
     const bool chromaFlag                 = CS::isDualITree( *cu.cs ) ? ( isChroma( cu.chType ) ? true : false ) : true;
+#endif
           bool nonZeroCoeffNonTs;
 #if JVET_O0094_LFNST_ZERO_PRIM_COEFFS
           bool nonZeroCoeffNonTsCorner8x8 = ( lumaFlag && cuCtx.violatesLfnstConstrained[CHANNEL_TYPE_LUMA] ) || (chromaFlag && cuCtx.violatesLfnstConstrained[CHANNEL_TYPE_CHROMA] );
 #else
           bool nonZeroCoeffNonTsCorner8x8 = CU::getNumNonZeroCoeffNonTsCorner8x8( cu, lumaFlag, chromaFlag ) > 0;
 #endif
+#if JVET_O0050_LOCAL_DUAL_TREE
+    const int  nonZeroCoeffThr            = cu.isSepTree() ? ( isLuma( cu.chType ) ? LFNST_SIG_NZ_LUMA : LFNST_SIG_NZ_CHROMA ) : LFNST_SIG_NZ_LUMA + LFNST_SIG_NZ_CHROMA;
+#else
     const int  nonZeroCoeffThr            = CS::isDualITree( *cu.cs ) ? ( isLuma( cu.chType ) ? LFNST_SIG_NZ_LUMA : LFNST_SIG_NZ_CHROMA ) : LFNST_SIG_NZ_LUMA + LFNST_SIG_NZ_CHROMA;
+#endif
     cuCtx.numNonZeroCoeffNonTs            = CU::getNumNonZeroCoeffNonTs( cu, lumaFlag, chromaFlag );
     nonZeroCoeffNonTs                     = cuCtx.numNonZeroCoeffNonTs > nonZeroCoeffThr;
 #if JVET_O0368_LFNST_WITH_DCT2_ONLY
@@ -2766,9 +2924,17 @@ void CABACWriter::residual_lfnst_mode( const CodingUnit& cu, CUCtx& cuCtx )
 
   unsigned cctx = 0;
 #if JVET_O0368_LFNST_WITH_DCT2_ONLY
+#if JVET_O0050_LOCAL_DUAL_TREE
+  if ( cu.isSepTree() ) cctx++;
+#else
   if ( CS::isDualITree(*cu.cs) ) cctx++;
+#endif
+#else
+#if JVET_O0050_LOCAL_DUAL_TREE
+  if( cu.firstTU->mtsIdx < MTS_DST7_DST7 && cu.isSepTree() ) cctx++;
 #else
   if( cu.firstTU->mtsIdx < MTS_DST7_DST7 && CS::isDualITree( *cu.cs ) ) cctx++;
+#endif
 #endif
 
   const uint32_t idxLFNST = cu.lfnstIdx;
diff --git a/source/Lib/EncoderLib/CABACWriter.h b/source/Lib/EncoderLib/CABACWriter.h
index 6a2453e2a..aafe19ad1 100644
--- a/source/Lib/EncoderLib/CABACWriter.h
+++ b/source/Lib/EncoderLib/CABACWriter.h
@@ -83,6 +83,9 @@ public:
   // coding (quad)tree (clause 7.3.8.4)
   void        coding_tree               ( const CodingStructure&        cs,       Partitioner&      pm,         CUCtx& cuCtx, Partitioner* pPartitionerChroma = nullptr, CUCtx* pCuCtxChroma = nullptr);
   void        split_cu_mode             ( const PartSplit               split,    const CodingStructure& cs,    Partitioner& pm );
+#if JVET_O0050_LOCAL_DUAL_TREE
+  void        mode_constraint           ( const PartSplit               split,    const CodingStructure& cs,    Partitioner& pm,    const ModeType modeType );
+#endif
 
   // coding unit (clause 7.3.8.5)
   void        coding_unit               ( const CodingUnit&             cu,       Partitioner&      pm,         CUCtx& cuCtx );
diff --git a/source/Lib/EncoderLib/EncCu.cpp b/source/Lib/EncoderLib/EncCu.cpp
index 920639e84..117a437ca 100644
--- a/source/Lib/EncoderLib/EncCu.cpp
+++ b/source/Lib/EncoderLib/EncCu.cpp
@@ -88,11 +88,19 @@ void EncCu::create( EncCfg* encCfg )
   unsigned      numHeights    = gp_sizeIdxInfo->numHeights();
   m_pTempCS = new CodingStructure**  [numWidths];
   m_pBestCS = new CodingStructure**  [numWidths];
+#if JVET_O0050_LOCAL_DUAL_TREE
+  m_pTempCS2 = new CodingStructure** [numWidths];
+  m_pBestCS2 = new CodingStructure** [numWidths];
+#endif
 
   for( unsigned w = 0; w < numWidths; w++ )
   {
     m_pTempCS[w] = new CodingStructure*  [numHeights];
     m_pBestCS[w] = new CodingStructure*  [numHeights];
+    #if JVET_O0050_LOCAL_DUAL_TREE
+    m_pTempCS2[w] = new CodingStructure* [numHeights];
+    m_pBestCS2[w] = new CodingStructure* [numHeights];
+#endif
 
     for( unsigned h = 0; h < numHeights; h++ )
     {
@@ -106,11 +114,23 @@ void EncCu::create( EncCfg* encCfg )
 
         m_pTempCS[w][h]->create( chromaFormat, Area( 0, 0, width, height ), false );
         m_pBestCS[w][h]->create( chromaFormat, Area( 0, 0, width, height ), false );
+
+#if JVET_O0050_LOCAL_DUAL_TREE
+        m_pTempCS2[w][h] = new CodingStructure( m_unitCache.cuCache, m_unitCache.puCache, m_unitCache.tuCache );
+        m_pBestCS2[w][h] = new CodingStructure( m_unitCache.cuCache, m_unitCache.puCache, m_unitCache.tuCache );
+
+        m_pTempCS2[w][h]->create( chromaFormat, Area( 0, 0, width, height ), false );
+        m_pBestCS2[w][h]->create( chromaFormat, Area( 0, 0, width, height ), false );
+#endif
       }
       else
       {
         m_pTempCS[w][h] = nullptr;
         m_pBestCS[w][h] = nullptr;
+#if JVET_O0050_LOCAL_DUAL_TREE
+        m_pTempCS2[w][h] = nullptr;
+        m_pBestCS2[w][h] = nullptr;
+#endif
       }
     }
   }
@@ -190,14 +210,30 @@ void EncCu::destroy()
 
       delete m_pBestCS[w][h];
       delete m_pTempCS[w][h];
+
+#if JVET_O0050_LOCAL_DUAL_TREE
+      if( m_pBestCS2[w][h] ) m_pBestCS2[w][h]->destroy();
+      if( m_pTempCS2[w][h] ) m_pTempCS2[w][h]->destroy();
+
+      delete m_pBestCS2[w][h];
+      delete m_pTempCS2[w][h];
+#endif
     }
 
     delete[] m_pTempCS[w];
     delete[] m_pBestCS[w];
+#if JVET_O0050_LOCAL_DUAL_TREE
+    delete[] m_pTempCS2[w];
+    delete[] m_pBestCS2[w];
+#endif
   }
 
   delete[] m_pBestCS; m_pBestCS = nullptr;
   delete[] m_pTempCS; m_pTempCS = nullptr;
+#if JVET_O0050_LOCAL_DUAL_TREE
+  delete[] m_pBestCS2; m_pBestCS2 = nullptr;
+  delete[] m_pTempCS2; m_pTempCS2 = nullptr;
+#endif
 
 #if REUSE_CU_RESULTS
   if (m_tmpStorageLCU)
@@ -290,6 +326,9 @@ void EncCu::init( EncLib* pcEncLib, const SPS& sps PARL_PARAM( const int tId ) )
 void EncCu::compressCtu( CodingStructure& cs, const UnitArea& area, const unsigned ctuRsAddr, const int prevQP[], const int currQP[] )
 {
   m_modeCtrl->initCTUEncoding( *cs.slice );
+#if JVET_O0050_LOCAL_DUAL_TREE
+  cs.treeType = TREE_D;
+#endif
 
 #if ENABLE_SPLIT_PARALLELISM
   if( m_pcEncCfg->getNumSplitThreads() > 1 )
@@ -604,6 +643,11 @@ void EncCu::xCompressCU( CodingStructure *&tempCS, CodingStructure *&bestCS, Par
   const uint32_t uiLPelX  = tempCS->area.Y().lumaPos().x;
   const uint32_t uiTPelY  = tempCS->area.Y().lumaPos().y;
 
+#if JVET_O0050_LOCAL_DUAL_TREE
+  const ModeType modeTypeParent  = partitioner.modeType;
+  const TreeType treeTypeParent  = partitioner.treeType;
+  const ChannelType chTypeParent = partitioner.chType;
+#endif
   const UnitArea currCsArea = clipArea( CS::getArea( *bestCS, bestCS->area, partitioner.chType ), *tempCS->picture );
 
   m_modeCtrl->initCULevel( partitioner, *tempCS );
@@ -658,7 +702,11 @@ void EncCu::xCompressCU( CodingStructure *&tempCS, CodingStructure *&bestCS, Par
   {
     EncTestMode currTestMode = m_modeCtrl->currTestMode();
 
+#if JVET_O0050_LOCAL_DUAL_TREE
+    if (pps.getUseDQP() && partitioner.isSepTree(*tempCS) && isChroma( partitioner.chType ))
+#else
     if (pps.getUseDQP() && CS::isDualITree(*tempCS) && isChroma(partitioner.chType))
+#endif
     {
       const Position chromaCentral(tempCS->area.Cb().chromaPos().offset(tempCS->area.Cb().chromaSize().width >> 1, tempCS->area.Cb().chromaSize().height >> 1));
       const Position lumaRefPos(chromaCentral.x << getComponentScaleX(COMPONENT_Cb, tempCS->area.chromaFormat), chromaCentral.y << getComponentScaleY(COMPONENT_Cb, tempCS->area.chromaFormat));
@@ -752,8 +800,68 @@ void EncCu::xCompressCU( CodingStructure *&tempCS, CodingStructure *&bestCS, Par
     }
     else if( isModeSplit( currTestMode ) )
     {
+#if JVET_O0050_LOCAL_DUAL_TREE
+      assert( partitioner.modeType == tempCS->modeType );
+      int signalModeConsVal = tempCS->signalModeCons( getPartSplit( currTestMode ), partitioner, modeTypeParent );
+      int num_round_rdo = signalModeConsVal == 2 ? 2 : 1;
+      bool skipInterPass = false;
+      for( int i = 0; i < num_round_rdo; i++ )
+      {
+        //change cons modes
+        if( signalModeConsVal == 2 )
+        {
+          CHECK( num_round_rdo != 2, "num_round_rdo shall be 2 - [2]" );
+          tempCS->modeType = partitioner.modeType = (i == 0) ? MODE_TYPE_INTER : MODE_TYPE_INTRA;
+        }
+        else if( signalModeConsVal == 1 )
+        {
+          CHECK( num_round_rdo != 1, "num_round_rdo shall be 1 - [1]" );
+          tempCS->modeType = partitioner.modeType = MODE_TYPE_INTRA;
+        }
+        else if( signalModeConsVal == 0 )
+        {
+          CHECK( num_round_rdo != 1, "num_round_rdo shall be 1 - [0]" );
+          tempCS->modeType = partitioner.modeType = modeTypeParent;
+        }
 
+        //for lite intra encoding fast algorithm, set the status to save inter coding info
+        if( modeTypeParent == MODE_TYPE_ALL && tempCS->modeType == MODE_TYPE_INTER )
+        {
+          m_pcIntraSearch->setSaveCuCostInSCIPU( true );
+          m_pcIntraSearch->setNumCuInSCIPU( 0 );
+        }
+        else if( modeTypeParent == MODE_TYPE_ALL && tempCS->modeType != MODE_TYPE_INTER )
+        {
+          m_pcIntraSearch->setSaveCuCostInSCIPU( false );
+          if( tempCS->modeType == MODE_TYPE_ALL )
+          {
+            m_pcIntraSearch->setNumCuInSCIPU( 0 );
+          }
+        }
+
+        xCheckModeSplit( tempCS, bestCS, partitioner, currTestMode, modeTypeParent, skipInterPass );
+#else
       xCheckModeSplit( tempCS, bestCS, partitioner, currTestMode );
+#endif
+#if JVET_O0050_LOCAL_DUAL_TREE
+        //recover cons modes
+        tempCS->modeType = partitioner.modeType = modeTypeParent;
+        tempCS->treeType = partitioner.treeType = treeTypeParent;
+        partitioner.chType = chTypeParent;
+        if( modeTypeParent == MODE_TYPE_ALL )
+        {
+          m_pcIntraSearch->setSaveCuCostInSCIPU( false );
+          if( num_round_rdo == 2 && tempCS->modeType == MODE_TYPE_INTRA )
+          {
+            m_pcIntraSearch->initCuAreaCostInSCIPU();
+          }
+        }
+        if( skipInterPass )
+        {
+          break;
+        }
+      }
+#endif
     }
     else
     {
@@ -810,6 +918,12 @@ void EncCu::xCompressCU( CodingStructure *&tempCS, CodingStructure *&bestCS, Par
   bestCS->picture->getPredBuf(currCsArea).copyFrom(bestCS->getPredBuf(currCsArea));
   bestCS->picture->getRecoBuf( currCsArea ).copyFrom( bestCS->getRecoBuf( currCsArea ) );
   m_modeCtrl->finishCULevel( partitioner );
+#if JVET_O0050_LOCAL_DUAL_TREE
+  if( m_pcIntraSearch->getSaveCuCostInSCIPU() && bestCS->cus.size() == 1 )
+  {
+    m_pcIntraSearch->saveCuAreaCostInSCIPU( Area( partitioner.currArea().lumaPos(), partitioner.currArea().lumaSize() ), bestCS->cost );
+  }
+#endif
 
 #if ENABLE_SPLIT_PARALLELISM
   if( tempCS->picture->scheduler.getSplitJobId() == 0 && m_pcEncCfg->getNumSplitThreads() != 1 )
@@ -1095,7 +1209,11 @@ void EncCu::copyState( EncCu* other, Partitioner& partitioner, const UnitArea& c
 }
 #endif
 
+#if JVET_O0050_LOCAL_DUAL_TREE
+void EncCu::xCheckModeSplit(CodingStructure *&tempCS, CodingStructure *&bestCS, Partitioner &partitioner, const EncTestMode& encTestMode, const ModeType modeTypeParent, bool &skipInterPass )
+#else
 void EncCu::xCheckModeSplit(CodingStructure *&tempCS, CodingStructure *&bestCS, Partitioner &partitioner, const EncTestMode& encTestMode )
+#endif
 {
   const int qp                = encTestMode.qp;
   const Slice &slice          = *tempCS->slice;
@@ -1108,6 +1226,9 @@ void EncCu::xCheckModeSplit(CodingStructure *&tempCS, CodingStructure *&bestCS,
 #endif
 
   const PartSplit split = getPartSplit( encTestMode );
+#if JVET_O0050_LOCAL_DUAL_TREE
+  const ModeType modeTypeChild = partitioner.modeType;
+#endif
 
   CHECK( split == CU_DONT_SPLIT, "No proper split provided!" );
 
@@ -1119,10 +1240,15 @@ void EncCu::xCheckModeSplit(CodingStructure *&tempCS, CodingStructure *&bestCS,
   const TempCtx ctxStartQt( m_CtxCache, SubCtx( Ctx::SplitQtFlag, m_CABACEstimator->getCtx() ) );
   const TempCtx ctxStartHv( m_CtxCache, SubCtx( Ctx::SplitHvFlag, m_CABACEstimator->getCtx() ) );
   const TempCtx ctxStart12( m_CtxCache, SubCtx( Ctx::Split12Flag, m_CABACEstimator->getCtx() ) );
-
+#if JVET_O0050_LOCAL_DUAL_TREE
+  const TempCtx ctxStartMC( m_CtxCache, SubCtx( Ctx::ModeConsFlag, m_CABACEstimator->getCtx() ) );
+#endif
   m_CABACEstimator->resetBits();
 
   m_CABACEstimator->split_cu_mode( split, *tempCS, partitioner );
+#if JVET_O0050_LOCAL_DUAL_TREE
+  m_CABACEstimator->mode_constraint( split, *tempCS, partitioner, modeTypeChild );
+#endif
 
   const double factor = ( tempCS->currQP[partitioner.chType] > 30 ? 1.1 : 1.075 );
   tempCS->useDbCost = m_pcEncCfg->getUseEncDbOpt();
@@ -1134,7 +1260,9 @@ void EncCu::xCheckModeSplit(CodingStructure *&tempCS, CodingStructure *&bestCS,
   m_CABACEstimator->getCtx() = SubCtx( Ctx::SplitQtFlag, ctxStartQt );
   m_CABACEstimator->getCtx() = SubCtx( Ctx::SplitHvFlag, ctxStartHv );
   m_CABACEstimator->getCtx() = SubCtx( Ctx::Split12Flag, ctxStart12 );
-
+#if JVET_O0050_LOCAL_DUAL_TREE
+  m_CABACEstimator->getCtx() = SubCtx( Ctx::ModeConsFlag, ctxStartMC );
+#endif
   if (cost > bestCS->cost + bestCS->costDbOffset
 #if ENABLE_QPA_SUB_CTU
     || (m_pcEncCfg->getUsePerceptQPA() && !m_pcEncCfg->getUseRateCtrl() && pps.getUseDQP() && (pps.getCuQpDeltaSubdiv() > 0) && (split == CU_HORZ_SPLIT || split == CU_VERT_SPLIT) &&
@@ -1146,6 +1274,26 @@ void EncCu::xCheckModeSplit(CodingStructure *&tempCS, CodingStructure *&bestCS,
     return;
   }
 
+#if JVET_O0050_LOCAL_DUAL_TREE
+  const bool chromaNotSplit = modeTypeParent == MODE_TYPE_ALL && modeTypeChild == MODE_TYPE_INTRA ? true : false;
+  if( partitioner.treeType != TREE_D )
+  {
+    tempCS->treeType = TREE_L;
+  }
+  else
+  {
+    if( chromaNotSplit )
+    {
+      CHECK( partitioner.chType != CHANNEL_TYPE_LUMA, "chType must be luma" );
+      tempCS->treeType = partitioner.treeType = TREE_L;
+    }
+    else
+    {
+      tempCS->treeType = partitioner.treeType = TREE_D;
+    }
+  }
+#endif
+
   int startShareThisLevel = 0;
   const uint32_t uiLPelX = tempCS->area.Y().lumaPos().x;
   const uint32_t uiTPelY = tempCS->area.Y().lumaPos().y;
@@ -1239,6 +1387,18 @@ void EncCu::xCheckModeSplit(CodingStructure *&tempCS, CodingStructure *&bestCS,
         m_CurrCtx--;
         partitioner.exitCurrSplit();
         xCheckBestMode( tempCS, bestCS, partitioner, encTestMode );
+#if JVET_O0050_LOCAL_DUAL_TREE //early terminate
+        if( partitioner.chType == CHANNEL_TYPE_LUMA )
+        {
+          tempCS->motionLut = oldMotionLut;
+        }
+        if( startShareThisLevel == 1 )
+        {
+          m_shareState = NO_SHARE;
+          m_pcInterSearch->setShareState( m_shareState );
+          setShareStateDec( m_shareState );
+        }
+#endif
         return;
       }
 
@@ -1249,9 +1409,50 @@ void EncCu::xCheckModeSplit(CodingStructure *&tempCS, CodingStructure *&bestCS,
       {
         tempCS->prevQP[partitioner.chType] = bestSubCS->prevQP[partitioner.chType];
       }
+#if JVET_O0050_LOCAL_DUAL_TREE
+      if( partitioner.isConsInter() )
+      {
+        for( int i = 0; i < bestSubCS->cus.size(); i++ )
+        {
+          CHECK( bestSubCS->cus[i]->predMode != MODE_INTER, "all CUs must be inter mode in an Intra coding region (SCIPU)" );
+        }
+      }
+      else if( partitioner.isConsIntra() )
+      {
+        for( int i = 0; i < bestSubCS->cus.size(); i++ )
+        {
+          CHECK( bestSubCS->cus[i]->predMode != MODE_INTRA && bestSubCS->cus[i]->predMode != MODE_IBC, "all CUs must be intra/ibc mode in an Intra coding region (SCIPU)" );
+        }
+      }
+#endif
 
       tempSubCS->releaseIntermediateData();
       bestSubCS->releaseIntermediateData();
+#if JVET_O0050_LOCAL_DUAL_TREE //early terminate
+      if( !tempCS->slice->isIntra() && partitioner.isConsIntra() )
+      {
+        tempCS->cost = m_pcRdCost->calcRdCost( tempCS->fracBits, tempCS->dist );
+        if( tempCS->cost > bestCS->cost )
+        {
+          tempCS->cost = MAX_DOUBLE;
+          tempCS->costDbOffset = 0;
+          tempCS->useDbCost = m_pcEncCfg->getUseEncDbOpt();
+          m_CurrCtx--;
+          partitioner.exitCurrSplit();
+          if( partitioner.chType == CHANNEL_TYPE_LUMA )
+          {
+            tempCS->motionLut = oldMotionLut;
+          }
+          if( startShareThisLevel == 1 )
+          {
+            m_shareState = NO_SHARE;
+            m_pcInterSearch->setShareState( m_shareState );
+            setShareStateDec( m_shareState );
+          }
+          return;
+        }
+      }
+#endif
     }
   } while( partitioner.nextPart( *tempCS ) );
 
@@ -1266,6 +1467,49 @@ void EncCu::xCheckModeSplit(CodingStructure *&tempCS, CodingStructure *&bestCS,
 
   m_CurrCtx--;
 
+#if JVET_O0050_LOCAL_DUAL_TREE
+  if( chromaNotSplit )
+  {
+    assert( tempCS->treeType == TREE_L );
+    uint32_t numCuPuTu[6];
+    tempCS->picture->cs->getNumCuPuTuOffset( numCuPuTu );
+    tempCS->picture->cs->useSubStructure( *tempCS, partitioner.chType, CS::getArea( *tempCS, partitioner.currArea(), partitioner.chType ), false, true, false, false );
+
+    partitioner.chType = CHANNEL_TYPE_CHROMA;
+    tempCS->treeType = partitioner.treeType = TREE_C;
+
+    m_CurrCtx++;
+
+    const unsigned wIdx = gp_sizeIdxInfo->idxFrom( partitioner.currArea().lwidth() );
+    const unsigned hIdx = gp_sizeIdxInfo->idxFrom( partitioner.currArea().lheight() );
+    CodingStructure *tempCSChroma = m_pTempCS2[wIdx][hIdx];
+    CodingStructure *bestCSChroma = m_pBestCS2[wIdx][hIdx];
+    tempCS->initSubStructure( *tempCSChroma, partitioner.chType, partitioner.currArea(), false );
+    tempCS->initSubStructure( *bestCSChroma, partitioner.chType, partitioner.currArea(), false );
+    tempCS->treeType = TREE_D;
+    xCompressCU( tempCSChroma, bestCSChroma, partitioner );
+
+    //attach chromaCS to luma CS and update cost
+    bool keepResi = KEEP_PRED_AND_RESI_SIGNALS;
+    //bestCSChroma->treeType = tempCSChroma->treeType = TREE_C;
+    CHECK( bestCSChroma->treeType != TREE_C || tempCSChroma->treeType != TREE_C, "wrong treeType for chroma CS" );
+    tempCS->useSubStructure( *bestCSChroma, partitioner.chType, CS::getArea( *bestCSChroma, partitioner.currArea(), partitioner.chType ), KEEP_PRED_AND_RESI_SIGNALS, true, keepResi, true );
+
+    //release tmp resource
+    tempCSChroma->releaseIntermediateData();
+    bestCSChroma->releaseIntermediateData();
+    //tempCS->picture->cs->releaseIntermediateData();
+    tempCS->picture->cs->clearCuPuTuIdxMap( partitioner.currArea(), numCuPuTu[0], numCuPuTu[1], numCuPuTu[2], numCuPuTu + 3 );
+
+    m_CurrCtx--;
+
+    //recover luma tree status
+    partitioner.chType = CHANNEL_TYPE_LUMA;
+    partitioner.treeType = TREE_D;
+    partitioner.modeType = MODE_TYPE_ALL;
+  }
+#endif
+
   // Finally, generate split-signaling bits for RD-cost check
   const PartSplit implicitSplit = partitioner.getImplicitSplit( *tempCS );
 
@@ -1295,7 +1539,10 @@ void EncCu::xCheckModeSplit(CodingStructure *&tempCS, CodingStructure *&bestCS,
       m_CABACEstimator->resetBits();
 
       m_CABACEstimator->split_cu_mode( split, *tempCS, partitioner );
-
+#if JVET_O0050_LOCAL_DUAL_TREE
+      partitioner.modeType = modeTypeParent;
+      m_CABACEstimator->mode_constraint( split, *tempCS, partitioner, modeTypeChild );
+#endif
       tempCS->fracBits += m_CABACEstimator->getEstFracBits(); // split bits
     }
   }
@@ -1330,6 +1577,20 @@ void EncCu::xCheckModeSplit(CodingStructure *&tempCS, CodingStructure *&bestCS,
     bestCS->costDbOffset = 0;
   }
   tempCS->useDbCost = m_pcEncCfg->getUseEncDbOpt();
+#if JVET_O0050_LOCAL_DUAL_TREE
+  if( tempCS->cus.size() > 0 && modeTypeParent == MODE_TYPE_ALL && modeTypeChild == MODE_TYPE_INTER )
+  {
+    int areaSizeNoResiCu = 0;
+    for( int k = 0; k < tempCS->cus.size(); k++ )
+    {
+      areaSizeNoResiCu += (tempCS->cus[k]->rootCbf == false) ? tempCS->cus[k]->lumaSize().area() : 0;
+    }
+    if( areaSizeNoResiCu >= (tempCS->area.lumaSize().area() >> 1) )
+    {
+      skipInterPass = true;
+    }
+  }
+#endif
 
   // RD check for sub partitioned coding structure.
   xCheckBestMode( tempCS, bestCS, partitioner, encTestMode );
@@ -1378,7 +1639,11 @@ void EncCu::xCheckRDCostIntra( CodingStructure *&tempCS, CodingStructure *&bestC
   int    bestMtsFlag             =   0;
   int    bestLfnstIdx            =   0;
 
+#if JVET_O0050_LOCAL_DUAL_TREE
+  const int  maxLfnstIdx         = partitioner.isSepTree( *tempCS ) && partitioner.chType == CHANNEL_TYPE_CHROMA && ( partitioner.currArea().lwidth() < 8 || partitioner.currArea().lheight() < 8 ) ? 0 : 2;
+#else
   const int  maxLfnstIdx         = CS::isDualITree( *tempCS ) && partitioner.chType == CHANNEL_TYPE_CHROMA && ( partitioner.currArea().lwidth() < 8 || partitioner.currArea().lheight() < 8 ) ? 0 : 2;
+#endif
   bool       skipOtherLfnst      = false;
   int        startLfnstIdx       = 0;
   int        endLfnstIdx         = sps.getUseLFNST() ? maxLfnstIdx : 0;
@@ -1435,7 +1700,11 @@ void EncCu::xCheckRDCostIntra( CodingStructure *&tempCS, CodingStructure *&bestC
           if( isLuma( partitioner.chType ) )
           {
             //the Intra SubPartitions mode uses the value of the best cost so far (luma if it is the fast version) to avoid test non-necessary lines
+#if JVET_O0050_LOCAL_DUAL_TREE
+            const double bestCostSoFar = partitioner.isSepTree( *tempCS ) ? m_modeCtrl->getBestCostWithoutSplitFlags() : bestCU && bestCU->predMode == MODE_INTRA ? bestCS->lumaCost : bestCS->cost;
+#else
             const double bestCostSoFar = CS::isDualITree( *tempCS ) ? m_modeCtrl->getBestCostWithoutSplitFlags() : bestCU && bestCU->predMode == MODE_INTRA ? bestCS->lumaCost : bestCS->cost;
+#endif
             validCandRet = m_pcIntraSearch->estIntraPredLumaQT( cu, partitioner, bestCostSoFar, mtsFlag, startMTSIdx[ trGrpIdx ], endMTSIdx[ trGrpIdx ], ( trGrpIdx > 0 ) );
             if( sps.getUseLFNST() && ( !validCandRet || ( cu.ispMode && cu.firstTU->cbf[ COMPONENT_Y ] == 0 ) ) )
             {
@@ -1443,7 +1712,11 @@ void EncCu::xCheckRDCostIntra( CodingStructure *&tempCS, CodingStructure *&bestC
             }
 
             useIntraSubPartitions = cu.ispMode != NOT_INTRA_SUBPARTITIONS;
+#if JVET_O0050_LOCAL_DUAL_TREE
+            if( !partitioner.isSepTree( *tempCS ) )
+#else
             if( !CS::isDualITree( *tempCS ) )
+#endif
             {
               tempCS->lumaCost = m_pcRdCost->calcRdCost( tempCS->fracBits, tempCS->dist );
               if( useIntraSubPartitions )
@@ -1462,17 +1735,29 @@ void EncCu::xCheckRDCostIntra( CodingStructure *&tempCS, CodingStructure *&bestC
               continue;
             }
 
+#if JVET_O0050_LOCAL_DUAL_TREE
+            if( !partitioner.isSepTree( *tempCS ) )
+#else
             if( !CS::isDualITree( *tempCS ) )
+#endif
             {
               cu.cs->picture->getRecoBuf( cu.Y() ).copyFrom( cu.cs->getRecoBuf( COMPONENT_Y ) );
               cu.cs->picture->getPredBuf(cu.Y()).copyFrom(cu.cs->getPredBuf(COMPONENT_Y));
             }
           }
 
+#if JVET_O0050_LOCAL_DUAL_TREE
+          if( tempCS->area.chromaFormat != CHROMA_400 && ( partitioner.chType == CHANNEL_TYPE_CHROMA || !cu.isSepTree() ) )
+#else
           if( tempCS->area.chromaFormat != CHROMA_400 && ( partitioner.chType == CHANNEL_TYPE_CHROMA || !CS::isDualITree( *tempCS ) ) )
+#endif
           {
             TUIntraSubPartitioner subTuPartitioner( partitioner );
+#if JVET_O0050_LOCAL_DUAL_TREE
+            m_pcIntraSearch->estIntraPredChromaQT( cu, ( !useIntraSubPartitions || ( cu.isSepTree() && !isLuma( CHANNEL_TYPE_CHROMA ) ) ) ? partitioner : subTuPartitioner, maxCostAllowedForChroma );
+#else
             m_pcIntraSearch->estIntraPredChromaQT( cu, ( !useIntraSubPartitions || ( CS::isDualITree( *cu.cs ) && !isLuma( CHANNEL_TYPE_CHROMA ) ) ) ? partitioner : subTuPartitioner, maxCostAllowedForChroma );
+#endif
             if( useIntraSubPartitions && !cu.ispMode )
             {
               //At this point the temp cost is larger than the best cost. Therefore, we can already skip the remaining calculations
@@ -1515,7 +1800,11 @@ void EncCu::xCheckRDCostIntra( CodingStructure *&tempCS, CodingStructure *&bestC
           tempCS->fracBits = m_CABACEstimator->getEstFracBits();
           tempCS->cost     = m_pcRdCost->calcRdCost(tempCS->fracBits, tempCS->dist);
 
+#if JVET_O0050_LOCAL_DUAL_TREE
+          double bestIspCost = cu.ispMode ? cu.isSepTree() ? tempCS->cost : tempCS->lumaCost : MAX_DOUBLE;
+#else
           double bestIspCost = cu.ispMode ? CS::isDualITree( *tempCS ) ? tempCS->cost : tempCS->lumaCost : MAX_DOUBLE;
+#endif
 
           const double tmpCostWithoutSplitFlags = tempCS->cost;
           xEncodeDontSplit( *tempCS, partitioner );
@@ -1523,7 +1812,11 @@ void EncCu::xCheckRDCostIntra( CodingStructure *&tempCS, CodingStructure *&bestC
           xCheckDQP( *tempCS, partitioner );
 
           // Check if low frequency non-separable transform (LFNST) is too expensive
+#if JVET_O0050_LOCAL_DUAL_TREE
+          const int nonZeroCoeffThr = cu.isSepTree() ? ( isLuma( partitioner.chType ) ? LFNST_SIG_NZ_LUMA : LFNST_SIG_NZ_CHROMA ) : LFNST_SIG_NZ_LUMA + LFNST_SIG_NZ_CHROMA;
+#else
           const int nonZeroCoeffThr = CS::isDualITree( *tempCS ) ? ( isLuma( partitioner.chType ) ? LFNST_SIG_NZ_LUMA : LFNST_SIG_NZ_CHROMA ) : LFNST_SIG_NZ_LUMA + LFNST_SIG_NZ_CHROMA;
+#endif
           if( lfnstIdx && cuCtx.numNonZeroCoeffNonTs <= nonZeroCoeffThr )
           {
             if (cuCtx.numNonZeroCoeffNonTs > 0)
@@ -1711,7 +2004,11 @@ void EncCu::xCheckDQP( CodingStructure& cs, Partitioner& partitioner, bool bKeep
     return;
   }
 
+#if JVET_O0050_LOCAL_DUAL_TREE
+  if (partitioner.isSepTree(cs) && isChroma(partitioner.chType))
+#else
   if (CS::isDualITree(cs) && isChroma(partitioner.chType))
+#endif
   {
     return;
   }
@@ -3177,7 +3474,11 @@ void EncCu::xCheckRDCostIBCModeMerge2Nx2N(CodingStructure *&tempCS, CodingStruct
             PU::spanMotionInfo(pu, mergeCtx);
 
             assert(mergeCtx.mrgTypeNeighbours[mergeCand] == MRG_TYPE_IBC); //  should be IBC candidate at this round
+#if JVET_O0050_LOCAL_DUAL_TREE
+            const bool chroma = !pu.cu->isSepTree();
+#else
             const bool chroma = !(CS::isDualITree(*tempCS));
+#endif
 
             //  MC
             m_pcInterSearch->motionCompensation(pu,REF_PIC_LIST_0, true, chroma);
@@ -3273,7 +3574,11 @@ void EncCu::xCheckRDCostIBCMode(CodingStructure *&tempCS, CodingStructure *&best
       if (bValid)
       {
         PU::spanMotionInfo(pu);
+#if JVET_O0050_LOCAL_DUAL_TREE
+        const bool chroma = !pu.cu->isSepTree();
+#else
         const bool chroma = !(CS::isDualITree(*tempCS));
+#endif
         //  MC
         m_pcInterSearch->motionCompensation(pu, REF_PIC_LIST_0, true, chroma);
 
@@ -3713,8 +4018,13 @@ void EncCu::xCalDebCost( CodingStructure &cs, Partitioner &partitioner, bool cal
   if ( calDist )
   {
     const UnitArea currCsArea = clipArea( CS::getArea( cs, cs.area, partitioner.chType ), *cs.picture );
+#if JVET_O0050_LOCAL_DUAL_TREE
+    ComponentID compStr = ( cu->isSepTree() && !isLuma( partitioner.chType ) ) ? COMPONENT_Cb : COMPONENT_Y;
+    ComponentID compEnd = ( cu->isSepTree() && isLuma( partitioner.chType ) ) ? COMPONENT_Y : COMPONENT_Cr;
+#else
     ComponentID compStr = ( CS::isDualITree( cs ) && !isLuma( partitioner.chType ) ) ? COMPONENT_Cb : COMPONENT_Y;
     ComponentID compEnd = ( CS::isDualITree( cs ) && isLuma( partitioner.chType ) ) ? COMPONENT_Y : COMPONENT_Cr;
+#endif
     Distortion finalDistortion = 0;
     for ( int comp = compStr; comp <= compEnd; comp++ )
     {
@@ -3729,8 +4039,13 @@ void EncCu::xCalDebCost( CodingStructure &cs, Partitioner &partitioner, bool cal
 
   if ( anyEdgeAvai && m_pcEncCfg->getUseEncDbOpt() )
   {
+#if JVET_O0050_LOCAL_DUAL_TREE
+    ComponentID compStr = ( cu->isSepTree() && !isLuma( partitioner.chType ) ) ? COMPONENT_Cb : COMPONENT_Y;
+    ComponentID compEnd = ( cu->isSepTree() &&  isLuma( partitioner.chType ) ) ? COMPONENT_Y : COMPONENT_Cr;
+#else
     ComponentID compStr = ( CS::isDualITree( cs ) && !isLuma( partitioner.chType ) ) ? COMPONENT_Cb : COMPONENT_Y;
     ComponentID compEnd = ( CS::isDualITree( cs ) &&  isLuma( partitioner.chType ) ) ? COMPONENT_Y : COMPONENT_Cr;
+#endif
 
     const UnitArea currCsArea = clipArea( CS::getArea( cs, cs.area, partitioner.chType ), *cs.picture );
 
@@ -4291,6 +4606,10 @@ void EncCu::xEncodeDontSplit( CodingStructure &cs, Partitioner &partitioner )
   m_CABACEstimator->resetBits();
 
   m_CABACEstimator->split_cu_mode( CU_DONT_SPLIT, cs, partitioner );
+#if JVET_O0050_LOCAL_DUAL_TREE
+  if( partitioner.treeType == TREE_C )
+    CHECK( m_CABACEstimator->getEstFracBits() != 0, "must be 0 bit" );
+#endif
 
   cs.fracBits += m_CABACEstimator->getEstFracBits(); // split bits
   cs.cost      = m_pcRdCost->calcRdCost( cs.fracBits, cs.dist );
@@ -4338,7 +4657,11 @@ void EncCu::xReuseCachedResult( CodingStructure *&tempCS, CodingStructure *&best
     {
       const ComponentID compID = ComponentID( comp );
 
+#if JVET_O0050_LOCAL_DUAL_TREE
+      if( partitioner.isSepTree( *tempCS ) && toChannelType( compID ) != partitioner.chType )
+#else
       if( CS::isDualITree( *tempCS ) && toChannelType( compID ) != partitioner.chType )
+#endif
       {
         continue;
       }
diff --git a/source/Lib/EncoderLib/EncCu.h b/source/Lib/EncoderLib/EncCu.h
index 67ef7f05f..a6ec27862 100644
--- a/source/Lib/EncoderLib/EncCu.h
+++ b/source/Lib/EncoderLib/EncCu.h
@@ -102,6 +102,10 @@ private:
 
   CodingStructure    ***m_pTempCS;
   CodingStructure    ***m_pBestCS;
+#if JVET_O0050_LOCAL_DUAL_TREE
+  CodingStructure    ***m_pTempCS2;
+  CodingStructure    ***m_pBestCS2;
+#endif
   //  Access channel
   EncCfg*               m_pcEncCfg;
   IntraSearch*          m_pcIntraSearch;
@@ -184,7 +188,11 @@ protected:
   bool
     xCheckBestMode         ( CodingStructure *&tempCS, CodingStructure *&bestCS, Partitioner &pm, const EncTestMode& encTestmode );
 
+#if JVET_O0050_LOCAL_DUAL_TREE
+  void xCheckModeSplit        ( CodingStructure *&tempCS, CodingStructure *&bestCS, Partitioner &pm, const EncTestMode& encTestMode, const ModeType modeTypeParent, bool &skipInterPass );
+#else
   void xCheckModeSplit        ( CodingStructure *&tempCS, CodingStructure *&bestCS, Partitioner &pm, const EncTestMode& encTestMode );
+#endif
 
   void xCheckRDCostIntra      ( CodingStructure *&tempCS, CodingStructure *&bestCS, Partitioner &pm, const EncTestMode& encTestMode );
   void xCheckIntraPCM         ( CodingStructure *&tempCS, CodingStructure *&bestCS, Partitioner &pm, const EncTestMode& encTestMode );
diff --git a/source/Lib/EncoderLib/EncModeCtrl.cpp b/source/Lib/EncoderLib/EncModeCtrl.cpp
index e59cca54e..31a6cf582 100644
--- a/source/Lib/EncoderLib/EncModeCtrl.cpp
+++ b/source/Lib/EncoderLib/EncModeCtrl.cpp
@@ -881,11 +881,23 @@ bool BestEncInfoCache::setFromCs( const CodingStructure& cs, const Partitioner&
 
 bool BestEncInfoCache::isValid( const CodingStructure& cs, const Partitioner& partitioner, int qp )
 {
+#if JVET_O0050_LOCAL_DUAL_TREE
+  if( partitioner.treeType == TREE_C )
+  {
+    return false; //if save & load is allowed for chroma CUs, we should check whether luma info (pred, recon, etc) is the same, which is quite complex
+  }
+#endif
   unsigned idx1, idx2, idx3, idx4;
   getAreaIdx( cs.area.Y(), *m_slice_bencinf->getPPS()->pcv, idx1, idx2, idx3, idx4 );
 
   BestEncodingInfo& encInfo = *m_bestEncInfo[idx1][idx2][idx3][idx4];
 
+#if JVET_O0050_LOCAL_DUAL_TREE
+  if( encInfo.cu.treeType != partitioner.treeType || encInfo.cu.modeType != partitioner.modeType )
+  {
+    return false;
+  }
+#endif
   if( encInfo.cu.qp != qp )
     return false;
   if( cs.picture->poc != encInfo.poc || CS::getArea( cs, cs.area, partitioner.chType ) != CS::getArea( cs, encInfo.cu, partitioner.chType ) || !isTheSameNbHood( encInfo.cu, cs, partitioner
@@ -1140,7 +1152,11 @@ void EncModeCtrlMTnoRQT::initCULevel( Partitioner &partitioner, const CodingStru
 
   // QP
   int baseQP = cs.baseQP;
+#if JVET_O0050_LOCAL_DUAL_TREE
+  if (!partitioner.isSepTree(cs) || isLuma(partitioner.chType))
+#else
   if (!CS::isDualITree (cs) || isLuma (partitioner.chType))
+#endif
   {
     if (m_pcEncCfg->getUseAdaptiveQP())
     {
@@ -1296,6 +1312,20 @@ void EncModeCtrlMTnoRQT::initCULevel( Partitioner &partitioner, const CodingStru
 
   //////////////////////////////////////////////////////////////////////////
   // Add unit coding modes: Intra, InterME, InterMerge ...
+#if JVET_O0050_LOCAL_DUAL_TREE
+  bool try_intra_rdo = true;
+  bool try_inter_rdo = true;
+  bool try_ibc_rdo = true;
+  if( partitioner.isConsIntra() )
+  {
+    try_inter_rdo = false;
+  }
+  else if( partitioner.isConsInter() )
+  {
+    try_intra_rdo = try_ibc_rdo = false;
+  }
+  checkIbc &= try_ibc_rdo;
+#endif
 
   for( int qpLoop = maxQP; qpLoop >= minQP; qpLoop-- )
   {
@@ -1310,8 +1340,15 @@ void EncModeCtrlMTnoRQT::initCULevel( Partitioner &partitioner, const CodingStru
     }
 #endif
     // add intra modes
+#if JVET_O0050_LOCAL_DUAL_TREE
+    if( try_intra_rdo )
+    {
+#endif
     m_ComprCUCtxList.back().testModes.push_back( { ETM_IPCM,  ETO_STANDARD, qp, lossless } );
     m_ComprCUCtxList.back().testModes.push_back( { ETM_INTRA, ETO_STANDARD, qp, lossless } );
+#if JVET_O0050_LOCAL_DUAL_TREE
+    }
+#endif
     // add ibc mode to intra path
     if (cs.sps->getIBCFlag() && checkIbc)
     {
@@ -1324,7 +1361,11 @@ void EncModeCtrlMTnoRQT::initCULevel( Partitioner &partitioner, const CodingStru
   }
 
   // add first pass modes
+#if JVET_O0050_LOCAL_DUAL_TREE
+  if ( !m_slice->isIRAP() && !( cs.area.lwidth() == 4 && cs.area.lheight() == 4 ) && try_inter_rdo )
+#else
   if ( !m_slice->isIRAP() && !( cs.area.lwidth() == 4 && cs.area.lheight() == 4 ) )
+#endif
   {
     for( int qpLoop = maxQP; qpLoop >= minQP; qpLoop-- )
     {
@@ -1490,6 +1531,12 @@ bool EncModeCtrlMTnoRQT::tryMode( const EncTestMode& encTestmode, const CodingSt
     // INTRA MODES
     if (cs.sps->getIBCFlag() && !cuECtx.bestTU)
       return true;
+#if JVET_O0050_LOCAL_DUAL_TREE
+    if( partitioner.isConsIntra() && !cuECtx.bestTU )
+    {
+      return true;
+    }
+#endif
     if ( partitioner.currArea().lumaSize().width == 4 && partitioner.currArea().lumaSize().height == 4 && !slice.isIntra() && !cuECtx.bestTU )
     {
       return true;
@@ -1824,6 +1871,12 @@ bool EncModeCtrlMTnoRQT::tryMode( const EncTestMode& encTestmode, const CodingSt
 #if REUSE_CU_RESULTS
       setFromCs( *bestCS, partitioner );
 
+#endif
+#if JVET_O0050_LOCAL_DUAL_TREE
+      if( partitioner.modeType == MODE_TYPE_INTRA && partitioner.chType == CHANNEL_TYPE_LUMA )
+      {
+        return false; //not set best coding mode for intra coding pass
+      }
 #endif
       // assume the non-split modes are done and set the marks for the best found mode
       if( bestCS && bestCU )
diff --git a/source/Lib/EncoderLib/InterSearch.cpp b/source/Lib/EncoderLib/InterSearch.cpp
index 9aaac4ded..94fd4f40b 100644
--- a/source/Lib/EncoderLib/InterSearch.cpp
+++ b/source/Lib/EncoderLib/InterSearch.cpp
@@ -6574,7 +6574,11 @@ void InterSearch::xEstimateInterResidualQT(CodingStructure &cs, Partitioner &par
 
   if (bCheckFull)
   {
+#if JVET_O0050_LOCAL_DUAL_TREE
+    TransformUnit &tu = csFull->addTU(CS::getArea( cs, currArea, partitioner.chType ), partitioner.chType);
+#else
     TransformUnit &tu = csFull->addTU(CS::isDualITree(cs) ? cu : currArea, partitioner.chType);
+#endif
     tu.depth          = currDepth;
     tu.mtsIdx         = MTS_DCT2_DCT2;
     tu.checkTuNoResidual( partitioner.currPartIdx() );
@@ -6614,7 +6618,11 @@ void InterSearch::xEstimateInterResidualQT(CodingStructure &cs, Partitioner &par
     saveCS.picture = cs.picture;
     saveCS.area.repositionTo(currArea);
     saveCS.clearTUs();
+#if JVET_O0050_LOCAL_DUAL_TREE
+    TransformUnit & bestTU = saveCS.addTU(CS::getArea(cs, currArea, partitioner.chType), partitioner.chType);
+#else
     TransformUnit & bestTU = saveCS.addTU(CS::isDualITree(cs) ? cu : currArea, partitioner.chType);
+#endif
 
     for( uint32_t c = 0; c < numTBlocks; c++ )
     {
@@ -7316,6 +7324,10 @@ void InterSearch::encodeResAndCalcRdInterCU(CodingStructure &cs, Partitioner &pa
   m_pcRdCost->setChromaFormat(cs.sps->getChromaFormatIdc());
 
   CodingUnit &cu = *cs.getCU( partitioner.chType );
+#if JVET_O0050_LOCAL_DUAL_TREE
+  if( cu.predMode == MODE_INTER )
+    CHECK( cu.isSepTree(), "CU with Inter mode must be in single tree" );
+#endif
 
   const ChromaFormat format     = cs.area.chromaFormat;;
   const int  numValidComponents = getNumberValidComponents(format);
@@ -7338,7 +7350,11 @@ void InterSearch::encodeResAndCalcRdInterCU(CodingStructure &cs, Partitioner &pa
 
 
     // add an empty TU
+#if JVET_O0050_LOCAL_DUAL_TREE
+    cs.addTU(CS::getArea(cs, cs.area, partitioner.chType), partitioner.chType);
+#else
     cs.addTU(CS::isDualITree(cs) ? cu : cs.area, partitioner.chType);
+#endif
     Distortion distortion = 0;
 
     for (int comp = 0; comp < numValidComponents; comp++)
diff --git a/source/Lib/EncoderLib/IntraSearch.cpp b/source/Lib/EncoderLib/IntraSearch.cpp
index 9f501fe5b..a3d0942a6 100644
--- a/source/Lib/EncoderLib/IntraSearch.cpp
+++ b/source/Lib/EncoderLib/IntraSearch.cpp
@@ -265,6 +265,25 @@ void IntraSearch::init( EncCfg*        pcEncCfg,
 //////////////////////////////////////////////////////////////////////////
 // INTRA PREDICTION
 //////////////////////////////////////////////////////////////////////////
+#if JVET_O0050_LOCAL_DUAL_TREE
+#define COST_UNKNOWN     (-65536)
+
+double IntraSearch::findInterCUCost( CodingUnit &cu )
+{
+  if( cu.isConsIntra() && !cu.slice->isIntra() )
+  {
+    //search corresponding inter CU cost
+    for( int i = 0; i < m_numCuInSCIPU; i++ )
+    {
+      if( cu.lumaPos() == m_cuAreaInSCIPU[i].pos() && cu.lumaSize() == m_cuAreaInSCIPU[i].size() )
+      {
+        return m_cuCostInSCIPU[i];
+      }
+    }
+  }
+  return COST_UNKNOWN;
+}
+#endif
 
 bool IntraSearch::estIntraPredLumaQT( CodingUnit &cu, Partitioner &partitioner, const double bestCostSoFar, bool mtsCheckRangeFlag, int mtsFirstCheckId, int mtsLastCheckId, bool moreProbMTSIdxFirst )
 {
@@ -297,6 +316,9 @@ bool IntraSearch::estIntraPredLumaQT( CodingUnit &cu, Partitioner &partitioner,
   LFNSTSaveFlag &= sps.getUseIntraMTS() ? cu.mtsFlag == 0 : true;
 
   const uint32_t lfnstIdx = cu.lfnstIdx;
+#if JVET_O0050_LOCAL_DUAL_TREE
+  double costInterCU = findInterCUCost( cu );
+#endif
 
 
   const int width  = partitioner.currArea().lwidth();
@@ -916,6 +938,10 @@ bool IntraSearch::estIntraPredLumaQT( CodingUnit &cu, Partitioner &partitioner,
     csBest->slice = cs.slice;
     csTemp->initStructData();
     csBest->initStructData();
+#if JVET_O0050_LOCAL_DUAL_TREE
+    csTemp->picture = cs.picture;
+    csBest->picture = cs.picture;
+#endif
 
     m_bestCostNonMip = MAX_DOUBLE;
     static_vector<int, FAST_UDI_MAX_RDMODE_NUM> rdModeIdxList;
@@ -1086,6 +1112,22 @@ bool IntraSearch::estIntraPredLumaQT( CodingUnit &cu, Partitioner &partitioner,
       }
 
       csTemp->releaseIntermediateData();
+#if JVET_O0050_LOCAL_DUAL_TREE
+      if( cu.isConsIntra() && !cu.slice->isIntra() && csBest->cost != MAX_DOUBLE && costInterCU != COST_UNKNOWN && mode >= 0 )
+      {
+        if( m_pcEncCfg->getIntraPeriod() == -1 ) //For LDB case, the qp22 encoding time worst case is 109% without this fast algorithm
+        {
+          break;
+        }
+        else
+        {
+          if( csBest->cost > costInterCU * 1.5 )
+          {
+            break;
+          }
+        }
+      }
+#endif
     } // Mode loop
     cu.ispMode = uiBestPUMode.ispMod;
 
@@ -1120,7 +1162,11 @@ void IntraSearch::estIntraPredChromaQT( CodingUnit &cu, Partitioner &partitioner
   cs.setDecomp( cs.area.Cb(), false );
 
   double    bestCostSoFar = maxCostAllowed;
+#if JVET_O0050_LOCAL_DUAL_TREE
+  bool      lumaUsesISP   = !cu.isSepTree() && cu.ispMode;
+#else
   bool      lumaUsesISP   = !CS::isDualITree( *cu.cs ) && cu.ispMode;
+#endif
   PartSplit ispType       = lumaUsesISP ? CU::getISPType( cu, COMPONENT_Y ) : TU_NO_ISP;
   CHECK( cu.ispMode && bestCostSoFar < 0, "bestCostSoFar must be positive!" );
 
@@ -1147,13 +1193,21 @@ void IntraSearch::estIntraPredChromaQT( CodingUnit &cu, Partitioner &partitioner
       saveCS.area.repositionTo( cs.area );
       saveCS.clearTUs();
 
+#if JVET_O0050_LOCAL_DUAL_TREE
+      if( !cu.isSepTree() && cu.ispMode )
+#else
       if( !CS::isDualITree( cs ) && cu.ispMode )
+#endif
       {
         saveCS.clearCUs();
         saveCS.clearPUs();
       }
 
+#if JVET_O0050_LOCAL_DUAL_TREE
+      if( cu.isSepTree() )
+#else
       if( CS::isDualITree( cs ) )
+#endif
       {
         if( partitioner.canSplit( TU_MAX_TR_SPLIT, cs ) )
         {
@@ -1406,8 +1460,13 @@ void IntraSearch::estIntraPredChromaQT( CodingUnit &cu, Partitioner &partitioner
 
 void IntraSearch::IPCMSearch(CodingStructure &cs, Partitioner& partitioner)
 {
+#if JVET_O0050_LOCAL_DUAL_TREE
+  ComponentID compStr = (partitioner.isSepTree(cs) && !isLuma( partitioner.chType)) ? COMPONENT_Cb : COMPONENT_Y;
+  ComponentID compEnd = (partitioner.isSepTree(cs) && isLuma( partitioner.chType)) ? COMPONENT_Y : COMPONENT_Cr;
+#else
   ComponentID compStr = (CS::isDualITree(cs) && !isLuma(partitioner.chType)) ? COMPONENT_Cb: COMPONENT_Y;
   ComponentID compEnd = (CS::isDualITree(cs) && isLuma(partitioner.chType)) ? COMPONENT_Y : COMPONENT_Cr;
+#endif
   for( ComponentID compID = compStr; compID <= compEnd; compID = ComponentID(compID+1) )
   {
 
@@ -1460,6 +1519,28 @@ void IntraSearch::xEncPCM(CodingStructure &cs, Partitioner& partitioner, const C
   }
 }
 
+#if JVET_O0050_LOCAL_DUAL_TREE
+void IntraSearch::saveCuAreaCostInSCIPU( Area area, double cost )
+{
+  if( m_numCuInSCIPU < NUM_INTER_CU_INFO_SAVE )
+  {
+    m_cuAreaInSCIPU[m_numCuInSCIPU] = area;
+    m_cuCostInSCIPU[m_numCuInSCIPU] = cost;
+    m_numCuInSCIPU++;
+  }
+}
+
+void IntraSearch::initCuAreaCostInSCIPU()
+{
+  for( int i = 0; i < NUM_INTER_CU_INFO_SAVE; i++ )
+  {
+    m_cuAreaInSCIPU[i] = Area();
+    m_cuCostInSCIPU[i] = 0;
+  }
+  m_numCuInSCIPU = 0;
+}
+#endif
+
 // -------------------------------------------------------------------------------------------------------------------
 // Intra search
 // -------------------------------------------------------------------------------------------------------------------
@@ -2719,8 +2800,12 @@ ChromaCbfs IntraSearch::xRecurIntraChromaCodingQT( CodingStructure &cs, Partitio
 
 #if JVET_O0105_ICT
   bool lumaUsesISP                    = false;
+#else
+#if JVET_O0050_LOCAL_DUAL_TREE
+  bool lumaUsesISP                    = !currTU.cu->isSepTree() && currTU.cu->ispMode;
 #else
   bool lumaUsesISP                    = !CS::isDualITree( cs ) && currTU.cu->ispMode;
+#endif
 #endif
   uint32_t     currDepth                  = partitioner.currTrDepth;
   const PPS &pps                      = *cs.pps;
@@ -2740,7 +2825,11 @@ ChromaCbfs IntraSearch::xRecurIntraChromaCodingQT( CodingStructure &cs, Partitio
     saveCS.area.repositionTo( cs.area );
     saveCS.initStructData( MAX_INT, false, true );
 
+#if JVET_O0050_LOCAL_DUAL_TREE
+    if( !currTU.cu->isSepTree() && currTU.cu->ispMode )
+#else
     if( !CS::isDualITree( cs ) && currTU.cu->ispMode )
+#endif
     {
       saveCS.clearCUs();
       CodingUnit& auxCU = saveCS.addCU( *currTU.cu, partitioner.chType );
diff --git a/source/Lib/EncoderLib/IntraSearch.h b/source/Lib/EncoderLib/IntraSearch.h
index fd005a552..3b5aa6b16 100644
--- a/source/Lib/EncoderLib/IntraSearch.h
+++ b/source/Lib/EncoderLib/IntraSearch.h
@@ -76,6 +76,13 @@ private:
 
   CodingStructure **m_pSaveCS;
 
+#if JVET_O0050_LOCAL_DUAL_TREE
+  bool            m_saveCuCostInSCIPU;
+  uint8_t         m_numCuInSCIPU;
+  Area            m_cuAreaInSCIPU[NUM_INTER_CU_INFO_SAVE];
+  double          m_cuCostInSCIPU[NUM_INTER_CU_INFO_SAVE];
+#endif
+
   struct ModeInfo
   {
     bool     mipFlg; // CU::mipFlag
@@ -148,6 +155,15 @@ public:
 
   void setModeCtrl                ( EncModeCtrl *modeCtrl ) { m_modeCtrl = modeCtrl; }
 
+#if JVET_O0050_LOCAL_DUAL_TREE
+  bool getSaveCuCostInSCIPU       ()               { return m_saveCuCostInSCIPU; }
+  void setSaveCuCostInSCIPU       ( bool b )       { m_saveCuCostInSCIPU = b;  }
+  void setNumCuInSCIPU            ( uint8_t i )    { m_numCuInSCIPU = i; }
+  void saveCuAreaCostInSCIPU      ( Area area, double cost );
+  void initCuAreaCostInSCIPU      ();
+  double findInterCUCost          ( CodingUnit &cu );
+#endif
+
 public:
 
   bool estIntraPredLumaQT         ( CodingUnit &cu, Partitioner& pm, const double bestCostSoFar  = MAX_DOUBLE, bool mtsCheckRangeFlag = false, int mtsFirstCheckId = 0, int mtsLastCheckId = 0, bool moreProbMTSIdxFirst = false );
-- 
GitLab