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