Commit 9c1e6381 authored by Peter Chuang's avatar Peter Chuang Committed by Xiang Li

JVET-O0052: Method-1 TU-level context coded bin constraint

parent e77362ec
......@@ -330,10 +330,15 @@ static const int MMVD_ADD_NUM = (MMVD_MAX_RE
static const int MMVD_MRG_MAX_RD_NUM = MRG_MAX_NUM_CANDS;
static const int MMVD_MRG_MAX_RD_BUF_NUM = (MMVD_MRG_MAX_RD_NUM + 1);///< increase buffer size by 1
#if JVET_O0052_TU_LEVEL_CTX_CODED_BIN_CONSTRAINT
static const int MAX_TU_LEVEL_CTX_CODED_BIN_CONSTRAINT_LUMA = 28;
static const int MAX_TU_LEVEL_CTX_CODED_BIN_CONSTRAINT_CHROMA = 28;
#else
static const int MAX_NUM_REG_BINS_4x4SUBBLOCK = 32; ///< max number of context-coded bins (incl. gt2 bins) per 4x4 subblock
static const int MAX_NUM_GT2_BINS_4x4SUBBLOCK = 4; ///< max number of gt2 bins per 4x4 subblock
static const int MAX_NUM_REG_BINS_2x2SUBBLOCK = 8; ///< max number of context-coded bins (incl. gt2 bins) per 2x2 subblock (chroma)
static const int MAX_NUM_GT2_BINS_2x2SUBBLOCK = 2; ///< max number of gt2 bins per 2x2 subblock (chroma)
#endif
static const int BIO_EXTEND_SIZE = 1;
static const int BIO_TEMP_BUFFER_SIZE = (MAX_CU_SIZE + 2 * BIO_EXTEND_SIZE) * (MAX_CU_SIZE + 2 * BIO_EXTEND_SIZE);
......
......@@ -231,6 +231,10 @@ public:
return auiGoRicePars[ std::min(sum, 31) ];
}
#if JVET_O0052_TU_LEVEL_CTX_CODED_BIN_CONSTRAINT
int regBinLimit;
#endif
private:
// constant
const ComponentID m_compID;
......
......@@ -89,6 +89,12 @@ namespace DQIntern
int nextSbbBelow;
int posX;
int posY;
#if JVET_O0052_TU_LEVEL_CTX_CODED_BIN_CONSTRAINT
ChannelType chType;
int sbtInfo;
int tuWidth;
int tuHeight;
#endif
};
class Rom;
......@@ -370,6 +376,11 @@ namespace DQIntern
void TUParameters::xSetScanInfo( ScanInfo& scanInfo, int scanIdx )
{
#if JVET_O0052_TU_LEVEL_CTX_CODED_BIN_CONSTRAINT
scanInfo.chType = m_chType;
scanInfo.tuWidth = m_width;
scanInfo.tuHeight = m_height;
#endif
scanInfo.sbbSize = m_sbbSize;
scanInfo.numSbb = m_numSbb;
scanInfo.scanIdx = scanIdx;
......@@ -1018,7 +1029,11 @@ namespace DQIntern
int64_t m_rdCost;
uint16_t m_absLevelsAndCtxInit[24]; // 16x8bit for abs levels + 16x16bit for ctx init id
int8_t m_numSigSbb;
#if JVET_O0052_TU_LEVEL_CTX_CODED_BIN_CONSTRAINT
int m_remRegBins;
#else
int8_t m_remRegBins;
#endif
int8_t m_refSbbCtxId;
BinFracBits m_sbbFracBits;
BinFracBits m_sigFracBits;
......@@ -1030,6 +1045,11 @@ namespace DQIntern
const CoeffFracBits*const m_gtxFracBitsArray;
const uint32_t*const m_goRiceZeroArray;
CommonCtx& m_commonCtx;
#if JVET_O0052_TU_LEVEL_CTX_CODED_BIN_CONSTRAINT
public:
unsigned effWidth;
unsigned effHeight;
#endif
};
......@@ -1067,6 +1087,10 @@ namespace DQIntern
{
m_numSigSbb = 1;
m_refSbbCtxId = -1;
#if JVET_O0052_TU_LEVEL_CTX_CODED_BIN_CONSTRAINT
int ctxBinSampleRatio = (scanInfo.chType == CHANNEL_TYPE_LUMA) ? MAX_TU_LEVEL_CTX_CODED_BIN_CONSTRAINT_LUMA : MAX_TU_LEVEL_CTX_CODED_BIN_CONSTRAINT_CHROMA;
m_remRegBins = (effWidth * effHeight *ctxBinSampleRatio) / 16 - (decision.absLevel < 2 ? decision.absLevel : 3);
#else
if ( scanInfo.sbbSize == 4 )
{
m_remRegBins = MAX_NUM_REG_BINS_2x2SUBBLOCK - (decision.absLevel < 2 ? decision.absLevel : 3);
......@@ -1075,6 +1099,7 @@ namespace DQIntern
{
m_remRegBins = MAX_NUM_REG_BINS_4x4SUBBLOCK - (decision.absLevel < 2 ? decision.absLevel : 3);
}
#endif
::memset( m_absLevelsAndCtxInit, 0, 48*sizeof(uint8_t) );
}
......@@ -1258,6 +1283,17 @@ namespace DQIntern
const int sigNSbb = ( ( scanInfo.nextSbbRight ? sbbFlags[ scanInfo.nextSbbRight ] : false ) || ( scanInfo.nextSbbBelow ? sbbFlags[ scanInfo.nextSbbBelow ] : false ) ? 1 : 0 );
currState.m_numSigSbb = 0;
#if JVET_O0052_TU_LEVEL_CTX_CODED_BIN_CONSTRAINT
if (prevState)
{
currState.m_remRegBins = prevState->m_remRegBins;
}
else
{
int ctxBinSampleRatio = (scanInfo.chType == CHANNEL_TYPE_LUMA) ? MAX_TU_LEVEL_CTX_CODED_BIN_CONSTRAINT_LUMA : MAX_TU_LEVEL_CTX_CODED_BIN_CONSTRAINT_CHROMA;
currState.m_remRegBins = (currState.effWidth * currState.effHeight *ctxBinSampleRatio) / 16;
}
#else
if (scanInfo.sbbSize == 4)
{
currState.m_remRegBins = MAX_NUM_REG_BINS_2x2SUBBLOCK;
......@@ -1266,6 +1302,7 @@ namespace DQIntern
{
currState.m_remRegBins = MAX_NUM_REG_BINS_4x4SUBBLOCK;
}
#endif
currState.m_goRicePar = 0;
currState.m_refSbbCtxId = currState.m_stateId;
currState.m_sbbFracBits = m_sbbFlagBits[ sigNSbb ];
......@@ -1537,6 +1574,18 @@ namespace DQIntern
m_startState.init();
#if JVET_O0052_TU_LEVEL_CTX_CODED_BIN_CONSTRAINT
int effectWidth = std::min(32, effWidth);
int effectHeight = std::min(32, effHeight);
for (int k = 0; k < 12; k++)
{
m_allStates[k].effWidth = effectWidth;
m_allStates[k].effHeight = effectHeight;
}
m_startState.effWidth = effectWidth;
m_startState.effHeight = effectHeight;
#endif
//===== populate trellis =====
for( int scanIdx = firstTestPos; scanIdx >= 0; scanIdx-- )
{
......
......@@ -108,7 +108,9 @@ inline uint32_t QuantRDOQ::xGetCodedLevel( double& rd64CodedCost,
const BinFracBits& fracBitsPar,
const BinFracBits& fracBitsGt1,
const BinFracBits& fracBitsGt2,
#if !JVET_O0052_TU_LEVEL_CTX_CODED_BIN_CONSTRAINT
const int remGt2Bins,
#endif
const int remRegBins,
unsigned goRiceZero,
uint16_t ui16AbsGoRice,
......@@ -146,7 +148,11 @@ inline uint32_t QuantRDOQ::xGetCodedLevel( double& rd64CodedCost,
{
double dErr = double( lLevelDouble - ( Intermediate_Int(uiAbsLevel) << iQBits ) );
double dCurrCost = dErr * dErr * errorScale + xGetICost( xGetICRate( uiAbsLevel, fracBitsPar, fracBitsGt1, fracBitsGt2, remGt2Bins, remRegBins, goRiceZero, ui16AbsGoRice, true, maxLog2TrDynamicRange ) );
#if JVET_O0052_TU_LEVEL_CTX_CODED_BIN_CONSTRAINT
double dCurrCost = dErr * dErr * errorScale + xGetICost( xGetICRate( uiAbsLevel, fracBitsPar, fracBitsGt1, fracBitsGt2, remRegBins, goRiceZero, ui16AbsGoRice, true, maxLog2TrDynamicRange ) );
#else
double dCurrCost = dErr * dErr * errorScale + xGetICost( xGetICRate( uiAbsLevel, fracBitsPar, fracBitsGt1, fracBitsGt2, remGt2Bins, remRegBins, goRiceZero, ui16AbsGoRice, true, maxLog2TrDynamicRange ) );
#endif
dCurrCost += dCurrCostSig;
if( dCurrCost < rd64CodedCost )
......@@ -175,7 +181,9 @@ inline int QuantRDOQ::xGetICRate( const uint32_t uiAbsLevel,
const BinFracBits& fracBitsPar,
const BinFracBits& fracBitsGt1,
const BinFracBits& fracBitsGt2,
#if !JVET_O0052_TU_LEVEL_CTX_CODED_BIN_CONSTRAINT
const int remGt2Bins,
#endif
const int remRegBins,
unsigned goRiceZero,
const uint16_t ui16AbsGoRice,
......@@ -639,9 +647,14 @@ void QuantRDOQ::xRateDistOptQuant(TransformUnit &tu, const ComponentID &compID,
double d64BaseCost = 0;
int iLastScanPos = -1;
#if JVET_O0052_TU_LEVEL_CTX_CODED_BIN_CONSTRAINT
int ctxBinSampleRatio = (compID == COMPONENT_Y) ? MAX_TU_LEVEL_CTX_CODED_BIN_CONSTRAINT_LUMA : MAX_TU_LEVEL_CTX_CODED_BIN_CONSTRAINT_CHROMA;
int remRegBins = (uiWidth * uiHeight * ctxBinSampleRatio) >> 4;
#else
bool is2x2subblock = ( iCGSizeM1 == 3 );
int remGt2Bins = ( is2x2subblock ? MAX_NUM_GT2_BINS_2x2SUBBLOCK : MAX_NUM_GT2_BINS_4x4SUBBLOCK );
int remRegBins = ( is2x2subblock ? MAX_NUM_REG_BINS_2x2SUBBLOCK : MAX_NUM_REG_BINS_4x4SUBBLOCK );
#endif
uint32_t goRiceParam = 0;
double *pdCostCoeffGroupSig = m_pdCostCoeffGroupSig;
......@@ -736,16 +749,26 @@ void QuantRDOQ::xRateDistOptQuant(TransformUnit &tu, const ComponentID &compID,
if( iScanPos == iLastScanPos )
{
#if JVET_O0052_TU_LEVEL_CTX_CODED_BIN_CONSTRAINT
uiLevel = xGetCodedLevel( pdCostCoeff[ iScanPos ], pdCostCoeff0[ iScanPos ], pdCostSig[ iScanPos ],
lLevelDouble, uiMaxAbsLevel, nullptr, fracBitsPar, fracBitsGt1, fracBitsGt2, remRegBins, goRiceZero, goRiceParam, iQBits, errorScale, 1, extendedPrecision, maxLog2TrDynamicRange );
#else
uiLevel = xGetCodedLevel( pdCostCoeff[ iScanPos ], pdCostCoeff0[ iScanPos ], pdCostSig[ iScanPos ],
lLevelDouble, uiMaxAbsLevel, nullptr, fracBitsPar, fracBitsGt1, fracBitsGt2, remGt2Bins, remRegBins, goRiceZero, goRiceParam, iQBits, errorScale, 1, extendedPrecision, maxLog2TrDynamicRange );
#endif
}
else
{
DTRACE_COND( ( uiMaxAbsLevel != 0 ), g_trace_ctx, D_RDOQ_MORE, " uiCtxSig=%d", ctxIdSig );
const BinFracBits fracBitsSig = fracBits.getFracBitsArray( ctxIdSig );
#if JVET_O0052_TU_LEVEL_CTX_CODED_BIN_CONSTRAINT
uiLevel = xGetCodedLevel( pdCostCoeff[ iScanPos ], pdCostCoeff0[ iScanPos ], pdCostSig[ iScanPos ],
lLevelDouble, uiMaxAbsLevel, &fracBitsSig, fracBitsPar, fracBitsGt1, fracBitsGt2, remRegBins, goRiceZero, goRiceParam, iQBits, errorScale, 0, extendedPrecision, maxLog2TrDynamicRange );
#else
uiLevel = xGetCodedLevel( pdCostCoeff[ iScanPos ], pdCostCoeff0[ iScanPos ], pdCostSig[ iScanPos ],
lLevelDouble, uiMaxAbsLevel, &fracBitsSig, fracBitsPar, fracBitsGt1, fracBitsGt2, remGt2Bins, remRegBins, goRiceZero, goRiceParam, iQBits, errorScale, 0, extendedPrecision, maxLog2TrDynamicRange );
#endif
sigRateDelta[ uiBlkPos ] = ( remRegBins < 4 ? 0 : fracBitsSig.intBits[1] - fracBitsSig.intBits[0] );
}
......@@ -757,16 +780,27 @@ void QuantRDOQ::xRateDistOptQuant(TransformUnit &tu, const ComponentID &compID,
if( uiLevel > 0 )
{
#if JVET_O0052_TU_LEVEL_CTX_CODED_BIN_CONSTRAINT
int rateNow = xGetICRate( uiLevel, fracBitsPar, fracBitsGt1, fracBitsGt2, remRegBins, goRiceZero, goRiceParam, extendedPrecision, maxLog2TrDynamicRange );
rateIncUp [ uiBlkPos ] = xGetICRate( uiLevel+1, fracBitsPar, fracBitsGt1, fracBitsGt2, remRegBins, goRiceZero, goRiceParam, extendedPrecision, maxLog2TrDynamicRange ) - rateNow;
rateIncDown [ uiBlkPos ] = xGetICRate( uiLevel-1, fracBitsPar, fracBitsGt1, fracBitsGt2, remRegBins, goRiceZero, goRiceParam, extendedPrecision, maxLog2TrDynamicRange ) - rateNow;
#else
int rateNow = xGetICRate( uiLevel, fracBitsPar, fracBitsGt1, fracBitsGt2, remGt2Bins, remRegBins, goRiceZero, goRiceParam, extendedPrecision, maxLog2TrDynamicRange );
rateIncUp [ uiBlkPos ] = xGetICRate( uiLevel+1, fracBitsPar, fracBitsGt1, fracBitsGt2, remGt2Bins, remRegBins, goRiceZero, goRiceParam, extendedPrecision, maxLog2TrDynamicRange ) - rateNow;
rateIncDown [ uiBlkPos ] = xGetICRate( uiLevel-1, fracBitsPar, fracBitsGt1, fracBitsGt2, remGt2Bins, remRegBins, goRiceZero, goRiceParam, extendedPrecision, maxLog2TrDynamicRange ) - rateNow;
#endif
}
else // uiLevel == 0
{
if( remRegBins < 4 )
{
#if JVET_O0052_TU_LEVEL_CTX_CODED_BIN_CONSTRAINT
int rateNow = xGetICRate( uiLevel, fracBitsPar, fracBitsGt1, fracBitsGt2, remRegBins, goRiceZero, goRiceParam, extendedPrecision, maxLog2TrDynamicRange );
rateIncUp [ uiBlkPos ] = xGetICRate( uiLevel+1, fracBitsPar, fracBitsGt1, fracBitsGt2, remRegBins, goRiceZero, goRiceParam, extendedPrecision, maxLog2TrDynamicRange ) - rateNow;
#else
int rateNow = xGetICRate( uiLevel, fracBitsPar, fracBitsGt1, fracBitsGt2, remGt2Bins, remRegBins, goRiceZero, goRiceParam, extendedPrecision, maxLog2TrDynamicRange );
rateIncUp [ uiBlkPos ] = xGetICRate( uiLevel+1, fracBitsPar, fracBitsGt1, fracBitsGt2, remGt2Bins, remRegBins, goRiceZero, goRiceParam, extendedPrecision, maxLog2TrDynamicRange ) - rateNow;
#endif
}
else
{
......@@ -778,8 +812,10 @@ void QuantRDOQ::xRateDistOptQuant(TransformUnit &tu, const ComponentID &compID,
if( ( (iScanPos & iCGSizeM1) == 0 ) && ( iScanPos > 0 ) )
{
#if !JVET_O0052_TU_LEVEL_CTX_CODED_BIN_CONSTRAINT
remGt2Bins = ( is2x2subblock ? MAX_NUM_GT2_BINS_2x2SUBBLOCK : MAX_NUM_GT2_BINS_4x4SUBBLOCK );
remRegBins = ( is2x2subblock ? MAX_NUM_REG_BINS_2x2SUBBLOCK : MAX_NUM_REG_BINS_4x4SUBBLOCK ) - remGt2Bins;
#endif
goRiceParam = 0;
}
else if( remRegBins >= 4 )
......
......@@ -87,7 +87,9 @@ private:
const BinFracBits& fracBitsPar,
const BinFracBits& fracBitsGt1,
const BinFracBits& fracBitsGt2,
#if !JVET_O0052_TU_LEVEL_CTX_CODED_BIN_CONSTRAINT
const int remGt2Bins,
#endif
const int remRegBins,
unsigned goRiceZero,
uint16_t ui16AbsGoRice,
......@@ -100,7 +102,9 @@ private:
const BinFracBits& fracBitsPar,
const BinFracBits& fracBitsGt1,
const BinFracBits& fracBitsGt2,
#if !JVET_O0052_TU_LEVEL_CTX_CODED_BIN_CONSTRAINT
const int remGt2Bins,
#endif
const int remRegBins,
unsigned goRiceZero,
const uint16_t ui16AbsGoRice,
......
......@@ -50,6 +50,8 @@
#include <assert.h>
#include <cassert>
#define JVET_O0052_TU_LEVEL_CTX_CODED_BIN_CONSTRAINT 1 // JVET-O0052 Method-1: TU-level context coded bin constraint
#define JVET_O0216_ALF_COEFF_EG3 1 // JVET-O0216/O0302/O0648: using EG3 for ALF coefficients coding
#define JVET_O0272_LMCS_SIMP_INVERSE_MAPPING 1 // JVET-O0272: LMCS simplified inverse mapping
......
......@@ -764,5 +764,24 @@ void TransformUnit::checkTuNoResidual( unsigned idx )
noResidual = true;
}
}
#if JVET_O0052_TU_LEVEL_CTX_CODED_BIN_CONSTRAINT
int TransformUnit::getTbAreaAfterCoefZeroOut(ComponentID compID) const
{
int tbArea = blocks[compID].width * blocks[compID].height;
int tbZeroOutWidth = blocks[compID].width;
int tbZeroOutHeight = blocks[compID].height;
if ((mtsIdx > MTS_SKIP || (cu->sbtInfo != 0 && blocks[compID].width <= 32 && blocks[compID].height <= 32)) && !cu->transQuantBypass && compID == COMPONENT_Y)
{
tbZeroOutWidth = (blocks[compID].width == 32) ? 16 : tbZeroOutWidth;
tbZeroOutHeight = (blocks[compID].height == 32) ? 16 : tbZeroOutHeight;
}
tbZeroOutWidth = std::min<int>(JVET_C0024_ZERO_OUT_TH, tbZeroOutWidth);
tbZeroOutHeight = std::min<int>(JVET_C0024_ZERO_OUT_TH, tbZeroOutHeight);
tbArea = tbZeroOutWidth * tbZeroOutHeight;
return tbArea;
}
#endif
int TransformUnit::getChromaAdj() const { return m_chromaResScaleInv; }
void TransformUnit::setChromaAdj(int i) { m_chromaResScaleInv = i; }
......@@ -466,6 +466,9 @@ struct TransformUnit : public UnitArea
TransformUnit& operator=(const TransformUnit& other);
void copyComponentFrom (const TransformUnit& other, const ComponentID compID);
void checkTuNoResidual( unsigned idx );
#if JVET_O0052_TU_LEVEL_CTX_CODED_BIN_CONSTRAINT
int getTbAreaAfterCoefZeroOut(ComponentID compID) const;
#endif
CoeffBuf getCoeffs(const ComponentID id);
const CCoeffBuf getCoeffs(const ComponentID id) const;
......
......@@ -2473,6 +2473,10 @@ void CABACReader::residual_coding( TransformUnit& tu, ComponentID compID )
const int stateTransTab = ( tu.cs->slice->getDepQuantEnabledFlag() ? 32040 : 0 );
int state = 0;
#if JVET_O0052_TU_LEVEL_CTX_CODED_BIN_CONSTRAINT
int ctxBinSampleRatio = (compID == COMPONENT_Y) ? MAX_TU_LEVEL_CTX_CODED_BIN_CONSTRAINT_LUMA : MAX_TU_LEVEL_CTX_CODED_BIN_CONSTRAINT_CHROMA;
cctx.regBinLimit = (tu.getTbAreaAfterCoefZeroOut(compID) * ctxBinSampleRatio) >> 4;
#endif
for( int subSetId = ( cctx.scanPosLast() >> cctx.log2CGSize() ); subSetId >= 0; subSetId--)
{
......@@ -2771,8 +2775,12 @@ void CABACReader::residual_coding_subblock( CoeffCodingContext& cctx, TCoeff* co
int firstNZPos = nextSigPos;
int lastNZPos = -1;
int numNonZero = 0;
#if JVET_O0052_TU_LEVEL_CTX_CODED_BIN_CONSTRAINT
int remRegBins = cctx.regBinLimit;
#else
bool is2x2subblock = ( cctx.log2CGSize() == 2 );
int remRegBins = ( is2x2subblock ? MAX_NUM_REG_BINS_2x2SUBBLOCK : MAX_NUM_REG_BINS_4x4SUBBLOCK );
#endif
int firstPosMode2 = minSubPos - 1;
int sigBlkPos[ 1 << MLS_CG_SIZE ];
......@@ -2826,6 +2834,9 @@ void CABACReader::residual_coding_subblock( CoeffCodingContext& cctx, TCoeff* co
state = ( stateTransTable >> ((state<<2)+((coeff[blkPos]&1)<<1)) ) & 3;
}
firstPosMode2 = nextSigPos;
#if JVET_O0052_TU_LEVEL_CTX_CODED_BIN_CONSTRAINT
cctx.regBinLimit = remRegBins;
#endif
//===== 2nd PASS: Go-rice codes =====
......
......@@ -2372,6 +2372,11 @@ void CABACWriter::residual_coding( const TransformUnit& tu, ComponentID compID )
const int stateTab = ( tu.cs->slice->getDepQuantEnabledFlag() ? 32040 : 0 );
int state = 0;
#if JVET_O0052_TU_LEVEL_CTX_CODED_BIN_CONSTRAINT
int ctxBinSampleRatio = (compID == COMPONENT_Y) ? MAX_TU_LEVEL_CTX_CODED_BIN_CONSTRAINT_LUMA : MAX_TU_LEVEL_CTX_CODED_BIN_CONSTRAINT_CHROMA;
cctx.regBinLimit = (tu.getTbAreaAfterCoefZeroOut(compID) * ctxBinSampleRatio) >> 4;
#endif
for( int subSetId = ( cctx.scanPosLast() >> cctx.log2CGSize() ); subSetId >= 0; subSetId--)
{
cctx.initSubblock ( subSetId, sigGroupFlags[subSetId] );
......@@ -2634,8 +2639,12 @@ void CABACWriter::residual_coding_subblock( CoeffCodingContext& cctx, const TCoe
int remAbsLevel = -1;
int numNonZero = 0;
unsigned signPattern = 0;
#if JVET_O0052_TU_LEVEL_CTX_CODED_BIN_CONSTRAINT
int remRegBins = cctx.regBinLimit;
#else
bool is2x2subblock = ( cctx.log2CGSize() == 2 );
int remRegBins = ( is2x2subblock ? MAX_NUM_REG_BINS_2x2SUBBLOCK : MAX_NUM_REG_BINS_4x4SUBBLOCK );
#endif
int firstPosMode2 = minSubPos - 1;
for( ; nextSigPos >= minSubPos && remRegBins >= 4; nextSigPos-- )
......@@ -2689,7 +2698,9 @@ void CABACWriter::residual_coding_subblock( CoeffCodingContext& cctx, const TCoe
state = ( stateTransTable >> ((state<<2)+((Coeff&1)<<1)) ) & 3;
}
firstPosMode2 = nextSigPos;
#if JVET_O0052_TU_LEVEL_CTX_CODED_BIN_CONSTRAINT
cctx.regBinLimit = remRegBins;
#endif
//===== 2nd PASS: Go-rice codes =====
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment