//
// Copyright 2018 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.
//
// vk_utils:
//    Helper functions for the Vulkan Caps.
//

#include "libANGLE/renderer/vulkan/vk_caps_utils.h"

#include <type_traits>

#include "common/utilities.h"
#include "libANGLE/Caps.h"
#include "libANGLE/formatutils.h"
#include "libANGLE/renderer/vulkan/DisplayVk.h"
#include "libANGLE/renderer/vulkan/RendererVk.h"
#include "vk_format_utils.h"

namespace
{
constexpr unsigned int kComponentsPerVector = 4;
}  // anonymous namespace

namespace rx
{
namespace vk
{

void GenerateCaps(const VkPhysicalDeviceProperties &physicalDeviceProperties,
                  const VkPhysicalDeviceFeatures &physicalDeviceFeatures,
                  const VkQueueFamilyProperties &queueFamilyProperties,
                  const gl::TextureCapsMap &textureCaps,
                  gl::Caps *outCaps,
                  gl::Extensions *outExtensions,
                  gl::Limitations * /* outLimitations */)
{
    outExtensions->setTextureExtensionSupport(textureCaps);

    // Enable this for simple buffer readback testing, but some functionality is missing.
    // TODO(jmadill): Support full mapBufferRange extension.
    outExtensions->mapBuffer              = true;
    outExtensions->mapBufferRange         = true;
    outExtensions->textureStorage         = true;
    outExtensions->framebufferBlit        = true;
    outExtensions->copyTexture            = true;
    outExtensions->debugMarker            = true;
    outExtensions->robustness             = true;
    outExtensions->textureBorderClamp     = false;  // not implemented yet
    outExtensions->translatedShaderSource = true;

    // Only expose robust buffer access if the physical device supports it.
    outExtensions->robustBufferAccessBehavior = physicalDeviceFeatures.robustBufferAccess;

    outExtensions->eglSync = true;

    // We use secondary command buffers almost everywhere and they require a feature to be
    // able to execute in the presence of queries.  As a result, we won't support queries
    // unless that feature is available.
    outExtensions->occlusionQueryBoolean = physicalDeviceFeatures.inheritedQueries;

    // From the Vulkan specs:
    // > The number of valid bits in a timestamp value is determined by the
    // > VkQueueFamilyProperties::timestampValidBits property of the queue on which the timestamp is
    // > written. Timestamps are supported on any queue which reports a non-zero value for
    // > timestampValidBits via vkGetPhysicalDeviceQueueFamilyProperties.
    outExtensions->disjointTimerQuery          = queueFamilyProperties.timestampValidBits > 0;
    outExtensions->queryCounterBitsTimeElapsed = queueFamilyProperties.timestampValidBits;
    outExtensions->queryCounterBitsTimestamp   = queueFamilyProperties.timestampValidBits;

    outExtensions->textureFilterAnisotropic =
        physicalDeviceFeatures.samplerAnisotropy &&
        physicalDeviceProperties.limits.maxSamplerAnisotropy > 1.0f;
    outExtensions->maxTextureAnisotropy = outExtensions->textureFilterAnisotropic
                                              ? physicalDeviceProperties.limits.maxSamplerAnisotropy
                                              : 0.0f;

    // TODO(lucferron): Eventually remove everything above this line in this function as the caps
    // get implemented.
    // https://vulkan.lunarg.com/doc/view/1.0.30.0/linux/vkspec.chunked/ch31s02.html
    outCaps->maxElementIndex       = std::numeric_limits<GLuint>::max() - 1;
    outCaps->max3DTextureSize      = physicalDeviceProperties.limits.maxImageDimension3D;
    outCaps->max2DTextureSize      = physicalDeviceProperties.limits.maxImageDimension2D;
    outCaps->maxArrayTextureLayers = physicalDeviceProperties.limits.maxImageArrayLayers;
    outCaps->maxLODBias            = physicalDeviceProperties.limits.maxSamplerLodBias;
    outCaps->maxCubeMapTextureSize = physicalDeviceProperties.limits.maxImageDimensionCube;
    outCaps->maxRenderbufferSize   = outCaps->max2DTextureSize;
    outCaps->minAliasedPointSize =
        std::max(1.0f, physicalDeviceProperties.limits.pointSizeRange[0]);
    outCaps->maxAliasedPointSize = physicalDeviceProperties.limits.pointSizeRange[1];

    outCaps->minAliasedLineWidth = 1.0f;
    outCaps->maxAliasedLineWidth = 1.0f;

    outCaps->maxDrawBuffers =
        std::min<uint32_t>(physicalDeviceProperties.limits.maxColorAttachments,
                           physicalDeviceProperties.limits.maxFragmentOutputAttachments);
    outCaps->maxFramebufferWidth    = physicalDeviceProperties.limits.maxFramebufferWidth;
    outCaps->maxFramebufferHeight   = physicalDeviceProperties.limits.maxFramebufferHeight;
    outCaps->maxColorAttachments    = physicalDeviceProperties.limits.maxColorAttachments;
    outCaps->maxViewportWidth       = physicalDeviceProperties.limits.maxViewportDimensions[0];
    outCaps->maxViewportHeight      = physicalDeviceProperties.limits.maxViewportDimensions[1];
    outCaps->maxSampleMaskWords     = physicalDeviceProperties.limits.maxSampleMaskWords;
    outCaps->maxColorTextureSamples = physicalDeviceProperties.limits.sampledImageColorSampleCounts;
    outCaps->maxDepthTextureSamples = physicalDeviceProperties.limits.sampledImageDepthSampleCounts;
    outCaps->maxIntegerSamples = physicalDeviceProperties.limits.sampledImageIntegerSampleCounts;

    outCaps->maxVertexAttributes     = physicalDeviceProperties.limits.maxVertexInputAttributes;
    outCaps->maxVertexAttribBindings = physicalDeviceProperties.limits.maxVertexInputBindings;
    outCaps->maxVertexAttribRelativeOffset =
        physicalDeviceProperties.limits.maxVertexInputAttributeOffset;
    outCaps->maxVertexAttribStride = physicalDeviceProperties.limits.maxVertexInputBindingStride;

    outCaps->maxElementsIndices  = std::numeric_limits<GLuint>::max();
    outCaps->maxElementsVertices = std::numeric_limits<GLuint>::max();

    // Looks like all floats are IEEE according to the docs here:
    // https://www.khronos.org/registry/vulkan/specs/1.0-wsi_extensions/html/vkspec.html#spirvenv-precision-operation
    outCaps->vertexHighpFloat.setIEEEFloat();
    outCaps->vertexMediumpFloat.setIEEEFloat();
    outCaps->vertexLowpFloat.setIEEEFloat();
    outCaps->fragmentHighpFloat.setIEEEFloat();
    outCaps->fragmentMediumpFloat.setIEEEFloat();
    outCaps->fragmentLowpFloat.setIEEEFloat();

    // Can't find documentation on the int precision in Vulkan.
    outCaps->vertexHighpInt.setTwosComplementInt(32);
    outCaps->vertexMediumpInt.setTwosComplementInt(32);
    outCaps->vertexLowpInt.setTwosComplementInt(32);
    outCaps->fragmentHighpInt.setTwosComplementInt(32);
    outCaps->fragmentMediumpInt.setTwosComplementInt(32);
    outCaps->fragmentLowpInt.setTwosComplementInt(32);

    // TODO(lucferron): This is something we'll need to implement custom in the back-end.
    // Vulkan doesn't do any waiting for you, our back-end code is going to manage sync objects,
    // and we'll have to check that we've exceeded the max wait timeout. Also, this is ES 3.0 so
    // we'll defer the implementation until we tackle the next version.
    // outCaps->maxServerWaitTimeout

    GLuint maxUniformVectors = physicalDeviceProperties.limits.maxUniformBufferRange /
                               (sizeof(GLfloat) * kComponentsPerVector);

    // Clamp the maxUniformVectors to 1024u, on AMD the maxUniformBufferRange is way too high.
    maxUniformVectors = std::min(1024u, maxUniformVectors);

    const GLuint maxUniformComponents = maxUniformVectors * kComponentsPerVector;

    // Uniforms are implemented using a uniform buffer, so the max number of uniforms we can
    // support is the max buffer range divided by the size of a single uniform (4X float).
    outCaps->maxVertexUniformVectors                              = maxUniformVectors;
    outCaps->maxShaderUniformComponents[gl::ShaderType::Vertex]   = maxUniformComponents;
    outCaps->maxFragmentUniformVectors                            = maxUniformVectors;
    outCaps->maxShaderUniformComponents[gl::ShaderType::Fragment] = maxUniformComponents;

    // TODO(jmadill): this is an ES 3.0 property and we can skip implementing it for now.
    // This is maxDescriptorSetUniformBuffers minus the number of uniform buffers we
    // reserve for internal variables. We reserve one per shader stage for default uniforms
    // and likely one per shader stage for ANGLE internal variables.
    // outCaps->maxShaderUniformBlocks[gl::ShaderType::Vertex] = ...

    // we use the same bindings on each stage, so the limitation is the same combined or not.
    outCaps->maxCombinedTextureImageUnits =
        physicalDeviceProperties.limits.maxPerStageDescriptorSamplers;
    outCaps->maxShaderTextureImageUnits[gl::ShaderType::Fragment] =
        physicalDeviceProperties.limits.maxPerStageDescriptorSamplers;
    outCaps->maxShaderTextureImageUnits[gl::ShaderType::Vertex] =
        physicalDeviceProperties.limits.maxPerStageDescriptorSamplers;

    // The max vertex output components should not include gl_Position.
    // The gles2.0 section 2.10 states that "gl_Position is not a varying variable and does
    // not count against this limit.", but the Vulkan spec has no such mention in its Built-in
    // vars section. It is implicit that we need to actually reserve it for Vulkan in that case.
    // TODO(lucferron): AMD has a weird behavior when we edge toward the maximum number of varyings
    // and can often crash. Reserving an additional varying just for them bringing the total to 2.
    // http://anglebug.com/2483
    constexpr GLint kReservedVaryingCount = 2;
    outCaps->maxVaryingVectors =
        (physicalDeviceProperties.limits.maxVertexOutputComponents / 4) - kReservedVaryingCount;
    outCaps->maxVertexOutputComponents = outCaps->maxVaryingVectors * 4;
}
}  // namespace vk

namespace egl_vk
{

namespace
{

EGLint ComputeMaximumPBufferPixels(const VkPhysicalDeviceProperties &physicalDeviceProperties)
{
    // EGLints are signed 32-bit integers, it's fairly easy to overflow them, especially since
    // Vulkan's minimum guaranteed VkImageFormatProperties::maxResourceSize is 2^31 bytes.
    constexpr uint64_t kMaxValueForEGLint =
        static_cast<uint64_t>(std::numeric_limits<EGLint>::max());

    // TODO(geofflang): Compute the maximum size of a pbuffer by using the maxResourceSize result
    // from vkGetPhysicalDeviceImageFormatProperties for both the color and depth stencil format and
    // the exact image creation parameters that would be used to create the pbuffer. Because it is
    // always safe to return out-of-memory errors on pbuffer allocation, it's fine to simply return
    // the number of pixels in a max width by max height pbuffer for now. http://anglebug.com/2622

    // Storing the result of squaring a 32-bit unsigned int in a 64-bit unsigned int is safe.
    static_assert(std::is_same<decltype(physicalDeviceProperties.limits.maxImageDimension2D),
                               uint32_t>::value,
                  "physicalDeviceProperties.limits.maxImageDimension2D expected to be a uint32_t.");
    const uint64_t maxDimensionsSquared =
        static_cast<uint64_t>(physicalDeviceProperties.limits.maxImageDimension2D) *
        static_cast<uint64_t>(physicalDeviceProperties.limits.maxImageDimension2D);

    return static_cast<EGLint>(std::min(maxDimensionsSquared, kMaxValueForEGLint));
}

// Generates a basic config for a combination of color format, depth stencil format and sample
// count.
egl::Config GenerateDefaultConfig(const RendererVk *renderer,
                                  const gl::InternalFormat &colorFormat,
                                  const gl::InternalFormat &depthStencilFormat,
                                  EGLint sampleCount)
{
    const VkPhysicalDeviceProperties &physicalDeviceProperties =
        renderer->getPhysicalDeviceProperties();
    gl::Version maxSupportedESVersion = renderer->getMaxSupportedESVersion();

    EGLint es2Support = (maxSupportedESVersion.major >= 2 ? EGL_OPENGL_ES2_BIT : 0);
    EGLint es3Support = (maxSupportedESVersion.major >= 3 ? EGL_OPENGL_ES3_BIT : 0);

    egl::Config config;

    config.renderTargetFormat = colorFormat.internalFormat;
    config.depthStencilFormat = depthStencilFormat.internalFormat;
    config.bufferSize         = colorFormat.pixelBytes * 8;
    config.redSize            = colorFormat.redBits;
    config.greenSize          = colorFormat.greenBits;
    config.blueSize           = colorFormat.blueBits;
    config.alphaSize          = colorFormat.alphaBits;
    config.alphaMaskSize      = 0;
    config.bindToTextureRGB   = EGL_FALSE;
    config.bindToTextureRGBA  = EGL_FALSE;
    config.colorBufferType    = EGL_RGB_BUFFER;
    config.configCaveat       = EGL_NONE;
    config.conformant         = 0;
    config.depthSize          = depthStencilFormat.depthBits;
    config.stencilSize        = depthStencilFormat.stencilBits;
    config.level              = 0;
    config.matchNativePixmap  = EGL_NONE;
    config.maxPBufferWidth    = physicalDeviceProperties.limits.maxImageDimension2D;
    config.maxPBufferHeight   = physicalDeviceProperties.limits.maxImageDimension2D;
    config.maxPBufferPixels   = ComputeMaximumPBufferPixels(physicalDeviceProperties);
    config.maxSwapInterval    = 1;
    config.minSwapInterval    = 1;
    config.nativeRenderable   = EGL_TRUE;
    config.nativeVisualID     = 0;
    config.nativeVisualType   = EGL_NONE;
    config.renderableType     = es2Support | es3Support;
    config.sampleBuffers      = (sampleCount > 0) ? 1 : 0;
    config.samples            = sampleCount;
    config.surfaceType        = EGL_WINDOW_BIT | EGL_PBUFFER_BIT;
    // Vulkan surfaces use a different origin than OpenGL, always prefer to be flipped vertically if
    // possible.
    config.optimalOrientation    = EGL_SURFACE_ORIENTATION_INVERT_Y_ANGLE;
    config.transparentType       = EGL_NONE;
    config.transparentRedValue   = 0;
    config.transparentGreenValue = 0;
    config.transparentBlueValue  = 0;
    config.colorComponentType =
        gl_egl::GLComponentTypeToEGLColorComponentType(colorFormat.componentType);

    return config;
}

}  // anonymous namespace

egl::ConfigSet GenerateConfigs(const GLenum *colorFormats,
                               size_t colorFormatsCount,
                               const GLenum *depthStencilFormats,
                               size_t depthStencilFormatCount,
                               const EGLint *sampleCounts,
                               size_t sampleCountsCount,
                               DisplayVk *display)
{
    ASSERT(colorFormatsCount > 0);
    ASSERT(display != nullptr);

    egl::ConfigSet configSet;

    for (size_t colorFormatIdx = 0; colorFormatIdx < colorFormatsCount; colorFormatIdx++)
    {
        const gl::InternalFormat &colorFormatInfo =
            gl::GetSizedInternalFormatInfo(colorFormats[colorFormatIdx]);
        ASSERT(colorFormatInfo.sized);

        for (size_t depthStencilFormatIdx = 0; depthStencilFormatIdx < depthStencilFormatCount;
             depthStencilFormatIdx++)
        {
            const gl::InternalFormat &depthStencilFormatInfo =
                gl::GetSizedInternalFormatInfo(depthStencilFormats[depthStencilFormatIdx]);
            ASSERT(depthStencilFormats[depthStencilFormatIdx] == GL_NONE ||
                   depthStencilFormatInfo.sized);

            for (size_t sampleCountIndex = 0; sampleCountIndex < sampleCountsCount;
                 sampleCountIndex++)
            {
                egl::Config config =
                    GenerateDefaultConfig(display->getRenderer(), colorFormatInfo,
                                          depthStencilFormatInfo, sampleCounts[sampleCountIndex]);
                if (display->checkConfigSupport(&config))
                {
                    configSet.add(config);
                }
            }
        }
    }

    return configSet;
}

}  // namespace egl_vk

}  // namespace rx
