/*
 * Mesa 3-D graphics library
 *
 * Copyright (C) 2014 LunarG, Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 * Authors:
 *    Chia-I Wu <olv@lunarg.com>
 */

#ifndef ILO_BUILDER_H
#define ILO_BUILDER_H

#include "intel_winsys.h"

#include "ilo_core.h"
#include "ilo_debug.h"
#include "ilo_dev.h"

enum ilo_builder_writer_type {
   ILO_BUILDER_WRITER_BATCH,
   ILO_BUILDER_WRITER_INSTRUCTION,

   ILO_BUILDER_WRITER_COUNT,
};

enum ilo_builder_item_type {
   /* for dynamic buffer */
   ILO_BUILDER_ITEM_BLOB,
   ILO_BUILDER_ITEM_CLIP_VIEWPORT,
   ILO_BUILDER_ITEM_SF_VIEWPORT,
   ILO_BUILDER_ITEM_SCISSOR_RECT,
   ILO_BUILDER_ITEM_CC_VIEWPORT,
   ILO_BUILDER_ITEM_COLOR_CALC,
   ILO_BUILDER_ITEM_DEPTH_STENCIL,
   ILO_BUILDER_ITEM_BLEND,
   ILO_BUILDER_ITEM_SAMPLER,
   ILO_BUILDER_ITEM_INTERFACE_DESCRIPTOR,

   /* for surface buffer */
   ILO_BUILDER_ITEM_SURFACE,
   ILO_BUILDER_ITEM_BINDING_TABLE,

   /* for instruction buffer */
   ILO_BUILDER_ITEM_KERNEL,

   ILO_BUILDER_ITEM_COUNT,
};

struct ilo_builder_item {
   enum ilo_builder_item_type type;
   unsigned offset;
   unsigned size;
};

struct ilo_builder_writer {
   /* internal flags */
   unsigned flags;

   unsigned size;
   struct intel_bo *bo;
   void *ptr;

   /* data written to the bottom */
   unsigned used;
   /* data written to the top */
   unsigned stolen;

   /* for decoding */
   struct ilo_builder_item *items;
   unsigned item_alloc;
   unsigned item_used;
};

/**
 * A snapshot of the writer state.
 */
struct ilo_builder_snapshot {
   unsigned reloc_count;

   unsigned used;
   unsigned stolen;
   unsigned item_used;
};

struct ilo_builder {
   const struct ilo_dev *dev;
   struct intel_winsys *winsys;
   uint32_t mocs;

   struct ilo_builder_writer writers[ILO_BUILDER_WRITER_COUNT];
   bool unrecoverable_error;

   /* for writers that have their data appended */
   unsigned begin_used[ILO_BUILDER_WRITER_COUNT];

   /* for STATE_BASE_ADDRESS */
   unsigned sba_instruction_pos;
};

void
ilo_builder_init(struct ilo_builder *builder,
                 const struct ilo_dev *dev,
                 struct intel_winsys *winsys);

void
ilo_builder_reset(struct ilo_builder *builder);

void
ilo_builder_decode(struct ilo_builder *builder);

bool
ilo_builder_begin(struct ilo_builder *builder);

struct intel_bo *
ilo_builder_end(struct ilo_builder *builder, unsigned *used);

bool
ilo_builder_validate(struct ilo_builder *builder,
                     unsigned bo_count, struct intel_bo **bos);

/**
 * Return true if the builder has a relocation entry for \p bo.
 */
static inline bool
ilo_builder_has_reloc(const struct ilo_builder *builder,
                      struct intel_bo *bo)
{
   int i;

   for (i = 0; i < ILO_BUILDER_WRITER_COUNT; i++) {
      const struct ilo_builder_writer *writer = &builder->writers[i];
      if (intel_bo_has_reloc(writer->bo, bo))
         return true;
   }

   return false;
}

void
ilo_builder_writer_discard(struct ilo_builder *builder,
                           enum ilo_builder_writer_type which);

bool
ilo_builder_writer_grow(struct ilo_builder *builder,
                        enum ilo_builder_writer_type which,
                        unsigned new_size, bool preserve);

bool
ilo_builder_writer_record(struct ilo_builder *builder,
                          enum ilo_builder_writer_type which,
                          enum ilo_builder_item_type type,
                          unsigned offset, unsigned size);

