Skip to content

Commit 4a77127

Browse files
committed
Implement Adaptive Sampling use
1 parent 681088e commit 4a77127

File tree

9 files changed

+154
-40
lines changed

9 files changed

+154
-40
lines changed

Config/DefaultEngine.ini

+1-1
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ RayDepthGlossy=5
6363
RayDepthRefraction=5
6464
RayDepthGlossyRefraction=5
6565
RayDepthShadow=5
66+
NoiseThreshold=0.050000
6667

6768
[/Script/RPRPlugin.RPRSettings]
6869
MaximumRenderIterations=16384
@@ -134,4 +135,3 @@ InitialAverageFrameRate=0.016667
134135
PhysXTreeRebuildRate=10
135136
DefaultBroadphaseSettings=(bUseMBPOnClient=False,bUseMBPOnServer=False,MBPBounds=(Min=(X=0.000000,Y=0.000000,Z=0.000000),Max=(X=0.000000,Y=0.000000,Z=0.000000),IsValid=0),MBPNumSubdivs=2)
136137

137-

Plugins/RPRPlugin/Source/RPRCore/Private/RPRCoreSystemResources.cpp

+6
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,12 @@ bool FRPRCoreSystemResources::InitializeContextParameters()
259259
ContextSetUint(RPRContext, RPR_CONTEXT_MAX_DEPTH_REFRACTION, settings->RayDepthRefraction, TEXT("MAX_DEPTH_REFRACTION"));
260260
ContextSetUint(RPRContext, RPR_CONTEXT_MAX_DEPTH_GLOSSY_REFRACTION, settings->RayDepthGlossyRefraction, TEXT("MAX_DEPTH_GLOSSY_REFRACTION"));
261261
ContextSetFloat(RPRContext, RPR_CONTEXT_RADIANCE_CLAMP, settings->RadianceClamp, TEXT("RADIANCE_CLAMP"));
262+
if (settings->EnableAdaptiveSampling)
263+
{
264+
ContextSetUint(RPRContext, RPR_CONTEXT_ADAPTIVE_SAMPLING_TILE_SIZE, 4, TEXT("ADAPTIVE_SAMPLING_MIN_SPP"));
265+
ContextSetFloat(RPRContext, RPR_CONTEXT_ADAPTIVE_SAMPLING_THRESHOLD, settings->NoiseThreshold, TEXT("ADAPTIVE_SAMPLING_THRESHOLD"));
266+
ContextSetUint(RPRContext, RPR_CONTEXT_ADAPTIVE_SAMPLING_MIN_SPP, settings->SamplingMin, TEXT("ADAPTIVE_SAMPLING_MIN_SPP"));
267+
}
262268
break;
263269
case Hybrid:
264270
ContextSetUint(RPRContext, RPR_CONTEXT_MAX_RECURSION, 10, TEXT("MAX_RECURSION"));

Plugins/RPRPlugin/Source/RPRPlugin/Private/Renderer/RPRRendererWorker.cpp

+97-21
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,8 @@ int FRPRRendererWorker::ResizeFramebuffer()
403403
DestroyFrameBuffer(&m_RprAovDepthResolvedBuffer);
404404
DestroyFrameBuffer(&m_RprDiffuseAlbedoBuffer);
405405
DestroyFrameBuffer(&m_RprDiffuseAlbedoResolvedBuffer);
406+
DestroyFrameBuffer(&m_RprVarianceBuffer);
407+
DestroyFrameBuffer(&m_RprVarianceResolvedBuffer);
406408

