Prevent overflow when scaling textures

Fixes KartKrew/Kart#844 which is caused by scaling the texheight by a
spryscale that is too large, triggering arithmetic overflow before the
overflow check even occurs. This is a performance hit, but should not be
very intense with release optimizations.

Instead, we use saturating arithmetic using the same integer promotion
technique as before, but checking in both directions, and also checking
for the multiplication before the addition.

There is an optimization opportunity here and anywhere that overflow
checks are used, by using compiler intrinsics which check the overflow
flag bit on the CPU instead of using integer promotion.
This commit is contained in:
Eidolon 2024-01-28 12:57:00 -06:00
parent b323c6bdb7
commit 6648e55972

View file

@ -11,6 +11,8 @@
/// \file r_segs.c
/// \brief All the clipping: columns, horizontal spans, sky columns
#include <limits>
#include <tracy/tracy/Tracy.hpp>
#include "command.h"
@ -717,16 +719,45 @@ void R_RenderMaskedSegRange(drawseg_t *drawseg, INT32 x1, INT32 x2)
R_SetColumnFunc(BASEDRAWFUNC, false);
}
template <typename T>
static constexpr T saturating_add(T x, T y) noexcept
{
INT64 z;
z = static_cast<INT64>(x) + static_cast<INT64>(y);
if (z > static_cast<INT64>(std::numeric_limits<T>::max()))
{
z = static_cast<INT64>(std::numeric_limits<T>::max());
}
else if (z < static_cast<INT64>(std::numeric_limits<T>::min()))
{
z = static_cast<INT64>(std::numeric_limits<T>::min());
}
return static_cast<T>(z);
}
template <typename T>
static constexpr T saturating_mul(T x, T y) noexcept
{
INT64 z;
z = static_cast<INT64>(x) * static_cast<INT64>(y);
if (z > static_cast<INT64>(std::numeric_limits<T>::max()))
{
z = static_cast<INT64>(std::numeric_limits<T>::max());
}
else if (z < static_cast<INT64>(std::numeric_limits<T>::min()))
{
z = static_cast<INT64>(std::numeric_limits<T>::min());
}
return static_cast<T>(z);
}
// Loop through R_DrawMaskedColumn calls
static void R_DrawRepeatMaskedColumn(drawcolumndata_t* dc, column_t *col, column_t *bm, INT32 baseclip)
{
while (sprtopscreen < sprbotscreen)
{
R_DrawMaskedColumn(dc, col, bm, baseclip);
if ((INT64)sprtopscreen + dc->texheight*spryscale > (INT64)INT32_MAX) // prevent overflow
sprtopscreen = INT32_MAX;
else
sprtopscreen += dc->texheight*spryscale;
sprtopscreen = saturating_add(sprtopscreen, saturating_mul(dc->texheight, spryscale));
}
}
@ -734,7 +765,7 @@ static void R_DrawRepeatFlippedMaskedColumn(drawcolumndata_t* dc, column_t *col,
{
do {
R_DrawFlippedMaskedColumn(dc, col, bm, baseclip);
sprtopscreen += dc->texheight*spryscale;
sprtopscreen = saturating_add(sprtopscreen, saturating_mul(dc->texheight, spryscale));
} while (sprtopscreen < sprbotscreen);
}