HDK
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
CE_ArraySortImpl.h
Go to the documentation of this file.
1 /*
2  * PROPRIETARY INFORMATION. This software is proprietary to
3  * Side Effects Software Inc., and is not to be reproduced,
4  * transmitted, or disclosed in any way without written permission.
5  *
6  * NAME: CE_Array.h ( CE Library, C++)
7  *
8  * COMMENTS: UT_Array style class on GPU.
9  */
10 
11 #ifndef __CE_ArraySortImpl__
12 #define __CE_ArraySortImpl__
13 
14 #include "CE_ArrayView.h"
15 
16 #include <UT/UT_Swap.h>
17 #include <UT/UT_Tracing.h>
18 #include <UT/UT_WorkBuffer.h>
19 
20 // Buffer for auxiliary allocation.
21 // Necessary because CE_ArrayView cannot depend on CE_BufferDevice.
22 template <typename T>
23 template <typename V>
25 {
26 public:
27  exint size() { return mySize; }
28  const cl::Buffer &buffer() { return myBuffer; }
30 
32  {
33  if (size > 0)
34  {
36  myBuffer = context->allocBuffer(size * sizeof(V));
37  mySize = size;
38  }
39  else
40  {
41  mySize = 0;
42  }
43  }
45  {
47  context->releaseBuffer(std::move(myBuffer));
48  }
49 
50 private:
52  exint mySize;
53 };
54 
55 // Appends a type name we use as part of type defines in array.cl
56 template <typename T>
57 template <typename V>
58 void
60 {
61  if constexpr (std::is_same<V, uint8>::value)
62  wb.append("UCHAR");
63  if constexpr (std::is_same<V, int8>::value)
64  wb.append("CHAR");
65  if constexpr (std::is_same<V, uint16>::value)
66  wb.append("USHORT");
67  if constexpr (std::is_same<V, int16>::value)
68  wb.append("SHORT");
69  if constexpr (std::is_same<V, int32>::value)
70  wb.append("INT");
71  if constexpr (std::is_same<V, uint32>::value)
72  wb.append("UINT");
73  if constexpr (std::is_same<V, int64>::value)
74  wb.append("EXINT");
75  if constexpr (std::is_same<V, uint64>::value)
76  wb.append("ULONG");
78  wb.append("HALF");
80  wb.append("FLOAT");
82  wb.append("DOUBLE");
83 }
84 
85 template <typename T>
86 template <typename V>
87 void
89  int src_tuplesize, int dst_tuplesize,
90  exint src_offset, exint dst_offset,
91  exint nelements, T default_value)
92 {
93  UT_ASSERT(src_tuplesize >= 1 && dst_tuplesize >= 1);
94  if (!(src_tuplesize >= 1 && dst_tuplesize >= 1))
95  return;
96 
97  exint src_nelem = src.size() / src_tuplesize;
98  exint dst_nelem = size() / dst_tuplesize;
99  if (nelements < 0)
100  nelements = SYSmin(src_nelem - src_offset, dst_nelem - dst_offset);
101 
102  UT_ASSERT(src_offset >= 0 && dst_offset >= 0 &&
103  (src_nelem - src_offset) >= nelements &&
104  (dst_nelem - dst_offset) >= nelements);
105 
106  if (nelements > 0)
107  {
108  UT_WorkBuffer wb;
109  wb.append(" -D CEARRAY_VALUE_");
110  appendElemType<V>(wb);
111  const char *opt = wb.buffer();
112  cl::Kernel k = loadKernel("convertFrom", opt);
113 
114  CE_Context *context = CE_Context::getContext();
115  cl::NDRange global_range, local_range;
116  context->get1DRanges(k, nelements, global_range, local_range);
117  cl::KernelFunctor convert_from = k.bind(context->getQueue(),
118  global_range, local_range);
119 
120  convert_from(src.buffer(), buffer(),
121  src_tuplesize, dst_tuplesize,
122  src_offset, dst_offset,
123  nelements, scalarKernelArg(default_value));
124  }
125 }
126 
127 template <typename T>
128 template <typename V, typename I>
129 void
131  const CE_ArrayView<I> &indices,
132  int src_tuplesize, int dst_tuplesize,
133  exint dst_offset,
134  T default_value)
135 {
136  SYS_STATIC_ASSERT(SYS_IsIntegral_v<I>);
137  UT_ASSERT(src_tuplesize >= 1 && dst_tuplesize >= 1);
138  if (!(src_tuplesize >= 1 && dst_tuplesize >= 1))
139  return;
140 
141  SYS_MAYBE_UNUSED exint src_nelem = src.size() / src_tuplesize;
142  SYS_MAYBE_UNUSED exint dst_nelem = size() / dst_tuplesize;
143  UT_ASSERT((src_nelem - 1) <= std::numeric_limits<I>::max());
144  UT_ASSERT(dst_offset >= 0 && (dst_nelem - dst_offset) >= indices.size());
145 
146  if (size() > 0)
147  {
148  UT_WorkBuffer wb;
149  wb.append(" -D CEARRAY_VALUE_");
150  appendElemType<V>(wb);
151  wb.append(" -D CEARRAY_INDEX_");
152  appendElemType<I>(wb);
153  const char *opt = wb.buffer();
154  cl::Kernel k = loadKernel("convertFromIndices", opt);
155 
156  CE_Context *context = CE_Context::getContext();
157  cl::NDRange global_range, local_range;
158  context->get1DRanges(k, indices.size(), global_range, local_range);
159  cl::KernelFunctor convert_from = k.bind(context->getQueue(),
160  global_range, local_range);
161 
162  convert_from(src.buffer(), indices.buffer(), buffer(),
163  src_tuplesize, dst_tuplesize, dst_offset,
164  indices.size(), scalarKernelArg(default_value));
165  }
166 }
167 
168 template <typename T>
169 template <typename I>
170 void
172 {
173  // Index array must be an integer type with sufficient limits given size().
174  SYS_STATIC_ASSERT(SYS_IsIntegral_v<I>);
176  UT_ASSERT(order.size() == size());
177  if (!size()) return;
178 
179  UT_WorkBuffer wb;
180  wb.append(" -D CEARRAY_INDEX_");
181  appendElemType<I>(wb);
182  const char *opt = wb.buffer();
183  cl::Kernel k = loadKernel("buffer_reorder", opt);
184 
185  CE_Context *context = CE_Context::getContext();
186  cl::NDRange global_range, local_range;
187  context->get1DRanges(k, size(), global_range, local_range);
188  cl::KernelFunctor op = k.bind(context->getQueue(),
189  global_range, local_range);
190 
191  CE_BufferDevice<T> reordered(size());
192  int elemsize = sizeof(T);
193  op(buffer(), reordered.buffer(), size(), elemsize, order.buffer());
194 
195  // Copy reordered result to our buffer.
196  context->copyBuffer(reordered.buffer(), buffer(), size() * sizeof(T));
197 }
198 
199 template <typename T>
200 template <typename V>
201 void
203  CE_ArrayView<V> &vals,
204  bool is_descending,
205  int maxbits)
206 {
207  utZoneScoped;
208  exint nelem = size();
209  utZoneValue(nelem);
210  bool has_vals = !vals.isEmpty();
211  CE_Context *context = CE_Context::getContext();
212 
213  // Number of compute units and local memory size.
214  cl::Device dev = context->getDevice();
215  size_t ncompute = dev.getInfo<CL_DEVICE_MAX_COMPUTE_UNITS>();
216  size_t nlocal = dev.getInfo<CL_DEVICE_LOCAL_MEM_SIZE>();
217 
218  // Bits per radix.
219  constexpr size_t radixbits = 8;
220  // Radix value.
221  constexpr size_t radix = 1 << radixbits;
222  // How many total bits in the type.
223  size_t totalbits = 8 * sizeof(T);
224  // Limit the number of bits considered if provided.
225  // We still have to do it in multiples of radixbits.
226  if (maxbits > 0)
227  {
228  totalbits = SYSmin(SYSroundUpToMultipleOf(size_t(maxbits), radixbits),
229  totalbits);
230  }
231  size_t npasses = totalbits / radixbits;
232  // Max number of elements each workitem can process.
233  constexpr size_t maxelemperworkitem = 256;
234  // Workitems per group.
235  size_t nworkitems = 16;
236  // Usually 1K per workitem at radixbits = 8.
237  size_t localsize = sizeof(uint32) * radix * nworkitems;
238  // Give a little extra headroom in local memory.
239  while (localsize > nlocal * 1.2)
240  {
241  nworkitems /= 2;
242  localsize = sizeof(uint32) * radix * nworkitems;
243  }
244  // Elements processed per group.
245  size_t maxelempergroup = maxelemperworkitem * nworkitems;
246  // Just a heuristic, this is the maximum possible number of groups.
247  // We seem to get decreasing performance above 128.
248  size_t ngroups = SYSmin(size_t(128), ncompute * 4);
249  // Clamp groups from 1 to the min number necessary to process all elements.
250  size_t mingroups = SYSroundUpToMultipleOf(size_t(nelem), maxelempergroup) / maxelempergroup;
251  // size_t SYSclamp is ambigious on mac so use int version.
252  ngroups = SYSclamp((int)ngroups, 1, (int)mingroups);
253 
254  // TODO - write a single workgroup/single pass sort for when ngroups == 1.
255  cl::NDRange globalRange {ngroups * nworkitems};
256  cl::NDRange localRange {nworkitems};
257  const char *opt = is_descending ? "-D SORT_DESCENDING" : nullptr;
258  UT_WorkBuffer wb;
259  if (has_vals)
260  {
261  // Append a value flag and type.
262  wb.append(opt);
263  wb.append(" -D HAS_values -D CEARRAY_VALUE_");
264  appendElemType<V>(wb);
265  opt = wb.buffer();
266  }
267  cl::Kernel khist = loadKernel("radix_sort_histogram", opt);
268  cl::KernelFunctor hist = khist.bind(context->getQueue(), globalRange, localRange);
269  cl::Kernel kreorder = loadKernel("radix_sort_reorder", opt);
270  cl::KernelFunctor reorder = kreorder.bind(context->getQueue(), globalRange, localRange);
271 
272  // Each group takes nworkitems Kb at radixbits = 8.
273  Buffer<uint32> histogram(radix * nworkitems * ngroups);
274  Buffer<uint32> histosums(histogram.size());
275  auto localarg = cl::__local(localsize);
276 
277  CE_ArrayView<uint32> histogram_view = histogram.view();
278  CE_ArrayView<uint32> histosums_view = histosums.view();
279 
280  // Set up secondary key and value (if needed) buffers.
281  Buffer<T> dst_array(nelem);
282  CE_ArrayView<T> dst_arrayview = dst_array.view();
283 
284  Buffer<V> dst_vals(has_vals ? nelem : 0);
285  CE_ArrayView<V> dst_valsview = dst_vals.view();
286 
287  // Pointers to alternate src and dst arrays.
288  CE_ArrayView<T> *src = this;
289  CE_ArrayView<V> *v_src = &vals;
290  CE_ArrayView<T> *dst = &dst_arrayview;
291  CE_ArrayView<V> *v_dst = &dst_valsview;
292 
293  for(size_t pass=0; pass < npasses; pass++)
294  {
295  // Histogram.
296  hist(src->buffer(), nelem, static_cast<int>(pass), histogram.buffer(), localarg);
297  // Histogram prefix sum.
298  histogram_view.prefixSum(histosums_view);
299  // Sort, possibly with values.
300  if (has_vals)
301  {
302  reorder(src->buffer(), nelem, static_cast<int>(pass), histosums.buffer(),
303  dst->buffer(), v_src->buffer(), v_dst->buffer(), localarg);
304  UTswap(v_src, v_dst);
305  }
306  else
307  {
308  reorder(src->buffer(), nelem, static_cast<int>(pass),
309  histosums.buffer(), dst->buffer(), localarg);
310  }
311  // Swap buffers.
312  UTswap(src, dst);
313  }
314 
315  // Copy results if we had an odd number of passes
316  if (npasses & 1)
317  {
318  context->copyBuffer(dst_array.buffer(), buffer(), nelem * sizeof(T));
319  if (has_vals)
320  context->copyBuffer(dst_vals.buffer(), vals.buffer(), nelem * sizeof(V));
321  }
322 }
323 
324 #endif
OIIO_API std::vector< imagesize_t > histogram(const ImageBuf &src, int channel=0, int bins=256, float min=0.0f, float max=1.0f, bool ignore_empty=false, ROI roi={}, int nthreads=0)
#define SYS_STATIC_ASSERT(expr)
GLsizei GLenum const void * indices
Definition: glcorearb.h:406
void convertFromIndices(const CE_ArrayView< V > &src, const CE_ArrayView< I > &indices, int src_tuplesize=1, int dst_tuplesize=1, exint dst_offset=0, T default_value=0)
CE_ArrayView< V > view()
cl::Device getDevice() const
Returns the OpenCL Device object.
Definition: CE_Context.h:115
void UTswap(T &a, T &b)
Definition: UT_Swap.h:35
#define utZoneScoped
Definition: UT_Tracing.h:221
GLsizei const GLfloat * value
Definition: glcorearb.h:824
const cl::Buffer & buffer() const
Definition: CE_ArrayView.h:49
#define SYS_MAYBE_UNUSED
Definition: SYS_Compiler.h:64
LocalSpaceArg __local(::size_t size)
Definition: cl.hpp:2533
int64 exint
Definition: SYS_Types.h:125
void releaseBuffer(cl::Buffer &&buf, bool use_pool=true)
Release the specified buffer, possibly to the CE_MemoryPool.
SYS_FORCE_INLINE const char * buffer() const
void convertFrom(const CE_ArrayView< V > &src, int src_tuplesize=1, int dst_tuplesize=1, exint src_offset=0, exint dst_offset=0, exint nelements=-1, T default_value=0)
GLuint buffer
Definition: glcorearb.h:660
void prefixSum(CE_ArrayView< T > &dst, bool exclusive=true, CE_ScanOp op=CE_ScanOp::ADD) const
cl::CommandQueue getQueue() const
Definition: CE_Context.h:112
#define utZoneValue(value)
Definition: UT_Tracing.h:236
static CE_Context * getContext(bool gl_shared=true, bool shared_fallback=true)
const cl::Buffer myBuffer
Definition: CE_ArrayView.h:198
exint size() const
Returns the number of elements in the array.
Definition: CE_ArrayView.h:47
void reorder(const CE_ArrayView< I > &order)
UT_Vector3T< T > SYSclamp(const UT_Vector3T< T > &v, const UT_Vector3T< T > &min, const UT_Vector3T< T > &max)
Definition: UT_Vector3.h:1059
GLdouble GLdouble GLint GLint order
Definition: glad.h:2676
const exint mySize
Definition: CE_ArrayView.h:199
cl::Buffer allocBuffer(int64 size, bool use_pool=true, bool read=true, bool write=true, uint32 ogl_bind=SYS_UINT32_MAX)
#define CL_DEVICE_LOCAL_MEM_SIZE
Definition: cl.h:342
void sortInternal(CE_ArrayView< V > &vals, bool is_descending, int maxbits)
static void appendElemType(UT_WorkBuffer &wb)
void get1DRanges(const cl::Kernel &k, size_t items, cl::NDRange &g, cl::NDRange &l)
const cl::Buffer & buffer()
GLsizeiptr size
Definition: glcorearb.h:664
GLenum GLenum dst
Definition: glcorearb.h:1793
#define CL_DEVICE_MAX_COMPUTE_UNITS
Definition: cl.h:309
cl_int getInfo(cl_device_info name, T *param) const
Definition: cl.hpp:1289
SYS_FORCE_INLINE void append(char character)
ImageBuf OIIO_API max(Image_or_Const A, Image_or_Const B, ROI roi={}, int nthreads=0)
Kernel functor interface.
Definition: cl.hpp:3587
unsigned int uint32
Definition: SYS_Types.h:40
Memory buffer interface.
Definition: cl.hpp:1867
NDRange interface.
Definition: cl.hpp:2466
const cl::Buffer & buffer() const
Kernel interface that implements cl_kernel.
Definition: cl.hpp:2544
#define UT_ASSERT(ZZ)
Definition: UT_Assert.h:165
KernelFunctor bind(const CommandQueue &queue, const NDRange &offset, const NDRange &global, const NDRange &local)
Definition: cl.hpp:3999
Device interface for cl_device_id.
Definition: cl.hpp:1265
#define SYSmin(a, b)
Definition: SYS_Math.h:1953
bool isEmpty() const
Definition: CE_ArrayView.h:48
void copyBuffer(const cl::Buffer &src, const cl::Buffer &dst, size_t size, size_t src_offset=0, size_t dst_offset=0)
GLenum src
Definition: glcorearb.h:1793