/*
 * Mesa 3-D graphics library
 *
 * Copyright (C) 2012-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>
 */

#include "ilo_common.h"
#include "ilo_blitter.h"
#include "ilo_builder_3d.h"
#include "ilo_builder_media.h"
#include "ilo_state.h"
#include "ilo_render_gen.h"

#define DIRTY(state) (session->pipe_dirty & ILO_DIRTY_ ## state)

static void
gen6_emit_draw_dynamic_viewports(struct ilo_render *r,
                                 const struct ilo_state_vector *vec,
                                 struct ilo_render_draw_session *session)
{
   ILO_DEV_ASSERT(r->dev, 6, 6);

   /* SF_VIEWPORT, CLIP_VIEWPORT, and CC_VIEWPORT */
   if (DIRTY(VIEWPORT)) {
      r->state.CLIP_VIEWPORT = gen6_CLIP_VIEWPORT(r->builder,
            vec->viewport.cso, vec->viewport.count);

      r->state.SF_VIEWPORT = gen6_SF_VIEWPORT(r->builder,
            vec->viewport.cso, vec->viewport.count);

      r->state.CC_VIEWPORT = gen6_CC_VIEWPORT(r->builder,
            vec->viewport.cso, vec->viewport.count);

      session->viewport_changed = true;
   }
}

static void
gen7_emit_draw_dynamic_viewports(struct ilo_render *r,
                                 const struct ilo_state_vector *vec,
                                 struct ilo_render_draw_session *session)
{
   ILO_DEV_ASSERT(r->dev, 7, 7.5);

   /* SF_CLIP_VIEWPORT and CC_VIEWPORT */
   if (DIRTY(VIEWPORT)) {
      r->state.SF_CLIP_VIEWPORT = gen7_SF_CLIP_VIEWPORT(r->builder,
            vec->viewport.cso, vec->viewport.count);

      r->state.CC_VIEWPORT = gen6_CC_VIEWPORT(r->builder,
            vec->viewport.cso, vec->viewport.count);

      session->viewport_changed = true;
   }
}

static void
gen6_emit_draw_dynamic_scissors(struct ilo_render *r,
                                const struct ilo_state_vector *vec,
                                struct ilo_render_draw_session *session)
{
   ILO_DEV_ASSERT(r->dev, 6, 7.5);

   /* SCISSOR_RECT */
   if (DIRTY(SCISSOR) || DIRTY(VIEWPORT)) {
      /* there should be as many scissors as there are viewports */
      r->state.SCISSOR_RECT = gen6_SCISSOR_RECT(r->builder,
            &vec->scissor, vec->viewport.count);

      session->scissor_changed = true;
   }
}

static void
gen6_emit_draw_dynamic_cc(struct ilo_render *r,
                          const struct ilo_state_vector *vec,
                          struct ilo_render_draw_session *session)
{
   ILO_DEV_ASSERT(r->dev, 6, 7.5);

   /* BLEND_STATE */
   if (DIRTY(BLEND) || DIRTY(FB) || DIRTY(DSA)) {
      r->state.BLEND_STATE = gen6_BLEND_STATE(r->builder,
            vec->blend, &vec->fb, vec->dsa);

      session->blend_changed = true;
   }

   /* COLOR_CALC_STATE */
   if (DIRTY(DSA) || DIRTY(STENCIL_REF) || DIRTY(BLEND_COLOR)) {
      r->state.COLOR_CALC_STATE =
         gen6_COLOR_CALC_STATE(r->builder, &vec->stencil_ref,
               vec->dsa->alpha_ref, &vec->blend_color);

      session->cc_changed = true;
   }

   /* DEPTH_STENCIL_STATE */
   if (DIRTY(DSA)) {
      r->state.DEPTH_STENCIL_STATE =
         gen6_DEPTH_STENCIL_STATE(r->builder, vec->dsa);

      session->dsa_changed = true;
   }
}

static void
gen6_emit_draw_dynamic_samplers(struct ilo_render *r,
                                const struct ilo_state_vector *vec,
                                int shader_type,
                                struct ilo_render_draw_session *session)
{
   const struct ilo_sampler_cso * const *samplers =
      vec->sampler[shader_type].cso;
   const struct pipe_sampler_view * const *views =
      (const struct pipe_sampler_view **) vec->view[shader_type].states;
   uint32_t *sampler_state, *border_color_state;
   int sampler_count;
   bool emit_border_color = false;
   bool skip = false;