static inline void
ilo_builder_writer_checked_record(struct ilo_builder *builder,
                                  enum ilo_builder_writer_type which,
                                  enum ilo_builder_item_type item,
                                  unsigned offset, unsigned size)
{
   if (unlikely(ilo_debug & (ILO_DEBUG_BATCH | ILO_DEBUG_HANG))) {
      if (!ilo_builder_writer_record(builder, which, item, offset, size)) {
         builder->unrecoverable_error = true;
         builder->writers[which].item_used = 0;
      }
   }
}

/**
 * Return an offset to a region that is aligned to \p alignment and has at
 * least \p size bytes.  The region is reserved from the bottom.
 */
static inline unsigned
ilo_builder_writer_reserve_bottom(struct ilo_builder *builder,
                                  enum ilo_builder_writer_type which,
                                  unsigned alignment, unsigned size)
{
   struct ilo_builder_writer *writer = &builder->writers[which];
   unsigned offset;

   assert(alignment && util_is_power_of_two(alignment));
   offset = align(writer->used, alignment);

   if (unlikely(offset + size > writer->size - writer->stolen)) {
      if (!ilo_builder_writer_grow(builder, which,
            offset + size + writer->stolen, true)) {
         builder->unrecoverable_error = true;
         ilo_builder_writer_discard(builder, which);
         offset = 0;
      }

      assert(offset + size <= writer->size - writer->stolen);
   }

   return offset;
}

/**
 * Similar to ilo_builder_writer_reserve_bottom(), but reserve from the top.
 */
static inline unsigned
ilo_builder_writer_reserve_top(struct ilo_builder *builder,
                               enum ilo_builder_writer_type which,
                               unsigned alignment, unsigned size)
{
   struct ilo_builder_writer *writer = &builder->writers[which];
   unsigned offset;

   assert(alignment && util_is_power_of_two(alignment));
   offset = (writer->size - writer->stolen - size) & ~(alignment - 1);

   if (unlikely(offset < writer->used ||
            size > writer->size - writer->stolen)) {
      if (!ilo_builder_writer_grow(builder, which,
            align(writer->used, alignment) + size + writer->stolen, true)) {
         builder->unrecoverable_error = true;
         ilo_builder_writer_discard(builder, which);
      }

      offset = (writer->size - writer->stolen - size) & ~(alignment - 1);
      assert(offset + size <= writer->size - writer->stolen);
   }

   return offset;
}

/**
 * Add a relocation entry to the writer.
 */
static inline void
ilo_builder_writer_reloc(struct ilo_builder *builder,
                         enum ilo_builder_writer_type which,
                         unsigned offset, struct intel_bo *bo,
                         unsigned bo_offset, unsigned reloc_flags,
                         bool write_presumed_offset_hi)
{
   struct ilo_builder_writer *writer = &builder->writers[which];
   uint64_t presumed_offset;
   int err;

   if (write_presumed_offset_hi)
      ILO_DEV_ASSERT(builder->dev, 8, 8);
   else
      ILO_DEV_ASSERT(builder->dev, 6, 7.5);

   assert(offset + sizeof(uint32_t) <= writer->used ||
          (offset >= writer->size - writer->stolen &&
           offset + sizeof(uint32_t) <= writer->size));

   err = intel_bo_add_reloc(writer->bo, offset, bo, bo_offset,
         reloc_flags, &presumed_offset);
   if (unlikely(err))
      builder->unrecoverable_error = true;

   if (write_presumed_offset_hi) {
      *((uint64_t *) ((char *) writer->ptr + offset)) = presumed_offset;
   } else {
      /* 32-bit addressing */
      assert(presumed_offset == (uint64_t) ((uint32_t) presumed_offset));
      *((uint32_t *) ((char *) writer->ptr + offset)) = presumed_offset;
   }
}

/**
 * Reserve a region from the dynamic buffer.  Both the offset, in bytes, and
 * the pointer to the reserved region are returned.  The pointer is only valid
 * until the next reserve call.
 *
 * Note that \p alignment is in bytes and \p len is in DWords.
 */
