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)
GCC has since added -Og to bring the total to 8. From the man page: -O0 (do no optimization, the default if no optimization level is specified) -O1 (optimize minimally)
GCC has a range of optimization levels, plus individual options to enable or disable particular optimizations. The overall compiler optimization level is controlled by the command line option -On, where n is the required optimization level, as follows: -O0 . (default).
6.4 Optimization levels. In order to control compilation-time and compiler memory usage, and the trade-offs between speed and space for the resulting executable, GCC provides a range of general optimization levels, numbered from 0--3, as well as individual options for specific types of optimization.
The compiler optimizes to reduce the size of the binary instead of execution speed. If you do not specify an optimization option, gcc attempts to reduce the compilation time and to make debugging always yield the result expected from reading the source code.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With