mirror of
https://github.com/ZDoom/gzdoom-gles.git
synced 2024-11-11 15:22:15 +00:00
- Implementing RGB666 colormatching to replace less precise RGB555 in some parts of the code.
This commit is contained in:
parent
14c282da30
commit
2ba402dc74
6 changed files with 137 additions and 2 deletions
|
@ -314,10 +314,17 @@ namespace swrenderer
|
|||
uint8_t pix = source[frac >> bits];
|
||||
if (pix != 0)
|
||||
{
|
||||
#ifdef NO_RGB666
|
||||
uint32_t fg = fg2rgb[colormap[pix]];
|
||||
uint32_t bg = bg2rgb[*dest];
|
||||
fg = (fg + bg) | 0x1f07c1f;
|
||||
*dest = RGB32k.All[fg & (fg >> 15)];
|
||||
#else
|
||||
uint32_t r = clamp(GPalette.BaseColors[colormap[pix]].r + GPalette.BaseColors[*dest].r, 0, 255);
|
||||
uint32_t g = clamp(GPalette.BaseColors[colormap[pix]].g + GPalette.BaseColors[*dest].g, 0, 255);
|
||||
uint32_t b = clamp(GPalette.BaseColors[colormap[pix]].b + GPalette.BaseColors[*dest].b, 0, 255);
|
||||
*dest = RGB256k.RGB[r>>2][g>>2][b>>2];
|
||||
#endif
|
||||
}
|
||||
frac += fracstep;
|
||||
dest += pitch;
|
||||
|
@ -357,10 +364,17 @@ namespace swrenderer
|
|||
uint8_t pix = _bufplce[i][vplce[i] >> bits];
|
||||
if (pix != 0)
|
||||
{
|
||||
#ifdef NO_RGB666
|
||||
uint32_t fg = fg2rgb[_palookupoffse[i][pix]];
|
||||
uint32_t bg = bg2rgb[dest[i]];
|
||||
fg = (fg + bg) | 0x1f07c1f;
|
||||
dest[i] = RGB32k.All[fg & (fg >> 15)];
|
||||
#else
|
||||
uint32_t r = clamp(GPalette.BaseColors[_palookupoffse[i][pix]].r + GPalette.BaseColors[dest[i]].r, 0, 255);
|
||||
uint32_t g = clamp(GPalette.BaseColors[_palookupoffse[i][pix]].g + GPalette.BaseColors[dest[i]].g, 0, 255);
|
||||
uint32_t b = clamp(GPalette.BaseColors[_palookupoffse[i][pix]].b + GPalette.BaseColors[dest[i]].b, 0, 255);
|
||||
dest[i] = RGB256k.RGB[r>>2][g>>2][b>>2];
|
||||
#endif
|
||||
}
|
||||
vplce[i] += vince[i];
|
||||
}
|
||||
|
@ -396,6 +410,7 @@ namespace swrenderer
|
|||
uint8_t pix = source[frac >> bits];
|
||||
if (pix != 0)
|
||||
{
|
||||
#ifdef NO_RGB666
|
||||
uint32_t a = fg2rgb[colormap[pix]] + bg2rgb[*dest];
|
||||
uint32_t b = a;
|
||||
|
||||
|
@ -405,6 +420,12 @@ namespace swrenderer
|
|||
b = b - (b >> 5);
|
||||
a |= b;
|
||||
*dest = RGB32k.All[a & (a >> 15)];
|
||||
#else
|
||||
uint32_t r = clamp(GPalette.BaseColors[colormap[pix]].r + GPalette.BaseColors[*dest].r, 0, 255);
|
||||
uint32_t g = clamp(GPalette.BaseColors[colormap[pix]].g + GPalette.BaseColors[*dest].g, 0, 255);
|
||||
uint32_t b = clamp(GPalette.BaseColors[colormap[pix]].b + GPalette.BaseColors[*dest].b, 0, 255);
|
||||
*dest = RGB256k.RGB[r>>2][g>>2][b>>2];
|
||||
#endif
|
||||
}
|
||||
frac += fracstep;
|
||||
dest += pitch;
|
||||
|
@ -444,6 +465,7 @@ namespace swrenderer
|
|||
uint8_t pix = _bufplce[i][vplce[i] >> bits];
|
||||
if (pix != 0)
|
||||
{
|
||||
#ifdef NO_RGB666
|
||||
uint32_t a = fg2rgb[_palookupoffse[i][pix]] + bg2rgb[dest[i]];
|
||||
uint32_t b = a;
|
||||
|
||||
|
@ -453,6 +475,12 @@ namespace swrenderer
|
|||
b = b - (b >> 5);
|
||||
a |= b;
|
||||
dest[i] = RGB32k.All[a & (a >> 15)];
|
||||
#else
|
||||
uint32_t r = clamp(GPalette.BaseColors[_palookupoffse[i][pix]].r + GPalette.BaseColors[dest[i]].r, 0, 255);
|
||||
uint32_t g = clamp(GPalette.BaseColors[_palookupoffse[i][pix]].g + GPalette.BaseColors[dest[i]].g, 0, 255);
|
||||
uint32_t b = clamp(GPalette.BaseColors[_palookupoffse[i][pix]].b + GPalette.BaseColors[dest[i]].b, 0, 255);
|
||||
dest[i] = RGB256k.RGB[r>>2][g>>2][b>>2];
|
||||
#endif
|
||||
}
|
||||
vplce[i] += vince[i];
|
||||
}
|
||||
|
@ -488,6 +516,7 @@ namespace swrenderer
|
|||
uint8_t pix = source[frac >> bits];
|
||||
if (pix != 0)
|
||||
{
|
||||
#ifdef NO_RGB666
|
||||
uint32_t a = (fg2rgb[colormap[pix]] | 0x40100400) - bg2rgb[*dest];
|
||||
uint32_t b = a;
|
||||
|
||||
|
@ -496,6 +525,12 @@ namespace swrenderer
|
|||
a &= b;
|
||||
a |= 0x01f07c1f;
|
||||
*dest = RGB32k.All[a & (a >> 15)];
|
||||
#else
|
||||
uint32_t r = clamp(-GPalette.BaseColors[colormap[pix]].r + GPalette.BaseColors[*dest].r, 0, 255);
|
||||
uint32_t g = clamp(-GPalette.BaseColors[colormap[pix]].g + GPalette.BaseColors[*dest].g, 0, 255);
|
||||
uint32_t b = clamp(-GPalette.BaseColors[colormap[pix]].b + GPalette.BaseColors[*dest].b, 0, 255);
|
||||
*dest = RGB256k.RGB[r>>2][g>>2][b>>2];
|
||||
#endif
|
||||
}
|
||||
frac += fracstep;
|
||||
dest += pitch;
|
||||
|
@ -535,6 +570,7 @@ namespace swrenderer
|
|||
uint8_t pix = _bufplce[i][vplce[i] >> bits];
|
||||
if (pix != 0)
|
||||
{
|
||||
#ifdef NO_RGB666
|
||||
uint32_t a = (fg2rgb[_palookupoffse[i][pix]] | 0x40100400) - bg2rgb[dest[i]];
|
||||
uint32_t b = a;
|
||||
|
||||
|
@ -543,6 +579,12 @@ namespace swrenderer
|
|||
a &= b;
|
||||
a |= 0x01f07c1f;
|
||||
dest[i] = RGB32k.All[a & (a >> 15)];
|
||||
#else
|
||||
uint32_t r = clamp(-GPalette.BaseColors[_palookupoffse[i][pix]].r + GPalette.BaseColors[dest[i]].r, 0, 255);
|
||||
uint32_t g = clamp(-GPalette.BaseColors[_palookupoffse[i][pix]].g + GPalette.BaseColors[dest[i]].g, 0, 255);
|
||||
uint32_t b = clamp(-GPalette.BaseColors[_palookupoffse[i][pix]].b + GPalette.BaseColors[dest[i]].b, 0, 255);
|
||||
dest[i] = RGB256k.RGB[r>>2][g>>2][b>>2];
|
||||
#endif
|
||||
}
|
||||
vplce[i] += vince[i];
|
||||
}
|
||||
|
@ -578,6 +620,7 @@ namespace swrenderer
|
|||
uint8_t pix = source[frac >> bits];
|
||||
if (pix != 0)
|
||||
{
|
||||
#ifdef NO_RGB666
|
||||
uint32_t a = (bg2rgb[*dest] | 0x40100400) - fg2rgb[colormap[pix]];
|
||||
uint32_t b = a;
|
||||
|
||||
|
@ -586,6 +629,12 @@ namespace swrenderer
|
|||
a &= b;
|
||||
a |= 0x01f07c1f;
|
||||
*dest = RGB32k.All[a & (a >> 15)];
|
||||
#else
|
||||
uint32_t r = clamp(GPalette.BaseColors[colormap[pix]].r - GPalette.BaseColors[*dest].r, 0, 255);
|
||||
uint32_t g = clamp(GPalette.BaseColors[colormap[pix]].g - GPalette.BaseColors[*dest].g, 0, 255);
|
||||
uint32_t b = clamp(GPalette.BaseColors[colormap[pix]].b - GPalette.BaseColors[*dest].b, 0, 255);
|
||||
*dest = RGB256k.RGB[r>>2][g>>2][b>>2];
|
||||
#endif
|
||||
}
|
||||
frac += fracstep;
|
||||
dest += pitch;
|
||||
|
@ -625,6 +674,7 @@ namespace swrenderer
|
|||
uint8_t pix = _bufplce[i][vplce[i] >> bits];
|
||||
if (pix != 0)
|
||||
{
|
||||
#ifdef NO_RGB666
|
||||
uint32_t a = (bg2rgb[dest[i]] | 0x40100400) - fg2rgb[_palookupoffse[i][pix]];
|
||||
uint32_t b = a;
|
||||
|
||||
|
@ -633,6 +683,12 @@ namespace swrenderer
|
|||
a &= b;
|
||||
a |= 0x01f07c1f;
|
||||
dest[i] = RGB32k.All[a & (a >> 15)];
|
||||
#else
|
||||
uint32_t r = clamp(GPalette.BaseColors[_palookupoffse[i][pix]].r - GPalette.BaseColors[dest[i]].r, 0, 255);
|
||||
uint32_t g = clamp(GPalette.BaseColors[_palookupoffse[i][pix]].g - GPalette.BaseColors[dest[i]].g, 0, 255);
|
||||
uint32_t b = clamp(GPalette.BaseColors[_palookupoffse[i][pix]].b - GPalette.BaseColors[dest[i]].b, 0, 255);
|
||||
dest[i] = RGB256k.RGB[r>>2][g>>2][b>>2];
|
||||
#endif
|
||||
}
|
||||
vplce[i] += vince[i];
|
||||
}
|
||||
|
@ -716,7 +772,11 @@ namespace swrenderer
|
|||
c_red = (c_red * alpha_bottom + solid_bottom_r * inv_alpha_bottom) >> 8;
|
||||
c_green = (c_green * alpha_bottom + solid_bottom_g * inv_alpha_bottom) >> 8;
|
||||
c_blue = (c_blue * alpha_bottom + solid_bottom_b * inv_alpha_bottom) >> 8;
|
||||
#ifdef NO_RGB666
|
||||
*dest = RGB32k.RGB[(c_red >> 3)][(c_green >> 3)][(c_blue >> 3)];
|
||||
#else
|
||||
*dest = RGB256k.RGB[(c_red >> 2)][(c_green >> 2)][(c_blue >> 2)];
|
||||
#endif
|
||||
}
|
||||
|
||||
frac += fracstep;
|
||||
|
@ -744,8 +804,13 @@ namespace swrenderer
|
|||
int solid_bottom_r = RPART(solid_bottom);
|
||||
int solid_bottom_g = GPART(solid_bottom);
|
||||
int solid_bottom_b = BPART(solid_bottom);
|
||||
#ifdef NO_RGB666
|
||||
uint32_t solid_top_fill = RGB32k.RGB[(solid_top_r >> 3)][(solid_top_g >> 3)][(solid_top_b >> 3)];
|
||||
uint32_t solid_bottom_fill = RGB32k.RGB[(solid_bottom_r >> 3)][(solid_bottom_g >> 3)][(solid_bottom_b >> 3)];
|
||||
#else
|
||||
uint32_t solid_top_fill = RGB256k.RGB[(solid_top_r >> 2)][(solid_top_g >> 2)][(solid_top_b >> 2)];
|
||||
uint32_t solid_bottom_fill = RGB256k.RGB[(solid_bottom_r >> 2)][(solid_bottom_g >> 2)][(solid_bottom_b >> 2)];
|
||||
#endif
|
||||
solid_top_fill = (solid_top_fill << 24) | (solid_top_fill << 16) | (solid_top_fill << 8) | solid_top_fill;
|
||||
solid_bottom_fill = (solid_bottom_fill << 24) | (solid_bottom_fill << 16) | (solid_bottom_fill << 8) | solid_bottom_fill;
|
||||
|
||||
|
@ -805,8 +870,11 @@ namespace swrenderer
|
|||
c_red = (c_red * alpha_top + solid_top_r * inv_alpha_top) >> 8;
|
||||
c_green = (c_green * alpha_top + solid_top_g * inv_alpha_top) >> 8;
|
||||
c_blue = (c_blue * alpha_top + solid_top_b * inv_alpha_top) >> 8;
|
||||
#ifdef NO_RGB666
|
||||
output[col] = RGB32k.RGB[(c_red >> 3)][(c_green >> 3)][(c_blue >> 3)];
|
||||
|
||||
#else
|
||||
output[col] = RGB256k.RGB[(c_red >> 2)][(c_green >> 2)][(c_blue >> 2)];
|
||||
#endif
|
||||
frac[col] += fracstep[col];
|
||||
}
|
||||
*((uint32_t*)dest) = *((uint32_t*)output);
|
||||
|
@ -847,7 +915,11 @@ namespace swrenderer
|
|||
c_red = (c_red * alpha_bottom + solid_bottom_r * inv_alpha_bottom) >> 8;
|
||||
c_green = (c_green * alpha_bottom + solid_bottom_g * inv_alpha_bottom) >> 8;
|
||||
c_blue = (c_blue * alpha_bottom + solid_bottom_b * inv_alpha_bottom) >> 8;
|
||||
#ifdef NO_RGB666
|
||||
output[col] = RGB32k.RGB[(c_red >> 3)][(c_green >> 3)][(c_blue >> 3)];
|
||||
#else
|
||||
output[col] = RGB256k.RGB[(c_red >> 2)][(c_green >> 2)][(c_blue >> 2)];
|
||||
#endif
|
||||
|
||||
frac[col] += fracstep[col];
|
||||
}
|
||||
|
@ -929,7 +1001,11 @@ namespace swrenderer
|
|||
c_red = (c_red * alpha_bottom + solid_bottom_r * inv_alpha_bottom) >> 8;
|
||||
c_green = (c_green * alpha_bottom + solid_bottom_g * inv_alpha_bottom) >> 8;
|
||||
c_blue = (c_blue * alpha_bottom + solid_bottom_b * inv_alpha_bottom) >> 8;
|
||||
#ifdef NO_RGB666
|
||||
*dest = RGB32k.RGB[(c_red >> 3)][(c_green >> 3)][(c_blue >> 3)];
|
||||
#else
|
||||
*dest = RGB256k.RGB[(c_red >> 2)][(c_green >> 2)][(c_blue >> 2)];
|
||||
#endif
|
||||
}
|
||||
|
||||
frac += fracstep;
|
||||
|
@ -959,8 +1035,13 @@ namespace swrenderer
|
|||
int solid_bottom_r = RPART(solid_bottom);
|
||||
int solid_bottom_g = GPART(solid_bottom);
|
||||
int solid_bottom_b = BPART(solid_bottom);
|
||||
#ifdef NO_RGB666
|
||||
uint32_t solid_top_fill = RGB32k.RGB[(solid_top_r >> 3)][(solid_top_g >> 3)][(solid_top_b >> 3)];
|
||||
uint32_t solid_bottom_fill = RGB32k.RGB[(solid_bottom_r >> 3)][(solid_bottom_g >> 3)][(solid_bottom_b >> 3)];
|
||||
#else
|
||||
uint32_t solid_top_fill = RGB256k.RGB[(solid_top_r >> 2)][(solid_top_g >> 2)][(solid_top_b >> 2)];
|
||||
uint32_t solid_bottom_fill = RGB256k.RGB[(solid_bottom_r >> 2)][(solid_bottom_g >> 2)][(solid_bottom_b >> 2)];
|
||||
#endif
|
||||
solid_top_fill = (solid_top_fill << 24) | (solid_top_fill << 16) | (solid_top_fill << 8) | solid_top_fill;
|
||||
solid_bottom_fill = (solid_bottom_fill << 24) | (solid_bottom_fill << 16) | (solid_bottom_fill << 8) | solid_bottom_fill;
|
||||
|
||||
|
@ -1026,7 +1107,11 @@ namespace swrenderer
|
|||
c_red = (c_red * alpha_top + solid_top_r * inv_alpha_top) >> 8;
|
||||
c_green = (c_green * alpha_top + solid_top_g * inv_alpha_top) >> 8;
|
||||
c_blue = (c_blue * alpha_top + solid_top_b * inv_alpha_top) >> 8;
|
||||
#ifdef NO_RGB666
|
||||
output[col] = RGB32k.RGB[(c_red >> 3)][(c_green >> 3)][(c_blue >> 3)];
|
||||
#else
|
||||
output[col] = RGB256k.RGB[(c_red >> 2)][(c_green >> 2)][(c_blue >> 2)];
|
||||
#endif
|
||||
|
||||
frac[col] += fracstep[col];
|
||||
}
|
||||
|
@ -1080,7 +1165,11 @@ namespace swrenderer
|
|||
c_red = (c_red * alpha_bottom + solid_bottom_r * inv_alpha_bottom) >> 8;
|
||||
c_green = (c_green * alpha_bottom + solid_bottom_g * inv_alpha_bottom) >> 8;
|
||||
c_blue = (c_blue * alpha_bottom + solid_bottom_b * inv_alpha_bottom) >> 8;
|
||||
#ifdef NO_RGB666
|
||||
output[col] = RGB32k.RGB[(c_red >> 3)][(c_green >> 3)][(c_blue >> 3)];
|
||||
#else
|
||||
output[col] = RGB256k.RGB[(c_red >> 2)][(c_green >> 2)][(c_blue >> 2)];
|
||||
#endif
|
||||
|
||||
frac[col] += fracstep[col];
|
||||
}
|
||||
|
|
|
@ -536,7 +536,12 @@ void FPNGTexture::MakeTexture ()
|
|||
{
|
||||
if (!HaveTrans)
|
||||
{
|
||||
#ifdef NO_RGB666
|
||||
*out++ = RGB32k.RGB[in[0]>>3][in[1]>>3][in[2]>>3];
|
||||
#else
|
||||
*out++ = RGB256k.RGB[in[0]>>2][in[1]>>2][in[2]>>2];
|
||||
#endif
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -548,7 +553,11 @@ void FPNGTexture::MakeTexture ()
|
|||
}
|
||||
else
|
||||
{
|
||||
#ifdef NO_RGB666
|
||||
*out++ = RGB32k.RGB[in[0]>>3][in[1]>>3][in[2]>>3];
|
||||
#else
|
||||
*out++ = RGB256k.RGB[in[0]>>2][in[1]>>2][in[2]>>2];
|
||||
#endif
|
||||
}
|
||||
}
|
||||
in += pitch;
|
||||
|
@ -593,7 +602,11 @@ void FPNGTexture::MakeTexture ()
|
|||
{
|
||||
for (y = Height; y > 0; --y)
|
||||
{
|
||||
#ifdef NO_RGB666
|
||||
*out++ = in[3] < 128 ? 0 : RGB32k.RGB[in[0]>>3][in[1]>>3][in[2]>>3];
|
||||
#else
|
||||
*out++ = in[3] < 128 ? 0 : RGB256k.RGB[in[0]>>2][in[1]>>2][in[2]>>2];
|
||||
#endif
|
||||
in += pitch;
|
||||
}
|
||||
in -= backstep;
|
||||
|
|
|
@ -405,7 +405,11 @@ void FTGATexture::MakeTexture ()
|
|||
BYTE * p = ptr + y * Pitch;
|
||||
for(int x=0;x<Width;x++)
|
||||
{
|
||||
#ifdef NO_RGB666
|
||||
Pixels[x*Height+y] = RGB32k.RGB[p[2]>>3][p[1]>>3][p[0]>>3];
|
||||
#else
|
||||
Pixels[x*Height+y] = RGB256k.RGB[p[2]>>2][p[1]>>2][p[0]>>2];
|
||||
#endif
|
||||
p+=step_x;
|
||||
}
|
||||
}
|
||||
|
@ -419,7 +423,11 @@ void FTGATexture::MakeTexture ()
|
|||
BYTE * p = ptr + y * Pitch;
|
||||
for(int x=0;x<Width;x++)
|
||||
{
|
||||
#ifdef NO_RGB666
|
||||
Pixels[x*Height+y] = RGB32k.RGB[p[2]>>3][p[1]>>3][p[0]>>3];
|
||||
#else
|
||||
Pixels[x*Height+y] = RGB256k.RGB[p[2]>>2][p[1]>>2][p[0]>>2];
|
||||
#endif
|
||||
p+=step_x;
|
||||
}
|
||||
}
|
||||
|
@ -431,7 +439,11 @@ void FTGATexture::MakeTexture ()
|
|||
BYTE * p = ptr + y * Pitch;
|
||||
for(int x=0;x<Width;x++)
|
||||
{
|
||||
#ifdef NO_RGB666
|
||||
Pixels[x*Height+y] = p[3] >= 128? RGB32k.RGB[p[2]>>3][p[1]>>3][p[0]>>3] : 0;
|
||||
#else
|
||||
Pixels[x*Height+y] = p[3] >= 128? RGB256k.RGB[p[2]>>2][p[1]>>2][p[0]>>2] : 0;
|
||||
#endif
|
||||
p+=step_x;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1031,7 +1031,7 @@ void DCanvas::PUTTRANSDOT (int xx, int yy, int basecolor, int level)
|
|||
DWORD *fg2rgb = Col2RGB8[63-level];
|
||||
DWORD fg = fg2rgb[basecolor];
|
||||
DWORD bg = bg2rgb[*spot];
|
||||
bg = (fg+bg) | 0x1f07c1f;
|
||||
bg = (fg+bg) | 0x01f07c1f;
|
||||
*spot = RGB32k.All[bg&(bg>>15)];
|
||||
}
|
||||
|
||||
|
|
|
@ -144,8 +144,12 @@ DWORD Col2RGB8[65][256];
|
|||
DWORD *Col2RGB8_LessPrecision[65];
|
||||
DWORD Col2RGB8_Inverse[65][256];
|
||||
ColorTable32k RGB32k;
|
||||
#ifndef NO_RGB666
|
||||
ColorTable256k RGB256k;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
static DWORD Col2RGB8_2[63][256];
|
||||
|
||||
// [RH] The framebuffer is no longer a mere byte array.
|
||||
|
@ -669,6 +673,13 @@ static void BuildTransTable (const PalEntry *palette)
|
|||
for (g = 0; g < 32; g++)
|
||||
for (b = 0; b < 32; b++)
|
||||
RGB32k.RGB[r][g][b] = ColorMatcher.Pick ((r<<3)|(r>>2), (g<<3)|(g>>2), (b<<3)|(b>>2));
|
||||
#ifndef NO_RGB666
|
||||
// create the RGB666 lookup table
|
||||
for (r = 0; r < 64; r++)
|
||||
for (g = 0; g < 64; g++)
|
||||
for (b = 0; b < 64; b++)
|
||||
RGB256k.RGB[r][g][b] = ColorMatcher.Pick ((r<<2)|(r>>4), (g<<2)|(g>>4), (b<<2)|(b>>4));
|
||||
#endif
|
||||
|
||||
int x, y;
|
||||
|
||||
|
|
|
@ -462,6 +462,16 @@ union ColorTable32k
|
|||
};
|
||||
extern "C" ColorTable32k RGB32k;
|
||||
|
||||
// [SP] RGB666 support
|
||||
#ifndef NO_RGB666
|
||||
union ColorTable256k
|
||||
{
|
||||
BYTE RGB[64][64][64];
|
||||
BYTE All[64 *64 *64];
|
||||
};
|
||||
extern "C" ColorTable256k RGB256k;
|
||||
#endif
|
||||
|
||||
// Col2RGB8 is a pre-multiplied palette for color lookup. It is stored in a
|
||||
// special R10B10G10 format for efficient blending computation.
|
||||
// --RRRRRrrr--BBBBBbbb--GGGGGggg-- at level 64
|
||||
|
|
Loading…
Reference in a new issue