   ILO_DEV_ASSERT(r->dev, 6, 7.5);

   /* SAMPLER_BORDER_COLOR_STATE and SAMPLER_STATE */
   switch (shader_type) {
   case PIPE_SHADER_VERTEX:
      if (DIRTY(VS) || DIRTY(SAMPLER_VS) || DIRTY(VIEW_VS)) {
         sampler_state = &r->state.vs.SAMPLER_STATE;
         border_color_state = r->state.vs.SAMPLER_BORDER_COLOR_STATE;

         if (DIRTY(VS) || DIRTY(SAMPLER_VS))
            emit_border_color = true;

         sampler_count = (vec->vs) ? ilo_shader_get_kernel_param(vec->vs,
               ILO_KERNEL_SAMPLER_COUNT) : 0;

         session->sampler_vs_changed = true;
      } else {
         skip = true;
      }
      break;
   case PIPE_SHADER_FRAGMENT:
      if (DIRTY(FS) || DIRTY(SAMPLER_FS) || DIRTY(VIEW_FS)) {
         sampler_state = &r->state.wm.SAMPLER_STATE;
         border_color_state = r->state.wm.SAMPLER_BORDER_COLOR_STATE;

         if (DIRTY(VS) || DIRTY(SAMPLER_FS))
            emit_border_color = true;

         sampler_count = (vec->fs) ? ilo_shader_get_kernel_param(vec->fs,
               ILO_KERNEL_SAMPLER_COUNT) : 0;

         session->sampler_fs_changed = true;
      } else {
         skip = true;
      }
      break;
   default:
      skip = true;
      break;
   }

   if (skip)
      return;

   assert(sampler_count <= Elements(vec->view[shader_type].states) &&
          sampler_count <= Elements(vec->sampler[shader_type].cso));

   if (emit_border_color) {
      int i;

      for (i = 0; i < sampler_count; i++) {
         border_color_state[i] = (samplers[i]) ?
            gen6_SAMPLER_BORDER_COLOR_STATE(r->builder, samplers[i]) : 0;
      }
   }

   *sampler_state = gen6_SAMPLER_STATE(r->builder,
         samplers, views, border_color_state, sampler_count);
}

static void
gen6_emit_draw_dynamic_pcb(struct ilo_render *r,
                           const struct ilo_state_vector *vec,
                           struct ilo_render_draw_session *session)
{
   ILO_DEV_ASSERT(r->dev, 6, 7.5);

   /* push constant buffer for VS */
   if (DIRTY(VS) || DIRTY(CBUF) || DIRTY(CLIP)) {
      const int cbuf0_size = (vec->vs) ?
            ilo_shader_get_kernel_param(vec->vs,
                  ILO_KERNEL_PCB_CBUF0_SIZE) : 0;
      const int clip_state_size = (vec->vs) ?
            ilo_shader_get_kernel_param(vec->vs,
                  ILO_KERNEL_VS_PCB_UCP_SIZE) : 0;
      const int total_size = cbuf0_size + clip_state_size;

      if (total_size) {
         void *pcb;

         r->state.vs.PUSH_CONSTANT_BUFFER =
            gen6_push_constant_buffer(r->builder, total_size, &pcb);
         r->state.vs.PUSH_CONSTANT_BUFFER_size = total_size;

         if (cbuf0_size) {
            const struct ilo_cbuf_state *cbuf =
               &vec->cbuf[PIPE_SHADER_VERTEX];

            if (cbuf0_size <= cbuf->cso[0].user_buffer_size) {
               memcpy(pcb, cbuf->cso[0].user_buffer, cbuf0_size);
            } else {
               memcpy(pcb, cbuf->cso[0].user_buffer,
                     cbuf->cso[0].user_buffer_size);
               memset(pcb + cbuf->cso[0].user_buffer_size, 0,
                     cbuf0_size - cbuf->cso[0].user_buffer_size);
            }

            pcb += cbuf0_size;
         }

         if (clip_state_size)
            memcpy(pcb, &vec->clip, clip_state_size);

         session->pcb_vs_changed = true;
      } else if (r->state.vs.PUSH_CONSTANT_BUFFER_size) {
         r->state.vs.PUSH_CONSTANT_BUFFER = 0;
         r->state.vs.PUSH_CONSTANT_BUFFER_size = 0;

         session->pcb_vs_changed = true;
      }
   }