static inline uint32_t
ilo_builder_dynamic_pointer(struct ilo_builder *builder,
                            enum ilo_builder_item_type item,
                            unsigned alignment, unsigned len,
                            uint32_t **dw)
{
   const enum ilo_builder_writer_type which = ILO_BUILDER_WRITER_BATCH;
   const unsigned size = len << 2;
   const unsigned offset = ilo_builder_writer_reserve_top(builder,
         which, alignment, size);
   struct ilo_builder_writer *writer = &builder->writers[which];

   /* all states are at least aligned to 32-bytes */
   if (item != ILO_BUILDER_ITEM_BLOB)
      assert(alignment % 32 == 0);

   *dw = (uint32_t *) ((char *) writer->ptr + offset);

   writer->stolen = writer->size - offset;

   ilo_builder_writer_checked_record(builder, which, item, offset, size);

   return offset;
}

/**
 * Write a dynamic state to the dynamic buffer.
 */
static inline uint32_t
ilo_builder_dynamic_write(struct ilo_builder *builder,
                          enum ilo_builder_item_type item,
                          unsigned alignment, unsigned len,
                          const uint32_t *dw)
{
   uint32_t offset, *dst;

   offset = ilo_builder_dynamic_pointer(builder, item, alignment, len, &dst);
   memcpy(dst, dw, len << 2);

   return offset;
}

/**
 * Reserve some space from the top (for prefetches).
 */
static inline void
ilo_builder_dynamic_pad_top(struct ilo_builder *builder, unsigned len)
{
   const enum ilo_builder_writer_type which = ILO_BUILDER_WRITER_BATCH;
   const unsigned size = len << 2;
   struct ilo_builder_writer *writer = &builder->writers[which];

   if (writer->stolen < size) {
      ilo_builder_writer_reserve_top(builder, which,
            1, size - writer->stolen);
      writer->stolen = size;
   }
}

static inline unsigned
ilo_builder_dynamic_used(const struct ilo_builder *builder)
{
   const enum ilo_builder_writer_type which = ILO_BUILDER_WRITER_BATCH;
   const struct ilo_builder_writer *writer = &builder->writers[which];

   return writer->stolen >> 2;
}

/**
 * Reserve a region from the surface buffer.  Both the offset, in bytes, and
 * the pointer to the reserved region are returned.  The pointer is only valid
 * until the next reserve call.
 *
 * Note that \p alignment is in bytes and \p len is in DWords.
 */
static inline uint32_t
ilo_builder_surface_pointer(struct ilo_builder *builder,
                            enum ilo_builder_item_type item,
                            unsigned alignment, unsigned len,
                            uint32_t **dw)
{
   assert(item == ILO_BUILDER_ITEM_SURFACE ||
          item == ILO_BUILDER_ITEM_BINDING_TABLE);

   return ilo_builder_dynamic_pointer(builder, item, alignment, len, dw);
}

/**
 * Add a relocation entry for a DWord of a surface state.
 */
static inline void
ilo_builder_surface_reloc(struct ilo_builder *builder,
                          uint32_t offset, unsigned dw_index,
                          struct intel_bo *bo, unsigned bo_offset,
                          unsigned reloc_flags)
{
   const enum ilo_builder_writer_type which = ILO_BUILDER_WRITER_BATCH;

   ilo_builder_writer_reloc(builder, which, offset + (dw_index << 2),
         bo, bo_offset, reloc_flags, false);
}

static inline void
ilo_builder_surface_reloc64(struct ilo_builder *builder,
                            uint32_t offset, unsigned dw_index,
                            struct intel_bo *bo, unsigned bo_offset,
                            unsigned reloc_flags)
{
   const enum ilo_builder_writer_type which = ILO_BUILDER_WRITER_BATCH;

   ilo_builder_writer_reloc(builder, which, offset + (dw_index << 2),
         bo, bo_offset, reloc_flags, true);
}

static inline unsigned
ilo_builder_surface_used(const struct ilo_builder *builder)
{
   return ilo_builder_dynamic_used(builder);
}

/**
 * Write a kernel to the instruction buffer.  The offset, in bytes, of the
 * kernel is returned.
 */
static inline uint32_t
ilo_builder_instruction_write(struct ilo_builder *builder,
                              unsigned size, const void *kernel)
{
   const enum ilo_builder_writer_type which = ILO_BUILDER_WRITER_INSTRUCTION;
   /*
    * From the Sandy Bridge PRM, volume 4 part 2, page 112:
    *
    *     "Due to prefetch of the instruction stream, the EUs may attempt to
    *      access up to 8 instructions (128 bytes) beyond the end of the
    *      kernel program - possibly into the next memory page.  Although
    *      these instructions will not be executed, software must account for
    *      the prefetch in order to avoid invalid page access faults."
    */
   const unsigned reserved_size = size + 128;
   /* kernels are aligned to 64 bytes */
   const unsigned alignment = 64;
   const unsigned offset = ilo_builder_writer_reserve_bottom(builder,
         which, alignment, reserved_size);
   struct ilo_builder_writer *writer = &builder->writers[which];

   memcpy((char *) writer->ptr + offset, kernel, size);

   writer->used = offset + size;

   ilo_builder_writer_checked_record(builder, which,
         ILO_BUILDER_ITEM_KERNEL, offset, size);

   return offset;
}

