From 4076fc2fbe1c60d8b7b027ec94f21535e864281f Mon Sep 17 00:00:00 2001
From: Philippe de Lagrange <philippe.delagrange@technicolor.com>
Date: Thu, 28 Feb 2019 17:46:50 +0100
Subject: [PATCH] JVET-M0113/M0188 quantization groups based on area

---
 cfg/encoder_intra_vtm.cfg                |  2 +-
 cfg/encoder_lowdelay_P_vtm.cfg           |  2 +-
 cfg/encoder_lowdelay_vtm.cfg             |  2 +-
 cfg/encoder_randomaccess_vtm.cfg         |  2 +-
 source/App/EncoderApp/EncApp.cpp         |  5 ++
 source/App/EncoderApp/EncAppCfg.cpp      | 21 ++++++++
 source/App/EncoderApp/EncAppCfg.h        |  5 ++
 source/Lib/CommonLib/ContextModelling.h  |  9 ++++
 source/Lib/CommonLib/Slice.cpp           |  8 +++
 source/Lib/CommonLib/Slice.h             | 20 +++++++-
 source/Lib/CommonLib/TypeDef.h           |  1 +
 source/Lib/CommonLib/UnitPartitioner.cpp | 48 ++++++++++++++++++
 source/Lib/CommonLib/UnitPartitioner.h   | 11 ++++
 source/Lib/CommonLib/UnitTools.cpp       |  2 +
 source/Lib/CommonLib/UnitTools.h         |  2 +
 source/Lib/DecoderLib/CABACReader.cpp    | 25 ++++++++-
 source/Lib/DecoderLib/VLCReader.cpp      | 17 +++++++
 source/Lib/EncoderLib/CABACWriter.cpp    | 24 +++++++++
 source/Lib/EncoderLib/EncCfg.h           | 15 ++++++
 source/Lib/EncoderLib/EncCu.cpp          | 42 +++++++++++++++-
 source/Lib/EncoderLib/EncLib.cpp         | 34 +++++++++++++
 source/Lib/EncoderLib/EncModeCtrl.cpp    | 64 ++++++++++++++++++++++++
 source/Lib/EncoderLib/EncModeCtrl.h      |  4 ++
 source/Lib/EncoderLib/VLCWriter.cpp      |  8 +++
 24 files changed, 365 insertions(+), 8 deletions(-)

diff --git a/cfg/encoder_intra_vtm.cfg b/cfg/encoder_intra_vtm.cfg
index 18ae597ec..80c2ee847 100644
--- a/cfg/encoder_intra_vtm.cfg
+++ b/cfg/encoder_intra_vtm.cfg
@@ -26,7 +26,7 @@ FDM                           : 1           # Fast Decision for Merge RD cost
 #======== Quantization =============
 QP                            : 32          # Quantization parameter(0-51)
 MaxDeltaQP                    : 0           # CU-based multi-QP optimization
-MaxCuDQPDepth                 : 0           # Max depth of a minimum CuDQP for sub-LCU-level delta QP
+MaxCuDQPSubdiv                : 0           # Maximum subdiv for CU luma Qp adjustment
 DeltaQpRD                     : 0           # Slice-based multi-QP optimization
 RDOQ                          : 1           # RDOQ
 RDOQTS                        : 1           # RDOQ for transform skip
diff --git a/cfg/encoder_lowdelay_P_vtm.cfg b/cfg/encoder_lowdelay_P_vtm.cfg
index 7256c3b0d..6d345e4c7 100644
--- a/cfg/encoder_lowdelay_P_vtm.cfg
+++ b/cfg/encoder_lowdelay_P_vtm.cfg
@@ -34,7 +34,7 @@ FDM                           : 1           # Fast Decision for Merge RD cost
 #======== Quantization =============
 QP                            : 32          # Quantization parameter(0-51)
 MaxDeltaQP                    : 0           # CU-based multi-QP optimization
-MaxCuDQPDepth                 : 0           # Max depth of a minimum CuDQP for sub-LCU-level delta QP
+MaxCuDQPSubdiv                : 0           # Maximum subdiv for CU luma Qp adjustment
 DeltaQpRD                     : 0           # Slice-based multi-QP optimization
 RDOQ                          : 1           # RDOQ
 RDOQTS                        : 1           # RDOQ for transform skip
diff --git a/cfg/encoder_lowdelay_vtm.cfg b/cfg/encoder_lowdelay_vtm.cfg
index dc4902e24..07233c189 100644
--- a/cfg/encoder_lowdelay_vtm.cfg
+++ b/cfg/encoder_lowdelay_vtm.cfg
@@ -34,7 +34,7 @@ FDM                           : 1           # Fast Decision for Merge RD cost
 #======== Quantization =============
 QP                            : 32          # Quantization parameter(0-51)
 MaxDeltaQP                    : 0           # CU-based multi-QP optimization
-MaxCuDQPDepth                 : 0           # Max depth of a minimum CuDQP for sub-LCU-level delta QP
+MaxCuDQPSubdiv                : 0           # Maximum subdiv for CU luma Qp adjustment
 DeltaQpRD                     : 0           # Slice-based multi-QP optimization
 RDOQ                          : 1           # RDOQ
 RDOQTS                        : 1           # RDOQ for transform skip
diff --git a/cfg/encoder_randomaccess_vtm.cfg b/cfg/encoder_randomaccess_vtm.cfg
index 763c0ef98..389209a4c 100644
--- a/cfg/encoder_randomaccess_vtm.cfg
+++ b/cfg/encoder_randomaccess_vtm.cfg
@@ -48,7 +48,7 @@ FDM                           : 1           # Fast Decision for Merge RD cost
 #======== Quantization =============
 QP                            : 32          # Quantization parameter(0-51)
 MaxDeltaQP                    : 0           # CU-based multi-QP optimization
-MaxCuDQPDepth                 : 0           # Max depth of a minimum CuDQP for sub-LCU-level delta QP
+MaxCuDQPSubdiv                : 0           # Maximum subdiv for CU luma Qp adjustment
 DeltaQpRD                     : 0           # Slice-based multi-QP optimization
 RDOQ                          : 1           # RDOQ
 RDOQTS                        : 1           # RDOQ for transform skip
diff --git a/source/App/EncoderApp/EncApp.cpp b/source/App/EncoderApp/EncApp.cpp
index c0f2a3c5e..d54af7c56 100644
--- a/source/App/EncoderApp/EncApp.cpp
+++ b/source/App/EncoderApp/EncApp.cpp
@@ -216,8 +216,13 @@ void EncApp::xInitLibCfg()
 
   //====== Quality control ========
   m_cEncLib.setMaxDeltaQP                                        ( m_iMaxDeltaQP  );
+#if JVET_M0113_M0188_QG_SIZE
+  m_cEncLib.setCuQpDeltaSubdiv                                   ( m_cuQpDeltaSubdiv );
+  m_cEncLib.setCuChromaQpOffsetSubdiv                            ( m_cuChromaQpOffsetSubdiv );
+#else
   m_cEncLib.setMaxCuDQPDepth                                     ( m_iMaxCuDQPDepth  );
   m_cEncLib.setDiffCuChromaQpOffsetDepth                         ( m_diffCuChromaQpOffsetDepth );
+#endif
   m_cEncLib.setChromaCbQpOffset                                  ( m_cbQpOffset     );
   m_cEncLib.setChromaCrQpOffset                                  ( m_crQpOffset  );
   m_cEncLib.setChromaCbQpOffsetDualTree                          ( m_cbQpOffsetDualTree );
diff --git a/source/App/EncoderApp/EncAppCfg.cpp b/source/App/EncoderApp/EncAppCfg.cpp
index 4121873d6..2d44db673 100644
--- a/source/App/EncoderApp/EncAppCfg.cpp
+++ b/source/App/EncoderApp/EncAppCfg.cpp
@@ -997,8 +997,13 @@ bool EncAppCfg::parseCfg( int argc, char* argv[] )
 #endif
   ("DeltaQpRD,-dqr",                                  m_uiDeltaQpRD,                                       0u, "max dQp offset for slice")
   ("MaxDeltaQP,d",                                    m_iMaxDeltaQP,                                        0, "max dQp offset for block")
