/**************************************************************************
 *
 * Copyright 2012-2021 VMware, Inc.
 * 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 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
 * THE COPYRIGHT HOLDERS, AUTHORS 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.
 *
 * The above copyright notice and this permission notice (including the
 * next paragraph) shall be included in all copies or substantial portions
 * of the Software.
 *
 **************************************************************************/

/*
 * InputAssembly.cpp --
 *    Functions that manipulate the input assembly stage.
 */


#include <stdio.h>
#if defined(_MSC_VER) && !defined(snprintf)
#define snprintf _snprintf
#endif

#include "InputAssembly.h"
#include "State.h"

#include "Debug.h"
#include "Format.h"


/*
 * ----------------------------------------------------------------------
 *
 * IaSetTopology --
 *
 *    The IaSetTopology function sets the primitive topology to
 *    enable drawing for the input assember.
 *
 * ----------------------------------------------------------------------
 */

void APIENTRY
IaSetTopology(D3D10DDI_HDEVICE hDevice,                        // IN
              D3D10_DDI_PRIMITIVE_TOPOLOGY PrimitiveTopology)  // IN
{
   LOG_ENTRYPOINT();

   Device *pDevice = CastDevice(hDevice);

   enum pipe_prim_type primitive;
   switch (PrimitiveTopology) {
   case D3D10_DDI_PRIMITIVE_TOPOLOGY_UNDEFINED:
      /* Apps might set topology to UNDEFINED when cleaning up on exit. */
      primitive = PIPE_PRIM_MAX;
      break;
   case D3D10_DDI_PRIMITIVE_TOPOLOGY_POINTLIST:
      primitive = PIPE_PRIM_POINTS;
      break;
   case D3D10_DDI_PRIMITIVE_TOPOLOGY_LINELIST:
      primitive = PIPE_PRIM_LINES;
      break;
   case D3D10_DDI_PRIMITIVE_TOPOLOGY_LINESTRIP:
      primitive = PIPE_PRIM_LINE_STRIP;
      break;
   case D3D10_DDI_PRIMITIVE_TOPOLOGY_TRIANGLELIST:
      primitive = PIPE_PRIM_TRIANGLES;
      break;
   case D3D10_DDI_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP:
      primitive = PIPE_PRIM_TRIANGLE_STRIP;
      break;
   case D3D10_DDI_PRIMITIVE_TOPOLOGY_LINELIST_ADJ:
      primitive = PIPE_PRIM_LINES_ADJACENCY;
      break;
   case D3D10_DDI_PRIMITIVE_TOPOLOGY_LINESTRIP_ADJ:
      primitive = PIPE_PRIM_LINE_STRIP_ADJACENCY;
      break;
   case D3D10_DDI_PRIMITIVE_TOPOLOGY_TRIANGLELIST_ADJ:
      primitive = PIPE_PRIM_TRIANGLES_ADJACENCY;
      break;
   case D3D10_DDI_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP_ADJ:
      primitive = PIPE_PRIM_TRIANGLE_STRIP_ADJACENCY;
      break;
   default:
      assert(0);
      primitive = PIPE_PRIM_MAX;
      break;
   }

   pDevice->primitive = primitive;
}


/*
 * ----------------------------------------------------------------------
 *
 * IaSetVertexBuffers --
 *
 *    The IaSetVertexBuffers function sets vertex buffers
 *    for an input assembler.
 *
 * ----------------------------------------------------------------------
 */