/**
 * Reserve a region from the batch buffer.  Both the offset, in DWords, and
 * the pointer to the reserved region are returned.  The pointer is only valid
 * until the next reserve call.
 *
 * Note that \p len is in DWords.
 */
static inline unsigned
ilo_builder_batch_pointer(struct ilo_builder *builder,
                          unsigned len, uint32_t **dw)
{
   const enum ilo_builder_writer_type which = ILO_BUILDER_WRITER_BATCH;
   /*
    * We know the batch bo is always aligned.  Using 1 here should allow the
    * compiler to optimize away aligning.
    */
   const unsigned alignment = 1;
   const unsigned size = len << 2;
   const unsigned offset = ilo_builder_writer_reserve_bottom(builder,
         which, alignment, size);
   struct ilo_builder_writer *writer = &builder->writers[which];

   assert(offset % 4 == 0);
   *dw = (uint32_t *) ((char *) writer->ptr + offset);

   writer->used = offset + size;

   return offset >> 2;
}

/**
 * Write a command to the batch buffer.
 */
static inline unsigned
ilo_builder_batch_write(struct ilo_builder *builder,
                        unsigned len, const uint32_t *dw)
{
   unsigned pos;
   uint32_t *dst;

   pos = ilo_builder_batch_pointer(builder, len, &dst);
   memcpy(dst, dw, len << 2);

   return pos;
}

/**
 * Add a relocation entry for a DWord of a command.
 */
static inline void
ilo_builder_batch_reloc(struct ilo_builder *builder, unsigned pos,
                        struct intel_bo *bo, unsigned bo_offset,
                        unsigned reloc_flags)
{
   const enum ilo_builder_writer_type which = ILO_BUILDER_WRITER_BATCH;

   ilo_builder_writer_reloc(builder, which, pos << 2,
         bo, bo_offset, reloc_flags, false);
}

static inline void
ilo_builder_batch_reloc64(struct ilo_builder *builder, unsigned pos,
                          struct intel_bo *bo, unsigned bo_offset,
                          unsigned reloc_flags)
{
   const enum ilo_builder_writer_type which = ILO_BUILDER_WRITER_BATCH;

   ilo_builder_writer_reloc(builder, which, pos << 2,
         bo, bo_offset, reloc_flags, true);
}

static inline unsigned
ilo_builder_batch_used(const struct ilo_builder *builder)
{
   const enum ilo_builder_writer_type which = ILO_BUILDER_WRITER_BATCH;
   const struct ilo_builder_writer *writer = &builder->writers[which];

   return writer->used >> 2;
}

static inline unsigned
ilo_builder_batch_space(const struct ilo_builder *builder)
{
   const enum ilo_builder_writer_type which = ILO_BUILDER_WRITER_BATCH;
   const struct ilo_builder_writer *writer = &builder->writers[which];

   return (writer->size - writer->stolen - writer->used) >> 2;
}

static inline void
ilo_builder_batch_discard(struct ilo_builder *builder)
{
   ilo_builder_writer_discard(builder, ILO_BUILDER_WRITER_BATCH);
}

static inline void
ilo_builder_batch_print_stats(const struct ilo_builder *builder)
{
   const enum ilo_builder_writer_type which = ILO_BUILDER_WRITER_BATCH;
   const struct ilo_builder_writer *writer = &builder->writers[which];

   ilo_printf("%d+%d bytes (%d%% full)\n",
         writer->used, writer->stolen,
         (writer->used + writer->stolen) * 100 / writer->size);
}

void
ilo_builder_batch_snapshot(const struct ilo_builder *builder,
                           struct ilo_builder_snapshot *snapshot);

void
ilo_builder_batch_restore(struct ilo_builder *builder,
                          const struct ilo_builder_snapshot *snapshot);

#endif /* ILO_BUILDER_H */
