I am working on a rewrite of a raytracer that I developed for university last semester and I am running into the following problem: When I compile and run my code in Debug the output is as expected
But when I enable higher optimization levels e.g. "-O2" something completely different is the result:
And I am not sure why this happens. I tracked it down to the sphere intersection code
//#pragma GCC push_options
//#pragma GCC optimize("O0")
Intersection Sphere::intersect(const Ray& ray, const float previous) const
{
const auto oc = ray.origin - center_;
const auto lhv = -dot(ray.direction, oc);
const auto discriminant = lhv * lhv - (oc.lensqr() - radius_ * radius_);
if (discriminant < 0.0F)
{
return Intersection::failure();
}
float distance;
const auto rhv = std::sqrt(discriminant);
const auto r = std::minmax(lhv + rhv, lhv - rhv);
if (r.first <= 0.0F)
{
if (r.second <= 0.0F)
{
return Intersection::failure();
}
distance = r.second;
}
else
{
distance = r.first;
}
const auto hit = ray.getPoint(distance);
const auto normal = (hit - center_).normalize();
if (0.0F <= distance && distance < previous - epsilon)
{
return {distance, ray, this, normal, hit};
}
return Intersection::failure();
}
//#pragma GCC pop_options
If I uncomment the pragma in release mode, I get the expected result again. Maybe I have some undefined behaviour in my code that leads to this?
You can also have a look here as a minimal reproducible example is not easily possible. https://github.com/Yamahari/RayTracer/blob/master/rt/solid/Sphere.cpp
(You can also clone the repo and build the project with cmake, you only need SFML as a dependency.
Use -DSFML_INCLUDE_DIR="include_dir" and -DSFML_LIB_DIR="lib_dir" with the sfml library compiled with your desired compiler)
std::minmax
returns a pair of references to its arguments. If you call it with prvalues as arguments, the references in this pair will be dangling after the end of the full-expression, which means that the access r.first
in
if (r.first <= 0.0F)
will have undefined behavior here.
So store lhv + rhv
and lhv - rhv
in variables and call std::minmax
on them or use
std::minmax({lhv + rhv, lhv - rhv})
to select the std::initializer_list
overload, which returns a pair of actual values.
As noted by @Jarod42 in the comments, you don't actually need std::minmax
here. rhv
is the result of a std::sqrt
call, so it is always non-negative, making lhv + rhv
always the bigger (or equal) value.