/*
 * Copyright 2025 Google LLC
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#ifndef skgpu_VulkanPreferredFeatures_DEFINED
#define skgpu_VulkanPreferredFeatures_DEFINED

#include "include/private/base/SkAPI.h"
#include "include/private/gpu/vk/SkiaVulkan.h"

#include <cstddef>
#include <cstdint>
#include <vector>

namespace skgpu {

class SK_API VulkanPreferredFeatures {
public:
    ~VulkanPreferredFeatures();

    /*
     * VulkanPreferredFeatures is used by Skia to add extensions and features on top of what the app
     * wants to enable. The flow of Vulkan initialization for an app that wants to use Skia would
     * be:
     *
     * ```
     * // Query the loader for instance information, decide on API version to use
     *
     * // Prepare for Skia-enabled extensions and features
     * skgpu::VulkanPreferredFeatures skiaFeatures;
     * skiaFeatures.init(apiVersion);
     *
     * // Decide on instance extensions to use in the app.
     *
     * // Allow Skia to add to the instance extension list.
     * skiaFeatures.addToInstanceExtensions(...);
     *
     * // Create the instance, choose physical device, query available extensions, decide on
     * // device features to query
     *
     * // Allow Skia to add to the feature query list.
     * skiaFeatures.addFeaturesToQuery(...);
     *
     * // Query features, decide on extensions and features to enable.
     *
     * // Allow Skia to add to the device extension and feature list.
     * skiaFeatures.addFeaturesToEnable(...);
     *
     * // Create the Vulkan device.
     * ```
     *
     * Parameters:
     *
     * * appAPIVersion: The API version the app has specified in VkApplicationInfo::apiVersion (and
     *   will later provide in VulkanBackendContext::fMaxAPIVersion). The minimum supported version
     *   is VK_API_VERSION_1_1, maximum is VK_API_VERSION_1_4.
     */
    void init(uint32_t appAPIVersion);

    /*
     * Before creating a Vulkan instance, call addToInstanceExtensions to give Skia a chance to add
     * instance extensions it may take advantage of. Extensions are only added to appExtensions if
     * caller hasn't already included them to be enabled.
     *
     * Parameters:
     *
     * * instanceExtensions, instanceExtensionCount: The list of available instance extensions as
     *   queried from the loader.
     * * appExtensions: The list of extensions to be enabled on the instance; more extensions may be
     *   added to this list by this call.
     */
    void addToInstanceExtensions(const VkExtensionProperties* instanceExtensions,
                                 size_t instanceExtensionCount,
                                 std::vector<const char*>& appExtensions);

    /*
     * Before querying Vulkan device features, call addFeaturesToQuery to give Skia a chance to add
     * device extension features it may take advantage of to the query. Features are only added to
     * appFeatures if caller hasn't already included them in the query.
     *
     * Beware that the structs that get chained to appFeatures are member variables of this class,
     * so this object must not leave scope until feature query and device creation is complete.
     *
     * Parameters:
     *
     * * deviceExtensions, deviceExtensionCount: The list of available device extensions as queried
     *   from the physical device.
     * * appFeatures: The features to be queried from the physical device; more features may be
     *   added to the pNext chain by this call.
     */
    void addFeaturesToQuery(const VkExtensionProperties* deviceExtensions,
                            size_t deviceExtensionCount,
                            VkPhysicalDeviceFeatures2& appFeatures);

    /*
     * Before creating the Vulkan device, call addFeaturesToEnable to give Skia a chance to add
     * device extensions and features it may take advantage of. Extensions and features are only
     * added to appExtensions and appFeatures respectively if caller hasn't already included them to
     * be enabled. This function may replace chained features with VkPhysicalDeviceVulkanNNFeatures
     * structs instead in order to add features. Features that are already enabled by the
     * application are retained in that case in the new struct.
     *
     * Parameters:
     *
     * * appExtensions: The list of extensions to be enabled on the device; more extensions may be
     *   added to this list by this call.
     * * appFeatures: The features to be enabled on the device; more features may be
     *   added to the pNext chain by this call.
     */
    void addFeaturesToEnable(std::vector<const char*>& appExtensions,
                             VkPhysicalDeviceFeatures2& appFeatures);

