/**************************************************************************
 * 
 * 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.
 * 
 **************************************************************************/

 /*
  * Authors:
  *   Brian Paul
  */

#include "main/imports.h"
#include "main/feedback.h"
#include "main/macros.h"

#include "st_context.h"
#include "st_atom.h"
#include "st_cache.h"
#include "st_draw.h"
#include "st_program.h"
#include "st_cb_rasterpos.h"
#include "st_draw.h"
#include "st_format.h"
#include "pipe/p_context.h"
#include "pipe/p_defines.h"
#include "pipe/p_winsys.h"
#include "shader/prog_instruction.h"



static void
setup_vertex_attribs(GLcontext *ctx)
{
   struct pipe_context *pipe = ctx->st->pipe;
   const struct cso_vertex_shader *vs = ctx->st->state.vs;
   const struct st_vertex_program *stvp = ctx->st->vp;
   uint slot;

   /* all attributes come from the default attribute buffer */
   {
      struct pipe_vertex_buffer vbuffer;
      vbuffer.buffer = ctx->st->default_attrib_buffer;
      vbuffer.buffer_offset = 0;
      vbuffer.pitch = 0; /* must be zero! */
      vbuffer.max_index = 1;
      pipe->set_vertex_buffer(pipe, 0, &vbuffer);
   }

   for (slot = 0; slot < vs->state.num_inputs; slot++) {
      struct pipe_vertex_element velement;
      const GLuint attr = stvp->index_to_input[slot];

      velement.src_offset = attr * 4 * sizeof(GLfloat);
      velement.vertex_buffer_index = 0;
      velement.dst_offset = 0;
      velement.src_format = PIPE_FORMAT_R32G32B32A32_FLOAT;
      pipe->set_vertex_element(pipe, slot, &velement);
   }
}


static void
setup_feedback(GLcontext *ctx)
{
   struct pipe_context *pipe = ctx->st->pipe;
   const struct pipe_shader_state *vs = &ctx->st->state.vs->state;
   struct pipe_feedback_state feedback;
   uint i;

   memset(&feedback, 0, sizeof(feedback));
   feedback.enabled = 1;
   feedback.interleaved = 1;
   feedback.discard = 1;
   feedback.num_attribs = 0;

   /* feedback all results from vertex shader */
   for (i = 0; i < vs->num_outputs; i++) {
      feedback.attrib[feedback.num_attribs] = i;
      feedback.size[feedback.num_attribs] = 4;
      feedback.num_attribs++;
   }

   if (pipe->set_feedback_state)
      pipe->set_feedback_state(pipe, &feedback);
}





/**
 * Clip a point against the view volume.
 *
 * \param v vertex vector describing the point to clip.
 * 
 * \return zero if outside view volume, or one if inside.
 */
static GLuint
viewclip_point( const GLfloat v[] )
{
   if (   v[0] > v[3] || v[0] < -v[3]
       || v[1] > v[3] || v[1] < -v[3]
       || v[2] > v[3] || v[2] < -v[3] ) {
      return 0;
   }
   else {
      return 1;
   }
}


/**
 * Clip a point against the far/near Z clipping planes.
 *
 * \param v vertex vector describing the point to clip.
 * 
 * \return zero if outside view volume, or one if inside.
 */
static GLuint
viewclip_point_z( const GLfloat v[] )
{
   if (v[2] > v[3] || v[2] < -v[3] ) {
      return 0;
   }
   else {
      return 1;
   }
}


/**
 * Clip a point against the user clipping planes.
 * 
 * \param ctx GL context.
 * \param v vertex vector describing the point to clip.
 * 
 * \return zero if the point was clipped, or one otherwise.
 */
static GLuint
userclip_point( GLcontext *ctx, const GLfloat v[] )
{
   GLuint p;

   for (p = 0; p < ctx->Const.MaxClipPlanes; p++) {
      if (ctx->Transform.ClipPlanesEnabled & (1 << p)) {
	 GLfloat dot = v[0] * ctx->Transform._ClipUserPlane[p][0]
		     + v[1] * ctx->Transform._ClipUserPlane[p][1]
		     + v[2] * ctx->Transform._ClipUserPlane[p][2]
		     + v[3] * ctx->Transform._ClipUserPlane[p][3];
         if (dot < 0.0F) {
            return 0;
         }
      }
   }

   return 1;
}


/**
 * Update the current raster position.
 * Do clip testing, etc. here.
 */
