SOP/SOP_PrimVOP.C

/*
 * Copyright (c) 2013
 *      Side Effects Software Inc.  All rights reserved.
 *
 * Redistribution and use of Houdini Development Kit samples in source and
 * binary forms, with or without modification, are permitted provided that the
 * following conditions are met:
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. The name of Side Effects Software may not be used to endorse or
 *    promote products derived from this software without specific prior
 *    written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE `AS IS' AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN
 * NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *----------------------------------------------------------------------------
 * PrimVOP SOP
 */

#include <UT/UT_DSOVersion.h>
#include <GEO/GEO_AttributeHandle.h>
#include <GU/GU_Detail.h>
#include <PRM/PRM_Include.h>
#include <OP/OP_Channels.h>
#include <OP/OP_Operator.h>
#include <OP/OP_Director.h>
#include <OP/OP_OperatorTable.h>
#include <OP/OP_Caller.h>
#include <UT/UT_Interrupt.h>
#include <SHOP/SHOP_Node.h>

#include "SOP_PrimVOP.h"

#include <VEX/VEX_Error.h>
#include <CVEX/CVEX_Context.h>
#include <CVEX/CVEX_Value.h>
#include <PRM/PRM_DialogScript.h>
#include <PRM/PRM_Include.h>
#include <PRM/PRM_SpareData.h>
#include <VOP/VOP_CodeCompilerArgs.h>
#include <VOP/VOP_LanguageContextTypeList.h>

using namespace HDK_Sample;
void
newSopOperator(OP_OperatorTable *table)
{
     table->addOperator(new OP_Operator("hdk_primvop",
                                        "PrimVOP",
                                         SOP_PrimVOP::myConstructor,
                                         SOP_PrimVOP::myTemplateList,
                                         1,
                                         1,
                                        VOP_CodeGenerator::theLocalVariables));
}

static PRM_Default      scriptDefault(PRM_DialogSopVex, "null");

static UT_String         theVexError;
static UT_String         theVexWarning;

static void
vexErrorHandler(const char *msg)
{
    if (!theVexError.isstring())
        theVexError = "VEX Error:";
    theVexError += "\n";
    theVexError += msg;
    theVexError.harden();
}

static void
vexWarningHandler(const char *msg)
{
    if (!theVexWarning.isstring())
        theVexWarning = "VEX_Warning:";
    theVexWarning += "\n";
    theVexWarning += msg;
    theVexWarning.harden();
}


static PRM_Name         names[] = {
    PRM_Name("script",          "Script"),
    PRM_Name("clear",           "Re-load VEX Functions"),
    PRM_Name("autobind",        "Autobind by Name"),
    PRM_Name("bindings",        "Number of Bindings"),
    PRM_Name("shoppath",        "Shop Path"),
    PRM_Name("vexsrc",          "Vex Source"),
};

static PRM_Name         vexsrcNames[] =
{
    PRM_Name("myself",  "Myself"),
    PRM_Name("shop",    "Shop"),
    PRM_Name("script",  "Script"),
    PRM_Name(0)
};
static PRM_ChoiceList vexsrcMenu(PRM_CHOICELIST_SINGLE, vexsrcNames);

PRM_Template
SOP_PrimVOP::myTemplateList[]=
{
    PRM_Template(PRM_ORD,       PRM_Template::PRM_EXPORT_MAX, 1,
                            &names[5], 0, &vexsrcMenu),
    PRM_Template(PRM_STRING, PRM_TYPE_DYNAMIC_PATH,
                            PRM_Template::PRM_EXPORT_MAX, 1,
                            &names[4], 0, 0, 0, 0,
                            &PRM_SpareData::shopCVEX),
    PRM_Template(PRM_COMMAND,   PRM_Template::PRM_EXPORT_TBX,
                                1, &names[0], &scriptDefault),
    PRM_Template(PRM_STRING,    1, &VOP_CodeGenerator::theVopCompilerName,
                                &VOP_CodeGenerator::theVopCompilerVexDefault),
    PRM_Template(PRM_CALLBACK,  1, &VOP_CodeGenerator::theVopForceCompileName,
                                0, 0, 0, VOP_CodeGenerator::forceCompile),
    PRM_Template()
};


OP_Node *
SOP_PrimVOP::myConstructor(OP_Network *net,const char *name,OP_Operator *entry)
{
    return new SOP_PrimVOP(net, name, entry);
}


