Commit 3d61f1db authored by Laszlo Agocs's avatar Laszlo Agocs

Make some structs comparable and hashable

Focus on those that are used in combination with the pipeline state
and starting a renderpass.
parent 0856f98d
......@@ -416,6 +416,39 @@ QRhiColorClearValue::QRhiColorClearValue(float r, float g, float b, float a)
{
}
/*!
Returns \c true if the colors in the two QRhiColorClearValue objects \a a
and \a b are equal.
\relates QRhiColorClearValue
*/
bool operator==(const QRhiColorClearValue &a, const QRhiColorClearValue &b) Q_DECL_NOTHROW
{
return a.rgba() == b.rgba();
}
/*!
Returns \c false if the colors in the two QRhiColorClearValue objects \a a
and \a b are equal; otherwise returns \c true.
\relates QRhiColorClearValue
*/
bool operator!=(const QRhiColorClearValue &a, const QRhiColorClearValue &b) Q_DECL_NOTHROW
{
return !(a == b);
}
/*!
Returns the hash value for \a v, using \a seed to seed the calculation.
\relates QRhiColorClearValue
*/
uint qHash(const QRhiColorClearValue &v, uint seed) Q_DECL_NOTHROW
{
const QVector4D c = v.rgba();
return qFloor((c.x() + c.y() + c.z() + c.w()) * 1000) + seed;
}
/*!
\class QRhiDepthStencilClearValue
\inmodule QtRhi
......@@ -442,6 +475,39 @@ QRhiDepthStencilClearValue::QRhiDepthStencilClearValue(float d, quint32 s)
{
}
/*!
Returns \c true if the values in the two QRhiDepthStencilClearValue objects
\a a and \a b are equal.
\relates QRhiDepthStencilClearValue
*/
bool operator==(const QRhiDepthStencilClearValue &a, const QRhiDepthStencilClearValue &b) Q_DECL_NOTHROW
{
return a.depthClearValue() == b.depthClearValue()
&& a.stencilClearValue() == b.stencilClearValue();
}
/*!
Returns \c false if the values in the two QRhiDepthStencilClearValue
objects \a a and \a b are equal; otherwise returns \c true.
\relates QRhiDepthStencilClearValue
*/
bool operator!=(const QRhiDepthStencilClearValue &a, const QRhiDepthStencilClearValue &b) Q_DECL_NOTHROW
{
return !(a == b);
}
/*!
Returns the hash value for \a v, using \a seed to seed the calculation.
\relates QRhiDepthStencilClearValue
*/
uint qHash(const QRhiDepthStencilClearValue &v, uint seed) Q_DECL_NOTHROW
{
return seed * (qFloor(v.depthClearValue() * 100) + v.stencilClearValue());
}
/*!
\class QRhiViewport
\inmodule QtRhi
......@@ -494,6 +560,41 @@ QRhiViewport::QRhiViewport(float x, float y, float w, float h, float minDepth, f
{
}
/*!
Returns \c true if the values in the two QRhiViewport objects
\a a and \a b are equal.
\relates QRhiViewport
*/
bool operator==(const QRhiViewport &a, const QRhiViewport &b) Q_DECL_NOTHROW
{
return a.viewport() == b.viewport()
&& a.minDepth() == b.minDepth()
&& a.maxDepth() == b.maxDepth();
}
/*!
Returns \c false if the values in the two QRhiViewport
objects \a a and \a b are equal; otherwise returns \c true.
\relates QRhiViewport
*/
bool operator!=(const QRhiViewport &a, const QRhiViewport &b) Q_DECL_NOTHROW
{
return !(a == b);
}
/*!
Returns the hash value for \a v, using \a seed to seed the calculation.
\relates QRhiViewport
*/
uint qHash(const QRhiViewport &v, uint seed) Q_DECL_NOTHROW
{
const QVector4D r = v.viewport();
return seed + r.x() + r.y() + r.z() + r.w() + qFloor(v.minDepth() * 100) + qFloor(v.maxDepth() * 100);
}
/*!
\class QRhiScissor
\inmodule QtRhi
......@@ -527,6 +628,39 @@ QRhiScissor::QRhiScissor(int x, int y, int w, int h)
{
}
/*!
Returns \c true if the values in the two QRhiScissor objects
\a a and \a b are equal.
\relates QRhiScissor
*/
bool operator==(const QRhiScissor &a, const QRhiScissor &b) Q_DECL_NOTHROW
{
return a.scissor() == b.scissor();
}
/*!
Returns \c false if the values in the two QRhiScissor
objects \a a and \a b are equal; otherwise returns \c true.
\relates QRhiScissor
*/
bool operator!=(const QRhiScissor &a, const QRhiScissor &b) Q_DECL_NOTHROW
{
return !(a == b);
}
/*!
Returns the hash value for \a v, using \a seed to seed the calculation.
\relates QRhiScissor
*/
uint qHash(const QRhiScissor &v, uint seed) Q_DECL_NOTHROW
{
const QVector4D r = v.scissor();
return seed + r.x() + r.y() + r.z() + r.w();
}
/*!
\class QRhiVertexInputBinding
\inmodule QtRhi
......@@ -608,6 +742,40 @@ QRhiVertexInputBinding::QRhiVertexInputBinding(quint32 stride, Classification cl
{
}
/*!
Returns \c true if the values in the two QRhiVertexInputBinding objects
\a a and \a b are equal.
\relates QRhiVertexInputBinding
*/
bool operator==(const QRhiVertexInputBinding &a, const QRhiVertexInputBinding &b) Q_DECL_NOTHROW
{
return a.stride() == b.stride()
&& a.classification() == b.classification()
&& a.instanceStepRate() == b.instanceStepRate();
}
/*!
Returns \c false if the values in the two QRhiVertexInputBinding
objects \a a and \a b are equal; otherwise returns \c true.
\relates QRhiVertexInputBinding
*/
bool operator!=(const QRhiVertexInputBinding &a, const QRhiVertexInputBinding &b) Q_DECL_NOTHROW
{
return !(a == b);
}
/*!
Returns the hash value for \a v, using \a seed to seed the calculation.
\relates QRhiVertexInputBinding
*/
uint qHash(const QRhiVertexInputBinding &v, uint seed) Q_DECL_NOTHROW
{
return seed + v.stride() + v.classification();
}
/*!
\class QRhiVertexInputAttribute
\inmodule QtRhi
......@@ -709,6 +877,41 @@ QRhiVertexInputAttribute::QRhiVertexInputAttribute(int binding, int location, Fo
{
}
/*!
Returns \c true if the values in the two QRhiVertexInputAttribute objects
\a a and \a b are equal.
\relates QRhiVertexInputAttribute
*/
bool operator==(const QRhiVertexInputAttribute &a, const QRhiVertexInputAttribute &b) Q_DECL_NOTHROW
{
return a.binding() == b.binding()
&& a.location() == b.location()
&& a.format() == b.format()
&& a.offset() == b.offset();
}
/*!
Returns \c false if the values in the two QRhiVertexInputAttribute
objects \a a and \a b are equal; otherwise returns \c true.
\relates QRhiVertexInputAttribute
*/
bool operator!=(const QRhiVertexInputAttribute &a, const QRhiVertexInputAttribute &b) Q_DECL_NOTHROW
{
return !(a == b);
}
/*!
Returns the hash value for \a v, using \a seed to seed the calculation.
\relates QRhiVertexInputAttribute
*/
uint qHash(const QRhiVertexInputAttribute &v, uint seed) Q_DECL_NOTHROW
{
return seed + v.binding() + v.location() + v.format() + v.offset();
}
/*!
\class QRhiVertexInputLayout
\inmodule QtRhi
......@@ -725,6 +928,39 @@ QRhiVertexInputLayout::QRhiVertexInputLayout()
{
}
/*!
Returns \c true if the values in the two QRhiVertexInputLayout objects
\a a and \a b are equal.
\relates QRhiVertexInputLayout
*/
bool operator==(const QRhiVertexInputLayout &a, const QRhiVertexInputLayout &b) Q_DECL_NOTHROW
{
return a.bindings() == b.bindings()
&& a.attributes() == b.attributes();
}
/*!
Returns \c false if the values in the two QRhiVertexInputLayout
objects \a a and \a b are equal; otherwise returns \c true.
\relates QRhiVertexInputLayout
*/
bool operator!=(const QRhiVertexInputLayout &a, const QRhiVertexInputLayout &b) Q_DECL_NOTHROW
{
return !(a == b);
}
/*!
Returns the hash value for \a v, using \a seed to seed the calculation.
\relates QRhiVertexInputLayout
*/
uint qHash(const QRhiVertexInputLayout &v, uint seed) Q_DECL_NOTHROW
{
return qHash(v.bindings(), seed) + qHash(v.attributes(), seed);
}
/*!
\class QRhiGraphicsShaderStage
\inmodule QtRhi
......@@ -761,6 +997,39 @@ QRhiGraphicsShaderStage::QRhiGraphicsShaderStage(Type type, const QBakedShader &
{
}
/*!
Returns \c true if the values in the two QRhiGraphicsShaderStage objects
\a a and \a b are equal.
\relates QRhiGraphicsShaderStage
*/
bool operator==(const QRhiGraphicsShaderStage &a, const QRhiGraphicsShaderStage &b) Q_DECL_NOTHROW
{
return a.type() == b.type()
&& a.shader() == b.shader();
}
/*!
Returns \c false if the values in the two QRhiGraphicsShaderStage
objects \a a and \a b are equal; otherwise returns \c true.
\relates QRhiGraphicsShaderStage
*/
bool operator!=(const QRhiGraphicsShaderStage &a, const QRhiGraphicsShaderStage &b) Q_DECL_NOTHROW
{
return !(a == b);
}
/*!
Returns the hash value for \a v, using \a seed to seed the calculation.
\relates QRhiGraphicsShaderStage
*/
uint qHash(const QRhiGraphicsShaderStage &v, uint seed) Q_DECL_NOTHROW
{
return v.type() + qHash(v.shader(), seed);
}
/*!
\class QRhiColorAttachment
\inmodule QtRhi
......@@ -1927,8 +2196,8 @@ QRhiShaderResourceBinding QRhiShaderResourceBinding::sampledTexture(
objects \a a and \a b are equal.
\relates QRhiShaderResourceBinding
*/
bool operator==(const QRhiShaderResourceBinding &a, const QRhiShaderResourceBinding &b)
*/
bool operator==(const QRhiShaderResourceBinding &a, const QRhiShaderResourceBinding &b) Q_DECL_NOTHROW
{
if (a.d == b.d)
return true;
......@@ -1969,13 +2238,18 @@ bool operator==(const QRhiShaderResourceBinding &a, const QRhiShaderResourceBind
objects \a a and \a b are equal; otherwise returns \c true.
\relates QRhiShaderResourceBinding
*/
bool operator!=(const QRhiShaderResourceBinding &a, const QRhiShaderResourceBinding &b)
*/
bool operator!=(const QRhiShaderResourceBinding &a, const QRhiShaderResourceBinding &b) Q_DECL_NOTHROW
{
return !(a == b);
}
uint qHash(const QRhiShaderResourceBinding &b, uint seed)
/*!
Returns the hash value for \a b, using \a seed to seed the calculation.
\relates QRhiShaderResourceBinding
*/
uint qHash(const QRhiShaderResourceBinding &b, uint seed) Q_DECL_NOTHROW
{
const char *u = reinterpret_cast<const char *>(&b.d->u);
return seed + b.d->binding + 10 * b.d->stage + 100 * b.d->type
......
......@@ -78,6 +78,10 @@ private:
Q_DECLARE_TYPEINFO(QRhiColorClearValue, Q_MOVABLE_TYPE);
Q_RHI_EXPORT bool operator==(const QRhiColorClearValue &a, const QRhiColorClearValue &b) Q_DECL_NOTHROW;
Q_RHI_EXPORT bool operator!=(const QRhiColorClearValue &a, const QRhiColorClearValue &b) Q_DECL_NOTHROW;
Q_RHI_EXPORT uint qHash(const QRhiColorClearValue &v, uint seed = 0) Q_DECL_NOTHROW;
class Q_RHI_EXPORT QRhiDepthStencilClearValue
{
public:
......@@ -97,6 +101,10 @@ private:
Q_DECLARE_TYPEINFO(QRhiDepthStencilClearValue, Q_MOVABLE_TYPE);
Q_RHI_EXPORT bool operator==(const QRhiDepthStencilClearValue &a, const QRhiDepthStencilClearValue &b) Q_DECL_NOTHROW;
Q_RHI_EXPORT bool operator!=(const QRhiDepthStencilClearValue &a, const QRhiDepthStencilClearValue &b) Q_DECL_NOTHROW;
Q_RHI_EXPORT uint qHash(const QRhiDepthStencilClearValue &v, uint seed = 0) Q_DECL_NOTHROW;
class Q_RHI_EXPORT QRhiViewport
{
public:
......@@ -120,6 +128,10 @@ private:
Q_DECLARE_TYPEINFO(QRhiViewport, Q_MOVABLE_TYPE);
Q_RHI_EXPORT bool operator==(const QRhiViewport &a, const QRhiViewport &b) Q_DECL_NOTHROW;
Q_RHI_EXPORT bool operator!=(const QRhiViewport &a, const QRhiViewport &b) Q_DECL_NOTHROW;
Q_RHI_EXPORT uint qHash(const QRhiViewport &v, uint seed = 0) Q_DECL_NOTHROW;
class Q_RHI_EXPORT QRhiScissor
{
public:
......@@ -135,6 +147,10 @@ private:
Q_DECLARE_TYPEINFO(QRhiScissor, Q_MOVABLE_TYPE);
Q_RHI_EXPORT bool operator==(const QRhiScissor &a, const QRhiScissor &b) Q_DECL_NOTHROW;
Q_RHI_EXPORT bool operator!=(const QRhiScissor &a, const QRhiScissor &b) Q_DECL_NOTHROW;
Q_RHI_EXPORT uint qHash(const QRhiScissor &v, uint seed = 0) Q_DECL_NOTHROW;
class Q_RHI_EXPORT QRhiVertexInputBinding
{
public:
......@@ -164,6 +180,10 @@ private:
Q_DECLARE_TYPEINFO(QRhiVertexInputBinding, Q_MOVABLE_TYPE);
Q_RHI_EXPORT bool operator==(const QRhiVertexInputBinding &a, const QRhiVertexInputBinding &b) Q_DECL_NOTHROW;
Q_RHI_EXPORT bool operator!=(const QRhiVertexInputBinding &a, const QRhiVertexInputBinding &b) Q_DECL_NOTHROW;
Q_RHI_EXPORT uint qHash(const QRhiVertexInputBinding &v, uint seed = 0) Q_DECL_NOTHROW;
class Q_RHI_EXPORT QRhiVertexInputAttribute
{
public:
......@@ -202,6 +222,10 @@ private:
Q_DECLARE_TYPEINFO(QRhiVertexInputAttribute, Q_MOVABLE_TYPE);
Q_RHI_EXPORT bool operator==(const QRhiVertexInputAttribute &a, const QRhiVertexInputAttribute &b) Q_DECL_NOTHROW;
Q_RHI_EXPORT bool operator!=(const QRhiVertexInputAttribute &a, const QRhiVertexInputAttribute &b) Q_DECL_NOTHROW;
Q_RHI_EXPORT uint qHash(const QRhiVertexInputAttribute &v, uint seed = 0) Q_DECL_NOTHROW;
class Q_RHI_EXPORT QRhiVertexInputLayout
{
public:
......@@ -221,6 +245,10 @@ private:
Q_DECLARE_TYPEINFO(QRhiVertexInputLayout, Q_MOVABLE_TYPE);
Q_RHI_EXPORT bool operator==(const QRhiVertexInputLayout &a, const QRhiVertexInputLayout &b) Q_DECL_NOTHROW;
Q_RHI_EXPORT bool operator!=(const QRhiVertexInputLayout &a, const QRhiVertexInputLayout &b) Q_DECL_NOTHROW;
Q_RHI_EXPORT uint qHash(const QRhiVertexInputLayout &v, uint seed = 0) Q_DECL_NOTHROW;
class Q_RHI_EXPORT QRhiGraphicsShaderStage
{
public:
......@@ -248,6 +276,10 @@ private:
Q_DECLARE_TYPEINFO(QRhiGraphicsShaderStage, Q_MOVABLE_TYPE);
Q_RHI_EXPORT bool operator==(const QRhiGraphicsShaderStage &a, const QRhiGraphicsShaderStage &b) Q_DECL_NOTHROW;
Q_RHI_EXPORT bool operator!=(const QRhiGraphicsShaderStage &a, const QRhiGraphicsShaderStage &b) Q_DECL_NOTHROW;
Q_RHI_EXPORT uint qHash(const QRhiGraphicsShaderStage &s, uint seed = 0) Q_DECL_NOTHROW;
class Q_RHI_EXPORT QRhiShaderResourceBinding
{
public:
......@@ -277,9 +309,9 @@ public:
private:
QRhiShaderResourceBindingPrivate *d;
friend class QRhiShaderResourceBindingPrivate;
friend Q_RHI_EXPORT bool operator==(const QRhiShaderResourceBinding &, const QRhiShaderResourceBinding &);
friend Q_RHI_EXPORT bool operator!=(const QRhiShaderResourceBinding &, const QRhiShaderResourceBinding &);
friend Q_RHI_EXPORT uint qHash(const QRhiShaderResourceBinding &, uint);
friend Q_RHI_EXPORT bool operator==(const QRhiShaderResourceBinding &, const QRhiShaderResourceBinding &) Q_DECL_NOTHROW;
friend Q_RHI_EXPORT bool operator!=(const QRhiShaderResourceBinding &, const QRhiShaderResourceBinding &) Q_DECL_NOTHROW;
friend Q_RHI_EXPORT uint qHash(const QRhiShaderResourceBinding &, uint) Q_DECL_NOTHROW;
#ifndef QT_NO_DEBUG_STREAM
friend Q_RHI_EXPORT QDebug operator<<(QDebug, const QRhiShaderResourceBinding &);
#endif
......@@ -287,9 +319,9 @@ private:
Q_DECLARE_OPERATORS_FOR_FLAGS(QRhiShaderResourceBinding::StageFlags)
Q_RHI_EXPORT bool operator==(const QRhiShaderResourceBinding &a, const QRhiShaderResourceBinding &b);
Q_RHI_EXPORT bool operator!=(const QRhiShaderResourceBinding &a, const QRhiShaderResourceBinding &b);
Q_RHI_EXPORT uint qHash(const QRhiShaderResourceBinding &b, uint seed = 0);
Q_RHI_EXPORT bool operator==(const QRhiShaderResourceBinding &a, const QRhiShaderResourceBinding &b) Q_DECL_NOTHROW;
Q_RHI_EXPORT bool operator!=(const QRhiShaderResourceBinding &a, const QRhiShaderResourceBinding &b) Q_DECL_NOTHROW;
Q_RHI_EXPORT uint qHash(const QRhiShaderResourceBinding &b, uint seed = 0) Q_DECL_NOTHROW;
#ifndef QT_NO_DEBUG_STREAM
Q_RHI_EXPORT QDebug operator<<(QDebug, const QRhiShaderResourceBinding &);
#endif
......
......@@ -436,6 +436,21 @@ QBakedShader QBakedShader::fromSerialized(const QByteArray &data)
return bs;
}
bool operator==(const QBakedShader &lhs, const QBakedShader &rhs) Q_DECL_NOTHROW
{
return lhs.d->stage == rhs.d->stage
&& lhs.d->shaders == rhs.d->shaders;
// do not bother with desc, if the shader code is the same, the description must match too
}
uint qHash(const QBakedShader &s, uint seed) Q_DECL_NOTHROW
{
uint h = s.stage();
for (auto it = s.d->shaders.constBegin(), itEnd = s.d->shaders.constEnd(); it != itEnd; ++it)
h += qHash(it.key(), seed) + qHash(it.value().shader(), seed);
return h;
}
bool operator==(const QBakedShaderVersion &lhs, const QBakedShaderVersion &rhs) Q_DECL_NOTHROW
{
return lhs.version() == rhs.version() && lhs.flags() == rhs.flags();
......@@ -452,8 +467,12 @@ bool operator==(const QBakedShaderCode &lhs, const QBakedShaderCode &rhs) Q_DECL
return lhs.shader() == rhs.shader() && lhs.entryPoint() == rhs.entryPoint();
}
#ifndef QT_NO_DEBUG_STREAM
uint qHash(const QBakedShaderKey &k, uint seed) Q_DECL_NOTHROW
{
return seed + 10 * k.source() + k.sourceVersion().version() + k.sourceVersion().flags() + k.sourceVariant();
}
#ifndef QT_NO_DEBUG_STREAM
QDebug operator<<(QDebug dbg, const QBakedShader &bs)
{
const QBakedShaderPrivate *d = bs.d;
......@@ -468,11 +487,6 @@ QDebug operator<<(QDebug dbg, const QBakedShader &bs)
return dbg;
}
uint qHash(const QBakedShaderKey &k, uint seed)
{
return seed + 10 * k.source() + k.sourceVersion().version() + k.sourceVersion().flags() + k.sourceVariant();
}
QDebug operator<<(QDebug dbg, const QBakedShaderKey &k)
{
QDebugStateSaver saver(dbg);
......@@ -488,7 +502,6 @@ QDebug operator<<(QDebug dbg, const QBakedShaderVersion &v)
dbg.nospace() << "Version(" << v.version() << " " << v.flags() << ")";
return dbg;
}
#endif // QT_NO_DEBUG_STREAM
QT_END_NAMESPACE
......@@ -173,11 +173,21 @@ public:
private:
QBakedShaderPrivate *d;
friend struct QBakedShaderPrivate;
friend Q_SHADERTOOLS_EXPORT bool operator==(const QBakedShader &, const QBakedShader &) Q_DECL_NOTHROW;
friend Q_SHADERTOOLS_EXPORT uint qHash(const QBakedShader &, uint) Q_DECL_NOTHROW;
#ifndef QT_NO_DEBUG_STREAM
friend Q_SHADERTOOLS_EXPORT QDebug operator<<(QDebug, const QBakedShader &);
#endif
};
Q_SHADERTOOLS_EXPORT bool operator==(const QBakedShader &lhs, const QBakedShader &rhs) Q_DECL_NOTHROW;
Q_SHADERTOOLS_EXPORT uint qHash(const QBakedShader &s, uint seed = 0) Q_DECL_NOTHROW;
inline bool operator!=(const QBakedShader &lhs, const QBakedShader &rhs) Q_DECL_NOTHROW
{
return !(lhs == rhs);
}
Q_SHADERTOOLS_EXPORT bool operator==(const QBakedShaderVersion &lhs, const QBakedShaderVersion &rhs) Q_DECL_NOTHROW;
Q_SHADERTOOLS_EXPORT bool operator==(const QBakedShaderKey &lhs, const QBakedShaderKey &rhs) Q_DECL_NOTHROW;
Q_SHADERTOOLS_EXPORT bool operator==(const QBakedShaderCode &lhs, const QBakedShaderCode &rhs) Q_DECL_NOTHROW;
......@@ -197,7 +207,7 @@ inline bool operator!=(const QBakedShaderCode &lhs, const QBakedShaderCode &rhs)
return !(lhs == rhs);
}
Q_SHADERTOOLS_EXPORT uint qHash(const QBakedShaderKey &k, uint seed = 0);
Q_SHADERTOOLS_EXPORT uint qHash(const QBakedShaderKey &k, uint seed = 0) Q_DECL_NOTHROW;
#ifndef QT_NO_DEBUG_STREAM
Q_SHADERTOOLS_EXPORT QDebug operator<<(QDebug, const QBakedShader &);
......
......@@ -436,6 +436,7 @@ void tst_QShaderBaker::bakedShaderImplicitSharing()
QCOMPARE(s1.availableShaders().count(), 1);
QVERIFY(s1.availableShaders().contains(QBakedShaderKey(QBakedShaderKey::SpirvShader, QBakedShaderVersion(100))));
QCOMPARE(s0.stage(), s1.stage());
QCOMPARE(s0, s1);
s1.detach();
QVERIFY(QBakedShaderPrivate::get(&s0) != QBakedShaderPrivate::get(&s1));
......@@ -444,6 +445,7 @@ void tst_QShaderBaker::bakedShaderImplicitSharing()
QCOMPARE(s1.availableShaders().count(), 1);
QVERIFY(s1.availableShaders().contains(QBakedShaderKey(QBakedShaderKey::SpirvShader, QBakedShaderVersion(100))));
QCOMPARE(s0.stage(), s1.stage());
QCOMPARE(s0, s1);
}
{
......@@ -465,6 +467,7 @@ void tst_QShaderBaker::bakedShaderImplicitSharing()
QCOMPARE(d1.inputVariables().count(), 2);
QCOMPARE(d1.outputVariables().count(), 1);
QCOMPARE(d1.uniformBlocks().count(), 1);
QVERIFY(s0 != s1);
}
}
......
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