@@ -82,7 +82,7 @@ namespace AZ
|
||||
// Register Shader Asset Builder
|
||||
AssetBuilderSDK::AssetBuilderDesc shaderAssetBuilderDescriptor;
|
||||
shaderAssetBuilderDescriptor.m_name = "Shader Asset Builder";
|
||||
shaderAssetBuilderDescriptor.m_version = 110; // Add "Definitions" field to shader asset to support convenient addition of preprocessor definitions
|
||||
shaderAssetBuilderDescriptor.m_version = 111; // Enable shader PDB generation globally if Atom/GraphicsDevMode settings registry key is set
|
||||
shaderAssetBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern( AZStd::string::format("*.%s", RPI::ShaderSourceData::Extension), AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
|
||||
shaderAssetBuilderDescriptor.m_busId = azrtti_typeid<ShaderAssetBuilder>();
|
||||
shaderAssetBuilderDescriptor.m_createJobFunction = AZStd::bind(&ShaderAssetBuilder::CreateJobs, &m_shaderAssetBuilder, AZStd::placeholders::_1, AZStd::placeholders::_2);
|
||||
|
||||
@@ -318,10 +318,8 @@ namespace AZ
|
||||
RPI::RenderPipelineDescriptor renderPipelineDescriptor = *RPI::GetDataFromAnyAsset<RPI::RenderPipelineDescriptor>(pipelineAsset);
|
||||
renderPipelineDescriptor.m_name = AZStd::string::format("%s_%i", renderPipelineDescriptor.m_name.c_str(), viewportContext->GetId());
|
||||
|
||||
// Make sure non-msaa super variant is used for non-msaa pipeline
|
||||
bool isNonMsaaPipeline = (renderPipelineDescriptor.m_renderSettings.m_multisampleState.m_samples == 1);
|
||||
const char* supervariantName = isNonMsaaPipeline ? AZ::RPI::NoMsaaSupervariantName : "";
|
||||
AZ::RPI::ShaderSystemInterface::Get()->SetSupervariantName(AZ::Name(supervariantName));
|
||||
// The default pipeline determines the initial MSAA state for the application
|
||||
AZ::RPI::RPISystemInterface::Get()->SetApplicationMultisampleState(renderPipelineDescriptor.m_renderSettings.m_multisampleState);
|
||||
|
||||
if (!scene->GetRenderPipeline(AZ::Name(renderPipelineDescriptor.m_name)))
|
||||
{
|
||||
|
||||
@@ -34,11 +34,11 @@
|
||||
"Attachment": "Output"
|
||||
}
|
||||
},
|
||||
"MultisampleSource": {
|
||||
"Pass": "Pipeline"
|
||||
},
|
||||
"ImageDescriptor": {
|
||||
"Format": "D32_FLOAT_S8X24_UINT",
|
||||
"MultisampleState": {
|
||||
"samples": 4
|
||||
},
|
||||
"SharedQueueMask": "Graphics"
|
||||
}
|
||||
}
|
||||
|
||||
+18
@@ -29,6 +29,24 @@
|
||||
"RootShaderVariantAssetFileName": "diffuseprobegridrender_null_0.azshadervariant"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "NoMSAA",
|
||||
"RootShaderVariantAssets":
|
||||
[
|
||||
{
|
||||
"APIName": "dx12",
|
||||
"RootShaderVariantAssetFileName": "diffuseprobegridrender-nomsaa_dx12_0.azshadervariant"
|
||||
},
|
||||
{
|
||||
"APIName": "vulkan",
|
||||
"RootShaderVariantAssetFileName": "diffuseprobegridrender-nomsaa_vulkan_0.azshadervariant"
|
||||
},
|
||||
{
|
||||
"APIName": "null",
|
||||
"RootShaderVariantAssetFileName": "diffuseprobegridrender-nomsaa_null_0.azshadervariant"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
+16
-13
@@ -92,21 +92,24 @@ namespace AZ
|
||||
AZ_Error("DiffuseProbeGridFeatureProcessor", m_probeGridRenderData.m_srgLayout != nullptr, "Failed to find ObjectSrg layout");
|
||||
}
|
||||
|
||||
// initialize the buffer pools for the DiffuseProbeGrid visualization
|
||||
m_visualizationBufferPools = RHI::RayTracingBufferPools::CreateRHIRayTracingBufferPools();
|
||||
m_visualizationBufferPools->Init(device);
|
||||
|
||||
// load probe visualization model, the BLAS will be created in OnAssetReady()
|
||||
m_visualizationModelAsset = AZ::RPI::AssetUtils::GetAssetByProductPath<AZ::RPI::ModelAsset>(
|
||||
"Models/DiffuseProbeSphere.azmodel",
|
||||
AZ::RPI::AssetUtils::TraceLevel::Assert);
|
||||
|
||||
if (!m_visualizationModelAsset.IsReady())
|
||||
if (device->GetFeatures().m_rayTracing)
|
||||
{
|
||||
m_visualizationModelAsset.QueueLoad();
|
||||
}
|
||||
// initialize the buffer pools for the DiffuseProbeGrid visualization
|
||||
m_visualizationBufferPools = RHI::RayTracingBufferPools::CreateRHIRayTracingBufferPools();
|
||||
m_visualizationBufferPools->Init(device);
|
||||
|
||||
Data::AssetBus::MultiHandler::BusConnect(m_visualizationModelAsset.GetId());
|
||||
// load probe visualization model, the BLAS will be created in OnAssetReady()
|
||||
m_visualizationModelAsset = AZ::RPI::AssetUtils::GetAssetByProductPath<AZ::RPI::ModelAsset>(
|
||||
"Models/DiffuseProbeSphere.azmodel",
|
||||
AZ::RPI::AssetUtils::TraceLevel::Assert);
|
||||
|
||||
if (!m_visualizationModelAsset.IsReady())
|
||||
{
|
||||
m_visualizationModelAsset.QueueLoad();
|
||||
}
|
||||
|
||||
Data::AssetBus::MultiHandler::BusConnect(m_visualizationModelAsset.GetId());
|
||||
}
|
||||
|
||||
EnableSceneNotification();
|
||||
}
|
||||
|
||||
@@ -115,10 +115,6 @@ namespace AZ
|
||||
// all faces of the cubemap have been rendered, invoke the callback
|
||||
m_callback(m_environmentCubeMapPass->GetTextureData(), m_environmentCubeMapPass->GetTextureFormat());
|
||||
|
||||
// remove the pipeline
|
||||
m_scene->RemoveRenderPipeline(m_environmentCubeMapPipelineId);
|
||||
m_environmentCubeMapPass = nullptr;
|
||||
|
||||
// restore exposures
|
||||
sceneSrg->SetConstant(m_globalIblExposureConstantIndex, m_previousGlobalIblExposure);
|
||||
sceneSrg->SetConstant(m_skyBoxExposureConstantIndex, m_previousSkyBoxExposure);
|
||||
@@ -223,6 +219,16 @@ namespace AZ
|
||||
}
|
||||
}
|
||||
|
||||
void ReflectionProbe::OnRenderEnd()
|
||||
{
|
||||
if (m_environmentCubeMapPass && m_environmentCubeMapPass->IsFinished())
|
||||
{
|
||||
// remove the cubemap pipeline
|
||||
// Note: this must be done here (not in Simulate) to avoid a race condition with other feature processors
|
||||
m_scene->RemoveRenderPipeline(m_environmentCubeMapPipelineId);
|
||||
m_environmentCubeMapPass = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void ReflectionProbe::SetTransform(const AZ::Transform& transform)
|
||||
{
|
||||
@@ -282,7 +288,7 @@ namespace AZ
|
||||
|
||||
AZ::RPI::RenderPipelineDescriptor environmentCubeMapPipelineDesc;
|
||||
environmentCubeMapPipelineDesc.m_mainViewTagName = "MainCamera";
|
||||
environmentCubeMapPipelineDesc.m_renderSettings.m_multisampleState.m_samples = 4;
|
||||
environmentCubeMapPipelineDesc.m_renderSettings.m_multisampleState = RPI::RPISystemInterface::Get()->GetApplicationMultisampleState();
|
||||
environmentCubeMapPipelineDesc.m_renderSettings.m_size.m_width = RPI::EnvironmentCubeMapPass::CubeMapFaceSize;
|
||||
environmentCubeMapPipelineDesc.m_renderSettings.m_size.m_height = RPI::EnvironmentCubeMapPass::CubeMapFaceSize;
|
||||
|
||||
|
||||
@@ -75,6 +75,7 @@ namespace AZ
|
||||
|
||||
void Init(RPI::Scene* scene, ReflectionRenderData* reflectionRenderData);
|
||||
void Simulate(uint32_t probeIndex);
|
||||
void OnRenderEnd();
|
||||
|
||||
const Vector3& GetPosition() const { return m_transform.GetTranslation(); }
|
||||
const AZ::Transform& GetTransform() const { return m_transform; }
|
||||
|
||||
+12
@@ -166,6 +166,18 @@ namespace AZ
|
||||
}
|
||||
}
|
||||
|
||||
void ReflectionProbeFeatureProcessor::OnRenderEnd()
|
||||
{
|
||||
// call OnRenderEnd on all reflection probes
|
||||
for (uint32_t probeIndex = 0; probeIndex < m_reflectionProbes.size(); ++probeIndex)
|
||||
{
|
||||
AZStd::shared_ptr<ReflectionProbe>& reflectionProbe = m_reflectionProbes[probeIndex];
|
||||
AZ_Assert(reflectionProbe.use_count() > 1, "ReflectionProbe found with no corresponding owner, ensure that RemoveProbe() is called before releasing probe handles");
|
||||
|
||||
reflectionProbe->OnRenderEnd();
|
||||
}
|
||||
}
|
||||
|
||||
ReflectionProbeHandle ReflectionProbeFeatureProcessor::AddProbe(const AZ::Transform& transform, bool useParallaxCorrection)
|
||||
{
|
||||
AZStd::shared_ptr<ReflectionProbe> reflectionProbe = AZStd::make_shared<ReflectionProbe>();
|
||||
|
||||
@@ -46,6 +46,7 @@ namespace AZ
|
||||
void Activate() override;
|
||||
void Deactivate() override;
|
||||
void Simulate(const FeatureProcessor::SimulatePacket& packet) override;
|
||||
void OnRenderEnd() override;
|
||||
|
||||
// find the reflection probe volumes that contain the position
|
||||
using ReflectionProbeVector = AZStd::vector<AZStd::shared_ptr<ReflectionProbe>>;
|
||||
|
||||
@@ -42,6 +42,9 @@ namespace AZ
|
||||
|
||||
//! Returns if the current bakcend is a null renderer
|
||||
bool IsNullRenderer();
|
||||
|
||||
//! Returns true if the Atom/GraphicsDevMode settings registry key is set
|
||||
bool IsGraphicsDevModeEnabled();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,9 +9,12 @@
|
||||
#include <Atom/RHI/RHIUtils.h>
|
||||
#include <Atom/RHI/RHISystemInterface.h>
|
||||
#include <Atom/RHI/Factory.h>
|
||||
#include <AzCore/Settings/SettingsRegistry.h>
|
||||
#include <AzFramework/API/ApplicationAPI.h>
|
||||
#include <AzFramework/CommandLine/CommandLine.h>
|
||||
|
||||
static constexpr char GraphicsDevModeSetting[] = "/Atom/GraphicsDevMode";
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
namespace RHI
|
||||
@@ -134,5 +137,16 @@ namespace AZ
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsGraphicsDevModeEnabled()
|
||||
{
|
||||
AZ::SettingsRegistryInterface* settingsRegistry = AZ::SettingsRegistry::Get();
|
||||
bool graphicsDevMode = false;
|
||||
if (settingsRegistry)
|
||||
{
|
||||
settingsRegistry->Get(graphicsDevMode, GraphicsDevModeSetting);
|
||||
}
|
||||
return graphicsDevMode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,10 +12,10 @@
|
||||
#include <Atom/RHI.Edit/Utils.h>
|
||||
#include <Atom/RHI.Reflect/DX12/PipelineLayoutDescriptor.h>
|
||||
#include <Atom/RHI.Reflect/DX12/ShaderStageFunction.h>
|
||||
#include <Atom/RHI/RHIUtils.h>
|
||||
|
||||
#include <AzCore/IO/FileIO.h>
|
||||
#include <AzCore/IO/SystemFile.h>
|
||||
|
||||
#include <AzFramework/StringFunc/StringFunc.h>
|
||||
|
||||
namespace AZ
|
||||
@@ -253,9 +253,11 @@ namespace AZ
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool graphicsDevMode = RHI::IsGraphicsDevModeEnabled();
|
||||
|
||||
// Compilation parameters
|
||||
AZStd::string params = shaderCompilerArguments.MakeAdditionalDxcCommandLineString();
|
||||
if (BuildHasDebugInfo(shaderCompilerArguments))
|
||||
if (graphicsDevMode || BuildHasDebugInfo(shaderCompilerArguments))
|
||||
{
|
||||
params += " -Zi"; // Generate debug information
|
||||
params += " -Zss"; // Compute Shader Hash considering source information
|
||||
@@ -284,7 +286,7 @@ namespace AZ
|
||||
// If we use the auto-name (hash), there is no way we can retrieve that name apart from listing the directory.
|
||||
// Instead, let's just generate that hash ourselves.
|
||||
AZStd::string symbolDatabaseFileCliArgument{" "}; // when not debug: still insert a space between 5.dxil and 7.hlsl-in
|
||||
if (BuildHasDebugInfo(shaderCompilerArguments))
|
||||
if (graphicsDevMode || BuildHasDebugInfo(shaderCompilerArguments))
|
||||
{
|
||||
// prepare .pdb filename:
|
||||
AZStd::string md5hex = RHI::ByteToHexString(md5);
|
||||
@@ -353,7 +355,7 @@ namespace AZ
|
||||
byProducts.m_dynamicBranchCount = ByProducts::UnknownDynamicBranchCount;
|
||||
}
|
||||
|
||||
if (BuildHasDebugInfo(shaderCompilerArguments))
|
||||
if (graphicsDevMode || BuildHasDebugInfo(shaderCompilerArguments))
|
||||
{
|
||||
byProducts.m_intermediatePaths.emplace(AZStd::move(objectCodeOutputFile));
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
#include <Atom/RHI.Reflect/Vulkan/Base.h>
|
||||
#include <Atom/RHI.Reflect/Vulkan/ShaderStageFunction.h>
|
||||
#include <Atom/RHI/RHIUtils.h>
|
||||
|
||||
#include <AzCore/IO/FileIO.h>
|
||||
#include <AzCore/IO/SystemFile.h>
|
||||
@@ -281,7 +282,9 @@ namespace AZ
|
||||
args.m_destinationFolder = tempFolder.c_str();
|
||||
|
||||
const auto dxcInputFile = RHI::PrependFile(args); // Prepend header
|
||||
if (BuildHasDebugInfo(shaderCompilerArguments))
|
||||
const bool graphicsDevMode = RHI::IsGraphicsDevModeEnabled();
|
||||
|
||||
if (graphicsDevMode || BuildHasDebugInfo(shaderCompilerArguments))
|
||||
{
|
||||
// dump intermediate "true final HLSL" file (shadername.vulkan.shadersource.prepend)
|
||||
byProducts.m_intermediatePaths.insert(dxcInputFile);
|
||||
@@ -334,7 +337,7 @@ namespace AZ
|
||||
byProducts.m_dynamicBranchCount = ByProducts::UnknownDynamicBranchCount;
|
||||
}
|
||||
|
||||
if (BuildHasDebugInfo(shaderCompilerArguments))
|
||||
if (graphicsDevMode || BuildHasDebugInfo(shaderCompilerArguments))
|
||||
{
|
||||
byProducts.m_intermediatePaths.emplace(AZStd::move(objectCodeOutputFile));
|
||||
}
|
||||
|
||||
@@ -86,6 +86,8 @@ namespace AZ
|
||||
const RPISystemDescriptor& GetDescriptor() const override;
|
||||
Name GetRenderApiName() const override;
|
||||
uint64_t GetCurrentTick() const override;
|
||||
void SetApplicationMultisampleState(const RHI::MultisampleState& multisampleState) override;
|
||||
const RHI::MultisampleState& GetApplicationMultisampleState() const override;
|
||||
|
||||
// AZ::Debug::TraceMessageBus::Handler overrides...
|
||||
bool OnPreAssert(const char* fileName, int line, const char* func, const char* message) override;
|
||||
@@ -136,6 +138,9 @@ namespace AZ
|
||||
bool m_systemAssetsInitialized = false;
|
||||
|
||||
uint64_t m_renderTick = 0;
|
||||
|
||||
// Application multisample state
|
||||
RHI::MultisampleState m_multisampleState;
|
||||
};
|
||||
|
||||
} // namespace RPI
|
||||
|
||||
@@ -90,6 +90,10 @@ namespace AZ
|
||||
|
||||
//! Get the index of current render tick
|
||||
virtual uint64_t GetCurrentTick() const = 0;
|
||||
|
||||
//! Application multisample state
|
||||
virtual void SetApplicationMultisampleState(const RHI::MultisampleState& multisampleState) = 0;
|
||||
virtual const RHI::MultisampleState& GetApplicationMultisampleState() const = 0;
|
||||
};
|
||||
|
||||
} // namespace RPI
|
||||
|
||||
@@ -434,5 +434,29 @@ namespace AZ
|
||||
return m_renderTick;
|
||||
}
|
||||
|
||||
void RPISystem::SetApplicationMultisampleState(const RHI::MultisampleState& multisampleState)
|
||||
{
|
||||
m_multisampleState = multisampleState;
|
||||
|
||||
bool isNonMsaaPipeline = (m_multisampleState.m_samples == 1);
|
||||
const char* supervariantName = isNonMsaaPipeline ? AZ::RPI::NoMsaaSupervariantName : "";
|
||||
AZ::RPI::ShaderSystemInterface::Get()->SetSupervariantName(AZ::Name(supervariantName));
|
||||
|
||||
// reinitialize pipelines for all scenes
|
||||
for (auto& scene : m_scenes)
|
||||
{
|
||||
for (auto& renderPipeline : scene->GetRenderPipelines())
|
||||
{
|
||||
renderPipeline->GetRenderSettings().m_multisampleState = multisampleState;
|
||||
renderPipeline->SetPassNeedsRecreate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const RHI::MultisampleState& RPISystem::GetApplicationMultisampleState() const
|
||||
{
|
||||
return m_multisampleState;
|
||||
}
|
||||
|
||||
} //namespace RPI
|
||||
} //namespace AZ
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
|
||||
#include <AzCore/RTTI/ReflectContext.h>
|
||||
#include <AzCore/Serialization/SerializeContext.h>
|
||||
#include <AzCore/Serialization/EditContext.h>
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
@@ -35,6 +36,15 @@ namespace AZ
|
||||
->Field("MaterialSlots", &ModelAsset::m_materialSlots)
|
||||
->Field("LodAssets", &ModelAsset::m_lodAssets)
|
||||
;
|
||||
|
||||
// Note: This class needs to have edit context reflection so PropertyAssetCtrl::OnEditButtonClicked
|
||||
// can open the asset with the preferred asset editor (Scene Settings).
|
||||
if (auto* editContext = serializeContext->GetEditContext())
|
||||
{
|
||||
editContext->Class<ModelAsset>("Model Asset", "")
|
||||
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
|
||||
;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -63,10 +63,8 @@ namespace AtomToolsFramework
|
||||
pipelineDesc.m_mainViewTagName = "MainCamera";
|
||||
pipelineDesc.m_name = pipelineName;
|
||||
pipelineDesc.m_rootPassTemplate = "ToolsPipelineRenderToTexture";
|
||||
pipelineDesc.m_renderSettings.m_multisampleState = AZ::RPI::RPISystemInterface::Get()->GetApplicationMultisampleState();
|
||||
|
||||
// We have to set the samples to 4 to match the pipeline passes' setting, otherwise it may lead to device lost issue
|
||||
// [GFX TODO] [ATOM-13551] Default value sand validation required to prevent pipeline crash and device lost
|
||||
pipelineDesc.m_renderSettings.m_multisampleState.m_samples = 4;
|
||||
m_renderPipeline = AZ::RPI::RenderPipeline::CreateRenderPipeline(pipelineDesc);
|
||||
m_scene->AddRenderPipeline(m_renderPipeline);
|
||||
m_scene->Activate();
|
||||
|
||||
@@ -72,6 +72,9 @@ namespace AZ
|
||||
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
|
||||
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
|
||||
->DataElement(AZ::Edit::UIHandlers::Default, &MeshComponentConfig::m_modelAsset, "Mesh Asset", "Mesh asset reference")
|
||||
->Attribute(AZ_CRC_CE("EditButton"), "")
|
||||
->Attribute(AZ_CRC_CE("EditDescription"), "Open in Scene Settings")
|
||||
->Attribute(AZ_CRC_CE("DisableEditButtonWhenNoAssetSelected"), true)
|
||||
->DataElement(AZ::Edit::UIHandlers::Default, &MeshComponentConfig::m_sortKey, "Sort Key", "Transparent meshes are drawn by sort key then depth. Used this to force certain transparent meshes to draw before or after others.")
|
||||
->Attribute(AZ::Edit::Attributes::Visibility, &MeshComponentConfig::IsAssetSet)
|
||||
->DataElement(AZ::Edit::UIHandlers::CheckBox, &MeshComponentConfig::m_excludeFromReflectionCubeMaps, "Exclude from reflection cubemaps", "Mesh will not be visible in baked reflection probe cubemaps")
|
||||
|
||||
+1
-1
@@ -42,5 +42,5 @@ ly_associate_package(PACKAGE_NAME astc-encoder-3.2-rev5-mac
|
||||
ly_associate_package(PACKAGE_NAME ISPCTexComp-36b80aa-rev1-mac TARGETS ISPCTexComp PACKAGE_HASH 8a4e93277b8face6ea2fd57c6d017bdb55643ed3d6387110bc5f6b3b884dd169)
|
||||
ly_associate_package(PACKAGE_NAME lz4-1.9.3-vcpkg-rev4-mac TARGETS lz4 PACKAGE_HASH 891ff630bf34f7ab1d8eaee2ea0a8f1fca89dbdc63fca41ee592703dd488a73b)
|
||||
ly_associate_package(PACKAGE_NAME azslc-1.7.35-rev1-mac TARGETS azslc PACKAGE_HASH 03cb1ea8c47d4c80c893e2e88767272d5d377838f5ba94b777a45902dd85052e)
|
||||
ly_associate_package(PACKAGE_NAME SQLite-3.37.2-rev1-mac TARGETS SQLite PACKAGE_HASH f9101023f99cf32fc5867284ceb28c0761c23d2c5a4b1748349c69f976a2fbea)
|
||||
ly_associate_package(PACKAGE_NAME SQLite-3.37.2-rev2-mac TARGETS SQLite PACKAGE_HASH b7d9abdb68045003e030e1a9a805db1aefa5e8fde6dccfbb4fab3a06249a41fc)
|
||||
ly_associate_package(PACKAGE_NAME AwsIotDeviceSdkCpp-1.15.2-rev2-mac TARGETS AwsIotDeviceSdkCpp PACKAGE_HASH 4854edb7b88fa6437b4e69e87d0ee111a25313ac2a2db5bb2f8b674ba0974f95)
|
||||
|
||||
Reference in New Issue
Block a user