//
// Copyright 2015 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// PlatformMethods.h: The public interface ANGLE exposes to the API layer, for
//   doing platform-specific tasks like gathering data, or for tracing.

#ifndef ANGLE_PLATFORMMETHODS_H
#define ANGLE_PLATFORMMETHODS_H

#include <stdint.h>
#include <stdlib.h>
#include <array>

#define EGL_PLATFORM_ANGLE_PLATFORM_METHODS_ANGLEX 0x3482

#if !defined(ANGLE_PLATFORM_EXPORT)
#    if defined(_WIN32)
#        if !defined(LIBANGLE_IMPLEMENTATION)
#            define ANGLE_PLATFORM_EXPORT __declspec(dllimport)
#        else
#            define ANGLE_PLATFORM_EXPORT __declspec(dllexport)
#        endif
#    elif defined(__GNUC__) || defined(__clang__)
#        define ANGLE_PLATFORM_EXPORT __attribute__((visibility("default")))
#    endif
#endif
#if !defined(ANGLE_PLATFORM_EXPORT)
#    define ANGLE_PLATFORM_EXPORT
#endif

#if defined(_WIN32)
#    define ANGLE_APIENTRY __stdcall
#else
#    define ANGLE_APIENTRY
#endif

namespace angle
{
struct FeaturesD3D;
struct FeaturesVk;
struct FeaturesMtl;
using TraceEventHandle = uint64_t;
using EGLDisplayType   = void *;
struct PlatformMethods;

// Use a C-like API to not trigger undefined calling behaviour.
// Avoid using decltype here to work around sanitizer limitations.
// TODO(jmadill): Use decltype here if/when UBSAN is fixed.

// System --------------------------------------------------------------

// Wall clock time in seconds since the epoch.
// TODO(jmadill): investigate using an ANGLE internal time library
using CurrentTimeFunc = double (*)(PlatformMethods *platform);
inline double DefaultCurrentTime(PlatformMethods *platform)
{
    return 0.0;
}

// Monotonically increasing time in seconds from an arbitrary fixed point in the past.
// This function is expected to return at least millisecond-precision values. For this reason,
// it is recommended that the fixed point be no further in the past than the epoch.
using MonotonicallyIncreasingTimeFunc = double (*)(PlatformMethods *platform);
inline double DefaultMonotonicallyIncreasingTime(PlatformMethods *platform)
{
    return 0.0;
}

// Logging ------------------------------------------------------------

// Log an error message within the platform implementation.
using LogErrorFunc = void (*)(PlatformMethods *platform, const char *errorMessage);
inline void DefaultLogError(PlatformMethods *platform, const char *errorMessage) {}

// Log a warning message within the platform implementation.
using LogWarningFunc = void (*)(PlatformMethods *platform, const char *warningMessage);
inline void DefaultLogWarning(PlatformMethods *platform, const char *warningMessage) {}

// Log an info message within the platform implementation.
using LogInfoFunc = void (*)(PlatformMethods *platform, const char *infoMessage);
inline void DefaultLogInfo(PlatformMethods *platform, const char *infoMessage) {}

// Tracing --------

// Get a pointer to the enabled state of the given trace category. The
// embedder can dynamically change the enabled state as trace event
// recording is started and stopped by the application. Only long-lived
// literal strings should be given as the category name. The implementation
// expects the returned pointer to be held permanently in a local static. If
// the unsigned char is non-zero, tracing is enabled. If tracing is enabled,
// addTraceEvent is expected to be called by the trace event macros.
using GetTraceCategoryEnabledFlagFunc = const unsigned char *(*)(PlatformMethods *platform,
                                                                 const char *categoryName);
inline const unsigned char *DefaultGetTraceCategoryEnabledFlag(PlatformMethods *platform,
                                                               const char *categoryName)
{
    return nullptr;
}

//
// Add a trace event to the platform tracing system. Depending on the actual
// enabled state, this event may be recorded or dropped.
// - phase specifies the type of event:
//   - BEGIN ('B'): Marks the beginning of a scoped event.
//   - END ('E'): Marks the end of a scoped event.
//   - COMPLETE ('X'): Marks the beginning of a scoped event, but doesn't
//     need a matching END event. Instead, at the end of the scope,
//     updateTraceEventDuration() must be called with the TraceEventHandle
//     returned from addTraceEvent().
//   - INSTANT ('I'): Standalone, instantaneous event.
//   - START ('S'): Marks the beginning of an asynchronous event (the end
//     event can occur in a different scope or thread). The id parameter is
//     used to match START/FINISH pairs.
//   - FINISH ('F'): Marks the end of an asynchronous event.
//   - COUNTER ('C'): Used to trace integer quantities that change over
//     time. The argument values are expected to be of type int.
//   - METADATA ('M'): Reserved for internal use.
// - categoryEnabled is the pointer returned by getTraceCategoryEnabledFlag.
// - name is the name of the event. Also used to match BEGIN/END and
//   START/FINISH pairs.
// - id optionally allows events of the same name to be distinguished from
//   each other. For example, to trace the construction and destruction of
//   objects, specify the pointer as the id parameter.
// - timestamp should be a time value returned from monotonicallyIncreasingTime.
// - numArgs specifies the number of elements in argNames, argTypes, and
//   argValues.
// - argNames is the array of argument names. Use long-lived literal strings
//   or specify the COPY flag.
// - argTypes is the array of argument types:
//   - BOOL (1): bool
//   - UINT (2): unsigned long long
//   - INT (3): long long
//   - DOUBLE (4): double
//   - POINTER (5): void*
//   - STRING (6): char* (long-lived null-terminated char* string)
//   - COPY_STRING (7): char* (temporary null-terminated char* string)
//   - CONVERTABLE (8): WebConvertableToTraceFormat
// - argValues is the array of argument values. Each value is the unsigned
//   long long member of a union of all supported types.
// - flags can be 0 or one or more of the following, ORed together:
//   - COPY (0x1): treat all strings (name, argNames and argValues of type
//     string) as temporary so that they will be copied by addTraceEvent.
//   - HAS_ID (0x2): use the id argument to uniquely identify the event for
//     matching with other events of the same name.
//   - MANGLE_ID (0x4): specify this flag if the id parameter is the value
//     of a pointer.
using AddTraceEventFunc = angle::TraceEventHandle (*)(PlatformMethods *platform,
                                                      char phase,
                                                      const unsigned char *categoryEnabledFlag,
                                                      const char *name,
                                                      unsigned long long id,
                                                      double timestamp,
                                                      int numArgs,
                                                      const char **argNames,
                                                      const unsigned char *argTypes,
                                                      const unsigned long long *argValues,
                                                      unsigned char flags);
inline angle::TraceEventHandle DefaultAddTraceEvent(PlatformMethods *platform,
                                                    char phase,
                                                    const unsigned char *categoryEnabledFlag,
                                                    const char *name,
                                                    unsigned long long id,
                                                    double timestamp,
                                                    int numArgs,
                                                    const char **argNames,
                                                    const unsigned char *argTypes,
                                                    const unsigned long long *argValues,
                                                    unsigned char flags)
{
    return 0;
}

// Set the duration field of a COMPLETE trace event.
using UpdateTraceEventDurationFunc = void (*)(PlatformMethods *platform,
                                              const unsigned char *categoryEnabledFlag,
                                              const char *name,
                                              angle::TraceEventHandle eventHandle);
inline void DefaultUpdateTraceEventDuration(PlatformMethods *platform,
                                            const unsigned char *categoryEnabledFlag,
                                            const char *name,
                                            angle::TraceEventHandle eventHandle)
{}

// Callbacks for reporting histogram data.
// CustomCounts histogram has exponential bucket sizes, so that min=1, max=1000000, bucketCount=50
// would do.
using HistogramCustomCountsFunc = void (*)(PlatformMethods *platform,
                                           const char *name,
                                           int sample,
                                           int min,
                                           int max,
                                           int bucketCount);
inline void DefaultHistogramCustomCounts(PlatformMethods *platform,
                                         const char *name,
                                         int sample,
                                         int min,
                                         int max,
                                         int bucketCount)
{}
// Enumeration histogram buckets are linear, boundaryValue should be larger than any possible sample
// value.
using HistogramEnumerationFunc = void (*)(PlatformMethods *platform,
                                          const char *name,
                                          int sample,
                                          int boundaryValue);
inline void DefaultHistogramEnumeration(PlatformMethods *platform,
                                        const char *name,
                                        int sample,
                                        int boundaryValue)
{}
// Unlike enumeration histograms, sparse histograms only allocate memory for non-empty buckets.
using HistogramSparseFunc = void (*)(PlatformMethods *platform, const char *name, int sample);
inline void DefaultHistogramSparse(PlatformMethods *platform, const char *name, int sample) {}
// Boolean histograms track two-state variables.
using HistogramBooleanFunc = void (*)(PlatformMethods *platform, const char *name, bool sample);
inline void DefaultHistogramBoolean(PlatformMethods *platform, const char *name, bool sample) {}

// Allows us to programatically override ANGLE's default workarounds for testing purposes.
using OverrideWorkaroundsD3DFunc = void (*)(PlatformMethods *platform,
                                            angle::FeaturesD3D *featuresD3D);
inline void DefaultOverrideWorkaroundsD3D(PlatformMethods *platform,
                                          angle::FeaturesD3D *featuresD3D)
{}

using OverrideFeaturesVkFunc = void (*)(PlatformMethods *platform,
                                        angle::FeaturesVk *featuresVulkan);
inline void DefaultOverrideFeaturesVk(PlatformMethods *platform, angle::FeaturesVk *featuresVulkan)
{}

using OverrideFeaturesMtlFunc = void (*)(PlatformMethods *platform,
                                         angle::FeaturesMtl *featuresMetal);
inline void DefaultOverrideFeaturesMtl(PlatformMethods *platform, angle::FeaturesMtl *featuresMetal)
{}

// Callback on a successful program link with the program binary. Can be used to store
// shaders to disk. Keys are a 160-bit SHA-1 hash.
using ProgramKeyType   = std::array<uint8_t, 20>;
using CacheProgramFunc = void (*)(PlatformMethods *platform,
                                  const ProgramKeyType &key,
                                  size_t programSize,
                                  const uint8_t *programBytes);
inline void DefaultCacheProgram(PlatformMethods *platform,
                                const ProgramKeyType &key,
                                size_t programSize,
                                const uint8_t *programBytes)
{}

using PostWorkerTaskCallback                       = void (*)(void *userData);
using PostWorkerTaskFunc                           = void (*)(PlatformMethods *platform,
                                    PostWorkerTaskCallback callback,
                                    void *userData);
constexpr PostWorkerTaskFunc DefaultPostWorkerTask = nullptr;

// Platform methods are enumerated here once.
#define ANGLE_PLATFORM_OP(OP)                                    \
    OP(currentTime, CurrentTime)                                 \
    OP(monotonicallyIncreasingTime, MonotonicallyIncreasingTime) \
    OP(logError, LogError)                                       \
    OP(logWarning, LogWarning)                                   \
    OP(logInfo, LogInfo)                                         \
    OP(getTraceCategoryEnabledFlag, GetTraceCategoryEnabledFlag) \
    OP(addTraceEvent, AddTraceEvent)                             \
    OP(updateTraceEventDuration, UpdateTraceEventDuration)       \
    OP(histogramCustomCounts, HistogramCustomCounts)             \
    OP(histogramEnumeration, HistogramEnumeration)               \
    OP(histogramSparse, HistogramSparse)                         \
    OP(histogramBoolean, HistogramBoolean)                       \
    OP(overrideWorkaroundsD3D, OverrideWorkaroundsD3D)           \
    OP(overrideFeaturesVk, OverrideFeaturesVk)                   \
    OP(cacheProgram, CacheProgram)                               \
    OP(overrideFeaturesMtl, OverrideFeaturesMtl)                 \
    OP(postWorkerTask, PostWorkerTask)

#define ANGLE_PLATFORM_METHOD_DEF(Name, CapsName) CapsName##Func Name = Default##CapsName;

struct ANGLE_PLATFORM_EXPORT PlatformMethods
{
    inline PlatformMethods();

