Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1b1853f9 authored by Alec Mouri's avatar Alec Mouri
Browse files

Support capturing a gainmapped screenshot

This uses the gainmap concept from UltraHDR and ISO 21496-1 to produce
screenshots that (a) can be rendered in HDR and (b) that are perfectly
backwards-compatible on SDR displays are scenes where displaying SDR is
preferable.

The technical details of the screenshot process at a high level are:
* A client requests that they want a gainmap attached to the screenshot
  result
* SurfaceFlinger asks RenderEngine to perform three render passes
    * First, render the SDR base image, tonemapping using MouriMap
    * Second, render an HDR rendition. This is *also* tonemapped using
      MouriMap to the current display conditions. For HDR UI, this is
      effectively a no-op. For PQ and HLG content, this allows for
      approximating what is on-screen as closely as possible. Note that
      to preserve precision as much as possible, we require an FP16
      framebuffer.
    * Finally, combine the SDR and HDR renditions into a gainmap
* The client now receives the base image with the gainmap, as well as an
  HDR/SDR ratio. The client can use this information to generate gainmap
  metadata for encoding or round-tripping to the display.

This MVP is sufficient for generating screenshots through adb screencap.
Eventually, this can be used to integrate with the applications that
want HDR screenshots, or to remove hacks that disable tonemapping during
certain system animations.

Bug: 329470026
Flag: com.android.graphics.surfaceflinger.flags.true_hdr_screenshots
Test: builds
Test: adb screencap
Change-Id: I0434059d957bb6cc38d019743619d72dda72a269
parent 2b96d359
Loading
Loading
Loading
Loading
+15 −0
Original line number Diff line number Diff line
@@ -40,6 +40,13 @@ status_t ScreenCaptureResults::writeToParcel(android::Parcel* parcel) const {
    SAFE_PARCEL(parcel->writeBool, capturedSecureLayers);
    SAFE_PARCEL(parcel->writeBool, capturedHdrLayers);
    SAFE_PARCEL(parcel->writeUint32, static_cast<uint32_t>(capturedDataspace));
    if (optionalGainMap != nullptr) {
        SAFE_PARCEL(parcel->writeBool, true);
        SAFE_PARCEL(parcel->write, *optionalGainMap);
    } else {
        SAFE_PARCEL(parcel->writeBool, false);
    }
    SAFE_PARCEL(parcel->writeFloat, hdrSdrRatio);
    return NO_ERROR;
}

@@ -68,6 +75,14 @@ status_t ScreenCaptureResults::readFromParcel(const android::Parcel* parcel) {
    uint32_t dataspace = 0;
    SAFE_PARCEL(parcel->readUint32, &dataspace);
    capturedDataspace = static_cast<ui::Dataspace>(dataspace);

    bool hasGainmap;
    SAFE_PARCEL(parcel->readBool, &hasGainmap);
    if (hasGainmap) {
        optionalGainMap = new GraphicBuffer();
        SAFE_PARCEL(parcel->read, *optionalGainMap);
    }
    SAFE_PARCEL(parcel->readFloat, &hdrSdrRatio);
    return NO_ERROR;
}

+5 −0
Original line number Diff line number Diff line
@@ -69,5 +69,10 @@ parcelable CaptureArgs {
    // exact colorspace is not an appropriate intermediate result.
    // Note that if the caller is requesting a specific dataspace, this hint does nothing.
    boolean hintForSeamlessTransition = false;

    // Allows the screenshot to attach a gainmap, which allows for a per-pixel
    // transformation of the screenshot to another luminance range, typically
    // mapping an SDR base image into HDR.
    boolean attachGainmap = false;
}
+5 −0
Original line number Diff line number Diff line
@@ -36,6 +36,11 @@ public:
    bool capturedSecureLayers{false};
    bool capturedHdrLayers{false};
    ui::Dataspace capturedDataspace{ui::Dataspace::V0_SRGB};
    // A gainmap that can be used to "lift" the screenshot into HDR
    sp<GraphicBuffer> optionalGainMap;
    // HDR/SDR ratio value that fully applies the gainmap.
    // Note that we use 1/64 epsilon offsets to eliminate precision issues
    float hdrSdrRatio{1.0f};
};

} // namespace android::gui
+1 −0
Original line number Diff line number Diff line
@@ -100,6 +100,7 @@ filegroup {
        "skia/debug/SkiaCapture.cpp",
        "skia/debug/SkiaMemoryReporter.cpp",
        "skia/filters/BlurFilter.cpp",
        "skia/filters/GainmapFactory.cpp",
        "skia/filters/GaussianBlurFilter.cpp",
        "skia/filters/KawaseBlurDualFilter.cpp",
        "skia/filters/KawaseBlurFilter.cpp",
+23 −5
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@
#include "skia/GraphiteVkRenderEngine.h"
#include "skia/SkiaGLRenderEngine.h"
#include "threaded/RenderEngineThreaded.h"
#include "ui/GraphicTypes.h"

#include <com_android_graphics_surfaceflinger_flags.h>
#include <cutils/properties.h>
@@ -101,17 +102,34 @@ ftl::Future<FenceResult> RenderEngine::drawLayers(const DisplaySettings& display
                                                  base::unique_fd&& bufferFence) {
    const auto resultPromise = std::make_shared<std::promise<FenceResult>>();
    std::future<FenceResult> resultFuture = resultPromise->get_future();
    updateProtectedContext(layers, buffer);
    updateProtectedContext(layers, {buffer.get()});
    drawLayersInternal(std::move(resultPromise), display, layers, buffer, std::move(bufferFence));
    return resultFuture;
}

ftl::Future<FenceResult> RenderEngine::drawGainmap(
        const std::shared_ptr<ExternalTexture>& sdr, base::borrowed_fd&& sdrFence,
        const std::shared_ptr<ExternalTexture>& hdr, base::borrowed_fd&& hdrFence,
        float hdrSdrRatio, ui::Dataspace dataspace,
        const std::shared_ptr<ExternalTexture>& gainmap) {
    const auto resultPromise = std::make_shared<std::promise<FenceResult>>();
    std::future<FenceResult> resultFuture = resultPromise->get_future();
    updateProtectedContext({}, {sdr.get(), hdr.get(), gainmap.get()});
    drawGainmapInternal(std::move(resultPromise), sdr, std::move(sdrFence), hdr,
                        std::move(hdrFence), hdrSdrRatio, dataspace, gainmap);
    return resultFuture;
}

void RenderEngine::updateProtectedContext(const std::vector<LayerSettings>& layers,
                                          const std::shared_ptr<ExternalTexture>& buffer) {
                                          vector<const ExternalTexture*> buffers) {
    const bool needsProtectedContext =
            (buffer && (buffer->getUsage() & GRALLOC_USAGE_PROTECTED)) ||
            std::any_of(layers.begin(), layers.end(), [](const LayerSettings& layer) {
                const std::shared_ptr<ExternalTexture>& buffer = layer.source.buffer.buffer;
            std::any_of(layers.begin(), layers.end(),
                        [](const LayerSettings& layer) {
                            const std::shared_ptr<ExternalTexture>& buffer =
                                    layer.source.buffer.buffer;
                            return buffer && (buffer->getUsage() & GRALLOC_USAGE_PROTECTED);
                        }) ||
            std::any_of(buffers.begin(), buffers.end(), [](const ExternalTexture* buffer) {
                return buffer && (buffer->getUsage() & GRALLOC_USAGE_PROTECTED);
            });
    useProtectedContext(needsProtectedContext);
Loading