1 // Copyright Contributors to the Open Shading Language project.
2 // SPDX-License-Identifier: BSD-3-Clause
3 // https://github.com/AcademySoftwareFoundation/OpenShadingLanguage
4
5
6 #include <cmath>
7 #include <fstream>
8 #include <iostream>
9 #include <locale>
10 #include <memory>
11 #include <string>
12 #include <vector>
13
14 #include <OpenImageIO/imageio.h>
15 #include <OpenImageIO/imagebuf.h>
16 #include <OpenImageIO/imagebufalgo.h>
17 #include <OpenImageIO/imagebufalgo_util.h>
18 #include <OpenImageIO/imagecache.h>
19 #include <OpenImageIO/argparse.h>
20 #include <OpenImageIO/strutil.h>
21 #include <OpenImageIO/sysutil.h>
22 #include <OpenImageIO/filesystem.h>
23 #include <OpenImageIO/timer.h>
24
25 #ifdef OSL_USE_OPTIX
26 // purely to get optix version -- once optix 7.0 is required this can go away
27 #include <optix.h>
28 #endif
29
30 #include <OSL/oslexec.h>
31 #include <OSL/oslcomp.h>
32 #include <OSL/oslquery.h>
33 #include "optixgridrender.h"
34 #include "simplerend.h"
35
36 using namespace OSL;
37 using OIIO::TypeDesc;
38 using OIIO::ParamValue;
39 using OIIO::ParamValueList;
40
41 static ShadingSystem *shadingsys = NULL;
42 static std::vector<std::string> shadernames;
43 static std::vector<std::string> outputfiles;
44 static std::vector<std::string> outputvars;
45 static std::vector<ustring> outputvarnames;
46 static std::string dataformatname = "";
47 static std::string shaderpath;
48 static std::vector<std::string> entrylayers;
49 static std::vector<std::string> entryoutputs;
50 static std::vector<int> entrylayer_index;
51 static std::vector<const ShaderSymbol *> entrylayer_symbols;
52 static bool debug1 = false;
53 static bool debug2 = false;
54 static bool llvm_debug = false;
55 static bool verbose = false;
56 static bool runstats = false;
57 static bool vary_Pdxdy = false;
58 static bool vary_udxdy = false;
59 static bool vary_vdxdy = false;
60 static bool saveptx = false;
61 static bool warmup = false;
62 static bool profile = false;
63 static bool O0 = false, O1 = false, O2 = false;
64 static bool pixelcenters = false;
65 static bool debugnan = false;
66 static bool debug_uninit = false;
67 static bool use_group_outputs = false;
68 static bool do_oslquery = false;
69 static bool inbuffer = false;
70 static bool use_shade_image = false;
71 static bool userdata_isconnected = false;
72 static bool print_outputs = false;
73 static bool use_optix = OIIO::Strutil::stoi(OIIO::Sysutil::getenv("TESTSHADE_OPTIX"));
74 static int xres = 1, yres = 1;
75 static int num_threads = 0;
76 static std::string groupname;
77 static std::string groupspec;
78 static std::string layername;
79 static std::vector<std::string> connections;
80 static ParamValueList params;
81 static ParamValueList reparams;
82 static std::string reparam_layer;
83 static ErrorHandler errhandler;
84 static int iters = 1;
85 static std::string raytype = "camera";
86 static bool raytype_opt = false;
87 static std::string extraoptions;
88 static std::string texoptions;
89 static OSL::Matrix44 Mshad; // "shader" space to "common" space matrix
90 static OSL::Matrix44 Mobj; // "object" space to "common" space matrix
91 static ShaderGroupRef shadergroup;
92 static std::string archivegroup;
93 static int exprcount = 0;
94 static bool shadingsys_options_set = false;
95 static float uscale = 1, vscale = 1;
96 static float uoffset = 0, voffset = 0;
97 static std::vector<const char*> shader_setup_args;
98 static std::string localename = OIIO::Sysutil::getenv("TESTSHADE_LOCALE");
99 static OIIO::ParamValueList userdata;
100
101
102
103 static void
inject_params()104 inject_params ()
105 {
106 for (auto&& pv : params)
107 shadingsys->Parameter (*shadergroup, pv.name(), pv.type(), pv.data(),
108 pv.interp() == ParamValue::INTERP_CONSTANT);
109 }
110
111
112
113 // Set shading system global attributes based on command line options.
114 static void
set_shadingsys_options()115 set_shadingsys_options ()
116 {
117 // If benchmarking it isn't necessary to clear the memory. however for
118 // unit tests and tracking down early exit issues we may not want the
119 // previous sample's group data masquerading as correct values for the
120 // next sample, who due to a bug, may not have correct control flow and
121 // not actually write to those values.
122 OSL_DEV_ONLY(shadingsys->attribute ("clearmemory", 1));
123
124 // Always generate llvm debugging info
125 shadingsys->attribute ("llvm_debugging_symbols", 1);
126
127 // Always emit llvm Intel profiling events
128 shadingsys->attribute ("llvm_profiling_events", 1);
129
130 OSL_DEV_ONLY(llvm_debug = true);
131 shadingsys->attribute ("llvm_debug", (llvm_debug ? 2 : 0));
132
133 shadingsys->attribute ("debug", debug2 ? 2 : (debug1 ? 1 : 0));
134 shadingsys->attribute ("compile_report", debug1|debug2);
135 int opt = 2; // default
136 if (O0) opt = 0;
137 if (O1) opt = 1;
138 if (O2) opt = 2;
139 if (const char *opt_env = getenv ("TESTSHADE_OPT")) // overrides opt
140 opt = atoi(opt_env);
141 shadingsys->attribute ("optimize", opt);
142 shadingsys->attribute ("profile", int(profile));
143 shadingsys->attribute ("lockgeom", 1);
144 shadingsys->attribute ("debug_nan", debugnan);
145 shadingsys->attribute ("debug_uninit", debug_uninit);
146 shadingsys->attribute ("userdata_isconnected", userdata_isconnected);
147 if (! shaderpath.empty())
148 shadingsys->attribute ("searchpath:shader", shaderpath);
149 if (extraoptions.size())
150 shadingsys->attribute ("options", extraoptions);
151 if (texoptions.size())
152 shadingsys->texturesys()->attribute ("options", texoptions);
153 shadingsys_options_set = true;
154 }
155
156
157
158 static void
compile_buffer(const std::string & sourcecode,const std::string & shadername)159 compile_buffer (const std::string &sourcecode,
160 const std::string &shadername)
161 {
162 // std::cout << "source was\n---\n" << sourcecode << "---\n\n";
163 std::string osobuffer;
164 OSLCompiler compiler;
165 std::vector<std::string> options;
166
167 if (! compiler.compile_buffer (sourcecode, osobuffer, options)) {
168 std::cerr << "Could not compile \"" << shadername << "\"\n";
169 exit (EXIT_FAILURE);
170 }
171 // std::cout << "Compiled to oso:\n---\n" << osobuffer << "---\n\n";
172
173 if (! shadingsys->LoadMemoryCompiledShader (shadername, osobuffer)) {
174 std::cerr << "Could not load compiled buffer from \""
175 << shadername << "\"\n";
176 exit (EXIT_FAILURE);
177 }
178 }
179
180
181
182 static void
shader_from_buffers(std::string shadername)183 shader_from_buffers (std::string shadername)
184 {
185 std::string oslfilename = shadername;
186 if (! OIIO::Strutil::ends_with (oslfilename, ".osl"))
187 oslfilename += ".osl";
188 std::string sourcecode;
189 if (! OIIO::Filesystem::read_text_file (oslfilename, sourcecode)) {
190 std::cerr << "Could not open \"" << oslfilename << "\"\n";
191 exit (EXIT_FAILURE);
192 }
193
194 compile_buffer (sourcecode, shadername);
195 // std::cout << "Read and compiled " << shadername << "\n";
196 }
197
198
199
200 static int
add_shader(int argc,const char * argv[])201 add_shader (int argc, const char *argv[])
202 {
203 OSL_DASSERT(argc == 1);
204 string_view shadername (argv[0]);
205
206 set_shadingsys_options ();
207
208 if (inbuffer) // Request to exercise the buffer-based API calls
209 shader_from_buffers (shadername);
210
211 for (int i = 0; i < argc; i++) {
212 inject_params ();
213 shadernames.push_back (shadername);
214 shadingsys->Shader (*shadergroup, "surface", shadername, layername);
215 layername.clear ();
216 params.clear ();
217 }
218 return 0;
219 }
220
221
222
223 static void
action_shaderdecl(int,const char * argv[])224 action_shaderdecl (int /*argc*/, const char *argv[])
225 {
226 // `--shader shadername layername` is exactly equivalent to:
227 // `--layer layername` followed by naming the shader.
228 layername = argv[2];
229 add_shader (1, argv+1);
230 }
231
232
233
234 // The --expr ARG command line option will take ARG that is a snipped of
235 // OSL source code, embed it in some boilerplate shader wrapper, compile
236 // it from memory, and run that in the same way that would have been done
237 // if it were a compiled shader on disk. The boilerplate assumes that there
238 // are two output parameters for the shader: color result, and float alpha.
239 //
240 // Example use:
241 // testshade -v -g 64 64 -o result out.exr -expr 'result=color(u,v,0);'
242 //
243 static void
specify_expr(int argc OSL_MAYBE_UNUSED,const char * argv[])244 specify_expr (int argc OSL_MAYBE_UNUSED, const char *argv[])
245 {
246 OSL_DASSERT(argc == 2);
247 std::string shadername = OIIO::Strutil::sprintf("expr_%d", exprcount++);
248 std::string sourcecode =
249 "shader " + shadername + " (\n"
250 " float s = u [[ int lockgeom=0 ]],\n"
251 " float t = v [[ int lockgeom=0 ]],\n"
252 " output color result = 0,\n"
253 " output float alpha = 1,\n"
254 " )\n"
255 "{\n"
256 " " + std::string(argv[1]) + "\n"
257 " ;\n"
258 "}\n";
259 if (verbose)
260 std::cout << "Expression-based shader text is:\n---\n"
261 << sourcecode << "---\n";
262
263 set_shadingsys_options ();
264
265 compile_buffer (sourcecode, shadername);
266
267 inject_params ();
268 shadernames.push_back (shadername);
269 shadingsys->Shader (*shadergroup, "surface", shadername, layername);
270 layername.clear ();
271 params.clear ();
272 }
273
274
275
276 // Utility: Add {paramname, stringval} to the given parameter list.
277 static void
add_param(ParamValueList & params,string_view command,string_view paramname,string_view stringval)278 add_param (ParamValueList& params, string_view command,
279 string_view paramname, string_view stringval)
280 {
281 TypeDesc type = TypeDesc::UNKNOWN;
282 bool unlockgeom = false;
283 float f[16];
284
285 size_t pos;
286 while ((pos = command.find_first_of(":")) != std::string::npos) {
287 command = command.substr (pos+1, std::string::npos);
288 std::vector<std::string> splits;
289 OIIO::Strutil::split (command, splits, ":", 1);
290 if (splits.size() < 1) {}
291 else if (OIIO::Strutil::istarts_with(splits[0],"type="))
292 type.fromstring (splits[0].c_str()+5);
293 else if (OIIO::Strutil::istarts_with(splits[0],"lockgeom="))
294 unlockgeom = (OIIO::Strutil::from_string<int> (splits[0]) == 0);
295 }
296
297 // If it is or might be a matrix, look for 16 comma-separated floats
298 if ((type == TypeDesc::UNKNOWN || type == TypeDesc::TypeMatrix)
299 && sscanf (stringval.c_str(),
300 "%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f",
301 &f[0], &f[1], &f[2], &f[3],
302 &f[4], &f[5], &f[6], &f[7], &f[8], &f[9], &f[10], &f[11],
303 &f[12], &f[13], &f[14], &f[15]) == 16) {
304 params.emplace_back (paramname, TypeDesc::TypeMatrix, 1, f);
305 if (unlockgeom)
306 params.back().interp (ParamValue::INTERP_VERTEX);
307 return;
308 }
309 // If it is or might be a vector type, look for 3 comma-separated floats
310 if ((type == TypeDesc::UNKNOWN || equivalent(type,TypeDesc::TypeVector))
311 && sscanf (stringval.c_str(), "%g, %g, %g", &f[0], &f[1], &f[2]) == 3) {
312 if (type == TypeDesc::UNKNOWN)
313 type = TypeDesc::TypeVector;
314 params.emplace_back (paramname, type, 1, f);
315 if (unlockgeom)
316 params.back().interp (ParamValue::INTERP_VERTEX);
317 return;
318 }
319 // If it is or might be an int, look for an int that takes up the whole
320 // string.
321 if ((type == TypeDesc::UNKNOWN || type == TypeDesc::TypeInt)
322 && OIIO::Strutil::string_is<int>(stringval)) {
323 params.emplace_back (paramname, OIIO::Strutil::from_string<int>(stringval));
324 if (unlockgeom)
325 params.back().interp (ParamValue::INTERP_VERTEX);
326 return;
327 }
328 // If it is or might be an float, look for a float that takes up the
329 // whole string.
330 if ((type == TypeDesc::UNKNOWN || type == TypeDesc::TypeFloat)
331 && OIIO::Strutil::string_is<float>(stringval)) {
332 params.emplace_back (paramname, OIIO::Strutil::from_string<float>(stringval));
333 if (unlockgeom)
334 params.back().interp (ParamValue::INTERP_VERTEX);
335 return;
336 }
337
338 // Catch-all for float types and arrays
339 if (type.basetype == TypeDesc::FLOAT) {
340 int n = type.aggregate * type.numelements();
341 std::vector<float> vals (n);
342 for (int i = 0; i < n; ++i) {
343 OIIO::Strutil::parse_float (stringval, vals[i]);
344 OIIO::Strutil::parse_char (stringval, ',');
345 }
346 params.emplace_back (paramname, type, 1, &vals[0]);
347 if (unlockgeom)
348 params.back().interp (ParamValue::INTERP_VERTEX);
349 return;
350 }
351
352 // Catch-all for int types and arrays
353 if (type.basetype == TypeDesc::INT) {
354 int n = type.aggregate * type.numelements();
355 std::vector<int> vals (n);
356 for (int i = 0; i < n; ++i) {
357 OIIO::Strutil::parse_int (stringval, vals[i]);
358 OIIO::Strutil::parse_char (stringval, ',');
359 }
360 params.emplace_back (paramname, type, 1, &vals[0]);
361 if (unlockgeom)
362 params.back().interp (ParamValue::INTERP_VERTEX);
363 return;
364 }
365
366 // String arrays are slightly tricky
367 if (type.basetype == TypeDesc::STRING && type.is_array()) {
368 std::vector<string_view> splitelements;
369 OIIO::Strutil::split (stringval, splitelements, ",", type.arraylen);
370 splitelements.resize (type.arraylen);
371 std::vector<ustring> strelements;
372 for (auto&& s : splitelements)
373 strelements.push_back (ustring(s));
374 params.emplace_back (paramname, type, 1, &strelements[0]);
375 if (unlockgeom)
376 params.back().interp (ParamValue::INTERP_VERTEX);
377 return;
378 }
379
380 // All remaining cases -- it's a string
381 const char *s = stringval.c_str();
382 params.emplace_back (paramname, TypeDesc::TypeString, 1, &s);
383 if (unlockgeom)
384 params.back().interp (ParamValue::INTERP_VERTEX);
385 }
386
387
388
389 static void
action_param(int,const char * argv[])390 action_param(int /*argc*/, const char *argv[])
391 {
392 std::string command = argv[0];
393 bool use_reparam = false;
394 if (OIIO::Strutil::istarts_with(command, "--reparam") ||
395 OIIO::Strutil::istarts_with(command, "-reparam"))
396 use_reparam = true;
397 ParamValueList ¶ms (use_reparam ? reparams : (::params));
398
399 add_param(params, command, argv[1], argv[2]);
400 }
401
402
403
404 // reparam -- just set reparam_layer and then let action_param do all the
405 // hard work.
406 static void
action_reparam(int,const char * argv[])407 action_reparam (int /*argc*/, const char *argv[])
408 {
409 reparam_layer = argv[1];
410 const char *newargv[] = { argv[0], argv[2], argv[3] };
411 action_param (3, newargv);
412 }
413
414
415
416 static void
action_groupspec(int,const char * argv[])417 action_groupspec (int /*argc*/, const char *argv[])
418 {
419 shadingsys->ShaderGroupEnd (*shadergroup);
420 std::string groupspec (argv[1]);
421 if (OIIO::Filesystem::exists (groupspec)) {
422 // If it names a file, use the contents of the file as the group
423 // specification.
424 OIIO::Filesystem::read_text_file (groupspec, groupspec);
425 }
426 set_shadingsys_options ();
427 if (verbose)
428 std::cout << "Processing group specification:\n---\n"
429 << groupspec << "\n---\n";
430 shadergroup = shadingsys->ShaderGroupBegin (groupname, "surface", groupspec);
431 }
432
433
434
435 static void
stash_shader_arg(int argc,const char * argv[])436 stash_shader_arg (int argc, const char* argv[])
437 {
438 for (int i = 0; i < argc; ++i)
439 shader_setup_args.push_back (argv[i]);
440 }
441
442
443
444 static void
stash_userdata(int argc,const char * argv[])445 stash_userdata(int argc, const char* argv[])
446 {
447 add_param(userdata, argv[0], argv[1], argv[2]);
448 }
449
450
451
452 void
print_info()453 print_info()
454 {
455 ErrorHandler errhandler;
456 SimpleRenderer* rend = nullptr;
457 #ifdef OSL_USE_OPTIX
458 if (use_optix)
459 rend = new OptixGridRenderer;
460 else
461 #endif
462 rend = new SimpleRenderer;
463 TextureSystem *texturesys = TextureSystem::create();
464 shadingsys = new ShadingSystem(rend, texturesys, &errhandler);
465 rend->init_shadingsys(shadingsys);
466
467 std::cout << "\n" << shadingsys->getstats (5) << "\n";
468
469 delete shadingsys;
470 delete rend;
471 }
472
473
474
475 static void
getargs(int argc,const char * argv[])476 getargs (int argc, const char *argv[])
477 {
478 static bool help = false;
479
480 // We have a bit of a chicken-and-egg problem here, where some arguments
481 // set up the shader instances, but other args and housekeeping are
482 // needed first. Untangle by just storing the shader setup args until
483 // they can be later processed in full.
484 shader_setup_args.clear();
485 shader_setup_args.push_back("testshade"); // seed with 'program'
486
487 OIIO::ArgParse ap;
488 ap.options ("Usage: testshade [options] shader...",
489 "%*", stash_shader_arg, "",
490 "--help", &help, "Print help message",
491 "-v", &verbose, "Verbose messages",
492 "-t %d", &num_threads, "Render using N threads (default: auto-detect)",
493 "--optix", &use_optix, "Use OptiX if available",
494 "--debug", &debug1, "Lots of debugging info",
495 "--debug2", &debug2, "Even more debugging info",
496 "--llvm_debug", &llvm_debug, "Turn on LLVM debugging info",
497 "--runstats", &runstats, "Print run statistics",
498 "--stats", &runstats, "", // DEPRECATED 1.7
499 "--vary_pdxdy", &vary_Pdxdy, "populate Dx(P) & Dy(P) with varying values (vs. uniform)",
500 "--vary_udxdy", &vary_udxdy, "populate Dx(u) & Dy(u) with varying values (vs. uniform)",
501 "--vary_vdxdy", &vary_vdxdy, "populate Dx(v) & Dy(v) with varying values (vs. uniform)",
502 "--profile", &profile, "Print profile information",
503 "--saveptx", &saveptx, "Save the generated PTX (OptiX mode only)",
504 "--warmup", &warmup, "Perform a warmup launch",
505 "--path %s", &shaderpath, "Specify oso search path",
506 "--res %d %d", &xres, &yres, "Make an W x H image",
507 "-g %d %d", &xres, &yres, "", // synonym for -res
508 "--options %s", &extraoptions, "Set extra OSL options",
509 "--texoptions %s", &texoptions, "Set extra TextureSystem options",
510 "-o %L %L", &outputvars, &outputfiles,
511 "Output (variable, filename) [filename='null' means don't save]",
512 "-d %s", &dataformatname, "Set the output data format to one of: "
513 "uint8, half, float",
514 "-od %s", &dataformatname, "", // old name
515 "--print", &print_outputs, "Print values of all -o outputs to console instead of saving images",
516 "--groupname %s", &groupname, "Set shader group name",
517 "--layer %@ %s", stash_shader_arg, NULL, "Set next layer name",
518 "--param %@ %s %s", stash_shader_arg, NULL, NULL,
519 "Add a parameter (args: name value) (options: type=%s, lockgeom=%d)",
520 "--shader %@ %s %s", stash_shader_arg, NULL, NULL,
521 "Declare a shader node (args: shader layername)",
522 "--connect %@ %s %s %s %s",
523 stash_shader_arg, NULL, NULL, NULL, NULL,
524 "Connect fromlayer fromoutput tolayer toinput",
525 "--reparam %@ %s %s %s", stash_shader_arg, NULL, NULL, NULL,
526 "Change a parameter (args: layername paramname value) (options: type=%s)",
527 "--group %@ %s", stash_shader_arg, NULL,
528 "Specify a full group command",
529 "--archivegroup %s", &archivegroup,
530 "Archive the group to a given filename",
531 "--raytype %s", &raytype, "Set the raytype",
532 "--raytype_opt", &raytype_opt, "Specify ray type mask for optimization",
533 "--iters %d", &iters, "Number of iterations",
534 "-O0", &O0, "Do no runtime shader optimization",
535 "-O1", &O1, "Do a little runtime shader optimization",
536 "-O2", &O2, "Do lots of runtime shader optimization",
537 "--entry %L", &entrylayers, "Add layer to the list of entry points",
538 "--entryoutput %L", &entryoutputs, "Add output symbol to the list of entry points",
539 "--center", &pixelcenters, "Shade at output pixel 'centers' rather than corners",
540 "--debugnan", &debugnan, "Turn on 'debug_nan' mode",
541 "--debuguninit", &debug_uninit, "Turn on 'debug_uninit' mode",
542 "--groupoutputs", &use_group_outputs, "Specify group outputs, not global outputs",
543 "--oslquery", &do_oslquery, "Test OSLQuery at runtime",
544 "--inbuffer", &inbuffer, "Compile osl source from and to buffer",
545 "--shadeimage", &use_shade_image, "Use shade_image utility",
546 "--noshadeimage %!", &use_shade_image, "Don't use shade_image utility",
547 "--expr %@ %s", stash_shader_arg, NULL, "Specify an OSL expression to evaluate",
548 "--offsetuv %f %f", &uoffset, &voffset, "Offset s & t texture coordinates (default: 0 0)",
549 "--offsetst %f %f", &uoffset, &voffset, "", // old name
550 "--scaleuv %f %f", &uscale, &vscale, "Scale s & t texture lookups (default: 1, 1)",
551 "--scalest %f %f", &uscale, &vscale, "", // old name
552 "--userdata %@ %s %s", stash_userdata, nullptr, nullptr,
553 "Add userdata (args: name value) (options: type=%s)",
554 "--userdata_isconnected", &userdata_isconnected, "Consider lockgeom=0 to be isconnected()",
555 "--locale %s", &localename, "Set a different locale",
556 NULL);
557 if (ap.parse(argc, argv) < 0 /*|| (shadernames.empty() && groupspec.empty())*/) {
558 std::cerr << ap.geterror() << std::endl;
559 ap.usage ();
560 exit (EXIT_FAILURE);
561 }
562 if (help) {
563 std::cout << "testshade -- Test Open Shading Language\n"
564 OSL_COPYRIGHT_STRING "\n";
565 ap.usage ();
566 print_info();
567 exit (EXIT_SUCCESS);
568 }
569 }
570
571
572
573 static void
process_shader_setup_args(int argc,const char * argv[])574 process_shader_setup_args (int argc, const char *argv[])
575 {
576 OIIO::ArgParse ap;
577 ap.options ("Usage: testshade [options] shader...",
578 "%*", add_shader, "",
579 "--layer %s", &layername, "Set next layer name",
580 "--param %@ %s %s", &action_param, NULL, NULL,
581 "Add a parameter (args: name value) (options: type=%s, lockgeom=%d)",
582 "--shader %@ %s %s", &action_shaderdecl, NULL, NULL,
583 "Declare a shader node (args: shader layername)",
584 "--connect %L %L %L %L",
585 &connections, &connections, &connections, &connections,
586 "Connect fromlayer fromoutput tolayer toinput",
587 "--reparam %@ %s %s %s", &action_reparam, NULL, NULL, NULL,
588 "Change a parameter (args: layername paramname value) (options: type=%s)",
589 "--group %@ %s", &action_groupspec, &groupspec,
590 "Specify a full group command",
591 "--expr %@ %s", &specify_expr, NULL, "Specify an OSL expression to evaluate",
592 NULL);
593 if (ap.parse(argc, argv) < 0 || (shadernames.empty() && groupspec.empty())) {
594 std::cerr << "ERROR: No shader or group was specified.\n";
595 std::cerr << ap.geterror() << std::endl;
596 std::cerr << "Try `testshade --help` for an explanation of all arguments\n";
597 exit (EXIT_FAILURE);
598 }
599 }
600
601
602
603 // Here we set up transformations. These are just examples, set up so
604 // that our unit tests can transform among spaces in ways that we will
605 // recognize as correct. The "shader" and "object" spaces are required
606 // by OSL and the ShaderGlobals will need to have references to them.
607 // For good measure, we also set up a "myspace" space, registering it
608 // with the RendererServices.
609 //
610 static void
setup_transformations(SimpleRenderer & rend,OSL::Matrix44 & Mshad,OSL::Matrix44 & Mobj)611 setup_transformations (SimpleRenderer &rend, OSL::Matrix44 &Mshad,
612 OSL::Matrix44 &Mobj)
613 {
614 Matrix44 M (1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1);
615 rend.camera_params (M, ustring("perspective"), 90.0f,
616 0.1f, 1000.0f, xres, yres);
617
618 // Make a "shader" space that is translated one unit in x and rotated
619 // 45deg about the z axis.
620 Mshad.makeIdentity ();
621 Mshad.translate (OSL::Vec3 (1.0, 0.0, 0.0));
622 Mshad.rotate (OSL::Vec3 (0.0, 0.0, M_PI_4));
623 // std::cout << "shader-to-common matrix: " << Mshad << "\n";
624
625 // Make an object space that is translated one unit in y and rotated
626 // 90deg about the z axis.
627 Mobj.makeIdentity ();
628 Mobj.translate (OSL::Vec3 (0.0, 1.0, 0.0));
629 Mobj.rotate (OSL::Vec3 (0.0, 0.0, M_PI_2));
630 // std::cout << "object-to-common matrix: " << Mobj << "\n";
631
632 OSL::Matrix44 Mmyspace;
633 Mmyspace.scale (OSL::Vec3 (1.0, 2.0, 1.0));
634 // std::cout << "myspace-to-common matrix: " << Mmyspace << "\n";
635 rend.name_transform ("myspace", Mmyspace);
636 }
637
638
639
640 // Set up the ShaderGlobals fields for pixel (x,y).
641 static void
setup_shaderglobals(ShaderGlobals & sg,ShadingSystem * shadingsys,int x,int y)642 setup_shaderglobals (ShaderGlobals &sg, ShadingSystem *shadingsys,
643 int x, int y)
644 {
645 // Just zero the whole thing out to start
646 memset ((char *)&sg, 0, sizeof(ShaderGlobals));
647
648 // In our SimpleRenderer, the "renderstate" itself just a pointer to
649 // the ShaderGlobals.
650 sg.renderstate = &sg;
651
652 // Set "shader" space to be Mshad. In a real renderer, this may be
653 // different for each shader group.
654 sg.shader2common = OSL::TransformationPtr (&Mshad);
655
656 // Set "object" space to be Mobj. In a real renderer, this may be
657 // different for each object.
658 sg.object2common = OSL::TransformationPtr (&Mobj);
659
660 // Just make it look like all shades are the result of 'raytype' rays.
661 sg.raytype = shadingsys->raytype_bit (ustring (raytype));
662
663 // Set up u,v to vary across the "patch", and also their derivatives.
664 // Note that since u & x, and v & y are aligned, we only need to set
665 // values for dudx and dvdy, we can use the memset above to have set
666 // dvdx and dudy to 0.
667 if (pixelcenters) {
668 // Our patch is like an "image" with shading samples at the
669 // centers of each pixel.
670 sg.u = uscale * (float)(x+0.5f) / xres + uoffset;
671 sg.v = vscale * (float)(y+0.5f) / yres + voffset;
672 if (vary_udxdy) {
673 sg.dudx = 1.0f - sg.u;
674 sg.dudy = sg.u;
675 } else {
676 sg.dudx = uscale / xres;
677 }
678 if (vary_vdxdy) {
679 sg.dvdx = 1.0f - sg.v;
680 sg.dvdy = sg.v;
681 } else {
682 sg.dvdy = vscale / yres;
683 }
684 } else {
685 // Our patch is like a Reyes grid of points, with the border
686 // samples being exactly on u,v == 0 or 1.
687 sg.u = uscale * ((xres == 1) ? 0.5f : (float) x / (xres - 1)) + uoffset;
688 sg.v = vscale * ((yres == 1) ? 0.5f : (float) y / (yres - 1)) + voffset;
689 if (vary_udxdy) {
690 sg.dudx = 1.0f - sg.u;
691 sg.dudy = sg.u;
692 } else {
693 sg.dudx = uscale / std::max (1, xres-1);
694 }
695 if (vary_vdxdy) {
696 sg.dvdx = 1.0f - sg.v;
697 sg.dvdy = sg.v;
698 } else {
699 sg.dvdy = vscale / std::max (1, yres-1);
700 }
701 }
702
703 // Assume that position P is simply (u,v,1), that makes the patch lie
704 // on [0,1] at z=1.
705 sg.P = Vec3 (sg.u, sg.v, 1.0f);
706 // Derivatives with respect to x,y
707 if (vary_Pdxdy) {
708 sg.dPdx = Vec3 (1.0f - sg.u, 1.0f - sg.v, sg.u*0.5);
709 sg.dPdy = Vec3 (1.0f - sg.v, 1.0f - sg.u, sg.v*0.5);
710 } else {
711 sg.dPdx = Vec3 (uscale / std::max (1, xres-1), 0.0f, 0.0f);
712 sg.dPdy = Vec3 (0.0f, vscale / std::max (1, yres-1), 0.0f);
713 }
714 sg.dPdz = Vec3 (0.0f, 0.0f, 0.0f); // just use 0 for volume tangent
715 // Tangents of P with respect to surface u,v
716 sg.dPdu = Vec3 (1.0f, 0.0f, 0.0f);
717 sg.dPdv = Vec3 (0.0f, 1.0f, 0.0f);
718 // That also implies that our normal points to (0,0,1)
719 sg.N = Vec3 (0, 0, 1);
720 sg.Ng = Vec3 (0, 0, 1);
721
722 // Set the surface area of the patch to 1 (which it is). This is
723 // only used for light shaders that call the surfacearea() function.
724 sg.surfacearea = 1;
725 }
726
727
728
729 static void
setup_output_images(SimpleRenderer * rend,ShadingSystem * shadingsys,ShaderGroupRef & shadergroup)730 setup_output_images (SimpleRenderer *rend, ShadingSystem *shadingsys,
731 ShaderGroupRef &shadergroup)
732 {
733 // Tell the shading system which outputs we want
734 if (outputvars.size()) {
735 std::vector<const char *> aovnames (outputvars.size());
736 for (size_t i = 0; i < outputvars.size(); ++i) {
737 ustring varname (outputvars[i]);
738 aovnames[i] = varname.c_str();
739 size_t dot = varname.find('.');
740 if (dot != ustring::npos) {
741 // If the name contains a dot, it's intended to be layer.symbol
742 varname = ustring (varname, dot+1);
743 }
744 }
745 shadingsys->attribute (use_group_outputs ? shadergroup.get() : NULL,
746 "renderer_outputs",
747 TypeDesc(TypeDesc::STRING,(int)aovnames.size()),
748 &aovnames[0]);
749 if (use_group_outputs)
750 std::cout << "Marking group outputs, not global renderer outputs.\n";
751 }
752
753 if (entrylayers.size()) {
754 std::vector<const char *> layers;
755 std::cout << "Entry layers:";
756 for (size_t i = 0; i < entrylayers.size(); ++i) {
757 ustring layername (entrylayers[i]); // convert to ustring
758 int layid = shadingsys->find_layer (*shadergroup, layername);
759 layers.push_back (layername.c_str());
760 entrylayer_index.push_back (layid);
761 std::cout << ' ' << entrylayers[i] << "(" << layid << ")";
762 }
763 std::cout << "\n";
764 shadingsys->attribute (shadergroup.get(), "entry_layers",
765 TypeDesc(TypeDesc::STRING,(int)entrylayers.size()),
766 &layers[0]);
767 }
768
769 OSL::PerThreadInfo *thread_info = shadingsys->create_thread_info();
770 ShadingContext *ctx = shadingsys->get_context(thread_info);
771 // Because we can only call find_symbol or get_symbol on something that
772 // has been set up to shade (or executed), we call execute() but tell it
773 // not to actually run the shader.
774 ShaderGlobals sg;
775 setup_shaderglobals (sg, shadingsys, 0, 0);
776
777 int raytype_bit = shadingsys->raytype_bit (ustring (raytype));
778 if (raytype_opt)
779 shadingsys->optimize_group (shadergroup.get(), raytype_bit, ~raytype_bit, ctx);
780 shadingsys->execute (*ctx, *shadergroup, sg, false);
781
782 if (entryoutputs.size()) {
783 std::cout << "Entry outputs:";
784 for (size_t i = 0; i < entryoutputs.size(); ++i) {
785 ustring name (entryoutputs[i]); // convert to ustring
786 const ShaderSymbol *sym = shadingsys->find_symbol (*shadergroup, name);
787 if (!sym) {
788 std::cout << "\nEntry output " << entryoutputs[i] << " not found. Abording.\n";
789 exit (EXIT_FAILURE);
790 }
791 entrylayer_symbols.push_back (sym);
792 std::cout << ' ' << entryoutputs[i];
793 }
794 std::cout << "\n";
795 }
796
797 // For each output file specified on the command line...
798 for (size_t i = 0; i < outputfiles.size(); ++i) {
799 // Make a ustring version of the output name, for fast manipulation
800 outputvarnames.emplace_back(outputvars[i]);
801
802 // Ask for a pointer to the symbol's data, as computed by this
803 // shader.
804 TypeDesc t;
805 const void *data = shadingsys->get_symbol (*ctx, outputvarnames[i], t);
806 if (!data) {
807 std::cout << "Output " << outputvars[i]
808 << " not found, skipping.\n";
809 continue; // Skip if symbol isn't found
810 }
811 std::cout << "Output " << outputvars[i] << " to "
812 << outputfiles[i] << "\n";
813
814 if (outputfiles[i] == "null") {
815 // Filename "null" means to consider this a "renderer output",
816 // but not save it in an image file.
817 continue;
818 }
819
820 // And the "base" type, i.e. the type of each element or channel
821 TypeDesc tbase = TypeDesc ((TypeDesc::BASETYPE)t.basetype);
822
823 // But which type are we going to write? Use the true data type
824 // from OSL, unless the command line options indicated that
825 // something else was desired.
826 TypeDesc outtypebase = tbase;
827 if (dataformatname == "uint8")
828 outtypebase = TypeDesc::UINT8;
829 else if (dataformatname == "half")
830 outtypebase = TypeDesc::HALF;
831 else if (dataformatname == "float")
832 outtypebase = TypeDesc::FLOAT;
833
834 // Number of channels to write to the image is the number of (array)
835 // elements times the number of channels (e.g. 1 for scalar, 3 for
836 // vector, etc.)
837 int nchans = t.numelements() * t.aggregate;
838
839 // Make an ImageBuf of the right type and size to hold this
840 // symbol's output, and initially clear it to all black pixels.
841 rend->add_output (outputvars[i], outputfiles[i], tbase, nchans);
842 }
843
844 if (! rend->noutputs()) {
845 rend->add_output ("Cout", "Cout.tif", OIIO::TypeFloat, 3);
846 }
847
848 shadingsys->release_context (ctx); // don't need this anymore for now
849 shadingsys->destroy_thread_info(thread_info);
850 }
851
852
853
854 // For pixel (x,y) that was just shaded by the given shading context,
855 // save each of the requested outputs to the corresponding output
856 // ImageBuf.
857 //
858 // In a real renderer, this is illustrative of how you would pull shader
859 // outputs into "AOV's" (arbitrary output variables, or additional
860 // renderer outputs). You would, of course, also grab the closure Ci
861 // and integrate the lights using that BSDF to determine the radiance
862 // in the direction of the camera for that pixel.
863 static void
save_outputs(SimpleRenderer * rend,ShadingSystem * shadingsys,ShadingContext * ctx,int x,int y)864 save_outputs (SimpleRenderer *rend, ShadingSystem *shadingsys,
865 ShadingContext *ctx, int x, int y)
866 {
867 if (print_outputs)
868 printf ("Pixel (%d, %d):\n", x, y);
869 // For each output requested on the command line...
870 for (size_t i = 0, e = rend->noutputs(); i < e; ++i) {
871 // Skip if we couldn't open the image or didn't match a known output
872 OIIO::ImageBuf* outputimg = rend->outputbuf(i);
873 if (! outputimg)
874 continue;
875
876 // Ask for a pointer to the symbol's data, as computed by this
877 // shader.
878 TypeDesc t;
879 const void *data = shadingsys->get_symbol (*ctx, rend->outputname(i), t);
880 if (!data)
881 continue; // Skip if symbol isn't found
882
883 int nchans = outputimg->nchannels();
884 if (t.basetype == TypeDesc::FLOAT) {
885 // If the variable we are outputting is float-based, set it
886 // directly in the output buffer.
887 outputimg->setpixel (x, y, (const float *)data);
888 if (print_outputs) {
889 printf (" %s :", outputvarnames[i].c_str());
890 for (int c = 0; c < nchans; ++c)
891 printf (" %g", ((const float *)data)[c]);
892 printf ("\n");
893 }
894 } else if (t.basetype == TypeDesc::INT) {
895 // We are outputting an integer variable, so we need to
896 // convert it to floating point.
897 float *pixel = OIIO_ALLOCA(float, nchans);
898 OIIO::convert_types (TypeDesc::BASETYPE(t.basetype), data,
899 TypeDesc::FLOAT, pixel, nchans);
900 outputimg->setpixel (x, y, &pixel[0]);
901 if (print_outputs) {
902 printf (" %s :", outputvarnames[i].c_str());
903 for (int c = 0; c < nchans; ++c)
904 printf (" %d", ((const int *)data)[c]);
905 printf ("\n");
906 }
907 }
908 // N.B. Drop any outputs that aren't float- or int-based
909 }
910 }
911
912
913
914 static void
test_group_attributes(ShaderGroup * group)915 test_group_attributes (ShaderGroup *group)
916 {
917 int nt = 0;
918 if (shadingsys->getattribute (group, "num_textures_needed", nt)) {
919 std::cout << "Need " << nt << " textures:\n";
920 ustring *tex = NULL;
921 shadingsys->getattribute (group, "textures_needed",
922 TypeDesc::PTR, &tex);
923 for (int i = 0; i < nt; ++i)
924 std::cout << " " << tex[i] << "\n";
925 int unk = 0;
926 shadingsys->getattribute (group, "unknown_textures_needed", unk);
927 if (unk)
928 std::cout << " and unknown textures\n";
929 }
930 int nclosures = 0;
931 if (shadingsys->getattribute (group, "num_closures_needed", nclosures)) {
932 std::cout << "Need " << nclosures << " closures:\n";
933 ustring *closures = NULL;
934 shadingsys->getattribute (group, "closures_needed",
935 TypeDesc::PTR, &closures);
936 for (int i = 0; i < nclosures; ++i)
937 std::cout << " " << closures[i] << "\n";
938 int unk = 0;
939 shadingsys->getattribute (group, "unknown_closures_needed", unk);
940 if (unk)
941 std::cout << " and unknown closures\n";
942 }
943 int nglobals = 0;
944 if (shadingsys->getattribute (group, "num_globals_needed", nglobals)) {
945 std::cout << "Need " << nglobals << " globals: ";
946 ustring *globals = NULL;
947 shadingsys->getattribute (group, "globals_needed",
948 TypeDesc::PTR, &globals);
949 for (int i = 0; i < nglobals; ++i)
950 std::cout << " " << globals[i];
951 std::cout << "\n";
952 }
953
954 int globals_read = 0;
955 int globals_write = 0;
956 shadingsys->getattribute (group, "globals_read", globals_read);
957 shadingsys->getattribute (group, "globals_write", globals_write);
958 std::cout << "Globals read: (" << globals_read << ") ";
959 for (int i = 1; i < int(SGBits::last); i <<= 1)
960 if (globals_read & i)
961 std::cout << ' ' << shadingsys->globals_name (SGBits(i));
962 std::cout << "\nGlobals written: (" << globals_write << ") ";
963 for (int i = 1; i < int(SGBits::last); i <<= 1)
964 if (globals_write & i)
965 std::cout << ' ' << shadingsys->globals_name (SGBits(i));
966 std::cout << "\n";
967
968 int nuser = 0;
969 if (shadingsys->getattribute (group, "num_userdata", nuser) && nuser) {
970 std::cout << "Need " << nuser << " user data items:\n";
971 ustring *userdata_names = NULL;
972 TypeDesc *userdata_types = NULL;
973 int *userdata_offsets = NULL;
974 bool *userdata_derivs = NULL;
975 shadingsys->getattribute (group, "userdata_names",
976 TypeDesc::PTR, &userdata_names);
977 shadingsys->getattribute (group, "userdata_types",
978 TypeDesc::PTR, &userdata_types);
979 shadingsys->getattribute (group, "userdata_offsets",
980 TypeDesc::PTR, &userdata_offsets);
981 shadingsys->getattribute (group, "userdata_derivs",
982 TypeDesc::PTR, &userdata_derivs);
983 OSL_DASSERT(userdata_names && userdata_types && userdata_offsets);
984 for (int i = 0; i < nuser; ++i)
985 std::cout << " " << userdata_names[i] << ' '
986 << userdata_types[i] << " offset="
987 << userdata_offsets[i] << " deriv="
988 << userdata_derivs[i] << "\n";
989 }
990 int nattr = 0;
991 if (shadingsys->getattribute (group, "num_attributes_needed", nattr) && nattr) {
992 std::cout << "Need " << nattr << " attributes:\n";
993 ustring *names = NULL;
994 ustring *scopes = NULL;
995 shadingsys->getattribute (group, "attributes_needed",
996 TypeDesc::PTR, &names);
997 shadingsys->getattribute (group, "attribute_scopes",
998 TypeDesc::PTR, &scopes);
999 OSL_DASSERT(names && scopes);
1000 for (int i = 0; i < nattr; ++i)
1001 std::cout << " " << names[i] << ' '
1002 << scopes[i] << "\n";
1003
1004 int unk = 0;
1005 shadingsys->getattribute (group, "unknown_attributes_needed", unk);
1006 if (unk)
1007 std::cout << " and unknown attributes\n";
1008 }
1009 int raytype_queries = 0;
1010 shadingsys->getattribute (group, "raytype_queries", raytype_queries);
1011 std::cout << "raytype() query mask: " << raytype_queries << "\n";
1012 }
1013
1014
1015
1016 void
shade_region(SimpleRenderer * rend,ShaderGroup * shadergroup,OIIO::ROI roi,bool save)1017 shade_region (SimpleRenderer *rend, ShaderGroup *shadergroup,
1018 OIIO::ROI roi, bool save)
1019 {
1020 // Request an OSL::PerThreadInfo for this thread.
1021 OSL::PerThreadInfo *thread_info = shadingsys->create_thread_info();
1022
1023 // Request a shading context so that we can execute the shader.
1024 // We could get_context/release_context for each shading point,
1025 // but to save overhead, it's more efficient to reuse a context
1026 // within a thread.
1027 ShadingContext *ctx = shadingsys->get_context (thread_info);
1028
1029 // Set up shader globals and a little test grid of points to shade.
1030 ShaderGlobals shaderglobals;
1031
1032 // Loop over all pixels in the image (in x and y)...
1033 for (int y = roi.ybegin; y < roi.yend; ++y) {
1034 for (int x = roi.xbegin; x < roi.xend; ++x) {
1035 // In a real renderer, this is where you would figure
1036 // out what object point is visible in this pixel (or
1037 // this sample, for antialiasing). Once determined,
1038 // you'd set up a ShaderGlobals that contained the vital
1039 // information about that point, such as its location,
1040 // the normal there, the u and v coordinates on the
1041 // surface, the transformation of that object, and so
1042 // on.
1043 //
1044 // This test app is not a real renderer, so we just
1045 // set it up rigged to look like we're rendering a single
1046 // quadrilateral that exactly fills the viewport, and that
1047 // setup is done in the following function call:
1048 setup_shaderglobals (shaderglobals, shadingsys, x, y);
1049
1050 // Actually run the shader for this point
1051 if (entrylayer_index.empty()) {
1052 // Sole entry point for whole group, default behavior
1053 shadingsys->execute (*ctx, *shadergroup, shaderglobals);
1054 } else {
1055 // Explicit list of entries to call in order
1056 shadingsys->execute_init (*ctx, *shadergroup, shaderglobals);
1057 if (entrylayer_symbols.size()) {
1058 for (size_t i = 0, e = entrylayer_symbols.size(); i < e; ++i)
1059 shadingsys->execute_layer (*ctx, shaderglobals, entrylayer_symbols[i]);
1060 } else {
1061 for (size_t i = 0, e = entrylayer_index.size(); i < e; ++i)
1062 shadingsys->execute_layer (*ctx, shaderglobals, entrylayer_index[i]);
1063 }
1064 shadingsys->execute_cleanup (*ctx);
1065 }
1066
1067 // Save all the designated outputs. But only do so if we
1068 // are on the last iteration requested, so that if we are
1069 // doing a bunch of iterations for time trials, we only
1070 // including the output pixel copying once in the timing.
1071 if (save)
1072 save_outputs (rend, shadingsys, ctx, x, y);
1073 }
1074 }
1075
1076 // We're done shading with this context.
1077 shadingsys->release_context (ctx);
1078 shadingsys->destroy_thread_info(thread_info);
1079 }
1080
1081
synchio()1082 static void synchio() {
1083 // Synch all writes to stdout & stderr now (mostly for Windows)
1084 std::cout.flush();
1085 std::cerr.flush();
1086 fflush(stdout);
1087 fflush(stderr);
1088 }
1089
1090 extern "C" OSL_DLL_EXPORT int
test_shade(int argc,const char * argv[])1091 test_shade (int argc, const char *argv[])
1092 {
1093 OIIO::Timer timer;
1094
1095 // Get the command line arguments. Those that set up the shader
1096 // instances are queued up in shader_setup_args for later handling.
1097 getargs (argc, argv);
1098
1099 // For testing purposes, allow user to set global locale
1100 if (localename.size()) {
1101 std::locale::global (std::locale(localename.c_str()));
1102 if (debug1 || verbose)
1103 printf("testshade: locale '%s', floats look like: %g\n",
1104 localename.c_str(), 3.5);
1105 }
1106
1107 SimpleRenderer *rend = nullptr;
1108 #ifdef OSL_USE_OPTIX
1109 if (use_optix)
1110 rend = new OptixGridRenderer;
1111 else
1112 #endif
1113 rend = new SimpleRenderer;
1114
1115 // Other renderer and global options
1116 if (debug1 || verbose)
1117 rend->errhandler().verbosity (ErrorHandler::VERBOSE);
1118 rend->attribute("saveptx", (int)saveptx);
1119
1120 // Hand the userdata options from the command line over to the renderer
1121 #if OIIO_VERSION >= 20202
1122 rend->userdata.merge(userdata);
1123 #else
1124 rend->userdata = userdata;
1125 #endif
1126
1127 // Request a TextureSystem (by default it will be the global shared
1128 // one). This isn't strictly necessary, if you pass nullptr to
1129 // ShadingSystem ctr, it will ask for the shared one internally.
1130 TextureSystem *texturesys = TextureSystem::create();
1131
1132 // Create a new shading system. We pass it the RendererServices
1133 // object that services callbacks from the shading system, the
1134 // TextureSystem (note: passing nullptr just makes the ShadingSystem
1135 // make its own TS), and an error handler.
1136 shadingsys = new ShadingSystem (rend, texturesys, &errhandler);
1137 rend->init_shadingsys (shadingsys);
1138
1139 // Register the layout of all closures known to this renderer
1140 // Any closure used by the shader which is not registered, or
1141 // registered with a different number of arguments will lead
1142 // to a runtime error.
1143 register_closures(shadingsys);
1144
1145 // Remember that each shader parameter may optionally have a
1146 // metadata hint [[int lockgeom=...]], where 0 indicates that the
1147 // parameter may be overridden by the geometry itself, for example
1148 // with data interpolated from the mesh vertices, and a value of 1
1149 // means that it is "locked" with respect to the geometry (i.e. it
1150 // will not be overridden with interpolated or
1151 // per-geometric-primitive data).
1152 //
1153 // In order to most fully optimize the shader, we typically want any
1154 // shader parameter not explicitly specified to default to being
1155 // locked (i.e. no per-geometry override):
1156 shadingsys->attribute("lockgeom", 1);
1157
1158 // Now we declare our shader.
1159 //
1160 // Each material in the scene is comprised of a "shader group."
1161 // Each group is comprised of one or more "layers" (a.k.a. shader
1162 // instances) with possible connections from outputs of
1163 // upstream/early layers into the inputs of downstream/later layers.
1164 // A shader instance is the combination of a reference to a shader
1165 // master and its parameter values that may override the defaults in
1166 // the shader source and may be particular to this instance (versus
1167 // all the other instances of the same shader).
1168 //
1169 // A shader group declaration typically looks like this:
1170 //
1171 // ShaderGroupRef group = ss->ShaderGroupBegin ();
1172 // ss->Parameter (*group, "paramname", TypeDesc paramtype, void *value);
1173 // ... and so on for all the other parameters of...
1174 // ss->Shader (*group, "shadertype", "shadername", "layername");
1175 // The Shader() call creates a new instance, which gets
1176 // all the pending Parameter() values made right before it.
1177 // ... and other shader instances in this group, interspersed with...
1178 // ss->ConnectShaders (*group, "layer1", "param1", "layer2", "param2");
1179 // ... and other connections ...
1180 // ss->ShaderGroupEnd (*group);
1181 //
1182 // It looks so simple, and it really is, except that the way this
1183 // testshade program works is that all the Parameter() and Shader()
1184 // calls are done inside getargs(), as it walks through the command
1185 // line arguments, whereas the connections accumulate and have
1186 // to be processed at the end. Bear with us.
1187
1188 // Start the shader group and grab a reference to it.
1189 shadergroup = shadingsys->ShaderGroupBegin (groupname);
1190
1191 // Revisit the command line arguments that we stashed to set up the
1192 // shader itself.
1193 process_shader_setup_args ((int)shader_setup_args.size(),
1194 shader_setup_args.data());
1195 if (params.size()) {
1196 std::cerr << "ERROR: Pending parameters without a shader:";
1197 for (auto&& pv : params)
1198 std::cerr << " " << pv.name();
1199 std::cerr << "\n";
1200 std::cerr << "Did you mistakenly put --param after the shader declaration?\n";
1201 return EXIT_FAILURE;
1202 }
1203
1204 if (! shadergroup) {
1205 std::cerr << "ERROR: Invalid shader group. Exiting testshade.\n";
1206 return EXIT_FAILURE;
1207 }
1208
1209 // Set shading sys options again, in case late-encountered command line
1210 // options change their values.
1211 set_shadingsys_options ();
1212
1213 if (groupname.size())
1214 shadingsys->attribute (shadergroup.get(), "groupname", groupname);
1215
1216 // Now set up the connections
1217 for (size_t i = 0; i < connections.size(); i += 4) {
1218 if (i+3 < connections.size()) {
1219 std::cout << "Connect "
1220 << connections[i] << "." << connections[i+1]
1221 << " to " << connections[i+2] << "." << connections[i+3]
1222 << "\n";
1223 synchio();
1224 bool ok = shadingsys->ConnectShaders (*shadergroup,
1225 connections[i],
1226 connections[i+1],
1227 connections[i+2],
1228 connections[i+3]);
1229 if (!ok) {
1230 return EXIT_FAILURE;
1231 }
1232 }
1233 }
1234
1235 // End the group
1236 shadingsys->ShaderGroupEnd (*shadergroup);
1237
1238 if (verbose || do_oslquery) {
1239 std::string pickle;
1240 shadingsys->getattribute (shadergroup.get(), "pickle", pickle);
1241 std::cout << "Shader group:\n---\n" << pickle << "\n---\n";
1242 std::cout << "\n";
1243 ustring groupname;
1244 shadingsys->getattribute (shadergroup.get(), "groupname", groupname);
1245 std::cout << "Shader group \"" << groupname << "\" layers are:\n";
1246 int num_layers = 0;
1247 shadingsys->getattribute (shadergroup.get(), "num_layers", num_layers);
1248 if (num_layers > 0) {
1249 std::vector<const char *> layers (size_t(num_layers), NULL);
1250 shadingsys->getattribute (shadergroup.get(), "layer_names",
1251 TypeDesc(TypeDesc::STRING, num_layers),
1252 &layers[0]);
1253 for (int i = 0; i < num_layers; ++i) {
1254 std::cout << " " << (layers[i] ? layers[i] : "<unnamed>") << "\n";
1255 if (do_oslquery) {
1256 OSLQuery q;
1257 q.init (shadergroup.get(), i);
1258 for (size_t p = 0; p < q.nparams(); ++p) {
1259 const OSLQuery::Parameter *param = q.getparam(p);
1260 std::cout << "\t" << (param->isoutput ? "output " : "")
1261 << param->type << ' ' << param->name << "\n";
1262 }
1263 }
1264 }
1265 }
1266 std::cout << "\n";
1267 }
1268 if (archivegroup.size())
1269 shadingsys->archive_shadergroup (shadergroup.get(), archivegroup);
1270
1271 if (outputfiles.size() != 0)
1272 std::cout << "\n";
1273
1274 rend->shaders().push_back (shadergroup);
1275
1276 // Set up the named transformations, including shader and object.
1277 // For this test application, we just do this statically; in a real
1278 // renderer, the global named space (like "myspace") would probably
1279 // be static, but shader and object spaces may be different for each
1280 // object.
1281 setup_transformations (*rend, Mshad, Mobj);
1282
1283 #ifdef OSL_USE_OPTIX
1284 #if (OPTIX_VERSION >= 70000)
1285 if (use_optix)
1286 reinterpret_cast<OptixGridRenderer *> (rend)->synch_attributes();
1287 #endif
1288 #endif
1289
1290 // Set up the image outputs requested on the command line
1291 setup_output_images (rend, shadingsys, shadergroup);
1292
1293 if (debug1)
1294 test_group_attributes (shadergroup.get());
1295
1296 if (num_threads < 1)
1297 num_threads = OIIO::Sysutil::hardware_concurrency();
1298
1299 synchio();
1300
1301 rend->prepare_render ();
1302
1303 double setuptime = timer.lap ();
1304
1305 if (warmup)
1306 rend->warmup();
1307 double warmuptime = timer.lap ();
1308
1309 // Allow a settable number of iterations to "render" the whole image,
1310 // which is useful for time trials of things that would be too quick
1311 // to accurately time for a single iteration
1312 for (int iter = 0; iter < iters; ++iter) {
1313 OIIO::ROI roi (0, xres, 0, yres);
1314
1315 if (use_optix) {
1316 rend->render (xres, yres);
1317 } else if (use_shade_image) {
1318 OSL::shade_image (*shadingsys, *shadergroup, NULL,
1319 *rend->outputbuf(0), outputvarnames,
1320 pixelcenters ? ShadePixelCenters : ShadePixelGrid,
1321 roi, num_threads);
1322 } else {
1323 bool save = (iter == (iters-1)); // save on last iteration
1324 #if 0
1325 shade_region (rend, shadergroup.get(), roi, save);
1326 #else
1327 OIIO::ImageBufAlgo::parallel_image (roi, num_threads,
1328 std::bind (shade_region, rend, shadergroup.get(), std::placeholders::_1, save));
1329 #endif
1330 }
1331
1332 // If any reparam was requested, do it now
1333 if (reparams.size() && reparam_layer.size()) {
1334 for (size_t p = 0; p < reparams.size(); ++p) {
1335 const ParamValue &pv (reparams[p]);
1336 shadingsys->ReParameter (*shadergroup, reparam_layer.c_str(),
1337 pv.name().c_str(), pv.type(),
1338 pv.data());
1339 }
1340 }
1341 }
1342 double runtime = timer.lap();
1343
1344 if (outputfiles.size() == 0)
1345 std::cout << "\n";
1346
1347 // Write the output images to disk
1348 rend->finalize_pixel_buffer ();
1349 for (size_t i = 0; i < rend->noutputs(); ++i) {
1350 OIIO::ImageBuf* outputimg = rend->outputbuf(i);
1351 if (outputimg) {
1352 if (! print_outputs) {
1353 std::string filename = outputimg->name();
1354 TypeDesc datatype = outputimg->spec().format;
1355 if (dataformatname == "uint8")
1356 datatype = TypeDesc::UINT8;
1357 else if (dataformatname == "half")
1358 datatype = TypeDesc::HALF;
1359 else if (dataformatname == "float")
1360 datatype = TypeDesc::FLOAT;
1361
1362 // JPEG, GIF, and PNG images should be automatically saved
1363 // as sRGB because they are almost certainly supposed to
1364 // be displayed on web pages.
1365 using namespace OIIO;
1366 if (Strutil::iends_with (filename, ".jpg") ||
1367 Strutil::iends_with (filename, ".jpeg") ||
1368 Strutil::iends_with (filename, ".gif") ||
1369 Strutil::iends_with (filename, ".png")) {
1370 ImageBuf ccbuf;
1371 ImageBufAlgo::colorconvert (ccbuf, *outputimg,
1372 "linear", "sRGB", false,
1373 "", "");
1374 ccbuf.set_write_format (datatype);
1375 ccbuf.write (filename);
1376 } else {
1377 outputimg->set_write_format (datatype);
1378 outputimg->write (filename);
1379 }
1380 }
1381 // outputimg->reset();
1382 }
1383 }
1384
1385 // Print some debugging info
1386 if (debug1 || runstats || profile) {
1387 double writetime = timer.lap();
1388 std::cout << "\n";
1389 std::cout << "Setup : " << OIIO::Strutil::timeintervalformat (setuptime,4) << "\n";
1390 std::cout << "Warmup: " << OIIO::Strutil::timeintervalformat (warmuptime,4) << "\n";
1391 std::cout << "Run : " << OIIO::Strutil::timeintervalformat (runtime,4) << "\n";
1392 std::cout << "Write : " << OIIO::Strutil::timeintervalformat (writetime,4) << "\n";
1393 std::cout << "\n";
1394 std::cout << shadingsys->getstats (5) << "\n";
1395 OIIO::TextureSystem *texturesys = shadingsys->texturesys();
1396 if (texturesys)
1397 std::cout << texturesys->getstats (5) << "\n";
1398 std::cout << ustring::getstats() << "\n";
1399 }
1400
1401 // Give the renderer a chance to do initial cleanup while everything is still alive
1402 rend->clear();
1403
1404 // We're done with the shading system now, destroy it
1405 shadergroup.reset (); // Must release this before destroying shadingsys
1406
1407 delete shadingsys;
1408 int retcode = EXIT_SUCCESS;
1409
1410 // Double check that there were no uncaught errors in the texture
1411 // system and image cache.
1412 std::string err = texturesys->geterror();
1413 if (!err.empty()) {
1414 std::cout << "ERRORS left in TextureSystem:\n" << err << "\n";
1415 retcode = EXIT_FAILURE;
1416 }
1417 OIIO::ImageCache *ic = texturesys->imagecache();
1418 err = ic ? ic->geterror() : std::string();
1419 if (!err.empty()) {
1420 std::cout << "ERRORS left in ImageCache:\n" << err << "\n";
1421 retcode = EXIT_FAILURE;
1422 }
1423
1424 delete rend;
1425
1426 return retcode;
1427 }
1428