   /* push constant buffer for FS */
   if (DIRTY(FS) || DIRTY(CBUF)) {
      const int cbuf0_size = (vec->fs) ?
         ilo_shader_get_kernel_param(vec->fs, ILO_KERNEL_PCB_CBUF0_SIZE) : 0;

      if (cbuf0_size) {
         const struct ilo_cbuf_state *cbuf = &vec->cbuf[PIPE_SHADER_FRAGMENT];
         void *pcb;

         r->state.wm.PUSH_CONSTANT_BUFFER =
            gen6_push_constant_buffer(r->builder, cbuf0_size, &pcb);
         r->state.wm.PUSH_CONSTANT_BUFFER_size = cbuf0_size;

         if (cbuf0_size <= cbuf->cso[0].user_buffer_size) {
            memcpy(pcb, cbuf->cso[0].user_buffer, cbuf0_size);
         } else {
            memcpy(pcb, cbuf->cso[0].user_buffer,
                  cbuf->cso[0].user_buffer_size);
            memset(pcb + cbuf->cso[0].user_buffer_size, 0,
                  cbuf0_size - cbuf->cso[0].user_buffer_size);
         }

         session->pcb_fs_changed = true;
      } else if (r->state.wm.PUSH_CONSTANT_BUFFER_size) {
         r->state.wm.PUSH_CONSTANT_BUFFER = 0;
         r->state.wm.PUSH_CONSTANT_BUFFER_size = 0;

         session->pcb_fs_changed = true;
      }
   }
}

#undef DIRTY

int
ilo_render_get_draw_dynamic_states_len(const struct ilo_render *render,
                                       const struct ilo_state_vector *vec)
{
   static int static_len;
   int sh_type, len;

   ILO_DEV_ASSERT(render->dev, 6, 7.5);

   if (!static_len) {
      /* 64 bytes, or 16 dwords */
      const int alignment = 64 / 4;

      /* pad first */
      len = alignment - 1;

      /* CC states */
      len += align(GEN6_BLEND_STATE__SIZE * ILO_MAX_DRAW_BUFFERS, alignment);
      len += align(GEN6_DEPTH_STENCIL_STATE__SIZE, alignment);
      len += align(GEN6_COLOR_CALC_STATE__SIZE, alignment);

      /* viewport arrays */
      if (ilo_dev_gen(render->dev) >= ILO_GEN(7)) {
         len += 15 + /* pad first */
            align(GEN7_SF_CLIP_VIEWPORT__SIZE * ILO_MAX_VIEWPORTS, 16) +
            align(GEN6_CC_VIEWPORT__SIZE * ILO_MAX_VIEWPORTS, 8) +
            align(GEN6_SCISSOR_RECT__SIZE * ILO_MAX_VIEWPORTS, 8);
      } else {
         len += 7 + /* pad first */
            align(GEN6_SF_VIEWPORT__SIZE * ILO_MAX_VIEWPORTS, 8) +
            align(GEN6_CLIP_VIEWPORT__SIZE * ILO_MAX_VIEWPORTS, 8) +
            align(GEN6_CC_VIEWPORT__SIZE * ILO_MAX_VIEWPORTS, 8) +
            align(GEN6_SCISSOR_RECT__SIZE * ILO_MAX_VIEWPORTS, 8);
      }

      static_len = len;
   }

   len = static_len;

   for (sh_type = 0; sh_type < PIPE_SHADER_TYPES; sh_type++) {
      const int alignment = 32 / 4;
      int num_samplers = 0, pcb_len = 0;

      switch (sh_type) {
      case PIPE_SHADER_VERTEX:
         if (vec->vs) {
            num_samplers = ilo_shader_get_kernel_param(vec->vs,
                  ILO_KERNEL_SAMPLER_COUNT);
            pcb_len = ilo_shader_get_kernel_param(vec->vs,
                  ILO_KERNEL_PCB_CBUF0_SIZE);
            pcb_len += ilo_shader_get_kernel_param(vec->vs,
                  ILO_KERNEL_VS_PCB_UCP_SIZE);
         }
         break;
      case PIPE_SHADER_GEOMETRY:
         break;
      case PIPE_SHADER_FRAGMENT:
         if (vec->fs) {
            num_samplers = ilo_shader_get_kernel_param(vec->fs,
                  ILO_KERNEL_SAMPLER_COUNT);
            pcb_len = ilo_shader_get_kernel_param(vec->fs,
                  ILO_KERNEL_PCB_CBUF0_SIZE);
         }
         break;
      default:
         break;
      }

      /* SAMPLER_STATE array and SAMPLER_BORDER_COLORs */
      if (num_samplers) {
         /* prefetches are done in multiples of 4 */
         num_samplers = align(num_samplers, 4);

         len += align(GEN6_SAMPLER_STATE__SIZE * num_samplers, alignment) +
            align(GEN6_SAMPLER_BORDER_COLOR__SIZE, alignment) * num_samplers;
      }

      /* PCB */
      if (pcb_len)
         len += align(pcb_len, alignment);
   }

   return len;
}

