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

Commit 06f456db authored by Melody Hsu's avatar Melody Hsu
Browse files

Reduce SF stateLock on screenshot path

Minimizes the areas that we accesss mStateLock on the screenshot
path by grouping all work that requires mStateLock in the same
stage of the screenshot. Simplifies the captureDisplay functions
and RegionSamplingThread to only access the display on the main
thread with the state lock held. This removes the need to pass
around the display as a wp, which was originally necessary due to
sp<DisplayDevice> lifetime limitations.

Bug: b/377758217, b/159112860
Test: atest SurfaceFlinger_test
Test: atest CompositionTest
Test: atest RegionSamplingTest (libgui and sf)
Flag: EXEMPT, refactor
Change-Id: Ia88cf5dfbea35cd0531e469e4d01276ebce9feb4
parent 22bca30e
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;
+115 −131
Original line number Diff line number Diff line
@@ -7457,33 +7457,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");
@@ -7491,88 +7464,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"};
@@ -7722,13 +7648,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
@@ -7740,7 +7667,7 @@ bool SurfaceFlinger::getSnapshotsFromMainThread(ScreenshotArgs& args) {
                                                        ui::UNASSIGNED_LAYER_STACK);
                    }
                }
                return getDisplayStateOnMainThread(args);
                return status;
            })
            .get();
}
@@ -7749,6 +7676,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.",
@@ -7757,12 +7689,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()));
@@ -7839,17 +7765,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 {
@@ -7866,10 +7791,69 @@ 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.displayIdVariant = display->getDisplayIdVariant();
        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.displayIdVariant = display->getDisplayIdVariant();
        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) {
@@ -7884,10 +7868,10 @@ 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;
        }
        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
@@ -904,9 +904,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};
@@ -963,12 +965,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