diff options
Diffstat (limited to 'intern')
56 files changed, 3267 insertions, 1619 deletions
diff --git a/intern/clog/clog.c b/intern/clog/clog.c index 50a51ebe913..416ea25ee0c 100644 --- a/intern/clog/clog.c +++ b/intern/clog/clog.c @@ -322,7 +322,9 @@ static bool clg_ctx_filter_check(CLogContext *ctx, const char *identifier) if (flt->match[0] == '*' && flt->match[len - 1] == '*') { char *match = MEM_callocN(sizeof(char) * len - 1, __func__); memcpy(match, flt->match + 1, len - 2); - if (strstr(identifier, match) != NULL) { + const bool success = (strstr(identifier, match) != NULL); + MEM_freeN(match); + if (success) { return (bool)i; } } diff --git a/intern/cycles/app/CMakeLists.txt b/intern/cycles/app/CMakeLists.txt index 67b852013f3..7a1e5d62dd2 100644 --- a/intern/cycles/app/CMakeLists.txt +++ b/intern/cycles/app/CMakeLists.txt @@ -71,6 +71,16 @@ if(WITH_CYCLES_STANDALONE) target_link_libraries(cycles ${LIBRARIES}) cycles_target_link_libraries(cycles) + if(APPLE) + if(WITH_OPENCOLORIO) + set_property(TARGET cycles APPEND_STRING PROPERTY LINK_FLAGS " -framework IOKit") + endif() + if(WITH_OPENIMAGEDENOISE AND "${CMAKE_OSX_ARCHITECTURES}" STREQUAL "arm64") + # OpenImageDenoise uses BNNS from the Accelerate framework. + set_property(TARGET cycles APPEND_STRING PROPERTY LINK_FLAGS " -framework Accelerate") + endif() + endif() + if(UNIX AND NOT APPLE) set_target_properties(cycles PROPERTIES INSTALL_RPATH $ORIGIN/lib) endif() diff --git a/intern/cycles/blender/addon/engine.py b/intern/cycles/blender/addon/engine.py index dfa696714fb..d8398772a84 100644 --- a/intern/cycles/blender/addon/engine.py +++ b/intern/cycles/blender/addon/engine.py @@ -19,16 +19,16 @@ from __future__ import annotations def _is_using_buggy_driver(): - import bgl + import gpu # We need to be conservative here because in multi-GPU systems display card # might be quite old, but others one might be just good. # # So We shouldn't disable possible good dedicated cards just because display # card seems weak. And instead we only blacklist configurations which are # proven to cause problems. - if bgl.glGetString(bgl.GL_VENDOR) == "ATI Technologies Inc.": + if gpu.platform.vendor_get() == "ATI Technologies Inc.": import re - version = bgl.glGetString(bgl.GL_VERSION) + version = gpu.platform.version_get() if version.endswith("Compatibility Profile Context"): # Old HD 4xxx and 5xxx series drivers did not have driver version # in the version string, but those cards do not quite work and diff --git a/intern/cycles/blender/blender_object.cpp b/intern/cycles/blender/blender_object.cpp index dcf6e3cc949..cb84013c551 100644 --- a/intern/cycles/blender/blender_object.cpp +++ b/intern/cycles/blender/blender_object.cpp @@ -564,10 +564,12 @@ void BlenderSync::sync_objects(BL::Depsgraph &b_depsgraph, if (!cancel && !motion) { sync_background_light(b_v3d, use_portal); - /* handle removed data and modified pointers */ + /* Handle removed data and modified pointers, as this may free memory, delete Nodes in the + * right order to ensure that dependent data is freed after their users. Objects should be + * freed before particle systems and geometries. */ light_map.post_sync(); - geometry_map.post_sync(); object_map.post_sync(); + geometry_map.post_sync(); particle_system_map.post_sync(); } diff --git a/intern/cycles/blender/blender_sync.cpp b/intern/cycles/blender/blender_sync.cpp index 4af8da402b1..9d0f9f29f94 100644 --- a/intern/cycles/blender/blender_sync.cpp +++ b/intern/cycles/blender/blender_sync.cpp @@ -69,7 +69,8 @@ BlenderSync::BlenderSync(BL::RenderEngine &b_engine, experimental(false), dicing_rate(1.0f), max_subdivisions(12), - progress(progress) + progress(progress), + has_updates_(true) { PointerRNA cscene = RNA_pointer_get(&b_scene.ptr, "cycles"); dicing_rate = preview ? RNA_float_get(&cscene, "preview_dicing_rate") : @@ -84,7 +85,9 @@ BlenderSync::~BlenderSync() void BlenderSync::reset(BL::BlendData &b_data, BL::Scene &b_scene) { /* Update data and scene pointers in case they change in session reset, - * for example after undo. */ + * for example after undo. + * Note that we do not modify the `has_updates_` flag here because the sync + * reset is also used during viewport navigation. */ this->b_data = b_data; this->b_scene = b_scene; } @@ -117,6 +120,8 @@ void BlenderSync::sync_recalc(BL::Depsgraph &b_depsgraph, BL::SpaceView3D &b_v3d } if (dicing_prop_changed) { + has_updates_ = true; + for (const pair<const GeometryKey, Geometry *> &iter : geometry_map.key_to_scene_data()) { Geometry *geom = iter.second; if (geom->is_mesh()) { @@ -133,6 +138,12 @@ void BlenderSync::sync_recalc(BL::Depsgraph &b_depsgraph, BL::SpaceView3D &b_v3d /* Iterate over all IDs in this depsgraph. */ for (BL::DepsgraphUpdate &b_update : b_depsgraph.updates) { + /* TODO(sergey): Can do more selective filter here. For example, ignore changes made to + * screen datablock. Note that sync_data() needs to be called after object deletion, and + * currently this is ensured by the scene ID tagged for update, which sets the `has_updates_` + * flag. */ + has_updates_ = true; + BL::ID b_id(b_update.id()); /* Material */ @@ -227,6 +238,10 @@ void BlenderSync::sync_data(BL::RenderSettings &b_render, int height, void **python_thread_state) { + if (!has_updates_) { + return; + } + scoped_timer timer; BL::ViewLayer b_view_layer = b_depsgraph.view_layer_eval(); @@ -254,6 +269,8 @@ void BlenderSync::sync_data(BL::RenderSettings &b_render, free_data_after_sync(b_depsgraph); VLOG(1) << "Total time spent synchronizing data: " << timer.get_time(); + + has_updates_ = false; } /* Integrator */ diff --git a/intern/cycles/blender/blender_sync.h b/intern/cycles/blender/blender_sync.h index a222c5e490e..15a10f2b46b 100644 --- a/intern/cycles/blender/blender_sync.h +++ b/intern/cycles/blender/blender_sync.h @@ -264,6 +264,12 @@ class BlenderSync { } view_layer; Progress &progress; + + protected: + /* Indicates that `sync_recalc()` detected changes in the scene. + * If this flag is false then the data is considered to be up-to-date and will not be + * synchronized at all. */ + bool has_updates_ = true; }; CCL_NAMESPACE_END diff --git a/intern/cycles/device/device_optix.cpp b/intern/cycles/device/device_optix.cpp index bb6027254f9..01de0724cb2 100644 --- a/intern/cycles/device/device_optix.cpp +++ b/intern/cycles/device/device_optix.cpp @@ -960,14 +960,21 @@ class OptiXDevice : public CUDADevice { // Create OptiX denoiser handle on demand when it is first used OptixDenoiserOptions denoiser_options = {}; assert(task.denoising.input_passes >= 1 && task.denoising.input_passes <= 3); +# if OPTIX_ABI_VERSION >= 47 + denoiser_options.guideAlbedo = task.denoising.input_passes >= 2; + denoiser_options.guideNormal = task.denoising.input_passes >= 3; + check_result_optix_ret(optixDenoiserCreate( + context, OPTIX_DENOISER_MODEL_KIND_HDR, &denoiser_options, &denoiser)); +# else denoiser_options.inputKind = static_cast<OptixDenoiserInputKind>( OPTIX_DENOISER_INPUT_RGB + (task.denoising.input_passes - 1)); -# if OPTIX_ABI_VERSION < 28 +# if OPTIX_ABI_VERSION < 28 denoiser_options.pixelFormat = OPTIX_PIXEL_FORMAT_FLOAT3; -# endif +# endif check_result_optix_ret(optixDenoiserCreate(context, &denoiser_options, &denoiser)); check_result_optix_ret( optixDenoiserSetModel(denoiser, OPTIX_DENOISER_MODEL_KIND_HDR, NULL, 0)); +# endif // OptiX denoiser handle was created with the requested number of input passes denoiser_input_passes = task.denoising.input_passes; @@ -1037,10 +1044,34 @@ class OptiXDevice : public CUDADevice { # endif output_layers[0].format = OPTIX_PIXEL_FORMAT_FLOAT3; +# if OPTIX_ABI_VERSION >= 47 + OptixDenoiserLayer image_layers = {}; + image_layers.input = input_layers[0]; + image_layers.output = output_layers[0]; + + OptixDenoiserGuideLayer guide_layers = {}; + guide_layers.albedo = input_layers[1]; + guide_layers.normal = input_layers[2]; +# endif + // Finally run denonising OptixDenoiserParams params = {}; // All parameters are disabled/zero +# if OPTIX_ABI_VERSION >= 47 check_result_optix_ret(optixDenoiserInvoke(denoiser, - 0, + NULL, + ¶ms, + denoiser_state.device_pointer, + scratch_offset, + &guide_layers, + &image_layers, + 1, + overlap_offset.x, + overlap_offset.y, + denoiser_state.device_pointer + scratch_offset, + scratch_size)); +# else + check_result_optix_ret(optixDenoiserInvoke(denoiser, + NULL, ¶ms, denoiser_state.device_pointer, scratch_offset, @@ -1051,6 +1082,7 @@ class OptiXDevice : public CUDADevice { output_layers, denoiser_state.device_pointer + scratch_offset, scratch_size)); +# endif # if OPTIX_DENOISER_NO_PIXEL_STRIDE void *output_args[] = {&input_ptr, diff --git a/intern/cycles/graph/node.cpp b/intern/cycles/graph/node.cpp index c926f6ab8ef..57f25283f85 100644 --- a/intern/cycles/graph/node.cpp +++ b/intern/cycles/graph/node.cpp @@ -367,9 +367,17 @@ void Node::copy_value(const SocketType &socket, const Node &other, const SocketT case SocketType::TRANSFORM_ARRAY: copy_array<Transform>(this, socket, &other, other_socket); break; - case SocketType::NODE_ARRAY: + case SocketType::NODE_ARRAY: { copy_array<void *>(this, socket, &other, other_socket); + + array<Node *> &node_array = get_socket_value<array<Node *>>(this, socket); + + for (Node *node : node_array) { + node->reference(); + } + break; + } default: assert(0); break; @@ -379,6 +387,14 @@ void Node::copy_value(const SocketType &socket, const Node &other, const SocketT const void *src = ((char *)&other) + other_socket.struct_offset; void *dst = ((char *)this) + socket.struct_offset; memcpy(dst, src, socket.size()); + + if (socket.type == SocketType::NODE) { + Node *node = get_socket_value<Node *>(this, socket); + + if (node) { + node->reference(); + } + } } } @@ -773,6 +789,26 @@ void Node::set_owner(const NodeOwner *owner_) owner = owner_; } +void Node::dereference_all_used_nodes() +{ + foreach (const SocketType &socket, type->inputs) { + if (socket.type == SocketType::NODE) { + Node *node = get_socket_value<Node *>(this, socket); + + if (node) { + node->dereference(); + } + } + else if (socket.type == SocketType::NODE_ARRAY) { + const array<Node *> &nodes = get_socket_value<array<Node *>>(this, socket); + + for (Node *node : nodes) { + node->dereference(); + } + } + } +} + bool Node::socket_is_modified(const SocketType &input) const { return (socket_modified & input.modified_flag_bit) != 0; @@ -803,6 +839,25 @@ template<typename T> void Node::set_if_different(const SocketType &input, T valu socket_modified |= input.modified_flag_bit; } +void Node::set_if_different(const SocketType &input, Node *value) +{ + if (get_socket_value<Node *>(this, input) == value) { + return; + } + + Node *old_node = get_socket_value<Node *>(this, input); + if (old_node) { + old_node->dereference(); + } + + if (value) { + value->reference(); + } + + get_socket_value<Node *>(this, input) = value; + socket_modified |= input.modified_flag_bit; +} + template<typename T> void Node::set_if_different(const SocketType &input, array<T> &value) { if (!socket_is_modified(input)) { @@ -815,6 +870,27 @@ template<typename T> void Node::set_if_different(const SocketType &input, array< socket_modified |= input.modified_flag_bit; } +void Node::set_if_different(const SocketType &input, array<Node *> &value) +{ + if (!socket_is_modified(input)) { + if (get_socket_value<array<Node *>>(this, input) == value) { + return; + } + } + + array<Node *> &old_nodes = get_socket_value<array<Node *>>(this, input); + for (Node *old_node : old_nodes) { + old_node->dereference(); + } + + for (Node *new_node : value) { + new_node->reference(); + } + + get_socket_value<array<Node *>>(this, input).steal_data(value); + socket_modified |= input.modified_flag_bit; +} + void Node::print_modified_sockets() const { printf("Node : %s\n", name.c_str()); diff --git a/intern/cycles/graph/node.h b/intern/cycles/graph/node.h index 2fc9a1e0281..aa365baeccd 100644 --- a/intern/cycles/graph/node.h +++ b/intern/cycles/graph/node.h @@ -177,8 +177,32 @@ struct Node { const NodeOwner *get_owner() const; void set_owner(const NodeOwner *owner_); + int reference_count() const + { + return ref_count; + } + + void reference() + { + ref_count += 1; + } + + void dereference() + { + ref_count -= 1; + } + + /* Set the reference count to zero. This should only be called when we know for sure that the + * Node is not used by anyone else. For now, this is only the case when "deleting" shaders, as + * they are never actually deleted. */ + void clear_reference_count() + { + ref_count = 0; + } + protected: const NodeOwner *owner; + int ref_count{0}; template<typename T> static T &get_socket_value(const Node *node, const SocketType &socket) { @@ -189,7 +213,19 @@ struct Node { template<typename T> void set_if_different(const SocketType &input, T value); + /* Explicit overload for Node sockets so we can handle reference counting. The old Node is + * dereferenced, and the new one is referenced. */ + void set_if_different(const SocketType &input, Node *value); + template<typename T> void set_if_different(const SocketType &input, array<T> &value); + + /* Explicit overload for Node sockets so we can handle reference counting. The old Nodes are + * dereferenced, and the new ones are referenced. */ + void set_if_different(const SocketType &input, array<Node *> &value); + + /* Call this function in derived classes' destructors to ensure that used Nodes are dereferenced + * properly. */ + void dereference_all_used_nodes(); }; CCL_NAMESPACE_END diff --git a/intern/cycles/kernel/CMakeLists.txt b/intern/cycles/kernel/CMakeLists.txt index f6b4b963a7a..ea0f16c9233 100644 --- a/intern/cycles/kernel/CMakeLists.txt +++ b/intern/cycles/kernel/CMakeLists.txt @@ -93,6 +93,7 @@ set(SRC_BVH_HEADERS bvh/bvh_local.h bvh/bvh_traversal.h bvh/bvh_types.h + bvh/bvh_util.h bvh/bvh_volume.h bvh/bvh_volume_all.h bvh/bvh_embree.h diff --git a/intern/cycles/kernel/bvh/bvh.h b/intern/cycles/kernel/bvh/bvh.h index 3049f243ae9..3a3f38539c5 100644 --- a/intern/cycles/kernel/bvh/bvh.h +++ b/intern/cycles/kernel/bvh/bvh.h @@ -29,9 +29,10 @@ # include "kernel/bvh/bvh_embree.h" #endif -CCL_NAMESPACE_BEGIN - #include "kernel/bvh/bvh_types.h" +#include "kernel/bvh/bvh_util.h" + +CCL_NAMESPACE_BEGIN #ifndef __KERNEL_OPTIX__ @@ -533,97 +534,4 @@ ccl_device_intersect uint scene_intersect_volume_all(KernelGlobals *kg, } #endif /* __VOLUME_RECORD_ALL__ */ -/* Ray offset to avoid self intersection. - * - * This function should be used to compute a modified ray start position for - * rays leaving from a surface. */ - -ccl_device_inline float3 ray_offset(float3 P, float3 Ng) -{ -#ifdef __INTERSECTION_REFINE__ - const float epsilon_f = 1e-5f; - /* ideally this should match epsilon_f, but instancing and motion blur - * precision makes it problematic */ - const float epsilon_test = 1.0f; - const int epsilon_i = 32; - - float3 res; - - /* x component */ - if (fabsf(P.x) < epsilon_test) { - res.x = P.x + Ng.x * epsilon_f; - } - else { - uint ix = __float_as_uint(P.x); - ix += ((ix ^ __float_as_uint(Ng.x)) >> 31) ? -epsilon_i : epsilon_i; - res.x = __uint_as_float(ix); - } - - /* y component */ - if (fabsf(P.y) < epsilon_test) { - res.y = P.y + Ng.y * epsilon_f; - } - else { - uint iy = __float_as_uint(P.y); - iy += ((iy ^ __float_as_uint(Ng.y)) >> 31) ? -epsilon_i : epsilon_i; - res.y = __uint_as_float(iy); - } - - /* z component */ - if (fabsf(P.z) < epsilon_test) { - res.z = P.z + Ng.z * epsilon_f; - } - else { - uint iz = __float_as_uint(P.z); - iz += ((iz ^ __float_as_uint(Ng.z)) >> 31) ? -epsilon_i : epsilon_i; - res.z = __uint_as_float(iz); - } - - return res; -#else - const float epsilon_f = 1e-4f; - return P + epsilon_f * Ng; -#endif -} - -#if defined(__VOLUME_RECORD_ALL__) || (defined(__SHADOW_RECORD_ALL__) && defined(__KERNEL_CPU__)) -/* ToDo: Move to another file? */ -ccl_device int intersections_compare(const void *a, const void *b) -{ - const Intersection *isect_a = (const Intersection *)a; - const Intersection *isect_b = (const Intersection *)b; - - if (isect_a->t < isect_b->t) - return -1; - else if (isect_a->t > isect_b->t) - return 1; - else - return 0; -} -#endif - -#if defined(__SHADOW_RECORD_ALL__) -ccl_device_inline void sort_intersections(Intersection *hits, uint num_hits) -{ -# ifdef __KERNEL_GPU__ - /* Use bubble sort which has more friendly memory pattern on GPU. */ - bool swapped; - do { - swapped = false; - for (int j = 0; j < num_hits - 1; ++j) { - if (hits[j].t > hits[j + 1].t) { - struct Intersection tmp = hits[j]; - hits[j] = hits[j + 1]; - hits[j + 1] = tmp; - swapped = true; - } - } - --num_hits; - } while (swapped); -# else - qsort(hits, num_hits, sizeof(Intersection), intersections_compare); -# endif -} -#endif /* __SHADOW_RECORD_ALL__ | __VOLUME_RECORD_ALL__ */ - CCL_NAMESPACE_END diff --git a/intern/cycles/kernel/bvh/bvh_shadow_all.h b/intern/cycles/kernel/bvh/bvh_shadow_all.h index dccd257d2de..2e94b1d7c37 100644 --- a/intern/cycles/kernel/bvh/bvh_shadow_all.h +++ b/intern/cycles/kernel/bvh/bvh_shadow_all.h @@ -180,25 +180,10 @@ ccl_device_inline /* todo: optimize so primitive visibility flag indicates if * the primitive has a transparent shadow shader? */ - int prim = kernel_tex_fetch(__prim_index, isect_array->prim); - int shader = 0; - -#ifdef __HAIR__ - if (kernel_tex_fetch(__prim_type, isect_array->prim) & PRIMITIVE_ALL_TRIANGLE) -#endif - { - shader = kernel_tex_fetch(__tri_shader, prim); - } -#ifdef __HAIR__ - else { - float4 str = kernel_tex_fetch(__curves, prim); - shader = __float_as_int(str.z); - } -#endif - int flag = kernel_tex_fetch(__shaders, (shader & SHADER_MASK)).flags; + const int flags = intersection_get_shader_flags(kg, isect_array); /* if no transparent shadows, all light is blocked */ - if (!(flag & SD_HAS_TRANSPARENT_SHADOW)) { + if (!(flags & SD_HAS_TRANSPARENT_SHADOW)) { return true; } /* if maximum number of hits reached, block all light */ diff --git a/intern/cycles/kernel/bvh/bvh_util.h b/intern/cycles/kernel/bvh/bvh_util.h new file mode 100644 index 00000000000..a694e4dc259 --- /dev/null +++ b/intern/cycles/kernel/bvh/bvh_util.h @@ -0,0 +1,162 @@ +/* + * Copyright 2011-2013 Blender Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +CCL_NAMESPACE_BEGIN + +/* Ray offset to avoid self intersection. + * + * This function should be used to compute a modified ray start position for + * rays leaving from a surface. */ + +ccl_device_inline float3 ray_offset(float3 P, float3 Ng) +{ +#ifdef __INTERSECTION_REFINE__ + const float epsilon_f = 1e-5f; + /* ideally this should match epsilon_f, but instancing and motion blur + * precision makes it problematic */ + const float epsilon_test = 1.0f; + const int epsilon_i = 32; + + float3 res; + + /* x component */ + if (fabsf(P.x) < epsilon_test) { + res.x = P.x + Ng.x * epsilon_f; + } + else { + uint ix = __float_as_uint(P.x); + ix += ((ix ^ __float_as_uint(Ng.x)) >> 31) ? -epsilon_i : epsilon_i; + res.x = __uint_as_float(ix); + } + + /* y component */ + if (fabsf(P.y) < epsilon_test) { + res.y = P.y + Ng.y * epsilon_f; + } + else { + uint iy = __float_as_uint(P.y); + iy += ((iy ^ __float_as_uint(Ng.y)) >> 31) ? -epsilon_i : epsilon_i; + res.y = __uint_as_float(iy); + } + + /* z component */ + if (fabsf(P.z) < epsilon_test) { + res.z = P.z + Ng.z * epsilon_f; + } + else { + uint iz = __float_as_uint(P.z); + iz += ((iz ^ __float_as_uint(Ng.z)) >> 31) ? -epsilon_i : epsilon_i; + res.z = __uint_as_float(iz); + } + + return res; +#else + const float epsilon_f = 1e-4f; + return P + epsilon_f * Ng; +#endif +} + +#if defined(__VOLUME_RECORD_ALL__) || (defined(__SHADOW_RECORD_ALL__) && defined(__KERNEL_CPU__)) +/* ToDo: Move to another file? */ +ccl_device int intersections_compare(const void *a, const void *b) +{ + const Intersection *isect_a = (const Intersection *)a; + const Intersection *isect_b = (const Intersection *)b; + + if (isect_a->t < isect_b->t) + return -1; + else if (isect_a->t > isect_b->t) + return 1; + else + return 0; +} +#endif + +#if defined(__SHADOW_RECORD_ALL__) +ccl_device_inline void sort_intersections(Intersection *hits, uint num_hits) +{ + kernel_assert(num_hits > 0); + +# ifdef __KERNEL_GPU__ + /* Use bubble sort which has more friendly memory pattern on GPU. */ + bool swapped; + do { + swapped = false; + for (int j = 0; j < num_hits - 1; ++j) { + if (hits[j].t > hits[j + 1].t) { + struct Intersection tmp = hits[j]; + hits[j] = hits[j + 1]; + hits[j + 1] = tmp; + swapped = true; + } + } + --num_hits; + } while (swapped); +# else + qsort(hits, num_hits, sizeof(Intersection), intersections_compare); +# endif +} +#endif /* __SHADOW_RECORD_ALL__ | __VOLUME_RECORD_ALL__ */ + +/* Utility to quickly get a shader flags from an intersection. */ + +ccl_device_forceinline int intersection_get_shader_flags(KernelGlobals *ccl_restrict kg, + const Intersection *isect) +{ + const int prim = kernel_tex_fetch(__prim_index, isect->prim); + int shader = 0; + +#ifdef __HAIR__ + if (kernel_tex_fetch(__prim_type, isect->prim) & PRIMITIVE_ALL_TRIANGLE) +#endif + { + shader = kernel_tex_fetch(__tri_shader, prim); + } +#ifdef __HAIR__ + else { + float4 str = kernel_tex_fetch(__curves, prim); + shader = __float_as_int(str.z); + } +#endif + + return kernel_tex_fetch(__shaders, (shader & SHADER_MASK)).flags; +} + +ccl_device_forceinline int intersection_get_shader(KernelGlobals *ccl_restrict kg, + const Intersection *isect) +{ + const int prim = kernel_tex_fetch(__prim_index, isect->prim); + int shader = 0; + +#ifdef __HAIR__ + if (kernel_tex_fetch(__prim_type, isect->prim) & PRIMITIVE_ALL_TRIANGLE) +#endif + { + shader = kernel_tex_fetch(__tri_shader, prim); + } +#ifdef __HAIR__ + else { + float4 str = kernel_tex_fetch(__curves, prim); + shader = __float_as_int(str.z); + } +#endif + + return shader & SHADER_MASK; +} + +CCL_NAMESPACE_END diff --git a/intern/cycles/kernel/kernel_path.h b/intern/cycles/kernel/kernel_path.h index 5681510fc25..dd2390808ea 100644 --- a/intern/cycles/kernel/kernel_path.h +++ b/intern/cycles/kernel/kernel_path.h @@ -65,7 +65,6 @@ ccl_device_forceinline bool kernel_path_scene_intersect(KernelGlobals *kg, uint visibility = path_state_ray_visibility(kg, state); if (path_state_ao_bounce(kg, state)) { - visibility = PATH_RAY_SHADOW; ray->t = kernel_data.background.ao_distance; } @@ -416,7 +415,13 @@ ccl_device void kernel_path_indirect(KernelGlobals *kg, break; } else if (path_state_ao_bounce(kg, state)) { - break; + if (intersection_get_shader_flags(kg, &isect) & + (SD_HAS_TRANSPARENT_SHADOW | SD_HAS_EMISSION)) { + state->flag |= PATH_RAY_TERMINATE_AFTER_TRANSPARENT; + } + else { + break; + } } /* Setup shader data. */ @@ -554,7 +559,13 @@ ccl_device_forceinline void kernel_path_integrate(KernelGlobals *kg, break; } else if (path_state_ao_bounce(kg, state)) { - break; + if (intersection_get_shader_flags(kg, &isect) & + (SD_HAS_TRANSPARENT_SHADOW | SD_HAS_EMISSION)) { + state->flag |= PATH_RAY_TERMINATE_AFTER_TRANSPARENT; + } + else { + break; + } } /* Setup shader data. */ diff --git a/intern/cycles/kernel/kernel_types.h b/intern/cycles/kernel/kernel_types.h index c661d77edb1..74fa2826cd4 100644 --- a/intern/cycles/kernel/kernel_types.h +++ b/intern/cycles/kernel/kernel_types.h @@ -891,6 +891,8 @@ enum ShaderDataFlag { SD_HAS_CONSTANT_EMISSION = (1 << 27), /* Needs to access attributes for volume rendering */ SD_NEED_VOLUME_ATTRIBUTES = (1 << 28), + /* Shader has emission */ + SD_HAS_EMISSION = (1 << 29), SD_SHADER_FLAGS = (SD_USE_MIS | SD_HAS_TRANSPARENT_SHADOW | SD_HAS_VOLUME | SD_HAS_ONLY_VOLUME | SD_HETEROGENEOUS_VOLUME | SD_HAS_BSSRDF_BUMP | SD_VOLUME_EQUIANGULAR | diff --git a/intern/cycles/render/CMakeLists.txt b/intern/cycles/render/CMakeLists.txt index c67919b375a..feead27c5ca 100644 --- a/intern/cycles/render/CMakeLists.txt +++ b/intern/cycles/render/CMakeLists.txt @@ -24,6 +24,7 @@ set(INC_SYS set(SRC alembic.cpp + alembic_read.cpp attribute.cpp background.cpp bake.cpp @@ -67,6 +68,7 @@ set(SRC set(SRC_HEADERS alembic.h + alembic_read.h attribute.h bake.h background.h diff --git a/intern/cycles/render/alembic.cpp b/intern/cycles/render/alembic.cpp index c3a7b20f512..cf345ee075d 100644 --- a/intern/cycles/render/alembic.cpp +++ b/intern/cycles/render/alembic.cpp @@ -16,6 +16,7 @@ #include "render/alembic.h" +#include "render/alembic_read.h" #include "render/camera.h" #include "render/curves.h" #include "render/mesh.h" @@ -34,7 +35,48 @@ using namespace Alembic::AbcGeom; CCL_NAMESPACE_BEGIN -/* TODO(@kevindietrich): motion blur support */ +/* TODO(kevindietrich): motion blur support. */ + +template<typename SchemaType> +static vector<FaceSetShaderIndexPair> parse_face_sets_for_shader_assignment( + SchemaType &schema, const array<Node *> &used_shaders) +{ + vector<FaceSetShaderIndexPair> result; + + std::vector<std::string> face_set_names; + schema.getFaceSetNames(face_set_names); + + if (face_set_names.empty()) { + return result; + } + + for (const std::string &face_set_name : face_set_names) { + int shader_index = 0; + + for (Node *node : used_shaders) { + if (node->name == face_set_name) { + break; + } + + ++shader_index; + } + + if (shader_index >= used_shaders.size()) { + /* use the first shader instead if none was found */ + shader_index = 0; + } + + const Alembic::AbcGeom::IFaceSet face_set = schema.getFaceSet(face_set_name); + + if (!face_set.valid()) { + continue; + } + + result.push_back({face_set, shader_index}); + } + + return result; +} void CachedData::clear() { @@ -54,7 +96,7 @@ void CachedData::clear() subd_start_corner.clear(); transforms.clear(); triangles.clear(); - triangles_loops.clear(); + uv_loops.clear(); vertices.clear(); for (CachedAttribute &attr : attributes) { @@ -101,7 +143,7 @@ bool CachedData::is_constant() const CHECK_IF_CONSTANT(subd_start_corner) CHECK_IF_CONSTANT(transforms) CHECK_IF_CONSTANT(triangles) - CHECK_IF_CONSTANT(triangles_loops) + CHECK_IF_CONSTANT(uv_loops) CHECK_IF_CONSTANT(vertices) for (const CachedAttribute &attr : attributes) { @@ -140,7 +182,7 @@ void CachedData::invalidate_last_loaded_time(bool attributes_only) subd_start_corner.invalidate_last_loaded_time(); transforms.invalidate_last_loaded_time(); triangles.invalidate_last_loaded_time(); - triangles_loops.invalidate_last_loaded_time(); + uv_loops.invalidate_last_loaded_time(); vertices.invalidate_last_loaded_time(); } @@ -161,7 +203,7 @@ void CachedData::set_time_sampling(TimeSampling time_sampling) subd_start_corner.set_time_sampling(time_sampling); transforms.set_time_sampling(time_sampling); triangles.set_time_sampling(time_sampling); - triangles_loops.set_time_sampling(time_sampling); + uv_loops.set_time_sampling(time_sampling); vertices.set_time_sampling(time_sampling); for (CachedAttribute &attr : attributes) { @@ -169,36 +211,6 @@ void CachedData::set_time_sampling(TimeSampling time_sampling) } } -/* get the sample times to load data for the given the start and end frame of the procedural */ -static set<chrono_t> get_relevant_sample_times(AlembicProcedural *proc, - const TimeSampling &time_sampling, - size_t num_samples) -{ - set<chrono_t> result; - - if (num_samples < 2) { - result.insert(0.0); - return result; - } - - double start_frame = (double)(proc->get_start_frame() / proc->get_frame_rate()); - double end_frame = (double)((proc->get_end_frame() + 1) / proc->get_frame_rate()); - - size_t start_index = time_sampling.getFloorIndex(start_frame, num_samples).first; - size_t end_index = time_sampling.getCeilIndex(end_frame, num_samples).first; - - for (size_t i = start_index; i < end_index; ++i) { - result.insert(time_sampling.getSampleTime(i)); - } - - return result; -} - -static float3 make_float3_from_yup(const V3f &v) -{ - return make_float3(v.x, -v.z, v.y); -} - static M44d convert_yup_zup(const M44d &mtx, float scale_mult) { V3d scale, shear, rotation, translation; @@ -366,249 +378,6 @@ static Transform make_transform(const M44d &a, float scale) return trans; } -static void add_uvs(AlembicProcedural *proc, - const IV2fGeomParam &uvs, - CachedData &cached_data, - Progress &progress) -{ - if (uvs.getScope() != kFacevaryingScope) { - return; - } - - const TimeSamplingPtr time_sampling_ptr = uvs.getTimeSampling(); - - TimeSampling time_sampling; - if (time_sampling_ptr) { - time_sampling = *time_sampling_ptr; - } - - std::string name = Alembic::Abc::GetSourceName(uvs.getMetaData()); - - /* According to the convention, primary UVs should have had their name - * set using Alembic::Abc::SetSourceName, but you can't expect everyone - * to follow it! :) */ - if (name.empty()) { - name = uvs.getName(); - } - - CachedData::CachedAttribute &attr = cached_data.add_attribute(ustring(name), time_sampling); - attr.std = ATTR_STD_UV; - - ccl::set<chrono_t> times = get_relevant_sample_times(proc, time_sampling, uvs.getNumSamples()); - - /* Keys used to determine if the UVs do actually change over time. */ - ArraySample::Key previous_indices_key; - ArraySample::Key previous_values_key; - - foreach (chrono_t time, times) { - if (progress.get_cancel()) { - return; - } - - const ISampleSelector iss = ISampleSelector(time); - const IV2fGeomParam::Sample uvsample = uvs.getIndexedValue(iss); - - if (!uvsample.valid()) { - continue; - } - - const array<int3> *triangles = - cached_data.triangles.data_for_time_no_check(time).get_data_or_null(); - const array<int3> *triangles_loops = - cached_data.triangles_loops.data_for_time_no_check(time).get_data_or_null(); - - if (!triangles || !triangles_loops) { - continue; - } - - array<char> data; - data.resize(triangles->size() * 3 * sizeof(float2)); - - float2 *data_float2 = reinterpret_cast<float2 *>(data.data()); - - const ArraySample::Key indices_key = uvsample.getIndices()->getKey(); - const ArraySample::Key values_key = uvsample.getVals()->getKey(); - - if (indices_key == previous_indices_key && values_key == previous_values_key) { - attr.data.reuse_data_for_last_time(time); - } - else { - const unsigned int *indices = uvsample.getIndices()->get(); - const V2f *values = uvsample.getVals()->get(); - - for (const int3 &loop : *triangles_loops) { - unsigned int v0 = indices[loop.x]; - unsigned int v1 = indices[loop.y]; - unsigned int v2 = indices[loop.z]; - - data_float2[0] = make_float2(values[v0][0], values[v0][1]); - data_float2[1] = make_float2(values[v1][0], values[v1][1]); - data_float2[2] = make_float2(values[v2][0], values[v2][1]); - data_float2 += 3; - } - - attr.data.add_data(data, time); - } - - previous_indices_key = indices_key; - previous_values_key = values_key; - } -} - -static void add_normals(const Int32ArraySamplePtr face_indices, - const IN3fGeomParam &normals, - double time, - CachedData &cached_data) -{ - switch (normals.getScope()) { - case kFacevaryingScope: { - const ISampleSelector iss = ISampleSelector(time); - const IN3fGeomParam::Sample sample = normals.getExpandedValue(iss); - - if (!sample.valid()) { - return; - } - - CachedData::CachedAttribute &attr = cached_data.add_attribute(ustring(normals.getName()), - *normals.getTimeSampling()); - attr.std = ATTR_STD_VERTEX_NORMAL; - - const array<float3> *vertices = - cached_data.vertices.data_for_time_no_check(time).get_data_or_null(); - - if (!vertices) { - return; - } - - array<char> data; - data.resize(vertices->size() * sizeof(float3)); - - float3 *data_float3 = reinterpret_cast<float3 *>(data.data()); - - const int *face_indices_array = face_indices->get(); - const N3fArraySamplePtr values = sample.getVals(); - - for (size_t i = 0; i < face_indices->size(); ++i) { - int point_index = face_indices_array[i]; - data_float3[point_index] = make_float3_from_yup(values->get()[i]); - } - - attr.data.add_data(data, time); - break; - } - case kVaryingScope: - case kVertexScope: { - const ISampleSelector iss = ISampleSelector(time); - const IN3fGeomParam::Sample sample = normals.getExpandedValue(iss); - - if (!sample.valid()) { - return; - } - - CachedData::CachedAttribute &attr = cached_data.add_attribute(ustring(normals.getName()), - *normals.getTimeSampling()); - attr.std = ATTR_STD_VERTEX_NORMAL; - - const array<float3> *vertices = - cached_data.vertices.data_for_time_no_check(time).get_data_or_null(); - - if (!vertices) { - return; - } - - array<char> data; - data.resize(vertices->size() * sizeof(float3)); - - float3 *data_float3 = reinterpret_cast<float3 *>(data.data()); - - const Imath::V3f *values = sample.getVals()->get(); - - for (size_t i = 0; i < vertices->size(); ++i) { - data_float3[i] = make_float3_from_yup(values[i]); - } - - attr.data.add_data(data, time); - - break; - } - default: { - break; - } - } -} - -static void add_positions(const P3fArraySamplePtr positions, double time, CachedData &cached_data) -{ - if (!positions) { - return; - } - - array<float3> vertices; - vertices.reserve(positions->size()); - - for (size_t i = 0; i < positions->size(); i++) { - V3f f = positions->get()[i]; - vertices.push_back_reserved(make_float3_from_yup(f)); - } - - cached_data.vertices.add_data(vertices, time); -} - -static void add_triangles(const Int32ArraySamplePtr face_counts, - const Int32ArraySamplePtr face_indices, - double time, - CachedData &cached_data, - const array<int> &polygon_to_shader) -{ - if (!face_counts || !face_indices) { - return; - } - - const size_t num_faces = face_counts->size(); - const int *face_counts_array = face_counts->get(); - const int *face_indices_array = face_indices->get(); - - size_t num_triangles = 0; - for (size_t i = 0; i < face_counts->size(); i++) { - num_triangles += face_counts_array[i] - 2; - } - - array<int> shader; - array<int3> triangles; - array<int3> triangles_loops; - shader.reserve(num_triangles); - triangles.reserve(num_triangles); - triangles_loops.reserve(num_triangles); - int index_offset = 0; - - for (size_t i = 0; i < num_faces; i++) { - int current_shader = 0; - - if (!polygon_to_shader.empty()) { - current_shader = polygon_to_shader[i]; - } - - for (int j = 0; j < face_counts_array[i] - 2; j++) { - int v0 = face_indices_array[index_offset]; - int v1 = face_indices_array[index_offset + j + 1]; - int v2 = face_indices_array[index_offset + j + 2]; - - shader.push_back_reserved(current_shader); - - /* Alembic orders the loops following the RenderMan convention, so need to go in reverse. */ - triangles.push_back_reserved(make_int3(v2, v1, v0)); - triangles_loops.push_back_reserved( - make_int3(index_offset + j + 2, index_offset + j + 1, index_offset)); - } - - index_offset += face_counts_array[i]; - } - - cached_data.triangles.add_data(triangles, time); - cached_data.triangles_loops.add_data(triangles_loops, time); - cached_data.shader.add_data(shader, time); -} - NODE_DEFINE(AlembicObject) { NodeType *type = NodeType::add("alembic_object", create); @@ -648,398 +417,137 @@ bool AlembicObject::has_data_loaded() const return data_loaded; } -void AlembicObject::update_shader_attributes(const ICompoundProperty &arb_geom_params, - Progress &progress) -{ - AttributeRequestSet requested_attributes = get_requested_attributes(); - - foreach (const AttributeRequest &attr, requested_attributes.requests) { - if (progress.get_cancel()) { - return; - } - - bool attr_exists = false; - foreach (CachedData::CachedAttribute &cached_attr, cached_data.attributes) { - if (cached_attr.name == attr.name) { - attr_exists = true; - break; - } - } - - if (attr_exists) { - continue; - } - - read_attribute(arb_geom_params, attr.name, progress); - } - - cached_data.invalidate_last_loaded_time(true); - need_shader_update = false; -} - -template<typename SchemaType> -void AlembicObject::read_face_sets(SchemaType &schema, - array<int> &polygon_to_shader, - ISampleSelector sample_sel) +void AlembicObject::load_data_in_cache(CachedData &cached_data, + AlembicProcedural *proc, + IPolyMeshSchema &schema, + Progress &progress) { - std::vector<std::string> face_sets; - schema.getFaceSetNames(face_sets); - - if (face_sets.empty()) { - return; - } - - const Int32ArraySamplePtr face_counts = schema.getFaceCountsProperty().getValue(); - - polygon_to_shader.resize(face_counts->size()); - - foreach (const std::string &face_set_name, face_sets) { - int shader_index = 0; - - foreach (Node *node, get_used_shaders()) { - if (node->name == face_set_name) { - break; - } - - ++shader_index; - } - - if (shader_index >= get_used_shaders().size()) { - /* use the first shader instead if none was found */ - shader_index = 0; - } - - const IFaceSet face_set = schema.getFaceSet(face_set_name); - - if (!face_set.valid()) { - continue; - } - - const IFaceSetSchema face_schem = face_set.getSchema(); - const IFaceSetSchema::Sample face_sample = face_schem.getValue(sample_sel); - const Int32ArraySamplePtr group_faces = face_sample.getFaces(); - const size_t num_group_faces = group_faces->size(); - - for (size_t l = 0; l < num_group_faces; l++) { - size_t pos = (*group_faces)[l]; - - if (pos >= polygon_to_shader.size()) { - continue; - } - - polygon_to_shader[pos] = shader_index; - } - } -} - -void AlembicObject::load_all_data(AlembicProcedural *proc, - IPolyMeshSchema &schema, - Progress &progress) -{ - cached_data.clear(); - /* Only load data for the original Geometry. */ if (instance_of) { return; } - const TimeSamplingPtr time_sampling = schema.getTimeSampling(); - cached_data.set_time_sampling(*time_sampling); - - const IN3fGeomParam &normals = schema.getNormalsParam(); - - ccl::set<chrono_t> times = get_relevant_sample_times( - proc, *time_sampling, schema.getNumSamples()); - - /* Key used to determine if the triangles change over time, if the key is the same as the - * last one, we can avoid creating a new entry in the cache and simply point to the last - * frame. */ - ArraySample::Key previous_key; - - /* read topology */ - foreach (chrono_t time, times) { - if (progress.get_cancel()) { - return; - } - - const ISampleSelector iss = ISampleSelector(time); - const IPolyMeshSchema::Sample sample = schema.getValue(iss); - - add_positions(sample.getPositions(), time, cached_data); - - /* Only copy triangles for other frames if the topology is changing over time as well. */ - if (schema.getTopologyVariance() != kHomogenousTopology || cached_data.triangles.size() == 0) { - const ArraySample::Key key = sample.getFaceIndices()->getKey(); - - if (key == previous_key) { - cached_data.triangles.reuse_data_for_last_time(time); - cached_data.triangles_loops.reuse_data_for_last_time(time); - cached_data.shader.reuse_data_for_last_time(time); - } - else { - /* start by reading the face sets (per face shader), as we directly split polygons to - * triangles - */ - array<int> polygon_to_shader; - read_face_sets(schema, polygon_to_shader, iss); - - add_triangles( - sample.getFaceCounts(), sample.getFaceIndices(), time, cached_data, polygon_to_shader); - } + cached_data.clear(); - previous_key = key; - } + PolyMeshSchemaData data; + data.topology_variance = schema.getTopologyVariance(); + data.time_sampling = schema.getTimeSampling(); + data.positions = schema.getPositionsProperty(); + data.face_counts = schema.getFaceCountsProperty(); + data.face_indices = schema.getFaceIndicesProperty(); + data.normals = schema.getNormalsParam(); + data.num_samples = schema.getNumSamples(); + data.shader_face_sets = parse_face_sets_for_shader_assignment(schema, get_used_shaders()); - if (normals.valid()) { - add_normals(sample.getFaceIndices(), normals, time, cached_data); - } - } + read_geometry_data(proc, cached_data, data, progress); if (progress.get_cancel()) { return; } - update_shader_attributes(schema.getArbGeomParams(), progress); + /* Use the schema as the base compound property to also be able to look for top level properties. + */ + read_attributes( + proc, cached_data, schema, schema.getUVsParam(), get_requested_attributes(), progress); if (progress.get_cancel()) { return; } - const IV2fGeomParam &uvs = schema.getUVsParam(); - - if (uvs.valid()) { - add_uvs(proc, uvs, cached_data, progress); - } - + cached_data.invalidate_last_loaded_time(true); data_loaded = true; } -void AlembicObject::load_all_data(AlembicProcedural *proc, ISubDSchema &schema, Progress &progress) +void AlembicObject::load_data_in_cache(CachedData &cached_data, + AlembicProcedural *proc, + ISubDSchema &schema, + Progress &progress) { - cached_data.clear(); - /* Only load data for the original Geometry. */ if (instance_of) { return; } - AttributeRequestSet requested_attributes = get_requested_attributes(); - - const TimeSamplingPtr time_sampling = schema.getTimeSampling(); - cached_data.set_time_sampling(*time_sampling); - - ccl::set<chrono_t> times = get_relevant_sample_times( - proc, *time_sampling, schema.getNumSamples()); - - /* read topology */ - foreach (chrono_t time, times) { - if (progress.get_cancel()) { - return; - } - - const ISampleSelector iss = ISampleSelector(time); - const ISubDSchema::Sample sample = schema.getValue(iss); - - add_positions(sample.getPositions(), time, cached_data); - - const Int32ArraySamplePtr face_counts = sample.getFaceCounts(); - const Int32ArraySamplePtr face_indices = sample.getFaceIndices(); - - /* start by reading the face sets (per face shader) */ - array<int> polygon_to_shader; - read_face_sets(schema, polygon_to_shader, iss); - - /* read faces */ - array<int> subd_start_corner; - array<int> shader; - array<int> subd_num_corners; - array<bool> subd_smooth; - array<int> subd_ptex_offset; - array<int> subd_face_corners; - - const size_t num_faces = face_counts->size(); - const int *face_counts_array = face_counts->get(); - const int *face_indices_array = face_indices->get(); - - int num_ngons = 0; - int num_corners = 0; - for (size_t i = 0; i < face_counts->size(); i++) { - num_ngons += (face_counts_array[i] == 4 ? 0 : 1); - num_corners += face_counts_array[i]; - } - - subd_start_corner.reserve(num_faces); - subd_num_corners.reserve(num_faces); - subd_smooth.reserve(num_faces); - subd_ptex_offset.reserve(num_faces); - shader.reserve(num_faces); - subd_face_corners.reserve(num_corners); - - int start_corner = 0; - int current_shader = 0; - int ptex_offset = 0; - - for (size_t i = 0; i < face_counts->size(); i++) { - num_corners = face_counts_array[i]; - - if (!polygon_to_shader.empty()) { - current_shader = polygon_to_shader[i]; - } - - subd_start_corner.push_back_reserved(start_corner); - subd_num_corners.push_back_reserved(num_corners); - - for (int j = 0; j < num_corners; ++j) { - subd_face_corners.push_back_reserved(face_indices_array[start_corner + j]); - } - - shader.push_back_reserved(current_shader); - subd_smooth.push_back_reserved(1); - subd_ptex_offset.push_back_reserved(ptex_offset); - - ptex_offset += (num_corners == 4 ? 1 : num_corners); - - start_corner += num_corners; - } - - cached_data.shader.add_data(shader, time); - cached_data.subd_start_corner.add_data(subd_start_corner, time); - cached_data.subd_num_corners.add_data(subd_num_corners, time); - cached_data.subd_smooth.add_data(subd_smooth, time); - cached_data.subd_ptex_offset.add_data(subd_ptex_offset, time); - cached_data.subd_face_corners.add_data(subd_face_corners, time); - cached_data.num_ngons.add_data(num_ngons, time); - - /* read creases */ - Int32ArraySamplePtr creases_length = sample.getCreaseLengths(); - Int32ArraySamplePtr creases_indices = sample.getCreaseIndices(); - FloatArraySamplePtr creases_sharpnesses = sample.getCreaseSharpnesses(); - - if (creases_length && creases_indices && creases_sharpnesses) { - array<int> creases_edge; - array<float> creases_weight; - - creases_edge.reserve(creases_sharpnesses->size() * 2); - creases_weight.reserve(creases_sharpnesses->size()); - - int length_offset = 0; - int weight_offset = 0; - for (size_t c = 0; c < creases_length->size(); ++c) { - const int crease_length = creases_length->get()[c]; - - for (size_t j = 0; j < crease_length - 1; ++j) { - creases_edge.push_back_reserved(creases_indices->get()[length_offset + j]); - creases_edge.push_back_reserved(creases_indices->get()[length_offset + j + 1]); - creases_weight.push_back_reserved(creases_sharpnesses->get()[weight_offset++]); - } - - length_offset += crease_length; - } - - cached_data.subd_creases_edge.add_data(creases_edge, time); - cached_data.subd_creases_weight.add_data(creases_weight, time); - } - } + cached_data.clear(); - /* TODO(@kevindietrich) : attributes, need test files */ + SubDSchemaData data; + data.time_sampling = schema.getTimeSampling(); + data.num_samples = schema.getNumSamples(); + data.topology_variance = schema.getTopologyVariance(); + data.face_counts = schema.getFaceCountsProperty(); + data.face_indices = schema.getFaceIndicesProperty(); + data.positions = schema.getPositionsProperty(); + data.face_varying_interpolate_boundary = schema.getFaceVaryingInterpolateBoundaryProperty(); + data.face_varying_propagate_corners = schema.getFaceVaryingPropagateCornersProperty(); + data.interpolate_boundary = schema.getInterpolateBoundaryProperty(); + data.crease_indices = schema.getCreaseIndicesProperty(); + data.crease_lengths = schema.getCreaseLengthsProperty(); + data.crease_sharpnesses = schema.getCreaseSharpnessesProperty(); + data.corner_indices = schema.getCornerIndicesProperty(); + data.corner_sharpnesses = schema.getCornerSharpnessesProperty(); + data.holes = schema.getHolesProperty(); + data.subdivision_scheme = schema.getSubdivisionSchemeProperty(); + data.velocities = schema.getVelocitiesProperty(); + data.shader_face_sets = parse_face_sets_for_shader_assignment(schema, get_used_shaders()); + + read_geometry_data(proc, cached_data, data, progress); if (progress.get_cancel()) { return; } + /* Use the schema as the base compound property to also be able to look for top level properties. + */ + read_attributes( + proc, cached_data, schema, schema.getUVsParam(), get_requested_attributes(), progress); + + cached_data.invalidate_last_loaded_time(true); data_loaded = true; } -void AlembicObject::load_all_data(AlembicProcedural *proc, - const ICurvesSchema &schema, - Progress &progress, - float default_radius) +void AlembicObject::load_data_in_cache(CachedData &cached_data, + AlembicProcedural *proc, + const ICurvesSchema &schema, + Progress &progress) { - cached_data.clear(); - /* Only load data for the original Geometry. */ if (instance_of) { return; } - const TimeSamplingPtr time_sampling = schema.getTimeSampling(); - cached_data.set_time_sampling(*time_sampling); - - ccl::set<chrono_t> times = get_relevant_sample_times( - proc, *time_sampling, schema.getNumSamples()); - - foreach (chrono_t time, times) { - if (progress.get_cancel()) { - return; - } - - const ISampleSelector iss = ISampleSelector(time); - const ICurvesSchema::Sample sample = schema.getValue(iss); - - const Int32ArraySamplePtr curves_num_vertices = sample.getCurvesNumVertices(); - const P3fArraySamplePtr position = sample.getPositions(); - - const IFloatGeomParam widths_param = schema.getWidthsParam(); - FloatArraySamplePtr radiuses; - - if (widths_param.valid()) { - IFloatGeomParam::Sample wsample = widths_param.getExpandedValue(iss); - radiuses = wsample.getVals(); - } - - const bool do_radius = (radiuses != nullptr) && (radiuses->size() > 1); - float radius = (radiuses && radiuses->size() == 1) ? (*radiuses)[0] : default_radius; - - array<float3> curve_keys; - array<float> curve_radius; - array<int> curve_first_key; - array<int> curve_shader; - - const bool is_homogenous = schema.getTopologyVariance() == kHomogenousTopology; - - curve_keys.reserve(position->size()); - curve_radius.reserve(position->size()); - curve_first_key.reserve(curves_num_vertices->size()); - curve_shader.reserve(curves_num_vertices->size()); - - int offset = 0; - for (size_t i = 0; i < curves_num_vertices->size(); i++) { - const int num_vertices = curves_num_vertices->get()[i]; - - for (int j = 0; j < num_vertices; j++) { - const V3f &f = position->get()[offset + j]; - curve_keys.push_back_reserved(make_float3_from_yup(f)); - - if (do_radius) { - radius = (*radiuses)[offset + j]; - } - - curve_radius.push_back_reserved(radius * radius_scale); - } - - if (!is_homogenous || cached_data.curve_first_key.size() == 0) { - curve_first_key.push_back_reserved(offset); - curve_shader.push_back_reserved(0); - } - - offset += num_vertices; - } + cached_data.clear(); - cached_data.curve_keys.add_data(curve_keys, time); - cached_data.curve_radius.add_data(curve_radius, time); + CurvesSchemaData data; + data.positions = schema.getPositionsProperty(); + data.position_weights = schema.getPositionWeightsProperty(); + data.normals = schema.getNormalsParam(); + data.knots = schema.getKnotsProperty(); + data.orders = schema.getOrdersProperty(); + data.widths = schema.getWidthsParam(); + data.velocities = schema.getVelocitiesProperty(); + data.time_sampling = schema.getTimeSampling(); + data.topology_variance = schema.getTopologyVariance(); + data.num_samples = schema.getNumSamples(); + data.num_vertices = schema.getNumVerticesProperty(); + data.default_radius = proc->get_default_radius(); + data.radius_scale = get_radius_scale(); + + read_geometry_data(proc, cached_data, data, progress); - if (!is_homogenous || cached_data.curve_first_key.size() == 0) { - cached_data.curve_first_key.add_data(curve_first_key, time); - cached_data.curve_shader.add_data(curve_shader, time); - } + if (progress.get_cancel()) { + return; } - // TODO(@kevindietrich): attributes, need example files + /* Use the schema as the base compound property to also be able to look for top level properties. + */ + read_attributes( + proc, cached_data, schema, schema.getUVsParam(), get_requested_attributes(), progress); + cached_data.invalidate_last_loaded_time(true); data_loaded = true; } -void AlembicObject::setup_transform_cache(float scale) +void AlembicObject::setup_transform_cache(CachedData &cached_data, float scale) { cached_data.transforms.clear(); cached_data.transforms.invalidate_last_loaded_time(); @@ -1102,188 +610,6 @@ AttributeRequestSet AlembicObject::get_requested_attributes() return requested_attributes; } -void AlembicObject::read_attribute(const ICompoundProperty &arb_geom_params, - const ustring &attr_name, - Progress &progress) -{ - const PropertyHeader *prop = arb_geom_params.getPropertyHeader(attr_name.c_str()); - - if (prop == nullptr) { - return; - } - - if (IV2fProperty::matches(prop->getMetaData()) && Alembic::AbcGeom::isUV(*prop)) { - const IV2fGeomParam ¶m = IV2fGeomParam(arb_geom_params, prop->getName()); - - CachedData::CachedAttribute &attribute = cached_data.add_attribute(attr_name, - *param.getTimeSampling()); - - for (size_t i = 0; i < param.getNumSamples(); ++i) { - if (progress.get_cancel()) { - return; - } - - ISampleSelector iss = ISampleSelector(index_t(i)); - - IV2fGeomParam::Sample sample; - param.getIndexed(sample, iss); - - const chrono_t time = param.getTimeSampling()->getSampleTime(index_t(i)); - - if (param.getScope() == kFacevaryingScope) { - V2fArraySamplePtr values = sample.getVals(); - UInt32ArraySamplePtr indices = sample.getIndices(); - - attribute.std = ATTR_STD_NONE; - attribute.element = ATTR_ELEMENT_CORNER; - attribute.type_desc = TypeFloat2; - - const array<int3> *triangles = - cached_data.triangles.data_for_time_no_check(time).get_data_or_null(); - const array<int3> *triangles_loops = - cached_data.triangles_loops.data_for_time_no_check(time).get_data_or_null(); - - if (!triangles || !triangles_loops) { - return; - } - - array<char> data; - data.resize(triangles->size() * 3 * sizeof(float2)); - - float2 *data_float2 = reinterpret_cast<float2 *>(data.data()); - - for (const int3 &loop : *triangles_loops) { - unsigned int v0 = (*indices)[loop.x]; - unsigned int v1 = (*indices)[loop.y]; - unsigned int v2 = (*indices)[loop.z]; - - data_float2[0] = make_float2((*values)[v0][0], (*values)[v0][1]); - data_float2[1] = make_float2((*values)[v1][0], (*values)[v1][1]); - data_float2[2] = make_float2((*values)[v2][0], (*values)[v2][1]); - data_float2 += 3; - } - - attribute.data.set_time_sampling(*param.getTimeSampling()); - attribute.data.add_data(data, time); - } - } - } - else if (IC3fProperty::matches(prop->getMetaData())) { - const IC3fGeomParam ¶m = IC3fGeomParam(arb_geom_params, prop->getName()); - - CachedData::CachedAttribute &attribute = cached_data.add_attribute(attr_name, - *param.getTimeSampling()); - - for (size_t i = 0; i < param.getNumSamples(); ++i) { - if (progress.get_cancel()) { - return; - } - - ISampleSelector iss = ISampleSelector(index_t(i)); - - IC3fGeomParam::Sample sample; - param.getIndexed(sample, iss); - - const chrono_t time = param.getTimeSampling()->getSampleTime(index_t(i)); - - C3fArraySamplePtr values = sample.getVals(); - - attribute.std = ATTR_STD_NONE; - - if (param.getScope() == kVaryingScope) { - attribute.element = ATTR_ELEMENT_CORNER_BYTE; - attribute.type_desc = TypeRGBA; - - const array<int3> *triangles = - cached_data.triangles.data_for_time_no_check(time).get_data_or_null(); - - if (!triangles) { - return; - } - - array<char> data; - data.resize(triangles->size() * 3 * sizeof(uchar4)); - - uchar4 *data_uchar4 = reinterpret_cast<uchar4 *>(data.data()); - - int offset = 0; - for (const int3 &tri : *triangles) { - Imath::C3f v = (*values)[tri.x]; - data_uchar4[offset + 0] = color_float_to_byte(make_float3(v.x, v.y, v.z)); - - v = (*values)[tri.y]; - data_uchar4[offset + 1] = color_float_to_byte(make_float3(v.x, v.y, v.z)); - - v = (*values)[tri.z]; - data_uchar4[offset + 2] = color_float_to_byte(make_float3(v.x, v.y, v.z)); - - offset += 3; - } - - attribute.data.set_time_sampling(*param.getTimeSampling()); - attribute.data.add_data(data, time); - } - } - } - else if (IC4fProperty::matches(prop->getMetaData())) { - const IC4fGeomParam ¶m = IC4fGeomParam(arb_geom_params, prop->getName()); - - CachedData::CachedAttribute &attribute = cached_data.add_attribute(attr_name, - *param.getTimeSampling()); - - for (size_t i = 0; i < param.getNumSamples(); ++i) { - if (progress.get_cancel()) { - return; - } - - ISampleSelector iss = ISampleSelector(index_t(i)); - - IC4fGeomParam::Sample sample; - param.getIndexed(sample, iss); - - const chrono_t time = param.getTimeSampling()->getSampleTime(index_t(i)); - - C4fArraySamplePtr values = sample.getVals(); - - attribute.std = ATTR_STD_NONE; - - if (param.getScope() == kVaryingScope) { - attribute.element = ATTR_ELEMENT_CORNER_BYTE; - attribute.type_desc = TypeRGBA; - - const array<int3> *triangles = - cached_data.triangles.data_for_time_no_check(time).get_data_or_null(); - - if (!triangles) { - return; - } - - array<char> data; - data.resize(triangles->size() * 3 * sizeof(uchar4)); - - uchar4 *data_uchar4 = reinterpret_cast<uchar4 *>(data.data()); - - int offset = 0; - for (const int3 &tri : *triangles) { - Imath::C4f v = (*values)[tri.x]; - data_uchar4[offset + 0] = color_float4_to_uchar4(make_float4(v.r, v.g, v.b, v.a)); - - v = (*values)[tri.y]; - data_uchar4[offset + 1] = color_float4_to_uchar4(make_float4(v.r, v.g, v.b, v.a)); - - v = (*values)[tri.z]; - data_uchar4[offset + 2] = color_float4_to_uchar4(make_float4(v.r, v.g, v.b, v.a)); - - offset += 3; - } - - attribute.data.set_time_sampling(*param.getTimeSampling()); - attribute.data.add_data(data, time); - } - } - } -} - /* Update existing attributes and remove any attribute not in the cached_data, those attributes * were added by Cycles (e.g. face normals) */ static void update_attributes(AttributeSet &attributes, CachedData &cached_data, double frame_time) @@ -1291,7 +617,11 @@ static void update_attributes(AttributeSet &attributes, CachedData &cached_data, set<Attribute *> cached_attributes; for (CachedData::CachedAttribute &attribute : cached_data.attributes) { - const array<char> *attr_data = attribute.data.data_for_time(frame_time).get_data_or_null(); + const CacheLookupResult<array<char>> result = attribute.data.data_for_time(frame_time); + + if (result.has_no_data_for_time()) { + continue; + } Attribute *attr = nullptr; if (attribute.std != ATTR_STD_NONE) { @@ -1304,18 +634,19 @@ static void update_attributes(AttributeSet &attributes, CachedData &cached_data, cached_attributes.insert(attr); - if (!attr_data) { - /* no new data */ + if (!result.has_new_data()) { continue; } + const ccl::array<char> &attr_data = result.get_data(); + /* weak way of detecting if the topology has changed * todo: reuse code from device_update patch */ - if (attr->buffer.size() != attr_data->size()) { - attr->buffer.resize(attr_data->size()); + if (attr->buffer.size() != attr_data.size()) { + attr->buffer.resize(attr_data.size()); } - memcpy(attr->data(), attr_data->data(), attr_data->size()); + memcpy(attr->data(), attr_data.data(), attr_data.size()); attr->modified = true; } @@ -1476,6 +807,7 @@ void AlembicProcedural::generate(Scene *scene, Progress &progress) read_subd(object, frame_time); } + object->need_shader_update = false; object->clear_modified(); } @@ -1960,12 +1292,17 @@ void AlembicProcedural::build_caches(Progress &progress) if (!object->has_data_loaded()) { IPolyMesh polymesh(object->iobject, Alembic::Abc::kWrapExisting); IPolyMeshSchema schema = polymesh.getSchema(); - object->load_all_data(this, schema, progress); + object->load_data_in_cache(object->get_cached_data(), this, schema, progress); } else if (object->need_shader_update) { IPolyMesh polymesh(object->iobject, Alembic::Abc::kWrapExisting); IPolyMeshSchema schema = polymesh.getSchema(); - object->update_shader_attributes(schema.getArbGeomParams(), progress); + read_attributes(this, + object->get_cached_data(), + schema, + schema.getUVsParam(), + object->get_requested_attributes(), + progress); } } else if (object->schema_type == AlembicObject::CURVES) { @@ -1973,24 +1310,29 @@ void AlembicProcedural::build_caches(Progress &progress) object->radius_scale_is_modified()) { ICurves curves(object->iobject, Alembic::Abc::kWrapExisting); ICurvesSchema schema = curves.getSchema(); - object->load_all_data(this, schema, progress, default_radius); + object->load_data_in_cache(object->get_cached_data(), this, schema, progress); } } else if (object->schema_type == AlembicObject::SUBD) { if (!object->has_data_loaded()) { ISubD subd_mesh(object->iobject, Alembic::Abc::kWrapExisting); ISubDSchema schema = subd_mesh.getSchema(); - object->load_all_data(this, schema, progress); + object->load_data_in_cache(object->get_cached_data(), this, schema, progress); } else if (object->need_shader_update) { ISubD subd_mesh(object->iobject, Alembic::Abc::kWrapExisting); ISubDSchema schema = subd_mesh.getSchema(); - object->update_shader_attributes(schema.getArbGeomParams(), progress); + read_attributes(this, + object->get_cached_data(), + schema, + schema.getUVsParam(), + object->get_requested_attributes(), + progress); } } if (scale_is_modified() || object->get_cached_data().transforms.size() == 0) { - object->setup_transform_cache(scale); + object->setup_transform_cache(object->get_cached_data(), scale); } } } diff --git a/intern/cycles/render/alembic.h b/intern/cycles/render/alembic.h index 3bbd10fad61..61c0e40fe4a 100644 --- a/intern/cycles/render/alembic.h +++ b/intern/cycles/render/alembic.h @@ -152,6 +152,10 @@ template<typename T> class DataStore { double last_loaded_time = std::numeric_limits<double>::max(); public: + /* Keys used to compare values. */ + Alembic::AbcCoreAbstract::ArraySample::Key key1; + Alembic::AbcCoreAbstract::ArraySample::Key key2; + void set_time_sampling(Alembic::AbcCoreAbstract::TimeSampling time_sampling_) { time_sampling = time_sampling_; @@ -225,6 +229,11 @@ template<typename T> class DataStore { index_data_map.push_back({time, data_index.source_time, data_index.index}); } + void add_no_data(double time) + { + index_data_map.push_back({time, time, -1ul}); + } + bool is_constant() const { return data.size() <= 1; @@ -284,7 +293,7 @@ struct CachedData { DataStore<array<int3>> triangles{}; /* triangle "loops" are the polygons' vertices indices used for indexing face varying attributes * (like UVs) */ - DataStore<array<int3>> triangles_loops{}; + DataStore<array<int>> uv_loops{}; DataStore<array<int>> shader{}; /* subd data */ @@ -362,16 +371,18 @@ class AlembicObject : public Node { void set_object(Object *object); Object *get_object(); - void load_all_data(AlembicProcedural *proc, - Alembic::AbcGeom::IPolyMeshSchema &schema, - Progress &progress); - void load_all_data(AlembicProcedural *proc, - Alembic::AbcGeom::ISubDSchema &schema, - Progress &progress); - void load_all_data(AlembicProcedural *proc, - const Alembic::AbcGeom::ICurvesSchema &schema, - Progress &progress, - float default_radius); + void load_data_in_cache(CachedData &cached_data, + AlembicProcedural *proc, + Alembic::AbcGeom::IPolyMeshSchema &schema, + Progress &progress); + void load_data_in_cache(CachedData &cached_data, + AlembicProcedural *proc, + Alembic::AbcGeom::ISubDSchema &schema, + Progress &progress); + void load_data_in_cache(CachedData &cached_data, + AlembicProcedural *proc, + const Alembic::AbcGeom::ICurvesSchema &schema, + Progress &progress); bool has_data_loaded() const; @@ -397,33 +408,21 @@ class AlembicObject : public Node { CachedData &get_cached_data() { - return cached_data; + return cached_data_; } bool is_constant() const { - return cached_data.is_constant(); + return cached_data_.is_constant(); } Object *object = nullptr; bool data_loaded = false; - CachedData cached_data; - - void update_shader_attributes(const Alembic::AbcGeom::ICompoundProperty &arb_geom_params, - Progress &progress); - - void read_attribute(const Alembic::AbcGeom::ICompoundProperty &arb_geom_params, - const ustring &attr_name, - Progress &progress); - - template<typename SchemaType> - void read_face_sets(SchemaType &schema, - array<int> &polygon_to_shader, - Alembic::AbcGeom::ISampleSelector sample_sel); + CachedData cached_data_; - void setup_transform_cache(float scale); + void setup_transform_cache(CachedData &cached_data, float scale); AttributeRequestSet get_requested_attributes(); }; diff --git a/intern/cycles/render/alembic_read.cpp b/intern/cycles/render/alembic_read.cpp new file mode 100644 index 00000000000..4d09c40b07b --- /dev/null +++ b/intern/cycles/render/alembic_read.cpp @@ -0,0 +1,1007 @@ +/* + * Copyright 2021 Blender Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "render/alembic_read.h" + +#include "render/alembic.h" +#include "render/mesh.h" + +#include "util/util_progress.h" + +#ifdef WITH_ALEMBIC + +using namespace Alembic::AbcGeom; + +CCL_NAMESPACE_BEGIN + +static float3 make_float3_from_yup(const V3f &v) +{ + return make_float3(v.x, -v.z, v.y); +} + +/* get the sample times to load data for the given the start and end frame of the procedural */ +static set<chrono_t> get_relevant_sample_times(AlembicProcedural *proc, + const TimeSampling &time_sampling, + size_t num_samples) +{ + set<chrono_t> result; + + if (num_samples < 2) { + result.insert(0.0); + return result; + } + + // load the data for the entire animation + const double start_frame = static_cast<double>(proc->get_start_frame()); + const double end_frame = static_cast<double>(proc->get_end_frame()); + + const double frame_rate = static_cast<double>(proc->get_frame_rate()); + const double start_time = start_frame / frame_rate; + const double end_time = (end_frame + 1) / frame_rate; + + const size_t start_index = time_sampling.getFloorIndex(start_time, num_samples).first; + const size_t end_index = time_sampling.getCeilIndex(end_time, num_samples).first; + + for (size_t i = start_index; i < end_index; ++i) { + result.insert(time_sampling.getSampleTime(i)); + } + + return result; +} + +/* Main function to read data, this will iterate over all the relevant sample times for the + * duration of the requested animation, and call the DataReadingFunc for each of those sample time. + */ +template<typename Params, typename DataReadingFunc> +static void read_data_loop(AlembicProcedural *proc, + CachedData &cached_data, + const Params ¶ms, + DataReadingFunc &&func, + Progress &progress) +{ + const std::set<chrono_t> times = get_relevant_sample_times( + proc, *params.time_sampling, params.num_samples); + + cached_data.set_time_sampling(*params.time_sampling); + + for (chrono_t time : times) { + if (progress.get_cancel()) { + return; + } + + func(cached_data, params, time); + } +} + +/* Polygon Mesh Geometries. */ + +/* Compute the vertex normals in case none are present in the IPolyMeshSchema, this is mostly used + * to avoid computing them in the GeometryManager in order to speed up data updates. */ +static void compute_vertex_normals(CachedData &cache, double current_time) +{ + if (cache.vertices.size() == 0) { + return; + } + + CachedData::CachedAttribute &attr_normal = cache.add_attribute( + ustring("N"), cache.vertices.get_time_sampling()); + attr_normal.std = ATTR_STD_VERTEX_NORMAL; + attr_normal.element = ATTR_ELEMENT_VERTEX; + attr_normal.type_desc = TypeNormal; + + const array<float3> *vertices = + cache.vertices.data_for_time_no_check(current_time).get_data_or_null(); + const array<int3> *triangles = + cache.triangles.data_for_time_no_check(current_time).get_data_or_null(); + + if (!vertices || !triangles) { + attr_normal.data.add_no_data(current_time); + return; + } + + array<char> attr_data(vertices->size() * sizeof(float3)); + float3 *attr_ptr = reinterpret_cast<float3 *>(attr_data.data()); + memset(attr_ptr, 0, vertices->size() * sizeof(float3)); + + for (size_t t = 0; t < triangles->size(); ++t) { + const int3 tri_int3 = triangles->data()[t]; + Mesh::Triangle tri{}; + tri.v[0] = tri_int3[0]; + tri.v[1] = tri_int3[1]; + tri.v[2] = tri_int3[2]; + + const float3 tri_N = tri.compute_normal(vertices->data()); + + for (int v = 0; v < 3; ++v) { + attr_ptr[tri_int3[v]] += tri_N; + } + } + + for (size_t v = 0; v < vertices->size(); ++v) { + attr_ptr[v] = normalize(attr_ptr[v]); + } + + attr_normal.data.add_data(attr_data, current_time); +} + +static void add_normals(const Int32ArraySamplePtr face_indices, + const IN3fGeomParam &normals, + double time, + CachedData &cached_data) +{ + switch (normals.getScope()) { + case kFacevaryingScope: { + const ISampleSelector iss = ISampleSelector(time); + const IN3fGeomParam::Sample sample = normals.getExpandedValue(iss); + + if (!sample.valid()) { + return; + } + + CachedData::CachedAttribute &attr = cached_data.add_attribute(ustring(normals.getName()), + *normals.getTimeSampling()); + attr.std = ATTR_STD_VERTEX_NORMAL; + + const array<float3> *vertices = + cached_data.vertices.data_for_time_no_check(time).get_data_or_null(); + + if (!vertices) { + return; + } + + array<char> data; + data.resize(vertices->size() * sizeof(float3)); + + float3 *data_float3 = reinterpret_cast<float3 *>(data.data()); + + const int *face_indices_array = face_indices->get(); + const N3fArraySamplePtr values = sample.getVals(); + + for (size_t i = 0; i < face_indices->size(); ++i) { + int point_index = face_indices_array[i]; + data_float3[point_index] = make_float3_from_yup(values->get()[i]); + } + + attr.data.add_data(data, time); + break; + } + case kVaryingScope: + case kVertexScope: { + const ISampleSelector iss = ISampleSelector(time); + const IN3fGeomParam::Sample sample = normals.getExpandedValue(iss); + + if (!sample.valid()) { + return; + } + + CachedData::CachedAttribute &attr = cached_data.add_attribute(ustring(normals.getName()), + *normals.getTimeSampling()); + attr.std = ATTR_STD_VERTEX_NORMAL; + + const array<float3> *vertices = + cached_data.vertices.data_for_time_no_check(time).get_data_or_null(); + + if (!vertices) { + return; + } + + array<char> data; + data.resize(vertices->size() * sizeof(float3)); + + float3 *data_float3 = reinterpret_cast<float3 *>(data.data()); + + const Imath::V3f *values = sample.getVals()->get(); + + for (size_t i = 0; i < vertices->size(); ++i) { + data_float3[i] = make_float3_from_yup(values[i]); + } + + attr.data.add_data(data, time); + + break; + } + default: { + break; + } + } +} + +static void add_positions(const P3fArraySamplePtr positions, double time, CachedData &cached_data) +{ + if (!positions) { + return; + } + + array<float3> vertices; + vertices.reserve(positions->size()); + + for (size_t i = 0; i < positions->size(); i++) { + V3f f = positions->get()[i]; + vertices.push_back_reserved(make_float3_from_yup(f)); + } + + cached_data.vertices.add_data(vertices, time); +} + +static void add_triangles(const Int32ArraySamplePtr face_counts, + const Int32ArraySamplePtr face_indices, + double time, + CachedData &cached_data, + const array<int> &polygon_to_shader) +{ + if (!face_counts || !face_indices) { + return; + } + + const size_t num_faces = face_counts->size(); + const int *face_counts_array = face_counts->get(); + const int *face_indices_array = face_indices->get(); + + size_t num_triangles = 0; + for (size_t i = 0; i < face_counts->size(); i++) { + num_triangles += face_counts_array[i] - 2; + } + + array<int> shader; + array<int3> triangles; + array<int> uv_loops; + shader.reserve(num_triangles); + triangles.reserve(num_triangles); + uv_loops.reserve(num_triangles * 3); + int index_offset = 0; + + for (size_t i = 0; i < num_faces; i++) { + int current_shader = 0; + + if (!polygon_to_shader.empty()) { + current_shader = polygon_to_shader[i]; + } + + for (int j = 0; j < face_counts_array[i] - 2; j++) { + int v0 = face_indices_array[index_offset]; + int v1 = face_indices_array[index_offset + j + 1]; + int v2 = face_indices_array[index_offset + j + 2]; + + shader.push_back_reserved(current_shader); + + /* Alembic orders the loops following the RenderMan convention, so need to go in reverse. */ + triangles.push_back_reserved(make_int3(v2, v1, v0)); + uv_loops.push_back_reserved(index_offset + j + 2); + uv_loops.push_back_reserved(index_offset + j + 1); + uv_loops.push_back_reserved(index_offset); + } + + index_offset += face_counts_array[i]; + } + + cached_data.triangles.add_data(triangles, time); + cached_data.uv_loops.add_data(uv_loops, time); + cached_data.shader.add_data(shader, time); +} + +static array<int> compute_polygon_to_shader_map( + const Int32ArraySamplePtr &face_counts, + const vector<FaceSetShaderIndexPair> &face_set_shader_index, + ISampleSelector sample_sel) +{ + if (face_set_shader_index.empty()) { + return {}; + } + + if (!face_counts) { + return {}; + } + + if (face_counts->size() == 0) { + return {}; + } + + array<int> polygon_to_shader(face_counts->size()); + + for (const FaceSetShaderIndexPair &pair : face_set_shader_index) { + const IFaceSet &face_set = pair.face_set; + const IFaceSetSchema face_schem = face_set.getSchema(); + const IFaceSetSchema::Sample face_sample = face_schem.getValue(sample_sel); + const Int32ArraySamplePtr group_faces = face_sample.getFaces(); + const size_t num_group_faces = group_faces->size(); + + for (size_t l = 0; l < num_group_faces; l++) { + size_t pos = (*group_faces)[l]; + + if (pos >= polygon_to_shader.size()) { + continue; + } + + polygon_to_shader[pos] = pair.shader_index; + } + } + + return polygon_to_shader; +} + +static void read_poly_mesh_geometry(CachedData &cached_data, + const PolyMeshSchemaData &data, + chrono_t time) +{ + const ISampleSelector iss = ISampleSelector(time); + + add_positions(data.positions.getValue(iss), time, cached_data); + + const Int32ArraySamplePtr face_counts = data.face_counts.getValue(iss); + const Int32ArraySamplePtr face_indices = data.face_indices.getValue(iss); + + /* Only copy triangles for other frames if the topology is changing over time as well. */ + if (data.topology_variance != kHomogeneousTopology || cached_data.triangles.size() == 0) { + bool do_triangles = true; + + /* Compare key with last one to check whether the topology changed. */ + if (cached_data.triangles.size() > 0) { + const ArraySample::Key key = face_indices->getKey(); + + if (key == cached_data.triangles.key1) { + do_triangles = false; + } + + cached_data.triangles.key1 = key; + } + + if (do_triangles) { + const array<int> polygon_to_shader = compute_polygon_to_shader_map( + face_counts, data.shader_face_sets, iss); + add_triangles(face_counts, face_indices, time, cached_data, polygon_to_shader); + } + else { + cached_data.triangles.reuse_data_for_last_time(time); + cached_data.uv_loops.reuse_data_for_last_time(time); + cached_data.shader.reuse_data_for_last_time(time); + } + + /* Initialize the first key. */ + if (data.topology_variance != kHomogeneousTopology && cached_data.triangles.size() == 1) { + cached_data.triangles.key1 = face_indices->getKey(); + } + } + + if (data.normals.valid()) { + add_normals(face_indices, data.normals, time, cached_data); + } + else { + compute_vertex_normals(cached_data, time); + } +} + +void read_geometry_data(AlembicProcedural *proc, + CachedData &cached_data, + const PolyMeshSchemaData &data, + Progress &progress) +{ + read_data_loop(proc, cached_data, data, read_poly_mesh_geometry, progress); +} + +/* Subdivision Geometries */ + +static void add_subd_polygons(CachedData &cached_data, const SubDSchemaData &data, chrono_t time) +{ + const ISampleSelector iss = ISampleSelector(time); + + const Int32ArraySamplePtr face_counts = data.face_counts.getValue(iss); + const Int32ArraySamplePtr face_indices = data.face_indices.getValue(iss); + + array<int> subd_start_corner; + array<int> shader; + array<int> subd_num_corners; + array<bool> subd_smooth; + array<int> subd_ptex_offset; + array<int> subd_face_corners; + array<int> uv_loops; + + const size_t num_faces = face_counts->size(); + const int *face_counts_array = face_counts->get(); + const int *face_indices_array = face_indices->get(); + + int num_ngons = 0; + int num_corners = 0; + for (size_t i = 0; i < face_counts->size(); i++) { + num_ngons += (face_counts_array[i] == 4 ? 0 : 1); + num_corners += face_counts_array[i]; + } + + subd_start_corner.reserve(num_faces); + subd_num_corners.reserve(num_faces); + subd_smooth.reserve(num_faces); + subd_ptex_offset.reserve(num_faces); + shader.reserve(num_faces); + subd_face_corners.reserve(num_corners); + uv_loops.reserve(num_corners); + + int start_corner = 0; + int current_shader = 0; + int ptex_offset = 0; + + const array<int> polygon_to_shader = compute_polygon_to_shader_map( + face_counts, data.shader_face_sets, iss); + + for (size_t i = 0; i < face_counts->size(); i++) { + num_corners = face_counts_array[i]; + + if (!polygon_to_shader.empty()) { + current_shader = polygon_to_shader[i]; + } + + subd_start_corner.push_back_reserved(start_corner); + subd_num_corners.push_back_reserved(num_corners); + + for (int j = 0; j < num_corners; ++j) { + subd_face_corners.push_back_reserved(face_indices_array[start_corner + j]); + uv_loops.push_back_reserved(start_corner + j); + } + + shader.push_back_reserved(current_shader); + subd_smooth.push_back_reserved(1); + subd_ptex_offset.push_back_reserved(ptex_offset); + + ptex_offset += (num_corners == 4 ? 1 : num_corners); + + start_corner += num_corners; + } + + cached_data.shader.add_data(shader, time); + cached_data.subd_start_corner.add_data(subd_start_corner, time); + cached_data.subd_num_corners.add_data(subd_num_corners, time); + cached_data.subd_smooth.add_data(subd_smooth, time); + cached_data.subd_ptex_offset.add_data(subd_ptex_offset, time); + cached_data.subd_face_corners.add_data(subd_face_corners, time); + cached_data.num_ngons.add_data(num_ngons, time); + cached_data.uv_loops.add_data(uv_loops, time); +} + +static void add_subd_creases(CachedData &cached_data, const SubDSchemaData &data, chrono_t time) +{ + if (!(data.crease_indices.valid() && data.crease_indices.valid() && + data.crease_sharpnesses.valid())) { + return; + } + + const ISampleSelector iss = ISampleSelector(time); + + const Int32ArraySamplePtr creases_length = data.crease_lengths.getValue(iss); + const Int32ArraySamplePtr creases_indices = data.crease_indices.getValue(iss); + const FloatArraySamplePtr creases_sharpnesses = data.crease_sharpnesses.getValue(iss); + + if (creases_length && creases_indices && creases_sharpnesses) { + array<int> creases_edge; + array<float> creases_weight; + + creases_edge.reserve(creases_sharpnesses->size() * 2); + creases_weight.reserve(creases_sharpnesses->size()); + + int length_offset = 0; + int weight_offset = 0; + for (size_t c = 0; c < creases_length->size(); ++c) { + const int crease_length = creases_length->get()[c]; + + for (size_t j = 0; j < crease_length - 1; ++j) { + creases_edge.push_back_reserved(creases_indices->get()[length_offset + j]); + creases_edge.push_back_reserved(creases_indices->get()[length_offset + j + 1]); + creases_weight.push_back_reserved(creases_sharpnesses->get()[weight_offset++]); + } + + length_offset += crease_length; + } + + cached_data.subd_creases_edge.add_data(creases_edge, time); + cached_data.subd_creases_weight.add_data(creases_weight, time); + } +} + +static void read_subd_geometry(CachedData &cached_data, const SubDSchemaData &data, chrono_t time) +{ + const ISampleSelector iss = ISampleSelector(time); + + add_positions(data.positions.getValue(iss), time, cached_data); + + if (data.topology_variance != kHomogenousTopology || cached_data.shader.size() == 0) { + add_subd_polygons(cached_data, data, time); + add_subd_creases(cached_data, data, time); + } +} + +void read_geometry_data(AlembicProcedural *proc, + CachedData &cached_data, + const SubDSchemaData &data, + Progress &progress) +{ + read_data_loop(proc, cached_data, data, read_subd_geometry, progress); +} + +/* Curve Geometries. */ + +static void read_curves_data(CachedData &cached_data, const CurvesSchemaData &data, chrono_t time) +{ + const ISampleSelector iss = ISampleSelector(time); + + const Int32ArraySamplePtr curves_num_vertices = data.num_vertices.getValue(iss); + const P3fArraySamplePtr position = data.positions.getValue(iss); + + FloatArraySamplePtr radiuses; + + if (data.widths.valid()) { + IFloatGeomParam::Sample wsample = data.widths.getExpandedValue(iss); + radiuses = wsample.getVals(); + } + + const bool do_radius = (radiuses != nullptr) && (radiuses->size() > 1); + float radius = (radiuses && radiuses->size() == 1) ? (*radiuses)[0] : data.default_radius; + + array<float3> curve_keys; + array<float> curve_radius; + array<int> curve_first_key; + array<int> curve_shader; + + const bool is_homogenous = data.topology_variance == kHomogenousTopology; + + curve_keys.reserve(position->size()); + curve_radius.reserve(position->size()); + curve_first_key.reserve(curves_num_vertices->size()); + curve_shader.reserve(curves_num_vertices->size()); + + int offset = 0; + for (size_t i = 0; i < curves_num_vertices->size(); i++) { + const int num_vertices = curves_num_vertices->get()[i]; + + for (int j = 0; j < num_vertices; j++) { + const V3f &f = position->get()[offset + j]; + // todo(@kevindietrich): we are reading too much data? + curve_keys.push_back_slow(make_float3_from_yup(f)); + + if (do_radius) { + radius = (*radiuses)[offset + j]; + } + + curve_radius.push_back_slow(radius * data.radius_scale); + } + + if (!is_homogenous || cached_data.curve_first_key.size() == 0) { + curve_first_key.push_back_reserved(offset); + curve_shader.push_back_reserved(0); + } + + offset += num_vertices; + } + + cached_data.curve_keys.add_data(curve_keys, time); + cached_data.curve_radius.add_data(curve_radius, time); + + if (!is_homogenous || cached_data.curve_first_key.size() == 0) { + cached_data.curve_first_key.add_data(curve_first_key, time); + cached_data.curve_shader.add_data(curve_shader, time); + } +} + +void read_geometry_data(AlembicProcedural *proc, + CachedData &cached_data, + const CurvesSchemaData &data, + Progress &progress) +{ + read_data_loop(proc, cached_data, data, read_curves_data, progress); +} + +/* Attributes conversions. */ + +/* Type traits for converting between Alembic and Cycles types. + */ + +template<typename T> struct value_type_converter { + using cycles_type = float; + static constexpr TypeDesc type_desc = TypeFloat; + static constexpr const char *type_name = "float (default)"; + + static cycles_type convert_value(T value) + { + return static_cast<float>(value); + } +}; + +template<> struct value_type_converter<Imath::V2f> { + using cycles_type = float2; + static constexpr TypeDesc type_desc = TypeFloat2; + static constexpr const char *type_name = "float2"; + + static cycles_type convert_value(Imath::V2f value) + { + return make_float2(value.x, value.y); + } +}; + +template<> struct value_type_converter<Imath::V3f> { + using cycles_type = float3; + static constexpr TypeDesc type_desc = TypeVector; + static constexpr const char *type_name = "float3"; + + static cycles_type convert_value(Imath::V3f value) + { + return make_float3_from_yup(value); + } +}; + +template<> struct value_type_converter<Imath::C3f> { + using cycles_type = uchar4; + static constexpr TypeDesc type_desc = TypeRGBA; + static constexpr const char *type_name = "rgb"; + + static cycles_type convert_value(Imath::C3f value) + { + return color_float_to_byte(make_float3(value.x, value.y, value.z)); + } +}; + +template<> struct value_type_converter<Imath::C4f> { + using cycles_type = uchar4; + static constexpr TypeDesc type_desc = TypeRGBA; + static constexpr const char *type_name = "rgba"; + + static cycles_type convert_value(Imath::C4f value) + { + return color_float4_to_uchar4(make_float4(value.r, value.g, value.b, value.a)); + } +}; + +/* Main function used to read attributes of any type. */ +template<typename TRAIT> +static void process_attribute(CachedData &cache, + CachedData::CachedAttribute &attribute, + GeometryScope scope, + const typename ITypedGeomParam<TRAIT>::Sample &sample, + double time) +{ + using abc_type = typename TRAIT::value_type; + using cycles_type = typename value_type_converter<abc_type>::cycles_type; + + const TypedArraySample<TRAIT> &values = *sample.getVals(); + + switch (scope) { + case kConstantScope: + case kVertexScope: { + const array<float3> *vertices = + cache.vertices.data_for_time_no_check(time).get_data_or_null(); + + if (!vertices) { + attribute.data.add_no_data(time); + return; + } + + if (vertices->size() != values.size()) { + attribute.data.add_no_data(time); + return; + } + + array<char> data(vertices->size() * sizeof(cycles_type)); + + cycles_type *pod_typed_data = reinterpret_cast<cycles_type *>(data.data()); + + for (size_t i = 0; i < values.size(); ++i) { + *pod_typed_data++ = value_type_converter<abc_type>::convert_value(values[i]); + } + + attribute.data.add_data(data, time); + break; + } + case kVaryingScope: { + const array<int3> *triangles = + cache.triangles.data_for_time_no_check(time).get_data_or_null(); + + if (!triangles) { + attribute.data.add_no_data(time); + return; + } + + array<char> data(triangles->size() * 3 * sizeof(cycles_type)); + + cycles_type *pod_typed_data = reinterpret_cast<cycles_type *>(data.data()); + + for (const int3 &tri : *triangles) { + *pod_typed_data++ = value_type_converter<abc_type>::convert_value(values[tri.x]); + *pod_typed_data++ = value_type_converter<abc_type>::convert_value(values[tri.y]); + *pod_typed_data++ = value_type_converter<abc_type>::convert_value(values[tri.z]); + } + + attribute.data.add_data(data, time); + break; + } + default: { + break; + } + } +} + +/* UVs are processed separately as their indexing is based on loops, instead of vertices or + * corners. */ +static void process_uvs(CachedData &cache, + CachedData::CachedAttribute &attribute, + GeometryScope scope, + const IV2fGeomParam::Sample &sample, + double time) +{ + if (scope != kFacevaryingScope) { + return; + } + + const array<int> *uv_loops = cache.uv_loops.data_for_time_no_check(time).get_data_or_null(); + + if (!uv_loops) { + return; + } + + const array<int3> *triangles = cache.triangles.data_for_time_no_check(time).get_data_or_null(); + const array<int> *corners = + cache.subd_face_corners.data_for_time_no_check(time).get_data_or_null(); + + array<char> data; + if (triangles) { + data.resize(triangles->size() * 3 * sizeof(float2)); + } + else if (corners) { + data.resize(corners->size() * sizeof(float2)); + } + else { + return; + } + + float2 *data_float2 = reinterpret_cast<float2 *>(data.data()); + + const uint32_t *indices = sample.getIndices()->get(); + const V2f *values = sample.getVals()->get(); + + for (const int uv_loop_index : *uv_loops) { + const uint32_t index = indices[uv_loop_index]; + *data_float2++ = make_float2(values[index][0], values[index][1]); + } + + attribute.data.add_data(data, time); +} + +/* Type of the function used to parse one time worth of data, either process_uvs or + * process_attribute_generic. */ +template<typename TRAIT> +using process_callback_type = void (*)(CachedData &, + CachedData::CachedAttribute &, + GeometryScope, + const typename ITypedGeomParam<TRAIT>::Sample &, + double); + +/* Main loop to process the attributes, this will look at the given param's TimeSampling and + * extract data based on which frame time is requested by the procedural and execute the callback + * for each of those requested time. */ +template<typename TRAIT> +static void read_attribute_loop(AlembicProcedural *proc, + CachedData &cache, + const ITypedGeomParam<TRAIT> ¶m, + process_callback_type<TRAIT> callback, + Progress &progress, + AttributeStandard std = ATTR_STD_NONE) +{ + const std::set<chrono_t> times = get_relevant_sample_times( + proc, *param.getTimeSampling(), param.getNumSamples()); + + if (times.empty()) { + return; + } + + std::string name = param.getName(); + + if (std == ATTR_STD_UV) { + std::string uv_source_name = Alembic::Abc::GetSourceName(param.getMetaData()); + + /* According to the convention, primary UVs should have had their name + * set using Alembic::Abc::SetSourceName, but you can't expect everyone + * to follow it! :) */ + if (!uv_source_name.empty()) { + name = uv_source_name; + } + } + + CachedData::CachedAttribute &attribute = cache.add_attribute(ustring(name), + *param.getTimeSampling()); + + using abc_type = typename TRAIT::value_type; + + attribute.data.set_time_sampling(*param.getTimeSampling()); + attribute.std = std; + attribute.type_desc = value_type_converter<abc_type>::type_desc; + + if (attribute.type_desc == TypeRGBA) { + attribute.element = ATTR_ELEMENT_CORNER_BYTE; + } + else { + if (param.getScope() == kVaryingScope || param.getScope() == kFacevaryingScope) { + attribute.element = ATTR_ELEMENT_CORNER; + } + else { + attribute.element = ATTR_ELEMENT_VERTEX; + } + } + + for (const chrono_t time : times) { + if (progress.get_cancel()) { + return; + } + + ISampleSelector iss = ISampleSelector(time); + typename ITypedGeomParam<TRAIT>::Sample sample; + param.getIndexed(sample, iss); + + if (!sample.valid()) { + continue; + } + + if (!sample.getVals()) { + attribute.data.add_no_data(time); + continue; + } + + /* Check whether we already loaded constant data. */ + if (attribute.data.size() != 0) { + if (param.isConstant()) { + return; + } + + const ArraySample::Key indices_key = sample.getIndices()->getKey(); + const ArraySample::Key values_key = sample.getVals()->getKey(); + + const bool is_same_as_last_time = (indices_key == attribute.data.key1 && + values_key == attribute.data.key2); + + attribute.data.key1 = indices_key; + attribute.data.key2 = values_key; + + if (is_same_as_last_time) { + attribute.data.reuse_data_for_last_time(time); + continue; + } + } + + callback(cache, attribute, param.getScope(), sample, time); + } +} + +/* Attributes requests. */ + +/* This structure is used to tell which ICoumpoundProperty the PropertyHeader comes from, as we + * need the parent when downcasting to the proper type. */ +struct PropHeaderAndParent { + const PropertyHeader *prop; + ICompoundProperty parent; +}; + +/* Parse the ICompoundProperty to look for properties whose names appear in the + * AttributeRequestSet. This also looks into any child ICompoundProperty of the given + * ICompoundProperty. If no property of the given name is found, let it be that way, Cycles will + * use a zero value for the missing attribute. */ +static void parse_requested_attributes_recursive(const AttributeRequestSet &requested_attributes, + const ICompoundProperty &arb_geom_params, + vector<PropHeaderAndParent> &requested_properties) +{ + if (!arb_geom_params.valid()) { + return; + } + + for (const AttributeRequest &req : requested_attributes.requests) { + const PropertyHeader *property_header = arb_geom_params.getPropertyHeader(req.name.c_str()); + + if (!property_header) { + continue; + } + + requested_properties.push_back({property_header, arb_geom_params}); + } + + /* Look into children compound properties. */ + for (size_t i = 0; i < arb_geom_params.getNumProperties(); ++i) { + const PropertyHeader &property_header = arb_geom_params.getPropertyHeader(i); + + if (property_header.isCompound()) { + ICompoundProperty compound_property = ICompoundProperty(arb_geom_params, + property_header.getName()); + parse_requested_attributes_recursive( + requested_attributes, compound_property, requested_properties); + } + } +} + +/* Main entry point for parsing requested attributes from an ICompoundProperty, this exists so that + * we can simply return the list of properties instead of allocating it on the stack and passing it + * as a parameter. */ +static vector<PropHeaderAndParent> parse_requested_attributes( + const AttributeRequestSet &requested_attributes, const ICompoundProperty &arb_geom_params) +{ + vector<PropHeaderAndParent> requested_properties; + parse_requested_attributes_recursive( + requested_attributes, arb_geom_params, requested_properties); + return requested_properties; +} + +/* Read the attributes requested by the shaders from the archive. This will recursively find named + * attributes from the AttributeRequestSet in the ICompoundProperty and any of its compound child. + * The attributes are added to the CachedData's attribute list. For each attribute we will try to + * deduplicate data across consecutive frames. */ +void read_attributes(AlembicProcedural *proc, + CachedData &cache, + const ICompoundProperty &arb_geom_params, + const IV2fGeomParam &default_uvs_param, + const AttributeRequestSet &requested_attributes, + Progress &progress) +{ + if (default_uvs_param.valid()) { + /* Only the default UVs should be treated as the standard UV attribute. */ + read_attribute_loop(proc, cache, default_uvs_param, process_uvs, progress, ATTR_STD_UV); + } + + vector<PropHeaderAndParent> requested_properties = parse_requested_attributes( + requested_attributes, arb_geom_params); + + for (const PropHeaderAndParent &prop_and_parent : requested_properties) { + if (progress.get_cancel()) { + return; + } + + const PropertyHeader *prop = prop_and_parent.prop; + const ICompoundProperty &parent = prop_and_parent.parent; + + if (IBoolGeomParam::matches(*prop)) { + const IBoolGeomParam ¶m = IBoolGeomParam(parent, prop->getName()); + read_attribute_loop(proc, cache, param, process_attribute<BooleanTPTraits>, progress); + } + else if (IInt32GeomParam::matches(*prop)) { + const IInt32GeomParam ¶m = IInt32GeomParam(parent, prop->getName()); + read_attribute_loop(proc, cache, param, process_attribute<Int32TPTraits>, progress); + } + else if (IFloatGeomParam::matches(*prop)) { + const IFloatGeomParam ¶m = IFloatGeomParam(parent, prop->getName()); + read_attribute_loop(proc, cache, param, process_attribute<Float32TPTraits>, progress); + } + else if (IV2fGeomParam::matches(*prop)) { + const IV2fGeomParam ¶m = IV2fGeomParam(parent, prop->getName()); + if (Alembic::AbcGeom::isUV(*prop)) { + read_attribute_loop(proc, cache, param, process_uvs, progress); + } + else { + read_attribute_loop(proc, cache, param, process_attribute<V2fTPTraits>, progress); + } + } + else if (IV3fGeomParam::matches(*prop)) { + const IV3fGeomParam ¶m = IV3fGeomParam(parent, prop->getName()); + read_attribute_loop(proc, cache, param, process_attribute<V3fTPTraits>, progress); + } + else if (IN3fGeomParam::matches(*prop)) { + const IN3fGeomParam ¶m = IN3fGeomParam(parent, prop->getName()); + read_attribute_loop(proc, cache, param, process_attribute<N3fTPTraits>, progress); + } + else if (IC3fGeomParam::matches(*prop)) { + const IC3fGeomParam ¶m = IC3fGeomParam(parent, prop->getName()); + read_attribute_loop(proc, cache, param, process_attribute<C3fTPTraits>, progress); + } + else if (IC4fGeomParam::matches(*prop)) { + const IC4fGeomParam ¶m = IC4fGeomParam(parent, prop->getName()); + read_attribute_loop(proc, cache, param, process_attribute<C4fTPTraits>, progress); + } + } + + cache.invalidate_last_loaded_time(true); +} + +CCL_NAMESPACE_END + +#endif diff --git a/intern/cycles/render/alembic_read.h b/intern/cycles/render/alembic_read.h new file mode 100644 index 00000000000..9cc8622a1ba --- /dev/null +++ b/intern/cycles/render/alembic_read.h @@ -0,0 +1,134 @@ +/* + * Copyright 2021 Blender Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#ifdef WITH_ALEMBIC + +# include <Alembic/AbcCoreFactory/All.h> +# include <Alembic/AbcGeom/All.h> + +# include "util/util_vector.h" + +CCL_NAMESPACE_BEGIN + +class AlembicProcedural; +class AttributeRequestSet; +class Progress; +struct CachedData; + +/* Maps a FaceSet whose name matches that of a Shader to the index of said shader in the Geometry's + * used_shaders list. */ +struct FaceSetShaderIndexPair { + Alembic::AbcGeom::IFaceSet face_set; + int shader_index; +}; + +/* Data of an IPolyMeshSchema that we need to read. */ +struct PolyMeshSchemaData { + Alembic::AbcGeom::TimeSamplingPtr time_sampling; + size_t num_samples; + Alembic::AbcGeom::MeshTopologyVariance topology_variance; + + Alembic::AbcGeom::IP3fArrayProperty positions; + Alembic::AbcGeom::IInt32ArrayProperty face_indices; + Alembic::AbcGeom::IInt32ArrayProperty face_counts; + + Alembic::AbcGeom::IN3fGeomParam normals; + + vector<FaceSetShaderIndexPair> shader_face_sets; + + // Unsupported for now. + Alembic::AbcGeom::IV3fArrayProperty velocities; +}; + +void read_geometry_data(AlembicProcedural *proc, + CachedData &cached_data, + const PolyMeshSchemaData &data, + Progress &progress); + +/* Data of an ISubDSchema that we need to read. */ +struct SubDSchemaData { + Alembic::AbcGeom::TimeSamplingPtr time_sampling; + size_t num_samples; + Alembic::AbcGeom::MeshTopologyVariance topology_variance; + + Alembic::AbcGeom::IInt32ArrayProperty face_counts; + Alembic::AbcGeom::IInt32ArrayProperty face_indices; + Alembic::AbcGeom::IP3fArrayProperty positions; + + Alembic::AbcGeom::IInt32ArrayProperty crease_indices; + Alembic::AbcGeom::IInt32ArrayProperty crease_lengths; + Alembic::AbcGeom::IFloatArrayProperty crease_sharpnesses; + + vector<FaceSetShaderIndexPair> shader_face_sets; + + // Those are unsupported for now. + Alembic::AbcGeom::IInt32ArrayProperty corner_indices; + Alembic::AbcGeom::IFloatArrayProperty corner_sharpnesses; + Alembic::AbcGeom::IInt32Property face_varying_interpolate_boundary; + Alembic::AbcGeom::IInt32Property face_varying_propagate_corners; + Alembic::AbcGeom::IInt32Property interpolate_boundary; + Alembic::AbcGeom::IInt32ArrayProperty holes; + Alembic::AbcGeom::IStringProperty subdivision_scheme; + Alembic::AbcGeom::IV3fArrayProperty velocities; +}; + +void read_geometry_data(AlembicProcedural *proc, + CachedData &cached_data, + const SubDSchemaData &data, + Progress &progress); + +/* Data of a ICurvesSchema that we need to read. */ +struct CurvesSchemaData { + Alembic::AbcGeom::TimeSamplingPtr time_sampling; + size_t num_samples; + Alembic::AbcGeom::MeshTopologyVariance topology_variance; + + Alembic::AbcGeom::IP3fArrayProperty positions; + + Alembic::AbcGeom::IInt32ArrayProperty num_vertices; + + float default_radius; + float radius_scale; + + // Those are unsupported for now. + Alembic::AbcGeom::IV3fArrayProperty velocities; + // if this property is invalid then the weight for every point is 1 + Alembic::AbcGeom::IFloatArrayProperty position_weights; + Alembic::AbcGeom::IN3fGeomParam normals; + Alembic::AbcGeom::IFloatGeomParam widths; + Alembic::AbcGeom::IUcharArrayProperty orders; + Alembic::AbcGeom::IFloatArrayProperty knots; + + // TODO(@kevindietrich): type, basis, wrap +}; + +void read_geometry_data(AlembicProcedural *proc, + CachedData &cached_data, + const CurvesSchemaData &data, + Progress &progress); + +void read_attributes(AlembicProcedural *proc, + CachedData &cache, + const Alembic::AbcGeom::ICompoundProperty &arb_geom_params, + const Alembic::AbcGeom::IV2fGeomParam &default_uvs_param, + const AttributeRequestSet &requested_attributes, + Progress &progress); + +CCL_NAMESPACE_END + +#endif diff --git a/intern/cycles/render/background.cpp b/intern/cycles/render/background.cpp index f0a779da012..b925e755434 100644 --- a/intern/cycles/render/background.cpp +++ b/intern/cycles/render/background.cpp @@ -59,6 +59,7 @@ Background::Background() : Node(get_node_type()) Background::~Background() { + dereference_all_used_nodes(); } void Background::device_update(Device *device, DeviceScene *dscene, Scene *scene) diff --git a/intern/cycles/render/geometry.cpp b/intern/cycles/render/geometry.cpp index 6ab927a7909..16fc36231b4 100644 --- a/intern/cycles/render/geometry.cpp +++ b/intern/cycles/render/geometry.cpp @@ -85,6 +85,7 @@ Geometry::Geometry(const NodeType *node_type, const Type type) Geometry::~Geometry() { + dereference_all_used_nodes(); delete bvh; } diff --git a/intern/cycles/render/geometry.h b/intern/cycles/render/geometry.h index 4d11d04e4c8..7db122f69cb 100644 --- a/intern/cycles/render/geometry.h +++ b/intern/cycles/render/geometry.h @@ -50,7 +50,7 @@ enum PackFlags : uint32_t { /* Pack the geometry information (e.g. triangle or curve keys indices). */ PACK_GEOMETRY = (1u << 0), - /* Pack the vertice, for Meshes and Volumes' bouding meshes. */ + /* Pack the vertices, for Meshes and Volumes' bounding meshes. */ PACK_VERTICES = (1u << 1), /* Pack the visibility flags for each triangle or curve. */ diff --git a/intern/cycles/render/light.cpp b/intern/cycles/render/light.cpp index a069b850512..5290d68e75a 100644 --- a/intern/cycles/render/light.cpp +++ b/intern/cycles/render/light.cpp @@ -159,6 +159,7 @@ NODE_DEFINE(Light) Light::Light() : Node(get_node_type()) { + dereference_all_used_nodes(); } void Light::tag_update(Scene *scene) diff --git a/intern/cycles/render/osl.cpp b/intern/cycles/render/osl.cpp index 53c67049571..9bd6e3a5e2d 100644 --- a/intern/cycles/render/osl.cpp +++ b/intern/cycles/render/osl.cpp @@ -91,10 +91,10 @@ void OSLShaderManager::reset(Scene * /*scene*/) shading_system_init(); } -void OSLShaderManager::device_update(Device *device, - DeviceScene *dscene, - Scene *scene, - Progress &progress) +void OSLShaderManager::device_update_specific(Device *device, + DeviceScene *dscene, + Scene *scene, + Progress &progress) { if (!need_update()) return; @@ -1149,7 +1149,7 @@ void OSLCompiler::compile(OSLGlobals *og, Shader *shader) shader->has_integrator_dependency = false; /* generate surface shader */ - if (shader->used && graph && output->input("Surface")->link) { + if (shader->reference_count() && graph && output->input("Surface")->link) { shader->osl_surface_ref = compile_type(shader, shader->graph, SHADER_TYPE_SURFACE); if (has_bump) @@ -1165,7 +1165,7 @@ void OSLCompiler::compile(OSLGlobals *og, Shader *shader) } /* generate volume shader */ - if (shader->used && graph && output->input("Volume")->link) { + if (shader->reference_count() && graph && output->input("Volume")->link) { shader->osl_volume_ref = compile_type(shader, shader->graph, SHADER_TYPE_VOLUME); shader->has_volume = true; } @@ -1173,7 +1173,7 @@ void OSLCompiler::compile(OSLGlobals *og, Shader *shader) shader->osl_volume_ref = OSL::ShaderGroupRef(); /* generate displacement shader */ - if (shader->used && graph && output->input("Displacement")->link) { + if (shader->reference_count() && graph && output->input("Displacement")->link) { shader->osl_displacement_ref = compile_type(shader, shader->graph, SHADER_TYPE_DISPLACEMENT); shader->has_displacement = true; } diff --git a/intern/cycles/render/osl.h b/intern/cycles/render/osl.h index ea2d0ca492c..f6aa98d867a 100644 --- a/intern/cycles/render/osl.h +++ b/intern/cycles/render/osl.h @@ -72,15 +72,18 @@ class OSLShaderManager : public ShaderManager { static void free_memory(); - void reset(Scene *scene); + void reset(Scene *scene) override; - bool use_osl() + bool use_osl() override { return true; } - void device_update(Device *device, DeviceScene *dscene, Scene *scene, Progress &progress); - void device_free(Device *device, DeviceScene *dscene, Scene *scene); + void device_update_specific(Device *device, + DeviceScene *dscene, + Scene *scene, + Progress &progress) override; + void device_free(Device *device, DeviceScene *dscene, Scene *scene) override; /* osl compile and query */ static bool osl_compile(const string &inputfile, const string &outputfile); diff --git a/intern/cycles/render/scene.cpp b/intern/cycles/render/scene.cpp index 70474368a4e..c4e7d2c79d6 100644 --- a/intern/cycles/render/scene.cpp +++ b/intern/cycles/render/scene.cpp @@ -143,21 +143,27 @@ void Scene::free_memory(bool final) delete bvh; bvh = NULL; - foreach (Shader *s, shaders) - delete s; - /* delete procedurals before other types as they may hold pointers to those types */ + /* The order of deletion is important to make sure data is freed based on possible dependencies + * as the Nodes' reference counts are decremented in the destructors: + * + * - Procedurals can create and hold pointers to any other types. + * - Objects can hold pointers to Geometries and ParticleSystems + * - Lights and Geometries can hold pointers to Shaders. + * + * Similarly, we first delete all nodes and their associated device data, and then the managers + * and their associated device data. + */ foreach (Procedural *p, procedurals) delete p; - foreach (Geometry *g, geometry) - delete g; foreach (Object *o, objects) delete o; - foreach (Light *l, lights) - delete l; + foreach (Geometry *g, geometry) + delete g; foreach (ParticleSystem *p, particle_systems) delete p; + foreach (Light *l, lights) + delete l; - shaders.clear(); geometry.clear(); objects.clear(); lights.clear(); @@ -169,7 +175,25 @@ void Scene::free_memory(bool final) film->device_free(device, &dscene, this); background->device_free(device, &dscene); integrator->device_free(device, &dscene, true); + } + + if (final) { + delete camera; + delete dicing_camera; + delete film; + delete background; + delete integrator; + } + + /* Delete Shaders after every other nodes to ensure that we do not try to decrement the reference + * count on some dangling pointer. */ + foreach (Shader *s, shaders) + delete s; + + shaders.clear(); + /* Now that all nodes have been deleted, we can safely delete managers and device data. */ + if (device) { object_manager->device_free(device, &dscene, true); geometry_manager->device_free(device, &dscene, true); shader_manager->device_free(device, &dscene, this); @@ -189,11 +213,6 @@ void Scene::free_memory(bool final) if (final) { delete lookup_tables; - delete camera; - delete dicing_camera; - delete film; - delete background; - delete integrator; delete object_manager; delete geometry_manager; delete shader_manager; @@ -504,9 +523,6 @@ bool Scene::update(Progress &progress, bool &kernel_switch_needed) { /* update scene */ if (need_update()) { - /* Updated used shader tag so we know which features are need for the kernel. */ - shader_manager->update_shaders_used(this); - /* Update max_closures. */ KernelIntegrator *kintegrator = &dscene.data.integrator; if (params.background) { @@ -581,7 +597,7 @@ int Scene::get_max_closure_count() int max_closures = 0; for (int i = 0; i < shaders.size(); i++) { Shader *shader = shaders[i]; - if (shader->used) { + if (shader->reference_count()) { int num_closures = shader->graph->get_num_closures(); max_closures = max(max_closures, num_closures); } @@ -742,9 +758,10 @@ template<> void Scene::delete_node_impl(ParticleSystem *node) particle_system_manager->tag_update(this); } -template<> void Scene::delete_node_impl(Shader * /*node*/) +template<> void Scene::delete_node_impl(Shader *shader) { /* don't delete unused shaders, not supported */ + shader->clear_reference_count(); } template<> void Scene::delete_node_impl(Procedural *node) @@ -811,9 +828,12 @@ template<> void Scene::delete_nodes(const set<ParticleSystem *> &nodes, const No particle_system_manager->tag_update(this); } -template<> void Scene::delete_nodes(const set<Shader *> & /*nodes*/, const NodeOwner * /*owner*/) +template<> void Scene::delete_nodes(const set<Shader *> &nodes, const NodeOwner * /*owner*/) { /* don't delete unused shaders, not supported */ + for (Shader *shader : nodes) { + shader->clear_reference_count(); + } } template<> void Scene::delete_nodes(const set<Procedural *> &nodes, const NodeOwner *owner) diff --git a/intern/cycles/render/shader.cpp b/intern/cycles/render/shader.cpp index 5ecbd92d96d..59b60904746 100644 --- a/intern/cycles/render/shader.cpp +++ b/intern/cycles/render/shader.cpp @@ -16,7 +16,6 @@ #include "device/device.h" -#include "render/alembic.h" #include "render/background.h" #include "render/camera.h" #include "render/colorspace.h" @@ -27,6 +26,7 @@ #include "render/nodes.h" #include "render/object.h" #include "render/osl.h" +#include "render/procedural.h" #include "render/scene.h" #include "render/shader.h" #include "render/svm.h" @@ -218,7 +218,6 @@ Shader::Shader() : Node(get_node_type()) displacement_method = DISPLACE_BUMP; id = -1; - used = false; need_update_uvs = true; need_update_attribute = true; @@ -382,8 +381,9 @@ void Shader::tag_used(Scene *scene) { /* if an unused shader suddenly gets used somewhere, it needs to be * recompiled because it was skipped for compilation before */ - if (!used) { + if (!reference_count()) { tag_modified(); + /* We do not reference here as the shader will be referenced when added to a socket. */ scene->shader_manager->tag_update(scene, ShaderManager::SHADER_MODIFIED); } } @@ -461,52 +461,28 @@ int ShaderManager::get_shader_id(Shader *shader, bool smooth) return id; } -void ShaderManager::update_shaders_used(Scene *scene) +void ShaderManager::device_update(Device *device, + DeviceScene *dscene, + Scene *scene, + Progress &progress) { if (!need_update()) { return; } - /* figure out which shaders are in use, so SVM/OSL can skip compiling them - * for speed and avoid loading image textures into memory */ uint id = 0; foreach (Shader *shader, scene->shaders) { - shader->used = false; shader->id = id++; } - scene->default_surface->used = true; - scene->default_light->used = true; - scene->default_background->used = true; - scene->default_empty->used = true; + /* Those shaders should always be compiled as they are used as fallback if a shader cannot be + * found, e.g. bad shader index for the triangle shaders on a Mesh. */ + assert(scene->default_surface->reference_count() != 0); + assert(scene->default_light->reference_count() != 0); + assert(scene->default_background->reference_count() != 0); + assert(scene->default_empty->reference_count() != 0); - if (scene->background->get_shader()) - scene->background->get_shader()->used = true; - -#ifdef WITH_ALEMBIC - foreach (Procedural *procedural, scene->procedurals) { - AlembicProcedural *abc_proc = static_cast<AlembicProcedural *>(procedural); - - foreach (Node *abc_node, abc_proc->get_objects()) { - AlembicObject *abc_object = static_cast<AlembicObject *>(abc_node); - - foreach (Node *node, abc_object->get_used_shaders()) { - Shader *shader = static_cast<Shader *>(node); - shader->used = true; - } - } - } -#endif - - foreach (Geometry *geom, scene->geometry) - foreach (Node *node, geom->get_used_shaders()) { - Shader *shader = static_cast<Shader *>(node); - shader->used = true; - } - - foreach (Light *light, scene->lights) - if (light->get_shader()) - const_cast<Shader *>(light->get_shader())->used = true; + device_update_specific(device, dscene, scene, progress); } void ShaderManager::device_update_common(Device *device, @@ -528,6 +504,8 @@ void ShaderManager::device_update_common(Device *device, if (shader->get_use_mis()) flag |= SD_USE_MIS; + if (shader->has_surface_emission) + flag |= SD_HAS_EMISSION; if (shader->has_surface_transparent && shader->get_use_transparent_shadow()) flag |= SD_HAS_TRANSPARENT_SHADOW; if (shader->has_volume) { @@ -637,6 +615,7 @@ void ShaderManager::add_default(Scene *scene) Shader *shader = scene->create_node<Shader>(); shader->name = "default_surface"; shader->set_graph(graph); + shader->reference(); scene->default_surface = shader; shader->tag_update(scene); } @@ -655,6 +634,8 @@ void ShaderManager::add_default(Scene *scene) shader->set_graph(graph); scene->default_volume = shader; shader->tag_update(scene); + /* No default reference for the volume to avoid compiling volume kernels if there are no actual + * volumes in the scene */ } /* default light */ @@ -671,6 +652,7 @@ void ShaderManager::add_default(Scene *scene) Shader *shader = scene->create_node<Shader>(); shader->name = "default_light"; shader->set_graph(graph); + shader->reference(); scene->default_light = shader; shader->tag_update(scene); } @@ -682,6 +664,7 @@ void ShaderManager::add_default(Scene *scene) Shader *shader = scene->create_node<Shader>(); shader->name = "default_background"; shader->set_graph(graph); + shader->reference(); scene->default_background = shader; shader->tag_update(scene); } @@ -693,6 +676,7 @@ void ShaderManager::add_default(Scene *scene) Shader *shader = scene->create_node<Shader>(); shader->name = "default_empty"; shader->set_graph(graph); + shader->reference(); scene->default_empty = shader; shader->tag_update(scene); } @@ -733,7 +717,7 @@ void ShaderManager::get_requested_features(Scene *scene, requested_features->nodes_features = 0; for (int i = 0; i < scene->shaders.size(); i++) { Shader *shader = scene->shaders[i]; - if (!shader->used) { + if (!shader->reference_count()) { continue; } diff --git a/intern/cycles/render/shader.h b/intern/cycles/render/shader.h index f47d64f346c..50c8bed4669 100644 --- a/intern/cycles/render/shader.h +++ b/intern/cycles/render/shader.h @@ -132,7 +132,6 @@ class Shader : public Node { /* determined before compiling */ uint id; - bool used; #ifdef WITH_OSL /* osl shading state references */ @@ -187,10 +186,11 @@ class ShaderManager { } /* device update */ - virtual void device_update(Device *device, - DeviceScene *dscene, - Scene *scene, - Progress &progress) = 0; + void device_update(Device *device, DeviceScene *dscene, Scene *scene, Progress &progress); + virtual void device_update_specific(Device *device, + DeviceScene *dscene, + Scene *scene, + Progress &progress) = 0; virtual void device_free(Device *device, DeviceScene *dscene, Scene *scene) = 0; void device_update_common(Device *device, DeviceScene *dscene, Scene *scene, Progress &progress); @@ -208,7 +208,6 @@ class ShaderManager { static void add_default(Scene *scene); /* Selective nodes compilation. */ - void update_shaders_used(Scene *scene); void get_requested_features(Scene *scene, DeviceRequestedFeatures *requested_features); static void free_memory(); diff --git a/intern/cycles/render/svm.cpp b/intern/cycles/render/svm.cpp index fce604234f1..5c793c5c016 100644 --- a/intern/cycles/render/svm.cpp +++ b/intern/cycles/render/svm.cpp @@ -69,10 +69,10 @@ void SVMShaderManager::device_update_shader(Scene *scene, << summary.full_report(); } -void SVMShaderManager::device_update(Device *device, - DeviceScene *dscene, - Scene *scene, - Progress &progress) +void SVMShaderManager::device_update_specific(Device *device, + DeviceScene *dscene, + Scene *scene, + Progress &progress) { if (!need_update()) return; @@ -776,7 +776,7 @@ void SVMCompiler::compile_type(Shader *shader, ShaderGraph *graph, ShaderType ty add_node(NODE_ENTER_BUMP_EVAL, bump_state_offset); } - if (shader->used) { + if (shader->reference_count()) { CompilerState state(graph); if (clin->link) { bool generate = false; diff --git a/intern/cycles/render/svm.h b/intern/cycles/render/svm.h index a4ca68e1d8d..d23ff3e2a47 100644 --- a/intern/cycles/render/svm.h +++ b/intern/cycles/render/svm.h @@ -44,10 +44,13 @@ class SVMShaderManager : public ShaderManager { SVMShaderManager(); ~SVMShaderManager(); - void reset(Scene *scene); + void reset(Scene *scene) override; - void device_update(Device *device, DeviceScene *dscene, Scene *scene, Progress &progress); - void device_free(Device *device, DeviceScene *dscene, Scene *scene); + void device_update_specific(Device *device, + DeviceScene *dscene, + Scene *scene, + Progress &progress) override; + void device_free(Device *device, DeviceScene *dscene, Scene *scene) override; protected: void device_update_shader(Scene *scene, diff --git a/intern/cycles/util/util_math_fast.h b/intern/cycles/util/util_math_fast.h index 107b36ce6cd..5ae56290f05 100644 --- a/intern/cycles/util/util_math_fast.h +++ b/intern/cycles/util/util_math_fast.h @@ -362,7 +362,7 @@ ccl_device float fast_atan2f(float y, float x) ccl_device float fast_log2f(float x) { /* NOTE: clamp to avoid special cases and make result "safe" from large - * negative values/nans. */ + * negative values/NAN's. */ x = clamp(x, FLT_MIN, FLT_MAX); unsigned bits = __float_as_uint(x); int exponent = (int)(bits >> 23) - 127; diff --git a/intern/cycles/util/util_simd.h b/intern/cycles/util/util_simd.h index 718ec9266b1..8e8caa98a1b 100644 --- a/intern/cycles/util/util_simd.h +++ b/intern/cycles/util/util_simd.h @@ -124,7 +124,7 @@ static struct StepTy { template<class type, int i0, int i1, int i2, int i3> type shuffle_neon(const type &a) { if (i0 == i1 && i0 == i2 && i0 == i3) { - return vdupq_laneq_s32(a, i0); + return type(vdupq_laneq_s32(int32x4_t(a), i0)); } static const uint8_t tbl[16] = {(i0 * 4) + 0, (i0 * 4) + 1, @@ -143,7 +143,7 @@ template<class type, int i0, int i1, int i2, int i3> type shuffle_neon(const typ (i3 * 4) + 2, (i3 * 4) + 3}; - return vqtbl1q_s8(int8x16_t(a), *(int8x16_t *)tbl); + return type(vqtbl1q_s8(int8x16_t(a), *(uint8x16_t *)tbl)); } template<class type, int i0, int i1, int i2, int i3> @@ -167,7 +167,7 @@ type shuffle_neon(const type &a, const type &b) (i3 * 4) + 2, (i3 * 4) + 3}; - return vqtbl1q_s8(int8x16_t(b), *(int8x16_t *)tbl); + return type(vqtbl1q_s8(int8x16_t(b), *(uint8x16_t *)tbl)); } else { @@ -188,7 +188,7 @@ type shuffle_neon(const type &a, const type &b) (i3 * 4) + 2 + 16, (i3 * 4) + 3 + 16}; - return vqtbl2q_s8((int8x16x2_t){a, b}, *(int8x16_t *)tbl); + return type(vqtbl2q_s8((int8x16x2_t){int8x16_t(a), int8x16_t(b)}, *(uint8x16_t *)tbl)); } } #endif /* __KERNEL_NEON */ diff --git a/intern/cycles/util/util_sseb.h b/intern/cycles/util/util_sseb.h index 1488da46b09..4dbd5b8046e 100644 --- a/intern/cycles/util/util_sseb.h +++ b/intern/cycles/util/util_sseb.h @@ -283,7 +283,7 @@ __forceinline uint32_t popcnt(const sseb &a) { # if defined(__KERNEL_NEON__) const int32x4_t mask = {1, 1, 1, 1}; - int32x4_t t = vandq_s32(a.m128, mask); + int32x4_t t = vandq_s32(vreinterpretq_s32_m128(a.m128), mask); return vaddvq_s32(t); # else return _mm_popcnt_u32(_mm_movemask_ps(a)); @@ -299,7 +299,7 @@ __forceinline uint32_t popcnt(const sseb &a) __forceinline bool reduce_and(const sseb &a) { # if defined(__KERNEL_NEON__) - return vaddvq_s32(a.m128) == -4; + return vaddvq_s32(vreinterpretq_s32_m128(a.m128)) == -4; # else return _mm_movemask_ps(a) == 0xf; # endif @@ -307,7 +307,7 @@ __forceinline bool reduce_and(const sseb &a) __forceinline bool reduce_or(const sseb &a) { # if defined(__KERNEL_NEON__) - return vaddvq_s32(a.m128) != 0x0; + return vaddvq_s32(vreinterpretq_s32_m128(a.m128)) != 0x0; # else return _mm_movemask_ps(a) != 0x0; # endif @@ -315,7 +315,7 @@ __forceinline bool reduce_or(const sseb &a) __forceinline bool all(const sseb &b) { # if defined(__KERNEL_NEON__) - return vaddvq_s32(b.m128) == -4; + return vaddvq_s32(vreinterpretq_s32_m128(b.m128)) == -4; # else return _mm_movemask_ps(b) == 0xf; # endif @@ -323,7 +323,7 @@ __forceinline bool all(const sseb &b) __forceinline bool any(const sseb &b) { # if defined(__KERNEL_NEON__) - return vaddvq_s32(b.m128) != 0x0; + return vaddvq_s32(vreinterpretq_s32_m128(b.m128)) != 0x0; # else return _mm_movemask_ps(b) != 0x0; # endif @@ -331,7 +331,7 @@ __forceinline bool any(const sseb &b) __forceinline bool none(const sseb &b) { # if defined(__KERNEL_NEON__) - return vaddvq_s32(b.m128) == 0x0; + return vaddvq_s32(vreinterpretq_s32_m128(b.m128)) == 0x0; # else return _mm_movemask_ps(b) == 0x0; # endif diff --git a/intern/cycles/util/util_ssef.h b/intern/cycles/util/util_ssef.h index d039b50a7d2..0c81ed87553 100644 --- a/intern/cycles/util/util_ssef.h +++ b/intern/cycles/util/util_ssef.h @@ -596,7 +596,7 @@ template<size_t i0, size_t i1, size_t i2, size_t i3> __forceinline const ssef shuffle(const ssef &b) { # ifdef __KERNEL_NEON__ - return shuffle_neon<ssef, i0, i1, i2, i3>(b.m128); + return shuffle_neon<float32x4_t, i0, i1, i2, i3>(b.m128); # else return _mm_castsi128_ps(_mm_shuffle_epi32(_mm_castps_si128(b), _MM_SHUFFLE(i3, i2, i1, i0))); # endif @@ -625,7 +625,7 @@ __forceinline const ssef shuffle(const ssef &a, const ssef &b) template<size_t i0> __forceinline const ssef shuffle(const ssef &a, const ssef &b) { # ifdef __KERNEL_NEON__ - return shuffle<float32x4_t, i0, i0, i0, i0>(a, b); + return shuffle_neon<float32x4_t, i0, i0, i0, i0>(a, b); # else return _mm_shuffle_ps(a, b, _MM_SHUFFLE(i0, i0, i0, i0)); # endif diff --git a/intern/cycles/util/util_ssei.h b/intern/cycles/util/util_ssei.h index 3ec69ab3700..cd51dbff2f1 100644 --- a/intern/cycles/util/util_ssei.h +++ b/intern/cycles/util/util_ssei.h @@ -446,7 +446,8 @@ template<size_t i0, size_t i1, size_t i2, size_t i3> __forceinline const ssei shuffle(const ssei &a) { # ifdef __KERNEL_NEON__ - return shuffle_neon<ssei, i0, i1, i2, i3>(a); + int32x4_t result = shuffle_neon<int32x4_t, i0, i1, i2, i3>(vreinterpretq_s32_m128i(a)); + return vreinterpretq_m128i_s32(result); # else return _mm_shuffle_epi32(a, _MM_SHUFFLE(i3, i2, i1, i0)); # endif @@ -456,7 +457,9 @@ template<size_t i0, size_t i1, size_t i2, size_t i3> __forceinline const ssei shuffle(const ssei &a, const ssei &b) { # ifdef __KERNEL_NEON__ - return shuffle_neon<ssei, i0, i1, i2, i3>(a, b); + int32x4_t result = shuffle_neon<int32x4_t, i0, i1, i2, i3>(vreinterpretq_s32_m128i(a), + vreinterpretq_s32_m128i(b)); + return vreinterpretq_m128i_s32(result); # else return _mm_castps_si128( _mm_shuffle_ps(_mm_castsi128_ps(a), _mm_castsi128_ps(b), _MM_SHUFFLE(i3, i2, i1, i0))); @@ -514,7 +517,7 @@ __forceinline const ssei vreduce_add(const ssei &v) __forceinline int reduce_min(const ssei &v) { # ifdef __KERNEL_NEON__ - return vminvq_s32(v); + return vminvq_s32(vreinterpretq_s32_m128i(v)); # else return extract<0>(vreduce_min(v)); # endif @@ -522,7 +525,7 @@ __forceinline int reduce_min(const ssei &v) __forceinline int reduce_max(const ssei &v) { # ifdef __KERNEL_NEON__ - return vmaxvq_s32(v); + return vmaxvq_s32(vreinterpretq_s32_m128i(v)); # else return extract<0>(vreduce_max(v)); # endif @@ -530,7 +533,7 @@ __forceinline int reduce_max(const ssei &v) __forceinline int reduce_add(const ssei &v) { # ifdef __KERNEL_NEON__ - return vaddvq_s32(v); + return vaddvq_s32(vreinterpretq_s32_m128i(v)); # else return extract<0>(vreduce_add(v)); # endif diff --git a/intern/cycles/util/util_texture.h b/intern/cycles/util/util_texture.h index b445ab1488f..71bf9c65911 100644 --- a/intern/cycles/util/util_texture.h +++ b/intern/cycles/util/util_texture.h @@ -21,18 +21,12 @@ CCL_NAMESPACE_BEGIN -/* Texture limits on devices. */ -#define TEX_NUM_MAX (INT_MAX >> 4) - /* Color to use when textures are not found. */ #define TEX_IMAGE_MISSING_R 1 #define TEX_IMAGE_MISSING_G 0 #define TEX_IMAGE_MISSING_B 1 #define TEX_IMAGE_MISSING_A 1 -/* Texture type. */ -#define kernel_tex_type(tex) (tex & IMAGE_DATA_TYPE_MASK) - /* Interpolation types for textures * cuda also use texture space to store other objects */ typedef enum InterpolationType { @@ -45,9 +39,6 @@ typedef enum InterpolationType { INTERPOLATION_NUM_TYPES, } InterpolationType; -/* Texture types - * Since we store the type in the lower bits of a flat index, - * the shift and bit mask constant below need to be kept in sync. */ typedef enum ImageDataType { IMAGE_DATA_TYPE_FLOAT4 = 0, IMAGE_DATA_TYPE_BYTE4 = 1, @@ -75,9 +66,6 @@ typedef enum ImageAlphaType { IMAGE_ALPHA_NUM_TYPES, } ImageAlphaType; -#define IMAGE_DATA_TYPE_SHIFT 4 -#define IMAGE_DATA_TYPE_MASK 0xF - /* Extension types for textures. * * Defines how the image is extrapolated past its original bounds. */ diff --git a/intern/ffmpeg/ffmpeg_compat.h b/intern/ffmpeg/ffmpeg_compat.h index 727fd4b9601..54a004d53e2 100644 --- a/intern/ffmpeg/ffmpeg_compat.h +++ b/intern/ffmpeg/ffmpeg_compat.h @@ -22,10 +22,17 @@ #include <libavformat/avformat.h> -/* check our ffmpeg is new enough, avoids user complaints */ -#if (LIBAVFORMAT_VERSION_MAJOR < 52) || \ - ((LIBAVFORMAT_VERSION_MAJOR == 52) && (LIBAVFORMAT_VERSION_MINOR <= 64)) -# error "FFmpeg 0.7 or newer is needed, Upgrade your FFmpeg or disable it" +/* Check if our ffmpeg is new enough, avoids user complaints. + * Minimum supported version is currently 3.2.0 which mean the following library versions: + * libavutil > 55.30 + * libavcodec > 57.60 + * libavformat > 57.50 + * + * We only check for one of these as they are usually updated in tandem. + */ +#if (LIBAVFORMAT_VERSION_MAJOR < 57) || \ + ((LIBAVFORMAT_VERSION_MAJOR == 57) && (LIBAVFORMAT_VERSION_MINOR <= 50)) +# error "FFmpeg 3.2.0 or newer is needed, Upgrade your FFmpeg or disable it" #endif /* end sanity check */ @@ -36,274 +43,6 @@ # define FFMPEG_INLINE static inline #endif -#include <libavcodec/avcodec.h> -#include <libavutil/mathematics.h> -#include <libavutil/opt.h> -#include <libavutil/rational.h> - -#if (LIBAVFORMAT_VERSION_MAJOR > 52) || \ - ((LIBAVFORMAT_VERSION_MAJOR >= 52) && (LIBAVFORMAT_VERSION_MINOR >= 101)) -# define FFMPEG_HAVE_PARSE_UTILS 1 -# include <libavutil/parseutils.h> -#endif - -#include <libswscale/swscale.h> - -#if (LIBAVFORMAT_VERSION_MAJOR > 52) || \ - ((LIBAVFORMAT_VERSION_MAJOR >= 52) && (LIBAVFORMAT_VERSION_MINOR >= 105)) -# define FFMPEG_HAVE_AVIO 1 -#endif - -#if (LIBAVCODEC_VERSION_MAJOR > 53) || \ - ((LIBAVCODEC_VERSION_MAJOR == 53) && (LIBAVCODEC_VERSION_MINOR > 1)) || \ - ((LIBAVCODEC_VERSION_MAJOR == 53) && (LIBAVCODEC_VERSION_MINOR == 1) && \ - (LIBAVCODEC_VERSION_MICRO >= 1)) || \ - ((LIBAVCODEC_VERSION_MAJOR == 52) && (LIBAVCODEC_VERSION_MINOR >= 121)) -# define FFMPEG_HAVE_DEFAULT_VAL_UNION 1 -#endif - -#if (LIBAVFORMAT_VERSION_MAJOR > 52) || \ - ((LIBAVFORMAT_VERSION_MAJOR >= 52) && (LIBAVFORMAT_VERSION_MINOR >= 101)) -# define FFMPEG_HAVE_AV_DUMP_FORMAT 1 -#endif - -#if (LIBAVFORMAT_VERSION_MAJOR > 52) || \ - ((LIBAVFORMAT_VERSION_MAJOR >= 52) && (LIBAVFORMAT_VERSION_MINOR >= 45)) -# define FFMPEG_HAVE_AV_GUESS_FORMAT 1 -#endif - -#if (LIBAVCODEC_VERSION_MAJOR > 52) || \ - ((LIBAVCODEC_VERSION_MAJOR >= 52) && (LIBAVCODEC_VERSION_MINOR >= 23)) -# define FFMPEG_HAVE_DECODE_AUDIO3 1 -# define FFMPEG_HAVE_DECODE_VIDEO2 1 -#endif - -#if (LIBAVCODEC_VERSION_MAJOR > 52) || \ - ((LIBAVCODEC_VERSION_MAJOR >= 52) && (LIBAVCODEC_VERSION_MINOR >= 64)) -# define FFMPEG_HAVE_AVMEDIA_TYPES 1 -#endif - -#if ((LIBAVCODEC_VERSION_MAJOR > 52) || \ - (LIBAVCODEC_VERSION_MAJOR >= 52) && (LIBAVCODEC_VERSION_MINOR >= 29)) && \ - ((LIBSWSCALE_VERSION_MAJOR > 0) || \ - (LIBSWSCALE_VERSION_MAJOR >= 0) && (LIBSWSCALE_VERSION_MINOR >= 10)) -# define FFMPEG_SWSCALE_COLOR_SPACE_SUPPORT -#endif - -#if ((LIBAVCODEC_VERSION_MAJOR > 54) || \ - (LIBAVCODEC_VERSION_MAJOR >= 54) && (LIBAVCODEC_VERSION_MINOR > 14)) -# define FFMPEG_HAVE_CANON_H264_RESOLUTION_FIX -#endif - -#if ((LIBAVCODEC_VERSION_MAJOR > 53) || \ - (LIBAVCODEC_VERSION_MAJOR >= 53) && (LIBAVCODEC_VERSION_MINOR >= 60)) -# define FFMPEG_HAVE_ENCODE_AUDIO2 -#endif - -#if ((LIBAVCODEC_VERSION_MAJOR > 53) || \ - (LIBAVCODEC_VERSION_MAJOR >= 53) && (LIBAVCODEC_VERSION_MINOR >= 42)) -# define FFMPEG_HAVE_DECODE_AUDIO4 -#endif - -#if ((LIBAVCODEC_VERSION_MAJOR > 54) || \ - (LIBAVCODEC_VERSION_MAJOR >= 54) && (LIBAVCODEC_VERSION_MINOR >= 13)) -# define FFMPEG_HAVE_AVFRAME_SAMPLE_RATE -#endif - -#if ((LIBAVUTIL_VERSION_MAJOR > 51) || \ - (LIBAVUTIL_VERSION_MAJOR == 51) && (LIBAVUTIL_VERSION_MINOR >= 21)) -# define FFMPEG_FFV1_ALPHA_SUPPORTED -# define FFMPEG_SAMPLE_FMT_S16P_SUPPORTED -#else - -FFMPEG_INLINE -int av_sample_fmt_is_planar(enum AVSampleFormat sample_fmt) -{ - /* no planar formats in FFmpeg < 0.9 */ - (void)sample_fmt; - return 0; -} - -#endif - -/* XXX TODO Probably fix to correct modern flags in code? Not sure how old FFMPEG we want to - * support though, so for now this will do. */ - -#ifndef FF_MIN_BUFFER_SIZE -# ifdef AV_INPUT_BUFFER_MIN_SIZE -# define FF_MIN_BUFFER_SIZE AV_INPUT_BUFFER_MIN_SIZE -# endif -#endif - -#ifndef FF_INPUT_BUFFER_PADDING_SIZE -# ifdef AV_INPUT_BUFFER_PADDING_SIZE -# define FF_INPUT_BUFFER_PADDING_SIZE AV_INPUT_BUFFER_PADDING_SIZE -# endif -#endif - -#ifndef CODEC_FLAG_GLOBAL_HEADER -# ifdef AV_CODEC_FLAG_GLOBAL_HEADER -# define CODEC_FLAG_GLOBAL_HEADER AV_CODEC_FLAG_GLOBAL_HEADER -# endif -#endif - -#ifndef CODEC_FLAG_GLOBAL_HEADER -# ifdef AV_CODEC_FLAG_GLOBAL_HEADER -# define CODEC_FLAG_GLOBAL_HEADER AV_CODEC_FLAG_GLOBAL_HEADER -# endif -#endif - -#ifndef CODEC_FLAG_INTERLACED_DCT -# ifdef AV_CODEC_FLAG_INTERLACED_DCT -# define CODEC_FLAG_INTERLACED_DCT AV_CODEC_FLAG_INTERLACED_DCT -# endif -#endif - -#ifndef CODEC_FLAG_INTERLACED_ME -# ifdef AV_CODEC_FLAG_INTERLACED_ME -# define CODEC_FLAG_INTERLACED_ME AV_CODEC_FLAG_INTERLACED_ME -# endif -#endif - -/* FFmpeg upstream 1.0 is the first who added AV_ prefix. */ -#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(54, 59, 100) -# define AV_CODEC_ID_NONE CODEC_ID_NONE -# define AV_CODEC_ID_MPEG4 CODEC_ID_MPEG4 -# define AV_CODEC_ID_MJPEG CODEC_ID_MJPEG -# define AV_CODEC_ID_DNXHD CODEC_ID_DNXHD -# define AV_CODEC_ID_MPEG2VIDEO CODEC_ID_MPEG2VIDEO -# define AV_CODEC_ID_MPEG1VIDEO CODEC_ID_MPEG1VIDEO -# define AV_CODEC_ID_DVVIDEO CODEC_ID_DVVIDEO -# define AV_CODEC_ID_THEORA CODEC_ID_THEORA -# define AV_CODEC_ID_PNG CODEC_ID_PNG -# define AV_CODEC_ID_QTRLE CODEC_ID_QTRLE -# define AV_CODEC_ID_FFV1 CODEC_ID_FFV1 -# define AV_CODEC_ID_HUFFYUV CODEC_ID_HUFFYUV -# define AV_CODEC_ID_H264 CODEC_ID_H264 -# define AV_CODEC_ID_FLV1 CODEC_ID_FLV1 - -# define AV_CODEC_ID_AAC CODEC_ID_AAC -# define AV_CODEC_ID_AC3 CODEC_ID_AC3 -# define AV_CODEC_ID_MP3 CODEC_ID_MP3 -# define AV_CODEC_ID_MP2 CODEC_ID_MP2 -# define AV_CODEC_ID_FLAC CODEC_ID_FLAC -# define AV_CODEC_ID_PCM_U8 CODEC_ID_PCM_U8 -# define AV_CODEC_ID_PCM_S16LE CODEC_ID_PCM_S16LE -# define AV_CODEC_ID_PCM_S24LE CODEC_ID_PCM_S24LE -# define AV_CODEC_ID_PCM_S32LE CODEC_ID_PCM_S32LE -# define AV_CODEC_ID_PCM_F32LE CODEC_ID_PCM_F32LE -# define AV_CODEC_ID_PCM_F64LE CODEC_ID_PCM_F64LE -# define AV_CODEC_ID_VORBIS CODEC_ID_VORBIS -#endif - -FFMPEG_INLINE -int av_get_cropped_height_from_codec(AVCodecContext *pCodecCtx) -{ - int y = pCodecCtx->height; - -#ifndef FFMPEG_HAVE_CANON_H264_RESOLUTION_FIX - /* really bad hack to remove this dreadfull black bar at the bottom - with Canon footage and old ffmpeg versions. - (to fix this properly in older ffmpeg versions one has to write a new - demuxer...) - - see the actual fix here for reference: - - http://git.libav.org/?p=libav.git;a=commit;h=30f515091c323da59c0f1b533703dedca2f4b95d - - We do our best to apply this only to matching footage. -*/ - if (pCodecCtx->width == 1920 && pCodecCtx->height == 1088 && - pCodecCtx->pix_fmt == PIX_FMT_YUVJ420P && pCodecCtx->codec_id == AV_CODEC_ID_H264) { - y = 1080; - } -#endif - - return y; -} - -#if ((LIBAVUTIL_VERSION_MAJOR < 51) || \ - (LIBAVUTIL_VERSION_MAJOR == 51) && (LIBAVUTIL_VERSION_MINOR < 22)) -FFMPEG_INLINE -int av_opt_set(void *obj, const char *name, const char *val, int search_flags) -{ - const AVOption *rv = NULL; - (void)search_flags; - av_set_string3(obj, name, val, 1, &rv); - return rv != NULL; -} - -FFMPEG_INLINE -int av_opt_set_int(void *obj, const char *name, int64_t val, int search_flags) -{ - const AVOption *rv = NULL; - (void)search_flags; - rv = av_set_int(obj, name, val); - return rv != NULL; -} - -FFMPEG_INLINE -int av_opt_set_double(void *obj, const char *name, double val, int search_flags) -{ - const AVOption *rv = NULL; - (void)search_flags; - rv = av_set_double(obj, name, val); - return rv != NULL; -} - -# define AV_OPT_TYPE_INT FF_OPT_TYPE_INT -# define AV_OPT_TYPE_INT64 FF_OPT_TYPE_INT64 -# define AV_OPT_TYPE_STRING FF_OPT_TYPE_STRING -# define AV_OPT_TYPE_CONST FF_OPT_TYPE_CONST -# define AV_OPT_TYPE_DOUBLE FF_OPT_TYPE_DOUBLE -# define AV_OPT_TYPE_FLOAT FF_OPT_TYPE_FLOAT -#endif - -#if ((LIBAVUTIL_VERSION_MAJOR < 51) || \ - (LIBAVUTIL_VERSION_MAJOR == 51) && (LIBAVUTIL_VERSION_MINOR < 54)) -FFMPEG_INLINE -enum AVSampleFormat av_get_packed_sample_fmt(enum AVSampleFormat sample_fmt) -{ - if (sample_fmt < 0 || sample_fmt >= AV_SAMPLE_FMT_NB) - return AV_SAMPLE_FMT_NONE; - return sample_fmt; -} -#endif - -#if ((LIBAVCODEC_VERSION_MAJOR < 53) || \ - (LIBAVCODEC_VERSION_MAJOR == 53 && LIBAVCODEC_VERSION_MINOR < 35)) -FFMPEG_INLINE -int avcodec_open2(AVCodecContext *avctx, AVCodec *codec, AVDictionary **options) -{ - /* TODO: no options are taking into account */ - (void)options; - return avcodec_open(avctx, codec); -} -#endif - -#if ((LIBAVFORMAT_VERSION_MAJOR < 53) || \ - (LIBAVFORMAT_VERSION_MAJOR == 53 && LIBAVFORMAT_VERSION_MINOR < 21)) -FFMPEG_INLINE -AVStream *avformat_new_stream(AVFormatContext *s, AVCodec *c) -{ - /* TODO: no codec is taking into account */ - (void)c; - return av_new_stream(s, 0); -} - -FFMPEG_INLINE -int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options) -{ - /* TODO: no options are taking into account */ - (void)options; - return av_find_stream_info(ic); -} -#endif - -#if ((LIBAVFORMAT_VERSION_MAJOR > 53) || \ - ((LIBAVFORMAT_VERSION_MAJOR == 53) && (LIBAVFORMAT_VERSION_MINOR > 32)) || \ - ((LIBAVFORMAT_VERSION_MAJOR == 53) && (LIBAVFORMAT_VERSION_MINOR == 24) && \ - (LIBAVFORMAT_VERSION_MICRO >= 100))) FFMPEG_INLINE void my_update_cur_dts(AVFormatContext *s, AVStream *ref_st, int64_t timestamp) { @@ -323,103 +62,12 @@ void av_update_cur_dts(AVFormatContext *s, AVStream *ref_st, int64_t timestamp) { my_update_cur_dts(s, ref_st, timestamp); } -#endif - -#if ((LIBAVCODEC_VERSION_MAJOR < 54) || \ - (LIBAVCODEC_VERSION_MAJOR == 54 && LIBAVCODEC_VERSION_MINOR < 28)) -FFMPEG_INLINE -void avcodec_free_frame(AVFrame **frame) -{ - /* don't need to do anything with old AVFrame - * since it does not have malloced members */ - (void)frame; -} -#endif - -#if ((LIBAVCODEC_VERSION_MAJOR > 54) || \ - (LIBAVCODEC_VERSION_MAJOR >= 54) && (LIBAVCODEC_VERSION_MINOR >= 13)) -# define FFMPEG_HAVE_AVFRAME_SAMPLE_RATE -#endif - -#if ((LIBAVCODEC_VERSION_MAJOR > 54) || \ - (LIBAVCODEC_VERSION_MAJOR == 54 && LIBAVCODEC_VERSION_MINOR >= 13)) -# define FFMPEG_HAVE_FRAME_CHANNEL_LAYOUT -#endif - -#ifndef FFMPEG_HAVE_AVIO -# define AVIO_FLAG_WRITE URL_WRONLY -# define avio_open url_fopen -# define avio_tell url_ftell -# define avio_close url_fclose -# define avio_size url_fsize -#endif - -/* There are some version in between, which have avio_... functions but no - * AVIO_FLAG_... */ -#ifndef AVIO_FLAG_WRITE -# define AVIO_FLAG_WRITE URL_WRONLY -#endif - -#ifndef AV_PKT_FLAG_KEY -# define AV_PKT_FLAG_KEY PKT_FLAG_KEY -#endif - -#ifndef FFMPEG_HAVE_AV_DUMP_FORMAT -# define av_dump_format dump_format -#endif - -#ifndef FFMPEG_HAVE_AV_GUESS_FORMAT -# define av_guess_format guess_format -#endif - -#ifndef FFMPEG_HAVE_PARSE_UTILS -# define av_parse_video_rate av_parse_video_frame_rate -#endif - -#ifdef FFMPEG_HAVE_DEFAULT_VAL_UNION -# define FFMPEG_DEF_OPT_VAL_INT(OPT) OPT->default_val.i64 -# define FFMPEG_DEF_OPT_VAL_DOUBLE(OPT) OPT->default_val.dbl -#else -# define FFMPEG_DEF_OPT_VAL_INT(OPT) OPT->default_val -# define FFMPEG_DEF_OPT_VAL_DOUBLE(OPT) OPT->default_val -#endif - -#ifndef FFMPEG_HAVE_AVMEDIA_TYPES -# define AVMEDIA_TYPE_VIDEO CODEC_TYPE_VIDEO -# define AVMEDIA_TYPE_AUDIO CODEC_TYPE_AUDIO -#endif - -#ifndef FFMPEG_HAVE_DECODE_AUDIO3 -FFMPEG_INLINE -int avcodec_decode_audio3(AVCodecContext *avctx, - int16_t *samples, - int *frame_size_ptr, - AVPacket *avpkt) -{ - return avcodec_decode_audio2(avctx, samples, frame_size_ptr, avpkt->data, avpkt->size); -} -#endif - -#ifndef FFMPEG_HAVE_DECODE_VIDEO2 -FFMPEG_INLINE -int avcodec_decode_video2(AVCodecContext *avctx, - AVFrame *picture, - int *got_picture_ptr, - AVPacket *avpkt) -{ - return avcodec_decode_video(avctx, picture, got_picture_ptr, avpkt->data, avpkt->size); -} -#endif FFMPEG_INLINE int64_t av_get_pts_from_frame(AVFormatContext *avctx, AVFrame *picture) { int64_t pts; -#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(55, 34, 100) pts = picture->pts; -#else - pts = picture->pkt_pts; -#endif if (pts == AV_NOPTS_VALUE) { pts = picture->pkt_dts; @@ -432,124 +80,16 @@ int64_t av_get_pts_from_frame(AVFormatContext *avctx, AVFrame *picture) return pts; } -/* obsolete constant formerly defined in FFMpeg libavcodec/avcodec.h */ -#ifndef AVCODEC_MAX_AUDIO_FRAME_SIZE -# define AVCODEC_MAX_AUDIO_FRAME_SIZE 192000 // 1 second of 48khz 32bit audio -#endif - -#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(54, 1, 0) -FFMPEG_INLINE -int avcodec_encode_video2(AVCodecContext *avctx, - AVPacket *pkt, - const AVFrame *frame, - int *got_output) -{ - int outsize, ret; - - ret = av_new_packet(pkt, avctx->width * avctx->height * 7 + 10000); - if (ret < 0) - return ret; - - outsize = avcodec_encode_video(avctx, pkt->data, pkt->size, frame); - if (outsize <= 0) { - *got_output = 0; - av_free_packet(pkt); - } - else { - *got_output = 1; - av_shrink_packet(pkt, outsize); - if (avctx->coded_frame) { - pkt->pts = avctx->coded_frame->pts; - if (avctx->coded_frame->key_frame) - pkt->flags |= AV_PKT_FLAG_KEY; - } - } - - return outsize >= 0 ? 0 : outsize; -} - -#endif - -#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(53, 17, 0) -FFMPEG_INLINE -void avformat_close_input(AVFormatContext **ctx) -{ - av_close_input_file(*ctx); - *ctx = NULL; -} -#endif - -#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(52, 8, 0) -FFMPEG_INLINE -AVFrame *av_frame_alloc(void) -{ - return avcodec_alloc_frame(); -} - -FFMPEG_INLINE -void av_frame_free(AVFrame **frame) -{ - av_freep(frame); -} -#endif - -FFMPEG_INLINE -const char *av_get_metadata_key_value(AVDictionary *metadata, const char *key) -{ - if (metadata == NULL) { - return NULL; - } - AVDictionaryEntry *tag = NULL; - while ((tag = av_dict_get(metadata, "", tag, AV_DICT_IGNORE_SUFFIX))) { - if (!strcmp(tag->key, key)) { - return tag->value; - } - } - return NULL; -} - -FFMPEG_INLINE -bool av_check_encoded_with_ffmpeg(AVFormatContext *ctx) -{ - const char *encoder = av_get_metadata_key_value(ctx->metadata, "ENCODER"); - if (encoder != NULL && !strncmp(encoder, "Lavf", 4)) { - return true; - } - return false; -} - -#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(51, 32, 0) -# define AV_OPT_SEARCH_FAKE_OBJ 0 -#endif - -#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(54, 59, 100) -# define FFMPEG_HAVE_DEPRECATED_FLAGS2 -#endif - -/* Since FFmpeg-1.1 this constant have AV_ prefix. */ -#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(52, 3, 100) -# define AV_PIX_FMT_BGR32 PIX_FMT_BGR32 -# define AV_PIX_FMT_YUV422P PIX_FMT_YUV422P -# define AV_PIX_FMT_BGRA PIX_FMT_BGRA -# define AV_PIX_FMT_ARGB PIX_FMT_ARGB -# define AV_PIX_FMT_RGBA PIX_FMT_RGBA -#endif - -/* New API from FFmpeg-2.0 which soon became recommended one. */ -#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(52, 38, 100) -# define av_frame_alloc avcodec_alloc_frame -# define av_frame_free avcodec_free_frame -# define av_frame_unref avcodec_get_frame_defaults -#endif - -#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 24, 102) - -/* NOTE: The code in this block are from FFmpeg 2.6.4, which is licensed by LGPL. */ +/* -------------------------------------------------------------------- */ +/** \name Deinterlace code block + * + * NOTE: The code in this block are from FFmpeg 2.6.4, which is licensed by LGPL. + * \{ */ -# define MAX_NEG_CROP 1024 +#define MAX_NEG_CROP 1024 -# define times4(x) x, x, x, x -# define times256(x) times4(times4(times4(times4(times4(x))))) +#define times4(x) x, x, x, x +#define times256(x) times4(times4(times4(times4(times4(x))))) static const uint8_t ff_compat_crop_tab[256 + 2 * MAX_NEG_CROP] = { times256(0x00), 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, @@ -575,8 +115,8 @@ static const uint8_t ff_compat_crop_tab[256 + 2 * MAX_NEG_CROP] = { 0xEF, 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, times256(0xFF)}; -# undef times4 -# undef times256 +#undef times4 +#undef times256 /* filter parameters: [-1 4 2 4 -1] // 8 */ FFMPEG_INLINE @@ -668,8 +208,9 @@ int deinterlace_bottom_field_inplace(uint8_t *src1, int src_wrap, int width, int uint8_t *src_m1, *src_0, *src_p1, *src_p2; int y; uint8_t *buf = (uint8_t *)av_malloc(width); - if (!buf) + if (!buf) { return AVERROR(ENOMEM); + } src_m1 = src1; memcpy(buf, src_m1, width); @@ -689,24 +230,21 @@ int deinterlace_bottom_field_inplace(uint8_t *src1, int src_wrap, int width, int return 0; } -# ifdef __GNUC__ -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wdeprecated-declarations" -# endif - FFMPEG_INLINE -int avpicture_deinterlace( - AVPicture *dst, const AVPicture *src, enum AVPixelFormat pix_fmt, int width, int height) +int av_image_deinterlace( + AVFrame *dst, const AVFrame *src, enum AVPixelFormat pix_fmt, int width, int height) { int i, ret; if (pix_fmt != AV_PIX_FMT_YUV420P && pix_fmt != AV_PIX_FMT_YUVJ420P && pix_fmt != AV_PIX_FMT_YUV422P && pix_fmt != AV_PIX_FMT_YUVJ422P && pix_fmt != AV_PIX_FMT_YUV444P && pix_fmt != AV_PIX_FMT_YUV411P && - pix_fmt != AV_PIX_FMT_GRAY8) + pix_fmt != AV_PIX_FMT_GRAY8) { return -1; - if ((width & 3) != 0 || (height & 3) != 0) + } + if ((width & 3) != 0 || (height & 3) != 0) { return -1; + } for (i = 0; i < 3; i++) { if (i == 1) { @@ -732,8 +270,9 @@ int avpicture_deinterlace( } if (src == dst) { ret = deinterlace_bottom_field_inplace(dst->data[i], dst->linesize[i], width, height); - if (ret < 0) + if (ret < 0) { return ret; + } } else { deinterlace_bottom_field( @@ -743,10 +282,6 @@ int avpicture_deinterlace( return 0; } -# ifdef __GNUC__ -# pragma GCC diagnostic pop -# endif - -#endif +/** \} Deinterlace code block */ #endif diff --git a/intern/ghost/CMakeLists.txt b/intern/ghost/CMakeLists.txt index 1739659ab88..f90e8a973bf 100644 --- a/intern/ghost/CMakeLists.txt +++ b/intern/ghost/CMakeLists.txt @@ -79,6 +79,7 @@ set(SRC intern/GHOST_SystemPaths.h intern/GHOST_TimerManager.h intern/GHOST_TimerTask.h + intern/GHOST_Util.h intern/GHOST_Window.h intern/GHOST_WindowManager.h ) @@ -438,6 +439,7 @@ endif() if(WITH_XR_OPENXR) list(APPEND SRC intern/GHOST_Xr.cpp + intern/GHOST_XrAction.cpp intern/GHOST_XrContext.cpp intern/GHOST_XrEvent.cpp intern/GHOST_XrGraphicsBinding.cpp @@ -446,6 +448,7 @@ if(WITH_XR_OPENXR) GHOST_IXrContext.h intern/GHOST_IXrGraphicsBinding.h + intern/GHOST_XrAction.h intern/GHOST_XrContext.h intern/GHOST_XrException.h intern/GHOST_XrSession.h diff --git a/intern/ghost/GHOST_C-api.h b/intern/ghost/GHOST_C-api.h index d79111b742f..2bd9af6df5c 100644 --- a/intern/ghost/GHOST_C-api.h +++ b/intern/ghost/GHOST_C-api.h @@ -276,7 +276,7 @@ extern int GHOST_GetFullScreen(GHOST_SystemHandle systemhandle); * wait (block) until the next event before returning. * \return Indication of the presence of events. */ -extern int GHOST_ProcessEvents(GHOST_SystemHandle systemhandle, int waitForEvent); +extern bool GHOST_ProcessEvents(GHOST_SystemHandle systemhandle, bool waitForEvent); /** * Retrieves events from the queue and send them to the event consumers. @@ -1059,7 +1059,110 @@ int GHOST_XrSessionNeedsUpsideDownDrawing(const GHOST_XrContextHandle xr_context * \returns GHOST_kSuccess if any event was handled, otherwise GHOST_kFailure. */ GHOST_TSuccess GHOST_XrEventsHandle(GHOST_XrContextHandle xr_context); -#endif + +/* actions */ +/** + * Create an OpenXR action set for input/output. + */ +int GHOST_XrCreateActionSet(GHOST_XrContextHandle xr_context, const GHOST_XrActionSetInfo *info); + +/** + * Destroy a previously created OpenXR action set. + */ +void GHOST_XrDestroyActionSet(GHOST_XrContextHandle xr_context, const char *action_set_name); + +/** + * Create OpenXR input/output actions. + */ +int GHOST_XrCreateActions(GHOST_XrContextHandle xr_context, + const char *action_set_name, + GHOST_TUns32 count, + const GHOST_XrActionInfo *infos); + +/** + * Destroy previously created OpenXR actions. + */ +void GHOST_XrDestroyActions(GHOST_XrContextHandle xr_context, + const char *action_set_name, + GHOST_TUns32 count, + const char *const *action_names); + +/** + * Create spaces for pose-based OpenXR actions. + */ +int GHOST_XrCreateActionSpaces(GHOST_XrContextHandle xr_context, + const char *action_set_name, + GHOST_TUns32 count, + const GHOST_XrActionSpaceInfo *infos); + +/** + * Destroy previously created spaces for OpenXR actions. + */ +void GHOST_XrDestroyActionSpaces(GHOST_XrContextHandle xr_context, + const char *action_set_name, + GHOST_TUns32 count, + const GHOST_XrActionSpaceInfo *infos); + +/** + * Create input/output path bindings for OpenXR actions. + */ +int GHOST_XrCreateActionBindings(GHOST_XrContextHandle xr_context, + const char *action_set_name, + GHOST_TUns32 count, + const GHOST_XrActionProfileInfo *infos); + +/** + * Destroy previously created bindings for OpenXR actions. + */ +void GHOST_XrDestroyActionBindings(GHOST_XrContextHandle xr_context, + const char *action_set_name, + GHOST_TUns32 count, + const GHOST_XrActionProfileInfo *infos); + +/** + * Attach all created action sets to the current OpenXR session. + */ +int GHOST_XrAttachActionSets(GHOST_XrContextHandle xr_context); + +/** + * Update button/tracking states for OpenXR actions. + * + * \param action_set_name: The name of the action set to sync. If NULL, all action sets + * attached to the session will be synced. + */ +int GHOST_XrSyncActions(GHOST_XrContextHandle xr_context, const char *action_set_name); + +/** + * Apply an OpenXR haptic output action. + */ +int GHOST_XrApplyHapticAction(GHOST_XrContextHandle xr_context, + const char *action_set_name, + const char *action_name, + const GHOST_TInt64 *duration, + const float *frequency, + const float *amplitude); + +/** + * Stop a previously applied OpenXR haptic output action. + */ +void GHOST_XrStopHapticAction(GHOST_XrContextHandle xr_context, + const char *action_set_name, + const char *action_name); + +/** + * Get action set custom data (owned by Blender, not GHOST). + */ +void *GHOST_XrGetActionSetCustomdata(GHOST_XrContextHandle xr_context, + const char *action_set_name); + +/** + * Get action custom data (owned by Blender, not GHOST). + */ +void *GHOST_XrGetActionCustomdata(GHOST_XrContextHandle xr_context, + const char *action_set_name, + const char *action_name); + +#endif /* WITH_XR_OPENXR */ #ifdef __cplusplus } diff --git a/intern/ghost/GHOST_IXrContext.h b/intern/ghost/GHOST_IXrContext.h index dd266a3b6ae..86fe78814a7 100644 --- a/intern/ghost/GHOST_IXrContext.h +++ b/intern/ghost/GHOST_IXrContext.h @@ -22,6 +22,8 @@ #include "GHOST_Types.h" +class GHOST_XrSession; + class GHOST_IXrContext { public: virtual ~GHOST_IXrContext() = default; @@ -31,6 +33,10 @@ class GHOST_IXrContext { virtual bool isSessionRunning() const = 0; virtual void drawSessionViews(void *draw_customdata) = 0; + /* Needed for the GHOST C api. */ + virtual GHOST_XrSession *getSession() = 0; + virtual const GHOST_XrSession *getSession() const = 0; + virtual void dispatchErrorMessage(const class GHOST_XrException *) const = 0; virtual void setGraphicsContextBindFuncs(GHOST_XrGraphicsContextBindFn bind_fn, diff --git a/intern/ghost/GHOST_Types.h b/intern/ghost/GHOST_Types.h index bd7ba6657f0..3a8d0fbfecf 100644 --- a/intern/ghost/GHOST_Types.h +++ b/intern/ghost/GHOST_Types.h @@ -416,7 +416,10 @@ typedef enum { GHOST_kGrabNormal, /** Wrap the mouse location to prevent limiting screen bounds. */ GHOST_kGrabWrap, - /** Hide the mouse while grabbing and restore the original location on release (numbuts). */ + /** + * Hide the mouse while grabbing and restore the original location on release + * (used for number buttons and some other draggable UI elements). + */ GHOST_kGrabHide, } GHOST_TGrabCursorMode; @@ -631,7 +634,9 @@ typedef enum GHOST_TXrGraphicsBinding { typedef void (*GHOST_XrErrorHandlerFn)(const struct GHOST_XrError *); +typedef void (*GHOST_XrSessionCreateFn)(void); typedef void (*GHOST_XrSessionExitFn)(void *customdata); +typedef void (*GHOST_XrCustomdataFreeFn)(void *customdata); typedef void *(*GHOST_XrGraphicsContextBindFn)(void); typedef void (*GHOST_XrGraphicsContextUnbindFn)(GHOST_ContextHandle graphics_context); @@ -662,6 +667,7 @@ typedef struct { typedef struct { GHOST_XrPose base_pose; + GHOST_XrSessionCreateFn create_fn; GHOST_XrSessionExitFn exit_fn; void *exit_customdata; } GHOST_XrSessionBeginInfo; @@ -688,4 +694,54 @@ typedef struct GHOST_XrError { void *customdata; } GHOST_XrError; -#endif +typedef struct GHOST_XrActionSetInfo { + const char *name; + + GHOST_XrCustomdataFreeFn customdata_free_fn; + void *customdata; /* wmXrActionSet */ +} GHOST_XrActionSetInfo; + +/** XR action type. Enum values match those in OpenXR's + * XrActionType enum for consistency. */ +typedef enum GHOST_XrActionType { + GHOST_kXrActionTypeBooleanInput = 1, + GHOST_kXrActionTypeFloatInput = 2, + GHOST_kXrActionTypeVector2fInput = 3, + GHOST_kXrActionTypePoseInput = 4, + GHOST_kXrActionTypeVibrationOutput = 100, +} GHOST_XrActionType; + +typedef struct GHOST_XrActionInfo { + const char *name; + GHOST_XrActionType type; + GHOST_TUns32 count_subaction_paths; + const char **subaction_paths; + /** States for each subaction path. */ + void *states; + + GHOST_XrCustomdataFreeFn customdata_free_fn; + void *customdata; /* wmXrAction */ +} GHOST_XrActionInfo; + +typedef struct GHOST_XrActionSpaceInfo { + const char *action_name; + GHOST_TUns32 count_subaction_paths; + const char **subaction_paths; + /** Poses for each subaction path. */ + const GHOST_XrPose *poses; +} GHOST_XrActionSpaceInfo; + +typedef struct GHOST_XrActionBindingInfo { + const char *action_name; + GHOST_TUns32 count_interaction_paths; + /** Interaction path: User (subaction) path + component path. */ + const char **interaction_paths; +} GHOST_XrActionBindingInfo; + +typedef struct GHOST_XrActionProfileInfo { + const char *profile_path; + GHOST_TUns32 count_bindings; + const GHOST_XrActionBindingInfo *bindings; +} GHOST_XrActionProfileInfo; + +#endif /* WITH_XR_OPENXR */ diff --git a/intern/ghost/intern/GHOST_C-api.cpp b/intern/ghost/intern/GHOST_C-api.cpp index af65e1cd4d2..955f35274ea 100644 --- a/intern/ghost/intern/GHOST_C-api.cpp +++ b/intern/ghost/intern/GHOST_C-api.cpp @@ -33,6 +33,7 @@ #include "intern/GHOST_Debug.h" #ifdef WITH_XR_OPENXR # include "GHOST_IXrContext.h" +# include "intern/GHOST_XrSession.h" #endif #include "intern/GHOST_CallbackEventConsumer.h" #include "intern/GHOST_XrException.h" @@ -248,11 +249,11 @@ int GHOST_GetFullScreen(GHOST_SystemHandle systemhandle) return (int)system->getFullScreen(); } -int GHOST_ProcessEvents(GHOST_SystemHandle systemhandle, int waitForEvent) +bool GHOST_ProcessEvents(GHOST_SystemHandle systemhandle, bool waitForEvent) { GHOST_ISystem *system = (GHOST_ISystem *)systemhandle; - return (int)system->processEvents(waitForEvent ? true : false); + return system->processEvents(waitForEvent); } void GHOST_DispatchEvents(GHOST_SystemHandle systemhandle) @@ -953,4 +954,145 @@ int GHOST_XrSessionNeedsUpsideDownDrawing(const GHOST_XrContextHandle xr_context return 0; /* Only reached if exception is thrown. */ } -#endif +int GHOST_XrCreateActionSet(GHOST_XrContextHandle xr_contexthandle, + const GHOST_XrActionSetInfo *info) +{ + GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle; + GHOST_XrSession *xr_session = xr_context->getSession(); + GHOST_XR_CAPI_CALL_RET(xr_session->createActionSet(*info), xr_context); + return 0; +} + +void GHOST_XrDestroyActionSet(GHOST_XrContextHandle xr_contexthandle, const char *action_set_name) +{ + GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle; + GHOST_XrSession *xr_session = xr_context->getSession(); + GHOST_XR_CAPI_CALL(xr_session->destroyActionSet(action_set_name), xr_context); +} + +int GHOST_XrCreateActions(GHOST_XrContextHandle xr_contexthandle, + const char *action_set_name, + GHOST_TUns32 count, + const GHOST_XrActionInfo *infos) +{ + GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle; + GHOST_XrSession *xr_session = xr_context->getSession(); + GHOST_XR_CAPI_CALL_RET(xr_session->createActions(action_set_name, count, infos), xr_context); + return 0; +} + +void GHOST_XrDestroyActions(GHOST_XrContextHandle xr_contexthandle, + const char *action_set_name, + GHOST_TUns32 count, + const char *const *action_names) +{ + GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle; + GHOST_XrSession *xr_session = xr_context->getSession(); + GHOST_XR_CAPI_CALL(xr_session->destroyActions(action_set_name, count, action_names), xr_context); +} + +int GHOST_XrCreateActionSpaces(GHOST_XrContextHandle xr_contexthandle, + const char *action_set_name, + GHOST_TUns32 count, + const GHOST_XrActionSpaceInfo *infos) +{ + GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle; + GHOST_XrSession *xr_session = xr_context->getSession(); + GHOST_XR_CAPI_CALL_RET(xr_session->createActionSpaces(action_set_name, count, infos), + xr_context); + return 0; +} + +void GHOST_XrDestroyActionSpaces(GHOST_XrContextHandle xr_contexthandle, + const char *action_set_name, + GHOST_TUns32 count, + const GHOST_XrActionSpaceInfo *infos) +{ + GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle; + GHOST_XrSession *xr_session = xr_context->getSession(); + GHOST_XR_CAPI_CALL(xr_session->destroyActionSpaces(action_set_name, count, infos), xr_context); +} + +int GHOST_XrCreateActionBindings(GHOST_XrContextHandle xr_contexthandle, + const char *action_set_name, + GHOST_TUns32 count, + const GHOST_XrActionProfileInfo *infos) +{ + GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle; + GHOST_XrSession *xr_session = xr_context->getSession(); + GHOST_XR_CAPI_CALL_RET(xr_session->createActionBindings(action_set_name, count, infos), + xr_context); + return 0; +} + +void GHOST_XrDestroyActionBindings(GHOST_XrContextHandle xr_contexthandle, + const char *action_set_name, + GHOST_TUns32 count, + const GHOST_XrActionProfileInfo *infos) +{ + GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle; + GHOST_XrSession *xr_session = xr_context->getSession(); + GHOST_XR_CAPI_CALL(xr_session->destroyActionBindings(action_set_name, count, infos), xr_context); +} + +int GHOST_XrAttachActionSets(GHOST_XrContextHandle xr_contexthandle) +{ + GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle; + GHOST_XrSession *xr_session = xr_context->getSession(); + GHOST_XR_CAPI_CALL_RET(xr_session->attachActionSets(), xr_context); + return 0; +} + +int GHOST_XrSyncActions(GHOST_XrContextHandle xr_contexthandle, const char *action_set_name) +{ + GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle; + GHOST_XrSession *xr_session = xr_context->getSession(); + GHOST_XR_CAPI_CALL_RET(xr_session->syncActions(action_set_name), xr_context); + return 0; +} + +int GHOST_XrApplyHapticAction(GHOST_XrContextHandle xr_contexthandle, + const char *action_set_name, + const char *action_name, + const GHOST_TInt64 *duration, + const float *frequency, + const float *amplitude) +{ + GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle; + GHOST_XrSession *xr_session = xr_context->getSession(); + GHOST_XR_CAPI_CALL_RET(xr_session->applyHapticAction( + action_set_name, action_name, *duration, *frequency, *amplitude), + xr_context); + return 0; +} + +void GHOST_XrStopHapticAction(GHOST_XrContextHandle xr_contexthandle, + const char *action_set_name, + const char *action_name) +{ + GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle; + GHOST_XrSession *xr_session = xr_context->getSession(); + GHOST_XR_CAPI_CALL(xr_session->stopHapticAction(action_set_name, action_name), xr_context); +} + +void *GHOST_XrGetActionSetCustomdata(GHOST_XrContextHandle xr_contexthandle, + const char *action_set_name) +{ + GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle; + GHOST_XrSession *xr_session = xr_context->getSession(); + GHOST_XR_CAPI_CALL_RET(xr_session->getActionSetCustomdata(action_set_name), xr_context); + return 0; +} + +void *GHOST_XrGetActionCustomdata(GHOST_XrContextHandle xr_contexthandle, + const char *action_set_name, + const char *action_name) +{ + GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle; + GHOST_XrSession *xr_session = xr_context->getSession(); + GHOST_XR_CAPI_CALL_RET(xr_session->getActionCustomdata(action_set_name, action_name), + xr_context); + return 0; +} + +#endif /* WITH_XR_OPENXR */ diff --git a/intern/ghost/intern/GHOST_DropTargetX11.cpp b/intern/ghost/intern/GHOST_DropTargetX11.cpp index 82ed9de230d..a62db31c952 100644 --- a/intern/ghost/intern/GHOST_DropTargetX11.cpp +++ b/intern/ghost/intern/GHOST_DropTargetX11.cpp @@ -112,8 +112,7 @@ GHOST_DropTargetX11::~GHOST_DropTargetX11() } } -/* based on a code from Saul Rennison - * http://stackoverflow.com/questions/2673207/c-c-url-decode-library */ +/* Based on: https://stackoverflow.com/a/2766963/432509 */ typedef enum DecodeState_e { STATE_SEARCH = 0, ///< searching for an ampersand to convert diff --git a/intern/ghost/intern/GHOST_SystemCocoa.mm b/intern/ghost/intern/GHOST_SystemCocoa.mm index 050c90097c5..6ddb7f031f5 100644 --- a/intern/ghost/intern/GHOST_SystemCocoa.mm +++ b/intern/ghost/intern/GHOST_SystemCocoa.mm @@ -1711,7 +1711,7 @@ GHOST_TSuccess GHOST_SystemCocoa::handleMouseEvent(void *eventPtr) dx = [event scrollingDeltaX]; dy = [event scrollingDeltaY]; - /* However, wacom tablet (intuos5) needs old deltas, + /* However, Wacom tablet (intuos5) needs old deltas, * it then has momentum and phase at zero. */ if (phase == NSEventPhaseNone && momentumPhase == NSEventPhaseNone) { dx = [event deltaX]; diff --git a/intern/ghost/intern/GHOST_Util.h b/intern/ghost/intern/GHOST_Util.h new file mode 100644 index 00000000000..8be5e373b28 --- /dev/null +++ b/intern/ghost/intern/GHOST_Util.h @@ -0,0 +1,45 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup GHOST + */ + +#pragma once + +#include <functional> + +/** + * RAII wrapper for typical C `void *` custom data. + * Used for exception safe custom-data handling during constructor calls. + */ +struct GHOST_C_CustomDataWrapper { + using FreeFn = std::function<void(void *)>; + + void *custom_data_; + FreeFn free_fn_; + + GHOST_C_CustomDataWrapper(void *custom_data, FreeFn free_fn) + : custom_data_(custom_data), free_fn_(free_fn) + { + } + ~GHOST_C_CustomDataWrapper() + { + if (free_fn_ != nullptr && custom_data_ != nullptr) { + free_fn_(custom_data_); + } + } +}; diff --git a/intern/ghost/intern/GHOST_XrAction.cpp b/intern/ghost/intern/GHOST_XrAction.cpp new file mode 100644 index 00000000000..172ac40c84f --- /dev/null +++ b/intern/ghost/intern/GHOST_XrAction.cpp @@ -0,0 +1,477 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup GHOST + */ + +#include <cassert> +#include <cstring> + +#include "GHOST_Types.h" + +#include "GHOST_XrException.h" +#include "GHOST_Xr_intern.h" + +#include "GHOST_XrAction.h" + +/* -------------------------------------------------------------------- */ +/** \name GHOST_XrActionSpace + * + * \{ */ + +GHOST_XrActionSpace::GHOST_XrActionSpace(XrInstance instance, + XrSession session, + XrAction action, + const GHOST_XrActionSpaceInfo &info, + uint32_t subaction_idx) +{ + const char *subaction_path = info.subaction_paths[subaction_idx]; + CHECK_XR(xrStringToPath(instance, subaction_path, &m_subaction_path), + (std::string("Failed to get user path \"") + subaction_path + "\".").data()); + + XrActionSpaceCreateInfo action_space_info{XR_TYPE_ACTION_SPACE_CREATE_INFO}; + action_space_info.action = action; + action_space_info.subactionPath = m_subaction_path; + copy_ghost_pose_to_openxr_pose(info.poses[subaction_idx], action_space_info.poseInActionSpace); + + CHECK_XR(xrCreateActionSpace(session, &action_space_info, &m_space), + (std::string("Failed to create space \"") + subaction_path + "\" for action \"" + + info.action_name + "\".") + .data()); +} + +GHOST_XrActionSpace::~GHOST_XrActionSpace() +{ + if (m_space != XR_NULL_HANDLE) { + CHECK_XR_ASSERT(xrDestroySpace(m_space)); + } +} + +XrSpace GHOST_XrActionSpace::getSpace() const +{ + return m_space; +} + +const XrPath &GHOST_XrActionSpace::getSubactionPath() const +{ + return m_subaction_path; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name GHOST_XrActionProfile + * + * \{ */ + +GHOST_XrActionProfile::GHOST_XrActionProfile(XrInstance instance, + XrAction action, + const char *profile_path, + const GHOST_XrActionBindingInfo &info) +{ + CHECK_XR( + xrStringToPath(instance, profile_path, &m_profile), + (std::string("Failed to get interaction profile path \"") + profile_path + "\".").data()); + + /* Create bindings. */ + XrInteractionProfileSuggestedBinding bindings_info{ + XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING}; + bindings_info.interactionProfile = m_profile; + bindings_info.countSuggestedBindings = 1; + + for (uint32_t interaction_idx = 0; interaction_idx < info.count_interaction_paths; + ++interaction_idx) { + const char *interaction_path = info.interaction_paths[interaction_idx]; + if (m_bindings.find(interaction_path) != m_bindings.end()) { + continue; + } + + XrActionSuggestedBinding sbinding; + sbinding.action = action; + CHECK_XR(xrStringToPath(instance, interaction_path, &sbinding.binding), + (std::string("Failed to get interaction path \"") + interaction_path + "\".").data()); + bindings_info.suggestedBindings = &sbinding; + + /* Although the bindings will be re-suggested in GHOST_XrSession::attachActionSets(), it + * greatly improves error checking to suggest them here first. */ + CHECK_XR(xrSuggestInteractionProfileBindings(instance, &bindings_info), + (std::string("Failed to create binding for profile \"") + profile_path + + "\" and action \"" + info.action_name + + "\". Are the profile and action paths correct?") + .data()); + + m_bindings.insert({interaction_path, sbinding.binding}); + } +} + +void GHOST_XrActionProfile::getBindings( + XrAction action, std::map<XrPath, std::vector<XrActionSuggestedBinding>> &r_bindings) const +{ + std::map<XrPath, std::vector<XrActionSuggestedBinding>>::iterator it = r_bindings.find( + m_profile); + if (it == r_bindings.end()) { + it = r_bindings + .emplace(std::piecewise_construct, std::make_tuple(m_profile), std::make_tuple()) + .first; + } + + std::vector<XrActionSuggestedBinding> &sbindings = it->second; + + for (auto &[path, binding] : m_bindings) { + XrActionSuggestedBinding sbinding; + sbinding.action = action; + sbinding.binding = binding; + + sbindings.push_back(std::move(sbinding)); + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name GHOST_XrAction + * + * \{ */ + +GHOST_XrAction::GHOST_XrAction(XrInstance instance, + XrActionSet action_set, + const GHOST_XrActionInfo &info) + : m_type(info.type), + m_states(info.states), + m_custom_data_( + std::make_unique<GHOST_C_CustomDataWrapper>(info.customdata, info.customdata_free_fn)) +{ + m_subaction_paths.resize(info.count_subaction_paths); + + for (uint32_t i = 0; i < info.count_subaction_paths; ++i) { + CHECK_XR(xrStringToPath(instance, info.subaction_paths[i], &m_subaction_paths[i]), + (std::string("Failed to get user path \"") + info.subaction_paths[i] + "\".").data()); + } + + XrActionCreateInfo action_info{XR_TYPE_ACTION_CREATE_INFO}; + strcpy(action_info.actionName, info.name); + strcpy(action_info.localizedActionName, info.name); /* Just use same name for localized. This can + be changed in the future if necessary. */ + + switch (info.type) { + case GHOST_kXrActionTypeBooleanInput: + action_info.actionType = XR_ACTION_TYPE_BOOLEAN_INPUT; + break; + case GHOST_kXrActionTypeFloatInput: + action_info.actionType = XR_ACTION_TYPE_FLOAT_INPUT; + break; + case GHOST_kXrActionTypeVector2fInput: + action_info.actionType = XR_ACTION_TYPE_VECTOR2F_INPUT; + break; + case GHOST_kXrActionTypePoseInput: + action_info.actionType = XR_ACTION_TYPE_POSE_INPUT; + break; + case GHOST_kXrActionTypeVibrationOutput: + action_info.actionType = XR_ACTION_TYPE_VIBRATION_OUTPUT; + break; + } + action_info.countSubactionPaths = info.count_subaction_paths; + action_info.subactionPaths = m_subaction_paths.data(); + + CHECK_XR(xrCreateAction(action_set, &action_info, &m_action), + (std::string("Failed to create action \"") + info.name + + "\". Action name and/or paths are invalid. Name must not contain upper " + "case letters or special characters other than '-', '_', or '.'.") + .data()); +} + +GHOST_XrAction::~GHOST_XrAction() +{ + if (m_action != XR_NULL_HANDLE) { + CHECK_XR_ASSERT(xrDestroyAction(m_action)); + } +} + +bool GHOST_XrAction::createSpace(XrInstance instance, + XrSession session, + const GHOST_XrActionSpaceInfo &info) +{ + uint32_t subaction_idx = 0; + for (; subaction_idx < info.count_subaction_paths; ++subaction_idx) { + if (m_spaces.find(info.subaction_paths[subaction_idx]) != m_spaces.end()) { + return false; + } + } + + for (subaction_idx = 0; subaction_idx < info.count_subaction_paths; ++subaction_idx) { + m_spaces.emplace(std::piecewise_construct, + std::make_tuple(info.subaction_paths[subaction_idx]), + std::make_tuple(instance, session, m_action, info, subaction_idx)); + } + + return true; +} + +void GHOST_XrAction::destroySpace(const char *subaction_path) +{ + if (m_spaces.find(subaction_path) != m_spaces.end()) { + m_spaces.erase(subaction_path); + } +} + +bool GHOST_XrAction::createBinding(XrInstance instance, + const char *profile_path, + const GHOST_XrActionBindingInfo &info) +{ + if (m_profiles.find(profile_path) != m_profiles.end()) { + return false; + } + + m_profiles.emplace(std::piecewise_construct, + std::make_tuple(profile_path), + std::make_tuple(instance, m_action, profile_path, info)); + + return true; +} + +void GHOST_XrAction::destroyBinding(const char *interaction_profile_path) +{ + if (m_profiles.find(interaction_profile_path) != m_profiles.end()) { + m_profiles.erase(interaction_profile_path); + } +} + +void GHOST_XrAction::updateState(XrSession session, + const char *action_name, + XrSpace reference_space, + const XrTime &predicted_display_time) +{ + XrActionStateGetInfo state_info{XR_TYPE_ACTION_STATE_GET_INFO}; + state_info.action = m_action; + + const size_t count_subaction_paths = m_subaction_paths.size(); + for (size_t subaction_idx = 0; subaction_idx < count_subaction_paths; ++subaction_idx) { + state_info.subactionPath = m_subaction_paths[subaction_idx]; + + switch (m_type) { + case GHOST_kXrActionTypeBooleanInput: { + XrActionStateBoolean state{XR_TYPE_ACTION_STATE_BOOLEAN}; + CHECK_XR(xrGetActionStateBoolean(session, &state_info, &state), + (std::string("Failed to get state for boolean action \"") + action_name + "\".") + .data()); + if (state.isActive) { + ((bool *)m_states)[subaction_idx] = state.currentState; + } + break; + } + case GHOST_kXrActionTypeFloatInput: { + XrActionStateFloat state{XR_TYPE_ACTION_STATE_FLOAT}; + CHECK_XR( + xrGetActionStateFloat(session, &state_info, &state), + (std::string("Failed to get state for float action \"") + action_name + "\".").data()); + if (state.isActive) { + ((float *)m_states)[subaction_idx] = state.currentState; + } + break; + } + case GHOST_kXrActionTypeVector2fInput: { + XrActionStateVector2f state{XR_TYPE_ACTION_STATE_VECTOR2F}; + CHECK_XR(xrGetActionStateVector2f(session, &state_info, &state), + (std::string("Failed to get state for vector2f action \"") + action_name + "\".") + .data()); + if (state.isActive) { + memcpy(((float(*)[2])m_states)[subaction_idx], &state.currentState, sizeof(float[2])); + } + break; + } + case GHOST_kXrActionTypePoseInput: { + XrActionStatePose state{XR_TYPE_ACTION_STATE_POSE}; + CHECK_XR( + xrGetActionStatePose(session, &state_info, &state), + (std::string("Failed to get state for pose action \"") + action_name + "\".").data()); + if (state.isActive) { + XrSpace pose_space = XR_NULL_HANDLE; + for (auto &[path, space] : m_spaces) { + if (space.getSubactionPath() == state_info.subactionPath) { + pose_space = space.getSpace(); + break; + } + } + + if (pose_space != XR_NULL_HANDLE) { + XrSpaceLocation space_location{XR_TYPE_SPACE_LOCATION}; + CHECK_XR( + xrLocateSpace( + pose_space, reference_space, predicted_display_time, &space_location), + (std::string("Failed to query pose space for action \"") + action_name + "\".") + .data()); + copy_openxr_pose_to_ghost_pose(space_location.pose, + ((GHOST_XrPose *)m_states)[subaction_idx]); + } + } + break; + } + case GHOST_kXrActionTypeVibrationOutput: { + break; + } + } + } +} + +void GHOST_XrAction::applyHapticFeedback(XrSession session, + const char *action_name, + const GHOST_TInt64 &duration, + const float &frequency, + const float &litude) +{ + XrHapticVibration vibration{XR_TYPE_HAPTIC_VIBRATION}; + vibration.duration = (duration == 0) ? XR_MIN_HAPTIC_DURATION : + static_cast<XrDuration>(duration); + vibration.frequency = frequency; + vibration.amplitude = amplitude; + + XrHapticActionInfo haptic_info{XR_TYPE_HAPTIC_ACTION_INFO}; + haptic_info.action = m_action; + + for (std::vector<XrPath>::iterator it = m_subaction_paths.begin(); it != m_subaction_paths.end(); + ++it) { + haptic_info.subactionPath = *it; + CHECK_XR(xrApplyHapticFeedback(session, &haptic_info, (const XrHapticBaseHeader *)&vibration), + (std::string("Failed to apply haptic action \"") + action_name + "\".").data()); + } +} + +void GHOST_XrAction::stopHapticFeedback(XrSession session, const char *action_name) +{ + XrHapticActionInfo haptic_info{XR_TYPE_HAPTIC_ACTION_INFO}; + haptic_info.action = m_action; + + for (std::vector<XrPath>::iterator it = m_subaction_paths.begin(); it != m_subaction_paths.end(); + ++it) { + haptic_info.subactionPath = *it; + CHECK_XR(xrStopHapticFeedback(session, &haptic_info), + (std::string("Failed to stop haptic action \"") + action_name + "\".").data()); + } +} + +void *GHOST_XrAction::getCustomdata() +{ + if (m_custom_data_ == nullptr) { + return nullptr; + } + return m_custom_data_->custom_data_; +} + +void GHOST_XrAction::getBindings( + std::map<XrPath, std::vector<XrActionSuggestedBinding>> &r_bindings) const +{ + for (auto &[path, profile] : m_profiles) { + profile.getBindings(m_action, r_bindings); + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name GHOST_XrActionSet + * + * \{ */ + +GHOST_XrActionSet::GHOST_XrActionSet(XrInstance instance, const GHOST_XrActionSetInfo &info) + : m_custom_data_( + std::make_unique<GHOST_C_CustomDataWrapper>(info.customdata, info.customdata_free_fn)) +{ + XrActionSetCreateInfo action_set_info{XR_TYPE_ACTION_SET_CREATE_INFO}; + strcpy(action_set_info.actionSetName, info.name); + strcpy(action_set_info.localizedActionSetName, + info.name); /* Just use same name for localized. This can be changed in the future if + necessary. */ + action_set_info.priority = 0; /* Use same (default) priority for all action sets. */ + + CHECK_XR(xrCreateActionSet(instance, &action_set_info, &m_action_set), + (std::string("Failed to create action set \"") + info.name + + "\". Name must not contain upper case letters or special characters " + "other than '-', '_', or '.'.") + .data()); +} + +GHOST_XrActionSet::~GHOST_XrActionSet() +{ + /* This needs to be done before xrDestroyActionSet() to avoid an assertion in the GHOST_XrAction + * destructor (which calls xrDestroyAction()). */ + m_actions.clear(); + + if (m_action_set != XR_NULL_HANDLE) { + CHECK_XR_ASSERT(xrDestroyActionSet(m_action_set)); + } +} + +bool GHOST_XrActionSet::createAction(XrInstance instance, const GHOST_XrActionInfo &info) +{ + if (m_actions.find(info.name) != m_actions.end()) { + return false; + } + + m_actions.emplace(std::piecewise_construct, + std::make_tuple(info.name), + std::make_tuple(instance, m_action_set, info)); + + return true; +} + +void GHOST_XrActionSet::destroyAction(const char *action_name) +{ + if (m_actions.find(action_name) != m_actions.end()) { + m_actions.erase(action_name); + } +} + +GHOST_XrAction *GHOST_XrActionSet::findAction(const char *action_name) +{ + std::map<std::string, GHOST_XrAction>::iterator it = m_actions.find(action_name); + if (it == m_actions.end()) { + return nullptr; + } + return &it->second; +} + +void GHOST_XrActionSet::updateStates(XrSession session, + XrSpace reference_space, + const XrTime &predicted_display_time) +{ + for (auto &[name, action] : m_actions) { + action.updateState(session, name.data(), reference_space, predicted_display_time); + } +} + +XrActionSet GHOST_XrActionSet::getActionSet() const +{ + return m_action_set; +} + +void *GHOST_XrActionSet::getCustomdata() +{ + if (m_custom_data_ == nullptr) { + return nullptr; + } + return m_custom_data_->custom_data_; +} + +void GHOST_XrActionSet::getBindings( + std::map<XrPath, std::vector<XrActionSuggestedBinding>> &r_bindings) const +{ + for (auto &[name, action] : m_actions) { + action.getBindings(r_bindings); + } +} + +/** \} */ diff --git a/intern/ghost/intern/GHOST_XrAction.h b/intern/ghost/intern/GHOST_XrAction.h new file mode 100644 index 00000000000..bdc6cafb4a9 --- /dev/null +++ b/intern/ghost/intern/GHOST_XrAction.h @@ -0,0 +1,145 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup GHOST + */ + +/* Note: Requires OpenXR headers to be included before this one for OpenXR types (XrSpace, XrPath, + * etc.). */ + +#pragma once + +#include <map> +#include <memory> +#include <string> + +#include "GHOST_Util.h" + +/* -------------------------------------------------------------------- */ + +class GHOST_XrActionSpace { + public: + GHOST_XrActionSpace() = delete; /* Default constructor for map storage. */ + GHOST_XrActionSpace(XrInstance instance, + XrSession session, + XrAction action, + const GHOST_XrActionSpaceInfo &info, + uint32_t subaction_idx); + ~GHOST_XrActionSpace(); + + XrSpace getSpace() const; + const XrPath &getSubactionPath() const; + + private: + XrSpace m_space = XR_NULL_HANDLE; + XrPath m_subaction_path = XR_NULL_PATH; +}; + +/* -------------------------------------------------------------------- */ + +class GHOST_XrActionProfile { + public: + GHOST_XrActionProfile() = delete; /* Default constructor for map storage. */ + GHOST_XrActionProfile(XrInstance instance, + XrAction action, + const char *profile_path, + const GHOST_XrActionBindingInfo &info); + ~GHOST_XrActionProfile() = default; + + void getBindings(XrAction action, + std::map<XrPath, std::vector<XrActionSuggestedBinding>> &r_bindings) const; + + private: + XrPath m_profile = XR_NULL_PATH; + /* Bindings identified by interaction (user (subaction) + component) path. */ + std::map<std::string, XrPath> m_bindings; +}; + +/* -------------------------------------------------------------------- */ + +class GHOST_XrAction { + public: + GHOST_XrAction() = delete; /* Default constructor for map storage. */ + GHOST_XrAction(XrInstance instance, XrActionSet action_set, const GHOST_XrActionInfo &info); + ~GHOST_XrAction(); + + bool createSpace(XrInstance instance, XrSession session, const GHOST_XrActionSpaceInfo &info); + void destroySpace(const char *subaction_path); + + bool createBinding(XrInstance instance, + const char *profile_path, + const GHOST_XrActionBindingInfo &info); + void destroyBinding(const char *profile_path); + + void updateState(XrSession session, + const char *action_name, + XrSpace reference_space, + const XrTime &predicted_display_time); + void applyHapticFeedback(XrSession session, + const char *action_name, + const GHOST_TInt64 &duration, + const float &frequency, + const float &litude); + void stopHapticFeedback(XrSession session, const char *action_name); + + void *getCustomdata(); + void getBindings(std::map<XrPath, std::vector<XrActionSuggestedBinding>> &r_bindings) const; + + private: + XrAction m_action = XR_NULL_HANDLE; + GHOST_XrActionType m_type; + std::vector<XrPath> m_subaction_paths; + /** States for each subaction path. */ + void *m_states; + + std::unique_ptr<GHOST_C_CustomDataWrapper> m_custom_data_ = nullptr; /* wmXrAction */ + + /* Spaces identified by user (subaction) path. */ + std::map<std::string, GHOST_XrActionSpace> m_spaces; + /* Profiles identified by interaction profile path. */ + std::map<std::string, GHOST_XrActionProfile> m_profiles; +}; + +/* -------------------------------------------------------------------- */ + +class GHOST_XrActionSet { + public: + GHOST_XrActionSet() = delete; /* Default constructor for map storage. */ + GHOST_XrActionSet(XrInstance instance, const GHOST_XrActionSetInfo &info); + ~GHOST_XrActionSet(); + + bool createAction(XrInstance instance, const GHOST_XrActionInfo &info); + void destroyAction(const char *action_name); + GHOST_XrAction *findAction(const char *action_name); + + void updateStates(XrSession session, + XrSpace reference_space, + const XrTime &predicted_display_time); + + XrActionSet getActionSet() const; + void *getCustomdata(); + void getBindings(std::map<XrPath, std::vector<XrActionSuggestedBinding>> &r_bindings) const; + + private: + XrActionSet m_action_set = XR_NULL_HANDLE; + + std::unique_ptr<GHOST_C_CustomDataWrapper> m_custom_data_ = nullptr; /* wmXrActionSet */ + + std::map<std::string, GHOST_XrAction> m_actions; +}; + +/* -------------------------------------------------------------------- */ diff --git a/intern/ghost/intern/GHOST_XrContext.cpp b/intern/ghost/intern/GHOST_XrContext.cpp index 2bf67c121f8..daad0b8190a 100644 --- a/intern/ghost/intern/GHOST_XrContext.cpp +++ b/intern/ghost/intern/GHOST_XrContext.cpp @@ -55,7 +55,6 @@ void *GHOST_XrContext::s_error_handler_customdata = nullptr; /* -------------------------------------------------------------------- */ /** \name Create, Initialize and Destruct - * * \{ */ GHOST_XrContext::GHOST_XrContext(const GHOST_XrContextCreateInfo *create_info) @@ -153,7 +152,6 @@ void GHOST_XrContext::storeInstanceProperties() /* -------------------------------------------------------------------- */ /** \name Debug Printing - * * \{ */ void GHOST_XrContext::printInstanceInfo() @@ -242,14 +240,13 @@ void GHOST_XrContext::initDebugMessenger() /* -------------------------------------------------------------------- */ /** \name Error handling - * * \{ */ void GHOST_XrContext::dispatchErrorMessage(const GHOST_XrException *exception) const { GHOST_XrError error; - error.user_message = exception->m_msg; + error.user_message = exception->m_msg.data(); error.customdata = s_error_handler_customdata; if (isDebugMode()) { @@ -273,7 +270,6 @@ void GHOST_XrContext::setErrorHandler(GHOST_XrErrorHandlerFn handler_fn, void *c /* -------------------------------------------------------------------- */ /** \name OpenXR API-Layers and Extensions - * * \{ */ /** @@ -378,7 +374,7 @@ void GHOST_XrContext::getAPILayersToEnable(std::vector<const char *> &r_ext_name for (const std::string &layer : try_layers) { if (openxr_layer_is_available(m_oxr->layers, layer)) { - r_ext_names.push_back(layer.c_str()); + r_ext_names.push_back(layer.data()); } } } @@ -488,6 +484,7 @@ GHOST_TXrGraphicsBinding GHOST_XrContext::determineGraphicsBindingTypeToUse( void GHOST_XrContext::startSession(const GHOST_XrSessionBeginInfo *begin_info) { + m_custom_funcs.session_create_fn = begin_info->create_fn; m_custom_funcs.session_exit_fn = begin_info->exit_fn; m_custom_funcs.session_exit_customdata = begin_info->exit_customdata; @@ -538,6 +535,16 @@ void GHOST_XrContext::handleSessionStateChange(const XrEventDataSessionStateChan * Public as in, exposed in the Ghost API. * \{ */ +GHOST_XrSession *GHOST_XrContext::getSession() +{ + return m_session.get(); +} + +const GHOST_XrSession *GHOST_XrContext::getSession() const +{ + return m_session.get(); +} + void GHOST_XrContext::setGraphicsContextBindFuncs(GHOST_XrGraphicsContextBindFn bind_fn, GHOST_XrGraphicsContextUnbindFn unbind_fn) { @@ -564,7 +571,6 @@ bool GHOST_XrContext::needsUpsideDownDrawing() const /* -------------------------------------------------------------------- */ /** \name Ghost Internal Accessors and Mutators - * * \{ */ GHOST_TXrOpenXRRuntimeID GHOST_XrContext::getOpenXRRuntimeID() const diff --git a/intern/ghost/intern/GHOST_XrContext.h b/intern/ghost/intern/GHOST_XrContext.h index 59c7786ed7a..f29d7349f7e 100644 --- a/intern/ghost/intern/GHOST_XrContext.h +++ b/intern/ghost/intern/GHOST_XrContext.h @@ -35,6 +35,7 @@ struct GHOST_XrCustomFuncs { /** Function to release (possibly free) a graphics context. */ GHOST_XrGraphicsContextUnbindFn gpu_ctx_unbind_fn = nullptr; + GHOST_XrSessionCreateFn session_create_fn = nullptr; GHOST_XrSessionExitFn session_exit_fn = nullptr; void *session_exit_customdata = nullptr; @@ -72,6 +73,10 @@ class GHOST_XrContext : public GHOST_IXrContext { bool isSessionRunning() const override; void drawSessionViews(void *draw_customdata) override; + /** Needed for the GHOST C api. */ + GHOST_XrSession *getSession() override; + const GHOST_XrSession *getSession() const override; + static void setErrorHandler(GHOST_XrErrorHandlerFn handler_fn, void *customdata); void dispatchErrorMessage(const class GHOST_XrException *exception) const override; diff --git a/intern/ghost/intern/GHOST_XrException.h b/intern/ghost/intern/GHOST_XrException.h index 30c33eaf98f..e93164e04c8 100644 --- a/intern/ghost/intern/GHOST_XrException.h +++ b/intern/ghost/intern/GHOST_XrException.h @@ -21,6 +21,7 @@ #pragma once #include <exception> +#include <string> class GHOST_XrException : public std::exception { friend class GHOST_XrContext; @@ -33,10 +34,10 @@ class GHOST_XrException : public std::exception { const char *what() const noexcept override { - return m_msg; + return m_msg.data(); } private: - const char *m_msg; + std::string m_msg; int m_result; }; diff --git a/intern/ghost/intern/GHOST_XrSession.cpp b/intern/ghost/intern/GHOST_XrSession.cpp index a1fd7fc2781..a7438fae13c 100644 --- a/intern/ghost/intern/GHOST_XrSession.cpp +++ b/intern/ghost/intern/GHOST_XrSession.cpp @@ -28,6 +28,7 @@ #include "GHOST_C-api.h" #include "GHOST_IXrGraphicsBinding.h" +#include "GHOST_XrAction.h" #include "GHOST_XrContext.h" #include "GHOST_XrException.h" #include "GHOST_XrSwapchain.h" @@ -46,6 +47,8 @@ struct OpenXRSessionData { XrSpace view_space; std::vector<XrView> views; std::vector<GHOST_XrSwapchain> swapchains; + + std::map<std::string, GHOST_XrActionSet> action_sets; }; struct GHOST_XrDrawInfo { @@ -59,7 +62,6 @@ struct GHOST_XrDrawInfo { /* -------------------------------------------------------------------- */ /** \name Create, Initialize and Destruct - * * \{ */ GHOST_XrSession::GHOST_XrSession(GHOST_XrContext &xr_context) @@ -72,6 +74,7 @@ GHOST_XrSession::~GHOST_XrSession() unbindGraphicsContext(); m_oxr->swapchains.clear(); + m_oxr->action_sets.clear(); if (m_oxr->reference_space != XR_NULL_HANDLE) { CHECK_XR_ASSERT(xrDestroySpace(m_oxr->reference_space)); @@ -110,7 +113,6 @@ void GHOST_XrSession::initSystem() /* -------------------------------------------------------------------- */ /** \name State Management - * * \{ */ static void create_reference_spaces(OpenXRSessionData &oxr, const GHOST_XrPose &base_pose) @@ -179,7 +181,7 @@ void GHOST_XrSession::start(const GHOST_XrSessionBeginInfo *begin_info) std::ostringstream strstream; strstream << "Available graphics context version does not meet the following requirements: " << requirement_str; - throw GHOST_XrException(strstream.str().c_str()); + throw GHOST_XrException(strstream.str().data()); } m_gpu_binding->initFromGhostContext(*m_gpu_ctx); @@ -196,6 +198,9 @@ void GHOST_XrSession::start(const GHOST_XrSessionBeginInfo *begin_info) prepareDrawing(); create_reference_spaces(*m_oxr, begin_info->base_pose); + + /* Create and bind actions here. */ + m_context->getCustomFuncs().session_create_fn(); } void GHOST_XrSession::requestEnd() @@ -225,10 +230,9 @@ GHOST_XrSession::LifeExpectancy GHOST_XrSession::handleStateChangeEvent( assert(m_oxr->session == XR_NULL_HANDLE || m_oxr->session == lifecycle.session); switch (lifecycle.state) { - case XR_SESSION_STATE_READY: { + case XR_SESSION_STATE_READY: beginSession(); break; - } case XR_SESSION_STATE_STOPPING: endSession(); break; @@ -245,7 +249,6 @@ GHOST_XrSession::LifeExpectancy GHOST_XrSession::handleStateChangeEvent( /* -------------------------------------------------------------------- */ /** \name Drawing - * * \{ */ void GHOST_XrSession::prepareDrawing() @@ -352,18 +355,6 @@ void GHOST_XrSession::draw(void *draw_customdata) endFrameDrawing(layers); } -static void copy_openxr_pose_to_ghost_pose(const XrPosef &oxr_pose, GHOST_XrPose &r_ghost_pose) -{ - /* Set and convert to Blender coordinate space. */ - r_ghost_pose.position[0] = oxr_pose.position.x; - r_ghost_pose.position[1] = oxr_pose.position.y; - r_ghost_pose.position[2] = oxr_pose.position.z; - r_ghost_pose.orientation_quat[0] = oxr_pose.orientation.w; - r_ghost_pose.orientation_quat[1] = oxr_pose.orientation.x; - r_ghost_pose.orientation_quat[2] = oxr_pose.orientation.y; - r_ghost_pose.orientation_quat[3] = oxr_pose.orientation.z; -} - static void ghost_xr_draw_view_info_from_view(const XrView &view, GHOST_XrDrawViewInfo &r_info) { /* Set and convert to Blender coordinate space. */ @@ -457,7 +448,6 @@ bool GHOST_XrSession::needsUpsideDownDrawing() const /* -------------------------------------------------------------------- */ /** \name State Queries - * * \{ */ bool GHOST_XrSession::isRunning() const @@ -505,3 +495,340 @@ void GHOST_XrSession::unbindGraphicsContext() } /** \} */ /* Graphics Context Injection */ + +/* -------------------------------------------------------------------- */ +/** \name Actions + * + * \{ */ + +static GHOST_XrActionSet *find_action_set(OpenXRSessionData *oxr, const char *action_set_name) +{ + std::map<std::string, GHOST_XrActionSet>::iterator it = oxr->action_sets.find(action_set_name); + if (it == oxr->action_sets.end()) { + return nullptr; + } + return &it->second; +} + +bool GHOST_XrSession::createActionSet(const GHOST_XrActionSetInfo &info) +{ + std::map<std::string, GHOST_XrActionSet> &action_sets = m_oxr->action_sets; + if (action_sets.find(info.name) != action_sets.end()) { + return false; + } + + XrInstance instance = m_context->getInstance(); + + action_sets.emplace( + std::piecewise_construct, std::make_tuple(info.name), std::make_tuple(instance, info)); + + return true; +} + +void GHOST_XrSession::destroyActionSet(const char *action_set_name) +{ + std::map<std::string, GHOST_XrActionSet> &action_sets = m_oxr->action_sets; + if (action_sets.find(action_set_name) != action_sets.end()) { + action_sets.erase(action_set_name); + } +} + +bool GHOST_XrSession::createActions(const char *action_set_name, + uint32_t count, + const GHOST_XrActionInfo *infos) +{ + GHOST_XrActionSet *action_set = find_action_set(m_oxr.get(), action_set_name); + if (action_set == nullptr) { + return false; + } + + XrInstance instance = m_context->getInstance(); + + for (uint32_t i = 0; i < count; ++i) { + if (!action_set->createAction(instance, infos[i])) { + return false; + } + } + + return true; +} + +void GHOST_XrSession::destroyActions(const char *action_set_name, + uint32_t count, + const char *const *action_names) +{ + GHOST_XrActionSet *action_set = find_action_set(m_oxr.get(), action_set_name); + if (action_set == nullptr) { + return; + } + + for (uint32_t i = 0; i < count; ++i) { + action_set->destroyAction(action_names[i]); + } +} + +bool GHOST_XrSession::createActionSpaces(const char *action_set_name, + uint32_t count, + const GHOST_XrActionSpaceInfo *infos) +{ + GHOST_XrActionSet *action_set = find_action_set(m_oxr.get(), action_set_name); + if (action_set == nullptr) { + return false; + } + + XrInstance instance = m_context->getInstance(); + XrSession session = m_oxr->session; + + for (uint32_t action_idx = 0; action_idx < count; ++action_idx) { + const GHOST_XrActionSpaceInfo &info = infos[action_idx]; + + GHOST_XrAction *action = action_set->findAction(info.action_name); + if (action == nullptr) { + continue; + } + + if (!action->createSpace(instance, session, info)) { + return false; + } + } + + return true; +} + +void GHOST_XrSession::destroyActionSpaces(const char *action_set_name, + uint32_t count, + const GHOST_XrActionSpaceInfo *infos) +{ + GHOST_XrActionSet *action_set = find_action_set(m_oxr.get(), action_set_name); + if (action_set == nullptr) { + return; + } + + for (uint32_t action_idx = 0; action_idx < count; ++action_idx) { + const GHOST_XrActionSpaceInfo &info = infos[action_idx]; + + GHOST_XrAction *action = action_set->findAction(info.action_name); + if (action == nullptr) { + continue; + } + + for (uint32_t subaction_idx = 0; subaction_idx < info.count_subaction_paths; ++subaction_idx) { + action->destroySpace(info.subaction_paths[subaction_idx]); + } + } +} + +bool GHOST_XrSession::createActionBindings(const char *action_set_name, + uint32_t count, + const GHOST_XrActionProfileInfo *infos) +{ + GHOST_XrActionSet *action_set = find_action_set(m_oxr.get(), action_set_name); + if (action_set == nullptr) { + return false; + } + + XrInstance instance = m_context->getInstance(); + + for (uint32_t profile_idx = 0; profile_idx < count; ++profile_idx) { + const GHOST_XrActionProfileInfo &info = infos[profile_idx]; + const char *profile_path = info.profile_path; + + for (uint32_t binding_idx = 0; binding_idx < info.count_bindings; ++binding_idx) { + const GHOST_XrActionBindingInfo &binding = info.bindings[binding_idx]; + + GHOST_XrAction *action = action_set->findAction(binding.action_name); + if (action == nullptr) { + continue; + } + + action->createBinding(instance, profile_path, binding); + } + } + + return true; +} + +void GHOST_XrSession::destroyActionBindings(const char *action_set_name, + uint32_t count, + const GHOST_XrActionProfileInfo *infos) +{ + GHOST_XrActionSet *action_set = find_action_set(m_oxr.get(), action_set_name); + if (action_set == nullptr) { + return; + } + + for (uint32_t profile_idx = 0; profile_idx < count; ++profile_idx) { + const GHOST_XrActionProfileInfo &info = infos[profile_idx]; + const char *profile_path = info.profile_path; + + for (uint32_t binding_idx = 0; binding_idx < info.count_bindings; ++binding_idx) { + const GHOST_XrActionBindingInfo &binding = info.bindings[binding_idx]; + + GHOST_XrAction *action = action_set->findAction(binding.action_name); + if (action == nullptr) { + continue; + } + + action->destroyBinding(profile_path); + } + } +} + +bool GHOST_XrSession::attachActionSets() +{ + /* Suggest action bindings for all action sets. */ + std::map<XrPath, std::vector<XrActionSuggestedBinding>> profile_bindings; + for (auto &[name, action_set] : m_oxr->action_sets) { + action_set.getBindings(profile_bindings); + } + + if (profile_bindings.size() < 1) { + return false; + } + + XrInteractionProfileSuggestedBinding bindings_info{ + XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING}; + XrInstance instance = m_context->getInstance(); + + for (auto &[profile, bindings] : profile_bindings) { + bindings_info.interactionProfile = profile; + bindings_info.countSuggestedBindings = (uint32_t)bindings.size(); + bindings_info.suggestedBindings = bindings.data(); + + CHECK_XR(xrSuggestInteractionProfileBindings(instance, &bindings_info), + "Failed to suggest interaction profile bindings."); + } + + /* Attach action sets. */ + XrSessionActionSetsAttachInfo attach_info{XR_TYPE_SESSION_ACTION_SETS_ATTACH_INFO}; + attach_info.countActionSets = (uint32_t)m_oxr->action_sets.size(); + + /* Create an aligned copy of the action sets to pass to xrAttachSessionActionSets(). */ + std::vector<XrActionSet> action_sets(attach_info.countActionSets); + uint32_t i = 0; + for (auto &[name, action_set] : m_oxr->action_sets) { + action_sets[i++] = action_set.getActionSet(); + } + attach_info.actionSets = action_sets.data(); + + CHECK_XR(xrAttachSessionActionSets(m_oxr->session, &attach_info), + "Failed to attach XR action sets."); + + return true; +} + +bool GHOST_XrSession::syncActions(const char *action_set_name) +{ + std::map<std::string, GHOST_XrActionSet> &action_sets = m_oxr->action_sets; + + XrActionsSyncInfo sync_info{XR_TYPE_ACTIONS_SYNC_INFO}; + sync_info.countActiveActionSets = (action_set_name != nullptr) ? 1 : + (uint32_t)action_sets.size(); + if (sync_info.countActiveActionSets < 1) { + return false; + } + + std::vector<XrActiveActionSet> active_action_sets(sync_info.countActiveActionSets); + GHOST_XrActionSet *action_set = nullptr; + + if (action_set_name != nullptr) { + action_set = find_action_set(m_oxr.get(), action_set_name); + if (action_set == nullptr) { + return false; + } + + XrActiveActionSet &active_action_set = active_action_sets[0]; + active_action_set.actionSet = action_set->getActionSet(); + active_action_set.subactionPath = XR_NULL_PATH; + } + else { + uint32_t i = 0; + for (auto &[name, action_set] : action_sets) { + XrActiveActionSet &active_action_set = active_action_sets[i++]; + active_action_set.actionSet = action_set.getActionSet(); + active_action_set.subactionPath = XR_NULL_PATH; + } + } + sync_info.activeActionSets = active_action_sets.data(); + + CHECK_XR(xrSyncActions(m_oxr->session, &sync_info), "Failed to synchronize XR actions."); + + /* Update action states (i.e. Blender custom data). */ + XrSession session = m_oxr->session; + XrSpace reference_space = m_oxr->reference_space; + const XrTime &predicted_display_time = m_draw_info->frame_state.predictedDisplayTime; + + if (action_set != nullptr) { + action_set->updateStates(session, reference_space, predicted_display_time); + } + else { + for (auto &[name, action_set] : action_sets) { + action_set.updateStates(session, reference_space, predicted_display_time); + } + } + + return true; +} + +bool GHOST_XrSession::applyHapticAction(const char *action_set_name, + const char *action_name, + const GHOST_TInt64 &duration, + const float &frequency, + const float &litude) +{ + GHOST_XrActionSet *action_set = find_action_set(m_oxr.get(), action_set_name); + if (action_set == nullptr) { + return false; + } + + GHOST_XrAction *action = action_set->findAction(action_name); + if (action == nullptr) { + return false; + } + + action->applyHapticFeedback(m_oxr->session, action_name, duration, frequency, amplitude); + + return true; +} + +void GHOST_XrSession::stopHapticAction(const char *action_set_name, const char *action_name) +{ + GHOST_XrActionSet *action_set = find_action_set(m_oxr.get(), action_set_name); + if (action_set == nullptr) { + return; + } + + GHOST_XrAction *action = action_set->findAction(action_name); + if (action == nullptr) { + return; + } + + action->stopHapticFeedback(m_oxr->session, action_name); +} + +void *GHOST_XrSession::getActionSetCustomdata(const char *action_set_name) +{ + GHOST_XrActionSet *action_set = find_action_set(m_oxr.get(), action_set_name); + if (action_set == nullptr) { + return nullptr; + } + + return action_set->getCustomdata(); +} + +void *GHOST_XrSession::getActionCustomdata(const char *action_set_name, const char *action_name) +{ + GHOST_XrActionSet *action_set = find_action_set(m_oxr.get(), action_set_name); + if (action_set == nullptr) { + return nullptr; + } + + GHOST_XrAction *action = action_set->findAction(action_name); + if (action == nullptr) { + return nullptr; + } + + return action->getCustomdata(); +} + +/** \} */ /* Actions */ diff --git a/intern/ghost/intern/GHOST_XrSession.h b/intern/ghost/intern/GHOST_XrSession.h index 79a586411e9..d09c78e1ea7 100644 --- a/intern/ghost/intern/GHOST_XrSession.h +++ b/intern/ghost/intern/GHOST_XrSession.h @@ -52,6 +52,43 @@ class GHOST_XrSession { void draw(void *draw_customdata); + /** Action functions to be called pre-session start. + * Note: The "destroy" functions can also be called post-session start. */ + bool createActionSet(const GHOST_XrActionSetInfo &info); + void destroyActionSet(const char *action_set_name); + bool createActions(const char *action_set_name, uint32_t count, const GHOST_XrActionInfo *infos); + void destroyActions(const char *action_set_name, + uint32_t count, + const char *const *action_names); + bool createActionSpaces(const char *action_set_name, + uint32_t count, + const GHOST_XrActionSpaceInfo *infos); + void destroyActionSpaces(const char *action_set_name, + uint32_t count, + const GHOST_XrActionSpaceInfo *infos); + bool createActionBindings(const char *action_set_name, + uint32_t count, + const GHOST_XrActionProfileInfo *infos); + void destroyActionBindings(const char *action_set_name, + uint32_t count, + const GHOST_XrActionProfileInfo *infos); + bool attachActionSets(); + + /** Action functions to be called post-session start. */ + bool syncActions( + const char *action_set_name = nullptr); /* If action_set_name is nullptr, all attached + * action sets will be synced. */ + bool applyHapticAction(const char *action_set_name, + const char *action_name, + const GHOST_TInt64 &duration, + const float &frequency, + const float &litude); + void stopHapticAction(const char *action_set_name, const char *action_name); + + /* Custom data (owned by Blender, not GHOST) accessors. */ + void *getActionSetCustomdata(const char *action_set_name); + void *getActionCustomdata(const char *action_set_name, const char *action_name); + private: /** Pointer back to context managing this session. Would be nice to avoid, but needed to access * custom callbacks set before session start. */ diff --git a/intern/ghost/intern/GHOST_Xr_intern.h b/intern/ghost/intern/GHOST_Xr_intern.h index 137541c4528..0616e426da3 100644 --- a/intern/ghost/intern/GHOST_Xr_intern.h +++ b/intern/ghost/intern/GHOST_Xr_intern.h @@ -45,3 +45,27 @@ (void)_res; \ } \ (void)0 + +inline void copy_ghost_pose_to_openxr_pose(const GHOST_XrPose &ghost_pose, XrPosef &r_oxr_pose) +{ + /* Set and convert to OpenXR coordinate space. */ + r_oxr_pose.position.x = ghost_pose.position[0]; + r_oxr_pose.position.y = ghost_pose.position[1]; + r_oxr_pose.position.z = ghost_pose.position[2]; + r_oxr_pose.orientation.w = ghost_pose.orientation_quat[0]; + r_oxr_pose.orientation.x = ghost_pose.orientation_quat[1]; + r_oxr_pose.orientation.y = ghost_pose.orientation_quat[2]; + r_oxr_pose.orientation.z = ghost_pose.orientation_quat[3]; +} + +inline void copy_openxr_pose_to_ghost_pose(const XrPosef &oxr_pose, GHOST_XrPose &r_ghost_pose) +{ + /* Set and convert to Blender coordinate space. */ + r_ghost_pose.position[0] = oxr_pose.position.x; + r_ghost_pose.position[1] = oxr_pose.position.y; + r_ghost_pose.position[2] = oxr_pose.position.z; + r_ghost_pose.orientation_quat[0] = oxr_pose.orientation.w; + r_ghost_pose.orientation_quat[1] = oxr_pose.orientation.x; + r_ghost_pose.orientation_quat[2] = oxr_pose.orientation.y; + r_ghost_pose.orientation_quat[3] = oxr_pose.orientation.z; +} diff --git a/intern/ghost/test/gears/GHOST_C-Test.c b/intern/ghost/test/gears/GHOST_C-Test.c index 4283f990cfb..8cd1b5acb89 100644 --- a/intern/ghost/test/gears/GHOST_C-Test.c +++ b/intern/ghost/test/gears/GHOST_C-Test.c @@ -477,7 +477,7 @@ int main(int argc, char **argv) /* Enter main loop */ while (!sExitRequested) { - if (!GHOST_ProcessEvents(shSystem, 0)) { + if (!GHOST_ProcessEvents(shSystem, false)) { #ifdef WIN32 /* If there were no events, be nice to other applications */ Sleep(10); diff --git a/intern/ghost/test/multitest/MultiTest.c b/intern/ghost/test/multitest/MultiTest.c index 8c8858fc6d8..3b424f1ca89 100644 --- a/intern/ghost/test/multitest/MultiTest.c +++ b/intern/ghost/test/multitest/MultiTest.c @@ -926,7 +926,7 @@ void multitestapp_exit(MultiTestApp *app) void multitestapp_run(MultiTestApp *app) { while (!app->exit) { - GHOST_ProcessEvents(app->sys, 1); + GHOST_ProcessEvents(app->sys, true); GHOST_DispatchEvents(app->sys); } } |