/*
 * Handling pipelines
 * Pipelines are used to turn objects into polygons
 *
 * Copyright (c) 1998 Criterion Software Ltd.
 */

/****************************************************************************
 Includes
 */

#include <stdlib.h>
#include <string.h>

#include "batypes.h"
#include "batype.h"
#include "balibtyp.h"
#include "badebug.h"
#include "batkreg.h"
#include "badevice.h"
#include "bapiputl.h"

/* This file */
#include "bapipe.h"

static const char _rcsid[] __RWUNUSED__ = "@@(#)$Id: bapipcom.c,v 1.64 2001/03/09 10:22:37 johns Exp $";


/****************************************************************************
 Global prototypes
 */

/****************************************************************************
 Local Types
 */

/****************************************************************************
 Local (Static) Prototypes
 */

/****************************************************************************
 Local Defines
 */

#define RWPIPEGLOBAL(var) (RWPLUGINOFFSET(rwPipeGlobals, RwEngineInstance, pipeModule.globalsOffset)->var)

/****************************************************************************
 Globals (across program)
 */

/****************************************************************************
 Local (static) Globals
 */

static RwModuleInfo pipeModule;

/****************************************************************************
 Functions
 */

/****************************************************************************
 nullPushContextFunction

 On entry   : pipeline to push
 On exit    : TRUE on success
 */
static              RwBool
nullPushContextFunction(const RwRenderPipeline * pipeline __RWUNUSED__)
{
    RWFUNCTION(RWSTRING("nullPushContextFunction"));

    /* Make it fail - no-one has installed a pipeline yet */
    RWRETURN(FALSE);
}

/****************************************************************************
 nullPopContextFunction

 On entry   : None
 On exit    : TRUE on success
 */
static              RwBool
nullPopContextFunction(void)
{
    RWFUNCTION(RWSTRING("nullPopContextFunction"));

    /* Make it fail - noone has installed a pipeline */
    RWRETURN(FALSE);
}

/****************************************************************************
 _rwRenderPipeSetContextFns

 On entry   : Context switching functions
 On exit    : None
 */
void
_rwRenderPipeSetContextFns(RwRenderPipelinePushContextFunction
                          fpPushContext,
                          RwRenderPipelinePopContextFunction
                          fpPopContext)
{
    RWFUNCTION(RWSTRING("_rwRenderPipeSetContextFns"));
    RWASSERT(fpPushContext);
    RWASSERT(fpPopContext);

    RWPIPEGLOBAL(fpPushContext) = fpPushContext;
    RWPIPEGLOBAL(fpPopContext) = fpPopContext;

    RWRETURNVOID();
}

/****************************************************************************
 _rwRenderPipelineOpen

 On entry   : None
 On exit    : TRUE on success
 */
void               *
_rwRenderPipelineOpen(void *instance, RwInt32 offset,
                      RwInt32 size __RWUNUSED__)
{
    RWFUNCTION(RWSTRING("_rwRenderPipelineOpen"));

    /* Save the global data offset (same for all instances) */
    pipeModule.globalsOffset = offset;

    /* Get on with the setup */
    RWPIPEGLOBAL(pipelineFreeList) =
        RwFreeListCreate(sizeof(RwRenderPipeline), 5, 0);
    if (!RWPIPEGLOBAL(pipelineFreeList))
    {
        /* Failure */
        RWRETURN(NULL);
    }

    /* Put in the dummy context hooks */
    _rwRenderPipeSetContextFns(nullPushContextFunction,
                              nullPopContextFunction);

    /* One more module instance */
    pipeModule.numInstances++;

    /* Stack depth to start */
    RWPIPEGLOBAL(pipeDepth) = 0;

    /* Success */
    RWRETURN(instance);
}

/****************************************************************************
 _rwRenderPipeLineClose

 On entry   : None
 On exit    : TRUE on success
 */