    // User data pointer for any implementation specific members. Put it at the start of the
    // platform structure so it doesn't become overwritten if one version of the platform
    // adds or removes new members.
    void *context = 0;

    ANGLE_PLATFORM_OP(ANGLE_PLATFORM_METHOD_DEF)
};

inline PlatformMethods::PlatformMethods() = default;

#undef ANGLE_PLATFORM_METHOD_DEF

// Subtract one to account for the context pointer.
constexpr unsigned int g_NumPlatformMethods = (sizeof(PlatformMethods) / sizeof(uintptr_t)) - 1;

#define ANGLE_PLATFORM_METHOD_STRING(Name) #Name
#define ANGLE_PLATFORM_METHOD_STRING2(Name, CapsName) ANGLE_PLATFORM_METHOD_STRING(Name),

constexpr const char *const g_PlatformMethodNames[g_NumPlatformMethods] = {
    ANGLE_PLATFORM_OP(ANGLE_PLATFORM_METHOD_STRING2)};

#undef ANGLE_PLATFORM_METHOD_STRING2
#undef ANGLE_PLATFORM_METHOD_STRING

}  // namespace angle

extern "C" {

// Gets the platform methods on the passed-in EGL display. If the method name signature does not
// match the compiled signature for this ANGLE, false is returned. On success true is returned.
// The application should set any platform methods it cares about on the returned pointer.
// If display is not valid, behaviour is undefined.

ANGLE_PLATFORM_EXPORT bool ANGLE_APIENTRY ANGLEGetDisplayPlatform(angle::EGLDisplayType display,
                                                                  const char *const methodNames[],
                                                                  unsigned int methodNameCount,
                                                                  void *context,
                                                                  void *platformMethodsOut);

// Sets the platform methods back to their defaults.
// If display is not valid, behaviour is undefined.
ANGLE_PLATFORM_EXPORT void ANGLE_APIENTRY ANGLEResetDisplayPlatform(angle::EGLDisplayType display);
}  // extern "C"

namespace angle
{
typedef bool(ANGLE_APIENTRY *GetDisplayPlatformFunc)(angle::EGLDisplayType,
                                                     const char *const *,
                                                     unsigned int,
                                                     void *,
                                                     void *);
typedef void(ANGLE_APIENTRY *ResetDisplayPlatformFunc)(angle::EGLDisplayType);
}  // namespace angle

// This function is not exported
angle::PlatformMethods *ANGLEPlatformCurrent();

#endif  // ANGLE_PLATFORMMETHODS_H
