From 16b61dc71dadbec294a995f31d92e89b04761ea5 Mon Sep 17 00:00:00 2001
From: Hang Huang <huanghang@oppo.com>
Date: Mon, 11 Nov 2024 06:23:33 +0000
Subject: [PATCH] JVET-AJ0081: Chroma TMRL (Test 2.18)

---
 source/Lib/CommonLib/CommonDef.h          |   6 +
 source/Lib/CommonLib/Contexts.h           |   3 +
 source/Lib/CommonLib/Contexts_ecm14.0.inl |  25 ++
 source/Lib/CommonLib/IntraPrediction.cpp  | 489 +++++++++++++++++++++-
 source/Lib/CommonLib/IntraPrediction.h    |   6 +
 source/Lib/CommonLib/TypeDef.h            |   1 +
 source/Lib/CommonLib/Unit.cpp             |  15 +
 source/Lib/CommonLib/Unit.h               |   5 +
 source/Lib/CommonLib/UnitTools.cpp        |  17 +
 source/Lib/CommonLib/UnitTools.h          |   3 +
 source/Lib/DecoderLib/CABACReader.cpp     |  25 ++
 source/Lib/DecoderLib/CABACReader.h       |   3 +
 source/Lib/DecoderLib/DecCu.cpp           |  37 +-
 source/Lib/EncoderLib/CABACWriter.cpp     |  24 ++
 source/Lib/EncoderLib/CABACWriter.h       |   3 +
 source/Lib/EncoderLib/EncCu.cpp           |  16 +
 source/Lib/EncoderLib/IntraSearch.cpp     | 272 ++++++++++++
 source/Lib/EncoderLib/IntraSearch.h       |   4 +
 18 files changed, 945 insertions(+), 9 deletions(-)

diff --git a/source/Lib/CommonLib/CommonDef.h b/source/Lib/CommonLib/CommonDef.h
index 207e85c6e..16f3f31c1 100644
--- a/source/Lib/CommonLib/CommonDef.h
+++ b/source/Lib/CommonLib/CommonDef.h
@@ -703,6 +703,12 @@ static const int MRL_IDX_RICE_CODE_DIVISOR =                        4;
 static const int TMRL_MPM_SIZE =                                   10;
 static const int TMRL_MODECOST =     NUM_LUMA_MODE * MAX_REF_LINE_IDX;
 static const int EXT_REF_LINE_IDX[] =              { 1, 3, 5, 7, 12 };
+#if JVET_AJ0081_CHROMA_TMRL
+static const int CHROMA_TMRL_LIST_SIZE = 6;
+static const int CHROMA_TMRL_MPM_SIZE = 8;
+static const int MAX_CHROMA_MRL_IDX = 3;
+static const int CHROMA_MULTI_REF_LINE_IDX[MAX_CHROMA_MRL_IDX] = { 1, 3, 5 };
+#endif
 #endif
 #if JVET_AG0058_EIP
 static const int EIP_IDX =                                        201;
diff --git a/source/Lib/CommonLib/Contexts.h b/source/Lib/CommonLib/Contexts.h
index 11d9324cc..277ce562f 100644
--- a/source/Lib/CommonLib/Contexts.h
+++ b/source/Lib/CommonLib/Contexts.h
@@ -795,6 +795,9 @@ public:
 #endif
 #if JVET_AB0157_TMRL
   static const CtxSet   TmrlDerive;
+#if JVET_AJ0081_CHROMA_TMRL
+  static const CtxSet   ChromaTmrlFlag;
+#endif
 #endif
 #if JVET_AE0059_INTER_CCCM
   static const CtxSet   InterCccmFlag;
diff --git a/source/Lib/CommonLib/Contexts_ecm14.0.inl b/source/Lib/CommonLib/Contexts_ecm14.0.inl
index 5b9830d97..ed52c9ee4 100644
--- a/source/Lib/CommonLib/Contexts_ecm14.0.inl
+++ b/source/Lib/CommonLib/Contexts_ecm14.0.inl
@@ -6519,5 +6519,30 @@ const CtxSet ContextSetCfg::SeparateTree = ContextSetCfg::addCtxSet
   { DWO, DWO, DWO },
   });
 #endif
+#if JVET_AJ0081_CHROMA_TMRL
+const CtxSet ContextSetCfg::ChromaTmrlFlag = ContextSetCfg::addCtxSet
+({
+  { CNU, },
+  { CNU, },
+  { CNU, },
+  { CNU, },
+  { DWS, },
+  { DWS, },
+  { DWS, },
+  { DWS, },
+  { DWE, },
+  { DWE, },
+  { DWE, },
+  { DWE, },
+  { DWO, },
+  { DWO, },
+  { DWO, },
+  { DWO, },
+  { DWO, },
+  { DWO, },
+  { DWO, },
+  { DWO, },
+  });
+#endif
 // CONTEXTS WSA STOP
 #endif