void               *
_rwRenderPipelineClose(void *instance, RwInt32 offset __RWUNUSED__,
                       RwInt32 size __RWUNUSED__)
{
    RWFUNCTION(RWSTRING("_rwRenderPipelineClose"));

    if (RWPIPEGLOBAL(pipelineFreeList))
    {
        RwFreeListDestroy(RWPIPEGLOBAL(pipelineFreeList));
        RWPIPEGLOBAL(pipelineFreeList) = NULL;
    }

    /* One less module instance */
    pipeModule.numInstances--;

    /* Success */
    RWRETURN(instance);
}

/****************************************************************************
 _rwPipeNULLInstance

 On entry   : Object
 On exit    : NULL - suppresses rest of pipeline execution
 */
static RwResEntry  *
PipeNULLInstance(void *object __RWUNUSED__)
{
    RWFUNCTION(RWSTRING("PipeNULLInstance"));
    RWRETURN(NULL);
}

/****************************************************************************
 _rwPipeNULLLight

 On entry   : Instanced data
            : apply light callback
 On exit    : none
 */

static void
PipeNULLLight(RwResEntry * repEntry __RWUNUSED__,
                 RwApplyLightFunction applyLight __RWUNUSED__)
{
    RWFUNCTION(RWSTRING("PipeNULLLight"));
    RWRETURNVOID();
}

/***************************************************************************
 _rwPipeNULLApplyLight

 On entry   : Instanced data
            : Number of vertices
            : Light
            : Optional inverse object matrix (to transform light to object space)
            : Surface properties of the light
 On exit    :
 */

static void
PipeNULLApplyLight(RwResEntry * repEntry __RWUNUSED__,
                      const void *voidLight __RWUNUSED__,
                      const RwMatrix * inverseMat __RWUNUSED__,
                      RwReal invScale __RWUNUSED__,
                      const RwSurfaceProperties *
                      surfaceProps __RWUNUSED__)
{
    RWFUNCTION(RWSTRING("PipeNULLApplyLight"));
    RWRETURNVOID();
}

/****************************************************************************
 _rwPipeNULLPipeStage

 On entry   : Instanced data
 On exit    :
 */

static void
PipeNULLPipeStage(RwResEntry * repEntry __RWUNUSED__)
{
    RWFUNCTION(RWSTRING("PipeNULLPipeStage"));
    RWRETURNVOID();
}

/**
 * \ingroup rwrenderpipeline
 * \ref RwRenderPipelineCreate is used to create a new rendering
 * pipeline containing the standard RenderWare pipeline stages.
 *
 * Initially, the transform, prelight and postlight pipeline stages
 * point to RenderWare's default pipeline functions, while the instance,
 * light, apply light and render stages point to dummy functions and should
 * be set to user-defined functions before the pipeline is executed.
 *
 * RenderWare currently supports three separate rendering pipelines that
 * individually render 3D immediate mode primitives, atomics and world
 * sectors.
 *
 * \return Returns pointer to the new rendering pipeline if successful or
 * NULL if there is an error.
 *
 * \see RwRenderPipelineDestroy
 * \see RwRenderPipelineExecute
 * \see RwRenderPipelinePushContext
 * \see RwRenderPipelinePopContext
 * \see RwIm3DGetRenderPipeline
 * \see RpAtomicGetRenderPipeline
 * \see RpWorldGetRenderPipeline
 *
 */
RwRenderPipeline   *
RwRenderPipelineCreate(void)
{
    RWAPIFUNCTION(RWSTRING("RwRenderPipelineCreate"));
    RWASSERT(pipeModule.numInstances);

    {
        RwRenderPipeline   *pipeline;

        RWASSERT(RWPIPEGLOBAL(pipelineFreeList));
        pipeline =
            (RwRenderPipeline *)RwFreeListAlloc(RWPIPEGLOBAL(pipelineFreeList));

        RWASSERT(RWFREELISTALIGNED(pipeline, RWPIPEGLOBAL(pipelineFreeList)));
        if (pipeline)
        {
            /* Initialise with NULL ops */
            /* Driver gets to init the transform (which is generally a
             * common element)
             */
            pipeline->fpInstance = PipeNULLInstance;
            pipeline->fpTransform = PipeNULLPipeStage;
            pipeline->fpPreLight = PipeNULLPipeStage;
            pipeline->fpApplyLight = PipeNULLApplyLight;
            pipeline->fpLight = PipeNULLLight;
            pipeline->fpPostLight = PipeNULLPipeStage;
            pipeline->fpRender = PipeNULLPipeStage;

            /* Allow the driver to overload with driver specific elements */
            if (_rwDeviceSystemRequest(&RWSRCGLOBAL(dOpenDevice),
                                       rwDEVICESYSTEMINITPIPELINE,
                                       0, pipeline, 0))
            {
                /* AOK */
                RWRETURN(pipeline);
            }

            /* Device failed to init pipe - what are we to do??? */
            RWRETURN(NULL);
        }

        /* Free list allocation failure */
        RWRETURN(NULL);
    }
}