+#if JVET_M0113_M0188_QG_SIZE
+  ("MaxCuDQPSubdiv,-dqd",                             m_cuQpDeltaSubdiv,                                    0, "Maximum subdiv for CU luma Qp adjustment")
+  ("MaxCuChromaQpOffsetSubdiv",                       m_cuChromaQpOffsetSubdiv,                            -1, "Maximum subdiv for CU chroma Qp adjustment - set less than 0 to disable")
+#else
   ("MaxCuDQPDepth,-dqd",                              m_iMaxCuDQPDepth,                                     0, "max depth for a minimum CuDQP")
   ("MaxCUChromaQpAdjustmentDepth",                    m_diffCuChromaQpOffsetDepth,                         -1, "Maximum depth for CU chroma Qp adjustment - set less than 0 to disable")
+#endif
   ("FastDeltaQP",                                     m_bFastDeltaQP,                                   false, "Fast Delta QP Algorithm")
 #if SHARP_LUMA_DELTA_QP
   ("LumaLevelToDeltaQPMode",                          lumaLevelToDeltaQPMode,                              0u, "Luma based Delta QP 0(default): not used. 1: Based on CTU average, 2: Based on Max luma in CTU")
@@ -1573,7 +1578,11 @@ bool EncAppCfg::parseCfg( int argc, char* argv[] )
                                            !m_enableIntraReferenceSmoothing          ||
                                            m_persistentRiceAdaptationEnabledFlag     ||
                                            m_log2MaxTransformSkipBlockSize!=2;
+#if JVET_M0113_M0188_QG_SIZE
+      const bool bUsingChromaQPAdjustment= m_cuChromaQpOffsetSubdiv >= 0;
+#else
       const bool bUsingChromaQPAdjustment= m_diffCuChromaQpOffsetDepth >= 0;
