Nothing works
This commit is contained in:
@@ -55,7 +55,7 @@ float4 frag(PixelInput input) : SV_Target
|
||||
[numthreads(64, 1, 1)]
|
||||
void deform(uint currentID: SV_DispatchThreadID)
|
||||
{
|
||||
if(currentID > pParams.indirectDrawBuffer[9] * 4)
|
||||
if(currentID >= pParams.indirectDrawBuffer[9] * 4)
|
||||
return;
|
||||
|
||||
uint bisectorID = currentID / 4;
|
||||
@@ -66,6 +66,7 @@ void deform(uint currentID: SV_DispatchThreadID)
|
||||
currentID = localVertexID < 3 ? bisectorID * 3 + localVertexID : 3 * pParams.geometry.totalNumElements + bisectorID;
|
||||
|
||||
float3 positionWS = pParams.lebPositionBuffer[currentID];
|
||||
float3 positionRWS = positionWS - pViewParams.cameraPosition_WS.xyz;
|
||||
|
||||
float3 positionPS = positionWS;
|
||||
|
||||
@@ -73,5 +74,5 @@ void deform(uint currentID: SV_DispatchThreadID)
|
||||
|
||||
float2 sampleUV = float2(0, 0);
|
||||
|
||||
pParams.currentVertexBuffer[currentID] = positionWS;
|
||||
pParams.currentVertexBuffer[currentID] = positionRWS;
|
||||
}
|
||||
@@ -2,13 +2,13 @@
|
||||
const static int INVALID_POINTER = 4294967295;
|
||||
|
||||
// Possible culling state
|
||||
const static int BACK_FACE_CULLED =-3;
|
||||
const static int FRUSTUM_CULLED =-2;
|
||||
const static int TOO_SMALL =-1;
|
||||
const static int UNCHANGED_ELEMENT= 0;
|
||||
const static int BISECT_ELEMENT= 1;
|
||||
const static int SIMPLIFY_ELEMENT= 2;
|
||||
const static int MERGED_ELEMENT= 3;
|
||||
const static int BACK_FACE_CULLED = -3;
|
||||
const static int FRUSTUM_CULLED = -2;
|
||||
const static int TOO_SMALL = -1;
|
||||
const static int UNCHANGED_ELEMENT = 0;
|
||||
const static int BISECT_ELEMENT = 1;
|
||||
const static int SIMPLIFY_ELEMENT = 2;
|
||||
const static int MERGED_ELEMENT = 3;
|
||||
|
||||
// Bisector flags
|
||||
const static int VISIBLE_BISECTOR = 0x1;
|
||||
|
||||
@@ -1,26 +1,44 @@
|
||||
import Parameters;
|
||||
|
||||
// The maximal size of the LDS is 16kbyte.
|
||||
#ifndef WORKGROUP_SIZE
|
||||
#define WORKGROUP_SIZE 64
|
||||
#endif
|
||||
|
||||
/*
|
||||
Level 0: 32 bit // [0, 131072] x 1, needs a minimum of 18 bits (rounded up to 32 for alignment and required for atomic operations)
|
||||
Level 1: 32 bit // [0, 65536] x 2, needs a minimum of 17 bits (rounded up to 32 for alignment and required for atomic operations)
|
||||
Level 2: 32 bit // [0, 32768] x 4, needs a minimum of 16 bits (bumped to 32 bits for atomic operations)
|
||||
Level 3: 32 bit // [0, 16384] x 8, needs a minimum of 15 bits (rounded up to 16 for alignment and bumped to 32 bits for atomic operations)
|
||||
Level 4: 32 bit // [0, 8192] x 16, needs a minimum of 14 bits (rounded up to 16 for alignment and bumped to 32 bits for atomic operations)
|
||||
Level 5: 32 bit // [0, 4096] x 32, needs a minimum of 13 bits (rounded up to 16 for alignment and bumped to 32 bits for atomic operations)
|
||||
Level 6: 32 bit // [0, 2048] x 64, needs a minimum of 12 bits (rounded up to 16 for alignment and bumped to 32 bits for atomic operations)
|
||||
|
||||
Level 7: 16 bit // [0, 1024] x 128, needs a minimum of 11 bits (rounded up to 16 for alignment)
|
||||
Level 8: 16 bit // [0, 512] x 256, needs a minimum of 10 bits (rounded up to 16 for alignment)
|
||||
Level 9: 16 bit // [0, 256] x 512, needs a minimum of 9 bits (rounded up to 16 for alignment)
|
||||
Level 10: 8 bit // [0, 128] x 1024, needs a minimum of 8 bits
|
||||
|
||||
Level 11: Raw 64 bits representation
|
||||
*/
|
||||
|
||||
// Num elements
|
||||
#define OCBT_NUM_ELEMENTS 1048576
|
||||
#define OCBT_NUM_ELEMENTS 131072
|
||||
// Tree sizes
|
||||
#define OCBT_TREE_SIZE_BITS (32 * 1 + 32 * 2 + 32 * 4 + 32 * 8 + 32 * 16 + 32 * 32 + 32 * 64 + 16 * 128 + 16 * 256 + 16 * 512 + 16 * 1024 + 16* 2048 + 16 * 4096 + 8 * 8192)
|
||||
#define OCBT_TREE_SIZE_BITS (32 * 1 + 32 * 2 + 32 * 4 + 32 * 8 + 32 * 16 + 32 * 32 + 32 * 64 + 16 * 128 + 16 * 256 + 16 * 512 + 8 * 1024)
|
||||
#define OCBT_TREE_NUM_SLOTS (OCBT_TREE_SIZE_BITS / 32)
|
||||
#define OCBT_BITFIELD_NUM_SLOTS (OCBT_NUM_ELEMENTS / 64)
|
||||
#define OCBT_LAST_LEVEL_SIZE 8192
|
||||
#define OCBT_LAST_LEVEL_SIZE 1024
|
||||
|
||||
// Tree last level
|
||||
#define TREE_LAST_LEVEL 13
|
||||
#define TREE_LAST_LEVEL 10
|
||||
// First virtual level
|
||||
#define FIRST_VIRTUAL_LEVEL 14
|
||||
#define FIRST_VIRTUAL_LEVEL 11
|
||||
// Leaf level
|
||||
#define LEAF_LEVEL 20
|
||||
#define LEAF_LEVEL 17
|
||||
|
||||
// per level offset
|
||||
static const uint32_t OCBT_depth_offset[21] = { 0, // Level 0
|
||||
static const uint32_t OCBT_depth_offset[18] = { 0, // Level 0
|
||||
32 * 1, // level 1
|
||||
32 * 1 + 32 * 2, // level 2
|
||||
32 * 1 + 32 * 2 + 32 * 4, // level 3
|
||||
@@ -32,20 +50,17 @@ static const uint32_t OCBT_depth_offset[21] = { 0, // Level 0
|
||||
32 * 1 + 32 * 2 + 32 * 4 + 32 * 8 + 32 * 16 + 32 * 32 + 32 * 64 + 16 * 128, // Level 8
|
||||
32 * 1 + 32 * 2 + 32 * 4 + 32 * 8 + 32 * 16 + 32 * 32 + 32 * 64 + 16 * 128 + 16 * 256, // Level 9
|
||||
32 * 1 + 32 * 2 + 32 * 4 + 32 * 8 + 32 * 16 + 32 * 32 + 32 * 64 + 16 * 128 + 16 * 256 + 16 * 512, // Level 10
|
||||
32 * 1 + 32 * 2 + 32 * 4 + 32 * 8 + 32 * 16 + 32 * 32 + 32 * 64 + 16 * 128 + 16 * 256 + 16 * 512 + 16 * 1024, // Level 11
|
||||
32 * 1 + 32 * 2 + 32 * 4 + 32 * 8 + 32 * 16 + 32 * 32 + 32 * 64 + 16 * 128 + 16 * 256 + 16 * 512 + 16 * 1024 + 16 * 2048, // Level 12
|
||||
32 * 1 + 32 * 2 + 32 * 4 + 32 * 8 + 32 * 16 + 32 * 32 + 32 * 64 + 16 * 128 + 16 * 256 + 16 * 512 + 16 * 1024 + 16 * 2048 + 16 * 4096, // Level 13
|
||||
|
||||
0, // Level 12
|
||||
0, // Level 13
|
||||
0, // Level 14
|
||||
0, // Level 15
|
||||
0, // Level 16
|
||||
0, // Level 17
|
||||
0, // Level 18
|
||||
0, // Level 19
|
||||
0, // Level 20
|
||||
};
|
||||
|
||||
static const uint64_t OCBT_bit_mask[21] = { 0xffffffff, // Root 17
|
||||
static const uint64_t OCBT_bit_mask[18] = { 0xffffffff, // Root 17
|
||||
0xffffffff, // Level 16
|
||||
0xffffffff, // level 15
|
||||
0xffffffff, // level 14
|
||||
@@ -56,9 +71,6 @@ static const uint64_t OCBT_bit_mask[21] = { 0xffffffff, // Root 17
|
||||
0xffff, // level 10
|
||||
0xffff, // level 9
|
||||
0xffff, // level 8
|
||||
0xffff, // level 8
|
||||
0xffff, // level 8
|
||||
0xffff, // level 8
|
||||
0xff, // level 8
|
||||
|
||||
0xffffffffffffffff, // level 7
|
||||
@@ -70,7 +82,7 @@ static const uint64_t OCBT_bit_mask[21] = { 0xffffffff, // Root 17
|
||||
0x1, // level 1
|
||||
};
|
||||
|
||||
static const uint32_t OCBT_bit_count[21] = { 32, // Root 17
|
||||
static const uint32_t OCBT_bit_count[18] = { 32, // Root 17
|
||||
32, // Level 16
|
||||
32, // level 15
|
||||
32, // level 14
|
||||
@@ -81,9 +93,6 @@ static const uint32_t OCBT_bit_count[21] = { 32, // Root 17
|
||||
16, // level 10
|
||||
16, // level 9
|
||||
16, // level 8
|
||||
16, // level 8
|
||||
16, // level 8
|
||||
16, // level 8
|
||||
8, // level 8
|
||||
|
||||
64, // Level 5
|
||||
@@ -417,11 +426,9 @@ void reduce_first_pass(uint dispatchThreadID, uint groupIndex)
|
||||
{
|
||||
// Load the lowest level (and only the last level)
|
||||
const uint level0Offset = OCBT_depth_offset[TREE_LAST_LEVEL] / 32;
|
||||
for (uint e = 0; e < 4; ++e)
|
||||
{
|
||||
uint target_element = 4 * dispatchThreadID + e;
|
||||
gs_cbtTree[level0Offset + target_element] = pParams.cbtBuffer[level0Offset + target_element];
|
||||
}
|
||||
if (groupIndex % 2 == 0)
|
||||
gs_cbtTree[level0Offset + dispatchThreadID / 2] = pParams.cbtBuffer[level0Offset + dispatchThreadID / 2];
|
||||
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
|
||||
// First we do a reduction until each lane has exactly one element to process
|
||||
@@ -444,29 +451,15 @@ void reduce_first_pass(uint dispatchThreadID, uint groupIndex)
|
||||
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
|
||||
// Load the first reduced level
|
||||
const uint level2Offset = OCBT_depth_offset[TREE_LAST_LEVEL - 1] / 32;
|
||||
for (uint e = 0; e < 4; ++e)
|
||||
{
|
||||
uint target_element = 4 * dispatchThreadID + e;
|
||||
pParams.cbtBuffer[level2Offset + target_element] = gs_cbtTree[level2Offset + target_element];
|
||||
}
|
||||
|
||||
// Load the first reduced level
|
||||
const uint level3Offset = OCBT_depth_offset[TREE_LAST_LEVEL - 2] / 32;
|
||||
for (uint e = 0; e < 2; ++e)
|
||||
{
|
||||
uint target_element = 2 * dispatchThreadID + e;
|
||||
pParams.cbtBuffer[level3Offset + target_element] = gs_cbtTree[level3Offset + target_element];
|
||||
}
|
||||
|
||||
const uint level4Offset = OCBT_depth_offset[TREE_LAST_LEVEL - 3] / 32;
|
||||
pParams.cbtBuffer[level4Offset + dispatchThreadID] = gs_cbtTree[level4Offset + dispatchThreadID];
|
||||
|
||||
const uint level5Offset = OCBT_depth_offset[TREE_LAST_LEVEL - 4] / 32;
|
||||
if (groupIndex % 2 == 0)
|
||||
pParams.cbtBuffer[level5Offset + dispatchThreadID / 2] = gs_cbtTree[level5Offset + dispatchThreadID / 2];
|
||||
pParams.cbtBuffer[level2Offset + dispatchThreadID / 2] = gs_cbtTree[level2Offset + dispatchThreadID / 2];
|
||||
|
||||
const uint level3Offset = OCBT_depth_offset[TREE_LAST_LEVEL - 2] / 32;
|
||||
if (groupIndex % 4 == 0)
|
||||
pParams.cbtBuffer[level3Offset + dispatchThreadID / 4] = gs_cbtTree[level3Offset + dispatchThreadID / 4];
|
||||
}
|
||||
|
||||
void reduce_second_pass(uint groupIndex)
|
||||
{
|
||||
// Load the lowest level (and only the last level)
|
||||
|
||||
@@ -16,7 +16,7 @@ struct BisectorGeometry
|
||||
|
||||
// global
|
||||
// update
|
||||
int classifyBisector(in BisectorGeometry tri, uint depth)
|
||||
int classifyBisector(in BisectorGeometry tri, uint depth, inout DebugStruct debug)
|
||||
{
|
||||
float3 triNormal = normalize(cross(tri.p[2] - tri.p[1], tri.p[0] - tri.p[1]));
|
||||
float3 triCenter = (tri.p[0] + tri.p[1] + tri.p[2]) / 3.0;
|
||||
@@ -24,49 +24,49 @@ int classifyBisector(in BisectorGeometry tri, uint depth)
|
||||
float fDotV = dot(viewDir, pViewParams.cameraForward_WS.xyz);
|
||||
float vDotN = dot(viewDir, triNormal);
|
||||
|
||||
debug.fDotV = fDotV;
|
||||
debug.vDotN = vDotN;
|
||||
if(fDotV < 0.0 && vDotN < -1e-3)
|
||||
{
|
||||
debug.area = 420;
|
||||
return BACK_FACE_CULLED;
|
||||
}
|
||||
|
||||
|
||||
AABB aabb;
|
||||
aabb.minCorner = float3(min(min(tri.p[0].x, tri.p[1].x), tri.p[2].x), min(min(tri.p[0].y, tri.p[1].y), tri.p[2].y), min(min(tri.p[0].z, tri.p[1].z), tri.p[2].z));
|
||||
aabb.maxCorner = float3(max(max(tri.p[0].x, tri.p[1].x), tri.p[2].x), max(max(tri.p[0].y, tri.p[1].y), tri.p[2].y), max(max(tri.p[0].z, tri.p[1].z), tri.p[2].z));
|
||||
|
||||
if(aabb.insideFrustum(pViewParams.viewFrustum))
|
||||
return FRUSTUM_CULLED;
|
||||
//if(aabb.insideFrustum(pViewParams.viewFrustum))
|
||||
// return FRUSTUM_CULLED;
|
||||
|
||||
float4x4 viewProjectionMatrix = mul(pViewParams.projectionMatrix, pViewParams.viewMatrix);
|
||||
|
||||
float4 p0P = mul(pViewParams.viewMatrix, float4(tri.p[0], 1.0));
|
||||
p0P.xy = p0P.xy / p0P.w;
|
||||
p0P.xy = (p0P.xy * 0.5 + 0.5);
|
||||
float4 p0P = clipToScreen(mul(viewProjectionMatrix, float4(tri.p[0], 1.0)));
|
||||
|
||||
float4 p1P = mul(viewProjectionMatrix, float4(tri.p[1], 1.0));
|
||||
p1P.xy = p1P.xy / p1P.w;
|
||||
p1P.xy = (p1P.xy * 0.5 + 0.5);
|
||||
float4 p1P = clipToScreen(mul(viewProjectionMatrix, float4(tri.p[1], 1.0)));
|
||||
|
||||
float4 p2P = mul(viewProjectionMatrix, float4(tri.p[2], 1.0));
|
||||
p2P.xy = p2P.xy / p2P.w;
|
||||
p2P.xy = (p2P.xy * 0.5 + 0.5);
|
||||
float4 p2P = clipToScreen(mul(viewProjectionMatrix, float4(tri.p[2], 1.0)));
|
||||
|
||||
float area = 0.5 * abs(p0P.x * (p2P.y - p1P.y) + p1P.x * (p0P.y - p2P.y) + p2P.x * (p1P.y - p0P.y));
|
||||
area *= pViewParams.screenDimensions.x * pViewParams.screenDimensions.y;
|
||||
float area = 0.5 * abs(p0P.x * (p1P.y - p2P.y) + p1P.x * (p2P.y - p0P.y) + p2P.x * (p0P.y - p1P.y));
|
||||
|
||||
float areaOverestimation = lerp(2.0, 1.0, pow(vDotN, 0.2));
|
||||
area *= areaOverestimation;
|
||||
|
||||
debug.areaP[0] = p0P.xy;
|
||||
debug.areaP[1] = p1P.xy;
|
||||
debug.areaP[2] = p2P.xy;
|
||||
debug.area = area;
|
||||
|
||||
if(pParams.update.triangleSize < area && depth < pParams.update.maxSubdivisionDepth)
|
||||
{
|
||||
return BISECT_ELEMENT;
|
||||
}
|
||||
else if((pParams.update.triangleSize * 0.5 > area) || (depth > pParams.update.maxSubdivisionDepth))
|
||||
{
|
||||
float4 p3P = mul(viewProjectionMatrix, float4(tri.p[3], 1.0));
|
||||
p3P.xy = p3P.xy / p3P.w;
|
||||
p3P.xy = (p3P.xy * 0.5 + 0.5);
|
||||
float4 p3P = clipToScreen(mul(viewProjectionMatrix, float4(tri.p[3], 1.0)));
|
||||
|
||||
float areaParent = 0.5 * abs(p0P.x * (p2P.y - p3P.y) + p3P.x * (p0P.y - p2P.y) + p2P.x * (p3P.y - p0P.y));
|
||||
areaParent *= pViewParams.screenDimensions.x * pViewParams.screenDimensions.y;
|
||||
float areaParent = 0.5 * abs(p0P.x * (p3P.y - p2P.y) + p3P.x * (p2P.y - p0P.y) + p2P.x * (p0P.y - p3P.y));
|
||||
areaParent *= areaOverestimation;
|
||||
return ((pParams.update.triangleSize >= areaParent ) || (depth > pParams.update.maxSubdivisionDepth)) ? TOO_SMALL : UNCHANGED_ELEMENT;
|
||||
}
|
||||
@@ -136,7 +136,7 @@ void reset()
|
||||
[numthreads(WORKGROUP_SIZE, 1, 1)]
|
||||
void classify(uint dispatchID : SV_DispatchThreadID)
|
||||
{
|
||||
if(dispatchID > pParams.indirectDrawBuffer[9])
|
||||
if(dispatchID >= pParams.indirectDrawBuffer[9])
|
||||
return;
|
||||
|
||||
uint currentID = pParams.indexedBisectorBuffer[dispatchID];
|
||||
@@ -155,7 +155,13 @@ void classify(uint dispatchID : SV_DispatchThreadID)
|
||||
cBisectorData.problematicNeighbor = INVALID_POINTER;
|
||||
cBisectorData.flags = VISIBLE_BISECTOR;
|
||||
|
||||
int currentValidity = classifyBisector(bis, depth);
|
||||
DebugStruct debug;
|
||||
debug.sourceP[0] = float4(bis.p[0], 1);
|
||||
debug.sourceP[1] = float4(bis.p[1], 1);
|
||||
debug.sourceP[2] = float4(bis.p[2], 1);
|
||||
int currentValidity = classifyBisector(bis, depth, debug);
|
||||
debug.validity = currentValidity;
|
||||
pParams.debugBuffer[dispatchID] = debug;
|
||||
if(currentValidity > UNCHANGED_ELEMENT)
|
||||
{
|
||||
uint targetSlot;
|
||||
@@ -1085,7 +1091,7 @@ void leb_DecodeNodeAttributeArray_parent_child(uint64_t heapID, inout float3 chi
|
||||
}
|
||||
}
|
||||
|
||||
void evaluateElementPosition(uint64_t heapID, uint32_t vertexDataOffset, uint minDepth, out Triangle parentTri, out Triangle childTri)
|
||||
void evaluateElementPosition(uint64_t heapID, uint32_t vertexDataOffset, uint minDepth, RWStructuredBuffer<float3> vertexBuffer, out Triangle parentTri, out Triangle childTri)
|
||||
{
|
||||
// Get the depth of the element
|
||||
uint depth = heapIDDepth(heapID);
|
||||
@@ -1098,9 +1104,9 @@ void evaluateElementPosition(uint64_t heapID, uint32_t vertexDataOffset, uint mi
|
||||
uint primitiveID = uint((heapID >> subTreeDepth) - baseHeapID);
|
||||
|
||||
// Grab the base positions of the element
|
||||
float3 p0 = float3(pParams.currentVertexBuffer[3 * primitiveID + vertexDataOffset]);
|
||||
float3 p1 = float3(pParams.currentVertexBuffer[3 * primitiveID + 1 + vertexDataOffset]);
|
||||
float3 p2 = float3(pParams.currentVertexBuffer[3 * primitiveID + 2 + vertexDataOffset]);
|
||||
float3 p0 = float3(vertexBuffer[3 * primitiveID + vertexDataOffset]);
|
||||
float3 p1 = float3(vertexBuffer[3 * primitiveID + 1 + vertexDataOffset]);
|
||||
float3 p2 = float3(vertexBuffer[3 * primitiveID + 2 + vertexDataOffset]);
|
||||
|
||||
// Heap ID in the sub triangle
|
||||
uint64_t mask = subTreeDepth != 0uL ? 0xFFFFFFFFFFFFFFFFull >> (64ull - subTreeDepth) : 0ull;
|
||||
@@ -1127,6 +1133,9 @@ void evaluateElementPosition(uint64_t heapID, uint32_t vertexDataOffset, uint mi
|
||||
childTri.p[2] = float3(childArray[0][2], childArray[1][2], childArray[2][2]);
|
||||
}
|
||||
|
||||
layout(push_constant)
|
||||
ConstantBuffer<uint> preRender;
|
||||
|
||||
[numthreads(WORKGROUP_SIZE, 1, 1)]
|
||||
void evaluateLeb(uint currentID : SV_DispatchThreadID, uint groupIndex: SV_GroupIndex)
|
||||
{
|
||||
@@ -1136,7 +1145,7 @@ void evaluateLeb(uint currentID : SV_DispatchThreadID, uint groupIndex: SV_Group
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
|
||||
uint numBisectors;
|
||||
if(pViewParams.frameIndex == -1)
|
||||
if(preRender == 1)
|
||||
{
|
||||
numBisectors = pParams.indirectDrawBuffer[9];
|
||||
}
|
||||
@@ -1153,7 +1162,7 @@ void evaluateLeb(uint currentID : SV_DispatchThreadID, uint groupIndex: SV_Group
|
||||
uint depth = heapIDDepth(cHeapID);
|
||||
|
||||
Triangle parentTri, childTri;
|
||||
evaluateElementPosition(cHeapID, 0, pParams.geometry.baseDepth, parentTri, childTri);
|
||||
evaluateElementPosition(cHeapID, 0, pParams.geometry.baseDepth, pParams.currentVertexBuffer, parentTri, childTri);
|
||||
|
||||
pParams.lebPositionBuffer[3 * currentID + 0] = childTri.p[0];
|
||||
pParams.lebPositionBuffer[3 * currentID + 1] = childTri.p[1];
|
||||
|
||||
@@ -26,6 +26,16 @@ struct UpdateCB
|
||||
float farPlaneDistance;
|
||||
}
|
||||
|
||||
struct DebugStruct
|
||||
{
|
||||
float4 sourceP[3];
|
||||
float2 areaP[3];
|
||||
float area;
|
||||
int validity;
|
||||
float fDotV;
|
||||
float vDotN;
|
||||
}
|
||||
|
||||
struct ComputeParams
|
||||
{
|
||||
ConstantBuffer<GeometryCB> geometry;
|
||||
@@ -52,5 +62,6 @@ struct ComputeParams
|
||||
RWStructuredBuffer<uint> modifiedBisectorIndices;
|
||||
RWStructuredBuffer<float3> lebPositionBuffer;
|
||||
StructuredBuffer<float3x3> lebMatrixCache;
|
||||
RWStructuredBuffer<DebugStruct> debugBuffer;
|
||||
};
|
||||
ParameterBlock<ComputeParams> pParams;
|
||||
|
||||
Reference in New Issue
Block a user