From bc01475920a7ba0804db338e1d08b810a8ecb344 Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Mon, 24 Jun 2019 18:46:27 -0700 Subject: [PATCH] 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). --- libs/ibl/include/ibl/Cubemap.h | 3 ++- libs/ibl/include/ibl/CubemapUtils.h | 8 ++++++ libs/ibl/src/Cubemap.cpp | 14 +++++++++- libs/ibl/src/CubemapUtils.cpp | 14 +++++----- libs/ibl/src/CubemapUtilsImpl.h | 42 ++++++++++++++++++++++++++++- 5 files changed, 71 insertions(+), 10 deletions(-) diff --git a/libs/ibl/include/ibl/Cubemap.h b/libs/ibl/include/ibl/Cubemap.h index 2e49ff9ec9..192abc5eb8 100644 --- a/libs/ibl/include/ibl/Cubemap.h +++ b/libs/ibl/include/ibl/Cubemap.h @@ -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, diff --git a/libs/ibl/include/ibl/CubemapUtils.h b/libs/ibl/include/ibl/CubemapUtils.h index 8ef9e696a0..84dac6aa01 100644 --- a/libs/ibl/include/ibl/CubemapUtils.h +++ b/libs/ibl/include/ibl/CubemapUtils.h @@ -55,6 +55,14 @@ public: ReduceProc reduce = [](STATE&) {}, const STATE& prototype = STATE()); + //! process the cubemap + template + static void processSingleThreaded(Cubemap& cm, + utils::JobSystem& js, + ScanlineProc proc, + ReduceProc reduce = [](STATE&) {}, + const STATE& prototype = STATE()); + //! Converts equirectangular Image to a Cubemap static void equirectangularToCubemap(utils::JobSystem& js, Cubemap& dst, const Image& src); diff --git a/libs/ibl/src/Cubemap.cpp b/libs/ibl/src/Cubemap.cpp index 329efb7151..f5413ab192 100644 --- a/libs/ibl/src/Cubemap.cpp +++ b/libs/ibl/src/Cubemap.cpp @@ -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) { diff --git a/libs/ibl/src/CubemapUtils.cpp b/libs/ibl/src/CubemapUtils.cpp index 6955364288..4505546cbb 100644 --- a/libs/ibl/src/CubemapUtils.cpp +++ b/libs/ibl/src/CubemapUtils.cpp @@ -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(dst, js, + processSingleThreaded(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(dst, js, + processSingleThreaded(dst, js, [&](EmptyState&, size_t y, Cubemap::Face f, Cubemap::Texel* data, size_t dim) { for (size_t x=0 ; x(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::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 +void CubemapUtils::processSingleThreaded( + Cubemap& cm, + utils::JobSystem& js, + CubemapUtils::ScanlineProc proc, + ReduceProc 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(image.getPixelRef(0, y)); + proc(s, y, f, data, dim); + } + } + reduce(s); +} + + } // namespace ibl } // namespace filament