mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2025-07-23 18:45:36 +00:00
Merge branch 'main' into qtsaves2
This commit is contained in:
commit
fb7285b4f5
@ -271,7 +271,8 @@ void SetupCapabilities(const Info& info, const Profile& profile, EmitContext& ct
|
||||
if (info.has_image_query) {
|
||||
ctx.AddCapability(spv::Capability::ImageQuery);
|
||||
}
|
||||
if (info.uses_atomic_float_min_max && profile.supports_image_fp32_atomic_min_max) {
|
||||
if ((info.uses_image_atomic_float_min_max && profile.supports_image_fp32_atomic_min_max) ||
|
||||
(info.uses_buffer_atomic_float_min_max && profile.supports_buffer_fp32_atomic_min_max)) {
|
||||
ctx.AddExtension("SPV_EXT_shader_atomic_float_min_max");
|
||||
ctx.AddCapability(spv::Capability::AtomicFloat32MinMaxEXT);
|
||||
}
|
||||
|
@ -50,9 +50,17 @@ Id SharedAtomicU64(EmitContext& ctx, Id offset, Id value,
|
||||
});
|
||||
}
|
||||
|
||||
template <bool is_float = false>
|
||||
Id BufferAtomicU32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value,
|
||||
Id (Sirit::Module::*atomic_func)(Id, Id, Id, Id, Id)) {
|
||||
const auto& buffer = ctx.buffers[handle];
|
||||
const auto type = [&] {
|
||||
if constexpr (is_float) {
|
||||
return ctx.F32[1];
|
||||
} else {
|
||||
return ctx.U32[1];
|
||||
}
|
||||
}();
|
||||
if (Sirit::ValidId(buffer.offset)) {
|
||||
address = ctx.OpIAdd(ctx.U32[1], address, buffer.offset);
|
||||
}
|
||||
@ -60,8 +68,8 @@ Id BufferAtomicU32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id
|
||||
const auto [id, pointer_type] = buffer[EmitContext::PointerType::U32];
|
||||
const Id ptr = ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, index);
|
||||
const auto [scope, semantics]{AtomicArgs(ctx)};
|
||||
return AccessBoundsCheck<32>(ctx, index, buffer.size_dwords, [&] {
|
||||
return (ctx.*atomic_func)(ctx.U32[1], ptr, scope, semantics, value);
|
||||
return AccessBoundsCheck<32, 1, is_float>(ctx, index, buffer.size_dwords, [&] {
|
||||
return (ctx.*atomic_func)(type, ptr, scope, semantics, value);
|
||||
});
|
||||
}
|
||||
|
||||
@ -196,6 +204,24 @@ Id EmitBufferAtomicUMin32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id addre
|
||||
return BufferAtomicU32(ctx, inst, handle, address, value, &Sirit::Module::OpAtomicUMin);
|
||||
}
|
||||
|
||||
Id EmitBufferAtomicFMin32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) {
|
||||
if (ctx.profile.supports_buffer_fp32_atomic_min_max) {
|
||||
return BufferAtomicU32<true>(ctx, inst, handle, address, value,
|
||||
&Sirit::Module::OpAtomicFMin);
|
||||
}
|
||||
|
||||
const auto u32_value = ctx.OpBitcast(ctx.U32[1], value);
|
||||
const auto sign_bit_set =
|
||||
ctx.OpBitFieldUExtract(ctx.U32[1], u32_value, ctx.ConstU32(31u), ctx.ConstU32(1u));
|
||||
|
||||
const auto result = ctx.OpSelect(
|
||||
ctx.F32[1], sign_bit_set,
|
||||
EmitBitCastF32U32(ctx, EmitBufferAtomicUMax32(ctx, inst, handle, address, u32_value)),
|
||||
EmitBitCastF32U32(ctx, EmitBufferAtomicSMin32(ctx, inst, handle, address, u32_value)));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Id EmitBufferAtomicSMax32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) {
|
||||
return BufferAtomicU32(ctx, inst, handle, address, value, &Sirit::Module::OpAtomicSMax);
|
||||
}
|
||||
@ -204,6 +230,24 @@ Id EmitBufferAtomicUMax32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id addre
|
||||
return BufferAtomicU32(ctx, inst, handle, address, value, &Sirit::Module::OpAtomicUMax);
|
||||
}
|
||||
|
||||
Id EmitBufferAtomicFMax32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) {
|
||||
if (ctx.profile.supports_buffer_fp32_atomic_min_max) {
|
||||
return BufferAtomicU32<true>(ctx, inst, handle, address, value,
|
||||
&Sirit::Module::OpAtomicFMax);
|
||||
}
|
||||
|
||||
const auto u32_value = ctx.OpBitcast(ctx.U32[1], value);
|
||||
const auto sign_bit_set =
|
||||
ctx.OpBitFieldUExtract(ctx.U32[1], u32_value, ctx.ConstU32(31u), ctx.ConstU32(1u));
|
||||
|
||||
const auto result = ctx.OpSelect(
|
||||
ctx.F32[1], sign_bit_set,
|
||||
EmitBitCastF32U32(ctx, EmitBufferAtomicUMin32(ctx, inst, handle, address, u32_value)),
|
||||
EmitBitCastF32U32(ctx, EmitBufferAtomicSMax32(ctx, inst, handle, address, u32_value)));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Id EmitBufferAtomicInc32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) {
|
||||
return BufferAtomicU32IncDec(ctx, inst, handle, address, &Sirit::Module::OpAtomicIIncrement);
|
||||
}
|
||||
|
@ -92,8 +92,10 @@ Id EmitBufferAtomicIAdd64(EmitContext& ctx, IR::Inst* inst, u32 handle, Id addre
|
||||
Id EmitBufferAtomicISub32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
|
||||
Id EmitBufferAtomicSMin32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
|
||||
Id EmitBufferAtomicUMin32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
|
||||
Id EmitBufferAtomicFMin32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
|
||||
Id EmitBufferAtomicSMax32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
|
||||
Id EmitBufferAtomicUMax32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
|
||||
Id EmitBufferAtomicFMax32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
|
||||
Id EmitBufferAtomicInc32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address);
|
||||
Id EmitBufferAtomicDec32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address);
|
||||
Id EmitBufferAtomicAnd32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
|
||||
|
@ -90,6 +90,10 @@ void Translator::EmitVectorMemory(const GcnInst& inst) {
|
||||
return BUFFER_ATOMIC(AtomicOp::Inc, inst);
|
||||
case Opcode::BUFFER_ATOMIC_DEC:
|
||||
return BUFFER_ATOMIC(AtomicOp::Dec, inst);
|
||||
case Opcode::BUFFER_ATOMIC_FMIN:
|
||||
return BUFFER_ATOMIC(AtomicOp::Fmin, inst);
|
||||
case Opcode::BUFFER_ATOMIC_FMAX:
|
||||
return BUFFER_ATOMIC(AtomicOp::Fmax, inst);
|
||||
|
||||
// MIMG
|
||||
// Image load operations
|
||||
@ -357,6 +361,10 @@ void Translator::BUFFER_ATOMIC(AtomicOp op, const GcnInst& inst) {
|
||||
return ir.BufferAtomicInc(handle, address, buffer_info);
|
||||
case AtomicOp::Dec:
|
||||
return ir.BufferAtomicDec(handle, address, buffer_info);
|
||||
case AtomicOp::Fmin:
|
||||
return ir.BufferAtomicFMin(handle, address, vdata_val, buffer_info);
|
||||
case AtomicOp::Fmax:
|
||||
return ir.BufferAtomicFMax(handle, address, vdata_val, buffer_info);
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
@ -215,7 +215,8 @@ struct Info {
|
||||
bool has_image_query{};
|
||||
bool has_perspective_interp{};
|
||||
bool has_linear_interp{};
|
||||
bool uses_atomic_float_min_max{};
|
||||
bool uses_buffer_atomic_float_min_max{};
|
||||
bool uses_image_atomic_float_min_max{};
|
||||
bool uses_lane_id{};
|
||||
bool uses_group_quad{};
|
||||
bool uses_group_ballot{};
|
||||
|
@ -504,12 +504,22 @@ Value IREmitter::BufferAtomicIMin(const Value& handle, const Value& address, con
|
||||
: Inst(Opcode::BufferAtomicUMin32, Flags{info}, handle, address, value);
|
||||
}
|
||||
|
||||
Value IREmitter::BufferAtomicFMin(const Value& handle, const Value& address, const Value& value,
|
||||
BufferInstInfo info) {
|
||||
return Inst(Opcode::BufferAtomicFMin32, Flags{info}, handle, address, value);
|
||||
}
|
||||
|
||||
Value IREmitter::BufferAtomicIMax(const Value& handle, const Value& address, const Value& value,
|
||||
bool is_signed, BufferInstInfo info) {
|
||||
return is_signed ? Inst(Opcode::BufferAtomicSMax32, Flags{info}, handle, address, value)
|
||||
: Inst(Opcode::BufferAtomicUMax32, Flags{info}, handle, address, value);
|
||||
}
|
||||
|
||||
Value IREmitter::BufferAtomicFMax(const Value& handle, const Value& address, const Value& value,
|
||||
BufferInstInfo info) {
|
||||
return Inst(Opcode::BufferAtomicFMax32, Flags{info}, handle, address, value);
|
||||
}
|
||||
|
||||
Value IREmitter::BufferAtomicInc(const Value& handle, const Value& address, BufferInstInfo info) {
|
||||
return Inst(Opcode::BufferAtomicInc32, Flags{info}, handle, address);
|
||||
}
|
||||
|
@ -140,8 +140,12 @@ public:
|
||||
const Value& value, BufferInstInfo info);
|
||||
[[nodiscard]] Value BufferAtomicIMin(const Value& handle, const Value& address,
|
||||
const Value& value, bool is_signed, BufferInstInfo info);
|
||||
[[nodiscard]] Value BufferAtomicFMin(const Value& handle, const Value& address,
|
||||
const Value& value, BufferInstInfo info);
|
||||
[[nodiscard]] Value BufferAtomicIMax(const Value& handle, const Value& address,
|
||||
const Value& value, bool is_signed, BufferInstInfo info);
|
||||
[[nodiscard]] Value BufferAtomicFMax(const Value& handle, const Value& address,
|
||||
const Value& value, BufferInstInfo info);
|
||||
[[nodiscard]] Value BufferAtomicInc(const Value& handle, const Value& address,
|
||||
BufferInstInfo info);
|
||||
[[nodiscard]] Value BufferAtomicDec(const Value& handle, const Value& address,
|
||||
|
@ -71,8 +71,10 @@ bool Inst::MayHaveSideEffects() const noexcept {
|
||||
case Opcode::BufferAtomicISub32:
|
||||
case Opcode::BufferAtomicSMin32:
|
||||
case Opcode::BufferAtomicUMin32:
|
||||
case Opcode::BufferAtomicFMin32:
|
||||
case Opcode::BufferAtomicSMax32:
|
||||
case Opcode::BufferAtomicUMax32:
|
||||
case Opcode::BufferAtomicFMax32:
|
||||
case Opcode::BufferAtomicInc32:
|
||||
case Opcode::BufferAtomicDec32:
|
||||
case Opcode::BufferAtomicAnd32:
|
||||
|
@ -125,8 +125,10 @@ OPCODE(BufferAtomicIAdd64, U64, Opaq
|
||||
OPCODE(BufferAtomicISub32, U32, Opaque, Opaque, U32 )
|
||||
OPCODE(BufferAtomicSMin32, U32, Opaque, Opaque, U32 )
|
||||
OPCODE(BufferAtomicUMin32, U32, Opaque, Opaque, U32 )
|
||||
OPCODE(BufferAtomicFMin32, U32, Opaque, Opaque, F32 )
|
||||
OPCODE(BufferAtomicSMax32, U32, Opaque, Opaque, U32 )
|
||||
OPCODE(BufferAtomicUMax32, U32, Opaque, Opaque, U32 )
|
||||
OPCODE(BufferAtomicFMax32, U32, Opaque, Opaque, F32 )
|
||||
OPCODE(BufferAtomicInc32, U32, Opaque, Opaque, )
|
||||
OPCODE(BufferAtomicDec32, U32, Opaque, Opaque, )
|
||||
OPCODE(BufferAtomicAnd32, U32, Opaque, Opaque, U32, )
|
||||
|
@ -21,8 +21,10 @@ bool IsBufferAtomic(const IR::Inst& inst) {
|
||||
case IR::Opcode::BufferAtomicISub32:
|
||||
case IR::Opcode::BufferAtomicSMin32:
|
||||
case IR::Opcode::BufferAtomicUMin32:
|
||||
case IR::Opcode::BufferAtomicFMin32:
|
||||
case IR::Opcode::BufferAtomicSMax32:
|
||||
case IR::Opcode::BufferAtomicUMax32:
|
||||
case IR::Opcode::BufferAtomicFMax32:
|
||||
case IR::Opcode::BufferAtomicInc32:
|
||||
case IR::Opcode::BufferAtomicDec32:
|
||||
case IR::Opcode::BufferAtomicAnd32:
|
||||
|
@ -92,7 +92,11 @@ void Visit(Info& info, const IR::Inst& inst) {
|
||||
break;
|
||||
case IR::Opcode::ImageAtomicFMax32:
|
||||
case IR::Opcode::ImageAtomicFMin32:
|
||||
info.uses_atomic_float_min_max = true;
|
||||
info.uses_image_atomic_float_min_max = true;
|
||||
break;
|
||||
case IR::Opcode::BufferAtomicFMax32:
|
||||
case IR::Opcode::BufferAtomicFMin32:
|
||||
info.uses_buffer_atomic_float_min_max = true;
|
||||
break;
|
||||
case IR::Opcode::LaneId:
|
||||
info.uses_lane_id = true;
|
||||
|
@ -28,6 +28,7 @@ struct Profile {
|
||||
bool supports_native_cube_calc{};
|
||||
bool supports_trinary_minmax{};
|
||||
bool supports_robust_buffer_access{};
|
||||
bool supports_buffer_fp32_atomic_min_max{};
|
||||
bool supports_image_fp32_atomic_min_max{};
|
||||
bool supports_workgroup_explicit_memory_layout{};
|
||||
bool has_broken_spirv_clamp{};
|
||||
|
@ -281,6 +281,8 @@ bool Instance::CreateDevice() {
|
||||
if (shader_atomic_float2) {
|
||||
shader_atomic_float2_features =
|
||||
feature_chain.get<vk::PhysicalDeviceShaderAtomicFloat2FeaturesEXT>();
|
||||
LOG_INFO(Render_Vulkan, "- shaderBufferFloat32AtomicMinMax: {}",
|
||||
shader_atomic_float2_features.shaderBufferFloat32AtomicMinMax);
|
||||
LOG_INFO(Render_Vulkan, "- shaderImageFloat32AtomicMinMax: {}",
|
||||
shader_atomic_float2_features.shaderImageFloat32AtomicMinMax);
|
||||
}
|
||||
@ -433,6 +435,8 @@ bool Instance::CreateDevice() {
|
||||
.legacyVertexAttributes = true,
|
||||
},
|
||||
vk::PhysicalDeviceShaderAtomicFloat2FeaturesEXT{
|
||||
.shaderBufferFloat32AtomicMinMax =
|
||||
shader_atomic_float2_features.shaderBufferFloat32AtomicMinMax,
|
||||
.shaderImageFloat32AtomicMinMax =
|
||||
shader_atomic_float2_features.shaderImageFloat32AtomicMinMax,
|
||||
},
|
||||
|
@ -165,6 +165,13 @@ public:
|
||||
return amd_shader_trinary_minmax;
|
||||
}
|
||||
|
||||
/// Returns true when the shaderBufferFloat32AtomicMinMax feature of
|
||||
/// VK_EXT_shader_atomic_float2 is supported.
|
||||
bool IsShaderAtomicFloatBuffer32MinMaxSupported() const {
|
||||
return shader_atomic_float2 &&
|
||||
shader_atomic_float2_features.shaderBufferFloat32AtomicMinMax;
|
||||
}
|
||||
|
||||
/// Returns true when the shaderImageFloat32AtomicMinMax feature of
|
||||
/// VK_EXT_shader_atomic_float2 is supported.
|
||||
bool IsShaderAtomicFloatImage32MinMaxSupported() const {
|
||||
@ -324,6 +331,9 @@ public:
|
||||
return driver_id != vk::DriverId::eMoltenvk;
|
||||
}
|
||||
|
||||
/// Determines if a format is supported for a set of feature flags.
|
||||
[[nodiscard]] bool IsFormatSupported(vk::Format format, vk::FormatFeatureFlags2 flags) const;
|
||||
|
||||
private:
|
||||
/// Creates the logical device opportunistically enabling extensions
|
||||
bool CreateDevice();
|
||||
@ -338,9 +348,6 @@ private:
|
||||
/// Gets the supported feature flags for a format.
|
||||
[[nodiscard]] vk::FormatFeatureFlags2 GetFormatFeatureFlags(vk::Format format) const;
|
||||
|
||||
/// Determines if a format is supported for a set of feature flags.
|
||||
[[nodiscard]] bool IsFormatSupported(vk::Format format, vk::FormatFeatureFlags2 flags) const;
|
||||
|
||||
private:
|
||||
vk::UniqueInstance instance;
|
||||
vk::PhysicalDevice physical_device;
|
||||
|
@ -216,6 +216,8 @@ PipelineCache::PipelineCache(const Instance& instance_, Scheduler& scheduler_,
|
||||
.supports_trinary_minmax = instance_.IsAmdShaderTrinaryMinMaxSupported(),
|
||||
// TODO: Emitted bounds checks cause problems with phi control flow; needs to be fixed.
|
||||
.supports_robust_buffer_access = true, // instance_.IsRobustBufferAccess2Supported(),
|
||||
.supports_buffer_fp32_atomic_min_max =
|
||||
instance_.IsShaderAtomicFloatBuffer32MinMaxSupported(),
|
||||
.supports_image_fp32_atomic_min_max = instance_.IsShaderAtomicFloatImage32MinMaxSupported(),
|
||||
.supports_workgroup_explicit_memory_layout =
|
||||
instance_.IsWorkgroupMemoryExplicitLayoutSupported(),
|
||||
@ -346,8 +348,15 @@ bool PipelineCache::RefreshGraphicsKey() {
|
||||
col_buf.GetDataFmt() == AmdGpu::DataFormat::Format8_8 ||
|
||||
col_buf.GetDataFmt() == AmdGpu::DataFormat::Format8_8_8_8);
|
||||
|
||||
key.color_formats[remapped_cb] =
|
||||
const auto format =
|
||||
LiverpoolToVK::SurfaceFormat(col_buf.GetDataFmt(), col_buf.GetNumberFmt());
|
||||
key.color_formats[remapped_cb] = format;
|
||||
if (!instance.IsFormatSupported(format, vk::FormatFeatureFlagBits2::eColorAttachment)) {
|
||||
LOG_WARNING(Render_Vulkan,
|
||||
"color buffer format {} does not support COLOR_ATTACHMENT_BIT",
|
||||
vk::to_string(format));
|
||||
}
|
||||
|
||||
key.color_buffers[remapped_cb] = Shader::PsColorBuffer{
|
||||
.num_format = col_buf.GetNumberFmt(),
|
||||
.num_conversion = col_buf.GetNumberConversion(),
|
||||
|
@ -130,11 +130,24 @@ Image::Image(const Vulkan::Instance& instance_, Vulkan::Scheduler& scheduler_,
|
||||
|
||||
constexpr auto tiling = vk::ImageTiling::eOptimal;
|
||||
const auto supported_format = instance->GetSupportedFormat(info.pixel_format, format_features);
|
||||
const auto properties = instance->GetPhysicalDevice().getImageFormatProperties(
|
||||
supported_format, info.type, tiling, usage_flags, flags);
|
||||
const auto supported_samples = properties.result == vk::Result::eSuccess
|
||||
? properties.value.sampleCounts
|
||||
: vk::SampleCountFlagBits::e1;
|
||||
const vk::PhysicalDeviceImageFormatInfo2 format_info{
|
||||
.format = supported_format,
|
||||
.type = info.type,
|
||||
.tiling = tiling,
|
||||
.usage = usage_flags,
|
||||
.flags = flags,
|
||||
};
|
||||
const auto image_format_properties =
|
||||
instance->GetPhysicalDevice().getImageFormatProperties2(format_info);
|
||||
if (image_format_properties.result == vk::Result::eErrorFormatNotSupported) {
|
||||
LOG_ERROR(Render_Vulkan, "image format {} type {} is not supported (flags {}, usage {})",
|
||||
vk::to_string(supported_format), vk::to_string(info.type),
|
||||
vk::to_string(format_info.flags), vk::to_string(format_info.usage));
|
||||
}
|
||||
const auto supported_samples =
|
||||
image_format_properties.result == vk::Result::eSuccess
|
||||
? image_format_properties.value.imageFormatProperties.sampleCounts
|
||||
: vk::SampleCountFlagBits::e1;
|
||||
|
||||
const vk::ImageCreateInfo image_ci = {
|
||||
.flags = flags,
|
||||
|
@ -29,6 +29,24 @@ vk::ImageViewType ConvertImageViewType(AmdGpu::ImageType type) {
|
||||
}
|
||||
}
|
||||
|
||||
bool IsViewTypeCompatible(vk::ImageViewType view_type, vk::ImageType image_type) {
|
||||
switch (view_type) {
|
||||
case vk::ImageViewType::e1D:
|
||||
case vk::ImageViewType::e1DArray:
|
||||
return image_type == vk::ImageType::e1D;
|
||||
case vk::ImageViewType::e2D:
|
||||
case vk::ImageViewType::e2DArray:
|
||||
return image_type == vk::ImageType::e2D || image_type == vk::ImageType::e3D;
|
||||
case vk::ImageViewType::eCube:
|
||||
case vk::ImageViewType::eCubeArray:
|
||||
return image_type == vk::ImageType::e2D;
|
||||
case vk::ImageViewType::e3D:
|
||||
return image_type == vk::ImageType::e3D;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
ImageViewInfo::ImageViewInfo(const AmdGpu::Image& image, const Shader::ImageResource& desc) noexcept
|
||||
: is_storage{desc.is_written} {
|
||||
const auto dfmt = image.GetDataFmt();
|
||||
@ -106,6 +124,11 @@ ImageView::ImageView(const Vulkan::Instance& instance, const ImageViewInfo& info
|
||||
.layerCount = info.range.extent.layers,
|
||||
},
|
||||
};
|
||||
if (!IsViewTypeCompatible(image_view_ci.viewType, image.info.type)) {
|
||||
LOG_ERROR(Render_Vulkan, "image view type {} is incompatible with image type {}",
|
||||
vk::to_string(image_view_ci.viewType), vk::to_string(image.info.type));
|
||||
}
|
||||
|
||||
auto [view_result, view] = instance.GetDevice().createImageViewUnique(image_view_ci);
|
||||
ASSERT_MSG(view_result == vk::Result::eSuccess, "Failed to create image view: {}",
|
||||
vk::to_string(view_result));
|
||||
|
Loading…
Reference in New Issue
Block a user