1 /*
2  * Licensed under the Apache License, Version 2.0 (the "License");
3  * you may not use this file except in compliance with the License.
4  * You may obtain a copy of the License at
5  *
6  * http://www.apache.org/licenses/LICENSE-2.0
7  *
8  * Unless required by applicable law or agreed to in writing, software
9  * distributed under the License is distributed on an "AS IS" BASIS,
10  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11  * See the License for the specific language governing permissions and
12  * limitations under the License.
13  */
14 
15 /* Object Primitive
16  *
17  * All mesh and curve primitives are part of an object. The same mesh and curves
18  * may be instanced multiple times by different objects.
19  *
20  * If the mesh is not instanced multiple times, the object will not be explicitly
21  * stored as a primitive in the BVH, rather the bare triangles are curved are
22  * directly primitives in the BVH with world space locations applied, and the object
23  * ID is looked up afterwards. */
24 
25 CCL_NAMESPACE_BEGIN
26 
27 /* Object attributes, for now a fixed size and contents */
28 
29 enum ObjectTransform {
30   OBJECT_TRANSFORM = 0,
31   OBJECT_INVERSE_TRANSFORM = 1,
32 };
33 
34 enum ObjectVectorTransform { OBJECT_PASS_MOTION_PRE = 0, OBJECT_PASS_MOTION_POST = 1 };
35 
36 /* Object to world space transformation */
37 
object_fetch_transform(KernelGlobals * kg,int object,enum ObjectTransform type)38 ccl_device_inline Transform object_fetch_transform(KernelGlobals *kg,
39                                                    int object,
40                                                    enum ObjectTransform type)
41 {
42   if (type == OBJECT_INVERSE_TRANSFORM) {
43     return kernel_tex_fetch(__objects, object).itfm;
44   }
45   else {
46     return kernel_tex_fetch(__objects, object).tfm;
47   }
48 }
49 
50 /* Lamp to world space transformation */
51 
lamp_fetch_transform(KernelGlobals * kg,int lamp,bool inverse)52 ccl_device_inline Transform lamp_fetch_transform(KernelGlobals *kg, int lamp, bool inverse)
53 {
54   if (inverse) {
55     return kernel_tex_fetch(__lights, lamp).itfm;
56   }
57   else {
58     return kernel_tex_fetch(__lights, lamp).tfm;
59   }
60 }
61 
62 /* Object to world space transformation for motion vectors */
63 
object_fetch_motion_pass_transform(KernelGlobals * kg,int object,enum ObjectVectorTransform type)64 ccl_device_inline Transform object_fetch_motion_pass_transform(KernelGlobals *kg,
65                                                                int object,
66                                                                enum ObjectVectorTransform type)
67 {
68   int offset = object * OBJECT_MOTION_PASS_SIZE + (int)type;
69   return kernel_tex_fetch(__object_motion_pass, offset);
70 }
71 
72 /* Motion blurred object transformations */
73 
74 #ifdef __OBJECT_MOTION__
object_fetch_transform_motion(KernelGlobals * kg,int object,float time)75 ccl_device_inline Transform object_fetch_transform_motion(KernelGlobals *kg,
76                                                           int object,
77                                                           float time)
78 {
79   const uint motion_offset = kernel_tex_fetch(__objects, object).motion_offset;
80   const ccl_global DecomposedTransform *motion = &kernel_tex_fetch(__object_motion, motion_offset);
81   const uint num_steps = kernel_tex_fetch(__objects, object).numsteps * 2 + 1;
82 
83   Transform tfm;
84   transform_motion_array_interpolate(&tfm, motion, num_steps, time);
85 
86   return tfm;
87 }
88 
object_fetch_transform_motion_test(KernelGlobals * kg,int object,float time,Transform * itfm)89 ccl_device_inline Transform object_fetch_transform_motion_test(KernelGlobals *kg,
90                                                                int object,
91                                                                float time,
92                                                                Transform *itfm)
93 {
94   int object_flag = kernel_tex_fetch(__object_flag, object);
95   if (object_flag & SD_OBJECT_MOTION) {
96     /* if we do motion blur */
97     Transform tfm = object_fetch_transform_motion(kg, object, time);
98 
99     if (itfm)
100       *itfm = transform_quick_inverse(tfm);
101 
102     return tfm;
103   }
104   else {
105     Transform tfm = object_fetch_transform(kg, object, OBJECT_TRANSFORM);
106     if (itfm)
107       *itfm = object_fetch_transform(kg, object, OBJECT_INVERSE_TRANSFORM);
108 
109     return tfm;
110   }
111 }
112 #endif
113 
114 /* Transform position from object to world space */
115 
object_position_transform(KernelGlobals * kg,const ShaderData * sd,float3 * P)116 ccl_device_inline void object_position_transform(KernelGlobals *kg,
117                                                  const ShaderData *sd,
118                                                  float3 *P)
119 {
120 #ifdef __OBJECT_MOTION__
121   *P = transform_point_auto(&sd->ob_tfm, *P);
122 #else
123   Transform tfm = object_fetch_transform(kg, sd->object, OBJECT_TRANSFORM);
124   *P = transform_point(&tfm, *P);
125 #endif
126 }
127 
128 /* Transform position from world to object space */
129 
object_inverse_position_transform(KernelGlobals * kg,const ShaderData * sd,float3 * P)130 ccl_device_inline void object_inverse_position_transform(KernelGlobals *kg,
131                                                          const ShaderData *sd,
132                                                          float3 *P)
133 {
134 #ifdef __OBJECT_MOTION__
135   *P = transform_point_auto(&sd->ob_itfm, *P);
136 #else
137   Transform tfm = object_fetch_transform(kg, sd->object, OBJECT_INVERSE_TRANSFORM);
138   *P = transform_point(&tfm, *P);
139 #endif
140 }
141 
142 /* Transform normal from world to object space */
143 
object_inverse_normal_transform(KernelGlobals * kg,const ShaderData * sd,float3 * N)144 ccl_device_inline void object_inverse_normal_transform(KernelGlobals *kg,
145                                                        const ShaderData *sd,
146                                                        float3 *N)
147 {
148 #ifdef __OBJECT_MOTION__
149   if ((sd->object != OBJECT_NONE) || (sd->type == PRIMITIVE_LAMP)) {
150     *N = normalize(transform_direction_transposed_auto(&sd->ob_tfm, *N));
151   }
152 #else
153   if (sd->object != OBJECT_NONE) {
154     Transform tfm = object_fetch_transform(kg, sd->object, OBJECT_TRANSFORM);
155     *N = normalize(transform_direction_transposed(&tfm, *N));
156   }
157   else if (sd->type == PRIMITIVE_LAMP) {
158     Transform tfm = lamp_fetch_transform(kg, sd->lamp, false);
159     *N = normalize(transform_direction_transposed(&tfm, *N));
160   }
161 #endif
162 }
163 
164 /* Transform normal from object to world space */
165 
object_normal_transform(KernelGlobals * kg,const ShaderData * sd,float3 * N)166 ccl_device_inline void object_normal_transform(KernelGlobals *kg, const ShaderData *sd, float3 *N)
167 {
168 #ifdef __OBJECT_MOTION__
169   *N = normalize(transform_direction_transposed_auto(&sd->ob_itfm, *N));
170 #else
171   Transform tfm = object_fetch_transform(kg, sd->object, OBJECT_INVERSE_TRANSFORM);
172   *N = normalize(transform_direction_transposed(&tfm, *N));
173 #endif
174 }
175 
176 /* Transform direction vector from object to world space */
177 
object_dir_transform(KernelGlobals * kg,const ShaderData * sd,float3 * D)178 ccl_device_inline void object_dir_transform(KernelGlobals *kg, const ShaderData *sd, float3 *D)
179 {
180 #ifdef __OBJECT_MOTION__
181   *D = transform_direction_auto(&sd->ob_tfm, *D);
182 #else
183   Transform tfm = object_fetch_transform(kg, sd->object, OBJECT_TRANSFORM);
184   *D = transform_direction(&tfm, *D);
185 #endif
186 }
187 
188 /* Transform direction vector from world to object space */
189 
object_inverse_dir_transform(KernelGlobals * kg,const ShaderData * sd,float3 * D)190 ccl_device_inline void object_inverse_dir_transform(KernelGlobals *kg,
191                                                     const ShaderData *sd,
192                                                     float3 *D)
193 {
194 #ifdef __OBJECT_MOTION__
195   *D = transform_direction_auto(&sd->ob_itfm, *D);
196 #else
197   Transform tfm = object_fetch_transform(kg, sd->object, OBJECT_INVERSE_TRANSFORM);
198   *D = transform_direction(&tfm, *D);
199 #endif
200 }
201 
202 /* Object center position */
203 
object_location(KernelGlobals * kg,const ShaderData * sd)204 ccl_device_inline float3 object_location(KernelGlobals *kg, const ShaderData *sd)
205 {
206   if (sd->object == OBJECT_NONE)
207     return make_float3(0.0f, 0.0f, 0.0f);
208 
209 #ifdef __OBJECT_MOTION__
210   return make_float3(sd->ob_tfm.x.w, sd->ob_tfm.y.w, sd->ob_tfm.z.w);
211 #else
212   Transform tfm = object_fetch_transform(kg, sd->object, OBJECT_TRANSFORM);
213   return make_float3(tfm.x.w, tfm.y.w, tfm.z.w);
214 #endif
215 }
216 
217 /* Total surface area of object */
218 
object_surface_area(KernelGlobals * kg,int object)219 ccl_device_inline float object_surface_area(KernelGlobals *kg, int object)
220 {
221   return kernel_tex_fetch(__objects, object).surface_area;
222 }
223 
224 /* Color of the object */
225 
object_color(KernelGlobals * kg,int object)226 ccl_device_inline float3 object_color(KernelGlobals *kg, int object)
227 {
228   if (object == OBJECT_NONE)
229     return make_float3(0.0f, 0.0f, 0.0f);
230 
231   const ccl_global KernelObject *kobject = &kernel_tex_fetch(__objects, object);
232   return make_float3(kobject->color[0], kobject->color[1], kobject->color[2]);
233 }
234 
235 /* Pass ID number of object */
236 
object_pass_id(KernelGlobals * kg,int object)237 ccl_device_inline float object_pass_id(KernelGlobals *kg, int object)
238 {
239   if (object == OBJECT_NONE)
240     return 0.0f;
241 
242   return kernel_tex_fetch(__objects, object).pass_id;
243 }
244 
245 /* Per lamp random number for shader variation */
246 
lamp_random_number(KernelGlobals * kg,int lamp)247 ccl_device_inline float lamp_random_number(KernelGlobals *kg, int lamp)
248 {
249   if (lamp == LAMP_NONE)
250     return 0.0f;
251 
252   return kernel_tex_fetch(__lights, lamp).random;
253 }
254 
255 /* Per object random number for shader variation */
256 
object_random_number(KernelGlobals * kg,int object)257 ccl_device_inline float object_random_number(KernelGlobals *kg, int object)
258 {
259   if (object == OBJECT_NONE)
260     return 0.0f;
261 
262   return kernel_tex_fetch(__objects, object).random_number;
263 }
264 
265 /* Particle ID from which this object was generated */
266 
object_particle_id(KernelGlobals * kg,int object)267 ccl_device_inline int object_particle_id(KernelGlobals *kg, int object)
268 {
269   if (object == OBJECT_NONE)
270     return 0;
271 
272   return kernel_tex_fetch(__objects, object).particle_index;
273 }
274 
275 /* Generated texture coordinate on surface from where object was instanced */
276 
object_dupli_generated(KernelGlobals * kg,int object)277 ccl_device_inline float3 object_dupli_generated(KernelGlobals *kg, int object)
278 {
279   if (object == OBJECT_NONE)
280     return make_float3(0.0f, 0.0f, 0.0f);
281 
282   const ccl_global KernelObject *kobject = &kernel_tex_fetch(__objects, object);
283   return make_float3(
284       kobject->dupli_generated[0], kobject->dupli_generated[1], kobject->dupli_generated[2]);
285 }
286 
287 /* UV texture coordinate on surface from where object was instanced */
288 
object_dupli_uv(KernelGlobals * kg,int object)289 ccl_device_inline float3 object_dupli_uv(KernelGlobals *kg, int object)
290 {
291   if (object == OBJECT_NONE)
292     return make_float3(0.0f, 0.0f, 0.0f);
293 
294   const ccl_global KernelObject *kobject = &kernel_tex_fetch(__objects, object);
295   return make_float3(kobject->dupli_uv[0], kobject->dupli_uv[1], 0.0f);
296 }
297 
298 /* Information about mesh for motion blurred triangles and curves */
299 
object_motion_info(KernelGlobals * kg,int object,int * numsteps,int * numverts,int * numkeys)300 ccl_device_inline void object_motion_info(
301     KernelGlobals *kg, int object, int *numsteps, int *numverts, int *numkeys)
302 {
303   if (numkeys) {
304     *numkeys = kernel_tex_fetch(__objects, object).numkeys;
305   }
306 
307   if (numsteps)
308     *numsteps = kernel_tex_fetch(__objects, object).numsteps;
309   if (numverts)
310     *numverts = kernel_tex_fetch(__objects, object).numverts;
311 }
312 
313 /* Offset to an objects patch map */
314 
object_patch_map_offset(KernelGlobals * kg,int object)315 ccl_device_inline uint object_patch_map_offset(KernelGlobals *kg, int object)
316 {
317   if (object == OBJECT_NONE)
318     return 0;
319 
320   return kernel_tex_fetch(__objects, object).patch_map_offset;
321 }
322 
323 /* Volume step size */
324 
object_volume_density(KernelGlobals * kg,int object)325 ccl_device_inline float object_volume_density(KernelGlobals *kg, int object)
326 {
327   if (object == OBJECT_NONE) {
328     return 1.0f;
329   }
330 
331   return kernel_tex_fetch(__objects, object).surface_area;
332 }
333 
object_volume_step_size(KernelGlobals * kg,int object)334 ccl_device_inline float object_volume_step_size(KernelGlobals *kg, int object)
335 {
336   if (object == OBJECT_NONE) {
337     return kernel_data.background.volume_step_size;
338   }
339 
340   return kernel_tex_fetch(__object_volume_step, object);
341 }
342 
343 /* Pass ID for shader */
344 
shader_pass_id(KernelGlobals * kg,const ShaderData * sd)345 ccl_device int shader_pass_id(KernelGlobals *kg, const ShaderData *sd)
346 {
347   return kernel_tex_fetch(__shaders, (sd->shader & SHADER_MASK)).pass_id;
348 }
349 
350 /* Cryptomatte ID */
351 
object_cryptomatte_id(KernelGlobals * kg,int object)352 ccl_device_inline float object_cryptomatte_id(KernelGlobals *kg, int object)
353 {
354   if (object == OBJECT_NONE)
355     return 0.0f;
356 
357   return kernel_tex_fetch(__objects, object).cryptomatte_object;
358 }
359 
object_cryptomatte_asset_id(KernelGlobals * kg,int object)360 ccl_device_inline float object_cryptomatte_asset_id(KernelGlobals *kg, int object)
361 {
362   if (object == OBJECT_NONE)
363     return 0;
364 
365   return kernel_tex_fetch(__objects, object).cryptomatte_asset;
366 }
367 
368 /* Particle data from which object was instanced */
369 
particle_index(KernelGlobals * kg,int particle)370 ccl_device_inline uint particle_index(KernelGlobals *kg, int particle)
371 {
372   return kernel_tex_fetch(__particles, particle).index;
373 }
374 
particle_age(KernelGlobals * kg,int particle)375 ccl_device float particle_age(KernelGlobals *kg, int particle)
376 {
377   return kernel_tex_fetch(__particles, particle).age;
378 }
379 
particle_lifetime(KernelGlobals * kg,int particle)380 ccl_device float particle_lifetime(KernelGlobals *kg, int particle)
381 {
382   return kernel_tex_fetch(__particles, particle).lifetime;
383 }
384 
particle_size(KernelGlobals * kg,int particle)385 ccl_device float particle_size(KernelGlobals *kg, int particle)
386 {
387   return kernel_tex_fetch(__particles, particle).size;
388 }
389 
particle_rotation(KernelGlobals * kg,int particle)390 ccl_device float4 particle_rotation(KernelGlobals *kg, int particle)
391 {
392   return kernel_tex_fetch(__particles, particle).rotation;
393 }
394 
particle_location(KernelGlobals * kg,int particle)395 ccl_device float3 particle_location(KernelGlobals *kg, int particle)
396 {
397   return float4_to_float3(kernel_tex_fetch(__particles, particle).location);
398 }
399 
particle_velocity(KernelGlobals * kg,int particle)400 ccl_device float3 particle_velocity(KernelGlobals *kg, int particle)
401 {
402   return float4_to_float3(kernel_tex_fetch(__particles, particle).velocity);
403 }
404 
particle_angular_velocity(KernelGlobals * kg,int particle)405 ccl_device float3 particle_angular_velocity(KernelGlobals *kg, int particle)
406 {
407   return float4_to_float3(kernel_tex_fetch(__particles, particle).angular_velocity);
408 }
409 
410 /* Object intersection in BVH */
411 
bvh_clamp_direction(float3 dir)412 ccl_device_inline float3 bvh_clamp_direction(float3 dir)
413 {
414   const float ooeps = 8.271806E-25f;
415   return make_float3((fabsf(dir.x) > ooeps) ? dir.x : copysignf(ooeps, dir.x),
416                      (fabsf(dir.y) > ooeps) ? dir.y : copysignf(ooeps, dir.y),
417                      (fabsf(dir.z) > ooeps) ? dir.z : copysignf(ooeps, dir.z));
418 }
419 
bvh_inverse_direction(float3 dir)420 ccl_device_inline float3 bvh_inverse_direction(float3 dir)
421 {
422   return rcp(dir);
423 }
424 
425 /* Transform ray into object space to enter static object in BVH */
426 
bvh_instance_push(KernelGlobals * kg,int object,const Ray * ray,float3 * P,float3 * dir,float3 * idir,float t)427 ccl_device_inline float bvh_instance_push(
428     KernelGlobals *kg, int object, const Ray *ray, float3 *P, float3 *dir, float3 *idir, float t)
429 {
430   Transform tfm = object_fetch_transform(kg, object, OBJECT_INVERSE_TRANSFORM);
431 
432   *P = transform_point(&tfm, ray->P);
433 
434   float len;
435   *dir = bvh_clamp_direction(normalize_len(transform_direction(&tfm, ray->D), &len));
436   *idir = bvh_inverse_direction(*dir);
437 
438   if (t != FLT_MAX) {
439     t *= len;
440   }
441 
442   return t;
443 }
444 
445 /* Transorm ray to exit static object in BVH */
446 
bvh_instance_pop(KernelGlobals * kg,int object,const Ray * ray,float3 * P,float3 * dir,float3 * idir,float t)447 ccl_device_inline float bvh_instance_pop(
448     KernelGlobals *kg, int object, const Ray *ray, float3 *P, float3 *dir, float3 *idir, float t)
449 {
450   if (t != FLT_MAX) {
451     Transform tfm = object_fetch_transform(kg, object, OBJECT_INVERSE_TRANSFORM);
452     t /= len(transform_direction(&tfm, ray->D));
453   }
454 
455   *P = ray->P;
456   *dir = bvh_clamp_direction(ray->D);
457   *idir = bvh_inverse_direction(*dir);
458 
459   return t;
460 }
461 
462 /* Same as above, but returns scale factor to apply to multiple intersection distances */
463 
bvh_instance_pop_factor(KernelGlobals * kg,int object,const Ray * ray,float3 * P,float3 * dir,float3 * idir,float * t_fac)464 ccl_device_inline void bvh_instance_pop_factor(KernelGlobals *kg,
465                                                int object,
466                                                const Ray *ray,
467                                                float3 *P,
468                                                float3 *dir,
469                                                float3 *idir,
470                                                float *t_fac)
471 {
472   Transform tfm = object_fetch_transform(kg, object, OBJECT_INVERSE_TRANSFORM);
473   *t_fac = 1.0f / len(transform_direction(&tfm, ray->D));
474 
475   *P = ray->P;
476   *dir = bvh_clamp_direction(ray->D);
477   *idir = bvh_inverse_direction(*dir);
478 }
479 
480 #ifdef __OBJECT_MOTION__
481 /* Transform ray into object space to enter motion blurred object in BVH */
482 
bvh_instance_motion_push(KernelGlobals * kg,int object,const Ray * ray,float3 * P,float3 * dir,float3 * idir,float t,Transform * itfm)483 ccl_device_inline float bvh_instance_motion_push(KernelGlobals *kg,
484                                                  int object,
485                                                  const Ray *ray,
486                                                  float3 *P,
487                                                  float3 *dir,
488                                                  float3 *idir,
489                                                  float t,
490                                                  Transform *itfm)
491 {
492   object_fetch_transform_motion_test(kg, object, ray->time, itfm);
493 
494   *P = transform_point(itfm, ray->P);
495 
496   float len;
497   *dir = bvh_clamp_direction(normalize_len(transform_direction(itfm, ray->D), &len));
498   *idir = bvh_inverse_direction(*dir);
499 
500   if (t != FLT_MAX) {
501     t *= len;
502   }
503 
504   return t;
505 }
506 
507 /* Transorm ray to exit motion blurred object in BVH */
508 
bvh_instance_motion_pop(KernelGlobals * kg,int object,const Ray * ray,float3 * P,float3 * dir,float3 * idir,float t,Transform * itfm)509 ccl_device_inline float bvh_instance_motion_pop(KernelGlobals *kg,
510                                                 int object,
511                                                 const Ray *ray,
512                                                 float3 *P,
513                                                 float3 *dir,
514                                                 float3 *idir,
515                                                 float t,
516                                                 Transform *itfm)
517 {
518   if (t != FLT_MAX) {
519     t /= len(transform_direction(itfm, ray->D));
520   }
521 
522   *P = ray->P;
523   *dir = bvh_clamp_direction(ray->D);
524   *idir = bvh_inverse_direction(*dir);
525 
526   return t;
527 }
528 
529 /* Same as above, but returns scale factor to apply to multiple intersection distances */
530 
bvh_instance_motion_pop_factor(KernelGlobals * kg,int object,const Ray * ray,float3 * P,float3 * dir,float3 * idir,float * t_fac,Transform * itfm)531 ccl_device_inline void bvh_instance_motion_pop_factor(KernelGlobals *kg,
532                                                       int object,
533                                                       const Ray *ray,
534                                                       float3 *P,
535                                                       float3 *dir,
536                                                       float3 *idir,
537                                                       float *t_fac,
538                                                       Transform *itfm)
539 {
540   *t_fac = 1.0f / len(transform_direction(itfm, ray->D));
541   *P = ray->P;
542   *dir = bvh_clamp_direction(ray->D);
543   *idir = bvh_inverse_direction(*dir);
544 }
545 
546 #endif
547 
548 /* TODO(sergey): This is only for until we've got OpenCL 2.0
549  * on all devices we consider supported. It'll be replaced with
550  * generic address space.
551  */
552 
553 #ifdef __KERNEL_OPENCL__
object_position_transform_addrspace(KernelGlobals * kg,const ShaderData * sd,ccl_addr_space float3 * P)554 ccl_device_inline void object_position_transform_addrspace(KernelGlobals *kg,
555                                                            const ShaderData *sd,
556                                                            ccl_addr_space float3 *P)
557 {
558   float3 private_P = *P;
559   object_position_transform(kg, sd, &private_P);
560   *P = private_P;
561 }
562 
object_dir_transform_addrspace(KernelGlobals * kg,const ShaderData * sd,ccl_addr_space float3 * D)563 ccl_device_inline void object_dir_transform_addrspace(KernelGlobals *kg,
564                                                       const ShaderData *sd,
565                                                       ccl_addr_space float3 *D)
566 {
567   float3 private_D = *D;
568   object_dir_transform(kg, sd, &private_D);
569   *D = private_D;
570 }
571 
object_normal_transform_addrspace(KernelGlobals * kg,const ShaderData * sd,ccl_addr_space float3 * N)572 ccl_device_inline void object_normal_transform_addrspace(KernelGlobals *kg,
573                                                          const ShaderData *sd,
574                                                          ccl_addr_space float3 *N)
575 {
576   float3 private_N = *N;
577   object_normal_transform(kg, sd, &private_N);
578   *N = private_N;
579 }
580 #endif
581 
582 #ifndef __KERNEL_OPENCL__
583 #  define object_position_transform_auto object_position_transform
584 #  define object_dir_transform_auto object_dir_transform
585 #  define object_normal_transform_auto object_normal_transform
586 #else
587 #  define object_position_transform_auto object_position_transform_addrspace
588 #  define object_dir_transform_auto object_dir_transform_addrspace
589 #  define object_normal_transform_auto object_normal_transform_addrspace
590 #endif
591 
592 CCL_NAMESPACE_END
593