+#endif
       const bool bUsingExtendedPrecision = m_extendedPrecisionProcessingFlag;
       if (m_onePictureOnlyConstraintFlag)
       {
@@ -2121,7 +2130,11 @@ bool EncAppCfg::xCheckParameter()
                                            !m_enableIntraReferenceSmoothing          ||
                                            m_persistentRiceAdaptationEnabledFlag     ||
                                            m_log2MaxTransformSkipBlockSize!=2;
+#if JVET_M0113_M0188_QG_SIZE
+      const bool bUsingChromaQPTool      = m_cuChromaQpOffsetSubdiv >= 0;
+#else
       const bool bUsingChromaQPTool      = m_diffCuChromaQpOffsetDepth >= 0;
+#endif
       const bool bUsingExtendedPrecision = m_extendedPrecisionProcessingFlag;
 
       xConfirmPara((m_chromaFormatConstraint==CHROMA_420 || m_chromaFormatConstraint==CHROMA_400) && bUsingChromaQPTool, "CU Chroma QP adjustment cannot be used for 4:0:0 or 4:2:0 RExt profiles");
@@ -3088,7 +3101,11 @@ void EncAppCfg::xPrintParameter()
 #else
   msg( DETAILS, "QP                                     : %5.2f\n", m_fQP );
 #endif
+#if JVET_M0113_M0188_QG_SIZE
+  msg( DETAILS, "Max dQP signaling subdiv               : %d\n", m_cuQpDeltaSubdiv);
+#else
   msg( DETAILS, "Max dQP signaling depth                : %d\n", m_iMaxCuDQPDepth);
+#endif
 
   msg( DETAILS, "Cb QP Offset (dual tree)               : %d (%d)\n", m_cbQpOffset, m_cbQpOffsetDualTree);
   msg( DETAILS, "Cr QP Offset (dual tree)               : %d (%d)\n", m_crQpOffset, m_crQpOffsetDualTree);
@@ -3100,7 +3117,11 @@ void EncAppCfg::xPrintParameter()
   msg( DETAILS, "PCM sample bit depth                   : (Y:%d, C:%d)\n", m_bPCMInputBitDepthFlag ? m_MSBExtendedBitDepth[CHANNEL_TYPE_LUMA] : m_internalBitDepth[CHANNEL_TYPE_LUMA],
                                                                     m_bPCMInputBitDepthFlag ? m_MSBExtendedBitDepth[CHANNEL_TYPE_CHROMA] : m_internalBitDepth[CHANNEL_TYPE_CHROMA] );
   msg( DETAILS, "Intra reference smoothing              : %s\n", (m_enableIntraReferenceSmoothing           ? "Enabled" : "Disabled") );
+#if JVET_M0113_M0188_QG_SIZE
+  msg( DETAILS, "cu_chroma_qp_offset_subdiv             : %d\n", m_cuChromaQpOffsetSubdiv);
+#else
   msg( DETAILS, "diff_cu_chroma_qp_offset_depth         : %d\n", m_diffCuChromaQpOffsetDepth);
+#endif
   msg( DETAILS, "extended_precision_processing_flag     : %s\n", (m_extendedPrecisionProcessingFlag         ? "Enabled" : "Disabled") );
   msg( DETAILS, "implicit_rdpcm_enabled_flag            : %s\n", (m_rdpcmEnabledFlag[RDPCM_SIGNAL_IMPLICIT] ? "Enabled" : "Disabled") );
   msg( DETAILS, "explicit_rdpcm_enabled_flag            : %s\n", (m_rdpcmEnabledFlag[RDPCM_SIGNAL_EXPLICIT] ? "Enabled" : "Disabled") );
diff --git a/source/App/EncoderApp/EncAppCfg.h b/source/App/EncoderApp/EncAppCfg.h
index da31485c4..f819cc1ba 100644
--- a/source/App/EncoderApp/EncAppCfg.h
+++ b/source/App/EncoderApp/EncAppCfg.h
@@ -205,8 +205,13 @@ protected:
   int*      m_aidQP;                                          ///< array of slice QP values
   int       m_iMaxDeltaQP;                                    ///< max. |delta QP|
   uint32_t      m_uiDeltaQpRD;                                    ///< dQP range for multi-pass slice QP optimization
+#if JVET_M0113_M0188_QG_SIZE
+  int       m_cuQpDeltaSubdiv;                                ///< Maximum subdiv for CU luma Qp adjustment (0:default)
+  int       m_cuChromaQpOffsetSubdiv;                         ///< If negative, then do not apply chroma qp offsets.
+#else
   int       m_iMaxCuDQPDepth;                                 ///< Max. depth for a minimum CuDQPSize (0:default)
   int       m_diffCuChromaQpOffsetDepth;                      ///< If negative, then do not apply chroma qp offsets.
+#endif
   bool      m_bFastDeltaQP;                                   ///< Fast Delta QP (false:default)
 
   int       m_cbQpOffset;                                     ///< Chroma Cb QP Offset (0:default)
diff --git a/source/Lib/CommonLib/ContextModelling.h b/source/Lib/CommonLib/ContextModelling.h
index c93dc780c..869a7e0b0 100644
--- a/source/Lib/CommonLib/ContextModelling.h
+++ b/source/Lib/CommonLib/ContextModelling.h
@@ -249,13 +249,22 @@ class CUCtx
 {
 public:
   CUCtx()              : isDQPCoded(false), isChromaQpAdjCoded(false),
+#if JVET_M0113_M0188_QG_SIZE
+                         qgStart(false),
+#endif
                          numNonZeroCoeffNonTs(0) {}
   CUCtx(int _qp)       : isDQPCoded(false), isChromaQpAdjCoded(false),
+#if JVET_M0113_M0188_QG_SIZE
+                         qgStart(false),
+#endif
                          numNonZeroCoeffNonTs(0), qp(_qp) {}
   ~CUCtx() {}
 public:
   bool      isDQPCoded;
   bool      isChromaQpAdjCoded;
+#if JVET_M0113_M0188_QG_SIZE
+  bool      qgStart;
+#endif
   uint32_t      numNonZeroCoeffNonTs;
   int8_t     qp;                   // used as a previous(last) QP and for QP prediction
 };
diff --git a/source/Lib/CommonLib/Slice.cpp b/source/Lib/CommonLib/Slice.cpp
index ec8f544c1..6c4afd219 100644
--- a/source/Lib/CommonLib/Slice.cpp
+++ b/source/Lib/CommonLib/Slice.cpp
@@ -1879,7 +1879,11 @@ const int SPS::m_winUnitY[]={1,2,1,1};
 PPSRExt::PPSRExt()
 : m_log2MaxTransformSkipBlockSize      (2)
 , m_crossComponentPredictionEnabledFlag(false)
+#if JVET_M0113_M0188_QG_SIZE
+, m_cuChromaQpOffsetSubdiv             (0)
+#else
 , m_diffCuChromaQpOffsetDepth          (0)
+#endif
 , m_chromaQpOffsetListLen              (0)
 // m_ChromaQpAdjTableIncludingNullEntry initialized below
 // m_log2SaoOffsetScale initialized below
@@ -1899,7 +1903,11 @@ PPS::PPS()
 , m_useDQP                           (false)
 , m_bConstrainedIntraPred            (false)
 , m_bSliceChromaQpFlag               (false)
+#if JVET_M0113_M0188_QG_SIZE
+, m_cuQpDeltaSubdiv                  (0)
+#else
 , m_uiMaxCuDQPDepth                  (0)
+#endif
 , m_chromaCbQpOffset                 (0)
 , m_chromaCrQpOffset                 (0)
 , m_numRefIdxL0DefaultActive         (1)
diff --git a/source/Lib/CommonLib/Slice.h b/source/Lib/CommonLib/Slice.h
index aed91d301..69d1c85cd 100644
--- a/source/Lib/CommonLib/Slice.h
+++ b/source/Lib/CommonLib/Slice.h
@@ -1330,7 +1330,11 @@ private:
   bool             m_crossComponentPredictionEnabledFlag;
 
   // Chroma QP Adjustments
+#if JVET_M0113_M0188_QG_SIZE
+  int              m_cuChromaQpOffsetSubdiv;
+#else
   int              m_diffCuChromaQpOffsetDepth;
+#endif
   int              m_chromaQpOffsetListLen; // size (excludes the null entry used in the following array).
   ChromaQpAdj      m_ChromaQpAdjTableIncludingNullEntry[1+MAX_QP_OFFSET_LIST_SIZE]; //!< Array includes entry [0] for the null offset used when cu_chroma_qp_offset_flag=0, and entries [cu_chroma_qp_offset_idx+1...] otherwise
 
@@ -1356,8 +1360,13 @@ public:
 
   void                   clearChromaQpOffsetList()                                        { m_chromaQpOffsetListLen = 0;                    }
 
+#if JVET_M0113_M0188_QG_SIZE
+  uint32_t               getCuChromaQpOffsetSubdiv () const                               { return m_cuChromaQpOffsetSubdiv;                }
+  void                   setCuChromaQpOffsetSubdiv ( uint32_t u )                         { m_cuChromaQpOffsetSubdiv = u;                   }
+#else
   uint32_t                   getDiffCuChromaQpOffsetDepth () const                            { return m_diffCuChromaQpOffsetDepth;             }
   void                   setDiffCuChromaQpOffsetDepth ( uint32_t u )                          { m_diffCuChromaQpOffsetDepth = u;                }
+#endif
 
   bool                   getChromaQpOffsetListEnabledFlag() const                         { return getChromaQpOffsetListLen()>0;            }
   int                    getChromaQpOffsetListLen() const                                 { return m_chromaQpOffsetListLen;                 }
@@ -1395,7 +1404,11 @@ private:
   bool             m_bSliceChromaQpFlag;       // slicelevel_chroma_qp_flag
 
   // access channel
-  uint32_t             m_uiMaxCuDQPDepth;
+#if JVET_M0113_M0188_QG_SIZE
+  uint32_t         m_cuQpDeltaSubdiv;           // cu_qp_delta_subdiv
+#else
+  uint32_t         m_uiMaxCuDQPDepth;
+#endif
 
   int              m_chromaCbQpOffset;
   int              m_chromaCrQpOffset;
@@ -1463,8 +1476,13 @@ public:
   bool                   getSliceChromaQpFlag() const                                     { return  m_bSliceChromaQpFlag;                 }
   void                   setSliceChromaQpFlag( bool b )                                   { m_bSliceChromaQpFlag = b;                     }
 
+#if JVET_M0113_M0188_QG_SIZE
+  void                   setCuQpDeltaSubdiv( uint32_t u )                                 { m_cuQpDeltaSubdiv = u;                         }
+  uint32_t               getCuQpDeltaSubdiv() const                                       { return m_cuQpDeltaSubdiv;                      }
+#else
   void                   setMaxCuDQPDepth( uint32_t u )                                       { m_uiMaxCuDQPDepth = u;                        }
   uint32_t                   getMaxCuDQPDepth() const                                         { return m_uiMaxCuDQPDepth;                     }
+#endif
 
   void                   setQpOffset(ComponentID compID, int i )
   {
diff --git a/source/Lib/CommonLib/TypeDef.h b/source/Lib/CommonLib/TypeDef.h
index e91a15bd6..12c147c7e 100644
--- a/source/Lib/CommonLib/TypeDef.h
+++ b/source/Lib/CommonLib/TypeDef.h
@@ -372,6 +372,7 @@ typedef std::pair<int, int>  TrCost;
 #define ER_CHROMA_QP_WCG_PPS                              1 ///< Chroma QP model for WCG used in Anchor 3.2
 #define ENABLE_QPA                                        1 ///< Non-normative perceptual QP adaptation according to JVET-H0047 and JVET-K0206. Deactivated by default, activated using encoder arguments --PerceptQPA=1 --SliceChromaQPOffsetPeriodicity=1
 #define ENABLE_QPA_SUB_CTU                              ( 1 && ENABLE_QPA ) ///< when maximum delta-QP depth is greater than zero, use sub-CTU QPA
+#define JVET_M0113_M0188_QG_SIZE                          1 ///< JVET-M0113/M0188 quantization groups based on area
 
 
 #define RDOQ_CHROMA                                       1 ///< use of RDOQ in chroma
diff --git a/source/Lib/CommonLib/UnitPartitioner.cpp b/source/Lib/CommonLib/UnitPartitioner.cpp
index 849698ca2..fe5d47a07 100644
--- a/source/Lib/CommonLib/UnitPartitioner.cpp
+++ b/source/Lib/CommonLib/UnitPartitioner.cpp
@@ -53,6 +53,10 @@ PartLevel::PartLevel()
 , implicitSplit       ( CU_DONT_SPLIT )
 , firstSubPartSplit   ( CU_DONT_SPLIT )
 , canQtSplit          ( true          )
+#if JVET_M0113_M0188_QG_SIZE
+, qgEnable            ( true          )
+, qgChromaEnable      ( true          )
+#endif
 {
 }
 
@@ -65,6 +69,10 @@ PartLevel::PartLevel( const PartSplit _split, const Partitioning& _parts )
 , implicitSplit       ( CU_DONT_SPLIT )
 , firstSubPartSplit   ( CU_DONT_SPLIT )
 , canQtSplit          ( true          )
+#if JVET_M0113_M0188_QG_SIZE
+, qgEnable            ( true          )
+, qgChromaEnable      ( true          )
+#endif
 {
 }
 
@@ -77,6 +85,10 @@ PartLevel::PartLevel( const PartSplit _split, Partitioning&& _parts )
 , implicitSplit       ( CU_DONT_SPLIT                        )
 , firstSubPartSplit   ( CU_DONT_SPLIT                        )
 , canQtSplit          ( true                                 )
+#if JVET_M0113_M0188_QG_SIZE
+, qgEnable            ( true                                 )
+, qgChromaEnable      ( true                                 )
+#endif
 {
 }
 
@@ -117,6 +129,9 @@ void Partitioner::copyState( const Partitioner& other )
   currDepth   = other.currDepth;
   currMtDepth = other.currMtDepth;
   currTrDepth = other.currTrDepth;
+#if JVET_M0113_M0188_QG_SIZE
+  currSubdiv  = other.currSubdiv;
+#endif
   currImplicitBtDepth
               = other.currImplicitBtDepth;
   chType      = other.chType;
@@ -224,6 +239,9 @@ void QTBTPartitioner::initCtu( const UnitArea& ctuArea, const ChannelType _chTyp
   currBtDepth = 0;
   currMtDepth = 0;
   currQtDepth = 0;
+#if JVET_M0113_M0188_QG_SIZE
+  currSubdiv  = 0;
+#endif
   currImplicitBtDepth = 0;
   chType      = _chType;
 
@@ -237,6 +255,10 @@ void QTBTPartitioner::splitCurrArea( const PartSplit split, const CodingStructur
 
   bool isImplicit = isSplitImplicit( split, cs );
   bool canQtSplit = canSplit( CU_QUAD_SPLIT, cs );
+#if JVET_M0113_M0188_QG_SIZE
+  bool qgEnable = currQgEnable();
+  bool qgChromaEnable = currQgChromaEnable();
+#endif
 
   switch( split )
   {
@@ -272,6 +294,9 @@ void QTBTPartitioner::splitCurrArea( const PartSplit split, const CodingStructur
   }
 
   currDepth++;
+#if JVET_M0113_M0188_QG_SIZE
+  currSubdiv++;
+#endif
 #if _DEBUG
   m_currArea = m_partStack.back().parts.front();
 #endif
@@ -301,6 +326,9 @@ void QTBTPartitioner::splitCurrArea( const PartSplit split, const CodingStructur
     {
       // first and last part of triple split are equivalent to double bt split
       currBtDepth++;
+#if JVET_M0113_M0188_QG_SIZE
+      currSubdiv++;
+#endif
     }
     m_partStack.back().canQtSplit = canQtSplit;
   }
@@ -311,7 +339,14 @@ void QTBTPartitioner::splitCurrArea( const PartSplit split, const CodingStructur
     currMtDepth = 0;
     currBtDepth = 0;
     currQtDepth++;
+#if JVET_M0113_M0188_QG_SIZE
+    currSubdiv++;
+#endif
   }
+#if JVET_M0113_M0188_QG_SIZE
+  m_partStack.back().qgEnable       = qgEnable       && (currSubdiv <= cs.pps->getCuQpDeltaSubdiv());
+  m_partStack.back().qgChromaEnable = qgChromaEnable && (currSubdiv <= cs.pps->getPpsRangeExtension().getCuChromaQpOffsetSubdiv());
+#endif
 }
 
 #if JVET_M0421_SPLIT_SIG
@@ -630,6 +665,9 @@ void QTBTPartitioner::exitCurrSplit()
 
   CHECK( currDepth == 0, "depth is '0', although a split was performed" );
   currDepth--;
+#if JVET_M0113_M0188_QG_SIZE
+  currSubdiv--;
+#endif
 #if _DEBUG
   m_currArea = m_partStack.back().parts[m_partStack.back().idx];
 #endif
@@ -646,6 +684,9 @@ void QTBTPartitioner::exitCurrSplit()
     {
       CHECK( currBtDepth == 0, "BT depth is '0', athough a TT split was performed" );
       currBtDepth--;
+#if JVET_M0113_M0188_QG_SIZE
+      currSubdiv--;
+#endif
     }
   }
   else if( currSplit == TU_MAX_TR_SPLIT )
@@ -666,6 +707,9 @@ void QTBTPartitioner::exitCurrSplit()
 
     CHECK( currQtDepth == 0, "QT depth is '0', although a QT split was performed" );
     currQtDepth--;
+#if JVET_M0113_M0188_QG_SIZE
+    currSubdiv--;
+#endif
   }
 }
 
@@ -691,6 +735,10 @@ bool QTBTPartitioner::nextPart( const CodingStructure &cs, bool autoPop /*= fals
       // adapt the current bt depth
       if( currIdx == 1 ) currBtDepth--;
       else               currBtDepth++;
+#if JVET_M0113_M0188_QG_SIZE
+      if( currIdx == 1 ) currSubdiv--;
+      else               currSubdiv++;
+#endif
     }
 #if _DEBUG
     m_currArea = m_partStack.back().parts[currIdx];
diff --git a/source/Lib/CommonLib/UnitPartitioner.h b/source/Lib/CommonLib/UnitPartitioner.h
index 3060e8459..b363b7b0c 100644
--- a/source/Lib/CommonLib/UnitPartitioner.h
+++ b/source/Lib/CommonLib/UnitPartitioner.h
@@ -100,6 +100,10 @@ struct PartLevel
   PartSplit    implicitSplit;
   PartSplit    firstSubPartSplit;
   bool         canQtSplit;
+#if JVET_M0113_M0188_QG_SIZE
+  bool         qgEnable;
+  bool         qgChromaEnable;
+#endif
 
   PartLevel();
   PartLevel( const PartSplit _split, const Partitioning&  _parts );
@@ -123,6 +127,9 @@ public:
   unsigned currTrDepth;
   unsigned currBtDepth;
   unsigned currMtDepth;
+#if JVET_M0113_M0188_QG_SIZE
+  unsigned currSubdiv;
+#endif
 
   unsigned currImplicitBtDepth;
   ChannelType chType;
@@ -133,6 +140,10 @@ public:
   const UnitArea&  currArea               () const { return currPartLevel().parts[currPartIdx()]; }
   const unsigned   currPartIdx            () const { return currPartLevel().idx; }
   const PartitioningStack& getPartStack   () const { return m_partStack; }
+#if JVET_M0113_M0188_QG_SIZE
+  const bool currQgEnable                 () const { return currPartLevel().qgEnable; }
+  const bool currQgChromaEnable           () const { return currPartLevel().qgChromaEnable; }
+#endif
 
   SplitSeries getSplitSeries              () const;
 
diff --git a/source/Lib/CommonLib/UnitTools.cpp b/source/Lib/CommonLib/UnitTools.cpp
index 015d752ca..c6828c143 100644
--- a/source/Lib/CommonLib/UnitTools.cpp
+++ b/source/Lib/CommonLib/UnitTools.cpp
@@ -196,6 +196,7 @@ int CU::predictQP( const CodingUnit& cu, const int prevQP )
   return ( a + b + 1 ) >> 1;
 }
 
+#if !JVET_M0113_M0188_QG_SIZE
 bool CU::isQGStart( const CodingUnit& cu, Partitioner& partitioner )
 {
   int maxDqpDepth = cu.slice->getPPS()->getMaxCuDQPDepth();
@@ -211,6 +212,7 @@ bool CU::isQGStart( const CodingUnit& cu, Partitioner& partitioner )
   else
     return true;
 }
+#endif
 
 uint32_t CU::getNumPUs( const CodingUnit& cu )
 {
diff --git a/source/Lib/CommonLib/UnitTools.h b/source/Lib/CommonLib/UnitTools.h
index 06b4eefbd..682bbed54 100644
--- a/source/Lib/CommonLib/UnitTools.h
+++ b/source/Lib/CommonLib/UnitTools.h
@@ -77,7 +77,9 @@ namespace CU
   uint32_t getCtuAddr                     (const CodingUnit &cu);
 
   int  predictQP                      (const CodingUnit& cu, const int prevQP );
+#if !JVET_M0113_M0188_QG_SIZE
   bool isQGStart                      (const CodingUnit& cu, Partitioner& partitioner ); // check if start of a Quantization Group
+#endif
 
   uint32_t getNumPUs                      (const CodingUnit& cu);
   void addPUs                         (      CodingUnit& cu);
diff --git a/source/Lib/DecoderLib/CABACReader.cpp b/source/Lib/DecoderLib/CABACReader.cpp
index 92f87e1fc..903d0412a 100644
--- a/source/Lib/DecoderLib/CABACReader.cpp
+++ b/source/Lib/DecoderLib/CABACReader.cpp
@@ -385,11 +385,20 @@ bool CABACReader::coding_tree( CodingStructure& cs, Partitioner& partitioner, CU
   bool           lastSegment  = false;
 
   // Reset delta QP coding flag and ChromaQPAdjustemt coding flag
+#if JVET_M0113_M0188_QG_SIZE
+  if( pps.getUseDQP() && partitioner.currQgEnable() )
+  {
+    cuCtx.qgStart    = true;
+    cuCtx.isDQPCoded = false;
+  }
+  if( cs.slice->getUseChromaQpAdj() && partitioner.currQgChromaEnable() )
+#else
   if( pps.getUseDQP() && partitioner.currDepth <= pps.getMaxCuDQPDepth() )
   {
     cuCtx.isDQPCoded          = false;
   }
   if( cs.slice->getUseChromaQpAdj() && partitioner.currDepth <= pps.getPpsRangeExtension().getDiffCuChromaQpOffsetDepth() )
+#endif
   {
     cuCtx.isChromaQpAdjCoded  = false;
   }
@@ -397,12 +406,20 @@ bool CABACReader::coding_tree( CodingStructure& cs, Partitioner& partitioner, CU
   // Reset delta QP coding flag and ChromaQPAdjustemt coding flag
   if (CS::isDualITree(cs) && pPartitionerChroma != nullptr)
   {
-
+#if JVET_M0113_M0188_QG_SIZE
+    if (pps.getUseDQP() && pPartitionerChroma->currQgEnable())
+    {
+      pCuCtxChroma->qgStart    = true;
+      pCuCtxChroma->isDQPCoded = false;
+    }
+    if (cs.slice->getUseChromaQpAdj() && pPartitionerChroma->currQgChromaEnable())
+#else
     if (pps.getUseDQP() && pPartitionerChroma->currDepth <= pps.getMaxCuDQPDepth())
     {
       pCuCtxChroma->isDQPCoded = false;
     }
     if (cs.slice->getUseChromaQpAdj() && pPartitionerChroma->currDepth <= pps.getPpsRangeExtension().getDiffCuChromaQpOffsetDepth())
+#endif
     {
       pCuCtxChroma->isChromaQpAdjCoded = false;
     }
@@ -638,8 +655,14 @@ bool CABACReader::coding_tree( CodingStructure& cs, Partitioner& partitioner, CU
 #endif
 
   // Predict QP on start of quantization group
+#if JVET_M0113_M0188_QG_SIZE
+  if( cuCtx.qgStart )
+  {
+    cuCtx.qgStart = false;
+#else
   if( pps.getUseDQP() && !cuCtx.isDQPCoded && CU::isQGStart( cu, partitioner ) )
   {
+#endif
     cuCtx.qp = CU::predictQP( cu, cuCtx.qp );
   }
 
diff --git a/source/Lib/DecoderLib/VLCReader.cpp b/source/Lib/DecoderLib/VLCReader.cpp
index 32afc7e3f..16a26e145 100644
--- a/source/Lib/DecoderLib/VLCReader.cpp
+++ b/source/Lib/DecoderLib/VLCReader.cpp
@@ -413,12 +413,21 @@ void HLSyntaxReader::parsePPS( PPS* pcPPS )
   READ_FLAG( uiCode, "cu_qp_delta_enabled_flag" );            pcPPS->setUseDQP( uiCode ? true : false );
   if( pcPPS->getUseDQP() )
   {
+#if JVET_M0113_M0188_QG_SIZE
+    READ_UVLC( uiCode, "cu_qp_delta_subdiv" );
+    pcPPS->setCuQpDeltaSubdiv( uiCode );
+#else
     READ_UVLC( uiCode, "diff_cu_qp_delta_depth" );
     pcPPS->setMaxCuDQPDepth( uiCode );
+#endif
   }
   else
   {
+#if JVET_M0113_M0188_QG_SIZE
+    pcPPS->setCuQpDeltaSubdiv( 0 );
+#else
     pcPPS->setMaxCuDQPDepth( 0 );
+#endif
   }
   READ_SVLC( iCode, "pps_cb_qp_offset");
   pcPPS->setQpOffset(COMPONENT_Cb, iCode);
@@ -561,11 +570,19 @@ void HLSyntaxReader::parsePPS( PPS* pcPPS )
           if (uiCode == 0)
           {
             ppsRangeExtension.clearChromaQpOffsetList();
+#if JVET_M0113_M0188_QG_SIZE
+            ppsRangeExtension.setCuChromaQpOffsetSubdiv(0);
+#else
             ppsRangeExtension.setDiffCuChromaQpOffsetDepth(0);
+#endif
           }
           else
           {
+#if JVET_M0113_M0188_QG_SIZE
+            READ_UVLC(uiCode, "cu_chroma_qp_offset_subdiv"); ppsRangeExtension.setCuChromaQpOffsetSubdiv(uiCode);
+#else
             READ_UVLC(uiCode, "diff_cu_chroma_qp_offset_depth"); ppsRangeExtension.setDiffCuChromaQpOffsetDepth(uiCode);
+#endif
             uint32_t tableSizeMinus1 = 0;
             READ_UVLC(tableSizeMinus1, "chroma_qp_offset_list_len_minus1");
             CHECK(tableSizeMinus1 >= MAX_QP_OFFSET_LIST_SIZE, "Table size exceeds maximum");
diff --git a/source/Lib/EncoderLib/CABACWriter.cpp b/source/Lib/EncoderLib/CABACWriter.cpp
index 513f6a35a..ce1a899f6 100644
--- a/source/Lib/EncoderLib/CABACWriter.cpp
+++ b/source/Lib/EncoderLib/CABACWriter.cpp
@@ -375,22 +375,40 @@ void CABACWriter::coding_tree(const CodingStructure& cs, Partitioner& partitione
   const CodingUnit &cu        = *cs.getCU( currArea.blocks[partitioner.chType], partitioner.chType );
 
   // Reset delta QP coding flag and ChromaQPAdjustemt coding flag
+#if JVET_M0113_M0188_QG_SIZE
+  if( pps.getUseDQP() && partitioner.currQgEnable() )
+  {
+    cuCtx.qgStart    = true;
+    cuCtx.isDQPCoded          = false;
+  }
+  if( cs.slice->getUseChromaQpAdj() && partitioner.currQgChromaEnable() )
+#else
   if( pps.getUseDQP() && partitioner.currDepth <= pps.getMaxCuDQPDepth() )
   {
     cuCtx.isDQPCoded          = false;
   }
   if( cs.slice->getUseChromaQpAdj() && partitioner.currDepth <= pps.getPpsRangeExtension().getDiffCuChromaQpOffsetDepth() )
+#endif
   {
     cuCtx.isChromaQpAdjCoded  = false;
   }
   // Reset delta QP coding flag and ChromaQPAdjustemt coding flag
   if (CS::isDualITree(cs) && pPartitionerChroma != nullptr)
   {
+#if JVET_M0113_M0188_QG_SIZE
+    if (pps.getUseDQP() && pPartitionerChroma->currQgEnable())
+    {
+      pCuCtxChroma->qgStart    = true;
+      pCuCtxChroma->isDQPCoded = false;
+    }
+    if (cs.slice->getUseChromaQpAdj() && pPartitionerChroma->currQgChromaEnable())
+#else
     if (pps.getUseDQP() && pPartitionerChroma->currDepth <= pps.getMaxCuDQPDepth())
     {
       pCuCtxChroma->isDQPCoded = false;
     }
     if (cs.slice->getUseChromaQpAdj() && pPartitionerChroma->currDepth <= pps.getPpsRangeExtension().getDiffCuChromaQpOffsetDepth())
+#endif
     {
       pCuCtxChroma->isChromaQpAdjCoded = false;
     }
@@ -521,8 +539,14 @@ void CABACWriter::coding_tree(const CodingStructure& cs, Partitioner& partitione
 
 #endif
   // Predict QP on start of quantization group
+#if JVET_M0113_M0188_QG_SIZE
+  if( cuCtx.qgStart )
+  {
+    cuCtx.qgStart = false;
+#else
   if( pps.getUseDQP() && !cuCtx.isDQPCoded && CU::isQGStart( cu, partitioner ) )
   {
+#endif
     cuCtx.qp = CU::predictQP( cu, cuCtx.qp );
   }
 
diff --git a/source/Lib/EncoderLib/EncCfg.h b/source/Lib/EncoderLib/EncCfg.h
index 6e25a19a1..8c85c8ba3 100644
--- a/source/Lib/EncoderLib/EncCfg.h
+++ b/source/Lib/EncoderLib/EncCfg.h
@@ -335,8 +335,13 @@ protected:
 
   //====== Quality control ========
   int       m_iMaxDeltaQP;                      //  Max. absolute delta QP (1:default)
+#if JVET_M0113_M0188_QG_SIZE
+  int       m_cuQpDeltaSubdiv;                  //  Max. subdivision level for a CuDQP (0:default)
+  int       m_cuChromaQpOffsetSubdiv;           ///< If negative, then do not apply chroma qp offsets.
+#else
   int       m_iMaxCuDQPDepth;                   //  Max. depth for a minimum CuDQP (0:default)
   int       m_diffCuChromaQpOffsetDepth;        ///< If negative, then do not apply chroma qp offsets.
+#endif
 
   int       m_chromaCbQpOffset;                 //  Chroma Cb QP Offset (0:default)
   int       m_chromaCrQpOffset;                 //  Chroma Cr Qp Offset (0:default)
@@ -923,10 +928,16 @@ public:
 
   //====== Quality control ========
   void      setMaxDeltaQP                   ( int   i )      { m_iMaxDeltaQP = i; }
+#if JVET_M0113_M0188_QG_SIZE
+  void      setCuQpDeltaSubdiv              ( int   i )      { m_cuQpDeltaSubdiv = i; }
+  int       getCuChromaQpOffsetSubdiv       ()         const { return m_cuChromaQpOffsetSubdiv;  }
+  void      setCuChromaQpOffsetSubdiv       (int value)      { m_cuChromaQpOffsetSubdiv = value; }
+#else
   void      setMaxCuDQPDepth                ( int   i )      { m_iMaxCuDQPDepth = i; }
 
   int       getDiffCuChromaQpOffsetDepth    ()         const { return m_diffCuChromaQpOffsetDepth;  }
   void      setDiffCuChromaQpOffsetDepth    (int value)      { m_diffCuChromaQpOffsetDepth = value; }
+#endif
 
   void      setChromaCbQpOffset             ( int   i )      { m_chromaCbQpOffset = i; }
   void      setChromaCrQpOffset             ( int   i )      { m_chromaCrQpOffset = i; }
@@ -1030,7 +1041,11 @@ public:
 
   //==== Quality control ========
   int       getMaxDeltaQP                   () const { return m_iMaxDeltaQP; }
+#if JVET_M0113_M0188_QG_SIZE
+  int       getCuQpDeltaSubdiv              () const { return m_cuQpDeltaSubdiv; }
+#else
   int       getMaxCuDQPDepth                () const { return m_iMaxCuDQPDepth; }
+#endif
   bool      getUseAdaptiveQP                () const { return m_bUseAdaptiveQP; }
   int       getQPAdaptationRange            () const { return m_iQPAdaptationRange; }
 #if ENABLE_QPA
diff --git a/source/Lib/EncoderLib/EncCu.cpp b/source/Lib/EncoderLib/EncCu.cpp
index dd0321288..2eb72295a 100644
--- a/source/Lib/EncoderLib/EncCu.cpp
+++ b/source/Lib/EncoderLib/EncCu.cpp
@@ -668,8 +668,14 @@ void EncCu::xCompressCU( CodingStructure *&tempCS, CodingStructure *&bestCS, Par
 
   if( slice.getUseChromaQpAdj() )
   {
+#if JVET_M0113_M0188_QG_SIZE
+    // TODO M0133 : double check encoder decisions with respect to chroma QG detection and actual encode
+    int lgMinCuSize = sps.getLog2MinCodingBlockSize() +
+      std::max<int>( 0, sps.getLog2DiffMaxMinCodingBlockSize() - int( pps.getPpsRangeExtension().getCuChromaQpOffsetSubdiv()/2 ) );
+#else
     int lgMinCuSize = sps.getLog2MinCodingBlockSize() +
       std::max<int>( 0, sps.getLog2DiffMaxMinCodingBlockSize() - int( pps.getPpsRangeExtension().getDiffCuChromaQpOffsetDepth() ) );
+#endif
     m_cuChromaQpOffsetIdxPlus1 = ( ( uiLPelX >> lgMinCuSize ) + ( uiTPelY >> lgMinCuSize ) ) % ( pps.getPpsRangeExtension().getChromaQpOffsetListLen() + 1 );
   }
 
@@ -711,7 +717,11 @@ void EncCu::xCompressCU( CodingStructure *&tempCS, CodingStructure *&bestCS, Par
     }
 
 #if SHARP_LUMA_DELTA_QP || ENABLE_QPA_SUB_CTU
+#if JVET_M0113_M0188_QG_SIZE
+    if (partitioner.currQgEnable() && (
+#else
     if (partitioner.currDepth <= pps.getMaxCuDQPDepth() && (
+#endif
 #if SHARP_LUMA_DELTA_QP
         (m_pcEncCfg->getLumaLevelToDeltaQPMapping().isEnabled()) ||
 #endif
@@ -1127,12 +1137,14 @@ void EncCu::copyState( EncCu* other, Partitioner& partitioner, const UnitArea& c
 void EncCu::xCheckModeSplit(CodingStructure *&tempCS, CodingStructure *&bestCS, Partitioner &partitioner, const EncTestMode& encTestMode )
 {
   const int qp                = encTestMode.qp;
-  const PPS &pps              = *tempCS->pps;
   const Slice &slice          = *tempCS->slice;
   const bool bIsLosslessMode  = false; // False at this level. Next level down may set it to true.
   const int oldPrevQp         = tempCS->prevQP[partitioner.chType];
   const auto oldMotionLut     = tempCS->motionLut;
-  const uint32_t currDepth = partitioner.currDepth;
+#if !JVET_M0113_M0188_QG_SIZE
+  const PPS &pps              = *tempCS->pps;
+  const uint32_t currDepth    = partitioner.currDepth;
+#endif
 
   const PartSplit split = getPartSplit( encTestMode );
 
@@ -1253,6 +1265,9 @@ void EncCu::xCheckModeSplit(CodingStructure *&tempCS, CodingStructure *&bestCS,
 #endif
 
   partitioner.splitCurrArea( split, *tempCS );
+#if JVET_M0113_M0188_QG_SIZE
+  bool qgEnableChildren = partitioner.currQgEnable(); // QG possible at children level
+#endif
 
   m_CurrCtx++;
 
@@ -1308,7 +1323,11 @@ void EncCu::xCheckModeSplit(CodingStructure *&tempCS, CodingStructure *&bestCS,
       bool keepResi = KEEP_PRED_AND_RESI_SIGNALS;
       tempCS->useSubStructure( *bestSubCS, partitioner.chType, CS::getArea( *tempCS, subCUArea, partitioner.chType ), KEEP_PRED_AND_RESI_SIGNALS, true, keepResi, keepResi );
 
+#if JVET_M0113_M0188_QG_SIZE
+      if( partitioner.currQgEnable() )
+#else
       if(currDepth < pps.getMaxCuDQPDepth())
+#endif
       {
         tempCS->prevQP[partitioner.chType] = bestSubCS->prevQP[partitioner.chType];
       }
@@ -1381,6 +1400,9 @@ void EncCu::xCheckModeSplit(CodingStructure *&tempCS, CodingStructure *&bestCS,
   tempCS->cost = m_pcRdCost->calcRdCost( tempCS->fracBits, tempCS->dist );
 
   // Check Delta QP bits for splitted structure
+#if JVET_M0113_M0188_QG_SIZE
+  if( !qgEnableChildren ) // check at deepest QG level only
+#endif
   xCheckDQP( *tempCS, partitioner, true );
 
   // If the configuration being tested exceeds the maximum number of bytes for a slice / slice-segment, then
@@ -1772,15 +1794,23 @@ void EncCu::xCheckDQP( CodingStructure& cs, Partitioner& partitioner, bool bKeep
     return;
   }
 
+#if JVET_M0113_M0188_QG_SIZE
+  if( !partitioner.currQgEnable() ) // do not consider split or leaf/not leaf QG condition (checked by caller)
+#else
+  // partitioner.currDepth != cs.pps->getMaxCuDQPDepth() means we are not at leaf QG level (condition needed to call predictQP only once per QG)
   if( bKeepCtx && partitioner.currDepth != cs.pps->getMaxCuDQPDepth() )
+#endif
   {
     return;
   }
 
+#if !JVET_M0113_M0188_QG_SIZE
+  // not split or implicit, and deeper than QG (never happens with JVET_M0113_M0188_QG_SIZE)
   if( !bKeepCtx && partitioner.currDepth > cs.pps->getMaxCuDQPDepth() )
   {
     return;
   }
+#endif
 
   CodingUnit* cuFirst = cs.getCU( partitioner.chType );
 
@@ -3581,7 +3611,11 @@ void EncCu::xCheckRDCostIBCModeMerge2Nx2N(CodingStructure *&tempCS, CodingStruct
             xCheckDQP (*tempCS, partitioner);
 #else
             // this if-check is redundant
+#if JVET_M0113_M0188_QG_SIZE
+            if (tempCS->pps->getUseDQP() && partitioner.currQgEnable())
+#else
             if (tempCS->pps->getUseDQP() && (partitioner.currDepth) <= tempCS->pps->getMaxCuDQPDepth())
+#endif
             {
               xCheckDQP(*tempCS, partitioner);
             }
@@ -3727,7 +3761,11 @@ void EncCu::xCheckRDCostIBCMode(CodingStructure *&tempCS, CodingStructure *&best
           xCheckDQP (*tempCS, partitioner);
 #else
           // this if-check is redundant
+#if JVET_M0113_M0188_QG_SIZE
+          if (tempCS->pps->getUseDQP() && partitioner.currQgEnable())
+#else
           if (tempCS->pps->getUseDQP() && (partitioner.currDepth) <= tempCS->pps->getMaxCuDQPDepth())
+#endif
           {
             xCheckDQP(*tempCS, partitioner);
           }
diff --git a/source/Lib/EncoderLib/EncLib.cpp b/source/Lib/EncoderLib/EncLib.cpp
index e62fedc1c..db1ef4502 100644
--- a/source/Lib/EncoderLib/EncLib.cpp
+++ b/source/Lib/EncoderLib/EncLib.cpp
@@ -777,7 +777,11 @@ void EncLib::xGetNewPicBuffer ( std::list<PelUnitBuf*>& rcListPicYuvRecOut, Pict
     rpcPic->create( sps.getChromaFormatIdc(), Size( sps.getPicWidthInLumaSamples(), sps.getPicHeightInLumaSamples()), sps.getMaxCUWidth(), sps.getMaxCUWidth()+16, false );
     if ( getUseAdaptiveQP() )
     {
+#if JVET_M0113_M0188_QG_SIZE
+      const uint32_t iMaxDQPLayer = pps.getCuQpDeltaSubdiv()/2+1;
+#else
       const uint32_t iMaxDQPLayer = pps.getMaxCuDQPDepth()+1;
+#endif
       rpcPic->aqlayer.resize( iMaxDQPLayer );
       for (uint32_t d = 0; d < iMaxDQPLayer; d++)
       {
@@ -1269,7 +1273,11 @@ void EncLib::xInitPPS(PPS &pps, const SPS &sps)
   pps.setSPSId(sps.getSPSId());
 
   pps.setConstrainedIntraPred( m_bUseConstrainedIntraPred );
+#if JVET_M0113_M0188_QG_SIZE
+  bool bUseDQP = (getCuQpDeltaSubdiv() > 0)? true : false;
+#else
   bool bUseDQP = (getMaxCuDQPDepth() > 0)? true : false;
+#endif
 
   if((getMaxDeltaQP() != 0 )|| getUseAdaptiveQP())
   {
@@ -1285,7 +1293,11 @@ void EncLib::xInitPPS(PPS &pps, const SPS &sps)
 #if ENABLE_QPA
   if (getUsePerceptQPA() && !bUseDQP)
   {
+#if JVET_M0113_M0188_QG_SIZE
+    CHECK( m_cuQpDeltaSubdiv != 0, "max. delta-QP subdiv must be zero!" );
+#else
     CHECK( m_iMaxCuDQPDepth != 0, "max. delta-QP depth must be zero!" );
+#endif
     bUseDQP = (getBaseQP() < 38) && (getSourceWidth() > 512 || getSourceHeight() > 320);
   }
 #endif
@@ -1299,29 +1311,51 @@ void EncLib::xInitPPS(PPS &pps, const SPS &sps)
   if ( m_RCEnableRateControl )
   {
     pps.setUseDQP(true);
+#if JVET_M0113_M0188_QG_SIZE
+    pps.setCuQpDeltaSubdiv( 0 );
+#else
     pps.setMaxCuDQPDepth( 0 );
+#endif
   }
   else if(bUseDQP)
   {
     pps.setUseDQP(true);
+#if JVET_M0113_M0188_QG_SIZE
+    pps.setCuQpDeltaSubdiv( m_cuQpDeltaSubdiv );
+#else
     pps.setMaxCuDQPDepth( m_iMaxCuDQPDepth );
+#endif
   }
   else
   {
     pps.setUseDQP(false);
+#if JVET_M0113_M0188_QG_SIZE
+    pps.setCuQpDeltaSubdiv( 0 );
+#else
     pps.setMaxCuDQPDepth( 0 );
+#endif
   }
 
+#if JVET_M0113_M0188_QG_SIZE
+  if ( m_cuChromaQpOffsetSubdiv >= 0 )
+  {
+    pps.getPpsRangeExtension().setCuChromaQpOffsetSubdiv(m_cuChromaQpOffsetSubdiv);
+#else
   if ( m_diffCuChromaQpOffsetDepth >= 0 )
   {
     pps.getPpsRangeExtension().setDiffCuChromaQpOffsetDepth(m_diffCuChromaQpOffsetDepth);
+#endif
     pps.getPpsRangeExtension().clearChromaQpOffsetList();
     pps.getPpsRangeExtension().setChromaQpOffsetListEntry(1, 6, 6);
     /* todo, insert table entries from command line (NB, 0 should not be touched) */
   }
   else
   {
+#if JVET_M0113_M0188_QG_SIZE
+    pps.getPpsRangeExtension().setCuChromaQpOffsetSubdiv(0);
+#else
     pps.getPpsRangeExtension().setDiffCuChromaQpOffsetDepth(0);
+#endif
     pps.getPpsRangeExtension().clearChromaQpOffsetList();
   }
   pps.getPpsRangeExtension().setCrossComponentPredictionEnabledFlag(m_crossComponentPredictionEnabledFlag);
diff --git a/source/Lib/EncoderLib/EncModeCtrl.cpp b/source/Lib/EncoderLib/EncModeCtrl.cpp
index bbf54d978..6e5020f55 100644
--- a/source/Lib/EncoderLib/EncModeCtrl.cpp
+++ b/source/Lib/EncoderLib/EncModeCtrl.cpp
@@ -132,7 +132,11 @@ void EncModeCtrl::setBest( CodingStructure& cs )
   }
 }
 
