diff --git a/source/Lib/CommonLib/Buffer.cpp b/source/Lib/CommonLib/Buffer.cpp
index f245bff72d8aa2a3ebe80d1569707cdaf63a5b7d..4fbd8c9dda66b96520b058067327872460cabd16 100644
--- a/source/Lib/CommonLib/Buffer.cpp
+++ b/source/Lib/CommonLib/Buffer.cpp
@@ -389,12 +389,14 @@ void AreaBuf<Pel>::rspSignal(std::vector<Pel>& pLUT)
 {
   Pel* dst = buf;
   Pel* src = buf;
+#if !JVET_M0102_INTRA_SUBPARTITIONS
   if (width == 1)
   {
     THROW("Blocks of width = 1 not supported");
   }  
   else
   {
+#endif
     for (unsigned y = 0; y < height; y++)
     {
       for (unsigned x = 0; x < width; x++)
@@ -404,7 +406,9 @@ void AreaBuf<Pel>::rspSignal(std::vector<Pel>& pLUT)
       dst += stride;
       src += stride;
     }
+#if !JVET_M0102_INTRA_SUBPARTITIONS
   }
+#endif
 }
 
 template<>
@@ -456,13 +460,14 @@ template<>
 Pel AreaBuf <Pel> ::computeAvg() const
 {
   const Pel* src = buf;
-
+#if !JVET_M0102_INTRA_SUBPARTITIONS
   if (width == 1)
   {
     THROW("Blocks of width = 1 not supported");
   }
   else
   {
+#endif
     int32_t acc = 0;
 #define AVG_INC   \
     src +=       stride; 
@@ -471,7 +476,9 @@ Pel AreaBuf <Pel> ::computeAvg() const
 #undef AVG_INC
 #undef AVG_OP
     return Pel((acc + (area() >> 1)) / area());
+#if !JVET_M0102_INTRA_SUBPARTITIONS
   }
+#endif
 }
 
 #endif
diff --git a/source/Lib/CommonLib/CodingStatistics.h b/source/Lib/CommonLib/CodingStatistics.h
index 748138fdf7a5339d69a21cab167088f17ac5e40c..22a9449c3d448cac60ed25adec4ce86f3f38190b 100644
--- a/source/Lib/CommonLib/CodingStatistics.h
+++ b/source/Lib/CommonLib/CodingStatistics.h
@@ -68,6 +68,10 @@ enum CodingStatisticsType
   STATS__CABAC_BITS__MVD_EP,
   STATS__CABAC_BITS__AFFINE_FLAG,
   STATS__CABAC_BITS__AFFINE_TYPE,
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  STATS__CABAC_BITS__ISP_MODE_FLAG,
+  STATS__CABAC_BITS__ISP_SPLIT_FLAG,
+#endif
   STATS__CABAC_BITS__TRANSFORM_SUBDIV_FLAG,
   STATS__CABAC_BITS__QT_ROOT_CBF,
   STATS__CABAC_BITS__DELTA_QP_EP,
@@ -150,6 +154,10 @@ static inline const char* getName(CodingStatisticsType name)
     "CABAC_BITS__MVD_EP",
     "CABAC_BITS__AFFINE_FLAG",
     "CABAC_BITS__AFFINE_TYPE",
+#if JVET_M0102_INTRA_SUBPARTITIONS
+    "CABAC_BITS__ISP_MODE_FLAG",
+    "CABAC_BITS__ISP_SPLIT_FLAG",
+#endif
     "CABAC_BITS__TRANSFORM_SUBDIV_FLAG",
     "CABAC_BITS__QT_ROOT_CBF",
     "CABAC_BITS__DELTA_QP_EP",
diff --git a/source/Lib/CommonLib/CodingStructure.cpp b/source/Lib/CommonLib/CodingStructure.cpp
index 829a7eeaca528e622d36c7cfb99671cbcd3032ee..76aea6cf4d7b188d1ca2640710cecad749057901 100644
--- a/source/Lib/CommonLib/CodingStructure.cpp
+++ b/source/Lib/CommonLib/CodingStructure.cpp
@@ -259,7 +259,11 @@ const PredictionUnit * CodingStructure::getPU( const Position &pos, const Channe
   }
 }
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+TransformUnit* CodingStructure::getTU( const Position &pos, const ChannelType effChType, const int subTuIdx )
+#else
 TransformUnit* CodingStructure::getTU( const Position &pos, const ChannelType effChType )