diff --git a/source/Lib/CommonLib/IntraPrediction.cpp b/source/Lib/CommonLib/IntraPrediction.cpp
index f800a2da5..979a708f4 100644
--- a/source/Lib/CommonLib/IntraPrediction.cpp
+++ b/source/Lib/CommonLib/IntraPrediction.cpp
@@ -3576,7 +3576,11 @@ void IntraPrediction::initPredIntraParams(const PredictionUnit & pu, const CompA
 #else
   m_ipaParam.isModeVer            = predMode >= DIA_IDX;
 #endif
-  m_ipaParam.multiRefIndex        = isLuma (chType) ? pu.multiRefIdx : 0 ;
+#if JVET_AJ0081_CHROMA_TMRL
+  m_ipaParam.multiRefIndex        = isLuma(chType) ? pu.multiRefIdx : pu.chromaMrlIdx;
+#else
+  m_ipaParam.multiRefIndex        = isLuma(chType) ? pu.multiRefIdx : 0;
+#endif
   m_ipaParam.refFilterFlag        = false;
   m_ipaParam.interpolationFlag    = false;
   m_ipaParam.applyPDPC            = (puSize.width >= MIN_TB_SIZEY && puSize.height >= MIN_TB_SIZEY) && m_ipaParam.multiRefIndex == 0;
@@ -5794,7 +5798,11 @@ void IntraPrediction::xFillReferenceSamples( const CPelBuf &recoBuf, Pel* refBuf
   const SPS             &sps    = *cs.sps;
   const PreCalcValues   &pcv    = *cs.pcv;
 
+#if JVET_AJ0081_CHROMA_TMRL
+  const int multiRefIdx         = (area.compID == COMPONENT_Y) ? cu.firstPU->multiRefIdx : cu.firstPU->chromaMrlIdx;
+#else
   const int multiRefIdx         = (area.compID == COMPONENT_Y) ? cu.firstPU->multiRefIdx : 0;
+#endif
 
   const int  tuWidth            = area.width;
   const int  tuHeight           = area.height;
@@ -6159,7 +6167,11 @@ void IntraPrediction::xFillReferenceSamplesForCoLuma(const CPelBuf &recoBuf, Pel
   const SPS             &sps = *cs.sps;
   const PreCalcValues   &pcv = *cs.pcv;
 
+#if JVET_AJ0081_CHROMA_TMRL
+  const int multiRefIdx = cu.firstPU->multiRefIdx;
+#else
   const int multiRefIdx = 0;
+#endif
 
   const int  tuWidth = area.width;
   const int  tuHeight = area.height;
@@ -6172,9 +6184,18 @@ void IntraPrediction::xFillReferenceSamplesForCoLuma(const CPelBuf &recoBuf, Pel
   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()));
 
+#if JVET_AJ0081_CHROMA_TMRL
+  int leftMrlUnitNum = multiRefIdx / unitHeight;
+  int aboveMrlUnitNum = multiRefIdx / unitWidth;
+#endif
+
   const int  totalAboveUnits = (predSize + (unitWidth - 1)) / unitWidth;
   const int  totalLeftUnits = (predHSize + (unitHeight - 1)) / unitHeight;
+#if JVET_AJ0081_CHROMA_TMRL
+  const int  totalUnits = totalAboveUnits + totalLeftUnits + 1 + leftMrlUnitNum + aboveMrlUnitNum; // for top-left and mrl
+#else
   const int  totalUnits = totalAboveUnits + totalLeftUnits + 1; //+1 for top-left
+#endif
 
   const int  numAboveUnits = std::max<int>(tuWidth / unitWidth, 1);
   const int  numLeftUnits = std::max<int>(tuHeight / unitHeight, 1);
@@ -6184,11 +6205,84 @@ void IntraPrediction::xFillReferenceSamplesForCoLuma(const CPelBuf &recoBuf, Pel
   CHECK(numAboveUnits <= 0 || numLeftUnits <= 0 || numAboveRightUnits <= 0 || numLeftBelowUnits <= 0, "Size not supported");
 
   // ----- Step 1: analyze neighborhood -----
+#if JVET_AJ0081_CHROMA_TMRL
+  bool  neighborFlags[4 * MAX_NUM_PART_IDXS_IN_CTU_WIDTH + MAX_REF_LINE_IDX + 1];
+#else
   bool  neighborFlags[4 * MAX_NUM_PART_IDXS_IN_CTU_WIDTH + 1];
+#endif
   int   numIntraNeighbor = 0;
 
   memset(neighborFlags, 0, totalUnits);
 
+#if JVET_AJ0081_CHROMA_TMRL
+  //top-left
+  neighborFlags[totalLeftUnits + leftMrlUnitNum] = (area.x - multiRefIdx) > 0 && (area.y - multiRefIdx) > 0;
+  numIntraNeighbor += neighborFlags[totalLeftUnits + leftMrlUnitNum] ? 1 : 0;
+  //mrl above
+  numIntraNeighbor += ((area.x - aboveMrlUnitNum * unitWidth) > 0 && (area.y - multiRefIdx) > 0) ? aboveMrlUnitNum : 0;
+  for (int i = totalLeftUnits + leftMrlUnitNum + 1; i < totalLeftUnits + leftMrlUnitNum + 1 + aboveMrlUnitNum; i++)
+  {
+    neighborFlags[i] = ((area.x - aboveMrlUnitNum * unitWidth) > 0 && (area.y - multiRefIdx) > 0) ? true : false;
+  }
+  //above
+  numIntraNeighbor += (area.y - multiRefIdx) > 0 ? numAboveUnits : 0;
+  for (int i = totalLeftUnits + leftMrlUnitNum + 1 + aboveMrlUnitNum; i < totalLeftUnits + leftMrlUnitNum + 1 + aboveMrlUnitNum + numAboveUnits; i++)
+  {
+    neighborFlags[i] = (area.y - multiRefIdx) > 0 ? true : false;
+  }
+  //above right
+  int picWidth = sps.getMaxPicWidthInLumaSamples();
+  int ctuWidth = sps.getCTUSize();
+  int ctuWidthInNum = area.x / ctuWidth;
+  int wUnit = std::min((picWidth - area.x - tuWidth) / unitWidth, ((ctuWidthInNum + 1) * ctuWidth - area.x - tuWidth) / unitWidth);
+#if JVET_AI0136_ADAPTIVE_DUAL_TREE // to fix chroma ipm reorderding
+  if (cu.slice->isIntra())
+#endif
+  numIntraNeighbor += (area.y - multiRefIdx) > 0 ? std::min(numAboveRightUnits, wUnit) : 0;
+  for (int i = totalLeftUnits + leftMrlUnitNum + 1 + aboveMrlUnitNum + numAboveUnits; i < totalLeftUnits + leftMrlUnitNum + 1 + aboveMrlUnitNum + numAboveUnits + numAboveRightUnits; i++)
+  {
+    neighborFlags[i] = (area.y - multiRefIdx) > 0 ? (i - (totalLeftUnits + 1 + numAboveUnits) < wUnit ? true : false) : false;
+#if JVET_AI0136_ADAPTIVE_DUAL_TREE  // to fix chroma ipm reorderding
+    if (!cu.slice->isIntra())
+    {
+      neighborFlags[i] = false;
+    }
+#endif
+  }
+  //mrl left
+  numIntraNeighbor += ((area.x - multiRefIdx) > 0 && (area.y - leftMrlUnitNum * unitHeight) > 0) ? leftMrlUnitNum : 0;
+  for (int i = totalLeftUnits + leftMrlUnitNum - 1; i > totalLeftUnits - 1; i--)
+  {
+    neighborFlags[i] = ((area.x - multiRefIdx) > 0 && (area.y - leftMrlUnitNum * unitHeight) > 0) ? true : false;
+  }
+  //left
+  numIntraNeighbor += (area.x - multiRefIdx) > 0 ? numLeftUnits : 0;
+  for (int i = totalLeftUnits - 1; i > totalLeftUnits - 1 - numLeftUnits; i--)
+  {
+    neighborFlags[i] = (area.x - multiRefIdx) > 0 ? true : false;
+  }
+  //left below
+  int picHeight = sps.getMaxPicHeightInLumaSamples();
+  int ctuHeight = sps.getCTUSize();
+  int ctuHeightInNum = area.y / ctuHeight;
+  int hUnit = std::min((picHeight - area.y - tuHeight) / unitHeight, ((ctuHeightInNum + 1) * ctuHeight - area.y - tuHeight) / unitHeight);
+#if JVET_AI0136_ADAPTIVE_DUAL_TREE
+  if (cu.slice->isIntra())
+#endif
+  numIntraNeighbor += (area.x - multiRefIdx) > 0 ? std::min(numLeftBelowUnits, hUnit) : 0;
+  int y = 0;
+  for (int i = totalLeftUnits - 1 - numLeftUnits; i > totalLeftUnits - 1 - numLeftUnits - numLeftBelowUnits; i--)
+  {
+    neighborFlags[i] = (area.x - multiRefIdx) > 0 ? (y < hUnit ? true : false) : false;
+#if JVET_AI0136_ADAPTIVE_DUAL_TREE  // to fix chroma ipm reorderding
+    if (!cu.slice->isIntra())
+    {
+      neighborFlags[i] = false;
+    }
+#endif
+    y++;
+  }
+#else
   neighborFlags[totalLeftUnits] = area.x > 0 && area.y > 0;
   numIntraNeighbor += neighborFlags[totalLeftUnits] ? 1 : 0;
   numIntraNeighbor += area.y > 0 ? numAboveUnits : 0;
@@ -6203,10 +6297,10 @@ void IntraPrediction::xFillReferenceSamplesForCoLuma(const CPelBuf &recoBuf, Pel
 #if JVET_AI0136_ADAPTIVE_DUAL_TREE // to fix chroma ipm reorderding
   if (cu.slice->isIntra())
 #endif
-  numIntraNeighbor += area.y > 0 ? std::min(numAboveRightUnits, wUnit) : 0;
+    numIntraNeighbor += area.y > 0 ? std::min(numAboveRightUnits, wUnit) : 0;
   for (int i = totalLeftUnits + 1 + numAboveUnits; i < totalLeftUnits + 1 + numAboveUnits + numAboveRightUnits; i++)
   {
-    neighborFlags[i] = area.y > 0 ? ( i - (totalLeftUnits + 1 + numAboveUnits) < wUnit ? true : false) : false;
+    neighborFlags[i] = area.y > 0 ? (i - (totalLeftUnits + 1 + numAboveUnits) < wUnit ? true : false) : false;
 #if JVET_AI0136_ADAPTIVE_DUAL_TREE  // to fix chroma ipm reorderding
     if (!cu.slice->isIntra())
     {
@@ -6226,7 +6320,7 @@ void IntraPrediction::xFillReferenceSamplesForCoLuma(const CPelBuf &recoBuf, Pel
 #if JVET_AI0136_ADAPTIVE_DUAL_TREE
   if (cu.slice->isIntra())
 #endif
-  numIntraNeighbor += area.x > 0 ? std::min(numLeftBelowUnits, hUnit) : 0;
+    numIntraNeighbor += area.x > 0 ? std::min(numLeftBelowUnits, hUnit) : 0;
   int y = 0;
   for (int i = totalLeftUnits - 1 - numLeftUnits; i > totalLeftUnits - 1 - numLeftUnits - numLeftBelowUnits; i--)
   {
@@ -6239,6 +6333,7 @@ void IntraPrediction::xFillReferenceSamplesForCoLuma(const CPelBuf &recoBuf, Pel
 #endif
     y++;
   }
+#endif
 
   // ----- Step 2: fill reference samples (depending on neighborhood) -----
 
@@ -6289,9 +6384,15 @@ void IntraPrediction::xFillReferenceSamplesForCoLuma(const CPelBuf &recoBuf, Pel
     }
 
     // Fill left & below-left samples if available (downwards)
+#if JVET_AJ0081_CHROMA_TMRL
+    ptrSrc += (1 + multiRefIdx - leftMrlUnitNum * unitHeight) * srcStride;
+    ptrDst += (1 + multiRefIdx - leftMrlUnitNum * unitHeight) + predStride;
+    for (int unitIdx = totalLeftUnits + leftMrlUnitNum - 1; unitIdx > 0; unitIdx--)
+#else
     ptrSrc += (1 + multiRefIdx) * srcStride;
     ptrDst += (1 + multiRefIdx) + predStride;
     for (int unitIdx = totalLeftUnits - 1; unitIdx > 0; unitIdx--)
+#endif
     {
       if (neighborFlags[unitIdx])
       {
@@ -6314,9 +6415,15 @@ void IntraPrediction::xFillReferenceSamplesForCoLuma(const CPelBuf &recoBuf, Pel
     }
 
     // Fill above & above-right samples if available (left-to-right)
+#if JVET_AJ0081_CHROMA_TMRL
+    ptrSrc = srcBuf - srcStride * (1 + multiRefIdx) - aboveMrlUnitNum * unitWidth;
+    ptrDst = refBufUnfiltered + 1 + multiRefIdx - aboveMrlUnitNum * unitWidth;
+    for (int unitIdx = totalLeftUnits + leftMrlUnitNum + 1; unitIdx < totalUnits - 1; unitIdx++)
+#else
     ptrSrc = srcBuf - srcStride * (1 + multiRefIdx);
     ptrDst = refBufUnfiltered + 1 + multiRefIdx;
     for (int unitIdx = totalLeftUnits + 1; unitIdx < totalUnits - 1; unitIdx++)
+#endif
     {
       if (neighborFlags[unitIdx])
       {
@@ -6353,6 +6460,20 @@ void IntraPrediction::xFillReferenceSamplesForCoLuma(const CPelBuf &recoBuf, Pel
       int firstAvailRow = -1;
       int firstAvailCol = 0;
 
+#if JVET_AJ0081_CHROMA_TMRL
+      if (firstAvailUnit < totalLeftUnits + leftMrlUnitNum)
+      {
+        firstAvailRow = (totalLeftUnits + leftMrlUnitNum - firstAvailUnit) * unitHeight + multiRefIdx - leftMrlUnitNum * unitHeight;
+      }
+      else if (firstAvailUnit == totalLeftUnits + leftMrlUnitNum)
+      {
+        firstAvailRow = multiRefIdx - leftMrlUnitNum * unitHeight;
+      }
+      else
+      {
+        firstAvailCol = (firstAvailUnit - totalLeftUnits - leftMrlUnitNum - 1) * unitWidth + 1 + multiRefIdx - aboveMrlUnitNum * unitWidth;
+      }
+#else
       if (firstAvailUnit < totalLeftUnits)
       {
         firstAvailRow = (totalLeftUnits - firstAvailUnit) * unitHeight + multiRefIdx;
@@ -6365,6 +6486,7 @@ void IntraPrediction::xFillReferenceSamplesForCoLuma(const CPelBuf &recoBuf, Pel
       {
         firstAvailCol = (firstAvailUnit - totalLeftUnits - 1) * unitWidth + 1 + multiRefIdx;
       }
+#endif
 
       const Pel firstAvailSample = ptrDst[firstAvailRow < 0 ? firstAvailCol : firstAvailRow + predStride];
 
@@ -6396,6 +6518,20 @@ void IntraPrediction::xFillReferenceSamplesForCoLuma(const CPelBuf &recoBuf, Pel
         // last available sample
         int lastAvailRow = -1;
         int lastAvailCol = 0;
+#if JVET_AJ0081_CHROMA_TMRL
+        if (lastAvailUnit < totalLeftUnits + leftMrlUnitNum)
+        {
+          lastAvailRow = (totalLeftUnits + leftMrlUnitNum - lastAvailUnit - 1) * unitHeight + multiRefIdx - leftMrlUnitNum * unitHeight + 1;
+        }
+        else if (lastAvailUnit == totalLeftUnits + leftMrlUnitNum)
+        {
+          lastAvailCol = multiRefIdx - leftMrlUnitNum * unitHeight;
+        }
+        else
+        {
+          lastAvailCol = (lastAvailUnit - totalLeftUnits - leftMrlUnitNum) * unitWidth + multiRefIdx - aboveMrlUnitNum * unitWidth;
+        }
+#else
         if (lastAvailUnit < totalLeftUnits)
         {
           lastAvailRow = (totalLeftUnits - lastAvailUnit - 1) * unitHeight + multiRefIdx + 1;
@@ -6408,9 +6544,38 @@ void IntraPrediction::xFillReferenceSamplesForCoLuma(const CPelBuf &recoBuf, Pel
         {
           lastAvailCol = (lastAvailUnit - totalLeftUnits) * unitWidth + multiRefIdx;
         }
+#endif
         const Pel lastAvailSample = ptrDst[lastAvailRow < 0 ? lastAvailCol : lastAvailRow + predStride];
 
         // fill current unit with last available sample
+#if JVET_AJ0081_CHROMA_TMRL
+        if (currUnit < totalLeftUnits + leftMrlUnitNum)
+        {
+          for (int i = lastAvailRow - 1; i >= lastAvailRow - unitHeight; i--)
+          {
+            ptrDst[i + predStride] = lastAvailSample;
+          }
+        }
+        else if (currUnit == totalLeftUnits + leftMrlUnitNum)
+        {
+          for (int i = 0; i < multiRefIdx - leftMrlUnitNum * unitHeight + 1; i++)
+          {
+            ptrDst[i + predStride] = lastAvailSample;
+          }
+          for (int j = 0; j < multiRefIdx - aboveMrlUnitNum * unitWidth + 1; j++)
+          {
+            ptrDst[j] = lastAvailSample;
+          }
+        }
+        else
+        {
+          int numSamplesInUnit = (currUnit == totalUnits - 1) ? ((predSize % unitWidth == 0) ? unitWidth : predSize % unitWidth) : unitWidth;
+          for (int j = lastAvailCol + 1; j <= lastAvailCol + numSamplesInUnit; j++)
+          {
+            ptrDst[j] = lastAvailSample;
+          }
+        }
+#else
         if (currUnit < totalLeftUnits)
         {
           for (int i = lastAvailRow - 1; i >= lastAvailRow - unitHeight; i--)
@@ -6437,6 +6602,7 @@ void IntraPrediction::xFillReferenceSamplesForCoLuma(const CPelBuf &recoBuf, Pel
             ptrDst[j] = lastAvailSample;
           }
         }
+#endif
       }
       lastAvailUnit = currUnit;
       currUnit++;
@@ -27342,6 +27508,321 @@ void IntraPrediction::getTmrlList(CodingUnit& cu)
 }
 #endif
 
+#if JVET_AJ0081_CHROMA_TMRL
+void IntraPrediction::getChromaTmrlSearchRange(const PredictionUnit& pu, int8_t* tmrlRefList, uint8_t* tmrlIntraList, uint8_t& sizeRef, uint8_t& sizeMode)
+{
+  const CompArea& area = pu.Cb();
+  CodingUnit& cu = *pu.cu;
+  int aboveLines = cu.block(COMPONENT_Cb).y;
+  sizeRef = 0;
+
+  for (; sizeRef < MAX_CHROMA_MRL_IDX; sizeRef++)
+  {
+    tmrlRefList[sizeRef] = CHROMA_MULTI_REF_LINE_IDX[sizeRef];
+    if (CHROMA_MULTI_REF_LINE_IDX[sizeRef] >= aboveLines)
+    {
+      break;
+    }
+  }
+
+  sizeMode = CHROMA_TMRL_MPM_SIZE;
+  uint8_t chromaDirMode;
+  int vaildNum = 0;
+  bool hasMode[67] = { false };
+  hasMode[PLANAR_IDX] = true;
+  chromaDirMode = PU::getCoLocatedIntraLumaMode(pu);
+  if (hasMode[chromaDirMode] == false)
+  {
+    hasMode[chromaDirMode] = true;
+    tmrlIntraList[vaildNum] = chromaDirMode;
+    vaildNum++;
+  }
+  chromaDirMode = cu.dimdChromaMode;
+  if (hasMode[chromaDirMode] == false)
+  {
+    hasMode[chromaDirMode] = true;
+    tmrlIntraList[vaildNum] = chromaDirMode;
+    vaildNum++;
+  }
+  chromaDirMode = cu.dimdChromaModeSecond;
+  if (hasMode[chromaDirMode] == false)
+  {
+    hasMode[chromaDirMode] = true;
+    tmrlIntraList[vaildNum] = chromaDirMode;
+    vaildNum++;
+  }
+  chromaDirMode = DC_IDX;
+  if (hasMode[chromaDirMode] == false)
+  {
+    hasMode[chromaDirMode] = true;
+    tmrlIntraList[vaildNum] = chromaDirMode;
+    vaildNum++;
+  }
+  // get co-located modes
+  CompArea lumaArea = CompArea(COMPONENT_Y, pu.chromaFormat, pu.Cb().lumaPos(), recalcSize(pu.chromaFormat, CHANNEL_TYPE_CHROMA, CHANNEL_TYPE_LUMA, pu.Cb().size()));
+  lumaArea = clipArea(lumaArea, pu.cs->picture->block(COMPONENT_Y));
+
+  Position posList[5] = { lumaArea.center(), lumaArea.topLeft(), lumaArea.topRight(), lumaArea.bottomLeft(), lumaArea.bottomRight() };
+  for (int n = 0; n < 5; n++)
+  {
+    if (vaildNum < sizeMode)
+    {
+#if JVET_AI0136_ADAPTIVE_DUAL_TREE
+      const PredictionUnit* lumaPU = (CS::isDualITree(*pu.cs) || (pu.cu->isSST && pu.cu->separateTree))
+        ? pu.cs->getLumaPU(posList[n], CHANNEL_TYPE_LUMA)
+        : pu.cs->getPU(posList[n], CHANNEL_TYPE_LUMA);
+#else
+      const PredictionUnit* lumaPU = pu.cs->picture->cs->getPU(posList[n], CHANNEL_TYPE_LUMA);
+#endif
+      if (lumaPU->cu->timd || lumaPU->cu->tmrlFlag)
+      {
+        chromaDirMode = MAP131TO67(PU::getIntraDirLuma(*lumaPU, 0));
+      }
+      else
+      {
+        chromaDirMode = PU::getIntraDirLuma(*lumaPU, 0);
+      }
+      if (hasMode[chromaDirMode] == false)
+      {
+        hasMode[chromaDirMode] = true;
+        tmrlIntraList[vaildNum] = chromaDirMode;
+        vaildNum++;
+      }
+    }
+  }
+
+  // get neighboring modes
+  const Position posCand[5] =
+  {
+    pu.chromaPos().offset(-1, area.height - 1),
+    pu.chromaPos().offset(area.width - 1, -1),
+    pu.chromaPos().offset(-1, area.height),
+    pu.chromaPos().offset(area.width, -1),
+    pu.chromaPos().offset(-1, -1)
+  };
+
+  for (const Position& posLT : posCand)
+  {
+    if (vaildNum < sizeMode)
+    {
+      const PredictionUnit* puRef = PU::getPUFromPos(pu, CHANNEL_TYPE_CHROMA, posLT);
+#if JVET_AH0136_CHROMA_REORDERING
+#if JVET_AI0136_ADAPTIVE_DUAL_TREE
+      if (puRef != nullptr && CU::isIntra(*puRef->cu) && !PU::isLMCMode(puRef->intraDir[1]) && !PU::isDbvMode(puRef->intraDir[1])
+        && (puRef->intraDir[1] != DM_CHROMA_IDX || puRef->cu->slice->isIntra())
+        && (puRef->intraDir[1] != DIMD_CHROMA_IDX || puRef->cu->slice->isIntra()))
+#else
+      if (puRef != nullptr && CU::isIntra(*puRef->cu) && !PU::isLMCMode(puRef->intraDir[1]) && !PU::isDbvMode(puRef->intraDir[1]))
+#endif
+#else
+      if (puRef != nullptr && CU::isIntra(*puRef->cu) && !PU::isLMCMode(puRef->intraDir[1]) && puRef->intraDir[1] != DBV_CHROMA_IDX)
+#endif
+      {
+        chromaDirMode = PU::getFinalIntraMode(*puRef, CHANNEL_TYPE_CHROMA);
+        if (hasMode[chromaDirMode] == false)
+        {
+          hasMode[chromaDirMode] = true;
+          tmrlIntraList[vaildNum] = chromaDirMode;
+          vaildNum++;
+        }
+      }
+    }
+  }
+
+  fillMPMList(pu, tmrlIntraList, sizeMode, vaildNum, false);
+}
+void IntraPrediction::predChromaTmrlIntraAng(PredictionUnit& pu, const ComponentID compID, Pel* pPred, uint32_t uiStride)
+{
+  const CPelBuf& srcBuf = CPelBuf(m_refBuffer[compID][0], m_refBufferStride[compID], 2);
+  const ClpRng& clpRng(pu.cu->cs->slice->clpRng(compID));
+
+  switch (pu.intraDir[1])
+  {
+  case(DC_IDX):     xPredTmrlIntraDc(srcBuf, pPred, uiStride); break;
+  default:          xPredTmrlIntraAng(srcBuf, clpRng, pPred, uiStride); break;
+  }
+}
+void IntraPrediction::getChromaTmrlList(const CPelBuf& recoBufY, const CPelBuf& recoBufCb, const CPelBuf& recoBufCr, const CompArea& areaY, const CompArea& areaCb, const CompArea& areaCr, CodingUnit& cu, PredictionUnit& pu
+  , InterPrediction* pcInterPred)
+{
+  if (!CS::isDualITree(*pu.cs) || !pu.cs->sps->getUseTmrl())
+  {
+    return;
+  }
+  // step-1. prepare buffers, cost functions, initialize size.
+  UnitArea area(CHROMA_420, areaY);
+  CodingUnit lumaCU(area);
+  PredictionUnit lumaPU(area);
+  lumaPU.cu = &lumaCU;
+  lumaCU.firstPU = &lumaPU;
+  lumaCU.cs = cu.cs;
+  lumaPU.cs = pu.cs;
+  lumaCU.slice = cu.slice;
+#if JVET_AI0136_ADAPTIVE_DUAL_TREE
+  lumaCU.isSST = cu.isSST;
+  lumaCU.separateTree = cu.separateTree;
+#endif
+  int width = areaY.width;
+  int height = areaY.height;
+  const UnitArea localUnitArea(CHROMA_420, Area(0, 0, width, height));
+  PelBuf predY = m_tempBuffer[0].getBuf(localUnitArea.Y());
+  DistParam cDistParamSatd;
+  m_timdSatdCost->setDistParam(cDistParamSatd, recoBufY, predY, pu.cs->sps->getBitDepth(CHANNEL_TYPE_LUMA), COMPONENT_Y, true);
+  cDistParamSatd.applyWeight = false;
+  cDistParamSatd.useMR = false;
+
+  int intraDir = pu.intraDir[1];
+  tmrlInfo.uiWidth = areaCb.width;
+  tmrlInfo.uiHeight = areaCb.height;
+  tmrlInfo.uiTemplateAbove = TMRL_TPL_SIZE;
+  tmrlInfo.uiTemplateLeft = TMRL_TPL_SIZE;
+  tmrlInfo.uiRefWidth = areaCb.width + TMRL_TPL_SIZE;
+  tmrlInfo.uiRefHeight = areaCb.height + TMRL_TPL_SIZE;
+  int line = TMRL_TPL_SIZE;
+  const UnitArea localUnitArea2(CHROMA_420, Area(0, 0, (areaCb.width + line) * 2, (areaCb.height + line) * 2));
+  PelBuf predCb = m_tempBuffer[0].getBuf(localUnitArea2.Cb());
+  PelBuf predCr = m_tempBuffer[0].getBuf(localUnitArea2.Cr());
+  int iRefX = 0;
+  int iRefY = 0;
+  uint32_t uiRefWidth = 0;
+  uint32_t uiRefHeight = 0;
+  TemplateType eTplType = CU::deriveTimdRefType(areaCb.x, areaCb.y, areaCb.width, areaCb.height, line, line, iRefX,
+    iRefY, uiRefWidth, uiRefHeight);
+  const Pel* piCb = recoBufCb.buf;
+  int  iCbStride = recoBufCb.stride;
+  piCb += (iRefY - areaCb.y) * iCbStride + (iRefX - areaCb.x);
+  const Pel* piCr = recoBufCr.buf;
+  int  iCrStride = recoBufCr.stride;
+  piCr += (iRefY - areaCr.y) * iCrStride + (iRefX - areaCr.x);
+
+  DistParam distParamSatd[2][2];   // above, left
+  distParamSatd[0][0].applyWeight = false;
+  distParamSatd[0][0].useMR = false;
+  distParamSatd[0][1].applyWeight = false;
+  distParamSatd[0][1].useMR = false;
+  distParamSatd[1][0].applyWeight = false;
+  distParamSatd[1][0].useMR = false;
+  distParamSatd[1][1].applyWeight = false;
+  distParamSatd[1][1].useMR = false;
+  if (eTplType == LEFT_ABOVE_NEIGHBOR)
+  {
+    m_timdSatdCost->setTimdDistParam(distParamSatd[0][0], piCb + line, predCb.buf + line, iCbStride, predCb.stride, pu.cs->sps->getBitDepth(CHANNEL_TYPE_CHROMA), COMPONENT_Cb, areaCb.width, line, 0, 1, false);
+    m_timdSatdCost->setTimdDistParam(distParamSatd[0][1], piCb + line * iCbStride, predCb.buf + line * predCb.stride, iCbStride, predCb.stride, pu.cs->sps->getBitDepth(CHANNEL_TYPE_CHROMA), COMPONENT_Cb, line, areaCb.height, 0, 1, false);
+    m_timdSatdCost->setTimdDistParam(distParamSatd[1][0], piCr + line, predCr.buf + line, iCrStride, predCr.stride, pu.cs->sps->getBitDepth(CHANNEL_TYPE_CHROMA), COMPONENT_Cr, areaCr.width, line, 0, 1, false);
+    m_timdSatdCost->setTimdDistParam(distParamSatd[1][1], piCr + line * iCrStride, predCr.buf + line * predCr.stride, iCrStride, predCr.stride, pu.cs->sps->getBitDepth(CHANNEL_TYPE_CHROMA), COMPONENT_Cr, line, areaCr.height, 0, 1, false);
+  }
+  else if (eTplType == LEFT_NEIGHBOR)
+  {
+    m_timdSatdCost->setTimdDistParam(distParamSatd[0][1], piCb, predCb.buf + line * predCb.stride, iCbStride, predCb.stride, pu.cs->sps->getBitDepth(CHANNEL_TYPE_CHROMA), COMPONENT_Cb, line, areaCb.height, 0, 1, false);
+    m_timdSatdCost->setTimdDistParam(distParamSatd[1][1], piCr, predCr.buf + line * predCr.stride, iCrStride, predCr.stride, pu.cs->sps->getBitDepth(CHANNEL_TYPE_CHROMA), COMPONENT_Cr, line, areaCr.height, 0, 1, false);
+  }
+  else if (eTplType == ABOVE_NEIGHBOR)
+  {
+    m_timdSatdCost->setTimdDistParam(distParamSatd[0][0], piCb, predCb.buf + line, iCbStride, predCb.stride, pu.cs->sps->getBitDepth(CHANNEL_TYPE_CHROMA), COMPONENT_Cb, areaCb.width, line, 0, 1, false);
+    m_timdSatdCost->setTimdDistParam(distParamSatd[1][0], piCr, predCr.buf + line, iCrStride, predCr.stride, pu.cs->sps->getBitDepth(CHANNEL_TYPE_CHROMA), COMPONENT_Cr, areaCr.width, line, 0, 1, false);
+  }
+
+  int logW = floorLog2(areaCb.width);
+  int logH = floorLog2(areaCb.height);
+  int logN = floorLog2(line);
+
+  // step-2. define search range.
+  int8_t tmrlRefList[MAX_CHROMA_MRL_IDX]{ 0 };
+  uint8_t tmrlIntraModeList[NUM_LUMA_MODE]{ 0 };
+  uint8_t sizeRef, sizeMode;
+  getChromaTmrlSearchRange(pu, tmrlRefList, tmrlIntraModeList, sizeRef, sizeMode);
+
+  // step-3. search and sort
+  static_vector<TmrlMode, MRL_LIST_SIZE> uiModeList;
+  static_vector<uint64_t, MRL_LIST_SIZE> uiCostList;
+
+  int iBestN = CHROMA_TMRL_LIST_SIZE;
+
+  if (!pu.cs->pcv->isEncoder)
+  {
+    iBestN = pu.chromaTmrlIdx + 1;
+  }
+
+  for (uint8_t refIdx = 0; refIdx < sizeRef; refIdx++)
+  {
+    lumaPU.multiRefIdx = tmrlRefList[refIdx] << 1;
+    m_topRefLength = (areaCb.width + line) << 1;
+    m_leftRefLength = (areaCb.height + line) << 1;
+    pu.chromaMrlIdx = tmrlRefList[refIdx];
+    xFillReferenceSamples(recoBufCb, m_refBuffer[COMPONENT_Cb][PRED_BUF_UNFILTERED], areaCb, *pu.cu);
+    xFillReferenceSamples(recoBufCr, m_refBuffer[COMPONENT_Cr][PRED_BUF_UNFILTERED], areaCr, *pu.cu);
+    for (uint8_t modeIdx = 0; modeIdx < sizeMode; modeIdx++)
+    {
+      Distortion uiCost = 0;
+      lumaPU.intraDir[0] = tmrlIntraModeList[modeIdx];
+      // pred co-located luma area
+      predCoLuma(areaY, recoBufY, lumaPU, lumaPU.intraDir[0], predY, pcInterPred, cu);
+      uiCost += cDistParamSatd.distFunc(cDistParamSatd);
+
+      // chroma
+      m_topRefLength = (areaCb.width + line) << 1;
+      m_leftRefLength = (areaCb.height + line) << 1;
+      pu.intraDir[1] = tmrlIntraModeList[modeIdx];
+      if (eTplType != NO_NEIGHBOR)
+      {
+        Distortion costCbA = 0;
+        Distortion costCbL = 0;
+        Distortion costCrA = 0;
+        Distortion costCrL = 0;
+        initPredIntraParams(pu, areaCb, *(pu.cs->sps));
+        predChromaTmrlIntraAng(pu, COMPONENT_Cb, predCb.buf, predCb.stride);
+        initPredIntraParams(pu, areaCr, *(pu.cs->sps));
+        predChromaTmrlIntraAng(pu, COMPONENT_Cr, predCr.buf, predCr.stride);
+
+        if (eTplType == LEFT_ABOVE_NEIGHBOR)
+        {
+          costCbA += distParamSatd[0][0].distFunc(distParamSatd[0][0]);
+          costCbL += distParamSatd[0][1].distFunc(distParamSatd[0][1]);
+
+          costCrA += distParamSatd[1][0].distFunc(distParamSatd[1][0]);
+          costCrL += distParamSatd[1][1].distFunc(distParamSatd[1][1]);
+          uiCost += 8 * uiCost + ((costCbA + costCrA) << (logH + 2 - logN)) + ((costCbL + costCrL) << (logW + 2 - logN));
+        }
+        else if (eTplType == LEFT_NEIGHBOR)
+        {
+          costCbL += distParamSatd[0][1].distFunc(distParamSatd[0][1]);
+          costCrL += distParamSatd[1][1].distFunc(distParamSatd[1][1]);
+          uiCost += 8 * uiCost + ((costCbL + costCrL) << (logW + 2 - logN));
+        }
+        else if (eTplType == ABOVE_NEIGHBOR)
+        {
+          costCbA += distParamSatd[0][0].distFunc(distParamSatd[0][0]);
+          costCrA += distParamSatd[1][0].distFunc(distParamSatd[1][0]);
+          uiCost += 8 * uiCost + ((costCbA + costCrA) << (logH + 2 - logN));
+        }
+        else
+        {
+          CHECK(true, "wrong template type!");
+        }
+      }
+
+      if (uiCostList.size() >= iBestN)
+      {
+        uint64_t uiCostMax = uiCostList[iBestN - 1];
+        if (uiCost > uiCostMax)
+        {
+          continue;
+        }
+      }
+      updateCandList(TmrlMode(tmrlRefList[refIdx], tmrlIntraModeList[modeIdx]), uiCost, uiModeList, uiCostList, iBestN);
+    }
+  }
+
+  // step-4. fill the list
+  for (auto i = 0; i < uiModeList.size(); i++)
+  {
+    m_chromaTmrlList[i] = uiModeList[i];
+  }
+  pu.chromaMrlIdx = 0;
+  pu.intraDir[1] = intraDir;
+}
+#endif
+
 #if JVET_AG0058_EIP
 int64_t IntraPrediction::calcAeipGroupSum(const Pel* src1, const Pel* src2, const int numSamples)
 {
diff --git a/source/Lib/CommonLib/IntraPrediction.h b/source/Lib/CommonLib/IntraPrediction.h
index 486b1f6e9..b833e2b26 100644
--- a/source/Lib/CommonLib/IntraPrediction.h
+++ b/source/Lib/CommonLib/IntraPrediction.h
@@ -940,6 +940,12 @@ public:
   void getTmrlSearchRange(const PredictionUnit& pu, int8_t* tmrlRefList, uint8_t* tmrlIntraList, uint8_t& sizeRef, uint8_t& sizeMode);
   TmrlMode m_tmrlList[MRL_LIST_SIZE];
   void getTmrlList(CodingUnit& cu);
+#if JVET_AJ0081_CHROMA_TMRL
+  TmrlMode m_chromaTmrlList[CHROMA_TMRL_LIST_SIZE];
+  void getChromaTmrlSearchRange(const PredictionUnit& pu, int8_t* tmrlRefList, uint8_t* tmrlIntraList, uint8_t& sizeRef, uint8_t& sizeMode);
+  void predChromaTmrlIntraAng(PredictionUnit& pu, const ComponentID compID, Pel* pPred, uint32_t uiStride);
+  void getChromaTmrlList(const CPelBuf& recoBufY, const CPelBuf& recoBufCb, const CPelBuf& recoBufCr, const CompArea& areaY, const CompArea& areaCb, const CompArea& areaCr, CodingUnit& cu, PredictionUnit& pu, InterPrediction* pcInterPred);
+#endif
 #endif
 #if JVET_AG0058_EIP
   void initEipParams(const PredictionUnit& pu, const ComponentID compId);
diff --git a/source/Lib/CommonLib/TypeDef.h b/source/Lib/CommonLib/TypeDef.h
index 8d1d8515f..b46230864 100644
--- a/source/Lib/CommonLib/TypeDef.h
+++ b/source/Lib/CommonLib/TypeDef.h
@@ -194,6 +194,7 @@
 #if JVET_AB0157_TMRL
 #define JVET_AD0082_TMRL_CONFIG                           1 // JVET-AD0082: a configuration option for TMRL
 #define JVET_AD0085_TMRL_EXTENSION                        1 // JVET-AD0085: TMRL angular extension and intra candidate list modifications
+#define JVET_AJ0081_CHROMA_TMRL                           1 // JVET-AJ0081: Chroma TMRL
 #endif
 #define JVET_AB0157_INTRA_FUSION                          1 // JVET-AB0157: Intra prediction fusion
 #define JVET_AC0094_REF_SAMPLES_OPT                       1 // JVET-AC0094: Optimizing the use of reference samples
diff --git a/source/Lib/CommonLib/Unit.cpp b/source/Lib/CommonLib/Unit.cpp
index 071886416..98d850376 100644
--- a/source/Lib/CommonLib/Unit.cpp
+++ b/source/Lib/CommonLib/Unit.cpp
@@ -1101,6 +1101,11 @@ void PredictionUnit::initData()
 #if JVET_AG0059_CCP_MERGE_ENHANCEMENT
   ccpMergeFusionFlag = 0;
   ccpMergeFusionType = 0;
+#endif
+#if JVET_AJ0081_CHROMA_TMRL
+  chromaMrlIdx = 0;
+  chromaTmrlFlag = false;
+  chromaTmrlIdx = 0;
 #endif
   // inter data
 #if JVET_AC0185_ENHANCED_TEMPORAL_MOTION_DERIVATION
@@ -1323,6 +1328,11 @@ PredictionUnit& PredictionUnit::operator=(const IntraPredictionData& predData)
 #if JVET_AG0059_CCP_MERGE_ENHANCEMENT
   ccpMergeFusionFlag = predData.ccpMergeFusionFlag;
   ccpMergeFusionType = predData.ccpMergeFusionType;
+#endif
+#if JVET_AJ0081_CHROMA_TMRL
+  chromaMrlIdx = predData.chromaMrlIdx;
+  chromaTmrlFlag = predData.chromaTmrlFlag;
+  chromaTmrlIdx = predData.chromaTmrlIdx;
 #endif
   return *this;
 }
@@ -1555,6 +1565,11 @@ PredictionUnit& PredictionUnit::operator=( const PredictionUnit& other )
 #if JVET_AG0059_CCP_MERGE_ENHANCEMENT
   ccpMergeFusionFlag = other.ccpMergeFusionFlag;
   ccpMergeFusionType = other.ccpMergeFusionType;
+#endif
+#if JVET_AJ0081_CHROMA_TMRL
+  chromaMrlIdx = other.chromaMrlIdx;
+  chromaTmrlFlag = other.chromaTmrlFlag;
+  chromaTmrlIdx = other.chromaTmrlIdx;
 #endif
   mergeFlag   = other.mergeFlag;
 #if JVET_AG0276_LIC_FLAG_SIGNALING
diff --git a/source/Lib/CommonLib/Unit.h b/source/Lib/CommonLib/Unit.h
index fb1d0386e..e62a44e5b 100644
--- a/source/Lib/CommonLib/Unit.h
+++ b/source/Lib/CommonLib/Unit.h
@@ -638,6 +638,11 @@ struct IntraPredictionData
   int       ccpMergeFusionFlag;
   int       ccpMergeFusionType;
 #endif
+#if JVET_AJ0081_CHROMA_TMRL
+  int       chromaMrlIdx;
+  bool      chromaTmrlFlag;
+  int       chromaTmrlIdx;
+#endif
 };
 
 struct InterPredictionData
diff --git a/source/Lib/CommonLib/UnitTools.cpp b/source/Lib/CommonLib/UnitTools.cpp
index ae9b12e7a..7d0faa132 100644
--- a/source/Lib/CommonLib/UnitTools.cpp
+++ b/source/Lib/CommonLib/UnitTools.cpp
@@ -3752,6 +3752,23 @@ bool PU::hasCCPMergeFusionFlag(const PredictionUnit& pu)
 }
 #endif
 
+#if JVET_AJ0081_CHROMA_TMRL
+bool PU::hasChromaTmrl(const PredictionUnit& pu)
+{
+  if (CS::isDualITree(*pu.cs))
+  {
+    bool hasChromaTmrl = true;
+    int aboveLines = pu.block(COMPONENT_Cb).y;
+    if (aboveLines < 4)
+    {
+      hasChromaTmrl = false;
+    }
+    return hasChromaTmrl;
+  }
+  return false;
+}
+#endif
+
 #if JVET_AC0071_DBV
 bool PU::hasChromaBvFlag(const PredictionUnit &pu)
 {
diff --git a/source/Lib/CommonLib/UnitTools.h b/source/Lib/CommonLib/UnitTools.h
index c55bc2be0..49b8239e1 100644
--- a/source/Lib/CommonLib/UnitTools.h
+++ b/source/Lib/CommonLib/UnitTools.h
@@ -963,6 +963,9 @@ namespace PU
 #if JVET_AG0059_CCP_MERGE_ENHANCEMENT
   bool hasCCPMergeFusionFlag(const PredictionUnit& pu);
 #endif
+#if JVET_AJ0081_CHROMA_TMRL
+  bool hasChromaTmrl(const PredictionUnit& pu);
+#endif
 #if JVET_AC0071_DBV
   bool hasChromaBvFlag(const PredictionUnit &pu);
 #endif
diff --git a/source/Lib/DecoderLib/CABACReader.cpp b/source/Lib/DecoderLib/CABACReader.cpp
index 02de8efb9..c5745f088 100644
--- a/source/Lib/DecoderLib/CABACReader.cpp
+++ b/source/Lib/DecoderLib/CABACReader.cpp
@@ -3363,6 +3363,19 @@ void CABACReader::intraChromaFusionMode(PredictionUnit& pu)
 }
 #endif
 
+#if JVET_AJ0081_CHROMA_TMRL
+void CABACReader::intraChromaTmrl(PredictionUnit& pu)
+{
+  pu.chromaTmrlIdx = 0;
+  pu.chromaTmrlFlag = bool(m_BinDecoder.decodeBin(Ctx::ChromaTmrlFlag()));
+  if (pu.chromaTmrlFlag)
+  {
+    pu.chromaTmrlIdx = unary_max_eqprob(CHROMA_TMRL_LIST_SIZE - 1);
+  }
+  DTRACE(g_trace_ctx, D_SYNTAX, "intraChromaTmrl() ctx=%d pos=(%d,%d) chromaTmrlFlag=%d chromaTmrlIdx=%d\n", 0, pu.blocks[CHANNEL_TYPE_CHROMA].x, pu.blocks[CHANNEL_TYPE_CHROMA].y, pu.chromaTmrlFlag ? 1 : 0, pu.chromaTmrlIdx);
+}
+#endif
+
 void CABACReader::intra_chroma_pred_mode(PredictionUnit& pu)
 {
   RExt__DECODER_DEBUG_BIT_STATISTICS_CREATE_SET_SIZE2(STATS__CABAC_BITS__INTRA_DIR_ANG, pu.cu->blocks[pu.chType].lumaSize(), CHANNEL_TYPE_CHROMA);
@@ -3389,6 +3402,18 @@ void CABACReader::intra_chroma_pred_mode(PredictionUnit& pu)
     }
   }
 
+#if JVET_AJ0081_CHROMA_TMRL
+  if (PU::hasChromaTmrl(pu) && pu.cs->sps->getUseTmrl())
+  {
+    intraChromaTmrl(pu);
+    if (pu.chromaTmrlFlag)
+    {
+      CHECK(pu.chromaTmrlIdx >= CHROMA_TMRL_LIST_SIZE, "Chroma tmrl index out of bounds");
+      return;
+    }
+  }
+#endif
+
 #if JVET_AC0071_DBV
   if (PU::hasChromaBvFlag(pu))
   {
diff --git a/source/Lib/DecoderLib/CABACReader.h b/source/Lib/DecoderLib/CABACReader.h
index 0696b2e94..882603215 100644
--- a/source/Lib/DecoderLib/CABACReader.h
+++ b/source/Lib/DecoderLib/CABACReader.h
@@ -134,6 +134,9 @@ public:
 #endif
 #if JVET_AB0157_TMRL
   void        cuTmrlFlag                ( CodingUnit&                   cu );
+#if JVET_AJ0081_CHROMA_TMRL
+  void        intraChromaTmrl           ( PredictionUnit&               pu );
+#endif
 #endif
   void        intra_chroma_pred_modes   ( CodingUnit&                   cu );
   bool        intra_chroma_lmc_mode     ( PredictionUnit&               pu );
diff --git a/source/Lib/DecoderLib/DecCu.cpp b/source/Lib/DecoderLib/DecCu.cpp
index 606cc7859..4ce223339 100644
--- a/source/Lib/DecoderLib/DecCu.cpp
+++ b/source/Lib/DecoderLib/DecCu.cpp
@@ -668,11 +668,19 @@ void DecCu::xIntraRecBlk( TransformUnit& tu, const ComponentID compID )
 #if JVET_AG0059_CCP_MERGE_ENHANCEMENT
 #if JVET_AH0136_CHROMA_REORDERING
 #if JVET_AI0136_ADAPTIVE_DUAL_TREE
-  if ( ( (!PU::isLMCMode(pu.intraDir[1]) && compID == COMPONENT_Cb && !pu.cu->bdpcmModeChroma) 
-         && (CS::isDualITree(cs) || (pu.cu->isSST && pu.cu->separateTree) ) && pu.cs->sps->getUseChromaReordering() && pu.cs->slice->isIntra()
-       ) 
+#if JVET_AJ0081_CHROMA_TMRL
+  if (((!PU::isLMCMode(pu.intraDir[1]) && compID == COMPONENT_Cb && !pu.cu->bdpcmModeChroma)
+    && (CS::isDualITree(cs) || (pu.cu->isSST && pu.cu->separateTree)) && pu.cs->sps->getUseChromaReordering() && pu.cs->slice->isIntra()
+    )
+    || ((pu.intraDir[1] == DIMD_CHROMA_IDX || pu.ccpMergeFusionType == 1 || pu.chromaTmrlFlag) && compID == COMPONENT_Cb)
+    )
+#else
+  if (((!PU::isLMCMode(pu.intraDir[1]) && compID == COMPONENT_Cb && !pu.cu->bdpcmModeChroma)
+    && (CS::isDualITree(cs) || (pu.cu->isSST && pu.cu->separateTree)) && pu.cs->sps->getUseChromaReordering() && pu.cs->slice->isIntra()
+    )
     || ((pu.intraDir[1] == DIMD_CHROMA_IDX || pu.ccpMergeFusionType == 1) && compID == COMPONENT_Cb)
     )
+#endif
 #else
   if (((!PU::isLMCMode(pu.intraDir[1]) && compID == COMPONENT_Cb && !pu.cu->bdpcmModeChroma) && CS::isDualITree(cs) && pu.cs->sps->getUseChromaReordering()) || ((pu.intraDir[1] == DIMD_CHROMA_IDX || pu.ccpMergeFusionType == 1) && compID == COMPONENT_Cb))
 #endif
@@ -702,12 +710,31 @@ void DecCu::xIntraRecBlk( TransformUnit& tu, const ComponentID compID )
 #endif
   }
 #endif
+#if JVET_AJ0081_CHROMA_TMRL
+  if (pu.chromaTmrlFlag && compID == COMPONENT_Cb && CS::isDualITree(cs))
+  {
+    CompArea areaCb = pu.Cb();
+    CompArea areaCr = pu.Cr();
+    CompArea lumaArea = CompArea(COMPONENT_Y, pu.chromaFormat, areaCb.lumaPos(), recalcSize(pu.chromaFormat, CHANNEL_TYPE_CHROMA, CHANNEL_TYPE_LUMA, areaCb.size()));
+    m_pcIntraPred->getChromaTmrlList(cs.picture->getRecoBuf(lumaArea), cs.picture->getRecoBuf(areaCb), cs.picture->getRecoBuf(areaCr), lumaArea, areaCb, areaCr, *pu.cu, pu, m_pcInterPred);
+    pu.chromaMrlIdx = m_pcIntraPred->m_chromaTmrlList[pu.chromaTmrlIdx].multiRefIdx;
+    pu.intraDir[1] = m_pcIntraPred->m_chromaTmrlList[pu.chromaTmrlIdx].intraDir;
+    CHECK(pu.intraDir[1] >= NUM_LUMA_MODE, "error intra mode")
+  }
+#endif
 #if JVET_AH0136_CHROMA_REORDERING
 #if JVET_AI0136_ADAPTIVE_DUAL_TREE
+#if JVET_AJ0081_CHROMA_TMRL
   if (
-       (!PU::isLMCMode(pu.intraDir[1]) && compID == COMPONENT_Cb && !pu.cu->bdpcmModeChroma) 
-       && (CS::isDualITree(cs) || (pu.cu->isSST && pu.cu->separateTree) ) && pu.cs->sps->getUseChromaReordering() && pu.cu->slice->isIntra()
+    (!PU::isLMCMode(pu.intraDir[1]) && !pu.chromaTmrlFlag && compID == COMPONENT_Cb && !pu.cu->bdpcmModeChroma)
+    && (CS::isDualITree(cs) || (pu.cu->isSST && pu.cu->separateTree)) && pu.cs->sps->getUseChromaReordering() && pu.cu->slice->isIntra()
     )
+#else
+  if (
+    (!PU::isLMCMode(pu.intraDir[1]) && compID == COMPONENT_Cb && !pu.cu->bdpcmModeChroma)
+    && (CS::isDualITree(cs) || (pu.cu->isSST && pu.cu->separateTree)) && pu.cs->sps->getUseChromaReordering() && pu.cu->slice->isIntra()
+    )
+#endif
 #else
   if ((!PU::isLMCMode(pu.intraDir[1]) && compID == COMPONENT_Cb && !pu.cu->bdpcmModeChroma) && CS::isDualITree(cs) && pu.cs->sps->getUseChromaReordering())
 #endif
diff --git a/source/Lib/EncoderLib/CABACWriter.cpp b/source/Lib/EncoderLib/CABACWriter.cpp
index 125796186..a3a12b0b4 100644
--- a/source/Lib/EncoderLib/CABACWriter.cpp
+++ b/source/Lib/EncoderLib/CABACWriter.cpp
@@ -3050,6 +3050,19 @@ void CABACWriter::intraChromaFusionMode(const PredictionUnit& pu)
 }
 #endif
 
+#if JVET_AJ0081_CHROMA_TMRL
+void CABACWriter::intraChromaTmrl(const PredictionUnit& pu)
+{
+  m_BinEncoder.encodeBin(pu.chromaTmrlFlag ? 1 : 0, Ctx::ChromaTmrlFlag());
+  if (pu.chromaTmrlFlag)
+  {
+    CHECK(pu.chromaTmrlIdx >= CHROMA_TMRL_LIST_SIZE, "Chroma tmrl index out of bounds");
+    unary_max_eqprob(pu.chromaTmrlIdx, CHROMA_TMRL_LIST_SIZE - 1);
+  }
+  DTRACE(g_trace_ctx, D_SYNTAX, "intraChromaTmrl() ctx=%d pos=(%d,%d) chromaTmrlFlag=%d chromaTmrlIdx=%d\n", 0, pu.blocks[CHANNEL_TYPE_CHROMA].x, pu.blocks[CHANNEL_TYPE_CHROMA].y, pu.chromaTmrlFlag ? 1 : 0, pu.chromaTmrlIdx);
+}
+#endif
+
 #if JVET_AD0120_LBCCP
 void CABACWriter::ccInsideFilterFlag(const PredictionUnit &pu)
 {
@@ -3093,6 +3106,17 @@ void CABACWriter::intra_chroma_pred_mode(const PredictionUnit& pu)
     }
   }
 
+#if JVET_AJ0081_CHROMA_TMRL
+  if (PU::hasChromaTmrl(pu) && pu.cs->sps->getUseTmrl())
+  {
+    intraChromaTmrl(pu);
+    if (pu.chromaTmrlFlag)
+    {
+      return;
+    }
+  }
+#endif
+
 #if JVET_AC0071_DBV
 #if JVET_AH0136_CHROMA_REORDERING
   bool hasDBV = false;
diff --git a/source/Lib/EncoderLib/CABACWriter.h b/source/Lib/EncoderLib/CABACWriter.h
index 2460594ca..cc52dfb8f 100644
--- a/source/Lib/EncoderLib/CABACWriter.h
+++ b/source/Lib/EncoderLib/CABACWriter.h
@@ -170,6 +170,9 @@ public:
 #endif
 #if JVET_AB0157_TMRL
   void        cuTmrlFlag                ( const CodingUnit&             cu );
+#if JVET_AJ0081_CHROMA_TMRL
+  void        intraChromaTmrl           ( const PredictionUnit&         pu );
+#endif
 #endif
   void        intra_chroma_pred_modes   ( const CodingUnit&             cu );
   void        intra_chroma_lmc_mode     ( const PredictionUnit&         pu );
diff --git a/source/Lib/EncoderLib/EncCu.cpp b/source/Lib/EncoderLib/EncCu.cpp
index 7afe537db..81a2b847f 100644
--- a/source/Lib/EncoderLib/EncCu.cpp
+++ b/source/Lib/EncoderLib/EncCu.cpp
@@ -3518,6 +3518,22 @@ bool EncCu::xCheckRDCostIntra(CodingStructure *&tempCS, CodingStructure *&bestCS
       dimdChromaMode = cu.dimdChromaMode;
       dimdChromaModeSecond = cu.dimdChromaModeSecond;
 
+#if JVET_AJ0081_CHROMA_TMRL
+      if (tempCS->slice->getSPS()->getUseTmrl())
+      {
+        PredictionUnit pu(tempCS->area);
+        pu.cu = &cu;
+        cu.firstPU = &pu;
+        pu.cs = bestCS;
+        cu.cs = bestCS;
+
+        if (PU::hasChromaTmrl(pu))
+        {
+          m_pcIntraSearch->getChromaTmrlList(bestCS->picture->getRecoBuf(lumaArea), bestCS->picture->getRecoBuf(areaCb), bestCS->picture->getRecoBuf(areaCr), lumaArea, areaCb, areaCr, cu, pu, m_pcInterSearch);
+        }
+      }
+#endif
+
 #if JVET_AH0136_CHROMA_REORDERING
       for (int i = 0; i < 5; i++)
       {
diff --git a/source/Lib/EncoderLib/IntraSearch.cpp b/source/Lib/EncoderLib/IntraSearch.cpp
index d180b2b11..4d0e0cfda 100644
--- a/source/Lib/EncoderLib/IntraSearch.cpp
+++ b/source/Lib/EncoderLib/IntraSearch.cpp
@@ -207,6 +207,13 @@ void IntraSearch::destroy()
     m_pSharedPredTransformSkip[ch] = nullptr;
   }
 
+#if JVET_AJ0081_CHROMA_TMRL
+  for (uint32_t i = 0; i < CHROMA_TMRL_LIST_SIZE; i++)
+  {
+    m_chromaMrlStorage[i].destroy();
+  }
+#endif
+
 #if JVET_AC0119_LM_CHROMA_FUSION
   for (uint32_t i = 0; i < 2; i++)
   {
@@ -382,6 +389,13 @@ void IntraSearch::init( EncCfg*        pcEncCfg,
   m_tmpStorageLCU.create(UnitArea(cform, Area(0, 0, maxCUWidth, maxCUHeight)));
   m_colorTransResiBuf.create(UnitArea(cform, Area(0, 0, maxCUWidth, maxCUHeight)));
 
+#if JVET_AJ0081_CHROMA_TMRL
+  for (uint32_t i = 0; i < CHROMA_TMRL_LIST_SIZE; i++)
+  {
+    m_chromaMrlStorage[i].create(UnitArea(cform, Area(0, 0, maxCUWidth, maxCUHeight)));
+  }
+#endif
+
 #if JVET_AC0119_LM_CHROMA_FUSION
   for (uint32_t i = 0; i < 2; i++)
   {
@@ -4192,6 +4206,10 @@ void IntraSearch::estIntraPredChromaQT( CodingUnit &cu, Partitioner &partitioner
     int bestCcpMergeFusionFlag = 0;
     int bestCcpMergeFusionType = 0;
 #endif
+#if JVET_AJ0081_CHROMA_TMRL
+    bool bestChromaTmrlFlag = false;
+    int bestChromaTmrlIdx = 0;
+#endif
 
     //----- init mode list ----
     {
@@ -5517,6 +5535,13 @@ void IntraSearch::estIntraPredChromaQT( CodingUnit &cu, Partitioner &partitioner
         fusionStorage[i] = m_fusionStorage[i].getBuf(localArea);
       }
 #endif
+#if JVET_AJ0081_CHROMA_TMRL
+      PelUnitBuf chromaMrlStorage[CHROMA_TMRL_LIST_SIZE];
+      for (uint32_t i = 0; i < CHROMA_TMRL_LIST_SIZE; i++)
+      {
+        chromaMrlStorage[i] = m_chromaMrlStorage[i].getBuf(localArea);
+      }
+#endif
 #if JVET_AF0066_ENABLE_DBV_4_SINGLE_TREE
       if (singleTreeLumaIntraTmp)
       {
@@ -6039,6 +6064,209 @@ void IntraSearch::estIntraPredChromaQT( CodingUnit &cu, Partitioner &partitioner
       cs.setDecomp(pu.Cb(), false);
 #endif
 
+#if JVET_AJ0081_CHROMA_TMRL
+      pu.chromaTmrlFlag = false;
+      pu.chromaTmrlIdx = 0;
+      if (PU::hasChromaTmrl(pu) && pu.cs->sps->getUseTmrl())
+      {
+        pu.chromaTmrlFlag = true;
+        bool mrlIsEnable[CHROMA_TMRL_LIST_SIZE];
+        int32_t mrlMap[CHROMA_TMRL_LIST_SIZE][2];
+        double satdChromaMrlCost[CHROMA_TMRL_LIST_SIZE];
+        int satdChromaMrlList[CHROMA_TMRL_LIST_SIZE];
+        const double sqrtLambdaForFirstPass = m_pcRdCost->getMotionLambda() * FRAC_BITS_SCALE;
+        for (int i = 0; i < CHROMA_TMRL_LIST_SIZE; i++)
+        {
+          mrlIsEnable[i] = false;
+          mrlMap[i][0] = mrlMap[i][1] = 0;
+          satdChromaMrlCost[i] = MAX_DOUBLE;
+          satdChromaMrlList[i] = i;
+        }
+
+        Distortion sad = 0;
+        Distortion sadCb = 0, satdCb = 0;
+        Distortion sadCr = 0, satdCr = 0;
+        CodingStructure& cs = *(pu.cs);
+        DistParam distParamSadCb, distParamSatdCb;
+        DistParam distParamSadCr, distParamSatdCr;
+
+        m_pcRdCost->setDistParam(distParamSadCb, cs.getOrgBuf(pu.Cb()), chromaMrlStorage[0].Cb(), pu.cs->sps->getBitDepth(CHANNEL_TYPE_CHROMA), COMPONENT_Cb, false);
+        m_pcRdCost->setDistParam(distParamSatdCb, cs.getOrgBuf(pu.Cb()), chromaMrlStorage[0].Cb(), pu.cs->sps->getBitDepth(CHANNEL_TYPE_CHROMA), COMPONENT_Cb, true);
+        m_pcRdCost->setDistParam(distParamSadCr, cs.getOrgBuf(pu.Cr()), chromaMrlStorage[0].Cr(), pu.cs->sps->getBitDepth(CHANNEL_TYPE_CHROMA), COMPONENT_Cr, false);
+        m_pcRdCost->setDistParam(distParamSatdCr, cs.getOrgBuf(pu.Cr()), chromaMrlStorage[0].Cr(), pu.cs->sps->getBitDepth(CHANNEL_TYPE_CHROMA), COMPONENT_Cr, true);
+
+        distParamSadCb.applyWeight = false;
+        distParamSatdCb.applyWeight = false;
+        distParamSadCr.applyWeight = false;
+        distParamSatdCr.applyWeight = false;
+
+        for (auto multiRefIdx : CHROMA_MULTI_REF_LINE_IDX)
+        {
+          pu.chromaMrlIdx = multiRefIdx;
+          initIntraPatternChType(cu, pu.Cb());
+          initIntraPatternChType(cu, pu.Cr());
+
+          for (auto i = 0; i < CHROMA_TMRL_LIST_SIZE; i++)
+          {
+            if (m_chromaTmrlList[i].multiRefIdx != multiRefIdx)
+            {
+              continue;
+            }
+            pu.intraDir[1] = m_chromaTmrlList[i].intraDir;
+            pu.chromaTmrlIdx = i;
+            initPredIntraParams(pu, pu.Cb(), *pu.cs->sps);
+            predIntraAng(COMPONENT_Cb, chromaMrlStorage[i].Cb(), pu);
+            initPredIntraParams(pu, pu.Cr(), *pu.cs->sps);
+            predIntraAng(COMPONENT_Cr, chromaMrlStorage[i].Cr(), pu);
+
+            distParamSadCb.cur = chromaMrlStorage[i].Cb();
+            distParamSatdCb.cur = chromaMrlStorage[i].Cb();
+            distParamSadCr.cur = chromaMrlStorage[i].Cr();
+            distParamSatdCr.cur = chromaMrlStorage[i].Cr();
+
+            sadCb = distParamSadCb.distFunc(distParamSadCb) * 2;
+            satdCb = distParamSatdCb.distFunc(distParamSatdCb);
+            sad = std::min(sadCb, satdCb);
+            sadCr = distParamSadCr.distFunc(distParamSadCr) * 2;
+            satdCr = distParamSatdCr.distFunc(distParamSatdCr);
+            sad += std::min(sadCr, satdCr);
+
+            m_CABACEstimator->getCtx() = ctxStart;
+            m_CABACEstimator->resetBits();
+            m_CABACEstimator->intraChromaTmrl(pu);
+            uint64_t estbits = m_CABACEstimator->getEstFracBits();
+            double   curCost = (double)sad + sqrtLambdaForFirstPass * (double)estbits;
+
+            mrlMap[i][0] = pu.intraDir[1];
+            mrlMap[i][1] = pu.chromaMrlIdx;
+            satdChromaMrlCost[i] = curCost;
+            mrlIsEnable[i] = true;
+          }
+        }
+
+        for (int i = 0; i < 1; i++)
+        {
+          for (int j = i + 1; j < CHROMA_TMRL_LIST_SIZE; j++)
+          {
+            if (satdChromaMrlCost[j] < satdChromaMrlCost[i])
+            {
+              std::swap(satdChromaMrlList[i], satdChromaMrlList[j]);
+              std::swap(satdChromaMrlCost[i], satdChromaMrlCost[j]);
+            }
+          }
+        }
+
+        for (int i = 1; i < CHROMA_TMRL_LIST_SIZE; i++)
+        {
+          mrlIsEnable[satdChromaMrlList[i]] = false;
+        }
+        for (int32_t lstIdx = 0; lstIdx < 1; lstIdx++)
+        {
+          int iModedx = satdChromaMrlList[lstIdx];
+          pu.chromaTmrlIdx = iModedx;
+          if (!mrlIsEnable[iModedx])
+          {
+            break;
+          }
+          int chromaIntraMode = mrlMap[iModedx][0];
+          pu.chromaMrlIdx = mrlMap[iModedx][1];
+          cs.setDecomp(pu.Cb(), false);
+          cs.dist = baseDist;
+          //----- restore context models -----
+          m_CABACEstimator->getCtx() = ctxStart;
+
+          //----- chroma coding -----
+          pu.intraDir[1] = chromaIntraMode;
+          xRecurIntraChromaCodingQT(cs, partitioner, bestCostSoFar, ispType
+            , chromaMrlStorage[iModedx]
+#if JVET_AD0208_IBC_ADAPT_FOR_CAM_CAPTURED_CONTENTS
+            , pcInterPred
+#endif
+          );
+          if (lumaUsesISP && cs.dist == MAX_UINT)
+          {
+            continue;
+          }
+
+          if (cs.sps->getTransformSkipEnabledFlag())
+          {
+            m_CABACEstimator->getCtx() = ctxStart;
+          }
+
+          uint64_t fracBits = xGetIntraFracBitsQT(cs, partitioner, false, true, -1, ispType);
+          Distortion uiDist = cs.dist;
+          double    dCost = m_pcRdCost->calcRdCost(fracBits, uiDist - baseDist);
+
+          //----- compare -----
+          if (dCost < dBestCost)
+          {
+#if JVET_AG0154_DECODER_DERIVED_CCP_FUSION
+            if (uiDist < bestDist)
+            {
+              bestDist = uiDist;
+            }
+#endif
+            if (lumaUsesISP && dCost < bestCostSoFar)
+            {
+              bestCostSoFar = dCost;
+            }
+            for (uint32_t i = getFirstComponentOfChannel(CHANNEL_TYPE_CHROMA); i < numberValidComponents; i++)
+            {
+              const CompArea& area = pu.blocks[i];
+
+              saveCS.getRecoBuf(area).copyFrom(cs.getRecoBuf(area));
+#if KEEP_PRED_AND_RESI_SIGNALS
+              saveCS.getPredBuf(area).copyFrom(cs.getPredBuf(area));
+              saveCS.getResiBuf(area).copyFrom(cs.getResiBuf(area));
+#endif
+              saveCS.getPredBuf(area).copyFrom(cs.getPredBuf(area));
+              cs.picture->getPredBuf(area).copyFrom(cs.getPredBuf(area));
+#if JVET_Z0118_GDR
+              cs.updateReconMotIPM(area);
+#else
+              cs.picture->getRecoBuf(area).copyFrom(cs.getRecoBuf(area));
+#endif
+
+              for (uint32_t j = 0; j < saveCS.tus.size(); j++)
+              {
+                saveCS.tus[j]->copyComponentFrom(*orgTUs[j], area.compID);
+              }
+            }
+
+            dBestCost = dCost;
+            uiBestDist = uiDist;
+            uiBestMode = chromaIntraMode;
+            bestBDPCMMode = cu.bdpcmModeChroma;
+#if JVET_Z0050_DIMD_CHROMA_FUSION
+            isChromaFusion = pu.isChromaFusion;
+#endif
+            bestChromaTmrlFlag = pu.chromaTmrlFlag;
+            bestChromaTmrlIdx = pu.chromaTmrlIdx;
+#if JVET_AD0188_CCP_MERGE
+            if (isChromaFusion == 1)
+            {
+              ccpModelBest = pu.curCand;
+            }
+            else
+            {
+              ccpModelBest.type = CCP_TYPE_NONE;
+            }
+#endif
+          }
+        }
+        pu.chromaMrlIdx = 0;
+        pu.chromaTmrlFlag = false;
+        pu.chromaTmrlIdx = 0;
+        initPredIntraParams(pu, pu.Cb(), *pu.cs->sps);
+        Pel* refBufUnfiltered = m_refBuffer[COMPONENT_Cb][PRED_BUF_UNFILTERED];
+        xFillReferenceSamples(cs.picture->getRecoBuf(cu.Cb()), refBufUnfiltered, cu.Cb(), cu);
+        initPredIntraParams(pu, pu.Cr(), *pu.cs->sps);
+        refBufUnfiltered = m_refBuffer[COMPONENT_Cr][PRED_BUF_UNFILTERED];
+        xFillReferenceSamples(cs.picture->getRecoBuf(cu.Cr()), refBufUnfiltered, cu.Cr(), cu);
+      }
+
+#endif
+
 #if JVET_Z0050_CCLM_SLOPE
 #if MMLM
       for (int32_t uiMode = 0; uiMode < 2; uiMode++)
@@ -6132,6 +6360,10 @@ void IntraSearch::estIntraPredChromaQT( CodingUnit &cu, Partitioner &partitioner
 #if JVET_Z0050_DIMD_CHROMA_FUSION
               isChromaFusion  = pu.isChromaFusion;
 #endif
+#if JVET_AJ0081_CHROMA_TMRL
+              bestChromaTmrlFlag = pu.chromaTmrlFlag;
+              bestChromaTmrlIdx = pu.chromaTmrlIdx;
+#endif
 #if JVET_AA0126_GLM
               bestGlmIdc      = pu.glmIdc;
 #endif
@@ -6431,6 +6663,10 @@ void IntraSearch::estIntraPredChromaQT( CodingUnit &cu, Partitioner &partitioner
 #if JVET_Z0050_DIMD_CHROMA_FUSION
             isChromaFusion  = pu.isChromaFusion;
 #endif
+#if JVET_AJ0081_CHROMA_TMRL
+            bestChromaTmrlFlag = pu.chromaTmrlFlag;
+            bestChromaTmrlIdx = pu.chromaTmrlIdx;
+#endif
 #if JVET_Z0050_CCLM_SLOPE
             bestCclmOffsets = pu.cclmOffsets;
 #endif
@@ -6632,6 +6868,10 @@ void IntraSearch::estIntraPredChromaQT( CodingUnit &cu, Partitioner &partitioner
 #if JVET_Z0050_DIMD_CHROMA_FUSION
           isChromaFusion = pu.isChromaFusion;
 #endif
+#if JVET_AJ0081_CHROMA_TMRL
+          bestChromaTmrlFlag = pu.chromaTmrlFlag;
+          bestChromaTmrlIdx = pu.chromaTmrlIdx;
+#endif
 #if JVET_Z0050_CCLM_SLOPE
           bestCclmOffsets = pu.cclmOffsets;
 #endif
@@ -7022,6 +7262,10 @@ void IntraSearch::estIntraPredChromaQT( CodingUnit &cu, Partitioner &partitioner
 #if JVET_Z0050_DIMD_CHROMA_FUSION
             isChromaFusion = pu.isChromaFusion;
 #endif
+#if JVET_AJ0081_CHROMA_TMRL
+            bestChromaTmrlFlag = pu.chromaTmrlFlag;
+            bestChromaTmrlIdx = pu.chromaTmrlIdx;
+#endif
 #if JVET_Z0050_CCLM_SLOPE
             bestCclmOffsets = pu.cclmOffsets;
 #endif
@@ -7129,6 +7373,10 @@ void IntraSearch::estIntraPredChromaQT( CodingUnit &cu, Partitioner &partitioner
 #if JVET_Z0050_DIMD_CHROMA_FUSION
               isChromaFusion = pu.isChromaFusion;
 #endif
+#if JVET_AJ0081_CHROMA_TMRL
+              bestChromaTmrlFlag = pu.chromaTmrlFlag;
+              bestChromaTmrlIdx = pu.chromaTmrlIdx;
+#endif
 #if JVET_Z0050_CCLM_SLOPE
               bestCclmOffsets = pu.cclmOffsets;
 #endif
@@ -7260,6 +7508,10 @@ void IntraSearch::estIntraPredChromaQT( CodingUnit &cu, Partitioner &partitioner
 #if JVET_Z0050_DIMD_CHROMA_FUSION
               isChromaFusion = pu.isChromaFusion;
 #endif
+#if JVET_AJ0081_CHROMA_TMRL
+              bestChromaTmrlFlag = pu.chromaTmrlFlag;
+              bestChromaTmrlIdx = pu.chromaTmrlIdx;
+#endif
 #if JVET_Z0050_CCLM_SLOPE
               bestCclmOffsets = pu.cclmOffsets;
 #endif
@@ -7385,6 +7637,10 @@ void IntraSearch::estIntraPredChromaQT( CodingUnit &cu, Partitioner &partitioner
             bestBDPCMMode = cu.bdpcmModeChroma;
 #if JVET_Z0050_DIMD_CHROMA_FUSION
             isChromaFusion = 0;
+#endif
+#if JVET_AJ0081_CHROMA_TMRL
+            bestChromaTmrlFlag = false;
+            bestChromaTmrlIdx = 0;
 #endif
             decoderDerivedCcpModeBest = pu.decoderDerivedCcpMode;
 #if JVET_AA0057_CCCM
@@ -7491,6 +7747,10 @@ void IntraSearch::estIntraPredChromaQT( CodingUnit &cu, Partitioner &partitioner
 #if JVET_Z0050_DIMD_CHROMA_FUSION
     pu.isChromaFusion = isChromaFusion;
 #endif
+#if JVET_AJ0081_CHROMA_TMRL
+    pu.chromaTmrlFlag = bestChromaTmrlFlag;
+    pu.chromaTmrlIdx = bestChromaTmrlIdx;
+#endif
 #if JVET_AA0126_GLM
     pu.glmIdc          = bestGlmIdc;
 #endif
@@ -12621,6 +12881,15 @@ ChromaCbfs IntraSearch::xRecurIntraChromaCodingQT( CodingStructure &cs, Partitio
     }
     else
     {
+#if JVET_AJ0081_CHROMA_TMRL
+      if (pu.cs->slice->isIntra() && pu.chromaTmrlFlag && !predStorage.bufs.empty())
+      {
+        piPredCb.copyFrom(predStorage.Cb());
+        piPredCr.copyFrom(predStorage.Cr());
+      }
+      else
+      {
+#endif
 #if JVET_AC0119_LM_CHROMA_FUSION
       if (pu.cs->slice->isIntra() && pu.isChromaFusion && !predStorage.bufs.empty())
       {
@@ -12680,6 +12949,9 @@ ChromaCbfs IntraSearch::xRecurIntraChromaCodingQT( CodingStructure &cs, Partitio
 #endif
 #if JVET_AC0119_LM_CHROMA_FUSION
       }
+#endif
+#if JVET_AJ0081_CHROMA_TMRL
+      }
 #endif
       }
 
diff --git a/source/Lib/EncoderLib/IntraSearch.h b/source/Lib/EncoderLib/IntraSearch.h
index bee4cf923..731b16b60 100644
--- a/source/Lib/EncoderLib/IntraSearch.h
+++ b/source/Lib/EncoderLib/IntraSearch.h
@@ -674,6 +674,10 @@ private:
   PelStorage      m_fusionStorage[6];
 #endif
 
+#if JVET_AJ0081_CHROMA_TMRL
+  PelStorage     m_chromaMrlStorage[CHROMA_TMRL_LIST_SIZE];
+#endif
+
 #if JVET_AD0120_LBCCP
   PelStorage      m_lmPredFiltStorage[LBCCP_FILTER_MMLMNUM];
   struct lmPredFiltModeInfo
-- 
GitLab