bool
OSLInput::read_native_tiles (
#if OIIO_PLUGIN_VERSION >= 21
                             int subimage, int miplevel,
#endif
                             int xbegin, int xend, int ybegin, int yend,
                             int zbegin, int zend, void *data)
{
#if OIIO_PLUGIN_VERSION >= 21
    lock_guard lock (m_mutex);
    if (! seek_subimage (subimage, miplevel))
        return false;
#endif

    // Create an ImageBuf wrapper of the user's data
    ImageSpec spec = m_spec; // Make a spec that describes just this scanline
    spec.x = xbegin;
    spec.y = ybegin;
    spec.z = zbegin;
    spec.width  = xend-xbegin;
    spec.height = yend-ybegin;
    spec.depth  = zend-zbegin;
    ImageBuf ibwrapper (spec, data);

    // Now run the shader on the ImageBuf pixels, which really point to
    // the caller's data buffer.
    ASSERT (m_group.get());
    ROI roi (spec.x, spec.x+spec.width, spec.y, spec.y+spec.height,
             spec.z, spec.z+spec.depth);
    return shade_image (*shadingsys, *m_group, NULL, ibwrapper, m_outputs,
                        ShadePixelCenters, roi, 1);
}
bool
OSLInput::read_native_scanlines (int ybegin, int yend, int z, void *data)
{
    // Create an ImageBuf wrapper of the user's data
    ImageSpec spec = m_spec; // Make a spec that describes just this scanline
    spec.y = ybegin;
    spec.z = z;
    spec.height = yend-ybegin;
    spec.depth = 1;
    ImageBuf ibwrapper (spec, data);

    // Now run the shader on the ImageBuf pixels, which really point to
    // the caller's data buffer.
    ASSERT (m_group.get());
    ROI roi (spec.x, spec.x+spec.width, spec.y, spec.y+spec.height,
             spec.z, spec.z+spec.depth);
    return shade_image (*shadingsys, *m_group, NULL, ibwrapper, m_outputs,
                        ShadePixelCenters, roi, 1);
}
extern "C" int
test_shade (int argc, const char *argv[])
{
    OIIO::Timer timer;

    // Create a new shading system.  We pass it the RendererServices
    // object that services callbacks from the shading system, NULL for
    // the TextureSystem (that just makes 'create' make its own TS), and
    // an error handler.
    shadingsys = ShadingSystem::create (&rend, NULL, &errhandler);
    register_closures(shadingsys);

    // Remember that each shader parameter may optionally have a
    // metadata hint [[int lockgeom=...]], where 0 indicates that the
    // parameter may be overridden by the geometry itself, for example
    // with data interpolated from the mesh vertices, and a value of 1
    // means that it is "locked" with respect to the geometry (i.e. it
    // will not be overridden with interpolated or
    // per-geometric-primitive data).
    // 
    // In order to most fully optimize shader, we typically want any
    // shader parameter not explicitly specified to default to being
    // locked (i.e. no per-geometry override):
    shadingsys->attribute("lockgeom", 1);

    // Now we declare our shader.
    // 
    // Each material in the scene is comprised of a "shader group."
    // Each group is comprised of one or more "layers" (a.k.a. shader
    // instances) with possible connections from outputs of
    // upstream/early layers into the inputs of downstream/later layers.
    // A shader instance is the combination of a reference to a shader
    // master and its parameter values that may override the defaults in
    // the shader source and may be particular to this instance (versus
    // all the other instances of the same shader).
    // 
    // A shader group declaration typically looks like this:
    //
    //   ShaderGroupRef shadergroup = ss->ShaderGroupBegin ();
    //   ss->Parameter ("paramname", TypeDesc paramtype, void *value);
    //      ... and so on for all the other parameters of...
    //   ss->Shader ("shadertype", "shadername", "layername");
    //      The Shader() call creates a new instance, which gets
    //      all the pending Parameter() values made right before it.
    //   ... and other shader instances in this group, interspersed with...
    //   ss->ConnectShaders ("layer1", "param1", "layer2", "param2");
    //   ... and other connections ...
    //   ss->ShaderGroupEnd ();
    // 
    // It looks so simple, and it really is, except that the way this
    // testshade program works is that all the Parameter() and Shader()
    // calls are done inside getargs(), as it walks through the command
    // line arguments, whereas the connections accumulate and have
    // to be processed at the end.  Bear with us.
    
    // Start the shader group and grab a reference to it.
    ShaderGroupRef shadergroup = shadingsys->ShaderGroupBegin ();

    // Get the command line arguments.  That will set up all the shader
    // instances and their parameters for the group.
    getargs (argc, argv);

    // Now set up the connections
    for (size_t i = 0;  i < connections.size();  i += 4) {
        if (i+3 < connections.size()) {
            std::cout << "Connect " 
                      << connections[i] << "." << connections[i+1]
                      << " to " << connections[i+2] << "." << connections[i+3]
                      << "\n";
            shadingsys->ConnectShaders (connections[i].c_str(),
                                        connections[i+1].c_str(),
                                        connections[i+2].c_str(),
                                        connections[i+3].c_str());
        }
    }

    // End the group
    shadingsys->ShaderGroupEnd ();

    if (outputfiles.size() != 0)
        std::cout << "\n";

    // Set up the named transformations, including shader and object.
    // For this test application, we just do this statically; in a real
    // renderer, the global named space (like "myspace") would probably
    // be static, but shader and object spaces may be different for each
    // object.
    setup_transformations (rend, Mshad, Mobj);

    // Set up the image outputs requested on the command line
    setup_output_images (shadingsys, shadergroup);

    // Set up shader globals and a little test grid of points to shade.
    ShaderGlobals shaderglobals;

    double setuptime = timer.lap ();

    // Optional: high-performance apps may request this thread-specific
    // pointer in order to save a bit of time on each shade.  Just like
    // the name implies, a multithreaded renderer would need to do this
    // separately for each thread, and be careful to always use the same
    // thread_info each time for that thread.
    //
    // There's nothing wrong with a simpler app just passing NULL for
    // the thread_info; in such a case, the ShadingSystem will do the
    // necessary calls to find the thread-specific pointer itself, but
    // this will degrade performance just a bit.
    OSL::PerThreadInfo *thread_info = shadingsys->create_thread_info();

    // Request a shading context so that we can execute the shader.
    // We could get_context/release_constext for each shading point,
    // but to save overhead, it's more efficient to reuse a context
    // within a thread.
    ShadingContext *ctx = shadingsys->get_context (thread_info);

    // Allow a settable number of iterations to "render" the whole image,
    // which is useful for time trials of things that would be too quick
    // to accurately time for a single iteration
    for (int iter = 0;  iter < iters;  ++iter) {

        // Loop over all pixels in the image (in x and y)...
        for (int y = 0, n = 0;  y < yres;  ++y) {
            for (int x = 0;  x < xres;  ++x, ++n) {

                // In a real renderer, this is where you would figure
                // out what object point is visible in this pixel (or
                // this sample, for antialiasing).  Once determined,
                // you'd set up a ShaderGlobals that contained the vital
                // information about that point, such as its location,
                // the normal there, the u and v coordinates on the
                // surface, the transformation of that object, and so
                // on.  
                //
                // This test app is not a real renderer, so we just
                // set it up rigged to look like we're rendering a single
                // quadrilateral that exactly fills the viewport, and that
                // setup is done in the following function call:
                setup_shaderglobals (shaderglobals, shadingsys, x, y);

                // Actually run the shader for this point
                shadingsys->execute (*ctx, *shadergroup, shaderglobals);

                // Save all the designated outputs.  But only do so if we
                // are on the last iteration requested, so that if we are
                // doing a bunch of iterations for time trials, we only
                // including the output pixel copying once in the timing.
                if (iter == (iters - 1)) {
                    save_outputs (shadingsys, ctx, x, y);
                }
            }
        }

        // If any reparam was requested, do it now
        if (reparams.size() && reparam_layer.size()) {
            for (size_t p = 0;  p < reparams.size();  ++p) {
                const ParamValue &pv (reparams[p]);
                shadingsys->ReParameter (*shadergroup, reparam_layer.c_str(),
                                         pv.name().c_str(), pv.type(),
                                         pv.data());
            }
        }
    }

    // We're done shading with this context.
    shadingsys->release_context (ctx);

    // Now that we're done rendering, release the thread=specific
    // pointer we saved.  A simple app could skip this; but if the app
    // asks for it (as we have in this example), then it should also
    // destroy it when done with it.
    shadingsys->destroy_thread_info(thread_info);

    if (outputfiles.size() == 0)
        std::cout << "\n";

    // Write the output images to disk
    for (size_t i = 0;  i < outputimgs.size();  ++i) {
        if (outputimgs[i]) {
            outputimgs[i]->save();
            delete outputimgs[i];
            outputimgs[i] = NULL;
        }
    }

    // Print some debugging info
    if (debug || stats) {
        double runtime = timer.lap();
        std::cout << "\n";
        std::cout << "Setup: " << OIIO::Strutil::timeintervalformat (setuptime,2) << "\n";
        std::cout << "Run  : " << OIIO::Strutil::timeintervalformat (runtime,2) << "\n";
        std::cout << "\n";
        std::cout << shadingsys->getstats (5) << "\n";
    }

    // We're done with the shading system now, destroy it
    shadergroup.reset ();  // Must release this before destroying shadingsys
    ShadingSystem::destroy (shadingsys);

    return EXIT_SUCCESS;
}
bool
OSLInput::open (const std::string &name, ImageSpec &newspec,
                const ImageSpec &config)
{
    // std::cout << "OSLInput::open \"" << name << "\"\n";
    setup_shadingsys ();

    std::vector<std::pair<string_view,string_view> > args;
    string_view shadername = deconstruct_uri (name, &args);
    if (shadername.empty())
        return false;
    if (! Strutil::ends_with (shadername, ".osl") &&
        ! Strutil::ends_with (shadername, ".oso") &&
        ! Strutil::ends_with (shadername, ".oslgroup") &&
        ! Strutil::ends_with (shadername, ".oslbody"))
        return false;

    m_filename = name;
    m_topspec = ImageSpec (1024, 1024, 4, TypeDesc::FLOAT);

    // std::cout << "  name = " << shadername << " args? " << args.size() << "\n";
    for (size_t i = 0; i < args.size(); ++i) {
        // std::cout << "    " << args[i].first << "  =  " << args[i].second << "\n";
        if (args[i].first == "RES") {
            parse_res (args[i].second, m_topspec.width, m_topspec.height, m_topspec.depth);
        } else if (args[i].first == "TILE" || args[i].first == "TILES") {
            parse_res (args[i].second, m_topspec.tile_width, m_topspec.tile_height,
                       m_topspec.tile_depth);
        } else if (args[i].first == "OUTPUT") {
            m_outputs.emplace_back(args[i].second);
        } else if (args[i].first == "MIP") {
            m_mip = Strutil::from_string<int>(args[i].second);
        } else if (args[i].first.size() && args[i].second.size()) {
            parse_param (args[i].first, args[i].second, m_topspec);
        }
    }
    if (m_outputs.empty()) {
        m_outputs.emplace_back("result");
        m_outputs.emplace_back("alpha");
    }

    m_topspec.full_x = m_topspec.x;
    m_topspec.full_y = m_topspec.y;
    m_topspec.full_z = m_topspec.z;
    m_topspec.full_width = m_topspec.width;
    m_topspec.full_height = m_topspec.height;
    m_topspec.full_depth = m_topspec.depth;

    bool ok = true;
    if (Strutil::ends_with (shadername, ".oslgroup")) { // Serialized group
        // No further processing necessary
        std::string groupspec;
        if (! OIIO::Filesystem::read_text_file (shadername, groupspec)) {
            // If it didn't name a disk file, assume it's the "inline"
            // serialized group.
            groupspec = groupspec.substr (0, groupspec.size()-9);
        }
        // std::cout << "Processing group specification:\n---\n"
        //           << groupspec << "\n---\n";
        OIIO::lock_guard lock (shading_mutex);
        m_group = shadingsys->ShaderGroupBegin ("", "surface", groupspec);
        if (! m_group)
            return false;   // Failed
        shadingsys->ShaderGroupEnd ();
    }
    if (Strutil::ends_with (shadername, ".oso")) { // Compiled shader
        OIIO::lock_guard lock (shading_mutex);
        shadername.remove_suffix (4);
        m_group = shadingsys->ShaderGroupBegin ();
        for (size_t p = 0, np = m_topspec.extra_attribs.size(); p < np; ++p) {
            const ParamValue &pv (m_topspec.extra_attribs[p]);
            shadingsys->Parameter (pv.name(), pv.type(), pv.data(),
                                   pv.interp() == ParamValue::INTERP_CONSTANT);
        }
        if (! shadingsys->Shader ("surface", shadername, "" /*layername*/ )) {
            error ("y %s", errhandler.haserror() ? errhandler.geterror() : std::string("OSL error"));
            ok = false;
        }
        shadingsys->ShaderGroupEnd ();
    }

    if (Strutil::ends_with (shadername, ".osl")) { // shader source
    }
    if (Strutil::ends_with (shadername, ".oslbody")) { // shader source
        OIIO::lock_guard lock (shading_mutex);
        shadername.remove_suffix (8);
        static int exprcount = 0;
        std::string exprname = OIIO::Strutil::format("expr_%d", exprcount++);
        std::string sourcecode =
            "shader " + exprname + " (\n"
            "    float s = u [[ int lockgeom=0 ]],\n"
            "    float t = v [[ int lockgeom=0 ]],\n"
            "    output color result = 0,\n"
            "    output float alpha = 1,\n"
            "  )\n"
            "{\n"
            "    " + std::string(shadername) + "\n"
            "    ;\n"
            "}\n";
        // std::cout << "Expression-based shader text is:\n---\n"
        //           << sourcecode << "---\n";
        std::string err;
        if (! compile_buffer (sourcecode, exprname, err)) {
            error ("%s", err);
            return false;
        }
        m_group = shadingsys->ShaderGroupBegin ();
        for (size_t p = 0, np = m_topspec.extra_attribs.size(); p < np; ++p) {
            const ParamValue &pv (m_topspec.extra_attribs[p]);
            shadingsys->Parameter (pv.name(), pv.type(), pv.data(),
                                   pv.interp() == ParamValue::INTERP_CONSTANT);
        }
        shadingsys->Shader ("surface", exprname, "" /*layername*/) ;
        shadingsys->ShaderGroupEnd ();
    }

    if (!ok || m_group.get() == NULL)
        return false;

    shadingsys->attribute (m_group.get(), "renderer_outputs",
                           TypeDesc(TypeDesc::STRING,m_outputs.size()),
                           &m_outputs[0]);

#if OIIO_PLUGIN_VERSION < 21
    return ok && seek_subimage (0, 0, newspec);
#else
    ok &= seek_subimage (0, 0);
    if (ok)
        newspec = spec();
    else
        close ();
    return ok;
#endif
}
 // Reset everything to initial state
 void init () {
     m_group.reset ();
     m_mip = false;
     m_subimage = -1;
     m_miplevel = -1;
 }