void
ilo_render_emit_draw_dynamic_states(struct ilo_render *render,
                                    const struct ilo_state_vector *vec,
                                    struct ilo_render_draw_session *session)
{
   const unsigned dynamic_used = ilo_builder_dynamic_used(render->builder);

   ILO_DEV_ASSERT(render->dev, 6, 7.5);

   if (ilo_dev_gen(render->dev) >= ILO_GEN(7))
      gen7_emit_draw_dynamic_viewports(render, vec, session);
   else
      gen6_emit_draw_dynamic_viewports(render, vec, session);

   gen6_emit_draw_dynamic_cc(render, vec, session);
   gen6_emit_draw_dynamic_scissors(render, vec, session);
   gen6_emit_draw_dynamic_pcb(render, vec, session);

   gen6_emit_draw_dynamic_samplers(render, vec,
         PIPE_SHADER_VERTEX, session);
   gen6_emit_draw_dynamic_samplers(render, vec,
         PIPE_SHADER_FRAGMENT, session);

   assert(ilo_builder_dynamic_used(render->builder) <= dynamic_used +
         ilo_render_get_draw_dynamic_states_len(render, vec));
}

int
ilo_render_get_rectlist_dynamic_states_len(const struct ilo_render *render,
                                           const struct ilo_blitter *blitter)
{
   ILO_DEV_ASSERT(render->dev, 6, 7.5);

   return 96;
}

void
ilo_render_emit_rectlist_dynamic_states(struct ilo_render *render,
                                        const struct ilo_blitter *blitter,
                                        struct ilo_render_rectlist_session *session)
{
   const unsigned dynamic_used = ilo_builder_dynamic_used(render->builder);

   ILO_DEV_ASSERT(render->dev, 6, 7.5);

   /* both are inclusive */
   session->vb_start = gen6_user_vertex_buffer(render->builder,
         sizeof(blitter->vertices), (const void *) blitter->vertices);
   session->vb_end = session->vb_start + sizeof(blitter->vertices) - 1;

   if (blitter->uses & ILO_BLITTER_USE_DSA) {
      render->state.DEPTH_STENCIL_STATE =
         gen6_DEPTH_STENCIL_STATE(render->builder, &blitter->dsa);
   }

   if (blitter->uses & ILO_BLITTER_USE_CC) {
      render->state.COLOR_CALC_STATE =
         gen6_COLOR_CALC_STATE(render->builder, &blitter->cc.stencil_ref,
               blitter->cc.alpha_ref, &blitter->cc.blend_color);
   }

   if (blitter->uses & ILO_BLITTER_USE_VIEWPORT) {
      render->state.CC_VIEWPORT =
         gen6_CC_VIEWPORT(render->builder, &blitter->viewport, 1);
   }

   assert(ilo_builder_dynamic_used(render->builder) <= dynamic_used +
         ilo_render_get_rectlist_dynamic_states_len(render, blitter));
}

