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 <OpenImageIO/imagebufalgo.h>
7
8 #include <OSL/oslexec.h>
9 #include <OSL/genclosure.h>
10 #include "simplerend.h"
11 using namespace OSL;
12
13
14 // anonymous namespace
15 namespace {
16
17 // unique identifier for each closure supported by testshade
18 enum ClosureIDs {
19 EMISSION_ID = 1,
20 BACKGROUND_ID,
21 DIFFUSE_ID,
22 OREN_NAYAR_ID,
23 TRANSLUCENT_ID,
24 PHONG_ID,
25 WARD_ID,
26 MICROFACET_ID,
27 REFLECTION_ID,
28 FRESNEL_REFLECTION_ID,
29 REFRACTION_ID,
30 TRANSPARENT_ID,
31 DEBUG_ID,
32 HOLDOUT_ID,
33 };
34
35 // these structures hold the parameters of each closure type
36 // they will be contained inside ClosureComponent
37 struct EmptyParams { };
38 struct DiffuseParams { Vec3 N; ustring label; };
39 struct OrenNayarParams { Vec3 N; float sigma; };
40 struct PhongParams { Vec3 N; float exponent; ustring label; };
41 struct WardParams { Vec3 N, T; float ax, ay; };
42 struct ReflectionParams { Vec3 N; float eta; };
43 struct RefractionParams { Vec3 N; float eta; };
44 struct MicrofacetParams { ustring dist; Vec3 N, U; float xalpha, yalpha, eta; int refract; };
45 struct DebugParams { ustring tag; };
46
47 } // anonymous namespace
48
49
50 OSL_NAMESPACE_ENTER
51
52 static ustring u_camera("camera"), u_screen("screen");
53 static ustring u_NDC("NDC"), u_raster("raster");
54 static ustring u_perspective("perspective");
55 static ustring u_s("s"), u_t("t");
56 static TypeDesc TypeFloatArray2 (TypeDesc::FLOAT, 2);
57 static TypeDesc TypeFloatArray4 (TypeDesc::FLOAT, 4);
58 static TypeDesc TypeIntArray2 (TypeDesc::INT, 2);
59
60
register_closures(OSL::ShadingSystem * shadingsys)61 void register_closures(OSL::ShadingSystem* shadingsys) {
62 // Describe the memory layout of each closure type to the OSL runtime
63 enum { MaxParams = 32 };
64 struct BuiltinClosures {
65 const char* name;
66 int id;
67 ClosureParam params[MaxParams]; // upper bound
68 };
69 BuiltinClosures builtins[] = {
70 { "emission" , EMISSION_ID, { CLOSURE_FINISH_PARAM(EmptyParams) } },
71 { "background" , BACKGROUND_ID, { CLOSURE_FINISH_PARAM(EmptyParams) } },
72 { "diffuse" , DIFFUSE_ID, { CLOSURE_VECTOR_PARAM(DiffuseParams, N),
73 CLOSURE_STRING_KEYPARAM(DiffuseParams, label, "label"), // example of custom key param
74 CLOSURE_FINISH_PARAM(DiffuseParams) } },
75 { "oren_nayar" , OREN_NAYAR_ID, { CLOSURE_VECTOR_PARAM(OrenNayarParams, N),
76 CLOSURE_FLOAT_PARAM (OrenNayarParams, sigma),
77 CLOSURE_FINISH_PARAM(OrenNayarParams) } },
78 { "translucent", TRANSLUCENT_ID, { CLOSURE_VECTOR_PARAM(DiffuseParams, N),
79 CLOSURE_FINISH_PARAM(DiffuseParams) } },
80 { "phong" , PHONG_ID, { CLOSURE_VECTOR_PARAM(PhongParams, N),
81 CLOSURE_FLOAT_PARAM (PhongParams, exponent),
82 CLOSURE_STRING_KEYPARAM(PhongParams, label, "label"), // example of custom key param
83 CLOSURE_FINISH_PARAM(PhongParams) } },
84 { "ward" , WARD_ID, { CLOSURE_VECTOR_PARAM(WardParams, N),
85 CLOSURE_VECTOR_PARAM(WardParams, T),
86 CLOSURE_FLOAT_PARAM (WardParams, ax),
87 CLOSURE_FLOAT_PARAM (WardParams, ay),
88 CLOSURE_FINISH_PARAM(WardParams) } },
89 { "microfacet", MICROFACET_ID, { CLOSURE_STRING_PARAM(MicrofacetParams, dist),
90 CLOSURE_VECTOR_PARAM(MicrofacetParams, N),
91 CLOSURE_VECTOR_PARAM(MicrofacetParams, U),
92 CLOSURE_FLOAT_PARAM (MicrofacetParams, xalpha),
93 CLOSURE_FLOAT_PARAM (MicrofacetParams, yalpha),
94 CLOSURE_FLOAT_PARAM (MicrofacetParams, eta),
95 CLOSURE_INT_PARAM (MicrofacetParams, refract),
96 CLOSURE_FINISH_PARAM(MicrofacetParams) } },
97 { "reflection" , REFLECTION_ID, { CLOSURE_VECTOR_PARAM(ReflectionParams, N),
98 CLOSURE_FINISH_PARAM(ReflectionParams) } },
99 { "reflection" , FRESNEL_REFLECTION_ID, { CLOSURE_VECTOR_PARAM(ReflectionParams, N),
100 CLOSURE_FLOAT_PARAM (ReflectionParams, eta),
101 CLOSURE_FINISH_PARAM(ReflectionParams) } },
102 { "refraction" , REFRACTION_ID, { CLOSURE_VECTOR_PARAM(RefractionParams, N),
103 CLOSURE_FLOAT_PARAM (RefractionParams, eta),
104 CLOSURE_FINISH_PARAM(RefractionParams) } },
105 { "transparent", TRANSPARENT_ID, { CLOSURE_FINISH_PARAM(EmptyParams) } },
106 { "debug" , DEBUG_ID, { CLOSURE_STRING_PARAM(DebugParams, tag),
107 CLOSURE_FINISH_PARAM(DebugParams) } },
108 { "holdout" , HOLDOUT_ID, { CLOSURE_FINISH_PARAM(EmptyParams) } }
109 };
110
111 for (const auto& b : builtins)
112 shadingsys->register_closure(b.name, b.id, b.params, nullptr, nullptr);
113 }
114
115
116
SimpleRenderer()117 SimpleRenderer::SimpleRenderer ()
118 {
119 Matrix44 M; M.makeIdentity();
120 camera_params (M, u_perspective, 90.0f,
121 0.1f, 1000.0f, 256, 256);
122
123 // Set up getters
124 m_attr_getters[ustring("osl:version")] = &SimpleRenderer::get_osl_version;
125 m_attr_getters[ustring("camera:resolution")] = &SimpleRenderer::get_camera_resolution;
126 m_attr_getters[ustring("camera:projection")] = &SimpleRenderer::get_camera_projection;
127 m_attr_getters[ustring("camera:pixelaspect")] = &SimpleRenderer::get_camera_pixelaspect;
128 m_attr_getters[ustring("camera:screen_window")] = &SimpleRenderer::get_camera_screen_window;
129 m_attr_getters[ustring("camera:fov")] = &SimpleRenderer::get_camera_fov;
130 m_attr_getters[ustring("camera:clip")] = &SimpleRenderer::get_camera_clip;
131 m_attr_getters[ustring("camera:clip_near")] = &SimpleRenderer::get_camera_clip_near;
132 m_attr_getters[ustring("camera:clip_far")] = &SimpleRenderer::get_camera_clip_far;
133 m_attr_getters[ustring("camera:shutter")] = &SimpleRenderer::get_camera_shutter;
134 m_attr_getters[ustring("camera:shutter_open")] = &SimpleRenderer::get_camera_shutter_open;
135 m_attr_getters[ustring("camera:shutter_close")] = &SimpleRenderer::get_camera_shutter_close;
136 }
137
138
139
140 int
supports(string_view) const141 SimpleRenderer::supports (string_view /*feature*/) const
142 {
143 return false;
144 }
145
146
147
148 OIIO::ParamValue*
find_attribute(string_view name,TypeDesc searchtype,bool casesensitive)149 SimpleRenderer::find_attribute(string_view name, TypeDesc searchtype,
150 bool casesensitive)
151 {
152 auto iter = options.find(name, searchtype, casesensitive);
153 if (iter != options.end())
154 return &(*iter);
155 return nullptr;
156 }
157
158
159
160 const OIIO::ParamValue*
find_attribute(string_view name,TypeDesc searchtype,bool casesensitive) const161 SimpleRenderer::find_attribute(string_view name, TypeDesc searchtype,
162 bool casesensitive) const
163 {
164 auto iter = options.find(name, searchtype, casesensitive);
165 if (iter != options.end())
166 return &(*iter);
167 return nullptr;
168 }
169
170
171
172 void
attribute(string_view name,TypeDesc type,const void * value)173 SimpleRenderer::attribute (string_view name, TypeDesc type, const void *value)
174 {
175 if (name.empty()) // Guard against bogus empty names
176 return;
177 // Don't allow duplicates
178 auto f = find_attribute(name);
179 if (!f) {
180 options.resize(options.size() + 1);
181 f = &options.back();
182 }
183 f->init(name, type, 1, value);
184 }
185
186
187
188 void
camera_params(const Matrix44 & world_to_camera,ustring projection,float hfov,float hither,float yon,int xres,int yres)189 SimpleRenderer::camera_params (const Matrix44 &world_to_camera,
190 ustring projection, float hfov,
191 float hither, float yon,
192 int xres, int yres)
193 {
194 m_world_to_camera = world_to_camera;
195 m_projection = projection;
196 m_fov = hfov;
197 m_pixelaspect = 1.0f; // hard-coded
198 m_hither = hither;
199 m_yon = yon;
200 m_shutter[0] = 0.0f; m_shutter[1] = 1.0f; // hard-coded
201 float frame_aspect = float(xres)/float(yres) * m_pixelaspect;
202 m_screen_window[0] = -frame_aspect;
203 m_screen_window[1] = -1.0f;
204 m_screen_window[2] = frame_aspect;
205 m_screen_window[3] = 1.0f;
206 m_xres = xres;
207 m_yres = yres;
208 }
209
210
211
212 bool
get_matrix(ShaderGlobals *,Matrix44 & result,TransformationPtr xform,float)213 SimpleRenderer::get_matrix (ShaderGlobals* /*sg*/, Matrix44 &result,
214 TransformationPtr xform,
215 float /*time*/)
216 {
217 // SimpleRenderer doesn't understand motion blur and transformations
218 // are just simple 4x4 matrices.
219 result = *reinterpret_cast<const Matrix44*>(xform);
220 return true;
221 }
222
223
224
225 bool
get_matrix(ShaderGlobals *,Matrix44 & result,ustring from,float)226 SimpleRenderer::get_matrix (ShaderGlobals* /*sg*/, Matrix44 &result,
227 ustring from, float /*time*/)
228 {
229 TransformMap::const_iterator found = m_named_xforms.find (from);
230 if (found != m_named_xforms.end()) {
231 result = *(found->second);
232 return true;
233 } else {
234 return false;
235 }
236 }
237
238
239
240 bool
get_matrix(ShaderGlobals *,Matrix44 & result,TransformationPtr xform)241 SimpleRenderer::get_matrix (ShaderGlobals* /*sg*/, Matrix44 &result,
242 TransformationPtr xform)
243 {
244 // SimpleRenderer doesn't understand motion blur and transformations
245 // are just simple 4x4 matrices.
246 result = *(OSL::Matrix44 *)xform;
247 return true;
248 }
249
250
251
252 bool
get_matrix(ShaderGlobals *,Matrix44 & result,ustring from)253 SimpleRenderer::get_matrix (ShaderGlobals* /*sg*/, Matrix44 &result,
254 ustring from)
255 {
256 // SimpleRenderer doesn't understand motion blur, so we never fail
257 // on account of time-varying transformations.
258 TransformMap::const_iterator found = m_named_xforms.find (from);
259 if (found != m_named_xforms.end()) {
260 result = *(found->second);
261 return true;
262 } else {
263 return false;
264 }
265 }
266
267
268
269 bool
get_inverse_matrix(ShaderGlobals *,Matrix44 & result,ustring to,float)270 SimpleRenderer::get_inverse_matrix (ShaderGlobals* /*sg*/, Matrix44 &result,
271 ustring to, float /*time*/)
272 {
273 if (to == u_camera || to == u_screen || to == u_NDC || to == u_raster) {
274 Matrix44 M = m_world_to_camera;
275 if (to == u_screen || to == u_NDC || to == u_raster) {
276 float depthrange = (double)m_yon-(double)m_hither;
277 if (m_projection == u_perspective) {
278 float tanhalffov = tanf (0.5f * m_fov * M_PI/180.0);
279 Matrix44 camera_to_screen (1/tanhalffov, 0, 0, 0,
280 0, 1/tanhalffov, 0, 0,
281 0, 0, m_yon/depthrange, 1,
282 0, 0, -m_yon*m_hither/depthrange, 0);
283 M = M * camera_to_screen;
284 } else {
285 Matrix44 camera_to_screen (1, 0, 0, 0,
286 0, 1, 0, 0,
287 0, 0, 1/depthrange, 0,
288 0, 0, -m_hither/depthrange, 1);
289 M = M * camera_to_screen;
290 }
291 if (to == u_NDC || to == u_raster) {
292 float screenleft = -1.0, screenwidth = 2.0;
293 float screenbottom = -1.0, screenheight = 2.0;
294 Matrix44 screen_to_ndc (1/screenwidth, 0, 0, 0,
295 0, 1/screenheight, 0, 0,
296 0, 0, 1, 0,
297 -screenleft/screenwidth, -screenbottom/screenheight, 0, 1);
298 M = M * screen_to_ndc;
299 if (to == u_raster) {
300 Matrix44 ndc_to_raster (m_xres, 0, 0, 0,
301 0, m_yres, 0, 0,
302 0, 0, 1, 0,
303 0, 0, 0, 1);
304 M = M * ndc_to_raster;
305 }
306 }
307 }
308 result = M;
309 return true;
310 }
311
312 TransformMap::const_iterator found = m_named_xforms.find (to);
313 if (found != m_named_xforms.end()) {
314 result = *(found->second);
315 result.invert();
316 return true;
317 } else {
318 return false;
319 }
320 }
321
322
323
324 void
name_transform(const char * name,const OSL::Matrix44 & xform)325 SimpleRenderer::name_transform (const char *name, const OSL::Matrix44 &xform)
326 {
327 std::shared_ptr<Transformation> M (new OSL::Matrix44 (xform));
328 m_named_xforms[ustring(name)] = M;
329 }
330
331
332
333 bool
get_array_attribute(ShaderGlobals * sg,bool derivatives,ustring object,TypeDesc type,ustring name,int index,void * val)334 SimpleRenderer::get_array_attribute (ShaderGlobals *sg, bool derivatives, ustring object,
335 TypeDesc type, ustring name,
336 int index, void *val)
337 {
338 AttrGetterMap::const_iterator g = m_attr_getters.find (name);
339 if (g != m_attr_getters.end()) {
340 AttrGetter getter = g->second;
341 return (this->*(getter)) (sg, derivatives, object, type, name, val);
342 }
343
344 // In order to test getattribute(), respond positively to
345 // "options"/"blahblah"
346 if (object == "options" && name == "blahblah" &&
347 type == TypeDesc::TypeFloat) {
348 *(float *)val = 3.14159;
349 return true;
350 }
351
352 // If no named attribute was found, allow userdata to bind to the
353 // attribute request.
354 if (object.empty() && index == -1)
355 return get_userdata (derivatives, name, type, sg, val);
356
357 return false;
358 }
359
360
361
362 bool
get_attribute(ShaderGlobals * sg,bool derivatives,ustring object,TypeDesc type,ustring name,void * val)363 SimpleRenderer::get_attribute (ShaderGlobals *sg, bool derivatives, ustring object,
364 TypeDesc type, ustring name, void *val)
365 {
366 return get_array_attribute (sg, derivatives, object,
367 type, name, -1, val);
368 }
369
370
371
372 bool
get_userdata(bool derivatives,ustring name,TypeDesc type,ShaderGlobals * sg,void * val)373 SimpleRenderer::get_userdata (bool derivatives, ustring name, TypeDesc type,
374 ShaderGlobals *sg, void *val)
375 {
376 // Just to illustrate how this works, respect s and t userdata, filled
377 // in with the uv coordinates. In a real renderer, it would probably
378 // look up something specific to the primitive, rather than have hard-
379 // coded names.
380
381 if (name == u_s && type == TypeDesc::TypeFloat) {
382 ((float *)val)[0] = sg->u;
383 if (derivatives) {
384 ((float *)val)[1] = sg->dudx;
385 ((float *)val)[2] = sg->dudy;
386 }
387 return true;
388 }
389 if (name == u_t && type == TypeDesc::TypeFloat) {
390 ((float *)val)[0] = sg->v;
391 if (derivatives) {
392 ((float *)val)[1] = sg->dvdx;
393 ((float *)val)[2] = sg->dvdy;
394 }
395 return true;
396 }
397
398 #if OIIO_VERSION >= 20202
399 if (const OIIO::ParamValue* p = userdata.find_pv(name, type)) {
400 size_t size = p->type().size();
401 memcpy(val, p->data(), size);
402 if (derivatives)
403 memcpy(val, (char*)p->data() + size, 2 * size);
404 return true;
405 }
406 #else
407 auto p = userdata.find(name, type);
408 if (p != userdata.end()) {
409 size_t size = p->type().size();
410 memcpy(val, p->data(), size);
411 if (derivatives)
412 memcpy(val, (char*)p->data() + size, 2 * size);
413 return true;
414 }
415 #endif
416
417 return false;
418 }
419
420
421 bool
get_osl_version(ShaderGlobals *,bool,ustring,TypeDesc type,ustring,void * val)422 SimpleRenderer::get_osl_version (ShaderGlobals* /*sg*/, bool /*derivs*/, ustring /*object*/,
423 TypeDesc type, ustring /*name*/, void *val)
424 {
425 if (type == TypeDesc::TypeInt) {
426 ((int *)val)[0] = OSL_VERSION;
427 return true;
428 }
429 return false;
430 }
431
432
433 bool
get_camera_resolution(ShaderGlobals *,bool,ustring,TypeDesc type,ustring,void * val)434 SimpleRenderer::get_camera_resolution (ShaderGlobals* /*sg*/, bool /*derivs*/, ustring /*object*/,
435 TypeDesc type, ustring /*name*/, void *val)
436 {
437 if (type == TypeIntArray2) {
438 ((int *)val)[0] = m_xres;
439 ((int *)val)[1] = m_yres;
440 return true;
441 }
442 return false;
443 }
444
445
446 bool
get_camera_projection(ShaderGlobals *,bool,ustring,TypeDesc type,ustring,void * val)447 SimpleRenderer::get_camera_projection (ShaderGlobals* /*sg*/, bool /*derivs*/, ustring /*object*/,
448 TypeDesc type, ustring /*name*/, void *val)
449 {
450 if (type == TypeDesc::TypeString) {
451 ((ustring *)val)[0] = m_projection;
452 return true;
453 }
454 return false;
455 }
456
457
458 bool
get_camera_fov(ShaderGlobals *,bool derivs,ustring,TypeDesc type,ustring,void * val)459 SimpleRenderer::get_camera_fov (ShaderGlobals* /*sg*/, bool derivs, ustring /*object*/,
460 TypeDesc type, ustring /*name*/, void *val)
461 {
462 // N.B. in a real renderer, this may be time-dependent
463 if (type == TypeDesc::TypeFloat) {
464 ((float *)val)[0] = m_fov;
465 if (derivs)
466 memset ((char *)val+type.size(), 0, 2*type.size());
467 return true;
468 }
469 return false;
470 }
471
472
473 bool
get_camera_pixelaspect(ShaderGlobals *,bool derivs,ustring,TypeDesc type,ustring,void * val)474 SimpleRenderer::get_camera_pixelaspect (ShaderGlobals* /*sg*/, bool derivs, ustring /*object*/,
475 TypeDesc type, ustring /*name*/, void *val)
476 {
477 if (type == TypeDesc::TypeFloat) {
478 ((float *)val)[0] = m_pixelaspect;
479 if (derivs)
480 memset ((char *)val+type.size(), 0, 2*type.size());
481 return true;
482 }
483 return false;
484 }
485
486
487 bool
get_camera_clip(ShaderGlobals *,bool derivs,ustring,TypeDesc type,ustring,void * val)488 SimpleRenderer::get_camera_clip (ShaderGlobals* /*sg*/, bool derivs, ustring /*object*/,
489 TypeDesc type, ustring /*name*/, void *val)
490 {
491 if (type == TypeFloatArray2) {
492 ((float *)val)[0] = m_hither;
493 ((float *)val)[1] = m_yon;
494 if (derivs)
495 memset ((char *)val+type.size(), 0, 2*type.size());
496 return true;
497 }
498 return false;
499 }
500
501
502 bool
get_camera_clip_near(ShaderGlobals *,bool derivs,ustring,TypeDesc type,ustring,void * val)503 SimpleRenderer::get_camera_clip_near (ShaderGlobals* /*sg*/, bool derivs, ustring /*object*/,
504 TypeDesc type, ustring /*name*/, void *val)
505 {
506 if (type == TypeDesc::TypeFloat) {
507 ((float *)val)[0] = m_hither;
508 if (derivs)
509 memset ((char *)val+type.size(), 0, 2*type.size());
510 return true;
511 }
512 return false;
513 }
514
515
516 bool
get_camera_clip_far(ShaderGlobals *,bool derivs,ustring,TypeDesc type,ustring,void * val)517 SimpleRenderer::get_camera_clip_far (ShaderGlobals* /*sg*/, bool derivs, ustring /*object*/,
518 TypeDesc type, ustring /*name*/, void *val)
519 {
520 if (type == TypeDesc::TypeFloat) {
521 ((float *)val)[0] = m_yon;
522 if (derivs)
523 memset ((char *)val+type.size(), 0, 2*type.size());
524 return true;
525 }
526 return false;
527 }
528
529
530
531 bool
get_camera_shutter(ShaderGlobals *,bool derivs,ustring,TypeDesc type,ustring,void * val)532 SimpleRenderer::get_camera_shutter (ShaderGlobals* /*sg*/, bool derivs, ustring /*object*/,
533 TypeDesc type, ustring /*name*/, void *val)
534 {
535 if (type == TypeFloatArray2) {
536 ((float *)val)[0] = m_shutter[0];
537 ((float *)val)[1] = m_shutter[1];
538 if (derivs)
539 memset ((char *)val+type.size(), 0, 2*type.size());
540 return true;
541 }
542 return false;
543 }
544
545
546 bool
get_camera_shutter_open(ShaderGlobals *,bool derivs,ustring,TypeDesc type,ustring,void * val)547 SimpleRenderer::get_camera_shutter_open (ShaderGlobals* /*sg*/, bool derivs, ustring /*object*/,
548 TypeDesc type, ustring /*name*/, void *val)
549 {
550 if (type == TypeDesc::TypeFloat) {
551 ((float *)val)[0] = m_shutter[0];
552 if (derivs)
553 memset ((char *)val+type.size(), 0, 2*type.size());
554 return true;
555 }
556 return false;
557 }
558
559
560 bool
get_camera_shutter_close(ShaderGlobals *,bool derivs,ustring,TypeDesc type,ustring,void * val)561 SimpleRenderer::get_camera_shutter_close (ShaderGlobals* /*sg*/, bool derivs, ustring /*object*/,
562 TypeDesc type, ustring /*name*/, void *val)
563 {
564 if (type == TypeDesc::TypeFloat) {
565 ((float *)val)[0] = m_shutter[1];
566 if (derivs)
567 memset ((char *)val+type.size(), 0, 2*type.size());
568 return true;
569 }
570 return false;
571 }
572
573
574 bool
get_camera_screen_window(ShaderGlobals *,bool derivs,ustring,TypeDesc type,ustring,void * val)575 SimpleRenderer::get_camera_screen_window (ShaderGlobals* /*sg*/, bool derivs, ustring /*object*/,
576 TypeDesc type, ustring /*name*/, void *val)
577 {
578 // N.B. in a real renderer, this may be time-dependent
579 if (type == TypeFloatArray4) {
580 ((float *)val)[0] = m_screen_window[0];
581 ((float *)val)[1] = m_screen_window[1];
582 ((float *)val)[2] = m_screen_window[2];
583 ((float *)val)[3] = m_screen_window[3];
584 if (derivs)
585 memset ((char *)val+type.size(), 0, 2*type.size());
586 return true;
587 }
588 return false;
589 }
590
591
592
593 bool
add_output(string_view varname,string_view filename,TypeDesc datatype,int nchannels)594 SimpleRenderer::add_output (string_view varname, string_view filename,
595 TypeDesc datatype, int nchannels)
596 {
597 // FIXME: use name to figure out
598 OIIO::ImageSpec spec(m_xres, m_yres, nchannels, datatype);
599 m_outputvars.emplace_back(varname);
600 m_outputbufs.emplace_back(new OIIO::ImageBuf(filename, spec));
601 OIIO::ImageBufAlgo::zero (*m_outputbufs.back());
602 return true;
603 }
604
605
606
607 OSL_NAMESPACE_EXIT
608