/*
 * Copyright (C) 2015 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.
 */

//! \file

#ifndef TNT_FILAMENT_VERTEXBUFFER_H
#define TNT_FILAMENT_VERTEXBUFFER_H

#include <filament/FilamentAPI.h>
#include <filament/MaterialEnums.h>

#include <backend/BufferDescriptor.h>
#include <backend/DriverEnums.h>

#include <utils/compiler.h>

namespace filament {

class FVertexBuffer;

class BufferObject;
class Engine;

/**
 * Holds a set of buffers that define the geometry of a Renderable.
 *
 * The geometry of the Renderable itself is defined by a set of vertex attributes such as
 * position, color, normals, tangents, etc...
 *
 * There is no need to have a 1-to-1 mapping between attributes and buffer. A buffer can hold the
 * data of several attributes -- attributes are then referred as being "interleaved".
 *
 * The buffers themselves are GPU resources, therefore mutating their data can be relatively slow.
 * For this reason, it is best to separate the constant data from the dynamic data into multiple
 * buffers.
 *
 * It is possible, and even encouraged, to use a single vertex buffer for several Renderables.
 *
 * @see IndexBuffer, RenderableManager
 */
class UTILS_PUBLIC VertexBuffer : public FilamentAPI {
    struct BuilderDetails;

public:
    using AttributeType = backend::ElementType;
    using BufferDescriptor = backend::BufferDescriptor;

    class Builder : public BuilderBase<BuilderDetails> {
        friend struct BuilderDetails;
    public:
        Builder() noexcept;
        Builder(Builder const& rhs) noexcept;
        Builder(Builder&& rhs) noexcept;
        ~Builder() noexcept;
        Builder& operator=(Builder const& rhs) noexcept;
        Builder& operator=(Builder&& rhs) noexcept;

        /**
         * Defines how many buffers will be created in this vertex buffer set. These buffers are
         * later referenced by index from 0 to \p bufferCount - 1.
         *
         * This call is mandatory. The default is 0.
         *
         * @param bufferCount Number of buffers in this vertex buffer set. The maximum value is 8.
         * @return A reference to this Builder for chaining calls.
         */
        Builder& bufferCount(uint8_t bufferCount) noexcept;

        /**
         * Size of each buffer in the set in vertex.
         *
         * @param vertexCount Number of vertices in each buffer in this set.
         * @return A reference to this Builder for chaining calls.
         */
        Builder& vertexCount(uint32_t vertexCount) noexcept;

        /**
         * Allows buffers to be swapped out and shared using BufferObject.
         *
         * If buffer objects mode is enabled, clients must call setBufferObjectAt rather than
         * setBufferAt. This allows sharing of data between VertexBuffer objects, but it may
         * slightly increase the memory footprint of Filament's internal bookkeeping.
         *
         * @param enabled If true, enables buffer object mode.  False by default.
         */
        Builder& enableBufferObjects(bool enabled = true) noexcept;

        /**
         * Sets up an attribute for this vertex buffer set.
         *
         * Using \p byteOffset and \p byteStride, attributes can be interleaved in the same buffer.
         *
         * @param attribute The attribute to set up.
         * @param bufferIndex  The index of the buffer containing the data for this attribute. Must
         *                     be between 0 and bufferCount() - 1.
         * @param attributeType The type of the attribute data (e.g. byte, float3, etc...)
         * @param byteOffset Offset in *bytes* into the buffer \p bufferIndex
         * @param byteStride Stride in *bytes* to the next element of this attribute. When set to
         *                   zero the attribute size, as defined by \p attributeType is used.
         *
         * @return A reference to this Builder for chaining calls.
         *
         * @warning VertexAttribute::TANGENTS must be specified as a quaternion and is how normals
         *          are specified.
         *
         * @warning Not all backends support 3-component attributes that are not floats. For help
         *          with conversion, see geometry::Transcoder.
         *
         * @see VertexAttribute
         *
         * This is a no-op if the \p attribute is an invalid enum.
         * This is a no-op if the \p bufferIndex is out of bounds.
         *
         */
        Builder& attribute(VertexAttribute attribute, uint8_t bufferIndex,
                AttributeType attributeType,
                uint32_t byteOffset = 0, uint8_t byteStride = 0) noexcept;