SOP_PrimVOP::SOP_PrimVOP(OP_Network *net, const char *name, OP_Operator *entry)
        : SOP_Node(net, name, entry) ,
            // Set up our code generator for CVEX
            myCodeGenerator(this, new VOP_LanguageContextTypeList( 
                VOP_LANGUAGE_VEX, VOPconvertToContextType( VEX_CVEX_CONTEXT )),
            1, 1)
{ 
    // Add a operator table so we can have child nodes.
    setOperatorTable(getOperatorTable(VOP_TABLE_NAME));
}

SOP_PrimVOP::~SOP_PrimVOP() 
{
}


unsigned
SOP_PrimVOP::disableParms()
{
    int         changed = 0;

    return changed;
}

OP_ERROR
SOP_PrimVOP::cookMySop(OP_Context &context)
{
    double                      t = context.getTime();
    UT_String                   script;
    UT_Interrupt                *boss = UTgetInterrupt();

    if (lockInputs(context) >= UT_ERROR_ABORT)
        return error();

    duplicateSource(0, context);

    // Build our VEX script, either from the .vex file, or
    // from the SHOP, or from the contained VOPs.
    buildScript(script, context.getTime());

    // Bind our error handlers.
    VEXerrorHandler      prevHandler;
    VEXerrorHandler      prevWHandler;

    prevHandler = VEXgetErrorHandler();
    prevWHandler = VEXgetWarningHandler();
    theVexError = 0;
    theVexWarning = 0;
    VEXsetErrorHandler(vexErrorHandler);
    VEXsetWarningHandler(vexWarningHandler);

    // If script is null, default to null script.
    if (!script.isstring())
    {
        script = "null";
        addWarning(SOP_VEX_ERROR,
                   "No script specified. Using null.");
    }

    // Parse the script's parameters.
    int                  argc;
    char                *argv[4096];
    argc = script.parse(argv, 4096);

    // Provided we don't already have an error...
    if (!theVexError.isstring())
    {
        int id;
        if (boss->opStart("Executing Volume VEX", 0, 0, &id))
        {
            // This op caller is what allows op: style paths inside
            // the CVEX context to properly set up dependencies to ourselves
            // Ie, if you refer to a point cloud on another SOP, this
            // SOP needs to recook when the referred to SOP is coooked.
            OP_Caller           caller(this);

            // Actually process the vex function
            executeVex(argc, argv, t, caller);
        }
        boss->opEnd(id);
    }


    // Restore our error handlers.
    VEXsetErrorHandler(prevHandler);
    VEXsetErrorHandler(prevWHandler);

    if (theVexError.isstring())
        addError(SOP_VEX_ERROR, (const char *)theVexError);
    if (theVexWarning.isstring())
        addWarning(SOP_VEX_ERROR, (const char *)theVexWarning);

    unlockInputs();
    return error();
}

void
SOP_PrimVOP::executeVex(int argc, char **argv, 
                            fpreal t,
                            OP_Caller &opcaller)
{
    CVEX_Context                 context;
    int                          chunksize, n;

    // Note that this is a reasonable level to multithread at.
    // Each thread will build its own VEX Context independently
    // and thus, provided the read/write code is properly threadsafe
    // be threadsafe.

    // Set the eval collection
    // The getSTID() is to ensure we are thread safe.
    int              stid = UTgetSTID();
    CH_Manager      *chman = OPgetDirector()->getChannelManager();
    CH_Collection   *pushed = chman->getEvalCollection(stid);
    chman->setEvalCollection(getChannels(), stid);

    // Set the callback.
    context.setOpCaller(&opcaller);

    // The vex processing is block based.  We first marshall a block
    // of parameters from our primitive information.  We then bind
    // those parameters to vex.  Then we process vex, and read out
    // the new values.
    chunksize = 16384;
    int                 *primid = new int[chunksize];
    GEO_Primitive       *prim;

    n = 0;
    FOR_ALL_PRIMITIVES(gdp, prim)
    {
        primid[n] = prim->getNum();
        n++;

        if (n >= chunksize)
        {
            processVexBlock(context, argc, argv, primid, n, t);
            n = 0;
        }
    }

    // Handle any trailing values.
    if (n)
        processVexBlock(context, argc, argv, primid, n, t);

    // Free our buffers
    delete [] primid;

    // Restore our eval collection.
    chman->setEvalCollection(pushed, stid);
}

namespace HDK_Sample {
class sop_bindparms
{
public:
    const static int NUM_BUFFERS = 2;
    const static int INPUT_BUFFER = 0;
    const static int OUTPUT_BUFFER = 1;

