1// Copyright 2009-2021 Intel Corporation
2// SPDX-License-Identifier: Apache-2.0
3
4#include "device.h"
5#include "../hash.h"
6#include "scene_triangle_mesh.h"
7#include "scene_user_geometry.h"
8#include "scene_instance.h"
9#include "scene_curves.h"
10#include "scene_subdiv_mesh.h"
11
12#include "../subdiv/tessellation_cache.h"
13
14#include "acceln.h"
15#include "geometry.h"
16
17#include "../geometry/cylinder.h"
18
19#include "../bvh/bvh4_factory.h"
20#include "../bvh/bvh8_factory.h"
21
22#include "../../common/tasking/taskscheduler.h"
23#include "../../common/sys/alloc.h"
24
25namespace embree
26{
27 /*! some global variables that can be set via rtcSetParameter1i for debugging purposes */
28 ssize_t Device::debug_int0 = 0;
29 ssize_t Device::debug_int1 = 0;
30 ssize_t Device::debug_int2 = 0;
31 ssize_t Device::debug_int3 = 0;
32
33 DECLARE_SYMBOL2(RayStreamFilterFuncs,rayStreamFilterFuncs);
34
35 static MutexSys g_mutex;
36 static std::map<Device*,size_t> g_cache_size_map;
37 static std::map<Device*,size_t> g_num_threads_map;
38
39 Device::Device (const char* cfg)
40 {
41 /* check that CPU supports lowest ISA */
42 if (!hasISA(ISA)) {
43 throw_RTCError(RTC_ERROR_UNSUPPORTED_CPU,"CPU does not support " ISA_STR);
44 }
45
46 /* set default frequency level for detected CPU */
47 switch (getCPUModel()) {
48 case CPU::UNKNOWN: frequency_level = FREQUENCY_SIMD256; break;
49 case CPU::XEON_ICE_LAKE: frequency_level = FREQUENCY_SIMD256; break;
50 case CPU::CORE_ICE_LAKE: frequency_level = FREQUENCY_SIMD256; break;
51 case CPU::CORE_TIGER_LAKE: frequency_level = FREQUENCY_SIMD128; break;
52 case CPU::CORE_COMET_LAKE: frequency_level = FREQUENCY_SIMD128; break;
53 case CPU::CORE_CANNON_LAKE:frequency_level = FREQUENCY_SIMD128; break;
54 case CPU::CORE_KABY_LAKE: frequency_level = FREQUENCY_SIMD128; break;
55 case CPU::XEON_SKY_LAKE: frequency_level = FREQUENCY_SIMD128; break;
56 case CPU::CORE_SKY_LAKE: frequency_level = FREQUENCY_SIMD128; break;
57 case CPU::XEON_BROADWELL: frequency_level = FREQUENCY_SIMD256; break;
58 case CPU::CORE_BROADWELL: frequency_level = FREQUENCY_SIMD256; break;
59 case CPU::XEON_HASWELL: frequency_level = FREQUENCY_SIMD256; break;
60 case CPU::CORE_HASWELL: frequency_level = FREQUENCY_SIMD256; break;
61 case CPU::XEON_IVY_BRIDGE: frequency_level = FREQUENCY_SIMD256; break;
62 case CPU::CORE_IVY_BRIDGE: frequency_level = FREQUENCY_SIMD256; break;
63 case CPU::SANDY_BRIDGE: frequency_level = FREQUENCY_SIMD256; break;
64 case CPU::NEHALEM: frequency_level = FREQUENCY_SIMD128; break;
65 case CPU::CORE2: frequency_level = FREQUENCY_SIMD128; break;
66 case CPU::CORE1: frequency_level = FREQUENCY_SIMD128; break;
67 case CPU::XEON_PHI_KNIGHTS_MILL : frequency_level = FREQUENCY_SIMD512; break;
68 case CPU::XEON_PHI_KNIGHTS_LANDING: frequency_level = FREQUENCY_SIMD512; break;
69#if defined(__APPLE__)
70 case CPU::ARM: frequency_level = FREQUENCY_SIMD256; break; // Apple M1 supports high throughput for SIMD4
71#else
72 case CPU::ARM: frequency_level = FREQUENCY_SIMD128; break;
73#endif
74 }
75
76 /* initialize global state */
77#if defined(EMBREE_CONFIG)
78 State::parseString(EMBREE_CONFIG);
79#endif
80 State::parseString(cfg);
81 State::verify();
82
83 /* check whether selected ISA is supported by the HW, as the user could have forced an unsupported ISA */
84 if (!checkISASupport()) {
85 throw_RTCError(RTC_ERROR_UNSUPPORTED_CPU,"CPU does not support selected ISA");
86 }
87
88 /*! do some internal tests */
89 assert(isa::Cylinder::verify());
90
91 /*! enable huge page support if desired */
92#if defined(__WIN32__)
93 if (State::enable_selockmemoryprivilege)
94 State::hugepages_success &= win_enable_selockmemoryprivilege(State::verbosity(3));
95#endif
96 State::hugepages_success &= os_init(State::hugepages,State::verbosity(3));
97
98 /*! set tessellation cache size */
99 setCacheSize( State::tessellation_cache_size );
100
101 /*! enable some floating point exceptions to catch bugs */
102 if (State::float_exceptions)
103 {
104 int exceptions = _MM_MASK_MASK;
105 //exceptions &= ~_MM_MASK_INVALID;
106 exceptions &= ~_MM_MASK_DENORM;
107 exceptions &= ~_MM_MASK_DIV_ZERO;
108 //exceptions &= ~_MM_MASK_OVERFLOW;
109 //exceptions &= ~_MM_MASK_UNDERFLOW;
110 //exceptions &= ~_MM_MASK_INEXACT;
111 _MM_SET_EXCEPTION_MASK(exceptions);
112 }
113
114 /* print info header */
115 if (State::verbosity(1))
116 print();
117 if (State::verbosity(2))
118 State::print();
119
120 /* register all algorithms */
121 bvh4_factory = make_unique(new BVH4Factory(enabled_builder_cpu_features, enabled_cpu_features));
122
123#if defined(EMBREE_TARGET_SIMD8)
124 bvh8_factory = make_unique(new BVH8Factory(enabled_builder_cpu_features, enabled_cpu_features));
125#endif
126
127 /* setup tasking system */
128 initTaskingSystem(numThreads);
129
130 /* ray stream SOA to AOS conversion */
131#if defined(EMBREE_RAY_PACKETS)
132 RayStreamFilterFuncsType rayStreamFilterFuncs;
133 SELECT_SYMBOL_DEFAULT_SSE42_AVX_AVX2_AVX512(enabled_cpu_features,rayStreamFilterFuncs);
134 rayStreamFilters = rayStreamFilterFuncs();
135#endif
136 }
137
138 Device::~Device ()
139 {
140 setCacheSize(0);
141 exitTaskingSystem();
142 }
143
144 std::string getEnabledTargets()
145 {
146 std::string v;
147#if defined(EMBREE_TARGET_SSE2)
148 v += "SSE2 ";
149#endif
150#if defined(EMBREE_TARGET_SSE42)
151 v += "SSE4.2 ";
152#endif
153#if defined(EMBREE_TARGET_AVX)
154 v += "AVX ";
155#endif
156#if defined(EMBREE_TARGET_AVX2)
157 v += "AVX2 ";
158#endif
159#if defined(EMBREE_TARGET_AVX512)
160 v += "AVX512 ";
161#endif
162 return v;
163 }
164
165 std::string getEmbreeFeatures()
166 {
167 std::string v;
168#if defined(EMBREE_RAY_MASK)
169 v += "raymasks ";
170#endif
171#if defined (EMBREE_BACKFACE_CULLING)
172 v += "backfaceculling ";
173#endif
174#if defined (EMBREE_BACKFACE_CULLING_CURVES)
175 v += "backfacecullingcurves ";
176#endif
177#if defined(EMBREE_FILTER_FUNCTION)
178 v += "intersection_filter ";
179#endif
180#if defined (EMBREE_COMPACT_POLYS)
181 v += "compact_polys ";
182#endif
183 return v;
184 }
185
186 void Device::print()
187 {
188 const int cpu_features = getCPUFeatures();
189 std::cout << std::endl;
190 std::cout << "Embree Ray Tracing Kernels " << RTC_VERSION_STRING << " (" << RTC_HASH << ")" << std::endl;
191 std::cout << " Compiler : " << getCompilerName() << std::endl;
192 std::cout << " Build : ";
193#if defined(DEBUG)
194 std::cout << "Debug " << std::endl;
195#else
196 std::cout << "Release " << std::endl;
197#endif
198 std::cout << " Platform : " << getPlatformName() << std::endl;
199 std::cout << " CPU : " << stringOfCPUModel(getCPUModel()) << " (" << getCPUVendor() << ")" << std::endl;
200 std::cout << " Threads : " << getNumberOfLogicalThreads() << std::endl;
201 std::cout << " ISA : " << stringOfCPUFeatures(cpu_features) << std::endl;
202 std::cout << " Targets : " << supportedTargetList(cpu_features) << std::endl;
203 const bool hasFTZ = _mm_getcsr() & _MM_FLUSH_ZERO_ON;
204 const bool hasDAZ = _mm_getcsr() & _MM_DENORMALS_ZERO_ON;
205 std::cout << " MXCSR : " << "FTZ=" << hasFTZ << ", DAZ=" << hasDAZ << std::endl;
206 std::cout << " Config" << std::endl;
207 std::cout << " Threads : " << (numThreads ? toString(numThreads) : std::string("default")) << std::endl;
208 std::cout << " ISA : " << stringOfCPUFeatures(enabled_cpu_features) << std::endl;
209 std::cout << " Targets : " << supportedTargetList(enabled_cpu_features) << " (supported)" << std::endl;
210 std::cout << " " << getEnabledTargets() << " (compile time enabled)" << std::endl;
211 std::cout << " Features: " << getEmbreeFeatures() << std::endl;
212 std::cout << " Tasking : ";
213#if defined(TASKING_TBB)
214 std::cout << "TBB" << TBB_VERSION_MAJOR << "." << TBB_VERSION_MINOR << " ";
215 #if TBB_INTERFACE_VERSION >= 12002
216 std::cout << "TBB_header_interface_" << TBB_INTERFACE_VERSION << " TBB_lib_interface_" << TBB_runtime_interface_version() << " ";
217 #else
218 std::cout << "TBB_header_interface_" << TBB_INTERFACE_VERSION << " TBB_lib_interface_" << tbb::TBB_runtime_interface_version() << " ";
219 #endif
220#endif
221#if defined(TASKING_INTERNAL)
222 std::cout << "internal_tasking_system ";
223#endif
224#if defined(TASKING_PPL)
225 std::cout << "PPL ";
226#endif
227 std::cout << std::endl;
228
229 /* check of FTZ and DAZ flags are set in CSR */
230 if (!hasFTZ || !hasDAZ)
231 {
232#if !defined(_DEBUG)
233 if (State::verbosity(1))
234#endif
235 {
236 std::cout << std::endl;
237 std::cout << "================================================================================" << std::endl;
238 std::cout << " WARNING: \"Flush to Zero\" or \"Denormals are Zero\" mode not enabled " << std::endl
239 << " in the MXCSR control and status register. This can have a severe " << std::endl
240 << " performance impact. Please enable these modes for each application " << std::endl
241 << " thread the following way:" << std::endl
242 << std::endl
243 << " #include \"xmmintrin.h\"" << std::endl
244 << " #include \"pmmintrin.h\"" << std::endl
245 << std::endl
246 << " _MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON);" << std::endl
247 << " _MM_SET_DENORMALS_ZERO_MODE(_MM_DENORMALS_ZERO_ON);" << std::endl;
248 std::cout << "================================================================================" << std::endl;
249 std::cout << std::endl;
250 }
251 }
252 std::cout << std::endl;
253 }
254
255 void Device::setDeviceErrorCode(RTCError error)
256 {
257 RTCError* stored_error = errorHandler.error();
258 if (*stored_error == RTC_ERROR_NONE)
259 *stored_error = error;
260 }
261
262 RTCError Device::getDeviceErrorCode()
263 {
264 RTCError* stored_error = errorHandler.error();
265 RTCError error = *stored_error;
266 *stored_error = RTC_ERROR_NONE;
267 return error;
268 }
269
270 void Device::setThreadErrorCode(RTCError error)
271 {
272 RTCError* stored_error = g_errorHandler.error();
273 if (*stored_error == RTC_ERROR_NONE)
274 *stored_error = error;
275 }
276
277 RTCError Device::getThreadErrorCode()
278 {
279 RTCError* stored_error = g_errorHandler.error();
280 RTCError error = *stored_error;
281 *stored_error = RTC_ERROR_NONE;
282 return error;
283 }
284
285 void Device::process_error(Device* device, RTCError error, const char* str)
286 {
287 /* store global error code when device construction failed */
288 if (!device)
289 return setThreadErrorCode(error);
290
291 /* print error when in verbose mode */
292 if (device->verbosity(1))
293 {
294 switch (error) {
295 case RTC_ERROR_NONE : std::cerr << "Embree: No error"; break;
296 case RTC_ERROR_UNKNOWN : std::cerr << "Embree: Unknown error"; break;
297 case RTC_ERROR_INVALID_ARGUMENT : std::cerr << "Embree: Invalid argument"; break;
298 case RTC_ERROR_INVALID_OPERATION: std::cerr << "Embree: Invalid operation"; break;
299 case RTC_ERROR_OUT_OF_MEMORY : std::cerr << "Embree: Out of memory"; break;
300 case RTC_ERROR_UNSUPPORTED_CPU : std::cerr << "Embree: Unsupported CPU"; break;
301 default : std::cerr << "Embree: Invalid error code"; break;
302 };
303 if (str) std::cerr << ", (" << str << ")";
304 std::cerr << std::endl;
305 }
306
307 /* call user specified error callback */
308 if (device->error_function)
309 device->error_function(device->error_function_userptr,error,str);
310
311 /* record error code */
312 device->setDeviceErrorCode(error);
313 }
314
315 void Device::memoryMonitor(ssize_t bytes, bool post)
316 {
317 if (State::memory_monitor_function && bytes != 0) {
318 if (!State::memory_monitor_function(State::memory_monitor_userptr,bytes,post)) {
319 if (bytes > 0) { // only throw exception when we allocate memory to never throw inside a destructor
320 throw_RTCError(RTC_ERROR_OUT_OF_MEMORY,"memory monitor forced termination");
321 }
322 }
323 }
324 }
325
326 size_t getMaxNumThreads()
327 {
328 size_t maxNumThreads = 0;
329 for (std::map<Device*,size_t>::iterator i=g_num_threads_map.begin(); i != g_num_threads_map.end(); i++)
330 maxNumThreads = max(maxNumThreads, (*i).second);
331 if (maxNumThreads == 0)
332 maxNumThreads = std::numeric_limits<size_t>::max();
333 return maxNumThreads;
334 }
335
336 size_t getMaxCacheSize()
337 {
338 size_t maxCacheSize = 0;
339 for (std::map<Device*,size_t>::iterator i=g_cache_size_map.begin(); i!= g_cache_size_map.end(); i++)
340 maxCacheSize = max(maxCacheSize, (*i).second);
341 return maxCacheSize;
342 }
343
344 void Device::setCacheSize(size_t bytes)
345 {
346#if defined(EMBREE_GEOMETRY_SUBDIVISION)
347 Lock<MutexSys> lock(g_mutex);
348 if (bytes == 0) g_cache_size_map.erase(this);
349 else g_cache_size_map[this] = bytes;
350
351 size_t maxCacheSize = getMaxCacheSize();
352 resizeTessellationCache(maxCacheSize);
353#endif
354 }
355
356 void Device::initTaskingSystem(size_t numThreads)
357 {
358 Lock<MutexSys> lock(g_mutex);
359 if (numThreads == 0)
360 g_num_threads_map[this] = std::numeric_limits<size_t>::max();
361 else
362 g_num_threads_map[this] = numThreads;
363
364 /* create task scheduler */
365 size_t maxNumThreads = getMaxNumThreads();
366 TaskScheduler::create(maxNumThreads,State::set_affinity,State::start_threads);
367#if USE_TASK_ARENA
368 const size_t nThreads = min(maxNumThreads,TaskScheduler::threadCount());
369 const size_t uThreads = min(max(numUserThreads,(size_t)1),nThreads);
370 arena = make_unique(new tbb::task_arena((int)nThreads,(unsigned int)uThreads));
371#endif
372 }
373
374 void Device::exitTaskingSystem()
375 {
376 Lock<MutexSys> lock(g_mutex);
377 g_num_threads_map.erase(this);
378
379 /* terminate tasking system */
380 if (g_num_threads_map.size() == 0) {
381 TaskScheduler::destroy();
382 }
383 /* or configure new number of threads */
384 else {
385 size_t maxNumThreads = getMaxNumThreads();
386 TaskScheduler::create(maxNumThreads,State::set_affinity,State::start_threads);
387 }
388#if USE_TASK_ARENA
389 arena.reset();
390#endif
391 }
392
393 void Device::setProperty(const RTCDeviceProperty prop, ssize_t val)
394 {
395 /* hidden internal properties */
396 switch ((size_t)prop)
397 {
398 case 1000000: debug_int0 = val; return;
399 case 1000001: debug_int1 = val; return;
400 case 1000002: debug_int2 = val; return;
401 case 1000003: debug_int3 = val; return;
402 }
403
404 throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "unknown writable property");
405 }
406
407 ssize_t Device::getProperty(const RTCDeviceProperty prop)
408 {
409 size_t iprop = (size_t)prop;
410
411 /* get name of internal regression test */
412 if (iprop >= 2000000 && iprop < 3000000)
413 {
414 RegressionTest* test = getRegressionTest(iprop-2000000);
415 if (test) return (ssize_t) test->name.c_str();
416 else return 0;
417 }
418
419 /* run internal regression test */
420 if (iprop >= 3000000 && iprop < 4000000)
421 {
422 RegressionTest* test = getRegressionTest(iprop-3000000);
423 if (test) return test->run();
424 else return 0;
425 }
426
427 /* documented properties */
428 switch (prop)
429 {
430 case RTC_DEVICE_PROPERTY_VERSION_MAJOR: return RTC_VERSION_MAJOR;
431 case RTC_DEVICE_PROPERTY_VERSION_MINOR: return RTC_VERSION_MINOR;
432 case RTC_DEVICE_PROPERTY_VERSION_PATCH: return RTC_VERSION_PATCH;
433 case RTC_DEVICE_PROPERTY_VERSION : return RTC_VERSION;
434
435#if defined(EMBREE_TARGET_SIMD4) && defined(EMBREE_RAY_PACKETS)
436 case RTC_DEVICE_PROPERTY_NATIVE_RAY4_SUPPORTED: return hasISA(SSE2);
437#else
438 case RTC_DEVICE_PROPERTY_NATIVE_RAY4_SUPPORTED: return 0;
439#endif
440
441#if defined(EMBREE_TARGET_SIMD8) && defined(EMBREE_RAY_PACKETS)
442 case RTC_DEVICE_PROPERTY_NATIVE_RAY8_SUPPORTED: return hasISA(AVX);
443#else
444 case RTC_DEVICE_PROPERTY_NATIVE_RAY8_SUPPORTED: return 0;
445#endif
446
447#if defined(EMBREE_TARGET_SIMD16) && defined(EMBREE_RAY_PACKETS)
448 case RTC_DEVICE_PROPERTY_NATIVE_RAY16_SUPPORTED: return hasISA(AVX512);
449#else
450 case RTC_DEVICE_PROPERTY_NATIVE_RAY16_SUPPORTED: return 0;
451#endif
452
453#if defined(EMBREE_RAY_PACKETS)
454 case RTC_DEVICE_PROPERTY_RAY_STREAM_SUPPORTED: return 1;
455#else
456 case RTC_DEVICE_PROPERTY_RAY_STREAM_SUPPORTED: return 0;
457#endif
458
459#if defined(EMBREE_RAY_MASK)
460 case RTC_DEVICE_PROPERTY_RAY_MASK_SUPPORTED: return 1;
461#else
462 case RTC_DEVICE_PROPERTY_RAY_MASK_SUPPORTED: return 0;
463#endif
464
465#if defined(EMBREE_BACKFACE_CULLING)
466 case RTC_DEVICE_PROPERTY_BACKFACE_CULLING_ENABLED: return 1;
467#else
468 case RTC_DEVICE_PROPERTY_BACKFACE_CULLING_ENABLED: return 0;
469#endif
470
471#if defined(EMBREE_BACKFACE_CULLING_CURVES)
472 case RTC_DEVICE_PROPERTY_BACKFACE_CULLING_CURVES_ENABLED: return 1;
473#else
474 case RTC_DEVICE_PROPERTY_BACKFACE_CULLING_CURVES_ENABLED: return 0;
475#endif
476
477#if defined(EMBREE_COMPACT_POLYS)
478 case RTC_DEVICE_PROPERTY_COMPACT_POLYS_ENABLED: return 1;
479#else
480 case RTC_DEVICE_PROPERTY_COMPACT_POLYS_ENABLED: return 0;
481#endif
482
483#if defined(EMBREE_FILTER_FUNCTION)
484 case RTC_DEVICE_PROPERTY_FILTER_FUNCTION_SUPPORTED: return 1;
485#else
486 case RTC_DEVICE_PROPERTY_FILTER_FUNCTION_SUPPORTED: return 0;
487#endif
488
489#if defined(EMBREE_IGNORE_INVALID_RAYS)
490 case RTC_DEVICE_PROPERTY_IGNORE_INVALID_RAYS_ENABLED: return 1;
491#else
492 case RTC_DEVICE_PROPERTY_IGNORE_INVALID_RAYS_ENABLED: return 0;
493#endif
494
495#if defined(TASKING_INTERNAL)
496 case RTC_DEVICE_PROPERTY_TASKING_SYSTEM: return 0;
497#endif
498
499#if defined(TASKING_TBB)
500 case RTC_DEVICE_PROPERTY_TASKING_SYSTEM: return 1;
501#endif
502
503#if defined(TASKING_PPL)
504 case RTC_DEVICE_PROPERTY_TASKING_SYSTEM: return 2;
505#endif
506
507#if defined(EMBREE_GEOMETRY_TRIANGLE)
508 case RTC_DEVICE_PROPERTY_TRIANGLE_GEOMETRY_SUPPORTED: return 1;
509#else
510 case RTC_DEVICE_PROPERTY_TRIANGLE_GEOMETRY_SUPPORTED: return 0;
511#endif
512
513#if defined(EMBREE_GEOMETRY_QUAD)
514 case RTC_DEVICE_PROPERTY_QUAD_GEOMETRY_SUPPORTED: return 1;
515#else
516 case RTC_DEVICE_PROPERTY_QUAD_GEOMETRY_SUPPORTED: return 0;
517#endif
518
519#if defined(EMBREE_GEOMETRY_CURVE)
520 case RTC_DEVICE_PROPERTY_CURVE_GEOMETRY_SUPPORTED: return 1;
521#else
522 case RTC_DEVICE_PROPERTY_CURVE_GEOMETRY_SUPPORTED: return 0;
523#endif
524
525#if defined(EMBREE_GEOMETRY_SUBDIVISION)
526 case RTC_DEVICE_PROPERTY_SUBDIVISION_GEOMETRY_SUPPORTED: return 1;
527#else
528 case RTC_DEVICE_PROPERTY_SUBDIVISION_GEOMETRY_SUPPORTED: return 0;
529#endif
530
531#if defined(EMBREE_GEOMETRY_USER)
532 case RTC_DEVICE_PROPERTY_USER_GEOMETRY_SUPPORTED: return 1;
533#else
534 case RTC_DEVICE_PROPERTY_USER_GEOMETRY_SUPPORTED: return 0;
535#endif
536
537#if defined(EMBREE_GEOMETRY_POINT)
538 case RTC_DEVICE_PROPERTY_POINT_GEOMETRY_SUPPORTED: return 1;
539#else
540 case RTC_DEVICE_PROPERTY_POINT_GEOMETRY_SUPPORTED: return 0;
541#endif
542
543#if defined(TASKING_PPL)
544 case RTC_DEVICE_PROPERTY_JOIN_COMMIT_SUPPORTED: return 0;
545#elif defined(TASKING_TBB) && (TBB_INTERFACE_VERSION_MAJOR < 8)
546 case RTC_DEVICE_PROPERTY_JOIN_COMMIT_SUPPORTED: return 0;
547#else
548 case RTC_DEVICE_PROPERTY_JOIN_COMMIT_SUPPORTED: return 1;
549#endif
550
551#if defined(TASKING_TBB) && TASKING_TBB_USE_TASK_ISOLATION
552 case RTC_DEVICE_PROPERTY_PARALLEL_COMMIT_SUPPORTED: return 1;
553#else
554 case RTC_DEVICE_PROPERTY_PARALLEL_COMMIT_SUPPORTED: return 0;
555#endif
556
557 default: throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "unknown readable property"); break;
558 };
559 }
560}
561