void APIENTRY
IaSetVertexBuffers(D3D10DDI_HDEVICE hDevice,                                     // IN
                   UINT StartBuffer,                                             // IN
                   UINT NumBuffers,                                              // IN
                   __in_ecount (NumBuffers) const D3D10DDI_HRESOURCE *phBuffers, // IN
                   __in_ecount (NumBuffers) const UINT *pStrides,                // IN
                   __in_ecount (NumBuffers) const UINT *pOffsets)                // IN
{
   static const float dummy[4] = {0.0f, 0.0f, 0.0f, 0.0f};

   LOG_ENTRYPOINT();

   Device *pDevice = CastDevice(hDevice);
   struct pipe_context *pipe = pDevice->pipe;
   unsigned i;

   for (i = 0; i < NumBuffers; i++) {
      struct pipe_vertex_buffer *vb = &pDevice->vertex_buffers[StartBuffer + i];
      struct pipe_resource *resource = CastPipeResource(phBuffers[i]);
      Resource *res = CastResource(phBuffers[i]);
      struct pipe_stream_output_target *so_target =
         res ? res->so_target : NULL;

      if (so_target && pDevice->draw_so_target != so_target) {
         if (pDevice->draw_so_target) {
            pipe_so_target_reference(&pDevice->draw_so_target, NULL);
         }
         pipe_so_target_reference(&pDevice->draw_so_target,
                                  so_target);
      }

      if (resource) {
         vb->stride = pStrides[i];
         vb->buffer_offset = pOffsets[i];
         if (vb->is_user_buffer) {
            vb->buffer.resource = NULL;
            vb->is_user_buffer = FALSE;
         }
         pipe_resource_reference(&vb->buffer.resource, resource);
      }
      else {
         vb->stride = 0;
         vb->buffer_offset = 0;
         if (!vb->is_user_buffer) {
            pipe_resource_reference(&vb->buffer.resource, NULL);
            vb->is_user_buffer = TRUE;
         }
         vb->buffer.user = dummy;
      }
   }

   for (i = 0; i < PIPE_MAX_ATTRIBS; ++i) {
      struct pipe_vertex_buffer *vb = &pDevice->vertex_buffers[i];

      /* XXX this is odd... */
      if (!vb->is_user_buffer && !vb->buffer.resource) {
         vb->stride = 0;
         vb->buffer_offset = 0;
         vb->is_user_buffer = TRUE;
         vb->buffer.user = dummy;
      }
   }

   /* Resubmit old and new vertex buffers.
    */
   pipe->set_vertex_buffers(pipe, 0, PIPE_MAX_ATTRIBS, 0, FALSE, pDevice->vertex_buffers);
}


/*
 * ----------------------------------------------------------------------
 *
 * IaSetIndexBuffer --
 *
 *    The IaSetIndexBuffer function sets an index buffer for
 *    an input assembler.
 *
 * ----------------------------------------------------------------------
 */

void APIENTRY
IaSetIndexBuffer(D3D10DDI_HDEVICE hDevice,   // IN
                 D3D10DDI_HRESOURCE hBuffer, // IN
                 DXGI_FORMAT Format,         // IN
                 UINT Offset)                // IN
{
   LOG_ENTRYPOINT();

   Device *pDevice = CastDevice(hDevice);
   struct pipe_resource *resource = CastPipeResource(hBuffer);

   if (resource) {
      pDevice->ib_offset = Offset;

      switch (Format) {
      case DXGI_FORMAT_R16_UINT:
         pDevice->index_size = 2;
         pDevice->restart_index = 0xffff;
         break;
      case DXGI_FORMAT_R32_UINT:
         pDevice->restart_index = 0xffffffff;
         pDevice->index_size = 4;
         break;
      default:
         assert(0);             /* should not happen */
         pDevice->index_size = 2;
         break;
      }
      pipe_resource_reference(&pDevice->index_buffer, resource);
   } else {
      pipe_resource_reference(&pDevice->index_buffer, NULL);
   }
}


/*
 * ----------------------------------------------------------------------
 *
 * CalcPrivateElementLayoutSize --
 *
 *    The CalcPrivateElementLayoutSize function determines the size
 *    of the user-mode display driver's private region of memory
 *    (that is, the size of internal driver structures, not the size
 *    of the resource video memory) for an element layout.
 *
 * ----------------------------------------------------------------------
 */

SIZE_T APIENTRY
CalcPrivateElementLayoutSize(
   D3D10DDI_HDEVICE hDevice,                                         // IN
   __in const D3D10DDIARG_CREATEELEMENTLAYOUT *pCreateElementLayout) // IN
{
   return sizeof(ElementLayout);
}


