1 /*
2  * Adapted from code copyright 2009-2010 NVIDIA Corporation
3  * Modifications Copyright 2011, Blender Foundation.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  * http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 
18 #include "bvh/bvh_sort.h"
19 
20 #include "bvh/bvh_build.h"
21 
22 #include "util/util_algorithm.h"
23 #include "util/util_task.h"
24 
25 CCL_NAMESPACE_BEGIN
26 
27 static const int BVH_SORT_THRESHOLD = 4096;
28 
29 struct BVHReferenceCompare {
30  public:
31   int dim;
32   const BVHUnaligned *unaligned_heuristic;
33   const Transform *aligned_space;
34 
BVHReferenceCompareBVHReferenceCompare35   BVHReferenceCompare(int dim,
36                       const BVHUnaligned *unaligned_heuristic,
37                       const Transform *aligned_space)
38       : dim(dim), unaligned_heuristic(unaligned_heuristic), aligned_space(aligned_space)
39   {
40   }
41 
get_prim_boundsBVHReferenceCompare42   __forceinline BoundBox get_prim_bounds(const BVHReference &prim) const
43   {
44     return (aligned_space != NULL) ?
45                unaligned_heuristic->compute_aligned_prim_boundbox(prim, *aligned_space) :
46                prim.bounds();
47   }
48 
49   /* Compare two references.
50    *
51    * Returns value is similar to return value of strcmp().
52    */
compareBVHReferenceCompare53   __forceinline int compare(const BVHReference &ra, const BVHReference &rb) const
54   {
55     BoundBox ra_bounds = get_prim_bounds(ra), rb_bounds = get_prim_bounds(rb);
56     float ca = ra_bounds.min[dim] + ra_bounds.max[dim];
57     float cb = rb_bounds.min[dim] + rb_bounds.max[dim];
58 
59     if (ca < cb)
60       return -1;
61     else if (ca > cb)
62       return 1;
63     else if (ra.prim_object() < rb.prim_object())
64       return -1;
65     else if (ra.prim_object() > rb.prim_object())
66       return 1;
67     else if (ra.prim_index() < rb.prim_index())
68       return -1;
69     else if (ra.prim_index() > rb.prim_index())
70       return 1;
71     else if (ra.prim_type() < rb.prim_type())
72       return -1;
73     else if (ra.prim_type() > rb.prim_type())
74       return 1;
75 
76     return 0;
77   }
78 
operator ()BVHReferenceCompare79   bool operator()(const BVHReference &ra, const BVHReference &rb)
80   {
81     return (compare(ra, rb) < 0);
82   }
83 };
84 
85 static void bvh_reference_sort_threaded(TaskPool *task_pool,
86                                         BVHReference *data,
87                                         const int job_start,
88                                         const int job_end,
89                                         const BVHReferenceCompare &compare);
90 
91 /* Multi-threaded reference sort. */
bvh_reference_sort_threaded(TaskPool * task_pool,BVHReference * data,const int job_start,const int job_end,const BVHReferenceCompare & compare)92 static void bvh_reference_sort_threaded(TaskPool *task_pool,
93                                         BVHReference *data,
94                                         const int job_start,
95                                         const int job_end,
96                                         const BVHReferenceCompare &compare)
97 {
98   int start = job_start, end = job_end;
99   bool have_work = (start < end);
100   while (have_work) {
101     const int count = job_end - job_start;
102     if (count < BVH_SORT_THRESHOLD) {
103       /* Number of reference low enough, faster to finish the job
104        * in one thread rather than to spawn more threads.
105        */
106       sort(data + job_start, data + job_end + 1, compare);
107       break;
108     }
109     /* Single QSort step.
110      * Use median-of-three method for the pivot point.
111      */
112     int left = start, right = end;
113     int center = (left + right) >> 1;
114     if (compare.compare(data[left], data[center]) > 0) {
115       swap(data[left], data[center]);
116     }
117     if (compare.compare(data[left], data[right]) > 0) {
118       swap(data[left], data[right]);
119     }
120     if (compare.compare(data[center], data[right]) > 0) {
121       swap(data[center], data[right]);
122     }
123     swap(data[center], data[right - 1]);
124     BVHReference median = data[right - 1];
125     do {
126       while (compare.compare(data[left], median) < 0) {
127         ++left;
128       }
129       while (compare.compare(data[right], median) > 0) {
130         --right;
131       }
132       if (left <= right) {
133         swap(data[left], data[right]);
134         ++left;
135         --right;
136       }
137     } while (left <= right);
138     /* We only create one new task here to reduce downside effects of
139      * latency in TaskScheduler.
140      * So generally current thread keeps working on the left part of the
141      * array, and we create new task for the right side.
142      * However, if there's nothing to be done in the left side of the array
143      * we don't create any tasks and make it so current thread works on the
144      * right side.
145      */
146     have_work = false;
147     if (left < end) {
148       if (start < right) {
149         task_pool->push(
150             function_bind(bvh_reference_sort_threaded, task_pool, data, left, end, compare));
151       }
152       else {
153         start = left;
154         have_work = true;
155       }
156     }
157     if (start < right) {
158       end = right;
159       have_work = true;
160     }
161   }
162 }
163 
bvh_reference_sort(int start,int end,BVHReference * data,int dim,const BVHUnaligned * unaligned_heuristic,const Transform * aligned_space)164 void bvh_reference_sort(int start,
165                         int end,
166                         BVHReference *data,
167                         int dim,
168                         const BVHUnaligned *unaligned_heuristic,
169                         const Transform *aligned_space)
170 {
171   const int count = end - start;
172   BVHReferenceCompare compare(dim, unaligned_heuristic, aligned_space);
173   if (count < BVH_SORT_THRESHOLD) {
174     /* It is important to not use any mutex if array is small enough,
175      * otherwise we end up in situation when we're going to sleep far
176      * too often.
177      */
178     sort(data + start, data + end, compare);
179   }
180   else {
181     TaskPool task_pool;
182     bvh_reference_sort_threaded(&task_pool, data, start, end - 1, compare);
183     task_pool.wait_work();
184   }
185 }
186 
187 CCL_NAMESPACE_END
188