    sop_bindparms()
    {
        clear();
    }
    sop_bindparms(const char *name, CVEX_Type type)
    {
        clear();
        myName.harden(name);
        myType = type;
    }
    sop_bindparms(const sop_bindparms &src)
    {
        clear();
        *this = src;
    }

    void clear()
    {
        myType = CVEX_TYPE_INVALID;
        for (int i = 0; i < NUM_BUFFERS; i++)
        {
            myBuffer[i] = 0;
            myBufLen[i] = 0;
        }
    }

    sop_bindparms &operator=(const sop_bindparms &src)
    {
        myName.harden(src.name());
        myType = src.type();

        for (int i = 0; i < NUM_BUFFERS; i++)
        {
            delete [] myBuffer[i];
            myBufLen[i] = src.myBufLen[i];
            myBuffer[i] = 0;
            if (src.buffer(i) && src.myBufLen[i])
            {
                myBuffer[i] = new char[myBufLen[i]];
                memcpy(myBuffer[i], src.buffer(i), src.myBufLen[i]);
            }
        }

        return *this;
    }

    ~sop_bindparms()
    {
        for (int i = 0; i < NUM_BUFFERS; i++)
        {
            delete [] myBuffer[i];
        }
    }

    void        allocateBuffer(int bufnum, int n)
    {
        delete [] myBuffer[bufnum];
        switch (myType)
        {
            case CVEX_TYPE_INTEGER:
                myBuffer[bufnum] = new char [sizeof(int) * n];
                break;
            case CVEX_TYPE_FLOAT:
                myBuffer[bufnum] = new char [sizeof(float) * n];
                break;
            case CVEX_TYPE_VECTOR3:
                myBuffer[bufnum] = new char [3*sizeof(float) * n];
                break;
            case CVEX_TYPE_VECTOR4:
                myBuffer[bufnum] = new char [4*sizeof(float) * n];
                break;
            default:
                UT_ASSERT(0);
                break;
        }
        myBufLen[bufnum] = n;
    }

    void        marshallIntoBuffer(int bufnum, GU_Detail *gdp, int *primid, int n)
    {
        GEO_AttributeHandle             gah;
        int                             i;

        allocateBuffer(bufnum, n);

        gah = gdp->getPrimAttribute(name());
        if (gah.isAttributeValid())
        {
            for (i = 0; i < n; i++)
            {
                gah.setElement(gdp->primitives()(primid[i]));
                switch (myType)
                {
                    case CVEX_TYPE_INTEGER:
                        ((int *)myBuffer[bufnum])[i] = gah.getI();
                        break;
                    case CVEX_TYPE_FLOAT:
                        ((float *)myBuffer[bufnum])[i] = gah.getF();
                        break;
                    case CVEX_TYPE_VECTOR3:
                        ((UT_Vector3 *)myBuffer[bufnum])[i] = gah.getV3();
                        break;
                    case CVEX_TYPE_VECTOR4:
                        ((UT_Vector4 *)myBuffer[bufnum])[i] = gah.getV4();
                        break;
                    default:
                        UT_ASSERT(0);
                        break;
                }
            }
        }
    }

    void        marshallDataToGdp(int bufnum, GU_Detail *gdp, int *primid, int n, int inc)
    {
        GEO_AttributeHandle             gah;
        int                             i;
        int                             src = 0;

        gah = gdp->getPrimAttribute(name());
        if (gah.isAttributeValid())
        {
            for (i = 0; i < n; i++)
            {
                gah.setElement(gdp->primitives()(primid[i]));
                switch (myType)
                {
                    case CVEX_TYPE_INTEGER:
                        gah.setI(((int *)myBuffer[bufnum])[src]);
                        break;
                    case CVEX_TYPE_FLOAT:
                        gah.setF(((float *)myBuffer[bufnum])[src]);
                        break;
                    case CVEX_TYPE_VECTOR3:
                        gah.setV3(((UT_Vector3 *)myBuffer[bufnum])[src]);
                        break;
                    case CVEX_TYPE_VECTOR4:
                        gah.setV4(((UT_Vector4 *)myBuffer[bufnum])[src]);
                        break;
                    default:
                        UT_ASSERT(0);
                        break;
                }
                src += inc;
            }
        }
    }