407409
m_RprFrameBufferFormat.num_components = 4;
408410
m_RprFrameBufferFormat.type = RPR_COMPONENT_TYPE_FLOAT32;
@@ -425,15 +427,19 @@ int FRPRRendererWorker::ResizeFramebuffer()
425427
ContextCreateFrameBuffer(m_RprContext, m_RprFrameBufferFormat, &m_RprFrameBufferDesc, &m_RprAovDepthResolvedBuffer) != RPR_SUCCESS ||
426428
ContextCreateFrameBuffer(m_RprContext, m_RprFrameBufferFormat, &m_RprFrameBufferDesc, &m_RprDiffuseAlbedoBuffer) != RPR_SUCCESS ||
427429
ContextCreateFrameBuffer(m_RprContext, m_RprFrameBufferFormat, &m_RprFrameBufferDesc, &m_RprDiffuseAlbedoResolvedBuffer) != RPR_SUCCESS ||
428-
RPR::Context::SetAOV(m_RprContext, RPR::EAOV::Color, m_RprColorFrameBuffer) != RPR_SUCCESS ||
429-
RPR::Context::SetAOV(m_RprContext, m_AOV, m_RprFrameBuffer) != RPR_SUCCESS)
430+
431+
RPR::Context::SetAOV(m_RprContext, RPR::EAOV::Color, m_RprColorFrameBuffer) != RPR_SUCCESS ||
432+
RPR::Context::SetAOV(m_RprContext, m_AOV, m_RprFrameBuffer) != RPR_SUCCESS)
430433
{
431434
UE_LOG(LogRPRRenderer, Error, TEXT("RPR FrameBuffer creation failed"));
432435
RPR::Error::LogLastError(m_RprContext);
433436
}
434437
else
435438
UE_LOG(LogRPRRenderer, Log, TEXT("Framebuffer successfully created (%d,%d)"), m_Width, m_Height);
436439

440+
if (!RPR::GetSettings()->IsHybrid && RPR::GetSettings()->EnableAdaptiveSampling)
441+
EnableAdaptiveSampling();
442+
437443
m_Resize = false;
438444
m_ClearFramebuffer = true;
439445
m_DataLock.Unlock();
@@ -443,6 +449,8 @@ int FRPRRendererWorker::ResizeFramebuffer()
443449

