void
ShaderInstance::parameters (const ParamValueList &params)
{
    // Seed the params with the master's defaults
    m_iparams = m_master->m_idefaults;
    m_fparams = m_master->m_fdefaults;
    m_sparams = m_master->m_sdefaults;

    m_instoverrides.resize (std::max (0, lastparam()));

    // Set the initial lockgeom and dataoffset on the instoverrides, based
    // on the master.
    for (int i = 0, e = (int)m_instoverrides.size(); i < e; ++i) {
        Symbol *sym = master()->symbol(i);
        m_instoverrides[i].lockgeom (sym->lockgeom());
        m_instoverrides[i].dataoffset (sym->dataoffset());
    }

    for (auto&& p : params) {
        if (p.name().size() == 0)
            continue;   // skip empty names
        int i = findparam (p.name());
        if (i >= 0) {
            // if (shadingsys().debug())
            //     shadingsys().info (" PARAMETER %s %s", p.name(), p.type());
            const Symbol *sm = master()->symbol(i);    // This sym in the master
            SymOverrideInfo *so = &m_instoverrides[i]; // Slot for sym's override info
            TypeSpec sm_typespec = sm->typespec(); // Type of the master's param
            if (sm_typespec.is_closure_based()) {
                // Can't assign a closure instance value.
                shadingsys().warning ("skipping assignment of closure: %s", sm->name());
                continue;
            }
            if (sm_typespec.is_structure())
                continue;    // structs are just placeholders; skip

            const void *data = p.data();
            float tmpdata[3]; // used for inline conversions to float/float[3]

            // Check type of parameter and matching symbol. Note that the
            // compatible accounts for indefinite-length arrays.
            TypeDesc paramtype = sm_typespec.simpletype();  // what the shader writer wants
            TypeDesc valuetype = p.type();                  // what the data provided actually is

            if (master()->shadingsys().relaxed_param_typecheck()) {
                // first handle cases where we actually need to modify the data (like setting a float parameter with an int)
                 if ((paramtype == TypeDesc::FLOAT || paramtype.is_vec3()) && valuetype.basetype == TypeDesc::INT && valuetype.basevalues() == 1) {
                    int val = *static_cast<const int*>(p.data());
                    float conv = float(val);
                    if (val != int(conv))
                        shadingsys().error ("attempting to set parameter from wrong type would change the value: %s (set %.9g from %d)",
                            sm->name(), conv, val);
                    tmpdata[0] = conv;
                    data = tmpdata;
                    valuetype = TypeDesc::FLOAT;
                }

                // Relaxed rules just look to see that the types are isomorphic to each other (ie: same number of base values)
                // Note that:
                //   * basetypes must match exactly (int vs float vs string)
                //   * valuetype cannot be unsized (we must know the concrete number of values)
                //   * if paramtype is sized (or not an array) just check for the total number of entries
                //   * if paramtype is unsized (shader writer is flexible about how many values come in) -- make sure we are a multiple of the target type
                //   * allow a single float setting a vec3 (or equivalent)
                if (!( valuetype.basetype == paramtype.basetype &&
                      !valuetype.is_unsized_array() &&
                      ((!paramtype.is_unsized_array() && valuetype.basevalues() == paramtype.basevalues()) ||
                       ( paramtype.is_unsized_array() && valuetype.basevalues() % paramtype.aggregate == 0) ||
                       ( paramtype.is_vec3()          && valuetype == TypeDesc::FLOAT) ) )) {
                    // We are being very relaxed in this mode, so if the user _still_ got it wrong
                    // something more serious is at play and we should treat it as an error.
                    shadingsys().error ("attempting to set parameter from incompatible type: %s (expected '%s', received '%s')",
                                          sm->name(), paramtype, valuetype);
                    continue;
                }
            } else if (!compatible_param(paramtype, valuetype)) {
                shadingsys().warning ("attempting to set parameter with wrong type: %s (expected '%s', received '%s')",
                                      sm->name(), paramtype, valuetype);
                continue;
            }

            // Mark that the override as an instance value
            so->valuesource (Symbol::InstanceVal);

            // Lock the param against geometric primitive overrides if the
            // master thinks it was so locked, AND the Parameter() call
            // didn't specify lockgeom=false (which would be indicated by
            // the parameter's interpolation being non-CONSTANT).
            bool lockgeom = (sm->lockgeom() &&
                             p.interp() == ParamValue::INTERP_CONSTANT);
            so->lockgeom (lockgeom);

            DASSERT (so->dataoffset() == sm->dataoffset());
            so->dataoffset (sm->dataoffset());

            if (paramtype.is_vec3() && valuetype == TypeDesc::FLOAT) {
                // Handle the special case of assigning a float for a triple
                // by replicating it into local memory.
                tmpdata[0] = *(const float *)data;
                tmpdata[1] = *(const float *)data;
                tmpdata[2] = *(const float *)data;
                data = &tmpdata;
                valuetype = paramtype;
            }

            if (paramtype.arraylen < 0) {
                // An array of definite size was supplied to a parameter
                // that was an array of indefinite size. Magic! The trick
                // here is that we need to allocate paramter space at the
                // END of the ordinary param storage, since when we assigned
                // data offsets to each parameter, we didn't know the length
                // needed to allocate this param in its proper spot.
                int nelements = valuetype.basevalues();
                // Store the actual length in the shader instance parameter
                // override info. Compute the length this way to account for relaxed
                // parameter checking (for example passing an array of floats to an array of colors)
                so->arraylen (nelements / paramtype.aggregate);
                // Allocate space for the new param size at the end of its
                // usual parameter area, and set the new dataoffset to that
                // position.
                if (paramtype.basetype == TypeDesc::FLOAT) {
                    so->dataoffset((int) m_fparams.size());
                    expand (m_fparams, nelements);
                } else if (paramtype.basetype == TypeDesc::INT) {
                    so->dataoffset((int) m_iparams.size());
                    expand (m_iparams, nelements);
                } else if (paramtype.basetype == TypeDesc::STRING) {
                    so->dataoffset((int) m_sparams.size());
                    expand (m_sparams, nelements);
                } else {
                    ASSERT (0 && "unexpected type");
                }
                // FIXME: There's a tricky case that we overlook here, where
                // an indefinite-length-array parameter is given DIFFERENT
                // definite length in subsequent rerenders. Don't do that.
            }
            else {
                // If the instance value is the same as the master's default,
                // just skip the parameter, let it "keep" the default.
                // Note that this can't/shouldn't happen for the indefinite-
                // sized array case, which is why we have it in the 'else'
                // clause of that test.
                void *defaultdata = m_master->param_default_storage(i);
                if (lockgeom &&
                      memcmp (defaultdata, data, valuetype.size()) == 0) {
                    // Must reset valuesource to default, in case the parameter
                    // was set already, and now is being changed back to default.
                    so->valuesource (Symbol::DefaultVal);
                }
            }

            // Copy the supplied data into place.
            memcpy (param_storage(i), data, valuetype.size());
        }
        else {
            shadingsys().warning ("attempting to set nonexistent parameter: %s", p.name());
        }
    }

    {
        // Adjust the stats
        ShadingSystemImpl &ss (shadingsys());
        size_t symmem = vectorbytes(m_instoverrides);
        size_t parammem = (vectorbytes(m_iparams) + vectorbytes(m_fparams) +
                           vectorbytes(m_sparams));
        spin_lock lock (ss.m_stat_mutex);
        ss.m_stat_mem_inst_syms += symmem;
        ss.m_stat_mem_inst_paramvals += parammem;
        ss.m_stat_mem_inst += (symmem+parammem);
        ss.m_stat_memory += (symmem+parammem);
    }
}
// Can a parameter with type 'a' be bound to a value of type b?
// Requires matching types (and if arrays, matching lengths or for
// a's length to be undetermined), or it's also ok to bind a single float to
// a non-array triple. All triples are considered equivalent for this test.
inline bool compatible_param (const TypeDesc& a, const TypeDesc& b)
{
    return equivalent (a, b) || (a.is_vec3() && b == TypeDesc::FLOAT);
}