/*
 * Functionality for 2D rendering
 *
 * Copyright (c) 1998 Criterion Software Ltd.
 */

/****************************************************************************
 *                                                                          *
 *  Module  :   ps2stroke.c                                                 *
 *                                                                          *
 *  Purpose :   graphics state                                              *
 *                                                                          *
 ****************************************************************************/

/****************************************************************************
 includes
 */

#include <math.h>

#include <rwcore.h>
#include <rpworld.h>

#include <rpdbgerr.h>

#include "rt2d.h"
#include "stroke.h"
#include "brush.h"
#include "gstate.h"

#include "ps2pipes.h"
#include "ps2stroke.h"

#include "vustroke.h"

/****************************************************************************
 local defines
 */
#define MESSAGE(_string)                                            \
    RwDebugSendMessage(rwDEBUGMESSAGE, "Rt2dPS2PathStroke", _string)

#define     RT2DSTROKEPS2QWORD    9


static Rt2dPS2PipeState strokePipeState = {
    (RwUInt64) 0,               /* subdiv */
    (RxPipeline *)NULL,                       /* Pipeline */
    {NULL2D, NULL2D, NULL2D, NULL2D, /* vucode */
     NULL2D, NULL2D, NULL2D, NULL2D,
     NULL2D, NULL2D, NULL2D, NULL2D,
     NULL2D, NULL2D, NULL2D, NULL2D}
};

