1

I have a template 2D image buffer class that can be used with many values types. The values are stored as a 1D dynamic array of T, accessed by a Row method to get a pointer to the correct row.

One of the methods of the class is used to sample a value in the image bilinearly.

The code generally works, but ever so rarely I get an access violation exception in this method in production which I can't seem to recreate, because the crash dump doesn't include the coordinates that were passed to the method.

These are the relevant parts of the code:

T* data;
int width, height;

T* Row(int y) const { return data + width * y; }

T GetValueBilinear(float x, float y) const
{
    const float PIXEL_CENTER_OFFSET = 0.5F;

    const float cx = clamp(0.0F, width - 1.0F, x - PIXEL_CENTER_OFFSET);
    const float cy = clamp(0.0F, height - 1.0F, y - PIXEL_CENTER_OFFSET);

    const float tx = fmod(cx, 1.0F);
    const float ty = fmod(cy, 1.0F);

    const int xInt = (int)cx;
    const int yInt = (int)cy;

    const T* r0 = Row(yInt);
    const T* r1 = ty && yInt < (height - 1) ? Row(yInt + 1) : r0;

    //interpolate on Y
    const T& c00 = r0[xInt];
    const T& c01 = r1[xInt];
    T c0 = lerp(c00, c01, ty);

    if (tx && xInt < (width - 1))
    {
        //interpolate on X
        const T& c10 = r0[xInt + 1];
        const T& c11 = r1[xInt + 1];
        T c1 = lerp(c10, c11, ty);
        return lerp(c0, c1, tx);
    }
    else
    {
        return c0;
    }
}

The definitions for clamp, and lerp are:

template <typename T>
inline T clamp(T min, T max, T value) { return value < min ? min : value > max ? max : value; }

template <typename T>
inline T lerp(T a, T b, float t) { return a + (b - a) * t; } //i.e. a(1-t)+bt

Do you see any obvious errors which would cause an access violation for any values of x and y which are not NaN?

You can assume that width, height and data are valid and correct (i.e., positive dimensions - in this particular case 1280x720, data is not dangling pointer).

If it matters, then T is a float in this case.

The fact that this is non-reproducible and generally working 99.9% of the time, makes me feel like it could be an accuracy issue, though I can't see where it would come from.

Alternatively, what debugging techniques could I use to analyze the crash dumps more effectively?

Rotem
  • 21,452
  • 6
  • 62
  • 109
  • Can `xInt` be negative? – Alessandro Jacopson May 25 '16 at 14:32
  • @AlessandroJacopson I don't see how if `cx` is clamped to `0.0f`. – Rotem May 25 '16 at 14:37
  • Can width or height be less than 1? Additionally, what is the behavior of `Row(yInt)`? Later on you access the returned `r0` with `xInt +1`. There's just not enough detail here. You need to step through with a debugger to see the values of your indices and the sizes of your arrays. – AndyG May 25 '16 at 14:54
  • @AndyG `width` and `height` are always positive. The `Row` function is given at the top of the code block. `r0` is the first value of a row, `r0[xInt]` is the xth value of the row, `r0[xInt + 1]` is one value after that. If I could step through with a debugger I would, unfortunately there are no meaningful values for the entire method in the two crash dumps that I received, that's why I'm asking if anyone can spot any mistakes. – Rotem May 25 '16 at 14:58
  • @Rotem Can you modify the code? If yes, is it acceptable/feasible to do a bounds checking and having `GetValueBilinear` return `T(0)` or whatever or throw an exception in case of error instead of an access violation? – Alessandro Jacopson May 25 '16 at 14:58
  • @AlessandroJacopson I can modify the code, but wouldn't I just be saying "there's a bug I can't find, let's just use a wrong value"? The function should be able to return a meaningful value for any coordinate. – Rotem May 25 '16 at 15:01
  • @Rotem: "Positive" is not sufficient. If width or height is 0.9, what is the behavior of calling clamp to generate cx and cy? – AndyG May 25 '16 at 15:06
  • @AndyG Sorry, `width` and `height` are `int`s. I realize now it's not specifically stated. – Rotem May 25 '16 at 15:07
  • @Rotem Did you try to analyze the dump with Windbg? – Alessandro Jacopson May 25 '16 at 19:23
  • @Rotem I tested your `GetValueBilinear` with 1073741824 random values for the pair (`x`,`y`) on a 1280x720 `data` with no access violation.. so I would say it is working fine 99.999999% of the time :-) I suspect the problem is not in `GetValueBilinear` but elsewhere... – Alessandro Jacopson May 26 '16 at 08:26
  • @AlessandroJacopson lol, thanks so much for taking the time! I'll give it a try with WinDbg to see if I can dig up any more data. – Rotem May 26 '16 at 08:27
  • @AlessandroJacopson WinDbg shows the same info, listing `x` as `1.863726958e-043` and `y` as `1.5612089e-038` which I suspect is junk. No other locals are available. If I ever find out the problem I'll post it. Thanks anyways. – Rotem May 26 '16 at 08:39

