Merge branch 'master' into temp_bmesh_multires

This commit is contained in:
Joseph Eagar 2021-05-16 23:32:35 -07:00
commit 9bea7259e1
224 changed files with 6692 additions and 1483 deletions

View File

@ -4,7 +4,9 @@ Simple Render Engine
"""
import bpy
import bgl
import array
import gpu
from gpu_extras.presets import draw_texture_2d
class CustomRenderEngine(bpy.types.RenderEngine):
@ -100,8 +102,7 @@ class CustomRenderEngine(bpy.types.RenderEngine):
dimensions = region.width, region.height
# Bind shader that converts from scene linear to display space,
bgl.glEnable(bgl.GL_BLEND)
bgl.glBlendFunc(bgl.GL_ONE, bgl.GL_ONE_MINUS_SRC_ALPHA)
gpu.state.blend_set('ALPHA_PREMULT')
self.bind_display_space_shader(scene)
if not self.draw_data or self.draw_data.dimensions != dimensions:
@ -110,7 +111,7 @@ class CustomRenderEngine(bpy.types.RenderEngine):
self.draw_data.draw()
self.unbind_display_space_shader()
bgl.glDisable(bgl.GL_BLEND)
gpu.state.blend_set('NONE')
class CustomDrawData:
@ -119,68 +120,21 @@ class CustomDrawData:
self.dimensions = dimensions
width, height = dimensions
pixels = [0.1, 0.2, 0.1, 1.0] * width * height
pixels = bgl.Buffer(bgl.GL_FLOAT, width * height * 4, pixels)
pixels = width * height * array.array('f', [0.1, 0.2, 0.1, 1.0])
pixels = gpu.types.Buffer('FLOAT', width * height * 4, pixels)
# Generate texture
self.texture = bgl.Buffer(bgl.GL_INT, 1)
bgl.glGenTextures(1, self.texture)
bgl.glActiveTexture(bgl.GL_TEXTURE0)
bgl.glBindTexture(bgl.GL_TEXTURE_2D, self.texture[0])
bgl.glTexImage2D(bgl.GL_TEXTURE_2D, 0, bgl.GL_RGBA16F, width, height, 0, bgl.GL_RGBA, bgl.GL_FLOAT, pixels)
bgl.glTexParameteri(bgl.GL_TEXTURE_2D, bgl.GL_TEXTURE_MIN_FILTER, bgl.GL_LINEAR)
bgl.glTexParameteri(bgl.GL_TEXTURE_2D, bgl.GL_TEXTURE_MAG_FILTER, bgl.GL_LINEAR)
bgl.glBindTexture(bgl.GL_TEXTURE_2D, 0)
self.texture = gpu.types.GPUTexture((width, height), format='RGBA16F', data=pixels)
# Bind shader that converts from scene linear to display space,
# use the scene's color management settings.
shader_program = bgl.Buffer(bgl.GL_INT, 1)
bgl.glGetIntegerv(bgl.GL_CURRENT_PROGRAM, shader_program)
# Generate vertex array
self.vertex_array = bgl.Buffer(bgl.GL_INT, 1)
bgl.glGenVertexArrays(1, self.vertex_array)
bgl.glBindVertexArray(self.vertex_array[0])
texturecoord_location = bgl.glGetAttribLocation(shader_program[0], "texCoord")
position_location = bgl.glGetAttribLocation(shader_program[0], "pos")
bgl.glEnableVertexAttribArray(texturecoord_location)
bgl.glEnableVertexAttribArray(position_location)
# Generate geometry buffers for drawing textured quad
position = [0.0, 0.0, width, 0.0, width, height, 0.0, height]
position = bgl.Buffer(bgl.GL_FLOAT, len(position), position)
texcoord = [0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0]
texcoord = bgl.Buffer(bgl.GL_FLOAT, len(texcoord), texcoord)
self.vertex_buffer = bgl.Buffer(bgl.GL_INT, 2)
bgl.glGenBuffers(2, self.vertex_buffer)
bgl.glBindBuffer(bgl.GL_ARRAY_BUFFER, self.vertex_buffer[0])
bgl.glBufferData(bgl.GL_ARRAY_BUFFER, 32, position, bgl.GL_STATIC_DRAW)
bgl.glVertexAttribPointer(position_location, 2, bgl.GL_FLOAT, bgl.GL_FALSE, 0, None)
bgl.glBindBuffer(bgl.GL_ARRAY_BUFFER, self.vertex_buffer[1])
bgl.glBufferData(bgl.GL_ARRAY_BUFFER, 32, texcoord, bgl.GL_STATIC_DRAW)
bgl.glVertexAttribPointer(texturecoord_location, 2, bgl.GL_FLOAT, bgl.GL_FALSE, 0, None)
bgl.glBindBuffer(bgl.GL_ARRAY_BUFFER, 0)
bgl.glBindVertexArray(0)
# Note: This is just a didactic example.
# In this case it would be more convenient to fill the texture with:
# self.texture.clear('FLOAT', value=[0.1, 0.2, 0.1, 1.0])
def __del__(self):
bgl.glDeleteBuffers(2, self.vertex_buffer)
bgl.glDeleteVertexArrays(1, self.vertex_array)
bgl.glBindTexture(bgl.GL_TEXTURE_2D, 0)
bgl.glDeleteTextures(1, self.texture)
del self.texture
def draw(self):
bgl.glActiveTexture(bgl.GL_TEXTURE0)
bgl.glBindTexture(bgl.GL_TEXTURE_2D, self.texture[0])
bgl.glBindVertexArray(self.vertex_array[0])
bgl.glDrawArrays(bgl.GL_TRIANGLE_FAN, 0, 4)
bgl.glBindVertexArray(0)
bgl.glBindTexture(bgl.GL_TEXTURE_2D, 0)
draw_texture_2d(self.texture, (0, 0), self.texture.width, self.texture.height)
# RenderEngines also need to tell UI Panels that they are compatible with.

View File

@ -1029,7 +1029,6 @@ def pymodule2sphinx(basepath, module_name, module, title, module_all_extra):
context_type_map = {
# context_member: (RNA type, is_collection)
"active_annotation_layer": ("GPencilLayer", False),
"active_base": ("ObjectBase", False),
"active_bone": ("EditBone", False),
"active_gpencil_frame": ("GreasePencilLayer", True),
"active_gpencil_layer": ("GPencilLayer", True),
@ -1549,8 +1548,8 @@ def pyrna2sphinx(basepath):
fw(".. hlist::\n")
fw(" :columns: 2\n\n")
# context does its own thing
# "active_base": ("ObjectBase", False),
# Context does its own thing.
# "active_object": ("Object", False),
for ref_attr, (ref_type, ref_is_seq) in sorted(context_type_map.items()):
if ref_type == struct_id:
fw(" * :mod:`bpy.context.%s`\n" % ref_attr)

View File

@ -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;
}
}

View File

@ -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

View File

@ -57,14 +57,24 @@ ccl_device ccl_addr_space void *closure_alloc_extra(ShaderData *sd, int size)
ccl_device_inline ShaderClosure *bsdf_alloc(ShaderData *sd, int size, float3 weight)
{
ShaderClosure *sc = closure_alloc(sd, size, CLOSURE_NONE_ID, weight);
kernel_assert(isfinite3_safe(weight));
if (sc == NULL)
return NULL;
const float sample_weight = fabsf(average(weight));
float sample_weight = fabsf(average(weight));
sc->sample_weight = sample_weight;
return (sample_weight >= CLOSURE_WEIGHT_CUTOFF) ? sc : NULL;
/* Use comparison this way to help dealing with non-finite weight: if the average is not finite
* we will not allocate new closure. */
if (sample_weight >= CLOSURE_WEIGHT_CUTOFF) {
ShaderClosure *sc = closure_alloc(sd, size, CLOSURE_NONE_ID, weight);
if (sc == NULL) {
return NULL;
}
sc->sample_weight = sample_weight;
return sc;
}
return NULL;
}
#ifdef __OSL__
@ -73,17 +83,27 @@ ccl_device_inline ShaderClosure *bsdf_alloc_osl(ShaderData *sd,
float3 weight,
void *data)
{
ShaderClosure *sc = closure_alloc(sd, size, CLOSURE_NONE_ID, weight);
kernel_assert(isfinite3_safe(weight));
if (!sc)
return NULL;
const float sample_weight = fabsf(average(weight));
memcpy((void *)sc, data, size);
/* Use comparison this way to help dealing with non-finite weight: if the average is not finite
* we will not allocate new closure. */
if (sample_weight >= CLOSURE_WEIGHT_CUTOFF) {
ShaderClosure *sc = closure_alloc(sd, size, CLOSURE_NONE_ID, weight);
if (!sc) {
return NULL;
}
float sample_weight = fabsf(average(weight));
sc->weight = weight;
sc->sample_weight = sample_weight;
return (sample_weight >= CLOSURE_WEIGHT_CUTOFF) ? sc : NULL;
memcpy((void *)sc, data, size);
sc->weight = weight;
sc->sample_weight = sample_weight;
return sc;
}
return NULL;
}
#endif

View File

@ -370,10 +370,13 @@ ccl_device void svm_node_tangent(KernelGlobals *kg, ShaderData *sd, float *stack
if (direction_type == NODE_TANGENT_UVMAP) {
/* UV map */
if (desc.offset == ATTR_STD_NOT_FOUND)
tangent = make_float3(0.0f, 0.0f, 0.0f);
else
if (desc.offset == ATTR_STD_NOT_FOUND) {
stack_store_float3(stack, tangent_offset, zero_float3());
return;
}
else {
tangent = attribute_value;
}
}
else {
/* radial */

View File

@ -46,6 +46,12 @@ CCL_NAMESPACE_BEGIN
/* Geometry */
PackFlags operator|=(PackFlags &pack_flags, uint32_t value)
{
pack_flags = (PackFlags)((uint32_t)pack_flags | value);
return pack_flags;
}
NODE_ABSTRACT_DEFINE(Geometry)
{
NodeType *type = NodeType::add("geometry_base", NULL);
@ -1236,7 +1242,16 @@ void GeometryManager::device_update_bvh(Device *device,
const bool can_refit = scene->bvh != nullptr &&
(bparams.bvh_layout == BVHLayout::BVH_LAYOUT_OPTIX);
const bool pack_all = scene->bvh == nullptr;
PackFlags pack_flags = PackFlags::PACK_NONE;
if (scene->bvh == nullptr) {
pack_flags |= PackFlags::PACK_ALL;
}
if (dscene->prim_visibility.is_modified()) {
pack_flags |= PackFlags::PACK_VISIBILITY;
}
BVH *bvh = scene->bvh;
if (!scene->bvh) {
@ -1274,10 +1289,14 @@ void GeometryManager::device_update_bvh(Device *device,
pack.root_index = -1;
if (!pack_all) {
if (pack_flags != PackFlags::PACK_ALL) {
/* if we do not need to recreate the BVH, then only the vertices are updated, so we can
* safely retake the memory */
dscene->prim_tri_verts.give_data(pack.prim_tri_verts);
if ((pack_flags & PackFlags::PACK_VISIBILITY) != 0) {
dscene->prim_visibility.give_data(pack.prim_visibility);
}
}
else {
/* It is not strictly necessary to skip those resizes we if do not have to repack, as the OS
@ -1306,13 +1325,21 @@ void GeometryManager::device_update_bvh(Device *device,
// Iterate over scene mesh list instead of objects, since 'optix_prim_offset' was calculated
// based on that list, which may be ordered differently from the object list.
foreach (Geometry *geom, scene->geometry) {
if (!pack_all && !geom->is_modified()) {
/* Make a copy of the pack_flags so the current geometry's flags do not pollute the others'.
*/
PackFlags geom_pack_flags = pack_flags;
if (geom->is_modified()) {
geom_pack_flags |= PackFlags::PACK_VERTICES;
}
if (geom_pack_flags == PACK_NONE) {
continue;
}
const pair<int, uint> &info = geometry_to_object_info[geom];
pool.push(function_bind(
&Geometry::pack_primitives, geom, &pack, info.first, info.second, pack_all));
&Geometry::pack_primitives, geom, &pack, info.first, info.second, geom_pack_flags));
}
pool.wait_work();
}
@ -1347,7 +1374,7 @@ void GeometryManager::device_update_bvh(Device *device,
dscene->prim_type.steal_data(pack.prim_type);
dscene->prim_type.copy_to_device();
}
if (pack.prim_visibility.size() && (dscene->prim_visibility.need_realloc() || has_bvh2_layout)) {
if (pack.prim_visibility.size() && (dscene->prim_visibility.is_modified() || has_bvh2_layout)) {
dscene->prim_visibility.steal_data(pack.prim_visibility);
dscene->prim_visibility.copy_to_device();
}
@ -1595,6 +1622,10 @@ void GeometryManager::device_update_preprocess(Device *device, Scene *scene, Pro
}
}
if ((update_flags & VISIBILITY_MODIFIED) != 0) {
dscene->prim_visibility.tag_modified();
}
if (device_update_flags & ATTR_FLOAT_NEEDS_REALLOC) {
dscene->attributes_map.tag_realloc();
dscene->attributes_float.tag_realloc();
@ -1921,7 +1952,8 @@ void GeometryManager::device_update(Device *device,
* Also update the BVH if the transformations change, we cannot rely on tagging the Geometry
* as modified in this case, as we may accumulate displacement if the vertices do not also
* change. */
bool need_update_scene_bvh = (scene->bvh == nullptr || (update_flags & TRANSFORM_MODIFIED) != 0);
bool need_update_scene_bvh = (scene->bvh == nullptr ||
(update_flags & (TRANSFORM_MODIFIED | VISIBILITY_MODIFIED)) != 0);
{
scoped_callback_timer timer([scene](double time) {
if (scene->update_stats) {

View File

@ -43,6 +43,24 @@ class Shader;
class Volume;
struct PackedBVH;
/* Flags used to determine which geometry data need to be packed. */
enum PackFlags : uint32_t {
PACK_NONE = 0u,
/* Pack the geometry information (e.g. triangle or curve keys indices). */
PACK_GEOMETRY = (1u << 0),
/* Pack the vertices, for Meshes and Volumes' bounding meshes. */
PACK_VERTICES = (1u << 1),
/* Pack the visibility flags for each triangle or curve. */
PACK_VISIBILITY = (1u << 2),
PACK_ALL = (PACK_GEOMETRY | PACK_VERTICES | PACK_VISIBILITY),
};
PackFlags operator|=(PackFlags &pack_flags, uint32_t value);
/* Geometry
*
* Base class for geometric types like Mesh and Hair. */
@ -126,7 +144,10 @@ class Geometry : public Node {
int n,
int total);
virtual void pack_primitives(PackedBVH *pack, int object, uint visibility, bool pack_all) = 0;
virtual void pack_primitives(PackedBVH *pack,
int object,
uint visibility,
PackFlags pack_flags) = 0;
/* Check whether the geometry should have own BVH built separately. Briefly,
* own BVH is needed for geometry, if:
@ -191,6 +212,8 @@ class GeometryManager {
TRANSFORM_MODIFIED = (1 << 10),
VISIBILITY_MODIFIED = (1 << 11),
/* tag everything in the manager for an update */
UPDATE_ALL = ~0u,

View File

@ -494,38 +494,47 @@ void Hair::pack_curves(Scene *scene,
}
}
void Hair::pack_primitives(PackedBVH *pack, int object, uint visibility, bool pack_all)
void Hair::pack_primitives(PackedBVH *pack, int object, uint visibility, PackFlags pack_flags)
{
if (curve_first_key.empty())
return;
/* If the BVH does not have to be recreated, we can bail out. */
if (!pack_all) {
return;
/* Separate loop as other arrays are not initialized if their packing is not required. */
if ((pack_flags & PACK_VISIBILITY) != 0) {
unsigned int *prim_visibility = &pack->prim_visibility[optix_prim_offset];
size_t index = 0;
for (size_t j = 0; j < num_curves(); ++j) {
Curve curve = get_curve(j);
for (size_t k = 0; k < curve.num_segments(); ++k, ++index) {
prim_visibility[index] = visibility;
}
}
}
unsigned int *prim_tri_index = &pack->prim_tri_index[optix_prim_offset];
int *prim_type = &pack->prim_type[optix_prim_offset];
unsigned int *prim_visibility = &pack->prim_visibility[optix_prim_offset];
int *prim_index = &pack->prim_index[optix_prim_offset];
int *prim_object = &pack->prim_object[optix_prim_offset];
// 'pack->prim_time' is unused by Embree and OptiX
if ((pack_flags & PACK_GEOMETRY) != 0) {
unsigned int *prim_tri_index = &pack->prim_tri_index[optix_prim_offset];
int *prim_type = &pack->prim_type[optix_prim_offset];
int *prim_index = &pack->prim_index[optix_prim_offset];
int *prim_object = &pack->prim_object[optix_prim_offset];
// 'pack->prim_time' is unused by Embree and OptiX
uint type = has_motion_blur() ?
((curve_shape == CURVE_RIBBON) ? PRIMITIVE_MOTION_CURVE_RIBBON :
PRIMITIVE_MOTION_CURVE_THICK) :
((curve_shape == CURVE_RIBBON) ? PRIMITIVE_CURVE_RIBBON : PRIMITIVE_CURVE_THICK);
uint type = has_motion_blur() ?
((curve_shape == CURVE_RIBBON) ? PRIMITIVE_MOTION_CURVE_RIBBON :
PRIMITIVE_MOTION_CURVE_THICK) :
((curve_shape == CURVE_RIBBON) ? PRIMITIVE_CURVE_RIBBON :
PRIMITIVE_CURVE_THICK);
size_t index = 0;
for (size_t j = 0; j < num_curves(); ++j) {
Curve curve = get_curve(j);
for (size_t k = 0; k < curve.num_segments(); ++k, ++index) {
prim_tri_index[index] = -1;
prim_type[index] = PRIMITIVE_PACK_SEGMENT(type, k);
prim_visibility[index] = visibility;
// Each curve segment points back to its curve index
prim_index[index] = j + prim_offset;
prim_object[index] = object;
size_t index = 0;
for (size_t j = 0; j < num_curves(); ++j) {
Curve curve = get_curve(j);
for (size_t k = 0; k < curve.num_segments(); ++k, ++index) {
prim_tri_index[index] = -1;
prim_type[index] = PRIMITIVE_PACK_SEGMENT(type, k);
// Each curve segment points back to its curve index
prim_index[index] = j + prim_offset;
prim_object[index] = object;
}
}
}
}

View File

@ -146,7 +146,10 @@ class Hair : public Geometry {
/* BVH */
void pack_curves(Scene *scene, float4 *curve_key_co, float4 *curve_data, size_t curvekey_offset);
void pack_primitives(PackedBVH *pack, int object, uint visibility, bool pack_all) override;
void pack_primitives(PackedBVH *pack,
int object,
uint visibility,
PackFlags pack_flags) override;
};
CCL_NAMESPACE_END

View File

@ -805,7 +805,7 @@ void Mesh::pack_patches(uint *patch_data, uint vert_offset, uint face_offset, ui
}
}
void Mesh::pack_primitives(ccl::PackedBVH *pack, int object, uint visibility, bool pack_all)
void Mesh::pack_primitives(ccl::PackedBVH *pack, int object, uint visibility, PackFlags pack_flags)
{
if (triangles.empty())
return;
@ -819,28 +819,38 @@ void Mesh::pack_primitives(ccl::PackedBVH *pack, int object, uint visibility, bo
uint type = has_motion_blur() ? PRIMITIVE_MOTION_TRIANGLE : PRIMITIVE_TRIANGLE;
if (pack_all) {
/* Use optix_prim_offset for indexing as those arrays also contain data for Hair geometries. */
unsigned int *prim_tri_index = &pack->prim_tri_index[optix_prim_offset];
int *prim_type = &pack->prim_type[optix_prim_offset];
/* Separate loop as other arrays are not initialized if their packing is not required. */
if ((pack_flags & PackFlags::PACK_VISIBILITY) != 0) {
unsigned int *prim_visibility = &pack->prim_visibility[optix_prim_offset];
int *prim_index = &pack->prim_index[optix_prim_offset];
int *prim_object = &pack->prim_object[optix_prim_offset];
for (size_t k = 0; k < num_prims; ++k) {
prim_tri_index[k] = (prim_offset + k) * 3;
prim_type[k] = type;
prim_index[k] = prim_offset + k;
prim_object[k] = object;
prim_visibility[k] = visibility;
}
}
for (size_t k = 0; k < num_prims; ++k) {
const Mesh::Triangle t = get_triangle(k);
prim_tri_verts[k * 3] = float3_to_float4(verts[t.v[0]]);
prim_tri_verts[k * 3 + 1] = float3_to_float4(verts[t.v[1]]);
prim_tri_verts[k * 3 + 2] = float3_to_float4(verts[t.v[2]]);
if ((pack_flags & PackFlags::PACK_GEOMETRY) != 0) {
/* Use optix_prim_offset for indexing as those arrays also contain data for Hair geometries. */
unsigned int *prim_tri_index = &pack->prim_tri_index[optix_prim_offset];
int *prim_type = &pack->prim_type[optix_prim_offset];
int *prim_index = &pack->prim_index[optix_prim_offset];
int *prim_object = &pack->prim_object[optix_prim_offset];
for (size_t k = 0; k < num_prims; ++k) {
if ((pack_flags & PackFlags::PACK_GEOMETRY) != 0) {
prim_tri_index[k] = (prim_offset + k) * 3;
prim_type[k] = type;
prim_index[k] = prim_offset + k;
prim_object[k] = object;
}
}
}
if ((pack_flags & PackFlags::PACK_VERTICES) != 0) {
for (size_t k = 0; k < num_prims; ++k) {
const Mesh::Triangle t = get_triangle(k);
prim_tri_verts[k * 3] = float3_to_float4(verts[t.v[0]]);
prim_tri_verts[k * 3 + 1] = float3_to_float4(verts[t.v[1]]);
prim_tri_verts[k * 3 + 2] = float3_to_float4(verts[t.v[2]]);
}
}
}

View File

@ -232,7 +232,10 @@ class Mesh : public Geometry {
size_t tri_offset);
void pack_patches(uint *patch_data, uint vert_offset, uint face_offset, uint corner_offset);
void pack_primitives(PackedBVH *pack, int object, uint visibility, bool pack_all) override;
void pack_primitives(PackedBVH *pack,
int object,
uint visibility,
PackFlags pack_flags) override;
void tessellate(DiagSplit *split);

View File

@ -220,6 +220,10 @@ void Object::tag_update(Scene *scene)
flag |= ObjectManager::TRANSFORM_MODIFIED;
}
if (visibility_is_modified()) {
flag |= ObjectManager::VISIBILITY_MODIFIED;
}
foreach (Node *node, geometry->get_used_shaders()) {
Shader *shader = static_cast<Shader *>(node);
if (shader->get_use_mis() && shader->has_surface_emission)
@ -914,6 +918,10 @@ void ObjectManager::tag_update(Scene *scene, uint32_t flag)
geometry_flag |= GeometryManager::TRANSFORM_MODIFIED;
}
if ((flag & VISIBILITY_MODIFIED) != 0) {
geometry_flag |= GeometryManager::VISIBILITY_MODIFIED;
}
scene->geometry_manager->tag_update(scene, geometry_flag);
}

View File

@ -134,6 +134,7 @@ class ObjectManager {
OBJECT_MODIFIED = (1 << 5),
HOLDOUT_MODIFIED = (1 << 6),
TRANSFORM_MODIFIED = (1 << 7),
VISIBILITY_MODIFIED = (1 << 8),
/* tag everything in the manager for an update */
UPDATE_ALL = ~0u,

View File

@ -72,9 +72,9 @@ 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;
}
@ -83,7 +83,7 @@ class OSLShaderManager : public ShaderManager {
DeviceScene *dscene,
Scene *scene,
Progress &progress) override;
void device_free(Device *device, DeviceScene *dscene, Scene *scene);
void device_free(Device *device, DeviceScene *dscene, Scene *scene) override;
/* osl compile and query */
static bool osl_compile(const string &inputfile, const string &outputfile);

View File

@ -44,13 +44,13 @@ class SVMShaderManager : public ShaderManager {
SVMShaderManager();
~SVMShaderManager();
void reset(Scene *scene);
void reset(Scene *scene) override;
void device_update_specific(Device *device,
DeviceScene *dscene,
Scene *scene,
Progress &progress) override;
void device_free(Device *device, DeviceScene *dscene, Scene *scene);
void device_free(Device *device, DeviceScene *dscene, Scene *scene) override;
protected:
void device_update_shader(Scene *scene,

View File

@ -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

View File

@ -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
}

View File

@ -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,

View File

@ -634,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);
@ -665,6 +667,7 @@ typedef struct {
typedef struct {
GHOST_XrPose base_pose;
GHOST_XrSessionCreateFn create_fn;
GHOST_XrSessionExitFn exit_fn;
void *exit_customdata;
} GHOST_XrSessionBeginInfo;
@ -691,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 */

View File

@ -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"
@ -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 */

View File

@ -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_);
}
}
};

View File

@ -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 &amplitude)
{
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);
}
}
/** \} */

View File

@ -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 &amplitude);
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;
};
/* -------------------------------------------------------------------- */

View File

@ -246,7 +246,7 @@ void GHOST_XrContext::dispatchErrorMessage(const GHOST_XrException *exception) c
{
GHOST_XrError error;
error.user_message = exception->m_msg;
error.user_message = exception->m_msg.data();
error.customdata = s_error_handler_customdata;
if (isDebugMode()) {
@ -374,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());
}
}
}
@ -484,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;
@ -534,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)
{

View File

@ -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;

View File

@ -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;
};

View File

@ -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 {
@ -71,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));
@ -177,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);
@ -194,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()
@ -223,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;
@ -349,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. */
@ -501,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 &amplitude)
{
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 */

View File

@ -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 &amplitude);
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. */

View File

@ -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 coodinate 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;
}

View File

@ -29,10 +29,10 @@ class Version:
def get_download_file_names(version: Version):
yield f"blender-{version}-linux64.tar.xz"
yield f"blender-{version}-macOS.dmg"
yield f"blender-{version}-windows64.msi"
yield f"blender-{version}-windows64.zip"
yield f"blender-{version}-linux-x86_64.tar.xz"
yield f"blender-{version}-darwin-x86_64.dmg"
yield f"blender-{version}-windows-amd64.msi"
yield f"blender-{version}-windows-amd64.zip"
def get_download_url(version: Version, file_name: str) -> str:

@ -1 +1 @@
Subproject commit bb16aba5bd3873794eefe167497118b6063b9a85
Subproject commit 4fcdbfe7c20edfc1204c0aa46c98ea25354abcd9

View File

@ -543,22 +543,6 @@ def module_bl_info(mod, info_basis=None):
if not addon_info["name"]:
addon_info["name"] = mod.__name__
# Replace 'wiki_url' with 'doc_url'.
doc_url = addon_info.pop("wiki_url", None)
if doc_url is not None:
# Unlikely, but possible that both are set.
if not addon_info["doc_url"]:
addon_info["doc_url"] = doc_url
if _bpy.app.debug:
print(
"Warning: add-on \"%s\": 'wiki_url' in 'bl_info' "
"is deprecated please use 'doc_url' instead!\n"
" %s" % (
addon_info['name'],
getattr(mod, "__file__", None),
)
)
doc_url = addon_info["doc_url"]
if doc_url:
doc_url_prefix = "{BLENDER_MANUAL_URL}"

View File

@ -21,7 +21,7 @@
def url_prefill_from_blender(addon_info=None):
import bpy
import bgl
import gpu
import struct
import platform
import urllib.parse
@ -38,9 +38,9 @@ def url_prefill_from_blender(addon_info=None):
)
fh.write(
"Graphics card: %s %s %s\n" % (
bgl.glGetString(bgl.GL_RENDERER),
bgl.glGetString(bgl.GL_VENDOR),
bgl.glGetString(bgl.GL_VERSION),
gpu.platform.renderer_get(),
gpu.platform.vendor_get(),
gpu.platform.version_get(),
)
)
fh.write(

View File

@ -370,7 +370,7 @@ def module_names(path, recursive=False):
def basename(path):
"""
Equivalent to os.path.basename, but skips a "//" prefix.
Equivalent to ``os.path.basename``, but skips a "//" prefix.
Use for Windows compatibility.
"""

View File

@ -235,7 +235,7 @@ def draw(layout, context, context_member, property_type, use_edit=True):
assert(isinstance(rna_item, property_type))
items = rna_item.items()
items = list(rna_item.items())
items.sort()
# TODO: Allow/support adding new custom props to overrides.

View File

@ -28,7 +28,7 @@ def write_sysinfo(filepath):
import subprocess
import bpy
import bgl
import gpu
# pretty repr
def prepr(v):
@ -190,46 +190,29 @@ def write_sysinfo(filepath):
if bpy.app.background:
output.write("\nOpenGL: missing, background mode\n")
else:
output.write(title("OpenGL"))
version = bgl.glGetString(bgl.GL_RENDERER)
output.write("renderer:\t%r\n" % version)
output.write("vendor:\t\t%r\n" % (bgl.glGetString(bgl.GL_VENDOR)))
output.write("version:\t%r\n" % (bgl.glGetString(bgl.GL_VERSION)))
output.write(title("GPU"))
output.write("renderer:\t%r\n" % gpu.platform.renderer_get())
output.write("vendor:\t\t%r\n" % gpu.platform.vendor_get())
output.write("version:\t%r\n" % gpu.platform.version_get())
output.write("extensions:\n")
limit = bgl.Buffer(bgl.GL_INT, 1)
bgl.glGetIntegerv(bgl.GL_NUM_EXTENSIONS, limit)
glext = []
for i in range(limit[0]):
glext.append(bgl.glGetStringi(bgl.GL_EXTENSIONS, i))
glext = sorted(glext)
glext = sorted(gpu.capabilities.extensions_get())
for l in glext:
output.write("\t%s\n" % l)
output.write(title("Implementation Dependent OpenGL Limits"))
bgl.glGetIntegerv(bgl.GL_MAX_ELEMENTS_VERTICES, limit)
output.write("Maximum DrawElements Vertices:\t%d\n" % limit[0])
bgl.glGetIntegerv(bgl.GL_MAX_ELEMENTS_INDICES, limit)
output.write("Maximum DrawElements Indices:\t%d\n" % limit[0])
output.write(title("Implementation Dependent GPU Limits"))
output.write("Maximum Batch Vertices:\t%d\n" % gpu.capabilities.max_batch_vertices_get())
output.write("Maximum Batch Indices:\t%d\n" % gpu.capabilities.max_batch_indices_get())
output.write("\nGLSL:\n")
bgl.glGetIntegerv(bgl.GL_MAX_VARYING_FLOATS, limit)
output.write("Maximum Varying Floats:\t%d\n" % limit[0])
bgl.glGetIntegerv(bgl.GL_MAX_VERTEX_ATTRIBS, limit)
output.write("Maximum Vertex Attributes:\t%d\n" % limit[0])
bgl.glGetIntegerv(bgl.GL_MAX_VERTEX_UNIFORM_COMPONENTS, limit)
output.write("Maximum Vertex Uniform Components:\t%d\n" % limit[0])
bgl.glGetIntegerv(bgl.GL_MAX_FRAGMENT_UNIFORM_COMPONENTS, limit)
output.write("Maximum Fragment Uniform Components:\t%d\n" % limit[0])
bgl.glGetIntegerv(bgl.GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, limit)
output.write("Maximum Vertex Image Units:\t%d\n" % limit[0])
bgl.glGetIntegerv(bgl.GL_MAX_TEXTURE_IMAGE_UNITS, limit)
output.write("Maximum Fragment Image Units:\t%d\n" % limit[0])
bgl.glGetIntegerv(bgl.GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, limit)
output.write("Maximum Pipeline Image Units:\t%d\n" % limit[0])
output.write("Maximum Varying Floats:\t%d\n" % gpu.capabilities.max_varying_floats_get())
output.write("Maximum Vertex Attributes:\t%d\n" % gpu.capabilities.max_vertex_attribs_get())
output.write("Maximum Vertex Uniform Components:\t%d\n" % gpu.capabilities.max_uniforms_vert_get())
output.write("Maximum Fragment Uniform Components:\t%d\n" % gpu.capabilities.max_uniforms_frag_get())
output.write("Maximum Vertex Image Units:\t%d\n" % gpu.capabilities.max_textures_vert_get())
output.write("Maximum Fragment Image Units:\t%d\n" % gpu.capabilities.max_textures_frag_get())
output.write("Maximum Pipeline Image Units:\t%d\n" % gpu.capabilities.max_textures_get())
if bpy.app.build_options.cycles:
import cycles

View File

@ -3333,8 +3333,6 @@ def km_weight_paint(params):
*_template_paint_radial_control("weight_paint"),
("wm.context_toggle", {"type": 'M', "value": 'PRESS'},
{"properties": [("data_path", 'weight_paint_object.data.use_paint_mask')]}),
("wm.context_toggle", {"type": 'V', "value": 'PRESS'},
{"properties": [("data_path", 'weight_paint_object.data.use_paint_mask_vertex')]}),
("wm.context_toggle", {"type": 'S', "value": 'PRESS', "shift": True},
{"properties": [("data_path", 'tool_settings.weight_paint.brush.use_smooth_stroke')]}),
*_template_items_context_panel("VIEW3D_PT_paint_weight_context_menu", {"type": 'RIGHTMOUSE', "value": 'PRESS'}),

View File

@ -590,7 +590,7 @@ class PREFERENCES_OT_addon_install(Operator):
name="Target Path",
items=(
('DEFAULT', "Default", ""),
('PREFS', "User Prefs", ""),
('PREFS', "Preferences", ""),
),
)

View File

@ -113,8 +113,8 @@ class GPENCIL_MT_layer_context_menu(Menu):
layout.operator("gpencil.layer_merge", icon='SORT_ASC', text="Merge Down")
layout.separator()
layout.menu("VIEW3D_MT_gpencil_append_active_layer")
layout.menu("VIEW3D_MT_gpencil_append_all_layers")
layout.operator("gpencil.layer_duplicate_object", text="Copy Layer to Selected").only_active=True
layout.operator("gpencil.layer_duplicate_object", text="Copy All Layers to Selected").only_active=False
class DATA_PT_gpencil_layers(DataButtonsPanel, Panel):

View File

@ -46,16 +46,18 @@ class GPENCIL_MT_material_context_menu(Menu):
layout.separator()
layout.operator("object.material_slot_remove_unused")
layout.operator("gpencil.stroke_merge_material", text="Merge Similar")
layout.separator()
layout.operator("gpencil.material_to_vertex_color", text="Convert Materials to Vertex Color")
layout.operator("gpencil.extract_palette_vertex", text="Extract Palette from Vertex Color")
layout.separator()
layout.menu("VIEW3D_MT_gpencil_append_active_material")
layout.menu("VIEW3D_MT_gpencil_append_all_materials")
layout.operator("gpencil.materials_copy_to_object", text="Copy Material to Selected").only_active = True
layout.operator("gpencil.materials_copy_to_object", text="Copy All Materials to Selected").only_active = False
layout.separator()
layout.operator("gpencil.stroke_merge_material", text="Merge Similar")
layout.operator("object.material_slot_remove_unused")
class GPENCIL_UL_matslots(UIList):

View File

@ -162,13 +162,13 @@ class OUTLINER_MT_collection_view_layer(Menu):
layout.operator("outliner.collection_exclude_set")
layout.operator("outliner.collection_exclude_clear")
layout.operator("outliner.collection_holdout_set")
layout.operator("outliner.collection_holdout_clear")
if context.engine == 'CYCLES':
layout.operator("outliner.collection_indirect_only_set")
layout.operator("outliner.collection_indirect_only_clear")
layout.operator("outliner.collection_holdout_set")
layout.operator("outliner.collection_holdout_clear")
class OUTLINER_MT_collection_visibility(Menu):
bl_label = "Visibility"

View File

@ -4956,75 +4956,6 @@ class VIEW3D_MT_assign_material(Menu):
icon='LAYER_ACTIVE' if mat == mat_active else 'BLANK1').material = mat.name
def gpencil_layer_append_menu_items(context, layout, only_active):
done = False
view_layer = context.view_layer
obact = context.active_object
gpl = context.active_gpencil_layer
done = False
if gpl is not None:
for ob in view_layer.objects:
if ob.type == 'GPENCIL' and ob != obact:
op = layout.operator("gpencil.layer_duplicate_object", text=ob.name)
op.object = ob.name
op.only_active = only_active
done = True
if done is False:
layout.label(text="No destination object", icon='ERROR')
else:
layout.label(text="No layer to copy", icon='ERROR')
class VIEW3D_MT_gpencil_append_active_layer(Menu):
bl_label = "Append Active Layer to Object"
def draw(self, context):
layout = self.layout
gpencil_layer_append_menu_items(context, layout, True)
class VIEW3D_MT_gpencil_append_all_layers(Menu):
bl_label = "Append All Layers to Object"
def draw(self, context):
layout = self.layout
gpencil_layer_append_menu_items(context, layout, False)
def gpencil_material_menu_items(context, layout, only_selected):
done = False
view_layer = context.view_layer
obact = context.active_object
for ob in view_layer.objects:
if ob.type == 'GPENCIL' and ob != obact:
op = layout.operator("gpencil.materials_append_to_object", text=ob.name)
op.object = ob.name
op.only_selected = only_selected
done = True
if done is False:
layout.label(text="No destination object", icon='ERROR')
class VIEW3D_MT_gpencil_append_active_material(Menu):
bl_label = "Append Active Material to Object"
def draw(self, context):
layout = self.layout
gpencil_material_menu_items(context, layout, True)
class VIEW3D_MT_gpencil_append_all_materials(Menu):
bl_label = "Append All Materials to Object"
def draw(self, context):
layout = self.layout
gpencil_material_menu_items(context, layout, False)
class VIEW3D_MT_edit_gpencil(Menu):
bl_label = "Grease Pencil"
@ -7689,10 +7620,6 @@ classes = (
VIEW3D_MT_weight_gpencil,
VIEW3D_MT_gpencil_animation,
VIEW3D_MT_gpencil_simplify,
VIEW3D_MT_gpencil_append_active_layer,
VIEW3D_MT_gpencil_append_all_layers,
VIEW3D_MT_gpencil_append_active_material,
VIEW3D_MT_gpencil_append_all_materials,
VIEW3D_MT_gpencil_autoweights,
VIEW3D_MT_gpencil_edit_context_menu,
VIEW3D_MT_edit_curve,

View File

@ -488,6 +488,7 @@ geometry_node_categories = [
NodeItem("GeometryNodeAttributeClamp"),
NodeItem("GeometryNodeAttributeCompare"),
NodeItem("GeometryNodeAttributeConvert"),
NodeItem("GeometryNodeAttributeCache"),
NodeItem("GeometryNodeAttributeCurveMap"),
NodeItem("GeometryNodeAttributeFill"),
NodeItem("GeometryNodeAttributeMix"),

View File

@ -27,6 +27,80 @@
#include "BLI_color.hh"
#include "BLI_float2.hh"
#include "BLI_float3.hh"
#include "BLI_function_ref.hh"
/**
* Contains information about an attribute in a geometry component.
* More information can be added in the future. E.g. whether the attribute is builtin and how it is
* stored (uv map, vertex group, ...).
*/
struct AttributeMetaData {
AttributeDomain domain;
CustomDataType data_type;
};
/**
* Base class for the attribute intializer types described below.
*/
struct AttributeInit {
enum class Type {
Default,
VArray,
MoveArray,
};
Type type;
AttributeInit(const Type type) : type(type)
{
}
};
/**
* Create an attribute using the default value for the data type.
* The default values may depend on the attribute provider implementation.
*/
struct AttributeInitDefault : public AttributeInit {
AttributeInitDefault() : AttributeInit(Type::Default)
{
}
};
/**
* Create an attribute by copying data from an existing virtual array. The virtual array
* must have the same type as the newly created attribute.
*
* Note that this can be used to fill the new attribute with the default
*/
struct AttributeInitVArray : public AttributeInit {
const blender::fn::GVArray *varray;
AttributeInitVArray(const blender::fn::GVArray *varray)
: AttributeInit(Type::VArray), varray(varray)
{
}
};
/**
* Create an attribute with a by passing ownership of a pre-allocated contiguous array of data.
* Sometimes data is created before a geometry component is available. In that case, it's
* preferable to move data directly to the created attribute to avoid a new allocation and a copy.
*
* Note that this will only have a benefit for attributes that are stored directly as contiguous
* arrays, so not for some built-in attributes.
*
* The array must be allocated with MEM_*, since `attribute_try_create` will free the array if it
* can't be used directly, and that is generally how Blender expects custom data to be allocated.
*/
struct AttributeInitMove : public AttributeInit {
void *data = nullptr;
AttributeInitMove(void *data) : AttributeInit(Type::MoveArray), data(data)
{
}
};
/* Returns false when the iteration should be stopped. */
using AttributeForeachCallback = blender::FunctionRef<bool(blender::StringRefNull attribute_name,
const AttributeMetaData &meta_data)>;
namespace blender::bke {

View File

@ -227,8 +227,10 @@ template<typename T> class SimpleMixer {
}
};
/** This mixer accumulates values in a type that is different from the one that is mixed. Some
* types cannot encode the floating point weights in their values (e.g. int and bool). */
/**
* This mixer accumulates values in a type that is different from the one that is mixed.
* Some types cannot encode the floating point weights in their values (e.g. int and bool).
*/
template<typename T, typename AccumulationT, T (*ConvertToT)(const AccumulationT &value)>
class SimpleMixerWithAccumulationType {
private:

View File

@ -59,6 +59,7 @@ typedef enum {
BKE_CB_EVT_VERSION_UPDATE,
BKE_CB_EVT_LOAD_FACTORY_USERDEF_POST,
BKE_CB_EVT_LOAD_FACTORY_STARTUP_POST,
BKE_CB_EVT_XR_SESSION_START_PRE,
BKE_CB_EVT_TOT,
} eCbEvent;

View File

@ -25,7 +25,6 @@
#include "BLI_float3.hh"
#include "BLI_float4x4.hh"
#include "BLI_function_ref.hh"
#include "BLI_hash.hh"
#include "BLI_map.hh"
#include "BLI_set.hh"
@ -57,79 +56,6 @@ class ComponentAttributeProviders;
class GeometryComponent;
/**
* Contains information about an attribute in a geometry component.
* More information can be added in the future. E.g. whether the attribute is builtin and how it is
* stored (uv map, vertex group, ...).
*/
struct AttributeMetaData {
AttributeDomain domain;
CustomDataType data_type;
};
/* Returns false when the iteration should be stopped. */
using AttributeForeachCallback = blender::FunctionRef<bool(blender::StringRefNull attribute_name,
const AttributeMetaData &meta_data)>;
/**
* Base class for the attribute intializer types described below.
*/
struct AttributeInit {
enum class Type {
Default,
VArray,
MoveArray,
};
Type type;
AttributeInit(const Type type) : type(type)
{
}
};
/**
* Create an attribute using the default value for the data type.
* The default values may depend on the attribute provider implementation.
*/
struct AttributeInitDefault : public AttributeInit {
AttributeInitDefault() : AttributeInit(Type::Default)
{
}
};
/**
* Create an attribute by copying data from an existing virtual array. The virtual array
* must have the same type as the newly created attribute.
*
* Note that this can be used to fill the new attribute with the default
*/
struct AttributeInitVArray : public AttributeInit {
const blender::fn::GVArray *varray;
AttributeInitVArray(const blender::fn::GVArray *varray)
: AttributeInit(Type::VArray), varray(varray)
{
}
};
/**
* Create an attribute with a by passing ownership of a pre-allocated contiguous array of data.
* Sometimes data is created before a geometry component is available. In that case, it's
* preferable to move data directly to the created attribute to avoid a new allocation and a copy.
*
* Note that this will only have a benefit for attributes that are stored directly as contiguous
* arrays, so not for some built-in attributes.
*
* The array must be allocated with MEM_*, since `attribute_try_create` will free the array if it
* can't be used directly, and that is generally how Blender expects custom data to be allocated.
*/
struct AttributeInitMove : public AttributeInit {
void *data = nullptr;
AttributeInitMove(void *data) : AttributeInit(Type::MoveArray), data(data)
{
}
};
/**
* This is the base class for specialized geometry component types.
*/

View File

@ -108,7 +108,10 @@ void BKE_gpencil_stroke_select_index_reset(struct bGPDstroke *gps);
struct bGPDframe *BKE_gpencil_frame_addnew(struct bGPDlayer *gpl, int cframe);
struct bGPDframe *BKE_gpencil_frame_addcopy(struct bGPDlayer *gpl, int cframe);
struct bGPDlayer *BKE_gpencil_layer_addnew(struct bGPdata *gpd, const char *name, bool setactive);
struct bGPDlayer *BKE_gpencil_layer_addnew(struct bGPdata *gpd,
const char *name,
const bool setactive,
const bool add_to_header);
struct bGPdata *BKE_gpencil_data_addnew(struct Main *bmain, const char name[]);
struct bGPDframe *BKE_gpencil_frame_duplicate(const struct bGPDframe *gpf_src,

View File

@ -325,6 +325,7 @@ int BKE_image_get_tile_from_pos(struct Image *ima,
const float uv[2],
float r_uv[2],
float r_ofs[2]);
int BKE_image_find_nearest_tile(const struct Image *image, const float co[2]);
void BKE_image_get_size(struct Image *image, struct ImageUser *iuser, int *r_width, int *r_height);
void BKE_image_get_size_fl(struct Image *image, struct ImageUser *iuser, float r_size[2]);

View File

@ -95,8 +95,16 @@ struct AvailableAttributeInfo {
};
struct NodeUIStorage {
std::mutex mutex;
blender::Vector<NodeWarning> warnings;
blender::Set<AvailableAttributeInfo> attribute_hints;
NodeUIStorage() = default;
/* Needed because the mutex can't be moved or copied. */
NodeUIStorage(NodeUIStorage &&other)
: warnings(std::move(other.warnings)), attribute_hints(std::move(other.attribute_hints))
{
}
};
struct NodeTreeUIStorage {

View File

@ -502,7 +502,11 @@ typedef struct SculptSession {
/* Total number of polys of the base mesh. */
int totfaces;
/* Face sets store its visibility in the sign of the integer, using the absolute value as the
* Face Set ID. Positive IDs are visible, negative IDs are hidden. */
* Face Set ID. Positive IDs are visible, negative IDs are hidden.
* The 0 ID is not used by the tools or the visibility system, it is just used when creating new
* geometry (the trim tool, for example) to detect which geometry was just added, so it can be
* assigned a valid Face Set after creation. Tools are not intended to run with Face Sets IDs set
* to 0. */
int *face_sets;
/* BMesh for dynamic topology sculpting */

View File

@ -48,10 +48,12 @@ using SplinePtr = std::unique_ptr<Spline>;
* evaluation happens in a layer on top of the evaluated points generated by the derived types.
*
* There are a few methods to evaluate a spline:
* 1. #evaluated_positions and #interpolate_to_evaluated_points give data at the initial
* 1. #evaluated_positions and #interpolate_to_evaluated_points give data for the initial
* evaluated points, depending on the resolution.
* 2. #lookup_evaluated_factor and #lookup_evaluated_factor are meant for one-off lookups
* along the length of a curve.
* 3. #sample_uniform_index_factors returns an array that stores uniform-length samples
* along the spline which can be used to interpolate data from method 1.
*
* Commonly used evaluated data is stored in caches on the spline itself so that operations on
* splines don't need to worry about taking ownership of evaluated data when they don't need to.
@ -64,11 +66,6 @@ class Spline {
Poly,
};
protected:
Type type_;
bool is_cyclic_ = false;
public:
enum NormalCalculationMode {
ZUp,
Minimum,
@ -78,6 +75,9 @@ class Spline {
NormalCalculationMode normal_mode;
protected:
Type type_;
bool is_cyclic_ = false;
/** Direction of the spline at each evaluated point. */
mutable blender::Vector<blender::float3> evaluated_tangents_cache_;
mutable std::mutex tangent_cache_mutex_;
@ -99,7 +99,7 @@ class Spline {
{
}
Spline(Spline &other)
: type_(other.type_), is_cyclic_(other.is_cyclic_), normal_mode(other.normal_mode)
: normal_mode(other.normal_mode), type_(other.type_), is_cyclic_(other.is_cyclic_)
{
}
@ -206,8 +206,12 @@ class BezierSpline final : public Spline {
blender::Vector<HandleType> handle_types_left_;
blender::Vector<HandleType> handle_types_right_;
blender::Vector<blender::float3> handle_positions_left_;
blender::Vector<blender::float3> handle_positions_right_;
/* These are mutable to allow lazy recalculation of #Auto and #Vector handle positions. */
mutable blender::Vector<blender::float3> handle_positions_left_;
mutable blender::Vector<blender::float3> handle_positions_right_;
mutable std::mutex auto_handle_mutex_;
mutable bool auto_handles_dirty_ = true;
/** Start index in evaluated points array for every control point. */
mutable blender::Vector<int> offset_cache_;
@ -286,7 +290,7 @@ class BezierSpline final : public Spline {
int next_control_point_index;
/**
* Linear interpolation weight between the two indices, from 0 to 1.
* Higher means next control point.
* Higher means closer to next control point.
*/
float factor;
};
@ -296,6 +300,7 @@ class BezierSpline final : public Spline {
const blender::fn::GVArray &source_data) const override;
private:
void ensure_auto_handles() const;
void correct_end_tangents() const final;
bool segment_is_vector(const int start_index) const;
void evaluate_bezier_segment(const int index,
@ -316,6 +321,8 @@ class NURBSpline final : public Spline {
EndPoint,
Bezier,
};
/** Method used to recalculate the knots vector when points are added or removed. */
KnotsMode knots_mode;
struct BasisCache {
@ -425,12 +432,10 @@ class NURBSpline final : public Spline {
* points does not change it.
*/
class PolySpline final : public Spline {
public:
blender::Vector<blender::float3> positions_;
blender::Vector<float> radii_;
blender::Vector<float> tilts_;
private:
public:
SplinePtr copy() const final;
PolySpline() : Spline(Type::Poly)
@ -473,14 +478,24 @@ class PolySpline final : public Spline {
* more of the data is stored in the splines, but also just to be different than the name in DNA.
*/
class CurveEval {
private:
blender::Vector<SplinePtr> splines_;
public:
blender::Vector<SplinePtr> splines;
blender::Span<SplinePtr> splines() const;
blender::MutableSpan<SplinePtr> splines();
void add_spline(SplinePtr spline);
void remove_splines(blender::IndexMask mask);
CurveEval *copy();
void translate(const blender::float3 &translation);
void transform(const blender::float4x4 &matrix);
void bounds_min_max(blender::float3 &min, blender::float3 &max, const bool use_evaluated) const;
blender::Array<int> control_point_offsets() const;
blender::Array<int> evaluated_point_offsets() const;
};
std::unique_ptr<CurveEval> curve_eval_from_dna_curve(const Curve &curve);

View File

@ -947,7 +947,7 @@ static bool nlastrips_path_rename_fix(ID *owner_id,
owner_id, prefix, oldName, newName, oldKey, newKey, &strip->act->curves, verify_paths);
}
/* Ignore own F-Curves, since those are local. */
/* Check sub-strips (if metas) */
/* Check sub-strips (if meta-strips). */
is_changed |= nlastrips_path_rename_fix(
owner_id, prefix, oldName, newName, oldKey, newKey, &strip->strips, verify_paths);
}
@ -1177,7 +1177,7 @@ static bool nlastrips_path_remove_fix(const char *prefix, ListBase *strips)
any_removed |= fcurves_path_remove_fix(prefix, &strip->act->curves);
}
/* check sub-strips (if metas) */
/* Check sub-strips (if meta-strips). */
any_removed |= nlastrips_path_remove_fix(prefix, &strip->strips);
}
return any_removed;
@ -1245,7 +1245,7 @@ static void nlastrips_apply_all_curves_cb(ID *id, ListBase *strips, AllFCurvesCb
fcurves_apply_cb(id, &strip->act->curves, wrapper->func, wrapper->user_data);
}
/* check sub-strips (if metas) */
/* Check sub-strips (if meta-strips). */
nlastrips_apply_all_curves_cb(id, &strip->strips, wrapper);
}
}

View File

@ -1040,6 +1040,7 @@ static NlaEvalChannelSnapshot *nlaevalchan_snapshot_new(NlaEvalChannel *nec)
nec_snapshot->channel = nec;
nec_snapshot->length = length;
nlavalidmask_init(&nec_snapshot->blend_domain, length);
nlavalidmask_init(&nec_snapshot->remap_domain, length);
return nec_snapshot;
}
@ -1050,6 +1051,7 @@ static void nlaevalchan_snapshot_free(NlaEvalChannelSnapshot *nec_snapshot)
BLI_assert(!nec_snapshot->is_base);
nlavalidmask_free(&nec_snapshot->blend_domain);
nlavalidmask_free(&nec_snapshot->remap_domain);
MEM_freeN(nec_snapshot);
}
@ -1649,6 +1651,363 @@ static bool nla_combine_quaternion_get_inverted_strip_values(const float lower_v
return true;
}
/* ---------------------- */
/* Assert necs and necs->channel is nonNull. */
static void nlaevalchan_assert_nonNull(NlaEvalChannelSnapshot *necs)
{
UNUSED_VARS_NDEBUG(necs);
BLI_assert(necs != NULL && necs->channel != NULL);
}
/* Assert that the channels given can be blended or combined together. */
static void nlaevalchan_assert_blendOrcombine_compatible(NlaEvalChannelSnapshot *lower_necs,
NlaEvalChannelSnapshot *upper_necs,
NlaEvalChannelSnapshot *blended_necs)
{
UNUSED_VARS_NDEBUG(lower_necs, upper_necs, blended_necs);
BLI_assert(!ELEM(NULL, lower_necs, blended_necs));
BLI_assert(upper_necs == NULL || lower_necs->length == upper_necs->length);
BLI_assert(lower_necs->length == blended_necs->length);
}
/* Assert that the channels given can be blended or combined together as a quaternion. */
static void nlaevalchan_assert_blendOrcombine_compatible_quaternion(
NlaEvalChannelSnapshot *lower_necs,
NlaEvalChannelSnapshot *upper_necs,
NlaEvalChannelSnapshot *blended_necs)
{
nlaevalchan_assert_blendOrcombine_compatible(lower_necs, upper_necs, blended_necs);
BLI_assert(lower_necs->length == 4);
}
static void nlaevalchan_copy_values(NlaEvalChannelSnapshot *dst, NlaEvalChannelSnapshot *src)
{
memcpy(dst->values, src->values, src->length * sizeof(float));
}
/**
* Copies lower necs to blended necs if upper necs is NULL or has zero influence.
* \return true if copied.
*/
static bool nlaevalchan_blendOrcombine_try_copy_lower(NlaEvalChannelSnapshot *lower_necs,
NlaEvalChannelSnapshot *upper_necs,
const float upper_influence,
NlaEvalChannelSnapshot *r_blended_necs)
{
const bool has_influence = !IS_EQF(upper_influence, 0.0f);
if (upper_necs != NULL && has_influence) {
return false;
}
nlaevalchan_copy_values(r_blended_necs, lower_necs);
return true;
}
/**
* Based on blend-mode, blend lower necs with upper necs into blended necs.
*
* Each upper value's blend domain determines whether to blend or to copy directly from lower.
*/
static void nlaevalchan_blend_value(NlaEvalChannelSnapshot *lower_necs,
NlaEvalChannelSnapshot *upper_necs,
const int upper_blendmode,
const float upper_influence,
NlaEvalChannelSnapshot *r_blended_necs)
{
nlaevalchan_assert_blendOrcombine_compatible(lower_necs, upper_necs, r_blended_necs);
if (nlaevalchan_blendOrcombine_try_copy_lower(
lower_necs, upper_necs, upper_influence, r_blended_necs)) {
return;
}
const int length = lower_necs->length;
for (int j = 0; j < length; j++) {
if (!BLI_BITMAP_TEST_BOOL(upper_necs->blend_domain.ptr, j)) {
r_blended_necs->values[j] = lower_necs->values[j];
continue;
}
r_blended_necs->values[j] = nla_blend_value(
upper_blendmode, lower_necs->values[j], upper_necs->values[j], upper_influence);
}
}
/**
* Based on mix-mode, provided by one the necs,
* combines lower necs with upper necs into blended necs.
*
* Each upper value's blend domain determines whether to blend or to copy directly from lower.
*/
static void nlaevalchan_combine_value(NlaEvalChannelSnapshot *lower_necs,
NlaEvalChannelSnapshot *upper_necs,
const float upper_influence,
NlaEvalChannelSnapshot *r_blended_necs)
{
nlaevalchan_assert_blendOrcombine_compatible(lower_necs, upper_necs, r_blended_necs);
if (nlaevalchan_blendOrcombine_try_copy_lower(
lower_necs, upper_necs, upper_influence, r_blended_necs)) {
return;
}
/* Assumes every base is the same. */
float *base_values = lower_necs->channel->base_snapshot.values;
const int length = lower_necs->length;
const char mix_mode = lower_necs->channel->mix_mode;
for (int j = 0; j < length; j++) {
if (!BLI_BITMAP_TEST_BOOL(upper_necs->blend_domain.ptr, j)) {
r_blended_necs->values[j] = lower_necs->values[j];
continue;
}
r_blended_necs->values[j] = nla_combine_value(
mix_mode, base_values[j], lower_necs->values[j], upper_necs->values[j], upper_influence);
}
}
/**
* Quaternion combines lower necs with upper necs into blended necs.
*
* Each upper value's blend domain determines whether to blend or to copy directly
* from lower.
*/
static void nlaevalchan_combine_quaternion(NlaEvalChannelSnapshot *lower_necs,
NlaEvalChannelSnapshot *upper_necs,
const float upper_influence,
NlaEvalChannelSnapshot *r_blended_necs)
{
nlaevalchan_assert_blendOrcombine_compatible_quaternion(lower_necs, upper_necs, r_blended_necs);
if (nlaevalchan_blendOrcombine_try_copy_lower(
lower_necs, upper_necs, upper_influence, r_blended_necs)) {
return;
}
/** No need to check per index. We limit to all or nothing combining for quaternions. */
if (!BLI_BITMAP_TEST_BOOL(upper_necs->blend_domain.ptr, 0)) {
nlaevalchan_copy_values(r_blended_necs, lower_necs);
return;
}
nla_combine_quaternion(
lower_necs->values, upper_necs->values, upper_influence, r_blended_necs->values);
}
/**
* Based on blend-mode and mix-mode, blend lower necs with upper necs into blended necs.
*
* Each upper value's blend domain determines whether to blend or to copy directly
* from lower.
*
* \param lower_necs: Never NULL.
* \param upper_necs: Can be NULL.
* \param upper_blendmode: Enum value in eNlaStrip_Blend_Mode.
* \param upper_influence: Value in range [0, 1].
* \param upper_necs: Never NULL.
*
*/
static void nlaevalchan_blendOrcombine(NlaEvalChannelSnapshot *lower_necs,
NlaEvalChannelSnapshot *upper_necs,
const int upper_blendmode,
const float upper_influence,
NlaEvalChannelSnapshot *r_blended_necs)
{
nlaevalchan_assert_nonNull(r_blended_necs);
switch (upper_blendmode) {
case NLASTRIP_MODE_COMBINE: {
switch (r_blended_necs->channel->mix_mode) {
case NEC_MIX_QUATERNION: {
nlaevalchan_combine_quaternion(lower_necs, upper_necs, upper_influence, r_blended_necs);
return;
}
case NEC_MIX_ADD:
case NEC_MIX_AXIS_ANGLE:
case NEC_MIX_MULTIPLY: {
nlaevalchan_combine_value(lower_necs, upper_necs, upper_influence, r_blended_necs);
return;
}
default:
BLI_assert("Mix mode should've been handled");
}
return;
}
case NLASTRIP_MODE_ADD:
case NLASTRIP_MODE_SUBTRACT:
case NLASTRIP_MODE_MULTIPLY:
case NLASTRIP_MODE_REPLACE: {
nlaevalchan_blend_value(
lower_necs, upper_necs, upper_blendmode, upper_influence, r_blended_necs);
return;
}
default:
BLI_assert("Blend mode should've been handled");
}
}
/**
* Based on blend-mode, solve for the upper values such that when lower blended with upper then we
* get blended values as a result.
*
* Only processes blended values in the remap domain. Successfully remapped upper values are placed
* in the remap domain so caller knows which values are usable.
*/
static void nlaevalchan_blend_value_get_inverted_upper_evalchan(
NlaEvalChannelSnapshot *lower_necs,
NlaEvalChannelSnapshot *blended_necs,
const int upper_blendmode,
const float upper_influence,
NlaEvalChannelSnapshot *r_upper_necs)
{
nlaevalchan_assert_nonNull(r_upper_necs);
nlaevalchan_assert_blendOrcombine_compatible(lower_necs, r_upper_necs, blended_necs);
const int length = lower_necs->length;
for (int j = 0; j < length; j++) {
if (!BLI_BITMAP_TEST_BOOL(blended_necs->remap_domain.ptr, j)) {
BLI_BITMAP_DISABLE(r_upper_necs->remap_domain.ptr, j);
continue;
}
const bool success = nla_blend_get_inverted_strip_value(upper_blendmode,
lower_necs->values[j],
blended_necs->values[j],
upper_influence,
&r_upper_necs->values[j]);
BLI_BITMAP_SET(r_upper_necs->remap_domain.ptr, j, success);
}
}
/**
* Based on mix-mode, solve for the upper values such that when lower combined with upper then we
* get blended values as a result.
*
* Only processes blended values in the remap domain. Successfully remapped upper values are placed
* in the remap domain so caller knows which values are usable.
*/
static void nlaevalchan_combine_value_get_inverted_upper_evalchan(
NlaEvalChannelSnapshot *lower_necs,
NlaEvalChannelSnapshot *blended_necs,
const float upper_influence,
NlaEvalChannelSnapshot *r_upper_necs)
{
nlaevalchan_assert_nonNull(r_upper_necs);
nlaevalchan_assert_blendOrcombine_compatible(lower_necs, r_upper_necs, blended_necs);
/* Assumes every channel's base is the same. */
float *base_values = lower_necs->channel->base_snapshot.values;
const int length = lower_necs->length;
const char mix_mode = lower_necs->channel->mix_mode;
for (int j = 0; j < length; j++) {
if (!BLI_BITMAP_TEST_BOOL(blended_necs->remap_domain.ptr, j)) {
BLI_BITMAP_DISABLE(r_upper_necs->remap_domain.ptr, j);
continue;
}
const bool success = nla_combine_get_inverted_strip_value(mix_mode,
base_values[j],
lower_necs->values[j],
blended_necs->values[j],
upper_influence,
&r_upper_necs->values[j]);
BLI_BITMAP_SET(r_upper_necs->remap_domain.ptr, j, success);
}
}
/**
* Solve for the upper values such that when lower quaternion combined with upper then we get
* blended values as a result.
*
* All blended values must be in the remap domain. If successfully remapped, then all upper values
* are placed in the remap domain so caller knows the result is usable.
*/
static void nlaevalchan_combine_quaternion_get_inverted_upper_evalchan(
NlaEvalChannelSnapshot *lower_necs,
NlaEvalChannelSnapshot *blended_necs,
const float upper_influence,
NlaEvalChannelSnapshot *r_upper_necs)
{
nlaevalchan_assert_nonNull(r_upper_necs);
nlaevalchan_assert_blendOrcombine_compatible_quaternion(lower_necs, r_upper_necs, blended_necs);
/* Must check each domain index individually in case animator had a non-combine NLA strip with a
* subset of quaternion channels and remapping through any of them failed and thus potentially
* has undefined values. */
for (int j = 0; j < 4; j++) {
if (!BLI_BITMAP_TEST_BOOL(blended_necs->remap_domain.ptr, j)) {
BLI_bitmap_set_all(r_upper_necs->remap_domain.ptr, false, 4);
return;
}
}
const bool success = nla_combine_quaternion_get_inverted_strip_values(
lower_necs->values, blended_necs->values, upper_influence, r_upper_necs->values);
BLI_bitmap_set_all(r_upper_necs->remap_domain.ptr, success, 4);
}
/**
* Based on blend-mode and mix mode, solve for the upper values such that when lower blended or
* combined with upper then we get blended values as a result.
*
* Only processes blended values in the remap domain. Successfully remapped upper values are placed
* in the remap domain so caller knows which values are usable.
*
* \param lower_necs: Never NULL.
* \param blended_necs: Never NULL.
* \param upper_blendmode: Enum value in eNlaStrip_Blend_Mode.
* \param upper_influence: Value in range [0, 1].
* \param r_upper_necs: Never NULL.
*/
static void nlaevalchan_blendOrcombine_get_inverted_upper_evalchan(
NlaEvalChannelSnapshot *lower_necs,
NlaEvalChannelSnapshot *blended_necs,
const int upper_blendmode,
const float upper_influence,
NlaEvalChannelSnapshot *r_upper_necs)
{
nlaevalchan_assert_nonNull(r_upper_necs);
if (IS_EQF(upper_influence, 0.0f)) {
BLI_bitmap_set_all(r_upper_necs->remap_domain.ptr, false, r_upper_necs->length);
return;
}
switch (upper_blendmode) {
case NLASTRIP_MODE_COMBINE: {
switch (r_upper_necs->channel->mix_mode) {
case NEC_MIX_QUATERNION: {
nlaevalchan_combine_quaternion_get_inverted_upper_evalchan(
lower_necs, blended_necs, upper_influence, r_upper_necs);
return;
}
case NEC_MIX_ADD:
case NEC_MIX_AXIS_ANGLE:
case NEC_MIX_MULTIPLY: {
nlaevalchan_combine_value_get_inverted_upper_evalchan(
lower_necs, blended_necs, upper_influence, r_upper_necs);
return;
}
default:
BLI_assert("Mix mode should've been handled");
}
return;
}
case NLASTRIP_MODE_ADD:
case NLASTRIP_MODE_SUBTRACT:
case NLASTRIP_MODE_MULTIPLY:
case NLASTRIP_MODE_REPLACE: {
nlaevalchan_blend_value_get_inverted_upper_evalchan(
lower_necs, blended_necs, upper_blendmode, upper_influence, r_upper_necs);
return;
}
default:
BLI_assert("Blend mode should've been handled");
}
}
/* ---------------------- */
/* F-Modifier stack joining/separation utilities -
* should we generalize these for BLI_listbase.h interface? */
@ -2048,12 +2407,12 @@ static void nla_eval_domain_strips(PointerRNA *ptr,
GSet *touched_actions)
{
LISTBASE_FOREACH (NlaStrip *, strip, strips) {
/* check strip's action */
/* Check strip's action. */
if (strip->act) {
nla_eval_domain_action(ptr, channels, strip->act, touched_actions);
}
/* check sub-strips (if metas) */
/* Check sub-strips (if meta-strips). */
nla_eval_domain_strips(ptr, channels, &strip->strips, touched_actions);
}
}
@ -2500,9 +2859,9 @@ void nlasnapshot_ensure_channels(NlaEvalData *eval_data, NlaEvalSnapshot *snapsh
* Blends the \a lower_snapshot with the \a upper_snapshot into \a r_blended_snapshot according
* to the given \a upper_blendmode and \a upper_influence.
*
* For \a upper_snapshot, blending limited to values in the \a blend_domain. For Replace blendmode,
* this allows the upper snapshot to have a location XYZ channel where only a subset of values are
* blended.
* For \a upper_snapshot, blending limited to values in the \a blend_domain.
* For Replace blend-mode, this allows the upper snapshot to have a location XYZ channel
* where only a subset of values are blended.
*/
void nlasnapshot_blend(NlaEvalData *eval_data,
NlaEvalSnapshot *lower_snapshot,
@ -2513,11 +2872,7 @@ void nlasnapshot_blend(NlaEvalData *eval_data,
{
nlaeval_snapshot_ensure_size(r_blended_snapshot, eval_data->num_channels);
const bool zero_upper_influence = IS_EQF(upper_influence, 0.0f);
LISTBASE_FOREACH (NlaEvalChannel *, nec, &eval_data->channels) {
const int length = nec->base_snapshot.length;
NlaEvalChannelSnapshot *upper_necs = nlaeval_snapshot_get(upper_snapshot, nec->index);
NlaEvalChannelSnapshot *lower_necs = nlaeval_snapshot_get(lower_snapshot, nec->index);
if (upper_necs == NULL && lower_necs == NULL) {
@ -2530,49 +2885,44 @@ void nlasnapshot_blend(NlaEvalData *eval_data,
}
NlaEvalChannelSnapshot *result_necs = nlaeval_snapshot_ensure_channel(r_blended_snapshot, nec);
nlaevalchan_blendOrcombine(
lower_necs, upper_necs, upper_blendmode, upper_influence, result_necs);
}
}
/** Always copy \a lower_snapshot to result, irrelevant of whether \a upper_snapshot has a
* corresponding channel. This only matters when \a lower_snapshot not the same as
* \a r_blended_snapshot. */
memcpy(result_necs->values, lower_necs->values, length * sizeof(float));
if (upper_necs == NULL || zero_upper_influence) {
/**
* Using \a blended_snapshot and \a lower_snapshot, we can solve for the \a r_upper_snapshot.
*
* Only channels that exist within \a blended_snapshot are inverted.
*
* For \a r_upper_snapshot, disables \a NlaEvalChannelSnapshot->remap_domain for failed inversions.
* Only values within the \a remap_domain are processed.
*/
void nlasnapshot_blend_get_inverted_upper_snapshot(NlaEvalData *eval_data,
NlaEvalSnapshot *lower_snapshot,
NlaEvalSnapshot *blended_snapshot,
const short upper_blendmode,
const float upper_influence,
NlaEvalSnapshot *r_upper_snapshot)
{
nlaeval_snapshot_ensure_size(r_upper_snapshot, eval_data->num_channels);
LISTBASE_FOREACH (NlaEvalChannel *, nec, &eval_data->channels) {
NlaEvalChannelSnapshot *blended_necs = nlaeval_snapshot_get(blended_snapshot, nec->index);
if (blended_necs == NULL) {
/** We assume the caller only wants a subset of channels to be inverted, those that exist
* within \a blended_snapshot. */
continue;
}
if (upper_blendmode == NLASTRIP_MODE_COMBINE) {
const int mix_mode = nec->mix_mode;
if (mix_mode == NEC_MIX_QUATERNION) {
if (!BLI_BITMAP_TEST_BOOL(upper_necs->blend_domain.ptr, 0)) {
continue;
}
nla_combine_quaternion(
lower_necs->values, upper_necs->values, upper_influence, result_necs->values);
}
else {
for (int j = 0; j < length; j++) {
if (!BLI_BITMAP_TEST_BOOL(upper_necs->blend_domain.ptr, j)) {
continue;
}
result_necs->values[j] = nla_combine_value(mix_mode,
nec->base_snapshot.values[j],
lower_necs->values[j],
upper_necs->values[j],
upper_influence);
}
}
NlaEvalChannelSnapshot *lower_necs = nlaeval_snapshot_get(lower_snapshot, nec->index);
if (lower_necs == NULL) {
lower_necs = nlaeval_snapshot_find_channel(lower_snapshot->base, nec);
}
else {
for (int j = 0; j < length; j++) {
if (!BLI_BITMAP_TEST_BOOL(upper_necs->blend_domain.ptr, j)) {
continue;
}
result_necs->values[j] = nla_blend_value(
upper_blendmode, lower_necs->values[j], upper_necs->values[j], upper_influence);
}
}
NlaEvalChannelSnapshot *result_necs = nlaeval_snapshot_ensure_channel(r_upper_snapshot, nec);
nlaevalchan_blendOrcombine_get_inverted_upper_evalchan(
lower_necs, blended_necs, upper_blendmode, upper_influence, result_necs);
}
}
@ -2670,74 +3020,64 @@ bool BKE_animsys_nla_remap_keyframe_values(struct NlaKeyframingContext *context,
return false;
}
/* Find the evaluation channel for the NLA stack below current strip. */
/** Create \a blended_snapshot and fill with input \a values. */
NlaEvalData *eval_data = &context->lower_eval_data;
NlaEvalSnapshot blended_snapshot;
nlaeval_snapshot_init(&blended_snapshot, eval_data, NULL);
NlaEvalChannelKey key = {
.ptr = *prop_ptr,
.prop = prop,
};
/**
* Remove lower NLA stack effects.
*
* Using the tweak strip's blended result and the lower snapshot value, we can solve for the
* tweak strip value it must evaluate to.
*/
NlaEvalData *const lower_eval_data = &context->lower_eval_data;
NlaEvalChannel *const lower_nec = nlaevalchan_verify_key(lower_eval_data, NULL, &key);
if ((lower_nec->base_snapshot.length != count)) {
NlaEvalChannel *nec = nlaevalchan_verify_key(eval_data, NULL, &key);
BLI_assert(nec);
if (nec->base_snapshot.length != count) {
BLI_assert(!"invalid value count");
nlaeval_snapshot_free_data(&blended_snapshot);
return false;
}
/* Invert the blending operation to compute the desired strip values. */
NlaEvalChannelSnapshot *const lower_nec_snapshot = nlaeval_snapshot_find_channel(
&lower_eval_data->eval_snapshot, lower_nec);
NlaEvalChannelSnapshot *blended_necs = nlaeval_snapshot_ensure_channel(&blended_snapshot, nec);
memcpy(blended_necs->values, values, sizeof(float) * count);
BLI_bitmap_set_all(blended_necs->remap_domain.ptr, true, count);
float *lower_values = lower_nec_snapshot->values;
/** Remove lower NLA stack effects. */
nlasnapshot_blend_get_inverted_upper_snapshot(eval_data,
&context->lower_eval_data.eval_snapshot,
&blended_snapshot,
blend_mode,
influence,
&blended_snapshot);
if (blend_mode == NLASTRIP_MODE_COMBINE) {
/* Quaternion combine handles all sub-channels as a unit. */
if (lower_nec->mix_mode == NEC_MIX_QUATERNION) {
if (r_force_all == NULL) {
return false;
}
/** Write results into \a values. */
bool successful_remap = true;
if (blended_necs->channel->mix_mode == NEC_MIX_QUATERNION &&
blend_mode == NLASTRIP_MODE_COMBINE) {
if (r_force_all != NULL) {
*r_force_all = true;
if (!nla_combine_quaternion_get_inverted_strip_values(
lower_values, values, influence, values)) {
return false;
}
index = -1;
}
else {
float *base_values = lower_nec->base_snapshot.values;
for (int i = 0; i < count; i++) {
if (ELEM(index, i, -1)) {
if (!nla_combine_get_inverted_strip_value(lower_nec->mix_mode,
base_values[i],
lower_values[i],
values[i],
influence,
&values[i])) {
return false;
}
}
}
}
}
else {
for (int i = 0; i < count; i++) {
if (ELEM(index, i, -1)) {
if (!nla_blend_get_inverted_strip_value(
blend_mode, lower_values[i], values[i], influence, &values[i])) {
return false;
}
}
successful_remap = false;
}
}
return true;
for (int i = 0; i < count; i++) {
if (!ELEM(index, i, -1)) {
continue;
}
if (!BLI_BITMAP_TEST_BOOL(blended_necs->remap_domain.ptr, i)) {
successful_remap = false;
}
values[i] = blended_necs->values[i];
}
nlaeval_snapshot_free_data(&blended_snapshot);
return successful_remap;
}
/**

View File

@ -23,16 +23,39 @@
#include "BKE_curve.h"
#include "BKE_spline.hh"
using blender::Array;
using blender::float3;
using blender::float4x4;
using blender::Span;
blender::Span<SplinePtr> CurveEval::splines() const
{
return splines_;
}
blender::MutableSpan<SplinePtr> CurveEval::splines()
{
return splines_;
}
void CurveEval::add_spline(SplinePtr spline)
{
splines_.append(std::move(spline));
}
void CurveEval::remove_splines(blender::IndexMask mask)
{
for (int i = mask.size() - 1; i >= 0; i--) {
splines_.remove_and_reorder(mask.indices()[i]);
}
}
CurveEval *CurveEval::copy()
{
CurveEval *new_curve = new CurveEval();
for (SplinePtr &spline : this->splines) {
new_curve->splines.append(spline->copy());
for (SplinePtr &spline : this->splines()) {
new_curve->add_spline(spline->copy());
}
return new_curve;
@ -40,7 +63,7 @@ CurveEval *CurveEval::copy()
void CurveEval::translate(const float3 &translation)
{
for (SplinePtr &spline : this->splines) {
for (SplinePtr &spline : this->splines()) {
spline->translate(translation);
spline->mark_cache_invalid();
}
@ -48,18 +71,52 @@ void CurveEval::translate(const float3 &translation)
void CurveEval::transform(const float4x4 &matrix)
{
for (SplinePtr &spline : this->splines) {
for (SplinePtr &spline : this->splines()) {
spline->transform(matrix);
}
}
void CurveEval::bounds_min_max(float3 &min, float3 &max, const bool use_evaluated) const
{
for (const SplinePtr &spline : this->splines) {
for (const SplinePtr &spline : this->splines()) {
spline->bounds_min_max(min, max, use_evaluated);
}
}
/**
* Return the start indices for each of the curve spline's evaluated points, as if they were part
* of a flattened array. This can be used to facilitate parallelism by avoiding the need to
* accumulate an offset while doing more complex calculations.
*
* \note The result array is one longer than the spline count; the last element is the total size.
*/
blender::Array<int> CurveEval::control_point_offsets() const
{
Array<int> offsets(splines_.size() + 1);
int offset = 0;
for (const int i : splines_.index_range()) {
offsets[i] = offset;
offset += splines_[i]->size();
}
offsets.last() = offset;
return offsets;
}
/**
* Exactly like #control_point_offsets, but uses the number of evaluated points instead.
*/
blender::Array<int> CurveEval::evaluated_point_offsets() const
{
Array<int> offsets(splines_.size() + 1);
int offset = 0;
for (const int i : splines_.index_range()) {
offsets[i] = offset;
offset += splines_[i]->evaluated_points_size();
}
offsets.last() = offset;
return offsets;
}
static BezierSpline::HandleType handle_type_from_dna_bezt(const eBezTriple_Handle dna_handle_type)
{
switch (dna_handle_type) {
@ -115,8 +172,6 @@ std::unique_ptr<CurveEval> curve_eval_from_dna_curve(const Curve &dna_curve)
const ListBase *nurbs = BKE_curve_nurbs_get(&const_cast<Curve &>(dna_curve));
curve->splines.reserve(BLI_listbase_count(nurbs));
/* TODO: Optimize by reserving the correct points size. */
LISTBASE_FOREACH (const Nurb *, nurb, nurbs) {
switch (nurb->type) {
@ -135,7 +190,7 @@ std::unique_ptr<CurveEval> curve_eval_from_dna_curve(const Curve &dna_curve)
bezt.tilt);
}
curve->splines.append(std::move(spline));
curve->add_spline(std::move(spline));
break;
}
case CU_NURBS: {
@ -149,7 +204,7 @@ std::unique_ptr<CurveEval> curve_eval_from_dna_curve(const Curve &dna_curve)
spline->add_point(bp.vec, bp.radius, bp.tilt, bp.vec[3]);
}
curve->splines.append(std::move(spline));
curve->add_spline(std::move(spline));
break;
}
case CU_POLY: {
@ -160,7 +215,7 @@ std::unique_ptr<CurveEval> curve_eval_from_dna_curve(const Curve &dna_curve)
spline->add_point(bp.vec, bp.radius, bp.tilt);
}
curve->splines.append(std::move(spline));
curve->add_spline(std::move(spline));
break;
}
default: {
@ -174,11 +229,9 @@ std::unique_ptr<CurveEval> curve_eval_from_dna_curve(const Curve &dna_curve)
* from multiple curve objects, where the value may be different. */
const Spline::NormalCalculationMode normal_mode = normal_mode_from_dna_curve(
dna_curve.twist_mode);
for (SplinePtr &spline : curve->splines) {
for (SplinePtr &spline : curve->splines()) {
spline->normal_mode = normal_mode;
}
return curve;
}
/** \} */

View File

@ -125,13 +125,13 @@ int CurveComponent::attribute_domain_size(const AttributeDomain domain) const
}
if (domain == ATTR_DOMAIN_POINT) {
int total = 0;
for (const SplinePtr &spline : curve_->splines) {
for (const SplinePtr &spline : curve_->splines()) {
total += spline->size();
}
return total;
}
if (domain == ATTR_DOMAIN_CURVE) {
return curve_->splines.size();
return curve_->splines().size();
}
return 0;
}
@ -245,7 +245,7 @@ static void set_spline_resolution(SplinePtr &spline, const int resolution)
static GVArrayPtr make_resolution_read_attribute(const CurveEval &curve)
{
return std::make_unique<fn::GVArray_For_DerivedSpan<SplinePtr, int, get_spline_resolution>>(
curve.splines.as_span());
curve.splines());
}
static GVMutableArrayPtr make_resolution_write_attribute(CurveEval &curve)
@ -254,7 +254,7 @@ static GVMutableArrayPtr make_resolution_write_attribute(CurveEval &curve)
int,
get_spline_resolution,
set_spline_resolution>>(
curve.splines.as_mutable_span());
curve.splines());
}
static bool get_cyclic_value(const SplinePtr &spline)
@ -273,25 +273,422 @@ static void set_cyclic_value(SplinePtr &spline, const bool value)
static GVArrayPtr make_cyclic_read_attribute(const CurveEval &curve)
{
return std::make_unique<fn::GVArray_For_DerivedSpan<SplinePtr, bool, get_cyclic_value>>(
curve.splines.as_span());
curve.splines());
}
static GVMutableArrayPtr make_cyclic_write_attribute(CurveEval &curve)
{
return std::make_unique<
fn::GVMutableArray_For_DerivedSpan<SplinePtr, bool, get_cyclic_value, set_cyclic_value>>(
curve.splines.as_mutable_span());
curve.splines());
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Builtin Control Point Attributes
*
* Attributes with a value for every control point. Most of the complexity here is due to the fact
* that we must provide access to the attribute data as if it was a contiguous array when it is
* really stored separately on each spline. That will be inherently rather slow, but these virtual
* array implementations try to make it workable in common situations.
* \{ */
namespace {
struct PointIndices {
int spline_index;
int point_index;
};
} // namespace
static PointIndices lookup_point_indices(Span<int> offsets, const int index)
{
const int spline_index = std::upper_bound(offsets.begin(), offsets.end(), index) -
offsets.begin() - 1;
const int index_in_spline = index - offsets[spline_index];
return {spline_index, index_in_spline};
}
template<typename T>
static void point_attribute_materialize(Span<Span<T>> data,
Span<int> offsets,
const IndexMask mask,
MutableSpan<T> r_span)
{
const int total_size = offsets.last();
if (mask.is_range() && mask.as_range() == IndexRange(total_size)) {
for (const int spline_index : data.index_range()) {
const int offset = offsets[spline_index];
const int next_offset = offsets[spline_index + 1];
initialized_copy_n(data[spline_index].data(), next_offset - offset, r_span.data() + offset);
}
}
else {
int spline_index = 0;
for (const int i : r_span.index_range()) {
const int dst_index = mask[i];
while (offsets[spline_index] < dst_index) {
spline_index++;
}
const int index_in_spline = dst_index - offsets[spline_index];
r_span[dst_index] = data[spline_index][index_in_spline];
}
}
}
template<typename T>
static void point_attribute_materialize_to_uninitialized(Span<Span<T>> data,
Span<int> offsets,
const IndexMask mask,
MutableSpan<T> r_span)
{
T *dst = r_span.data();
const int total_size = offsets.last();
if (mask.is_range() && mask.as_range() == IndexRange(total_size)) {
for (const int spline_index : data.index_range()) {
const int offset = offsets[spline_index];
const int next_offset = offsets[spline_index + 1];
uninitialized_copy_n(data[spline_index].data(), next_offset - offset, dst + offset);
}
}
else {
int spline_index = 0;
for (const int i : r_span.index_range()) {
const int dst_index = mask[i];
while (offsets[spline_index] < dst_index) {
spline_index++;
}
const int index_in_spline = dst_index - offsets[spline_index];
new (dst + dst_index) T(data[spline_index][index_in_spline]);
}
}
}
/**
* Virtual array for any control point data accessed with spans and an offset array.
*/
template<typename T> class VArray_For_SplinePoints : public VArray<T> {
private:
const Array<Span<T>> data_;
Array<int> offsets_;
public:
VArray_For_SplinePoints(Array<Span<T>> data, Array<int> offsets)
: VArray<T>(offsets.last()), data_(std::move(data)), offsets_(std::move(offsets))
{
}
T get_impl(const int64_t index) const final
{
const PointIndices indices = lookup_point_indices(offsets_, index);
return data_[indices.spline_index][indices.point_index];
}
void materialize_impl(const IndexMask mask, MutableSpan<T> r_span) const final
{
point_attribute_materialize(data_.as_span(), offsets_, mask, r_span);
}
void materialize_to_uninitialized_impl(const IndexMask mask, MutableSpan<T> r_span) const final
{
point_attribute_materialize_to_uninitialized(data_.as_span(), offsets_, mask, r_span);
}
};
/**
* Mutable virtual array for any control point data accessed with spans and an offset array.
*/
template<typename T> class VMutableArray_For_SplinePoints final : public VMutableArray<T> {
private:
Array<MutableSpan<T>> data_;
Array<int> offsets_;
public:
VMutableArray_For_SplinePoints(Array<MutableSpan<T>> data, Array<int> offsets)
: VMutableArray<T>(offsets.last()), data_(std::move(data)), offsets_(std::move(offsets))
{
}
T get_impl(const int64_t index) const final
{
const PointIndices indices = lookup_point_indices(offsets_, index);
return data_[indices.spline_index][indices.point_index];
}
void set_impl(const int64_t index, T value) final
{
const PointIndices indices = lookup_point_indices(offsets_, index);
data_[indices.spline_index][indices.point_index] = value;
}
void set_all_impl(Span<T> src) final
{
for (const int spline_index : data_.index_range()) {
const int offset = offsets_[spline_index];
const int next_offsets = offsets_[spline_index + 1];
data_[spline_index].copy_from(src.slice(offset, next_offsets - offset));
}
}
void materialize_impl(const IndexMask mask, MutableSpan<T> r_span) const final
{
point_attribute_materialize({(Span<T> *)data_.data(), data_.size()}, offsets_, mask, r_span);
}
void materialize_to_uninitialized_impl(const IndexMask mask, MutableSpan<T> r_span) const final
{
point_attribute_materialize_to_uninitialized(
{(Span<T> *)data_.data(), data_.size()}, offsets_, mask, r_span);
}
};
/**
* Virtual array implementation specifically for control point positions. This is only needed for
* Bezier splines, where adjusting the position also requires adjusting handle positions depending
* on handle types. We pay a small price for this when other spline types are mixed with Bezier.
*
* \note There is no need to check the handle type to avoid changing auto handles, since
* retrieving write access to the position data will mark them for recomputation anyway.
*/
class VMutableArray_For_SplinePosition final : public VMutableArray<float3> {
private:
MutableSpan<SplinePtr> splines_;
Array<int> offsets_;
public:
VMutableArray_For_SplinePosition(MutableSpan<SplinePtr> splines, Array<int> offsets)
: VMutableArray<float3>(offsets.last()), splines_(splines), offsets_(std::move(offsets))
{
}
float3 get_impl(const int64_t index) const final
{
const PointIndices indices = lookup_point_indices(offsets_, index);
return splines_[indices.spline_index]->positions()[indices.point_index];
}
void set_impl(const int64_t index, float3 value) final
{
const PointIndices indices = lookup_point_indices(offsets_, index);
Spline &spline = *splines_[indices.spline_index];
if (BezierSpline *bezier_spline = dynamic_cast<BezierSpline *>(&spline)) {
const float3 delta = value - bezier_spline->positions()[indices.point_index];
bezier_spline->handle_positions_left()[indices.point_index] += delta;
bezier_spline->handle_positions_right()[indices.point_index] += delta;
bezier_spline->positions()[indices.point_index] = value;
}
else {
spline.positions()[indices.point_index] = value;
}
}
void set_all_impl(Span<float3> src) final
{
for (const int spline_index : splines_.index_range()) {
Spline &spline = *splines_[spline_index];
const int offset = offsets_[spline_index];
const int next_offset = offsets_[spline_index + 1];
if (BezierSpline *bezier_spline = dynamic_cast<BezierSpline *>(&spline)) {
MutableSpan<float3> positions = bezier_spline->positions();
MutableSpan<float3> handle_positions_left = bezier_spline->handle_positions_left();
MutableSpan<float3> handle_positions_right = bezier_spline->handle_positions_right();
for (const int i : IndexRange(next_offset - offset)) {
const float3 delta = src[offset + i] - positions[i];
handle_positions_left[i] += delta;
handle_positions_right[i] += delta;
positions[i] = src[offset + i];
}
}
else {
spline.positions().copy_from(src.slice(offset, next_offset - offset));
}
}
}
/** Utility so we can pass positions to the materialize functions above. */
Array<Span<float3>> get_position_spans() const
{
Array<Span<float3>> spans(splines_.size());
for (const int i : spans.index_range()) {
spans[i] = splines_[i]->positions();
}
return spans;
}
void materialize_impl(const IndexMask mask, MutableSpan<float3> r_span) const final
{
Array<Span<float3>> spans = this->get_position_spans();
point_attribute_materialize(spans.as_span(), offsets_, mask, r_span);
}
void materialize_to_uninitialized_impl(const IndexMask mask,
MutableSpan<float3> r_span) const final
{
Array<Span<float3>> spans = this->get_position_spans();
point_attribute_materialize_to_uninitialized(spans.as_span(), offsets_, mask, r_span);
}
};
/**
* Provider for any builtin control point attribute that doesn't need
* special handling like access to other arrays in the spline.
*/
template<typename T> class BuiltinPointAttributeProvider : public BuiltinAttributeProvider {
protected:
using GetSpan = Span<T> (*)(const Spline &spline);
using GetMutableSpan = MutableSpan<T> (*)(Spline &spline);
using UpdateOnWrite = void (*)(Spline &spline);
const GetSpan get_span_;
const GetMutableSpan get_mutable_span_;
const UpdateOnWrite update_on_write_;
public:
BuiltinPointAttributeProvider(std::string attribute_name,
const WritableEnum writable,
const GetSpan get_span,
const GetMutableSpan get_mutable_span,
const UpdateOnWrite update_on_write)
: BuiltinAttributeProvider(std::move(attribute_name),
ATTR_DOMAIN_POINT,
bke::cpp_type_to_custom_data_type(CPPType::get<T>()),
BuiltinAttributeProvider::NonCreatable,
writable,
BuiltinAttributeProvider::NonDeletable),
get_span_(get_span),
get_mutable_span_(get_mutable_span),
update_on_write_(update_on_write)
{
}
GVArrayPtr try_get_for_read(const GeometryComponent &component) const override
{
const CurveEval *curve = get_curve_from_component_for_read(component);
if (curve == nullptr) {
return {};
}
Span<SplinePtr> splines = curve->splines();
if (splines.size() == 1) {
return std::make_unique<fn::GVArray_For_GSpan>(get_span_(*splines.first()));
}
Array<int> offsets = curve->control_point_offsets();
Array<Span<T>> spans(splines.size());
for (const int i : splines.index_range()) {
spans[i] = get_span_(*splines[i]);
}
return std::make_unique<fn::GVArray_For_EmbeddedVArray<T, VArray_For_SplinePoints<T>>>(
offsets.last(), std::move(spans), std::move(offsets));
}
GVMutableArrayPtr try_get_for_write(GeometryComponent &component) const override
{
CurveEval *curve = get_curve_from_component_for_write(component);
if (curve == nullptr) {
return {};
}
MutableSpan<SplinePtr> splines = curve->splines();
if (splines.size() == 1) {
return std::make_unique<fn::GVMutableArray_For_GMutableSpan>(
get_mutable_span_(*splines.first()));
}
Array<int> offsets = curve->control_point_offsets();
Array<MutableSpan<T>> spans(splines.size());
for (const int i : splines.index_range()) {
spans[i] = get_mutable_span_(*splines[i]);
if (update_on_write_) {
update_on_write_(*splines[i]);
}
}
return std::make_unique<
fn::GVMutableArray_For_EmbeddedVMutableArray<T, VMutableArray_For_SplinePoints<T>>>(
offsets.last(), std::move(spans), std::move(offsets));
}
bool try_delete(GeometryComponent &UNUSED(component)) const final
{
return false;
}
bool try_create(GeometryComponent &UNUSED(component),
const AttributeInit &UNUSED(initializer)) const final
{
return false;
}
bool exists(const GeometryComponent &component) const final
{
return component.attribute_domain_size(ATTR_DOMAIN_POINT) != 0;
}
};
/**
* Special attribute provider for the position attribute. Keeping this separate means we don't
* need to make #BuiltinPointAttributeProvider overly generic, and the special handling for the
* positions is more clear.
*/
class PositionAttributeProvider final : public BuiltinPointAttributeProvider<float3> {
public:
PositionAttributeProvider()
: BuiltinPointAttributeProvider(
"position",
BuiltinAttributeProvider::Writable,
[](const Spline &spline) { return spline.positions(); },
[](Spline &spline) { return spline.positions(); },
[](Spline &spline) { spline.mark_cache_invalid(); })
{
}
GVMutableArrayPtr try_get_for_write(GeometryComponent &component) const final
{
CurveEval *curve = get_curve_from_component_for_write(component);
if (curve == nullptr) {
return {};
}
bool curve_has_bezier_spline = false;
for (SplinePtr &spline : curve->splines()) {
if (spline->type() == Spline::Type::Bezier) {
curve_has_bezier_spline = true;
break;
}
}
/* Use the regular position virtual array when there aren't any Bezier splines
* to avoid the overhead of thecking the spline type for every point. */
if (!curve_has_bezier_spline) {
return BuiltinPointAttributeProvider<float3>::try_get_for_write(component);
}
/* Changing the positions requires recalculation of cached evaluated data in many cases.
* This could set more specific flags in the future to avoid unnecessary recomputation. */
for (SplinePtr &spline : curve->splines()) {
spline->mark_cache_invalid();
}
Array<int> offsets = curve->control_point_offsets();
return std::make_unique<
fn::GVMutableArray_For_EmbeddedVMutableArray<float3, VMutableArray_For_SplinePosition>>(
offsets.last(), curve->splines(), std::move(offsets));
}
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Attribute Provider Declaration
* \{ */
/**
* In this function all the attribute providers for a curve component are created. Most data
* in this function is statically allocated, because it does not change over time.
* In this function all the attribute providers for a curve component are created.
* Most data in this function is statically allocated, because it does not change over time.
*/
static ComponentAttributeProviders create_attribute_providers_for_curve()
{
@ -307,7 +704,23 @@ static ComponentAttributeProviders create_attribute_providers_for_curve()
make_cyclic_read_attribute,
make_cyclic_write_attribute);
return ComponentAttributeProviders({&resolution, &cyclic}, {});
static PositionAttributeProvider position;
static BuiltinPointAttributeProvider<float> radius(
"radius",
BuiltinAttributeProvider::Writable,
[](const Spline &spline) { return spline.radii(); },
[](Spline &spline) { return spline.radii(); },
nullptr);
static BuiltinPointAttributeProvider<float> tilt(
"tilt",
BuiltinAttributeProvider::Writable,
[](const Spline &spline) { return spline.tilts(); },
[](Spline &spline) { return spline.tilts(); },
[](Spline &spline) { spline.mark_cache_invalid(); });
return ComponentAttributeProviders({&position, &radius, &tilt, &resolution, &cyclic}, {});
}
} // namespace blender::bke

View File

@ -528,11 +528,11 @@ static void join_curve_splines(Span<GeometryInstanceGroup> set_groups, CurveComp
}
const CurveEval &source_curve = *set.get_curve_for_read();
for (const SplinePtr &source_spline : source_curve.splines) {
for (const SplinePtr &source_spline : source_curve.splines()) {
for (const float4x4 &transform : set_group.transforms) {
SplinePtr new_spline = source_spline->copy();
new_spline->transform(transform);
new_curve->splines.append(std::move(new_spline));
new_curve->add_spline(std::move(new_spline));
}
}
}

View File

@ -653,9 +653,13 @@ bGPDframe *BKE_gpencil_frame_addcopy(bGPDlayer *gpl, int cframe)
* \param gpd: Grease pencil data-block
* \param name: Name of the layer
* \param setactive: Set as active
* \param add_to_header: Used to force the layer added at header
* \return Pointer to new layer
*/
bGPDlayer *BKE_gpencil_layer_addnew(bGPdata *gpd, const char *name, bool setactive)
bGPDlayer *BKE_gpencil_layer_addnew(bGPdata *gpd,
const char *name,
const bool setactive,
const bool add_to_header)
{
bGPDlayer *gpl = NULL;
bGPDlayer *gpl_active = NULL;
@ -671,14 +675,18 @@ bGPDlayer *BKE_gpencil_layer_addnew(bGPdata *gpd, const char *name, bool setacti
gpl_active = BKE_gpencil_layer_active_get(gpd);
/* Add to data-block. */
if (gpl_active == NULL) {
BLI_addtail(&gpd->layers, gpl);
if (add_to_header) {
BLI_addhead(&gpd->layers, gpl);
}
else {
/* if active layer, add after that layer */
BLI_insertlinkafter(&gpd->layers, gpl_active, gpl);
if (gpl_active == NULL) {
BLI_addtail(&gpd->layers, gpl);
}
else {
/* if active layer, add after that layer */
BLI_insertlinkafter(&gpd->layers, gpl_active, gpl);
}
}
/* annotation vs GP Object behavior is slightly different */
if (gpd->flag & GP_DATA_ANNOTATIONS) {
/* set default color of new strokes for this layer */

View File

@ -515,7 +515,7 @@ void BKE_gpencil_convert_curve(Main *bmain,
if (collection != NULL) {
gpl = BKE_gpencil_layer_named_get(gpd, collection->id.name + 2);
if (gpl == NULL) {
gpl = BKE_gpencil_layer_addnew(gpd, collection->id.name + 2, true);
gpl = BKE_gpencil_layer_addnew(gpd, collection->id.name + 2, true, false);
}
}
}
@ -523,7 +523,7 @@ void BKE_gpencil_convert_curve(Main *bmain,
if (gpl == NULL) {
gpl = BKE_gpencil_layer_active_get(gpd);
if (gpl == NULL) {
gpl = BKE_gpencil_layer_addnew(gpd, DATA_("GP_Layer"), true);
gpl = BKE_gpencil_layer_addnew(gpd, DATA_("GP_Layer"), true, false);
}
}

View File

@ -2439,7 +2439,7 @@ bool BKE_gpencil_convert_mesh(Main *bmain,
/* Create Layer and Frame. */
bGPDlayer *gpl_fill = BKE_gpencil_layer_named_get(gpd, element_name);
if (gpl_fill == NULL) {
gpl_fill = BKE_gpencil_layer_addnew(gpd, element_name, true);
gpl_fill = BKE_gpencil_layer_addnew(gpd, element_name, true, false);
}
bGPDframe *gpf_fill = BKE_gpencil_layer_frame_get(
gpl_fill, CFRA + frame_offset, GP_GETFRAME_ADD_NEW);
@ -2492,7 +2492,7 @@ bool BKE_gpencil_convert_mesh(Main *bmain,
/* Create Layer and Frame. */
bGPDlayer *gpl_stroke = BKE_gpencil_layer_named_get(gpd, element_name);
if (gpl_stroke == NULL) {
gpl_stroke = BKE_gpencil_layer_addnew(gpd, element_name, true);
gpl_stroke = BKE_gpencil_layer_addnew(gpd, element_name, true, false);
}
bGPDframe *gpf_stroke = BKE_gpencil_layer_frame_get(
gpl_stroke, CFRA + frame_offset, GP_GETFRAME_ADD_NEW);

View File

@ -735,6 +735,37 @@ int BKE_image_get_tile_from_pos(struct Image *ima,
return tile_number;
}
/**
* Return the tile_number for the closest UDIM tile.
*/
int BKE_image_find_nearest_tile(const Image *image, const float co[2])
{
const float co_floor[2] = {floorf(co[0]), floorf(co[1])};
/* Distance to the closest UDIM tile. */
float dist_best_sq = FLT_MAX;
int tile_number_best = -1;
LISTBASE_FOREACH (const ImageTile *, tile, &image->tiles) {
const int tile_index = tile->tile_number - 1001;
/* Coordinates of the current tile. */
const float tile_index_co[2] = {tile_index % 10, tile_index / 10};
if (equals_v2v2(co_floor, tile_index_co)) {
return tile->tile_number;
}
/* Distance between co[2] and UDIM tile. */
const float dist_sq = len_squared_v2v2(tile_index_co, co);
if (dist_sq < dist_best_sq) {
dist_best_sq = dist_sq;
tile_number_best = tile->tile_number;
}
}
return tile_number_best;
}
static void image_init_color_management(Image *ima)
{
ImBuf *ibuf;

View File

@ -1669,7 +1669,7 @@ bool BKE_lib_override_library_property_operation_operands_validate(
return true;
}
/** Check against potential \a bmain. */
/** Check against potential \a bmain. */
void BKE_lib_override_library_validate(Main *UNUSED(bmain), ID *id, ReportList *reports)
{
if (id->override_library == NULL) {
@ -1703,7 +1703,7 @@ void BKE_lib_override_library_validate(Main *UNUSED(bmain), ID *id, ReportList *
}
}
/** Check against potential \a bmain. */
/** Check against potential \a bmain. */
void BKE_lib_override_library_main_validate(Main *bmain, ReportList *reports)
{
ID *id;

View File

@ -162,7 +162,7 @@ static void make_box_from_metaelem(Box *r, const MetaElem *ml)
}
/**
* Partitions part of mainb array [start, end) along axis s. Returns i,
* Partitions part of #process.mainb array [start, end) along axis s. Returns i,
* where centroids of elements in the [start, i) segment lie "on the right side" of div,
* and elements in the [i, end) segment lie "on the left"
*/
@ -1170,8 +1170,9 @@ static void polygonize(PROCESS *process)
/**
* Iterates over ALL objects in the scene and all of its sets, including
* making all duplis(not only metas). Copies metas to mainb array.
* Computes bounding boxes for building BVH. */
* making all duplis (not only meta-elements). Copies meta-elements to #process.mainb array.
* Computes bounding boxes for building BVH.
*/
static void init_meta(Depsgraph *depsgraph, PROCESS *process, Scene *scene, Object *ob)
{
Scene *sce_iter = scene;

View File

@ -303,6 +303,16 @@ static void library_foreach_node_socket(LibraryForeachIDData *data, bNodeSocket
BKE_LIB_FOREACHID_PROCESS(data, default_value->value, IDWALK_CB_USER);
break;
}
case SOCK_TEXTURE: {
bNodeSocketValueTexture *default_value = (bNodeSocketValueTexture *)sock->default_value;
BKE_LIB_FOREACHID_PROCESS(data, default_value->value, IDWALK_CB_USER);
break;
}
case SOCK_MATERIAL: {
bNodeSocketValueMaterial *default_value = (bNodeSocketValueMaterial *)sock->default_value;
BKE_LIB_FOREACHID_PROCESS(data, default_value->value, IDWALK_CB_USER);
break;
}
case SOCK_FLOAT:
case SOCK_VECTOR:
case SOCK_RGBA:
@ -434,6 +444,12 @@ static void write_node_socket_default_value(BlendWriter *writer, bNodeSocket *so
case SOCK_COLLECTION:
BLO_write_struct(writer, bNodeSocketValueCollection, sock->default_value);
break;
case SOCK_TEXTURE:
BLO_write_struct(writer, bNodeSocketValueTexture, sock->default_value);
break;
case SOCK_MATERIAL:
BLO_write_struct(writer, bNodeSocketValueMaterial, sock->default_value);
break;
case __SOCK_MESH:
case SOCK_CUSTOM:
case SOCK_SHADER:
@ -820,6 +836,16 @@ static void lib_link_node_socket(BlendLibReader *reader, Library *lib, bNodeSock
BLO_read_id_address(reader, lib, &default_value->value);
break;
}
case SOCK_TEXTURE: {
bNodeSocketValueTexture *default_value = (bNodeSocketValueTexture *)sock->default_value;
BLO_read_id_address(reader, lib, &default_value->value);
break;
}
case SOCK_MATERIAL: {
bNodeSocketValueMaterial *default_value = (bNodeSocketValueMaterial *)sock->default_value;
BLO_read_id_address(reader, lib, &default_value->value);
break;
}
case SOCK_FLOAT:
case SOCK_VECTOR:
case SOCK_RGBA:
@ -905,6 +931,16 @@ static void expand_node_socket(BlendExpander *expander, bNodeSocket *sock)
BLO_expand(expander, default_value->value);
break;
}
case SOCK_TEXTURE: {
bNodeSocketValueTexture *default_value = (bNodeSocketValueTexture *)sock->default_value;
BLO_expand(expander, default_value->value);
break;
}
case SOCK_MATERIAL: {
bNodeSocketValueMaterial *default_value = (bNodeSocketValueMaterial *)sock->default_value;
BLO_expand(expander, default_value->value);
break;
}
case SOCK_FLOAT:
case SOCK_VECTOR:
case SOCK_RGBA:
@ -1472,6 +1508,16 @@ static void socket_id_user_increment(bNodeSocket *sock)
id_us_plus((ID *)default_value->value);
break;
}
case SOCK_TEXTURE: {
bNodeSocketValueTexture *default_value = (bNodeSocketValueTexture *)sock->default_value;
id_us_plus((ID *)default_value->value);
break;
}
case SOCK_MATERIAL: {
bNodeSocketValueMaterial *default_value = (bNodeSocketValueMaterial *)sock->default_value;
id_us_plus((ID *)default_value->value);
break;
}
case SOCK_FLOAT:
case SOCK_VECTOR:
case SOCK_RGBA:
@ -1511,6 +1557,20 @@ static void socket_id_user_decrement(bNodeSocket *sock)
}
break;
}
case SOCK_TEXTURE: {
bNodeSocketValueTexture *default_value = (bNodeSocketValueTexture *)sock->default_value;
if (default_value->value != nullptr) {
id_us_min(&default_value->value->id);
}
break;
}
case SOCK_MATERIAL: {
bNodeSocketValueMaterial *default_value = (bNodeSocketValueMaterial *)sock->default_value;
if (default_value->value != nullptr) {
id_us_min(&default_value->value->id);
}
break;
}
case SOCK_FLOAT:
case SOCK_VECTOR:
case SOCK_RGBA:
@ -1654,6 +1714,10 @@ const char *nodeStaticSocketType(int type, int subtype)
return "NodeSocketGeometry";
case SOCK_COLLECTION:
return "NodeSocketCollection";
case SOCK_TEXTURE:
return "NodeSocketTexture";
case SOCK_MATERIAL:
return "NodeSocketMaterial";
}
return nullptr;
}
@ -1725,6 +1789,10 @@ const char *nodeStaticSocketInterfaceType(int type, int subtype)
return "NodeSocketInterfaceGeometry";
case SOCK_COLLECTION:
return "NodeSocketInterfaceCollection";
case SOCK_TEXTURE:
return "NodeSocketInterfaceTexture";
case SOCK_MATERIAL:
return "NodeSocketInterfaceMaterial";
}
return nullptr;
}
@ -2190,6 +2258,17 @@ bNodeTree *ntreeCopyTree_ex_new_pointers(const bNodeTree *ntree,
return new_ntree;
}
static int node_count_links(const bNodeTree *ntree, const bNodeSocket *socket)
{
int count = 0;
LISTBASE_FOREACH (bNodeLink *, link, &ntree->links) {
if (ELEM(socket, link->fromsock, link->tosock)) {
count++;
}
}
return count;
}
/* also used via rna api, so we check for proper input output direction */
bNodeLink *nodeAddLink(
bNodeTree *ntree, bNode *fromnode, bNodeSocket *fromsock, bNode *tonode, bNodeSocket *tosock)
@ -2226,6 +2305,10 @@ bNodeLink *nodeAddLink(
ntree->update |= NTREE_UPDATE_LINKS;
}
if (link->tosock->flag & SOCK_MULTI_INPUT) {
link->multi_input_socket_index = node_count_links(ntree, link->tosock) - 1;
}
return link;
}
@ -4244,7 +4327,7 @@ void ntreeUpdateAllUsers(Main *main, ID *id)
if (GS(id->name) == ID_NT) {
bNodeTree *ngroup = (bNodeTree *)id;
if (ngroup->type == NTREE_GEOMETRY) {
if (ngroup->type == NTREE_GEOMETRY && (ngroup->update & NTREE_UPDATE_GROUP)) {
LISTBASE_FOREACH (Object *, object, &main->objects) {
LISTBASE_FOREACH (ModifierData *, md, &object->modifiers) {
if (md->type == eModifierType_Nodes) {

View File

@ -152,6 +152,7 @@ void BKE_nodetree_error_message_add(bNodeTree &ntree,
node_error_message_log(ntree, node, message, type);
NodeUIStorage &node_ui_storage = node_ui_storage_ensure(ntree, context, node);
std::lock_guard lock{node_ui_storage.mutex};
node_ui_storage.warnings.append({type, std::move(message)});
}
@ -163,6 +164,7 @@ void BKE_nodetree_attribute_hint_add(bNodeTree &ntree,
const CustomDataType data_type)
{
NodeUIStorage &node_ui_storage = node_ui_storage_ensure(ntree, context, node);
std::lock_guard lock{node_ui_storage.mutex};
node_ui_storage.attribute_hints.add_as(
AvailableAttributeInfo{attribute_name, domain, data_type});
}

View File

@ -119,10 +119,12 @@ MutableSpan<BezierSpline::HandleType> BezierSpline::handle_types_left()
}
Span<float3> BezierSpline::handle_positions_left() const
{
this->ensure_auto_handles();
return handle_positions_left_;
}
MutableSpan<float3> BezierSpline::handle_positions_left()
{
this->ensure_auto_handles();
return handle_positions_left_;
}
Span<BezierSpline::HandleType> BezierSpline::handle_types_right() const
@ -135,13 +137,94 @@ MutableSpan<BezierSpline::HandleType> BezierSpline::handle_types_right()
}
Span<float3> BezierSpline::handle_positions_right() const
{
this->ensure_auto_handles();
return handle_positions_right_;
}
MutableSpan<float3> BezierSpline::handle_positions_right()
{
this->ensure_auto_handles();
return handle_positions_right_;
}
static float3 previous_position(Span<float3> positions, const bool cyclic, const int i)
{
if (i == 0) {
if (cyclic) {
return positions[positions.size() - 1];
}
return 2.0f * positions[i] - positions[i + 1];
}
return positions[i - 1];
}
static float3 next_position(Span<float3> positions, const bool cyclic, const int i)
{
if (i == positions.size() - 1) {
if (cyclic) {
return positions[0];
}
return 2.0f * positions[i] - positions[i - 1];
}
return positions[i + 1];
}
/**
* Recalculate all #Auto and #Vector handles with positions automatically
* derived from the neighboring control points.
*/
void BezierSpline::ensure_auto_handles() const
{
if (!auto_handles_dirty_) {
return;
}
std::lock_guard lock{auto_handle_mutex_};
if (!auto_handles_dirty_) {
return;
}
for (const int i : IndexRange(this->size())) {
if (ELEM(HandleType::Auto, handle_types_left_[i], handle_types_right_[i])) {
const float3 prev_diff = positions_[i] - previous_position(positions_, is_cyclic_, i);
const float3 next_diff = next_position(positions_, is_cyclic_, i) - positions_[i];
float prev_len = prev_diff.length();
float next_len = next_diff.length();
if (prev_len == 0.0f) {
prev_len = 1.0f;
}
if (next_len == 0.0f) {
next_len = 1.0f;
}
const float3 dir = next_diff / next_len + prev_diff / prev_len;
/* This magic number is unfortunate, but comes from elsewhere in Blender. */
const float len = dir.length() * 2.5614f;
if (len != 0.0f) {
if (handle_types_left_[i] == HandleType::Auto) {
const float prev_len_clamped = std::min(prev_len, next_len * 5.0f);
handle_positions_left_[i] = positions_[i] + dir * -(prev_len_clamped / len);
}
if (handle_types_right_[i] == HandleType::Auto) {
const float next_len_clamped = std::min(next_len, prev_len * 5.0f);
handle_positions_right_[i] = positions_[i] + dir * (next_len_clamped / len);
}
}
}
if (handle_types_left_[i] == HandleType::Vector) {
const float3 prev = previous_position(positions_, is_cyclic_, i);
handle_positions_left_[i] = float3::interpolate(positions_[i], prev, 1.0f / 3.0f);
}
if (handle_types_right_[i] == HandleType::Vector) {
const float3 next = next_position(positions_, is_cyclic_, i);
handle_positions_right_[i] = float3::interpolate(positions_[i], next, 1.0f / 3.0f);
}
}
auto_handles_dirty_ = false;
}
void BezierSpline::translate(const blender::float3 &translation)
{
for (float3 &position : this->positions()) {
@ -179,9 +262,13 @@ bool BezierSpline::point_is_sharp(const int index) const
bool BezierSpline::segment_is_vector(const int index) const
{
if (index == this->size() - 1) {
BLI_assert(is_cyclic_);
return handle_types_right_.last() == HandleType::Vector &&
handle_types_left_.first() == HandleType::Vector;
if (is_cyclic_) {
return handle_types_right_.last() == HandleType::Vector &&
handle_types_left_.first() == HandleType::Vector;
}
/* There is actually no segment in this case, but it's nice to avoid
* having a special case for the last segment in calling code. */
return true;
}
return handle_types_right_[index] == HandleType::Vector &&
handle_types_left_[index + 1] == HandleType::Vector;
@ -195,19 +282,13 @@ void BezierSpline::mark_cache_invalid()
tangent_cache_dirty_ = true;
normal_cache_dirty_ = true;
length_cache_dirty_ = true;
auto_handles_dirty_ = true;
}
int BezierSpline::evaluated_points_size() const
{
const int points_len = this->size();
BLI_assert(points_len > 0);
const int last_offset = this->control_point_offsets().last();
if (is_cyclic_ && points_len > 1) {
return last_offset + (this->segment_is_vector(points_len - 1) ? 1 : resolution_);
}
return last_offset + 1;
BLI_assert(this->size() > 0);
return this->control_point_offsets().last();
}
/**
@ -278,8 +359,12 @@ void BezierSpline::evaluate_bezier_segment(const int index,
/**
* Returns access to a cache of offsets into the evaluated point array for each control point.
* This is important because while most control point edges generate the number of edges specified
* by the resolution, vector segments only generate one edge.
* While most control point edges generate the number of edges specified by the resolution, vector
* segments only generate one edge.
*
* \note The length of the result is one greater than the number of points, so that the last item
* is the total number of evaluated points. This is useful to avoid recalculating the size of the
* last segment everywhere.
*/
Span<int> BezierSpline::control_point_offsets() const
{
@ -293,12 +378,12 @@ Span<int> BezierSpline::control_point_offsets() const
}
const int points_len = this->size();
offset_cache_.resize(points_len);
offset_cache_.resize(points_len + 1);
MutableSpan<int> offsets = offset_cache_;
int offset = 0;
for (const int i : IndexRange(points_len - 1)) {
for (const int i : IndexRange(points_len)) {
offsets[i] = offset;
offset += this->segment_is_vector(i) ? 1 : resolution_;
}
@ -320,7 +405,7 @@ static void calculate_mappings_linear_resolution(Span<int> offsets,
}
const int grain_size = std::max(2048 / resolution, 1);
blender::parallel_for(IndexRange(1, size - 2), grain_size, [&](IndexRange range) {
parallel_for(IndexRange(1, size - 2), grain_size, [&](IndexRange range) {
for (const int i_control_point : range) {
const int segment_len = offsets[i_control_point + 1] - offsets[i_control_point];
const float segment_len_inv = 1.0f / segment_len;
@ -331,7 +416,7 @@ static void calculate_mappings_linear_resolution(Span<int> offsets,
});
if (is_cyclic) {
const int last_segment_len = offsets[size - 1] - offsets[size - 2];
const int last_segment_len = offsets[size] - offsets[size - 1];
const float last_segment_len_inv = 1.0f / last_segment_len;
for (const int i : IndexRange(last_segment_len)) {
r_mappings[offsets[size - 1] + i] = size - 1 + i * last_segment_len_inv;
@ -389,25 +474,26 @@ Span<float3> BezierSpline::evaluated_positions() const
return evaluated_position_cache_;
}
this->ensure_auto_handles();
const int size = this->size();
const int eval_size = this->evaluated_points_size();
evaluated_position_cache_.resize(eval_size);
MutableSpan<float3> positions = evaluated_position_cache_;
Span<int> offsets = this->control_point_offsets();
BLI_assert(offsets.last() <= eval_size);
const int grain_size = std::max(512 / resolution_, 1);
blender::parallel_for(IndexRange(this->size() - 1), grain_size, [&](IndexRange range) {
parallel_for(IndexRange(size - 1), grain_size, [&](IndexRange range) {
for (const int i : range) {
this->evaluate_bezier_segment(
i, i + 1, positions.slice(offsets[i], offsets[i + 1] - offsets[i]));
}
});
const int i_last = this->size() - 1;
if (is_cyclic_) {
this->evaluate_bezier_segment(i_last, 0, positions.slice(offsets.last(), resolution_));
this->evaluate_bezier_segment(
size - 1, 0, positions.slice(offsets[size - 1], offsets[size] - offsets[size - 1]));
}
else {
/* Since evaluating the bezier segment doesn't add the final point,
@ -422,7 +508,7 @@ Span<float3> BezierSpline::evaluated_positions() const
/**
* Convert the data encoded in #evaulated_mappings into its parts-- the information necessary
* to interpolate data from control points to evaluated points between them. The next control
* point index result will not overflow the size of the vector.
* point index result will not overflow the size of the control point vectors.
*/
BezierSpline::InterpolationData BezierSpline::interpolation_data_from_index_factor(
const float index_factor) const

View File

@ -259,7 +259,7 @@ static void calculate_basis_for_point(const float parameter,
MutableSpan<float> basis_buffer,
NURBSpline::BasisCache &basis_cache)
{
/* Clamp parameter due to floating point inaccuracy. TODO: Look into using doubles. */
/* Clamp parameter due to floating point inaccuracy. */
const float t = std::clamp(parameter, knots[0], knots[points_len + order - 1]);
int start = 0;

View File

@ -82,6 +82,10 @@ typedef struct NlaEvalChannelSnapshot {
/** For an upper snapshot channel, marks values that should be blended. */
NlaValidMask blend_domain;
/** Only used for keyframe remapping. Any values not in the \a remap_domain will not be used
* for keyframe remapping. */
NlaValidMask remap_domain;
int length; /* Number of values in the property. */
bool is_base; /* Base snapshot of the channel. */
@ -196,6 +200,13 @@ void nlasnapshot_blend(NlaEvalData *eval_data,
const float upper_influence,
NlaEvalSnapshot *r_blended_snapshot);
void nlasnapshot_blend_get_inverted_upper_snapshot(NlaEvalData *eval_data,
NlaEvalSnapshot *lower_snapshot,
NlaEvalSnapshot *blended_snapshot,
const short upper_blendmode,
const float upper_influence,
NlaEvalSnapshot *r_upper_snapshot);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,73 @@
/*
* 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.
*/
#pragma once
#ifdef WITH_TBB
# include <tbb/enumerable_thread_specific.h>
#endif
#include <atomic>
#include <mutex>
#include "BLI_map.hh"
#include "BLI_utility_mixins.hh"
namespace blender {
namespace enumerable_thread_specific_utils {
inline std::atomic<int> next_id = 0;
inline thread_local int thread_id = next_id.fetch_add(1, std::memory_order_relaxed);
} // namespace enumerable_thread_specific_utils
/**
* This is mainly a wrapper for `tbb::enumerable_thread_specific`. The wrapper is needed because we
* want to be able to build without tbb.
*
* More features of the tbb version can be wrapped when they are used.
*/
template<typename T> class EnumerableThreadSpecific : NonCopyable, NonMovable {
#ifdef WITH_TBB
private:
tbb::enumerable_thread_specific<T> values_;
public:
T &local()
{
return values_.local();
}
#else /* WITH_TBB */
private:
std::mutex mutex_;
/* Maps thread ids to their corresponding values. The values are not embedded in the map, so that
* their addresses do not change when the map grows. */
Map<int, std::unique_ptr<T>> values_;
public:
T &local()
{
const int thread_id = enumerable_thread_specific_utils::thread_id;
std::lock_guard lock{mutex_};
return *values_.lookup_or_add_cb(thread_id, []() { return std::make_unique<T>(); });
}
#endif /* WITH_TBB */
};
} // namespace blender

View File

@ -85,9 +85,12 @@
namespace blender {
/**
* If there is no other specialization of #DefaultHash for a given type, try to call `hash()` on
* the value. If there is no such method, this will result in a compiler error. Usually that means
* that you have to implement a hash function using one of three strategies listed above.
* If there is no other specialization of #DefaultHash for a given type, look for a hash function
* on the type itself. Implementing a `hash()` method on a type is often significantly easier than
* specializing #DefaultHash.
*
* To support heterogeneous lookup, a type can also implement a static `hash_as(const OtherType &)`
* function.
*
* In the case of an enum type, the default hash is just to cast the enum value to an integer.
*/
@ -95,12 +98,25 @@ template<typename T> struct DefaultHash {
uint64_t operator()(const T &value) const
{
if constexpr (std::is_enum_v<T>) {
/* For enums use the value as hash directly. */
return (uint64_t)value;
}
else {
/* Try to call the `hash()` function on the value. */
/* If this results in a compiler error, no hash function for the type has been found. */
return value.hash();
}
}
template<typename U> uint64_t operator()(const U &value) const
{
/* Try calling the static `T::hash_as(value)` function with the given value. The returned hash
* should be "compatible" with `T::hash()`. Usually that means that if `value` is converted to
* `T` its hash does not change. */
/* If this results in a compiler error, no hash function for the heterogeneous lookup has been
* found. */
return T::hash_as(value);
}
};
/**

View File

@ -128,6 +128,21 @@ template<typename Allocator = GuardedAllocator> class LinearAllocator : NonCopya
return destruct_ptr<T>(value);
}
/**
* Construct multiple instances of a type in an array. The constructor of is called with the
* given arguments. The caller is responsible for calling the destructor (and not `delete`) on
* the constructed elements.
*/
template<typename T, typename... Args>
MutableSpan<T> construct_array(int64_t size, Args &&... args)
{
MutableSpan<T> array = this->allocate_array<T>(size);
for (const int64_t i : IndexRange(size)) {
new (&array[i]) T(std::forward<Args>(args)...);
}
return array;
}
/**
* Copy the given array into a memory buffer provided by this allocator.
*/

View File

@ -604,6 +604,37 @@ class Map {
return this->lookup_or_add_cb_as(std::forward<ForwardKey>(key), []() { return Value(); });
}
/**
* Returns the key that is stored in the set that compares equal to the given key. This invokes
* undefined behavior when the key is not in the map.
*/
const Key &lookup_key(const Key &key) const
{
return this->lookup_key_as(key);
}
template<typename ForwardKey> const Key &lookup_key_as(const ForwardKey &key) const
{
const Slot &slot = this->lookup_slot(key, hash_(key));
return *slot.key();
}
/**
* Returns a pointer to the key that is stored in the map that compares equal to the given key.
* If the key is not in the map, null is returned.
*/
const Key *lookup_key_ptr(const Key &key) const
{
return this->lookup_key_ptr_as(key);
}
template<typename ForwardKey> const Key *lookup_key_ptr_as(const ForwardKey &key) const
{
const Slot *slot = this->lookup_slot_ptr(key, hash_(key));
if (slot == nullptr) {
return nullptr;
}
return slot->key();
}
/**
* Calls the provided callback for every key-value-pair in the map. The callback is expected
* to take a `const Key &` as first and a `const Value &` as second parameter.

View File

@ -414,6 +414,38 @@ class VectorSet {
return this->index_of_or_add__impl(std::forward<ForwardKey>(key), hash_(key));
}
/**
* Returns the key that is stored in the vector set that compares equal to the given key. This
* invokes undefined behavior when the key is not in the set.
*/
const Key &lookup_key(const Key &key) const
{
return this->lookup_key_as(key);
}
template<typename ForwardKey> const Key &lookup_key_as(const ForwardKey &key) const
{
const Key *key_ptr = this->lookup_key_ptr_as(key);
BLI_assert(key_ptr != nullptr);
return *key_ptr;
}
/**
* Returns a pointer to the key that is stored in the vector set that compares equal to the given
* key. If the key is not in the set, null is returned.
*/
const Key *lookup_key_ptr(const Key &key) const
{
return this->lookup_key_ptr_as(key);
}
template<typename ForwardKey> const Key *lookup_key_ptr_as(const ForwardKey &key) const
{
const int64_t index = this->index_of_try__impl(key, hash_(key));
if (index >= 0) {
return keys_ + index;
}
return nullptr;
}
/**
* Get a pointer to the beginning of the array containing all keys.
*/

View File

@ -185,6 +185,7 @@ set(SRC
BLI_edgehash.h
BLI_endian_switch.h
BLI_endian_switch_inline.h
BLI_enumerable_thread_specific.hh
BLI_expr_pylike_eval.h
BLI_fileops.h
BLI_fileops_types.h

View File

@ -136,4 +136,17 @@ TEST(linear_allocator, ManyAllocations)
}
}
TEST(linear_allocator, ConstructArray)
{
LinearAllocator<> allocator;
MutableSpan<std::string> strings = allocator.construct_array<std::string>(4, "hello");
EXPECT_EQ(strings[0], "hello");
EXPECT_EQ(strings[1], "hello");
EXPECT_EQ(strings[2], "hello");
EXPECT_EQ(strings[3], "hello");
for (std::string &string : strings) {
string.~basic_string();
}
}
} // namespace blender::tests

View File

@ -640,6 +640,19 @@ TEST(map, RemoveDuringIteration)
EXPECT_EQ(map.lookup(3), 3);
}
TEST(map, LookupKey)
{
Map<std::string, int> map;
map.add("a", 0);
map.add("b", 1);
map.add("c", 2);
EXPECT_EQ(map.lookup_key("a"), "a");
EXPECT_EQ(map.lookup_key_as("c"), "c");
EXPECT_EQ(map.lookup_key_ptr_as("d"), nullptr);
EXPECT_EQ(map.lookup_key_ptr_as("b")->size(), 1);
EXPECT_EQ(map.lookup_key_ptr("a"), map.lookup_key_ptr_as("a"));
}
/**
* Set this to 1 to activate the benchmark. It is disabled by default, because it prints a lot.
*/

View File

@ -258,4 +258,17 @@ TEST(vector_set, Clear)
EXPECT_EQ(set.size(), 0);
}
TEST(vector_set, LookupKey)
{
VectorSet<std::string> set;
set.add("a");
set.add("b");
set.add("c");
EXPECT_EQ(set.lookup_key("a"), "a");
EXPECT_EQ(set.lookup_key_as("c"), "c");
EXPECT_EQ(set.lookup_key_ptr_as("d"), nullptr);
EXPECT_EQ(set.lookup_key_ptr_as("b")->size(), 1);
EXPECT_EQ(set.lookup_key_ptr("a"), set.lookup_key_ptr_as("a"));
}
} // namespace blender::tests

View File

@ -31,6 +31,7 @@
#include "BKE_lib_id.h"
#include "BKE_main.h"
#include "BKE_node.h"
#include "BLO_readfile.h"
#include "readfile.h"
@ -57,6 +58,30 @@ void do_versions_after_linking_300(Main *bmain, ReportList *UNUSED(reports))
*/
{
/* Keep this block, even when empty. */
/* Use new texture socket in Attribute Sample Texture node. */
LISTBASE_FOREACH (bNodeTree *, ntree, &bmain->nodetrees) {
if (ntree->type != NTREE_GEOMETRY) {
continue;
}
LISTBASE_FOREACH (bNode *, node, &ntree->nodes) {
if (node->type != GEO_NODE_ATTRIBUTE_SAMPLE_TEXTURE) {
continue;
}
if (node->id == NULL) {
continue;
}
LISTBASE_FOREACH (bNodeSocket *, socket, &node->inputs) {
if (socket->type == SOCK_TEXTURE) {
bNodeSocketValueTexture *socket_value = (bNodeSocketValueTexture *)
socket->default_value;
socket_value->value = (Tex *)node->id;
break;
}
}
node->id = NULL;
}
}
}
}

View File

@ -30,4 +30,4 @@ double ChunkOrderHotspot::calc_distance(int x, int y)
return result;
}
} // namespace blender::compositor
} // namespace blender::compositor

View File

@ -1557,6 +1557,12 @@ void DepsgraphNodeBuilder::build_nodetree_socket(bNodeSocket *socket)
else if (socket->type == SOCK_COLLECTION) {
build_id((ID *)((bNodeSocketValueCollection *)socket->default_value)->value);
}
else if (socket->type == SOCK_TEXTURE) {
build_id((ID *)((bNodeSocketValueTexture *)socket->default_value)->value);
}
else if (socket->type == SOCK_MATERIAL) {
build_id((ID *)((bNodeSocketValueMaterial *)socket->default_value)->value);
}
}
void DepsgraphNodeBuilder::build_nodetree(bNodeTree *ntree)

View File

@ -2401,6 +2401,18 @@ void DepsgraphRelationBuilder::build_nodetree_socket(bNodeSocket *socket)
build_collection(nullptr, nullptr, collection);
}
}
else if (socket->type == SOCK_TEXTURE) {
Tex *texture = ((bNodeSocketValueTexture *)socket->default_value)->value;
if (texture != nullptr) {
build_texture(texture);
}
}
else if (socket->type == SOCK_MATERIAL) {
Material *material = ((bNodeSocketValueMaterial *)socket->default_value)->value;
if (material != nullptr) {
build_material(material);
}
}
}
void DepsgraphRelationBuilder::build_nodetree(bNodeTree *ntree)

View File

@ -478,6 +478,7 @@ add_dependencies(bf_draw bf_dna)
if(WITH_GTESTS)
if(WITH_OPENGL_DRAW_TESTS)
set(TEST_SRC
tests/draw_testing.cc
tests/shaders_test.cc
)
set(TEST_INC

View File

@ -158,9 +158,9 @@ void EEVEE_cryptomatte_output_init(EEVEE_ViewLayerData *UNUSED(sldata),
const ViewLayer *view_layer = draw_ctx->view_layer;
const int num_cryptomatte_layers = eevee_cryptomatte_layers_count(view_layer);
eGPUTextureFormat format = (num_cryptomatte_layers == 1) ? GPU_R32F :
(num_cryptomatte_layers == 2) ? GPU_RG32F :
GPU_RGBA32F;
eGPUTextureFormat format = (num_cryptomatte_layers == 1) ?
GPU_R32F :
(num_cryptomatte_layers == 2) ? GPU_RG32F : GPU_RGBA32F;
const float *viewport_size = DRW_viewport_size_get();
const int buffer_size = viewport_size[0] * viewport_size[1];

View File

@ -556,6 +556,11 @@ static void eevee_render_to_image(void *vedata,
EEVEE_renderpasses_output_init(
sldata, vedata, g_data->render_sample_count_per_timestep * time_steps_tot);
if (scene->world) {
/* Update world in case of animated world material. */
eevee_id_world_update(vedata, scene->world);
}
EEVEE_temporal_sampling_create_view(vedata);
EEVEE_render_draw(vedata, engine, render_layer, rect);

View File

@ -51,53 +51,72 @@
closure_##t2##_##subroutine(in_##t2##_2, eval_##t2##_2, cl_common, sub_data, out_##t2##_2); \
closure_##t3##_##subroutine(in_##t3##_3, eval_##t3##_3, cl_common, sub_data, out_##t3##_3);
#ifndef DEPTH_SHADER
/* Inputs are inout so that callers can get the final inputs used for evaluation. */
#define CLOSURE_EVAL_FUNCTION_DECLARE(name, t0, t1, t2, t3) \
void closure_##name##_eval(ClosureInputCommon in_common, \
inout ClosureInput##t0 in_##t0##_0, \
inout ClosureInput##t1 in_##t1##_1, \
inout ClosureInput##t2 in_##t2##_2, \
inout ClosureInput##t3 in_##t3##_3, \
out ClosureOutput##t0 out_##t0##_0, \
out ClosureOutput##t1 out_##t1##_1, \
out ClosureOutput##t2 out_##t2##_2, \
out ClosureOutput##t3 out_##t3##_3) \
{ \
CLOSURE_EVAL_DECLARE(t0, t1, t2, t3); \
# define CLOSURE_EVAL_FUNCTION_DECLARE(name, t0, t1, t2, t3) \
void closure_##name##_eval(ClosureInputCommon in_common, \
inout ClosureInput##t0 in_##t0##_0, \
inout ClosureInput##t1 in_##t1##_1, \
inout ClosureInput##t2 in_##t2##_2, \
inout ClosureInput##t3 in_##t3##_3, \
out ClosureOutput##t0 out_##t0##_0, \
out ClosureOutput##t1 out_##t1##_1, \
out ClosureOutput##t2 out_##t2##_2, \
out ClosureOutput##t3 out_##t3##_3) \
{ \
CLOSURE_EVAL_DECLARE(t0, t1, t2, t3); \
\
/* Starts at 1 because 0 is world cubemap. */ \
for (int i = 1; cl_common.specular_accum > 0.0 && i < prbNumRenderCube && i < MAX_PROBE; \
i++) { \
ClosureCubemapData cube = closure_cubemap_eval_init(i, cl_common); \
if (cube.attenuation > 1e-8) { \
CLOSURE_META_SUBROUTINE_DATA(cubemap_eval, cube, t0, t1, t2, t3); \
/* Starts at 1 because 0 is world cubemap. */ \
for (int i = 1; cl_common.specular_accum > 0.0 && i < prbNumRenderCube && i < MAX_PROBE; \
i++) { \
ClosureCubemapData cube = closure_cubemap_eval_init(i, cl_common); \
if (cube.attenuation > 1e-8) { \
CLOSURE_META_SUBROUTINE_DATA(cubemap_eval, cube, t0, t1, t2, t3); \
} \
} \
} \
\
/* Starts at 1 because 0 is world irradiance. */ \
for (int i = 1; cl_common.diffuse_accum > 0.0 && i < prbNumRenderGrid && i < MAX_GRID; i++) { \
ClosureGridData grid = closure_grid_eval_init(i, cl_common); \
if (grid.attenuation > 1e-8) { \
CLOSURE_META_SUBROUTINE_DATA(grid_eval, grid, t0, t1, t2, t3); \
/* Starts at 1 because 0 is world irradiance. */ \
for (int i = 1; cl_common.diffuse_accum > 0.0 && i < prbNumRenderGrid && i < MAX_GRID; \
i++) { \
ClosureGridData grid = closure_grid_eval_init(i, cl_common); \
if (grid.attenuation > 1e-8) { \
CLOSURE_META_SUBROUTINE_DATA(grid_eval, grid, t0, t1, t2, t3); \
} \
} \
} \
\
CLOSURE_META_SUBROUTINE(indirect_end, t0, t1, t2, t3); \
CLOSURE_META_SUBROUTINE(indirect_end, t0, t1, t2, t3); \
\
ClosurePlanarData planar = closure_planar_eval_init(cl_common); \
if (planar.attenuation > 1e-8) { \
CLOSURE_META_SUBROUTINE_DATA(planar_eval, planar, t0, t1, t2, t3); \
} \
\
for (int i = 0; i < laNumLight && i < MAX_LIGHT; i++) { \
ClosureLightData light = closure_light_eval_init(cl_common, i); \
if (light.vis > 1e-8) { \
CLOSURE_META_SUBROUTINE_DATA(light_eval, light, t0, t1, t2, t3); \
ClosurePlanarData planar = closure_planar_eval_init(cl_common); \
if (planar.attenuation > 1e-8) { \
CLOSURE_META_SUBROUTINE_DATA(planar_eval, planar, t0, t1, t2, t3); \
} \
} \
\
CLOSURE_META_SUBROUTINE(eval_end, t0, t1, t2, t3); \
}
for (int i = 0; i < laNumLight && i < MAX_LIGHT; i++) { \
ClosureLightData light = closure_light_eval_init(cl_common, i); \
if (light.vis > 1e-8) { \
CLOSURE_META_SUBROUTINE_DATA(light_eval, light, t0, t1, t2, t3); \
} \
} \
\
CLOSURE_META_SUBROUTINE(eval_end, t0, t1, t2, t3); \
}
#else
/* Inputs are inout so that callers can get the final inputs used for evaluation. */
# define CLOSURE_EVAL_FUNCTION_DECLARE(name, t0, t1, t2, t3) \
void closure_##name##_eval(ClosureInputCommon in_common, \
inout ClosureInput##t0 in_##t0##_0, \
inout ClosureInput##t1 in_##t1##_1, \
inout ClosureInput##t2 in_##t2##_2, \
inout ClosureInput##t3 in_##t3##_3, \
out ClosureOutput##t0 out_##t0##_0, \
out ClosureOutput##t1 out_##t1##_1, \
out ClosureOutput##t2 out_##t2##_2, \
out ClosureOutput##t3 out_##t3##_3) \
{ \
CLOSURE_EVAL_DECLARE(t0, t1, t2, t3); \
}
#endif
#define CLOSURE_EVAL_FUNCTION(name, t0, t1, t2, t3) \
closure_##name##_eval(in_common, \

View File

@ -136,7 +136,7 @@ const float layer_offset_fg = 0.5 + 1.0;
/* Extra offset for convolution layers to avoid light leaking from background. */
const float layer_offset = 0.5 + 0.5;
#define DOF_MAX_SLIGHT_FOCUS_RADIUS 5
#define DOF_MAX_SLIGHT_FOCUS_RADIUS 16
float dof_layer_weight(float coc, const bool is_foreground)
{

View File

@ -40,7 +40,7 @@ void dof_slight_focus_gather(float radius, out vec4 out_color, out float out_wei
DofGatherData fg_accum = GATHER_DATA_INIT;
DofGatherData bg_accum = GATHER_DATA_INIT;
int i_radius = clamp(int(radius), 0, int(layer_threshold));
int i_radius = clamp(int(radius + 0.5), 0, int(layer_threshold));
const int resolve_ring_density = DOF_SLIGHT_FOCUS_DENSITY;
ivec2 texel = ivec2(gl_FragCoord.xy);

View File

@ -1043,7 +1043,7 @@ static void draw_bone_update_disp_matrix_default(EditBone *eBone, bPoseChannel *
bone_mat = pchan->pose_mat;
disp_mat = pchan->disp_mat;
disp_tail_mat = pchan->disp_tail_mat;
mul_v3_v3fl(bone_scale, pchan->custom_scale_xyz, pchan->bone->length);
copy_v3_fl(bone_scale, pchan->bone->length);
}
else {
eBone->length = len_v3v3(eBone->tail, eBone->head);
@ -1271,9 +1271,9 @@ static void draw_bone_update_disp_matrix_custom(bPoseChannel *pchan)
copy_m4_m4(disp_mat, bone_mat);
translate_m4(disp_mat,
pchan->custom_translation[0],
pchan->custom_translation[1],
pchan->custom_translation[2]);
pchan->custom_translation[0],
pchan->custom_translation[1],
pchan->custom_translation[2]);
mul_m4_m4m3(disp_mat, disp_mat, rot_mat);
rescale_m4(disp_mat, bone_scale);
copy_m4_m4(disp_tail_mat, disp_mat);

View File

@ -0,0 +1,18 @@
/* Apache License, Version 2.0 */
#include "draw_testing.hh"
#include "GPU_shader.h"
#include "draw_manager_testing.h"
namespace blender::draw {
/* Base class for draw test cases. It will setup and tear down the GPU part around each test. */
void DrawTest::SetUp()
{
GPUTest::SetUp();
DRW_draw_state_init_gtests(GPU_SHADER_CFG_DEFAULT);
}
} // namespace blender::draw

View File

@ -0,0 +1,13 @@
/* Apache License, Version 2.0 */
#include "gpu_testing.hh"
namespace blender::draw {
/* Base class for draw test cases. It will setup and tear down the GPU part around each test. */
class DrawTest : public blender::gpu::GPUTest {
public:
void SetUp() override;
};
} // namespace blender::draw

View File

@ -2,12 +2,12 @@
#include "testing/testing.h"
#include "draw_testing.hh"
#include "intern/draw_manager_testing.h"
#include "GPU_context.h"
#include "GPU_init_exit.h"
#include "GPU_shader.h"
#include "gpu_testing.hh"
#include "engines/eevee/eevee_private.h"
#include "engines/gpencil/gpencil_engine.h"
@ -17,19 +17,9 @@
namespace blender::draw {
/* Base class for draw test cases. It will setup and tear down the GPU part around each test. */
class DrawTest : public blender::gpu::GPUTest {
void SetUp() override
{
GPUTest::SetUp();
DRW_draw_state_init_gtests(GPU_SHADER_CFG_DEFAULT);
}
};
TEST_F(DrawTest, workbench_glsl_shaders)
{
workbench_shader_library_ensure();
DRW_draw_state_init_gtests(GPU_SHADER_CFG_DEFAULT);
const int MAX_WPD = 6;
WORKBENCH_PrivateData wpds[MAX_WPD];

View File

@ -17,6 +17,7 @@
set(INC
../include
../../blenfont
../../blenkernel
../../blenlib
../../blentranslation

View File

@ -33,6 +33,7 @@
#include "DNA_armature_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "DNA_vec_types.h"
#include "BKE_fcurve.h"
#include "BKE_nla.h"
@ -50,15 +51,28 @@
#include "WM_types.h"
#include "UI_interface.h"
#include "UI_resources.h"
#include "ED_armature.h"
#include "ED_keyframes_draw.h"
#include "ED_markers.h"
#include "ED_numinput.h"
#include "ED_screen.h"
#include "ED_space_api.h"
#include "GPU_immediate.h"
#include "GPU_immediate_util.h"
#include "GPU_matrix.h"
#include "GPU_state.h"
#include "armature_intern.h"
#include "BLF_api.h"
/* Pixel distance from 0% to 100%. */
#define SLIDE_PIXEL_DISTANCE (300 * U.pixelsize)
#define OVERSHOOT_RANGE_DELTA 0.2f
/* **************************************************** */
/* == POSE 'SLIDING' TOOLS ==
*
@ -110,15 +124,36 @@ typedef struct tPoseSlideOp {
/** unused for now, but can later get used for storing runtime settings.... */
short flag;
/* Store overlay settings when invoking the operator. Bones will be temporarily hidden. */
int overlay_flag;
/** which transforms/channels are affected (ePoseSlide_Channels) */
short channels;
/** axis-limits for transforms (ePoseSlide_AxisLock) */
short axislock;
/** 0-1 value for determining the influence of whatever is relevant */
/* Allow overshoot or clamp between 0% and 100%. */
bool overshoot;
/* Reduces percentage delta from mouse movement. */
bool precision;
/* Move percentage in 10% steps. */
bool increments;
/* Draw callback handler. */
void *draw_handle;
/* Accumulative, unclamped and unrounded percentage. */
float raw_percentage;
/* 0-1 value for determining the influence of whatever is relevant. */
float percentage;
/** numeric input */
/* Last cursor position in screen space used for mouse movement delta calculation. */
int last_cursor_x;
/* Numeric input. */
NumInput num;
struct tPoseSlideObject *ob_data_array;
@ -187,6 +222,240 @@ static const EnumPropertyItem prop_axis_lock_types[] = {
/* ------------------------------------ */
static void draw_overshoot_triangle(const uint8_t color[4],
const bool facing_right,
const float x,
const float y)
{
const uint shdr_pos_2d = GPU_vertformat_attr_add(
immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
GPU_blend(GPU_BLEND_ALPHA);
GPU_polygon_smooth(true);
immUniformColor3ubvAlpha(color, 225);
const float triangle_side_length = facing_right ? 6 * U.pixelsize : -6 * U.pixelsize;
const float triangle_offset = facing_right ? 2 * U.pixelsize : -2 * U.pixelsize;
immBegin(GPU_PRIM_TRIS, 3);
immVertex2f(shdr_pos_2d, x + triangle_offset + triangle_side_length, y);
immVertex2f(shdr_pos_2d, x + triangle_offset, y + triangle_side_length / 2);
immVertex2f(shdr_pos_2d, x + triangle_offset, y - triangle_side_length / 2);
immEnd();
GPU_polygon_smooth(false);
GPU_blend(GPU_BLEND_NONE);
immUnbindProgram();
}
static void draw_ticks(const float start_percentage,
const float end_percentage,
const struct vec2f line_start,
const float base_tick_height,
const float line_width,
const uint8_t color_overshoot[4],
const uint8_t color_line[4])
{
/* Use percentage represented as 0-100 int to avoid floating point precision problems. */
const int tick_increment = 10;
/* Round initial_tick_percentage up to the next tick_increment. */
int tick_percentage = ceil((start_percentage * 100) / tick_increment) * tick_increment;
float tick_height = base_tick_height;
while (tick_percentage <= (int)(end_percentage * 100)) {
/* Different ticks have different heights. Multiples of 100% are the tallest, 50% is a bit
* smaller and the rest is the minimum size. */
if (tick_percentage % 100 == 0) {
tick_height = base_tick_height;
}
else if (tick_percentage % 50 == 0) {
tick_height = base_tick_height * 0.8;
}
else {
tick_height = base_tick_height * 0.5;
}
const float x = line_start.x +
(((float)tick_percentage / 100) - start_percentage) * SLIDE_PIXEL_DISTANCE;
const struct rctf tick_rect = {.xmin = x - (line_width / 2),
.xmax = x + (line_width / 2),
.ymin = line_start.y - (tick_height / 2),
.ymax = line_start.y + (tick_height / 2)};
if (tick_percentage < 0 || tick_percentage > 100) {
UI_draw_roundbox_3ub_alpha(&tick_rect, true, 1, color_overshoot, 255);
}
else {
UI_draw_roundbox_3ub_alpha(&tick_rect, true, 1, color_line, 255);
}
tick_percentage += tick_increment;
}
}
static void draw_main_line(const struct rctf main_line_rect,
const float percentage,
const bool overshoot,
const uint8_t color_overshoot[4],
const uint8_t color_line[4])
{
if (overshoot) {
/* In overshoot mode, draw the 0-100% range differently to provide a visual reference. */
const float line_zero_percent = main_line_rect.xmin -
((percentage - 0.5f - OVERSHOOT_RANGE_DELTA) *
SLIDE_PIXEL_DISTANCE);
const float clamped_line_zero_percent = clamp_f(
line_zero_percent, main_line_rect.xmin, main_line_rect.xmax);
const float clamped_line_hundred_percent = clamp_f(
line_zero_percent + SLIDE_PIXEL_DISTANCE, main_line_rect.xmin, main_line_rect.xmax);
const struct rctf left_overshoot_line_rect = {.xmin = main_line_rect.xmin,
.xmax = clamped_line_zero_percent,
.ymin = main_line_rect.ymin,
.ymax = main_line_rect.ymax};
const struct rctf right_overshoot_line_rect = {.xmin = clamped_line_hundred_percent,
.xmax = main_line_rect.xmax,
.ymin = main_line_rect.ymin,
.ymax = main_line_rect.ymax};
UI_draw_roundbox_3ub_alpha(&left_overshoot_line_rect, true, 0, color_overshoot, 255);
UI_draw_roundbox_3ub_alpha(&right_overshoot_line_rect, true, 0, color_overshoot, 255);
const struct rctf non_overshoot_line_rect = {.xmin = clamped_line_zero_percent,
.xmax = clamped_line_hundred_percent,
.ymin = main_line_rect.ymin,
.ymax = main_line_rect.ymax};
UI_draw_roundbox_3ub_alpha(&non_overshoot_line_rect, true, 0, color_line, 255);
}
else {
UI_draw_roundbox_3ub_alpha(&main_line_rect, true, 0, color_line, 255);
}
}
static void draw_backdrop(const int fontid,
const struct rctf main_line_rect,
const float color_bg[4],
const short region_y_size,
const float base_tick_height)
{
float string_pixel_size[2];
const char *percentage_placeholder = "000%%";
BLF_width_and_height(fontid,
percentage_placeholder,
sizeof(percentage_placeholder),
&string_pixel_size[0],
&string_pixel_size[1]);
const struct vec2f pad = {.x = (region_y_size - base_tick_height) / 2, .y = 2.0f * U.pixelsize};
const struct rctf backdrop_rect = {.xmin = main_line_rect.xmin - string_pixel_size[0] - pad.x,
.xmax = main_line_rect.xmax + pad.x,
.ymin = pad.y,
.ymax = region_y_size - pad.y};
UI_draw_roundbox_aa(&backdrop_rect, true, 4.0f, color_bg);
}
/* Draw an on screen Slider for a Pose Slide Operator. */
static void pose_slide_draw_2d_slider(const struct bContext *UNUSED(C), ARegion *region, void *arg)
{
tPoseSlideOp *pso = arg;
/* Only draw in region from which the Operator was started. */
if (region != pso->region) {
return;
}
uint8_t color_text[4];
uint8_t color_line[4];
uint8_t color_handle[4];
uint8_t color_overshoot[4];
float color_bg[4];
/* Get theme colors. */
UI_GetThemeColor4ubv(TH_TEXT, color_text);
UI_GetThemeColor4ubv(TH_TEXT, color_line);
UI_GetThemeColor4ubv(TH_TEXT, color_overshoot);
UI_GetThemeColor4ubv(TH_ACTIVE, color_handle);
UI_GetThemeColor3fv(TH_BACK, color_bg);
color_bg[3] = 0.5f;
color_overshoot[0] = color_overshoot[0] * 0.7;
color_overshoot[1] = color_overshoot[1] * 0.7;
color_overshoot[2] = color_overshoot[2] * 0.7;
/* Get the default font. */
const uiStyle *style = UI_style_get();
const uiFontStyle *fstyle = &style->widget;
const int fontid = fstyle->uifont_id;
BLF_color3ubv(fontid, color_text);
BLF_rotation(fontid, 0.0f);
const float line_width = 1.5 * U.pixelsize;
const float base_tick_height = 12.0 * U.pixelsize;
const float line_y = region->winy / 2;
struct rctf main_line_rect = {.xmin = (region->winx / 2) - (SLIDE_PIXEL_DISTANCE / 2),
.xmax = (region->winx / 2) + (SLIDE_PIXEL_DISTANCE / 2),
.ymin = line_y - line_width / 2,
.ymax = line_y + line_width / 2};
float line_start_percentage = 0;
int handle_pos_x = main_line_rect.xmin + SLIDE_PIXEL_DISTANCE * pso->percentage;
if (pso->overshoot) {
main_line_rect.xmin = main_line_rect.xmin - SLIDE_PIXEL_DISTANCE * OVERSHOOT_RANGE_DELTA;
main_line_rect.xmax = main_line_rect.xmax + SLIDE_PIXEL_DISTANCE * OVERSHOOT_RANGE_DELTA;
line_start_percentage = pso->percentage - 0.5f - OVERSHOOT_RANGE_DELTA;
handle_pos_x = region->winx / 2;
}
draw_backdrop(fontid, main_line_rect, color_bg, pso->region->winy, base_tick_height);
draw_main_line(main_line_rect, pso->percentage, pso->overshoot, color_overshoot, color_line);
const float percentage_range = pso->overshoot ? 1 + OVERSHOOT_RANGE_DELTA * 2 : 1;
const struct vec2f line_start_position = {.x = main_line_rect.xmin, .y = line_y};
draw_ticks(line_start_percentage,
line_start_percentage + percentage_range,
line_start_position,
base_tick_height,
line_width,
color_overshoot,
color_line);
/* Draw triangles at the ends of the line in overshoot mode to indicate direction of 0-100%
* range.*/
if (pso->overshoot) {
if (pso->percentage > 1 + OVERSHOOT_RANGE_DELTA + 0.5) {
draw_overshoot_triangle(color_line, false, main_line_rect.xmin, line_y);
}
if (pso->percentage < 0 - OVERSHOOT_RANGE_DELTA - 0.5) {
draw_overshoot_triangle(color_line, true, main_line_rect.xmax, line_y);
}
}
char percentage_string[256];
/* Draw handle indicating current percentage. */
const struct rctf handle_rect = {.xmin = handle_pos_x - (line_width),
.xmax = handle_pos_x + (line_width),
.ymin = line_y - (base_tick_height / 2),
.ymax = line_y + (base_tick_height / 2)};
UI_draw_roundbox_3ub_alpha(&handle_rect, true, 1, color_handle, 255);
BLI_snprintf(percentage_string, sizeof(percentage_string), "%.0f%%", pso->percentage * 100);
/* Draw percentage string. */
float percentage_pixel_size[2];
BLF_width_and_height(fontid,
percentage_string,
sizeof(percentage_string),
&percentage_pixel_size[0],
&percentage_pixel_size[1]);
BLF_position(fontid,
main_line_rect.xmin - 24.0 * U.pixelsize - percentage_pixel_size[0] / 2,
(region->winy / 2) - percentage_pixel_size[1] / 2,
0.0f);
BLF_draw(fontid, percentage_string, sizeof(percentage_string));
}
/* operator init */
static int pose_slide_init(bContext *C, wmOperator *op, ePoseSlide_Modes mode)
{
@ -205,6 +474,7 @@ static int pose_slide_init(bContext *C, wmOperator *op, ePoseSlide_Modes mode)
/* set range info from property values - these may get overridden for the invoke() */
pso->percentage = RNA_float_get(op->ptr, "percentage");
pso->raw_percentage = pso->percentage;
pso->prevFrame = RNA_int_get(op->ptr, "prev_frame");
pso->nextFrame = RNA_int_get(op->ptr, "next_frame");
@ -257,6 +527,17 @@ static int pose_slide_init(bContext *C, wmOperator *op, ePoseSlide_Modes mode)
pso->num.val_flag[0] |= NUM_NO_NEGATIVE;
pso->num.unit_type[0] = B_UNIT_NONE; /* percentages don't have any units... */
/* Register UI drawing callback. */
/* pso->draw_handle = ED_region_draw_cb_activate(
pso->region->type, pose_slide_draw_2d_slider, pso, REGION_DRAW_POST_PIXEL); */
LISTBASE_FOREACH (ARegion *, region, &pso->area->regionbase) {
if (region->regiontype == RGN_TYPE_HEADER) {
pso->region = region;
pso->draw_handle = ED_region_draw_cb_activate(
region->type, pose_slide_draw_2d_slider, pso, REGION_DRAW_POST_PIXEL);
}
}
/* return status is whether we've got all the data we were requested to get */
return 1;
}
@ -266,6 +547,13 @@ static void pose_slide_exit(wmOperator *op)
{
tPoseSlideOp *pso = op->customdata;
/* Hide Bone Overlay. */
View3D *v3d = pso->area->spacedata.first;
v3d->overlay.flag = pso->overlay_flag;
/* Remove UI drawing callback. */
ED_region_draw_cb_exit(pso->region->type, pso->draw_handle);
/* if data exists, clear its data and exit */
if (pso) {
/* free the temp pchan links and their data */
@ -602,7 +890,7 @@ static void pose_slide_apply_quat(tPoseSlideOp *pso, tPChanFCurveLink *pfl)
static void pose_slide_rest_pose_apply_vec3(tPoseSlideOp *pso, float vec[3], float default_value)
{
/* We only slide to the rest pose. So only use the default rest pose value */
/* We only slide to the rest pose. So only use the default rest pose value. */
const int lock = pso->axislock;
for (int idx = 0; idx < 3; idx++) {
if ((lock == 0) || ((lock & PS_LOCK_X) && (idx == 0)) || ((lock & PS_LOCK_Y) && (idx == 1)) ||
@ -621,7 +909,7 @@ static void pose_slide_rest_pose_apply_vec3(tPoseSlideOp *pso, float vec[3], flo
static void pose_slide_rest_pose_apply_other_rot(tPoseSlideOp *pso, float vec[4], bool quat)
{
/* We only slide to the rest pose. So only use the default rest pose value */
/* We only slide to the rest pose. So only use the default rest pose value. */
float default_values[] = {1.0f, 0.0f, 0.0f, 0.0f};
if (!quat) {
/* Axis Angle */
@ -789,14 +1077,18 @@ static void pose_slide_reset(tPoseSlideOp *pso)
/* ------------------------------------ */
/* draw percentage indicator in header */
/* Draw percentage indicator in workspace footer. */
/* TODO: Include hints about locks here... */
static void pose_slide_draw_status(tPoseSlideOp *pso)
static void pose_slide_draw_status(bContext *C, tPoseSlideOp *pso)
{
char status_str[UI_MAX_DRAW_STR];
char limits_str[UI_MAX_DRAW_STR];
char axis_str[50];
char mode_str[32];
char overshoot_str[50];
char precision_str[50];
char increments_str[50];
char bone_vis_str[50];
switch (pso->mode) {
case POSESLIDE_PUSH:
@ -871,25 +1163,51 @@ static void pose_slide_draw_status(tPoseSlideOp *pso)
break;
}
if (pso->overshoot) {
BLI_strncpy(overshoot_str, TIP_("[E] - Disable overshoot"), sizeof(overshoot_str));
}
else {
BLI_strncpy(overshoot_str, TIP_("E - Enable overshoot"), sizeof(overshoot_str));
}
if (pso->precision) {
BLI_strncpy(precision_str, TIP_("[Shift] - Precision active"), sizeof(precision_str));
}
else {
BLI_strncpy(precision_str, TIP_("Shift - Hold for precision"), sizeof(precision_str));
}
if (pso->increments) {
BLI_strncpy(increments_str, TIP_("[Ctrl] - Increments active"), sizeof(increments_str));
}
else {
BLI_strncpy(increments_str, TIP_("Ctrl - Hold for 10% increments"), sizeof(increments_str));
}
BLI_strncpy(bone_vis_str, TIP_("[H] - Toggle bone visibility"), sizeof(increments_str));
if (hasNumInput(&pso->num)) {
Scene *scene = pso->scene;
char str_ofs[NUM_STR_REP_LEN];
char str_offs[NUM_STR_REP_LEN];
outputNumInput(&pso->num, str_ofs, &scene->unit);
outputNumInput(&pso->num, str_offs, &scene->unit);
BLI_snprintf(
status_str, sizeof(status_str), "%s: %s | %s", mode_str, str_ofs, limits_str);
BLI_snprintf(status_str, sizeof(status_str), "%s: %s | %s", mode_str, str_offs, limits_str);
}
else {
BLI_snprintf(status_str,
sizeof(status_str),
"%s: %d %% | %s",
mode_str,
(int)(pso->percentage * 100.0f),
limits_str);
sizeof(status_str),
"%s: %s | %s | %s | %s | %s",
mode_str,
limits_str,
overshoot_str,
precision_str,
increments_str,
bone_vis_str);
}
ED_area_status_text(pso->area, status_str);
ED_workspace_status_text(C, status_str);
ED_area_status_text(pso->area, "");
}
/* common code for invoke() methods */
@ -975,21 +1293,40 @@ static int pose_slide_invoke_common(bContext *C, wmOperator *op, tPoseSlideOp *p
WM_cursor_modal_set(win, WM_CURSOR_EW_SCROLL);
/* header print */
pose_slide_draw_status(pso);
pose_slide_draw_status(C, pso);
/* add a modal handler for this operator */
WM_event_add_modal_handler(C, op);
/* Hide Bone Overlay. */
View3D *v3d = pso->area->spacedata.first;
pso->overlay_flag = v3d->overlay.flag;
v3d->overlay.flag |= V3D_OVERLAY_HIDE_BONES;
return OPERATOR_RUNNING_MODAL;
}
/* calculate percentage based on position of mouse (we only use x-axis for now.
* since this is more convenient for users to do), and store new percentage value
/* Calculate percentage based on mouse movement, clamp or round to increments if
* enabled by the user. Store the new percentage value.
*/
static void pose_slide_mouse_update_percentage(tPoseSlideOp *pso,
wmOperator *op,
const wmEvent *event)
{
pso->percentage = (event->x - pso->region->winrct.xmin) / ((float)pso->region->winx);
const float percentage_delta = (event->x - pso->last_cursor_x) / ((float)(SLIDE_PIXEL_DISTANCE));
/* Reduced percentage delta in precision mode (shift held). */
pso->raw_percentage += pso->precision ? (percentage_delta / 8) : percentage_delta;
pso->percentage = pso->raw_percentage;
pso->last_cursor_x = event->x;
if (!pso->overshoot) {
pso->percentage = clamp_f(pso->percentage, 0, 1);
}
if (pso->increments) {
pso->percentage = round(pso->percentage * 10) / 10;
}
RNA_float_set(op->ptr, "percentage", pso->percentage);
}
@ -1056,9 +1393,13 @@ static int pose_slide_modal(bContext *C, wmOperator *op, const wmEvent *event)
case EVT_PADENTER: {
if (event->val == KM_PRESS) {
/* return to normal cursor and header status */
ED_workspace_status_text(C, NULL);
ED_area_status_text(pso->area, NULL);
WM_cursor_modal_restore(win);
/* Depsgraph updates + redraws. Redraw needed to remove UI. */
pose_slide_refresh(C, pso);
/* insert keyframes as required... */
pose_slide_autoKeyframe(C, pso);
pose_slide_exit(op);
@ -1073,13 +1414,14 @@ static int pose_slide_modal(bContext *C, wmOperator *op, const wmEvent *event)
case RIGHTMOUSE: {
if (event->val == KM_PRESS) {
/* return to normal cursor and header status */
ED_workspace_status_text(C, NULL);
ED_area_status_text(pso->area, NULL);
WM_cursor_modal_restore(win);
/* reset transforms back to original state */
pose_slide_reset(pso);
/* depsgraph updates + redraws */
/* Depsgraph updates + redraws.*/
pose_slide_refresh(C, pso);
/* clean up temp data */
@ -1178,10 +1520,58 @@ static int pose_slide_modal(bContext *C, wmOperator *op, const wmEvent *event)
break;
}
/* Overshoot. */
case EVT_EKEY: {
pso->overshoot = !pso->overshoot;
do_pose_update = true;
break;
}
/* Precision mode. */
case EVT_LEFTSHIFTKEY:
case EVT_RIGHTSHIFTKEY: {
pso->precision = true;
do_pose_update = true;
break;
}
/* Increments mode. */
case EVT_LEFTCTRLKEY:
case EVT_RIGHTCTRLKEY: {
pso->increments = true;
do_pose_update = true;
break;
}
/* Toggle Bone visibility. */
case EVT_HKEY: {
View3D *v3d = pso->area->spacedata.first;
v3d->overlay.flag ^= V3D_OVERLAY_HIDE_BONES;
}
default: /* Some other unhandled key... */
break;
}
}
/* Precision and stepping only active while button is held. */
else if (event->val == KM_RELEASE) {
switch (event->type) {
case EVT_LEFTSHIFTKEY:
case EVT_RIGHTSHIFTKEY: {
pso->precision = false;
do_pose_update = true;
break;
}
case EVT_LEFTCTRLKEY:
case EVT_RIGHTCTRLKEY: {
pso->increments = false;
do_pose_update = true;
break;
}
default:
break;
}
}
else {
/* unhandled event - maybe it was some view manipulation? */
/* allow to pass through */
@ -1193,8 +1583,10 @@ static int pose_slide_modal(bContext *C, wmOperator *op, const wmEvent *event)
/* Perform pose updates - in response to some user action
* (e.g. pressing a key or moving the mouse). */
if (do_pose_update) {
pose_slide_mouse_update_percentage(pso, op, event);
/* update percentage indicator in header */
pose_slide_draw_status(pso);
pose_slide_draw_status(C, pso);
/* reset transforms (to avoid accumulation errors) */
pose_slide_reset(pso);
@ -1247,11 +1639,11 @@ static void pose_slide_opdef_properties(wmOperatorType *ot)
PropertyRNA *prop;
prop = RNA_def_float_factor(ot->srna,
"factor",
"percentage",
0.5f,
0.0f,
1.0f,
"Factor",
"Percentage",
"Weighting factor for which keyframe is favored more",
0.0,
1.0);
@ -1310,6 +1702,8 @@ static int pose_slide_push_invoke(bContext *C, wmOperator *op, const wmEvent *ev
pso = op->customdata;
pso->last_cursor_x = event->x;
/* Initialize percentage so that it won't pop on first mouse move. */
pose_slide_mouse_update_percentage(pso, op, event);
@ -1349,7 +1743,7 @@ void POSE_OT_push(wmOperatorType *ot)
ot->poll = ED_operator_posemode;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING;
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING | OPTYPE_GRAB_CURSOR_X;
/* Properties */
pose_slide_opdef_properties(ot);
@ -1370,6 +1764,8 @@ static int pose_slide_relax_invoke(bContext *C, wmOperator *op, const wmEvent *e
pso = op->customdata;
pso->last_cursor_x = event->x;
/* Initialize percentage so that it won't pop on first mouse move. */
pose_slide_mouse_update_percentage(pso, op, event);
@ -1409,7 +1805,7 @@ void POSE_OT_relax(wmOperatorType *ot)
ot->poll = ED_operator_posemode;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING;
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING | OPTYPE_GRAB_CURSOR_X;
/* Properties */
pose_slide_opdef_properties(ot);
@ -1429,6 +1825,8 @@ static int pose_slide_push_rest_invoke(bContext *C, wmOperator *op, const wmEven
pso = op->customdata;
pso->last_cursor_x = event->x;
/* Initialize percentage so that it won't pop on first mouse move. */
pose_slide_mouse_update_percentage(pso, op, event);
@ -1468,7 +1866,7 @@ void POSE_OT_push_rest(wmOperatorType *ot)
ot->poll = ED_operator_posemode;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING;
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING | OPTYPE_GRAB_CURSOR_X;
/* Properties */
pose_slide_opdef_properties(ot);
@ -1489,6 +1887,8 @@ static int pose_slide_relax_rest_invoke(bContext *C, wmOperator *op, const wmEve
pso = op->customdata;
pso->last_cursor_x = event->x;
/* Initialize percentage so that it won't pop on first mouse move. */
pose_slide_mouse_update_percentage(pso, op, event);
@ -1528,7 +1928,7 @@ void POSE_OT_relax_rest(wmOperatorType *ot)
ot->poll = ED_operator_posemode;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING;
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING | OPTYPE_GRAB_CURSOR_X;
/* Properties */
pose_slide_opdef_properties(ot);
@ -1549,6 +1949,8 @@ static int pose_slide_breakdown_invoke(bContext *C, wmOperator *op, const wmEven
pso = op->customdata;
pso->last_cursor_x = event->x;
/* Initialize percentage so that it won't pop on first mouse move. */
pose_slide_mouse_update_percentage(pso, op, event);
@ -1588,7 +1990,7 @@ void POSE_OT_breakdown(wmOperatorType *ot)
ot->poll = ED_operator_posemode;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING;
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING | OPTYPE_GRAB_CURSOR_X;
/* Properties */
pose_slide_opdef_properties(ot);

View File

@ -1548,7 +1548,7 @@ static void annotation_paint_initstroke(tGPsdata *p,
if (p->gpl == NULL) {
/* tag for annotations */
p->gpd->flag |= GP_DATA_ANNOTATIONS;
p->gpl = BKE_gpencil_layer_addnew(p->gpd, DATA_("Note"), true);
p->gpl = BKE_gpencil_layer_addnew(p->gpd, DATA_("Note"), true, false);
if (p->custom_color[3]) {
copy_v3_v3(p->gpl->color, p->custom_color);

View File

@ -90,7 +90,7 @@ void ED_gpencil_create_blank(bContext *C, Object *ob, float UNUSED(mat[4][4]))
ob->actcol = color_black + 1;
/* layers */
bGPDlayer *layer = BKE_gpencil_layer_addnew(gpd, "GP_Layer", true);
bGPDlayer *layer = BKE_gpencil_layer_addnew(gpd, "GP_Layer", true, false);
/* frames */
BKE_gpencil_frame_addnew(layer, CFRA);

View File

@ -96,7 +96,7 @@ void ED_gpencil_create_lineart(bContext *C, Object *ob)
ob->actcol = color_black + 1;
/* layers */
bGPDlayer *lines = BKE_gpencil_layer_addnew(gpd, "Lines", true);
bGPDlayer *lines = BKE_gpencil_layer_addnew(gpd, "Lines", true, false);
/* frames */
BKE_gpencil_frame_addnew(lines, 0);

View File

@ -837,8 +837,8 @@ void ED_gpencil_create_monkey(bContext *C, Object *ob, float mat[4][4])
/* layers */
/* NOTE: For now, we just add new layers, to make it easier to separate out old/new instances */
bGPDlayer *Fills = BKE_gpencil_layer_addnew(gpd, "Fills", false);
bGPDlayer *Lines = BKE_gpencil_layer_addnew(gpd, "Lines", true);
bGPDlayer *Fills = BKE_gpencil_layer_addnew(gpd, "Fills", false, false);
bGPDlayer *Lines = BKE_gpencil_layer_addnew(gpd, "Lines", true, false);
/* frames */
/* NOTE: No need to check for existing, as this will take care of it for us */

View File

@ -225,8 +225,8 @@ void ED_gpencil_create_stroke(bContext *C, Object *ob, float mat[4][4])
ob->actcol = color_black + 1;
/* layers */
bGPDlayer *colors = BKE_gpencil_layer_addnew(gpd, "Colors", false);
bGPDlayer *lines = BKE_gpencil_layer_addnew(gpd, "Lines", true);
bGPDlayer *colors = BKE_gpencil_layer_addnew(gpd, "Colors", false, false);
bGPDlayer *lines = BKE_gpencil_layer_addnew(gpd, "Lines", true, false);
/* frames */
bGPDframe *frame_color = BKE_gpencil_frame_addnew(colors, CFRA);

View File

@ -1857,7 +1857,7 @@ static int image_to_gpencil_exec(bContext *C, wmOperator *op)
/* Add layer and frame. */
bGPdata *gpd = (bGPdata *)ob->data;
bGPDlayer *gpl = BKE_gpencil_layer_addnew(gpd, "Image Layer", true);
bGPDlayer *gpl = BKE_gpencil_layer_addnew(gpd, "Image Layer", true, false);
bGPDframe *gpf = BKE_gpencil_frame_addnew(gpl, CFRA);
done = BKE_gpencil_from_image(sima, gpd, gpf, size, is_mask);

Some files were not shown because too many files have changed in this diff Show More