diff options
-rw-r--r-- | libs/renderengine/Android.bp | 1 | ||||
-rw-r--r-- | libs/renderengine/include/renderengine/DisplaySettings.h | 16 | ||||
-rw-r--r-- | libs/renderengine/skia/SkiaRenderEngine.cpp | 24 | ||||
-rw-r--r-- | libs/renderengine/skia/filters/MouriMap.cpp | 183 | ||||
-rw-r--r-- | libs/renderengine/skia/filters/MouriMap.h | 81 | ||||
-rw-r--r-- | libs/ui/DisplayIdentification.cpp | 5 | ||||
-rw-r--r-- | services/surfaceflinger/FrontEnd/RequestedLayerState.cpp | 24 | ||||
-rw-r--r-- | services/surfaceflinger/ScreenCaptureOutput.cpp | 13 | ||||
-rw-r--r-- | services/surfaceflinger/ScreenCaptureOutput.h | 5 | ||||
-rw-r--r-- | services/surfaceflinger/SurfaceFlinger.cpp | 47 | ||||
-rw-r--r-- | services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp | 37 | ||||
-rw-r--r-- | vulkan/libvulkan/driver.cpp | 8 | ||||
-rw-r--r-- | vulkan/scripts/driver_generator.py | 2 |
13 files changed, 412 insertions, 34 deletions
diff --git a/libs/renderengine/Android.bp b/libs/renderengine/Android.bp index c003111ebd..757d935647 100644 --- a/libs/renderengine/Android.bp +++ b/libs/renderengine/Android.bp @@ -102,6 +102,7 @@ filegroup { "skia/filters/GaussianBlurFilter.cpp", "skia/filters/KawaseBlurFilter.cpp", "skia/filters/LinearEffect.cpp", + "skia/filters/MouriMap.cpp", "skia/filters/StretchShaderFactory.cpp", ], } diff --git a/libs/renderengine/include/renderengine/DisplaySettings.h b/libs/renderengine/include/renderengine/DisplaySettings.h index deb62534a5..b640983a55 100644 --- a/libs/renderengine/include/renderengine/DisplaySettings.h +++ b/libs/renderengine/include/renderengine/DisplaySettings.h @@ -86,6 +86,22 @@ struct DisplaySettings { // Configures the rendering intent of the output display. This is used for tonemapping. aidl::android::hardware::graphics::composer3::RenderIntent renderIntent = aidl::android::hardware::graphics::composer3::RenderIntent::TONE_MAP_COLORIMETRIC; + + // Tonemapping strategy to use for each layer. This is only used for tonemapping HDR source + // content + enum class TonemapStrategy { + // Use a tonemapper defined by libtonemap. This may be OEM-defined as of Android 13, aka + // undefined. + // This is typically a global tonemapper, designed to match what is on screen. + Libtonemap, + // Use a local tonemapper. Because local tonemapping uses large intermediate allocations, + // this + // method is primarily recommended for infrequent rendering that does not need to exactly + // match + // pixels that are on-screen. + Local, + }; + TonemapStrategy tonemapStrategy = TonemapStrategy::Libtonemap; }; static inline bool operator==(const DisplaySettings& lhs, const DisplaySettings& rhs) { diff --git a/libs/renderengine/skia/SkiaRenderEngine.cpp b/libs/renderengine/skia/SkiaRenderEngine.cpp index d3441643f6..e62640eb85 100644 --- a/libs/renderengine/skia/SkiaRenderEngine.cpp +++ b/libs/renderengine/skia/SkiaRenderEngine.cpp @@ -79,6 +79,7 @@ #include "filters/GaussianBlurFilter.h" #include "filters/KawaseBlurFilter.h" #include "filters/LinearEffect.h" +#include "filters/MouriMap.h" #include "log/log_main.h" #include "skia/compat/SkiaBackendTexture.h" #include "skia/debug/SkiaCapture.h" @@ -509,9 +510,9 @@ sk_sp<SkShader> SkiaRenderEngine::createRuntimeEffectShader( // Determine later on if we need to leverage the stertch shader within // surface flinger const auto& stretchEffect = parameters.layer.stretchEffect; + const auto& targetBuffer = parameters.layer.source.buffer.buffer; auto shader = parameters.shader; if (stretchEffect.hasEffect()) { - const auto targetBuffer = parameters.layer.source.buffer.buffer; const auto graphicBuffer = targetBuffer ? targetBuffer->getBuffer() : nullptr; if (graphicBuffer && parameters.shader) { shader = mStretchShaderFactory.createSkShader(shader, stretchEffect); @@ -519,6 +520,24 @@ sk_sp<SkShader> SkiaRenderEngine::createRuntimeEffectShader( } if (parameters.requiresLinearEffect) { + const auto format = targetBuffer != nullptr + ? std::optional<ui::PixelFormat>( + static_cast<ui::PixelFormat>(targetBuffer->getPixelFormat())) + : std::nullopt; + + if (parameters.display.tonemapStrategy == DisplaySettings::TonemapStrategy::Local) { + // TODO: Handle color matrix transforms in linear space. + SkImage* image = parameters.shader->isAImage((SkMatrix*)nullptr, (SkTileMode*)nullptr); + if (image) { + static MouriMap kMapper; + const float ratio = getHdrRenderType(parameters.layer.sourceDataspace, format) == + HdrRenderType::GENERIC_HDR + ? 1.0f + : parameters.layerDimmingRatio; + return kMapper.mouriMap(getActiveContext(), parameters.shader, ratio); + } + } + auto effect = shaders::LinearEffect{.inputDataspace = parameters.layer.sourceDataspace, .outputDataspace = parameters.outputDataSpace, @@ -712,8 +731,7 @@ void SkiaRenderEngine::drawLayersInternal( [&](const auto& l) { return l.whitePointNits; }); // ...and compute the dimming ratio if dimming is requested - const float displayDimmingRatio = display.targetLuminanceNits > 0.f && - maxLayerWhitePoint > 0.f && display.targetLuminanceNits > maxLayerWhitePoint + const float displayDimmingRatio = display.targetLuminanceNits > 0.f && maxLayerWhitePoint > 0.f ? maxLayerWhitePoint / display.targetLuminanceNits : 1.f; diff --git a/libs/renderengine/skia/filters/MouriMap.cpp b/libs/renderengine/skia/filters/MouriMap.cpp new file mode 100644 index 0000000000..7d8b8a5116 --- /dev/null +++ b/libs/renderengine/skia/filters/MouriMap.cpp @@ -0,0 +1,183 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "MouriMap.h" +#include <SkCanvas.h> +#include <SkColorType.h> +#include <SkPaint.h> +#include <SkTileMode.h> + +namespace android { +namespace renderengine { +namespace skia { +namespace { +sk_sp<SkRuntimeEffect> makeEffect(const SkString& sksl) { + auto [effect, error] = SkRuntimeEffect::MakeForShader(sksl); + LOG_ALWAYS_FATAL_IF(!effect, "RuntimeShader error: %s", error.c_str()); + return effect; +} +const SkString kCrosstalkAndChunk16x16(R"( + uniform shader bitmap; + uniform float hdrSdrRatio; + vec4 main(vec2 xy) { + float maximum = 0.0; + for (int y = 0; y < 16; y++) { + for (int x = 0; x < 16; x++) { + float3 linear = toLinearSrgb(bitmap.eval(xy * 16 + vec2(x, y)).rgb) * hdrSdrRatio; + float maxRGB = max(linear.r, max(linear.g, linear.b)); + maximum = max(maximum, log2(max(maxRGB, 1.0))); + } + } + return float4(float3(maximum), 1.0); + } +)"); +const SkString kChunk8x8(R"( + uniform shader bitmap; + vec4 main(vec2 xy) { + float maximum = 0.0; + for (int y = 0; y < 8; y++) { + for (int x = 0; x < 8; x++) { + maximum = max(maximum, bitmap.eval(xy * 8 + vec2(x, y)).r); + } + } + return float4(float3(maximum), 1.0); + } +)"); +const SkString kBlur(R"( + uniform shader bitmap; + vec4 main(vec2 xy) { + float C[5]; + C[0] = 1.0 / 16.0; + C[1] = 4.0 / 16.0; + C[2] = 6.0 / 16.0; + C[3] = 4.0 / 16.0; + C[4] = 1.0 / 16.0; + float result = 0.0; + for (int y = -2; y <= 2; y++) { + for (int x = -2; x <= 2; x++) { + result += C[y + 2] * C[x + 2] * bitmap.eval(xy + vec2(x, y)).r; + } + } + return float4(float3(exp2(result)), 1.0); + } +)"); +const SkString kTonemap(R"( + uniform shader image; + uniform shader lux; + uniform float scaleFactor; + uniform float hdrSdrRatio; + vec4 main(vec2 xy) { + float localMax = lux.eval(xy * scaleFactor).r; + float4 rgba = image.eval(xy); + float3 linear = toLinearSrgb(rgba.rgb) * hdrSdrRatio; + + if (localMax <= 1.0) { + return float4(fromLinearSrgb(linear), 1.0); + } + + float maxRGB = max(linear.r, max(linear.g, linear.b)); + localMax = max(localMax, maxRGB); + float gain = (1 + maxRGB / (localMax * localMax)) / (1 + maxRGB); + return float4(fromLinearSrgb(linear * gain), 1.0); + } +)"); + +// Draws the given runtime shader on a GPU surface and returns the result as an SkImage. +sk_sp<SkImage> makeImage(SkSurface* surface, const SkRuntimeShaderBuilder& builder) { + sk_sp<SkShader> shader = builder.makeShader(nullptr); + LOG_ALWAYS_FATAL_IF(!shader, "%s, Failed to make shader!", __func__); + SkPaint paint; + paint.setShader(std::move(shader)); + paint.setBlendMode(SkBlendMode::kSrc); + surface->getCanvas()->drawPaint(paint); + return surface->makeImageSnapshot(); +} + +} // namespace + +MouriMap::MouriMap() + : mCrosstalkAndChunk16x16(makeEffect(kCrosstalkAndChunk16x16)), + mChunk8x8(makeEffect(kChunk8x8)), + mBlur(makeEffect(kBlur)), + mTonemap(makeEffect(kTonemap)) {} + +sk_sp<SkShader> MouriMap::mouriMap(SkiaGpuContext* context, sk_sp<SkShader> input, + float hdrSdrRatio) { + auto downchunked = downchunk(context, input, hdrSdrRatio); + auto localLux = blur(context, downchunked.get()); + return tonemap(input, localLux.get(), hdrSdrRatio); +} + +sk_sp<SkImage> MouriMap::downchunk(SkiaGpuContext* context, sk_sp<SkShader> input, + float hdrSdrRatio) const { + SkMatrix matrix; + SkImage* image = input->isAImage(&matrix, (SkTileMode*)nullptr); + SkRuntimeShaderBuilder crosstalkAndChunk16x16Builder(mCrosstalkAndChunk16x16); + crosstalkAndChunk16x16Builder.child("bitmap") = input; + crosstalkAndChunk16x16Builder.uniform("hdrSdrRatio") = hdrSdrRatio; + // TODO: fp16 might be overkill. Most practical surfaces use 8-bit RGB for HDR UI and 10-bit YUV + // for HDR video. These downsample operations compute log2(max(linear RGB, 1.0)). So we don't + // care about LDR precision since they all resolve to LDR-max. For appropriately mastered HDR + // content that follows BT. 2408, 25% of the bit range for HLG and 42% of the bit range for PQ + // are reserved for HDR. This means that we can fit the entire HDR range for 10-bit HLG inside + // of 8 bits. We can also fit about half of the range for PQ, but most content does not fill the + // entire 10k nit range for PQ. Furthermore, we blur all of this later on anyways, so we might + // not need to be so precise. So, it's possible that we could use A8 or R8 instead. If we want + // to be really conservative we can try to use R16 or even RGBA1010102 to fake an R10 surface, + // which would cut write bandwidth significantly. + static constexpr auto kFirstDownscaleAmount = 16; + sk_sp<SkSurface> firstDownsampledSurface = context->createRenderTarget( + image->imageInfo() + .makeWH(std::max(1, image->width() / kFirstDownscaleAmount), + std::max(1, image->height() / kFirstDownscaleAmount)) + .makeColorType(kRGBA_F16_SkColorType)); + LOG_ALWAYS_FATAL_IF(!firstDownsampledSurface, "%s: Failed to create surface!", __func__); + auto firstDownsampledImage = + makeImage(firstDownsampledSurface.get(), crosstalkAndChunk16x16Builder); + SkRuntimeShaderBuilder chunk8x8Builder(mChunk8x8); + chunk8x8Builder.child("bitmap") = + firstDownsampledImage->makeRawShader(SkTileMode::kClamp, SkTileMode::kClamp, + SkSamplingOptions()); + static constexpr auto kSecondDownscaleAmount = 8; + sk_sp<SkSurface> secondDownsampledSurface = context->createRenderTarget( + firstDownsampledImage->imageInfo() + .makeWH(std::max(1, firstDownsampledImage->width() / kSecondDownscaleAmount), + std::max(1, firstDownsampledImage->height() / kSecondDownscaleAmount))); + LOG_ALWAYS_FATAL_IF(!secondDownsampledSurface, "%s: Failed to create surface!", __func__); + return makeImage(secondDownsampledSurface.get(), chunk8x8Builder); +} +sk_sp<SkImage> MouriMap::blur(SkiaGpuContext* context, SkImage* input) const { + SkRuntimeShaderBuilder blurBuilder(mBlur); + blurBuilder.child("bitmap") = + input->makeRawShader(SkTileMode::kClamp, SkTileMode::kClamp, SkSamplingOptions()); + sk_sp<SkSurface> blurSurface = context->createRenderTarget(input->imageInfo()); + LOG_ALWAYS_FATAL_IF(!blurSurface, "%s: Failed to create surface!", __func__); + return makeImage(blurSurface.get(), blurBuilder); +} +sk_sp<SkShader> MouriMap::tonemap(sk_sp<SkShader> input, SkImage* localLux, + float hdrSdrRatio) const { + static constexpr float kScaleFactor = 1.0f / 128.0f; + SkRuntimeShaderBuilder tonemapBuilder(mTonemap); + tonemapBuilder.child("image") = input; + tonemapBuilder.child("lux") = + localLux->makeRawShader(SkTileMode::kClamp, SkTileMode::kClamp, + SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kNone)); + tonemapBuilder.uniform("scaleFactor") = kScaleFactor; + tonemapBuilder.uniform("hdrSdrRatio") = hdrSdrRatio; + return tonemapBuilder.makeShader(); +} +} // namespace skia +} // namespace renderengine +} // namespace android
\ No newline at end of file diff --git a/libs/renderengine/skia/filters/MouriMap.h b/libs/renderengine/skia/filters/MouriMap.h new file mode 100644 index 0000000000..3c0df8abf0 --- /dev/null +++ b/libs/renderengine/skia/filters/MouriMap.h @@ -0,0 +1,81 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once +#include <SkImage.h> +#include <SkRuntimeEffect.h> +#include <SkShader.h> +#include "../compat/SkiaGpuContext.h" +namespace android { +namespace renderengine { +namespace skia { +/** + * MouriMap is a fast, albeit not realtime, tonemapping algorithm optimized for near-exact + * preservation of SDR (or, equivalently, LDR) regions, while trying to do an acceptable job of + * preserving HDR detail. + * + * MouriMap is a local tonemapping algorithm, meaning that nearby pixels are taken into + * consideration when choosing a tonemapping curve. + * + * The algorithm conceptually is as follows: + * 1. Partition the image into 128x128 chunks, computing the log2(maximum luminance) in each chunk + *. a. Maximum luminance is computed as max(R, G, B), where the R, G, B values are in linear + *. luminance on a scale defined by the destination color gamut. Max(R, G, B) has been found + *. to minimize difference in hue while restricting to typical LDR color volumes. See: Burke, + *. Adam & Smith, Michael & Zink, Michael. 2020. Color Volume and Hue-preservation in HDR + *. Tone Mapping. SMPTE Motion Imaging Journal. + *. b. Each computed luminance is lower-bounded by 1.0 in Skia's color + *. management, or 203 nits. + * 2. Blur the resulting chunks using a 5x5 gaussian kernel, to smooth out the local luminance map. + * 3. Now, for each pixel in the original image: + * a. Upsample from the blurred chunks of luminance computed in (2). Call this luminance value + *. L: an estimate of the maximum luminance of surrounding pixels. + *. b. If the luminance is less than 1.0 (203 nits), then do not modify the pixel value of the + *. original image. + *. c. Otherwise, + *. parameterize a tone-mapping curve using a method described by Chrome: + *. https://docs.google.com/document/d/17T2ek1i2R7tXdfHCnM-i5n6__RoYe0JyMfKmTEjoGR8/. + *. i. Compute a gain G = (1 + max(linear R, linear G, linear B) / (L * L)) + *. / (1 + max(linear R, linear G, linear B)). Note the similarity with the 1D curve + *. described by Erik Reinhard, Michael Stark, Peter Shirley, and James Ferwerda. 2002. + *. Photographic tone reproduction for digital images. ACM Trans. Graph. + *. ii. Multiply G by the linear source colors to compute the final colors. + * + * Because it is a multi-renderpass algorithm requiring multiple off-screen textures, MouriMap is + * typically not suitable to be ran "frequently", at high refresh rates (e.g., 120hz). However, + * MouriMap is sufficiently fast enough for infrequent composition where preserving SDR detail is + * most important, such as for screenshots. + */ +class MouriMap { +public: + MouriMap(); + // Apply the MouriMap tonemmaping operator to the input. + // The HDR/SDR ratio describes the luminace range of the input. 1.0 means SDR. Anything larger + // then 1.0 means that there is headroom above the SDR region. + sk_sp<SkShader> mouriMap(SkiaGpuContext* context, sk_sp<SkShader> input, float hdrSdrRatio); + +private: + sk_sp<SkImage> downchunk(SkiaGpuContext* context, sk_sp<SkShader> input, + float hdrSdrRatio) const; + sk_sp<SkImage> blur(SkiaGpuContext* context, SkImage* input) const; + sk_sp<SkShader> tonemap(sk_sp<SkShader> input, SkImage* localLux, float hdrSdrRatio) const; + const sk_sp<SkRuntimeEffect> mCrosstalkAndChunk16x16; + const sk_sp<SkRuntimeEffect> mChunk8x8; + const sk_sp<SkRuntimeEffect> mBlur; + const sk_sp<SkRuntimeEffect> mTonemap; +}; +} // namespace skia +} // namespace renderengine +} // namespace android
\ No newline at end of file diff --git a/libs/ui/DisplayIdentification.cpp b/libs/ui/DisplayIdentification.cpp index 0908ae85a8..e5af7406ed 100644 --- a/libs/ui/DisplayIdentification.cpp +++ b/libs/ui/DisplayIdentification.cpp @@ -320,6 +320,11 @@ std::optional<PnpId> getPnpId(PhysicalDisplayId displayId) { std::optional<DisplayIdentificationInfo> parseDisplayIdentificationData( uint8_t port, const DisplayIdentificationData& data) { + if (data.empty()) { + ALOGI("Display identification data is empty."); + return {}; + } + if (!isEdid(data)) { ALOGE("Display identification data has unknown format."); return {}; diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp index 139428aca1..3e8d74094d 100644 --- a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp +++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp @@ -587,23 +587,22 @@ bool RequestedLayerState::isSimpleBufferUpdate(const layer_state_t& s) const { const uint64_t deniedFlags = layer_state_t::eProducerDisconnect | layer_state_t::eLayerChanged | layer_state_t::eRelativeLayerChanged | layer_state_t::eTransparentRegionChanged | - layer_state_t::eFlagsChanged | layer_state_t::eBlurRegionsChanged | - layer_state_t::eLayerStackChanged | layer_state_t::eReparent | + layer_state_t::eBlurRegionsChanged | layer_state_t::eLayerStackChanged | + layer_state_t::eReparent | (FlagManager::getInstance().latch_unsignaled_with_auto_refresh_changed() ? 0 - : layer_state_t::eAutoRefreshChanged); + : (layer_state_t::eAutoRefreshChanged | layer_state_t::eFlagsChanged)); if (s.what & deniedFlags) { ATRACE_FORMAT_INSTANT("%s: false [has denied flags 0x%" PRIx64 "]", __func__, s.what & deniedFlags); return false; } - bool changedFlags = diff(s); - static constexpr auto deniedChanges = layer_state_t::ePositionChanged | - layer_state_t::eAlphaChanged | layer_state_t::eColorTransformChanged | - layer_state_t::eBackgroundColorChanged | layer_state_t::eMatrixChanged | - layer_state_t::eCornerRadiusChanged | layer_state_t::eBackgroundBlurRadiusChanged | - layer_state_t::eBufferTransformChanged | + const uint64_t changedFlags = diff(s); + const uint64_t deniedChanges = layer_state_t::ePositionChanged | layer_state_t::eAlphaChanged | + layer_state_t::eColorTransformChanged | layer_state_t::eBackgroundColorChanged | + layer_state_t::eMatrixChanged | layer_state_t::eCornerRadiusChanged | + layer_state_t::eBackgroundBlurRadiusChanged | layer_state_t::eBufferTransformChanged | layer_state_t::eTransformToDisplayInverseChanged | layer_state_t::eCropChanged | layer_state_t::eDataspaceChanged | layer_state_t::eHdrMetadataChanged | layer_state_t::eSidebandStreamChanged | layer_state_t::eColorSpaceAgnosticChanged | @@ -611,10 +610,13 @@ bool RequestedLayerState::isSimpleBufferUpdate(const layer_state_t& s) const { layer_state_t::eTrustedOverlayChanged | layer_state_t::eStretchChanged | layer_state_t::eBufferCropChanged | layer_state_t::eDestinationFrameChanged | layer_state_t::eDimmingEnabledChanged | layer_state_t::eExtendedRangeBrightnessChanged | - layer_state_t::eDesiredHdrHeadroomChanged; + layer_state_t::eDesiredHdrHeadroomChanged | + (FlagManager::getInstance().latch_unsignaled_with_auto_refresh_changed() + ? layer_state_t::eFlagsChanged + : 0); if (changedFlags & deniedChanges) { ATRACE_FORMAT_INSTANT("%s: false [has denied changes flags 0x%" PRIx64 "]", __func__, - s.what & deniedChanges); + changedFlags & deniedChanges); return false; } diff --git a/services/surfaceflinger/ScreenCaptureOutput.cpp b/services/surfaceflinger/ScreenCaptureOutput.cpp index dd03366bcc..8bb72b8470 100644 --- a/services/surfaceflinger/ScreenCaptureOutput.cpp +++ b/services/surfaceflinger/ScreenCaptureOutput.cpp @@ -30,7 +30,7 @@ std::shared_ptr<ScreenCaptureOutput> createScreenCaptureOutput(ScreenCaptureOutp ScreenCaptureOutput, compositionengine::CompositionEngine, const RenderArea&, const compositionengine::Output::ColorProfile&, bool>(args.compositionEngine, args.renderArea, args.colorProfile, args.regionSampling, - args.dimInGammaSpaceForEnhancedScreenshots); + args.dimInGammaSpaceForEnhancedScreenshots, args.enableLocalTonemapping); output->editState().isSecure = args.renderArea.isSecure(); output->editState().isProtected = args.isProtected; output->setCompositionEnabled(true); @@ -63,11 +63,13 @@ std::shared_ptr<ScreenCaptureOutput> createScreenCaptureOutput(ScreenCaptureOutp ScreenCaptureOutput::ScreenCaptureOutput( const RenderArea& renderArea, const compositionengine::Output::ColorProfile& colorProfile, - bool regionSampling, bool dimInGammaSpaceForEnhancedScreenshots) + bool regionSampling, bool dimInGammaSpaceForEnhancedScreenshots, + bool enableLocalTonemapping) : mRenderArea(renderArea), mColorProfile(colorProfile), mRegionSampling(regionSampling), - mDimInGammaSpaceForEnhancedScreenshots(dimInGammaSpaceForEnhancedScreenshots) {} + mDimInGammaSpaceForEnhancedScreenshots(dimInGammaSpaceForEnhancedScreenshots), + mEnableLocalTonemapping(enableLocalTonemapping) {} void ScreenCaptureOutput::updateColorProfile(const compositionengine::CompositionRefreshArgs&) { auto& outputState = editState(); @@ -88,6 +90,11 @@ renderengine::DisplaySettings ScreenCaptureOutput::generateClientCompositionDisp aidl::android::hardware::graphics::composer3::DimmingStage::GAMMA_OETF; } + if (mEnableLocalTonemapping) { + clientCompositionDisplay.tonemapStrategy = + renderengine::DisplaySettings::TonemapStrategy::Local; + } + return clientCompositionDisplay; } diff --git a/services/surfaceflinger/ScreenCaptureOutput.h b/services/surfaceflinger/ScreenCaptureOutput.h index 069f458bdb..c233ead575 100644 --- a/services/surfaceflinger/ScreenCaptureOutput.h +++ b/services/surfaceflinger/ScreenCaptureOutput.h @@ -39,6 +39,7 @@ struct ScreenCaptureOutputArgs { bool treat170mAsSrgb; bool dimInGammaSpaceForEnhancedScreenshots; bool isProtected = false; + bool enableLocalTonemapping = false; }; // ScreenCaptureOutput is used to compose a set of layers into a preallocated buffer. @@ -49,7 +50,8 @@ class ScreenCaptureOutput : public compositionengine::impl::Output { public: ScreenCaptureOutput(const RenderArea& renderArea, const compositionengine::Output::ColorProfile& colorProfile, - bool regionSampling, bool dimInGammaSpaceForEnhancedScreenshots); + bool regionSampling, bool dimInGammaSpaceForEnhancedScreenshots, + bool enableLocalTonemapping); void updateColorProfile(const compositionengine::CompositionRefreshArgs&) override; @@ -67,6 +69,7 @@ private: const compositionengine::Output::ColorProfile& mColorProfile; const bool mRegionSampling; const bool mDimInGammaSpaceForEnhancedScreenshots; + const bool mEnableLocalTonemapping; }; std::shared_ptr<ScreenCaptureOutput> createScreenCaptureOutput(ScreenCaptureOutputArgs); diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index b8b7a70677..5f81cd45cc 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -6522,12 +6522,17 @@ void SurfaceFlinger::dumpDisplayIdentificationData(std::string& result) const { uint8_t port; DisplayIdentificationData data; if (!getHwComposer().getDisplayIdentificationData(*hwcDisplayId, &port, &data)) { - result.append("no identification data\n"); + result.append("no display identification data\n"); + continue; + } + + if (data.empty()) { + result.append("empty display identification data\n"); continue; } if (!isEdid(data)) { - result.append("unknown identification data\n"); + result.append("unknown format for display identification data\n"); continue; } @@ -8326,6 +8331,9 @@ ftl::SharedFuture<FenceResult> SurfaceFlinger::renderScreenImpl( captureResults.capturedDataspace = requestedDataspace; + const bool enableLocalTonemapping = FlagManager::getInstance().local_tonemap_screenshots() && + !renderArea->getHintForSeamlessTransition(); + { Mutex::Autolock lock(mStateLock); const DisplayDevice* display = nullptr; @@ -8359,16 +8367,19 @@ ftl::SharedFuture<FenceResult> SurfaceFlinger::renderScreenImpl( displayBrightnessNits = sdrWhitePointNits; } else { displayBrightnessNits = state.displayBrightnessNits; - // Only clamp the display brightness if this is not a seamless transition. Otherwise - // for seamless transitions it's important to match the current display state as the - // buffer will be shown under these same conditions, and we want to avoid any - // flickers - if (sdrWhitePointNits > 1.0f && !renderArea->getHintForSeamlessTransition()) { - // Restrict the amount of HDR "headroom" in the screenshot to avoid over-dimming - // the SDR portion. 2.0 chosen by experimentation - constexpr float kMaxScreenshotHeadroom = 2.0f; - displayBrightnessNits = std::min(sdrWhitePointNits * kMaxScreenshotHeadroom, - displayBrightnessNits); + + if (!enableLocalTonemapping) { + // Only clamp the display brightness if this is not a seamless transition. + // Otherwise for seamless transitions it's important to match the current + // display state as the buffer will be shown under these same conditions, and we + // want to avoid any flickers + if (sdrWhitePointNits > 1.0f && !renderArea->getHintForSeamlessTransition()) { + // Restrict the amount of HDR "headroom" in the screenshot to avoid + // over-dimming the SDR portion. 2.0 chosen by experimentation + constexpr float kMaxScreenshotHeadroom = 2.0f; + displayBrightnessNits = std::min(sdrWhitePointNits * kMaxScreenshotHeadroom, + displayBrightnessNits); + } } } @@ -8400,7 +8411,8 @@ ftl::SharedFuture<FenceResult> SurfaceFlinger::renderScreenImpl( auto present = [this, buffer = capturedBuffer, dataspace = captureResults.capturedDataspace, sdrWhitePointNits, displayBrightnessNits, grayscale, isProtected, layerFEs = copyLayerFEs(), layerStack, regionSampling, - renderArea = std::move(renderArea), renderIntent]() -> FenceResult { + renderArea = std::move(renderArea), renderIntent, + enableLocalTonemapping]() -> FenceResult { std::unique_ptr<compositionengine::CompositionEngine> compositionEngine = mFactory.createCompositionEngine(); compositionEngine->setRenderEngine(mRenderEngine.get()); @@ -8409,7 +8421,11 @@ ftl::SharedFuture<FenceResult> SurfaceFlinger::renderScreenImpl( .renderIntent = renderIntent}; float targetBrightness = 1.0f; - if (dataspace == ui::Dataspace::BT2020_HLG) { + if (enableLocalTonemapping) { + // Boost the whole scene so that SDR white is at 1.0 while still communicating the hdr + // sdr ratio via display brightness / sdrWhite nits. + targetBrightness = sdrWhitePointNits / displayBrightnessNits; + } else if (dataspace == ui::Dataspace::BT2020_HLG) { const float maxBrightnessNits = displayBrightnessNits / sdrWhitePointNits * 203; // With a low dimming ratio, don't fit the entire curve. Otherwise mixed content // will appear way too bright. @@ -8435,7 +8451,8 @@ ftl::SharedFuture<FenceResult> SurfaceFlinger::renderScreenImpl( .treat170mAsSrgb = mTreat170mAsSrgb, .dimInGammaSpaceForEnhancedScreenshots = dimInGammaSpaceForEnhancedScreenshots, - .isProtected = isProtected}); + .isProtected = isProtected, + .enableLocalTonemapping = enableLocalTonemapping}); const float colorSaturation = grayscale ? 0 : 1; compositionengine::CompositionRefreshArgs refreshArgs{ diff --git a/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp b/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp index cfc8e99f49..97bd79fcee 100644 --- a/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp +++ b/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp @@ -592,4 +592,41 @@ TEST_F(LayerLifecycleManagerTest, layerSecureChangesSetsVisibilityChangeFlag) { mLifecycleManager.commitChanges(); } +TEST_F(LayerLifecycleManagerTest, isSimpleBufferUpdate) { + auto layer = rootLayer(1); + + // no buffer changes + EXPECT_FALSE(layer->isSimpleBufferUpdate({})); + + { + layer_state_t state; + state.what = layer_state_t::eBufferChanged; + EXPECT_TRUE(layer->isSimpleBufferUpdate(state)); + } + + { + layer_state_t state; + state.what = layer_state_t::eReparent | layer_state_t::eBufferChanged; + EXPECT_FALSE(layer->isSimpleBufferUpdate(state)); + } + + { + layer_state_t state; + state.what = layer_state_t::ePositionChanged | layer_state_t::eBufferChanged; + state.x = 9; + state.y = 10; + EXPECT_FALSE(layer->isSimpleBufferUpdate(state)); + } + + { + layer->x = 9; + layer->y = 10; + layer_state_t state; + state.what = layer_state_t::ePositionChanged | layer_state_t::eBufferChanged; + state.x = 9; + state.y = 10; + EXPECT_TRUE(layer->isSimpleBufferUpdate(state)); + } +} + } // namespace android::surfaceflinger::frontend diff --git a/vulkan/libvulkan/driver.cpp b/vulkan/libvulkan/driver.cpp index 7ea98f5469..3f89960e32 100644 --- a/vulkan/libvulkan/driver.cpp +++ b/vulkan/libvulkan/driver.cpp @@ -964,14 +964,20 @@ PFN_vkVoidFunction GetInstanceProcAddr(VkInstance instance, const char* pName) { PFN_vkVoidFunction GetDeviceProcAddr(VkDevice device, const char* pName) { const ProcHook* hook = GetProcHook(pName); + PFN_vkVoidFunction drv_func = GetData(device).driver.GetDeviceProcAddr(device, pName); + if (!hook) - return GetData(device).driver.GetDeviceProcAddr(device, pName); + return drv_func; if (hook->type != ProcHook::DEVICE) { ALOGE("internal vkGetDeviceProcAddr called for %s", pName); return nullptr; } + // Don't hook if we don't have a device entry function below for the core function. + if (!drv_func && (hook->extension >= ProcHook::EXTENSION_CORE_1_0)) + return nullptr; + return (GetData(device).hook_extensions[hook->extension]) ? hook->proc : nullptr; } diff --git a/vulkan/scripts/driver_generator.py b/vulkan/scripts/driver_generator.py index 78b550c202..48c0ae9304 100644 --- a/vulkan/scripts/driver_generator.py +++ b/vulkan/scripts/driver_generator.py @@ -239,6 +239,8 @@ struct ProcHook { f.write(gencom.indent(2) + gencom.base_ext_name(ext) + ',\n') f.write('\n') + # EXTENSION_CORE_xxx API list must be the last set of enums after the extensions. + # This allows to easily identify "a" core function hook for version in gencom.version_code_list: f.write(gencom.indent(2) + 'EXTENSION_CORE_' + version + ',\n') |