static              RwBool
PS2StrokeNode2d(RxPS2Mesh * ps2mesh __RWUNUSED__,
                RxPS2DMASessionRecord * dmaSessionRec __RWUNUSED__,
                RwReal * pPos, RwReal * pNrm)
{
    RwInt32             i;
    RwUInt64            tmp, tmp1;
    u_long128           ltmp = 0;
    rt2dPathNode       *pnode;
    RwReal              colScale;

    RWFUNCTION(RWSTRING("PS2StrokeNode2d"));

    pnode = Rt2dGlobals.pnode0;

    /* Instance the DMA  data */

    for (i = 0; i < (Rt2dGlobals.numVerts - 2); i++)
    {
        /* instance the Pos and Normal */
        *pPos++ = pnode->pos.x;
        *pPos++ = pnode->pos.y;
        *pPos++ = pnode->dist;

        *pNrm++ = pnode->normal.x;
        *pNrm++ = pnode->normal.y;

        pnode++;
    }

    /*************************************************
        * Call a RW3-PS2-specific stealth function to
        * create enough space to store an additional 11
        * quadwords of DMA data.  In fact the RW code
        * will most likely already have enough spare
        * space, since it allocates memory in much bigger
        * chunks.
        * SWE_PKT_LOCAL: RW is responsible for the memory
        *    used for this new packet and will free it
        *    up when the packet has been sent.
        * SWE_PKT_CIRCALLOC: The memory used is taken
        *    from a circularly allocated buffer, which is
        *    much faster than calling regular memory
        *    functions.
        * SWE_PKT_VU1: The packet is destined for the
        *    Vector Unit 1.  (If the SWE_LPS_NOFIXUP
        *    symbol was not used here, then the DMA
        *    manager would try to combine further DMA
        *    packets if they were also destined for the
        *    VU1.)
        ************************************************/

    /* Reserve 9 quadwords for the following data.
     * *
     * * Brush width, inset, oobaseu, layerDepth
     *
     * * Brush top RGBA
     * * Brush top delta RGBA
     * * Brush bot RGBA
     * * Brush bot delta RGBA
     * *
     * * Brush top UV
     * * Brush top delta UV
     * * Brush bot UV
     * * Brush bot delta UV
     * *
     */
    sweFinaliseOpenLocalPkt(SWE_PKT_DMA_MODE_CHAIN_TTE |
                            SWE_PKT_LOCAL |
                            SWE_PKT_VU1 |
                            SWE_PKT_CIRCALLOC,
                            -(RT2DSTROKEPS2QWORD + 2));

    /*************************************************
        * If the above call worked then we get a quad-
        * word pointer called sweLocalPacket which points
        * to the memory where we can fill in the DMA
        * data.  The macros below access this pointer and
        * advance this pointer.
        ************************************************/

    if (!sweLocalPacket)
    {
        printf("%s(%d): DMA memory failure\n", __FILE__, __LINE__);
    }
    else
    {

        /*************************************************
            * We build a DMA packet to transfer the necessary
            * data to the VIF.  This is a complete, ready-to
            * go packet, containing commands for the VIF. We
            * send data that must end up in the VU memory,
            * as well as commands that get passed to the GS
            * and program it's registers.
            * We end with an interrupt.
            ************************************************/

        /*************************************************
            * First off we set up a continuous DMA packet
            * to transfer 9 quadwords of data.
            ************************************************/

        tmp = (RT2DSTROKEPS2QWORD) | /* qwords */
            (1l << 28);        /* ID(cnt) */

        tmp1 =
        /*************************************************
            * We'll be transferring data with no scattering
            ************************************************/
            (VIFCMD_CYCLE | (4l << 8) | ((4))) |
        /*************************************************
            * Write 9 quadwords into VU1 memory
            ************************************************/
            /* UNPACK           qwords      where */
            ((VIFCMD_UNPACK
              | (RT2DSTROKEPS2QWORD << 16) |
              ((long) (vuStrokeSymbStaticData))) << 32);

        MAKE128(ltmp, tmp1, tmp);
        SWEADDCONTFAST(ltmp);

        /* width, inset, layerDepth, oobaseu */
        {
            Rt2dUnion128    pack;

            pack.float32[0] = Rt2dGlobals.brush->halfwidth;
            pack.float32[1] = Rt2dGlobals.pathInset;
            pack.float32[2] = Rt2dGlobals.layerDepth;
            pack.float32[3] = Rt2dGlobals.oobaseu;

            ltmp = pack.int128;
            SWEADDCONTFAST(ltmp);
        }

        /* Colour scale, depending if a texture is present. */
        if (Rt2dGlobals.brush->texture != NULL)
        {
            colScale = RT2DPS2COLSCALE;
        }
        else
        {
            colScale = (RwReal) 1.0;
        }

        /* Brush colour. */
        {
            Rt2dUnion128    pack;

            pack.float32[0] = Rt2dGlobals.brush->top.col.red * colScale;
            pack.float32[1] = Rt2dGlobals.brush->top.col.green * colScale;
            pack.float32[2] = Rt2dGlobals.brush->top.col.blue * colScale;
            pack.float32[3] = Rt2dGlobals.brush->top.col.alpha * (RwReal) RT2DPS2COLSCALE;

            ltmp =  pack.int128;
            SWEADDCONTFAST(ltmp);
        }

        {
            Rt2dUnion128    pack;

            pack.float32[0] = Rt2dGlobals.brush->dtop.col.red * colScale;
            pack.float32[1] = Rt2dGlobals.brush->dtop.col.green * colScale;
            pack.float32[2] = Rt2dGlobals.brush->dtop.col.blue * colScale;
            pack.float32[3] = Rt2dGlobals.brush->dtop.col.alpha * (RwReal) RT2DPS2COLSCALE;

            ltmp =  pack.int128;
            SWEADDCONTFAST(ltmp);
        }

        {
            Rt2dUnion128    pack;

            pack.float32[0] = Rt2dGlobals.brush->bottom.col.red * colScale;
            pack.float32[1] = Rt2dGlobals.brush->bottom.col.green * colScale;
            pack.float32[2] = Rt2dGlobals.brush->bottom.col.blue * colScale;
            pack.float32[3] = Rt2dGlobals.brush->bottom.col.alpha * (RwReal) RT2DPS2COLSCALE;

            ltmp =  pack.int128;
            SWEADDCONTFAST(ltmp);
        }

        {
            Rt2dUnion128    pack;

            pack.float32[0] = Rt2dGlobals.brush->dbottom.col.red * colScale;
            pack.float32[1] = Rt2dGlobals.brush->dbottom.col.green * colScale;
            pack.float32[2] = Rt2dGlobals.brush->dbottom.col.blue * colScale;
            pack.float32[3] = Rt2dGlobals.brush->dbottom.col.alpha * (RwReal) RT2DPS2COLSCALE;

            ltmp =  pack.int128;
            SWEADDCONTFAST(ltmp);
        }

        /* Brush uv. */
        {
            Rt2dUnion128    pack;

            pack.float32[0] = Rt2dGlobals.brush->top.uv.x;
            pack.float32[1] = Rt2dGlobals.brush->top.uv.y;
            pack.float32[2] = (RwReal) 0;
            pack.float32[3] = (RwReal) 0;

            ltmp =  pack.int128;
            SWEADDCONTFAST(ltmp);
        }

        {
            Rt2dUnion128    pack;

            pack.float32[0] = Rt2dGlobals.brush->dtop.uv.x;
            pack.float32[1] = Rt2dGlobals.brush->dtop.uv.y;
            pack.float32[2] = (RwReal) 0;
            pack.float32[3] = (RwReal) 0;

            ltmp =  pack.int128;
            SWEADDCONTFAST(ltmp);
        }

        {
            Rt2dUnion128    pack;

            pack.float32[0] = Rt2dGlobals.brush->bottom.uv.x;
            pack.float32[1] = Rt2dGlobals.brush->bottom.uv.y;
            pack.float32[2] = (RwReal) 0;
            pack.float32[3] = (RwReal) 0;

            ltmp =  pack.int128;
            SWEADDCONTFAST(ltmp);
        }

        {
            Rt2dUnion128    pack;

            pack.float32[0] = Rt2dGlobals.brush->dbottom.uv.x;
            pack.float32[1] = Rt2dGlobals.brush->dbottom.uv.y;
            pack.float32[2] = (RwReal) 0;
            pack.float32[3] = (RwReal) 0;

            ltmp =  pack.int128;
            SWEADDCONTFAST(ltmp);
        }

        /*    End with intr */
        tmp = (0xfl << 28);
        MAKE128(ltmp, 0l, tmp);
        SWEADDCONTFAST(ltmp);

        sweFinaliseOpenLocalPkt(SWE_LPS_CONT, 0);
    }

    RWRETURN(TRUE);
}

