diff --git a/doc/software-manual.tex b/doc/software-manual.tex
index e2dbefb6a4da155cef2574c85571dcd90070821b..ddc4700e57d53ae99824fadf4d8637d65cb64212 100644
--- a/doc/software-manual.tex
+++ b/doc/software-manual.tex
@@ -3767,7 +3767,18 @@ SEI messages.
 \begin{OptionTableNoShorthand}{Scalable nesting SEI message encoder parameters}{tab:sei-scalable-nesting}
 \Option{SEIScalableNesting} &
 \Default{0} &
-Enables or disables the use of the scalable nesting SEI messages.
+Enables creation of scalable nesting SEI messages for buffering period and picture timing SEI messages.
+\\
+\Option{SubpicDecodedPictureHash} &
+\Default{0} &
+Enables creation of decoded picture hash SEI messages for each subpicture and writes these in scalable nesting SEI messages.
+\par
+\begin{tabular}{cp{0.35\textwidth}}
+  0 & Disabled \\
+  1 & MD5 \\
+  2 & CRCs \\
+  3 & checksum \\
+\end{tabular}
 \\
 \end{OptionTableNoShorthand}
 
diff --git a/source/App/BitstreamExtractorApp/BitstreamExtractorApp.cpp b/source/App/BitstreamExtractorApp/BitstreamExtractorApp.cpp
index 8e4bf3ca10f146d62498d7a4c61f7f589b38edc9..8b3b68720dbf9f5d2695e00dd5347df697019991 100644
--- a/source/App/BitstreamExtractorApp/BitstreamExtractorApp.cpp
+++ b/source/App/BitstreamExtractorApp/BitstreamExtractorApp.cpp
@@ -350,6 +350,10 @@ void BitstreamExtractorApp::xWriteVPS(VPS *vps, std::ostream& out, int layerId,
   m_hlSyntaxWriter.setBitstream( &naluOut.m_Bitstream );
   m_hlSyntaxWriter.codeVPS( vps );
 
+#if JVET_R0294_SUBPIC_HASH
+  NALUnitEBSP naluWithHeader(naluOut);
+  writeAnnexBNalUnit(out, naluWithHeader, true);
+#else
   // create a dummy AU
   AccessUnit tmpAu;
   // convert to EBSP (this adds emulation prevention!) and add into NAL unit
@@ -361,6 +365,7 @@ void BitstreamExtractorApp::xWriteVPS(VPS *vps, std::ostream& out, int layerId,
   //       AU works without chaning the start code length.
   //       This cannot be done for VLC NAL units!
   writeAnnexB (out, tmpAu);
+#endif
 }
 
 void BitstreamExtractorApp::xWriteSPS(SPS *sps, std::ostream& out, int layerId, int temporalId)
@@ -373,6 +378,10 @@ void BitstreamExtractorApp::xWriteSPS(SPS *sps, std::ostream& out, int layerId,
   m_hlSyntaxWriter.setBitstream( &naluOut.m_Bitstream );
   m_hlSyntaxWriter.codeSPS( sps );
 
+#if JVET_R0294_SUBPIC_HASH
+  NALUnitEBSP naluWithHeader(naluOut);
+  writeAnnexBNalUnit(out, naluWithHeader, true);
+#else
   // create a dummy AU
   AccessUnit tmpAu;
   // convert to EBSP (this adds emulation prevention!) and add into NAL unit
@@ -384,6 +393,7 @@ void BitstreamExtractorApp::xWriteSPS(SPS *sps, std::ostream& out, int layerId,
   //       AU works without chaning the start code length.
   //       This cannot be done for VLC NAL units!
   writeAnnexB (out, tmpAu);
+#endif
 }
 
 void BitstreamExtractorApp::xWritePPS(PPS *pps, std::ostream& out, int layerId, int temporalId)
@@ -395,6 +405,10 @@ void BitstreamExtractorApp::xWritePPS(PPS *pps, std::ostream& out, int layerId,
   m_hlSyntaxWriter.setBitstream( &naluOut.m_Bitstream );
   m_hlSyntaxWriter.codePPS( pps );
 
+#if JVET_R0294_SUBPIC_HASH
+  NALUnitEBSP naluWithHeader(naluOut);
+  writeAnnexBNalUnit(out, naluWithHeader, true);
+#else
   // create a dummy AU
   AccessUnit tmpAu;
   // convert to EBSP (this adds emulation prevention!) and add into NAL unit
@@ -406,6 +420,7 @@ void BitstreamExtractorApp::xWritePPS(PPS *pps, std::ostream& out, int layerId,
   //       AU works without chaning the start code length.
   //       This cannot be done for VLC NAL units!
   writeAnnexB (out, tmpAu);
+#endif
 }
 
 // returns true, if the NAL unit is to be discarded
@@ -425,6 +440,48 @@ bool BitstreamExtractorApp::xCheckNumSubLayers(InputNALUnit &nalu, VPS *vps)
   return retval;
 }
 
+#if JVET_R0294_SUBPIC_HASH
+bool BitstreamExtractorApp::xCheckSEIsSubPicture(SEIMessages& SEIs, InputNALUnit& nalu, std::ostream& out)
+{
+  SEIMessages scalableNestingSEIs = getSeisByType(SEIs, SEI::SCALABLE_NESTING);
+  if (scalableNestingSEIs.size())
+  {
+    CHECK ( scalableNestingSEIs.size() > 1, "There shall be only one Scalable Nesting SEI in one NAL unit" );
+    CHECK ( scalableNestingSEIs.size() != SEIs.size(), "Scalable Nesting SEI shall not be in the same NAL unit as other SEIs" );
+    // check, if the scalable nesting SEI applies to the target subpicture
+    SEIScalableNesting *sei = (SEIScalableNesting*) scalableNestingSEIs.front();
+
+    if (sei->m_snSubpicFlag == 0)
+    {
+      // does not apply to a subpicture -> remove
+      return false;
+    }
+    if (std::find(sei->m_snSubpicId.begin(), sei->m_snSubpicId.end(), m_subPicId) != sei->m_snSubpicId.end())
+    {
+      // applies to target subpicture -> extract
+      OutputNALUnit outNalu( nalu.m_nalUnitType, nalu.m_nuhLayerId, nalu.m_temporalId );
+      m_seiWriter.writeSEImessages(outNalu.m_Bitstream, sei->m_nestedSEIs, m_hrd, false, nalu.m_temporalId);
+      NALUnitEBSP naluWithHeader(outNalu);
+      writeAnnexBNalUnit(out, naluWithHeader, true);
+      return false;
+    }
+    else
+    {
+      // does not apply to target subpicture -> remove
+      return false;
+    }
+  }
+  // remove not nested decoded picture hash SEIs
+  SEIMessages hashSEI = getSeisByType(SEIs, SEI::DECODED_PICTURE_HASH);
+  if (hashSEI.size() > 0)
+  {
+    return false;
+  }
+  // keep all other SEIs
+  return true;
+}
+#endif
+
 uint32_t BitstreamExtractorApp::decode()
 {
   std::ifstream bitstreamFileIn(m_bitstreamFileNameIn.c_str(), std::ifstream::in | std::ifstream::binary);
@@ -648,17 +705,28 @@ uint32_t BitstreamExtractorApp::decode()
       {
         xReadPicHeader(nalu);
       }
+#if JVET_R0294_SUBPIC_HASH
+      if ( (nalu.m_nalUnitType == NAL_UNIT_PREFIX_SEI) || (nalu.m_nalUnitType == NAL_UNIT_SUFFIX_SEI))
+      {
+#else
       if (m_targetOlsIdx>=0)
       {
         if (nalu.m_nalUnitType == NAL_UNIT_PREFIX_SEI)
         {
-          // decoding a SEI
-          SEIMessages SEIs;
-          HRD hrd;
-          m_seiReader.parseSEImessage(&(nalu.getBitstream()), SEIs, nalu.m_nalUnitType, nalu.m_nuhLayerId, nalu.m_temporalId, vps, m_parameterSetManager.getActiveSPS(), hrd, &std::cout);
+#endif
+        // decode SEI
+        SEIMessages SEIs;
+#if !JVET_R0294_SUBPIC_HASH
+        HRD hrd;
+        m_seiReader.parseSEImessage(&(nalu.getBitstream()), SEIs, nalu.m_nalUnitType, nalu.m_nuhLayerId, nalu.m_temporalId, vps, m_parameterSetManager.getActiveSPS(), hrd, &std::cout);
+#else
+        m_seiReader.parseSEImessage(&(nalu.getBitstream()), SEIs, nalu.m_nalUnitType, nalu.m_nuhLayerId, nalu.m_temporalId, vps, m_parameterSetManager.getActiveSPS(), m_hrd, &std::cout);
+        if (m_targetOlsIdx>=0)
+        {
+#endif
           for (auto sei : SEIs)
           {
-            // remove unqualiified scalable nesting SEI
+            // remove unqualified scalable nesting SEI
             if (sei->payloadType() == SEI::SCALABLE_NESTING)
             {
               SEIScalableNesting *seiNesting = (SEIScalableNesting *)sei;
@@ -683,12 +751,22 @@ uint32_t BitstreamExtractorApp::decode()
               writeInpuNalUnitToStream &= !targetOlsIdxGreaterThanZero;
             }
           }
+#if !JVET_R0294_SUBPIC_HASH
+        }
+#endif
+          if (m_vpsId == -1)
+          {
+            delete vps;
+          }
         }
-        if (m_vpsId == -1)
+#if JVET_R0294_SUBPIC_HASH
+        if (m_subPicId>=0)
         {
-          delete vps;
+          writeInpuNalUnitToStream &= xCheckSEIsSubPicture(SEIs, nalu, bitstreamFileOut);
         }
       }
+#endif
+
 #if JVET_R0107_BITSTREAM_EXTACTION
       Slice slice;
       if (nalu.isSlice())
diff --git a/source/App/BitstreamExtractorApp/BitstreamExtractorApp.h b/source/App/BitstreamExtractorApp/BitstreamExtractorApp.h
index 748189bb3df717701d1e03e3b05258cfd5cd37c1..9e087052ba2fc696c28e0d5efc57b3004c10933d 100644
--- a/source/App/BitstreamExtractorApp/BitstreamExtractorApp.h
+++ b/source/App/BitstreamExtractorApp/BitstreamExtractorApp.h
@@ -50,7 +50,9 @@
 #include "VLCWriter.h"
 
 #include "SEIread.h"
-
+#if JVET_R0294_SUBPIC_HASH
+#include "SEIwrite.h"
+#endif
 class BitstreamExtractorApp : public BitstreamExtractorAppCfg
 {
 
@@ -73,6 +75,9 @@ protected:
   bool xCheckSliceSubpicture(InputNALUnit &nalu, int subPicId);
 #endif
   void xReadPicHeader(InputNALUnit &nalu);
+#if JVET_R0294_SUBPIC_HASH
+  bool xCheckSEIsSubPicture(SEIMessages& SEIs, InputNALUnit& nalu, std::ostream& out);
+#endif
 
   void xSetSPSUpdated(int spsId)   { return m_updatedSPSList.push_back(spsId); }
   bool xIsSPSUpdate(int spsId)     { return (std::find(m_updatedSPSList.begin(),m_updatedSPSList.end(), spsId) != m_updatedSPSList.end()); }
@@ -88,6 +93,11 @@ protected:
   HLSyntaxReader        m_hlSynaxReader;
   HLSWriter             m_hlSyntaxWriter;
   SEIReader             m_seiReader;
+#if JVET_R0294_SUBPIC_HASH
+  SEIWriter             m_seiWriter;
+  HRD                   m_hrd;
+#endif
+
   int                   m_vpsId;
   bool                  m_removeTimingSEI;
 
diff --git a/source/App/EncoderApp/EncApp.cpp b/source/App/EncoderApp/EncApp.cpp
index efcfbc0a05e0a72209b3b3a8b04be2db554cc498..af077e021d1511d06ab5a2fa7f97763f513935f0 100644
--- a/source/App/EncoderApp/EncApp.cpp
+++ b/source/App/EncoderApp/EncApp.cpp
@@ -785,6 +785,9 @@ void EncApp::xInitLibCfg()
   m_cEncLib.setSaoGreedyMergeEnc                                 ( m_saoGreedyMergeEnc);
   m_cEncLib.setIntraSmoothingDisabledFlag                        (!m_enableIntraReferenceSmoothing );
   m_cEncLib.setDecodedPictureHashSEIType                         ( m_decodedPictureHashSEIType );
+#if JVET_R0294_SUBPIC_HASH
+  m_cEncLib.setSubpicDecodedPictureHashType                      ( m_subpicDecodedPictureHashType );
+#endif
   m_cEncLib.setDependentRAPIndicationSEIEnabled                  ( m_drapPeriod > 0 );
   m_cEncLib.setBufferingPeriodSEIEnabled                         ( m_bufferingPeriodSEIEnabled );
   m_cEncLib.setPictureTimingSEIEnabled                           ( m_pictureTimingSEIEnabled );
@@ -1328,7 +1331,11 @@ void EncApp::xWriteOutput( int iNumEncoded, std::list<PelUnitBuf*>& recBufList )
 
 void EncApp::outputAU( const AccessUnit& au )
 {
+#if JVET_R0294_SUBPIC_HASH
+  const vector<uint32_t>& stats = writeAnnexBAccessUnit(m_bitstream, au);
+#else
   const vector<uint32_t>& stats = writeAnnexB(m_bitstream, au);
+#endif
   rateStatsAccum(au, stats);
   m_bitstream.flush();
 }
diff --git a/source/App/EncoderApp/EncAppCfg.cpp b/source/App/EncoderApp/EncAppCfg.cpp
index 0b71aa07e0fbe2699bce4df329df7ff5281ea339..a07a813b085b86d0876e5cf21b79eb0904e7a753 100644
--- a/source/App/EncoderApp/EncAppCfg.cpp
+++ b/source/App/EncoderApp/EncAppCfg.cpp
@@ -559,6 +559,9 @@ bool EncAppCfg::parseCfg( int argc, char* argv[] )
   int tmpFastInterSearchMode;
   int tmpMotionEstimationSearchMethod;
   int tmpDecodedPictureHashSEIMappedType;
+#if JVET_R0294_SUBPIC_HASH
+  int tmpSubpicDecodedPictureHashMappedType;
+#endif
   string inputColourSpaceConvert;
   string inputPathPrefix;
   ExtendedProfileName extendedProfile;
@@ -1147,6 +1150,13 @@ bool EncAppCfg::parseCfg( int argc, char* argv[] )
                                                                                                                "\t2: CRC\n"
                                                                                                                "\t1: use MD5\n"
                                                                                                                "\t0: disable")
+#if JVET_R0294_SUBPIC_HASH
+  ("SubpicDecodedPictureHash",                        tmpSubpicDecodedPictureHashMappedType,                0, "Control generation of decode picture hash SEI messages for each subpicture\n"
+                                                                                                               "\t3: checksum\n"
+                                                                                                               "\t2: CRC\n"
+                                                                                                               "\t1: use MD5\n"
+                                                                                                               "\t0: disable")
+#endif
   ("TMVPMode",                                        m_TMVPModeId,                                         1, "TMVP mode 0: TMVP disable for all slices. 1: TMVP enable for all slices (default) 2: TMVP enable for certain slices only")
   ("SliceLevelRpl",                                   m_sliceLevelRpl,                                   true, "Code reference picture lists in slice headers rather than picture header.")
   ("SliceLevelDblk",                                  m_sliceLevelDblk,                                  true, "Code deblocking filter parameters in slice headers rather than picture header.")
@@ -1959,7 +1969,17 @@ bool EncAppCfg::parseCfg( int argc, char* argv[] )
   {
     m_decodedPictureHashSEIType=HashType(tmpDecodedPictureHashSEIMappedType-1);
   }
-
+#if JVET_R0294_SUBPIC_HASH
+  // Need to map values to match those of the SEI message:
+  if (tmpSubpicDecodedPictureHashMappedType==0)
+  {
+    m_subpicDecodedPictureHashType=HASHTYPE_NONE;
+  }
+  else
+  {
+    m_subpicDecodedPictureHashType=HashType(tmpSubpicDecodedPictureHashMappedType-1);
+  }
+#endif
   // allocate slice-based dQP values
   m_aidQP = new int[ m_framesToBeEncoded + m_iGOPSize + 1 ];
   ::memset( m_aidQP, 0, sizeof(int)*( m_framesToBeEncoded + m_iGOPSize + 1 ) );
diff --git a/source/App/EncoderApp/EncAppCfg.h b/source/App/EncoderApp/EncAppCfg.h
index 810978a126af34b91f92964c8efa519db8acbd87..745f9214c233f8273df9cffa719da0859ae1eee7 100644
--- a/source/App/EncoderApp/EncAppCfg.h
+++ b/source/App/EncoderApp/EncAppCfg.h
@@ -483,6 +483,9 @@ protected:
   bool      m_bUseBLambdaForNonKeyLowDelayPictures;
 
   HashType  m_decodedPictureHashSEIType;                      ///< Checksum mode for decoded picture hash SEI message
+#if JVET_R0294_SUBPIC_HASH
+  HashType  m_subpicDecodedPictureHashType;
+#endif
   bool      m_bufferingPeriodSEIEnabled;
   bool      m_pictureTimingSEIEnabled;
   bool      m_bpDeltasGOPStructure;
diff --git a/source/Lib/CommonLib/TypeDef.h b/source/Lib/CommonLib/TypeDef.h
index 4eb7af104b34782ddfbcec84210ce6a61f1749a0..659c8f93a59136a0d3770c465995ac45c2b8b020 100644
--- a/source/Lib/CommonLib/TypeDef.h
+++ b/source/Lib/CommonLib/TypeDef.h
@@ -113,6 +113,8 @@
 
 #define JVET_S0185_PROPOSAL2_SEI_CLEANUP                  1 // JVET-S0185_PROPOSAL2: Move signalling of syntax element bp_alt_cpb_params_present_flag
 
+#define JVET_R0294_SUBPIC_HASH                            1 // JVET-R0294: Allow decoded picture hash SEI messages to be nested in subpicture context
+
 #define JVET_S0181_PROPOSAL1                              1 // JVET-0181_Proposal1: Conditionally signal bp_sublayer_initial_cpb_removal_delay_present_flag
 
 #define JVET_Q0406_CABAC_ZERO                             1 // JVET-Q0406: signal cabac_zero_words per sub-picture
diff --git a/source/Lib/DecoderLib/SEIread.cpp b/source/Lib/DecoderLib/SEIread.cpp
index bf641c4851ee242ba63d5e5c11295e0c5390b147..608f84febdb4834dc840971618ff1519100143b4 100644
--- a/source/Lib/DecoderLib/SEIread.cpp
+++ b/source/Lib/DecoderLib/SEIread.cpp
@@ -326,6 +326,12 @@ void SEIReader::xReadSEImessage(SEIMessages& seis, const NalUnitType nalUnitType
         sei = new SEIDecodedPictureHash;
         xParseSEIDecodedPictureHash((SEIDecodedPictureHash&) *sei, payloadSize, pDecodedMessageOutputStream);
         break;
+#if JVET_R0294_SUBPIC_HASH
+      case SEI::SCALABLE_NESTING:
+        sei = new SEIScalableNesting;
+        xParseSEIScalableNesting((SEIScalableNesting&)*sei, nalUnitType, nuh_layer_id, payloadSize, vps, sps, pDecodedMessageOutputStream);
+        break;
+#endif
       default:
         for (uint32_t i = 0; i < payloadSize; i++)
         {
diff --git a/source/Lib/EncoderLib/AnnexBwrite.h b/source/Lib/EncoderLib/AnnexBwrite.h
index 03477b243e36b1cd55c86d791261b336e81d53d6..d891bf47a55d4b6230dc0e78b653d94712dbebd0 100644
--- a/source/Lib/EncoderLib/AnnexBwrite.h
+++ b/source/Lib/EncoderLib/AnnexBwrite.h
@@ -42,6 +42,30 @@
 //! \ingroup EncoderLib
 //! \{
 
+#if JVET_R0294_SUBPIC_HASH
+uint32_t writeAnnexBNalUnit(std::ostream& out, const NALUnitEBSP& nalu, bool useLongStartcode)
+{
+  uint32_t size = 0; /* size of annexB unit in bytes */
+
+  static const uint8_t startCodePrefix[] = {0,0,0,1};
+
+  if (useLongStartcode)
+  {
+    out.write(reinterpret_cast<const char*>(startCodePrefix), 4);
+    size += 4;
+  }
+  else
+  {
+    out.write(reinterpret_cast<const char*>(startCodePrefix+1), 3);
+    size += 3;
+  }
+  out << nalu.m_nalUnitData.str();
+  size += uint32_t(nalu.m_nalUnitData.str().size());
+
+  return size;
+}
+#endif
+
 /**
  * write all NALunits in au to bytestream out in a manner satisfying
  * AnnexB of AVC.  NALunits are written in the order they are found in au.
@@ -49,13 +73,18 @@
  *  - the initial startcode in the access unit,
  *  - any SPS/PPS nal units
  */
+#if JVET_R0294_SUBPIC_HASH
+std::vector<uint32_t> writeAnnexBAccessUnit(std::ostream& out, const AccessUnit& au)
+#else
 static std::vector<uint32_t> writeAnnexB(std::ostream& out, const AccessUnit& au)
+#endif
 {
   std::vector<uint32_t> annexBsizes;
 
   for (AccessUnit::const_iterator it = au.begin(); it != au.end(); it++)
   {
     const NALUnitEBSP& nalu = **it;
+#if !JVET_R0294_SUBPIC_HASH
     uint32_t size = 0; /* size of annexB unit in bytes */
 
     static const uint8_t start_code_prefix[] = {0,0,0,1};
@@ -81,7 +110,12 @@ static std::vector<uint32_t> writeAnnexB(std::ostream& out, const AccessUnit& au
     }
     out << nalu.m_nalUnitData.str();
     size += uint32_t(nalu.m_nalUnitData.str().size());
+#else
+    const bool useLongStartCode = (it == au.begin() || nalu.m_nalUnitType == NAL_UNIT_DCI || nalu.m_nalUnitType == NAL_UNIT_VPS || nalu.m_nalUnitType == NAL_UNIT_SPS
+                                   || nalu.m_nalUnitType == NAL_UNIT_PPS || nalu.m_nalUnitType == NAL_UNIT_PREFIX_APS || nalu.m_nalUnitType == NAL_UNIT_SUFFIX_APS);
+    const uint32_t size = writeAnnexBNalUnit(out, nalu, useLongStartCode);
 
+#endif
     annexBsizes.push_back(size);
   }
 
@@ -92,6 +126,7 @@ static std::vector<uint32_t> writeAnnexB(std::ostream& out, const AccessUnit& au
 
   return annexBsizes;
 }
+
 //! \}
 
 #endif
diff --git a/source/Lib/EncoderLib/EncCfg.h b/source/Lib/EncoderLib/EncCfg.h
index f432632772a57fdd75344d3ddd3d423d83fa1746..ad9e4b0d2549b2a646c9206604720db14cc60c65 100644
--- a/source/Lib/EncoderLib/EncCfg.h
+++ b/source/Lib/EncoderLib/EncCfg.h
@@ -512,6 +512,9 @@ protected:
   bool      m_entryPointPresentFlag;                           ///< flag for the presence of entry points
 
   HashType  m_decodedPictureHashSEIType;
+#if JVET_R0294_SUBPIC_HASH
+  HashType  m_subpicDecodedPictureHashType;
+#endif
   bool      m_bufferingPeriodSEIEnabled;
   bool      m_pictureTimingSEIEnabled;
   bool      m_frameFieldInfoSEIEnabled;
@@ -1470,6 +1473,11 @@ public:
   void  setEntryPointPresentFlag(bool b)                             { m_entryPointPresentFlag = b; }
   void  setDecodedPictureHashSEIType(HashType m)                     { m_decodedPictureHashSEIType = m; }
   HashType getDecodedPictureHashSEIType() const                      { return m_decodedPictureHashSEIType; }
+#if JVET_R0294_SUBPIC_HASH
+  void  setSubpicDecodedPictureHashType(HashType m)                  { m_subpicDecodedPictureHashType = m; }
+  HashType getSubpicDecodedPictureHashType() const                   { return m_subpicDecodedPictureHashType; }
+#endif
+
   void  setBufferingPeriodSEIEnabled(bool b)                         { m_bufferingPeriodSEIEnabled = b; }
   bool  getBufferingPeriodSEIEnabled() const                         { return m_bufferingPeriodSEIEnabled; }
   void  setPictureTimingSEIEnabled(bool b)                           { m_pictureTimingSEIEnabled = b; }
diff --git a/source/Lib/EncoderLib/EncGOP.cpp b/source/Lib/EncoderLib/EncGOP.cpp
index c5975257d76870d48ac324d629740abccacad220..0ca0ef69d1e8dfabbec175792c5de2667d89a69c 100644
--- a/source/Lib/EncoderLib/EncGOP.cpp
+++ b/source/Lib/EncoderLib/EncGOP.cpp
@@ -752,7 +752,11 @@ void EncGOP::xCreatePerPictureSEIMessages (int picInGOP, SEIMessages& seiMessage
 
 }
 
+#if JVET_R0294_SUBPIC_HASH
+void EncGOP::xCreateScalableNestingSEI(SEIMessages& seiMessages, SEIMessages& nestedSeiMessages, const std::vector<int> &targetOLSs, const std::vector<int> &targetLayers, const std::vector<uint16_t>& subpicIDs)
+#else
 void EncGOP::xCreateScalableNestingSEI(SEIMessages& seiMessages, SEIMessages& nestedSeiMessages, const std::vector<uint16_t>& subpicIDs)
+#endif
 {
   SEIMessages tmpMessages;
   while (!nestedSeiMessages.empty())
@@ -761,7 +765,11 @@ void EncGOP::xCreateScalableNestingSEI(SEIMessages& seiMessages, SEIMessages& ne
     nestedSeiMessages.pop_front();
     tmpMessages.push_back(sei);
     SEIScalableNesting *nestingSEI = new SEIScalableNesting();
+#if JVET_R0294_SUBPIC_HASH
+    m_seiEncoder.initSEIScalableNesting(nestingSEI, tmpMessages, targetOLSs, targetLayers, subpicIDs);
+#else
     m_seiEncoder.initSEIScalableNesting(nestingSEI, tmpMessages, subpicIDs);
+#endif
     seiMessages.push_back(nestingSEI);
     tmpMessages.clear();
   }
@@ -3516,6 +3524,29 @@ void EncGOP::compressGOP( int iPOCLast, int iNumPicRcvd, PicList& rcListPic,
         m_seiEncoder.initDecodedPictureHashSEI(decodedPictureHashSei, recoBuf, digestStr, pcSlice->getSPS()->getBitDepths());
         trailingSeiMessages.push_back(decodedPictureHashSei);
       }
+#if JVET_R0294_SUBPIC_HASH
+      // create per-subpicture decoded picture hash SEI messages, if more than one subpicture is enabled
+      const PPS* pps = pcPic->cs->pps;
+      const int numSubpics = pps->getNumSubPics();
+      std::string subPicDigest;
+      if (numSubpics > 1 && m_pcCfg->getSubpicDecodedPictureHashType() != HASHTYPE_NONE )
+      {
+        for (int subPicIdx = 0; subPicIdx < numSubpics; subPicIdx++)
+        {
+          const SubPic& subpic = pps->getSubPic(subPicIdx);
+          const UnitArea area = UnitArea(pcSlice->getSPS()->getChromaFormatIdc(), Area(subpic.getSubPicLeft(), subpic.getSubPicTop(), subpic.getSubPicWidthInLumaSample(), subpic.getSubPicHeightInLumaSample()));
+          PelUnitBuf recoBuf = pcPic->cs->getRecoBuf(area);
+          SEIDecodedPictureHash *decodedPictureHashSEI = new SEIDecodedPictureHash();
+          m_seiEncoder.initDecodedPictureHashSEI(decodedPictureHashSEI, recoBuf, subPicDigest, pcSlice->getSPS()->getBitDepths());
+          SEIMessages nestedSEI;
+          nestedSEI.push_back(decodedPictureHashSEI);
+          const std::vector<uint16_t> subPicIds = { (uint16_t)subpic.getSubPicID() };
+          std::vector<int> targetOLS;
+          std::vector<int> targetLayers = {pcPic->layerId};
+          xCreateScalableNestingSEI(trailingSeiMessages, nestedSEI, targetOLS, targetLayers, subPicIds);
+        }
+      }
+#endif
 
       m_pcCfg->setEncodedFlag(iGOPid, true);
 
@@ -3588,7 +3619,15 @@ void EncGOP::compressGOP( int iPOCLast, int iNumPicRcvd, PicList& rcListPic,
             }
           }
         }
+#if JVET_R0294_SUBPIC_HASH
+        // Note (KJS): Using targetOLS = 0, 1 is as random as encapsulating the same SEIs in scalable nesting.
+        //             This can just be seen as example regarding how to write scalable nesting, not what to write.
+        std::vector<int> targetOLS = {0, 1};
+        std::vector<int> targetLayers;
+        xCreateScalableNestingSEI(leadingSeiMessages, nestedSeiMessages, targetOLS, targetLayers, subpicIDs);
+#else
         xCreateScalableNestingSEI(leadingSeiMessages, nestedSeiMessages, subpicIDs);
+#endif
       }
 
       xWriteLeadingSEIMessages( leadingSeiMessages, duInfoSeiMessages, accessUnit, pcSlice->getTLayer(), pcSlice->getSPS(), duData );
diff --git a/source/Lib/EncoderLib/EncGOP.h b/source/Lib/EncoderLib/EncGOP.h
index b21293029d19598bc64c8893f0a3b49a105c44b6..d514474e3be6f20bb761dcd3a41b286ec624b3b2 100644
--- a/source/Lib/EncoderLib/EncGOP.h
+++ b/source/Lib/EncoderLib/EncGOP.h
@@ -307,7 +307,11 @@ protected:
   void xUpdateDuData(AccessUnit &testAU, std::deque<DUData> &duData);
   void xUpdateTimingSEI(SEIPictureTiming *pictureTimingSEI, std::deque<DUData> &duData, const SPS *sps);
   void xUpdateDuInfoSEI(SEIMessages &duInfoSeiMessages, SEIPictureTiming *pictureTimingSEI, int maxSubLayers);
+#if JVET_R0294_SUBPIC_HASH
+  void xCreateScalableNestingSEI(SEIMessages& seiMessages, SEIMessages& nestedSeiMessages, const std::vector<int> &targetOLSs, const std::vector<int> &targetLayers, const std::vector<uint16_t>& subpicIDs);
+#else
   void xCreateScalableNestingSEI(SEIMessages& seiMessages, SEIMessages& nestedSeiMessages, const std::vector<uint16_t>& subpicIDs);
+#endif
   void xWriteSEI (NalUnitType naluType, SEIMessages& seiMessages, AccessUnit &accessUnit, AccessUnit::iterator &auPos, int temporalId);
   void xWriteSEISeparately (NalUnitType naluType, SEIMessages& seiMessages, AccessUnit &accessUnit, AccessUnit::iterator &auPos, int temporalId);
   void xClearSEIs(SEIMessages& seiMessages, bool deleteMessages);
diff --git a/source/Lib/EncoderLib/SEIEncoder.cpp b/source/Lib/EncoderLib/SEIEncoder.cpp
index 631021b17551eef9b159cdd35bb4fc601dbbb6ed..8ef6a69da3e559a4588bb84166ea9a28d97edd15 100644
--- a/source/Lib/EncoderLib/SEIEncoder.cpp
+++ b/source/Lib/EncoderLib/SEIEncoder.cpp
@@ -396,11 +396,53 @@ void SEIEncoder::initSEISampleAspectRatioInfo(SEISampleAspectRatioInfo* seiSampl
 //! initialize scalable nesting SEI message.
 //! Note: The SEI message structures input into this function will become part of the scalable nesting SEI and will be
 //!       automatically freed, when the nesting SEI is disposed.
+#if JVET_R0294_SUBPIC_HASH
+//  either targetOLS or targetLayer should be active, call with empty vector for the inactive mode
+void SEIEncoder::initSEIScalableNesting(SEIScalableNesting *scalableNestingSEI, SEIMessages &nestedSEIs, const std::vector<int> &targetOLSs, const std::vector<int> &targetLayers, const std::vector<uint16_t> &subpictureIDs)
+#else
 void SEIEncoder::initSEIScalableNesting(SEIScalableNesting *scalableNestingSEI, SEIMessages &nestedSEIs, const std::vector<uint16_t> &subpictureIDs)
+#endif
 {
   CHECK(!(m_isInitialized), "Scalable Nesting SEI already initialized ");
   CHECK(!(scalableNestingSEI != NULL), "No Scalable Nesting SEI object passed");
+#if JVET_R0294_SUBPIC_HASH
+  CHECK (targetOLSs.size() > 0 && targetLayers.size() > 0, "Scalable Nesting SEI can apply to either OLS or layer(s), not both");
 
+  scalableNestingSEI->m_snOlsFlag = (targetOLSs.size() > 0) ? 1 : 0;  // If the nested SEI messages are picture buffering SEI messages, picture timing SEI messages or sub-picture timing SEI messages, nesting_ols_flag shall be equal to 1, by default case
+  if (scalableNestingSEI->m_snOlsFlag)
+  {
+    scalableNestingSEI->m_snNumOlssMinus1 =  (uint32_t) targetOLSs.size() - 1;
+    // initialize absolute indexes
+    for (int i = 0; i <= scalableNestingSEI->m_snNumOlssMinus1; i++)
+    {
+      scalableNestingSEI->m_snOlsIdx[i] = targetOLSs[i];
+    }
+    // calculate delta indexes from absolute ones
+    for (int i = 0; i <= scalableNestingSEI->m_snNumOlssMinus1; i++)
+    {
+      if (i == 0)
+      {
+        CHECK (scalableNestingSEI->m_snOlsIdx[i] < 0, "OLS indexes must be  equal to or greater than 0");
+        // no "-1" operation for the first index although the name implies one
+        scalableNestingSEI->m_snOlsIdxDeltaMinus1[i] = scalableNestingSEI->m_snOlsIdx[i];
+      }
+      else
+      {
+        CHECK (scalableNestingSEI->m_snOlsIdx[i] <= scalableNestingSEI->m_snOlsIdx[i - 1], "OLS indexes must be in ascending order");
+        scalableNestingSEI->m_snOlsIdxDeltaMinus1[i] = scalableNestingSEI->m_snOlsIdx[i] - scalableNestingSEI->m_snOlsIdx[i - 1] - 1;
+      }
+    }
+  }
+  else
+  {
+    scalableNestingSEI->m_snAllLayersFlag = 0;                          // nesting is not applied to all layers
+    scalableNestingSEI->m_snNumLayersMinus1 = (uint32_t) targetLayers.size() - 1;  //nesting_num_layers_minus1
+    for (int i=0; i <= scalableNestingSEI->m_snNumLayersMinus1; i++ )
+    {
+      scalableNestingSEI->m_snLayerId[i] = targetLayers[i];
+    }
+  }
+#else
   //KJS: OLS and layer targeting needs to be fixed for the actual OLSs in the bitstream
   scalableNestingSEI->m_snOlsFlag = 1;         // If the nested SEI messages are picture buffering SEI messages, picture timing SEI messages or sub-picture timing SEI messages, nesting_ols_flag shall be equal to 1, by default case
   scalableNestingSEI->m_snNumOlssMinus1 =  1;  // by default the nesting scalable SEI message applies to two OLSs.
@@ -423,6 +465,7 @@ void SEIEncoder::initSEIScalableNesting(SEIScalableNesting *scalableNestingSEI,
   scalableNestingSEI->m_snAllLayersFlag = 1; // nesting is not applied to all layers
   scalableNestingSEI->m_snNumLayersMinus1 = 2 - 1;  //nesting_num_layers_minus1
   scalableNestingSEI->m_snLayerId[0] = 0;
+#endif
   if (!subpictureIDs.empty())
   {
     scalableNestingSEI->m_snSubpicFlag = 1;
diff --git a/source/Lib/EncoderLib/SEIEncoder.h b/source/Lib/EncoderLib/SEIEncoder.h
index 2af62bb11b96afea025377c286a63e7f90009b22..bc2984c5f2e5300303b1b4108e2a5f5de47fbe04 100644
--- a/source/Lib/EncoderLib/SEIEncoder.h
+++ b/source/Lib/EncoderLib/SEIEncoder.h
@@ -72,8 +72,11 @@ public:
 #if U0033_ALTERNATIVE_TRANSFER_CHARACTERISTICS_SEI
   void initSEIAlternativeTransferCharacteristics(SEIAlternativeTransferCharacteristics *sei);
 #endif
+#if JVET_R0294_SUBPIC_HASH
+  void initSEIScalableNesting(SEIScalableNesting *scalableNestingSEI, SEIMessages &nestedSEIs, const std::vector<int> &targetOLSs, const std::vector<int> &targetLayers, const std::vector<uint16_t> &subpictureIDs);
+#else
   void initSEIScalableNesting(SEIScalableNesting *scalableNestingSEI, SEIMessages &nestedSEIs, const std::vector<uint16_t> &subpictureIDs);
-  // trailing SEIs
+#endif
   void initDecodedPictureHashSEI(SEIDecodedPictureHash *sei, PelUnitBuf& pic, std::string &rHashString, const BitDepths &bitDepths);
   void initSEIErp(SEIEquirectangularProjection *sei);
   void initSEISphereRotation(SEISphereRotation *sei);