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

Commit 36233224 authored by Melody Hsu's avatar Melody Hsu Committed by Android (Google) Code Review
Browse files

Merge changes from topic "revert-33583074-YPLAWPHJJE" into main

* changes:
  Revert^2 "[Lut screenshot] captureLayers doesn't update"
  Revert^2 "Reduce SF stateLock on screenshot path"
parents e61a93c6 a555add3
Loading
Loading
Loading
Loading
+8 −27
Original line number Diff line number Diff line
@@ -214,7 +214,7 @@ void RegionSamplingThread::binderDied(const wp<IBinder>& who) {
}

float sampleArea(const uint32_t* data, int32_t width, int32_t height, int32_t stride,
                 uint32_t orientation, const Rect& sample_area) {
                 const Rect& sample_area) {
    if (!sample_area.isValid() || (sample_area.getWidth() > width) ||
        (sample_area.getHeight() > height)) {
        ALOGE("invalid sampling region requested");
@@ -243,7 +243,7 @@ float sampleArea(const uint32_t* data, int32_t width, int32_t height, int32_t st

std::vector<float> RegionSamplingThread::sampleBuffer(
        const sp<GraphicBuffer>& buffer, const Point& leftTop,
        const std::vector<RegionSamplingThread::Descriptor>& descriptors, uint32_t orientation) {
        const std::vector<RegionSamplingThread::Descriptor>& descriptors) {
    void* data_raw = nullptr;
    buffer->lock(GRALLOC_USAGE_SW_READ_OFTEN, &data_raw);
    std::shared_ptr<uint32_t> data(reinterpret_cast<uint32_t*>(data_raw),
@@ -256,7 +256,7 @@ std::vector<float> RegionSamplingThread::sampleBuffer(
    std::vector<float> lumas(descriptors.size());
    std::transform(descriptors.begin(), descriptors.end(), lumas.begin(),
                   [&](auto const& descriptor) {
                       return sampleArea(data.get(), width, height, stride, orientation,
                       return sampleArea(data.get(), width, height, stride,
                                         descriptor.area - leftTop);
                   });
    return lumas;
@@ -270,23 +270,6 @@ void RegionSamplingThread::captureSample() {
        return;
    }

    wp<const DisplayDevice> displayWeak;

    ui::LayerStack layerStack;
    ui::Transform::RotationFlags orientation;
    ui::Size displaySize;
    Rect layerStackSpaceRect;

    {
        // TODO(b/159112860): Don't keep sp<DisplayDevice> outside of SF main thread
        const sp<const DisplayDevice> display = mFlinger.getFrontInternalDisplay();
        displayWeak = display;
        layerStack = display->getLayerStack();
        orientation = ui::Transform::toRotationFlags(display->getOrientation());
        displaySize = display->getSize();
        layerStackSpaceRect = display->getLayerStackSpaceRect();
    }

    std::vector<RegionSamplingThread::Descriptor> descriptors;
    Region sampleRegion;
    for (const auto& [listener, descriptor] : mDescriptors) {
@@ -360,15 +343,13 @@ void RegionSamplingThread::captureSample() {
    }

    SurfaceFlinger::ScreenshotArgs
            screenshotArgs{.captureTypeVariant = displayWeak,
            screenshotArgs{.captureTypeVariant = std::monostate{},
                           .displayIdVariant = std::nullopt,
                           .snapshotRequest =
                                   SurfaceFlinger::SnapshotRequestArgs{.uid = gui::Uid::INVALID,
                                                                       .layerStack = layerStack,
                                                                       .snapshotFilterFn =
                                                                               filterFn},
                           .sourceCrop =
                                   sampledBounds.isEmpty() ? layerStackSpaceRect : sampledBounds,
                           .sourceCrop = sampledBounds,
                           .size = sampledBounds.getSize(),
                           .dataspace = ui::Dataspace::V0_SRGB,
                           .disableBlur = true,
@@ -378,7 +359,7 @@ void RegionSamplingThread::captureSample() {
                           .debugName = "RegionSampling"};

    std::vector<std::pair<Layer*, sp<LayerFE>>> layers;
    mFlinger.getSnapshotsFromMainThread(screenshotArgs);
    mFlinger.setScreenshotSnapshotsAndDisplayState(screenshotArgs);
    FenceResult fenceResult = mFlinger.captureScreenshot(screenshotArgs, buffer, nullptr).get();
    if (fenceResult.ok()) {
        fenceResult.value()->waitForever(LOG_TAG);
@@ -392,8 +373,8 @@ void RegionSamplingThread::captureSample() {
    }

    ALOGV("Sampling %zu descriptors", activeDescriptors.size());
    std::vector<float> lumas = sampleBuffer(buffer->getBuffer(), sampledBounds.leftTop(),
                                            activeDescriptors, orientation);
    std::vector<float> lumas =
            sampleBuffer(buffer->getBuffer(), sampledBounds.leftTop(), activeDescriptors);
    if (lumas.size() != activeDescriptors.size()) {
        ALOGW("collected %zu median luma values for %zu descriptors", lumas.size(),
              activeDescriptors.size());
+2 −2
Original line number Diff line number Diff line
@@ -44,7 +44,7 @@ struct SamplingOffsetCallback;
using gui::IRegionSamplingListener;

float sampleArea(const uint32_t* data, int32_t width, int32_t height, int32_t stride,
                 uint32_t orientation, const Rect& area);
                 const Rect& area);

class RegionSamplingThread : public IBinder::DeathRecipient {
public:
@@ -96,7 +96,7 @@ private:

    std::vector<float> sampleBuffer(
            const sp<GraphicBuffer>& buffer, const Point& leftTop,
            const std::vector<RegionSamplingThread::Descriptor>& descriptors, uint32_t orientation);
            const std::vector<RegionSamplingThread::Descriptor>& descriptors);

    void doSample(std::optional<std::chrono::steady_clock::time_point> samplingDeadline);
    void binderDied(const wp<IBinder>& who) override;
+114 −131
Original line number Diff line number Diff line
@@ -7510,33 +7510,6 @@ void SurfaceFlinger::captureDisplay(const DisplayCaptureArgs& args,
        return;
    }

    wp<const DisplayDevice> displayWeak;
    ftl::Optional<DisplayIdVariant> displayIdVariantOpt;
    ui::LayerStack layerStack;
    ui::Size reqSize(args.width, args.height);
    Rect layerStackSpaceRect;
    bool displayIsSecure;

    {
        Mutex::Autolock lock(mStateLock);
        sp<DisplayDevice> display = getDisplayDeviceLocked(args.displayToken);
        if (!display) {
            ALOGD("Unable to find display device for captureDisplay");
            invokeScreenCaptureError(NAME_NOT_FOUND, captureListener);
            return;
        }
        displayWeak = display;
        displayIdVariantOpt = display->getDisplayIdVariant();
        layerStack = display->getLayerStack();
        displayIsSecure = display->isSecure();

        layerStackSpaceRect = display->getLayerStackSpaceRect();
        // set the requested width/height to the logical display layer stack rect size by default
        if (args.width == 0 || args.height == 0) {
            reqSize = layerStackSpaceRect.getSize();
        }
    }

    auto excludeLayerIds = getExcludeLayerIds(captureArgs.excludeHandles);
    if (!excludeLayerIds) {
        ALOGD("Invalid layer handle passed as excludeLayer to captureDisplay");
@@ -7544,88 +7517,41 @@ void SurfaceFlinger::captureDisplay(const DisplayCaptureArgs& args,
        return;
    }

    ScreenshotArgs screenshotArgs{.captureTypeVariant = displayWeak,
                                  .displayIdVariant = displayIdVariantOpt,
    ScreenshotArgs screenshotArgs{.captureTypeVariant = args.displayToken,
                                  .snapshotRequest =
                                          SnapshotRequestArgs{.uid = gui::Uid{static_cast<uid_t>(
                                                                      captureArgs.uid)},
                                                              .layerStack = layerStack,
                                                              .excludeLayerIds =
                                                                      excludeLayerIds.value()},
                                  .sourceCrop = gui::aidl_utils::fromARect(captureArgs.sourceCrop),
                                  .size = reqSize,
                                  .size = ui::Size(args.width, args.height),
                                  .dataspace = static_cast<ui::Dataspace>(captureArgs.dataspace),
                                  .disableBlur = false,
                                  .isGrayscale = captureArgs.grayscale,
                                  .isSecure = captureArgs.captureSecureLayers && displayIsSecure,
                                  .isSecure = captureArgs.captureSecureLayers,
                                  .includeProtected = captureArgs.allowProtected,
                                  .seamlessTransition = captureArgs.hintForSeamlessTransition,
                                  .debugName = "ScreenCapture"};

    if (screenshotArgs.sourceCrop.isEmpty()) {
        screenshotArgs.sourceCrop = layerStackSpaceRect;
    }

    captureScreenCommon(screenshotArgs, static_cast<ui::PixelFormat>(captureArgs.pixelFormat),
                        captureListener);
}

void SurfaceFlinger::captureDisplay(DisplayId displayId, const CaptureArgs& args,
                                    const sp<IScreenCaptureListener>& captureListener) {
    ui::LayerStack layerStack;
    wp<const DisplayDevice> displayWeak;
    ftl::Optional<DisplayIdVariant> displayIdVariantOpt;
    ui::Size size;
    Rect layerStackSpaceRect;
    bool displayIsSecure;

    {
        Mutex::Autolock lock(mStateLock);

        const auto display = getDisplayDeviceLocked(displayId);
        if (!display) {
            ALOGD("Unable to find display device for captureDisplay");
            invokeScreenCaptureError(NAME_NOT_FOUND, captureListener);
            return;
        }

        displayWeak = display;
        displayIdVariantOpt = display->getDisplayIdVariant();
        layerStack = display->getLayerStack();
        layerStackSpaceRect = display->getLayerStackSpaceRect();
        size = display->getLayerStackSpaceRect().getSize();
        displayIsSecure = display->isSecure();
    }

    size.width *= args.frameScaleX;
    size.height *= args.frameScaleY;

    // We could query a real value for this but it'll be a long, long time until we support
    // displays that need upwards of 1GB per buffer so...
    constexpr auto kMaxTextureSize = 16384;
    if (size.width <= 0 || size.height <= 0 || size.width >= kMaxTextureSize ||
        size.height >= kMaxTextureSize) {
        ALOGD("captureDisplay resolved to invalid size %d x %d", size.width, size.height);
        invokeScreenCaptureError(BAD_VALUE, captureListener);
        return;
    }

    if (captureListener == nullptr) {
        ALOGE("capture screen must provide a capture listener callback");
        invokeScreenCaptureError(BAD_VALUE, captureListener);
        return;
    }

    ScreenshotArgs screenshotArgs{.captureTypeVariant = displayWeak,
                                  .displayIdVariant = displayIdVariantOpt,
                                  .snapshotRequest = SnapshotRequestArgs{.uid = gui::Uid::INVALID,
                                                                         .layerStack = layerStack},
                                  .sourceCrop = layerStackSpaceRect,
                                  .size = size,
    ScreenshotArgs screenshotArgs{.captureTypeVariant = displayId,
                                  .snapshotRequest = SnapshotRequestArgs{.uid = gui::Uid::INVALID},
                                  .size = ui::Size(args.frameScaleX, args.frameScaleY),
                                  .dataspace = static_cast<ui::Dataspace>(args.dataspace),
                                  .disableBlur = false,
                                  .isGrayscale = false,
                                  .isSecure = args.captureSecureLayers && displayIsSecure,
                                  .isSecure = args.captureSecureLayers,
                                  .includeProtected = false,
                                  .seamlessTransition = args.hintForSeamlessTransition,
                                  .debugName = "ScreenCapture"};
@@ -7775,13 +7701,14 @@ bool SurfaceFlinger::layersHasProtectedLayer(
// Getting layer snapshots and accessing display state should take place on
// main thread. Accessing display requires mStateLock, and contention for
// this lock is reduced when grabbed from the main thread, thus also reducing
// risk of deadlocks. Returns false if no display is found.
bool SurfaceFlinger::getSnapshotsFromMainThread(ScreenshotArgs& args) {
// risk of deadlocks. Returns an error status if no display is found.
status_t SurfaceFlinger::setScreenshotSnapshotsAndDisplayState(ScreenshotArgs& args) {
    return mScheduler
            ->schedule([=, this, &args]() REQUIRES(kMainThreadContext) {
                SFTRACE_NAME_FOR_TRACK(WorkloadTracer::TRACK_NAME, "Screenshot");
                mPowerAdvisor->setScreenshotWorkload();
                SFTRACE_NAME("getSnapshotsFromMainThread");
                SFTRACE_NAME("setScreenshotSnapshotsAndDisplayState");
                status_t status = setScreenshotDisplayState(args);
                args.layers = getLayerSnapshotsForScreenshots(args.snapshotRequest);
                // Non-threaded RenderEngine eventually returns to the main thread a 2nd time
                // to complete the screenshot. Release fences should only be added during the 2nd
@@ -7793,7 +7720,7 @@ bool SurfaceFlinger::getSnapshotsFromMainThread(ScreenshotArgs& args) {
                                                        ui::UNASSIGNED_LAYER_STACK);
                    }
                }
                return getDisplayStateOnMainThread(args);
                return status;
            })
            .get();
}
@@ -7802,6 +7729,11 @@ void SurfaceFlinger::captureScreenCommon(ScreenshotArgs& args, ui::PixelFormat r
                                         const sp<IScreenCaptureListener>& captureListener) {
    SFTRACE_CALL();

    status_t status = setScreenshotSnapshotsAndDisplayState(args);
    if (status != OK) {
        invokeScreenCaptureError(status, captureListener);
    }

    if (exceedsMaxRenderTargetSize(args.size.getWidth(), args.size.getHeight())) {
        ALOGE("Attempted to capture screen with size (%" PRId32 ", %" PRId32
              ") that exceeds render target size limit.",
@@ -7810,12 +7742,6 @@ void SurfaceFlinger::captureScreenCommon(ScreenshotArgs& args, ui::PixelFormat r
        return;
    }

    bool hasDisplayState = getSnapshotsFromMainThread(args);
    if (!hasDisplayState) {
        ALOGD("Display state not found");
        invokeScreenCaptureError(NO_MEMORY, captureListener);
    }

    const bool hasHdrLayer =
            std::any_of(args.layers.cbegin(), args.layers.cend(), [this](const auto& layer) {
                return isHdrLayer(*(layer.second->mSnapshot.get()));
@@ -7892,17 +7818,16 @@ void SurfaceFlinger::captureScreenCommon(ScreenshotArgs& args, ui::PixelFormat r
    futureFence.get();
}

// Returns true if display is found and args was populated with display state
// data. Otherwise, returns false.
bool SurfaceFlinger::getDisplayStateOnMainThread(ScreenshotArgs& args) {
    sp<const DisplayDevice> display = nullptr;
    {
// Returns OK if display is found and args was populated with display state
// data. Otherwise, returns an error status.
status_t SurfaceFlinger::setScreenshotDisplayState(ScreenshotArgs& args) {
    Mutex::Autolock lock(mStateLock);
    sp<const DisplayDevice> display = nullptr;

    // Screenshot initiated through captureLayers
    if (auto* layerSequence = std::get_if<int32_t>(&args.captureTypeVariant)) {
        // LayerSnapshotBuilder should only be accessed from the main thread.
            const frontend::LayerSnapshot* snapshot =
                    mLayerSnapshotBuilder.getSnapshot(*layerSequence);
        const frontend::LayerSnapshot* snapshot = mLayerSnapshotBuilder.getSnapshot(*layerSequence);
        if (!snapshot) {
            ALOGW("Couldn't find layer snapshot for %d", *layerSequence);
        } else {
@@ -7919,10 +7844,67 @@ bool SurfaceFlinger::getDisplayStateOnMainThread(ScreenshotArgs& args) {
            args.debugName.append(", ").append(snapshot->debugName);
        }

            // Screenshot initiated through captureDisplay
        } else if (auto* displayWeak =
                           std::get_if<wp<const DisplayDevice>>(&args.captureTypeVariant)) {
            display = displayWeak->promote();
        // Screenshot initiated through captureDisplay by ID
    } else if (auto* displayId = std::get_if<DisplayId>(&args.captureTypeVariant)) {
        display = getDisplayDeviceLocked(*displayId);
        if (!display) {
            ALOGD("Unable to find display device for captureDisplay by ID");
            return NAME_NOT_FOUND;
        }

        Rect layerStackSpaceRect = display->getLayerStackSpaceRect();
        args.isSecure &= display->isSecure();
        args.snapshotRequest.layerStack = display->getLayerStack();
        args.sourceCrop = layerStackSpaceRect;
        args.size.width *= layerStackSpaceRect.getWidth();
        args.size.height *= layerStackSpaceRect.getHeight();

        // We could query a real value for this but it'll be a long, long time until we support
        // displays that need upwards of 1GB per buffer so...
        constexpr auto kMaxTextureSize = 16384;
        if (args.size.width <= 0 || args.size.height <= 0 || args.size.width >= kMaxTextureSize ||
            args.size.height >= kMaxTextureSize) {
            ALOGD("captureDisplay resolved to invalid size %d x %d", args.size.width,
                  args.size.height);
            return BAD_VALUE;
        }

        // Screenshot initiated through captureDisplay by displayToken
    } else if (auto* displayToken = std::get_if<sp<IBinder>>(&args.captureTypeVariant)) {
        display = getDisplayDeviceLocked(*displayToken);
        if (!display) {
            ALOGD("Unable to find display device for captureDisplay by displayToken");
            return NAME_NOT_FOUND;
        }

        Rect layerStackSpaceRect = display->getLayerStackSpaceRect();
        args.isSecure &= display->isSecure();
        args.snapshotRequest.layerStack = display->getLayerStack();

        if (args.sourceCrop.isEmpty()) {
            args.sourceCrop = layerStackSpaceRect;
        }
        // Set the requested width/height to the logical display layer stack rect size by
        // default
        if (args.size.width == 0 || args.size.height == 0) {
            args.size = layerStackSpaceRect.getSize();
        }

        // Screenshot initiated for region sampling
    } else if (std::holds_alternative<std::monostate>(args.captureTypeVariant)) {
        display = getFrontInternalDisplayLocked();
        if (!display) {
            ALOGD("Unable to find display device for region sampling");
            return NAME_NOT_FOUND;
        }

        args.snapshotRequest.layerStack = display->getLayerStack();

        if (args.sourceCrop.isEmpty()) {
            Rect layerStackSpaceRect = display->getLayerStackSpaceRect();
            args.sourceCrop = layerStackSpaceRect;
            args.size = layerStackSpaceRect.getSize();
        }
    }

    if (display == nullptr) {
@@ -7937,10 +7919,11 @@ bool SurfaceFlinger::getDisplayStateOnMainThread(ScreenshotArgs& args) {
        args.colorMode = state.colorMode;
        args.debugName.append(", ").append(display->getDisplayName());
        args.debugName.append(" (").append(to_string(display->getId())).append(")");
            return true;
        }
        args.displayIdVariant = display->getDisplayIdVariant();
        return OK;
    }
    return false;
    ALOGD("Display state not found");
    return NO_MEMORY;
}

ftl::SharedFuture<FenceResult> SurfaceFlinger::captureScreenshot(
+7 −5
Original line number Diff line number Diff line
@@ -906,9 +906,11 @@ private:
     */
    struct ScreenshotArgs {
        // Contains the sequence ID of the parent layer if the screenshot is
        // initiated though captureLayers(), or the display that the render
        // result will be on if initiated through captureDisplay()
        std::variant<int32_t, wp<const DisplayDevice>> captureTypeVariant;
        // initiated though captureLayers(), or the displayToken or displayID
        // that the render result will be on if initiated through captureDisplay().
        // The monostate type is used to denote that the screenshot is initiated
        // for region sampling.
        std::variant<std::monostate, int32_t, sp<IBinder>, DisplayId> captureTypeVariant;

        // Display ID of the display the result will be on
        ftl::Optional<DisplayIdVariant> displayIdVariant{std::nullopt};
@@ -965,12 +967,12 @@ private:
        std::string debugName;
    };

    bool getSnapshotsFromMainThread(ScreenshotArgs& args);
    status_t setScreenshotSnapshotsAndDisplayState(ScreenshotArgs& args);

    void captureScreenCommon(ScreenshotArgs& args, ui::PixelFormat,
                             const sp<IScreenCaptureListener>&);

    bool getDisplayStateOnMainThread(ScreenshotArgs& args) REQUIRES(kMainThreadContext);
    status_t setScreenshotDisplayState(ScreenshotArgs& args) REQUIRES(kMainThreadContext);

    ftl::SharedFuture<FenceResult> captureScreenshot(
            ScreenshotArgs& args, const std::shared_ptr<renderengine::ExternalTexture>& buffer,
+9 −10
Original line number Diff line number Diff line
@@ -39,20 +39,19 @@ public:
    static int constexpr kWidth = 98;
    static int constexpr kStride = 100;
    static int constexpr kHeight = 29;
    static int constexpr kOrientation = ui::Transform::ROT_0;
    std::array<uint32_t, kHeight * kStride> buffer;
    Rect const whole_area{0, 0, kWidth, kHeight};
};

TEST_F(RegionSamplingTest, calculate_mean_white) {
    std::fill(buffer.begin(), buffer.end(), kWhite);
    EXPECT_THAT(sampleArea(buffer.data(), kWidth, kHeight, kStride, kOrientation, whole_area),
    EXPECT_THAT(sampleArea(buffer.data(), kWidth, kHeight, kStride, whole_area),
                testing::FloatEq(1.0f));
}

TEST_F(RegionSamplingTest, calculate_mean_black) {
    std::fill(buffer.begin(), buffer.end(), kBlack);
    EXPECT_THAT(sampleArea(buffer.data(), kWidth, kHeight, kStride, kOrientation, whole_area),
    EXPECT_THAT(sampleArea(buffer.data(), kWidth, kHeight, kStride, whole_area),
                testing::FloatEq(0.0f));
}

@@ -63,7 +62,7 @@ TEST_F(RegionSamplingTest, calculate_mean_partial_region) {
                                 whole_area.top + halfway_down};
    std::fill(buffer.begin(), buffer.begin() + half, 0);
    std::fill(buffer.begin() + half, buffer.end(), kWhite);
    EXPECT_THAT(sampleArea(buffer.data(), kWidth, kHeight, kStride, kOrientation, partial_region),
    EXPECT_THAT(sampleArea(buffer.data(), kWidth, kHeight, kStride, partial_region),
                testing::FloatEq(0.0f));
}

@@ -74,14 +73,14 @@ TEST_F(RegionSamplingTest, calculate_mean_mixed_values) {
        return pixel;
    });

    EXPECT_THAT(sampleArea(buffer.data(), kWidth, kHeight, kStride, kOrientation, whole_area),
    EXPECT_THAT(sampleArea(buffer.data(), kWidth, kHeight, kStride, whole_area),
                testing::FloatNear(0.16f, 0.01f));
}

TEST_F(RegionSamplingTest, bimodal_tiebreaker) {
    std::generate(buffer.begin(), buffer.end(),
                  [n = 0]() mutable { return (n++ % 2) ? kBlack : kWhite; });
    EXPECT_THAT(sampleArea(buffer.data(), kWidth, kHeight, kStride, kOrientation, whole_area),
    EXPECT_THAT(sampleArea(buffer.data(), kWidth, kHeight, kStride, whole_area),
                testing::FloatEq(0.5f));
}

@@ -90,19 +89,19 @@ TEST_F(RegionSamplingTest, bounds_checking) {
                  [n = 0]() mutable { return (n++ > (kStride * kHeight >> 1)) ? kBlack : kWhite; });

    Rect invalid_region{0, 0, 4, kHeight + 1};
    EXPECT_THAT(sampleArea(buffer.data(), kWidth, kHeight, kStride, kOrientation, invalid_region),
    EXPECT_THAT(sampleArea(buffer.data(), kWidth, kHeight, kStride, invalid_region),
                testing::Eq(0.0));

    invalid_region = Rect{0, 0, -4, kHeight};
    EXPECT_THAT(sampleArea(buffer.data(), kWidth, kHeight, kStride, kOrientation, invalid_region),
    EXPECT_THAT(sampleArea(buffer.data(), kWidth, kHeight, kStride, invalid_region),
                testing::Eq(0.0));

    invalid_region = Rect{3, 0, 2, 0};
    EXPECT_THAT(sampleArea(buffer.data(), kWidth, kHeight, kStride, kOrientation, invalid_region),
    EXPECT_THAT(sampleArea(buffer.data(), kWidth, kHeight, kStride, invalid_region),
                testing::Eq(0.0));

    invalid_region = Rect{0, 3, 0, 2};
    EXPECT_THAT(sampleArea(buffer.data(), kWidth, kHeight, kStride, kOrientation, invalid_region),
    EXPECT_THAT(sampleArea(buffer.data(), kWidth, kHeight, kStride, invalid_region),
                testing::Eq(0.0));
}

Loading