1 Answers1

1

I tested your GetValueBilinear with 1073741824 random values for the pair (x,y) on a 1280x720 data with no access violation.. so I would say it is working fine 99.999999%1 of the time :-) I suspect the problem is not in GetValueBilinear but elsewhere...

#include <cmath>
#include <algorithm>

template <typename T>
inline T clamp(T min, T max, T value) { return value < min ? min : value > max ? max : value; }

template <typename T>
inline T lerp(T a, T b, float t) { return a + (b - a) * t; } //i.e. a(1-t)+bt

template < typename T >
class C
{
public:
    C(int w, int h) : height(h), width(w) {
        float lower_bound = T(0);
        float upper_bound = std::nextafter(T(255), std::numeric_limits<T>::max());
        std::uniform_real_distribution<float> unif(lower_bound, upper_bound);
        std::default_random_engine re;
        data = new T[width*height];// I know... a leak! But... who cares?!
        std::generate(data, data + (width*height), [&]() {return unif(re); });
    }
    T GetValueBilinear(float x, float y) const
    {
        const float PIXEL_CENTER_OFFSET = 0.5F;

        const float cx = clamp(0.0F, width - 1.0F, x - PIXEL_CENTER_OFFSET);
        const float cy = clamp(0.0F, height - 1.0F, y - PIXEL_CENTER_OFFSET);

        const float tx = fmod(cx, 1.0F);
        const float ty = fmod(cy, 1.0F);

        const int xInt = (int)cx;
        const int yInt = (int)cy;

        const T* r0 = Row(yInt);
        const T* r1 = ty && yInt < (height - 1) ? Row(yInt + 1) : r0;

        //interpolate on Y
        const T& c00 = r0[xInt];
        const T& c01 = r1[xInt];
        T c0 = lerp(c00, c01, ty);

        if (tx && xInt < (width - 1))
        {
            //interpolate on X
            const T& c10 = r0[xInt + 1];
            const T& c11 = r1[xInt + 1];
            T c1 = lerp(c10, c11, ty);
            return lerp(c0, c1, tx);
        }
        else
        {
            return c0;
        }
    }




    T* data;
    int width, height;

    T* Row(int y) const { return data + width * y; }


};

#include <random>
#include <iostream>

#include <Windows.h>


float x;
float y;

LONG WINAPI my_filter(_In_  struct _EXCEPTION_POINTERS *ExceptionInfo)
{
    std::cout << x << " " << y << "\n";
    return EXCEPTION_EXECUTE_HANDLER;
}

int main()
{
    auto a = ::SetUnhandledExceptionFilter(my_filter);

    float lower_bound = -(1 << 20);
    float upper_bound = -lower_bound;
    std::uniform_real_distribution<float> unif(lower_bound, upper_bound);
    std::default_random_engine re;

    float acc = 0;
    C<float> img(1280, 720);

    img.GetValueBilinear(1.863726958e-043, 1.5612089e-038);

    for (size_t i = 0; i < (1 << 30); i++) {
        x = unif(re);
        y = unif(re);

        acc += img.GetValueBilinear(x, y);
    }

    return static_cast<int>(acc);
}


1Even if no access violation was found I cannot say that the algorithm works well 100%, using a naïve model and this R code:
prop.test(0,1073741824)

I get a confidence interval for the true value of the proportion, the interval is (0.000000e+00, 4.460345e-09) and so the success percentage is (1-4.460345e-09)*100, but... do not trust me, I am not a statistician!

Alessandro Jacopson
  • 18,047
  • 15
  • 98
  • 153