extern "C" OSL_DLL_EXPORT int
test_shade (int argc, const char *argv[])
{
    OIIO::Timer timer;

    // Create a new shading system.  We pass it the RendererServices
    // object that services callbacks from the shading system, NULL for
    // the TextureSystem (that just makes 'create' make its own TS), and
    // an error handler.
    shadingsys = new ShadingSystem (&rend, NULL, &errhandler);

    // Register the layout of all closures known to this renderer
    // Any closure used by the shader which is not registered, or
    // registered with a different number of arguments will lead
    // to a runtime error.
    register_closures(shadingsys);

    // Remember that each shader parameter may optionally have a
    // metadata hint [[int lockgeom=...]], where 0 indicates that the
    // parameter may be overridden by the geometry itself, for example
    // with data interpolated from the mesh vertices, and a value of 1
    // means that it is "locked" with respect to the geometry (i.e. it
    // will not be overridden with interpolated or
    // per-geometric-primitive data).
    // 
    // In order to most fully optimize shader, we typically want any
    // shader parameter not explicitly specified to default to being
    // locked (i.e. no per-geometry override):
    shadingsys->attribute("lockgeom", 1);

    // Now we declare our shader.
    // 
    // Each material in the scene is comprised of a "shader group."
    // Each group is comprised of one or more "layers" (a.k.a. shader
    // instances) with possible connections from outputs of
    // upstream/early layers into the inputs of downstream/later layers.
    // A shader instance is the combination of a reference to a shader
    // master and its parameter values that may override the defaults in
    // the shader source and may be particular to this instance (versus
    // all the other instances of the same shader).
    // 
    // A shader group declaration typically looks like this:
    //
    //   ShaderGroupRef shadergroup = ss->ShaderGroupBegin ();
    //   ss->Parameter ("paramname", TypeDesc paramtype, void *value);
    //      ... and so on for all the other parameters of...
    //   ss->Shader ("shadertype", "shadername", "layername");
    //      The Shader() call creates a new instance, which gets
    //      all the pending Parameter() values made right before it.
    //   ... and other shader instances in this group, interspersed with...
    //   ss->ConnectShaders ("layer1", "param1", "layer2", "param2");
    //   ... and other connections ...
    //   ss->ShaderGroupEnd ();
    // 
    // It looks so simple, and it really is, except that the way this
    // testshade program works is that all the Parameter() and Shader()
    // calls are done inside getargs(), as it walks through the command
    // line arguments, whereas the connections accumulate and have
    // to be processed at the end.  Bear with us.
    
    // Start the shader group and grab a reference to it.
    shadergroup = shadingsys->ShaderGroupBegin (groupname);

    // Get the command line arguments.  That will set up all the shader
    // instances and their parameters for the group.
    getargs (argc, argv);

    if (! shadergroup) {
        std::cerr << "ERROR: Invalid shader group. Exiting testshade.\n";
        return EXIT_FAILURE;
    }

    shadingsys->attribute (shadergroup.get(), "groupname", groupname);

    // Now set up the connections
    for (size_t i = 0;  i < connections.size();  i += 4) {
        if (i+3 < connections.size()) {
            std::cout << "Connect " 
                      << connections[i] << "." << connections[i+1]
                      << " to " << connections[i+2] << "." << connections[i+3]
                      << "\n";
            shadingsys->ConnectShaders (connections[i].c_str(),
                                        connections[i+1].c_str(),
                                        connections[i+2].c_str(),
                                        connections[i+3].c_str());
        }
    }

    // End the group
    shadingsys->ShaderGroupEnd ();

    if (verbose || do_oslquery) {
        std::string pickle;
        shadingsys->getattribute (shadergroup.get(), "pickle", pickle);
        std::cout << "Shader group:\n---\n" << pickle << "\n---\n";
        std::cout << "\n";
        ustring groupname;
        shadingsys->getattribute (shadergroup.get(), "groupname", groupname);
        std::cout << "Shader group \"" << groupname << "\" layers are:\n";
        int num_layers = 0;
        shadingsys->getattribute (shadergroup.get(), "num_layers", num_layers);
        if (num_layers > 0) {
            std::vector<const char *> layers (size_t(num_layers), NULL);
            shadingsys->getattribute (shadergroup.get(), "layer_names",
                                      TypeDesc(TypeDesc::STRING, num_layers),
                                      &layers[0]);
            for (int i = 0; i < num_layers; ++i) {
                std::cout << "    " << (layers[i] ? layers[i] : "<unnamed>") << "\n";
                if (do_oslquery) {
                    OSLQuery q;
                    q.init (shadergroup.get(), i);
                    for (size_t p = 0;  p < q.nparams(); ++p) {
                        const OSLQuery::Parameter *param = q.getparam(p);
                        std::cout << "\t" << (param->isoutput ? "output "  : "")
                                  << param->type << ' ' << param->name << "\n";
                    }
                }
            }
        }
        std::cout << "\n";
    }
    if (archivegroup.size())
        shadingsys->archive_shadergroup (shadergroup.get(), archivegroup);

    if (outputfiles.size() != 0)
        std::cout << "\n";

    // Set up the named transformations, including shader and object.
    // For this test application, we just do this statically; in a real
    // renderer, the global named space (like "myspace") would probably
    // be static, but shader and object spaces may be different for each
    // object.
    setup_transformations (rend, Mshad, Mobj);

    // Set up the image outputs requested on the command line
    setup_output_images (shadingsys, shadergroup);

    if (debug)
        test_group_attributes (shadergroup.get());

    if (num_threads < 1)
        num_threads = boost::thread::hardware_concurrency();

    double setuptime = timer.lap ();

    // Allow a settable number of iterations to "render" the whole image,
    // which is useful for time trials of things that would be too quick
    // to accurately time for a single iteration
    for (int iter = 0;  iter < iters;  ++iter) {
        OIIO::ROI roi (0, xres, 0, yres);

        if (use_shade_image)
            OSL::shade_image (*shadingsys, *shadergroup, NULL,
                              *outputimgs[0], outputvarnames,
                              pixelcenters ? ShadePixelCenters : ShadePixelGrid,
                              roi, num_threads);
        else {
            bool save = (iter == (iters-1));   // save on last iteration
#if 0
            shade_region (shadergroup.get(), roi, save);
#else
            OIIO::ImageBufAlgo::parallel_image (
                    boost::bind (shade_region, shadergroup.get(), _1, save),
                    roi, num_threads);
#endif
        }

        // If any reparam was requested, do it now
        if (reparams.size() && reparam_layer.size()) {
            for (size_t p = 0;  p < reparams.size();  ++p) {
                const ParamValue &pv (reparams[p]);
                shadingsys->ReParameter (*shadergroup, reparam_layer.c_str(),
                                         pv.name().c_str(), pv.type(),
                                         pv.data());
            }
        }
    }
    double runtime = timer.lap();

    if (outputfiles.size() == 0)
        std::cout << "\n";

    // Write the output images to disk
    for (size_t i = 0;  i < outputimgs.size();  ++i) {
        if (outputimgs[i]) {
            if (! print_outputs) {
                std::string filename = outputimgs[i]->name();
                // JPEG, GIF, and PNG images should be automatically saved
                // as sRGB because they are almost certainly supposed to
                // be displayed on web pages.
                using namespace OIIO;
                if (Strutil::iends_with (filename, ".jpg") ||
                    Strutil::iends_with (filename, ".jpeg") ||
                    Strutil::iends_with (filename, ".gif") ||
                    Strutil::iends_with (filename, ".png")) {
                    ImageBuf ccbuf;
                    ImageBufAlgo::colorconvert (ccbuf, *outputimgs[i],
                                                "linear", "sRGB", false,
                                                "", "");
                    ccbuf.set_write_format (outputimgs[i]->spec().format);
                    ccbuf.write (filename);
                } else {
                    outputimgs[i]->write (filename);
                }
            }
            delete outputimgs[i];
            outputimgs[i] = NULL;
        }
    }

    // Print some debugging info
    if (debug || runstats || profile) {
        double writetime = timer.lap();
        std::cout << "\n";
        std::cout << "Setup: " << OIIO::Strutil::timeintervalformat (setuptime,2) << "\n";
        std::cout << "Run  : " << OIIO::Strutil::timeintervalformat (runtime,2) << "\n";
        std::cout << "Write: " << OIIO::Strutil::timeintervalformat (writetime,2) << "\n";
        std::cout << "\n";
        std::cout << shadingsys->getstats (5) << "\n";
        OIIO::TextureSystem *texturesys = shadingsys->texturesys();
        if (texturesys)
            std::cout << texturesys->getstats (5) << "\n";
        std::cout << ustring::getstats() << "\n";
    }

    // We're done with the shading system now, destroy it
    shadergroup.reset ();  // Must release this before destroying shadingsys
    delete shadingsys;

    return EXIT_SUCCESS;
}
static void
setup_output_images (ShadingSystem *shadingsys,
                     ShaderGroupRef &shadergroup)
{
    // Tell the shading system which outputs we want
    if (outputvars.size()) {
        std::vector<const char *> aovnames (outputvars.size());
        for (size_t i = 0; i < outputvars.size(); ++i) {
            ustring varname (outputvars[i]);
            aovnames[i] = varname.c_str();
            size_t dot = varname.find('.');
            if (dot != ustring::npos) {
                // If the name contains a dot, it's intended to be layer.symbol
                varname = ustring (varname, dot+1);
            }
        }
        shadingsys->attribute (use_group_outputs ? shadergroup.get() : NULL,
                               "renderer_outputs",
                               TypeDesc(TypeDesc::STRING,(int)aovnames.size()),
                               &aovnames[0]);
        if (use_group_outputs)
            std::cout << "Marking group outputs, not global renderer outputs.\n";
    }

    if (entrylayers.size()) {
        std::vector<const char *> layers;
        std::cout << "Entry layers:";
        for (size_t i = 0; i < entrylayers.size(); ++i) {
            ustring layername (entrylayers[i]);  // convert to ustring
            int layid = shadingsys->find_layer (*shadergroup, layername);
            layers.push_back (layername.c_str());
            entrylayer_index.push_back (layid);
            std::cout << ' ' << entrylayers[i] << "(" << layid << ")";
        }
        std::cout << "\n";
        shadingsys->attribute (shadergroup.get(), "entry_layers",
                               TypeDesc(TypeDesc::STRING,(int)entrylayers.size()),
                               &layers[0]);
    }

    if (extraoptions.size())
        shadingsys->attribute ("options", extraoptions);

    ShadingContext *ctx = shadingsys->get_context ();
    // Because we can only call find_symbol or get_symbol on something that
    // has been set up to shade (or executed), we call execute() but tell it
    // not to actually run the shader.
    ShaderGlobals sg;
    setup_shaderglobals (sg, shadingsys, 0, 0);

    if (raytype_opt)
        shadingsys->optimize_group (shadergroup.get(), raytype_bit, ~raytype_bit);
    shadingsys->execute (ctx, *shadergroup, sg, false);

    if (entryoutputs.size()) {
        std::cout << "Entry outputs:";
        for (size_t i = 0; i < entryoutputs.size(); ++i) {
            ustring name (entryoutputs[i]);  // convert to ustring
            const ShaderSymbol *sym = shadingsys->find_symbol (*shadergroup, name);
            if (!sym) {
                std::cout << "\nEntry output " << entryoutputs[i] << " not found. Abording.\n";
                exit (EXIT_FAILURE);
            }
            entrylayer_symbols.push_back (sym);
            std::cout << ' ' << entryoutputs[i];
        }
        std::cout << "\n";
    }

    // For each output file specified on the command line...
    for (size_t i = 0;  i < outputfiles.size();  ++i) {
        // Make a ustring version of the output name, for fast manipulation
        outputvarnames.push_back (ustring(outputvars[i]));
        // Start with a NULL ImageBuf pointer
        outputimgs.push_back (NULL);

        // Ask for a pointer to the symbol's data, as computed by this
        // shader.
        TypeDesc t;
        const void *data = shadingsys->get_symbol (*ctx, outputvarnames[i], t);
        if (!data) {
            std::cout << "Output " << outputvars[i] 
                      << " not found, skipping.\n";
            continue;  // Skip if symbol isn't found
        }
        std::cout << "Output " << outputvars[i] << " to "
                  << outputfiles[i] << "\n";

        // And the "base" type, i.e. the type of each element or channel
        TypeDesc tbase = TypeDesc ((TypeDesc::BASETYPE)t.basetype);

        // But which type are we going to write?  Use the true data type
        // from OSL, unless the command line options indicated that
        // something else was desired.
        TypeDesc outtypebase = tbase;
        if (dataformatname == "uint8")
            outtypebase = TypeDesc::UINT8;
        else if (dataformatname == "half")
            outtypebase = TypeDesc::HALF;
        else if (dataformatname == "float")
            outtypebase = TypeDesc::FLOAT;

        // Number of channels to write to the image is the number of (array)
        // elements times the number of channels (e.g. 1 for scalar, 3 for
        // vector, etc.)
        int nchans = t.numelements() * t.aggregate;

        // Make an ImageBuf of the right type and size to hold this
        // symbol's output, and initially clear it to all black pixels.
        OIIO::ImageSpec spec (xres, yres, nchans, TypeDesc::FLOAT);
        outputimgs[i] = new OIIO::ImageBuf(outputfiles[i], spec);
        outputimgs[i]->set_write_format (outtypebase);
        OIIO::ImageBufAlgo::zero (*outputimgs[i]);
    }

    if (outputimgs.empty()) {
        OIIO::ImageSpec spec (xres, yres, 3, TypeDesc::FLOAT);
        outputimgs.push_back(new OIIO::ImageBuf(spec));
    }

    shadingsys->release_context (ctx);  // don't need this anymore for now
}
extern "C" int
test_shade (int argc, const char *argv[])
{
    OIIO::Timer timer;

    // Create a new shading system.  We pass it the RendererServices
    // object that services callbacks from the shading system, NULL for
    // the TextureSystem (that just makes 'create' make its own TS), and
    // an error handler.
    shadingsys = new ShadingSystem (&rend, NULL, &errhandler);
    register_closures(shadingsys);

    // Remember that each shader parameter may optionally have a
    // metadata hint [[int lockgeom=...]], where 0 indicates that the
    // parameter may be overridden by the geometry itself, for example
    // with data interpolated from the mesh vertices, and a value of 1
    // means that it is "locked" with respect to the geometry (i.e. it
    // will not be overridden with interpolated or
    // per-geometric-primitive data).
    // 
    // In order to most fully optimize shader, we typically want any
    // shader parameter not explicitly specified to default to being
    // locked (i.e. no per-geometry override):
    shadingsys->attribute("lockgeom", 1);

    // Now we declare our shader.
    // 
    // Each material in the scene is comprised of a "shader group."
    // Each group is comprised of one or more "layers" (a.k.a. shader
    // instances) with possible connections from outputs of
    // upstream/early layers into the inputs of downstream/later layers.
    // A shader instance is the combination of a reference to a shader
    // master and its parameter values that may override the defaults in
    // the shader source and may be particular to this instance (versus
    // all the other instances of the same shader).
    // 
    // A shader group declaration typically looks like this:
    //
    //   ShaderGroupRef shadergroup = ss->ShaderGroupBegin ();
    //   ss->Parameter ("paramname", TypeDesc paramtype, void *value);
    //      ... and so on for all the other parameters of...
    //   ss->Shader ("shadertype", "shadername", "layername");
    //      The Shader() call creates a new instance, which gets
    //      all the pending Parameter() values made right before it.
    //   ... and other shader instances in this group, interspersed with...
    //   ss->ConnectShaders ("layer1", "param1", "layer2", "param2");
    //   ... and other connections ...
    //   ss->ShaderGroupEnd ();
    // 
    // It looks so simple, and it really is, except that the way this
    // testshade program works is that all the Parameter() and Shader()
    // calls are done inside getargs(), as it walks through the command
    // line arguments, whereas the connections accumulate and have
    // to be processed at the end.  Bear with us.
    
    // Start the shader group and grab a reference to it.
    ShaderGroupRef shadergroup = shadingsys->ShaderGroupBegin ();

    // Get the command line arguments.  That will set up all the shader
    // instances and their parameters for the group.
    getargs (argc, argv);

    // Now set up the connections
    for (size_t i = 0;  i < connections.size();  i += 4) {
        if (i+3 < connections.size()) {
            std::cout << "Connect " 
                      << connections[i] << "." << connections[i+1]
                      << " to " << connections[i+2] << "." << connections[i+3]
                      << "\n";
            shadingsys->ConnectShaders (connections[i].c_str(),
                                        connections[i+1].c_str(),
                                        connections[i+2].c_str(),
                                        connections[i+3].c_str());
        }
    }

    // End the group
    shadingsys->ShaderGroupEnd ();

    if (outputfiles.size() != 0)
        std::cout << "\n";

    // Set up the named transformations, including shader and object.
    // For this test application, we just do this statically; in a real
    // renderer, the global named space (like "myspace") would probably
    // be static, but shader and object spaces may be different for each
    // object.
    setup_transformations (rend, Mshad, Mobj);

    // Set up the image outputs requested on the command line
    setup_output_images (shadingsys, shadergroup);

    if (debug)
        test_group_attributes (shadergroup.get());

    if (num_threads < 1)
        num_threads = boost::thread::hardware_concurrency();

    double setuptime = timer.lap ();

    // Allow a settable number of iterations to "render" the whole image,
    // which is useful for time trials of things that would be too quick
    // to accurately time for a single iteration
    for (int iter = 0;  iter < iters;  ++iter) {
        OIIO::ROI roi (0, xres, 0, yres);
        bool save = (iter == (iters-1));   // save on last iteration

#if 0
        shade_region (shadergroup.get(), roi, save);
#else
        OIIO::ImageBufAlgo::parallel_image (
            boost::bind (shade_region, shadergroup.get(), _1, save),
            roi, num_threads);
#endif

        // If any reparam was requested, do it now
        if (reparams.size() && reparam_layer.size()) {
            for (size_t p = 0;  p < reparams.size();  ++p) {
                const ParamValue &pv (reparams[p]);
                shadingsys->ReParameter (*shadergroup, reparam_layer.c_str(),
                                         pv.name().c_str(), pv.type(),
                                         pv.data());
            }
        }
    }

    if (outputfiles.size() == 0)
        std::cout << "\n";

    // Write the output images to disk
    for (size_t i = 0;  i < outputimgs.size();  ++i) {
        if (outputimgs[i]) {
            outputimgs[i]->save();
            delete outputimgs[i];
            outputimgs[i] = NULL;
        }
    }

    // Print some debugging info
    if (debug || stats) {
        double runtime = timer.lap();
        std::cout << "\n";
        std::cout << "Setup: " << OIIO::Strutil::timeintervalformat (setuptime,2) << "\n";
        std::cout << "Run  : " << OIIO::Strutil::timeintervalformat (runtime,2) << "\n";
        std::cout << "\n";
        std::cout << shadingsys->getstats (5) << "\n";
        OIIO::TextureSystem *texturesys = shadingsys->texturesys();
        if (texturesys)
            std::cout << texturesys->getstats (5) << "\n";
        std::cout << ustring::getstats() << "\n";
    }

    // We're done with the shading system now, destroy it
    shadergroup.reset ();  // Must release this before destroying shadingsys
    delete shadingsys;

    return EXIT_SUCCESS;
}
static void
setup_output_images (ShadingSystem *shadingsys,
                     ShaderGroupRef &shadergroup)
{
    // Tell the shading system which outputs we want
    if (outputvars.size()) {
        std::vector<const char *> aovnames (outputvars.size());
        for (size_t i = 0; i < outputvars.size(); ++i)
            aovnames[i] = outputvars[i].c_str();
        shadingsys->attribute (use_group_outputs ? shadergroup.get() : NULL,
                               "renderer_outputs",
                               TypeDesc(TypeDesc::STRING,(int)aovnames.size()),
                               &aovnames[0]);
        if (use_group_outputs)
            std::cout << "Marking group outputs, not global renderer outputs.\n";
    }

    if (extraoptions.size())
        shadingsys->attribute ("options", extraoptions);

    ShadingContext *ctx = shadingsys->get_context ();
    // Because we can only call get_symbol on something that has been
    // set up to shade (or executed), we call execute() but tell it not
    // to actually run the shader.
    ShaderGlobals sg;
    setup_shaderglobals (sg, shadingsys, 0, 0);
    shadingsys->execute (*ctx, *shadergroup, sg, false);

    // For each output file specified on the command line...
    for (size_t i = 0;  i < outputfiles.size();  ++i) {
        // Make a ustring version of the output name, for fast manipulation
        outputvarnames.push_back (ustring(outputvars[i]));
        // Start with a NULL ImageBuf pointer
        outputimgs.push_back (NULL);

        // Ask for a pointer to the symbol's data, as computed by this
        // shader.
        TypeDesc t;
        const void *data = shadingsys->get_symbol (*ctx, outputvarnames[i], t);
        if (!data) {
            std::cout << "Output " << outputvars[i] 
                      << " not found, skipping.\n";
            continue;  // Skip if symbol isn't found
        }
        std::cout << "Output " << outputvars[i] << " to "
                  << outputfiles[i] << "\n";

        // And the "base" type, i.e. the type of each element or channel
        TypeDesc tbase = TypeDesc ((TypeDesc::BASETYPE)t.basetype);

        // But which type are we going to write?  Use the true data type
        // from OSL, unless the command line options indicated that
        // something else was desired.
        TypeDesc outtypebase = tbase;
        if (dataformatname == "uint8")
            outtypebase = TypeDesc::UINT8;
        else if (dataformatname == "half")
            outtypebase = TypeDesc::HALF;
        else if (dataformatname == "float")
            outtypebase = TypeDesc::FLOAT;

        // Number of channels to write to the image is the number of (array)
        // elements times the number of channels (e.g. 1 for scalar, 3 for
        // vector, etc.)
        int nchans = t.numelements() * t.aggregate;

        // Make an ImageBuf of the right type and size to hold this
        // symbol's output, and initially clear it to all black pixels.
        OIIO::ImageSpec spec (xres, yres, nchans, outtypebase);
        outputimgs[i] = new OIIO::ImageBuf(outputfiles[i], spec);
        OIIO::ImageBufAlgo::zero (*outputimgs[i]);
    }

    shadingsys->release_context (ctx);  // don't need this anymore for now
}