Add target to texture
Test: refactoring cl. bug:32413624 Change-Id: I94b1c31cd4e0712dfcfd7777a0012424c1bf0dca
This commit is contained in:
@@ -119,7 +119,6 @@ public:
|
||||
|
||||
struct TextureData {
|
||||
Texture* texture;
|
||||
GLenum target;
|
||||
GLenum filter;
|
||||
GLenum clamp;
|
||||
Matrix4* textureTransform;
|
||||
|
||||
@@ -308,8 +308,7 @@ GlopBuilder& GlopBuilder::setFillTexturePaint(Texture& texture,
|
||||
|
||||
GLenum filter = (textureFillFlags & TextureFillFlags::ForceFilter)
|
||||
? GL_LINEAR : PaintUtils::getFilter(paint);
|
||||
mOutGlop->fill.texture = { &texture,
|
||||
GL_TEXTURE_2D, filter, GL_CLAMP_TO_EDGE, nullptr };
|
||||
mOutGlop->fill.texture = { &texture, filter, GL_CLAMP_TO_EDGE, nullptr };
|
||||
|
||||
if (paint) {
|
||||
int color = paint->getColor();
|
||||
@@ -352,10 +351,10 @@ GlopBuilder& GlopBuilder::setFillPaint(const SkPaint& paint, float alphaScale, b
|
||||
|
||||
if (CC_LIKELY(!shadowInterp)) {
|
||||
mOutGlop->fill.texture = {
|
||||
nullptr, GL_INVALID_ENUM, GL_INVALID_ENUM, GL_INVALID_ENUM, nullptr };
|
||||
nullptr, GL_INVALID_ENUM, GL_INVALID_ENUM, nullptr };
|
||||
} else {
|
||||
mOutGlop->fill.texture = {
|
||||
mCaches.textureState().getShadowLutTexture(), GL_TEXTURE_2D,
|
||||
mCaches.textureState().getShadowLutTexture(),
|
||||
GL_INVALID_ENUM, GL_INVALID_ENUM, nullptr };
|
||||
}
|
||||
|
||||
@@ -373,7 +372,7 @@ GlopBuilder& GlopBuilder::setFillPathTexturePaint(PathTexture& texture,
|
||||
REQUIRE_STAGES(kMeshStage | kRoundRectClipStage);
|
||||
|
||||
//specify invalid filter/clamp, since these are always static for PathTextures
|
||||
mOutGlop->fill.texture = { &texture, GL_TEXTURE_2D, GL_INVALID_ENUM, GL_INVALID_ENUM, nullptr };
|
||||
mOutGlop->fill.texture = { &texture, GL_INVALID_ENUM, GL_INVALID_ENUM, nullptr };
|
||||
|
||||
setFill(paint.getColor(), alphaScale,
|
||||
paint.getBlendMode(), Blend::ModeOrderSwap::NoSwap,
|
||||
@@ -390,7 +389,7 @@ GlopBuilder& GlopBuilder::setFillShadowTexturePaint(ShadowTexture& texture, int
|
||||
REQUIRE_STAGES(kMeshStage | kRoundRectClipStage);
|
||||
|
||||
//specify invalid filter/clamp, since these are always static for ShadowTextures
|
||||
mOutGlop->fill.texture = { &texture, GL_TEXTURE_2D, GL_INVALID_ENUM, GL_INVALID_ENUM, nullptr };
|
||||
mOutGlop->fill.texture = { &texture, GL_INVALID_ENUM, GL_INVALID_ENUM, nullptr };
|
||||
|
||||
const int ALPHA_BITMASK = SK_ColorBLACK;
|
||||
const int COLOR_BITMASK = ~ALPHA_BITMASK;
|
||||
@@ -412,7 +411,7 @@ GlopBuilder& GlopBuilder::setFillBlack() {
|
||||
TRIGGER_STAGE(kFillStage);
|
||||
REQUIRE_STAGES(kMeshStage | kRoundRectClipStage);
|
||||
|
||||
mOutGlop->fill.texture = { nullptr, GL_INVALID_ENUM, GL_INVALID_ENUM, GL_INVALID_ENUM, nullptr };
|
||||
mOutGlop->fill.texture = { nullptr, GL_INVALID_ENUM, GL_INVALID_ENUM, nullptr };
|
||||
setFill(SK_ColorBLACK, 1.0f, SkBlendMode::kSrcOver, Blend::ModeOrderSwap::NoSwap,
|
||||
nullptr, nullptr);
|
||||
return *this;
|
||||
@@ -422,7 +421,7 @@ GlopBuilder& GlopBuilder::setFillClear() {
|
||||
TRIGGER_STAGE(kFillStage);
|
||||
REQUIRE_STAGES(kMeshStage | kRoundRectClipStage);
|
||||
|
||||
mOutGlop->fill.texture = { nullptr, GL_INVALID_ENUM, GL_INVALID_ENUM, GL_INVALID_ENUM, nullptr };
|
||||
mOutGlop->fill.texture = { nullptr, GL_INVALID_ENUM, GL_INVALID_ENUM, nullptr };
|
||||
setFill(SK_ColorBLACK, 1.0f, SkBlendMode::kClear, Blend::ModeOrderSwap::NoSwap,
|
||||
nullptr, nullptr);
|
||||
return *this;
|
||||
@@ -433,8 +432,7 @@ GlopBuilder& GlopBuilder::setFillLayer(Texture& texture, const SkColorFilter* co
|
||||
TRIGGER_STAGE(kFillStage);
|
||||
REQUIRE_STAGES(kMeshStage | kRoundRectClipStage);
|
||||
|
||||
mOutGlop->fill.texture = { &texture,
|
||||
GL_TEXTURE_2D, GL_LINEAR, GL_CLAMP_TO_EDGE, nullptr };
|
||||
mOutGlop->fill.texture = { &texture, GL_LINEAR, GL_CLAMP_TO_EDGE, nullptr };
|
||||
|
||||
setFill(SK_ColorWHITE, alpha, mode, modeUsage, nullptr, colorFilter);
|
||||
|
||||
@@ -447,7 +445,7 @@ GlopBuilder& GlopBuilder::setFillTextureLayer(Layer& layer, float alpha) {
|
||||
REQUIRE_STAGES(kMeshStage | kRoundRectClipStage);
|
||||
|
||||
mOutGlop->fill.texture = { &(layer.getTexture()),
|
||||
layer.getRenderTarget(), GL_LINEAR, GL_CLAMP_TO_EDGE, &layer.getTexTransform() };
|
||||
GL_LINEAR, GL_CLAMP_TO_EDGE, &layer.getTexTransform() };
|
||||
|
||||
setFill(SK_ColorWHITE, alpha, layer.getMode(), Blend::ModeOrderSwap::NoSwap,
|
||||
nullptr, layer.getColorFilter());
|
||||
@@ -461,9 +459,7 @@ GlopBuilder& GlopBuilder::setFillExternalTexture(Texture& texture, Matrix4& text
|
||||
TRIGGER_STAGE(kFillStage);
|
||||
REQUIRE_STAGES(kMeshStage | kRoundRectClipStage);
|
||||
|
||||
mOutGlop->fill.texture = { &texture,
|
||||
GL_TEXTURE_EXTERNAL_OES, GL_LINEAR, GL_CLAMP_TO_EDGE,
|
||||
&textureTransform };
|
||||
mOutGlop->fill.texture = { &texture, GL_LINEAR, GL_CLAMP_TO_EDGE, &textureTransform };
|
||||
|
||||
setFill(SK_ColorWHITE, 1.0f, SkBlendMode::kSrc, Blend::ModeOrderSwap::NoSwap,
|
||||
nullptr, nullptr);
|
||||
@@ -603,7 +599,7 @@ void verify(const ProgramDescription& description, const Glop& glop) {
|
||||
void GlopBuilder::build() {
|
||||
REQUIRE_STAGES(kAllStages);
|
||||
if (mOutGlop->mesh.vertices.attribFlags & VertexAttribFlags::TextureCoord) {
|
||||
if (mOutGlop->fill.texture.target == GL_TEXTURE_2D) {
|
||||
if (mOutGlop->fill.texture.texture->target() == GL_TEXTURE_2D) {
|
||||
mDescription.hasTexture = true;
|
||||
} else {
|
||||
mDescription.hasExternalTexture = true;
|
||||
@@ -668,7 +664,8 @@ void GlopBuilder::dump(const Glop& glop) {
|
||||
ALOGD(" program %p", fill.program);
|
||||
if (fill.texture.texture) {
|
||||
ALOGD(" texture %p, target %d, filter %d, clamp %d",
|
||||
fill.texture.texture, fill.texture.target, fill.texture.filter, fill.texture.clamp);
|
||||
fill.texture.texture, fill.texture.texture->target(),
|
||||
fill.texture.filter, fill.texture.clamp);
|
||||
if (fill.texture.textureTransform) {
|
||||
fill.texture.textureTransform->dump("texture transform");
|
||||
}
|
||||
|
||||
@@ -72,9 +72,8 @@ public:
|
||||
GlopBuilder& setFillLayer(Texture& texture, const SkColorFilter* colorFilter,
|
||||
float alpha, SkBlendMode mode, Blend::ModeOrderSwap modeUsage);
|
||||
GlopBuilder& setFillTextureLayer(Layer& layer, float alpha);
|
||||
// TODO: Texture should probably know and own its target.
|
||||
// setFillLayer() forces it to GL_TEXTURE which isn't always correct.
|
||||
// Similarly setFillLayer normally forces its own wrap & filter mode
|
||||
// TODO: setFillLayer normally forces its own wrap & filter mode,
|
||||
// which isn't always correct.
|
||||
GlopBuilder& setFillExternalTexture(Texture& texture, Matrix4& textureTransform);
|
||||
|
||||
GlopBuilder& setTransform(const Matrix4& canvas, const int transformFlags);
|
||||
|
||||
@@ -41,7 +41,6 @@ Layer::Layer(RenderState& renderState, uint32_t layerWidth, uint32_t layerHeight
|
||||
// TODO: This is a violation of Android's typical ref counting, but it
|
||||
// preserves the old inc/dec ref locations. This should be changed...
|
||||
incStrong(nullptr);
|
||||
renderTarget = GL_NONE; // see DeferredLayerUpdater::updateLayer()
|
||||
texture.mWidth = layerWidth;
|
||||
texture.mHeight = layerHeight;
|
||||
renderState.registerLayer(this);
|
||||
@@ -66,7 +65,7 @@ void Layer::setColorFilter(SkColorFilter* filter) {
|
||||
|
||||
void Layer::bindTexture() const {
|
||||
if (texture.mId) {
|
||||
caches.textureState().bindTexture(renderTarget, texture.mId);
|
||||
caches.textureState().bindTexture(texture.target(), texture.mId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -75,7 +75,8 @@ public:
|
||||
}
|
||||
|
||||
void setSize(uint32_t width, uint32_t height) {
|
||||
texture.updateSize(width, height, texture.internalFormat(), texture.format());
|
||||
texture.updateSize(width, height, texture.internalFormat(), texture.format(),
|
||||
texture.target());
|
||||
}
|
||||
|
||||
inline void setBlend(bool blend) {
|
||||
@@ -120,23 +121,23 @@ public:
|
||||
}
|
||||
|
||||
inline GLenum getRenderTarget() const {
|
||||
return renderTarget;
|
||||
return texture.target();
|
||||
}
|
||||
|
||||
inline void setRenderTarget(GLenum renderTarget) {
|
||||
this->renderTarget = renderTarget;
|
||||
texture.mTarget = renderTarget;
|
||||
}
|
||||
|
||||
inline bool isRenderable() const {
|
||||
return renderTarget != GL_NONE;
|
||||
return texture.target() != GL_NONE;
|
||||
}
|
||||
|
||||
void setWrap(GLenum wrap, bool bindTexture = false, bool force = false) {
|
||||
texture.setWrap(wrap, bindTexture, force, renderTarget);
|
||||
texture.setWrap(wrap, bindTexture, force);
|
||||
}
|
||||
|
||||
void setFilter(GLenum filter, bool bindTexture = false, bool force = false) {
|
||||
texture.setFilter(filter, bindTexture, force, renderTarget);
|
||||
texture.setFilter(filter, bindTexture, force);
|
||||
}
|
||||
|
||||
inline SkColorFilter* getColorFilter() const {
|
||||
@@ -185,11 +186,6 @@ private:
|
||||
*/
|
||||
Texture texture;
|
||||
|
||||
/**
|
||||
* Indicates the render target.
|
||||
*/
|
||||
GLenum renderTarget = GL_TEXTURE_2D;
|
||||
|
||||
/**
|
||||
* Color filter used to draw this layer. Optional.
|
||||
*/
|
||||
|
||||
@@ -196,8 +196,8 @@ CopyResult Readback::copySurfaceInto(renderthread::RenderThread& renderThread,
|
||||
}
|
||||
|
||||
Texture sourceTexture(caches);
|
||||
sourceTexture.wrap(sourceTexId,
|
||||
sourceBuffer->getWidth(), sourceBuffer->getHeight(), 0, 0 /* total lie */);
|
||||
sourceTexture.wrap(sourceTexId, sourceBuffer->getWidth(),
|
||||
sourceBuffer->getHeight(), 0, 0 /* total lie */, GL_TEXTURE_EXTERNAL_OES);
|
||||
|
||||
CopyResult copyResult = copyTextureInto(caches, renderThread.renderState(),
|
||||
sourceTexture, texTransform, srcRect, bitmap);
|
||||
|
||||
@@ -48,37 +48,34 @@ static int bytesPerPixel(GLint glFormat) {
|
||||
}
|
||||
}
|
||||
|
||||
void Texture::setWrapST(GLenum wrapS, GLenum wrapT, bool bindTexture, bool force,
|
||||
GLenum renderTarget) {
|
||||
void Texture::setWrapST(GLenum wrapS, GLenum wrapT, bool bindTexture, bool force) {
|
||||
|
||||
if (force || wrapS != mWrapS || wrapT != mWrapT) {
|
||||
mWrapS = wrapS;
|
||||
mWrapT = wrapT;
|
||||
|
||||
if (bindTexture) {
|
||||
mCaches.textureState().bindTexture(renderTarget, mId);
|
||||
mCaches.textureState().bindTexture(mTarget, mId);
|
||||
}
|
||||
|
||||
glTexParameteri(renderTarget, GL_TEXTURE_WRAP_S, wrapS);
|
||||
glTexParameteri(renderTarget, GL_TEXTURE_WRAP_T, wrapT);
|
||||
glTexParameteri(mTarget, GL_TEXTURE_WRAP_S, wrapS);
|
||||
glTexParameteri(mTarget, GL_TEXTURE_WRAP_T, wrapT);
|
||||
}
|
||||
}
|
||||
|
||||
void Texture::setFilterMinMag(GLenum min, GLenum mag, bool bindTexture, bool force,
|
||||
GLenum renderTarget) {
|
||||
|
||||
void Texture::setFilterMinMag(GLenum min, GLenum mag, bool bindTexture, bool force) {
|
||||
if (force || min != mMinFilter || mag != mMagFilter) {
|
||||
mMinFilter = min;
|
||||
mMagFilter = mag;
|
||||
|
||||
if (bindTexture) {
|
||||
mCaches.textureState().bindTexture(renderTarget, mId);
|
||||
mCaches.textureState().bindTexture(mTarget, mId);
|
||||
}
|
||||
|
||||
if (mipMap && min == GL_LINEAR) min = GL_LINEAR_MIPMAP_LINEAR;
|
||||
|
||||
glTexParameteri(renderTarget, GL_TEXTURE_MIN_FILTER, min);
|
||||
glTexParameteri(renderTarget, GL_TEXTURE_MAG_FILTER, mag);
|
||||
glTexParameteri(mTarget, GL_TEXTURE_MIN_FILTER, min);
|
||||
glTexParameteri(mTarget, GL_TEXTURE_MAG_FILTER, mag);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,15 +84,20 @@ void Texture::deleteTexture() {
|
||||
mId = 0;
|
||||
}
|
||||
|
||||
bool Texture::updateSize(uint32_t width, uint32_t height, GLint internalFormat, GLint format) {
|
||||
if (mWidth == width && mHeight == height &&
|
||||
mFormat == format && mInternalFormat == internalFormat) {
|
||||
bool Texture::updateSize(uint32_t width, uint32_t height, GLint internalFormat,
|
||||
GLint format, GLenum target) {
|
||||
if (mWidth == width
|
||||
&& mHeight == height
|
||||
&& mFormat == format
|
||||
&& mInternalFormat == internalFormat
|
||||
&& mTarget == target) {
|
||||
return false;
|
||||
}
|
||||
mWidth = width;
|
||||
mHeight = height;
|
||||
mFormat = format;
|
||||
mInternalFormat = internalFormat;
|
||||
mTarget = target;
|
||||
notifySizeChanged(mWidth * mHeight * bytesPerPixel(internalFormat));
|
||||
return true;
|
||||
}
|
||||
@@ -110,7 +112,7 @@ void Texture::resetCachedParams() {
|
||||
void Texture::upload(GLint internalFormat, uint32_t width, uint32_t height,
|
||||
GLenum format, GLenum type, const void* pixels) {
|
||||
GL_CHECKPOINT(MODERATE);
|
||||
bool needsAlloc = updateSize(width, height, internalFormat, format);
|
||||
bool needsAlloc = updateSize(width, height, internalFormat, format, GL_TEXTURE_2D);
|
||||
if (!mId) {
|
||||
glGenTextures(1, &mId);
|
||||
needsAlloc = true;
|
||||
@@ -241,7 +243,7 @@ void Texture::upload(Bitmap& bitmap) {
|
||||
GLint internalFormat, format, type;
|
||||
colorTypeToGlFormatAndType(mCaches, bitmap.colorType(), needSRGB, &internalFormat, &format, &type);
|
||||
|
||||
if (updateSize(bitmap.width(), bitmap.height(), internalFormat, format)) {
|
||||
if (updateSize(bitmap.width(), bitmap.height(), internalFormat, format, GL_TEXTURE_2D)) {
|
||||
needsAlloc = true;
|
||||
}
|
||||
|
||||
@@ -286,12 +288,14 @@ void Texture::upload(Bitmap& bitmap) {
|
||||
}
|
||||
}
|
||||
|
||||
void Texture::wrap(GLuint id, uint32_t width, uint32_t height, GLint internalFormat, GLint format) {
|
||||
void Texture::wrap(GLuint id, uint32_t width, uint32_t height,
|
||||
GLint internalFormat, GLint format, GLenum target) {
|
||||
mId = id;
|
||||
mWidth = width;
|
||||
mHeight = height;
|
||||
mFormat = format;
|
||||
mInternalFormat = internalFormat;
|
||||
mTarget = target;
|
||||
// We're wrapping an existing texture, so don't double count this memory
|
||||
notifySizeChanged(0);
|
||||
}
|
||||
|
||||
@@ -41,21 +41,19 @@ public:
|
||||
|
||||
virtual ~Texture() { }
|
||||
|
||||
inline void setWrap(GLenum wrap, bool bindTexture = false, bool force = false,
|
||||
GLenum renderTarget = GL_TEXTURE_2D) {
|
||||
setWrapST(wrap, wrap, bindTexture, force, renderTarget);
|
||||
inline void setWrap(GLenum wrap, bool bindTexture = false, bool force = false) {
|
||||
setWrapST(wrap, wrap, bindTexture, force);
|
||||
}
|
||||
|
||||
virtual void setWrapST(GLenum wrapS, GLenum wrapT, bool bindTexture = false,
|
||||
bool force = false, GLenum renderTarget = GL_TEXTURE_2D);
|
||||
bool force = false);
|
||||
|
||||
inline void setFilter(GLenum filter, bool bindTexture = false, bool force = false,
|
||||
GLenum renderTarget = GL_TEXTURE_2D) {
|
||||
setFilterMinMag(filter, filter, bindTexture, force, renderTarget);
|
||||
inline void setFilter(GLenum filter, bool bindTexture = false, bool force = false) {
|
||||
setFilterMinMag(filter, filter, bindTexture, force);
|
||||
}
|
||||
|
||||
virtual void setFilterMinMag(GLenum min, GLenum mag, bool bindTexture = false,
|
||||
bool force = false, GLenum renderTarget = GL_TEXTURE_2D);
|
||||
bool force = false);
|
||||
|
||||
/**
|
||||
* Convenience method to call glDeleteTextures() on this texture's id.
|
||||
@@ -91,7 +89,8 @@ public:
|
||||
/**
|
||||
* Wraps an existing texture.
|
||||
*/
|
||||
void wrap(GLuint id, uint32_t width, uint32_t height, GLint internalFormat, GLint format);
|
||||
void wrap(GLuint id, uint32_t width, uint32_t height, GLint internalFormat,
|
||||
GLint format, GLenum target);
|
||||
|
||||
GLuint id() const {
|
||||
return mId;
|
||||
@@ -113,6 +112,10 @@ public:
|
||||
return mInternalFormat;
|
||||
}
|
||||
|
||||
GLenum target() const {
|
||||
return mTarget;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generation of the backing bitmap,
|
||||
*/
|
||||
@@ -152,7 +155,8 @@ private:
|
||||
friend class Layer;
|
||||
|
||||
// Returns true if the size changed, false if it was the same
|
||||
bool updateSize(uint32_t width, uint32_t height, GLint internalFormat, GLint format);
|
||||
bool updateSize(uint32_t width, uint32_t height, GLint internalFormat,
|
||||
GLint format, GLenum target);
|
||||
void resetCachedParams();
|
||||
|
||||
GLuint mId = 0;
|
||||
@@ -160,6 +164,7 @@ private:
|
||||
uint32_t mHeight = 0;
|
||||
GLint mFormat = 0;
|
||||
GLint mInternalFormat = 0;
|
||||
GLenum mTarget = GL_NONE;
|
||||
|
||||
/* See GLES spec section 3.8.14
|
||||
* "In the initial state, the value assigned to TEXTURE_MIN_FILTER is
|
||||
|
||||
@@ -278,12 +278,12 @@ void RenderState::render(const Glop& glop, const Matrix4& orthoMatrix) {
|
||||
// texture always takes slot 0, shader samplers increment from there
|
||||
mCaches->textureState().activateTexture(0);
|
||||
|
||||
mCaches->textureState().bindTexture(texture.target, texture.texture->id());
|
||||
mCaches->textureState().bindTexture(texture.texture->target(), texture.texture->id());
|
||||
if (texture.clamp != GL_INVALID_ENUM) {
|
||||
texture.texture->setWrap(texture.clamp, false, false, texture.target);
|
||||
texture.texture->setWrap(texture.clamp, false, false);
|
||||
}
|
||||
if (texture.filter != GL_INVALID_ENUM) {
|
||||
texture.texture->setFilter(texture.filter, false, false, texture.target);
|
||||
texture.texture->setFilter(texture.filter, false, false);
|
||||
}
|
||||
|
||||
if (texture.textureTransform) {
|
||||
|
||||
@@ -45,7 +45,11 @@ static void expectFillEq(Glop::Fill& expectedFill, Glop::Fill& builtFill) {
|
||||
EXPECT_EQ(expectedFill.skiaShaderData.skiaShaderType, builtFill.skiaShaderData.skiaShaderType);
|
||||
EXPECT_EQ(expectedFill.texture.clamp, builtFill.texture.clamp);
|
||||
EXPECT_EQ(expectedFill.texture.filter, builtFill.texture.filter);
|
||||
EXPECT_EQ(expectedFill.texture.target, builtFill.texture.target);
|
||||
EXPECT_TRUE((expectedFill.texture.texture && builtFill.texture.texture)
|
||||
|| (!expectedFill.texture.texture && !builtFill.texture.texture));
|
||||
if (expectedFill.texture.texture) {
|
||||
EXPECT_EQ(expectedFill.texture.texture->target(), builtFill.texture.texture->target());
|
||||
}
|
||||
EXPECT_EQ(expectedFill.texture.textureTransform, builtFill.texture.textureTransform);
|
||||
}
|
||||
|
||||
@@ -108,7 +112,7 @@ static std::unique_ptr<Glop> blackUnitQuadGlop(RenderState& renderState) {
|
||||
glop->fill.color.set(Color::Black);
|
||||
glop->fill.skiaShaderData.skiaShaderType = kNone_SkiaShaderType;
|
||||
glop->fill.filterMode = ProgramDescription::ColorFilterMode::None;
|
||||
glop->fill.texture = { nullptr, GL_INVALID_ENUM, GL_INVALID_ENUM, GL_INVALID_ENUM, nullptr };
|
||||
glop->fill.texture = { nullptr, GL_INVALID_ENUM, GL_INVALID_ENUM, nullptr };
|
||||
return glop;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user