mirror of
https://github.com/ZDoom/gzdoom-gles.git
synced 2024-11-11 15:22:15 +00:00
Split wall drawer selection from sprite
This commit is contained in:
parent
36a23d60b8
commit
e17c8c1678
6 changed files with 132 additions and 151 deletions
|
@ -59,7 +59,7 @@ namespace swrenderer
|
|||
return active_drawers;
|
||||
}
|
||||
|
||||
DrawerArgs::DrawerArgs()
|
||||
ColumnDrawerArgs::ColumnDrawerArgs()
|
||||
{
|
||||
colfunc = &SWPixelFormatDrawers::DrawColumn;
|
||||
basecolfunc = &SWPixelFormatDrawers::DrawColumn;
|
||||
|
@ -343,7 +343,7 @@ namespace swrenderer
|
|||
}
|
||||
}
|
||||
|
||||
bool DrawerArgs::SetBlendFunc(int op, fixed_t fglevel, fixed_t bglevel, int flags)
|
||||
bool ColumnDrawerArgs::SetBlendFunc(int op, fixed_t fglevel, fixed_t bglevel, int flags)
|
||||
{
|
||||
// r_drawtrans is a seriously bad thing to turn off. I wonder if I should
|
||||
// just remove it completely.
|
||||
|
@ -353,7 +353,7 @@ namespace swrenderer
|
|||
{
|
||||
colfunc = &SWPixelFormatDrawers::FillColumn;
|
||||
}
|
||||
else if (mTranslation == nullptr)
|
||||
else if (TranslationMap() == nullptr)
|
||||
{
|
||||
colfunc = basecolfunc;
|
||||
}
|
||||
|
@ -398,7 +398,7 @@ namespace swrenderer
|
|||
{
|
||||
colfunc = &SWPixelFormatDrawers::FillAddColumn;
|
||||
}
|
||||
else if (mTranslation == nullptr)
|
||||
else if (TranslationMap() == nullptr)
|
||||
{
|
||||
colfunc = &SWPixelFormatDrawers::DrawAddColumn;
|
||||
}
|
||||
|
@ -414,7 +414,7 @@ namespace swrenderer
|
|||
{
|
||||
colfunc = &SWPixelFormatDrawers::FillAddClampColumn;
|
||||
}
|
||||
else if (mTranslation == nullptr)
|
||||
else if (TranslationMap() == nullptr)
|
||||
{
|
||||
colfunc = &SWPixelFormatDrawers::DrawAddClampColumn;
|
||||
}
|
||||
|
@ -431,7 +431,7 @@ namespace swrenderer
|
|||
{
|
||||
colfunc = &SWPixelFormatDrawers::FillSubClampColumn;
|
||||
}
|
||||
else if (mTranslation == nullptr)
|
||||
else if (TranslationMap() == nullptr)
|
||||
{
|
||||
colfunc = &SWPixelFormatDrawers::DrawSubClampColumn;
|
||||
}
|
||||
|
@ -451,7 +451,7 @@ namespace swrenderer
|
|||
{
|
||||
colfunc = &SWPixelFormatDrawers::FillRevSubClampColumn;
|
||||
}
|
||||
else if (mTranslation == nullptr)
|
||||
else if (TranslationMap() == nullptr)
|
||||
{
|
||||
colfunc = &SWPixelFormatDrawers::DrawRevSubClampColumn;
|
||||
}
|
||||
|
@ -467,7 +467,7 @@ namespace swrenderer
|
|||
}
|
||||
}
|
||||
|
||||
fixed_t DrawerArgs::GetAlpha(int type, fixed_t alpha)
|
||||
fixed_t ColumnDrawerArgs::GetAlpha(int type, fixed_t alpha)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
|
@ -479,7 +479,7 @@ namespace swrenderer
|
|||
}
|
||||
}
|
||||
|
||||
bool DrawerArgs::SetPatchStyle(FRenderStyle style, fixed_t alpha, int translation, uint32_t color, FDynamicColormap *&basecolormap, fixed_t shadedlightshade)
|
||||
bool ColumnDrawerArgs::SetPatchStyle(FRenderStyle style, fixed_t alpha, int translation, uint32_t color, FDynamicColormap *&basecolormap, fixed_t shadedlightshade)
|
||||
{
|
||||
fixed_t fglevel, bglevel;
|
||||
|
||||
|
@ -513,16 +513,16 @@ namespace swrenderer
|
|||
|
||||
if (translation != -1)
|
||||
{
|
||||
mTranslation = nullptr;
|
||||
SetTranslationMap(nullptr);
|
||||
if (translation != 0)
|
||||
{
|
||||
FRemapTable *table = TranslationToTable(translation);
|
||||
if (table != NULL && !table->Inactive)
|
||||
{
|
||||
if (r_swtruecolor)
|
||||
mTranslation = (uint8_t*)table->Palette;
|
||||
SetTranslationMap((uint8_t*)table->Palette);
|
||||
else
|
||||
mTranslation = table->Remap;
|
||||
SetTranslationMap(table->Remap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -581,14 +581,14 @@ namespace swrenderer
|
|||
SetColorMapLight(&identitycolormap, 0, 0);
|
||||
}
|
||||
|
||||
if (!DrawerArgs::SetBlendFunc(style.BlendOp, fglevel, bglevel, style.Flags))
|
||||
if (!ColumnDrawerArgs::SetBlendFunc(style.BlendOp, fglevel, bglevel, style.Flags))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DrawerArgs::SetPatchStyle(FRenderStyle style, float alpha, int translation, uint32_t color, FDynamicColormap *&basecolormap, fixed_t shadedlightshade)
|
||||
bool ColumnDrawerArgs::SetPatchStyle(FRenderStyle style, float alpha, int translation, uint32_t color, FDynamicColormap *&basecolormap, fixed_t shadedlightshade)
|
||||
{
|
||||
return SetPatchStyle(style, FLOAT2FIXED(alpha), translation, color, basecolormap, shadedlightshade);
|
||||
}
|
||||
|
@ -600,27 +600,6 @@ namespace swrenderer
|
|||
dc_dest_y = y;
|
||||
}
|
||||
|
||||
WallDrawerFunc WallDrawerArgs::GetTransMaskDrawer()
|
||||
{
|
||||
if (colfunc == &SWPixelFormatDrawers::DrawAddColumn)
|
||||
{
|
||||
return &SWPixelFormatDrawers::DrawWallAddColumn;
|
||||
}
|
||||
if (colfunc == &SWPixelFormatDrawers::DrawAddClampColumn)
|
||||
{
|
||||
return &SWPixelFormatDrawers::DrawWallAddClampColumn;
|
||||
}
|
||||
if (colfunc == &SWPixelFormatDrawers::DrawSubClampColumn)
|
||||
{
|
||||
return &SWPixelFormatDrawers::DrawWallSubClampColumn;
|
||||
}
|
||||
if (colfunc == &SWPixelFormatDrawers::DrawRevSubClampColumn)
|
||||
{
|
||||
return &SWPixelFormatDrawers::DrawWallRevSubClampColumn;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void SpanDrawerArgs::SetSpanStyle(bool masked, bool additive, fixed_t alpha)
|
||||
{
|
||||
if (masked)
|
||||
|
@ -677,6 +656,37 @@ namespace swrenderer
|
|||
}
|
||||
}
|
||||
|
||||
void WallDrawerArgs::SetStyle(bool masked, bool additive, fixed_t alpha)
|
||||
{
|
||||
if (alpha < OPAQUE || additive)
|
||||
{
|
||||
if (!additive)
|
||||
{
|
||||
wallfunc = &SWPixelFormatDrawers::DrawWallAddColumn;
|
||||
dc_srcblend = Col2RGB8[alpha >> 10];
|
||||
dc_destblend = Col2RGB8[(OPAQUE - alpha) >> 10];
|
||||
dc_srcalpha = alpha;
|
||||
dc_destalpha = OPAQUE - alpha;
|
||||
}
|
||||
else
|
||||
{
|
||||
wallfunc = &SWPixelFormatDrawers::DrawWallAddClampColumn;
|
||||
dc_srcblend = Col2RGB8_LessPrecision[alpha >> 10];
|
||||
dc_destblend = Col2RGB8_LessPrecision[FRACUNIT >> 10];
|
||||
dc_srcalpha = alpha;
|
||||
dc_destalpha = FRACUNIT;
|
||||
}
|
||||
}
|
||||
else if (masked)
|
||||
{
|
||||
wallfunc = &SWPixelFormatDrawers::DrawWallMaskedColumn;
|
||||
}
|
||||
else
|
||||
{
|
||||
wallfunc = &SWPixelFormatDrawers::DrawWallColumn;
|
||||
}
|
||||
}
|
||||
|
||||
void SpanDrawerArgs::DrawSpan()
|
||||
{
|
||||
(Drawers()->*spanfunc)(*this);
|
||||
|
|
|
@ -42,11 +42,6 @@ namespace swrenderer
|
|||
class DrawerArgs
|
||||
{
|
||||
public:
|
||||
DrawerArgs();
|
||||
|
||||
bool SetPatchStyle(FRenderStyle style, fixed_t alpha, int translation, uint32_t color, FDynamicColormap *&basecolormap, fixed_t shadedlightshade = 0);
|
||||
bool SetPatchStyle(FRenderStyle style, float alpha, int translation, uint32_t color, FDynamicColormap *&basecolormap, fixed_t shadedlightshade = 0);
|
||||
|
||||
void SetColorMapLight(FSWColormap *base_colormap, float light, int shade);
|
||||
void SetTranslationMap(lighttable_t *translation);
|
||||
|
||||
|
@ -58,27 +53,7 @@ namespace swrenderer
|
|||
|
||||
SWPixelFormatDrawers *Drawers() const;
|
||||
|
||||
ColumnDrawerFunc colfunc;
|
||||
ColumnDrawerFunc basecolfunc;
|
||||
ColumnDrawerFunc fuzzcolfunc;
|
||||
ColumnDrawerFunc transcolfunc;
|
||||
|
||||
uint32_t *dc_srcblend;
|
||||
uint32_t *dc_destblend;
|
||||
fixed_t dc_srcalpha;
|
||||
fixed_t dc_destalpha;
|
||||
|
||||
int dc_color = 0;
|
||||
uint32_t dc_srccolor;
|
||||
uint32_t dc_srccolor_bgra;
|
||||
|
||||
protected:
|
||||
bool drawer_needs_pal_input = false;
|
||||
|
||||
private:
|
||||
bool SetBlendFunc(int op, fixed_t fglevel, fixed_t bglevel, int flags);
|
||||
static fixed_t GetAlpha(int type, fixed_t alpha);
|
||||
|
||||
FSWColormap *mBaseColormap = nullptr;
|
||||
float mLight = 0.0f;
|
||||
int mShade = 0;
|
||||
|
@ -108,37 +83,6 @@ namespace swrenderer
|
|||
int dc_dest_y = 0;
|
||||
};
|
||||
|
||||
class WallDrawerArgs : public DrawerArgs
|
||||
{
|
||||
public:
|
||||
void SetDest(int x, int y);
|
||||
|
||||
WallDrawerFunc GetTransMaskDrawer();
|
||||
|
||||
uint8_t *Dest() const { return dc_dest; }
|
||||
int DestY() const { return dc_dest_y; }
|
||||
|
||||
fixed_t dc_iscale;
|
||||
fixed_t dc_texturefrac;
|
||||
uint32_t dc_texturefracx;
|
||||
uint32_t dc_textureheight;
|
||||
const uint8_t *dc_source;
|
||||
const uint8_t *dc_source2;
|
||||
int dc_count;
|
||||
|
||||
int dc_wall_fracbits;
|
||||
|
||||
FVector3 dc_normal;
|
||||
FVector3 dc_viewpos;
|
||||
FVector3 dc_viewpos_step;
|
||||
TriLight *dc_lights = nullptr;
|
||||
int dc_num_lights = 0;
|
||||
|
||||
private:
|
||||
uint8_t *dc_dest = nullptr;
|
||||
int dc_dest_y = 0;
|
||||
};
|
||||
|
||||
class SpanDrawerArgs : public DrawerArgs
|
||||
{
|
||||
public:
|
||||
|
@ -152,6 +96,11 @@ namespace swrenderer
|
|||
void DrawColoredSpan(int y, int x1, int x2);
|
||||
void DrawFogBoundaryLine(int y, int x1, int x2);
|
||||
|
||||
uint32_t *dc_srcblend;
|
||||
uint32_t *dc_destblend;
|
||||
fixed_t dc_srcalpha;
|
||||
fixed_t dc_destalpha;
|
||||
|
||||
int ds_y;
|
||||
int ds_x1;
|
||||
int ds_x2;
|
||||
|
@ -177,9 +126,51 @@ namespace swrenderer
|
|||
SpanDrawerFunc spanfunc;
|
||||
};
|
||||
|
||||
class WallDrawerArgs : public DrawerArgs
|
||||
{
|
||||
public:
|
||||
void SetStyle(bool masked, bool additive, fixed_t alpha);
|
||||
void SetDest(int x, int y);
|
||||
|
||||
uint8_t *Dest() const { return dc_dest; }
|
||||
int DestY() const { return dc_dest_y; }
|
||||
|
||||
uint32_t *dc_srcblend;
|
||||
uint32_t *dc_destblend;
|
||||
fixed_t dc_srcalpha;
|
||||
fixed_t dc_destalpha;
|
||||
|
||||
fixed_t dc_iscale;
|
||||
fixed_t dc_texturefrac;
|
||||
uint32_t dc_texturefracx;
|
||||
uint32_t dc_textureheight;
|
||||
const uint8_t *dc_source;
|
||||
const uint8_t *dc_source2;
|
||||
int dc_count;
|
||||
|
||||
int dc_wall_fracbits;
|
||||
|
||||
FVector3 dc_normal;
|
||||
FVector3 dc_viewpos;
|
||||
FVector3 dc_viewpos_step;
|
||||
TriLight *dc_lights = nullptr;
|
||||
int dc_num_lights = 0;
|
||||
|
||||
WallDrawerFunc wallfunc = nullptr;
|
||||
|
||||
private:
|
||||
uint8_t *dc_dest = nullptr;
|
||||
int dc_dest_y = 0;
|
||||
};
|
||||
|
||||
class ColumnDrawerArgs : public DrawerArgs
|
||||
{
|
||||
public:
|
||||
ColumnDrawerArgs();
|
||||
|
||||
bool SetPatchStyle(FRenderStyle style, fixed_t alpha, int translation, uint32_t color, FDynamicColormap *&basecolormap, fixed_t shadedlightshade = 0);
|
||||
bool SetPatchStyle(FRenderStyle style, float alpha, int translation, uint32_t color, FDynamicColormap *&basecolormap, fixed_t shadedlightshade = 0);
|
||||
|
||||
void DrawMaskedColumn(int x, fixed_t iscale, FTexture *texture, fixed_t column, double spryscale, double sprtopscreen, bool sprflipvert, const short *mfloorclip, const short *mceilingclip, bool unmasked = false);
|
||||
void FillColumn();
|
||||
|
||||
|
@ -191,19 +182,37 @@ namespace swrenderer
|
|||
int dc_x;
|
||||
int dc_yl;
|
||||
int dc_yh;
|
||||
|
||||
fixed_t dc_iscale;
|
||||
fixed_t dc_texturefrac;
|
||||
uint32_t dc_texturefracx;
|
||||
uint32_t dc_textureheight;
|
||||
const uint8_t *dc_source;
|
||||
const uint8_t *dc_source2;
|
||||
uint32_t dc_texturefracx;
|
||||
int dc_count;
|
||||
|
||||
int dc_color = 0;
|
||||
uint32_t dc_srccolor;
|
||||
uint32_t dc_srccolor_bgra;
|
||||
|
||||
uint32_t *dc_srcblend;
|
||||
uint32_t *dc_destblend;
|
||||
fixed_t dc_srcalpha;
|
||||
fixed_t dc_destalpha;
|
||||
|
||||
ColumnDrawerFunc colfunc;
|
||||
ColumnDrawerFunc basecolfunc;
|
||||
ColumnDrawerFunc fuzzcolfunc;
|
||||
ColumnDrawerFunc transcolfunc;
|
||||
|
||||
private:
|
||||
bool SetBlendFunc(int op, fixed_t fglevel, fixed_t bglevel, int flags);
|
||||
static fixed_t GetAlpha(int type, fixed_t alpha);
|
||||
void DrawMaskedColumnBgra(int x, fixed_t iscale, FTexture *tex, fixed_t column, double spryscale, double sprtopscreen, bool sprflipvert, const short *mfloorclip, const short *mceilingclip, bool unmasked);
|
||||
|
||||
uint8_t *dc_dest = nullptr;
|
||||
int dc_dest_y = 0;
|
||||
bool drawer_needs_pal_input = false;
|
||||
};
|
||||
|
||||
void R_InitColumnDrawers();
|
||||
|
|
|
@ -932,8 +932,7 @@ namespace swrenderer
|
|||
|
||||
WallDrawerArgs drawerargs;
|
||||
|
||||
// [RH] Color if not texturing line
|
||||
drawerargs.dc_color = (((int)(curline - segs) * 8) + 4) & 255;
|
||||
drawerargs.SetStyle(false, false, OPAQUE);
|
||||
|
||||
CameraLight *cameraLight = CameraLight::Instance();
|
||||
if (cameraLight->fixedlightlev >= 0)
|
||||
|
|
|
@ -66,15 +66,15 @@ namespace swrenderer
|
|||
|
||||
curline = ds->curline;
|
||||
|
||||
FDynamicColormap *patchstylecolormap = nullptr;
|
||||
float alpha = (float)MIN(curline->linedef->alpha, 1.);
|
||||
bool additive = (curline->linedef->flags & ML_ADDTRANS) != 0;
|
||||
|
||||
WallDrawerArgs walldrawerargs;
|
||||
walldrawerargs.SetPatchStyle(LegacyRenderStyles[curline->linedef->flags & ML_ADDTRANS ? STYLE_Add : STYLE_Translucent],
|
||||
(float)MIN(curline->linedef->alpha, 1.), 0, 0, patchstylecolormap);
|
||||
walldrawerargs.SetStyle(true, additive, FLOAT2FIXED(alpha));
|
||||
|
||||
ColumnDrawerArgs columndrawerargs;
|
||||
bool visible = columndrawerargs.SetPatchStyle(LegacyRenderStyles[curline->linedef->flags & ML_ADDTRANS ? STYLE_Add : STYLE_Translucent],
|
||||
(float)MIN(curline->linedef->alpha, 1.), 0, 0, patchstylecolormap);
|
||||
FDynamicColormap *patchstylecolormap = nullptr;
|
||||
bool visible = columndrawerargs.SetPatchStyle(LegacyRenderStyles[additive ? STYLE_Add : STYLE_Translucent], alpha, 0, 0, patchstylecolormap);
|
||||
|
||||
if (!visible && !ds->bFogBoundary && !ds->bFakeBoundary)
|
||||
{
|
||||
|
@ -394,13 +394,12 @@ namespace swrenderer
|
|||
double yscale;
|
||||
|
||||
fixed_t Alpha = Scale(rover->alpha, OPAQUE, 255);
|
||||
WallDrawerArgs drawerargs;
|
||||
bool visible = drawerargs.SetPatchStyle(LegacyRenderStyles[rover->flags & FF_ADDITIVETRANS ? STYLE_Add : STYLE_Translucent],
|
||||
Alpha, 0, 0, basecolormap);
|
||||
|
||||
if (!visible)
|
||||
if (Alpha <= 0)
|
||||
return;
|
||||
|
||||
WallDrawerArgs drawerargs;
|
||||
drawerargs.SetStyle(true, (rover->flags & FF_ADDITIVETRANS) != 0, Alpha);
|
||||
|
||||
rw_lightstep = ds->lightstep;
|
||||
rw_light = ds->light + (x1 - ds->x1) * rw_lightstep;
|
||||
|
||||
|
|
|
@ -360,33 +360,7 @@ namespace swrenderer
|
|||
|
||||
void RenderWallPart::ProcessNormalWall(const short *uwal, const short *dwal, double texturemid, float *swal, fixed_t *lwal)
|
||||
{
|
||||
ProcessWallWorker(uwal, dwal, texturemid, swal, lwal, &SWPixelFormatDrawers::DrawWallColumn);
|
||||
}
|
||||
|
||||
void RenderWallPart::ProcessMaskedWall(const short *uwal, const short *dwal, double texturemid, float *swal, fixed_t *lwal)
|
||||
{
|
||||
if (!rw_pic->bMasked) // Textures that aren't masked can use the faster ProcessNormalWall.
|
||||
{
|
||||
ProcessNormalWall(uwal, dwal, texturemid, swal, lwal);
|
||||
}
|
||||
else
|
||||
{
|
||||
ProcessWallWorker(uwal, dwal, texturemid, swal, lwal, &SWPixelFormatDrawers::DrawWallMaskedColumn);
|
||||
}
|
||||
}
|
||||
|
||||
void RenderWallPart::ProcessTranslucentWall(const short *uwal, const short *dwal, double texturemid, float *swal, fixed_t *lwal)
|
||||
{
|
||||
WallDrawerFunc drawcol1 = drawerargs.GetTransMaskDrawer();
|
||||
if (drawcol1 == nullptr)
|
||||
{
|
||||
// The current translucency is unsupported, so draw with regular ProcessMaskedWall instead.
|
||||
ProcessMaskedWall(uwal, dwal, texturemid, swal, lwal);
|
||||
}
|
||||
else
|
||||
{
|
||||
ProcessWallWorker(uwal, dwal, texturemid, swal, lwal, drawcol1);
|
||||
}
|
||||
ProcessWallWorker(uwal, dwal, texturemid, swal, lwal, drawerargs.wallfunc);
|
||||
}
|
||||
|
||||
void RenderWallPart::ProcessStripedWall(const short *uwal, const short *dwal, double texturemid, float *swal, fixed_t *lwal)
|
||||
|
@ -428,19 +402,12 @@ namespace swrenderer
|
|||
|
||||
void RenderWallPart::ProcessWall(const short *uwal, const short *dwal, double texturemid, float *swal, fixed_t *lwal)
|
||||
{
|
||||
if (mask)
|
||||
// Textures that aren't masked can use the faster ProcessNormalWall.
|
||||
if (!rw_pic->bMasked && drawerargs.wallfunc == &SWPixelFormatDrawers::DrawWallMaskedColumn)
|
||||
{
|
||||
if (drawerargs.colfunc == drawerargs.basecolfunc)
|
||||
{
|
||||
ProcessMaskedWall(uwal, dwal, texturemid, swal, lwal);
|
||||
drawerargs.SetStyle(true, false, OPAQUE);
|
||||
}
|
||||
else
|
||||
{
|
||||
ProcessTranslucentWall(uwal, dwal, texturemid, swal, lwal);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
CameraLight *cameraLight = CameraLight::Instance();
|
||||
if (cameraLight->fixedcolormap != NULL || cameraLight->fixedlightlev >= 0 || !(frontsector->e && frontsector->e->XFloor.lightlist.Size()))
|
||||
{
|
||||
|
@ -451,7 +418,6 @@ namespace swrenderer
|
|||
ProcessStripedWall(uwal, dwal, texturemid, swal, lwal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
//
|
||||
|
|
|
@ -62,8 +62,6 @@ namespace swrenderer
|
|||
void ProcessWallNP2(const short *uwal, const short *dwal, double texturemid, float *swal, fixed_t *lwal, double top, double bot);
|
||||
void ProcessWall(const short *uwal, const short *dwal, double texturemid, float *swal, fixed_t *lwal);
|
||||
void ProcessStripedWall(const short *uwal, const short *dwal, double texturemid, float *swal, fixed_t *lwal);
|
||||
void ProcessTranslucentWall(const short *uwal, const short *dwal, double texturemid, float *swal, fixed_t *lwal);
|
||||
void ProcessMaskedWall(const short *uwal, const short *dwal, double texturemid, float *swal, fixed_t *lwal);
|
||||
void ProcessNormalWall(const short *uwal, const short *dwal, double texturemid, float *swal, fixed_t *lwal);
|
||||
void ProcessWallWorker(const short *uwal, const short *dwal, double texturemid, float *swal, fixed_t *lwal, WallDrawerFunc drawcolumn);
|
||||
void Draw1Column(int x, int y1, int y2, WallSampler &sampler, WallDrawerFunc draw1column);
|
||||
|
|
Loading…
Reference in a new issue