//
// Copyright 2024 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.
//

#ifndef LIBANGLE_RENDERER_WGPU_WGPU_COMMAND_BUFFER_H_
#define LIBANGLE_RENDERER_WGPU_WGPU_COMMAND_BUFFER_H_

#include "common/debug.h"
#include "common/unsafe_buffers.h"
#include "libANGLE/renderer/wgpu/wgpu_utils.h"

#include <webgpu/webgpu.h>
#include <unordered_set>

namespace rx
{
namespace webgpu
{

#define ANGLE_WGPU_COMMANDS_X(PROC) \
    PROC(BeginOcclusionQuery)       \
    PROC(Draw)                      \
    PROC(DrawIndexed)               \
    PROC(DrawIndexedIndirect)       \
    PROC(DrawIndirect)              \
    PROC(End)                       \
    PROC(EndOcclusionQuery)         \
    PROC(ExecuteBundles)            \
    PROC(InsertDebugMarker)         \
    PROC(PixelLocalStorageBarrier)  \
    PROC(PopDebugGroup)             \
    PROC(PushDebugGroup)            \
    PROC(SetBindGroup)              \
    PROC(SetBlendConstant)          \
    PROC(SetIndexBuffer)            \
    PROC(SetLabel)                  \
    PROC(SetPipeline)               \
    PROC(SetScissorRect)            \
    PROC(SetStencilReference)       \
    PROC(SetVertexBuffer)           \
    PROC(SetViewport)               \
    PROC(WriteTimestamp)

#define WGPU_DECLARE_COMMAND_ID(CMD) CMD,

enum class CommandID : uint8_t
{
    Invalid = 0,
    ANGLE_WGPU_COMMANDS_X(WGPU_DECLARE_COMMAND_ID)
};

ANGLE_ENABLE_STRUCT_PADDING_WARNINGS

struct BeginOcclusionQueryCommand
{
    uint64_t pad;
};

struct DrawCommand
{
    uint32_t vertexCount;
    uint32_t instanceCount;
    uint32_t firstVertex;
    uint32_t firstInstance;
};

struct DrawIndexedCommand
{
    uint32_t indexCount;
    uint32_t instanceCount;
    uint32_t firstIndex;
    uint32_t baseVertex;
    uint32_t firstInstance;
    uint32_t pad;
};

struct DrawIndexedIndirectCommand
{
    uint64_t pad;
};

struct DrawIndirectCommand
{
    uint64_t pad;
};

struct EndCommand
{
    uint64_t pad;
};

struct EndOcclusionQueryCommand
{
    uint64_t pad;
};

struct ExecuteBundlesCommand
{
    uint64_t pad;
};

struct InsertDebugMarkerCommand
{
    uint64_t pad;
};

struct PixelLocalStorageBarrierCommand
{
    uint64_t pad;
};

struct PopDebugGroupCommand
{
    uint64_t pad;
};

struct PushDebugGroupCommand
{
    uint64_t pad;
};

struct SetBindGroupCommand
{
    uint32_t groupIndex;
    uint32_t pad0;
    union
    {
        WGPUBindGroup bindGroup;
        uint64_t pad1;  // Pad to 64 bits on 32-bit systems
    };
};

struct SetBlendConstantCommand
{
    float r;
    float g;
    float b;
    float a;
};

struct SetIndexBufferCommand
{
    union
    {
        WGPUBuffer buffer;
        uint64_t pad0;  // Pad to 64 bits on 32-bit systems
    };
    WGPUIndexFormat format;
    uint32_t pad1;
    uint64_t offset;
    uint64_t size;
};

struct SetLabelCommand
{
    uint64_t pad;
};

struct SetPipelineCommand
{
    union
    {
        WGPURenderPipeline pipeline;
        uint64_t padding;  // Pad to 64 bits on 32-bit systems
    };
};

struct SetScissorRectCommand
{
    uint32_t x;
    uint32_t y;
    uint32_t width;
    uint32_t height;
};

struct SetStencilReferenceCommand
{
    uint32_t referenceValue;
    uint32_t pad;
};

struct SetVertexBufferCommand
{
    uint32_t slot;
    uint32_t pad0;
    union
    {
        WGPUBuffer buffer;
        uint64_t pad1;  // Pad to 64 bits on 32-bit systems
    };
    uint64_t offset;
    uint64_t size;
};

struct SetViewportCommand
{
    float x;
    float y;
    float width;
    float height;
    float minDepth;
    float maxDepth;
};

struct WriteTimestampCommand
{
    uint64_t pad;
};

ANGLE_DISABLE_STRUCT_PADDING_WARNINGS

#define VERIFY_COMMAND_8_BYTE_ALIGNMENT(CMD) \
    static_assert((sizeof(CMD##Command) % 8) == 0, "Check " #CMD "Command alignment");
ANGLE_WGPU_COMMANDS_X(VERIFY_COMMAND_8_BYTE_ALIGNMENT)
#undef VERIFY_COMMAND_8_BYTE_ALIGNMENT

template <CommandID>
struct CommandTypeHelper;

#define ANGLE_WGPU_COMMAND_TYPE_HELPER(CMD)  \
    template <>                              \
    struct CommandTypeHelper<CommandID::CMD> \
    {                                        \
        using CommandType = CMD##Command;    \
    };
ANGLE_WGPU_COMMANDS_X(ANGLE_WGPU_COMMAND_TYPE_HELPER)
#undef ANGLE_WGPU_COMMAND_TYPE_HELPER

static constexpr size_t kCommandBlockSize = 1 << 14;  // 16kB

class CommandBuffer
{
  public:
    CommandBuffer();

    void draw(uint32_t vertexCount,
              uint32_t instanceCount,
              uint32_t firstVertex,
              uint32_t firstInstance);
    void drawIndexed(uint32_t indexCount,
                     uint32_t instanceCount,
                     uint32_t firstIndex,
                     int32_t baseVertex,
                     uint32_t firstInstance);
    void setBindGroup(uint32_t groupIndex, BindGroupHandle bindGroup);
    void setBlendConstant(float r, float g, float b, float a);
    void setPipeline(RenderPipelineHandle pipeline);
    void setScissorRect(uint32_t x, uint32_t y, uint32_t width, uint32_t height);
    void setStencilReference(uint32_t refVal);
    void setViewport(float x, float y, float width, float height, float minDepth, float maxDepth);
    void setIndexBuffer(BufferHandle buffer,
                        WGPUIndexFormat format,
                        uint64_t offset,
                        uint64_t size);
    void setVertexBuffer(uint32_t slot, BufferHandle buffer, uint64_t offset, uint64_t size);

    void clear();

    bool hasCommands() const { return mState.commandCount > 0; }
    bool hasSetScissorCommand() const { return mState.hasSetScissorCommand; }
    bool hasSetStencilRefCommand() const { return mState.hasSetStencilRefCommand; }
    bool hasSetViewportCommand() const { return mState.hasSetViewportCommand; }
    bool hasSetBlendConstantCommand() const { return mState.hasSetBlendConstantCommand; }

    void recordCommands(const DawnProcTable *wgpu, RenderPassEncoderHandle encoder);

  private:
    struct CommandBlock
    {
        static constexpr size_t kCommandBlockDataSize = kCommandBlockSize - (sizeof(size_t) * 2);
        uint8_t mData[kCommandBlockDataSize]          = {0};

        size_t mCurrentPosition = 0;

        static constexpr size_t kCommandIDSize = sizeof(CommandID);
        static constexpr size_t kCommandBlockInitialRemainingSize =
            kCommandBlockDataSize - kCommandIDSize;  // Leave room for one command ID at the end to
                                                     // signify the end of the list
        size_t mRemainingSize = kCommandBlockInitialRemainingSize;

        void clear();
        void finalize();

        template <typename T>
        T *getDataAtCurrentPositionAndReserveSpace(size_t space)
        {
            T *data = reinterpret_cast<T *>(&ANGLE_UNSAFE_TODO(mData[mCurrentPosition]));

            ASSERT(mRemainingSize >= space);
            mCurrentPosition += space;
            mRemainingSize -= space;

            return data;
        }
    };
    static constexpr size_t kCommandBlockStructSize = sizeof(CommandBlock);
    static_assert(kCommandBlockStructSize == kCommandBlockSize, "Size mismatch");

    std::vector<std::unique_ptr<CommandBlock>> mCommandBlocks;

    // State for the current commands held in mCommandBlocks. In a structure so that it can be
    // easily reset by calling the constructor.
    struct PerSubmissionData
    {
        size_t currentCommandBlock = 0;

        size_t commandCount             = 0;
        bool hasSetScissorCommand       = false;
        bool hasSetStencilRefCommand    = false;
        bool hasSetViewportCommand      = false;
        bool hasSetBlendConstantCommand = false;

        // std::unordered_set required because it does not move elements and stored command
        // reference addresses in the set
        std::unordered_set<RenderPipelineHandle> referencedRenderPipelines;
        std::unordered_set<BufferHandle> referencedBuffers;
        std::unordered_set<BindGroupHandle> referencedBindGroups;
    };
    PerSubmissionData mState;

    void nextCommandBlock();

    void ensureCommandSpace(size_t space)
    {
        if (mCommandBlocks.empty() ||
            mCommandBlocks[mState.currentCommandBlock]->mRemainingSize < space)
        {
            nextCommandBlock();
        }
    }

    template <CommandID Command, typename CommandType = CommandTypeHelper<Command>::CommandType>
    CommandType *initCommand()
    {
        constexpr size_t allocationSize = sizeof(CommandID) + sizeof(CommandType);
        ensureCommandSpace(allocationSize);
        CommandBlock *commandBlock = mCommandBlocks[mState.currentCommandBlock].get();

        uint8_t *idAndCommandStorage =
            commandBlock->getDataAtCurrentPositionAndReserveSpace<uint8_t>(allocationSize);

        CommandID *id = reinterpret_cast<CommandID *>(idAndCommandStorage);
        *id           = Command;

        CommandType *commandStruct = reinterpret_cast<CommandType *>(
            ANGLE_UNSAFE_TODO(idAndCommandStorage + sizeof(CommandID)));

        mState.commandCount++;

        return commandStruct;
    }
};

}  // namespace webgpu
}  // namespace rx

#endif  // LIBANGLE_RENDERER_WGPU_WGPU_COMMAND_BUFFER_H_