444450
void FRPRRendererWorker::ClearFramebuffer()
445451
{
452+
bool allIsGood = true;
453+
446454
if (rprFrameBufferClear(m_RprFrameBuffer) != RPR_SUCCESS ||
447455
rprFrameBufferClear(m_RprResolvedFrameBuffer) != RPR_SUCCESS ||
448456
rprFrameBufferClear(m_RprColorFrameBuffer) != RPR_SUCCESS ||
@@ -455,10 +463,21 @@ void FRPRRendererWorker::ClearFramebuffer()
455463
rprFrameBufferClear(m_RprDiffuseAlbedoBuffer) != RPR_SUCCESS ||
456464
rprFrameBufferClear(m_RprDiffuseAlbedoResolvedBuffer) != RPR_SUCCESS)
457465
{
466+
allIsGood = false;
458467
UE_LOG(LogRPRRenderer, Error, TEXT("Couldn't clear framebuffer"));
459468
RPR::Error::LogLastError(m_RprContext);
460469
}
461-
else
470+
471+
if (!RPR::GetSettings()->IsHybrid)
472+
if (rprFrameBufferClear(m_RprVarianceBuffer) != RPR_SUCCESS ||
473+
rprFrameBufferClear(m_RprVarianceResolvedBuffer) != RPR_SUCCESS)
474+
{
475+
allIsGood = false;
476+
UE_LOG(LogRPRRenderer, Error, TEXT("Couldn't clear variance framebuffers"));
477+
RPR::Error::LogLastError(m_RprContext);
478+
}
479+
480+
if (allIsGood)
462481
{
463482
m_CurrentIteration = 0;
464483
m_PreviousRenderedIteration = 0;
@@ -844,30 +863,62 @@ int FRPRRendererWorker::RunDenoiser()
844863
return RPR_SUCCESS;
845864
}
846865

866+
void FRPRRendererWorker::EnableAdaptiveSampling()
867+
{
868+
if (ContextCreateFrameBuffer(m_RprContext, m_RprFrameBufferFormat, &m_RprFrameBufferDesc, &m_RprVarianceBuffer) != RPR_SUCCESS ||
869+
ContextCreateFrameBuffer(m_RprContext, m_RprFrameBufferFormat, &m_RprFrameBufferDesc, &m_RprVarianceResolvedBuffer) != RPR_SUCCESS)
870+
{
871+
UE_LOG(LogRPRRenderer, Error, TEXT("RPR VarianceBuffer creation failed"));
872+
RPR::Error::LogLastError(m_RprContext);
873+
}
874+
875+
if (RPR::Context::SetAOV(m_RprContext, RPR::EAOV::Variance, m_RprVarianceBuffer) != RPR_SUCCESS)
876+
{
877+
UE_LOG(LogRPRRenderer, Error, TEXT("Can't set AOV Variance"));
878+
RPR::Error::LogLastError(m_RprContext);
879+
}
880+
}
881+
882+
bool FRPRRendererWorker::IsAdaptiveSamplingFinalized()
883+
{
884+
rpr_uint activePixels = 1;
885+
RPR::Context::GetInfo(m_RprContext, RPR_CONTEXT_ACTIVE_PIXEL_COUNT, sizeof(activePixels), &activePixels);
886+
887+
return activePixels == 0;
888+
}
889+
847890
uint32 FRPRRendererWorker::Run()
848891
{
849892
URPRSettings *settings = RPR::GetSettings();
850893

851894
bool denoised = false;
852-
int status;
853895

854896
while (m_StopTaskCounter.GetValue() == 0)
855897
{
856898
const bool isPaused = PreRenderLoop();
899+
const bool applyAdapitveSampling = !settings->IsHybrid && settings->EnableAdaptiveSampling && m_CurrentIteration > settings->SamplingMin;
900+
const bool adaptiveSamplingFinalized = applyAdapitveSampling ? IsAdaptiveSamplingFinalized() : false;
901+
902+
const uint32 iterationCeil =
903+
(applyAdapitveSampling && settings->MaximumRenderIterations < settings->SamplingMax)
904+
? settings->SamplingMax
905+
: settings->MaximumRenderIterations;
906+
907+
const bool iterationCeilReached = m_CurrentIteration >= iterationCeil;
857908

858-
if (isPaused || m_CurrentIteration >= settings->MaximumRenderIterations)
909+
if (isPaused || iterationCeilReached || adaptiveSamplingFinalized)
859910
{
860-
if (settings->UseDenoiser && !denoised && m_CurrentIteration >= settings->MaximumRenderIterations)
911+
if (settings->UseDenoiser && !denoised && (iterationCeilReached || adaptiveSamplingFinalized))
861912
{
862-
status = ApplyDenoiser();
863-
if (status == RPR_SUCCESS) {
913+
const bool isSuccess = ApplyDenoiser() == RPR_SUCCESS;
914+
915+
if (isSuccess) {
864916
UE_LOG(LogRPRRenderer, Log, TEXT("Denoiser applied"));
865-
denoised = true;
866-
}
867-
else {
868-
UE_LOG(LogRPRRenderer, Log, TEXT("Denoiser apply failed. Ignore"));
869-
denoised = false;
917+
} else {
918+
UE_LOG(LogRPRRenderer, Log, TEXT("Denoiser applying failed. Ignore"));
870919
}
920+
921+
denoised = isSuccess;
871922
}
872923
FPlatformProcess::Sleep(0.1f);
873924
continue;
@@ -877,6 +928,12 @@ uint32 FRPRRendererWorker::Run()
877928
{
878929
SCOPE_CYCLE_COUNTER(STAT_ProRender_Render);
879930

931+
if (!settings->IsHybrid)
932+
{
933+
int status = rprContextSetParameterByKey1u(m_RprContext, RPR_CONTEXT_FRAMECOUNT, m_CurrentIteration);
934+
CHECK_WARNING(status, TEXT("Can't set CONTEXT_FRAMECOUNT"));
935+
}
936+
880937
// Render + Resolve
881938
if (RPR::Context::Render(m_RprContext) != RPR_SUCCESS)
882939
{
@@ -889,18 +946,26 @@ uint32 FRPRRendererWorker::Run()
889946
{
890947
SCOPE_CYCLE_COUNTER(STAT_ProRender_Resolve);
891948
if (!settings->IsHybrid)
892-
if (RPR::Context::ResolveFrameBuffer(m_RprContext, m_RprFrameBuffer, m_RprResolvedFrameBuffer) != RPR_SUCCESS ||
893-
RPR::Context::ResolveFrameBuffer(m_RprContext, m_RprShadingNormalBuffer, m_RprShadingNormalResolvedBuffer) != RPR_SUCCESS ||
894-
RPR::Context::ResolveFrameBuffer(m_RprContext, m_RprAovDepthBuffer, m_RprAovDepthResolvedBuffer) != RPR_SUCCESS ||
895-
RPR::Context::ResolveFrameBuffer(m_RprContext, m_RprDiffuseAlbedoBuffer, m_RprDiffuseAlbedoResolvedBuffer) != RPR_SUCCESS ||
896-
RPR::Context::ResolveFrameBuffer(m_RprContext, m_RprWorldCoordinatesBuffer, m_RprWorldCoordinatesResolvedBuffer) != RPR_SUCCESS ||
897-
RPR::Context::ResolveFrameBuffer(m_RprContext, m_RprAovDepthBuffer, m_RprAovDepthResolvedBuffer) != RPR_SUCCESS ||
898-
RPR::Context::ResolveFrameBuffer(m_RprContext, m_RprDiffuseAlbedoBuffer, m_RprDiffuseAlbedoResolvedBuffer) != RPR_SUCCESS)
949+
{
950+
if (RPR::Context::ResolveFrameBuffer(m_RprContext, m_RprFrameBuffer, m_RprResolvedFrameBuffer) != RPR_SUCCESS ||
951+
RPR::Context::ResolveFrameBuffer(m_RprContext, m_RprAovDepthBuffer, m_RprAovDepthResolvedBuffer) != RPR_SUCCESS ||
952+
RPR::Context::ResolveFrameBuffer(m_RprContext, m_RprShadingNormalBuffer, m_RprShadingNormalResolvedBuffer) != RPR_SUCCESS ||
953+
RPR::Context::ResolveFrameBuffer(m_RprContext, m_RprWorldCoordinatesBuffer, m_RprWorldCoordinatesResolvedBuffer) != RPR_SUCCESS ||
954+
RPR::Context::ResolveFrameBuffer(m_RprContext, m_RprDiffuseAlbedoBuffer, m_RprDiffuseAlbedoResolvedBuffer) != RPR_SUCCESS)
899955
{
900956
RPR::Error::LogLastError(m_RprContext);
901957
m_RenderLock.Unlock();
902958
UE_LOG(LogRPRRenderer, Error, TEXT("Couldn't resolve framebuffer at iteration %d, stopping.."), m_CurrentIteration);
903959
}
960+
961+
if (settings->EnableAdaptiveSampling)
962+
if (RPR::Context::ResolveFrameBuffer(m_RprContext, m_RprVarianceBuffer, m_RprVarianceResolvedBuffer) != RPR_SUCCESS)
963+
{
964+
RPR::Error::LogLastError(m_RprContext);
965+
m_RenderLock.Unlock();
966+
UE_LOG(LogRPRRenderer, Error, TEXT("Couldn't resolve Adaptive Sampling framebuffer at iteration %d, stopping.."), m_CurrentIteration);
967+
}
968+
}
904969
}
905970
m_RenderLock.Unlock();
906971

@@ -1018,7 +1083,18 @@ int FRPRRendererWorker::DestroyBuffers()
10181083
CHECK_WARNING(status, TEXT("can't destroy diffuse albedo framebuffer"));
10191084

10201085
status = DestroyFrameBuffer(&m_RprDiffuseAlbedoResolvedBuffer);
1021-
CHECK_WARNING(status, TEXT("can't destroy resolved framebuffer"));
1086+
CHECK_WARNING(status, TEXT("can't destroy diffuse albedo resolved framebuffer"));
1087+
1088+
if (m_RprVarianceBuffer) {
1089+
status = RPR::Context::UnSetAOV(m_RprContext, RPR::EAOV::Variance);
1090+
CHECK_WARNING(status, TEXT("can't unset variance aov buffer"));
1091+
1092+
status = DestroyFrameBuffer(&m_RprVarianceBuffer);
1093+
CHECK_WARNING(status, TEXT("can't destroy rpr variance framebuffer"));
1094+
}
1095+
1096+
status = DestroyFrameBuffer(&m_RprVarianceResolvedBuffer);
1097+
CHECK_WARNING(status, TEXT("can't destroy variance resolved framebuffer"));
10221098

10231099
return RPR_SUCCESS;
10241100
}

Plugins/RPRPlugin/Source/RPRPlugin/Private/Renderer/RPRRendererWorker.h

+4
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ class FRPRRendererWorker : public FRunnable
8686
int InitializeDenoiser();
8787
int CreateDenoiserFilter(RifFilterType type);
8888
int RunDenoiser();
89+
void EnableAdaptiveSampling();
90+
bool IsAdaptiveSamplingFinalized();
8991

9092
private:
9193

@@ -123,6 +125,8 @@ class FRPRRendererWorker : public FRunnable
123125
RPR::FFrameBuffer m_RprAovDepthResolvedBuffer;
124126
RPR::FFrameBuffer m_RprDiffuseAlbedoBuffer;
125127
RPR::FFrameBuffer m_RprDiffuseAlbedoResolvedBuffer;
128+
RPR::FFrameBuffer m_RprVarianceBuffer;
129+
RPR::FFrameBuffer m_RprVarianceResolvedBuffer;
126130

127131

128132
RPR::FPostEffect m_RprWhiteBalance;

Plugins/RPRPlugin/Source/RPRTools/Private/Helpers/ContextHelper.cpp

+38-15
Original file line numberDiff line numberDiff line change
@@ -61,28 +61,51 @@ namespace RPR
6161
return status;
6262
}
6363

64-
FResult UnSetAOV(FContext Context, RPR::EAOV AOV)
64+
FResult SetAOV(FContext Context, RPR::EAOV AOV, FFrameBuffer& FrameBuffer)
6565
{
66-
RPR::FResult status = rprContextSetAOV(Context, (rpr_aov) AOV, nullptr);
66+
RPR::FResult status = rprContextSetAOV(Context, (rpr_aov)AOV, FrameBuffer.get());
6767

68-
UE_LOG(LogRPRTools_Step, Verbose, TEXT("rprContextSetAOV(context=%p, AOV=%d) -> %d"),
68+
UE_LOG(
69+
LogRPRTools_Step,
70+
Verbose,
71+
TEXT("rprContextSetAOV(context=%p, AOV=%d, framebuffer=%p) -> %d"),
6972
Context,
70-
(uint8) AOV,
71-
status);
73+
(uint8)AOV,
74+
FrameBuffer.get(),
75+
status
76+
);
7277

7378
return status;
7479
}
7580

76-
77-
FResult SetAOV(FContext Context, RPR::EAOV AOV, FFrameBuffer& FrameBuffer)
81+
FResult UnSetAOV(FContext Context, RPR::EAOV AOV)
7882
{
79-
RPR::FResult status = rprContextSetAOV(Context, (rpr_aov) AOV, FrameBuffer.get());
83+
RPR::FResult status = rprContextSetAOV(Context, (rpr_aov)AOV, nullptr);
8084

81-
UE_LOG(LogRPRTools_Step, Verbose, TEXT("rprContextSetAOV(context=%p, AOV=%d, framebuffer=%p) -> %d"),
85+
UE_LOG(
86+
LogRPRTools_Step,
87+
Verbose,
88+
TEXT("rprContextSetAOV(context=%p, AOV=%d) -> %d"),
8289
Context,
83-
(uint8) AOV,
84-
FrameBuffer.get(),
85-
status);
90+
(uint8)AOV,
91+
status
92+
);
93+
94+
return status;
95+
}
96+
97+
FResult GetInfo(FContext context, rpr_int param, size_t outBufferSize, void* outBuffer, size_t* returnedDataSize)
98+
{
99+
FResult status = rprContextGetInfo(context, param, outBufferSize, outBuffer, returnedDataSize);
100+
101+
UE_LOG(
102+
LogRPRTools_Step,
103+
Verbose,
104+
TEXT("rprContextGetInfo(context=%p, context_info=%d) -> %d"),
105+
context,
106+
param,
107+
status
108+
);
86109

87110
return status;
88111
}
@@ -167,15 +190,15 @@ namespace RPR
167190
{
168191
return (rprContextSetParameterByKey1f(Context, ParamName, Value));
169192
}
170-
}
193+
} // namespace Parameters
171194

172195
namespace MaterialSystem
173196
{
174197
FResult Create(RPR::FContext Context, RPR::FMaterialSystemType Type, RPR::FMaterialSystem& OutMaterialSystem)
175198
{
176199
return (rprContextCreateMaterialSystem(Context, Type, &OutMaterialSystem));
177200
}
178-
}
201+
} // namespace MaterialSystem
179202

180-
}
203+
} // namespace Context
181204
}