/* Wrapper CB func to do instancing inside PS2Manager */
static              RwBool
rxNode2dPS2StrokePS2ManagerInstanceCallBack(void **clusterData,
                                            RwUInt32 numClusters
                                            __RWUNUSED__)
{
    RWFUNCTION(RWSTRING("rxNode2dPS2StrokePS2ManagerInstanceCallBack"));

    RWRETURN(PS2StrokeNode2d((RxPS2Mesh *) (clusterData[0]), (RxPS2DMASessionRecord *) (clusterData[1]), (RwReal *) (clusterData[2]), /* Stroke Pos */
                             (RwReal *) (clusterData[3]) /* Stroke Nrm */
             ));
}

RxPipeline         *
_rt2dPS2StrokePipe(void)
{
    RxPipeline         *pipe;

    RWFUNCTION(RWSTRING("_rt2dPS2StrokePipe"));

    pipe = (RxPipeline *)NULL;

    /* Stroke Pipe. */
    pipe = RxPipelineCreate();

    if (pipe)
    {
        RwInt32             i;
        RxLockedPipe       *lpipe = (RxPipeline *)NULL;

        lpipe = RxPipelineLock(pipe);

        if (NULL != lpipe)
        {

            RxNodeDefinition   *ps2man;
            RxPipelineNode     *plnode, *result;

            ps2man = RxNodeDefinitionGetPS2Manager(rxOBJTYPE_IM3D);

            lpipe = RxLockedPipeAddFragment(lpipe,
                                            (RwUInt32 *)NULL,
                                            ps2man,
                                            (RxNodeDefinition *)NULL);
            RWASSERT(lpipe != NULL);

            plnode = RxPipelineFindNodeByName(lpipe,
                                              ps2man->name,
                                              (RxPipelineNode *)NULL,
                                              (RwInt32 *)NULL);
            RWASSERT(plnode != NULL);

            /* Set up the first to carry the vert's x, y, dist */
            Rt2dGlobals.RxClPS2StrokePos = RxClPS2user1;
            Rt2dGlobals.RxClPS2StrokePos.defaultAttributes &= ~CL_V4_32;
            Rt2dGlobals.RxClPS2StrokePos.defaultAttributes |= CL_V3_32;

            result =
                RxPipelineNodePS2ManagerGenerateCluster(plnode,
                                                        &Rt2dGlobals.
                                                        RxClPS2StrokePos,
                                                        CL_USER1);

            /* Set up the second to carry the vert's nrm */
            Rt2dGlobals.RxClPS2StrokeNrm = RxClPS2user2;
            Rt2dGlobals.RxClPS2StrokeNrm.defaultAttributes &= ~CL_V4_32;
            Rt2dGlobals.RxClPS2StrokeNrm.defaultAttributes |= CL_V2_32;

            result = RxPipelineNodePS2ManagerGenerateCluster
                (plnode, &Rt2dGlobals.RxClPS2StrokeNrm, CL_USER2);

            RxPipelineNodePS2ManagerSetVUBufferSizes(plnode, /* Node */
                                                     vuStrokeSymbStrideOfInputCluster, /* Stride of input cluster */
                                                     vuStrokeSymbTSVertexCount, /* Tristrip vertex count */
                                                     vuStrokeSymbTLTriCount); /* Trilist vertex count */

            lpipe = RxLockedPipeUnlock(lpipe);
        }

        if (NULL != lpipe)
        {
            RxNodeDefinition   *ps2man;
            RxPipelineNode     *plnode, *result;

            ps2man = RxNodeDefinitionGetPS2Manager(rxOBJTYPE_IM3D);

            plnode = RxPipelineFindNodeByName(lpipe,
                                              ps2man->name,
                                              (RxPipelineNode *)NULL,
                                              (RwInt32 *)NULL);

            RWASSERT(plnode != NULL);

            /* We now insert our own instance callback */
            result = RxPipelineNodePS2ManagerSetInstanceCallBack(plnode,
                                                                 rxNode2dPS2StrokePS2ManagerInstanceCallBack);

            /*  Set most of the transforms to be null */
            for (i = 0; i < 16; i++)
            {
                strokePipeState.vucode[i] = &stroke2d;
            }

            /* Set the transforms/renderers */
            /* pipeState.vucode[TRANSNFOG | TRANSNCL | TRANSSTRIP | TRANSPER] = &font2d;  */

            /* We now insert our own VU code */
            result =
                RxPipelineNodePS2ManagerSetVU1CodeArray(plnode,
                                                        strokePipeState.vucode);

            Rt2dGlobals.default_stroke_pipe = pipe;
            Rt2dGlobals.use_stroke_pipe = pipe;
        }
    }

    RWRETURN(pipe);
}

