Allow multiple CoglAtlases for textures
Previously Cogl would only ever use one atlas for textures and if it reached the maximum texture size then all other new textures would get their own GL texture. This patch makes it so that we create as many atlases as needed. This should avoid breaking up some batches and it will be particularly good if we switch to always using multi-texturing with a default shader that selects between multiple atlases using a vertex attribute. Whenever a new atlas is created it is stored in a GSList on the context. A weak weference is taken on the atlas using cogl_object_set_user_data so that it can be removed from the list when the atlas is destroyed. The atlas textures themselves take a reference to the atlas and this is the only thing that keeps the atlas alive. This means that once the atlas becomes empty it will automatically be destroyed. All of the COGL_NOTEs pertaining to atlases are now prefixed with the atlas pointer to make it clearer which atlas is changing.
This commit is contained in:
parent
0f0f763570
commit
9aea72fab5
@ -46,9 +46,10 @@ struct _CoglAtlasTexture
|
|||||||
atlas. This includes the 1-pixel border */
|
atlas. This includes the 1-pixel border */
|
||||||
CoglRectangleMapEntry rectangle;
|
CoglRectangleMapEntry rectangle;
|
||||||
|
|
||||||
/* The texture might need to be migrated out in which case this will
|
/* The atlas that this texture is in. If the texture is no longer in
|
||||||
be set to TRUE and sub_texture will actually be a real texture */
|
an atlas then this will be NULL. A reference is taken on the
|
||||||
gboolean in_atlas;
|
atlas by the texture (but not vice versa so there is no cycle) */
|
||||||
|
CoglAtlas *atlas;
|
||||||
|
|
||||||
/* A CoglSubTexture representing the region for easy rendering */
|
/* A CoglSubTexture representing the region for easy rendering */
|
||||||
CoglHandle sub_texture;
|
CoglHandle sub_texture;
|
||||||
@ -62,7 +63,4 @@ _cogl_atlas_texture_new_from_bitmap (CoglBitmap *bmp,
|
|||||||
CoglTextureFlags flags,
|
CoglTextureFlags flags,
|
||||||
CoglPixelFormat internal_format);
|
CoglPixelFormat internal_format);
|
||||||
|
|
||||||
CoglAtlas *
|
|
||||||
_cogl_atlas_texture_get_atlas (void);
|
|
||||||
|
|
||||||
#endif /* __COGL_ATLAS_TEXTURE_H */
|
#endif /* __COGL_ATLAS_TEXTURE_H */
|
||||||
|
@ -96,7 +96,8 @@ _cogl_atlas_texture_reorganize_foreach_cb (const CoglRectangleMapEntry *entry,
|
|||||||
static void
|
static void
|
||||||
_cogl_atlas_texture_reorganize_cb (void *data)
|
_cogl_atlas_texture_reorganize_cb (void *data)
|
||||||
{
|
{
|
||||||
CoglAtlas *atlas;
|
CoglAtlas *atlas = data;
|
||||||
|
|
||||||
/* We don't know if any pipelines may currently be referenced in
|
/* We don't know if any pipelines may currently be referenced in
|
||||||
* the journal that depend on the current underlying GL texture
|
* the journal that depend on the current underlying GL texture
|
||||||
* storage so we flush the journal before migrating.
|
* storage so we flush the journal before migrating.
|
||||||
@ -106,31 +107,49 @@ _cogl_atlas_texture_reorganize_cb (void *data)
|
|||||||
*/
|
*/
|
||||||
_cogl_journal_flush ();
|
_cogl_journal_flush ();
|
||||||
|
|
||||||
atlas = _cogl_atlas_texture_get_atlas ();
|
|
||||||
|
|
||||||
if (atlas->map)
|
if (atlas->map)
|
||||||
_cogl_rectangle_map_foreach (atlas->map,
|
_cogl_rectangle_map_foreach (atlas->map,
|
||||||
_cogl_atlas_texture_reorganize_foreach_cb,
|
_cogl_atlas_texture_reorganize_foreach_cb,
|
||||||
NULL);
|
NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
CoglAtlas *
|
static void
|
||||||
_cogl_atlas_texture_get_atlas (void)
|
_cogl_atlas_texture_atlas_destroyed_cb (void *user_data)
|
||||||
{
|
{
|
||||||
|
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
|
||||||
|
|
||||||
|
/* Remove the atlas from the global list */
|
||||||
|
ctx->atlases = g_slist_remove (ctx->atlases, user_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static CoglAtlas *
|
||||||
|
_cogl_atlas_texture_create_atlas (void)
|
||||||
|
{
|
||||||
|
static CoglUserDataKey atlas_private_key;
|
||||||
|
|
||||||
|
CoglAtlas *atlas;
|
||||||
|
|
||||||
_COGL_GET_CONTEXT (ctx, COGL_INVALID_HANDLE);
|
_COGL_GET_CONTEXT (ctx, COGL_INVALID_HANDLE);
|
||||||
|
|
||||||
if (ctx->atlas == COGL_INVALID_HANDLE)
|
atlas = _cogl_atlas_new (COGL_PIXEL_FORMAT_RGBA_8888,
|
||||||
{
|
|
||||||
ctx->atlas = _cogl_atlas_new (COGL_PIXEL_FORMAT_RGBA_8888,
|
|
||||||
0,
|
0,
|
||||||
_cogl_atlas_texture_update_position_cb);
|
_cogl_atlas_texture_update_position_cb);
|
||||||
|
|
||||||
_cogl_atlas_add_reorganize_callback (ctx->atlas,
|
_cogl_atlas_add_reorganize_callback (atlas,
|
||||||
_cogl_atlas_texture_reorganize_cb,
|
_cogl_atlas_texture_reorganize_cb,
|
||||||
NULL);
|
atlas);
|
||||||
}
|
|
||||||
|
|
||||||
return ctx->atlas;
|
ctx->atlases = g_slist_prepend (ctx->atlases, atlas);
|
||||||
|
|
||||||
|
/* Set some data on the atlas so we can get notification when it is
|
||||||
|
destroyed in order to remove it from the list. ctx->atlases
|
||||||
|
effectively holds a weak reference. We don't need a strong
|
||||||
|
reference because the atlas textures take a reference on the
|
||||||
|
atlas so it will stay alive */
|
||||||
|
cogl_object_set_user_data (COGL_OBJECT (atlas), &atlas_private_key, atlas,
|
||||||
|
_cogl_atlas_texture_atlas_destroyed_cb);
|
||||||
|
|
||||||
|
return atlas;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@ -173,12 +192,13 @@ _cogl_atlas_texture_set_wrap_mode_parameters (CoglTexture *tex,
|
|||||||
static void
|
static void
|
||||||
_cogl_atlas_texture_remove_from_atlas (CoglAtlasTexture *atlas_tex)
|
_cogl_atlas_texture_remove_from_atlas (CoglAtlasTexture *atlas_tex)
|
||||||
{
|
{
|
||||||
if (atlas_tex->in_atlas)
|
if (atlas_tex->atlas)
|
||||||
{
|
{
|
||||||
_cogl_atlas_remove (_cogl_atlas_texture_get_atlas (),
|
_cogl_atlas_remove (atlas_tex->atlas,
|
||||||
&atlas_tex->rectangle);
|
&atlas_tex->rectangle);
|
||||||
|
|
||||||
atlas_tex->in_atlas = FALSE;
|
cogl_object_unref (atlas_tex->atlas);
|
||||||
|
atlas_tex->atlas = NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -270,7 +290,7 @@ static void
|
|||||||
_cogl_atlas_texture_migrate_out_of_atlas (CoglAtlasTexture *atlas_tex)
|
_cogl_atlas_texture_migrate_out_of_atlas (CoglAtlasTexture *atlas_tex)
|
||||||
{
|
{
|
||||||
/* Make sure this texture is not in the atlas */
|
/* Make sure this texture is not in the atlas */
|
||||||
if (atlas_tex->in_atlas)
|
if (atlas_tex->atlas)
|
||||||
{
|
{
|
||||||
COGL_NOTE (ATLAS, "Migrating texture out of the atlas");
|
COGL_NOTE (ATLAS, "Migrating texture out of the atlas");
|
||||||
|
|
||||||
@ -291,7 +311,7 @@ _cogl_atlas_texture_migrate_out_of_atlas (CoglAtlasTexture *atlas_tex)
|
|||||||
cogl_handle_unref (atlas_tex->sub_texture);
|
cogl_handle_unref (atlas_tex->sub_texture);
|
||||||
|
|
||||||
atlas_tex->sub_texture =
|
atlas_tex->sub_texture =
|
||||||
_cogl_atlas_copy_rectangle (_cogl_atlas_texture_get_atlas (),
|
_cogl_atlas_copy_rectangle (atlas_tex->atlas,
|
||||||
atlas_tex->rectangle.x + 1,
|
atlas_tex->rectangle.x + 1,
|
||||||
atlas_tex->rectangle.y + 1,
|
atlas_tex->rectangle.y + 1,
|
||||||
atlas_tex->rectangle.width - 2,
|
atlas_tex->rectangle.width - 2,
|
||||||
@ -340,7 +360,7 @@ _cogl_atlas_texture_set_region_with_border (CoglAtlasTexture *atlas_tex,
|
|||||||
unsigned int dst_height,
|
unsigned int dst_height,
|
||||||
CoglBitmap *bmp)
|
CoglBitmap *bmp)
|
||||||
{
|
{
|
||||||
CoglAtlas *atlas = _cogl_atlas_texture_get_atlas ();
|
CoglAtlas *atlas = atlas_tex->atlas;
|
||||||
|
|
||||||
/* Copy the central data */
|
/* Copy the central data */
|
||||||
if (!_cogl_texture_set_region_from_bitmap (atlas->texture,
|
if (!_cogl_texture_set_region_from_bitmap (atlas->texture,
|
||||||
@ -408,7 +428,7 @@ _cogl_atlas_texture_set_region (CoglTexture *tex,
|
|||||||
|
|
||||||
/* If the texture is in the atlas then we need to copy the edge
|
/* If the texture is in the atlas then we need to copy the edge
|
||||||
pixels to the border */
|
pixels to the border */
|
||||||
if (atlas_tex->in_atlas)
|
if (atlas_tex->atlas)
|
||||||
{
|
{
|
||||||
gboolean ret;
|
gboolean ret;
|
||||||
|
|
||||||
@ -505,6 +525,8 @@ _cogl_atlas_texture_new_from_bitmap (CoglBitmap *bmp,
|
|||||||
int bmp_width;
|
int bmp_width;
|
||||||
int bmp_height;
|
int bmp_height;
|
||||||
CoglPixelFormat bmp_format;
|
CoglPixelFormat bmp_format;
|
||||||
|
CoglAtlas *atlas;
|
||||||
|
GSList *l;
|
||||||
|
|
||||||
_COGL_GET_CONTEXT (ctx, COGL_INVALID_HANDLE);
|
_COGL_GET_CONTEXT (ctx, COGL_INVALID_HANDLE);
|
||||||
|
|
||||||
@ -557,15 +579,34 @@ _cogl_atlas_texture_new_from_bitmap (CoglBitmap *bmp,
|
|||||||
|
|
||||||
atlas_tex->sub_texture = COGL_INVALID_HANDLE;
|
atlas_tex->sub_texture = COGL_INVALID_HANDLE;
|
||||||
|
|
||||||
|
/* Look for an existing atlas that can hold the texture */
|
||||||
|
for (l = ctx->atlases; l; l = l->next)
|
||||||
/* Try to make some space in the atlas for the texture */
|
/* Try to make some space in the atlas for the texture */
|
||||||
if (!_cogl_atlas_reserve_space (_cogl_atlas_texture_get_atlas (),
|
if (_cogl_atlas_reserve_space (atlas = l->data,
|
||||||
/* Add two pixels for the border */
|
/* Add two pixels for the border */
|
||||||
bmp_width + 2, bmp_height + 2,
|
bmp_width + 2, bmp_height + 2,
|
||||||
atlas_tex))
|
atlas_tex))
|
||||||
{
|
{
|
||||||
|
cogl_object_ref (atlas);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If we couldn't find a suitable atlas then start another */
|
||||||
|
if (l == NULL)
|
||||||
|
{
|
||||||
|
atlas = _cogl_atlas_texture_create_atlas ();
|
||||||
|
COGL_NOTE (ATLAS, "Created new atlas for textures: %p", atlas);
|
||||||
|
if (!_cogl_atlas_reserve_space (atlas,
|
||||||
|
/* Add two pixels for the border */
|
||||||
|
bmp_width + 2, bmp_height + 2,
|
||||||
|
atlas_tex))
|
||||||
|
{
|
||||||
|
/* Ok, this means we really can't add it to the atlas */
|
||||||
|
cogl_object_unref (atlas);
|
||||||
g_free (atlas_tex);
|
g_free (atlas_tex);
|
||||||
return COGL_INVALID_HANDLE;
|
return COGL_INVALID_HANDLE;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
dst_bmp = _cogl_texture_prepare_for_upload (bmp,
|
dst_bmp = _cogl_texture_prepare_for_upload (bmp,
|
||||||
internal_format,
|
internal_format,
|
||||||
@ -576,15 +617,15 @@ _cogl_atlas_texture_new_from_bitmap (CoglBitmap *bmp,
|
|||||||
|
|
||||||
if (dst_bmp == NULL)
|
if (dst_bmp == NULL)
|
||||||
{
|
{
|
||||||
_cogl_atlas_remove (_cogl_atlas_texture_get_atlas (),
|
_cogl_atlas_remove (atlas, &atlas_tex->rectangle);
|
||||||
&atlas_tex->rectangle);
|
cogl_object_unref (atlas);
|
||||||
g_free (atlas_tex);
|
g_free (atlas_tex);
|
||||||
return COGL_INVALID_HANDLE;
|
return COGL_INVALID_HANDLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
atlas_tex->_parent.vtable = &cogl_atlas_texture_vtable;
|
atlas_tex->_parent.vtable = &cogl_atlas_texture_vtable;
|
||||||
atlas_tex->format = internal_format;
|
atlas_tex->format = internal_format;
|
||||||
atlas_tex->in_atlas = TRUE;
|
atlas_tex->atlas = atlas;
|
||||||
|
|
||||||
/* Make another bitmap so that we can override the format */
|
/* Make another bitmap so that we can override the format */
|
||||||
override_bmp = _cogl_bitmap_new_shared (dst_bmp,
|
override_bmp = _cogl_bitmap_new_shared (dst_bmp,
|
||||||
|
@ -489,7 +489,8 @@ _cogl_atlas_reserve_space (CoglAtlas *atlas,
|
|||||||
_cogl_rectangle_map_get_remaining_space (atlas->map) *
|
_cogl_rectangle_map_get_remaining_space (atlas->map) *
|
||||||
100 / (_cogl_rectangle_map_get_width (atlas->map) *
|
100 / (_cogl_rectangle_map_get_width (atlas->map) *
|
||||||
_cogl_rectangle_map_get_height (atlas->map));
|
_cogl_rectangle_map_get_height (atlas->map));
|
||||||
COGL_NOTE (ATLAS, "Atlas is %ix%i, has %i textures and is %i%% waste",
|
COGL_NOTE (ATLAS, "%p: Atlas is %ix%i, has %i textures and is %i%% waste",
|
||||||
|
atlas,
|
||||||
_cogl_rectangle_map_get_width (atlas->map),
|
_cogl_rectangle_map_get_width (atlas->map),
|
||||||
_cogl_rectangle_map_get_height (atlas->map),
|
_cogl_rectangle_map_get_height (atlas->map),
|
||||||
_cogl_rectangle_map_get_n_rectangles (atlas->map),
|
_cogl_rectangle_map_get_n_rectangles (atlas->map),
|
||||||
@ -559,7 +560,7 @@ _cogl_atlas_reserve_space (CoglAtlas *atlas,
|
|||||||
/* If we can't create a map with the texture then give up */
|
/* If we can't create a map with the texture then give up */
|
||||||
if (new_map == NULL)
|
if (new_map == NULL)
|
||||||
{
|
{
|
||||||
COGL_NOTE (ATLAS, "Could not fit texture in the atlas");
|
COGL_NOTE (ATLAS, "%p: Could not fit texture in the atlas", atlas);
|
||||||
ret = FALSE;
|
ret = FALSE;
|
||||||
}
|
}
|
||||||
/* We need to migrate the existing textures into a new texture */
|
/* We need to migrate the existing textures into a new texture */
|
||||||
@ -568,7 +569,7 @@ _cogl_atlas_reserve_space (CoglAtlas *atlas,
|
|||||||
_cogl_rectangle_map_get_width (new_map),
|
_cogl_rectangle_map_get_width (new_map),
|
||||||
_cogl_rectangle_map_get_height (new_map))) == COGL_INVALID_HANDLE)
|
_cogl_rectangle_map_get_height (new_map))) == COGL_INVALID_HANDLE)
|
||||||
{
|
{
|
||||||
COGL_NOTE (ATLAS, "Could not create a CoglTexture2D");
|
COGL_NOTE (ATLAS, "%p: Could not create a CoglTexture2D", atlas);
|
||||||
_cogl_rectangle_map_free (new_map);
|
_cogl_rectangle_map_free (new_map);
|
||||||
ret = FALSE;
|
ret = FALSE;
|
||||||
}
|
}
|
||||||
@ -579,7 +580,8 @@ _cogl_atlas_reserve_space (CoglAtlas *atlas,
|
|||||||
_cogl_atlas_notify_reorganize (atlas);
|
_cogl_atlas_notify_reorganize (atlas);
|
||||||
|
|
||||||
COGL_NOTE (ATLAS,
|
COGL_NOTE (ATLAS,
|
||||||
"Atlas %s with size %ix%i",
|
"%p: Atlas %s with size %ix%i",
|
||||||
|
atlas,
|
||||||
atlas->map == NULL ||
|
atlas->map == NULL ||
|
||||||
_cogl_rectangle_map_get_width (atlas->map) !=
|
_cogl_rectangle_map_get_width (atlas->map) !=
|
||||||
_cogl_rectangle_map_get_width (new_map) ||
|
_cogl_rectangle_map_get_width (new_map) ||
|
||||||
@ -616,7 +618,8 @@ _cogl_atlas_reserve_space (CoglAtlas *atlas,
|
|||||||
100 / (_cogl_rectangle_map_get_width (atlas->map) *
|
100 / (_cogl_rectangle_map_get_width (atlas->map) *
|
||||||
_cogl_rectangle_map_get_height (atlas->map)));
|
_cogl_rectangle_map_get_height (atlas->map)));
|
||||||
|
|
||||||
COGL_NOTE (ATLAS, "Atlas is %ix%i, has %i textures and is %i%% waste",
|
COGL_NOTE (ATLAS, "%p: Atlas is %ix%i, has %i textures and is %i%% waste",
|
||||||
|
atlas,
|
||||||
_cogl_rectangle_map_get_width (atlas->map),
|
_cogl_rectangle_map_get_width (atlas->map),
|
||||||
_cogl_rectangle_map_get_height (atlas->map),
|
_cogl_rectangle_map_get_height (atlas->map),
|
||||||
_cogl_rectangle_map_get_n_rectangles (atlas->map),
|
_cogl_rectangle_map_get_n_rectangles (atlas->map),
|
||||||
@ -636,10 +639,12 @@ _cogl_atlas_remove (CoglAtlas *atlas,
|
|||||||
{
|
{
|
||||||
_cogl_rectangle_map_remove (atlas->map, rectangle);
|
_cogl_rectangle_map_remove (atlas->map, rectangle);
|
||||||
|
|
||||||
COGL_NOTE (ATLAS, "Removed rectangle sized %ix%i",
|
COGL_NOTE (ATLAS, "%p: Removed rectangle sized %ix%i",
|
||||||
|
atlas,
|
||||||
rectangle->width,
|
rectangle->width,
|
||||||
rectangle->height);
|
rectangle->height);
|
||||||
COGL_NOTE (ATLAS, "Atlas is %ix%i, has %i textures and is %i%% waste",
|
COGL_NOTE (ATLAS, "%p: Atlas is %ix%i, has %i textures and is %i%% waste",
|
||||||
|
atlas,
|
||||||
_cogl_rectangle_map_get_width (atlas->map),
|
_cogl_rectangle_map_get_width (atlas->map),
|
||||||
_cogl_rectangle_map_get_height (atlas->map),
|
_cogl_rectangle_map_get_height (atlas->map),
|
||||||
_cogl_rectangle_map_get_n_rectangles (atlas->map),
|
_cogl_rectangle_map_get_n_rectangles (atlas->map),
|
||||||
|
@ -82,9 +82,6 @@ _cogl_atlas_copy_rectangle (CoglAtlas *atlas,
|
|||||||
CoglTextureFlags flags,
|
CoglTextureFlags flags,
|
||||||
CoglPixelFormat format);
|
CoglPixelFormat format);
|
||||||
|
|
||||||
CoglAtlas *
|
|
||||||
_cogl_atlas_get_default (void);
|
|
||||||
|
|
||||||
void
|
void
|
||||||
_cogl_atlas_add_reorganize_callback (CoglAtlas *atlas,
|
_cogl_atlas_add_reorganize_callback (CoglAtlas *atlas,
|
||||||
CoglCallbackListFunc callback,
|
CoglCallbackListFunc callback,
|
||||||
|
@ -285,7 +285,7 @@ cogl_create_context (void)
|
|||||||
_cogl_enable (enable_flags);
|
_cogl_enable (enable_flags);
|
||||||
_cogl_flush_face_winding ();
|
_cogl_flush_face_winding ();
|
||||||
|
|
||||||
_context->atlas = NULL;
|
_context->atlases = NULL;
|
||||||
|
|
||||||
/* As far as I can tell, GL_POINT_SPRITE doesn't have any effect
|
/* As far as I can tell, GL_POINT_SPRITE doesn't have any effect
|
||||||
unless GL_COORD_REPLACE is enabled for an individual
|
unless GL_COORD_REPLACE is enabled for an individual
|
||||||
@ -362,8 +362,7 @@ _cogl_destroy_context (void)
|
|||||||
if (_context->current_clip_stack_valid)
|
if (_context->current_clip_stack_valid)
|
||||||
_cogl_clip_stack_unref (_context->current_clip_stack);
|
_cogl_clip_stack_unref (_context->current_clip_stack);
|
||||||
|
|
||||||
if (_context->atlas)
|
g_slist_free (_context->atlases);
|
||||||
cogl_object_unref (_context->atlas);
|
|
||||||
|
|
||||||
_cogl_bitmask_destroy (&_context->arrays_enabled);
|
_cogl_bitmask_destroy (&_context->arrays_enabled);
|
||||||
_cogl_bitmask_destroy (&_context->temp_bitmask);
|
_cogl_bitmask_destroy (&_context->temp_bitmask);
|
||||||
|
@ -178,7 +178,7 @@ typedef struct
|
|||||||
|
|
||||||
CoglPipeline *texture_download_pipeline;
|
CoglPipeline *texture_download_pipeline;
|
||||||
|
|
||||||
CoglAtlas *atlas;
|
GSList *atlases;
|
||||||
|
|
||||||
/* This debugging variable is used to pick a colour for visually
|
/* This debugging variable is used to pick a colour for visually
|
||||||
displaying the quad batches. It needs to be global so that it can
|
displaying the quad batches. It needs to be global so that it can
|
||||||
|
@ -228,6 +228,7 @@ cogl_pango_glyph_cache_lookup (CoglPangoGlyphCache *cache,
|
|||||||
atlas = _cogl_atlas_new (COGL_PIXEL_FORMAT_A_8,
|
atlas = _cogl_atlas_new (COGL_PIXEL_FORMAT_A_8,
|
||||||
TRUE,
|
TRUE,
|
||||||
cogl_pango_glyph_cache_update_position_cb);
|
cogl_pango_glyph_cache_update_position_cb);
|
||||||
|
COGL_NOTE (ATLAS, "Created new atlas for glyphs: %p", atlas);
|
||||||
/* If we still can't reserve space then something has gone
|
/* If we still can't reserve space then something has gone
|
||||||
seriously wrong so we'll just give up */
|
seriously wrong so we'll just give up */
|
||||||
if (!_cogl_atlas_reserve_space (atlas,
|
if (!_cogl_atlas_reserve_space (atlas,
|
||||||
|
Loading…
Reference in New Issue
Block a user