Merge pull request #215 from Qining/spec-constants-operations
SPV: Spec Constant Operations
This commit is contained in:
commit
3dad506ac6
@ -130,7 +130,7 @@ protected:
|
||||
void addMemberDecoration(spv::Id id, int member, spv::Decoration dec, unsigned value);
|
||||
spv::Id createSpvConstant(const glslang::TIntermTyped&);
|
||||
spv::Id createSpvConstantFromConstUnionArray(const glslang::TType& type, const glslang::TConstUnionArray&, int& nextConst, bool specConstant);
|
||||
spv::Id createSpvConstantFromConstSubTree(const glslang::TIntermTyped* subTree);
|
||||
spv::Id createSpvConstantFromConstSubTree(glslang::TIntermTyped* subTree);
|
||||
bool isTrivialLeaf(const glslang::TIntermTyped* node);
|
||||
bool isTrivial(const glslang::TIntermTyped* node);
|
||||
spv::Id createShortCircuit(glslang::TOperator, glslang::TIntermTyped& left, glslang::TIntermTyped& right);
|
||||
@ -3854,15 +3854,36 @@ spv::Id TGlslangToSpvTraverser::createSpvConstantFromConstUnionArray(const glsla
|
||||
return builder.makeCompositeConstant(typeId, spvConsts);
|
||||
}
|
||||
|
||||
namespace {
|
||||
class SpecConstantOpModeGuard {
|
||||
public:
|
||||
SpecConstantOpModeGuard(spv::Builder* builder)
|
||||
: builder_(builder) {
|
||||
previous_flag_ = builder->isInSpecConstCodeGenMode();
|
||||
builder->setToSpecConstCodeGenMode();
|
||||
}
|
||||
~SpecConstantOpModeGuard() {
|
||||
previous_flag_ ? builder_->setToSpecConstCodeGenMode()
|
||||
: builder_->setToNormalCodeGenMode();
|
||||
}
|
||||
|
||||
private:
|
||||
spv::Builder* builder_;
|
||||
bool previous_flag_;
|
||||
};
|
||||
}
|
||||
|
||||
// Create constant ID from const initializer sub tree.
|
||||
spv::Id TGlslangToSpvTraverser::createSpvConstantFromConstSubTree(
|
||||
const glslang::TIntermTyped* subTree) {
|
||||
glslang::TIntermTyped* subTree)
|
||||
{
|
||||
const glslang::TType& glslangType = subTree->getType();
|
||||
spv::Id typeId = convertGlslangToSpvType(glslangType);
|
||||
bool is_spec_const = subTree->getType().getQualifier().isSpecConstant();
|
||||
if (const glslang::TIntermAggregate* an = subTree->getAsAggregate()) {
|
||||
// Aggregate node, we should generate OpConstantComposite or
|
||||
// OpSpecConstantComposite instruction.
|
||||
|
||||
std::vector<spv::Id> const_constituents;
|
||||
for (auto NI = an->getSequence().begin(); NI != an->getSequence().end();
|
||||
NI++) {
|
||||
@ -3881,17 +3902,27 @@ spv::Id TGlslangToSpvTraverser::createSpvConstantFromConstSubTree(
|
||||
return const_constituents.front();
|
||||
}
|
||||
|
||||
} else if (const glslang::TIntermBinary* bn = subTree->getAsBinaryNode()) {
|
||||
} else if (glslang::TIntermBinary* bn = subTree->getAsBinaryNode()) {
|
||||
// Binary operation node, we should generate OpSpecConstantOp <binary op>
|
||||
// This case should only happen when Specialization Constants are involved.
|
||||
spv::MissingFunctionality("OpSpecConstantOp <binary op> not implemented");
|
||||
return spv::NoResult;
|
||||
|
||||
} else if (const glslang::TIntermUnary* un = subTree->getAsUnaryNode()) {
|
||||
// Spec constants defined with binary operations and other constants requires
|
||||
// OpSpecConstantOp instruction.
|
||||
SpecConstantOpModeGuard set_to_spec_const_mode(&builder);
|
||||
|
||||
bn->traverse(this);
|
||||
return accessChainLoad(bn->getType());
|
||||
|
||||
} else if (glslang::TIntermUnary* un = subTree->getAsUnaryNode()) {
|
||||
// Unary operation node, similar to binary operation node, should only
|
||||
// happen when specialization constants are involved.
|
||||
spv::MissingFunctionality("OpSpecConstantOp <unary op> not implemented");
|
||||
return spv::NoResult;
|
||||
|
||||
// Spec constants defined with unary operations and other constants requires
|
||||
// OpSpecConstantOp instruction.
|
||||
SpecConstantOpModeGuard set_to_spec_const_mode(&builder);
|
||||
|
||||
un->traverse(this);
|
||||
return accessChainLoad(un->getType());
|
||||
|
||||
} else if (const glslang::TIntermConstantUnion* cn = subTree->getAsConstantUnion()) {
|
||||
// ConstantUnion node, should redirect to
|
||||
|
||||
@ -64,7 +64,8 @@ Builder::Builder(unsigned int magicNumber) :
|
||||
builderNumber(magicNumber),
|
||||
buildPoint(0),
|
||||
uniqueId(0),
|
||||
mainFunction(0)
|
||||
mainFunction(0),
|
||||
generatingOpCodeForSpecConst(false)
|
||||
{
|
||||
clearAccessChain();
|
||||
}
|
||||
@ -1063,6 +1064,11 @@ Id Builder::createArrayLength(Id base, unsigned int member)
|
||||
|
||||
Id Builder::createCompositeExtract(Id composite, Id typeId, unsigned index)
|
||||
{
|
||||
// Generate code for spec constants if in spec constant operation
|
||||
// generation mode.
|
||||
if (generatingOpCodeForSpecConst) {
|
||||
return createSpecConstantOp(OpCompositeExtract, typeId, {composite}, {index});
|
||||
}
|
||||
Instruction* extract = new Instruction(getUniqueId(), typeId, OpCompositeExtract);
|
||||
extract->addIdOperand(composite);
|
||||
extract->addImmediateOperand(index);
|
||||
@ -1073,6 +1079,11 @@ Id Builder::createCompositeExtract(Id composite, Id typeId, unsigned index)
|
||||
|
||||
Id Builder::createCompositeExtract(Id composite, Id typeId, std::vector<unsigned>& indexes)
|
||||
{
|
||||
// Generate code for spec constants if in spec constant operation
|
||||
// generation mode.
|
||||
if (generatingOpCodeForSpecConst) {
|
||||
return createSpecConstantOp(OpCompositeExtract, typeId, {composite}, indexes);
|
||||
}
|
||||
Instruction* extract = new Instruction(getUniqueId(), typeId, OpCompositeExtract);
|
||||
extract->addIdOperand(composite);
|
||||
for (int i = 0; i < (int)indexes.size(); ++i)
|
||||
@ -1170,6 +1181,11 @@ void Builder::createMemoryBarrier(unsigned executionScope, unsigned memorySemant
|
||||
// An opcode that has one operands, a result id, and a type
|
||||
Id Builder::createUnaryOp(Op opCode, Id typeId, Id operand)
|
||||
{
|
||||
// Generate code for spec constants if in spec constant operation
|
||||
// generation mode.
|
||||
if (generatingOpCodeForSpecConst) {
|
||||
return createSpecConstantOp(opCode, typeId, {operand}, {});
|
||||
}
|
||||
Instruction* op = new Instruction(getUniqueId(), typeId, opCode);
|
||||
op->addIdOperand(operand);
|
||||
buildPoint->addInstruction(std::unique_ptr<Instruction>(op));
|
||||
@ -1179,6 +1195,11 @@ Id Builder::createUnaryOp(Op opCode, Id typeId, Id operand)
|
||||
|
||||
Id Builder::createBinOp(Op opCode, Id typeId, Id left, Id right)
|
||||
{
|
||||
// Generate code for spec constants if in spec constant operation
|
||||
// generation mode.
|
||||
if (generatingOpCodeForSpecConst) {
|
||||
return createSpecConstantOp(opCode, typeId, {left, right}, {});
|
||||
}
|
||||
Instruction* op = new Instruction(getUniqueId(), typeId, opCode);
|
||||
op->addIdOperand(left);
|
||||
op->addIdOperand(right);
|
||||
@ -1208,6 +1229,20 @@ Id Builder::createOp(Op opCode, Id typeId, const std::vector<Id>& operands)
|
||||
return op->getResultId();
|
||||
}
|
||||
|
||||
Id Builder::createSpecConstantOp(Op opCode, Id typeId, const std::vector<Id>& operands, const std::vector<unsigned>& literals)
|
||||
{
|
||||
Instruction* op = new Instruction(getUniqueId(), typeId, OpSpecConstantOp);
|
||||
op->addImmediateOperand((unsigned) opCode);
|
||||
for (auto it = operands.cbegin(); it != operands.cend(); ++it)
|
||||
op->addIdOperand(*it);
|
||||
for (auto it = literals.cbegin(); it != literals.cend(); ++it)
|
||||
op->addImmediateOperand(*it);
|
||||
module.mapInstruction(op);
|
||||
constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(op));
|
||||
|
||||
return op->getResultId();
|
||||
}
|
||||
|
||||
Id Builder::createFunctionCall(spv::Function* function, std::vector<spv::Id>& args)
|
||||
{
|
||||
Instruction* op = new Instruction(getUniqueId(), function->getReturnType(), OpFunctionCall);
|
||||
@ -1225,6 +1260,9 @@ Id Builder::createRvalueSwizzle(Decoration precision, Id typeId, Id source, std:
|
||||
if (channels.size() == 1)
|
||||
return setPrecision(createCompositeExtract(source, typeId, channels.front()), precision);
|
||||
|
||||
if (generatingOpCodeForSpecConst) {
|
||||
return setPrecision(createSpecConstantOp(OpVectorShuffle, typeId, {source, source}, channels), precision);
|
||||
}
|
||||
Instruction* swizzle = new Instruction(getUniqueId(), typeId, OpVectorShuffle);
|
||||
assert(isVector(source));
|
||||
swizzle->addIdOperand(source);
|
||||
@ -1290,10 +1328,23 @@ Id Builder::smearScalar(Decoration precision, Id scalar, Id vectorType)
|
||||
if (numComponents == 1)
|
||||
return scalar;
|
||||
|
||||
Instruction* smear = new Instruction(getUniqueId(), vectorType, OpCompositeConstruct);
|
||||
Instruction* smear = nullptr;
|
||||
if (generatingOpCodeForSpecConst) {
|
||||
auto members = std::vector<spv::Id>(numComponents, scalar);
|
||||
// 'scalar' can not be spec constant here. All spec constant involved
|
||||
// promotion is done in createSpvConstantFromConstUnionArray(). This
|
||||
// 'if' branch is only accessed when 'scalar' is used in the def-chain
|
||||
// of other vector type spec constants. In such cases, all the
|
||||
// instructions needed to promote 'scalar' to a vector type constants
|
||||
// should be added at module level.
|
||||
auto result_id = makeCompositeConstant(vectorType, members, false);
|
||||
smear = module.getInstruction(result_id);
|
||||
} else {
|
||||
smear = new Instruction(getUniqueId(), vectorType, OpCompositeConstruct);
|
||||
for (int c = 0; c < numComponents; ++c)
|
||||
smear->addIdOperand(scalar);
|
||||
buildPoint->addInstruction(std::unique_ptr<Instruction>(smear));
|
||||
}
|
||||
|
||||
return setPrecision(smear->getResultId(), precision);
|
||||
}
|
||||
|
||||
@ -262,6 +262,7 @@ public:
|
||||
Id createTriOp(Op, Id typeId, Id operand1, Id operand2, Id operand3);
|
||||
Id createOp(Op, Id typeId, const std::vector<Id>& operands);
|
||||
Id createFunctionCall(spv::Function*, std::vector<spv::Id>&);
|
||||
Id createSpecConstantOp(Op, Id typeId, const std::vector<spv::Id>& operands, const std::vector<unsigned>& literals);
|
||||
|
||||
// Take an rvalue (source) and a set of channels to extract from it to
|
||||
// make a new rvalue, which is returned.
|
||||
@ -521,6 +522,13 @@ public:
|
||||
void createConditionalBranch(Id condition, Block* thenBlock, Block* elseBlock);
|
||||
void createLoopMerge(Block* mergeBlock, Block* continueBlock, unsigned int control);
|
||||
|
||||
// Sets to generate opcode for specialization constants.
|
||||
void setToSpecConstCodeGenMode() { generatingOpCodeForSpecConst = true; }
|
||||
// Sets to generate opcode for non-specialization constants (normal mode).
|
||||
void setToNormalCodeGenMode() { generatingOpCodeForSpecConst = false; }
|
||||
// Check if the builder is generating code for spec constants.
|
||||
bool isInSpecConstCodeGenMode() { return generatingOpCodeForSpecConst; }
|
||||
|
||||
protected:
|
||||
Id makeIntConstant(Id typeId, unsigned value, bool specConstant);
|
||||
Id findScalarConstant(Op typeClass, Op opcode, Id typeId, unsigned value) const;
|
||||
@ -544,6 +552,7 @@ public:
|
||||
Block* buildPoint;
|
||||
Id uniqueId;
|
||||
Function* mainFunction;
|
||||
bool generatingOpCodeForSpecConst;
|
||||
AccessChain accessChain;
|
||||
|
||||
// special blocks of instructions for output
|
||||
|
||||
116
Test/baseResults/spv.specConstantOperations.vert.out
Normal file
116
Test/baseResults/spv.specConstantOperations.vert.out
Normal file
@ -0,0 +1,116 @@
|
||||
spv.specConstantOperations.vert
|
||||
Warning, version 450 is not yet complete; most version-specific features are present, but some are missing.
|
||||
|
||||
|
||||
Linked vertex stage:
|
||||
|
||||
|
||||
// Module Version 10000
|
||||
// Generated by (magic number): 80001
|
||||
// Id's are bound by 94
|
||||
|
||||
Capability Shader
|
||||
Capability Float64
|
||||
1: ExtInstImport "GLSL.std.450"
|
||||
MemoryModel Logical GLSL450
|
||||
EntryPoint Vertex 4 "main"
|
||||
Source GLSL 450
|
||||
Name 4 "main"
|
||||
Decorate 7 SpecId 200
|
||||
Decorate 9 SpecId 201
|
||||
Decorate 11 SpecId 202
|
||||
Decorate 12 SpecId 203
|
||||
2: TypeVoid
|
||||
3: TypeFunction 2
|
||||
6: TypeFloat 32
|
||||
7: 6(float) SpecConstant 1078530010
|
||||
8: TypeInt 32 1
|
||||
9: 8(int) SpecConstant 10
|
||||
10: TypeInt 32 0
|
||||
11: 10(int) SpecConstant 100
|
||||
12: 8(int) SpecConstant 4294967286
|
||||
13: TypeFloat 64
|
||||
14: 13(float) SpecConstantOp 115 7
|
||||
15: 6(float) SpecConstantOp 115 14
|
||||
16: 8(int) SpecConstantOp 126 9
|
||||
17: 8(int) SpecConstantOp 200 9
|
||||
18: 8(int) Constant 2
|
||||
19: 8(int) SpecConstantOp 128 9 18
|
||||
20: 8(int) SpecConstantOp 128 9 18
|
||||
21: 8(int) Constant 3
|
||||
22: 8(int) SpecConstantOp 130 20 21
|
||||
23: 8(int) Constant 4
|
||||
24: 8(int) SpecConstantOp 130 19 23
|
||||
25: 8(int) SpecConstantOp 132 12 18
|
||||
26: 10(int) Constant 2
|
||||
27: 10(int) SpecConstantOp 132 11 26
|
||||
28: 8(int) Constant 5
|
||||
29: 8(int) SpecConstantOp 135 25 28
|
||||
30: 10(int) Constant 5
|
||||
31: 10(int) SpecConstantOp 134 27 30
|
||||
32: 8(int) SpecConstantOp 139 12 23
|
||||
33: 10(int) Constant 4
|
||||
34: 10(int) SpecConstantOp 137 11 33
|
||||
35: 8(int) SpecConstantOp 132 12 21
|
||||
36: 8(int) SpecConstantOp 135 35 28
|
||||
37: 8(int) Constant 10
|
||||
38: 8(int) SpecConstantOp 195 12 37
|
||||
39: 8(int) Constant 20
|
||||
40: 10(int) SpecConstantOp 194 11 39
|
||||
41: 8(int) Constant 1
|
||||
42: 8(int) SpecConstantOp 196 12 41
|
||||
43: 10(int) SpecConstantOp 196 11 18
|
||||
44: 8(int) Constant 256
|
||||
45: 8(int) SpecConstantOp 197 12 44
|
||||
46: 10(int) Constant 512
|
||||
47: 10(int) SpecConstantOp 198 11 46
|
||||
48: TypeBool
|
||||
49: 48(bool) SpecConstantOp 177 9 12
|
||||
50: 48(bool) SpecConstantOp 170 11 11
|
||||
51: 48(bool) SpecConstantOp 173 9 12
|
||||
52: TypeVector 8(int) 4
|
||||
53: 8(int) Constant 30
|
||||
54: 52(ivec4) SpecConstantComposite 39 53 9 9
|
||||
55: TypeVector 10(int) 4
|
||||
56: 10(int) Constant 4294967295
|
||||
57: 10(int) Constant 4294967294
|
||||
58: 55(ivec4) SpecConstantComposite 11 11 56 57
|
||||
59: TypeVector 6(float) 4
|
||||
60: 6(float) Constant 1067450368
|
||||
61: 59(fvec4) SpecConstantComposite 7 60 7 60
|
||||
62: TypeVector 13(float) 4
|
||||
63: 62(fvec4) SpecConstantOp 115 61
|
||||
64: 59(fvec4) SpecConstantOp 115 63
|
||||
65: 52(ivec4) SpecConstantOp 200 54
|
||||
66: 52(ivec4) SpecConstantOp 126 54
|
||||
67: 52(ivec4) ConstantComposite 18 18 18 18
|
||||
68: 52(ivec4) SpecConstantOp 128 54 67
|
||||
69: 52(ivec4) SpecConstantOp 128 54 67
|
||||
70: 52(ivec4) ConstantComposite 21 21 21 21
|
||||
71: 52(ivec4) SpecConstantOp 130 69 70
|
||||
72: 52(ivec4) ConstantComposite 23 23 23 23
|
||||
73: 52(ivec4) SpecConstantOp 130 71 72
|
||||
74: 52(ivec4) SpecConstantOp 132 54 67
|
||||
75: 52(ivec4) ConstantComposite 28 28 28 28
|
||||
76: 52(ivec4) SpecConstantOp 135 74 75
|
||||
77: 52(ivec4) SpecConstantOp 139 54 72
|
||||
78: 52(ivec4) ConstantComposite 37 37 37 37
|
||||
79: 52(ivec4) SpecConstantOp 195 54 78
|
||||
80: 52(ivec4) SpecConstantOp 196 54 67
|
||||
81: 8(int) Constant 1024
|
||||
82: 52(ivec4) ConstantComposite 81 81 81 81
|
||||
83: 52(ivec4) SpecConstantOp 197 54 82
|
||||
84: 10(int) Constant 2048
|
||||
85: 55(ivec4) ConstantComposite 84 84 84 84
|
||||
86: 55(ivec4) SpecConstantOp 198 58 85
|
||||
87: 10(int) Constant 0
|
||||
88: 8(int) SpecConstantOp 81 54 0
|
||||
89: TypeVector 8(int) 2
|
||||
90: 89(ivec2) SpecConstantOp 79 54 54 1(GLSL.std.450) 0
|
||||
91: TypeVector 8(int) 3
|
||||
92: 91(ivec3) SpecConstantOp 79 54 54 2 1(GLSL.std.450) 0
|
||||
93: 52(ivec4) SpecConstantOp 79 54 54 1(GLSL.std.450) 2 0 3
|
||||
4(main): 2 Function None 3
|
||||
5: Label
|
||||
Return
|
||||
FunctionEnd
|
||||
90
Test/spv.specConstantOperations.vert
Normal file
90
Test/spv.specConstantOperations.vert
Normal file
@ -0,0 +1,90 @@
|
||||
#version 450
|
||||
|
||||
layout(constant_id = 200) const float sp_float = 3.1415926;
|
||||
layout(constant_id = 201) const int sp_int = 10;
|
||||
layout(constant_id = 202) const uint sp_uint = 100;
|
||||
layout(constant_id = 203) const int sp_sint = -10;
|
||||
|
||||
|
||||
//
|
||||
// Scalars
|
||||
//
|
||||
|
||||
// Size convert
|
||||
const double float_to_double = double(sp_float);
|
||||
const float double_to_float = float(float_to_double);
|
||||
|
||||
// Negate and Not
|
||||
const int negate_int = -sp_int;
|
||||
const int not_int = ~sp_int;
|
||||
|
||||
// Add and Subtract
|
||||
const int sp_int_add_two = sp_int + 2;
|
||||
const int sp_int_add_two_sub_three = sp_int + 2 - 3;
|
||||
const int sp_int_add_two_sub_four = sp_int_add_two - 4;
|
||||
|
||||
// Mul, Div and Rem
|
||||
const int sp_sint_mul_two = sp_sint * 2;
|
||||
const uint sp_uint_mul_two = sp_uint * 2;
|
||||
const int sp_sint_mul_two_div_five = sp_sint_mul_two / 5;
|
||||
const uint sp_uint_mul_two_div_five = sp_uint_mul_two / 5;
|
||||
const int sp_sint_rem_four = sp_sint % 4;
|
||||
const uint sp_uint_rem_four = sp_uint % 4;
|
||||
const int sp_sint_mul_three_div_five = sp_sint * 3 / 5;
|
||||
|
||||
// Shift
|
||||
const int sp_sint_shift_right_arithmetic = sp_sint >> 10;
|
||||
const uint sp_uint_shift_right_arithmetic = sp_uint >> 20;
|
||||
const int sp_sint_shift_left = sp_sint << 1;
|
||||
const uint sp_uint_shift_left = sp_uint << 2;
|
||||
|
||||
// Bitwise And, Or, Xor
|
||||
const int sp_sint_or_256 = sp_sint | 0x100;
|
||||
const uint sp_uint_xor_512 = sp_uint ^ 0x200;
|
||||
|
||||
/* // Scalar comparison */
|
||||
const bool sp_int_lt_sp_sint = sp_int < sp_sint;
|
||||
const bool sp_uint_equal_sp_uint = sp_uint == sp_uint;
|
||||
const bool sp_int_gt_sp_sint = sp_int > sp_sint;
|
||||
|
||||
//
|
||||
// Vectors
|
||||
//
|
||||
const ivec4 iv = ivec4(20, 30, sp_int, sp_int);
|
||||
const uvec4 uv = uvec4(sp_uint, sp_uint, -1, -2);
|
||||
const vec4 fv = vec4(sp_float, 1.25, sp_float, 1.25);
|
||||
|
||||
// Size convert
|
||||
const dvec4 fv_to_dv = dvec4(fv);
|
||||
const vec4 dv_to_fv = vec4(fv_to_dv);
|
||||
|
||||
// Negate and Not
|
||||
const ivec4 not_iv = ~iv;
|
||||
const ivec4 negate_iv = -iv;
|
||||
|
||||
// Add and Subtract
|
||||
const ivec4 iv_add_two = iv + 2;
|
||||
const ivec4 iv_add_two_sub_three = iv + 2 - 3;
|
||||
const ivec4 iv_add_two_sub_four = iv_add_two_sub_three - 4;
|
||||
|
||||
// Mul, Div and Rem
|
||||
const ivec4 iv_mul_two = iv * 2;
|
||||
const ivec4 iv_mul_two_div_five = iv_mul_two / 5;
|
||||
const ivec4 iv_rem_four = iv % 4;
|
||||
|
||||
// Shift
|
||||
const ivec4 iv_shift_right_arithmetic = iv >> 10;
|
||||
const ivec4 iv_shift_left = iv << 2;
|
||||
|
||||
// Bitwise And, Or, Xor
|
||||
const ivec4 iv_or_1024 = iv | 0x400;
|
||||
const uvec4 uv_xor_2048 = uv ^ 0x800;
|
||||
|
||||
// Swizzles
|
||||
const int iv_x = iv.x;
|
||||
const ivec2 iv_yx = iv.yx;
|
||||
const ivec3 iv_zyx = iv.zyx;
|
||||
const ivec4 iv_yzxw = iv.yzxw;
|
||||
|
||||
void main() {}
|
||||
|
||||
@ -103,6 +103,7 @@ spv.subpass.frag
|
||||
spv.specConstant.vert
|
||||
spv.specConstant.comp
|
||||
spv.specConstantComposite.vert
|
||||
spv.specConstantOperations.vert
|
||||
# GLSL-level semantics
|
||||
vulkan.frag
|
||||
vulkan.vert
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user