private:
    uint32_t fAPIVersion = 0;
    // Track what the application did, so warnings can be generated if the class is not
    // fully/correctly used.
    bool fHasAddedToInstanceExtensions = false;
    bool fHasAddedFeaturesToQuery = false;
    bool fHasAddedFeaturesToEnable = false;

    // The list of device features Skia is interested in. If any are included in
    // VkPhysicalDeviceFeatures2::pNext by the app, it is not included again by Skia. However, to
    // take best advantage of the Vulkan API, the app should not intentionally include these feature
    // structs only to forcefully disable the feature - this blocks Skia from leveraging it.
    //
    // When ambiguous, the extension name is included to determine which extension a feature struct
    // has come from. This is usually needed when addFeaturesToQuery has included a feature struct
    // that is present in multiple extensions (e.g. due to extension promotion). It is later used by
    // addFeaturesToEnable to know which extension to enable.

    // Available since Vulkan 1.2
    VkPhysicalDeviceVulkan11Features fVulkan11 = {};

    // Available since Vulkan 1.2
    VkPhysicalDeviceVulkan12Features fVulkan12 = {};

    // Available since Vulkan 1.3
    VkPhysicalDeviceVulkan13Features fVulkan13 = {};

    // Available since Vulkan 1.4
    VkPhysicalDeviceVulkan14Features fVulkan14 = {};

    // Feature of VK_EXT_rasterization_order_attachment_access or
    // VK_ARM_rasterization_order_attachment_access.
    VkPhysicalDeviceRasterizationOrderAttachmentAccessFeaturesEXT
            fRasterizationOrderAttachmentAccess = {};
    const char* fRasterizationOrderAttachmentAccessExtension = nullptr;

    // Feature of VK_EXT_blend_operation_advanced
    VkPhysicalDeviceBlendOperationAdvancedFeaturesEXT fBlendOperationAdvanced = {};

    // Feature of VK_EXT_extended_dynamic_state
    VkPhysicalDeviceExtendedDynamicStateFeaturesEXT fExtendedDynamicState = {};

    // Feature of VK_EXT_extended_dynamic_state2
    VkPhysicalDeviceExtendedDynamicState2FeaturesEXT fExtendedDynamicState2 = {};

    // Feature of VK_EXT_vertex_input_dynamic_state
    VkPhysicalDeviceVertexInputDynamicStateFeaturesEXT fVertexInputDynamicState = {};

    // Feature of VK_EXT_graphics_pipeline_library
    VkPhysicalDeviceGraphicsPipelineLibraryFeaturesEXT fGraphicsPipelineLibrary = {};

    // Feature of VK_KHR_sampler_ycbcr_conversion or Vulkan 1.1
    VkPhysicalDeviceSamplerYcbcrConversionFeatures fSamplerYcbcrConversion = {};

    // Feature of VK_EXT_rgba10x6_formats
    VkPhysicalDeviceRGBA10X6FormatsFeaturesEXT fRGBA10x6Formats = {};

    // Feature of VK_KHR_synchronization2 or Vulkan 1.3
    VkPhysicalDeviceDynamicRenderingFeatures fSynchronization2 = {};

    // Feature of VK_KHR_dynamic_rendering or Vulkan 1.3
    VkPhysicalDeviceDynamicRenderingFeatures fDynamicRendering = {};

    // Feature of VK_KHR_dynamic_rendering_local_read or Vulkan 1.4
    VkPhysicalDeviceDynamicRenderingLocalReadFeatures fDynamicRenderingLocalRead = {};

    // Feature of VK_EXT_multisampled_render_to_single_sampled
    VkPhysicalDeviceMultisampledRenderToSingleSampledFeaturesEXT
            fMultisampledRenderToSingleSampled = {};

    // Feature of VK_EXT_host_image_copy or Vulkan 1.4
    VkPhysicalDeviceHostImageCopyFeatures fHostImageCopy = {};

    // Feature of VK_EXT_pipeline_creation_cache_control or Vulkan 1.3
    VkPhysicalDevicePipelineCreationCacheControlFeatures fPipelineCreationCacheControl = {};

    // Feature of VK_EXT_frame_boundary
    VkPhysicalDeviceFrameBoundaryFeaturesEXT fFrameBoundary = {};

    // Extensions that don't have a feature:
    // VK_KHR_driver_properties or Vulkan 1.2
    const char* fDriverPropertiesExtension = nullptr;
    // VK_KHR_create_renderpass2 or Vulkan 1.2
    const char* fCreateRenderpass2Extension = nullptr;
    // VK_EXT_load_store_op_none, VK_KHR_load_store_op_none or Vulkan 1.4
    const char* fLoadStoreOpNoneExtension = nullptr;
    // VK_EXT_conservative_rasterization
    const char* fConservativeRasterizationExtension = nullptr;

    // Extensions that the other extensions above depend on:
    // Dependency of VK_EXT_graphics_pipeline_library: VK_KHR_pipeline_library
    const char* fPipelineLibraryExtension = nullptr;
    // Dependencies of VK_EXT_host_image_copy: VK_KHR_copy_commands2, VK_KHR_format_feature_flags2
    const char* fCopyCommands2Extension = nullptr;
    const char* fFormatFeatureFlags2Extension = nullptr;
    // Dependency of VK_EXT_multisampled_render_to_single_sampled: VK_KHR_depth_stencil_resolve
    const char* fDepthStencilResolveExtension = nullptr;
};

}  // namespace skgpu

#endif  // skgpu_VulkanPreferredFeatures_DEFINED
