Commit 1999b5e3 authored by Philipp Merkle's avatar Philipp Merkle
Browse files

JVET-N0217: matrix-based intra prediction (MIP)

parent 21f33cdd
......@@ -330,6 +330,9 @@ void EncApp::xInitLibCfg()
#if JVET_N0242_NON_LINEAR_ALF
m_cEncLib.setUseNonLinearAlfLuma ( m_useNonLinearAlfLuma );
m_cEncLib.setUseNonLinearAlfChroma ( m_useNonLinearAlfChroma );
#endif
#if JVET_N0217_MATRIX_INTRAPRED
m_cEncLib.setUseMIP ( m_MIP );
#endif
m_cEncLib.setCrossComponentPredictionEnabledFlag ( m_crossComponentPredictionEnabledFlag );
m_cEncLib.setUseReconBasedCrossCPredictionEstimate ( m_reconBasedCrossCPredictionEstimate );
......
......@@ -914,6 +914,9 @@ bool EncAppCfg::parseCfg( int argc, char* argv[] )
#if JVET_N0242_NON_LINEAR_ALF
("UseNonLinearAlfLuma", m_useNonLinearAlfLuma, true, "Non-linear adaptive loop filters for Luma Channel")
("UseNonLinearAlfChroma", m_useNonLinearAlfChroma, true, "Non-linear adaptive loop filters for Chroma Channels")
#endif
#if JVET_N0217_MATRIX_INTRAPRED
("MIP", m_MIP, true, "Enable MIP (matrix-based intra prediction)")
#endif
// Unit definition parameters
("MaxCUWidth", m_uiMaxCUWidth, 64u)
......@@ -3219,6 +3222,9 @@ void EncAppCfg::xPrintParameter()
msg(VERBOSE, "(Sigal:%s ", m_reshapeSignalType==0? "SDR" : "HDR-PQ");
msg(VERBOSE, ") ");
}
#if JVET_N0217_MATRIX_INTRAPRED
msg(VERBOSE, "MIP:%d ", m_MIP);
#endif
msg(VERBOSE, "EncDbOpt:%d ", m_encDbOpt);
msg( VERBOSE, "\nFAST TOOL CFG: " );
msg( VERBOSE, "LCTUFast:%d ", m_useFastLCTU );
......
......@@ -312,6 +312,9 @@ protected:
bool m_useNonLinearAlfLuma;
bool m_useNonLinearAlfChroma;
#endif
#if JVET_N0217_MATRIX_INTRAPRED
bool m_MIP;
#endif
int m_numSplitThreads;
......
......@@ -259,6 +259,16 @@ public:
}
};
#if JVET_N0217_MATRIX_INTRAPRED
struct AvailableInfo
{
int maxPosTop;
int maxPosLeft;
AvailableInfo() : maxPosTop(0), maxPosLeft(0) {}
AvailableInfo(const int top, const int left) : maxPosTop(top), maxPosLeft(left) {}
};
#endif
#endif
......@@ -173,6 +173,11 @@ static const int MAX_VPS_NUH_RESERVED_ZERO_LAYER_ID_PLUS1 = 1;
static const int MAXIMUM_INTRA_FILTERED_WIDTH = 16;
static const int MAXIMUM_INTRA_FILTERED_HEIGHT = 16;
#if JVET_N0217_MATRIX_INTRAPRED
static const int MIP_MAX_WIDTH = 64;
static const int MIP_MAX_HEIGHT = 64;
#endif
#if JVET_N0178_IMPLICIT_BDOF_SPLIT
static const int MAX_BDOF_APPLICATION_REGION = 16;
#endif
......@@ -236,9 +241,17 @@ static const uint32_t NUM_TRAFO_MODES_MTS = 6; ///<
static const uint32_t MTS_INTRA_MAX_CU_SIZE = 32; ///< Max Intra CU size applying EMT, supported values: 8, 16, 32, 64, 128
static const uint32_t MTS_INTER_MAX_CU_SIZE = 32; ///< Max Inter CU size applying EMT, supported values: 8, 16, 32, 64, 128
static const int NUM_MOST_PROBABLE_MODES = 6;
#if JVET_N0217_MATRIX_INTRAPRED
static const int NUM_MPM_MIP = 3; ///< number of most probable modes for MIP
#endif
static const int LM_SYMBOL_NUM = (1 + NUM_LMC_MODE);
#if JVET_N0217_MATRIX_INTRAPRED
static const int MAX_NUM_MIP_MODE = 35; ///< maximum number of MIP modes
static const int FAST_UDI_MAX_RDMODE_NUM = (NUM_LUMA_MODE + MAX_NUM_MIP_MODE); ///< maximum number of RD comparison in fast-UDI estimation loop
#else
static const int FAST_UDI_MAX_RDMODE_NUM = NUM_LUMA_MODE; ///< maximum number of RD comparison in fast-UDI estimation loop
#endif
static const int MDCS_ANGLE_LIMIT = 9; ///< 0 = Horizontal/vertical only, 1 = Horizontal/vertical +/- 1, 2 = Horizontal/vertical +/- 2 etc...
......
......@@ -598,3 +598,21 @@ void MergeCtx::setMmvdMergeCandiInfo(PredictionUnit& pu, int candIdx)
PU::restrictBiPredMergeCandsOne(pu);
}
#if JVET_N0217_MATRIX_INTRAPRED
unsigned DeriveCtx::CtxMipFlag( const CodingUnit& cu )
{
const CodingStructure *cs = cu.cs;
unsigned ctxId = 0;
const CodingUnit *cuLeft = cs->getCURestricted( cu.lumaPos().offset( -1, 0 ), cu, CH_L );
ctxId = (cuLeft && cuLeft->mipFlag) ? 1 : 0;
const CodingUnit *cuAbove = cs->getCURestricted( cu.lumaPos().offset( 0, -1 ), cu, CH_L );
ctxId += (cuAbove && cuAbove->mipFlag) ? 1 : 0;
ctxId = (cu.lwidth() > 2*cu.lheight() || cu.lheight() > 2*cu.lwidth()) ? 3 : ctxId;
return ctxId;
}
#endif
\ No newline at end of file
......@@ -389,6 +389,9 @@ unsigned CtxTriangleFlag( const CodingUnit& cu );
#endif
unsigned CtxPredModeFlag( const CodingUnit& cu );
unsigned CtxIBCFlag(const CodingUnit& cu);
#if JVET_N0217_MATRIX_INTRAPRED
unsigned CtxMipFlag ( const CodingUnit& cu );
#endif
}
#endif // __CONTEXTMODELLING__
......@@ -304,9 +304,15 @@ const CtxSet ContextSetCfg::PredMode = ContextSetCfg::addCtxSet
const CtxSet ContextSetCfg::MultiRefLineIdx = ContextSetCfg::addCtxSet
({
#if JVET_N0217_MATRIX_INTRAPRED
{ 105, 212, CNU, },
{ 133, 212, CNU, },
{ 134, 169, CNU, },
#else
{ 90, 212, CNU, },
{ 118, 212, CNU, },
{ 119, 169, CNU, },
#endif
{ 8, 8, DWS, },
});
......@@ -337,6 +343,24 @@ const CtxSet ContextSetCfg::IntraChromaPredMode = ContextSetCfg::addCtxSet
{ 5, 8, 9,},
});
#if JVET_N0217_MATRIX_INTRAPRED
const CtxSet ContextSetCfg::MipFlag = ContextSetCfg::addCtxSet
({
{ 182, 183, 184, 153},
{ 182, 183, 184, 153},
{ 151, 197, 169, 153},
{ 9, 9, 9, 0},
});
const CtxSet ContextSetCfg::MipMode = ContextSetCfg::addCtxSet
({
{ 196, },
{ 196, },
{ 168, },
{ 8, }
});
#endif
const CtxSet ContextSetCfg::DeltaQP = ContextSetCfg::addCtxSet
({
{ 154, 154, 154,},
......@@ -695,10 +719,17 @@ const CtxSet ContextSetCfg::MTSIndex = ContextSetCfg::addCtxSet
const CtxSet ContextSetCfg::ISPMode = ContextSetCfg::addCtxSet
({
#if JVET_N0217_MATRIX_INTRAPRED
{ 151, 154, },
{ 165, 169, },
{ 166, 169, },
{ 9, 4, },
#else
{ 152, 154, },
{ 166, 154, },
{ 152, 154, },
{ 8, 5, },
#endif
});
const CtxSet ContextSetCfg::SbtFlag = ContextSetCfg::addCtxSet
......
......@@ -213,6 +213,10 @@ public:
static const CtxSet IntraLumaPlanarFlag;
#endif
static const CtxSet IntraChromaPredMode;
#if JVET_N0217_MATRIX_INTRAPRED
static const CtxSet MipFlag;
static const CtxSet MipMode;
#endif
static const CtxSet DeltaQP;
static const CtxSet InterDir;
static const CtxSet RefPic;
......
......@@ -1441,6 +1441,10 @@ bool IntraPrediction::useFilteredIntraRefSamples( const ComponentID &compID, con
if( pu.cu->ispMode && isLuma(compID) ) { return false; }
#if JVET_N0217_MATRIX_INTRAPRED
if( PU::isMIP( pu, chType ) ) { return false; }
#endif
if( !modeSpecific ) { return true; }
if (pu.multiRefIdx) { return false; }
......@@ -2272,4 +2276,28 @@ void IntraPrediction::xGetLMParameters(const PredictionUnit &pu, const Component
}
}
#if JVET_N0217_MATRIX_INTRAPRED
void IntraPrediction::initIntraMip( const PredictionUnit &pu )
{
CHECK( pu.lwidth() > MIP_MAX_WIDTH || pu.lheight() > MIP_MAX_HEIGHT, "Error: block size not supported for MIP" );
// derive above and left availability
AvailableInfo availInfo = PU::getAvailableInfoLuma(pu);
// prepare input (boundary) data for prediction
m_matrixIntraPred.prepareInputForPred(pu.cs->picture->getRecoBuf(COMPONENT_Y), pu.Y(), pu.cu->slice->getSPS()->getBitDepth(CHANNEL_TYPE_LUMA), availInfo);
}
void IntraPrediction::predIntraMip( const ComponentID compId, PelBuf &piPred, const PredictionUnit &pu )
{
CHECK( compId != COMPONENT_Y, "Error: chroma not supported" );
CHECK( pu.lwidth() > MIP_MAX_WIDTH || pu.lheight() > MIP_MAX_HEIGHT, "Error: block size not supported for MIP" );
CHECK( pu.lwidth() != (1 << g_aucLog2[pu.lwidth()]) || pu.lheight() != (1 << g_aucLog2[pu.lheight()]), "Error: expecting blocks of size 2^M x 2^N" );
// generate mode-specific prediction
const int bitDepth = pu.cu->slice->getSPS()->getBitDepth(CHANNEL_TYPE_LUMA);
m_matrixIntraPred.predBlock( pu.Y(), pu.intraDir[CHANNEL_TYPE_LUMA], piPred, bitDepth );
}
#endif
//! \}
......@@ -44,6 +44,9 @@
#include "Buffer.h"
#include "Picture.h"
#if JVET_N0217_MATRIX_INTRAPRED
#include "MatrixIntraPrediction.h"
#endif
//! \ingroup CommonLib
//! \{
......@@ -102,6 +105,10 @@ private:
Pel* m_piTemp;
Pel* m_pMdlmTemp; // for MDLM mode
#if JVET_N0217_MATRIX_INTRAPRED
MatrixIntraPrediction m_matrixIntraPred;
#endif
protected:
ChromaFormat m_currChromaFormat;
......@@ -158,7 +165,14 @@ public:
/// set parameters from CU data for accessing intra data
void initIntraPatternChType (const CodingUnit &cu, const CompArea &area, const bool forceRefFilterFlag = false); // use forceRefFilterFlag to get both filtered and unfiltered buffers
static bool useFilteredIntraRefSamples( const ComponentID &compID, const PredictionUnit &pu, bool modeSpecific, const UnitArea &tuArea );
#if JVET_N0217_MATRIX_INTRAPRED
// Matrix-based intra prediction
void initIntraMip (const PredictionUnit &pu);
void predIntraMip (const ComponentID compId, PelBuf &piPred, const PredictionUnit &pu);
#endif
static bool useFilteredIntraRefSamples( const ComponentID &compID, const PredictionUnit &pu, bool modeSpecific, const UnitArea &tuArea );
static bool useDPCMForFirstPassIntraEstimation(const PredictionUnit &pu, const uint32_t &uiDirMode);
void geneWeightedPred (const ComponentID compId, PelBuf &pred, const PredictionUnit &pu, Pel *srcBuf);
......
This diff is collapsed.
/* The copyright in this software is being made available under the BSD
* License, included below. This software may be subject to other third party
* and contributor rights, including patent rights, and no such rights are
* granted under this license.
*
* Copyright (c) 2010-2019, ITU/ISO/IEC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of the ITU/ISO/IEC nor the names of its contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
/** \file MatrixIntraPrediction.h
\brief matrix-based intra prediction class (header)
*/
#ifndef __MATRIXINTRAPPREDICTION__
#define __MATRIXINTRAPPREDICTION__
#include "Unit.h"
#if JVET_N0217_MATRIX_INTRAPRED
static const int MIP_MAX_INPUT_SIZE = 8;
static const int MIP_MAX_REDUCED_OUTPUT_SAMPLES = 64;
namespace Mip
{
class PredictorMIP
{
public:
PredictorMIP();
void deriveBoundaryData(const CPelBuf& src, const Area& block, const int bitDepth, const AvailableInfo &availInfo);
void getPrediction (int* const result, const int modeIdx, const int bitDepth);
private:
static_vector<int, MIP_MAX_INPUT_SIZE> m_reducedBoundary; // downsampled boundary of a block
static_vector<int, MIP_MAX_INPUT_SIZE> m_reducedBoundaryTransposed; // downsampled, transposed boundary of a block
static_vector<int, MIP_MAX_WIDTH> m_boundaryForUpsamplingTop; // top boundary samples for upsampling
static_vector<int, MIP_MAX_HEIGHT> m_boundaryForUpsamplingLeft; // left boundary samples for upsampling
Size m_blockSize;
int m_numModes;
Size m_reducedBoundarySize;
Size m_reducedPredictionSize;
Size m_boundarySizeForUpsampling;
unsigned int m_upsmpFactorHor;
unsigned int m_upsmpFactorVer;
void initPredBlockParams(const Size& block);
static void boundaryDownsampling1D( int* reducedDst, int* fullSrcAndIntermediateDst, const SizeType srcLen, const SizeType dstLen, const bool saveIntermediate, const SizeType intermediateLen );
static void doDownsampling( int* dst, const int* src, const SizeType srcLen, const SizeType dstLen );
void predictionUpsampling( int* const dst, const int* const src, const bool transpose ) const;
static void predictionUpsampling1D( int* const dst, const int* const src, const int* const bndry,
const SizeType srcSizeUpsmpDim, const SizeType srcSizeOrthDim,
const SizeType srcStep, const SizeType srcStride,
const SizeType dstStep, const SizeType dstStride,
const unsigned int upsmpFactor );
void getMatrixBias( const short*& matrix, const short*& bias, const int modeIdx ) const;
void getShifts( int &shiftMatrix, int &shiftBias, const int modeIdx, const int bitDepth ) const;
bool isTransposed( const int modeIdx ) const;
int getWeightIdx( const int modeIdx ) const;
void xComputeMatrixTimesRedBndryPlusBias( int*const result, const int* const input,
const short*matrix, const short*bias,
const bool leave_hor_out, const bool leave_ver_out,
const int right_shift_result, const int left_shift_bias,
const bool transpose, const bool needUpsampling );
};
}
class MatrixIntraPrediction
{
public:
MatrixIntraPrediction();
Mip::PredictorMIP m_predictorMip;
void prepareInputForPred(const CPelBuf &src, const Area& puArea, const int bitDepth, const AvailableInfo &availInfo);
void predBlock( const Size &puSize, const int modeIdx, PelBuf &dst, const int bitDepth );
};
#endif // JVET_N0217_MATRIX_INTRAPRED
#endif //__MATRIXINTRAPPREDICTION__
This diff is collapsed.
......@@ -617,6 +617,32 @@ const uint8_t g_chroma422IntraAngleMappingTable[NUM_INTRA_MODE] =
//0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, DM
{ 0, 1, 2, 2, 2, 2, 2, 2, 2, 3, 4, 6, 8, 10, 12, 13, 14, 16, 18, 20, 22, 23, 24, 26, 28, 30, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 44, 44, 45, 46, 46, 46, 47, 48, 48, 48, 49, 50, 51, 52, 52, 52, 53, 54, 54, 54, 55, 56, 56, 56, 57, 58, 59, 60, DM_CHROMA_IDX };
#if JVET_N0217_MATRIX_INTRAPRED
extern const uint8_t g_intraMode65to33AngMapping[NUM_INTRA_MODE] =
// H D V
//0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, DM
{ 0, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, 25, 25, 26, 26, 27, 27, 28, 28, 29, 29, 30, 30, 31, 31, 32, 32, 33, 33, 34, DM_CHROMA_IDX };
const uint8_t g_mapMipToAngular65[3][MAX_NUM_MIP_MODE] =
{
{ 0, 18, 18, 0, 18, 0, 12, 0, 18, 2, 18, 12, 18, 18, 1, 18, 18, 0, 0, 50, 0, 50, 0, 56, 0, 50, 66, 50, 56, 50, 50, 1, 50, 50, 50 },
{ 0, 1, 0, 1, 0, 22, 18, 18, 1, 0, 1, 0, 1, 0, 44, 0, 50, 1, 0 },
{ 1, 1, 1, 1, 18, 0, 1, 0, 1, 50, 0 },
};
const uint8_t g_mapAngular33ToMip[3][35] =
{
{ 17, 17, 17, 9, 9, 9, 9, 17, 17, 17, 17, 17, 17, 17, 5, 5, 5, 5, 34, 22, 22, 22, 22, 34, 34, 34, 34, 34, 34, 34, 26, 26, 26, 26, 26 },
{ 0, 0, 10, 10, 10, 10, 10, 4, 6, 7, 7, 7, 5, 5, 0, 0, 3, 3, 12, 12, 12, 12, 14, 14, 14, 16, 16, 16, 15, 13, 1, 1, 1, 1, 1 },
{ 5, 1, 3, 3, 3, 3, 0, 0, 0, 4, 4, 4, 5, 1, 1, 1, 1, 1, 6, 6, 6, 6, 6, 10, 10, 9, 9, 9, 9, 9, 8, 8, 8, 8, 8 },
};
const int g_sortedMipMpms[3][NUM_MPM_MIP] =
{
{ 17, 34, 5 },
{ 0, 7, 16 },
{ 1, 4, 6 },
};
#endif
......
......@@ -88,6 +88,12 @@ extern const int g_invQuantScales[SCALING_LIST_REM_NUM]; // IQ(QP%6)
static const int g_numTransformMatrixSizes = 6;
static const int g_transformMatrixShift[TRANSFORM_NUMBER_OF_DIRECTIONS] = { 6, 6 };
#if JVET_N0217_MATRIX_INTRAPRED
extern const uint8_t g_intraMode65to33AngMapping[NUM_INTRA_MODE];
extern const uint8_t g_mapMipToAngular65[3][MAX_NUM_MIP_MODE];
extern const uint8_t g_mapAngular33ToMip[3][35];
extern const int g_sortedMipMpms [3][NUM_MPM_MIP];
#endif
// ====================================================================================================================
// Luma QP to Chroma QP mapping
......
......@@ -1864,6 +1864,9 @@ SPS::SPS()
, m_LadfQpOffset { 0 }
, m_LadfIntervalLowerBound { 0 }
#endif
#if JVET_N0217_MATRIX_INTRAPRED
, m_MIP ( false )
#endif
{
for(int ch=0; ch<MAX_NUM_CHANNEL_TYPE; ch++)
{
......
......@@ -1129,7 +1129,9 @@ private:
int m_LadfQpOffset[MAX_LADF_INTERVALS];
int m_LadfIntervalLowerBound[MAX_LADF_INTERVALS];
#endif
#if JVET_N0217_MATRIX_INTRAPRED
bool m_MIP;
#endif
public:
......@@ -1412,6 +1414,10 @@ public:
bool getUseMHIntra () const { return m_MHIntra; }
void setUseTriangle ( bool b ) { m_Triangle = b; }
bool getUseTriangle () const { return m_Triangle; }
#if JVET_N0217_MATRIX_INTRAPRED
void setUseMIP ( bool b ) { m_MIP = b; }
bool getUseMIP () const { return m_MIP; }
#endif
};
......
......@@ -50,6 +50,9 @@
#include <assert.h>
#include <cassert>
#define JVET_N0217_MATRIX_INTRAPRED 1 // matrix-based intra prediction (MIP)
#define JVET_N0400_SIGNAL_TRIANGLE_CAND_NUM 1 // JVET-N0400, JVET-N0447, JVET-N0500, JVET-N0851, align triangle merge candidate number and regular merge candidate number
#define JVET_N0413_RDPCM 1 // Residual DPCM JVET-N0413/N0214
......
......@@ -286,6 +286,10 @@ CodingUnit& CodingUnit::operator=( const CodingUnit& other )
shareParentSize = other.shareParentSize;
smvdMode = other.smvdMode;
ispMode = other.ispMode;
#if JVET_N0217_MATRIX_INTRAPRED
mipFlag = other.mipFlag;
#endif
return *this;
}
......@@ -322,6 +326,9 @@ void CodingUnit::initData()
shareParentSize.height = -1;
smvdMode = 0;
ispMode = 0;
#if JVET_N0217_MATRIX_INTRAPRED
mipFlag = false;
#endif
}
const uint8_t CodingUnit::checkAllowedSbt() const
......
Supports Markdown
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