    const char *name() const { return myName; };
    CVEX_Type   type() const { return myType; }
    const char  *buffer(int bufnum) const { return myBuffer[bufnum]; }
    char        *buffer(int bufnum) { return myBuffer[bufnum]; }

private:
    UT_String           myName;
    CVEX_Type           myType;
    char                *myBuffer[NUM_BUFFERS];
    int                 myBufLen[NUM_BUFFERS];
};
}

void
SOP_PrimVOP::processVexBlock(CVEX_Context &context,
                            int argc, char **argv, 
                            int *primid, int n,
                            fpreal t)
{
    CVEX_Value                  *var;
    GB_Attribute                *attrib;
    UT_Vector3                  *P = 0;
    int                          i, j;

    UT_RefArray<sop_bindparms>   bindlist;

    // We always export our primitive ids, so bind as integer.
    context.addInput("primid", CVEX_TYPE_INTEGER, primid, n);

    // These are lazy evaluated since we don't want to pay
    // the cost if not needed.
    context.addInput("P",                       // Name of parameter
                     CVEX_TYPE_VECTOR3,         // VEX Type
                     true);                     // Is varying?

    // We lazy add our time dependent inputs as we only want to
    // flag time dependent if they are used.
    context.addInput("Time", CVEX_TYPE_FLOAT, false);
    context.addInput("Timeinc", CVEX_TYPE_FLOAT, false);
    context.addInput("Frame", CVEX_TYPE_FLOAT, false);

    // Lazily bind all of our primitive attributes.
    for (attrib = gdp->primitiveAttribs().getHead();
         attrib;
         attrib = (GB_Attribute *) attrib->next())
    {
        CVEX_Type               type = CVEX_TYPE_INVALID;

        switch (attrib->getType())
        {
            case GB_ATTRIB_FLOAT:
                if (attrib->getSize() < sizeof(float)*3)
                    type = CVEX_TYPE_FLOAT;
                else if (attrib->getSize() < sizeof(float)*4)
                    type = CVEX_TYPE_VECTOR3;
                else 
                    type = CVEX_TYPE_VECTOR4;
                break;
            case GB_ATTRIB_INT:
                type = CVEX_TYPE_INTEGER;
                break;
            case GB_ATTRIB_VECTOR:
                type = CVEX_TYPE_VECTOR3;
                break;
            default:
                type = CVEX_TYPE_INVALID;
                break;
        }
        if (type == CVEX_TYPE_INVALID)
            continue;

        context.addInput(attrib->getName(), type, true);

        bindlist.append( sop_bindparms(attrib->getName(),
                                            type) );
    }

    // We want to evaluate at the context time, not at what
    // the global frame time happens to be.
    context.setTime(t);

    // Load our array.
    context.load(argc, argv, 
            0                   // This should be info.job() if multithreaded
            );

    // Abort early if a compiler error ocurred.
    if (theVexError.isstring())
        goto error;

    // Check for lazily bound inputs 
    var = context.findInput("P", CVEX_TYPE_VECTOR3);
    if (var)
    {
        P = new UT_Vector3[n];
        for (i = 0; i < n; i++)
        {
            P[i] = gdp->primitives()(primid[i])->baryCenter();
        }
        var->setData(P, n);
    }

    // Check if any of our parameters exist as either inputs or outputs.
    for (j = 0; j < bindlist.entries(); j++)
    {
        var = context.findInput(bindlist(j).name(), bindlist(j).type());
        if (var)
        {
            // This exists as an input, we have to marshall it.
            bindlist(j).marshallIntoBuffer(sop_bindparms::INPUT_BUFFER, 
                                            gdp, primid, n);
            var->setData(bindlist(j).buffer(sop_bindparms::INPUT_BUFFER), n);
        }

        // The same attribute may be both an input and an output
        // This results in different CVEX_Values so requires two
        // buffers in the bindings.
        if (var = context.findOutput(bindlist(j).name(), bindlist(j).type()))
        {
            bindlist(j).allocateBuffer(sop_bindparms::OUTPUT_BUFFER, n);
            if (var->isVarying())
                var->setData(bindlist(j).buffer(sop_bindparms::OUTPUT_BUFFER), n);
            else
                var->setData(bindlist(j).buffer(sop_bindparms::OUTPUT_BUFFER), 1);
        }

    }

    // Compute the time dependent inputs.
    fpreal32            curtime, curtimeinc, curframe;

    var = context.findInput("Time", CVEX_TYPE_FLOAT);
    if (var)
    {
        OP_Node::flags().timeDep = true;

        curtime = t;

        var->setData(&curtime, 1);
    }
    var = context.findInput("Timeinc", CVEX_TYPE_FLOAT);
    if (var)
    {
        OP_Node::flags().timeDep = true;

        curtimeinc = 1.0f/OPgetDirector()->getChannelManager()->getSamplesPerSec();

        var->setData(&curtimeinc, 1);
    }
    var = context.findInput("Frame", CVEX_TYPE_FLOAT);
    if (var)
    {
        OP_Node::flags().timeDep = true;

        curframe = OPgetDirector()->getChannelManager()->getSample(t);

        var->setData(&curframe, 1);
    }

    // USERFLAG1 is used to detect time dependence of ch expressions.
    // context.clearFlag(VEX_Instance::USERFLAG1);
    context.clearFlag(0x1000);

    // Actually execute the vex code!
    // Allow interrupts.
    context.run(n, true);

    // Update our timedependency based on the userflag.
    // if (context.getFlag(VEX_Instance::USERFLAG1))
    if (context.getFlag(0x1000))
        OP_Node::flags().timeDep = 1;

    // Write out all bound parameters.
    for (j = 0; j < bindlist.entries(); j++)
    {
        var = context.findOutput(bindlist(j).name(), bindlist(j).type());
        if (var)
        {
            bindlist(j).marshallDataToGdp(sop_bindparms::OUTPUT_BUFFER, gdp, primid, n, (var->isVarying() ? 1 : 0));
        }
    }


error:
    delete [] P;
}

