optimize libibl for smaller workloads

We now never process mirroring or mipmaping with multithreading
it's just not worth it given the overhead of the jobsystem.

We also require 64 lines per job, below that, we only multithread per
face (6 threads). This should probably depends on the sample count,
but we don't have this facility yet.

Special case downsampling for mipmaping.

This alone improves performance by 2x for small cubemaps (e.g. 16x16).
This commit is contained in:
Mathias Agopian
2019-06-24 18:46:27 -07:00
committed by Mathias Agopian
parent 56e9d8eda5
commit bc01475920
5 changed files with 71 additions and 10 deletions

View File

@@ -99,7 +99,8 @@ public:
inline Texel filterAt(const filament::math::double3& direction) const;
//! samples an image at the given location in pixel using bilinear filtering
static Texel filterAt(const Image& image, double x, double y);
static Texel filterAt(const Image& image, float x, float y);
static Texel filterAtCenter(const Image& image, size_t x, size_t y);
//! samples two cubemaps in a given direction and lerps the result by a given lerp factor
static Texel trilinearFilterAt(const Cubemap& c0, const Cubemap& c1, double lerp,

View File

@@ -55,6 +55,14 @@ public:
ReduceProc<STATE> reduce = [](STATE&) {},
const STATE& prototype = STATE());
//! process the cubemap
template<typename STATE>
static void processSingleThreaded(Cubemap& cm,
utils::JobSystem& js,
ScanlineProc<STATE> proc,
ReduceProc<STATE> reduce = [](STATE&) {},
const STATE& prototype = STATE());
//! Converts equirectangular Image to a Cubemap
static void equirectangularToCubemap(utils::JobSystem& js, Cubemap& dst, const Image& src);

View File

@@ -169,7 +169,7 @@ void Cubemap::makeSeamless() {
corners(Face::NY);
}
Cubemap::Texel Cubemap::filterAt(const Image& image, double x, double y) {
Cubemap::Texel Cubemap::filterAt(const Image& image, float x, float y) {
const size_t x0 = size_t(x);
const size_t y0 = size_t(y);
// we allow ourselves to read past the width/height of the Image because the data is valid
@@ -188,6 +188,18 @@ Cubemap::Texel Cubemap::filterAt(const Image& image, double x, double y) {
return (one_minus_u*one_minus_v)*c0 + (u*one_minus_v)*c1 + (one_minus_u*v)*c2 + (u*v)*c3;
}
Cubemap::Texel Cubemap::filterAtCenter(const Image& image, size_t x0, size_t y0) {
// we allow ourselves to read past the width/height of the Image because the data is valid
// and contain the "seamless" data.
size_t x1 = x0 + 1;
size_t y1 = y0 + 1;
const Texel& c0 = sampleAt(image.getPixelRef(x0, y0));
const Texel& c1 = sampleAt(image.getPixelRef(x1, y0));
const Texel& c2 = sampleAt(image.getPixelRef(x0, y1));
const Texel& c3 = sampleAt(image.getPixelRef(x1, y1));
return (c0 + c1 + c2 + c3) * 0.25f;
}
Cubemap::Texel Cubemap::trilinearFilterAt(const Cubemap& l0, const Cubemap& l1, double lerp,
const double3& L)
{

View File

@@ -229,13 +229,13 @@ void CubemapUtils::crossToCubemap(JobSystem& js, Cubemap& dst, const Image& src)
void CubemapUtils::downsampleCubemapLevelBoxFilter(JobSystem& js, Cubemap& dst, const Cubemap& src) {
size_t scale = src.getDimensions() / dst.getDimensions();
process<EmptyState>(dst, js,
processSingleThreaded<EmptyState>(dst, js,
[&](EmptyState&, size_t y, Cubemap::Face f, Cubemap::Texel* data, size_t dim) {
const Image& image(src.getImageForFace(f));
for (size_t x=0 ; x<dim ; ++x, ++data) {
Cubemap::writeAt(data, Cubemap::filterAt(image, x*scale+0.5, y*scale+0.5));
}
});
const Image& image(src.getImageForFace(f));
for (size_t x = 0; x < dim; ++x, ++data) {
Cubemap::writeAt(data, Cubemap::filterAtCenter(image, x * scale, y * scale));
}
});
}
// ------------------------------------------------------------------------------------------------
@@ -311,7 +311,7 @@ Cubemap CubemapUtils::create(Image& image, size_t dim, bool horizontal) {
}
void CubemapUtils::mirrorCubemap(JobSystem& js, Cubemap& dst, const Cubemap& src) {
process<EmptyState>(dst, js,
processSingleThreaded<EmptyState>(dst, js,
[&](EmptyState&, size_t y, Cubemap::Face f, Cubemap::Texel* data, size_t dim) {
for (size_t x=0 ; x<dim ; ++x, ++data) {
const double3 N(dst.getDirectionFor(f, x, y));

View File

@@ -36,6 +36,22 @@ void CubemapUtils::process(
const size_t dim = cm.getDimensions();
// multithread only on large-ish cubemaps
if (dim < 64) {
// 64 pixels occupies 12 cache lines exactly
STATE s;
for (size_t faceIndex = 0; faceIndex < 6; faceIndex++) {
const Cubemap::Face f = (Cubemap::Face)faceIndex;
Image& image(cm.getImageForFace(f));
for (size_t y = 0; y < dim; y++) {
Cubemap::Texel* data = static_cast<Cubemap::Texel*>(image.getPixelRef(0, y));
proc(s, y, f, data, dim);
}
}
reduce(s);
return;
}
STATE states[6];
for (STATE& s : states) {
s = prototype;
@@ -60,7 +76,7 @@ void CubemapUtils::process(
if (std::is_same<STATE, CubemapUtils::EmptyState>::value) {
auto job = jobs::parallel_for(js, parent, 0, uint32_t(dim),
std::ref(parallelJobTask), jobs::CountSplitter<1, 8>());
std::ref(parallelJobTask), jobs::CountSplitter<64, 8>());
// we need to wait here because parallelJobTask is passed by reference
js.runAndWait(job);
@@ -79,6 +95,30 @@ void CubemapUtils::process(
}
}
template<typename STATE>
void CubemapUtils::processSingleThreaded(
Cubemap& cm,
utils::JobSystem& js,
CubemapUtils::ScanlineProc<STATE> proc,
ReduceProc<STATE> reduce,
const STATE& prototype) {
using namespace utils;
const size_t dim = cm.getDimensions();
STATE s;
for (size_t faceIndex = 0; faceIndex < 6; faceIndex++) {
const Cubemap::Face f = (Cubemap::Face)faceIndex;
Image& image(cm.getImageForFace(f));
for (size_t y = 0; y < dim; y++) {
Cubemap::Texel* data = static_cast<Cubemap::Texel*>(image.getPixelRef(0, y));
proc(s, y, f, data, dim);
}
}
reduce(s);
}
} // namespace ibl
} // namespace filament