/*
 * Copyright 2022 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.
 */

// #define LOG_NDEBUG 0
#undef LOG_TAG
#define LOG_TAG "SurfaceFlinger"
#define ATRACE_TAG ATRACE_TAG_GRAPHICS

#include <gui/GLConsumer.h>
#include <gui/TraceUtils.h>
#include <math/vec3.h>
#include <system/window.h>
#include <utils/Log.h>

#include "LayerFE.h"
#include "SurfaceFlinger.h"
#include "common/FlagManager.h"
#include "ui/FenceResult.h"
#include "ui/LayerStack.h"

namespace android {

namespace {
constexpr float defaultMaxLuminance = 1000.0;

constexpr mat4 inverseOrientation(uint32_t transform) {
    const mat4 flipH(-1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1);
    const mat4 flipV(1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1);
    const mat4 rot90(0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1);
    mat4 tr;

    if (transform & NATIVE_WINDOW_TRANSFORM_ROT_90) {
        tr = tr * rot90;
    }
    if (transform & NATIVE_WINDOW_TRANSFORM_FLIP_H) {
        tr = tr * flipH;
    }
    if (transform & NATIVE_WINDOW_TRANSFORM_FLIP_V) {
        tr = tr * flipV;
    }
    return inverse(tr);
}

FloatRect reduce(const FloatRect& win, const Region& exclude) {
    if (CC_LIKELY(exclude.isEmpty())) {
        return win;
    }
    // Convert through Rect (by rounding) for lack of FloatRegion
    return Region(Rect{win}).subtract(exclude).getBounds().toFloatRect();
}

// Computes the transform matrix using the setFilteringEnabled to determine whether the
// transform matrix should be computed for use with bilinear filtering.
void getDrawingTransformMatrix(const std::shared_ptr<renderengine::ExternalTexture>& buffer,
                               Rect bufferCrop, uint32_t bufferTransform, bool filteringEnabled,
                               float outMatrix[16]) {
    if (!buffer) {
        ALOGE("Buffer should not be null!");
        return;
    }
    GLConsumer::computeTransformMatrix(outMatrix, static_cast<float>(buffer->getWidth()),
                                       static_cast<float>(buffer->getHeight()),
                                       buffer->getPixelFormat(), bufferCrop, bufferTransform,
                                       filteringEnabled);
}

} // namespace

LayerFE::LayerFE(const std::string& name) : mName(name) {}

LayerFE::~LayerFE() {
    // Ensures that no promise is left unfulfilled before the LayerFE is destroyed.
    // An unfulfilled promise could occur when a screenshot is attempted, but the
    // render area is invalid and there is no memory for the capture result.
    if (FlagManager::getInstance().ce_fence_promise() &&
        mReleaseFencePromiseStatus == ReleaseFencePromiseStatus::INITIALIZED) {
        setReleaseFence(Fence::NO_FENCE);
    }
}

const compositionengine::LayerFECompositionState* LayerFE::getCompositionState() const {
    return mSnapshot.get();
}

bool LayerFE::onPreComposition(bool) {
    return mSnapshot->hasReadyFrame;
}

std::optional<compositionengine::LayerFE::LayerSettings> LayerFE::prepareClientComposition(
        compositionengine::LayerFE::ClientCompositionTargetSettings& targetSettings) const {
    std::optional<compositionengine::LayerFE::LayerSettings> layerSettings =
            prepareClientCompositionInternal(targetSettings);
    // Nothing to render.
    if (!layerSettings) {
        return {};
    }

    // HWC requests to clear this layer.
    if (targetSettings.clearContent) {
        prepareClearClientComposition(*layerSettings, false /* blackout */);
        return layerSettings;
    }

    // set the shadow for the layer if needed
    prepareShadowClientComposition(*layerSettings, targetSettings.viewport);

    return layerSettings;
}

std::optional<compositionengine::LayerFE::LayerSettings> LayerFE::prepareClientCompositionInternal(
        compositionengine::LayerFE::ClientCompositionTargetSettings& targetSettings) const {
    ATRACE_CALL();
    compositionengine::LayerFE::LayerSettings layerSettings;
    layerSettings.geometry.boundaries =
            reduce(mSnapshot->geomLayerBounds, mSnapshot->transparentRegionHint);
    layerSettings.geometry.positionTransform = mSnapshot->geomLayerTransform.asMatrix4();

    // skip drawing content if the targetSettings indicate the content will be occluded
    const bool drawContent = targetSettings.realContentIsVisible || targetSettings.clearContent;
    layerSettings.skipContentDraw = !drawContent;

    if (!mSnapshot->colorTransformIsIdentity) {
        layerSettings.colorTransform = mSnapshot->colorTransform;
    }

    const auto& roundedCornerState = mSnapshot->roundedCorner;
    layerSettings.geometry.roundedCornersRadius = roundedCornerState.radius;
    layerSettings.geometry.roundedCornersCrop = roundedCornerState.cropRect;

    layerSettings.alpha = mSnapshot->alpha;
    layerSettings.sourceDataspace = mSnapshot->dataspace;

    // Override the dataspace transfer from 170M to sRGB if the device configuration requests this.
    // We do this here instead of in buffer info so that dumpsys can still report layers that are
    // using the 170M transfer.
    if (targetSettings.treat170mAsSrgb &&
        (layerSettings.sourceDataspace & HAL_DATASPACE_TRANSFER_MASK) ==
                HAL_DATASPACE_TRANSFER_SMPTE_170M) {
        layerSettings.sourceDataspace = static_cast<ui::Dataspace>(
                (layerSettings.sourceDataspace & HAL_DATASPACE_STANDARD_MASK) |
                (layerSettings.sourceDataspace & HAL_DATASPACE_RANGE_MASK) |
                HAL_DATASPACE_TRANSFER_SRGB);
    }

    layerSettings.whitePointNits = targetSettings.whitePointNits;
    switch (targetSettings.blurSetting) {
        case LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled:
            layerSettings.backgroundBlurRadius = mSnapshot->backgroundBlurRadius;
            layerSettings.blurRegions = mSnapshot->blurRegions;
            layerSettings.blurRegionTransform = mSnapshot->localTransformInverse.asMatrix4();
            break;
        case LayerFE::ClientCompositionTargetSettings::BlurSetting::BackgroundBlurOnly:
            layerSettings.backgroundBlurRadius = mSnapshot->backgroundBlurRadius;
            break;
        case LayerFE::ClientCompositionTargetSettings::BlurSetting::BlurRegionsOnly:
            layerSettings.blurRegions = mSnapshot->blurRegions;
            layerSettings.blurRegionTransform = mSnapshot->localTransformInverse.asMatrix4();
            break;
        case LayerFE::ClientCompositionTargetSettings::BlurSetting::Disabled:
        default:
            break;
    }
    layerSettings.stretchEffect = mSnapshot->stretchEffect;
    // Record the name of the layer for debugging further down the stack.
    layerSettings.name = mSnapshot->name;

    if (hasEffect() && !hasBufferOrSidebandStream()) {
        prepareEffectsClientComposition(layerSettings, targetSettings);
        return layerSettings;
    }

    prepareBufferStateClientComposition(layerSettings, targetSettings);
    return layerSettings;
}

void LayerFE::prepareClearClientComposition(LayerFE::LayerSettings& layerSettings,
                                            bool blackout) const {
    layerSettings.source.buffer.buffer = nullptr;
    layerSettings.source.solidColor = half3(0.0f, 0.0f, 0.0f);
    layerSettings.disableBlending = true;
    layerSettings.bufferId = 0;
    layerSettings.frameNumber = 0;

    // If layer is blacked out, force alpha to 1 so that we draw a black color layer.
    layerSettings.alpha = blackout ? 1.0f : 0.0f;
    layerSettings.name = mSnapshot->name;
}

void LayerFE::prepareEffectsClientComposition(
        compositionengine::LayerFE::LayerSettings& layerSettings,
        compositionengine::LayerFE::ClientCompositionTargetSettings& targetSettings) const {
    // If fill bounds are occluded or the fill color is invalid skip the fill settings.
    if (targetSettings.realContentIsVisible && fillsColor()) {
        // Set color for color fill settings.
        layerSettings.source.solidColor = mSnapshot->color.rgb;
    } else if (hasBlur() || drawShadows()) {
        layerSettings.skipContentDraw = true;
    }
}

void LayerFE::prepareBufferStateClientComposition(
        compositionengine::LayerFE::LayerSettings& layerSettings,
        compositionengine::LayerFE::ClientCompositionTargetSettings& targetSettings) const {
    ATRACE_CALL();
    if (CC_UNLIKELY(!mSnapshot->externalTexture)) {
        // If there is no buffer for the layer or we have sidebandstream where there is no
        // activeBuffer, then we need to return LayerSettings.
        return;
    }
    bool blackOutLayer;
    if (FlagManager::getInstance().display_protected()) {
        blackOutLayer = (mSnapshot->hasProtectedContent && !targetSettings.isProtected) ||
                (mSnapshot->isSecure && !targetSettings.isSecure);
    } else {
        blackOutLayer = (mSnapshot->hasProtectedContent && !targetSettings.isProtected) ||
                ((mSnapshot->isSecure || mSnapshot->hasProtectedContent) &&
                 !targetSettings.isSecure);
    }
    const bool bufferCanBeUsedAsHwTexture =
            mSnapshot->externalTexture->getUsage() & GraphicBuffer::USAGE_HW_TEXTURE;
    if (blackOutLayer || !bufferCanBeUsedAsHwTexture) {
        ALOGE_IF(!bufferCanBeUsedAsHwTexture, "%s is blacked out as buffer is not gpu readable",
                 mSnapshot->name.c_str());
        prepareClearClientComposition(layerSettings, true /* blackout */);
        return;
    }

    layerSettings.source.buffer.buffer = mSnapshot->externalTexture;
    layerSettings.source.buffer.isOpaque = mSnapshot->contentOpaque;
    layerSettings.source.buffer.fence = mSnapshot->acquireFence;
    layerSettings.source.buffer.usePremultipliedAlpha = mSnapshot->premultipliedAlpha;
    bool hasSmpte2086 = mSnapshot->hdrMetadata.validTypes & HdrMetadata::SMPTE2086;
    bool hasCta861_3 = mSnapshot->hdrMetadata.validTypes & HdrMetadata::CTA861_3;
    float maxLuminance = 0.f;
    if (hasSmpte2086 && hasCta861_3) {
        maxLuminance = std::min(mSnapshot->hdrMetadata.smpte2086.maxLuminance,
                                mSnapshot->hdrMetadata.cta8613.maxContentLightLevel);
    } else if (hasSmpte2086) {
        maxLuminance = mSnapshot->hdrMetadata.smpte2086.maxLuminance;
    } else if (hasCta861_3) {
        maxLuminance = mSnapshot->hdrMetadata.cta8613.maxContentLightLevel;
    } else {
        switch (layerSettings.sourceDataspace & HAL_DATASPACE_TRANSFER_MASK) {
            case HAL_DATASPACE_TRANSFER_ST2084:
            case HAL_DATASPACE_TRANSFER_HLG:
                // Behavior-match previous releases for HDR content
                maxLuminance = defaultMaxLuminance;
                break;
        }
    }
    layerSettings.source.buffer.maxLuminanceNits = maxLuminance;
    layerSettings.frameNumber = mSnapshot->frameNumber;
    layerSettings.bufferId = mSnapshot->externalTexture->getId();

    const bool useFiltering = targetSettings.needsFiltering ||
                              mSnapshot->geomLayerTransform.needsBilinearFiltering();

    // Query the texture matrix given our current filtering mode.
    float textureMatrix[16];
    getDrawingTransformMatrix(layerSettings.source.buffer.buffer, mSnapshot->geomContentCrop,
                              mSnapshot->geomBufferTransform, useFiltering,
                              textureMatrix);

    if (mSnapshot->geomBufferUsesDisplayInverseTransform) {
        /*
         * the code below applies the primary display's inverse transform to
         * the texture transform
         */
        uint32_t transform = SurfaceFlinger::getActiveDisplayRotationFlags();
        mat4 tr = inverseOrientation(transform);

        /**
         * TODO(b/36727915): This is basically a hack.
         *
         * Ensure that regardless of the parent transformation,
         * this buffer is always transformed from native display
         * orientation to display orientation. For example, in the case
         * of a camera where the buffer remains in native orientation,
         * we want the pixels to always be upright.
         */
        const auto parentTransform = mSnapshot->parentTransform;
        tr = tr * inverseOrientation(parentTransform.getOrientation());

        // and finally apply it to the original texture matrix
        const mat4 texTransform(mat4(static_cast<const float*>(textureMatrix)) * tr);
        memcpy(textureMatrix, texTransform.asArray(), sizeof(textureMatrix));
    }

    const Rect win{layerSettings.geometry.boundaries};
    float bufferWidth = static_cast<float>(mSnapshot->bufferSize.getWidth());
    float bufferHeight = static_cast<float>(mSnapshot->bufferSize.getHeight());

    // Layers can have a "buffer size" of [0, 0, -1, -1] when no display frame has
    // been set and there is no parent layer bounds. In that case, the scale is meaningless so
    // ignore them.
    if (!mSnapshot->bufferSize.isValid()) {
        bufferWidth = float(win.right) - float(win.left);
        bufferHeight = float(win.bottom) - float(win.top);
    }

    const float scaleHeight = (float(win.bottom) - float(win.top)) / bufferHeight;
    const float scaleWidth = (float(win.right) - float(win.left)) / bufferWidth;
    const float translateY = float(win.top) / bufferHeight;
    const float translateX = float(win.left) / bufferWidth;

    // Flip y-coordinates because GLConsumer expects OpenGL convention.
    mat4 tr = mat4::translate(vec4(.5f, .5f, 0.f, 1.f)) * mat4::scale(vec4(1.f, -1.f, 1.f, 1.f)) *
            mat4::translate(vec4(-.5f, -.5f, 0.f, 1.f)) *
            mat4::translate(vec4(translateX, translateY, 0.f, 1.f)) *
            mat4::scale(vec4(scaleWidth, scaleHeight, 1.0f, 1.0f));

    layerSettings.source.buffer.useTextureFiltering = useFiltering;
    layerSettings.source.buffer.textureTransform =
            mat4(static_cast<const float*>(textureMatrix)) * tr;

    return;
}

void LayerFE::prepareShadowClientComposition(LayerFE::LayerSettings& caster,
                                             const Rect& layerStackRect) const {
    ShadowSettings state = mSnapshot->shadowSettings;
    if (state.length <= 0.f || (state.ambientColor.a <= 0.f && state.spotColor.a <= 0.f)) {
        return;
    }

    // Shift the spot light x-position to the middle of the display and then
    // offset it by casting layer's screen pos.
    state.lightPos.x =
            (static_cast<float>(layerStackRect.width()) / 2.f) - mSnapshot->transformedBounds.left;
    state.lightPos.y -= mSnapshot->transformedBounds.top;
    caster.shadow = state;
}

void LayerFE::onLayerDisplayed(ftl::SharedFuture<FenceResult> futureFenceResult,
                               ui::LayerStack layerStack) {
    mCompositionResult.releaseFences.emplace_back(std::move(futureFenceResult), layerStack);
}

CompositionResult&& LayerFE::stealCompositionResult() {
    return std::move(mCompositionResult);
}

const char* LayerFE::getDebugName() const {
    return mName.c_str();
}

const LayerMetadata* LayerFE::getMetadata() const {
    return &mSnapshot->layerMetadata;
}

const LayerMetadata* LayerFE::getRelativeMetadata() const {
    return &mSnapshot->relativeLayerMetadata;
}

int32_t LayerFE::getSequence() const {
    return static_cast<int32_t>(mSnapshot->uniqueSequence);
}

bool LayerFE::hasRoundedCorners() const {
    return mSnapshot->roundedCorner.hasRoundedCorners();
}

void LayerFE::setWasClientComposed(const sp<Fence>& fence) {
    mCompositionResult.lastClientCompositionFence = fence;
}

bool LayerFE::hasBufferOrSidebandStream() const {
    return mSnapshot->externalTexture || mSnapshot->sidebandStream;
}

bool LayerFE::fillsColor() const {
    return mSnapshot->color.r >= 0.0_hf && mSnapshot->color.g >= 0.0_hf &&
            mSnapshot->color.b >= 0.0_hf;
}

bool LayerFE::hasBlur() const {
    return mSnapshot->backgroundBlurRadius > 0 || mSnapshot->blurRegions.size() > 0;
}

bool LayerFE::drawShadows() const {
    return mSnapshot->shadowSettings.length > 0.f &&
            (mSnapshot->shadowSettings.ambientColor.a > 0 ||
             mSnapshot->shadowSettings.spotColor.a > 0);
};

const sp<GraphicBuffer> LayerFE::getBuffer() const {
    return mSnapshot->externalTexture ? mSnapshot->externalTexture->getBuffer() : nullptr;
}

void LayerFE::setReleaseFence(const FenceResult& releaseFence) {
    // Promises should not be fulfilled more than once. This case can occur if virtual
    // displays with the same layerstack ID are being created and destroyed in quick
    // succession, such as in tests. This would result in a race condition in which
    // multiple displays have the same layerstack ID within the same vsync interval.
    if (mReleaseFencePromiseStatus == ReleaseFencePromiseStatus::FULFILLED) {
        return;
    }
    mReleaseFence.set_value(releaseFence);
    mReleaseFencePromiseStatus = ReleaseFencePromiseStatus::FULFILLED;
}

// LayerFEs are reused and a new fence needs to be created whevever a buffer is latched.
ftl::Future<FenceResult> LayerFE::createReleaseFenceFuture() {
    if (mReleaseFencePromiseStatus == ReleaseFencePromiseStatus::INITIALIZED) {
        LOG_ALWAYS_FATAL("Attempting to create a new promise while one is still unfulfilled.");
    }
    mReleaseFence = std::promise<FenceResult>();
    mReleaseFencePromiseStatus = ReleaseFencePromiseStatus::INITIALIZED;
    return mReleaseFence.get_future();
}

LayerFE::ReleaseFencePromiseStatus LayerFE::getReleaseFencePromiseStatus() {
    return mReleaseFencePromiseStatus;
}
} // namespace android
