/**************************************************************************
 * 
 * Copyright 2007 Tungsten Graphics, Inc., Cedar Park, Texas.
 * All Rights Reserved.
 *
 * 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, sub license, 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 (including the
 * next paragraph) 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 NON-INFRINGEMENT.
 * IN NO EVENT SHALL TUNGSTEN GRAPHICS AND/OR ITS SUPPLIERS 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.
 * 
 **************************************************************************/

/**
 * Primitive/vertex feedback (and/or discard) stage.
 * Used to implement transformation feedback/streaming and other things
 * which require a post-transformed vertex position (such as rasterpos,
 * selection and feedback modes).
 *
 * Authors:
 *  Brian Paul
 */


#include "pipe/p_util.h"
#include "draw_private.h"


struct feedback_stage {
   struct draw_stage stage;      /**< base class */
   uint num_prim_generated;      /**< number of primitives received */
   uint num_prim_emitted;        /**< number of primitives fed back */
   uint num_vert_emitted;        /**< number of vertices fed back */
   uint max_vert_emit;           /**< max number of verts we can emit */
   float *dest[PIPE_MAX_FEEDBACK_ATTRIBS];  /**< dests for vertex attribs */
};



/**
 * Check if there's space to store 'numVerts' in the feedback buffer(s).
 */
static boolean
check_space(const struct draw_stage *stage, uint numVerts)
{
   const struct feedback_stage *fs = (struct feedback_stage *) stage;
   return fs->num_vert_emitted + numVerts <= fs->max_vert_emit;
}


/**
 * Record the given vertex's attributes into the feedback buffer(s).
 */
static void
feedback_vertex(struct draw_stage *stage, const struct vertex_header *vertex)
{
   struct feedback_stage *fs = (struct feedback_stage *) stage;
   const struct pipe_feedback_state *feedback = &stage->draw->feedback;
   const uint select = feedback->interleaved ? 0 : 1;
   uint i;

   /*
    * Note: 'select' is either 0 or 1.  By multiplying 'i' by 'select'
    * we can either address output buffer 0 (for interleaving) or
    * output buffer i (for non-interleaved).
    */
   for (i = 0; i < feedback->num_attribs; i++) {
      const uint slot = feedback->attrib[i];
      const float *src = slot ? vertex->data[slot] : vertex->clip;
      const uint size = feedback->size[i];
      float *dest = fs->dest[i * select];

      switch (size) {
      case 4:
         dest[3] = src[3];
         /* fall-through */
      case 3:
         dest[2] = src[2];
         /* fall-through */
      case 2:
         dest[1] = src[1];
         /* fall-through */
      case 1:
         dest[0] = src[0];
         /* fall-through */
      default:
         ;
      }
      fs->dest[i * select] += size;
   }

   fs->num_vert_emitted++;
}


static void feedback_begin( struct draw_stage *stage )
{
   struct feedback_stage *fs = (struct feedback_stage *) stage;
   const struct pipe_feedback_state *feedback = &stage->draw->feedback;

   fs->num_prim_generated = 0;
   fs->num_prim_emitted = 0;
   fs->num_vert_emitted = 0;

   assert(feedback->enabled);

   /* Compute max_vert_emit, the max number of vertices we can emit.
    * And, setup dest[] pointers.
    */
   if (stage->draw->feedback.interleaved) {
      uint i, vertex_size = 0;
      /* compute size of each interleaved vertex, in floats */
      for (i = 0; i < feedback->num_attribs; i++) {
         vertex_size += feedback->size[i];
      }
      /* compute max number of vertices we can feedback */
      fs->max_vert_emit = stage->draw->mapped_feedback_buffer_size[0]
         / sizeof(float) / vertex_size;

      fs->dest[0] = (float *) stage->draw->mapped_feedback_buffer[0];
   }
   else {
      uint i;
      uint max = ~0;
      for (i = 0; i < feedback->num_attribs; i++) {
         uint n = stage->draw->mapped_feedback_buffer_size[i]
            / sizeof(float) / feedback->size[i];
         if (n < max)
            max = n;
         fs->dest[i] = (float *) stage->draw->mapped_feedback_buffer[i];
      }
      fs->max_vert_emit = max;
   }

   if (!feedback->discard)
      stage->next->begin( stage->next );
}


static void feedback_tri( struct draw_stage *stage,
                          struct prim_header *header )
{
   struct feedback_stage *fs = (struct feedback_stage *) stage;

   fs->num_prim_generated++;

   if (stage->draw->feedback.enabled && check_space(stage, 3)) {
      feedback_vertex(stage, header->v[0]);
      feedback_vertex(stage, header->v[1]);
      feedback_vertex(stage, header->v[2]);
      fs->num_prim_emitted++;
   }

   if (!stage->draw->feedback.discard)
      stage->next->tri( stage->next, header );
}


static void feedback_line( struct draw_stage *stage,
                           struct prim_header *header )
{
   struct feedback_stage *fs = (struct feedback_stage *) stage;

   fs->num_prim_generated++;

   if (stage->draw->feedback.enabled && check_space(stage, 2)) {
      feedback_vertex(stage, header->v[0]);
      feedback_vertex(stage, header->v[1]);
      fs->num_prim_emitted++;
   }

   if (!stage->draw->feedback.discard)
      stage->next->line( stage->next, header );
}


static void feedback_point( struct draw_stage *stage,
                            struct prim_header *header )
{
   struct feedback_stage *fs = (struct feedback_stage *) stage;

   fs->num_prim_generated++;

   if (stage->draw->feedback.enabled && check_space(stage, 1)) {
      feedback_vertex(stage, header->v[0]);
      fs->num_prim_emitted++;
   }

   if (!stage->draw->feedback.discard)
      stage->next->point( stage->next, header );
}


static void feedback_end( struct draw_stage *stage )
{

   /* XXX Unmap the vertex feedback buffers so we can write to them */


   if (!stage->draw->feedback.discard)
      stage->next->end( stage->next );
}



static void feedback_reset_stipple_counter( struct draw_stage *stage )
{
   if (!stage->draw->feedback.discard)
      stage->next->reset_stipple_counter( stage->next );
}


/**
 * Create feedback drawing stage.
 */
struct draw_stage *draw_feedback_stage( struct draw_context *draw )
{
   struct feedback_stage *feedback = CALLOC_STRUCT(feedback_stage);

   feedback->stage.draw = draw;
   feedback->stage.next = NULL;
   feedback->stage.begin = feedback_begin;
   feedback->stage.point = feedback_point;
   feedback->stage.line = feedback_line;
   feedback->stage.tri = feedback_tri;
   feedback->stage.end = feedback_end;
   feedback->stage.reset_stipple_counter = feedback_reset_stipple_counter;

   return &feedback->stage;
}