Я вижу три улучшения, которые вы могли бы сделать.
Первая большая проблема (которая сложная) заключается в том, что у вас есть много условных веток в вашем коде, которые наверняка замедлят ваш процессор, поскольку он не может хорошо предсказать путь к коду (что также верно при компиляции). Например, я вижу, что вы сначала пересекаете, а затем проверяете, является ли узел листом, затем выполняете пересечение со всеми простыми числами. Не могли бы вы сначала проверить, если это лист, а затем сделать правильное пересечение? Это немного уменьшило бы ветвление.
Во-вторых, какова структура памяти вашего BVH? Не могли бы вы оптимизировать его, чтобы сделать его дружественным для вас? Вы могли бы попытаться посмотреть на количество пропусков кэша, которое происходит во время вашего обхода, что было бы хорошим показателем того, имеет ли ваша память правильную структуру или нет. Хотя это и не связано напрямую, теперь приятно, что ваша платформа и базовое оборудование. Я рекомендую прочитать this .
Наконец, и именно здесь вы окажете наибольшее влияние на производительность, используйте SSE / AVX! С помощью небольшого рефакторинга в вашем коде пересечения вы можете пересекать четыре ограничивающие рамки одновременно и, следовательно, иметь хороший импульс в вашем приложении. Вы могли бы взглянуть на то, что делает embree (intel tracer), особенно в математической библиотеке.
Кроме того, я только что увидел, что вы используете double
. Есть ли причина для этого? Наш pathtracer вообще не использует double, так как на самом деле не существует случаев, когда вам нужна такая точность для рендеринга.
Надеюсь, это поможет!
РЕДАКТИРОВАТЬ: я сделал sse версия вашего пересечения bbox, если вы хотите попробовать это. Он частично основан на нашем коде, но я не совсем уверен, сработает ли он, вы должны протестировать его и протестировать!
#include <xmmintrin.h>
#include <emmintrin.h>
#include <smmintrin.h>
#include <cmath>
#include <limits>
constexpr float pos_inf = std::numeric_limits<float>::max();
constexpr float neg_inf = std::numeric_limits<float>::min();
size_t __bsf(size_t v)
{
size_t r = 0; asm ("bsf %1,%0" : "=r"(r) : "r"(v));
return r;
}
__m128 mini(const __m128 a, const __m128 b)
{
return _mm_castsi128_ps(_mm_min_epi32(_mm_castps_si128(a),_mm_castps_si128(b)));
}
__m128 maxi(const __m128 a, const __m128 b)
{
return _mm_castsi128_ps(_mm_max_epi32(_mm_castps_si128(a),_mm_castps_si128(b)));
}
__m128 abs(const __m128 a)
{
return _mm_andnot_ps(_mm_set1_ps(-0.0f), a);
}
__m128 select(const __m128 mask, const __m128 t, const __m128 f)
{
return _mm_blendv_ps(f, t, mask);
}
template<size_t i0, size_t i1, size_t i2, size_t i3>
__m128 shuffle(const __m128 b)
{
return _mm_castsi128_ps(_mm_shuffle_epi32(_mm_castps_si128(b), _MM_SHUFFLE(i3, i2, i1, i0)));
}
__m128 min(const __m128 a, const __m128 b) { return _mm_min_ps(a, b); }
__m128 max(const __m128 a, const __m128 b) { return _mm_max_ps(a, b); }
__m128 vreduce_min(const __m128 v)
{
__m128 h = min(shuffle<1,0,3,2>(v),v);
return min(shuffle<2,3,0,1>(h),h);
}
__m128 vreduce_max(const __m128 v)
{
__m128 h = max(shuffle<1,0,3,2>(v),v);
return max(shuffle<2,3,0,1>(h),h);
}
size_t select_min(__m128 valid, __m128 v)
{
const __m128 a = select(valid, v, _mm_set_ps1(pos_inf));
return __bsf(_mm_movemask_ps(_mm_and_ps(valid, (a == vreduce_min(a)))));
}
size_t select_max(const __m128 valid, const __m128 v)
{
const __m128 a = select(valid, v, _mm_set_ps1(neg_inf));
return __bsf(_mm_movemask_ps(_mm_and_ps(valid, (a == vreduce_max(a)))));
}
struct Ray
{
vec3 o, inv_d;
float min_t, max_t;
};
struct BBox
{
vec3 min, max;
bool intersect(const Ray& r) const;
};
bool BBox::intersect(const Ray& r) const
{
const __m128 lowerSlab = _mm_mul_ps(_mm_sub_ps(max.m128, r.o.m128), r.inv_d.m128);
const __m128 upperSlab = _mm_mul_ps(_mm_sub_ps(min.m128, r.o.m128), r.inv_d.m128);
__m128 tmin = mini(lowerSlab, upperSlab);
__m128 tmax = maxi(lowerSlab, upperSlab);
reinterpret_cast<float*>(&tmin)[3] = r.min_t;
reinterpret_cast<float*>(&tmax)[3] = r.max_t;
const __m128 maskmin = _mm_castsi128_ps(_mm_cmpeq_epi32(tmin, tmin));
const __m128 maskmax = _mm_castsi128_ps(_mm_cmpeq_epi32(tmax, tmax));
const float tNear = abs(tmin[select_max(maskmin, tmin)]); // select the max non NaN value and ensure the result is positive using abs
const float tFar = tmax[select_min(maskmax, tmax)]; // select the min non NaN value
return tNear <= tFar;
}