//
// Copyright 2021 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.
//
// dispatch.cpp: Implements a function to fetch the ANGLE OpenCL dispatch table.

#include "libOpenCL/dispatch.h"

#include "anglebase/no_destructor.h"
#include "common/debug.h"
#include "common/system_utils.h"

#include <memory>

#ifdef _WIN32
#    include <windows.h>
#endif

namespace cl
{

namespace
{

std::unique_ptr<angle::Library> &EntryPointsLib()
{
    static angle::base::NoDestructor<std::unique_ptr<angle::Library>> sEntryPointsLib;
    return *sEntryPointsLib;
}

IcdDispatch CreateDispatch()
{
    const cl_icd_dispatch *clIcdDispatch = nullptr;
    const char *error                    = nullptr;

    // Try to find ANGLE's GLESv2 library in the consistent way, which might fail
    // if the current library or a link to it is not in ANGLE's binary directory
    EntryPointsLib().reset(
        angle::OpenSharedLibrary(ANGLE_GLESV2_LIBRARY_NAME, angle::SearchType::ModuleDir));
    if (EntryPointsLib() && EntryPointsLib()->getNative() != nullptr)
    {
        EntryPointsLib()->getAs("gCLIcdDispatchTable", &clIcdDispatch);
        if (clIcdDispatch == nullptr)
        {
            INFO() << "Found system's instead of ANGLE's GLESv2 library";
        }
    }
    else
    {
        error = "Not able to find GLESv2 library";
    }

    // If not found try to find ANGLE's GLESv2 library in build path
    if (clIcdDispatch == nullptr)
    {
#ifdef _WIN32
        // On Windows the build path 'ANGLE_GLESV2_LIBRARY_PATH' is provided by the build system
        const char path[] = ANGLE_GLESV2_LIBRARY_PATH "\\" ANGLE_GLESV2_LIBRARY_NAME ".dll";
        // This function allows to load further dependent libraries from the same directory
        HMODULE handle = LoadLibraryExA(path, NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
        if (handle != nullptr)
        {
            clIcdDispatch = reinterpret_cast<const cl_icd_dispatch *>(
                GetProcAddress(handle, "gCLIcdDispatchTable"));
            if (clIcdDispatch == nullptr)
            {
                error = "Error loading CL dispatch table.";
            }
        }
#else
        // On posix-compatible systems this will also search in the rpath, which is the build path
        EntryPointsLib().reset(
            angle::OpenSharedLibrary(ANGLE_GLESV2_LIBRARY_NAME, angle::SearchType::SystemDir));
        if (EntryPointsLib() && EntryPointsLib()->getNative() != nullptr)
        {
            EntryPointsLib()->getAs("gCLIcdDispatchTable", &clIcdDispatch);
            if (clIcdDispatch == nullptr)
            {
                INFO() << "Found system's instead of ANGLE's GLESv2 library";
            }
        }
#endif
    }

    IcdDispatch dispatch;
    if (clIcdDispatch != nullptr)
    {
        static_cast<cl_icd_dispatch &>(dispatch) = *clIcdDispatch;
        dispatch.clIcdGetPlatformIDsKHR          = reinterpret_cast<clIcdGetPlatformIDsKHR_fn>(
            clIcdDispatch->clGetExtensionFunctionAddress("clIcdGetPlatformIDsKHR"));
        // TODO: below extensions will be refactored to not to expose them publicly later
        // http://anglebug.com/378017028
        dispatch.clEnqueueAcquireExternalMemObjectsKHR =
            reinterpret_cast<clEnqueueAcquireExternalMemObjectsKHR_fn>(
                clIcdDispatch->clGetExtensionFunctionAddress(
                    "clEnqueueAcquireExternalMemObjectsKHR"));
        dispatch.clEnqueueReleaseExternalMemObjectsKHR =
            reinterpret_cast<clEnqueueReleaseExternalMemObjectsKHR_fn>(
                clIcdDispatch->clGetExtensionFunctionAddress(
                    "clEnqueueReleaseExternalMemObjectsKHR"));
        dispatch.clImportMemoryARM = reinterpret_cast<clImportMemoryARM_fn>(
            clIcdDispatch->clGetExtensionFunctionAddress("clImportMemoryARM"));
    }
    else if (error != nullptr)
    {
        ERR() << error;
    }
    return dispatch;
}

}  // anonymous namespace

const IcdDispatch &GetDispatch()
{
    static const IcdDispatch sDispatch(CreateDispatch());
    return sDispatch;
}

}  // namespace cl
