diff --git a/src/r_draw.cpp b/src/r_draw.cpp index 80b91ed2d..5f592223b 100644 --- a/src/r_draw.cpp +++ b/src/r_draw.cpp @@ -110,6 +110,8 @@ DWORD vplce[4]; DWORD vince[4]; BYTE* palookupoffse[4]; const BYTE* bufplce[4]; +const BYTE* bufplce2[4]; +uint32_t bufheight[4]; // just for profiling int dccount; @@ -2142,6 +2144,245 @@ void tmvline4_revsubclamp () } while (--count); } +void R_DrawSingleSkyCol1(uint32_t solid_top, uint32_t solid_bottom) +{ + uint8_t *dest = dc_dest; + int count = dc_count; + int pitch = dc_pitch; + const uint8_t *source0 = bufplce[0]; + int textureheight0 = bufheight[0]; + + int32_t frac = vplce[0]; + int32_t fracstep = vince[0]; + + int start_fade = 2; // How fast it should fade out + + int solid_top_r = RPART(solid_top); + int solid_top_g = GPART(solid_top); + int solid_top_b = BPART(solid_top); + int solid_bottom_r = RPART(solid_bottom); + int solid_bottom_g = GPART(solid_bottom); + int solid_bottom_b = BPART(solid_bottom); + + for (int index = 0; index < count; index++) + { + uint32_t sample_index = (((((uint32_t)frac) << 8) >> FRACBITS) * textureheight0) >> FRACBITS; + uint8_t fg = source0[sample_index]; + + int alpha_top = MAX(MIN(frac >> (16 - start_fade), 256), 0); + int alpha_bottom = MAX(MIN(((2 << 24) - frac) >> (16 - start_fade), 256), 0); + + if (alpha_top == 256 && alpha_bottom == 256) + { + *dest = fg; + } + else + { + int inv_alpha_top = 256 - alpha_top; + int inv_alpha_bottom = 256 - alpha_bottom; + + const auto &c = GPalette.BaseColors[fg]; + int c_red = c.r; + int c_green = c.g; + int c_blue = c.b; + 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; + 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; + *dest = RGB32k.RGB[(c_red >> 3)][(c_green >> 3)][(c_blue >> 3)]; + } + + frac += fracstep; + dest += pitch; + } +} + +void R_DrawSingleSkyCol4(uint32_t solid_top, uint32_t solid_bottom) +{ + for (int col = 0; col < 4; col++) + { + uint8_t *dest = dc_dest + col; + int count = dc_count; + int pitch = dc_pitch; + const uint8_t *source0 = bufplce[col]; + int textureheight0 = bufheight[0]; + + int32_t frac = vplce[col]; + int32_t fracstep = vince[col]; + + int start_fade = 2; // How fast it should fade out + + int solid_top_r = RPART(solid_top); + int solid_top_g = GPART(solid_top); + int solid_top_b = BPART(solid_top); + int solid_bottom_r = RPART(solid_bottom); + int solid_bottom_g = GPART(solid_bottom); + int solid_bottom_b = BPART(solid_bottom); + + for (int index = 0; index < count; index++) + { + uint32_t sample_index = (((((uint32_t)frac) << 8) >> FRACBITS) * textureheight0) >> FRACBITS; + uint8_t fg = source0[sample_index]; + + int alpha_top = MAX(MIN(frac >> (16 - start_fade), 256), 0); + int alpha_bottom = MAX(MIN(((2 << 24) - frac) >> (16 - start_fade), 256), 0); + + if (alpha_top == 256 && alpha_bottom == 256) + { + *dest = fg; + } + else + { + int inv_alpha_top = 256 - alpha_top; + int inv_alpha_bottom = 256 - alpha_bottom; + + const auto &c = GPalette.BaseColors[fg]; + int c_red = c.r; + int c_green = c.g; + int c_blue = c.b; + 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; + 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; + *dest = RGB32k.RGB[(c_red >> 3)][(c_green >> 3)][(c_blue >> 3)]; + } + + frac += fracstep; + dest += pitch; + } + } +} + +void R_DrawDoubleSkyCol1(uint32_t solid_top, uint32_t solid_bottom) +{ + uint8_t *dest = dc_dest; + int count = dc_count; + int pitch = dc_pitch; + const uint8_t *source0 = bufplce[0]; + const uint8_t *source1 = bufplce2[0]; + int textureheight0 = bufheight[0]; + uint32_t maxtextureheight1 = bufheight[1] - 1; + + int32_t frac = vplce[0]; + int32_t fracstep = vince[0]; + + int start_fade = 2; // How fast it should fade out + + int solid_top_r = RPART(solid_top); + int solid_top_g = GPART(solid_top); + int solid_top_b = BPART(solid_top); + int solid_bottom_r = RPART(solid_bottom); + int solid_bottom_g = GPART(solid_bottom); + int solid_bottom_b = BPART(solid_bottom); + + for (int index = 0; index < count; index++) + { + uint32_t sample_index = (((((uint32_t)frac) << 8) >> FRACBITS) * textureheight0) >> FRACBITS; + uint8_t fg = source0[sample_index]; + if (fg == 0) + { + uint32_t sample_index2 = MIN(sample_index, maxtextureheight1); + fg = source1[sample_index2]; + } + + int alpha_top = MAX(MIN(frac >> (16 - start_fade), 256), 0); + int alpha_bottom = MAX(MIN(((2 << 24) - frac) >> (16 - start_fade), 256), 0); + + if (alpha_top == 256 && alpha_bottom == 256) + { + *dest = fg; + } + else + { + int inv_alpha_top = 256 - alpha_top; + int inv_alpha_bottom = 256 - alpha_bottom; + + const auto &c = GPalette.BaseColors[fg]; + int c_red = c.r; + int c_green = c.g; + int c_blue = c.b; + 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; + 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; + *dest = RGB32k.RGB[(c_red >> 3)][(c_green >> 3)][(c_blue >> 3)]; + } + + frac += fracstep; + dest += pitch; + } +} + +void R_DrawDoubleSkyCol4(uint32_t solid_top, uint32_t solid_bottom) +{ + for (int col = 0; col < 4; col++) + { + uint8_t *dest = dc_dest + col; + int count = dc_count; + int pitch = dc_pitch; + const uint8_t *source0 = bufplce[col]; + const uint8_t *source1 = bufplce2[col]; + int textureheight0 = bufheight[0]; + uint32_t maxtextureheight1 = bufheight[1] - 1; + + int32_t frac = vplce[col]; + int32_t fracstep = vince[col]; + + int start_fade = 2; // How fast it should fade out + + int solid_top_r = RPART(solid_top); + int solid_top_g = GPART(solid_top); + int solid_top_b = BPART(solid_top); + int solid_bottom_r = RPART(solid_bottom); + int solid_bottom_g = GPART(solid_bottom); + int solid_bottom_b = BPART(solid_bottom); + + for (int index = 0; index < count; index++) + { + uint32_t sample_index = (((((uint32_t)frac) << 8) >> FRACBITS) * textureheight0) >> FRACBITS; + uint8_t fg = source0[sample_index]; + if (fg == 0) + { + uint32_t sample_index2 = MIN(sample_index, maxtextureheight1); + fg = source1[sample_index2]; + } + + int alpha_top = MAX(MIN(frac >> (16 - start_fade), 256), 0); + int alpha_bottom = MAX(MIN(((2 << 24) - frac) >> (16 - start_fade), 256), 0); + + if (alpha_top == 256 && alpha_bottom == 256) + { + *dest = fg; + } + else + { + int inv_alpha_top = 256 - alpha_top; + int inv_alpha_bottom = 256 - alpha_bottom; + + const auto &c = GPalette.BaseColors[fg]; + int c_red = c.r; + int c_green = c.g; + int c_blue = c.b; + 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; + 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; + *dest = RGB32k.RGB[(c_red >> 3)][(c_green >> 3)][(c_blue >> 3)]; + } + + frac += fracstep; + dest += pitch; + } + } +} //========================================================================== // diff --git a/src/r_draw.h b/src/r_draw.h index cb2f68f33..c45700a21 100644 --- a/src/r_draw.h +++ b/src/r_draw.h @@ -51,6 +51,8 @@ extern "C" DWORD vplce[4]; extern "C" DWORD vince[4]; extern "C" BYTE* palookupoffse[4]; extern "C" const BYTE* bufplce[4]; +extern "C" const BYTE* bufplce2[4]; +extern "C" uint32_t bufheight[4]; // [RH] Temporary buffer for column drawing extern "C" BYTE *dc_temp; @@ -293,4 +295,9 @@ void maskwallscan (int x1, int x2, short *uwal, short *dwal, float *swal, fixed_ // transmaskwallscan is like maskwallscan, but it can also blend to the background void transmaskwallscan (int x1, int x2, short *uwal, short *dwal, float *swal, fixed_t *lwal, double yrepeat, const BYTE *(*getcol)(FTexture *tex, int col)=R_GetColumn); +void R_DrawSingleSkyCol1(uint32_t solid_top, uint32_t solid_bottom); +void R_DrawSingleSkyCol4(uint32_t solid_top, uint32_t solid_bottom); +void R_DrawDoubleSkyCol1(uint32_t solid_top, uint32_t solid_bottom); +void R_DrawDoubleSkyCol4(uint32_t solid_top, uint32_t solid_bottom); + #endif diff --git a/src/r_plane.cpp b/src/r_plane.cpp index 07001bf1b..8c12b3ca2 100644 --- a/src/r_plane.cpp +++ b/src/r_plane.cpp @@ -63,6 +63,8 @@ #pragma warning(disable:4244) #endif +CVAR(Bool, r_capsky, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG); + //EXTERN_CVAR (Int, tx) //EXTERN_CVAR (Int, ty) @@ -898,8 +900,171 @@ static const BYTE *R_GetTwoSkyColumns (FTexture *fronttex, int x) return composite; } +static void R_DrawSkyColumnStripe(int start_x, int y1, int y2, int columns, double scale, double texturemid, double yrepeat) +{ + uint32_t height = frontskytex->GetHeight(); + + for (int i = 0; i < columns; i++) + { + double uv_stepd = skyiscale * yrepeat; + double v = (texturemid + uv_stepd * (y1 - CenterY + 0.5)) / height; + double v_step = uv_stepd / height; + + uint32_t uv_pos = (uint32_t)(v * 0x01000000); + uint32_t uv_step = (uint32_t)(v_step * 0x01000000); + + int x = start_x + i; + if (MirrorFlags & RF_XFLIP) + x = (viewwidth - x); + + DWORD ang, angle1, angle2; + + ang = (skyangle + xtoviewangle[x]) ^ skyflip; + angle1 = (DWORD)((UMulScale16(ang, frontcyl) + frontpos) >> FRACBITS); + angle2 = (DWORD)((UMulScale16(ang, backcyl) + backpos) >> FRACBITS); + + bufplce[i] = (const BYTE *)frontskytex->GetColumn(angle1, nullptr); + bufplce2[i] = backskytex ? (const BYTE *)backskytex->GetColumn(angle2, nullptr) : nullptr; + + vince[i] = uv_step; + vplce[i] = uv_pos; + } + + bufheight[0] = height; + bufheight[1] = backskytex ? backskytex->GetHeight() : height; + dc_dest = (ylookup[y1] + start_x) + dc_destorg; + dc_count = y2 - y1; + + uint32_t solid_top = frontskytex->GetSWSkyCapColor(false); + uint32_t solid_bottom = frontskytex->GetSWSkyCapColor(true); + + if (columns == 4) + if (!backskytex) + R_DrawSingleSkyCol4(solid_top, solid_bottom); + else + R_DrawDoubleSkyCol4(solid_top, solid_bottom); + else + if (!backskytex) + R_DrawSingleSkyCol1(solid_top, solid_bottom); + else + R_DrawDoubleSkyCol1(solid_top, solid_bottom); +} + +static void R_DrawSkyColumn(int start_x, int y1, int y2, int columns) +{ + if (1 << frontskytex->HeightBits == frontskytex->GetHeight()) + { + double texturemid = skymid * frontskytex->Scale.Y + frontskytex->GetHeight(); + R_DrawSkyColumnStripe(start_x, y1, y2, columns, frontskytex->Scale.Y, texturemid, frontskytex->Scale.Y); + } + else + { + double yrepeat = frontskytex->Scale.Y; + double scale = frontskytex->Scale.Y * skyscale; + double iscale = 1 / scale; + short drawheight = short(frontskytex->GetHeight() * scale); + double topfrac = fmod(skymid + iscale * (1 - CenterY), frontskytex->GetHeight()); + if (topfrac < 0) topfrac += frontskytex->GetHeight(); + double texturemid = topfrac - iscale * (1 - CenterY); + R_DrawSkyColumnStripe(start_x, y1, y2, columns, scale, texturemid, yrepeat); + } +} + +static void R_DrawCapSky(visplane_t *pl) +{ + int x1 = pl->left; + int x2 = pl->right; + short *uwal = (short *)pl->top; + short *dwal = (short *)pl->bottom; + + // Calculate where 4 column alignment begins and ends: + int aligned_x1 = clamp((x1 + 3) / 4 * 4, x1, x2); + int aligned_x2 = clamp(x2 / 4 * 4, x1, x2); + + // First unaligned columns: + for (int x = x1; x < aligned_x1; x++) + { + int y1 = uwal[x]; + int y2 = dwal[x]; + if (y2 <= y1) + continue; + + R_DrawSkyColumn(x, y1, y2, 1); + } + + // The aligned columns + for (int x = aligned_x1; x < aligned_x2; x += 4) + { + // Find y1, y2, light and uv values for four columns: + int y1[4] = { uwal[x], uwal[x + 1], uwal[x + 2], uwal[x + 3] }; + int y2[4] = { dwal[x], dwal[x + 1], dwal[x + 2], dwal[x + 3] }; + + // Figure out where we vertically can start and stop drawing 4 columns in one go + int middle_y1 = y1[0]; + int middle_y2 = y2[0]; + for (int i = 1; i < 4; i++) + { + middle_y1 = MAX(y1[i], middle_y1); + middle_y2 = MIN(y2[i], middle_y2); + } + + // If we got an empty column in our set we cannot draw 4 columns in one go: + bool empty_column_in_set = false; + for (int i = 0; i < 4; i++) + { + if (y2[i] <= y1[i]) + empty_column_in_set = true; + } + if (empty_column_in_set || middle_y2 <= middle_y1) + { + for (int i = 0; i < 4; i++) + { + if (y2[i] <= y1[i]) + continue; + + R_DrawSkyColumn(x + i, y1[i], y2[i], 1); + } + continue; + } + + // Draw the first rows where not all 4 columns are active + for (int i = 0; i < 4; i++) + { + if (y1[i] < middle_y1) + R_DrawSkyColumn(x + i, y1[i], middle_y1, 1); + } + + // Draw the area where all 4 columns are active + R_DrawSkyColumn(x, middle_y1, middle_y2, 4); + + // Draw the last rows where not all 4 columns are active + for (int i = 0; i < 4; i++) + { + if (middle_y2 < y2[i]) + R_DrawSkyColumn(x + i, middle_y2, y2[i], 1); + } + } + + // The last unaligned columns: + for (int x = aligned_x2; x < x2; x++) + { + int y1 = uwal[x]; + int y2 = dwal[x]; + if (y2 <= y1) + continue; + + R_DrawSkyColumn(x, y1, y2, 1); + } +} + static void R_DrawSky (visplane_t *pl) { + if (r_capsky) + { + R_DrawCapSky(pl); + return; + } + int x; float swal; diff --git a/src/textures/texture.cpp b/src/textures/texture.cpp index 7b90c295f..f3c9e19a1 100644 --- a/src/textures/texture.cpp +++ b/src/textures/texture.cpp @@ -45,6 +45,7 @@ #include "v_video.h" #include "m_fixed.h" #include "textures/textures.h" +#include "v_palette.h" typedef bool (*CheckFunc)(FileReader & file); typedef FTexture * (*CreateFunc)(FileReader & file, int lumpnum); @@ -569,6 +570,78 @@ void FTexture::SetScaledSize(int fitwidth, int fitheight) if (int(Scale.Y * fitheight) != Height) Scale.Y += (1 / 65536.); } +//=========================================================================== +// +// Gets the average color of a texture for use as a sky cap color +// +//=========================================================================== + +namespace +{ + PalEntry averageColor(const DWORD *data, int size, int maxout) + { + int i; + unsigned int r, g, b; + + // First clear them. + r = g = b = 0; + if (size == 0) + { + return PalEntry(255, 255, 255); + } + for (i = 0; i < size; i++) + { + r += BPART(data[i]); + g += GPART(data[i]); + b += RPART(data[i]); + } + + r = r / size; + g = g / size; + b = b / size; + + int maxv = MAX(MAX(r, g), b); + + if (maxv && maxout) + { + r = Scale(r, maxout, maxv); + g = Scale(g, maxout, maxv); + b = Scale(b, maxout, maxv); + } + return PalEntry(255, r, g, b); + } +} + +PalEntry FTexture::GetSWSkyCapColor(bool bottom) +{ + PalEntry col; + int w; + int h; + + if (!bSWSkyColorDone) + { + bSWSkyColorDone = true; + + FBitmap bitmap; + bitmap.Create(GetWidth(), GetHeight()); + CopyTrueColorPixels(&bitmap, 0, 0); + int w = GetWidth(); + int h = GetHeight(); + + const uint32_t *buffer = (const uint32_t *)bitmap.GetPixels(); + if (buffer) + { + SWCeilingSkyColor = averageColor((DWORD *)buffer, w * MIN(30, h), 0); + if (h>30) + { + SWFloorSkyColor = averageColor(((DWORD *)buffer) + (h - 30)*w, w * 30, 0); + } + else SWFloorSkyColor = SWCeilingSkyColor; + } + } + return bottom ? SWFloorSkyColor : SWCeilingSkyColor; +} + FDummyTexture::FDummyTexture () { diff --git a/src/textures/textures.h b/src/textures/textures.h index 298b169f2..9402417f7 100644 --- a/src/textures/textures.h +++ b/src/textures/textures.h @@ -239,6 +239,7 @@ public: } void SetScaledSize(int fitwidth, int fitheight); + PalEntry GetSWSkyCapColor(bool bottom); virtual void HackHack (int newheight); // called by FMultipatchTexture to discover corrupt patches. @@ -259,6 +260,11 @@ protected: Rotations = other->Rotations; } +private: + bool bSWSkyColorDone = false; + PalEntry SWFloorSkyColor; + PalEntry SWCeilingSkyColor; + public: static void FlipSquareBlock (BYTE *block, int x, int y); static void FlipSquareBlockRemap (BYTE *block, int x, int y, const BYTE *remap); diff --git a/wadsrc/static/language.enu b/wadsrc/static/language.enu index 50d14ab75..5a10527ac 100644 --- a/wadsrc/static/language.enu +++ b/wadsrc/static/language.enu @@ -1787,6 +1787,7 @@ DSPLYMNU_PICKUPFADE = "Pickup Flash Intensity"; DSPLYMNU_PALLETEHACK = "DirectDraw palette hack"; // Not used DSPLYMNU_ATTACHEDSURFACES = "Use attached surfaces"; // Not used DSPLYMNU_STRETCHSKY = "Stretch short skies"; +DSPLYMNU_CAPSKY = "Cap skies with solid color"; DSPLYMNU_DRAWFUZZ = "Use fuzz effect"; DSPLYMNU_TRANSSOUL = "Lost Soul translucency"; DSPLYMNU_FAKECONTRAST = "Use fake contrast"; diff --git a/wadsrc/static/menudef.txt b/wadsrc/static/menudef.txt index cd004bb0d..f7131be55 100644 --- a/wadsrc/static/menudef.txt +++ b/wadsrc/static/menudef.txt @@ -674,6 +674,7 @@ OptionMenu "VideoOptions" //Option "$DSPLYMNU_ATTACHEDSURFACES", "vid_attachedsurfaces", "OnOff" } + Option "$DSPLYMNU_CAPSKY", "r_capsky", "OnOff" Option "$DSPLYMNU_STRETCHSKY", "r_stretchsky", "OnOff" Option "$DSPLYMNU_DRAWFUZZ", "r_drawfuzz", "Fuzziness" Slider "$DSPLYMNU_TRANSSOUL", "transsouls", 0.25, 1.0, 0.05, 2