        /**
         * Sets whether a given attribute should be normalized. By default attributes are not
         * normalized. A normalized attribute is mapped between 0 and 1 in the shader. This applies
         * only to integer types.
         *
         * @param attribute Enum of the attribute to set the normalization flag to.
         * @param normalize true to automatically normalize the given attribute.
         * @return A reference to this Builder for chaining calls.
         *
         * This is a no-op if the \p attribute is an invalid enum.
         */
        Builder& normalized(VertexAttribute attribute, bool normalize = true) noexcept;

        /**
         * Creates the VertexBuffer object and returns a pointer to it.
         *
         * @param engine Reference to the filament::Engine to associate this VertexBuffer with.
         *
         * @return pointer to the newly created object or nullptr if exceptions are disabled and
         *         an error occurred.
         *
         * @exception utils::PostConditionPanic if a runtime error occurred, such as running out of
         *            memory or other resources.
         * @exception utils::PreConditionPanic if a parameter to a builder function was invalid.
         */
        VertexBuffer* build(Engine& engine);

    private:
        friend class FVertexBuffer;
    };

    /**
     * Returns the vertex count.
     * @return Number of vertices in this vertex buffer set.
     */
    size_t getVertexCount() const noexcept;

    /**
     * Asynchronously copy-initializes the specified buffer from the given buffer data.
     *
     * Do not use this if you called enableBufferObjects() on the Builder.
     *
     * @param engine Reference to the filament::Engine to associate this VertexBuffer with.
     * @param bufferIndex Index of the buffer to initialize. Must be between 0
     *                    and Builder::bufferCount() - 1.
     * @param buffer A BufferDescriptor representing the data used to initialize the buffer at
     *               index \p bufferIndex. BufferDescriptor points to raw, untyped data that will
     *               be copied as-is into the buffer.
     * @param byteOffset Offset in *bytes* into the buffer at index \p bufferIndex of this vertex
     *                   buffer set.
     */
    void setBufferAt(Engine& engine, uint8_t bufferIndex, BufferDescriptor&& buffer,
            uint32_t byteOffset = 0);

    /**
     * Swaps in the given buffer object.
     *
     * To use this, you must first call enableBufferObjects() on the Builder.
     *
     * @param engine Reference to the filament::Engine to associate this VertexBuffer with.
     * @param bufferIndex Index of the buffer to initialize. Must be between 0
     *                    and Builder::bufferCount() - 1.
     * @param bufferObject The handle to the GPU data that will be used in this buffer slot.
     */
    void setBufferObjectAt(Engine& engine, uint8_t bufferIndex, BufferObject const* bufferObject);

    /**
     * Specifies the quaternion type for the "populateTangentQuaternions" utility.
     */
    enum QuatType {
        HALF4,  //!< 2 bytes per component as half-floats (8 bytes per quat)
        SHORT4, //!< 2 bytes per component as normalized integers (8 bytes per quat)
        FLOAT4, //!< 4 bytes per component as floats (16 bytes per quat)
    };

    /**
     * Specifies the parameters for the "populateTangentQuaternions" utility.
     */
    struct QuatTangentContext {
        QuatType quatType;                      //!< desired quaternion type (required)
        size_t quatCount;                       //!< number of quaternions (required)
        void* outBuffer;                        //!< pre-allocated output buffer (required)
        size_t outStride;                       //!< desired stride in bytes (optional)
        const math::float3* normals;  //!< source normals (required)
        size_t normalsStride;                   //!< normals stride in bytes (optional)
        const math::float4* tangents; //!< source tangents (optional)
        size_t tangentsStride;                  //!< tangents stride in bytes (optional)
    };

    /**
     * Convenience function that consumes normal vectors (and, optionally, tangent vectors) and
     * produces quaternions that can be passed into a TANGENTS buffer.
     *
     * The given output buffer must be preallocated with at least quatCount * outStride bytes.
     *
     * Normals are required but tangents are optional, in which case this function tries to generate
     * reasonable tangents. The given normals should be unit length.
     *
     * If supplied, the tangent vectors should be unit length and should be orthogonal to the
     * normals. The w component of the tangent is a sign (-1 or +1) indicating handedness of the
     * basis.
     *
     * @param ctx An initialized QuatTangentContext structure.
     *
     * @deprecated Instead please use filament::geometry::SurfaceOrientation from libgeometry, it
     * has additional capabilities and a daisy-chain API. Be sure to explicitly link libgeometry
     * since its dependency might be removed in future versions of libfilament.
     */
    UTILS_DEPRECATED
    static void populateTangentQuaternions(const QuatTangentContext& ctx);
};

} // namespace filament

#endif // TNT_FILAMENT_VERTEXBUFFER_H