static void
gen6_emit_launch_grid_dynamic_samplers(struct ilo_render *r,
                                       const struct ilo_state_vector *vec,
                                       struct ilo_render_launch_grid_session *session)
{
   const unsigned shader_type = PIPE_SHADER_COMPUTE;
   const struct ilo_shader_state *cs = vec->cs;
   const struct ilo_sampler_cso * const *samplers =
      vec->sampler[shader_type].cso;
   const struct pipe_sampler_view * const *views =
      (const struct pipe_sampler_view **) vec->view[shader_type].states;
   int sampler_count, i;

   ILO_DEV_ASSERT(r->dev, 7, 7.5);

   sampler_count = ilo_shader_get_kernel_param(cs, ILO_KERNEL_SAMPLER_COUNT);

   assert(sampler_count <= Elements(vec->view[shader_type].states) &&
          sampler_count <= Elements(vec->sampler[shader_type].cso));

   for (i = 0; i < sampler_count; i++) {
      r->state.cs.SAMPLER_BORDER_COLOR_STATE[i] = (samplers[i]) ?
         gen6_SAMPLER_BORDER_COLOR_STATE(r->builder, samplers[i]) : 0;
   }

   r->state.cs.SAMPLER_STATE = gen6_SAMPLER_STATE(r->builder, samplers, views,
         r->state.cs.SAMPLER_BORDER_COLOR_STATE, sampler_count);
}

static void
gen6_emit_launch_grid_dynamic_pcb(struct ilo_render *r,
                                  const struct ilo_state_vector *vec,
                                  struct ilo_render_launch_grid_session *session)
{
   r->state.cs.PUSH_CONSTANT_BUFFER = 0;
   r->state.cs.PUSH_CONSTANT_BUFFER_size = 0;
}

static void
gen6_emit_launch_grid_dynamic_idrt(struct ilo_render *r,
                                   const struct ilo_state_vector *vec,
                                   struct ilo_render_launch_grid_session *session)
{
   const struct ilo_shader_state *cs = vec->cs;
   struct gen6_idrt_data data;

   ILO_DEV_ASSERT(r->dev, 7, 7.5);

   memset(&data, 0, sizeof(data));

   data.cs = cs;
   data.sampler_offset = r->state.cs.SAMPLER_STATE;
   data.binding_table_offset = r->state.cs.BINDING_TABLE_STATE;

   data.curbe_size = r->state.cs.PUSH_CONSTANT_BUFFER_size;
   data.thread_group_size = session->thread_group_size;

   session->idrt = gen6_INTERFACE_DESCRIPTOR_DATA(r->builder, &data, 1);
   session->idrt_size = 32;
}

int
ilo_render_get_launch_grid_dynamic_states_len(const struct ilo_render *render,
                                              const struct ilo_state_vector *vec)
{
   const int alignment = 32 / 4;
   int num_samplers;
   int len = 0;

   ILO_DEV_ASSERT(render->dev, 7, 7.5);

   num_samplers = ilo_shader_get_kernel_param(vec->cs,
         ILO_KERNEL_SAMPLER_COUNT);

   /* SAMPLER_STATE array and SAMPLER_BORDER_COLORs */
   if (num_samplers) {
      /* prefetches are done in multiples of 4 */
      num_samplers = align(num_samplers, 4);

      len += align(GEN6_SAMPLER_STATE__SIZE * num_samplers, alignment) +
         align(GEN6_SAMPLER_BORDER_COLOR__SIZE, alignment) * num_samplers;
   }

   len += GEN6_INTERFACE_DESCRIPTOR_DATA__SIZE;

   return len;
}

void
ilo_render_emit_launch_grid_dynamic_states(struct ilo_render *render,
                                           const struct ilo_state_vector *vec,
                                           struct ilo_render_launch_grid_session *session)
{
   const unsigned dynamic_used = ilo_builder_dynamic_used(render->builder);

   ILO_DEV_ASSERT(render->dev, 7, 7.5);

   gen6_emit_launch_grid_dynamic_samplers(render, vec, session);
   gen6_emit_launch_grid_dynamic_pcb(render, vec, session);
   gen6_emit_launch_grid_dynamic_idrt(render, vec, session);

   assert(ilo_builder_dynamic_used(render->builder) <= dynamic_used +
         ilo_render_get_launch_grid_dynamic_states_len(render, vec));
}