void
SOP_PrimVOP::buildScript(UT_String &script, float t)
{
    UT_String           shoppath;
    int                 vexsrc = VEXSRC(t);

    script = "";

    switch (vexsrc)
    {
        case 0:
        {
            getFullPath(shoppath);
            script = "op:";
            script += shoppath;
            // buildVexCommand appends to our script variable.
            buildVexCommand(script, getSpareParmTemplates(), t);
            break;
        }

        case 2:
            // Straightforward, use the explicit script
            SCRIPT(script, t);
            break;

        case 1:
        {
            SHOPPATH(shoppath, t);

            // Use the referenced shop network.
            SHOP_Node *shop;
            shop = findSHOPNode(shoppath);
            if (shop)
            {
                shop->buildVexCommand(script, shop->getSpareParmTemplates(), t);
                addExtraInput(shop, OP_INTEREST_DATA);

                // It is possible that we are dealing with a multi-context
                // shader (ie, shop). In that case, we need to specify the
                // shader context which we want to interpret it as. This is done
                // by passing it as an argument. Since this operator invokes
                // CVEX shader, we specify that type. Single-context shaders
                // will ignore it.
                shop->buildShaderString(script, t, 0, 0, 0, SHOP_CVEX);
            }
            break;
        }
    }
}

void
SOP_PrimVOP::getVariableString(int index, UT_String &value, int thread)
{
    if( !myCodeGenerator.getVariableString(index, value) )
        SOP_Node::getVariableString(index, value, thread);
}

OP_OperatorFilter *
SOP_PrimVOP::getOperatorFilter()
{
    return myCodeGenerator.getOperatorFilter();
}

VOP_CodeGenerator *
SOP_PrimVOP::getVopCodeGenerator()
{
    return &myCodeGenerator;
}

const char *
SOP_PrimVOP::getChildType() const
{
    return VOP_OPTYPE_NAME;
}

OP_OpTypeId
SOP_PrimVOP::getChildTypeID() const
{
    return VOP_OPTYPE_ID;
}

void
SOP_PrimVOP::opChanged(OP_EventType reason, void *data)
{
    SOP_Node::opChanged(reason, data);
    myCodeGenerator.ownerChanged(reason, data);
}

void
SOP_PrimVOP::finishedLoadingNetwork(bool is_child_call)
{
    myCodeGenerator.ownerFinishedLoadingNetwork();
    SOP_Node::finishedLoadingNetwork(is_child_call);
}

void
SOP_PrimVOP::addNode(OP_Node *node, int notify, int explicitly)
{
    myCodeGenerator.beforeAddNode(node);
    SOP_Node::addNode(node, notify, explicitly);
    myCodeGenerator.afterAddNode(node);
}

void
SOP_PrimVOP::getNodeSpecificInfoText(OP_Context &context,
                                       int verbose,
                                       UT_WorkBuffer &text)
{
    SOP_Node::getNodeSpecificInfoText(context, verbose, text);
    myCodeGenerator.appendCompileErrors(text, verbose, false);
}


Generated on Mon Jan 28 00:26:26 2013 for HDK by  doxygen 1.5.9