Plugins/RPRPlugin/Source/RPRTools/Private/RPRSettings.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ URPRSettings::URPRSettings(const FObjectInitializer& PCIP)
6767
DefaultRootDirectoryForImportedTextures.Path = TEXT("/Game/Textures");
6868
ErrorTexture = LoadObject<UTexture2D>(nullptr, TEXT("/RPRPlugin/UETextures/T_TextureNotSupported.T_TextureNotSupported"));
6969
TryLoadUberMaterialFromDefaultLocation();
70+
EnableAdaptiveSampling = NoiseThreshold > 0.0f;
7071
}
7172

7273
void URPRSettings::TryLoadUberMaterialFromDefaultLocation()

Plugins/RPRPlugin/Source/RPRTools/Public/Enums/RPREnums.h

+3-2
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,8 @@ namespace RPR
318318
LightGroup1 = RPR_AOV_LIGHT_GROUP1,
319319
LightGroup2 = RPR_AOV_LIGHT_GROUP2,
320320
LightGroup3 = RPR_AOV_LIGHT_GROUP3,
321-
Max = RPR_AOV_MAX
321+
Max = RPR_AOV_MAX,
322+
Variance = RPR_AOV_VARIANCE
322323
};
323324

324325
enum class ESceneInfo
@@ -328,7 +329,7 @@ namespace RPR
328329
ShapeList = RPR_SCENE_SHAPE_LIST,
329330
LightList = RPR_SCENE_LIGHT_LIST,
330331
Camera = RPR_SCENE_CAMERA,
331-
BackgroundImage = RPR_SCENE_BACKGROUND_IMAGE,
332+
BackgroundImage = RPR_SCENE_BACKGROUND_IMAGE,
332333
EnvironmentOverrideReflection = RPR_SCENE_ENVIRONMENT_OVERRIDE_REFLECTION,
333334
EnvironmentOverrideRefraction = RPR_SCENE_ENVIRONMENT_OVERRIDE_REFRACTION,
334335
EnvironmentOverrideTransparency = RPR_SCENE_ENVIRONMENT_OVERRIDE_TRANSPARENCY,

