diff options
author | Jacques Lucke <jacques@blender.org> | 2021-05-20 15:41:07 +0300 |
---|---|---|
committer | Jacques Lucke <jacques@blender.org> | 2021-05-20 15:41:07 +0300 |
commit | 1895205be3308ef7d0c2553519c1433e9117dc33 (patch) | |
tree | fe4146e10a6ded237e65dedba18a885cebb3c6f4 | |
parent | 619b015a46786a4f4057b0dd4d20d27b61d13c0f (diff) | |
parent | 933999db752681ce881760e75befccd17e706f41 (diff) |
Merge branch 'master' into profiler-editorprofiler-editor
432 files changed, 14193 insertions, 4348 deletions
diff --git a/.clang-format b/.clang-format index b61ce6018d3..9cc3cdeaaf8 100644 --- a/.clang-format +++ b/.clang-format @@ -255,6 +255,7 @@ ForEachMacros: - SCULPT_VERTEX_DUPLICATES_AND_NEIGHBORS_ITER_BEGIN - SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN - SEQ_ALL_BEGIN + - SEQ_ITERATOR_FOREACH - SURFACE_QUAD_ITER_BEGIN - foreach - ED_screen_areas_iter diff --git a/doc/python_api/examples/bpy.types.RenderEngine.py b/doc/python_api/examples/bpy.types.RenderEngine.py index 45910194244..0b8795340ad 100644 --- a/doc/python_api/examples/bpy.types.RenderEngine.py +++ b/doc/python_api/examples/bpy.types.RenderEngine.py @@ -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. diff --git a/doc/python_api/examples/mathutils.Matrix.LocRotScale.py b/doc/python_api/examples/mathutils.Matrix.LocRotScale.py new file mode 100644 index 00000000000..016a5002e82 --- /dev/null +++ b/doc/python_api/examples/mathutils.Matrix.LocRotScale.py @@ -0,0 +1,5 @@ +# Compute local object transformation matrix: +if obj.rotation_mode == 'QUATERNION': + matrix = mathutils.Matrix.LocRotScale(obj.location, obj.rotation_quaternion, obj.scale) +else: + matrix = mathutils.Matrix.LocRotScale(obj.location, obj.rotation_euler, obj.scale) diff --git a/doc/python_api/examples/mathutils.Matrix.py b/doc/python_api/examples/mathutils.Matrix.py index 26c7ccba27c..84e09b97c15 100644 --- a/doc/python_api/examples/mathutils.Matrix.py +++ b/doc/python_api/examples/mathutils.Matrix.py @@ -14,10 +14,14 @@ mat_rot = mathutils.Matrix.Rotation(math.radians(45.0), 4, 'X') mat_out = mat_loc @ mat_rot @ mat_sca print(mat_out) -# extract components back out of the matrix +# extract components back out of the matrix as two vectors and a quaternion loc, rot, sca = mat_out.decompose() print(loc, rot, sca) +# recombine extracted components +mat_out2 = mathutils.Matrix.LocRotScale(loc, rot, sca) +print(mat_out2) + # it can also be useful to access components of a matrix directly mat = mathutils.Matrix() mat[0][0], mat[1][0], mat[2][0] = 0.0, 1.0, 2.0 diff --git a/doc/python_api/sphinx_doc_gen.py b/doc/python_api/sphinx_doc_gen.py index 8be194a58a2..032f8a86bd5 100644 --- a/doc/python_api/sphinx_doc_gen.py +++ b/doc/python_api/sphinx_doc_gen.py @@ -545,6 +545,13 @@ def range_str(val): def example_extract_docstring(filepath): + ''' + Return (text, line_no, line_no_has_content) where: + - ``text`` is the doc-string text. + - ``line_no`` is the line the doc-string text ends. + - ``line_no_has_content`` when False, this file only contains a doc-string. + There is no need to include the remainder. + ''' file = open(filepath, "r", encoding="utf-8") line = file.readline() line_no = 0 @@ -553,7 +560,7 @@ def example_extract_docstring(filepath): line_no += 1 else: file.close() - return "", 0, False + return "", 0, True for line in file: line_no += 1 @@ -1029,7 +1036,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 +1555,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) diff --git a/extern/mantaflow/preprocessed/fileio/iovdb.cpp b/extern/mantaflow/preprocessed/fileio/iovdb.cpp index e615741e0f7..1846ef7ecbb 100644 --- a/extern/mantaflow/preprocessed/fileio/iovdb.cpp +++ b/extern/mantaflow/preprocessed/fileio/iovdb.cpp @@ -29,10 +29,10 @@ #if OPENVDB == 1 # include "openvdb/openvdb.h" -# include <openvdb/points/PointConversion.h> -# include <openvdb/points/PointCount.h> -# include <openvdb/tools/Clip.h> -# include <openvdb/tools/Dense.h> +# include "openvdb/points/PointConversion.h" +# include "openvdb/points/PointCount.h" +# include "openvdb/tools/Clip.h" +# include "openvdb/tools/Dense.h" #endif #define POSITION_NAME "P" @@ -519,7 +519,7 @@ int writeObjectsVDB(const string &filename, } } - // Write only if the is at least one grid, optionally write with compression. + // Write only if there is at least one grid, optionally write with compression. if (gridsVDB.size()) { int vdb_flags = openvdb::io::COMPRESS_ACTIVE_MASK; switch (compression) { @@ -534,7 +534,8 @@ int writeObjectsVDB(const string &filename, } case COMPRESSION_BLOSC: { # if OPENVDB_BLOSC == 1 - vdb_flags |= openvdb::io::COMPRESS_BLOSC; + // Cannot use |= here, causes segfault with blosc 1.5.0 (== recommended version) + vdb_flags = openvdb::io::COMPRESS_BLOSC; # else debMsg("OpenVDB was built without Blosc support, using Zip compression instead", 1); vdb_flags |= openvdb::io::COMPRESS_ZIP; diff --git a/extern/mantaflow/preprocessed/fluidsolver.h b/extern/mantaflow/preprocessed/fluidsolver.h index 0c871bca3a1..6770f8b7b05 100644 --- a/extern/mantaflow/preprocessed/fluidsolver.h +++ b/extern/mantaflow/preprocessed/fluidsolver.h @@ -384,6 +384,7 @@ class FluidSolver : public PbClass { GridStorage<Real> mGrids4dReal; GridStorage<Vec3> mGrids4dVec; GridStorage<Vec4> mGrids4dVec4; + public: PbArgs _args; } diff --git a/extern/mantaflow/preprocessed/general.h b/extern/mantaflow/preprocessed/general.h index 50eac71e87e..8bf1c2e25de 100644 --- a/extern/mantaflow/preprocessed/general.h +++ b/extern/mantaflow/preprocessed/general.h @@ -42,7 +42,7 @@ inline void updateQtGui(bool full, int frame, float time, const std::string &cur # ifdef _DEBUG # define DEBUG 1 # endif // _DEBUG -#endif // DEBUG +#endif // DEBUG // Standard exception class Error : public std::exception { diff --git a/extern/mantaflow/preprocessed/gitinfo.h b/extern/mantaflow/preprocessed/gitinfo.h index 1bb96fe3baa..03fd0112095 100644 --- a/extern/mantaflow/preprocessed/gitinfo.h +++ b/extern/mantaflow/preprocessed/gitinfo.h @@ -1,3 +1,3 @@ -#define MANTA_GIT_VERSION "commit 39b7a415721ecbf6643612a24e8eadd221aeb934" +#define MANTA_GIT_VERSION "commit 9c505cd22e289b98c9aa717efba8ef3201c7e458" diff --git a/extern/mantaflow/preprocessed/grid.h b/extern/mantaflow/preprocessed/grid.h index 9c3d954771e..2c4296e78dd 100644 --- a/extern/mantaflow/preprocessed/grid.h +++ b/extern/mantaflow/preprocessed/grid.h @@ -389,6 +389,7 @@ class GridBase : public PbClass { Real mDx; bool m3D; // precomputed Z shift: to ensure 2D compatibility, always use this instead of sx*sy ! IndexInt mStrideZ; + public: PbArgs _args; } diff --git a/extern/mantaflow/preprocessed/grid4d.h b/extern/mantaflow/preprocessed/grid4d.h index 9b64116fc9d..1741db590b7 100644 --- a/extern/mantaflow/preprocessed/grid4d.h +++ b/extern/mantaflow/preprocessed/grid4d.h @@ -326,6 +326,7 @@ class Grid4dBase : public PbClass { // precomputed Z,T shift: to ensure 2D compatibility, always use this instead of sx*sy ! IndexInt mStrideZ; IndexInt mStrideT; + public: PbArgs _args; } @@ -950,6 +951,7 @@ template<class T> class Grid4d : public Grid4dBase { protected: T *mData; + public: PbArgs _args; } diff --git a/extern/mantaflow/preprocessed/levelset.h b/extern/mantaflow/preprocessed/levelset.h index db542bb1fe6..ae162f73c3d 100644 --- a/extern/mantaflow/preprocessed/levelset.h +++ b/extern/mantaflow/preprocessed/levelset.h @@ -266,6 +266,7 @@ class LevelsetGrid : public Grid<Real> { } static Real invalidTimeValue(); + public: PbArgs _args; } diff --git a/extern/mantaflow/preprocessed/mesh.h b/extern/mantaflow/preprocessed/mesh.h index d3a69abc4ea..b5de66ce095 100644 --- a/extern/mantaflow/preprocessed/mesh.h +++ b/extern/mantaflow/preprocessed/mesh.h @@ -796,6 +796,7 @@ class Mesh : public PbClass { std::vector<MeshDataImpl<int> *> mMdataInt; //! indicate that mdata of this mesh is copied, and needs to be freed bool mFreeMdata; + public: PbArgs _args; } @@ -881,6 +882,7 @@ class MeshDataBase : public PbClass { protected: Mesh *mMesh; + public: PbArgs _args; } @@ -1645,6 +1647,7 @@ template<class T> class MeshDataImpl : public MeshDataBase { //! optionally , we might have an associated grid from which to grab new data Grid<T> *mpGridSource; //! unfortunately , we need to distinguish mac vs regular vec3 bool mGridSourceMAC; + public: PbArgs _args; } diff --git a/extern/mantaflow/preprocessed/movingobs.h b/extern/mantaflow/preprocessed/movingobs.h index 0661ddf5b37..83ef6ed0c9f 100644 --- a/extern/mantaflow/preprocessed/movingobs.h +++ b/extern/mantaflow/preprocessed/movingobs.h @@ -154,6 +154,7 @@ class MovingObstacle : public PbClass { int mEmptyType; int mID; static int sIDcnt; + public: PbArgs _args; } diff --git a/extern/mantaflow/preprocessed/noisefield.h b/extern/mantaflow/preprocessed/noisefield.h index 73c9de779ef..6ed8ac0012d 100644 --- a/extern/mantaflow/preprocessed/noisefield.h +++ b/extern/mantaflow/preprocessed/noisefield.h @@ -236,6 +236,7 @@ class WaveletNoiseField : public PbClass { static int randomSeed; // global reference count for noise tile static std::atomic<int> mNoiseReferenceCount; + public: PbArgs _args; } diff --git a/extern/mantaflow/preprocessed/particle.h b/extern/mantaflow/preprocessed/particle.h index 7fcc7e5ca32..7e0c64e6d03 100644 --- a/extern/mantaflow/preprocessed/particle.h +++ b/extern/mantaflow/preprocessed/particle.h @@ -205,6 +205,7 @@ class ParticleBase : public PbClass { //! custom seed for particle systems, used by plugins int mSeed; //! fix global random seed storage, used mainly by functions in this class static int globalSeed; + public: PbArgs _args; } @@ -628,6 +629,7 @@ template<class S> class ParticleSystem : public ParticleBase { std::vector<S> mData; //! reduce storage , called by doCompress virtual void compress(); + public: PbArgs _args; } @@ -918,6 +920,7 @@ class ParticleIndexSystem : public ParticleSystem<ParticleIndexData> { return -1; } }; + public: PbArgs _args; } @@ -982,6 +985,7 @@ template<class DATA, class CON> class ConnectedParticleSystem : public ParticleS protected: std::vector<CON> mSegments; virtual void compress(); + public: PbArgs _args; } @@ -1071,6 +1075,7 @@ class ParticleDataBase : public PbClass { protected: ParticleBase *mpParticleSys; + public: PbArgs _args; } @@ -1843,6 +1848,7 @@ template<class T> class ParticleDataImpl : public ParticleDataBase { //! optionally , we might have an associated grid from which to grab new data Grid<T> *mpGridSource; //! unfortunately , we need to distinguish mac vs regular vec3 bool mGridSourceMAC; + public: PbArgs _args; } diff --git a/extern/mantaflow/preprocessed/plugin/meshplugins.cpp b/extern/mantaflow/preprocessed/plugin/meshplugins.cpp index 043660db20b..cf315429d65 100644 --- a/extern/mantaflow/preprocessed/plugin/meshplugins.cpp +++ b/extern/mantaflow/preprocessed/plugin/meshplugins.cpp @@ -234,10 +234,10 @@ void subdivideMesh( normalize(ne2); // Real thisArea = sqrMag(cross(-e2,e0)); - // small angle approximation says sin(x) = arcsin(x) = x, - // arccos(x) = pi/2 - arcsin(x), - // cos(x) = dot(A,B), - // so angle is approximately 1 - dot(A,B). + // small angle approximation says sin(x) = arcsin(x) = x, + // arccos(x) = pi/2 - arcsin(x), + // cos(x) = dot(A,B), + // so angle is approximately 1 - dot(A,B). Real angle[3]; angle[0] = 1.0 - dot(ne0, -ne2); angle[1] = 1.0 - dot(ne1, -ne0); diff --git a/extern/mantaflow/preprocessed/plugin/secondaryparticles.cpp b/extern/mantaflow/preprocessed/plugin/secondaryparticles.cpp index 7a1d8224d94..2f876376f53 100644 --- a/extern/mantaflow/preprocessed/plugin/secondaryparticles.cpp +++ b/extern/mantaflow/preprocessed/plugin/secondaryparticles.cpp @@ -2287,9 +2287,10 @@ struct knFlipComputePotentialTrappedAir : public KernelBase { const Vec3 &vj = scaleFromManta * v.getCentered(x, y, z); const Vec3 xij = xi - xj; const Vec3 vij = vi - vj; - Real h = !pot.is3D() ? 1.414 * radius : - 1.732 * radius; // estimate sqrt(2)*radius resp. sqrt(3)*radius - // for h, due to squared resp. cubic neighbor area + Real h = !pot.is3D() ? + 1.414 * radius : + 1.732 * radius; // estimate sqrt(2)*radius resp. sqrt(3)*radius for h, due + // to squared resp. cubic neighbor area vdiff += norm(vij) * (1 - dot(getNormalized(vij), getNormalized(xij))) * (1 - norm(xij) / h); } diff --git a/extern/mantaflow/preprocessed/shapes.h b/extern/mantaflow/preprocessed/shapes.h index fa645389bfe..5a400eaed09 100644 --- a/extern/mantaflow/preprocessed/shapes.h +++ b/extern/mantaflow/preprocessed/shapes.h @@ -269,6 +269,7 @@ class Shape : public PbClass { protected: GridType mType; + public: PbArgs _args; } @@ -319,6 +320,7 @@ class NullShape : public Shape { { gridSetConst<Real>(phi, 1000.0f); } + public: PbArgs _args; } @@ -394,6 +396,7 @@ class Box : public Shape { protected: Vec3 mP0, mP1; + public: PbArgs _args; } @@ -455,6 +458,7 @@ class Sphere : public Shape { protected: Vec3 mCenter, mScale; Real mRadius; + public: PbArgs _args; } @@ -579,6 +583,7 @@ class Cylinder : public Shape { protected: Vec3 mCenter, mZDir; Real mRadius, mZ; + public: PbArgs _args; } @@ -655,6 +660,7 @@ class Slope : public Shape { Real mAnglexy, mAngleyz; Real mOrigin; Vec3 mGs; + public: PbArgs _args; } diff --git a/extern/mantaflow/preprocessed/turbulencepart.h b/extern/mantaflow/preprocessed/turbulencepart.h index 81c94d77722..5177aeb2d96 100644 --- a/extern/mantaflow/preprocessed/turbulencepart.h +++ b/extern/mantaflow/preprocessed/turbulencepart.h @@ -199,6 +199,7 @@ class TurbulenceParticleSystem : public ParticleSystem<TurbulenceParticleData> { private: WaveletNoiseField &noise; + public: PbArgs _args; } diff --git a/extern/mantaflow/preprocessed/vortexpart.h b/extern/mantaflow/preprocessed/vortexpart.h index e48fbc7f507..8f80cf910eb 100644 --- a/extern/mantaflow/preprocessed/vortexpart.h +++ b/extern/mantaflow/preprocessed/vortexpart.h @@ -127,6 +127,7 @@ class VortexParticleSystem : public ParticleSystem<VortexParticleData> { } virtual ParticleBase *clone(); + public: PbArgs _args; } diff --git a/extern/mantaflow/preprocessed/vortexsheet.h b/extern/mantaflow/preprocessed/vortexsheet.h index 01c32e4e806..0fc0f3a1258 100644 --- a/extern/mantaflow/preprocessed/vortexsheet.h +++ b/extern/mantaflow/preprocessed/vortexsheet.h @@ -240,6 +240,7 @@ class VortexSheetMesh : public Mesh { VorticityChannel mVorticity; TexCoord3Channel mTex1, mTex2; TurbulenceChannel mTurb; + public: PbArgs _args; } diff --git a/intern/clog/clog.c b/intern/clog/clog.c index 50a51ebe913..416ea25ee0c 100644 --- a/intern/clog/clog.c +++ b/intern/clog/clog.c @@ -322,7 +322,9 @@ static bool clg_ctx_filter_check(CLogContext *ctx, const char *identifier) if (flt->match[0] == '*' && flt->match[len - 1] == '*') { char *match = MEM_callocN(sizeof(char) * len - 1, __func__); memcpy(match, flt->match + 1, len - 2); - if (strstr(identifier, match) != NULL) { + const bool success = (strstr(identifier, match) != NULL); + MEM_freeN(match); + if (success) { return (bool)i; } } diff --git a/intern/cycles/blender/addon/engine.py b/intern/cycles/blender/addon/engine.py index dfa696714fb..d8398772a84 100644 --- a/intern/cycles/blender/addon/engine.py +++ b/intern/cycles/blender/addon/engine.py @@ -19,16 +19,16 @@ from __future__ import annotations def _is_using_buggy_driver(): - import bgl + import gpu # We need to be conservative here because in multi-GPU systems display card # might be quite old, but others one might be just good. # # So We shouldn't disable possible good dedicated cards just because display # card seems weak. And instead we only blacklist configurations which are # proven to cause problems. - if bgl.glGetString(bgl.GL_VENDOR) == "ATI Technologies Inc.": + if gpu.platform.vendor_get() == "ATI Technologies Inc.": import re - version = bgl.glGetString(bgl.GL_VERSION) + version = gpu.platform.version_get() if version.endswith("Compatibility Profile Context"): # Old HD 4xxx and 5xxx series drivers did not have driver version # in the version string, but those cards do not quite work and diff --git a/intern/cycles/blender/addon/ui.py b/intern/cycles/blender/addon/ui.py index 9aea7725810..ce93bd96bd5 100644 --- a/intern/cycles/blender/addon/ui.py +++ b/intern/cycles/blender/addon/ui.py @@ -725,7 +725,7 @@ class CYCLES_RENDER_PT_performance_tiles(CyclesButtonsPanel, Panel): col.prop(cscene, "tile_order", text="Order") sub = col.column() - sub.active = not rd.use_save_buffers + sub.active = not rd.use_save_buffers and not cscene.use_adaptive_sampling sub.prop(cscene, "use_progressive_refine") diff --git a/intern/cycles/blender/blender_object.cpp b/intern/cycles/blender/blender_object.cpp index 19f49acddd7..cb84013c551 100644 --- a/intern/cycles/blender/blender_object.cpp +++ b/intern/cycles/blender/blender_object.cpp @@ -96,7 +96,49 @@ bool BlenderSync::object_is_light(BL::Object &b_ob) return (b_ob_data && b_ob_data.is_a(&RNA_Light)); } -/* Object */ +void BlenderSync::sync_object_motion_init(BL::Object &b_parent, BL::Object &b_ob, Object *object) +{ + /* Initialize motion blur for object, detecting if it's enabled and creating motion + * steps array if so. */ + array<Transform> motion; + object->set_motion(motion); + + Scene::MotionType need_motion = scene->need_motion(); + if (need_motion == Scene::MOTION_NONE || !object->get_geometry()) { + return; + } + + Geometry *geom = object->get_geometry(); + geom->set_use_motion_blur(false); + geom->set_motion_steps(0); + + uint motion_steps; + + if (need_motion == Scene::MOTION_BLUR) { + motion_steps = object_motion_steps(b_parent, b_ob, Object::MAX_MOTION_STEPS); + geom->set_motion_steps(motion_steps); + if (motion_steps && object_use_deform_motion(b_parent, b_ob)) { + geom->set_use_motion_blur(true); + } + } + else { + motion_steps = 3; + geom->set_motion_steps(motion_steps); + } + + motion.resize(motion_steps, transform_empty()); + + if (motion_steps) { + motion[motion_steps / 2] = object->get_tfm(); + + /* update motion socket before trying to access object->motion_time */ + object->set_motion(motion); + + for (size_t step = 0; step < motion_steps; step++) { + motion_times.insert(object->motion_time(step)); + } + } +} Object *BlenderSync::sync_object(BL::Depsgraph &b_depsgraph, BL::ViewLayer &b_view_layer, @@ -219,10 +261,8 @@ Object *BlenderSync::sync_object(BL::Depsgraph &b_depsgraph, } /* test if we need to sync */ - bool object_updated = false; - - if (object_map.add_or_update(&object, b_ob, b_parent, key)) - object_updated = true; + bool object_updated = object_map.add_or_update(&object, b_ob, b_parent, key) || + (tfm != object->get_tfm()); /* mesh sync */ /* b_ob is owned by the iterator and will go out of scope at the end of the block. @@ -271,49 +311,11 @@ Object *BlenderSync::sync_object(BL::Depsgraph &b_depsgraph, * transform comparison should not be needed, but duplis don't work perfect * in the depsgraph and may not signal changes, so this is a workaround */ if (object->is_modified() || object_updated || - (object->get_geometry() && object->get_geometry()->is_modified()) || - tfm != object->get_tfm()) { + (object->get_geometry() && object->get_geometry()->is_modified())) { object->name = b_ob.name().c_str(); object->set_pass_id(b_ob.pass_index()); object->set_color(get_float3(b_ob.color())); object->set_tfm(tfm); - array<Transform> motion; - object->set_motion(motion); - - /* motion blur */ - Scene::MotionType need_motion = scene->need_motion(); - if (need_motion != Scene::MOTION_NONE && object->get_geometry()) { - Geometry *geom = object->get_geometry(); - geom->set_use_motion_blur(false); - geom->set_motion_steps(0); - - uint motion_steps; - - if (need_motion == Scene::MOTION_BLUR) { - motion_steps = object_motion_steps(b_parent, b_ob, Object::MAX_MOTION_STEPS); - geom->set_motion_steps(motion_steps); - if (motion_steps && object_use_deform_motion(b_parent, b_ob)) { - geom->set_use_motion_blur(true); - } - } - else { - motion_steps = 3; - geom->set_motion_steps(motion_steps); - } - - motion.resize(motion_steps, transform_empty()); - - if (motion_steps) { - motion[motion_steps / 2] = tfm; - - /* update motion socket before trying to access object->motion_time */ - object->set_motion(motion); - - for (size_t step = 0; step < motion_steps; step++) { - motion_times.insert(object->motion_time(step)); - } - } - } /* dupli texture coordinates and random_id */ if (is_instance) { @@ -331,6 +333,8 @@ Object *BlenderSync::sync_object(BL::Depsgraph &b_depsgraph, object->tag_update(scene); } + sync_object_motion_init(b_parent, b_ob, object); + if (is_instance) { /* Sync possible particle data. */ sync_dupli_particle(b_parent, b_instance, object); @@ -613,7 +617,7 @@ void BlenderSync::sync_motion(BL::RenderSettings &b_render, if (b_cam) { sync_camera_motion(b_render, b_cam, width, height, 0.0f); } - sync_objects(b_depsgraph, b_v3d, 0.0f); + sync_objects(b_depsgraph, b_v3d); } /* Insert motion times from camera. Motion times from other objects diff --git a/intern/cycles/blender/blender_shader.cpp b/intern/cycles/blender/blender_shader.cpp index 72328333732..736c10e8881 100644 --- a/intern/cycles/blender/blender_shader.cpp +++ b/intern/cycles/blender/blender_shader.cpp @@ -1553,13 +1553,9 @@ void BlenderSync::sync_lights(BL::Depsgraph &b_depsgraph, bool update_all) void BlenderSync::sync_shaders(BL::Depsgraph &b_depsgraph, BL::SpaceView3D &b_v3d) { /* for auto refresh images */ - bool auto_refresh_update = false; - - if (preview) { - ImageManager *image_manager = scene->image_manager; - int frame = b_scene.frame_current(); - auto_refresh_update = image_manager->set_animation_frame_update(frame); - } + ImageManager *image_manager = scene->image_manager; + const int frame = b_scene.frame_current(); + const bool auto_refresh_update = image_manager->set_animation_frame_update(frame); shader_map.pre_sync(); diff --git a/intern/cycles/blender/blender_sync.cpp b/intern/cycles/blender/blender_sync.cpp index aa87ac1dd81..9d0f9f29f94 100644 --- a/intern/cycles/blender/blender_sync.cpp +++ b/intern/cycles/blender/blender_sync.cpp @@ -69,7 +69,8 @@ BlenderSync::BlenderSync(BL::RenderEngine &b_engine, experimental(false), dicing_rate(1.0f), max_subdivisions(12), - progress(progress) + progress(progress), + has_updates_(true) { PointerRNA cscene = RNA_pointer_get(&b_scene.ptr, "cycles"); dicing_rate = preview ? RNA_float_get(&cscene, "preview_dicing_rate") : @@ -84,7 +85,9 @@ BlenderSync::~BlenderSync() void BlenderSync::reset(BL::BlendData &b_data, BL::Scene &b_scene) { /* Update data and scene pointers in case they change in session reset, - * for example after undo. */ + * for example after undo. + * Note that we do not modify the `has_updates_` flag here because the sync + * reset is also used during viewport navigation. */ this->b_data = b_data; this->b_scene = b_scene; } @@ -117,6 +120,8 @@ void BlenderSync::sync_recalc(BL::Depsgraph &b_depsgraph, BL::SpaceView3D &b_v3d } if (dicing_prop_changed) { + has_updates_ = true; + for (const pair<const GeometryKey, Geometry *> &iter : geometry_map.key_to_scene_data()) { Geometry *geom = iter.second; if (geom->is_mesh()) { @@ -133,6 +138,12 @@ void BlenderSync::sync_recalc(BL::Depsgraph &b_depsgraph, BL::SpaceView3D &b_v3d /* Iterate over all IDs in this depsgraph. */ for (BL::DepsgraphUpdate &b_update : b_depsgraph.updates) { + /* TODO(sergey): Can do more selective filter here. For example, ignore changes made to + * screen datablock. Note that sync_data() needs to be called after object deletion, and + * currently this is ensured by the scene ID tagged for update, which sets the `has_updates_` + * flag. */ + has_updates_ = true; + BL::ID b_id(b_update.id()); /* Material */ @@ -227,6 +238,10 @@ void BlenderSync::sync_data(BL::RenderSettings &b_render, int height, void **python_thread_state) { + if (!has_updates_) { + return; + } + scoped_timer timer; BL::ViewLayer b_view_layer = b_depsgraph.view_layer_eval(); @@ -254,6 +269,8 @@ void BlenderSync::sync_data(BL::RenderSettings &b_render, free_data_after_sync(b_depsgraph); VLOG(1) << "Total time spent synchronizing data: " << timer.get_time(); + + has_updates_ = false; } /* Integrator */ @@ -739,12 +756,18 @@ void BlenderSync::free_data_after_sync(BL::Depsgraph &b_depsgraph) * caches to be releases from blender side in order to reduce peak memory * footprint during synchronization process. */ + const bool is_interface_locked = b_engine.render() && b_engine.render().use_lock_interface(); - const bool can_free_caches = (BlenderSession::headless || is_interface_locked) && - /* Baking re-uses the depsgraph multiple times, clearing crashes - * reading un-evaluated mesh data which isn't aligned with the - * geometry we're baking, see T71012. */ - !scene->bake_manager->get_baking(); + const bool is_persistent_data = b_engine.render() && b_engine.render().use_persistent_data(); + const bool can_free_caches = + (BlenderSession::headless || is_interface_locked) && + /* Baking re-uses the depsgraph multiple times, clearing crashes + * reading un-evaluated mesh data which isn't aligned with the + * geometry we're baking, see T71012. */ + !scene->bake_manager->get_baking() && + /* Persistent data must main caches for performance and correctness. */ + !is_persistent_data; + if (!can_free_caches) { return; } @@ -869,6 +892,9 @@ SessionParams BlenderSync::get_session_params(BL::RenderEngine &b_engine, /* Clamp samples. */ params.samples = min(params.samples, Integrator::MAX_SAMPLES); + /* Adaptive sampling. */ + params.adaptive_sampling = RNA_boolean_get(&cscene, "use_adaptive_sampling"); + /* tiles */ const bool is_cpu = (params.device.type == DEVICE_CPU); if (!is_cpu && !background) { @@ -921,7 +947,7 @@ SessionParams BlenderSync::get_session_params(BL::RenderEngine &b_engine, BL::RenderSettings b_r = b_scene.render(); params.progressive_refine = b_engine.is_preview() || get_boolean(cscene, "use_progressive_refine"); - if (b_r.use_save_buffers()) + if (b_r.use_save_buffers() || params.adaptive_sampling) params.progressive_refine = false; if (background) { @@ -957,8 +983,6 @@ SessionParams BlenderSync::get_session_params(BL::RenderEngine &b_engine, params.use_profiling = params.device.has_profiling && !b_engine.is_preview() && background && BlenderSession::print_render_stats; - params.adaptive_sampling = RNA_boolean_get(&cscene, "use_adaptive_sampling"); - return params; } diff --git a/intern/cycles/blender/blender_sync.h b/intern/cycles/blender/blender_sync.h index 787189da182..15a10f2b46b 100644 --- a/intern/cycles/blender/blender_sync.h +++ b/intern/cycles/blender/blender_sync.h @@ -150,6 +150,7 @@ class BlenderSync { BlenderObjectCulling &culling, bool *use_portal, TaskPool *geom_task_pool); + void sync_object_motion_init(BL::Object &b_parent, BL::Object &b_ob, Object *object); bool sync_object_attributes(BL::DepsgraphObjectInstance &b_instance, Object *object); @@ -263,6 +264,12 @@ class BlenderSync { } view_layer; Progress &progress; + + protected: + /* Indicates that `sync_recalc()` detected changes in the scene. + * If this flag is false then the data is considered to be up-to-date and will not be + * synchronized at all. */ + bool has_updates_ = true; }; CCL_NAMESPACE_END diff --git a/intern/cycles/bvh/bvh_optix.cpp b/intern/cycles/bvh/bvh_optix.cpp index d630e8965dc..cd266f72f89 100644 --- a/intern/cycles/bvh/bvh_optix.cpp +++ b/intern/cycles/bvh/bvh_optix.cpp @@ -17,6 +17,8 @@ #ifdef WITH_OPTIX +# include "device/device.h" + # include "bvh/bvh_optix.h" CCL_NAMESPACE_BEGIN @@ -26,6 +28,7 @@ BVHOptiX::BVHOptiX(const BVHParams ¶ms_, const vector<Object *> &objects_, Device *device) : BVH(params_, geometry_, objects_), + device(device), traversable_handle(0), as_data(device, params_.top_level ? "optix tlas" : "optix blas", false), motion_transform_data(device, "optix motion transform", false) @@ -34,7 +37,9 @@ BVHOptiX::BVHOptiX(const BVHParams ¶ms_, BVHOptiX::~BVHOptiX() { - // Acceleration structure memory is freed via the 'as_data' destructor + // Acceleration structure memory is delayed freed on device, since deleting the + // BVH may happen while still being used for rendering. + device->release_optix_bvh(this); } CCL_NAMESPACE_END diff --git a/intern/cycles/bvh/bvh_optix.h b/intern/cycles/bvh/bvh_optix.h index aa514beae0d..ba5d90471d1 100644 --- a/intern/cycles/bvh/bvh_optix.h +++ b/intern/cycles/bvh/bvh_optix.h @@ -28,6 +28,7 @@ CCL_NAMESPACE_BEGIN class BVHOptiX : public BVH { public: + Device *device; uint64_t traversable_handle; device_only_memory<char> as_data; device_only_memory<char> motion_transform_data; diff --git a/intern/cycles/device/device.h b/intern/cycles/device/device.h index b5468248e5a..bdf18d09b31 100644 --- a/intern/cycles/device/device.h +++ b/intern/cycles/device/device.h @@ -61,7 +61,6 @@ enum DeviceTypeMask { }; enum DeviceKernelStatus { - DEVICE_KERNEL_WAITING_FOR_FEATURE_KERNEL = 0, DEVICE_KERNEL_FEATURE_KERNEL_AVAILABLE, DEVICE_KERNEL_USING_FEATURE_KERNEL, DEVICE_KERNEL_FEATURE_KERNEL_INVALID, @@ -427,6 +426,9 @@ class Device { /* acceleration structure building */ virtual void build_bvh(BVH *bvh, Progress &progress, bool refit); + /* OptiX specific destructor. */ + virtual void release_optix_bvh(BVH *){}; + #ifdef WITH_NETWORK /* networking */ void server_run(); diff --git a/intern/cycles/device/device_memory.cpp b/intern/cycles/device/device_memory.cpp index 9eee86b0814..80a05fc32fe 100644 --- a/intern/cycles/device/device_memory.cpp +++ b/intern/cycles/device/device_memory.cpp @@ -35,10 +35,54 @@ device_memory::device_memory(Device *device, const char *name, MemoryType type) device_pointer(0), host_pointer(0), shared_pointer(0), - shared_counter(0) + shared_counter(0), + original_device_ptr(0), + original_device_size(0), + original_device(0), + need_realloc_(false), + modified(false) { } +device_memory::device_memory(device_memory &&other) noexcept + : data_type(other.data_type), + data_elements(other.data_elements), + data_size(other.data_size), + device_size(other.device_size), + data_width(other.data_width), + data_height(other.data_height), + data_depth(other.data_depth), + type(other.type), + name(other.name), + device(other.device), + device_pointer(other.device_pointer), + host_pointer(other.host_pointer), + shared_pointer(other.shared_pointer), + shared_counter(other.shared_counter), + original_device_ptr(other.original_device_ptr), + original_device_size(other.original_device_size), + original_device(other.original_device), + need_realloc_(other.need_realloc_), + modified(other.modified) +{ + other.data_elements = 0; + other.data_size = 0; + other.device_size = 0; + other.data_width = 0; + other.data_height = 0; + other.data_depth = 0; + other.device = 0; + other.device_pointer = 0; + other.host_pointer = 0; + other.shared_pointer = 0; + other.shared_counter = 0; + other.original_device_ptr = 0; + other.original_device_size = 0; + other.original_device = 0; + other.need_realloc_ = false; + other.modified = false; +} + device_memory::~device_memory() { assert(shared_pointer == 0); diff --git a/intern/cycles/device/device_memory.h b/intern/cycles/device/device_memory.h index 97459b9ae6a..80f4d7b0468 100644 --- a/intern/cycles/device/device_memory.h +++ b/intern/cycles/device/device_memory.h @@ -238,6 +238,7 @@ class device_memory { /* Only create through subclasses. */ device_memory(Device *device, const char *name, MemoryType type); + device_memory(device_memory &&other) noexcept; /* No copying allowed. */ device_memory(const device_memory &) = delete; @@ -277,6 +278,10 @@ template<typename T> class device_only_memory : public device_memory { data_elements = max(device_type_traits<T>::num_elements, 1); } + device_only_memory(device_only_memory &&other) noexcept : device_memory(std::move(other)) + { + } + virtual ~device_only_memory() { free(); diff --git a/intern/cycles/device/device_multi.cpp b/intern/cycles/device/device_multi.cpp index 35faadcbec5..85ffa5fcd52 100644 --- a/intern/cycles/device/device_multi.cpp +++ b/intern/cycles/device/device_multi.cpp @@ -232,10 +232,6 @@ class MultiDevice : public Device { foreach (SubDevice &sub, devices) { DeviceKernelStatus subresult = sub.device->get_active_kernel_switch_state(); switch (subresult) { - case DEVICE_KERNEL_WAITING_FOR_FEATURE_KERNEL: - result = subresult; - break; - case DEVICE_KERNEL_FEATURE_KERNEL_INVALID: case DEVICE_KERNEL_FEATURE_KERNEL_AVAILABLE: return subresult; diff --git a/intern/cycles/device/device_optix.cpp b/intern/cycles/device/device_optix.cpp index cce11507fa1..01de0724cb2 100644 --- a/intern/cycles/device/device_optix.cpp +++ b/intern/cycles/device/device_optix.cpp @@ -193,6 +193,9 @@ class OptiXDevice : public CUDADevice { device_only_memory<unsigned char> denoiser_state; int denoiser_input_passes = 0; + vector<device_only_memory<char>> delayed_free_bvh_memory; + thread_mutex delayed_free_bvh_mutex; + public: OptiXDevice(DeviceInfo &info_, Stats &stats_, Profiler &profiler_, bool background_) : CUDADevice(info_, stats_, profiler_, background_), @@ -258,6 +261,8 @@ class OptiXDevice : public CUDADevice { // Make CUDA context current const CUDAContextScope scope(cuContext); + free_bvh_memory_delayed(); + sbt_data.free(); texture_info.free(); launch_params.free(); @@ -1297,6 +1302,8 @@ class OptiXDevice : public CUDADevice { return; } + free_bvh_memory_delayed(); + BVHOptiX *const bvh_optix = static_cast<BVHOptiX *>(bvh); progress.set_substatus("Building OptiX acceleration structure"); @@ -1767,6 +1774,24 @@ class OptiXDevice : public CUDADevice { } } + void release_optix_bvh(BVH *bvh) override + { + thread_scoped_lock lock(delayed_free_bvh_mutex); + /* Do delayed free of BVH memory, since geometry holding BVH might be deleted + * while GPU is still rendering. */ + BVHOptiX *const bvh_optix = static_cast<BVHOptiX *>(bvh); + + delayed_free_bvh_memory.emplace_back(std::move(bvh_optix->as_data)); + delayed_free_bvh_memory.emplace_back(std::move(bvh_optix->motion_transform_data)); + bvh_optix->traversable_handle = 0; + } + + void free_bvh_memory_delayed() + { + thread_scoped_lock lock(delayed_free_bvh_mutex); + delayed_free_bvh_memory.free_memory(); + } + void const_copy_to(const char *name, void *host, size_t size) override { // Set constant memory for CUDA module diff --git a/intern/cycles/device/opencl/device_opencl.h b/intern/cycles/device/opencl/device_opencl.h index 2d6c6d04214..a65e764b0d4 100644 --- a/intern/cycles/device/opencl/device_opencl.h +++ b/intern/cycles/device/opencl/device_opencl.h @@ -269,7 +269,6 @@ class OpenCLDevice : public Device { cl_device_id cdDevice; cl_int ciErr; int device_num; - bool use_preview_kernels; class OpenCLProgram { public: @@ -369,8 +368,7 @@ class OpenCLDevice : public Device { /* Load the kernels and put the created kernels in the given * `programs` parameter. */ void load_kernels(vector<OpenCLProgram *> &programs, - const DeviceRequestedFeatures &requested_features, - bool is_preview = false); + const DeviceRequestedFeatures &requested_features); }; DeviceSplitKernel *split_kernel; @@ -382,7 +380,6 @@ class OpenCLDevice : public Device { OpenCLProgram denoising_program; OpenCLSplitPrograms kernel_programs; - OpenCLSplitPrograms preview_programs; typedef map<string, device_vector<uchar> *> ConstMemMap; typedef map<string, device_ptr> MemMap; @@ -412,7 +409,6 @@ class OpenCLDevice : public Device { string device_md5_hash(string kernel_custom_build_options = ""); bool load_kernels(const DeviceRequestedFeatures &requested_features); void load_required_kernels(const DeviceRequestedFeatures &requested_features); - void load_preview_kernels(); bool wait_for_availability(const DeviceRequestedFeatures &requested_features); DeviceKernelStatus get_active_kernel_switch_state(); @@ -422,8 +418,7 @@ class OpenCLDevice : public Device { /* Get the program file name to compile (*.cl) for the given kernel */ const string get_opencl_program_filename(const string &kernel_name); string get_build_options(const DeviceRequestedFeatures &requested_features, - const string &opencl_program_name, - bool preview_kernel = false); + const string &opencl_program_name); /* Enable the default features to reduce recompilation events */ void enable_default_features(DeviceRequestedFeatures &features); diff --git a/intern/cycles/device/opencl/device_opencl_impl.cpp b/intern/cycles/device/opencl/device_opencl_impl.cpp index d378d32914c..715213175c9 100644 --- a/intern/cycles/device/opencl/device_opencl_impl.cpp +++ b/intern/cycles/device/opencl/device_opencl_impl.cpp @@ -107,8 +107,7 @@ void OpenCLDevice::enable_default_features(DeviceRequestedFeatures &features) } string OpenCLDevice::get_build_options(const DeviceRequestedFeatures &requested_features, - const string &opencl_program_name, - bool preview_kernel) + const string &opencl_program_name) { /* first check for non-split kernel programs */ if (opencl_program_name == "base" || opencl_program_name == "denoising") { @@ -185,13 +184,7 @@ string OpenCLDevice::get_build_options(const DeviceRequestedFeatures &requested_ enable_default_features(nofeatures); /* Add program specific optimized compile directives */ - if (preview_kernel) { - DeviceRequestedFeatures preview_features; - preview_features.use_hair = true; - build_options += "-D__KERNEL_AO_PREVIEW__ "; - build_options += preview_features.get_build_options(); - } - else if (opencl_program_name == "split_do_volume" && !requested_features.use_volume) { + if (opencl_program_name == "split_do_volume" && !requested_features.use_volume) { build_options += nofeatures.get_build_options(); } else { @@ -238,9 +231,7 @@ OpenCLDevice::OpenCLSplitPrograms::~OpenCLSplitPrograms() } void OpenCLDevice::OpenCLSplitPrograms::load_kernels( - vector<OpenCLProgram *> &programs, - const DeviceRequestedFeatures &requested_features, - bool is_preview) + vector<OpenCLProgram *> &programs, const DeviceRequestedFeatures &requested_features) { if (!requested_features.use_baking) { # define ADD_SPLIT_KERNEL_BUNDLE_PROGRAM(kernel_name) \ @@ -251,7 +242,7 @@ void OpenCLDevice::OpenCLSplitPrograms::load_kernels( device, \ program_name_##kernel_name, \ "kernel_" #kernel_name ".cl", \ - device->get_build_options(requested_features, program_name_##kernel_name, is_preview)); \ + device->get_build_options(requested_features, program_name_##kernel_name)); \ program_##kernel_name.add_kernel(ustring("path_trace_" #kernel_name)); \ programs.push_back(&program_##kernel_name); @@ -259,7 +250,7 @@ void OpenCLDevice::OpenCLSplitPrograms::load_kernels( ADD_SPLIT_KERNEL_PROGRAM(subsurface_scatter); ADD_SPLIT_KERNEL_PROGRAM(direct_lighting); ADD_SPLIT_KERNEL_PROGRAM(indirect_background); - if (requested_features.use_volume || is_preview) { + if (requested_features.use_volume) { ADD_SPLIT_KERNEL_PROGRAM(do_volume); } ADD_SPLIT_KERNEL_PROGRAM(shader_eval); @@ -274,7 +265,7 @@ void OpenCLDevice::OpenCLSplitPrograms::load_kernels( device, "split_bundle", "kernel_split_bundle.cl", - device->get_build_options(requested_features, "split_bundle", is_preview)); + device->get_build_options(requested_features, "split_bundle")); ADD_SPLIT_KERNEL_BUNDLE_PROGRAM(data_init); ADD_SPLIT_KERNEL_BUNDLE_PROGRAM(state_buffer_size); @@ -403,7 +394,7 @@ class OpenCLSplitKernel : public DeviceSplitKernel { device, program_name, device->get_opencl_program_filename(kernel_name), - device->get_build_options(requested_features, program_name, device->use_preview_kernels)); + device->get_build_options(requested_features, program_name)); kernel->program.add_kernel(ustring("path_trace_" + kernel_name)); kernel->program.load(); @@ -569,6 +560,11 @@ class OpenCLSplitKernel : public DeviceSplitKernel { size_t num_elements = max_elements_for_max_buffer_size(kg, data, max_buffer_size); int2 global_size = make_int2(max(round_down((int)sqrt(num_elements), 64), 64), (int)sqrt(num_elements)); + + if (device->info.description.find("Intel") != string::npos) { + global_size = make_int2(min(512, global_size.x), min(512, global_size.y)); + } + VLOG(1) << "Global size: " << global_size << "."; return global_size; } @@ -612,7 +608,6 @@ OpenCLDevice::OpenCLDevice(DeviceInfo &info, Stats &stats, Profiler &profiler, b : Device(info, stats, profiler, background), load_kernel_num_compiling(0), kernel_programs(this), - preview_programs(this), memory_manager(this), texture_info(this, "__texture_info", MEM_GLOBAL) { @@ -622,7 +617,6 @@ OpenCLDevice::OpenCLDevice(DeviceInfo &info, Stats &stats, Profiler &profiler, b cqCommandQueue = NULL; device_initialized = false; textures_need_update = true; - use_preview_kernels = !background; vector<OpenCLPlatformDevice> usable_devices; OpenCLInfo::get_usable_devices(&usable_devices); @@ -678,9 +672,6 @@ OpenCLDevice::OpenCLDevice(DeviceInfo &info, Stats &stats, Profiler &profiler, b device_initialized = true; split_kernel = new OpenCLSplitKernel(this); - if (use_preview_kernels) { - load_preview_kernels(); - } } OpenCLDevice::~OpenCLDevice() @@ -771,7 +762,7 @@ bool OpenCLDevice::load_kernels(const DeviceRequestedFeatures &requested_feature load_required_kernels(requested_features); vector<OpenCLProgram *> programs; - kernel_programs.load_kernels(programs, requested_features, false); + kernel_programs.load_kernels(programs, requested_features); if (!requested_features.use_baking && requested_features.use_denoising) { denoising_program = OpenCLProgram( @@ -849,19 +840,6 @@ void OpenCLDevice::load_required_kernels(const DeviceRequestedFeatures &requeste } } -void OpenCLDevice::load_preview_kernels() -{ - DeviceRequestedFeatures no_features; - vector<OpenCLProgram *> programs; - preview_programs.load_kernels(programs, no_features, true); - - foreach (OpenCLProgram *program, programs) { - if (!program->load()) { - load_required_kernel_task_pool.push(function_bind(&OpenCLProgram::compile, program)); - } - } -} - bool OpenCLDevice::wait_for_availability(const DeviceRequestedFeatures &requested_features) { if (requested_features.use_baking) { @@ -869,59 +847,18 @@ bool OpenCLDevice::wait_for_availability(const DeviceRequestedFeatures &requeste return true; } - if (background) { - load_kernel_task_pool.wait_work(); - use_preview_kernels = false; - } - else { - /* We use a device setting to determine to load preview kernels or not - * Better to check on device level than per kernel as mixing preview and - * non-preview kernels does not work due to different data types */ - if (use_preview_kernels) { - use_preview_kernels = load_kernel_num_compiling.load() > 0; - } - } + load_kernel_task_pool.wait_work(); return split_kernel->load_kernels(requested_features); } OpenCLDevice::OpenCLSplitPrograms *OpenCLDevice::get_split_programs() { - return use_preview_kernels ? &preview_programs : &kernel_programs; + return &kernel_programs; } DeviceKernelStatus OpenCLDevice::get_active_kernel_switch_state() { - /* Do not switch kernels for background renderings - * We do foreground rendering but use the preview kernels - * Check for the optimized kernels - * - * This works also the other way around, where we are using - * optimized kernels but new ones are being compiled due - * to other features that are needed */ - if (background) { - /* The if-statements below would find the same result, - * But as the `finished` method uses a mutex we added - * this as an early exit */ - return DEVICE_KERNEL_USING_FEATURE_KERNEL; - } - - bool other_kernels_finished = load_kernel_num_compiling.load() == 0; - if (use_preview_kernels) { - if (other_kernels_finished) { - return DEVICE_KERNEL_FEATURE_KERNEL_AVAILABLE; - } - else { - return DEVICE_KERNEL_WAITING_FOR_FEATURE_KERNEL; - } - } - else { - if (other_kernels_finished) { - return DEVICE_KERNEL_USING_FEATURE_KERNEL; - } - else { - return DEVICE_KERNEL_FEATURE_KERNEL_INVALID; - } - } + return DEVICE_KERNEL_USING_FEATURE_KERNEL; } void OpenCLDevice::mem_alloc(device_memory &mem) diff --git a/intern/cycles/kernel/closure/alloc.h b/intern/cycles/kernel/closure/alloc.h index 341d1e16eb1..99a5a675976 100644 --- a/intern/cycles/kernel/closure/alloc.h +++ b/intern/cycles/kernel/closure/alloc.h @@ -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)); + + /* 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; - float sample_weight = fabsf(average(weight)); - sc->sample_weight = sample_weight; - return (sample_weight >= CLOSURE_WEIGHT_CUTOFF) ? sc : NULL; + 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 diff --git a/intern/cycles/kernel/kernel_subsurface.h b/intern/cycles/kernel/kernel_subsurface.h index b70df747a73..dd922b86722 100644 --- a/intern/cycles/kernel/kernel_subsurface.h +++ b/intern/cycles/kernel/kernel_subsurface.h @@ -25,8 +25,9 @@ CCL_NAMESPACE_BEGIN ccl_device_inline float3 subsurface_scatter_eval(ShaderData *sd, const ShaderClosure *sc, float disk_r, float r, bool all) { - /* this is the veach one-sample model with balance heuristic, some pdf - * factors drop out when using balance heuristic weighting */ + /* This is the Veach one-sample model with balance heuristic, some pdf + * factors drop out when using balance heuristic weighting. For branched + * path tracing (all) we sample all closure and don't use MIS. */ float3 eval_sum = zero_float3(); float pdf_sum = 0.0f; float sample_weight_inv = 0.0f; @@ -65,6 +66,30 @@ subsurface_scatter_eval(ShaderData *sd, const ShaderClosure *sc, float disk_r, f return (pdf_sum > 0.0f) ? eval_sum / pdf_sum : zero_float3(); } +ccl_device_inline float3 subsurface_scatter_walk_eval(ShaderData *sd, + const ShaderClosure *sc, + float3 throughput, + bool all) +{ + /* This is the Veach one-sample model with balance heuristic, some pdf + * factors drop out when using balance heuristic weighting. For branched + * path tracing (all) we sample all closure and don't use MIS. */ + if (!all) { + float bssrdf_weight = 0.0f; + float weight = sc->sample_weight; + + for (int i = 0; i < sd->num_closure; i++) { + sc = &sd->closure[i]; + + if (CLOSURE_IS_BSSRDF(sc->type)) { + bssrdf_weight += sc->sample_weight; + } + } + throughput *= bssrdf_weight / weight; + } + return throughput; +} + /* replace closures with a single diffuse bsdf closure after scatter step */ ccl_device void subsurface_scatter_setup_diffuse_bsdf( KernelGlobals *kg, ShaderData *sd, ClosureType type, float roughness, float3 weight, float3 N) @@ -437,7 +462,8 @@ ccl_device_noinline ccl_addr_space PathState *state, const ShaderClosure *sc, const float bssrdf_u, - const float bssrdf_v) + const float bssrdf_v, + bool all) { /* Sample diffuse surface scatter into the object. */ float3 D; @@ -669,7 +695,7 @@ ccl_device_noinline /* TODO: gain back performance lost from merging with disk BSSRDF. We * only need to return on hit so this indirect ray push/pop overhead * is not actually needed, but it does keep the code simpler. */ - ss_isect->weight[0] = throughput; + ss_isect->weight[0] = subsurface_scatter_walk_eval(sd, sc, throughput, all); #ifdef __SPLIT_KERNEL__ ss_isect->ray = *ray; #endif @@ -691,7 +717,7 @@ ccl_device_inline int subsurface_scatter_multi_intersect(KernelGlobals *kg, return subsurface_scatter_disk(kg, ss_isect, sd, sc, lcg_state, bssrdf_u, bssrdf_v, all); } else { - return subsurface_random_walk(kg, ss_isect, sd, state, sc, bssrdf_u, bssrdf_v); + return subsurface_random_walk(kg, ss_isect, sd, state, sc, bssrdf_u, bssrdf_v, all); } } diff --git a/intern/cycles/kernel/kernel_types.h b/intern/cycles/kernel/kernel_types.h index 18c4d2f86ad..74fa2826cd4 100644 --- a/intern/cycles/kernel/kernel_types.h +++ b/intern/cycles/kernel/kernel_types.h @@ -99,27 +99,23 @@ CCL_NAMESPACE_BEGIN #define __AO__ #define __PASSES__ #define __HAIR__ - -/* Without these we get an AO render, used by OpenCL preview kernel. */ -#ifndef __KERNEL_AO_PREVIEW__ -# define __SVM__ -# define __EMISSION__ -# define __HOLDOUT__ -# define __MULTI_CLOSURE__ -# define __TRANSPARENT_SHADOWS__ -# define __BACKGROUND_MIS__ -# define __LAMP_MIS__ -# define __CAMERA_MOTION__ -# define __OBJECT_MOTION__ -# define __BAKING__ -# define __PRINCIPLED__ -# define __SUBSURFACE__ -# define __VOLUME__ -# define __VOLUME_SCATTER__ -# define __CMJ__ -# define __SHADOW_RECORD_ALL__ -# define __BRANCHED_PATH__ -#endif +#define __SVM__ +#define __EMISSION__ +#define __HOLDOUT__ +#define __MULTI_CLOSURE__ +#define __TRANSPARENT_SHADOWS__ +#define __BACKGROUND_MIS__ +#define __LAMP_MIS__ +#define __CAMERA_MOTION__ +#define __OBJECT_MOTION__ +#define __BAKING__ +#define __PRINCIPLED__ +#define __SUBSURFACE__ +#define __VOLUME__ +#define __VOLUME_SCATTER__ +#define __CMJ__ +#define __SHADOW_RECORD_ALL__ +#define __BRANCHED_PATH__ /* Device specific features */ #ifdef __KERNEL_CPU__ diff --git a/intern/cycles/kernel/svm/svm_tex_coord.h b/intern/cycles/kernel/svm/svm_tex_coord.h index 4fe940f1a67..fc46bb584be 100644 --- a/intern/cycles/kernel/svm/svm_tex_coord.h +++ b/intern/cycles/kernel/svm/svm_tex_coord.h @@ -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 */ diff --git a/intern/cycles/render/alembic.cpp b/intern/cycles/render/alembic.cpp index 5842f4c313d..cf345ee075d 100644 --- a/intern/cycles/render/alembic.cpp +++ b/intern/cycles/render/alembic.cpp @@ -498,12 +498,8 @@ void AlembicObject::load_data_in_cache(CachedData &cached_data, /* Use the schema as the base compound property to also be able to look for top level properties. */ - read_attributes(proc, - cached_data, - schema.getArbGeomParams(), - schema.getUVsParam(), - get_requested_attributes(), - progress); + read_attributes( + proc, cached_data, schema, schema.getUVsParam(), get_requested_attributes(), progress); cached_data.invalidate_last_loaded_time(true); data_loaded = true; diff --git a/intern/cycles/render/alembic_read.cpp b/intern/cycles/render/alembic_read.cpp index 9fdc7561957..4d09c40b07b 100644 --- a/intern/cycles/render/alembic_read.cpp +++ b/intern/cycles/render/alembic_read.cpp @@ -894,6 +894,10 @@ static void parse_requested_attributes_recursive(const AttributeRequestSet &requ const ICompoundProperty &arb_geom_params, vector<PropHeaderAndParent> &requested_properties) { + if (!arb_geom_params.valid()) { + return; + } + for (const AttributeRequest &req : requested_attributes.requests) { const PropertyHeader *property_header = arb_geom_params.getPropertyHeader(req.name.c_str()); diff --git a/intern/cycles/render/geometry.cpp b/intern/cycles/render/geometry.cpp index aac5af06e9f..16fc36231b4 100644 --- a/intern/cycles/render/geometry.cpp +++ b/intern/cycles/render/geometry.cpp @@ -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) { diff --git a/intern/cycles/render/geometry.h b/intern/cycles/render/geometry.h index abdd851a089..7db122f69cb 100644 --- a/intern/cycles/render/geometry.h +++ b/intern/cycles/render/geometry.h @@ -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, diff --git a/intern/cycles/render/hair.cpp b/intern/cycles/render/hair.cpp index dad235aa340..72fc612c0c0 100644 --- a/intern/cycles/render/hair.cpp +++ b/intern/cycles/render/hair.cpp @@ -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 - - 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; + 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); + + 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; + } } } } diff --git a/intern/cycles/render/hair.h b/intern/cycles/render/hair.h index 4b949f984e5..1a8f422e8c4 100644 --- a/intern/cycles/render/hair.h +++ b/intern/cycles/render/hair.h @@ -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 diff --git a/intern/cycles/render/mesh.cpp b/intern/cycles/render/mesh.cpp index d5e5b960665..fd9879dd5dd 100644 --- a/intern/cycles/render/mesh.cpp +++ b/intern/cycles/render/mesh.cpp @@ -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) { + /* 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]; + for (size_t k = 0; k < num_prims; ++k) { + prim_visibility[k] = visibility; + } + } + + 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]; - 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; + 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; + } } } - 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_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]]); + } } } diff --git a/intern/cycles/render/mesh.h b/intern/cycles/render/mesh.h index 2b0ff92ab62..e9e79f7f20d 100644 --- a/intern/cycles/render/mesh.h +++ b/intern/cycles/render/mesh.h @@ -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); diff --git a/intern/cycles/render/object.cpp b/intern/cycles/render/object.cpp index 8b3ac31cf8b..f65f8bc6e90 100644 --- a/intern/cycles/render/object.cpp +++ b/intern/cycles/render/object.cpp @@ -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); } diff --git a/intern/cycles/render/object.h b/intern/cycles/render/object.h index 23682270fd1..e4bc7ac3d8e 100644 --- a/intern/cycles/render/object.h +++ b/intern/cycles/render/object.h @@ -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, diff --git a/intern/cycles/render/osl.h b/intern/cycles/render/osl.h index c9eaf26e70a..f6aa98d867a 100644 --- a/intern/cycles/render/osl.h +++ b/intern/cycles/render/osl.h @@ -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); diff --git a/intern/cycles/render/scene.cpp b/intern/cycles/render/scene.cpp index f753bb43c42..c4e7d2c79d6 100644 --- a/intern/cycles/render/scene.cpp +++ b/intern/cycles/render/scene.cpp @@ -542,9 +542,6 @@ bool Scene::update(Progress &progress, bool &kernel_switch_needed) DeviceKernelStatus kernel_switch_status = device->get_active_kernel_switch_state(); kernel_switch_needed = kernel_switch_status == DEVICE_KERNEL_FEATURE_KERNEL_AVAILABLE || kernel_switch_status == DEVICE_KERNEL_FEATURE_KERNEL_INVALID; - if (kernel_switch_status == DEVICE_KERNEL_WAITING_FOR_FEATURE_KERNEL) { - progress.set_kernel_status("Compiling render kernels"); - } if (new_kernels_needed || kernel_switch_needed) { progress.set_kernel_status("Compiling render kernels"); device->wait_for_availability(loaded_kernel_features); diff --git a/intern/cycles/render/session.cpp b/intern/cycles/render/session.cpp index 3c601e18126..7830ca2293a 100644 --- a/intern/cycles/render/session.cpp +++ b/intern/cycles/render/session.cpp @@ -243,11 +243,6 @@ void Session::run_gpu() } } - /* Don't go in pause mode when image was rendered with preview kernels - * When feature kernels become available the session will be reset. */ - else if (no_tiles && kernel_state == DEVICE_KERNEL_WAITING_FOR_FEATURE_KERNEL) { - time_sleep(0.1); - } else if (no_tiles && kernel_state == DEVICE_KERNEL_FEATURE_KERNEL_AVAILABLE) { reset_gpu(tile_manager.params, params.samples); } @@ -762,11 +757,6 @@ void Session::run_cpu() } } - /* Don't go in pause mode when preview kernels are used - * When feature kernels become available the session will be reset. */ - else if (no_tiles && kernel_state == DEVICE_KERNEL_WAITING_FOR_FEATURE_KERNEL) { - time_sleep(0.1); - } else if (no_tiles && kernel_state == DEVICE_KERNEL_FEATURE_KERNEL_AVAILABLE) { reset_cpu(tile_manager.params, params.samples); } diff --git a/intern/cycles/render/svm.h b/intern/cycles/render/svm.h index 85b4b9c419f..d23ff3e2a47 100644 --- a/intern/cycles/render/svm.h +++ b/intern/cycles/render/svm.h @@ -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, diff --git a/intern/cycles/util/util_vector.h b/intern/cycles/util/util_vector.h index 04fb33368d9..87cd4de8438 100644 --- a/intern/cycles/util/util_vector.h +++ b/intern/cycles/util/util_vector.h @@ -43,8 +43,8 @@ class vector : public std::vector<value_type, allocator_type> { /* Try as hard as possible to use zero memory. */ void free_memory() { - BaseClass::resize(0); - BaseClass::shrink_to_fit(); + vector<value_type, allocator_type> empty; + BaseClass::swap(empty); } /* Some external API might demand working with std::vector. */ diff --git a/intern/ffmpeg/ffmpeg_compat.h b/intern/ffmpeg/ffmpeg_compat.h index 727fd4b9601..54a004d53e2 100644 --- a/intern/ffmpeg/ffmpeg_compat.h +++ b/intern/ffmpeg/ffmpeg_compat.h @@ -22,10 +22,17 @@ #include <libavformat/avformat.h> -/* check our ffmpeg is new enough, avoids user complaints */ -#if (LIBAVFORMAT_VERSION_MAJOR < 52) || \ - ((LIBAVFORMAT_VERSION_MAJOR == 52) && (LIBAVFORMAT_VERSION_MINOR <= 64)) -# error "FFmpeg 0.7 or newer is needed, Upgrade your FFmpeg or disable it" +/* Check if our ffmpeg is new enough, avoids user complaints. + * Minimum supported version is currently 3.2.0 which mean the following library versions: + * libavutil > 55.30 + * libavcodec > 57.60 + * libavformat > 57.50 + * + * We only check for one of these as they are usually updated in tandem. + */ +#if (LIBAVFORMAT_VERSION_MAJOR < 57) || \ + ((LIBAVFORMAT_VERSION_MAJOR == 57) && (LIBAVFORMAT_VERSION_MINOR <= 50)) +# error "FFmpeg 3.2.0 or newer is needed, Upgrade your FFmpeg or disable it" #endif /* end sanity check */ @@ -36,274 +43,6 @@ # define FFMPEG_INLINE static inline #endif -#include <libavcodec/avcodec.h> -#include <libavutil/mathematics.h> -#include <libavutil/opt.h> -#include <libavutil/rational.h> - -#if (LIBAVFORMAT_VERSION_MAJOR > 52) || \ - ((LIBAVFORMAT_VERSION_MAJOR >= 52) && (LIBAVFORMAT_VERSION_MINOR >= 101)) -# define FFMPEG_HAVE_PARSE_UTILS 1 -# include <libavutil/parseutils.h> -#endif - -#include <libswscale/swscale.h> - -#if (LIBAVFORMAT_VERSION_MAJOR > 52) || \ - ((LIBAVFORMAT_VERSION_MAJOR >= 52) && (LIBAVFORMAT_VERSION_MINOR >= 105)) -# define FFMPEG_HAVE_AVIO 1 -#endif - -#if (LIBAVCODEC_VERSION_MAJOR > 53) || \ - ((LIBAVCODEC_VERSION_MAJOR == 53) && (LIBAVCODEC_VERSION_MINOR > 1)) || \ - ((LIBAVCODEC_VERSION_MAJOR == 53) && (LIBAVCODEC_VERSION_MINOR == 1) && \ - (LIBAVCODEC_VERSION_MICRO >= 1)) || \ - ((LIBAVCODEC_VERSION_MAJOR == 52) && (LIBAVCODEC_VERSION_MINOR >= 121)) -# define FFMPEG_HAVE_DEFAULT_VAL_UNION 1 -#endif - -#if (LIBAVFORMAT_VERSION_MAJOR > 52) || \ - ((LIBAVFORMAT_VERSION_MAJOR >= 52) && (LIBAVFORMAT_VERSION_MINOR >= 101)) -# define FFMPEG_HAVE_AV_DUMP_FORMAT 1 -#endif - -#if (LIBAVFORMAT_VERSION_MAJOR > 52) || \ - ((LIBAVFORMAT_VERSION_MAJOR >= 52) && (LIBAVFORMAT_VERSION_MINOR >= 45)) -# define FFMPEG_HAVE_AV_GUESS_FORMAT 1 -#endif - -#if (LIBAVCODEC_VERSION_MAJOR > 52) || \ - ((LIBAVCODEC_VERSION_MAJOR >= 52) && (LIBAVCODEC_VERSION_MINOR >= 23)) -# define FFMPEG_HAVE_DECODE_AUDIO3 1 -# define FFMPEG_HAVE_DECODE_VIDEO2 1 -#endif - -#if (LIBAVCODEC_VERSION_MAJOR > 52) || \ - ((LIBAVCODEC_VERSION_MAJOR >= 52) && (LIBAVCODEC_VERSION_MINOR >= 64)) -# define FFMPEG_HAVE_AVMEDIA_TYPES 1 -#endif - -#if ((LIBAVCODEC_VERSION_MAJOR > 52) || \ - (LIBAVCODEC_VERSION_MAJOR >= 52) && (LIBAVCODEC_VERSION_MINOR >= 29)) && \ - ((LIBSWSCALE_VERSION_MAJOR > 0) || \ - (LIBSWSCALE_VERSION_MAJOR >= 0) && (LIBSWSCALE_VERSION_MINOR >= 10)) -# define FFMPEG_SWSCALE_COLOR_SPACE_SUPPORT -#endif - -#if ((LIBAVCODEC_VERSION_MAJOR > 54) || \ - (LIBAVCODEC_VERSION_MAJOR >= 54) && (LIBAVCODEC_VERSION_MINOR > 14)) -# define FFMPEG_HAVE_CANON_H264_RESOLUTION_FIX -#endif - -#if ((LIBAVCODEC_VERSION_MAJOR > 53) || \ - (LIBAVCODEC_VERSION_MAJOR >= 53) && (LIBAVCODEC_VERSION_MINOR >= 60)) -# define FFMPEG_HAVE_ENCODE_AUDIO2 -#endif - -#if ((LIBAVCODEC_VERSION_MAJOR > 53) || \ - (LIBAVCODEC_VERSION_MAJOR >= 53) && (LIBAVCODEC_VERSION_MINOR >= 42)) -# define FFMPEG_HAVE_DECODE_AUDIO4 -#endif - -#if ((LIBAVCODEC_VERSION_MAJOR > 54) || \ - (LIBAVCODEC_VERSION_MAJOR >= 54) && (LIBAVCODEC_VERSION_MINOR >= 13)) -# define FFMPEG_HAVE_AVFRAME_SAMPLE_RATE -#endif - -#if ((LIBAVUTIL_VERSION_MAJOR > 51) || \ - (LIBAVUTIL_VERSION_MAJOR == 51) && (LIBAVUTIL_VERSION_MINOR >= 21)) -# define FFMPEG_FFV1_ALPHA_SUPPORTED -# define FFMPEG_SAMPLE_FMT_S16P_SUPPORTED -#else - -FFMPEG_INLINE -int av_sample_fmt_is_planar(enum AVSampleFormat sample_fmt) -{ - /* no planar formats in FFmpeg < 0.9 */ - (void)sample_fmt; - return 0; -} - -#endif - -/* XXX TODO Probably fix to correct modern flags in code? Not sure how old FFMPEG we want to - * support though, so for now this will do. */ - -#ifndef FF_MIN_BUFFER_SIZE -# ifdef AV_INPUT_BUFFER_MIN_SIZE -# define FF_MIN_BUFFER_SIZE AV_INPUT_BUFFER_MIN_SIZE -# endif -#endif - -#ifndef FF_INPUT_BUFFER_PADDING_SIZE -# ifdef AV_INPUT_BUFFER_PADDING_SIZE -# define FF_INPUT_BUFFER_PADDING_SIZE AV_INPUT_BUFFER_PADDING_SIZE -# endif -#endif - -#ifndef CODEC_FLAG_GLOBAL_HEADER -# ifdef AV_CODEC_FLAG_GLOBAL_HEADER -# define CODEC_FLAG_GLOBAL_HEADER AV_CODEC_FLAG_GLOBAL_HEADER -# endif -#endif - -#ifndef CODEC_FLAG_GLOBAL_HEADER -# ifdef AV_CODEC_FLAG_GLOBAL_HEADER -# define CODEC_FLAG_GLOBAL_HEADER AV_CODEC_FLAG_GLOBAL_HEADER -# endif -#endif - -#ifndef CODEC_FLAG_INTERLACED_DCT -# ifdef AV_CODEC_FLAG_INTERLACED_DCT -# define CODEC_FLAG_INTERLACED_DCT AV_CODEC_FLAG_INTERLACED_DCT -# endif -#endif - -#ifndef CODEC_FLAG_INTERLACED_ME -# ifdef AV_CODEC_FLAG_INTERLACED_ME -# define CODEC_FLAG_INTERLACED_ME AV_CODEC_FLAG_INTERLACED_ME -# endif -#endif - -/* FFmpeg upstream 1.0 is the first who added AV_ prefix. */ -#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(54, 59, 100) -# define AV_CODEC_ID_NONE CODEC_ID_NONE -# define AV_CODEC_ID_MPEG4 CODEC_ID_MPEG4 -# define AV_CODEC_ID_MJPEG CODEC_ID_MJPEG -# define AV_CODEC_ID_DNXHD CODEC_ID_DNXHD -# define AV_CODEC_ID_MPEG2VIDEO CODEC_ID_MPEG2VIDEO -# define AV_CODEC_ID_MPEG1VIDEO CODEC_ID_MPEG1VIDEO -# define AV_CODEC_ID_DVVIDEO CODEC_ID_DVVIDEO -# define AV_CODEC_ID_THEORA CODEC_ID_THEORA -# define AV_CODEC_ID_PNG CODEC_ID_PNG -# define AV_CODEC_ID_QTRLE CODEC_ID_QTRLE -# define AV_CODEC_ID_FFV1 CODEC_ID_FFV1 -# define AV_CODEC_ID_HUFFYUV CODEC_ID_HUFFYUV -# define AV_CODEC_ID_H264 CODEC_ID_H264 -# define AV_CODEC_ID_FLV1 CODEC_ID_FLV1 - -# define AV_CODEC_ID_AAC CODEC_ID_AAC -# define AV_CODEC_ID_AC3 CODEC_ID_AC3 -# define AV_CODEC_ID_MP3 CODEC_ID_MP3 -# define AV_CODEC_ID_MP2 CODEC_ID_MP2 -# define AV_CODEC_ID_FLAC CODEC_ID_FLAC -# define AV_CODEC_ID_PCM_U8 CODEC_ID_PCM_U8 -# define AV_CODEC_ID_PCM_S16LE CODEC_ID_PCM_S16LE -# define AV_CODEC_ID_PCM_S24LE CODEC_ID_PCM_S24LE -# define AV_CODEC_ID_PCM_S32LE CODEC_ID_PCM_S32LE -# define AV_CODEC_ID_PCM_F32LE CODEC_ID_PCM_F32LE -# define AV_CODEC_ID_PCM_F64LE CODEC_ID_PCM_F64LE -# define AV_CODEC_ID_VORBIS CODEC_ID_VORBIS -#endif - -FFMPEG_INLINE -int av_get_cropped_height_from_codec(AVCodecContext *pCodecCtx) -{ - int y = pCodecCtx->height; - -#ifndef FFMPEG_HAVE_CANON_H264_RESOLUTION_FIX - /* really bad hack to remove this dreadfull black bar at the bottom - with Canon footage and old ffmpeg versions. - (to fix this properly in older ffmpeg versions one has to write a new - demuxer...) - - see the actual fix here for reference: - - http://git.libav.org/?p=libav.git;a=commit;h=30f515091c323da59c0f1b533703dedca2f4b95d - - We do our best to apply this only to matching footage. -*/ - if (pCodecCtx->width == 1920 && pCodecCtx->height == 1088 && - pCodecCtx->pix_fmt == PIX_FMT_YUVJ420P && pCodecCtx->codec_id == AV_CODEC_ID_H264) { - y = 1080; - } -#endif - - return y; -} - -#if ((LIBAVUTIL_VERSION_MAJOR < 51) || \ - (LIBAVUTIL_VERSION_MAJOR == 51) && (LIBAVUTIL_VERSION_MINOR < 22)) -FFMPEG_INLINE -int av_opt_set(void *obj, const char *name, const char *val, int search_flags) -{ - const AVOption *rv = NULL; - (void)search_flags; - av_set_string3(obj, name, val, 1, &rv); - return rv != NULL; -} - -FFMPEG_INLINE -int av_opt_set_int(void *obj, const char *name, int64_t val, int search_flags) -{ - const AVOption *rv = NULL; - (void)search_flags; - rv = av_set_int(obj, name, val); - return rv != NULL; -} - -FFMPEG_INLINE -int av_opt_set_double(void *obj, const char *name, double val, int search_flags) -{ - const AVOption *rv = NULL; - (void)search_flags; - rv = av_set_double(obj, name, val); - return rv != NULL; -} - -# define AV_OPT_TYPE_INT FF_OPT_TYPE_INT -# define AV_OPT_TYPE_INT64 FF_OPT_TYPE_INT64 -# define AV_OPT_TYPE_STRING FF_OPT_TYPE_STRING -# define AV_OPT_TYPE_CONST FF_OPT_TYPE_CONST -# define AV_OPT_TYPE_DOUBLE FF_OPT_TYPE_DOUBLE -# define AV_OPT_TYPE_FLOAT FF_OPT_TYPE_FLOAT -#endif - -#if ((LIBAVUTIL_VERSION_MAJOR < 51) || \ - (LIBAVUTIL_VERSION_MAJOR == 51) && (LIBAVUTIL_VERSION_MINOR < 54)) -FFMPEG_INLINE -enum AVSampleFormat av_get_packed_sample_fmt(enum AVSampleFormat sample_fmt) -{ - if (sample_fmt < 0 || sample_fmt >= AV_SAMPLE_FMT_NB) - return AV_SAMPLE_FMT_NONE; - return sample_fmt; -} -#endif - -#if ((LIBAVCODEC_VERSION_MAJOR < 53) || \ - (LIBAVCODEC_VERSION_MAJOR == 53 && LIBAVCODEC_VERSION_MINOR < 35)) -FFMPEG_INLINE -int avcodec_open2(AVCodecContext *avctx, AVCodec *codec, AVDictionary **options) -{ - /* TODO: no options are taking into account */ - (void)options; - return avcodec_open(avctx, codec); -} -#endif - -#if ((LIBAVFORMAT_VERSION_MAJOR < 53) || \ - (LIBAVFORMAT_VERSION_MAJOR == 53 && LIBAVFORMAT_VERSION_MINOR < 21)) -FFMPEG_INLINE -AVStream *avformat_new_stream(AVFormatContext *s, AVCodec *c) -{ - /* TODO: no codec is taking into account */ - (void)c; - return av_new_stream(s, 0); -} - -FFMPEG_INLINE -int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options) -{ - /* TODO: no options are taking into account */ - (void)options; - return av_find_stream_info(ic); -} -#endif - -#if ((LIBAVFORMAT_VERSION_MAJOR > 53) || \ - ((LIBAVFORMAT_VERSION_MAJOR == 53) && (LIBAVFORMAT_VERSION_MINOR > 32)) || \ - ((LIBAVFORMAT_VERSION_MAJOR == 53) && (LIBAVFORMAT_VERSION_MINOR == 24) && \ - (LIBAVFORMAT_VERSION_MICRO >= 100))) FFMPEG_INLINE void my_update_cur_dts(AVFormatContext *s, AVStream *ref_st, int64_t timestamp) { @@ -323,103 +62,12 @@ void av_update_cur_dts(AVFormatContext *s, AVStream *ref_st, int64_t timestamp) { my_update_cur_dts(s, ref_st, timestamp); } -#endif - -#if ((LIBAVCODEC_VERSION_MAJOR < 54) || \ - (LIBAVCODEC_VERSION_MAJOR == 54 && LIBAVCODEC_VERSION_MINOR < 28)) -FFMPEG_INLINE -void avcodec_free_frame(AVFrame **frame) -{ - /* don't need to do anything with old AVFrame - * since it does not have malloced members */ - (void)frame; -} -#endif - -#if ((LIBAVCODEC_VERSION_MAJOR > 54) || \ - (LIBAVCODEC_VERSION_MAJOR >= 54) && (LIBAVCODEC_VERSION_MINOR >= 13)) -# define FFMPEG_HAVE_AVFRAME_SAMPLE_RATE -#endif - -#if ((LIBAVCODEC_VERSION_MAJOR > 54) || \ - (LIBAVCODEC_VERSION_MAJOR == 54 && LIBAVCODEC_VERSION_MINOR >= 13)) -# define FFMPEG_HAVE_FRAME_CHANNEL_LAYOUT -#endif - -#ifndef FFMPEG_HAVE_AVIO -# define AVIO_FLAG_WRITE URL_WRONLY -# define avio_open url_fopen -# define avio_tell url_ftell -# define avio_close url_fclose -# define avio_size url_fsize -#endif - -/* There are some version in between, which have avio_... functions but no - * AVIO_FLAG_... */ -#ifndef AVIO_FLAG_WRITE -# define AVIO_FLAG_WRITE URL_WRONLY -#endif - -#ifndef AV_PKT_FLAG_KEY -# define AV_PKT_FLAG_KEY PKT_FLAG_KEY -#endif - -#ifndef FFMPEG_HAVE_AV_DUMP_FORMAT -# define av_dump_format dump_format -#endif - -#ifndef FFMPEG_HAVE_AV_GUESS_FORMAT -# define av_guess_format guess_format -#endif - -#ifndef FFMPEG_HAVE_PARSE_UTILS -# define av_parse_video_rate av_parse_video_frame_rate -#endif - -#ifdef FFMPEG_HAVE_DEFAULT_VAL_UNION -# define FFMPEG_DEF_OPT_VAL_INT(OPT) OPT->default_val.i64 -# define FFMPEG_DEF_OPT_VAL_DOUBLE(OPT) OPT->default_val.dbl -#else -# define FFMPEG_DEF_OPT_VAL_INT(OPT) OPT->default_val -# define FFMPEG_DEF_OPT_VAL_DOUBLE(OPT) OPT->default_val -#endif - -#ifndef FFMPEG_HAVE_AVMEDIA_TYPES -# define AVMEDIA_TYPE_VIDEO CODEC_TYPE_VIDEO -# define AVMEDIA_TYPE_AUDIO CODEC_TYPE_AUDIO -#endif - -#ifndef FFMPEG_HAVE_DECODE_AUDIO3 -FFMPEG_INLINE -int avcodec_decode_audio3(AVCodecContext *avctx, - int16_t *samples, - int *frame_size_ptr, - AVPacket *avpkt) -{ - return avcodec_decode_audio2(avctx, samples, frame_size_ptr, avpkt->data, avpkt->size); -} -#endif - -#ifndef FFMPEG_HAVE_DECODE_VIDEO2 -FFMPEG_INLINE -int avcodec_decode_video2(AVCodecContext *avctx, - AVFrame *picture, - int *got_picture_ptr, - AVPacket *avpkt) -{ - return avcodec_decode_video(avctx, picture, got_picture_ptr, avpkt->data, avpkt->size); -} -#endif FFMPEG_INLINE int64_t av_get_pts_from_frame(AVFormatContext *avctx, AVFrame *picture) { int64_t pts; -#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(55, 34, 100) pts = picture->pts; -#else - pts = picture->pkt_pts; -#endif if (pts == AV_NOPTS_VALUE) { pts = picture->pkt_dts; @@ -432,124 +80,16 @@ int64_t av_get_pts_from_frame(AVFormatContext *avctx, AVFrame *picture) return pts; } -/* obsolete constant formerly defined in FFMpeg libavcodec/avcodec.h */ -#ifndef AVCODEC_MAX_AUDIO_FRAME_SIZE -# define AVCODEC_MAX_AUDIO_FRAME_SIZE 192000 // 1 second of 48khz 32bit audio -#endif - -#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(54, 1, 0) -FFMPEG_INLINE -int avcodec_encode_video2(AVCodecContext *avctx, - AVPacket *pkt, - const AVFrame *frame, - int *got_output) -{ - int outsize, ret; - - ret = av_new_packet(pkt, avctx->width * avctx->height * 7 + 10000); - if (ret < 0) - return ret; - - outsize = avcodec_encode_video(avctx, pkt->data, pkt->size, frame); - if (outsize <= 0) { - *got_output = 0; - av_free_packet(pkt); - } - else { - *got_output = 1; - av_shrink_packet(pkt, outsize); - if (avctx->coded_frame) { - pkt->pts = avctx->coded_frame->pts; - if (avctx->coded_frame->key_frame) - pkt->flags |= AV_PKT_FLAG_KEY; - } - } - - return outsize >= 0 ? 0 : outsize; -} - -#endif - -#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(53, 17, 0) -FFMPEG_INLINE -void avformat_close_input(AVFormatContext **ctx) -{ - av_close_input_file(*ctx); - *ctx = NULL; -} -#endif - -#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(52, 8, 0) -FFMPEG_INLINE -AVFrame *av_frame_alloc(void) -{ - return avcodec_alloc_frame(); -} - -FFMPEG_INLINE -void av_frame_free(AVFrame **frame) -{ - av_freep(frame); -} -#endif - -FFMPEG_INLINE -const char *av_get_metadata_key_value(AVDictionary *metadata, const char *key) -{ - if (metadata == NULL) { - return NULL; - } - AVDictionaryEntry *tag = NULL; - while ((tag = av_dict_get(metadata, "", tag, AV_DICT_IGNORE_SUFFIX))) { - if (!strcmp(tag->key, key)) { - return tag->value; - } - } - return NULL; -} - -FFMPEG_INLINE -bool av_check_encoded_with_ffmpeg(AVFormatContext *ctx) -{ - const char *encoder = av_get_metadata_key_value(ctx->metadata, "ENCODER"); - if (encoder != NULL && !strncmp(encoder, "Lavf", 4)) { - return true; - } - return false; -} - -#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(51, 32, 0) -# define AV_OPT_SEARCH_FAKE_OBJ 0 -#endif - -#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(54, 59, 100) -# define FFMPEG_HAVE_DEPRECATED_FLAGS2 -#endif - -/* Since FFmpeg-1.1 this constant have AV_ prefix. */ -#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(52, 3, 100) -# define AV_PIX_FMT_BGR32 PIX_FMT_BGR32 -# define AV_PIX_FMT_YUV422P PIX_FMT_YUV422P -# define AV_PIX_FMT_BGRA PIX_FMT_BGRA -# define AV_PIX_FMT_ARGB PIX_FMT_ARGB -# define AV_PIX_FMT_RGBA PIX_FMT_RGBA -#endif - -/* New API from FFmpeg-2.0 which soon became recommended one. */ -#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(52, 38, 100) -# define av_frame_alloc avcodec_alloc_frame -# define av_frame_free avcodec_free_frame -# define av_frame_unref avcodec_get_frame_defaults -#endif - -#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 24, 102) - -/* NOTE: The code in this block are from FFmpeg 2.6.4, which is licensed by LGPL. */ +/* -------------------------------------------------------------------- */ +/** \name Deinterlace code block + * + * NOTE: The code in this block are from FFmpeg 2.6.4, which is licensed by LGPL. + * \{ */ -# define MAX_NEG_CROP 1024 +#define MAX_NEG_CROP 1024 -# define times4(x) x, x, x, x -# define times256(x) times4(times4(times4(times4(times4(x))))) +#define times4(x) x, x, x, x +#define times256(x) times4(times4(times4(times4(times4(x))))) static const uint8_t ff_compat_crop_tab[256 + 2 * MAX_NEG_CROP] = { times256(0x00), 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, @@ -575,8 +115,8 @@ static const uint8_t ff_compat_crop_tab[256 + 2 * MAX_NEG_CROP] = { 0xEF, 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, times256(0xFF)}; -# undef times4 -# undef times256 +#undef times4 +#undef times256 /* filter parameters: [-1 4 2 4 -1] // 8 */ FFMPEG_INLINE @@ -668,8 +208,9 @@ int deinterlace_bottom_field_inplace(uint8_t *src1, int src_wrap, int width, int uint8_t *src_m1, *src_0, *src_p1, *src_p2; int y; uint8_t *buf = (uint8_t *)av_malloc(width); - if (!buf) + if (!buf) { return AVERROR(ENOMEM); + } src_m1 = src1; memcpy(buf, src_m1, width); @@ -689,24 +230,21 @@ int deinterlace_bottom_field_inplace(uint8_t *src1, int src_wrap, int width, int return 0; } -# ifdef __GNUC__ -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wdeprecated-declarations" -# endif - FFMPEG_INLINE -int avpicture_deinterlace( - AVPicture *dst, const AVPicture *src, enum AVPixelFormat pix_fmt, int width, int height) +int av_image_deinterlace( + AVFrame *dst, const AVFrame *src, enum AVPixelFormat pix_fmt, int width, int height) { int i, ret; if (pix_fmt != AV_PIX_FMT_YUV420P && pix_fmt != AV_PIX_FMT_YUVJ420P && pix_fmt != AV_PIX_FMT_YUV422P && pix_fmt != AV_PIX_FMT_YUVJ422P && pix_fmt != AV_PIX_FMT_YUV444P && pix_fmt != AV_PIX_FMT_YUV411P && - pix_fmt != AV_PIX_FMT_GRAY8) + pix_fmt != AV_PIX_FMT_GRAY8) { return -1; - if ((width & 3) != 0 || (height & 3) != 0) + } + if ((width & 3) != 0 || (height & 3) != 0) { return -1; + } for (i = 0; i < 3; i++) { if (i == 1) { @@ -732,8 +270,9 @@ int avpicture_deinterlace( } if (src == dst) { ret = deinterlace_bottom_field_inplace(dst->data[i], dst->linesize[i], width, height); - if (ret < 0) + if (ret < 0) { return ret; + } } else { deinterlace_bottom_field( @@ -743,10 +282,6 @@ int avpicture_deinterlace( return 0; } -# ifdef __GNUC__ -# pragma GCC diagnostic pop -# endif - -#endif +/** \} Deinterlace code block */ #endif diff --git a/intern/ghost/CMakeLists.txt b/intern/ghost/CMakeLists.txt index 1739659ab88..f90e8a973bf 100644 --- a/intern/ghost/CMakeLists.txt +++ b/intern/ghost/CMakeLists.txt @@ -79,6 +79,7 @@ set(SRC intern/GHOST_SystemPaths.h intern/GHOST_TimerManager.h intern/GHOST_TimerTask.h + intern/GHOST_Util.h intern/GHOST_Window.h intern/GHOST_WindowManager.h ) @@ -438,6 +439,7 @@ endif() if(WITH_XR_OPENXR) list(APPEND SRC intern/GHOST_Xr.cpp + intern/GHOST_XrAction.cpp intern/GHOST_XrContext.cpp intern/GHOST_XrEvent.cpp intern/GHOST_XrGraphicsBinding.cpp @@ -446,6 +448,7 @@ if(WITH_XR_OPENXR) GHOST_IXrContext.h intern/GHOST_IXrGraphicsBinding.h + intern/GHOST_XrAction.h intern/GHOST_XrContext.h intern/GHOST_XrException.h intern/GHOST_XrSession.h diff --git a/intern/ghost/GHOST_C-api.h b/intern/ghost/GHOST_C-api.h index c776eb6b44c..2bd9af6df5c 100644 --- a/intern/ghost/GHOST_C-api.h +++ b/intern/ghost/GHOST_C-api.h @@ -1059,7 +1059,110 @@ int GHOST_XrSessionNeedsUpsideDownDrawing(const GHOST_XrContextHandle xr_context * \returns GHOST_kSuccess if any event was handled, otherwise GHOST_kFailure. */ GHOST_TSuccess GHOST_XrEventsHandle(GHOST_XrContextHandle xr_context); -#endif + +/* actions */ +/** + * Create an OpenXR action set for input/output. + */ +int GHOST_XrCreateActionSet(GHOST_XrContextHandle xr_context, const GHOST_XrActionSetInfo *info); + +/** + * Destroy a previously created OpenXR action set. + */ +void GHOST_XrDestroyActionSet(GHOST_XrContextHandle xr_context, const char *action_set_name); + +/** + * Create OpenXR input/output actions. + */ +int GHOST_XrCreateActions(GHOST_XrContextHandle xr_context, + const char *action_set_name, + GHOST_TUns32 count, + const GHOST_XrActionInfo *infos); + +/** + * Destroy previously created OpenXR actions. + */ +void GHOST_XrDestroyActions(GHOST_XrContextHandle xr_context, + const char *action_set_name, + GHOST_TUns32 count, + const char *const *action_names); + +/** + * Create spaces for pose-based OpenXR actions. + */ +int GHOST_XrCreateActionSpaces(GHOST_XrContextHandle xr_context, + const char *action_set_name, + GHOST_TUns32 count, + const GHOST_XrActionSpaceInfo *infos); + +/** + * Destroy previously created spaces for OpenXR actions. + */ +void GHOST_XrDestroyActionSpaces(GHOST_XrContextHandle xr_context, + const char *action_set_name, + GHOST_TUns32 count, + const GHOST_XrActionSpaceInfo *infos); + +/** + * Create input/output path bindings for OpenXR actions. + */ +int GHOST_XrCreateActionBindings(GHOST_XrContextHandle xr_context, + const char *action_set_name, + GHOST_TUns32 count, + const GHOST_XrActionProfileInfo *infos); + +/** + * Destroy previously created bindings for OpenXR actions. + */ +void GHOST_XrDestroyActionBindings(GHOST_XrContextHandle xr_context, + const char *action_set_name, + GHOST_TUns32 count, + const GHOST_XrActionProfileInfo *infos); + +/** + * Attach all created action sets to the current OpenXR session. + */ +int GHOST_XrAttachActionSets(GHOST_XrContextHandle xr_context); + +/** + * Update button/tracking states for OpenXR actions. + * + * \param action_set_name: The name of the action set to sync. If NULL, all action sets + * attached to the session will be synced. + */ +int GHOST_XrSyncActions(GHOST_XrContextHandle xr_context, const char *action_set_name); + +/** + * Apply an OpenXR haptic output action. + */ +int GHOST_XrApplyHapticAction(GHOST_XrContextHandle xr_context, + const char *action_set_name, + const char *action_name, + const GHOST_TInt64 *duration, + const float *frequency, + const float *amplitude); + +/** + * Stop a previously applied OpenXR haptic output action. + */ +void GHOST_XrStopHapticAction(GHOST_XrContextHandle xr_context, + const char *action_set_name, + const char *action_name); + +/** + * Get action set custom data (owned by Blender, not GHOST). + */ +void *GHOST_XrGetActionSetCustomdata(GHOST_XrContextHandle xr_context, + const char *action_set_name); + +/** + * Get action custom data (owned by Blender, not GHOST). + */ +void *GHOST_XrGetActionCustomdata(GHOST_XrContextHandle xr_context, + const char *action_set_name, + const char *action_name); + +#endif /* WITH_XR_OPENXR */ #ifdef __cplusplus } diff --git a/intern/ghost/GHOST_IXrContext.h b/intern/ghost/GHOST_IXrContext.h index dd266a3b6ae..86fe78814a7 100644 --- a/intern/ghost/GHOST_IXrContext.h +++ b/intern/ghost/GHOST_IXrContext.h @@ -22,6 +22,8 @@ #include "GHOST_Types.h" +class GHOST_XrSession; + class GHOST_IXrContext { public: virtual ~GHOST_IXrContext() = default; @@ -31,6 +33,10 @@ class GHOST_IXrContext { virtual bool isSessionRunning() const = 0; virtual void drawSessionViews(void *draw_customdata) = 0; + /* Needed for the GHOST C api. */ + virtual GHOST_XrSession *getSession() = 0; + virtual const GHOST_XrSession *getSession() const = 0; + virtual void dispatchErrorMessage(const class GHOST_XrException *) const = 0; virtual void setGraphicsContextBindFuncs(GHOST_XrGraphicsContextBindFn bind_fn, diff --git a/intern/ghost/GHOST_Types.h b/intern/ghost/GHOST_Types.h index 7d819913efc..3a8d0fbfecf 100644 --- a/intern/ghost/GHOST_Types.h +++ b/intern/ghost/GHOST_Types.h @@ -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 */ diff --git a/intern/ghost/intern/GHOST_C-api.cpp b/intern/ghost/intern/GHOST_C-api.cpp index 1d96354c504..955f35274ea 100644 --- a/intern/ghost/intern/GHOST_C-api.cpp +++ b/intern/ghost/intern/GHOST_C-api.cpp @@ -33,6 +33,7 @@ #include "intern/GHOST_Debug.h" #ifdef WITH_XR_OPENXR # include "GHOST_IXrContext.h" +# include "intern/GHOST_XrSession.h" #endif #include "intern/GHOST_CallbackEventConsumer.h" #include "intern/GHOST_XrException.h" @@ -953,4 +954,145 @@ int GHOST_XrSessionNeedsUpsideDownDrawing(const GHOST_XrContextHandle xr_context return 0; /* Only reached if exception is thrown. */ } -#endif +int GHOST_XrCreateActionSet(GHOST_XrContextHandle xr_contexthandle, + const GHOST_XrActionSetInfo *info) +{ + GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle; + GHOST_XrSession *xr_session = xr_context->getSession(); + GHOST_XR_CAPI_CALL_RET(xr_session->createActionSet(*info), xr_context); + return 0; +} + +void GHOST_XrDestroyActionSet(GHOST_XrContextHandle xr_contexthandle, const char *action_set_name) +{ + GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle; + GHOST_XrSession *xr_session = xr_context->getSession(); + GHOST_XR_CAPI_CALL(xr_session->destroyActionSet(action_set_name), xr_context); +} + +int GHOST_XrCreateActions(GHOST_XrContextHandle xr_contexthandle, + const char *action_set_name, + GHOST_TUns32 count, + const GHOST_XrActionInfo *infos) +{ + GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle; + GHOST_XrSession *xr_session = xr_context->getSession(); + GHOST_XR_CAPI_CALL_RET(xr_session->createActions(action_set_name, count, infos), xr_context); + return 0; +} + +void GHOST_XrDestroyActions(GHOST_XrContextHandle xr_contexthandle, + const char *action_set_name, + GHOST_TUns32 count, + const char *const *action_names) +{ + GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle; + GHOST_XrSession *xr_session = xr_context->getSession(); + GHOST_XR_CAPI_CALL(xr_session->destroyActions(action_set_name, count, action_names), xr_context); +} + +int GHOST_XrCreateActionSpaces(GHOST_XrContextHandle xr_contexthandle, + const char *action_set_name, + GHOST_TUns32 count, + const GHOST_XrActionSpaceInfo *infos) +{ + GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle; + GHOST_XrSession *xr_session = xr_context->getSession(); + GHOST_XR_CAPI_CALL_RET(xr_session->createActionSpaces(action_set_name, count, infos), + xr_context); + return 0; +} + +void GHOST_XrDestroyActionSpaces(GHOST_XrContextHandle xr_contexthandle, + const char *action_set_name, + GHOST_TUns32 count, + const GHOST_XrActionSpaceInfo *infos) +{ + GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle; + GHOST_XrSession *xr_session = xr_context->getSession(); + GHOST_XR_CAPI_CALL(xr_session->destroyActionSpaces(action_set_name, count, infos), xr_context); +} + +int GHOST_XrCreateActionBindings(GHOST_XrContextHandle xr_contexthandle, + const char *action_set_name, + GHOST_TUns32 count, + const GHOST_XrActionProfileInfo *infos) +{ + GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle; + GHOST_XrSession *xr_session = xr_context->getSession(); + GHOST_XR_CAPI_CALL_RET(xr_session->createActionBindings(action_set_name, count, infos), + xr_context); + return 0; +} + +void GHOST_XrDestroyActionBindings(GHOST_XrContextHandle xr_contexthandle, + const char *action_set_name, + GHOST_TUns32 count, + const GHOST_XrActionProfileInfo *infos) +{ + GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle; + GHOST_XrSession *xr_session = xr_context->getSession(); + GHOST_XR_CAPI_CALL(xr_session->destroyActionBindings(action_set_name, count, infos), xr_context); +} + +int GHOST_XrAttachActionSets(GHOST_XrContextHandle xr_contexthandle) +{ + GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle; + GHOST_XrSession *xr_session = xr_context->getSession(); + GHOST_XR_CAPI_CALL_RET(xr_session->attachActionSets(), xr_context); + return 0; +} + +int GHOST_XrSyncActions(GHOST_XrContextHandle xr_contexthandle, const char *action_set_name) +{ + GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle; + GHOST_XrSession *xr_session = xr_context->getSession(); + GHOST_XR_CAPI_CALL_RET(xr_session->syncActions(action_set_name), xr_context); + return 0; +} + +int GHOST_XrApplyHapticAction(GHOST_XrContextHandle xr_contexthandle, + const char *action_set_name, + const char *action_name, + const GHOST_TInt64 *duration, + const float *frequency, + const float *amplitude) +{ + GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle; + GHOST_XrSession *xr_session = xr_context->getSession(); + GHOST_XR_CAPI_CALL_RET(xr_session->applyHapticAction( + action_set_name, action_name, *duration, *frequency, *amplitude), + xr_context); + return 0; +} + +void GHOST_XrStopHapticAction(GHOST_XrContextHandle xr_contexthandle, + const char *action_set_name, + const char *action_name) +{ + GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle; + GHOST_XrSession *xr_session = xr_context->getSession(); + GHOST_XR_CAPI_CALL(xr_session->stopHapticAction(action_set_name, action_name), xr_context); +} + +void *GHOST_XrGetActionSetCustomdata(GHOST_XrContextHandle xr_contexthandle, + const char *action_set_name) +{ + GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle; + GHOST_XrSession *xr_session = xr_context->getSession(); + GHOST_XR_CAPI_CALL_RET(xr_session->getActionSetCustomdata(action_set_name), xr_context); + return 0; +} + +void *GHOST_XrGetActionCustomdata(GHOST_XrContextHandle xr_contexthandle, + const char *action_set_name, + const char *action_name) +{ + GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle; + GHOST_XrSession *xr_session = xr_context->getSession(); + GHOST_XR_CAPI_CALL_RET(xr_session->getActionCustomdata(action_set_name, action_name), + xr_context); + return 0; +} + +#endif /* WITH_XR_OPENXR */ diff --git a/intern/ghost/intern/GHOST_Util.h b/intern/ghost/intern/GHOST_Util.h new file mode 100644 index 00000000000..8be5e373b28 --- /dev/null +++ b/intern/ghost/intern/GHOST_Util.h @@ -0,0 +1,45 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup GHOST + */ + +#pragma once + +#include <functional> + +/** + * RAII wrapper for typical C `void *` custom data. + * Used for exception safe custom-data handling during constructor calls. + */ +struct GHOST_C_CustomDataWrapper { + using FreeFn = std::function<void(void *)>; + + void *custom_data_; + FreeFn free_fn_; + + GHOST_C_CustomDataWrapper(void *custom_data, FreeFn free_fn) + : custom_data_(custom_data), free_fn_(free_fn) + { + } + ~GHOST_C_CustomDataWrapper() + { + if (free_fn_ != nullptr && custom_data_ != nullptr) { + free_fn_(custom_data_); + } + } +}; diff --git a/intern/ghost/intern/GHOST_XrAction.cpp b/intern/ghost/intern/GHOST_XrAction.cpp new file mode 100644 index 00000000000..172ac40c84f --- /dev/null +++ b/intern/ghost/intern/GHOST_XrAction.cpp @@ -0,0 +1,477 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup GHOST + */ + +#include <cassert> +#include <cstring> + +#include "GHOST_Types.h" + +#include "GHOST_XrException.h" +#include "GHOST_Xr_intern.h" + +#include "GHOST_XrAction.h" + +/* -------------------------------------------------------------------- */ +/** \name GHOST_XrActionSpace + * + * \{ */ + +GHOST_XrActionSpace::GHOST_XrActionSpace(XrInstance instance, + XrSession session, + XrAction action, + const GHOST_XrActionSpaceInfo &info, + uint32_t subaction_idx) +{ + const char *subaction_path = info.subaction_paths[subaction_idx]; + CHECK_XR(xrStringToPath(instance, subaction_path, &m_subaction_path), + (std::string("Failed to get user path \"") + subaction_path + "\".").data()); + + XrActionSpaceCreateInfo action_space_info{XR_TYPE_ACTION_SPACE_CREATE_INFO}; + action_space_info.action = action; + action_space_info.subactionPath = m_subaction_path; + copy_ghost_pose_to_openxr_pose(info.poses[subaction_idx], action_space_info.poseInActionSpace); + + CHECK_XR(xrCreateActionSpace(session, &action_space_info, &m_space), + (std::string("Failed to create space \"") + subaction_path + "\" for action \"" + + info.action_name + "\".") + .data()); +} + +GHOST_XrActionSpace::~GHOST_XrActionSpace() +{ + if (m_space != XR_NULL_HANDLE) { + CHECK_XR_ASSERT(xrDestroySpace(m_space)); + } +} + +XrSpace GHOST_XrActionSpace::getSpace() const +{ + return m_space; +} + +const XrPath &GHOST_XrActionSpace::getSubactionPath() const +{ + return m_subaction_path; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name GHOST_XrActionProfile + * + * \{ */ + +GHOST_XrActionProfile::GHOST_XrActionProfile(XrInstance instance, + XrAction action, + const char *profile_path, + const GHOST_XrActionBindingInfo &info) +{ + CHECK_XR( + xrStringToPath(instance, profile_path, &m_profile), + (std::string("Failed to get interaction profile path \"") + profile_path + "\".").data()); + + /* Create bindings. */ + XrInteractionProfileSuggestedBinding bindings_info{ + XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING}; + bindings_info.interactionProfile = m_profile; + bindings_info.countSuggestedBindings = 1; + + for (uint32_t interaction_idx = 0; interaction_idx < info.count_interaction_paths; + ++interaction_idx) { + const char *interaction_path = info.interaction_paths[interaction_idx]; + if (m_bindings.find(interaction_path) != m_bindings.end()) { + continue; + } + + XrActionSuggestedBinding sbinding; + sbinding.action = action; + CHECK_XR(xrStringToPath(instance, interaction_path, &sbinding.binding), + (std::string("Failed to get interaction path \"") + interaction_path + "\".").data()); + bindings_info.suggestedBindings = &sbinding; + + /* Although the bindings will be re-suggested in GHOST_XrSession::attachActionSets(), it + * greatly improves error checking to suggest them here first. */ + CHECK_XR(xrSuggestInteractionProfileBindings(instance, &bindings_info), + (std::string("Failed to create binding for profile \"") + profile_path + + "\" and action \"" + info.action_name + + "\". Are the profile and action paths correct?") + .data()); + + m_bindings.insert({interaction_path, sbinding.binding}); + } +} + +void GHOST_XrActionProfile::getBindings( + XrAction action, std::map<XrPath, std::vector<XrActionSuggestedBinding>> &r_bindings) const +{ + std::map<XrPath, std::vector<XrActionSuggestedBinding>>::iterator it = r_bindings.find( + m_profile); + if (it == r_bindings.end()) { + it = r_bindings + .emplace(std::piecewise_construct, std::make_tuple(m_profile), std::make_tuple()) + .first; + } + + std::vector<XrActionSuggestedBinding> &sbindings = it->second; + + for (auto &[path, binding] : m_bindings) { + XrActionSuggestedBinding sbinding; + sbinding.action = action; + sbinding.binding = binding; + + sbindings.push_back(std::move(sbinding)); + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name GHOST_XrAction + * + * \{ */ + +GHOST_XrAction::GHOST_XrAction(XrInstance instance, + XrActionSet action_set, + const GHOST_XrActionInfo &info) + : m_type(info.type), + m_states(info.states), + m_custom_data_( + std::make_unique<GHOST_C_CustomDataWrapper>(info.customdata, info.customdata_free_fn)) +{ + m_subaction_paths.resize(info.count_subaction_paths); + + for (uint32_t i = 0; i < info.count_subaction_paths; ++i) { + CHECK_XR(xrStringToPath(instance, info.subaction_paths[i], &m_subaction_paths[i]), + (std::string("Failed to get user path \"") + info.subaction_paths[i] + "\".").data()); + } + + XrActionCreateInfo action_info{XR_TYPE_ACTION_CREATE_INFO}; + strcpy(action_info.actionName, info.name); + strcpy(action_info.localizedActionName, info.name); /* Just use same name for localized. This can + be changed in the future if necessary. */ + + switch (info.type) { + case GHOST_kXrActionTypeBooleanInput: + action_info.actionType = XR_ACTION_TYPE_BOOLEAN_INPUT; + break; + case GHOST_kXrActionTypeFloatInput: + action_info.actionType = XR_ACTION_TYPE_FLOAT_INPUT; + break; + case GHOST_kXrActionTypeVector2fInput: + action_info.actionType = XR_ACTION_TYPE_VECTOR2F_INPUT; + break; + case GHOST_kXrActionTypePoseInput: + action_info.actionType = XR_ACTION_TYPE_POSE_INPUT; + break; + case GHOST_kXrActionTypeVibrationOutput: + action_info.actionType = XR_ACTION_TYPE_VIBRATION_OUTPUT; + break; + } + action_info.countSubactionPaths = info.count_subaction_paths; + action_info.subactionPaths = m_subaction_paths.data(); + + CHECK_XR(xrCreateAction(action_set, &action_info, &m_action), + (std::string("Failed to create action \"") + info.name + + "\". Action name and/or paths are invalid. Name must not contain upper " + "case letters or special characters other than '-', '_', or '.'.") + .data()); +} + +GHOST_XrAction::~GHOST_XrAction() +{ + if (m_action != XR_NULL_HANDLE) { + CHECK_XR_ASSERT(xrDestroyAction(m_action)); + } +} + +bool GHOST_XrAction::createSpace(XrInstance instance, + XrSession session, + const GHOST_XrActionSpaceInfo &info) +{ + uint32_t subaction_idx = 0; + for (; subaction_idx < info.count_subaction_paths; ++subaction_idx) { + if (m_spaces.find(info.subaction_paths[subaction_idx]) != m_spaces.end()) { + return false; + } + } + + for (subaction_idx = 0; subaction_idx < info.count_subaction_paths; ++subaction_idx) { + m_spaces.emplace(std::piecewise_construct, + std::make_tuple(info.subaction_paths[subaction_idx]), + std::make_tuple(instance, session, m_action, info, subaction_idx)); + } + + return true; +} + +void GHOST_XrAction::destroySpace(const char *subaction_path) +{ + if (m_spaces.find(subaction_path) != m_spaces.end()) { + m_spaces.erase(subaction_path); + } +} + +bool GHOST_XrAction::createBinding(XrInstance instance, + const char *profile_path, + const GHOST_XrActionBindingInfo &info) +{ + if (m_profiles.find(profile_path) != m_profiles.end()) { + return false; + } + + m_profiles.emplace(std::piecewise_construct, + std::make_tuple(profile_path), + std::make_tuple(instance, m_action, profile_path, info)); + + return true; +} + +void GHOST_XrAction::destroyBinding(const char *interaction_profile_path) +{ + if (m_profiles.find(interaction_profile_path) != m_profiles.end()) { + m_profiles.erase(interaction_profile_path); + } +} + +void GHOST_XrAction::updateState(XrSession session, + const char *action_name, + XrSpace reference_space, + const XrTime &predicted_display_time) +{ + XrActionStateGetInfo state_info{XR_TYPE_ACTION_STATE_GET_INFO}; + state_info.action = m_action; + + const size_t count_subaction_paths = m_subaction_paths.size(); + for (size_t subaction_idx = 0; subaction_idx < count_subaction_paths; ++subaction_idx) { + state_info.subactionPath = m_subaction_paths[subaction_idx]; + + switch (m_type) { + case GHOST_kXrActionTypeBooleanInput: { + XrActionStateBoolean state{XR_TYPE_ACTION_STATE_BOOLEAN}; + CHECK_XR(xrGetActionStateBoolean(session, &state_info, &state), + (std::string("Failed to get state for boolean action \"") + action_name + "\".") + .data()); + if (state.isActive) { + ((bool *)m_states)[subaction_idx] = state.currentState; + } + break; + } + case GHOST_kXrActionTypeFloatInput: { + XrActionStateFloat state{XR_TYPE_ACTION_STATE_FLOAT}; + CHECK_XR( + xrGetActionStateFloat(session, &state_info, &state), + (std::string("Failed to get state for float action \"") + action_name + "\".").data()); + if (state.isActive) { + ((float *)m_states)[subaction_idx] = state.currentState; + } + break; + } + case GHOST_kXrActionTypeVector2fInput: { + XrActionStateVector2f state{XR_TYPE_ACTION_STATE_VECTOR2F}; + CHECK_XR(xrGetActionStateVector2f(session, &state_info, &state), + (std::string("Failed to get state for vector2f action \"") + action_name + "\".") + .data()); + if (state.isActive) { + memcpy(((float(*)[2])m_states)[subaction_idx], &state.currentState, sizeof(float[2])); + } + break; + } + case GHOST_kXrActionTypePoseInput: { + XrActionStatePose state{XR_TYPE_ACTION_STATE_POSE}; + CHECK_XR( + xrGetActionStatePose(session, &state_info, &state), + (std::string("Failed to get state for pose action \"") + action_name + "\".").data()); + if (state.isActive) { + XrSpace pose_space = XR_NULL_HANDLE; + for (auto &[path, space] : m_spaces) { + if (space.getSubactionPath() == state_info.subactionPath) { + pose_space = space.getSpace(); + break; + } + } + + if (pose_space != XR_NULL_HANDLE) { + XrSpaceLocation space_location{XR_TYPE_SPACE_LOCATION}; + CHECK_XR( + xrLocateSpace( + pose_space, reference_space, predicted_display_time, &space_location), + (std::string("Failed to query pose space for action \"") + action_name + "\".") + .data()); + copy_openxr_pose_to_ghost_pose(space_location.pose, + ((GHOST_XrPose *)m_states)[subaction_idx]); + } + } + break; + } + case GHOST_kXrActionTypeVibrationOutput: { + break; + } + } + } +} + +void GHOST_XrAction::applyHapticFeedback(XrSession session, + const char *action_name, + const GHOST_TInt64 &duration, + const float &frequency, + const float &litude) +{ + XrHapticVibration vibration{XR_TYPE_HAPTIC_VIBRATION}; + vibration.duration = (duration == 0) ? XR_MIN_HAPTIC_DURATION : + static_cast<XrDuration>(duration); + vibration.frequency = frequency; + vibration.amplitude = amplitude; + + XrHapticActionInfo haptic_info{XR_TYPE_HAPTIC_ACTION_INFO}; + haptic_info.action = m_action; + + for (std::vector<XrPath>::iterator it = m_subaction_paths.begin(); it != m_subaction_paths.end(); + ++it) { + haptic_info.subactionPath = *it; + CHECK_XR(xrApplyHapticFeedback(session, &haptic_info, (const XrHapticBaseHeader *)&vibration), + (std::string("Failed to apply haptic action \"") + action_name + "\".").data()); + } +} + +void GHOST_XrAction::stopHapticFeedback(XrSession session, const char *action_name) +{ + XrHapticActionInfo haptic_info{XR_TYPE_HAPTIC_ACTION_INFO}; + haptic_info.action = m_action; + + for (std::vector<XrPath>::iterator it = m_subaction_paths.begin(); it != m_subaction_paths.end(); + ++it) { + haptic_info.subactionPath = *it; + CHECK_XR(xrStopHapticFeedback(session, &haptic_info), + (std::string("Failed to stop haptic action \"") + action_name + "\".").data()); + } +} + +void *GHOST_XrAction::getCustomdata() +{ + if (m_custom_data_ == nullptr) { + return nullptr; + } + return m_custom_data_->custom_data_; +} + +void GHOST_XrAction::getBindings( + std::map<XrPath, std::vector<XrActionSuggestedBinding>> &r_bindings) const +{ + for (auto &[path, profile] : m_profiles) { + profile.getBindings(m_action, r_bindings); + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name GHOST_XrActionSet + * + * \{ */ + +GHOST_XrActionSet::GHOST_XrActionSet(XrInstance instance, const GHOST_XrActionSetInfo &info) + : m_custom_data_( + std::make_unique<GHOST_C_CustomDataWrapper>(info.customdata, info.customdata_free_fn)) +{ + XrActionSetCreateInfo action_set_info{XR_TYPE_ACTION_SET_CREATE_INFO}; + strcpy(action_set_info.actionSetName, info.name); + strcpy(action_set_info.localizedActionSetName, + info.name); /* Just use same name for localized. This can be changed in the future if + necessary. */ + action_set_info.priority = 0; /* Use same (default) priority for all action sets. */ + + CHECK_XR(xrCreateActionSet(instance, &action_set_info, &m_action_set), + (std::string("Failed to create action set \"") + info.name + + "\". Name must not contain upper case letters or special characters " + "other than '-', '_', or '.'.") + .data()); +} + +GHOST_XrActionSet::~GHOST_XrActionSet() +{ + /* This needs to be done before xrDestroyActionSet() to avoid an assertion in the GHOST_XrAction + * destructor (which calls xrDestroyAction()). */ + m_actions.clear(); + + if (m_action_set != XR_NULL_HANDLE) { + CHECK_XR_ASSERT(xrDestroyActionSet(m_action_set)); + } +} + +bool GHOST_XrActionSet::createAction(XrInstance instance, const GHOST_XrActionInfo &info) +{ + if (m_actions.find(info.name) != m_actions.end()) { + return false; + } + + m_actions.emplace(std::piecewise_construct, + std::make_tuple(info.name), + std::make_tuple(instance, m_action_set, info)); + + return true; +} + +void GHOST_XrActionSet::destroyAction(const char *action_name) +{ + if (m_actions.find(action_name) != m_actions.end()) { + m_actions.erase(action_name); + } +} + +GHOST_XrAction *GHOST_XrActionSet::findAction(const char *action_name) +{ + std::map<std::string, GHOST_XrAction>::iterator it = m_actions.find(action_name); + if (it == m_actions.end()) { + return nullptr; + } + return &it->second; +} + +void GHOST_XrActionSet::updateStates(XrSession session, + XrSpace reference_space, + const XrTime &predicted_display_time) +{ + for (auto &[name, action] : m_actions) { + action.updateState(session, name.data(), reference_space, predicted_display_time); + } +} + +XrActionSet GHOST_XrActionSet::getActionSet() const +{ + return m_action_set; +} + +void *GHOST_XrActionSet::getCustomdata() +{ + if (m_custom_data_ == nullptr) { + return nullptr; + } + return m_custom_data_->custom_data_; +} + +void GHOST_XrActionSet::getBindings( + std::map<XrPath, std::vector<XrActionSuggestedBinding>> &r_bindings) const +{ + for (auto &[name, action] : m_actions) { + action.getBindings(r_bindings); + } +} + +/** \} */ diff --git a/intern/ghost/intern/GHOST_XrAction.h b/intern/ghost/intern/GHOST_XrAction.h new file mode 100644 index 00000000000..bdc6cafb4a9 --- /dev/null +++ b/intern/ghost/intern/GHOST_XrAction.h @@ -0,0 +1,145 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup GHOST + */ + +/* Note: Requires OpenXR headers to be included before this one for OpenXR types (XrSpace, XrPath, + * etc.). */ + +#pragma once + +#include <map> +#include <memory> +#include <string> + +#include "GHOST_Util.h" + +/* -------------------------------------------------------------------- */ + +class GHOST_XrActionSpace { + public: + GHOST_XrActionSpace() = delete; /* Default constructor for map storage. */ + GHOST_XrActionSpace(XrInstance instance, + XrSession session, + XrAction action, + const GHOST_XrActionSpaceInfo &info, + uint32_t subaction_idx); + ~GHOST_XrActionSpace(); + + XrSpace getSpace() const; + const XrPath &getSubactionPath() const; + + private: + XrSpace m_space = XR_NULL_HANDLE; + XrPath m_subaction_path = XR_NULL_PATH; +}; + +/* -------------------------------------------------------------------- */ + +class GHOST_XrActionProfile { + public: + GHOST_XrActionProfile() = delete; /* Default constructor for map storage. */ + GHOST_XrActionProfile(XrInstance instance, + XrAction action, + const char *profile_path, + const GHOST_XrActionBindingInfo &info); + ~GHOST_XrActionProfile() = default; + + void getBindings(XrAction action, + std::map<XrPath, std::vector<XrActionSuggestedBinding>> &r_bindings) const; + + private: + XrPath m_profile = XR_NULL_PATH; + /* Bindings identified by interaction (user (subaction) + component) path. */ + std::map<std::string, XrPath> m_bindings; +}; + +/* -------------------------------------------------------------------- */ + +class GHOST_XrAction { + public: + GHOST_XrAction() = delete; /* Default constructor for map storage. */ + GHOST_XrAction(XrInstance instance, XrActionSet action_set, const GHOST_XrActionInfo &info); + ~GHOST_XrAction(); + + bool createSpace(XrInstance instance, XrSession session, const GHOST_XrActionSpaceInfo &info); + void destroySpace(const char *subaction_path); + + bool createBinding(XrInstance instance, + const char *profile_path, + const GHOST_XrActionBindingInfo &info); + void destroyBinding(const char *profile_path); + + void updateState(XrSession session, + const char *action_name, + XrSpace reference_space, + const XrTime &predicted_display_time); + void applyHapticFeedback(XrSession session, + const char *action_name, + const GHOST_TInt64 &duration, + const float &frequency, + const float &litude); + void stopHapticFeedback(XrSession session, const char *action_name); + + void *getCustomdata(); + void getBindings(std::map<XrPath, std::vector<XrActionSuggestedBinding>> &r_bindings) const; + + private: + XrAction m_action = XR_NULL_HANDLE; + GHOST_XrActionType m_type; + std::vector<XrPath> m_subaction_paths; + /** States for each subaction path. */ + void *m_states; + + std::unique_ptr<GHOST_C_CustomDataWrapper> m_custom_data_ = nullptr; /* wmXrAction */ + + /* Spaces identified by user (subaction) path. */ + std::map<std::string, GHOST_XrActionSpace> m_spaces; + /* Profiles identified by interaction profile path. */ + std::map<std::string, GHOST_XrActionProfile> m_profiles; +}; + +/* -------------------------------------------------------------------- */ + +class GHOST_XrActionSet { + public: + GHOST_XrActionSet() = delete; /* Default constructor for map storage. */ + GHOST_XrActionSet(XrInstance instance, const GHOST_XrActionSetInfo &info); + ~GHOST_XrActionSet(); + + bool createAction(XrInstance instance, const GHOST_XrActionInfo &info); + void destroyAction(const char *action_name); + GHOST_XrAction *findAction(const char *action_name); + + void updateStates(XrSession session, + XrSpace reference_space, + const XrTime &predicted_display_time); + + XrActionSet getActionSet() const; + void *getCustomdata(); + void getBindings(std::map<XrPath, std::vector<XrActionSuggestedBinding>> &r_bindings) const; + + private: + XrActionSet m_action_set = XR_NULL_HANDLE; + + std::unique_ptr<GHOST_C_CustomDataWrapper> m_custom_data_ = nullptr; /* wmXrActionSet */ + + std::map<std::string, GHOST_XrAction> m_actions; +}; + +/* -------------------------------------------------------------------- */ diff --git a/intern/ghost/intern/GHOST_XrContext.cpp b/intern/ghost/intern/GHOST_XrContext.cpp index 2bf67c121f8..daad0b8190a 100644 --- a/intern/ghost/intern/GHOST_XrContext.cpp +++ b/intern/ghost/intern/GHOST_XrContext.cpp @@ -55,7 +55,6 @@ void *GHOST_XrContext::s_error_handler_customdata = nullptr; /* -------------------------------------------------------------------- */ /** \name Create, Initialize and Destruct - * * \{ */ GHOST_XrContext::GHOST_XrContext(const GHOST_XrContextCreateInfo *create_info) @@ -153,7 +152,6 @@ void GHOST_XrContext::storeInstanceProperties() /* -------------------------------------------------------------------- */ /** \name Debug Printing - * * \{ */ void GHOST_XrContext::printInstanceInfo() @@ -242,14 +240,13 @@ void GHOST_XrContext::initDebugMessenger() /* -------------------------------------------------------------------- */ /** \name Error handling - * * \{ */ void GHOST_XrContext::dispatchErrorMessage(const GHOST_XrException *exception) const { GHOST_XrError error; - error.user_message = exception->m_msg; + error.user_message = exception->m_msg.data(); error.customdata = s_error_handler_customdata; if (isDebugMode()) { @@ -273,7 +270,6 @@ void GHOST_XrContext::setErrorHandler(GHOST_XrErrorHandlerFn handler_fn, void *c /* -------------------------------------------------------------------- */ /** \name OpenXR API-Layers and Extensions - * * \{ */ /** @@ -378,7 +374,7 @@ void GHOST_XrContext::getAPILayersToEnable(std::vector<const char *> &r_ext_name for (const std::string &layer : try_layers) { if (openxr_layer_is_available(m_oxr->layers, layer)) { - r_ext_names.push_back(layer.c_str()); + r_ext_names.push_back(layer.data()); } } } @@ -488,6 +484,7 @@ GHOST_TXrGraphicsBinding GHOST_XrContext::determineGraphicsBindingTypeToUse( void GHOST_XrContext::startSession(const GHOST_XrSessionBeginInfo *begin_info) { + m_custom_funcs.session_create_fn = begin_info->create_fn; m_custom_funcs.session_exit_fn = begin_info->exit_fn; m_custom_funcs.session_exit_customdata = begin_info->exit_customdata; @@ -538,6 +535,16 @@ void GHOST_XrContext::handleSessionStateChange(const XrEventDataSessionStateChan * Public as in, exposed in the Ghost API. * \{ */ +GHOST_XrSession *GHOST_XrContext::getSession() +{ + return m_session.get(); +} + +const GHOST_XrSession *GHOST_XrContext::getSession() const +{ + return m_session.get(); +} + void GHOST_XrContext::setGraphicsContextBindFuncs(GHOST_XrGraphicsContextBindFn bind_fn, GHOST_XrGraphicsContextUnbindFn unbind_fn) { @@ -564,7 +571,6 @@ bool GHOST_XrContext::needsUpsideDownDrawing() const /* -------------------------------------------------------------------- */ /** \name Ghost Internal Accessors and Mutators - * * \{ */ GHOST_TXrOpenXRRuntimeID GHOST_XrContext::getOpenXRRuntimeID() const diff --git a/intern/ghost/intern/GHOST_XrContext.h b/intern/ghost/intern/GHOST_XrContext.h index 59c7786ed7a..f29d7349f7e 100644 --- a/intern/ghost/intern/GHOST_XrContext.h +++ b/intern/ghost/intern/GHOST_XrContext.h @@ -35,6 +35,7 @@ struct GHOST_XrCustomFuncs { /** Function to release (possibly free) a graphics context. */ GHOST_XrGraphicsContextUnbindFn gpu_ctx_unbind_fn = nullptr; + GHOST_XrSessionCreateFn session_create_fn = nullptr; GHOST_XrSessionExitFn session_exit_fn = nullptr; void *session_exit_customdata = nullptr; @@ -72,6 +73,10 @@ class GHOST_XrContext : public GHOST_IXrContext { bool isSessionRunning() const override; void drawSessionViews(void *draw_customdata) override; + /** Needed for the GHOST C api. */ + GHOST_XrSession *getSession() override; + const GHOST_XrSession *getSession() const override; + static void setErrorHandler(GHOST_XrErrorHandlerFn handler_fn, void *customdata); void dispatchErrorMessage(const class GHOST_XrException *exception) const override; diff --git a/intern/ghost/intern/GHOST_XrException.h b/intern/ghost/intern/GHOST_XrException.h index 30c33eaf98f..e93164e04c8 100644 --- a/intern/ghost/intern/GHOST_XrException.h +++ b/intern/ghost/intern/GHOST_XrException.h @@ -21,6 +21,7 @@ #pragma once #include <exception> +#include <string> class GHOST_XrException : public std::exception { friend class GHOST_XrContext; @@ -33,10 +34,10 @@ class GHOST_XrException : public std::exception { const char *what() const noexcept override { - return m_msg; + return m_msg.data(); } private: - const char *m_msg; + std::string m_msg; int m_result; }; diff --git a/intern/ghost/intern/GHOST_XrSession.cpp b/intern/ghost/intern/GHOST_XrSession.cpp index a1fd7fc2781..a7438fae13c 100644 --- a/intern/ghost/intern/GHOST_XrSession.cpp +++ b/intern/ghost/intern/GHOST_XrSession.cpp @@ -28,6 +28,7 @@ #include "GHOST_C-api.h" #include "GHOST_IXrGraphicsBinding.h" +#include "GHOST_XrAction.h" #include "GHOST_XrContext.h" #include "GHOST_XrException.h" #include "GHOST_XrSwapchain.h" @@ -46,6 +47,8 @@ struct OpenXRSessionData { XrSpace view_space; std::vector<XrView> views; std::vector<GHOST_XrSwapchain> swapchains; + + std::map<std::string, GHOST_XrActionSet> action_sets; }; struct GHOST_XrDrawInfo { @@ -59,7 +62,6 @@ struct GHOST_XrDrawInfo { /* -------------------------------------------------------------------- */ /** \name Create, Initialize and Destruct - * * \{ */ GHOST_XrSession::GHOST_XrSession(GHOST_XrContext &xr_context) @@ -72,6 +74,7 @@ GHOST_XrSession::~GHOST_XrSession() unbindGraphicsContext(); m_oxr->swapchains.clear(); + m_oxr->action_sets.clear(); if (m_oxr->reference_space != XR_NULL_HANDLE) { CHECK_XR_ASSERT(xrDestroySpace(m_oxr->reference_space)); @@ -110,7 +113,6 @@ void GHOST_XrSession::initSystem() /* -------------------------------------------------------------------- */ /** \name State Management - * * \{ */ static void create_reference_spaces(OpenXRSessionData &oxr, const GHOST_XrPose &base_pose) @@ -179,7 +181,7 @@ void GHOST_XrSession::start(const GHOST_XrSessionBeginInfo *begin_info) std::ostringstream strstream; strstream << "Available graphics context version does not meet the following requirements: " << requirement_str; - throw GHOST_XrException(strstream.str().c_str()); + throw GHOST_XrException(strstream.str().data()); } m_gpu_binding->initFromGhostContext(*m_gpu_ctx); @@ -196,6 +198,9 @@ void GHOST_XrSession::start(const GHOST_XrSessionBeginInfo *begin_info) prepareDrawing(); create_reference_spaces(*m_oxr, begin_info->base_pose); + + /* Create and bind actions here. */ + m_context->getCustomFuncs().session_create_fn(); } void GHOST_XrSession::requestEnd() @@ -225,10 +230,9 @@ GHOST_XrSession::LifeExpectancy GHOST_XrSession::handleStateChangeEvent( assert(m_oxr->session == XR_NULL_HANDLE || m_oxr->session == lifecycle.session); switch (lifecycle.state) { - case XR_SESSION_STATE_READY: { + case XR_SESSION_STATE_READY: beginSession(); break; - } case XR_SESSION_STATE_STOPPING: endSession(); break; @@ -245,7 +249,6 @@ GHOST_XrSession::LifeExpectancy GHOST_XrSession::handleStateChangeEvent( /* -------------------------------------------------------------------- */ /** \name Drawing - * * \{ */ void GHOST_XrSession::prepareDrawing() @@ -352,18 +355,6 @@ void GHOST_XrSession::draw(void *draw_customdata) endFrameDrawing(layers); } -static void copy_openxr_pose_to_ghost_pose(const XrPosef &oxr_pose, GHOST_XrPose &r_ghost_pose) -{ - /* Set and convert to Blender coordinate space. */ - r_ghost_pose.position[0] = oxr_pose.position.x; - r_ghost_pose.position[1] = oxr_pose.position.y; - r_ghost_pose.position[2] = oxr_pose.position.z; - r_ghost_pose.orientation_quat[0] = oxr_pose.orientation.w; - r_ghost_pose.orientation_quat[1] = oxr_pose.orientation.x; - r_ghost_pose.orientation_quat[2] = oxr_pose.orientation.y; - r_ghost_pose.orientation_quat[3] = oxr_pose.orientation.z; -} - static void ghost_xr_draw_view_info_from_view(const XrView &view, GHOST_XrDrawViewInfo &r_info) { /* Set and convert to Blender coordinate space. */ @@ -457,7 +448,6 @@ bool GHOST_XrSession::needsUpsideDownDrawing() const /* -------------------------------------------------------------------- */ /** \name State Queries - * * \{ */ bool GHOST_XrSession::isRunning() const @@ -505,3 +495,340 @@ void GHOST_XrSession::unbindGraphicsContext() } /** \} */ /* Graphics Context Injection */ + +/* -------------------------------------------------------------------- */ +/** \name Actions + * + * \{ */ + +static GHOST_XrActionSet *find_action_set(OpenXRSessionData *oxr, const char *action_set_name) +{ + std::map<std::string, GHOST_XrActionSet>::iterator it = oxr->action_sets.find(action_set_name); + if (it == oxr->action_sets.end()) { + return nullptr; + } + return &it->second; +} + +bool GHOST_XrSession::createActionSet(const GHOST_XrActionSetInfo &info) +{ + std::map<std::string, GHOST_XrActionSet> &action_sets = m_oxr->action_sets; + if (action_sets.find(info.name) != action_sets.end()) { + return false; + } + + XrInstance instance = m_context->getInstance(); + + action_sets.emplace( + std::piecewise_construct, std::make_tuple(info.name), std::make_tuple(instance, info)); + + return true; +} + +void GHOST_XrSession::destroyActionSet(const char *action_set_name) +{ + std::map<std::string, GHOST_XrActionSet> &action_sets = m_oxr->action_sets; + if (action_sets.find(action_set_name) != action_sets.end()) { + action_sets.erase(action_set_name); + } +} + +bool GHOST_XrSession::createActions(const char *action_set_name, + uint32_t count, + const GHOST_XrActionInfo *infos) +{ + GHOST_XrActionSet *action_set = find_action_set(m_oxr.get(), action_set_name); + if (action_set == nullptr) { + return false; + } + + XrInstance instance = m_context->getInstance(); + + for (uint32_t i = 0; i < count; ++i) { + if (!action_set->createAction(instance, infos[i])) { + return false; + } + } + + return true; +} + +void GHOST_XrSession::destroyActions(const char *action_set_name, + uint32_t count, + const char *const *action_names) +{ + GHOST_XrActionSet *action_set = find_action_set(m_oxr.get(), action_set_name); + if (action_set == nullptr) { + return; + } + + for (uint32_t i = 0; i < count; ++i) { + action_set->destroyAction(action_names[i]); + } +} + +bool GHOST_XrSession::createActionSpaces(const char *action_set_name, + uint32_t count, + const GHOST_XrActionSpaceInfo *infos) +{ + GHOST_XrActionSet *action_set = find_action_set(m_oxr.get(), action_set_name); + if (action_set == nullptr) { + return false; + } + + XrInstance instance = m_context->getInstance(); + XrSession session = m_oxr->session; + + for (uint32_t action_idx = 0; action_idx < count; ++action_idx) { + const GHOST_XrActionSpaceInfo &info = infos[action_idx]; + + GHOST_XrAction *action = action_set->findAction(info.action_name); + if (action == nullptr) { + continue; + } + + if (!action->createSpace(instance, session, info)) { + return false; + } + } + + return true; +} + +void GHOST_XrSession::destroyActionSpaces(const char *action_set_name, + uint32_t count, + const GHOST_XrActionSpaceInfo *infos) +{ + GHOST_XrActionSet *action_set = find_action_set(m_oxr.get(), action_set_name); + if (action_set == nullptr) { + return; + } + + for (uint32_t action_idx = 0; action_idx < count; ++action_idx) { + const GHOST_XrActionSpaceInfo &info = infos[action_idx]; + + GHOST_XrAction *action = action_set->findAction(info.action_name); + if (action == nullptr) { + continue; + } + + for (uint32_t subaction_idx = 0; subaction_idx < info.count_subaction_paths; ++subaction_idx) { + action->destroySpace(info.subaction_paths[subaction_idx]); + } + } +} + +bool GHOST_XrSession::createActionBindings(const char *action_set_name, + uint32_t count, + const GHOST_XrActionProfileInfo *infos) +{ + GHOST_XrActionSet *action_set = find_action_set(m_oxr.get(), action_set_name); + if (action_set == nullptr) { + return false; + } + + XrInstance instance = m_context->getInstance(); + + for (uint32_t profile_idx = 0; profile_idx < count; ++profile_idx) { + const GHOST_XrActionProfileInfo &info = infos[profile_idx]; + const char *profile_path = info.profile_path; + + for (uint32_t binding_idx = 0; binding_idx < info.count_bindings; ++binding_idx) { + const GHOST_XrActionBindingInfo &binding = info.bindings[binding_idx]; + + GHOST_XrAction *action = action_set->findAction(binding.action_name); + if (action == nullptr) { + continue; + } + + action->createBinding(instance, profile_path, binding); + } + } + + return true; +} + +void GHOST_XrSession::destroyActionBindings(const char *action_set_name, + uint32_t count, + const GHOST_XrActionProfileInfo *infos) +{ + GHOST_XrActionSet *action_set = find_action_set(m_oxr.get(), action_set_name); + if (action_set == nullptr) { + return; + } + + for (uint32_t profile_idx = 0; profile_idx < count; ++profile_idx) { + const GHOST_XrActionProfileInfo &info = infos[profile_idx]; + const char *profile_path = info.profile_path; + + for (uint32_t binding_idx = 0; binding_idx < info.count_bindings; ++binding_idx) { + const GHOST_XrActionBindingInfo &binding = info.bindings[binding_idx]; + + GHOST_XrAction *action = action_set->findAction(binding.action_name); + if (action == nullptr) { + continue; + } + + action->destroyBinding(profile_path); + } + } +} + +bool GHOST_XrSession::attachActionSets() +{ + /* Suggest action bindings for all action sets. */ + std::map<XrPath, std::vector<XrActionSuggestedBinding>> profile_bindings; + for (auto &[name, action_set] : m_oxr->action_sets) { + action_set.getBindings(profile_bindings); + } + + if (profile_bindings.size() < 1) { + return false; + } + + XrInteractionProfileSuggestedBinding bindings_info{ + XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING}; + XrInstance instance = m_context->getInstance(); + + for (auto &[profile, bindings] : profile_bindings) { + bindings_info.interactionProfile = profile; + bindings_info.countSuggestedBindings = (uint32_t)bindings.size(); + bindings_info.suggestedBindings = bindings.data(); + + CHECK_XR(xrSuggestInteractionProfileBindings(instance, &bindings_info), + "Failed to suggest interaction profile bindings."); + } + + /* Attach action sets. */ + XrSessionActionSetsAttachInfo attach_info{XR_TYPE_SESSION_ACTION_SETS_ATTACH_INFO}; + attach_info.countActionSets = (uint32_t)m_oxr->action_sets.size(); + + /* Create an aligned copy of the action sets to pass to xrAttachSessionActionSets(). */ + std::vector<XrActionSet> action_sets(attach_info.countActionSets); + uint32_t i = 0; + for (auto &[name, action_set] : m_oxr->action_sets) { + action_sets[i++] = action_set.getActionSet(); + } + attach_info.actionSets = action_sets.data(); + + CHECK_XR(xrAttachSessionActionSets(m_oxr->session, &attach_info), + "Failed to attach XR action sets."); + + return true; +} + +bool GHOST_XrSession::syncActions(const char *action_set_name) +{ + std::map<std::string, GHOST_XrActionSet> &action_sets = m_oxr->action_sets; + + XrActionsSyncInfo sync_info{XR_TYPE_ACTIONS_SYNC_INFO}; + sync_info.countActiveActionSets = (action_set_name != nullptr) ? 1 : + (uint32_t)action_sets.size(); + if (sync_info.countActiveActionSets < 1) { + return false; + } + + std::vector<XrActiveActionSet> active_action_sets(sync_info.countActiveActionSets); + GHOST_XrActionSet *action_set = nullptr; + + if (action_set_name != nullptr) { + action_set = find_action_set(m_oxr.get(), action_set_name); + if (action_set == nullptr) { + return false; + } + + XrActiveActionSet &active_action_set = active_action_sets[0]; + active_action_set.actionSet = action_set->getActionSet(); + active_action_set.subactionPath = XR_NULL_PATH; + } + else { + uint32_t i = 0; + for (auto &[name, action_set] : action_sets) { + XrActiveActionSet &active_action_set = active_action_sets[i++]; + active_action_set.actionSet = action_set.getActionSet(); + active_action_set.subactionPath = XR_NULL_PATH; + } + } + sync_info.activeActionSets = active_action_sets.data(); + + CHECK_XR(xrSyncActions(m_oxr->session, &sync_info), "Failed to synchronize XR actions."); + + /* Update action states (i.e. Blender custom data). */ + XrSession session = m_oxr->session; + XrSpace reference_space = m_oxr->reference_space; + const XrTime &predicted_display_time = m_draw_info->frame_state.predictedDisplayTime; + + if (action_set != nullptr) { + action_set->updateStates(session, reference_space, predicted_display_time); + } + else { + for (auto &[name, action_set] : action_sets) { + action_set.updateStates(session, reference_space, predicted_display_time); + } + } + + return true; +} + +bool GHOST_XrSession::applyHapticAction(const char *action_set_name, + const char *action_name, + const GHOST_TInt64 &duration, + const float &frequency, + const float &litude) +{ + GHOST_XrActionSet *action_set = find_action_set(m_oxr.get(), action_set_name); + if (action_set == nullptr) { + return false; + } + + GHOST_XrAction *action = action_set->findAction(action_name); + if (action == nullptr) { + return false; + } + + action->applyHapticFeedback(m_oxr->session, action_name, duration, frequency, amplitude); + + return true; +} + +void GHOST_XrSession::stopHapticAction(const char *action_set_name, const char *action_name) +{ + GHOST_XrActionSet *action_set = find_action_set(m_oxr.get(), action_set_name); + if (action_set == nullptr) { + return; + } + + GHOST_XrAction *action = action_set->findAction(action_name); + if (action == nullptr) { + return; + } + + action->stopHapticFeedback(m_oxr->session, action_name); +} + +void *GHOST_XrSession::getActionSetCustomdata(const char *action_set_name) +{ + GHOST_XrActionSet *action_set = find_action_set(m_oxr.get(), action_set_name); + if (action_set == nullptr) { + return nullptr; + } + + return action_set->getCustomdata(); +} + +void *GHOST_XrSession::getActionCustomdata(const char *action_set_name, const char *action_name) +{ + GHOST_XrActionSet *action_set = find_action_set(m_oxr.get(), action_set_name); + if (action_set == nullptr) { + return nullptr; + } + + GHOST_XrAction *action = action_set->findAction(action_name); + if (action == nullptr) { + return nullptr; + } + + return action->getCustomdata(); +} + +/** \} */ /* Actions */ diff --git a/intern/ghost/intern/GHOST_XrSession.h b/intern/ghost/intern/GHOST_XrSession.h index 79a586411e9..d09c78e1ea7 100644 --- a/intern/ghost/intern/GHOST_XrSession.h +++ b/intern/ghost/intern/GHOST_XrSession.h @@ -52,6 +52,43 @@ class GHOST_XrSession { void draw(void *draw_customdata); + /** Action functions to be called pre-session start. + * Note: The "destroy" functions can also be called post-session start. */ + bool createActionSet(const GHOST_XrActionSetInfo &info); + void destroyActionSet(const char *action_set_name); + bool createActions(const char *action_set_name, uint32_t count, const GHOST_XrActionInfo *infos); + void destroyActions(const char *action_set_name, + uint32_t count, + const char *const *action_names); + bool createActionSpaces(const char *action_set_name, + uint32_t count, + const GHOST_XrActionSpaceInfo *infos); + void destroyActionSpaces(const char *action_set_name, + uint32_t count, + const GHOST_XrActionSpaceInfo *infos); + bool createActionBindings(const char *action_set_name, + uint32_t count, + const GHOST_XrActionProfileInfo *infos); + void destroyActionBindings(const char *action_set_name, + uint32_t count, + const GHOST_XrActionProfileInfo *infos); + bool attachActionSets(); + + /** Action functions to be called post-session start. */ + bool syncActions( + const char *action_set_name = nullptr); /* If action_set_name is nullptr, all attached + * action sets will be synced. */ + bool applyHapticAction(const char *action_set_name, + const char *action_name, + const GHOST_TInt64 &duration, + const float &frequency, + const float &litude); + void stopHapticAction(const char *action_set_name, const char *action_name); + + /* Custom data (owned by Blender, not GHOST) accessors. */ + void *getActionSetCustomdata(const char *action_set_name); + void *getActionCustomdata(const char *action_set_name, const char *action_name); + private: /** Pointer back to context managing this session. Would be nice to avoid, but needed to access * custom callbacks set before session start. */ diff --git a/intern/ghost/intern/GHOST_Xr_intern.h b/intern/ghost/intern/GHOST_Xr_intern.h index 137541c4528..0616e426da3 100644 --- a/intern/ghost/intern/GHOST_Xr_intern.h +++ b/intern/ghost/intern/GHOST_Xr_intern.h @@ -45,3 +45,27 @@ (void)_res; \ } \ (void)0 + +inline void copy_ghost_pose_to_openxr_pose(const GHOST_XrPose &ghost_pose, XrPosef &r_oxr_pose) +{ + /* Set and convert to OpenXR coordinate space. */ + r_oxr_pose.position.x = ghost_pose.position[0]; + r_oxr_pose.position.y = ghost_pose.position[1]; + r_oxr_pose.position.z = ghost_pose.position[2]; + r_oxr_pose.orientation.w = ghost_pose.orientation_quat[0]; + r_oxr_pose.orientation.x = ghost_pose.orientation_quat[1]; + r_oxr_pose.orientation.y = ghost_pose.orientation_quat[2]; + r_oxr_pose.orientation.z = ghost_pose.orientation_quat[3]; +} + +inline void copy_openxr_pose_to_ghost_pose(const XrPosef &oxr_pose, GHOST_XrPose &r_ghost_pose) +{ + /* Set and convert to Blender coordinate space. */ + r_ghost_pose.position[0] = oxr_pose.position.x; + r_ghost_pose.position[1] = oxr_pose.position.y; + r_ghost_pose.position[2] = oxr_pose.position.z; + r_ghost_pose.orientation_quat[0] = oxr_pose.orientation.w; + r_ghost_pose.orientation_quat[1] = oxr_pose.orientation.x; + r_ghost_pose.orientation_quat[2] = oxr_pose.orientation.y; + r_ghost_pose.orientation_quat[3] = oxr_pose.orientation.z; +} diff --git a/release/datafiles/locale b/release/datafiles/locale -Subproject f7b706dd6434db2d752f47c4b8c3148b2990fd7 +Subproject 5ab29b1331d2103dae634b987f121c4599459d7 diff --git a/release/lts/create_download_urls.py b/release/lts/create_download_urls.py index 0d0b2554d2a..80613693e33 100755 --- a/release/lts/create_download_urls.py +++ b/release/lts/create_download_urls.py @@ -27,12 +27,11 @@ class Version: def __str__(self) -> str: return self.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-x64.tar.xz" + yield f"blender-{version}-macos-x64.dmg" + yield f"blender-{version}-windows-x64.msi" + yield f"blender-{version}-windows-x64.zip" def get_download_url(version: Version, file_name: str) -> str: diff --git a/release/scripts/addons b/release/scripts/addons -Subproject 4cb833e84acfd2be5fa08ce75118ce9cb60643b +Subproject 4fcdbfe7c20edfc1204c0aa46c98ea25354abcd diff --git a/release/scripts/addons_contrib b/release/scripts/addons_contrib -Subproject 8970953d4a8a4ea3bf77c66370c817ed0cf1308 +Subproject 7d78c8a63f2f4b146f9327ddc0d567a5921b94e diff --git a/release/scripts/modules/addon_utils.py b/release/scripts/modules/addon_utils.py index 387691f9f05..1e308dc9602 100644 --- a/release/scripts/modules/addon_utils.py +++ b/release/scripts/modules/addon_utils.py @@ -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}" diff --git a/release/scripts/modules/bl_ui_utils/bug_report_url.py b/release/scripts/modules/bl_ui_utils/bug_report_url.py index 5676e0d6815..3fc57467dac 100644 --- a/release/scripts/modules/bl_ui_utils/bug_report_url.py +++ b/release/scripts/modules/bl_ui_utils/bug_report_url.py @@ -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( diff --git a/release/scripts/modules/bpy/path.py b/release/scripts/modules/bpy/path.py index fad52eae84a..e9e9671cc35 100644 --- a/release/scripts/modules/bpy/path.py +++ b/release/scripts/modules/bpy/path.py @@ -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. """ diff --git a/release/scripts/modules/bpy_extras/anim_utils.py b/release/scripts/modules/bpy_extras/anim_utils.py index 269592b3bb1..0868b772a2b 100644 --- a/release/scripts/modules/bpy_extras/anim_utils.py +++ b/release/scripts/modules/bpy_extras/anim_utils.py @@ -296,7 +296,7 @@ def bake_action_iter( pbone.keyframe_insert("rotation_axis_angle", index=-1, frame=f, group=name) else: # euler, XYZ, ZXY etc if euler_prev is not None: - euler = pbone.matrix_basis.to_euler(obj.rotation_mode, euler_prev) + euler = pbone.matrix_basis.to_euler(pbone.rotation_mode, euler_prev) pbone.rotation_euler = euler del euler euler_prev = pbone.rotation_euler.copy() diff --git a/release/scripts/modules/bpy_extras/asset_utils.py b/release/scripts/modules/bpy_extras/asset_utils.py index db982e119d4..1656c21a137 100644 --- a/release/scripts/modules/bpy_extras/asset_utils.py +++ b/release/scripts/modules/bpy_extras/asset_utils.py @@ -34,7 +34,7 @@ __all__ = ( class SpaceAssetInfo: @classmethod def is_asset_browser(cls, space_data: bpy.types.Space): - return space_data.type == 'FILE_BROWSER' and space_data.browse_mode == 'ASSETS' + return space_data and space_data.type == 'FILE_BROWSER' and space_data.browse_mode == 'ASSETS' @classmethod def is_asset_browser_poll(cls, context: Context): diff --git a/release/scripts/modules/rna_manual_reference.py b/release/scripts/modules/rna_manual_reference.py index 4478b0dc367..7e2d09efcd4 100644 --- a/release/scripts/modules/rna_manual_reference.py +++ b/release/scripts/modules/rna_manual_reference.py @@ -305,7 +305,7 @@ url_manual_mapping = ( ("bpy.types.geometrynodealignrotationtovector*", "modeling/geometry_nodes/point/align_rotation_to_vector.html#bpy-types-geometrynodealignrotationtovector"), ("bpy.types.greasepencil.curve_edit_threshold*", "grease_pencil/modes/edit/curve_editing.html#bpy-types-greasepencil-curve-edit-threshold"), ("bpy.types.materialgpencilstyle.stroke_style*", "grease_pencil/materials/properties.html#bpy-types-materialgpencilstyle-stroke-style"), - ("bpy.types.objectlineart.use_crease_override*", "scene_layout/object/properties/lineart.html#bpy-types-objectlineart-use-crease-override"), + ("bpy.types.objectlineart.use_crease_override*", "scene_layout/object/properties/line_art.html#bpy-types-objectlineart-use-crease-override"), ("bpy.types.rendersettings.preview_pixel_size*", "render/cycles/render_settings/performance.html#bpy-types-rendersettings-preview-pixel-size"), ("bpy.types.rendersettings.use_crop_to_border*", "render/output/properties/dimensions.html#bpy-types-rendersettings-use-crop-to-border"), ("bpy.types.rendersettings.use_file_extension*", "render/output/properties/output.html#bpy-types-rendersettings-use-file-extension"), @@ -449,7 +449,7 @@ url_manual_mapping = ( ("bpy.types.nodesocketinterface*.max_value*", "interface/controls/nodes/groups.html#bpy-types-nodesocketinterface-max-value"), ("bpy.types.nodesocketinterface*.min_value*", "interface/controls/nodes/groups.html#bpy-types-nodesocketinterface-min-value"), ("bpy.types.nodesocketinterface.hide_value*", "interface/controls/nodes/groups.html#bpy-types-nodesocketinterface-hide-value"), - ("bpy.types.objectlineart.crease_threshold*", "scene_layout/object/properties/lineart.html#bpy-types-objectlineart-crease-threshold"), + ("bpy.types.objectlineart.crease_threshold*", "scene_layout/object/properties/line_art.html#bpy-types-objectlineart-crease-threshold"), ("bpy.types.rendersettings.use_compositing*", "render/output/properties/post_processing.html#bpy-types-rendersettings-use-compositing"), ("bpy.types.rendersettings.use_placeholder*", "render/output/properties/output.html#bpy-types-rendersettings-use-placeholder"), ("bpy.types.shadernodesubsurfacescattering*", "render/shader_nodes/shader/sss.html#bpy-types-shadernodesubsurfacescattering"), @@ -487,6 +487,7 @@ url_manual_mapping = ( ("bpy.types.fluidflowsettings.temperature*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-temperature"), ("bpy.types.fluidflowsettings.use_texture*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-use-texture"), ("bpy.types.fmodifierenvelopecontrolpoint*", "editors/graph_editor/fcurves/sidebar/modifiers.html#bpy-types-fmodifierenvelopecontrolpoint"), + ("bpy.types.geometrynodeattributemaprange*", "modeling/geometry_nodes/attribute/attribute_map_range.html#bpy-types-geometrynodeattributemaprange"), ("bpy.types.layercollection.hide_viewport*", "editors/outliner/interface.html#bpy-types-layercollection-hide-viewport"), ("bpy.types.layercollection.indirect_only*", "editors/outliner/interface.html#bpy-types-layercollection-indirect-only"), ("bpy.types.material.use_sss_translucency*", "render/eevee/materials/settings.html#bpy-types-material-use-sss-translucency"), @@ -947,7 +948,7 @@ url_manual_mapping = ( ("bpy.types.imagepaint.use_occlude*", "sculpt_paint/texture_paint/tool_settings/options.html#bpy-types-imagepaint-use-occlude"), ("bpy.types.imagesequence.use_flip*", "video_editing/sequencer/sidebar/strip.html#bpy-types-imagesequence-use-flip"), ("bpy.types.latticegpencilmodifier*", "grease_pencil/modifiers/deform/lattice.html#bpy-types-latticegpencilmodifier"), - ("bpy.types.lineartgpencilmodifier*", "grease_pencil/modifiers/generate/lineart.html#bpy-types-lineartgpencilmodifier"), + ("bpy.types.lineartgpencilmodifier*", "grease_pencil/modifiers/generate/line_art.html#bpy-types-lineartgpencilmodifier"), ("bpy.types.mesh.auto_smooth_angle*", "modeling/meshes/structure.html#bpy-types-mesh-auto-smooth-angle"), ("bpy.types.objectsolverconstraint*", "animation/constraints/motion_tracking/object_solver.html#bpy-types-objectsolverconstraint"), ("bpy.types.opacitygpencilmodifier*", "grease_pencil/modifiers/color/opacity.html#bpy-types-opacitygpencilmodifier"), @@ -1110,6 +1111,7 @@ url_manual_mapping = ( ("bpy.types.ffmpegsettings.audio*", "render/output/properties/output.html#bpy-types-ffmpegsettings-audio"), ("bpy.types.followpathconstraint*", "animation/constraints/relationship/follow_path.html#bpy-types-followpathconstraint"), ("bpy.types.gaussianblursequence*", "video_editing/sequencer/strips/effects/blur.html#bpy-types-gaussianblursequence"), + ("bpy.types.geometrynodeboundbox*", "modeling/geometry_nodes/geometry/bounding_box.html#bpy-types-geometrynodeboundbox"), ("bpy.types.geometrynodemeshcone*", "modeling/geometry_nodes/mesh_primitives/cone.html#bpy-types-geometrynodemeshcone"), ("bpy.types.geometrynodemeshcube*", "modeling/geometry_nodes/mesh_primitives/cube.html#bpy-types-geometrynodemeshcube"), ("bpy.types.geometrynodemeshgrid*", "modeling/geometry_nodes/mesh_primitives/grid.html#bpy-types-geometrynodemeshgrid"), @@ -1152,6 +1154,7 @@ url_manual_mapping = ( ("bpy.ops.clip.set_scene_frames*", "movie_clip/tracking/clip/editing/clip.html#bpy-ops-clip-set-scene-frames"), ("bpy.ops.curve.handle_type_set*", "modeling/curves/editing/control_points.html#bpy-ops-curve-handle-type-set"), ("bpy.ops.curve.spline_type_set*", "modeling/curves/editing/curve.html#bpy-ops-curve-spline-type-set"), + ("bpy.ops.file.unpack_libraries*", "files/blend/packed_data.html#bpy-ops-file-unpack-libraries"), ("bpy.ops.gpencil.move_to_layer*", "grease_pencil/modes/edit/stroke_menu.html#bpy-ops-gpencil-move-to-layer"), ("bpy.ops.gpencil.stroke_sample*", "grease_pencil/modes/edit/stroke_menu.html#bpy-ops-gpencil-stroke-sample"), ("bpy.ops.gpencil.stroke_smooth*", "grease_pencil/modes/edit/point_menu.html#bpy-ops-gpencil-stroke-smooth"), @@ -1219,7 +1222,7 @@ url_manual_mapping = ( ("bpy.types.mesh.use_paint_mask*", "sculpt_paint/brush/introduction.html#bpy-types-mesh-use-paint-mask"), ("bpy.types.movietrackingcamera*", "movie_clip/tracking/clip/sidebar/track/camera.html#bpy-types-movietrackingcamera"), ("bpy.types.object.display_type*", "scene_layout/object/properties/display.html#bpy-types-object-display-type"), - ("bpy.types.objectlineart.usage*", "scene_layout/object/properties/lineart.html#bpy-types-objectlineart-usage"), + ("bpy.types.objectlineart.usage*", "scene_layout/object/properties/line_art.html#bpy-types-objectlineart-usage"), ("bpy.types.particledupliweight*", "physics/particles/emitter/vertex_groups.html#bpy-types-particledupliweight"), ("bpy.types.poseboneconstraints*", "animation/armatures/posing/bone_constraints/index.html#bpy-types-poseboneconstraints"), ("bpy.types.rigidbodyconstraint*", "physics/rigid_body/constraints/index.html#bpy-types-rigidbodyconstraint"), @@ -1260,6 +1263,7 @@ url_manual_mapping = ( ("bpy.ops.console.autocomplete*", "editors/python_console.html#bpy-ops-console-autocomplete"), ("bpy.ops.curve.dissolve_verts*", "modeling/curves/editing/curve.html#bpy-ops-curve-dissolve-verts"), ("bpy.ops.curve.duplicate_move*", "modeling/curves/editing/curve.html#bpy-ops-curve-duplicate-move"), + ("bpy.ops.file.autopack_toggle*", "files/blend/packed_data.html#bpy-ops-file-autopack-toggle"), ("bpy.ops.fluid.bake_particles*", "physics/fluid/type/domain/liquid/particles.html#bpy-ops-fluid-bake-particles"), ("bpy.ops.fluid.free_particles*", "physics/fluid/type/domain/liquid/particles.html#bpy-ops-fluid-free-particles"), ("bpy.ops.gpencil.extrude_move*", "grease_pencil/modes/edit/point_menu.html#bpy-ops-gpencil-extrude-move"), @@ -1297,6 +1301,7 @@ url_manual_mapping = ( ("bpy.types.alphaundersequence*", "video_editing/sequencer/strips/effects/alpha_over_under_overdrop.html#bpy-types-alphaundersequence"), ("bpy.types.armature.show_axes*", "animation/armatures/properties/display.html#bpy-types-armature-show-axes"), ("bpy.types.armatureconstraint*", "animation/constraints/relationship/armature.html#bpy-types-armatureconstraint"), + ("bpy.types.compositornodeblur*", "compositing/types/filter/blur_node.html#bpy-types-compositornodeblur"), ("bpy.types.compositornodecomb*", "editors/texture_node/types/color/combine_separate.html#bpy-types-compositornodecomb"), ("bpy.types.compositornodecrop*", "compositing/types/distort/crop.html#bpy-types-compositornodecrop"), ("bpy.types.compositornodeflip*", "compositing/types/distort/flip.html#bpy-types-compositornodeflip"), @@ -1358,6 +1363,7 @@ url_manual_mapping = ( ("bpy.ops.curve.primitive*add*", "modeling/curves/primitives.html#bpy-ops-curve-primitive-add"), ("bpy.ops.curve.smooth_radius*", "modeling/curves/editing/control_points.html#bpy-ops-curve-smooth-radius"), ("bpy.ops.curve.smooth_weight*", "modeling/curves/editing/control_points.html#bpy-ops-curve-smooth-weight"), + ("bpy.ops.file.pack_libraries*", "files/blend/packed_data.html#bpy-ops-file-pack-libraries"), ("bpy.ops.font.change_spacing*", "modeling/texts/editing.html#bpy-ops-font-change-spacing"), ("bpy.ops.gpencil.stroke_flip*", "grease_pencil/modes/edit/stroke_menu.html#bpy-ops-gpencil-stroke-flip"), ("bpy.ops.gpencil.stroke_join*", "grease_pencil/modes/edit/stroke_menu.html#bpy-ops-gpencil-stroke-join"), @@ -1453,7 +1459,7 @@ url_manual_mapping = ( ("bpy.types.viewlayer.use_sky*", "render/layers/introduction.html#bpy-types-viewlayer-use-sky"), ("bpy.types.wireframemodifier*", "modeling/modifiers/generate/wireframe.html#bpy-types-wireframemodifier"), ("bpy.types.worldmistsettings*", "render/cycles/world_settings.html#bpy-types-worldmistsettings"), - ("bpy.ops.anim.channels_move*", "editors/nla/editing.html#bpy-ops-anim-channels-move"), + ("bpy.ops.anim.channels_move*", "editors/graph_editor/channels.html#bpy-ops-anim-channels-move"), ("bpy.ops.buttons.toggle_pin*", "editors/properties_editor.html#bpy-ops-buttons-toggle-pin"), ("bpy.ops.clip.filter_tracks*", "movie_clip/tracking/clip/editing/track.html#bpy-ops-clip-filter-tracks"), ("bpy.ops.clip.select_circle*", "movie_clip/tracking/clip/selecting.html#bpy-ops-clip-select-circle"), @@ -1691,6 +1697,7 @@ url_manual_mapping = ( ("bpy.ops.clip.set_origin*", "movie_clip/tracking/clip/editing/reconstruction.html#bpy-ops-clip-set-origin"), ("bpy.ops.curve.subdivide*", "modeling/curves/editing/segments.html#bpy-ops-curve-subdivide"), ("bpy.ops.ed.undo_history*", "interface/undo_redo.html#bpy-ops-ed-undo-history"), + ("bpy.ops.file.unpack_all*", "files/blend/packed_data.html#bpy-ops-file-unpack-all"), ("bpy.ops.fluid.bake_data*", "physics/fluid/type/domain/settings.html#bpy-ops-fluid-bake-data"), ("bpy.ops.fluid.bake_mesh*", "physics/fluid/type/domain/liquid/mesh.html#bpy-ops-fluid-bake-mesh"), ("bpy.ops.fluid.free_data*", "physics/fluid/type/domain/settings.html#bpy-ops-fluid-free-data"), @@ -1749,7 +1756,7 @@ url_manual_mapping = ( ("bpy.types.nlastrip.name*", "editors/nla/sidebar.html#bpy-types-nlastrip-name"), ("bpy.types.nodesmodifier*", "modeling/modifiers/generate/geometry_nodes.html#bpy-types-nodesmodifier"), ("bpy.types.object.parent*", "scene_layout/object/editing/parent.html#bpy-types-object-parent"), - ("bpy.types.objectlineart*", "scene_layout/object/properties/lineart.html#bpy-types-objectlineart"), + ("bpy.types.objectlineart*", "scene_layout/object/properties/line_art.html#bpy-types-objectlineart"), ("bpy.types.oceanmodifier*", "modeling/modifiers/physics/ocean.html#bpy-types-oceanmodifier"), ("bpy.types.particlebrush*", "physics/particles/mode.html#bpy-types-particlebrush"), ("bpy.types.scene.gravity*", "physics/forces/gravity.html#bpy-types-scene-gravity"), @@ -1845,6 +1852,7 @@ url_manual_mapping = ( ("bpy.types.wipesequence*", "video_editing/sequencer/strips/transitions/wipe.html#bpy-types-wipesequence"), ("bpy.ops.clip.prefetch*", "movie_clip/tracking/clip/editing/clip.html#bpy-ops-clip-prefetch"), ("bpy.ops.clip.set_axis*", "movie_clip/tracking/clip/editing/reconstruction.html#bpy-ops-clip-set-axis"), + ("bpy.ops.file.pack_all*", "files/blend/packed_data.html#bpy-ops-file-pack-all"), ("bpy.ops.gpencil.paste*", "grease_pencil/modes/edit/grease_pencil_menu.html#bpy-ops-gpencil-paste"), ("bpy.ops.image.project*", "sculpt_paint/texture_paint/tool_settings/options.html#bpy-ops-image-project"), ("bpy.ops.material.copy*", "render/materials/assignment.html#bpy-ops-material-copy"), @@ -2115,9 +2123,4 @@ url_manual_mapping = ( ("bpy.ops.ed*", "interface/undo_redo.html#bpy-ops-ed"), ("bpy.ops.ui*", "interface/index.html#bpy-ops-ui"), ("bpy.ops.wm*", "interface/index.html#bpy-ops-wm"), - ("bpy.ops.file.pack_all", "blend/packed_data#pack-resources"), - ("bpy.ops.file.unpack_all", "blend/packed_data#unpack-resources"), - ("bpy.ops.file.autopack_toggle", "blend/packed_data#automatically-pack-resources"), - ("bpy.ops.file.pack_libraries", "blend/packed_data#pack-linked-libraries"), - ("bpy.ops.file.unpack_libraries", "blend/packed_data#unpack-linked-libraries"), ) diff --git a/release/scripts/modules/rna_prop_ui.py b/release/scripts/modules/rna_prop_ui.py index e3158118146..54cde1e1c04 100644 --- a/release/scripts/modules/rna_prop_ui.py +++ b/release/scripts/modules/rna_prop_ui.py @@ -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. diff --git a/release/scripts/modules/sys_info.py b/release/scripts/modules/sys_info.py index 5116e0f0088..192fc1a201f 100644 --- a/release/scripts/modules/sys_info.py +++ b/release/scripts/modules/sys_info.py @@ -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 diff --git a/release/scripts/presets/keyconfig/keymap_data/blender_default.py b/release/scripts/presets/keyconfig/keymap_data/blender_default.py index 8d0a5806adc..9bccc69d41f 100644 --- a/release/scripts/presets/keyconfig/keymap_data/blender_default.py +++ b/release/scripts/presets/keyconfig/keymap_data/blender_default.py @@ -278,15 +278,11 @@ def _template_items_uv_select_mode(params): else: return [ *_template_items_editmode_mesh_select_mode(params), + # Hack to prevent fall-through, when sync select isn't enabled (and the island button isn't visible). ("mesh.select_mode", {"type": 'FOUR', "value": 'PRESS'}, None), - ("wm.context_set_enum", {"type": 'ONE', "value": 'PRESS'}, - {"properties": [("data_path", 'tool_settings.uv_select_mode'), ("value", 'VERTEX')]}), - ("wm.context_set_enum", {"type": 'TWO', "value": 'PRESS'}, - {"properties": [("data_path", 'tool_settings.uv_select_mode'), ("value", 'EDGE')]}), - ("wm.context_set_enum", {"type": 'THREE', "value": 'PRESS'}, - {"properties": [("data_path", 'tool_settings.uv_select_mode'), ("value", 'FACE')]}), - ("wm.context_set_enum", {"type": 'FOUR', "value": 'PRESS'}, - {"properties": [("data_path", 'tool_settings.uv_select_mode'), ("value", 'ISLAND')]}), + *(("wm.context_set_enum", {"type": NUMBERS_1[i], "value": 'PRESS'}, + {"properties": [("data_path", 'tool_settings.uv_select_mode'), ("value", ty)]}) + for i, ty in enumerate(('VERTEX', 'EDGE', 'FACE', 'ISLAND'))) ] @@ -3381,6 +3377,11 @@ def km_grease_pencil_stroke_paint_mode(params): # Brush size ("wm.radial_control", {"type": 'F', "value": 'PRESS'}, {"properties": [("data_path_primary", 'tool_settings.gpencil_paint.brush.size')]}), + # Increase/Decrease brush size + ("brush.scale_size", {"type": 'LEFT_BRACKET', "value": 'PRESS', "repeat": True}, + {"properties": [("scalar", 0.9)]}), + ("brush.scale_size", {"type": 'RIGHT_BRACKET', "value": 'PRESS', "repeat": True}, + {"properties": [("scalar", 1.0 / 0.9)]}), # Draw delete menu op_menu("GPENCIL_MT_gpencil_draw_delete", {"type": 'X', "value": 'PRESS'}), # Animation menu @@ -3548,6 +3549,11 @@ def km_grease_pencil_stroke_sculpt_mode(params): # Brush size ("wm.radial_control", {"type": 'F', "value": 'PRESS'}, {"properties": [("data_path_primary", 'tool_settings.gpencil_sculpt_paint.brush.size')]}), + # Increase/Decrease brush size + ("brush.scale_size", {"type": 'LEFT_BRACKET', "value": 'PRESS', "repeat": True}, + {"properties": [("scalar", 0.9)]}), + ("brush.scale_size", {"type": 'RIGHT_BRACKET', "value": 'PRESS', "repeat": True}, + {"properties": [("scalar", 1.0 / 0.9)]}), # Copy ("gpencil.copy", {"type": 'C', "value": 'PRESS', "ctrl": True}, None), # Display @@ -3762,6 +3768,11 @@ def km_grease_pencil_stroke_weight_mode(params): # Brush size ("wm.radial_control", {"type": 'F', "value": 'PRESS'}, {"properties": [("data_path_primary", 'tool_settings.gpencil_weight_paint.brush.size')]}), + # Increase/Decrease brush size + ("brush.scale_size", {"type": 'LEFT_BRACKET', "value": 'PRESS', "repeat": True}, + {"properties": [("scalar", 0.9)]}), + ("brush.scale_size", {"type": 'RIGHT_BRACKET', "value": 'PRESS', "repeat": True}, + {"properties": [("scalar", 1.0 / 0.9)]}), # Display *_grease_pencil_display(), # Keyframe menu @@ -3819,6 +3830,11 @@ def km_grease_pencil_stroke_vertex_mode(params): # Brush size ("wm.radial_control", {"type": 'F', "value": 'PRESS'}, {"properties": [("data_path_primary", 'tool_settings.gpencil_vertex_paint.brush.size')]}), + # Increase/Decrease brush size + ("brush.scale_size", {"type": 'LEFT_BRACKET', "value": 'PRESS', "repeat": True}, + {"properties": [("scalar", 0.9)]}), + ("brush.scale_size", {"type": 'RIGHT_BRACKET', "value": 'PRESS', "repeat": True}, + {"properties": [("scalar", 1.0 / 0.9)]}), # Display *_grease_pencil_display(), # Tools diff --git a/release/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py b/release/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py index 31f2be57e38..dbf583149e3 100644 --- a/release/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py +++ b/release/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py @@ -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'}), diff --git a/release/scripts/startup/bl_operators/clip.py b/release/scripts/startup/bl_operators/clip.py index 0470f542c63..45d1ea98a8a 100644 --- a/release/scripts/startup/bl_operators/clip.py +++ b/release/scripts/startup/bl_operators/clip.py @@ -206,8 +206,8 @@ class CLIP_OT_filter_tracks(Operator): @classmethod def poll(cls, context): - space = context.space_data - return (space.type == 'CLIP_EDITOR') and space.clip + sc = context.space_data + return sc and (sc.type == 'CLIP_EDITOR') and sc.clip def execute(self, context): num_tracks = self._filter_values(context, self.track_threshold) @@ -221,8 +221,8 @@ class CLIP_OT_set_active_clip(Operator): @classmethod def poll(cls, context): - space = context.space_data - return space.type == 'CLIP_EDITOR' and space.clip + sc = context.space_data + return sc and (sc.type == 'CLIP_EDITOR') and sc.clip def execute(self, context): clip = context.space_data.clip @@ -268,8 +268,8 @@ class CLIP_OT_track_to_empty(Operator): @classmethod def poll(cls, context): - space = context.space_data - return space.type == 'CLIP_EDITOR' and space.clip + sc = context.space_data + return sc and (sc.type == 'CLIP_EDITOR') and sc.clip def execute(self, context): sc = context.space_data @@ -293,7 +293,7 @@ class CLIP_OT_bundles_to_mesh(Operator): @classmethod def poll(cls, context): sc = context.space_data - return (sc.type == 'CLIP_EDITOR') and sc.clip + return sc and (sc.type == 'CLIP_EDITOR') and sc.clip def execute(self, context): from bpy_extras.io_utils import unpack_list @@ -341,12 +341,8 @@ class CLIP_OT_delete_proxy(Operator): @classmethod def poll(cls, context): - if context.space_data.type != 'CLIP_EDITOR': - return False - sc = context.space_data - - return sc.clip + return sc and (sc.type == 'CLIP_EDITOR') and sc.clip def invoke(self, context, event): wm = context.window_manager @@ -424,12 +420,8 @@ class CLIP_OT_set_viewport_background(Operator): @classmethod def poll(cls, context): - if context.space_data.type != 'CLIP_EDITOR': - return False - sc = context.space_data - - return sc.clip + return sc and (sc.type == 'CLIP_EDITOR') and sc.clip def execute(self, context): sc = context.space_data @@ -563,13 +555,11 @@ class CLIP_OT_setup_tracking_scene(Operator): @classmethod def poll(cls, context): sc = context.space_data - - if sc.type != 'CLIP_EDITOR': - return False - - clip = sc.clip - - return clip and clip.tracking.reconstruction.is_valid + if sc and sc.type == 'CLIP_EDITOR': + clip = sc.clip + if clip and clip.tracking.reconstruction.is_valid: + return True + return False @staticmethod def _setupScene(context): @@ -1018,13 +1008,11 @@ class CLIP_OT_track_settings_as_default(Operator): @classmethod def poll(cls, context): sc = context.space_data - - if sc.type != 'CLIP_EDITOR': - return False - - clip = sc.clip - - return clip and clip.tracking.tracks.active + if sc and sc.type == 'CLIP_EDITOR': + clip = sc.clip + if clip and clip.tracking.tracks.active: + return True + return False def execute(self, context): sc = context.space_data @@ -1068,11 +1056,12 @@ class CLIP_OT_track_settings_to_track(Operator): @classmethod def poll(cls, context): - space = context.space_data - if space.type != 'CLIP_EDITOR': - return False - clip = space.clip - return clip and clip.tracking.tracks.active + sc = context.space_data + if sc and sc.type == 'CLIP_EDITOR': + clip = sc.clip + if clip and clip.tracking.tracks.active: + return True + return False def execute(self, context): space = context.space_data diff --git a/release/scripts/startup/bl_operators/constraint.py b/release/scripts/startup/bl_operators/constraint.py index 49fc6a04112..f18f3bb3a49 100644 --- a/release/scripts/startup/bl_operators/constraint.py +++ b/release/scripts/startup/bl_operators/constraint.py @@ -33,6 +33,11 @@ class CONSTRAINT_OT_add_target(Operator): bl_label = "Add Target" bl_options = {'UNDO', 'INTERNAL'} + @classmethod + def poll(cls, context): + constraint = getattr(context, "constraint", None) + return constraint + def execute(self, context): context.constraint.targets.new() return {'FINISHED'} @@ -46,6 +51,11 @@ class CONSTRAINT_OT_remove_target(Operator): index: IntProperty() + @classmethod + def poll(cls, context): + constraint = getattr(context, "constraint", None) + return constraint + def execute(self, context): tgts = context.constraint.targets tgts.remove(tgts[self.index]) @@ -58,6 +68,11 @@ class CONSTRAINT_OT_normalize_target_weights(Operator): bl_label = "Normalize Weights" bl_options = {'UNDO', 'INTERNAL'} + @classmethod + def poll(cls, context): + constraint = getattr(context, "constraint", None) + return constraint + def execute(self, context): tgts = context.constraint.targets total = sum(t.weight for t in tgts) diff --git a/release/scripts/startup/bl_operators/node.py b/release/scripts/startup/bl_operators/node.py index e0d0fc1e145..6150789ea10 100644 --- a/release/scripts/startup/bl_operators/node.py +++ b/release/scripts/startup/bl_operators/node.py @@ -120,7 +120,7 @@ class NodeAddOperator: def poll(cls, context): space = context.space_data # needs active node editor and a tree to add nodes to - return ((space.type == 'NODE_EDITOR') and + return (space and (space.type == 'NODE_EDITOR') and space.edit_tree and not space.edit_tree.library) # Default execute simply adds a node @@ -265,7 +265,7 @@ class NODE_OT_collapse_hide_unused_toggle(Operator): def poll(cls, context): space = context.space_data # needs active node editor and a tree - return ((space.type == 'NODE_EDITOR') and + return (space and (space.type == 'NODE_EDITOR') and (space.edit_tree and not space.edit_tree.library)) def execute(self, context): @@ -296,7 +296,7 @@ class NODE_OT_tree_path_parent(Operator): def poll(cls, context): space = context.space_data # needs active node editor and a tree - return (space.type == 'NODE_EDITOR' and len(space.path) > 1) + return (space and (space.type == 'NODE_EDITOR') and len(space.path) > 1) def execute(self, context): space = context.space_data @@ -315,6 +315,8 @@ class NODE_OT_active_preview_toggle(Operator): @classmethod def poll(cls, context): space = context.space_data + if space is None: + return False if space.type != 'NODE_EDITOR': return False if space.edit_tree is None: diff --git a/release/scripts/startup/bl_operators/object.py b/release/scripts/startup/bl_operators/object.py index 5a388047ddd..d61bed71cab 100644 --- a/release/scripts/startup/bl_operators/object.py +++ b/release/scripts/startup/bl_operators/object.py @@ -133,7 +133,7 @@ class SelectCamera(Operator): scene = context.scene view_layer = context.view_layer view = context.space_data - if view.type == 'VIEW_3D' and view.use_local_camera: + if view and view.type == 'VIEW_3D' and view.use_local_camera: camera = view.camera else: camera = scene.camera diff --git a/release/scripts/startup/bl_operators/userpref.py b/release/scripts/startup/bl_operators/userpref.py index f77f00d2234..3969386bad7 100644 --- a/release/scripts/startup/bl_operators/userpref.py +++ b/release/scripts/startup/bl_operators/userpref.py @@ -590,7 +590,7 @@ class PREFERENCES_OT_addon_install(Operator): name="Target Path", items=( ('DEFAULT', "Default", ""), - ('PREFS', "User Prefs", ""), + ('PREFS', "Preferences", ""), ), ) diff --git a/release/scripts/startup/bl_operators/view3d.py b/release/scripts/startup/bl_operators/view3d.py index ff5bcdb034f..0fa82e36d20 100644 --- a/release/scripts/startup/bl_operators/view3d.py +++ b/release/scripts/startup/bl_operators/view3d.py @@ -208,7 +208,8 @@ class VIEW3D_OT_transform_gizmo_set(Operator): @classmethod def poll(cls, context): - return context.area.type == 'VIEW_3D' + area = context.area + return area and (area.type == 'VIEW_3D') def execute(self, context): space_data = context.space_data diff --git a/release/scripts/startup/bl_ui/properties_data_bone.py b/release/scripts/startup/bl_ui/properties_data_bone.py index f3e116ca321..6452ad8465b 100644 --- a/release/scripts/startup/bl_ui/properties_data_bone.py +++ b/release/scripts/startup/bl_ui/properties_data_bone.py @@ -292,10 +292,15 @@ class BONE_PT_display_custom_shape(BoneButtonsPanel, Panel): sub = col.column() sub.active = bool(pchan and pchan.custom_shape) sub.separator() - sub.prop(pchan, "custom_shape_scale", text="Scale") + + sub.prop(pchan, "custom_shape_scale_xyz", text="Scale") + sub.prop(pchan, "custom_shape_translation", text="Translation") + sub.prop(pchan, "custom_shape_rotation_euler", text="Rotation") + sub.prop_search(pchan, "custom_shape_transform", ob.pose, "bones", text="Override Transform") sub.prop(pchan, "use_custom_shape_bone_size") + sub.separator() sub.prop(bone, "show_wire", text="Wireframe") diff --git a/release/scripts/startup/bl_ui/properties_data_gpencil.py b/release/scripts/startup/bl_ui/properties_data_gpencil.py index 69720a6c54b..e71ea2f31a4 100644 --- a/release/scripts/startup/bl_ui/properties_data_gpencil.py +++ b/release/scripts/startup/bl_ui/properties_data_gpencil.py @@ -113,7 +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_copy_layer") + 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): diff --git a/release/scripts/startup/bl_ui/properties_grease_pencil_common.py b/release/scripts/startup/bl_ui/properties_grease_pencil_common.py index 3064b33c7f7..55a49878b71 100644 --- a/release/scripts/startup/bl_ui/properties_grease_pencil_common.py +++ b/release/scripts/startup/bl_ui/properties_grease_pencil_common.py @@ -887,37 +887,43 @@ class GreasePencilFlipTintColors(Operator): bl_idname = "gpencil.tint_flip" bl_description = "Switch tint colors" - def execute(self, context): - try: - ts = context.tool_settings - settings = None - if context.mode == 'PAINT_GPENCIL': - settings = ts.gpencil_paint - if context.mode == 'SCULPT_GPENCIL': - settings = ts.gpencil_sculpt_paint - elif context.mode == 'WEIGHT_GPENCIL': - settings = ts.gpencil_weight_paint - elif context.mode == 'VERTEX_GPENCIL': - settings = ts.gpencil_vertex_paint - - brush = settings.brush - if brush is not None: - color = brush.color - secondary_color = brush.secondary_color + @classmethod + def poll(cls, context): + ts = context.tool_settings + settings = None + if context.mode == 'PAINT_GPENCIL': + settings = ts.gpencil_paint + if context.mode == 'SCULPT_GPENCIL': + settings = ts.gpencil_sculpt_paint + elif context.mode == 'WEIGHT_GPENCIL': + settings = ts.gpencil_weight_paint + elif context.mode == 'VERTEX_GPENCIL': + settings = ts.gpencil_vertex_paint - orig_prim = color.hsv - orig_sec = secondary_color.hsv + return settings and settings.brush - color.hsv = orig_sec - secondary_color.hsv = orig_prim + def execute(self, context): + ts = context.tool_settings + settings = None + if context.mode == 'PAINT_GPENCIL': + settings = ts.gpencil_paint + if context.mode == 'SCULPT_GPENCIL': + settings = ts.gpencil_sculpt_paint + elif context.mode == 'WEIGHT_GPENCIL': + settings = ts.gpencil_weight_paint + elif context.mode == 'VERTEX_GPENCIL': + settings = ts.gpencil_vertex_paint - return {'FINISHED'} + brush = settings.brush + color = brush.color + secondary_color = brush.secondary_color - except Exception as e: - utils_core.error_handlers(self, "gpencil.tint_flip", e, - "Flip Colors could not be completed") + orig_prim = color.hsv + orig_sec = secondary_color.hsv - return {'CANCELLED'} + color.hsv = orig_sec + secondary_color.hsv = orig_prim + return {'FINISHED'} classes = ( diff --git a/release/scripts/startup/bl_ui/properties_material_gpencil.py b/release/scripts/startup/bl_ui/properties_material_gpencil.py index 6a5c000116f..9d099ff2231 100644 --- a/release/scripts/startup/bl_ui/properties_material_gpencil.py +++ b/release/scripts/startup/bl_ui/properties_material_gpencil.py @@ -46,13 +46,19 @@ 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.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): def draw_item(self, _context, layout, _data, item, icon, _active_data, _active_propname, _index): diff --git a/release/scripts/startup/bl_ui/properties_paint_common.py b/release/scripts/startup/bl_ui/properties_paint_common.py index 4e3e4c2d58d..f3462dfb35d 100644 --- a/release/scripts/startup/bl_ui/properties_paint_common.py +++ b/release/scripts/startup/bl_ui/properties_paint_common.py @@ -1225,7 +1225,7 @@ def brush_basic_gpencil_paint_settings(layout, context, brush, *, compact=False) row = layout.row(align=True) row.prop(gp_settings, "fill_factor") row = layout.row(align=True) - row.prop(gp_settings, "dilate_pixels") + row.prop(gp_settings, "dilate") row = layout.row(align=True) row.prop(brush, "size", text="Thickness") layout.use_property_split = use_property_split_prev diff --git a/release/scripts/startup/bl_ui/space_outliner.py b/release/scripts/startup/bl_ui/space_outliner.py index 1e799379543..6eafa570f4c 100644 --- a/release/scripts/startup/bl_ui/space_outliner.py +++ b/release/scripts/startup/bl_ui/space_outliner.py @@ -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" diff --git a/release/scripts/startup/bl_ui/space_time.py b/release/scripts/startup/bl_ui/space_time.py index 774e2938deb..30967e9746d 100644 --- a/release/scripts/startup/bl_ui/space_time.py +++ b/release/scripts/startup/bl_ui/space_time.py @@ -300,6 +300,8 @@ class TIME_PT_keyframing_settings(TimelinePanelButtons, Panel): col.label(text="New Keyframe Type") col.prop(tool_settings, "keyframe_type", text="") + layout.prop(tool_settings, "use_keyframe_cycle_aware") + class TIME_PT_auto_keyframing(TimelinePanelButtons, Panel): bl_label = "Auto Keyframing" @@ -327,8 +329,6 @@ class TIME_PT_auto_keyframing(TimelinePanelButtons, Panel): if not prefs.edit.use_keyframe_insert_available: col.prop(tool_settings, "use_record_with_nla", text="Layered Recording") - col.prop(tool_settings, "use_keyframe_cycle_aware") - ################################### diff --git a/release/scripts/startup/bl_ui/space_view3d.py b/release/scripts/startup/bl_ui/space_view3d.py index d86e65d9f0d..18c6564b7d4 100644 --- a/release/scripts/startup/bl_ui/space_view3d.py +++ b/release/scripts/startup/bl_ui/space_view3d.py @@ -4956,28 +4956,6 @@ class VIEW3D_MT_assign_material(Menu): icon='LAYER_ACTIVE' if mat == mat_active else 'BLANK1').material = mat.name -class VIEW3D_MT_gpencil_copy_layer(Menu): - bl_label = "Copy Layer to Object" - - def draw(self, context): - layout = self.layout - 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: - layout.operator("gpencil.layer_duplicate_object", text=ob.name).object = ob.name - 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_edit_gpencil(Menu): bl_label = "Grease Pencil" @@ -6191,10 +6169,13 @@ class VIEW3D_PT_overlay_geometry(Panel): sub.prop(overlay, "wireframe_opacity", text="Opacity") row = col.row(align=True) - if context.mode not in { - 'EDIT_ARMATURE', 'POSE', 'OBJECT', - 'PAINT_GPENCIL', 'VERTEX_GPENCIL', 'WEIGHT_GPENCIL', 'SCULPT_GPENCIL', 'EDIT_GPENCIL', - }: + + # These properties should be always available in the UI for all modes + # other than Object. + # Even when the Fade Inactive Geometry overlay is not affecting the + # current active object depending on its mode, it will always affect + # the rest of the scene. + if context.mode != 'OBJECT': row.prop(overlay, "show_fade_inactive", text="") sub = row.row() sub.active = overlay.show_fade_inactive @@ -7645,7 +7626,6 @@ classes = ( VIEW3D_MT_weight_gpencil, VIEW3D_MT_gpencil_animation, VIEW3D_MT_gpencil_simplify, - VIEW3D_MT_gpencil_copy_layer, VIEW3D_MT_gpencil_autoweights, VIEW3D_MT_gpencil_edit_context_menu, VIEW3D_MT_edit_curve, diff --git a/release/scripts/startup/nodeitems_builtins.py b/release/scripts/startup/nodeitems_builtins.py index 379fa145c92..6006161debf 100644 --- a/release/scripts/startup/nodeitems_builtins.py +++ b/release/scripts/startup/nodeitems_builtins.py @@ -488,11 +488,13 @@ geometry_node_categories = [ NodeItem("GeometryNodeAttributeClamp"), NodeItem("GeometryNodeAttributeCompare"), NodeItem("GeometryNodeAttributeConvert"), + NodeItem("GeometryNodeAttributeCurveMap"), NodeItem("GeometryNodeAttributeFill"), NodeItem("GeometryNodeAttributeMix"), NodeItem("GeometryNodeAttributeProximity"), NodeItem("GeometryNodeAttributeColorRamp"), NodeItem("GeometryNodeAttributeVectorMath"), + NodeItem("GeometryNodeAttributeVectorRotate"), NodeItem("GeometryNodeAttributeSampleTexture"), NodeItem("GeometryNodeAttributeCombineXYZ"), NodeItem("GeometryNodeAttributeSeparateXYZ"), @@ -507,6 +509,7 @@ geometry_node_categories = [ ]), GeometryNodeCategory("GEO_CURVE", "Curve", items=[ NodeItem("GeometryNodeCurveToMesh"), + NodeItem("GeometryNodeCurveResample"), ]), GeometryNodeCategory("GEO_GEOMETRY", "Geometry", items=[ NodeItem("GeometryNodeBoundBox"), @@ -520,8 +523,12 @@ geometry_node_categories = [ NodeItem("ShaderNodeValue"), NodeItem("FunctionNodeInputString"), NodeItem("FunctionNodeInputVector"), + NodeItem("GeometryNodeInputMaterial"), NodeItem("GeometryNodeIsViewport"), ]), + GeometryNodeCategory("GEO_MATERIAL", "Material", items=[ + NodeItem("GeometryNodeMaterialAssign"), + ]), GeometryNodeCategory("GEO_MESH", "Mesh", items=[ NodeItem("GeometryNodeBoolean"), NodeItem("GeometryNodeTriangulate"), diff --git a/source/blender/blenkernel/BKE_attribute_access.hh b/source/blender/blenkernel/BKE_attribute_access.hh index 5feae1bf4ca..c3bc4d3ca4a 100644 --- a/source/blender/blenkernel/BKE_attribute_access.hh +++ b/source/blender/blenkernel/BKE_attribute_access.hh @@ -27,6 +27,85 @@ #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; + + constexpr friend bool operator==(AttributeMetaData a, AttributeMetaData b) + { + return (a.domain == b.domain) && (a.data_type == b.data_type); + } +}; + +/** + * Base class for the attribute initializer 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 { @@ -231,4 +310,36 @@ template<typename T> class OutputAttribute_Typed { } }; +/** + * A basic container around DNA CustomData so that its users + * don't have to implement special copy and move constructors. + */ +class CustomDataAttributes { + /** + * #CustomData needs a size to be freed, and unfortunately it isn't stored in the struct + * itself, so keep track of the size here so this class can implement its own destructor. + * If the implementation of the attribute storage changes, this could be removed. + */ + int size_; + + public: + CustomData data; + + CustomDataAttributes(); + ~CustomDataAttributes(); + CustomDataAttributes(const CustomDataAttributes &other); + CustomDataAttributes(CustomDataAttributes &&other); + + void reallocate(const int size); + + std::optional<blender::fn::GSpan> get_for_read(const blender::StringRef name) const; + std::optional<blender::fn::GMutableSpan> get_for_write(const blender::StringRef name); + bool create(const blender::StringRef name, const CustomDataType data_type); + bool create_by_move(const blender::StringRef name, const CustomDataType data_type, void *buffer); + bool remove(const blender::StringRef name); + + bool foreach_attribute(const AttributeForeachCallback callback, + const AttributeDomain domain) const; +}; + } // namespace blender::bke diff --git a/source/blender/blenkernel/BKE_attribute_math.hh b/source/blender/blenkernel/BKE_attribute_math.hh index b0d32b20d44..0afdc436415 100644 --- a/source/blender/blenkernel/BKE_attribute_math.hh +++ b/source/blender/blenkernel/BKE_attribute_math.hh @@ -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: diff --git a/source/blender/blenkernel/BKE_callbacks.h b/source/blender/blenkernel/BKE_callbacks.h index fadba5644de..f04b5e45720 100644 --- a/source/blender/blenkernel/BKE_callbacks.h +++ b/source/blender/blenkernel/BKE_callbacks.h @@ -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; diff --git a/source/blender/blenkernel/BKE_geometry_set.hh b/source/blender/blenkernel/BKE_geometry_set.hh index 106af8172d1..3b3856f11ab 100644 --- a/source/blender/blenkernel/BKE_geometry_set.hh +++ b/source/blender/blenkernel/BKE_geometry_set.hh @@ -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" @@ -58,79 +57,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. */ class GeometryComponent { @@ -597,6 +523,7 @@ class InstancesComponent : public GeometryComponent { void clear(); void reserve(int min_capacity); + void resize(int capacity); int add_reference(InstanceReference reference); void add_instance(int instance_handle, const blender::float4x4 &transform, const int id = -1); @@ -604,6 +531,7 @@ class InstancesComponent : public GeometryComponent { blender::Span<InstanceReference> references() const; blender::Span<int> instance_reference_handles() const; + blender::MutableSpan<int> instance_reference_handles(); blender::MutableSpan<blender::float4x4> instance_transforms(); blender::Span<blender::float4x4> instance_transforms() const; blender::MutableSpan<int> instance_ids(); diff --git a/source/blender/blenkernel/BKE_gpencil.h b/source/blender/blenkernel/BKE_gpencil.h index 4b4886e8bf3..bb145580928 100644 --- a/source/blender/blenkernel/BKE_gpencil.h +++ b/source/blender/blenkernel/BKE_gpencil.h @@ -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, diff --git a/source/blender/blenkernel/BKE_image.h b/source/blender/blenkernel/BKE_image.h index c51a5f7e5e1..d298e5dcf6d 100644 --- a/source/blender/blenkernel/BKE_image.h +++ b/source/blender/blenkernel/BKE_image.h @@ -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]); diff --git a/source/blender/blenkernel/BKE_lib_id.h b/source/blender/blenkernel/BKE_lib_id.h index adac92105ee..6b706f3bcd0 100644 --- a/source/blender/blenkernel/BKE_lib_id.h +++ b/source/blender/blenkernel/BKE_lib_id.h @@ -104,6 +104,10 @@ enum { * specific code in some copy cases (mostly for node trees). */ LIB_ID_CREATE_LOCAL = 1 << 9, + /** Create for the depsgraph, when set #LIB_TAG_COPIED_ON_WRITE must be set. + * Internally this is used to share some pointers instead of duplicating them. */ + LIB_ID_COPY_SET_COPIED_ON_WRITE = 1 << 10, + /* *** Specific options to some ID types or usages. *** */ /* *** May be ignored by unrelated ID copying functions. *** */ /** Object only, needed by make_local code. */ diff --git a/source/blender/blenkernel/BKE_lib_override.h b/source/blender/blenkernel/BKE_lib_override.h index b9a478f8227..f1ed5a453ba 100644 --- a/source/blender/blenkernel/BKE_lib_override.h +++ b/source/blender/blenkernel/BKE_lib_override.h @@ -85,10 +85,12 @@ bool BKE_lib_override_library_resync(struct Main *bmain, struct ID *id_root, struct Collection *override_resync_residual_storage, const bool do_hierarchy_enforce, - const bool do_post_process); + const bool do_post_process, + struct ReportList *reports); void BKE_lib_override_library_main_resync(struct Main *bmain, struct Scene *scene, - struct ViewLayer *view_layer); + struct ViewLayer *view_layer, + struct ReportList *reports); void BKE_lib_override_library_delete(struct Main *bmain, struct ID *id_root); diff --git a/source/blender/blenkernel/BKE_material.h b/source/blender/blenkernel/BKE_material.h index 14ea50f808a..dc471fcb62f 100644 --- a/source/blender/blenkernel/BKE_material.h +++ b/source/blender/blenkernel/BKE_material.h @@ -51,6 +51,9 @@ void BKE_object_material_remap(struct Object *ob, const unsigned int *remap); void BKE_object_material_remap_calc(struct Object *ob_dst, struct Object *ob_src, short *remap_src_to_dst); +void BKE_object_material_from_eval_data(struct Main *bmain, + struct Object *ob_orig, + struct ID *data_eval); struct Material *BKE_material_add(struct Main *bmain, const char *name); struct Material *BKE_gpencil_material_add(struct Main *bmain, const char *name); void BKE_gpencil_material_attr_init(struct Material *ma); @@ -105,6 +108,12 @@ struct Material *BKE_id_material_pop(struct Main *bmain, /* index is an int because of RNA. */ int index); void BKE_id_material_clear(struct Main *bmain, struct ID *id); + +/* eval api */ +struct Material *BKE_object_material_get_eval(struct Object *ob, short act); +int BKE_object_material_count_eval(struct Object *ob); +void BKE_id_material_eval_assign(struct ID *id, int slot, struct Material *material); + /* rendering */ void ramp_blend(int type, float r_col[3], const float fac, const float col[3]); diff --git a/source/blender/blenkernel/BKE_mesh.h b/source/blender/blenkernel/BKE_mesh.h index fae0ee4f81d..62837c4f2a7 100644 --- a/source/blender/blenkernel/BKE_mesh.h +++ b/source/blender/blenkernel/BKE_mesh.h @@ -215,7 +215,8 @@ void BKE_mesh_split_faces(struct Mesh *mesh, bool free_loop_normals); * ignored otherwise. */ struct Mesh *BKE_mesh_new_from_object(struct Depsgraph *depsgraph, struct Object *object, - bool preserve_all_data_layers); + const bool preserve_all_data_layers, + const bool preserve_origindex); /* This is a version of BKE_mesh_new_from_object() which stores mesh in the given main database. * However, that function enforces object type to be a geometry one, and ensures a mesh is always diff --git a/source/blender/blenkernel/BKE_modifier.h b/source/blender/blenkernel/BKE_modifier.h index c0153dd8241..48b4540e3d9 100644 --- a/source/blender/blenkernel/BKE_modifier.h +++ b/source/blender/blenkernel/BKE_modifier.h @@ -446,8 +446,8 @@ bool BKE_modifier_is_preview(struct ModifierData *md); void BKE_modifiers_foreach_ID_link(struct Object *ob, IDWalkFunc walk, void *userData); void BKE_modifiers_foreach_tex_link(struct Object *ob, TexWalkFunc walk, void *userData); -struct ModifierData *BKE_modifiers_findby_type(struct Object *ob, ModifierType type); -struct ModifierData *BKE_modifiers_findby_name(struct Object *ob, const char *name); +struct ModifierData *BKE_modifiers_findby_type(const struct Object *ob, ModifierType type); +struct ModifierData *BKE_modifiers_findby_name(const struct Object *ob, const char *name); void BKE_modifiers_clear_errors(struct Object *ob); int BKE_modifiers_get_cage_index(const struct Scene *scene, struct Object *ob, diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h index bcab456b36e..a08a2edd5f6 100644 --- a/source/blender/blenkernel/BKE_node.h +++ b/source/blender/blenkernel/BKE_node.h @@ -325,6 +325,7 @@ typedef struct bNodeType { /* Execute a geometry node. */ NodeGeometryExecFunction geometry_node_execute; + bool geometry_node_execute_supports_lazyness; /* RNA integration */ ExtensionRNA rna_ext; @@ -1419,6 +1420,11 @@ int ntreeTexExecTree(struct bNodeTree *ntree, #define GEO_NODE_SWITCH 1043 #define GEO_NODE_ATTRIBUTE_TRANSFER 1044 #define GEO_NODE_CURVE_TO_MESH 1045 +#define GEO_NODE_ATTRIBUTE_CURVE_MAP 1046 +#define GEO_NODE_CURVE_RESAMPLE 1047 +#define GEO_NODE_ATTRIBUTE_VECTOR_ROTATE 1048 +#define GEO_NODE_MATERIAL_ASSIGN 1049 +#define GEO_NODE_INPUT_MATERIAL 1050 /** \} */ diff --git a/source/blender/blenkernel/BKE_node_ui_storage.hh b/source/blender/blenkernel/BKE_node_ui_storage.hh index 5f9c039ef9e..8bf89cd8f58 100644 --- a/source/blender/blenkernel/BKE_node_ui_storage.hh +++ b/source/blender/blenkernel/BKE_node_ui_storage.hh @@ -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 { diff --git a/source/blender/blenkernel/BKE_paint.h b/source/blender/blenkernel/BKE_paint.h index 228b52123f3..73413b61456 100644 --- a/source/blender/blenkernel/BKE_paint.h +++ b/source/blender/blenkernel/BKE_paint.h @@ -486,7 +486,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 */ diff --git a/source/blender/blenkernel/BKE_persistent_data_handle.hh b/source/blender/blenkernel/BKE_persistent_data_handle.hh deleted file mode 100644 index bbee09c7bf4..00000000000 --- a/source/blender/blenkernel/BKE_persistent_data_handle.hh +++ /dev/null @@ -1,153 +0,0 @@ -/* - * 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 - -/** \file - * \ingroup bke - * - * A PersistentDataHandle is a weak reference to some data in a Blender file. The handle itself is - * just a number. A PersistentDataHandleMap is used to convert between handles and the actual data. - */ - -#include "BLI_map.hh" - -#include "DNA_ID.h" - -struct Collection; -struct Object; - -namespace blender::bke { - -class PersistentDataHandleMap; - -class PersistentDataHandle { - private: - /* Negative values indicate that the handle is "empty". */ - int32_t handle_; - - friend PersistentDataHandleMap; - - protected: - PersistentDataHandle(int handle) : handle_(handle) - { - } - - public: - PersistentDataHandle() : handle_(-1) - { - } - - friend bool operator==(const PersistentDataHandle &a, const PersistentDataHandle &b) - { - return a.handle_ == b.handle_; - } - - friend bool operator!=(const PersistentDataHandle &a, const PersistentDataHandle &b) - { - return !(a == b); - } - - friend std::ostream &operator<<(std::ostream &stream, const PersistentDataHandle &a) - { - stream << a.handle_; - return stream; - } - - uint64_t hash() const - { - return static_cast<uint64_t>(handle_); - } -}; - -class PersistentIDHandle : public PersistentDataHandle { - friend PersistentDataHandleMap; - using PersistentDataHandle::PersistentDataHandle; -}; - -class PersistentObjectHandle : public PersistentIDHandle { - friend PersistentDataHandleMap; - using PersistentIDHandle::PersistentIDHandle; -}; - -class PersistentCollectionHandle : public PersistentIDHandle { - friend PersistentDataHandleMap; - using PersistentIDHandle::PersistentIDHandle; -}; - -class PersistentDataHandleMap { - private: - Map<int32_t, ID *> id_by_handle_; - Map<ID *, int32_t> handle_by_id_; - - public: - void add(int32_t handle, ID &id) - { - BLI_assert(handle >= 0); - handle_by_id_.add(&id, handle); - id_by_handle_.add(handle, &id); - } - - PersistentIDHandle lookup(ID *id) const - { - const int handle = handle_by_id_.lookup_default(id, -1); - return PersistentIDHandle(handle); - } - - PersistentObjectHandle lookup(Object *object) const - { - const int handle = handle_by_id_.lookup_default((ID *)object, -1); - return PersistentObjectHandle(handle); - } - - PersistentCollectionHandle lookup(Collection *collection) const - { - const int handle = handle_by_id_.lookup_default((ID *)collection, -1); - return PersistentCollectionHandle(handle); - } - - ID *lookup(const PersistentIDHandle &handle) const - { - ID *id = id_by_handle_.lookup_default(handle.handle_, nullptr); - return id; - } - - Object *lookup(const PersistentObjectHandle &handle) const - { - ID *id = this->lookup((const PersistentIDHandle &)handle); - if (id == nullptr) { - return nullptr; - } - if (GS(id->name) != ID_OB) { - return nullptr; - } - return (Object *)id; - } - - Collection *lookup(const PersistentCollectionHandle &handle) const - { - ID *id = this->lookup((const PersistentIDHandle &)handle); - if (id == nullptr) { - return nullptr; - } - if (GS(id->name) != ID_GR) { - return nullptr; - } - return (Collection *)id; - } -}; - -} // namespace blender::bke diff --git a/source/blender/blenkernel/BKE_softbody.h b/source/blender/blenkernel/BKE_softbody.h index 4f8b21141b6..58dc90f62dc 100644 --- a/source/blender/blenkernel/BKE_softbody.h +++ b/source/blender/blenkernel/BKE_softbody.h @@ -47,7 +47,7 @@ typedef struct BodyPoint { } BodyPoint; /* allocates and initializes general main data */ -extern struct SoftBody *sbNew(struct Scene *scene); +extern struct SoftBody *sbNew(void); /* frees internal data and soft-body itself */ extern void sbFree(struct Object *ob); diff --git a/source/blender/blenkernel/BKE_spline.hh b/source/blender/blenkernel/BKE_spline.hh index 098abf6de65..9e5552082af 100644 --- a/source/blender/blenkernel/BKE_spline.hh +++ b/source/blender/blenkernel/BKE_spline.hh @@ -28,6 +28,7 @@ #include "BLI_float4x4.hh" #include "BLI_vector.hh" +#include "BKE_attribute_access.hh" #include "BKE_attribute_math.hh" struct Curve; @@ -48,10 +49,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 +67,6 @@ class Spline { Poly, }; - protected: - Type type_; - bool is_cyclic_ = false; - - public: enum NormalCalculationMode { ZUp, Minimum, @@ -77,7 +75,12 @@ class Spline { /* Only #Zup is supported at the moment. */ NormalCalculationMode normal_mode; + blender::bke::CustomDataAttributes attributes; + 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 +102,10 @@ class Spline { { } Spline(Spline &other) - : type_(other.type_), is_cyclic_(other.is_cyclic_), normal_mode(other.normal_mode) + : normal_mode(other.normal_mode), + attributes(other.attributes), + type_(other.type_), + is_cyclic_(other.is_cyclic_) { } @@ -113,6 +119,7 @@ class Spline { bool is_cyclic() const; void set_cyclic(const bool value); + virtual void resize(const int size) = 0; virtual blender::MutableSpan<blender::float3> positions() = 0; virtual blender::Span<blender::float3> positions() const = 0; virtual blender::MutableSpan<float> radii() = 0; @@ -163,6 +170,9 @@ class Spline { LookupResult lookup_evaluated_factor(const float factor) const; LookupResult lookup_evaluated_length(const float length) const; + blender::Array<float> sample_uniform_index_factors(const int samples_size) const; + LookupResult lookup_data_from_index_factor(const float index_factor) const; + /** * Interpolate a virtual array of data with the size of the number of control points to the * evaluated points. For poly splines, the lifetime of the returned virtual array must not @@ -194,15 +204,21 @@ class BezierSpline final : public Spline { }; private: - blender::Vector<HandleType> handle_types_left_; - blender::Vector<blender::float3> handle_positions_left_; blender::Vector<blender::float3> positions_; - blender::Vector<HandleType> handle_types_right_; - blender::Vector<blender::float3> handle_positions_right_; blender::Vector<float> radii_; blender::Vector<float> tilts_; int resolution_; + blender::Vector<HandleType> handle_types_left_; + blender::Vector<HandleType> handle_types_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_; mutable std::mutex offset_cache_mutex_; @@ -225,14 +241,14 @@ class BezierSpline final : public Spline { } BezierSpline(const BezierSpline &other) : Spline((Spline &)other), - handle_types_left_(other.handle_types_left_), - handle_positions_left_(other.handle_positions_left_), positions_(other.positions_), - handle_types_right_(other.handle_types_right_), - handle_positions_right_(other.handle_positions_right_), radii_(other.radii_), tilts_(other.tilts_), - resolution_(other.resolution_) + resolution_(other.resolution_), + handle_types_left_(other.handle_types_left_), + handle_types_right_(other.handle_types_right_), + handle_positions_left_(other.handle_positions_left_), + handle_positions_right_(other.handle_positions_right_) { } @@ -248,6 +264,7 @@ class BezierSpline final : public Spline { const float radius, const float tilt); + void resize(const int size) final; blender::MutableSpan<blender::float3> positions() final; blender::Span<blender::float3> positions() const final; blender::MutableSpan<float> radii() final; @@ -279,22 +296,22 @@ 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; }; InterpolationData interpolation_data_from_index_factor(const float index_factor) const; virtual blender::fn::GVArrayPtr interpolate_to_evaluated_points( - const blender::fn::GVArray &source_data) const; + 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, const int next_index, blender::MutableSpan<blender::float3> positions) const; - blender::Array<int> evaluated_point_offsets() const; }; /** @@ -310,6 +327,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 { @@ -387,6 +406,7 @@ class NURBSpline final : public Spline { bool check_valid_size_and_order() const; int knots_size() const; + void resize(const int size) final; blender::MutableSpan<blender::float3> positions() final; blender::Span<blender::float3> positions() const final; blender::MutableSpan<float> radii() final; @@ -418,12 +438,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) @@ -441,6 +459,7 @@ class PolySpline final : public Spline { void add_point(const blender::float3 position, const float radius, const float tilt); + void resize(const int size) final; blender::MutableSpan<blender::float3> positions() final; blender::Span<blender::float3> positions() const final; blender::MutableSpan<float> radii() final; @@ -465,14 +484,34 @@ 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::bke::CustomDataAttributes attributes; + + CurveEval() = default; + CurveEval(const CurveEval &other) : attributes(other.attributes) + { + for (const SplinePtr &spline : other.splines()) { + this->add_spline(spline->copy()); + } + } - CurveEval *copy(); + blender::Span<SplinePtr> splines() const; + blender::MutableSpan<SplinePtr> splines(); + + void add_spline(SplinePtr spline); + void remove_splines(blender::IndexMask mask); 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; + + void assert_valid_point_attributes() const; }; std::unique_ptr<CurveEval> curve_eval_from_dna_curve(const Curve &curve); diff --git a/source/blender/blenkernel/CMakeLists.txt b/source/blender/blenkernel/CMakeLists.txt index 0944bbed2f3..021d7e15814 100644 --- a/source/blender/blenkernel/CMakeLists.txt +++ b/source/blender/blenkernel/CMakeLists.txt @@ -112,7 +112,7 @@ set(SRC intern/curve_convert.c intern/curve_decimate.c intern/curve_deform.c - intern/curve_eval.cc + intern/curve_eval.cc intern/curveprofile.c intern/customdata.c intern/customdata_file.c @@ -406,7 +406,6 @@ set(SRC BKE_paint.h BKE_particle.h BKE_pbvh.h - BKE_persistent_data_handle.hh BKE_pointcache.h BKE_pointcloud.h BKE_preferences.h @@ -592,10 +591,6 @@ if(WITH_CODEC_FFMPEG) ${FFMPEG_LIBRARIES} ) add_definitions(-DWITH_FFMPEG) - - remove_strict_c_flags_file( - intern/writeffmpeg.c - ) endif() if(WITH_PYTHON) @@ -774,6 +769,7 @@ if(WITH_GTESTS) intern/fcurve_test.cc intern/lattice_deform_test.cc intern/layer_test.cc + intern/lib_id_test.cc intern/tracking_test.cc ) set(TEST_INC diff --git a/source/blender/blenkernel/intern/DerivedMesh.cc b/source/blender/blenkernel/intern/DerivedMesh.cc index c63447c6cdd..b40d859754e 100644 --- a/source/blender/blenkernel/intern/DerivedMesh.cc +++ b/source/blender/blenkernel/intern/DerivedMesh.cc @@ -713,11 +713,13 @@ static float (*get_orco_coords(Object *ob, BMEditMesh *em, int layer, int *free) if (!em) { ClothModifierData *clmd = (ClothModifierData *)BKE_modifiers_findby_type( ob, eModifierType_Cloth); - KeyBlock *kb = BKE_keyblock_from_key(BKE_key_from_object(ob), - clmd->sim_parms->shapekey_rest); + if (clmd) { + KeyBlock *kb = BKE_keyblock_from_key(BKE_key_from_object(ob), + clmd->sim_parms->shapekey_rest); - if (kb && kb->data) { - return (float(*)[3])kb->data; + if (kb && kb->data) { + return (float(*)[3])kb->data; + } } } diff --git a/source/blender/blenkernel/intern/action.c b/source/blender/blenkernel/intern/action.c index 3f3aa0386e2..a7e36b09516 100644 --- a/source/blender/blenkernel/intern/action.c +++ b/source/blender/blenkernel/intern/action.c @@ -656,7 +656,9 @@ bPoseChannel *BKE_pose_channel_ensure(bPose *pose, const char *name) BLI_strncpy(chan->name, name, sizeof(chan->name)); - chan->custom_scale = 1.0f; + copy_v3_fl(chan->custom_scale_xyz, 1.0f); + zero_v3(chan->custom_translation); + zero_v3(chan->custom_rotation_euler); /* init vars to prevent math errors */ unit_qt(chan->quat); @@ -1235,8 +1237,10 @@ void BKE_pose_channel_copy_data(bPoseChannel *pchan, const bPoseChannel *pchan_f if (pchan->custom) { id_us_plus(&pchan->custom->id); } + copy_v3_v3(pchan->custom_scale_xyz, pchan_from->custom_scale_xyz); + copy_v3_v3(pchan->custom_translation, pchan_from->custom_translation); + copy_v3_v3(pchan->custom_rotation_euler, pchan_from->custom_rotation_euler); - pchan->custom_scale = pchan_from->custom_scale; pchan->drawflag = pchan_from->drawflag; } diff --git a/source/blender/blenkernel/intern/anim_data.c b/source/blender/blenkernel/intern/anim_data.c index 68de3e93a8e..44b760aefc8 100644 --- a/source/blender/blenkernel/intern/anim_data.c +++ b/source/blender/blenkernel/intern/anim_data.c @@ -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); } } diff --git a/source/blender/blenkernel/intern/anim_sys.c b/source/blender/blenkernel/intern/anim_sys.c index 6f4af6f655d..e347306e0ae 100644 --- a/source/blender/blenkernel/intern/anim_sys.c +++ b/source/blender/blenkernel/intern/anim_sys.c @@ -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); } @@ -1650,6 +1652,363 @@ static bool nla_combine_quaternion_get_inverted_strip_values(const float lower_v } /* ---------------------- */ + +/* 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; - } - } - } + successful_remap = 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; - } - } + + 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]; } - return true; + nlaeval_snapshot_free_data(&blended_snapshot); + + return successful_remap; } /** diff --git a/source/blender/blenkernel/intern/armature.c b/source/blender/blenkernel/intern/armature.c index 80992cff34d..f67c2cb4372 100644 --- a/source/blender/blenkernel/intern/armature.c +++ b/source/blender/blenkernel/intern/armature.c @@ -2881,7 +2881,8 @@ bool BKE_pose_minmax(Object *ob, float r_min[3], float r_max[3], bool use_hidden NULL; if (bb_custom) { float mat[4][4], smat[4][4]; - scale_m4_fl(smat, PCHAN_CUSTOM_DRAW_SIZE(pchan)); + scale_m4_fl(smat, PCHAN_CUSTOM_BONE_LENGTH(pchan)); + mul_m4_v3(smat, pchan->custom_scale_xyz); mul_m4_series(mat, ob->obmat, pchan_tx->pose_mat, smat); BKE_boundbox_minmax(bb_custom, mat, r_min, r_max); } diff --git a/source/blender/blenkernel/intern/armature_update.c b/source/blender/blenkernel/intern/armature_update.c index 94c2755d4c6..4504f10967c 100644 --- a/source/blender/blenkernel/intern/armature_update.c +++ b/source/blender/blenkernel/intern/armature_update.c @@ -340,13 +340,22 @@ static int position_tail_on_spline(bSplineIKConstraint *ik_data, float isect_1[3], isect_2[3]; /* Calculate the intersection point. */ - isect_line_sphere_v3(prev_bp->vec, bp->vec, head_pos, sphere_radius, isect_1, isect_2); + int ret = isect_line_sphere_v3(prev_bp->vec, bp->vec, head_pos, sphere_radius, isect_1, isect_2); - /* Because of how `isect_line_sphere_v3` works, we know that `isect_1` contains the - * intersection point we want. And it will always intersect as we go from inside to outside - * of the sphere. - */ - copy_v3_v3(r_tail_pos, isect_1); + if (ret > 0) { + /* Because of how `isect_line_sphere_v3` works, we know that `isect_1` contains the + * intersection point we want. And it will always intersect as we go from inside to outside + * of the sphere. + */ + copy_v3_v3(r_tail_pos, isect_1); + } + else { + /* Couldn't find an intersection point. This means that the floating point + * values are too small and thus the intersection check fails. + * So assume that the distance is so small that tail_pos == head_pos. + */ + copy_v3_v3(r_tail_pos, head_pos); + } cur_seg_idx = bp_idx - 2; float prev_seg_len = 0; @@ -360,7 +369,7 @@ static int position_tail_on_spline(bSplineIKConstraint *ik_data, } /* Convert the point back into the 0-1 interpolation range. */ - const float isect_seg_len = len_v3v3(prev_bp->vec, isect_1); + const float isect_seg_len = len_v3v3(prev_bp->vec, r_tail_pos); const float frac = isect_seg_len / len_v3v3(prev_bp->vec, bp->vec); *r_new_curve_pos = (prev_seg_len + isect_seg_len) / spline_len; @@ -380,7 +389,7 @@ static void splineik_evaluate_bone( { bSplineIKConstraint *ik_data = tree->ik_data; - if (pchan->bone->length == 0.0f) { + if (pchan->bone->length < FLT_EPSILON) { /* Only move the bone position with zero length bones. */ float bone_pos[4], dir[3], rad; BKE_where_on_path(ik_data->tar, state->curve_position, bone_pos, dir, NULL, &rad, NULL); @@ -516,6 +525,25 @@ static void splineik_evaluate_bone( */ cross_v3_v3v3(raxis, rmat[1], spline_vec); + /* Check if the old and new bone direction is parallel to each other. + * If they are, then 'raxis' should be near zero and we will have to get the rotation axis in + * some other way. + */ + float norm = normalize_v3(raxis); + + if (norm < FLT_EPSILON) { + /* Can't use cross product! */ + int order[3] = {0, 1, 2}; + float tmp_axis[3]; + zero_v3(tmp_axis); + + axis_sort_v3(spline_vec, order); + + /* Use the second largest axis as the basis for the rotation axis. */ + tmp_axis[order[1]] = 1.0f; + cross_v3_v3v3(raxis, tmp_axis, spline_vec); + } + rangle = dot_v3v3(rmat[1], spline_vec); CLAMP(rangle, -1.0f, 1.0f); rangle = acosf(rangle); diff --git a/source/blender/blenkernel/intern/attribute_access.cc b/source/blender/blenkernel/intern/attribute_access.cc index c24630c5666..62833e10438 100644 --- a/source/blender/blenkernel/intern/attribute_access.cc +++ b/source/blender/blenkernel/intern/attribute_access.cc @@ -45,6 +45,7 @@ using blender::Set; using blender::StringRef; using blender::StringRefNull; using blender::fn::GMutableSpan; +using blender::fn::GSpan; namespace blender::bke { @@ -590,6 +591,105 @@ void NamedLegacyCustomDataProvider::foreach_domain( callback(domain_); } +CustomDataAttributes::CustomDataAttributes() +{ + CustomData_reset(&data); + size_ = 0; +} + +CustomDataAttributes::~CustomDataAttributes() +{ + CustomData_free(&data, size_); +} + +CustomDataAttributes::CustomDataAttributes(const CustomDataAttributes &other) +{ + size_ = other.size_; + CustomData_copy(&other.data, &data, CD_MASK_ALL, CD_DUPLICATE, size_); +} + +CustomDataAttributes::CustomDataAttributes(CustomDataAttributes &&other) +{ + size_ = other.size_; + data = other.data; + CustomData_reset(&other.data); +} + +std::optional<GSpan> CustomDataAttributes::get_for_read(const StringRef name) const +{ + BLI_assert(size_ != 0); + for (const CustomDataLayer &layer : Span(data.layers, data.totlayer)) { + if (layer.name == name) { + const CPPType *cpp_type = custom_data_type_to_cpp_type((CustomDataType)layer.type); + BLI_assert(cpp_type != nullptr); + return GSpan(*cpp_type, layer.data, size_); + } + } + return {}; +} + +std::optional<GMutableSpan> CustomDataAttributes::get_for_write(const StringRef name) +{ + BLI_assert(size_ != 0); + for (CustomDataLayer &layer : MutableSpan(data.layers, data.totlayer)) { + if (layer.name == name) { + const CPPType *cpp_type = custom_data_type_to_cpp_type((CustomDataType)layer.type); + BLI_assert(cpp_type != nullptr); + return GMutableSpan(*cpp_type, layer.data, size_); + } + } + return {}; +} + +bool CustomDataAttributes::create(const StringRef name, const CustomDataType data_type) +{ + char name_c[MAX_NAME]; + name.copy(name_c); + void *result = CustomData_add_layer_named(&data, data_type, CD_DEFAULT, nullptr, size_, name_c); + return result != nullptr; +} + +bool CustomDataAttributes::create_by_move(const blender::StringRef name, + const CustomDataType data_type, + void *buffer) +{ + char name_c[MAX_NAME]; + name.copy(name_c); + void *result = CustomData_add_layer_named(&data, data_type, CD_ASSIGN, buffer, size_, name_c); + return result != nullptr; +} + +bool CustomDataAttributes::remove(const blender::StringRef name) +{ + bool result = false; + for (const int i : IndexRange(data.totlayer)) { + const CustomDataLayer &layer = data.layers[i]; + if (layer.name == name) { + CustomData_free_layer(&data, layer.type, size_, i); + result = true; + } + } + return result; +} + +void CustomDataAttributes::reallocate(const int size) +{ + size_ = size; + CustomData_realloc(&data, size); +} + +bool CustomDataAttributes::foreach_attribute(const AttributeForeachCallback callback, + const AttributeDomain domain) const +{ + for (const CustomDataLayer &layer : Span(data.layers, data.totlayer)) { + AttributeMetaData meta_data{domain, (CustomDataType)layer.type}; + if (!callback(layer.name, meta_data)) { + return false; + } + } + return true; +} + } // namespace blender::bke /* -------------------------------------------------------------------- */ diff --git a/source/blender/blenkernel/intern/blendfile.c b/source/blender/blenkernel/intern/blendfile.c index a61e7a2d1d8..54fd3f55c31 100644 --- a/source/blender/blenkernel/intern/blendfile.c +++ b/source/blender/blenkernel/intern/blendfile.c @@ -399,7 +399,8 @@ static void setup_app_data(bContext *C, BKE_lib_override_library_main_resync( bmain, curscene, - bfd->cur_view_layer ? bfd->cur_view_layer : BKE_view_layer_default_view(curscene)); + bfd->cur_view_layer ? bfd->cur_view_layer : BKE_view_layer_default_view(curscene), + reports); /* We need to rebuild some of the deleted override rules (for UI feedback purpose). */ BKE_lib_override_library_main_operations_create(bmain, true); } diff --git a/source/blender/blenkernel/intern/curve_eval.cc b/source/blender/blenkernel/intern/curve_eval.cc index 4c63c7f05ee..9cafe1124b1 100644 --- a/source/blender/blenkernel/intern/curve_eval.cc +++ b/source/blender/blenkernel/intern/curve_eval.cc @@ -16,31 +16,50 @@ #include "BLI_array.hh" #include "BLI_listbase.h" +#include "BLI_map.hh" #include "BLI_span.hh" +#include "BLI_string_ref.hh" #include "DNA_curve_types.h" #include "BKE_curve.h" #include "BKE_spline.hh" +using blender::Array; using blender::float3; using blender::float4x4; +using blender::Map; using blender::Span; +using blender::StringRefNull; -CurveEval *CurveEval::copy() +blender::Span<SplinePtr> CurveEval::splines() const { - CurveEval *new_curve = new CurveEval(); + return splines_; +} - for (SplinePtr &spline : this->splines) { - new_curve->splines.append(spline->copy()); - } +blender::MutableSpan<SplinePtr> CurveEval::splines() +{ + return splines_; +} - return new_curve; +/** + * \warning Call #reallocate on the spline's attributes after adding all 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]); + } } 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 +67,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 +168,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) { @@ -134,8 +185,8 @@ std::unique_ptr<CurveEval> curve_eval_from_dna_curve(const Curve &dna_curve) bezt.radius, bezt.tilt); } - - curve->splines.append(std::move(spline)); + spline->attributes.reallocate(spline->size()); + curve->add_spline(std::move(spline)); break; } case CU_NURBS: { @@ -148,8 +199,8 @@ std::unique_ptr<CurveEval> curve_eval_from_dna_curve(const Curve &dna_curve) for (const BPoint &bp : Span(nurb->bp, nurb->pntsu)) { spline->add_point(bp.vec, bp.radius, bp.tilt, bp.vec[3]); } - - curve->splines.append(std::move(spline)); + spline->attributes.reallocate(spline->size()); + curve->add_spline(std::move(spline)); break; } case CU_POLY: { @@ -159,8 +210,8 @@ std::unique_ptr<CurveEval> curve_eval_from_dna_curve(const Curve &dna_curve) for (const BPoint &bp : Span(nurb->bp, nurb->pntsu)) { spline->add_point(bp.vec, bp.radius, bp.tilt); } - - curve->splines.append(std::move(spline)); + spline->attributes.reallocate(spline->size()); + curve->add_spline(std::move(spline)); break; } default: { @@ -170,15 +221,52 @@ std::unique_ptr<CurveEval> curve_eval_from_dna_curve(const Curve &dna_curve) } } + /* Though the curve has no attributes, this is necessary to properly set the custom data size. */ + curve->attributes.reallocate(curve->splines().size()); + /* Note: Normal mode is stored separately in each spline to facilitate combining splines * 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; } -/** \} */ +/** + * Check the invariants that curve control point attributes should always uphold, necessary + * because attributes are stored on splines rather than in a flat array on the curve: + * - The same set of attributes exists on every spline. + * - Attributes with the same name have the same type on every spline. + */ +void CurveEval::assert_valid_point_attributes() const +{ +#ifdef DEBUG + if (splines_.size() == 0) { + return; + } + const int layer_len = splines_.first()->attributes.data.totlayer; + Map<StringRefNull, AttributeMetaData> map; + for (const SplinePtr &spline : splines_) { + BLI_assert(spline->attributes.data.totlayer == layer_len); + spline->attributes.foreach_attribute( + [&](StringRefNull name, const AttributeMetaData &meta_data) { + map.add_or_modify( + name, + [&](AttributeMetaData *map_data) { + /* All unique attribute names should be added on the first spline. */ + BLI_assert(spline == splines_.first()); + *map_data = meta_data; + }, + [&](AttributeMetaData *map_data) { + /* Attributes on different splines should all have the same type. */ + BLI_assert(meta_data == *map_data); + }); + return true; + }, + ATTR_DOMAIN_POINT); + } +#endif +}
\ No newline at end of file diff --git a/source/blender/blenkernel/intern/dynamicpaint.c b/source/blender/blenkernel/intern/dynamicpaint.c index 4a25b0e9d98..42af3a391ed 100644 --- a/source/blender/blenkernel/intern/dynamicpaint.c +++ b/source/blender/blenkernel/intern/dynamicpaint.c @@ -1702,7 +1702,7 @@ static void dynamicPaint_setInitialColor(const Scene *scene, DynamicPaintSurface } for (int i = 0; i < totloop; i++) { - rgba_uchar_to_float(pPoint[mloop[i].v].color, (const unsigned char *)&col[mloop[i].v].r); + rgba_uchar_to_float(pPoint[mloop[i].v].color, (const unsigned char *)&col[i].r); } } else if (surface->format == MOD_DPAINT_SURFACE_F_IMAGESEQ) { diff --git a/source/blender/blenkernel/intern/geometry_component_curve.cc b/source/blender/blenkernel/intern/geometry_component_curve.cc index 0490d577b88..d08681da6ec 100644 --- a/source/blender/blenkernel/intern/geometry_component_curve.cc +++ b/source/blender/blenkernel/intern/geometry_component_curve.cc @@ -22,6 +22,12 @@ #include "attribute_access_intern.hh" +using blender::fn::GMutableSpan; +using blender::fn::GSpan; +using blender::fn::GVArray_For_GSpan; +using blender::fn::GVArray_GSpan; +using blender::fn::GVMutableArray_For_GMutableSpan; + /* -------------------------------------------------------------------- */ /** \name Geometry Component Implementation * \{ */ @@ -39,7 +45,7 @@ GeometryComponent *CurveComponent::copy() const { CurveComponent *new_component = new CurveComponent(); if (curve_ != nullptr) { - new_component->curve_ = curve_->copy(); + new_component->curve_ = new CurveEval(*curve_); new_component->ownership_ = GeometryOwnershipType::Owned; } return new_component; @@ -87,7 +93,7 @@ CurveEval *CurveComponent::get_for_write() { BLI_assert(this->is_mutable()); if (ownership_ == GeometryOwnershipType::ReadOnly) { - curve_ = curve_->copy(); + curve_ = new CurveEval(*curve_); ownership_ = GeometryOwnershipType::Owned; } return curve_; @@ -107,7 +113,7 @@ void CurveComponent::ensure_owns_direct_data() { BLI_assert(this->is_mutable()); if (ownership_ != GeometryOwnershipType::Owned) { - curve_ = curve_->copy(); + curve_ = new CurveEval(*curve_); ownership_ = GeometryOwnershipType::Owned; } } @@ -115,7 +121,7 @@ void CurveComponent::ensure_owns_direct_data() /** \} */ /* -------------------------------------------------------------------- */ -/** \name Attribute Access +/** \name Attribute Access Helper Functions * \{ */ int CurveComponent::attribute_domain_size(const AttributeDomain domain) const @@ -125,23 +131,44 @@ 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; } +static CurveEval *get_curve_from_component_for_write(GeometryComponent &component) +{ + BLI_assert(component.type() == GEO_COMPONENT_TYPE_CURVE); + CurveComponent &curve_component = static_cast<CurveComponent &>(component); + return curve_component.get_for_write(); +} + +static const CurveEval *get_curve_from_component_for_read(const GeometryComponent &component) +{ + BLI_assert(component.type() == GEO_COMPONENT_TYPE_CURVE); + const CurveComponent &curve_component = static_cast<const CurveComponent &>(component); + return curve_component.get_for_read(); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Builtin Spline Attributes + * + * Attributes with a value for every spline, stored contiguously or in every spline separately. + * \{ */ + namespace blender::bke { class BuiltinSplineAttributeProvider final : public BuiltinAttributeProvider { using AsReadAttribute = GVArrayPtr (*)(const CurveEval &data); using AsWriteAttribute = GVMutableArrayPtr (*)(CurveEval &data); - using UpdateOnWrite = void (*)(Spline &spline); const AsReadAttribute as_read_attribute_; const AsWriteAttribute as_write_attribute_; @@ -164,12 +191,10 @@ class BuiltinSplineAttributeProvider final : public BuiltinAttributeProvider { GVArrayPtr try_get_for_read(const GeometryComponent &component) const final { - const CurveComponent &curve_component = static_cast<const CurveComponent &>(component); - const CurveEval *curve = curve_component.get_for_read(); + const CurveEval *curve = get_curve_from_component_for_read(component); if (curve == nullptr) { return {}; } - return as_read_attribute_(*curve); } @@ -178,12 +203,10 @@ class BuiltinSplineAttributeProvider final : public BuiltinAttributeProvider { if (writable_ != Writable) { return {}; } - CurveComponent &curve_component = static_cast<CurveComponent &>(component); - CurveEval *curve = curve_component.get_for_write(); + CurveEval *curve = get_curve_from_component_for_write(component); if (curve == nullptr) { return {}; } - return as_write_attribute_(*curve); } @@ -228,7 +251,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) @@ -237,7 +260,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) @@ -256,19 +279,683 @@ 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); + } +}; + +template<typename T> GVArrayPtr point_data_gvarray(Array<Span<T>> spans, Array<int> offsets) +{ + return std::make_unique<fn::GVArray_For_EmbeddedVArray<T, VArray_For_SplinePoints<T>>>( + offsets.last(), std::move(spans), std::move(offsets)); +} + +template<typename T> +GVMutableArrayPtr point_data_gvarray(Array<MutableSpan<T>> spans, Array<int> offsets) +{ + return std::make_unique< + fn::GVMutableArray_For_EmbeddedVMutableArray<T, VMutableArray_For_SplinePoints<T>>>( + offsets.last(), std::move(spans), std::move(offsets)); +} + +/** + * 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 point_data_gvarray(spans, 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 point_data_gvarray(spans, 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 checking 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 Dynamic Control Point Attributes + * + * The dynamic control point attribute implementation is very similar to the builtin attribute + * implementation-- it uses the same virtual array types. In order to work, this code depends on + * the fact that all a curve's splines will have the same attributes and they all have the same + * type. + * \{ */ + +class DynamicPointAttributeProvider final : public DynamicAttributesProvider { + private: + static constexpr uint64_t supported_types_mask = CD_MASK_PROP_FLOAT | CD_MASK_PROP_FLOAT2 | + CD_MASK_PROP_FLOAT3 | CD_MASK_PROP_INT32 | + CD_MASK_PROP_COLOR | CD_MASK_PROP_BOOL; + + public: + ReadAttributeLookup try_get_for_read(const GeometryComponent &component, + const StringRef attribute_name) const final + { + const CurveEval *curve = get_curve_from_component_for_read(component); + if (curve == nullptr || curve->splines().size() == 0) { + return {}; + } + + Span<SplinePtr> splines = curve->splines(); + Vector<GSpan> spans; /* GSpan has no default constructor. */ + spans.reserve(splines.size()); + std::optional<GSpan> first_span = splines[0]->attributes.get_for_read(attribute_name); + if (!first_span) { + return {}; + } + spans.append(*first_span); + for (const int i : IndexRange(1, splines.size() - 1)) { + std::optional<GSpan> span = splines[i]->attributes.get_for_read(attribute_name); + if (!span) { + /* All splines should have the same set of data layers. It would be possible to recover + * here and return partial data instead, but that would add a lot of complexity for a + * situation we don't even expect to encounter. */ + BLI_assert_unreachable(); + return {}; + } + if (span->type() != spans.last().type()) { + /* Data layer types on separate splines do not match. */ + BLI_assert_unreachable(); + return {}; + } + spans.append(*span); + } + + /* First check for the simpler situation when we can return a simpler span virtual array. */ + if (spans.size() == 1) { + return {std::make_unique<GVArray_For_GSpan>(spans.first()), ATTR_DOMAIN_POINT}; + } + + ReadAttributeLookup attribute = {}; + Array<int> offsets = curve->control_point_offsets(); + attribute_math::convert_to_static_type(spans[0].type(), [&](auto dummy) { + using T = decltype(dummy); + Array<Span<T>> data(splines.size()); + for (const int i : splines.index_range()) { + data[i] = spans[i].typed<T>(); + BLI_assert(data[i].data() != nullptr); + } + attribute = {point_data_gvarray(data, offsets), ATTR_DOMAIN_POINT}; + }); + return attribute; + } + + /* This function is almost the same as #try_get_for_read, but without const. */ + WriteAttributeLookup try_get_for_write(GeometryComponent &component, + const StringRef attribute_name) const final + { + CurveEval *curve = get_curve_from_component_for_write(component); + if (curve == nullptr || curve->splines().size() == 0) { + return {}; + } + + MutableSpan<SplinePtr> splines = curve->splines(); + Vector<GMutableSpan> spans; /* GMutableSpan has no default constructor. */ + spans.reserve(splines.size()); + std::optional<GMutableSpan> first_span = splines[0]->attributes.get_for_write(attribute_name); + if (!first_span) { + return {}; + } + spans.append(*first_span); + for (const int i : IndexRange(1, splines.size() - 1)) { + std::optional<GMutableSpan> span = splines[i]->attributes.get_for_write(attribute_name); + if (!span) { + /* All splines should have the same set of data layers. It would be possible to recover + * here and return partial data instead, but that would add a lot of complexity for a + * situation we don't even expect to encounter. */ + BLI_assert_unreachable(); + return {}; + } + if (span->type() != spans.last().type()) { + /* Data layer types on separate splines do not match. */ + BLI_assert_unreachable(); + return {}; + } + spans.append(*span); + } + + /* First check for the simpler situation when we can return a simpler span virtual array. */ + if (spans.size() == 1) { + return {std::make_unique<GVMutableArray_For_GMutableSpan>(spans.first()), ATTR_DOMAIN_POINT}; + } + + WriteAttributeLookup attribute = {}; + Array<int> offsets = curve->control_point_offsets(); + attribute_math::convert_to_static_type(spans[0].type(), [&](auto dummy) { + using T = decltype(dummy); + Array<MutableSpan<T>> data(splines.size()); + for (const int i : splines.index_range()) { + data[i] = spans[i].typed<T>(); + BLI_assert(data[i].data() != nullptr); + } + attribute = {point_data_gvarray(data, offsets), ATTR_DOMAIN_POINT}; + }); + return attribute; + } + + bool try_delete(GeometryComponent &component, const StringRef attribute_name) const final + { + CurveEval *curve = get_curve_from_component_for_write(component); + if (curve == nullptr) { + return false; + } + + bool layer_freed = false; + for (SplinePtr &spline : curve->splines()) { + spline->attributes.remove(attribute_name); + } + return layer_freed; + } + + static GVArrayPtr varray_from_initializer(const AttributeInit &initializer, + const CustomDataType data_type, + const int total_size) + { + switch (initializer.type) { + case AttributeInit::Type::Default: + /* This function shouldn't be called in this case, since there + * is no need to copy anything to the new custom data array. */ + BLI_assert_unreachable(); + return {}; + case AttributeInit::Type::VArray: + return static_cast<const AttributeInitVArray &>(initializer).varray->shallow_copy(); + case AttributeInit::Type::MoveArray: + return std::make_unique<fn::GVArray_For_GSpan>( + GSpan(*bke::custom_data_type_to_cpp_type(data_type), + static_cast<const AttributeInitMove &>(initializer).data, + total_size)); + } + BLI_assert_unreachable(); + return {}; + } + + bool try_create(GeometryComponent &component, + const StringRef attribute_name, + const AttributeDomain domain, + const CustomDataType data_type, + const AttributeInit &initializer) const final + { + BLI_assert(this->type_is_supported(data_type)); + if (domain != ATTR_DOMAIN_POINT) { + return false; + } + CurveEval *curve = get_curve_from_component_for_write(component); + if (curve == nullptr || curve->splines().size() == 0) { + return false; + } + + MutableSpan<SplinePtr> splines = curve->splines(); + + /* First check the one case that allows us to avoid copying the input data. */ + if (splines.size() == 1 && initializer.type == AttributeInit::Type::MoveArray) { + void *source_data = static_cast<const AttributeInitMove &>(initializer).data; + if (!splines[0]->attributes.create_by_move(attribute_name, data_type, source_data)) { + MEM_freeN(source_data); + return false; + } + return true; + } + + /* Otherwise just create a custom data layer on each of the splines. */ + for (const int i : splines.index_range()) { + if (!splines[i]->attributes.create(attribute_name, data_type)) { + /* If attribute creation fails on one of the splines, we cannot leave the custom data + * layers in the previous splines around, so delete them before returning. However, + * this is not an expected case. */ + BLI_assert_unreachable(); + return false; + } + } + + /* With a default initializer type, we can keep the values at their initial values. */ + if (initializer.type == AttributeInit::Type::Default) { + return true; + } + + WriteAttributeLookup write_attribute = this->try_get_for_write(component, attribute_name); + /* We just created the attribute, it should exist. */ + BLI_assert(write_attribute); + + const int total_size = curve->control_point_offsets().last(); + GVArrayPtr source_varray = varray_from_initializer(initializer, data_type, total_size); + /* TODO: When we can call a variant of #set_all with a virtual array argument, + * this theoretically unnecessary materialize step could be removed. */ + GVArray_GSpan source_varray_span{*source_varray}; + write_attribute.varray->set_all(source_varray_span.data()); + + if (initializer.type == AttributeInit::Type::MoveArray) { + MEM_freeN(static_cast<const AttributeInitMove &>(initializer).data); + } + + return true; + } + + bool foreach_attribute(const GeometryComponent &component, + const AttributeForeachCallback callback) const final + { + const CurveEval *curve = get_curve_from_component_for_read(component); + if (curve == nullptr || curve->splines().size() == 0) { + return false; + } + + Span<SplinePtr> splines = curve->splines(); + + /* In a debug build, check that all corresponding custom data layers have the same type. */ + curve->assert_valid_point_attributes(); + + /* Use the first spline as a representative for all the others. */ + splines.first()->attributes.foreach_attribute(callback, ATTR_DOMAIN_POINT); + + return true; + } + + void foreach_domain(const FunctionRef<void(AttributeDomain)> callback) const final + { + callback(ATTR_DOMAIN_POINT); + } + + bool type_is_supported(CustomDataType data_type) const + { + return ((1ULL << data_type) & supported_types_mask) != 0; + } +}; + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \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() { @@ -284,7 +971,40 @@ static ComponentAttributeProviders create_attribute_providers_for_curve() make_cyclic_read_attribute, make_cyclic_write_attribute); - return ComponentAttributeProviders({&resolution, &cyclic}, {}); + static CustomDataAccessInfo spline_custom_data_access = { + [](GeometryComponent &component) -> CustomData * { + CurveEval *curve = get_curve_from_component_for_write(component); + return curve ? &curve->attributes.data : nullptr; + }, + [](const GeometryComponent &component) -> const CustomData * { + const CurveEval *curve = get_curve_from_component_for_read(component); + return curve ? &curve->attributes.data : nullptr; + }, + nullptr}; + + static CustomDataAttributeProvider spline_custom_data(ATTR_DOMAIN_CURVE, + spline_custom_data_access); + + 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(); }); + + static DynamicPointAttributeProvider point_custom_data; + + return ComponentAttributeProviders({&position, &radius, &tilt, &resolution, &cyclic}, + {&spline_custom_data, &point_custom_data}); } } // namespace blender::bke diff --git a/source/blender/blenkernel/intern/geometry_component_instances.cc b/source/blender/blenkernel/intern/geometry_component_instances.cc index 55025ed5ac9..3b1b7456162 100644 --- a/source/blender/blenkernel/intern/geometry_component_instances.cc +++ b/source/blender/blenkernel/intern/geometry_component_instances.cc @@ -56,6 +56,19 @@ void InstancesComponent::reserve(int min_capacity) instance_ids_.reserve(min_capacity); } +/** + * Resize the transform, handles, and ID vectors to the specified capacity. + * + * \note This function should be used carefully, only when it's guaranteed + * that the data will be filled. + */ +void InstancesComponent::resize(int capacity) +{ + instance_reference_handles_.resize(capacity); + instance_transforms_.resize(capacity); + instance_ids_.resize(capacity); +} + void InstancesComponent::clear() { instance_reference_handles_.clear(); @@ -81,6 +94,11 @@ blender::Span<int> InstancesComponent::instance_reference_handles() const return instance_reference_handles_; } +blender::MutableSpan<int> InstancesComponent::instance_reference_handles() +{ + return instance_reference_handles_; +} + blender::MutableSpan<blender::float4x4> InstancesComponent::instance_transforms() { return instance_transforms_; diff --git a/source/blender/blenkernel/intern/geometry_set_instances.cc b/source/blender/blenkernel/intern/geometry_set_instances.cc index a792e268d5c..9abd00c2b4f 100644 --- a/source/blender/blenkernel/intern/geometry_set_instances.cc +++ b/source/blender/blenkernel/intern/geometry_set_instances.cc @@ -15,6 +15,7 @@ */ #include "BKE_geometry_set_instances.hh" +#include "BKE_material.h" #include "BKE_mesh.h" #include "BKE_mesh_wrapper.h" #include "BKE_modifier.h" @@ -361,6 +362,8 @@ static Mesh *join_mesh_topology_and_builtin_attributes(Span<GeometryInstanceGrou int64_t cd_dirty_poly = 0; int64_t cd_dirty_edge = 0; int64_t cd_dirty_loop = 0; + VectorSet<Material *> materials; + for (const GeometryInstanceGroup &set_group : set_groups) { const GeometrySet &set = set_group.geometry_set; const int tot_transforms = set_group.transforms.size(); @@ -374,6 +377,10 @@ static Mesh *join_mesh_topology_and_builtin_attributes(Span<GeometryInstanceGrou cd_dirty_poly |= mesh.runtime.cd_dirty_poly; cd_dirty_edge |= mesh.runtime.cd_dirty_edge; cd_dirty_loop |= mesh.runtime.cd_dirty_loop; + for (const int slot_index : IndexRange(mesh.totcol)) { + Material *material = mesh.mat[slot_index]; + materials.add(material); + } } if (convert_points_to_vertices && set.has_pointcloud()) { const PointCloud &pointcloud = *set.get_pointcloud_for_read(); @@ -396,6 +403,10 @@ static Mesh *join_mesh_topology_and_builtin_attributes(Span<GeometryInstanceGrou break; } } + for (const int i : IndexRange(materials.size())) { + Material *material = materials[i]; + BKE_id_material_eval_assign(&new_mesh->id, i + 1, material); + } new_mesh->runtime.cd_dirty_vert = cd_dirty_vert; new_mesh->runtime.cd_dirty_poly = cd_dirty_poly; new_mesh->runtime.cd_dirty_edge = cd_dirty_edge; @@ -409,6 +420,14 @@ static Mesh *join_mesh_topology_and_builtin_attributes(Span<GeometryInstanceGrou const GeometrySet &set = set_group.geometry_set; if (set.has_mesh()) { const Mesh &mesh = *set.get_mesh_for_read(); + + Array<int> material_index_map(mesh.totcol); + for (const int i : IndexRange(mesh.totcol)) { + Material *material = mesh.mat[i]; + const int new_material_index = materials.index_of(material); + material_index_map[i] = new_material_index; + } + for (const float4x4 &transform : set_group.transforms) { for (const int i : IndexRange(mesh.totvert)) { const MVert &old_vert = mesh.mvert[i]; @@ -438,6 +457,13 @@ static Mesh *join_mesh_topology_and_builtin_attributes(Span<GeometryInstanceGrou MPoly &new_poly = new_mesh->mpoly[poly_offset + i]; new_poly = old_poly; new_poly.loopstart += loop_offset; + if (old_poly.mat_nr >= 0 && old_poly.mat_nr < mesh.totcol) { + new_poly.mat_nr = material_index_map[new_poly.mat_nr]; + } + else { + /* The material index was invalid before. */ + new_poly.mat_nr = 0; + } } vert_offset += mesh.totvert; @@ -528,15 +554,26 @@ 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)); } } } + for (SplinePtr &spline : new_curve->splines()) { + /* Spline instances should have no custom attributes, since they always come + * from original objects which currently do not support custom attributes. + * + * This is only true as long as a #GeometrySet cannot be instanced directly. */ + BLI_assert(spline->attributes.data.totlayer == 0); + UNUSED_VARS_NDEBUG(spline); + } + + new_curve->attributes.reallocate(new_curve->splines().size()); + result.replace(new_curve); } diff --git a/source/blender/blenkernel/intern/gpencil.c b/source/blender/blenkernel/intern/gpencil.c index 9c84d155330..421cb0ac4f1 100644 --- a/source/blender/blenkernel/intern/gpencil.c +++ b/source/blender/blenkernel/intern/gpencil.c @@ -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 */ diff --git a/source/blender/blenkernel/intern/gpencil_curve.c b/source/blender/blenkernel/intern/gpencil_curve.c index 88d3e917a7a..906d0fb0792 100644 --- a/source/blender/blenkernel/intern/gpencil_curve.c +++ b/source/blender/blenkernel/intern/gpencil_curve.c @@ -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); } } diff --git a/source/blender/blenkernel/intern/gpencil_geom.c b/source/blender/blenkernel/intern/gpencil_geom.c index 04403e264a4..501ee0c2014 100644 --- a/source/blender/blenkernel/intern/gpencil_geom.c +++ b/source/blender/blenkernel/intern/gpencil_geom.c @@ -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); diff --git a/source/blender/blenkernel/intern/idprop.c b/source/blender/blenkernel/intern/idprop.c index 6b164e6bc50..58715ac2e05 100644 --- a/source/blender/blenkernel/intern/idprop.c +++ b/source/blender/blenkernel/intern/idprop.c @@ -503,7 +503,7 @@ void IDP_SyncGroupValues(IDProperty *dest, const IDProperty *src) void IDP_SyncGroupTypes(IDProperty *dest, const IDProperty *src, const bool do_arraylen) { - LISTBASE_FOREACH_MUTABLE (IDProperty *, prop_dst, &src->data.group) { + LISTBASE_FOREACH_MUTABLE (IDProperty *, prop_dst, &dest->data.group) { const IDProperty *prop_src = IDP_GetPropertyFromGroup((IDProperty *)src, prop_dst->name); if (prop_src != NULL) { /* check of we should replace? */ diff --git a/source/blender/blenkernel/intern/image.c b/source/blender/blenkernel/intern/image.c index 368b1c2e66b..2f7e2b41a73 100644 --- a/source/blender/blenkernel/intern/image.c +++ b/source/blender/blenkernel/intern/image.c @@ -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; diff --git a/source/blender/blenkernel/intern/lib_id.c b/source/blender/blenkernel/intern/lib_id.c index 795a5ad9468..c2e5006cbc1 100644 --- a/source/blender/blenkernel/intern/lib_id.c +++ b/source/blender/blenkernel/intern/lib_id.c @@ -525,7 +525,13 @@ static int id_copy_libmanagement_cb(LibraryIDLinkCallbackData *cb_data) /* Increase used IDs refcount if needed and required. */ if ((data->flag & LIB_ID_CREATE_NO_USER_REFCOUNT) == 0 && (cb_flag & IDWALK_CB_USER)) { - id_us_plus(id); + if ((data->flag & LIB_ID_CREATE_NO_MAIN) != 0) { + BLI_assert(cb_data->id_self->tag & LIB_TAG_NO_MAIN); + id_us_plus_no_lib(id); + } + else { + id_us_plus(id); + } } return IDWALK_RET_NOP; @@ -578,7 +584,7 @@ ID *BKE_id_copy_ex(Main *bmain, const ID *id, ID **r_newid, const int flag) } } - /* Early output is source is NULL. */ + /* Early output if source is NULL. */ if (id == NULL) { return NULL; } @@ -1245,6 +1251,13 @@ void BKE_libblock_copy_ex(Main *bmain, const ID *id, ID **r_newid, const int ori } BLI_assert(new_id != NULL); + if ((flag & LIB_ID_COPY_SET_COPIED_ON_WRITE) != 0) { + new_id->tag |= LIB_TAG_COPIED_ON_WRITE; + } + else { + new_id->tag &= ~LIB_TAG_COPIED_ON_WRITE; + } + const size_t id_len = BKE_libblock_get_alloc_info(GS(new_id->name), NULL); const size_t id_offset = sizeof(ID); if ((int)id_len - (int)id_offset > 0) { /* signed to allow neg result */ /* XXX ????? */ @@ -1348,12 +1361,12 @@ void id_sort_by_name(ListBase *lb, ID *id, ID *id_sorting_hint) BLI_remlink(lb, id); /* Check if we can actually insert id before or after id_sorting_hint, if given. */ - if (!ELEM(id_sorting_hint, NULL, id)) { + if (!ELEM(id_sorting_hint, NULL, id) && id_sorting_hint->lib == id->lib) { BLI_assert(BLI_findindex(lb, id_sorting_hint) >= 0); ID *id_sorting_hint_next = id_sorting_hint->next; if (BLI_strcasecmp(id_sorting_hint->name, id->name) < 0 && - (id_sorting_hint_next == NULL || + (id_sorting_hint_next == NULL || id_sorting_hint_next->lib != id->lib || BLI_strcasecmp(id_sorting_hint_next->name, id->name) > 0)) { BLI_insertlinkafter(lb, id_sorting_hint, id); return; @@ -1361,7 +1374,7 @@ void id_sort_by_name(ListBase *lb, ID *id, ID *id_sorting_hint) ID *id_sorting_hint_prev = id_sorting_hint->prev; if (BLI_strcasecmp(id_sorting_hint->name, id->name) > 0 && - (id_sorting_hint_prev == NULL || + (id_sorting_hint_prev == NULL || id_sorting_hint_prev->lib != id->lib || BLI_strcasecmp(id_sorting_hint_prev->name, id->name) < 0)) { BLI_insertlinkbefore(lb, id_sorting_hint, id); return; @@ -1376,16 +1389,33 @@ void id_sort_by_name(ListBase *lb, ID *id, ID *id_sorting_hint) /* Note: We start from the end, because in typical 'heavy' case (insertion of lots of IDs at * once using the same base name), newly inserted items will generally be towards the end * (higher extension numbers). */ - for (idtest = lb->last, item_array_index = ID_SORT_STEP_SIZE - 1; idtest != NULL; - idtest = idtest->prev, item_array_index--) { + bool is_in_library = false; + item_array_index = ID_SORT_STEP_SIZE - 1; + for (idtest = lb->last; idtest != NULL; idtest = idtest->prev) { + if (is_in_library) { + if (idtest->lib != id->lib) { + /* We got out of expected library 'range' in the list, so we are done here and can move on + * to the next step. */ + break; + } + } + else if (idtest->lib == id->lib) { + /* We are entering the expected library 'range' of IDs in the list. */ + is_in_library = true; + } + + if (!is_in_library) { + continue; + } + item_array[item_array_index] = idtest; if (item_array_index == 0) { - if ((idtest->lib == NULL && id->lib != NULL) || - BLI_strcasecmp(idtest->name, id->name) <= 0) { + if (BLI_strcasecmp(idtest->name, id->name) <= 0) { break; } item_array_index = ID_SORT_STEP_SIZE; } + item_array_index--; } /* Step two: we go forward in the selected chunk of items and check all of them, as we know @@ -1397,7 +1427,7 @@ void id_sort_by_name(ListBase *lb, ID *id, ID *id_sorting_hint) * So we can increment that index in any case. */ for (item_array_index++; item_array_index < ID_SORT_STEP_SIZE; item_array_index++) { idtest = item_array[item_array_index]; - if ((idtest->lib != NULL && id->lib == NULL) || BLI_strcasecmp(idtest->name, id->name) > 0) { + if (BLI_strcasecmp(idtest->name, id->name) > 0) { BLI_insertlinkbefore(lb, idtest, id); break; } @@ -1405,12 +1435,18 @@ void id_sort_by_name(ListBase *lb, ID *id, ID *id_sorting_hint) if (item_array_index == ID_SORT_STEP_SIZE) { if (idtest == NULL) { /* If idtest is NULL here, it means that in the first loop, the last comparison was - * performed exactly on the first item of the list, and that it also failed. In other - * words, all items in the list are greater than inserted one, so we can put it at the - * start of the list. */ - /* Note that BLI_insertlinkafter() would have same behavior in that case, but better be - * explicit here. */ - BLI_addhead(lb, id); + * performed exactly on the first item of the list, and that it also failed. And that the + * second loop was not walked at all. + * + * In other words, if `id` is local, all the items in the list are greater than the inserted + * one, so we can put it at the start of the list. Or, if `id` is linked, it is the first one + * of its library, and we can put it at the very end of the list. */ + if (ID_IS_LINKED(id)) { + BLI_addtail(lb, id); + } + else { + BLI_addhead(lb, id); + } } else { BLI_insertlinkafter(lb, idtest, id); @@ -1678,12 +1714,14 @@ static bool check_for_dupid(ListBase *lb, ID *id, char *name, ID **r_id_sorting_ */ bool BKE_id_new_name_validate(ListBase *lb, ID *id, const char *tname) { - bool result; + bool result = false; char name[MAX_ID_NAME - 2]; - /* if library, don't rename */ + /* If library, don't rename, but do ensure proper sorting. */ if (ID_IS_LINKED(id)) { - return false; + id_sort_by_name(lb, id, NULL); + + return result; } /* if no name given, use name of current ID diff --git a/source/blender/blenkernel/intern/lib_id_test.cc b/source/blender/blenkernel/intern/lib_id_test.cc new file mode 100644 index 00000000000..9bfd19ae4d6 --- /dev/null +++ b/source/blender/blenkernel/intern/lib_id_test.cc @@ -0,0 +1,112 @@ +/* + * 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. + * + * The Original Code is Copyright (C) 2020 by Blender Foundation. + */ +#include "testing/testing.h" + +#include "MEM_guardedalloc.h" + +#include "BLI_listbase.h" + +#include "BKE_idtype.h" +#include "BKE_lib_id.h" +#include "BKE_main.h" + +#include "DNA_ID.h" +#include "DNA_mesh_types.h" +#include "DNA_object_types.h" + +namespace blender::bke::tests { + +struct LibIDMainSortTestContext { + Main *bmain; +}; + +static void test_lib_id_main_sort_init(LibIDMainSortTestContext *ctx) +{ + ctx->bmain = BKE_main_new(); +} + +static void test_lib_id_main_sort_free(LibIDMainSortTestContext *ctx) +{ + BKE_main_free(ctx->bmain); +} + +static void test_lib_id_main_sort_check_order(std::initializer_list<ID *> list) +{ + ID *prev_id = nullptr; + for (ID *id : list) { + EXPECT_EQ(id->prev, prev_id); + if (prev_id != nullptr) { + EXPECT_EQ(prev_id->next, id); + } + prev_id = id; + } + EXPECT_EQ(prev_id->next, nullptr); +} + +TEST(lib_id_main_sort, local_ids_1) +{ + LibIDMainSortTestContext ctx = {nullptr}; + test_lib_id_main_sort_init(&ctx); + EXPECT_TRUE(BLI_listbase_is_empty(&ctx.bmain->libraries)); + + ID *id_c = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "OB_C")); + ID *id_a = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "OB_A")); + ID *id_b = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "OB_B")); + EXPECT_TRUE(ctx.bmain->objects.first == id_a); + EXPECT_TRUE(ctx.bmain->objects.last == id_c); + test_lib_id_main_sort_check_order({id_a, id_b, id_c}); + + test_lib_id_main_sort_free(&ctx); +} + +TEST(lib_id_main_sort, linked_ids_1) +{ + LibIDMainSortTestContext ctx = {nullptr}; + test_lib_id_main_sort_init(&ctx); + EXPECT_TRUE(BLI_listbase_is_empty(&ctx.bmain->libraries)); + + Library *lib_a = static_cast<Library *>(BKE_id_new(ctx.bmain, ID_LI, "LI_A")); + Library *lib_b = static_cast<Library *>(BKE_id_new(ctx.bmain, ID_LI, "LI_B")); + ID *id_c = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "OB_C")); + ID *id_a = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "OB_A")); + ID *id_b = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "OB_B")); + + id_a->lib = lib_a; + id_sort_by_name(&ctx.bmain->objects, id_a, nullptr); + id_b->lib = lib_a; + id_sort_by_name(&ctx.bmain->objects, id_b, nullptr); + EXPECT_TRUE(ctx.bmain->objects.first == id_c); + EXPECT_TRUE(ctx.bmain->objects.last == id_b); + test_lib_id_main_sort_check_order({id_c, id_a, id_b}); + + id_a->lib = lib_b; + id_sort_by_name(&ctx.bmain->objects, id_a, nullptr); + EXPECT_TRUE(ctx.bmain->objects.first == id_c); + EXPECT_TRUE(ctx.bmain->objects.last == id_a); + test_lib_id_main_sort_check_order({id_c, id_b, id_a}); + + id_b->lib = lib_b; + id_sort_by_name(&ctx.bmain->objects, id_b, nullptr); + EXPECT_TRUE(ctx.bmain->objects.first == id_c); + EXPECT_TRUE(ctx.bmain->objects.last == id_b); + test_lib_id_main_sort_check_order({id_c, id_a, id_b}); + + test_lib_id_main_sort_free(&ctx); +} + +} // namespace blender::bke::tests diff --git a/source/blender/blenkernel/intern/lib_override.c b/source/blender/blenkernel/intern/lib_override.c index 84091abd410..8341c5b6e78 100644 --- a/source/blender/blenkernel/intern/lib_override.c +++ b/source/blender/blenkernel/intern/lib_override.c @@ -719,8 +719,19 @@ static void lib_override_library_create_post_process(Main *bmain, if (BLI_gset_lookup(all_objects_in_scene, ob_new) == NULL) { if (id_root != NULL && default_instantiating_collection == NULL) { - switch (GS(id_root->name)) { + ID *id_ref = id_root->newid != NULL ? id_root->newid : id_root; + switch (GS(id_ref->name)) { case ID_GR: { + /* Adding the object to a specific collection outside of the root overridden one is a + * fairly bad idea (it breaks the override hierarchy concept). But there is no other + * way to do this currently (we cannot add new collections to overridden root one, + * this is not currently supported). + * Since that will be fairly annoying and noisy, only do that in case the override + * object is not part of any existing collection (i.e. its user count is 0). In + * practice this should never happen I think. */ + if (ID_REAL_USERS(ob_new) != 0) { + continue; + } default_instantiating_collection = BKE_collection_add( bmain, (Collection *)id_root, "OVERRIDE_HIDDEN"); /* Hide the collection from viewport and render. */ @@ -731,9 +742,9 @@ static void lib_override_library_create_post_process(Main *bmain, case ID_OB: { /* Add the other objects to one of the collections instantiating the * root object, or scene's master collection if none found. */ - Object *ob_root = (Object *)id_root; + Object *ob_ref = (Object *)id_ref; LISTBASE_FOREACH (Collection *, collection, &bmain->collections) { - if (BKE_collection_has_object(collection, ob_root) && + if (BKE_collection_has_object(collection, ob_ref) && BKE_view_layer_has_collection(view_layer, collection) && !ID_IS_LINKED(collection) && !ID_IS_OVERRIDE_LIBRARY(collection)) { default_instantiating_collection = collection; @@ -867,7 +878,8 @@ bool BKE_lib_override_library_resync(Main *bmain, ID *id_root, Collection *override_resync_residual_storage, const bool do_hierarchy_enforce, - const bool do_post_process) + const bool do_post_process, + ReportList *reports) { BLI_assert(ID_IS_OVERRIDE_LIBRARY_REAL(id_root)); BLI_assert(!ID_IS_LINKED(id_root)); @@ -891,6 +903,19 @@ bool BKE_lib_override_library_resync(Main *bmain, BLI_ghashutil_ptrhash, BLI_ghashutil_ptrcmp, __func__); ID *id; FOREACH_MAIN_ID_BEGIN (bmain, id) { + /* IDs that get fully removed from linked data remain as local overrides (using place-holder + * linked IDs as reference), but they are often not reachable from any current valid local + * override hierarchy anymore. This will ensure they get properly deleted at the end of this + * function. */ + if (!ID_IS_LINKED(id) && ID_IS_OVERRIDE_LIBRARY_REAL(id) && + (id->override_library->reference->tag & LIB_TAG_MISSING) != 0 && + /* Unfortunately deleting obdata means deleting their objects too. Since there is no + * guarantee that a valid override object using an obsolete override obdata gets properly + * updated, we ignore those here for now. In practice this should not be a big issue. */ + !OB_DATA_SUPPORT_ID(GS(id->name))) { + id->tag |= LIB_TAG_MISSING; + } + if (id->tag & LIB_TAG_DOIT && !ID_IS_LINKED(id) && ID_IS_OVERRIDE_LIBRARY_REAL(id)) { /* While this should not happen in typical cases (and won't be properly supported here), user * is free to do all kind of very bad things, including having different local overrides of a @@ -1033,6 +1058,7 @@ bool BKE_lib_override_library_resync(Main *bmain, /* Delete old override IDs. * Note that we have to use tagged group deletion here, since ID deletion also uses LIB_TAG_DOIT. * This improves performances anyway, so everything is fine. */ + int user_edited_overrides_deletion_count = 0; FOREACH_MAIN_ID_BEGIN (bmain, id) { if (id->tag & LIB_TAG_DOIT) { /* Note that this works because linked IDs are always after local ones (including overrides), @@ -1057,6 +1083,7 @@ bool BKE_lib_override_library_resync(Main *bmain, id->tag &= ~LIB_TAG_MISSING; CLOG_INFO(&LOG, 2, "Old override %s is being deleted", id->name); } +#if 0 else { /* Otherwise, keep them, user needs to decide whether what to do with them. */ BLI_assert((id->tag & LIB_TAG_DOIT) == 0); @@ -1064,6 +1091,17 @@ bool BKE_lib_override_library_resync(Main *bmain, id->flag |= LIB_LIB_OVERRIDE_RESYNC_LEFTOVER; CLOG_INFO(&LOG, 2, "Old override %s is being kept around as it was user-edited", id->name); } +#else + else { + /* Delete them nevertheless, with fat warning, user needs to decide whether they want to + * save that version of the file (and accept the loss), or not. */ + id->tag |= LIB_TAG_DOIT; + id->tag &= ~LIB_TAG_MISSING; + CLOG_WARN( + &LOG, "Old override %s is being deleted even though it was user-edited", id->name); + user_edited_overrides_deletion_count++; + } +#endif } } FOREACH_MAIN_ID_END; @@ -1074,6 +1112,15 @@ bool BKE_lib_override_library_resync(Main *bmain, */ id_root = id_root_reference->newid; + if (user_edited_overrides_deletion_count > 0) { + BKE_reportf(reports, + RPT_WARNING, + "During resync of data-block %s, %d obsolete overrides were deleted, that had " + "local changes defined by user", + id_root->name + 2, + user_edited_overrides_deletion_count); + } + if (do_post_process) { /* Essentially ensures that potentially new overrides of new objects will be instantiated. */ /* Note: Here 'reference' collection and 'newly added' collection are the same, which is fine @@ -1112,7 +1159,10 @@ bool BKE_lib_override_library_resync(Main *bmain, * Then it will handle the resync of necessary IDs (through calls to * #BKE_lib_override_library_resync). */ -void BKE_lib_override_library_main_resync(Main *bmain, Scene *scene, ViewLayer *view_layer) +void BKE_lib_override_library_main_resync(Main *bmain, + Scene *scene, + ViewLayer *view_layer, + ReportList *reports) { /* We use a specific collection to gather/store all 'orphaned' override collections and objects * generated by re-sync-process. This avoids putting them in scene's master collection. */ @@ -1228,7 +1278,7 @@ void BKE_lib_override_library_main_resync(Main *bmain, Scene *scene, ViewLayer * CLOG_INFO(&LOG, 2, "Resyncing %s...", id->name); const bool success = BKE_lib_override_library_resync( - bmain, scene, view_layer, id, override_resync_residual_storage, false, false); + bmain, scene, view_layer, id, override_resync_residual_storage, false, false, reports); CLOG_INFO(&LOG, 2, "\tSuccess: %d", success); break; } @@ -1619,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) { @@ -1653,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; diff --git a/source/blender/blenkernel/intern/material.c b/source/blender/blenkernel/intern/material.c index 37d47a984cc..73b64e6efb3 100644 --- a/source/blender/blenkernel/intern/material.c +++ b/source/blender/blenkernel/intern/material.c @@ -77,6 +77,7 @@ #include "DEG_depsgraph.h" #include "DEG_depsgraph_build.h" +#include "DEG_depsgraph_query.h" #include "GPU_material.h" @@ -700,6 +701,98 @@ Material *BKE_object_material_get(Object *ob, short act) return ma_p ? *ma_p : NULL; } +static ID *get_evaluated_object_data_with_materials(Object *ob) +{ + ID *data = ob->data; + /* Meshes in edit mode need special handling. */ + if (ob->type == OB_MESH && ob->mode == OB_MODE_EDIT) { + Mesh *mesh = ob->data; + if (mesh->edit_mesh && mesh->edit_mesh->mesh_eval_final) { + data = &mesh->edit_mesh->mesh_eval_final->id; + } + } + return data; +} + +/** + * On evaluated objects the number of materials on an object and its data might go out of sync. + * This is because during evaluation materials can be added/removed on the object data. + * + * For rendering or exporting we generally use the materials on the object data. However, some + * material indices might be overwritten by the object. + */ +Material *BKE_object_material_get_eval(Object *ob, short act) +{ + BLI_assert(DEG_is_evaluated_object(ob)); + const int slot_index = act - 1; + + if (slot_index < 0) { + return NULL; + } + ID *data = get_evaluated_object_data_with_materials(ob); + const short *tot_slots_data_ptr = BKE_id_material_len_p(data); + const int tot_slots_data = tot_slots_data_ptr ? *tot_slots_data_ptr : 0; + if (slot_index >= tot_slots_data) { + return NULL; + } + const int tot_slots_object = ob->totcol; + + Material ***materials_data_ptr = BKE_id_material_array_p(data); + Material **materials_data = materials_data_ptr ? *materials_data_ptr : NULL; + Material **materials_object = ob->mat; + + /* Check if slot is overwritten by object. */ + if (slot_index < tot_slots_object) { + if (ob->matbits) { + if (ob->matbits[slot_index]) { + Material *material = materials_object[slot_index]; + if (material != NULL) { + return material; + } + } + } + } + /* Otherwise use data from object-data. */ + if (slot_index < tot_slots_data) { + Material *material = materials_data[slot_index]; + return material; + } + return NULL; +} + +int BKE_object_material_count_eval(Object *ob) +{ + BLI_assert(DEG_is_evaluated_object(ob)); + ID *id = get_evaluated_object_data_with_materials(ob); + const short *len_p = BKE_id_material_len_p(id); + return len_p ? *len_p : 0; +} + +void BKE_id_material_eval_assign(ID *id, int slot, Material *material) +{ + Material ***materials_ptr = BKE_id_material_array_p(id); + short *len_ptr = BKE_id_material_len_p(id); + if (ELEM(NULL, materials_ptr, len_ptr)) { + BLI_assert_unreachable(); + return; + } + + const int slot_index = slot - 1; + const int old_length = *len_ptr; + + if (slot_index >= old_length) { + /* Need to grow slots array. */ + const int new_length = slot_index + 1; + *materials_ptr = MEM_reallocN(*materials_ptr, sizeof(void *) * new_length); + *len_ptr = new_length; + for (int i = old_length; i < new_length; i++) { + (*materials_ptr)[i] = NULL; + } + } + + (*materials_ptr)[slot_index] = material; +} + Material *BKE_gpencil_material(Object *ob, short act) { Material *ma = BKE_object_material_get(ob, act); @@ -1028,6 +1121,43 @@ void BKE_object_material_remap_calc(Object *ob_dst, Object *ob_src, short *remap BLI_ghash_free(gh_mat_map, NULL, NULL); } +/** + * Copy materials from evaluated geometry to the original geometry of an object. + */ +void BKE_object_material_from_eval_data(Main *bmain, Object *ob_orig, ID *data_eval) +{ + ID *data_orig = ob_orig->data; + + short *orig_totcol = BKE_id_material_len_p(data_orig); + Material ***orig_mat = BKE_id_material_array_p(data_orig); + + short *eval_totcol = BKE_id_material_len_p(data_eval); + Material ***eval_mat = BKE_id_material_array_p(data_eval); + + if (ELEM(NULL, orig_totcol, orig_mat, eval_totcol, eval_mat)) { + return; + } + + /* Remove old materials from original geometry. */ + for (int i = 0; i < *orig_totcol; i++) { + id_us_min(&(*orig_mat)[i]->id); + } + MEM_SAFE_FREE(*orig_mat); + + /* Create new material slots based on materials on evaluated geometry. */ + *orig_totcol = *eval_totcol; + *orig_mat = MEM_callocN(sizeof(void *) * (*eval_totcol), __func__); + for (int i = 0; i < *eval_totcol; i++) { + Material *material_eval = (*eval_mat)[i]; + if (material_eval != NULL) { + Material *material_orig = (Material *)DEG_get_original_id(&material_eval->id); + (*orig_mat)[i] = material_orig; + id_us_plus(&material_orig->id); + } + } + BKE_object_materials_test(bmain, ob_orig, data_orig); +} + /* XXX - this calls many more update calls per object then are needed, could be optimized */ void BKE_object_material_array_assign(Main *bmain, struct Object *ob, diff --git a/source/blender/blenkernel/intern/mball_tessellate.c b/source/blender/blenkernel/intern/mball_tessellate.c index 1550401cc9c..7a06fd4ea9d 100644 --- a/source/blender/blenkernel/intern/mball_tessellate.c +++ b/source/blender/blenkernel/intern/mball_tessellate.c @@ -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; diff --git a/source/blender/blenkernel/intern/mesh_convert.c b/source/blender/blenkernel/intern/mesh_convert.c index 098d9c420aa..e893a3983bd 100644 --- a/source/blender/blenkernel/intern/mesh_convert.c +++ b/source/blender/blenkernel/intern/mesh_convert.c @@ -1211,7 +1211,9 @@ static Mesh *mesh_new_from_mesh(Object *object, Mesh *mesh) return mesh_result; } -static Mesh *mesh_new_from_mesh_object_with_layers(Depsgraph *depsgraph, Object *object) +static Mesh *mesh_new_from_mesh_object_with_layers(Depsgraph *depsgraph, + Object *object, + const bool preserve_origindex) { if (DEG_is_original_id(&object->id)) { return mesh_new_from_mesh(object, (Mesh *)object->data); @@ -1228,16 +1230,23 @@ static Mesh *mesh_new_from_mesh_object_with_layers(Depsgraph *depsgraph, Object Scene *scene = DEG_get_evaluated_scene(depsgraph); CustomData_MeshMasks mask = CD_MASK_MESH; + if (preserve_origindex) { + mask.vmask |= CD_MASK_ORIGINDEX; + mask.emask |= CD_MASK_ORIGINDEX; + mask.lmask |= CD_MASK_ORIGINDEX; + mask.pmask |= CD_MASK_ORIGINDEX; + } Mesh *result = mesh_create_eval_final(depsgraph, scene, &object_for_eval, &mask); return result; } static Mesh *mesh_new_from_mesh_object(Depsgraph *depsgraph, Object *object, - bool preserve_all_data_layers) + const bool preserve_all_data_layers, + const bool preserve_origindex) { - if (preserve_all_data_layers) { - return mesh_new_from_mesh_object_with_layers(depsgraph, object); + if (preserve_all_data_layers || preserve_origindex) { + return mesh_new_from_mesh_object_with_layers(depsgraph, object, preserve_origindex); } Mesh *mesh_input = object->data; /* If we are in edit mode, use evaluated mesh from edit structure, matching to what @@ -1248,7 +1257,10 @@ static Mesh *mesh_new_from_mesh_object(Depsgraph *depsgraph, return mesh_new_from_mesh(object, mesh_input); } -Mesh *BKE_mesh_new_from_object(Depsgraph *depsgraph, Object *object, bool preserve_all_data_layers) +Mesh *BKE_mesh_new_from_object(Depsgraph *depsgraph, + Object *object, + const bool preserve_all_data_layers, + const bool preserve_origindex) { Mesh *new_mesh = NULL; switch (object->type) { @@ -1261,7 +1273,8 @@ Mesh *BKE_mesh_new_from_object(Depsgraph *depsgraph, Object *object, bool preser new_mesh = mesh_new_from_mball_object(object); break; case OB_MESH: - new_mesh = mesh_new_from_mesh_object(depsgraph, object, preserve_all_data_layers); + new_mesh = mesh_new_from_mesh_object( + depsgraph, object, preserve_all_data_layers, preserve_origindex); break; default: /* Object does not have geometry data. */ @@ -1326,7 +1339,7 @@ Mesh *BKE_mesh_new_from_object_to_bmain(Main *bmain, { BLI_assert(ELEM(object->type, OB_FONT, OB_CURVE, OB_SURF, OB_MBALL, OB_MESH)); - Mesh *mesh = BKE_mesh_new_from_object(depsgraph, object, preserve_all_data_layers); + Mesh *mesh = BKE_mesh_new_from_object(depsgraph, object, preserve_all_data_layers, false); if (mesh == NULL) { /* Unable to convert the object to a mesh, return an empty one. */ Mesh *mesh_in_bmain = BKE_mesh_add(bmain, ((ID *)object->data)->name + 2); diff --git a/source/blender/blenkernel/intern/modifier.c b/source/blender/blenkernel/intern/modifier.c index 971a2154435..bd729ae5837 100644 --- a/source/blender/blenkernel/intern/modifier.c +++ b/source/blender/blenkernel/intern/modifier.c @@ -282,7 +282,7 @@ bool BKE_modifier_is_preview(ModifierData *md) return false; } -ModifierData *BKE_modifiers_findby_type(Object *ob, ModifierType type) +ModifierData *BKE_modifiers_findby_type(const Object *ob, ModifierType type) { LISTBASE_FOREACH (ModifierData *, md, &ob->modifiers) { if (md->type == type) { @@ -292,7 +292,7 @@ ModifierData *BKE_modifiers_findby_type(Object *ob, ModifierType type) return NULL; } -ModifierData *BKE_modifiers_findby_name(Object *ob, const char *name) +ModifierData *BKE_modifiers_findby_name(const Object *ob, const char *name) { return BLI_findstring(&(ob->modifiers), name, offsetof(ModifierData, name)); } diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc index 6634c42a274..6405888d5be 100644 --- a/source/blender/blenkernel/intern/node.cc +++ b/source/blender/blenkernel/intern/node.cc @@ -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: @@ -501,6 +517,12 @@ void ntreeBlendWrite(BlendWriter *writer, bNodeTree *ntree) ELEM(node->type, SH_NODE_CURVE_VEC, SH_NODE_CURVE_RGB)) { BKE_curvemapping_blend_write(writer, (const CurveMapping *)node->storage); } + else if ((ntree->type == NTREE_GEOMETRY) && (node->type == GEO_NODE_ATTRIBUTE_CURVE_MAP)) { + BLO_write_struct_by_name(writer, node->typeinfo->storagename, node->storage); + NodeAttributeCurveMap *data = (NodeAttributeCurveMap *)node->storage; + BKE_curvemapping_blend_write(writer, (const CurveMapping *)data->curve_vec); + BKE_curvemapping_blend_write(writer, (const CurveMapping *)data->curve_rgb); + } else if (ntree->type == NTREE_SHADER && (node->type == SH_NODE_SCRIPT)) { NodeShaderScript *nss = (NodeShaderScript *)node->storage; if (nss->bytecode) { @@ -676,6 +698,18 @@ void ntreeBlendReadData(BlendDataReader *reader, bNodeTree *ntree) BKE_curvemapping_blend_read(reader, (CurveMapping *)node->storage); break; } + case GEO_NODE_ATTRIBUTE_CURVE_MAP: { + NodeAttributeCurveMap *data = (NodeAttributeCurveMap *)node->storage; + BLO_read_data_address(reader, &data->curve_vec); + if (data->curve_vec) { + BKE_curvemapping_blend_read(reader, data->curve_vec); + } + BLO_read_data_address(reader, &data->curve_rgb); + if (data->curve_rgb) { + BKE_curvemapping_blend_read(reader, data->curve_rgb); + } + break; + } case SH_NODE_SCRIPT: { NodeShaderScript *nss = (NodeShaderScript *)node->storage; BLO_read_data_address(reader, &nss->bytecode); @@ -802,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: @@ -887,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: @@ -1454,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: @@ -1493,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: @@ -1636,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; } @@ -1707,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; } @@ -2172,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) @@ -2208,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; } @@ -4226,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) { @@ -4934,6 +5035,7 @@ static void registerGeometryNodes() register_node_type_geo_attribute_combine_xyz(); register_node_type_geo_attribute_compare(); register_node_type_geo_attribute_convert(); + register_node_type_geo_attribute_curve_map(); register_node_type_geo_attribute_fill(); register_node_type_geo_attribute_map_range(); register_node_type_geo_attribute_math(); @@ -4943,14 +5045,18 @@ static void registerGeometryNodes() register_node_type_geo_attribute_separate_xyz(); register_node_type_geo_attribute_transfer(); register_node_type_geo_attribute_vector_math(); + register_node_type_geo_attribute_vector_rotate(); register_node_type_geo_attribute_remove(); register_node_type_geo_boolean(); register_node_type_geo_bounding_box(); register_node_type_geo_collection_info(); register_node_type_geo_curve_to_mesh(); + register_node_type_geo_curve_resample(); register_node_type_geo_edge_split(); + register_node_type_geo_input_material(); register_node_type_geo_is_viewport(); register_node_type_geo_join_geometry(); + register_node_type_geo_material_assign(); register_node_type_geo_mesh_primitive_circle(); register_node_type_geo_mesh_primitive_cone(); register_node_type_geo_mesh_primitive_cube(); diff --git a/source/blender/blenkernel/intern/node_ui_storage.cc b/source/blender/blenkernel/intern/node_ui_storage.cc index cc910bab6ac..7a28fd295fb 100644 --- a/source/blender/blenkernel/intern/node_ui_storage.cc +++ b/source/blender/blenkernel/intern/node_ui_storage.cc @@ -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}); } diff --git a/source/blender/blenkernel/intern/object.c b/source/blender/blenkernel/intern/object.c index 02be16d1d28..109037bc95a 100644 --- a/source/blender/blenkernel/intern/object.c +++ b/source/blender/blenkernel/intern/object.c @@ -2289,7 +2289,7 @@ Object *BKE_object_add_for_data( void BKE_object_copy_softbody(Object *ob_dst, const Object *ob_src, const int flag) { SoftBody *sb = ob_src->soft; - bool tagged_no_main = ob_dst->id.tag & LIB_TAG_NO_MAIN; + const bool is_orig = (flag & LIB_ID_COPY_SET_COPIED_ON_WRITE) == 0; ob_dst->softflag = ob_src->softflag; if (sb == NULL) { @@ -2330,7 +2330,7 @@ void BKE_object_copy_softbody(Object *ob_dst, const Object *ob_src, const int fl sbn->scratch = NULL; - if (!tagged_no_main) { + if (is_orig) { sbn->shared = MEM_dupallocN(sb->shared); sbn->shared->pointcache = BKE_ptcache_copy_list( &sbn->shared->ptcaches, &sb->shared->ptcaches, flag); @@ -5668,7 +5668,7 @@ Mesh *BKE_object_to_mesh(Depsgraph *depsgraph, Object *object, bool preserve_all { BKE_object_to_mesh_clear(object); - Mesh *mesh = BKE_mesh_new_from_object(depsgraph, object, preserve_all_data_layers); + Mesh *mesh = BKE_mesh_new_from_object(depsgraph, object, preserve_all_data_layers, false); object->runtime.object_as_temp_mesh = mesh; return mesh; } diff --git a/source/blender/blenkernel/intern/ocean.c b/source/blender/blenkernel/intern/ocean.c index d2f4d0702ed..9d53dad8d03 100644 --- a/source/blender/blenkernel/intern/ocean.c +++ b/source/blender/blenkernel/intern/ocean.c @@ -911,8 +911,12 @@ void BKE_ocean_init(struct Ocean *o, for (i = 0; i < o->_M; i++) { for (j = 0; j < o->_N; j++) { /* This ensures we get a value tied to the surface location, avoiding dramatic surface - * change with changing resolution. */ - int new_seed = seed + BLI_hash_int_2d(o->_kx[i] * 360.0f, o->_kz[j] * 360.0f); + * change with changing resolution. + * Explicitly cast to signed int first to ensure consistent behavior on all processors, + * since behavior of float to unsigned int cast is undefined in C. */ + const int hash_x = o->_kx[i] * 360.0f; + const int hash_z = o->_kz[j] * 360.0f; + int new_seed = seed + BLI_hash_int_2d(hash_x, hash_z); BLI_rng_seed(rng, new_seed); float r1 = gaussRand(rng); diff --git a/source/blender/blenkernel/intern/rigidbody.c b/source/blender/blenkernel/intern/rigidbody.c index 19078446009..2539b990210 100644 --- a/source/blender/blenkernel/intern/rigidbody.c +++ b/source/blender/blenkernel/intern/rigidbody.c @@ -260,10 +260,12 @@ static RigidBodyOb *rigidbody_copy_object(const Object *ob, const int flag) RigidBodyOb *rboN = NULL; if (ob->rigidbody_object) { + const bool is_orig = (flag & LIB_ID_COPY_SET_COPIED_ON_WRITE) == 0; + /* just duplicate the whole struct first (to catch all the settings) */ rboN = MEM_dupallocN(ob->rigidbody_object); - if ((flag & LIB_ID_CREATE_NO_MAIN) == 0) { + if (is_orig) { /* This is a regular copy, and not a CoW copy for depsgraph evaluation */ rboN->shared = MEM_callocN(sizeof(*rboN->shared), "RigidBodyOb_Shared"); } diff --git a/source/blender/blenkernel/intern/softbody.c b/source/blender/blenkernel/intern/softbody.c index 3b1230ce3b6..fcc1afbc59b 100644 --- a/source/blender/blenkernel/intern/softbody.c +++ b/source/blender/blenkernel/intern/softbody.c @@ -829,13 +829,13 @@ static void calculate_collision_balls(Object *ob) } /* creates new softbody if didn't exist yet, makes new points and springs arrays */ -static void renew_softbody(Scene *scene, Object *ob, int totpoint, int totspring) +static void renew_softbody(Object *ob, int totpoint, int totspring) { SoftBody *sb; int i; short softflag; if (ob->soft == NULL) { - ob->soft = sbNew(scene); + ob->soft = sbNew(); } else { free_softbody_intern(ob->soft); @@ -2679,7 +2679,7 @@ static void springs_from_mesh(Object *ob) } /* makes totally fresh start situation */ -static void mesh_to_softbody(Scene *scene, Object *ob) +static void mesh_to_softbody(Object *ob) { SoftBody *sb; Mesh *me = ob->data; @@ -2697,7 +2697,7 @@ static void mesh_to_softbody(Scene *scene, Object *ob) } /* renew ends with ob->soft with points and edges, also checks & makes ob->soft */ - renew_softbody(scene, ob, me->totvert, totedge); + renew_softbody(ob, me->totvert, totedge); /* we always make body points */ sb = ob->soft; @@ -2909,7 +2909,7 @@ static void makelatticesprings(Lattice *lt, BodySpring *bs, int dostiff, Object } /* makes totally fresh start situation */ -static void lattice_to_softbody(Scene *scene, Object *ob) +static void lattice_to_softbody(Object *ob) { Lattice *lt = ob->data; SoftBody *sb; @@ -2929,7 +2929,7 @@ static void lattice_to_softbody(Scene *scene, Object *ob) } /* renew ends with ob->soft with points and edges, also checks & makes ob->soft */ - renew_softbody(scene, ob, totvert, totspring); + renew_softbody(ob, totvert, totspring); sb = ob->soft; /* can be created in renew_softbody() */ bp = sb->bpoint; @@ -2972,7 +2972,7 @@ static void lattice_to_softbody(Scene *scene, Object *ob) } /* makes totally fresh start situation */ -static void curve_surf_to_softbody(Scene *scene, Object *ob) +static void curve_surf_to_softbody(Object *ob) { Curve *cu = ob->data; SoftBody *sb; @@ -2993,7 +2993,7 @@ static void curve_surf_to_softbody(Scene *scene, Object *ob) } /* renew ends with ob->soft with points and edges, also checks & makes ob->soft */ - renew_softbody(scene, ob, totvert, totspring); + renew_softbody(ob, totvert, totspring); sb = ob->soft; /* can be created in renew_softbody() */ /* set vars now */ @@ -3117,7 +3117,7 @@ static void sb_new_scratch(SoftBody *sb) /* ************ Object level, exported functions *************** */ /* allocates and initializes general main data */ -SoftBody *sbNew(Scene *scene) +SoftBody *sbNew(void) { SoftBody *sb; @@ -3140,12 +3140,6 @@ SoftBody *sbNew(Scene *scene) /*todo backward file compat should copy inspring to inpush while reading old files*/ sb->inpush = 0.5f; - sb->interval = 10; - if (scene != NULL) { - sb->sfra = scene->r.sfra; - sb->efra = scene->r.efra; - } - sb->colball = 0.49f; sb->balldamp = 0.50f; sb->ballstiff = 1.0f; @@ -3577,17 +3571,17 @@ void sbObjectStep(struct Depsgraph *depsgraph, switch (ob->type) { case OB_MESH: - mesh_to_softbody(scene, ob); + mesh_to_softbody(ob); break; case OB_LATTICE: - lattice_to_softbody(scene, ob); + lattice_to_softbody(ob); break; case OB_CURVE: case OB_SURF: - curve_surf_to_softbody(scene, ob); + curve_surf_to_softbody(ob); break; default: - renew_softbody(scene, ob, numVerts, 0); + renew_softbody(ob, numVerts, 0); break; } diff --git a/source/blender/blenkernel/intern/spline_base.cc b/source/blender/blenkernel/intern/spline_base.cc index 31c2178fa3f..11620a30948 100644 --- a/source/blender/blenkernel/intern/spline_base.cc +++ b/source/blender/blenkernel/intern/spline_base.cc @@ -16,11 +16,13 @@ #include "BLI_array.hh" #include "BLI_span.hh" - -#include "FN_generic_virtual_array.hh" +#include "BLI_timeit.hh" #include "BKE_spline.hh" +#include "FN_generic_virtual_array.hh" + +using blender::Array; using blender::float3; using blender::IndexRange; using blender::MutableSpan; @@ -59,7 +61,8 @@ int Spline::evaluated_edges_size() const float Spline::length() const { - return this->evaluated_lengths().last(); + Span<float> lengths = this->evaluated_lengths(); + return (lengths.size() == 0) ? 0 : this->evaluated_lengths().last(); } int Spline::segments_size() const @@ -265,6 +268,72 @@ Spline::LookupResult Spline::lookup_evaluated_length(const float length) const return LookupResult{index, next_index, factor}; } +/** + * Return an array of evenly spaced samples along the length of the spline. The samples are indices + * and factors to the next index encoded in floats. The logic for converting from the float values + * to interpolation data is in #lookup_data_from_index_factor. + */ +Array<float> Spline::sample_uniform_index_factors(const int samples_size) const +{ + const Span<float> lengths = this->evaluated_lengths(); + + BLI_assert(samples_size > 0); + Array<float> samples(samples_size); + + samples[0] = 0.0f; + if (samples_size == 1) { + return samples; + } + + const float total_length = this->length(); + const float sample_length = total_length / (samples_size - (is_cyclic_ ? 0 : 1)); + + /* Store the length at the previous evaluated point in a variable so it can + * start out at zero (the lengths array doesn't contain 0 for the first point). */ + float prev_length = 0.0f; + int i_sample = 1; + for (const int i_evaluated : IndexRange(this->evaluated_edges_size())) { + const float length = lengths[i_evaluated]; + + /* Add every sample that fits in this evaluated edge. */ + while ((sample_length * i_sample) < length && i_sample < samples_size) { + const float factor = (sample_length * i_sample - prev_length) / (length - prev_length); + samples[i_sample] = i_evaluated + factor; + i_sample++; + } + + prev_length = length; + } + + if (!is_cyclic_) { + /* In rare cases this can prevent overflow of the stored index. */ + samples.last() = lengths.size(); + } + + return samples; +} + +Spline::LookupResult Spline::lookup_data_from_index_factor(const float index_factor) const +{ + const int points_len = this->evaluated_points_size(); + + if (is_cyclic_) { + if (index_factor < points_len) { + const int index = std::floor(index_factor); + const int next_index = (index < points_len - 1) ? index + 1 : 0; + return LookupResult{index, next_index, index_factor - index}; + } + return LookupResult{points_len - 1, 0, 1.0f}; + } + + if (index_factor < points_len - 1) { + const int index = std::floor(index_factor); + const int next_index = index + 1; + return LookupResult{index, next_index, index_factor - index}; + } + return LookupResult{points_len - 2, points_len - 1, 1.0f}; +} + void Spline::bounds_min_max(float3 &min, float3 &max, const bool use_evaluated) const { Span<float3> positions = use_evaluated ? this->evaluated_positions() : this->positions(); diff --git a/source/blender/blenkernel/intern/spline_bezier.cc b/source/blender/blenkernel/intern/spline_bezier.cc index f62718011da..4be3ba8576e 100644 --- a/source/blender/blenkernel/intern/spline_bezier.cc +++ b/source/blender/blenkernel/intern/spline_bezier.cc @@ -55,6 +55,9 @@ void BezierSpline::set_resolution(const int value) this->mark_cache_invalid(); } +/** + * \warning Call #reallocate on the spline's attributes after adding all points. + */ void BezierSpline::add_point(const float3 position, const HandleType handle_type_start, const float3 handle_position_start, @@ -73,6 +76,19 @@ void BezierSpline::add_point(const float3 position, this->mark_cache_invalid(); } +void BezierSpline::resize(const int size) +{ + handle_types_left_.resize(size); + handle_positions_left_.resize(size); + positions_.resize(size); + handle_types_right_.resize(size); + handle_positions_right_.resize(size); + radii_.resize(size); + tilts_.resize(size); + this->mark_cache_invalid(); + attributes.reallocate(size); +} + MutableSpan<float3> BezierSpline::positions() { return positions_; @@ -107,10 +123,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 @@ -123,13 +141,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()) { @@ -167,9 +266,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; @@ -183,19 +286,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) ? 0 : resolution_); - } - - return last_offset + 1; + BLI_assert(this->size() > 0); + return this->control_point_offsets().last(); } /** @@ -266,8 +363,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 { @@ -281,12 +382,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_; } @@ -308,7 +409,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; @@ -319,7 +420,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; @@ -377,25 +478,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, @@ -410,7 +512,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 @@ -458,6 +560,10 @@ blender::fn::GVArrayPtr BezierSpline::interpolate_to_evaluated_points( { BLI_assert(source_data.size() == this->size()); + if (source_data.is_single()) { + return source_data.shallow_copy(); + } + const int eval_size = this->evaluated_points_size(); if (eval_size == 1) { return source_data.shallow_copy(); diff --git a/source/blender/blenkernel/intern/spline_nurbs.cc b/source/blender/blenkernel/intern/spline_nurbs.cc index 37d1232cfeb..ae691d26cdb 100644 --- a/source/blender/blenkernel/intern/spline_nurbs.cc +++ b/source/blender/blenkernel/intern/spline_nurbs.cc @@ -65,6 +65,9 @@ void NURBSpline::set_order(const uint8_t value) this->mark_cache_invalid(); } +/** + * \warning Call #reallocate on the spline's attributes after adding all points. + */ void NURBSpline::add_point(const float3 position, const float radius, const float tilt, @@ -78,6 +81,16 @@ void NURBSpline::add_point(const float3 position, this->mark_cache_invalid(); } +void NURBSpline::resize(const int size) +{ + positions_.resize(size); + radii_.resize(size); + tilts_.resize(size); + weights_.resize(size); + this->mark_cache_invalid(); + attributes.reallocate(size); +} + MutableSpan<float3> NURBSpline::positions() { return positions_; @@ -250,7 +263,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; @@ -376,6 +389,10 @@ blender::fn::GVArrayPtr NURBSpline::interpolate_to_evaluated_points( { BLI_assert(source_data.size() == this->size()); + if (source_data.is_single()) { + return source_data.shallow_copy(); + } + this->calculate_basis_cache(); Span<BasisCache> weights(basis_cache_); diff --git a/source/blender/blenkernel/intern/spline_poly.cc b/source/blender/blenkernel/intern/spline_poly.cc index 09c578c0503..5c33b0052fc 100644 --- a/source/blender/blenkernel/intern/spline_poly.cc +++ b/source/blender/blenkernel/intern/spline_poly.cc @@ -36,6 +36,9 @@ int PolySpline::size() const return size; } +/** + * \warning Call #reallocate on the spline's attributes after adding all points. + */ void PolySpline::add_point(const float3 position, const float radius, const float tilt) { positions_.append(position); @@ -44,6 +47,15 @@ void PolySpline::add_point(const float3 position, const float radius, const floa this->mark_cache_invalid(); } +void PolySpline::resize(const int size) +{ + positions_.resize(size); + radii_.resize(size); + tilts_.resize(size); + this->mark_cache_invalid(); + attributes.reallocate(size); +} + MutableSpan<float3> PolySpline::positions() { return positions_; diff --git a/source/blender/blenkernel/intern/writeffmpeg.c b/source/blender/blenkernel/intern/writeffmpeg.c index 7fc9e8cc0ef..39f65d76e3c 100644 --- a/source/blender/blenkernel/intern/writeffmpeg.c +++ b/source/blender/blenkernel/intern/writeffmpeg.c @@ -56,6 +56,7 @@ # include <libavcodec/avcodec.h> # include <libavformat/avformat.h> # include <libavutil/imgutils.h> +# include <libavutil/opt.h> # include <libavutil/rational.h> # include <libavutil/samplefmt.h> # include <libswscale/swscale.h> @@ -80,6 +81,8 @@ typedef struct FFMpegContext { int ffmpeg_preset; /* see eFFMpegPreset */ AVFormatContext *outfile; + AVCodecContext *video_codec; + AVCodecContext *audio_codec; AVStream *video_stream; AVStream *audio_stream; AVFrame *current_frame; /* Image frame in output pixel format. */ @@ -91,10 +94,6 @@ typedef struct FFMpegContext { uint8_t *audio_input_buffer; uint8_t *audio_deinterleave_buffer; int audio_input_samples; -# ifndef FFMPEG_HAVE_ENCODE_AUDIO2 - uint8_t *audio_output_buffer; - int audio_outbuf_size; -# endif double audio_time; bool audio_deinterleave; int audio_sample_size; @@ -141,32 +140,22 @@ static int request_float_audio_buffer(int codec_id) } # ifdef WITH_AUDASPACE + static int write_audio_frame(FFMpegContext *context) { - AVCodecContext *c = NULL; - AVPacket pkt; AVFrame *frame = NULL; - int got_output = 0; - - c = context->audio_stream->codec; - - av_init_packet(&pkt); - pkt.size = 0; - pkt.data = NULL; + AVCodecContext *c = context->audio_codec; AUD_Device_read( context->audio_mixdown_device, context->audio_input_buffer, context->audio_input_samples); context->audio_time += (double)context->audio_input_samples / (double)c->sample_rate; -# ifdef FFMPEG_HAVE_ENCODE_AUDIO2 frame = av_frame_alloc(); - av_frame_unref(frame); frame->pts = context->audio_time / av_q2d(c->time_base); frame->nb_samples = context->audio_input_samples; frame->format = c->sample_fmt; -# ifdef FFMPEG_HAVE_FRAME_CHANNEL_LAYOUT + frame->channels = c->channels; frame->channel_layout = c->channel_layout; -# endif if (context->audio_deinterleave) { int channel, i; @@ -194,61 +183,49 @@ static int write_audio_frame(FFMpegContext *context) context->audio_input_samples * c->channels * context->audio_sample_size, 1); - if (avcodec_encode_audio2(c, &pkt, frame, &got_output) < 0) { - // XXX error("Error writing audio packet"); - return -1; - } + int success = 0; - if (!got_output) { - av_frame_free(&frame); - return 0; + int ret = avcodec_send_frame(c, frame); + if (ret < 0) { + /* Can't send frame to encoder. This shouldn't happen. */ + fprintf(stderr, "Can't send audio frame: %s\n", av_err2str(ret)); + success = -1; } -# else - pkt.size = avcodec_encode_audio(c, - context->audio_output_buffer, - context->audio_outbuf_size, - (short *)context->audio_input_buffer); - if (pkt.size < 0) { - // XXX error("Error writing audio packet"); - return -1; - } + AVPacket *pkt = av_packet_alloc(); - pkt.data = context->audio_output_buffer; - got_output = 1; -# endif + while (ret >= 0) { - if (got_output) { - if (pkt.pts != AV_NOPTS_VALUE) { - pkt.pts = av_rescale_q(pkt.pts, c->time_base, context->audio_stream->time_base); + ret = avcodec_receive_packet(c, pkt); + if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { + break; } - if (pkt.dts != AV_NOPTS_VALUE) { - pkt.dts = av_rescale_q(pkt.dts, c->time_base, context->audio_stream->time_base); + if (ret < 0) { + fprintf(stderr, "Error encoding audio frame: %s\n", av_err2str(ret)); + success = -1; } - if (pkt.duration > 0) { - pkt.duration = av_rescale_q(pkt.duration, c->time_base, context->audio_stream->time_base); + + av_packet_rescale_ts(pkt, c->time_base, context->audio_stream->time_base); + if (pkt->duration > 0) { + pkt->duration = av_rescale_q(pkt->duration, c->time_base, context->audio_stream->time_base); } - pkt.stream_index = context->audio_stream->index; + pkt->stream_index = context->audio_stream->index; - pkt.flags |= AV_PKT_FLAG_KEY; + pkt->flags |= AV_PKT_FLAG_KEY; - if (av_interleaved_write_frame(context->outfile, &pkt) != 0) { - fprintf(stderr, "Error writing audio packet!\n"); - if (frame) { - av_frame_free(&frame); - } - return -1; + int write_ret = av_interleaved_write_frame(context->outfile, pkt); + if (write_ret != 0) { + fprintf(stderr, "Error writing audio packet: %s\n", av_err2str(write_ret)); + success = -1; + break; } - - av_free_packet(&pkt); } - if (frame) { - av_frame_free(&frame); - } + av_packet_free(&pkt); + av_frame_free(&frame); - return 0; + return success; } # endif /* #ifdef WITH_AUDASPACE */ @@ -264,14 +241,15 @@ static AVFrame *alloc_picture(int pix_fmt, int width, int height) if (!f) { return NULL; } - size = avpicture_get_size(pix_fmt, width, height); + size = av_image_get_buffer_size(pix_fmt, width, height, 1); /* allocate the actual picture buffer */ buf = MEM_mallocN(size, "AVFrame buffer"); if (!buf) { free(f); return NULL; } - avpicture_fill((AVPicture *)f, buf, pix_fmt, width, height); + + av_image_fill_arrays(f->data, f->linesize, buf, pix_fmt, width, height, 1); f->format = pix_fmt; f->width = width; f->height = height; @@ -341,58 +319,57 @@ static const char **get_file_extensions(int format) } /* Write a frame to the output file */ -static int write_video_frame( - FFMpegContext *context, const RenderData *rd, int cfra, AVFrame *frame, ReportList *reports) +static int write_video_frame(FFMpegContext *context, int cfra, AVFrame *frame, ReportList *reports) { - int got_output; int ret, success = 1; - AVCodecContext *c = context->video_stream->codec; - AVPacket packet = {0}; + AVPacket *packet = av_packet_alloc(); - av_init_packet(&packet); + AVCodecContext *c = context->video_codec; frame->pts = cfra; - ret = avcodec_encode_video2(c, &packet, frame, &got_output); + ret = avcodec_send_frame(c, frame); + if (ret < 0) { + /* Can't send frame to encoder. This shouldn't happen. */ + fprintf(stderr, "Can't send video frame: %s\n", av_err2str(ret)); + success = -1; + } - if (ret >= 0 && got_output) { - if (packet.pts != AV_NOPTS_VALUE) { - packet.pts = av_rescale_q(packet.pts, c->time_base, context->video_stream->time_base); - PRINT("Video Frame PTS: %d\n", (int)packet.pts); - } - else { - PRINT("Video Frame PTS: not set\n"); - } - if (packet.dts != AV_NOPTS_VALUE) { - packet.dts = av_rescale_q(packet.dts, c->time_base, context->video_stream->time_base); - PRINT("Video Frame DTS: %d\n", (int)packet.dts); + while (ret >= 0) { + ret = avcodec_receive_packet(c, packet); + + if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { + /* No more packets available. */ + break; } - else { - PRINT("Video Frame DTS: not set\n"); + if (ret < 0) { + fprintf(stderr, "Error encoding frame: %s\n", av_err2str(ret)); + break; } - packet.stream_index = context->video_stream->index; - ret = av_interleaved_write_frame(context->outfile, &packet); - success = (ret == 0); - } - else if (ret < 0) { - success = 0; + packet->stream_index = context->video_stream->index; + av_packet_rescale_ts(packet, c->time_base, context->video_stream->time_base); + if (av_interleaved_write_frame(context->outfile, packet) != 0) { + success = -1; + break; + } } if (!success) { BKE_report(reports, RPT_ERROR, "Error writing frame"); + PRINT("Error writing frame: %s\n", av_err2str(ret)); } + av_packet_free(&packet); + return success; } /* read and encode a frame of audio from the buffer */ -static AVFrame *generate_video_frame(FFMpegContext *context, - const uint8_t *pixels, - ReportList *reports) +static AVFrame *generate_video_frame(FFMpegContext *context, const uint8_t *pixels) { - AVCodecContext *c = context->video_stream->codec; - int height = c->height; + AVCodecParameters *codec = context->video_stream->codecpar; + int height = codec->height; AVFrame *rgb_frame; if (context->img_convert_frame != NULL) { @@ -437,7 +414,7 @@ static AVFrame *generate_video_frame(FFMpegContext *context, (const uint8_t *const *)rgb_frame->data, rgb_frame->linesize, 0, - c->height, + codec->height, context->current_frame->data, context->current_frame->linesize); } @@ -445,9 +422,7 @@ static AVFrame *generate_video_frame(FFMpegContext *context, return context->current_frame; } -static void set_ffmpeg_property_option(AVCodecContext *c, - IDProperty *prop, - AVDictionary **dictionary) +static void set_ffmpeg_property_option(IDProperty *prop, AVDictionary **dictionary) { char name[128]; char *param; @@ -535,7 +510,7 @@ static void set_ffmpeg_properties(RenderData *rd, for (curr = prop->data.group.first; curr; curr = curr->next) { if (ffmpeg_proprty_valid(c, prop_name, curr)) { - set_ffmpeg_property_option(c, curr, dictionary); + set_ffmpeg_property_option(curr, dictionary); } } } @@ -552,7 +527,6 @@ static AVStream *alloc_video_stream(FFMpegContext *context, int error_size) { AVStream *st; - AVCodecContext *c; AVCodec *codec; AVDictionary *opts = NULL; @@ -566,7 +540,8 @@ static AVStream *alloc_video_stream(FFMpegContext *context, /* Set up the codec context */ - c = st->codec; + context->video_codec = avcodec_alloc_context3(NULL); + AVCodecContext *c = context->video_codec; c->codec_id = codec_id; c->codec_type = AVMEDIA_TYPE_VIDEO; @@ -649,11 +624,9 @@ static AVStream *alloc_video_stream(FFMpegContext *context, } } - /* Deprecated and not doing anything since July 2015, deleted in recent ffmpeg */ - // c->me_method = ME_EPZS; - codec = avcodec_find_encoder(c->codec_id); if (!codec) { + avcodec_free_context(&c); return NULL; } @@ -713,7 +686,7 @@ static AVStream *alloc_video_stream(FFMpegContext *context, if ((of->oformat->flags & AVFMT_GLOBALHEADER)) { PRINT("Using global header\n"); - c->flags |= CODEC_FLAG_GLOBAL_HEADER; + c->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; } /* xasp & yasp got float lately... */ @@ -741,6 +714,7 @@ static AVStream *alloc_video_stream(FFMpegContext *context, if (avcodec_open2(c, codec, &opts) < 0) { BLI_strncpy(error, IMB_ffmpeg_last_error(), error_size); av_dict_free(&opts); + avcodec_free_context(&c); return NULL; } av_dict_free(&opts); @@ -768,6 +742,8 @@ static AVStream *alloc_video_stream(FFMpegContext *context, NULL); } + avcodec_parameters_from_context(st->codecpar, c); + return st; } @@ -779,7 +755,6 @@ static AVStream *alloc_audio_stream(FFMpegContext *context, int error_size) { AVStream *st; - AVCodecContext *c; AVCodec *codec; AVDictionary *opts = NULL; @@ -791,7 +766,8 @@ static AVStream *alloc_audio_stream(FFMpegContext *context, } st->id = 1; - c = st->codec; + context->audio_codec = avcodec_alloc_context3(NULL); + AVCodecContext *c = context->audio_codec; c->thread_count = BLI_system_thread_count(); c->thread_type = FF_THREAD_SLICE; @@ -803,7 +779,6 @@ static AVStream *alloc_audio_stream(FFMpegContext *context, c->sample_fmt = AV_SAMPLE_FMT_S16; c->channels = rd->ffcodecdata.audio_channels; -# ifdef FFMPEG_HAVE_FRAME_CHANNEL_LAYOUT switch (rd->ffcodecdata.audio_channels) { case FFM_CHANNELS_MONO: c->channel_layout = AV_CH_LAYOUT_MONO; @@ -821,7 +796,6 @@ static AVStream *alloc_audio_stream(FFMpegContext *context, c->channel_layout = AV_CH_LAYOUT_7POINT1; break; } -# endif if (request_float_audio_buffer(codec_id)) { /* mainly for AAC codec which is experimental */ @@ -832,6 +806,7 @@ static AVStream *alloc_audio_stream(FFMpegContext *context, codec = avcodec_find_encoder(c->codec_id); if (!codec) { // XXX error("Couldn't find a valid audio codec"); + avcodec_free_context(&c); return NULL; } @@ -843,13 +818,13 @@ static AVStream *alloc_audio_stream(FFMpegContext *context, * Float samples in particular are not always supported. */ const enum AVSampleFormat *p = codec->sample_fmts; for (; *p != -1; p++) { - if (*p == st->codec->sample_fmt) { + if (*p == c->sample_fmt) { break; } } if (*p == -1) { /* sample format incompatible with codec. Defaulting to a format known to work */ - st->codec->sample_fmt = codec->sample_fmts[0]; + c->sample_fmt = codec->sample_fmts[0]; } } @@ -858,18 +833,18 @@ static AVStream *alloc_audio_stream(FFMpegContext *context, int best = 0; int best_dist = INT_MAX; for (; *p; p++) { - int dist = abs(st->codec->sample_rate - *p); + int dist = abs(c->sample_rate - *p); if (dist < best_dist) { best_dist = dist; best = *p; } } /* best is the closest supported sample rate (same as selected if best_dist == 0) */ - st->codec->sample_rate = best; + c->sample_rate = best; } if (of->oformat->flags & AVFMT_GLOBALHEADER) { - c->flags |= CODEC_FLAG_GLOBAL_HEADER; + c->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; } set_ffmpeg_properties(rd, c, "audio", &opts); @@ -878,32 +853,25 @@ static AVStream *alloc_audio_stream(FFMpegContext *context, // XXX error("Couldn't initialize audio codec"); BLI_strncpy(error, IMB_ffmpeg_last_error(), error_size); av_dict_free(&opts); + avcodec_free_context(&c); return NULL; } av_dict_free(&opts); /* need to prevent floating point exception when using vorbis audio codec, * initialize this value in the same way as it's done in FFmpeg itself (sergey) */ - st->codec->time_base.num = 1; - st->codec->time_base.den = st->codec->sample_rate; - -# ifndef FFMPEG_HAVE_ENCODE_AUDIO2 - context->audio_outbuf_size = FF_MIN_BUFFER_SIZE; -# endif + c->time_base.num = 1; + c->time_base.den = c->sample_rate; if (c->frame_size == 0) { /* Used to be if ((c->codec_id >= CODEC_ID_PCM_S16LE) && (c->codec_id <= CODEC_ID_PCM_DVD)) * not sure if that is needed anymore, so let's try out if there are any * complaints regarding some FFmpeg versions users might have. */ - context->audio_input_samples = FF_MIN_BUFFER_SIZE * 8 / c->bits_per_coded_sample / c->channels; + context->audio_input_samples = AV_INPUT_BUFFER_MIN_SIZE * 8 / c->bits_per_coded_sample / + c->channels; } else { context->audio_input_samples = c->frame_size; -# ifndef FFMPEG_HAVE_ENCODE_AUDIO2 - if (c->frame_size * c->channels * sizeof(int16_t) * 4 > context->audio_outbuf_size) { - context->audio_outbuf_size = c->frame_size * c->channels * sizeof(int16_t) * 4; - } -# endif } context->audio_deinterleave = av_sample_fmt_is_planar(c->sample_fmt); @@ -912,10 +880,6 @@ static AVStream *alloc_audio_stream(FFMpegContext *context, context->audio_input_buffer = (uint8_t *)av_malloc(context->audio_input_samples * c->channels * context->audio_sample_size); -# ifndef FFMPEG_HAVE_ENCODE_AUDIO2 - context->audio_output_buffer = (uint8_t *)av_malloc(context->audio_outbuf_size); -# endif - if (context->audio_deinterleave) { context->audio_deinterleave_buffer = (uint8_t *)av_malloc( context->audio_input_samples * c->channels * context->audio_sample_size); @@ -923,6 +887,8 @@ static AVStream *alloc_audio_stream(FFMpegContext *context, context->audio_time = 0.0f; + avcodec_parameters_from_context(st->codecpar, c); + return st; } /* essential functions -- start, append, end */ @@ -948,7 +914,7 @@ static void ffmpeg_dict_set_float(AVDictionary **dict, const char *key, float va static void ffmpeg_add_metadata_callback(void *data, const char *propname, char *propvalue, - int len) + int UNUSED(len)) { AVDictionary **metadata = (AVDictionary **)data; av_dict_set(metadata, propname, propvalue, 0); @@ -1039,7 +1005,7 @@ static int start_ffmpeg_impl(FFMpegContext *context, fmt->audio_codec = context->ffmpeg_audio_codec; - BLI_strncpy(of->filename, name, sizeof(of->filename)); + of->url = av_strdup(name); /* set the codec to the user's selection */ switch (context->ffmpeg_type) { case FFMPEG_AVI: @@ -1104,9 +1070,11 @@ static int start_ffmpeg_impl(FFMpegContext *context, if (!context->video_stream) { if (error[0]) { BKE_report(reports, RPT_ERROR, error); + PRINT("Video stream error: %s\n", error); } else { BKE_report(reports, RPT_ERROR, "Error initializing video stream"); + PRINT("Error initializing video stream"); } goto fail; } @@ -1118,9 +1086,11 @@ static int start_ffmpeg_impl(FFMpegContext *context, if (!context->audio_stream) { if (error[0]) { BKE_report(reports, RPT_ERROR, error); + PRINT("Audio stream error: %s\n", error); } else { BKE_report(reports, RPT_ERROR, "Error initializing audio stream"); + PRINT("Error initializing audio stream"); } goto fail; } @@ -1128,6 +1098,7 @@ static int start_ffmpeg_impl(FFMpegContext *context, if (!(fmt->flags & AVFMT_NOFILE)) { if (avio_open(&of->pb, name, AVIO_FLAG_WRITE) < 0) { BKE_report(reports, RPT_ERROR, "Could not open file for writing"); + PRINT("Could not open file for writing\n"); goto fail; } } @@ -1137,10 +1108,12 @@ static int start_ffmpeg_impl(FFMpegContext *context, &of->metadata, context->stamp_data, ffmpeg_add_metadata_callback, false); } - if (avformat_write_header(of, NULL) < 0) { + int ret = avformat_write_header(of, NULL); + if (ret < 0) { BKE_report(reports, RPT_ERROR, "Could not initialize streams, probably unsupported codec combination"); + PRINT("Could not write media header: %s\n", av_err2str(ret)); goto fail; } @@ -1155,13 +1128,11 @@ fail: avio_close(of->pb); } - if (context->video_stream && context->video_stream->codec) { - avcodec_close(context->video_stream->codec); + if (context->video_stream) { context->video_stream = NULL; } - if (context->audio_stream && context->audio_stream->codec) { - avcodec_close(context->audio_stream->codec); + if (context->audio_stream) { context->audio_stream = NULL; } @@ -1189,46 +1160,36 @@ fail: */ static void flush_ffmpeg(FFMpegContext *context) { - int ret = 0; + AVCodecContext *c = context->video_codec; + AVPacket *packet = av_packet_alloc(); - AVCodecContext *c = context->video_stream->codec; - /* get the delayed frames */ - while (1) { - int got_output; - AVPacket packet = {0}; - av_init_packet(&packet); + avcodec_send_frame(c, NULL); - ret = avcodec_encode_video2(c, &packet, NULL, &got_output); - if (ret < 0) { - fprintf(stderr, "Error encoding delayed frame %d\n", ret); + /* Get the packets frames. */ + int ret = 1; + while (ret >= 0) { + ret = avcodec_receive_packet(c, packet); + + if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { + /* No more packets to flush. */ break; } - if (!got_output) { + if (ret < 0) { + fprintf(stderr, "Error encoding delayed frame: %s\n", av_err2str(ret)); break; } - if (packet.pts != AV_NOPTS_VALUE) { - packet.pts = av_rescale_q(packet.pts, c->time_base, context->video_stream->time_base); - PRINT("Video Frame PTS: %d\n", (int)packet.pts); - } - else { - PRINT("Video Frame PTS: not set\n"); - } - if (packet.dts != AV_NOPTS_VALUE) { - packet.dts = av_rescale_q(packet.dts, c->time_base, context->video_stream->time_base); - PRINT("Video Frame DTS: %d\n", (int)packet.dts); - } - else { - PRINT("Video Frame DTS: not set\n"); - } - packet.stream_index = context->video_stream->index; - ret = av_interleaved_write_frame(context->outfile, &packet); - if (ret != 0) { - fprintf(stderr, "Error writing delayed frame %d\n", ret); + packet->stream_index = context->video_stream->index; + av_packet_rescale_ts(packet, c->time_base, context->video_stream->time_base); + + int write_ret = av_interleaved_write_frame(context->outfile, packet); + if (write_ret != 0) { + fprintf(stderr, "Error writing delayed frame: %s\n", av_err2str(write_ret)); break; } } - avcodec_flush_buffers(context->video_stream->codec); + + av_packet_free(&packet); } /* ********************************************************************** @@ -1326,7 +1287,8 @@ int BKE_ffmpeg_start(void *context_v, success = start_ffmpeg_impl(context, rd, rectx, recty, suffix, reports); # ifdef WITH_AUDASPACE if (context->audio_stream) { - AVCodecContext *c = context->audio_stream->codec; + AVCodecContext *c = context->audio_codec; + AUD_DeviceSpecs specs; specs.channels = c->channels; @@ -1353,10 +1315,6 @@ int BKE_ffmpeg_start(void *context_v, specs.rate = rd->ffcodecdata.audio_mixrate; context->audio_mixdown_device = BKE_sound_mixdown( scene, specs, preview ? rd->psfra : rd->sfra, rd->ffcodecdata.audio_volume); -# ifdef FFMPEG_CODEC_TIME_BASE - c->time_base.den = specs.rate; - c->time_base.num = 1; -# endif } # endif return success; @@ -1397,8 +1355,8 @@ int BKE_ffmpeg_append(void *context_v, // write_audio_frames(frame / (((double)rd->frs_sec) / rd->frs_sec_base)); if (context->video_stream) { - avframe = generate_video_frame(context, (unsigned char *)pixels, reports); - success = (avframe && write_video_frame(context, rd, frame - start_frame, avframe, reports)); + avframe = generate_video_frame(context, (unsigned char *)pixels); + success = (avframe && write_video_frame(context, frame - start_frame, avframe, reports)); if (context->ffmpeg_autosplit) { if (avio_tell(context->outfile->pb) > FFMPEG_AUTOSPLIT_SIZE) { @@ -1427,9 +1385,11 @@ static void end_ffmpeg_impl(FFMpegContext *context, int is_autosplit) context->audio_mixdown_device = NULL; } } +# else + UNUSED_VARS(is_autosplit); # endif - if (context->video_stream && context->video_stream->codec) { + if (context->video_stream) { PRINT("Flushing delayed frames...\n"); flush_ffmpeg(context); } @@ -1440,14 +1400,12 @@ static void end_ffmpeg_impl(FFMpegContext *context, int is_autosplit) /* Close the video codec */ - if (context->video_stream != NULL && context->video_stream->codec != NULL) { - avcodec_close(context->video_stream->codec); + if (context->video_stream != NULL) { PRINT("zero video stream %p\n", context->video_stream); context->video_stream = NULL; } - if (context->audio_stream != NULL && context->audio_stream->codec != NULL) { - avcodec_close(context->audio_stream->codec); + if (context->audio_stream != NULL) { context->audio_stream = NULL; } @@ -1466,6 +1424,16 @@ static void end_ffmpeg_impl(FFMpegContext *context, int is_autosplit) avio_close(context->outfile->pb); } } + + if (context->video_codec != NULL) { + avcodec_free_context(&context->video_codec); + context->video_codec = NULL; + } + if (context->audio_codec != NULL) { + avcodec_free_context(&context->audio_codec); + context->audio_codec = NULL; + } + if (context->outfile != NULL) { avformat_free_context(context->outfile); context->outfile = NULL; @@ -1474,12 +1442,6 @@ static void end_ffmpeg_impl(FFMpegContext *context, int is_autosplit) av_free(context->audio_input_buffer); context->audio_input_buffer = NULL; } -# ifndef FFMPEG_HAVE_ENCODE_AUDIO2 - if (context->audio_output_buffer != NULL) { - av_free(context->audio_output_buffer); - context->audio_output_buffer = NULL; - } -# endif if (context->audio_deinterleave_buffer != NULL) { av_free(context->audio_deinterleave_buffer); @@ -1559,12 +1521,12 @@ static IDProperty *BKE_ffmpeg_property_add(RenderData *rd, switch (o->type) { case AV_OPT_TYPE_INT: case AV_OPT_TYPE_INT64: - val.i = FFMPEG_DEF_OPT_VAL_INT(o); + val.i = o->default_val.i64; idp_type = IDP_INT; break; case AV_OPT_TYPE_DOUBLE: case AV_OPT_TYPE_FLOAT: - val.f = FFMPEG_DEF_OPT_VAL_DOUBLE(o); + val.f = o->default_val.dbl; idp_type = IDP_FLOAT; break; case AV_OPT_TYPE_STRING: @@ -1706,16 +1668,9 @@ static void ffmpeg_set_expert_options(RenderData *rd) BKE_ffmpeg_property_add_string(rd, "video", "trellis:0"); BKE_ffmpeg_property_add_string(rd, "video", "weightb:1"); -# ifdef FFMPEG_HAVE_DEPRECATED_FLAGS2 - BKE_ffmpeg_property_add_string(rd, "video", "flags2:dct8x8"); - BKE_ffmpeg_property_add_string(rd, "video", "directpred:3"); - BKE_ffmpeg_property_add_string(rd, "video", "flags2:fastpskip"); - BKE_ffmpeg_property_add_string(rd, "video", "flags2:wpred"); -# else BKE_ffmpeg_property_add_string(rd, "video", "8x8dct:1"); BKE_ffmpeg_property_add_string(rd, "video", "fast-pskip:1"); BKE_ffmpeg_property_add_string(rd, "video", "wpredp:2"); -# endif } else if (codec_id == AV_CODEC_ID_DNXHD) { if (rd->ffcodecdata.flags & FFMPEG_LOSSLESS_OUTPUT) { @@ -1870,14 +1825,12 @@ bool BKE_ffmpeg_alpha_channel_is_supported(const RenderData *rd) { int codec = rd->ffcodecdata.codec; -# ifdef FFMPEG_FFV1_ALPHA_SUPPORTED - /* Visual Studio 2019 doesn't like #ifdef within ELEM(). */ - if (codec == AV_CODEC_ID_FFV1) { - return true; - } -# endif - - return ELEM(codec, AV_CODEC_ID_QTRLE, AV_CODEC_ID_PNG, AV_CODEC_ID_VP9, AV_CODEC_ID_HUFFYUV); + return ELEM(codec, + AV_CODEC_ID_FFV1, + AV_CODEC_ID_QTRLE, + AV_CODEC_ID_PNG, + AV_CODEC_ID_VP9, + AV_CODEC_ID_HUFFYUV); } void *BKE_ffmpeg_context_create(void) diff --git a/source/blender/blenkernel/nla_private.h b/source/blender/blenkernel/nla_private.h index 706bcac4f17..71b5a74ddf7 100644 --- a/source/blender/blenkernel/nla_private.h +++ b/source/blender/blenkernel/nla_private.h @@ -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 diff --git a/source/blender/blenlib/BLI_enumerable_thread_specific.hh b/source/blender/blenlib/BLI_enumerable_thread_specific.hh new file mode 100644 index 00000000000..89be4cad848 --- /dev/null +++ b/source/blender/blenlib/BLI_enumerable_thread_specific.hh @@ -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 diff --git a/source/blender/blenlib/BLI_fileops.h b/source/blender/blenlib/BLI_fileops.h index df80e720363..bf18dd1070e 100644 --- a/source/blender/blenlib/BLI_fileops.h +++ b/source/blender/blenlib/BLI_fileops.h @@ -87,7 +87,7 @@ typedef enum eFileAttributes { FILE_ATTR_RESTRICTED = 1 << 6, /* Protected by OS. */ FILE_ATTR_TEMPORARY = 1 << 7, /* Used for temporary storage. */ FILE_ATTR_SPARSE_FILE = 1 << 8, /* Sparse File. */ - FILE_ATTR_OFFLINE = 1 << 9, /* Data is not immediately available. */ + FILE_ATTR_OFFLINE = 1 << 9, /* Contents available after a short delay. */ FILE_ATTR_ALIAS = 1 << 10, /* Mac Alias or Windows LNK. File-based redirection. */ FILE_ATTR_REPARSE_POINT = 1 << 11, /* File has associated re-parse point. */ FILE_ATTR_SYMLINK = 1 << 12, /* Reference to another file. */ diff --git a/source/blender/blenlib/BLI_hash.hh b/source/blender/blenlib/BLI_hash.hh index 4022c2baa1f..fbed321534c 100644 --- a/source/blender/blenlib/BLI_hash.hh +++ b/source/blender/blenlib/BLI_hash.hh @@ -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); + } }; /** diff --git a/source/blender/blenlib/BLI_linear_allocator.hh b/source/blender/blenlib/BLI_linear_allocator.hh index bd883c1de16..87361d412cb 100644 --- a/source/blender/blenlib/BLI_linear_allocator.hh +++ b/source/blender/blenlib/BLI_linear_allocator.hh @@ -149,6 +149,21 @@ template<typename Allocator = GuardedAllocator> class LinearAllocator : NonCopya } /** + * 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. */ template<typename T> MutableSpan<T> construct_array_copy(Span<T> src) diff --git a/source/blender/blenlib/BLI_map.hh b/source/blender/blenlib/BLI_map.hh index 95afbfc2ec6..4d254960f34 100644 --- a/source/blender/blenlib/BLI_map.hh +++ b/source/blender/blenlib/BLI_map.hh @@ -605,6 +605,37 @@ class Map { } /** + * 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. */ diff --git a/source/blender/blenlib/BLI_vector_set.hh b/source/blender/blenlib/BLI_vector_set.hh index 2f5d04aefa2..567e4fd8128 100644 --- a/source/blender/blenlib/BLI_vector_set.hh +++ b/source/blender/blenlib/BLI_vector_set.hh @@ -415,6 +415,38 @@ class VectorSet { } /** + * 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. */ const Key *data() const diff --git a/source/blender/blenlib/CMakeLists.txt b/source/blender/blenlib/CMakeLists.txt index 06f937ce5ca..c8436b6b9cc 100644 --- a/source/blender/blenlib/CMakeLists.txt +++ b/source/blender/blenlib/CMakeLists.txt @@ -186,6 +186,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 diff --git a/source/blender/blenlib/intern/storage.c b/source/blender/blenlib/intern/storage.c index cb2634e6fda..5f823396ed9 100644 --- a/source/blender/blenlib/intern/storage.c +++ b/source/blender/blenlib/intern/storage.c @@ -266,7 +266,8 @@ eFileAttributes BLI_file_attributes(const char *path) if (attr & FILE_ATTRIBUTE_SPARSE_FILE) { ret |= FILE_ATTR_SPARSE_FILE; } - if (attr & FILE_ATTRIBUTE_OFFLINE) { + if (attr & FILE_ATTRIBUTE_OFFLINE || attr & FILE_ATTRIBUTE_RECALL_ON_OPEN || + attr & FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS) { ret |= FILE_ATTR_OFFLINE; } if (attr & FILE_ATTRIBUTE_REPARSE_POINT) { diff --git a/source/blender/blenlib/tests/BLI_linear_allocator_test.cc b/source/blender/blenlib/tests/BLI_linear_allocator_test.cc index 977e5dba497..0e0145e592a 100644 --- a/source/blender/blenlib/tests/BLI_linear_allocator_test.cc +++ b/source/blender/blenlib/tests/BLI_linear_allocator_test.cc @@ -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 diff --git a/source/blender/blenlib/tests/BLI_map_test.cc b/source/blender/blenlib/tests/BLI_map_test.cc index 18be456bd20..679a10e9ce0 100644 --- a/source/blender/blenlib/tests/BLI_map_test.cc +++ b/source/blender/blenlib/tests/BLI_map_test.cc @@ -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. */ diff --git a/source/blender/blenlib/tests/BLI_vector_set_test.cc b/source/blender/blenlib/tests/BLI_vector_set_test.cc index c535ac57774..c4016ca75e1 100644 --- a/source/blender/blenlib/tests/BLI_vector_set_test.cc +++ b/source/blender/blenlib/tests/BLI_vector_set_test.cc @@ -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 diff --git a/source/blender/blenloader/intern/versioning_300.c b/source/blender/blenloader/intern/versioning_300.c index 182231b5878..8c5e86eadd3 100644 --- a/source/blender/blenloader/intern/versioning_300.c +++ b/source/blender/blenloader/intern/versioning_300.c @@ -21,6 +21,7 @@ #define DNA_DEPRECATED_ALLOW #include "BLI_listbase.h" +#include "BLI_math_vector.h" #include "BLI_utildefines.h" #include "DNA_brush_types.h" @@ -30,6 +31,7 @@ #include "BKE_lib_id.h" #include "BKE_main.h" +#include "BKE_node.h" #include "BLO_readfile.h" #include "readfile.h" @@ -56,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; + } + } } } @@ -95,5 +121,15 @@ void blo_do_versions_300(FileData *fd, Library *UNUSED(lib), Main *bmain) */ { /* Keep this block, even when empty. */ + if (!DNA_struct_elem_find(fd->filesdna, "bPoseChannel", "float", "custom_scale_xyz[3]")) { + LISTBASE_FOREACH (Object *, ob, &bmain->objects) { + if (ob->pose == NULL) { + continue; + } + LISTBASE_FOREACH (bPoseChannel *, pchan, &ob->pose->chanbase) { + copy_v3_fl(pchan->custom_scale_xyz, pchan->custom_scale); + } + } + } } } diff --git a/source/blender/blenloader/intern/versioning_legacy.c b/source/blender/blenloader/intern/versioning_legacy.c index f2e73e161ca..56b2f18f8a6 100644 --- a/source/blender/blenloader/intern/versioning_legacy.c +++ b/source/blender/blenloader/intern/versioning_legacy.c @@ -1278,12 +1278,6 @@ void blo_do_versions_pre250(FileData *fd, Library *lib, Main *bmain) if (ob->soft->physics_speed == 0.0f) { ob->soft->physics_speed = 1.0f; } - - if (ob->soft->interval == 0) { - ob->soft->interval = 2; - ob->soft->sfra = 1; - ob->soft->efra = 100; - } } if (ob->soft && ob->soft->vertgroup == 0) { bDeformGroup *locGroup = BKE_object_defgroup_find_name(ob, "SOFTGOAL"); diff --git a/source/blender/compositor/intern/COM_ChunkOrderHotspot.cc b/source/blender/compositor/intern/COM_ChunkOrderHotspot.cc index 79afcc9deea..b8e19fc2c34 100644 --- a/source/blender/compositor/intern/COM_ChunkOrderHotspot.cc +++ b/source/blender/compositor/intern/COM_ChunkOrderHotspot.cc @@ -30,4 +30,4 @@ double ChunkOrderHotspot::calc_distance(int x, int y) return result; } -} // namespace blender::compositor
\ No newline at end of file +} // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_MemoryBuffer.cc b/source/blender/compositor/intern/COM_MemoryBuffer.cc index 68e39b19eaf..8c30d3215d7 100644 --- a/source/blender/compositor/intern/COM_MemoryBuffer.cc +++ b/source/blender/compositor/intern/COM_MemoryBuffer.cc @@ -25,29 +25,49 @@ namespace blender::compositor { MemoryBuffer::MemoryBuffer(MemoryProxy *memoryProxy, const rcti &rect, MemoryBufferState state) { m_rect = rect; + this->m_is_a_single_elem = false; this->m_memoryProxy = memoryProxy; this->m_num_channels = COM_data_type_num_channels(memoryProxy->getDataType()); this->m_buffer = (float *)MEM_mallocN_aligned( sizeof(float) * buffer_len() * this->m_num_channels, 16, "COM_MemoryBuffer"); this->m_state = state; this->m_datatype = memoryProxy->getDataType(); + + set_strides(); } -MemoryBuffer::MemoryBuffer(DataType dataType, const rcti &rect) +MemoryBuffer::MemoryBuffer(DataType dataType, const rcti &rect, bool is_a_single_elem) { m_rect = rect; + this->m_is_a_single_elem = is_a_single_elem; this->m_memoryProxy = nullptr; this->m_num_channels = COM_data_type_num_channels(dataType); this->m_buffer = (float *)MEM_mallocN_aligned( sizeof(float) * buffer_len() * this->m_num_channels, 16, "COM_MemoryBuffer"); this->m_state = MemoryBufferState::Temporary; this->m_datatype = dataType; + + set_strides(); } MemoryBuffer::MemoryBuffer(const MemoryBuffer &src) - : MemoryBuffer(src.m_memoryProxy, src.m_rect, MemoryBufferState::Temporary) + : MemoryBuffer(src.m_datatype, src.m_rect, false) +{ + m_memoryProxy = src.m_memoryProxy; + /* src may be single elem buffer */ + fill_from(src); +} + +void MemoryBuffer::set_strides() { - memcpy(m_buffer, src.m_buffer, buffer_len() * m_num_channels * sizeof(float)); + if (m_is_a_single_elem) { + this->elem_stride = 0; + this->row_stride = 0; + } + else { + this->elem_stride = m_num_channels; + this->row_stride = getWidth() * m_num_channels; + } } void MemoryBuffer::clear() @@ -100,6 +120,8 @@ MemoryBuffer::~MemoryBuffer() void MemoryBuffer::fill_from(const MemoryBuffer &src) { + BLI_assert(!this->is_a_single_elem()); + unsigned int otherY; unsigned int minX = MAX2(this->m_rect.xmin, src.m_rect.xmin); unsigned int maxX = MIN2(this->m_rect.xmax, src.m_rect.xmax); @@ -109,10 +131,8 @@ void MemoryBuffer::fill_from(const MemoryBuffer &src) int otherOffset; for (otherY = minY; otherY < maxY; otherY++) { - otherOffset = ((otherY - src.m_rect.ymin) * src.getWidth() + minX - src.m_rect.xmin) * - this->m_num_channels; - offset = ((otherY - this->m_rect.ymin) * getWidth() + minX - this->m_rect.xmin) * - this->m_num_channels; + otherOffset = src.get_coords_offset(minX, otherY); + offset = this->get_coords_offset(minX, otherY); memcpy(&this->m_buffer[offset], &src.m_buffer[otherOffset], (maxX - minX) * this->m_num_channels * sizeof(float)); @@ -123,8 +143,7 @@ void MemoryBuffer::writePixel(int x, int y, const float color[4]) { if (x >= this->m_rect.xmin && x < this->m_rect.xmax && y >= this->m_rect.ymin && y < this->m_rect.ymax) { - const int offset = (getWidth() * (y - this->m_rect.ymin) + x - this->m_rect.xmin) * - this->m_num_channels; + const int offset = get_coords_offset(x, y); memcpy(&this->m_buffer[offset], color, sizeof(float) * this->m_num_channels); } } @@ -133,8 +152,7 @@ void MemoryBuffer::addPixel(int x, int y, const float color[4]) { if (x >= this->m_rect.xmin && x < this->m_rect.xmax && y >= this->m_rect.ymin && y < this->m_rect.ymax) { - const int offset = (getWidth() * (y - this->m_rect.ymin) + x - this->m_rect.xmin) * - this->m_num_channels; + const int offset = get_coords_offset(x, y); float *dst = &this->m_buffer[offset]; const float *src = color; for (int i = 0; i < this->m_num_channels; i++, dst++, src++) { @@ -151,26 +169,31 @@ static void read_ewa_pixel_sampled(void *userdata, int x, int y, float result[4] void MemoryBuffer::readEWA(float *result, const float uv[2], const float derivatives[2][2]) { - BLI_assert(this->m_datatype == DataType::Color); - float inv_width = 1.0f / (float)this->getWidth(), inv_height = 1.0f / (float)this->getHeight(); - /* TODO(sergey): Render pipeline uses normalized coordinates and derivatives, - * but compositor uses pixel space. For now let's just divide the values and - * switch compositor to normalized space for EWA later. - */ - float uv_normal[2] = {uv[0] * inv_width, uv[1] * inv_height}; - float du_normal[2] = {derivatives[0][0] * inv_width, derivatives[0][1] * inv_height}; - float dv_normal[2] = {derivatives[1][0] * inv_width, derivatives[1][1] * inv_height}; - - BLI_ewa_filter(this->getWidth(), - this->getHeight(), - false, - true, - uv_normal, - du_normal, - dv_normal, - read_ewa_pixel_sampled, - this, - result); + if (m_is_a_single_elem) { + memcpy(result, m_buffer, sizeof(float) * this->m_num_channels); + } + else { + BLI_assert(this->m_datatype == DataType::Color); + float inv_width = 1.0f / (float)this->getWidth(), inv_height = 1.0f / (float)this->getHeight(); + /* TODO(sergey): Render pipeline uses normalized coordinates and derivatives, + * but compositor uses pixel space. For now let's just divide the values and + * switch compositor to normalized space for EWA later. + */ + float uv_normal[2] = {uv[0] * inv_width, uv[1] * inv_height}; + float du_normal[2] = {derivatives[0][0] * inv_width, derivatives[0][1] * inv_height}; + float dv_normal[2] = {derivatives[1][0] * inv_width, derivatives[1][1] * inv_height}; + + BLI_ewa_filter(this->getWidth(), + this->getHeight(), + false, + true, + uv_normal, + du_normal, + dv_normal, + read_ewa_pixel_sampled, + this, + result); + } } } // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_MemoryBuffer.h b/source/blender/compositor/intern/COM_MemoryBuffer.h index 060a67f8797..97b220508e0 100644 --- a/source/blender/compositor/intern/COM_MemoryBuffer.h +++ b/source/blender/compositor/intern/COM_MemoryBuffer.h @@ -50,6 +50,25 @@ class MemoryProxy; * \brief a MemoryBuffer contains access to the data of a chunk */ class MemoryBuffer { + public: + /** + * Offset between elements. + * + * Should always be used for the x dimension when calculating buffer offsets. + * It will be 0 when is_a_single_elem=true. + * e.g: buffer_index = y * buffer.row_stride + x * buffer.elem_stride + */ + int elem_stride; + + /** + * Offset between rows. + * + * Should always be used for the y dimension when calculating buffer offsets. + * It will be 0 when is_a_single_elem=true. + * e.g: buffer_index = y * buffer.row_stride + x * buffer.elem_stride + */ + int row_stride; + private: /** * \brief proxy of the memory (same for all chunks in the same buffer) @@ -82,6 +101,11 @@ class MemoryBuffer { */ uint8_t m_num_channels; + /** + * Whether buffer is a single element in memory. + */ + bool m_is_a_single_elem; + public: /** * \brief construct new temporarily MemoryBuffer for an area @@ -91,7 +115,7 @@ class MemoryBuffer { /** * \brief construct new temporarily MemoryBuffer for an area */ - MemoryBuffer(DataType datatype, const rcti &rect); + MemoryBuffer(DataType datatype, const rcti &rect, bool is_a_single_elem = false); /** * Copy constructor @@ -103,6 +127,102 @@ class MemoryBuffer { */ ~MemoryBuffer(); + /** + * Whether buffer is a single element in memory independently of its resolution. True for set + * operations buffers. + */ + bool is_a_single_elem() const + { + return m_is_a_single_elem; + } + + float &operator[](int index) + { + BLI_assert(m_is_a_single_elem ? index < m_num_channels : + index < get_coords_offset(getWidth(), getHeight())); + return m_buffer[index]; + } + + const float &operator[](int index) const + { + BLI_assert(m_is_a_single_elem ? index < m_num_channels : + index < get_coords_offset(getWidth(), getHeight())); + return m_buffer[index]; + } + + /** + * Get offset needed to jump from buffer start to given coordinates. + */ + int get_coords_offset(int x, int y) const + { + return (y - m_rect.ymin) * row_stride + (x - m_rect.xmin) * elem_stride; + } + + /** + * Get buffer element at given coordinates. + */ + float *get_elem(int x, int y) + { + BLI_assert(x >= m_rect.xmin && x < m_rect.xmax && y >= m_rect.ymin && y < m_rect.ymax); + return m_buffer + get_coords_offset(x, y); + } + + /** + * Get buffer element at given coordinates. + */ + const float *get_elem(int x, int y) const + { + BLI_assert(x >= m_rect.xmin && x < m_rect.xmax && y >= m_rect.ymin && y < m_rect.ymax); + return m_buffer + get_coords_offset(x, y); + } + + /** + * Get channel value at given coordinates. + */ + float &get_value(int x, int y, int channel) + { + BLI_assert(x >= m_rect.xmin && x < m_rect.xmax && y >= m_rect.ymin && y < m_rect.ymax && + channel >= 0 && channel < m_num_channels); + return m_buffer[get_coords_offset(x, y) + channel]; + } + + /** + * Get channel value at given coordinates. + */ + const float &get_value(int x, int y, int channel) const + { + BLI_assert(x >= m_rect.xmin && x < m_rect.xmax && y >= m_rect.ymin && y < m_rect.ymax && + channel >= 0 && channel < m_num_channels); + return m_buffer[get_coords_offset(x, y) + channel]; + } + + /** + * Get the buffer row end. + */ + const float *get_row_end(int y) const + { + BLI_assert(y >= 0 && y < getHeight()); + return m_buffer + (is_a_single_elem() ? m_num_channels : get_coords_offset(getWidth(), y)); + } + + /** + * Get the number of elements in memory for a row. For single element buffers it will always + * be 1. + */ + int get_memory_width() const + { + return is_a_single_elem() ? 1 : getWidth(); + } + + /** + * Get number of elements in memory for a column. For single element buffers it will + * always be 1. + */ + int get_memory_height() const + { + return is_a_single_elem() ? 1 : getHeight(); + } + uint8_t get_num_channels() { return this->m_num_channels; @@ -216,7 +336,7 @@ class MemoryBuffer { int u = x; int v = y; this->wrap_pixel(u, v, extend_x, extend_y); - const int offset = (getWidth() * y + x) * this->m_num_channels; + const int offset = get_coords_offset(u, v); float *buffer = &this->m_buffer[offset]; memcpy(result, buffer, sizeof(float) * this->m_num_channels); } @@ -232,7 +352,7 @@ class MemoryBuffer { int v = y; this->wrap_pixel(u, v, extend_x, extend_y); - const int offset = (getWidth() * v + u) * this->m_num_channels; + const int offset = get_coords_offset(u, v); BLI_assert(offset >= 0); BLI_assert(offset < this->buffer_len() * this->m_num_channels); @@ -258,15 +378,20 @@ class MemoryBuffer { copy_vn_fl(result, this->m_num_channels, 0.0f); return; } - BLI_bilinear_interpolation_wrap_fl(this->m_buffer, - result, - getWidth(), - getHeight(), - this->m_num_channels, - u, - v, - extend_x == MemoryBufferExtend::Repeat, - extend_y == MemoryBufferExtend::Repeat); + if (m_is_a_single_elem) { + memcpy(result, m_buffer, sizeof(float) * this->m_num_channels); + } + else { + BLI_bilinear_interpolation_wrap_fl(this->m_buffer, + result, + getWidth(), + getHeight(), + this->m_num_channels, + u, + v, + extend_x == MemoryBufferExtend::Repeat, + extend_y == MemoryBufferExtend::Repeat); + } } void readEWA(float *result, const float uv[2], const float derivatives[2][2]); @@ -321,9 +446,10 @@ class MemoryBuffer { float get_max_value(const rcti &rect) const; private: + void set_strides(); const int buffer_len() const { - return getWidth() * getHeight(); + return get_memory_width() * get_memory_height(); } #ifdef WITH_CXX_GUARDEDALLOC diff --git a/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc b/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc index ec5037fb29c..d0fe91bc96d 100644 --- a/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc +++ b/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc @@ -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) diff --git a/source/blender/depsgraph/intern/builder/deg_builder_relations.cc b/source/blender/depsgraph/intern/builder/deg_builder_relations.cc index 0f8b613f7ac..8a02228146a 100644 --- a/source/blender/depsgraph/intern/builder/deg_builder_relations.cc +++ b/source/blender/depsgraph/intern/builder/deg_builder_relations.cc @@ -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) diff --git a/source/blender/depsgraph/intern/eval/deg_eval_copy_on_write.cc b/source/blender/depsgraph/intern/eval/deg_eval_copy_on_write.cc index e1959c8bf5e..43bcb23a38a 100644 --- a/source/blender/depsgraph/intern/eval/deg_eval_copy_on_write.cc +++ b/source/blender/depsgraph/intern/eval/deg_eval_copy_on_write.cc @@ -304,7 +304,8 @@ bool id_copy_inplace_no_main(const ID *id, ID *newid) bool result = (BKE_id_copy_ex(nullptr, (ID *)id_for_copy, &newid, - LIB_ID_COPY_LOCALIZE | LIB_ID_CREATE_NO_ALLOCATE) != nullptr); + (LIB_ID_COPY_LOCALIZE | LIB_ID_CREATE_NO_ALLOCATE | + LIB_ID_COPY_SET_COPIED_ON_WRITE)) != nullptr); #ifdef NESTED_ID_NASTY_WORKAROUND if (result) { @@ -333,7 +334,8 @@ bool scene_copy_inplace_no_main(const Scene *scene, Scene *new_scene) bool result = (BKE_id_copy_ex(nullptr, id_for_copy, (ID **)&new_scene, - LIB_ID_COPY_LOCALIZE | LIB_ID_CREATE_NO_ALLOCATE) != nullptr); + (LIB_ID_COPY_LOCALIZE | LIB_ID_CREATE_NO_ALLOCATE | + LIB_ID_COPY_SET_COPIED_ON_WRITE)) != nullptr); #ifdef NESTED_ID_NASTY_WORKAROUND if (result) { diff --git a/source/blender/draw/CMakeLists.txt b/source/blender/draw/CMakeLists.txt index 196b46953c4..27167ce839b 100644 --- a/source/blender/draw/CMakeLists.txt +++ b/source/blender/draw/CMakeLists.txt @@ -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 diff --git a/source/blender/draw/engines/eevee/eevee_cryptomatte.c b/source/blender/draw/engines/eevee/eevee_cryptomatte.c index 0cb2d55d1eb..7fe984b4397 100644 --- a/source/blender/draw/engines/eevee/eevee_cryptomatte.c +++ b/source/blender/draw/engines/eevee/eevee_cryptomatte.c @@ -121,7 +121,7 @@ void EEVEE_cryptomatte_renderpasses_init(EEVEE_Data *vedata) ViewLayer *view_layer = draw_ctx->view_layer; /* Cryptomatte is only rendered for final image renders */ - if (!DRW_state_is_image_render()) { + if (!DRW_state_is_scene_render()) { return; } const eViewLayerCryptomatteFlags active_layers = eevee_cryptomatte_active_layers(view_layer); @@ -265,8 +265,7 @@ void EEVEE_cryptomatte_object_hair_cache_populate(EEVEE_Data *vedata, Object *ob) { BLI_assert(ob->type == OB_HAIR); - Hair *hair = ob->data; - Material *material = hair->mat ? hair->mat[HAIR_MATERIAL_NR - 1] : NULL; + Material *material = BKE_object_material_get_eval(ob, HAIR_MATERIAL_NR); eevee_cryptomatte_hair_cache_populate(vedata, sldata, ob, NULL, NULL, material); } @@ -291,8 +290,7 @@ void EEVEE_cryptomatte_particle_hair_cache_populate(EEVEE_Data *vedata, if (draw_as != PART_DRAW_PATH) { continue; } - Mesh *mesh = ob->data; - Material *material = part->omat - 1 < mesh->totcol ? NULL : mesh->mat[part->omat - 1]; + Material *material = BKE_object_material_get_eval(ob, part->omat); eevee_cryptomatte_hair_cache_populate(vedata, sldata, ob, psys, md, material); } } @@ -318,7 +316,7 @@ void EEVEE_cryptomatte_cache_populate(EEVEE_Data *vedata, EEVEE_ViewLayerData *s if (geom == NULL) { continue; } - Material *material = BKE_object_material_get(ob, i + 1); + Material *material = BKE_object_material_get_eval(ob, i + 1); DRWShadingGroup *grp = eevee_cryptomatte_shading_group_create( vedata, sldata, ob, material, false); DRW_shgroup_call(grp, geom, ob); diff --git a/source/blender/draw/engines/eevee/eevee_engine.c b/source/blender/draw/engines/eevee/eevee_engine.c index 509c002e25a..88fd823bc72 100644 --- a/source/blender/draw/engines/eevee/eevee_engine.c +++ b/source/blender/draw/engines/eevee/eevee_engine.c @@ -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); diff --git a/source/blender/draw/engines/eevee/eevee_materials.c b/source/blender/draw/engines/eevee/eevee_materials.c index a4325675ea9..d2e0c8308c5 100644 --- a/source/blender/draw/engines/eevee/eevee_materials.c +++ b/source/blender/draw/engines/eevee/eevee_materials.c @@ -723,7 +723,7 @@ BLI_INLINE Material *eevee_object_material_get(Object *ob, int slot, bool holdou if (holdout) { return BKE_material_default_holdout(); } - Material *ma = BKE_object_material_get(ob, slot + 1); + Material *ma = BKE_object_material_get_eval(ob, slot + 1); if (ma == NULL) { if (ob->type == OB_VOLUME) { ma = BKE_material_default_volume(); diff --git a/source/blender/draw/engines/eevee/eevee_volumes.c b/source/blender/draw/engines/eevee/eevee_volumes.c index 3d24cd6fab5..eed36221fcb 100644 --- a/source/blender/draw/engines/eevee/eevee_volumes.c +++ b/source/blender/draw/engines/eevee/eevee_volumes.c @@ -394,9 +394,10 @@ static bool eevee_volume_object_grids_init(Object *ob, ListBase *gpu_grids, DRWS * - Grid exists and texture was loaded -> use texture. * - Grid exists but has zero size or failed to load -> use zero. * - Grid does not exist -> use default value. */ - GPUTexture *grid_tex = (drw_grid) ? drw_grid->texture : - (volume_grid) ? e_data.dummy_zero : - eevee_volume_default_texture(gpu_grid->default_value); + GPUTexture *grid_tex = (drw_grid) ? drw_grid->texture : + (volume_grid) ? + e_data.dummy_zero : + eevee_volume_default_texture(gpu_grid->default_value); DRW_shgroup_uniform_texture(grp, gpu_grid->sampler_name, grid_tex); @@ -500,7 +501,7 @@ void EEVEE_volumes_cache_object_add(EEVEE_ViewLayerData *sldata, Scene *scene, Object *ob) { - Material *ma = BKE_object_material_get(ob, 1); + Material *ma = BKE_object_material_get_eval(ob, 1); if (ma == NULL) { if (ob->type == OB_VOLUME) { diff --git a/source/blender/draw/engines/eevee/shaders/closure_eval_lib.glsl b/source/blender/draw/engines/eevee/shaders/closure_eval_lib.glsl index b554fd113df..94dd1a439db 100644 --- a/source/blender/draw/engines/eevee/shaders/closure_eval_lib.glsl +++ b/source/blender/draw/engines/eevee/shaders/closure_eval_lib.glsl @@ -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); \ - } \ + 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); \ + 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); \ - } + 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, \ diff --git a/source/blender/draw/engines/eevee/shaders/effect_dof_lib.glsl b/source/blender/draw/engines/eevee/shaders/effect_dof_lib.glsl index faac216480b..dac53719149 100644 --- a/source/blender/draw/engines/eevee/shaders/effect_dof_lib.glsl +++ b/source/blender/draw/engines/eevee/shaders/effect_dof_lib.glsl @@ -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) { diff --git a/source/blender/draw/engines/eevee/shaders/effect_dof_resolve_frag.glsl b/source/blender/draw/engines/eevee/shaders/effect_dof_resolve_frag.glsl index cd69a0e2ab1..32841b7749c 100644 --- a/source/blender/draw/engines/eevee/shaders/effect_dof_resolve_frag.glsl +++ b/source/blender/draw/engines/eevee/shaders/effect_dof_resolve_frag.glsl @@ -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); diff --git a/source/blender/draw/engines/gpencil/gpencil_cache_utils.c b/source/blender/draw/engines/gpencil/gpencil_cache_utils.c index ee51b751187..adb70f97585 100644 --- a/source/blender/draw/engines/gpencil/gpencil_cache_utils.c +++ b/source/blender/draw/engines/gpencil/gpencil_cache_utils.c @@ -60,7 +60,8 @@ GPENCIL_tObject *gpencil_object_cache_add(GPENCIL_PrivateData *pd, Object *ob) /* Check if any material with holdout flag enabled. */ tgp_ob->do_mat_holdout = false; - for (int i = 0; i < ob->totcol; i++) { + const int tot_materials = BKE_object_material_count_eval(ob); + for (int i = 0; i < tot_materials; i++) { MaterialGPencilStyle *gp_style = BKE_gpencil_material_settings(ob, i + 1); if (((gp_style != NULL) && (gp_style->flag & GP_MATERIAL_IS_STROKE_HOLDOUT)) || ((gp_style->flag & GP_MATERIAL_IS_FILL_HOLDOUT))) { diff --git a/source/blender/draw/engines/gpencil/gpencil_draw_data.c b/source/blender/draw/engines/gpencil/gpencil_draw_data.c index 526f553329e..e3e84dd4c8c 100644 --- a/source/blender/draw/engines/gpencil/gpencil_draw_data.c +++ b/source/blender/draw/engines/gpencil/gpencil_draw_data.c @@ -186,7 +186,7 @@ GPENCIL_MaterialPool *gpencil_material_pool_create(GPENCIL_PrivateData *pd, Obje { GPENCIL_MaterialPool *matpool = pd->last_material_pool; - int mat_len = max_ii(1, ob->totcol); + int mat_len = max_ii(1, BKE_object_material_count_eval(ob)); bool reuse_matpool = matpool && ((matpool->used_count + mat_len) <= GP_MATERIAL_BUFFER_LEN); diff --git a/source/blender/draw/engines/gpencil/gpencil_engine.c b/source/blender/draw/engines/gpencil/gpencil_engine.c index 8bb336ebc96..32884eb9e3f 100644 --- a/source/blender/draw/engines/gpencil/gpencil_engine.c +++ b/source/blender/draw/engines/gpencil/gpencil_engine.c @@ -819,7 +819,10 @@ static void gpencil_draw_mask(GPENCIL_Data *vedata, GPENCIL_tObject *ob, GPENCIL } GPENCIL_tLayer *mask_layer = gpencil_layer_cache_get(ob, i); - BLI_assert(mask_layer); + /* When filtering by viewlayer, the mask could be null and must be ignored. */ + if (mask_layer == NULL) { + continue; + } DRW_draw_pass(mask_layer->geom_ps); } diff --git a/source/blender/draw/engines/overlay/overlay_armature.c b/source/blender/draw/engines/overlay/overlay_armature.c index 54224071d23..fbad60ff4ab 100644 --- a/source/blender/draw/engines/overlay/overlay_armature.c +++ b/source/blender/draw/engines/overlay/overlay_armature.c @@ -1031,7 +1031,7 @@ static void pchan_draw_data_init(bPoseChannel *pchan) static void draw_bone_update_disp_matrix_default(EditBone *eBone, bPoseChannel *pchan) { float ebmat[4][4]; - float length; + float bone_scale[3]; float(*bone_mat)[4]; float(*disp_mat)[4]; float(*disp_tail_mat)[4]; @@ -1040,23 +1040,23 @@ static void draw_bone_update_disp_matrix_default(EditBone *eBone, bPoseChannel * * and not be tight to the draw pass creation. * This would refresh armature without invalidating the draw cache */ if (pchan) { - length = pchan->bone->length; bone_mat = pchan->pose_mat; disp_mat = pchan->disp_mat; disp_tail_mat = pchan->disp_tail_mat; + copy_v3_fl(bone_scale, pchan->bone->length); } else { eBone->length = len_v3v3(eBone->tail, eBone->head); ED_armature_ebone_to_mat4(eBone, ebmat); - length = eBone->length; + copy_v3_fl(bone_scale, eBone->length); bone_mat = ebmat; disp_mat = eBone->disp_mat; disp_tail_mat = eBone->disp_tail_mat; } copy_m4_m4(disp_mat, bone_mat); - rescale_m4(disp_mat, (float[3]){length, length, length}); + rescale_m4(disp_mat, bone_scale); copy_m4_m4(disp_tail_mat, disp_mat); translate_m4(disp_tail_mat, 0.0f, 1.0f, 0.0f); } @@ -1255,19 +1255,27 @@ static void draw_bone_update_disp_matrix_bbone(EditBone *eBone, bPoseChannel *pc static void draw_bone_update_disp_matrix_custom(bPoseChannel *pchan) { - float length; + float bone_scale[3]; float(*bone_mat)[4]; float(*disp_mat)[4]; float(*disp_tail_mat)[4]; + float rot_mat[3][3]; /* See TODO above */ - length = PCHAN_CUSTOM_DRAW_SIZE(pchan); + mul_v3_v3fl(bone_scale, pchan->custom_scale_xyz, PCHAN_CUSTOM_BONE_LENGTH(pchan)); bone_mat = pchan->custom_tx ? pchan->custom_tx->pose_mat : pchan->pose_mat; disp_mat = pchan->disp_mat; disp_tail_mat = pchan->disp_tail_mat; + eulO_to_mat3(rot_mat, pchan->custom_rotation_euler, ROT_MODE_XYZ); + copy_m4_m4(disp_mat, bone_mat); - rescale_m4(disp_mat, (float[3]){length, length, length}); + translate_m4(disp_mat, + 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); translate_m4(disp_tail_mat, 0.0f, 1.0f, 0.0f); } diff --git a/source/blender/draw/engines/overlay/overlay_engine.c b/source/blender/draw/engines/overlay/overlay_engine.c index e9736402ae7..b8f721946f2 100644 --- a/source/blender/draw/engines/overlay/overlay_engine.c +++ b/source/blender/draw/engines/overlay/overlay_engine.c @@ -285,10 +285,6 @@ static bool overlay_should_fade_object(Object *ob, Object *active_object) return false; } - if (ob->base_flag & BASE_FROM_DUPLI) { - return false; - } - return true; } diff --git a/source/blender/draw/engines/overlay/overlay_gpencil.c b/source/blender/draw/engines/overlay/overlay_gpencil.c index 891142fe0a2..aa26aa47faa 100644 --- a/source/blender/draw/engines/overlay/overlay_gpencil.c +++ b/source/blender/draw/engines/overlay/overlay_gpencil.c @@ -382,7 +382,7 @@ static void overlay_gpencil_draw_stroke_color_name(bGPDlayer *UNUSED(gpl), void *thunk) { Object *ob = (Object *)thunk; - Material *ma = BKE_object_material_get(ob, gps->mat_nr + 1); + Material *ma = BKE_object_material_get_eval(ob, gps->mat_nr + 1); if (ma == NULL) { return; } diff --git a/source/blender/draw/engines/overlay/overlay_paint.c b/source/blender/draw/engines/overlay/overlay_paint.c index 89e724bcfcc..d52640ed174 100644 --- a/source/blender/draw/engines/overlay/overlay_paint.c +++ b/source/blender/draw/engines/overlay/overlay_paint.c @@ -48,7 +48,7 @@ static bool paint_object_is_rendered_transparent(View3D *v3d, Object *ob) v3d->shading.color_type == V3D_SHADING_MATERIAL_COLOR) { Mesh *me = ob->data; for (int i = 0; i < me->totcol; i++) { - Material *mat = me->mat[i]; + Material *mat = BKE_object_material_get_eval(ob, i + 1); if (mat && mat->a < 1.0f) { return true; } diff --git a/source/blender/draw/engines/overlay/overlay_particle.c b/source/blender/draw/engines/overlay/overlay_particle.c index 71064e7ff47..5fa74a8c3a6 100644 --- a/source/blender/draw/engines/overlay/overlay_particle.c +++ b/source/blender/draw/engines/overlay/overlay_particle.c @@ -186,7 +186,7 @@ void OVERLAY_particle_cache_populate(OVERLAY_Data *vedata, Object *ob) /* TODO(fclem): Here would be a good place for preemptive culling. */ /* NOTE(fclem): Is color even useful in our modern context? */ - Material *ma = BKE_object_material_get(ob, part->omat); + Material *ma = BKE_object_material_get_eval(ob, part->omat); float color[4] = {0.6f, 0.6f, 0.6f, part->draw_size}; if (ma != NULL) { copy_v3_v3(color, &ma->r); diff --git a/source/blender/draw/engines/workbench/workbench_materials.c b/source/blender/draw/engines/workbench/workbench_materials.c index 6e9118bfe46..800d1085505 100644 --- a/source/blender/draw/engines/workbench/workbench_materials.c +++ b/source/blender/draw/engines/workbench/workbench_materials.c @@ -93,7 +93,7 @@ void workbench_material_ubo_data(WORKBENCH_PrivateData *wpd, /* Return correct material or empty default material if slot is empty. */ BLI_INLINE Material *workbench_object_material_get(Object *ob, int mat_nr) { - Material *ma = BKE_object_material_get(ob, mat_nr); + Material *ma = BKE_object_material_get_eval(ob, mat_nr); if (ma == NULL) { ma = BKE_material_default_empty(); } diff --git a/source/blender/draw/engines/workbench/workbench_volume.c b/source/blender/draw/engines/workbench/workbench_volume.c index 525a81b5581..ddda6d7b58e 100644 --- a/source/blender/draw/engines/workbench/workbench_volume.c +++ b/source/blender/draw/engines/workbench/workbench_volume.c @@ -202,7 +202,7 @@ static void workbench_volume_material_color(WORKBENCH_PrivateData *wpd, eV3DShadingColorType color_type, float color[3]) { - Material *ma = BKE_object_material_get(ob, VOLUME_MATERIAL_NR); + Material *ma = BKE_object_material_get_eval(ob, VOLUME_MATERIAL_NR); WORKBENCH_UBO_Material ubo_data; workbench_material_ubo_data(wpd, ob, ma, &ubo_data, color_type); copy_v3_v3(color, ubo_data.base_color); diff --git a/source/blender/draw/intern/draw_cache_extract.h b/source/blender/draw/intern/draw_cache_extract.h index c929fe7dfd3..bbb97fc09a3 100644 --- a/source/blender/draw/intern/draw_cache_extract.h +++ b/source/blender/draw/intern/draw_cache_extract.h @@ -88,6 +88,10 @@ typedef enum eMRExtractType { BLI_INLINE int mesh_render_mat_len_get(Mesh *me) { + /* In edit mode, the displayed mesh is stored in the edit-mesh. */ + if (me->edit_mesh && me->edit_mesh->mesh_eval_final) { + return MAX2(1, me->edit_mesh->mesh_eval_final->totcol); + } return MAX2(1, me->totcol); } diff --git a/source/blender/draw/tests/draw_testing.cc b/source/blender/draw/tests/draw_testing.cc new file mode 100644 index 00000000000..0104437e921 --- /dev/null +++ b/source/blender/draw/tests/draw_testing.cc @@ -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 diff --git a/source/blender/draw/tests/draw_testing.hh b/source/blender/draw/tests/draw_testing.hh new file mode 100644 index 00000000000..ec0b15b611e --- /dev/null +++ b/source/blender/draw/tests/draw_testing.hh @@ -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 diff --git a/source/blender/draw/tests/shaders_test.cc b/source/blender/draw/tests/shaders_test.cc index 96d544fd855..c96f22859ca 100644 --- a/source/blender/draw/tests/shaders_test.cc +++ b/source/blender/draw/tests/shaders_test.cc @@ -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]; diff --git a/source/blender/editors/armature/CMakeLists.txt b/source/blender/editors/armature/CMakeLists.txt index 98c050950be..0030e78002b 100644 --- a/source/blender/editors/armature/CMakeLists.txt +++ b/source/blender/editors/armature/CMakeLists.txt @@ -17,6 +17,7 @@ set(INC ../include + ../../blenfont ../../blenkernel ../../blenlib ../../blentranslation diff --git a/source/blender/editors/armature/pose_slide.c b/source/blender/editors/armature/pose_slide.c index 93d36abe792..f7b54b79601 100644 --- a/source/blender/editors/armature/pose_slide.c +++ b/source/blender/editors/armature/pose_slide.c @@ -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" @@ -41,6 +42,7 @@ #include "BKE_layer.h" #include "BKE_object.h" #include "BKE_report.h" +#include "BKE_screen.h" #include "BKE_unit.h" #include "RNA_access.h" @@ -50,15 +52,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 +125,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 +223,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 +475,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 +528,14 @@ 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. */ + ARegion *region_header = BKE_area_find_region_type(pso->area, RGN_TYPE_HEADER); + if (region_header != NULL) { + pso->region = region_header; + pso->draw_handle = ED_region_draw_cb_activate( + region_header->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 +545,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 +888,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 +907,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 +1075,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 +1161,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", + "%s: %s | %s | %s | %s | %s", mode_str, - (int)(pso->percentage * 100.0f), - limits_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 +1291,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 +1391,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 +1412,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 +1518,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 +1581,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 +1637,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 +1700,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 +1741,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 +1762,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 +1803,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 +1823,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 +1864,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 +1885,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 +1926,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 +1947,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 +1988,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); diff --git a/source/blender/editors/gpencil/annotate_paint.c b/source/blender/editors/gpencil/annotate_paint.c index 5c40bc8e418..c155587e95a 100644 --- a/source/blender/editors/gpencil/annotate_paint.c +++ b/source/blender/editors/gpencil/annotate_paint.c @@ -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); diff --git a/source/blender/editors/gpencil/gpencil_add_blank.c b/source/blender/editors/gpencil/gpencil_add_blank.c index 3bd61d5c88a..3aa16e54597 100644 --- a/source/blender/editors/gpencil/gpencil_add_blank.c +++ b/source/blender/editors/gpencil/gpencil_add_blank.c @@ -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); diff --git a/source/blender/editors/gpencil/gpencil_add_lineart.c b/source/blender/editors/gpencil/gpencil_add_lineart.c index 6b28c6ec13e..ac0da0ad1db 100644 --- a/source/blender/editors/gpencil/gpencil_add_lineart.c +++ b/source/blender/editors/gpencil/gpencil_add_lineart.c @@ -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); diff --git a/source/blender/editors/gpencil/gpencil_add_monkey.c b/source/blender/editors/gpencil/gpencil_add_monkey.c index 4497d963c6d..d8734c4ae6b 100644 --- a/source/blender/editors/gpencil/gpencil_add_monkey.c +++ b/source/blender/editors/gpencil/gpencil_add_monkey.c @@ -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 */ diff --git a/source/blender/editors/gpencil/gpencil_add_stroke.c b/source/blender/editors/gpencil/gpencil_add_stroke.c index 26237636526..e95496b51ee 100644 --- a/source/blender/editors/gpencil/gpencil_add_stroke.c +++ b/source/blender/editors/gpencil/gpencil_add_stroke.c @@ -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); diff --git a/source/blender/editors/gpencil/gpencil_convert.c b/source/blender/editors/gpencil/gpencil_convert.c index ac75ae44c8a..8ab413e907c 100644 --- a/source/blender/editors/gpencil/gpencil_convert.c +++ b/source/blender/editors/gpencil/gpencil_convert.c @@ -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); diff --git a/source/blender/editors/gpencil/gpencil_data.c b/source/blender/editors/gpencil/gpencil_data.c index c93fcb9eb8c..d9a807d17ab 100644 --- a/source/blender/editors/gpencil/gpencil_data.c +++ b/source/blender/editors/gpencil/gpencil_data.c @@ -129,7 +129,7 @@ static int gpencil_data_add_exec(bContext *C, wmOperator *op) gpd->flag |= GP_DATA_ANNOTATIONS; /* add new layer (i.e. a "note") */ - BKE_gpencil_layer_addnew(*gpd_ptr, DATA_("Note"), true); + BKE_gpencil_layer_addnew(*gpd_ptr, DATA_("Note"), true, false); /* notifiers */ WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); @@ -231,7 +231,7 @@ static int gpencil_layer_add_exec(bContext *C, wmOperator *op) /* mark as annotation */ (*gpd_ptr)->flag |= GP_DATA_ANNOTATIONS; - BKE_gpencil_layer_addnew(*gpd_ptr, DATA_("Note"), true); + BKE_gpencil_layer_addnew(*gpd_ptr, DATA_("Note"), true, false); gpd = *gpd_ptr; } else { @@ -239,7 +239,7 @@ static int gpencil_layer_add_exec(bContext *C, wmOperator *op) Object *ob = CTX_data_active_object(C); if ((ob != NULL) && (ob->type == OB_GPENCIL)) { gpd = (bGPdata *)ob->data; - bGPDlayer *gpl = BKE_gpencil_layer_addnew(gpd, DATA_("GP_Layer"), true); + bGPDlayer *gpl = BKE_gpencil_layer_addnew(gpd, DATA_("GP_Layer"), true, false); /* Add a new frame to make it visible in Dopesheet. */ if (gpl != NULL) { gpl->actframe = BKE_gpencil_layer_frame_get(gpl, CFRA, GP_GETFRAME_ADD_NEW); @@ -524,7 +524,6 @@ enum { static bool gpencil_layer_duplicate_object_poll(bContext *C) { - ViewLayer *view_layer = CTX_data_view_layer(C); Object *ob = CTX_data_active_object(C); if ((ob == NULL) || (ob->type != OB_GPENCIL)) { return false; @@ -537,90 +536,75 @@ static bool gpencil_layer_duplicate_object_poll(bContext *C) return false; } - /* check there are more grease pencil objects */ - LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) { - if ((base->object != ob) && (base->object->type == OB_GPENCIL)) { - return true; - } - } - - return false; + return true; } static int gpencil_layer_duplicate_object_exec(bContext *C, wmOperator *op) { Main *bmain = CTX_data_main(C); - Scene *scene = CTX_data_scene(C); - char name[MAX_ID_NAME - 2]; - RNA_string_get(op->ptr, "object", name); - - if (name[0] == '\0') { - return OPERATOR_CANCELLED; - } - - Object *ob_dst = (Object *)BKE_scene_object_find_by_name(scene, name); - - int mode = RNA_enum_get(op->ptr, "mode"); + const bool only_active = RNA_boolean_get(op->ptr, "only_active"); + const int mode = RNA_enum_get(op->ptr, "mode"); Object *ob_src = CTX_data_active_object(C); bGPdata *gpd_src = (bGPdata *)ob_src->data; - bGPDlayer *gpl_src = BKE_gpencil_layer_active_get(gpd_src); - - /* Sanity checks. */ - if (ELEM(NULL, gpd_src, gpl_src, ob_dst)) { - return OPERATOR_CANCELLED; - } - /* Cannot copy itself and check destination type. */ - if ((ob_src == ob_dst) || (ob_dst->type != OB_GPENCIL)) { - return OPERATOR_CANCELLED; - } - - bGPdata *gpd_dst = (bGPdata *)ob_dst->data; + bGPDlayer *gpl_active = BKE_gpencil_layer_active_get(gpd_src); - /* Create new layer. */ - bGPDlayer *gpl_dst = BKE_gpencil_layer_addnew(gpd_dst, gpl_src->info, true); - /* Need to copy some variables (not all). */ - gpl_dst->onion_flag = gpl_src->onion_flag; - gpl_dst->thickness = gpl_src->thickness; - gpl_dst->line_change = gpl_src->line_change; - copy_v4_v4(gpl_dst->tintcolor, gpl_src->tintcolor); - gpl_dst->opacity = gpl_src->opacity; - - /* Create all frames. */ - LISTBASE_FOREACH (bGPDframe *, gpf_src, &gpl_src->frames) { - - if ((mode == GP_LAYER_COPY_OBJECT_ACT_FRAME) && (gpf_src != gpl_src->actframe)) { + CTX_DATA_BEGIN (C, Object *, ob, selected_objects) { + if ((ob == ob_src) || (ob->type != OB_GPENCIL)) { continue; } + bGPdata *gpd_dst = (bGPdata *)ob->data; + LISTBASE_FOREACH_BACKWARD (bGPDlayer *, gpl_src, &gpd_src->layers) { + if ((only_active) && (gpl_src != gpl_active)) { + continue; + } + /* Create new layer (adding at head of the list). */ + bGPDlayer *gpl_dst = BKE_gpencil_layer_addnew(gpd_dst, gpl_src->info, true, true); + /* Need to copy some variables (not all). */ + gpl_dst->onion_flag = gpl_src->onion_flag; + gpl_dst->thickness = gpl_src->thickness; + gpl_dst->line_change = gpl_src->line_change; + copy_v4_v4(gpl_dst->tintcolor, gpl_src->tintcolor); + gpl_dst->opacity = gpl_src->opacity; + + /* Create all frames. */ + LISTBASE_FOREACH (bGPDframe *, gpf_src, &gpl_src->frames) { + + if ((mode == GP_LAYER_COPY_OBJECT_ACT_FRAME) && (gpf_src != gpl_src->actframe)) { + continue; + } - /* Create new frame. */ - bGPDframe *gpf_dst = BKE_gpencil_frame_addnew(gpl_dst, gpf_src->framenum); + /* Create new frame. */ + bGPDframe *gpf_dst = BKE_gpencil_frame_addnew(gpl_dst, gpf_src->framenum); - /* Copy strokes. */ - LISTBASE_FOREACH (bGPDstroke *, gps_src, &gpf_src->strokes) { + /* Copy strokes. */ + LISTBASE_FOREACH (bGPDstroke *, gps_src, &gpf_src->strokes) { - /* Make copy of source stroke. */ - bGPDstroke *gps_dst = BKE_gpencil_stroke_duplicate(gps_src, true, true); + /* Make copy of source stroke. */ + bGPDstroke *gps_dst = BKE_gpencil_stroke_duplicate(gps_src, true, true); - /* Check if material is in destination object, - * otherwise add the slot with the material. */ - Material *ma_src = BKE_object_material_get(ob_src, gps_src->mat_nr + 1); - if (ma_src != NULL) { - int idx = BKE_gpencil_object_material_ensure(bmain, ob_dst, ma_src); + /* Check if material is in destination object, + * otherwise add the slot with the material. */ + Material *ma_src = BKE_object_material_get(ob_src, gps_src->mat_nr + 1); + if (ma_src != NULL) { + int idx = BKE_gpencil_object_material_ensure(bmain, ob, ma_src); - /* Reassign the stroke material to the right slot in destination object. */ - gps_dst->mat_nr = idx; - } + /* Reassign the stroke material to the right slot in destination object. */ + gps_dst->mat_nr = idx; + } - /* Add new stroke to frame. */ - BLI_addtail(&gpf_dst->strokes, gps_dst); + /* Add new stroke to frame. */ + BLI_addtail(&gpf_dst->strokes, gps_dst); + } + } } + /* notifiers */ + DEG_id_tag_update(&gpd_dst->id, + ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY | ID_RECALC_COPY_ON_WRITE); + DEG_id_tag_update(&ob->id, ID_RECALC_COPY_ON_WRITE); } + CTX_DATA_END; - /* notifiers */ - DEG_id_tag_update(&gpd_dst->id, - ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY | ID_RECALC_COPY_ON_WRITE); - DEG_id_tag_update(&ob_dst->id, ID_RECALC_COPY_ON_WRITE); WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); return OPERATOR_FINISHED; @@ -628,6 +612,8 @@ static int gpencil_layer_duplicate_object_exec(bContext *C, wmOperator *op) void GPENCIL_OT_layer_duplicate_object(wmOperatorType *ot) { + PropertyRNA *prop; + static const EnumPropertyItem copy_mode[] = { {GP_LAYER_COPY_OBJECT_ALL_FRAME, "ALL", 0, "All Frames", ""}, {GP_LAYER_COPY_OBJECT_ACT_FRAME, "ACTIVE", 0, "Active Frame", ""}, @@ -637,7 +623,7 @@ void GPENCIL_OT_layer_duplicate_object(wmOperatorType *ot) /* identifiers */ ot->name = "Duplicate Layer to New Object"; ot->idname = "GPENCIL_OT_layer_duplicate_object"; - ot->description = "Make a copy of the active Grease Pencil layer to new object"; + ot->description = "Make a copy of the active Grease Pencil layer to selected object"; /* callbacks */ ot->exec = gpencil_layer_duplicate_object_exec; @@ -646,11 +632,14 @@ void GPENCIL_OT_layer_duplicate_object(wmOperatorType *ot) /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; - ot->prop = RNA_def_string( - ot->srna, "object", NULL, MAX_ID_NAME - 2, "Object", "Name of the destination object"); - RNA_def_property_flag(ot->prop, PROP_HIDDEN | PROP_SKIP_SAVE); + ot->prop = RNA_def_enum(ot->srna, "mode", copy_mode, GP_LAYER_COPY_OBJECT_ALL_FRAME, "Mode", ""); - RNA_def_enum(ot->srna, "mode", copy_mode, GP_LAYER_COPY_OBJECT_ALL_FRAME, "Mode", ""); + prop = RNA_def_boolean(ot->srna, + "only_active", + true, + "Only Active", + "Copy only active Layer, uncheck to append all layers"); + RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE); } /* ********************* Duplicate Frame ************************** */ @@ -1443,7 +1432,7 @@ static int gpencil_layer_change_exec(bContext *C, wmOperator *op) /* Get layer or create new one */ if (layer_num == -1) { /* Create layer */ - gpl = BKE_gpencil_layer_addnew(gpd, DATA_("GP_Layer"), true); + gpl = BKE_gpencil_layer_addnew(gpd, DATA_("GP_Layer"), true, false); } else { /* Try to get layer */ @@ -3589,6 +3578,79 @@ void GPENCIL_OT_set_active_material(wmOperatorType *ot) ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } +/* ********************* Append Materials in a new object ************************** */ +static bool gpencil_materials_copy_to_object_poll(bContext *C) +{ + Object *ob = CTX_data_active_object(C); + if ((ob == NULL) || (ob->type != OB_GPENCIL)) { + return false; + } + short *totcolp = BKE_object_material_len_p(ob); + if (*totcolp == 0) { + return false; + } + + return true; +} + +static int gpencil_materials_copy_to_object_exec(bContext *C, wmOperator *op) +{ + Main *bmain = CTX_data_main(C); + const bool only_active = RNA_boolean_get(op->ptr, "only_active"); + Object *ob_src = CTX_data_active_object(C); + Material *ma_active = BKE_gpencil_material(ob_src, ob_src->actcol); + + CTX_DATA_BEGIN (C, Object *, ob, selected_objects) { + if ((ob == ob_src) || (ob->type != OB_GPENCIL)) { + continue; + } + /* Duplicate materials. */ + for (int i = 0; i < ob_src->totcol; i++) { + Material *ma_src = BKE_object_material_get(ob_src, i + 1); + if (only_active && ma_src != ma_active) { + continue; + } + + if (ma_src != NULL) { + BKE_gpencil_object_material_ensure(bmain, ob, ma_src); + } + } + + /* notifiers */ + DEG_id_tag_update(&ob->id, ID_RECALC_COPY_ON_WRITE); + } + CTX_DATA_END; + + /* notifiers */ + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_materials_copy_to_object(wmOperatorType *ot) +{ + PropertyRNA *prop; + + /* identifiers */ + ot->name = "Copy Materials to Selected Object"; + ot->idname = "GPENCIL_OT_materials_copy_to_object"; + ot->description = "Append Materials of the active Grease Pencil to other object"; + + /* callbacks */ + ot->exec = gpencil_materials_copy_to_object_exec; + ot->poll = gpencil_materials_copy_to_object_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + prop = RNA_def_boolean(ot->srna, + "only_active", + true, + "Only Active", + "Append only active material, uncheck to append all materials"); + RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE); +} + /* Parent GPencil object to Lattice */ bool ED_gpencil_add_lattice_modifier(const bContext *C, ReportList *reports, diff --git a/source/blender/editors/gpencil/gpencil_edit.c b/source/blender/editors/gpencil/gpencil_edit.c index d90f74fbf4f..e65afa1abff 100644 --- a/source/blender/editors/gpencil/gpencil_edit.c +++ b/source/blender/editors/gpencil/gpencil_edit.c @@ -1677,7 +1677,7 @@ static int gpencil_strokes_paste_exec(bContext *C, wmOperator *op) if (gpl == NULL) { /* no active layer - let's just create one */ - gpl = BKE_gpencil_layer_addnew(gpd, DATA_("GP_Layer"), true); + gpl = BKE_gpencil_layer_addnew(gpd, DATA_("GP_Layer"), true, false); } else if ((BKE_gpencil_layer_is_editable(gpl) == false) && (type == GP_COPY_TO_ACTIVE)) { BKE_report( @@ -1835,7 +1835,7 @@ static int gpencil_move_to_layer_exec(bContext *C, wmOperator *op) } else { /* Create a new layer. */ - target_layer = BKE_gpencil_layer_addnew(gpd, "GP_Layer", true); + target_layer = BKE_gpencil_layer_addnew(gpd, "GP_Layer", true, false); } if (target_layer == NULL) { @@ -4544,6 +4544,9 @@ static int gpencil_stroke_separate_exec(bContext *C, wmOperator *op) eGP_SeparateModes mode = RNA_enum_get(op->ptr, "mode"); + const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd_src); + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd_src); + /* sanity checks */ if (ELEM(NULL, gpd_src)) { return OPERATOR_CANCELLED; @@ -4554,8 +4557,22 @@ static int gpencil_stroke_separate_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } - const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd_src); - const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd_src); + /* Cancel if nothing selected. */ + if (ELEM(mode, GP_SEPARATE_POINT, GP_SEPARATE_STROKE)) { + bool has_selected = false; + CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) { + if (ED_gpencil_layer_has_selected_stroke(gpl, is_multiedit)) { + has_selected = true; + break; + } + } + CTX_DATA_END; + + if (!has_selected) { + BKE_report(op->reports, RPT_ERROR, "Nothing selected"); + return OPERATOR_CANCELLED; + } + } /* Create a new object. */ /* Take into account user preferences for duplicating actions. */ @@ -4600,7 +4617,7 @@ static int gpencil_stroke_separate_exec(bContext *C, wmOperator *op) if (gps->flag & GP_STROKE_SELECT) { /* add layer if not created before */ if (gpl_dst == NULL) { - gpl_dst = BKE_gpencil_layer_addnew(gpd_dst, gpl->info, false); + gpl_dst = BKE_gpencil_layer_addnew(gpd_dst, gpl->info, false, false); } /* add frame if not created before */ diff --git a/source/blender/editors/gpencil/gpencil_fill.c b/source/blender/editors/gpencil/gpencil_fill.c index 62d64c67b08..f74e211dd65 100644 --- a/source/blender/editors/gpencil/gpencil_fill.c +++ b/source/blender/editors/gpencil/gpencil_fill.c @@ -1689,7 +1689,7 @@ static tGPDfill *gpencil_session_init_fill(bContext *C, wmOperator *op) tgpf->gpd = gpd; tgpf->gpl = BKE_gpencil_layer_active_get(gpd); if (tgpf->gpl == NULL) { - tgpf->gpl = BKE_gpencil_layer_addnew(tgpf->gpd, DATA_("GP_Layer"), true); + tgpf->gpl = BKE_gpencil_layer_addnew(tgpf->gpd, DATA_("GP_Layer"), true, false); } tgpf->lock_axis = ts->gp_sculpt.lock_axis; diff --git a/source/blender/editors/gpencil/gpencil_intern.h b/source/blender/editors/gpencil/gpencil_intern.h index 09200125cb7..9f48c81c8f1 100644 --- a/source/blender/editors/gpencil/gpencil_intern.h +++ b/source/blender/editors/gpencil/gpencil_intern.h @@ -538,6 +538,7 @@ void GPENCIL_OT_material_lock_unused(struct wmOperatorType *ot); void GPENCIL_OT_material_select(struct wmOperatorType *ot); void GPENCIL_OT_material_set(struct wmOperatorType *ot); void GPENCIL_OT_set_active_material(struct wmOperatorType *ot); +void GPENCIL_OT_materials_copy_to_object(struct wmOperatorType *ot); /* convert old 2.7 files to 2.8 */ void GPENCIL_OT_convert_old_files(struct wmOperatorType *ot); diff --git a/source/blender/editors/gpencil/gpencil_ops.c b/source/blender/editors/gpencil/gpencil_ops.c index 7d454eb3be1..698d784c3b0 100644 --- a/source/blender/editors/gpencil/gpencil_ops.c +++ b/source/blender/editors/gpencil/gpencil_ops.c @@ -651,6 +651,7 @@ void ED_operatortypes_gpencil(void) WM_operatortype_append(GPENCIL_OT_material_to_vertex_color); WM_operatortype_append(GPENCIL_OT_extract_palette_vertex); + WM_operatortype_append(GPENCIL_OT_materials_copy_to_object); WM_operatortype_append(GPENCIL_OT_transform_fill); WM_operatortype_append(GPENCIL_OT_reset_transform_fill); diff --git a/source/blender/editors/gpencil/gpencil_paint.c b/source/blender/editors/gpencil/gpencil_paint.c index d072d8a35df..30d85b09974 100644 --- a/source/blender/editors/gpencil/gpencil_paint.c +++ b/source/blender/editors/gpencil/gpencil_paint.c @@ -2120,7 +2120,7 @@ static void gpencil_paint_initstroke(tGPsdata *p, /* get active layer (or add a new one if non-existent) */ p->gpl = BKE_gpencil_layer_active_get(p->gpd); if (p->gpl == NULL) { - p->gpl = BKE_gpencil_layer_addnew(p->gpd, DATA_("GP_Layer"), true); + p->gpl = BKE_gpencil_layer_addnew(p->gpd, DATA_("GP_Layer"), true, false); changed = true; if (p->custom_color[3]) { copy_v3_v3(p->gpl->color, p->custom_color); @@ -3677,14 +3677,6 @@ static int gpencil_draw_modal(bContext *C, wmOperator *op, const wmEvent *event) * is essential for ensuring that they can quickly return to that view */ } - else if ((event->type == EVT_BKEY) && (event->val == KM_RELEASE)) { - /* Add Blank Frame - * - Since this operator is non-modal, we can just call it here, and keep going... - * - This operator is especially useful when animating - */ - WM_operator_name_call(C, "GPENCIL_OT_blank_frame_add", WM_OP_EXEC_DEFAULT, NULL); - estate = OPERATOR_RUNNING_MODAL; - } else if ((!ELEM(p->paintmode, GP_PAINTMODE_ERASER, GP_PAINTMODE_SET_CP))) { gpencil_guide_event_handling(C, op, event, p); estate = OPERATOR_RUNNING_MODAL; diff --git a/source/blender/editors/gpencil/gpencil_primitive.c b/source/blender/editors/gpencil/gpencil_primitive.c index 3d20e32ed49..1b0a40b1be1 100644 --- a/source/blender/editors/gpencil/gpencil_primitive.c +++ b/source/blender/editors/gpencil/gpencil_primitive.c @@ -314,7 +314,7 @@ static void gpencil_primitive_set_initdata(bContext *C, tGPDprimitive *tgpi) /* if layer doesn't exist, create a new one */ if (gpl == NULL) { - gpl = BKE_gpencil_layer_addnew(tgpi->gpd, DATA_("Primitives"), true); + gpl = BKE_gpencil_layer_addnew(tgpi->gpd, DATA_("Primitives"), true, false); } tgpi->gpl = gpl; diff --git a/source/blender/editors/gpencil/gpencil_trace_ops.c b/source/blender/editors/gpencil/gpencil_trace_ops.c index d2e5fa3db32..cd75f9313ad 100644 --- a/source/blender/editors/gpencil/gpencil_trace_ops.c +++ b/source/blender/editors/gpencil/gpencil_trace_ops.c @@ -207,7 +207,7 @@ static void trace_initialize_job_data(TraceJob *trace_job) trace_job->gpd = (bGPdata *)trace_job->ob_gpencil->data; trace_job->gpl = BKE_gpencil_layer_active_get(trace_job->gpd); if (trace_job->gpl == NULL) { - trace_job->gpl = BKE_gpencil_layer_addnew(trace_job->gpd, DATA_("Trace"), true); + trace_job->gpl = BKE_gpencil_layer_addnew(trace_job->gpd, DATA_("Trace"), true, false); } } diff --git a/source/blender/editors/gpencil/gpencil_utils.c b/source/blender/editors/gpencil/gpencil_utils.c index 8d42024a518..04764587ebe 100644 --- a/source/blender/editors/gpencil/gpencil_utils.c +++ b/source/blender/editors/gpencil/gpencil_utils.c @@ -543,6 +543,38 @@ bool gpencil_stroke_inside_circle(const float mval[2], int rad, int x0, int y0, } /* ******************************************************** */ +/* Selection Validity Testing */ + +bool ED_gpencil_frame_has_selected_stroke(const bGPDframe *gpf) +{ + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { + if (gps->flag & GP_STROKE_SELECT) { + return true; + } + } + + return false; +} + +bool ED_gpencil_layer_has_selected_stroke(const bGPDlayer *gpl, const bool is_multiedit) +{ + bGPDframe *init_gpf = (is_multiedit) ? gpl->frames.first : gpl->actframe; + for (bGPDframe *gpf = init_gpf; gpf; gpf = gpf->next) { + if ((gpf == gpl->actframe) || ((gpf->flag & GP_FRAME_SELECT) && (is_multiedit))) { + if (ED_gpencil_frame_has_selected_stroke(gpf)) { + return true; + } + } + /* If not multiedit, exit loop. */ + if (!is_multiedit) { + break; + } + } + + return false; +} + +/* ******************************************************** */ /* Stroke Validity Testing */ /* Check whether given stroke can be edited given the supplied context */ @@ -1244,7 +1276,6 @@ void ED_gpencil_stroke_reproject(Depsgraph *depsgraph, } else { /* Geometry - Snap to surfaces of visible geometry */ - float ray_start[3]; float ray_normal[3]; /* magic value for initial depth copied from the default * value of Python's Scene.ray_cast function @@ -1253,13 +1284,14 @@ void ED_gpencil_stroke_reproject(Depsgraph *depsgraph, float location[3] = {0.0f, 0.0f, 0.0f}; float normal[3] = {0.0f, 0.0f, 0.0f}; - ED_view3d_win_to_ray(region, xy, &ray_start[0], &ray_normal[0]); + ED_view3d_win_to_vector(region, xy, &ray_normal[0]); + BLI_assert(gps->flag & GP_STROKE_3DSPACE); if (ED_transform_snap_object_project_ray(sctx, depsgraph, &(const struct SnapObjectParams){ .snap_select = SNAP_ALL, }, - &ray_start[0], + &pt2.x, &ray_normal[0], &depth, &location[0], diff --git a/source/blender/editors/include/ED_gpencil.h b/source/blender/editors/include/ED_gpencil.h index 1df3d993c39..bad080e1609 100644 --- a/source/blender/editors/include/ED_gpencil.h +++ b/source/blender/editors/include/ED_gpencil.h @@ -144,6 +144,8 @@ bool ED_gpencil_data_owner_is_annotation(struct PointerRNA *owner_ptr); bool ED_gpencil_has_keyframe_v3d(struct Scene *scene, struct Object *ob, int cfra); /* ----------- Stroke Editing Utilities ---------------- */ +bool ED_gpencil_frame_has_selected_stroke(const struct bGPDframe *gpf); +bool ED_gpencil_layer_has_selected_stroke(const struct bGPDlayer *gpl, const bool is_multiedit); bool ED_gpencil_stroke_can_use_direct(const struct ScrArea *area, const struct bGPDstroke *gps); bool ED_gpencil_stroke_can_use(const struct bContext *C, const struct bGPDstroke *gps); diff --git a/source/blender/editors/include/ED_object.h b/source/blender/editors/include/ED_object.h index 8279268bfd0..6f88ab4253a 100644 --- a/source/blender/editors/include/ED_object.h +++ b/source/blender/editors/include/ED_object.h @@ -247,6 +247,7 @@ void ED_object_texture_paint_mode_enter(struct bContext *C); void ED_object_texture_paint_mode_exit_ex(struct Main *bmain, struct Scene *scene, Object *ob); void ED_object_texture_paint_mode_exit(struct bContext *C); +bool ED_object_particle_edit_mode_supported(const Object *ob); void ED_object_particle_edit_mode_enter_ex(struct Depsgraph *depsgraph, struct Scene *scene, Object *ob); diff --git a/source/blender/editors/include/ED_screen.h b/source/blender/editors/include/ED_screen.h index c46f0f78eb5..bdd7ec571dc 100644 --- a/source/blender/editors/include/ED_screen.h +++ b/source/blender/editors/include/ED_screen.h @@ -200,8 +200,6 @@ ScrArea *ED_screen_areas_iter_next(const bScreen *screen, const ScrArea *area); /* screens */ void ED_screens_init(struct Main *bmain, struct wmWindowManager *wm); void ED_screen_draw_edges(struct wmWindow *win); -void ED_screen_draw_join_highlight(struct ScrArea *sa1, struct ScrArea *sa2); -void ED_screen_draw_split_preview(struct ScrArea *area, const int dir, const float fac); void ED_screen_refresh(struct wmWindowManager *wm, struct wmWindow *win); void ED_screen_ensure_updated(struct wmWindowManager *wm, struct wmWindow *win, @@ -450,10 +448,10 @@ enum { }; /* SCREEN_OT_space_context_cycle direction */ -enum { +typedef enum eScreenCycle { SPACE_CONTEXT_CYCLE_PREV, SPACE_CONTEXT_CYCLE_NEXT, -}; +} eScreenCycle; #ifdef __cplusplus } diff --git a/source/blender/editors/interface/interface.c b/source/blender/editors/interface/interface.c index 90d604b3190..a31efefd99c 100644 --- a/source/blender/editors/interface/interface.c +++ b/source/blender/editors/interface/interface.c @@ -2327,6 +2327,14 @@ bool ui_but_is_float(const uiBut *but) return false; } +PropertyScaleType ui_but_scale_type(const uiBut *but) +{ + if (but->rnaprop) { + return RNA_property_ui_scale(but->rnaprop); + } + return PROP_SCALE_LINEAR; +} + bool ui_but_is_bool(const uiBut *but) { if (ELEM(but->type, diff --git a/source/blender/editors/interface/interface_eyedropper.c b/source/blender/editors/interface/interface_eyedropper.c index 178f663ff58..e4f502950ab 100644 --- a/source/blender/editors/interface/interface_eyedropper.c +++ b/source/blender/editors/interface/interface_eyedropper.c @@ -130,14 +130,19 @@ void eyedropper_draw_cursor_text_region(const struct bContext *C, const char *name) { wmWindow *win = CTX_wm_window(C); - const int x = win->eventstate->x - region->winrct.xmin; - const int y = win->eventstate->y - region->winrct.ymin; + const int x = win->eventstate->x; + const int y = win->eventstate->y; if ((name[0] == '\0') || (BLI_rcti_isect_pt(®ion->winrct, x, y) == false)) { return; } - eyedropper_draw_cursor_text_ex(x, y, name); + const int mval[2] = { + x - region->winrct.xmin, + y - region->winrct.ymin, + }; + + eyedropper_draw_cursor_text_ex(mval[0], mval[1], name); } /** diff --git a/source/blender/editors/interface/interface_handlers.c b/source/blender/editors/interface/interface_handlers.c index 5a254db0eec..282d470c7ea 100644 --- a/source/blender/editors/interface/interface_handlers.c +++ b/source/blender/editors/interface/interface_handlers.c @@ -126,6 +126,24 @@ #define UI_MAX_PASSWORD_STR 128 /** + * This is a lower limit on the soft minimum of the range. + * Usually the derived lower limit from the visible precision is higher, + * so this number is the backup minimum. + * + * Logarithmic scale does not work with a minimum value of zero, + * but we want to support it anyway. It is set to 0.5e... for + * correct rounding since when the tweaked value is lower than + * the log minimum (lower limit), it will snap to 0. + */ +#define UI_PROP_SCALE_LOG_MIN 0.5e-8f +/** + * This constant defines an offset for the precision change in + * snap rounding, when going to higher values. It is set to + * `0.5 - log10(3) = 0.03` to make the switch at `0.3` values. + */ +#define UI_PROP_SCALE_LOG_SNAP_OFFSET 0.03f + +/** * When #USER_CONTINUOUS_MOUSE is disabled or tablet input is used, * Use this as a maximum soft range for mapping cursor motion to the value. * Otherwise min/max of #FLT_MAX, #INT_MAX cause small adjustments to jump to large numbers. @@ -477,6 +495,7 @@ static bool ui_do_but_extra_operator_icon(bContext *C, static void ui_do_but_extra_operator_icons_mousemove(uiBut *but, uiHandleButtonData *data, const wmEvent *event); +static void ui_numedit_begin_set_values(uiBut *but, uiHandleButtonData *data); #ifdef USE_DRAG_MULTINUM static void ui_multibut_restore(bContext *C, uiHandleButtonData *data, uiBlock *block); @@ -3361,6 +3380,8 @@ static void ui_textedit_begin(bContext *C, uiBut *but, uiHandleButtonData *data) if (is_num_but) { BLI_assert(data->is_str_dynamic == false); ui_but_convert_to_unit_alt_name(but, data->str, data->maxlen); + + ui_numedit_begin_set_values(but, data); } /* won't change from now on */ @@ -3897,6 +3918,14 @@ static void ui_do_but_textedit_select( /** \name Button Number Editing (various types) * \{ */ +static void ui_numedit_begin_set_values(uiBut *but, uiHandleButtonData *data) +{ + data->startvalue = ui_but_value_get(but); + data->origvalue = data->startvalue; + data->value = data->origvalue; + but->editval = &data->value; +} + static void ui_numedit_begin(uiBut *but, uiHandleButtonData *data) { if (but->type == UI_BTYPE_CURVE) { @@ -3922,19 +3951,21 @@ static void ui_numedit_begin(uiBut *but, uiHandleButtonData *data) but->editvec = data->vec; } else { - float softrange, softmin, softmax; + ui_numedit_begin_set_values(but, data); - data->startvalue = ui_but_value_get(but); - data->origvalue = data->startvalue; - data->value = data->origvalue; - but->editval = &data->value; + float softmin = but->softmin; + float softmax = but->softmax; + float softrange = softmax - softmin; + const PropertyScaleType scale_type = ui_but_scale_type(but); - softmin = but->softmin; - softmax = but->softmax; - softrange = softmax - softmin; + float log_min = (scale_type == PROP_SCALE_LOG) ? max_ff(softmin, UI_PROP_SCALE_LOG_MIN) : 0.0f; if ((but->type == UI_BTYPE_NUM) && (ui_but_is_cursor_warp(but) == false)) { uiButNumber *number_but = (uiButNumber *)but; + + if (scale_type == PROP_SCALE_LOG) { + log_min = max_ff(log_min, powf(10, -number_but->precision) * 0.5f); + } /* Use a minimum so we have a predictable range, * otherwise some float buttons get a large range. */ const float value_step_float_min = 0.1f; @@ -3983,7 +4014,31 @@ static void ui_numedit_begin(uiBut *but, uiHandleButtonData *data) } } - data->dragfstart = (softrange == 0.0f) ? 0.0f : ((float)data->value - softmin) / softrange; + if (softrange == 0.0f) { + data->dragfstart = 0.0f; + } + else { + switch (scale_type) { + case PROP_SCALE_LINEAR: { + data->dragfstart = ((float)data->value - softmin) / softrange; + break; + } + case PROP_SCALE_LOG: { + BLI_assert(log_min != 0.0f); + const float base = softmax / log_min; + data->dragfstart = logf((float)data->value / log_min) / logf(base); + break; + } + case PROP_SCALE_CUBIC: { + const float cubic_min = cube_f(softmin); + const float cubic_max = cube_f(softmax); + const float cubic_range = cubic_max - cubic_min; + const float f = ((float)data->value - softmin) * cubic_range / softrange + cubic_min; + data->dragfstart = (cbrtf(f) - softmin) / softrange; + break; + } + } + } data->dragf = data->dragfstart; data->drag_map_soft_min = softmin; @@ -4701,6 +4756,7 @@ static float ui_numedit_apply_snapf( /* pass */ } else { + const PropertyScaleType scale_type = ui_but_scale_type(but); float softrange = softmax - softmin; float fac = 1.0f; @@ -4738,31 +4794,30 @@ static float ui_numedit_apply_snapf( } } - if (snap == SNAP_ON) { - if (softrange < 2.10f) { - tempf = roundf(tempf * 10.0f) * 0.1f; - } - else if (softrange < 21.0f) { - tempf = roundf(tempf); - } - else { - tempf = roundf(tempf * 0.1f) * 10.0f; - } - } - else if (snap == SNAP_ON_SMALL) { - if (softrange < 2.10f) { - tempf = roundf(tempf * 100.0f) * 0.01f; - } - else if (softrange < 21.0f) { - tempf = roundf(tempf * 10.0f) * 0.1f; + BLI_assert(ELEM(snap, SNAP_ON, SNAP_ON_SMALL)); + switch (scale_type) { + case PROP_SCALE_LINEAR: + case PROP_SCALE_CUBIC: { + const float snap_fac = (snap == SNAP_ON_SMALL ? 0.1f : 1.0f); + if (softrange < 2.10f) { + tempf = roundf(tempf * 10.0f / snap_fac) * 0.1f * snap_fac; + } + else if (softrange < 21.0f) { + tempf = roundf(tempf / snap_fac) * snap_fac; + } + else { + tempf = roundf(tempf * 0.1f / snap_fac) * 10.0f * snap_fac; + } + break; } - else { - tempf = roundf(tempf); + case PROP_SCALE_LOG: { + const float snap_fac = powf(10.0f, + roundf(log10f(tempf) + UI_PROP_SCALE_LOG_SNAP_OFFSET) - + (snap == SNAP_ON_SMALL ? 2.0f : 1.0f)); + tempf = roundf(tempf / snap_fac) * snap_fac; + break; } } - else { - BLI_assert(0); - } if (fac != 1.0f) { tempf *= fac; @@ -4807,6 +4862,7 @@ static bool ui_numedit_but_NUM(uiButNumber *number_but, int lvalue, temp; bool changed = false; const bool is_float = ui_but_is_float(but); + const PropertyScaleType scale_type = ui_but_scale_type(but); /* prevent unwanted drag adjustments, test motion so modifier keys refresh. */ if ((is_motion || data->draglock) && (ui_but_dragedit_update_mval(data, mx) == false)) { @@ -4818,21 +4874,74 @@ static bool ui_numedit_but_NUM(uiButNumber *number_but, const float softmax = but->softmax; const float softrange = softmax - softmin; + const float log_min = (scale_type == PROP_SCALE_LOG) ? + max_ff(max_ff(softmin, UI_PROP_SCALE_LOG_MIN), + powf(10, -number_but->precision) * 0.5f) : + 0; + /* Mouse location isn't screen clamped to the screen so use a linear mapping * 2px == 1-int, or 1px == 1-ClickStep */ if (is_float) { fac *= 0.01f * number_but->step_size; - tempf = (float)data->startvalue + ((float)(mx - data->dragstartx) * fac); + switch (scale_type) { + case PROP_SCALE_LINEAR: { + tempf = (float)data->startvalue + (float)(mx - data->dragstartx) * fac; + break; + } + case PROP_SCALE_LOG: { + const float startvalue = max_ff((float)data->startvalue, log_min); + tempf = expf((float)(mx - data->dragstartx) * fac) * startvalue; + if (tempf <= log_min) { + tempf = 0.0f; + } + break; + } + case PROP_SCALE_CUBIC: { + tempf = cbrtf((float)data->startvalue) + (float)(mx - data->dragstartx) * fac; + tempf *= tempf * tempf; + break; + } + } + tempf = ui_numedit_apply_snapf(but, tempf, softmin, softmax, snap); #if 1 /* fake moving the click start, nicer for dragging back after passing the limit */ - if (tempf < softmin) { - data->dragstartx -= (softmin - tempf) / fac; - tempf = softmin; - } - else if (tempf > softmax) { - data->dragstartx += (tempf - softmax) / fac; - tempf = softmax; + switch (scale_type) { + case PROP_SCALE_LINEAR: { + if (tempf < softmin) { + data->dragstartx -= (softmin - tempf) / fac; + tempf = softmin; + } + else if (tempf > softmax) { + data->dragstartx -= (softmax - tempf) / fac; + tempf = softmax; + } + break; + } + case PROP_SCALE_LOG: { + if (tempf < log_min) { + data->dragstartx -= logf(log_min / (float)data->startvalue) / fac - + (float)(mx - data->dragstartx); + tempf = softmin; + } + else if (tempf > softmax) { + data->dragstartx -= logf(softmax / (float)data->startvalue) / fac - + (float)(mx - data->dragstartx); + tempf = softmax; + } + break; + } + case PROP_SCALE_CUBIC: { + if (tempf < softmin) { + data->dragstartx = mx - (int)((cbrtf(softmin) - cbrtf((float)data->startvalue)) / fac); + tempf = softmin; + } + else if (tempf > softmax) { + data->dragstartx = mx - (int)((cbrtf(softmax) - cbrtf((float)data->startvalue)) / fac); + tempf = softmax; + } + break; + } } #else CLAMP(tempf, softmin, softmax); @@ -4939,7 +5048,31 @@ static bool ui_numedit_but_NUM(uiButNumber *number_but, } data->draglastx = mx; - tempf = (softmin + data->dragf * softrange); + + switch (scale_type) { + case PROP_SCALE_LINEAR: { + tempf = (softmin + data->dragf * softrange); + break; + } + case PROP_SCALE_LOG: { + const float log_min = max_ff(max_ff(softmin, UI_PROP_SCALE_LOG_MIN), + powf(10.0f, -number_but->precision) * 0.5f); + const float base = softmax / log_min; + tempf = powf(base, data->dragf) * log_min; + if (tempf <= log_min) { + tempf = 0.0f; + } + break; + } + case PROP_SCALE_CUBIC: { + tempf = (softmin + data->dragf * softrange); + tempf *= tempf * tempf; + float cubic_min = softmin * softmin * softmin; + float cubic_max = softmax * softmax * softmax; + tempf = (tempf - cubic_min) / (cubic_max - cubic_min) * softrange + softmin; + break; + } + } if (!is_float) { temp = round_fl_to_int(tempf); @@ -5186,9 +5319,19 @@ static int ui_do_but_NUM( else { /* Float Value. */ if (but->drawflag & (UI_BUT_ACTIVE_LEFT | UI_BUT_ACTIVE_RIGHT)) { + const PropertyScaleType scale_type = ui_but_scale_type(but); + button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); - const double value_step = (double)number_but->step_size * UI_PRECISION_FLOAT_SCALE; + double value_step; + if (scale_type == PROP_SCALE_LOG) { + value_step = powf(10.0f, + (roundf(log10f(data->value) + UI_PROP_SCALE_LOG_SNAP_OFFSET) - 1.0f) + + log10f(number_but->step_size)); + } + else { + value_step = (double)number_but->step_size * UI_PRECISION_FLOAT_SCALE; + } BLI_assert(value_step > 0.0f); const double value_test = (but->drawflag & UI_BUT_ACTIVE_LEFT) ? (double)max_ff(but->softmin, @@ -5236,6 +5379,8 @@ static bool ui_numedit_but_SLI(uiBut *but, return changed; } + const PropertyScaleType scale_type = ui_but_scale_type(but); + softmin = but->softmin; softmax = but->softmax; softrange = softmax - softmin; @@ -5277,7 +5422,24 @@ static bool ui_numedit_but_SLI(uiBut *but, #endif /* done correcting mouse */ - tempf = softmin + f * softrange; + switch (scale_type) { + case PROP_SCALE_LINEAR: { + tempf = softmin + f * softrange; + break; + } + case PROP_SCALE_LOG: { + tempf = powf(softmax / softmin, f) * softmin; + break; + } + case PROP_SCALE_CUBIC: { + const float cubicmin = cube_f(softmin); + const float cubicmax = cube_f(softmax); + const float cubicrange = cubicmax - cubicmin; + tempf = cube_f(softmin + f * softrange); + tempf = (tempf - cubicmin) / cubicrange * softrange + softmin; + break; + } + } temp = round_fl_to_int(tempf); if (snap) { @@ -5471,6 +5633,8 @@ static int ui_do_but_SLI( if (click) { if (click == 2) { + const PropertyScaleType scale_type = ui_but_scale_type(but); + /* nudge slider to the left or right */ float f, tempf, softmin, softmax, softrange; int temp; @@ -5495,14 +5659,20 @@ static int ui_do_but_SLI( f = (float)(mx - but->rect.xmin) / (BLI_rctf_size_x(&but->rect)); } - f = softmin + f * softrange; + if (scale_type == PROP_SCALE_LOG) { + f = powf(softmax / softmin, f) * softmin; + } + else { + f = softmin + f * softrange; + } if (!ui_but_is_float(but)) { + int value_step = 1; if (f < temp) { - temp--; + temp -= value_step; } else { - temp++; + temp += value_step; } if (temp >= softmin && temp <= softmax) { @@ -5513,14 +5683,23 @@ static int ui_do_but_SLI( } } else { - if (f < tempf) { - tempf -= 0.01f; - } - else { - tempf += 0.01f; - } - if (tempf >= softmin && tempf <= softmax) { + float value_step; + if (scale_type == PROP_SCALE_LOG) { + value_step = powf(10.0f, roundf(log10f(tempf) + UI_PROP_SCALE_LOG_SNAP_OFFSET) - 1.0f); + } + else { + value_step = 0.01f; + } + + if (f < tempf) { + tempf -= value_step; + } + else { + tempf += value_step; + } + + CLAMP(tempf, softmin, softmax); data->value = tempf; } else { diff --git a/source/blender/editors/interface/interface_intern.h b/source/blender/editors/interface/interface_intern.h index 333dc1beb22..40a3f0d330f 100644 --- a/source/blender/editors/interface/interface_intern.h +++ b/source/blender/editors/interface/interface_intern.h @@ -660,6 +660,7 @@ bool ui_but_context_poll_operator(struct bContext *C, struct wmOperatorType *ot, extern void ui_but_update(uiBut *but); extern void ui_but_update_edited(uiBut *but); +extern PropertyScaleType ui_but_scale_type(const uiBut *but) ATTR_WARN_UNUSED_RESULT; extern bool ui_but_is_float(const uiBut *but) ATTR_WARN_UNUSED_RESULT; extern bool ui_but_is_bool(const uiBut *but) ATTR_WARN_UNUSED_RESULT; extern bool ui_but_is_unit(const uiBut *but) ATTR_WARN_UNUSED_RESULT; diff --git a/source/blender/editors/interface/interface_widgets.c b/source/blender/editors/interface/interface_widgets.c index c9c1f56dc98..d2e6165a20f 100644 --- a/source/blender/editors/interface/interface_widgets.c +++ b/source/blender/editors/interface/interface_widgets.c @@ -3752,12 +3752,35 @@ static void widget_numslider( float factor, factor_ui; float factor_discard = 1.0f; /* No discard. */ const float value = (float)ui_but_value_get(but); - - if (but->rnaprop && (RNA_property_subtype(but->rnaprop) == PROP_PERCENTAGE)) { - factor = value / but->softmax; - } - else { - factor = (value - but->softmin) / (but->softmax - but->softmin); + const float softmin = but->softmin; + const float softmax = but->softmax; + const float softrange = softmax - softmin; + const PropertyScaleType scale_type = ui_but_scale_type(but); + + switch (scale_type) { + case PROP_SCALE_LINEAR: { + if (but->rnaprop && (RNA_property_subtype(but->rnaprop) == PROP_PERCENTAGE)) { + factor = value / softmax; + } + else { + factor = (value - softmin) / softrange; + } + break; + } + case PROP_SCALE_LOG: { + const float logmin = fmaxf(softmin, 0.5e-8f); + const float base = softmax / logmin; + factor = logf(value / logmin) / logf(base); + break; + } + case PROP_SCALE_CUBIC: { + const float cubicmin = cube_f(softmin); + const float cubicmax = cube_f(softmax); + const float cubicrange = cubicmax - cubicmin; + const float f = (value - softmin) * cubicrange / softrange + cubicmin; + factor = (cbrtf(f) - softmin) / softrange; + break; + } } const float width = (float)BLI_rcti_size_x(rect); diff --git a/source/blender/editors/io/io_alembic.c b/source/blender/editors/io/io_alembic.c index 592467c2a85..28838d677f0 100644 --- a/source/blender/editors/io/io_alembic.c +++ b/source/blender/editors/io/io_alembic.c @@ -121,6 +121,7 @@ static int wm_alembic_export_exec(bContext *C, wmOperator *op) .uvs = RNA_boolean_get(op->ptr, "uvs"), .normals = RNA_boolean_get(op->ptr, "normals"), .vcolors = RNA_boolean_get(op->ptr, "vcolors"), + .orcos = RNA_boolean_get(op->ptr, "orcos"), .apply_subdiv = RNA_boolean_get(op->ptr, "apply_subdiv"), .curves_as_mesh = RNA_boolean_get(op->ptr, "curves_as_mesh"), .flatten_hierarchy = RNA_boolean_get(op->ptr, "flatten"), @@ -210,6 +211,7 @@ static void ui_alembic_export_settings(uiLayout *layout, PointerRNA *imfptr) uiItemR(col, imfptr, "normals", 0, NULL, ICON_NONE); uiItemR(col, imfptr, "vcolors", 0, NULL, ICON_NONE); + uiItemR(col, imfptr, "orcos", 0, NULL, ICON_NONE); uiItemR(col, imfptr, "face_sets", 0, NULL, ICON_NONE); uiItemR(col, imfptr, "curves_as_mesh", 0, NULL, ICON_NONE); @@ -378,6 +380,12 @@ void WM_OT_alembic_export(wmOperatorType *ot) RNA_def_boolean(ot->srna, "vcolors", 0, "Vertex Colors", "Export vertex colors"); + RNA_def_boolean(ot->srna, + "orcos", + true, + "Generated Coordinates", + "Export undeformed mesh vertex coordinates"); + RNA_def_boolean( ot->srna, "face_sets", 0, "Face Sets", "Export per face shading group assignments"); diff --git a/source/blender/editors/object/CMakeLists.txt b/source/blender/editors/object/CMakeLists.txt index 77b5379ddd4..4338b043fc9 100644 --- a/source/blender/editors/object/CMakeLists.txt +++ b/source/blender/editors/object/CMakeLists.txt @@ -87,7 +87,7 @@ if(WITH_INTERNATIONAL) endif() if(WITH_EXPERIMENTAL_FEATURES) - add_definitions(-DWITH_GEOMETRY_NODES) + add_definitions(-DWITH_SIMULATION_DATABLOCK) add_definitions(-DWITH_POINT_CLOUD) add_definitions(-DWITH_HAIR_NODES) endif() diff --git a/source/blender/editors/object/object_add.c b/source/blender/editors/object/object_add.c index ce441e7b551..8cf7d60e6c4 100644 --- a/source/blender/editors/object/object_add.c +++ b/source/blender/editors/object/object_add.c @@ -1311,6 +1311,8 @@ static int object_gpencil_add_exec(bContext *C, wmOperator *op) bGPdata *gpd = (ob && (ob->type == OB_GPENCIL)) ? ob->data : NULL; const int type = RNA_enum_get(op->ptr, "type"); + const bool use_in_front = RNA_boolean_get(op->ptr, "use_in_front"); + const int stroke_depth_order = RNA_enum_get(op->ptr, "stroke_depth_order"); ushort local_view_bits; float loc[3], rot[3]; @@ -1338,6 +1340,7 @@ static int object_gpencil_add_exec(bContext *C, wmOperator *op) break; } case GP_LRT_OBJECT: + case GP_LRT_SCENE: case GP_LRT_COLLECTION: { ob_name = "Line Art"; break; @@ -1430,7 +1433,15 @@ static int object_gpencil_add_exec(bContext *C, wmOperator *op) } /* Stroke object is drawn in front of meshes by default. */ - ob->dtx |= OB_DRAW_IN_FRONT; + if (use_in_front) { + ob->dtx |= OB_DRAW_IN_FRONT; + } + else { + if (stroke_depth_order == GP_DRAWMODE_3D) { + gpd->draw_mode = GP_DRAWMODE_3D; + } + } + break; } default: @@ -1449,6 +1460,38 @@ static int object_gpencil_add_exec(bContext *C, wmOperator *op) return OPERATOR_FINISHED; } +static void object_add_ui(bContext *UNUSED(C), wmOperator *op) +{ + uiLayout *layout = op->layout; + + uiLayoutSetPropSep(layout, true); + + uiItemR(layout, op->ptr, "radius", 0, NULL, ICON_NONE); + uiItemR(layout, op->ptr, "align", 0, NULL, ICON_NONE); + uiItemR(layout, op->ptr, "location", 0, NULL, ICON_NONE); + uiItemR(layout, op->ptr, "rotation", 0, NULL, ICON_NONE); + uiItemR(layout, op->ptr, "type", 0, NULL, ICON_NONE); + + int type = RNA_enum_get(op->ptr, "type"); + if (type == GP_LRT_COLLECTION || type == GP_LRT_OBJECT || type == GP_LRT_SCENE) { + uiItemR(layout, op->ptr, "use_in_front", 0, NULL, ICON_NONE); + bool in_front = RNA_boolean_get(op->ptr, "use_in_front"); + uiLayout *row = uiLayoutRow(layout, false); + uiLayoutSetActive(row, !in_front); + uiItemR(row, op->ptr, "stroke_depth_order", 0, NULL, ICON_NONE); + } +} + +static EnumPropertyItem rna_enum_gpencil_add_stroke_depth_order_items[] = { + {GP_DRAWMODE_2D, + "2D", + 0, + "2D Layers", + "Display strokes using grease pencil layers to define order"}, + {GP_DRAWMODE_3D, "3D", 0, "3D Location", "Display strokes using real 3D position in 3D space"}, + {0, NULL, 0, NULL, NULL}, +}; + void OBJECT_OT_gpencil_add(wmOperatorType *ot) { /* identifiers */ @@ -1464,11 +1507,26 @@ void OBJECT_OT_gpencil_add(wmOperatorType *ot) /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + /* ui */ + ot->ui = object_add_ui; + /* properties */ ED_object_add_unit_props_radius(ot); ED_object_add_generic_props(ot, false); ot->prop = RNA_def_enum(ot->srna, "type", rna_enum_object_gpencil_type_items, 0, "Type", ""); + RNA_def_boolean(ot->srna, + "use_in_front", + false, + "In Front", + "Show line art grease pencil in front of everything"); + RNA_def_enum( + ot->srna, + "stroke_depth_order", + rna_enum_gpencil_add_stroke_depth_order_items, + GP_DRAWMODE_3D, + "Stroke Depth Order", + "Defines how the strokes are ordered in 3D space for objects not displayed 'In Front'"); } /** \} */ @@ -2841,6 +2899,7 @@ static int object_convert_exec(bContext *C, wmOperator *op) me_eval = BKE_mesh_copy_for_eval(me_eval, false); /* Full (edge-angle based) draw calculation should ideally be performed. */ BKE_mesh_edges_set_draw_render(me_eval); + BKE_object_material_from_eval_data(bmain, newob, &me_eval->id); BKE_mesh_nomain_to_mesh(me_eval, newob->data, newob, &CD_MASK_MESH, true); BKE_object_free_modifiers(newob, 0); /* after derivedmesh calls! */ } diff --git a/source/blender/editors/object/object_bake_api.c b/source/blender/editors/object/object_bake_api.c index f651f5bc3fd..3370476d466 100644 --- a/source/blender/editors/object/object_bake_api.c +++ b/source/blender/editors/object/object_bake_api.c @@ -669,9 +669,11 @@ static void bake_targets_clear(Main *bmain, const bool is_tangent) } /* create new mesh with edit mode changes and modifiers applied */ -static Mesh *bake_mesh_new_from_object(Object *object) +static Mesh *bake_mesh_new_from_object(Depsgraph *depsgraph, + Object *object, + const bool preserve_origindex) { - Mesh *me = BKE_mesh_new_from_object(NULL, object, false); + Mesh *me = BKE_mesh_new_from_object(depsgraph, object, false, preserve_origindex); if (me->flag & ME_AUTOSMOOTH) { BKE_mesh_split_faces(me, true); @@ -961,10 +963,39 @@ static bool bake_targets_init_vertex_colors(BakeTargets *targets, Object *ob, Re return true; } +static int find_original_loop(const Mesh *me_orig, + const int *vert_origindex, + const int *poly_origindex, + const int poly_eval, + const int vert_eval) +{ + /* Get original vertex and polygon index. There is currently no loop mapping + * in modifier stack evaluation. */ + const int vert_orig = vert_origindex[vert_eval]; + const int poly_orig = poly_origindex[poly_eval]; + + if (vert_orig == ORIGINDEX_NONE || poly_orig == ORIGINDEX_NONE) { + return ORIGINDEX_NONE; + } + + /* Find matching loop with original vertex in original polygon. */ + MPoly *mpoly_orig = me_orig->mpoly + poly_orig; + MLoop *mloop_orig = me_orig->mloop + mpoly_orig->loopstart; + for (int j = 0; j < mpoly_orig->totloop; ++j, ++mloop_orig) { + if (mloop_orig->v == vert_orig) { + return mpoly_orig->loopstart + j; + } + } + + return ORIGINDEX_NONE; +} + static void bake_targets_populate_pixels_vertex_colors(BakeTargets *targets, - Mesh *me, + Object *ob, + Mesh *me_eval, BakePixel *pixel_array) { + Mesh *me = ob->data; const int num_pixels = targets->num_pixels; /* Initialize blank pixels. */ @@ -983,16 +1014,31 @@ static void bake_targets_populate_pixels_vertex_colors(BakeTargets *targets, } /* Populate through adjacent triangles, first triangle wins. */ - const int tottri = poly_to_tri_count(me->totpoly, me->totloop); + const int tottri = poly_to_tri_count(me_eval->totpoly, me_eval->totloop); MLoopTri *looptri = MEM_mallocN(sizeof(*looptri) * tottri, __func__); - BKE_mesh_recalc_looptri(me->mloop, me->mpoly, me->mvert, me->totloop, me->totpoly, looptri); + BKE_mesh_recalc_looptri( + me_eval->mloop, me_eval->mpoly, me_eval->mvert, me_eval->totloop, me_eval->totpoly, looptri); + + /* For mapping back to original mesh in case there are modifiers. */ + const int *vert_origindex = CustomData_get_layer(&me_eval->vdata, CD_ORIGINDEX); + const int *poly_origindex = CustomData_get_layer(&me_eval->pdata, CD_ORIGINDEX); for (int i = 0; i < tottri; i++) { const MLoopTri *lt = &looptri[i]; for (int j = 0; j < 3; j++) { - const unsigned int l = lt->tri[j]; + unsigned int l = lt->tri[j]; + unsigned int v = me_eval->mloop[l].v; + + /* Map back to original loop if there are modifiers. */ + if (vert_origindex != NULL && poly_origindex != NULL) { + l = find_original_loop(me, vert_origindex, poly_origindex, lt->poly, v); + if (l == ORIGINDEX_NONE || l >= me->totloop) { + continue; + } + } + BakePixel *pixel = &pixel_array[l]; if (pixel->primitive_id != -1) { @@ -1004,7 +1050,7 @@ static void bake_targets_populate_pixels_vertex_colors(BakeTargets *targets, /* Seed is the vertex, so that sampling noise is coherent for the same * vertex, but different corners can still have different normals, * materials and UVs. */ - pixel->seed = me->mloop[l].v; + pixel->seed = v; /* Barycentric coordinates, nudged a bit to avoid precision issues that * may happen when exactly at the vertex coordinate. */ @@ -1043,7 +1089,7 @@ static void bake_result_add_to_rgba(float rgba[4], const float *result, const in } } -static bool bake_targets_output_vertex_colors(BakeTargets *targets, Object *ob, Mesh *me_split) +static bool bake_targets_output_vertex_colors(BakeTargets *targets, Object *ob) { Mesh *me = ob->data; MPropCol *mcol = CustomData_get_layer(&me->vdata, CD_PROP_COLOR); @@ -1052,11 +1098,6 @@ static bool bake_targets_output_vertex_colors(BakeTargets *targets, Object *ob, const int num_channels = targets->num_channels; const float *result = targets->result; - /* We bake using a mesh with additional vertices for split normals, but the - * number of loops must match to be able to transfer the vertex colors. */ - BLI_assert(me->totloop == me_split->totloop); - UNUSED_VARS_NDEBUG(me_split); - if (mcol_valid) { const int totvert = me->totvert; const int totloop = me->totloop; @@ -1111,16 +1152,17 @@ static bool bake_targets_output_vertex_colors(BakeTargets *targets, Object *ob, static bool bake_targets_init(const BakeAPIRender *bkr, BakeTargets *targets, Object *ob, + Object *ob_eval, ReportList *reports) { if (bkr->target == R_BAKE_TARGET_IMAGE_TEXTURES) { if (bkr->save_mode == R_BAKE_SAVE_INTERNAL) { - if (!bake_targets_init_internal(bkr, targets, ob, reports)) { + if (!bake_targets_init_internal(bkr, targets, ob_eval, reports)) { return false; } } else if (bkr->save_mode == R_BAKE_SAVE_EXTERNAL) { - if (!bake_targets_init_external(bkr, targets, ob, reports)) { + if (!bake_targets_init_external(bkr, targets, ob_eval, reports)) { return false; } } @@ -1145,14 +1187,15 @@ static bool bake_targets_init(const BakeAPIRender *bkr, static void bake_targets_populate_pixels(const BakeAPIRender *bkr, BakeTargets *targets, - Mesh *me, + Object *ob, + Mesh *me_eval, BakePixel *pixel_array) { if (bkr->target == R_BAKE_TARGET_VERTEX_COLORS) { - bake_targets_populate_pixels_vertex_colors(targets, me, pixel_array); + bake_targets_populate_pixels_vertex_colors(targets, ob, me_eval, pixel_array); } else { - RE_bake_pixels_populate(me, pixel_array, targets->num_pixels, targets, bkr->uv_layer); + RE_bake_pixels_populate(me_eval, pixel_array, targets->num_pixels, targets, bkr->uv_layer); } } @@ -1160,7 +1203,7 @@ static bool bake_targets_output(const BakeAPIRender *bkr, BakeTargets *targets, Object *ob, Object *ob_eval, - Mesh *me, + Mesh *me_eval, BakePixel *pixel_array, ReportList *reports) { @@ -1169,11 +1212,12 @@ static bool bake_targets_output(const BakeAPIRender *bkr, return bake_targets_output_internal(bkr, targets, ob, pixel_array, reports); } if (bkr->save_mode == R_BAKE_SAVE_EXTERNAL) { - return bake_targets_output_external(bkr, targets, ob, ob_eval, me, pixel_array, reports); + return bake_targets_output_external( + bkr, targets, ob, ob_eval, me_eval, pixel_array, reports); } } else if (bkr->target == R_BAKE_TARGET_VERTEX_COLORS) { - return bake_targets_output_vertex_colors(targets, ob, me); + return bake_targets_output_vertex_colors(targets, ob); } return false; @@ -1213,8 +1257,8 @@ static int bake(const BakeAPIRender *bkr, BakeHighPolyData *highpoly = NULL; int tot_highpoly = 0; - Mesh *me_low = NULL; - Mesh *me_cage = NULL; + Mesh *me_low_eval = NULL; + Mesh *me_cage_eval = NULL; MultiresModifierData *mmd_low = NULL; int mmd_flags_low = 0; @@ -1224,6 +1268,8 @@ static int bake(const BakeAPIRender *bkr, BakeTargets targets = {NULL}; + const bool preserve_origindex = (bkr->target == R_BAKE_TARGET_VERTEX_COLORS); + RE_bake_engine_set_engine_parameters(re, bmain, scene); if (!RE_bake_has_engine(re)) { @@ -1287,10 +1333,10 @@ static int bake(const BakeAPIRender *bkr, ob_low_eval = DEG_get_evaluated_object(depsgraph, ob_low); /* get the mesh as it arrives in the renderer */ - me_low = bake_mesh_new_from_object(ob_low_eval); + me_low_eval = bake_mesh_new_from_object(depsgraph, ob_low_eval, preserve_origindex); /* Initialize bake targets. */ - if (!bake_targets_init(bkr, &targets, ob_low_eval, reports)) { + if (!bake_targets_init(bkr, &targets, ob_low, ob_low_eval, reports)) { goto cleanup; } @@ -1298,7 +1344,7 @@ static int bake(const BakeAPIRender *bkr, * it is populated later with the cage mesh (smoothed version of the mesh). */ pixel_array_low = MEM_mallocN(sizeof(BakePixel) * targets.num_pixels, "bake pixels low poly"); if ((bkr->is_selected_to_active && (ob_cage == NULL) && bkr->is_cage) == false) { - bake_targets_populate_pixels(bkr, &targets, me_low, pixel_array_low); + bake_targets_populate_pixels(bkr, &targets, ob_low, me_low_eval, pixel_array_low); } if (bkr->is_selected_to_active) { @@ -1307,8 +1353,9 @@ static int bake(const BakeAPIRender *bkr, /* prepare cage mesh */ if (ob_cage) { - me_cage = bake_mesh_new_from_object(ob_cage_eval); - if ((me_low->totpoly != me_cage->totpoly) || (me_low->totloop != me_cage->totloop)) { + me_cage_eval = bake_mesh_new_from_object(depsgraph, ob_cage_eval, preserve_origindex); + if ((me_low_eval->totpoly != me_cage_eval->totpoly) || + (me_low_eval->totloop != me_cage_eval->totloop)) { BKE_report(reports, RPT_ERROR, "Invalid cage object, the cage mesh must have the same number " @@ -1348,8 +1395,8 @@ static int bake(const BakeAPIRender *bkr, BKE_object_handle_data_update(depsgraph, scene, ob_low_eval); } - me_cage = BKE_mesh_new_from_object(NULL, ob_low_eval, false); - bake_targets_populate_pixels(bkr, &targets, me_cage, pixel_array_low); + me_cage_eval = BKE_mesh_new_from_object(NULL, ob_low_eval, false, preserve_origindex); + bake_targets_populate_pixels(bkr, &targets, ob_low, me_cage_eval, pixel_array_low); } highpoly = MEM_callocN(sizeof(BakeHighPolyData) * tot_highpoly, "bake high poly objects"); @@ -1367,7 +1414,7 @@ static int bake(const BakeAPIRender *bkr, highpoly[i].ob_eval = DEG_get_evaluated_object(depsgraph, ob_iter); highpoly[i].ob_eval->restrictflag &= ~OB_RESTRICT_RENDER; highpoly[i].ob_eval->base_flag |= (BASE_VISIBLE_DEPSGRAPH | BASE_ENABLED_RENDER); - highpoly[i].me = BKE_mesh_new_from_object(NULL, highpoly[i].ob_eval, false); + highpoly[i].me = BKE_mesh_new_from_object(NULL, highpoly[i].ob_eval, false, false); /* Low-poly to high-poly transformation matrix. */ copy_m4_m4(highpoly[i].obmat, highpoly[i].ob->obmat); @@ -1391,7 +1438,7 @@ static int bake(const BakeAPIRender *bkr, pixel_array_high = MEM_mallocN(sizeof(BakePixel) * targets.num_pixels, "bake pixels high poly"); - if (!RE_bake_pixels_populate_from_objects(me_low, + if (!RE_bake_pixels_populate_from_objects(me_low_eval, pixel_array_low, pixel_array_high, highpoly, @@ -1402,7 +1449,7 @@ static int bake(const BakeAPIRender *bkr, bkr->max_ray_distance, ob_low_eval->obmat, (ob_cage ? ob_cage->obmat : ob_low_eval->obmat), - me_cage)) { + me_cage_eval)) { BKE_report(reports, RPT_ERROR, "Error handling selected objects"); goto cleanup; } @@ -1478,7 +1525,7 @@ static int bake(const BakeAPIRender *bkr, targets.num_pixels, targets.num_channels, targets.result, - me_low, + me_low_eval, bkr->normal_swizzle, ob_low_eval->obmat); } @@ -1497,8 +1544,8 @@ static int bake(const BakeAPIRender *bkr, } /* Evaluate modifiers again. */ - me_nores = BKE_mesh_new_from_object(NULL, ob_low_eval, false); - bake_targets_populate_pixels(bkr, &targets, me_nores, pixel_array_low); + me_nores = BKE_mesh_new_from_object(NULL, ob_low_eval, false, false); + bake_targets_populate_pixels(bkr, &targets, ob_low, me_nores, pixel_array_low); RE_bake_normal_world_to_tangent(pixel_array_low, targets.num_pixels, @@ -1527,7 +1574,7 @@ static int bake(const BakeAPIRender *bkr, else { /* save the results */ if (bake_targets_output( - bkr, &targets, ob_low, ob_low_eval, me_low, pixel_array_low, reports)) { + bkr, &targets, ob_low, ob_low_eval, me_low_eval, pixel_array_low, reports)) { op_result = OPERATOR_FINISHED; } else { @@ -1562,12 +1609,12 @@ cleanup: bake_targets_free(&targets); - if (me_low != NULL) { - BKE_id_free(NULL, &me_low->id); + if (me_low_eval != NULL) { + BKE_id_free(NULL, &me_low_eval->id); } - if (me_cage != NULL) { - BKE_id_free(NULL, &me_cage->id); + if (me_cage_eval != NULL) { + BKE_id_free(NULL, &me_cage_eval->id); } DEG_graph_free(depsgraph); diff --git a/source/blender/editors/object/object_edit.c b/source/blender/editors/object/object_edit.c index 07c8e7725e3..5be572baec5 100644 --- a/source/blender/editors/object/object_edit.c +++ b/source/blender/editors/object/object_edit.c @@ -1590,30 +1590,10 @@ static const EnumPropertyItem *object_mode_set_itemsf(bContext *C, return rna_enum_object_mode_items; } - Object *ob = CTX_data_active_object(C); + const Object *ob = CTX_data_active_object(C); if (ob) { - const bool use_mode_particle_edit = (BLI_listbase_is_empty(&ob->particlesystem) == false) || - (ob->soft != NULL) || - (BKE_modifiers_findby_type(ob, eModifierType_Cloth) != - NULL); while (input->identifier) { - if ((input->value == OB_MODE_EDIT && OB_TYPE_SUPPORT_EDITMODE(ob->type)) || - (input->value == OB_MODE_POSE && (ob->type == OB_ARMATURE)) || - (input->value == OB_MODE_PARTICLE_EDIT && use_mode_particle_edit) || - (ELEM(input->value, - OB_MODE_SCULPT, - OB_MODE_VERTEX_PAINT, - OB_MODE_WEIGHT_PAINT, - OB_MODE_TEXTURE_PAINT) && - (ob->type == OB_MESH)) || - (ELEM(input->value, - OB_MODE_EDIT_GPENCIL, - OB_MODE_PAINT_GPENCIL, - OB_MODE_SCULPT_GPENCIL, - OB_MODE_WEIGHT_GPENCIL, - OB_MODE_VERTEX_GPENCIL) && - (ob->type == OB_GPENCIL)) || - (input->value == OB_MODE_OBJECT)) { + if (ED_object_mode_compat_test(ob, input->value)) { RNA_enum_item_add(&item, &totitem, input); } input++; diff --git a/source/blender/editors/object/object_modes.c b/source/blender/editors/object/object_modes.c index ff1855cafc5..dcb294bcb12 100644 --- a/source/blender/editors/object/object_modes.c +++ b/source/blender/editors/object/object_modes.c @@ -115,43 +115,46 @@ static const char *object_mode_op_string(eObjectMode mode) */ bool ED_object_mode_compat_test(const Object *ob, eObjectMode mode) { - if (ob) { - if (mode == OB_MODE_OBJECT) { - return true; - } + if (mode == OB_MODE_OBJECT) { + return true; + } - switch (ob->type) { - case OB_MESH: - if (mode & (OB_MODE_EDIT | OB_MODE_SCULPT | OB_MODE_VERTEX_PAINT | OB_MODE_WEIGHT_PAINT | - OB_MODE_TEXTURE_PAINT | OB_MODE_PARTICLE_EDIT)) { - return true; - } - break; - case OB_CURVE: - case OB_SURF: - case OB_FONT: - case OB_MBALL: - if (mode & OB_MODE_EDIT) { - return true; - } - break; - case OB_LATTICE: - if (mode & (OB_MODE_EDIT | OB_MODE_WEIGHT_PAINT)) { - return true; - } - break; - case OB_ARMATURE: - if (mode & (OB_MODE_EDIT | OB_MODE_POSE)) { - return true; - } - break; - case OB_GPENCIL: - if (mode & (OB_MODE_EDIT | OB_MODE_EDIT_GPENCIL | OB_MODE_PAINT_GPENCIL | - OB_MODE_SCULPT_GPENCIL | OB_MODE_WEIGHT_GPENCIL | OB_MODE_VERTEX_GPENCIL)) { + switch (ob->type) { + case OB_MESH: + if (mode & (OB_MODE_EDIT | OB_MODE_SCULPT | OB_MODE_VERTEX_PAINT | OB_MODE_WEIGHT_PAINT | + OB_MODE_TEXTURE_PAINT)) { + return true; + } + if (mode & OB_MODE_PARTICLE_EDIT) { + if (ED_object_particle_edit_mode_supported(ob)) { return true; } - break; - } + } + break; + case OB_CURVE: + case OB_SURF: + case OB_FONT: + case OB_MBALL: + if (mode & OB_MODE_EDIT) { + return true; + } + break; + case OB_LATTICE: + if (mode & (OB_MODE_EDIT | OB_MODE_WEIGHT_PAINT)) { + return true; + } + break; + case OB_ARMATURE: + if (mode & (OB_MODE_EDIT | OB_MODE_POSE)) { + return true; + } + break; + case OB_GPENCIL: + if (mode & (OB_MODE_EDIT_GPENCIL | OB_MODE_PAINT_GPENCIL | OB_MODE_SCULPT_GPENCIL | + OB_MODE_WEIGHT_GPENCIL | OB_MODE_VERTEX_GPENCIL)) { + return true; + } + break; } return false; diff --git a/source/blender/editors/object/object_modifier.c b/source/blender/editors/object/object_modifier.c index 0d896f14888..e14e5cbd44b 100644 --- a/source/blender/editors/object/object_modifier.c +++ b/source/blender/editors/object/object_modifier.c @@ -63,6 +63,7 @@ #include "BKE_lattice.h" #include "BKE_lib_id.h" #include "BKE_main.h" +#include "BKE_material.h" #include "BKE_mesh.h" #include "BKE_mesh_mapping.h" #include "BKE_mesh_runtime.h" @@ -210,7 +211,7 @@ ModifierData *ED_object_modifier_add( /* special cases */ if (type == eModifierType_Softbody) { if (!ob->soft) { - ob->soft = sbNew(scene); + ob->soft = sbNew(); ob->softflag |= OB_SB_GOAL | OB_SB_EDGES; } } @@ -772,6 +773,8 @@ static bool modifier_apply_obdata( return false; } + Main *bmain = DEG_get_bmain(depsgraph); + BKE_object_material_from_eval_data(bmain, ob, &mesh_applied->id); BKE_mesh_nomain_to_mesh(mesh_applied, me, ob, &CD_MASK_MESH, true); if (md_eval->type == eModifierType_Multires) { diff --git a/source/blender/editors/object/object_relations.c b/source/blender/editors/object/object_relations.c index a7308002e76..b685a93f27b 100644 --- a/source/blender/editors/object/object_relations.c +++ b/source/blender/editors/object/object_relations.c @@ -801,7 +801,7 @@ bool ED_object_parent_set(ReportList *reports, * so we check this by assuming that the parent is selected too. */ /* XXX currently this should only happen for meshes, curves, surfaces, - * and lattices - this stuff isn't available for metas yet */ + * and lattices - this stuff isn't available for meta-balls yet. */ if (ELEM(ob->type, OB_MESH, OB_CURVE, OB_SURF, OB_FONT, OB_LATTICE)) { ModifierData *md; diff --git a/source/blender/editors/physics/particle_edit.c b/source/blender/editors/physics/particle_edit.c index de4ad913d6d..5b545784e5b 100644 --- a/source/blender/editors/physics/particle_edit.c +++ b/source/blender/editors/physics/particle_edit.c @@ -5375,8 +5375,7 @@ static bool particle_edit_toggle_poll(bContext *C) return 0; } - return (ob->particlesystem.first || BKE_modifiers_findby_type(ob, eModifierType_Cloth) || - BKE_modifiers_findby_type(ob, eModifierType_Softbody)); + return ED_object_particle_edit_mode_supported(ob); } static void free_all_psys_edit(Object *object) @@ -5391,6 +5390,12 @@ static void free_all_psys_edit(Object *object) } } +bool ED_object_particle_edit_mode_supported(const Object *ob) +{ + return (ob->particlesystem.first || BKE_modifiers_findby_type(ob, eModifierType_Cloth) || + BKE_modifiers_findby_type(ob, eModifierType_Softbody)); +} + void ED_object_particle_edit_mode_enter_ex(Depsgraph *depsgraph, Scene *scene, Object *ob) { /* Needed so #ParticleSystemModifierData.mesh_final is set. */ diff --git a/source/blender/editors/physics/rigidbody_object.c b/source/blender/editors/physics/rigidbody_object.c index 81a8b57776b..fca3b5817b0 100644 --- a/source/blender/editors/physics/rigidbody_object.c +++ b/source/blender/editors/physics/rigidbody_object.c @@ -520,6 +520,26 @@ static int rigidbody_objects_calc_mass_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } +static bool mass_calculate_poll_property(const bContext *UNUSED(C), + wmOperator *op, + const PropertyRNA *prop) +{ + const char *prop_id = RNA_property_identifier(prop); + + /* Disable density input when not using the 'Custom' preset. */ + if (STREQ(prop_id, "density")) { + int material = RNA_enum_get(op->ptr, "material"); + if (material >= 0) { + RNA_def_property_clear_flag((PropertyRNA *)prop, PROP_EDITABLE); + } + else { + RNA_def_property_flag((PropertyRNA *)prop, PROP_EDITABLE); + } + } + + return true; +} + void RIGIDBODY_OT_mass_calculate(wmOperatorType *ot) { PropertyRNA *prop; @@ -533,6 +553,7 @@ void RIGIDBODY_OT_mass_calculate(wmOperatorType *ot) ot->invoke = WM_menu_invoke; /* XXX */ ot->exec = rigidbody_objects_calc_mass_exec; ot->poll = ED_operator_rigidbody_active_poll; + ot->poll_property = mass_calculate_poll_property; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; @@ -554,7 +575,7 @@ void RIGIDBODY_OT_mass_calculate(wmOperatorType *ot) FLT_MIN, FLT_MAX, "Density", - "Custom density value (kg/m^3) to use instead of material preset", + "Density value (kg/m^3), allows custom value if the 'Custom' preset is used", 1.0f, 2500.0f); } diff --git a/source/blender/editors/render/render_preview.c b/source/blender/editors/render/render_preview.c index 157f2b3ecb4..e28fe8a5d04 100644 --- a/source/blender/editors/render/render_preview.c +++ b/source/blender/editors/render/render_preview.c @@ -70,6 +70,7 @@ #include "BKE_node.h" #include "BKE_object.h" #include "BKE_scene.h" +#include "BKE_screen.h" #include "BKE_texture.h" #include "BKE_world.h" @@ -776,16 +777,21 @@ static void object_preview_render(IconPreview *preview, IconPreviewSize *preview U.pixelsize = 2.0f; + View3DShading shading; + BKE_screen_view3d_shading_init(&shading); + /* Enable shadows, makes it a bit easier to see the shape. */ + shading.flag |= V3D_SHADING_SHADOW; + ImBuf *ibuf = ED_view3d_draw_offscreen_imbuf_simple( depsgraph, DEG_get_evaluated_scene(depsgraph), - NULL, - OB_SOLID, + &shading, + OB_TEXTURE, DEG_get_evaluated_object(depsgraph, scene->camera), preview_sized->sizex, preview_sized->sizey, IB_rect, - V3D_OFSDRAW_NONE, + V3D_OFSDRAW_OVERRIDE_SCENE_SETTINGS, R_ALPHAPREMUL, NULL, NULL, diff --git a/source/blender/editors/screen/area.c b/source/blender/editors/screen/area.c index bd2b1c4c553..b83eccdcfdd 100644 --- a/source/blender/editors/screen/area.c +++ b/source/blender/editors/screen/area.c @@ -1172,12 +1172,12 @@ static void region_azones_add(const bScreen *screen, ScrArea *area, ARegion *reg } /* dir is direction to check, not the splitting edge direction! */ -static int rct_fits(const rcti *rect, char dir, int size) +static int rct_fits(const rcti *rect, const eScreenAxis dir_axis, int size) { - if (dir == 'h') { + if (dir_axis == SCREEN_AXIS_H) { return BLI_rcti_size_x(rect) + 1 - size; } - /* 'v' */ + /* Vertical. */ return BLI_rcti_size_y(rect) + 1 - size; } @@ -1398,7 +1398,8 @@ static void region_rect_recursive( region->flag |= RGN_FLAG_TOO_SMALL; } } - else if (rct_fits(remainder, 'v', 1) < 0 || rct_fits(remainder, 'h', 1) < 0) { + else if (rct_fits(remainder, SCREEN_AXIS_V, 1) < 0 || + rct_fits(remainder, SCREEN_AXIS_H, 1) < 0) { /* remainder is too small for any usage */ region->flag |= RGN_FLAG_TOO_SMALL; } @@ -1410,11 +1411,11 @@ static void region_rect_recursive( else if (ELEM(alignment, RGN_ALIGN_TOP, RGN_ALIGN_BOTTOM)) { rcti *winrct = (region->overlap) ? overlap_remainder : remainder; - if ((prefsizey == 0) || (rct_fits(winrct, 'v', prefsizey) < 0)) { + if ((prefsizey == 0) || (rct_fits(winrct, SCREEN_AXIS_V, prefsizey) < 0)) { region->flag |= RGN_FLAG_TOO_SMALL; } else { - int fac = rct_fits(winrct, 'v', prefsizey); + int fac = rct_fits(winrct, SCREEN_AXIS_V, prefsizey); if (fac < 0) { prefsizey += fac; @@ -1436,11 +1437,11 @@ static void region_rect_recursive( else if (ELEM(alignment, RGN_ALIGN_LEFT, RGN_ALIGN_RIGHT)) { rcti *winrct = (region->overlap) ? overlap_remainder : remainder; - if ((prefsizex == 0) || (rct_fits(winrct, 'h', prefsizex) < 0)) { + if ((prefsizex == 0) || (rct_fits(winrct, SCREEN_AXIS_H, prefsizex) < 0)) { region->flag |= RGN_FLAG_TOO_SMALL; } else { - int fac = rct_fits(winrct, 'h', prefsizex); + int fac = rct_fits(winrct, SCREEN_AXIS_H, prefsizex); if (fac < 0) { prefsizex += fac; @@ -1464,7 +1465,7 @@ static void region_rect_recursive( region->winrct = *remainder; if (alignment == RGN_ALIGN_HSPLIT) { - if (rct_fits(remainder, 'h', prefsizex) > 4) { + if (rct_fits(remainder, SCREEN_AXIS_H, prefsizex) > 4) { region->winrct.xmax = BLI_rcti_cent_x(remainder); remainder->xmin = region->winrct.xmax + 1; } @@ -1473,7 +1474,7 @@ static void region_rect_recursive( } } else { - if (rct_fits(remainder, 'v', prefsizey) > 4) { + if (rct_fits(remainder, SCREEN_AXIS_V, prefsizey) > 4) { region->winrct.ymax = BLI_rcti_cent_y(remainder); remainder->ymin = region->winrct.ymax + 1; } diff --git a/source/blender/editors/screen/screen_draw.c b/source/blender/editors/screen/screen_draw.c index 6d1409a9044..2c45524ef94 100644 --- a/source/blender/editors/screen/screen_draw.c +++ b/source/blender/editors/screen/screen_draw.c @@ -237,23 +237,25 @@ void ED_screen_draw_edges(wmWindow *win) * \param sa1: Area from which the resultant originates. * \param sa2: Target area that will be replaced. */ -void ED_screen_draw_join_highlight(ScrArea *sa1, ScrArea *sa2) +void screen_draw_join_highlight(ScrArea *sa1, ScrArea *sa2) { - int dir = area_getorientation(sa1, sa2); - if (dir == -1) { + const eScreenDir dir = area_getorientation(sa1, sa2); + if (dir == SCREEN_DIR_NONE) { return; } /* Rect of the combined areas.*/ - bool vertical = ELEM(dir, 1, 3); - rctf combined = {.xmin = vertical ? MAX2(sa1->totrct.xmin, sa2->totrct.xmin) : - MIN2(sa1->totrct.xmin, sa2->totrct.xmin), - .xmax = vertical ? MIN2(sa1->totrct.xmax, sa2->totrct.xmax) : - MAX2(sa1->totrct.xmax, sa2->totrct.xmax), - .ymin = vertical ? MIN2(sa1->totrct.ymin, sa2->totrct.ymin) : - MAX2(sa1->totrct.ymin, sa2->totrct.ymin), - .ymax = vertical ? MAX2(sa1->totrct.ymax, sa2->totrct.ymax) : - MIN2(sa1->totrct.ymax, sa2->totrct.ymax)}; + const bool vertical = SCREEN_DIR_IS_VERTICAL(dir); + const rctf combined = { + .xmin = vertical ? MAX2(sa1->totrct.xmin, sa2->totrct.xmin) : + MIN2(sa1->totrct.xmin, sa2->totrct.xmin), + .xmax = vertical ? MIN2(sa1->totrct.xmax, sa2->totrct.xmax) : + MAX2(sa1->totrct.xmax, sa2->totrct.xmax), + .ymin = vertical ? MIN2(sa1->totrct.ymin, sa2->totrct.ymin) : + MAX2(sa1->totrct.ymin, sa2->totrct.ymin), + .ymax = vertical ? MAX2(sa1->totrct.ymax, sa2->totrct.ymax) : + MIN2(sa1->totrct.ymax, sa2->totrct.ymax), + }; uint pos_id = GPU_vertformat_attr_add( immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); @@ -320,7 +322,7 @@ void ED_screen_draw_join_highlight(ScrArea *sa1, ScrArea *sa2) UI_draw_roundbox_4fv(&combined, false, 7 * U.pixelsize, (float[4]){1.0f, 1.0f, 1.0f, 0.8f}); } -void ED_screen_draw_split_preview(ScrArea *area, const int dir, const float fac) +void screen_draw_split_preview(ScrArea *area, const eScreenAxis dir_axis, const float fac) { uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); @@ -332,7 +334,7 @@ void ED_screen_draw_split_preview(ScrArea *area, const int dir, const float fac) immBegin(GPU_PRIM_LINES, 2); - if (dir == 'h') { + if (dir_axis == SCREEN_AXIS_H) { const float y = (1 - fac) * area->totrct.ymin + fac * area->totrct.ymax; immVertex2f(pos, area->totrct.xmin, y); @@ -350,7 +352,7 @@ void ED_screen_draw_split_preview(ScrArea *area, const int dir, const float fac) immEnd(); } else { - BLI_assert(dir == 'v'); + BLI_assert(dir_axis == SCREEN_AXIS_V); const float x = (1 - fac) * area->totrct.xmin + fac * area->totrct.xmax; immVertex2f(pos, x, area->totrct.ymin); diff --git a/source/blender/editors/screen/screen_edit.c b/source/blender/editors/screen/screen_edit.c index 75aa709a5d1..6fb5f33d836 100644 --- a/source/blender/editors/screen/screen_edit.c +++ b/source/blender/editors/screen/screen_edit.c @@ -104,8 +104,12 @@ static void screen_delarea(bContext *C, bScreen *screen, ScrArea *area) MEM_freeN(area); } -ScrArea *area_split( - const wmWindow *win, bScreen *screen, ScrArea *area, char dir, float fac, int merge) +ScrArea *area_split(const wmWindow *win, + bScreen *screen, + ScrArea *area, + const eScreenAxis dir_axis, + const float fac, + const bool merge) { ScrArea *newa = NULL; @@ -116,7 +120,7 @@ ScrArea *area_split( rcti window_rect; WM_window_rect_calc(win, &window_rect); - short split = screen_geom_find_area_split_point(area, &window_rect, dir, fac); + short split = screen_geom_find_area_split_point(area, &window_rect, dir_axis, fac); if (split == 0) { return NULL; } @@ -125,7 +129,7 @@ ScrArea *area_split( * normally it shouldn't matter which is used since the copy should match the original * however with viewport rendering and python console this isn't the case. - campbell */ - if (dir == 'h') { + if (dir_axis == SCREEN_AXIS_H) { /* new vertices */ ScrVert *sv1 = screen_geom_vertex_add(screen, area->v1->vec.x, split); ScrVert *sv2 = screen_geom_vertex_add(screen, area->v4->vec.x, split); @@ -284,10 +288,10 @@ void screen_new_activate_prepare(const wmWindow *win, bScreen *screen_new) * -1 = not valid check. * used with join operator. */ -int area_getorientation(ScrArea *sa_a, ScrArea *sa_b) +eScreenDir area_getorientation(ScrArea *sa_a, ScrArea *sa_b) { if (sa_a == NULL || sa_b == NULL || sa_a == sa_b) { - return -1; + return SCREEN_DIR_NONE; } const vec2s *sa_bl = &sa_a->v1->vec; @@ -327,29 +331,31 @@ int area_getorientation(ScrArea *sa_a, ScrArea *sa_b) /** * Get alignment offset of adjacent areas. 'dir' value is like #area_getorientation(). */ -void area_getoffsets(ScrArea *sa_a, ScrArea *sa_b, const int dir, int *r_offset1, int *r_offset2) +void area_getoffsets( + ScrArea *sa_a, ScrArea *sa_b, const eScreenDir dir, int *r_offset1, int *r_offset2) { if (sa_a == NULL || sa_b == NULL) { *r_offset1 = INT_MAX; *r_offset2 = INT_MAX; } - else if (dir == 0) { /* West: sa on right and sa_b to the left. */ + else if (dir == SCREEN_DIR_W) { /* West: sa on right and sa_b to the left. */ *r_offset1 = sa_b->v3->vec.y - sa_a->v2->vec.y; *r_offset2 = sa_b->v4->vec.y - sa_a->v1->vec.y; } - else if (dir == 1) { /* North: sa below and sa_b above. */ + else if (dir == SCREEN_DIR_N) { /* North: sa below and sa_b above. */ *r_offset1 = sa_a->v2->vec.x - sa_b->v1->vec.x; *r_offset2 = sa_a->v3->vec.x - sa_b->v4->vec.x; } - else if (dir == 2) { /* East: sa on left and sa_b to the right. */ + else if (dir == SCREEN_DIR_E) { /* East: sa on left and sa_b to the right. */ *r_offset1 = sa_b->v2->vec.y - sa_a->v3->vec.y; *r_offset2 = sa_b->v1->vec.y - sa_a->v4->vec.y; } - else if (dir == 3) { /* South: sa above and sa_b below. */ + else if (dir == SCREEN_DIR_S) { /* South: sa above and sa_b below. */ *r_offset1 = sa_a->v1->vec.x - sa_b->v2->vec.x; *r_offset2 = sa_a->v4->vec.x - sa_b->v3->vec.x; } else { + BLI_assert(dir == SCREEN_DIR_NONE); *r_offset1 = INT_MAX; *r_offset2 = INT_MAX; } @@ -386,11 +392,11 @@ static void screen_verts_valign(const wmWindow *win, /* Adjust all screen edges to allow joining two areas. 'dir' value is like area_getorientation(). */ static void screen_areas_align( - bContext *C, bScreen *screen, ScrArea *sa1, ScrArea *sa2, const int dir) + bContext *C, bScreen *screen, ScrArea *sa1, ScrArea *sa2, const eScreenDir dir) { wmWindow *win = CTX_wm_window(C); - if (ELEM(dir, 0, 2)) { + if (SCREEN_DIR_IS_HORIZONTAL(dir)) { /* horizontal join, use average for new top and bottom. */ int top = (sa1->v2->vec.y + sa2->v2->vec.y) / 2; int bottom = (sa1->v4->vec.y + sa2->v4->vec.y) / 2; @@ -421,8 +427,8 @@ static void screen_areas_align( /* Simple join of two areas without any splitting. Will return false if not possible. */ static bool screen_area_join_aligned(bContext *C, bScreen *screen, ScrArea *sa1, ScrArea *sa2) { - int dir = area_getorientation(sa1, sa2); - if (dir == -1) { + const eScreenDir dir = area_getorientation(sa1, sa2); + if (dir == SCREEN_DIR_NONE) { return false; } @@ -430,7 +436,7 @@ static bool screen_area_join_aligned(bContext *C, bScreen *screen, ScrArea *sa1, int offset2; area_getoffsets(sa1, sa2, dir, &offset1, &offset2); - int tolerance = ELEM(dir, 0, 2) ? AREAJOINTOLERANCEY : AREAJOINTOLERANCEX; + int tolerance = SCREEN_DIR_IS_HORIZONTAL(dir) ? AREAJOINTOLERANCEY : AREAJOINTOLERANCEX; if ((abs(offset1) >= tolerance) || (abs(offset2) >= tolerance)) { return false; } @@ -438,27 +444,27 @@ static bool screen_area_join_aligned(bContext *C, bScreen *screen, ScrArea *sa1, /* Align areas if they are not. */ screen_areas_align(C, screen, sa1, sa2, dir); - if (dir == 0) { /* sa1 to right of sa2 = W */ - sa1->v1 = sa2->v1; /* BL */ - sa1->v2 = sa2->v2; /* TL */ + if (dir == SCREEN_DIR_W) { /* sa1 to right of sa2 = West. */ + sa1->v1 = sa2->v1; /* BL */ + sa1->v2 = sa2->v2; /* TL */ screen_geom_edge_add(screen, sa1->v2, sa1->v3); screen_geom_edge_add(screen, sa1->v1, sa1->v4); } - else if (dir == 1) { /* sa1 to bottom of sa2 = N */ - sa1->v2 = sa2->v2; /* TL */ - sa1->v3 = sa2->v3; /* TR */ + else if (dir == SCREEN_DIR_N) { /* sa1 to bottom of sa2 = North. */ + sa1->v2 = sa2->v2; /* TL */ + sa1->v3 = sa2->v3; /* TR */ screen_geom_edge_add(screen, sa1->v1, sa1->v2); screen_geom_edge_add(screen, sa1->v3, sa1->v4); } - else if (dir == 2) { /* sa1 to left of sa2 = E */ - sa1->v3 = sa2->v3; /* TR */ - sa1->v4 = sa2->v4; /* BR */ + else if (dir == SCREEN_DIR_E) { /* sa1 to left of sa2 = East. */ + sa1->v3 = sa2->v3; /* TR */ + sa1->v4 = sa2->v4; /* BR */ screen_geom_edge_add(screen, sa1->v2, sa1->v3); screen_geom_edge_add(screen, sa1->v1, sa1->v4); } - else if (dir == 3) { /* sa1 on top of sa2 = S */ - sa1->v1 = sa2->v1; /* BL */ - sa1->v4 = sa2->v4; /* BR */ + else if (dir == SCREEN_DIR_S) { /* sa1 on top of sa2 = South. */ + sa1->v1 = sa2->v1; /* BL */ + sa1->v4 = sa2->v4; /* BR */ screen_geom_edge_add(screen, sa1->v1, sa1->v2); screen_geom_edge_add(screen, sa1->v3, sa1->v4); } @@ -473,9 +479,9 @@ static bool screen_area_join_aligned(bContext *C, bScreen *screen, ScrArea *sa1, /* Slice off and return new area. "Reverse" gives right/bottom, rather than left/top. */ static ScrArea *screen_area_trim( - bContext *C, bScreen *screen, ScrArea **area, int size, int dir, bool reverse) + bContext *C, bScreen *screen, ScrArea **area, int size, eScreenDir dir, bool reverse) { - bool vertical = ELEM(dir, 1, 3); + const bool vertical = SCREEN_DIR_IS_VERTICAL(dir); if (abs(size) < (vertical ? AREAJOINTOLERANCEX : AREAJOINTOLERANCEY)) { return NULL; } @@ -484,7 +490,8 @@ static ScrArea *screen_area_trim( float fac = abs(size) / (float)(vertical ? ((*area)->v3->vec.x - (*area)->v1->vec.x) : ((*area)->v3->vec.y - (*area)->v1->vec.y)); fac = (reverse == vertical) ? 1.0f - fac : fac; - ScrArea *newsa = area_split(CTX_wm_window(C), screen, *area, vertical ? 'v' : 'h', fac, 1); + ScrArea *newsa = area_split( + CTX_wm_window(C), screen, *area, vertical ? SCREEN_AXIS_V : SCREEN_AXIS_H, fac, true); /* area_split always returns smallest of the two areas, so might have to swap. */ if (((fac > 0.5f) == vertical) != reverse) { @@ -500,8 +507,8 @@ static ScrArea *screen_area_trim( static bool screen_area_join_ex( bContext *C, bScreen *screen, ScrArea *sa1, ScrArea *sa2, bool close_all_remainders) { - int dir = area_getorientation(sa1, sa2); - if (dir == -1) { + const eScreenDir dir = area_getorientation(sa1, sa2); + if (dir == SCREEN_DIR_NONE) { return false; } @@ -534,7 +541,7 @@ int screen_area_join(bContext *C, bScreen *screen, ScrArea *sa1, ScrArea *sa2) return screen_area_join_ex(C, screen, sa1, sa2, false); } -/* Close a screen area, allowing any neighbor to take its place. */ +/* Close a screen area, allowing most-aligned neighbor to take its place. */ bool screen_area_close(struct bContext *C, bScreen *screen, ScrArea *area) { if (area == NULL) { @@ -542,32 +549,28 @@ bool screen_area_close(struct bContext *C, bScreen *screen, ScrArea *area) } ScrArea *sa2 = NULL; - - /* Find the most-aligned joinable area. Larger size breaks ties. */ - int min_alignment = INT_MAX; - int max_size = 0; - LISTBASE_FOREACH (ScrArea *, ar, &screen->areabase) { - int dir = area_getorientation(area, ar); - if (dir != -1) { - int offset1; - int offset2; - area_getoffsets(area, ar, dir, &offset1, &offset2); - int area_alignment = abs(offset1) + abs(offset2); - if (area_alignment < min_alignment) { - min_alignment = area_alignment; - max_size = ar->winx * ar->winy; - sa2 = ar; - } - else if (area_alignment == min_alignment) { - int area_size = ar->winx * ar->winy; - if (area_size > max_size) { - max_size = area_size; - sa2 = ar; - } + float best_alignment = 0.0f; + + LISTBASE_FOREACH (ScrArea *, neighbor, &screen->areabase) { + const eScreenDir dir = area_getorientation(area, neighbor); + /* Must at least partially share an edge and not be a global area. */ + if ((dir != SCREEN_DIR_NONE) && (neighbor->global == NULL)) { + /* Winx/Winy might not be updated yet, so get lengths from verts. */ + const bool vertical = SCREEN_DIR_IS_VERTICAL(dir); + const int area_length = vertical ? (area->v3->vec.x - area->v1->vec.x) : + (area->v3->vec.y - area->v1->vec.y); + const int ar_length = vertical ? (neighbor->v3->vec.x - neighbor->v1->vec.x) : + (neighbor->v3->vec.y - neighbor->v1->vec.y); + /* Calculate the ratio of the lengths of the shared edges. */ + float alignment = MIN2(area_length, ar_length) / (float)MAX2(area_length, ar_length); + if (alignment > best_alignment) { + best_alignment = alignment; + sa2 = neighbor; } } } + /* Join from neighbor into this area to close it. */ return screen_area_join_ex(C, screen, sa2, area, true); } diff --git a/source/blender/editors/screen/screen_geometry.c b/source/blender/editors/screen/screen_geometry.c index ac159f4d633..51edad0332b 100644 --- a/source/blender/editors/screen/screen_geometry.c +++ b/source/blender/editors/screen/screen_geometry.c @@ -304,7 +304,7 @@ void screen_geom_vertices_scale(const wmWindow *win, bScreen *screen) */ short screen_geom_find_area_split_point(const ScrArea *area, const rcti *window_rect, - char dir, + const eScreenAxis dir_axis, float fac) { const int cur_area_width = screen_geom_area_width(area); @@ -313,17 +313,21 @@ short screen_geom_find_area_split_point(const ScrArea *area, const short area_min_y = ED_area_headersize(); /* area big enough? */ - if ((dir == 'v') && (cur_area_width <= 2 * area_min_x)) { - return 0; + if (dir_axis == SCREEN_AXIS_V) { + if (cur_area_width <= 2 * area_min_x) { + return 0; + } } - if ((dir == 'h') && (cur_area_height <= 2 * area_min_y)) { - return 0; + else if (dir_axis == SCREEN_AXIS_H) { + if (cur_area_height <= 2 * area_min_y) { + return 0; + } } /* to be sure */ CLAMP(fac, 0.0f, 1.0f); - if (dir == 'h') { + if (dir_axis == SCREEN_AXIS_H) { short y = area->v1->vec.y + round_fl_to_short(fac * cur_area_height); int area_min = area_min_y; @@ -373,13 +377,13 @@ void screen_geom_select_connected_edge(const wmWindow *win, ScrEdge *edge) { bScreen *screen = WM_window_get_active_screen(win); - /* 'dir' is the direction of EDGE */ - char dir; + /* 'dir_axis' is the direction of EDGE */ + eScreenAxis dir_axis; if (edge->v1->vec.x == edge->v2->vec.x) { - dir = 'v'; + dir_axis = SCREEN_AXIS_V; } else { - dir = 'h'; + dir_axis = SCREEN_AXIS_H; } ED_screen_verts_iter(win, screen, sv) @@ -396,13 +400,13 @@ void screen_geom_select_connected_edge(const wmWindow *win, ScrEdge *edge) oneselected = false; LISTBASE_FOREACH (ScrEdge *, se, &screen->edgebase) { if (se->v1->flag + se->v2->flag == 1) { - if (dir == 'h') { + if (dir_axis == SCREEN_AXIS_H) { if (se->v1->vec.y == se->v2->vec.y) { se->v1->flag = se->v2->flag = 1; oneselected = true; } } - if (dir == 'v') { + else if (dir_axis == SCREEN_AXIS_V) { if (se->v1->vec.x == se->v2->vec.x) { se->v1->flag = se->v2->flag = 1; oneselected = true; diff --git a/source/blender/editors/screen/screen_intern.h b/source/blender/editors/screen/screen_intern.h index dd196f9621b..683f2844371 100644 --- a/source/blender/editors/screen/screen_intern.h +++ b/source/blender/editors/screen/screen_intern.h @@ -29,6 +29,29 @@ struct bContextDataResult; /* internal exports only */ +typedef enum eScreenDir { + /** This can mean unset, unknown or invalid. */ + SCREEN_DIR_NONE = -1, + /** West/Left. */ + SCREEN_DIR_W = 0, + /** North/Up. */ + SCREEN_DIR_N = 1, + /** East/Right. */ + SCREEN_DIR_E = 2, + /** South/Down. */ + SCREEN_DIR_S = 3, +} eScreenDir; + +#define SCREEN_DIR_IS_VERTICAL(dir) (ELEM(dir, SCREEN_DIR_N, SCREEN_DIR_S)) +#define SCREEN_DIR_IS_HORIZONTAL(dir) (ELEM(dir, SCREEN_DIR_W, SCREEN_DIR_E)) + +typedef enum eScreenAxis { + /** Horizontal. */ + SCREEN_AXIS_H = 'h', + /** Vertical. */ + SCREEN_AXIS_V = 'v', +} eScreenAxis; + #define AZONESPOTW UI_HEADER_OFFSET /* width of corner #AZone - max */ #define AZONESPOTH (0.6f * U.widget_unit) /* height of corner #AZone */ #define AZONEFADEIN (5.0f * U.widget_unit) /* when #AZone is totally visible */ @@ -46,6 +69,10 @@ void ED_area_data_copy(ScrArea *area_dst, ScrArea *area_src, const bool do_free) void ED_area_data_swap(ScrArea *area_dst, ScrArea *area_src); void region_toggle_hidden(struct bContext *C, ARegion *region, const bool do_fade); +/* screen_draw.c */ +void screen_draw_join_highlight(struct ScrArea *sa1, struct ScrArea *sa2); +void screen_draw_split_preview(struct ScrArea *area, const eScreenAxis dir_axis, const float fac); + /* screen_edit.c */ bScreen *screen_add(struct Main *bmain, const char *name, const rcti *rect); void screen_data_copy(bScreen *to, bScreen *from); @@ -56,11 +83,16 @@ void screen_change_prepare(bScreen *screen_old, struct Main *bmain, struct bContext *C, wmWindow *win); -ScrArea *area_split( - const wmWindow *win, bScreen *screen, ScrArea *area, char dir, float fac, int merge); +ScrArea *area_split(const wmWindow *win, + bScreen *screen, + ScrArea *area, + const eScreenAxis dir_axis, + const float fac, + const bool merge); int screen_area_join(struct bContext *C, bScreen *screen, ScrArea *sa1, ScrArea *sa2); -int area_getorientation(ScrArea *sa_a, ScrArea *sa_b); -void area_getoffsets(ScrArea *sa_a, ScrArea *sa_b, const int dir, int *r_offset1, int *r_offset2); +eScreenDir area_getorientation(ScrArea *sa_a, ScrArea *sa_b); +void area_getoffsets( + ScrArea *sa_a, ScrArea *sa_b, const eScreenDir dir, int *r_offset1, int *r_offset2); bool screen_area_close(struct bContext *C, bScreen *screen, ScrArea *area); struct AZone *ED_area_actionzone_find_xy(ScrArea *area, const int xy[2]); @@ -83,7 +115,7 @@ ScrEdge *screen_geom_find_active_scredge(const wmWindow *win, void screen_geom_vertices_scale(const wmWindow *win, bScreen *screen); short screen_geom_find_area_split_point(const ScrArea *area, const rcti *window_rect, - char dir, + const eScreenAxis dir_axis, float fac); void screen_geom_select_connected_edge(const wmWindow *win, ScrEdge *edge); diff --git a/source/blender/editors/screen/screen_ops.c b/source/blender/editors/screen/screen_ops.c index eadc18de443..6b8d4e73f12 100644 --- a/source/blender/editors/screen/screen_ops.c +++ b/source/blender/editors/screen/screen_ops.c @@ -694,7 +694,9 @@ static bool screen_active_editable(bContext *C) typedef struct sActionzoneData { ScrArea *sa1, *sa2; AZone *az; - int x, y, gesture_dir, modifier; + int x, y; + eScreenDir gesture_dir; + int modifier; } sActionzoneData; /* quick poll to save operators to be created and handled */ @@ -1045,16 +1047,16 @@ static int actionzone_modal(bContext *C, wmOperator *op, const wmEvent *event) /* Calculate gesture cardinal direction. */ if (delta_y > abs(delta_x)) { - sad->gesture_dir = 'n'; + sad->gesture_dir = SCREEN_DIR_N; } else if (delta_x >= abs(delta_y)) { - sad->gesture_dir = 'e'; + sad->gesture_dir = SCREEN_DIR_E; } else if (delta_y < -abs(delta_x)) { - sad->gesture_dir = 's'; + sad->gesture_dir = SCREEN_DIR_S; } else { - sad->gesture_dir = 'w'; + sad->gesture_dir = SCREEN_DIR_W; } bool is_gesture; @@ -1071,22 +1073,24 @@ static int actionzone_modal(bContext *C, wmOperator *op, const wmEvent *event) /* Are we still in same area? */ if (BKE_screen_find_area_xy(screen, SPACE_TYPE_ANY, event->x, event->y) == sad->sa1) { /* Same area, so possible split. */ - WM_cursor_set( - win, (ELEM(sad->gesture_dir, 'n', 's')) ? WM_CURSOR_H_SPLIT : WM_CURSOR_V_SPLIT); + WM_cursor_set(win, + SCREEN_DIR_IS_VERTICAL(sad->gesture_dir) ? WM_CURSOR_H_SPLIT : + WM_CURSOR_V_SPLIT); is_gesture = (delta_max > split_threshold); } else { /* Different area, so possible join. */ - if (sad->gesture_dir == 'n') { + if (sad->gesture_dir == SCREEN_DIR_N) { WM_cursor_set(win, WM_CURSOR_N_ARROW); } - else if (sad->gesture_dir == 's') { + else if (sad->gesture_dir == SCREEN_DIR_S) { WM_cursor_set(win, WM_CURSOR_S_ARROW); } - else if (sad->gesture_dir == 'e') { + else if (sad->gesture_dir == SCREEN_DIR_E) { WM_cursor_set(win, WM_CURSOR_E_ARROW); } else { + BLI_assert(sad->gesture_dir == SCREEN_DIR_W); WM_cursor_set(win, WM_CURSOR_W_ARROW); } is_gesture = (delta_max > join_threshold); @@ -1480,7 +1484,7 @@ static void SCREEN_OT_area_close(wmOperatorType *ot) typedef struct sAreaMoveData { int bigger, smaller, origval, step; - char dir; + eScreenAxis dir_axis; enum AreaMoveSnapType { /* Snapping disabled */ SNAP_NONE = 0, @@ -1499,7 +1503,7 @@ typedef struct sAreaMoveData { * need window bounds in order to get correct limits */ static void area_move_set_limits(wmWindow *win, bScreen *screen, - int dir, + const eScreenAxis dir_axis, int *bigger, int *smaller, bool *use_bigger_smaller_snap) @@ -1552,7 +1556,7 @@ static void area_move_set_limits(wmWindow *win, WM_window_rect_calc(win, &window_rect); LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) { - if (dir == 'h') { + if (dir_axis == SCREEN_AXIS_H) { int areamin = ED_area_headersize(); if (area->v1->vec.y > window_rect.ymin) { @@ -1615,8 +1619,8 @@ static bool area_move_init(bContext *C, wmOperator *op) sAreaMoveData *md = MEM_callocN(sizeof(sAreaMoveData), "sAreaMoveData"); op->customdata = md; - md->dir = screen_geom_edge_is_horizontal(actedge) ? 'h' : 'v'; - if (md->dir == 'h') { + md->dir_axis = screen_geom_edge_is_horizontal(actedge) ? SCREEN_AXIS_H : SCREEN_AXIS_V; + if (md->dir_axis == SCREEN_AXIS_H) { md->origval = actedge->v1->vec.y; } else { @@ -1631,7 +1635,8 @@ static bool area_move_init(bContext *C, wmOperator *op) } bool use_bigger_smaller_snap = false; - area_move_set_limits(win, screen, md->dir, &md->bigger, &md->smaller, &use_bigger_smaller_snap); + area_move_set_limits( + win, screen, md->dir_axis, &md->bigger, &md->smaller, &use_bigger_smaller_snap); md->snap_type = use_bigger_smaller_snap ? SNAP_BIGGER_SMALLER_ONLY : SNAP_AREAGRID; @@ -1642,7 +1647,7 @@ static int area_snap_calc_location(const bScreen *screen, const enum AreaMoveSnapType snap_type, const int delta, const int origval, - const int dir, + const eScreenAxis dir_axis, const int bigger, const int smaller) { @@ -1667,7 +1672,7 @@ static int area_snap_calc_location(const bScreen *screen, break; case SNAP_FRACTION_AND_ADJACENT: { - const int axis = (dir == 'v') ? 0 : 1; + const int axis = (dir_axis == SCREEN_AXIS_V) ? 0 : 1; int snap_dist_best = INT_MAX; { const float div_array[] = { @@ -1735,7 +1740,7 @@ static int area_snap_calc_location(const bScreen *screen, static void area_move_apply_do(const bContext *C, int delta, const int origval, - const int dir, + const eScreenAxis dir_axis, const int bigger, const int smaller, const enum AreaMoveSnapType snap_type) @@ -1753,11 +1758,12 @@ static void area_move_apply_do(const bContext *C, final_loc = origval + delta; } else { - final_loc = area_snap_calc_location(screen, snap_type, delta, origval, dir, bigger, smaller); + final_loc = area_snap_calc_location( + screen, snap_type, delta, origval, dir_axis, bigger, smaller); } BLI_assert(final_loc != -1); - short axis = (dir == 'v') ? 0 : 1; + short axis = (dir_axis == SCREEN_AXIS_V) ? 0 : 1; ED_screen_verts_iter(win, screen, v1) { @@ -1813,7 +1819,7 @@ static void area_move_apply(bContext *C, wmOperator *op) sAreaMoveData *md = op->customdata; int delta = RNA_int_get(op->ptr, "delta"); - area_move_apply_do(C, delta, md->origval, md->dir, md->bigger, md->smaller, md->snap_type); + area_move_apply_do(C, delta, md->origval, md->dir_axis, md->bigger, md->smaller, md->snap_type); } static void area_move_exit(bContext *C, wmOperator *op) @@ -1878,7 +1884,7 @@ static int area_move_modal(bContext *C, wmOperator *op, const wmEvent *event) int x = RNA_int_get(op->ptr, "x"); int y = RNA_int_get(op->ptr, "y"); - int delta = (md->dir == 'v') ? event->x - x : event->y - y; + const int delta = (md->dir_axis == SCREEN_AXIS_V) ? event->x - x : event->y - y; RNA_int_set(op->ptr, "delta", delta); area_move_apply(C, op); @@ -1944,7 +1950,7 @@ static void SCREEN_OT_area_move(wmOperatorType *ot) /* * operator state vars: * fac spit point - * dir direction 'v' or 'h' + * dir direction #SCREEN_AXIS_V or #SCREEN_AXIS_H * * operator customdata: * area pointer to (active) area @@ -1981,7 +1987,7 @@ typedef struct sAreaSplitData { int delta; /* delta move edge */ int origmin, origsize; /* to calculate fac, for property storage */ int previewmode; /* draw previewline, then split */ - void *draw_callback; /* call `ED_screen_draw_split_preview` */ + void *draw_callback; /* call `screen_draw_split_preview` */ bool do_snap; ScrEdge *nedge; /* new edge */ @@ -1996,10 +2002,10 @@ static void area_split_draw_cb(const struct wmWindow *UNUSED(win), void *userdat sAreaSplitData *sd = op->customdata; if (sd->sarea) { - int dir = RNA_enum_get(op->ptr, "direction"); + const eScreenAxis dir_axis = RNA_enum_get(op->ptr, "direction"); float fac = RNA_float_get(op->ptr, "factor"); - ED_screen_draw_split_preview(sd->sarea, dir, fac); + screen_draw_split_preview(sd->sarea, dir_axis, fac); } } @@ -2026,14 +2032,18 @@ static bool area_split_init(bContext *C, wmOperator *op) } /* required properties */ - int dir = RNA_enum_get(op->ptr, "direction"); + const eScreenAxis dir_axis = RNA_enum_get(op->ptr, "direction"); /* minimal size */ - if (dir == 'v' && area->winx < 2 * AREAMINX) { - return false; + if (dir_axis == SCREEN_AXIS_V) { + if (area->winx < 2 * AREAMINX) { + return false; + } } - if (dir == 'h' && area->winy < 2 * ED_area_headersize()) { - return false; + else { + if (area->winy < 2 * ED_area_headersize()) { + return false; + } } /* custom data */ @@ -2041,7 +2051,7 @@ static bool area_split_init(bContext *C, wmOperator *op) op->customdata = sd; sd->sarea = area; - if (dir == 'v') { + if (dir_axis == SCREEN_AXIS_V) { sd->origmin = area->v1->vec.x; sd->origsize = area->v4->vec.x - sd->origmin; } @@ -2090,9 +2100,9 @@ static bool area_split_apply(bContext *C, wmOperator *op) sAreaSplitData *sd = (sAreaSplitData *)op->customdata; float fac = RNA_float_get(op->ptr, "factor"); - int dir = RNA_enum_get(op->ptr, "direction"); + const eScreenAxis dir_axis = RNA_enum_get(op->ptr, "direction"); - sd->narea = area_split(win, screen, sd->sarea, dir, fac, 0); /* 0 = no merge */ + sd->narea = area_split(win, screen, sd->sarea, dir_axis, fac, false); /* false = no merge */ if (sd->narea == NULL) { return false; @@ -2109,7 +2119,7 @@ static bool area_split_apply(bContext *C, wmOperator *op) sd->nedge->v1->editflag = 1; sd->nedge->v2->editflag = 1; - if (dir == 'h') { + if (dir_axis == SCREEN_AXIS_H) { sd->origval = sd->nedge->v1->vec.y; } else { @@ -2158,8 +2168,8 @@ static void area_split_exit(bContext *C, wmOperator *op) static void area_split_preview_update_cursor(bContext *C, wmOperator *op) { wmWindow *win = CTX_wm_window(C); - int dir = RNA_enum_get(op->ptr, "direction"); - WM_cursor_set(win, dir == 'h' ? WM_CURSOR_H_SPLIT : WM_CURSOR_V_SPLIT); + const eScreenAxis dir_axis = RNA_enum_get(op->ptr, "direction"); + WM_cursor_set(win, (dir_axis == SCREEN_AXIS_H) ? WM_CURSOR_H_SPLIT : WM_CURSOR_V_SPLIT); } /* UI callback, adds new handler */ @@ -2175,7 +2185,7 @@ static int area_split_invoke(bContext *C, wmOperator *op, const wmEvent *event) PropertyRNA *prop_factor = RNA_struct_find_property(op->ptr, "factor"); PropertyRNA *prop_cursor = RNA_struct_find_property(op->ptr, "cursor"); - int dir; + eScreenAxis dir_axis; if (event->type == EVT_ACTIONZONE_AREA) { sActionzoneData *sad = event->customdata; @@ -2203,12 +2213,12 @@ static int area_split_invoke(bContext *C, wmOperator *op, const wmEvent *event) float factor; /* Prepare operator state vars. */ - if (ELEM(sad->gesture_dir, 'n', 's')) { - dir = 'h'; + if (SCREEN_DIR_IS_VERTICAL(sad->gesture_dir)) { + dir_axis = SCREEN_AXIS_H; factor = factor_h; } else { - dir = 'v'; + dir_axis = SCREEN_AXIS_V; factor = factor_v; } @@ -2218,7 +2228,7 @@ static int area_split_invoke(bContext *C, wmOperator *op, const wmEvent *event) RNA_property_float_set(op->ptr, prop_factor, factor); - RNA_property_enum_set(op->ptr, prop_dir, dir); + RNA_property_enum_set(op->ptr, prop_dir, dir_axis); /* general init, also non-UI case, adds customdata, sets area and defaults */ if (!area_split_init(C, op)) { @@ -2230,8 +2240,8 @@ static int area_split_invoke(bContext *C, wmOperator *op, const wmEvent *event) if (area == NULL) { return OPERATOR_CANCELLED; } - dir = RNA_property_enum_get(op->ptr, prop_dir); - if (dir == 'h') { + dir_axis = RNA_property_enum_get(op->ptr, prop_dir); + if (dir_axis == SCREEN_AXIS_H) { RNA_property_float_set( op->ptr, prop_factor, ((float)(event->x - area->v1->vec.x)) / (float)area->winx); } @@ -2264,9 +2274,9 @@ static int area_split_invoke(bContext *C, wmOperator *op, const wmEvent *event) return OPERATOR_CANCELLED; } - dir = screen_geom_edge_is_horizontal(actedge) ? 'v' : 'h'; + dir_axis = screen_geom_edge_is_horizontal(actedge) ? SCREEN_AXIS_V : SCREEN_AXIS_H; - RNA_property_enum_set(op->ptr, prop_dir, dir); + RNA_property_enum_set(op->ptr, prop_dir, dir_axis); /* special case, adds customdata, sets defaults */ if (!area_split_menu_init(C, op)) { @@ -2279,7 +2289,7 @@ static int area_split_invoke(bContext *C, wmOperator *op, const wmEvent *event) if (event->type == EVT_ACTIONZONE_AREA) { /* do the split */ if (area_split_apply(C, op)) { - area_move_set_limits(win, screen, dir, &sd->bigger, &sd->smaller, NULL); + area_move_set_limits(win, screen, dir_axis, &sd->bigger, &sd->smaller, NULL); /* add temp handler for edge move or cancel */ G.moving |= G_TRANSFORM_WM; @@ -2367,8 +2377,9 @@ static int area_split_modal(bContext *C, wmOperator *op, const wmEvent *event) else { if (event->val == KM_PRESS) { if (sd->sarea) { - int dir = RNA_property_enum_get(op->ptr, prop_dir); - RNA_property_enum_set(op->ptr, prop_dir, (dir == 'v') ? 'h' : 'v'); + const eScreenAxis dir_axis = RNA_property_enum_get(op->ptr, prop_dir); + RNA_property_enum_set( + op->ptr, prop_dir, (dir_axis == SCREEN_AXIS_V) ? SCREEN_AXIS_H : SCREEN_AXIS_V); area_split_preview_update_cursor(C, op); update_factor = true; } @@ -2389,9 +2400,9 @@ static int area_split_modal(bContext *C, wmOperator *op, const wmEvent *event) } if (update_factor) { - const int dir = RNA_property_enum_get(op->ptr, prop_dir); + const eScreenAxis dir_axis = RNA_property_enum_get(op->ptr, prop_dir); - sd->delta = (dir == 'v') ? event->x - sd->origval : event->y - sd->origval; + sd->delta = (dir_axis == SCREEN_AXIS_V) ? event->x - sd->origval : event->y - sd->origval; if (sd->previewmode == 0) { if (sd->do_snap) { @@ -2399,12 +2410,12 @@ static int area_split_modal(bContext *C, wmOperator *op, const wmEvent *event) SNAP_FRACTION_AND_ADJACENT, sd->delta, sd->origval, - dir, + dir_axis, sd->bigger, sd->smaller); sd->delta = snap_loc - sd->origval; } - area_move_apply_do(C, sd->delta, sd->origval, dir, sd->bigger, sd->smaller, SNAP_NONE); + area_move_apply_do(C, sd->delta, sd->origval, dir_axis, sd->bigger, sd->smaller, SNAP_NONE); } else { if (sd->sarea) { @@ -2415,7 +2426,7 @@ static int area_split_modal(bContext *C, wmOperator *op, const wmEvent *event) if (sd->sarea) { ScrArea *area = sd->sarea; - if (dir == 'v') { + if (dir_axis == SCREEN_AXIS_V) { sd->origmin = area->v1->vec.x; sd->origsize = area->v4->vec.x - sd->origmin; } @@ -2431,7 +2442,7 @@ static int area_split_modal(bContext *C, wmOperator *op, const wmEvent *event) SNAP_FRACTION_AND_ADJACENT, sd->delta, sd->origval, - dir, + dir_axis, sd->origmin + sd->origsize, -sd->origmin); @@ -2453,8 +2464,8 @@ static int area_split_modal(bContext *C, wmOperator *op, const wmEvent *event) } static const EnumPropertyItem prop_direction_items[] = { - {'h', "HORIZONTAL", 0, "Horizontal", ""}, - {'v', "VERTICAL", 0, "Vertical", ""}, + {SCREEN_AXIS_H, "HORIZONTAL", 0, "Horizontal", ""}, + {SCREEN_AXIS_V, "VERTICAL", 0, "Vertical", ""}, {0, NULL, 0, NULL, NULL}, }; @@ -2475,7 +2486,7 @@ static void SCREEN_OT_area_split(wmOperatorType *ot) ot->flag = OPTYPE_BLOCKING | OPTYPE_INTERNAL; /* rna */ - RNA_def_enum(ot->srna, "direction", prop_direction_items, 'h', "Direction", ""); + RNA_def_enum(ot->srna, "direction", prop_direction_items, SCREEN_AXIS_H, "Direction", ""); RNA_def_float(ot->srna, "factor", 0.5f, 0.0, 1.0, "Factor", "", 0.0, 1.0); RNA_def_int_vector( ot->srna, "cursor", 2, NULL, INT_MIN, INT_MAX, "Cursor", "", INT_MIN, INT_MAX); @@ -3272,8 +3283,8 @@ static void SCREEN_OT_screen_full_area(wmOperatorType *ot) typedef struct sAreaJoinData { ScrArea *sa1; /* Potential source area (kept). */ ScrArea *sa2; /* Potential target area (removed or reduced). */ - int dir; /* Direction of potential join. */ - void *draw_callback; /* call 'ED_screen_draw_join_highlight' */ + eScreenDir dir; /* Direction of potential join. */ + void *draw_callback; /* call #screen_draw_join_highlight */ } sAreaJoinData; @@ -3282,8 +3293,8 @@ static void area_join_draw_cb(const struct wmWindow *UNUSED(win), void *userdata const wmOperator *op = userdata; sAreaJoinData *sd = op->customdata; - if (sd->sa1 && sd->sa2 && (sd->dir != -1)) { - ED_screen_draw_join_highlight(sd->sa1, sd->sa2); + if (sd->sa1 && sd->sa2 && (sd->dir != SCREEN_DIR_NONE)) { + screen_draw_join_highlight(sd->sa1, sd->sa2); } } @@ -3305,7 +3316,7 @@ static bool area_join_init(bContext *C, wmOperator *op, ScrArea *sa1, ScrArea *s jd->sa1 = sa1; jd->sa2 = sa2; - jd->dir = -1; + jd->dir = SCREEN_DIR_NONE; op->customdata = jd; @@ -3318,7 +3329,7 @@ static bool area_join_init(bContext *C, wmOperator *op, ScrArea *sa1, ScrArea *s static bool area_join_apply(bContext *C, wmOperator *op) { sAreaJoinData *jd = (sAreaJoinData *)op->customdata; - if (!jd || (jd->dir == -1)) { + if (!jd || (jd->dir == SCREEN_DIR_NONE)) { return false; } @@ -3429,21 +3440,21 @@ static int area_join_modal(bContext *C, wmOperator *op, const wmEvent *event) jd->dir = area_getorientation(jd->sa1, jd->sa2); } else if (area != jd->sa2) { - jd->dir = -1; + jd->dir = SCREEN_DIR_NONE; } WM_event_add_notifier(C, NC_WINDOW, NULL); - if (jd->dir == 1) { + if (jd->dir == SCREEN_DIR_N) { WM_cursor_set(win, WM_CURSOR_N_ARROW); } - else if (jd->dir == 3) { + else if (jd->dir == SCREEN_DIR_S) { WM_cursor_set(win, WM_CURSOR_S_ARROW); } - else if (jd->dir == 2) { + else if (jd->dir == SCREEN_DIR_E) { WM_cursor_set(win, WM_CURSOR_E_ARROW); } - else if (jd->dir == 0) { + else if (jd->dir == SCREEN_DIR_W) { WM_cursor_set(win, WM_CURSOR_W_ARROW); } else { @@ -3454,7 +3465,7 @@ static int area_join_modal(bContext *C, wmOperator *op, const wmEvent *event) } case LEFTMOUSE: if (event->val == KM_RELEASE) { - if (jd->dir == -1) { + if (jd->dir == SCREEN_DIR_NONE) { area_join_cancel(C, op); return OPERATOR_CANCELLED; } @@ -3528,7 +3539,7 @@ static int screen_area_options_invoke(bContext *C, wmOperator *op, const wmEvent &ptr); /* store initial mouse cursor position. */ RNA_int_set_array(&ptr, "cursor", &event->x); - RNA_enum_set(&ptr, "direction", 'v'); + RNA_enum_set(&ptr, "direction", SCREEN_AXIS_V); /* Horizontal Split */ uiItemFullO(layout, @@ -3541,7 +3552,7 @@ static int screen_area_options_invoke(bContext *C, wmOperator *op, const wmEvent &ptr); /* store initial mouse cursor position. */ RNA_int_set_array(&ptr, "cursor", &event->x); - RNA_enum_set(&ptr, "direction", 'h'); + RNA_enum_set(&ptr, "direction", SCREEN_AXIS_H); if (sa1 && sa2) { uiItemS(layout); @@ -4126,7 +4137,7 @@ static void screen_area_menu_items(ScrArea *area, uiLayout *layout) &ptr); RNA_int_set_array(&ptr, "cursor", loc); - RNA_enum_set(&ptr, "direction", 'v'); + RNA_enum_set(&ptr, "direction", SCREEN_AXIS_V); /* Horizontal Split */ uiItemFullO(layout, @@ -4139,7 +4150,7 @@ static void screen_area_menu_items(ScrArea *area, uiLayout *layout) &ptr); RNA_int_set_array(&ptr, "cursor", &loc[0]); - RNA_enum_set(&ptr, "direction", 'h'); + RNA_enum_set(&ptr, "direction", SCREEN_AXIS_H); uiItemS(layout); @@ -5420,7 +5431,7 @@ static void context_cycle_prop_get(bScreen *screen, static int space_context_cycle_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) { - const int direction = RNA_enum_get(op->ptr, "direction"); + const eScreenCycle direction = RNA_enum_get(op->ptr, "direction"); PointerRNA ptr; PropertyRNA *prop; @@ -5469,7 +5480,7 @@ static int space_workspace_cycle_invoke(bContext *C, wmOperator *op, const wmEve } Main *bmain = CTX_data_main(C); - const int direction = RNA_enum_get(op->ptr, "direction"); + const eScreenCycle direction = RNA_enum_get(op->ptr, "direction"); WorkSpace *workspace_src = WM_window_get_active_workspace(win); WorkSpace *workspace_dst = NULL; diff --git a/source/blender/editors/sculpt_paint/paint_cursor.c b/source/blender/editors/sculpt_paint/paint_cursor.c index 7740fb42c37..3829aeebbeb 100644 --- a/source/blender/editors/sculpt_paint/paint_cursor.c +++ b/source/blender/editors/sculpt_paint/paint_cursor.c @@ -1324,6 +1324,13 @@ static bool paint_cursor_context_init(bContext *C, copy_v3_fl(pcontext->outline_col, 0.8f); } + const bool is_brush_tool = PAINT_brush_tool_poll(C); + if (!is_brush_tool) { + /* Use a default color for tools that are not brushes. */ + pcontext->outline_alpha = 0.8f; + copy_v3_fl(pcontext->outline_col, 0.8f); + } + pcontext->is_stroke_active = pcontext->ups->stroke_active; return true; @@ -1610,9 +1617,11 @@ static void paint_cursor_draw_3d_view_brush_cursor_inactive(PaintCursorContext * pcontext->radius); } + const bool is_brush_tool = PAINT_brush_tool_poll(pcontext->C); + /* Pose brush updates and rotation origins. */ - if (brush->sculpt_tool == SCULPT_TOOL_POSE) { + if (is_brush_tool && brush->sculpt_tool == SCULPT_TOOL_POSE) { /* Just after switching to the Pose Brush, the active vertex can be the same and the * cursor won't be tagged to update, so always initialize the preview chain if it is * null before drawing it. */ @@ -1645,7 +1654,7 @@ static void paint_cursor_draw_3d_view_brush_cursor_inactive(PaintCursorContext * 2); } - if (brush->sculpt_tool == SCULPT_TOOL_BOUNDARY) { + if (is_brush_tool && brush->sculpt_tool == SCULPT_TOOL_BOUNDARY) { paint_cursor_preview_boundary_data_update(pcontext, update_previews); paint_cursor_preview_boundary_data_pivot_draw(pcontext); } @@ -1666,17 +1675,18 @@ static void paint_cursor_draw_3d_view_brush_cursor_inactive(PaintCursorContext * GPU_matrix_mul(pcontext->vc.obact->obmat); /* Drawing Cursor overlays in 3D object space. */ - if (brush->sculpt_tool == SCULPT_TOOL_GRAB && (brush->flag & BRUSH_GRAB_ACTIVE_VERTEX)) { + if (is_brush_tool && brush->sculpt_tool == SCULPT_TOOL_GRAB && + (brush->flag & BRUSH_GRAB_ACTIVE_VERTEX)) { SCULPT_geometry_preview_lines_update(pcontext->C, pcontext->ss, pcontext->radius); sculpt_geometry_preview_lines_draw( pcontext->pos, pcontext->brush, pcontext->is_multires, pcontext->ss); } - if (brush->sculpt_tool == SCULPT_TOOL_POSE) { + if (is_brush_tool && brush->sculpt_tool == SCULPT_TOOL_POSE) { paint_cursor_pose_brush_segments_draw(pcontext); } - if (brush->sculpt_tool == SCULPT_TOOL_BOUNDARY) { + if (is_brush_tool && brush->sculpt_tool == SCULPT_TOOL_BOUNDARY) { SCULPT_boundary_edges_preview_draw( pcontext->pos, pcontext->ss, pcontext->outline_col, pcontext->outline_alpha); SCULPT_boundary_pivot_line_preview_draw(pcontext->pos, pcontext->ss); @@ -1692,7 +1702,7 @@ static void paint_cursor_draw_3d_view_brush_cursor_inactive(PaintCursorContext * paint_cursor_draw_main_inactive_cursor(pcontext); /* Cloth brush local simulation areas. */ - if (brush->sculpt_tool == SCULPT_TOOL_CLOTH && + if (is_brush_tool && brush->sculpt_tool == SCULPT_TOOL_CLOTH && brush->cloth_simulation_area_type != BRUSH_CLOTH_SIMULATION_AREA_GLOBAL) { const float white[3] = {1.0f, 1.0f, 1.0f}; const float zero_v[3] = {0.0f}; @@ -1704,7 +1714,7 @@ static void paint_cursor_draw_3d_view_brush_cursor_inactive(PaintCursorContext * } /* Layer brush height. */ - if (brush->sculpt_tool == SCULPT_TOOL_LAYER) { + if (is_brush_tool && brush->sculpt_tool == SCULPT_TOOL_LAYER) { SCULPT_layer_brush_height_preview_draw(pcontext->pos, brush, pcontext->radius, diff --git a/source/blender/editors/sculpt_paint/paint_intern.h b/source/blender/editors/sculpt_paint/paint_intern.h index 3ca0d853d6a..3f5093222e9 100644 --- a/source/blender/editors/sculpt_paint/paint_intern.h +++ b/source/blender/editors/sculpt_paint/paint_intern.h @@ -87,7 +87,7 @@ struct ViewContext *paint_stroke_view_context(struct PaintStroke *stroke); void *paint_stroke_mode_data(struct PaintStroke *stroke); float paint_stroke_distance_get(struct PaintStroke *stroke); void paint_stroke_set_mode_data(struct PaintStroke *stroke, void *mode_data); -bool paint_poll(struct bContext *C); +bool PAINT_brush_tool_poll(struct bContext *C); void paint_cursor_start(struct Paint *p, bool (*poll)(struct bContext *C)); void paint_cursor_delete_textures(void); diff --git a/source/blender/editors/sculpt_paint/paint_ops.c b/source/blender/editors/sculpt_paint/paint_ops.c index e1dc8fa30b9..fed89e02e8f 100644 --- a/source/blender/editors/sculpt_paint/paint_ops.c +++ b/source/blender/editors/sculpt_paint/paint_ops.c @@ -139,13 +139,14 @@ static int brush_scale_size_exec(bContext *C, wmOperator *op) Scene *scene = CTX_data_scene(C); Paint *paint = BKE_paint_get_active_from_context(C); Brush *brush = BKE_paint_brush(paint); + const bool is_gpencil = (brush && brush->gpencil_settings != NULL); // Object *ob = CTX_data_active_object(C); float scalar = RNA_float_get(op->ptr, "scalar"); if (brush) { /* pixel radius */ { - const int old_size = BKE_brush_size_get(scene, brush); + const int old_size = (!is_gpencil) ? BKE_brush_size_get(scene, brush) : brush->size; int size = (int)(scalar * old_size); if (abs(old_size - size) < U.pixelsize) { @@ -156,6 +157,12 @@ static int brush_scale_size_exec(bContext *C, wmOperator *op) size -= U.pixelsize; } } + /* Grease Pencil does not use unified size. */ + if (is_gpencil) { + brush->size = max_ii(size, 1); + WM_main_add_notifier(NC_BRUSH | NA_EDITED, brush); + return OPERATOR_FINISHED; + } BKE_brush_size_set(scene, brush, size); } diff --git a/source/blender/editors/sculpt_paint/paint_stroke.c b/source/blender/editors/sculpt_paint/paint_stroke.c index 49ddf2f82d8..b093f07226e 100644 --- a/source/blender/editors/sculpt_paint/paint_stroke.c +++ b/source/blender/editors/sculpt_paint/paint_stroke.c @@ -1462,7 +1462,7 @@ int paint_stroke_modal(bContext *C, wmOperator *op, const wmEvent *event) if (paint_supports_smooth_stroke(br, mode)) { stroke->stroke_cursor = WM_paint_cursor_activate( - SPACE_TYPE_ANY, RGN_TYPE_ANY, paint_poll, paint_draw_smooth_cursor, stroke); + SPACE_TYPE_ANY, RGN_TYPE_ANY, PAINT_brush_tool_poll, paint_draw_smooth_cursor, stroke); } stroke->stroke_init = true; @@ -1489,7 +1489,7 @@ int paint_stroke_modal(bContext *C, wmOperator *op, const wmEvent *event) if (br->flag & BRUSH_LINE) { stroke->stroke_cursor = WM_paint_cursor_activate( - SPACE_TYPE_ANY, RGN_TYPE_ANY, paint_poll, paint_draw_line_cursor, stroke); + SPACE_TYPE_ANY, RGN_TYPE_ANY, PAINT_brush_tool_poll, paint_draw_line_cursor, stroke); } first_dab = true; @@ -1659,7 +1659,7 @@ void paint_stroke_set_mode_data(PaintStroke *stroke, void *mode_data) stroke->mode_data = mode_data; } -bool paint_poll(bContext *C) +bool PAINT_brush_tool_poll(bContext *C) { Paint *p = BKE_paint_get_active_from_context(C); Object *ob = CTX_data_active_object(C); diff --git a/source/blender/editors/sculpt_paint/sculpt.c b/source/blender/editors/sculpt_paint/sculpt.c index 964e5bdaa90..2e1dd928f96 100644 --- a/source/blender/editors/sculpt_paint/sculpt.c +++ b/source/blender/editors/sculpt_paint/sculpt.c @@ -780,6 +780,10 @@ static void sculpt_vertex_neighbors_get_faces(SculptSession *ss, iter->neighbors = iter->neighbors_fixed; for (int i = 0; i < ss->pmap[index].count; i++) { + if (ss->face_sets[vert_map->indices[i]] < 0) { + /* Skip connectivity from hidden faces. */ + continue; + } const MPoly *p = &ss->mpoly[vert_map->indices[i]]; uint f_adj_v[2]; if (poly_get_adj_loops_from_vert(p, ss->mloop, index, f_adj_v) != -1) { @@ -6602,7 +6606,7 @@ bool SCULPT_poll_view3d(bContext *C) bool SCULPT_poll(bContext *C) { - return SCULPT_mode_poll(C) && paint_poll(C); + return SCULPT_mode_poll(C) && PAINT_brush_tool_poll(C); } static const char *sculpt_tool_name(Sculpt *sd) diff --git a/source/blender/editors/sculpt_paint/sculpt_expand.c b/source/blender/editors/sculpt_paint/sculpt_expand.c index 8b8ed42a694..40874375772 100644 --- a/source/blender/editors/sculpt_paint/sculpt_expand.c +++ b/source/blender/editors/sculpt_paint/sculpt_expand.c @@ -273,7 +273,7 @@ static bool sculpt_expand_state_get(SculptSession *ss, ExpandCache *expand_cache */ static bool sculpt_expand_face_state_get(SculptSession *ss, ExpandCache *expand_cache, const int f) { - if (ss->face_sets[f] <= 0) { + if (expand_cache->original_face_sets[f] <= 0) { return false; } @@ -1398,6 +1398,13 @@ static void sculpt_expand_face_sets_restore(SculptSession *ss, ExpandCache *expa { const int totfaces = ss->totfaces; for (int i = 0; i < totfaces; i++) { + if (expand_cache->original_face_sets[i] <= 0) { + /* Do not modify hidden Face Sets, even when restoring the IDs state. */ + continue; + } + if (!sculpt_expand_is_face_in_active_component(ss, expand_cache, i)) { + continue; + } ss->face_sets[i] = expand_cache->initial_face_sets[i]; } } @@ -1892,13 +1899,22 @@ static int sculpt_expand_modal(bContext *C, wmOperator *op, const wmEvent *event * The faces that were using the `delete_id` Face Set are filled * using the content from their neighbors. */ -static void sculpt_expand_delete_face_set_id( - int *r_face_sets, Mesh *mesh, MeshElemMap *pmap, const int totface, const int delete_id) +static void sculpt_expand_delete_face_set_id(int *r_face_sets, + SculptSession *ss, + ExpandCache *expand_cache, + Mesh *mesh, + const int delete_id) { + const int totface = ss->totfaces; + MeshElemMap *pmap = ss->pmap; + /* Check that all the face sets IDs in the mesh are not equal to `delete_id` * before attempting to delete it. */ bool all_same_id = true; for (int i = 0; i < totface; i++) { + if (!sculpt_expand_is_face_in_active_component(ss, expand_cache, i)) { + continue; + } if (r_face_sets[i] != delete_id) { all_same_id = false; break; @@ -1921,6 +1937,7 @@ static void sculpt_expand_delete_face_set_id( } while (BLI_LINKSTACK_SIZE(queue)) { + bool any_updated = false; while (BLI_LINKSTACK_SIZE(queue)) { const int f_index = POINTER_AS_INT(BLI_LINKSTACK_POP(queue)); int other_id = delete_id; @@ -1931,6 +1948,10 @@ static void sculpt_expand_delete_face_set_id( for (int i = 0; i < vert_map->count; i++) { const int neighbor_face_index = vert_map->indices[i]; + if (expand_cache->original_face_sets[neighbor_face_index] <= 0) { + /* Skip picking IDs from hidden Face Sets. */ + continue; + } if (r_face_sets[neighbor_face_index] != delete_id) { other_id = r_face_sets[neighbor_face_index]; } @@ -1938,18 +1959,36 @@ static void sculpt_expand_delete_face_set_id( } if (other_id != delete_id) { + any_updated = true; r_face_sets[f_index] = other_id; } else { BLI_LINKSTACK_PUSH(queue_next, POINTER_FROM_INT(f_index)); } } + if (!any_updated) { + /* No Face Sets where updated in this iteration, which means that no more content to keep + * filling the polys of the deleted Face Set was found. Break to avoid entering an infinite + * loop trying to search for those polys again. */ + break; + } BLI_LINKSTACK_SWAP(queue, queue_next); } BLI_LINKSTACK_FREE(queue); BLI_LINKSTACK_FREE(queue_next); + + /* Ensure that the visibility state of the modified Face Sets is the same as the original ones. + */ + for (int i = 0; i < totface; i++) { + if (expand_cache->original_face_sets[i] >= 0) { + r_face_sets[i] = abs(r_face_sets[i]); + } + else { + r_face_sets[i] = -abs(r_face_sets[i]); + } + } } static void sculpt_expand_cache_initial_config_set(bContext *C, @@ -2070,9 +2109,9 @@ static int sculpt_expand_invoke(bContext *C, wmOperator *op, const wmEvent *even if (ss->expand_cache->modify_active_face_set) { sculpt_expand_delete_face_set_id(ss->expand_cache->initial_face_sets, + ss, + ss->expand_cache, ob->data, - ss->pmap, - ss->totfaces, ss->expand_cache->next_face_set); } diff --git a/source/blender/editors/space_buttons/CMakeLists.txt b/source/blender/editors/space_buttons/CMakeLists.txt index c71e5e49d8d..b5f6874fcfc 100644 --- a/source/blender/editors/space_buttons/CMakeLists.txt +++ b/source/blender/editors/space_buttons/CMakeLists.txt @@ -50,7 +50,7 @@ if(WITH_FREESTYLE) endif() if(WITH_EXPERIMENTAL_FEATURES) - add_definitions(-DWITH_GEOMETRY_NODES) + add_definitions(-DWITH_SIMULATION_DATABLOCK) add_definitions(-DWITH_POINT_CLOUD) add_definitions(-DWITH_HAIR_NODES) endif() diff --git a/source/blender/editors/space_buttons/buttons_context.c b/source/blender/editors/space_buttons/buttons_context.c index 1699e704a4d..aeb2c04656e 100644 --- a/source/blender/editors/space_buttons/buttons_context.c +++ b/source/blender/editors/space_buttons/buttons_context.c @@ -975,7 +975,8 @@ int /*eContextResult*/ buttons_context(const bContext *C, if (matnr < 0) { matnr = 0; } - CTX_data_pointer_set(result, &ob->id, &RNA_MaterialSlot, &ob->mat[matnr]); + /* Keep aligned with rna_Object_material_slots_get. */ + CTX_data_pointer_set(result, &ob->id, &RNA_MaterialSlot, POINTER_FROM_INT(matnr + 1)); } } diff --git a/source/blender/editors/space_buttons/buttons_intern.h b/source/blender/editors/space_buttons/buttons_intern.h index 74e7bc11c26..7564fa4b930 100644 --- a/source/blender/editors/space_buttons/buttons_intern.h +++ b/source/blender/editors/space_buttons/buttons_intern.h @@ -35,6 +35,7 @@ struct bContext; struct bContextDataResult; struct bNode; struct bNodeTree; +struct bNodeSocket; struct wmOperatorType; struct SpaceProperties_Runtime { @@ -66,6 +67,7 @@ typedef struct ButsTextureUser { struct bNodeTree *ntree; struct bNode *node; + struct bNodeSocket *socket; const char *category; int icon; diff --git a/source/blender/editors/space_buttons/buttons_texture.c b/source/blender/editors/space_buttons/buttons_texture.c index 43128ed00fa..97e3cb750c1 100644 --- a/source/blender/editors/space_buttons/buttons_texture.c +++ b/source/blender/editors/space_buttons/buttons_texture.c @@ -75,15 +75,16 @@ static SpaceProperties *find_space_properties(const bContext *C); /************************* Texture User **************************/ -static void buttons_texture_user_node_property_add(ListBase *users, - ID *id, - PointerRNA ptr, - PropertyRNA *prop, - bNodeTree *ntree, - bNode *node, - const char *category, - int icon, - const char *name) +static void buttons_texture_user_socket_property_add(ListBase *users, + ID *id, + PointerRNA ptr, + PropertyRNA *prop, + bNodeTree *ntree, + bNode *node, + bNodeSocket *socket, + const char *category, + int icon, + const char *name) { ButsTextureUser *user = MEM_callocN(sizeof(ButsTextureUser), "ButsTextureUser"); @@ -92,6 +93,7 @@ static void buttons_texture_user_node_property_add(ListBase *users, user->prop = prop; user->ntree = ntree; user->node = node; + user->socket = socket; user->category = category; user->icon = icon; user->name = name; @@ -181,25 +183,29 @@ static void buttons_texture_modifier_geonodes_users_add(Object *ob, /* Recurse into the node group */ buttons_texture_modifier_geonodes_users_add(ob, nmd, (bNodeTree *)node->id, users); } - else if (node->type == GEO_NODE_ATTRIBUTE_SAMPLE_TEXTURE) { - RNA_pointer_create(&node_tree->id, &RNA_Node, node, &ptr); - prop = RNA_struct_find_property(&ptr, "texture"); - if (prop == NULL) { + LISTBASE_FOREACH (bNodeSocket *, socket, &node->inputs) { + if (socket->flag & SOCK_UNAVAIL) { + continue; + } + if (socket->type != SOCK_TEXTURE) { continue; } + RNA_pointer_create(&node_tree->id, &RNA_NodeSocket, socket, &ptr); + prop = RNA_struct_find_property(&ptr, "default_value"); PointerRNA texptr = RNA_property_pointer_get(&ptr, prop); Tex *tex = (RNA_struct_is_a(texptr.type, &RNA_Texture)) ? (Tex *)texptr.data : NULL; if (tex != NULL) { - buttons_texture_user_node_property_add(users, - &ob->id, - ptr, - prop, - node_tree, - node, - N_("Geometry Nodes"), - RNA_struct_ui_icon(ptr.type), - nmd->modifier.name); + buttons_texture_user_socket_property_add(users, + &ob->id, + ptr, + prop, + node_tree, + node, + socket, + N_("Geometry Nodes"), + RNA_struct_ui_icon(ptr.type), + nmd->modifier.name); } } } diff --git a/source/blender/editors/space_clip/tracking_ops_track.c b/source/blender/editors/space_clip/tracking_ops_track.c index 9882304d97d..0a99d1020f6 100644 --- a/source/blender/editors/space_clip/tracking_ops_track.c +++ b/source/blender/editors/space_clip/tracking_ops_track.c @@ -24,8 +24,11 @@ #include "MEM_guardedalloc.h" #include "BLI_math.h" +#include "BLI_string.h" #include "BLI_utildefines.h" +#include "BLT_translation.h" + #include "BKE_context.h" #include "BKE_global.h" #include "BKE_main.h" @@ -398,6 +401,28 @@ static int track_markers_modal(bContext *C, wmOperator *UNUSED(op), const wmEven return OPERATOR_PASS_THROUGH; } +static char *track_markers_desc(bContext *UNUSED(C), wmOperatorType *UNUSED(op), PointerRNA *ptr) +{ + const bool backwards = RNA_boolean_get(ptr, "backwards"); + const bool sequence = RNA_boolean_get(ptr, "sequence"); + + if (backwards && sequence) { + return BLI_strdup(TIP_("Track the selected markers backward for the entire clip")); + } + if (backwards && !sequence) { + return BLI_strdup(TIP_("Track the selected markers backward by one frame")); + } + if (!backwards && sequence) { + return BLI_strdup(TIP_("Track the selected markers forward for the entire clip")); + } + if (!backwards && !sequence) { + return BLI_strdup(TIP_("Track the selected markers forward by one frame")); + } + + /* Use default description. */ + return NULL; +} + void CLIP_OT_track_markers(wmOperatorType *ot) { /* identifiers */ @@ -410,6 +435,7 @@ void CLIP_OT_track_markers(wmOperatorType *ot) ot->invoke = track_markers_invoke; ot->modal = track_markers_modal; ot->poll = ED_space_clip_tracking_poll; + ot->get_description = track_markers_desc; /* flags */ ot->flag = OPTYPE_UNDO; diff --git a/source/blender/editors/space_file/file_draw.c b/source/blender/editors/space_file/file_draw.c index c1dcf2e56d3..189b9b4c874 100644 --- a/source/blender/editors/space_file/file_draw.c +++ b/source/blender/editors/space_file/file_draw.c @@ -323,6 +323,7 @@ static void file_draw_preview(uiBlock *block, int ex, ey; bool show_outline = !is_icon && (file->typeflag & (FILE_TYPE_IMAGE | FILE_TYPE_MOVIE | FILE_TYPE_BLENDER)); + const bool is_offline = (file->attributes & FILE_ATTR_OFFLINE); BLI_assert(imb != NULL); @@ -419,14 +420,14 @@ static void file_draw_preview(uiBlock *block, icon_x, icon_y, icon, icon_aspect / U.dpi_fac, icon_opacity, 0.0f, icon_color, false); } - if (is_link) { - /* Arrow icon to indicate it is a shortcut, link, or alias. */ + if (is_link || is_offline) { + /* Icon at bottom to indicate it is a shortcut, link, alias, or offline. */ float icon_x, icon_y; icon_x = xco + (2.0f * UI_DPI_FAC); icon_y = yco + (2.0f * UI_DPI_FAC); - const int arrow = ICON_LOOP_FORWARDS; + const int arrow = is_link ? ICON_LOOP_FORWARDS : ICON_URL; if (!is_icon) { - /* Arrow at very bottom-left if preview style. */ + /* At very bottom-left if preview style. */ const uchar dark[4] = {0, 0, 0, 255}; const uchar light[4] = {255, 255, 255, 255}; UI_icon_draw_ex(icon_x + 1, icon_y - 1, arrow, 1.0f / U.dpi_fac, 0.2f, 0.0f, dark, false); diff --git a/source/blender/editors/space_file/filelist.c b/source/blender/editors/space_file/filelist.c index c2b3a7ac0ee..37a32164cfc 100644 --- a/source/blender/editors/space_file/filelist.c +++ b/source/blender/editors/space_file/filelist.c @@ -1601,37 +1601,51 @@ static void filelist_cache_previews_push(FileList *filelist, FileDirEntry *entry BLI_assert(cache->flags & FLC_PREVIEWS_ACTIVE); - if (!entry->preview_icon_id && !(entry->flags & FILE_ENTRY_INVALID_PREVIEW) && - (entry->typeflag & (FILE_TYPE_IMAGE | FILE_TYPE_MOVIE | FILE_TYPE_FTFONT | - FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP | FILE_TYPE_BLENDERLIB))) { - FileListEntryPreview *preview = MEM_mallocN(sizeof(*preview), __func__); - FileListInternEntry *intern_entry = filelist->filelist_intern.filtered[index]; + if (!entry->preview_icon_id && (entry->attributes & FILE_ATTR_OFFLINE)) { + entry->flags |= FILE_ENTRY_INVALID_PREVIEW; + return; + } - if (entry->redirection_path) { - BLI_strncpy(preview->path, entry->redirection_path, FILE_MAXDIR); - } - else { - BLI_join_dirfile( - preview->path, sizeof(preview->path), filelist->filelist.root, entry->relpath); - } + if (entry->preview_icon_id) { + return; + } - preview->index = index; - preview->flags = entry->typeflag; - preview->in_memory_preview = intern_entry->local_data.preview_image; - preview->icon_id = 0; - // printf("%s: %d - %s - %p\n", __func__, preview->index, preview->path, preview->img); + if (entry->flags & FILE_ENTRY_INVALID_PREVIEW) { + return; + } + + if (!(entry->typeflag & (FILE_TYPE_IMAGE | FILE_TYPE_MOVIE | FILE_TYPE_FTFONT | + FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP | FILE_TYPE_BLENDERLIB))) { + return; + } - filelist_cache_preview_ensure_running(cache); + FileListEntryPreview *preview = MEM_mallocN(sizeof(*preview), __func__); + FileListInternEntry *intern_entry = filelist->filelist_intern.filtered[index]; - FileListEntryPreviewTaskData *preview_taskdata = MEM_mallocN(sizeof(*preview_taskdata), - __func__); - preview_taskdata->preview = preview; - BLI_task_pool_push(cache->previews_pool, - filelist_cache_preview_runf, - preview_taskdata, - true, - filelist_cache_preview_freef); + if (entry->redirection_path) { + BLI_strncpy(preview->path, entry->redirection_path, FILE_MAXDIR); + } + else { + BLI_join_dirfile( + preview->path, sizeof(preview->path), filelist->filelist.root, entry->relpath); } + + preview->index = index; + preview->flags = entry->typeflag; + preview->in_memory_preview = intern_entry->local_data.preview_image; + preview->icon_id = 0; + // printf("%s: %d - %s - %p\n", __func__, preview->index, preview->path, preview->img); + + filelist_cache_preview_ensure_running(cache); + + FileListEntryPreviewTaskData *preview_taskdata = MEM_mallocN(sizeof(*preview_taskdata), + __func__); + preview_taskdata->preview = preview; + BLI_task_pool_push(cache->previews_pool, + filelist_cache_preview_runf, + preview_taskdata, + true, + filelist_cache_preview_freef); } static void filelist_cache_init(FileListEntryCache *cache, size_t cache_size) @@ -2360,17 +2374,19 @@ bool filelist_file_cache_block(struct FileList *filelist, const int index) // printf("Re-queueing previews...\n"); - /* Note we try to preview first images around given index - i.e. assumed visible ones. */ if (cache->flags & FLC_PREVIEWS_ACTIVE) { - for (i = 0; ((index + i) < end_index) || ((index - i) >= start_index); i++) { - if ((index - i) >= start_index) { - const int idx = (cache->block_cursor + (index - start_index) - i) % cache_size; - filelist_cache_previews_push(filelist, cache->block_entries[idx], index - i); - } - if ((index + i) < end_index) { - const int idx = (cache->block_cursor + (index - start_index) + i) % cache_size; - filelist_cache_previews_push(filelist, cache->block_entries[idx], index + i); - } + /* Note we try to preview first images around given index - i.e. assumed visible ones. */ + int block_index = cache->block_cursor + (index - start_index); + int offs_max = max_ii(end_index - index, index - start_index); + for (i = 0; i <= offs_max; i++) { + int offs = i; + do { + int offs_idx = index + offs; + if (start_index <= offs_idx && offs_idx < end_index) { + int offs_block_idx = (block_index + offs) % (int)cache_size; + filelist_cache_previews_push(filelist, cache->block_entries[offs_block_idx], offs_idx); + } + } while ((offs = -offs) < 0); /* Switch between negative and positive offset. */ } } diff --git a/source/blender/editors/space_nla/nla_draw.c b/source/blender/editors/space_nla/nla_draw.c index 8bf6f2698e0..7d4011e0812 100644 --- a/source/blender/editors/space_nla/nla_draw.c +++ b/source/blender/editors/space_nla/nla_draw.c @@ -266,7 +266,7 @@ static void nla_strip_get_color_inside(AnimData *adt, NlaStrip *strip, float col } else if (strip->type == NLASTRIP_TYPE_META) { /* Meta Clip */ - /* TODO: should temporary metas get different colors too? */ + /* TODO: should temporary meta-strips get different colors too? */ if (strip->flag & NLASTRIP_FLAG_SELECT) { /* selected - use a bold purple color */ UI_GetThemeColor3fv(TH_NLA_META_SEL, color); diff --git a/source/blender/editors/space_node/drawnode.c b/source/blender/editors/space_node/drawnode.c index 5110c14ef4d..6b4366b2966 100644 --- a/source/blender/editors/space_node/drawnode.c +++ b/source/blender/editors/space_node/drawnode.c @@ -456,7 +456,9 @@ static void node_draw_frame(const bContext *C, } /* label */ - node_draw_frame_label(ntree, node, snode->runtime->aspect); + if (node->label[0] != '\0') { + node_draw_frame_label(ntree, node, snode->runtime->aspect); + } UI_block_end(C, node->block); UI_block_draw(C, node->block); @@ -3332,12 +3334,14 @@ static const float std_node_socket_colors[][4] = { {0.39, 0.78, 0.39, 1.0}, /* SOCK_SHADER */ {0.80, 0.65, 0.84, 1.0}, /* SOCK_BOOLEAN */ {0.0, 0.0, 0.0, 1.0}, /*__SOCK_MESH (deprecated) */ - {0.25, 0.75, 0.26, 1.0}, /* SOCK_INT */ + {0.35, 0.55, 0.36, 1.0}, /* SOCK_INT */ {0.44, 0.70, 1.00, 1.0}, /* SOCK_STRING */ {0.93, 0.62, 0.36, 1.0}, /* SOCK_OBJECT */ - {0.89, 0.76, 0.43, 1.0}, /* SOCK_IMAGE */ + {0.39, 0.22, 0.39, 1.0}, /* SOCK_IMAGE */ {0.00, 0.84, 0.64, 1.0}, /* SOCK_GEOMETRY */ {0.96, 0.96, 0.96, 1.0}, /* SOCK_COLLECTION */ + {0.62, 0.31, 0.64, 1.0}, /* SOCK_TEXTURE */ + {0.92, 0.46, 0.51, 1.0}, /* SOCK_MATERIAL */ }; /* common color callbacks for standard types */ @@ -3478,6 +3482,14 @@ static void std_node_socket_draw( uiItemR(layout, ptr, "default_value", DEFAULT_FLAGS, text, 0); break; } + case SOCK_TEXTURE: { + uiTemplateID(layout, C, ptr, "default_value", "texture.new", NULL, NULL, 0, ICON_NONE, NULL); + break; + } + case SOCK_MATERIAL: { + uiItemR(layout, ptr, "default_value", DEFAULT_FLAGS, text, 0); + break; + } default: node_socket_button_label(C, layout, ptr, node_ptr, text); break; diff --git a/source/blender/editors/space_node/node_edit.c b/source/blender/editors/space_node/node_edit.c index d4780534a83..50fa8b28468 100644 --- a/source/blender/editors/space_node/node_edit.c +++ b/source/blender/editors/space_node/node_edit.c @@ -1713,8 +1713,6 @@ static int node_mute_exec(bContext *C, wmOperator *UNUSED(op)) } } - do_tag_update |= ED_node_is_geometry(snode); - snode_notify(C, snode); if (do_tag_update) { snode_dag_update(C, snode); @@ -1755,8 +1753,6 @@ static int node_delete_exec(bContext *C, wmOperator *UNUSED(op)) } } - do_tag_update |= ED_node_is_geometry(snode); - ntreeUpdateTree(CTX_data_main(C), snode->edittree); snode_notify(C, snode); diff --git a/source/blender/editors/space_node/node_relationships.c b/source/blender/editors/space_node/node_relationships.c index 91fe8f5ec89..28c660b0632 100644 --- a/source/blender/editors/space_node/node_relationships.c +++ b/source/blender/editors/space_node/node_relationships.c @@ -852,8 +852,6 @@ static void node_link_exit(bContext *C, wmOperator *op, bool apply_links) } ntree->is_updating = false; - do_tag_update |= ED_node_is_geometry(snode); - ntreeUpdateTree(bmain, ntree); snode_notify(C, snode); if (do_tag_update) { @@ -1291,8 +1289,6 @@ static int cut_links_exec(bContext *C, wmOperator *op) } } - do_tag_update |= ED_node_is_geometry(snode); - if (found) { ntreeUpdateTree(CTX_data_main(C), snode->edittree); snode_notify(C, snode); @@ -1399,8 +1395,6 @@ static int mute_links_exec(bContext *C, wmOperator *op) link->flag &= ~NODE_LINK_TEST; } - do_tag_update |= ED_node_is_geometry(snode); - ntreeUpdateTree(CTX_data_main(C), snode->edittree); snode_notify(C, snode); if (do_tag_update) { @@ -1882,28 +1876,63 @@ void ED_node_link_intersect_test(ScrArea *area, int test) } } -/* assumes sockets in list */ -static bNodeSocket *socket_best_match(ListBase *sockets) -{ - /* find type range */ - int maxtype = 0; +static int get_main_socket_priority(const bNodeSocket *socket) +{ + switch ((eNodeSocketDatatype)socket->type) { + case __SOCK_MESH: + case SOCK_CUSTOM: + return -1; + case SOCK_BOOLEAN: + return 0; + case SOCK_INT: + return 1; + case SOCK_FLOAT: + return 2; + case SOCK_VECTOR: + return 3; + case SOCK_RGBA: + return 4; + case SOCK_STRING: + case SOCK_SHADER: + case SOCK_OBJECT: + case SOCK_IMAGE: + case SOCK_GEOMETRY: + case SOCK_COLLECTION: + case SOCK_TEXTURE: + case SOCK_MATERIAL: + return 5; + } + return -1; +} + +/** Get the "main" socket of a socket list using a heuristic based on socket types. */ +static bNodeSocket *get_main_socket(ListBase *sockets) +{ + /* find priority range */ + int maxpriority = -1; LISTBASE_FOREACH (bNodeSocket *, sock, sockets) { - maxtype = max_ii(sock->type, maxtype); + if (sock->flag & SOCK_UNAVAIL) { + continue; + } + maxpriority = max_ii(get_main_socket_priority(sock), maxpriority); } - /* try all types, starting from 'highest' (i.e. colors, vectors, values) */ - for (int type = maxtype; type >= 0; type--) { + /* try all priorities, starting from 'highest' */ + for (int priority = maxpriority; priority >= 0; priority--) { LISTBASE_FOREACH (bNodeSocket *, sock, sockets) { - if (!nodeSocketIsHidden(sock) && type == sock->type) { + if (!nodeSocketIsHidden(sock) && priority == get_main_socket_priority(sock)) { return sock; } } } - /* no visible sockets, unhide first of highest type */ - for (int type = maxtype; type >= 0; type--) { + /* no visible sockets, unhide first of highest priority */ + for (int priority = maxpriority; priority >= 0; priority--) { LISTBASE_FOREACH (bNodeSocket *, sock, sockets) { - if (type == sock->type) { + if (sock->flag & SOCK_UNAVAIL) { + continue; + } + if (priority == get_main_socket_priority(sock)) { sock->flag &= ~SOCK_HIDDEN; return sock; } @@ -2248,8 +2277,8 @@ void ED_node_link_insert(Main *bmain, ScrArea *area) } if (link) { - bNodeSocket *best_input = socket_best_match(&select->inputs); - bNodeSocket *best_output = socket_best_match(&select->outputs); + bNodeSocket *best_input = get_main_socket(&select->inputs); + bNodeSocket *best_output = get_main_socket(&select->outputs); if (best_input && best_output) { bNode *node = link->tonode; diff --git a/source/blender/editors/space_outliner/outliner_tools.c b/source/blender/editors/space_outliner/outliner_tools.c index 456c2079f27..c532dd8accd 100644 --- a/source/blender/editors/space_outliner/outliner_tools.c +++ b/source/blender/editors/space_outliner/outliner_tools.c @@ -902,7 +902,7 @@ static void id_override_library_reset_fn(bContext *C, } static void id_override_library_resync_fn(bContext *C, - ReportList *UNUSED(reports), + ReportList *reports, Scene *scene, TreeElement *te, TreeStoreElem *UNUSED(tsep), @@ -931,7 +931,7 @@ static void id_override_library_resync_fn(bContext *C, } BKE_lib_override_library_resync( - bmain, scene, CTX_data_view_layer(C), id_root, NULL, do_hierarchy_enforce, true); + bmain, scene, CTX_data_view_layer(C), id_root, NULL, do_hierarchy_enforce, true, reports); WM_event_add_notifier(C, NC_WINDOW, NULL); } diff --git a/source/blender/editors/space_sequencer/sequencer_draw.c b/source/blender/editors/space_sequencer/sequencer_draw.c index 7beb61e48d2..b55c8035fa3 100644 --- a/source/blender/editors/space_sequencer/sequencer_draw.c +++ b/source/blender/editors/space_sequencer/sequencer_draw.c @@ -885,10 +885,12 @@ static void draw_seq_background(Scene *scene, immUniformColor4ubv(col); if (seq->startstill) { - immRectf(pos, seq->startdisp, y1, (float)(seq->start), y2); + const float content_start = min_ff(seq->enddisp, seq->start); + immRectf(pos, seq->startdisp, y1, content_start, y2); } if (seq->endstill) { - immRectf(pos, (float)(seq->start + seq->len), y1, seq->enddisp, y2); + const float content_end = max_ff(seq->startdisp, seq->start + seq->len); + immRectf(pos, content_end, y1, seq->enddisp, y2); } } @@ -1105,6 +1107,10 @@ static void draw_seq_strip(const bContext *C, x2 = (seq->endstill) ? (seq->start + seq->len) : seq->enddisp; y2 = seq->machine + SEQ_STRIP_OFSTOP; + /* Limit body to strip bounds. Meta strip can end up with content outside of strip range. */ + x1 = min_ff(x1, seq->enddisp); + x2 = max_ff(x2, seq->startdisp); + float text_margin_y; bool y_threshold; if ((sseq->flag & SEQ_SHOW_STRIP_NAME) || (sseq->flag & SEQ_SHOW_STRIP_SOURCE) || @@ -1522,10 +1528,10 @@ static void *sequencer_OCIO_transform_ibuf(const bContext *C, ImBuf *ibuf, bool *r_glsl_used, eGPUTextureFormat *r_format, - eGPUDataFormat *r_data) + eGPUDataFormat *r_data, + void **r_buffer_cache_handle) { void *display_buffer; - void *cache_handle = NULL; bool force_fallback = false; *r_glsl_used = false; force_fallback |= (ED_draw_imbuf_method(ibuf) != IMAGE_DRAW_METHOD_GLSL); @@ -1578,13 +1584,10 @@ static void *sequencer_OCIO_transform_ibuf(const bContext *C, /* There is data to be displayed, but GLSL is not initialized * properly, in this case we fallback to CPU-based display transform. */ if ((ibuf->rect || ibuf->rect_float) && !*r_glsl_used) { - display_buffer = IMB_display_buffer_acquire_ctx(C, ibuf, &cache_handle); + display_buffer = IMB_display_buffer_acquire_ctx(C, ibuf, r_buffer_cache_handle); *r_format = GPU_RGBA8; *r_data = GPU_DATA_UBYTE; } - if (cache_handle) { - IMB_display_buffer_release(cache_handle); - } return display_buffer; } @@ -1658,6 +1661,7 @@ static void sequencer_draw_display_buffer(const bContext *C, bool draw_backdrop) { void *display_buffer; + void *buffer_cache_handle = NULL; if (sseq->mainb == SEQ_DRAW_IMG_IMBUF && sseq->flag & SEQ_USE_ALPHA) { GPU_blend(GPU_BLEND_ALPHA); @@ -1685,7 +1689,8 @@ static void sequencer_draw_display_buffer(const bContext *C, data = GPU_DATA_UBYTE; } else { - display_buffer = sequencer_OCIO_transform_ibuf(C, ibuf, &glsl_used, &format, &data); + display_buffer = sequencer_OCIO_transform_ibuf( + C, ibuf, &glsl_used, &format, &data, &buffer_cache_handle); } if (draw_backdrop) { @@ -1745,6 +1750,10 @@ static void sequencer_draw_display_buffer(const bContext *C, IMB_colormanagement_finish_glsl_draw(); } + if (buffer_cache_handle) { + IMB_display_buffer_release(buffer_cache_handle); + } + if (sseq->mainb == SEQ_DRAW_IMG_IMBUF && sseq->flag & SEQ_USE_ALPHA) { GPU_blend(GPU_BLEND_NONE); } diff --git a/source/blender/editors/space_sequencer/sequencer_edit.c b/source/blender/editors/space_sequencer/sequencer_edit.c index 9c204373ffc..ebd4c0090b4 100644 --- a/source/blender/editors/space_sequencer/sequencer_edit.c +++ b/source/blender/editors/space_sequencer/sequencer_edit.c @@ -290,7 +290,7 @@ static int sequencer_snap_exec(bContext *C, wmOperator *op) snap_frame = RNA_int_get(op->ptr, "frame"); - /* Check metas. */ + /* Check meta-strips. */ for (seq = ed->seqbasep->first; seq; seq = seq->next) { if (seq->flag & SELECT && !(seq->depth == 0 && seq->flag & SEQ_LOCK) && SEQ_transform_sequence_can_be_translated(seq)) { @@ -348,7 +348,7 @@ static int sequencer_snap_exec(bContext *C, wmOperator *op) } } - SEQ_sort(scene); + SEQ_sort(SEQ_active_seqbase_get(ed)); DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS); WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene); @@ -1443,7 +1443,7 @@ static int sequencer_split_exec(bContext *C, wmOperator *op) } } - SEQ_sort(scene); + SEQ_sort(SEQ_active_seqbase_get(ed)); } if (changed) { WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene); @@ -1577,17 +1577,6 @@ void SEQUENCER_OT_split(struct wmOperatorType *ot) /** \name Duplicate Strips Operator * \{ */ -static int apply_unique_name_fn(Sequence *seq, void *arg_pt) -{ - Scene *scene = (Scene *)arg_pt; - char name[sizeof(seq->name) - 2]; - - BLI_strncpy_utf8(name, seq->name + 2, sizeof(name)); - SEQ_sequence_base_unique_name_recursive(&scene->ed->seqbase, seq); - SEQ_dupe_animdata(scene, name, seq->name + 2); - return 1; -} - static int sequencer_add_duplicate_exec(bContext *C, wmOperator *UNUSED(op)) { Scene *scene = CTX_data_scene(C); @@ -1608,7 +1597,7 @@ static int sequencer_add_duplicate_exec(bContext *C, wmOperator *UNUSED(op)) BLI_movelisttolist(ed->seqbasep, &nseqbase); for (; seq; seq = seq->next) { - SEQ_recursive_apply(seq, apply_unique_name_fn, scene); + SEQ_ensure_unique_name(seq, scene); } WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene); @@ -1829,7 +1818,7 @@ static int sequencer_separate_images_exec(bContext *C, wmOperator *op) } } - SEQ_sort(scene); + SEQ_sort(SEQ_active_seqbase_get(ed)); WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene); @@ -1867,13 +1856,13 @@ static int sequencer_meta_toggle_exec(bContext *C, wmOperator *UNUSED(op)) Sequence *active_seq = SEQ_select_active_get(scene); if (active_seq && active_seq->type == SEQ_TYPE_META && active_seq->flag & SELECT) { - /* Enter metastrip. */ + /* Enter meta-strip. */ SEQ_meta_stack_alloc(ed, active_seq); SEQ_seqbase_active_set(ed, &active_seq->seqbase); SEQ_select_active_set(scene, NULL); } else { - /* Exit metastrip if possible. */ + /* Exit meta-strip if possible. */ if (BLI_listbase_is_empty(&ed->metastack)) { return OPERATOR_CANCELLED; } @@ -1895,7 +1884,7 @@ void SEQUENCER_OT_meta_toggle(wmOperatorType *ot) /* Identifiers. */ ot->name = "Toggle Meta Strip"; ot->idname = "SEQUENCER_OT_meta_toggle"; - ot->description = "Toggle a metastrip (to edit enclosed strips)"; + ot->description = "Toggle a meta-strip (to edit enclosed strips)"; /* Api callbacks. */ ot->exec = sequencer_meta_toggle_exec; @@ -1963,7 +1952,7 @@ void SEQUENCER_OT_meta_make(wmOperatorType *ot) /* Identifiers. */ ot->name = "Make Meta Strip"; ot->idname = "SEQUENCER_OT_meta_make"; - ot->description = "Group selected strips into a metastrip"; + ot->description = "Group selected strips into a meta-strip"; /* Api callbacks. */ ot->exec = sequencer_meta_make_exec; @@ -2014,7 +2003,7 @@ static int sequencer_meta_separate_exec(bContext *C, wmOperator *UNUSED(op)) } } - SEQ_sort(scene); + SEQ_sort(active_seqbase); DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS); WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene); @@ -2026,7 +2015,7 @@ void SEQUENCER_OT_meta_separate(wmOperatorType *ot) /* Identifiers. */ ot->name = "UnMeta Strip"; ot->idname = "SEQUENCER_OT_meta_separate"; - ot->description = "Put the contents of a metastrip back in the sequencer"; + ot->description = "Put the contents of a meta-strip back in the sequencer"; /* Api callbacks. */ ot->exec = sequencer_meta_separate_exec; @@ -2237,7 +2226,7 @@ static int sequencer_swap_exec(bContext *C, wmOperator *op) } } - SEQ_sort(scene); + SEQ_sort(SEQ_active_seqbase_get(ed)); WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene); @@ -2465,7 +2454,7 @@ static int sequencer_paste_exec(bContext *C, wmOperator *op) for (iseq = iseq_first; iseq; iseq = iseq->next) { /* Make sure, that pasted strips have unique names. */ - SEQ_recursive_apply(iseq, apply_unique_name_fn, scene); + SEQ_ensure_unique_name(iseq, scene); /* Translate after name has been changed, otherwise this will affect animdata of original * strip. */ SEQ_transform_translate_sequence(scene, iseq, ofs); diff --git a/source/blender/editors/space_sequencer/sequencer_select.c b/source/blender/editors/space_sequencer/sequencer_select.c index a9f8a70d61e..e386a48ed7e 100644 --- a/source/blender/editors/space_sequencer/sequencer_select.c +++ b/source/blender/editors/space_sequencer/sequencer_select.c @@ -26,7 +26,6 @@ #include <string.h> #include "BLI_blenlib.h" -#include "BLI_ghash.h" #include "BLI_math.h" #include "BLI_utildefines.h" @@ -1187,7 +1186,7 @@ static int sequencer_select_side_of_frame_exec(bContext *C, wmOperator *op) case 1: test = (timeline_frame <= seq->startdisp); break; - case 0: + case 2: test = (timeline_frame <= seq->enddisp) && (timeline_frame >= seq->startdisp); break; } @@ -1210,6 +1209,7 @@ void SEQUENCER_OT_select_side_of_frame(wmOperatorType *ot) static const EnumPropertyItem sequencer_select_left_right_types[] = { {-1, "LEFT", 0, "Left", "Select to the left of the current frame"}, {1, "RIGHT", 0, "Right", "Select to the right of the current frame"}, + {2, "CURRENT", 0, "Current frame", "Select intersecting with the current frame"}, {0, NULL, 0, NULL, NULL}, }; @@ -1622,64 +1622,47 @@ static bool select_grouped_time_overlap(Editing *ed, Sequence *actseq) return changed; } -static bool select_grouped_effect_link(Editing *ed, Sequence *actseq, const int channel) +/* Query strips that are in lower channel and intersect in time with seq_reference. */ +static void query_lower_channel_strips(Sequence *seq_reference, + ListBase *seqbase, + SeqCollection *collection) { - bool changed = false; - const bool is_audio = ((actseq->type == SEQ_TYPE_META) || SEQ_IS_SOUND(actseq)); - int startdisp = actseq->startdisp; - int enddisp = actseq->enddisp; - int machine = actseq->machine; - SeqIterator iter; - - LISTBASE_FOREACH (Sequence *, seq, SEQ_active_seqbase_get(ed)) { - seq->tmp = NULL; - } - - actseq->tmp = POINTER_FROM_INT(true); - - Sequence *seq = NULL; - for (SEQ_iterator_begin(ed, &iter, true); iter.valid; SEQ_iterator_next(&iter)) { - seq = iter.seq; - - /* Ignore all seqs already selected. */ - /* Ignore all seqs not sharing some time with active one. */ - /* Ignore all seqs of incompatible types (audio vs video). */ - if (!SEQ_CHANNEL_CHECK(seq, channel) || (seq->flag & SELECT) || (seq->startdisp >= enddisp) || - (seq->enddisp < startdisp) || (!is_audio && SEQ_IS_SOUND(seq)) || - (is_audio && !((seq->type == SEQ_TYPE_META) || SEQ_IS_SOUND(seq)))) { - continue; + LISTBASE_FOREACH (Sequence *, seq_test, seqbase) { + if (seq_test->machine > seq_reference->machine) { + continue; /* Not lower channel. */ } + if (seq_test->enddisp <= seq_reference->startdisp || + seq_test->startdisp >= seq_reference->enddisp) { + continue; /* Not intersecting in time. */ + } + SEQ_collection_append_strip(seq_test, collection); + } +} - /* If the seq is an effect one, we need extra checking. */ - if (SEQ_IS_EFFECT(seq) && ((seq->seq1 && seq->seq1->tmp) || (seq->seq2 && seq->seq2->tmp) || - (seq->seq3 && seq->seq3->tmp))) { - if (startdisp > seq->startdisp) { - startdisp = seq->startdisp; - } - if (enddisp < seq->enddisp) { - enddisp = seq->enddisp; - } - if (machine < seq->machine) { - machine = seq->machine; - } - - seq->tmp = POINTER_FROM_INT(true); +/* Select all strips within time range and with lower channel of initial selection. Then select + * effect chains of these strips. */ +static bool select_grouped_effect_link(Editing *ed, + Sequence *UNUSED(actseq), + const int UNUSED(channel)) +{ + ListBase *seqbase = SEQ_active_seqbase_get(ed); - seq->flag |= SELECT; - changed = true; + /* Get collection of strips. */ + SeqCollection *collection = SEQ_query_selected_strips(seqbase); + const int selected_strip_count = BLI_gset_len(collection->set); + SEQ_collection_expand(seqbase, collection, query_lower_channel_strips); + SEQ_collection_expand(seqbase, collection, SEQ_query_strip_effect_chain); - /* Unfortunately, we must restart checks from the beginning. */ - SEQ_iterator_end(&iter); - SEQ_iterator_begin(ed, &iter, true); - } + /* Check if other strips will be affected. */ + const bool changed = BLI_gset_len(collection->set) > selected_strip_count; - /* Video strips below active one, or any strip for audio (order doesn't matter here). */ - else if (seq->machine < machine || is_audio) { - seq->flag |= SELECT; - changed = true; - } + /* Actual logic. */ + Sequence *seq; + SEQ_ITERATOR_FOREACH (seq, collection) { + seq->flag |= SELECT; } - SEQ_iterator_end(&iter); + + SEQ_collection_free(collection); return changed; } diff --git a/source/blender/editors/space_view3d/space_view3d.c b/source/blender/editors/space_view3d/space_view3d.c index e6916c34a88..b5274c2357e 100644 --- a/source/blender/editors/space_view3d/space_view3d.c +++ b/source/blender/editors/space_view3d/space_view3d.c @@ -1595,7 +1595,6 @@ static void space_view3d_refresh(const bContext *C, ScrArea *UNUSED(area)) } const char *view3d_context_dir[] = { - "active_base", "active_object", NULL, }; @@ -1608,20 +1607,6 @@ static int view3d_context(const bContext *C, const char *member, bContextDataRes if (CTX_data_dir(member)) { CTX_data_dir_set(result, view3d_context_dir); } - else if (CTX_data_equals(member, "active_base")) { - Scene *scene = CTX_data_scene(C); - ViewLayer *view_layer = CTX_data_view_layer(C); - if (view_layer->basact) { - Object *ob = view_layer->basact->object; - /* if hidden but in edit mode, we still display, can happen with animation */ - if ((view_layer->basact->flag & BASE_VISIBLE_DEPSGRAPH) != 0 || - (ob->mode != OB_MODE_OBJECT)) { - CTX_data_pointer_set(result, &scene->id, &RNA_ObjectBase, view_layer->basact); - } - } - - return 1; - } else if (CTX_data_equals(member, "active_object")) { /* In most cases the active object is the `view_layer->basact->object`. * For the 3D view however it can be NULL when hidden. diff --git a/source/blender/editors/space_view3d/view3d_draw.c b/source/blender/editors/space_view3d/view3d_draw.c index f3a279ee12b..0a89b7d0292 100644 --- a/source/blender/editors/space_view3d/view3d_draw.c +++ b/source/blender/editors/space_view3d/view3d_draw.c @@ -1288,6 +1288,11 @@ static void draw_viewport_name(ARegion *region, View3D *v3d, int xoffset, int *y name_array[name_array_len++] = IFACE_(" (Local)"); } + /* Indicate that clipping region is enabled. */ + if (rv3d->rflag & RV3D_CLIPPING) { + name_array[name_array_len++] = IFACE_(" (Clipped)"); + } + if (name_array_len > 1) { BLI_string_join_array(tmpstr, sizeof(tmpstr), name_array, name_array_len); name = tmpstr; @@ -2017,7 +2022,6 @@ ImBuf *ED_view3d_draw_offscreen_imbuf_simple(Depsgraph *depsgraph, source_shading_settings = shading_override; } memcpy(&v3d.shading, source_shading_settings, sizeof(View3DShading)); - v3d.shading.type = drawtype; if (drawtype == OB_MATERIAL) { v3d.shading.flag = V3D_SHADING_SCENE_WORLD | V3D_SHADING_SCENE_LIGHTS; @@ -2027,6 +2031,12 @@ ImBuf *ED_view3d_draw_offscreen_imbuf_simple(Depsgraph *depsgraph, v3d.shading.flag = V3D_SHADING_SCENE_WORLD_RENDER | V3D_SHADING_SCENE_LIGHTS_RENDER; v3d.shading.render_pass = SCE_PASS_COMBINED; } + else if (drawtype == OB_TEXTURE) { + drawtype = OB_SOLID; + v3d.shading.light = V3D_LIGHTING_STUDIO; + v3d.shading.color_type = V3D_SHADING_TEXTURE_COLOR; + } + v3d.shading.type = drawtype; v3d.flag2 = V3D_HIDE_OVERLAYS; @@ -2067,7 +2077,7 @@ ImBuf *ED_view3d_draw_offscreen_imbuf_simple(Depsgraph *depsgraph, return ED_view3d_draw_offscreen_imbuf(depsgraph, scene, - drawtype, + v3d.shading.type, &v3d, ®ion, width, diff --git a/source/blender/editors/space_view3d/view3d_gizmo_ruler.c b/source/blender/editors/space_view3d/view3d_gizmo_ruler.c index 3134553c159..0d568363b00 100644 --- a/source/blender/editors/space_view3d/view3d_gizmo_ruler.c +++ b/source/blender/editors/space_view3d/view3d_gizmo_ruler.c @@ -446,7 +446,7 @@ static bool view3d_ruler_to_gpencil(bContext *C, wmGizmoGroup *gzgroup) gpl = view3d_ruler_layer_get(gpd); if (gpl == NULL) { - gpl = BKE_gpencil_layer_addnew(gpd, ruler_name, false); + gpl = BKE_gpencil_layer_addnew(gpd, ruler_name, false, false); copy_v4_v4(gpl->color, U.gpencil_new_layer_col); gpl->thickness = 1; gpl->flag |= GP_LAYER_HIDE | GP_LAYER_IS_RULER; diff --git a/source/blender/editors/transform/transform.c b/source/blender/editors/transform/transform.c index bb3afd77dba..ba8e5c1e2c6 100644 --- a/source/blender/editors/transform/transform.c +++ b/source/blender/editors/transform/transform.c @@ -782,7 +782,7 @@ static bool transform_event_modal_constraint(TransInfo *t, short modal_type) } else { short orient_index = 1; - if (t->orient_curr == 0 || ELEM(constraint_curr, -1, constraint_new)) { + if (t->orient_curr == O_DEFAULT || ELEM(constraint_curr, -1, constraint_new)) { /* Successive presses on existing axis, cycle orientation modes. */ orient_index = (short)((t->orient_curr + 1) % (int)ARRAY_SIZE(t->orient)); } @@ -1412,25 +1412,6 @@ void saveTransform(bContext *C, TransInfo *t, wmOperator *op) ToolSettings *ts = CTX_data_tool_settings(C); PropertyRNA *prop; - if (!(t->con.mode & CON_APPLY) && (t->flag & T_MODAL) && - ELEM(t->mode, TFM_TRANSLATION, TFM_RESIZE)) { - /* When redoing these modes the first time, it's more convenient to save - * in the Global orientation. */ - if (t->mode == TFM_TRANSLATION) { - mul_m3_v3(t->spacemtx, t->values_final); - } - else { - float tmat[3][3], sizemat[3][3]; - size_to_mat3(sizemat, t->values_final); - mul_m3_m3m3(tmat, t->spacemtx, sizemat); - mat3_to_size(t->values_final, tmat); - } - - BLI_assert(t->orient_curr == 0); - unit_m3(t->spacemtx); - t->orient[0].type = V3D_ORIENT_GLOBAL; - } - /* Save back mode in case we're in the generic operator */ if ((prop = RNA_struct_find_property(op->ptr, "mode"))) { RNA_property_enum_set(op->ptr, prop, t->mode); @@ -1638,7 +1619,7 @@ static void initSnapSpatial(TransInfo *t, float r_snap[2]) /** * \note caller needs to free 't' on a 0 return - * \warning \a event might be NULL (when tweaking from redo panel) + * \warning \a event might be NULL (when tweaking from redo panel) * \see #saveTransform which writes these values back. */ bool initTransform(bContext *C, TransInfo *t, wmOperator *op, const wmEvent *event, int mode) diff --git a/source/blender/editors/transform/transform.h b/source/blender/editors/transform/transform.h index 090b8b83dcc..a10a53b2983 100644 --- a/source/blender/editors/transform/transform.h +++ b/source/blender/editors/transform/transform.h @@ -596,11 +596,18 @@ typedef struct TransInfo { * mouse button then.) */ bool is_launch_event_tweak; + bool is_orient_set; + struct { short type; float matrix[3][3]; } orient[3]; - short orient_curr; + + enum { + O_DEFAULT = 0, + O_SCENE, + O_SET, + } orient_curr; /** backup from view3d, to restore on end. */ short gizmo_flag; diff --git a/source/blender/editors/transform/transform_constraints.c b/source/blender/editors/transform/transform_constraints.c index 2037981e655..615467932a7 100644 --- a/source/blender/editors/transform/transform_constraints.c +++ b/source/blender/editors/transform/transform_constraints.c @@ -953,9 +953,8 @@ void startConstraint(TransInfo *t) void stopConstraint(TransInfo *t) { - if (t->orient_curr != 0) { - t->orient_curr = 0; - transform_orientations_current_set(t, t->orient_curr); + if (t->orient_curr != O_DEFAULT) { + transform_orientations_current_set(t, O_DEFAULT); } t->con.mode &= ~(CON_APPLY | CON_SELECT); @@ -971,8 +970,8 @@ void stopConstraint(TransInfo *t) void initSelectConstraint(TransInfo *t) { - if (t->orient_curr == 0) { - transform_orientations_current_set(t, 1); + if (t->orient_curr == O_DEFAULT) { + transform_orientations_current_set(t, O_SCENE); } setUserConstraint(t, CON_APPLY | CON_SELECT, "%s"); diff --git a/source/blender/editors/transform/transform_convert.c b/source/blender/editors/transform/transform_convert.c index 98db27c83f7..b4175faacf4 100644 --- a/source/blender/editors/transform/transform_convert.c +++ b/source/blender/editors/transform/transform_convert.c @@ -37,6 +37,7 @@ #include "BKE_context.h" #include "BKE_fcurve.h" #include "BKE_global.h" +#include "BKE_image.h" #include "BKE_layer.h" #include "BKE_lib_id.h" #include "BKE_main.h" @@ -505,9 +506,27 @@ bool clipUVTransform(TransInfo *t, float vec[2], const bool resize) bool clipx = true, clipy = true; float min[2], max[2]; - min[0] = min[1] = 0.0f; - max[0] = t->aspect[0]; - max[1] = t->aspect[1]; + /* Check if the current image in UV editor is a tiled image or not. */ + const SpaceImage *sima = t->area->spacedata.first; + const Image *image = sima->image; + const bool is_tiled_image = image && (image->source == IMA_SRC_TILED); + /* Stores the coordinates of the closest UDIM tile. + * Also acts as an offset to the tile from the origin of UV space. */ + float base_offset[2] = {0.0f, 0.0f}; + + /* If tiled image then constrain to correct/closest UDIM tile, else 0-1 UV space. */ + if (is_tiled_image) { + int nearest_tile_index = BKE_image_find_nearest_tile(image, t->center_global); + if (nearest_tile_index != -1) { + nearest_tile_index -= 1001; + /* Getting coordinates of nearest tile from the tile index. */ + base_offset[0] = nearest_tile_index % 10; + base_offset[1] = nearest_tile_index / 10; + } + } + + min[0] = min[1] = FLT_MAX; + max[0] = max[1] = FLT_MIN; FOREACH_TRANS_DATA_CONTAINER (t, tc) { @@ -520,42 +539,48 @@ bool clipUVTransform(TransInfo *t, float vec[2], const bool resize) } if (resize) { - if (min[0] < 0.0f && t->center_global[0] > 0.0f && t->center_global[0] < t->aspect[0] * 0.5f) { - vec[0] *= t->center_global[0] / (t->center_global[0] - min[0]); + if (min[0] < base_offset[0] && t->center_global[0] > base_offset[0] && + t->center_global[0] < base_offset[0] + (t->aspect[0] * 0.5f)) { + vec[0] *= (t->center_global[0] - base_offset[0]) / (t->center_global[0] - min[0]); } - else if (max[0] > t->aspect[0] && t->center_global[0] < t->aspect[0]) { - vec[0] *= (t->center_global[0] - t->aspect[0]) / (t->center_global[0] - max[0]); + else if (max[0] > (base_offset[0] + t->aspect[0]) && + t->center_global[0] < (base_offset[0] + t->aspect[0])) { + vec[0] *= (t->center_global[0] - (base_offset[0] + t->aspect[0])) / + (t->center_global[0] - max[0]); } else { clipx = 0; } - if (min[1] < 0.0f && t->center_global[1] > 0.0f && t->center_global[1] < t->aspect[1] * 0.5f) { - vec[1] *= t->center_global[1] / (t->center_global[1] - min[1]); + if (min[1] < base_offset[1] && t->center_global[1] > base_offset[1] && + t->center_global[1] < base_offset[1] + (t->aspect[1] * 0.5f)) { + vec[1] *= (t->center_global[1] - base_offset[1]) / (t->center_global[1] - min[1]); } - else if (max[1] > t->aspect[1] && t->center_global[1] < t->aspect[1]) { - vec[1] *= (t->center_global[1] - t->aspect[1]) / (t->center_global[1] - max[1]); + else if (max[1] > (base_offset[1] + t->aspect[1]) && + t->center_global[1] < (base_offset[1] + t->aspect[1])) { + vec[1] *= (t->center_global[1] - (base_offset[1] + t->aspect[1])) / + (t->center_global[1] - max[1]); } else { clipy = 0; } } else { - if (min[0] < 0.0f) { - vec[0] -= min[0]; + if (min[0] < base_offset[0]) { + vec[0] += base_offset[0] - min[0]; } - else if (max[0] > t->aspect[0]) { - vec[0] -= max[0] - t->aspect[0]; + else if (max[0] > base_offset[0] + t->aspect[0]) { + vec[0] -= max[0] - base_offset[0] - t->aspect[0]; } else { clipx = 0; } - if (min[1] < 0.0f) { - vec[1] -= min[1]; + if (min[1] < base_offset[1]) { + vec[1] += base_offset[1] - min[1]; } - else if (max[1] > t->aspect[1]) { - vec[1] -= max[1] - t->aspect[1]; + else if (max[1] > base_offset[1] + t->aspect[1]) { + vec[1] -= max[1] - base_offset[1] - t->aspect[1]; } else { clipy = 0; diff --git a/source/blender/editors/transform/transform_convert_sequencer.c b/source/blender/editors/transform/transform_convert_sequencer.c index 30418471d6d..34be89e5ed9 100644 --- a/source/blender/editors/transform/transform_convert_sequencer.c +++ b/source/blender/editors/transform/transform_convert_sequencer.c @@ -491,7 +491,7 @@ static void freeSeqData(TransInfo *t, TransDataContainer *tc, TransCustomData *c } } - SEQ_sort(t->scene); + SEQ_sort(seqbasep); } else { /* Canceled, need to update the strips display */ @@ -707,7 +707,7 @@ static void flushTransSeq(TransInfo *t) /* originally TFM_TIME_EXTEND, transform changes */ if (ELEM(t->mode, TFM_SEQ_SLIDE, TFM_TIME_TRANSLATE)) { - /* Special annoying case here, need to calc metas with TFM_TIME_EXTEND only */ + /* Special annoying case here, need to calc meta-strips with TFM_TIME_EXTEND only */ /* calc all meta's then effects T27953. */ for (seq = seqbasep->first; seq; seq = seq->next) { diff --git a/source/blender/editors/transform/transform_generics.c b/source/blender/editors/transform/transform_generics.c index c9b6bef5904..71c91221fbb 100644 --- a/source/blender/editors/transform/transform_generics.c +++ b/source/blender/editors/transform/transform_generics.c @@ -414,8 +414,6 @@ void initTransInfo(bContext *C, TransInfo *t, wmOperator *op, const wmEvent *eve int orient_type_set = -1; int orient_type_matrix_set = -1; - bool use_orient_axis = false; - if ((t->spacetype == SPACE_VIEW3D) && (t->region->regiontype == RGN_TYPE_WINDOW)) { TransformOrientationSlot *orient_slot = &t->scene->orientation_slots[SCE_ORIENT_DEFAULT]; orient_type_scene = orient_slot->type; @@ -435,7 +433,6 @@ void initTransInfo(bContext *C, TransInfo *t, wmOperator *op, const wmEvent *eve if (op && (prop = RNA_struct_find_property(op->ptr, "orient_axis"))) { t->orient_axis = RNA_property_enum_get(op->ptr, prop); - use_orient_axis = true; } if (op && (prop = RNA_struct_find_property(op->ptr, "orient_axis_ortho"))) { @@ -457,28 +454,23 @@ void initTransInfo(bContext *C, TransInfo *t, wmOperator *op, const wmEvent *eve if (orient_type_set != -1) { orient_type_default = orient_type_set; + t->is_orient_set = true; } else if (orient_type_matrix_set != -1) { orient_type_default = orient_type_set = orient_type_matrix_set; + t->is_orient_set = true; } else if (t->con.mode & CON_APPLY) { orient_type_default = orient_type_set = orient_type_scene; } else { + orient_type_default = orient_type_scene; if (orient_type_scene == V3D_ORIENT_GLOBAL) { orient_type_set = V3D_ORIENT_LOCAL; } else { orient_type_set = V3D_ORIENT_GLOBAL; } - - if ((t->flag & T_MODAL) && (use_orient_axis || transform_mode_is_changeable(t->mode)) && - (t->mode != TFM_ALIGN)) { - orient_type_default = V3D_ORIENT_VIEW; - } - else { - orient_type_default = orient_type_scene; - } } BLI_assert(!ELEM(-1, orient_type_default, orient_type_set)); @@ -487,9 +479,9 @@ void initTransInfo(bContext *C, TransInfo *t, wmOperator *op, const wmEvent *eve orient_type_set = V3D_ORIENT_CUSTOM_MATRIX; } - orient_types[0] = (short)orient_type_default; - orient_types[1] = (short)orient_type_scene; - orient_types[2] = (short)orient_type_set; + orient_types[O_DEFAULT] = (short)orient_type_default; + orient_types[O_SCENE] = (short)orient_type_scene; + orient_types[O_SET] = (short)orient_type_set; for (int i = 0; i < 3; i++) { /* For efficiency, avoid calculating the same orientation twice. */ @@ -506,9 +498,6 @@ void initTransInfo(bContext *C, TransInfo *t, wmOperator *op, const wmEvent *eve } } - /* Set orient_curr to -1 in order to force the update in - * `transform_orientations_current_set`. */ - t->orient_curr = -1; transform_orientations_current_set(t, (t->con.mode & CON_APPLY) ? 2 : 0); } diff --git a/source/blender/editors/transform/transform_mode.c b/source/blender/editors/transform/transform_mode.c index 867a6cc4291..d61e7bb867c 100644 --- a/source/blender/editors/transform/transform_mode.c +++ b/source/blender/editors/transform/transform_mode.c @@ -45,6 +45,7 @@ #include "transform.h" #include "transform_convert.h" +#include "transform_orientations.h" #include "transform_snap.h" /* Own include. */ @@ -1271,4 +1272,38 @@ void transform_mode_init(TransInfo *t, wmOperator *op, const int mode) * BLI_assert(t->mode == mode); */ } +/** + * When in modal and not set, initializes a default orientation for the mode. + */ +void transform_mode_default_modal_orientation_set(TransInfo *t, int type) +{ + /* Currently only these types are supported. */ + BLI_assert(ELEM(type, V3D_ORIENT_GLOBAL, V3D_ORIENT_VIEW)); + + if (t->is_orient_set) { + return; + } + + if (!(t->flag & T_MODAL)) { + return; + } + + if (t->orient[O_DEFAULT].type == type) { + return; + } + + RegionView3D *rv3d = NULL; + if ((type == V3D_ORIENT_VIEW) && (t->spacetype == SPACE_VIEW3D) && t->region && + (t->region->regiontype == RGN_TYPE_WINDOW)) { + rv3d = t->region->regiondata; + } + + t->orient[O_DEFAULT].type = ED_transform_calc_orientation_from_type_ex( + NULL, t->orient[O_DEFAULT].matrix, NULL, rv3d, NULL, NULL, type, 0); + + if (t->orient_curr == O_DEFAULT) { + /* Update Orientation. */ + transform_orientations_current_set(t, O_DEFAULT); + } +} /** \} */ diff --git a/source/blender/editors/transform/transform_mode.h b/source/blender/editors/transform/transform_mode.h index 6d7a0b528ae..106dc68c9ee 100644 --- a/source/blender/editors/transform/transform_mode.h +++ b/source/blender/editors/transform/transform_mode.h @@ -61,6 +61,7 @@ short getAnimEdit_SnapMode(TransInfo *t); void doAnimEdit_SnapFrame( TransInfo *t, TransData *td, TransData2D *td2d, struct AnimData *adt, short autosnap); void transform_mode_init(TransInfo *t, struct wmOperator *op, const int mode); +void transform_mode_default_modal_orientation_set(TransInfo *t, int type); /* transform_mode_align.c */ void initAlign(TransInfo *t); diff --git a/source/blender/editors/transform/transform_mode_edge_rotate_normal.c b/source/blender/editors/transform/transform_mode_edge_rotate_normal.c index c78115561b2..b7b3de69731 100644 --- a/source/blender/editors/transform/transform_mode_edge_rotate_normal.c +++ b/source/blender/editors/transform/transform_mode_edge_rotate_normal.c @@ -148,5 +148,7 @@ void initNormalRotation(TransInfo *t) storeCustomLNorValue(tc, bm); } + + transform_mode_default_modal_orientation_set(t, V3D_ORIENT_VIEW); } /** \} */ diff --git a/source/blender/editors/transform/transform_mode_resize.c b/source/blender/editors/transform/transform_mode_resize.c index fcdc57a98e2..0d7d0be3c0e 100644 --- a/source/blender/editors/transform/transform_mode_resize.c +++ b/source/blender/editors/transform/transform_mode_resize.c @@ -193,5 +193,7 @@ void initResize(TransInfo *t) t->num.unit_type[0] = B_UNIT_NONE; t->num.unit_type[1] = B_UNIT_NONE; t->num.unit_type[2] = B_UNIT_NONE; + + transform_mode_default_modal_orientation_set(t, V3D_ORIENT_GLOBAL); } /** \} */ diff --git a/source/blender/editors/transform/transform_mode_rotate.c b/source/blender/editors/transform/transform_mode_rotate.c index 8d8594d5775..0fdbfb25989 100644 --- a/source/blender/editors/transform/transform_mode_rotate.c +++ b/source/blender/editors/transform/transform_mode_rotate.c @@ -249,5 +249,7 @@ void initRotation(TransInfo *t) if (t->flag & T_2D_EDIT) { t->flag |= T_NO_CONSTRAINT; } + + transform_mode_default_modal_orientation_set(t, V3D_ORIENT_VIEW); } /** \} */ diff --git a/source/blender/editors/transform/transform_mode_shear.c b/source/blender/editors/transform/transform_mode_shear.c index a41c49710b9..23ee55bf6c5 100644 --- a/source/blender/editors/transform/transform_mode_shear.c +++ b/source/blender/editors/transform/transform_mode_shear.c @@ -232,5 +232,7 @@ void initShear(TransInfo *t) t->num.unit_type[0] = B_UNIT_NONE; /* Don't think we have any unit here? */ t->flag |= T_NO_CONSTRAINT; + + transform_mode_default_modal_orientation_set(t, V3D_ORIENT_VIEW); } /** \} */ diff --git a/source/blender/editors/transform/transform_mode_translate.c b/source/blender/editors/transform/transform_mode_translate.c index 41fc6ee0aaf..0bef6364214 100644 --- a/source/blender/editors/transform/transform_mode_translate.c +++ b/source/blender/editors/transform/transform_mode_translate.c @@ -470,5 +470,7 @@ void initTranslation(TransInfo *t) t->num.unit_type[1] = B_UNIT_NONE; t->num.unit_type[2] = B_UNIT_NONE; } + + transform_mode_default_modal_orientation_set(t, V3D_ORIENT_GLOBAL); } /** \} */ diff --git a/source/blender/editors/transform/transform_orientations.c b/source/blender/editors/transform/transform_orientations.c index 5a5f478fc2b..b3ed294845d 100644 --- a/source/blender/editors/transform/transform_orientations.c +++ b/source/blender/editors/transform/transform_orientations.c @@ -671,10 +671,6 @@ const char *transform_orientations_spacename_get(TransInfo *t, const short orien void transform_orientations_current_set(TransInfo *t, const short orient_index) { - if (t->orient_curr == orient_index) { - return; - } - const short orientation = t->orient[orient_index].type; const char *spacename = transform_orientations_spacename_get(t, orientation); diff --git a/source/blender/functions/FN_generic_virtual_array.hh b/source/blender/functions/FN_generic_virtual_array.hh index 2e91479dd30..d530d10b3c8 100644 --- a/source/blender/functions/FN_generic_virtual_array.hh +++ b/source/blender/functions/FN_generic_virtual_array.hh @@ -135,6 +135,9 @@ class GVArray { this->get_internal_single(r_value); } + void materialize(void *dst) const; + void materialize(const IndexMask mask, void *dst) const; + void materialize_to_uninitialized(void *dst) const; void materialize_to_uninitialized(const IndexMask mask, void *dst) const; @@ -162,6 +165,7 @@ class GVArray { virtual bool is_single_impl() const; virtual void get_internal_single_impl(void *UNUSED(r_value)) const; + virtual void materialize_impl(const IndexMask mask, void *dst) const; virtual void materialize_to_uninitialized_impl(const IndexMask mask, void *dst) const; virtual const void *try_get_internal_varray_impl() const; @@ -216,11 +220,19 @@ class GVMutableArray : public GVArray { void fill(const void *value); + /* Copy the values from the source buffer to all elements in the virtual array. */ + void set_all(const void *src) + { + this->set_all_impl(src); + } + protected: virtual void set_by_copy_impl(const int64_t index, const void *value); virtual void set_by_relocate_impl(const int64_t index, void *value); virtual void set_by_move_impl(const int64_t index, void *value) = 0; + virtual void set_all_impl(const void *src); + virtual void *try_get_internal_mutable_varray_impl(); }; @@ -370,6 +382,11 @@ template<typename T> class GVArray_For_VArray : public GVArray { *(T *)r_value = varray_->get_internal_single(); } + void materialize_impl(const IndexMask mask, void *dst) const override + { + varray_->materialize(mask, MutableSpan((T *)dst, mask.min_array_size())); + } + void materialize_to_uninitialized_impl(const IndexMask mask, void *dst) const override { varray_->materialize_to_uninitialized(mask, MutableSpan((T *)dst, mask.min_array_size())); @@ -545,6 +562,21 @@ template<typename T> class GVMutableArray_For_VMutableArray : public GVMutableAr varray_->set(index, std::move(value_)); } + void set_all_impl(const void *src) override + { + varray_->set_all(Span((T *)src, size_)); + } + + void materialize_impl(const IndexMask mask, void *dst) const override + { + varray_->materialize(mask, MutableSpan((T *)dst, mask.min_array_size())); + } + + void materialize_to_uninitialized_impl(const IndexMask mask, void *dst) const override + { + varray_->materialize_to_uninitialized(mask, MutableSpan((T *)dst, mask.min_array_size())); + } + const void *try_get_internal_varray_impl() const override { return (const VArray<T> *)varray_; diff --git a/source/blender/functions/FN_multi_function_params.hh b/source/blender/functions/FN_multi_function_params.hh index 3b15f0278f3..e292d11def7 100644 --- a/source/blender/functions/FN_multi_function_params.hh +++ b/source/blender/functions/FN_multi_function_params.hh @@ -27,6 +27,7 @@ #include "BLI_resource_scope.hh" +#include "FN_generic_pointer.hh" #include "FN_generic_vector_array.hh" #include "FN_generic_virtual_vector_array.hh" #include "FN_multi_function_signature.hh" @@ -64,6 +65,12 @@ class MFParamsBuilder { this->add_readonly_single_input(scope_.construct<GVArray_For_GSpan>(__func__, span), expected_name); } + void add_readonly_single_input(GPointer value, StringRef expected_name = "") + { + this->add_readonly_single_input(scope_.construct<GVArray_For_SingleValueRef>( + __func__, *value.type(), min_array_size_, value.get()), + expected_name); + } void add_readonly_single_input(const GVArray &ref, StringRef expected_name = "") { this->assert_current_param_type(MFParamType::ForSingleInput(ref.type()), expected_name); diff --git a/source/blender/functions/intern/generic_virtual_array.cc b/source/blender/functions/intern/generic_virtual_array.cc index ca2bd0f806f..87dae06ccdc 100644 --- a/source/blender/functions/intern/generic_virtual_array.cc +++ b/source/blender/functions/intern/generic_virtual_array.cc @@ -53,6 +53,24 @@ class GVArray_For_ShallowCopy : public GVArray { * GVArray. */ +void GVArray::materialize(void *dst) const +{ + this->materialize(IndexMask(size_), dst); +} + +void GVArray::materialize(const IndexMask mask, void *dst) const +{ + this->materialize_impl(mask, dst); +} + +void GVArray::materialize_impl(const IndexMask mask, void *dst) const +{ + for (const int64_t i : mask) { + void *elem_dst = POINTER_OFFSET(dst, type_->size() * i); + this->get(i, elem_dst); + } +} + void GVArray::materialize_to_uninitialized(void *dst) const { this->materialize_to_uninitialized(IndexMask(size_), dst); @@ -142,6 +160,19 @@ void GVMutableArray::set_by_relocate_impl(const int64_t index, void *value) type_->destruct(value); } +void GVMutableArray::set_all_impl(const void *src) +{ + if (this->is_span()) { + const GMutableSpan span = this->get_internal_span(); + type_->copy_to_initialized_n(src, span.data(), size_); + } + else { + for (int64_t i : IndexRange(size_)) { + this->set_by_copy(i, POINTER_OFFSET(src, type_->size() * i)); + } + } +} + void *GVMutableArray::try_get_internal_mutable_varray_impl() { return nullptr; diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencillineart.c b/source/blender/gpencil_modifiers/intern/MOD_gpencillineart.c index a2f35c8e575..cc79810d2a2 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencillineart.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencillineart.c @@ -194,6 +194,24 @@ static bool isDisabled(GpencilModifierData *md, int UNUSED(userRenderParams)) return isModifierDisabled(md); } +static void add_this_collection(Collection *c, + const ModifierUpdateDepsgraphContext *ctx, + const int mode) +{ + FOREACH_COLLECTION_VISIBLE_OBJECT_RECURSIVE_BEGIN (c, ob, mode) { + if (ELEM(ob->type, OB_MESH, OB_MBALL, OB_CURVE, OB_SURF, OB_FONT)) { + if (ob->lineart.usage != OBJECT_LRT_EXCLUDE) { + DEG_add_object_relation(ctx->node, ob, DEG_OB_COMP_GEOMETRY, "Line Art Modifier"); + DEG_add_object_relation(ctx->node, ob, DEG_OB_COMP_TRANSFORM, "Line Art Modifier"); + } + } + if (ob->type == OB_EMPTY && (ob->transflag & OB_DUPLICOLLECTION)) { + add_this_collection(ob->instance_collection, ctx, mode); + } + } + FOREACH_COLLECTION_VISIBLE_OBJECT_RECURSIVE_END; +} + static void updateDepsgraph(GpencilModifierData *md, const ModifierUpdateDepsgraphContext *ctx, const int mode) @@ -208,15 +226,7 @@ static void updateDepsgraph(GpencilModifierData *md, ctx->node, lmd->source_object, DEG_OB_COMP_TRANSFORM, "Line Art Modifier"); } else { - FOREACH_COLLECTION_VISIBLE_OBJECT_RECURSIVE_BEGIN (ctx->scene->master_collection, ob, mode) { - if (ELEM(ob->type, OB_MESH, OB_MBALL, OB_CURVE, OB_SURF, OB_FONT)) { - if (ob->lineart.usage != OBJECT_LRT_EXCLUDE) { - DEG_add_object_relation(ctx->node, ob, DEG_OB_COMP_GEOMETRY, "Line Art Modifier"); - DEG_add_object_relation(ctx->node, ob, DEG_OB_COMP_TRANSFORM, "Line Art Modifier"); - } - } - } - FOREACH_COLLECTION_VISIBLE_OBJECT_RECURSIVE_END; + add_this_collection(ctx->scene->master_collection, ctx, mode); } DEG_add_object_relation( ctx->node, ctx->scene->camera, DEG_OB_COMP_TRANSFORM, "Line Art Modifier"); diff --git a/source/blender/gpencil_modifiers/intern/lineart/MOD_lineart.h b/source/blender/gpencil_modifiers/intern/lineart/MOD_lineart.h index 56cd7fa1456..44ff0616fe9 100644 --- a/source/blender/gpencil_modifiers/intern/lineart/MOD_lineart.h +++ b/source/blender/gpencil_modifiers/intern/lineart/MOD_lineart.h @@ -322,9 +322,11 @@ typedef enum eLineartTriangleFlags { LRT_TRIANGLE_NO_INTERSECTION = (1 << 4), } eLineartTriangleFlags; -/** Controls how many edges a worker thread is processing at one request. +/** + * Controls how many edges a worker thread is processing at one request. * There's no significant performance impact on choosing different values. - * Don't make it too small so that the worker thread won't request too many times. */ + * Don't make it too small so that the worker thread won't request too many times. + */ #define LRT_THREAD_EDGE_COUNT 1000 typedef struct LineartRenderTaskInfo { diff --git a/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c b/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c index 9c4e428f58a..e9cb8453f43 100644 --- a/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c +++ b/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c @@ -1510,7 +1510,7 @@ static void lineart_geometry_object_load(Depsgraph *dg, use_mesh = DEG_get_evaluated_object(dg, ob)->data; } else { - use_mesh = BKE_mesh_new_from_object(NULL, ob, false); + use_mesh = BKE_mesh_new_from_object(NULL, ob, false, false); } /* In case we can not get any mesh geometry data from the object */ diff --git a/source/blender/gpu/GPU_capabilities.h b/source/blender/gpu/GPU_capabilities.h index b95053a3715..f54ecece659 100644 --- a/source/blender/gpu/GPU_capabilities.h +++ b/source/blender/gpu/GPU_capabilities.h @@ -37,6 +37,15 @@ int GPU_max_textures(void); int GPU_max_textures_vert(void); int GPU_max_textures_geom(void); int GPU_max_textures_frag(void); +int GPU_max_uniforms_vert(void); +int GPU_max_uniforms_frag(void); +int GPU_max_batch_indices(void); +int GPU_max_batch_vertices(void); +int GPU_max_vertex_attribs(void); +int GPU_max_varying_floats(void); + +int GPU_extensions_len(void); +const char *GPU_extension_get(int i); int GPU_texture_size_with_limit(int res, bool limit_gl_texture_size); diff --git a/source/blender/gpu/GPU_platform.h b/source/blender/gpu/GPU_platform.h index c457b829bf7..fa7d5d7fba8 100644 --- a/source/blender/gpu/GPU_platform.h +++ b/source/blender/gpu/GPU_platform.h @@ -68,6 +68,9 @@ extern "C" { bool GPU_type_matches(eGPUDeviceType device, eGPUOSType os, eGPUDriverType driver); eGPUSupportLevel GPU_platform_support_level(void); +const char *GPU_platform_vendor(void); +const char *GPU_platform_renderer(void); +const char *GPU_platform_version(void); const char *GPU_platform_support_level_key(void); const char *GPU_platform_gpu_name(void); diff --git a/source/blender/gpu/intern/gpu_capabilities.cc b/source/blender/gpu/intern/gpu_capabilities.cc index 6d9182dcf17..d8764502800 100644 --- a/source/blender/gpu/intern/gpu_capabilities.cc +++ b/source/blender/gpu/intern/gpu_capabilities.cc @@ -82,6 +82,46 @@ int GPU_max_textures(void) return GCaps.max_textures; } +int GPU_max_uniforms_vert(void) +{ + return GCaps.max_uniforms_vert; +} + +int GPU_max_uniforms_frag(void) +{ + return GCaps.max_uniforms_frag; +} + +int GPU_max_batch_indices(void) +{ + return GCaps.max_batch_indices; +} + +int GPU_max_batch_vertices(void) +{ + return GCaps.max_batch_vertices; +} + +int GPU_max_vertex_attribs(void) +{ + return GCaps.max_vertex_attribs; +} + +int GPU_max_varying_floats(void) +{ + return GCaps.max_varying_floats; +} + +int GPU_extensions_len(void) +{ + return GCaps.extensions_len; +} + +const char *GPU_extension_get(int i) +{ + return GCaps.extension_get ? GCaps.extension_get(i) : "\0"; +} + bool GPU_mip_render_workaround(void) { return GCaps.mip_render_workaround; diff --git a/source/blender/gpu/intern/gpu_capabilities_private.hh b/source/blender/gpu/intern/gpu_capabilities_private.hh index 2b3292749f8..7c1d4590ce8 100644 --- a/source/blender/gpu/intern/gpu_capabilities_private.hh +++ b/source/blender/gpu/intern/gpu_capabilities_private.hh @@ -41,6 +41,15 @@ struct GPUCapabilities { int max_textures_vert = 0; int max_textures_geom = 0; int max_textures_frag = 0; + int max_uniforms_vert = 0; + int max_uniforms_frag = 0; + int max_batch_indices = 0; + int max_batch_vertices = 0; + int max_vertex_attribs = 0; + int max_varying_floats = 0; + int extensions_len = 0; + const char *(*extension_get)(int); + bool mem_stats_support = false; bool shader_image_load_store_support = false; /* OpenGL related workarounds. */ diff --git a/source/blender/gpu/intern/gpu_platform.cc b/source/blender/gpu/intern/gpu_platform.cc index 6b9878f2ba4..49dde473300 100644 --- a/source/blender/gpu/intern/gpu_platform.cc +++ b/source/blender/gpu/intern/gpu_platform.cc @@ -41,10 +41,10 @@ namespace blender::gpu { GPUPlatformGlobal GPG; -void GPUPlatformGlobal::create_key(eGPUSupportLevel support_level, - const char *vendor, - const char *renderer, - const char *version) +static char *create_key(eGPUSupportLevel support_level, + const char *vendor, + const char *renderer, + const char *version) { DynStr *ds = BLI_dynstr_new(); BLI_dynstr_appendf(ds, "{%s/%s/%s}=", vendor, renderer, version); @@ -58,29 +58,56 @@ void GPUPlatformGlobal::create_key(eGPUSupportLevel support_level, BLI_dynstr_append(ds, "UNSUPPORTED"); } - support_key = BLI_dynstr_get_cstring(ds); + char *support_key = BLI_dynstr_get_cstring(ds); BLI_dynstr_free(ds); BLI_str_replace_char(support_key, '\n', ' '); BLI_str_replace_char(support_key, '\r', ' '); + return support_key; } -void GPUPlatformGlobal::create_gpu_name(const char *vendor, - const char *renderer, - const char *version) +static char *create_gpu_name(const char *vendor, const char *renderer, const char *version) { DynStr *ds = BLI_dynstr_new(); BLI_dynstr_appendf(ds, "%s %s %s", vendor, renderer, version); - gpu_name = BLI_dynstr_get_cstring(ds); + char *gpu_name = BLI_dynstr_get_cstring(ds); BLI_dynstr_free(ds); BLI_str_replace_char(gpu_name, '\n', ' '); BLI_str_replace_char(gpu_name, '\r', ' '); + return gpu_name; +} + +void GPUPlatformGlobal::init(eGPUDeviceType gpu_device, + eGPUOSType os_type, + eGPUDriverType driver_type, + eGPUSupportLevel gpu_support_level, + const char *vendor_str, + const char *renderer_str, + const char *version_str) +{ + this->clear(); + + this->initialized = true; + + this->device = gpu_device; + this->os = os_type; + this->driver = driver_type; + this->support_level = gpu_support_level; + + this->vendor = BLI_strdup(vendor_str); + this->renderer = BLI_strdup(renderer_str); + this->version = BLI_strdup(version_str); + this->support_key = create_key(gpu_support_level, vendor_str, renderer_str, version_str); + this->gpu_name = create_gpu_name(vendor_str, renderer_str, version_str); } void GPUPlatformGlobal::clear() { - MEM_SAFE_FREE(GPG.support_key); - MEM_SAFE_FREE(GPG.gpu_name); + MEM_SAFE_FREE(vendor); + MEM_SAFE_FREE(renderer); + MEM_SAFE_FREE(version); + MEM_SAFE_FREE(support_key); + MEM_SAFE_FREE(gpu_name); initialized = false; } @@ -96,22 +123,44 @@ using namespace blender::gpu; eGPUSupportLevel GPU_platform_support_level() { + BLI_assert(GPG.initialized); return GPG.support_level; } -const char *GPU_platform_support_level_key() +const char *GPU_platform_vendor(void) +{ + BLI_assert(GPG.initialized); + return GPG.vendor; +} + +const char *GPU_platform_renderer(void) +{ + BLI_assert(GPG.initialized); + return GPG.renderer; +} + +const char *GPU_platform_version(void) +{ + BLI_assert(GPG.initialized); + return GPG.version; +} + +const char *GPU_platform_support_level_key(void) { + BLI_assert(GPG.initialized); return GPG.support_key; } const char *GPU_platform_gpu_name(void) { + BLI_assert(GPG.initialized); return GPG.gpu_name; } /* GPU Types */ bool GPU_type_matches(eGPUDeviceType device, eGPUOSType os, eGPUDriverType driver) { + BLI_assert(GPG.initialized); return (GPG.device & device) && (GPG.os & os) && (GPG.driver & driver); } diff --git a/source/blender/gpu/intern/gpu_platform_private.hh b/source/blender/gpu/intern/gpu_platform_private.hh index 02d99efa4a9..f823269ab54 100644 --- a/source/blender/gpu/intern/gpu_platform_private.hh +++ b/source/blender/gpu/intern/gpu_platform_private.hh @@ -34,16 +34,20 @@ class GPUPlatformGlobal { eGPUOSType os; eGPUDriverType driver; eGPUSupportLevel support_level; + char *vendor = nullptr; + char *renderer = nullptr; + char *version = nullptr; char *support_key = nullptr; char *gpu_name = nullptr; public: - void create_key(eGPUSupportLevel support_level, - const char *vendor, - const char *renderer, - const char *version); - - void create_gpu_name(const char *vendor, const char *renderer, const char *version); + void init(eGPUDeviceType gpu_device, + eGPUOSType os_type, + eGPUDriverType driver_type, + eGPUSupportLevel gpu_support_level, + const char *vendor_str, + const char *renderer_str, + const char *version_str); void clear(void); }; diff --git a/source/blender/gpu/opengl/gl_backend.cc b/source/blender/gpu/opengl/gl_backend.cc index ef7788194a1..e7d67be6d0e 100644 --- a/source/blender/gpu/opengl/gl_backend.cc +++ b/source/blender/gpu/opengl/gl_backend.cc @@ -41,39 +41,42 @@ namespace blender::gpu { void GLBackend::platform_init() { BLI_assert(!GPG.initialized); - GPG.initialized = true; + + const char *vendor = (const char *)glGetString(GL_VENDOR); + const char *renderer = (const char *)glGetString(GL_RENDERER); + const char *version = (const char *)glGetString(GL_VERSION); + eGPUDeviceType device = GPU_DEVICE_ANY; + eGPUOSType os = GPU_OS_ANY; + eGPUDriverType driver = GPU_DRIVER_ANY; + eGPUSupportLevel support_level = GPU_SUPPORT_LEVEL_SUPPORTED; #ifdef _WIN32 - GPG.os = GPU_OS_WIN; + os = GPU_OS_WIN; #elif defined(__APPLE__) - GPG.os = GPU_OS_MAC; + os = GPU_OS_MAC; #else - GPG.os = GPU_OS_UNIX; + os = GPU_OS_UNIX; #endif - const char *vendor = (const char *)glGetString(GL_VENDOR); - const char *renderer = (const char *)glGetString(GL_RENDERER); - const char *version = (const char *)glGetString(GL_VERSION); - if (strstr(vendor, "ATI") || strstr(vendor, "AMD")) { - GPG.device = GPU_DEVICE_ATI; - GPG.driver = GPU_DRIVER_OFFICIAL; + device = GPU_DEVICE_ATI; + driver = GPU_DRIVER_OFFICIAL; } else if (strstr(vendor, "NVIDIA")) { - GPG.device = GPU_DEVICE_NVIDIA; - GPG.driver = GPU_DRIVER_OFFICIAL; + device = GPU_DEVICE_NVIDIA; + driver = GPU_DRIVER_OFFICIAL; } else if (strstr(vendor, "Intel") || /* src/mesa/drivers/dri/intel/intel_context.c */ strstr(renderer, "Mesa DRI Intel") || strstr(renderer, "Mesa DRI Mobile Intel")) { - GPG.device = GPU_DEVICE_INTEL; - GPG.driver = GPU_DRIVER_OFFICIAL; + device = GPU_DEVICE_INTEL; + driver = GPU_DRIVER_OFFICIAL; if (strstr(renderer, "UHD Graphics") || /* Not UHD but affected by the same bugs. */ strstr(renderer, "HD Graphics 530") || strstr(renderer, "Kaby Lake GT2") || strstr(renderer, "Whiskey Lake")) { - GPG.device |= GPU_DEVICE_INTEL_UHD; + device |= GPU_DEVICE_INTEL_UHD; } } else if ((strstr(renderer, "Mesa DRI R")) || @@ -81,49 +84,47 @@ void GLBackend::platform_init() (strstr(renderer, "AMD") && strstr(vendor, "X.Org")) || (strstr(renderer, "Gallium ") && strstr(renderer, " on ATI ")) || (strstr(renderer, "Gallium ") && strstr(renderer, " on AMD "))) { - GPG.device = GPU_DEVICE_ATI; - GPG.driver = GPU_DRIVER_OPENSOURCE; + device = GPU_DEVICE_ATI; + driver = GPU_DRIVER_OPENSOURCE; } else if (strstr(renderer, "Nouveau") || strstr(vendor, "nouveau")) { - GPG.device = GPU_DEVICE_NVIDIA; - GPG.driver = GPU_DRIVER_OPENSOURCE; + device = GPU_DEVICE_NVIDIA; + driver = GPU_DRIVER_OPENSOURCE; } else if (strstr(vendor, "Mesa")) { - GPG.device = GPU_DEVICE_SOFTWARE; - GPG.driver = GPU_DRIVER_SOFTWARE; + device = GPU_DEVICE_SOFTWARE; + driver = GPU_DRIVER_SOFTWARE; } else if (strstr(vendor, "Microsoft")) { - GPG.device = GPU_DEVICE_SOFTWARE; - GPG.driver = GPU_DRIVER_SOFTWARE; + device = GPU_DEVICE_SOFTWARE; + driver = GPU_DRIVER_SOFTWARE; } else if (strstr(vendor, "Apple")) { /* Apple Silicon. */ - GPG.device = GPU_DEVICE_APPLE; - GPG.driver = GPU_DRIVER_OFFICIAL; + device = GPU_DEVICE_APPLE; + driver = GPU_DRIVER_OFFICIAL; } else if (strstr(renderer, "Apple Software Renderer")) { - GPG.device = GPU_DEVICE_SOFTWARE; - GPG.driver = GPU_DRIVER_SOFTWARE; + device = GPU_DEVICE_SOFTWARE; + driver = GPU_DRIVER_SOFTWARE; } else if (strstr(renderer, "llvmpipe") || strstr(renderer, "softpipe")) { - GPG.device = GPU_DEVICE_SOFTWARE; - GPG.driver = GPU_DRIVER_SOFTWARE; + device = GPU_DEVICE_SOFTWARE; + driver = GPU_DRIVER_SOFTWARE; } else { printf("Warning: Could not find a matching GPU name. Things may not behave as expected.\n"); printf("Detected OpenGL configuration:\n"); printf("Vendor: %s\n", vendor); printf("Renderer: %s\n", renderer); - GPG.device = GPU_DEVICE_ANY; - GPG.driver = GPU_DRIVER_ANY; } /* Detect support level */ if (!GLEW_VERSION_3_3) { - GPG.support_level = GPU_SUPPORT_LEVEL_UNSUPPORTED; + support_level = GPU_SUPPORT_LEVEL_UNSUPPORTED; } else { - if (GPU_type_matches(GPU_DEVICE_INTEL, GPU_OS_WIN, GPU_DRIVER_ANY)) { + if ((device & GPU_DEVICE_INTEL) && (os & GPU_OS_WIN)) { /* Old Intel drivers with known bugs that cause material properties to crash. * Version Build 10.18.14.5067 is the latest available and appears to be working * ok with our workarounds, so excluded from this list. */ @@ -132,19 +133,19 @@ void GLBackend::platform_init() strstr(version, "Build 9.18") || strstr(version, "Build 10.18.10.3") || strstr(version, "Build 10.18.10.4") || strstr(version, "Build 10.18.10.5") || strstr(version, "Build 10.18.14.4")) { - GPG.support_level = GPU_SUPPORT_LEVEL_LIMITED; + support_level = GPU_SUPPORT_LEVEL_LIMITED; } } - if (GPU_type_matches(GPU_DEVICE_ATI, GPU_OS_UNIX, GPU_DRIVER_ANY)) { + if ((device & GPU_DEVICE_ATI) && (os & GPU_OS_UNIX)) { /* Platform seems to work when SB backend is disabled. This can be done * by adding the environment variable `R600_DEBUG=nosb`. */ if (strstr(renderer, "AMD CEDAR")) { - GPG.support_level = GPU_SUPPORT_LEVEL_LIMITED; + support_level = GPU_SUPPORT_LEVEL_LIMITED; } } } - GPG.create_key(GPG.support_level, vendor, renderer, version); - GPG.create_gpu_name(vendor, renderer, version); + + GPG.init(device, os, driver, support_level, vendor, renderer, version); } void GLBackend::platform_exit() @@ -204,6 +205,11 @@ static bool detect_mip_render_workaround() return enable_workaround; } +static const char *gl_extension_get(int i) +{ + return (char *)glGetStringi(GL_EXTENSIONS, i); +} + static void detect_workarounds() { const char *vendor = (const char *)glGetString(GL_VENDOR); @@ -286,9 +292,10 @@ static void detect_workarounds() strstr(renderer, " RX 480 ") || strstr(renderer, " RX 490 ") || strstr(renderer, " RX 560 ") || strstr(renderer, " RX 560X ") || strstr(renderer, " RX 570 ") || strstr(renderer, " RX 580 ") || - strstr(renderer, " RX 590 ") || strstr(renderer, " RX550/550 ") || - strstr(renderer, " (TM) 520 ") || strstr(renderer, " (TM) 530 ") || - strstr(renderer, " R5 ") || strstr(renderer, " R7 ") || strstr(renderer, " R9 ")) { + strstr(renderer, " RX 580X ") || strstr(renderer, " RX 590 ") || + strstr(renderer, " RX550/550 ") || strstr(renderer, " (TM) 520 ") || + strstr(renderer, " (TM) 530 ") || strstr(renderer, " R5 ") || strstr(renderer, " R7 ") || + strstr(renderer, " R9 ")) { GCaps.use_hq_normals_workaround = true; } } @@ -418,6 +425,16 @@ void GLBackend::capabilities_init() glGetIntegerv(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, &GCaps.max_textures_vert); glGetIntegerv(GL_MAX_GEOMETRY_TEXTURE_IMAGE_UNITS, &GCaps.max_textures_geom); glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &GCaps.max_textures); + glGetIntegerv(GL_MAX_VERTEX_UNIFORM_COMPONENTS, &GCaps.max_uniforms_vert); + glGetIntegerv(GL_MAX_FRAGMENT_UNIFORM_COMPONENTS, &GCaps.max_uniforms_frag); + glGetIntegerv(GL_MAX_ELEMENTS_INDICES, &GCaps.max_batch_indices); + glGetIntegerv(GL_MAX_ELEMENTS_VERTICES, &GCaps.max_batch_vertices); + glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &GCaps.max_vertex_attribs); + glGetIntegerv(GL_MAX_VARYING_FLOATS, &GCaps.max_varying_floats); + + glGetIntegerv(GL_NUM_EXTENSIONS, &GCaps.extensions_len); + GCaps.extension_get = gl_extension_get; + GCaps.mem_stats_support = GLEW_NVX_gpu_memory_info || GLEW_ATI_meminfo; GCaps.shader_image_load_store_support = GLEW_ARB_shader_image_load_store; /* GL specific capabilities. */ diff --git a/source/blender/gpu/shaders/material/gpu_shader_material_geometry.glsl b/source/blender/gpu/shaders/material/gpu_shader_material_geometry.glsl index 4db27c3049d..a14ff5021bf 100644 --- a/source/blender/gpu/shaders/material/gpu_shader_material_geometry.glsl +++ b/source/blender/gpu/shaders/material/gpu_shader_material_geometry.glsl @@ -37,8 +37,13 @@ void node_geometry(vec3 I, normal = (toworld * vec4(N, 0.0)).xyz; true_normal = normal; # endif + +# ifdef HAIR_SHADER + tangent = -hairTangent; +# else tangent_orco_z(orco, orco); node_tangent(N, orco, objmat, tangent); +# endif parametric = vec3(barycentric, 0.0); backfacing = (gl_FrontFacing) ? 0.0 : 1.0; diff --git a/source/blender/gpu/shaders/material/gpu_shader_material_subsurface_scattering.glsl b/source/blender/gpu/shaders/material/gpu_shader_material_subsurface_scattering.glsl index 5a68f802659..5129bf71903 100644 --- a/source/blender/gpu/shaders/material/gpu_shader_material_subsurface_scattering.glsl +++ b/source/blender/gpu/shaders/material/gpu_shader_material_subsurface_scattering.glsl @@ -23,8 +23,8 @@ void node_subsurface_scattering(vec4 color, /* Not perfect for texture_blur values between 0.0 and 1.0. * Interpolate between separated color and color applied on irradiance. */ float one_minus_texture_blur = 1.0 - texture_blur; - vec3 sss_albedo = color.rgb * texture_blur + one_minus_texture_blur; - vec3 radiance_tint = color.rgb * one_minus_texture_blur + texture_blur; + vec3 sss_albedo = color.rgb * one_minus_texture_blur + texture_blur; + vec3 radiance_tint = color.rgb * texture_blur + one_minus_texture_blur; /* Consider output radiance as irradiance. */ out_Diffuse_0.radiance *= radiance_tint; diff --git a/source/blender/gpu/tests/gpu_testing.cc b/source/blender/gpu/tests/gpu_testing.cc index b9fc78dc084..ac42c5875c8 100644 --- a/source/blender/gpu/tests/gpu_testing.cc +++ b/source/blender/gpu/tests/gpu_testing.cc @@ -2,6 +2,8 @@ #include "testing/testing.h" +#include "CLG_log.h" + #include "GPU_context.h" #include "GPU_init_exit.h" #include "gpu_testing.hh" @@ -13,6 +15,7 @@ namespace blender::gpu { void GPUTest::SetUp() { GHOST_GLSettings glSettings = {0}; + CLG_init(); ghost_system = GHOST_CreateSystem(); ghost_context = GHOST_CreateOpenGLContext(ghost_system, glSettings); context = GPU_context_create(nullptr); @@ -26,6 +29,7 @@ void GPUTest::TearDown() GPU_context_discard(context); GHOST_DisposeOpenGLContext(ghost_system, ghost_context); GHOST_DisposeSystem(ghost_system); + CLG_exit(); } } // namespace blender::gpu diff --git a/source/blender/imbuf/CMakeLists.txt b/source/blender/imbuf/CMakeLists.txt index 7ce795280a3..be0e364c85f 100644 --- a/source/blender/imbuf/CMakeLists.txt +++ b/source/blender/imbuf/CMakeLists.txt @@ -166,12 +166,6 @@ if(WITH_CODEC_FFMPEG) ${OPENJPEG_LIBRARIES} ) add_definitions(-DWITH_FFMPEG) - - remove_strict_c_flags_file( - intern/anim_movie.c - intern/indexer.c - intern/util.c - ) endif() if(WITH_IMAGE_DDS) diff --git a/source/blender/imbuf/intern/IMB_anim.h b/source/blender/imbuf/intern/IMB_anim.h index 1239d3881de..7d7864306a1 100644 --- a/source/blender/imbuf/intern/IMB_anim.h +++ b/source/blender/imbuf/intern/IMB_anim.h @@ -135,7 +135,7 @@ struct anim { struct ImBuf *last_frame; int64_t last_pts; int64_t next_pts; - AVPacket next_packet; + AVPacket *next_packet; #endif char index_dir[768]; diff --git a/source/blender/imbuf/intern/anim_movie.c b/source/blender/imbuf/intern/anim_movie.c index 432b62d172a..cdf6ca5c181 100644 --- a/source/blender/imbuf/intern/anim_movie.c +++ b/source/blender/imbuf/intern/anim_movie.c @@ -79,6 +79,7 @@ # include <libavcodec/avcodec.h> # include <libavformat/avformat.h> +# include <libavutil/imgutils.h> # include <libavutil/rational.h> # include <libswscale/swscale.h> @@ -519,12 +520,10 @@ static int startffmpeg(struct anim *anim) double frs_den; int streamcount; -# ifdef FFMPEG_SWSCALE_COLOR_SPACE_SUPPORT /* The following for color space determination */ int srcRange, dstRange, brightness, contrast, saturation; int *table; const int *inv_table; -# endif if (anim == NULL) { return (-1); @@ -547,7 +546,7 @@ static int startffmpeg(struct anim *anim) video_stream_index = -1; for (i = 0; i < pFormatCtx->nb_streams; i++) { - if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) { + if (pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { if (streamcount > 0) { streamcount--; continue; @@ -563,16 +562,17 @@ static int startffmpeg(struct anim *anim) } video_stream = pFormatCtx->streams[video_stream_index]; - pCodecCtx = video_stream->codec; /* Find the decoder for the video stream */ - pCodec = avcodec_find_decoder(pCodecCtx->codec_id); + pCodec = avcodec_find_decoder(video_stream->codecpar->codec_id); if (pCodec == NULL) { avformat_close_input(&pFormatCtx); return -1; } - pCodecCtx->workaround_bugs = 1; + pCodecCtx = avcodec_alloc_context3(NULL); + avcodec_parameters_to_context(pCodecCtx, video_stream->codecpar); + pCodecCtx->workaround_bugs = FF_BUG_AUTODETECT; if (pCodec->capabilities & AV_CODEC_CAP_AUTO_THREADS) { pCodecCtx->thread_count = 0; @@ -593,7 +593,7 @@ static int startffmpeg(struct anim *anim) return -1; } if (pCodecCtx->pix_fmt == AV_PIX_FMT_NONE) { - avcodec_close(anim->pCodecCtx); + avcodec_free_context(&anim->pCodecCtx); avformat_close_input(&pFormatCtx); return -1; } @@ -639,7 +639,7 @@ static int startffmpeg(struct anim *anim) anim->params = 0; anim->x = pCodecCtx->width; - anim->y = av_get_cropped_height_from_codec(pCodecCtx); + anim->y = pCodecCtx->height; anim->pFormatCtx = pFormatCtx; anim->pCodecCtx = pCodecCtx; @@ -654,7 +654,8 @@ static int startffmpeg(struct anim *anim) anim->last_frame = 0; anim->last_pts = -1; anim->next_pts = -1; - anim->next_packet.stream_index = -1; + anim->next_packet = av_packet_alloc(); + anim->next_packet->stream_index = -1; anim->pFrame = av_frame_alloc(); anim->pFrameComplete = false; @@ -668,8 +669,9 @@ static int startffmpeg(struct anim *anim) if (av_frame_get_buffer(anim->pFrameRGB, 32) < 0) { fprintf(stderr, "Could not allocate frame data.\n"); - avcodec_close(anim->pCodecCtx); + avcodec_free_context(&anim->pCodecCtx); avformat_close_input(&anim->pFormatCtx); + av_packet_free(&anim->next_packet); av_frame_free(&anim->pFrameRGB); av_frame_free(&anim->pFrameDeinterlaced); av_frame_free(&anim->pFrame); @@ -678,10 +680,11 @@ static int startffmpeg(struct anim *anim) } } - if (avpicture_get_size(AV_PIX_FMT_RGBA, anim->x, anim->y) != anim->x * anim->y * 4) { + if (av_image_get_buffer_size(AV_PIX_FMT_RGBA, anim->x, anim->y, 1) != anim->x * anim->y * 4) { fprintf(stderr, "ffmpeg has changed alloc scheme ... ARGHHH!\n"); - avcodec_close(anim->pCodecCtx); + avcodec_free_context(&anim->pCodecCtx); avformat_close_input(&anim->pFormatCtx); + av_packet_free(&anim->next_packet); av_frame_free(&anim->pFrameRGB); av_frame_free(&anim->pFrameDeinterlaced); av_frame_free(&anim->pFrame); @@ -690,14 +693,17 @@ static int startffmpeg(struct anim *anim) } if (anim->ib_flags & IB_animdeinterlace) { - avpicture_fill((AVPicture *)anim->pFrameDeinterlaced, - MEM_callocN(avpicture_get_size(anim->pCodecCtx->pix_fmt, - anim->pCodecCtx->width, - anim->pCodecCtx->height), - "ffmpeg deinterlace"), - anim->pCodecCtx->pix_fmt, - anim->pCodecCtx->width, - anim->pCodecCtx->height); + av_image_fill_arrays(anim->pFrameDeinterlaced->data, + anim->pFrameDeinterlaced->linesize, + MEM_callocN(av_image_get_buffer_size(anim->pCodecCtx->pix_fmt, + anim->pCodecCtx->width, + anim->pCodecCtx->height, + 1), + "ffmpeg deinterlace"), + anim->pCodecCtx->pix_fmt, + anim->pCodecCtx->width, + anim->pCodecCtx->height, + 1); } if (pCodecCtx->has_b_frames) { @@ -720,8 +726,9 @@ static int startffmpeg(struct anim *anim) if (!anim->img_convert_ctx) { fprintf(stderr, "Can't transform color space??? Bailing out...\n"); - avcodec_close(anim->pCodecCtx); + avcodec_free_context(&anim->pCodecCtx); avformat_close_input(&anim->pFormatCtx); + av_packet_free(&anim->next_packet); av_frame_free(&anim->pFrameRGB); av_frame_free(&anim->pFrameDeinterlaced); av_frame_free(&anim->pFrame); @@ -729,7 +736,6 @@ static int startffmpeg(struct anim *anim) return -1; } -# ifdef FFMPEG_SWSCALE_COLOR_SPACE_SUPPORT /* Try do detect if input has 0-255 YCbCR range (JFIF Jpeg MotionJpeg) */ if (!sws_getColorspaceDetails(anim->img_convert_ctx, (int **)&inv_table, @@ -756,7 +762,6 @@ static int startffmpeg(struct anim *anim) else { fprintf(stderr, "Warning: Could not set libswscale colorspace details.\n"); } -# endif return 0; } @@ -795,11 +800,11 @@ static void ffmpeg_postprocess(struct anim *anim) input->data[3]); if (anim->ib_flags & IB_animdeinterlace) { - if (avpicture_deinterlace((AVPicture *)anim->pFrameDeinterlaced, - (const AVPicture *)anim->pFrame, - anim->pCodecCtx->pix_fmt, - anim->pCodecCtx->width, - anim->pCodecCtx->height) < 0) { + if (av_image_deinterlace(anim->pFrameDeinterlaced, + anim->pFrame, + anim->pCodecCtx->pix_fmt, + anim->pCodecCtx->width, + anim->pCodecCtx->height) < 0) { filter_y = true; } else { @@ -808,11 +813,13 @@ static void ffmpeg_postprocess(struct anim *anim) } if (!need_aligned_ffmpeg_buffer(anim)) { - avpicture_fill((AVPicture *)anim->pFrameRGB, - (unsigned char *)ibuf->rect, - AV_PIX_FMT_RGBA, - anim->x, - anim->y); + av_image_fill_arrays(anim->pFrameRGB->data, + anim->pFrameRGB->linesize, + (unsigned char *)ibuf->rect, + AV_PIX_FMT_RGBA, + anim->x, + anim->y, + 1); } # if defined(__x86_64__) || defined(_M_X64) @@ -903,82 +910,70 @@ static int ffmpeg_decode_video_frame(struct anim *anim) av_log(anim->pFormatCtx, AV_LOG_DEBUG, " DECODE VIDEO FRAME\n"); - if (anim->next_packet.stream_index == anim->videoStream) { - av_free_packet(&anim->next_packet); - anim->next_packet.stream_index = -1; + if (anim->next_packet->stream_index == anim->videoStream) { + av_packet_unref(anim->next_packet); + anim->next_packet->stream_index = -1; } - while ((rval = av_read_frame(anim->pFormatCtx, &anim->next_packet)) >= 0) { + while ((rval = av_read_frame(anim->pFormatCtx, anim->next_packet)) >= 0) { av_log(anim->pFormatCtx, AV_LOG_DEBUG, "%sREAD: strID=%d (VID: %d) dts=%" PRId64 " pts=%" PRId64 " %s\n", - (anim->next_packet.stream_index == anim->videoStream) ? "->" : " ", - anim->next_packet.stream_index, + (anim->next_packet->stream_index == anim->videoStream) ? "->" : " ", + anim->next_packet->stream_index, anim->videoStream, - (anim->next_packet.dts == AV_NOPTS_VALUE) ? -1 : (int64_t)anim->next_packet.dts, - (anim->next_packet.pts == AV_NOPTS_VALUE) ? -1 : (int64_t)anim->next_packet.pts, - (anim->next_packet.flags & AV_PKT_FLAG_KEY) ? " KEY" : ""); - if (anim->next_packet.stream_index == anim->videoStream) { + (anim->next_packet->dts == AV_NOPTS_VALUE) ? -1 : (int64_t)anim->next_packet->dts, + (anim->next_packet->pts == AV_NOPTS_VALUE) ? -1 : (int64_t)anim->next_packet->pts, + (anim->next_packet->flags & AV_PKT_FLAG_KEY) ? " KEY" : ""); + if (anim->next_packet->stream_index == anim->videoStream) { anim->pFrameComplete = 0; - avcodec_decode_video2( - anim->pCodecCtx, anim->pFrame, &anim->pFrameComplete, &anim->next_packet); + avcodec_send_packet(anim->pCodecCtx, anim->next_packet); + anim->pFrameComplete = avcodec_receive_frame(anim->pCodecCtx, anim->pFrame) == 0; if (anim->pFrameComplete) { anim->next_pts = av_get_pts_from_frame(anim->pFormatCtx, anim->pFrame); av_log(anim->pFormatCtx, AV_LOG_DEBUG, - " FRAME DONE: next_pts=%" PRId64 " pkt_pts=%" PRId64 ", guessed_pts=%" PRId64 "\n", + " FRAME DONE: next_pts=%" PRId64 ", guessed_pts=%" PRId64 "\n", (anim->pFrame->pts == AV_NOPTS_VALUE) ? -1 : (int64_t)anim->pFrame->pts, - (anim->pFrame->pkt_pts == AV_NOPTS_VALUE) ? -1 : (int64_t)anim->pFrame->pkt_pts, (int64_t)anim->next_pts); break; } } - av_free_packet(&anim->next_packet); - anim->next_packet.stream_index = -1; + av_packet_unref(anim->next_packet); + anim->next_packet->stream_index = -1; } if (rval == AVERROR_EOF) { - /* this sets size and data fields to zero, - * which is necessary to decode the remaining data - * in the decoder engine after EOF. It also prevents a memory - * leak, since av_read_frame spills out a full size packet even - * on EOF... (and: it's safe to call on NULL packets) */ - - av_free_packet(&anim->next_packet); - - anim->next_packet.size = 0; - anim->next_packet.data = 0; - + /* Flush any remaining frames out of the decoder. */ anim->pFrameComplete = 0; - avcodec_decode_video2( - anim->pCodecCtx, anim->pFrame, &anim->pFrameComplete, &anim->next_packet); + avcodec_send_packet(anim->pCodecCtx, NULL); + anim->pFrameComplete = avcodec_receive_frame(anim->pCodecCtx, anim->pFrame) == 0; if (anim->pFrameComplete) { anim->next_pts = av_get_pts_from_frame(anim->pFormatCtx, anim->pFrame); av_log(anim->pFormatCtx, AV_LOG_DEBUG, - " FRAME DONE (after EOF): next_pts=%" PRId64 " pkt_pts=%" PRId64 - ", guessed_pts=%" PRId64 "\n", + " FRAME DONE (after EOF): next_pts=%" PRId64 ", guessed_pts=%" PRId64 "\n", (anim->pFrame->pts == AV_NOPTS_VALUE) ? -1 : (int64_t)anim->pFrame->pts, - (anim->pFrame->pkt_pts == AV_NOPTS_VALUE) ? -1 : (int64_t)anim->pFrame->pkt_pts, (int64_t)anim->next_pts); rval = 0; } } if (rval < 0) { - anim->next_packet.stream_index = -1; + av_packet_unref(anim->next_packet); + anim->next_packet->stream_index = -1; av_log(anim->pFormatCtx, AV_LOG_ERROR, " DECODE READ FAILED: av_read_frame() " - "returned error: %d\n", - rval); + "returned error: %s\n", + av_err2str(rval)); } return (rval >= 0); @@ -1154,7 +1149,7 @@ static int ffmpeg_generic_seek_workaround(struct anim *anim, int64_t requested_p /* Read first video stream packet. */ AVPacket read_packet = {0}; while (av_read_frame(anim->pFormatCtx, &read_packet) >= 0) { - if (anim->next_packet.stream_index == anim->videoStream) { + if (anim->next_packet->stream_index == anim->videoStream) { break; } } @@ -1246,9 +1241,9 @@ static void ffmpeg_seek_and_decode(struct anim *anim, int position, struct anim_ anim->next_pts = -1; - if (anim->next_packet.stream_index == anim->videoStream) { - av_free_packet(&anim->next_packet); - anim->next_packet.stream_index = -1; + if (anim->next_packet->stream_index == anim->videoStream) { + av_packet_unref(anim->next_packet); + anim->next_packet->stream_index = -1; } /* memset(anim->pFrame, ...) ?? */ @@ -1351,32 +1346,30 @@ static void free_anim_ffmpeg(struct anim *anim) } if (anim->pCodecCtx) { - avcodec_close(anim->pCodecCtx); + avcodec_free_context(&anim->pCodecCtx); avformat_close_input(&anim->pFormatCtx); + av_packet_free(&anim->next_packet); - /* Special case here: pFrame could share pointers with codec, - * so in order to avoid double-free we don't use av_frame_free() - * to free the frame. - * - * Could it be a bug in FFmpeg? - */ - av_free(anim->pFrame); + av_frame_free(&anim->pFrame); if (!need_aligned_ffmpeg_buffer(anim)) { /* If there's no need for own aligned buffer it means that FFmpeg's * frame shares the same buffer as temporary ImBuf. In this case we * should not free the buffer when freeing the FFmpeg buffer. */ - avpicture_fill((AVPicture *)anim->pFrameRGB, NULL, AV_PIX_FMT_RGBA, anim->x, anim->y); + av_image_fill_arrays(anim->pFrameRGB->data, + anim->pFrameRGB->linesize, + NULL, + AV_PIX_FMT_RGBA, + anim->x, + anim->y, + 1); } av_frame_free(&anim->pFrameRGB); av_frame_free(&anim->pFrameDeinterlaced); sws_freeContext(anim->img_convert_ctx); IMB_freeImBuf(anim->last_frame); - if (anim->next_packet.stream_index != -1) { - av_free_packet(&anim->next_packet); - } } anim->duration_in_frames = 0; } diff --git a/source/blender/imbuf/intern/indexer.c b/source/blender/imbuf/intern/indexer.c index ef9f6d861a3..11ce77e3091 100644 --- a/source/blender/imbuf/intern/indexer.c +++ b/source/blender/imbuf/intern/indexer.c @@ -48,6 +48,7 @@ #ifdef WITH_FFMPEG # include "ffmpeg_compat.h" +# include <libavutil/imgutils.h> #endif static const char magic[] = "BlenMIdx"; @@ -488,14 +489,14 @@ static struct proxy_output_ctx *alloc_proxy_output_ffmpeg( rv->of = avformat_alloc_context(); rv->of->oformat = av_guess_format("avi", NULL, NULL); - BLI_strncpy(rv->of->filename, fname, sizeof(rv->of->filename)); + rv->of->url = av_strdup(fname); - fprintf(stderr, "Starting work on proxy: %s\n", rv->of->filename); + fprintf(stderr, "Starting work on proxy: %s\n", rv->of->url); rv->st = avformat_new_stream(rv->of, NULL); rv->st->id = 0; - rv->c = rv->st->codec; + rv->c = avcodec_alloc_context3(NULL); rv->c->codec_type = AVMEDIA_TYPE_VIDEO; rv->c->codec_id = AV_CODEC_ID_H264; rv->c->width = width; @@ -513,7 +514,9 @@ static struct proxy_output_ctx *alloc_proxy_output_ffmpeg( fprintf(stderr, "No ffmpeg encoder available? " "Proxy not built!\n"); - av_free(rv->of); + avcodec_free_context(&rv->c); + avformat_free_context(rv->of); + MEM_freeN(rv); return NULL; } @@ -524,7 +527,7 @@ static struct proxy_output_ctx *alloc_proxy_output_ffmpeg( rv->c->pix_fmt = AV_PIX_FMT_YUVJ420P; } - rv->c->sample_aspect_ratio = rv->st->sample_aspect_ratio = st->codec->sample_aspect_ratio; + rv->c->sample_aspect_ratio = rv->st->sample_aspect_ratio = st->sample_aspect_ratio; rv->c->time_base.den = 25; rv->c->time_base.num = 1; @@ -557,34 +560,54 @@ static struct proxy_output_ctx *alloc_proxy_output_ffmpeg( } if (rv->of->flags & AVFMT_GLOBALHEADER) { - rv->c->flags |= CODEC_FLAG_GLOBAL_HEADER; + rv->c->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; } - if (avio_open(&rv->of->pb, fname, AVIO_FLAG_WRITE) < 0) { + avcodec_parameters_from_context(rv->st->codecpar, rv->c); + + int ret = avio_open(&rv->of->pb, fname, AVIO_FLAG_WRITE); + + if (ret < 0) { fprintf(stderr, - "Couldn't open outputfile! " - "Proxy not built!\n"); - av_free(rv->of); - return 0; + "Couldn't open IO: %s\n" + "Proxy not built!\n", + av_err2str(ret)); + avcodec_free_context(&rv->c); + avformat_free_context(rv->of); + MEM_freeN(rv); + return NULL; } - avcodec_open2(rv->c, rv->codec, &codec_opts); + ret = avcodec_open2(rv->c, rv->codec, &codec_opts); + if (ret < 0) { + fprintf(stderr, + "Couldn't open codec: %s\n" + "Proxy not built!\n", + av_err2str(ret)); + avcodec_free_context(&rv->c); + avformat_free_context(rv->of); + MEM_freeN(rv); + return NULL; + } - rv->orig_height = av_get_cropped_height_from_codec(st->codec); + rv->orig_height = st->codecpar->height; - if (st->codec->width != width || st->codec->height != height || - st->codec->pix_fmt != rv->c->pix_fmt) { + if (st->codecpar->width != width || st->codecpar->height != height || + st->codecpar->format != rv->c->pix_fmt) { rv->frame = av_frame_alloc(); - avpicture_fill((AVPicture *)rv->frame, - MEM_mallocN(avpicture_get_size(rv->c->pix_fmt, round_up(width, 16), height), - "alloc proxy output frame"), - rv->c->pix_fmt, - round_up(width, 16), - height); - - rv->sws_ctx = sws_getContext(st->codec->width, + av_image_fill_arrays( + rv->frame->data, + rv->frame->linesize, + MEM_mallocN(av_image_get_buffer_size(rv->c->pix_fmt, round_up(width, 16), height, 1), + "alloc proxy output frame"), + rv->c->pix_fmt, + round_up(width, 16), + height, + 1); + + rv->sws_ctx = sws_getContext(st->codecpar->width, rv->orig_height, - st->codec->pix_fmt, + st->codecpar->format, width, height, rv->c->pix_fmt, @@ -594,26 +617,30 @@ static struct proxy_output_ctx *alloc_proxy_output_ffmpeg( NULL); } - if (avformat_write_header(rv->of, NULL) < 0) { + ret = avformat_write_header(rv->of, NULL); + if (ret < 0) { fprintf(stderr, - "Couldn't set output parameters? " - "Proxy not built!\n"); - av_free(rv->of); - return 0; + "Couldn't write header: %s\n" + "Proxy not built!\n", + av_err2str(ret)); + + if (rv->frame) { + av_frame_free(&rv->frame); + } + + avcodec_free_context(&rv->c); + avformat_free_context(rv->of); + MEM_freeN(rv); + return NULL; } return rv; } -static int add_to_proxy_output_ffmpeg(struct proxy_output_ctx *ctx, AVFrame *frame) +static void add_to_proxy_output_ffmpeg(struct proxy_output_ctx *ctx, AVFrame *frame) { - AVPacket packet = {0}; - int ret, got_output; - - av_init_packet(&packet); - if (!ctx) { - return 0; + return; } if (ctx->sws_ctx && frame && @@ -633,35 +660,46 @@ static int add_to_proxy_output_ffmpeg(struct proxy_output_ctx *ctx, AVFrame *fra frame->pts = ctx->cfra++; } - ret = avcodec_encode_video2(ctx->c, &packet, frame, &got_output); + int ret = avcodec_send_frame(ctx->c, frame); if (ret < 0) { - fprintf(stderr, "Error encoding proxy frame %d for '%s'\n", ctx->cfra - 1, ctx->of->filename); - return 0; + /* Can't send frame to encoder. This shouldn't happen. */ + fprintf(stderr, "Can't send video frame: %s\n", av_err2str(ret)); + return; } + AVPacket *packet = av_packet_alloc(); + + while (ret >= 0) { + ret = avcodec_receive_packet(ctx->c, packet); - if (got_output) { - if (packet.pts != AV_NOPTS_VALUE) { - packet.pts = av_rescale_q(packet.pts, ctx->c->time_base, ctx->st->time_base); + if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { + /* No more packets to flush. */ + break; } - if (packet.dts != AV_NOPTS_VALUE) { - packet.dts = av_rescale_q(packet.dts, ctx->c->time_base, ctx->st->time_base); + if (ret < 0) { + fprintf(stderr, + "Error encoding proxy frame %d for '%s': %s\n", + ctx->cfra - 1, + ctx->of->url, + av_err2str(ret)); + break; } - packet.stream_index = ctx->st->index; + packet->stream_index = ctx->st->index; + av_packet_rescale_ts(packet, ctx->c->time_base, ctx->st->time_base); - if (av_interleaved_write_frame(ctx->of, &packet) != 0) { + int write_ret = av_interleaved_write_frame(ctx->of, packet); + if (write_ret != 0) { fprintf(stderr, "Error writing proxy frame %d " - "into '%s'\n", + "into '%s': %s\n", ctx->cfra - 1, - ctx->of->filename); - return 0; + ctx->of->url, + av_err2str(write_ret)); + break; } - - return 1; } - return 0; + av_packet_free(&packet); } static void free_proxy_output_ffmpeg(struct proxy_output_ctx *ctx, int rollback) @@ -674,15 +712,15 @@ static void free_proxy_output_ffmpeg(struct proxy_output_ctx *ctx, int rollback) } if (!rollback) { - while (add_to_proxy_output_ffmpeg(ctx, NULL)) { - } + /* Flush the remaining packets. */ + add_to_proxy_output_ffmpeg(ctx, NULL); } avcodec_flush_buffers(ctx->c); av_write_trailer(ctx->of); - avcodec_close(ctx->c); + avcodec_free_context(&ctx->c); if (ctx->of->oformat) { if (!(ctx->of->oformat->flags & AVFMT_NOFILE)) { @@ -777,7 +815,7 @@ static IndexBuildContext *index_ffmpeg_create_context(struct anim *anim, /* Find the video stream */ context->videoStream = -1; for (i = 0; i < context->iFormatCtx->nb_streams; i++) { - if (context->iFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) { + if (context->iFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { if (streamcount > 0) { streamcount--; continue; @@ -794,9 +832,8 @@ static IndexBuildContext *index_ffmpeg_create_context(struct anim *anim, } context->iStream = context->iFormatCtx->streams[context->videoStream]; - context->iCodecCtx = context->iStream->codec; - context->iCodec = avcodec_find_decoder(context->iCodecCtx->codec_id); + context->iCodec = avcodec_find_decoder(context->iStream->codecpar->codec_id); if (context->iCodec == NULL) { avformat_close_input(&context->iFormatCtx); @@ -804,7 +841,9 @@ static IndexBuildContext *index_ffmpeg_create_context(struct anim *anim, return NULL; } - context->iCodecCtx->workaround_bugs = 1; + context->iCodecCtx = avcodec_alloc_context3(NULL); + avcodec_parameters_to_context(context->iCodecCtx, context->iStream->codecpar); + context->iCodecCtx->workaround_bugs = FF_BUG_AUTODETECT; if (context->iCodec->capabilities & AV_CODEC_CAP_AUTO_THREADS) { context->iCodecCtx->thread_count = 0; @@ -822,19 +861,19 @@ static IndexBuildContext *index_ffmpeg_create_context(struct anim *anim, if (avcodec_open2(context->iCodecCtx, context->iCodec, NULL) < 0) { avformat_close_input(&context->iFormatCtx); + avcodec_free_context(&context->iCodecCtx); MEM_freeN(context); return NULL; } for (i = 0; i < num_proxy_sizes; i++) { if (proxy_sizes_in_use & proxy_sizes[i]) { - context->proxy_ctx[i] = alloc_proxy_output_ffmpeg( - anim, - context->iStream, - proxy_sizes[i], - context->iCodecCtx->width * proxy_fac[i], - av_get_cropped_height_from_codec(context->iCodecCtx) * proxy_fac[i], - quality); + context->proxy_ctx[i] = alloc_proxy_output_ffmpeg(anim, + context->iStream, + proxy_sizes[i], + context->iCodecCtx->width * proxy_fac[i], + context->iCodecCtx->height * proxy_fac[i], + quality); if (!context->proxy_ctx[i]) { proxy_sizes_in_use &= ~proxy_sizes[i]; } @@ -873,7 +912,7 @@ static void index_rebuild_ffmpeg_finish(FFmpegIndexBuilderContext *context, int } } - avcodec_close(context->iCodecCtx); + avcodec_free_context(&context->iCodecCtx); avformat_close_input(&context->iFormatCtx); MEM_freeN(context); @@ -938,23 +977,18 @@ static int index_rebuild_ffmpeg(FFmpegIndexBuilderContext *context, short *do_update, float *progress) { - AVFrame *in_frame = 0; - AVPacket next_packet; + AVFrame *in_frame = av_frame_alloc(); + AVPacket *next_packet = av_packet_alloc(); uint64_t stream_size; - memset(&next_packet, 0, sizeof(AVPacket)); - - in_frame = av_frame_alloc(); - stream_size = avio_size(context->iFormatCtx->pb); context->frame_rate = av_q2d(av_guess_frame_rate(context->iFormatCtx, context->iStream, NULL)); context->pts_time_base = av_q2d(context->iStream->time_base); - while (av_read_frame(context->iFormatCtx, &next_packet) >= 0) { - int frame_finished = 0; + while (av_read_frame(context->iFormatCtx, next_packet) >= 0) { float next_progress = - (float)((int)floor(((double)next_packet.pos) * 100 / ((double)stream_size) + 0.5)) / 100; + (float)((int)floor(((double)next_packet->pos) * 100 / ((double)stream_size) + 0.5)) / 100; if (*progress != next_progress) { *progress = next_progress; @@ -962,50 +996,59 @@ static int index_rebuild_ffmpeg(FFmpegIndexBuilderContext *context, } if (*stop) { - av_free_packet(&next_packet); break; } - if (next_packet.stream_index == context->videoStream) { - if (next_packet.flags & AV_PKT_FLAG_KEY) { + if (next_packet->stream_index == context->videoStream) { + if (next_packet->flags & AV_PKT_FLAG_KEY) { context->last_seek_pos = context->seek_pos; context->last_seek_pos_dts = context->seek_pos_dts; - context->seek_pos = next_packet.pos; - context->seek_pos_dts = next_packet.dts; - context->seek_pos_pts = next_packet.pts; + context->seek_pos = next_packet->pos; + context->seek_pos_dts = next_packet->dts; + context->seek_pos_pts = next_packet->pts; } - avcodec_decode_video2(context->iCodecCtx, in_frame, &frame_finished, &next_packet); - } + int ret = avcodec_send_packet(context->iCodecCtx, next_packet); + while (ret >= 0) { + ret = avcodec_receive_frame(context->iCodecCtx, in_frame); - if (frame_finished) { - index_rebuild_ffmpeg_proc_decoded_frame(context, &next_packet, in_frame); + if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { + /* No more frames to flush. */ + break; + } + if (ret < 0) { + fprintf(stderr, "Error decoding proxy frame: %s\n", av_err2str(ret)); + break; + } + index_rebuild_ffmpeg_proc_decoded_frame(context, next_packet, in_frame); + } } - av_free_packet(&next_packet); } /* process pictures still stuck in decoder engine after EOF - * according to ffmpeg docs using 0-size packets. + * according to ffmpeg docs using NULL packets. * * At least, if we haven't already stopped... */ - /* this creates the 0-size packet and prevents a memory leak. */ - av_free_packet(&next_packet); - if (!*stop) { - int frame_finished; - - do { - frame_finished = 0; + int ret = avcodec_send_packet(context->iCodecCtx, NULL); - avcodec_decode_video2(context->iCodecCtx, in_frame, &frame_finished, &next_packet); + while (ret >= 0) { + ret = avcodec_receive_frame(context->iCodecCtx, in_frame); - if (frame_finished) { - index_rebuild_ffmpeg_proc_decoded_frame(context, &next_packet, in_frame); + if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { + /* No more frames to flush. */ + break; } - } while (frame_finished); + if (ret < 0) { + fprintf(stderr, "Error flushing proxy frame: %s\n", av_err2str(ret)); + break; + } + index_rebuild_ffmpeg_proc_decoded_frame(context, next_packet, in_frame); + } } + av_packet_free(&next_packet); av_free(in_frame); return 1; diff --git a/source/blender/imbuf/intern/util.c b/source/blender/imbuf/intern/util.c index 64dad5de902..fabf6c5c3bd 100644 --- a/source/blender/imbuf/intern/util.c +++ b/source/blender/imbuf/intern/util.c @@ -245,7 +245,6 @@ static void ffmpeg_log_callback(void *ptr, int level, const char *format, va_lis void IMB_ffmpeg_init(void) { - av_register_all(); avdevice_register_all(); ffmpeg_last_error[0] = '\0'; @@ -269,7 +268,6 @@ static int isffmpeg(const char *filepath) unsigned int i; int videoStream; AVCodec *pCodec; - AVCodecContext *pCodecCtx; if (BLI_path_extension_check_n(filepath, ".swf", @@ -310,8 +308,8 @@ static int isffmpeg(const char *filepath) /* Find the first video stream */ videoStream = -1; for (i = 0; i < pFormatCtx->nb_streams; i++) { - if (pFormatCtx->streams[i] && pFormatCtx->streams[i]->codec && - (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)) { + if (pFormatCtx->streams[i] && pFormatCtx->streams[i]->codecpar && + (pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)) { videoStream = i; break; } @@ -322,21 +320,15 @@ static int isffmpeg(const char *filepath) return 0; } - pCodecCtx = pFormatCtx->streams[videoStream]->codec; + AVCodecParameters *codec_par = pFormatCtx->streams[videoStream]->codecpar; /* Find the decoder for the video stream */ - pCodec = avcodec_find_decoder(pCodecCtx->codec_id); + pCodec = avcodec_find_decoder(codec_par->codec_id); if (pCodec == NULL) { avformat_close_input(&pFormatCtx); return 0; } - if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) { - avformat_close_input(&pFormatCtx); - return 0; - } - - avcodec_close(pCodecCtx); avformat_close_input(&pFormatCtx); return 1; diff --git a/source/blender/io/alembic/ABC_alembic.h b/source/blender/io/alembic/ABC_alembic.h index 9785f6d68ab..5664a43233a 100644 --- a/source/blender/io/alembic/ABC_alembic.h +++ b/source/blender/io/alembic/ABC_alembic.h @@ -49,6 +49,7 @@ struct AlembicExportParams { bool uvs; bool normals; bool vcolors; + bool orcos; bool apply_subdiv; bool curves_as_mesh; bool flatten_hierarchy; diff --git a/source/blender/io/alembic/exporter/abc_writer_mball.cc b/source/blender/io/alembic/exporter/abc_writer_mball.cc index a797310f864..ad78f8ce802 100644 --- a/source/blender/io/alembic/exporter/abc_writer_mball.cc +++ b/source/blender/io/alembic/exporter/abc_writer_mball.cc @@ -69,7 +69,7 @@ Mesh *ABCMetaballWriter::get_export_mesh(Object *object_eval, bool &r_needsfree) return mesh_eval; } r_needsfree = true; - return BKE_mesh_new_from_object(args_.depsgraph, object_eval, false); + return BKE_mesh_new_from_object(args_.depsgraph, object_eval, false, false); } void ABCMetaballWriter::free_export_mesh(Mesh *mesh) diff --git a/source/blender/io/alembic/exporter/abc_writer_mesh.cc b/source/blender/io/alembic/exporter/abc_writer_mesh.cc index c00e57c8edc..fd7db005dd2 100644 --- a/source/blender/io/alembic/exporter/abc_writer_mesh.cc +++ b/source/blender/io/alembic/exporter/abc_writer_mesh.cc @@ -200,6 +200,7 @@ void ABCGenericMeshWriter::do_write(HierarchyContext &context) } m_custom_data_config.pack_uvs = args_.export_params->packuv; + m_custom_data_config.mesh = mesh; m_custom_data_config.mpoly = mesh->mpoly; m_custom_data_config.mloop = mesh->mloop; m_custom_data_config.totpoly = mesh->totpoly; @@ -250,7 +251,7 @@ void ABCGenericMeshWriter::write_mesh(HierarchyContext &context, Mesh *mesh) UVSample uvs_and_indices; - if (!frame_has_been_written_ && args_.export_params->uvs) { + if (args_.export_params->uvs) { const char *name = get_uv_sample(uvs_and_indices, m_custom_data_config, &mesh->ldata); if (!uvs_and_indices.indices.empty() && !uvs_and_indices.uvs.empty()) { @@ -279,6 +280,10 @@ void ABCGenericMeshWriter::write_mesh(HierarchyContext &context, Mesh *mesh) mesh_sample.setNormals(normals_sample); } + if (args_.export_params->orcos) { + write_generated_coordinates(abc_poly_mesh_schema_.getArbGeomParams(), m_custom_data_config); + } + if (liquid_sim_modifier_ != nullptr) { get_velocities(mesh, velocities); mesh_sample.setVelocities(V3fArraySample(velocities)); @@ -312,7 +317,7 @@ void ABCGenericMeshWriter::write_subd(HierarchyContext &context, struct Mesh *me V3fArraySample(points), Int32ArraySample(poly_verts), Int32ArraySample(loop_counts)); UVSample sample; - if (!frame_has_been_written_ && args_.export_params->uvs) { + if (args_.export_params->uvs) { const char *name = get_uv_sample(sample, m_custom_data_config, &mesh->ldata); if (!sample.indices.empty() && !sample.uvs.empty()) { @@ -329,6 +334,10 @@ void ABCGenericMeshWriter::write_subd(HierarchyContext &context, struct Mesh *me abc_subdiv_schema_.getArbGeomParams(), m_custom_data_config, &mesh->ldata, CD_MLOOPUV); } + if (args_.export_params->orcos) { + write_generated_coordinates(abc_poly_mesh_schema_.getArbGeomParams(), m_custom_data_config); + } + if (!crease_indices.empty()) { subdiv_sample.setCreaseIndices(Int32ArraySample(crease_indices)); subdiv_sample.setCreaseLengths(Int32ArraySample(crease_lengths)); diff --git a/source/blender/io/alembic/intern/abc_customdata.cc b/source/blender/io/alembic/intern/abc_customdata.cc index 66e05504303..ccf353595c9 100644 --- a/source/blender/io/alembic/intern/abc_customdata.cc +++ b/source/blender/io/alembic/intern/abc_customdata.cc @@ -22,12 +22,14 @@ */ #include "abc_customdata.h" +#include "abc_axis_conversion.h" #include <Alembic/AbcGeom/All.h> #include <algorithm> #include <unordered_map> #include "DNA_customdata_types.h" +#include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" #include "BLI_math_base.h" @@ -50,8 +52,13 @@ using Alembic::Abc::V2fArraySample; using Alembic::AbcGeom::OC4fGeomParam; using Alembic::AbcGeom::OV2fGeomParam; +using Alembic::AbcGeom::OV3fGeomParam; namespace blender::io::alembic { +/* ORCO, Generated Coordinates, and Reference Points ("Pref") are all terms for the same thing. + * Other applications (Maya, Houdini) write these to a property called "Pref". */ +static const std::string propNameOriginalCoordinates("Pref"); + static void get_uvs(const CDStreamConfig &config, std::vector<Imath::V2f> &uvs, std::vector<uint32_t> &uvidx, @@ -222,6 +229,32 @@ static void write_mcol(const OCompoundProperty &prop, param.set(sample); } +void write_generated_coordinates(const OCompoundProperty &prop, CDStreamConfig &config) +{ + const void *customdata = CustomData_get_layer(&config.mesh->vdata, CD_ORCO); + if (customdata == nullptr) { + /* Data not available, so don't even bother creating an Alembic property for it. */ + return; + } + const float(*orcodata)[3] = static_cast<const float(*)[3]>(customdata); + + /* Convert 3D vertices from float[3] z=up to V3f y=up. */ + std::vector<Imath::V3f> coords(config.totvert); + float orco_yup[3]; + for (int vertex_idx = 0; vertex_idx < config.totvert; vertex_idx++) { + copy_yup_from_zup(orco_yup, orcodata[vertex_idx]); + coords[vertex_idx].setValue(orco_yup[0], orco_yup[1], orco_yup[2]); + } + + if (!config.abc_ocro.valid()) { + /* Create the Alembic property and keep a reference so future frames can reuse it. */ + config.abc_ocro = OV3fGeomParam(prop, propNameOriginalCoordinates, false, kVertexScope, 1); + } + + OV3fGeomParam::Sample sample(coords, kVertexScope); + config.abc_ocro.set(sample); +} + void write_custom_data(const OCompoundProperty &prop, CDStreamConfig &config, CustomData *data, @@ -263,6 +296,7 @@ using Alembic::Abc::PropertyHeader; using Alembic::AbcGeom::IC3fGeomParam; using Alembic::AbcGeom::IC4fGeomParam; using Alembic::AbcGeom::IV2fGeomParam; +using Alembic::AbcGeom::IV3fGeomParam; static void read_uvs(const CDStreamConfig &config, void *data, @@ -448,6 +482,44 @@ static void read_custom_data_uvs(const ICompoundProperty &prop, read_uvs(config, cd_data, sample.getVals(), sample.getIndices()); } +void read_generated_coordinates(const ICompoundProperty &prop, + const CDStreamConfig &config, + const Alembic::Abc::ISampleSelector &iss) +{ + if (prop.getPropertyHeader(propNameOriginalCoordinates) == nullptr) { + /* The ORCO property isn't there, so don't bother trying to process it. */ + return; + } + + IV3fGeomParam param(prop, propNameOriginalCoordinates); + if (!param.valid() || param.isIndexed()) { + /* Invalid or indexed coordinates aren't supported. */ + return; + } + if (param.getScope() != kVertexScope) { + /* These are original vertex coordinates, so must be vertex-scoped. */ + return; + } + + IV3fGeomParam::Sample sample = param.getExpandedValue(iss); + Alembic::AbcGeom::V3fArraySamplePtr abc_ocro = sample.getVals(); + const size_t totvert = abc_ocro.get()->size(); + + void *cd_data; + if (CustomData_has_layer(&config.mesh->vdata, CD_ORCO)) { + cd_data = CustomData_get_layer(&config.mesh->vdata, CD_ORCO); + } + else { + cd_data = CustomData_add_layer(&config.mesh->vdata, CD_ORCO, CD_CALLOC, nullptr, totvert); + } + + float(*orcodata)[3] = static_cast<float(*)[3]>(cd_data); + for (int vertex_idx = 0; vertex_idx < totvert; ++vertex_idx) { + const Imath::V3f &abc_coords = (*abc_ocro)[vertex_idx]; + copy_zup_from_yup(orcodata[vertex_idx], abc_coords.getValue()); + } +} + void read_custom_data(const std::string &iobject_full_name, const ICompoundProperty &prop, const CDStreamConfig &config, diff --git a/source/blender/io/alembic/intern/abc_customdata.h b/source/blender/io/alembic/intern/abc_customdata.h index 4eb515f132c..9ee964c0545 100644 --- a/source/blender/io/alembic/intern/abc_customdata.h +++ b/source/blender/io/alembic/intern/abc_customdata.h @@ -72,12 +72,16 @@ struct CDStreamConfig { const char **modifier_error_message; - /* Alembic needs Blender to keep references to C++ objects (the destructors - * finalize the writing to ABC). This map stores OV2fGeomParam objects for the - * 2nd and subsequent UV maps; the primary UV map is kept alive by the Alembic - * mesh sample itself. */ + /* Alembic needs Blender to keep references to C++ objects (the destructors finalize the writing + * to ABC). The following fields are all used to keep these references. */ + + /* Mapping from UV map name to its ABC property, for the 2nd and subsequent UV maps; the primary + * UV map is kept alive by the Alembic mesh sample itself. */ std::map<std::string, Alembic::AbcGeom::OV2fGeomParam> abc_uv_maps; + /* OCRO coordinates, aka Generated Coordinates. */ + Alembic::AbcGeom::OV3fGeomParam abc_ocro; + CDStreamConfig() : mloop(NULL), totloop(0), @@ -102,6 +106,12 @@ struct CDStreamConfig { * For now the active layer is used, maybe needs a better way to choose this. */ const char *get_uv_sample(UVSample &sample, const CDStreamConfig &config, CustomData *data); +void write_generated_coordinates(const OCompoundProperty &prop, CDStreamConfig &config); + +void read_generated_coordinates(const ICompoundProperty &prop, + const CDStreamConfig &config, + const Alembic::Abc::ISampleSelector &iss); + void write_custom_data(const OCompoundProperty &prop, CDStreamConfig &config, CustomData *data, diff --git a/source/blender/io/alembic/intern/abc_reader_mesh.cc b/source/blender/io/alembic/intern/abc_reader_mesh.cc index 8133f615080..11b6c1c18ca 100644 --- a/source/blender/io/alembic/intern/abc_reader_mesh.cc +++ b/source/blender/io/alembic/intern/abc_reader_mesh.cc @@ -439,6 +439,7 @@ static void read_mesh_sample(const std::string &iobject_full_name, if ((settings->read_flag & MOD_MESHSEQ_READ_VERT) != 0) { read_mverts(config, abc_mesh_data); + read_generated_coordinates(schema.getArbGeomParams(), config, selector); } if ((settings->read_flag & MOD_MESHSEQ_READ_POLY) != 0) { @@ -558,7 +559,7 @@ void AbcMeshReader::readObjectData(Main *bmain, const Alembic::Abc::ISampleSelec /* XXX fixme after 2.80; mesh->flag isn't copied by BKE_mesh_nomain_to_mesh() */ /* read_mesh can be freed by BKE_mesh_nomain_to_mesh(), so get the flag before that happens. */ short autosmooth = (read_mesh->flag & ME_AUTOSMOOTH); - BKE_mesh_nomain_to_mesh(read_mesh, mesh, m_object, &CD_MASK_MESH, true); + BKE_mesh_nomain_to_mesh(read_mesh, mesh, m_object, &CD_MASK_EVERYTHING, true); mesh->flag |= autosmooth; } @@ -868,7 +869,7 @@ void AbcSubDReader::readObjectData(Main *bmain, const Alembic::Abc::ISampleSelec Mesh *read_mesh = this->read_mesh(mesh, sample_sel, MOD_MESHSEQ_READ_ALL, nullptr); if (read_mesh != mesh) { - BKE_mesh_nomain_to_mesh(read_mesh, mesh, m_object, &CD_MASK_MESH, true); + BKE_mesh_nomain_to_mesh(read_mesh, mesh, m_object, &CD_MASK_EVERYTHING, true); } ISubDSchema::Sample sample; diff --git a/source/blender/io/collada/Materials.cpp b/source/blender/io/collada/Materials.cpp index 2cc80de2ec1..ac4c65464c8 100644 --- a/source/blender/io/collada/Materials.cpp +++ b/source/blender/io/collada/Materials.cpp @@ -105,7 +105,7 @@ bNodeTree *MaterialNode::prepare_material_nodetree() return ntree; } -void MaterialNode::update_material_nodetree() +void MaterialNode::update_material_nodetree() { ntreeUpdateTree(CTX_data_main(mContext), ntree); } @@ -133,6 +133,19 @@ void MaterialNode::add_link(bNode *from_node, int from_index, bNode *to_node, in nodeAddLink(ntree, from_node, from_socket, to_node, to_socket); } +void MaterialNode::add_link(bNode *from_node, + const char *from_label, + bNode *to_node, + const char *to_label) +{ + bNodeSocket *from_socket = nodeFindSocket(from_node, SOCK_OUT, from_label); + bNodeSocket *to_socket = nodeFindSocket(to_node, SOCK_IN, to_label); + + if (from_socket && to_socket) { + nodeAddLink(ntree, from_node, from_socket, to_node, to_socket); + } +} + void MaterialNode::set_reflectivity(COLLADAFW::FloatOrParam &val) { float reflectivity = val.getFloatValue(); @@ -218,22 +231,32 @@ void MaterialNode::set_alpha(COLLADAFW::EffectCommon::OpaqueMode mode, void MaterialNode::set_diffuse(COLLADAFW::ColorOrTexture &cot) { int locy = -300 * (node_map.size() - 2); - if (cot.isColor()) { - COLLADAFW::Color col = cot.getColor(); - bNodeSocket *socket = nodeFindSocket(shader_node, SOCK_IN, "Base Color"); - float *fcol = (float *)socket->default_value; - fcol[0] = material->r = col.getRed(); - fcol[1] = material->g = col.getGreen(); - fcol[2] = material->b = col.getBlue(); - fcol[3] = material->a = col.getAlpha(); - } - else if (cot.isTexture()) { + if (cot.isTexture()) { bNode *texture_node = add_texture_node(cot, -300, locy, "Base Color"); if (texture_node != nullptr) { add_link(texture_node, 0, shader_node, 0); } } + else { + bNodeSocket *socket = nodeFindSocket(shader_node, SOCK_IN, "Base Color"); + float *fcol = (float *)socket->default_value; + + if (cot.isColor()) { + COLLADAFW::Color col = cot.getColor(); + fcol[0] = material->r = col.getRed(); + fcol[1] = material->g = col.getGreen(); + fcol[2] = material->b = col.getBlue(); + fcol[3] = material->a = col.getAlpha(); + } + else { + /* no diffuse term = same as black */ + fcol[0] = material->r = 0.0f; + fcol[1] = material->g = 0.0f; + fcol[2] = material->b = 0.0f; + fcol[3] = material->a = 1.0f; + } + } } Image *MaterialNode::get_diffuse_image() @@ -326,7 +349,7 @@ void MaterialNode::set_emission(COLLADAFW::ColorOrTexture &cot) else if (cot.isTexture()) { bNode *texture_node = add_texture_node(cot, -300, locy, "Emission"); if (texture_node != nullptr) { - add_link(texture_node, 0, shader_node, 0); + add_link(texture_node, "Color", shader_node, "Emission"); } } @@ -363,18 +386,38 @@ void MaterialNode::set_opacity(COLLADAFW::ColorOrTexture &cot) void MaterialNode::set_specular(COLLADAFW::ColorOrTexture &cot) { + bool has_specularity = true; int locy = -300 * (node_map.size() - 2); if (cot.isColor()) { COLLADAFW::Color col = cot.getColor(); - bNode *node = add_node(SH_NODE_RGB, -300, locy, "Specular"); - set_color(node, col); - /* TODO: Connect node */ + + if (col.getRed() == 0 && col.getGreen() == 0 && col.getBlue() == 0) { + has_specularity = false; + } + else { + bNode *node = add_node(SH_NODE_RGB, -300, locy, "Specular"); + set_color(node, col); + /* TODO: Connect node */ + } } - /* texture */ else if (cot.isTexture()) { add_texture_node(cot, -300, locy, "Specular"); /* TODO: Connect node */ } + else { + /* no specular term) */ + has_specularity = false; + } + + if (!has_specularity) { + /* If specularity is black or not defined reset the Specular value to 0 + TODO: This is a solution only for a corner case. We must find a better + way to handle specularity in general. Also note that currently we + do not export specularity values, see EffectExporter::operator() + */ + bNodeSocket *socket = nodeFindSocket(shader_node, SOCK_IN, "Specular"); + ((bNodeSocketValueFloat *)socket->default_value)->value = 0.0f; + } } bNode *MaterialNode::add_texture_node(COLLADAFW::ColorOrTexture &cot, diff --git a/source/blender/io/collada/Materials.h b/source/blender/io/collada/Materials.h index 2d8c823a4c2..1886acb7a6a 100644 --- a/source/blender/io/collada/Materials.h +++ b/source/blender/io/collada/Materials.h @@ -48,6 +48,7 @@ class MaterialNode { bNodeTree *prepare_material_nodetree(); bNode *add_node(int node_type, int locx, int locy, std::string label); void add_link(bNode *from_node, int from_index, bNode *to_node, int to_index); + void add_link(bNode *from_node, const char *from_label, bNode *to_node, const char *to_label); bNode *add_texture_node(COLLADAFW::ColorOrTexture &cot, int locx, int locy, std::string label); void setShaderType(); diff --git a/source/blender/io/gpencil/intern/gpencil_io_base.cc b/source/blender/io/gpencil/intern/gpencil_io_base.cc index bfa3abb1dcd..e79a2bc98ff 100644 --- a/source/blender/io/gpencil/intern/gpencil_io_base.cc +++ b/source/blender/io/gpencil/intern/gpencil_io_base.cc @@ -69,6 +69,17 @@ GpencilIO::GpencilIO(const GpencilIOParams *iparams) cfra_ = iparams->frame_cur; /* Calculate camera matrix. */ + prepare_camera_params(iparams); +} + +void GpencilIO::prepare_camera_params(const GpencilIOParams *iparams) +{ + params_ = *iparams; + const bool is_pdf = params_.mode == GP_EXPORT_TO_PDF; + const bool any_camera = (params_.v3d->camera != nullptr); + const bool force_camera_view = is_pdf && any_camera; + + /* Calculate camera matrix. */ Object *cam_ob = params_.v3d->camera; if (cam_ob != nullptr) { /* Set up parameters. */ @@ -96,7 +107,7 @@ GpencilIO::GpencilIO(const GpencilIOParams *iparams) winy_ = params_.region->winy; /* Camera rectangle. */ - if (rv3d_->persp == RV3D_CAMOB) { + if ((rv3d_->persp == RV3D_CAMOB) || (force_camera_view)) { render_x_ = (scene_->r.xsch * scene_->r.size) / 100; render_y_ = (scene_->r.ysch * scene_->r.size) / 100; diff --git a/source/blender/io/gpencil/intern/gpencil_io_base.hh b/source/blender/io/gpencil/intern/gpencil_io_base.hh index cbcd35e470d..2e1e1707c78 100644 --- a/source/blender/io/gpencil/intern/gpencil_io_base.hh +++ b/source/blender/io/gpencil/intern/gpencil_io_base.hh @@ -50,6 +50,7 @@ class GpencilIO { GpencilIO(const GpencilIOParams *iparams); void frame_number_set(const int value); + void prepare_camera_params(const GpencilIOParams *iparams); protected: GpencilIOParams params_; diff --git a/source/blender/io/gpencil/intern/gpencil_io_capi.cc b/source/blender/io/gpencil/intern/gpencil_io_capi.cc index a710c175a77..8093ec3c52d 100644 --- a/source/blender/io/gpencil/intern/gpencil_io_capi.cc +++ b/source/blender/io/gpencil/intern/gpencil_io_capi.cc @@ -121,6 +121,7 @@ static bool gpencil_io_export_pdf(Depsgraph *depsgraph, CFRA = i; BKE_scene_graph_update_for_newframe(depsgraph); + exporter->prepare_camera_params(iparams); exporter->frame_number_set(i); exporter->add_newpage(); exporter->add_body(); @@ -132,6 +133,7 @@ static bool gpencil_io_export_pdf(Depsgraph *depsgraph, BKE_scene_graph_update_for_newframe(depsgraph); } else { + exporter->prepare_camera_params(iparams); exporter->add_newpage(); exporter->add_body(); result = exporter->write(); @@ -151,6 +153,8 @@ static bool gpencil_io_export_frame_svg(GpencilExporterSVG *exporter, { bool result = false; exporter->frame_number_set(iparams->frame_cur); + exporter->prepare_camera_params(iparams); + if (newpage) { result |= exporter->add_newpage(); } diff --git a/source/blender/io/gpencil/intern/gpencil_io_import_svg.cc b/source/blender/io/gpencil/intern/gpencil_io_import_svg.cc index dc76b6ed661..73b3e46959b 100644 --- a/source/blender/io/gpencil/intern/gpencil_io_import_svg.cc +++ b/source/blender/io/gpencil/intern/gpencil_io_import_svg.cc @@ -102,7 +102,7 @@ bool GpencilImporterSVG::read() bGPDlayer *gpl = (bGPDlayer *)BLI_findstring( &gpd_->layers, layer_id, offsetof(bGPDlayer, info)); if (gpl == nullptr) { - gpl = BKE_gpencil_layer_addnew(gpd_, layer_id, true); + gpl = BKE_gpencil_layer_addnew(gpd_, layer_id, true, false); /* Disable lights. */ gpl->flag &= ~GP_LAYER_USE_LIGHTS; } diff --git a/source/blender/io/gpencil/nanosvg/nanosvg.h b/source/blender/io/gpencil/nanosvg/nanosvg.h index 886b6402267..94dad37861a 100644 --- a/source/blender/io/gpencil/nanosvg/nanosvg.h +++ b/source/blender/io/gpencil/nanosvg/nanosvg.h @@ -2362,7 +2362,12 @@ static void nsvg__pathArcTo(NSVGparser *p, float *cpx, float *cpy, float *args, // The loop assumes an iteration per end point (including start and end), this +1. ndivs = (int)(fabsf(da) / (NSVG_PI * 0.5f) + 1.0f); hda = (da / (float)ndivs) / 2.0f; - kappa = fabsf(4.0f / 3.0f * (1.0f - cosf(hda)) / sinf(hda)); + // Fix for ticket #179: division by 0: avoid cotangens around 0 (infinite) + if ((hda < 1e-3f) && (hda > -1e-3f)) + hda *= 0.5f; + else + hda = (1.0f - cosf(hda)) / sinf(hda); + kappa = fabsf(4.0f / 3.0f * hda); if (da < 0.0f) kappa = -kappa; diff --git a/source/blender/io/usd/intern/usd_writer_metaball.cc b/source/blender/io/usd/intern/usd_writer_metaball.cc index 8e32bd4705a..28c96c3a511 100644 --- a/source/blender/io/usd/intern/usd_writer_metaball.cc +++ b/source/blender/io/usd/intern/usd_writer_metaball.cc @@ -62,7 +62,7 @@ Mesh *USDMetaballWriter::get_export_mesh(Object *object_eval, bool &r_needsfree) return mesh_eval; } r_needsfree = true; - return BKE_mesh_new_from_object(usd_export_context_.depsgraph, object_eval, false); + return BKE_mesh_new_from_object(usd_export_context_.depsgraph, object_eval, false, false); } void USDMetaballWriter::free_export_mesh(Mesh *mesh) diff --git a/source/blender/makesdna/DNA_ID.h b/source/blender/makesdna/DNA_ID.h index fa60bba54d5..8bf9afafa1b 100644 --- a/source/blender/makesdna/DNA_ID.h +++ b/source/blender/makesdna/DNA_ID.h @@ -555,8 +555,15 @@ enum { /* RESET_AFTER_USE tag existing data before linking so we know what is new. */ LIB_TAG_PRE_EXISTING = 1 << 11, - /* The data-block is a copy-on-write/localized version. */ + /** + * The data-block is a copy-on-write/localized version. + * + * \warning This should not be cleared on existing data. + * If support for this is needed, see T88026 as this flag controls memory ownership + * of physics *shared* pointers. + */ LIB_TAG_COPIED_ON_WRITE = 1 << 12, + LIB_TAG_COPIED_ON_WRITE_EVAL_RESULT = 1 << 13, LIB_TAG_LOCALIZED = 1 << 14, diff --git a/source/blender/makesdna/DNA_action_types.h b/source/blender/makesdna/DNA_action_types.h index 5cc525a6cff..583e56de9c2 100644 --- a/source/blender/makesdna/DNA_action_types.h +++ b/source/blender/makesdna/DNA_action_types.h @@ -265,9 +265,10 @@ typedef struct bPoseChannel { * since the alternative is highly complicated - campbell */ struct bPoseChannel *custom_tx; - float custom_scale; - - char _pad1[4]; + float custom_scale; /* Deprecated */ + float custom_scale_xyz[3]; + float custom_translation[3]; + float custom_rotation_euler[3]; /** Transforms - written in by actions or transform. */ float loc[3]; @@ -417,9 +418,9 @@ typedef enum ePchan_DrawFlag { PCHAN_DRAW_NO_CUSTOM_BONE_SIZE = (1 << 0), } ePchan_DrawFlag; -#define PCHAN_CUSTOM_DRAW_SIZE(pchan) \ - (pchan)->custom_scale *( \ - ((pchan)->drawflag & PCHAN_DRAW_NO_CUSTOM_BONE_SIZE) ? 1.0f : (pchan)->bone->length) +/* Note: It doesn't take custom_scale_xyz into account */ +#define PCHAN_CUSTOM_BONE_LENGTH(pchan) \ + (((pchan)->drawflag & PCHAN_DRAW_NO_CUSTOM_BONE_SIZE) ? 1.0f : (pchan)->bone->length) #ifdef DNA_DEPRECATED_ALLOW /* PoseChannel->bboneflag */ diff --git a/source/blender/makesdna/DNA_boid_types.h b/source/blender/makesdna/DNA_boid_types.h index 882d4eb1b3b..540446ccd9d 100644 --- a/source/blender/makesdna/DNA_boid_types.h +++ b/source/blender/makesdna/DNA_boid_types.h @@ -97,7 +97,8 @@ typedef struct BoidRuleFollowLeader { } BoidRuleFollowLeader; typedef struct BoidRuleAverageSpeed { BoidRule rule; - float wander, level, speed, rt; + float wander, level, speed; + char _pad0[4]; } BoidRuleAverageSpeed; typedef struct BoidRuleFight { BoidRule rule; @@ -178,7 +179,7 @@ typedef struct BoidState { //} BoidSignal; // typedef struct BoidSignalDefine { // struct BoidSignalDefine *next, *prev; -// int id, rt; +// int id, _pad[4]; // char name[32]; //} BoidSignalDefine; diff --git a/source/blender/makesdna/DNA_effect_types.h b/source/blender/makesdna/DNA_effect_types.h index 33f2e1b47c0..307a212a139 100644 --- a/source/blender/makesdna/DNA_effect_types.h +++ b/source/blender/makesdna/DNA_effect_types.h @@ -71,13 +71,15 @@ extern "C" { typedef struct Effect { struct Effect *next, *prev; - short type, flag, buttype, rt; + short type, flag, buttype; + char _pad0[2]; } Effect; typedef struct BuildEff { struct BuildEff *next, *prev; - short type, flag, buttype, rt; + short type, flag, buttype; + char _pad0[2]; float len, sfra; @@ -88,7 +90,8 @@ typedef struct BuildEff { typedef struct Particle { float co[3], no[3]; float time, lifetime; - short mat_nr, rt; + short mat_nr; + char _pad0[2]; } Particle; struct Collection; diff --git a/source/blender/makesdna/DNA_ipo_types.h b/source/blender/makesdna/DNA_ipo_types.h index 8bb94976414..c5e207c4e20 100644 --- a/source/blender/makesdna/DNA_ipo_types.h +++ b/source/blender/makesdna/DNA_ipo_types.h @@ -77,8 +77,9 @@ typedef struct IpoCurve { short totvert; /** Interpolation and extrapolation modes . */ short ipo, extrap; - /** Flag= settings; rt= ???. */ - short flag, rt; + /** Flag= settings. */ + short flag; + char _pad0[2]; /** Minimum/maximum y-extents for curve. */ float ymin, ymax; /** ???. */ diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h index 47f2c221279..58c94b6f369 100644 --- a/source/blender/makesdna/DNA_node_types.h +++ b/source/blender/makesdna/DNA_node_types.h @@ -45,6 +45,8 @@ struct bNodePreview; struct bNodeTreeExec; struct bNodeType; struct uiBlock; +struct Tex; +struct Material; #define NODE_MAXSTR 64 @@ -165,6 +167,8 @@ typedef enum eNodeSocketDatatype { SOCK_IMAGE = 9, SOCK_GEOMETRY = 10, SOCK_COLLECTION = 11, + SOCK_TEXTURE = 12, + SOCK_MATERIAL = 13, } eNodeSocketDatatype; /* socket shape */ @@ -593,6 +597,14 @@ typedef struct bNodeSocketValueCollection { struct Collection *value; } bNodeSocketValueCollection; +typedef struct bNodeSocketValueTexture { + struct Tex *value; +} bNodeSocketValueTexture; + +typedef struct bNodeSocketValueMaterial { + struct Material *value; +} bNodeSocketValueMaterial; + /* data structs, for node->storage */ enum { CMP_NODE_MASKTYPE_ADD = 0, @@ -1184,10 +1196,31 @@ typedef struct NodeAttributeVectorMath { uint8_t input_type_c; } NodeAttributeVectorMath; +typedef struct NodeAttributeVectorRotate { + /* GeometryNodeAttributeVectorRotateMode */ + uint8_t mode; + + /* GeometryNodeAttributeInputMode */ + uint8_t input_type_vector; + uint8_t input_type_center; + uint8_t input_type_axis; + uint8_t input_type_angle; + uint8_t input_type_rotation; + char _pad[2]; +} NodeAttributeVectorRotate; + typedef struct NodeAttributeColorRamp { ColorBand color_ramp; } NodeAttributeColorRamp; +typedef struct NodeAttributeCurveMap { + /* CustomDataType. */ + uint8_t data_type; + char _pad[7]; + CurveMapping *curve_vec; + CurveMapping *curve_rgb; +} NodeAttributeCurveMap; + typedef struct NodeInputVector { float vector[3]; } NodeInputVector; @@ -1312,6 +1345,11 @@ typedef struct NodeSwitch { uint8_t input_type; } NodeSwitch; +typedef struct NodeGeometryCurveResample { + /* GeometryNodeCurveSampleMode. */ + uint8_t mode; +} NodeGeometryCurveResample; + typedef struct NodeGeometryAttributeTransfer { /* AttributeDomain. */ int8_t domain; @@ -1762,6 +1800,14 @@ typedef enum GeometryNodeRotatePointsType { GEO_NODE_POINT_ROTATE_TYPE_AXIS_ANGLE = 1, } GeometryNodeRotatePointsType; +typedef enum GeometryNodeAttributeVectorRotateMode { + GEO_NODE_VECTOR_ROTATE_TYPE_AXIS = 0, + GEO_NODE_VECTOR_ROTATE_TYPE_AXIS_X = 1, + GEO_NODE_VECTOR_ROTATE_TYPE_AXIS_Y = 2, + GEO_NODE_VECTOR_ROTATE_TYPE_AXIS_Z = 3, + GEO_NODE_VECTOR_ROTATE_TYPE_EULER_XYZ = 4, +} GeometryNodeAttributeVectorRotateMode; + typedef enum GeometryNodeAttributeRandomizeMode { GEO_NODE_ATTRIBUTE_RANDOMIZE_REPLACE_CREATE = 0, GEO_NODE_ATTRIBUTE_RANDOMIZE_ADD = 1, @@ -1813,6 +1859,11 @@ typedef enum GeometryNodeMeshLineCountMode { GEO_NODE_MESH_LINE_COUNT_RESOLUTION = 1, } GeometryNodeMeshLineCountMode; +typedef enum GeometryNodeCurveSampleMode { + GEO_NODE_CURVE_SAMPLE_COUNT = 0, + GEO_NODE_CURVE_SAMPLE_LENGTH = 1, +} GeometryNodeCurveSampleMode; + typedef enum GeometryNodeAttributeTransferMapMode { GEO_NODE_ATTRIBUTE_TRANSFER_NEAREST_FACE_INTERPOLATED = 0, GEO_NODE_ATTRIBUTE_TRANSFER_NEAREST = 1, diff --git a/source/blender/makesdna/DNA_object_force_types.h b/source/blender/makesdna/DNA_object_force_types.h index 37013f5b4d6..3d8418fb734 100644 --- a/source/blender/makesdna/DNA_object_force_types.h +++ b/source/blender/makesdna/DNA_object_force_types.h @@ -182,8 +182,8 @@ typedef struct EffectorWeights { /** Effector type specific weights. */ float weight[14]; float global_gravity; - short flag, rt[3]; - char _pad[4]; + short flag; + char _pad[2]; } EffectorWeights; /* EffectorWeights->flag */ @@ -267,10 +267,9 @@ typedef struct SoftBody { char namedVG_Spring_K[64]; /* baking */ - int sfra, efra; - int interval; + char _pad1[6]; /** Local==1: use local coords for baking. */ - short local, solverflags; + char local, solverflags; /* -- these must be kept for backwards compatibility -- */ /** Array of size totpointkey. */ diff --git a/source/blender/makesdna/DNA_particle_types.h b/source/blender/makesdna/DNA_particle_types.h index cc40e26b92b..30b1fbe09d3 100644 --- a/source/blender/makesdna/DNA_particle_types.h +++ b/source/blender/makesdna/DNA_particle_types.h @@ -64,7 +64,7 @@ typedef struct BoidParticle { struct BoidData data; float gravity[3]; float wander[3]; - float rt; + char _pad0[4]; } BoidParticle; typedef struct ParticleSpring { @@ -82,7 +82,7 @@ typedef struct ChildParticle { float w[4]; /** Face vertex weights and offset. */ float fuv[4], foffset; - float rt; + char _pad0[4]; } ChildParticle; typedef struct ParticleTarget { @@ -99,7 +99,8 @@ typedef struct ParticleDupliWeight { short count; short flag; /** Only updated on file save and used on file load. */ - short index, rt; + short index; + char _pad0[2]; } ParticleDupliWeight; typedef struct ParticleData { @@ -191,7 +192,8 @@ typedef struct ParticleSettings { struct EffectorWeights *effector_weights; struct Collection *collision_group; - int flag, rt; + int flag; + char _pad1[4]; short type, from, distr, texact; /* physics modes */ short phystype, rotmode, avemode, reactevent; diff --git a/source/blender/makesdna/DNA_pointcache_types.h b/source/blender/makesdna/DNA_pointcache_types.h index de2fa3f10fe..ad5f386bf2b 100644 --- a/source/blender/makesdna/DNA_pointcache_types.h +++ b/source/blender/makesdna/DNA_pointcache_types.h @@ -107,7 +107,8 @@ typedef struct PointCache { int totpoint; /** Modifier stack index. */ int index; - short compression, rt; + short compression; + char _pad0[2]; char name[64]; char prev_name[64]; diff --git a/source/blender/makesdna/DNA_scene_types.h b/source/blender/makesdna/DNA_scene_types.h index c7f7e610a1a..0b07b8271a5 100644 --- a/source/blender/makesdna/DNA_scene_types.h +++ b/source/blender/makesdna/DNA_scene_types.h @@ -952,7 +952,8 @@ typedef struct ParticleEditSettings { /** Runtime. */ void *paintcursor; - float emitterdist, rt; + float emitterdist; + char _pad0[4]; int selectmode; int edittype; @@ -1550,7 +1551,8 @@ typedef struct UnitSettings { typedef struct PhysicsSettings { float gravity[3]; - int flag, quick_cache_step, rt; + int flag, quick_cache_step; + char _pad0[4]; } PhysicsSettings; /* ------------------------------------------- */ diff --git a/source/blender/makesdna/DNA_sequence_types.h b/source/blender/makesdna/DNA_sequence_types.h index fa11a7dfd13..46ac4435e95 100644 --- a/source/blender/makesdna/DNA_sequence_types.h +++ b/source/blender/makesdna/DNA_sequence_types.h @@ -521,7 +521,7 @@ enum { SEQ_SCENE_NO_GPENCIL = (1 << 28), SEQ_USE_VIEWS = (1 << 29), - /* access scene strips directly (like a metastrip) */ + /* Access scene strips directly (like a meta-strip). */ SEQ_SCENE_STRIPS = (1 << 30), SEQ_INVALID_EFFECT = (1u << 31), diff --git a/source/blender/makesdna/DNA_texture_types.h b/source/blender/makesdna/DNA_texture_types.h index 5381c524e4d..60c255e8637 100644 --- a/source/blender/makesdna/DNA_texture_types.h +++ b/source/blender/makesdna/DNA_texture_types.h @@ -57,7 +57,8 @@ typedef struct MTex { short colormodel, pmapto, pmaptoneg; short normapspace, which_output; float r, g, b, k; - float def_var, rt; + float def_var; + char _pad1[4]; /* common */ float colfac, varfac; diff --git a/source/blender/makesdna/DNA_xr_types.h b/source/blender/makesdna/DNA_xr_types.h index 2ce32a723a7..8e63760fef7 100644 --- a/source/blender/makesdna/DNA_xr_types.h +++ b/source/blender/makesdna/DNA_xr_types.h @@ -58,6 +58,21 @@ typedef enum eXRSessionBasePoseType { XR_BASE_POSE_CUSTOM = 2, } eXRSessionBasePoseType; +/** XR action type. Enum values match those in GHOST_XrActionType enum for consistency. */ +typedef enum eXrActionType { + XR_BOOLEAN_INPUT = 1, + XR_FLOAT_INPUT = 2, + XR_VECTOR2F_INPUT = 3, + XR_POSE_INPUT = 4, + XR_VIBRATION_OUTPUT = 100, +} eXrActionType; + +typedef enum eXrOpFlag { + XR_OP_PRESS = 0, + XR_OP_RELEASE = 1, + XR_OP_MODAL = 2, +} eXrOpFlag; + #ifdef __cplusplus } #endif diff --git a/source/blender/makesrna/RNA_access.h b/source/blender/makesrna/RNA_access.h index 7fb19ffe63a..dae494ef5ec 100644 --- a/source/blender/makesrna/RNA_access.h +++ b/source/blender/makesrna/RNA_access.h @@ -846,6 +846,7 @@ const char *RNA_property_description(PropertyRNA *prop); PropertyType RNA_property_type(PropertyRNA *prop); PropertySubType RNA_property_subtype(PropertyRNA *prop); PropertyUnit RNA_property_unit(PropertyRNA *prop); +PropertyScaleType RNA_property_ui_scale(PropertyRNA *prop); int RNA_property_flag(PropertyRNA *prop); int RNA_property_override_flag(PropertyRNA *prop); int RNA_property_tags(PropertyRNA *prop); diff --git a/source/blender/makesrna/RNA_define.h b/source/blender/makesrna/RNA_define.h index 17309d847bd..a31182b2f5a 100644 --- a/source/blender/makesrna/RNA_define.h +++ b/source/blender/makesrna/RNA_define.h @@ -388,6 +388,7 @@ void RNA_def_property_string_default(PropertyRNA *prop, const char *value); void RNA_def_property_ui_text(PropertyRNA *prop, const char *name, const char *description); void RNA_def_property_ui_range( PropertyRNA *prop, double min, double max, double step, int precision); +void RNA_def_property_ui_scale_type(PropertyRNA *prop, PropertyScaleType scale_type); void RNA_def_property_ui_icon(PropertyRNA *prop, int icon, int consecutive); void RNA_def_property_update(PropertyRNA *prop, int noteflag, const char *updatefunc); diff --git a/source/blender/makesrna/RNA_types.h b/source/blender/makesrna/RNA_types.h index 6cd3678017e..4a6d6dddec7 100644 --- a/source/blender/makesrna/RNA_types.h +++ b/source/blender/makesrna/RNA_types.h @@ -95,6 +95,32 @@ typedef enum PropertyUnit { PROP_UNIT_TEMPERATURE = (11 << 16), /* C */ } PropertyUnit; +/** + * Use values besides #PROP_SCALE_LINEAR + * so the movement of the mouse doesn't map linearly to the value of the slider. + * + * For some settings it's useful to space motion in a non-linear way, see T77868. + * + * NOTE: The scale types are available for all float sliders. + * For integer sliders they are only available if they use the visible value bar. + * Sliders with logarithmic scale and value bar must have a range > 0 + * while logarithmic sliders without the value bar can have a range of >= 0. + */ +typedef enum PropertyScaleType { + /** Linear scale (default). */ + PROP_SCALE_LINEAR = 0, + /** + * Logarithmic scale + * - Maximum range: `0 <= x < inf` + */ + PROP_SCALE_LOG = 1, + /** + * Cubic scale. + * - Maximum range: `-inf < x < inf` + */ + PROP_SCALE_CUBIC = 2, +} PropertyScaleType; + #define RNA_SUBTYPE_UNIT(subtype) ((subtype)&0x00FF0000) #define RNA_SUBTYPE_VALUE(subtype) ((subtype) & ~0x00FF0000) #define RNA_SUBTYPE_UNIT_VALUE(subtype) ((subtype) >> 16) diff --git a/source/blender/makesrna/intern/CMakeLists.txt b/source/blender/makesrna/intern/CMakeLists.txt index 4fafa356879..95b7b7e5406 100644 --- a/source/blender/makesrna/intern/CMakeLists.txt +++ b/source/blender/makesrna/intern/CMakeLists.txt @@ -99,7 +99,7 @@ set(DEFSRC ) if(WITH_EXPERIMENTAL_FEATURES) - add_definitions(-DWITH_GEOMETRY_NODES) + add_definitions(-DWITH_SIMULATION_DATABLOCK) add_definitions(-DWITH_POINT_CLOUD) add_definitions(-DWITH_HAIR_NODES) list(APPEND DEFSRC diff --git a/source/blender/makesrna/intern/makesrna.c b/source/blender/makesrna/intern/makesrna.c index b20d9218316..efe12114d55 100644 --- a/source/blender/makesrna/intern/makesrna.c +++ b/source/blender/makesrna/intern/makesrna.c @@ -684,6 +684,29 @@ static char *rna_def_property_get_func( } } } + + /* Check log scale sliders for negative range. */ + if (prop->type == PROP_FLOAT) { + FloatPropertyRNA *fprop = (FloatPropertyRNA *)prop; + /* NOTE: UI_BTYPE_NUM_SLIDER can't have a softmin of zero. */ + if ((fprop->ui_scale_type == PROP_SCALE_LOG) && (fprop->hardmin < 0 || fprop->softmin < 0)) { + CLOG_ERROR( + &LOG, "\"%s.%s\", range for log scale < 0.", srna->identifier, prop->identifier); + DefRNA.error = true; + return NULL; + } + } + if (prop->type == PROP_INT) { + IntPropertyRNA *iprop = (IntPropertyRNA *)prop; + /* Only UI_BTYPE_NUM_SLIDER is implemented and that one can't have a softmin of zero. */ + if ((iprop->ui_scale_type == PROP_SCALE_LOG) && + (iprop->hardmin <= 0 || iprop->softmin <= 0)) { + CLOG_ERROR( + &LOG, "\"%s.%s\", range for log scale <= 0.", srna->identifier, prop->identifier); + DefRNA.error = true; + return NULL; + } + } } func = rna_alloc_function_name(srna->identifier, rna_safe_id(prop->identifier), "get"); @@ -3935,6 +3958,8 @@ static void rna_generate_property(FILE *f, StructRNA *srna, const char *nest, Pr rna_function_string(iprop->getarray_ex), rna_function_string(iprop->setarray_ex), rna_function_string(iprop->range_ex)); + rna_int_print(f, iprop->ui_scale_type); + fprintf(f, ", "); rna_int_print(f, iprop->softmin); fprintf(f, ", "); rna_int_print(f, iprop->softmax); @@ -3969,6 +3994,8 @@ static void rna_generate_property(FILE *f, StructRNA *srna, const char *nest, Pr rna_function_string(fprop->getarray_ex), rna_function_string(fprop->setarray_ex), rna_function_string(fprop->range_ex)); + rna_float_print(f, fprop->ui_scale_type); + fprintf(f, ", "); rna_float_print(f, fprop->softmin); fprintf(f, ", "); rna_float_print(f, fprop->softmax); @@ -4341,7 +4368,7 @@ static RNAProcessItem PROCESS_ITEMS[] = { {"rna_screen.c", NULL, RNA_def_screen}, {"rna_sculpt_paint.c", NULL, RNA_def_sculpt_paint}, {"rna_sequencer.c", "rna_sequencer_api.c", RNA_def_sequencer}, -#ifdef WITH_GEOMETRY_NODES +#ifdef WITH_SIMULATION_DATABLOCK {"rna_simulation.c", NULL, RNA_def_simulation}, #endif {"rna_space.c", "rna_space_api.c", RNA_def_space}, diff --git a/source/blender/makesrna/intern/rna_ID.c b/source/blender/makesrna/intern/rna_ID.c index 4fe19493614..5c0691f0320 100644 --- a/source/blender/makesrna/intern/rna_ID.c +++ b/source/blender/makesrna/intern/rna_ID.c @@ -346,7 +346,7 @@ short RNA_type_to_ID_code(const StructRNA *type) if (base_type == &RNA_Screen) { return ID_SCR; } -# ifdef WITH_GEOMETRY_NODES +# ifdef WITH_SIMULATION_DATABLOCK if (base_type == &RNA_Simulation) { return ID_SIM; } @@ -454,7 +454,7 @@ StructRNA *ID_code_to_RNA_type(short idcode) case ID_SCR: return &RNA_Screen; case ID_SIM: -# ifdef WITH_GEOMETRY_NODES +# ifdef WITH_SIMULATION_DATABLOCK return &RNA_Simulation; # else return &RNA_ID; diff --git a/source/blender/makesrna/intern/rna_access.c b/source/blender/makesrna/intern/rna_access.c index 9b57096ec19..150a455f1c7 100644 --- a/source/blender/makesrna/intern/rna_access.c +++ b/source/blender/makesrna/intern/rna_access.c @@ -1189,6 +1189,24 @@ PropertyUnit RNA_property_unit(PropertyRNA *prop) return RNA_SUBTYPE_UNIT(RNA_property_subtype(prop)); } +PropertyScaleType RNA_property_ui_scale(PropertyRNA *prop) +{ + PropertyRNA *rna_prop = rna_ensure_property(prop); + + switch (rna_prop->type) { + case PROP_INT: { + IntPropertyRNA *iprop = (IntPropertyRNA *)rna_prop; + return iprop->ui_scale_type; + } + case PROP_FLOAT: { + FloatPropertyRNA *fprop = (FloatPropertyRNA *)rna_prop; + return fprop->ui_scale_type; + } + default: + return PROP_SCALE_LINEAR; + } +} + int RNA_property_flag(PropertyRNA *prop) { return rna_ensure_property(prop)->flag; diff --git a/source/blender/makesrna/intern/rna_brush.c b/source/blender/makesrna/intern/rna_brush.c index 044e6ca2da1..7e1d513502c 100644 --- a/source/blender/makesrna/intern/rna_brush.c +++ b/source/blender/makesrna/intern/rna_brush.c @@ -1618,7 +1618,7 @@ static void rna_def_gpencil_options(BlenderRNA *brna) RNA_def_parameter_clear_flags(prop, PROP_ANIMATABLE, 0); /* Number of pixels to dilate fill area. */ - prop = RNA_def_property(srna, "dilate_pixels", PROP_INT, PROP_NONE); + prop = RNA_def_property(srna, "dilate", PROP_INT, PROP_PIXEL); RNA_def_property_int_sdna(prop, NULL, "dilate_pixels"); RNA_def_property_range(prop, 0, 20); RNA_def_property_int_default(prop, 1); diff --git a/source/blender/makesrna/intern/rna_cloth.c b/source/blender/makesrna/intern/rna_cloth.c index 2bc00dd5af5..9e57368f8f9 100644 --- a/source/blender/makesrna/intern/rna_cloth.c +++ b/source/blender/makesrna/intern/rna_cloth.c @@ -652,6 +652,7 @@ static void rna_def_cloth_sim_settings(BlenderRNA *brna) prop = RNA_def_property(srna, "mass", PROP_FLOAT, PROP_UNIT_MASS); RNA_def_property_range(prop, 0.0f, FLT_MAX); RNA_def_property_ui_text(prop, "Vertex Mass", "The mass of each vertex on the cloth material"); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); RNA_def_property_update(prop, 0, "rna_cloth_update"); prop = RNA_def_property(srna, "vertex_group_mass", PROP_STRING, PROP_NONE); diff --git a/source/blender/makesrna/intern/rna_define.c b/source/blender/makesrna/intern/rna_define.c index 2fdf7e5eaa7..9b9d561603b 100644 --- a/source/blender/makesrna/intern/rna_define.c +++ b/source/blender/makesrna/intern/rna_define.c @@ -1754,6 +1754,28 @@ void RNA_def_property_ui_range( } } +void RNA_def_property_ui_scale_type(PropertyRNA *prop, PropertyScaleType ui_scale_type) +{ + StructRNA *srna = DefRNA.laststruct; + + switch (prop->type) { + case PROP_INT: { + IntPropertyRNA *iprop = (IntPropertyRNA *)prop; + iprop->ui_scale_type = ui_scale_type; + break; + } + case PROP_FLOAT: { + FloatPropertyRNA *fprop = (FloatPropertyRNA *)prop; + fprop->ui_scale_type = ui_scale_type; + break; + } + default: + CLOG_ERROR(&LOG, "\"%s.%s\", invalid type for scale.", srna->identifier, prop->identifier); + DefRNA.error = true; + break; + } +} + void RNA_def_property_range(PropertyRNA *prop, double min, double max) { StructRNA *srna = DefRNA.laststruct; diff --git a/source/blender/makesrna/intern/rna_gpencil.c b/source/blender/makesrna/intern/rna_gpencil.c index 5b3fa1f6aa2..91e13a4bee3 100644 --- a/source/blender/makesrna/intern/rna_gpencil.c +++ b/source/blender/makesrna/intern/rna_gpencil.c @@ -1037,7 +1037,7 @@ static bGPDframe *rna_GPencil_frame_copy(bGPDlayer *layer, bGPDframe *src) static bGPDlayer *rna_GPencil_layer_new(bGPdata *gpd, const char *name, bool setactive) { - bGPDlayer *gpl = BKE_gpencil_layer_addnew(gpd, name, setactive != 0); + bGPDlayer *gpl = BKE_gpencil_layer_addnew(gpd, name, setactive != 0, false); WM_main_add_notifier(NC_GPENCIL | ND_DATA | NA_EDITED, NULL); diff --git a/source/blender/makesrna/intern/rna_gpencil_modifier.c b/source/blender/makesrna/intern/rna_gpencil_modifier.c index 2b4391fb822..85a3bd2192c 100644 --- a/source/blender/makesrna/intern/rna_gpencil_modifier.c +++ b/source/blender/makesrna/intern/rna_gpencil_modifier.c @@ -68,6 +68,11 @@ const EnumPropertyItem rna_enum_object_greasepencil_modifier_type_items[] = { ICON_MOD_BUILD, "Build", "Create duplication of strokes"}, + {eGpencilModifierType_Lineart, + "GP_LINEART", + ICON_MOD_EDGESPLIT, /* TODO: Use a proper icon. */ + "Line Art", + "Generate line art strokes from selected source"}, {eGpencilModifierType_Mirror, "GP_MIRROR", ICON_MOD_MIRROR, @@ -88,11 +93,6 @@ const EnumPropertyItem rna_enum_object_greasepencil_modifier_type_items[] = { ICON_MOD_SUBSURF, "Subdivide", "Subdivide stroke adding more control points"}, - {eGpencilModifierType_Lineart, - "GP_LINEART", - ICON_MOD_EDGESPLIT, /* TODO: Use a proper icon. */ - "Line Art", - "Generate line art strokes from selected source"}, {0, "", 0, N_("Deform"), ""}, {eGpencilModifierType_Armature, "GP_ARMATURE", diff --git a/source/blender/makesrna/intern/rna_internal.h b/source/blender/makesrna/intern/rna_internal.h index 95972dd444f..bfe9d4bb77c 100644 --- a/source/blender/makesrna/intern/rna_internal.h +++ b/source/blender/makesrna/intern/rna_internal.h @@ -476,7 +476,7 @@ void RNA_def_main_hairs(BlenderRNA *brna, PropertyRNA *cprop); void RNA_def_main_pointclouds(BlenderRNA *brna, PropertyRNA *cprop); #endif void RNA_def_main_volumes(BlenderRNA *brna, PropertyRNA *cprop); -#ifdef WITH_GEOMETRY_NODES +#ifdef WITH_SIMULATION_DATABLOCK void RNA_def_main_simulations(BlenderRNA *brna, PropertyRNA *cprop); #endif diff --git a/source/blender/makesrna/intern/rna_internal_types.h b/source/blender/makesrna/intern/rna_internal_types.h index 0c0260c889c..245730919b0 100644 --- a/source/blender/makesrna/intern/rna_internal_types.h +++ b/source/blender/makesrna/intern/rna_internal_types.h @@ -400,6 +400,7 @@ typedef struct IntPropertyRNA { PropIntArraySetFuncEx setarray_ex; PropIntRangeFuncEx range_ex; + PropertyScaleType ui_scale_type; int softmin, softmax; int hardmin, hardmax; int step; @@ -423,6 +424,7 @@ typedef struct FloatPropertyRNA { PropFloatArraySetFuncEx setarray_ex; PropFloatRangeFuncEx range_ex; + PropertyScaleType ui_scale_type; float softmin, softmax; float hardmin, hardmax; float step; diff --git a/source/blender/makesrna/intern/rna_key.c b/source/blender/makesrna/intern/rna_key.c index 3b9fc970072..a48727526c9 100644 --- a/source/blender/makesrna/intern/rna_key.c +++ b/source/blender/makesrna/intern/rna_key.c @@ -699,6 +699,16 @@ static void rna_Key_update_data(Main *bmain, Scene *UNUSED(scene), PointerRNA *p } } +static void rna_ShapeKey_update_minmax(Main *bmain, Scene *scene, PointerRNA *ptr) +{ + KeyBlock *data = (KeyBlock *)ptr->data; + if (IN_RANGE_INCL(data->curval, data->slidermin, data->slidermax)) { + return; + } + CLAMP(data->curval, data->slidermin, data->slidermax); + rna_Key_update_data(bmain, scene, ptr); +} + static KeyBlock *rna_ShapeKeyData_find_keyblock(Key *key, float *point) { KeyBlock *kb; @@ -955,6 +965,7 @@ static void rna_def_keyblock(BlenderRNA *brna) RNA_def_property_float_funcs( prop, NULL, "rna_ShapeKey_slider_min_set", "rna_ShapeKey_slider_min_range"); RNA_def_property_ui_text(prop, "Slider Min", "Minimum for slider"); + RNA_def_property_update(prop, 0, "rna_ShapeKey_update_minmax"); prop = RNA_def_property(srna, "slider_max", PROP_FLOAT, PROP_NONE); RNA_def_property_float_sdna(prop, NULL, "slidermax"); @@ -963,6 +974,7 @@ static void rna_def_keyblock(BlenderRNA *brna) RNA_def_property_float_funcs( prop, NULL, "rna_ShapeKey_slider_max_set", "rna_ShapeKey_slider_max_range"); RNA_def_property_ui_text(prop, "Slider Max", "Maximum for slider"); + RNA_def_property_update(prop, 0, "rna_ShapeKey_update_minmax"); prop = RNA_def_property(srna, "data", PROP_COLLECTION, PROP_NONE); RNA_def_property_collection_sdna(prop, NULL, "data", "totelem"); diff --git a/source/blender/makesrna/intern/rna_main.c b/source/blender/makesrna/intern/rna_main.c index c80f856dd6b..464abc6b543 100644 --- a/source/blender/makesrna/intern/rna_main.c +++ b/source/blender/makesrna/intern/rna_main.c @@ -134,7 +134,7 @@ RNA_MAIN_LISTBASE_FUNCS_DEF(pointclouds) RNA_MAIN_LISTBASE_FUNCS_DEF(scenes) RNA_MAIN_LISTBASE_FUNCS_DEF(screens) RNA_MAIN_LISTBASE_FUNCS_DEF(shapekeys) -# ifdef WITH_GEOMETRY_NODES +# ifdef WITH_SIMULATION_DATABLOCK RNA_MAIN_LISTBASE_FUNCS_DEF(simulations) # endif RNA_MAIN_LISTBASE_FUNCS_DEF(sounds) @@ -407,7 +407,7 @@ void RNA_def_main(BlenderRNA *brna) "Volumes", "Volume data-blocks", RNA_def_main_volumes}, -# ifdef WITH_GEOMETRY_NODES +# ifdef WITH_SIMULATION_DATABLOCK {"simulations", "Simulation", "rna_Main_simulations_begin", diff --git a/source/blender/makesrna/intern/rna_main_api.c b/source/blender/makesrna/intern/rna_main_api.c index 6c2fb649986..8e6ff961721 100644 --- a/source/blender/makesrna/intern/rna_main_api.c +++ b/source/blender/makesrna/intern/rna_main_api.c @@ -806,7 +806,7 @@ static Volume *rna_Main_volumes_new(Main *bmain, const char *name) return volume; } -# ifdef WITH_GEOMETRY_NODES +# ifdef WITH_SIMULATION_DATABLOCK static Simulation *rna_Main_simulations_new(Main *bmain, const char *name) { char safe_name[MAX_ID_NAME - 2]; @@ -870,7 +870,7 @@ RNA_MAIN_ID_TAG_FUNCS_DEF(hairs, hairs, ID_HA) RNA_MAIN_ID_TAG_FUNCS_DEF(pointclouds, pointclouds, ID_PT) # endif RNA_MAIN_ID_TAG_FUNCS_DEF(volumes, volumes, ID_VO) -# ifdef WITH_GEOMETRY_NODES +# ifdef WITH_SIMULATION_DATABLOCK RNA_MAIN_ID_TAG_FUNCS_DEF(simulations, simulations, ID_SIM) # endif @@ -2412,7 +2412,7 @@ void RNA_def_main_volumes(BlenderRNA *brna, PropertyRNA *cprop) RNA_def_parameter_flags(parm, 0, PARM_REQUIRED); } -# ifdef WITH_GEOMETRY_NODES +# ifdef WITH_SIMULATION_DATABLOCK void RNA_def_main_simulations(BlenderRNA *brna, PropertyRNA *cprop) { StructRNA *srna; diff --git a/source/blender/makesrna/intern/rna_modifier.c b/source/blender/makesrna/intern/rna_modifier.c index e3fb443951f..674e5845ccb 100644 --- a/source/blender/makesrna/intern/rna_modifier.c +++ b/source/blender/makesrna/intern/rna_modifier.c @@ -2781,7 +2781,8 @@ static void rna_def_modifier_boolean(BlenderRNA *brna) prop = RNA_def_property(srna, "double_threshold", PROP_FLOAT, PROP_DISTANCE); RNA_def_property_float_sdna(prop, NULL, "double_threshold"); RNA_def_property_range(prop, 0, 1.0f); - RNA_def_property_ui_range(prop, 0, 1, 0.0001, 6); + RNA_def_property_ui_range(prop, 0, 1, 1.0, 6); + RNA_def_property_ui_scale_type(prop, PROP_SCALE_LOG); RNA_def_property_ui_text( prop, "Overlap Threshold", "Threshold for checking overlapping geometry"); RNA_def_property_update(prop, 0, "rna_Modifier_update"); @@ -5530,6 +5531,7 @@ static void rna_def_modifier_remesh(BlenderRNA *brna) RNA_def_property_float_sdna(prop, NULL, "voxel_size"); RNA_def_property_range(prop, 0.0001f, FLT_MAX); RNA_def_property_ui_range(prop, 0.0001, 2, 0.1, 3); + RNA_def_property_ui_scale_type(prop, PROP_SCALE_LOG); RNA_def_property_ui_text(prop, "Voxel Size", "Size of the voxel in object space used for volume evaluation. Lower " diff --git a/source/blender/makesrna/intern/rna_nodetree.c b/source/blender/makesrna/intern/rna_nodetree.c index 128c65aecd9..49abb892acc 100644 --- a/source/blender/makesrna/intern/rna_nodetree.c +++ b/source/blender/makesrna/intern/rna_nodetree.c @@ -76,6 +76,8 @@ static const EnumPropertyItem node_socket_data_type_items[] = { {SOCK_IMAGE, "IMAGE", 0, "Image", ""}, {SOCK_GEOMETRY, "GEOMETRY", 0, "Geometry", ""}, {SOCK_COLLECTION, "COLLECTION", 0, "Collection", ""}, + {SOCK_TEXTURE, "TEXTURE", 0, "Texture", ""}, + {SOCK_MATERIAL, "MATERIAL", 0, "Material", ""}, {0, NULL, 0, NULL, NULL}, }; @@ -102,6 +104,8 @@ static const EnumPropertyItem node_socket_type_items[] = { {SOCK_IMAGE, "IMAGE", 0, "Image", ""}, {SOCK_GEOMETRY, "GEOMETRY", 0, "Geometry", ""}, {SOCK_COLLECTION, "COLLECTION", 0, "Collection", ""}, + {SOCK_TEXTURE, "TEXTURE", 0, "Texture", ""}, + {SOCK_MATERIAL, "MATERIAL", 0, "Material", ""}, {0, NULL, 0, NULL, NULL}, }; @@ -2182,6 +2186,17 @@ static const EnumPropertyItem *rna_GeometryNodeAttributeMapRange_type_itemf( return itemf_function_check(rna_enum_attribute_type_items, attribute_map_range_type_supported); } +static bool attribute_curve_map_type_supported(const EnumPropertyItem *item) +{ + return ELEM(item->value, CD_PROP_FLOAT, CD_PROP_FLOAT3, CD_PROP_COLOR); +} +static const EnumPropertyItem *rna_GeometryNodeAttributeCurveMap_type_itemf( + bContext *UNUSED(C), PointerRNA *UNUSED(ptr), PropertyRNA *UNUSED(prop), bool *r_free) +{ + *r_free = true; + return itemf_function_check(rna_enum_attribute_type_items, attribute_curve_map_type_supported); +} + static StructRNA *rna_ShaderNode_register(Main *bmain, ReportList *reports, void *data, @@ -4440,6 +4455,13 @@ void rna_ShaderNodePointDensity_density_minmax(bNode *self, RE_point_density_minmax(depsgraph, pd, r_min, r_max); } +bool rna_NodeSocketMaterial_default_value_poll(PointerRNA *UNUSED(ptr), PointerRNA value) +{ + /* Do not show grease pencil materials for now. */ + Material *ma = (Material *)value.data; + return ma->gp_style == NULL; +} + #else static const EnumPropertyItem prop_image_layer_items[] = { @@ -9262,6 +9284,85 @@ static void def_geo_attribute_color_ramp(StructRNA *srna) RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); } +static void def_geo_attribute_curve_map(StructRNA *srna) +{ + PropertyRNA *prop; + + RNA_def_struct_sdna_from(srna, "NodeAttributeCurveMap", "storage"); + + prop = RNA_def_property(srna, "data_type", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, NULL, "data_type"); + RNA_def_property_enum_items(prop, rna_enum_attribute_type_items); + RNA_def_property_enum_funcs(prop, NULL, NULL, "rna_GeometryNodeAttributeCurveMap_type_itemf"); + RNA_def_property_ui_text(prop, "Data Type", ""); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); + + prop = RNA_def_property(srna, "curve_vec", PROP_POINTER, PROP_NONE); + RNA_def_property_struct_type(prop, "CurveMapping"); + RNA_def_property_ui_text(prop, "Mapping", ""); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); + + prop = RNA_def_property(srna, "curve_rgb", PROP_POINTER, PROP_NONE); + RNA_def_property_struct_type(prop, "CurveMapping"); + RNA_def_property_ui_text(prop, "Mapping", ""); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); +} + +static void def_geo_attribute_vector_rotate(StructRNA *srna) +{ + static const EnumPropertyItem rotate_mode_items[] = { + {GEO_NODE_VECTOR_ROTATE_TYPE_AXIS, + "AXIS_ANGLE", + 0, + "Axis Angle", + "Rotate a point using axis angle"}, + {GEO_NODE_VECTOR_ROTATE_TYPE_AXIS_X, "X_AXIS", 0, "X Axis", "Rotate a point using X axis"}, + {GEO_NODE_VECTOR_ROTATE_TYPE_AXIS_Y, "Y_AXIS", 0, "Y Axis", "Rotate a point using Y axis"}, + {GEO_NODE_VECTOR_ROTATE_TYPE_AXIS_Z, "Z_AXIS", 0, "Z Axis", "Rotate a point using Z axis"}, + {GEO_NODE_VECTOR_ROTATE_TYPE_EULER_XYZ, + "EULER_XYZ", + 0, + "Euler", + "Rotate a point using XYZ order"}, + {0, NULL, 0, NULL, NULL}, + }; + + PropertyRNA *prop; + + RNA_def_struct_sdna_from(srna, "NodeAttributeVectorRotate", "storage"); + + prop = RNA_def_property(srna, "rotation_mode", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, NULL, "mode"); + RNA_def_property_enum_items(prop, rotate_mode_items); + RNA_def_property_ui_text(prop, "Mode", "Type of rotation"); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_ShaderNode_socket_update"); + + prop = RNA_def_property(srna, "input_type_vector", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, rna_node_geometry_attribute_input_type_items_vector); + RNA_def_property_ui_text(prop, "Input Type Vector", ""); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); + + prop = RNA_def_property(srna, "input_type_center", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, rna_node_geometry_attribute_input_type_items_vector); + RNA_def_property_ui_text(prop, "Input Type Center", ""); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); + + prop = RNA_def_property(srna, "input_type_axis", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, rna_node_geometry_attribute_input_type_items_vector); + RNA_def_property_ui_text(prop, "Input Type Axis", ""); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); + + prop = RNA_def_property(srna, "input_type_angle", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, rna_node_geometry_attribute_input_type_items_float); + RNA_def_property_ui_text(prop, "Input Type Angle", ""); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); + + prop = RNA_def_property(srna, "input_type_rotation", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, rna_node_geometry_attribute_input_type_items_vector); + RNA_def_property_ui_text(prop, "Input Type Rotation", ""); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); +} + static void def_geo_point_rotate(StructRNA *srna) { static const EnumPropertyItem type_items[] = { @@ -9416,19 +9517,6 @@ static void def_geo_point_translate(StructRNA *srna) RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); } -static void def_geo_attribute_sample_texture(StructRNA *srna) -{ - PropertyRNA *prop; - - prop = RNA_def_property(srna, "texture", PROP_POINTER, PROP_NONE); - RNA_def_property_pointer_sdna(prop, NULL, "id"); - RNA_def_property_struct_type(prop, "Texture"); - RNA_def_property_flag(prop, PROP_EDITABLE | PROP_ID_REFCOUNT); - RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); - RNA_def_property_ui_text(prop, "Texture", "Texture to sample values from"); - RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update_relations"); -} - static void def_geo_object_info(StructRNA *srna) { PropertyRNA *prop; @@ -9709,6 +9797,33 @@ static void def_geo_switch(StructRNA *srna) RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); } +static void def_geo_curve_resample(StructRNA *srna) +{ + PropertyRNA *prop; + + static EnumPropertyItem mode_items[] = { + {GEO_NODE_CURVE_SAMPLE_COUNT, + "COUNT", + 0, + "Count", + "Sample the specified number of points along each spline"}, + {GEO_NODE_CURVE_SAMPLE_LENGTH, + "LENGTH", + 0, + "Length", + "Calculate the number of samples by splitting each spline into segments with the specified " + "length"}, + {0, NULL, 0, NULL, NULL}, + }; + + RNA_def_struct_sdna_from(srna, "NodeGeometryCurveResample", "storage"); + + prop = RNA_def_property(srna, "mode", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, mode_items); + RNA_def_property_ui_text(prop, "Mode", "How to specify the amount of samples"); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); +} + static void def_geo_attribute_transfer(StructRNA *srna) { static EnumPropertyItem mapping_items[] = { @@ -9743,6 +9858,19 @@ static void def_geo_attribute_transfer(StructRNA *srna) RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); } +static void def_geo_input_material(StructRNA *srna) +{ + PropertyRNA *prop; + + prop = RNA_def_property(srna, "material", PROP_POINTER, PROP_NONE); + RNA_def_property_pointer_sdna(prop, NULL, "id"); + RNA_def_property_struct_type(prop, "Material"); + RNA_def_property_flag(prop, PROP_EDITABLE); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); + RNA_def_property_ui_text(prop, "Material", ""); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); +} + /* -------------------------------------------------------------------------- */ static void rna_def_shader_node(BlenderRNA *brna) @@ -10515,6 +10643,80 @@ static void rna_def_node_socket_collection(BlenderRNA *brna, RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeSocketInterface_update"); } +static void rna_def_node_socket_texture(BlenderRNA *brna, + const char *identifier, + const char *interface_idname) +{ + StructRNA *srna; + PropertyRNA *prop; + + srna = RNA_def_struct(brna, identifier, "NodeSocketStandard"); + RNA_def_struct_ui_text(srna, "Texture Node Socket", "Texture socket of a node"); + RNA_def_struct_sdna(srna, "bNodeSocket"); + + RNA_def_struct_sdna_from(srna, "bNodeSocketValueTexture", "default_value"); + + prop = RNA_def_property(srna, "default_value", PROP_POINTER, PROP_NONE); + RNA_def_property_pointer_sdna(prop, NULL, "value"); + RNA_def_property_struct_type(prop, "Texture"); + RNA_def_property_ui_text(prop, "Default Value", "Input value used for unconnected socket"); + RNA_def_property_update( + prop, NC_NODE | NA_EDITED, "rna_NodeSocketStandard_value_and_relation_update"); + RNA_def_property_flag(prop, PROP_EDITABLE | PROP_ID_REFCOUNT | PROP_CONTEXT_UPDATE); + + /* socket interface */ + srna = RNA_def_struct(brna, interface_idname, "NodeSocketInterfaceStandard"); + RNA_def_struct_ui_text(srna, "Texture Node Socket Interface", "Texture socket of a node"); + RNA_def_struct_sdna(srna, "bNodeSocket"); + + RNA_def_struct_sdna_from(srna, "bNodeSocketValueTexture", "default_value"); + + prop = RNA_def_property(srna, "default_value", PROP_POINTER, PROP_NONE); + RNA_def_property_pointer_sdna(prop, NULL, "value"); + RNA_def_property_struct_type(prop, "Texture"); + RNA_def_property_ui_text(prop, "Default Value", "Input value used for unconnected socket"); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeSocketInterface_update"); +} + +static void rna_def_node_socket_material(BlenderRNA *brna, + const char *identifier, + const char *interface_idname) +{ + StructRNA *srna; + PropertyRNA *prop; + + srna = RNA_def_struct(brna, identifier, "NodeSocketStandard"); + RNA_def_struct_ui_text(srna, "Material Node Socket", "Material socket of a node"); + RNA_def_struct_sdna(srna, "bNodeSocket"); + + RNA_def_struct_sdna_from(srna, "bNodeSocketValueMaterial", "default_value"); + + prop = RNA_def_property(srna, "default_value", PROP_POINTER, PROP_NONE); + RNA_def_property_pointer_sdna(prop, NULL, "value"); + RNA_def_property_struct_type(prop, "Material"); + RNA_def_property_pointer_funcs( + prop, NULL, NULL, NULL, "rna_NodeSocketMaterial_default_value_poll"); + RNA_def_property_ui_text(prop, "Default Value", "Input value used for unconnected socket"); + RNA_def_property_update( + prop, NC_NODE | NA_EDITED, "rna_NodeSocketStandard_value_and_relation_update"); + RNA_def_property_flag(prop, PROP_EDITABLE | PROP_ID_REFCOUNT | PROP_CONTEXT_UPDATE); + + /* socket interface */ + srna = RNA_def_struct(brna, interface_idname, "NodeSocketInterfaceStandard"); + RNA_def_struct_ui_text(srna, "Material Node Socket Interface", "Material socket of a node"); + RNA_def_struct_sdna(srna, "bNodeSocket"); + + RNA_def_struct_sdna_from(srna, "bNodeSocketValueMaterial", "default_value"); + + prop = RNA_def_property(srna, "default_value", PROP_POINTER, PROP_NONE); + RNA_def_property_pointer_sdna(prop, NULL, "value"); + RNA_def_property_struct_type(prop, "Material"); + RNA_def_property_pointer_funcs( + prop, NULL, NULL, NULL, "rna_NodeSocketMaterial_default_value_poll"); + RNA_def_property_ui_text(prop, "Default Value", "Input value used for unconnected socket"); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeSocketInterface_update"); +} + static void rna_def_node_socket_standard_types(BlenderRNA *brna) { /* XXX Workaround: Registered functions are not exposed in python by bpy, @@ -10659,6 +10861,10 @@ static void rna_def_node_socket_standard_types(BlenderRNA *brna) rna_def_node_socket_geometry(brna, "NodeSocketGeometry", "NodeSocketInterfaceGeometry"); rna_def_node_socket_collection(brna, "NodeSocketCollection", "NodeSocketInterfaceCollection"); + + rna_def_node_socket_texture(brna, "NodeSocketTexture", "NodeSocketInterfaceTexture"); + + rna_def_node_socket_material(brna, "NodeSocketMaterial", "NodeSocketInterfaceMaterial"); } static void rna_def_internal_node(BlenderRNA *brna) diff --git a/source/blender/makesrna/intern/rna_object.c b/source/blender/makesrna/intern/rna_object.c index 3edfd5c44f4..1dc7519258b 100644 --- a/source/blender/makesrna/intern/rna_object.c +++ b/source/blender/makesrna/intern/rna_object.c @@ -62,6 +62,8 @@ #include "WM_api.h" #include "WM_types.h" +#include "DEG_depsgraph_query.h" + const EnumPropertyItem rna_enum_object_mode_items[] = { {OB_MODE_OBJECT, "OBJECT", ICON_OBJECT_DATAMODE, "Object Mode", ""}, {OB_MODE_EDIT, "EDIT", ICON_EDITMODE_HLT, "Edit Mode", ""}, @@ -1253,10 +1255,16 @@ static int rna_Object_rotation_4d_editable(PointerRNA *ptr, int index) return PROP_EDITABLE; } +static int rna_MaterialSlot_index(PointerRNA *ptr) +{ + /* There is an offset of one, so that `ptr->data` is not null. */ + return POINTER_AS_INT(ptr->data) - 1; +} + static int rna_MaterialSlot_material_editable(PointerRNA *ptr, const char **UNUSED(r_info)) { Object *ob = (Object *)ptr->owner_id; - const int index = (Material **)ptr->data - ob->mat; + const int index = rna_MaterialSlot_index(ptr); bool is_editable; if ((ob->matbits == NULL) || ob->matbits[index]) { @@ -1273,9 +1281,14 @@ static PointerRNA rna_MaterialSlot_material_get(PointerRNA *ptr) { Object *ob = (Object *)ptr->owner_id; Material *ma; - const int index = (Material **)ptr->data - ob->mat; + const int index = rna_MaterialSlot_index(ptr); - ma = BKE_object_material_get(ob, index + 1); + if (DEG_is_evaluated_object(ob)) { + ma = BKE_object_material_get_eval(ob, index + 1); + } + else { + ma = BKE_object_material_get(ob, index + 1); + } return rna_pointer_inherit_refine(ptr, &RNA_Material, ma); } @@ -1284,7 +1297,7 @@ static void rna_MaterialSlot_material_set(PointerRNA *ptr, struct ReportList *UNUSED(reports)) { Object *ob = (Object *)ptr->owner_id; - int index = (Material **)ptr->data - ob->mat; + int index = rna_MaterialSlot_index(ptr); BLI_assert(BKE_id_is_in_global_main(&ob->id)); BLI_assert(BKE_id_is_in_global_main(value.data)); @@ -1309,15 +1322,17 @@ static bool rna_MaterialSlot_material_poll(PointerRNA *ptr, PointerRNA value) static int rna_MaterialSlot_link_get(PointerRNA *ptr) { Object *ob = (Object *)ptr->owner_id; - int index = (Material **)ptr->data - ob->mat; - - return ob->matbits[index] != 0; + int index = rna_MaterialSlot_index(ptr); + if (index < ob->totcol) { + return ob->matbits[index] != 0; + } + return false; } static void rna_MaterialSlot_link_set(PointerRNA *ptr, int value) { Object *ob = (Object *)ptr->owner_id; - int index = (Material **)ptr->data - ob->mat; + int index = rna_MaterialSlot_index(ptr); if (value) { ob->matbits[index] = 1; @@ -1335,7 +1350,7 @@ static int rna_MaterialSlot_name_length(PointerRNA *ptr) { Object *ob = (Object *)ptr->owner_id; Material *ma; - int index = (Material **)ptr->data - ob->mat; + int index = rna_MaterialSlot_index(ptr); ma = BKE_object_material_get(ob, index + 1); @@ -1350,7 +1365,7 @@ static void rna_MaterialSlot_name_get(PointerRNA *ptr, char *str) { Object *ob = (Object *)ptr->owner_id; Material *ma; - int index = (Material **)ptr->data - ob->mat; + int index = rna_MaterialSlot_index(ptr); ma = BKE_object_material_get(ob, index + 1); @@ -1373,10 +1388,49 @@ static void rna_MaterialSlot_update(Main *bmain, Scene *scene, PointerRNA *ptr) static char *rna_MaterialSlot_path(PointerRNA *ptr) { + int index = rna_MaterialSlot_index(ptr); + return BLI_sprintfN("material_slots[%d]", index); +} + +static int rna_Object_material_slots_length(PointerRNA *ptr) +{ Object *ob = (Object *)ptr->owner_id; - int index = (Material **)ptr->data - ob->mat; + if (DEG_is_evaluated_object(ob)) { + return BKE_object_material_count_eval(ob); + } + else { + return ob->totcol; + } +} - return BLI_sprintfN("material_slots[%d]", index); +static void rna_Object_material_slots_begin(CollectionPropertyIterator *iter, PointerRNA *ptr) +{ + const int length = rna_Object_material_slots_length(ptr); + iter->internal.count.item = 0; + iter->internal.count.ptr = ptr->owner_id; + iter->valid = length > 0; +} + +static void rna_Object_material_slots_next(CollectionPropertyIterator *iter) +{ + const int length = rna_Object_material_slots_length(&iter->ptr); + iter->internal.count.item++; + iter->valid = iter->internal.count.item < length; +} + +static PointerRNA rna_Object_material_slots_get(CollectionPropertyIterator *iter) +{ + PointerRNA ptr; + RNA_pointer_create((ID *)iter->internal.count.ptr, + &RNA_MaterialSlot, + /* Add one, so that `ptr->data` is not null. */ + POINTER_FROM_INT(iter->internal.count.item + 1), + &ptr); + return ptr; +} + +static void rna_Object_material_slots_end(CollectionPropertyIterator *UNUSED(iter)) +{ } static PointerRNA rna_Object_display_get(PointerRNA *ptr) @@ -2958,12 +3012,18 @@ static void rna_def_object(BlenderRNA *brna) /* materials */ prop = RNA_def_property(srna, "material_slots", PROP_COLLECTION, PROP_NONE); - RNA_def_property_collection_sdna(prop, NULL, "mat", "totcol"); RNA_def_property_struct_type(prop, "MaterialSlot"); RNA_def_property_override_flag(prop, PROPOVERRIDE_NO_PROP_NAME); - /* don't dereference pointer! */ - RNA_def_property_collection_funcs( - prop, NULL, NULL, NULL, "rna_iterator_array_get", NULL, NULL, NULL, NULL); + /* Don't dereference the material slot pointer, it is the slot index encoded in a pointer. */ + RNA_def_property_collection_funcs(prop, + "rna_Object_material_slots_begin", + "rna_Object_material_slots_next", + "rna_Object_material_slots_end", + "rna_Object_material_slots_get", + "rna_Object_material_slots_length", + NULL, + NULL, + NULL); RNA_def_property_ui_text(prop, "Material Slots", "Material slots in the object"); prop = RNA_def_property(srna, "active_material", PROP_POINTER, PROP_NONE); diff --git a/source/blender/makesrna/intern/rna_pose.c b/source/blender/makesrna/intern/rna_pose.c index ba65e42895c..b8bb4f58dcd 100644 --- a/source/blender/makesrna/intern/rna_pose.c +++ b/source/blender/makesrna/intern/rna_pose.c @@ -1359,12 +1359,27 @@ static void rna_def_pose_channel(BlenderRNA *brna) RNA_def_property_editable_func(prop, "rna_PoseChannel_proxy_editable"); RNA_def_property_update(prop, NC_OBJECT | ND_POSE, "rna_Pose_dependency_update"); - prop = RNA_def_property(srna, "custom_shape_scale", PROP_FLOAT, PROP_NONE); - RNA_def_property_float_sdna(prop, NULL, "custom_scale"); - RNA_def_property_range(prop, 0.0f, 1000.0f); + prop = RNA_def_property(srna, "custom_shape_scale_xyz", PROP_FLOAT, PROP_XYZ); + RNA_def_property_float_sdna(prop, NULL, "custom_scale_xyz"); + RNA_def_property_flag(prop, PROP_PROPORTIONAL); + RNA_def_property_float_array_default(prop, rna_default_scale_3d); RNA_def_property_ui_text(prop, "Custom Shape Scale", "Adjust the size of the custom shape"); RNA_def_property_update(prop, NC_OBJECT | ND_POSE, "rna_Pose_update"); + prop = RNA_def_property(srna, "custom_shape_translation", PROP_FLOAT, PROP_XYZ); + RNA_def_property_float_sdna(prop, NULL, "custom_translation"); + RNA_def_property_flag(prop, PROP_PROPORTIONAL); + RNA_def_property_ui_text( + prop, "Custom Shape Translation", "Adjust the location of the custom shape"); + RNA_def_property_ui_range(prop, -FLT_MAX, FLT_MAX, 1, RNA_TRANSLATION_PREC_DEFAULT); + RNA_def_property_update(prop, NC_OBJECT | ND_POSE, "rna_Pose_update"); + + prop = RNA_def_property(srna, "custom_shape_rotation_euler", PROP_FLOAT, PROP_EULER); + RNA_def_property_float_sdna(prop, NULL, "custom_rotation_euler"); + RNA_def_property_ui_text( + prop, "Custom Shape Rotation", "Adjust the rotation of the custom shape"); + RNA_def_property_update(prop, NC_OBJECT | ND_POSE, "rna_Pose_update"); + prop = RNA_def_property(srna, "use_custom_shape_bone_size", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_negative_sdna(prop, NULL, "drawflag", PCHAN_DRAW_NO_CUSTOM_BONE_SIZE); RNA_def_property_ui_text( diff --git a/source/blender/makesrna/intern/rna_sculpt_paint.c b/source/blender/makesrna/intern/rna_sculpt_paint.c index 4481555b931..81acedb76f2 100644 --- a/source/blender/makesrna/intern/rna_sculpt_paint.c +++ b/source/blender/makesrna/intern/rna_sculpt_paint.c @@ -786,7 +786,8 @@ static void rna_def_sculpt(BlenderRNA *brna) RNA_def_property_update(prop, NC_OBJECT | ND_DRAW, "rna_Sculpt_ShowMask_update"); prop = RNA_def_property(srna, "detail_size", PROP_FLOAT, PROP_PIXEL); - RNA_def_property_ui_range(prop, 0.5, 40.0, 10, 2); + RNA_def_property_ui_range(prop, 0.5, 40.0, 0.1, 2); + RNA_def_property_ui_scale_type(prop, PROP_SCALE_CUBIC); RNA_def_property_ui_text( prop, "Detail Size", "Maximum edge length for dynamic topology sculpting (in pixels)"); RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL); diff --git a/source/blender/makesrna/intern/rna_sequencer.c b/source/blender/makesrna/intern/rna_sequencer.c index b2e399d41f5..d6b2e61eb6d 100644 --- a/source/blender/makesrna/intern/rna_sequencer.c +++ b/source/blender/makesrna/intern/rna_sequencer.c @@ -293,7 +293,7 @@ static void do_sequence_frame_change_update(Scene *scene, Sequence *seq) if (SEQ_transform_test_overlap(seqbase, seq)) { SEQ_transform_seqbase_shuffle(seqbase, seq, scene); /* XXX - BROKEN!, uses context seqbasep */ } - SEQ_sort(scene); + SEQ_sort(seqbase); } /* A simple wrapper around above func, directly usable as prop update func. @@ -476,7 +476,7 @@ static void rna_Sequence_channel_set(PointerRNA *ptr, int value) /* XXX - BROKEN!, uses context seqbasep */ SEQ_transform_seqbase_shuffle_ex(seqbase, seq, scene, channel_delta); } - SEQ_sort(scene); + SEQ_sort(seqbase); SEQ_relations_invalidate_cache_composite(scene, seq); } diff --git a/source/blender/modifiers/CMakeLists.txt b/source/blender/modifiers/CMakeLists.txt index 6ac2629c006..91327b97fe4 100644 --- a/source/blender/modifiers/CMakeLists.txt +++ b/source/blender/modifiers/CMakeLists.txt @@ -193,6 +193,18 @@ if(WITH_GMP) ) endif() +if(WITH_TBB) + add_definitions(-DWITH_TBB) + + list(APPEND INC_SYS + ${TBB_INCLUDE_DIRS} + ) + + list(APPEND LIB + ${TBB_LIBRARIES} + ) +endif() + if(WITH_OPENVDB) list(APPEND INC ../../../intern/openvdb @@ -208,7 +220,7 @@ if(WITH_OPENVDB) endif() if(WITH_EXPERIMENTAL_FEATURES) - add_definitions(-DWITH_GEOMETRY_NODES) + add_definitions(-DWITH_SIMULATION_DATABLOCK) add_definitions(-DWITH_POINT_CLOUD) add_definitions(-DWITH_HAIR_NODES) endif() diff --git a/source/blender/modifiers/intern/MOD_nodes.cc b/source/blender/modifiers/intern/MOD_nodes.cc index 71a47a24f54..a19e078957f 100644 --- a/source/blender/modifiers/intern/MOD_nodes.cc +++ b/source/blender/modifiers/intern/MOD_nodes.cc @@ -94,9 +94,6 @@ using blender::Span; using blender::StringRef; using blender::StringRefNull; using blender::Vector; -using blender::bke::PersistentCollectionHandle; -using blender::bke::PersistentDataHandleMap; -using blender::bke::PersistentObjectHandle; using blender::fn::GMutablePointer; using blender::fn::GPointer; using blender::nodes::GeoNodeExecParams; @@ -285,9 +282,7 @@ struct SocketPropertyType { IDProperty *(*create_default_ui_prop)(const bNodeSocket &socket, const char *name); PropertyType (*rna_subtype_get)(const bNodeSocket &socket); bool (*is_correct_type)(const IDProperty &property); - void (*init_cpp_value)(const IDProperty &property, - const PersistentDataHandleMap &handles, - void *r_value); + void (*init_cpp_value)(const IDProperty &property, void *r_value); }; static IDProperty *socket_add_property(IDProperty *settings_prop_group, @@ -380,9 +375,7 @@ static const SocketPropertyType *get_socket_property_type(const bNodeSocket &bso return (PropertyType)((bNodeSocketValueFloat *)socket.default_value)->subtype; }, [](const IDProperty &property) { return ELEM(property.type, IDP_FLOAT, IDP_DOUBLE); }, - [](const IDProperty &property, - const PersistentDataHandleMap &UNUSED(handles), - void *r_value) { + [](const IDProperty &property, void *r_value) { if (property.type == IDP_FLOAT) { *(float *)r_value = IDP_Float(&property); } @@ -423,9 +416,7 @@ static const SocketPropertyType *get_socket_property_type(const bNodeSocket &bso return (PropertyType)((bNodeSocketValueInt *)socket.default_value)->subtype; }, [](const IDProperty &property) { return property.type == IDP_INT; }, - [](const IDProperty &property, - const PersistentDataHandleMap &UNUSED(handles), - void *r_value) { *(int *)r_value = IDP_Int(&property); }, + [](const IDProperty &property, void *r_value) { *(int *)r_value = IDP_Int(&property); }, }; return &int_type; } @@ -468,9 +459,9 @@ static const SocketPropertyType *get_socket_property_type(const bNodeSocket &bso return property.type == IDP_ARRAY && property.subtype == IDP_FLOAT && property.len == 3; }, - [](const IDProperty &property, - const PersistentDataHandleMap &UNUSED(handles), - void *r_value) { copy_v3_v3((float *)r_value, (const float *)IDP_Array(&property)); }, + [](const IDProperty &property, void *r_value) { + copy_v3_v3((float *)r_value, (const float *)IDP_Array(&property)); + }, }; return &vector_type; } @@ -500,9 +491,9 @@ static const SocketPropertyType *get_socket_property_type(const bNodeSocket &bso }, nullptr, [](const IDProperty &property) { return property.type == IDP_INT; }, - [](const IDProperty &property, - const PersistentDataHandleMap &UNUSED(handles), - void *r_value) { *(bool *)r_value = IDP_Int(&property) != 0; }, + [](const IDProperty &property, void *r_value) { + *(bool *)r_value = IDP_Int(&property) != 0; + }, }; return &boolean_type; } @@ -522,9 +513,9 @@ static const SocketPropertyType *get_socket_property_type(const bNodeSocket &bso }, nullptr, [](const IDProperty &property) { return property.type == IDP_STRING; }, - [](const IDProperty &property, - const PersistentDataHandleMap &UNUSED(handles), - void *r_value) { new (r_value) std::string(IDP_String(&property)); }, + [](const IDProperty &property, void *r_value) { + new (r_value) std::string(IDP_String(&property)); + }, }; return &string_type; } @@ -541,10 +532,10 @@ static const SocketPropertyType *get_socket_property_type(const bNodeSocket &bso nullptr, nullptr, [](const IDProperty &property) { return property.type == IDP_ID; }, - [](const IDProperty &property, const PersistentDataHandleMap &handles, void *r_value) { + [](const IDProperty &property, void *r_value) { ID *id = IDP_Id(&property); Object *object = (id && GS(id->name) == ID_OB) ? (Object *)id : nullptr; - new (r_value) PersistentObjectHandle(handles.lookup(object)); + *(Object **)r_value = object; }, }; return &object_type; @@ -562,10 +553,10 @@ static const SocketPropertyType *get_socket_property_type(const bNodeSocket &bso nullptr, nullptr, [](const IDProperty &property) { return property.type == IDP_ID; }, - [](const IDProperty &property, const PersistentDataHandleMap &handles, void *r_value) { + [](const IDProperty &property, void *r_value) { ID *id = IDP_Id(&property); Collection *collection = (id && GS(id->name) == ID_GR) ? (Collection *)id : nullptr; - new (r_value) PersistentCollectionHandle(handles.lookup(collection)); + *(Collection **)r_value = collection; }, }; return &collection_type; @@ -653,7 +644,6 @@ void MOD_nodes_init(Main *bmain, NodesModifierData *nmd) } static void initialize_group_input(NodesModifierData &nmd, - const PersistentDataHandleMap &handle_map, const bNodeSocket &socket, const CPPType &cpp_type, void *r_value) @@ -677,22 +667,7 @@ static void initialize_group_input(NodesModifierData &nmd, blender::nodes::socket_cpp_value_get(socket, r_value); return; } - property_type->init_cpp_value(*property, handle_map, r_value); -} - -static void fill_data_handle_map(const NodesModifierSettings &settings, - const DerivedNodeTree &tree, - PersistentDataHandleMap &handle_map) -{ - Set<ID *> used_ids; - find_used_ids_from_settings(settings, used_ids); - find_used_ids_from_nodes(*tree.root_context().tree().btree(), used_ids); - - int current_handle = 0; - for (ID *id : used_ids) { - handle_map.add(current_handle, *id); - current_handle++; - } + property_type->init_cpp_value(*property, r_value); } static void reset_tree_ui_storage(Span<const blender::nodes::NodeTreeRef *> trees, @@ -710,8 +685,11 @@ static void reset_tree_ui_storage(Span<const blender::nodes::NodeTreeRef *> tree static Vector<SpaceSpreadsheet *> find_spreadsheet_editors(Main *bmain) { - Vector<SpaceSpreadsheet *> spreadsheets; wmWindowManager *wm = (wmWindowManager *)bmain->wm.first; + if (wm == nullptr) { + return {}; + } + Vector<SpaceSpreadsheet *> spreadsheets; LISTBASE_FOREACH (wmWindow *, window, &wm->windows) { bScreen *screen = BKE_workspace_active_screen_get(window->workspace_hook); LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) { @@ -880,9 +858,6 @@ static GeometrySet compute_geometry(const DerivedNodeTree &tree, blender::LinearAllocator<> &allocator = scope.linear_allocator(); blender::nodes::MultiFunctionByNode mf_by_node = get_multi_function_per_node(tree, scope); - PersistentDataHandleMap handle_map; - fill_data_handle_map(nmd->settings, tree, handle_map); - Map<DOutputSocket, GMutablePointer> group_inputs; const DTreeContext *root_context = &tree.root_context(); @@ -908,7 +883,7 @@ static GeometrySet compute_geometry(const DerivedNodeTree &tree, for (const OutputSocketRef *socket : remaining_input_sockets) { const CPPType &cpp_type = *blender::nodes::socket_cpp_type_get(*socket->typeinfo()); void *value_in = allocator.allocate(cpp_type.size(), cpp_type.alignment()); - initialize_group_input(*nmd, handle_map, *socket->bsocket(), cpp_type, value_in); + initialize_group_input(*nmd, *socket->bsocket(), cpp_type, value_in); group_inputs.add_new({root_context, socket}, {cpp_type, value_in}); } } @@ -937,7 +912,6 @@ static GeometrySet compute_geometry(const DerivedNodeTree &tree, eval_params.input_values = group_inputs; eval_params.output_sockets = group_outputs; eval_params.mf_by_node = &mf_by_node; - eval_params.handle_map = &handle_map; eval_params.modifier_ = nmd; eval_params.depsgraph = ctx->depsgraph; eval_params.self_object = ctx->object; diff --git a/source/blender/modifiers/intern/MOD_nodes_evaluator.cc b/source/blender/modifiers/intern/MOD_nodes_evaluator.cc index b04c9f7d2a6..ac5249b40a2 100644 --- a/source/blender/modifiers/intern/MOD_nodes_evaluator.cc +++ b/source/blender/modifiers/intern/MOD_nodes_evaluator.cc @@ -24,432 +24,1544 @@ #include "FN_generic_value_map.hh" #include "FN_multi_function.hh" -#include "BLI_profile.hh" +#include "BLI_enumerable_thread_specific.hh" +#include "BLI_stack.hh" +#include "BLI_task.h" +#include "BLI_task.hh" +#include "BLI_vector_set.hh" namespace blender::modifiers::geometry_nodes { -using bke::PersistentCollectionHandle; -using bke::PersistentObjectHandle; using fn::CPPType; using fn::GValueMap; using nodes::GeoNodeExecParams; using namespace fn::multi_function_types; -class NodeParamsProvider : public nodes::GeoNodeExecParamsProvider { - public: - LinearAllocator<> *allocator; - GValueMap<StringRef> *input_values; - GValueMap<StringRef> *output_values; +enum class ValueUsage : uint8_t { + /* The value is definitely used. */ + Required, + /* The value may be used. */ + Maybe, + /* The value will definitely not be used. */ + Unused, +}; + +struct SingleInputValue { + /** + * Points either to null or to a value of the type of input. + */ + void *value = nullptr; +}; + +struct MultiInputValueItem { + /** + * The socket where this value is coming from. This is required to sort the inputs correctly + * based on the link order later on. + */ + DSocket origin; + /** + * Should only be null directly after construction. After that it should always point to a value + * of the correct type. + */ + void *value = nullptr; +}; + +struct MultiInputValue { + /** + * Collection of all the inputs that have been provided already. Note, the same origin can occur + * multiple times. However, it is guaranteed that if two items have the same origin, they will + * also have the same value (the pointer is different, but they point to values that would + * compare equal). + */ + Vector<MultiInputValueItem> items; + /** + * Number of items that need to be added until all inputs have been provided. + */ + int expected_size = 0; +}; + +struct InputState { + + /** + * Type of the socket. If this is null, the socket should just be ignored. + */ + const CPPType *type = nullptr; + + /** + * Value of this input socket. By default, the value is empty. When other nodes are done + * computing their outputs, the computed values will be forwarded to linked input sockets. + * The value will then live here until it is consumed by the node or it was found that the value + * is not needed anymore. + * Whether the `single` or `multi` value is used depends on the socket. + */ + union { + SingleInputValue *single; + MultiInputValue *multi; + } value; + + /** + * How the node intends to use this input. By default all inputs may be used. Based on which + * outputs are used, a node can tell the evaluator that an input will definitely be used or is + * never used. This allows the evaluator to free values early, avoid copies and other unnecessary + * computations. + */ + ValueUsage usage = ValueUsage::Maybe; + + /** + * True when this input is/was used for an execution. While a node is running, only the inputs + * that have this set to true are allowed to be used. This makes sure that inputs created while + * the node is running correctly trigger the node to run again. Furthermore, it gives the node a + * consistent view of which inputs are available that does not change unexpectedly. + * + * While the node is running, this can be checked without a lock, because no one is writing to + * it. If this is true, the value can be read without a lock as well, because the value is not + * changed by others anymore. + */ + bool was_ready_for_execution = false; +}; + +struct OutputState { + /** + * If this output has been computed and forwarded already. If this is true, the value is not + * computed/forwarded again. + */ + bool has_been_computed = false; + + /** + * Keeps track of how the output value is used. If a connected input becomes required, this + * output has to become required as well. The output becomes ignored when it has zero potential + * users that are counted below. + */ + ValueUsage output_usage = ValueUsage::Maybe; + + /** + * This is a copy of `output_usage` that is done right before node execution starts. This is + * done so that the node gets a consistent view of what outputs are used, even when this changes + * while the node is running (the node might be reevaluated in that case). + * + * While the node is running, this can be checked without a lock, because no one is writing to + * it. + */ + ValueUsage output_usage_for_execution = ValueUsage::Maybe; + + /** + * Counts how many times the value from this output might be used. If this number reaches zero, + * the output is not needed anymore. + */ + int potential_users = 0; +}; + +enum class NodeScheduleState { + /** + * Default state of every node. + */ + NotScheduled, + /** + * The node has been added to the task group and will be executed by it in the future. + */ + Scheduled, + /** + * The node is currently running. + */ + Running, + /** + * The node is running and has been rescheduled while running. In this case the node will run + * again. However, we don't add it to the task group immediately, because then the node might run + * twice at the same time, which is not allowed. Instead, once the node is done running, it will + * reschedule itself. + */ + RunningAndRescheduled, +}; + +struct NodeState { + /** + * Needs to be locked when any data in this state is accessed that is not explicitely marked as + * otherwise. + */ + std::mutex mutex; + + /** + * States of the individual input and output sockets. One can index into these arrays without + * locking. However, to access the data inside a lock is generally necessary. + * + * These spans have to be indexed with the socket index. Unavailable sockets have a state as + * well. Maybe we can handle unavailable sockets differently in Blender in general, so I did not + * want to add complexity around it here. + */ + MutableSpan<InputState> inputs; + MutableSpan<OutputState> outputs; - bool can_get_input(StringRef identifier) const override + /** + * Nodes that don't support lazyness have some special handling the first time they are executed. + */ + bool non_lazy_node_is_initialized = false; + + /** + * Used to check that nodes that don't support lazyness do not run more than once. + */ + bool has_been_executed = false; + + /** + * Becomes true when the node will never be executed again and its inputs are destructed. + * Generally, a node has finished once all of its outputs with (potential) users have been + * computed. + */ + bool node_has_finished = false; + + /** + * Counts the number of values that still have to be forwarded to this node until it should run + * again. It counts values from a multi input socket separately. + * This is used as an optimization so that nodes are not scheduled unnecessarily in many cases. + */ + int missing_required_inputs = 0; + + /** + * A node is always in one specific schedule state. This helps to ensure that the same node does + * not run twice at the same time accidentally. + */ + NodeScheduleState schedule_state = NodeScheduleState::NotScheduled; +}; + +/** + * Container for a node and its state. Packing them into a single struct allows the use of + * `VectorSet` instead of a `Map` for `node_states_` which simplifies parallel loops over all + * states. + * + * Equality operators and a hash function for `DNode` are provided so that one can lookup this type + * in `node_states_` just with a `DNode`. + */ +struct NodeWithState { + DNode node; + /* Store a pointer instead of `NodeState` directly to keep it small and movable. */ + NodeState *state = nullptr; + + friend bool operator==(const NodeWithState &a, const NodeWithState &b) { - return input_values->contains(identifier); + return a.node == b.node; } - bool can_set_output(StringRef identifier) const override + friend bool operator==(const NodeWithState &a, const DNode &b) { - return !output_values->contains(identifier); + return a.node == b; } - GMutablePointer extract_input(StringRef identifier) override + friend bool operator==(const DNode &a, const NodeWithState &b) { - return this->input_values->extract(identifier); + return a == b.node; } - Vector<GMutablePointer> extract_multi_input(StringRef identifier) override + uint64_t hash() const { - Vector<GMutablePointer> values; - int index = 0; - while (true) { - std::string sub_identifier = identifier; - if (index > 0) { - sub_identifier += "[" + std::to_string(index) + "]"; - } - if (!this->input_values->contains(sub_identifier)) { - break; - } - values.append(input_values->extract(sub_identifier)); - index++; - } - return values; + return node.hash(); } - GPointer get_input(StringRef identifier) const override + static uint64_t hash_as(const DNode &node) { - return this->input_values->lookup(identifier); + return node.hash(); } +}; + +class GeometryNodesEvaluator; + +/** + * Utility class that locks the state of a node. Having this is a separate class is useful because + * it allows methods to communicate that they expect the node to be locked. + */ +class LockedNode : NonCopyable, NonMovable { + private: + GeometryNodesEvaluator &evaluator_; - GMutablePointer alloc_output_value(StringRef identifier, const CPPType &type) override + public: + /** + * This is the node that is currently locked. + */ + const DNode node; + NodeState &node_state; + + /** + * Used to delay notifying (and therefore locking) other nodes until the current node is not + * locked anymore. This might not be strictly necessary to avoid deadlocks in the current code, + * but it is a good measure to avoid accidentally adding a deadlock later on. By not locking + * more than one node per thread at a time, deadlocks are avoided. + * + * The notifications will be send right after the node is not locked anymore. + */ + Vector<DOutputSocket> delayed_required_outputs; + Vector<DOutputSocket> delayed_unused_outputs; + Vector<DNode> delayed_scheduled_nodes; + + LockedNode(GeometryNodesEvaluator &evaluator, const DNode node, NodeState &node_state) + : evaluator_(evaluator), node(node), node_state(node_state) { - void *buffer = this->allocator->allocate(type.size(), type.alignment()); - GMutablePointer ptr{&type, buffer}; - this->output_values->add_new_direct(identifier, ptr); - return ptr; + node_state.mutex.lock(); } + + ~LockedNode(); }; -class GeometryNodesEvaluator { +static const CPPType *get_socket_cpp_type(const DSocket socket) +{ + return nodes::socket_cpp_type_get(*socket->typeinfo()); +} + +static const CPPType *get_socket_cpp_type(const SocketRef &socket) +{ + return nodes::socket_cpp_type_get(*socket.typeinfo()); +} + +static bool node_supports_lazyness(const DNode node) +{ + return node->typeinfo()->geometry_node_execute_supports_lazyness; +} + +/** Implements the callbacks that might be called when a node is executed. */ +class NodeParamsProvider : public nodes::GeoNodeExecParamsProvider { + private: + GeometryNodesEvaluator &evaluator_; + NodeState &node_state_; + public: - using LogSocketValueFn = std::function<void(DSocket, Span<GPointer>)>; + NodeParamsProvider(GeometryNodesEvaluator &evaluator, DNode dnode, NodeState &node_state); + + bool can_get_input(StringRef identifier) const override; + bool can_set_output(StringRef identifier) const override; + GMutablePointer extract_input(StringRef identifier) override; + Vector<GMutablePointer> extract_multi_input(StringRef identifier) override; + GPointer get_input(StringRef identifier) const override; + GMutablePointer alloc_output_value(const CPPType &type) override; + void set_output(StringRef identifier, GMutablePointer value) override; + void set_input_unused(StringRef identifier) override; + bool output_is_required(StringRef identifier) const override; + bool lazy_require_input(StringRef identifier) override; + bool lazy_output_is_required(StringRef identifier) const override; +}; + +class GeometryNodesEvaluator { private: - blender::LinearAllocator<> &allocator_; - Map<std::pair<DInputSocket, DOutputSocket>, GMutablePointer> value_by_input_; - Vector<DInputSocket> group_outputs_; - blender::nodes::MultiFunctionByNode &mf_by_node_; + /** + * This allocator lives on after the evaluator has been destructed. Therefore outputs of the + * entire evaluator should be allocated here. + */ + LinearAllocator<> &outer_allocator_; + /** + * A local linear allocator for each thread. Only use this for values that do not need to live + * longer than the lifetime of the evaluator itself. Considerations for the future: + * - We could use an allocator that can free here, some temporary values don't live long. + * - If we ever run into false sharing bottlenecks, we could use local allocators that allocate + * on cache line boundaries. Note, just because a value is allocated in one specific thread, + * does not mean that it will only be used by that thread. + */ + EnumerableThreadSpecific<LinearAllocator<>> local_allocators_; + + /** + * Every node that is reachable from the output gets its own state. Once all states have been + * constructed, this map can be used for lookups from multiple threads. + */ + VectorSet<NodeWithState> node_states_; + + /** + * Contains all the tasks for the nodes that are currently scheduled. + */ + TaskPool *task_pool_ = nullptr; + + GeometryNodesEvaluationParams ¶ms_; const blender::nodes::DataTypeConversions &conversions_; - const PersistentDataHandleMap &handle_map_; - const Object *self_object_; - const ModifierData *modifier_; - Depsgraph *depsgraph_; - LogSocketValueFn log_socket_value_fn_; + + friend NodeParamsProvider; public: GeometryNodesEvaluator(GeometryNodesEvaluationParams ¶ms) - : allocator_(params.allocator), - group_outputs_(std::move(params.output_sockets)), - mf_by_node_(*params.mf_by_node), - conversions_(blender::nodes::get_implicit_type_conversions()), - handle_map_(*params.handle_map), - self_object_(params.self_object), - modifier_(¶ms.modifier_->modifier), - depsgraph_(params.depsgraph), - log_socket_value_fn_(std::move(params.log_socket_value_fn)) + : outer_allocator_(params.allocator), + params_(params), + conversions_(blender::nodes::get_implicit_type_conversions()) { - for (auto item : params.input_values.items()) { - this->log_socket_value(item.key, item.value); - this->forward_to_inputs(item.key, item.value); - } } - Vector<GMutablePointer> execute() + void execute() + { + task_pool_ = BLI_task_pool_create(this, TASK_PRIORITY_HIGH); + + this->create_states_for_reachable_nodes(); + this->forward_group_inputs(); + this->schedule_initial_nodes(); + + /* This runs until all initially requested inputs have been computed. */ + BLI_task_pool_work_and_wait(task_pool_); + BLI_task_pool_free(task_pool_); + + this->extract_group_outputs(); + this->destruct_node_states(); + } + + void create_states_for_reachable_nodes() { - Vector<GMutablePointer> results; - for (const DInputSocket &group_output : group_outputs_) { - Vector<GMutablePointer> result = this->get_input_values(group_output); - this->log_socket_value(group_output, result); - results.append(result[0]); + /* This does a depth first search for all the nodes that are reachable from the group + * outputs. This finds all nodes that are relevant. */ + Stack<DNode> nodes_to_check; + /* Start at the output sockets. */ + for (const DInputSocket &socket : params_.output_sockets) { + nodes_to_check.push(socket.node()); } - for (GMutablePointer value : value_by_input_.values()) { - value.destruct(); + /* Use the local allocator because the states do not need to outlive the evaluator. */ + LinearAllocator<> &allocator = local_allocators_.local(); + while (!nodes_to_check.is_empty()) { + const DNode node = nodes_to_check.pop(); + if (node_states_.contains_as(node)) { + /* This node has been handled already. */ + continue; + } + /* Create a new state for the node. */ + NodeState &node_state = *allocator.construct<NodeState>().release(); + node_states_.add_new({node, &node_state}); + + /* Push all linked origins on the stack. */ + for (const InputSocketRef *input_ref : node->inputs()) { + const DInputSocket input{node.context(), input_ref}; + input.foreach_origin_socket( + [&](const DSocket origin) { nodes_to_check.push(origin.node()); }); + } } - return results; + + /* Initialize the more complex parts of the node states in parallel. At this point no new + * node states are added anymore, so it is safe to lookup states from `node_states_` from + * multiple threads. */ + parallel_for(IndexRange(node_states_.size()), 50, [&, this](const IndexRange range) { + LinearAllocator<> &allocator = this->local_allocators_.local(); + for (const NodeWithState &item : node_states_.as_span().slice(range)) { + this->initialize_node_state(item.node, *item.state, allocator); + } + }); } - private: - Vector<GMutablePointer> get_input_values(const DInputSocket socket_to_compute) + void initialize_node_state(const DNode node, NodeState &node_state, LinearAllocator<> &allocator) { - Vector<DSocket> from_sockets; - socket_to_compute.foreach_origin_socket([&](DSocket socket) { from_sockets.append(socket); }); + /* Construct arrays of the correct size. */ + node_state.inputs = allocator.construct_array<InputState>(node->inputs().size()); + node_state.outputs = allocator.construct_array<OutputState>(node->outputs().size()); - if (from_sockets.is_empty()) { - /* The input is not connected, use the value from the socket itself. */ - const CPPType &type = *blender::nodes::socket_cpp_type_get(*socket_to_compute->typeinfo()); - return {get_unlinked_input_value(socket_to_compute, type)}; + /* Initialize input states. */ + for (const int i : node->inputs().index_range()) { + InputState &input_state = node_state.inputs[i]; + const DInputSocket socket = node.input(i); + if (!socket->is_available()) { + /* Unavailable sockets should never be used. */ + input_state.type = nullptr; + input_state.usage = ValueUsage::Unused; + continue; + } + const CPPType *type = get_socket_cpp_type(socket); + input_state.type = type; + if (type == nullptr) { + /* This is not a known data socket, it shouldn't be used. */ + input_state.usage = ValueUsage::Unused; + continue; + } + /* Construct the correct struct that can hold the input(s). */ + if (socket->is_multi_input_socket()) { + input_state.value.multi = allocator.construct<MultiInputValue>().release(); + /* Count how many values should be added until the socket is complete. */ + socket.foreach_origin_socket( + [&](DSocket UNUSED(origin)) { input_state.value.multi->expected_size++; }); + /* If no links are connected, we do read the value from socket itself. */ + if (input_state.value.multi->expected_size == 0) { + input_state.value.multi->expected_size = 1; + } + } + else { + input_state.value.single = allocator.construct<SingleInputValue>().release(); + } } - - /* Multi-input sockets contain a vector of inputs. */ - if (socket_to_compute->is_multi_input_socket()) { - return this->get_inputs_from_incoming_links(socket_to_compute, from_sockets); + /* Initialize output states. */ + for (const int i : node->outputs().index_range()) { + OutputState &output_state = node_state.outputs[i]; + const DOutputSocket socket = node.output(i); + if (!socket->is_available()) { + /* Unavailable outputs should never be used. */ + output_state.output_usage = ValueUsage::Unused; + continue; + } + const CPPType *type = get_socket_cpp_type(socket); + if (type == nullptr) { + /* Non data sockets should never be used. */ + output_state.output_usage = ValueUsage::Unused; + continue; + } + /* Count the number of potential users for this socket. */ + socket.foreach_target_socket( + [&, this](const DInputSocket target_socket) { + const DNode target_node = target_socket.node(); + if (!this->node_states_.contains_as(target_node)) { + /* The target node is not computed because it is not computed to the output. */ + return; + } + output_state.potential_users += 1; + }, + {}); + if (output_state.potential_users == 0) { + /* If it does not have any potential users, it is unused. */ + output_state.output_usage = ValueUsage::Unused; + } } + } - const DSocket from_socket = from_sockets[0]; - GMutablePointer value = this->get_input_from_incoming_link(socket_to_compute, from_socket); - return {value}; + void destruct_node_states() + { + parallel_for(IndexRange(node_states_.size()), 50, [&, this](const IndexRange range) { + for (const NodeWithState &item : node_states_.as_span().slice(range)) { + this->destruct_node_state(item.node, *item.state); + } + }); } - Vector<GMutablePointer> get_inputs_from_incoming_links(const DInputSocket socket_to_compute, - const Span<DSocket> from_sockets) + void destruct_node_state(const DNode node, NodeState &node_state) { - Vector<GMutablePointer> values; - for (const int i : from_sockets.index_range()) { - const DSocket from_socket = from_sockets[i]; - const int first_occurence = from_sockets.take_front(i).first_index_try(from_socket); - if (first_occurence == -1) { - values.append(this->get_input_from_incoming_link(socket_to_compute, from_socket)); + /* Need to destruct stuff manually, because it's allocated by a custom allocator. */ + for (const int i : node->inputs().index_range()) { + InputState &input_state = node_state.inputs[i]; + if (input_state.type == nullptr) { + continue; + } + const InputSocketRef &socket_ref = node->input(i); + if (socket_ref.is_multi_input_socket()) { + MultiInputValue &multi_value = *input_state.value.multi; + for (MultiInputValueItem &item : multi_value.items) { + input_state.type->destruct(item.value); + } + multi_value.~MultiInputValue(); } else { - /* If the same from-socket occurs more than once, we make a copy of the first value. This - * can happen when a node linked to a multi-input-socket is muted. */ - GMutablePointer value = values[first_occurence]; - const CPPType *type = value.type(); - void *copy_buffer = allocator_.allocate(type->size(), type->alignment()); - type->copy_to_uninitialized(value.get(), copy_buffer); - values.append({type, copy_buffer}); + SingleInputValue &single_value = *input_state.value.single; + void *value = single_value.value; + if (value != nullptr) { + input_state.type->destruct(value); + } + single_value.~SingleInputValue(); } } - return values; + + destruct_n(node_state.inputs.data(), node_state.inputs.size()); + destruct_n(node_state.outputs.data(), node_state.outputs.size()); + + node_state.~NodeState(); } - GMutablePointer get_input_from_incoming_link(const DInputSocket socket_to_compute, - const DSocket from_socket) + void forward_group_inputs() { - if (from_socket->is_output()) { - const DOutputSocket from_output_socket{from_socket}; - const std::pair<DInputSocket, DOutputSocket> key = std::make_pair(socket_to_compute, - from_output_socket); - std::optional<GMutablePointer> value = value_by_input_.pop_try(key); - if (value.has_value()) { - /* This input has been computed before, return it directly. */ - return {*value}; + for (auto &&item : params_.input_values.items()) { + const DOutputSocket socket = item.key; + GMutablePointer value = item.value; + this->log_socket_value(socket, value); + + const DNode node = socket.node(); + if (!node_states_.contains_as(node)) { + /* The socket is not connected to any output. */ + value.destruct(); + continue; } + this->forward_output(socket, value); + } + } - /* Compute the socket now. */ - this->compute_output_and_forward(from_output_socket); - return {value_by_input_.pop(key)}; + void schedule_initial_nodes() + { + for (const DInputSocket &socket : params_.output_sockets) { + const DNode node = socket.node(); + NodeState &node_state = this->get_node_state(node); + LockedNode locked_node{*this, node, node_state}; + /* Setting an input as required will schedule any linked node. */ + this->set_input_required(locked_node, socket); } + } - /* Get value from an unlinked input socket. */ - const CPPType &type = *blender::nodes::socket_cpp_type_get(*socket_to_compute->typeinfo()); - const DInputSocket from_input_socket{from_socket}; - return {get_unlinked_input_value(from_input_socket, type)}; + void schedule_node(LockedNode &locked_node) + { + switch (locked_node.node_state.schedule_state) { + case NodeScheduleState::NotScheduled: { + /* The node will be scheduled once it is not locked anymore. We could schedule the node + * right here, but that would result in a deadlock if the task pool decides to run the task + * immediately (this only happens when Blender is started with a single thread). */ + locked_node.node_state.schedule_state = NodeScheduleState::Scheduled; + locked_node.delayed_scheduled_nodes.append(locked_node.node); + break; + } + case NodeScheduleState::Scheduled: { + /* Scheduled already, nothing to do. */ + break; + } + case NodeScheduleState::Running: { + /* Reschedule node while it is running. + * The node will reschedule itself when it is done. */ + locked_node.node_state.schedule_state = NodeScheduleState::RunningAndRescheduled; + break; + } + case NodeScheduleState::RunningAndRescheduled: { + /* Scheduled already, nothing to do. */ + break; + } + } } - void compute_output_and_forward(const DOutputSocket socket_to_compute) + static void run_node_from_task_pool(TaskPool *task_pool, void *task_data) { - const DNode node{socket_to_compute.context(), &socket_to_compute->node()}; + void *user_data = BLI_task_pool_user_data(task_pool); + GeometryNodesEvaluator &evaluator = *(GeometryNodesEvaluator *)user_data; + const NodeWithState *node_with_state = (const NodeWithState *)task_data; + + evaluator.node_task_run(node_with_state->node, *node_with_state->state); + } - if (!socket_to_compute->is_available()) { - /* If the output is not available, use a default value. */ - const CPPType &type = *blender::nodes::socket_cpp_type_get(*socket_to_compute->typeinfo()); - void *buffer = allocator_.allocate(type.size(), type.alignment()); - type.copy_to_uninitialized(type.default_value(), buffer); - this->forward_to_inputs(socket_to_compute, {type, buffer}); + void node_task_run(const DNode node, NodeState &node_state) + { + /* These nodes are sometimes scheduled. We could also check for them in other places, but + * it's the easiest to do it here. */ + if (node->is_group_input_node() || node->is_group_output_node()) { return; } - /* Prepare inputs required to execute the node. */ - GValueMap<StringRef> node_inputs_map{allocator_}; - for (const InputSocketRef *input_socket : node->inputs()) { - if (input_socket->is_available()) { - DInputSocket dsocket{node.context(), input_socket}; - Vector<GMutablePointer> values = this->get_input_values(dsocket); - this->log_socket_value(dsocket, values); - for (int i = 0; i < values.size(); ++i) { - /* Values from Multi Input Sockets are stored in input map with the format - * <identifier>[<index>]. */ - blender::StringRefNull key = allocator_.copy_string( - input_socket->identifier() + (i > 0 ? ("[" + std::to_string(i)) + "]" : "")); - node_inputs_map.add_new_direct(key, std::move(values[i])); - } - } + const bool do_execute_node = this->node_task_preprocessing(node, node_state); + + /* Only execute the node if all prerequisites are met. There has to be an output that is + * required and all required inputs have to be provided already. */ + if (do_execute_node) { + this->execute_node(node, node_state); + } + + this->node_task_postprocessing(node, node_state); + } + + bool node_task_preprocessing(const DNode node, NodeState &node_state) + { + LockedNode locked_node{*this, node, node_state}; + BLI_assert(node_state.schedule_state == NodeScheduleState::Scheduled); + node_state.schedule_state = NodeScheduleState::Running; + + /* Early return if the node has finished already. */ + if (locked_node.node_state.node_has_finished) { + return false; + } + /* Prepare outputs and check if actually any new outputs have to be computed. */ + if (!this->prepare_node_outputs_for_execution(locked_node)) { + return false; } + /* Initialize nodes that don't support lazyness. This is done after at least one output is + * required and before we check that all required inputs are provided. This reduces the + * number of "round-trips" through the task pool by one for most nodes. */ + if (!node_state.non_lazy_node_is_initialized && !node_supports_lazyness(node)) { + this->initialize_non_lazy_node(locked_node); + node_state.non_lazy_node_is_initialized = true; + } + /* Prepare inputs and check if all required inputs are provided. */ + if (!this->prepare_node_inputs_for_execution(locked_node)) { + return false; + } + return true; + } - /* Execute the node. */ - GValueMap<StringRef> node_outputs_map{allocator_}; - NodeParamsProvider params_provider; - params_provider.dnode = node; - params_provider.handle_map = &handle_map_; - params_provider.self_object = self_object_; - params_provider.depsgraph = depsgraph_; - params_provider.allocator = &allocator_; - params_provider.input_values = &node_inputs_map; - params_provider.output_values = &node_outputs_map; - params_provider.modifier = modifier_; - this->execute_node(node, params_provider); + /* A node is finished when it has computed all outputs that may be used. */ + bool finish_node_if_possible(LockedNode &locked_node) + { + if (locked_node.node_state.node_has_finished) { + /* Early return in case this node is known to have finished already. */ + return true; + } - /* Forward computed outputs to linked input sockets. */ - for (const OutputSocketRef *output_socket : node->outputs()) { - if (output_socket->is_available()) { - const DOutputSocket dsocket{node.context(), output_socket}; - GMutablePointer value = node_outputs_map.extract(output_socket->identifier()); - this->log_socket_value(dsocket, value); - this->forward_to_inputs(dsocket, value); + /* Check if there is any output that might be used but has not been computed yet. */ + bool has_remaining_output = false; + for (OutputState &output_state : locked_node.node_state.outputs) { + if (output_state.has_been_computed) { + continue; + } + if (output_state.output_usage != ValueUsage::Unused) { + has_remaining_output = true; + break; + } + } + if (!has_remaining_output) { + /* If there are no remaining outputs, all the inputs can be destructed and/or can become + * unused. This can also trigger a chain reaction where nodes to the left become finished + * too. */ + for (const int i : locked_node.node->inputs().index_range()) { + const DInputSocket socket = locked_node.node.input(i); + InputState &input_state = locked_node.node_state.inputs[i]; + if (input_state.usage == ValueUsage::Maybe) { + this->set_input_unused(locked_node, socket); + } + else if (input_state.usage == ValueUsage::Required) { + /* The value was required, so it cannot become unused. However, we can destruct the + * value. */ + this->destruct_input_value_if_exists(locked_node, socket); + } } + locked_node.node_state.node_has_finished = true; } + return locked_node.node_state.node_has_finished; } - void log_socket_value(const DSocket socket, Span<GPointer> values) + bool prepare_node_outputs_for_execution(LockedNode &locked_node) { - if (log_socket_value_fn_) { - log_socket_value_fn_(socket, values); + bool execution_is_necessary = false; + for (OutputState &output_state : locked_node.node_state.outputs) { + /* Update the output usage for execution to the latest value. */ + output_state.output_usage_for_execution = output_state.output_usage; + if (!output_state.has_been_computed) { + if (output_state.output_usage == ValueUsage::Required) { + /* Only evaluate when there is an output that is required but has not been computed. */ + execution_is_necessary = true; + } + } } + return execution_is_necessary; } - void log_socket_value(const DSocket socket, Span<GMutablePointer> values) + void initialize_non_lazy_node(LockedNode &locked_node) { - this->log_socket_value(socket, values.cast<GPointer>()); + for (const int i : locked_node.node->inputs().index_range()) { + InputState &input_state = locked_node.node_state.inputs[i]; + if (input_state.type == nullptr) { + /* Ignore unavailable/non-data sockets. */ + continue; + } + /* Nodes that don't support lazyness require all inputs. */ + const DInputSocket input_socket = locked_node.node.input(i); + this->set_input_required(locked_node, input_socket); + } } - void log_socket_value(const DSocket socket, GPointer value) + /** + * Checks if requested inputs are available and "marks" all the inputs that are available + * during the node execution. Inputs that are provided after this function ends but before the + * node is executed, cannot be read by the node in the execution (note that this only affects + * nodes that support lazy inputs). + */ + bool prepare_node_inputs_for_execution(LockedNode &locked_node) { - this->log_socket_value(socket, Span<GPointer>(&value, 1)); + for (const int i : locked_node.node_state.inputs.index_range()) { + InputState &input_state = locked_node.node_state.inputs[i]; + if (input_state.type == nullptr) { + /* Ignore unavailable and non-data sockets. */ + continue; + } + const DInputSocket socket = locked_node.node.input(i); + const bool is_required = input_state.usage == ValueUsage::Required; + + /* No need to check this socket again. */ + if (input_state.was_ready_for_execution) { + continue; + } + + if (socket->is_multi_input_socket()) { + MultiInputValue &multi_value = *input_state.value.multi; + /* Checks if all the linked sockets have been provided already. */ + if (multi_value.items.size() == multi_value.expected_size) { + input_state.was_ready_for_execution = true; + this->log_socket_value(socket, input_state, multi_value.items); + } + else if (is_required) { + /* The input is required but is not fully provided yet. Therefore the node cannot be + * executed yet. */ + return false; + } + } + else { + SingleInputValue &single_value = *input_state.value.single; + if (single_value.value != nullptr) { + input_state.was_ready_for_execution = true; + this->log_socket_value(socket, GPointer{input_state.type, single_value.value}); + } + else if (is_required) { + /* The input is required but has not been provided yet. Therefore the node cannot be + * executed yet. */ + return false; + } + } + } + /* All required inputs have been provided. */ + return true; } - void execute_node(const DNode node, NodeParamsProvider ¶ms_provider) + /** + * Actually execute the node. All the required inputs are available and at least one output is + * required. + */ + void execute_node(const DNode node, NodeState &node_state) { - BLI_PROFILE_SCOPE(node->name().c_str()); - const bNode &bnode = *params_provider.dnode->bnode(); + const bNode &bnode = *node->bnode(); - /* Use the geometry-node-execute callback if it exists. */ + if (node_state.has_been_executed) { + if (!node_supports_lazyness(node)) { + /* Nodes that don't support lazyness must not be executed more than once. */ + BLI_assert_unreachable(); + } + } + node_state.has_been_executed = true; + + /* Use the geometry node execute callback if it exists. */ if (bnode.typeinfo->geometry_node_execute != nullptr) { - GeoNodeExecParams params{params_provider}; - bnode.typeinfo->geometry_node_execute(params); + this->execute_geometry_node(node, node_state); return; } /* Use the multi-function implementation if it exists. */ - const MultiFunction *multi_function = mf_by_node_.lookup_default(node, nullptr); + const MultiFunction *multi_function = params_.mf_by_node->lookup_default(node, nullptr); if (multi_function != nullptr) { - this->execute_multi_function_node(node, params_provider, *multi_function); + this->execute_multi_function_node(node, *multi_function, node_state); return; } - /* Just output default values if no implementation exists. */ - this->execute_unknown_node(node, params_provider); + this->execute_unknown_node(node, node_state); + } + + void execute_geometry_node(const DNode node, NodeState &node_state) + { + const bNode &bnode = *node->bnode(); + + NodeParamsProvider params_provider{*this, node, node_state}; + GeoNodeExecParams params{params_provider}; + bnode.typeinfo->geometry_node_execute(params); } void execute_multi_function_node(const DNode node, - NodeParamsProvider ¶ms_provider, - const MultiFunction &fn) + const MultiFunction &fn, + NodeState &node_state) { MFContextBuilder fn_context; MFParamsBuilder fn_params{fn, 1}; - Vector<GMutablePointer> input_data; - for (const InputSocketRef *socket_ref : node->inputs()) { - if (socket_ref->is_available()) { - GMutablePointer data = params_provider.extract_input(socket_ref->identifier()); - fn_params.add_readonly_single_input(GSpan(*data.type(), data.get(), 1)); - input_data.append(data); + LinearAllocator<> &allocator = local_allocators_.local(); + + /* Prepare the inputs for the multi function. */ + for (const int i : node->inputs().index_range()) { + const InputSocketRef &socket_ref = node->input(i); + if (!socket_ref.is_available()) { + continue; } + BLI_assert(!socket_ref.is_multi_input_socket()); + InputState &input_state = node_state.inputs[i]; + BLI_assert(input_state.was_ready_for_execution); + SingleInputValue &single_value = *input_state.value.single; + BLI_assert(single_value.value != nullptr); + fn_params.add_readonly_single_input(GPointer{*input_state.type, single_value.value}); } - Vector<GMutablePointer> output_data; - for (const OutputSocketRef *socket_ref : node->outputs()) { - if (socket_ref->is_available()) { - const CPPType &type = *blender::nodes::socket_cpp_type_get(*socket_ref->typeinfo()); - GMutablePointer output_value = params_provider.alloc_output_value(socket_ref->identifier(), - type); - fn_params.add_uninitialized_single_output(GMutableSpan{type, output_value.get(), 1}); - output_data.append(output_value); + /* Prepare the outputs for the multi function. */ + Vector<GMutablePointer> outputs; + for (const int i : node->outputs().index_range()) { + const OutputSocketRef &socket_ref = node->output(i); + if (!socket_ref.is_available()) { + continue; } + const CPPType &type = *get_socket_cpp_type(socket_ref); + void *buffer = allocator.allocate(type.size(), type.alignment()); + fn_params.add_uninitialized_single_output(GMutableSpan{type, buffer, 1}); + outputs.append({type, buffer}); } + fn.call(IndexRange(1), fn_params, fn_context); - for (GMutablePointer value : input_data) { - value.destruct(); + + /* Forward the computed outputs. */ + int output_index = 0; + for (const int i : node->outputs().index_range()) { + const OutputSocketRef &socket_ref = node->output(i); + if (!socket_ref.is_available()) { + continue; + } + OutputState &output_state = node_state.outputs[i]; + const DOutputSocket socket{node.context(), &socket_ref}; + GMutablePointer value = outputs[output_index]; + this->forward_output(socket, value); + output_state.has_been_computed = true; + output_index++; } } - void execute_unknown_node(const DNode node, NodeParamsProvider ¶ms_provider) + void execute_unknown_node(const DNode node, NodeState &node_state) { + LinearAllocator<> &allocator = local_allocators_.local(); for (const OutputSocketRef *socket : node->outputs()) { - if (socket->is_available()) { - const CPPType &type = *blender::nodes::socket_cpp_type_get(*socket->typeinfo()); - params_provider.output_values->add_new_by_copy(socket->identifier(), - {type, type.default_value()}); + if (!socket->is_available()) { + continue; + } + const CPPType *type = get_socket_cpp_type(*socket); + if (type == nullptr) { + continue; + } + /* Just forward the default value of the type as a fallback. That's typically better than + * crashing or doing nothing. */ + OutputState &output_state = node_state.outputs[socket->index()]; + output_state.has_been_computed = true; + void *buffer = allocator.allocate(type->size(), type->alignment()); + type->copy_to_uninitialized(type->default_value(), buffer); + this->forward_output({node.context(), socket}, {*type, buffer}); + } + } + + void node_task_postprocessing(const DNode node, NodeState &node_state) + { + LockedNode locked_node{*this, node, node_state}; + + const bool node_has_finished = this->finish_node_if_possible(locked_node); + const bool reschedule_requested = node_state.schedule_state == + NodeScheduleState::RunningAndRescheduled; + node_state.schedule_state = NodeScheduleState::NotScheduled; + if (reschedule_requested && !node_has_finished) { + /* Either the node rescheduled itself or another node tried to schedule it while it ran. */ + this->schedule_node(locked_node); + } + + this->assert_expected_outputs_have_been_computed(locked_node); + } + + void assert_expected_outputs_have_been_computed(LockedNode &locked_node) + { +#ifdef DEBUG + /* Outputs can only be computed when all required inputs have been provided. */ + if (locked_node.node_state.missing_required_inputs > 0) { + return; + } + /* If the node is still scheduled, it is not necessary that all its expected outputs are + * computed yet. */ + if (locked_node.node_state.schedule_state == NodeScheduleState::Scheduled) { + return; + } + + const bool supports_lazyness = node_supports_lazyness(locked_node.node); + /* Iterating over sockets instead of the states directly, because that makes it easier to + * figure out which socket is missing when one of the asserts is hit. */ + for (const OutputSocketRef *socket_ref : locked_node.node->outputs()) { + OutputState &output_state = locked_node.node_state.outputs[socket_ref->index()]; + if (supports_lazyness) { + /* Expected that at least all required sockets have been computed. If more outputs become + * required later, the node will be executed again. */ + if (output_state.output_usage_for_execution == ValueUsage::Required) { + BLI_assert(output_state.has_been_computed); + } + } + else { + /* Expect that all outputs that may be used have been computed, because the node cannot + * be executed again. */ + if (output_state.output_usage_for_execution != ValueUsage::Unused) { + BLI_assert(output_state.has_been_computed); + } + } + } +#else + UNUSED_VARS(locked_node); +#endif + } + + void extract_group_outputs() + { + for (const DInputSocket &socket : params_.output_sockets) { + BLI_assert(socket->is_available()); + BLI_assert(!socket->is_multi_input_socket()); + + const DNode node = socket.node(); + NodeState &node_state = this->get_node_state(node); + InputState &input_state = node_state.inputs[socket->index()]; + + SingleInputValue &single_value = *input_state.value.single; + void *value = single_value.value; + + /* The value should have been computed by now. If this assert is hit, it means that there + * was some scheduling issue before. */ + BLI_assert(value != nullptr); + + /* Move value into memory owned by the outer allocator. */ + const CPPType &type = *input_state.type; + void *buffer = outer_allocator_.allocate(type.size(), type.alignment()); + type.move_to_uninitialized(value, buffer); + + params_.r_output_values.append({type, buffer}); + } + } + + /** + * Load the required input from the socket or trigger nodes to the left to compute the value. + * When this function is called, the node will always be executed again eventually (either + * immediately, or when all required inputs have been computed by other nodes). + */ + void set_input_required(LockedNode &locked_node, const DInputSocket input_socket) + { + BLI_assert(locked_node.node == input_socket.node()); + InputState &input_state = locked_node.node_state.inputs[input_socket->index()]; + + /* Value set as unused cannot become used again. */ + BLI_assert(input_state.usage != ValueUsage::Unused); + + if (input_state.usage == ValueUsage::Required) { + /* The value is already required, but the node might expect to be evaluated again. */ + this->schedule_node(locked_node); + /* Returning here also ensure that the code below is executed at most once per input. */ + return; + } + input_state.usage = ValueUsage::Required; + + if (input_state.was_ready_for_execution) { + /* The value was already ready, but the node might expect to be evaluated again. */ + this->schedule_node(locked_node); + return; + } + + /* Count how many values still have to be added to this input until it is "complete". */ + int missing_values = 0; + if (input_socket->is_multi_input_socket()) { + MultiInputValue &multi_value = *input_state.value.multi; + missing_values = multi_value.expected_size - multi_value.items.size(); + } + else { + SingleInputValue &single_value = *input_state.value.single; + if (single_value.value == nullptr) { + missing_values = 1; + } + } + if (missing_values == 0) { + /* The input is fully available already, but the node might expect to be evaluated again. */ + this->schedule_node(locked_node); + return; + } + /* Increase the total number of missing required inputs. This ensures that the node will be + * scheduled correctly when all inputs have been provided. */ + locked_node.node_state.missing_required_inputs += missing_values; + + /* Get all origin sockets, because we have to tag those as required as well. */ + Vector<DSocket> origin_sockets; + input_socket.foreach_origin_socket( + [&, this](const DSocket origin_socket) { origin_sockets.append(origin_socket); }); + + if (origin_sockets.is_empty()) { + /* If there are no origin sockets, just load the value from the socket directly. */ + this->load_unlinked_input_value(locked_node, input_socket, input_state, input_socket); + locked_node.node_state.missing_required_inputs -= 1; + this->schedule_node(locked_node); + return; + } + bool will_be_triggered_by_other_node = false; + for (const DSocket origin_socket : origin_sockets) { + if (origin_socket->is_input()) { + /* Load the value directly from the origin socket. In most cases this is an unlinked + * group input. */ + this->load_unlinked_input_value(locked_node, input_socket, input_state, origin_socket); + locked_node.node_state.missing_required_inputs -= 1; + this->schedule_node(locked_node); + return; + } + /* The value has not been computed yet, so when it will be forwarded by another node, this + * node will be triggered. */ + will_be_triggered_by_other_node = true; + + locked_node.delayed_required_outputs.append(DOutputSocket(origin_socket)); + } + /* If this node will be triggered by another node, we don't have to schedule it now. */ + if (!will_be_triggered_by_other_node) { + this->schedule_node(locked_node); + } + } + + void set_input_unused(LockedNode &locked_node, const DInputSocket socket) + { + InputState &input_state = locked_node.node_state.inputs[socket->index()]; + + /* A required socket cannot become unused. */ + BLI_assert(input_state.usage != ValueUsage::Required); + + if (input_state.usage == ValueUsage::Unused) { + /* Nothing to do in this case. */ + return; + } + input_state.usage = ValueUsage::Unused; + + /* If the input is unused, it's value can be destructed now. */ + this->destruct_input_value_if_exists(locked_node, socket); + + if (input_state.was_ready_for_execution) { + /* If the value was already computed, we don't need to notify origin nodes. */ + return; + } + + /* Notify origin nodes that might want to set its inputs as unused as well. */ + socket.foreach_origin_socket([&, this](const DSocket origin_socket) { + if (origin_socket->is_input()) { + /* Values from these sockets are loaded directly from the sockets, so there is no node to + * notify. */ + return; } + /* Delay notification of the other node until this node is not locked anymore. */ + locked_node.delayed_unused_outputs.append(DOutputSocket(origin_socket)); + }); + } + + void send_output_required_notification(const DOutputSocket socket) + { + const DNode node = socket.node(); + NodeState &node_state = this->get_node_state(node); + OutputState &output_state = node_state.outputs[socket->index()]; + + LockedNode locked_node{*this, node, node_state}; + if (output_state.output_usage == ValueUsage::Required) { + /* Output is marked as required already. So the node is scheduled already. */ + return; + } + /* The origin node needs to be scheduled so that it provides the requested input + * eventually. */ + output_state.output_usage = ValueUsage::Required; + this->schedule_node(locked_node); + } + + void send_output_unused_notification(const DOutputSocket socket) + { + const DNode node = socket.node(); + NodeState &node_state = this->get_node_state(node); + OutputState &output_state = node_state.outputs[socket->index()]; + + LockedNode locked_node{*this, node, node_state}; + output_state.potential_users -= 1; + if (output_state.potential_users == 0) { + /* The output socket has no users anymore. */ + output_state.output_usage = ValueUsage::Unused; + /* Schedule the origin node in case it wants to set its inputs as unused as well. */ + this->schedule_node(locked_node); } } - void forward_to_inputs(const DOutputSocket from_socket, GMutablePointer value_to_forward) + void add_node_to_task_pool(const DNode node) { - /* For all sockets that are linked with the from_socket push the value to their node. */ - Vector<DInputSocket> to_sockets_all; + /* Push the task to the pool while it is not locked to avoid a deadlock in case when the task + * is executed immediately. */ + const NodeWithState *node_with_state = node_states_.lookup_key_ptr_as(node); + BLI_task_pool_push( + task_pool_, run_node_from_task_pool, (void *)node_with_state, false, nullptr); + } + + /** + * Moves a newly computed value from an output socket to all the inputs that might need it. + */ + void forward_output(const DOutputSocket from_socket, GMutablePointer value_to_forward) + { + BLI_assert(value_to_forward.get() != nullptr); - auto handle_target_socket_fn = [&](DInputSocket to_socket) { - to_sockets_all.append_non_duplicates(to_socket); + Vector<DInputSocket> to_sockets; + auto handle_target_socket_fn = [&, this](const DInputSocket to_socket) { + if (this->should_forward_to_socket(to_socket)) { + to_sockets.append(to_socket); + } }; - auto handle_skipped_socket_fn = [&, this](DSocket socket) { + auto handle_skipped_socket_fn = [&, this](const DSocket socket) { + /* Log socket value on intermediate sockets to support e.g. attribute search or spreadsheet + * breadcrumbs on group nodes. */ this->log_socket_value(socket, value_to_forward); }; - from_socket.foreach_target_socket(handle_target_socket_fn, handle_skipped_socket_fn); + LinearAllocator<> &allocator = local_allocators_.local(); + const CPPType &from_type = *value_to_forward.type(); Vector<DInputSocket> to_sockets_same_type; - for (const DInputSocket &to_socket : to_sockets_all) { - const CPPType &to_type = *blender::nodes::socket_cpp_type_get(*to_socket->typeinfo()); - const std::pair<DInputSocket, DOutputSocket> key = std::make_pair(to_socket, from_socket); + for (const DInputSocket &to_socket : to_sockets) { + const CPPType &to_type = *get_socket_cpp_type(to_socket); if (from_type == to_type) { + /* All target sockets that do not need a conversion will be handled afterwards. */ to_sockets_same_type.append(to_socket); + continue; } - else { - void *buffer = allocator_.allocate(to_type.size(), to_type.alignment()); - if (conversions_.is_convertible(from_type, to_type)) { - conversions_.convert_to_uninitialized( - from_type, to_type, value_to_forward.get(), buffer); - } - else { - to_type.copy_to_uninitialized(to_type.default_value(), buffer); - } - add_value_to_input_socket(key, GMutablePointer{to_type, buffer}); - } + this->forward_to_socket_with_different_type( + allocator, value_to_forward, from_socket, to_socket, to_type); } + this->forward_to_sockets_with_same_type( + allocator, to_sockets_same_type, value_to_forward, from_socket); + } - if (to_sockets_same_type.size() == 0) { - /* This value is not further used, so destruct it. */ - value_to_forward.destruct(); + bool should_forward_to_socket(const DInputSocket socket) + { + const DNode to_node = socket.node(); + const NodeWithState *target_node_with_state = node_states_.lookup_key_ptr_as(to_node); + if (target_node_with_state == nullptr) { + /* If the socket belongs to a node that has no state, the entire node is not used. */ + return false; + } + NodeState &target_node_state = *target_node_with_state->state; + InputState &target_input_state = target_node_state.inputs[socket->index()]; + + std::lock_guard lock{target_node_state.mutex}; + /* Do not forward to an input socket whose value won't be used. */ + return target_input_state.usage != ValueUsage::Unused; + } + + void forward_to_socket_with_different_type(LinearAllocator<> &allocator, + const GPointer value_to_forward, + const DOutputSocket from_socket, + const DInputSocket to_socket, + const CPPType &to_type) + { + const CPPType &from_type = *value_to_forward.type(); + + /* Allocate a buffer for the converted value. */ + void *buffer = allocator.allocate(to_type.size(), to_type.alignment()); + + if (conversions_.is_convertible(from_type, to_type)) { + /* Do the conversion if possible. */ + conversions_.convert_to_uninitialized(from_type, to_type, value_to_forward.get(), buffer); } - else if (to_sockets_same_type.size() == 1) { - /* This value is only used on one input socket, no need to copy it. */ - const DInputSocket to_socket = to_sockets_same_type[0]; - const std::pair<DInputSocket, DOutputSocket> key = std::make_pair(to_socket, from_socket); + else { + /* Cannot convert, use default value instead. */ + to_type.copy_to_uninitialized(to_type.default_value(), buffer); + } + this->add_value_to_input_socket(to_socket, from_socket, {to_type, buffer}); + } - add_value_to_input_socket(key, value_to_forward); + void forward_to_sockets_with_same_type(LinearAllocator<> &allocator, + Span<DInputSocket> to_sockets, + GMutablePointer value_to_forward, + const DOutputSocket from_socket) + { + if (to_sockets.is_empty()) { + /* Value is not used anymore, so it can be destructed. */ + value_to_forward.destruct(); + } + else if (to_sockets.size() == 1) { + /* Value is only used by one input socket, no need to copy it. */ + const DInputSocket to_socket = to_sockets[0]; + this->add_value_to_input_socket(to_socket, from_socket, value_to_forward); } else { /* Multiple inputs use the value, make a copy for every input except for one. */ - const DInputSocket first_to_socket = to_sockets_same_type[0]; - Span<DInputSocket> other_to_sockets = to_sockets_same_type.as_span().drop_front(1); + /* First make the copies, so that the next node does not start modifying the value while we + * are still making copies. */ const CPPType &type = *value_to_forward.type(); - const std::pair<DInputSocket, DOutputSocket> first_key = std::make_pair(first_to_socket, - from_socket); - add_value_to_input_socket(first_key, value_to_forward); - for (const DInputSocket &to_socket : other_to_sockets) { - const std::pair<DInputSocket, DOutputSocket> key = std::make_pair(to_socket, from_socket); - void *buffer = allocator_.allocate(type.size(), type.alignment()); + for (const DInputSocket &to_socket : to_sockets.drop_front(1)) { + void *buffer = allocator.allocate(type.size(), type.alignment()); type.copy_to_uninitialized(value_to_forward.get(), buffer); - add_value_to_input_socket(key, GMutablePointer{type, buffer}); + this->add_value_to_input_socket(to_socket, from_socket, {type, buffer}); } + /* Forward the original value to one of the targets. */ + const DInputSocket to_socket = to_sockets[0]; + this->add_value_to_input_socket(to_socket, from_socket, value_to_forward); } } - void add_value_to_input_socket(const std::pair<DInputSocket, DOutputSocket> key, + void add_value_to_input_socket(const DInputSocket socket, + const DOutputSocket origin, GMutablePointer value) { - value_by_input_.add_new(key, value); + BLI_assert(socket->is_available()); + + const DNode node = socket.node(); + NodeState &node_state = this->get_node_state(node); + InputState &input_state = node_state.inputs[socket->index()]; + + /* Lock the node because we want to change its state. */ + LockedNode locked_node{*this, node, node_state}; + + if (socket->is_multi_input_socket()) { + /* Add a new value to the multi-input. */ + MultiInputValue &multi_value = *input_state.value.multi; + multi_value.items.append({origin, value.get()}); + } + else { + /* Assign the value to the input. */ + SingleInputValue &single_value = *input_state.value.single; + BLI_assert(single_value.value == nullptr); + single_value.value = value.get(); + } + + if (input_state.usage == ValueUsage::Required) { + node_state.missing_required_inputs--; + if (node_state.missing_required_inputs == 0) { + /* Schedule node if all the required inputs have been provided. */ + this->schedule_node(locked_node); + } + } } - GMutablePointer get_unlinked_input_value(const DInputSocket &socket, - const CPPType &required_type) + void load_unlinked_input_value(LockedNode &locked_node, + const DInputSocket input_socket, + InputState &input_state, + const DSocket origin_socket) { - bNodeSocket *bsocket = socket->bsocket(); - const CPPType &type = *blender::nodes::socket_cpp_type_get(*socket->typeinfo()); - void *buffer = allocator_.allocate(type.size(), type.alignment()); + /* Only takes locked node as parameter, because the node needs to be locked. */ + UNUSED_VARS(locked_node); - if (bsocket->type == SOCK_OBJECT) { - Object *object = socket->default_value<bNodeSocketValueObject>()->value; - PersistentObjectHandle object_handle = handle_map_.lookup(object); - new (buffer) PersistentObjectHandle(object_handle); + GMutablePointer value = this->get_value_from_socket(origin_socket, *input_state.type); + if (input_socket->is_multi_input_socket()) { + MultiInputValue &multi_value = *input_state.value.multi; + multi_value.items.append({input_socket, value.get()}); + } + else { + SingleInputValue &single_value = *input_state.value.single; + single_value.value = value.get(); } - else if (bsocket->type == SOCK_COLLECTION) { - Collection *collection = socket->default_value<bNodeSocketValueCollection>()->value; - PersistentCollectionHandle collection_handle = handle_map_.lookup(collection); - new (buffer) PersistentCollectionHandle(collection_handle); + } + + void destruct_input_value_if_exists(LockedNode &locked_node, const DInputSocket socket) + { + InputState &input_state = locked_node.node_state.inputs[socket->index()]; + if (socket->is_multi_input_socket()) { + MultiInputValue &multi_value = *input_state.value.multi; + for (MultiInputValueItem &item : multi_value.items) { + input_state.type->destruct(item.value); + } + multi_value.items.clear(); } else { - blender::nodes::socket_cpp_value_get(*bsocket, buffer); + SingleInputValue &single_value = *input_state.value.single; + if (single_value.value != nullptr) { + input_state.type->destruct(single_value.value); + single_value.value = nullptr; + } } + } + + GMutablePointer get_value_from_socket(const DSocket socket, const CPPType &required_type) + { + LinearAllocator<> &allocator = local_allocators_.local(); + + bNodeSocket *bsocket = socket->bsocket(); + const CPPType &type = *get_socket_cpp_type(socket); + void *buffer = allocator.allocate(type.size(), type.alignment()); + blender::nodes::socket_cpp_value_get(*bsocket, buffer); if (type == required_type) { return {type, buffer}; } if (conversions_.is_convertible(type, required_type)) { - void *converted_buffer = allocator_.allocate(required_type.size(), - required_type.alignment()); + /* Convert the loaded value to the required type if possible. */ + void *converted_buffer = allocator.allocate(required_type.size(), required_type.alignment()); conversions_.convert_to_uninitialized(type, required_type, buffer, converted_buffer); type.destruct(buffer); return {required_type, converted_buffer}; } - void *default_buffer = allocator_.allocate(required_type.size(), required_type.alignment()); + /* Use a default fallback value when the loaded type is not compatible. */ + void *default_buffer = allocator.allocate(required_type.size(), required_type.alignment()); required_type.copy_to_uninitialized(required_type.default_value(), default_buffer); return {required_type, default_buffer}; } + + NodeState &get_node_state(const DNode node) + { + return *node_states_.lookup_key_as(node).state; + } + + void log_socket_value(const DSocket socket, Span<GPointer> values) + { + if (params_.log_socket_value_fn) { + params_.log_socket_value_fn(socket, values); + } + } + + void log_socket_value(const DSocket socket, + InputState &input_state, + Span<MultiInputValueItem> values) + { + Vector<GPointer, 16> value_pointers; + value_pointers.reserve(values.size()); + const CPPType &type = *input_state.type; + for (const MultiInputValueItem &item : values) { + value_pointers.append({type, item.value}); + } + this->log_socket_value(socket, value_pointers); + } + + void log_socket_value(const DSocket socket, GPointer value) + { + this->log_socket_value(socket, Span<GPointer>(&value, 1)); + } }; +LockedNode::~LockedNode() +{ + /* First unlock the current node. */ + node_state.mutex.unlock(); + /* Then send notifications to the other nodes. */ + for (const DOutputSocket &socket : delayed_required_outputs) { + evaluator_.send_output_required_notification(socket); + } + for (const DOutputSocket &socket : delayed_unused_outputs) { + evaluator_.send_output_unused_notification(socket); + } + for (const DNode &node : delayed_scheduled_nodes) { + evaluator_.add_node_to_task_pool(node); + } +} + +/* TODO: Use a map data structure or so to make this faster. */ +static DInputSocket get_input_by_identifier(const DNode node, const StringRef identifier) +{ + for (const InputSocketRef *socket : node->inputs()) { + if (socket->identifier() == identifier) { + return {node.context(), socket}; + } + } + return {}; +} + +static DOutputSocket get_output_by_identifier(const DNode node, const StringRef identifier) +{ + for (const OutputSocketRef *socket : node->outputs()) { + if (socket->identifier() == identifier) { + return {node.context(), socket}; + } + } + return {}; +} + +NodeParamsProvider::NodeParamsProvider(GeometryNodesEvaluator &evaluator, + DNode dnode, + NodeState &node_state) + : evaluator_(evaluator), node_state_(node_state) +{ + this->dnode = dnode; + this->self_object = evaluator.params_.self_object; + this->modifier = &evaluator.params_.modifier_->modifier; + this->depsgraph = evaluator.params_.depsgraph; +} + +bool NodeParamsProvider::can_get_input(StringRef identifier) const +{ + const DInputSocket socket = get_input_by_identifier(this->dnode, identifier); + BLI_assert(socket); + + InputState &input_state = node_state_.inputs[socket->index()]; + if (!input_state.was_ready_for_execution) { + return false; + } + + if (socket->is_multi_input_socket()) { + MultiInputValue &multi_value = *input_state.value.multi; + return multi_value.items.size() == multi_value.expected_size; + } + SingleInputValue &single_value = *input_state.value.single; + return single_value.value != nullptr; +} + +bool NodeParamsProvider::can_set_output(StringRef identifier) const +{ + const DOutputSocket socket = get_output_by_identifier(this->dnode, identifier); + BLI_assert(socket); + + OutputState &output_state = node_state_.outputs[socket->index()]; + return !output_state.has_been_computed; +} + +GMutablePointer NodeParamsProvider::extract_input(StringRef identifier) +{ + const DInputSocket socket = get_input_by_identifier(this->dnode, identifier); + BLI_assert(socket); + BLI_assert(!socket->is_multi_input_socket()); + BLI_assert(this->can_get_input(identifier)); + + InputState &input_state = node_state_.inputs[socket->index()]; + SingleInputValue &single_value = *input_state.value.single; + void *value = single_value.value; + single_value.value = nullptr; + return {*input_state.type, value}; +} + +Vector<GMutablePointer> NodeParamsProvider::extract_multi_input(StringRef identifier) +{ + const DInputSocket socket = get_input_by_identifier(this->dnode, identifier); + BLI_assert(socket); + BLI_assert(socket->is_multi_input_socket()); + BLI_assert(this->can_get_input(identifier)); + + InputState &input_state = node_state_.inputs[socket->index()]; + MultiInputValue &multi_value = *input_state.value.multi; + + Vector<GMutablePointer> ret_values; + socket.foreach_origin_socket([&](DSocket origin) { + for (const MultiInputValueItem &item : multi_value.items) { + if (item.origin == origin) { + ret_values.append({*input_state.type, item.value}); + return; + } + } + BLI_assert_unreachable(); + }); + multi_value.items.clear(); + return ret_values; +} + +GPointer NodeParamsProvider::get_input(StringRef identifier) const +{ + const DInputSocket socket = get_input_by_identifier(this->dnode, identifier); + BLI_assert(socket); + BLI_assert(!socket->is_multi_input_socket()); + BLI_assert(this->can_get_input(identifier)); + + InputState &input_state = node_state_.inputs[socket->index()]; + SingleInputValue &single_value = *input_state.value.single; + return {*input_state.type, single_value.value}; +} + +GMutablePointer NodeParamsProvider::alloc_output_value(const CPPType &type) +{ + LinearAllocator<> &allocator = evaluator_.local_allocators_.local(); + return {type, allocator.allocate(type.size(), type.alignment())}; +} + +void NodeParamsProvider::set_output(StringRef identifier, GMutablePointer value) +{ + const DOutputSocket socket = get_output_by_identifier(this->dnode, identifier); + BLI_assert(socket); + + evaluator_.log_socket_value(socket, value); + + OutputState &output_state = node_state_.outputs[socket->index()]; + BLI_assert(!output_state.has_been_computed); + evaluator_.forward_output(socket, value); + output_state.has_been_computed = true; +} + +bool NodeParamsProvider::lazy_require_input(StringRef identifier) +{ + BLI_assert(node_supports_lazyness(this->dnode)); + const DInputSocket socket = get_input_by_identifier(this->dnode, identifier); + BLI_assert(socket); + + InputState &input_state = node_state_.inputs[socket->index()]; + if (input_state.was_ready_for_execution) { + return false; + } + LockedNode locked_node{evaluator_, this->dnode, node_state_}; + evaluator_.set_input_required(locked_node, socket); + return true; +} + +void NodeParamsProvider::set_input_unused(StringRef identifier) +{ + const DInputSocket socket = get_input_by_identifier(this->dnode, identifier); + BLI_assert(socket); + + LockedNode locked_node{evaluator_, this->dnode, node_state_}; + evaluator_.set_input_unused(locked_node, socket); +} + +bool NodeParamsProvider::output_is_required(StringRef identifier) const +{ + const DOutputSocket socket = get_output_by_identifier(this->dnode, identifier); + BLI_assert(socket); + + OutputState &output_state = node_state_.outputs[socket->index()]; + if (output_state.has_been_computed) { + return false; + } + return output_state.output_usage_for_execution != ValueUsage::Unused; +} + +bool NodeParamsProvider::lazy_output_is_required(StringRef identifier) const +{ + BLI_assert(node_supports_lazyness(this->dnode)); + const DOutputSocket socket = get_output_by_identifier(this->dnode, identifier); + BLI_assert(socket); + + OutputState &output_state = node_state_.outputs[socket->index()]; + if (output_state.has_been_computed) { + return false; + } + return output_state.output_usage_for_execution == ValueUsage::Required; +} + void evaluate_geometry_nodes(GeometryNodesEvaluationParams ¶ms) { GeometryNodesEvaluator evaluator{params}; - params.r_output_values = evaluator.execute(); + evaluator.execute(); } } // namespace blender::modifiers::geometry_nodes diff --git a/source/blender/modifiers/intern/MOD_nodes_evaluator.hh b/source/blender/modifiers/intern/MOD_nodes_evaluator.hh index 3d9a03350f7..84249e4244e 100644 --- a/source/blender/modifiers/intern/MOD_nodes_evaluator.hh +++ b/source/blender/modifiers/intern/MOD_nodes_evaluator.hh @@ -23,14 +23,11 @@ #include "FN_generic_pointer.hh" -#include "BKE_persistent_data_handle.hh" - #include "DNA_modifier_types.h" namespace blender::modifiers::geometry_nodes { using namespace nodes::derived_node_tree_types; -using bke::PersistentDataHandleMap; using fn::GMutablePointer; using fn::GPointer; @@ -42,7 +39,6 @@ struct GeometryNodesEvaluationParams { Map<DOutputSocket, GMutablePointer> input_values; Vector<DInputSocket> output_sockets; nodes::MultiFunctionByNode *mf_by_node; - const PersistentDataHandleMap *handle_map; const NodesModifierData *modifier_; Depsgraph *depsgraph; Object *self_object; diff --git a/source/blender/nodes/CMakeLists.txt b/source/blender/nodes/CMakeLists.txt index c116ceb8968..1096f2c9eb4 100644 --- a/source/blender/nodes/CMakeLists.txt +++ b/source/blender/nodes/CMakeLists.txt @@ -145,6 +145,7 @@ set(SRC geometry/nodes/node_geo_attribute_combine_xyz.cc geometry/nodes/node_geo_attribute_compare.cc geometry/nodes/node_geo_attribute_convert.cc + geometry/nodes/node_geo_attribute_curve_map.cc geometry/nodes/node_geo_attribute_fill.cc geometry/nodes/node_geo_attribute_map_range.cc geometry/nodes/node_geo_attribute_math.cc @@ -156,14 +157,18 @@ set(SRC geometry/nodes/node_geo_attribute_separate_xyz.cc geometry/nodes/node_geo_attribute_transfer.cc geometry/nodes/node_geo_attribute_vector_math.cc + geometry/nodes/node_geo_attribute_vector_rotate.cc geometry/nodes/node_geo_boolean.cc geometry/nodes/node_geo_bounding_box.cc geometry/nodes/node_geo_collection_info.cc geometry/nodes/node_geo_common.cc geometry/nodes/node_geo_curve_to_mesh.cc + geometry/nodes/node_geo_curve_resample.cc geometry/nodes/node_geo_edge_split.cc + geometry/nodes/node_geo_input_material.cc geometry/nodes/node_geo_is_viewport.cc geometry/nodes/node_geo_join_geometry.cc + geometry/nodes/node_geo_material_assign.cc geometry/nodes/node_geo_mesh_primitive_circle.cc geometry/nodes/node_geo_mesh_primitive_cone.cc geometry/nodes/node_geo_mesh_primitive_cube.cc diff --git a/source/blender/nodes/NOD_derived_node_tree.hh b/source/blender/nodes/NOD_derived_node_tree.hh index e294bef2ea8..7ff05449c0b 100644 --- a/source/blender/nodes/NOD_derived_node_tree.hh +++ b/source/blender/nodes/NOD_derived_node_tree.hh @@ -92,6 +92,9 @@ class DNode { operator bool() const; uint64_t hash() const; + + DInputSocket input(int index) const; + DOutputSocket output(int index) const; }; /* A (nullable) reference to a socket and the context it is in. It is unique within an entire @@ -274,6 +277,16 @@ inline uint64_t DNode::hash() const return get_default_hash_2(context_, node_ref_); } +inline DInputSocket DNode::input(int index) const +{ + return {context_, &node_ref_->input(index)}; +} + +inline DOutputSocket DNode::output(int index) const +{ + return {context_, &node_ref_->output(index)}; +} + /* -------------------------------------------------------------------- * DSocket inline methods. */ diff --git a/source/blender/nodes/NOD_geometry.h b/source/blender/nodes/NOD_geometry.h index 14c9e235bb6..d43cd5044ee 100644 --- a/source/blender/nodes/NOD_geometry.h +++ b/source/blender/nodes/NOD_geometry.h @@ -35,6 +35,7 @@ void register_node_type_geo_attribute_color_ramp(void); void register_node_type_geo_attribute_combine_xyz(void); void register_node_type_geo_attribute_compare(void); void register_node_type_geo_attribute_convert(void); +void register_node_type_geo_attribute_curve_map(void); void register_node_type_geo_attribute_fill(void); void register_node_type_geo_attribute_map_range(void); void register_node_type_geo_attribute_math(void); @@ -44,14 +45,18 @@ void register_node_type_geo_attribute_randomize(void); void register_node_type_geo_attribute_separate_xyz(void); void register_node_type_geo_attribute_transfer(void); void register_node_type_geo_attribute_vector_math(void); +void register_node_type_geo_attribute_vector_rotate(void); void register_node_type_geo_attribute_remove(void); void register_node_type_geo_boolean(void); void register_node_type_geo_bounding_box(void); void register_node_type_geo_collection_info(void); void register_node_type_geo_curve_to_mesh(void); +void register_node_type_geo_curve_resample(void); void register_node_type_geo_edge_split(void); +void register_node_type_geo_input_material(void); void register_node_type_geo_is_viewport(void); void register_node_type_geo_join_geometry(void); +void register_node_type_geo_material_assign(void); void register_node_type_geo_mesh_primitive_circle(void); void register_node_type_geo_mesh_primitive_cone(void); void register_node_type_geo_mesh_primitive_cube(void); diff --git a/source/blender/nodes/NOD_geometry_exec.hh b/source/blender/nodes/NOD_geometry_exec.hh index 83f084c2f25..52d7e097f0d 100644 --- a/source/blender/nodes/NOD_geometry_exec.hh +++ b/source/blender/nodes/NOD_geometry_exec.hh @@ -22,7 +22,6 @@ #include "BKE_geometry_set.hh" #include "BKE_geometry_set_instances.hh" #include "BKE_node_ui_storage.hh" -#include "BKE_persistent_data_handle.hh" #include "DNA_node_types.h" @@ -36,8 +35,6 @@ namespace blender::nodes { using bke::geometry_set_realize_instances; using bke::OutputAttribute; using bke::OutputAttribute_Typed; -using bke::PersistentDataHandleMap; -using bke::PersistentObjectHandle; using bke::ReadAttributeLookup; using bke::WriteAttributeLookup; using fn::CPPType; @@ -63,7 +60,6 @@ using fn::GVMutableArrayPtr; class GeoNodeExecParamsProvider { public: DNode dnode; - const PersistentDataHandleMap *handle_map = nullptr; const Object *self_object = nullptr; const ModifierData *modifier = nullptr; Depsgraph *depsgraph = nullptr; @@ -100,7 +96,18 @@ class GeoNodeExecParamsProvider { * Prepare a memory buffer for an output value of the node. The returned memory has to be * initialized by the caller. The identifier and type are expected to be correct. */ - virtual GMutablePointer alloc_output_value(StringRef identifier, const CPPType &type) = 0; + virtual GMutablePointer alloc_output_value(const CPPType &type) = 0; + + /** + * The value has been allocated with #alloc_output_value. + */ + virtual void set_output(StringRef identifier, GMutablePointer value) = 0; + + /* A description for these methods is provided in GeoNodeExecParams. */ + virtual void set_input_unused(StringRef identifier) = 0; + virtual bool output_is_required(StringRef identifier) const = 0; + virtual bool lazy_require_input(StringRef identifier) = 0; + virtual bool lazy_output_is_required(StringRef identifier) const = 0; }; class GeoNodeExecParams { @@ -178,21 +185,56 @@ class GeoNodeExecParams { #ifdef DEBUG this->check_output_access(identifier, type); #endif - GMutablePointer gvalue = provider_->alloc_output_value(identifier, type); + GMutablePointer gvalue = provider_->alloc_output_value(type); new (gvalue.get()) StoredT(std::forward<T>(value)); + provider_->set_output(identifier, gvalue); } /** - * Get the node that is currently being executed. + * Tell the evaluator that a specific input won't be used anymore. */ - const bNode &node() const + void set_input_unused(StringRef identifier) { - return *provider_->dnode->bnode(); + provider_->set_input_unused(identifier); } - const PersistentDataHandleMap &handle_map() const + /** + * Returns true when the output has to be computed. + * Nodes that support lazyness could use the #lazy_output_is_required variant to possibly avoid + * some computations. + */ + bool output_is_required(StringRef identifier) const + { + return provider_->output_is_required(identifier); + } + + /** + * Tell the evaluator that a specific input is required. + * This returns true when the input will only be available in the next execution. + * False is returned if the input is available already. + * This can only be used when the node supports lazyness. + */ + bool lazy_require_input(StringRef identifier) { - return *provider_->handle_map; + return provider_->lazy_require_input(identifier); + } + + /** + * Asks the evaluator if a specific output is required right now. If this returns false, the + * value might still need to be computed later. + * This can only be used when the node supports lazyness. + */ + bool lazy_output_is_required(StringRef identifier) + { + return provider_->lazy_output_is_required(identifier); + } + + /** + * Get the node that is currently being executed. + */ + const bNode &node() const + { + return *provider_->dnode->bnode(); } const Object *self_object() const diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h index 2e4187229f4..b49eb1b1d99 100644 --- a/source/blender/nodes/NOD_static_types.h +++ b/source/blender/nodes/NOD_static_types.h @@ -263,56 +263,61 @@ DefNode(TextureNode, TEX_NODE_PROC+TEX_DISTNOISE, 0, "TEX_DI DefNode(FunctionNode, FN_NODE_BOOLEAN_MATH, def_boolean_math, "BOOLEAN_MATH", BooleanMath, "Boolean Math", "") DefNode(FunctionNode, FN_NODE_FLOAT_COMPARE, def_float_compare, "FLOAT_COMPARE", FloatCompare, "Float Compare", "") -DefNode(FunctionNode, FN_NODE_RANDOM_FLOAT, 0, "RANDOM_FLOAT", RandomFloat, "Random Float", "") -DefNode(FunctionNode, FN_NODE_INPUT_VECTOR, def_fn_input_vector, "INPUT_VECTOR", InputVector, "Vector", "") DefNode(FunctionNode, FN_NODE_INPUT_STRING, def_fn_input_string, "INPUT_STRING", InputString, "String", "") +DefNode(FunctionNode, FN_NODE_INPUT_VECTOR, def_fn_input_vector, "INPUT_VECTOR", InputVector, "Vector", "") +DefNode(FunctionNode, FN_NODE_RANDOM_FLOAT, 0, "RANDOM_FLOAT", RandomFloat, "Random Float", "") -DefNode(GeometryNode, GEO_NODE_TRIANGULATE, def_geo_triangulate, "TRIANGULATE", Triangulate, "Triangulate", "") -DefNode(GeometryNode, GEO_NODE_EDGE_SPLIT, 0, "EDGE_SPLIT", EdgeSplit, "Edge Split", "") -DefNode(GeometryNode, GEO_NODE_TRANSFORM, 0, "TRANSFORM", Transform, "Transform", "") -DefNode(GeometryNode, GEO_NODE_SUBDIVISION_SURFACE, 0, "SUBDIVISION_SURFACE", SubdivisionSurface, "Subdivision Surface", "") -DefNode(GeometryNode, GEO_NODE_BOOLEAN, def_geo_boolean, "BOOLEAN", Boolean, "Boolean", "") -DefNode(GeometryNode, GEO_NODE_POINT_DISTRIBUTE, def_geo_point_distribute, "POINT_DISTRIBUTE", PointDistribute, "Point Distribute", "") -DefNode(GeometryNode, GEO_NODE_POINT_INSTANCE, def_geo_point_instance, "POINT_INSTANCE", PointInstance, "Point Instance", "") -DefNode(GeometryNode, GEO_NODE_OBJECT_INFO, def_geo_object_info, "OBJECT_INFO", ObjectInfo, "Object Info", "") -DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_RANDOMIZE, def_geo_attribute_randomize, "ATTRIBUTE_RANDOMIZE", AttributeRandomize, "Attribute Randomize", "") -DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_MATH, def_geo_attribute_math, "ATTRIBUTE_MATH", AttributeMath, "Attribute Math", "") -DefNode(GeometryNode, GEO_NODE_JOIN_GEOMETRY, 0, "JOIN_GEOMETRY", JoinGeometry, "Join Geometry", "") -DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_FILL, def_geo_attribute_fill, "ATTRIBUTE_FILL", AttributeFill, "Attribute Fill", "") -DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_MIX, def_geo_attribute_mix, "ATTRIBUTE_MIX", AttributeMix, "Attribute Mix", "") +DefNode(GeometryNode, GEO_NODE_ALIGN_ROTATION_TO_VECTOR, def_geo_align_rotation_to_vector, "ALIGN_ROTATION_TO_VECTOR", AlignRotationToVector, "Align Rotation to Vector", "") +DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_CLAMP, def_geo_attribute_clamp, "ATTRIBUTE_CLAMP", AttributeClamp, "Attribute Clamp", "") DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_COLOR_RAMP, def_geo_attribute_color_ramp, "ATTRIBUTE_COLOR_RAMP", AttributeColorRamp, "Attribute Color Ramp", "") -DefNode(GeometryNode, GEO_NODE_POINT_SEPARATE, 0, "POINT_SEPARATE", PointSeparate, "Point Separate", "") +DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_COMBINE_XYZ, def_geo_attribute_combine_xyz, "ATTRIBUTE_COMBINE_XYZ", AttributeCombineXYZ, "Attribute Combine XYZ", "") DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_COMPARE, def_geo_attribute_attribute_compare, "ATTRIBUTE_COMPARE", AttributeCompare, "Attribute Compare", "") -DefNode(GeometryNode, GEO_NODE_POINT_ROTATE, def_geo_point_rotate, "POINT_ROTATE", RotatePoints, "Point Rotate", "") +DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_CONVERT, def_geo_attribute_convert, "ATTRIBUTE_CONVERT", AttributeConvert, "Attribute Convert", "") +DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_CURVE_MAP, def_geo_attribute_curve_map, "ATTRIBUTE_CURVE_MAP", AttributeCurveMap, "Attribute Curve Map", "") +DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_FILL, def_geo_attribute_fill, "ATTRIBUTE_FILL", AttributeFill, "Attribute Fill", "") +DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_MAP_RANGE, def_geo_attribute_map_range, "ATTRIBUTE_MAP_RANGE", AttributeMapRange, "Attribute Map Range", "") +DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_MATH, def_geo_attribute_math, "ATTRIBUTE_MATH", AttributeMath, "Attribute Math", "") +DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_MIX, def_geo_attribute_mix, "ATTRIBUTE_MIX", AttributeMix, "Attribute Mix", "") +DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_PROXIMITY, def_geo_attribute_proximity, "ATTRIBUTE_PROXIMITY", AttributeProximity, "Attribute Proximity", "") +DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_RANDOMIZE, def_geo_attribute_randomize, "ATTRIBUTE_RANDOMIZE", AttributeRandomize, "Attribute Randomize", "") +DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_REMOVE, 0, "ATTRIBUTE_REMOVE", AttributeRemove, "Attribute Remove", "") +DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_SAMPLE_TEXTURE, 0, "ATTRIBUTE_SAMPLE_TEXTURE", AttributeSampleTexture, "Attribute Sample Texture", "") +DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_SEPARATE_XYZ, def_geo_attribute_separate_xyz, "ATTRIBUTE_SEPARATE_XYZ", AttributeSeparateXYZ, "Attribute Separate XYZ", "") +DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_TRANSFER, def_geo_attribute_transfer, "ATTRIBUTE_TRANSFER", AttributeTransfer, "Attribute Transfer", "") DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_VECTOR_MATH, def_geo_attribute_vector_math, "ATTRIBUTE_VECTOR_MATH", AttributeVectorMath, "Attribute Vector Math", "") -DefNode(GeometryNode, GEO_NODE_ALIGN_ROTATION_TO_VECTOR, def_geo_align_rotation_to_vector, "ALIGN_ROTATION_TO_VECTOR", AlignRotationToVector, "Align Rotation to Vector", "") -DefNode(GeometryNode, GEO_NODE_POINT_SCALE, def_geo_point_scale, "POINT_SCALE", PointScale, "Point Scale", "") -DefNode(GeometryNode, GEO_NODE_POINT_TRANSLATE, def_geo_point_translate, "POINT_TRANSLATE", PointTranslate, "Point Translate", "") -DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_SAMPLE_TEXTURE, def_geo_attribute_sample_texture, "ATTRIBUTE_SAMPLE_TEXTURE", AttributeSampleTexture, "Attribute Sample Texture", "") -DefNode(GeometryNode, GEO_NODE_POINTS_TO_VOLUME, def_geo_points_to_volume, "POINTS_TO_VOLUME", PointsToVolume, "Points to Volume", "") +DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_VECTOR_ROTATE, def_geo_attribute_vector_rotate, "ATTRIBUTE_VECTOR_ROTATE", AttributeVectorRotate, "Attribute Vector Rotate", "") +DefNode(GeometryNode, GEO_NODE_BOOLEAN, def_geo_boolean, "BOOLEAN", Boolean, "Boolean", "") +DefNode(GeometryNode, GEO_NODE_BOUNDING_BOX, 0, "BOUNDING_BOX", BoundBox, "Bounding Box", "") DefNode(GeometryNode, GEO_NODE_COLLECTION_INFO, def_geo_collection_info, "COLLECTION_INFO", CollectionInfo, "Collection Info", "") +DefNode(GeometryNode, GEO_NODE_CURVE_RESAMPLE, def_geo_curve_resample, "CURVE_RESAMPLE", CurveResample, "Resample Curve", "") +DefNode(GeometryNode, GEO_NODE_CURVE_TO_MESH, 0, "CURVE_TO_MESH", CurveToMesh, "Curve to Mesh", "") +DefNode(GeometryNode, GEO_NODE_EDGE_SPLIT, 0, "EDGE_SPLIT", EdgeSplit, "Edge Split", "") +DefNode(GeometryNode, GEO_NODE_INPUT_MATERIAL, def_geo_input_material, "INPUT_MATERIAL", InputMaterial, "Material", "") DefNode(GeometryNode, GEO_NODE_IS_VIEWPORT, 0, "IS_VIEWPORT", IsViewport, "Is Viewport", "") -DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_PROXIMITY, def_geo_attribute_proximity, "ATTRIBUTE_PROXIMITY", AttributeProximity, "Attribute Proximity", "") -DefNode(GeometryNode, GEO_NODE_VOLUME_TO_MESH, def_geo_volume_to_mesh, "VOLUME_TO_MESH", VolumeToMesh, "Volume to Mesh", "") -DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_COMBINE_XYZ, def_geo_attribute_combine_xyz, "ATTRIBUTE_COMBINE_XYZ", AttributeCombineXYZ, "Attribute Combine XYZ", "") -DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_SEPARATE_XYZ, def_geo_attribute_separate_xyz, "ATTRIBUTE_SEPARATE_XYZ", AttributeSeparateXYZ, "Attribute Separate XYZ", "") -DefNode(GeometryNode, GEO_NODE_SUBDIVIDE, 0, "SUBDIVIDE", Subdivide, "Subdivide", "") -DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_REMOVE, 0, "ATTRIBUTE_REMOVE", AttributeRemove, "Attribute Remove", "") -DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_CONVERT, def_geo_attribute_convert, "ATTRIBUTE_CONVERT", AttributeConvert, "Attribute Convert", "") -DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_CUBE, 0, "MESH_PRIMITIVE_CUBE", MeshCube, "Cube", "") +DefNode(GeometryNode, GEO_NODE_JOIN_GEOMETRY, 0, "JOIN_GEOMETRY", JoinGeometry, "Join Geometry", "") +DefNode(GeometryNode, GEO_NODE_MATERIAL_ASSIGN, 0, "MATERIAL_ASSIGN", MaterialAssign, "Material Assign", "") DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_CIRCLE, def_geo_mesh_circle, "MESH_PRIMITIVE_CIRCLE", MeshCircle, "Circle", "") -DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_UV_SPHERE, 0, "MESH_PRIMITIVE_UV_SPHERE", MeshUVSphere, "UV Sphere", "") +DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_CONE, def_geo_mesh_cone, "MESH_PRIMITIVE_CONE", MeshCone, "Cone", "") +DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_CUBE, 0, "MESH_PRIMITIVE_CUBE", MeshCube, "Cube", "") DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_CYLINDER, def_geo_mesh_cylinder, "MESH_PRIMITIVE_CYLINDER", MeshCylinder, "Cylinder", "") +DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_GRID, 0, "MESH_PRIMITIVE_GRID", MeshGrid, "Grid", "") DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_ICO_SPHERE, 0, "MESH_PRIMITIVE_ICO_SPHERE", MeshIcoSphere, "Ico Sphere", "") -DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_CONE, def_geo_mesh_cone, "MESH_PRIMITIVE_CONE", MeshCone, "Cone", "") DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_LINE, def_geo_mesh_line, "MESH_PRIMITIVE_LINE", MeshLine, "Line", "") -DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_GRID, 0, "MESH_PRIMITIVE_GRID", MeshGrid, "Grid", "") -DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_MAP_RANGE, def_geo_attribute_map_range, "ATTRIBUTE_MAP_RANGE", AttributeMapRange, "Attribute Map Range", "") -DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_CLAMP, def_geo_attribute_clamp, "ATTRIBUTE_CLAMP", AttributeClamp, "Attribute Clamp", "") -DefNode(GeometryNode, GEO_NODE_BOUNDING_BOX, 0, "BOUNDING_BOX", BoundBox, "Bounding Box", "") +DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_UV_SPHERE, 0, "MESH_PRIMITIVE_UV_SPHERE", MeshUVSphere, "UV Sphere", "") +DefNode(GeometryNode, GEO_NODE_OBJECT_INFO, def_geo_object_info, "OBJECT_INFO", ObjectInfo, "Object Info", "") +DefNode(GeometryNode, GEO_NODE_POINT_DISTRIBUTE, def_geo_point_distribute, "POINT_DISTRIBUTE", PointDistribute, "Point Distribute", "") +DefNode(GeometryNode, GEO_NODE_POINT_INSTANCE, def_geo_point_instance, "POINT_INSTANCE", PointInstance, "Point Instance", "") +DefNode(GeometryNode, GEO_NODE_POINT_ROTATE, def_geo_point_rotate, "POINT_ROTATE", RotatePoints, "Point Rotate", "") +DefNode(GeometryNode, GEO_NODE_POINT_SCALE, def_geo_point_scale, "POINT_SCALE", PointScale, "Point Scale", "") +DefNode(GeometryNode, GEO_NODE_POINT_SEPARATE, 0, "POINT_SEPARATE", PointSeparate, "Point Separate", "") +DefNode(GeometryNode, GEO_NODE_POINT_TRANSLATE, def_geo_point_translate, "POINT_TRANSLATE", PointTranslate, "Point Translate", "") +DefNode(GeometryNode, GEO_NODE_POINTS_TO_VOLUME, def_geo_points_to_volume, "POINTS_TO_VOLUME", PointsToVolume, "Points to Volume", "") +DefNode(GeometryNode, GEO_NODE_SUBDIVIDE, 0, "SUBDIVIDE", Subdivide, "Subdivide", "") +DefNode(GeometryNode, GEO_NODE_SUBDIVISION_SURFACE, 0, "SUBDIVISION_SURFACE", SubdivisionSurface, "Subdivision Surface", "") DefNode(GeometryNode, GEO_NODE_SWITCH, def_geo_switch, "SWITCH", Switch, "Switch", "") -DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_TRANSFER, def_geo_attribute_transfer, "ATTRIBUTE_TRANSFER", AttributeTransfer, "Attribute Transfer", "") -DefNode(GeometryNode, GEO_NODE_CURVE_TO_MESH, 0, "CURVE_TO_MESH", CurveToMesh, "Curve to Mesh", "") +DefNode(GeometryNode, GEO_NODE_TRANSFORM, 0, "TRANSFORM", Transform, "Transform", "") +DefNode(GeometryNode, GEO_NODE_TRIANGULATE, def_geo_triangulate, "TRIANGULATE", Triangulate, "Triangulate", "") +DefNode(GeometryNode, GEO_NODE_VOLUME_TO_MESH, def_geo_volume_to_mesh, "VOLUME_TO_MESH", VolumeToMesh, "Volume to Mesh", "") /* undefine macros */ #undef DefNode diff --git a/source/blender/nodes/geometry/node_geometry_tree.cc b/source/blender/nodes/geometry/node_geometry_tree.cc index afb86ff57b8..f4cd00b88ed 100644 --- a/source/blender/nodes/geometry/node_geometry_tree.cc +++ b/source/blender/nodes/geometry/node_geometry_tree.cc @@ -84,6 +84,17 @@ static void foreach_nodeclass(Scene *UNUSED(scene), void *calldata, bNodeClassCa func(calldata, NODE_CLASS_LAYOUT, N_("Layout")); } +static bool geometry_node_tree_validate_link(bNodeTree *UNUSED(ntree), bNodeLink *link) +{ + /* Geometry, string, object, material, texture and collection sockets can only be connected to + * themselves. The other types can be converted between each other. */ + if (ELEM(link->fromsock->type, SOCK_FLOAT, SOCK_VECTOR, SOCK_RGBA, SOCK_BOOLEAN, SOCK_INT) && + ELEM(link->tosock->type, SOCK_FLOAT, SOCK_VECTOR, SOCK_RGBA, SOCK_BOOLEAN, SOCK_INT)) { + return true; + } + return (link->tosock->type == link->fromsock->type); +} + static bool geometry_node_tree_socket_type_valid(eNodeSocketDatatype socket_type, bNodeTreeType *UNUSED(ntreetype)) { @@ -96,7 +107,9 @@ static bool geometry_node_tree_socket_type_valid(eNodeSocketDatatype socket_type SOCK_STRING, SOCK_OBJECT, SOCK_GEOMETRY, - SOCK_COLLECTION); + SOCK_COLLECTION, + SOCK_TEXTURE, + SOCK_MATERIAL); } void register_node_tree_type_geo(void) @@ -113,6 +126,7 @@ void register_node_tree_type_geo(void) tt->get_from_context = geometry_node_tree_get_from_context; tt->foreach_nodeclass = foreach_nodeclass; tt->valid_socket_type = geometry_node_tree_socket_type_valid; + tt->validate_link = geometry_node_tree_validate_link; ntreeTypeAdd(tt); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_align_rotation_to_vector.cc b/source/blender/nodes/geometry/nodes/node_geo_align_rotation_to_vector.cc index 720ca9731a8..d1b71d6f2ba 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_align_rotation_to_vector.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_align_rotation_to_vector.cc @@ -51,6 +51,28 @@ static void geo_node_align_rotation_to_vector_layout(uiLayout *layout, namespace blender::nodes { +static void geo_node_align_rotation_to_vector_init(bNodeTree *UNUSED(ntree), bNode *node) +{ + NodeGeometryAlignRotationToVector *node_storage = (NodeGeometryAlignRotationToVector *) + MEM_callocN(sizeof(NodeGeometryAlignRotationToVector), __func__); + + node_storage->axis = GEO_NODE_ALIGN_ROTATION_TO_VECTOR_AXIS_X; + node_storage->input_type_factor = GEO_NODE_ATTRIBUTE_INPUT_FLOAT; + node_storage->input_type_vector = GEO_NODE_ATTRIBUTE_INPUT_VECTOR; + + node->storage = node_storage; +} + +static void geo_node_align_rotation_to_vector_update(bNodeTree *UNUSED(ntree), bNode *node) +{ + NodeGeometryAlignRotationToVector *node_storage = (NodeGeometryAlignRotationToVector *) + node->storage; + update_attribute_input_socket_availabilities( + *node, "Factor", (GeometryNodeAttributeInputMode)node_storage->input_type_factor); + update_attribute_input_socket_availabilities( + *node, "Vector", (GeometryNodeAttributeInputMode)node_storage->input_type_vector); +} + static void align_rotations_auto_pivot(const VArray<float3> &vectors, const VArray<float> &factors, const float3 local_main_axis, @@ -195,28 +217,6 @@ static void geo_node_align_rotation_to_vector_exec(GeoNodeExecParams params) params.set_output("Geometry", geometry_set); } -static void geo_node_align_rotation_to_vector_init(bNodeTree *UNUSED(ntree), bNode *node) -{ - NodeGeometryAlignRotationToVector *node_storage = (NodeGeometryAlignRotationToVector *) - MEM_callocN(sizeof(NodeGeometryAlignRotationToVector), __func__); - - node_storage->axis = GEO_NODE_ALIGN_ROTATION_TO_VECTOR_AXIS_X; - node_storage->input_type_factor = GEO_NODE_ATTRIBUTE_INPUT_FLOAT; - node_storage->input_type_vector = GEO_NODE_ATTRIBUTE_INPUT_VECTOR; - - node->storage = node_storage; -} - -static void geo_node_align_rotation_to_vector_update(bNodeTree *UNUSED(ntree), bNode *node) -{ - NodeGeometryAlignRotationToVector *node_storage = (NodeGeometryAlignRotationToVector *) - node->storage; - update_attribute_input_socket_availabilities( - *node, "Factor", (GeometryNodeAttributeInputMode)node_storage->input_type_factor); - update_attribute_input_socket_availabilities( - *node, "Vector", (GeometryNodeAttributeInputMode)node_storage->input_type_vector); -} - } // namespace blender::nodes void register_node_type_geo_align_rotation_to_vector() diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_color_ramp.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_color_ramp.cc index 26ddb0da515..b13e82e676d 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_color_ramp.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_color_ramp.cc @@ -44,6 +44,14 @@ static void geo_node_attribute_color_ramp_layout(uiLayout *layout, namespace blender::nodes { +static void geo_node_attribute_color_ramp_init(bNodeTree *UNUSED(ntree), bNode *node) +{ + NodeAttributeColorRamp *node_storage = (NodeAttributeColorRamp *)MEM_callocN( + sizeof(NodeAttributeColorRamp), __func__); + BKE_colorband_init(&node_storage->color_ramp, true); + node->storage = node_storage; +} + static AttributeDomain get_result_domain(const GeometryComponent &component, StringRef input_name, StringRef result_name) @@ -115,14 +123,6 @@ static void geo_node_attribute_color_ramp_exec(GeoNodeExecParams params) params.set_output("Geometry", std::move(geometry_set)); } -static void geo_node_attribute_color_ramp_init(bNodeTree *UNUSED(ntree), bNode *node) -{ - NodeAttributeColorRamp *node_storage = (NodeAttributeColorRamp *)MEM_callocN( - sizeof(NodeAttributeColorRamp), __func__); - BKE_colorband_init(&node_storage->color_ramp, true); - node->storage = node_storage; -} - } // namespace blender::nodes void register_node_type_geo_attribute_color_ramp() diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_curve_map.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_curve_map.cc new file mode 100644 index 00000000000..2fc86269797 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_curve_map.cc @@ -0,0 +1,231 @@ +/* + * 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. + */ + +#include "BLI_blenlib.h" +#include "BLI_task.hh" + +#include "BKE_colortools.h" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "node_geometry_util.hh" + +static bNodeSocketTemplate geo_node_attribute_curve_map_in[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {SOCK_STRING, N_("Attribute")}, + {SOCK_STRING, N_("Result")}, + {-1, ""}, +}; + +static bNodeSocketTemplate geo_node_attribute_curve_map_out[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {-1, ""}, +}; + +static void geo_node_attribute_curve_map_layout(uiLayout *layout, + bContext *UNUSED(C), + PointerRNA *ptr) +{ + uiItemR(layout, ptr, "data_type", 0, "", ICON_NONE); + bNode *node = (bNode *)ptr->data; + NodeAttributeCurveMap *data = (NodeAttributeCurveMap *)node->storage; + switch (data->data_type) { + case CD_PROP_FLOAT: + uiTemplateCurveMapping(layout, ptr, "curve_vec", 0, false, false, false, false); + break; + case CD_PROP_FLOAT3: + uiTemplateCurveMapping(layout, ptr, "curve_vec", 'v', false, false, false, false); + break; + case CD_PROP_COLOR: + uiTemplateCurveMapping(layout, ptr, "curve_rgb", 'c', false, false, false, false); + break; + } +} + +static void geo_node_attribute_curve_map_free_storage(bNode *node) +{ + if (node->storage) { + NodeAttributeCurveMap *data = (NodeAttributeCurveMap *)node->storage; + BKE_curvemapping_free(data->curve_vec); + BKE_curvemapping_free(data->curve_rgb); + MEM_freeN(node->storage); + } +} + +static void geo_node_attribute_curve_map_copy_storage(bNodeTree *UNUSED(dest_ntree), + bNode *dest_node, + const bNode *src_node) +{ + dest_node->storage = MEM_dupallocN(src_node->storage); + NodeAttributeCurveMap *src_data = (NodeAttributeCurveMap *)src_node->storage; + NodeAttributeCurveMap *dest_data = (NodeAttributeCurveMap *)dest_node->storage; + dest_data->curve_vec = BKE_curvemapping_copy(src_data->curve_vec); + dest_data->curve_rgb = BKE_curvemapping_copy(src_data->curve_rgb); +} + +static void geo_node_attribute_curve_map_init(bNodeTree *UNUSED(ntree), bNode *node) +{ + NodeAttributeCurveMap *data = (NodeAttributeCurveMap *)MEM_callocN(sizeof(NodeAttributeCurveMap), + __func__); + + data->data_type = CD_PROP_FLOAT; + data->curve_vec = BKE_curvemapping_add(4, -1.0f, -1.0f, 1.0f, 1.0f); + data->curve_vec->cur = 3; + data->curve_rgb = BKE_curvemapping_add(4, 0.0f, 0.0f, 1.0f, 1.0f); + node->storage = data; +} + +static void geo_node_attribute_curve_map_update(bNodeTree *UNUSED(ntree), bNode *node) +{ + /* Set the active curve when data type is changed. */ + NodeAttributeCurveMap *data = (NodeAttributeCurveMap *)node->storage; + if (data->data_type == CD_PROP_FLOAT) { + data->curve_vec->cur = 3; + } + else if (data->data_type == CD_PROP_FLOAT3) { + data->curve_vec->cur = 0; + } +} + +namespace blender::nodes { + +static AttributeDomain get_result_domain(const GeometryComponent &component, + StringRef input_name, + StringRef result_name) +{ + /* Use the domain of the result attribute if it already exists. */ + ReadAttributeLookup result_attribute = component.attribute_try_get_for_read(result_name); + if (result_attribute) { + return result_attribute.domain; + } + + /* Otherwise use the input attribute's domain if it exists. */ + ReadAttributeLookup input_attribute = component.attribute_try_get_for_read(input_name); + if (input_attribute) { + return input_attribute.domain; + } + + return ATTR_DOMAIN_POINT; +} + +static void execute_on_component(const GeoNodeExecParams ¶ms, GeometryComponent &component) +{ + const bNode &bnode = params.node(); + NodeAttributeCurveMap &node_storage = *(NodeAttributeCurveMap *)bnode.storage; + const std::string result_name = params.get_input<std::string>("Result"); + const std::string input_name = params.get_input<std::string>("Attribute"); + + const CustomDataType result_type = (CustomDataType)node_storage.data_type; + const AttributeDomain result_domain = get_result_domain(component, input_name, result_name); + + OutputAttribute attribute_result = component.attribute_try_get_for_output_only( + result_name, result_domain, result_type); + if (!attribute_result) { + return; + } + + switch (result_type) { + case CD_PROP_FLOAT: { + const CurveMapping *cumap = (CurveMapping *)node_storage.curve_vec; + GVArray_Typed<float> attribute_in = component.attribute_get_for_read<float>( + input_name, result_domain, float(0.0f)); + MutableSpan<float> results = attribute_result.as_span<float>(); + parallel_for(IndexRange(attribute_in.size()), 512, [&](IndexRange range) { + for (const int i : range) { + results[i] = BKE_curvemapping_evaluateF(cumap, 3, attribute_in[i]); + } + }); + break; + } + case CD_PROP_FLOAT3: { + const CurveMapping *cumap = (CurveMapping *)node_storage.curve_vec; + GVArray_Typed<float3> attribute_in = component.attribute_get_for_read<float3>( + input_name, result_domain, float3(0.0f)); + MutableSpan<float3> results = attribute_result.as_span<float3>(); + parallel_for(IndexRange(attribute_in.size()), 512, [&](IndexRange range) { + for (const int i : range) { + BKE_curvemapping_evaluate3F(cumap, results[i], attribute_in[i]); + } + }); + break; + } + case CD_PROP_COLOR: { + const CurveMapping *cumap = (CurveMapping *)node_storage.curve_rgb; + GVArray_Typed<Color4f> attribute_in = component.attribute_get_for_read<Color4f>( + input_name, result_domain, Color4f(0.0f, 0.0f, 0.0f, 1.0f)); + MutableSpan<Color4f> results = attribute_result.as_span<Color4f>(); + parallel_for(IndexRange(attribute_in.size()), 512, [&](IndexRange range) { + for (const int i : range) { + BKE_curvemapping_evaluateRGBF(cumap, results[i], attribute_in[i]); + } + }); + break; + } + default: { + BLI_assert_unreachable(); + break; + } + } + + attribute_result.save(); +} + +static void geo_node_attribute_curve_map_exec(GeoNodeExecParams params) +{ + const bNode &bnode = params.node(); + NodeAttributeCurveMap *data = (NodeAttributeCurveMap *)bnode.storage; + BKE_curvemapping_init(data->curve_vec); + BKE_curvemapping_init(data->curve_rgb); + + GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + + geometry_set = geometry_set_realize_instances(geometry_set); + + if (geometry_set.has<MeshComponent>()) { + execute_on_component(params, geometry_set.get_component_for_write<MeshComponent>()); + } + if (geometry_set.has<PointCloudComponent>()) { + execute_on_component(params, geometry_set.get_component_for_write<PointCloudComponent>()); + } + if (geometry_set.has<CurveComponent>()) { + execute_on_component(params, geometry_set.get_component_for_write<CurveComponent>()); + } + + params.set_output("Geometry", std::move(geometry_set)); +} + +} // namespace blender::nodes + +void register_node_type_geo_attribute_curve_map() +{ + static bNodeType ntype; + + geo_node_type_base( + &ntype, GEO_NODE_ATTRIBUTE_CURVE_MAP, "Attribute Curve Map", NODE_CLASS_ATTRIBUTE, 0); + node_type_socket_templates( + &ntype, geo_node_attribute_curve_map_in, geo_node_attribute_curve_map_out); + node_type_update(&ntype, geo_node_attribute_curve_map_update); + node_type_init(&ntype, geo_node_attribute_curve_map_init); + node_type_size_preset(&ntype, NODE_SIZE_LARGE); + node_type_storage(&ntype, + "NodeAttributeCurveMap", + geo_node_attribute_curve_map_free_storage, + geo_node_attribute_curve_map_copy_storage); + ntype.geometry_node_execute = blender::nodes::geo_node_attribute_curve_map_exec; + ntype.draw_buttons = geo_node_attribute_curve_map_layout; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_mix.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_mix.cc index 3e5326edbf6..e502a183ef5 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_mix.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_mix.cc @@ -59,6 +59,28 @@ static void geo_node_attribute_mix_layout(uiLayout *layout, bContext *UNUSED(C), namespace blender::nodes { +static void geo_node_attribute_mix_init(bNodeTree *UNUSED(ntree), bNode *node) +{ + NodeAttributeMix *data = (NodeAttributeMix *)MEM_callocN(sizeof(NodeAttributeMix), + "attribute mix node"); + data->blend_type = MA_RAMP_BLEND; + data->input_type_factor = GEO_NODE_ATTRIBUTE_INPUT_FLOAT; + data->input_type_a = GEO_NODE_ATTRIBUTE_INPUT_ATTRIBUTE; + data->input_type_b = GEO_NODE_ATTRIBUTE_INPUT_ATTRIBUTE; + node->storage = data; +} + +static void geo_node_attribute_mix_update(bNodeTree *UNUSED(ntree), bNode *node) +{ + NodeAttributeMix *node_storage = (NodeAttributeMix *)node->storage; + update_attribute_input_socket_availabilities( + *node, "Factor", (GeometryNodeAttributeInputMode)node_storage->input_type_factor); + update_attribute_input_socket_availabilities( + *node, "A", (GeometryNodeAttributeInputMode)node_storage->input_type_a); + update_attribute_input_socket_availabilities( + *node, "B", (GeometryNodeAttributeInputMode)node_storage->input_type_b); +} + static void do_mix_operation_float(const int blend_mode, const VArray<float> &factors, const VArray<float> &inputs_a, @@ -216,28 +238,6 @@ static void geo_node_attribute_mix_exec(GeoNodeExecParams params) params.set_output("Geometry", geometry_set); } -static void geo_node_attribute_mix_init(bNodeTree *UNUSED(ntree), bNode *node) -{ - NodeAttributeMix *data = (NodeAttributeMix *)MEM_callocN(sizeof(NodeAttributeMix), - "attribute mix node"); - data->blend_type = MA_RAMP_BLEND; - data->input_type_factor = GEO_NODE_ATTRIBUTE_INPUT_FLOAT; - data->input_type_a = GEO_NODE_ATTRIBUTE_INPUT_ATTRIBUTE; - data->input_type_b = GEO_NODE_ATTRIBUTE_INPUT_ATTRIBUTE; - node->storage = data; -} - -static void geo_node_attribute_mix_update(bNodeTree *UNUSED(ntree), bNode *node) -{ - NodeAttributeMix *node_storage = (NodeAttributeMix *)node->storage; - update_attribute_input_socket_availabilities( - *node, "Factor", (GeometryNodeAttributeInputMode)node_storage->input_type_factor); - update_attribute_input_socket_availabilities( - *node, "A", (GeometryNodeAttributeInputMode)node_storage->input_type_a); - update_attribute_input_socket_availabilities( - *node, "B", (GeometryNodeAttributeInputMode)node_storage->input_type_b); -} - } // namespace blender::nodes void register_node_type_geo_attribute_mix() diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_sample_texture.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_sample_texture.cc index 59790e5e46c..aa558314b9e 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_sample_texture.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_sample_texture.cc @@ -30,6 +30,7 @@ static bNodeSocketTemplate geo_node_attribute_sample_texture_in[] = { {SOCK_GEOMETRY, N_("Geometry")}, + {SOCK_TEXTURE, N_("Texture")}, {SOCK_STRING, N_("Mapping")}, {SOCK_STRING, N_("Result")}, {-1, ""}, @@ -40,13 +41,6 @@ static bNodeSocketTemplate geo_node_attribute_sample_texture_out[] = { {-1, ""}, }; -static void geo_node_attribute_sample_texture_layout(uiLayout *layout, - bContext *C, - PointerRNA *ptr) -{ - uiTemplateID(layout, C, ptr, "texture", "texture.new", nullptr, nullptr, 0, ICON_NONE, nullptr); -} - namespace blender::nodes { static AttributeDomain get_result_domain(const GeometryComponent &component, @@ -71,8 +65,7 @@ static AttributeDomain get_result_domain(const GeometryComponent &component, static void execute_on_component(GeometryComponent &component, const GeoNodeExecParams ¶ms) { - const bNode &node = params.node(); - Tex *texture = reinterpret_cast<Tex *>(node.id); + Tex *texture = params.get_input<Tex *>("Texture"); if (texture == nullptr) { return; } @@ -144,6 +137,5 @@ void register_node_type_geo_sample_texture() node_type_socket_templates( &ntype, geo_node_attribute_sample_texture_in, geo_node_attribute_sample_texture_out); ntype.geometry_node_execute = blender::nodes::geo_node_attribute_sample_texture_exec; - ntype.draw_buttons = geo_node_attribute_sample_texture_layout; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_vector_rotate.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_vector_rotate.cc new file mode 100644 index 00000000000..4d568ab5c3a --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_vector_rotate.cc @@ -0,0 +1,352 @@ +/* + * 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. + */ + +#include "node_geometry_util.hh" + +#include "BLI_task.hh" + +#include "UI_interface.h" +#include "UI_resources.h" + +static bNodeSocketTemplate geo_node_attribute_vector_rotate_in[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {SOCK_STRING, N_("Vector")}, + {SOCK_VECTOR, N_("Vector"), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, PROP_NONE, SOCK_HIDE_VALUE}, + {SOCK_STRING, N_("Center")}, + {SOCK_VECTOR, N_("Center"), 0.0f, 0.0f, 0.0f, 1.0f, -FLT_MAX, FLT_MAX, PROP_XYZ}, + {SOCK_STRING, N_("Axis")}, + {SOCK_VECTOR, N_("Axis"), 0.0f, 0.0f, 1.0f, 0.0f, -1.0f, 1.0f, PROP_XYZ, PROP_NONE}, + {SOCK_STRING, N_("Angle")}, + {SOCK_FLOAT, N_("Angle"), 0.0f, 0.0f, 0.0f, 1.0f, -FLT_MAX, FLT_MAX, PROP_ANGLE, PROP_NONE}, + {SOCK_STRING, N_("Rotation")}, + {SOCK_VECTOR, N_("Rotation"), 0.0f, 0.0f, 0.0f, 1.0f, -FLT_MAX, FLT_MAX, PROP_EULER}, + {SOCK_BOOLEAN, N_("Invert")}, + {SOCK_STRING, N_("Result")}, + {-1, ""}, +}; + +static bNodeSocketTemplate geo_node_attribute_vector_rotate_out[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {-1, ""}, +}; + +static void geo_node_attribute_vector_rotate_layout(uiLayout *layout, + bContext *UNUSED(C), + PointerRNA *ptr) +{ + bNode *node = (bNode *)ptr->data; + const NodeAttributeVectorRotate &node_storage = *(NodeAttributeVectorRotate *)node->storage; + const GeometryNodeAttributeVectorRotateMode mode = (const GeometryNodeAttributeVectorRotateMode) + node_storage.mode; + + uiLayoutSetPropSep(layout, true); + uiLayoutSetPropDecorate(layout, false); + uiLayout *column = uiLayoutColumn(layout, false); + + uiItemR(column, ptr, "rotation_mode", 0, "", ICON_NONE); + + uiItemR(column, ptr, "input_type_vector", 0, IFACE_("Vector"), ICON_NONE); + uiItemR(column, ptr, "input_type_center", 0, IFACE_("Center"), ICON_NONE); + if (mode == GEO_NODE_VECTOR_ROTATE_TYPE_AXIS) { + uiItemR(column, ptr, "input_type_axis", 0, IFACE_("Axis"), ICON_NONE); + } + if (mode != GEO_NODE_VECTOR_ROTATE_TYPE_EULER_XYZ) { + uiItemR(column, ptr, "input_type_angle", 0, IFACE_("Angle"), ICON_NONE); + } + if (mode == GEO_NODE_VECTOR_ROTATE_TYPE_EULER_XYZ) { + uiItemR(column, ptr, "input_type_rotation", 0, IFACE_("Rotation"), ICON_NONE); + } +} + +namespace blender::nodes { + +static void geo_node_attribute_vector_rotate_update(bNodeTree *UNUSED(ntree), bNode *node) +{ + const NodeAttributeVectorRotate *node_storage = (NodeAttributeVectorRotate *)node->storage; + const GeometryNodeAttributeVectorRotateMode mode = (const GeometryNodeAttributeVectorRotateMode) + node_storage->mode; + + update_attribute_input_socket_availabilities( + *node, "Vector", (GeometryNodeAttributeInputMode)node_storage->input_type_vector); + update_attribute_input_socket_availabilities( + *node, "Center", (GeometryNodeAttributeInputMode)node_storage->input_type_center); + update_attribute_input_socket_availabilities( + *node, + "Axis", + (GeometryNodeAttributeInputMode)node_storage->input_type_axis, + (mode == GEO_NODE_VECTOR_ROTATE_TYPE_AXIS)); + update_attribute_input_socket_availabilities( + *node, + "Angle", + (GeometryNodeAttributeInputMode)node_storage->input_type_angle, + (mode != GEO_NODE_VECTOR_ROTATE_TYPE_EULER_XYZ)); + update_attribute_input_socket_availabilities( + *node, + "Rotation", + (GeometryNodeAttributeInputMode)node_storage->input_type_rotation, + (mode == GEO_NODE_VECTOR_ROTATE_TYPE_EULER_XYZ)); +} + +static float3 vector_rotate_around_axis(const float3 vector, + const float3 center, + const float3 axis, + const float angle) +{ + float3 result = vector - center; + float mat[3][3]; + axis_angle_to_mat3(mat, axis, angle); + mul_m3_v3(mat, result); + return result + center; +} + +static void geo_node_attribute_vector_rotate_init(bNodeTree *UNUSED(ntree), bNode *node) +{ + NodeAttributeVectorRotate *node_storage = (NodeAttributeVectorRotate *)MEM_callocN( + sizeof(NodeAttributeVectorRotate), __func__); + + node_storage->mode = GEO_NODE_VECTOR_ROTATE_TYPE_AXIS; + node_storage->input_type_vector = GEO_NODE_ATTRIBUTE_INPUT_ATTRIBUTE; + node_storage->input_type_center = GEO_NODE_ATTRIBUTE_INPUT_VECTOR; + node_storage->input_type_axis = GEO_NODE_ATTRIBUTE_INPUT_VECTOR; + node_storage->input_type_angle = GEO_NODE_ATTRIBUTE_INPUT_FLOAT; + node_storage->input_type_rotation = GEO_NODE_ATTRIBUTE_INPUT_VECTOR; + + node->storage = node_storage; +} + +static float3 vector_rotate_euler(const float3 vector, + const float3 center, + const float3 rotation, + const bool invert) +{ + float mat[3][3]; + float3 result = vector - center; + eul_to_mat3(mat, rotation); + if (invert) { + invert_m3(mat); + } + mul_m3_v3(mat, result); + return result + center; +} + +static void do_vector_rotate_around_axis(const VArray<float3> &vector, + const VArray<float3> ¢er, + const VArray<float3> &axis, + const VArray<float> &angle, + MutableSpan<float3> results, + const bool invert) +{ + VArray_Span<float3> span_vector{vector}; + VArray_Span<float3> span_center{center}; + VArray_Span<float3> span_axis{axis}; + VArray_Span<float> span_angle{angle}; + + parallel_for(IndexRange(results.size()), 1024, [&](IndexRange range) { + for (const int i : range) { + float angle = (invert) ? -span_angle[i] : span_angle[i]; + results[i] = vector_rotate_around_axis(span_vector[i], span_center[i], span_axis[i], angle); + } + }); +} + +static void do_vector_rotate_around_fixed_axis(const VArray<float3> &vector, + const VArray<float3> ¢er, + const float3 axis, + const VArray<float> &angle, + MutableSpan<float3> results, + const bool invert) +{ + VArray_Span<float3> span_vector{vector}; + VArray_Span<float3> span_center{center}; + VArray_Span<float> span_angle{angle}; + + parallel_for(IndexRange(results.size()), 1024, [&](IndexRange range) { + for (const int i : range) { + float angle = (invert) ? -span_angle[i] : span_angle[i]; + results[i] = vector_rotate_around_axis(span_vector[i], span_center[i], axis, angle); + } + }); +} + +static void do_vector_rotate_euler(const VArray<float3> &vector, + const VArray<float3> ¢er, + const VArray<float3> &rotation, + MutableSpan<float3> results, + const bool invert) +{ + VArray_Span<float3> span_vector{vector}; + VArray_Span<float3> span_center{center}; + VArray_Span<float3> span_rotation{rotation}; + + parallel_for(IndexRange(results.size()), 1024, [&](IndexRange range) { + for (const int i : range) { + results[i] = vector_rotate_euler(span_vector[i], span_center[i], span_rotation[i], invert); + } + }); +} + +static AttributeDomain get_result_domain(const GeometryComponent &component, + const GeoNodeExecParams ¶ms, + StringRef result_name) +{ + /* Use the domain of the result attribute if it already exists. */ + std::optional<AttributeMetaData> meta_data = component.attribute_get_meta_data(result_name); + if (meta_data) { + return meta_data->domain; + } + + /* Otherwise use the highest priority domain from existing input attributes, or the default. */ + const AttributeDomain default_domain = ATTR_DOMAIN_POINT; + return params.get_highest_priority_input_domain({"Vector", "Center"}, component, default_domain); +} + +static void execute_on_component(const GeoNodeExecParams ¶ms, GeometryComponent &component) +{ + const bNode &node = params.node(); + const NodeAttributeVectorRotate *node_storage = (const NodeAttributeVectorRotate *)node.storage; + const GeometryNodeAttributeVectorRotateMode mode = (GeometryNodeAttributeVectorRotateMode) + node_storage->mode; + const std::string result_name = params.get_input<std::string>("Result"); + const AttributeDomain result_domain = get_result_domain(component, params, result_name); + const bool invert = params.get_input<bool>("Invert"); + + GVArrayPtr attribute_vector = params.get_input_attribute( + "Vector", component, result_domain, CD_PROP_FLOAT3, nullptr); + if (!attribute_vector) { + return; + } + GVArrayPtr attribute_center = params.get_input_attribute( + "Center", component, result_domain, CD_PROP_FLOAT3, nullptr); + if (!attribute_center) { + return; + } + + OutputAttribute attribute_result = component.attribute_try_get_for_output_only( + result_name, result_domain, CD_PROP_FLOAT3); + if (!attribute_result) { + return; + } + + if (mode == GEO_NODE_VECTOR_ROTATE_TYPE_EULER_XYZ) { + GVArrayPtr attribute_rotation = params.get_input_attribute( + "Rotation", component, result_domain, CD_PROP_FLOAT3, nullptr); + if (!attribute_rotation) { + return; + } + do_vector_rotate_euler(attribute_vector->typed<float3>(), + attribute_center->typed<float3>(), + attribute_rotation->typed<float3>(), + attribute_result.as_span<float3>(), + invert); + attribute_result.save(); + return; + } + + GVArrayPtr attribute_angle = params.get_input_attribute( + "Angle", component, result_domain, CD_PROP_FLOAT, nullptr); + if (!attribute_angle) { + return; + } + + switch (mode) { + case GEO_NODE_VECTOR_ROTATE_TYPE_AXIS: { + GVArrayPtr attribute_axis = params.get_input_attribute( + "Axis", component, result_domain, CD_PROP_FLOAT3, nullptr); + if (!attribute_axis) { + return; + } + do_vector_rotate_around_axis(attribute_vector->typed<float3>(), + attribute_center->typed<float3>(), + attribute_axis->typed<float3>(), + attribute_angle->typed<float>(), + attribute_result.as_span<float3>(), + invert); + } break; + case GEO_NODE_VECTOR_ROTATE_TYPE_AXIS_X: + do_vector_rotate_around_fixed_axis(attribute_vector->typed<float3>(), + attribute_center->typed<float3>(), + float3(1.0f, 0.0f, 0.0f), + attribute_angle->typed<float>(), + attribute_result.as_span<float3>(), + invert); + break; + case GEO_NODE_VECTOR_ROTATE_TYPE_AXIS_Y: + do_vector_rotate_around_fixed_axis(attribute_vector->typed<float3>(), + attribute_center->typed<float3>(), + float3(0.0f, 1.0f, 0.0f), + attribute_angle->typed<float>(), + attribute_result.as_span<float3>(), + invert); + + break; + case GEO_NODE_VECTOR_ROTATE_TYPE_AXIS_Z: + do_vector_rotate_around_fixed_axis(attribute_vector->typed<float3>(), + attribute_center->typed<float3>(), + float3(0.0f, 0.0f, 1.0f), + attribute_angle->typed<float>(), + attribute_result.as_span<float3>(), + invert); + + break; + case GEO_NODE_VECTOR_ROTATE_TYPE_EULER_XYZ: + /* Euler is handled before other modes to avoid processing the unavailable angle socket. */ + BLI_assert_unreachable(); + break; + } + attribute_result.save(); +} + +static void geo_node_attribute_vector_rotate_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + + geometry_set = geometry_set_realize_instances(geometry_set); + + if (geometry_set.has<MeshComponent>()) { + execute_on_component(params, geometry_set.get_component_for_write<MeshComponent>()); + } + if (geometry_set.has<PointCloudComponent>()) { + execute_on_component(params, geometry_set.get_component_for_write<PointCloudComponent>()); + } + if (geometry_set.has<CurveComponent>()) { + execute_on_component(params, geometry_set.get_component_for_write<CurveComponent>()); + } + + params.set_output("Geometry", std::move(geometry_set)); +} + +} // namespace blender::nodes + +void register_node_type_geo_attribute_vector_rotate() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, + GEO_NODE_ATTRIBUTE_VECTOR_ROTATE, + "Attribute Vector Rotate", + NODE_CLASS_ATTRIBUTE, + 0); + node_type_socket_templates( + &ntype, geo_node_attribute_vector_rotate_in, geo_node_attribute_vector_rotate_out); + node_type_update(&ntype, blender::nodes::geo_node_attribute_vector_rotate_update); + node_type_init(&ntype, blender::nodes::geo_node_attribute_vector_rotate_init); + node_type_size(&ntype, 165, 100, 600); + node_type_storage( + &ntype, "NodeAttributeVectorRotate", node_free_standard_storage, node_copy_standard_storage); + ntype.geometry_node_execute = blender::nodes::geo_node_attribute_vector_rotate_exec; + ntype.draw_buttons = geo_node_attribute_vector_rotate_layout; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_collection_info.cc b/source/blender/nodes/geometry/nodes/node_geo_collection_info.cc index 5800d46b70d..9bc8a4bb4be 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_collection_info.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_collection_info.cc @@ -40,11 +40,17 @@ static void geo_node_collection_info_layout(uiLayout *layout, bContext *UNUSED(C namespace blender::nodes { +static void geo_node_collection_info_node_init(bNodeTree *UNUSED(tree), bNode *node) +{ + NodeGeometryCollectionInfo *data = (NodeGeometryCollectionInfo *)MEM_callocN( + sizeof(NodeGeometryCollectionInfo), __func__); + data->transform_space = GEO_NODE_TRANSFORM_SPACE_ORIGINAL; + node->storage = data; +} + static void geo_node_collection_info_exec(GeoNodeExecParams params) { - bke::PersistentCollectionHandle collection_handle = - params.extract_input<bke::PersistentCollectionHandle>("Collection"); - Collection *collection = params.handle_map().lookup(collection_handle); + Collection *collection = params.get_input<Collection *>("Collection"); GeometrySet geometry_set_out; @@ -76,14 +82,6 @@ static void geo_node_collection_info_exec(GeoNodeExecParams params) params.set_output("Geometry", geometry_set_out); } -static void geo_node_collection_info_node_init(bNodeTree *UNUSED(tree), bNode *node) -{ - NodeGeometryCollectionInfo *data = (NodeGeometryCollectionInfo *)MEM_callocN( - sizeof(NodeGeometryCollectionInfo), __func__); - data->transform_space = GEO_NODE_TRANSFORM_SPACE_ORIGINAL; - node->storage = data; -} - } // namespace blender::nodes void register_node_type_geo_collection_info() diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_resample.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_resample.cc new file mode 100644 index 00000000000..1c42b9341a0 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_resample.cc @@ -0,0 +1,238 @@ +/* + * 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. + */ + +#include "BLI_array.hh" +#include "BLI_task.hh" +#include "BLI_timeit.hh" + +#include "BKE_attribute_math.hh" +#include "BKE_spline.hh" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "node_geometry_util.hh" + +using blender::fn::GVArray_For_GSpan; +using blender::fn::GVArray_For_Span; +using blender::fn::GVArray_Typed; + +static bNodeSocketTemplate geo_node_curve_resample_in[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {SOCK_INT, N_("Count"), 10, 0, 0, 0, 1, 100000}, + {SOCK_FLOAT, N_("Length"), 0.1f, 0.0f, 0.0f, 0.0f, 0.001f, FLT_MAX, PROP_DISTANCE}, + {-1, ""}, +}; + +static bNodeSocketTemplate geo_node_curve_resample_out[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {-1, ""}, +}; + +static void geo_node_curve_resample_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +{ + uiItemR(layout, ptr, "mode", UI_ITEM_R_EXPAND, nullptr, ICON_NONE); +} + +static void geo_node_curve_resample_init(bNodeTree *UNUSED(tree), bNode *node) +{ + NodeGeometryCurveResample *data = (NodeGeometryCurveResample *)MEM_callocN( + sizeof(NodeGeometryCurveResample), __func__); + + data->mode = GEO_NODE_CURVE_SAMPLE_COUNT; + node->storage = data; +} + +static void geo_node_curve_resample_update(bNodeTree *UNUSED(ntree), bNode *node) +{ + NodeGeometryCurveResample &node_storage = *(NodeGeometryCurveResample *)node->storage; + const GeometryNodeCurveSampleMode mode = (GeometryNodeCurveSampleMode)node_storage.mode; + + bNodeSocket *count_socket = ((bNodeSocket *)node->inputs.first)->next; + bNodeSocket *length_socket = count_socket->next; + + nodeSetSocketAvailability(count_socket, mode == GEO_NODE_CURVE_SAMPLE_COUNT); + nodeSetSocketAvailability(length_socket, mode == GEO_NODE_CURVE_SAMPLE_LENGTH); +} + +namespace blender::nodes { + +struct SampleModeParam { + GeometryNodeCurveSampleMode mode; + std::optional<float> length; + std::optional<int> count; +}; + +template<typename T> +static void sample_span_to_output_spline(const Spline &input_spline, + Span<float> index_factors, + const VArray<T> &input_data, + MutableSpan<T> output_data) +{ + BLI_assert(input_data.size() == input_spline.evaluated_points_size()); + + parallel_for(output_data.index_range(), 1024, [&](IndexRange range) { + for (const int i : range) { + const Spline::LookupResult interp = input_spline.lookup_data_from_index_factor( + index_factors[i]); + output_data[i] = blender::attribute_math::mix2(interp.factor, + input_data[interp.evaluated_index], + input_data[interp.next_evaluated_index]); + } + }); +} + +static SplinePtr resample_spline(const Spline &input_spline, const int count) +{ + std::unique_ptr<PolySpline> output_spline = std::make_unique<PolySpline>(); + output_spline->set_cyclic(input_spline.is_cyclic()); + output_spline->normal_mode = input_spline.normal_mode; + + if (input_spline.evaluated_edges_size() < 1) { + output_spline->resize(1); + output_spline->positions().first() = input_spline.positions().first(); + return output_spline; + } + + output_spline->resize(count); + + Array<float> uniform_samples = input_spline.sample_uniform_index_factors(count); + + { + GVArray_For_Span positions(input_spline.evaluated_positions()); + GVArray_Typed<float3> positions_typed(positions); + sample_span_to_output_spline<float3>( + input_spline, uniform_samples, positions_typed, output_spline->positions()); + } + { + GVArrayPtr interpolated_data = input_spline.interpolate_to_evaluated_points( + GVArray_For_Span(input_spline.radii())); + GVArray_Typed<float> interpolated_data_typed{*interpolated_data}; + sample_span_to_output_spline<float>( + input_spline, uniform_samples, interpolated_data_typed, output_spline->radii()); + } + { + GVArrayPtr interpolated_data = input_spline.interpolate_to_evaluated_points( + GVArray_For_Span(input_spline.tilts())); + GVArray_Typed<float> interpolated_data_typed{*interpolated_data}; + sample_span_to_output_spline<float>( + input_spline, uniform_samples, interpolated_data_typed, output_spline->tilts()); + } + + output_spline->attributes.reallocate(count); + input_spline.attributes.foreach_attribute( + [&](StringRefNull name, const AttributeMetaData &meta_data) { + std::optional<GSpan> input_attribute = input_spline.attributes.get_for_read(name); + BLI_assert(input_attribute); + if (!output_spline->attributes.create(name, meta_data.data_type)) { + BLI_assert_unreachable(); + return false; + } + std::optional<GMutableSpan> output_attribute = output_spline->attributes.get_for_write( + name); + if (!output_attribute) { + BLI_assert_unreachable(); + return false; + } + GVArrayPtr interpolated_attribute = input_spline.interpolate_to_evaluated_points( + GVArray_For_GSpan(*input_attribute)); + attribute_math::convert_to_static_type(meta_data.data_type, [&](auto dummy) { + using T = decltype(dummy); + GVArray_Typed<T> interpolated_attribute_typed{*interpolated_attribute}; + sample_span_to_output_spline<T>(input_spline, + uniform_samples, + interpolated_attribute_typed, + (*output_attribute).typed<T>()); + }); + return true; + }, + ATTR_DOMAIN_POINT); + + return output_spline; +} + +static std::unique_ptr<CurveEval> resample_curve(const CurveEval &input_curve, + const SampleModeParam &mode_param) +{ + std::unique_ptr<CurveEval> output_curve = std::make_unique<CurveEval>(); + + for (const SplinePtr &spline : input_curve.splines()) { + if (mode_param.mode == GEO_NODE_CURVE_SAMPLE_COUNT) { + BLI_assert(mode_param.count); + output_curve->add_spline(resample_spline(*spline, *mode_param.count)); + } + else if (mode_param.mode == GEO_NODE_CURVE_SAMPLE_LENGTH) { + BLI_assert(mode_param.length); + const float length = spline->length(); + const int count = length / *mode_param.length; + output_curve->add_spline(resample_spline(*spline, count)); + } + } + + return output_curve; +} + +static void geo_node_resample_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + + geometry_set = bke::geometry_set_realize_instances(geometry_set); + + if (!geometry_set.has_curve()) { + params.set_output("Geometry", GeometrySet()); + return; + } + + const CurveEval &input_curve = *geometry_set.get_curve_for_read(); + NodeGeometryCurveResample &node_storage = *(NodeGeometryCurveResample *)params.node().storage; + const GeometryNodeCurveSampleMode mode = (GeometryNodeCurveSampleMode)node_storage.mode; + SampleModeParam mode_param; + mode_param.mode = mode; + if (mode == GEO_NODE_CURVE_SAMPLE_COUNT) { + const int count = params.extract_input<int>("Count"); + if (count < 1) { + params.set_output("Geometry", GeometrySet()); + return; + } + mode_param.count.emplace(count); + } + else if (mode == GEO_NODE_CURVE_SAMPLE_LENGTH) { + /* Don't allow asymptotic count increase for low resolution values. */ + const float resolution = std::max(params.extract_input<float>("Length"), 0.0001f); + mode_param.length.emplace(resolution); + } + + std::unique_ptr<CurveEval> output_curve = resample_curve(input_curve, mode_param); + + params.set_output("Geometry", GeometrySet::create_with_curve(output_curve.release())); +} + +} // namespace blender::nodes + +void register_node_type_geo_curve_resample() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_CURVE_RESAMPLE, "Resample Curve", NODE_CLASS_GEOMETRY, 0); + node_type_socket_templates(&ntype, geo_node_curve_resample_in, geo_node_curve_resample_out); + ntype.draw_buttons = geo_node_curve_resample_layout; + node_type_storage( + &ntype, "NodeGeometryCurveResample", node_free_standard_storage, node_copy_standard_storage); + node_type_init(&ntype, geo_node_curve_resample_init); + node_type_update(&ntype, geo_node_curve_resample_update); + ntype.geometry_node_execute = blender::nodes::geo_node_resample_exec; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc index 071504ad8df..f0effdc71f6 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc @@ -58,7 +58,7 @@ static void vert_extrude_to_mesh_data(const Spline &spline, edge.flag = ME_LOOSEEDGE; } - if (spline.is_cyclic()) { + if (spline.is_cyclic() && spline.evaluated_edges_size() > 1) { MEdge &edge = r_edges[edge_offset++]; edge.v1 = vert_offset; edge.v2 = vert_offset + positions.size() - 1; @@ -198,7 +198,7 @@ static void spline_extrude_to_mesh_data(const Spline &spline, if (profile_spline.type() == Spline::Type::Bezier) { const BezierSpline &bezier_spline = static_cast<const BezierSpline &>(profile_spline); Span<int> control_point_offsets = bezier_spline.control_point_offsets(); - for (const int i : control_point_offsets.index_range()) { + for (const int i : IndexRange(bezier_spline.size())) { if (bezier_spline.point_is_sharp(i)) { mark_edges_sharp(r_edges.slice( spline_edges_start + spline_edge_len * control_point_offsets[i], spline_edge_len)); @@ -211,7 +211,7 @@ static Mesh *curve_to_mesh_calculate(const CurveEval &curve, const CurveEval &pr { int profile_vert_total = 0; int profile_edge_total = 0; - for (const SplinePtr &profile_spline : profile_curve.splines) { + for (const SplinePtr &profile_spline : profile_curve.splines()) { profile_vert_total += profile_spline->evaluated_points_size(); profile_edge_total += profile_spline->evaluated_edges_size(); } @@ -219,7 +219,7 @@ static Mesh *curve_to_mesh_calculate(const CurveEval &curve, const CurveEval &pr int vert_total = 0; int edge_total = 0; int poly_total = 0; - for (const SplinePtr &spline : curve.splines) { + for (const SplinePtr &spline : curve.splines()) { const int spline_vert_len = spline->evaluated_points_size(); const int spline_edge_len = spline->evaluated_edges_size(); vert_total += spline_vert_len * profile_vert_total; @@ -247,8 +247,8 @@ static Mesh *curve_to_mesh_calculate(const CurveEval &curve, const CurveEval &pr int edge_offset = 0; int loop_offset = 0; int poly_offset = 0; - for (const SplinePtr &spline : curve.splines) { - for (const SplinePtr &profile_spline : profile_curve.splines) { + for (const SplinePtr &spline : curve.splines()) { + for (const SplinePtr &profile_spline : profile_curve.splines()) { spline_extrude_to_mesh_data(*spline, *profile_spline, verts, @@ -272,7 +272,7 @@ static CurveEval get_curve_single_vert() CurveEval curve; std::unique_ptr<PolySpline> spline = std::make_unique<PolySpline>(); spline->add_point(float3(0), 0, 0.0f); - curve.splines.append(std::move(spline)); + curve.add_spline(std::move(spline)); return curve; } diff --git a/source/blender/nodes/geometry/nodes/node_geo_edge_split.cc b/source/blender/nodes/geometry/nodes/node_geo_edge_split.cc index 534b7d754ec..740b828d503 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_edge_split.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_edge_split.cc @@ -44,6 +44,7 @@ static bNodeSocketTemplate geo_node_edge_split_out[] = { }; namespace blender::nodes { + static void geo_node_edge_split_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); @@ -82,6 +83,7 @@ static void geo_node_edge_split_exec(GeoNodeExecParams params) params.set_output("Geometry", std::move(geometry_set)); } + } // namespace blender::nodes void register_node_type_geo_edge_split() diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_material.cc b/source/blender/nodes/geometry/nodes/node_geo_input_material.cc new file mode 100644 index 00000000000..6bad71a62a2 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_input_material.cc @@ -0,0 +1,51 @@ +/* + * 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. + */ + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "node_geometry_util.hh" + +static bNodeSocketTemplate geo_node_input_material_out[] = { + {SOCK_MATERIAL, N_("Material")}, + {-1, ""}, +}; + +static void geo_node_input_material_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +{ + uiItemR(layout, ptr, "material", 0, "", ICON_NONE); +} + +namespace blender::nodes { + +static void geo_node_input_material_exec(GeoNodeExecParams params) +{ + Material *material = (Material *)params.node().id; + params.set_output("Material", material); +} + +} // namespace blender::nodes + +void register_node_type_geo_input_material() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_INPUT_MATERIAL, "Material", NODE_CLASS_INPUT, 0); + node_type_socket_templates(&ntype, nullptr, geo_node_input_material_out); + ntype.draw_buttons = geo_node_input_material_layout; + ntype.geometry_node_execute = blender::nodes::geo_node_input_material_exec; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc b/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc index 68bb3614751..adfd924f185 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc @@ -14,6 +14,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#include "BKE_material.h" #include "BKE_mesh.h" #include "BKE_mesh_runtime.h" #include "BKE_pointcloud.h" @@ -57,6 +58,8 @@ static Mesh *join_mesh_topology_and_builtin_attributes(Span<const MeshComponent int64_t cd_dirty_edge = 0; int64_t cd_dirty_loop = 0; + VectorSet<Material *> materials; + for (const MeshComponent *mesh_component : src_components) { const Mesh *mesh = mesh_component->get_for_read(); totverts += mesh->totvert; @@ -67,12 +70,22 @@ static Mesh *join_mesh_topology_and_builtin_attributes(Span<const MeshComponent cd_dirty_poly |= mesh->runtime.cd_dirty_poly; cd_dirty_edge |= mesh->runtime.cd_dirty_edge; cd_dirty_loop |= mesh->runtime.cd_dirty_loop; + + for (const int slot_index : IndexRange(mesh->totcol)) { + Material *material = mesh->mat[slot_index]; + materials.add(material); + } } const Mesh *first_input_mesh = src_components[0]->get_for_read(); Mesh *new_mesh = BKE_mesh_new_nomain(totverts, totedges, 0, totloops, totpolys); BKE_mesh_copy_settings(new_mesh, first_input_mesh); + for (const int i : IndexRange(materials.size())) { + Material *material = materials[i]; + BKE_id_material_eval_assign(&new_mesh->id, i + 1, material); + } + new_mesh->runtime.cd_dirty_vert = cd_dirty_vert; new_mesh->runtime.cd_dirty_poly = cd_dirty_poly; new_mesh->runtime.cd_dirty_edge = cd_dirty_edge; @@ -88,6 +101,13 @@ static Mesh *join_mesh_topology_and_builtin_attributes(Span<const MeshComponent continue; } + Array<int> material_index_map(mesh->totcol); + for (const int i : IndexRange(mesh->totcol)) { + Material *material = mesh->mat[i]; + const int new_material_index = materials.index_of(material); + material_index_map[i] = new_material_index; + } + for (const int i : IndexRange(mesh->totvert)) { const MVert &old_vert = mesh->mvert[i]; MVert &new_vert = new_mesh->mvert[vert_offset + i]; @@ -113,6 +133,13 @@ static Mesh *join_mesh_topology_and_builtin_attributes(Span<const MeshComponent MPoly &new_poly = new_mesh->mpoly[poly_offset + i]; new_poly = old_poly; new_poly.loopstart += loop_offset; + if (old_poly.mat_nr >= 0 && old_poly.mat_nr < mesh->totcol) { + new_poly.mat_nr = material_index_map[new_poly.mat_nr]; + } + else { + /* The material index was invalid before. */ + new_poly.mat_nr = 0; + } } vert_offset += mesh->totvert; @@ -305,11 +332,19 @@ static void join_curve_components(MutableSpan<GeometrySet> src_geometry_sets, Ge CurveEval *dst_curve = new CurveEval(); for (CurveComponent *component : src_components) { CurveEval *src_curve = component->get_for_write(); - for (SplinePtr &spline : src_curve->splines) { - dst_curve->splines.append(std::move(spline)); + for (SplinePtr &spline : src_curve->splines()) { + dst_curve->add_spline(std::move(spline)); } } + /* For now, remove all custom attributes, since they might have different types, + * or an attribute might not exist on all splines. */ + dst_curve->attributes.reallocate(dst_curve->splines().size()); + CustomData_reset(&dst_curve->attributes.data); + for (SplinePtr &spline : dst_curve->splines()) { + CustomData_reset(&spline->attributes.data); + } + dst_component.replace(dst_curve); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_material_assign.cc b/source/blender/nodes/geometry/nodes/node_geo_material_assign.cc new file mode 100644 index 00000000000..8c245afd3d2 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_material_assign.cc @@ -0,0 +1,98 @@ +/* + * 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. + */ + +#include "node_geometry_util.hh" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" + +#include "BKE_material.h" + +static bNodeSocketTemplate geo_node_material_assign_in[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {SOCK_MATERIAL, N_("Material")}, + {SOCK_STRING, N_("Selection")}, + {-1, ""}, +}; + +static bNodeSocketTemplate geo_node_material_assign_out[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {-1, ""}, +}; + +namespace blender::nodes { + +static void assign_material_to_faces(Mesh &mesh, const VArray<bool> &face_mask, Material *material) +{ + int new_material_index = -1; + for (const int i : IndexRange(mesh.totcol)) { + Material *other_material = mesh.mat[i]; + if (other_material == material) { + new_material_index = i; + break; + } + } + if (new_material_index == -1) { + /* Append a new material index. */ + new_material_index = mesh.totcol; + BKE_id_material_eval_assign(&mesh.id, new_material_index + 1, material); + } + + mesh.mpoly = (MPoly *)CustomData_duplicate_referenced_layer(&mesh.pdata, CD_MPOLY, mesh.totpoly); + for (const int i : IndexRange(mesh.totpoly)) { + if (face_mask[i]) { + MPoly &poly = mesh.mpoly[i]; + poly.mat_nr = new_material_index; + } + } +} + +static void geo_node_material_assign_exec(GeoNodeExecParams params) +{ + Material *material = params.extract_input<Material *>("Material"); + const std::string mask_name = params.extract_input<std::string>("Selection"); + + GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + + geometry_set = geometry_set_realize_instances(geometry_set); + + if (geometry_set.has<MeshComponent>()) { + MeshComponent &mesh_component = geometry_set.get_component_for_write<MeshComponent>(); + Mesh *mesh = mesh_component.get_for_write(); + if (mesh != nullptr) { + GVArray_Typed<bool> face_mask = mesh_component.attribute_get_for_read<bool>( + mask_name, ATTR_DOMAIN_FACE, true); + assign_material_to_faces(*mesh, face_mask, material); + } + } + + params.set_output("Geometry", std::move(geometry_set)); +} + +} // namespace blender::nodes + +void register_node_type_geo_material_assign() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_MATERIAL_ASSIGN, "Material Assign", NODE_CLASS_GEOMETRY, 0); + node_type_socket_templates(&ntype, geo_node_material_assign_in, geo_node_material_assign_out); + ntype.geometry_node_execute = blender::nodes::geo_node_material_assign_exec; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_object_info.cc b/source/blender/nodes/geometry/nodes/node_geo_object_info.cc index de099b8062f..16c943b310c 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_object_info.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_object_info.cc @@ -47,9 +47,7 @@ static void geo_node_object_info_exec(GeoNodeExecParams params) const bool transform_space_relative = (node_storage->transform_space == GEO_NODE_TRANSFORM_SPACE_RELATIVE); - bke::PersistentObjectHandle object_handle = params.extract_input<bke::PersistentObjectHandle>( - "Object"); - Object *object = params.handle_map().lookup(object_handle); + Object *object = params.get_input<Object *>("Object"); float3 location = {0, 0, 0}; float3 rotation = {0, 0, 0}; diff --git a/source/blender/nodes/geometry/nodes/node_geo_point_instance.cc b/source/blender/nodes/geometry/nodes/node_geo_point_instance.cc index 7929a5a1546..44b8b14f4e7 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_point_instance.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_point_instance.cc @@ -14,11 +14,10 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "BKE_persistent_data_handle.hh" - #include "DNA_collection_types.h" #include "BLI_hash.h" +#include "BLI_task.hh" #include "UI_interface.h" #include "UI_resources.h" @@ -48,6 +47,15 @@ static void geo_node_point_instance_layout(uiLayout *layout, bContext *UNUSED(C) namespace blender::nodes { +static void geo_node_point_instance_init(bNodeTree *UNUSED(tree), bNode *node) +{ + NodeGeometryPointInstance *data = (NodeGeometryPointInstance *)MEM_callocN( + sizeof(NodeGeometryPointInstance), __func__); + data->instance_type = GEO_NODE_POINT_INSTANCE_TYPE_OBJECT; + data->flag |= GEO_NODE_POINT_INSTANCE_WHOLE_COLLECTION; + node->storage = data; +} + static void geo_node_point_instance_update(bNodeTree *UNUSED(tree), bNode *node) { bNodeSocket *object_socket = (bNodeSocket *)BLI_findlink(&node->inputs, 1); @@ -65,98 +73,93 @@ static void geo_node_point_instance_update(bNodeTree *UNUSED(tree), bNode *node) seed_socket, type == GEO_NODE_POINT_INSTANCE_TYPE_COLLECTION && !use_whole_collection); } -static void get_instance_references__object(const GeoNodeExecParams ¶ms, - MutableSpan<InstanceReference> r_references) +static Vector<InstanceReference> get_instance_references__object(GeoNodeExecParams ¶ms) { - bke::PersistentObjectHandle object_handle = params.get_input<bke::PersistentObjectHandle>( - "Object"); - Object *object = params.handle_map().lookup(object_handle); + Object *object = params.extract_input<Object *>("Object"); if (object == params.self_object()) { - object = nullptr; + return {}; } if (object != nullptr) { - r_references.fill(*object); + return {*object}; } + return {}; } -static void get_instance_references__collection(const GeoNodeExecParams ¶ms, - const GeometryComponent &component, - MutableSpan<InstanceReference> r_references) +static Vector<InstanceReference> get_instance_references__collection(GeoNodeExecParams ¶ms) { const bNode &node = params.node(); NodeGeometryPointInstance *node_storage = (NodeGeometryPointInstance *)node.storage; - bke::PersistentCollectionHandle collection_handle = - params.get_input<bke::PersistentCollectionHandle>("Collection"); - Collection *collection = params.handle_map().lookup(collection_handle); + Collection *collection = params.get_input<Collection *>("Collection"); if (collection == nullptr) { - return; + return {}; } if (BLI_listbase_is_empty(&collection->children) && BLI_listbase_is_empty(&collection->gobject)) { params.error_message_add(NodeWarningType::Info, TIP_("Collection is empty")); - return; + return {}; } - const bool use_whole_collection = (node_storage->flag & - GEO_NODE_POINT_INSTANCE_WHOLE_COLLECTION) != 0; - if (use_whole_collection) { - r_references.fill(*collection); + if (node_storage->flag & GEO_NODE_POINT_INSTANCE_WHOLE_COLLECTION) { + return {*collection}; } - else { - Vector<InstanceReference> possible_references; - /* Direct child objects are instanced as objects. */ - LISTBASE_FOREACH (CollectionObject *, cob, &collection->gobject) { - possible_references.append(*cob->ob); - } - /* Direct child collections are instanced as collections. */ - LISTBASE_FOREACH (CollectionChild *, child, &collection->children) { - possible_references.append(*child->collection); - } - if (!possible_references.is_empty()) { - const int seed = params.get_input<int>("Seed"); - Array<uint32_t> ids = get_geometry_element_ids_as_uints(component, ATTR_DOMAIN_POINT); - for (const int i : r_references.index_range()) { - const int index = BLI_hash_int_2d(ids[i], seed) % possible_references.size(); - r_references[i] = possible_references[index]; - } - } + Vector<InstanceReference> references; + /* Direct child objects are instanced as objects. */ + LISTBASE_FOREACH (CollectionObject *, cob, &collection->gobject) { + references.append(*cob->ob); + } + /* Direct child collections are instanced as collections. */ + LISTBASE_FOREACH (CollectionChild *, child, &collection->children) { + references.append(*child->collection); } + + return references; } -static Array<InstanceReference> get_instance_references(const GeoNodeExecParams ¶ms, - const GeometryComponent &component, - const int amount) +static Vector<InstanceReference> get_instance_references(GeoNodeExecParams ¶ms) { const bNode &node = params.node(); NodeGeometryPointInstance *node_storage = (NodeGeometryPointInstance *)node.storage; const GeometryNodePointInstanceType type = (GeometryNodePointInstanceType) node_storage->instance_type; - Array<InstanceReference> references(amount); switch (type) { case GEO_NODE_POINT_INSTANCE_TYPE_OBJECT: { - get_instance_references__object(params, references); - break; + return get_instance_references__object(params); } case GEO_NODE_POINT_INSTANCE_TYPE_COLLECTION: { - get_instance_references__collection(params, component, references); - break; + return get_instance_references__collection(params); } } - return references; + return {}; } -static void add_instances_from_geometry_component(InstancesComponent &instances, - const GeometryComponent &src_geometry, - const GeoNodeExecParams ¶ms) +/** + * Add the instance references to the component as a separate step from actually creating the + * instances in order to avoid a map lookup for every transform. While this might add some + * unnecessary references if they are not chosen while adding transforms, in the common cases + * there are many more transforms than there are references, so that isn't likely. + */ +static Array<int> add_instance_references(InstancesComponent &instance_component, + Span<InstanceReference> possible_references) +{ + Array<int> possible_handles(possible_references.size()); + for (const int i : possible_references.index_range()) { + possible_handles[i] = instance_component.add_reference(possible_references[i]); + } + return possible_handles; +} + +static void add_instances_from_component(InstancesComponent &instances, + const GeometryComponent &src_geometry, + Span<int> possible_handles, + const GeoNodeExecParams ¶ms) { const AttributeDomain domain = ATTR_DOMAIN_POINT; const int domain_size = src_geometry.attribute_domain_size(domain); - Array<InstanceReference> references = get_instance_references(params, src_geometry, domain_size); GVArray_Typed<float3> positions = src_geometry.attribute_get_for_read<float3>( "position", domain, {0, 0, 0}); @@ -164,15 +167,39 @@ static void add_instances_from_geometry_component(InstancesComponent &instances, "rotation", domain, {0, 0, 0}); GVArray_Typed<float3> scales = src_geometry.attribute_get_for_read<float3>( "scale", domain, {1, 1, 1}); - GVArray_Typed<int> ids = src_geometry.attribute_get_for_read<int>("id", domain, -1); - - for (const int i : IndexRange(domain_size)) { - const InstanceReference &reference = references[i]; - if (reference.type() != InstanceReference::Type::None) { - const float4x4 matrix = float4x4::from_loc_eul_scale(positions[i], rotations[i], scales[i]); - const int handle = instances.add_reference(reference); - instances.add_instance(handle, matrix, ids[i]); - } + GVArray_Typed<int> id_attribute = src_geometry.attribute_get_for_read<int>("id", domain, -1); + + /* The initial size of the component might be non-zero if there are two component types. */ + const int start_len = instances.instances_amount(); + instances.resize(start_len + domain_size); + MutableSpan<int> handles = instances.instance_reference_handles().slice(start_len, domain_size); + MutableSpan<float4x4> transforms = instances.instance_transforms().slice(start_len, domain_size); + MutableSpan<int> instance_ids = instances.instance_ids().slice(start_len, domain_size); + + /* Skip all of the randomness handling if there is only a single possible instance + * (anything except for collection mode with "Whole Collection" turned off). */ + if (possible_handles.size() == 1) { + const int handle = possible_handles.first(); + parallel_for(IndexRange(domain_size), 1024, [&](IndexRange range) { + for (const int i : range) { + handles[i] = handle; + transforms[i] = float4x4::from_loc_eul_scale(positions[i], rotations[i], scales[i]); + instance_ids[i] = id_attribute[i]; + } + }); + } + else { + const int seed = params.get_input<int>("Seed"); + Array<uint32_t> ids = get_geometry_element_ids_as_uints(src_geometry, ATTR_DOMAIN_POINT); + parallel_for(IndexRange(domain_size), 1024, [&](IndexRange range) { + for (const int i : range) { + const int index = BLI_hash_int_2d(ids[i], seed) % possible_handles.size(); + const int handle = possible_handles[index]; + handles[i] = handle; + transforms[i] = float4x4::from_loc_eul_scale(positions[i], rotations[i], scales[i]); + instance_ids[i] = id_attribute[i]; + } + }); } } @@ -185,33 +212,37 @@ static void geo_node_point_instance_exec(GeoNodeExecParams params) * rather than making the entire input geometry set real. */ geometry_set = geometry_set_realize_instances(geometry_set); + const Vector<InstanceReference> possible_references = get_instance_references(params); + if (possible_references.is_empty()) { + params.set_output("Geometry", std::move(geometry_set_out)); + return; + } + InstancesComponent &instances = geometry_set_out.get_component_for_write<InstancesComponent>(); + Array<int> possible_handles = add_instance_references(instances, possible_references); + if (geometry_set.has<MeshComponent>()) { - add_instances_from_geometry_component( - instances, *geometry_set.get_component_for_read<MeshComponent>(), params); + add_instances_from_component(instances, + *geometry_set.get_component_for_read<MeshComponent>(), + possible_handles, + params); } if (geometry_set.has<PointCloudComponent>()) { - add_instances_from_geometry_component( - instances, *geometry_set.get_component_for_read<PointCloudComponent>(), params); + add_instances_from_component(instances, + *geometry_set.get_component_for_read<PointCloudComponent>(), + possible_handles, + params); } - if (geometry_set.has<CurveComponent>()) { - add_instances_from_geometry_component( - instances, *geometry_set.get_component_for_read<CurveComponent>(), params); + add_instances_from_component(instances, + *geometry_set.get_component_for_read<CurveComponent>(), + possible_handles, + params); } params.set_output("Geometry", std::move(geometry_set_out)); } -static void geo_node_point_instance_init(bNodeTree *UNUSED(tree), bNode *node) -{ - NodeGeometryPointInstance *data = (NodeGeometryPointInstance *)MEM_callocN( - sizeof(NodeGeometryPointInstance), __func__); - data->instance_type = GEO_NODE_POINT_INSTANCE_TYPE_OBJECT; - data->flag |= GEO_NODE_POINT_INSTANCE_WHOLE_COLLECTION; - node->storage = data; -} - } // namespace blender::nodes void register_node_type_geo_point_instance() diff --git a/source/blender/nodes/geometry/nodes/node_geo_point_rotate.cc b/source/blender/nodes/geometry/nodes/node_geo_point_rotate.cc index 73d489949ad..828d3f50551 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_point_rotate.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_point_rotate.cc @@ -59,6 +59,40 @@ static void geo_node_point_rotate_layout(uiLayout *layout, bContext *UNUSED(C), namespace blender::nodes { +static void geo_node_point_rotate_init(bNodeTree *UNUSED(ntree), bNode *node) +{ + NodeGeometryRotatePoints *node_storage = (NodeGeometryRotatePoints *)MEM_callocN( + sizeof(NodeGeometryRotatePoints), __func__); + + node_storage->type = GEO_NODE_POINT_ROTATE_TYPE_EULER; + node_storage->space = GEO_NODE_POINT_ROTATE_SPACE_OBJECT; + node_storage->input_type_axis = GEO_NODE_ATTRIBUTE_INPUT_VECTOR; + node_storage->input_type_angle = GEO_NODE_ATTRIBUTE_INPUT_FLOAT; + node_storage->input_type_rotation = GEO_NODE_ATTRIBUTE_INPUT_VECTOR; + + node->storage = node_storage; +} + +static void geo_node_point_rotate_update(bNodeTree *UNUSED(ntree), bNode *node) +{ + NodeGeometryRotatePoints *node_storage = (NodeGeometryRotatePoints *)node->storage; + update_attribute_input_socket_availabilities( + *node, + "Axis", + (GeometryNodeAttributeInputMode)node_storage->input_type_axis, + node_storage->type == GEO_NODE_POINT_ROTATE_TYPE_AXIS_ANGLE); + update_attribute_input_socket_availabilities( + *node, + "Angle", + (GeometryNodeAttributeInputMode)node_storage->input_type_angle, + node_storage->type == GEO_NODE_POINT_ROTATE_TYPE_AXIS_ANGLE); + update_attribute_input_socket_availabilities( + *node, + "Rotation", + (GeometryNodeAttributeInputMode)node_storage->input_type_rotation, + node_storage->type == GEO_NODE_POINT_ROTATE_TYPE_EULER); +} + static void point_rotate__axis_angle__object_space(const int domain_size, const VArray<float3> &axis, const VArray<float> &angles, @@ -176,44 +210,13 @@ static void geo_node_point_rotate_exec(GeoNodeExecParams params) if (geometry_set.has<PointCloudComponent>()) { point_rotate_on_component(geometry_set.get_component_for_write<PointCloudComponent>(), params); } + if (geometry_set.has<CurveComponent>()) { + point_rotate_on_component(geometry_set.get_component_for_write<CurveComponent>(), params); + } params.set_output("Geometry", geometry_set); } -static void geo_node_point_rotate_init(bNodeTree *UNUSED(ntree), bNode *node) -{ - NodeGeometryRotatePoints *node_storage = (NodeGeometryRotatePoints *)MEM_callocN( - sizeof(NodeGeometryRotatePoints), __func__); - - node_storage->type = GEO_NODE_POINT_ROTATE_TYPE_EULER; - node_storage->space = GEO_NODE_POINT_ROTATE_SPACE_OBJECT; - node_storage->input_type_axis = GEO_NODE_ATTRIBUTE_INPUT_VECTOR; - node_storage->input_type_angle = GEO_NODE_ATTRIBUTE_INPUT_FLOAT; - node_storage->input_type_rotation = GEO_NODE_ATTRIBUTE_INPUT_VECTOR; - - node->storage = node_storage; -} - -static void geo_node_point_rotate_update(bNodeTree *UNUSED(ntree), bNode *node) -{ - NodeGeometryRotatePoints *node_storage = (NodeGeometryRotatePoints *)node->storage; - update_attribute_input_socket_availabilities( - *node, - "Axis", - (GeometryNodeAttributeInputMode)node_storage->input_type_axis, - node_storage->type == GEO_NODE_POINT_ROTATE_TYPE_AXIS_ANGLE); - update_attribute_input_socket_availabilities( - *node, - "Angle", - (GeometryNodeAttributeInputMode)node_storage->input_type_angle, - node_storage->type == GEO_NODE_POINT_ROTATE_TYPE_AXIS_ANGLE); - update_attribute_input_socket_availabilities( - *node, - "Rotation", - (GeometryNodeAttributeInputMode)node_storage->input_type_rotation, - node_storage->type == GEO_NODE_POINT_ROTATE_TYPE_EULER); -} - } // namespace blender::nodes void register_node_type_geo_point_rotate() diff --git a/source/blender/nodes/geometry/nodes/node_geo_point_scale.cc b/source/blender/nodes/geometry/nodes/node_geo_point_scale.cc index 2ef21fb085b..655f5475856 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_point_scale.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_point_scale.cc @@ -43,6 +43,23 @@ static void geo_node_point_scale_layout(uiLayout *layout, bContext *UNUSED(C), P namespace blender::nodes { +static void geo_node_point_scale_init(bNodeTree *UNUSED(tree), bNode *node) +{ + NodeGeometryPointScale *data = (NodeGeometryPointScale *)MEM_callocN( + sizeof(NodeGeometryPointScale), __func__); + + data->input_type = GEO_NODE_ATTRIBUTE_INPUT_VECTOR; + node->storage = data; +} + +static void geo_node_point_scale_update(bNodeTree *UNUSED(ntree), bNode *node) +{ + NodeGeometryPointScale &node_storage = *(NodeGeometryPointScale *)node->storage; + + update_attribute_input_socket_availabilities( + *node, "Factor", (GeometryNodeAttributeInputMode)node_storage.input_type); +} + static void execute_on_component(GeoNodeExecParams params, GeometryComponent &component) { /* Note that scale doesn't necessarily need to be created with a vector type-- it could also use @@ -102,23 +119,6 @@ static void geo_node_point_scale_exec(GeoNodeExecParams params) params.set_output("Geometry", std::move(geometry_set)); } -static void geo_node_point_scale_init(bNodeTree *UNUSED(tree), bNode *node) -{ - NodeGeometryPointScale *data = (NodeGeometryPointScale *)MEM_callocN( - sizeof(NodeGeometryPointScale), __func__); - - data->input_type = GEO_NODE_ATTRIBUTE_INPUT_VECTOR; - node->storage = data; -} - -static void geo_node_point_scale_update(bNodeTree *UNUSED(ntree), bNode *node) -{ - NodeGeometryPointScale &node_storage = *(NodeGeometryPointScale *)node->storage; - - update_attribute_input_socket_availabilities( - *node, "Factor", (GeometryNodeAttributeInputMode)node_storage.input_type); -} - } // namespace blender::nodes void register_node_type_geo_point_scale() diff --git a/source/blender/nodes/geometry/nodes/node_geo_point_separate.cc b/source/blender/nodes/geometry/nodes/node_geo_point_separate.cc index 6541d982629..d2b024b208c 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_point_separate.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_point_separate.cc @@ -135,15 +135,27 @@ static GeometrySet separate_geometry_set(const GeometrySet &set_in, static void geo_node_point_separate_exec(GeoNodeExecParams params) { - const std::string mask_attribute_name = params.extract_input<std::string>("Mask"); - GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + bool wait_for_inputs = false; + wait_for_inputs |= params.lazy_require_input("Geometry"); + wait_for_inputs |= params.lazy_require_input("Mask"); + if (wait_for_inputs) { + return; + } + const std::string mask_attribute_name = params.get_input<std::string>("Mask"); + GeometrySet geometry_set = params.get_input<GeometrySet>("Geometry"); /* TODO: This is not necessary-- the input geometry set can be read only, * but it must be rewritten to handle instance groups. */ geometry_set = geometry_set_realize_instances(geometry_set); - params.set_output("Geometry 1", separate_geometry_set(geometry_set, mask_attribute_name, true)); - params.set_output("Geometry 2", separate_geometry_set(geometry_set, mask_attribute_name, false)); + if (params.lazy_output_is_required("Geometry 1")) { + params.set_output("Geometry 1", + separate_geometry_set(geometry_set, mask_attribute_name, true)); + } + if (params.lazy_output_is_required("Geometry 2")) { + params.set_output("Geometry 2", + separate_geometry_set(geometry_set, mask_attribute_name, false)); + } } } // namespace blender::nodes @@ -155,5 +167,6 @@ void register_node_type_geo_point_separate() geo_node_type_base(&ntype, GEO_NODE_POINT_SEPARATE, "Point Separate", NODE_CLASS_GEOMETRY, 0); node_type_socket_templates(&ntype, geo_node_point_instance_in, geo_node_point_instance_out); ntype.geometry_node_execute = blender::nodes::geo_node_point_separate_exec; + ntype.geometry_node_execute_supports_lazyness = true; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_points_to_volume.cc b/source/blender/nodes/geometry/nodes/node_geo_points_to_volume.cc index e85f6aaee54..65306b1c452 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_points_to_volume.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_points_to_volume.cc @@ -55,6 +55,35 @@ static void geo_node_points_to_volume_layout(uiLayout *layout, namespace blender::nodes { +static void geo_node_points_to_volume_init(bNodeTree *UNUSED(ntree), bNode *node) +{ + NodeGeometryPointsToVolume *data = (NodeGeometryPointsToVolume *)MEM_callocN( + sizeof(NodeGeometryPointsToVolume), __func__); + data->resolution_mode = GEO_NODE_POINTS_TO_VOLUME_RESOLUTION_MODE_AMOUNT; + data->input_type_radius = GEO_NODE_ATTRIBUTE_INPUT_FLOAT; + node->storage = data; + + bNodeSocket *radius_attribute_socket = nodeFindSocket(node, SOCK_IN, "Radius"); + bNodeSocketValueString *radius_attribute_socket_value = + (bNodeSocketValueString *)radius_attribute_socket->default_value; + STRNCPY(radius_attribute_socket_value->value, "radius"); +} + +static void geo_node_points_to_volume_update(bNodeTree *UNUSED(ntree), bNode *node) +{ + NodeGeometryPointsToVolume *data = (NodeGeometryPointsToVolume *)node->storage; + bNodeSocket *voxel_size_socket = nodeFindSocket(node, SOCK_IN, "Voxel Size"); + bNodeSocket *voxel_amount_socket = nodeFindSocket(node, SOCK_IN, "Voxel Amount"); + nodeSetSocketAvailability(voxel_amount_socket, + data->resolution_mode == + GEO_NODE_POINTS_TO_VOLUME_RESOLUTION_MODE_AMOUNT); + nodeSetSocketAvailability( + voxel_size_socket, data->resolution_mode == GEO_NODE_POINTS_TO_VOLUME_RESOLUTION_MODE_SIZE); + + update_attribute_input_socket_availabilities( + *node, "Radius", (GeometryNodeAttributeInputMode)data->input_type_radius); +} + #ifdef WITH_OPENVDB namespace { /* Implements the interface required by #openvdb::tools::ParticlesToLevelSet. */ @@ -231,35 +260,6 @@ static void geo_node_points_to_volume_exec(GeoNodeExecParams params) params.set_output("Geometry", std::move(geometry_set_out)); } -static void geo_node_points_to_volume_init(bNodeTree *UNUSED(ntree), bNode *node) -{ - NodeGeometryPointsToVolume *data = (NodeGeometryPointsToVolume *)MEM_callocN( - sizeof(NodeGeometryPointsToVolume), __func__); - data->resolution_mode = GEO_NODE_POINTS_TO_VOLUME_RESOLUTION_MODE_AMOUNT; - data->input_type_radius = GEO_NODE_ATTRIBUTE_INPUT_FLOAT; - node->storage = data; - - bNodeSocket *radius_attribute_socket = nodeFindSocket(node, SOCK_IN, "Radius"); - bNodeSocketValueString *radius_attribute_socket_value = - (bNodeSocketValueString *)radius_attribute_socket->default_value; - STRNCPY(radius_attribute_socket_value->value, "radius"); -} - -static void geo_node_points_to_volume_update(bNodeTree *UNUSED(ntree), bNode *node) -{ - NodeGeometryPointsToVolume *data = (NodeGeometryPointsToVolume *)node->storage; - bNodeSocket *voxel_size_socket = nodeFindSocket(node, SOCK_IN, "Voxel Size"); - bNodeSocket *voxel_amount_socket = nodeFindSocket(node, SOCK_IN, "Voxel Amount"); - nodeSetSocketAvailability(voxel_amount_socket, - data->resolution_mode == - GEO_NODE_POINTS_TO_VOLUME_RESOLUTION_MODE_AMOUNT); - nodeSetSocketAvailability( - voxel_size_socket, data->resolution_mode == GEO_NODE_POINTS_TO_VOLUME_RESOLUTION_MODE_SIZE); - - update_attribute_input_socket_availabilities( - *node, "Radius", (GeometryNodeAttributeInputMode)data->input_type_radius); -} - } // namespace blender::nodes void register_node_type_geo_points_to_volume() diff --git a/source/blender/nodes/geometry/nodes/node_geo_switch.cc b/source/blender/nodes/geometry/nodes/node_geo_switch.cc index 6736e963184..bb0a20f4561 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_switch.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_switch.cc @@ -70,20 +70,6 @@ static void geo_node_switch_init(bNodeTree *UNUSED(tree), bNode *node) namespace blender::nodes { -template<typename T> -void output_input(GeoNodeExecParams ¶ms, - const bool input, - const StringRef input_suffix, - const StringRef output_identifier) -{ - if (input) { - params.set_output(output_identifier, params.extract_input<T>("B" + input_suffix)); - } - else { - params.set_output(output_identifier, params.extract_input<T>("A" + input_suffix)); - } -} - static void geo_node_switch_update(bNodeTree *UNUSED(ntree), bNode *node) { NodeSwitch *node_storage = (NodeSwitch *)node->storage; @@ -99,10 +85,37 @@ static void geo_node_switch_update(bNodeTree *UNUSED(ntree), bNode *node) } } +template<typename T> +static void output_input(GeoNodeExecParams ¶ms, + const bool input, + const StringRef input_suffix, + const StringRef output_identifier) +{ + const std::string name_a = "A" + input_suffix; + const std::string name_b = "B" + input_suffix; + if (input) { + params.set_input_unused(name_a); + if (params.lazy_require_input(name_b)) { + return; + } + params.set_output(output_identifier, params.extract_input<T>(name_b)); + } + else { + params.set_input_unused(name_b); + if (params.lazy_require_input(name_a)) { + return; + } + params.set_output(output_identifier, params.extract_input<T>(name_a)); + } +} + static void geo_node_switch_exec(GeoNodeExecParams params) { + if (params.lazy_require_input("Switch")) { + return; + } const NodeSwitch &storage = *(const NodeSwitch *)params.node().storage; - const bool input = params.extract_input<bool>("Switch"); + const bool input = params.get_input<bool>("Switch"); switch ((eNodeSocketDatatype)storage.input_type) { case SOCK_FLOAT: { output_input<float>(params, input, "", "Output"); @@ -133,11 +146,11 @@ static void geo_node_switch_exec(GeoNodeExecParams params) break; } case SOCK_OBJECT: { - output_input<bke::PersistentObjectHandle>(params, input, "_007", "Output_007"); + output_input<Object *>(params, input, "_007", "Output_007"); break; } case SOCK_COLLECTION: { - output_input<bke::PersistentCollectionHandle>(params, input, "_008", "Output_008"); + output_input<Collection *>(params, input, "_008", "Output_008"); break; } default: @@ -158,6 +171,7 @@ void register_node_type_geo_switch() node_type_update(&ntype, blender::nodes::geo_node_switch_update); node_type_storage(&ntype, "NodeSwitch", node_free_standard_storage, node_copy_standard_storage); ntype.geometry_node_execute = blender::nodes::geo_node_switch_exec; + ntype.geometry_node_execute_supports_lazyness = true; ntype.draw_buttons = geo_node_switch_layout; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_transform.cc b/source/blender/nodes/geometry/nodes/node_geo_transform.cc index ce52dc90668..9714a4f8a80 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_transform.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_transform.cc @@ -179,22 +179,18 @@ static void geo_node_transform_exec(GeoNodeExecParams params) Mesh *mesh = geometry_set.get_mesh_for_write(); transform_mesh(mesh, translation, rotation, scale); } - if (geometry_set.has_pointcloud()) { PointCloud *pointcloud = geometry_set.get_pointcloud_for_write(); transform_pointcloud(pointcloud, translation, rotation, scale); } - if (geometry_set.has_instances()) { InstancesComponent &instances = geometry_set.get_component_for_write<InstancesComponent>(); transform_instances(instances, translation, rotation, scale); } - if (geometry_set.has_volume()) { Volume *volume = geometry_set.get_volume_for_write(); transform_volume(volume, translation, rotation, scale, params); } - if (geometry_set.has_curve()) { CurveEval *curve = geometry_set.get_curve_for_write(); transform_curve(*curve, translation, rotation, scale); diff --git a/source/blender/nodes/intern/node_socket.cc b/source/blender/nodes/intern/node_socket.cc index 5b5fe183823..ce2848b52a0 100644 --- a/source/blender/nodes/intern/node_socket.cc +++ b/source/blender/nodes/intern/node_socket.cc @@ -35,7 +35,6 @@ #include "BKE_geometry_set.hh" #include "BKE_lib_id.h" #include "BKE_node.h" -#include "BKE_persistent_data_handle.hh" #include "DNA_collection_types.h" @@ -295,6 +294,22 @@ void node_socket_init_default_value(bNodeSocket *sock) sock->default_value = dval; break; } + case SOCK_TEXTURE: { + bNodeSocketValueTexture *dval = (bNodeSocketValueTexture *)MEM_callocN( + sizeof(bNodeSocketValueTexture), "node socket value texture"); + dval->value = nullptr; + + sock->default_value = dval; + break; + } + case SOCK_MATERIAL: { + bNodeSocketValueMaterial *dval = (bNodeSocketValueMaterial *)MEM_callocN( + sizeof(bNodeSocketValueMaterial), "node socket value material"); + dval->value = nullptr; + + sock->default_value = dval; + break; + } } } @@ -374,6 +389,13 @@ void node_socket_copy_default_value(bNodeSocket *to, const bNodeSocket *from) id_us_plus(&toval->value->id); break; } + case SOCK_TEXTURE: { + bNodeSocketValueTexture *toval = (bNodeSocketValueTexture *)to->default_value; + bNodeSocketValueTexture *fromval = (bNodeSocketValueTexture *)from->default_value; + *toval = *fromval; + id_us_plus(&toval->value->id); + break; + } } to->flag |= (from->flag & SOCK_HIDE_VALUE); @@ -632,63 +654,17 @@ static bNodeSocketType *make_socket_type_string() return socktype; } -class ObjectSocketMultiFunction : public blender::fn::MultiFunction { - private: - Object *object_; - - public: - ObjectSocketMultiFunction(Object *object) : object_(object) - { - static blender::fn::MFSignature signature = create_signature(); - this->set_signature(&signature); - } - - static blender::fn::MFSignature create_signature() - { - blender::fn::MFSignatureBuilder signature{"Object Socket"}; - signature.depends_on_context(); - signature.single_output<blender::bke::PersistentObjectHandle>("Object"); - return signature.build(); - } - - void call(blender::IndexMask mask, - blender::fn::MFParams params, - blender::fn::MFContext context) const override - { - blender::MutableSpan output = - params.uninitialized_single_output<blender::bke::PersistentObjectHandle>(0, "Object"); - - /* Try to get a handle map, so that the object can be converted to a handle. */ - const blender::bke::PersistentDataHandleMap *handle_map = - context.get_global_context<blender::bke::PersistentDataHandleMap>( - "PersistentDataHandleMap"); - - if (handle_map == nullptr) { - /* Return empty handles when there is no handle map. */ - output.fill_indices(mask, blender::bke::PersistentObjectHandle()); - return; - } - - blender::bke::PersistentObjectHandle handle = handle_map->lookup(object_); - for (int64_t i : mask) { - output[i] = handle; - } - } -}; - -MAKE_CPP_TYPE(PersistentObjectHandle, blender::bke::PersistentObjectHandle); -MAKE_CPP_TYPE(PersistentCollectionHandle, blender::bke::PersistentCollectionHandle); +MAKE_CPP_TYPE(Object, Object *) +MAKE_CPP_TYPE(Collection, Collection *) +MAKE_CPP_TYPE(Texture, Tex *) +MAKE_CPP_TYPE(Material, Material *) static bNodeSocketType *make_socket_type_object() { bNodeSocketType *socktype = make_standard_socket_type(SOCK_OBJECT, PROP_NONE); - socktype->get_cpp_type = []() { - /* Objects are not passed along as raw pointers, but as handles. */ - return &blender::fn::CPPType::get<blender::bke::PersistentObjectHandle>(); - }; - socktype->expand_in_mf_network = [](blender::nodes::SocketMFNetworkBuilder &builder) { - Object *object = builder.socket_default_value<bNodeSocketValueObject>()->value; - builder.construct_generator_fn<ObjectSocketMultiFunction>(object); + socktype->get_cpp_type = []() { return &blender::fn::CPPType::get<Object *>(); }; + socktype->get_cpp_value = [](const bNodeSocket &socket, void *r_value) { + *(Object **)r_value = ((bNodeSocketValueObject *)socket.default_value)->value; }; return socktype; } @@ -706,9 +682,29 @@ static bNodeSocketType *make_socket_type_geometry() static bNodeSocketType *make_socket_type_collection() { bNodeSocketType *socktype = make_standard_socket_type(SOCK_COLLECTION, PROP_NONE); - socktype->get_cpp_type = []() { - /* Objects are not passed along as raw pointers, but as handles. */ - return &blender::fn::CPPType::get<blender::bke::PersistentCollectionHandle>(); + socktype->get_cpp_type = []() { return &blender::fn::CPPType::get<Collection *>(); }; + socktype->get_cpp_value = [](const bNodeSocket &socket, void *r_value) { + *(Collection **)r_value = ((bNodeSocketValueCollection *)socket.default_value)->value; + }; + return socktype; +} + +static bNodeSocketType *make_socket_type_texture() +{ + bNodeSocketType *socktype = make_standard_socket_type(SOCK_TEXTURE, PROP_NONE); + socktype->get_cpp_type = []() { return &blender::fn::CPPType::get<Tex *>(); }; + socktype->get_cpp_value = [](const bNodeSocket &socket, void *r_value) { + *(Tex **)r_value = ((bNodeSocketValueTexture *)socket.default_value)->value; + }; + return socktype; +} + +static bNodeSocketType *make_socket_type_material() +{ + bNodeSocketType *socktype = make_standard_socket_type(SOCK_MATERIAL, PROP_NONE); + socktype->get_cpp_type = []() { return &blender::fn::CPPType::get<Material *>(); }; + socktype->get_cpp_value = [](const bNodeSocket &socket, void *r_value) { + *(Material **)r_value = ((bNodeSocketValueMaterial *)socket.default_value)->value; }; return socktype; } @@ -754,5 +750,9 @@ void register_standard_node_socket_types(void) nodeRegisterSocketType(make_socket_type_collection()); + nodeRegisterSocketType(make_socket_type_texture()); + + nodeRegisterSocketType(make_socket_type_material()); + nodeRegisterSocketType(make_socket_type_virtual()); } diff --git a/source/blender/nodes/intern/node_tree_ref.cc b/source/blender/nodes/intern/node_tree_ref.cc index e42572b9cb7..5c0bc0b5ebc 100644 --- a/source/blender/nodes/intern/node_tree_ref.cc +++ b/source/blender/nodes/intern/node_tree_ref.cc @@ -240,7 +240,10 @@ void InputSocketRef::foreach_logical_origin(FunctionRef<void(const OutputSocketR } const OutputSocketRef &origin = link->from(); const NodeRef &origin_node = origin.node(); - if (origin_node.is_reroute_node()) { + if (!origin.is_available()) { + /* Non available sockets are ignored. */ + } + else if (origin_node.is_reroute_node()) { const InputSocketRef &reroute_input = origin_node.input(0); const OutputSocketRef &reroute_output = origin_node.output(0); skipped_fn.call_safe(reroute_input); @@ -281,7 +284,10 @@ void OutputSocketRef::foreach_logical_target( } const InputSocketRef &target = link->to(); const NodeRef &target_node = target.node(); - if (target_node.is_reroute_node()) { + if (!target.is_available()) { + /* Non available sockets are ignored. */ + } + else if (target_node.is_reroute_node()) { const OutputSocketRef &reroute_output = target_node.output(0); skipped_fn.call_safe(target); skipped_fn.call_safe(reroute_output); diff --git a/source/blender/nodes/intern/node_util.c b/source/blender/nodes/intern/node_util.c index 4076dc852b3..1aec280fd2b 100644 --- a/source/blender/nodes/intern/node_util.c +++ b/source/blender/nodes/intern/node_util.c @@ -140,8 +140,8 @@ void node_math_update(bNodeTree *UNUSED(ntree), bNode *node) switch (node->custom1) { case NODE_MATH_WRAP: - node_sock_label(sock2, "Min"); - node_sock_label(sock3, "Max"); + node_sock_label(sock2, "Max"); + node_sock_label(sock3, "Min"); break; case NODE_MATH_MULTIPLY_ADD: node_sock_label(sock2, "Multiplier"); @@ -459,6 +459,22 @@ static int node_datatype_priority(eNodeSocketDatatype from, eNodeSocketDatatype return -1; } } + case SOCK_TEXTURE: { + switch (from) { + case SOCK_TEXTURE: + return 1; + default: + return -1; + } + } + case SOCK_MATERIAL: { + switch (from) { + case SOCK_MATERIAL: + return 1; + default: + return -1; + } + } default: return -1; } diff --git a/source/blender/nodes/shader/node_shader_tree.c b/source/blender/nodes/shader/node_shader_tree.c index 25e4385de96..5ec982c4e7f 100644 --- a/source/blender/nodes/shader/node_shader_tree.c +++ b/source/blender/nodes/shader/node_shader_tree.c @@ -564,12 +564,14 @@ static bool ntree_shader_has_displacement(bNodeTree *ntree, /* Non-cycles node is used as an output. */ return false; } + if ((displacement->link != NULL) && !(displacement->link->flag & NODE_LINK_MUTED)) { *r_node = displacement->link->fromnode; *r_socket = displacement->link->fromsock; *r_link = displacement->link; + return true; } - return displacement->link != NULL; + return false; } static void ntree_shader_relink_node_normal(bNodeTree *ntree, diff --git a/source/blender/python/bmesh/bmesh_py_types.c b/source/blender/python/bmesh/bmesh_py_types.c index 563a76ac824..f1a8d450ea5 100644 --- a/source/blender/python/bmesh/bmesh_py_types.c +++ b/source/blender/python/bmesh/bmesh_py_types.c @@ -1087,7 +1087,7 @@ PyDoc_STRVAR( "3.0.\n"); static PyObject *bpy_bmesh_from_object(BPy_BMesh *self, PyObject *args, PyObject *kw) { - static const char *kwlist[] = {"object", "depsgraph", "deform", "cage", "face_normals", NULL}; + static const char *kwlist[] = {"object", "depsgraph", "cage", "face_normals", NULL}; PyObject *py_object; PyObject *py_depsgraph; Object *ob, *ob_eval; @@ -1095,7 +1095,6 @@ static PyObject *bpy_bmesh_from_object(BPy_BMesh *self, PyObject *args, PyObject struct Scene *scene_eval; Mesh *me_eval; BMesh *bm; - bool use_deform = true; bool use_cage = false; bool use_fnorm = true; const CustomData_MeshMasks data_masks = CD_MASK_BMESH; @@ -1104,13 +1103,11 @@ static PyObject *bpy_bmesh_from_object(BPy_BMesh *self, PyObject *args, PyObject if (!PyArg_ParseTupleAndKeywords(args, kw, - "OO|O&O&O&:from_object", + "OO|O&O&:from_object", (char **)kwlist, &py_object, &py_depsgraph, PyC_ParseBool, - &use_deform, - PyC_ParseBool, &use_cage, PyC_ParseBool, &use_fnorm) || @@ -1125,13 +1122,6 @@ static PyObject *bpy_bmesh_from_object(BPy_BMesh *self, PyObject *args, PyObject return NULL; } - if (use_deform == false) { - PyErr_WarnEx(PyExc_FutureWarning, - "from_object(...): the deform parameter is deprecated, assumed to be True, and " - "will be removed in version 3.0", - 1); - } - const bool use_render = DEG_get_mode(depsgraph) == DAG_EVAL_RENDER; scene_eval = DEG_get_evaluated_scene(depsgraph); ob_eval = DEG_get_evaluated_object(depsgraph, ob); @@ -1146,7 +1136,7 @@ static PyObject *bpy_bmesh_from_object(BPy_BMesh *self, PyObject *args, PyObject return NULL; } - me_eval = BKE_mesh_new_from_object(depsgraph, ob_eval, true); + me_eval = BKE_mesh_new_from_object(depsgraph, ob_eval, true, false); need_free = true; } else { diff --git a/source/blender/python/generic/idprop_py_api.c b/source/blender/python/generic/idprop_py_api.c index c329ea7965c..9b6ca7fcec5 100644 --- a/source/blender/python/generic/idprop_py_api.c +++ b/source/blender/python/generic/idprop_py_api.c @@ -42,6 +42,18 @@ extern bool pyrna_id_FromPyObject(PyObject *obj, ID **id); extern PyObject *pyrna_id_CreatePyObject(ID *id); extern bool pyrna_id_CheckPyObject(PyObject *obj); +/* Currently there is no need to expose this publicly. */ +static PyObject *BPy_IDGroup_IterKeys_CreatePyObject(BPy_IDProperty *group, const bool reversed); +static PyObject *BPy_IDGroup_IterValues_CreatePyObject(BPy_IDProperty *group, const bool reversed); +static PyObject *BPy_IDGroup_IterItems_CreatePyObject(BPy_IDProperty *group, const bool reversed); + +static PyObject *BPy_IDGroup_ViewKeys_CreatePyObject(BPy_IDProperty *group); +static PyObject *BPy_IDGroup_ViewValues_CreatePyObject(BPy_IDProperty *group); +static PyObject *BPy_IDGroup_ViewItems_CreatePyObject(BPy_IDProperty *group); + +static BPy_IDGroup_View *IDGroup_View_New_WithType(BPy_IDProperty *group, PyTypeObject *type); +static int BPy_IDGroup_Contains(BPy_IDProperty *self, PyObject *value); + /* -------------------------------------------------------------------- */ /** \name Python from ID-Property (Internal Conversions) * @@ -756,16 +768,11 @@ static int BPy_IDGroup_Map_SetItem(BPy_IDProperty *self, PyObject *key, PyObject static PyObject *BPy_IDGroup_iter(BPy_IDProperty *self) { - BPy_IDGroup_Iter *iter = PyObject_New(BPy_IDGroup_Iter, &BPy_IDGroup_Iter_Type); - iter->group = self; - iter->mode = IDPROP_ITER_KEYS; - iter->cur = self->prop->data.group.first; - Py_XINCREF(iter); - return (PyObject *)iter; + return BPy_IDGroup_ViewKeys_CreatePyObject(self); } /* for simple, non nested types this is the same as BPy_IDGroup_WrapData */ -static PyObject *BPy_IDGroup_MapDataToPy(IDProperty *prop) +PyObject *BPy_IDGroup_MapDataToPy(IDProperty *prop) { switch (prop->type) { case IDP_STRING: @@ -874,6 +881,370 @@ static PyObject *BPy_IDGroup_MapDataToPy(IDProperty *prop) /** \} */ /* -------------------------------------------------------------------- */ +/** \name ID-Property Group Iterator Type + * \{ */ + +static PyObject *BPy_IDGroup_Iter_repr(BPy_IDGroup_Iter *self) +{ + if (self->group == NULL) { + return PyUnicode_FromFormat("<%s>", Py_TYPE(self)->tp_name); + } + return PyUnicode_FromFormat("<%s \"%s\">", Py_TYPE(self)->tp_name, self->group->prop->name); +} + +static void BPy_IDGroup_Iter_dealloc(BPy_IDGroup_Iter *self) +{ + if (self->group != NULL) { + PyObject_GC_UnTrack(self); + } + Py_CLEAR(self->group); + PyObject_GC_Del(self); +} + +static int BPy_IDGroup_Iter_traverse(BPy_IDGroup_Iter *self, visitproc visit, void *arg) +{ + Py_VISIT(self->group); + return 0; +} + +static int BPy_IDGroup_Iter_clear(BPy_IDGroup_Iter *self) +{ + Py_CLEAR(self->group); + return 0; +} + +static bool BPy_Group_Iter_same_size_or_raise_error(BPy_IDGroup_Iter *self) +{ + if (self->len_init == self->group->prop->len) { + return true; + } + PyErr_SetString(PyExc_RuntimeError, "IDPropertyGroup changed size during iteration"); + return false; +} + +static PyObject *BPy_Group_IterKeys_next(BPy_IDGroup_Iter *self) +{ + if (self->cur != NULL) { + /* When `cur` is set, `group` cannot be NULL. */ + if (!BPy_Group_Iter_same_size_or_raise_error(self)) { + return NULL; + } + IDProperty *cur = self->cur; + self->cur = self->reversed ? self->cur->prev : self->cur->next; + return PyUnicode_FromString(cur->name); + } + PyErr_SetNone(PyExc_StopIteration); + return NULL; +} + +static PyObject *BPy_Group_IterValues_next(BPy_IDGroup_Iter *self) +{ + if (self->cur != NULL) { + /* When `cur` is set, `group` cannot be NULL. */ + if (!BPy_Group_Iter_same_size_or_raise_error(self)) { + return NULL; + } + IDProperty *cur = self->cur; + self->cur = self->reversed ? self->cur->prev : self->cur->next; + return BPy_IDGroup_WrapData(self->group->id, cur, self->group->prop); + } + PyErr_SetNone(PyExc_StopIteration); + return NULL; +} + +static PyObject *BPy_Group_IterItems_next(BPy_IDGroup_Iter *self) +{ + if (self->cur != NULL) { + /* When `cur` is set, `group` cannot be NULL. */ + if (!BPy_Group_Iter_same_size_or_raise_error(self)) { + return NULL; + } + IDProperty *cur = self->cur; + self->cur = self->reversed ? self->cur->prev : self->cur->next; + PyObject *ret = PyTuple_New(2); + PyTuple_SET_ITEMS(ret, + PyUnicode_FromString(cur->name), + BPy_IDGroup_WrapData(self->group->id, cur, self->group->prop)); + return ret; + } + PyErr_SetNone(PyExc_StopIteration); + return NULL; +} + +PyTypeObject BPy_IDGroup_IterKeys_Type = {PyVarObject_HEAD_INIT(NULL, 0)}; +PyTypeObject BPy_IDGroup_IterValues_Type = {PyVarObject_HEAD_INIT(NULL, 0)}; +PyTypeObject BPy_IDGroup_IterItems_Type = {PyVarObject_HEAD_INIT(NULL, 0)}; + +/* ID Property Group Iterator. */ +static void IDGroup_Iter_init_type(void) +{ +#define SHARED_MEMBER_SET(member, value) \ + { \ + k_ty->member = v_ty->member = i_ty->member = value; \ + } \ + ((void)0) + + PyTypeObject *k_ty = &BPy_IDGroup_IterKeys_Type; + PyTypeObject *v_ty = &BPy_IDGroup_IterValues_Type; + PyTypeObject *i_ty = &BPy_IDGroup_IterItems_Type; + + /* Unique members. */ + k_ty->tp_name = "IDPropertyGroupIterKeys"; + v_ty->tp_name = "IDPropertyGroupIterValues"; + i_ty->tp_name = "IDPropertyGroupIterItems"; + + k_ty->tp_iternext = (iternextfunc)BPy_Group_IterKeys_next; + v_ty->tp_iternext = (iternextfunc)BPy_Group_IterValues_next; + i_ty->tp_iternext = (iternextfunc)BPy_Group_IterItems_next; + + /* Shared members. */ + SHARED_MEMBER_SET(tp_basicsize, sizeof(BPy_IDGroup_Iter)); + SHARED_MEMBER_SET(tp_dealloc, (destructor)BPy_IDGroup_Iter_dealloc); + SHARED_MEMBER_SET(tp_repr, (reprfunc)BPy_IDGroup_Iter_repr); + SHARED_MEMBER_SET(tp_flags, Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC); + SHARED_MEMBER_SET(tp_traverse, (traverseproc)BPy_IDGroup_Iter_traverse); + SHARED_MEMBER_SET(tp_clear, (inquiry)BPy_IDGroup_Iter_clear); + SHARED_MEMBER_SET(tp_iter, PyObject_SelfIter); + +#undef SHARED_MEMBER_SET +} + +static PyObject *IDGroup_Iter_New_WithType(BPy_IDProperty *group, + const bool reversed, + PyTypeObject *type) +{ + BLI_assert(group ? group->prop->type == IDP_GROUP : true); + BPy_IDGroup_Iter *iter = PyObject_GC_New(BPy_IDGroup_Iter, type); + iter->reversed = reversed; + iter->group = group; + if (group != NULL) { + Py_INCREF(group); + PyObject_GC_Track(iter); + iter->cur = (reversed ? group->prop->data.group.last : group->prop->data.group.first); + iter->len_init = group->prop->len; + } + else { + iter->cur = NULL; + iter->len_init = 0; + } + return (PyObject *)iter; +} + +static PyObject *BPy_IDGroup_IterKeys_CreatePyObject(BPy_IDProperty *group, const bool reversed) +{ + return IDGroup_Iter_New_WithType(group, reversed, &BPy_IDGroup_IterKeys_Type); +} + +static PyObject *BPy_IDGroup_IterValues_CreatePyObject(BPy_IDProperty *group, const bool reversed) +{ + return IDGroup_Iter_New_WithType(group, reversed, &BPy_IDGroup_IterValues_Type); +} + +static PyObject *BPy_IDGroup_IterItems_CreatePyObject(BPy_IDProperty *group, const bool reversed) +{ + return IDGroup_Iter_New_WithType(group, reversed, &BPy_IDGroup_IterItems_Type); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name ID-Property Group View Types (Keys/Values/Items) + * + * This view types is a thin wrapper on keys/values/items, this matches Python's `dict_view` type. + * The is returned by `property.keys()` and is separate from the iterator that loops over keys. + * + * There are some less common features this type could support (matching Python's `dict_view`) + * + * TODO: + * - Efficient contains checks for values and items which currently convert to a list first. + * - Missing `dict_views.isdisjoint`. + * - Missing `tp_as_number` (`nb_subtract`, `nb_and`, `nb_xor`, `nb_or`). + * \{ */ + +static PyObject *BPy_IDGroup_View_repr(BPy_IDGroup_View *self) +{ + if (self->group == NULL) { + return PyUnicode_FromFormat("<%s>", Py_TYPE(self)->tp_name); + } + return PyUnicode_FromFormat("<%s \"%s\">", Py_TYPE(self)->tp_name, self->group->prop->name); +} + +static void BPy_IDGroup_View_dealloc(BPy_IDGroup_View *self) +{ + if (self->group != NULL) { + PyObject_GC_UnTrack(self); + } + Py_CLEAR(self->group); + PyObject_GC_Del(self); +} + +static int BPy_IDGroup_View_traverse(BPy_IDGroup_View *self, visitproc visit, void *arg) +{ + Py_VISIT(self->group); + return 0; +} + +static int BPy_IDGroup_View_clear(BPy_IDGroup_View *self) +{ + Py_CLEAR(self->group); + return 0; +} + +/* View Specific API's (Key/Value/Items). */ + +static PyObject *BPy_Group_ViewKeys_iter(BPy_IDGroup_View *self) +{ + return BPy_IDGroup_IterKeys_CreatePyObject(self->group, self->reversed); +} + +static PyObject *BPy_Group_ViewValues_iter(BPy_IDGroup_View *self) +{ + return BPy_IDGroup_IterValues_CreatePyObject(self->group, self->reversed); +} + +static PyObject *BPy_Group_ViewItems_iter(BPy_IDGroup_View *self) +{ + return BPy_IDGroup_IterItems_CreatePyObject(self->group, self->reversed); +} + +static Py_ssize_t BPy_Group_View_len(BPy_IDGroup_View *self) +{ + if (self->group == NULL) { + return 0; + } + return self->group->prop->len; +} + +static int BPy_Group_ViewKeys_Contains(BPy_IDGroup_View *self, PyObject *value) +{ + if (self->group == NULL) { + return 0; + } + return BPy_IDGroup_Contains(self->group, value); +} + +static int BPy_Group_ViewValues_Contains(BPy_IDGroup_View *self, PyObject *value) +{ + if (self->group == NULL) { + return 0; + } + /* TODO: implement this without first converting to a list. */ + PyObject *list = PySequence_List((PyObject *)self); + const int result = PySequence_Contains(list, value); + Py_DECREF(list); + return result; +} + +static int BPy_Group_ViewItems_Contains(BPy_IDGroup_View *self, PyObject *value) +{ + if (self->group == NULL) { + return 0; + } + /* TODO: implement this without first converting to a list. */ + PyObject *list = PySequence_List((PyObject *)self); + const int result = PySequence_Contains(list, value); + Py_DECREF(list); + return result; +} + +static PySequenceMethods BPy_IDGroup_ViewKeys_as_sequence = { + (lenfunc)BPy_Group_View_len, /* sq_length */ + 0, /* sq_concat */ + 0, /* sq_repeat */ + 0, /* sq_item */ + 0, /* sq_slice */ + 0, /* sq_ass_item */ + 0, /* sq_ass_slice */ + (objobjproc)BPy_Group_ViewKeys_Contains, /* sq_contains */ +}; + +static PySequenceMethods BPy_IDGroup_ViewValues_as_sequence = { + (lenfunc)BPy_Group_View_len, /* sq_length */ + 0, /* sq_concat */ + 0, /* sq_repeat */ + 0, /* sq_item */ + 0, /* sq_slice */ + 0, /* sq_ass_item */ + 0, /* sq_ass_slice */ + (objobjproc)BPy_Group_ViewValues_Contains, /* sq_contains */ +}; + +static PySequenceMethods BPy_IDGroup_ViewItems_as_sequence = { + (lenfunc)BPy_Group_View_len, /* sq_length */ + 0, /* sq_concat */ + 0, /* sq_repeat */ + 0, /* sq_item */ + 0, /* sq_slice */ + 0, /* sq_ass_item */ + 0, /* sq_ass_slice */ + (objobjproc)BPy_Group_ViewItems_Contains, /* sq_contains */ +}; + +/* Methods. */ + +PyDoc_STRVAR(BPy_IDGroup_View_reversed_doc, + "Return a reverse iterator over the ID Property keys values or items."); + +static PyObject *BPy_IDGroup_View_reversed(BPy_IDGroup_View *self, PyObject *UNUSED(ignored)) +{ + BPy_IDGroup_View *result = IDGroup_View_New_WithType(self->group, Py_TYPE(self)); + result->reversed = !self->reversed; + return (PyObject *)result; +} + +static PyMethodDef BPy_IDGroup_View_methods[] = { + {"__reversed__", + (PyCFunction)(void (*)(void))BPy_IDGroup_View_reversed, + METH_NOARGS, + BPy_IDGroup_View_reversed_doc}, + {NULL, NULL}, +}; + +PyTypeObject BPy_IDGroup_ViewKeys_Type = {PyVarObject_HEAD_INIT(NULL, 0)}; +PyTypeObject BPy_IDGroup_ViewValues_Type = {PyVarObject_HEAD_INIT(NULL, 0)}; +PyTypeObject BPy_IDGroup_ViewItems_Type = {PyVarObject_HEAD_INIT(NULL, 0)}; + +/* ID Property Group View. */ +static void IDGroup_View_init_type(void) +{ + PyTypeObject *k_ty = &BPy_IDGroup_ViewKeys_Type; + PyTypeObject *v_ty = &BPy_IDGroup_ViewValues_Type; + PyTypeObject *i_ty = &BPy_IDGroup_ViewItems_Type; + + /* Unique members. */ + k_ty->tp_name = "IDPropertyGroupViewKeys"; + v_ty->tp_name = "IDPropertyGroupViewValues"; + i_ty->tp_name = "IDPropertyGroupViewItems"; + + k_ty->tp_iter = (getiterfunc)BPy_Group_ViewKeys_iter; + v_ty->tp_iter = (getiterfunc)BPy_Group_ViewValues_iter; + i_ty->tp_iter = (getiterfunc)BPy_Group_ViewItems_iter; + + k_ty->tp_as_sequence = &BPy_IDGroup_ViewKeys_as_sequence; + v_ty->tp_as_sequence = &BPy_IDGroup_ViewValues_as_sequence; + i_ty->tp_as_sequence = &BPy_IDGroup_ViewItems_as_sequence; + + /* Shared members. */ +#define SHARED_MEMBER_SET(member, value) \ + { \ + k_ty->member = v_ty->member = i_ty->member = value; \ + } \ + ((void)0) + + SHARED_MEMBER_SET(tp_basicsize, sizeof(BPy_IDGroup_View)); + SHARED_MEMBER_SET(tp_dealloc, (destructor)BPy_IDGroup_View_dealloc); + SHARED_MEMBER_SET(tp_repr, (reprfunc)BPy_IDGroup_View_repr); + SHARED_MEMBER_SET(tp_flags, Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC); + SHARED_MEMBER_SET(tp_traverse, (traverseproc)BPy_IDGroup_View_traverse); + SHARED_MEMBER_SET(tp_clear, (inquiry)BPy_IDGroup_View_clear); + SHARED_MEMBER_SET(tp_methods, BPy_IDGroup_View_methods); + +#undef SHARED_MEMBER_SET +} + +/** \} */ + +/* -------------------------------------------------------------------- */ /** \name ID-Property Group Methods * \{ */ @@ -918,25 +1289,10 @@ static PyObject *BPy_IDGroup_pop(BPy_IDProperty *self, PyObject *args) return NULL; } - IDP_RemoveFromGroup(self->prop, idprop); + IDP_FreeFromGroup(self->prop, idprop); return pyform; } -PyDoc_STRVAR( - BPy_IDGroup_iter_items_doc, - ".. method:: iteritems()\n" - "\n" - " Iterate through the items in the dict; behaves like dictionary method iteritems.\n"); -static PyObject *BPy_IDGroup_iter_items(BPy_IDProperty *self) -{ - BPy_IDGroup_Iter *iter = PyObject_New(BPy_IDGroup_Iter, &BPy_IDGroup_Iter_Type); - iter->group = self; - iter->mode = IDPROP_ITER_ITEMS; - iter->cur = self->prop->data.group.first; - Py_XINCREF(iter); - return (PyObject *)iter; -} - /* utility function */ static void BPy_IDGroup_CorrectListLen(IDProperty *prop, PyObject *seq, int len, const char *func) { @@ -1021,13 +1377,37 @@ PyObject *BPy_Wrap_GetItems(ID *id, IDProperty *prop) return seq; } +PyObject *BPy_Wrap_GetKeys_View_WithID(ID *id, IDProperty *prop) +{ + PyObject *self = prop ? idprop_py_from_idp_group(id, prop, NULL) : NULL; + PyObject *ret = BPy_IDGroup_ViewKeys_CreatePyObject((BPy_IDProperty *)self); + Py_XDECREF(self); /* Owned by `ret`. */ + return ret; +} + +PyObject *BPy_Wrap_GetValues_View_WithID(ID *id, IDProperty *prop) +{ + PyObject *self = prop ? idprop_py_from_idp_group(id, prop, NULL) : NULL; + PyObject *ret = BPy_IDGroup_ViewValues_CreatePyObject((BPy_IDProperty *)self); + Py_XDECREF(self); /* Owned by `ret`. */ + return ret; +} + +PyObject *BPy_Wrap_GetItems_View_WithID(ID *id, IDProperty *prop) +{ + PyObject *self = prop ? idprop_py_from_idp_group(id, prop, NULL) : NULL; + PyObject *ret = BPy_IDGroup_ViewItems_CreatePyObject((BPy_IDProperty *)self); + Py_XDECREF(self); /* Owned by `ret`. */ + return ret; +} + PyDoc_STRVAR(BPy_IDGroup_keys_doc, ".. method:: keys()\n" "\n" " Return the keys associated with this group as a list of strings.\n"); static PyObject *BPy_IDGroup_keys(BPy_IDProperty *self) { - return BPy_Wrap_GetKeys(self->prop); + return BPy_IDGroup_ViewKeys_CreatePyObject(self); } PyDoc_STRVAR(BPy_IDGroup_values_doc, @@ -1036,16 +1416,16 @@ PyDoc_STRVAR(BPy_IDGroup_values_doc, " Return the values associated with this group.\n"); static PyObject *BPy_IDGroup_values(BPy_IDProperty *self) { - return BPy_Wrap_GetValues(self->id, self->prop); + return BPy_IDGroup_ViewValues_CreatePyObject(self); } PyDoc_STRVAR(BPy_IDGroup_items_doc, ".. method:: items()\n" "\n" - " Return the items associated with this group.\n"); + " Iterate through the items in the dict; behaves like dictionary method items.\n"); static PyObject *BPy_IDGroup_items(BPy_IDProperty *self) { - return BPy_Wrap_GetItems(self->id, self->prop); + return BPy_IDGroup_ViewItems_CreatePyObject(self); } static int BPy_IDGroup_Contains(BPy_IDProperty *self, PyObject *value) @@ -1146,7 +1526,6 @@ static PyObject *BPy_IDGroup_get(BPy_IDProperty *self, PyObject *args) static struct PyMethodDef BPy_IDGroup_methods[] = { {"pop", (PyCFunction)BPy_IDGroup_pop, METH_VARARGS, BPy_IDGroup_pop_doc}, - {"iteritems", (PyCFunction)BPy_IDGroup_iter_items, METH_NOARGS, BPy_IDGroup_iter_items_doc}, {"keys", (PyCFunction)BPy_IDGroup_keys, METH_NOARGS, BPy_IDGroup_keys_doc}, {"values", (PyCFunction)BPy_IDGroup_values, METH_NOARGS, BPy_IDGroup_values_doc}, {"items", (PyCFunction)BPy_IDGroup_items, METH_NOARGS, BPy_IDGroup_items_doc}, @@ -1676,101 +2055,59 @@ PyTypeObject BPy_IDArray_Type = { /** \} */ /* -------------------------------------------------------------------- */ -/** \name ID-Property Group Iterator Type +/** \name Initialize Types * \{ */ -static PyObject *IDGroup_Iter_repr(BPy_IDGroup_Iter *self) -{ - return PyUnicode_FromFormat("(ID Property Group Iter \"%s\")", self->group->prop->name); -} - -static PyObject *BPy_Group_Iter_Next(BPy_IDGroup_Iter *self) +void IDProp_Init_Types(void) { + IDGroup_Iter_init_type(); + IDGroup_View_init_type(); - if (self->cur) { - PyObject *ret; - IDProperty *cur; + PyType_Ready(&BPy_IDGroup_Type); + PyType_Ready(&BPy_IDArray_Type); - cur = self->cur; - self->cur = self->cur->next; + PyType_Ready(&BPy_IDGroup_IterKeys_Type); + PyType_Ready(&BPy_IDGroup_IterValues_Type); + PyType_Ready(&BPy_IDGroup_IterItems_Type); - if (self->mode == IDPROP_ITER_ITEMS) { - ret = PyTuple_New(2); - PyTuple_SET_ITEMS(ret, - PyUnicode_FromString(cur->name), - BPy_IDGroup_WrapData(self->group->id, cur, self->group->prop)); - return ret; - } + PyType_Ready(&BPy_IDGroup_ViewKeys_Type); + PyType_Ready(&BPy_IDGroup_ViewValues_Type); + PyType_Ready(&BPy_IDGroup_ViewItems_Type); +} - return PyUnicode_FromString(cur->name); +/** + * \note `group` may be NULL, unlike most other uses of this argument. + * This is supported so RNA keys/values/items methods returns an iterator with the expected type: + * - Without having ID-properties. + * - Without supporting #BPy_IDProperty.prop being NULL, which would incur many more checks. + * Python's own dictionary-views also works this way too. + */ +static BPy_IDGroup_View *IDGroup_View_New_WithType(BPy_IDProperty *group, PyTypeObject *type) +{ + BLI_assert(group ? group->prop->type == IDP_GROUP : true); + BPy_IDGroup_View *iter = PyObject_GC_New(BPy_IDGroup_View, type); + iter->reversed = false; + iter->group = group; + if (group != NULL) { + Py_INCREF(group); + PyObject_GC_Track(iter); } - - PyErr_SetNone(PyExc_StopIteration); - return NULL; + return iter; } -PyTypeObject BPy_IDGroup_Iter_Type = { - PyVarObject_HEAD_INIT(NULL, 0) - /* For printing, in format "<module>.<name>" */ - "IDPropertyGroupIter", /* char *tp_name; */ - sizeof(BPy_IDGroup_Iter), /* int tp_basicsize; */ - 0, /* tp_itemsize; For allocation */ - - /* Methods to implement standard operations */ - - NULL, /* destructor tp_dealloc; */ - 0, /* tp_vectorcall_offset */ - NULL, /* getattrfunc tp_getattr; */ - NULL, /* setattrfunc tp_setattr; */ - NULL, /* cmpfunc tp_compare; */ - (reprfunc)IDGroup_Iter_repr, /* reprfunc tp_repr; */ - - /* Method suites for standard classes */ - - NULL, /* PyNumberMethods *tp_as_number; */ - NULL, /* PySequenceMethods *tp_as_sequence; */ - NULL, /* PyMappingMethods *tp_as_mapping; */ - - /* More standard operations (here for binary compatibility) */ - - NULL, /* hashfunc tp_hash; */ - NULL, /* ternaryfunc tp_call; */ - NULL, /* reprfunc tp_str; */ - NULL, /* getattrofunc tp_getattro; */ - NULL, /* setattrofunc tp_setattro; */ - - /* Functions to access object as input/output buffer */ - NULL, /* PyBufferProcs *tp_as_buffer; */ - - /*** Flags to define presence of optional/expanded features ***/ - Py_TPFLAGS_DEFAULT, /* long tp_flags; */ - - NULL, /* char *tp_doc; Documentation string */ - /*** Assigned meaning in release 2.0 ***/ - /* call function for all accessible objects */ - NULL, /* traverseproc tp_traverse; */ - - /* delete references to contained objects */ - NULL, /* inquiry tp_clear; */ - - /*** Assigned meaning in release 2.1 ***/ - /*** rich comparisons ***/ - NULL, /* richcmpfunc tp_richcompare; */ - - /*** weak reference enabler ***/ - 0, /* long tp_weaklistoffset; */ +static PyObject *BPy_IDGroup_ViewKeys_CreatePyObject(BPy_IDProperty *group) +{ + return (PyObject *)IDGroup_View_New_WithType(group, &BPy_IDGroup_ViewKeys_Type); +} - /*** Added in release 2.2 ***/ - /* Iterators */ - PyObject_SelfIter, /* getiterfunc tp_iter; */ - (iternextfunc)BPy_Group_Iter_Next, /* iternextfunc tp_iternext; */ -}; +static PyObject *BPy_IDGroup_ViewValues_CreatePyObject(BPy_IDProperty *group) +{ + return (PyObject *)IDGroup_View_New_WithType(group, &BPy_IDGroup_ViewValues_Type); +} -void IDProp_Init_Types(void) +static PyObject *BPy_IDGroup_ViewItems_CreatePyObject(BPy_IDProperty *group) { - PyType_Ready(&BPy_IDGroup_Type); - PyType_Ready(&BPy_IDGroup_Iter_Type); - PyType_Ready(&BPy_IDArray_Type); + return (PyObject *)IDGroup_View_New_WithType(group, &BPy_IDGroup_ViewItems_Type); } /** \} */ @@ -1801,7 +2138,15 @@ static PyObject *BPyInit_idprop_types(void) /* bmesh_py_types.c */ PyModule_AddType(submodule, &BPy_IDGroup_Type); - PyModule_AddType(submodule, &BPy_IDGroup_Iter_Type); + + PyModule_AddType(submodule, &BPy_IDGroup_ViewKeys_Type); + PyModule_AddType(submodule, &BPy_IDGroup_ViewValues_Type); + PyModule_AddType(submodule, &BPy_IDGroup_ViewItems_Type); + + PyModule_AddType(submodule, &BPy_IDGroup_IterKeys_Type); + PyModule_AddType(submodule, &BPy_IDGroup_IterValues_Type); + PyModule_AddType(submodule, &BPy_IDGroup_IterItems_Type); + PyModule_AddType(submodule, &BPy_IDArray_Type); return submodule; diff --git a/source/blender/python/generic/idprop_py_api.h b/source/blender/python/generic/idprop_py_api.h index 5617efb9a8c..1e8e26a3b6d 100644 --- a/source/blender/python/generic/idprop_py_api.h +++ b/source/blender/python/generic/idprop_py_api.h @@ -25,22 +25,40 @@ struct ID; struct IDProperty; extern PyTypeObject BPy_IDArray_Type; -extern PyTypeObject BPy_IDGroup_Iter_Type; extern PyTypeObject BPy_IDGroup_Type; +extern PyTypeObject BPy_IDGroup_ViewKeys_Type; +extern PyTypeObject BPy_IDGroup_ViewValues_Type; +extern PyTypeObject BPy_IDGroup_ViewItems_Type; + +extern PyTypeObject BPy_IDGroup_IterKeys_Type; +extern PyTypeObject BPy_IDGroup_IterValues_Type; +extern PyTypeObject BPy_IDGroup_IterItems_Type; + #define BPy_IDArray_Check(v) (PyObject_TypeCheck(v, &BPy_IDArray_Type)) #define BPy_IDArray_CheckExact(v) (Py_TYPE(v) == &BPy_IDArray_Type) -#define BPy_IDGroup_Iter_Check(v) (PyObject_TypeCheck(v, &BPy_IDGroup_Iter_Type)) -#define BPy_IDGroup_Iter_CheckExact(v) (Py_TYPE(v) == &BPy_IDGroup_Iter_Type) #define BPy_IDGroup_Check(v) (PyObject_TypeCheck(v, &BPy_IDGroup_Type)) #define BPy_IDGroup_CheckExact(v) (Py_TYPE(v) == &BPy_IDGroup_Type) +#define BPy_IDGroup_ViewKeys_Check(v) (PyObject_TypeCheck(v, &BPy_IDGroup_ViewKeys_Type)) +#define BPy_IDGroup_ViewKeys_CheckExact(v) (Py_TYPE(v) == &BPy_IDGroup_ViewKeys_Type) +#define BPy_IDGroup_ViewValues_Check(v) (PyObject_TypeCheck(v, &BPy_IDGroup_ViewValues_Type)) +#define BPy_IDGroup_ViewValues_CheckExact(v) (Py_TYPE(v) == &BPy_IDGroup_ViewValues_Type) +#define BPy_IDGroup_ViewItems_Check(v) (PyObject_TypeCheck(v, &BPy_IDGroup_ViewItems_Type)) +#define BPy_IDGroup_ViewItems_CheckExact(v) (Py_TYPE(v) == &BPy_IDGroup_ViewItems_Type) + +#define BPy_IDGroup_IterKeys_Check(v) (PyObject_TypeCheck(v, &BPy_IDGroup_IterKeys_Type)) +#define BPy_IDGroup_IterKeys_CheckExact(v) (Py_TYPE(v) == &BPy_IDGroup_IterKeys_Type) +#define BPy_IDGroup_IterValues_Check(v) (PyObject_TypeCheck(v, &BPy_IDGroup_IterValues_Type)) +#define BPy_IDGroup_IterValues_CheckExact(v) (Py_TYPE(v) == &BPy_IDGroup_IterValues_Type) +#define BPy_IDGroup_IterItems_Check(v) (PyObject_TypeCheck(v, &BPy_IDGroup_IterItems_Type)) +#define BPy_IDGroup_IterItems_CheckExact(v) (Py_TYPE(v) == &BPy_IDGroup_IterItems_Type) + typedef struct BPy_IDProperty { PyObject_VAR_HEAD struct ID *id; /* can be NULL */ struct IDProperty *prop; /* must be second member */ struct IDProperty *parent; - PyObject *data_wrap; } BPy_IDProperty; typedef struct BPy_IDArray { @@ -53,20 +71,34 @@ typedef struct BPy_IDGroup_Iter { PyObject_VAR_HEAD BPy_IDProperty *group; struct IDProperty *cur; - int mode; + /** Use for detecting manipulation during iteration (which is not allowed). */ + int len_init; + /** Iterate in the reverse direction. */ + bool reversed; } BPy_IDGroup_Iter; +/** Use to implement `IDPropertyGroup.keys/values/items` */ +typedef struct BPy_IDGroup_View { + PyObject_VAR_HEAD + /** This will be NULL when accessing keys on data that has no ID properties. */ + BPy_IDProperty *group; + bool reversed; +} BPy_IDGroup_View; + PyObject *BPy_Wrap_GetKeys(struct IDProperty *prop); PyObject *BPy_Wrap_GetValues(struct ID *id, struct IDProperty *prop); PyObject *BPy_Wrap_GetItems(struct ID *id, struct IDProperty *prop); + +PyObject *BPy_Wrap_GetKeys_View_WithID(struct ID *id, struct IDProperty *prop); +PyObject *BPy_Wrap_GetValues_View_WithID(struct ID *id, struct IDProperty *prop); +PyObject *BPy_Wrap_GetItems_View_WithID(struct ID *id, struct IDProperty *prop); + int BPy_Wrap_SetMapItem(struct IDProperty *prop, PyObject *key, PyObject *val); +PyObject *BPy_IDGroup_MapDataToPy(struct IDProperty *prop); PyObject *BPy_IDGroup_WrapData(struct ID *id, struct IDProperty *prop, struct IDProperty *parent); bool BPy_IDProperty_Map_ValidateAndCreate(PyObject *key, struct IDProperty *group, PyObject *ob); void IDProp_Init_Types(void); PyObject *BPyInit_idprop(void); - -#define IDPROP_ITER_KEYS 0 -#define IDPROP_ITER_ITEMS 1 diff --git a/source/blender/python/gpu/CMakeLists.txt b/source/blender/python/gpu/CMakeLists.txt index fe5c559fcc0..1424b35a004 100644 --- a/source/blender/python/gpu/CMakeLists.txt +++ b/source/blender/python/gpu/CMakeLists.txt @@ -37,10 +37,12 @@ set(SRC gpu_py_api.c gpu_py_batch.c gpu_py_buffer.c + gpu_py_capabilities.c gpu_py_element.c gpu_py_framebuffer.c gpu_py_matrix.c gpu_py_offscreen.c + gpu_py_platform.c gpu_py_select.c gpu_py_shader.c gpu_py_state.c @@ -54,10 +56,12 @@ set(SRC gpu_py_api.h gpu_py_batch.h gpu_py_buffer.h + gpu_py_capabilities.h gpu_py_element.h gpu_py_framebuffer.h gpu_py_matrix.h gpu_py_offscreen.h + gpu_py_platform.h gpu_py_select.h gpu_py_shader.h gpu_py_state.h diff --git a/source/blender/python/gpu/gpu_py_api.c b/source/blender/python/gpu/gpu_py_api.c index 0bc18e73d0c..5119b3612f8 100644 --- a/source/blender/python/gpu/gpu_py_api.c +++ b/source/blender/python/gpu/gpu_py_api.c @@ -30,7 +30,9 @@ #include "../generic/python_utildefines.h" +#include "gpu_py_capabilities.h" #include "gpu_py_matrix.h" +#include "gpu_py_platform.h" #include "gpu_py_select.h" #include "gpu_py_state.h" #include "gpu_py_types.h" @@ -61,9 +63,15 @@ PyObject *BPyInit_gpu(void) PyModule_AddObject(mod, "types", (submodule = bpygpu_types_init())); PyDict_SetItem(sys_modules, PyModule_GetNameObject(submodule), submodule); + PyModule_AddObject(mod, "capabilities", (submodule = bpygpu_capabilities_init())); + PyDict_SetItem(sys_modules, PyModule_GetNameObject(submodule), submodule); + PyModule_AddObject(mod, "matrix", (submodule = bpygpu_matrix_init())); PyDict_SetItem(sys_modules, PyModule_GetNameObject(submodule), submodule); + PyModule_AddObject(mod, "platform", (submodule = bpygpu_platform_init())); + PyDict_SetItem(sys_modules, PyModule_GetNameObject(submodule), submodule); + PyModule_AddObject(mod, "select", (submodule = bpygpu_select_init())); PyDict_SetItem(sys_modules, PyModule_GetNameObject(submodule), submodule); diff --git a/source/blender/python/gpu/gpu_py_capabilities.c b/source/blender/python/gpu/gpu_py_capabilities.c new file mode 100644 index 00000000000..cedce485253 --- /dev/null +++ b/source/blender/python/gpu/gpu_py_capabilities.c @@ -0,0 +1,148 @@ +/* + * 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 bpygpu + * + * - Use ``bpygpu_`` for local API. + * - Use ``BPyGPU`` for public API. + */ + +#include <Python.h> + +#include "BLI_utildefines.h" + +#include "GPU_capabilities.h" + +#include "gpu_py_capabilities.h" /* own include */ + +/* -------------------------------------------------------------------- */ +/** \name Functions + * \{ */ + +static PyObject *pygpu_max_texture_size_get(PyObject *UNUSED(self)) +{ + return PyLong_FromLong(GPU_max_texture_size()); +} + +static PyObject *pygpu_max_texture_layers_get(PyObject *UNUSED(self)) +{ + return PyLong_FromLong(GPU_max_texture_layers()); +} + +static PyObject *pygpu_max_textures_get(PyObject *UNUSED(self)) +{ + return PyLong_FromLong(GPU_max_textures()); +} + +static PyObject *pygpu_max_textures_vert_get(PyObject *UNUSED(self)) +{ + return PyLong_FromLong(GPU_max_textures_vert()); +} + +static PyObject *pygpu_max_textures_geom_get(PyObject *UNUSED(self)) +{ + return PyLong_FromLong(GPU_max_textures_geom()); +} + +static PyObject *pygpu_max_textures_frag_get(PyObject *UNUSED(self)) +{ + return PyLong_FromLong(GPU_max_textures_frag()); +} + +static PyObject *pygpu_max_uniforms_vert_get(PyObject *UNUSED(self)) +{ + return PyLong_FromLong(GPU_max_uniforms_vert()); +} + +static PyObject *pygpu_max_uniforms_frag_get(PyObject *UNUSED(self)) +{ + return PyLong_FromLong(GPU_max_uniforms_frag()); +} + +static PyObject *pygpu_max_batch_indices_get(PyObject *UNUSED(self)) +{ + return PyLong_FromLong(GPU_max_batch_indices()); +} + +static PyObject *pygpu_max_batch_vertices_get(PyObject *UNUSED(self)) +{ + return PyLong_FromLong(GPU_max_batch_vertices()); +} + +static PyObject *pygpu_max_vertex_attribs_get(PyObject *UNUSED(self)) +{ + return PyLong_FromLong(GPU_max_vertex_attribs()); +} + +static PyObject *pygpu_max_varying_floats_get(PyObject *UNUSED(self)) +{ + return PyLong_FromLong(GPU_max_varying_floats()); +} + +static PyObject *pygpu_extensions_get(PyObject *UNUSED(self)) +{ + int extensions_len = GPU_extensions_len(); + PyObject *ret = PyTuple_New(extensions_len); + PyObject **ob_items = ((PyTupleObject *)ret)->ob_item; + for (int i = 0; i < extensions_len; i++) { + ob_items[i] = PyUnicode_FromString(GPU_extension_get(i)); + } + + return ret; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Module + * \{ */ + +static struct PyMethodDef pygpu_capabilities__tp_methods[] = { + {"max_texture_size_get", (PyCFunction)pygpu_max_texture_size_get, METH_NOARGS, NULL}, + {"max_texture_layers_get", (PyCFunction)pygpu_max_texture_layers_get, METH_NOARGS, NULL}, + {"max_textures_get", (PyCFunction)pygpu_max_textures_get, METH_NOARGS, NULL}, + {"max_textures_vert_get", (PyCFunction)pygpu_max_textures_vert_get, METH_NOARGS, NULL}, + {"max_textures_geom_get", (PyCFunction)pygpu_max_textures_geom_get, METH_NOARGS, NULL}, + {"max_textures_frag_get", (PyCFunction)pygpu_max_textures_frag_get, METH_NOARGS, NULL}, + {"max_uniforms_vert_get", (PyCFunction)pygpu_max_uniforms_vert_get, METH_NOARGS, NULL}, + {"max_uniforms_frag_get", (PyCFunction)pygpu_max_uniforms_frag_get, METH_NOARGS, NULL}, + {"max_batch_indices_get", (PyCFunction)pygpu_max_batch_indices_get, METH_NOARGS, NULL}, + {"max_batch_vertices_get", (PyCFunction)pygpu_max_batch_vertices_get, METH_NOARGS, NULL}, + {"max_vertex_attribs_get", (PyCFunction)pygpu_max_vertex_attribs_get, METH_NOARGS, NULL}, + {"max_varying_floats_get", (PyCFunction)pygpu_max_varying_floats_get, METH_NOARGS, NULL}, + {"extensions_get", (PyCFunction)pygpu_extensions_get, METH_NOARGS, NULL}, + {NULL, NULL, 0, NULL}, +}; + +PyDoc_STRVAR(pygpu_capabilities__tp_doc, "This module provides access to the GPU capabilities."); +static PyModuleDef pygpu_capabilities_module_def = { + PyModuleDef_HEAD_INIT, + .m_name = "gpu.capabilities", + .m_doc = pygpu_capabilities__tp_doc, + .m_methods = pygpu_capabilities__tp_methods, +}; + +PyObject *bpygpu_capabilities_init(void) +{ + PyObject *submodule; + + submodule = PyModule_Create(&pygpu_capabilities_module_def); + + return submodule; +} + +/** \} */ diff --git a/source/blender/python/gpu/gpu_py_capabilities.h b/source/blender/python/gpu/gpu_py_capabilities.h new file mode 100644 index 00000000000..ac138dda0c9 --- /dev/null +++ b/source/blender/python/gpu/gpu_py_capabilities.h @@ -0,0 +1,23 @@ +/* + * 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 bpygpu + */ + +#pragma once + +PyObject *bpygpu_capabilities_init(void); diff --git a/source/blender/python/gpu/gpu_py_framebuffer.c b/source/blender/python/gpu/gpu_py_framebuffer.c index 7f64cb90a97..0efc0713538 100644 --- a/source/blender/python/gpu/gpu_py_framebuffer.c +++ b/source/blender/python/gpu/gpu_py_framebuffer.c @@ -323,9 +323,9 @@ static PyObject *pygpu_framebuffer__tp_new(PyTypeObject *UNUSED(self), return NULL; } - for (int i = 1; i <= color_attachements_len; i++) { + for (int i = 0; i < color_attachements_len; i++) { PyObject *o = PySequence_GetItem(color_attachements, i); - bool ok = pygpu_framebuffer_new_parse_arg(o, &config[i]); + bool ok = pygpu_framebuffer_new_parse_arg(o, &config[i + 1]); Py_DECREF(o); if (!ok) { return NULL; diff --git a/source/blender/python/gpu/gpu_py_platform.c b/source/blender/python/gpu/gpu_py_platform.c new file mode 100644 index 00000000000..e49ad18dfd8 --- /dev/null +++ b/source/blender/python/gpu/gpu_py_platform.c @@ -0,0 +1,81 @@ +/* + * 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 bpygpu + * + * - Use ``bpygpu_`` for local API. + * - Use ``BPyGPU`` for public API. + */ + +#include <Python.h> + +#include "BLI_utildefines.h" + +#include "GPU_platform.h" + +#include "gpu_py_platform.h" /* own include */ + +/* -------------------------------------------------------------------- */ +/** \name Functions + * \{ */ + +static PyObject *pygpu_platform_vendor_get(PyObject *UNUSED(self)) +{ + return PyUnicode_FromString(GPU_platform_vendor()); +} + +static PyObject *pygpu_platform_renderer_get(PyObject *UNUSED(self)) +{ + return PyUnicode_FromString(GPU_platform_renderer()); +} + +static PyObject *pygpu_platform_version_get(PyObject *UNUSED(self)) +{ + return PyUnicode_FromString(GPU_platform_version()); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Module + * \{ */ + +static struct PyMethodDef pygpu_platform__tp_methods[] = { + {"vendor_get", (PyCFunction)pygpu_platform_vendor_get, METH_NOARGS, NULL}, + {"renderer_get", (PyCFunction)pygpu_platform_renderer_get, METH_NOARGS, NULL}, + {"version_get", (PyCFunction)pygpu_platform_version_get, METH_NOARGS, NULL}, + {NULL, NULL, 0, NULL}, +}; + +PyDoc_STRVAR(pygpu_platform__tp_doc, "This module provides access to GPU Platform definitions."); +static PyModuleDef pygpu_platform_module_def = { + PyModuleDef_HEAD_INIT, + .m_name = "gpu.platform", + .m_doc = pygpu_platform__tp_doc, + .m_methods = pygpu_platform__tp_methods, +}; + +PyObject *bpygpu_platform_init(void) +{ + PyObject *submodule; + + submodule = PyModule_Create(&pygpu_platform_module_def); + + return submodule; +} + +/** \} */ diff --git a/source/blender/python/gpu/gpu_py_platform.h b/source/blender/python/gpu/gpu_py_platform.h new file mode 100644 index 00000000000..19e3e41fb49 --- /dev/null +++ b/source/blender/python/gpu/gpu_py_platform.h @@ -0,0 +1,23 @@ +/* + * 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 bpygpu + */ + +#pragma once + +PyObject *bpygpu_platform_init(void); diff --git a/source/blender/python/gpu/gpu_py_texture.c b/source/blender/python/gpu/gpu_py_texture.c index 4df61d35d4c..2181c09b537 100644 --- a/source/blender/python/gpu/gpu_py_texture.c +++ b/source/blender/python/gpu/gpu_py_texture.c @@ -349,11 +349,12 @@ PyDoc_STRVAR(pygpu_texture_read_doc, static PyObject *pygpu_texture_read(BPyGPUTexture *self) { BPYGPU_TEXTURE_CHECK_OBJ(self); + eGPUTextureFormat tex_format = GPU_texture_format(self->tex); /* #GPU_texture_read is restricted in combining 'data_format' with 'tex_format'. * So choose data_format here. */ eGPUDataFormat best_data_format; - switch (GPU_texture_format(self->tex)) { + switch (tex_format) { case GPU_DEPTH_COMPONENT24: case GPU_DEPTH_COMPONENT16: case GPU_DEPTH_COMPONENT32F: @@ -389,8 +390,12 @@ static PyObject *pygpu_texture_read(BPyGPUTexture *self) } void *buf = GPU_texture_read(self->tex, best_data_format, 0); - const Py_ssize_t shape[2] = {GPU_texture_height(self->tex), GPU_texture_width(self->tex)}; - return (PyObject *)BPyGPU_Buffer_CreatePyObject(best_data_format, shape, ARRAY_SIZE(shape), buf); + const Py_ssize_t shape[3] = {GPU_texture_height(self->tex), + GPU_texture_width(self->tex), + GPU_texture_component_len(tex_format)}; + + int shape_len = (shape[2] == 1) ? 2 : 3; + return (PyObject *)BPyGPU_Buffer_CreatePyObject(best_data_format, shape, shape_len, buf); } #ifdef BPYGPU_USE_GPUOBJ_FREE_METHOD diff --git a/source/blender/python/gpu/gpu_py_texture.h b/source/blender/python/gpu/gpu_py_texture.h index 457ddcfc931..3eaaa3411bd 100644 --- a/source/blender/python/gpu/gpu_py_texture.h +++ b/source/blender/python/gpu/gpu_py_texture.h @@ -33,5 +33,5 @@ typedef struct BPyGPUTexture { int bpygpu_ParseTexture(PyObject *o, void *p); PyObject *bpygpu_texture_init(void); -PyObject *BPyGPUTexture_CreatePyObject(struct GPUTexture *tex, bool weak_reference) +PyObject *BPyGPUTexture_CreatePyObject(struct GPUTexture *tex, bool shared_reference) ATTR_NONNULL(1); diff --git a/source/blender/python/intern/bpy_app_handlers.c b/source/blender/python/intern/bpy_app_handlers.c index 8ecee9b3f2e..a0b543097e6 100644 --- a/source/blender/python/intern/bpy_app_handlers.c +++ b/source/blender/python/intern/bpy_app_handlers.c @@ -74,6 +74,7 @@ static PyStructSequence_Field app_cb_info_fields[] = { {"version_update", "on ending the versioning code"}, {"load_factory_preferences_post", "on loading factory preferences (after)"}, {"load_factory_startup_post", "on loading factory startup (after)"}, + {"xr_session_start_pre", "on starting an xr session (before)"}, /* sets the permanent tag */ #define APP_CB_OTHER_FIELDS 1 diff --git a/source/blender/python/intern/bpy_rna.c b/source/blender/python/intern/bpy_rna.c index 354aa9b6986..fb1cb823964 100644 --- a/source/blender/python/intern/bpy_rna.c +++ b/source/blender/python/intern/bpy_rna.c @@ -3562,7 +3562,7 @@ PyDoc_STRVAR(pyrna_struct_keys_doc, " dictionary function of the same name).\n" "\n" " :return: custom property keys.\n" - " :rtype: list of strings\n" + " :rtype: :class:`idprop.type.IDPropertyGroupViewKeys`\n" "\n" BPY_DOC_ID_PROP_TYPE_NOTE); static PyObject *pyrna_struct_keys(BPy_PropertyRNA *self) { @@ -3573,13 +3573,9 @@ static PyObject *pyrna_struct_keys(BPy_PropertyRNA *self) return NULL; } + /* `group` may be NULL. */ group = RNA_struct_idprops(&self->ptr, 0); - - if (group == NULL) { - return PyList_New(0); - } - - return BPy_Wrap_GetKeys(group); + return BPy_Wrap_GetKeys_View_WithID(self->ptr.owner_id, group); } PyDoc_STRVAR(pyrna_struct_items_doc, @@ -3589,7 +3585,7 @@ PyDoc_STRVAR(pyrna_struct_items_doc, " dictionary function of the same name).\n" "\n" " :return: custom property key, value pairs.\n" - " :rtype: list of key, value tuples\n" + " :rtype: :class:`idprop.type.IDPropertyGroupViewItems`\n" "\n" BPY_DOC_ID_PROP_TYPE_NOTE); static PyObject *pyrna_struct_items(BPy_PropertyRNA *self) { @@ -3600,13 +3596,9 @@ static PyObject *pyrna_struct_items(BPy_PropertyRNA *self) return NULL; } + /* `group` may be NULL. */ group = RNA_struct_idprops(&self->ptr, 0); - - if (group == NULL) { - return PyList_New(0); - } - - return BPy_Wrap_GetItems(self->ptr.owner_id, group); + return BPy_Wrap_GetItems_View_WithID(self->ptr.owner_id, group); } PyDoc_STRVAR(pyrna_struct_values_doc, @@ -3616,7 +3608,7 @@ PyDoc_STRVAR(pyrna_struct_values_doc, " dictionary function of the same name).\n" "\n" " :return: custom property values.\n" - " :rtype: list\n" + " :rtype: :class:`idprop.type.IDPropertyGroupViewValues`\n" "\n" BPY_DOC_ID_PROP_TYPE_NOTE); static PyObject *pyrna_struct_values(BPy_PropertyRNA *self) { @@ -3628,13 +3620,9 @@ static PyObject *pyrna_struct_values(BPy_PropertyRNA *self) return NULL; } + /* `group` may be NULL. */ group = RNA_struct_idprops(&self->ptr, 0); - - if (group == NULL) { - return PyList_New(0); - } - - return BPy_Wrap_GetValues(self->ptr.owner_id, group); + return BPy_Wrap_GetValues_View_WithID(self->ptr.owner_id, group); } PyDoc_STRVAR(pyrna_struct_is_property_set_doc, @@ -5006,8 +4994,13 @@ static PyObject *pyrna_struct_pop(BPy_StructRNA *self, PyObject *args) idprop = IDP_GetPropertyFromGroup(group, key); if (idprop) { - PyObject *ret = BPy_IDGroup_WrapData(self->ptr.owner_id, idprop, group); - IDP_RemoveFromGroup(group, idprop); + /* Don't use #BPy_IDGroup_WrapData as the id-property is being removed from the ID. */ + PyObject *ret = BPy_IDGroup_MapDataToPy(idprop); + /* Internal error. */ + if (UNLIKELY(ret == NULL)) { + return NULL; + } + IDP_FreeFromGroup(group, idprop); return ret; } } diff --git a/source/blender/python/mathutils/mathutils.c b/source/blender/python/mathutils/mathutils.c index 3791a6c2d29..16bf7120606 100644 --- a/source/blender/python/mathutils/mathutils.c +++ b/source/blender/python/mathutils/mathutils.c @@ -152,7 +152,7 @@ int mathutils_array_parse( return -1; } - memcpy(array, ((BaseMathObject *)value)->data, size * sizeof(float)); + memcpy(array, ((const BaseMathObject *)value)->data, size * sizeof(float)); } else #endif @@ -235,7 +235,7 @@ int mathutils_array_parse_alloc(float **array, } *array = PyMem_Malloc(size * sizeof(float)); - memcpy(*array, ((BaseMathObject *)value)->data, size * sizeof(float)); + memcpy(*array, ((const BaseMathObject *)value)->data, size * sizeof(float)); return size; } @@ -471,7 +471,7 @@ int mathutils_any_to_rotmat(float rmat[3][3], PyObject *value, const char *error return -1; } - eulO_to_mat3(rmat, ((EulerObject *)value)->eul, ((EulerObject *)value)->order); + eulO_to_mat3(rmat, ((const EulerObject *)value)->eul, ((const EulerObject *)value)->order); return 0; } if (QuaternionObject_Check(value)) { @@ -480,7 +480,7 @@ int mathutils_any_to_rotmat(float rmat[3][3], PyObject *value, const char *error } float tquat[4]; - normalize_qt_qt(tquat, ((QuaternionObject *)value)->quat); + normalize_qt_qt(tquat, ((const QuaternionObject *)value)->quat); quat_to_mat3(rmat, tquat); return 0; } @@ -530,8 +530,8 @@ int EXPP_FloatsAreEqual(float af, float bf, int maxDiff) { /* solid, fast routine across all platforms * with constant time behavior */ - const int ai = *(int *)(&af); - const int bi = *(int *)(&bf); + const int ai = *(const int *)(&af); + const int bi = *(const int *)(&bf); const int test = SIGNMASK(ai ^ bi); int diff, v1, v2; diff --git a/source/blender/python/mathutils/mathutils_Euler.c b/source/blender/python/mathutils/mathutils_Euler.c index f2a8af18073..b6a0183d04e 100644 --- a/source/blender/python/mathutils/mathutils_Euler.c +++ b/source/blender/python/mathutils/mathutils_Euler.c @@ -88,7 +88,7 @@ short euler_order_from_string(const char *str, const char *error_prefix) # define MAKE_ID3(a, b, c) (((a) << 24) | ((b) << 16) | ((c) << 8)) #endif - switch (*((PY_INT32_T *)str)) { + switch (*((const PY_INT32_T *)str)) { case MAKE_ID3('X', 'Y', 'Z'): return EULER_ORDER_XYZ; case MAKE_ID3('X', 'Z', 'Y'): diff --git a/source/blender/python/mathutils/mathutils_Matrix.c b/source/blender/python/mathutils/mathutils_Matrix.c index 161d2f41592..c312012b24c 100644 --- a/source/blender/python/mathutils/mathutils_Matrix.c +++ b/source/blender/python/mathutils/mathutils_Matrix.c @@ -187,7 +187,7 @@ static int mathutils_matrix_col_get(BaseMathObject *bmo, int col) } /* for 'translation' size will always be '3' even on 4x4 vec */ - num_row = min_ii(self->num_row, ((VectorObject *)bmo)->size); + num_row = min_ii(self->num_row, ((const VectorObject *)bmo)->size); for (row = 0; row < num_row; row++) { bmo->data[row] = MATRIX_ITEM(self, row, col); @@ -210,7 +210,7 @@ static int mathutils_matrix_col_set(BaseMathObject *bmo, int col) } /* for 'translation' size will always be '3' even on 4x4 vec */ - num_row = min_ii(self->num_row, ((VectorObject *)bmo)->size); + num_row = min_ii(self->num_row, ((const VectorObject *)bmo)->size); for (row = 0; row < num_row; row++) { MATRIX_ITEM(self, row, col) = bmo->data[row]; @@ -969,6 +969,104 @@ static PyObject *C_Matrix_Shear(PyObject *cls, PyObject *args) return Matrix_CreatePyObject(mat, matSize, matSize, (PyTypeObject *)cls); } +PyDoc_STRVAR( + C_Matrix_LocRotScale_doc, + ".. classmethod:: LocRotScale(location, rotation, scale)\n" + "\n" + " Create a matrix combining translation, rotation and scale,\n" + " acting as the inverse of the decompose() method.\n" + "\n" + " Any of the inputs may be replaced with None if not needed.\n" + "\n" + " :arg location: The translation component.\n" + " :type location: :class:`Vector` or None\n" + " :arg rotation: The rotation component.\n" + " :type rotation: 3x3 :class:`Matrix`, :class:`Quaternion`, :class:`Euler` or None\n" + " :arg scale: The scale component.\n" + " :type scale: :class:`Vector` or None\n" + " :return: Combined transformation matrix. \n" + " :rtype: 4x4 :class:`Matrix`\n"); +static PyObject *C_Matrix_LocRotScale(PyObject *cls, PyObject *args) +{ + PyObject *loc_obj, *rot_obj, *scale_obj; + float mat[4][4], loc[3]; + + if (!PyArg_ParseTuple(args, "OOO:Matrix.LocRotScale", &loc_obj, &rot_obj, &scale_obj)) { + return NULL; + } + + /* Decode location. */ + if (loc_obj == Py_None) { + zero_v3(loc); + } + else if (mathutils_array_parse( + loc, 3, 3, loc_obj, "Matrix.LocRotScale(), invalid location argument") == -1) { + return NULL; + } + + /* Decode rotation. */ + if (rot_obj == Py_None) { + unit_m4(mat); + } + else if (QuaternionObject_Check(rot_obj)) { + QuaternionObject *quat_obj = (QuaternionObject *)rot_obj; + + if (BaseMath_ReadCallback(quat_obj) == -1) { + return NULL; + } + + quat_to_mat4(mat, quat_obj->quat); + } + else if (EulerObject_Check(rot_obj)) { + EulerObject *eul_obj = (EulerObject *)rot_obj; + + if (BaseMath_ReadCallback(eul_obj) == -1) { + return NULL; + } + + eulO_to_mat4(mat, eul_obj->eul, eul_obj->order); + } + else if (MatrixObject_Check(rot_obj)) { + MatrixObject *mat_obj = (MatrixObject *)rot_obj; + + if (BaseMath_ReadCallback(mat_obj) == -1) { + return NULL; + } + + if (mat_obj->num_col == 3 && mat_obj->num_row == 3) { + copy_m4_m3(mat, (const float(*)[3])mat_obj->matrix); + } + else { + PyErr_SetString(PyExc_ValueError, + "Matrix.LocRotScale(): " + "inappropriate rotation matrix size - expects 3x3 matrix"); + return NULL; + } + } + else { + PyErr_SetString(PyExc_ValueError, + "Matrix.LocRotScale(): " + "rotation argument must be Matrix, Quaternion, Euler or None"); + return NULL; + } + + /* Decode scale. */ + if (scale_obj != Py_None) { + float scale[3]; + + if (mathutils_array_parse( + scale, 3, 3, scale_obj, "Matrix.LocRotScale(), invalid scale argument") == -1) { + return NULL; + } + + rescale_m4(mat, scale); + } + + copy_v3_v3(mat[3], loc); + + return Matrix_CreatePyObject(&mat[0][0], 4, 4, (PyTypeObject *)cls); +} + void matrix_as_3x3(float mat[3][3], MatrixObject *self) { copy_v3_v3(mat[0], MATRIX_COL_PTR(self, 0)); @@ -1029,7 +1127,7 @@ static float matrix_determinant_internal(const MatrixObject *self) MATRIX_ITEM(self, 2, 2)); } - return determinant_m4((float(*)[4])self->matrix); + return determinant_m4((const float(*)[4])self->matrix); } static void adjoint_matrix_n(float *mat_dst, const float *mat_src, const ushort dim) @@ -1037,15 +1135,15 @@ static void adjoint_matrix_n(float *mat_dst, const float *mat_src, const ushort /* calculate the classical adjoint */ switch (dim) { case 2: { - adjoint_m2_m2((float(*)[2])mat_dst, (float(*)[2])mat_src); + adjoint_m2_m2((float(*)[2])mat_dst, (const float(*)[2])mat_src); break; } case 3: { - adjoint_m3_m3((float(*)[3])mat_dst, (float(*)[3])mat_src); + adjoint_m3_m3((float(*)[3])mat_dst, (const float(*)[3])mat_src); break; } case 4: { - adjoint_m4_m4((float(*)[4])mat_dst, (float(*)[4])mat_src); + adjoint_m4_m4((float(*)[4])mat_dst, (const float(*)[4])mat_src); break; } default: @@ -1115,7 +1213,7 @@ static void matrix_invert_safe_internal(const MatrixObject *self, float *r_mat) float(*mat)[2] = (float(*)[2])in_mat; if (in_mat != self->matrix) { - copy_m2_m2(mat, (float(*)[2])self->matrix); + copy_m2_m2(mat, (const float(*)[2])self->matrix); } mat[0][0] += eps; mat[1][1] += eps; @@ -1130,7 +1228,7 @@ static void matrix_invert_safe_internal(const MatrixObject *self, float *r_mat) float(*mat)[3] = (float(*)[3])in_mat; if (in_mat != self->matrix) { - copy_m3_m3(mat, (float(*)[3])self->matrix); + copy_m3_m3(mat, (const float(*)[3])self->matrix); } mat[0][0] += eps; mat[1][1] += eps; @@ -1146,7 +1244,7 @@ static void matrix_invert_safe_internal(const MatrixObject *self, float *r_mat) float(*mat)[4] = (float(*)[4])in_mat; if (in_mat != self->matrix) { - copy_m4_m4(mat, (float(*)[4])self->matrix); + copy_m4_m4(mat, (const float(*)[4])self->matrix); } mat[0][0] += eps; mat[1][1] += eps; @@ -1194,7 +1292,7 @@ static PyObject *Matrix_to_quaternion(MatrixObject *self) mat3_to_quat(quat, (float(*)[3])self->matrix); } else { - mat4_to_quat(quat, (float(*)[4])self->matrix); + mat4_to_quat(quat, (const float(*)[4])self->matrix); } return Quaternion_CreatePyObject(quat, NULL); @@ -1243,10 +1341,10 @@ static PyObject *Matrix_to_euler(MatrixObject *self, PyObject *args) /*must be 3-4 cols, 3-4 rows, square matrix */ if (self->num_row == 3 && self->num_col == 3) { - copy_m3_m3(mat, (float(*)[3])self->matrix); + copy_m3_m3(mat, (const float(*)[3])self->matrix); } else if (self->num_row == 4 && self->num_col == 4) { - copy_m3_m4(mat, (float(*)[4])self->matrix); + copy_m3_m4(mat, (const float(*)[4])self->matrix); } else { PyErr_SetString(PyExc_ValueError, @@ -1321,7 +1419,7 @@ static PyObject *Matrix_resize_4x4(MatrixObject *self) memcpy(mat[col], MATRIX_COL_PTR(self, col), self->num_row * sizeof(float)); } - copy_m4_m4((float(*)[4])self->matrix, (float(*)[4])mat); + copy_m4_m4((float(*)[4])self->matrix, (const float(*)[4])mat); self->num_col = 4; self->num_row = 4; @@ -1479,7 +1577,7 @@ static bool matrix_invert_args_check(const MatrixObject *self, PyObject *args, b return true; case 1: if (check_type) { - const MatrixObject *fallback = (MatrixObject *)PyTuple_GET_ITEM(args, 0); + const MatrixObject *fallback = (const MatrixObject *)PyTuple_GET_ITEM(args, 0); if (!MatrixObject_Check(fallback)) { PyErr_SetString(PyExc_TypeError, "Matrix.invert: " @@ -1797,7 +1895,7 @@ static PyObject *Matrix_decompose(MatrixObject *self) return NULL; } - mat4_to_loc_rot_size(loc, rot, size, (float(*)[4])self->matrix); + mat4_to_loc_rot_size(loc, rot, size, (const float(*)[4])self->matrix); mat3_to_quat(quat, rot); ret = PyTuple_New(3); @@ -2059,7 +2157,7 @@ static PyObject *Matrix_identity(MatrixObject *self) static PyObject *Matrix_copy_notest(MatrixObject *self, const float *matrix) { - return Matrix_CreatePyObject((float *)matrix, self->num_col, self->num_row, Py_TYPE(self)); + return Matrix_CreatePyObject((const float *)matrix, self->num_col, self->num_row, Py_TYPE(self)); } PyDoc_STRVAR(Matrix_copy_doc, @@ -2960,10 +3058,10 @@ static PyObject *Matrix_is_negative_get(MatrixObject *self, void *UNUSED(closure /*must be 3-4 cols, 3-4 rows, square matrix*/ if (self->num_row == 4 && self->num_col == 4) { - return PyBool_FromLong(is_negative_m4((float(*)[4])self->matrix)); + return PyBool_FromLong(is_negative_m4((const float(*)[4])self->matrix)); } if (self->num_row == 3 && self->num_col == 3) { - return PyBool_FromLong(is_negative_m3((float(*)[3])self->matrix)); + return PyBool_FromLong(is_negative_m3((const float(*)[3])self->matrix)); } PyErr_SetString(PyExc_AttributeError, @@ -2982,10 +3080,10 @@ static PyObject *Matrix_is_orthogonal_get(MatrixObject *self, void *UNUSED(closu /*must be 3-4 cols, 3-4 rows, square matrix*/ if (self->num_row == 4 && self->num_col == 4) { - return PyBool_FromLong(is_orthonormal_m4((float(*)[4])self->matrix)); + return PyBool_FromLong(is_orthonormal_m4((const float(*)[4])self->matrix)); } if (self->num_row == 3 && self->num_col == 3) { - return PyBool_FromLong(is_orthonormal_m3((float(*)[3])self->matrix)); + return PyBool_FromLong(is_orthonormal_m3((const float(*)[3])self->matrix)); } PyErr_SetString(PyExc_AttributeError, @@ -3005,10 +3103,10 @@ static PyObject *Matrix_is_orthogonal_axis_vectors_get(MatrixObject *self, void /*must be 3-4 cols, 3-4 rows, square matrix*/ if (self->num_row == 4 && self->num_col == 4) { - return PyBool_FromLong(is_orthogonal_m4((float(*)[4])self->matrix)); + return PyBool_FromLong(is_orthogonal_m4((const float(*)[4])self->matrix)); } if (self->num_row == 3 && self->num_col == 3) { - return PyBool_FromLong(is_orthogonal_m3((float(*)[3])self->matrix)); + return PyBool_FromLong(is_orthogonal_m3((const float(*)[3])self->matrix)); } PyErr_SetString(PyExc_AttributeError, @@ -3111,6 +3209,10 @@ static struct PyMethodDef Matrix_methods[] = { (PyCFunction)C_Matrix_OrthoProjection, METH_VARARGS | METH_CLASS, C_Matrix_OrthoProjection_doc}, + {"LocRotScale", + (PyCFunction)C_Matrix_LocRotScale, + METH_VARARGS | METH_CLASS, + C_Matrix_LocRotScale_doc}, {NULL, NULL, 0, NULL}, }; diff --git a/source/blender/python/mathutils/mathutils_interpolate.c b/source/blender/python/mathutils/mathutils_interpolate.c index 6abb66899d5..bb6a7720c44 100644 --- a/source/blender/python/mathutils/mathutils_interpolate.c +++ b/source/blender/python/mathutils/mathutils_interpolate.c @@ -54,24 +54,15 @@ static PyObject *M_Interpolate_poly_3d_calc(PyObject *UNUSED(self), PyObject *ar PyObject *point, *veclist, *ret; int i; - if (!PyArg_ParseTuple(args, "OO!:poly_3d_calc", &veclist, &vector_Type, &point)) { + if (!PyArg_ParseTuple(args, "OO:poly_3d_calc", &veclist, &point)) { return NULL; } - if (BaseMath_ReadCallback((VectorObject *)point) == -1) { + if (mathutils_array_parse( + fp, 2, 3 | MU_ARRAY_ZERO, point, "pt must be a 2-3 dimensional vector") == -1) { return NULL; } - fp[0] = ((VectorObject *)point)->vec[0]; - fp[1] = ((VectorObject *)point)->vec[1]; - if (((VectorObject *)point)->size > 2) { - fp[2] = ((VectorObject *)point)->vec[2]; - } - else { - /* if its a 2d vector then set the z to be zero */ - fp[2] = 0.0f; - } - len = mathutils_array_parse_alloc_v(((float **)&vecs), 3, veclist, __func__); if (len == -1) { return NULL; diff --git a/source/blender/python/mathutils/mathutils_kdtree.c b/source/blender/python/mathutils/mathutils_kdtree.c index fe8f9ec0334..d54dbc9ab05 100644 --- a/source/blender/python/mathutils/mathutils_kdtree.c +++ b/source/blender/python/mathutils/mathutils_kdtree.c @@ -52,7 +52,7 @@ static void kdtree_nearest_to_py_tuple(const KDTreeNearest_3d *nearest, PyObject BLI_assert(PyTuple_GET_SIZE(py_retval) == 3); PyTuple_SET_ITEMS(py_retval, - Vector_CreatePyObject((float *)nearest->co, 3, NULL), + Vector_CreatePyObject(nearest->co, 3, NULL), PyLong_FromLong(nearest->index), PyFloat_FromDouble(nearest->dist)); } diff --git a/source/blender/sequencer/SEQ_iterator.h b/source/blender/sequencer/SEQ_iterator.h index b34f0558c56..c7c2dc275ee 100644 --- a/source/blender/sequencer/SEQ_iterator.h +++ b/source/blender/sequencer/SEQ_iterator.h @@ -27,33 +27,70 @@ extern "C" { #endif +#include "BLI_ghash.h" + struct Editing; struct Sequence; +struct GSet; +struct GSetIterator; -typedef struct SeqIterator { - struct Sequence **array; - int tot, cur; +#define SEQ_ITERATOR_FOREACH(var, collection) \ + for (SeqIterator iter = {{{NULL}}}; \ + SEQ_iterator_ensure(collection, &iter, &var) && var != NULL; \ + var = SEQ_iterator_yield(&iter)) - struct Sequence *seq; - int valid; -} SeqIterator; - -#define SEQ_ALL_BEGIN(ed, _seq) \ +#define SEQ_ALL_BEGIN(ed, var) \ { \ - SeqIterator iter_macro; \ - for (SEQ_iterator_begin(ed, &iter_macro, false); iter_macro.valid; \ - SEQ_iterator_next(&iter_macro)) { \ - _seq = iter_macro.seq; + if (ed != NULL) { \ + SeqCollection *all_strips = SEQ_query_all_strips_recursive(&ed->seqbase); \ + GSetIterator gsi; \ + GSET_ITER (gsi, all_strips->set) { \ + var = (Sequence *)(BLI_gsetIterator_getKey(&gsi)); #define SEQ_ALL_END \ } \ - SEQ_iterator_end(&iter_macro); \ + SEQ_collection_free(all_strips); \ + } \ } \ ((void)0) -void SEQ_iterator_begin(struct Editing *ed, SeqIterator *iter, const bool use_current_sequences); -void SEQ_iterator_next(SeqIterator *iter); -void SEQ_iterator_end(SeqIterator *iter); +typedef struct SeqCollection { + struct SeqCollection *next, *prev; + struct GSet *set; +} SeqCollection; + +typedef struct SeqIterator { + GSetIterator gsi; + SeqCollection *collection; + bool iterator_initialized; +} SeqIterator; + +bool SEQ_iterator_ensure(SeqCollection *collection, + SeqIterator *iterator, + struct Sequence **r_seq); +struct Sequence *SEQ_iterator_yield(SeqIterator *iterator); + +SeqCollection *SEQ_collection_create(void); +bool SEQ_collection_append_strip(struct Sequence *seq, SeqCollection *data); +bool SEQ_collection_remove_strip(struct Sequence *seq, SeqCollection *data); +void SEQ_collection_free(SeqCollection *collection); +void SEQ_collection_merge(SeqCollection *collection_dst, SeqCollection *collection_src); +void SEQ_collection_expand(struct ListBase *seqbase, + SeqCollection *collection, + void query_func(struct Sequence *seq_reference, + struct ListBase *seqbase, + SeqCollection *collection)); +SeqCollection *SEQ_query_by_reference(struct Sequence *seq_reference, + struct ListBase *seqbase, + void seq_query_func(struct Sequence *seq_reference, + struct ListBase *seqbase, + SeqCollection *collection)); +SeqCollection *SEQ_query_selected_strips(struct ListBase *seqbase); +SeqCollection *SEQ_query_all_strips(ListBase *seqbase); +SeqCollection *SEQ_query_all_strips_recursive(ListBase *seqbase); +void SEQ_query_strip_effect_chain(struct Sequence *seq_reference, + struct ListBase *seqbase, + SeqCollection *collection); #ifdef __cplusplus } diff --git a/source/blender/sequencer/SEQ_sequencer.h b/source/blender/sequencer/SEQ_sequencer.h index 85513faf3e6..4f49b8cb58f 100644 --- a/source/blender/sequencer/SEQ_sequencer.h +++ b/source/blender/sequencer/SEQ_sequencer.h @@ -44,8 +44,6 @@ enum { SEQ_SIDE_NO_CHANGE, }; -#define SEQ_CACHE_COST_MAX 10.0f - /* seq_dupli' flags */ #define SEQ_DUPE_UNIQUE_NAME (1 << 0) #define SEQ_DUPE_CONTEXT (1 << 1) diff --git a/source/blender/sequencer/SEQ_utils.h b/source/blender/sequencer/SEQ_utils.h index 52fac5d7d0e..9d529089ffc 100644 --- a/source/blender/sequencer/SEQ_utils.h +++ b/source/blender/sequencer/SEQ_utils.h @@ -35,7 +35,7 @@ struct Scene; struct Sequence; struct StripElem; -void SEQ_sort(struct Scene *scene); +void SEQ_sort(struct ListBase *seqbase); void SEQ_sequence_base_unique_name_recursive(struct ListBase *seqbasep, struct Sequence *seq); const char *SEQ_sequence_give_name(struct Sequence *seq); struct ListBase *SEQ_get_seqbase_from_sequence(struct Sequence *seq, int *r_offset); @@ -60,6 +60,7 @@ int SEQ_seqbase_recursive_apply(struct ListBase *seqbase, int SEQ_recursive_apply(struct Sequence *seq, int (*apply_fn)(struct Sequence *, void *), void *arg); +void SEQ_ensure_unique_name(struct Sequence *seq, struct Scene *scene); #ifdef __cplusplus } #endif diff --git a/source/blender/sequencer/intern/effects.c b/source/blender/sequencer/intern/effects.c index 278320d873e..d41a2c19d55 100644 --- a/source/blender/sequencer/intern/effects.c +++ b/source/blender/sequencer/intern/effects.c @@ -3033,10 +3033,9 @@ static ImBuf *do_adjustment_impl(const SeqRenderData *context, Sequence *seq, fl i = seq_render_give_ibuf_seqbase(context, timeline_frame, seq->machine - 1, seqbasep); } - /* found nothing? so let's work the way up the metastrip stack, so + /* Found nothing? so let's work the way up the meta-strip stack, so * that it is possible to group a bunch of adjustment strips into - * a metastrip and have that work on everything below the metastrip - */ + * a meta-strip and have that work on everything below the meta-strip. */ if (!i) { Sequence *meta; diff --git a/source/blender/sequencer/intern/image_cache.c b/source/blender/sequencer/intern/image_cache.c index 089bb5a6bec..a0c95c1c197 100644 --- a/source/blender/sequencer/intern/image_cache.c +++ b/source/blender/sequencer/intern/image_cache.c @@ -352,15 +352,18 @@ static void seq_disk_cache_update_file(SeqDiskCache *disk_cache, char *path) } /* Path format: - * <cache dir>/<project name>/<scene name>-<timestamp>/<seq name>/DCACHE_FNAME_FORMAT + * <cache dir>/<project name>_seq_cache/<scene name>-<timestamp>/<seq name>/DCACHE_FNAME_FORMAT */ static void seq_disk_cache_get_project_dir(SeqDiskCache *disk_cache, char *path, size_t path_len) { - char main_name[FILE_MAX]; - BLI_split_file_part(BKE_main_blendfile_path(disk_cache->bmain), main_name, sizeof(main_name)); + char cache_dir[FILE_MAX]; + BLI_split_file_part(BKE_main_blendfile_path(disk_cache->bmain), cache_dir, sizeof(cache_dir)); + /* Use suffix, so that the cache directory name does not conflict with the bmain's blend file. */ + const char *suffix = "_seq_cache"; + strncat(cache_dir, suffix, sizeof(cache_dir) - strlen(cache_dir) - 1); BLI_strncpy(path, seq_disk_cache_base_dir(), path_len); - BLI_path_append(path, path_len, main_name); + BLI_path_append(path, path_len, cache_dir); } static void seq_disk_cache_get_dir( @@ -421,7 +424,7 @@ static void seq_disk_cache_handle_versioning(SeqDiskCache *disk_cache) BLI_strncpy(path_version_file, path, sizeof(path_version_file)); BLI_path_append(path_version_file, sizeof(path_version_file), "cache_version"); - if (BLI_exists(path)) { + if (BLI_exists(path) && BLI_is_dir(path)) { FILE *file = BLI_fopen(path_version_file, "r"); if (file) { diff --git a/source/blender/sequencer/intern/iterator.c b/source/blender/sequencer/intern/iterator.c index 356f5db45e8..9bbc5362f18 100644 --- a/source/blender/sequencer/intern/iterator.c +++ b/source/blender/sequencer/intern/iterator.c @@ -31,110 +31,268 @@ #include "DNA_scene_types.h" #include "DNA_sequence_types.h" +#include "BLI_ghash.h" #include "BLI_listbase.h" #include "BKE_scene.h" #include "SEQ_iterator.h" -/* ************************* iterator ************************** */ -/* *************** (replaces old WHILE_SEQ) ********************* */ -/* **************** use now SEQ_ALL_BEGIN () SEQ_ALL_END ***************** */ +/* -------------------------------------------------------------------- */ +/** \Iterator API + * \{ */ -/* sequence strip iterator: - * - builds a full array, recursively into meta strips +/** + * Utility function for SEQ_ITERATOR_FOREACH macro. + * Ensure, that iterator is initialized. During initialization return pointer to collection element + * and step gset iterator. When this function is called after iterator has been initialized, it + * will do nothing and return true. + * + * \param collection: collection to iterate + * \param iterator: iterator to be initialized + * \param r_seq: pointer to Sequence pointer + * + * \return false when iterator can not be initialized, true otherwise */ - -static void seq_count(ListBase *seqbase, int *tot) +bool SEQ_iterator_ensure(SeqCollection *collection, SeqIterator *iterator, Sequence **r_seq) { - Sequence *seq; - - for (seq = seqbase->first; seq; seq = seq->next) { - (*tot)++; + if (iterator->iterator_initialized) { + return true; + } - if (seq->seqbase.first) { - seq_count(&seq->seqbase, tot); - } + if (BLI_gset_len(collection->set) == 0) { + return false; } + + iterator->collection = collection; + BLI_gsetIterator_init(&iterator->gsi, iterator->collection->set); + iterator->iterator_initialized = true; + + *r_seq = BLI_gsetIterator_getKey(&iterator->gsi); + BLI_gsetIterator_step(&iterator->gsi); + + return true; } -static void seq_build_array(ListBase *seqbase, Sequence ***array, int depth) +/** + * Utility function for SEQ_ITERATOR_FOREACH macro. + * Yield collection element + * + * \param iterator: iterator to be initialized + * + * \return collection element or NULL when iteration has ended + */ +Sequence *SEQ_iterator_yield(SeqIterator *iterator) { - Sequence *seq; + Sequence *seq = BLI_gsetIterator_done(&iterator->gsi) ? NULL : + BLI_gsetIterator_getKey(&iterator->gsi); + BLI_gsetIterator_step(&iterator->gsi); + return seq; +} - for (seq = seqbase->first; seq; seq = seq->next) { - seq->depth = depth; +/** + * Free strip collection. + * + * \param collection: collection to be freed + */ +void SEQ_collection_free(SeqCollection *collection) +{ + BLI_gset_free(collection->set, NULL); + MEM_freeN(collection); +} - if (seq->seqbase.first) { - seq_build_array(&seq->seqbase, array, depth + 1); - } +/** + * Create new empty strip collection. + * + * \return empty strip collection. + */ +SeqCollection *SEQ_collection_create(void) +{ + SeqCollection *collection = MEM_callocN(sizeof(SeqCollection), "SeqCollection"); + collection->set = BLI_gset_new( + BLI_ghashutil_ptrhash, BLI_ghashutil_ptrcmp, "SeqCollection GSet"); + return collection; +} - **array = seq; - (*array)++; +/** + * Query strips from seqbase. seq_reference is used by query function as filter condition. + * + * \param seq_reference: reference strip for query function + * \param seqbase: ListBase in which strips are queried + * \param seq_query_func: query function callback + * \return strip collection + */ +SeqCollection *SEQ_query_by_reference(Sequence *seq_reference, + ListBase *seqbase, + void seq_query_func(Sequence *seq_reference, + ListBase *seqbase, + SeqCollection *collection)) +{ + SeqCollection *collection = SEQ_collection_create(); + seq_query_func(seq_reference, seqbase, collection); + return collection; +} +/** + * Add strip to collection. + * + * \param seq: strip to be added + * \param collection: collection to which strip will be added + * \return false if strip is already in set, otherwise true + */ +bool SEQ_collection_append_strip(Sequence *seq, SeqCollection *collection) +{ + if (BLI_gset_lookup(collection->set, seq) != NULL) { + return false; } + BLI_gset_insert(collection->set, seq); + return true; } -static void seq_array(Editing *ed, - const bool use_current_sequences, - Sequence ***r_seqarray, - int *r_seqarray_len) +/** + * Remove strip from collection. + * + * \param seq: strip to be removed + * \param collection: collection from which strip will be removed + * \return true if strip exists in set and it was removed from set, otherwise false + */ +bool SEQ_collection_remove_strip(Sequence *seq, SeqCollection *collection) { - Sequence **array; - - *r_seqarray = NULL; - *r_seqarray_len = 0; + return BLI_gset_remove(collection->set, seq, NULL); +} - if (ed == NULL) { - return; +/** + * Move strips from collection_src to collection_dst. Source collection will be freed. + * + * \param collection_dst: destination collection + * \param collection_src: source collection + */ +void SEQ_collection_merge(SeqCollection *collection_dst, SeqCollection *collection_src) +{ + Sequence *seq; + SEQ_ITERATOR_FOREACH (seq, collection_src) { + SEQ_collection_append_strip(seq, collection_dst); } + SEQ_collection_free(collection_src); +} - if (use_current_sequences) { - seq_count(ed->seqbasep, r_seqarray_len); - } - else { - seq_count(&ed->seqbase, r_seqarray_len); - } +/** + * Expand collection by running SEQ_query() for each strip, which will be used as reference. + * Results of these queries will be merged into provided collection. + * + * \param seqbase: ListBase in which strips are queried + * \param collection: SeqCollection to be expanded + * \param seq_query_func: query function callback + */ +void SEQ_collection_expand(ListBase *seqbase, + SeqCollection *collection, + void seq_query_func(Sequence *seq_reference, + ListBase *seqbase, + SeqCollection *collection)) +{ + /* Collect expanded results for each sequence in provided SeqIteratorCollection. */ + ListBase expand_collections = {0}; - if (*r_seqarray_len == 0) { - return; + Sequence *seq; + SEQ_ITERATOR_FOREACH (seq, collection) { + SeqCollection *expand_collection = SEQ_query_by_reference(seq, seqbase, seq_query_func); + BLI_addtail(&expand_collections, expand_collection); } - *r_seqarray = array = MEM_mallocN(sizeof(Sequence *) * (*r_seqarray_len), "SeqArray"); - if (use_current_sequences) { - seq_build_array(ed->seqbasep, &array, 0); - } - else { - seq_build_array(&ed->seqbase, &array, 0); + /* Merge all expanded results in provided SeqIteratorCollection. */ + LISTBASE_FOREACH_MUTABLE (SeqCollection *, expand_collection, &expand_collections) { + BLI_remlink(&expand_collections, expand_collection); + SEQ_collection_merge(collection, expand_collection); } } -void SEQ_iterator_begin(Editing *ed, SeqIterator *iter, const bool use_current_sequences) -{ - memset(iter, 0, sizeof(*iter)); - seq_array(ed, use_current_sequences, &iter->array, &iter->tot); +/** \} */ - if (iter->tot) { - iter->cur = 0; - iter->seq = iter->array[iter->cur]; - iter->valid = 1; +/** + * Query all strips in seqbase and nested meta strips. + * + * \param seqbase: ListBase in which strips are queried + * \return strip collection + */ +SeqCollection *SEQ_query_all_strips_recursive(ListBase *seqbase) +{ + SeqCollection *collection = SEQ_collection_create(); + LISTBASE_FOREACH (Sequence *, seq, seqbase) { + if (seq->type == SEQ_TYPE_META) { + SEQ_collection_merge(collection, SEQ_query_all_strips_recursive(&seq->seqbase)); + } + SEQ_collection_append_strip(seq, collection); } + return collection; } -void SEQ_iterator_next(SeqIterator *iter) +/** + * Query all strips in seqbase. This does not include strips nested in meta strips. + * + * \param seqbase: ListBase in which strips are queried + * \return strip collection + */ +SeqCollection *SEQ_query_all_strips(ListBase *seqbase) { - if (++iter->cur < iter->tot) { - iter->seq = iter->array[iter->cur]; + SeqCollection *collection = SEQ_collection_create(); + LISTBASE_FOREACH (Sequence *, seq, seqbase) { + SEQ_collection_append_strip(seq, collection); } - else { - iter->valid = 0; + return collection; +} + +/** + * Query all selected strips in seqbase. + * + * \param seqbase: ListBase in which strips are queried + * \return strip collection + */ +SeqCollection *SEQ_query_selected_strips(ListBase *seqbase) +{ + SeqCollection *collection = SEQ_collection_create(); + LISTBASE_FOREACH (Sequence *, seq, seqbase) { + if ((seq->flag & SELECT) == 0) { + continue; + } + SEQ_collection_append_strip(seq, collection); } + return collection; } -void SEQ_iterator_end(SeqIterator *iter) +/** + * Query all effect strips that are directly or indirectly connected to seq_reference. + * This includes all effects of seq_reference, strips used by another inputs and their effects, so + * that whole chain is fully independent of other strips. + * + * \param seq_reference: reference strip + * \param seqbase: ListBase in which strips are queried + * \param collection: collection to be filled + */ +void SEQ_query_strip_effect_chain(Sequence *seq_reference, + ListBase *seqbase, + SeqCollection *collection) { - if (iter->array) { - MEM_freeN(iter->array); + if (!SEQ_collection_append_strip(seq_reference, collection)) { + return; /* Strip is already in set, so all effects connected to it are as well. */ + } + + /* Find all strips that seq_reference is connected to. */ + if (seq_reference->type & SEQ_TYPE_EFFECT) { + if (seq_reference->seq1) { + SEQ_query_strip_effect_chain(seq_reference->seq1, seqbase, collection); + } + if (seq_reference->seq2) { + SEQ_query_strip_effect_chain(seq_reference->seq2, seqbase, collection); + } + if (seq_reference->seq3) { + SEQ_query_strip_effect_chain(seq_reference->seq3, seqbase, collection); + } } - iter->valid = 0; + /* Find all strips connected to seq_reference. */ + LISTBASE_FOREACH (Sequence *, seq_test, seqbase) { + if (seq_test->seq1 == seq_reference || seq_test->seq2 == seq_reference || + seq_test->seq3 == seq_reference) { + SEQ_query_strip_effect_chain(seq_test, seqbase, collection); + } + } } diff --git a/source/blender/sequencer/intern/render.c b/source/blender/sequencer/intern/render.c index f892f1c1b41..e8e600f910c 100644 --- a/source/blender/sequencer/intern/render.c +++ b/source/blender/sequencer/intern/render.c @@ -65,6 +65,7 @@ #include "RE_pipeline.h" #include "SEQ_effects.h" +#include "SEQ_iterator.h" #include "SEQ_modifier.h" #include "SEQ_proxy.h" #include "SEQ_render.h" @@ -259,120 +260,132 @@ StripElem *SEQ_render_give_stripelem(Sequence *seq, int timeline_frame) return se; } -static int evaluate_seq_frame_gen(Sequence **seq_arr, - ListBase *seqbase, - int timeline_frame, - int chanshown) +static bool seq_is_effect_of(const Sequence *seq_effect, const Sequence *possibly_input) { - /* Use arbitrary sized linked list, the size could be over MAXSEQ. */ - LinkNodePair effect_inputs = {NULL, NULL}; - int totseq = 0; + if (seq_effect->seq1 == possibly_input || seq_effect->seq2 == possibly_input || + seq_effect->seq3 == possibly_input) { + return true; + } + return false; +} - memset(seq_arr, 0, sizeof(Sequence *) * (MAXSEQ + 1)); +/* Check if seq must be rendered. This depends on whole stack in some cases, not only seq itself. + * Order of applying these conditions is important. */ +static bool must_render_strip(const Sequence *seq, SeqCollection *strips_under_playhead) +{ + /* Sound strips are not rendered. */ + if (seq->type == SEQ_TYPE_SOUND_RAM) { + return false; + } + /* Muted strips are not rendered. */ + if ((seq->flag & SEQ_MUTE) != 0) { + return false; + } - LISTBASE_FOREACH (Sequence *, seq, seqbase) { - if ((seq->startdisp <= timeline_frame) && (seq->enddisp > timeline_frame)) { - if ((seq->type & SEQ_TYPE_EFFECT) && !(seq->flag & SEQ_MUTE)) { + bool seq_have_effect_in_stack = false; + Sequence *seq_iter; + SEQ_ITERATOR_FOREACH (seq_iter, strips_under_playhead) { + /* Strips is below another strip with replace blending are not rendered. */ + if (seq_iter->blend_mode == SEQ_BLEND_REPLACE && seq->machine < seq_iter->machine) { + return false; + } - if (seq->seq1) { - BLI_linklist_append_alloca(&effect_inputs, seq->seq1); - } + if ((seq_iter->type & SEQ_TYPE_EFFECT) != 0 && seq_is_effect_of(seq_iter, seq)) { + /* Strips in same channel or higher than its effect are rendered. */ + if (seq->machine >= seq_iter->machine) { + return true; + } + /* Mark that this strip has effect in stack, that is above the strip. */ + seq_have_effect_in_stack = true; + } + } - if (seq->seq2) { - BLI_linklist_append_alloca(&effect_inputs, seq->seq2); - } + /* All effects are rendered (with respect to conditions above). */ + if ((seq->type & SEQ_TYPE_EFFECT) != 0) { + return true; + } - if (seq->seq3) { - BLI_linklist_append_alloca(&effect_inputs, seq->seq3); - } - } + /* If strip has effects in stack, and all effects are above this strip, it it not rendered. */ + if (seq_have_effect_in_stack) { + return false; + } + + return true; +} - seq_arr[seq->machine] = seq; - totseq++; +static SeqCollection *query_strips_at_frame(ListBase *seqbase, const int timeline_frame) +{ + SeqCollection *collection = SEQ_collection_create(); + + LISTBASE_FOREACH (Sequence *, seq, seqbase) { + if ((seq->startdisp <= timeline_frame) && (seq->enddisp > timeline_frame)) { + SEQ_collection_append_strip(seq, collection); } } + return collection; +} - /* Drop strips which are used for effect inputs, we don't want - * them to blend into render stack in any other way than effect - * string rendering. */ - for (LinkNode *seq_item = effect_inputs.list; seq_item; seq_item = seq_item->next) { - Sequence *seq = seq_item->link; - /* It's possible that effect strip would be placed to the same - * 'machine' as its inputs. We don't want to clear such strips - * from the stack. */ - if (seq_arr[seq->machine] && seq_arr[seq->machine]->type & SEQ_TYPE_EFFECT) { - continue; - } - /* If we're shown a specified channel, then we want to see the strips - * which belongs to this machine. */ - if (chanshown != 0 && chanshown <= seq->machine) { +static void collection_filter_channel_up_to_incl(SeqCollection *collection, const int channel) +{ + Sequence *seq; + SEQ_ITERATOR_FOREACH (seq, collection) { + if (seq->machine <= channel) { continue; } - seq_arr[seq->machine] = NULL; + SEQ_collection_remove_strip(seq, collection); } - - return totseq; } -/** - * Count number of strips in timeline at timeline_frame - * - * \param seqbase: ListBase in which strips are located - * \param timeline_frame: frame on timeline from where gaps are searched for - * \return number of strips - */ -int SEQ_render_evaluate_frame(ListBase *seqbase, int timeline_frame) +/* Remove strips we don't want to render from collection. */ +static void collection_filter_rendered_strips(SeqCollection *collection) { - Sequence *seq_arr[MAXSEQ + 1]; - return evaluate_seq_frame_gen(seq_arr, seqbase, timeline_frame, 0); + Sequence *seq; + SEQ_ITERATOR_FOREACH (seq, collection) { + if (must_render_strip(seq, collection)) { + continue; + } + SEQ_collection_remove_strip(seq, collection); + } } -static bool video_seq_is_rendered(Sequence *seq) +static int seq_channel_cmp_fn(const void *a, const void *b) { - return (seq && !(seq->flag & SEQ_MUTE) && seq->type != SEQ_TYPE_SOUND_RAM); + return (*(Sequence **)a)->machine - (*(Sequence **)b)->machine; } -int seq_get_shown_sequences(ListBase *seqbasep, - int timeline_frame, - int chanshown, - Sequence **seq_arr_out) +int seq_get_shown_sequences(ListBase *seqbase, + const int timeline_frame, + const int chanshown, + Sequence **r_seq_arr) { - Sequence *seq_arr[MAXSEQ + 1]; - int b = chanshown; - int cnt = 0; + SeqCollection *collection = query_strips_at_frame(seqbase, timeline_frame); - if (b > MAXSEQ) { - return 0; - } - - if (evaluate_seq_frame_gen(seq_arr, seqbasep, timeline_frame, chanshown)) { - if (b == 0) { - b = MAXSEQ; - } - for (; b > 0; b--) { - if (video_seq_is_rendered(seq_arr[b])) { - break; - } - } + if (chanshown != 0) { + collection_filter_channel_up_to_incl(collection, chanshown); } + collection_filter_rendered_strips(collection); - chanshown = b; + const int strip_count = BLI_gset_len(collection->set); - for (; b > 0; b--) { - if (video_seq_is_rendered(seq_arr[b])) { - if (seq_arr[b]->blend_mode == SEQ_BLEND_REPLACE) { - break; - } - } + if (strip_count > MAXSEQ) { + BLI_assert(!"Too many strips, this shouldn't happen"); + return 0; } - for (; b <= chanshown && b >= 0; b++) { - if (video_seq_is_rendered(seq_arr[b])) { - seq_arr_out[cnt++] = seq_arr[b]; - } + /* Copy collection elements into array. */ + memset(r_seq_arr, 0, sizeof(Sequence *) * (MAXSEQ + 1)); + Sequence *seq; + int index = 0; + SEQ_ITERATOR_FOREACH (seq, collection) { + r_seq_arr[index] = seq; + index++; } + SEQ_collection_free(collection); + + /* Sort array by channel. */ + qsort(r_seq_arr, strip_count, sizeof(Sequence *), seq_channel_cmp_fn); - return cnt; + return strip_count; } /** \} */ diff --git a/source/blender/sequencer/intern/render.h b/source/blender/sequencer/intern/render.h index 1147516b8ec..a0cdf24d84b 100644 --- a/source/blender/sequencer/intern/render.h +++ b/source/blender/sequencer/intern/render.h @@ -60,10 +60,10 @@ struct ImBuf *seq_render_effect_execute_threaded(struct SeqEffectHandle *sh, struct ImBuf *ibuf2, struct ImBuf *ibuf3); void seq_imbuf_to_sequencer_space(struct Scene *scene, struct ImBuf *ibuf, bool make_float); -int seq_get_shown_sequences(struct ListBase *seqbasep, +int seq_get_shown_sequences(struct ListBase *seqbase, int timeline_frame, int chanshown, - struct Sequence **seq_arr_out); + struct Sequence **r_seq_arr); struct ImBuf *seq_render_strip(const struct SeqRenderData *context, struct SeqRenderState *state, struct Sequence *seq, diff --git a/source/blender/sequencer/intern/sequencer.c b/source/blender/sequencer/intern/sequencer.c index cc11796496c..55c14944a23 100644 --- a/source/blender/sequencer/intern/sequencer.c +++ b/source/blender/sequencer/intern/sequencer.c @@ -448,8 +448,8 @@ static Sequence *seq_dupli(const Scene *scene_src, seqn->strip->stripdata = NULL; BLI_listbase_clear(&seqn->seqbase); - /* WATCH OUT!!! - This metastrip is not recursively duplicated here - do this after!!! */ - /* - seq_dupli_recursive(&seq->seqbase, &seqn->seqbase);*/ + /* WARNING: This meta-strip is not recursively duplicated here - do this after! */ + // seq_dupli_recursive(&seq->seqbase, &seqn->seqbase); } else if (seq->type == SEQ_TYPE_SCENE) { seqn->strip->stripdata = NULL; diff --git a/source/blender/sequencer/intern/strip_add.c b/source/blender/sequencer/intern/strip_add.c index 1106f47c477..5ec2269b993 100644 --- a/source/blender/sequencer/intern/strip_add.c +++ b/source/blender/sequencer/intern/strip_add.c @@ -100,7 +100,7 @@ static void seq_add_generic_update(Scene *scene, ListBase *seqbase, Sequence *se { SEQ_sequence_base_unique_name_recursive(seqbase, seq); SEQ_time_update_sequence_bounds(scene, seq); - SEQ_sort(scene); + SEQ_sort(seqbase); SEQ_relations_invalidate_cache_composite(scene, seq); } diff --git a/source/blender/sequencer/intern/strip_edit.c b/source/blender/sequencer/intern/strip_edit.c index 4a27fb3a087..4de6ec3583c 100644 --- a/source/blender/sequencer/intern/strip_edit.c +++ b/source/blender/sequencer/intern/strip_edit.c @@ -39,10 +39,12 @@ #include "BKE_sound.h" #include "strip_time.h" +#include "utils.h" #include "SEQ_add.h" #include "SEQ_edit.h" #include "SEQ_effects.h" +#include "SEQ_iterator.h" #include "SEQ_relations.h" #include "SEQ_sequencer.h" #include "SEQ_time.h" @@ -149,7 +151,7 @@ void SEQ_edit_update_muting(Editing *ed) static void sequencer_flag_users_for_removal(Scene *scene, ListBase *seqbase, Sequence *seq) { LISTBASE_FOREACH (Sequence *, user_seq, seqbase) { - /* Look in metas for usage of seq. */ + /* Look in meta-strips for usage of seq. */ if (user_seq->type == SEQ_TYPE_META) { sequencer_flag_users_for_removal(scene, &user_seq->seqbase, seq); } @@ -251,22 +253,26 @@ bool SEQ_edit_move_strip_to_meta(Scene *scene, return false; } - /* Remove users of src_seq. Ideally these could be moved into meta as well, but this would be - * best to do with generalized iterator as described in D10337. */ - sequencer_flag_users_for_removal(scene, seqbase, src_seq); - SEQ_edit_remove_flagged_sequences(scene, seqbase); + SeqCollection *collection = SEQ_collection_create(); + SEQ_collection_append_strip(src_seq, collection); + SEQ_collection_expand(seqbase, collection, SEQ_query_strip_effect_chain); - /* Move to meta. */ - BLI_remlink(seqbase, src_seq); - BLI_addtail(&dst_seqm->seqbase, src_seq); - SEQ_relations_invalidate_cache_preprocessed(scene, src_seq); - - /* Update meta. */ - SEQ_time_update_sequence(scene, dst_seqm); - if (SEQ_transform_test_overlap(&dst_seqm->seqbase, src_seq)) { - SEQ_transform_seqbase_shuffle(&dst_seqm->seqbase, src_seq, scene); + Sequence *seq; + SEQ_ITERATOR_FOREACH (seq, collection) { + /* Move to meta. */ + BLI_remlink(seqbase, seq); + BLI_addtail(&dst_seqm->seqbase, seq); + SEQ_relations_invalidate_cache_preprocessed(scene, seq); + + /* Update meta. */ + SEQ_time_update_sequence(scene, dst_seqm); + if (SEQ_transform_test_overlap(&dst_seqm->seqbase, seq)) { + SEQ_transform_seqbase_shuffle(&dst_seqm->seqbase, seq, scene); + } } + SEQ_collection_free(collection); + return true; } @@ -343,6 +349,29 @@ static void seq_split_set_left_offset(Sequence *seq, int timeline_frame) SEQ_transform_set_left_handle_frame(seq, timeline_frame); } +static void seq_edit_split_handle_strip_offsets(Main *bmain, + Scene *scene, + Sequence *left_seq, + Sequence *right_seq, + const int timeline_frame, + const eSeqSplitMethod method) +{ + switch (method) { + case SEQ_SPLIT_SOFT: + seq_split_set_left_offset(right_seq, timeline_frame); + seq_split_set_right_offset(left_seq, timeline_frame); + break; + case SEQ_SPLIT_HARD: + seq_split_set_right_hold_offset(left_seq, timeline_frame); + seq_split_set_left_hold_offset(right_seq, timeline_frame); + SEQ_add_reload_new_file(bmain, scene, left_seq, false); + SEQ_add_reload_new_file(bmain, scene, right_seq, false); + break; + } + SEQ_time_update_sequence(scene, left_seq); + SEQ_time_update_sequence(scene, right_seq); +} + /** * Split Sequence at timeline_frame in two. * @@ -365,33 +394,44 @@ Sequence *SEQ_edit_strip_split(Main *bmain, return NULL; } - if (method == SEQ_SPLIT_HARD) { - /* Precaution, needed because the length saved on-disk may not match the length saved in the - * blend file, or our code may have minor differences reading file length between versions. - * This causes hard-split to fail, see: T47862. */ - SEQ_add_reload_new_file(bmain, scene, seq, true); - SEQ_time_update_sequence(scene, seq); + SeqCollection *collection = SEQ_collection_create(); + SEQ_collection_append_strip(seq, collection); + SEQ_collection_expand(seqbase, collection, SEQ_query_strip_effect_chain); + + /* Move strips in collection from seqbase to new ListBase. */ + ListBase left_strips = {NULL, NULL}; + SEQ_ITERATOR_FOREACH (seq, collection) { + BLI_remlink(seqbase, seq); + BLI_addtail(&left_strips, seq); } - Sequence *left_seq = seq; - Sequence *right_seq = SEQ_sequence_dupli_recursive( - scene, scene, seqbase, seq, SEQ_DUPE_UNIQUE_NAME | SEQ_DUPE_ANIM); + /* Sort list, so that no strip can depend on next strip in list. + * This is important for SEQ_time_update_sequence functionality. */ + SEQ_sort(&left_strips); + + /* Duplicate ListBase. */ + ListBase right_strips = {NULL, NULL}; + SEQ_sequence_base_dupli_recursive(scene, scene, &right_strips, &left_strips, SEQ_DUPE_ALL, 0); + + /* Split strips. */ + Sequence *left_seq = left_strips.first; + Sequence *right_seq = right_strips.first; + Sequence *return_seq = right_strips.first; + while (left_seq && right_seq) { + seq_edit_split_handle_strip_offsets(bmain, scene, left_seq, right_seq, timeline_frame, method); + left_seq = left_seq->next; + right_seq = right_seq->next; + } - switch (method) { - case SEQ_SPLIT_SOFT: - seq_split_set_left_offset(right_seq, timeline_frame); - seq_split_set_right_offset(left_seq, timeline_frame); - break; - case SEQ_SPLIT_HARD: - seq_split_set_right_hold_offset(left_seq, timeline_frame); - seq_split_set_left_hold_offset(right_seq, timeline_frame); - SEQ_add_reload_new_file(bmain, scene, left_seq, false); - SEQ_add_reload_new_file(bmain, scene, right_seq, false); - break; + seq = right_strips.first; + BLI_movelisttolist(seqbase, &left_strips); + BLI_movelisttolist(seqbase, &right_strips); + + for (; seq; seq = seq->next) { + SEQ_ensure_unique_name(seq, scene); } - SEQ_time_update_sequence(scene, left_seq); - SEQ_time_update_sequence(scene, right_seq); - return right_seq; + + return return_seq; } /** diff --git a/source/blender/sequencer/intern/strip_time.c b/source/blender/sequencer/intern/strip_time.c index 21dc9aa2cdd..40d7fade308 100644 --- a/source/blender/sequencer/intern/strip_time.c +++ b/source/blender/sequencer/intern/strip_time.c @@ -36,9 +36,11 @@ #include "IMB_imbuf.h" +#include "SEQ_iterator.h" #include "SEQ_render.h" #include "SEQ_sequencer.h" #include "SEQ_time.h" +#include "SEQ_transform.h" #include "strip_time.h" #include "utils.h" @@ -161,11 +163,41 @@ void SEQ_time_update_sequence_bounds(Scene *scene, Sequence *seq) } } +static void seq_time_update_meta_strip(Scene *scene, Sequence *seq_meta) +{ + if (BLI_listbase_is_empty(&seq_meta->seqbase)) { + return; + } + + int min = MAXFRAME * 2; + int max = -MAXFRAME * 2; + LISTBASE_FOREACH (Sequence *, seq, &seq_meta->seqbase) { + min = min_ii(seq->startdisp, min); + max = max_ii(seq->enddisp, max); + } + + seq_meta->start = min + seq_meta->anim_startofs; + seq_meta->len = max - min; + seq_meta->len -= seq_meta->anim_startofs; + seq_meta->len -= seq_meta->anim_endofs; + + seq_update_sound_bounds_recursive(scene, seq_meta); +} + +static void seq_time_update_meta_strip_range(Scene *scene, Sequence *seq_meta) +{ + seq_time_update_meta_strip(scene, seq_meta); + + /* Prevent meta-strip to move in timeline. */ + SEQ_transform_set_left_handle_frame(seq_meta, seq_meta->startdisp); + SEQ_transform_set_right_handle_frame(seq_meta, seq_meta->enddisp); +} + void SEQ_time_update_sequence(Scene *scene, Sequence *seq) { Sequence *seqm; - /* check all metas recursively */ + /* Check all meta-strips recursively. */ seqm = seq->seqbase.first; while (seqm) { if (seqm->seqbase.first) { @@ -211,6 +243,16 @@ void SEQ_time_update_sequence(Scene *scene, Sequence *seq) } } else { + if (seq->type == SEQ_TYPE_META) { + seq_time_update_meta_strip(scene, seq); + } + + Editing *ed = SEQ_editing_get(scene, false); + MetaStack *ms = SEQ_meta_stack_active_get(ed); + if (ms != NULL) { + seq_time_update_meta_strip_range(scene, ms->parseq); + } + SEQ_time_update_sequence_bounds(scene, seq); } } @@ -363,6 +405,17 @@ void SEQ_timeline_boundbox(const Scene *scene, const ListBase *seqbase, rctf *re } } +static bool strip_exists_at_frame(SeqCollection *all_strips, const int timeline_frame) +{ + Sequence *seq; + SEQ_ITERATOR_FOREACH (seq, all_strips) { + if ((seq->startdisp <= timeline_frame) && (seq->enddisp > timeline_frame)) { + return true; + } + } + return false; +} + /** * Find first gap between strips after initial_frame and describe it by filling data of r_gap_info * @@ -384,10 +437,12 @@ void seq_time_gap_info_get(const Scene *scene, int timeline_frame = initial_frame; r_gap_info->gap_exists = false; - if (SEQ_render_evaluate_frame(seqbase, initial_frame) == 0) { + SeqCollection *collection = SEQ_query_all_strips(seqbase); + + if (!strip_exists_at_frame(collection, initial_frame)) { /* Search backward for gap_start_frame. */ for (; timeline_frame >= sfra; timeline_frame--) { - if (SEQ_render_evaluate_frame(seqbase, timeline_frame) != 0) { + if (strip_exists_at_frame(collection, timeline_frame)) { break; } } @@ -397,7 +452,7 @@ void seq_time_gap_info_get(const Scene *scene, else { /* Search forward for gap_start_frame. */ for (; timeline_frame <= efra; timeline_frame++) { - if (SEQ_render_evaluate_frame(seqbase, timeline_frame) == 0) { + if (!strip_exists_at_frame(collection, timeline_frame)) { r_gap_info->gap_start_frame = timeline_frame; break; } @@ -405,7 +460,7 @@ void seq_time_gap_info_get(const Scene *scene, } /* Search forward for gap_end_frame. */ for (; timeline_frame <= efra; timeline_frame++) { - if (SEQ_render_evaluate_frame(seqbase, timeline_frame) != 0) { + if (strip_exists_at_frame(collection, timeline_frame)) { const int gap_end_frame = timeline_frame; r_gap_info->gap_length = gap_end_frame - r_gap_info->gap_start_frame; r_gap_info->gap_exists = true; diff --git a/source/blender/sequencer/intern/utils.c b/source/blender/sequencer/intern/utils.c index 6d5332b2b15..cf1d7d66476 100644 --- a/source/blender/sequencer/intern/utils.c +++ b/source/blender/sequencer/intern/utils.c @@ -33,10 +33,7 @@ #include "DNA_scene_types.h" #include "DNA_sequence_types.h" -#include "BLI_listbase.h" -#include "BLI_path_util.h" -#include "BLI_string.h" -#include "BLI_utildefines.h" +#include "BLI_blenlib.h" #include "BKE_image.h" #include "BKE_main.h" @@ -55,21 +52,27 @@ #include "proxy.h" #include "utils.h" -void SEQ_sort(Scene *scene) +/** + * Sort strips in provided seqbase. Effect strips are trailing the list and they are sorted by + * channel position as well. + * This is important for SEQ_time_update_sequence to work properly + * + * \param seqbase: ListBase with strips + */ +void SEQ_sort(ListBase *seqbase) { - /* all strips together per kind, and in order of y location ("machine") */ - ListBase seqbase, effbase; - Editing *ed = SEQ_editing_get(scene, false); - Sequence *seq, *seqt; - - if (ed == NULL) { + if (seqbase == NULL) { return; } - BLI_listbase_clear(&seqbase); + /* all strips together per kind, and in order of y location ("machine") */ + ListBase inputbase, effbase; + Sequence *seq, *seqt; + + BLI_listbase_clear(&inputbase); BLI_listbase_clear(&effbase); - while ((seq = BLI_pophead(ed->seqbasep))) { + while ((seq = BLI_pophead(seqbase))) { if (seq->type & SEQ_TYPE_EFFECT) { seqt = effbase.first; @@ -85,22 +88,22 @@ void SEQ_sort(Scene *scene) } } else { - seqt = seqbase.first; + seqt = inputbase.first; while (seqt) { if (seqt->machine >= seq->machine) { - BLI_insertlinkbefore(&seqbase, seqt, seq); + BLI_insertlinkbefore(&inputbase, seqt, seq); break; } seqt = seqt->next; } if (seqt == NULL) { - BLI_addtail(&seqbase, seq); + BLI_addtail(&inputbase, seq); } } } - BLI_movelisttolist(&seqbase, &effbase); - *(ed->seqbasep) = seqbase; + BLI_movelisttolist(seqbase, &inputbase); + BLI_movelisttolist(seqbase, &effbase); } typedef struct SeqUniqueInfo { @@ -612,3 +615,25 @@ int SEQ_recursive_apply(Sequence *seq, int (*apply_fn)(Sequence *, void *), void return ret; } + +/** + * Ensure, that provided Sequence has unique name. If animation data exists for this Sequence, it + * will be duplicated and mapped onto new name + * + * \param seq: Sequence which name will be ensured to be unique + * \param scene: Scene in which name must be unique + */ +void SEQ_ensure_unique_name(Sequence *seq, Scene *scene) +{ + char name[SEQ_NAME_MAXSTR]; + + BLI_strncpy_utf8(name, seq->name + 2, sizeof(name)); + SEQ_sequence_base_unique_name_recursive(&scene->ed->seqbase, seq); + SEQ_dupe_animdata(scene, name, seq->name + 2); + + if (seq->type == SEQ_TYPE_META) { + LISTBASE_FOREACH (Sequence *, seq_child, &seq->seqbase) { + SEQ_ensure_unique_name(seq_child, scene); + } + } +} diff --git a/source/blender/windowmanager/CMakeLists.txt b/source/blender/windowmanager/CMakeLists.txt index 0f26ec50816..183b22c9791 100644 --- a/source/blender/windowmanager/CMakeLists.txt +++ b/source/blender/windowmanager/CMakeLists.txt @@ -203,6 +203,7 @@ if(WITH_XR_OPENXR) list(APPEND SRC xr/intern/wm_xr.c + xr/intern/wm_xr_actions.c xr/intern/wm_xr_draw.c xr/intern/wm_xr_session.c diff --git a/source/blender/windowmanager/WM_api.h b/source/blender/windowmanager/WM_api.h index 280ee75a50f..edd5b555e2f 100644 --- a/source/blender/windowmanager/WM_api.h +++ b/source/blender/windowmanager/WM_api.h @@ -71,6 +71,11 @@ struct wmTabletData; struct wmNDOFMotionData; #endif +#ifdef WITH_XR_OPENXR +struct wmXrActionState; +struct wmXrPose; +#endif + typedef struct wmGizmo wmGizmo; typedef struct wmGizmoMap wmGizmoMap; typedef struct wmGizmoMapType wmGizmoMapType; @@ -929,7 +934,7 @@ void WM_generic_user_data_free(struct wmGenericUserData *wm_userdata); bool WM_region_use_viewport(struct ScrArea *area, struct ARegion *region); #ifdef WITH_XR_OPENXR -/* wm_xr.c */ +/* wm_xr_session.c */ bool WM_xr_session_exists(const wmXrData *xr); bool WM_xr_session_is_ready(const wmXrData *xr); struct wmXrSessionState *WM_xr_session_state_handle_get(const wmXrData *xr); @@ -939,7 +944,74 @@ bool WM_xr_session_state_viewer_pose_rotation_get(const wmXrData *xr, float r_ro bool WM_xr_session_state_viewer_pose_matrix_info_get(const wmXrData *xr, float r_viewmat[4][4], float *r_focal_len); -#endif +bool WM_xr_session_state_controller_pose_location_get(const wmXrData *xr, + unsigned int subaction_idx, + float r_location[3]); +bool WM_xr_session_state_controller_pose_rotation_get(const wmXrData *xr, + unsigned int subaction_idx, + float r_rotation[4]); + +/* wm_xr_actions.c */ +/* XR action functions to be called pre-XR session start. + * Note: The "destroy" functions can also be called post-session start. */ +bool WM_xr_action_set_create(wmXrData *xr, const char *action_set_name); +void WM_xr_action_set_destroy(wmXrData *xr, const char *action_set_name); +bool WM_xr_action_create(wmXrData *xr, + const char *action_set_name, + const char *action_name, + eXrActionType type, + unsigned int count_subaction_paths, + const char **subaction_paths, + const float *float_threshold, + struct wmOperatorType *ot, + struct IDProperty *op_properties, + eXrOpFlag op_flag); +void WM_xr_action_destroy(wmXrData *xr, const char *action_set_name, const char *action_name); +bool WM_xr_action_space_create(wmXrData *xr, + const char *action_set_name, + const char *action_name, + unsigned int count_subaction_paths, + const char **subaction_paths, + const struct wmXrPose *poses); +void WM_xr_action_space_destroy(wmXrData *xr, + const char *action_set_name, + const char *action_name, + unsigned int count_subaction_paths, + const char **subaction_paths); +bool WM_xr_action_binding_create(wmXrData *xr, + const char *action_set_name, + const char *profile_path, + const char *action_name, + unsigned int count_interaction_paths, + const char **interaction_paths); +void WM_xr_action_binding_destroy(wmXrData *xr, + const char *action_set_name, + const char *profile_path, + const char *action_name, + unsigned int count_interaction_paths, + const char **interaction_paths); + +bool WM_xr_active_action_set_set( + wmXrData *xr, const char *action_set_name); /* If action_set_name is NULL, then + * all action sets will be treated as active. */ +bool WM_xr_controller_pose_action_set(wmXrData *xr, + const char *action_set_name, + const char *action_name); + +/* XR action functions to be called post-XR session start. */ +bool WM_xr_action_state_get(const wmXrData *xr, + const char *action_set_name, + const char *action_name, + const char *subaction_path, + struct wmXrActionState *r_state); +bool WM_xr_haptic_action_apply(wmXrData *xr, + const char *action_set_name, + const char *action_name, + const long long *duration, + const float *frequency, + const float *amplitude); +void WM_xr_haptic_action_stop(wmXrData *xr, const char *action_set_name, const char *action_name); +#endif /* WITH_XR_OPENXR */ #ifdef __cplusplus } diff --git a/source/blender/windowmanager/WM_types.h b/source/blender/windowmanager/WM_types.h index 884ae6952fd..eb17377d0dd 100644 --- a/source/blender/windowmanager/WM_types.h +++ b/source/blender/windowmanager/WM_types.h @@ -686,6 +686,25 @@ typedef struct wmNDOFMotionData { } wmNDOFMotionData; #endif /* WITH_INPUT_NDOF */ +#ifdef WITH_XR_OPENXR +/* Similar to GHOST_XrPose. */ +typedef struct wmXrPose { + float position[3]; + /* Blender convention (w, x, y, z) */ + float orientation_quat[4]; +} wmXrPose; + +typedef struct wmXrActionState { + union { + bool state_boolean; + float state_float; + float state_vector2f[2]; + wmXrPose state_pose; + }; + int type; /* eXrActionType */ +} wmXrActionState; +#endif + /** Timer flags. */ typedef enum { /** Do not attempt to free customdata pointer even if non-NULL. */ diff --git a/source/blender/windowmanager/intern/wm_playanim.c b/source/blender/windowmanager/intern/wm_playanim.c index 85a05b88af7..5300649a0cd 100644 --- a/source/blender/windowmanager/intern/wm_playanim.c +++ b/source/blender/windowmanager/intern/wm_playanim.c @@ -47,9 +47,11 @@ #include "BLI_fileops.h" #include "BLI_listbase.h" #include "BLI_path_util.h" +#include "BLI_rect.h" #include "BLI_string.h" #include "BLI_utildefines.h" +#include "IMB_colormanagement.h" #include "IMB_imbuf.h" #include "IMB_imbuf_types.h" @@ -95,64 +97,85 @@ static AUD_Device *audio_device = NULL; struct PlayState; static void playanim_window_zoom(struct PlayState *ps, const float zoom_offset); +/** + * The current state of the player. + * + * \warning Don't store results of parsing command-line arguments + * in this struct if they need to persist across playing back different + * files as these will be cleared when playing other files (drag & drop). + */ typedef struct PlayState { - /* window and viewport size */ + /** Window and viewport size. */ int win_x, win_y; - /* current zoom level */ + /** Current zoom level. */ float zoom; - /* playback state */ + /** Playback direction (-1, 1). */ short direction; + /** Set the next frame to implement frame stepping (using shortcuts). */ short next_frame; + /** Playback once then wait. */ bool once; - bool turbo; + /** Play forwards/backwards. */ bool pingpong; + /** Disable frame skipping. */ bool noskip; + /** Display current frame over the window. */ bool indicator; + /** Single-frame stepping has been enabled (frame loading and update pending). */ bool sstep; + /** Playback has stopped the image has been displayed. */ bool wait2; + /** Playback stopped state once stop/start variables have been handled. */ bool stopped; + /** + * When disabled the current animation will exit, + * after this either the application exits or a new animation window is opened. + * + * This is used so drag & drop can load new files which setup a newly created animation window. + */ bool go; - /* waiting for images to load */ + /** True when waiting for images to load. */ bool loading; - /* x/y image flip */ + /** X/Y image flip (set via key bindings). */ bool draw_flip[2]; + /** The number of frames to step each update (default to 1, command line argument). */ int fstep; - /* current picture */ + /** Current frame (picture). */ struct PlayAnimPict *picture; - /* set once at the start */ + /** Image size in pixels, set once at the start. */ int ibufx, ibufy; + /** Mono-space font ID. */ int fontid; - /* saves passing args */ - struct ImBuf *curframe_ibuf; - - /* restarts player for file drop */ + /** Restarts player for file drop (drag & drop). */ char dropped_file[FILE_MAX]; + /** Force update when scrubbing with the cursor. */ bool need_frame_update; + /** The current frame calculated by scrubbing the mouse cursor. */ int frame_cursor_x; + + ColorManagedViewSettings view_settings; + ColorManagedDisplaySettings display_settings; } PlayState; /* for debugging */ #if 0 -void print_ps(PlayState *ps) +static void print_ps(PlayState *ps) { printf("ps:\n"); printf(" direction=%d,\n", (int)ps->direction); - printf(" next=%d,\n", ps->next); printf(" once=%d,\n", ps->once); - printf(" turbo=%d,\n", ps->turbo); printf(" pingpong=%d,\n", ps->pingpong); printf(" noskip=%d,\n", ps->noskip); printf(" sstep=%d,\n", ps->sstep); - printf(" pause=%d,\n", ps->pause); printf(" wait2=%d,\n", ps->wait2); printf(" stopped=%d,\n", ps->stopped); printf(" go=%d,\n\n", ps->go); @@ -269,8 +292,89 @@ static struct { .pics_size_in_memory = 0, .memory_limit = 0, }; + +static void frame_cache_add(PlayAnimPict *pic) +{ + pic->frame_cache_node = BLI_genericNodeN(pic); + BLI_addhead(&g_frame_cache.pics, pic->frame_cache_node); + g_frame_cache.pics_len++; + + if (g_frame_cache.memory_limit != 0) { + BLI_assert(pic->size_in_memory == 0); + pic->size_in_memory = IMB_get_size_in_memory(pic->ibuf); + g_frame_cache.pics_size_in_memory += pic->size_in_memory; + } +} + +static void frame_cache_remove(PlayAnimPict *pic) +{ + LinkData *node = pic->frame_cache_node; + IMB_freeImBuf(pic->ibuf); + if (g_frame_cache.memory_limit != 0) { + BLI_assert(pic->size_in_memory != 0); + g_frame_cache.pics_size_in_memory -= pic->size_in_memory; + pic->size_in_memory = 0; + } + pic->ibuf = NULL; + pic->frame_cache_node = NULL; + BLI_freelinkN(&g_frame_cache.pics, node); + g_frame_cache.pics_len--; +} + +/* Don't free the current frame by moving it to the head of the list. */ +static void frame_cache_touch(PlayAnimPict *pic) +{ + BLI_assert(pic->frame_cache_node->data == pic); + BLI_remlink(&g_frame_cache.pics, pic->frame_cache_node); + BLI_addhead(&g_frame_cache.pics, pic->frame_cache_node); +} + +static bool frame_cache_limit_exceeded(void) +{ + return g_frame_cache.memory_limit ? + (g_frame_cache.pics_size_in_memory > g_frame_cache.memory_limit) : + (g_frame_cache.pics_len > PLAY_FRAME_CACHE_MAX); +} + +static void frame_cache_limit_apply(ImBuf *ibuf_keep) +{ + /* Really basic memory conservation scheme. Keep frames in a FIFO queue. */ + LinkData *node = g_frame_cache.pics.last; + while (node && frame_cache_limit_exceeded()) { + PlayAnimPict *pic = node->data; + BLI_assert(pic->frame_cache_node == node); + + node = node->prev; + if (pic->ibuf && pic->ibuf != ibuf_keep) { + frame_cache_remove(pic); + } + } +} + #endif /* USE_FRAME_CACHE_LIMIT */ +static ImBuf *ibuf_from_picture(PlayAnimPict *pic) +{ + ImBuf *ibuf = NULL; + + if (pic->ibuf) { + ibuf = pic->ibuf; + } + else if (pic->anim) { + ibuf = IMB_anim_absolute(pic->anim, pic->frame, IMB_TC_NONE, IMB_PROXY_NONE); + } + else if (pic->mem) { + /* use correct colorspace here */ + ibuf = IMB_ibImageFromMemory(pic->mem, pic->size, pic->IB_flags, NULL, pic->name); + } + else { + /* use correct colorspace here */ + ibuf = IMB_loadiffname(pic->name, pic->IB_flags, NULL); + } + + return ibuf; +} + static PlayAnimPict *playanim_step(PlayAnimPict *playanim, int step) { if (step > 0) { @@ -297,6 +401,151 @@ static int pupdate_time(void) return (ptottime < 0); } +static void *ocio_transform_ibuf(PlayState *ps, + ImBuf *ibuf, + bool *r_glsl_used, + eGPUTextureFormat *r_format, + eGPUDataFormat *r_data, + void **r_buffer_cache_handle) +{ + void *display_buffer; + bool force_fallback = false; + *r_glsl_used = false; + force_fallback |= (ED_draw_imbuf_method(ibuf) != IMAGE_DRAW_METHOD_GLSL); + force_fallback |= (ibuf->dither != 0.0f); + + /* Default */ + *r_format = GPU_RGBA8; + *r_data = GPU_DATA_UBYTE; + + /* Fallback to CPU based color space conversion. */ + if (force_fallback) { + *r_glsl_used = false; + display_buffer = NULL; + } + else if (ibuf->rect_float) { + display_buffer = ibuf->rect_float; + + *r_data = GPU_DATA_FLOAT; + if (ibuf->channels == 4) { + *r_format = GPU_RGBA16F; + } + else if (ibuf->channels == 3) { + /* Alpha is implicitly 1. */ + *r_format = GPU_RGB16F; + } + + if (ibuf->float_colorspace) { + *r_glsl_used = IMB_colormanagement_setup_glsl_draw_from_space(&ps->view_settings, + &ps->display_settings, + ibuf->float_colorspace, + ibuf->dither, + false, + false); + } + else { + *r_glsl_used = IMB_colormanagement_setup_glsl_draw( + &ps->view_settings, &ps->display_settings, ibuf->dither, false); + } + } + else if (ibuf->rect) { + display_buffer = ibuf->rect; + *r_glsl_used = IMB_colormanagement_setup_glsl_draw_from_space(&ps->view_settings, + &ps->display_settings, + ibuf->rect_colorspace, + ibuf->dither, + false, + false); + } + else { + display_buffer = NULL; + } + + /* There is data to be displayed, but GLSL is not initialized + * properly, in this case we fallback to CPU-based display transform. */ + if ((ibuf->rect || ibuf->rect_float) && !*r_glsl_used) { + display_buffer = IMB_display_buffer_acquire( + ibuf, &ps->view_settings, &ps->display_settings, r_buffer_cache_handle); + *r_format = GPU_RGBA8; + *r_data = GPU_DATA_UBYTE; + } + + return display_buffer; +} + +static void draw_display_buffer(PlayState *ps, ImBuf *ibuf) +{ + void *display_buffer; + + /* Format needs to be created prior to any #immBindShader call. + * Do it here because OCIO binds its own shader. */ + eGPUTextureFormat format; + eGPUDataFormat data; + bool glsl_used = false; + GPUVertFormat *imm_format = immVertexFormat(); + uint pos = GPU_vertformat_attr_add(imm_format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); + uint texCoord = GPU_vertformat_attr_add( + imm_format, "texCoord", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); + + void *buffer_cache_handle = NULL; + display_buffer = ocio_transform_ibuf(ps, ibuf, &glsl_used, &format, &data, &buffer_cache_handle); + + GPUTexture *texture = GPU_texture_create_2d("display_buf", ibuf->x, ibuf->y, 1, format, NULL); + GPU_texture_update(texture, data, display_buffer); + GPU_texture_filter_mode(texture, false); + + GPU_texture_bind(texture, 0); + + if (!glsl_used) { + immBindBuiltinProgram(GPU_SHADER_2D_IMAGE_COLOR); + immUniformColor3f(1.0f, 1.0f, 1.0f); + immUniform1i("image", 0); + } + + immBegin(GPU_PRIM_TRI_FAN, 4); + + rctf preview; + rctf canvas; + + BLI_rctf_init(&canvas, 0.0f, 1.0f, 0.0f, 1.0f); + BLI_rctf_init(&preview, 0.0f, 1.0f, 0.0f, 1.0f); + + if (ps->draw_flip[0]) { + SWAP(float, canvas.xmin, canvas.xmax); + } + if (ps->draw_flip[1]) { + SWAP(float, canvas.ymin, canvas.ymax); + } + + immAttr2f(texCoord, canvas.xmin, canvas.ymin); + immVertex2f(pos, preview.xmin, preview.ymin); + + immAttr2f(texCoord, canvas.xmin, canvas.ymax); + immVertex2f(pos, preview.xmin, preview.ymax); + + immAttr2f(texCoord, canvas.xmax, canvas.ymax); + immVertex2f(pos, preview.xmax, preview.ymax); + + immAttr2f(texCoord, canvas.xmax, canvas.ymin); + immVertex2f(pos, preview.xmax, preview.ymin); + + immEnd(); + + GPU_texture_unbind(texture); + GPU_texture_free(texture); + + if (!glsl_used) { + immUnbindProgram(); + } + else { + IMB_colormanagement_finish_glsl_draw(); + } + + if (buffer_cache_handle) { + IMB_display_buffer_release(buffer_cache_handle); + } +} + static void playanim_toscreen( PlayState *ps, PlayAnimPict *picture, struct ImBuf *ibuf, int fontid, int fstep) { @@ -304,13 +553,6 @@ static void playanim_toscreen( printf("%s: no ibuf for picture '%s'\n", __func__, picture ? picture->name : "<NIL>"); return; } - if (ibuf->rect == NULL && ibuf->rect_float) { - IMB_rect_from_float(ibuf); - imb_freerectfloatImBuf(ibuf); - } - if (ibuf->rect == NULL) { - return; - } GHOST_ActivateWindowDrawingContext(g_WS.ghost_window); @@ -340,19 +582,7 @@ static void playanim_toscreen( 8); } - IMMDrawPixelsTexState state = immDrawPixelsTexSetup(GPU_SHADER_2D_IMAGE_COLOR); - - immDrawPixelsTex(&state, - offs_x + (ps->draw_flip[0] ? span_x : 0.0f), - offs_y + (ps->draw_flip[1] ? span_y : 0.0f), - ibuf->x, - ibuf->y, - GPU_RGBA8, - false, - ibuf->rect, - ((ps->draw_flip[0] ? -1.0f : 1.0f)) * (ps->zoom / (float)ps->win_x), - ((ps->draw_flip[1] ? -1.0f : 1.0f)) * (ps->zoom / (float)ps->win_y), - NULL); + draw_display_buffer(ps, ibuf); GPU_blend(GPU_BLEND_NONE); @@ -432,6 +662,14 @@ static void build_pict_list_ex( } } else { + /* Load images into cache until the cache is full, + * this resolves choppiness for images that are slow to load, see: T81751. */ +#ifdef USE_FRAME_CACHE_LIMIT + bool fill_cache = true; +#else + bool fill_cache = false; +#endif + int count = 0; int fp_framenr; @@ -518,22 +756,33 @@ static void build_pict_list_ex( pupdate_time(); - if (ptottime > 1.0) { + const bool display_imbuf = ptottime > 1.0; + + if (display_imbuf || fill_cache) { /* OCIO_TODO: support different input color space */ - struct ImBuf *ibuf; - if (picture->mem) { - ibuf = IMB_ibImageFromMemory( - picture->mem, picture->size, picture->IB_flags, NULL, picture->name); - } - else { - ibuf = IMB_loadiffname(picture->name, picture->IB_flags, NULL); - } + ImBuf *ibuf = ibuf_from_picture(picture); + if (ibuf) { - playanim_toscreen(ps, picture, ibuf, fontid, fstep); - IMB_freeImBuf(ibuf); + if (display_imbuf) { + playanim_toscreen(ps, picture, ibuf, fontid, fstep); + } +#ifdef USE_FRAME_CACHE_LIMIT + if (fill_cache) { + picture->ibuf = ibuf; + frame_cache_add(picture); + fill_cache = !frame_cache_limit_exceeded(); + } + else +#endif + { + IMB_freeImBuf(ibuf); + } + } + + if (display_imbuf) { + pupdate_time(); + ptottime = 0.0; } - pupdate_time(); - ptottime = 0.0; } /* create a new filepath each time */ @@ -641,16 +890,14 @@ static void change_frame(PlayState *ps) static int ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr ps_void) { PlayState *ps = (PlayState *)ps_void; - GHOST_TEventType type = GHOST_GetEventType(evt); - int val; + const GHOST_TEventType type = GHOST_GetEventType(evt); + /* Convert ghost event into value keyboard or mouse. */ + const int val = ELEM(type, GHOST_kEventKeyDown, GHOST_kEventButtonDown); // print_ps(ps); playanim_event_qual_update(); - /* convert ghost event into value keyboard or mouse */ - val = ELEM(type, GHOST_kEventKeyDown, GHOST_kEventButtonDown); - /* first check if we're busy loading files */ if (ps->loading) { switch (type) { @@ -674,8 +921,8 @@ static int ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr ps_void) return 1; } - if (ps->wait2 && ps->stopped) { - ps->stopped = false; + if (ps->wait2 && ps->stopped == false) { + ps->stopped = true; } if (ps->wait2) { @@ -834,9 +1081,9 @@ static int ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr ps_void) case GHOST_kKeyNumpadSlash: if (val) { if (g_WS.qual & WS_QUAL_SHIFT) { - if (ps->curframe_ibuf) { + if (ps->picture && ps->picture->ibuf) { printf(" Name: %s | Speed: %.2f frames/s\n", - ps->curframe_ibuf->name, + ps->picture->ibuf->name, ps->fstep / swaptime); } } @@ -1073,7 +1320,8 @@ static int ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr ps_void) playanim_gl_matrix(); ptottime = 0.0; - playanim_toscreen(ps, ps->picture, ps->curframe_ibuf, ps->fontid, ps->fstep); + playanim_toscreen( + ps, ps->picture, ps->picture ? ps->picture->ibuf : NULL, ps->fontid, ps->fstep); break; } @@ -1150,7 +1398,9 @@ static void playanim_window_zoom(PlayState *ps, const float zoom_offset) GHOST_SetClientSize(g_WS.ghost_window, sizex, sizey); } -/* return path for restart */ +/** + * \return The a path used to restart the animation player or NULL to exit. + */ static char *wm_main_playanim_intern(int argc, const char **argv) { struct ImBuf *ibuf = NULL; @@ -1169,7 +1419,6 @@ static char *wm_main_playanim_intern(int argc, const char **argv) ps.direction = true; ps.next_frame = 1; ps.once = false; - ps.turbo = false; ps.pingpong = false; ps.noskip = false; ps.sstep = false; @@ -1187,6 +1436,11 @@ static char *wm_main_playanim_intern(int argc, const char **argv) ps.fontid = -1; + STRNCPY(ps.display_settings.display_device, + IMB_colormanagement_role_colorspace_name_get(COLOR_ROLE_DEFAULT_BYTE)); + IMB_colormanagement_init_default_view_settings(&ps.view_settings, &ps.display_settings); + + /* Skip the first argument which is assumed to be '-a' (used to launch this player). */ while (argc > 1) { if (argv[1][0] == '-') { switch (argv[1][1]) { @@ -1423,21 +1677,8 @@ static char *wm_main_playanim_intern(int argc, const char **argv) IMB_freeImBuf(ibuf); } #endif - if (ps.picture->ibuf) { - ibuf = ps.picture->ibuf; - } - else if (ps.picture->anim) { - ibuf = IMB_anim_absolute(ps.picture->anim, ps.picture->frame, IMB_TC_NONE, IMB_PROXY_NONE); - } - else if (ps.picture->mem) { - /* use correct colorspace here */ - ibuf = IMB_ibImageFromMemory( - ps.picture->mem, ps.picture->size, ps.picture->IB_flags, NULL, ps.picture->name); - } - else { - /* use correct colorspace here */ - ibuf = IMB_loadiffname(ps.picture->name, ps.picture->IB_flags, NULL); - } + + ibuf = ibuf_from_picture(ps.picture); if (ibuf) { #ifdef USE_IMB_CACHE @@ -1446,50 +1687,12 @@ static char *wm_main_playanim_intern(int argc, const char **argv) #ifdef USE_FRAME_CACHE_LIMIT if (ps.picture->frame_cache_node == NULL) { - ps.picture->frame_cache_node = BLI_genericNodeN(ps.picture); - BLI_addhead(&g_frame_cache.pics, ps.picture->frame_cache_node); - g_frame_cache.pics_len++; - - if (g_frame_cache.memory_limit != 0) { - BLI_assert(ps.picture->size_in_memory == 0); - ps.picture->size_in_memory = IMB_get_size_in_memory(ps.picture->ibuf); - g_frame_cache.pics_size_in_memory += ps.picture->size_in_memory; - } + frame_cache_add(ps.picture); } else { - /* Don't free the current frame by moving it to the head of the list. */ - BLI_assert(ps.picture->frame_cache_node->data == ps.picture); - BLI_remlink(&g_frame_cache.pics, ps.picture->frame_cache_node); - BLI_addhead(&g_frame_cache.pics, ps.picture->frame_cache_node); - } - - /* Really basic memory conservation scheme. Keep frames in a FIFO queue. */ - LinkData *node = g_frame_cache.pics.last; - while (node && (g_frame_cache.memory_limit ? - (g_frame_cache.pics_size_in_memory > g_frame_cache.memory_limit) : - (g_frame_cache.pics_len > PLAY_FRAME_CACHE_MAX))) { - PlayAnimPict *pic = node->data; - BLI_assert(pic->frame_cache_node == node); - - if (pic->ibuf && pic->ibuf != ibuf) { - LinkData *node_tmp; - IMB_freeImBuf(pic->ibuf); - if (g_frame_cache.memory_limit != 0) { - BLI_assert(pic->size_in_memory != 0); - g_frame_cache.pics_size_in_memory -= pic->size_in_memory; - pic->size_in_memory = 0; - } - pic->ibuf = NULL; - pic->frame_cache_node = NULL; - node_tmp = node->prev; - BLI_freelinkN(&g_frame_cache.pics, node); - g_frame_cache.pics_len--; - node = node_tmp; - } - else { - node = node->prev; - } + frame_cache_touch(ps.picture); } + frame_cache_limit_apply(ibuf); #endif /* USE_FRAME_CACHE_LIMIT */ @@ -1538,14 +1741,15 @@ static char *wm_main_playanim_intern(int argc, const char **argv) ps.wait2 = ps.sstep; - if (ps.wait2 == false && ps.stopped == false) { - ps.stopped = true; + if (ps.wait2 == false && ps.stopped) { + ps.stopped = false; } pupdate_time(); if (ps.picture && ps.next_frame) { - /* always at least set one step */ + /* Advance to the next frame, always at least set one step. + * Implement frame-skipping when enabled and playback is not fast enough. */ while (ps.picture) { ps.picture = playanim_step(ps.picture, ps.next_frame); @@ -1558,7 +1762,7 @@ static char *wm_main_playanim_intern(int argc, const char **argv) } } - if (ps.wait2 || ptottime < swaptime || ps.turbo || ps.noskip) { + if (ps.wait2 || ptottime < swaptime || ps.noskip) { break; } ptottime -= swaptime; @@ -1602,6 +1806,7 @@ static char *wm_main_playanim_intern(int argc, const char **argv) #ifdef USE_FRAME_CACHE_LIMIT BLI_freelistN(&g_frame_cache.pics); g_frame_cache.pics_len = 0; + g_frame_cache.pics_size_in_memory = 0; #endif #ifdef WITH_AUDASPACE diff --git a/source/blender/windowmanager/xr/intern/wm_xr.c b/source/blender/windowmanager/xr/intern/wm_xr.c index 439d611b085..2a67c2bee9f 100644 --- a/source/blender/windowmanager/xr/intern/wm_xr.c +++ b/source/blender/windowmanager/xr/intern/wm_xr.c @@ -128,6 +128,11 @@ bool wm_xr_events_handle(wmWindowManager *wm) if (wm->xr.runtime && wm->xr.runtime->context) { GHOST_XrEventsHandle(wm->xr.runtime->context); + /* Process OpenXR action events. */ + if (WM_xr_session_is_ready(&wm->xr)) { + wm_xr_session_actions_update(&wm->xr); + } + /* wm_window_process_events() uses the return value to determine if it can put the main thread * to sleep for some milliseconds. We never want that to happen while the VR session runs on * the main thread. So always return true. */ diff --git a/source/blender/windowmanager/xr/intern/wm_xr_actions.c b/source/blender/windowmanager/xr/intern/wm_xr_actions.c new file mode 100644 index 00000000000..51ed3dcfd3c --- /dev/null +++ b/source/blender/windowmanager/xr/intern/wm_xr_actions.c @@ -0,0 +1,480 @@ +/* + * 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 wm + * + * \name Window-Manager XR Actions + * + * Uses the Ghost-XR API to manage OpenXR actions. + * All functions are designed to be usable by RNA / the Python API. + */ + +#include "BLI_math.h" + +#include "GHOST_C-api.h" + +#include "MEM_guardedalloc.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "wm_xr_intern.h" + +/* -------------------------------------------------------------------- */ +/** \name XR-Action API + * + * API functions for managing OpenXR actions. + * + * \{ */ + +static wmXrActionSet *action_set_create(const char *action_set_name) +{ + wmXrActionSet *action_set = MEM_callocN(sizeof(*action_set), __func__); + action_set->name = MEM_mallocN(strlen(action_set_name) + 1, "XrActionSet_Name"); + strcpy(action_set->name, action_set_name); + + return action_set; +} + +static void action_set_destroy(void *val) +{ + wmXrActionSet *action_set = val; + + MEM_SAFE_FREE(action_set->name); + + MEM_freeN(action_set); +} + +static wmXrActionSet *action_set_find(wmXrData *xr, const char *action_set_name) +{ + return GHOST_XrGetActionSetCustomdata(xr->runtime->context, action_set_name); +} + +static wmXrAction *action_create(const char *action_name, + eXrActionType type, + unsigned int count_subaction_paths, + const char **subaction_paths, + const float *float_threshold, + wmOperatorType *ot, + IDProperty *op_properties, + eXrOpFlag op_flag) +{ + wmXrAction *action = MEM_callocN(sizeof(*action), __func__); + action->name = MEM_mallocN(strlen(action_name) + 1, "XrAction_Name"); + strcpy(action->name, action_name); + action->type = type; + + const unsigned int count = count_subaction_paths; + action->count_subaction_paths = count; + + action->subaction_paths = MEM_mallocN(sizeof(*action->subaction_paths) * count, + "XrAction_SubactionPaths"); + for (unsigned int i = 0; i < count; ++i) { + action->subaction_paths[i] = MEM_mallocN(strlen(subaction_paths[i]) + 1, + "XrAction_SubactionPath"); + strcpy(action->subaction_paths[i], subaction_paths[i]); + } + + size_t size; + switch (type) { + case XR_BOOLEAN_INPUT: + size = sizeof(bool); + break; + case XR_FLOAT_INPUT: + size = sizeof(float); + break; + case XR_VECTOR2F_INPUT: + size = sizeof(float) * 2; + break; + case XR_POSE_INPUT: + size = sizeof(GHOST_XrPose); + break; + case XR_VIBRATION_OUTPUT: + return action; + } + action->states = MEM_calloc_arrayN(count, size, "XrAction_States"); + action->states_prev = MEM_calloc_arrayN(count, size, "XrAction_StatesPrev"); + + if (float_threshold) { + BLI_assert(type == XR_FLOAT_INPUT || type == XR_VECTOR2F_INPUT); + action->float_threshold = *float_threshold; + CLAMP(action->float_threshold, 0.0f, 1.0f); + } + + action->ot = ot; + action->op_properties = op_properties; + action->op_flag = op_flag; + + return action; +} + +static void action_destroy(void *val) +{ + wmXrAction *action = val; + + MEM_SAFE_FREE(action->name); + + const unsigned int count = action->count_subaction_paths; + char **subaction_paths = action->subaction_paths; + if (subaction_paths) { + for (unsigned int i = 0; i < count; ++i) { + MEM_SAFE_FREE(subaction_paths[i]); + } + MEM_freeN(subaction_paths); + } + + MEM_SAFE_FREE(action->states); + MEM_SAFE_FREE(action->states_prev); + + MEM_freeN(action); +} + +static wmXrAction *action_find(wmXrData *xr, const char *action_set_name, const char *action_name) +{ + return GHOST_XrGetActionCustomdata(xr->runtime->context, action_set_name, action_name); +} + +bool WM_xr_action_set_create(wmXrData *xr, const char *action_set_name) +{ + if (action_set_find(xr, action_set_name)) { + return false; + } + + wmXrActionSet *action_set = action_set_create(action_set_name); + + GHOST_XrActionSetInfo info = { + .name = action_set_name, + .customdata_free_fn = action_set_destroy, + .customdata = action_set, + }; + + if (!GHOST_XrCreateActionSet(xr->runtime->context, &info)) { + return false; + } + + return true; +} + +void WM_xr_action_set_destroy(wmXrData *xr, const char *action_set_name) +{ + wmXrActionSet *action_set = action_set_find(xr, action_set_name); + if (!action_set) { + return; + } + + wmXrSessionState *session_state = &xr->runtime->session_state; + + if (action_set == session_state->active_action_set) { + if (action_set->controller_pose_action) { + wm_xr_session_controller_data_clear(session_state); + action_set->controller_pose_action = NULL; + } + if (action_set->active_modal_action) { + action_set->active_modal_action = NULL; + } + session_state->active_action_set = NULL; + } + + GHOST_XrDestroyActionSet(xr->runtime->context, action_set_name); +} + +bool WM_xr_action_create(wmXrData *xr, + const char *action_set_name, + const char *action_name, + eXrActionType type, + unsigned int count_subaction_paths, + const char **subaction_paths, + const float *float_threshold, + wmOperatorType *ot, + IDProperty *op_properties, + eXrOpFlag op_flag) +{ + if (action_find(xr, action_set_name, action_name)) { + return false; + } + + wmXrAction *action = action_create(action_name, + type, + count_subaction_paths, + subaction_paths, + float_threshold, + ot, + op_properties, + op_flag); + + GHOST_XrActionInfo info = { + .name = action_name, + .count_subaction_paths = count_subaction_paths, + .subaction_paths = subaction_paths, + .states = action->states, + .customdata_free_fn = action_destroy, + .customdata = action, + }; + + switch (type) { + case XR_BOOLEAN_INPUT: + info.type = GHOST_kXrActionTypeBooleanInput; + break; + case XR_FLOAT_INPUT: + info.type = GHOST_kXrActionTypeFloatInput; + break; + case XR_VECTOR2F_INPUT: + info.type = GHOST_kXrActionTypeVector2fInput; + break; + case XR_POSE_INPUT: + info.type = GHOST_kXrActionTypePoseInput; + break; + case XR_VIBRATION_OUTPUT: + info.type = GHOST_kXrActionTypeVibrationOutput; + break; + } + + if (!GHOST_XrCreateActions(xr->runtime->context, action_set_name, 1, &info)) { + return false; + } + + return true; +} + +void WM_xr_action_destroy(wmXrData *xr, const char *action_set_name, const char *action_name) +{ + wmXrActionSet *action_set = action_set_find(xr, action_set_name); + if (!action_set) { + return; + } + + if (action_set->controller_pose_action && + STREQ(action_set->controller_pose_action->name, action_name)) { + if (action_set == xr->runtime->session_state.active_action_set) { + wm_xr_session_controller_data_clear(&xr->runtime->session_state); + } + action_set->controller_pose_action = NULL; + } + if (action_set->active_modal_action && + STREQ(action_set->active_modal_action->name, action_name)) { + action_set->active_modal_action = NULL; + } + + wmXrAction *action = action_find(xr, action_set_name, action_name); + if (!action) { + return; + } +} + +bool WM_xr_action_space_create(wmXrData *xr, + const char *action_set_name, + const char *action_name, + unsigned int count_subaction_paths, + const char **subaction_paths, + const wmXrPose *poses) +{ + GHOST_XrActionSpaceInfo info = { + .action_name = action_name, + .count_subaction_paths = count_subaction_paths, + .subaction_paths = subaction_paths, + }; + + GHOST_XrPose *ghost_poses = MEM_malloc_arrayN( + count_subaction_paths, sizeof(*ghost_poses), __func__); + for (unsigned int i = 0; i < count_subaction_paths; ++i) { + const wmXrPose *pose = &poses[i]; + GHOST_XrPose *ghost_pose = &ghost_poses[i]; + copy_v3_v3(ghost_pose->position, pose->position); + copy_qt_qt(ghost_pose->orientation_quat, pose->orientation_quat); + } + info.poses = ghost_poses; + + bool ret = GHOST_XrCreateActionSpaces(xr->runtime->context, action_set_name, 1, &info) ? true : + false; + MEM_freeN(ghost_poses); + return ret; +} + +void WM_xr_action_space_destroy(wmXrData *xr, + const char *action_set_name, + const char *action_name, + unsigned int count_subaction_paths, + const char **subaction_paths) +{ + GHOST_XrActionSpaceInfo info = { + .action_name = action_name, + .count_subaction_paths = count_subaction_paths, + .subaction_paths = subaction_paths, + }; + + GHOST_XrDestroyActionSpaces(xr->runtime->context, action_set_name, 1, &info); +} + +bool WM_xr_action_binding_create(wmXrData *xr, + const char *action_set_name, + const char *profile_path, + const char *action_name, + unsigned int count_interaction_paths, + const char **interaction_paths) +{ + GHOST_XrActionBindingInfo binding_info = { + .action_name = action_name, + .count_interaction_paths = count_interaction_paths, + .interaction_paths = interaction_paths, + }; + + GHOST_XrActionProfileInfo profile_info = { + .profile_path = profile_path, + .count_bindings = 1, + .bindings = &binding_info, + }; + + return GHOST_XrCreateActionBindings(xr->runtime->context, action_set_name, 1, &profile_info); +} + +void WM_xr_action_binding_destroy(wmXrData *xr, + const char *action_set_name, + const char *profile_path, + const char *action_name, + unsigned int count_interaction_paths, + const char **interaction_paths) +{ + GHOST_XrActionBindingInfo binding_info = { + .action_name = action_name, + .count_interaction_paths = count_interaction_paths, + .interaction_paths = interaction_paths, + }; + + GHOST_XrActionProfileInfo profile_info = { + .profile_path = profile_path, + .count_bindings = 1, + .bindings = &binding_info, + }; + + GHOST_XrDestroyActionBindings(xr->runtime->context, action_set_name, 1, &profile_info); +} + +bool WM_xr_active_action_set_set(wmXrData *xr, const char *action_set_name) +{ + wmXrActionSet *action_set = action_set_find(xr, action_set_name); + if (!action_set) { + return false; + } + + { + /* Unset active modal action (if any). */ + wmXrActionSet *active_action_set = xr->runtime->session_state.active_action_set; + if (active_action_set) { + wmXrAction *active_modal_action = active_action_set->active_modal_action; + if (active_modal_action) { + if (active_modal_action->active_modal_path) { + active_modal_action->active_modal_path = NULL; + } + active_action_set->active_modal_action = NULL; + } + } + } + + xr->runtime->session_state.active_action_set = action_set; + + if (action_set->controller_pose_action) { + wm_xr_session_controller_data_populate(action_set->controller_pose_action, xr); + } + + return true; +} + +bool WM_xr_controller_pose_action_set(wmXrData *xr, + const char *action_set_name, + const char *action_name) +{ + wmXrActionSet *action_set = action_set_find(xr, action_set_name); + if (!action_set) { + return false; + } + + wmXrAction *action = action_find(xr, action_set_name, action_name); + if (!action) { + return false; + } + + action_set->controller_pose_action = action; + + if (action_set == xr->runtime->session_state.active_action_set) { + wm_xr_session_controller_data_populate(action, xr); + } + + return true; +} + +bool WM_xr_action_state_get(const wmXrData *xr, + const char *action_set_name, + const char *action_name, + const char *subaction_path, + wmXrActionState *r_state) +{ + const wmXrAction *action = action_find((wmXrData *)xr, action_set_name, action_name); + if (!action) { + return false; + } + + BLI_assert(action->type == (eXrActionType)r_state->type); + + /* Find the action state corresponding to the subaction path. */ + for (unsigned int i = 0; i < action->count_subaction_paths; ++i) { + if (STREQ(subaction_path, action->subaction_paths[i])) { + switch ((eXrActionType)r_state->type) { + case XR_BOOLEAN_INPUT: + r_state->state_boolean = ((bool *)action->states)[i]; + break; + case XR_FLOAT_INPUT: + r_state->state_float = ((float *)action->states)[i]; + break; + case XR_VECTOR2F_INPUT: + copy_v2_v2(r_state->state_vector2f, ((float(*)[2])action->states)[i]); + break; + case XR_POSE_INPUT: { + const GHOST_XrPose *pose = &((GHOST_XrPose *)action->states)[i]; + copy_v3_v3(r_state->state_pose.position, pose->position); + copy_qt_qt(r_state->state_pose.orientation_quat, pose->orientation_quat); + break; + } + case XR_VIBRATION_OUTPUT: + BLI_assert_unreachable(); + break; + } + return true; + } + } + + return false; +} + +bool WM_xr_haptic_action_apply(wmXrData *xr, + const char *action_set_name, + const char *action_name, + const long long *duration, + const float *frequency, + const float *amplitude) +{ + return GHOST_XrApplyHapticAction( + xr->runtime->context, action_set_name, action_name, duration, frequency, amplitude) ? + true : + false; +} + +void WM_xr_haptic_action_stop(wmXrData *xr, const char *action_set_name, const char *action_name) +{ + GHOST_XrStopHapticAction(xr->runtime->context, action_set_name, action_name); +} + +/** \} */ /* XR-Action API */ diff --git a/source/blender/windowmanager/xr/intern/wm_xr_draw.c b/source/blender/windowmanager/xr/intern/wm_xr_draw.c index cc4a7e41e82..1f722855696 100644 --- a/source/blender/windowmanager/xr/intern/wm_xr_draw.c +++ b/source/blender/windowmanager/xr/intern/wm_xr_draw.c @@ -45,6 +45,12 @@ void wm_xr_pose_to_viewmat(const GHOST_XrPose *pose, float r_viewmat[4][4]) translate_m4(r_viewmat, -pose->position[0], -pose->position[1], -pose->position[2]); } +void wm_xr_controller_pose_to_mat(const GHOST_XrPose *pose, float r_mat[4][4]) +{ + quat_to_mat4(r_mat, pose->orientation_quat); + copy_v3_v3(r_mat[3], pose->position); +} + static void wm_xr_draw_matrices_create(const wmXrDrawData *draw_data, const GHOST_XrDrawViewInfo *draw_view, const XrSessionSettings *session_settings, diff --git a/source/blender/windowmanager/xr/intern/wm_xr_intern.h b/source/blender/windowmanager/xr/intern/wm_xr_intern.h index 25e3da3ffb4..9bf63be61dd 100644 --- a/source/blender/windowmanager/xr/intern/wm_xr_intern.h +++ b/source/blender/windowmanager/xr/intern/wm_xr_intern.h @@ -24,6 +24,21 @@ #include "wm_xr.h" +struct wmXrActionSet; + +typedef struct wmXrControllerData { + /** OpenXR path identifier. Length is dependent on OpenXR's XR_MAX_PATH_LENGTH (256). + This subaction path will later be combined with a component path, and that combined path should + also have a max of XR_MAX_PATH_LENGTH (e.g. subaction_path = /user/hand/left, component_path = + /input/trigger/value, interaction_path = /user/hand/left/input/trigger/value). + */ + char subaction_path[64]; + /** Last known controller pose (in world space) stored for queries. */ + GHOST_XrPose pose; + /** The last known controller matrix, calculated from above's controller pose. */ + float mat[4][4]; +} wmXrControllerData; + typedef struct wmXrSessionState { bool is_started; @@ -39,11 +54,23 @@ typedef struct wmXrSessionState { Object *prev_base_pose_object; /** Copy of XrSessionSettings.flag created on the last draw call, stored to detect changes. */ int prev_settings_flag; + /** Copy of wmXrDrawData.base_pose. */ + GHOST_XrPose prev_base_pose; + /** Copy of GHOST_XrDrawViewInfo.local_pose. */ + GHOST_XrPose prev_local_pose; /** Copy of wmXrDrawData.eye_position_ofs. */ float prev_eye_position_ofs[3]; bool force_reset_to_base_pose; bool is_view_data_set; + + /** Last known controller data. */ + wmXrControllerData controllers[2]; + + /** The currently active action set that will be updated on calls to + * wm_xr_session_actions_update(). If NULL, all action sets will be treated as active and + * updated. */ + struct wmXrActionSet *active_action_set; } wmXrSessionState; typedef struct wmXrRuntimeData { @@ -79,6 +106,40 @@ typedef struct wmXrDrawData { float eye_position_ofs[3]; /* Local/view space. */ } wmXrDrawData; +typedef struct wmXrAction { + char *name; + eXrActionType type; + unsigned int count_subaction_paths; + char **subaction_paths; + /** States for each subaction path. */ + void *states; + /** Previous states, stored to determine XR events. */ + void *states_prev; + + /** Input threshold for float/vector2f actions. */ + float float_threshold; + + /** The currently active subaction path (if any) for modal actions. */ + char **active_modal_path; + + /** Operator to be called on XR events. */ + struct wmOperatorType *ot; + IDProperty *op_properties; + eXrOpFlag op_flag; +} wmXrAction; + +typedef struct wmXrActionSet { + char *name; + + /** The XR pose action that determines the controller + * transforms. This is usually identified by the OpenXR path "/grip/pose" or "/aim/pose", + * although it could differ depending on the specification and hardware. */ + wmXrAction *controller_pose_action; + + /** The currently active modal action (if any). */ + wmXrAction *active_modal_action; +} wmXrActionSet; + wmXrRuntimeData *wm_xr_runtime_data_create(void); void wm_xr_runtime_data_free(wmXrRuntimeData **runtime); @@ -95,5 +156,12 @@ bool wm_xr_session_surface_offscreen_ensure(wmXrSurfaceData *surface_data, void *wm_xr_session_gpu_binding_context_create(void); void wm_xr_session_gpu_binding_context_destroy(GHOST_ContextHandle context); +void wm_xr_session_actions_init(wmXrData *xr); +void wm_xr_session_actions_update(wmXrData *xr); +void wm_xr_session_controller_data_populate(const wmXrAction *controller_pose_action, + wmXrData *xr); +void wm_xr_session_controller_data_clear(wmXrSessionState *state); + void wm_xr_pose_to_viewmat(const GHOST_XrPose *pose, float r_viewmat[4][4]); +void wm_xr_controller_pose_to_mat(const GHOST_XrPose *pose, float r_mat[4][4]); void wm_xr_draw_view(const GHOST_XrDrawViewInfo *draw_view, void *customdata); diff --git a/source/blender/windowmanager/xr/intern/wm_xr_session.c b/source/blender/windowmanager/xr/intern/wm_xr_session.c index b9ef40e3398..1ddbe228e05 100644 --- a/source/blender/windowmanager/xr/intern/wm_xr_session.c +++ b/source/blender/windowmanager/xr/intern/wm_xr_session.c @@ -18,7 +18,9 @@ * \ingroup wm */ +#include "BKE_callbacks.h" #include "BKE_context.h" +#include "BKE_global.h" #include "BKE_main.h" #include "BKE_scene.h" @@ -49,11 +51,24 @@ static CLG_LogRef LOG = {"wm.xr"}; /* -------------------------------------------------------------------- */ +static void wm_xr_session_create_cb(void) +{ + Main *bmain = G_MAIN; + wmWindowManager *wm = bmain->wm.first; + wmXrData *xr_data = &wm->xr; + + /* Get action set data from Python. */ + BKE_callback_exec_null(bmain, BKE_CB_EVT_XR_SESSION_START_PRE); + + wm_xr_session_actions_init(xr_data); +} + static void wm_xr_session_exit_cb(void *customdata) { wmXrData *xr_data = customdata; xr_data->runtime->session_state.is_started = false; + if (xr_data->runtime->exit_fn) { xr_data->runtime->exit_fn(xr_data); } @@ -65,6 +80,10 @@ static void wm_xr_session_exit_cb(void *customdata) static void wm_xr_session_begin_info_create(wmXrData *xr_data, GHOST_XrSessionBeginInfo *r_begin_info) { + /* Callback for when the session is created. This is needed to create and bind OpenXR actions + * after the session is created but before it is started. */ + r_begin_info->create_fn = wm_xr_session_create_cb; + /* WM-XR exit function, does some own stuff and calls callback passed to wm_xr_session_toggle(), * to allow external code to execute its own session-exit logic. */ r_begin_info->exit_fn = wm_xr_session_exit_cb; @@ -289,6 +308,7 @@ void wm_xr_session_draw_data_update(const wmXrSessionState *state, /** * Update information that is only stored for external state queries. E.g. for Python API to * request the current (as in, last known) viewer pose. + * Controller data and action sets will be updated separately via wm_xr_session_actions_update(). */ void wm_xr_session_state_update(const XrSessionSettings *settings, const wmXrDrawData *draw_data, @@ -322,6 +342,8 @@ void wm_xr_session_state_update(const XrSessionSettings *settings, DEFAULT_SENSOR_WIDTH); copy_v3_v3(state->prev_eye_position_ofs, draw_data->eye_position_ofs); + memcpy(&state->prev_base_pose, &draw_data->base_pose, sizeof(state->prev_base_pose)); + memcpy(&state->prev_local_pose, &draw_view->local_pose, sizeof(state->prev_local_pose)); state->prev_settings_flag = settings->flag; state->prev_base_pose_type = settings->base_pose_type; state->prev_base_pose_object = settings->base_pose_object; @@ -373,6 +395,132 @@ bool WM_xr_session_state_viewer_pose_matrix_info_get(const wmXrData *xr, return true; } +bool WM_xr_session_state_controller_pose_location_get(const wmXrData *xr, + unsigned int subaction_idx, + float r_location[3]) +{ + if (!WM_xr_session_is_ready(xr) || !xr->runtime->session_state.is_view_data_set || + subaction_idx >= ARRAY_SIZE(xr->runtime->session_state.controllers)) { + zero_v3(r_location); + return false; + } + + copy_v3_v3(r_location, xr->runtime->session_state.controllers[subaction_idx].pose.position); + return true; +} + +bool WM_xr_session_state_controller_pose_rotation_get(const wmXrData *xr, + unsigned int subaction_idx, + float r_rotation[4]) +{ + if (!WM_xr_session_is_ready(xr) || !xr->runtime->session_state.is_view_data_set || + subaction_idx >= ARRAY_SIZE(xr->runtime->session_state.controllers)) { + unit_qt(r_rotation); + return false; + } + + copy_v4_v4(r_rotation, + xr->runtime->session_state.controllers[subaction_idx].pose.orientation_quat); + return true; +} + +/* -------------------------------------------------------------------- */ +/** \name XR-Session Actions + * + * XR action processing and event dispatching. + * + * \{ */ + +void wm_xr_session_actions_init(wmXrData *xr) +{ + if (!xr->runtime) { + return; + } + + GHOST_XrAttachActionSets(xr->runtime->context); +} + +static void wm_xr_session_controller_mats_update(const XrSessionSettings *settings, + const wmXrAction *controller_pose_action, + wmXrSessionState *state) +{ + const unsigned int count = (unsigned int)min_ii( + (int)controller_pose_action->count_subaction_paths, (int)ARRAY_SIZE(state->controllers)); + + float view_ofs[3]; + float base_inv[4][4]; + float tmp[4][4]; + + zero_v3(view_ofs); + if ((settings->flag & XR_SESSION_USE_POSITION_TRACKING) == 0) { + add_v3_v3(view_ofs, state->prev_local_pose.position); + } + + wm_xr_pose_to_viewmat(&state->prev_base_pose, base_inv); + invert_m4(base_inv); + + for (unsigned int i = 0; i < count; ++i) { + wmXrControllerData *controller = &state->controllers[i]; + + /* Calculate controller matrix in world space. */ + wm_xr_controller_pose_to_mat(&((GHOST_XrPose *)controller_pose_action->states)[i], tmp); + + /* Apply eye position and base pose offsets. */ + sub_v3_v3(tmp[3], view_ofs); + mul_m4_m4m4(controller->mat, base_inv, tmp); + + /* Save final pose. */ + mat4_to_loc_quat( + controller->pose.position, controller->pose.orientation_quat, controller->mat); + } +} + +void wm_xr_session_actions_update(wmXrData *xr) +{ + if (!xr->runtime) { + return; + } + + GHOST_XrContextHandle xr_context = xr->runtime->context; + wmXrSessionState *state = &xr->runtime->session_state; + wmXrActionSet *active_action_set = state->active_action_set; + + int ret = GHOST_XrSyncActions(xr_context, active_action_set ? active_action_set->name : NULL); + if (!ret) { + return; + } + + /* Only update controller mats for active action set. */ + if (active_action_set) { + if (active_action_set->controller_pose_action) { + wm_xr_session_controller_mats_update( + &xr->session_settings, active_action_set->controller_pose_action, state); + } + } +} + +void wm_xr_session_controller_data_populate(const wmXrAction *controller_pose_action, wmXrData *xr) +{ + wmXrSessionState *state = &xr->runtime->session_state; + + const unsigned int count = (unsigned int)min_ii( + (int)ARRAY_SIZE(state->controllers), (int)controller_pose_action->count_subaction_paths); + + for (unsigned int i = 0; i < count; ++i) { + wmXrControllerData *c = &state->controllers[i]; + strcpy(c->subaction_path, controller_pose_action->subaction_paths[i]); + memset(&c->pose, 0, sizeof(c->pose)); + zero_m4(c->mat); + } +} + +void wm_xr_session_controller_data_clear(wmXrSessionState *state) +{ + memset(state->controllers, 0, sizeof(state->controllers)); +} + +/** \} */ /* XR-Session Actions */ + /* -------------------------------------------------------------------- */ /** \name XR-Session Surface * diff --git a/source/creator/creator.c b/source/creator/creator.c index b40718d1f7c..51efadf5e56 100644 --- a/source/creator/creator.c +++ b/source/creator/creator.c @@ -132,7 +132,6 @@ struct ApplicationState app_state = { /** \name Application Level Callbacks * * Initialize callbacks for the modules that need them. - * * \{ */ static void callback_mem_error(const char *errorStr) diff --git a/source/creator/creator_args.c b/source/creator/creator_args.c index 9c7b7dc3f34..36fdaef507b 100644 --- a/source/creator/creator_args.c +++ b/source/creator/creator_args.c @@ -1195,15 +1195,17 @@ static const char arg_handle_playback_mode_doc[] = "\t\tZero disables (clamping to a fixed number of frames instead)."; static int arg_handle_playback_mode(int argc, const char **argv, void *UNUSED(data)) { - /* not if -b was given first */ + /* Ignore the animation player if `-b` was given first. */ if (G.background == 0) { # ifdef WITH_FFMPEG /* Setup FFmpeg with current debug flags. */ IMB_ffmpeg_init(); # endif - WM_main_playanim(argc, argv); /* not the same argc and argv as before */ - exit(0); /* 2.4x didn't do this */ + /* This function knows to skip this argument ('-a'). */ + WM_main_playanim(argc, argv); + + exit(0); } return -2; diff --git a/source/tools b/source/tools -Subproject 2afbb8ec472cac5102eb239f57b006f8c938768 +Subproject f99d29ae3e6ad44d45d79309454c45f8088781a diff --git a/tests/python/bl_blendfile_library_overrides.py b/tests/python/bl_blendfile_library_overrides.py index e2c6432b380..48625a1ecdb 100644 --- a/tests/python/bl_blendfile_library_overrides.py +++ b/tests/python/bl_blendfile_library_overrides.py @@ -75,7 +75,7 @@ class TestLibraryOverrides(TestHelper, unittest.TestCase): assert(override_operation.operation == 'REPLACE') # Setting location.y overridded all elements in the location array. -1 is a wildcard. assert(override_operation.subitem_local_index == -1) - + def test_link_permissive(self): """ Linked assets with a permissive template. diff --git a/tests/python/bl_pyapi_idprop.py b/tests/python/bl_pyapi_idprop.py index 3d0cbd2a7bb..1e570bf9a7f 100644 --- a/tests/python/bl_pyapi_idprop.py +++ b/tests/python/bl_pyapi_idprop.py @@ -2,6 +2,7 @@ # ./blender.bin --background -noaudio --python tests/python/bl_pyapi_idprop.py -- --verbose import bpy +import idprop import unittest import numpy as np from array import array @@ -15,12 +16,12 @@ class TestHelper: def setUp(self): self._id = bpy.context.scene - assert(len(self._id.keys()) == 0 or self._id.keys() == ["cycles"]) + self._id.pop("cycles", None) + assert(len(self._id.keys()) == 0) def tearDown(self): for key in list(self._id.keys()): - if key != "cycles": - del self._id[key] + del self._id[key] def assertAlmostEqualSeq(self, list1, list2): self.assertEqual(len(list1), len(list2)) @@ -139,6 +140,51 @@ class TestIdPropertyCreation(TestHelper, unittest.TestCase): with self.assertRaises(TypeError): self.id["a"] = self +class TestIdPropertyGroupView(TestHelper, unittest.TestCase): + + def test_type(self): + self.assertEqual(type(self.id.keys()), idprop.types.IDPropertyGroupViewKeys) + self.assertEqual(type(self.id.values()), idprop.types.IDPropertyGroupViewValues) + self.assertEqual(type(self.id.items()), idprop.types.IDPropertyGroupViewItems) + + self.assertEqual(type(iter(self.id.keys())), idprop.types.IDPropertyGroupIterKeys) + self.assertEqual(type(iter(self.id.values())), idprop.types.IDPropertyGroupIterValues) + self.assertEqual(type(iter(self.id.items())), idprop.types.IDPropertyGroupIterItems) + + def test_basic(self): + text = ["A", "B", "C"] + for i, ch in enumerate(text): + self.id[ch] = i + self.assertEqual(len(self.id.keys()), len(text)) + self.assertEqual(list(self.id.keys()), text) + self.assertEqual(list(reversed(self.id.keys())), list(reversed(text))) + + self.assertEqual(len(self.id.values()), len(text)) + self.assertEqual(list(self.id.values()), list(range(len(text)))) + self.assertEqual(list(reversed(self.id.values())), list(reversed(range(len(text))))) + + self.assertEqual(len(self.id.items()), len(text)) + self.assertEqual(list(self.id.items()), [(k, v) for v, k in enumerate(text)]) + self.assertEqual(list(reversed(self.id.items())), list(reversed([(k, v) for v, k in enumerate(text)]))) + + def test_contains(self): + # Check `idprop.types.IDPropertyGroupView{Keys/Values/Items}.__contains__` + text = ["A", "B", "C"] + for i, ch in enumerate(text): + self.id[ch] = i + + self.assertIn("A", self.id) + self.assertNotIn("D", self.id) + + self.assertIn("A", self.id.keys()) + self.assertNotIn("D", self.id.keys()) + + self.assertIn(2, self.id.values()) + self.assertNotIn(3, self.id.values()) + + self.assertIn(("A", 0), self.id.items()) + self.assertNotIn(("D", 3), self.id.items()) + class TestBufferProtocol(TestHelper, unittest.TestCase): diff --git a/tests/python/bl_pyapi_mathutils.py b/tests/python/bl_pyapi_mathutils.py index 9dfc6c159cc..914b1689a5c 100644 --- a/tests/python/bl_pyapi_mathutils.py +++ b/tests/python/bl_pyapi_mathutils.py @@ -2,7 +2,7 @@ # ./blender.bin --background -noaudio --python tests/python/bl_pyapi_mathutils.py -- --verbose import unittest -from mathutils import Matrix, Vector, Quaternion +from mathutils import Matrix, Vector, Quaternion, Euler from mathutils import kdtree, geometry import math @@ -233,6 +233,27 @@ class MatrixTesting(unittest.TestCase): self.assertEqual(mat @ mat, prod_mat) + def test_loc_rot_scale(self): + euler = Euler((math.radians(90), 0, math.radians(90)), 'ZYX') + expected = Matrix(((0, -5, 0, 1), + (0, 0, -6, 2), + (4, 0, 0, 3), + (0, 0, 0, 1))) + + result = Matrix.LocRotScale((1, 2, 3), euler, (4, 5, 6)) + self.assertAlmostEqualMatrix(result, expected, 4) + + result = Matrix.LocRotScale((1, 2, 3), euler.to_quaternion(), (4, 5, 6)) + self.assertAlmostEqualMatrix(result, expected, 4) + + result = Matrix.LocRotScale((1, 2, 3), euler.to_matrix(), (4, 5, 6)) + self.assertAlmostEqualMatrix(result, expected, 4) + + def assertAlmostEqualMatrix(self, first, second, size, *, places=6, msg=None, delta=None): + for i in range(size): + for j in range(size): + self.assertAlmostEqual(first[i][j], second[i][j], places=places, msg=msg, delta=delta) + class VectorTesting(unittest.TestCase): diff --git a/tests/python/compositor_render_tests.py b/tests/python/compositor_render_tests.py index 6a026ae88d2..fbdae595c51 100644 --- a/tests/python/compositor_render_tests.py +++ b/tests/python/compositor_render_tests.py @@ -61,4 +61,3 @@ def main(): if not inside_blender and __name__ == "__main__": main() - diff --git a/tests/python/operators.py b/tests/python/operators.py index 4c863edfbdb..c209b01c20c 100644 --- a/tests/python/operators.py +++ b/tests/python/operators.py @@ -316,13 +316,13 @@ def main(): # normal case MeshTest("CubeFaceUnsubdivide", "testCubeUnsubdivide", "expectedCubeUnsubdivide", [OperatorSpecEditMode("unsubdivide", {}, "FACE", {i for i in range(6)})]), - + # T87259 - test cases MeshTest("CubeEdgeUnsubdivide", "testCubeEdgeUnsubdivide", "expectedCubeEdgeUnsubdivide", [OperatorSpecEditMode("unsubdivide", {}, "EDGE", {i for i in range(6)})]), MeshTest("UVSphereUnsubdivide", "testUVSphereUnsubdivide", "expectedUVSphereUnsubdivide", [OperatorSpecEditMode("unsubdivide", {'iterations': 9}, "FACE", {i for i in range(512)})]), - + # vert connect path # Tip: It works only if there is an already existing face or more than 2 vertices. MeshTest("CubeVertConnectPath", "testCubeVertConnectPath", "expectedCubeVertConnectPath", |