/*
 * Copyright (c) 2019-2020, The Linux Foundation. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above
      copyright notice, this list of conditions and the following
      disclaimer in the documentation and/or other materials provided
      with the distribution.
    * Neither the name of The Linux Foundation nor the names of its
      contributors may be used to endorse or promote products derived
      from this software without specific prior written permission.

 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/*
* Changes from Qualcomm Innovation Center are provided under the following license:
*
* Copyright (c) 2022-2023 Qualcomm Innovation Center, Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted (subject to the limitations in the
* disclaimer below) provided that the following conditions are met:
*
*    * Redistributions of source code must retain the above copyright
*      notice, this list of conditions and the following disclaimer.
*
*    * Redistributions in binary form must reproduce the above
*      copyright notice, this list of conditions and the following
*      disclaimer in the documentation and/or other materials provided
*      with the distribution.
*
*    * Neither the name of Qualcomm Innovation Center, Inc. nor the names of its
*      contributors may be used to endorse or promote products derived
*      from this software without specific prior written permission.
*
* NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE
* GRANTED BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT
* HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
* IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
* IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include "hwc_display_virtual_gpu.h"
#include "hwc_session.h"

#include <qdMetaData.h>

#define __CLASS__ "HWCDisplayVirtualGPU"

namespace sdm {

int HWCDisplayVirtualGPU::Init() {
  // Create client target.
  client_target_ = new HWCLayer(id_, buffer_allocator_);

  // Calls into SDM need to be dropped. Create Null Display interface.
  display_intf_ = new DisplayNull();

  disable_animation_ = Debug::IsExtAnimDisabled();

  return HWCDisplayVirtual::Init();
}

int HWCDisplayVirtualGPU::Deinit() {
  // Destory color convert instance. This destroys thread and underlying GL resources.
  if (gl_color_convert_) {
    color_convert_task_.PerformTask(ColorConvertTaskCode::kCodeDestroyInstance, nullptr);
  }

  delete static_cast<DisplayNull *>(display_intf_);
  delete client_target_;

  for (auto hwc_layer : layer_set_) {
    delete hwc_layer;
  }

  return 0;
}

HWCDisplayVirtualGPU::HWCDisplayVirtualGPU(CoreInterface *core_intf, HWCBufferAllocator
                                           *buffer_allocator, HWCCallbacks *callbacks,
                                           hwc2_display_t id, int32_t sdm_id, uint32_t width,
                                           uint32_t height, float min_lum, float max_lum) :
  HWCDisplayVirtual(core_intf, buffer_allocator, callbacks, id, sdm_id, width, height),
  color_convert_task_(*this) {
}

HWC2::Error HWCDisplayVirtualGPU::Validate(uint32_t *out_num_types, uint32_t *out_num_requests) {
  DTRACE_SCOPED();

  // Reset previous changes.
  layer_changes_.clear();
  layer_requests_.clear();

  // Mark all layers to GPU if there is no need to bypass.
  bool needs_gpu_bypass = NeedsGPUBypass() || FreezeScreen();
  for (auto hwc_layer : layer_set_) {
    auto layer = hwc_layer->GetSDMLayer();
    layer->composition = needs_gpu_bypass ? kCompositionSDE : kCompositionGPU;

    if (needs_gpu_bypass) {
      if (hwc_layer->GetClientRequestedCompositionType() == HWC2::Composition::Client) {
       layer_changes_[hwc_layer->GetId()] = HWC2::Composition::Device;
       layer_requests_[hwc_layer->GetId()] = HWC2::LayerRequest::ClearClientTarget;
      }
    } else {
      if (hwc_layer->GetClientRequestedCompositionType() != HWC2::Composition::Client) {
       layer_changes_[hwc_layer->GetId()] = HWC2::Composition::Client;
      }
    }
  }

  // Derive client target dataspace based on the color mode - bug/115482728
  int32_t client_target_dataspace = GetDataspaceFromColorMode(GetCurrentColorMode());
  SetClientTargetDataSpace(client_target_dataspace);

  *out_num_types = UINT32(layer_changes_.size());
  *out_num_requests = UINT32(layer_requests_.size());;
  has_client_composition_ = !needs_gpu_bypass;
  client_target_->ResetValidation();

  validated_ = true;

  return ((*out_num_types > 0) ? HWC2::Error::HasChanges : HWC2::Error::None);
}

HWC2::Error HWCDisplayVirtualGPU::SetOutputBuffer(buffer_handle_t buf,
                                                  shared_ptr<Fence> release_fence) {
  HWC2::Error error = HWCDisplayVirtual::SetOutputBuffer(buf, release_fence);
  if (error != HWC2::Error::None) {
    return error;
  }

  const private_handle_t *hnd = static_cast<const private_handle_t *>(buf);
  output_buffer_.width = hnd->width;
  output_buffer_.height = hnd->height;
  output_buffer_.unaligned_width = width_;
  output_buffer_.unaligned_height = height_;

  // Update active dimensions.
  BufferDim_t buffer_dim;
  if (getMetaData(const_cast<private_handle_t *>(hnd), GET_BUFFER_GEOMETRY, &buffer_dim) == 0) {
    output_buffer_.unaligned_width = buffer_dim.sliceWidth;
    output_buffer_.unaligned_height = buffer_dim.sliceHeight;
    color_convert_task_.PerformTask(ColorConvertTaskCode::kCodeReset, nullptr);
  }

  return HWC2::Error::None;
}

HWC2::Error HWCDisplayVirtualGPU::Present(shared_ptr<Fence> *out_retire_fence) {
  DTRACE_SCOPED();

  auto status = HWC2::Error::None;

  if (!validated_) {
    return HWC2::Error::NotValidated;
  }

  if (!output_buffer_.buffer_id) {
    return HWC2::Error::NoResources;
  }

  if (active_secure_sessions_.any() || layer_set_.empty()) {
    return status;
  }
  Layer *sdm_layer = client_target_->GetSDMLayer();
  LayerBuffer &input_buffer = sdm_layer->input_buffer;
  if (!input_buffer.buffer_id) {
    return HWC2::Error::NoResources;
  }


  layer_stack_.output_buffer = &output_buffer_;
  if (display_paused_) {
    validated_ = false;
  }

  // Ensure that blit is initialized.
  // GPU context gets in secure or non-secure mode depending on output buffer provided.
  if (!gl_color_convert_) {
    // Get instance.
    color_convert_task_.PerformTask(ColorConvertTaskCode::kCodeGetInstance, nullptr);
    if (gl_color_convert_ == nullptr) {
      DLOGE("Failed to get Color Convert Instance");
      return HWC2::Error::NoResources;
    } else {
      DLOGI("Created ColorConvert instance: %p", gl_color_convert_);
    }
  }

  ColorConvertBlitContext ctx = {};

  ctx.src_hnd = reinterpret_cast<const private_handle_t *>(input_buffer.buffer_id);
  ctx.dst_hnd = reinterpret_cast<const private_handle_t *>(output_handle_);
  ctx.dst_rect = {0, 0, FLOAT(output_buffer_.unaligned_width),
                  FLOAT(output_buffer_.unaligned_height)};
  ctx.src_acquire_fence = input_buffer.acquire_fence;
  ctx.dst_acquire_fence = output_buffer_.acquire_fence;

  color_convert_task_.PerformTask(ColorConvertTaskCode::kCodeBlit, &ctx);

  // todo blit
  DumpVDSBuffer();

  *out_retire_fence = ctx.release_fence;

  return status;
}

void HWCDisplayVirtualGPU::OnTask(const ColorConvertTaskCode &task_code,
                                  SyncTask<ColorConvertTaskCode>::TaskContext *task_context) {
  switch (task_code) {
    case ColorConvertTaskCode::kCodeGetInstance: {
        gl_color_convert_ = GLColorConvert::GetInstance(kTargetYUV, output_buffer_.flags.secure);
      }
      break;
    case ColorConvertTaskCode::kCodeBlit: {
        DTRACE_SCOPED();
        ColorConvertBlitContext* ctx = reinterpret_cast<ColorConvertBlitContext*>(task_context);
        gl_color_convert_->Blit(ctx->src_hnd, ctx->dst_hnd, ctx->src_rect, ctx->dst_rect,
                                ctx->src_acquire_fence, ctx->dst_acquire_fence,
                                &(ctx->release_fence));
      }
      break;
    case ColorConvertTaskCode::kCodeReset: {
        DTRACE_SCOPED();
        if (gl_color_convert_) {
          gl_color_convert_->Reset();
        }
      }
      break;
    case ColorConvertTaskCode::kCodeDestroyInstance: {
        if (gl_color_convert_) {
          GLColorConvert::Destroy(gl_color_convert_);
        }
      }
      break;
  }
}

bool HWCDisplayVirtualGPU::FreezeScreen() {
  if (!disable_animation_) {
    return false;
  }

  bool freeze_screen = false;
  if (animating_ && !animation_in_progress_) {
    // Start of animation. GPU comp is needed.
    animation_in_progress_ = true;
  } else if (!animating_ && animation_in_progress_) {
    // End of animation. Start composing.
    animation_in_progress_ = false;
  } else if (animating_ && animation_in_progress_) {
    // Animation in progress...
    freeze_screen = true;
  }

  return freeze_screen;
}

}  // namespace sdm