+#if JVET_M0113_M0188_QG_SIZE
+void EncModeCtrl::xGetMinMaxQP( int& minQP, int& maxQP, const CodingStructure& cs, const Partitioner &partitioner, const int baseQP, const SPS& sps, const PPS& pps, const PartSplit splitMode )
+#else
 void EncModeCtrl::xGetMinMaxQP( int& minQP, int& maxQP, const CodingStructure& cs, const Partitioner &partitioner, const int baseQP, const SPS& sps, const PPS& pps, const bool splitMode )
+#endif
 {
   if( m_pcEncCfg->getUseRateCtrl() )
   {
@@ -141,6 +145,37 @@ void EncModeCtrl::xGetMinMaxQP( int& minQP, int& maxQP, const CodingStructure& c
     return;
   }
 
+#if JVET_M0113_M0188_QG_SIZE
+  const unsigned subdivIncr = (splitMode == CU_QUAD_SPLIT) ? 2 : (splitMode == CU_BT_SPLIT) ? 1 : 0;
+  const bool qgEnable = partitioner.currQgEnable(); // QG possible at current level
+  const bool qgEnableChildren = qgEnable && ((partitioner.currSubdiv + subdivIncr) <= pps.getCuQpDeltaSubdiv()) && (subdivIncr > 0); // QG possible at next level
+  const bool isLeafQG = (qgEnable && !qgEnableChildren);
+
+  if( isLeafQG ) // QG at deepest level
+  {
+    int deltaQP = m_pcEncCfg->getMaxDeltaQP();
+    minQP = Clip3( -sps.getQpBDOffset( CHANNEL_TYPE_LUMA ), MAX_QP, baseQP - deltaQP );
+    maxQP = Clip3( -sps.getQpBDOffset( CHANNEL_TYPE_LUMA ), MAX_QP, baseQP + deltaQP );
+  }
+  else if( qgEnableChildren ) // more splits and not the deepest QG level
+  {
+    minQP = baseQP;
+    maxQP = baseQP;
+  }
+  else // deeper than QG
+  {
+    minQP = cs.currQP[partitioner.chType];
+    maxQP = cs.currQP[partitioner.chType];
+  }
+
+#if SHARP_LUMA_DELTA_QP
+  if( m_pcEncCfg->getLumaLevelToDeltaQPMapping().isEnabled() )
+  {
+    minQP = Clip3( -sps.getQpBDOffset( CHANNEL_TYPE_LUMA ), MAX_QP, baseQP - m_lumaQPOffset );
+    maxQP = minQP;
+  }
+#endif
+#else
   const uint32_t currDepth = partitioner.currDepth;
 
   if( !splitMode )
@@ -956,7 +991,11 @@ bool BestEncInfoCache::isValid( const CodingStructure& cs, const Partitioner& pa
 #else
     || encInfo.cu.ibc
 #endif
+#if JVET_M0113_M0188_QG_SIZE
+    || partitioner.currQgEnable() || cs.currQP[partitioner.chType] != encInfo.cu.qp
+#else
     || partitioner.currDepth <= cs.pps->getMaxCuDQPDepth() || cs.currQP[partitioner.chType] != encInfo.cu.qp
+#endif
     )
   {
     return false;
@@ -979,7 +1018,11 @@ bool BestEncInfoCache::setCsFrom( CodingStructure& cs, EncTestMode& testMode, co
     , encInfo.pu, (cs.picture->Y().width), (cs.picture->Y().height)
 #endif
     )
+#if JVET_M0113_M0188_QG_SIZE
+    || partitioner.currQgEnable() || cs.currQP[partitioner.chType] != encInfo.cu.qp
+#else
     || partitioner.currDepth <= cs.pps->getMaxCuDQPDepth() || cs.currQP[partitioner.chType] != encInfo.cu.qp
+#endif
     )
   {
     return false;
@@ -1181,7 +1224,11 @@ void EncModeCtrlMTnoRQT::initCULevel( Partitioner &partitioner, const CodingStru
     }
 #endif
 #if SHARP_LUMA_DELTA_QP
+#if JVET_M0113_M0188_QG_SIZE
+    if (m_pcEncCfg->getLumaLevelToDeltaQPMapping().isEnabled() && partitioner.currQgEnable())
+#else
     if (m_pcEncCfg->getLumaLevelToDeltaQPMapping().isEnabled() && partitioner.currDepth <= cs.pps->getMaxCuDQPDepth())
+#endif
     {
       CompArea clipedArea = clipArea( cs.area.Y(), cs.picture->Y() );
       // keep using the same m_QP_LUMA_OFFSET in the same CTU
@@ -1192,7 +1239,11 @@ void EncModeCtrlMTnoRQT::initCULevel( Partitioner &partitioner, const CodingStru
   int minQP = baseQP;
   int maxQP = baseQP;
 
+#if JVET_M0113_M0188_QG_SIZE
+  xGetMinMaxQP( minQP, maxQP, cs, partitioner, baseQP, *cs.sps, *cs.pps, CU_QUAD_SPLIT );
+#else
   xGetMinMaxQP( minQP, maxQP, cs, partitioner, baseQP, *cs.sps, *cs.pps, true );
+#endif
   bool checkIbc = true;
   if (cs.chType == CHANNEL_TYPE_CHROMA)
   {
@@ -1247,6 +1298,11 @@ void EncModeCtrlMTnoRQT::initCULevel( Partitioner &partitioner, const CodingStru
     }
   }
 
+#if JVET_M0113_M0188_QG_SIZE
+  int minQPq = minQP;
+  int maxQPq = maxQP;
+  xGetMinMaxQP( minQP, maxQP, cs, partitioner, baseQP, *cs.sps, *cs.pps, CU_BT_SPLIT );
+#endif
   if( partitioner.canSplit( CU_VERT_SPLIT, cs ) )
   {
     // add split modes
@@ -1277,7 +1333,11 @@ void EncModeCtrlMTnoRQT::initCULevel( Partitioner &partitioner, const CodingStru
 
   if( cuECtx.get<bool>( QT_BEFORE_BT ) )
   {
+#if JVET_M0113_M0188_QG_SIZE
+    for( int qp = maxQPq; qp >= minQPq; qp-- )
+#else
     for( int qp = maxQP; qp >= minQP; qp-- )
+#endif
     {
       m_ComprCUCtxList.back().testModes.push_back( { ETM_SPLIT_QT, ETO_STANDARD, qp, false } );
     }
@@ -1285,7 +1345,11 @@ void EncModeCtrlMTnoRQT::initCULevel( Partitioner &partitioner, const CodingStru
 
   m_ComprCUCtxList.back().testModes.push_back( { ETM_POST_DONT_SPLIT } );
 
+#if JVET_M0113_M0188_QG_SIZE
+  xGetMinMaxQP( minQP, maxQP, cs, partitioner, baseQP, *cs.sps, *cs.pps, CU_DONT_SPLIT );
+#else
   xGetMinMaxQP( minQP, maxQP, cs, partitioner, baseQP, *cs.sps, *cs.pps, false );
+#endif
 
   bool useLossless = false;
   int  lowestQP = minQP;
diff --git a/source/Lib/EncoderLib/EncModeCtrl.h b/source/Lib/EncoderLib/EncModeCtrl.h
index 505717ff3..553c9e811 100644
--- a/source/Lib/EncoderLib/EncModeCtrl.h
+++ b/source/Lib/EncoderLib/EncModeCtrl.h
@@ -345,7 +345,11 @@ public:
 
 protected:
   void xExtractFeatures ( const EncTestMode encTestmode, CodingStructure& cs );
+#if JVET_M0113_M0188_QG_SIZE
+  void xGetMinMaxQP     ( int& iMinQP, int& iMaxQP, const CodingStructure& cs, const Partitioner &pm, const int baseQP, const SPS& sps, const PPS& pps, const PartSplit splitMode );
+#else
   void xGetMinMaxQP     ( int& iMinQP, int& iMaxQP, const CodingStructure& cs, const Partitioner &pm, const int baseQP, const SPS& sps, const PPS& pps, const bool splitMode );
+#endif
   int  xComputeDQP      ( const CodingStructure &cs, const Partitioner &pm );
 };
 
diff --git a/source/Lib/EncoderLib/VLCWriter.cpp b/source/Lib/EncoderLib/VLCWriter.cpp
index 9b94251df..55c36ff77 100644
--- a/source/Lib/EncoderLib/VLCWriter.cpp
+++ b/source/Lib/EncoderLib/VLCWriter.cpp
@@ -233,7 +233,11 @@ void HLSWriter::codePPS( const PPS* pcPPS )
   WRITE_FLAG( pcPPS->getUseDQP() ? 1 : 0, "cu_qp_delta_enabled_flag" );
   if ( pcPPS->getUseDQP() )
   {
+#if JVET_M0113_M0188_QG_SIZE
+    WRITE_UVLC( pcPPS->getCuQpDeltaSubdiv(), "cu_qp_delta_subdiv" );
+#else
     WRITE_UVLC( pcPPS->getMaxCuDQPDepth(), "diff_cu_qp_delta_depth" );
+#endif
   }
 
   WRITE_SVLC( pcPPS->getQpOffset(COMPONENT_Cb), "pps_cb_qp_offset" );
@@ -341,7 +345,11 @@ void HLSWriter::codePPS( const PPS* pcPPS )
           WRITE_FLAG(uint32_t(ppsRangeExtension.getChromaQpOffsetListEnabledFlag()),           "chroma_qp_offset_list_enabled_flag" );
           if (ppsRangeExtension.getChromaQpOffsetListEnabledFlag())
           {
+#if JVET_M0113_M0188_QG_SIZE
+            WRITE_UVLC(ppsRangeExtension.getCuChromaQpOffsetSubdiv(),                      "cu_chroma_qp_offset_subdiv");
+#else
             WRITE_UVLC(ppsRangeExtension.getDiffCuChromaQpOffsetDepth(),                   "diff_cu_chroma_qp_offset_depth");
+#endif
             WRITE_UVLC(ppsRangeExtension.getChromaQpOffsetListLen() - 1,                   "chroma_qp_offset_list_len_minus1");
             /* skip zero index */
             for (int cuChromaQpOffsetIdx = 0; cuChromaQpOffsetIdx < ppsRangeExtension.getChromaQpOffsetListLen(); cuChromaQpOffsetIdx++)
-- 
GitLab