/*
 * ----------------------------------------------------------------------
 *
 * CreateElementLayout --
 *
 *    The CreateElementLayout function creates an element layout.
 *
 * ----------------------------------------------------------------------
 */

void APIENTRY
CreateElementLayout(
   D3D10DDI_HDEVICE hDevice,                                         // IN
   __in const D3D10DDIARG_CREATEELEMENTLAYOUT *pCreateElementLayout, // IN
   D3D10DDI_HELEMENTLAYOUT hElementLayout,                           // IN
   D3D10DDI_HRTELEMENTLAYOUT hRTElementLayout)                       // IN
{
   LOG_ENTRYPOINT();

   struct pipe_context *pipe = CastPipeContext(hDevice);
   ElementLayout *pElementLayout = CastElementLayout(hElementLayout);

   struct pipe_vertex_element elements[PIPE_MAX_ATTRIBS];
   memset(elements, 0, sizeof elements);

   unsigned num_elements = pCreateElementLayout->NumElements;
   unsigned max_elements = 0;
   for (unsigned i = 0; i < num_elements; i++) {
      const D3D10DDIARG_INPUT_ELEMENT_DESC* pVertexElement =
            &pCreateElementLayout->pVertexElements[i];
      struct pipe_vertex_element *ve =
            &elements[pVertexElement->InputRegister];

      ve->src_offset          = pVertexElement->AlignedByteOffset;
      ve->vertex_buffer_index = pVertexElement->InputSlot;
      ve->src_format          = FormatTranslate(pVertexElement->Format, FALSE);

      switch (pVertexElement->InputSlotClass) {
      case D3D10_DDI_INPUT_PER_VERTEX_DATA:
         ve->instance_divisor = 0;
         break;
      case D3D10_DDI_INPUT_PER_INSTANCE_DATA:
         if (!pVertexElement->InstanceDataStepRate) {
            LOG_UNSUPPORTED(!pVertexElement->InstanceDataStepRate);
            ve->instance_divisor = ~0;
         } else {
            ve->instance_divisor = pVertexElement->InstanceDataStepRate;
         }
         break;
      default:
         assert(0);
         break;
      }

      max_elements = MAX2(max_elements, pVertexElement->InputRegister + 1);
   }

   /* XXX: What do we do when there's a gap? */
   if (max_elements != num_elements) {
      DebugPrintf("%s: gap\n", __FUNCTION__);
   }

   pElementLayout->handle =
         pipe->create_vertex_elements_state(pipe, max_elements, elements);
}


/*
 * ----------------------------------------------------------------------
 *
 * DestroyElementLayout --
 *
 *    The DestroyElementLayout function destroys the specified
 *    element layout object. The element layout object can be
 *    destoyed only if it is not currently bound to a display device.
 *
 * ----------------------------------------------------------------------
 */

void APIENTRY
DestroyElementLayout(D3D10DDI_HDEVICE hDevice,                 // IN
                     D3D10DDI_HELEMENTLAYOUT hElementLayout)   // IN
{
   LOG_ENTRYPOINT();

   struct pipe_context *pipe = CastPipeContext(hDevice);
   ElementLayout *pElementLayout = CastElementLayout(hElementLayout);

   pipe->delete_vertex_elements_state(pipe, pElementLayout->handle);}


/*
 * ----------------------------------------------------------------------
 *
 * IaSetInputLayout --
 *
 *    The IaSetInputLayout function sets an input layout for
 *    the input assembler.
 *
 * ----------------------------------------------------------------------
 */

void APIENTRY
IaSetInputLayout(D3D10DDI_HDEVICE hDevice,               // IN
                 D3D10DDI_HELEMENTLAYOUT hInputLayout)   // IN
{
   LOG_ENTRYPOINT();

   struct pipe_context *pipe = CastPipeContext(hDevice);
   void *state = CastPipeInputLayout(hInputLayout);

   pipe->bind_vertex_elements_state(pipe, state);
}