static void
update_rasterpos(GLcontext *ctx,
                 const float clipPos[4],
                 const float color0[4],
                 const float color1[4],
                 const float *fog,
                 const float *tex)
{
   uint i;
   float d, ndc[3];

   /* clip to view volume */
   if (ctx->Transform.RasterPositionUnclipped) {
      /* GL_IBM_rasterpos_clip: only clip against Z */
      if (viewclip_point_z(clipPos) == 0) {
         ctx->Current.RasterPosValid = GL_FALSE;
         return;
      }
   }
   else if (viewclip_point(clipPos) == 0) {
      /* Normal OpenGL behaviour */
      ctx->Current.RasterPosValid = GL_FALSE;
      return;
   }

   /* clip to user clipping planes */
   if (ctx->Transform.ClipPlanesEnabled && !userclip_point(ctx, clipPos)) {
      ctx->Current.RasterPosValid = GL_FALSE;
      return;
   }


   /*
    * update current raster position
    */
   /* ndc = clip / W */
   d = (clipPos[3] == 0.0F) ? 1.0F : 1.0F / clipPos[3];
   ndc[0] = clipPos[0] * d;
   ndc[1] = clipPos[1] * d;
   ndc[2] = clipPos[2] * d;
   /* wincoord = viewport_mapping(ndc) */
   ctx->Current.RasterPos[0] = (ndc[0] * ctx->Viewport._WindowMap.m[MAT_SX]
                                + ctx->Viewport._WindowMap.m[MAT_TX]);
   ctx->Current.RasterPos[1] = (ndc[1] * ctx->Viewport._WindowMap.m[MAT_SY]
                                + ctx->Viewport._WindowMap.m[MAT_TY]);
   ctx->Current.RasterPos[2] = (ndc[2] * ctx->Viewport._WindowMap.m[MAT_SZ]
                                + ctx->Viewport._WindowMap.m[MAT_TZ])
                               / ctx->DrawBuffer->_DepthMaxF;
   ctx->Current.RasterPos[3] = clipPos[3];

   /* compute raster distance */
#if 0
   if (ctx->Fog.FogCoordinateSource == GL_FOG_COORDINATE_EXT)
      ctx->Current.RasterDistance = ctx->Current.Attrib[VERT_ATTRIB_FOG][0];
   else {
      /* XXX we don't have an eye coord! */
      ctx->Current.RasterDistance =
         SQRTF( eye[0]*eye[0] + eye[1]*eye[1] + eye[2]*eye[2] );
   }
#else
   ctx->Current.RasterDistance = fog[0];
#endif

   /* colors and texcoords */
   COPY_4FV(ctx->Current.RasterColor, color0);
   COPY_4FV(ctx->Current.RasterSecondaryColor, color1);
   for (i = 0; i < ctx->Const.MaxTextureCoordUnits; i++) {
      COPY_4FV(ctx->Current.RasterTexCoords + i, tex + i *4);
   }

   ctx->Current.RasterPosValid = GL_TRUE;

   if (ctx->RenderMode == GL_SELECT) {
      _mesa_update_hitflag( ctx, ctx->Current.RasterPos[2] );
   }
}



static void
st_RasterPos(GLcontext *ctx, const GLfloat v[4])
{
   const struct st_context *st = ctx->st;
   struct pipe_context *pipe = st->pipe;
   float *buf_map;
   struct pipe_feedback_buffer fb_buf;

   st_validate_state(ctx->st);

   /* setup vertex buffers */
   setup_vertex_attribs(ctx);

   /*
    * Load the default attribute buffer with current attribs.
    */
   {
      struct pipe_buffer_handle *buf = st->default_attrib_buffer;
      const unsigned size = sizeof(ctx->Current.Attrib);
      const void *data = ctx->Current.Attrib;
      /* colors, texcoords, etc */
      pipe->winsys->buffer_data(pipe->winsys, buf, size, data);
      /* position */
      pipe->winsys->buffer_subdata(pipe->winsys, buf,
                                   0, /* offset */
                                   4 * sizeof(float), /* size */
                                   v); /* data */
   }


   /* setup feedback state */
   setup_feedback(ctx);

   /* setup vertex feedback buffer */
   {
      fb_buf.size = 8 * 4 * sizeof(float);
      fb_buf.buffer = pipe->winsys->buffer_create(pipe->winsys, 0);
      fb_buf.start_offset = 0;
      pipe->winsys->buffer_data(pipe->winsys, fb_buf.buffer,
                                fb_buf.size,
                                NULL); /* data */
      if (pipe->set_feedback_buffer)
         pipe->set_feedback_buffer(pipe, 0, &fb_buf);
   }


   /* draw a point */
   pipe->draw_arrays(pipe, GL_POINTS, 0, 1);

   /* get feedback */
   buf_map = (float *) pipe->winsys->buffer_map(pipe->winsys, fb_buf.buffer,
                                                PIPE_BUFFER_FLAG_READ);

   /* extract values and update rasterpos state */
   {
      const GLuint *outputMapping = st->vertex_result_to_slot;
      const float *pos, *color0, *color1, *fog, *tex0;
      float *buf = buf_map;

      assert(outputMapping[VERT_RESULT_HPOS] != ~0);
      pos = buf;
      buf += 4;

      if (outputMapping[VERT_RESULT_COL0] != ~0) {
         color0 = buf;
         buf += 4;
      }
      else {
         color0 = ctx->Current.Attrib[VERT_ATTRIB_COLOR0];
      }

      if (outputMapping[VERT_RESULT_COL1] != ~0) {
         color1 = buf;
         buf += 4;
      }
      else {
         color1 = ctx->Current.Attrib[VERT_ATTRIB_COLOR1];
      }

      if (outputMapping[VERT_RESULT_FOGC] != ~0) {
         fog = buf;
         buf += 4;
      }
      else {
         fog = ctx->Current.Attrib[VERT_ATTRIB_FOG];
      }

      if (outputMapping[VERT_RESULT_TEX0] != ~0) {
         tex0 = buf;
         buf += 4;
      }
      else {
         tex0 = ctx->Current.Attrib[VERT_ATTRIB_TEX0];
      }

      update_rasterpos(ctx, pos, color0, color1, fog, tex0);
   }

   /* free vertex feedback buffer */
   pipe->winsys->buffer_unmap(pipe->winsys, fb_buf.buffer);
   pipe->winsys->buffer_reference(pipe->winsys, &fb_buf.buffer, NULL);

   /* restore pipe state */
   if (pipe->set_feedback_state)
      pipe->set_feedback_state(pipe, &st->state.feedback);
}


void st_init_rasterpos_functions(struct dd_function_table *functions)
{
   functions->RasterPos = st_RasterPos;
}
