mirror of
https://github.com/Lime3DS/Lime3DS.git
synced 2024-11-27 20:14:17 +01:00
video_core: Add vulkan shaders (#6619)
This commit is contained in:
parent
7c11b9b689
commit
3faddd5e03
@ -4,13 +4,12 @@
|
|||||||
|
|
||||||
set(SHADER_FILES
|
set(SHADER_FILES
|
||||||
format_reinterpreter/d24s8_to_rgba8.frag
|
format_reinterpreter/d24s8_to_rgba8.frag
|
||||||
format_reinterpreter/fullscreen_quad.vert
|
|
||||||
format_reinterpreter/rgba4_to_rgb5a1.frag
|
format_reinterpreter/rgba4_to_rgb5a1.frag
|
||||||
|
format_reinterpreter/vulkan_d24s8_to_rgba8.comp
|
||||||
texture_filtering/bicubic.frag
|
texture_filtering/bicubic.frag
|
||||||
texture_filtering/nearest_neighbor.frag
|
texture_filtering/nearest_neighbor.frag
|
||||||
texture_filtering/refine.frag
|
texture_filtering/refine.frag
|
||||||
texture_filtering/scale_force.frag
|
texture_filtering/scale_force.frag
|
||||||
texture_filtering/tex_coord.vert
|
|
||||||
texture_filtering/xbrz_freescale.frag
|
texture_filtering/xbrz_freescale.frag
|
||||||
texture_filtering/mmpx.frag
|
texture_filtering/mmpx.frag
|
||||||
texture_filtering/x_gradient.frag
|
texture_filtering/x_gradient.frag
|
||||||
@ -20,8 +19,22 @@ set(SHADER_FILES
|
|||||||
opengl_present.vert
|
opengl_present.vert
|
||||||
opengl_present_anaglyph.frag
|
opengl_present_anaglyph.frag
|
||||||
opengl_present_interlaced.frag
|
opengl_present_interlaced.frag
|
||||||
|
vulkan_depth_to_buffer.comp
|
||||||
|
vulkan_present.frag
|
||||||
|
vulkan_present.vert
|
||||||
|
vulkan_present_anaglyph.frag
|
||||||
|
vulkan_present_interlaced.frag
|
||||||
|
vulkan_blit_depth_stencil.frag
|
||||||
)
|
)
|
||||||
|
|
||||||
|
find_program(GLSLANGVALIDATOR "glslangValidator")
|
||||||
|
if ("${GLSLANGVALIDATOR}" STREQUAL "GLSLANGVALIDATOR-NOTFOUND")
|
||||||
|
message(FATAL_ERROR "Required program `glslangValidator` not found.")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(MACROS "-Dgl_VertexID=gl_VertexIndex")
|
||||||
|
set(QUIET_FLAG "--quiet")
|
||||||
|
|
||||||
set(SHADER_INCLUDE ${CMAKE_CURRENT_BINARY_DIR}/include)
|
set(SHADER_INCLUDE ${CMAKE_CURRENT_BINARY_DIR}/include)
|
||||||
set(SHADER_DIR ${SHADER_INCLUDE}/video_core/host_shaders)
|
set(SHADER_DIR ${SHADER_INCLUDE}/video_core/host_shaders)
|
||||||
set(HOST_SHADERS_INCLUDE ${SHADER_INCLUDE} PARENT_SCOPE)
|
set(HOST_SHADERS_INCLUDE ${SHADER_INCLUDE} PARENT_SCOPE)
|
||||||
@ -29,6 +42,23 @@ set(HOST_SHADERS_INCLUDE ${SHADER_INCLUDE} PARENT_SCOPE)
|
|||||||
set(INPUT_FILE ${CMAKE_CURRENT_SOURCE_DIR}/source_shader.h.in)
|
set(INPUT_FILE ${CMAKE_CURRENT_SOURCE_DIR}/source_shader.h.in)
|
||||||
set(HEADER_GENERATOR ${CMAKE_CURRENT_SOURCE_DIR}/StringShaderHeader.cmake)
|
set(HEADER_GENERATOR ${CMAKE_CURRENT_SOURCE_DIR}/StringShaderHeader.cmake)
|
||||||
|
|
||||||
|
# Check if `--quiet` is available on host's glslangValidator version
|
||||||
|
# glslangValidator prints to STDERR iff an unrecognized flag is passed to it
|
||||||
|
execute_process(
|
||||||
|
COMMAND
|
||||||
|
${GLSLANGVALIDATOR} ${QUIET_FLAG}
|
||||||
|
ERROR_VARIABLE
|
||||||
|
GLSLANG_ERROR
|
||||||
|
# STDOUT variable defined to silence unnecessary output during CMake configuration
|
||||||
|
OUTPUT_VARIABLE
|
||||||
|
GLSLANG_OUTPUT
|
||||||
|
)
|
||||||
|
|
||||||
|
if (NOT GLSLANG_ERROR STREQUAL "")
|
||||||
|
message(WARNING "Refusing to use unavailable flag `${QUIET_FLAG}` on `${GLSLANGVALIDATOR}`")
|
||||||
|
set(QUIET_FLAG "")
|
||||||
|
endif()
|
||||||
|
|
||||||
foreach(FILENAME IN ITEMS ${SHADER_FILES})
|
foreach(FILENAME IN ITEMS ${SHADER_FILES})
|
||||||
string(REPLACE "." "_" SHADER_NAME ${FILENAME})
|
string(REPLACE "." "_" SHADER_NAME ${FILENAME})
|
||||||
set(SOURCE_FILE ${CMAKE_CURRENT_SOURCE_DIR}/${FILENAME})
|
set(SOURCE_FILE ${CMAKE_CURRENT_SOURCE_DIR}/${FILENAME})
|
||||||
@ -48,6 +78,21 @@ foreach(FILENAME IN ITEMS ${SHADER_FILES})
|
|||||||
)
|
)
|
||||||
set(SHADER_HEADERS ${SHADER_HEADERS} ${SOURCE_HEADER_FILE})
|
set(SHADER_HEADERS ${SHADER_HEADERS} ${SOURCE_HEADER_FILE})
|
||||||
endif()
|
endif()
|
||||||
|
# Skip compiling to SPIR-V OpenGL exclusive files
|
||||||
|
if (NOT ${FILENAME} MATCHES "opengl.*")
|
||||||
|
get_filename_component(FILE_NAME ${SHADER_NAME} NAME)
|
||||||
|
string(TOUPPER ${FILE_NAME}_SPV SPIRV_VARIABLE_NAME)
|
||||||
|
set(SPIRV_HEADER_FILE ${SHADER_DIR}/${SHADER_NAME}_spv.h)
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT
|
||||||
|
${SPIRV_HEADER_FILE}
|
||||||
|
COMMAND
|
||||||
|
${GLSLANGVALIDATOR} --target-env vulkan1.1 --glsl-version 450 ${QUIET_FLAG} ${MACROS} --variable-name ${SPIRV_VARIABLE_NAME} -o ${SPIRV_HEADER_FILE} ${SOURCE_FILE}
|
||||||
|
MAIN_DEPENDENCY
|
||||||
|
${SOURCE_FILE}
|
||||||
|
)
|
||||||
|
set(SHADER_HEADERS ${SHADER_HEADERS} ${SPIRV_HEADER_FILE})
|
||||||
|
endif()
|
||||||
endforeach()
|
endforeach()
|
||||||
|
|
||||||
set(SHADER_SOURCES ${SHADER_FILES})
|
set(SHADER_SOURCES ${SHADER_FILES})
|
||||||
|
@ -0,0 +1,26 @@
|
|||||||
|
// Copyright 2023 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#version 450 core
|
||||||
|
#extension GL_EXT_samplerless_texture_functions : require
|
||||||
|
|
||||||
|
layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
|
||||||
|
layout(set = 0, binding = 0) uniform highp texture2D depth;
|
||||||
|
layout(set = 0, binding = 1) uniform lowp utexture2D stencil;
|
||||||
|
layout(set = 0, binding = 2, rgba8) uniform highp writeonly image2D color;
|
||||||
|
|
||||||
|
layout(push_constant, std140) uniform ComputeInfo {
|
||||||
|
mediump ivec2 src_offset;
|
||||||
|
mediump ivec2 extent;
|
||||||
|
};
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
ivec2 tex_coord = src_offset + ivec2(gl_GlobalInvocationID.xy);
|
||||||
|
highp uint depth_val =
|
||||||
|
uint(texelFetch(depth, tex_coord, 0).x * (exp2(32.0) - 1.0));
|
||||||
|
lowp uint stencil_val = texelFetch(stencil, tex_coord, 0).x;
|
||||||
|
highp uvec4 components =
|
||||||
|
uvec4(stencil_val, (uvec3(depth_val) >> uvec3(24u, 16u, 8u)) & 0x000000FFu);
|
||||||
|
imageStore(color, tex_coord, vec4(components) / (exp2(8.0) - 1.0));
|
||||||
|
}
|
@ -9,8 +9,20 @@ out gl_PerVertex {
|
|||||||
|
|
||||||
layout(location = 0) out vec2 texcoord;
|
layout(location = 0) out vec2 texcoord;
|
||||||
|
|
||||||
layout (location = 0) uniform vec2 tex_scale;
|
#ifdef VULKAN
|
||||||
layout (location = 1) uniform vec2 tex_offset;
|
#define BEGIN_PUSH_CONSTANTS layout(push_constant) uniform PushConstants {
|
||||||
|
#define END_PUSH_CONSTANTS };
|
||||||
|
#define UNIFORM(n)
|
||||||
|
#else // if OpenGL
|
||||||
|
#define BEGIN_PUSH_CONSTANTS
|
||||||
|
#define END_PUSH_CONSTANTS
|
||||||
|
#define UNIFORM(n) layout (location = n) uniform
|
||||||
|
#endif
|
||||||
|
|
||||||
|
BEGIN_PUSH_CONSTANTS
|
||||||
|
UNIFORM(0) vec2 tex_scale;
|
||||||
|
UNIFORM(1) vec2 tex_offset;
|
||||||
|
END_PUSH_CONSTANTS
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
float x = float((gl_VertexID & 1) << 2);
|
float x = float((gl_VertexID & 1) << 2);
|
||||||
|
@ -9,7 +9,14 @@ layout(location = 0) in vec2 tex_coord;
|
|||||||
layout(location = 0) out vec4 frag_color;
|
layout(location = 0) out vec4 frag_color;
|
||||||
|
|
||||||
layout(binding = 0) uniform sampler2D tex;
|
layout(binding = 0) uniform sampler2D tex;
|
||||||
layout(location = 2) uniform lowp float scale;
|
|
||||||
|
#ifdef VULKAN
|
||||||
|
layout(push_constant, std140) uniform XbrzInfo {
|
||||||
|
float scale;
|
||||||
|
};
|
||||||
|
#else
|
||||||
|
layout(location = 2) uniform float scale;
|
||||||
|
#endif
|
||||||
|
|
||||||
const int BLEND_NONE = 0;
|
const int BLEND_NONE = 0;
|
||||||
const int BLEND_NORMAL = 1;
|
const int BLEND_NORMAL = 1;
|
||||||
|
16
src/video_core/host_shaders/vulkan_blit_depth_stencil.frag
Normal file
16
src/video_core/host_shaders/vulkan_blit_depth_stencil.frag
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
// Copyright 2022 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#version 450 core
|
||||||
|
#extension GL_ARB_shader_stencil_export : require
|
||||||
|
|
||||||
|
layout(binding = 0) uniform sampler2D depth_tex;
|
||||||
|
layout(binding = 1) uniform usampler2D stencil_tex;
|
||||||
|
|
||||||
|
layout(location = 0) in vec2 texcoord;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
gl_FragDepth = textureLod(depth_tex, texcoord, 0).r;
|
||||||
|
gl_FragStencilRefARB = int(textureLod(stencil_tex, texcoord, 0).r);
|
||||||
|
}
|
29
src/video_core/host_shaders/vulkan_depth_to_buffer.comp
Normal file
29
src/video_core/host_shaders/vulkan_depth_to_buffer.comp
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
// Copyright 2023 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#version 450 core
|
||||||
|
|
||||||
|
layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
|
||||||
|
layout(binding = 0) uniform highp sampler2D depth;
|
||||||
|
layout(binding = 1) uniform lowp usampler2D stencil;
|
||||||
|
|
||||||
|
layout(binding = 2) writeonly buffer OutputBuffer{
|
||||||
|
uint pixels[];
|
||||||
|
} staging;
|
||||||
|
|
||||||
|
layout(push_constant, std140) uniform ComputeInfo {
|
||||||
|
mediump ivec2 src_offset;
|
||||||
|
mediump ivec2 extent;
|
||||||
|
};
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
ivec2 rect = ivec2(gl_NumWorkGroups.xy) * ivec2(8);
|
||||||
|
ivec2 dst_coord = ivec2(gl_GlobalInvocationID.xy);
|
||||||
|
ivec2 tex_icoord = src_offset + dst_coord;
|
||||||
|
highp vec2 tex_coord = vec2(tex_icoord) / vec2(extent);
|
||||||
|
highp uint depth_val = uint(texture(depth, tex_coord).x * (exp2(24.0) - 1.0));
|
||||||
|
lowp uint stencil_val = texture(stencil, tex_coord).x;
|
||||||
|
highp uint value = stencil_val | (depth_val << 8);
|
||||||
|
staging.pixels[dst_coord.y * rect.x + dst_coord.x] = value;
|
||||||
|
}
|
25
src/video_core/host_shaders/vulkan_present.frag
Normal file
25
src/video_core/host_shaders/vulkan_present.frag
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
// Copyright 2022 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#version 450 core
|
||||||
|
#extension GL_ARB_separate_shader_objects : enable
|
||||||
|
|
||||||
|
layout (location = 0) in vec2 frag_tex_coord;
|
||||||
|
layout (location = 0) out vec4 color;
|
||||||
|
|
||||||
|
layout (push_constant, std140) uniform DrawInfo {
|
||||||
|
mat4 modelview_matrix;
|
||||||
|
vec4 i_resolution;
|
||||||
|
vec4 o_resolution;
|
||||||
|
int screen_id_l;
|
||||||
|
int screen_id_r;
|
||||||
|
int layer;
|
||||||
|
int reverse_interlaced;
|
||||||
|
};
|
||||||
|
|
||||||
|
layout (set = 0, binding = 0) uniform sampler2D screen_textures[3];
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
color = texture(screen_textures[screen_id_l], frag_tex_coord);
|
||||||
|
}
|
25
src/video_core/host_shaders/vulkan_present.vert
Normal file
25
src/video_core/host_shaders/vulkan_present.vert
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
// Copyright 2022 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#version 450 core
|
||||||
|
#extension GL_ARB_separate_shader_objects : enable
|
||||||
|
|
||||||
|
layout (location = 0) in vec2 vert_position;
|
||||||
|
layout (location = 1) in vec2 vert_tex_coord;
|
||||||
|
layout (location = 0) out vec2 frag_tex_coord;
|
||||||
|
|
||||||
|
layout (push_constant, std140) uniform DrawInfo {
|
||||||
|
mat4 modelview_matrix;
|
||||||
|
vec4 i_resolution;
|
||||||
|
vec4 o_resolution;
|
||||||
|
int screen_id_l;
|
||||||
|
int screen_id_r;
|
||||||
|
int layer;
|
||||||
|
};
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec4 position = vec4(vert_position, 0.0, 1.0) * modelview_matrix;
|
||||||
|
gl_Position = vec4(position.x, position.y, 0.0, 1.0);
|
||||||
|
frag_tex_coord = vert_tex_coord;
|
||||||
|
}
|
39
src/video_core/host_shaders/vulkan_present_anaglyph.frag
Normal file
39
src/video_core/host_shaders/vulkan_present_anaglyph.frag
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
// Copyright 2022 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#version 450 core
|
||||||
|
#extension GL_ARB_separate_shader_objects : enable
|
||||||
|
|
||||||
|
layout (location = 0) in vec2 frag_tex_coord;
|
||||||
|
layout (location = 0) out vec4 color;
|
||||||
|
|
||||||
|
// Anaglyph Red-Cyan shader based on Dubois algorithm
|
||||||
|
// Constants taken from the paper:
|
||||||
|
// "Conversion of a Stereo Pair to Anaglyph with
|
||||||
|
// the Least-Squares Projection Method"
|
||||||
|
// Eric Dubois, March 2009
|
||||||
|
const mat3 l = mat3( 0.437, 0.449, 0.164,
|
||||||
|
-0.062,-0.062,-0.024,
|
||||||
|
-0.048,-0.050,-0.017);
|
||||||
|
const mat3 r = mat3(-0.011,-0.032,-0.007,
|
||||||
|
0.377, 0.761, 0.009,
|
||||||
|
-0.026,-0.093, 1.234);
|
||||||
|
|
||||||
|
layout (push_constant, std140) uniform DrawInfo {
|
||||||
|
mat4 modelview_matrix;
|
||||||
|
vec4 i_resolution;
|
||||||
|
vec4 o_resolution;
|
||||||
|
int screen_id_l;
|
||||||
|
int screen_id_r;
|
||||||
|
int layer;
|
||||||
|
int reverse_interlaced;
|
||||||
|
};
|
||||||
|
|
||||||
|
layout (set = 0, binding = 0) uniform sampler2D screen_textures[3];
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec4 color_tex_l = texture(screen_textures[screen_id_l], frag_tex_coord);
|
||||||
|
vec4 color_tex_r = texture(screen_textures[screen_id_r], frag_tex_coord);
|
||||||
|
color = vec4(color_tex_l.rgb*l+color_tex_r.rgb*r, color_tex_l.a);
|
||||||
|
}
|
29
src/video_core/host_shaders/vulkan_present_interlaced.frag
Normal file
29
src/video_core/host_shaders/vulkan_present_interlaced.frag
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
// Copyright 2022 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#version 450 core
|
||||||
|
#extension GL_ARB_separate_shader_objects : enable
|
||||||
|
|
||||||
|
layout (location = 0) in vec2 frag_tex_coord;
|
||||||
|
layout (location = 0) out vec4 color;
|
||||||
|
|
||||||
|
layout (push_constant, std140) uniform DrawInfo {
|
||||||
|
mat4 modelview_matrix;
|
||||||
|
vec4 i_resolution;
|
||||||
|
vec4 o_resolution;
|
||||||
|
int screen_id_l;
|
||||||
|
int screen_id_r;
|
||||||
|
int layer;
|
||||||
|
int reverse_interlaced;
|
||||||
|
};
|
||||||
|
|
||||||
|
layout (set = 0, binding = 0) uniform sampler2D screen_textures[3];
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
float screen_row = o_resolution.x * frag_tex_coord.x;
|
||||||
|
if (int(screen_row) % 2 == reverse_interlaced)
|
||||||
|
color = texture(screen_textures[screen_id_l], frag_tex_coord);
|
||||||
|
else
|
||||||
|
color = texture(screen_textures[screen_id_r], frag_tex_coord);
|
||||||
|
}
|
@ -1000,7 +1000,7 @@ void RasterizerCache<T>::UploadSurface(Surface& surface, SurfaceInterval interva
|
|||||||
}
|
}
|
||||||
|
|
||||||
const BufferTextureCopy upload = {
|
const BufferTextureCopy upload = {
|
||||||
.buffer_offset = 0,
|
.buffer_offset = staging.offset,
|
||||||
.buffer_size = staging.size,
|
.buffer_size = staging.size,
|
||||||
.texture_rect = surface.GetSubRect(load_info),
|
.texture_rect = surface.GetSubRect(load_info),
|
||||||
.texture_level = surface.LevelOf(load_info.addr),
|
.texture_level = surface.LevelOf(load_info.addr),
|
||||||
@ -1080,7 +1080,7 @@ void RasterizerCache<T>::DownloadSurface(Surface& surface, SurfaceInterval inter
|
|||||||
flush_info.width * flush_info.height * surface.GetInternalBytesPerPixel(), false);
|
flush_info.width * flush_info.height * surface.GetInternalBytesPerPixel(), false);
|
||||||
|
|
||||||
const BufferTextureCopy download = {
|
const BufferTextureCopy download = {
|
||||||
.buffer_offset = 0,
|
.buffer_offset = staging.offset,
|
||||||
.buffer_size = staging.size,
|
.buffer_size = staging.size,
|
||||||
.texture_rect = surface.GetSubRect(flush_info),
|
.texture_rect = surface.GetSubRect(flush_info),
|
||||||
.texture_level = surface.LevelOf(flush_start),
|
.texture_level = surface.LevelOf(flush_start),
|
||||||
@ -1137,7 +1137,7 @@ bool RasterizerCache<T>::ValidateByReinterpretation(Surface& surface, SurfacePar
|
|||||||
if (reinterpret_id) {
|
if (reinterpret_id) {
|
||||||
Surface& src_surface = slot_surfaces[reinterpret_id];
|
Surface& src_surface = slot_surfaces[reinterpret_id];
|
||||||
const SurfaceInterval copy_interval = src_surface.GetCopyableInterval(params);
|
const SurfaceInterval copy_interval = src_surface.GetCopyableInterval(params);
|
||||||
if (boost::icl::is_empty(copy_interval)) {
|
if (boost::icl::is_empty(copy_interval & interval)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const PAddr addr = boost::icl::lower(interval);
|
const PAddr addr = boost::icl::lower(interval);
|
||||||
|
@ -75,8 +75,8 @@ struct BufferTextureCopy {
|
|||||||
|
|
||||||
struct StagingData {
|
struct StagingData {
|
||||||
u32 size;
|
u32 size;
|
||||||
|
u32 offset;
|
||||||
std::span<u8> mapped;
|
std::span<u8> mapped;
|
||||||
u64 buffer_offset;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct TextureCubeConfig {
|
struct TextureCubeConfig {
|
||||||
|
@ -144,8 +144,8 @@ VideoCore::StagingData TextureRuntime::FindStaging(u32 size, bool upload) {
|
|||||||
}
|
}
|
||||||
return VideoCore::StagingData{
|
return VideoCore::StagingData{
|
||||||
.size = size,
|
.size = size,
|
||||||
|
.offset = 0,
|
||||||
.mapped = std::span{staging_buffer.data(), size},
|
.mapped = std::span{staging_buffer.data(), size},
|
||||||
.buffer_offset = 0,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user