/**
 * \ingroup rwrenderpipeline
 * \ref RwRenderPipelineDestroy is used to destroy the specified
 * rendering pipeline.
 *
 * \param pipeline  Pointer to the rendering pipeline.
 *
 * \return Returns TRUE if successful or FALSE if there is an error.
 *
 * \see RwRenderPipelineCreate
 * \see RwRenderPipelineExecute
 * \see RwRenderPipelinePushContext
 * \see RwRenderPipelinePopContext
 * \see RwIm3DGetRenderPipeline
 * \see RpAtomicGetRenderPipeline
 * \see RpWorldGetRenderPipeline
 *
 */
RwBool
RwRenderPipelineDestroy(RwRenderPipeline * pipeline)
{
    RWAPIFUNCTION(RWSTRING("RwRenderPipelineDestroy"));
    RWASSERT(pipeModule.numInstances);
    RWASSERT(pipeline);

    RwFreeListFree(RWPIPEGLOBAL(pipelineFreeList), pipeline);
    RWRETURN(TRUE);
}

/**
 * \ingroup rwrenderpipeline
 * \ref RwRenderPipelinePushContext is used to push the specified
 * rendering pipeline onto the pipeline context stack making
 * it the pipeline to use when \ref RwRenderPipelineExecute is called.
 * Because the pipeline stack has a limited size, the pipeline should be
 * popped from the pipeline stack as soon as it has finished rendering
 * objects, otherwise an application will run out of stack space very
 * quickly.
 *
 * \param pipeline  Pointer to the rendering pipeline.
 *
 * \return Returns pointer to the pipeline if successful or NULL if there
 * is an error or if the pipeline stack is full.
 *
 * \see RwRenderPipelinePopContext
 * \see RwRenderPipelineExecute
 * \see RwRenderPipelineCreate
 * \see RwRenderPipelineDestroy
 * \see RwIm3DGetRenderPipeline
 * \see RpAtomicGetRenderPipeline
 * \see RpWorldGetRenderPipeline
 *
 */
const RwRenderPipeline *
RwRenderPipelinePushContext(const RwRenderPipeline * pipeline)
{
    RWAPIFUNCTION(RWSTRING("RwRenderPipelinePushContext"));
    RWASSERT(pipeline);
    RWASSERT(RWPIPEGLOBAL(pipeDepth) < rwMAXPIPECONTEXT);

    RWPIPEGLOBAL(renderPipes[RWPIPEGLOBAL(pipeDepth)]) = pipeline;
    RWPIPEGLOBAL(pipeDepth)++;

    if (RWPIPEGLOBAL(fpPushContext) (pipeline))
    {
        RWRETURN(pipeline);
    }

    /* Failed to push */
    RWRETURN(NULL);
}

/**
 * \ingroup rwrenderpipeline
 * \ref RwRenderPipelineExecute is used to execute the current rendering
 * pipeline using the specified object. The current rendering pipeline
 * corresponds with the top of the pipeline stack.
 *
 * \param object  Pointer to the object.
 *
 * \return Returns pointer to the object if successful or NULL if there
 * is an error.
 *
 * \see RwRenderPipelinePushContext
 * \see RwRenderPipelinePopContext
 * \see RwRenderPipelineCreate
 * \see RwRenderPipelineDestroy
 * \see RwIm3DGetRenderPipeline
 * \see RpAtomicGetRenderPipeline
 * \see RpWorldGetRenderPipeline
 *
 */
