gpu: Add support for blend modes
This commit is contained in:
@@ -2122,6 +2122,46 @@ gsk_gpu_node_processor_add_shadow_node (GskGpuNodeProcessor *self,
|
||||
g_object_unref (image);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gsk_gpu_node_processor_create_blend_pattern (GskGpuPatternWriter *self,
|
||||
GskRenderNode *node)
|
||||
{
|
||||
GskRenderNode *bottom_child, *top_child;
|
||||
|
||||
bottom_child = gsk_blend_node_get_bottom_child (node);
|
||||
top_child = gsk_blend_node_get_top_child (node);
|
||||
|
||||
if (!gsk_gpu_node_processor_create_node_pattern (self, bottom_child))
|
||||
return FALSE;
|
||||
if (!gsk_rect_contains_rect (&bottom_child->bounds, &node->bounds))
|
||||
{
|
||||
gsk_gpu_pattern_writer_append_uint (self, GSK_GPU_PATTERN_CLIP);
|
||||
gsk_gpu_pattern_writer_append_rect (self, &bottom_child->bounds, &self->offset);
|
||||
}
|
||||
|
||||
gsk_gpu_pattern_writer_append_uint (self, GSK_GPU_PATTERN_PUSH_COLOR);
|
||||
|
||||
if (!gsk_gpu_pattern_writer_push_stack (self))
|
||||
return FALSE;
|
||||
|
||||
if (!gsk_gpu_node_processor_create_node_pattern (self, top_child))
|
||||
{
|
||||
gsk_gpu_pattern_writer_pop_stack (self);
|
||||
return FALSE;
|
||||
}
|
||||
if (!gsk_rect_contains_rect (&top_child->bounds, &node->bounds))
|
||||
{
|
||||
gsk_gpu_pattern_writer_append_uint (self, GSK_GPU_PATTERN_CLIP);
|
||||
gsk_gpu_pattern_writer_append_rect (self, &top_child->bounds, &self->offset);
|
||||
}
|
||||
|
||||
gsk_gpu_pattern_writer_append_uint (self, GSK_GPU_PATTERN_BLEND_DEFAULT + gsk_blend_node_get_blend_mode (node));
|
||||
|
||||
gsk_gpu_pattern_writer_pop_stack (self);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gsk_gpu_node_processor_create_cross_fade_pattern (GskGpuPatternWriter *self,
|
||||
GskRenderNode *node)
|
||||
@@ -2712,9 +2752,9 @@ static const struct
|
||||
},
|
||||
[GSK_BLEND_NODE] = {
|
||||
0,
|
||||
0,
|
||||
NULL,
|
||||
NULL,
|
||||
GSK_GPU_HANDLE_OPACITY,
|
||||
gsk_gpu_node_processor_add_node_as_pattern,
|
||||
gsk_gpu_node_processor_create_blend_pattern,
|
||||
},
|
||||
[GSK_CROSS_FADE_NODE] = {
|
||||
0,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <gdk/gdk.h>
|
||||
#include "gsk/gskenums.h"
|
||||
|
||||
#include "gdk/gdkmemoryformatprivate.h"
|
||||
|
||||
@@ -67,8 +68,40 @@ typedef enum {
|
||||
GSK_GPU_PATTERN_POP_MASK_LUMINANCE,
|
||||
GSK_GPU_PATTERN_POP_MASK_INVERTED_LUMINANCE,
|
||||
GSK_GPU_PATTERN_AFFINE,
|
||||
GSK_GPU_PATTERN_BLEND_DEFAULT,
|
||||
GSK_GPU_PATTERN_BLEND_MULTIPLY,
|
||||
GSK_GPU_PATTERN_BLEND_SCREEN,
|
||||
GSK_GPU_PATTERN_BLEND_OVERLAY,
|
||||
GSK_GPU_PATTERN_BLEND_DARKEN,
|
||||
GSK_GPU_PATTERN_BLEND_LIGHTEN,
|
||||
GSK_GPU_PATTERN_BLEND_COLOR_DODGE,
|
||||
GSK_GPU_PATTERN_BLEND_COLOR_BURN,
|
||||
GSK_GPU_PATTERN_BLEND_HARD_LIGHT,
|
||||
GSK_GPU_PATTERN_BLEND_SOFT_LIGHT,
|
||||
GSK_GPU_PATTERN_BLEND_DIFFERENCE,
|
||||
GSK_GPU_PATTERN_BLEND_EXCLUSION,
|
||||
GSK_GPU_PATTERN_BLEND_COLOR,
|
||||
GSK_GPU_PATTERN_BLEND_HUE,
|
||||
GSK_GPU_PATTERN_BLEND_SATURATION,
|
||||
GSK_GPU_PATTERN_BLEND_LUMINOSITY,
|
||||
} GskGpuPatternType;
|
||||
|
||||
G_STATIC_ASSERT (GSK_GPU_PATTERN_BLEND_MULTIPLY == GSK_GPU_PATTERN_BLEND_DEFAULT + GSK_BLEND_MODE_MULTIPLY);
|
||||
G_STATIC_ASSERT (GSK_GPU_PATTERN_BLEND_SCREEN == GSK_GPU_PATTERN_BLEND_DEFAULT + GSK_BLEND_MODE_SCREEN);
|
||||
G_STATIC_ASSERT (GSK_GPU_PATTERN_BLEND_OVERLAY == GSK_GPU_PATTERN_BLEND_DEFAULT + GSK_BLEND_MODE_OVERLAY);
|
||||
G_STATIC_ASSERT (GSK_GPU_PATTERN_BLEND_DARKEN == GSK_GPU_PATTERN_BLEND_DEFAULT + GSK_BLEND_MODE_DARKEN);
|
||||
G_STATIC_ASSERT (GSK_GPU_PATTERN_BLEND_LIGHTEN == GSK_GPU_PATTERN_BLEND_DEFAULT + GSK_BLEND_MODE_LIGHTEN);
|
||||
G_STATIC_ASSERT (GSK_GPU_PATTERN_BLEND_COLOR_DODGE == GSK_GPU_PATTERN_BLEND_DEFAULT + GSK_BLEND_MODE_COLOR_DODGE);
|
||||
G_STATIC_ASSERT (GSK_GPU_PATTERN_BLEND_COLOR_BURN == GSK_GPU_PATTERN_BLEND_DEFAULT + GSK_BLEND_MODE_COLOR_BURN);
|
||||
G_STATIC_ASSERT (GSK_GPU_PATTERN_BLEND_HARD_LIGHT == GSK_GPU_PATTERN_BLEND_DEFAULT + GSK_BLEND_MODE_HARD_LIGHT);
|
||||
G_STATIC_ASSERT (GSK_GPU_PATTERN_BLEND_SOFT_LIGHT == GSK_GPU_PATTERN_BLEND_DEFAULT + GSK_BLEND_MODE_SOFT_LIGHT);
|
||||
G_STATIC_ASSERT (GSK_GPU_PATTERN_BLEND_DIFFERENCE == GSK_GPU_PATTERN_BLEND_DEFAULT + GSK_BLEND_MODE_DIFFERENCE);
|
||||
G_STATIC_ASSERT (GSK_GPU_PATTERN_BLEND_EXCLUSION == GSK_GPU_PATTERN_BLEND_DEFAULT + GSK_BLEND_MODE_EXCLUSION);
|
||||
G_STATIC_ASSERT (GSK_GPU_PATTERN_BLEND_COLOR == GSK_GPU_PATTERN_BLEND_DEFAULT + GSK_BLEND_MODE_COLOR);
|
||||
G_STATIC_ASSERT (GSK_GPU_PATTERN_BLEND_HUE == GSK_GPU_PATTERN_BLEND_DEFAULT + GSK_BLEND_MODE_HUE);
|
||||
G_STATIC_ASSERT (GSK_GPU_PATTERN_BLEND_SATURATION == GSK_GPU_PATTERN_BLEND_DEFAULT + GSK_BLEND_MODE_SATURATION);
|
||||
G_STATIC_ASSERT (GSK_GPU_PATTERN_BLEND_LUMINOSITY == GSK_GPU_PATTERN_BLEND_DEFAULT + GSK_BLEND_MODE_LUMINOSITY);
|
||||
|
||||
typedef enum {
|
||||
GSK_GPU_OPTIMIZE_UBER = 1 << 0,
|
||||
GSK_GPU_OPTIMIZE_CLEAR = 1 << 1,
|
||||
|
||||
300
gsk/gpu/shaders/blendmode.glsl
Normal file
300
gsk/gpu/shaders/blendmode.glsl
Normal file
@@ -0,0 +1,300 @@
|
||||
#ifndef _BLENDMODE_
|
||||
#define _BLENDMODE_
|
||||
|
||||
vec4
|
||||
composite (vec4 Cs, vec4 Cb, vec3 B)
|
||||
{
|
||||
float ao = Cs.a + Cb.a * (1.0 - Cs.a);
|
||||
vec3 Co = (Cs.a*(1.0 - Cb.a)*Cs.rgb + Cs.a*Cb.a*B + (1.0 - Cs.a)*Cb.a*Cb.rgb) / ao;
|
||||
return vec4(Co, ao);
|
||||
}
|
||||
|
||||
vec4
|
||||
normal (vec4 Cs, vec4 Cb)
|
||||
{
|
||||
return composite (Cs, Cb, Cs.rgb);
|
||||
}
|
||||
|
||||
vec4
|
||||
multiply (vec4 Cs, vec4 Cb)
|
||||
{
|
||||
return composite (Cs, Cb, Cs.rgb * Cb.rgb);
|
||||
}
|
||||
|
||||
vec4
|
||||
difference (vec4 Cs, vec4 Cb)
|
||||
{
|
||||
return composite (Cs, Cb, abs(Cs.rgb - Cb.rgb));
|
||||
}
|
||||
|
||||
vec4
|
||||
screen (vec4 Cs, vec4 Cb)
|
||||
{
|
||||
return composite (Cs, Cb, Cs.rgb + Cb.rgb - Cs.rgb * Cb.rgb);
|
||||
}
|
||||
|
||||
float
|
||||
hard_light (float source, float backdrop)
|
||||
{
|
||||
if (source <= 0.5)
|
||||
return 2.0 * backdrop * source;
|
||||
else
|
||||
return 2.0 * (backdrop + source - backdrop * source) - 1.0;
|
||||
}
|
||||
|
||||
vec4
|
||||
hard_light (vec4 Cs, vec4 Cb)
|
||||
{
|
||||
vec3 B = vec3 (hard_light (Cs.r, Cb.r),
|
||||
hard_light (Cs.g, Cb.g),
|
||||
hard_light (Cs.b, Cb.b));
|
||||
return composite (Cs, Cb, B);
|
||||
}
|
||||
|
||||
float
|
||||
soft_light (float source, float backdrop)
|
||||
{
|
||||
float db;
|
||||
|
||||
if (backdrop <= 0.25)
|
||||
db = ((16.0 * backdrop - 12.0) * backdrop + 4.0) * backdrop;
|
||||
else
|
||||
db = sqrt (backdrop);
|
||||
|
||||
if (source <= 0.5)
|
||||
return backdrop - (1.0 - 2.0 * source) * backdrop * (1.0 - backdrop);
|
||||
else
|
||||
return backdrop + (2.0 * source - 1.0) * (db - backdrop);
|
||||
}
|
||||
|
||||
vec4
|
||||
soft_light (vec4 Cs, vec4 Cb)
|
||||
{
|
||||
vec3 B = vec3 (soft_light (Cs.r, Cb.r),
|
||||
soft_light (Cs.g, Cb.g),
|
||||
soft_light (Cs.b, Cb.b));
|
||||
return composite (Cs, Cb, B);
|
||||
}
|
||||
|
||||
vec4
|
||||
overlay (vec4 Cs, vec4 Cb)
|
||||
{
|
||||
vec3 B = vec3 (hard_light (Cb.r, Cs.r),
|
||||
hard_light (Cb.g, Cs.g),
|
||||
hard_light (Cb.b, Cs.b));
|
||||
return composite (Cs, Cb, B);
|
||||
}
|
||||
|
||||
vec4
|
||||
darken (vec4 Cs, vec4 Cb)
|
||||
{
|
||||
vec3 B = min (Cs.rgb, Cb.rgb);
|
||||
return composite (Cs, Cb, B);
|
||||
}
|
||||
|
||||
vec4
|
||||
lighten (vec4 Cs, vec4 Cb)
|
||||
{
|
||||
vec3 B = max (Cs.rgb, Cb.rgb);
|
||||
return composite (Cs, Cb, B);
|
||||
}
|
||||
|
||||
float
|
||||
color_dodge (float source, float backdrop)
|
||||
{
|
||||
return (source == 1.0) ? source : min (backdrop / (1.0 - source), 1.0);
|
||||
}
|
||||
|
||||
vec4
|
||||
color_dodge (vec4 Cs, vec4 Cb)
|
||||
{
|
||||
vec3 B = vec3 (color_dodge (Cs.r, Cb.r),
|
||||
color_dodge (Cs.g, Cb.g),
|
||||
color_dodge (Cs.b, Cb.b));
|
||||
return composite (Cs, Cb, B);
|
||||
}
|
||||
|
||||
|
||||
float
|
||||
color_burn (float source, float backdrop)
|
||||
{
|
||||
return (source == 0.0) ? source : max ((1.0 - ((1.0 - backdrop) / source)), 0.0);
|
||||
}
|
||||
|
||||
vec4
|
||||
color_burn (vec4 Cs, vec4 Cb)
|
||||
{
|
||||
vec3 B = vec3 (color_burn (Cs.r, Cb.r),
|
||||
color_burn (Cs.g, Cb.g),
|
||||
color_burn (Cs.b, Cb.b));
|
||||
return composite (Cs, Cb, B);
|
||||
}
|
||||
|
||||
vec4
|
||||
exclusion (vec4 Cs, vec4 Cb)
|
||||
{
|
||||
vec3 B = Cb.rgb + Cs.rgb - 2.0 * Cb.rgb * Cs.rgb;
|
||||
return composite (Cs, Cb, B);
|
||||
}
|
||||
|
||||
float
|
||||
lum (vec3 c)
|
||||
{
|
||||
return 0.3 * c.r + 0.59 * c.g + 0.11 * c.b;
|
||||
}
|
||||
|
||||
vec3
|
||||
clip_color (vec3 c)
|
||||
{
|
||||
float l = lum (c);
|
||||
float n = min (c.r, min (c.g, c.b));
|
||||
float x = max (c.r, max (c.g, c.b));
|
||||
if (n < 0.0) c = l + (((c - l) * l) / (l - n));
|
||||
if (x > 1.0) c = l + (((c - l) * (1.0 - l)) / (x - l));
|
||||
return c;
|
||||
}
|
||||
|
||||
vec3
|
||||
set_lum (vec3 c, float l)
|
||||
{
|
||||
float d = l - lum (c);
|
||||
return clip_color (vec3 (c.r + d, c.g + d, c.b + d));
|
||||
}
|
||||
|
||||
float
|
||||
sat (vec3 c)
|
||||
{
|
||||
return max (c.r, max (c.g, c.b)) - min (c.r, min (c.g, c.b));
|
||||
}
|
||||
|
||||
vec3
|
||||
set_sat (vec3 c, float s)
|
||||
{
|
||||
float cmin = min (c.r, min (c.g, c.b));
|
||||
float cmax = max (c.r, max (c.g, c.b));
|
||||
vec3 res;
|
||||
|
||||
if (cmax == cmin)
|
||||
res = vec3 (0, 0, 0);
|
||||
else
|
||||
{
|
||||
if (c.r == cmax)
|
||||
{
|
||||
if (c.g == cmin)
|
||||
{
|
||||
res.b = ((c.b - cmin) * s) / (cmax - cmin);
|
||||
res.g = 0.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
res.g = ((c.g - cmin) * s) / (cmax - cmin);
|
||||
res.b = 0.0;
|
||||
}
|
||||
res.r = s;
|
||||
}
|
||||
else if (c.g == cmax)
|
||||
{
|
||||
if (c.r == cmin)
|
||||
{
|
||||
res.b = ((c.b - cmin) * s) / (cmax - cmin);
|
||||
res.r = 0.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
res.r = ((c.r - cmin) * s) / (cmax - cmin);
|
||||
res.b = 0.0;
|
||||
}
|
||||
res.g = s;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (c.r == cmin)
|
||||
{
|
||||
res.g = ((c.g - cmin) * s) / (cmax - cmin);
|
||||
res.r = 0.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
res.r = ((c.r - cmin) * s) / (cmax - cmin);
|
||||
res.g = 0.0;
|
||||
}
|
||||
res.b = s;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
vec4
|
||||
color (vec4 Cs, vec4 Cb)
|
||||
{
|
||||
vec3 B = set_lum (Cs.rgb, lum (Cb.rgb));
|
||||
return composite (Cs, Cb, B);
|
||||
}
|
||||
|
||||
vec4
|
||||
hue (vec4 Cs, vec4 Cb)
|
||||
{
|
||||
vec3 B = set_lum (set_sat (Cs.rgb, sat (Cb.rgb)), lum (Cb.rgb));
|
||||
return composite (Cs, Cb, B);
|
||||
}
|
||||
|
||||
vec4
|
||||
saturation (vec4 Cs, vec4 Cb)
|
||||
{
|
||||
vec3 B = set_lum (set_sat (Cb.rgb, sat (Cs.rgb)), lum (Cb.rgb));
|
||||
return composite (Cs, Cb, B);
|
||||
}
|
||||
|
||||
vec4
|
||||
luminosity (vec4 Cs, vec4 Cb)
|
||||
{
|
||||
vec3 B = set_lum (Cb.rgb, lum (Cs.rgb));
|
||||
return composite (Cs, Cb, B);
|
||||
}
|
||||
|
||||
vec4
|
||||
blend_mode (vec4 bottom_color,
|
||||
vec4 top_color,
|
||||
uint u_mode)
|
||||
{
|
||||
vec4 result;
|
||||
|
||||
if (u_mode == 0u)
|
||||
result = normal(top_color, bottom_color);
|
||||
else if (u_mode == 1u)
|
||||
result = multiply(top_color, bottom_color);
|
||||
else if (u_mode == 2u)
|
||||
result = screen(top_color, bottom_color);
|
||||
else if (u_mode == 3u)
|
||||
result = overlay(top_color, bottom_color);
|
||||
else if (u_mode == 4u)
|
||||
result = darken(top_color, bottom_color);
|
||||
else if (u_mode == 5u)
|
||||
result = lighten(top_color, bottom_color);
|
||||
else if (u_mode == 6u)
|
||||
result = color_dodge(top_color, bottom_color);
|
||||
else if (u_mode == 7u)
|
||||
result = color_burn(top_color, bottom_color);
|
||||
else if (u_mode == 8u)
|
||||
result = hard_light(top_color, bottom_color);
|
||||
else if (u_mode == 9u)
|
||||
result = soft_light(top_color, bottom_color);
|
||||
else if (u_mode == 10u)
|
||||
result = difference(top_color, bottom_color);
|
||||
else if (u_mode == 11u)
|
||||
result = exclusion(top_color, bottom_color);
|
||||
else if (u_mode == 12u)
|
||||
result = color(top_color, bottom_color);
|
||||
else if (u_mode == 13u)
|
||||
result = hue(top_color, bottom_color);
|
||||
else if (u_mode == 14u)
|
||||
result = saturation(top_color, bottom_color);
|
||||
else if (u_mode == 15u)
|
||||
result = luminosity(top_color, bottom_color);
|
||||
else
|
||||
result = vec4 (10.0, 0.0, 0.8, 1.0);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -30,6 +30,22 @@
|
||||
#define GSK_GPU_PATTERN_POP_MASK_LUMINANCE 20u
|
||||
#define GSK_GPU_PATTERN_POP_MASK_INVERTED_LUMINANCE 21u
|
||||
#define GSK_GPU_PATTERN_AFFINE 22u
|
||||
#define GSK_GPU_PATTERN_BLEND_DEFAULT 23u
|
||||
#define GSK_GPU_PATTERN_BLEND_MULTIPLY 24u
|
||||
#define GSK_GPU_PATTERN_BLEND_SCREEN 25u
|
||||
#define GSK_GPU_PATTERN_BLEND_OVERLAY 26u
|
||||
#define GSK_GPU_PATTERN_BLEND_DARKEN 27u
|
||||
#define GSK_GPU_PATTERN_BLEND_LIGHTEN 28u
|
||||
#define GSK_GPU_PATTERN_BLEND_COLOR_DODGE 29u
|
||||
#define GSK_GPU_PATTERN_BLEND_COLOR_BURN 30u
|
||||
#define GSK_GPU_PATTERN_BLEND_HARD_LIGHT 31u
|
||||
#define GSK_GPU_PATTERN_BLEND_SOFT_LIGHT 32u
|
||||
#define GSK_GPU_PATTERN_BLEND_DIFFERENCE 33u
|
||||
#define GSK_GPU_PATTERN_BLEND_EXCLUSION 34u
|
||||
#define GSK_GPU_PATTERN_BLEND_COLOR 35u
|
||||
#define GSK_GPU_PATTERN_BLEND_HUE 36u
|
||||
#define GSK_GPU_PATTERN_BLEND_SATURATION 37u
|
||||
#define GSK_GPU_PATTERN_BLEND_LUMINOSITY 38u
|
||||
|
||||
#define TOP 0u
|
||||
#define RIGHT 1u
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
gsk_private_gpu_include_shaders = files([
|
||||
'blendmode.glsl',
|
||||
'color.glsl',
|
||||
'common.glsl',
|
||||
'common-gl.glsl',
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#define _PATTERN_
|
||||
|
||||
#include "common.glsl"
|
||||
#include "blendmode.glsl"
|
||||
#include "gradient.glsl"
|
||||
#include "rect.glsl"
|
||||
|
||||
@@ -222,6 +223,15 @@ mask_inverted_luminance_pattern (inout uint reader,
|
||||
color = source * (color.a - luminance (color.rgb));
|
||||
}
|
||||
|
||||
void
|
||||
blend_mode_pattern (inout vec4 color,
|
||||
uint mode)
|
||||
{
|
||||
vec4 bottom = stack_pop ();
|
||||
|
||||
color = blend_mode (bottom, color, mode);
|
||||
}
|
||||
|
||||
vec4
|
||||
glyphs_pattern (inout uint reader,
|
||||
Position pos)
|
||||
@@ -422,6 +432,24 @@ pattern (uint reader,
|
||||
case GSK_GPU_PATTERN_AFFINE:
|
||||
affine_pattern (reader, pos);
|
||||
break;
|
||||
case GSK_GPU_PATTERN_BLEND_DEFAULT:
|
||||
case GSK_GPU_PATTERN_BLEND_MULTIPLY:
|
||||
case GSK_GPU_PATTERN_BLEND_SCREEN:
|
||||
case GSK_GPU_PATTERN_BLEND_OVERLAY:
|
||||
case GSK_GPU_PATTERN_BLEND_DARKEN:
|
||||
case GSK_GPU_PATTERN_BLEND_LIGHTEN:
|
||||
case GSK_GPU_PATTERN_BLEND_COLOR_DODGE:
|
||||
case GSK_GPU_PATTERN_BLEND_COLOR_BURN:
|
||||
case GSK_GPU_PATTERN_BLEND_HARD_LIGHT:
|
||||
case GSK_GPU_PATTERN_BLEND_SOFT_LIGHT:
|
||||
case GSK_GPU_PATTERN_BLEND_DIFFERENCE:
|
||||
case GSK_GPU_PATTERN_BLEND_EXCLUSION:
|
||||
case GSK_GPU_PATTERN_BLEND_COLOR:
|
||||
case GSK_GPU_PATTERN_BLEND_HUE:
|
||||
case GSK_GPU_PATTERN_BLEND_SATURATION:
|
||||
case GSK_GPU_PATTERN_BLEND_LUMINOSITY:
|
||||
blend_mode_pattern (color, type - GSK_GPU_PATTERN_BLEND_DEFAULT);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user