Plugins/RPRPlugin/Source/RPRTools/Public/Helpers/ContextHelper.h

+1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ namespace RPR
4747
RPRTOOLS_API FResult SetScene(FContext Context, FScene Scene);
4848
RPRTOOLS_API FResult SetAOV(FContext Context, RPR::EAOV AOV, FFrameBuffer& FrameBuffer);
4949
RPRTOOLS_API FResult UnSetAOV(FContext Context, RPR::EAOV AOV);
50+
RPRTOOLS_API FResult GetInfo(FContext context, rpr_int param, size_t outBufferSize, void* outBuffer, size_t* returnedDataSize = nullptr);
5051
RPRTOOLS_API FResult Render(FContext Context);
5152
RPRTOOLS_API FResult ResolveFrameBuffer(FContext Context, FFrameBuffer& SrcFrameBuffer, FFrameBuffer& DstFrameBuffer, bool bNormalizeOnly = false);
5253

Plugins/RPRPlugin/Source/RPRTools/Public/RPRSettings.h

+3-1
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ class RPRTOOLS_API URPRSettings : public UObject
147147
uint32 SamplingMin;
148148

149149
UPROPERTY(Config, EditAnywhere, Category = RenderSettings)
150-
uint32 NoiseThreshold;
150+
float NoiseThreshold;
151151

152152
UPROPERTY(Config, EditAnywhere, Category = RenderSettings)
153153
uint32 RayDepthMax;
@@ -197,6 +197,8 @@ class RPRTOOLS_API URPRSettings : public UObject
197197
bool IsHybrid;
198198
ERenderType CurrentRenderType;
199199

200+
bool EnableAdaptiveSampling;
201+
200202
public:
201203

202204
void TryLoadUberMaterialFromDefaultLocation();

0 commit comments

Comments
 (0)