void               *
RwRenderPipelineExecute(void *object)
{
    const RwRenderPipeline *pipeline =
        RWPIPEGLOBAL(renderPipes[RWPIPEGLOBAL(pipeDepth) - 1]);
    RwResEntry *repEntry;

    RWAPIFUNCTION(RWSTRING("RwRenderPipelineExecute"));
    RWASSERT(pipeModule.numInstances);
    RWASSERT(RWPIPEGLOBAL(pipeDepth) > 0);
    RWASSERT(RWPIPEGLOBAL(pipeDepth) < rwMAXPIPECONTEXT);
    RWASSERT(object);

    /* Now do it */
    repEntry = pipeline->fpInstance(object);
    if (repEntry)
    {
        /* Instancing succeeded, zoom down the rest of the pipeline */
        pipeline->fpTransform(repEntry);
        pipeline->fpPreLight(repEntry);
        pipeline->fpLight(repEntry, pipeline->fpApplyLight);
        pipeline->fpPostLight(repEntry);
        pipeline->fpRender(repEntry);
    }

    RWRETURN(object);
}

/**
 * \ingroup rwrenderpipeline
 * \ref RwRenderPipelinePopContext is used to pop the rendering pipeline
 * stack. The pipeline that was previously below the top of the pipeline
 * stack, if any, now becomes the current rendering pipeline.
 *
 * \return Returns pointer to the popped pipeline if successful or NULL
 * if there is an error or if the pipeline stack is empty.
 *
 * \see RwRenderPipelinePushContext
 * \see RwRenderPipelineExecute
 * \see RwRenderPipelineCreate
 * \see RwRenderPipelineDestroy
 * \see RwIm3DGetRenderPipeline
 * \see RpAtomicGetRenderPipeline
 * \see RpWorldGetRenderPipeline
 *
 */
const RwRenderPipeline *
RwRenderPipelinePopContext(void)
{
    RWAPIFUNCTION(RWSTRING("RwRenderPipelinePopContext"));
    RWASSERT(RWPIPEGLOBAL(pipeDepth) > 0);

    if (RWPIPEGLOBAL(fpPopContext) ())
    {
        RWPIPEGLOBAL(pipeDepth)--;
        RWRETURN(RWPIPEGLOBAL(renderPipes[RWPIPEGLOBAL(pipeDepth)]));
    }

    /* Failed to pop */
    RWRETURN(NULL);
}

/****************************************************************************
 _rwRenderPipelineGetCurrent

 On entry   : None
 On exit    : Current pipeline on success, or NULL if no pipeline.
 */
const RwRenderPipeline *
_rwRenderPipelineGetCurrent(void)
{
    RWFUNCTION(RWSTRING("_rwRenderPipelineGetCurrent"));
    RWASSERT(RWPIPEGLOBAL(pipeDepth) > 0);

    RWRETURN(RWPIPEGLOBAL(renderPipes[RWPIPEGLOBAL(pipeDepth) - 1]));
}

/****************************************************************************
 _rwPipeAttach

 On entry   : None
 On exit    : TRUE on success
 */
RwBool
_rwPipeAttach(void)
{
    RWFUNCTION(RWSTRING("_rwPipeAttach"));

    RWRETURN(rwGetCorePipeInterface()->pipeAttach());
}

/****************************************************************************
 _rwPipeInitForCamera

 On entry   : Camera
 On exit    : None
 */
void
_rwPipeInitForCamera(const RwCamera * camera)
{
    RWFUNCTION(RWSTRING("_rwPipeInitForCamera"));
    RWASSERT(camera);

    rwGetCorePipeInterface()->pipeInitForCamera(camera);

    RWRETURNVOID();
}

/****************************************************************************
 _rwPipeSetupIm3DPipe

 On entry   : Pipeline
 On exit    : TRUE on success
 */
RwBool
_rwPipeSetupIm3DPipe(RwRenderPipeline * pipe)
{
    RWFUNCTION(RWSTRING("_rwPipeSetupIm3DPipe"));
    RWASSERT(pipe);

    rwGetCorePipeInterface()->pipeSetupIm3DPipe(pipe);

    RWRETURN(TRUE);
}