Rt2dPath           *
_rt2dPS2PathStroke(Rt2dPath * path, Rt2dBrush * brush)
{
    Rt2dPath           *flat;
    rt2dPathNode       *pnode;
    RwInt32             vcount, vcount_t;
    RWIM3DVERTEX       *vdst;
    RwReal              oobaseu, halfwidth, layerDepth;

    RWFUNCTION(RWSTRING("_rt2dPS2PathStroke"));

    /* NULL path is valid */
    if (path)
    {
        RwMatrix           *ctm = _rt2dCTMGet();
        rwIm3DPool         *pool;

        layerDepth = Rt2dGlobals.layerDepth;
        halfwidth = brush->halfwidth;

        /* we'll be using the flattened path */
        flat = path;
        if (!path->flat)
        {
            flat = _rt2dScratchPath();
            _rt2dSubPathFlatten(flat, path);
        }

        pnode = (rt2dPathNode *) rwSListGetArray(flat->segments);
        vcount_t = rwSListGetNumEntries(flat->segments);

        /*
         * Ensure we always send at least 3 verts. We just duplicate
         * the last vert to round things up.
         */
        if (0 && ((vcount_t % (RT2DPS2MAXVERT - 1)) == 2))
        {
            pnode = (rt2dPathNode *) rwSListGetNewEntry(flat->segments);

            *pnode = *(pnode - 1);

            pnode = (rt2dPathNode *) rwSListGetArray(flat->segments);

            vcount_t++;
        }

        if (brush->texture)
        {
            const RwTextureFilterMode filterMode =
                RwTextureGetFilterMode(brush->texture);
            const RwTextureAddressMode addressingMode =
                RwTextureGetAddressing(brush->texture);

            RwRenderStateSet(rwRENDERSTATETEXTURERASTER,
                             (void *)
                             RwTextureGetRaster(brush->texture));
            RwRenderStateSet(rwRENDERSTATETEXTUREFILTER,
                             (void *) filterMode);
            RwRenderStateSet(rwRENDERSTATETEXTUREADDRESS,
                             (void *) addressingMode);
        }
        else
        {
            RwRenderStateSet(rwRENDERSTATETEXTURERASTER, NULL);
        }

        /* oobaseu = 1.0f / pnode[vcount - 1].dist; */
        oobaseu = pnode[vcount_t - 1].dist;
        vdst = brush->vertex;

        Rt2dGlobals.pathInset = path->inset;
        Rt2dGlobals.brush = brush;
        Rt2dGlobals.oobaseu = oobaseu;

        pool = _rwIm3DGetPool();

        vcount = vcount_t;

        while (vcount_t > 0)
        {
            Rt2dGlobals.pnode0 = pnode;

            if (vcount_t > (RT2DPS2MAXVERT - 2))
            {
                /*
                 * Fool the upload code to think there at least 3 verts because
                 * we could be rendering just one char.
                 * The last two verts are ignored.
                 *
                 * This will be fixed once custom data is fully supported. For now
                 * we just use a tristrip as a wrapper.
                 */

                Rt2dGlobals.numVerts = (RT2DPS2MAXVERT + 2);

                vcount_t -= (RT2DPS2MAXVERT - 1);
                pnode += (RT2DPS2MAXVERT - 1);
            }
            else
            {
                Rt2dGlobals.numVerts = vcount_t + 2;

                vcount_t = 0;
                pnode = (rt2dPathNode *)NULL;
            }

            pool->numElements = (RwUInt16) Rt2dGlobals.numVerts;
            pool->elements = brush->vertex;
            pool->stride = 20; /* 5 items x 4 bytes per item */

            pool->stash.ltm = ctm;
            /* pool->stash.flags = rwIM3D_VERTEXUV | Rt2dGlobals.TransformFlags; */
            pool->stash.flags = Rt2dGlobals.TransformFlags;

            pool->stash.pipeline = (RxPipeline *) NULL;
            pool->stash.primType = (RwPrimitiveType) rwPRIMTYPETRISTRIP;
            pool->stash.indices = (RxVertexIndex *) NULL;
            pool->stash.numIndices = (RwUInt32) Rt2dGlobals.numVerts;

            if (RxPipelineExecute
                (Rt2dGlobals.use_stroke_pipe, (void *) &pool->stash,
                 FALSE) == NULL)
            {
                RWRETURN((Rt2dPath *)NULL);
            }

        }

        _rt2dPS2PathStroke(path->next, brush);
    }

    RWRETURN(path);
}
