361a88548bdaa477f973f66fd9167b97fa75471c
[blender.git] / intern / cycles / kernel / osl / osl_shader.cpp
1 /*
2  * Copyright 2011-2013 Blender Foundation
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 #include <OSL/oslexec.h>
18
19 #include "kernel_compat_cpu.h"
20 #include "kernel_montecarlo.h"
21 #include "kernel_types.h"
22 #include "kernel_globals.h"
23
24 #include "geom/geom_object.h"
25
26 #include "closure/bsdf_diffuse.h"
27 #include "closure/bssrdf.h"
28
29 #include "osl_bssrdf.h"
30 #include "osl_closures.h"
31 #include "osl_globals.h"
32 #include "osl_services.h"
33 #include "osl_shader.h"
34
35 #include "util_foreach.h"
36
37 #include "attribute.h"
38
39
40 CCL_NAMESPACE_BEGIN
41
42 /* Threads */
43
44 void OSLShader::thread_init(KernelGlobals *kg, KernelGlobals *kernel_globals, OSLGlobals *osl_globals)
45 {
46         /* no osl used? */
47         if(!osl_globals->use) {
48                 kg->osl = NULL;
49                 return;
50         }
51
52         /* per thread kernel data init*/
53         kg->osl = osl_globals;
54         kg->osl->services->thread_init(kernel_globals, osl_globals->ts);
55
56         OSL::ShadingSystem *ss = kg->osl->ss;
57         OSLThreadData *tdata = new OSLThreadData();
58
59         memset(&tdata->globals, 0, sizeof(OSL::ShaderGlobals));
60         tdata->globals.tracedata = &tdata->tracedata;
61         tdata->globals.flipHandedness = false;
62         tdata->osl_thread_info = ss->create_thread_info();
63
64         for(int i = 0; i < SHADER_CONTEXT_NUM; i++)
65                 tdata->context[i] = ss->get_context(tdata->osl_thread_info);
66
67         tdata->oiio_thread_info = osl_globals->ts->get_perthread_info();
68
69         kg->osl_ss = (OSLShadingSystem*)ss;
70         kg->osl_tdata = tdata;
71 }
72
73 void OSLShader::thread_free(KernelGlobals *kg)
74 {
75         if(!kg->osl)
76                 return;
77
78         OSL::ShadingSystem *ss = (OSL::ShadingSystem*)kg->osl_ss;
79         OSLThreadData *tdata = kg->osl_tdata;
80
81         for(int i = 0; i < SHADER_CONTEXT_NUM; i++)
82                 ss->release_context(tdata->context[i]);
83
84         ss->destroy_thread_info(tdata->osl_thread_info);
85
86         delete tdata;
87
88         kg->osl = NULL;
89         kg->osl_ss = NULL;
90         kg->osl_tdata = NULL;
91 }
92
93 /* Globals */
94
95 static void shaderdata_to_shaderglobals(KernelGlobals *kg, ShaderData *sd, PathState *state,
96                                         int path_flag, OSLThreadData *tdata)
97 {
98         OSL::ShaderGlobals *globals = &tdata->globals;
99
100         /* copy from shader data to shader globals */
101         globals->P = TO_VEC3(sd->P);
102         globals->dPdx = TO_VEC3(sd->dP.dx);
103         globals->dPdy = TO_VEC3(sd->dP.dy);
104         globals->I = TO_VEC3(sd->I);
105         globals->dIdx = TO_VEC3(sd->dI.dx);
106         globals->dIdy = TO_VEC3(sd->dI.dy);
107         globals->N = TO_VEC3(sd->N);
108         globals->Ng = TO_VEC3(sd->Ng);
109         globals->u = sd->u;
110         globals->dudx = sd->du.dx;
111         globals->dudy = sd->du.dy;
112         globals->v = sd->v;
113         globals->dvdx = sd->dv.dx;
114         globals->dvdy = sd->dv.dy;
115         globals->dPdu = TO_VEC3(sd->dPdu);
116         globals->dPdv = TO_VEC3(sd->dPdv);
117         globals->surfacearea = (sd->object == OBJECT_NONE) ? 1.0f : object_surface_area(kg, sd->object);
118         globals->time = sd->time;
119
120         /* booleans */
121         globals->raytype = path_flag;
122         globals->backfacing = (sd->flag & SD_BACKFACING);
123
124         /* shader data to be used in services callbacks */
125         globals->renderstate = sd; 
126
127         /* hacky, we leave it to services to fetch actual object matrix */
128         globals->shader2common = sd;
129         globals->object2common = sd;
130
131         /* must be set to NULL before execute */
132         globals->Ci = NULL;
133
134         /* clear trace data */
135         tdata->tracedata.init = false;
136
137         /* used by renderservices */
138         sd->osl_globals = kg;
139         sd->osl_path_state = state;
140 }
141
142 /* Surface */
143
144 static void flatten_surface_closure_tree(ShaderData *sd, int path_flag,
145                                          const OSL::ClosureColor *closure, float3 weight = make_float3(1.0f, 1.0f, 1.0f))
146 {
147         /* OSL gives us a closure tree, we flatten it into arrays per
148          * closure type, for evaluation, sampling, etc later on. */
149
150 #if OSL_LIBRARY_VERSION_CODE < 10700
151         switch(closure->type) {
152 #else
153         switch(closure->id) {
154 #endif
155                 case OSL::ClosureColor::MUL: {
156                         OSL::ClosureMul *mul = (OSL::ClosureMul *)closure;
157                         flatten_surface_closure_tree(sd, path_flag, mul->closure, TO_FLOAT3(mul->weight) * weight);
158                         break;
159                 }
160                 case OSL::ClosureColor::ADD: {
161                         OSL::ClosureAdd *add = (OSL::ClosureAdd *)closure;
162                         flatten_surface_closure_tree(sd, path_flag, add->closureA, weight);
163                         flatten_surface_closure_tree(sd, path_flag, add->closureB, weight);
164                         break;
165                 }
166                 default: {
167                         OSL::ClosureComponent *comp = (OSL::ClosureComponent *)closure;
168                         CClosurePrimitive *prim = (CClosurePrimitive *)comp->data();
169
170                         if(prim) {
171                                 ShaderClosure sc;
172
173 #ifdef OSL_SUPPORTS_WEIGHTED_CLOSURE_COMPONENTS
174                                 weight = weight*TO_FLOAT3(comp->w);
175 #endif
176                                 sc.weight = weight;
177
178                                 prim->setup();
179
180                                 switch(prim->category) {
181                                         case CClosurePrimitive::BSDF: {
182                                                 CBSDFClosure *bsdf = (CBSDFClosure *)prim;
183                                                 int scattering = bsdf->scattering();
184
185                                                 /* caustic options */
186                                                 if((scattering & LABEL_GLOSSY) && (path_flag & PATH_RAY_DIFFUSE)) {
187                                                         KernelGlobals *kg = sd->osl_globals;
188
189                                                         if((!kernel_data.integrator.caustics_reflective && (scattering & LABEL_REFLECT)) ||
190                                                            (!kernel_data.integrator.caustics_refractive && (scattering & LABEL_TRANSMIT)))
191                                                         {
192                                                                 return;
193                                                         }
194                                                 }
195
196                                                 /* sample weight */
197                                                 float sample_weight = fabsf(average(weight));
198
199                                                 sc.sample_weight = sample_weight;
200
201                                                 sc.type = bsdf->sc.type;
202                                                 sc.N = bsdf->sc.N;
203                                                 sc.T = bsdf->sc.T;
204                                                 sc.data0 = bsdf->sc.data0;
205                                                 sc.data1 = bsdf->sc.data1;
206                                                 sc.data2 = bsdf->sc.data2;
207                                                 sc.prim = bsdf->sc.prim;
208
209                                                 /* add */
210                                                 if(sc.sample_weight > CLOSURE_WEIGHT_CUTOFF && sd->num_closure < MAX_CLOSURE) {
211                                                         sd->closure[sd->num_closure++] = sc;
212                                                         sd->flag |= bsdf->shaderdata_flag();
213                                                 }
214                                                 break;
215                                         }
216                                         case CClosurePrimitive::Emissive: {
217                                                 /* sample weight */
218                                                 float sample_weight = fabsf(average(weight));
219
220                                                 sc.sample_weight = sample_weight;
221                                                 sc.type = CLOSURE_EMISSION_ID;
222                                                 sc.data0 = 0.0f;
223                                                 sc.data1 = 0.0f;
224                                                 sc.data2 = 0.0f;
225                                                 sc.prim = NULL;
226
227                                                 /* flag */
228                                                 if(sd->num_closure < MAX_CLOSURE) {
229                                                         sd->closure[sd->num_closure++] = sc;
230                                                         sd->flag |= SD_EMISSION;
231                                                 }
232                                                 break;
233                                         }
234                                         case CClosurePrimitive::AmbientOcclusion: {
235                                                 /* sample weight */
236                                                 float sample_weight = fabsf(average(weight));
237
238                                                 sc.sample_weight = sample_weight;
239                                                 sc.type = CLOSURE_AMBIENT_OCCLUSION_ID;
240                                                 sc.data0 = 0.0f;
241                                                 sc.data1 = 0.0f;
242                                                 sc.data2 = 0.0f;
243                                                 sc.prim = NULL;
244
245                                                 if(sd->num_closure < MAX_CLOSURE) {
246                                                         sd->closure[sd->num_closure++] = sc;
247                                                         sd->flag |= SD_AO;
248                                                 }
249                                                 break;
250                                         }
251                                         case CClosurePrimitive::Holdout: {
252                                                 sc.sample_weight = 0.0f;
253                                                 sc.type = CLOSURE_HOLDOUT_ID;
254                                                 sc.data0 = 0.0f;
255                                                 sc.data1 = 0.0f;
256                                                 sc.data2 = 0.0f;
257                                                 sc.prim = NULL;
258
259                                                 if(sd->num_closure < MAX_CLOSURE) {
260                                                         sd->closure[sd->num_closure++] = sc;
261                                                         sd->flag |= SD_HOLDOUT;
262                                                 }
263                                                 break;
264                                         }
265                                         case CClosurePrimitive::BSSRDF: {
266                                                 CBSSRDFClosure *bssrdf = (CBSSRDFClosure *)prim;
267                                                 float sample_weight = fabsf(average(weight));
268
269                                                 if(sample_weight > CLOSURE_WEIGHT_CUTOFF && sd->num_closure+2 < MAX_CLOSURE) {
270                                                         sc.sample_weight = sample_weight;
271
272                                                         sc.type = bssrdf->sc.type;
273                                                         sc.N = bssrdf->sc.N;
274                                                         sc.data1 = bssrdf->sc.data1;
275                                                         sc.T.x = bssrdf->sc.T.x;
276                                                         sc.prim = NULL;
277
278                                                         /* disable in case of diffuse ancestor, can't see it well then and
279                                                          * adds considerably noise due to probabilities of continuing path
280                                                          * getting lower and lower */
281                                                         if(path_flag & PATH_RAY_DIFFUSE_ANCESTOR)
282                                                                 bssrdf->radius = make_float3(0.0f, 0.0f, 0.0f);
283
284                                                         float3 albedo =
285                                                                 (bssrdf->sc.type == CLOSURE_BSSRDF_BURLEY_ID)
286                                                                         ? bssrdf->albedo
287                                                                         : make_float3(0.0f, 0.0f, 0.0f);
288
289                                                         /* create one closure for each color channel */
290                                                         if(fabsf(weight.x) > 0.0f) {
291                                                                 sc.weight = make_float3(weight.x, 0.0f, 0.0f);
292                                                                 sc.data0 = bssrdf->radius.x;
293                                                                 sc.data1 = 0.0f;
294                                                                 sc.data2 = albedo.x;
295                                                                 sd->flag |= bssrdf_setup(&sc, sc.type);
296                                                                 sd->closure[sd->num_closure++] = sc;
297                                                         }
298
299                                                         if(fabsf(weight.y) > 0.0f) {
300                                                                 sc.weight = make_float3(0.0f, weight.y, 0.0f);
301                                                                 sc.data0 = bssrdf->radius.y;
302                                                                 sc.data1 = 0.0f;
303                                                                 sc.data2 = albedo.y;
304                                                                 sd->flag |= bssrdf_setup(&sc, sc.type);
305                                                                 sd->closure[sd->num_closure++] = sc;
306                                                         }
307
308                                                         if(fabsf(weight.z) > 0.0f) {
309                                                                 sc.weight = make_float3(0.0f, 0.0f, weight.z);
310                                                                 sc.data0 = bssrdf->radius.z;
311                                                                 sc.data1 = 0.0f;
312                                                                 sc.data2 = albedo.z;
313                                                                 sd->flag |= bssrdf_setup(&sc, sc.type);
314                                                                 sd->closure[sd->num_closure++] = sc;
315                                                         }
316                                                 }
317                                                 break;
318                                         }
319                                         case CClosurePrimitive::Background:
320                                         case CClosurePrimitive::Volume:
321                                                 break; /* not relevant */
322                                 }
323                         }
324                         break;
325                 }
326         }
327 }
328
329 void OSLShader::eval_surface(KernelGlobals *kg, ShaderData *sd, PathState *state, int path_flag, ShaderContext ctx)
330 {
331         /* setup shader globals from shader data */
332         OSLThreadData *tdata = kg->osl_tdata;
333         shaderdata_to_shaderglobals(kg, sd, state, path_flag, tdata);
334
335         /* execute shader for this point */
336         OSL::ShadingSystem *ss = (OSL::ShadingSystem*)kg->osl_ss;
337         OSL::ShaderGlobals *globals = &tdata->globals;
338         OSL::ShadingContext *octx = tdata->context[(int)ctx];
339         int shader = sd->shader & SHADER_MASK;
340
341         if(kg->osl->surface_state[shader]) {
342                 ss->execute(octx, *(kg->osl->surface_state[shader]), *globals);
343         }
344
345         /* flatten closure tree */
346         if(globals->Ci)
347                 flatten_surface_closure_tree(sd, path_flag, globals->Ci);
348 }
349
350 /* Background */
351
352 static float3 flatten_background_closure_tree(const OSL::ClosureColor *closure)
353 {
354         /* OSL gives us a closure tree, if we are shading for background there
355          * is only one supported closure type at the moment, which has no evaluation
356          * functions, so we just sum the weights */
357
358 #if OSL_LIBRARY_VERSION_CODE < 10700
359         switch(closure->type) {
360 #else
361         switch(closure->id) {
362 #endif
363                 case OSL::ClosureColor::MUL: {
364                         OSL::ClosureMul *mul = (OSL::ClosureMul *)closure;
365
366                         return TO_FLOAT3(mul->weight) * flatten_background_closure_tree(mul->closure);
367                 }
368                 case OSL::ClosureColor::ADD: {
369                         OSL::ClosureAdd *add = (OSL::ClosureAdd *)closure;
370
371                         return flatten_background_closure_tree(add->closureA) +
372                                flatten_background_closure_tree(add->closureB);
373                 }
374                 default: {
375                         OSL::ClosureComponent *comp = (OSL::ClosureComponent *)closure;
376                         CClosurePrimitive *prim = (CClosurePrimitive *)comp->data();
377
378                         if(prim && prim->category == CClosurePrimitive::Background)
379 #ifdef OSL_SUPPORTS_WEIGHTED_CLOSURE_COMPONENTS
380                                 return TO_FLOAT3(comp->w);
381 #else
382                                 return make_float3(1.0f, 1.0f, 1.0f);
383 #endif
384                 }
385         }
386
387         return make_float3(0.0f, 0.0f, 0.0f);
388 }
389
390 float3 OSLShader::eval_background(KernelGlobals *kg, ShaderData *sd, PathState *state, int path_flag, ShaderContext ctx)
391 {
392         /* setup shader globals from shader data */
393         OSLThreadData *tdata = kg->osl_tdata;
394         shaderdata_to_shaderglobals(kg, sd, state, path_flag, tdata);
395
396         /* execute shader for this point */
397         OSL::ShadingSystem *ss = (OSL::ShadingSystem*)kg->osl_ss;
398         OSL::ShaderGlobals *globals = &tdata->globals;
399         OSL::ShadingContext *octx = tdata->context[(int)ctx];
400
401         if(kg->osl->background_state) {
402                 ss->execute(octx, *(kg->osl->background_state), *globals);
403         }
404
405         /* return background color immediately */
406         if(globals->Ci)
407                 return flatten_background_closure_tree(globals->Ci);
408
409         return make_float3(0.0f, 0.0f, 0.0f);
410 }
411
412 /* Volume */
413
414 static void flatten_volume_closure_tree(ShaderData *sd,
415                                         const OSL::ClosureColor *closure, float3 weight = make_float3(1.0f, 1.0f, 1.0f))
416 {
417         /* OSL gives us a closure tree, we flatten it into arrays per
418          * closure type, for evaluation, sampling, etc later on. */
419
420 #if OSL_LIBRARY_VERSION_CODE < 10700
421         switch(closure->type) {
422 #else
423         switch(closure->id) {
424 #endif
425                 case OSL::ClosureColor::MUL: {
426                         OSL::ClosureMul *mul = (OSL::ClosureMul *)closure;
427                         flatten_volume_closure_tree(sd, mul->closure, TO_FLOAT3(mul->weight) * weight);
428                         break;
429                 }
430                 case OSL::ClosureColor::ADD: {
431                         OSL::ClosureAdd *add = (OSL::ClosureAdd *)closure;
432                         flatten_volume_closure_tree(sd, add->closureA, weight);
433                         flatten_volume_closure_tree(sd, add->closureB, weight);
434                         break;
435                 }
436                 default: {
437                         OSL::ClosureComponent *comp = (OSL::ClosureComponent *)closure;
438                         CClosurePrimitive *prim = (CClosurePrimitive *)comp->data();
439
440                         if(prim) {
441                                 ShaderClosure sc;
442
443 #ifdef OSL_SUPPORTS_WEIGHTED_CLOSURE_COMPONENTS
444                                 weight = weight*TO_FLOAT3(comp->w);
445 #endif
446                                 sc.weight = weight;
447
448                                 prim->setup();
449
450                                 switch(prim->category) {
451                                         case CClosurePrimitive::Volume: {
452                                                 CVolumeClosure *volume = (CVolumeClosure *)prim;
453                                                 /* sample weight */
454                                                 float sample_weight = fabsf(average(weight));
455
456                                                 sc.sample_weight = sample_weight;
457                                                 sc.type = volume->sc.type;
458                                                 sc.data0 = volume->sc.data0;
459                                                 sc.data1 = volume->sc.data1;
460
461                                                 /* add */
462                                                 if((sc.sample_weight > CLOSURE_WEIGHT_CUTOFF) &&
463                                                    (sd->num_closure < MAX_CLOSURE))
464                                                 {
465                                                         sd->closure[sd->num_closure++] = sc;
466                                                         sd->flag |= volume->shaderdata_flag();
467                                                 }
468                                                 break;
469                                         }
470                                         case CClosurePrimitive::Emissive: {
471                                                 /* sample weight */
472                                                 float sample_weight = fabsf(average(weight));
473
474                                                 sc.sample_weight = sample_weight;
475                                                 sc.type = CLOSURE_EMISSION_ID;
476                                                 sc.data0 = 0.0f;
477                                                 sc.data1 = 0.0f;
478                                                 sc.prim = NULL;
479
480                                                 /* flag */
481                                                 if(sd->num_closure < MAX_CLOSURE) {
482                                                         sd->closure[sd->num_closure++] = sc;
483                                                         sd->flag |= SD_EMISSION;
484                                                 }
485                                                 break;
486                                         }
487                                         case CClosurePrimitive::Holdout:
488                                                 break; /* not implemented */
489                                         case CClosurePrimitive::Background:
490                                         case CClosurePrimitive::BSDF:
491                                         case CClosurePrimitive::BSSRDF:
492                                         case CClosurePrimitive::AmbientOcclusion:
493                                                 break; /* not relevant */
494                                 }
495                         }
496                 }
497         }
498 }
499
500 void OSLShader::eval_volume(KernelGlobals *kg, ShaderData *sd, PathState *state, int path_flag, ShaderContext ctx)
501 {
502         /* setup shader globals from shader data */
503         OSLThreadData *tdata = kg->osl_tdata;
504         shaderdata_to_shaderglobals(kg, sd, state, path_flag, tdata);
505
506         /* execute shader */
507         OSL::ShadingSystem *ss = (OSL::ShadingSystem*)kg->osl_ss;
508         OSL::ShaderGlobals *globals = &tdata->globals;
509         OSL::ShadingContext *octx = tdata->context[(int)ctx];
510         int shader = sd->shader & SHADER_MASK;
511
512         if(kg->osl->volume_state[shader]) {
513                 ss->execute(octx, *(kg->osl->volume_state[shader]), *globals);
514         }
515         
516         /* flatten closure tree */
517         if(globals->Ci)
518                 flatten_volume_closure_tree(sd, globals->Ci);
519 }
520
521 /* Displacement */
522
523 void OSLShader::eval_displacement(KernelGlobals *kg, ShaderData *sd, ShaderContext ctx)
524 {
525         /* setup shader globals from shader data */
526         OSLThreadData *tdata = kg->osl_tdata;
527
528         PathState state = {0};
529
530         shaderdata_to_shaderglobals(kg, sd, &state, 0, tdata);
531
532         /* execute shader */
533         OSL::ShadingSystem *ss = (OSL::ShadingSystem*)kg->osl_ss;
534         OSL::ShaderGlobals *globals = &tdata->globals;
535         OSL::ShadingContext *octx = tdata->context[(int)ctx];
536         int shader = sd->shader & SHADER_MASK;
537
538         if(kg->osl->displacement_state[shader]) {
539                 ss->execute(octx, *(kg->osl->displacement_state[shader]), *globals);
540         }
541
542         /* get back position */
543         sd->P = TO_FLOAT3(globals->P);
544 }
545
546 /* BSDF Closure */
547
548 int OSLShader::bsdf_sample(const ShaderData *sd, const ShaderClosure *sc, float randu, float randv, float3& eval, float3& omega_in, differential3& domega_in, float& pdf)
549 {
550         CBSDFClosure *sample_bsdf = (CBSDFClosure *)sc->prim;
551
552         pdf = 0.0f;
553
554         return sample_bsdf->sample(sd->Ng,
555                                    sd->I, sd->dI.dx, sd->dI.dy,
556                                    randu, randv,
557                                    omega_in, domega_in.dx, domega_in.dy,
558                                    pdf, eval);
559 }
560
561 float3 OSLShader::bsdf_eval(const ShaderData *sd, const ShaderClosure *sc, const float3& omega_in, float& pdf)
562 {
563         CBSDFClosure *bsdf = (CBSDFClosure *)sc->prim;
564         float3 bsdf_eval;
565
566         if(dot(sd->Ng, omega_in) >= 0.0f)
567                 bsdf_eval = bsdf->eval_reflect(sd->I, omega_in, pdf);
568         else
569                 bsdf_eval = bsdf->eval_transmit(sd->I, omega_in, pdf);
570         
571         return bsdf_eval;
572 }
573
574 void OSLShader::bsdf_blur(ShaderClosure *sc, float roughness)
575 {
576         CBSDFClosure *bsdf = (CBSDFClosure *)sc->prim;
577         bsdf->blur(roughness);
578 }
579
580 /* Attributes */
581
582 int OSLShader::find_attribute(KernelGlobals *kg, const ShaderData *sd, uint id, AttributeElement *elem)
583 {
584         /* for OSL, a hash map is used to lookup the attribute by name. */
585         int object = sd->object*ATTR_PRIM_TYPES;
586 #ifdef __HAIR__
587         if(sd->type & PRIMITIVE_ALL_CURVE) object += ATTR_PRIM_CURVE;
588 #endif
589
590         OSLGlobals::AttributeMap &attr_map = kg->osl->attribute_map[object];
591         ustring stdname(std::string("geom:") + std::string(Attribute::standard_name((AttributeStandard)id)));
592         OSLGlobals::AttributeMap::const_iterator it = attr_map.find(stdname);
593
594         if(it != attr_map.end()) {
595                 const OSLGlobals::Attribute &osl_attr = it->second;
596                 *elem = osl_attr.elem;
597
598                 if(sd->prim == PRIM_NONE && (AttributeElement)osl_attr.elem != ATTR_ELEMENT_MESH)
599                         return ATTR_STD_NOT_FOUND;
600
601                 /* return result */
602                 return (osl_attr.elem == ATTR_ELEMENT_NONE) ? (int)ATTR_STD_NOT_FOUND : osl_attr.offset;
603         }
604         else
605                 return (int)ATTR_STD_NOT_FOUND;
606 }
607
608 CCL_NAMESPACE_END
609