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

/**
 * @defgroup swappyVk Swappy for Vulkan
 * Vulkan part of Swappy.
 * @{
 */

#pragma once

#include "jni.h"
#include "swappy_common.h"

#ifndef VK_NO_PROTOTYPES
#define VK_NO_PROTOTYPES 1
#endif
#include <vulkan/vulkan.h>

#ifdef __cplusplus
extern "C" {
#endif

/**
 * @brief Determine any Vulkan device extensions that must be enabled for a new
 * VkDevice.
 *
 * Swappy-for-Vulkan (SwappyVk) benefits from certain Vulkan device extensions
 * (e.g. VK_GOOGLE_display_timing).  Before the application calls
 * vkCreateDevice, SwappyVk needs to look at the list of available extensions
 * (returned by vkEnumerateDeviceExtensionProperties) and potentially identify
 * one or more extensions that the application must add to:
 *
 * - VkDeviceCreateInfo::enabledExtensionCount
 * - VkDeviceCreateInfo::ppEnabledExtensionNames
 *
 * before the application calls vkCreateDevice.  For each VkPhysicalDevice that
 * the application will call vkCreateDevice for, the application must call this
 * function, and then must add the identified extension(s) to the list that are
 * enabled for the VkDevice.  Similar to many Vulkan functions, this function
 * can be called twice, once to identify the number of required extensions, and
 * again with application-allocated memory that the function can write into.
 *
 * @param[in]    physicalDevice          - The VkPhysicalDevice associated with
 * the available extensions.
 * @param[in]    availableExtensionCount - This is the returned value of
 *                    pPropertyCount from vkEnumerateDeviceExtensionProperties.
 * @param[in]    pAvailableExtensions    - This is the returned value of
 *                    pProperties from vkEnumerateDeviceExtensionProperties.
 * @param[inout] pRequiredExtensionCount - If pRequiredExtensions is nullptr,
 * the function sets this to the number of extensions that are required.  If
 * pRequiredExtensions is non-nullptr, this is the number of required extensions
 * that the function should write into pRequiredExtensions.
 * @param[inout] pRequiredExtensions - If non-nullptr, this is
 * application-allocated memory into which the function will write the names of
 *                    required extensions.  It is a pointer to an array of
 *                    char* strings (i.e. the same as
 *                    VkDeviceCreateInfo::ppEnabledExtensionNames).
 */
void SwappyVk_determineDeviceExtensions(
    VkPhysicalDevice physicalDevice, uint32_t availableExtensionCount,
    VkExtensionProperties* pAvailableExtensions,
    uint32_t* pRequiredExtensionCount, char** pRequiredExtensions);

/**
 * @brief Tell Swappy the queueFamilyIndex used to create a specific VkQueue
 *
 * Swappy needs to know the queueFamilyIndex used for creating a specific
 * VkQueue so it can use it when presenting.
 *
 * @param[in]  device            - The VkDevice associated with the queue
 * @param[in]  queue             - A device queue.
 * @param[in]  queueFamilyIndex  - The queue family index used to create the
 * VkQueue.
 *
 */
void SwappyVk_setQueueFamilyIndex(VkDevice device, VkQueue queue,
                                  uint32_t queueFamilyIndex);

// TBD: For now, SwappyVk assumes only one VkSwapchainKHR per VkDevice, and that
// applications don't re-create swapchains.  Is this long-term sufficient?

/**
 * Internal init function. Do not call directly.
 * See SwappyVk_initAndGetRefreshCycleDuration instead.
 * @private
 */
bool SwappyVk_initAndGetRefreshCycleDuration_internal(
    JNIEnv* env, jobject jactivity, VkPhysicalDevice physicalDevice,
    VkDevice device, VkSwapchainKHR swapchain, uint64_t* pRefreshDuration);

/**
 * @brief Initialize SwappyVk for a given device and swapchain, and obtain the
 * approximate time duration between vertical-blanking periods.
 *
 * Uses JNI to query AppVsyncOffset and PresentationDeadline.
 *
 * If your application presents to more than one swapchain at a time, you must
 * call this for each swapchain before calling swappyVkSetSwapInterval() for it.
 *
 * The duration between vertical-blanking periods (an interval) is expressed as
 * the approximate number of nanoseconds between vertical-blanking periods of
 * the swapchain’s physical display.
 *
 * If the application converts this number to a fraction (e.g. 16,666,666 nsec
 * to 0.016666666) and divides one by that fraction, it will be the approximate
 * refresh rate of the display (e.g. 16,666,666 nanoseconds corresponds to a
 * 60Hz display, 11,111,111 nsec corresponds to a 90Hz display).
 *
 * @param[in]  env - JNIEnv that is assumed to be from AttachCurrentThread
 * function
 * @param[in]  jactivity - NativeActivity object handle, used for JNI
 * @param[in]  physicalDevice   - The VkPhysicalDevice associated with the
 * swapchain
 * @param[in]  device    - The VkDevice associated with the swapchain
 * @param[in]  swapchain - The VkSwapchainKHR the application wants Swappy to
 * swap
 * @param[out] pRefreshDuration - The returned refresh cycle duration
 *
 * @return bool            - true if the value returned by pRefreshDuration is
 * valid, otherwise false if an error.
 */
bool SwappyVk_initAndGetRefreshCycleDuration(JNIEnv* env, jobject jactivity,
                                             VkPhysicalDevice physicalDevice,
                                             VkDevice device,
                                             VkSwapchainKHR swapchain,
                                             uint64_t* pRefreshDuration);

/**
 * @brief Tell Swappy which ANativeWindow to use when calling to ANativeWindow_*
 * API.
 * @param[in]  device    - The VkDevice associated with the swapchain
 * @param[in]  swapchain - The VkSwapchainKHR the application wants Swappy to
 * swap
 * @param[in]  window    - The ANativeWindow that was used to create the
 * VkSwapchainKHR
 */
void SwappyVk_setWindow(VkDevice device, VkSwapchainKHR swapchain,
                        ANativeWindow* window);

/**
 * @brief Tell Swappy the duration of that each presented image should be
 * visible.
 *
 * If your application presents to more than one swapchain at a time, you must
 * call this for each swapchain before presenting to it.
 *
 * @param[in]  device    - The VkDevice associated with the swapchain
 * @param[in]  swapchain - The VkSwapchainKHR the application wants Swappy to
 * swap
 * @param[in]  swap_ns   - The duration of that each presented image should be
 *                    visible in nanoseconds
 */
void SwappyVk_setSwapIntervalNS(VkDevice device, VkSwapchainKHR swapchain,
                                uint64_t swap_ns);

/**
 * @brief Tell Swappy to present one or more images to corresponding swapchains.
 *
 * Swappy will call vkQueuePresentKHR for your application.  Swappy may insert a
 * struct to the pNext-chain of VkPresentInfoKHR, or it may insert other Vulkan
 * commands in order to attempt to honor the desired swap interval.
 *
 * @note If your application presents to more than one swapchain at a time, and
 * if you use a different swap interval for each swapchain, Swappy will attempt
 * to honor the swap interval for each swapchain (being more successful on
 * devices that support an underlying presentation-timing extension, such as
 * VK_GOOGLE_display_timing).
 *
 * @param[in]  queue     - The VkQueue associated with the device and swapchain
 * @param[in]  pPresentInfo - A pointer to the VkPresentInfoKHR containing the
 *                    information about what image(s) to present on which
 *                    swapchain(s).
 */
VkResult SwappyVk_queuePresent(VkQueue queue,
                               const VkPresentInfoKHR* pPresentInfo);

/**
 * @brief Destroy the SwappyVk instance associated with a swapchain.
 *
 * This API is expected to be called before calling vkDestroySwapchainKHR()
 * so Swappy can cleanup its internal state.
 *
 * @param[in]  device    - The VkDevice associated with SwappyVk
 * @param[in]  swapchain - The VkSwapchainKHR the application wants Swappy to
 * destroy
 */
void SwappyVk_destroySwapchain(VkDevice device, VkSwapchainKHR swapchain);

/**
 * @brief Destroy any swapchains associated with the device and clean up the
 * device's resources
 *
 * This function should be called after SwappyVk_destroySwapchain if you no
 * longer need the device.
 *
 * @param[in]  device     - The VkDevice associated with SwappyVk
 */
void SwappyVk_destroyDevice(VkDevice device);

/**
 * @brief Enables Auto-Swap-Interval feature for all instances.
 *
 * By default this feature is enabled. Changing it is completely
 * optional for fine-tuning swappy behaviour.
 *
 * @param[in]  enabled - True means enable, false means disable
 */
void SwappyVk_setAutoSwapInterval(bool enabled);

/**
 * @brief Enables Auto-Pipeline-Mode feature for all instances.
 *
 * By default this feature is enabled. Changing it is completely
 * optional for fine-tuning swappy behaviour.
 *
 * @param[in]  enabled - True means enable, false means disable
 */
void SwappyVk_setAutoPipelineMode(bool enabled);

/**
 * @brief Sets the maximal swap duration for all instances.
 *
 * Sets the maximal duration for Auto-Swap-Interval in milliseconds.
 * If SwappyVk is operating in Auto-Swap-Interval and the frame duration is
 * longer than the provided duration, SwappyVk will not do any pacing and just
 * submit the frame as soon as possible.
 *
 * @param[in]  max_swap_ns - maximal swap duration in milliseconds.
 */
void SwappyVk_setMaxAutoSwapIntervalNS(uint64_t max_swap_ns);

/**
 * @brief The fence timeout parameter can be set for devices with faulty
 * drivers. Its default value is 50,000,000.
 */
void SwappyVk_setFenceTimeoutNS(uint64_t fence_timeout_ns);

/**
 * @brief Get the fence timeout parameter, for devices with faulty
 * drivers. Its default value is 50,000,000.
 */
uint64_t SwappyVk_getFenceTimeoutNS();

/**
 * @brief Inject callback functions to be called each frame.
 *
 * @param[in]  tracer - Collection of callback functions
 */
void SwappyVk_injectTracer(const SwappyTracer* tracer);

/**
 * @brief Remove callbacks that were previously added using
 * SwappyVk_injectTracer.
 *
 * Only removes callbacks that were previously added using
 * SwappyVK_injectTracer. If SwappyVK_injectTracker was not called with the
 * tracer, then there is no effect.
 *
 * @param[in]  tracer - Collection of callback functions
 */
void SwappyVk_uninjectTracer(const SwappyTracer* tracer);

/**
 * @brief A structure enabling you to provide your own Vulkan function wrappers
 * by calling ::SwappyVk_setFunctionProvider.
 *
 * Usage of this functionality is optional.
 */
typedef struct SwappyVkFunctionProvider {
  /**
   * @brief Callback to initialize the function provider.
   *
   * This function is called by Swappy before any functions are requested.
   * E.g. so you can call dlopen on the Vulkan library.
   */
  bool (*init)();

  /**
   * @brief Callback to get the address of a function.
   *
   * This function is called by Swappy to get the address of a Vulkan
   * function.
   * @param name The null-terminated name of the function.
   */
  void* (*getProcAddr)(const char* name);

  /**
   * @brief Callback to close any resources owned by the function provider.
   *
   * This function is called by Swappy when no more functions will be
   * requested, e.g. so you can call dlclose on the Vulkan library.
   */
  void (*close)();
} SwappyVkFunctionProvider;

/**
 * @brief Set the Vulkan function provider.
 *
 * This enables you to provide an object that will be used to look up Vulkan
 * functions, e.g. to hook usage of these functions.
 *
 * To use this functionality, you *must* call this function before any others.
 *
 * Usage of this function is entirely optional. If you do not use it, the Vulkan
 * functions required by Swappy will be dynamically loaded from libvulkan.so.
 *
 * @param[in] provider - provider object
 */
void SwappyVk_setFunctionProvider(
    const SwappyVkFunctionProvider* pSwappyVkFunctionProvider);

/**
 * @brief Get the swap interval value, in nanoseconds, for a given swapchain.
 *
 * @param[in] swapchain - the swapchain to query
 */
uint64_t SwappyVk_getSwapIntervalNS(VkSwapchainKHR swapchain);

/**
 * @brief Get the supported refresh periods of this device. Call once with
 * out_refreshrates set to nullptr to get the number of supported refresh
 * periods, then call again passing that number as allocated_entries and
 * an array of size equal to allocated_entries that will be filled with the
 * refresh periods.
 */
int SwappyVk_getSupportedRefreshPeriodsNS(uint64_t* out_refreshrates,
                                          int allocated_entries,
                                          VkSwapchainKHR swapchain);
/**
 * @brief Check if Swappy is enabled for the specified swapchain.
 *
 * @return false if SwappyVk_initAndGetRefreshCycleDuration was not
 * called for the specified swapchain, true otherwise.
 */
bool SwappyVk_isEnabled(VkSwapchainKHR swapchain, bool* isEnabled);

/**
 * @brief Toggle statistics collection on/off
 *
 * By default, stats collection is off and there is no overhead related to
 * stats. An app can turn on stats collection by calling
 * `SwappyVk_enableStats(swapchain, true)`. Then, the app is expected to call
 * ::SwappyVk_recordFrameStart for each frame before starting to do any CPU
 * related work. Stats will be logged to logcat with a 'FrameStatistics' tag. An
 * app can get the stats by calling ::SwappyVk_getStats.
 *
 * SwappyVk_initAndGetRefreshCycleDuration must have been called successfully
 * before for this swapchain, otherwise there is no effect in this call. Frame
 * stats are only available if the platform supports VK_GOOGLE_display_timing
 * extension.
 *
 * @param[in]  swapchain - The swapchain for which frame stat collection is
 *                           configured.
 * @param      enabled   - Whether to enable/disable frame stat collection.
 */
void SwappyVk_enableStats(VkSwapchainKHR swapchain, bool enabled);

/**
 * @brief Should be called if stats have been enabled with SwappyVk_enableStats.
 *
 * When stats collection is enabled with SwappyVk_enableStats, the app is
 * expected to call this function for each frame before starting to do any CPU
 * related work. It is assumed that this function will be called after a
 * successful call to vkAcquireNextImageKHR. See ::SwappyVk_enableStats for more
 * conditions.
 *
 * @param[in]  queue     - The VkQueue associated with the device and swapchain
 * @param[in]  swapchain - The swapchain where the frame is presented to.
 * @param[in]  image     - The image in swapchain that corresponds to the frame.

 * @see SwappyVk_enableStats.
 */
void SwappyVk_recordFrameStart(VkQueue queue, VkSwapchainKHR swapchain,
                               uint32_t image);

/**
 * @brief Returns the stats collected, if statistics collection was toggled on.
 *
 * Given that this API uses VkSwapchainKHR and the potential for this call to be
 * done on different threads, all calls to ::SwappyVk_getStats
 * must be externally synchronized with other SwappyVk calls. Unsynchronized
 * calls may lead to undefined behavior. See ::SwappyVk_enableStats for more
 * conditions.
 *
 * @param[in]  swapchain   - The swapchain for which stats are being queried.
 * @param      swappyStats - Pointer to a SwappyStats that will be populated
 * with the collected stats. Cannot be NULL.
 * @see SwappyStats
 */
void SwappyVk_getStats(VkSwapchainKHR swapchain, SwappyStats* swappyStats);

/**
 * @brief Clears the frame statistics collected so far.
 *
 * All the frame statistics collected are reset to 0, frame statistics are
 * collected normally after this call. See ::SwappyVk_enableStats for more
 * conditions.
 *
 * @param[in]  swapchain   - The swapchain for which stats are being cleared.
 */
void SwappyVk_clearStats(VkSwapchainKHR swapchain);

/**
 * @brief Reset the swappy pacing mechanism
 *
 * In cases where the frame timing history is irrelevant (for example during
 * scene/level transitions or after loading screens), calling this would
 * remove all the history for frame pacing. Calling this entry point
 * would reset the frame rate to the initial state at the end of the current
 * frame. Then swappy would just pace as normal with fresh state from next
 * frame. There are no error conditions associated with this call.
 *
 * @param[in]  swapchain   - The swapchain for which frame pacing is reset.
 */
void SwappyVk_resetFramePacing(VkSwapchainKHR swapchain);

/**
 * @brief Enable/Disable the swappy pacing mechanism
 *
 * By default frame pacing is enabled when swappy is used, however it can be
 * disabled at runtime by calling `SwappyVk_enableFramePacing(swapchain,
 * false)`. When the frame pacing is disabled, ::SwappyVk_enableBlockingWait
 * will control whether
 * ::SwappyVk_queuePresent will return immediately, or will block until the
 * previous frame's GPU work is completed. All enabling/disabling effects take
 * place from next frame. Once disabled, `SwappyVk_enableFramePacing(swapchain,
 * true)` will enable frame pacing again with a fresh frame time state -
 * equivalent of calling ::SwappyVk_resetFramePacing().
 *
 * @param[in]  swapchain   - The swapchain for which stats are being queried.
 * @param      enable      - If true, enables frame pacing otherwise disables it
 */
void SwappyVk_enableFramePacing(VkSwapchainKHR swapchain, bool enable);

/**
 * @brief Enable/Disable blocking wait when frame-pacing is disabled
 *
 * By default ::SwappyVk_queuePresent will do a blocking wait until previous
 * frame's GPU work is completed. However when frame pacing is disabled, calling
 * `SwappyVk_enableBlockingWait(swapchain, false)` will ensure that
 * ::SwappyVk_queuePresent returns without waiting for previous frame's GPU
 * work. This behaviour impacts the GPU time returned in the
 * ::SwappyPostWaitCallback callback. If the last frame's GPU work is done by
 * the time ::SwappyVk_queuePresent for this frame is called, then the previous
 * frame's GPU time will be returned otherwise `-1` will be delivered in the
 * callback for the frame. This setting has no impact when frame pacing is
 * enabled.
 *
 * @param[in]  swapchain   - The swapchain for which stats are being queried.
 * @param      enable      - If true, ::SwappyVk_queuePresent will block until
 * the previous frame's GPU work is complete.
 */
void SwappyVk_enableBlockingWait(VkSwapchainKHR swapchain, bool enable);

#ifdef __cplusplus
}  // extern "C"
#endif

/** @} */
