forked from AcademySoftwareFoundation/OpenShadingLanguage
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtestshade.cpp
More file actions
1216 lines (1074 loc) · 48.1 KB
/
testshade.cpp
File metadata and controls
1216 lines (1074 loc) · 48.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
Copyright (c) 2009-2010 Sony Pictures Imageworks Inc., et al.
All Rights Reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of Sony Pictures Imageworks nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <cmath>
#include <OpenImageIO/imageio.h>
#include <OpenImageIO/imagebuf.h>
#include <OpenImageIO/imagebufalgo.h>
#include <OpenImageIO/imagebufalgo_util.h>
#include <OpenImageIO/argparse.h>
#include <OpenImageIO/strutil.h>
#include <OpenImageIO/sysutil.h>
#include <OpenImageIO/filesystem.h>
#include <OpenImageIO/timer.h>
#include <OSL/oslexec.h>
#include <OSL/oslcomp.h>
#include <OSL/oslquery.h>
#include "simplerend.h"
using namespace OSL;
using OIIO::TypeDesc;
using OIIO::ParamValue;
using OIIO::ParamValueList;
static ShadingSystem *shadingsys = NULL;
static std::vector<std::string> shadernames;
static std::vector<std::string> outputfiles;
static std::vector<std::string> outputvars;
static std::vector<ustring> outputvarnames;
static std::vector<OIIO::ImageBuf*> outputimgs;
static std::string dataformatname = "";
static std::string shaderpath;
static std::vector<std::string> entrylayers;
static std::vector<std::string> entryoutputs;
static std::vector<int> entrylayer_index;
static std::vector<const ShaderSymbol *> entrylayer_symbols;
static bool debug = false;
static bool debug2 = false;
static bool verbose = false;
static bool runstats = false;
static int profile = 0;
static bool O0 = false, O1 = false, O2 = false;
static bool pixelcenters = false;
static bool debugnan = false;
static bool debug_uninit = false;
static bool use_group_outputs = false;
static bool do_oslquery = false;
static bool inbuffer = false;
static bool use_shade_image = false;
static bool userdata_isconnected = false;
static bool print_outputs = false;
static int xres = 1, yres = 1;
static int num_threads = 0;
static std::string groupname;
static std::string groupspec;
static std::string layername;
static std::vector<std::string> connections;
static ParamValueList params;
static ParamValueList reparams;
static std::string reparam_layer;
static ErrorHandler errhandler;
static int iters = 1;
static std::string raytype = "camera";
static int raytype_bit = 0;
static bool raytype_opt = false;
static std::string extraoptions;
static std::string texoptions;
static SimpleRenderer rend; // RendererServices
static OSL::Matrix44 Mshad; // "shader" space to "common" space matrix
static OSL::Matrix44 Mobj; // "object" space to "common" space matrix
static ShaderGroupRef shadergroup;
static std::string archivegroup;
static int exprcount = 0;
static bool shadingsys_options_set = false;
static float uscale = 1, vscale = 1;
static float uoffset = 0, voffset = 0;
static void
inject_params ()
{
for (auto&& pv : params)
shadingsys->Parameter (pv.name(), pv.type(), pv.data(),
pv.interp() == ParamValue::INTERP_CONSTANT);
}
static void
set_shadingsys_options ()
{
if (shadingsys_options_set)
return;
shadingsys->attribute ("debug", debug2 ? 2 : (debug ? 1 : 0));
shadingsys->attribute ("compile_report", debug|debug2);
int opt = 2; // default
if (O0) opt = 0;
if (O1) opt = 1;
if (O2) opt = 2;
if (const char *opt_env = getenv ("TESTSHADE_OPT")) // overrides opt
opt = atoi(opt_env);
shadingsys->attribute ("optimize", opt);
shadingsys->attribute ("lockgeom", 1);
shadingsys->attribute ("debug_nan", debugnan);
shadingsys->attribute ("debug_uninit", debug_uninit);
shadingsys->attribute ("userdata_isconnected", userdata_isconnected);
if (! shaderpath.empty())
shadingsys->attribute ("searchpath:shader", shaderpath);
shadingsys_options_set = true;
}
/// Read the entire contents of the named file and place it in str,
/// returning true on success, false on failure.
inline bool
read_text_file (string_view filename, std::string &str)
{
std::ifstream in (filename.c_str(), std::ios::in | std::ios::binary);
if (in) {
std::ostringstream contents;
contents << in.rdbuf();
in.close ();
str = contents.str();
return true;
}
return false;
}
static void
compile_buffer (const std::string &sourcecode,
const std::string &shadername)
{
// std::cout << "source was\n---\n" << sourcecode << "---\n\n";
std::string osobuffer;
OSLCompiler compiler;
std::vector<std::string> options;
if (! compiler.compile_buffer (sourcecode, osobuffer, options)) {
std::cerr << "Could not compile \"" << shadername << "\"\n";
exit (EXIT_FAILURE);
}
// std::cout << "Compiled to oso:\n---\n" << osobuffer << "---\n\n";
if (! shadingsys->LoadMemoryCompiledShader (shadername, osobuffer)) {
std::cerr << "Could not load compiled buffer from \""
<< shadername << "\"\n";
exit (EXIT_FAILURE);
}
}
static void
shader_from_buffers (std::string shadername)
{
std::string oslfilename = shadername;
if (! OIIO::Strutil::ends_with (oslfilename, ".osl"))
oslfilename += ".osl";
std::string sourcecode;
if (! read_text_file (oslfilename, sourcecode)) {
std::cerr << "Could not open \"" << oslfilename << "\"\n";
exit (EXIT_FAILURE);
}
compile_buffer (sourcecode, shadername);
// std::cout << "Read and compiled " << shadername << "\n";
}
static int
add_shader (int argc, const char *argv[])
{
ASSERT (argc == 1);
string_view shadername (argv[0]);
set_shadingsys_options ();
if (inbuffer) // Request to exercise the buffer-based API calls
shader_from_buffers (shadername);
for (int i = 0; i < argc; i++) {
inject_params ();
shadernames.push_back (shadername);
shadingsys->Shader ("surface", shadername, layername);
layername.clear ();
params.clear ();
}
return 0;
}
static void
action_shaderdecl (int argc, const char *argv[])
{
// `--shader shadername layername` is exactly equivalent to:
// `--layer layername` followed by naming the shader.
layername = argv[2];
add_shader (1, argv+1);
}
// The --expr ARG command line option will take ARG that is a snipped of
// OSL source code, embed it in some boilerplate shader wrapper, compile
// it from memory, and run that in the same way that would have been done
// if it were a compiled shader on disk. The boilerplate assumes that there
// are two output parameters for the shader: color result, and float alpha.
//
// Example use:
// testshade -v -g 64 64 -o result out.exr -expr 'result=color(u,v,0);'
//
static void
specify_expr (int argc, const char *argv[])
{
ASSERT (argc == 2);
std::string shadername = OIIO::Strutil::format("expr_%d", exprcount++);
std::string sourcecode =
"shader " + shadername + " (\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(argv[1]) + "\n"
" ;\n"
"}\n";
if (verbose)
std::cout << "Expression-based shader text is:\n---\n"
<< sourcecode << "---\n";
set_shadingsys_options ();
compile_buffer (sourcecode, shadername);
inject_params ();
shadernames.push_back (shadername);
shadingsys->Shader ("surface", shadername, layername);
layername.clear ();
params.clear ();
}
static void
action_param (int argc, const char *argv[])
{
std::string command = argv[0];
bool use_reparam = false;
if (OIIO::Strutil::istarts_with(command, "--reparam") ||
OIIO::Strutil::istarts_with(command, "-reparam"))
use_reparam = true;
ParamValueList ¶ms (use_reparam ? reparams : (::params));
string_view paramname (argv[1]);
string_view stringval (argv[2]);
TypeDesc type = TypeDesc::UNKNOWN;
bool unlockgeom = false;
float f[16];
size_t pos;
while ((pos = command.find_first_of(":")) != std::string::npos) {
command = command.substr (pos+1, std::string::npos);
std::vector<std::string> splits;
OIIO::Strutil::split (command, splits, ":", 1);
if (splits.size() < 1) {}
else if (OIIO::Strutil::istarts_with(splits[0],"type="))
type.fromstring (splits[0].c_str()+5);
else if (OIIO::Strutil::istarts_with(splits[0],"lockgeom="))
unlockgeom = (OIIO::Strutil::from_string<int> (splits[0]) == 0);
}
// If it is or might be a matrix, look for 16 comma-separated floats
if ((type == TypeDesc::UNKNOWN || type == TypeDesc::TypeMatrix)
&& sscanf (stringval.c_str(),
"%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f",
&f[0], &f[1], &f[2], &f[3],
&f[4], &f[5], &f[6], &f[7], &f[8], &f[9], &f[10], &f[11],
&f[12], &f[13], &f[14], &f[15]) == 16) {
params.emplace_back (paramname, TypeDesc::TypeMatrix, 1, f);
if (unlockgeom)
params.back().interp (ParamValue::INTERP_VERTEX);
return;
}
// If it is or might be a vector type, look for 3 comma-separated floats
if ((type == TypeDesc::UNKNOWN || equivalent(type,TypeDesc::TypeVector))
&& sscanf (stringval.c_str(), "%g, %g, %g", &f[0], &f[1], &f[2]) == 3) {
if (type == TypeDesc::UNKNOWN)
type = TypeDesc::TypeVector;
params.emplace_back (paramname, type, 1, f);
if (unlockgeom)
params.back().interp (ParamValue::INTERP_VERTEX);
return;
}
// If it is or might be an int, look for an int that takes up the whole
// string.
if ((type == TypeDesc::UNKNOWN || type == TypeDesc::TypeInt)
&& OIIO::Strutil::string_is<int>(stringval)) {
params.emplace_back (paramname, OIIO::Strutil::from_string<int>(stringval));
if (unlockgeom)
params.back().interp (ParamValue::INTERP_VERTEX);
return;
}
// If it is or might be an float, look for a float that takes up the
// whole string.
if ((type == TypeDesc::UNKNOWN || type == TypeDesc::TypeFloat)
&& OIIO::Strutil::string_is<float>(stringval)) {
params.emplace_back (paramname, OIIO::Strutil::from_string<float>(stringval));
if (unlockgeom)
params.back().interp (ParamValue::INTERP_VERTEX);
return;
}
// Catch-all for float types and arrays
if (type.basetype == TypeDesc::FLOAT) {
int n = type.aggregate * type.numelements();
std::vector<float> vals (n);
for (int i = 0; i < n; ++i) {
OIIO::Strutil::parse_float (stringval, vals[i]);
OIIO::Strutil::parse_char (stringval, ',');
}
params.emplace_back (paramname, type, 1, &vals[0]);
if (unlockgeom)
params.back().interp (ParamValue::INTERP_VERTEX);
return;
}
// Catch-all for int types and arrays
if (type.basetype == TypeDesc::INT) {
int n = type.aggregate * type.numelements();
std::vector<int> vals (n);
for (int i = 0; i < n; ++i) {
OIIO::Strutil::parse_int (stringval, vals[i]);
OIIO::Strutil::parse_char (stringval, ',');
}
params.emplace_back (paramname, type, 1, &vals[0]);
if (unlockgeom)
params.back().interp (ParamValue::INTERP_VERTEX);
return;
}
// String arrays are slightly tricky
if (type.basetype == TypeDesc::STRING && type.is_array()) {
std::vector<string_view> splitelements;
OIIO::Strutil::split (stringval, splitelements, ",", type.arraylen);
splitelements.resize (type.arraylen);
std::vector<ustring> strelements;
for (auto&& s : splitelements)
strelements.push_back (ustring(s));
params.emplace_back (paramname, type, 1, &strelements[0]);
if (unlockgeom)
params.back().interp (ParamValue::INTERP_VERTEX);
return;
}
// All remaining cases -- it's a string
const char *s = stringval.c_str();
params.emplace_back (paramname, TypeDesc::TypeString, 1, &s);
if (unlockgeom)
params.back().interp (ParamValue::INTERP_VERTEX);
}
// reparam -- just set reparam_layer and then let action_param do all the
// hard work.
static void
action_reparam (int argc, const char *argv[])
{
reparam_layer = argv[1];
const char *newargv[] = { argv[0], argv[2], argv[3] };
action_param (3, newargv);
}
static void
action_groupspec (int argc, const char *argv[])
{
shadingsys->ShaderGroupEnd ();
std::string groupspec (argv[1]);
if (OIIO::Filesystem::exists (groupspec)) {
// If it names a file, use the contents of the file as the group
// specification.
OIIO::Filesystem::read_text_file (groupspec, groupspec);
set_shadingsys_options ();
}
if (verbose)
std::cout << "Processing group specification:\n---\n"
<< groupspec << "\n---\n";
shadergroup = shadingsys->ShaderGroupBegin (groupname, "surface", groupspec);
}
static void
set_profile (int argc, const char *argv[])
{
profile = 1;
shadingsys->attribute ("profile", profile);
}
static void
getargs (int argc, const char *argv[])
{
static bool help = false;
OIIO::ArgParse ap;
ap.options ("Usage: testshade [options] shader...",
"%*", add_shader, "",
"--help", &help, "Print help message",
"-v", &verbose, "Verbose messages",
"-t %d", &num_threads, "Render using N threads (default: auto-detect)",
"--debug", &debug, "Lots of debugging info",
"--debug2", &debug2, "Even more debugging info",
"--runstats", &runstats, "Print run statistics",
"--stats", &runstats, "", // DEPRECATED 1.7
"--profile %@", &set_profile, NULL, "Print profile information",
"--path %s", &shaderpath, "Specify oso search path",
"-g %d %d", &xres, &yres, "Make an X x Y grid of shading points",
"-res %d %d", &xres, &yres, "", // synonym for -g
"--options %s", &extraoptions, "Set extra OSL options",
"--texoptions %s", &texoptions, "Set extra TextureSystem options",
"-o %L %L", &outputvars, &outputfiles,
"Output (variable, filename)",
"-d %s", &dataformatname, "Set the output data format to one of: "
"uint8, half, float",
"-od %s", &dataformatname, "", // old name
"--print", &print_outputs, "Print values of all -o outputs to console instead of saving images",
"--groupname %s", &groupname, "Set shader group name",
"--layer %s", &layername, "Set next layer name",
"--param %@ %s %s", &action_param, NULL, NULL,
"Add a parameter (args: name value) (options: type=%s, lockgeom=%d)",
"--shader %@ %s %s", &action_shaderdecl, NULL, NULL,
"Declare a shader node (args: shader layername)",
"--connect %L %L %L %L",
&connections, &connections, &connections, &connections,
"Connect fromlayer fromoutput tolayer toinput",
"--reparam %@ %s %s %s", &action_reparam, NULL, NULL, NULL,
"Change a parameter (args: layername paramname value) (options: type=%s)",
"--group %@ %s", &action_groupspec, &groupspec,
"Specify a full group command",
"--archivegroup %s", &archivegroup,
"Archive the group to a given filename",
"--raytype %s", &raytype, "Set the raytype",
"--raytype_opt", &raytype_opt, "Specify ray type mask for optimization",
"--iters %d", &iters, "Number of iterations",
"-O0", &O0, "Do no runtime shader optimization",
"-O1", &O1, "Do a little runtime shader optimization",
"-O2", &O2, "Do lots of runtime shader optimization",
"--entry %L", &entrylayers, "Add layer to the list of entry points",
"--entryoutput %L", &entryoutputs, "Add output symbol to the list of entry points",
"--center", &pixelcenters, "Shade at output pixel 'centers' rather than corners",
"--debugnan", &debugnan, "Turn on 'debug_nan' mode",
"--debuguninit", &debug_uninit, "Turn on 'debug_uninit' mode",
"--groupoutputs", &use_group_outputs, "Specify group outputs, not global outputs",
"--oslquery", &do_oslquery, "Test OSLQuery at runtime",
"--inbuffer", &inbuffer, "Compile osl source from and to buffer",
"--shadeimage", &use_shade_image, "Use shade_image utility",
"--noshadeimage %!", &use_shade_image, "Don't use shade_image utility",
"--expr %@ %s", &specify_expr, NULL, "Specify an OSL expression to evaluate",
"--offsetuv %f %f", &uoffset, &voffset, "Offset s & t texture coordinates (default: 0 0)",
"--offsetst %f %f", &uoffset, &voffset, "", // old name
"--scaleuv %f %f", &uscale, &vscale, "Scale s & t texture lookups (default: 1, 1)",
"--scalest %f %f", &uscale, &vscale, "", // old name
"--userdata_isconnected", &userdata_isconnected, "Consider lockgeom=0 to be isconnected()",
"-v", &verbose, "Verbose output",
NULL);
if (ap.parse(argc, argv) < 0 || (shadernames.empty() && groupspec.empty())) {
std::cerr << ap.geterror() << std::endl;
ap.usage ();
exit (EXIT_FAILURE);
}
if (help) {
std::cout << "testshade -- Test Open Shading Language\n"
OSL_COPYRIGHT_STRING "\n";
ap.usage ();
exit (EXIT_SUCCESS);
}
if (debug || verbose)
errhandler.verbosity (ErrorHandler::VERBOSE);
raytype_bit = shadingsys->raytype_bit (ustring (raytype));
}
// Here we set up transformations. These are just examples, set up so
// that our unit tests can transform among spaces in ways that we will
// recognize as correct. The "shader" and "object" spaces are required
// by OSL and the ShaderGlobals will need to have references to them.
// For good measure, we also set up a "myspace" space, registering it
// with the RendererServices.
//
static void
setup_transformations (SimpleRenderer &rend, OSL::Matrix44 &Mshad,
OSL::Matrix44 &Mobj)
{
Matrix44 M (1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1);
rend.camera_params (M, ustring("perspective"), 90.0f,
0.1f, 1000.0f, xres, yres);
// Make a "shader" space that is translated one unit in x and rotated
// 45deg about the z axis.
Mshad.makeIdentity ();
Mshad.translate (OSL::Vec3 (1.0, 0.0, 0.0));
Mshad.rotate (OSL::Vec3 (0.0, 0.0, M_PI_4));
// std::cout << "shader-to-common matrix: " << Mshad << "\n";
// Make an object space that is translated one unit in y and rotated
// 90deg about the z axis.
Mobj.makeIdentity ();
Mobj.translate (OSL::Vec3 (0.0, 1.0, 0.0));
Mobj.rotate (OSL::Vec3 (0.0, 0.0, M_PI_2));
// std::cout << "object-to-common matrix: " << Mobj << "\n";
OSL::Matrix44 Mmyspace;
Mmyspace.scale (OSL::Vec3 (1.0, 2.0, 1.0));
// std::cout << "myspace-to-common matrix: " << Mmyspace << "\n";
rend.name_transform ("myspace", Mmyspace);
}
// Set up the ShaderGlobals fields for pixel (x,y).
static void
setup_shaderglobals (ShaderGlobals &sg, ShadingSystem *shadingsys,
int x, int y)
{
// Just zero the whole thing out to start
memset(&sg, 0, sizeof(ShaderGlobals));
// In our SimpleRenderer, the "renderstate" itself just a pointer to
// the ShaderGlobals.
sg.renderstate = &sg;
// Set "shader" space to be Mshad. In a real renderer, this may be
// different for each shader group.
sg.shader2common = OSL::TransformationPtr (&Mshad);
// Set "object" space to be Mobj. In a real renderer, this may be
// different for each object.
sg.object2common = OSL::TransformationPtr (&Mobj);
// Just make it look like all shades are the result of 'raytype' rays.
sg.raytype = raytype_bit;
// Set up u,v to vary across the "patch", and also their derivatives.
// Note that since u & x, and v & y are aligned, we only need to set
// values for dudx and dvdy, we can use the memset above to have set
// dvdx and dudy to 0.
if (pixelcenters) {
// Our patch is like an "image" with shading samples at the
// centers of each pixel.
sg.u = uscale * (float)(x+0.5f) / xres + uoffset;
sg.v = vscale * (float)(y+0.5f) / yres + voffset;
sg.dudx = uscale / xres;
sg.dvdy = vscale / yres;
} else {
// Our patch is like a Reyes grid of points, with the border
// samples being exactly on u,v == 0 or 1.
sg.u = uscale * ((xres == 1) ? 0.5f : (float) x / (xres - 1)) + uoffset;
sg.v = vscale * ((yres == 1) ? 0.5f : (float) y / (yres - 1)) + voffset;
sg.dudx = uscale / std::max (1, xres-1);
sg.dvdy = vscale / std::max (1, yres-1);
}
// Assume that position P is simply (u,v,1), that makes the patch lie
// on [0,1] at z=1.
sg.P = Vec3 (sg.u, sg.v, 1.0f);
// Derivatives with respect to x,y
sg.dPdx = Vec3 (sg.dudx, sg.dudy, 0.0f);
sg.dPdy = Vec3 (sg.dvdx, sg.dvdy, 0.0f);
sg.dPdz = Vec3 (0.0f, 0.0f, 0.0f); // just use 0 for volume tangent
// Tangents of P with respect to surface u,v
sg.dPdu = Vec3 (1.0f, 0.0f, 0.0f);
sg.dPdv = Vec3 (0.0f, 1.0f, 0.0f);
// That also implies that our normal points to (0,0,1)
sg.N = Vec3 (0, 0, 1);
sg.Ng = Vec3 (0, 0, 1);
// Set the surface area of the patch to 1 (which it is). This is
// only used for light shaders that call the surfacearea() function.
sg.surfacearea = 1;
}
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);
if (texoptions.size())
shadingsys->texturesys()->attribute ("options", texoptions);
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.emplace_back(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
}
// For pixel (x,y) that was just shaded by the given shading context,
// save each of the requested outputs to the corresponding output
// ImageBuf.
//
// In a real renderer, this is illustrative of how you would pull shader
// outputs into "AOV's" (arbitrary output variables, or additional
// renderer outputs). You would, of course, also grab the closure Ci
// and integrate the lights using that BSDF to determine the radiance
// in the direction of the camera for that pixel.
static void
save_outputs (ShadingSystem *shadingsys, ShadingContext *ctx, int x, int y)
{
if (print_outputs)
printf ("Pixel (%d, %d):\n", x, y);
// For each output requested on the command line...
for (size_t i = 0; i < outputfiles.size(); ++i) {
// Skip if we couldn't open the image or didn't match a known output
if (! outputimgs[i])
continue;
// 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)
continue; // Skip if symbol isn't found
int nchans = outputimgs[i]->nchannels();
if (t.basetype == TypeDesc::FLOAT) {
// If the variable we are outputting is float-based, set it
// directly in the output buffer.
outputimgs[i]->setpixel (x, y, (const float *)data);
if (print_outputs) {
printf (" %s :", outputvarnames[i].c_str());
for (int c = 0; c < nchans; ++c)
printf (" %g", ((const float *)data)[c]);
printf ("\n");
}
} else if (t.basetype == TypeDesc::INT) {
// We are outputting an integer variable, so we need to
// convert it to floating point.
float *pixel = (float *) alloca (nchans * sizeof(float));
OIIO::convert_types (TypeDesc::BASETYPE(t.basetype), data,
TypeDesc::FLOAT, pixel, nchans);
outputimgs[i]->setpixel (x, y, &pixel[0]);
if (print_outputs) {
printf (" %s :", outputvarnames[i].c_str());
for (int c = 0; c < nchans; ++c)
printf (" %d", ((const int *)data)[c]);
printf ("\n");
}
}
// N.B. Drop any outputs that aren't float- or int-based
}
}
static void
test_group_attributes (ShaderGroup *group)
{
int nt = 0;
if (shadingsys->getattribute (group, "num_textures_needed", nt)) {
std::cout << "Need " << nt << " textures:\n";
ustring *tex = NULL;
shadingsys->getattribute (group, "textures_needed",
TypeDesc::PTR, &tex);
for (int i = 0; i < nt; ++i)
std::cout << " " << tex[i] << "\n";
int unk = 0;
shadingsys->getattribute (group, "unknown_textures_needed", unk);
if (unk)
std::cout << " and unknown textures\n";
}
int nclosures = 0;
if (shadingsys->getattribute (group, "num_closures_needed", nclosures)) {
std::cout << "Need " << nclosures << " closures:\n";
ustring *closures = NULL;
shadingsys->getattribute (group, "closures_needed",
TypeDesc::PTR, &closures);
for (int i = 0; i < nclosures; ++i)
std::cout << " " << closures[i] << "\n";
int unk = 0;
shadingsys->getattribute (group, "unknown_closures_needed", unk);
if (unk)
std::cout << " and unknown closures\n";
}
int nglobals = 0;
if (shadingsys->getattribute (group, "num_globals_needed", nglobals)) {
std::cout << "Need " << nglobals << " globals:\n";
ustring *globals = NULL;
shadingsys->getattribute (group, "globals_needed",
TypeDesc::PTR, &globals);
for (int i = 0; i < nglobals; ++i)
std::cout << " " << globals[i] << "\n";
}
int nuser = 0;
if (shadingsys->getattribute (group, "num_userdata", nuser) && nuser) {
std::cout << "Need " << nuser << " user data items:\n";
ustring *userdata_names = NULL;
TypeDesc *userdata_types = NULL;
int *userdata_offsets = NULL;
bool *userdata_derivs = NULL;
shadingsys->getattribute (group, "userdata_names",
TypeDesc::PTR, &userdata_names);
shadingsys->getattribute (group, "userdata_types",
TypeDesc::PTR, &userdata_types);
shadingsys->getattribute (group, "userdata_offsets",
TypeDesc::PTR, &userdata_offsets);
shadingsys->getattribute (group, "userdata_derivs",
TypeDesc::PTR, &userdata_derivs);
DASSERT (userdata_names && userdata_types && userdata_offsets);
for (int i = 0; i < nuser; ++i)
std::cout << " " << userdata_names[i] << ' '
<< userdata_types[i] << " offset="
<< userdata_offsets[i] << " deriv="
<< userdata_derivs[i] << "\n";
}
int nattr = 0;
if (shadingsys->getattribute (group, "num_attributes_needed", nattr) && nattr) {
std::cout << "Need " << nattr << " attributes:\n";
ustring *names = NULL;
ustring *scopes = NULL;
shadingsys->getattribute (group, "attributes_needed",
TypeDesc::PTR, &names);
shadingsys->getattribute (group, "attribute_scopes",
TypeDesc::PTR, &scopes);
DASSERT (names && scopes);
for (int i = 0; i < nattr; ++i)
std::cout << " " << names[i] << ' '
<< scopes[i] << "\n";
int unk = 0;
shadingsys->getattribute (group, "unknown_attributes_needed", unk);
if (unk)
std::cout << " and unknown attributes\n";
}
int raytype_queries = 0;
shadingsys->getattribute (group, "raytype_queries", raytype_queries);
std::cout << "raytype() query mask: " << raytype_queries << "\n";
}
void
shade_region (ShaderGroup *shadergroup, OIIO::ROI roi, bool save)
{
// 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);
// Set up shader globals and a little test grid of points to shade.
ShaderGlobals shaderglobals;
// Loop over all pixels in the image (in x and y)...
for (int y = roi.ybegin; y < roi.yend; ++y) {
for (int x = roi.xbegin; x < roi.xend; ++x) {
// 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
if (entrylayer_index.empty()) {
// Sole entry point for whole group, default behavior
shadingsys->execute (ctx, *shadergroup, shaderglobals);
} else {
// Explicit list of entries to call in order
shadingsys->execute_init (*ctx, *shadergroup, shaderglobals);
if (entrylayer_symbols.size()) {
for (size_t i = 0, e = entrylayer_symbols.size(); i < e; ++i)
shadingsys->execute_layer (*ctx, shaderglobals, entrylayer_symbols[i]);
} else {
for (size_t i = 0, e = entrylayer_index.size(); i < e; ++i)
shadingsys->execute_layer (*ctx, shaderglobals, entrylayer_index[i]);
}
shadingsys->execute_cleanup (*ctx);
}
// 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 (save)
save_outputs (shadingsys, ctx, x, y);
}
}
// 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);
}
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).