Add rendering stats for Data Upload
This commit is contained in:
@@ -49,7 +49,7 @@ namespace FlaxEditor.Windows.Profiler
|
|||||||
{
|
{
|
||||||
Title = "Draw (GPU)",
|
Title = "Draw (GPU)",
|
||||||
AnchorPreset = AnchorPresets.HorizontalStretchTop,
|
AnchorPreset = AnchorPresets.HorizontalStretchTop,
|
||||||
Offsets = new Margin(0, 0, _drawTimeCPU.Height + 2, 0),
|
Offsets = new Margin(0, 0, _drawTimeCPU.Height + 2, SingleChart.DefaultHeight),
|
||||||
FormatSample = v => (Mathf.RoundToInt(v * 10.0f) / 10.0f) + " ms",
|
FormatSample = v => (Mathf.RoundToInt(v * 10.0f) / 10.0f) + " ms",
|
||||||
Parent = mainPanel,
|
Parent = mainPanel,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include "Engine/Graphics/GPUDevice.h"
|
#include "Engine/Graphics/GPUDevice.h"
|
||||||
#include "Engine/Graphics/GPUPass.h"
|
#include "Engine/Graphics/GPUPass.h"
|
||||||
#include "Engine/Profiler/ProfilerCPU.h"
|
#include "Engine/Profiler/ProfilerCPU.h"
|
||||||
|
#include "Engine/Profiler/ProfilerGPU.h"
|
||||||
|
|
||||||
DefaultGPUTasksExecutor::DefaultGPUTasksExecutor()
|
DefaultGPUTasksExecutor::DefaultGPUTasksExecutor()
|
||||||
: _context(nullptr)
|
: _context(nullptr)
|
||||||
@@ -33,6 +34,7 @@ void DefaultGPUTasksExecutor::FrameBegin()
|
|||||||
const int32 count = GPUDevice::Instance->GetTasksManager()->RequestWork(buffer, 32);
|
const int32 count = GPUDevice::Instance->GetTasksManager()->RequestWork(buffer, 32);
|
||||||
if (count == 0)
|
if (count == 0)
|
||||||
return;
|
return;
|
||||||
|
PROFILE_GPU("GPUTasks");
|
||||||
GPUMemoryPass pass(_context->GPU);
|
GPUMemoryPass pass(_context->GPU);
|
||||||
for (int32 i = 0; i < count; i++)
|
for (int32 i = 0; i < count; i++)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -499,6 +499,7 @@ void GPUContextDX11::UpdateCB(GPUConstantBuffer* cb, const void* data)
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
_context->UpdateSubresource(cbDX11->GetBuffer(), 0, nullptr, data, size, 1);
|
_context->UpdateSubresource(cbDX11->GetBuffer(), 0, nullptr, data, size, 1);
|
||||||
|
RENDER_STAT_DATA_UPLOAD(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GPUContextDX11::Dispatch(GPUShaderProgramCS* shader, uint32 threadGroupCountX, uint32 threadGroupCountY, uint32 threadGroupCountZ)
|
void GPUContextDX11::Dispatch(GPUShaderProgramCS* shader, uint32 threadGroupCountX, uint32 threadGroupCountY, uint32 threadGroupCountZ)
|
||||||
@@ -904,6 +905,7 @@ void GPUContextDX11::UpdateBuffer(GPUBuffer* buffer, const void* data, uint32 si
|
|||||||
box.bottom = 1;
|
box.bottom = 1;
|
||||||
_context->UpdateSubresource(bufferDX11->GetResource(), 0, &box, data, size, 0);
|
_context->UpdateSubresource(bufferDX11->GetResource(), 0, &box, data, size, 0);
|
||||||
}
|
}
|
||||||
|
RENDER_STAT_DATA_UPLOAD(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GPUContextDX11::CopyBuffer(GPUBuffer* dstBuffer, GPUBuffer* srcBuffer, uint32 size, uint32 dstOffset, uint32 srcOffset)
|
void GPUContextDX11::CopyBuffer(GPUBuffer* dstBuffer, GPUBuffer* srcBuffer, uint32 size, uint32 dstOffset, uint32 srcOffset)
|
||||||
@@ -934,6 +936,7 @@ void GPUContextDX11::UpdateTexture(GPUTexture* texture, int32 arrayIndex, int32
|
|||||||
if (texture->IsVolume())
|
if (texture->IsVolume())
|
||||||
depthPitch /= Math::Max(1, texture->Depth() >> mipIndex);
|
depthPitch /= Math::Max(1, texture->Depth() >> mipIndex);
|
||||||
_context->UpdateSubresource(textureDX11->GetResource(), subresourceIndex, nullptr, data, (UINT)rowPitch, (UINT)depthPitch);
|
_context->UpdateSubresource(textureDX11->GetResource(), subresourceIndex, nullptr, data, (UINT)rowPitch, (UINT)depthPitch);
|
||||||
|
RENDER_STAT_DATA_UPLOAD(slicePitch);
|
||||||
|
|
||||||
//D3D11_MAPPED_SUBRESOURCE mapped;
|
//D3D11_MAPPED_SUBRESOURCE mapped;
|
||||||
//_device->GetIM()->Map(_resource, textureMipIndex, D3D11_MAP_WRITE_DISCARD, 0, &mapped);
|
//_device->GetIM()->Map(_resource, textureMipIndex, D3D11_MAP_WRITE_DISCARD, 0, &mapped);
|
||||||
|
|||||||
@@ -1131,6 +1131,7 @@ void GPUContextDX12::UpdateCB(GPUConstantBuffer* cb, const void* data)
|
|||||||
|
|
||||||
// Allocate bytes for the buffer
|
// Allocate bytes for the buffer
|
||||||
auto allocation = _device->UploadBuffer.Allocate(size, D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT);
|
auto allocation = _device->UploadBuffer.Allocate(size, D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT);
|
||||||
|
RENDER_STAT_DATA_UPLOAD(size);
|
||||||
|
|
||||||
// Copy data
|
// Copy data
|
||||||
Platform::MemoryCopy(allocation.CPUAddress, data, allocation.Size);
|
Platform::MemoryCopy(allocation.CPUAddress, data, allocation.Size);
|
||||||
@@ -1388,6 +1389,7 @@ void GPUContextDX12::UpdateBuffer(GPUBuffer* buffer, const void* data, uint32 si
|
|||||||
flushRBs();
|
flushRBs();
|
||||||
|
|
||||||
_device->UploadBuffer.UploadBuffer(GetCommandList(), bufferDX12->GetResource(), offset, data, size);
|
_device->UploadBuffer.UploadBuffer(GetCommandList(), bufferDX12->GetResource(), offset, data, size);
|
||||||
|
RENDER_STAT_DATA_UPLOAD(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GPUContextDX12::CopyBuffer(GPUBuffer* dstBuffer, GPUBuffer* srcBuffer, uint32 size, uint32 dstOffset, uint32 srcOffset)
|
void GPUContextDX12::CopyBuffer(GPUBuffer* dstBuffer, GPUBuffer* srcBuffer, uint32 size, uint32 dstOffset, uint32 srcOffset)
|
||||||
@@ -1414,6 +1416,7 @@ void GPUContextDX12::UpdateTexture(GPUTexture* texture, int32 arrayIndex, int32
|
|||||||
flushRBs();
|
flushRBs();
|
||||||
|
|
||||||
_device->UploadBuffer.UploadTexture(GetCommandList(), textureDX12->GetResource(), data, rowPitch, slicePitch, mipIndex, arrayIndex);
|
_device->UploadBuffer.UploadTexture(GetCommandList(), textureDX12->GetResource(), data, rowPitch, slicePitch, mipIndex, arrayIndex);
|
||||||
|
RENDER_STAT_DATA_UPLOAD(slicePitch);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GPUContextDX12::CopyTexture(GPUTexture* dstResource, uint32 dstSubresource, uint32 dstX, uint32 dstY, uint32 dstZ, GPUTexture* srcResource, uint32 srcSubresource)
|
void GPUContextDX12::CopyTexture(GPUTexture* dstResource, uint32 dstSubresource, uint32 dstX, uint32 dstY, uint32 dstZ, GPUTexture* srcResource, uint32 srcSubresource)
|
||||||
@@ -1469,6 +1472,7 @@ void GPUContextDX12::ResetCounter(GPUBuffer* buffer)
|
|||||||
|
|
||||||
uint32 value = 0;
|
uint32 value = 0;
|
||||||
_device->UploadBuffer.UploadBuffer(GetCommandList(), counter->GetResource(), 0, &value, 4);
|
_device->UploadBuffer.UploadBuffer(GetCommandList(), counter->GetResource(), 0, &value, 4);
|
||||||
|
RENDER_STAT_DATA_UPLOAD(4);
|
||||||
|
|
||||||
SetResourceState(counter, D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
|
SetResourceState(counter, D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ namespace D3D12MA
|
|||||||
}
|
}
|
||||||
class GPUResource;
|
class GPUResource;
|
||||||
class GPUContextDX12;
|
class GPUContextDX12;
|
||||||
class GPUAsyncContextDX12;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Default amount of frames to wait until resource delete.
|
/// Default amount of frames to wait until resource delete.
|
||||||
@@ -59,7 +58,6 @@ public:
|
|||||||
class ResourceOwnerDX12
|
class ResourceOwnerDX12
|
||||||
{
|
{
|
||||||
friend GPUContextDX12;
|
friend GPUContextDX12;
|
||||||
friend GPUAsyncContextDX12;
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
D3D12MA::Allocation* _allocation = nullptr;
|
D3D12MA::Allocation* _allocation = nullptr;
|
||||||
|
|||||||
@@ -682,6 +682,7 @@ void GPUContextVulkan::UpdateDescriptorSets(const SpirvShaderDescriptorInfo& des
|
|||||||
auto cb = (GPUConstantBufferVulkan*)_device->HelperResources.GetDummyConstantBuffer();
|
auto cb = (GPUConstantBufferVulkan*)_device->HelperResources.GetDummyConstantBuffer();
|
||||||
// TODO: cache this allocation within a frame
|
// TODO: cache this allocation within a frame
|
||||||
const auto allocation = _device->UniformBufferUploader->Allocate(cb->GetSize(), 0, this);
|
const auto allocation = _device->UniformBufferUploader->Allocate(cb->GetSize(), 0, this);
|
||||||
|
RENDER_STAT_DATA_UPLOAD(allocation.Size);
|
||||||
Platform::MemoryClear(allocation.CPUAddress, allocation.Size);
|
Platform::MemoryClear(allocation.CPUAddress, allocation.Size);
|
||||||
cb->Allocation = allocation;
|
cb->Allocation = allocation;
|
||||||
handle = cb;
|
handle = cb;
|
||||||
@@ -1187,6 +1188,7 @@ void GPUContextVulkan::UpdateCB(GPUConstantBuffer* cb, const void* data)
|
|||||||
|
|
||||||
// Allocate bytes for the buffer
|
// Allocate bytes for the buffer
|
||||||
const auto allocation = _device->UniformBufferUploader->Allocate(size, 0, this);
|
const auto allocation = _device->UniformBufferUploader->Allocate(size, 0, this);
|
||||||
|
RENDER_STAT_DATA_UPLOAD(size);
|
||||||
|
|
||||||
// Copy data
|
// Copy data
|
||||||
Platform::MemoryCopy(allocation.CPUAddress, data, allocation.Size);
|
Platform::MemoryCopy(allocation.CPUAddress, data, allocation.Size);
|
||||||
@@ -1538,6 +1540,7 @@ void GPUContextVulkan::UpdateBuffer(GPUBuffer* buffer, const void* data, uint32
|
|||||||
region.dstOffset = offset;
|
region.dstOffset = offset;
|
||||||
vkCmdCopyBuffer(cmdBuffer->GetHandle(), allocation.Buffer, ((GPUBufferVulkan*)buffer)->GetHandle(), 1, ®ion);
|
vkCmdCopyBuffer(cmdBuffer->GetHandle(), allocation.Buffer, ((GPUBufferVulkan*)buffer)->GetHandle(), 1, ®ion);
|
||||||
}
|
}
|
||||||
|
RENDER_STAT_DATA_UPLOAD(size);
|
||||||
|
|
||||||
// Memory transfer barrier to ensure buffer is ready to read (eg. by Draw or Dispatch)
|
// Memory transfer barrier to ensure buffer is ready to read (eg. by Draw or Dispatch)
|
||||||
if (_pass == 0)
|
if (_pass == 0)
|
||||||
@@ -1586,6 +1589,7 @@ void GPUContextVulkan::UpdateTexture(GPUTexture* texture, int32 arrayIndex, int3
|
|||||||
FlushBarriers();
|
FlushBarriers();
|
||||||
|
|
||||||
auto allocation = _device->UploadBuffer.Upload(data, slicePitch, 512);
|
auto allocation = _device->UploadBuffer.Upload(data, slicePitch, 512);
|
||||||
|
RENDER_STAT_DATA_UPLOAD(slicePitch);
|
||||||
|
|
||||||
// Setup buffer copy region
|
// Setup buffer copy region
|
||||||
int32 mipWidth, mipHeight, mipDepth;
|
int32 mipWidth, mipHeight, mipDepth;
|
||||||
|
|||||||
@@ -364,6 +364,7 @@ void GPUContextWebGPU::UpdateCB(GPUConstantBuffer* cb, const void* data)
|
|||||||
cbWebGPU->AllocationSize = alignedSize;
|
cbWebGPU->AllocationSize = alignedSize;
|
||||||
// TODO: consider holding CPU-side staging buffer and copying data to the GPU buffer in a single batch for all uniforms (before flushing the active command encoder)
|
// TODO: consider holding CPU-side staging buffer and copying data to the GPU buffer in a single batch for all uniforms (before flushing the active command encoder)
|
||||||
wgpuQueueWriteBuffer(_device->Queue, allocation.Buffer, allocation.Offset, data, size);
|
wgpuQueueWriteBuffer(_device->Queue, allocation.Buffer, allocation.Offset, data, size);
|
||||||
|
RENDER_STAT_DATA_UPLOAD(size);
|
||||||
_bindGroupDirty = true;
|
_bindGroupDirty = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -593,6 +594,7 @@ void GPUContextWebGPU::UpdateBuffer(GPUBuffer* buffer, const void* data, uint32
|
|||||||
// Efficient upload via queue
|
// Efficient upload via queue
|
||||||
wgpuQueueWriteBuffer(_device->Queue, bufferWebGPU->Buffer, offset, data, size);
|
wgpuQueueWriteBuffer(_device->Queue, bufferWebGPU->Buffer, offset, data, size);
|
||||||
}
|
}
|
||||||
|
RENDER_STAT_DATA_UPLOAD(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GPUContextWebGPU::CopyBuffer(GPUBuffer* dstBuffer, GPUBuffer* srcBuffer, uint32 size, uint32 dstOffset, uint32 srcOffset)
|
void GPUContextWebGPU::CopyBuffer(GPUBuffer* dstBuffer, GPUBuffer* srcBuffer, uint32 size, uint32 dstOffset, uint32 srcOffset)
|
||||||
@@ -633,6 +635,7 @@ void GPUContextWebGPU::UpdateTexture(GPUTexture* texture, int32 arrayIndex, int3
|
|||||||
dataLayout.rowsPerImage = mipHeight;
|
dataLayout.rowsPerImage = mipHeight;
|
||||||
WGPUExtent3D writeSize = { (uint32_t)mipWidth, (uint32_t)mipHeight, (uint32_t)mipDepth };
|
WGPUExtent3D writeSize = { (uint32_t)mipWidth, (uint32_t)mipHeight, (uint32_t)mipDepth };
|
||||||
wgpuQueueWriteTexture(_device->Queue, ©Info, data, slicePitch, &dataLayout, &writeSize);
|
wgpuQueueWriteTexture(_device->Queue, ©Info, data, slicePitch, &dataLayout, &writeSize);
|
||||||
|
RENDER_STAT_DATA_UPLOAD(slicePitch);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GPUContextWebGPU::CopyTexture(GPUTexture* dstResource, uint32 dstSubresource, uint32 dstX, uint32 dstY, uint32 dstZ, GPUTexture* srcResource, uint32 srcSubresource)
|
void GPUContextWebGPU::CopyTexture(GPUTexture* dstResource, uint32 dstSubresource, uint32 dstX, uint32 dstY, uint32 dstZ, GPUTexture* srcResource, uint32 srcSubresource)
|
||||||
|
|||||||
@@ -439,6 +439,8 @@ void GraphicsDumping::Print()
|
|||||||
sb.AppendFormat(TEXT(", 1 tri, {} verts"), FormatValue(NameBuffers[0], item.Stats.Vertices));
|
sb.AppendFormat(TEXT(", 1 tri, {} verts"), FormatValue(NameBuffers[0], item.Stats.Vertices));
|
||||||
else if (item.Stats.Triangles != 0)
|
else if (item.Stats.Triangles != 0)
|
||||||
sb.AppendFormat(TEXT(", {} tris, {} verts"), FormatValue(NameBuffers[0], item.Stats.Triangles), FormatValue(NameBuffers[1], item.Stats.Vertices));
|
sb.AppendFormat(TEXT(", {} tris, {} verts"), FormatValue(NameBuffers[0], item.Stats.Triangles), FormatValue(NameBuffers[1], item.Stats.Vertices));
|
||||||
|
if (item.Stats.DataUpload > 4096)
|
||||||
|
sb.AppendFormat(TEXT(", {} sent"), Utilities::BytesToText(item.Stats.DataUpload));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -16,39 +16,32 @@ API_STRUCT() struct RenderStatsData
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The draw calls count.
|
/// The draw calls count.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
API_FIELD() int64 DrawCalls;
|
API_FIELD() int64 DrawCalls = 0;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The compute shader dispatch calls count.
|
/// The compute shader dispatch calls count.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
API_FIELD() int64 DispatchCalls;
|
API_FIELD() int64 DispatchCalls = 0;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The vertices drawn count.
|
/// The vertices drawn count.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
API_FIELD() int64 Vertices;
|
API_FIELD() int64 Vertices = 0;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The triangles drawn count.
|
/// The triangles drawn count.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
API_FIELD() int64 Triangles;
|
API_FIELD() int64 Triangles = 0;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The pipeline state changes count.
|
/// The pipeline state changes count.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
API_FIELD() int64 PipelineStateChanges;
|
API_FIELD() int64 PipelineStateChanges = 0;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="RenderStatsData"/> struct.
|
/// The amount of bytes uploaded to GPU (to buffers and textures).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
RenderStatsData()
|
API_FIELD() int64 DataUpload = 0;
|
||||||
: DrawCalls(0)
|
|
||||||
, DispatchCalls(0)
|
|
||||||
, Vertices(0)
|
|
||||||
, Triangles(0)
|
|
||||||
, PipelineStateChanges(0)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The global rendering stats counter.
|
/// The global rendering stats counter.
|
||||||
@@ -67,6 +60,7 @@ API_STRUCT() struct RenderStatsData
|
|||||||
MIX(Vertices);
|
MIX(Vertices);
|
||||||
MIX(Triangles);
|
MIX(Triangles);
|
||||||
MIX(PipelineStateChanges);
|
MIX(PipelineStateChanges);
|
||||||
|
MIX(DataUpload);
|
||||||
#undef MIX
|
#undef MIX
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,6 +72,7 @@ API_STRUCT() struct RenderStatsData
|
|||||||
MIX(Vertices);
|
MIX(Vertices);
|
||||||
MIX(Triangles);
|
MIX(Triangles);
|
||||||
MIX(PipelineStateChanges);
|
MIX(PipelineStateChanges);
|
||||||
|
MIX(DataUpload);
|
||||||
#undef MIX
|
#undef MIX
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
@@ -90,6 +85,7 @@ API_STRUCT() struct RenderStatsData
|
|||||||
MIX(Vertices);
|
MIX(Vertices);
|
||||||
MIX(Triangles);
|
MIX(Triangles);
|
||||||
MIX(PipelineStateChanges);
|
MIX(PipelineStateChanges);
|
||||||
|
MIX(DataUpload);
|
||||||
#undef MIX
|
#undef MIX
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
@@ -97,6 +93,7 @@ API_STRUCT() struct RenderStatsData
|
|||||||
|
|
||||||
#define RENDER_STAT_DISPATCH_CALL() Platform::InterlockedIncrement(&RenderStatsData::Counter.DispatchCalls)
|
#define RENDER_STAT_DISPATCH_CALL() Platform::InterlockedIncrement(&RenderStatsData::Counter.DispatchCalls)
|
||||||
#define RENDER_STAT_PS_STATE_CHANGE() Platform::InterlockedIncrement(&RenderStatsData::Counter.PipelineStateChanges)
|
#define RENDER_STAT_PS_STATE_CHANGE() Platform::InterlockedIncrement(&RenderStatsData::Counter.PipelineStateChanges)
|
||||||
|
#define RENDER_STAT_DATA_UPLOAD(bytes) Platform::InterlockedAdd(&RenderStatsData::Counter.DataUpload, bytes)
|
||||||
#define RENDER_STAT_DRAW_CALL(vertices, triangles) \
|
#define RENDER_STAT_DRAW_CALL(vertices, triangles) \
|
||||||
Platform::InterlockedIncrement(&RenderStatsData::Counter.DrawCalls); \
|
Platform::InterlockedIncrement(&RenderStatsData::Counter.DrawCalls); \
|
||||||
Platform::InterlockedAdd(&RenderStatsData::Counter.Vertices, vertices); \
|
Platform::InterlockedAdd(&RenderStatsData::Counter.Vertices, vertices); \
|
||||||
@@ -106,6 +103,7 @@ API_STRUCT() struct RenderStatsData
|
|||||||
|
|
||||||
#define RENDER_STAT_DISPATCH_CALL()
|
#define RENDER_STAT_DISPATCH_CALL()
|
||||||
#define RENDER_STAT_PS_STATE_CHANGE()
|
#define RENDER_STAT_PS_STATE_CHANGE()
|
||||||
|
#define RENDER_STAT_DATA_UPLOAD(bytes)
|
||||||
#define RENDER_STAT_DRAW_CALL(vertices, primitives)
|
#define RENDER_STAT_DRAW_CALL(vertices, primitives)
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
Reference in New Issue
Block a user