diff options
author | 2022-03-04 22:13:48 +0000 | |
---|---|---|
committer | 2022-03-17 00:03:12 +0000 | |
commit | 196b0f209fd0cbc9133e6d87faf99f3faf2fb22a (patch) | |
tree | c40c76b990064013474b0fa5a5b9846877b428ca | |
parent | aedf10c2d5dbb93a75988920dc95f7afa7c631ab (diff) |
Tweak libtonemap's CPU interface to support batching.
This is in response to feedback that utilizing the tonemap api to
generate a LUT may require more CPU cycles as common parameters
describing the tone-mapping curve must be regenerated. Support this by
taking in a list of colors, rather than a single color, so that a LUT
can be one-shot computed.
Bug: 200310159
Test: librenderengine_test
Change-Id: I4b9ef8ef6bd95eb25aedd2b16268dc6e58828208
-rw-r--r-- | libs/renderengine/tests/RenderEngineTest.cpp | 10 | ||||
-rw-r--r-- | libs/tonemap/include/tonemap/tonemap.h | 15 | ||||
-rw-r--r-- | libs/tonemap/tonemap.cpp | 403 |
3 files changed, 226 insertions, 202 deletions
diff --git a/libs/renderengine/tests/RenderEngineTest.cpp b/libs/renderengine/tests/RenderEngineTest.cpp index add7a940de..ec1bd470b5 100644 --- a/libs/renderengine/tests/RenderEngineTest.cpp +++ b/libs/renderengine/tests/RenderEngineTest.cpp @@ -1562,15 +1562,21 @@ void RenderEngineTest::tonemap(ui::Dataspace sourceDataspace, std::function<vec3 const vec3 xyz = bt2020.getRGBtoXYZ() * linearRGB; const vec3 scaledXYZ = scaleOotf(xyz, kCurrentLuminanceNits); - const double gain = + const auto gains = tonemap::getToneMapper() ->lookupTonemapGain(static_cast<aidl::android::hardware::graphics::common:: Dataspace>(sourceDataspace), static_cast<aidl::android::hardware::graphics::common:: Dataspace>( ui::Dataspace::DISPLAY_P3), - scaleOotf(linearRGB, kCurrentLuminanceNits), scaledXYZ, + {tonemap:: + Color{.linearRGB = + scaleOotf(linearRGB, + kCurrentLuminanceNits), + .xyz = scaledXYZ}}, metadata); + EXPECT_EQ(1, gains.size()); + const double gain = gains.front(); const vec3 normalizedXYZ = scaledXYZ * gain / metadata.displayMaxLuminance; const vec3 targetRGB = OETF_sRGB(displayP3.getXYZtoRGB() * normalizedXYZ) * 255; diff --git a/libs/tonemap/include/tonemap/tonemap.h b/libs/tonemap/include/tonemap/tonemap.h index b9abf8cd52..9fba642b31 100644 --- a/libs/tonemap/include/tonemap/tonemap.h +++ b/libs/tonemap/include/tonemap/tonemap.h @@ -48,6 +48,14 @@ struct Metadata { float contentMaxLuminance = 0.0; }; +// Utility class containing pre-processed conversions for a particular color +struct Color { + // RGB color in linear space + vec3 linearRGB; + // CIE 1931 XYZ representation of the color + vec3 xyz; +}; + class ToneMapper { public: virtual ~ToneMapper() {} @@ -108,14 +116,15 @@ public: // described by destinationDataspace. To compute the gain, the input colors are provided by // linearRGB, which is the RGB colors in linear space. The colors in XYZ space are also // provided. Metadata is also provided for helping to compute the tonemapping curve. - virtual double lookupTonemapGain( + using Gain = double; + virtual std::vector<Gain> lookupTonemapGain( aidl::android::hardware::graphics::common::Dataspace sourceDataspace, aidl::android::hardware::graphics::common::Dataspace destinationDataspace, - vec3 linearRGB, vec3 xyz, const Metadata& metadata) = 0; + const std::vector<Color>& colors, const Metadata& metadata) = 0; }; // Retrieves a tonemapper instance. // This instance is globally constructed. ToneMapper* getToneMapper(); -} // namespace android::tonemap
\ No newline at end of file +} // namespace android::tonemap diff --git a/libs/tonemap/tonemap.cpp b/libs/tonemap/tonemap.cpp index bc0a884ee4..c4f46bd97c 100644 --- a/libs/tonemap/tonemap.cpp +++ b/libs/tonemap/tonemap.cpp @@ -236,136 +236,143 @@ public: return uniforms; } - double lookupTonemapGain( + std::vector<Gain> lookupTonemapGain( aidl::android::hardware::graphics::common::Dataspace sourceDataspace, aidl::android::hardware::graphics::common::Dataspace destinationDataspace, - vec3 /* linearRGB */, vec3 xyz, const Metadata& metadata) override { - if (xyz.y <= 0.0) { - return 1.0; - } - const int32_t sourceDataspaceInt = static_cast<int32_t>(sourceDataspace); - const int32_t destinationDataspaceInt = static_cast<int32_t>(destinationDataspace); - - double targetNits = 0.0; - switch (sourceDataspaceInt & kTransferMask) { - case kTransferST2084: - case kTransferHLG: - switch (destinationDataspaceInt & kTransferMask) { - case kTransferST2084: - targetNits = xyz.y; - break; - case kTransferHLG: - // PQ has a wider luminance range (10,000 nits vs. 1,000 nits) than HLG, so - // we'll clamp the luminance range in case we're mapping from PQ input to - // HLG output. - targetNits = std::clamp(xyz.y, 0.0f, 1000.0f); - break; - default: - // Here we're mapping from HDR to SDR content, so interpolate using a - // Hermitian polynomial onto the smaller luminance range. - - targetNits = xyz.y; - // if the max input luminance is less than what we can output then - // no tone mapping is needed as all color values will be in range. - if (metadata.contentMaxLuminance > metadata.displayMaxLuminance) { - // three control points - const double x0 = 10.0; - const double y0 = 17.0; - double x1 = metadata.displayMaxLuminance * 0.75; - double y1 = x1; - double x2 = x1 + (metadata.contentMaxLuminance - x1) / 2.0; - double y2 = y1 + (metadata.displayMaxLuminance - y1) * 0.75; - - // horizontal distances between the last three control points - double h12 = x2 - x1; - double h23 = metadata.contentMaxLuminance - x2; - // tangents at the last three control points - double m1 = (y2 - y1) / h12; - double m3 = (metadata.displayMaxLuminance - y2) / h23; - double m2 = (m1 + m3) / 2.0; - - if (targetNits < x0) { + const std::vector<Color>& colors, const Metadata& metadata) override { + std::vector<Gain> gains; + gains.reserve(colors.size()); + + for (const auto [_, xyz] : colors) { + if (xyz.y <= 0.0) { + gains.push_back(1.0); + continue; + } + const int32_t sourceDataspaceInt = static_cast<int32_t>(sourceDataspace); + const int32_t destinationDataspaceInt = static_cast<int32_t>(destinationDataspace); + + double targetNits = 0.0; + switch (sourceDataspaceInt & kTransferMask) { + case kTransferST2084: + case kTransferHLG: + switch (destinationDataspaceInt & kTransferMask) { + case kTransferST2084: + targetNits = xyz.y; + break; + case kTransferHLG: + // PQ has a wider luminance range (10,000 nits vs. 1,000 nits) than HLG, + // so we'll clamp the luminance range in case we're mapping from PQ + // input to HLG output. + targetNits = std::clamp(xyz.y, 0.0f, 1000.0f); + break; + default: + // Here we're mapping from HDR to SDR content, so interpolate using a + // Hermitian polynomial onto the smaller luminance range. + + targetNits = xyz.y; + // if the max input luminance is less than what we can output then + // no tone mapping is needed as all color values will be in range. + if (metadata.contentMaxLuminance > metadata.displayMaxLuminance) { + // three control points + const double x0 = 10.0; + const double y0 = 17.0; + double x1 = metadata.displayMaxLuminance * 0.75; + double y1 = x1; + double x2 = x1 + (metadata.contentMaxLuminance - x1) / 2.0; + double y2 = y1 + (metadata.displayMaxLuminance - y1) * 0.75; + + // horizontal distances between the last three control points + double h12 = x2 - x1; + double h23 = metadata.contentMaxLuminance - x2; + // tangents at the last three control points + double m1 = (y2 - y1) / h12; + double m3 = (metadata.displayMaxLuminance - y2) / h23; + double m2 = (m1 + m3) / 2.0; + + if (targetNits < x0) { + // scale [0.0, x0] to [0.0, y0] linearly + double slope = y0 / x0; + targetNits *= slope; + } else if (targetNits < x1) { + // scale [x0, x1] to [y0, y1] linearly + double slope = (y1 - y0) / (x1 - x0); + targetNits = y0 + (targetNits - x0) * slope; + } else if (targetNits < x2) { + // scale [x1, x2] to [y1, y2] using Hermite interp + double t = (targetNits - x1) / h12; + targetNits = (y1 * (1.0 + 2.0 * t) + h12 * m1 * t) * (1.0 - t) * + (1.0 - t) + + (y2 * (3.0 - 2.0 * t) + h12 * m2 * (t - 1.0)) * t * t; + } else { + // scale [x2, maxInLumi] to [y2, maxOutLumi] using Hermite + // interp + double t = (targetNits - x2) / h23; + targetNits = (y2 * (1.0 + 2.0 * t) + h23 * m2 * t) * (1.0 - t) * + (1.0 - t) + + (metadata.displayMaxLuminance * (3.0 - 2.0 * t) + + h23 * m3 * (t - 1.0)) * + t * t; + } + } + break; + } + break; + default: + // source is SDR + switch (destinationDataspaceInt & kTransferMask) { + case kTransferST2084: + case kTransferHLG: { + // Map from SDR onto an HDR output buffer + // Here we use a polynomial curve to map from [0, displayMaxLuminance] + // onto [0, maxOutLumi] which is hard-coded to be 3000 nits. + const double maxOutLumi = 3000.0; + + double x0 = 5.0; + double y0 = 2.5; + double x1 = metadata.displayMaxLuminance * 0.7; + double y1 = maxOutLumi * 0.15; + double x2 = metadata.displayMaxLuminance * 0.9; + double y2 = maxOutLumi * 0.45; + double x3 = metadata.displayMaxLuminance; + double y3 = maxOutLumi; + + double c1 = y1 / 3.0; + double c2 = y2 / 2.0; + double c3 = y3 / 1.5; + + targetNits = xyz.y; + + if (targetNits <= x0) { // scale [0.0, x0] to [0.0, y0] linearly double slope = y0 / x0; targetNits *= slope; - } else if (targetNits < x1) { - // scale [x0, x1] to [y0, y1] linearly - double slope = (y1 - y0) / (x1 - x0); - targetNits = y0 + (targetNits - x0) * slope; - } else if (targetNits < x2) { - // scale [x1, x2] to [y1, y2] using Hermite interp - double t = (targetNits - x1) / h12; - targetNits = (y1 * (1.0 + 2.0 * t) + h12 * m1 * t) * (1.0 - t) * - (1.0 - t) + - (y2 * (3.0 - 2.0 * t) + h12 * m2 * (t - 1.0)) * t * t; + } else if (targetNits <= x1) { + // scale [x0, x1] to [y0, y1] using a curve + double t = (targetNits - x0) / (x1 - x0); + targetNits = (1.0 - t) * (1.0 - t) * y0 + 2.0 * (1.0 - t) * t * c1 + + t * t * y1; + } else if (targetNits <= x2) { + // scale [x1, x2] to [y1, y2] using a curve + double t = (targetNits - x1) / (x2 - x1); + targetNits = (1.0 - t) * (1.0 - t) * y1 + 2.0 * (1.0 - t) * t * c2 + + t * t * y2; } else { - // scale [x2, maxInLumi] to [y2, maxOutLumi] using Hermite interp - double t = (targetNits - x2) / h23; - targetNits = (y2 * (1.0 + 2.0 * t) + h23 * m2 * t) * (1.0 - t) * - (1.0 - t) + - (metadata.displayMaxLuminance * (3.0 - 2.0 * t) + - h23 * m3 * (t - 1.0)) * - t * t; + // scale [x2, x3] to [y2, y3] using a curve + double t = (targetNits - x2) / (x3 - x2); + targetNits = (1.0 - t) * (1.0 - t) * y2 + 2.0 * (1.0 - t) * t * c3 + + t * t * y3; } - } - break; - } - break; - default: - // source is SDR - switch (destinationDataspaceInt & kTransferMask) { - case kTransferST2084: - case kTransferHLG: { - // Map from SDR onto an HDR output buffer - // Here we use a polynomial curve to map from [0, displayMaxLuminance] onto - // [0, maxOutLumi] which is hard-coded to be 3000 nits. - const double maxOutLumi = 3000.0; - - double x0 = 5.0; - double y0 = 2.5; - double x1 = metadata.displayMaxLuminance * 0.7; - double y1 = maxOutLumi * 0.15; - double x2 = metadata.displayMaxLuminance * 0.9; - double y2 = maxOutLumi * 0.45; - double x3 = metadata.displayMaxLuminance; - double y3 = maxOutLumi; - - double c1 = y1 / 3.0; - double c2 = y2 / 2.0; - double c3 = y3 / 1.5; - - targetNits = xyz.y; - - if (targetNits <= x0) { - // scale [0.0, x0] to [0.0, y0] linearly - double slope = y0 / x0; - targetNits *= slope; - } else if (targetNits <= x1) { - // scale [x0, x1] to [y0, y1] using a curve - double t = (targetNits - x0) / (x1 - x0); - targetNits = (1.0 - t) * (1.0 - t) * y0 + 2.0 * (1.0 - t) * t * c1 + - t * t * y1; - } else if (targetNits <= x2) { - // scale [x1, x2] to [y1, y2] using a curve - double t = (targetNits - x1) / (x2 - x1); - targetNits = (1.0 - t) * (1.0 - t) * y1 + 2.0 * (1.0 - t) * t * c2 + - t * t * y2; - } else { - // scale [x2, x3] to [y2, y3] using a curve - double t = (targetNits - x2) / (x3 - x2); - targetNits = (1.0 - t) * (1.0 - t) * y2 + 2.0 * (1.0 - t) * t * c3 + - t * t * y3; - } - } break; - default: - // For completeness, this is tone-mapping from SDR to SDR, where this is - // just a no-op. - targetNits = xyz.y; - break; - } + } break; + default: + // For completeness, this is tone-mapping from SDR to SDR, where this is + // just a no-op. + targetNits = xyz.y; + break; + } + } + gains.push_back(targetNits / xyz.y); } - - return targetNits / xyz.y; + return gains; } }; @@ -427,8 +434,6 @@ public: break; default: - // Here we're mapping from HDR to SDR content, so interpolate using a - // Hermitian polynomial onto the smaller luminance range. program.append(R"( float libtonemap_OETFTone(float channel) { channel = channel / 10000.0; @@ -548,95 +553,99 @@ public: return uniforms; } - double lookupTonemapGain( + std::vector<Gain> lookupTonemapGain( aidl::android::hardware::graphics::common::Dataspace sourceDataspace, aidl::android::hardware::graphics::common::Dataspace destinationDataspace, - vec3 linearRGB, vec3 /* xyz */, const Metadata& metadata) override { - double maxRGB = std::max({linearRGB.r, linearRGB.g, linearRGB.b}); + const std::vector<Color>& colors, const Metadata& metadata) override { + std::vector<Gain> gains; + gains.reserve(colors.size()); - if (maxRGB <= 0.0) { - return 1.0; - } - - const int32_t sourceDataspaceInt = static_cast<int32_t>(sourceDataspace); - const int32_t destinationDataspaceInt = static_cast<int32_t>(destinationDataspace); + // Precompute constants for HDR->SDR tonemapping parameters + constexpr double maxInLumi = 4000; + const double maxOutLumi = metadata.displayMaxLuminance; - double targetNits = 0.0; - switch (sourceDataspaceInt & kTransferMask) { - case kTransferST2084: - switch (destinationDataspaceInt & kTransferMask) { - case kTransferST2084: - targetNits = maxRGB; - break; - case kTransferHLG: - // PQ has a wider luminance range (10,000 nits vs. 1,000 nits) than HLG, so - // we'll clamp the luminance range in case we're mapping from PQ input to - // HLG output. - targetNits = std::clamp(maxRGB, 0.0, 1000.0); - break; - default: - // Here we're mapping from HDR to SDR content, so interpolate using a - // Hermitian polynomial onto the smaller luminance range. + const double x1 = maxOutLumi * 0.65; + const double y1 = x1; - double maxInLumi = 4000; - double maxOutLumi = metadata.displayMaxLuminance; + const double x3 = maxInLumi; + const double y3 = maxOutLumi; - targetNits = maxRGB; + const double x2 = x1 + (x3 - x1) * 4.0 / 17.0; + const double y2 = maxOutLumi * 0.9; - double x1 = maxOutLumi * 0.65; - double y1 = x1; + const double greyNorm1 = OETF_ST2084(x1); + const double greyNorm2 = OETF_ST2084(x2); + const double greyNorm3 = OETF_ST2084(x3); - double x3 = maxInLumi; - double y3 = maxOutLumi; + const double slope2 = (y2 - y1) / (greyNorm2 - greyNorm1); + const double slope3 = (y3 - y2) / (greyNorm3 - greyNorm2); - double x2 = x1 + (x3 - x1) * 4.0 / 17.0; - double y2 = maxOutLumi * 0.9; + for (const auto [linearRGB, _] : colors) { + double maxRGB = std::max({linearRGB.r, linearRGB.g, linearRGB.b}); - const double greyNorm1 = OETF_ST2084(x1); - const double greyNorm2 = OETF_ST2084(x2); - const double greyNorm3 = OETF_ST2084(x3); + if (maxRGB <= 0.0) { + gains.push_back(1.0); + continue; + } - double slope2 = (y2 - y1) / (greyNorm2 - greyNorm1); - double slope3 = (y3 - y2) / (greyNorm3 - greyNorm2); + const int32_t sourceDataspaceInt = static_cast<int32_t>(sourceDataspace); + const int32_t destinationDataspaceInt = static_cast<int32_t>(destinationDataspace); - if (targetNits < x1) { + double targetNits = 0.0; + switch (sourceDataspaceInt & kTransferMask) { + case kTransferST2084: + switch (destinationDataspaceInt & kTransferMask) { + case kTransferST2084: + targetNits = maxRGB; break; - } - - if (targetNits > maxInLumi) { - targetNits = maxOutLumi; + case kTransferHLG: + // PQ has a wider luminance range (10,000 nits vs. 1,000 nits) than HLG, + // so we'll clamp the luminance range in case we're mapping from PQ + // input to HLG output. + targetNits = std::clamp(maxRGB, 0.0, 1000.0); break; - } + default: + targetNits = maxRGB; + if (targetNits < x1) { + break; + } - const double greyNits = OETF_ST2084(targetNits); + if (targetNits > maxInLumi) { + targetNits = maxOutLumi; + break; + } - if (greyNits <= greyNorm2) { - targetNits = (greyNits - greyNorm2) * slope2 + y2; - } else if (greyNits <= greyNorm3) { - targetNits = (greyNits - greyNorm3) * slope3 + y3; - } else { - targetNits = maxOutLumi; - } - break; - } - break; - case kTransferHLG: - switch (destinationDataspaceInt & kTransferMask) { - case kTransferST2084: - case kTransferHLG: - targetNits = maxRGB; - break; - default: - targetNits = maxRGB * metadata.displayMaxLuminance / 1000.0; - break; - } - break; - default: - targetNits = maxRGB; - break; - } + const double greyNits = OETF_ST2084(targetNits); - return targetNits / maxRGB; + if (greyNits <= greyNorm2) { + targetNits = (greyNits - greyNorm2) * slope2 + y2; + } else if (greyNits <= greyNorm3) { + targetNits = (greyNits - greyNorm3) * slope3 + y3; + } else { + targetNits = maxOutLumi; + } + break; + } + break; + case kTransferHLG: + switch (destinationDataspaceInt & kTransferMask) { + case kTransferST2084: + case kTransferHLG: + targetNits = maxRGB; + break; + default: + targetNits = maxRGB * metadata.displayMaxLuminance / 1000.0; + break; + } + break; + default: + targetNits = maxRGB; + break; + } + + gains.push_back(targetNits / maxRGB); + } + return gains; } }; @@ -658,4 +667,4 @@ ToneMapper* getToneMapper() { return sToneMapper.get(); } -} // namespace android::tonemap
\ No newline at end of file +} // namespace android::tonemap |