+#endif
 {
   const CompArea &_blk = area.blocks[effChType];
 
@@ -272,13 +276,45 @@ TransformUnit* CodingStructure::getTU( const Position &pos, const ChannelType ef
   {
     const unsigned idx = m_tuIdx[effChType][rsAddr( pos, _blk.pos(), _blk.width, unitScale[effChType] )];
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+    if( idx != 0 )
+    {
+      unsigned extraIdx = 0;
+      if( isLuma( effChType ) )
+      {
+        const TransformUnit& tu = *tus[idx - 1];
+
+        if( tu.cu->ispMode ) // Intra SubPartitions mode
+        {
+          //we obtain the offset to index the corresponding sub-partition
+          if( subTuIdx != -1 )
+          {
+            extraIdx = subTuIdx;
+          }
+          else
+          {
+            while( pos != tus[idx - 1 + extraIdx]->blocks[getFirstComponentOfChannel( effChType )].pos() )
+            {
+              extraIdx++;
+            }
+          }
+        }
+      }
+      return tus[idx - 1 + extraIdx];
+    }
+#else
     if( idx != 0 )       return tus[ idx - 1 ];
+#endif
     else if( m_isTuEnc ) return parent->getTU( pos, effChType );
     else                 return nullptr;
   }
 }
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+const TransformUnit * CodingStructure::getTU( const Position &pos, const ChannelType effChType, const int subTuIdx ) const
+#else
 const TransformUnit * CodingStructure::getTU( const Position &pos, const ChannelType effChType ) const
+#endif
 {
   const CompArea &_blk = area.blocks[effChType];
 
@@ -290,8 +326,34 @@ const TransformUnit * CodingStructure::getTU( const Position &pos, const Channel
   else
   {
     const unsigned idx = m_tuIdx[effChType][rsAddr( pos, _blk.pos(), _blk.width, unitScale[effChType] )];
-
+#if JVET_M0102_INTRA_SUBPARTITIONS
+    if( idx != 0 )
+    {
+      unsigned extraIdx = 0;
+      if( isLuma( effChType ) )
+      {
+        const TransformUnit& tu = *tus[idx - 1];
+        if( tu.cu->ispMode ) // Intra SubPartitions mode
+        {
+          //we obtain the offset to index the corresponding sub-partition
+          if( subTuIdx != -1 )
+          {
+            extraIdx = subTuIdx;
+          }
+          else
+          {
+            while( pos != tus[idx - 1 + extraIdx]->blocks[effChType].pos() )
+            {
+              extraIdx++;
+            }
+          }
+        }
+      }
+      return tus[idx - 1 + extraIdx];
+    }
+#else
     if( idx != 0 )       return tus[idx - 1];
+#endif
     else if( m_isTuEnc ) return parent->getTU( pos, effChType );
     else                 return nullptr;
   }
@@ -418,6 +480,9 @@ TransformUnit& CodingStructure::addTU( const UnitArea &unit, const ChannelType c
   tu->UnitArea::operator=( unit );
   tu->initData();
   tu->next   = nullptr;
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  tu->prev   = nullptr;
+#endif
   tu->cs     = this;
   tu->cu     = m_isTuEnc ? cus[0] : getCU( unit.blocks[chType].pos(), chType );
   tu->chType = chType;
@@ -433,6 +498,9 @@ TransformUnit& CodingStructure::addTU( const UnitArea &unit, const ChannelType c
   if( prevTU && prevTU->cu == tu->cu )
   {
     prevTU->next = tu;
+#if JVET_M0102_INTRA_SUBPARTITIONS
+    tu->prev     = prevTU;
+#endif
 #if ENABLE_SPLIT_PARALLELISM || ENABLE_WPP_PARALLELISM
 
     CHECK( prevTU->cacheId != tu->cacheId, "Inconsintent cacheId between previous and current TU" );
@@ -470,11 +538,25 @@ TransformUnit& CodingStructure::addTU( const UnitArea &unit, const ChannelType c
       const CompArea &_selfBlk = area.blocks[i];
       const CompArea     &_blk = tu-> blocks[i];
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+      bool isIspTu = tu->cu != nullptr && tu->cu->ispMode && isLuma( _blk.compID );
+
+      bool isFirstIspTu = false;
+      if( isIspTu )
+      {
+        isFirstIspTu = CU::isISPFirst( *tu->cu, _blk, getFirstComponentOfChannel( ChannelType( i ) ) );
+      }
+      if( !isIspTu || isFirstIspTu )
+#endif
       {
         const UnitScale& scale = unitScale[_blk.compID];
 
         const Area scaledSelf  = scale.scale( _selfBlk );
+#if JVET_M0102_INTRA_SUBPARTITIONS
+        const Area scaledBlk   = isIspTu ? scale.scale( tu->cu->blocks[i] ) : scale.scale( _blk );
+#else
         const Area scaledBlk   = scale.scale(     _blk );
+#endif
         unsigned *idxPtr       = m_tuIdx[i] + rsAddr( scaledBlk.pos(), scaledSelf.pos(), scaledSelf.width );
         CHECK( *idxPtr, "Overwriting a pre-existing value, should be '0'!" );
         AreaBuf<uint32_t>( idxPtr, scaledSelf.width, scaledBlk.size() ).fill( idx );
@@ -1000,6 +1082,9 @@ void CodingStructure::initStructData( const int &QP, const bool &_isLosses, cons
   fracBits = 0;
   dist     = 0;
   cost     = MAX_DOUBLE;
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  lumaCost = MAX_DOUBLE;
+#endif
   interHad = std::numeric_limits<Distortion>::max();
 }
 
diff --git a/source/Lib/CommonLib/CodingStructure.h b/source/Lib/CommonLib/CodingStructure.h
index d7ab5422b8becd4f5060b0e840f150e1825a079b..f28500a798c9a02ea29c2f58e19501540d80f94a 100644
--- a/source/Lib/CommonLib/CodingStructure.h
+++ b/source/Lib/CommonLib/CodingStructure.h
@@ -129,11 +129,19 @@ public:
 
   const CodingUnit     *getCU(const Position &pos, const ChannelType _chType) const;
   const PredictionUnit *getPU(const Position &pos, const ChannelType _chType) const;
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  const TransformUnit  *getTU(const Position &pos, const ChannelType _chType, const int subTuIdx = -1) const;
+#else
   const TransformUnit  *getTU(const Position &pos, const ChannelType _chType) const;
+#endif
 
   CodingUnit     *getCU(const Position &pos, const ChannelType _chType);
   PredictionUnit *getPU(const Position &pos, const ChannelType _chType);
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  TransformUnit  *getTU(const Position &pos, const ChannelType _chType, const int subTuIdx = -1);
+#else
   TransformUnit  *getTU(const Position &pos, const ChannelType _chType);
+#endif
 
   const CodingUnit     *getCU(const ChannelType &_chType) const { return getCU(area.blocks[_chType].pos(), _chType); }
   const PredictionUnit *getPU(const ChannelType &_chType) const { return getPU(area.blocks[_chType].pos(), _chType); }
@@ -171,6 +179,9 @@ public:
   static_vector<double, NUM_ENC_FEATURES> features;
 
   double      cost;
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  double      lumaCost;
+#endif
   uint64_t      fracBits;
   Distortion  dist;
   Distortion  interHad;
diff --git a/source/Lib/CommonLib/Common.h b/source/Lib/CommonLib/Common.h
index 24746baad3be0895fd1ae962155c4fdbc17bccc4..81c92278b2bd60a59c558424bcdbedf0f1defb5b 100644
--- a/source/Lib/CommonLib/Common.h
+++ b/source/Lib/CommonLib/Common.h
@@ -72,6 +72,9 @@ struct Size
   bool operator!=(const Size &other)      const { return (width != other.width) || (height != other.height); }
   bool operator==(const Size &other)      const { return (width == other.width) && (height == other.height); }
   uint32_t area()                             const { return (uint32_t) width * (uint32_t) height; }
+#if REUSE_CU_RESULTS_WITH_MULTIPLE_TUS
+  void resizeTo(const Size newSize)             { width = newSize.width; height = newSize.height; }
+#endif
 };
 
 struct Area : public Position, public Size
diff --git a/source/Lib/CommonLib/ContextModelling.cpp b/source/Lib/CommonLib/ContextModelling.cpp
index 1da885ebc8d7e4e341d93a709d796cca6cbb9921..2f75ce0a3759ed2f93c5643cf59cb26d191fe0e0 100644
--- a/source/Lib/CommonLib/ContextModelling.cpp
+++ b/source/Lib/CommonLib/ContextModelling.cpp
@@ -50,8 +50,13 @@ CoeffCodingContext::CoeffCodingContext(const TransformUnit& tu, ComponentID comp
   , m_chType                    (toChannelType(m_compID))
   , m_width                     (tu.block(m_compID).width)
   , m_height                    (tu.block(m_compID).height)
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  , m_log2CGWidth               ( g_log2SbbSize[m_chType][ g_aucLog2[m_width] ][ g_aucLog2[m_height] ][0] )
+  , m_log2CGHeight              ( g_log2SbbSize[m_chType][ g_aucLog2[m_width] ][ g_aucLog2[m_height] ][1] )
+#else
   , m_log2CGWidth               ((m_width & 3) || (m_height & 3) ? 1 : 2)
   , m_log2CGHeight              ((m_width & 3) || (m_height & 3) ? 1 : 2)
+#endif
   , m_log2CGSize                (m_log2CGWidth + m_log2CGHeight)
 #if JVET_M0257
   , m_widthInGroups(std::min<unsigned>(JVET_C0024_ZERO_OUT_TH, m_width) >> m_log2CGWidth)
@@ -74,10 +79,17 @@ CoeffCodingContext::CoeffCodingContext(const TransformUnit& tu, ComponentID comp
 #else
   , m_scanType                  (SCAN_DIAG)
 #endif
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  , m_scan                      (g_scanOrder     [m_chType][SCAN_GROUPED_4x4][m_scanType][gp_sizeIdxInfo->idxFrom(m_width        )][gp_sizeIdxInfo->idxFrom(m_height        )])
+  , m_scanPosX                  (g_scanOrderPosXY[m_chType][SCAN_GROUPED_4x4][m_scanType][gp_sizeIdxInfo->idxFrom(m_width        )][gp_sizeIdxInfo->idxFrom(m_height        )][0])
+  , m_scanPosY                  (g_scanOrderPosXY[m_chType][SCAN_GROUPED_4x4][m_scanType][gp_sizeIdxInfo->idxFrom(m_width        )][gp_sizeIdxInfo->idxFrom(m_height        )][1])
+  , m_scanCG                    (g_scanOrder     [m_chType][SCAN_UNGROUPED  ][m_scanType][gp_sizeIdxInfo->idxFrom(m_widthInGroups)][gp_sizeIdxInfo->idxFrom(m_heightInGroups)])
+#else
   , m_scan                      (g_scanOrder     [SCAN_GROUPED_4x4][m_scanType][gp_sizeIdxInfo->idxFrom(m_width        )][gp_sizeIdxInfo->idxFrom(m_height        )])
   , m_scanPosX                  (g_scanOrderPosXY[SCAN_GROUPED_4x4][m_scanType][gp_sizeIdxInfo->idxFrom(m_width        )][gp_sizeIdxInfo->idxFrom(m_height        )][0])
   , m_scanPosY                  (g_scanOrderPosXY[SCAN_GROUPED_4x4][m_scanType][gp_sizeIdxInfo->idxFrom(m_width        )][gp_sizeIdxInfo->idxFrom(m_height        )][1])
   , m_scanCG                    (g_scanOrder[SCAN_UNGROUPED  ][m_scanType][gp_sizeIdxInfo->idxFrom(m_widthInGroups)][gp_sizeIdxInfo->idxFrom(m_heightInGroups)])
+#endif
   , m_CtxSetLastX               (Ctx::LastX[m_chType])
   , m_CtxSetLastY               (Ctx::LastY[m_chType])
 #if JVET_M0257
@@ -314,8 +326,17 @@ unsigned DeriveCtx::CtxCUsplit( const CodingStructure& cs, Partitioner& partitio
 }
 #endif
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+unsigned DeriveCtx::CtxQtCbf( const ComponentID compID, const unsigned trDepth, const bool prevCbCbf, const int ispIdx )
+{
+  if( ispIdx && isLuma( compID ) )
+  {
+    return 2 + (int)prevCbCbf;
+  }
+#else
 unsigned DeriveCtx::CtxQtCbf( const ComponentID compID, const unsigned trDepth, const bool prevCbCbf )
 {
+#endif
   if( compID == COMPONENT_Cr )
   {
     return ( prevCbCbf ? 1 : 0 );
diff --git a/source/Lib/CommonLib/ContextModelling.h b/source/Lib/CommonLib/ContextModelling.h
index 4f2c0d90327606deffffdf26773083927762a763..90ca2477514aed8a76d6d4ec8d3c8bb11c79eb21 100644
--- a/source/Lib/CommonLib/ContextModelling.h
+++ b/source/Lib/CommonLib/ContextModelling.h
@@ -308,7 +308,11 @@ void     CtxSplit     ( const CodingStructure& cs, Partitioner& partitioner, uns
 unsigned CtxCUsplit   ( const CodingStructure& cs, Partitioner& partitioner );
 unsigned CtxBTsplit   ( const CodingStructure& cs, Partitioner& partitioner );
 #endif
+#if JVET_M0102_INTRA_SUBPARTITIONS
+unsigned CtxQtCbf     ( const ComponentID compID, const unsigned trDepth, const bool prevCbCbf = false, const int ispIdx = 0 );
+#else
 unsigned CtxQtCbf     ( const ComponentID compID, const unsigned trDepth, const bool prevCbCbf );
+#endif
 unsigned CtxInterDir  ( const PredictionUnit& pu );
 unsigned CtxSkipFlag  ( const CodingUnit& cu );
 unsigned CtxIMVFlag   ( const CodingUnit& cu );
diff --git a/source/Lib/CommonLib/Contexts.cpp b/source/Lib/CommonLib/Contexts.cpp
index 78fcd14f01e7e5cb8b622f2ebba26d84678d037f..72c26adcc6b40c338d30e036a24ca7f237ecd4be 100644
--- a/source/Lib/CommonLib/Contexts.cpp
+++ b/source/Lib/CommonLib/Contexts.cpp
@@ -745,6 +745,24 @@ const CtxSet ContextSetCfg::QtRootCbf = ContextSetCfg::addCtxSet
 
 const CtxSet ContextSetCfg::QtCbf[] =
 {
+#if JVET_M0102_INTRA_SUBPARTITIONS
+#if JVET_M0453_CABAC_ENGINE
+  ContextSetCfg::addCtxSet
+  ({
+    {  155, 127, CNU, CNU},
+    {  141, 127, CNU, CNU},
+    {  CNU, 126, CNU, CNU},
+    {    4,   5, DWS, DWS},
+  }),
+#else
+  ContextSetCfg::addCtxSet
+  ({
+    { 140, 141, CNU, CNU},
+    { 155, 127, CNU, CNU},
+    { CNU, 126, CNU, CNU},
+  }),
+#endif
+#else
   ContextSetCfg::addCtxSet
   ({
 #if JVET_M0453_CABAC_ENGINE
@@ -758,6 +776,7 @@ const CtxSet ContextSetCfg::QtCbf[] =
     { CNU, 126, },
 #endif
   }),
+#endif
   ContextSetCfg::addCtxSet
   ({
 #if JVET_M0453_CABAC_ENGINE
@@ -1208,6 +1227,18 @@ const CtxSet ContextSetCfg::EMTCuFlag = ContextSetCfg::addCtxSet
 });
 #endif
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+const CtxSet ContextSetCfg::ISPMode = ContextSetCfg::addCtxSet
+({
+  { CNU, CNU },
+  { CNU, CNU },
+  { CNU, CNU },
+#if JVET_M0453_CABAC_ENGINE
+  { DWS, DWS },
+#endif
+});
+#endif
+
 const CtxSet ContextSetCfg::CrossCompPred = ContextSetCfg::addCtxSet
 ({
   {  154, 154, 154, 154, 154, 154, 154, 154, 154, 154,},
diff --git a/source/Lib/CommonLib/Contexts.h b/source/Lib/CommonLib/Contexts.h
index fcc59f0152a14eddf03460f23822adff43834592..c3f5dc50599288223ca2cc560f1250c80046fb28 100644
--- a/source/Lib/CommonLib/Contexts.h
+++ b/source/Lib/CommonLib/Contexts.h
@@ -306,6 +306,9 @@ public:
   static const CtxSet   TriangleIdx;
 #if JVET_M0444_SMVD
   static const CtxSet   SmvdFlag;
+#endif
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  static const CtxSet   ISPMode;
 #endif
   static const unsigned NumberOfContexts;
 
diff --git a/source/Lib/CommonLib/DepQuant.cpp b/source/Lib/CommonLib/DepQuant.cpp
index 29794986be8be56f00e4cfa77112c363d0641fe7..b50575d4089947065b4264e2c3af08bb25783fd3 100644
--- a/source/Lib/CommonLib/DepQuant.cpp
+++ b/source/Lib/CommonLib/DepQuant.cpp
@@ -128,8 +128,13 @@ namespace DQIntern
     Rom() : m_scansInitialized(false) {}
     ~Rom() { xUninitScanArrays(); }
     void                init        ()                       { xInitScanArrays(); }
+#if JVET_M0102_INTRA_SUBPARTITIONS
+    const NbInfoSbb*    getNbInfoSbb( int hd, int vd, int ch ) const { return m_scanId2NbInfoSbbArray[hd][vd][ch]; }
+    const NbInfoOut*    getNbInfoOut( int hd, int vd, int ch ) const { return m_scanId2NbInfoOutArray[hd][vd][ch]; }
+#else
     const NbInfoSbb*    getNbInfoSbb( int hd, int vd ) const { return m_scanId2NbInfoSbbArray[hd][vd]; }
     const NbInfoOut*    getNbInfoOut( int hd, int vd ) const { return m_scanId2NbInfoOutArray[hd][vd]; }
+#endif
     const TUParameters* getTUPars   ( const CompArea& area, const ComponentID compID ) const
     {
       return m_tuParameters[g_aucLog2[area.width]][g_aucLog2[area.height]][toChannelType(compID)];
@@ -139,8 +144,13 @@ namespace DQIntern
     void  xUninitScanArrays ();
   private:
     bool          m_scansInitialized;
+#if JVET_M0102_INTRA_SUBPARTITIONS
+    NbInfoSbb*    m_scanId2NbInfoSbbArray[ MAX_CU_DEPTH+1 ][ MAX_CU_DEPTH+1 ][ MAX_NUM_CHANNEL_TYPE ];
+    NbInfoOut*    m_scanId2NbInfoOutArray[ MAX_CU_DEPTH+1 ][ MAX_CU_DEPTH+1 ][ MAX_NUM_CHANNEL_TYPE ];
+#else
     NbInfoSbb*    m_scanId2NbInfoSbbArray[ MAX_CU_DEPTH+1 ][ MAX_CU_DEPTH+1 ];
     NbInfoOut*    m_scanId2NbInfoOutArray[ MAX_CU_DEPTH+1 ][ MAX_CU_DEPTH+1 ];
+#endif
     TUParameters* m_tuParameters         [ MAX_CU_DEPTH+1 ][ MAX_CU_DEPTH+1 ][ MAX_NUM_CHANNEL_TYPE ];
   };
 
@@ -156,26 +166,52 @@ namespace DQIntern
 
     uint32_t raster2id[ MAX_CU_SIZE * MAX_CU_SIZE ];
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+    for( int ch = 0; ch < MAX_NUM_CHANNEL_TYPE; ch++ )
+    {
+    for( int hd = 0; hd <= MAX_CU_DEPTH; hd++ )
+    {
+      for( int vd = 0; vd <= MAX_CU_DEPTH; vd++ )
+      {
+        if( (hd == 0 && vd <= 1) || (hd <= 1 && vd == 0) )
+        {
+          continue;
+        }
+#else
     for( int hd = 1; hd <= MAX_CU_DEPTH; hd++ )
     {
       for( int vd = 1; vd <= MAX_CU_DEPTH; vd++ )
       {
+#endif
         const uint32_t      blockWidth    = (1 << hd);
         const uint32_t      blockHeight   = (1 << vd);
         const uint32_t      totalValues   = blockWidth * blockHeight;
+#if JVET_M0102_INTRA_SUBPARTITIONS
+        const uint32_t      log2CGWidth   = g_log2SbbSize[ch][hd][vd][0];
+        const uint32_t      log2CGHeight  = g_log2SbbSize[ch][hd][vd][1];
+#else
         const uint32_t      log2CGWidth   = (blockWidth & 3) + (blockHeight & 3) > 0 ? 1 : 2;
         const uint32_t      log2CGHeight  = (blockWidth & 3) + (blockHeight & 3) > 0 ? 1 : 2;
+#endif
         const uint32_t      groupWidth    = 1 << log2CGWidth;
         const uint32_t      groupHeight   = 1 << log2CGHeight;
         const uint32_t      groupSize     = groupWidth * groupHeight;
         const CoeffScanType scanType      = SCAN_DIAG;
         const SizeType      blkWidthIdx   = gp_sizeIdxInfo->idxFrom( blockWidth  );
         const SizeType      blkHeightIdx  = gp_sizeIdxInfo->idxFrom( blockHeight );
+#if JVET_M0102_INTRA_SUBPARTITIONS
+        const uint32_t*     scanId2RP     = g_scanOrder     [ch][SCAN_GROUPED_4x4][scanType][blkWidthIdx][blkHeightIdx];
+        const uint32_t*     scanId2X      = g_scanOrderPosXY[ch][SCAN_GROUPED_4x4][scanType][blkWidthIdx][blkHeightIdx][0];
+        const uint32_t*     scanId2Y      = g_scanOrderPosXY[ch][SCAN_GROUPED_4x4][scanType][blkWidthIdx][blkHeightIdx][1];
+        NbInfoSbb*&         sId2NbSbb     = m_scanId2NbInfoSbbArray[hd][vd][ch];
+        NbInfoOut*&         sId2NbOut     = m_scanId2NbInfoOutArray[hd][vd][ch];
+#else
         const uint32_t*     scanId2RP     = g_scanOrder     [SCAN_GROUPED_4x4][scanType][blkWidthIdx][blkHeightIdx];
         const uint32_t*     scanId2X      = g_scanOrderPosXY[SCAN_GROUPED_4x4][scanType][blkWidthIdx][blkHeightIdx][0];
         const uint32_t*     scanId2Y      = g_scanOrderPosXY[SCAN_GROUPED_4x4][scanType][blkWidthIdx][blkHeightIdx][1];
         NbInfoSbb*&         sId2NbSbb     = m_scanId2NbInfoSbbArray[hd][vd];
         NbInfoOut*&         sId2NbOut     = m_scanId2NbInfoOutArray[hd][vd];
+#endif
 
         sId2NbSbb = new NbInfoSbb[ totalValues ];
         sId2NbOut = new NbInfoOut[ totalValues ];
@@ -276,12 +312,19 @@ namespace DQIntern
           nbOut.maxDist -= scanId;
         }
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+        m_tuParameters[hd][vd][ch] = new TUParameters( *this, blockWidth, blockHeight, ChannelType(ch) );
+#else
         for( int chId = 0; chId < MAX_NUM_CHANNEL_TYPE; chId++ )
         {
           m_tuParameters[hd][vd][chId] = new TUParameters( *this, blockWidth, blockHeight, ChannelType(chId) );
         }
+#endif
       }
     }
+#if JVET_M0102_INTRA_SUBPARTITIONS
+    }
+#endif
     m_scansInitialized = true;
   }
 
@@ -291,6 +334,32 @@ namespace DQIntern
     {
       return;
     }
+#if JVET_M0102_INTRA_SUBPARTITIONS
+    for( int hd = 0; hd <= MAX_CU_DEPTH; hd++ )
+    {
+      for( int vd = 0; vd <= MAX_CU_DEPTH; vd++ )
+      {
+        for( int ch = 0; ch < 2; ch++ )
+        {
+          NbInfoSbb*&     sId2NbSbb = m_scanId2NbInfoSbbArray[hd][vd][ch];
+          NbInfoOut*&     sId2NbOut = m_scanId2NbInfoOutArray[hd][vd][ch];
+          TUParameters*&  tuPars    = m_tuParameters         [hd][vd][ch];
+          if( sId2NbSbb )
+          {
+            delete [] sId2NbSbb;
+          }
+          if( sId2NbOut )
+          {
+            delete [] sId2NbOut;
+          }
+          if( tuPars )
+          {
+            delete tuPars;
+          }
+        }
+      }
+    }
+#else
     for( int hd = 0; hd <= MAX_CU_DEPTH; hd++ )
     {
       for( int vd = 0; vd <= MAX_CU_DEPTH; vd++ )
@@ -315,6 +384,7 @@ namespace DQIntern
         }
       }
     }
+#endif
     m_scansInitialized = false;
   }
 
@@ -328,9 +398,14 @@ namespace DQIntern
     m_width               = width;
     m_height              = height;
     m_numCoeff            = m_width * m_height;
+#if JVET_M0102_INTRA_SUBPARTITIONS
+    m_log2SbbWidth        = g_log2SbbSize[m_chType][ g_aucLog2[m_width] ][ g_aucLog2[m_height] ][0];
+    m_log2SbbHeight       = g_log2SbbSize[m_chType][ g_aucLog2[m_width] ][ g_aucLog2[m_height] ][1];
+#else
     const bool      no4x4 = ( ( m_width & 3 ) != 0 || ( m_height & 3 ) != 0 );
     m_log2SbbWidth        = ( no4x4 ? 1 : 2 );
     m_log2SbbHeight       = ( no4x4 ? 1 : 2 );
+#endif
     m_log2SbbSize         = m_log2SbbWidth + m_log2SbbHeight;
     m_sbbSize             = ( 1 << m_log2SbbSize );
     m_sbbMask             = m_sbbSize - 1;
@@ -352,6 +427,16 @@ namespace DQIntern
     SizeType        vsbb  = gp_sizeIdxInfo->idxFrom( m_heightInSbb );
     SizeType        hsId  = gp_sizeIdxInfo->idxFrom( m_width  );
     SizeType        vsId  = gp_sizeIdxInfo->idxFrom( m_height );
+#if JVET_M0102_INTRA_SUBPARTITIONS
+    m_scanSbbId2SbbPos    = g_scanOrder     [ chType ][ SCAN_UNGROUPED   ][ m_scanType ][ hsbb ][ vsbb ];
+    m_scanId2BlkPos       = g_scanOrder     [ chType ][ SCAN_GROUPED_4x4 ][ m_scanType ][ hsId ][ vsId ];
+    m_scanId2PosX         = g_scanOrderPosXY[ chType ][ SCAN_GROUPED_4x4 ][ m_scanType ][ hsId ][ vsId ][ 0 ];
+    m_scanId2PosY         = g_scanOrderPosXY[ chType ][ SCAN_GROUPED_4x4 ][ m_scanType ][ hsId ][ vsId ][ 1 ];
+    int log2W             = g_aucLog2[ m_width  ];
+    int log2H             = g_aucLog2[ m_height ];
+    m_scanId2NbInfoSbb    = rom.getNbInfoSbb( log2W, log2H, chType );
+    m_scanId2NbInfoOut    = rom.getNbInfoOut( log2W, log2H, chType );
+#else
     m_scanSbbId2SbbPos    = g_scanOrder     [ SCAN_UNGROUPED   ][ m_scanType ][ hsbb ][ vsbb ];
     m_scanId2BlkPos       = g_scanOrder     [ SCAN_GROUPED_4x4 ][ m_scanType ][ hsId ][ vsId ];
     m_scanId2PosX         = g_scanOrderPosXY[ SCAN_GROUPED_4x4 ][ m_scanType ][ hsId ][ vsId ][ 0 ];
@@ -360,6 +445,7 @@ namespace DQIntern
     int log2H             = g_aucLog2[ m_height ];
     m_scanId2NbInfoSbb    = rom.getNbInfoSbb( log2W, log2H );
     m_scanId2NbInfoOut    = rom.getNbInfoOut( log2W, log2H );
+#endif
     m_scanInfo            = new ScanInfo[ m_numCoeff ];
     for( int scanIdx = 0; scanIdx < m_numCoeff; scanIdx++ )
     {
@@ -473,8 +559,44 @@ namespace DQIntern
     }
     else
     {
+#if JVET_M0102_INTRA_SUBPARTITIONS
+      BinFracBits bits;
+      bool prevLumaCbf           = false;
+      bool lastCbfIsInferred     = false;
+      bool useIntraSubPartitions = tu.cu->ispMode && isLuma(chType);
+      if( useIntraSubPartitions )
+      {
+        bool rootCbfSoFar = false;
+        bool isLastSubPartition = CU::isISPLast(*tu.cu, tu.Y(), compID);
+        uint32_t nTus = tu.cu->ispMode == HOR_INTRA_SUBPARTITIONS ? tu.cu->lheight() >> g_aucLog2[tu.lheight()] : tu.cu->lwidth() >> g_aucLog2[tu.lwidth()];
+        if( isLastSubPartition )
+        {
+          TransformUnit* tuPointer = tu.cu->firstTU;
+          for( int tuIdx = 0; tuIdx < nTus - 1; tuIdx++ )
+          {
+            rootCbfSoFar |= TU::getCbfAtDepth(*tuPointer, COMPONENT_Y, tu.depth);
+            tuPointer     = tuPointer->next;
+          }
+          if( !rootCbfSoFar )
+          {
+            lastCbfIsInferred = true;
+          }
+        }
+        if( !lastCbfIsInferred )
+        {
+          prevLumaCbf = TU::getPrevTuCbfAtDepth(tu, compID, tu.depth);
+        }
+        bits = fracBitsAccess.getFracBitsArray(Ctx::QtCbf[compID](DeriveCtx::CtxQtCbf(compID, tu.depth, prevLumaCbf, true)));
+      }
+      else
+      {
+        bits = fracBitsAccess.getFracBitsArray(Ctx::QtCbf[compID](DeriveCtx::CtxQtCbf(compID, tu.depth, tu.cbf[COMPONENT_Cb])));
+      }
+      cbfDeltaBits = lastCbfIsInferred ? 0 : int32_t(bits.intBits[1]) - int32_t(bits.intBits[0]);
+#else
       BinFracBits bits = fracBitsAccess.getFracBitsArray( Ctx::QtCbf[compID]( DeriveCtx::CtxQtCbf( compID, tu.depth, tu.cbf[COMPONENT_Cb] ) ) );
       cbfDeltaBits = int32_t( bits.intBits[1] ) - int32_t( bits.intBits[0] );
+#endif
     }
 
     static const unsigned prefixCtx[] = { 0, 0, 0, 3, 6, 10, 15, 21 };
@@ -717,7 +839,11 @@ namespace DQIntern
 #else
     const CoeffScanType scanType  = SCAN_DIAG;
 #endif
+#if JVET_M0102_INTRA_SUBPARTITIONS
+    const unsigned*     scan      = g_scanOrder[ toChannelType(compID) ][ SCAN_GROUPED_4x4 ][ scanType ][ hsId ][ vsId ];
+#else
     const unsigned*     scan      = g_scanOrder[ SCAN_GROUPED_4x4 ][ scanType ][ hsId ][ vsId ];
+#endif
     const TCoeff*       qCoeff    = tu.getCoeffs( compID ).buf;
           TCoeff*       tCoeff    = recCoeff.buf;
 
diff --git a/source/Lib/CommonLib/IntraPrediction.cpp b/source/Lib/CommonLib/IntraPrediction.cpp
index d48ef851161b2a354022ccf6544e5bc48159de3c..c5ee314a9d48aa61680b64b207c1ddd3e4abbc5e 100644
--- a/source/Lib/CommonLib/IntraPrediction.cpp
+++ b/source/Lib/CommonLib/IntraPrediction.cpp
@@ -299,6 +299,9 @@ void IntraPrediction::predIntraAng( const ComponentID compId, PelBuf &piPred, co
   const ChannelType    channelType  = toChannelType( compID );
   const int            iWidth       = piPred.width;
   const int            iHeight      = piPred.height;
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  const Size           cuSize       = Size( pu.cu->blocks[compId].width, pu.cu->blocks[compId].height );
+#endif
   const uint32_t           uiDirMode    = PU::getFinalIntraMode( pu, channelType );
 
 
@@ -306,8 +309,14 @@ void IntraPrediction::predIntraAng( const ComponentID compId, PelBuf &piPred, co
   CHECK( g_aucLog2[iWidth] > 7, "Size not allowed" );
 
   const int  multiRefIdx = (compID == COMPONENT_Y) ? pu.multiRefIdx : 0;
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  const bool useISP = pu.cu->ispMode && isLuma( compID );
+  const int whRatio = useISP ? std::max( unsigned( 1 ), cuSize.width / cuSize.height ) : std::max( unsigned( 1 ), cuSize.width / cuSize.height );
+  const int hwRatio = useISP ? std::max( unsigned( 1 ), cuSize.height / cuSize.width ) : std::max( unsigned( 1 ), cuSize.height / cuSize.width );
+#else
   int whRatio           = std::max(1, iWidth / iHeight);
   int hwRatio           = std::max(1, iHeight / iWidth);
+#endif
   const int  srcStride  = m_topRefLength  + 1 + (whRatio + 1) * multiRefIdx;
   const int  srcHStride = m_leftRefLength + 1 + (hwRatio + 1) * multiRefIdx;
 
@@ -323,18 +332,30 @@ void IntraPrediction::predIntraAng( const ComponentID compId, PelBuf &piPred, co
     case(VDIA_IDX):
       if (getWideAngle(iWidth, iHeight, uiDirMode) == static_cast<int>(uiDirMode)) // check if uiDirMode is not wide-angle
       {
+#if JVET_M0102_INTRA_SUBPARTITIONS
+        xPredIntraAng(CPelBuf(ptrSrc, srcStride, srcHStride), piPred, channelType, uiDirMode, clpRng, *pu.cs->sps, multiRefIdx, useFilteredPredSamples, useISP, cuSize );
+#else
         xPredIntraAng(CPelBuf(ptrSrc, srcStride, srcHStride), piPred, channelType, uiDirMode, clpRng, *pu.cs->sps
           , multiRefIdx
           , useFilteredPredSamples);
+#endif
         break;
       }
+#if JVET_M0102_INTRA_SUBPARTITIONS
+    default:          xPredIntraAng(CPelBuf(getPredictorPtr(compID, false), srcStride, srcHStride), piPred, channelType, uiDirMode, clpRng, *pu.cs->sps, multiRefIdx, useFilteredPredSamples, useISP, cuSize); break;
+#else
     default:          xPredIntraAng(CPelBuf(getPredictorPtr(compID, false), srcStride, srcHStride), piPred, channelType, uiDirMode, clpRng, *pu.cs->sps
       , multiRefIdx
       , useFilteredPredSamples); break;
+#endif
   }
 
   bool pdpcCondition = (uiDirMode == PLANAR_IDX || uiDirMode == DC_IDX || uiDirMode == HOR_IDX || uiDirMode == VER_IDX);
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  if( pdpcCondition && multiRefIdx == 0 && !useISP )
+#else
   if (pdpcCondition && multiRefIdx == 0)
+#endif
   {
     const CPelBuf srcBuf = CPelBuf(ptrSrc, srcStride, srcStride);
     PelBuf dstBuf = piPred;
@@ -445,11 +466,20 @@ void IntraPrediction::xPredIntraPlanar( const CPelBuf &pSrc, PelBuf &pDst, const
 {
   const uint32_t width  = pDst.width;
   const uint32_t height = pDst.height;
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  const uint32_t log2W  = g_aucLog2[width  < 2 ? 2 : width];
+  const uint32_t log2H  = g_aucLog2[height < 2 ? 2 : height];
+#else
   const uint32_t log2W  = g_aucLog2[ width ];
   const uint32_t log2H  = g_aucLog2[ height ];
+#endif
 
   int leftColumn[MAX_CU_SIZE + 1], topRow[MAX_CU_SIZE + 1], bottomRow[MAX_CU_SIZE], rightColumn[MAX_CU_SIZE];
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  const uint32_t offset = 1 << (log2W + log2H);
+#else
   const uint32_t offset = width * height;
+#endif
 
   // Get left and above reference column and row
   for( int k = 0; k < width + 1; k++ )
@@ -561,17 +591,28 @@ void IntraPrediction::xPredIntraAng( const CPelBuf &pSrc, PelBuf &pDst, const Ch
   , int multiRefIdx
   , const bool enableBoundaryFilter )
 #else
+#if JVET_M0102_INTRA_SUBPARTITIONS
+void IntraPrediction::xPredIntraAng( const CPelBuf &pSrc, PelBuf &pDst, const ChannelType channelType, const uint32_t dirMode, const ClpRng& clpRng, const SPS& sps,
+                                           int      multiRefIdx,
+                                     const bool     useFilteredPredSamples ,
+                                     const bool     useISP,
+                                     const Size     cuSize )
+#else
 void IntraPrediction::xPredIntraAng( const CPelBuf &pSrc, PelBuf &pDst, const ChannelType channelType, const uint32_t dirMode, const ClpRng& clpRng, const SPS& sps
   , int multiRefIdx
   , const bool useFilteredPredSamples )
 #endif
+#endif
 {
   int width =int(pDst.width);
   int height=int(pDst.height);
 
   CHECK( !( dirMode > DC_IDX && dirMode < NUM_LUMA_MODE ), "Invalid intra dir" );
-
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  int              predMode           = useISP ? getWideAngle( cuSize.width, cuSize.height, dirMode ) : getWideAngle( cuSize.width, cuSize.height, dirMode );
+#else
   int              predMode           = getWideAngle(width, height, dirMode);
+#endif
   const bool       bIsModeVer         = predMode >= DIA_IDX;
   const int        intraPredAngleMode = (bIsModeVer) ? predMode - VER_IDX : -(predMode - HOR_IDX);
   const int        absAngMode         = abs(intraPredAngleMode);
@@ -595,8 +636,13 @@ void IntraPrediction::xPredIntraAng( const CPelBuf &pSrc, PelBuf &pDst, const Ch
   Pel  refAbove[2 * MAX_CU_SIZE + 3 + 33 * MAX_REF_LINE_IDX];
   Pel  refLeft [2 * MAX_CU_SIZE + 3 + 33 * MAX_REF_LINE_IDX];
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  const int whRatio = useISP ? std::max( unsigned( 1 ), cuSize.width / cuSize.height ) : std::max( unsigned( 1 ), cuSize.width / cuSize.height );
+  const int hwRatio = useISP ? std::max( unsigned( 1 ), cuSize.height / cuSize.width ) : std::max( unsigned( 1 ), cuSize.height / cuSize.width );
+#else
   int whRatio = std::max(1, width / height);
   int hwRatio = std::max(1, height / width);
+#endif
 
   // Initialize the Main and Left reference array.
   if (intraPredAngle < 0)
@@ -691,7 +737,11 @@ void IntraPrediction::xPredIntraAng( const CPelBuf &pSrc, PelBuf &pDst, const Ch
         if( isLuma(channelType) )
         {
           Pel                        p[4];
+#if JVET_M0102_INTRA_SUBPARTITIONS
+          const bool                 useCubicFilter = useISP ? ( width <= 8 ) : ( !useFilteredPredSamples || multiRefIdx > 0 );
+#else
           const bool                 useCubicFilter = !useFilteredPredSamples || multiRefIdx > 0;
+#endif
           TFilterCoeff const * const f              = (useCubicFilter) ? InterpolationFilter::getChromaFilterTable(deltaFract) : g_intraGaussFilter[deltaFract];
 
           int         refMainIndex   = deltaInt + 1;
@@ -735,6 +785,10 @@ void IntraPrediction::xPredIntraAng( const CPelBuf &pSrc, PelBuf &pDst, const Ch
       const int numModes = 8;
       const int scale = ((g_aucLog2[width] - 2 + g_aucLog2[height] - 2 + 2) >> 2);
       CHECK(scale < 0 || scale > 31, "PDPC: scale < 0 || scale > 31");
+#if JVET_M0102_INTRA_SUBPARTITIONS
+      if( !useISP )
+      {
+#endif
       if ((predMode == 2 || predMode == VDIA_IDX) && multiRefIdx == 0)
       {
         int wT = 16 >> std::min(31, ((y << 1) >> scale));
@@ -778,6 +832,9 @@ void IntraPrediction::xPredIntraAng( const CPelBuf &pSrc, PelBuf &pDst, const Ch
           pDsty[x] = ClipPel((wL * left + (64 - wL) * pDsty[x] + 32) >> 6, clpRng);
         }
       }
+#if JVET_M0102_INTRA_SUBPARTITIONS
+      }
+#endif
     }
 #if HEVC_USE_HOR_VER_PREDFILTERING
     if( edgeFilter && absAng <= 1 )
@@ -945,7 +1002,11 @@ void IntraPrediction::initIntraPatternChType(const CodingUnit &cu, const CompAre
   Pel *refBufUnfiltered   = m_piYuvExt[area.compID][PRED_BUF_UNFILTERED];
   Pel *refBufFiltered     = m_piYuvExt[area.compID][PRED_BUF_FILTERED];
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  setReferenceArrayLengths( cu.ispMode && isLuma( area.compID ) ? cu.blocks[area.compID] : area );
+#else
   setReferenceArrayLengths(area);
+#endif
 
   // ----- Step 1: unfiltered reference samples -----
   xFillReferenceSamples( cs.picture->getRecoBuf( area ), refBufUnfiltered, area, cu );
@@ -971,13 +1032,25 @@ void IntraPrediction::xFillReferenceSamples( const CPelBuf &recoBuf, Pel* refBuf
   const int  tuHeight           = area.height;
   const int  predSize           = m_topRefLength;
   const int  predHSize          = m_leftRefLength;
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  const int  cuWidth            = cu.blocks[area.compID].width;
+  const int  cuHeight           = cu.blocks[area.compID].height;
+  const int  whRatio            = cu.ispMode && isLuma(area.compID) ? std::max(1, cuWidth / cuHeight) : std::max(1, tuWidth / tuHeight);
+  const int  hwRatio            = cu.ispMode && isLuma(area.compID) ? std::max(1, cuHeight / cuWidth) : std::max(1, tuHeight / tuWidth);
+#else
   int whRatio                   = std::max(1, tuWidth / tuHeight);
   int hwRatio                   = std::max(1, tuHeight / tuWidth);
+#endif
   const int  predStride         = predSize + 1 + (whRatio + 1) * multiRefIdx;
 
   const bool noShift            = pcv.noChroma2x2 && area.width == 4; // don't shift on the lowest level (chroma not-split)
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  const int  unitWidth          = tuWidth  <= 2 && cu.ispMode && isLuma(area.compID) ? tuWidth  : pcv.minCUWidth  >> (noShift ? 0 : getComponentScaleX(area.compID, sps.getChromaFormatIdc()));
+  const int  unitHeight         = tuHeight <= 2 && cu.ispMode && isLuma(area.compID) ? tuHeight : pcv.minCUHeight >> (noShift ? 0 : getComponentScaleY(area.compID, sps.getChromaFormatIdc()));
+#else
   const int  unitWidth          = pcv.minCUWidth  >> (noShift ? 0 : getComponentScaleX( area.compID, sps.getChromaFormatIdc() ));
   const int  unitHeight         = pcv.minCUHeight >> (noShift ? 0 : getComponentScaleY( area.compID, sps.getChromaFormatIdc() ));
+#endif
 
   const int  totalAboveUnits    = (predSize + (unitWidth - 1)) / unitWidth;
   const int  totalLeftUnits     = (predHSize + (unitHeight - 1)) / unitHeight;
@@ -1293,6 +1366,9 @@ bool IntraPrediction::useFilteredIntraRefSamples( const ComponentID &compID, con
   if( sps.getSpsRangeExtension().getIntraSmoothingDisabledFlag() )                                       { return false; }
   if( !isLuma( chType ) && pu.chromaFormat != CHROMA_444 )                                               { return false; }
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  if( pu.cu->ispMode && isLuma(compID) )                                                                 { return false; }
+#endif
 
   if( !modeSpecific )                                                                                    { return true; }
 
diff --git a/source/Lib/CommonLib/IntraPrediction.h b/source/Lib/CommonLib/IntraPrediction.h
index 7a1995ed2e030f4570228fd40b8aa77c483134b9..8b162bb22d69931b594b1ce65bad0fd5f150c40a 100644
--- a/source/Lib/CommonLib/IntraPrediction.h
+++ b/source/Lib/CommonLib/IntraPrediction.h
@@ -91,10 +91,18 @@ protected:
   void xPredIntraAng              ( const CPelBuf &pSrc, PelBuf &pDst, const ChannelType channelType, const uint32_t dirMode, const ClpRng& clpRng, const bool bEnableEdgeFilters, const SPS& sps
     , int multiRefIdx
     , const bool enableBoundaryFilter = true );
+#else
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  void xPredIntraAng              ( const CPelBuf &pSrc, PelBuf &pDst, const ChannelType channelType, const uint32_t dirMode, const ClpRng& clpRng, const SPS& sps,
+                                          int  multiRefIdx,
+                                    const bool useFilteredPredSamples,
+                                    const bool useISP = false,
+                                    const Size cuSize = Size( 0, 0 ) );
 #else
   void xPredIntraAng              ( const CPelBuf &pSrc, PelBuf &pDst, const ChannelType channelType, const uint32_t dirMode, const ClpRng& clpRng, const SPS& sps
     , int multiRefIdx
     , const bool useFilteredPredSamples );
+#endif
 #endif
   Pel  xGetPredValDc              ( const CPelBuf &pSrc, const Size &dstSize );
 
diff --git a/source/Lib/CommonLib/LoopFilter.cpp b/source/Lib/CommonLib/LoopFilter.cpp
index 8c775174306a79e085e5efc752af33970cfe39cb..a9c41af40a240bb5b0fbb5e320a0c4c48061d913 100644
--- a/source/Lib/CommonLib/LoopFilter.cpp
+++ b/source/Lib/CommonLib/LoopFilter.cpp
@@ -508,6 +508,9 @@ void LoopFilter::xSetLoopfilterParam( const CodingUnit& cu )
   m_stLFCUParam.leftEdge     = ( 0 < pos.x ) && isAvailable ( cu, *cu.cs->getCU( pos.offset( -1,  0 ), cu.chType ), !slice.getLFCrossSliceBoundaryFlag());
   m_stLFCUParam.topEdge      = ( 0 < pos.y ) && isAvailable ( cu, *cu.cs->getCU( pos.offset(  0, -1 ), cu.chType ), !slice.getLFCrossSliceBoundaryFlag());
 #endif
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  m_stLFCUParam.internalEdge &= !cu.ispMode;
+#endif
 }
 
 unsigned LoopFilter::xGetBoundaryStrengthSingle ( const CodingUnit& cu, const DeblockEdgeDir edgeDir, const Position& localPos ) const
diff --git a/source/Lib/CommonLib/QuantRDOQ.cpp b/source/Lib/CommonLib/QuantRDOQ.cpp
index d711ea75bbe762181c357b1f37aaf542e522e475..2682f558a107fa6ee7aa9a3f62208548c35a9521 100644
--- a/source/Lib/CommonLib/QuantRDOQ.cpp
+++ b/source/Lib/CommonLib/QuantRDOQ.cpp
@@ -581,6 +581,9 @@ void QuantRDOQ::quant(TransformUnit &tu, const ComponentID &compID, const CCoeff
 
   bool useRDOQ = useTransformSkip ? m_useRDOQTS : m_useRDOQ;
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  if( !tu.cu->ispMode || !isLuma(compID) )
+#endif
   {
     useRDOQ &= uiWidth > 2;
     useRDOQ &= uiHeight > 2;
@@ -624,6 +627,9 @@ void QuantRDOQ::xRateDistOptQuant(TransformUnit &tu, const ComponentID &compID,
   const bool extendedPrecision     = sps.getSpsRangeExtension().getExtendedPrecisionProcessingFlag();
   const int  maxLog2TrDynamicRange = sps.getMaxLog2TrDynamicRange(chType);
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  const bool useIntraSubPartitions = tu.cu->ispMode && isLuma(compID);
+#endif
   /* for 422 chroma blocks, the effective scaling applied during transformation is not a power of 2, hence it cannot be
   * implemented as a bit-shift (the quantised result will be sqrt(2) * larger than required). Alternatively, adjust the
   * uiLog2TrSize applied in iTransformShift, such that the result is 1/sqrt(2) the required result (i.e. smaller)
@@ -1030,10 +1036,49 @@ void QuantRDOQ::xRateDistOptQuant(TransformUnit &tu, const ComponentID &compID,
   }
   else
   {
+#if JVET_M0102_INTRA_SUBPARTITIONS
+    bool previousCbf       = tu.cbf[COMPONENT_Cb];
+    bool lastCbfIsInferred = false;
+    if( useIntraSubPartitions )
+    {
+      bool rootCbfSoFar       = false;
+      bool isLastSubPartition = CU::isISPLast(*tu.cu, tu.Y(), compID);
+      uint32_t nTus = tu.cu->ispMode == HOR_INTRA_SUBPARTITIONS ? tu.cu->lheight() >> g_aucLog2[tu.lheight()] : tu.cu->lwidth() >> g_aucLog2[tu.lwidth()];
+      if( isLastSubPartition )
+      {
+        TransformUnit* tuPointer = tu.cu->firstTU;
+        for( int tuIdx = 0; tuIdx < nTus - 1; tuIdx++ )
+        {
+          rootCbfSoFar |= TU::getCbfAtDepth(*tuPointer, COMPONENT_Y, tu.depth);
+          tuPointer     = tuPointer->next;
+        }
+        if( !rootCbfSoFar )
+        {
+          lastCbfIsInferred = true;
+        }
+      }
+      if( !lastCbfIsInferred )
+      {
+        previousCbf = TU::getPrevTuCbfAtDepth(tu, compID, tu.depth);
+      }
+    }
+    BinFracBits fracBitsQtCbf = fracBits.getFracBitsArray( Ctx::QtCbf[compID]( DeriveCtx::CtxQtCbf( rect.compID, tu.depth, previousCbf, useIntraSubPartitions ) ) );
+
+    if( !lastCbfIsInferred )
+    {
+      d64BestCost  = d64BlockUncodedCost + xGetICost(fracBitsQtCbf.intBits[0]);
+      d64BaseCost += xGetICost(fracBitsQtCbf.intBits[1]);
+    }
+    else
+    {
+      d64BestCost  = d64BlockUncodedCost;
+    }
+#else
     BinFracBits fracBitsQtCbf = fracBits.getFracBitsArray( Ctx::QtCbf[compID]( DeriveCtx::CtxQtCbf( rect.compID, tu.depth, tu.cbf[COMPONENT_Cb] ) ) );
 
     d64BestCost  = d64BlockUncodedCost + xGetICost( fracBitsQtCbf.intBits[0] );
     d64BaseCost += xGetICost( fracBitsQtCbf.intBits[1] );
+#endif
   }
 
   int lastBitsX[LAST_SIGNIFICANT_GROUPS] = { 0 };
diff --git a/source/Lib/CommonLib/Rom.cpp b/source/Lib/CommonLib/Rom.cpp
index 85e3526dde46b53530932c070129462bec35cf7e..2141368ce5e1ddd8c3aae7271a81c3c881db1fab 100644
--- a/source/Lib/CommonLib/Rom.cpp
+++ b/source/Lib/CommonLib/Rom.cpp
@@ -310,6 +310,33 @@ uint32_t deriveWeightIdxBits(uint8_t gbiIdx) // Note: align this with TEncSbac::
   return numBits;
 }
 
+#if JVET_M0102_INTRA_SUBPARTITIONS // define the sbb sizes
+uint32_t g_log2SbbSize[2][MAX_CU_DEPTH+1][MAX_CU_DEPTH+1][2] = 
+{
+  //===== luma =====
+  {
+    { {0,0}, {0,1}, {0,2}, {0,3}, {0,4}, {0,4}, {0,4}, {0,4} },
+    { {1,0}, {1,1}, {1,2}, {1,3}, {1,3}, {1,3}, {1,3}, {1,3} },
+    { {2,0}, {2,1}, {2,2}, {2,2}, {2,2}, {2,2}, {2,2}, {2,2} },
+    { {3,0}, {3,1}, {2,2}, {2,2}, {2,2}, {2,2}, {2,2}, {2,2} },
+    { {4,0}, {3,1}, {2,2}, {2,2}, {2,2}, {2,2}, {2,2}, {2,2} },
+    { {4,0}, {3,1}, {2,2}, {2,2}, {2,2}, {2,2}, {2,2}, {2,2} },
+    { {4,0}, {3,1}, {2,2}, {2,2}, {2,2}, {2,2}, {2,2}, {2,2} },
+    { {4,0}, {3,1}, {2,2}, {2,2}, {2,2}, {2,2}, {2,2}, {2,2} }
+  },
+  //===== chroma =====
+  {
+    { {0,0}, {0,0}, {0,0}, {0,0}, {0,0}, {0,0}, {0,0}, {0,0} },
+    { {0,0}, {1,1}, {1,1}, {1,1}, {1,1}, {1,1}, {1,1}, {1,1} },
+    { {0,0}, {1,1}, {2,2}, {2,2}, {2,2}, {2,2}, {2,2}, {2,2} },
+    { {0,0}, {1,1}, {2,2}, {2,2}, {2,2}, {2,2}, {2,2}, {2,2} },
+    { {0,0}, {1,1}, {2,2}, {2,2}, {2,2}, {2,2}, {2,2}, {2,2} },
+    { {0,0}, {1,1}, {2,2}, {2,2}, {2,2}, {2,2}, {2,2}, {2,2} },
+    { {0,0}, {1,1}, {2,2}, {2,2}, {2,2}, {2,2}, {2,2}, {2,2} },
+    { {0,0}, {1,1}, {2,2}, {2,2}, {2,2}, {2,2}, {2,2}, {2,2} }
+  },
+};
+#endif
 // initialize ROM variables
 void initROM()
 {
@@ -373,6 +400,10 @@ void initROM()
   SizeIndexInfoLog2 sizeInfo;
   sizeInfo.init(MAX_CU_SIZE);
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  for( int ch = 0; ch < MAX_NUM_CHANNEL_TYPE; ch++ )
+  {
+#endif
   // initialize scan orders
   for (uint32_t blockHeightIdx = 0; blockHeightIdx < sizeInfo.numAllHeights(); blockHeightIdx++)
   {
@@ -390,9 +421,15 @@ void initROM()
       {
         const CoeffScanType scanType = CoeffScanType(scanTypeIndex);
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+        g_scanOrder     [ch][SCAN_UNGROUPED][scanType][blockWidthIdx][blockHeightIdx]    = new uint32_t[totalValues];
+        g_scanOrderPosXY[ch][SCAN_UNGROUPED][scanType][blockWidthIdx][blockHeightIdx][0] = new uint32_t[totalValues];
+        g_scanOrderPosXY[ch][SCAN_UNGROUPED][scanType][blockWidthIdx][blockHeightIdx][1] = new uint32_t[totalValues];
+#else
         g_scanOrder     [SCAN_UNGROUPED][scanType][blockWidthIdx][blockHeightIdx]    = new uint32_t[totalValues];
         g_scanOrderPosXY[SCAN_UNGROUPED][scanType][blockWidthIdx][blockHeightIdx][0] = new uint32_t[totalValues];
         g_scanOrderPosXY[SCAN_UNGROUPED][scanType][blockWidthIdx][blockHeightIdx][1] = new uint32_t[totalValues];
+#endif
 
         ScanGenerator fullBlockScan(blockWidth, blockHeight, blockWidth, scanType);
 
@@ -401,9 +438,15 @@ void initROM()
           const int rasterPos = fullBlockScan.GetNextIndex( 0, 0 );
           const int posY      = rasterPos / blockWidth;
           const int posX      = rasterPos - ( posY * blockWidth );
+#if JVET_M0102_INTRA_SUBPARTITIONS
+          g_scanOrder     [ch][SCAN_UNGROUPED][scanType][blockWidthIdx][blockHeightIdx]   [scanPosition] = rasterPos;
+          g_scanOrderPosXY[ch][SCAN_UNGROUPED][scanType][blockWidthIdx][blockHeightIdx][0][scanPosition] = posX;
+          g_scanOrderPosXY[ch][SCAN_UNGROUPED][scanType][blockWidthIdx][blockHeightIdx][1][scanPosition] = posY;
+#else
           g_scanOrder     [SCAN_UNGROUPED][scanType][blockWidthIdx][blockHeightIdx]   [scanPosition] = rasterPos;
           g_scanOrderPosXY[SCAN_UNGROUPED][scanType][blockWidthIdx][blockHeightIdx][0][scanPosition] = posX;
           g_scanOrderPosXY[SCAN_UNGROUPED][scanType][blockWidthIdx][blockHeightIdx][1][scanPosition] = posY;
+#endif
         }
       }
 
@@ -412,10 +455,15 @@ void initROM()
         // size indizes greater than numIdxs are sizes than are only used when grouping - they will never come up as a block size - thus they can be skipped at this point
         for( uint32_t scanTypeIndex = 0; scanTypeIndex < SCAN_NUMBER_OF_TYPES; scanTypeIndex++ )
         {
+#if JVET_M0102_INTRA_SUBPARTITIONS
+          g_scanOrder     [ch][SCAN_GROUPED_4x4][scanTypeIndex][blockWidthIdx][blockHeightIdx]    = nullptr;
+          g_scanOrderPosXY[ch][SCAN_GROUPED_4x4][scanTypeIndex][blockWidthIdx][blockHeightIdx][0] = nullptr;
+          g_scanOrderPosXY[ch][SCAN_GROUPED_4x4][scanTypeIndex][blockWidthIdx][blockHeightIdx][1] = nullptr;
+#else
           g_scanOrder     [SCAN_GROUPED_4x4][scanTypeIndex][blockWidthIdx][blockHeightIdx]    = nullptr;
           g_scanOrderPosXY[SCAN_GROUPED_4x4][scanTypeIndex][blockWidthIdx][blockHeightIdx][0] = nullptr;
           g_scanOrderPosXY[SCAN_GROUPED_4x4][scanTypeIndex][blockWidthIdx][blockHeightIdx][1] = nullptr;
-
+#endif
         }
 
         continue;
@@ -424,8 +472,14 @@ void initROM()
       //--------------------------------------------------------------------------------------------------
 
       //grouped scan orders
+#if JVET_M0102_INTRA_SUBPARTITIONS
+      const uint32_t* log2Sbb        = g_log2SbbSize[ch][ g_aucLog2[blockWidth] ][ g_aucLog2[blockHeight] ];
+      const uint32_t  log2CGWidth    = log2Sbb[0];
+      const uint32_t  log2CGHeight   = log2Sbb[1];
+#else
       const uint32_t  log2CGWidth    = (blockWidth & 3) + (blockHeight & 3) > 0 ? 1 : 2;
       const uint32_t  log2CGHeight   = (blockWidth & 3) + (blockHeight & 3) > 0 ? 1 : 2;
+#endif
 
       const uint32_t  groupWidth     = 1 << log2CGWidth;
       const uint32_t  groupHeight    = 1 << log2CGHeight;
@@ -444,10 +498,27 @@ void initROM()
       {
         const CoeffScanType scanType = CoeffScanType(scanTypeIndex);
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+        g_scanOrder     [ch][SCAN_GROUPED_4x4][scanType][blockWidthIdx][blockHeightIdx]    = new uint32_t[totalValues];
+        g_scanOrderPosXY[ch][SCAN_GROUPED_4x4][scanType][blockWidthIdx][blockHeightIdx][0] = new uint32_t[totalValues];
+        g_scanOrderPosXY[ch][SCAN_GROUPED_4x4][scanType][blockWidthIdx][blockHeightIdx][1] = new uint32_t[totalValues];
+#else
         g_scanOrder     [SCAN_GROUPED_4x4][scanType][blockWidthIdx][blockHeightIdx]    = new uint32_t[totalValues];
         g_scanOrderPosXY[SCAN_GROUPED_4x4][scanType][blockWidthIdx][blockHeightIdx][0] = new uint32_t[totalValues];
         g_scanOrderPosXY[SCAN_GROUPED_4x4][scanType][blockWidthIdx][blockHeightIdx][1] = new uint32_t[totalValues];
+#endif
 #if JVET_M0257
+#if JVET_M0102_INTRA_SUBPARTITIONS
+        if ( blockWidth > JVET_C0024_ZERO_OUT_TH || blockHeight > JVET_C0024_ZERO_OUT_TH )
+        {
+          for (uint32_t i = 0; i < totalValues; i++)
+          {
+            g_scanOrder     [ch][SCAN_GROUPED_4x4][scanType][blockWidthIdx][blockHeightIdx][i] = totalValues - 1;
+            g_scanOrderPosXY[ch][SCAN_GROUPED_4x4][scanType][blockWidthIdx][blockHeightIdx][0][i] = blockWidth - 1;
+            g_scanOrderPosXY[ch][SCAN_GROUPED_4x4][scanType][blockWidthIdx][blockHeightIdx][1][i] = blockHeight - 1;
+          }
+        }
+#else
         if ( blockWidth > JVET_C0024_ZERO_OUT_TH || blockHeight > JVET_C0024_ZERO_OUT_TH )
         {
           for (uint32_t i = 0; i < totalValues; i++)
@@ -457,6 +528,7 @@ void initROM()
             g_scanOrderPosXY[SCAN_GROUPED_4x4][scanType][blockWidthIdx][blockHeightIdx][1][i] = blockHeight - 1;
           }
         }
+#endif
 #endif
 
         ScanGenerator fullBlockScan(widthInGroups, heightInGroups, groupWidth, scanType);
@@ -477,9 +549,15 @@ void initROM()
             const int posY      = rasterPos / blockWidth;
             const int posX      = rasterPos - ( posY * blockWidth );
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+            g_scanOrder     [ch][SCAN_GROUPED_4x4][scanType][blockWidthIdx][blockHeightIdx]   [groupOffsetScan + scanPosition] = rasterPos;
+            g_scanOrderPosXY[ch][SCAN_GROUPED_4x4][scanType][blockWidthIdx][blockHeightIdx][0][groupOffsetScan + scanPosition] = posX;
+            g_scanOrderPosXY[ch][SCAN_GROUPED_4x4][scanType][blockWidthIdx][blockHeightIdx][1][groupOffsetScan + scanPosition] = posY;
+#else
             g_scanOrder     [SCAN_GROUPED_4x4][scanType][blockWidthIdx][blockHeightIdx]   [groupOffsetScan + scanPosition] = rasterPos;
             g_scanOrderPosXY[SCAN_GROUPED_4x4][scanType][blockWidthIdx][blockHeightIdx][0][groupOffsetScan + scanPosition] = posX;
             g_scanOrderPosXY[SCAN_GROUPED_4x4][scanType][blockWidthIdx][blockHeightIdx][1][groupOffsetScan + scanPosition] = posY;
+#endif
           }
 
           fullBlockScan.GetNextIndex(0, 0);
@@ -489,6 +567,9 @@ void initROM()
       //--------------------------------------------------------------------------------------------------
     }
   }
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  }
+#endif
 
   for( int idxH = MAX_CU_DEPTH - MIN_CU_LOG2; idxH >= 0; --idxH )
   {
@@ -518,6 +599,31 @@ void destroyROM()
   unsigned numWidths = gp_sizeIdxInfo->numAllWidths();
   unsigned numHeights = gp_sizeIdxInfo->numAllHeights();
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  for( uint32_t ch = 0; ch < MAX_NUM_CHANNEL_TYPE; ch++ )
+  {
+    for( uint32_t groupTypeIndex = 0; groupTypeIndex < SCAN_NUMBER_OF_GROUP_TYPES; groupTypeIndex++ )
+    {
+      for( uint32_t scanOrderIndex = 0; scanOrderIndex < SCAN_NUMBER_OF_TYPES; scanOrderIndex++ )
+      {
+        for( uint32_t blockWidthIdx = 0; blockWidthIdx <= numWidths; blockWidthIdx++ )
+        {
+          for( uint32_t blockHeightIdx = 0; blockHeightIdx <= numHeights; blockHeightIdx++ )
+          {
+            delete[] g_scanOrder[ch][groupTypeIndex][scanOrderIndex][blockWidthIdx][blockHeightIdx];
+            g_scanOrder[ch][groupTypeIndex][scanOrderIndex][blockWidthIdx][blockHeightIdx] = nullptr;
+
+            delete[] g_scanOrderPosXY[ch][groupTypeIndex][scanOrderIndex][blockWidthIdx][blockHeightIdx][0];
+            g_scanOrderPosXY[ch][groupTypeIndex][scanOrderIndex][blockWidthIdx][blockHeightIdx][0] = nullptr;
+
+            delete[] g_scanOrderPosXY[ch][groupTypeIndex][scanOrderIndex][blockWidthIdx][blockHeightIdx][1];
+            g_scanOrderPosXY[ch][groupTypeIndex][scanOrderIndex][blockWidthIdx][blockHeightIdx][1] = nullptr;
+          }
+        }
+      }
+    }
+  }
+#else
   for (uint32_t groupTypeIndex = 0; groupTypeIndex < SCAN_NUMBER_OF_GROUP_TYPES; groupTypeIndex++)
   {
     for (uint32_t scanOrderIndex = 0; scanOrderIndex < SCAN_NUMBER_OF_TYPES; scanOrderIndex++)
@@ -539,6 +645,7 @@ void destroyROM()
       }
     }
   }
+#endif
 
   delete gp_sizeIdxInfo;
   gp_sizeIdxInfo = nullptr;
@@ -672,8 +779,13 @@ UnitScale g_miScaling( MIN_CU_LOG2, MIN_CU_LOG2 );
 // ====================================================================================================================
 
 // scanning order table
+#if JVET_M0102_INTRA_SUBPARTITIONS
+uint32_t* g_scanOrder     [2][SCAN_NUMBER_OF_GROUP_TYPES][SCAN_NUMBER_OF_TYPES][MAX_CU_SIZE / 2 + 1][MAX_CU_SIZE / 2 + 1];
+uint32_t* g_scanOrderPosXY[2][SCAN_NUMBER_OF_GROUP_TYPES][SCAN_NUMBER_OF_TYPES][MAX_CU_SIZE / 2 + 1][MAX_CU_SIZE / 2 + 1][2];
+#else
 uint32_t* g_scanOrder     [SCAN_NUMBER_OF_GROUP_TYPES][SCAN_NUMBER_OF_TYPES][MAX_CU_SIZE / 2 + 1][MAX_CU_SIZE / 2 + 1];
 uint32_t* g_scanOrderPosXY[SCAN_NUMBER_OF_GROUP_TYPES][SCAN_NUMBER_OF_TYPES][MAX_CU_SIZE / 2 + 1][MAX_CU_SIZE / 2 + 1][2];
+#endif
 
 const uint32_t ctxIndMap4x4[4 * 4] =
 {
diff --git a/source/Lib/CommonLib/Rom.h b/source/Lib/CommonLib/Rom.h
index 63520f58e341698dc48697f9fa38263bd9aa8cf7..71d4d0ee85ab821e2cb87d8641b65d198a861671 100644
--- a/source/Lib/CommonLib/Rom.h
+++ b/source/Lib/CommonLib/Rom.h
@@ -64,8 +64,14 @@ void         generateTrafoBlockSizeScaling( SizeIndexInfo& sizeIdxInfo );
 // ====================================================================================================================
 
 // flexible conversion from relative to absolute index
+#if JVET_M0102_INTRA_SUBPARTITIONS
+extern       uint32_t   g_log2SbbSize   [2][MAX_CU_DEPTH+1][MAX_CU_DEPTH+1][2];
+extern       uint32_t*  g_scanOrder     [2][SCAN_NUMBER_OF_GROUP_TYPES][SCAN_NUMBER_OF_TYPES][MAX_CU_SIZE / 2 + 1][MAX_CU_SIZE / 2 + 1];
+extern       uint32_t*  g_scanOrderPosXY[2][SCAN_NUMBER_OF_GROUP_TYPES][SCAN_NUMBER_OF_TYPES][MAX_CU_SIZE / 2 + 1][MAX_CU_SIZE / 2 + 1][2];
+#else
 extern       uint32_t*  g_scanOrder     [SCAN_NUMBER_OF_GROUP_TYPES][SCAN_NUMBER_OF_TYPES][MAX_CU_SIZE / 2 + 1][MAX_CU_SIZE / 2 + 1];
 extern       uint32_t*  g_scanOrderPosXY[SCAN_NUMBER_OF_GROUP_TYPES][SCAN_NUMBER_OF_TYPES][MAX_CU_SIZE / 2 + 1][MAX_CU_SIZE / 2 + 1][2];
+#endif
 
 extern const int g_quantScales   [SCALING_LIST_REM_NUM];          // Q(QP%6)
 extern const int g_invQuantScales[SCALING_LIST_REM_NUM];          // IQ(QP%6)
diff --git a/source/Lib/CommonLib/TrQuant.cpp b/source/Lib/CommonLib/TrQuant.cpp
index 8816650a28acd4b1128dbf47d488f0b1e2ad8173..a756ed223e23da328717a8a134aad5401ff0c367 100644
--- a/source/Lib/CommonLib/TrQuant.cpp
+++ b/source/Lib/CommonLib/TrQuant.cpp
@@ -296,12 +296,21 @@ void TrQuant::getTrTypes ( TransformUnit tu, const ComponentID compID, int &trTy
 #else
   bool emtActivated = CU::isIntra( *tu.cu ) ? tu.cs->sps->getSpsNext().getUseIntraEMT() : tu.cs->sps->getSpsNext().getUseInterEMT();
 #endif
+
 #if JVET_M0303_IMPLICIT_MTS
   bool mtsImplicit  = CU::isIntra( *tu.cu ) && tu.cs->sps->getSpsNext().getUseImplicitMTS() && compID == COMPONENT_Y;
 #endif
 
   trTypeHor = DCT2;
   trTypeVer = DCT2;
+
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  if (tu.cu->ispMode && isLuma(compID))
+  {
+    TU::getTransformTypeISP(tu, compID, trTypeHor, trTypeVer);
+    return;
+}
+#endif
   
 #if JVET_M0464_UNI_MTS
   if ( mtsActivated )
@@ -386,10 +395,28 @@ void TrQuant::xT( const TransformUnit &tu, const ComponentID &compID, const CPel
     }
   }
   
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  if( width > 1 && height > 1 ) // 2-D transform
+  {
+#endif
   TCoeff *tmp = ( TCoeff * ) alloca( width * height * sizeof( TCoeff ) );
   
   fastFwdTrans[trTypeHor][transformWidthIndex ](block,        tmp, shift_1st, height,        0, skipWidth);
   fastFwdTrans[trTypeVer][transformHeightIndex](tmp, dstCoeff.buf, shift_2nd, width, skipWidth, skipHeight);
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  }
+  else if( height == 1 ) //1-D horizontal transform 
+  {
+    CHECKD( ( transformWidthIndex < 0 ), "There is a problem with the width." );
+    fastFwdTrans[trTypeHor][transformWidthIndex]( block, dstCoeff.buf, shift_1st, 1, 0, skipWidth );
+  }
+  else //if (iWidth == 1) //1-D vertical transform
+  {
+    CHECKD( ( transformHeightIndex < 0 ), "There is a problem with the height." );
+    int newShift = ( ( g_aucLog2[height] ) + bitDepth + TRANSFORM_MATRIX_SHIFT ) - maxLog2TrDynamicRange + COM16_C806_TRANS_PREC;
+    fastFwdTrans[trTypeVer][transformHeightIndex]( block, dstCoeff.buf, newShift, 1, 0, skipHeight );
+  }
+#endif
 }
 
 void TrQuant::xIT( const TransformUnit &tu, const ComponentID &compID, const CCoeffBuf &pCoeff, PelBuf &pResidual )
@@ -416,11 +443,32 @@ void TrQuant::xIT( const TransformUnit &tu, const ComponentID &compID, const CCo
   
   getTrTypes ( tu, compID, trTypeHor, trTypeVer );
   
+#if !JVET_M0102_INTRA_SUBPARTITIONS
   TCoeff *tmp   = ( TCoeff * ) alloca( width * height * sizeof( TCoeff ) );
+#endif
   TCoeff *block = ( TCoeff * ) alloca( width * height * sizeof( TCoeff ) );
   
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  if( width > 1 && height > 1 ) //2-D transform
+  {
+    TCoeff *tmp = ( TCoeff * ) alloca( width * height * sizeof( TCoeff ) );
+#endif
   fastInvTrans[trTypeVer][transformHeightIndex](pCoeff.buf, tmp, shift_1st, width, skipWidth, skipHeight, clipMinimum, clipMaximum);
   fastInvTrans[trTypeHor][transformWidthIndex] (tmp,      block, shift_2nd, height,         0, skipWidth, clipMinimum, clipMaximum);
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  }
+  else if( width == 1 ) //1-D vertical transform
+  {
+    CHECK( ( transformHeightIndex < 0 ), "There is a problem with the height." );
+    int newShift = ( TRANSFORM_MATRIX_SHIFT + maxLog2TrDynamicRange - 1 ) - bitDepth + COM16_C806_TRANS_PREC;
+    fastInvTrans[trTypeVer][transformHeightIndex]( pCoeff.buf, block, newShift + 1, 1, 0, skipHeight, clipMinimum, clipMaximum );
+  }
+  else //if(iHeight == 1) //1-D horizontal transform
+  {
+    CHECK( ( transformWidthIndex < 0 ), "There is a problem with the width." );
+    fastInvTrans[trTypeHor][transformWidthIndex]( pCoeff.buf, block, shift_2nd + 1, 1, 0, skipWidth, clipMinimum, clipMaximum );
+  }
+#endif
   
   Pel *resiBuf    = pResidual.buf;
   int  resiStride = pResidual.stride;
@@ -496,7 +544,11 @@ void TrQuant::xQuant(TransformUnit &tu, const ComponentID &compID, const CCoeffB
 }
 
 #if JVET_M0464_UNI_MTS
+#if JVET_M0102_INTRA_SUBPARTITIONS
+void TrQuant::transformNxN( TransformUnit &tu, const ComponentID &compID, const QpParam &cQP, std::vector<TrMode>* trModes, const int maxCand, double* diagRatio, double* horVerRatio )
+#else
 void TrQuant::transformNxN(TransformUnit &tu, const ComponentID &compID, const QpParam &cQP, std::vector<TrMode>* trModes, const int maxCand)
+#endif
 {
         CodingStructure &cs = *tu.cs;
   const SPS &sps            = *cs.sps;
@@ -545,6 +597,11 @@ void TrQuant::transformNxN(TransformUnit &tu, const ComponentID &compID, const Q
     it++;
   }
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  // it gets the distribution of the DCT-II coefficients energy, which will be useful to discard ISP tests
+  CoeffBuf coeffsDCT( m_mtsCoeffs[0], rect );
+  xGetCoeffEnergy( tu, compID, coeffsDCT, diagRatio, horVerRatio );
+#endif
   int numTests = 0;
   std::vector<TrCost>::iterator itC = trCosts.begin();
   const double fac   = facBB[g_aucLog2[std::max(width, height)]-2];
@@ -561,10 +618,18 @@ void TrQuant::transformNxN(TransformUnit &tu, const ComponentID &compID, const Q
 #endif
 
 #if JVET_M0464_UNI_MTS
+#if JVET_M0102_INTRA_SUBPARTITIONS
+void TrQuant::transformNxN( TransformUnit &tu, const ComponentID &compID, const QpParam &cQP, TCoeff &uiAbsSum, const Ctx &ctx, const bool loadTr, double* diagRatio, double* horVerRatio )
+#else
 void TrQuant::transformNxN(TransformUnit &tu, const ComponentID &compID, const QpParam &cQP, TCoeff &uiAbsSum, const Ctx &ctx, const bool loadTr)
+#endif
+#else
+#if JVET_M0102_INTRA_SUBPARTITIONS
+void TrQuant::transformNxN(TransformUnit &tu, const ComponentID &compID, const QpParam &cQP, TCoeff &uiAbsSum, const Ctx &ctx, double* diagRatio, double* horVerRatio)
 #else
 void TrQuant::transformNxN(TransformUnit &tu, const ComponentID &compID, const QpParam &cQP, TCoeff &uiAbsSum, const Ctx &ctx)
 #endif
+#endif
 {
         CodingStructure &cs = *tu.cs;
   const SPS &sps            = *cs.sps;
@@ -636,6 +701,20 @@ void TrQuant::transformNxN(TransformUnit &tu, const ComponentID &compID, const Q
       }
 #endif
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+      //we do this only with the DCT-II coefficients
+      if( isLuma(compID) && 
+#if JVET_M0464_UNI_MTS
+        !loadTr && tu.mtsIdx == 0
+#else
+        !tu.cu->emtFlag
+#endif
+        ) 
+      {
+        //it gets the distribution of the coefficients energy, which will be useful to discard ISP tests
+        xGetCoeffEnergy( tu, compID, tempCoeff, diagRatio, horVerRatio );
+      }
+#endif
       DTRACE_COEFF_BUF( D_TCOEFF, tempCoeff, tu, tu.cu->predMode, compID );
 
       xQuant( tu, compID, tempCoeff, uiAbsSum, cQP, ctx );
@@ -648,6 +727,43 @@ void TrQuant::transformNxN(TransformUnit &tu, const ComponentID &compID, const Q
   TU::setCbfAtDepth (tu, compID, tu.depth, uiAbsSum > 0);
 }
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+void TrQuant::xGetCoeffEnergy( TransformUnit &tu, const ComponentID &compID, const CoeffBuf& coeffs, double* diagRatio, double* horVerRatio )
+{
+  if( nullptr == diagRatio || nullptr == horVerRatio ) return;
+
+  if( tu.cu->predMode == MODE_INTRA && !tu.cu->ispMode && isLuma( compID ) && CU::canUseISPSplit( *tu.cu, compID ) != NOT_INTRA_SUBPARTITIONS )
+  {
+    const int width   = tu.cu->blocks[compID].width;
+    const int height  = tu.cu->blocks[compID].height;
+    const int log2Sl  = width <= height ? g_aucLog2[height >> g_aucLog2[width]] : g_aucLog2[width >> g_aucLog2[height]];
+    const int diPos1  = width <= height ? width  : height;
+    const int diPos2  = width <= height ? height : width;
+    const int ofsPos1 = width <= height ? 1 : coeffs.stride;
+    const int ofsPos2 = width <= height ? coeffs.stride : 1;
+
+    int wdtE = 0, hgtE = 0, diaE = 0;
+    int* gtE = width <= height ? &wdtE : &hgtE;
+    int* stE = width <= height ? &hgtE : &wdtE;
+
+    for( int pos1 = 0; pos1 < diPos1; pos1++ )
+    {
+      const int posN = pos1 << log2Sl;
+      for( int pos2 = 0; pos2 < diPos2; pos2++ )
+      {
+        const int blkP = pos1 * ofsPos1 + pos2 * ofsPos2;
+        if( posN  > pos2 ) *gtE += abs( coeffs.buf[ blkP ] );
+        if( posN  < pos2 ) *stE += abs( coeffs.buf[ blkP ] );
+        if( posN == pos2 ) diaE += abs( coeffs.buf[ blkP ] );
+      }
+    }
+
+    *horVerRatio = 0 == wdtE && 0 == hgtE ? 1 : double( wdtE ) / double( hgtE );
+    *diagRatio   = 0 == wdtE && 0 == hgtE && 0 == diaE ? 1 : double( diaE ) / double( wdtE + hgtE );
+  }
+}
+#endif
+
 void TrQuant::applyForwardRDPCM(TransformUnit &tu, const ComponentID &compID, const QpParam &cQP, TCoeff &uiAbsSum, const RDPCMMode &mode)
 {
   const bool bLossless      = tu.cu->transQuantBypass;
diff --git a/source/Lib/CommonLib/TrQuant.h b/source/Lib/CommonLib/TrQuant.h
index aa58a29981b7f6a8ac09601972a487a8811cc6f5..9f477ea9f9d853e531634673a3396d6054008b6d 100644
--- a/source/Lib/CommonLib/TrQuant.h
+++ b/source/Lib/CommonLib/TrQuant.h
@@ -88,10 +88,19 @@ public:
   void invTransformNxN  (TransformUnit &tu, const ComponentID &compID, PelBuf &pResi, const QpParam &cQPs);
 
 #if JVET_M0464_UNI_MTS
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  void transformNxN     ( TransformUnit &tu, const ComponentID &compID, const QpParam &cQP, std::vector<TrMode>* trModes, const int maxCand, double* diagRatio = nullptr, double* horVerRatio = nullptr );
+  void transformNxN     ( TransformUnit &tu, const ComponentID &compID, const QpParam &cQP, TCoeff &uiAbsSum, const Ctx &ctx, const bool loadTr = false, double* diagRatio = nullptr, double* horVerRatio = nullptr );
+#else
   void transformNxN     (TransformUnit &tu, const ComponentID &compID, const QpParam &cQP, std::vector<TrMode>* trModes, const int maxCand);
   void transformNxN     (TransformUnit &tu, const ComponentID &compID, const QpParam &cQP, TCoeff &uiAbsSum, const Ctx &ctx, const bool loadTr=false);
+#endif
+#else
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  void transformNxN     (TransformUnit &tu, const ComponentID &compID, const QpParam &cQP, TCoeff &uiAbsSum, const Ctx &ctx, double* diagRatio = nullptr, double* horVerRatio = nullptr);
 #else
   void transformNxN     (TransformUnit &tu, const ComponentID &compID, const QpParam &cQP, TCoeff &uiAbsSum, const Ctx &ctx);
+#endif
 #endif
   void rdpcmNxN         (TransformUnit &tu, const ComponentID &compID, const QpParam &cQP, TCoeff &uiAbsSum,       RDPCMMode &rdpcmMode);
   void applyForwardRDPCM(TransformUnit &tu, const ComponentID &compID, const QpParam &cQP, TCoeff &uiAbsSum, const RDPCMMode &rdpcmMode);
@@ -157,6 +166,14 @@ private:
                  const TransformUnit &tu,
                  const ComponentID   &component);
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  void xGetCoeffEnergy(
+                       TransformUnit  &tu,
+                 const ComponentID    &compID,
+                 const CoeffBuf       &coeffs,
+                       double*        diagRatio,
+                       double*        horVerRatio );
+#endif
 
 #ifdef TARGET_SIMD_X86
   template<X86_VEXT vext>
diff --git a/source/Lib/CommonLib/TypeDef.h b/source/Lib/CommonLib/TypeDef.h
index 97ec5e99a1d7640b0bc298f1b92065eba3bcda8b..2e57ed508b3d9107767009e4c6204438b546c222 100644
--- a/source/Lib/CommonLib/TypeDef.h
+++ b/source/Lib/CommonLib/TypeDef.h
@@ -50,6 +50,8 @@
 #include <assert.h>
 #include <cassert>
 
+#define JVET_M0102_INTRA_SUBPARTITIONS                    1
+
 #define JVET_M0303_IMPLICIT_MTS                           1 // Implicit transform selection (can be enabled with MTSImplicit encoder config parameter)
 
 #define FIX_DB_MAX_TRANSFORM_SIZE                         1
@@ -144,6 +146,10 @@ typedef std::pair<int, int>  TrCost;
 
 #define JVET_L0090_PAIR_AVG                               1 // Add pairwise average candidates, replace HEVC combined candidates
 #define REUSE_CU_RESULTS                                  1
+#if REUSE_CU_RESULTS && JVET_M0102_INTRA_SUBPARTITIONS
+#define REUSE_CU_RESULTS_WITH_MULTIPLE_TUS                1
+#define MAX_NUM_TUS                                       4
+#endif
 // clang-format on
 
 #ifndef JVET_B0051_NON_MPM_MODE
@@ -399,6 +405,17 @@ enum TransType
   DCT2_EMT = 4
 };
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+enum ISPType
+{
+  NOT_INTRA_SUBPARTITIONS       = 0,
+  HOR_INTRA_SUBPARTITIONS       = 1,
+  VER_INTRA_SUBPARTITIONS       = 2,
+  NUM_INTRA_SUBPARTITIONS_MODES = 3,
+  CAN_USE_VER_AND_HORL_SPLITS   = 4
+};
+#endif
+
 enum RDPCMMode
 {
   RDPCM_OFF             = 0,
diff --git a/source/Lib/CommonLib/Unit.cpp b/source/Lib/CommonLib/Unit.cpp
index 138617abf7246764d22a2a6348f93c2ffee6ea68..0f11344e0e1a69784337fff1eaf7c89cbd8fd4b8 100644
--- a/source/Lib/CommonLib/Unit.cpp
+++ b/source/Lib/CommonLib/Unit.cpp
@@ -189,6 +189,16 @@ bool UnitArea::contains( const UnitArea& other, const ChannelType chType ) const
   return any && ret;
 }
 
+#if REUSE_CU_RESULTS_WITH_MULTIPLE_TUS
+void UnitArea::resizeTo( const UnitArea& unitArea )
+{
+  for( uint32_t i = 0; i < blocks.size(); i++ )
+  {
+    blocks[i].resizeTo( unitArea.blocks[i] );
+  }
+}
+#endif
+
 void UnitArea::repositionTo(const UnitArea& unitArea)
 {
   for(uint32_t i = 0; i < blocks.size(); i++)
@@ -281,7 +291,9 @@ CodingUnit& CodingUnit::operator=( const CodingUnit& other )
 #if JVET_M0444_SMVD
   smvdMode        = other.smvdMode;
 #endif
-
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  ispMode           = other.ispMode;
+#endif
   return *this;
 }
 
@@ -323,6 +335,9 @@ void CodingUnit::initData()
 #if JVET_M0444_SMVD
   smvdMode        = 0;
 #endif
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  ispMode           = 0;
+#endif
 }
 
 
diff --git a/source/Lib/CommonLib/Unit.h b/source/Lib/CommonLib/Unit.h
index f2f30097778855feb8d00a7282a7a14ac2544476..8cb246d99791b5838187ac6bf9a98181a40c2381 100644
--- a/source/Lib/CommonLib/Unit.h
+++ b/source/Lib/CommonLib/Unit.h
@@ -153,6 +153,9 @@ struct CompArea : public Area
 
   const bool operator!=(const CompArea &other) const { return !(operator==(other)); }
 
+#if REUSE_CU_RESULTS_WITH_MULTIPLE_TUS 
+  void     resizeTo          (const Size& newSize)          { Size::resizeTo(newSize); }
+#endif
   void     repositionTo      (const Position& newPos)       { Position::repositionTo(newPos); }
   void     positionRelativeTo(const CompArea& origCompArea) { Position::relativeTo(origCompArea); }
 
@@ -214,6 +217,9 @@ struct UnitArea
     return true;
   }
 
+#if REUSE_CU_RESULTS_WITH_MULTIPLE_TUS
+  void resizeTo    (const UnitArea& unit);
+#endif
   void repositionTo(const UnitArea& unit);
 
   const bool operator!=(const UnitArea &other) const { return !(*this == other); }
@@ -320,6 +326,9 @@ struct CodingUnit : public UnitArea
 #if JVET_M0444_SMVD
   uint8_t          smvdMode;
 #endif
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  uint8_t        ispMode;
+#endif
 
   CodingUnit() : chType( CH_L ) { }
   CodingUnit(const UnitArea &unit);
@@ -464,6 +473,9 @@ struct TransformUnit : public UnitArea
 
   unsigned       idx;
   TransformUnit *next;
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  TransformUnit *prev;
+#endif
 
   void init(TCoeff **coeffs, Pel **pcmbuf);
 
diff --git a/source/Lib/CommonLib/UnitPartitioner.cpp b/source/Lib/CommonLib/UnitPartitioner.cpp
index dae0769736f028afa368c3d303d82047aeb09b0b..39be3bb0cc5c48b67b059e1198dec2899d0ffd3a 100644
--- a/source/Lib/CommonLib/UnitPartitioner.cpp
+++ b/source/Lib/CommonLib/UnitPartitioner.cpp
@@ -671,6 +671,106 @@ bool QTBTPartitioner::hasNextPart()
   return ( ( m_partStack.back().idx + 1 ) < m_partStack.back().parts.size() );
 }
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+void TUIntraSubPartitioner::splitCurrArea( const PartSplit split, const CodingStructure& cs )
+{
+  switch( split )
+  {
+    case TU_1D_HORZ_SPLIT:
+    case TU_1D_VERT_SPLIT:
+    {
+      const UnitArea &area = currArea();
+      m_partStack.push_back( PartLevel() );
+      m_partStack.back().split = split;
+      PartitionerImpl::getTUIntraSubPartitions( m_partStack.back().parts, area, cs, split );
+      break;
+    }
+    case TU_MAX_TR_SPLIT: //we need this non ISP split because of the maxTrSize limitation
+      m_partStack.push_back( PartLevel( split, PartitionerImpl::getMaxTuTiling( currArea(), cs ) ) );
+      break;
+    default:
+      THROW( "Unknown ISP split mode" );
+      break;
+  }
+
+  currDepth++;
+  currTrDepth++; // we need this to identify the level. since the 1d partitions are forbidden if the RQT is on, there area no compatibility issues
+
+#if _DEBUG
+  m_currArea = m_partStack.back().parts.front();
+#endif
+}
+
+void TUIntraSubPartitioner::exitCurrSplit()
+{
+  PartSplit currSplit = m_partStack.back().split;
+
+  m_partStack.pop_back();
+
+  CHECK( currDepth == 0, "depth is '0', although a split was performed" );
+
+  currDepth--;
+  currTrDepth--;
+
+#if _DEBUG
+  m_currArea = m_partStack.back().parts[m_partStack.back().idx];
+#endif
+
+  CHECK( !( currSplit == TU_1D_HORZ_SPLIT || currSplit == TU_1D_VERT_SPLIT || currSplit == TU_MAX_TR_SPLIT ), "Unknown 1D partition split type!" );
+}
+
+bool TUIntraSubPartitioner::nextPart( const CodingStructure &cs, bool autoPop /*= false*/ )
+{
+  unsigned currIdx = ++m_partStack.back().idx;
+
+  m_partStack.back().checkdIfImplicit = false;
+  m_partStack.back().isImplicit = false;
+
+  if( currIdx < m_partStack.back().parts.size() )
+  {
+#if _DEBUG
+    m_currArea = m_partStack.back().parts[m_partStack.back().idx];
+#endif
+    return true;
+  }
+  else
+  {
+    if( autoPop ) exitCurrSplit();
+    return false;
+  }
+}
+
+bool TUIntraSubPartitioner::hasNextPart()
+{
+  return ( ( m_partStack.back().idx + 1 ) < m_partStack.back().parts.size() );
+}
+
+bool TUIntraSubPartitioner::canSplit( const PartSplit split, const CodingStructure &cs )
+{
+  //const PartSplit implicitSplit = getImplicitSplit(cs);
+  const UnitArea &area = currArea();
+
+  switch( split )
+  {
+    case TU_1D_HORZ_SPLIT:
+    {
+      return area.lheight() == m_partStack[0].parts[0].lheight();
+    }
+    case TU_1D_VERT_SPLIT:
+    {
+      return area.lwidth() == m_partStack[0].parts[0].lwidth();
+    }
+    case TU_MAX_TR_SPLIT:
+    {
+      //this split is performed implicitly with the other splits
+      return false;
+    }
+    default:
+      THROW( "Unknown 1-D split mode" );
+      break;
+  }
+}
+#endif
 
 
 //////////////////////////////////////////////////////////////////////////
@@ -879,6 +979,63 @@ Partitioning PartitionerImpl::getCUSubPartitions( const UnitArea &cuArea, const
   }
 }
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+void PartitionerImpl::getTUIntraSubPartitions( Partitioning &sub, const UnitArea &tuArea, const CodingStructure &cs, const PartSplit splitType )
+{
+  uint32_t nPartitions;
+  uint32_t splitDimensionSize = CU::getISPSplitDim( tuArea.lumaSize().width, tuArea.lumaSize().height, splitType );
+
+  bool isDualTree = CS::isDualITree( cs );
+
+  if( splitType == TU_1D_HORZ_SPLIT )
+  {
+    nPartitions = tuArea.lumaSize().height >> g_aucLog2[splitDimensionSize];
+
+    sub.resize( nPartitions );
+
+    for( uint32_t i = 0; i < nPartitions; i++ )
+    {
+      sub[i] = tuArea;
+      CompArea& blkY = sub[i].blocks[COMPONENT_Y];
+
+      blkY.height = splitDimensionSize;
+      blkY.y = i > 0 ? sub[i - 1].blocks[COMPONENT_Y].y + splitDimensionSize : blkY.y;
+
+      CHECK( sub[i].lumaSize().height < 1, "the cs split causes the block to be smaller than the minimal TU size" );
+    }
+  }
+  else if( splitType == TU_1D_VERT_SPLIT )
+  {
+    nPartitions = tuArea.lumaSize().width >> g_aucLog2[splitDimensionSize];
+
+    sub.resize( nPartitions );
+
+    for( uint32_t i = 0; i < nPartitions; i++ )
+    {
+      sub[i] = tuArea;
+      CompArea& blkY = sub[i].blocks[COMPONENT_Y];
+
+      blkY.width = splitDimensionSize;
+      blkY.x = i > 0 ? sub[i - 1].blocks[COMPONENT_Y].x + splitDimensionSize : blkY.x;
+      CHECK( sub[i].lumaSize().width < 1, "the split causes the block to be smaller than the minimal TU size" );
+    }
+  }
+  else
+  {
+    THROW( "Unknown TU sub-partitioning" );
+  }
+  //we only partition luma, so there is going to be only one chroma tu at the end (unless it is dual tree, in which case there won't be any chroma components)
+  uint32_t partitionsWithoutChroma = isDualTree ? nPartitions : nPartitions - 1;
+  for( uint32_t i = 0; i < partitionsWithoutChroma; i++ )
+  {
+    CompArea& blkCb = sub[i].blocks[COMPONENT_Cb];
+    CompArea& blkCr = sub[i].blocks[COMPONENT_Cr];
+    blkCb = CompArea();
+    blkCr = CompArea();
+  }
+}
+#endif
+
 static const int g_maxRtGridSize = 3;
 
 static const int g_zScanToX[1 << ( g_maxRtGridSize << 1 )] =
diff --git a/source/Lib/CommonLib/UnitPartitioner.h b/source/Lib/CommonLib/UnitPartitioner.h
index 222fe99edeb0a83f5ef5ab5462a25f63d1bb1eb5..5cf07f530632290737dedb4072f041d1f8804bb8 100644
--- a/source/Lib/CommonLib/UnitPartitioner.h
+++ b/source/Lib/CommonLib/UnitPartitioner.h
@@ -43,7 +43,11 @@
 #include "CommonDef.h"
 
 static_assert( MAX_CU_TILING_PARTITIONS >= 4, "Minimum required number of partitions for the Partitioning type is 4!" );
+#if JVET_M0102_INTRA_SUBPARTITIONS
+typedef std::vector <UnitArea> Partitioning;
+#else
 typedef static_vector<UnitArea, MAX_CU_TILING_PARTITIONS> Partitioning;
+#endif
 
 //////////////////////////////////////////////////////////////////////////
 // PartManager class - manages the partitioning tree
@@ -63,6 +67,11 @@ enum PartSplit
   CU_TRIH_SPLIT,
   CU_TRIV_SPLIT,
   TU_MAX_TR_SPLIT,
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  TU_NO_ISP,
+  TU_1D_HORZ_SPLIT,
+  TU_1D_VERT_SPLIT,
+#endif
   NUM_PART_SPLIT,
   CU_MT_SPLIT             = 1000, ///< dummy element to indicate the MT (multi-type-tree) split
   CU_BT_SPLIT             = 1001, ///< dummy element to indicate the BT split
@@ -159,6 +168,37 @@ public:
   PartSplit getImplicitSplit      (                                                 const CodingStructure &cs );
 };
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+class TUIntraSubPartitioner : public Partitioner
+{
+public:
+  TUIntraSubPartitioner(Partitioner& _initialState)
+  {
+    //we copy the input partitioner data
+    m_partStack.push_back(PartLevel(TU_NO_ISP, { _initialState.currArea() }));
+
+    currDepth    = _initialState.currDepth;
+    currQtDepth  = _initialState.currQtDepth;
+    currTrDepth  = _initialState.currTrDepth;
+    currBtDepth  = _initialState.currBtDepth;
+    currMtDepth  = _initialState.currMtDepth;
+    chType       = _initialState.chType;
+#if _DEBUG
+    m_currArea   = _initialState.currArea();
+#endif
+  }
+
+  void initCtu               (const UnitArea& ctuArea, const ChannelType chType, const Slice& slice) {}; // not needed
+  void splitCurrArea         (const PartSplit split, const CodingStructure &cs);
+  void exitCurrSplit         ();
+  bool nextPart              (const CodingStructure &cs, bool autoPop = false);
+  bool hasNextPart           ();
+  void canSplit              (const CodingStructure &cs, bool& canNo, bool& canQt, bool& canBh, bool& canBv, bool& canTh, bool& canTv) {};
+  bool canSplit              (const PartSplit split, const CodingStructure &cs);
+  bool isSplitImplicit       (const PartSplit split, const CodingStructure &cs) { return false; }; //not needed
+  PartSplit getImplicitSplit (const CodingStructure &cs) { return CU_DONT_SPLIT; }; //not needed
+};
+#endif
 
 
 
@@ -176,6 +216,9 @@ namespace PartitionerImpl
 {
   Partitioning getCUSubPartitions( const UnitArea   &cuArea, const CodingStructure &cs, const PartSplit splitType = CU_QUAD_SPLIT );
   Partitioning getMaxTuTiling    ( const UnitArea& curArea, const CodingStructure &cs );
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  void    getTUIntraSubPartitions( Partitioning &sub, const UnitArea &tuArea, const CodingStructure &cs, const PartSplit splitType );
+#endif
 };
 
 #endif
diff --git a/source/Lib/CommonLib/UnitTools.cpp b/source/Lib/CommonLib/UnitTools.cpp
index e73eea309d8b5e5af61d6aa6d47a8cb5689fb522..f2ebcfbaf7bde7a7da230894f10ce0be525c151e 100644
--- a/source/Lib/CommonLib/UnitTools.cpp
+++ b/source/Lib/CommonLib/UnitTools.cpp
@@ -290,7 +290,175 @@ uint32_t CU::getNumNonZeroCoeffNonTs( const CodingUnit& cu )
   return count;
 }
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+bool CU::divideTuInRows( const CodingUnit &cu )
+{
+  CHECK( cu.ispMode != HOR_INTRA_SUBPARTITIONS && cu.ispMode != VER_INTRA_SUBPARTITIONS, "Intra Subpartitions type not recognized!" );
+  return cu.ispMode == HOR_INTRA_SUBPARTITIONS ? true : false;
+}
+
+bool CU::firstTestISPHorSplit( const int width, const int height, const ComponentID compID, const CodingUnit *cuLeft, const CodingUnit *cuAbove )
+{
+  //this function decides which split mode (horizontal or vertical) is tested first (encoder only)
+  //we check the logarithmic aspect ratios of the block
+  int aspectRatio = g_aucLog2[width] - g_aucLog2[height];
+  if( aspectRatio > 0 )
+  {
+    return true;
+  }
+  else if( aspectRatio < 0 )
+  {
+    return false;
+  }
+  else //if (aspectRatio == 0)
+  {
+    //we gather data from the neighboring CUs
+    const int cuLeftWidth    = cuLeft  != nullptr                                    ? cuLeft->blocks[compID].width   : -1;
+    const int cuLeftHeight   = cuLeft  != nullptr                                    ? cuLeft->blocks[compID].height  : -1;
+    const int cuAboveWidth   = cuAbove != nullptr                                    ? cuAbove->blocks[compID].width  : -1;
+    const int cuAboveHeight  = cuAbove != nullptr                                    ? cuAbove->blocks[compID].height : -1;
+    const int cuLeft1dSplit  = cuLeft  != nullptr &&  cuLeft->predMode == MODE_INTRA ? cuLeft->ispMode                :  0;
+    const int cuAbove1dSplit = cuAbove != nullptr && cuAbove->predMode == MODE_INTRA ? cuAbove->ispMode               :  0;
+    if( cuLeftWidth != -1 && cuAboveWidth == -1 )
+    {
+      int cuLeftAspectRatio = g_aucLog2[cuLeftWidth] - g_aucLog2[cuLeftHeight];
+      return cuLeftAspectRatio < 0 ? false : cuLeftAspectRatio > 0 ? true : cuLeft1dSplit == VER_INTRA_SUBPARTITIONS ? false : true;
+    }
+    else if( cuLeftWidth == -1 && cuAboveWidth != -1 )
+    {
+      int cuAboveAspectRatio = g_aucLog2[cuAboveWidth] - g_aucLog2[cuAboveHeight];
+      return cuAboveAspectRatio < 0 ? false : cuAboveAspectRatio > 0 ? true : cuAbove1dSplit == VER_INTRA_SUBPARTITIONS ? false : true;
+    }
+    else if( cuLeftWidth != -1 && cuAboveWidth != -1 )
+    {
+      int cuLeftAspectRatio = g_aucLog2[cuLeftWidth] - g_aucLog2[cuLeftHeight];
+      int cuAboveAspectRatio = g_aucLog2[cuAboveWidth] - g_aucLog2[cuAboveHeight];
+      if( cuLeftAspectRatio < 0 && cuAboveAspectRatio < 0 )
+      {
+        return false;
+      }
+      else if( cuLeftAspectRatio > 0 && cuAboveAspectRatio > 0 )
+      {
+        return true;
+      }
+      else if( cuLeftAspectRatio == 0 && cuAboveAspectRatio == 0 )
+      {
+        if( cuLeft1dSplit != 0 && cuAbove1dSplit != 0 )
+        {
+          return cuLeft1dSplit == VER_INTRA_SUBPARTITIONS && cuAbove1dSplit == VER_INTRA_SUBPARTITIONS ? false : true;
+        }
+        else if( cuLeft1dSplit != 0 && cuAbove1dSplit == 0 )
+        {
+          return cuLeft1dSplit == VER_INTRA_SUBPARTITIONS ? false : true;
+        }
+        else if( cuLeft1dSplit == 0 && cuAbove1dSplit != 0 )
+        {
+          return cuAbove1dSplit == VER_INTRA_SUBPARTITIONS ? false : true;
+        }
+        return true;
+      }
+      else
+      {
+        return cuLeftAspectRatio > cuAboveAspectRatio ? cuLeftAspectRatio > 0 : cuAboveAspectRatio > 0;
+      }
+      //return true;
+    }
+    return true;
+  }
+}
+
+PartSplit CU::getISPType( const CodingUnit &cu, const ComponentID compID )
+{
+  if( cu.ispMode && isLuma( compID ) )
+  {
+    const bool tuIsDividedInRows = CU::divideTuInRows( cu );
+
+    return tuIsDividedInRows ? TU_1D_HORZ_SPLIT : TU_1D_VERT_SPLIT;
+  }
+  return TU_NO_ISP;
+}
+
+bool CU::isISPLast( const CodingUnit &cu, const CompArea &tuArea, const ComponentID compID )
+{
+  PartSplit partitionType = CU::getISPType( cu, compID );
+
+  Area originalArea = cu.blocks[compID];
+  switch( partitionType )
+  {
+    case TU_1D_HORZ_SPLIT:
+      return tuArea.y + tuArea.height == originalArea.y + originalArea.height;
+    case TU_1D_VERT_SPLIT:
+      return tuArea.x + tuArea.width == originalArea.x + originalArea.width;
+    default:
+      THROW( "Unknown ISP processing order type!" );
+      return false;
+  }
+}
+
+bool CU::isISPFirst( const CodingUnit &cu, const CompArea &tuArea, const ComponentID compID )
+{
+  return tuArea == cu.firstTU->blocks[compID];
+}
+
+ISPType CU::canUseISPSplit( const CodingUnit &cu, const ComponentID compID )
+{
+  const int width     = cu.blocks[compID].width;
+  const int height    = cu.blocks[compID].height;
+  const int maxTrSize = cu.cs->sps->getMaxTrSize();
+  return CU::canUseISPSplit( width, height, maxTrSize );
+}
+
+ISPType CU::canUseISPSplit( const int width, const int height, const int maxTrSize )
+{
+  bool widthCannotBeUsed = false, heightCannotBeUsed = false;
+
+  const uint32_t minTuSizeForISP = MIN_TU_SIZE;
+  bool  notEnoughSamplesToSplit = ( g_aucLog2[width] + g_aucLog2[height] <= ( g_aucLog2[minTuSizeForISP] << 1 ) );
+  widthCannotBeUsed  = width  > maxTrSize || notEnoughSamplesToSplit;
+  heightCannotBeUsed = height > maxTrSize || notEnoughSamplesToSplit;
+
+  if( !widthCannotBeUsed && !heightCannotBeUsed )
+  {
+    return CAN_USE_VER_AND_HORL_SPLITS; //both splits can be used
+  }
+  else if( widthCannotBeUsed && !heightCannotBeUsed )
+  {
+    return VER_INTRA_SUBPARTITIONS; //only the vertical split can be performed
+  }
+  else if( !widthCannotBeUsed && heightCannotBeUsed )
+  {
+    return HOR_INTRA_SUBPARTITIONS; //only the horizontal split can be performed
+  }
+  else
+  {
+    return NOT_INTRA_SUBPARTITIONS; //neither of the splits can be used
+  }
+}
+
+uint32_t CU::getISPSplitDim( const int width, const int height, const PartSplit ispType )
+{
+  bool divideTuInRows = ispType == TU_1D_HORZ_SPLIT;
+  uint32_t splitDimensionSize, nonSplitDimensionSize, partitionSize, divShift = 2;
+
+  if( divideTuInRows )
+  {
+    splitDimensionSize    = height;
+    nonSplitDimensionSize = width;
+  }
+  else
+  {
+    splitDimensionSize    = width;
+    nonSplitDimensionSize = height;
+  }
 
+  const int minNumberOfSamplesPerCu = 1 << ( ( g_aucLog2[MIN_TU_SIZE] << 1 ) );
+  const int factorToMinSamples = nonSplitDimensionSize < minNumberOfSamplesPerCu ? minNumberOfSamplesPerCu >> g_aucLog2[nonSplitDimensionSize] : 1;
+  partitionSize = ( splitDimensionSize >> divShift ) < factorToMinSamples ? factorToMinSamples : ( splitDimensionSize >> divShift );
+
+  CHECK( g_aucLog2[partitionSize] + g_aucLog2[nonSplitDimensionSize] < g_aucLog2[minNumberOfSamplesPerCu], "A partition has less than the minimum amount of samples!" );
+  return partitionSize;
+}
+#endif
 
 
 PUTraverser CU::traversePUs( CodingUnit& cu )
@@ -319,6 +487,10 @@ int PU::getIntraMPMs( const PredictionUnit &pu, unsigned* mpm, const ChannelType
 {
   const int numMPMs = NUM_MOST_PROBABLE_MODES;
   const int extendRefLine = (channelType == CHANNEL_TYPE_LUMA) ? pu.multiRefIdx : 0;
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  const ISPType ispType = isLuma( channelType ) ? ISPType( pu.cu->ispMode ) : NOT_INTRA_SUBPARTITIONS;
+  const bool isHorSplit = ispType == HOR_INTRA_SUBPARTITIONS;
+#endif
   {
     int numCand      = -1;
     int leftIntraDir = PLANAR_IDX, aboveIntraDir = PLANAR_IDX;
@@ -413,6 +585,116 @@ int PU::getIntraMPMs( const PredictionUnit &pu, unsigned* mpm, const ChannelType
         }
       }
     }
+#if JVET_M0102_INTRA_SUBPARTITIONS
+    else if( ispType != NOT_INTRA_SUBPARTITIONS )
+    {
+      //default case
+      mpm[0] = PLANAR_IDX;
+      if( isHorSplit )
+      {
+        mpm[1] = HOR_IDX;
+        mpm[2] = 25;
+        mpm[3] = 10;
+        mpm[4] = 65;
+        mpm[5] = VER_IDX;
+      }
+      else
+      {
+        mpm[1] = VER_IDX;
+        mpm[2] = 43;
+        mpm[3] = 60;
+        mpm[4] = 3;
+        mpm[5] = HOR_IDX;
+      }
+      int canonicalMode = mpm[1];
+      if( leftIntraDir == aboveIntraDir ) //L=A
+      {
+        numCand = 1;
+        if( leftIntraDir > DC_IDX )
+        {
+          mpm[0] =     leftIntraDir;
+          mpm[1] = ( ( leftIntraDir + offset ) % mod ) + 2;
+          mpm[2] = ( ( leftIntraDir - 1 ) % mod ) + 2;
+          if( ( isHorSplit && leftIntraDir < DIA_IDX ) || ( !isHorSplit && leftIntraDir >= DIA_IDX ) )
+          {
+            mpm[3] = ( ( leftIntraDir + offset - 1 ) % mod ) + 2;
+            mpm[4] =   ( leftIntraDir                % mod ) + 2;
+            mpm[5] = ( ( leftIntraDir + offset - 2 ) % mod ) + 2;;
+          }
+          else
+          {
+            if( isHorSplit )
+            {
+              mpm[3] = HOR_IDX;
+              mpm[4] = 5;
+            }
+            else
+            {
+              mpm[3] = VER_IDX;
+              mpm[4] = VDIA_IDX - 3;
+            }
+            mpm[5] = PLANAR_IDX;
+          }
+        }
+      }
+      else //L!=A
+      {
+        numCand = 2;
+        if( ( leftIntraDir > DC_IDX ) && ( aboveIntraDir > DC_IDX ) )
+        {
+          int distLeftToCanonicalMode  = abs( leftIntraDir - canonicalMode );
+          int distAboveToCanonicalMode = abs( aboveIntraDir - canonicalMode );
+          mpm[0] = aboveIntraDir;
+          mpm[1] = leftIntraDir;
+          if( distLeftToCanonicalMode <= distAboveToCanonicalMode )
+          {
+            mpm[0] = leftIntraDir;
+            mpm[1] = aboveIntraDir;
+          }
+          int maxCandModeIdx = mpm[0] > mpm[1] ? 0 : 1;
+          int minCandModeIdx = 1 - maxCandModeIdx;
+          if( mpm[maxCandModeIdx] - mpm[minCandModeIdx] == 1 )
+          {
+            mpm[2] = ( ( mpm[minCandModeIdx] + offset )     % mod ) + 2;
+            mpm[3] = ( ( mpm[maxCandModeIdx] - 1 )          % mod ) + 2;
+            mpm[4] = ( ( mpm[minCandModeIdx] + offset - 1 ) % mod ) + 2;
+            mpm[5] =   ( mpm[maxCandModeIdx]                % mod ) + 2;
+          }
+          else if( mpm[maxCandModeIdx] - mpm[minCandModeIdx] >= 62 )
+          {
+            mpm[2] = ( ( mpm[minCandModeIdx] - 1 )          % mod ) + 2;
+            mpm[3] = ( ( mpm[maxCandModeIdx] + offset )     % mod ) + 2;
+            mpm[4] = ( ( mpm[minCandModeIdx] )              % mod ) + 2;
+            mpm[5] = ( ( mpm[maxCandModeIdx] + offset - 1 ) % mod ) + 2;
+          }
+          else if( mpm[maxCandModeIdx] - mpm[minCandModeIdx] == 2 )
+          {
+            mpm[2] = ( ( mpm[minCandModeIdx] - 1 )          % mod ) + 2;
+            mpm[3] = ( ( mpm[minCandModeIdx] + offset )     % mod ) + 2;
+            mpm[4] = ( ( mpm[maxCandModeIdx] - 1 )          % mod ) + 2;
+            mpm[5] = ( ( mpm[minCandModeIdx] + offset - 1 ) % mod ) + 2;
+          }
+          else
+          {
+            mpm[2] = ( ( mpm[minCandModeIdx] + offset )     % mod ) + 2;
+            mpm[3] = ( ( mpm[minCandModeIdx] - 1 )          % mod ) + 2;
+            mpm[4] = ( ( mpm[maxCandModeIdx] + offset )     % mod ) + 2;
+            mpm[5] = ( ( mpm[maxCandModeIdx] - 1 )          % mod ) + 2;
+          }
+        }
+        else if( leftIntraDir + aboveIntraDir > 2 )
+        {
+          //mpm[0] = PLANAR_IDX;
+          int angMode = leftIntraDir > DC_IDX ? leftIntraDir : aboveIntraDir;
+          mpm[1] = angMode;
+          mpm[2] = ( ( angMode + offset )     % mod ) + 2;
+          mpm[3] = ( ( angMode - 1 )          % mod ) + 2;
+          mpm[4] = ( ( angMode + offset - 1 ) % mod ) + 2;
+          mpm[5] = ( ( angMode )              % mod ) + 2;
+        }
+      }
+    }
+#endif
     else
     {
       mpm[0] = leftIntraDir;
@@ -4748,6 +5030,9 @@ bool TU::isTSAllowed(const TransformUnit &tu, const ComponentID compID)
 
   tsAllowed &= tu.cs->pps->getUseTransformSkip();
   tsAllowed &= !tu.cu->transQuantBypass;
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  tsAllowed &= ( !tu.cu->ispMode || !isLuma(compID) );
+#endif
 
   SizeType transformSkipMaxSize = 1 << maxSize;
   tsAllowed &= tu.lwidth() <= transformSkipMaxSize && tu.lheight() <= transformSkipMaxSize;
@@ -4762,6 +5047,9 @@ bool TU::isMTSAllowed(const TransformUnit &tu, const ComponentID compID)
 
   mtsAllowed &= CU::isIntra( *tu.cu ) ? tu.cs->sps->getSpsNext().getUseIntraMTS() : tu.cs->sps->getSpsNext().getUseInterMTS();
   mtsAllowed &= ( tu.lwidth() <= maxSize && tu.lheight() <= maxSize );
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  mtsAllowed &= !tu.cu->ispMode;
+#endif
   return mtsAllowed;
 }
 #else
@@ -4925,7 +5213,57 @@ bool TU::needsQP3Offset(const TransformUnit &tu, const ComponentID &compID)
 #endif
 
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+TransformUnit* TU::getPrevTU( const TransformUnit &tu, const ComponentID compID )
+{
+  TransformUnit* prevTU = tu.prev;
 
+  if( prevTU != nullptr && ( prevTU->cu != tu.cu || !prevTU->blocks[compID].valid() ) )
+  {
+    prevTU = nullptr;
+  }
+
+  return prevTU;
+}
+
+bool TU::getPrevTuCbfAtDepth( const TransformUnit &currentTu, const ComponentID compID, const int trDepth )
+{
+  const TransformUnit* prevTU = getPrevTU( currentTu, compID );
+  return ( prevTU != nullptr ) ? TU::getCbfAtDepth( *prevTU, compID, trDepth ) : false;
+}
+
+void TU::getTransformTypeISP( const TransformUnit &tu, const ComponentID compID, int &typeH, int &typeV )
+{
+  typeH = DCT2, typeV = DCT2;
+  const int uiChFinalMode = PU::getFinalIntraMode( *tu.cu->firstPU, toChannelType( compID ) );
+  bool intraModeIsEven = uiChFinalMode % 2 == 0;
+
+  if( uiChFinalMode == DC_IDX || uiChFinalMode == 33 || uiChFinalMode == 35 )
+  {
+    typeH = DCT2;
+    typeV = typeH;
+  }
+  else if( uiChFinalMode == PLANAR_IDX || ( uiChFinalMode >= 31 && uiChFinalMode <= 37 ) )
+  {
+    typeH = DST7;
+    typeV = typeH;
+  }
+  else if( ( intraModeIsEven && uiChFinalMode >= 2 && uiChFinalMode <= 30 ) || ( !intraModeIsEven && uiChFinalMode >= 39 && uiChFinalMode <= 65 ) )
+  {
+    typeH = DST7;
+    typeV = DCT2;
+  }
+  else if( ( !intraModeIsEven && uiChFinalMode >= 3 && uiChFinalMode <= 29 ) || ( intraModeIsEven && uiChFinalMode >= 38 && uiChFinalMode <= 66 ) )
+  {
+    typeH = DCT2;
+    typeV = DST7;
+  }
+  //Size restriction for non-DCT-II transforms
+  Area tuArea = tu.blocks[compID];
+  typeH = tuArea.width  <= 2 || tuArea.width  >= 32 ? DCT2 : typeH;
+  typeV = tuArea.height <= 2 || tuArea.height >= 32 ? DCT2 : typeV;
+}
+#endif
 
 
 // other tools
diff --git a/source/Lib/CommonLib/UnitTools.h b/source/Lib/CommonLib/UnitTools.h
index 2e30af9bb25a4c4d2274ffe0096e6569672f1a7c..27d400023d3feef96d3357a5f7b686e1276602a9 100644
--- a/source/Lib/CommonLib/UnitTools.h
+++ b/source/Lib/CommonLib/UnitTools.h
@@ -89,6 +89,17 @@ namespace CU
   void  setGbiIdx                     (CodingUnit& cu, uint8_t uh);
   uint8_t deriveGbiIdx                (uint8_t gbiLO, uint8_t gbiL1);
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  bool      divideTuInRows            ( const CodingUnit &cu );
+  bool      firstTestISPHorSplit      ( const int width, const int height,            const ComponentID compID, const CodingUnit *cuLeft = nullptr, const CodingUnit *cuAbove = nullptr );
+  PartSplit getISPType                ( const CodingUnit &cu,                         const ComponentID compID );
+  bool      isISPLast                 ( const CodingUnit &cu, const CompArea &tuArea, const ComponentID compID );
+  bool      isISPFirst                ( const CodingUnit &cu, const CompArea &tuArea, const ComponentID compID );
+  ISPType   canUseISPSplit            ( const CodingUnit &cu,                         const ComponentID compID );
+  ISPType   canUseISPSplit            ( const int width, const int height, const int maxTrSize = MAX_TU_SIZE );
+  uint32_t  getISPSplitDim            ( const int width, const int height, const PartSplit ispType );
+#endif
+
   PUTraverser traversePUs             (      CodingUnit& cu);
   TUTraverser traverseTUs             (      CodingUnit& cu);
   cPUTraverser traversePUs            (const CodingUnit& cu);
@@ -224,6 +235,11 @@ namespace TU
 #else
   bool needsQP3Offset                 (const TransformUnit &tu, const ComponentID &compID);
 #endif
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  TransformUnit* getPrevTU          ( const TransformUnit &tu, const ComponentID compID );
+  bool           getPrevTuCbfAtDepth( const TransformUnit &tu, const ComponentID compID, const int trDepth );
+  void           getTransformTypeISP( const TransformUnit &tu, const ComponentID compID, int &typeH, int &typeV );
+#endif
 }
 
 uint32_t getCtuAddr        (const Position& pos, const PreCalcValues &pcv);
diff --git a/source/Lib/DecoderLib/CABACReader.cpp b/source/Lib/DecoderLib/CABACReader.cpp
index bd9ff0f130a4937cb1945d5b3de3e3da4c9648b2..0ab7f1eb51e09cdfc47e0c35c764f5c8eb99ee01 100644
--- a/source/Lib/DecoderLib/CABACReader.cpp
+++ b/source/Lib/DecoderLib/CABACReader.cpp
@@ -924,6 +924,9 @@ bool CABACReader::coding_unit( CodingUnit &cu, Partitioner &partitioner, CUCtx&
 
   extend_ref_line( cu );
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  isp_mode( cu );
+#endif
 
   // prediction data ( intra prediction modes / reference indexes + motion vectors )
   cu_pred_data( cu );
@@ -1239,7 +1242,11 @@ void CABACReader::intra_luma_pred_modes( CodingUnit &cu )
   for( int k = 0; k < numBlocks; k++ )
   {
     CHECK(numBlocks != 1, "not supported yet");
+#if JVET_M0102_INTRA_SUBPARTITIONS
+    if( cu.firstPU->multiRefIdx || ( cu.ispMode && isLuma( cu.chType ) ) )
+#else
     if (cu.firstPU->multiRefIdx)
+#endif
     {
       mpmFlag[0] = true;
     }
@@ -1389,7 +1396,19 @@ void CABACReader::cu_residual( CodingUnit& cu, Partitioner &partitioner, CUCtx&
   }
 
   ChromaCbfs chromaCbfs;
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  if( cu.ispMode && isLuma( partitioner.chType ) )
+  {
+    TUIntraSubPartitioner subTuPartitioner( partitioner );
+    transform_tree( *cu.cs, subTuPartitioner, cuCtx, chromaCbfs, CU::getISPType( cu, getFirstComponentOfChannel( partitioner.chType ) ), 0 );
+  }
+  else
+  {
+    transform_tree( *cu.cs, partitioner, cuCtx, chromaCbfs );
+  }
+#else
   transform_tree( *cu.cs, partitioner, cuCtx, chromaCbfs );
+#endif
 }
 
 void CABACReader::rqt_root_cbf( CodingUnit& cu )
@@ -2096,23 +2115,53 @@ void CABACReader::pcm_samples( TransformUnit& tu )
 //    bool  cbf_comp            ( area, depth )
 //================================================================================
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+void CABACReader::transform_tree( CodingStructure &cs, Partitioner &partitioner, CUCtx& cuCtx, ChromaCbfs& chromaCbfs, const PartSplit ispType, const int subTuIdx )
+#else
 void CABACReader::transform_tree( CodingStructure &cs, Partitioner &partitioner, CUCtx& cuCtx, ChromaCbfs& chromaCbfs )
+#endif
 {
   const UnitArea& area          = partitioner.currArea();
 
   CodingUnit&     cu            = *cs.getCU( area.blocks[partitioner.chType], partitioner.chType );
   const unsigned  trDepth       = partitioner.currTrDepth;
+#if JVET_M0102_INTRA_SUBPARTITIONS
+        int       subTuCounter  = subTuIdx;
+#endif
 
   // split_transform_flag
   bool split = false;
 
   split = partitioner.canSplit( TU_MAX_TR_SPLIT, cs );
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  if( !split && cu.ispMode )
+  {
+    split = partitioner.canSplit( ispType, cs );
+  }
+  const bool chromaCbfISP = area.blocks[COMPONENT_Cb].valid() && cu.ispMode && !split;
+#endif
+
   // cbf_cb & cbf_cr
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  if( area.chromaFormat != CHROMA_400 && area.blocks[COMPONENT_Cb].valid() && ( !CS::isDualITree( cs ) || 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 ) )
+#endif
   {
     {
       {
+#if JVET_M0102_INTRA_SUBPARTITIONS
+        const int cbfDepth = chromaCbfISP ? trDepth - 1 : trDepth;
+        if( chromaCbfs.Cb )
+        {
+          chromaCbfs.Cb &= cbf_comp( cs, area.blocks[COMPONENT_Cb], cbfDepth );
+        }
+        if( chromaCbfs.Cr )
+        {
+          chromaCbfs.Cr &= cbf_comp( cs, area.blocks[COMPONENT_Cr], cbfDepth, chromaCbfs.Cb );
+        }
+#else
         if( chromaCbfs.Cb )
         {
           chromaCbfs.Cb &= cbf_comp( cs, area.blocks[COMPONENT_Cb], trDepth );
@@ -2121,6 +2170,7 @@ void CABACReader::transform_tree( CodingStructure &cs, Partitioner &partitioner,
         {
           chromaCbfs.Cr &= cbf_comp( cs, area.blocks[COMPONENT_Cr], trDepth, chromaCbfs.Cb );
         }
+#endif
       }
     }
   }
@@ -2133,7 +2183,11 @@ void CABACReader::transform_tree( CodingStructure &cs, Partitioner &partitioner,
   {
     {
 #if !JVET_M0464_UNI_MTS
+#if JVET_M0102_INTRA_SUBPARTITIONS
+      if( trDepth == 0 && !cu.ispMode ) emt_cu_flag( cu );
+#else
       if( trDepth == 0 ) emt_cu_flag( cu );
+#endif
 #endif
 
       if( partitioner.canSplit( TU_MAX_TR_SPLIT, cs ) )
@@ -2145,6 +2199,12 @@ void CABACReader::transform_tree( CodingStructure &cs, Partitioner &partitioner,
 #endif
         partitioner.splitCurrArea( TU_MAX_TR_SPLIT, cs );
       }
+#if JVET_M0102_INTRA_SUBPARTITIONS
+      else if( cu.ispMode )
+      {
+        partitioner.splitCurrArea( ispType, cs );
+      }
+#endif
       else
         THROW( "Implicit TU split not available!" );
     }
@@ -2152,7 +2212,12 @@ void CABACReader::transform_tree( CodingStructure &cs, Partitioner &partitioner,
     do
     {
       ChromaCbfs subCbfs = chromaCbfs;
+#if JVET_M0102_INTRA_SUBPARTITIONS
+      transform_tree( cs, partitioner, cuCtx, subCbfs, ispType, subTuCounter );
+      subTuCounter += subTuCounter != -1 ? 1 : 0;
+#else
       transform_tree( cs, partitioner, cuCtx, subCbfs );
+#endif
     } while( partitioner.nextPart( cs ) );
 
     partitioner.exitCurrSplit();
@@ -2162,7 +2227,17 @@ void CABACReader::transform_tree( CodingStructure &cs, Partitioner &partitioner,
     const unsigned numTBlocks = getNumberValidTBlocks( *cs.pcv );
 
     unsigned        compCbf[3] = { 0, 0, 0 };
-
+#if JVET_M0102_INTRA_SUBPARTITIONS
+    unsigned        cbfDepth   = 0;
+    for( auto &currTU : cs.traverseTUs( currArea, partitioner.chType ) )
+    {
+      for( unsigned ch = 0; ch < numTBlocks; ch++ )
+      {
+        cbfDepth     = !isLuma( ComponentID( ch ) ) && cu.ispMode ? currDepth : currDepth + 1;
+        compCbf[ch] |= ( TU::getCbfAtDepth( currTU, ComponentID( ch ), cbfDepth ) ? 1 : 0 );
+      }
+    }
+#else
     for( auto &currTU : cs.traverseTUs( currArea, partitioner.chType ) )
     {
       for( unsigned ch = 0; ch < numTBlocks; ch++ )
@@ -2170,6 +2245,7 @@ void CABACReader::transform_tree( CodingStructure &cs, Partitioner &partitioner,
         compCbf[ch] |= ( TU::getCbfAtDepth( currTU, ComponentID( ch ), currDepth + 1 ) ? 1 : 0 );
       }
     }
+#endif
 
     {
 
@@ -2208,11 +2284,43 @@ void CABACReader::transform_tree( CodingStructure &cs, Partitioner &partitioner,
       }
       else
       {
+#if JVET_M0102_INTRA_SUBPARTITIONS
+        bool previousCbf       = false;
+        bool rootCbfSoFar      = false;
+        bool lastCbfIsInferred = false;
+        if( cu.ispMode )
+        {
+          uint32_t nTus = cu.ispMode == HOR_INTRA_SUBPARTITIONS ? cu.lheight() >> g_aucLog2[tu.lheight()] : cu.lwidth() >> g_aucLog2[tu.lwidth()];
+          if( subTuCounter == nTus - 1 )
+          {
+            TransformUnit* tuPointer = cu.firstTU;
+            for( int tuIdx = 0; tuIdx < nTus - 1; tuIdx++ )
+            {
+              rootCbfSoFar |= TU::getCbfAtDepth( *tuPointer, COMPONENT_Y, trDepth );
+              tuPointer = tuPointer->next;
+            }
+            if( !rootCbfSoFar )
+            {
+              lastCbfIsInferred = true;
+            }
+          }
+          if( !lastCbfIsInferred )
+          {
+            previousCbf = TU::getPrevTuCbfAtDepth( tu, COMPONENT_Y, trDepth );
+          }
+        }
+        bool cbfY = lastCbfIsInferred ? true : cbf_comp( cs, tu.Y(), trDepth, previousCbf, cu.ispMode );
+#else
         bool cbfY = cbf_comp( cs, tu.Y(), trDepth );
+#endif
         TU::setCbfAtDepth( tu, COMPONENT_Y, trDepth, ( cbfY ? 1 : 0 ) );
       }
     }
+#if JVET_M0102_INTRA_SUBPARTITIONS
+    if( area.chromaFormat != CHROMA_400 && ( !cu.ispMode || chromaCbfISP ) )
+#else
     if( area.chromaFormat != CHROMA_400 )
+#endif
     {
       TU::setCbfAtDepth( tu, COMPONENT_Cb, trDepth, ( chromaCbfs.Cb ? 1 : 0 ) );
       TU::setCbfAtDepth( tu, COMPONENT_Cr, trDepth, ( chromaCbfs.Cr ? 1 : 0 ) );
@@ -2226,9 +2334,15 @@ void CABACReader::transform_tree( CodingStructure &cs, Partitioner &partitioner,
   }
 }
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+bool CABACReader::cbf_comp( CodingStructure& cs, const CompArea& area, unsigned depth, const bool prevCbf, const bool useISP )
+{
+  const unsigned  ctxId = DeriveCtx::CtxQtCbf( area.compID, depth, prevCbf, useISP && isLuma( area.compID ) );
+#else
 bool CABACReader::cbf_comp( CodingStructure& cs, const CompArea& area, unsigned depth, const bool prevCbCbf )
 {
   const unsigned  ctxId   = DeriveCtx::CtxQtCbf( area.compID, depth, prevCbCbf );
+#endif
   const CtxSet&   ctxSet  = Ctx::QtCbf[ area.compID ];
 
   RExt__DECODER_DEBUG_BIT_STATISTICS_CREATE_SET_SIZE2(STATS__CABAC_BITS__QT_CBF, area.size(), area.compID);
@@ -2464,6 +2578,9 @@ void CABACReader::residual_coding( TransformUnit& tu, ComponentID compID )
 #if !JVET_M0464_UNI_MTS
   bool useEmt = ( cu.cs->sps->getSpsNext().getUseIntraEMT() && cu.predMode == MODE_INTRA ) || ( cu.cs->sps->getSpsNext().getUseInterEMT() && cu.predMode != MODE_INTRA );
   useEmt = useEmt && isLuma(compID);
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  useEmt = useEmt && !cu.ispMode;
+#endif
 #endif
 
     for( int subSetId = ( cctx.scanPosLast() >> cctx.log2CGSize() ); subSetId >= 0; subSetId--)
@@ -2542,8 +2659,11 @@ void CABACReader::mts_coding( TransformUnit& tu, ComponentID compID )
 #else
 void CABACReader::transform_skip_flag( TransformUnit& tu, ComponentID compID )
 {
-
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  if( !tu.cu->cs->pps->getUseTransformSkip() || tu.cu->transQuantBypass || !TU::hasTransformSkipFlag( *tu.cs, tu.blocks[compID] ) || ( isLuma( compID ) && tu.cu->emtFlag ) || ( tu.cu->ispMode && isLuma( compID ) ) )
+#else
   if( !tu.cu->cs->pps->getUseTransformSkip() || tu.cu->transQuantBypass || !TU::hasTransformSkipFlag( *tu.cs, tu.blocks[compID] ) || ( isLuma( compID ) && tu.cu->emtFlag ) )
+#endif
   {
     tu.transformSkip[compID] = false;
     return;
@@ -2587,6 +2707,12 @@ void CABACReader::emt_tu_index( TransformUnit& tu )
 
 void CABACReader::emt_cu_flag( CodingUnit& cu )
 {
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  if ( CU::isIntra( cu ) && cu.ispMode )
+  {
+    return;
+  }
+#endif
   const CodingStructure &cs = *cu.cs;
 
   if( !( ( cs.sps->getSpsNext().getUseIntraEMT() && CU::isIntra( cu ) ) || ( cs.sps->getSpsNext().getUseInterEMT() && CU::isInter( cu ) ) ) || isChroma( cu.chType ) )
@@ -2620,6 +2746,46 @@ void CABACReader::emt_cu_flag( CodingUnit& cu )
 }
 #endif
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+void CABACReader::isp_mode( CodingUnit& cu )
+{
+  if( !CU::isIntra( cu ) || !isLuma( cu.chType ) || cu.firstPU->multiRefIdx )
+  {
+    cu.ispMode = NOT_INTRA_SUBPARTITIONS;
+    return;
+  }
+
+  const ISPType allowedSplits = CU::canUseISPSplit( cu, getFirstComponentOfChannel( cu.chType ) );
+  if( allowedSplits == NOT_INTRA_SUBPARTITIONS )
+  {
+    cu.ispMode = NOT_INTRA_SUBPARTITIONS;
+    return;
+  }
+
+  RExt__DECODER_DEBUG_BIT_STATISTICS_CREATE_SET( STATS__CABAC_BITS__ISP_MODE_FLAG );
+  cu.ispMode = NOT_INTRA_SUBPARTITIONS;
+  int symbol = m_BinDecoder.decodeBin( Ctx::ISPMode( 0 ) );
+
+  if( symbol )
+  {
+    if( allowedSplits == HOR_INTRA_SUBPARTITIONS )
+    {
+      cu.ispMode = HOR_INTRA_SUBPARTITIONS;
+    }
+    else if( allowedSplits == VER_INTRA_SUBPARTITIONS )
+    {
+      cu.ispMode = VER_INTRA_SUBPARTITIONS;
+    }
+    else
+    {
+      RExt__DECODER_DEBUG_BIT_STATISTICS_CREATE_SET( STATS__CABAC_BITS__ISP_SPLIT_FLAG );
+      cu.ispMode = 1 + m_BinDecoder.decodeBin( Ctx::ISPMode( 1 ) );
+    }
+  }
+  DTRACE( g_trace_ctx, D_SYNTAX, "intra_subPartitions() etype=%d pos=(%d,%d) ispIdx=%d\n", cu.chType, cu.blocks[cu.chType].x, cu.blocks[cu.chType].y, (int)cu.ispMode );
+}
+#endif
+
 void CABACReader::explicit_rdpcm_mode( TransformUnit& tu, ComponentID compID )
 {
   const CodingUnit& cu = *tu.cu;
diff --git a/source/Lib/DecoderLib/CABACReader.h b/source/Lib/DecoderLib/CABACReader.h
index 29e463caafb6d2e4ee3a6cb904c156e50601c4e8..941be20b9b125d72668365312bc8fb603a9a2a49 100644
--- a/source/Lib/DecoderLib/CABACReader.h
+++ b/source/Lib/DecoderLib/CABACReader.h
@@ -124,8 +124,13 @@ public:
   void        pcm_samples               ( TransformUnit&                tu );
 
   // transform tree (clause 7.3.8.8)
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  void        transform_tree            ( CodingStructure&              cs,     Partitioner&    pm,       CUCtx& cuCtx,  ChromaCbfs& chromaCbfs, const PartSplit ispType = TU_NO_ISP, const int subTuIdx = -1 );
+  bool        cbf_comp                  ( CodingStructure&              cs,     const CompArea& area,     unsigned depth, const bool prevCbCbf = false, const bool useISP = false );
+#else
   void        transform_tree            ( CodingStructure&              cs,     Partitioner&    pm,       CUCtx& cuCtx,  ChromaCbfs& chromaCbfs );
   bool        cbf_comp                  ( CodingStructure&              cs,     const CompArea& area,     unsigned depth, const bool prevCbCbf = false );
+#endif
 
   // mvd coding (clause 7.3.8.9)
   void        mvd_coding                ( Mv &rMvd );
@@ -143,6 +148,9 @@ public:
   void        transform_skip_flag       ( TransformUnit&                tu,     ComponentID     compID );
   void        emt_tu_index              ( TransformUnit&                tu );
   void        emt_cu_flag               ( CodingUnit&                   cu );
+#endif
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  void        isp_mode                  ( CodingUnit&                   cu );
 #endif
   void        explicit_rdpcm_mode       ( TransformUnit&                tu,     ComponentID     compID );
   int         last_sig_coeff            ( CoeffCodingContext&           cctx );
diff --git a/source/Lib/DecoderLib/DecCu.cpp b/source/Lib/DecoderLib/DecCu.cpp
index 56c3ac80fa98e6517bfdb474930b08c4afcf4f3f..baaf5739a88746a282c8c18a17933a2f49d49630 100644
--- a/source/Lib/DecoderLib/DecCu.cpp
+++ b/source/Lib/DecoderLib/DecCu.cpp
@@ -253,7 +253,18 @@ void DecCu::xIntraRecBlk( TransformUnit& tu, const ComponentID compID )
 
   PelBuf pReco = cs.getRecoBuf( area );
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  if( !tu.cu->ispMode || !isLuma( compID ) )
+  {
+    cs.setDecomp( area );
+  }
+  else if( tu.cu->ispMode && isLuma( compID ) && CU::isISPFirst( *tu.cu, tu.blocks[compID], compID ) )
+  {
+    cs.setDecomp( tu.cu->blocks[compID] );
+  }
+#else
   cs.setDecomp( area );
+#endif
 
 #if JVET_M0427_INLOOP_RESHAPER
 #if REUSE_CU_RESULTS
diff --git a/source/Lib/EncoderLib/CABACWriter.cpp b/source/Lib/EncoderLib/CABACWriter.cpp
index 0c5ca538c96f879290883445226651cc5e6b8f07..4323228d3b5cad6969d0f40fd5283e516b206a87 100644
--- a/source/Lib/EncoderLib/CABACWriter.cpp
+++ b/source/Lib/EncoderLib/CABACWriter.cpp
@@ -745,6 +745,9 @@ void CABACWriter::coding_unit( const CodingUnit& cu, Partitioner& partitioner, C
 
   extend_ref_line(cu);
 
+#if JVET_M0102_INTRA_SUBPARTITIONS 
+  isp_mode( cu );
+#endif
 
   // prediction data ( intra prediction modes / reference indexes + motion vectors )
   cu_pred_data( cu );
@@ -1015,7 +1018,11 @@ void CABACWriter::intra_luma_pred_modes( const CodingUnit& cu )
         break;
       }
     }
+#if JVET_M0102_INTRA_SUBPARTITIONS
+    if( pu->multiRefIdx || ( cu.ispMode && isLuma( cu.chType ) ) )
+#else
     if (pu->multiRefIdx)
+#endif
     {
       CHECK(mpm_idx >= numMPMs, "use of non-MPM");
     }
@@ -1100,7 +1107,11 @@ void CABACWriter::intra_luma_pred_mode( const PredictionUnit& pu )
       break;
     }
   }
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  if( pu.multiRefIdx || ( pu.cu->ispMode && isLuma( pu.cu->chType ) ) )
+#else
   if (pu.multiRefIdx)
+#endif
   {
     CHECK(mpm_idx >= numMPMs, "use of non-MPM");
   }
@@ -1240,7 +1251,19 @@ void CABACWriter::cu_residual( const CodingUnit& cu, Partitioner& partitioner, C
 
 
   ChromaCbfs chromaCbfs;
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  if( cu.ispMode && isLuma( partitioner.chType ) )
+  {
+    TUIntraSubPartitioner subTuPartitioner( partitioner );
+    transform_tree( *cu.cs, subTuPartitioner, cuCtx, chromaCbfs, CU::getISPType( cu, getFirstComponentOfChannel( partitioner.chType ) ), 0 );
+  }
+  else
+  {
+    transform_tree( *cu.cs, partitioner, cuCtx, chromaCbfs );
+  }
+#else
   transform_tree( *cu.cs, partitioner, cuCtx, chromaCbfs );
+#endif
 }
 
 void CABACWriter::rqt_root_cbf( const CodingUnit& cu )
@@ -1939,13 +1962,25 @@ void CABACWriter::pcm_samples( const TransformUnit& tu )
 //    bool  cbf_comp            ( cbf, area, depth )
 //================================================================================
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+void CABACWriter::transform_tree( const CodingStructure& cs, Partitioner& partitioner, CUCtx& cuCtx, ChromaCbfs& chromaCbfs, const PartSplit ispType, const int subTuIdx )
+#else
 void CABACWriter::transform_tree( const CodingStructure& cs, Partitioner& partitioner, CUCtx& cuCtx, ChromaCbfs& chromaCbfs )
+#endif
 {
   const UnitArea&       area          = partitioner.currArea();
+#if JVET_M0102_INTRA_SUBPARTITIONS
+        int             subTuCounter  = subTuIdx;
+  const TransformUnit&  tu            = *cs.getTU( area.blocks[partitioner.chType].pos(), partitioner.chType, subTuIdx );
+#else
   const TransformUnit&  tu            = *cs.getTU( area.blocks[partitioner.chType].pos(), partitioner.chType );
+#endif
   const CodingUnit&     cu            = *tu.cu;
   const unsigned        trDepth       = partitioner.currTrDepth;
   const bool            split         = ( tu.depth > trDepth );
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  const bool            chromaCbfISP  = area.blocks[COMPONENT_Cb].valid() && cu.ispMode && !split;
+#endif
 
   // split_transform_flag
   if( partitioner.canSplit( TU_MAX_TR_SPLIT, cs ) )
@@ -1953,12 +1988,43 @@ void CABACWriter::transform_tree( const CodingStructure& cs, Partitioner& partit
     CHECK( !split, "transform split implied" );
   }
   else
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  CHECK( split && !cu.ispMode, "transform split not allowed with QTBT" );
+#else
   CHECK( split, "transform split not allowed with QTBT" );
+#endif
+
 
   // cbf_cb & cbf_cr
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  if( area.chromaFormat != CHROMA_400 && area.blocks[COMPONENT_Cb].valid() && ( !CS::isDualITree( cs ) || 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 ) )
+#endif
   {
     {
+#if JVET_M0102_INTRA_SUBPARTITIONS
+      unsigned cbfDepth = chromaCbfISP ? trDepth - 1 : trDepth;
+      if( trDepth == 0 || chromaCbfs.Cb || chromaCbfISP )
+      {
+        chromaCbfs.Cb = TU::getCbfAtDepth( tu, COMPONENT_Cb, trDepth );
+        cbf_comp( cs, chromaCbfs.Cb, area.blocks[COMPONENT_Cb], cbfDepth );
+      }
+      else
+      {
+        CHECK( TU::getCbfAtDepth( tu, COMPONENT_Cb, cbfDepth ) != chromaCbfs.Cb, "incorrect Cb cbf" );
+      }
+
+      if( trDepth == 0 || chromaCbfs.Cr || chromaCbfISP )
+      {
+        chromaCbfs.Cr = TU::getCbfAtDepth( tu, COMPONENT_Cr, trDepth );
+        cbf_comp( cs, chromaCbfs.Cr, area.blocks[COMPONENT_Cr], cbfDepth, chromaCbfs.Cb );
+      }
+      else
+      {
+        CHECK( TU::getCbfAtDepth( tu, COMPONENT_Cr, cbfDepth ) != chromaCbfs.Cr, "incorrect Cr cbf" );
+      }
+#else
       if( trDepth == 0 || chromaCbfs.Cb )
       {
         chromaCbfs.Cb = TU::getCbfAtDepth( tu, COMPONENT_Cb, trDepth );
@@ -1978,6 +2044,7 @@ void CABACWriter::transform_tree( const CodingStructure& cs, Partitioner& partit
       {
         CHECK( TU::getCbfAtDepth( tu, COMPONENT_Cr, trDepth ) != chromaCbfs.Cr, "incorrect Cr cbf" );
       }
+#endif
     }
   }
   else if( CS::isDualITree( cs ) )
@@ -1993,7 +2060,11 @@ void CABACWriter::transform_tree( const CodingStructure& cs, Partitioner& partit
       chromaCbfs.Cr        = TU::getCbfAtDepth( tu, COMPONENT_Cr,  trDepth );
     }
 #if !JVET_M0464_UNI_MTS
+#if JVET_M0102_INTRA_SUBPARTITIONS
+    if ( trDepth == 0 && !cu.ispMode ) emt_cu_flag( cu );
+#else
     if( trDepth == 0 ) emt_cu_flag( cu );
+#endif
 #endif
 
     if( partitioner.canSplit( TU_MAX_TR_SPLIT, cs ) )
@@ -2005,13 +2076,24 @@ void CABACWriter::transform_tree( const CodingStructure& cs, Partitioner& partit
 #endif
       partitioner.splitCurrArea( TU_MAX_TR_SPLIT, cs );
     }
+#if JVET_M0102_INTRA_SUBPARTITIONS
+    else if( cu.ispMode )
+    {
+      partitioner.splitCurrArea( ispType, cs );
+    }
+#endif
     else
       THROW( "Implicit TU split not available" );
 
     do
     {
       ChromaCbfs subChromaCbfs = chromaCbfs;
+#if JVET_M0102_INTRA_SUBPARTITIONS
+      transform_tree( cs, partitioner, cuCtx, subChromaCbfs, ispType, subTuCounter );
+      subTuCounter += subTuCounter != -1 ? 1 : 0;
+#else
       transform_tree( cs, partitioner, cuCtx, subChromaCbfs );
+#endif
     } while( partitioner.nextPart( cs ) );
 
     partitioner.exitCurrSplit();
@@ -2028,7 +2110,38 @@ void CABACWriter::transform_tree( const CodingStructure& cs, Partitioner& partit
       }
       else
       {
+#if JVET_M0102_INTRA_SUBPARTITIONS
+        bool previousCbf       = false;
+        bool rootCbfSoFar      = false;
+        bool lastCbfIsInferred = false;
+        if( cu.ispMode )
+        {
+          uint32_t nTus = cu.ispMode == HOR_INTRA_SUBPARTITIONS ? cu.lheight() >> g_aucLog2[tu.lheight()] : cu.lwidth() >> g_aucLog2[tu.lwidth()];
+          if( subTuCounter == nTus - 1 )
+          {
+            TransformUnit* tuPointer = cu.firstTU;
+            for( int tuIdx = 0; tuIdx < subTuCounter; tuIdx++ )
+            {
+              rootCbfSoFar |= TU::getCbfAtDepth( *tuPointer, COMPONENT_Y, trDepth );
+              tuPointer = tuPointer->next;
+            }
+            if( !rootCbfSoFar )
+            {
+              lastCbfIsInferred = true;
+            }
+          }
+          if( !lastCbfIsInferred )
+          {
+            previousCbf = TU::getPrevTuCbfAtDepth( tu, COMPONENT_Y, partitioner.currTrDepth );
+          }
+        }
+        if( !lastCbfIsInferred )
+        {
+          cbf_comp( cs, TU::getCbfAtDepth( tu, COMPONENT_Y, trDepth ), tu.Y(), trDepth, previousCbf, cu.ispMode );
+        }
+#else
         cbf_comp( cs, TU::getCbfAtDepth( tu, COMPONENT_Y, trDepth ), tu.Y(), trDepth );
+#endif
       }
     }
 
@@ -2040,9 +2153,15 @@ void CABACWriter::transform_tree( const CodingStructure& cs, Partitioner& partit
   }
 }
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+void CABACWriter::cbf_comp( const CodingStructure& cs, bool cbf, const CompArea& area, unsigned depth, const bool prevCbCbf, const bool useISP )
+{
+  const unsigned  ctxId   = DeriveCtx::CtxQtCbf( area.compID, depth, prevCbCbf, useISP && isLuma(area.compID) );
+#else
 void CABACWriter::cbf_comp( const CodingStructure& cs, bool cbf, const CompArea& area, unsigned depth, const bool prevCbCbf )
 {
   const unsigned  ctxId   = DeriveCtx::CtxQtCbf( area.compID, depth, prevCbCbf );
+#endif
   const CtxSet&   ctxSet  = Ctx::QtCbf[ area.compID ];
 
   m_BinEncoder.encodeBin( cbf, ctxSet( ctxId ) );
@@ -2306,6 +2425,9 @@ void CABACWriter::residual_coding( const TransformUnit& tu, ComponentID compID )
 #if !JVET_M0464_UNI_MTS
   bool useEmt = ( cu.cs->sps->getSpsNext().getUseIntraEMT() && cu.predMode == MODE_INTRA ) || ( cu.cs->sps->getSpsNext().getUseInterEMT() && cu.predMode != MODE_INTRA );
   useEmt = useEmt && isLuma(compID);
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  useEmt = useEmt && !cu.ispMode;
+#endif
 #endif
 
   for( int subSetId = ( cctx.scanPosLast() >> cctx.log2CGSize() ); subSetId >= 0; subSetId--)
@@ -2387,7 +2509,11 @@ void CABACWriter::mts_coding( const TransformUnit& tu, ComponentID compID )
 #else
 void CABACWriter::transform_skip_flag( const TransformUnit& tu, ComponentID compID )
 {
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  if (!tu.cu->cs->pps->getUseTransformSkip() || tu.cu->transQuantBypass || !TU::hasTransformSkipFlag(*tu.cs, tu.blocks[compID]) || (isLuma(compID) && tu.cu->emtFlag) || (tu.cu->ispMode && isLuma(compID)))
+#else
   if( !tu.cu->cs->pps->getUseTransformSkip() || tu.cu->transQuantBypass || !TU::hasTransformSkipFlag( *tu.cs, tu.blocks[compID] ) || ( isLuma( compID ) && tu.cu->emtFlag ) )
+#endif
   {
     return;
   }
@@ -2420,6 +2546,12 @@ void CABACWriter::emt_tu_index( const TransformUnit& tu )
 //void CABACWriter::emt_cu_flag(const CodingUnit& cu, uint32_t depth, bool codeCuFlag, const int tuWidth,const int tuHeight)
 void CABACWriter::emt_cu_flag( const CodingUnit& cu )
 {
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  if ( CU::isIntra( cu ) && cu.ispMode )
+  {
+    return;
+  }
+#endif
   const CodingStructure& cs = *cu.cs;
 
   if( !( ( cs.sps->getSpsNext().getUseIntraEMT() && CU::isIntra( cu ) ) || ( cs.sps->getSpsNext().getUseInterEMT() && CU::isInter( cu ) ) ) || isChroma( cu.chType ) )
@@ -2448,6 +2580,33 @@ void CABACWriter::emt_cu_flag( const CodingUnit& cu )
 }
 #endif
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+void CABACWriter::isp_mode( const CodingUnit& cu )
+{
+  if( !CU::isIntra( cu ) || !isLuma( cu.chType ) || cu.firstPU->multiRefIdx )
+  {
+    CHECK( cu.ispMode != NOT_INTRA_SUBPARTITIONS, "error: cu.intraSubPartitions != 0" );
+    return;
+  }
+  const ISPType allowedSplits = CU::canUseISPSplit( cu, getFirstComponentOfChannel( cu.chType ) );
+  if( allowedSplits == NOT_INTRA_SUBPARTITIONS ) return;
+
+  if( cu.ispMode == NOT_INTRA_SUBPARTITIONS )
+  {
+    m_BinEncoder.encodeBin( 0, Ctx::ISPMode( 0 ) );
+  }
+  else
+  {
+    m_BinEncoder.encodeBin( 1, Ctx::ISPMode( 0 ) );
+
+    if( allowedSplits == CAN_USE_VER_AND_HORL_SPLITS )
+    {
+      m_BinEncoder.encodeBin( cu.ispMode - 1, Ctx::ISPMode( 1 ) );
+    }
+  }
+  DTRACE( g_trace_ctx, D_SYNTAX, "intra_subPartitions() etype=%d pos=(%d,%d) ispIdx=%d\n", cu.chType, cu.blocks[cu.chType].x, cu.blocks[cu.chType].y, (int)cu.ispMode );
+}
+#endif
 
 void CABACWriter::explicit_rdpcm_mode( const TransformUnit& tu, ComponentID compID )
 {
diff --git a/source/Lib/EncoderLib/CABACWriter.h b/source/Lib/EncoderLib/CABACWriter.h
index 51fbd8ddd98eb5705e26d18101e03cac957d3264..8ddd3c5fcb0eae3260426fcd6a559e17bb859de3 100644
--- a/source/Lib/EncoderLib/CABACWriter.h
+++ b/source/Lib/EncoderLib/CABACWriter.h
@@ -135,8 +135,13 @@ public:
   void        pcm_samples               ( const TransformUnit&          tu );
 
   // transform tree (clause 7.3.8.8)
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  void        transform_tree            ( const CodingStructure&        cs,       Partitioner&      pm,     CUCtx& cuCtx, ChromaCbfs& chromaCbfs, const PartSplit ispType = TU_NO_ISP, const int subTuIdx = -1 );
+  void        cbf_comp                  ( const CodingStructure&        cs,       bool              cbf,    const CompArea& area, unsigned depth, const bool prevCbCbf = false, const bool useISP = false );
+#else
   void        transform_tree            ( const CodingStructure&        cs,       Partitioner&      pm,     CUCtx& cuCtx,   ChromaCbfs& chromaCbfs );
   void        cbf_comp                  ( const CodingStructure&        cs,       bool              cbf,    const CompArea& area, unsigned depth, const bool prevCbCbf = false );
+#endif
 
   // mvd coding (clause 7.3.8.9)
 #if JVET_M0246_AFFINE_AMVR
@@ -157,6 +162,9 @@ public:
   void        transform_skip_flag       ( const TransformUnit&          tu,       ComponentID       compID );
   void        emt_tu_index              ( const TransformUnit&          tu );
   void        emt_cu_flag               ( const CodingUnit&             cu );
+#endif
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  void        isp_mode                  ( const CodingUnit&             cu );
 #endif
   void        explicit_rdpcm_mode       ( const TransformUnit&          tu,       ComponentID       compID );
   void        last_sig_coeff            ( CoeffCodingContext&           cctx );
diff --git a/source/Lib/EncoderLib/EncCu.cpp b/source/Lib/EncoderLib/EncCu.cpp
index 4271515528d26af2b720baa47e09598919279742..d91e00e72fe74d49738980306ee455b0e859d531 100644
--- a/source/Lib/EncoderLib/EncCu.cpp
+++ b/source/Lib/EncoderLib/EncCu.cpp
@@ -323,6 +323,9 @@ void EncCu::init( EncLib* pcEncLib, const SPS& sps PARL_PARAM( const int tId ) )
   m_modeCtrl->init( m_pcEncCfg, m_pcRateCtrl, m_pcRdCost );
 
   m_pcInterSearch->setModeCtrl( m_modeCtrl );
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  m_pcIntraSearch->setModeCtrl( m_modeCtrl );
+#endif
   ::memset(m_subMergeBlkSize, 0, sizeof(m_subMergeBlkSize));
   ::memset(m_subMergeBlkNum, 0, sizeof(m_subMergeBlkNum));
   m_prevPOC = MAX_UINT;
@@ -1464,6 +1467,13 @@ void EncCu::xCheckRDCostIntra( CodingStructure *&tempCS, CodingStructure *&bestC
   uint8_t considerEmtSecondPass = ( sps.getSpsNext().getUseIntraEMT() && isLuma( partitioner.chType ) && partitioner.currArea().lwidth() <= maxSizeEMT && partitioner.currArea().lheight() <= maxSizeEMT ) ? 1 : 0;
 #endif
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  bool   useIntraSubPartitions   = false;
+  double maxCostAllowedForChroma = MAX_DOUBLE;
+#if JVET_M0464_UNI_MTS
+  const  CodingUnit *bestCU      = bestCS->getCU( partitioner.chType );
+#endif
+#endif
   Distortion interHad = m_modeCtrl->getInterHad();
 
 
@@ -1504,6 +1514,9 @@ void EncCu::xCheckRDCostIntra( CodingStructure *&tempCS, CodingStructure *&bestC
 #if !JVET_M0464_UNI_MTS
     cu.emtFlag          = emtCuFlag;
 #endif
+#if JVET_M0102_INTRA_SUBPARTITIONS 
+    cu.ispMode          = NOT_INTRA_SUBPARTITIONS;
+#endif
 
     CU::addPUs( cu );
 
@@ -1511,7 +1524,24 @@ void EncCu::xCheckRDCostIntra( CodingStructure *&tempCS, CodingStructure *&bestC
 
     if( isLuma( partitioner.chType ) )
     {
+#if JVET_M0102_INTRA_SUBPARTITIONS
+      //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 
+      const double bestCostSoFar = CS::isDualITree( *tempCS ) ? m_modeCtrl->getBestCostWithoutSplitFlags() : bestCU && bestCU->predMode == MODE_INTRA ? bestCS->lumaCost : bestCS->cost;
+      m_pcIntraSearch->estIntraPredLumaQT( cu, partitioner, bestCostSoFar );
+
+      useIntraSubPartitions = cu.ispMode != NOT_INTRA_SUBPARTITIONS;
+      if( !CS::isDualITree( *tempCS ) )
+      {
+        tempCS->lumaCost = m_pcRdCost->calcRdCost( tempCS->fracBits, tempCS->dist );
+        if( useIntraSubPartitions )
+        {
+          //the difference between the best cost so far and the current luma cost is stored to avoid testing the Cr component if the cost of luma + Cb is larger than the best cost
+          maxCostAllowedForChroma = bestCS->cost < MAX_DOUBLE ? bestCS->cost - tempCS->lumaCost : MAX_DOUBLE;
+        }
+      }
+#else
       m_pcIntraSearch->estIntraPredLumaQT( cu, partitioner );
+#endif
 
       if (m_pcEncCfg->getUsePbIntraFast() && tempCS->dist == std::numeric_limits<Distortion>::max()
           && tempCS->interHad == 0)
@@ -1537,7 +1567,21 @@ void EncCu::xCheckRDCostIntra( CodingStructure *&tempCS, CodingStructure *&bestC
 
     if( tempCS->area.chromaFormat != CHROMA_400 && ( partitioner.chType == CHANNEL_TYPE_CHROMA || !CS::isDualITree( *tempCS ) ) )
     {
+#if JVET_M0102_INTRA_SUBPARTITIONS
+      TUIntraSubPartitioner subTuPartitioner( partitioner );
+      m_pcIntraSearch->estIntraPredChromaQT( cu, ( !useIntraSubPartitions || ( CS::isDualITree( *cu.cs ) && !isLuma( CHANNEL_TYPE_CHROMA ) ) ) ? partitioner : subTuPartitioner, maxCostAllowedForChroma );
+      if( useIntraSubPartitions && !cu.ispMode )
+      {
+        //At this point the temp cost is larger than the best cost. Therefore, we can already skip the remaining calculations
+#if JVET_M0464_UNI_MTS
+        return;
+#else
+        continue;
+#endif
+      }
+#else
       m_pcIntraSearch->estIntraPredChromaQT( cu, partitioner );
+#endif
     }
 
     cu.rootCbf = false;
@@ -1563,6 +1607,9 @@ void EncCu::xCheckRDCostIntra( CodingStructure *&tempCS, CodingStructure *&bestC
     }
     m_CABACEstimator->pred_mode      ( cu );
     m_CABACEstimator->extend_ref_line( cu );
+#if JVET_M0102_INTRA_SUBPARTITIONS
+    m_CABACEstimator->isp_mode       ( cu );
+#endif
     m_CABACEstimator->cu_pred_data   ( cu );
     m_CABACEstimator->pcm_data       ( cu, partitioner );
 
@@ -1575,10 +1622,22 @@ void EncCu::xCheckRDCostIntra( CodingStructure *&tempCS, CodingStructure *&bestC
     tempCS->fracBits = m_CABACEstimator->getEstFracBits();
     tempCS->cost     = m_pcRdCost->calcRdCost(tempCS->fracBits, tempCS->dist);
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+#if !JVET_M0464_UNI_MTS
+    double bestIspCost = cu.ispMode ? CS::isDualITree(*tempCS) ? tempCS->cost : tempCS->lumaCost : MAX_DOUBLE;
+#endif
+    const double tmpCostWithoutSplitFlags = tempCS->cost;
+#endif
     xEncodeDontSplit( *tempCS, partitioner );
 
     xCheckDQP( *tempCS, partitioner );
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+    if( tempCS->cost < bestCS->cost )
+    {
+      m_modeCtrl->setBestCostWithoutSplitFlags( tmpCostWithoutSplitFlags );
+    }
+#endif
 #if !JVET_M0464_UNI_MTS
     // we save the cost of the modes for the first EMT pass
     if( !emtCuFlag ) static_cast< double& >( costSize2Nx2NemtFirstPass ) = tempCS->cost;
@@ -1592,6 +1651,22 @@ void EncCu::xCheckRDCostIntra( CodingStructure *&tempCS, CodingStructure *&bestC
     xCheckBestMode( tempCS, bestCS, partitioner, encTestMode );
 
 #if !JVET_M0464_UNI_MTS
+#if JVET_M0102_INTRA_SUBPARTITIONS
+    //we decide to skip the second emt pass or not according to the ISP results
+    if (considerEmtSecondPass && cu.ispMode && !emtCuFlag && tempCS->slice->isIntra())
+    {
+      double bestCostDct2NoIsp = m_modeCtrl->getEmtFirstPassNoIspCost();
+      CHECKD(bestCostDct2NoIsp <= bestIspCost, "wrong cost!");
+      double nSamples = (double)(cu.lwidth() << g_aucLog2[cu.lheight()]);
+      double threshold = 1 + 1.4 / sqrt(nSamples);
+      if (bestCostDct2NoIsp > bestIspCost*threshold)
+      {
+        skipSecondEmtPass = true;
+        m_modeCtrl->setSkipSecondEMTPass(true);
+        break;
+      }
+    }
+#endif
     //now we check whether the second pass of SIZE_2Nx2N and the whole Intra SIZE_NxN should be skipped or not
     if( !emtCuFlag && !tempCS->slice->isIntra() && bestCU && bestCU->predMode != MODE_INTRA && m_pcEncCfg->getFastInterEMT() )
     {
diff --git a/source/Lib/EncoderLib/EncModeCtrl.cpp b/source/Lib/EncoderLib/EncModeCtrl.cpp
index 560a59cf5cb805694c73e311f5cbabb52ee2baeb..6e175ebeff5ed0d4452611186602876b1304de14 100644
--- a/source/Lib/EncoderLib/EncModeCtrl.cpp
+++ b/source/Lib/EncoderLib/EncModeCtrl.cpp
@@ -635,7 +635,15 @@ void BestEncInfoCache::create( const ChromaFormat chFmt )
 
               m_bestEncInfo[x][y][wIdx][hIdx]->cu.UnitArea::operator=( area );
               m_bestEncInfo[x][y][wIdx][hIdx]->pu.UnitArea::operator=( area );
+#if REUSE_CU_RESULTS_WITH_MULTIPLE_TUS
+              m_bestEncInfo[x][y][wIdx][hIdx]->numTus = 0;
+              for( int i = 0; i < MAX_NUM_TUS; i++ )
+              {
+                m_bestEncInfo[x][y][wIdx][hIdx]->tus[i].UnitArea::operator=(area);
+              }
+#else
               m_bestEncInfo[x][y][wIdx][hIdx]->tu.UnitArea::operator=( area );
+#endif
 
               m_bestEncInfo[x][y][wIdx][hIdx]->poc      = -1;
               m_bestEncInfo[x][y][wIdx][hIdx]->testMode = EncTestMode();
@@ -722,8 +730,13 @@ void BestEncInfoCache::init( const Slice &slice )
     }
   }
 
+#if REUSE_CU_RESULTS_WITH_MULTIPLE_TUS
+  m_pCoeff  = new TCoeff[numCoeff*MAX_NUM_TUS];
+  m_pPcmBuf = new Pel   [numCoeff*MAX_NUM_TUS];
+#else
   m_pCoeff  = new TCoeff[numCoeff];
   m_pPcmBuf = new Pel   [numCoeff];
+#endif
 
   TCoeff *coeffPtr = m_pCoeff;
   Pel    *pcmPtr   = m_pPcmBuf;
@@ -743,6 +756,22 @@ void BestEncInfoCache::init( const Slice &slice )
             TCoeff *coeff[MAX_NUM_TBLOCKS] = { 0, };
             Pel    *pcmbf[MAX_NUM_TBLOCKS] = { 0, };
 
+#if REUSE_CU_RESULTS_WITH_MULTIPLE_TUS
+            for( int i = 0; i < MAX_NUM_TUS; i++ )
+            {
+              TransformUnit &tu = m_bestEncInfo[x][y][wIdx][hIdx]->tus[i];
+              const UnitArea &area = tu;
+
+              for( int i = 0; i < area.blocks.size(); i++ )
+              {
+                coeff[i] = coeffPtr; coeffPtr += area.blocks[i].area();
+                pcmbf[i] = pcmPtr;   pcmPtr += area.blocks[i].area();
+              }
+
+              tu.cs = &m_dummyCS;
+              tu.init(coeff, pcmbf);
+            }
+#else
             const UnitArea &area = m_bestEncInfo[x][y][wIdx][hIdx]->tu;
 
             for( int i = 0; i < area.blocks.size(); i++ )
@@ -753,6 +782,7 @@ void BestEncInfoCache::init( const Slice &slice )
 
             m_bestEncInfo[x][y][wIdx][hIdx]->tu.cs = &m_dummyCS;
             m_bestEncInfo[x][y][wIdx][hIdx]->tu.init( coeff, pcmbf );
+#endif
           }
         }
       }
@@ -762,7 +792,11 @@ void BestEncInfoCache::init( const Slice &slice )
 
 bool BestEncInfoCache::setFromCs( const CodingStructure& cs, const Partitioner& partitioner )
 {
+#if REUSE_CU_RESULTS_WITH_MULTIPLE_TUS
+  if( cs.cus.size() != 1 || cs.pus.size() != 1 )
+#else
   if( cs.cus.size() != 1 || cs.tus.size() != 1 || cs.pus.size() != 1 )
+#endif
   {
     return false;
   }
@@ -775,13 +809,32 @@ bool BestEncInfoCache::setFromCs( const CodingStructure& cs, const Partitioner&
   encInfo.poc            =  cs.picture->poc;
   encInfo.cu.repositionTo( *cs.cus.front() );
   encInfo.pu.repositionTo( *cs.pus.front() );
+#if !REUSE_CU_RESULTS_WITH_MULTIPLE_TUS
   encInfo.tu.repositionTo( *cs.tus.front() );
+#endif
   encInfo.cu             = *cs.cus.front();
   encInfo.pu             = *cs.pus.front();
+#if REUSE_CU_RESULTS_WITH_MULTIPLE_TUS
+  int tuIdx = 0;
+  for( auto tu : cs.tus )
+  {
+    encInfo.tus[tuIdx].repositionTo( *tu );
+    encInfo.tus[tuIdx].resizeTo( *tu );
+    for( auto &blk : tu->blocks )
+    {
+      if( blk.valid() )
+        encInfo.tus[tuIdx].copyComponentFrom( *tu, blk.compID );
+    }
+    tuIdx++;
+  }
+  CHECKD( cs.tus.size() > MAX_NUM_TUS, "Exceeding tus array boundaries" );
+  encInfo.numTus = cs.tus.size();
+#else
   for( auto &blk : cs.tus.front()->blocks )
   {
     if( blk.valid() ) encInfo.tu.copyComponentFrom( *cs.tus.front(), blk.compID );
   }
+#endif
   encInfo.testMode       = getCSEncMode( cs );
 
   return true;
@@ -830,18 +883,35 @@ bool BestEncInfoCache::setCsFrom( CodingStructure& cs, EncTestMode& testMode, co
 
   CodingUnit     &cu = cs.addCU( CS::getArea( cs, cs.area, partitioner.chType ), partitioner.chType );
   PredictionUnit &pu = cs.addPU( CS::getArea( cs, cs.area, partitioner.chType ), partitioner.chType );
+#if !REUSE_CU_RESULTS_WITH_MULTIPLE_TUS
   TransformUnit  &tu = cs.addTU( CS::getArea( cs, cs.area, partitioner.chType ), partitioner.chType );
+#endif
 
   cu          .repositionTo( encInfo.cu );
   pu          .repositionTo( encInfo.pu );
+#if !REUSE_CU_RESULTS_WITH_MULTIPLE_TUS
   tu          .repositionTo( encInfo.tu );
+#endif
 
   cu          = encInfo.cu;
   pu          = encInfo.pu;
+#if REUSE_CU_RESULTS_WITH_MULTIPLE_TUS
+  CHECKD( !( encInfo.numTus > 0 ), "Empty tus array" );
+  for( int i = 0; i < encInfo.numTus; i++ )
+  {
+    TransformUnit  &tu = cs.addTU( encInfo.tus[i], partitioner.chType );
+
+    for( auto &blk : tu.blocks )
+    {
+      if( blk.valid() ) tu.copyComponentFrom( encInfo.tus[i], blk.compID );
+    }
+  }
+#else
   for( auto &blk : tu.blocks )
   {
     if( blk.valid() ) tu.copyComponentFrom( encInfo.tu, blk.compID );
   }
+#endif
 
   testMode    = encInfo.testMode;
 
@@ -1685,6 +1755,12 @@ bool EncModeCtrlMTnoRQT::useModeResult( const EncTestMode& encTestmode, CodingSt
     {
       cuECtx.bestEmtSize2Nx2N1stPass = tempCS->cost;
     }
+#if JVET_M0102_INTRA_SUBPARTITIONS
+    if (!cu.ispMode)
+    {
+      cuECtx.bestCostEmtFirstPassNoIsp = tempCS->cost;
+    }
+#endif
   }
 #endif
 
diff --git a/source/Lib/EncoderLib/EncModeCtrl.h b/source/Lib/EncoderLib/EncModeCtrl.h
index b44f18c7d9a35d84d4cec94f83c7c85b4ddb6f8f..fb3e9864a90a461a426ab5dad0b7738826a030cb 100644
--- a/source/Lib/EncoderLib/EncModeCtrl.h
+++ b/source/Lib/EncoderLib/EncModeCtrl.h
@@ -204,6 +204,12 @@ struct ComprCUCtx
 #if ENABLE_SPLIT_PARALLELISM
     , isLevelSplitParallel
                     ( false )
+#endif
+#if JVET_M0102_INTRA_SUBPARTITIONS
+    , bestCostWithoutSplitFlags( MAX_DOUBLE )
+#if !JVET_M0464_UNI_MTS
+    , bestCostEmtFirstPassNoIsp( MAX_DOUBLE )
+#endif
 #endif
   {
     getAreaIdx( cs.area.Y(), *cs.pcv, cuX, cuY, cuW, cuH );
@@ -238,6 +244,12 @@ struct ComprCUCtx
   Distortion                        interHad;
 #if ENABLE_SPLIT_PARALLELISM
   bool                              isLevelSplitParallel;
+#endif
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  double                            bestCostWithoutSplitFlags;
+#if !JVET_M0464_UNI_MTS
+  double                            bestCostEmtFirstPassNoIsp;
+#endif
 #endif
 
   template<typename T> T    get( int ft )       const { return typeid(T) == typeid(double) ? (T&)extraFeaturesd[ft] : T(extraFeatures[ft]); }
@@ -324,6 +336,14 @@ public:
   bool getSkipSecondEMTPass           ()                  const { return m_ComprCUCtxList.back().skipSecondEMTPass;       }
   void setSkipSecondEMTPass           ( bool b )                {        m_ComprCUCtxList.back().skipSecondEMTPass = b;   }
 #endif
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  double getBestCostWithoutSplitFlags ()                  const { return m_ComprCUCtxList.back().bestCostWithoutSplitFlags;         }
+  void   setBestCostWithoutSplitFlags ( double cost )           { m_ComprCUCtxList.back().bestCostWithoutSplitFlags = cost;         }
+#if !JVET_M0464_UNI_MTS
+  double getEmtFirstPassNoIspCost     ()                  const { return m_ComprCUCtxList.back().bestCostEmtFirstPassNoIsp; }
+  void   setEmtFirstPassNoIspCost     ( double cost )           { m_ComprCUCtxList.back().bestCostEmtFirstPassNoIsp = cost; }
+#endif
+#endif
 
 protected:
   void xExtractFeatures ( const EncTestMode encTestmode, CodingStructure& cs );
@@ -406,7 +426,12 @@ struct BestEncodingInfo
 {
   CodingUnit     cu;
   PredictionUnit pu;
+#if REUSE_CU_RESULTS_WITH_MULTIPLE_TUS
+  TransformUnit  tus[MAX_NUM_TUS];
+  size_t         numTus;
+#else
   TransformUnit  tu;
+#endif
   EncTestMode    testMode;
 
   int            poc;
diff --git a/source/Lib/EncoderLib/IntraSearch.cpp b/source/Lib/EncoderLib/IntraSearch.cpp
index 3117211d2b1fc42259f149aaac961eabc6767b0f..d7279af9ba3e7080967cff8c90a29065ceec1230 100644
--- a/source/Lib/EncoderLib/IntraSearch.cpp
+++ b/source/Lib/EncoderLib/IntraSearch.cpp
@@ -276,7 +276,11 @@ void IntraSearch::init( EncCfg*        pcEncCfg,
 // INTRA PREDICTION
 //////////////////////////////////////////////////////////////////////////
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+void IntraSearch::estIntraPredLumaQT( CodingUnit &cu, Partitioner &partitioner, const double bestCostSoFar )
+#else
 void IntraSearch::estIntraPredLumaQT( CodingUnit &cu, Partitioner &partitioner )
+#endif
 {
   CodingStructure       &cs            = *cu.cs;
   const SPS             &sps           = *cs.sps;
@@ -322,6 +326,63 @@ void IntraSearch::estIntraPredLumaQT( CodingUnit &cu, Partitioner &partitioner )
     emtUsageFlag = 0; //this forces the recalculation of the candidates list. Why is this necessary? (to be checked)
   }
 #endif
+#if JVET_M0102_INTRA_SUBPARTITIONS
+#if JVET_M0464_UNI_MTS
+  const int width   = partitioner.currArea().lwidth();
+  const int height  = partitioner.currArea().lheight();
+  int nOptionsForISP = NUM_INTRA_SUBPARTITIONS_MODES;
+#else
+  int nOptionsForISP = cu.emtFlag == 0 ? NUM_INTRA_SUBPARTITIONS_MODES : 1;
+#endif
+  double bestCurrentCost = bestCostSoFar;
+
+  int ispOptions[NUM_INTRA_SUBPARTITIONS_MODES] = { 0 };
+  if( nOptionsForISP > 1 )
+  {
+    auto splitsThatCanBeUsedForISP = CU::canUseISPSplit( width, height, cu.cs->sps->getMaxTrSize() );
+    if( splitsThatCanBeUsedForISP == CAN_USE_VER_AND_HORL_SPLITS )
+    {
+      const CodingUnit* cuLeft  = cu.ispMode != NOT_INTRA_SUBPARTITIONS ? cs.getCU( cs.area.blocks[partitioner.chType].pos().offset( -1, 0 ), partitioner.chType ) : nullptr;
+      const CodingUnit* cuAbove = cu.ispMode != NOT_INTRA_SUBPARTITIONS ? cs.getCU( cs.area.blocks[partitioner.chType].pos().offset( 0, -1 ), partitioner.chType ) : nullptr;
+      bool ispHorIsFirstTest = CU::firstTestISPHorSplit( width, height, COMPONENT_Y, cuLeft, cuAbove );
+      if( ispHorIsFirstTest )
+      {
+        ispOptions[1] = HOR_INTRA_SUBPARTITIONS;
+        ispOptions[2] = VER_INTRA_SUBPARTITIONS;
+      }
+      else
+      {
+        ispOptions[1] = VER_INTRA_SUBPARTITIONS;
+        ispOptions[2] = HOR_INTRA_SUBPARTITIONS;
+      }
+    }
+    else if( splitsThatCanBeUsedForISP == HOR_INTRA_SUBPARTITIONS )
+    {
+      nOptionsForISP = 2;
+      ispOptions[1] = HOR_INTRA_SUBPARTITIONS;
+    }
+    else if( splitsThatCanBeUsedForISP == VER_INTRA_SUBPARTITIONS )
+    {
+      nOptionsForISP = 2;
+      ispOptions[1] = VER_INTRA_SUBPARTITIONS;
+    }
+    else
+    {
+      nOptionsForISP = 1;
+    }
+  }
+  if( nOptionsForISP > 1 )
+  {
+    //variables for the full RD list without MRL modes
+    m_rdModeListWithoutMrl      .clear();
+    m_rdModeListWithoutMrlHor   .clear();
+    m_rdModeListWithoutMrlVer   .clear();
+    //variables with data from regular intra used to skip ISP splits
+    m_intraModeDiagRatio        .clear();
+    m_intraModeHorVerRatio      .clear();
+    m_intraModeTestedNormalIntra.clear();
+  }
+#endif
 
   static_vector<uint32_t,   FAST_UDI_MAX_RDMODE_NUM> uiHadModeList;
   static_vector<double, FAST_UDI_MAX_RDMODE_NUM> CandCostList;
@@ -498,6 +559,14 @@ void IntraSearch::estIntraPredLumaQT( CodingUnit &cu, Partitioner &partitioner )
             }
           }
         }
+#if JVET_M0102_INTRA_SUBPARTITIONS
+        if( nOptionsForISP > 1 )
+        {
+          //we save the list with no mrl modes to keep only the Hadamard selected modes (no mpms)
+          m_rdModeListWithoutMrl.resize( numModesForFullRD );
+          std::copy_n( uiRdModeList.begin(), numModesForFullRD, m_rdModeListWithoutMrl.begin() );
+        }
+#endif
         pu.multiRefIdx = 1;
         const int  numMPMs = NUM_MOST_PROBABLE_MODES;
         unsigned  multiRefMPM [numMPMs];
@@ -568,6 +637,36 @@ void IntraSearch::estIntraPredLumaQT( CodingUnit &cu, Partitioner &partitioner )
               uiRdModeList.push_back( mostProbableMode );
             }
           }
+#if JVET_M0102_INTRA_SUBPARTITIONS
+          if( nOptionsForISP > 1 )
+          {
+            //we add the ISP MPMs to the list without mrl modes
+            m_rdModeListWithoutMrlHor = m_rdModeListWithoutMrl;
+            m_rdModeListWithoutMrlVer = m_rdModeListWithoutMrl;
+            static_vector<uint32_t, FAST_UDI_MAX_RDMODE_NUM>* listPointer;
+            for( int k = 1; k < nOptionsForISP; k++ )
+            {
+              cu.ispMode = ispOptions[k];
+              listPointer = &( cu.ispMode == HOR_INTRA_SUBPARTITIONS ? m_rdModeListWithoutMrlHor : m_rdModeListWithoutMrlVer );
+              const int numCandISP = PU::getIntraMPMs( pu, uiPreds );
+              for( int j = 0; j < numCandISP; j++ )
+              {
+                bool mostProbableModeIncluded = false;
+                int  mostProbableMode = uiPreds[j];
+
+                for( int i = 0; i < listPointer->size(); i++ )
+                {
+                  mostProbableModeIncluded |= ( mostProbableMode == listPointer->at( i ) );
+                }
+                if( !mostProbableModeIncluded )
+                {
+                  listPointer->push_back( mostProbableMode );
+                }
+              }
+            }
+            cu.ispMode = NOT_INTRA_SUBPARTITIONS;
+          }
+#endif
         }
       }
       else
@@ -620,6 +719,44 @@ void IntraSearch::estIntraPredLumaQT( CodingUnit &cu, Partitioner &partitioner )
     }
 #endif
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+    if( nOptionsForISP > 1 ) // we remove the non-MPMs from the ISP lists
+    {
+      static_vector< uint32_t, FAST_UDI_MAX_RDMODE_NUM > uiRdModeListCopyHor = m_rdModeListWithoutMrlHor;
+      m_rdModeListWithoutMrlHor.clear();
+      static_vector< uint32_t, FAST_UDI_MAX_RDMODE_NUM > uiRdModeListCopyVer = m_rdModeListWithoutMrlVer;
+      m_rdModeListWithoutMrlVer.clear();
+      static_vector< uint32_t, FAST_UDI_MAX_RDMODE_NUM > *listPointerCopy, *listPointer;
+      for( int ispOptionIdx = 1; ispOptionIdx < nOptionsForISP; ispOptionIdx++ )
+      {
+        cu.ispMode = ispOptions[ispOptionIdx];
+        //we get the mpm cand list
+        const int numMPMs = NUM_MOST_PROBABLE_MODES;
+        unsigned  uiPreds[numMPMs];
+
+        pu.multiRefIdx = 0;
+
+        PU::getIntraMPMs( pu, uiPreds );
+
+        //we copy only the ISP MPMs
+        listPointerCopy = &( cu.ispMode == HOR_INTRA_SUBPARTITIONS ? uiRdModeListCopyHor : uiRdModeListCopyVer );
+        listPointer     = &( cu.ispMode == HOR_INTRA_SUBPARTITIONS ? m_rdModeListWithoutMrlHor : m_rdModeListWithoutMrlVer );
+        for( int k = 0; k < listPointerCopy->size(); k++ )
+        {
+          for( int q = 0; q < numMPMs; q++ )
+          {
+            if( listPointerCopy->at( k ) == uiPreds[q] )
+            {
+              listPointer->push_back( listPointerCopy->at( k ) );
+              break;
+            }
+          }
+        }
+      }
+      cu.ispMode = NOT_INTRA_SUBPARTITIONS;
+    }
+#endif
+
 
     CHECK( numModesForFullRD != uiRdModeList.size(), "Inconsistent state!" );
 
@@ -635,10 +772,24 @@ void IntraSearch::estIntraPredLumaQT( CodingUnit &cu, Partitioner &partitioner )
       if( CandHadList.size() < 3 || CandHadList[2] > cs.interHad * PBINTRA_RATIO )
       {
         uiRdModeList.resize( std::min<size_t>( uiRdModeList.size(), 2 ) );
+#if JVET_M0102_INTRA_SUBPARTITIONS
+        if( nOptionsForISP > 1 )
+        {
+          m_rdModeListWithoutMrlHor.resize( std::min<size_t>( m_rdModeListWithoutMrlHor.size(), 2 ) );
+          m_rdModeListWithoutMrlVer.resize( std::min<size_t>( m_rdModeListWithoutMrlVer.size(), 2 ) );
+        }
+#endif
       }
       if( CandHadList.size() < 2 || CandHadList[1] > cs.interHad * PBINTRA_RATIO )
       {
         uiRdModeList.resize( std::min<size_t>( uiRdModeList.size(), 1 ) );
+#if JVET_M0102_INTRA_SUBPARTITIONS
+        if( nOptionsForISP > 1 )
+        {
+          m_rdModeListWithoutMrlHor.resize( std::min<size_t>( m_rdModeListWithoutMrlHor.size(), 1 ) );
+          m_rdModeListWithoutMrlVer.resize( std::min<size_t>( m_rdModeListWithoutMrlVer.size(), 1 ) );
+        }
+#endif
       }
       if( CandHadList.size() < 1 || CandHadList[0] > cs.interHad * PBINTRA_RATIO )
       {
@@ -668,6 +819,60 @@ void IntraSearch::estIntraPredLumaQT( CodingUnit &cu, Partitioner &partitioner )
 
     // just to be sure
     numModesForFullRD = ( int ) uiRdModeList.size();
+#if JVET_M0102_INTRA_SUBPARTITIONS
+    PartSplit intraSubPartitionsProcOrder = TU_NO_ISP;
+    int       bestNormalIntraModeIndex    = -1;
+    uint8_t   bestIspOption               = NOT_INTRA_SUBPARTITIONS;
+    TUIntraSubPartitioner subTuPartitioner( partitioner );
+#if !JVET_M0464_UNI_MTS
+    if ( !cu.ispMode && !cu.emtFlag )
+    {
+      m_modeCtrl->setEmtFirstPassNoIspCost( MAX_DOUBLE );
+    }
+#endif
+    for( uint32_t ispOptionIdx = 0; ispOptionIdx < nOptionsForISP; ispOptionIdx++ )
+    {
+      cu.ispMode = ispOptions[ispOptionIdx];
+      int numModesForFullRDispOption = cu.ispMode == NOT_INTRA_SUBPARTITIONS ? numModesForFullRD : cu.ispMode == HOR_INTRA_SUBPARTITIONS ? (int)m_rdModeListWithoutMrlHor.size() : (int)m_rdModeListWithoutMrlVer.size();
+      for( uint32_t uiMode = 0; uiMode < numModesForFullRDispOption; uiMode++ )
+      {
+        // set luma prediction mode
+        uint32_t uiOrgMode = cu.ispMode == NOT_INTRA_SUBPARTITIONS ? uiRdModeList[uiMode] : cu.ispMode == HOR_INTRA_SUBPARTITIONS ? m_rdModeListWithoutMrlHor[uiMode] : m_rdModeListWithoutMrlVer[uiMode];
+
+        pu.intraDir[0] = uiOrgMode;
+
+        int multiRefIdx = 0;
+        pu.multiRefIdx = multiRefIdx;
+        if( cu.ispMode )
+        {
+          intraSubPartitionsProcOrder = CU::getISPType( cu, COMPONENT_Y );
+          bool tuIsDividedInRows = CU::divideTuInRows( cu );
+          if( m_intraModeDiagRatio.at( bestNormalIntraModeIndex ) > 1.25 )
+          {
+            continue;
+          }
+          if( uiOrgMode <= DC_IDX )
+          {
+            if( ( m_intraModeHorVerRatio.at( bestNormalIntraModeIndex ) > 1.25 && tuIsDividedInRows ) || ( m_intraModeHorVerRatio.at( bestNormalIntraModeIndex ) < 0.8 && !tuIsDividedInRows ) )
+            {
+              continue;
+            }
+          }
+          else
+          {
+            if( ( m_intraModeHorVerRatio.at( bestNormalIntraModeIndex ) > 1.25 && tuIsDividedInRows ) || ( m_intraModeHorVerRatio.at( bestNormalIntraModeIndex ) < 0.8 && !tuIsDividedInRows ) )
+            {
+              continue;
+            }
+          }
+        }
+        else
+        {
+          multiRefIdx = extendRefList[uiMode];
+          pu.multiRefIdx = multiRefIdx;
+          CHECK( pu.multiRefIdx && ( pu.intraDir[0] == DC_IDX || pu.intraDir[0] == PLANAR_IDX ), "ERL" );
+        }
+#else
     for (uint32_t uiMode = 0; uiMode < numModesForFullRD; uiMode++)
     {
       // set luma prediction mode
@@ -677,6 +882,7 @@ void IntraSearch::estIntraPredLumaQT( CodingUnit &cu, Partitioner &partitioner )
       int multiRefIdx = extendRefList[uiMode];
       pu.multiRefIdx  = multiRefIdx;
       CHECK(pu.multiRefIdx && (pu.intraDir[0] == DC_IDX || pu.intraDir[0] == PLANAR_IDX), "ERL");
+#endif
 
 
       // set context models
@@ -685,10 +891,31 @@ void IntraSearch::estIntraPredLumaQT( CodingUnit &cu, Partitioner &partitioner )
       // determine residual for partition
       cs.initSubStructure( *csTemp, partitioner.chType, cs.area, true );
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+      if( cu.ispMode )
+      {
+        xRecurIntraCodingLumaQT( *csTemp, subTuPartitioner, bestCurrentCost, 0, intraSubPartitionsProcOrder );
+      }
+      else
+      {
+        xRecurIntraCodingLumaQT( *csTemp, partitioner, MAX_DOUBLE, -1 );
+      }
+
+      if( cu.ispMode && !csTemp->cus[0]->firstTU->cbf[COMPONENT_Y] )
+      {
+        csTemp->cost = MAX_DOUBLE;
+      }
+#else
       xRecurIntraCodingLumaQT( *csTemp, partitioner );
+#endif
+
 
 #if !JVET_M0464_UNI_MTS
+#if JVET_M0102_INTRA_SUBPARTITIONS
+      if (emtUsageFlag == 1 && m_pcEncCfg->getFastIntraEMT() && !cu.ispMode)
+#else
       if( emtUsageFlag == 1 && m_pcEncCfg->getFastIntraEMT() )
+#endif
       {
         m_modeCostStore[puIndex][uiMode] = csTemp->cost; //cs.cost;
       }
@@ -703,16 +930,43 @@ void IntraSearch::estIntraPredLumaQT( CodingUnit &cu, Partitioner &partitioner )
 
         uiBestPUMode  = uiOrgMode;
         bestExtendRef = multiRefIdx;
+#if JVET_M0102_INTRA_SUBPARTITIONS
+        bestIspOption = cu.ispMode;
+#endif
 #if !JVET_M0464_UNI_MTS
+#if JVET_M0102_INTRA_SUBPARTITIONS
+        if (emtUsageFlag == 1 && m_pcEncCfg->getFastIntraEMT() && !cu.ispMode)
+#else
         if( ( emtUsageFlag == 1 ) && m_pcEncCfg->getFastIntraEMT() )
+#endif
         {
           m_bestModeCostStore[puIndex] = csBest->cost; //cs.cost;
         }
+#endif
+#if JVET_M0102_INTRA_SUBPARTITIONS
+        if( csBest->cost < bestCurrentCost )
+        {
+          bestCurrentCost = csBest->cost;
+        }
+        if( !cu.ispMode )
+        {
+          bestNormalIntraModeIndex = uiMode;
+        }
 #endif
       }
 
       csTemp->releaseIntermediateData();
     } // Mode loop
+#if JVET_M0102_INTRA_SUBPARTITIONS
+#if !JVET_M0464_UNI_MTS
+    if (!cu.ispMode && !cu.emtFlag)
+    {
+      m_modeCtrl->setEmtFirstPassNoIspCost(csBest->cost);
+    }
+#endif
+    }
+    cu.ispMode = bestIspOption;
+#endif
 
 #if JVET_M0427_INLOOP_RESHAPER
     cs.useSubStructure(*csBest, partitioner.chType, pu.singleChan(CHANNEL_TYPE_LUMA), true, true, keepResi, keepResi);
@@ -729,7 +983,11 @@ void IntraSearch::estIntraPredLumaQT( CodingUnit &cu, Partitioner &partitioner )
   m_CABACEstimator->getCtx() = ctxStart;
 }
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+void IntraSearch::estIntraPredChromaQT( CodingUnit &cu, Partitioner &partitioner, const double maxCostAllowed )
+#else
 void IntraSearch::estIntraPredChromaQT(CodingUnit &cu, Partitioner &partitioner)
+#endif
 {
   const ChromaFormat format   = cu.chromaFormat;
   const uint32_t    numberValidComponents = getNumberValidComponents(format);
@@ -738,6 +996,13 @@ void IntraSearch::estIntraPredChromaQT(CodingUnit &cu, Partitioner &partitioner)
 
   cs.setDecomp( cs.area.Cb(), false );
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  double    bestCostSoFar = maxCostAllowed;
+  bool      lumaUsesISP   = !CS::isDualITree( *cu.cs ) && cu.ispMode;
+  PartSplit ispType       = lumaUsesISP ? CU::getISPType( cu, COMPONENT_Y ) : TU_NO_ISP;
+  CHECK( cu.ispMode && bestCostSoFar < 0, "bestCostSoFar must be positive!" );
+#endif
+
   auto &pu = *cu.firstPU;
 
   {
@@ -761,6 +1026,14 @@ void IntraSearch::estIntraPredChromaQT(CodingUnit &cu, Partitioner &partitioner)
       saveCS.area.repositionTo( cs.area );
       saveCS.clearTUs();
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+      if( !CS::isDualITree( cs ) && cu.ispMode )
+      {
+        saveCS.clearCUs();
+        saveCS.clearPUs();
+      }
+#endif
+
       if( CS::isDualITree( cs ) )
       {
         if( partitioner.canSplit( TU_MAX_TR_SPLIT, cs ) )
@@ -780,17 +1053,37 @@ void IntraSearch::estIntraPredChromaQT(CodingUnit &cu, Partitioner &partitioner)
 
       std::vector<TransformUnit*> orgTUs;
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+      if( lumaUsesISP )
+      {
+        CodingUnit& auxCU = saveCS.addCU( cu, partitioner.chType );
+        auxCU.ispMode = cu.ispMode;
+        saveCS.sps = cu.cs->sps;
+        saveCS.addPU( *cu.firstPU, partitioner.chType );
+      }
+#endif
+
 
       // create a store for the TUs
       for( const auto &ptu : cs.tus )
       {
         // for split TUs in HEVC, add the TUs without Chroma parts for correct setting of Cbfs
+#if JVET_M0102_INTRA_SUBPARTITIONS
+        if( lumaUsesISP || pu.contains( *ptu, CHANNEL_TYPE_CHROMA ) )
+#else
         if( pu.contains( *ptu, CHANNEL_TYPE_CHROMA ) )
+#endif
         {
           saveCS.addTU( *ptu, partitioner.chType );
           orgTUs.push_back( ptu );
         }
       }
+#if JVET_M0102_INTRA_SUBPARTITIONS
+      if( lumaUsesISP )
+      {
+        saveCS.clearCUs();
+      }
+#endif
       // SATD pre-selecting.
       int satdModeList[NUM_CHROMA_MODE];
       int64_t satdSortedCost[NUM_CHROMA_MODE];
@@ -914,20 +1207,38 @@ void IntraSearch::estIntraPredChromaQT(CodingUnit &cu, Partitioner &partitioner)
         //----- chroma coding -----
         pu.intraDir[1] = chromaIntraMode;
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+        xRecurIntraChromaCodingQT( cs, partitioner, bestCostSoFar, ispType );
+        if( lumaUsesISP && cs.dist == MAX_UINT )
+        {
+          continue;
+        }
+#else
         xRecurIntraChromaCodingQT( cs, partitioner );
+#endif
 
         if (cs.pps->getUseTransformSkip())
         {
           m_CABACEstimator->getCtx() = ctxStart;
         }
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+        uint64_t fracBits   = xGetIntraFracBitsQT( cs, partitioner, false, true, -1, ispType );
+#else
         uint64_t fracBits   = xGetIntraFracBitsQT( cs, partitioner, false, true );
+#endif
         Distortion uiDist = cs.dist;
         double    dCost   = m_pcRdCost->calcRdCost( fracBits, uiDist - baseDist );
 
         //----- compare -----
         if( dCost < dBestCost )
         {
+#if JVET_M0102_INTRA_SUBPARTITIONS
+          if( lumaUsesISP && dCost < bestCostSoFar )
+          {
+            bestCostSoFar = dCost;
+          }
+#endif
           for( uint32_t i = getFirstComponentOfChannel( CHANNEL_TYPE_CHROMA ); i < numberValidComponents; i++ )
           {
             const CompArea &area = pu.blocks[i];
@@ -984,6 +1295,12 @@ void IntraSearch::estIntraPredChromaQT(CodingUnit &cu, Partitioner &partitioner)
 
   //----- restore context models -----
   m_CABACEstimator->getCtx() = ctxStart;
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  if( lumaUsesISP && bestCostSoFar >= maxCostAllowed )
+  {
+    cu.ispMode = 0;
+  }
+#endif
 }
 
 void IntraSearch::IPCMSearch(CodingStructure &cs, Partitioner& partitioner)
@@ -1042,13 +1359,21 @@ void IntraSearch::xEncPCM(CodingStructure &cs, Partitioner& partitioner, const C
 // Intra search
 // -------------------------------------------------------------------------------------------------------------------
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+void IntraSearch::xEncIntraHeader( CodingStructure &cs, Partitioner &partitioner, const bool &bLuma, const bool &bChroma, const int subTuIdx )
+#else
 void IntraSearch::xEncIntraHeader(CodingStructure &cs, Partitioner &partitioner, const bool &bLuma, const bool &bChroma)
+#endif
 {
   CodingUnit &cu = *cs.getCU( partitioner.chType );
 
   if (bLuma)
   {
+#if JVET_M0102_INTRA_SUBPARTITIONS
+    bool isFirst = cu.ispMode ? subTuIdx == 0 : partitioner.currArea().lumaPos() == cs.area.lumaPos();
+#else
     bool isFirst = partitioner.currArea().lumaPos() == cs.area.lumaPos();
+#endif
 
     // CU header
     if( isFirst )
@@ -1065,6 +1390,9 @@ void IntraSearch::xEncIntraHeader(CodingStructure &cs, Partitioner &partitioner,
         m_CABACEstimator->pred_mode   ( cu );
       }
       m_CABACEstimator->extend_ref_line(cu);
+#if JVET_M0102_INTRA_SUBPARTITIONS
+      m_CABACEstimator->isp_mode      ( cu );
+#endif
       if( CU::isIntra(cu) )
       {
         m_CABACEstimator->pcm_data( cu, partitioner );
@@ -1099,16 +1427,29 @@ void IntraSearch::xEncIntraHeader(CodingStructure &cs, Partitioner &partitioner,
   }
 }
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+void IntraSearch::xEncSubdivCbfQT( CodingStructure &cs, Partitioner &partitioner, const bool &bLuma, const bool &bChroma, const int subTuIdx, const PartSplit ispType )
+{
+  const UnitArea &currArea = partitioner.currArea();
+          int subTuCounter = subTuIdx;
+  TransformUnit &currTU = *cs.getTU( currArea.blocks[partitioner.chType], partitioner.chType, subTuCounter );
+  CodingUnit    &currCU = *currTU.cu;
+#else
 void IntraSearch::xEncSubdivCbfQT(CodingStructure &cs, Partitioner &partitioner, const bool &bLuma, const bool &bChroma)
 {
   const UnitArea &currArea = partitioner.currArea();
   TransformUnit &currTU    = *cs.getTU( currArea.blocks[partitioner.chType], partitioner.chType );
 #if !JVET_M0464_UNI_MTS
   CodingUnit &currCU       = *currTU.cu;
+#endif
 #endif
   uint32_t currDepth           = partitioner.currTrDepth;
 
   const bool subdiv        = currTU.depth > currDepth;
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  ComponentID compID = partitioner.chType == CHANNEL_TYPE_LUMA ? COMPONENT_Y : COMPONENT_Cb;
+  const bool chromaCbfISP = currArea.blocks[COMPONENT_Cb].valid() && currCU.ispMode && !subdiv;
+#endif
 
   if( partitioner.canSplit( TU_MAX_TR_SPLIT, cs ) )
   {
@@ -1116,21 +1457,39 @@ void IntraSearch::xEncSubdivCbfQT(CodingStructure &cs, Partitioner &partitioner,
   }
   else
   {
+#if JVET_M0102_INTRA_SUBPARTITIONS
+    CHECK( subdiv && !currCU.ispMode && isLuma( compID ), "No TU subdivision is allowed with QTBT" );
+  }
+
+  if( bChroma && ( !currCU.ispMode || chromaCbfISP ) )
+#else
     CHECK( subdiv, "No TU subdivision is allowed with QTBT" );
   }
 
   if (bChroma)
+#endif
   {
     const uint32_t numberValidComponents = getNumberValidComponents(currArea.chromaFormat);
+#if JVET_M0102_INTRA_SUBPARTITIONS
+    const uint32_t cbfDepth = ( chromaCbfISP ? currDepth - 1 : currDepth );
+#endif
 
     for (uint32_t ch = COMPONENT_Cb; ch < numberValidComponents; ch++)
     {
       const ComponentID compID = ComponentID(ch);
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+      if( currDepth == 0 || TU::getCbfAtDepth( currTU, compID, currDepth - 1 ) || chromaCbfISP )
+#else
       if( currDepth == 0 || TU::getCbfAtDepth( currTU, compID, currDepth - 1 ) )
+#endif
       {
         const bool prevCbf = ( compID == COMPONENT_Cr ? TU::getCbfAtDepth( currTU, COMPONENT_Cb, currDepth ) : false );
+#if JVET_M0102_INTRA_SUBPARTITIONS
+        m_CABACEstimator->cbf_comp( cs, TU::getCbfAtDepth( currTU, compID, currDepth ), currArea.blocks[compID], cbfDepth, prevCbf );
+#else
         m_CABACEstimator->cbf_comp( cs, TU::getCbfAtDepth( currTU, compID, currDepth ), currArea.blocks[compID], currDepth, prevCbf );
+#endif
 
       }
     }
@@ -1139,19 +1498,34 @@ void IntraSearch::xEncSubdivCbfQT(CodingStructure &cs, Partitioner &partitioner,
   if (subdiv)
   {
 #if !JVET_M0464_UNI_MTS
+#if JVET_M0102_INTRA_SUBPARTITIONS
+    if (!currCU.ispMode && isLuma( compID ) && currDepth == 0 && bLuma) m_CABACEstimator->emt_cu_flag( currCU );
+#else
     if( currDepth == 0 && bLuma ) m_CABACEstimator->emt_cu_flag( currCU );
+#endif
 #endif
 
     if( partitioner.canSplit( TU_MAX_TR_SPLIT, cs ) )
     {
       partitioner.splitCurrArea( TU_MAX_TR_SPLIT, cs );
     }
+#if JVET_M0102_INTRA_SUBPARTITIONS
+    else if( currCU.ispMode && isLuma( compID ) )
+    {
+      partitioner.splitCurrArea( ispType, cs );
+    }
+#endif
     else
     THROW( "Cannot perform an implicit split!" );
 
     do
     {
+#if JVET_M0102_INTRA_SUBPARTITIONS
+      xEncSubdivCbfQT( cs, partitioner, bLuma, bChroma, subTuCounter, ispType );
+      subTuCounter += subTuCounter != -1 ? 1 : 0;
+#else
       xEncSubdivCbfQT( cs, partitioner, bLuma, bChroma );
+#endif
     } while( partitioner.nextPart( cs ) );
 
     partitioner.exitCurrSplit();
@@ -1159,20 +1533,65 @@ void IntraSearch::xEncSubdivCbfQT(CodingStructure &cs, Partitioner &partitioner,
   else
   {
 #if !JVET_M0464_UNI_MTS
+#if JVET_M0102_INTRA_SUBPARTITIONS 
+    if (!currCU.ispMode && isLuma( compID ) && currDepth == 0 && bLuma && TU::getCbfAtDepth( currTU, COMPONENT_Y, 0) ) m_CABACEstimator->emt_cu_flag( currCU );
+#else
     if( currDepth == 0 && bLuma && TU::getCbfAtDepth( currTU, COMPONENT_Y, 0 ) ) m_CABACEstimator->emt_cu_flag( currCU );
+#endif
 #endif
     //===== Cbfs =====
     if (bLuma)
     {
+#if JVET_M0102_INTRA_SUBPARTITIONS
+      bool previousCbf       = false;
+      bool lastCbfIsInferred = false;
+      if( ispType != TU_NO_ISP )
+      {
+        bool rootCbfSoFar = false;
+        uint32_t nTus = currCU.ispMode == HOR_INTRA_SUBPARTITIONS ? currCU.lheight() >> g_aucLog2[currTU.lheight()] : currCU.lwidth() >> g_aucLog2[currTU.lwidth()];
+        if( subTuCounter == nTus - 1 )
+        {
+          TransformUnit* tuPointer = currCU.firstTU;
+          for( int tuIdx = 0; tuIdx < nTus - 1; tuIdx++ )
+          {
+            rootCbfSoFar |= TU::getCbfAtDepth( *tuPointer, COMPONENT_Y, currDepth );
+            tuPointer = tuPointer->next;
+          }
+          if( !rootCbfSoFar )
+          {
+            lastCbfIsInferred = true;
+          }
+        }
+        if( !lastCbfIsInferred )
+        {
+          previousCbf = TU::getPrevTuCbfAtDepth( currTU, COMPONENT_Y, partitioner.currTrDepth );
+        }
+      }
+      if( !lastCbfIsInferred )
+      {
+        m_CABACEstimator->cbf_comp( cs, TU::getCbfAtDepth( currTU, COMPONENT_Y, currDepth ), currTU.Y(), currTU.depth, previousCbf, currCU.ispMode );
+      }
+#else
       m_CABACEstimator->cbf_comp( cs, TU::getCbfAtDepth( currTU, COMPONENT_Y, currDepth ), currTU.Y(), currTU.depth );
+#endif
     }
   }
 }
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+void IntraSearch::xEncCoeffQT( CodingStructure &cs, Partitioner &partitioner, const ComponentID compID, const int subTuIdx, const PartSplit ispType )
+#else
 void IntraSearch::xEncCoeffQT(CodingStructure &cs, Partitioner &partitioner, const ComponentID &compID)
+#endif
 {
   const UnitArea &currArea  = partitioner.currArea();
+
+#if JVET_M0102_INTRA_SUBPARTITIONS
+       int subTuCounter     = subTuIdx;
+  TransformUnit &currTU     = *cs.getTU( currArea.blocks[partitioner.chType], partitioner.chType, subTuIdx );
+#else
   TransformUnit &currTU     = *cs.getTU( currArea.blocks[partitioner.chType], partitioner.chType );
+#endif
   uint32_t      currDepth       = partitioner.currTrDepth;
   const bool subdiv         = currTU.depth > currDepth;
 
@@ -1182,12 +1601,23 @@ void IntraSearch::xEncCoeffQT(CodingStructure &cs, Partitioner &partitioner, con
     {
       partitioner.splitCurrArea(TU_MAX_TR_SPLIT, cs);
     }
+#if JVET_M0102_INTRA_SUBPARTITIONS
+    else if( currTU.cu->ispMode )
+    {
+      partitioner.splitCurrArea( ispType, cs );
+    }
+#endif
     else
       THROW("Implicit TU split not available!");
 
     do
     {
+#if JVET_M0102_INTRA_SUBPARTITIONS
+      xEncCoeffQT( cs, partitioner, compID, subTuCounter, ispType );
+      subTuCounter += subTuCounter != -1 ? 1 : 0;
+#else
       xEncCoeffQT( cs, partitioner, compID );
+#endif
     } while( partitioner.nextPart( cs ) );
 
     partitioner.exitCurrSplit();
@@ -1207,27 +1637,80 @@ void IntraSearch::xEncCoeffQT(CodingStructure &cs, Partitioner &partitioner, con
   }
 }
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+uint64_t IntraSearch::xGetIntraFracBitsQT( CodingStructure &cs, Partitioner &partitioner, const bool &bLuma, const bool &bChroma, const int subTuIdx, const PartSplit ispType )
+#else
 uint64_t IntraSearch::xGetIntraFracBitsQT( CodingStructure &cs, Partitioner &partitioner, const bool &bLuma, const bool &bChroma )
+#endif
 {
   m_CABACEstimator->resetBits();
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  xEncIntraHeader( cs, partitioner, bLuma, bChroma, subTuIdx );
+  xEncSubdivCbfQT( cs, partitioner, bLuma, bChroma, subTuIdx, ispType );
+#else
   xEncIntraHeader( cs, partitioner, bLuma, bChroma );
   xEncSubdivCbfQT( cs, partitioner, bLuma, bChroma );
+#endif
+
 
   if( bLuma )
   {
+#if JVET_M0102_INTRA_SUBPARTITIONS
+    xEncCoeffQT( cs, partitioner, COMPONENT_Y, subTuIdx, ispType );
+#else
     xEncCoeffQT( cs, partitioner, COMPONENT_Y );
+#endif
   }
   if( bChroma )
   {
+#if JVET_M0102_INTRA_SUBPARTITIONS
+    xEncCoeffQT( cs, partitioner, COMPONENT_Cb, subTuIdx, ispType );
+    xEncCoeffQT( cs, partitioner, COMPONENT_Cr, subTuIdx, ispType );
+#else
     xEncCoeffQT( cs, partitioner, COMPONENT_Cb );
     xEncCoeffQT( cs, partitioner, COMPONENT_Cr );
+#endif
   }
 
   uint64_t fracBits = m_CABACEstimator->getEstFracBits();
   return fracBits;
 }
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+uint64_t IntraSearch::xGetIntraFracBitsQTSingleChromaComponent( CodingStructure &cs, Partitioner &partitioner, const ComponentID compID )
+{
+  m_CABACEstimator->resetBits();
+
+  if( compID == COMPONENT_Cb )
+  {
+    //intra mode coding 
+    PredictionUnit &pu = *cs.getPU( partitioner.currArea().lumaPos(), partitioner.chType );
+    m_CABACEstimator->intra_chroma_pred_mode( pu );
+    //xEncIntraHeader(cs, partitioner, false, true);
+  }
+  CHECK( partitioner.currTrDepth != 1, "error in the depth!" );
+  const UnitArea &currArea = partitioner.currArea();
+
+  TransformUnit &currTU = *cs.getTU( currArea.blocks[partitioner.chType], partitioner.chType );
+
+  //cbf coding
+  m_CABACEstimator->cbf_comp( cs, TU::getCbfAtDepth( currTU, compID, partitioner.currTrDepth ), currArea.blocks[compID], partitioner.currTrDepth - 1 );
+  //coeffs coding and cross comp coding
+  if( TU::hasCrossCompPredInfo( currTU, compID ) )
+  {
+    m_CABACEstimator->cross_comp_pred( currTU, compID );
+  }
+  if( TU::getCbf( currTU, compID ) )
+  {
+    m_CABACEstimator->residual_coding( currTU, compID );
+  }
+
+  uint64_t fracBits = m_CABACEstimator->getEstFracBits();
+  return fracBits;
+}
+#endif
+
 uint64_t IntraSearch::xGetIntraFracBitsQTChroma(TransformUnit& currTU, const ComponentID &compID)
 {
   m_CABACEstimator->resetBits();
@@ -1276,6 +1759,9 @@ void IntraSearch::xIntraCodingTUBlock(TransformUnit &tu, const ComponentID &comp
 
   const bool           bUseCrossCPrediction = pps.getPpsRangeExtension().getCrossComponentPredictionEnabledFlag() && isChroma( compID ) && PU::isChromaIntraModeCrossCheckMode( pu ) && checkCrossCPrediction;
   const bool           ccUseRecoResi        = m_pcEncCfg->getUseReconBasedCrossCPredictionEstimate();
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  const bool           ispSplitIsAllowed    = CU::canUseISPSplit( *tu.cu, compID );
+#endif
 
 
   //===== init availability pattern =====
@@ -1380,16 +1866,46 @@ void IntraSearch::xIntraCodingTUBlock(TransformUnit &tu, const ComponentID &comp
   }
 #endif 
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  double diagRatio = 0, horVerRatio = 0;
+#endif
+
 #if JVET_M0464_UNI_MTS
   if( trModes )
   {
+#if JVET_M0102_INTRA_SUBPARTITIONS
+    m_pcTrQuant->transformNxN( tu, compID, cQP, trModes, CU::isIntra( *tu.cu ) ? m_pcEncCfg->getIntraMTSMaxCand() : m_pcEncCfg->getInterMTSMaxCand(), ispSplitIsAllowed ? &diagRatio : nullptr, ispSplitIsAllowed ? &horVerRatio : nullptr );
+#else
     m_pcTrQuant->transformNxN( tu, compID, cQP, trModes, CU::isIntra( *tu.cu ) ? m_pcEncCfg->getIntraMTSMaxCand() : m_pcEncCfg->getInterMTSMaxCand() );
+#endif
     tu.mtsIdx = trModes->at(0).first;
   }
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  m_pcTrQuant->transformNxN( tu, compID, cQP, uiAbsSum, m_CABACEstimator->getCtx(), loadTr, &diagRatio, &horVerRatio );
+#else
   m_pcTrQuant->transformNxN(tu, compID, cQP, uiAbsSum, m_CABACEstimator->getCtx(), loadTr);
+#endif
+#else
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  m_pcTrQuant->transformNxN(tu, compID, cQP, uiAbsSum, m_CABACEstimator->getCtx(), ispSplitIsAllowed ? &diagRatio : nullptr, ispSplitIsAllowed ? &horVerRatio : nullptr);
 #else
   m_pcTrQuant->transformNxN(tu, compID, cQP, uiAbsSum, m_CABACEstimator->getCtx());
 #endif
+#endif
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  if (!tu.cu->ispMode && isLuma(compID) && ispSplitIsAllowed &&
+#if JVET_M0464_UNI_MTS
+    tu.mtsIdx == 0
+#else
+    !tu.cu->emtFlag
+#endif
+    )
+  {
+    m_intraModeDiagRatio        .push_back(diagRatio);
+    m_intraModeHorVerRatio      .push_back(horVerRatio);
+    m_intraModeTestedNormalIntra.push_back((int)uiChFinalMode);
+  }
+#endif
 
 
   DTRACE( g_trace_ctx, D_TU_ABS_SUM, "%d: comp=%d, abssum=%d\n", DTRACE_GET_COUNTER( g_trace_ctx, D_TU_ABS_SUM ), compID, uiAbsSum );
@@ -1459,11 +1975,20 @@ void IntraSearch::xIntraCodingTUBlock(TransformUnit &tu, const ComponentID &comp
   }
 }
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+void IntraSearch::xRecurIntraCodingLumaQT( CodingStructure &cs, Partitioner &partitioner, const double bestCostSoFar, const int subTuIdx, const PartSplit ispType )
+{
+        int   subTuCounter = subTuIdx;
+  const UnitArea &currArea = partitioner.currArea();
+  const CodingUnit     &cu = *cs.getCU( currArea.lumaPos(), partitioner.chType );
+        bool  earlySkipISP = false;
+#else
 void IntraSearch::xRecurIntraCodingLumaQT( CodingStructure &cs, Partitioner &partitioner )
 {
   const UnitArea &currArea = partitioner.currArea();
 #if !JVET_M0464_UNI_MTS
   const CodingUnit &cu     = *cs.getCU(currArea.lumaPos(), partitioner.chType);
+#endif
 #endif
   uint32_t currDepth       = partitioner.currTrDepth;
   const PPS &pps           = *cs.pps;
@@ -1473,6 +1998,13 @@ void IntraSearch::xRecurIntraCodingLumaQT( CodingStructure &cs, Partitioner &par
   bCheckFull               = !partitioner.canSplit( TU_MAX_TR_SPLIT, cs );
   bCheckSplit              = partitioner.canSplit( TU_MAX_TR_SPLIT, cs );
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  if( cu.ispMode )
+  {
+    bCheckSplit = partitioner.canSplit( ispType, cs );
+    bCheckFull = !bCheckSplit;
+  }
+#endif
   uint32_t    numSig           = 0;
 
 #if JVET_M0464_UNI_MTS
@@ -1539,6 +2071,9 @@ void IntraSearch::xRecurIntraCodingLumaQT( CodingStructure &cs, Partitioner &par
     checkTransformSkip &= TU::hasTransformSkipFlag( *tu.cs, tu.Y() );
     checkTransformSkip &= !cu.transQuantBypass;
     checkTransformSkip &= !cu.emtFlag;
+#if JVET_M0102_INTRA_SUBPARTITIONS
+    checkTransformSkip &= !cu.ispMode;
+#endif
 
     CHECK( !tu.Y().valid(), "Invalid TU" );
 
@@ -1639,6 +2174,12 @@ void IntraSearch::xRecurIntraCodingLumaQT( CodingStructure &cs, Partitioner &par
       {
         default0Save1Load2 = 2;
       }
+#if JVET_M0102_INTRA_SUBPARTITIONS
+      if( cu.ispMode )
+      {
+        default0Save1Load2 = 0;
+      }
+#endif
 #if JVET_M0464_UNI_MTS
       if( nNumTransformCands > 1 )
       {
@@ -1688,7 +2229,18 @@ void IntraSearch::xRecurIntraCodingLumaQT( CodingStructure &cs, Partitioner &par
       }
       else
       {
+#if JVET_M0102_INTRA_SUBPARTITIONS
+        if( cu.ispMode && m_pcRdCost->calcRdCost( csFull->fracBits, csFull->dist + singleDistTmpLuma ) > bestCostSoFar )
+        {
+          earlySkipISP = true;
+        }
+        else
+        {
+          singleTmpFracBits = xGetIntraFracBitsQT( *csFull, partitioner, true, false, subTuCounter, ispType );
+        }
+#else
         singleTmpFracBits = xGetIntraFracBitsQT( *csFull, partitioner, true, false );
+#endif
         singleCostTmp     = m_pcRdCost->calcRdCost( singleTmpFracBits, singleDistTmpLuma );
       }
 
@@ -1776,13 +2328,62 @@ void IntraSearch::xRecurIntraCodingLumaQT( CodingStructure &cs, Partitioner &par
       partitioner.splitCurrArea( TU_MAX_TR_SPLIT, cs );
     }
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+    if( cu.ispMode )
+    {
+      partitioner.splitCurrArea( ispType, *csSplit );
+    }
+#endif
     do
     {
+#if JVET_M0102_INTRA_SUBPARTITIONS
+      xRecurIntraCodingLumaQT( *csSplit, partitioner, bestCostSoFar, subTuCounter, ispType );
+      subTuCounter += subTuCounter != -1 ? 1 : 0;
+#else
       xRecurIntraCodingLumaQT( *csSplit, partitioner );
+#endif
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+      if( !cu.ispMode )
+      {
+        csSplit->setDecomp( partitioner.currArea().Y() );
+      }
+      else if( CU::isISPFirst( cu, partitioner.currArea().Y(), COMPONENT_Y ) )
+      {
+        csSplit->setDecomp( cu.Y() );
+      }
+#else
       csSplit->setDecomp( partitioner.currArea().Y() );
+#endif
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+      uiSplitCbfLuma |= TU::getCbfAtDepth( *csSplit->getTU( partitioner.currArea().lumaPos(), partitioner.chType, subTuCounter - 1 ), COMPONENT_Y, partitioner.currTrDepth );
+      if( cu.ispMode )
+      {
+        //exit condition if the accumulated cost is already larger than the best cost so far (no impact in RD performance)
+        if( csSplit->cost > bestCostSoFar )
+        {
+          earlySkipISP    = true;
+          splitIsSelected = false;
+          break;
+        }
+        else
+        {
+          //more restrictive exit condition
+          bool tuIsDividedInRows = CU::divideTuInRows( cu );
+          int nSubPartitions = tuIsDividedInRows ? cu.lheight() >> g_aucLog2[cu.firstTU->lheight()] : cu.lwidth() >> g_aucLog2[cu.firstTU->lwidth()];
+          double threshold = nSubPartitions == 2 ? 0.95 : subTuCounter == 0 ? 0.67 : subTuCounter == 1 ? 0.83 : 0.91;
+          if( subTuCounter < nSubPartitions && csSplit->cost > bestCostSoFar*threshold )
+          {
+            earlySkipISP    = true;
+            splitIsSelected = false;
+            break;
+          }
+        }
+      }
+#else
       uiSplitCbfLuma |= TU::getCbfAtDepth( *csSplit->getTU( partitioner.currArea().lumaPos(), partitioner.chType ), COMPONENT_Y, partitioner.currTrDepth );
+#endif
 
 
 
@@ -1804,7 +2405,11 @@ void IntraSearch::xRecurIntraCodingLumaQT( CodingStructure &cs, Partitioner &par
       m_CABACEstimator->getCtx() = ctxStart;
 
       //----- determine rate and r-d cost -----
+#if JVET_M0102_INTRA_SUBPARTITIONS
+      csSplit->fracBits = xGetIntraFracBitsQT( *csSplit, partitioner, true, false, cu.ispMode ? 0 : -1, ispType );
+#else
       csSplit->fracBits = xGetIntraFracBitsQT(*csSplit, partitioner, true, false);
+#endif
 
       //--- update cost ---
       csSplit->cost     = m_pcRdCost->calcRdCost(csSplit->fracBits, csSplit->dist);
@@ -1821,11 +2426,26 @@ void IntraSearch::xRecurIntraCodingLumaQT( CodingStructure &cs, Partitioner &par
 #endif
     }
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+    if( cu.ispMode && earlySkipISP )
+    {
+      cs.cost = MAX_DOUBLE;
+    }
+    else
+    {
+      cs.cost = m_pcRdCost->calcRdCost( cs.fracBits, cs.dist );
+    }
+#else
     cs.cost = m_pcRdCost->calcRdCost( cs.fracBits, cs.dist );
+#endif
   }
 }
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+ChromaCbfs IntraSearch::xRecurIntraChromaCodingQT( CodingStructure &cs, Partitioner& partitioner, const double bestCostSoFar, const PartSplit ispType )
+#else
 ChromaCbfs IntraSearch::xRecurIntraChromaCodingQT(CodingStructure &cs, Partitioner& partitioner)
+#endif
 {
   UnitArea currArea                   = partitioner.currArea();
   const bool keepResi                 = cs.sps->getSpsNext().getUseLMChroma() || KEEP_PRED_AND_RESI_SIGNALS;
@@ -1835,9 +2455,16 @@ ChromaCbfs IntraSearch::xRecurIntraChromaCodingQT(CodingStructure &cs, Partition
   TransformUnit &currTU               = *cs.getTU( currArea.chromaPos(), CHANNEL_TYPE_CHROMA );
   const PredictionUnit &pu            = *cs.getPU( currArea.chromaPos(), CHANNEL_TYPE_CHROMA );
 #if !JVET_M0464_UNI_MTS
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  const TransformUnit &currTULuma     = CS::isDualITree( cs ) ? *cs.picture->cs->getTU(currArea.lumaPos(), CHANNEL_TYPE_LUMA, 0 ) : currTU;
+#else
   const TransformUnit &currTULuma     = CS::isDualITree( cs ) ? *cs.picture->cs->getTU( currArea.lumaPos(), CHANNEL_TYPE_LUMA ) : currTU;
 #endif
+#endif
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  bool lumaUsesISP                    = !CS::isDualITree( cs ) && currTU.cu->ispMode;
+#endif
   uint32_t     currDepth                  = partitioner.currTrDepth;
   const PPS &pps                      = *cs.pps;
   ChromaCbfs cbfs                     ( false );
@@ -1879,6 +2506,18 @@ ChromaCbfs IntraSearch::xRecurIntraChromaCodingQT(CodingStructure &cs, Partition
     saveCS.area.repositionTo( cs.area );
     saveCS.initStructData( MAX_INT, false, true );
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+    if( !CS::isDualITree( cs ) && currTU.cu->ispMode )
+    {
+      saveCS.clearCUs();
+      CodingUnit& auxCU = saveCS.addCU( *currTU.cu, partitioner.chType );
+      auxCU.ispMode = currTU.cu->ispMode;
+      saveCS.sps = currTU.cs->sps;
+      saveCS.clearPUs();
+      saveCS.addPU( *currTU.cu->firstPU, partitioner.chType );
+    }
+#endif
+
     TransformUnit &tmpTU = saveCS.addTU(currArea, partitioner.chType);
 
 
@@ -1969,6 +2608,17 @@ ChromaCbfs IntraSearch::xRecurIntraChromaCodingQT(CodingStructure &cs, Partition
           {
             singleCostTmp = MAX_DOUBLE;
           }
+#if JVET_M0102_INTRA_SUBPARTITIONS
+          else if( lumaUsesISP && bestCostSoFar != MAX_DOUBLE && c == COMPONENT_Cb )
+          {
+            uint64_t fracBitsTmp = xGetIntraFracBitsQTSingleChromaComponent( cs, partitioner, ComponentID( c ) );
+            singleCostTmp = m_pcRdCost->calcRdCost( fracBitsTmp, singleDistCTmp );
+            if( isOneMode || ( !isOneMode && !isLastMode ) )
+            {
+              m_CABACEstimator->getCtx() = ctxStart;
+            }
+          }
+#endif
           else if( !isOneMode )
           {
             uint64_t fracBitsTmp = xGetIntraFracBitsQTChroma( currTU, compID );
@@ -2004,6 +2654,17 @@ ChromaCbfs IntraSearch::xRecurIntraChromaCodingQT(CodingStructure &cs, Partition
         }
       }
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+      if( lumaUsesISP && dSingleCost > bestCostSoFar && c == COMPONENT_Cb )
+      {
+        //Luma + Cb cost is already larger than the best cost, so we don't need to test Cr
+        cs.dist = MAX_UINT;
+        m_CABACEstimator->getCtx() = ctxStart;
+        break;
+        //return cbfs;
+      }
+#endif
+
       if (bestModeId < totalModesToTest)
       {
 #if KEEP_PRED_AND_RESI_SIGNALS
@@ -2043,12 +2704,22 @@ ChromaCbfs IntraSearch::xRecurIntraChromaCodingQT(CodingStructure &cs, Partition
     {
       partitioner.splitCurrArea( TU_MAX_TR_SPLIT, cs );
     }
+#if JVET_M0102_INTRA_SUBPARTITIONS
+    else if( currTU.cu->ispMode )
+    {
+      partitioner.splitCurrArea( ispType, cs );
+    }
+#endif
     else
       THROW( "Implicit TU split not available" );
 
     do
     {
+#if JVET_M0102_INTRA_SUBPARTITIONS
+      ChromaCbfs subCbfs = xRecurIntraChromaCodingQT( cs, partitioner, bestCostSoFar, ispType );
+#else
       ChromaCbfs subCbfs = xRecurIntraChromaCodingQT( cs, partitioner );
+#endif
 
       for( uint32_t ch = COMPONENT_Cb; ch < numValidTBlocks; ch++ )
       {
@@ -2059,11 +2730,30 @@ ChromaCbfs IntraSearch::xRecurIntraChromaCodingQT(CodingStructure &cs, Partition
 
     partitioner.exitCurrSplit();
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+    if( lumaUsesISP && cs.dist == MAX_UINT )
+    {
+      return cbfs;
+    }
+#endif
     {
 
       cbfs.Cb |= SplitCbfs.Cb;
       cbfs.Cr |= SplitCbfs.Cr;
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+      if( !lumaUsesISP )
+      {
+        for( auto &ptu : cs.tus )
+        {
+          if( currArea.Cb().contains( ptu->Cb() ) || ( !ptu->Cb().valid() && currArea.Y().contains( ptu->Y() ) ) )
+          {
+            TU::setCbfAtDepth( *ptu, COMPONENT_Cb, currDepth, SplitCbfs.Cb );
+            TU::setCbfAtDepth( *ptu, COMPONENT_Cr, currDepth, SplitCbfs.Cr );
+          }
+        }
+      }
+#else
       for( auto &ptu : cs.tus )
       {
         if( currArea.Cb().contains( ptu->Cb() ) || ( !ptu->Cb().valid() && currArea.Y().contains( ptu->Y() ) ) )
@@ -2072,6 +2762,7 @@ ChromaCbfs IntraSearch::xRecurIntraChromaCodingQT(CodingStructure &cs, Partition
           TU::setCbfAtDepth( *ptu, COMPONENT_Cr, currDepth, SplitCbfs.Cr );
         }
       }
+#endif
     }
   }
 
diff --git a/source/Lib/EncoderLib/IntraSearch.h b/source/Lib/EncoderLib/IntraSearch.h
index e6d1cc7362db97f2044759f89d5eb15a489d21b9..9f4a99ac2a08fdfd6e02e63dbbbaa162658c7c1b 100644
--- a/source/Lib/EncoderLib/IntraSearch.h
+++ b/source/Lib/EncoderLib/IntraSearch.h
@@ -65,6 +65,9 @@ class EncModeCtrl;
 class IntraSearch : public IntraPrediction, CrossComponentPrediction
 {
 private:
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  EncModeCtrl    *m_modeCtrl; 
+#endif
   Pel*            m_pSharedPredTransformSkip[MAX_NUM_TBLOCKS];
 
   XUCache         m_unitCache;
@@ -84,6 +87,15 @@ private:
   uint32_t   m_savedRdModeList  [4][NUM_LUMA_MODE], m_savedNumRdModes[4];
   int        m_savedExtendRefList[4][NUM_LUMA_MODE];
 #endif
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  static_vector<uint32_t, FAST_UDI_MAX_RDMODE_NUM> m_rdModeListWithoutMrl;
+  static_vector<uint32_t, FAST_UDI_MAX_RDMODE_NUM> m_rdModeListWithoutMrlHor;
+  static_vector<uint32_t, FAST_UDI_MAX_RDMODE_NUM> m_rdModeListWithoutMrlVer;
+
+  static_vector<double, FAST_UDI_MAX_RDMODE_NUM> m_intraModeDiagRatio;
+  static_vector<double, FAST_UDI_MAX_RDMODE_NUM> m_intraModeHorVerRatio;
+  static_vector<int,    FAST_UDI_MAX_RDMODE_NUM> m_intraModeTestedNormalIntra;
+#endif
 #if JVET_M0427_INLOOP_RESHAPER
   PelStorage      m_tmpStorageLCU;
 #endif
@@ -128,10 +140,19 @@ public:
   CodingStructure****getFullCSBuf () { return m_pFullCS; }
   CodingStructure  **getSaveCSBuf () { return m_pSaveCS; }
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  void setModeCtrl                ( EncModeCtrl *modeCtrl ) { m_modeCtrl = modeCtrl; }
+#endif
+
 public:
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  void estIntraPredLumaQT         ( CodingUnit &cu, Partitioner& pm, const double bestCostSoFar  = MAX_DOUBLE );
+  void estIntraPredChromaQT       ( CodingUnit &cu, Partitioner& pm, const double maxCostAllowed = MAX_DOUBLE );
+#else
   void estIntraPredLumaQT         ( CodingUnit &cu, Partitioner& pm );
   void estIntraPredChromaQT       (CodingUnit &cu, Partitioner& pm);
+#endif
   void IPCMSearch                 (CodingStructure &cs, Partitioner& partitioner);
   uint64_t xFracModeBitsIntra     (PredictionUnit &pu, const uint32_t &uiMode, const ChannelType &compID);
 
@@ -147,12 +168,23 @@ protected:
   // Intra search
   // -------------------------------------------------------------------------------------------------------------------
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  void     xEncIntraHeader                         ( CodingStructure &cs, Partitioner& pm, const bool &luma, const bool &chroma, const int subTuIdx = -1 );
+  void     xEncSubdivCbfQT                         ( CodingStructure &cs, Partitioner& pm, const bool &luma, const bool &chroma, const int subTuIdx = -1, const PartSplit ispType = TU_NO_ISP );
+  uint64_t xGetIntraFracBitsQT                     ( CodingStructure &cs, Partitioner& pm, const bool &luma, const bool &chroma, const int subTuIdx = -1, const PartSplit ispType = TU_NO_ISP );
+  uint64_t xGetIntraFracBitsQTSingleChromaComponent( CodingStructure &cs, Partitioner& pm, const ComponentID compID );
+#else
   void xEncIntraHeader            (CodingStructure &cs, Partitioner& pm, const bool &bLuma, const bool &bChroma);
   void xEncSubdivCbfQT            (CodingStructure &cs, Partitioner& pm, const bool &bLuma, const bool &bChroma);
   uint64_t xGetIntraFracBitsQT      (CodingStructure &cs, Partitioner& pm, const bool &bLuma, const bool &bChroma);
+#endif
 
   uint64_t xGetIntraFracBitsQTChroma(TransformUnit& tu, const ComponentID &compID);
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  void xEncCoeffQT                                 ( CodingStructure &cs, Partitioner& pm, const ComponentID compID, const int subTuIdx = -1, const PartSplit ispType = TU_NO_ISP );
+#else
   void xEncCoeffQT                (CodingStructure &cs, Partitioner& pm, const ComponentID &compID);
+#endif
 
 
 #if JVET_M0464_UNI_MTS
@@ -161,9 +193,14 @@ protected:
   void xIntraCodingTUBlock        (TransformUnit &tu, const ComponentID &compID, const bool &checkCrossCPrediction, Distortion& ruiDist, const int &default0Save1Load2 = 0, uint32_t* numSig = nullptr );
 #endif
 
+#if JVET_M0102_INTRA_SUBPARTITIONS
+  ChromaCbfs xRecurIntraChromaCodingQT( CodingStructure &cs, Partitioner& pm, const double bestCostSoFar = MAX_DOUBLE,                          const PartSplit ispType = TU_NO_ISP );
+  void       xRecurIntraCodingLumaQT  ( CodingStructure &cs, Partitioner& pm, const double bestCostSoFar = MAX_DOUBLE, const int subTuIdx = -1, const PartSplit ispType = TU_NO_ISP );
+#else
   ChromaCbfs xRecurIntraChromaCodingQT  (CodingStructure &cs, Partitioner& pm);
 
   void xRecurIntraCodingLumaQT    ( CodingStructure &cs, Partitioner& pm );
+#endif
 
 
   void encPredIntraDPCM( const ComponentID &compID, PelBuf &pOrg, PelBuf &pDst, const uint32_t &uiDirMode );